v4.19.13 snapshot.
diff --git a/drivers/media/usb/Kconfig b/drivers/media/usb/Kconfig
new file mode 100644
index 0000000..b24e753
--- /dev/null
+++ b/drivers/media/usb/Kconfig
@@ -0,0 +1,70 @@
+if USB && MEDIA_SUPPORT
+
+menuconfig MEDIA_USB_SUPPORT
+	bool "Media USB Adapters"
+	help
+	  Enable media drivers for USB bus.
+	  If you have such devices, say Y.
+
+if MEDIA_USB_SUPPORT
+
+if MEDIA_CAMERA_SUPPORT
+	comment "Webcam devices"
+source "drivers/media/usb/uvc/Kconfig"
+source "drivers/media/usb/gspca/Kconfig"
+source "drivers/media/usb/pwc/Kconfig"
+source "drivers/media/usb/cpia2/Kconfig"
+source "drivers/media/usb/zr364xx/Kconfig"
+source "drivers/media/usb/stkwebcam/Kconfig"
+source "drivers/media/usb/s2255/Kconfig"
+source "drivers/media/usb/usbtv/Kconfig"
+endif
+
+if MEDIA_ANALOG_TV_SUPPORT
+	comment "Analog TV USB devices"
+source "drivers/media/usb/pvrusb2/Kconfig"
+source "drivers/media/usb/hdpvr/Kconfig"
+source "drivers/media/usb/usbvision/Kconfig"
+source "drivers/media/usb/stk1160/Kconfig"
+source "drivers/media/usb/go7007/Kconfig"
+endif
+
+if (MEDIA_ANALOG_TV_SUPPORT || MEDIA_DIGITAL_TV_SUPPORT)
+	comment "Analog/digital TV USB devices"
+source "drivers/media/usb/au0828/Kconfig"
+source "drivers/media/usb/cx231xx/Kconfig"
+source "drivers/media/usb/tm6000/Kconfig"
+endif
+
+
+if I2C && MEDIA_DIGITAL_TV_SUPPORT
+	comment "Digital TV USB devices"
+source "drivers/media/usb/dvb-usb/Kconfig"
+source "drivers/media/usb/dvb-usb-v2/Kconfig"
+source "drivers/media/usb/ttusb-budget/Kconfig"
+source "drivers/media/usb/ttusb-dec/Kconfig"
+source "drivers/media/usb/siano/Kconfig"
+source "drivers/media/usb/b2c2/Kconfig"
+source "drivers/media/usb/as102/Kconfig"
+endif
+
+if (MEDIA_CAMERA_SUPPORT || MEDIA_ANALOG_TV_SUPPORT || MEDIA_DIGITAL_TV_SUPPORT)
+	comment "Webcam, TV (analog/digital) USB devices"
+source "drivers/media/usb/em28xx/Kconfig"
+endif
+
+if MEDIA_SDR_SUPPORT
+	comment "Software defined radio USB devices"
+source "drivers/media/usb/airspy/Kconfig"
+source "drivers/media/usb/hackrf/Kconfig"
+source "drivers/media/usb/msi2500/Kconfig"
+endif
+
+if MEDIA_CEC_SUPPORT
+	comment "USB HDMI CEC adapters"
+source "drivers/media/usb/pulse8-cec/Kconfig"
+source "drivers/media/usb/rainshadow-cec/Kconfig"
+endif
+
+endif #MEDIA_USB_SUPPORT
+endif #USB
diff --git a/drivers/media/usb/Makefile b/drivers/media/usb/Makefile
new file mode 100644
index 0000000..21e46b1
--- /dev/null
+++ b/drivers/media/usb/Makefile
@@ -0,0 +1,29 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the USB media device drivers
+#
+
+# DVB USB-only drivers
+obj-y += ttusb-dec/ ttusb-budget/ dvb-usb/ dvb-usb-v2/ siano/ b2c2/
+obj-y += zr364xx/ stkwebcam/ s2255/
+
+obj-$(CONFIG_USB_VIDEO_CLASS)	+= uvc/
+obj-$(CONFIG_USB_GSPCA)         += gspca/
+obj-$(CONFIG_USB_PWC)           += pwc/
+obj-$(CONFIG_USB_AIRSPY)        += airspy/
+obj-$(CONFIG_USB_HACKRF)        += hackrf/
+obj-$(CONFIG_USB_MSI2500)       += msi2500/
+obj-$(CONFIG_VIDEO_CPIA2) += cpia2/
+obj-$(CONFIG_VIDEO_AU0828) += au0828/
+obj-$(CONFIG_VIDEO_HDPVR)	+= hdpvr/
+obj-$(CONFIG_VIDEO_PVRUSB2) += pvrusb2/
+obj-$(CONFIG_VIDEO_USBVISION) += usbvision/
+obj-$(CONFIG_VIDEO_STK1160) += stk1160/
+obj-$(CONFIG_VIDEO_CX231XX) += cx231xx/
+obj-$(CONFIG_VIDEO_TM6000) += tm6000/
+obj-$(CONFIG_VIDEO_EM28XX) += em28xx/
+obj-$(CONFIG_VIDEO_USBTV) += usbtv/
+obj-$(CONFIG_VIDEO_GO7007) += go7007/
+obj-$(CONFIG_DVB_AS102) += as102/
+obj-$(CONFIG_USB_PULSE8_CEC) += pulse8-cec/
+obj-$(CONFIG_USB_RAINSHADOW_CEC) += rainshadow-cec/
diff --git a/drivers/media/usb/airspy/Kconfig b/drivers/media/usb/airspy/Kconfig
new file mode 100644
index 0000000..10b204c
--- /dev/null
+++ b/drivers/media/usb/airspy/Kconfig
@@ -0,0 +1,10 @@
+config USB_AIRSPY
+	tristate "AirSpy"
+	depends on VIDEO_V4L2
+	select VIDEOBUF2_VMALLOC
+	---help---
+	  This is a video4linux2 driver for AirSpy SDR device.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called airspy
+
diff --git a/drivers/media/usb/airspy/Makefile b/drivers/media/usb/airspy/Makefile
new file mode 100644
index 0000000..8d8e61c
--- /dev/null
+++ b/drivers/media/usb/airspy/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_USB_AIRSPY)              += airspy.o
diff --git a/drivers/media/usb/airspy/airspy.c b/drivers/media/usb/airspy/airspy.c
new file mode 100644
index 0000000..e70c9e2
--- /dev/null
+++ b/drivers/media/usb/airspy/airspy.c
@@ -0,0 +1,1108 @@
+/*
+ * AirSpy SDR driver
+ *
+ * Copyright (C) 2014 Antti Palosaari <crope@iki.fi>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-vmalloc.h>
+
+/* AirSpy USB API commands (from AirSpy Library) */
+enum {
+	CMD_INVALID                       = 0x00,
+	CMD_RECEIVER_MODE                 = 0x01,
+	CMD_SI5351C_WRITE                 = 0x02,
+	CMD_SI5351C_READ                  = 0x03,
+	CMD_R820T_WRITE                   = 0x04,
+	CMD_R820T_READ                    = 0x05,
+	CMD_SPIFLASH_ERASE                = 0x06,
+	CMD_SPIFLASH_WRITE                = 0x07,
+	CMD_SPIFLASH_READ                 = 0x08,
+	CMD_BOARD_ID_READ                 = 0x09,
+	CMD_VERSION_STRING_READ           = 0x0a,
+	CMD_BOARD_PARTID_SERIALNO_READ    = 0x0b,
+	CMD_SET_SAMPLE_RATE               = 0x0c,
+	CMD_SET_FREQ                      = 0x0d,
+	CMD_SET_LNA_GAIN                  = 0x0e,
+	CMD_SET_MIXER_GAIN                = 0x0f,
+	CMD_SET_VGA_GAIN                  = 0x10,
+	CMD_SET_LNA_AGC                   = 0x11,
+	CMD_SET_MIXER_AGC                 = 0x12,
+	CMD_SET_PACKING                   = 0x13,
+};
+
+/*
+ *       bEndpointAddress     0x81  EP 1 IN
+ *         Transfer Type            Bulk
+ *       wMaxPacketSize     0x0200  1x 512 bytes
+ */
+#define MAX_BULK_BUFS            (6)
+#define BULK_BUFFER_SIZE         (128 * 512)
+
+static const struct v4l2_frequency_band bands[] = {
+	{
+		.tuner = 0,
+		.type = V4L2_TUNER_ADC,
+		.index = 0,
+		.capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS,
+		.rangelow   = 20000000,
+		.rangehigh  = 20000000,
+	},
+};
+
+static const struct v4l2_frequency_band bands_rf[] = {
+	{
+		.tuner = 1,
+		.type = V4L2_TUNER_RF,
+		.index = 0,
+		.capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS,
+		.rangelow   =   24000000,
+		.rangehigh  = 1750000000,
+	},
+};
+
+/* stream formats */
+struct airspy_format {
+	char	*name;
+	u32	pixelformat;
+	u32	buffersize;
+};
+
+/* format descriptions for capture and preview */
+static struct airspy_format formats[] = {
+	{
+		.name		= "Real U12LE",
+		.pixelformat	= V4L2_SDR_FMT_RU12LE,
+		.buffersize	= BULK_BUFFER_SIZE,
+	},
+};
+
+static const unsigned int NUM_FORMATS = ARRAY_SIZE(formats);
+
+/* intermediate buffers with raw data from the USB device */
+struct airspy_frame_buf {
+	/* common v4l buffer stuff -- must be first */
+	struct vb2_v4l2_buffer vb;
+	struct list_head list;
+};
+
+struct airspy {
+#define POWER_ON	   1
+#define USB_STATE_URB_BUF  2
+	unsigned long flags;
+
+	struct device *dev;
+	struct usb_device *udev;
+	struct video_device vdev;
+	struct v4l2_device v4l2_dev;
+
+	/* videobuf2 queue and queued buffers list */
+	struct vb2_queue vb_queue;
+	struct list_head queued_bufs;
+	spinlock_t queued_bufs_lock; /* Protects queued_bufs */
+	unsigned sequence;	     /* Buffer sequence counter */
+	unsigned int vb_full;        /* vb is full and packets dropped */
+
+	/* Note if taking both locks v4l2_lock must always be locked first! */
+	struct mutex v4l2_lock;      /* Protects everything else */
+	struct mutex vb_queue_lock;  /* Protects vb_queue and capt_file */
+
+	struct urb     *urb_list[MAX_BULK_BUFS];
+	int            buf_num;
+	unsigned long  buf_size;
+	u8             *buf_list[MAX_BULK_BUFS];
+	dma_addr_t     dma_addr[MAX_BULK_BUFS];
+	int            urbs_initialized;
+	int            urbs_submitted;
+
+	/* USB control message buffer */
+	#define BUF_SIZE 128
+	u8 buf[BUF_SIZE];
+
+	/* Current configuration */
+	unsigned int f_adc;
+	unsigned int f_rf;
+	u32 pixelformat;
+	u32 buffersize;
+
+	/* Controls */
+	struct v4l2_ctrl_handler hdl;
+	struct v4l2_ctrl *lna_gain_auto;
+	struct v4l2_ctrl *lna_gain;
+	struct v4l2_ctrl *mixer_gain_auto;
+	struct v4l2_ctrl *mixer_gain;
+	struct v4l2_ctrl *if_gain;
+
+	/* Sample rate calc */
+	unsigned long jiffies_next;
+	unsigned int sample;
+	unsigned int sample_measured;
+};
+
+#define airspy_dbg_usb_control_msg(_dev, _r, _t, _v, _i, _b, _l) { \
+	char *_direction; \
+	if (_t & USB_DIR_IN) \
+		_direction = "<<<"; \
+	else \
+		_direction = ">>>"; \
+	dev_dbg(_dev, "%02x %02x %02x %02x %02x %02x %02x %02x %s %*ph\n", \
+			_t, _r, _v & 0xff, _v >> 8, _i & 0xff, _i >> 8, \
+			_l & 0xff, _l >> 8, _direction, _l, _b); \
+}
+
+/* execute firmware command */
+static int airspy_ctrl_msg(struct airspy *s, u8 request, u16 value, u16 index,
+		u8 *data, u16 size)
+{
+	int ret;
+	unsigned int pipe;
+	u8 requesttype;
+
+	switch (request) {
+	case CMD_RECEIVER_MODE:
+	case CMD_SET_FREQ:
+		pipe = usb_sndctrlpipe(s->udev, 0);
+		requesttype = (USB_TYPE_VENDOR | USB_DIR_OUT);
+		break;
+	case CMD_BOARD_ID_READ:
+	case CMD_VERSION_STRING_READ:
+	case CMD_BOARD_PARTID_SERIALNO_READ:
+	case CMD_SET_LNA_GAIN:
+	case CMD_SET_MIXER_GAIN:
+	case CMD_SET_VGA_GAIN:
+	case CMD_SET_LNA_AGC:
+	case CMD_SET_MIXER_AGC:
+		pipe = usb_rcvctrlpipe(s->udev, 0);
+		requesttype = (USB_TYPE_VENDOR | USB_DIR_IN);
+		break;
+	default:
+		dev_err(s->dev, "Unknown command %02x\n", request);
+		ret = -EINVAL;
+		goto err;
+	}
+
+	/* write request */
+	if (!(requesttype & USB_DIR_IN))
+		memcpy(s->buf, data, size);
+
+	ret = usb_control_msg(s->udev, pipe, request, requesttype, value,
+			index, s->buf, size, 1000);
+	airspy_dbg_usb_control_msg(s->dev, request, requesttype, value,
+			index, s->buf, size);
+	if (ret < 0) {
+		dev_err(s->dev, "usb_control_msg() failed %d request %02x\n",
+				ret, request);
+		goto err;
+	}
+
+	/* read request */
+	if (requesttype & USB_DIR_IN)
+		memcpy(data, s->buf, size);
+
+	return 0;
+err:
+	return ret;
+}
+
+/* Private functions */
+static struct airspy_frame_buf *airspy_get_next_fill_buf(struct airspy *s)
+{
+	unsigned long flags;
+	struct airspy_frame_buf *buf = NULL;
+
+	spin_lock_irqsave(&s->queued_bufs_lock, flags);
+	if (list_empty(&s->queued_bufs))
+		goto leave;
+
+	buf = list_entry(s->queued_bufs.next,
+			struct airspy_frame_buf, list);
+	list_del(&buf->list);
+leave:
+	spin_unlock_irqrestore(&s->queued_bufs_lock, flags);
+	return buf;
+}
+
+static unsigned int airspy_convert_stream(struct airspy *s,
+		void *dst, void *src, unsigned int src_len)
+{
+	unsigned int dst_len;
+
+	if (s->pixelformat == V4L2_SDR_FMT_RU12LE) {
+		memcpy(dst, src, src_len);
+		dst_len = src_len;
+	} else {
+		dst_len = 0;
+	}
+
+	/* calculate sample rate and output it in 10 seconds intervals */
+	if (unlikely(time_is_before_jiffies(s->jiffies_next))) {
+		#define MSECS 10000UL
+		unsigned int msecs = jiffies_to_msecs(jiffies -
+				s->jiffies_next + msecs_to_jiffies(MSECS));
+		unsigned int samples = s->sample - s->sample_measured;
+
+		s->jiffies_next = jiffies + msecs_to_jiffies(MSECS);
+		s->sample_measured = s->sample;
+		dev_dbg(s->dev, "slen=%u samples=%u msecs=%u sample rate=%lu\n",
+				src_len, samples, msecs,
+				samples * 1000UL / msecs);
+	}
+
+	/* total number of samples */
+	s->sample += src_len / 2;
+
+	return dst_len;
+}
+
+/*
+ * This gets called for the bulk stream pipe. This is done in interrupt
+ * time, so it has to be fast, not crash, and not stall. Neat.
+ */
+static void airspy_urb_complete(struct urb *urb)
+{
+	struct airspy *s = urb->context;
+	struct airspy_frame_buf *fbuf;
+
+	dev_dbg_ratelimited(s->dev, "status=%d length=%d/%d errors=%d\n",
+			urb->status, urb->actual_length,
+			urb->transfer_buffer_length, urb->error_count);
+
+	switch (urb->status) {
+	case 0:             /* success */
+	case -ETIMEDOUT:    /* NAK */
+		break;
+	case -ECONNRESET:   /* kill */
+	case -ENOENT:
+	case -ESHUTDOWN:
+		return;
+	default:            /* error */
+		dev_err_ratelimited(s->dev, "URB failed %d\n", urb->status);
+		break;
+	}
+
+	if (likely(urb->actual_length > 0)) {
+		void *ptr;
+		unsigned int len;
+		/* get free framebuffer */
+		fbuf = airspy_get_next_fill_buf(s);
+		if (unlikely(fbuf == NULL)) {
+			s->vb_full++;
+			dev_notice_ratelimited(s->dev,
+					"videobuf is full, %d packets dropped\n",
+					s->vb_full);
+			goto skip;
+		}
+
+		/* fill framebuffer */
+		ptr = vb2_plane_vaddr(&fbuf->vb.vb2_buf, 0);
+		len = airspy_convert_stream(s, ptr, urb->transfer_buffer,
+				urb->actual_length);
+		vb2_set_plane_payload(&fbuf->vb.vb2_buf, 0, len);
+		fbuf->vb.vb2_buf.timestamp = ktime_get_ns();
+		fbuf->vb.sequence = s->sequence++;
+		vb2_buffer_done(&fbuf->vb.vb2_buf, VB2_BUF_STATE_DONE);
+	}
+skip:
+	usb_submit_urb(urb, GFP_ATOMIC);
+}
+
+static int airspy_kill_urbs(struct airspy *s)
+{
+	int i;
+
+	for (i = s->urbs_submitted - 1; i >= 0; i--) {
+		dev_dbg(s->dev, "kill urb=%d\n", i);
+		/* stop the URB */
+		usb_kill_urb(s->urb_list[i]);
+	}
+	s->urbs_submitted = 0;
+
+	return 0;
+}
+
+static int airspy_submit_urbs(struct airspy *s)
+{
+	int i, ret;
+
+	for (i = 0; i < s->urbs_initialized; i++) {
+		dev_dbg(s->dev, "submit urb=%d\n", i);
+		ret = usb_submit_urb(s->urb_list[i], GFP_ATOMIC);
+		if (ret) {
+			dev_err(s->dev, "Could not submit URB no. %d - get them all back\n",
+					i);
+			airspy_kill_urbs(s);
+			return ret;
+		}
+		s->urbs_submitted++;
+	}
+
+	return 0;
+}
+
+static int airspy_free_stream_bufs(struct airspy *s)
+{
+	if (test_bit(USB_STATE_URB_BUF, &s->flags)) {
+		while (s->buf_num) {
+			s->buf_num--;
+			dev_dbg(s->dev, "free buf=%d\n", s->buf_num);
+			usb_free_coherent(s->udev, s->buf_size,
+					  s->buf_list[s->buf_num],
+					  s->dma_addr[s->buf_num]);
+		}
+	}
+	clear_bit(USB_STATE_URB_BUF, &s->flags);
+
+	return 0;
+}
+
+static int airspy_alloc_stream_bufs(struct airspy *s)
+{
+	s->buf_num = 0;
+	s->buf_size = BULK_BUFFER_SIZE;
+
+	dev_dbg(s->dev, "all in all I will use %u bytes for streaming\n",
+			MAX_BULK_BUFS * BULK_BUFFER_SIZE);
+
+	for (s->buf_num = 0; s->buf_num < MAX_BULK_BUFS; s->buf_num++) {
+		s->buf_list[s->buf_num] = usb_alloc_coherent(s->udev,
+				BULK_BUFFER_SIZE, GFP_ATOMIC,
+				&s->dma_addr[s->buf_num]);
+		if (!s->buf_list[s->buf_num]) {
+			dev_dbg(s->dev, "alloc buf=%d failed\n", s->buf_num);
+			airspy_free_stream_bufs(s);
+			return -ENOMEM;
+		}
+
+		dev_dbg(s->dev, "alloc buf=%d %p (dma %llu)\n", s->buf_num,
+				s->buf_list[s->buf_num],
+				(long long)s->dma_addr[s->buf_num]);
+		set_bit(USB_STATE_URB_BUF, &s->flags);
+	}
+
+	return 0;
+}
+
+static int airspy_free_urbs(struct airspy *s)
+{
+	int i;
+
+	airspy_kill_urbs(s);
+
+	for (i = s->urbs_initialized - 1; i >= 0; i--) {
+		if (s->urb_list[i]) {
+			dev_dbg(s->dev, "free urb=%d\n", i);
+			/* free the URBs */
+			usb_free_urb(s->urb_list[i]);
+		}
+	}
+	s->urbs_initialized = 0;
+
+	return 0;
+}
+
+static int airspy_alloc_urbs(struct airspy *s)
+{
+	int i, j;
+
+	/* allocate the URBs */
+	for (i = 0; i < MAX_BULK_BUFS; i++) {
+		dev_dbg(s->dev, "alloc urb=%d\n", i);
+		s->urb_list[i] = usb_alloc_urb(0, GFP_ATOMIC);
+		if (!s->urb_list[i]) {
+			for (j = 0; j < i; j++)
+				usb_free_urb(s->urb_list[j]);
+			return -ENOMEM;
+		}
+		usb_fill_bulk_urb(s->urb_list[i],
+				s->udev,
+				usb_rcvbulkpipe(s->udev, 0x81),
+				s->buf_list[i],
+				BULK_BUFFER_SIZE,
+				airspy_urb_complete, s);
+
+		s->urb_list[i]->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+		s->urb_list[i]->transfer_dma = s->dma_addr[i];
+		s->urbs_initialized++;
+	}
+
+	return 0;
+}
+
+/* Must be called with vb_queue_lock hold */
+static void airspy_cleanup_queued_bufs(struct airspy *s)
+{
+	unsigned long flags;
+
+	dev_dbg(s->dev, "\n");
+
+	spin_lock_irqsave(&s->queued_bufs_lock, flags);
+	while (!list_empty(&s->queued_bufs)) {
+		struct airspy_frame_buf *buf;
+
+		buf = list_entry(s->queued_bufs.next,
+				struct airspy_frame_buf, list);
+		list_del(&buf->list);
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+	}
+	spin_unlock_irqrestore(&s->queued_bufs_lock, flags);
+}
+
+/* The user yanked out the cable... */
+static void airspy_disconnect(struct usb_interface *intf)
+{
+	struct v4l2_device *v = usb_get_intfdata(intf);
+	struct airspy *s = container_of(v, struct airspy, v4l2_dev);
+
+	dev_dbg(s->dev, "\n");
+
+	mutex_lock(&s->vb_queue_lock);
+	mutex_lock(&s->v4l2_lock);
+	/* No need to keep the urbs around after disconnection */
+	s->udev = NULL;
+	v4l2_device_disconnect(&s->v4l2_dev);
+	video_unregister_device(&s->vdev);
+	mutex_unlock(&s->v4l2_lock);
+	mutex_unlock(&s->vb_queue_lock);
+
+	v4l2_device_put(&s->v4l2_dev);
+}
+
+/* Videobuf2 operations */
+static int airspy_queue_setup(struct vb2_queue *vq,
+		unsigned int *nbuffers,
+		unsigned int *nplanes, unsigned int sizes[], struct device *alloc_devs[])
+{
+	struct airspy *s = vb2_get_drv_priv(vq);
+
+	dev_dbg(s->dev, "nbuffers=%d\n", *nbuffers);
+
+	/* Need at least 8 buffers */
+	if (vq->num_buffers + *nbuffers < 8)
+		*nbuffers = 8 - vq->num_buffers;
+	*nplanes = 1;
+	sizes[0] = PAGE_ALIGN(s->buffersize);
+
+	dev_dbg(s->dev, "nbuffers=%d sizes[0]=%d\n", *nbuffers, sizes[0]);
+	return 0;
+}
+
+static void airspy_buf_queue(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct airspy *s = vb2_get_drv_priv(vb->vb2_queue);
+	struct airspy_frame_buf *buf =
+			container_of(vbuf, struct airspy_frame_buf, vb);
+	unsigned long flags;
+
+	/* Check the device has not disconnected between prep and queuing */
+	if (unlikely(!s->udev)) {
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+		return;
+	}
+
+	spin_lock_irqsave(&s->queued_bufs_lock, flags);
+	list_add_tail(&buf->list, &s->queued_bufs);
+	spin_unlock_irqrestore(&s->queued_bufs_lock, flags);
+}
+
+static int airspy_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+	struct airspy *s = vb2_get_drv_priv(vq);
+	int ret;
+
+	dev_dbg(s->dev, "\n");
+
+	if (!s->udev)
+		return -ENODEV;
+
+	mutex_lock(&s->v4l2_lock);
+
+	s->sequence = 0;
+
+	set_bit(POWER_ON, &s->flags);
+
+	ret = airspy_alloc_stream_bufs(s);
+	if (ret)
+		goto err_clear_bit;
+
+	ret = airspy_alloc_urbs(s);
+	if (ret)
+		goto err_free_stream_bufs;
+
+	ret = airspy_submit_urbs(s);
+	if (ret)
+		goto err_free_urbs;
+
+	/* start hardware streaming */
+	ret = airspy_ctrl_msg(s, CMD_RECEIVER_MODE, 1, 0, NULL, 0);
+	if (ret)
+		goto err_kill_urbs;
+
+	goto exit_mutex_unlock;
+
+err_kill_urbs:
+	airspy_kill_urbs(s);
+err_free_urbs:
+	airspy_free_urbs(s);
+err_free_stream_bufs:
+	airspy_free_stream_bufs(s);
+err_clear_bit:
+	clear_bit(POWER_ON, &s->flags);
+
+	/* return all queued buffers to vb2 */
+	{
+		struct airspy_frame_buf *buf, *tmp;
+
+		list_for_each_entry_safe(buf, tmp, &s->queued_bufs, list) {
+			list_del(&buf->list);
+			vb2_buffer_done(&buf->vb.vb2_buf,
+					VB2_BUF_STATE_QUEUED);
+		}
+	}
+
+exit_mutex_unlock:
+	mutex_unlock(&s->v4l2_lock);
+
+	return ret;
+}
+
+static void airspy_stop_streaming(struct vb2_queue *vq)
+{
+	struct airspy *s = vb2_get_drv_priv(vq);
+
+	dev_dbg(s->dev, "\n");
+
+	mutex_lock(&s->v4l2_lock);
+
+	/* stop hardware streaming */
+	airspy_ctrl_msg(s, CMD_RECEIVER_MODE, 0, 0, NULL, 0);
+
+	airspy_kill_urbs(s);
+	airspy_free_urbs(s);
+	airspy_free_stream_bufs(s);
+
+	airspy_cleanup_queued_bufs(s);
+
+	clear_bit(POWER_ON, &s->flags);
+
+	mutex_unlock(&s->v4l2_lock);
+}
+
+static const struct vb2_ops airspy_vb2_ops = {
+	.queue_setup            = airspy_queue_setup,
+	.buf_queue              = airspy_buf_queue,
+	.start_streaming        = airspy_start_streaming,
+	.stop_streaming         = airspy_stop_streaming,
+	.wait_prepare           = vb2_ops_wait_prepare,
+	.wait_finish            = vb2_ops_wait_finish,
+};
+
+static int airspy_querycap(struct file *file, void *fh,
+		struct v4l2_capability *cap)
+{
+	struct airspy *s = video_drvdata(file);
+
+	strlcpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver));
+	strlcpy(cap->card, s->vdev.name, sizeof(cap->card));
+	usb_make_path(s->udev, cap->bus_info, sizeof(cap->bus_info));
+	cap->device_caps = V4L2_CAP_SDR_CAPTURE | V4L2_CAP_STREAMING |
+			V4L2_CAP_READWRITE | V4L2_CAP_TUNER;
+	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+
+	return 0;
+}
+
+static int airspy_enum_fmt_sdr_cap(struct file *file, void *priv,
+		struct v4l2_fmtdesc *f)
+{
+	if (f->index >= NUM_FORMATS)
+		return -EINVAL;
+
+	strlcpy(f->description, formats[f->index].name, sizeof(f->description));
+	f->pixelformat = formats[f->index].pixelformat;
+
+	return 0;
+}
+
+static int airspy_g_fmt_sdr_cap(struct file *file, void *priv,
+		struct v4l2_format *f)
+{
+	struct airspy *s = video_drvdata(file);
+
+	f->fmt.sdr.pixelformat = s->pixelformat;
+	f->fmt.sdr.buffersize = s->buffersize;
+	memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved));
+
+	return 0;
+}
+
+static int airspy_s_fmt_sdr_cap(struct file *file, void *priv,
+		struct v4l2_format *f)
+{
+	struct airspy *s = video_drvdata(file);
+	struct vb2_queue *q = &s->vb_queue;
+	int i;
+
+	if (vb2_is_busy(q))
+		return -EBUSY;
+
+	memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved));
+	for (i = 0; i < NUM_FORMATS; i++) {
+		if (formats[i].pixelformat == f->fmt.sdr.pixelformat) {
+			s->pixelformat = formats[i].pixelformat;
+			s->buffersize = formats[i].buffersize;
+			f->fmt.sdr.buffersize = formats[i].buffersize;
+			return 0;
+		}
+	}
+
+	s->pixelformat = formats[0].pixelformat;
+	s->buffersize = formats[0].buffersize;
+	f->fmt.sdr.pixelformat = formats[0].pixelformat;
+	f->fmt.sdr.buffersize = formats[0].buffersize;
+
+	return 0;
+}
+
+static int airspy_try_fmt_sdr_cap(struct file *file, void *priv,
+		struct v4l2_format *f)
+{
+	int i;
+
+	memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved));
+	for (i = 0; i < NUM_FORMATS; i++) {
+		if (formats[i].pixelformat == f->fmt.sdr.pixelformat) {
+			f->fmt.sdr.buffersize = formats[i].buffersize;
+			return 0;
+		}
+	}
+
+	f->fmt.sdr.pixelformat = formats[0].pixelformat;
+	f->fmt.sdr.buffersize = formats[0].buffersize;
+
+	return 0;
+}
+
+static int airspy_s_tuner(struct file *file, void *priv,
+		const struct v4l2_tuner *v)
+{
+	int ret;
+
+	if (v->index == 0)
+		ret = 0;
+	else if (v->index == 1)
+		ret = 0;
+	else
+		ret = -EINVAL;
+
+	return ret;
+}
+
+static int airspy_g_tuner(struct file *file, void *priv, struct v4l2_tuner *v)
+{
+	int ret;
+
+	if (v->index == 0) {
+		strlcpy(v->name, "AirSpy ADC", sizeof(v->name));
+		v->type = V4L2_TUNER_ADC;
+		v->capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS;
+		v->rangelow  = bands[0].rangelow;
+		v->rangehigh = bands[0].rangehigh;
+		ret = 0;
+	} else if (v->index == 1) {
+		strlcpy(v->name, "AirSpy RF", sizeof(v->name));
+		v->type = V4L2_TUNER_RF;
+		v->capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS;
+		v->rangelow  = bands_rf[0].rangelow;
+		v->rangehigh = bands_rf[0].rangehigh;
+		ret = 0;
+	} else {
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int airspy_g_frequency(struct file *file, void *priv,
+		struct v4l2_frequency *f)
+{
+	struct airspy *s = video_drvdata(file);
+	int ret;
+
+	if (f->tuner == 0) {
+		f->type = V4L2_TUNER_ADC;
+		f->frequency = s->f_adc;
+		dev_dbg(s->dev, "ADC frequency=%u Hz\n", s->f_adc);
+		ret = 0;
+	} else if (f->tuner == 1) {
+		f->type = V4L2_TUNER_RF;
+		f->frequency = s->f_rf;
+		dev_dbg(s->dev, "RF frequency=%u Hz\n", s->f_rf);
+		ret = 0;
+	} else {
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int airspy_s_frequency(struct file *file, void *priv,
+		const struct v4l2_frequency *f)
+{
+	struct airspy *s = video_drvdata(file);
+	int ret;
+	u8 buf[4];
+
+	if (f->tuner == 0) {
+		s->f_adc = clamp_t(unsigned int, f->frequency,
+				bands[0].rangelow,
+				bands[0].rangehigh);
+		dev_dbg(s->dev, "ADC frequency=%u Hz\n", s->f_adc);
+		ret = 0;
+	} else if (f->tuner == 1) {
+		s->f_rf = clamp_t(unsigned int, f->frequency,
+				bands_rf[0].rangelow,
+				bands_rf[0].rangehigh);
+		dev_dbg(s->dev, "RF frequency=%u Hz\n", s->f_rf);
+		buf[0] = (s->f_rf >>  0) & 0xff;
+		buf[1] = (s->f_rf >>  8) & 0xff;
+		buf[2] = (s->f_rf >> 16) & 0xff;
+		buf[3] = (s->f_rf >> 24) & 0xff;
+		ret = airspy_ctrl_msg(s, CMD_SET_FREQ, 0, 0, buf, 4);
+	} else {
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int airspy_enum_freq_bands(struct file *file, void *priv,
+		struct v4l2_frequency_band *band)
+{
+	int ret;
+
+	if (band->tuner == 0) {
+		if (band->index >= ARRAY_SIZE(bands)) {
+			ret = -EINVAL;
+		} else {
+			*band = bands[band->index];
+			ret = 0;
+		}
+	} else if (band->tuner == 1) {
+		if (band->index >= ARRAY_SIZE(bands_rf)) {
+			ret = -EINVAL;
+		} else {
+			*band = bands_rf[band->index];
+			ret = 0;
+		}
+	} else {
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static const struct v4l2_ioctl_ops airspy_ioctl_ops = {
+	.vidioc_querycap          = airspy_querycap,
+
+	.vidioc_enum_fmt_sdr_cap  = airspy_enum_fmt_sdr_cap,
+	.vidioc_g_fmt_sdr_cap     = airspy_g_fmt_sdr_cap,
+	.vidioc_s_fmt_sdr_cap     = airspy_s_fmt_sdr_cap,
+	.vidioc_try_fmt_sdr_cap   = airspy_try_fmt_sdr_cap,
+
+	.vidioc_reqbufs           = vb2_ioctl_reqbufs,
+	.vidioc_create_bufs       = vb2_ioctl_create_bufs,
+	.vidioc_prepare_buf       = vb2_ioctl_prepare_buf,
+	.vidioc_querybuf          = vb2_ioctl_querybuf,
+	.vidioc_qbuf              = vb2_ioctl_qbuf,
+	.vidioc_dqbuf             = vb2_ioctl_dqbuf,
+
+	.vidioc_streamon          = vb2_ioctl_streamon,
+	.vidioc_streamoff         = vb2_ioctl_streamoff,
+
+	.vidioc_g_tuner           = airspy_g_tuner,
+	.vidioc_s_tuner           = airspy_s_tuner,
+
+	.vidioc_g_frequency       = airspy_g_frequency,
+	.vidioc_s_frequency       = airspy_s_frequency,
+	.vidioc_enum_freq_bands   = airspy_enum_freq_bands,
+
+	.vidioc_subscribe_event   = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+	.vidioc_log_status        = v4l2_ctrl_log_status,
+};
+
+static const struct v4l2_file_operations airspy_fops = {
+	.owner                    = THIS_MODULE,
+	.open                     = v4l2_fh_open,
+	.release                  = vb2_fop_release,
+	.read                     = vb2_fop_read,
+	.poll                     = vb2_fop_poll,
+	.mmap                     = vb2_fop_mmap,
+	.unlocked_ioctl           = video_ioctl2,
+};
+
+static const struct video_device airspy_template = {
+	.name                     = "AirSpy SDR",
+	.release                  = video_device_release_empty,
+	.fops                     = &airspy_fops,
+	.ioctl_ops                = &airspy_ioctl_ops,
+};
+
+static void airspy_video_release(struct v4l2_device *v)
+{
+	struct airspy *s = container_of(v, struct airspy, v4l2_dev);
+
+	v4l2_ctrl_handler_free(&s->hdl);
+	v4l2_device_unregister(&s->v4l2_dev);
+	kfree(s);
+}
+
+static int airspy_set_lna_gain(struct airspy *s)
+{
+	int ret;
+	u8 u8tmp;
+
+	dev_dbg(s->dev, "lna auto=%d->%d val=%d->%d\n",
+			s->lna_gain_auto->cur.val, s->lna_gain_auto->val,
+			s->lna_gain->cur.val, s->lna_gain->val);
+
+	ret = airspy_ctrl_msg(s, CMD_SET_LNA_AGC, 0, s->lna_gain_auto->val,
+			&u8tmp, 1);
+	if (ret)
+		goto err;
+
+	if (s->lna_gain_auto->val == false) {
+		ret = airspy_ctrl_msg(s, CMD_SET_LNA_GAIN, 0, s->lna_gain->val,
+				&u8tmp, 1);
+		if (ret)
+			goto err;
+	}
+err:
+	if (ret)
+		dev_dbg(s->dev, "failed=%d\n", ret);
+
+	return ret;
+}
+
+static int airspy_set_mixer_gain(struct airspy *s)
+{
+	int ret;
+	u8 u8tmp;
+
+	dev_dbg(s->dev, "mixer auto=%d->%d val=%d->%d\n",
+			s->mixer_gain_auto->cur.val, s->mixer_gain_auto->val,
+			s->mixer_gain->cur.val, s->mixer_gain->val);
+
+	ret = airspy_ctrl_msg(s, CMD_SET_MIXER_AGC, 0, s->mixer_gain_auto->val,
+			&u8tmp, 1);
+	if (ret)
+		goto err;
+
+	if (s->mixer_gain_auto->val == false) {
+		ret = airspy_ctrl_msg(s, CMD_SET_MIXER_GAIN, 0,
+				s->mixer_gain->val, &u8tmp, 1);
+		if (ret)
+			goto err;
+	}
+err:
+	if (ret)
+		dev_dbg(s->dev, "failed=%d\n", ret);
+
+	return ret;
+}
+
+static int airspy_set_if_gain(struct airspy *s)
+{
+	int ret;
+	u8 u8tmp;
+
+	dev_dbg(s->dev, "val=%d->%d\n", s->if_gain->cur.val, s->if_gain->val);
+
+	ret = airspy_ctrl_msg(s, CMD_SET_VGA_GAIN, 0, s->if_gain->val,
+			&u8tmp, 1);
+	if (ret)
+		dev_dbg(s->dev, "failed=%d\n", ret);
+
+	return ret;
+}
+
+static int airspy_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct airspy *s = container_of(ctrl->handler, struct airspy, hdl);
+	int ret;
+
+	switch (ctrl->id) {
+	case  V4L2_CID_RF_TUNER_LNA_GAIN_AUTO:
+	case  V4L2_CID_RF_TUNER_LNA_GAIN:
+		ret = airspy_set_lna_gain(s);
+		break;
+	case  V4L2_CID_RF_TUNER_MIXER_GAIN_AUTO:
+	case  V4L2_CID_RF_TUNER_MIXER_GAIN:
+		ret = airspy_set_mixer_gain(s);
+		break;
+	case  V4L2_CID_RF_TUNER_IF_GAIN:
+		ret = airspy_set_if_gain(s);
+		break;
+	default:
+		dev_dbg(s->dev, "unknown ctrl: id=%d name=%s\n",
+				ctrl->id, ctrl->name);
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static const struct v4l2_ctrl_ops airspy_ctrl_ops = {
+	.s_ctrl = airspy_s_ctrl,
+};
+
+static int airspy_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	struct airspy *s;
+	int ret;
+	u8 u8tmp, buf[BUF_SIZE];
+
+	s = kzalloc(sizeof(struct airspy), GFP_KERNEL);
+	if (s == NULL) {
+		dev_err(&intf->dev, "Could not allocate memory for state\n");
+		return -ENOMEM;
+	}
+
+	mutex_init(&s->v4l2_lock);
+	mutex_init(&s->vb_queue_lock);
+	spin_lock_init(&s->queued_bufs_lock);
+	INIT_LIST_HEAD(&s->queued_bufs);
+	s->dev = &intf->dev;
+	s->udev = interface_to_usbdev(intf);
+	s->f_adc = bands[0].rangelow;
+	s->f_rf = bands_rf[0].rangelow;
+	s->pixelformat = formats[0].pixelformat;
+	s->buffersize = formats[0].buffersize;
+
+	/* Detect device */
+	ret = airspy_ctrl_msg(s, CMD_BOARD_ID_READ, 0, 0, &u8tmp, 1);
+	if (ret == 0)
+		ret = airspy_ctrl_msg(s, CMD_VERSION_STRING_READ, 0, 0,
+				buf, BUF_SIZE);
+	if (ret) {
+		dev_err(s->dev, "Could not detect board\n");
+		goto err_free_mem;
+	}
+
+	buf[BUF_SIZE - 1] = '\0';
+
+	dev_info(s->dev, "Board ID: %02x\n", u8tmp);
+	dev_info(s->dev, "Firmware version: %s\n", buf);
+
+	/* Init videobuf2 queue structure */
+	s->vb_queue.type = V4L2_BUF_TYPE_SDR_CAPTURE;
+	s->vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
+	s->vb_queue.drv_priv = s;
+	s->vb_queue.buf_struct_size = sizeof(struct airspy_frame_buf);
+	s->vb_queue.ops = &airspy_vb2_ops;
+	s->vb_queue.mem_ops = &vb2_vmalloc_memops;
+	s->vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	ret = vb2_queue_init(&s->vb_queue);
+	if (ret) {
+		dev_err(s->dev, "Could not initialize vb2 queue\n");
+		goto err_free_mem;
+	}
+
+	/* Init video_device structure */
+	s->vdev = airspy_template;
+	s->vdev.queue = &s->vb_queue;
+	s->vdev.queue->lock = &s->vb_queue_lock;
+	video_set_drvdata(&s->vdev, s);
+
+	/* Register the v4l2_device structure */
+	s->v4l2_dev.release = airspy_video_release;
+	ret = v4l2_device_register(&intf->dev, &s->v4l2_dev);
+	if (ret) {
+		dev_err(s->dev, "Failed to register v4l2-device (%d)\n", ret);
+		goto err_free_mem;
+	}
+
+	/* Register controls */
+	v4l2_ctrl_handler_init(&s->hdl, 5);
+	s->lna_gain_auto = v4l2_ctrl_new_std(&s->hdl, &airspy_ctrl_ops,
+			V4L2_CID_RF_TUNER_LNA_GAIN_AUTO, 0, 1, 1, 0);
+	s->lna_gain = v4l2_ctrl_new_std(&s->hdl, &airspy_ctrl_ops,
+			V4L2_CID_RF_TUNER_LNA_GAIN, 0, 14, 1, 8);
+	v4l2_ctrl_auto_cluster(2, &s->lna_gain_auto, 0, false);
+	s->mixer_gain_auto = v4l2_ctrl_new_std(&s->hdl, &airspy_ctrl_ops,
+			V4L2_CID_RF_TUNER_MIXER_GAIN_AUTO, 0, 1, 1, 0);
+	s->mixer_gain = v4l2_ctrl_new_std(&s->hdl, &airspy_ctrl_ops,
+			V4L2_CID_RF_TUNER_MIXER_GAIN, 0, 15, 1, 8);
+	v4l2_ctrl_auto_cluster(2, &s->mixer_gain_auto, 0, false);
+	s->if_gain = v4l2_ctrl_new_std(&s->hdl, &airspy_ctrl_ops,
+			V4L2_CID_RF_TUNER_IF_GAIN, 0, 15, 1, 0);
+	if (s->hdl.error) {
+		ret = s->hdl.error;
+		dev_err(s->dev, "Could not initialize controls\n");
+		goto err_free_controls;
+	}
+
+	v4l2_ctrl_handler_setup(&s->hdl);
+
+	s->v4l2_dev.ctrl_handler = &s->hdl;
+	s->vdev.v4l2_dev = &s->v4l2_dev;
+	s->vdev.lock = &s->v4l2_lock;
+
+	ret = video_register_device(&s->vdev, VFL_TYPE_SDR, -1);
+	if (ret) {
+		dev_err(s->dev, "Failed to register as video device (%d)\n",
+				ret);
+		goto err_free_controls;
+	}
+	dev_info(s->dev, "Registered as %s\n",
+			video_device_node_name(&s->vdev));
+	dev_notice(s->dev, "SDR API is still slightly experimental and functionality changes may follow\n");
+	return 0;
+
+err_free_controls:
+	v4l2_ctrl_handler_free(&s->hdl);
+	v4l2_device_unregister(&s->v4l2_dev);
+err_free_mem:
+	kfree(s);
+	return ret;
+}
+
+/* USB device ID list */
+static const struct usb_device_id airspy_id_table[] = {
+	{ USB_DEVICE(0x1d50, 0x60a1) }, /* AirSpy */
+	{ }
+};
+MODULE_DEVICE_TABLE(usb, airspy_id_table);
+
+/* USB subsystem interface */
+static struct usb_driver airspy_driver = {
+	.name                     = KBUILD_MODNAME,
+	.probe                    = airspy_probe,
+	.disconnect               = airspy_disconnect,
+	.id_table                 = airspy_id_table,
+};
+
+module_usb_driver(airspy_driver);
+
+MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>");
+MODULE_DESCRIPTION("AirSpy SDR");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/as102/Kconfig b/drivers/media/usb/as102/Kconfig
new file mode 100644
index 0000000..28aba00
--- /dev/null
+++ b/drivers/media/usb/as102/Kconfig
@@ -0,0 +1,8 @@
+config DVB_AS102
+	tristate "Abilis AS102 DVB receiver"
+	depends on DVB_CORE && USB && I2C && INPUT
+	select FW_LOADER
+	help
+	  Choose Y or M here if you have a device containing an AS102
+
+	  To compile this driver as a module, choose M here
diff --git a/drivers/media/usb/as102/Makefile b/drivers/media/usb/as102/Makefile
new file mode 100644
index 0000000..b0b3196
--- /dev/null
+++ b/drivers/media/usb/as102/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+dvb-as102-objs := as102_drv.o as102_fw.o as10x_cmd.o as10x_cmd_stream.o \
+		  as102_usb_drv.o as10x_cmd_cfg.o
+
+obj-$(CONFIG_DVB_AS102) += dvb-as102.o
+
+ccflags-y += -Idrivers/media/dvb-frontends
diff --git a/drivers/media/usb/as102/as102_drv.c b/drivers/media/usb/as102/as102_drv.c
new file mode 100644
index 0000000..48b0c4e
--- /dev/null
+++ b/drivers/media/usb/as102/as102_drv.c
@@ -0,0 +1,402 @@
+/*
+ * Abilis Systems Single DVB-T Receiver
+ * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com>
+ * Copyright (C) 2010 Devin Heitmueller <dheitmueller@kernellabs.com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/mm.h>
+#include <linux/kref.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+
+/* header file for usb device driver*/
+#include "as102_drv.h"
+#include "as10x_cmd.h"
+#include "as102_fe.h"
+#include "as102_fw.h"
+#include <media/dvbdev.h>
+
+int dual_tuner;
+module_param_named(dual_tuner, dual_tuner, int, 0644);
+MODULE_PARM_DESC(dual_tuner, "Activate Dual-Tuner config (default: off)");
+
+static int fw_upload = 1;
+module_param_named(fw_upload, fw_upload, int, 0644);
+MODULE_PARM_DESC(fw_upload, "Turn on/off default FW upload (default: on)");
+
+static int pid_filtering;
+module_param_named(pid_filtering, pid_filtering, int, 0644);
+MODULE_PARM_DESC(pid_filtering, "Activate HW PID filtering (default: off)");
+
+static int ts_auto_disable;
+module_param_named(ts_auto_disable, ts_auto_disable, int, 0644);
+MODULE_PARM_DESC(ts_auto_disable, "Stream Auto Enable on FW (default: off)");
+
+int elna_enable = 1;
+module_param_named(elna_enable, elna_enable, int, 0644);
+MODULE_PARM_DESC(elna_enable, "Activate eLNA (default: on)");
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static void as102_stop_stream(struct as102_dev_t *dev)
+{
+	struct as10x_bus_adapter_t *bus_adap;
+
+	if (dev != NULL)
+		bus_adap = &dev->bus_adap;
+	else
+		return;
+
+	if (bus_adap->ops->stop_stream != NULL)
+		bus_adap->ops->stop_stream(dev);
+
+	if (ts_auto_disable) {
+		if (mutex_lock_interruptible(&dev->bus_adap.lock))
+			return;
+
+		if (as10x_cmd_stop_streaming(bus_adap) < 0)
+			dev_dbg(&dev->bus_adap.usb_dev->dev,
+				"as10x_cmd_stop_streaming failed\n");
+
+		mutex_unlock(&dev->bus_adap.lock);
+	}
+}
+
+static int as102_start_stream(struct as102_dev_t *dev)
+{
+	struct as10x_bus_adapter_t *bus_adap;
+	int ret = -EFAULT;
+
+	if (dev != NULL)
+		bus_adap = &dev->bus_adap;
+	else
+		return ret;
+
+	if (bus_adap->ops->start_stream != NULL)
+		ret = bus_adap->ops->start_stream(dev);
+
+	if (ts_auto_disable) {
+		if (mutex_lock_interruptible(&dev->bus_adap.lock))
+			return -EFAULT;
+
+		ret = as10x_cmd_start_streaming(bus_adap);
+
+		mutex_unlock(&dev->bus_adap.lock);
+	}
+
+	return ret;
+}
+
+static int as10x_pid_filter(struct as102_dev_t *dev,
+			    int index, u16 pid, int onoff) {
+
+	struct as10x_bus_adapter_t *bus_adap = &dev->bus_adap;
+	int ret = -EFAULT;
+
+	if (mutex_lock_interruptible(&dev->bus_adap.lock)) {
+		dev_dbg(&dev->bus_adap.usb_dev->dev,
+			"amutex_lock_interruptible(lock) failed !\n");
+		return -EBUSY;
+	}
+
+	switch (onoff) {
+	case 0:
+		ret = as10x_cmd_del_PID_filter(bus_adap, (uint16_t) pid);
+		dev_dbg(&dev->bus_adap.usb_dev->dev,
+			"DEL_PID_FILTER([%02d] 0x%04x) ret = %d\n",
+			index, pid, ret);
+		break;
+	case 1:
+	{
+		struct as10x_ts_filter filter;
+
+		filter.type = TS_PID_TYPE_TS;
+		filter.idx = 0xFF;
+		filter.pid = pid;
+
+		ret = as10x_cmd_add_PID_filter(bus_adap, &filter);
+		dev_dbg(&dev->bus_adap.usb_dev->dev,
+			"ADD_PID_FILTER([%02d -> %02d], 0x%04x) ret = %d\n",
+			index, filter.idx, filter.pid, ret);
+		break;
+	}
+	}
+
+	mutex_unlock(&dev->bus_adap.lock);
+	return ret;
+}
+
+static int as102_dvb_dmx_start_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+	int ret = 0;
+	struct dvb_demux *demux = dvbdmxfeed->demux;
+	struct as102_dev_t *as102_dev = demux->priv;
+
+	if (mutex_lock_interruptible(&as102_dev->sem))
+		return -ERESTARTSYS;
+
+	if (pid_filtering)
+		as10x_pid_filter(as102_dev, dvbdmxfeed->index,
+				 dvbdmxfeed->pid, 1);
+
+	if (as102_dev->streaming++ == 0)
+		ret = as102_start_stream(as102_dev);
+
+	mutex_unlock(&as102_dev->sem);
+	return ret;
+}
+
+static int as102_dvb_dmx_stop_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+	struct dvb_demux *demux = dvbdmxfeed->demux;
+	struct as102_dev_t *as102_dev = demux->priv;
+
+	if (mutex_lock_interruptible(&as102_dev->sem))
+		return -ERESTARTSYS;
+
+	if (--as102_dev->streaming == 0)
+		as102_stop_stream(as102_dev);
+
+	if (pid_filtering)
+		as10x_pid_filter(as102_dev, dvbdmxfeed->index,
+				 dvbdmxfeed->pid, 0);
+
+	mutex_unlock(&as102_dev->sem);
+	return 0;
+}
+
+static int as102_set_tune(void *priv, struct as10x_tune_args *tune_args)
+{
+	struct as10x_bus_adapter_t *bus_adap = priv;
+	int ret;
+
+	/* Set frontend arguments */
+	if (mutex_lock_interruptible(&bus_adap->lock))
+		return -EBUSY;
+
+	ret =  as10x_cmd_set_tune(bus_adap, tune_args);
+	if (ret != 0)
+		dev_dbg(&bus_adap->usb_dev->dev,
+			"as10x_cmd_set_tune failed. (err = %d)\n", ret);
+
+	mutex_unlock(&bus_adap->lock);
+
+	return ret;
+}
+
+static int as102_get_tps(void *priv, struct as10x_tps *tps)
+{
+	struct as10x_bus_adapter_t *bus_adap = priv;
+	int ret;
+
+	if (mutex_lock_interruptible(&bus_adap->lock))
+		return -EBUSY;
+
+	/* send abilis command: GET_TPS */
+	ret = as10x_cmd_get_tps(bus_adap, tps);
+
+	mutex_unlock(&bus_adap->lock);
+
+	return ret;
+}
+
+static int as102_get_status(void *priv, struct as10x_tune_status *tstate)
+{
+	struct as10x_bus_adapter_t *bus_adap = priv;
+	int ret;
+
+	if (mutex_lock_interruptible(&bus_adap->lock))
+		return -EBUSY;
+
+	/* send abilis command: GET_TUNE_STATUS */
+	ret = as10x_cmd_get_tune_status(bus_adap, tstate);
+	if (ret < 0) {
+		dev_dbg(&bus_adap->usb_dev->dev,
+			"as10x_cmd_get_tune_status failed (err = %d)\n",
+			ret);
+	}
+
+	mutex_unlock(&bus_adap->lock);
+
+	return ret;
+}
+
+static int as102_get_stats(void *priv, struct as10x_demod_stats *demod_stats)
+{
+	struct as10x_bus_adapter_t *bus_adap = priv;
+	int ret;
+
+	if (mutex_lock_interruptible(&bus_adap->lock))
+		return -EBUSY;
+
+	/* send abilis command: GET_TUNE_STATUS */
+	ret = as10x_cmd_get_demod_stats(bus_adap, demod_stats);
+	if (ret < 0) {
+		dev_dbg(&bus_adap->usb_dev->dev,
+			"as10x_cmd_get_demod_stats failed (probably not tuned)\n");
+	} else {
+		dev_dbg(&bus_adap->usb_dev->dev,
+			"demod status: fc: 0x%08x, bad fc: 0x%08x, bytes corrected: 0x%08x , MER: 0x%04x\n",
+			demod_stats->frame_count,
+			demod_stats->bad_frame_count,
+			demod_stats->bytes_fixed_by_rs,
+			demod_stats->mer);
+	}
+	mutex_unlock(&bus_adap->lock);
+
+	return ret;
+}
+
+static int as102_stream_ctrl(void *priv, int acquire, uint32_t elna_cfg)
+{
+	struct as10x_bus_adapter_t *bus_adap = priv;
+	int ret;
+
+	if (mutex_lock_interruptible(&bus_adap->lock))
+		return -EBUSY;
+
+	if (acquire) {
+		if (elna_enable)
+			as10x_cmd_set_context(bus_adap,
+					      CONTEXT_LNA, elna_cfg);
+
+		ret = as10x_cmd_turn_on(bus_adap);
+	} else {
+		ret = as10x_cmd_turn_off(bus_adap);
+	}
+
+	mutex_unlock(&bus_adap->lock);
+
+	return ret;
+}
+
+static const struct as102_fe_ops as102_fe_ops = {
+	.set_tune = as102_set_tune,
+	.get_tps  = as102_get_tps,
+	.get_status = as102_get_status,
+	.get_stats = as102_get_stats,
+	.stream_ctrl = as102_stream_ctrl,
+};
+
+int as102_dvb_register(struct as102_dev_t *as102_dev)
+{
+	struct device *dev = &as102_dev->bus_adap.usb_dev->dev;
+	int ret;
+
+	ret = dvb_register_adapter(&as102_dev->dvb_adap,
+			   as102_dev->name, THIS_MODULE,
+			   dev, adapter_nr);
+	if (ret < 0) {
+		dev_err(dev, "%s: dvb_register_adapter() failed: %d\n",
+			__func__, ret);
+		return ret;
+	}
+
+	as102_dev->dvb_dmx.priv = as102_dev;
+	as102_dev->dvb_dmx.filternum = pid_filtering ? 16 : 256;
+	as102_dev->dvb_dmx.feednum = 256;
+	as102_dev->dvb_dmx.start_feed = as102_dvb_dmx_start_feed;
+	as102_dev->dvb_dmx.stop_feed = as102_dvb_dmx_stop_feed;
+
+	as102_dev->dvb_dmx.dmx.capabilities = DMX_TS_FILTERING |
+					      DMX_SECTION_FILTERING;
+
+	as102_dev->dvb_dmxdev.filternum = as102_dev->dvb_dmx.filternum;
+	as102_dev->dvb_dmxdev.demux = &as102_dev->dvb_dmx.dmx;
+	as102_dev->dvb_dmxdev.capabilities = 0;
+
+	ret = dvb_dmx_init(&as102_dev->dvb_dmx);
+	if (ret < 0) {
+		dev_err(dev, "%s: dvb_dmx_init() failed: %d\n", __func__, ret);
+		goto edmxinit;
+	}
+
+	ret = dvb_dmxdev_init(&as102_dev->dvb_dmxdev, &as102_dev->dvb_adap);
+	if (ret < 0) {
+		dev_err(dev, "%s: dvb_dmxdev_init() failed: %d\n",
+			__func__, ret);
+		goto edmxdinit;
+	}
+
+	/* Attach the frontend */
+	as102_dev->dvb_fe = dvb_attach(as102_attach, as102_dev->name,
+				       &as102_fe_ops,
+				       &as102_dev->bus_adap,
+				       as102_dev->elna_cfg);
+	if (!as102_dev->dvb_fe) {
+		ret = -ENODEV;
+		dev_err(dev, "%s: as102_attach() failed: %d",
+		    __func__, ret);
+		goto efereg;
+	}
+
+	ret =  dvb_register_frontend(&as102_dev->dvb_adap, as102_dev->dvb_fe);
+	if (ret < 0) {
+		dev_err(dev, "%s: as102_dvb_register_frontend() failed: %d",
+		    __func__, ret);
+		goto efereg;
+	}
+
+	/* init bus mutex for token locking */
+	mutex_init(&as102_dev->bus_adap.lock);
+
+	/* init start / stop stream mutex */
+	mutex_init(&as102_dev->sem);
+
+	/*
+	 * try to load as102 firmware. If firmware upload failed, we'll be
+	 * able to upload it later.
+	 */
+	if (fw_upload)
+		try_then_request_module(as102_fw_upload(&as102_dev->bus_adap),
+				"firmware_class");
+
+	pr_info("Registered device %s", as102_dev->name);
+	return 0;
+
+efereg:
+	dvb_dmxdev_release(&as102_dev->dvb_dmxdev);
+edmxdinit:
+	dvb_dmx_release(&as102_dev->dvb_dmx);
+edmxinit:
+	dvb_unregister_adapter(&as102_dev->dvb_adap);
+	return ret;
+}
+
+void as102_dvb_unregister(struct as102_dev_t *as102_dev)
+{
+	/* unregister as102 frontend */
+	dvb_unregister_frontend(as102_dev->dvb_fe);
+
+	/* detach frontend */
+	dvb_frontend_detach(as102_dev->dvb_fe);
+
+	/* unregister demux device */
+	dvb_dmxdev_release(&as102_dev->dvb_dmxdev);
+	dvb_dmx_release(&as102_dev->dvb_dmx);
+
+	/* unregister dvb adapter */
+	dvb_unregister_adapter(&as102_dev->dvb_adap);
+
+	pr_info("Unregistered device %s", as102_dev->name);
+}
+
+module_usb_driver(as102_usb_driver);
+
+/* modinfo details */
+MODULE_DESCRIPTION(DRIVER_FULL_NAME);
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Pierrick Hascoet <pierrick.hascoet@abilis.com>");
diff --git a/drivers/media/usb/as102/as102_drv.h b/drivers/media/usb/as102/as102_drv.h
new file mode 100644
index 0000000..c92a1e4
--- /dev/null
+++ b/drivers/media/usb/as102/as102_drv.h
@@ -0,0 +1,83 @@
+/*
+ * Abilis Systems Single DVB-T Receiver
+ * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _AS102_DRV_H
+#define _AS102_DRV_H
+#include <linux/usb.h>
+#include <media/dvb_demux.h>
+#include <media/dvb_frontend.h>
+#include <media/dmxdev.h>
+#include "as10x_handle.h"
+#include "as10x_cmd.h"
+#include "as102_usb_drv.h"
+
+#define DRIVER_FULL_NAME "Abilis Systems as10x usb driver"
+#define DRIVER_NAME "as10x_usb"
+
+#define debug	as102_debug
+extern struct usb_driver as102_usb_driver;
+extern int elna_enable;
+
+#define AS102_DEVICE_MAJOR	192
+
+#define AS102_USB_BUF_SIZE	512
+#define MAX_STREAM_URB		32
+
+struct as10x_bus_adapter_t {
+	struct usb_device *usb_dev;
+	/* bus token lock */
+	struct mutex lock;
+	/* low level interface for bus adapter */
+	union as10x_bus_token_t {
+		/* usb token */
+		struct as10x_usb_token_cmd_t usb;
+	} token;
+
+	/* token cmd xfer id */
+	uint16_t cmd_xid;
+
+	/* as10x command and response for dvb interface*/
+	struct as10x_cmd_t *cmd, *rsp;
+
+	/* bus adapter private ops callback */
+	const struct as102_priv_ops_t *ops;
+};
+
+struct as102_dev_t {
+	const char *name;
+	struct as10x_bus_adapter_t bus_adap;
+	struct list_head device_entry;
+	struct kref kref;
+	uint8_t elna_cfg;
+
+	struct dvb_adapter dvb_adap;
+	struct dvb_frontend *dvb_fe;
+	struct dvb_demux dvb_dmx;
+	struct dmxdev dvb_dmxdev;
+
+	/* timer handle to trig ts stream download */
+	struct timer_list timer_handle;
+
+	struct mutex sem;
+	dma_addr_t dma_addr;
+	void *stream;
+	int streaming;
+	struct urb *stream_urb[MAX_STREAM_URB];
+};
+
+int as102_dvb_register(struct as102_dev_t *dev);
+void as102_dvb_unregister(struct as102_dev_t *dev);
+
+#endif
diff --git a/drivers/media/usb/as102/as102_fw.c b/drivers/media/usb/as102/as102_fw.c
new file mode 100644
index 0000000..38dbc12
--- /dev/null
+++ b/drivers/media/usb/as102/as102_fw.c
@@ -0,0 +1,235 @@
+/*
+ * Abilis Systems Single DVB-T Receiver
+ * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com>
+ * Copyright (C) 2010 Devin Heitmueller <dheitmueller@kernellabs.com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+
+#include "as102_drv.h"
+#include "as102_fw.h"
+
+static const char as102_st_fw1[] = "as102_data1_st.hex";
+static const char as102_st_fw2[] = "as102_data2_st.hex";
+static const char as102_dt_fw1[] = "as102_data1_dt.hex";
+static const char as102_dt_fw2[] = "as102_data2_dt.hex";
+
+static unsigned char atohx(unsigned char *dst, char *src)
+{
+	unsigned char value = 0;
+
+	char msb = tolower(*src) - '0';
+	char lsb = tolower(*(src + 1)) - '0';
+
+	if (msb > 9)
+		msb -= 7;
+	if (lsb > 9)
+		lsb -= 7;
+
+	*dst = value = ((msb & 0xF) << 4) | (lsb & 0xF);
+	return value;
+}
+
+/*
+ * Parse INTEL HEX firmware file to extract address and data.
+ */
+static int parse_hex_line(unsigned char *fw_data, unsigned char *addr,
+			  unsigned char *data, int *dataLength,
+			  unsigned char *addr_has_changed) {
+
+	int count = 0;
+	unsigned char *src, dst;
+
+	if (*fw_data++ != ':') {
+		pr_err("invalid firmware file\n");
+		return -EFAULT;
+	}
+
+	/* locate end of line */
+	for (src = fw_data; *src != '\n'; src += 2) {
+		atohx(&dst, src);
+		/* parse line to split addr / data */
+		switch (count) {
+		case 0:
+			*dataLength = dst;
+			break;
+		case 1:
+			addr[2] = dst;
+			break;
+		case 2:
+			addr[3] = dst;
+			break;
+		case 3:
+			/* check if data is an address */
+			if (dst == 0x04)
+				*addr_has_changed = 1;
+			else
+				*addr_has_changed = 0;
+			break;
+		case  4:
+		case  5:
+			if (*addr_has_changed)
+				addr[(count - 4)] = dst;
+			else
+				data[(count - 4)] = dst;
+			break;
+		default:
+			data[(count - 4)] = dst;
+			break;
+		}
+		count++;
+	}
+
+	/* return read value + ':' + '\n' */
+	return (count * 2) + 2;
+}
+
+static int as102_firmware_upload(struct as10x_bus_adapter_t *bus_adap,
+				 unsigned char *cmd,
+				 const struct firmware *firmware) {
+
+	struct as10x_fw_pkt_t *fw_pkt;
+	int total_read_bytes = 0, errno = 0;
+	unsigned char addr_has_changed = 0;
+
+	fw_pkt = kmalloc(sizeof(*fw_pkt), GFP_KERNEL);
+	if (!fw_pkt)
+		return -ENOMEM;
+
+
+	for (total_read_bytes = 0; total_read_bytes < firmware->size; ) {
+		int read_bytes = 0, data_len = 0;
+
+		/* parse intel hex line */
+		read_bytes = parse_hex_line(
+				(u8 *) (firmware->data + total_read_bytes),
+				fw_pkt->raw.address,
+				fw_pkt->raw.data,
+				&data_len,
+				&addr_has_changed);
+
+		if (read_bytes <= 0)
+			goto error;
+
+		/* detect the end of file */
+		total_read_bytes += read_bytes;
+		if (total_read_bytes == firmware->size) {
+			fw_pkt->u.request[0] = 0x00;
+			fw_pkt->u.request[1] = 0x03;
+
+			/* send EOF command */
+			errno = bus_adap->ops->upload_fw_pkt(bus_adap,
+							     (uint8_t *)
+							     fw_pkt, 2, 0);
+			if (errno < 0)
+				goto error;
+		} else {
+			if (!addr_has_changed) {
+				/* prepare command to send */
+				fw_pkt->u.request[0] = 0x00;
+				fw_pkt->u.request[1] = 0x01;
+
+				data_len += sizeof(fw_pkt->u.request);
+				data_len += sizeof(fw_pkt->raw.address);
+
+				/* send cmd to device */
+				errno = bus_adap->ops->upload_fw_pkt(bus_adap,
+								     (uint8_t *)
+								     fw_pkt,
+								     data_len,
+								     0);
+				if (errno < 0)
+					goto error;
+			}
+		}
+	}
+error:
+	kfree(fw_pkt);
+	return (errno == 0) ? total_read_bytes : errno;
+}
+
+int as102_fw_upload(struct as10x_bus_adapter_t *bus_adap)
+{
+	int errno = -EFAULT;
+	const struct firmware *firmware = NULL;
+	unsigned char *cmd_buf = NULL;
+	const char *fw1, *fw2;
+	struct usb_device *dev = bus_adap->usb_dev;
+
+	/* select fw file to upload */
+	if (dual_tuner) {
+		fw1 = as102_dt_fw1;
+		fw2 = as102_dt_fw2;
+	} else {
+		fw1 = as102_st_fw1;
+		fw2 = as102_st_fw2;
+	}
+
+	/* allocate buffer to store firmware upload command and data */
+	cmd_buf = kzalloc(MAX_FW_PKT_SIZE, GFP_KERNEL);
+	if (cmd_buf == NULL) {
+		errno = -ENOMEM;
+		goto error;
+	}
+
+	/* request kernel to locate firmware file: part1 */
+	errno = request_firmware(&firmware, fw1, &dev->dev);
+	if (errno < 0) {
+		pr_err("%s: unable to locate firmware file: %s\n",
+		       DRIVER_NAME, fw1);
+		goto error;
+	}
+
+	/* initiate firmware upload */
+	errno = as102_firmware_upload(bus_adap, cmd_buf, firmware);
+	if (errno < 0) {
+		pr_err("%s: error during firmware upload part1\n",
+		       DRIVER_NAME);
+		goto error;
+	}
+
+	pr_info("%s: firmware: %s loaded with success\n",
+		DRIVER_NAME, fw1);
+	release_firmware(firmware);
+	firmware = NULL;
+
+	/* wait for boot to complete */
+	mdelay(100);
+
+	/* request kernel to locate firmware file: part2 */
+	errno = request_firmware(&firmware, fw2, &dev->dev);
+	if (errno < 0) {
+		pr_err("%s: unable to locate firmware file: %s\n",
+		       DRIVER_NAME, fw2);
+		goto error;
+	}
+
+	/* initiate firmware upload */
+	errno = as102_firmware_upload(bus_adap, cmd_buf, firmware);
+	if (errno < 0) {
+		pr_err("%s: error during firmware upload part2\n",
+		       DRIVER_NAME);
+		goto error;
+	}
+
+	pr_info("%s: firmware: %s loaded with success\n",
+		DRIVER_NAME, fw2);
+error:
+	kfree(cmd_buf);
+	release_firmware(firmware);
+
+	return errno;
+}
diff --git a/drivers/media/usb/as102/as102_fw.h b/drivers/media/usb/as102/as102_fw.h
new file mode 100644
index 0000000..2732b78
--- /dev/null
+++ b/drivers/media/usb/as102/as102_fw.h
@@ -0,0 +1,34 @@
+/*
+ * Abilis Systems Single DVB-T Receiver
+ * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com>
+ *
+ * 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, 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.
+ */
+#define MAX_FW_PKT_SIZE	64
+
+extern int dual_tuner;
+
+struct as10x_raw_fw_pkt {
+	unsigned char address[4];
+	unsigned char data[MAX_FW_PKT_SIZE - 6];
+} __packed;
+
+struct as10x_fw_pkt_t {
+	union {
+		unsigned char request[2];
+		unsigned char length[2];
+	} __packed u;
+	struct as10x_raw_fw_pkt raw;
+} __packed;
+
+#ifdef __KERNEL__
+int as102_fw_upload(struct as10x_bus_adapter_t *bus_adap);
+#endif
diff --git a/drivers/media/usb/as102/as102_usb_drv.c b/drivers/media/usb/as102/as102_usb_drv.c
new file mode 100644
index 0000000..ea57859
--- /dev/null
+++ b/drivers/media/usb/as102/as102_usb_drv.c
@@ -0,0 +1,473 @@
+/*
+ * Abilis Systems Single DVB-T Receiver
+ * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com>
+ * Copyright (C) 2010 Devin Heitmueller <dheitmueller@kernellabs.com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/usb.h>
+
+#include "as102_drv.h"
+#include "as102_usb_drv.h"
+#include "as102_fw.h"
+
+static void as102_usb_disconnect(struct usb_interface *interface);
+static int as102_usb_probe(struct usb_interface *interface,
+			   const struct usb_device_id *id);
+
+static int as102_usb_start_stream(struct as102_dev_t *dev);
+static void as102_usb_stop_stream(struct as102_dev_t *dev);
+
+static int as102_open(struct inode *inode, struct file *file);
+static int as102_release(struct inode *inode, struct file *file);
+
+static const struct usb_device_id as102_usb_id_table[] = {
+	{ USB_DEVICE(AS102_USB_DEVICE_VENDOR_ID, AS102_USB_DEVICE_PID_0001) },
+	{ USB_DEVICE(PCTV_74E_USB_VID, PCTV_74E_USB_PID) },
+	{ USB_DEVICE(ELGATO_EYETV_DTT_USB_VID, ELGATO_EYETV_DTT_USB_PID) },
+	{ USB_DEVICE(NBOX_DVBT_DONGLE_USB_VID, NBOX_DVBT_DONGLE_USB_PID) },
+	{ USB_DEVICE(SKY_IT_DIGITAL_KEY_USB_VID, SKY_IT_DIGITAL_KEY_USB_PID) },
+	{ } /* Terminating entry */
+};
+
+/* Note that this table must always have the same number of entries as the
+   as102_usb_id_table struct */
+static const char * const as102_device_names[] = {
+	AS102_REFERENCE_DESIGN,
+	AS102_PCTV_74E,
+	AS102_ELGATO_EYETV_DTT_NAME,
+	AS102_NBOX_DVBT_DONGLE_NAME,
+	AS102_SKY_IT_DIGITAL_KEY_NAME,
+	NULL /* Terminating entry */
+};
+
+/* eLNA configuration: devices built on the reference design work best
+   with 0xA0, while custom designs seem to require 0xC0 */
+static uint8_t const as102_elna_cfg[] = {
+	0xA0,
+	0xC0,
+	0xC0,
+	0xA0,
+	0xA0,
+	0x00 /* Terminating entry */
+};
+
+struct usb_driver as102_usb_driver = {
+	.name		= DRIVER_FULL_NAME,
+	.probe		= as102_usb_probe,
+	.disconnect	= as102_usb_disconnect,
+	.id_table	= as102_usb_id_table
+};
+
+static const struct file_operations as102_dev_fops = {
+	.owner		= THIS_MODULE,
+	.open		= as102_open,
+	.release	= as102_release,
+};
+
+static struct usb_class_driver as102_usb_class_driver = {
+	.name		= "aton2-%d",
+	.fops		= &as102_dev_fops,
+	.minor_base	= AS102_DEVICE_MAJOR,
+};
+
+static int as102_usb_xfer_cmd(struct as10x_bus_adapter_t *bus_adap,
+			      unsigned char *send_buf, int send_buf_len,
+			      unsigned char *recv_buf, int recv_buf_len)
+{
+	int ret = 0;
+
+	if (send_buf != NULL) {
+		ret = usb_control_msg(bus_adap->usb_dev,
+				      usb_sndctrlpipe(bus_adap->usb_dev, 0),
+				      AS102_USB_DEVICE_TX_CTRL_CMD,
+				      USB_DIR_OUT | USB_TYPE_VENDOR |
+				      USB_RECIP_DEVICE,
+				      bus_adap->cmd_xid, /* value */
+				      0, /* index */
+				      send_buf, send_buf_len,
+				      USB_CTRL_SET_TIMEOUT /* 200 */);
+		if (ret < 0) {
+			dev_dbg(&bus_adap->usb_dev->dev,
+				"usb_control_msg(send) failed, err %i\n", ret);
+			return ret;
+		}
+
+		if (ret != send_buf_len) {
+			dev_dbg(&bus_adap->usb_dev->dev,
+			"only wrote %d of %d bytes\n", ret, send_buf_len);
+			return -1;
+		}
+	}
+
+	if (recv_buf != NULL) {
+#ifdef TRACE
+		dev_dbg(bus_adap->usb_dev->dev,
+			"want to read: %d bytes\n", recv_buf_len);
+#endif
+		ret = usb_control_msg(bus_adap->usb_dev,
+				      usb_rcvctrlpipe(bus_adap->usb_dev, 0),
+				      AS102_USB_DEVICE_RX_CTRL_CMD,
+				      USB_DIR_IN | USB_TYPE_VENDOR |
+				      USB_RECIP_DEVICE,
+				      bus_adap->cmd_xid, /* value */
+				      0, /* index */
+				      recv_buf, recv_buf_len,
+				      USB_CTRL_GET_TIMEOUT /* 200 */);
+		if (ret < 0) {
+			dev_dbg(&bus_adap->usb_dev->dev,
+				"usb_control_msg(recv) failed, err %i\n", ret);
+			return ret;
+		}
+#ifdef TRACE
+		dev_dbg(bus_adap->usb_dev->dev,
+			"read %d bytes\n", recv_buf_len);
+#endif
+	}
+
+	return ret;
+}
+
+static int as102_send_ep1(struct as10x_bus_adapter_t *bus_adap,
+			  unsigned char *send_buf,
+			  int send_buf_len,
+			  int swap32)
+{
+	int ret, actual_len;
+
+	ret = usb_bulk_msg(bus_adap->usb_dev,
+			   usb_sndbulkpipe(bus_adap->usb_dev, 1),
+			   send_buf, send_buf_len, &actual_len, 200);
+	if (ret) {
+		dev_dbg(&bus_adap->usb_dev->dev,
+			"usb_bulk_msg(send) failed, err %i\n", ret);
+		return ret;
+	}
+
+	if (actual_len != send_buf_len) {
+		dev_dbg(&bus_adap->usb_dev->dev, "only wrote %d of %d bytes\n",
+			actual_len, send_buf_len);
+		return -1;
+	}
+	return actual_len;
+}
+
+static int as102_read_ep2(struct as10x_bus_adapter_t *bus_adap,
+		   unsigned char *recv_buf, int recv_buf_len)
+{
+	int ret, actual_len;
+
+	if (recv_buf == NULL)
+		return -EINVAL;
+
+	ret = usb_bulk_msg(bus_adap->usb_dev,
+			   usb_rcvbulkpipe(bus_adap->usb_dev, 2),
+			   recv_buf, recv_buf_len, &actual_len, 200);
+	if (ret) {
+		dev_dbg(&bus_adap->usb_dev->dev,
+			"usb_bulk_msg(recv) failed, err %i\n", ret);
+		return ret;
+	}
+
+	if (actual_len != recv_buf_len) {
+		dev_dbg(&bus_adap->usb_dev->dev, "only read %d of %d bytes\n",
+			actual_len, recv_buf_len);
+		return -1;
+	}
+	return actual_len;
+}
+
+static const struct as102_priv_ops_t as102_priv_ops = {
+	.upload_fw_pkt	= as102_send_ep1,
+	.xfer_cmd	= as102_usb_xfer_cmd,
+	.as102_read_ep2	= as102_read_ep2,
+	.start_stream	= as102_usb_start_stream,
+	.stop_stream	= as102_usb_stop_stream,
+};
+
+static int as102_submit_urb_stream(struct as102_dev_t *dev, struct urb *urb)
+{
+	int err;
+
+	usb_fill_bulk_urb(urb,
+			  dev->bus_adap.usb_dev,
+			  usb_rcvbulkpipe(dev->bus_adap.usb_dev, 0x2),
+			  urb->transfer_buffer,
+			  AS102_USB_BUF_SIZE,
+			  as102_urb_stream_irq,
+			  dev);
+
+	err = usb_submit_urb(urb, GFP_ATOMIC);
+	if (err)
+		dev_dbg(&urb->dev->dev,
+			"%s: usb_submit_urb failed\n", __func__);
+
+	return err;
+}
+
+void as102_urb_stream_irq(struct urb *urb)
+{
+	struct as102_dev_t *as102_dev = urb->context;
+
+	if (urb->actual_length > 0) {
+		dvb_dmx_swfilter(&as102_dev->dvb_dmx,
+				 urb->transfer_buffer,
+				 urb->actual_length);
+	} else {
+		if (urb->actual_length == 0)
+			memset(urb->transfer_buffer, 0, AS102_USB_BUF_SIZE);
+	}
+
+	/* is not stopped, re-submit urb */
+	if (as102_dev->streaming)
+		as102_submit_urb_stream(as102_dev, urb);
+}
+
+static void as102_free_usb_stream_buffer(struct as102_dev_t *dev)
+{
+	int i;
+
+	for (i = 0; i < MAX_STREAM_URB; i++)
+		usb_free_urb(dev->stream_urb[i]);
+
+	usb_free_coherent(dev->bus_adap.usb_dev,
+			MAX_STREAM_URB * AS102_USB_BUF_SIZE,
+			dev->stream,
+			dev->dma_addr);
+}
+
+static int as102_alloc_usb_stream_buffer(struct as102_dev_t *dev)
+{
+	int i;
+
+	dev->stream = usb_alloc_coherent(dev->bus_adap.usb_dev,
+				       MAX_STREAM_URB * AS102_USB_BUF_SIZE,
+				       GFP_KERNEL,
+				       &dev->dma_addr);
+	if (!dev->stream) {
+		dev_dbg(&dev->bus_adap.usb_dev->dev,
+			"%s: usb_buffer_alloc failed\n", __func__);
+		return -ENOMEM;
+	}
+
+	memset(dev->stream, 0, MAX_STREAM_URB * AS102_USB_BUF_SIZE);
+
+	/* init urb buffers */
+	for (i = 0; i < MAX_STREAM_URB; i++) {
+		struct urb *urb;
+
+		urb = usb_alloc_urb(0, GFP_ATOMIC);
+		if (urb == NULL) {
+			as102_free_usb_stream_buffer(dev);
+			return -ENOMEM;
+		}
+
+		urb->transfer_buffer = dev->stream + (i * AS102_USB_BUF_SIZE);
+		urb->transfer_dma = dev->dma_addr + (i * AS102_USB_BUF_SIZE);
+		urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+		urb->transfer_buffer_length = AS102_USB_BUF_SIZE;
+
+		dev->stream_urb[i] = urb;
+	}
+	return 0;
+}
+
+static void as102_usb_stop_stream(struct as102_dev_t *dev)
+{
+	int i;
+
+	for (i = 0; i < MAX_STREAM_URB; i++)
+		usb_kill_urb(dev->stream_urb[i]);
+}
+
+static int as102_usb_start_stream(struct as102_dev_t *dev)
+{
+	int i, ret = 0;
+
+	for (i = 0; i < MAX_STREAM_URB; i++) {
+		ret = as102_submit_urb_stream(dev, dev->stream_urb[i]);
+		if (ret) {
+			as102_usb_stop_stream(dev);
+			return ret;
+		}
+	}
+
+	return 0;
+}
+
+static void as102_usb_release(struct kref *kref)
+{
+	struct as102_dev_t *as102_dev;
+
+	as102_dev = container_of(kref, struct as102_dev_t, kref);
+	if (as102_dev != NULL) {
+		usb_put_dev(as102_dev->bus_adap.usb_dev);
+		kfree(as102_dev);
+	}
+}
+
+static void as102_usb_disconnect(struct usb_interface *intf)
+{
+	struct as102_dev_t *as102_dev;
+
+	/* extract as102_dev_t from usb_device private data */
+	as102_dev = usb_get_intfdata(intf);
+
+	/* unregister dvb layer */
+	as102_dvb_unregister(as102_dev);
+
+	/* free usb buffers */
+	as102_free_usb_stream_buffer(as102_dev);
+
+	usb_set_intfdata(intf, NULL);
+
+	/* usb unregister device */
+	usb_deregister_dev(intf, &as102_usb_class_driver);
+
+	/* decrement usage counter */
+	kref_put(&as102_dev->kref, as102_usb_release);
+
+	pr_info("%s: device has been disconnected\n", DRIVER_NAME);
+}
+
+static int as102_usb_probe(struct usb_interface *intf,
+			   const struct usb_device_id *id)
+{
+	int ret;
+	struct as102_dev_t *as102_dev;
+	int i;
+
+	/* This should never actually happen */
+	if (ARRAY_SIZE(as102_usb_id_table) !=
+	    (sizeof(as102_device_names) / sizeof(const char *))) {
+		pr_err("Device names table invalid size");
+		return -EINVAL;
+	}
+
+	as102_dev = kzalloc(sizeof(struct as102_dev_t), GFP_KERNEL);
+	if (as102_dev == NULL)
+		return -ENOMEM;
+
+	/* Assign the user-friendly device name */
+	for (i = 0; i < ARRAY_SIZE(as102_usb_id_table); i++) {
+		if (id == &as102_usb_id_table[i]) {
+			as102_dev->name = as102_device_names[i];
+			as102_dev->elna_cfg = as102_elna_cfg[i];
+		}
+	}
+
+	if (as102_dev->name == NULL)
+		as102_dev->name = "Unknown AS102 device";
+
+	/* set private callback functions */
+	as102_dev->bus_adap.ops = &as102_priv_ops;
+
+	/* init cmd token for usb bus */
+	as102_dev->bus_adap.cmd = &as102_dev->bus_adap.token.usb.c;
+	as102_dev->bus_adap.rsp = &as102_dev->bus_adap.token.usb.r;
+
+	/* init kernel device reference */
+	kref_init(&as102_dev->kref);
+
+	/* store as102 device to usb_device private data */
+	usb_set_intfdata(intf, (void *) as102_dev);
+
+	/* store in as102 device the usb_device pointer */
+	as102_dev->bus_adap.usb_dev = usb_get_dev(interface_to_usbdev(intf));
+
+	/* we can register the device now, as it is ready */
+	ret = usb_register_dev(intf, &as102_usb_class_driver);
+	if (ret < 0) {
+		/* something prevented us from registering this driver */
+		dev_err(&intf->dev,
+			"%s: usb_register_dev() failed (errno = %d)\n",
+			__func__, ret);
+		goto failed;
+	}
+
+	pr_info("%s: device has been detected\n", DRIVER_NAME);
+
+	/* request buffer allocation for streaming */
+	ret = as102_alloc_usb_stream_buffer(as102_dev);
+	if (ret != 0)
+		goto failed_stream;
+
+	/* register dvb layer */
+	ret = as102_dvb_register(as102_dev);
+	if (ret != 0)
+		goto failed_dvb;
+
+	return ret;
+
+failed_dvb:
+	as102_free_usb_stream_buffer(as102_dev);
+failed_stream:
+	usb_deregister_dev(intf, &as102_usb_class_driver);
+failed:
+	usb_put_dev(as102_dev->bus_adap.usb_dev);
+	usb_set_intfdata(intf, NULL);
+	kfree(as102_dev);
+	return ret;
+}
+
+static int as102_open(struct inode *inode, struct file *file)
+{
+	int ret = 0, minor = 0;
+	struct usb_interface *intf = NULL;
+	struct as102_dev_t *dev = NULL;
+
+	/* read minor from inode */
+	minor = iminor(inode);
+
+	/* fetch device from usb interface */
+	intf = usb_find_interface(&as102_usb_driver, minor);
+	if (intf == NULL) {
+		pr_err("%s: can't find device for minor %d\n",
+		       __func__, minor);
+		ret = -ENODEV;
+		goto exit;
+	}
+
+	/* get our device */
+	dev = usb_get_intfdata(intf);
+	if (dev == NULL) {
+		ret = -EFAULT;
+		goto exit;
+	}
+
+	/* save our device object in the file's private structure */
+	file->private_data = dev;
+
+	/* increment our usage count for the device */
+	kref_get(&dev->kref);
+
+exit:
+	return ret;
+}
+
+static int as102_release(struct inode *inode, struct file *file)
+{
+	struct as102_dev_t *dev = NULL;
+
+	dev = file->private_data;
+	if (dev != NULL) {
+		/* decrement the count on our device */
+		kref_put(&dev->kref, as102_usb_release);
+	}
+
+	return 0;
+}
+
+MODULE_DEVICE_TABLE(usb, as102_usb_id_table);
diff --git a/drivers/media/usb/as102/as102_usb_drv.h b/drivers/media/usb/as102/as102_usb_drv.h
new file mode 100644
index 0000000..4fb1baa
--- /dev/null
+++ b/drivers/media/usb/as102/as102_usb_drv.h
@@ -0,0 +1,57 @@
+/*
+ * Abilis Systems Single DVB-T Receiver
+ * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com>
+ * Copyright (C) 2010 Devin Heitmueller <dheitmueller@kernellabs.com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#ifndef _AS102_USB_DRV_H_
+#define _AS102_USB_DRV_H_
+
+#define AS102_USB_DEVICE_TX_CTRL_CMD	0xF1
+#define AS102_USB_DEVICE_RX_CTRL_CMD	0xF2
+
+/* define these values to match the supported devices */
+
+/* Abilis system: "TITAN" */
+#define AS102_REFERENCE_DESIGN		"Abilis Systems DVB-Titan"
+#define AS102_USB_DEVICE_VENDOR_ID	0x1BA6
+#define AS102_USB_DEVICE_PID_0001	0x0001
+
+/* PCTV Systems: PCTV picoStick (74e) */
+#define AS102_PCTV_74E			"PCTV Systems picoStick (74e)"
+#define PCTV_74E_USB_VID		0x2013
+#define PCTV_74E_USB_PID		0x0246
+
+/* Elgato: EyeTV DTT Deluxe */
+#define AS102_ELGATO_EYETV_DTT_NAME	"Elgato EyeTV DTT Deluxe"
+#define ELGATO_EYETV_DTT_USB_VID	0x0fd9
+#define ELGATO_EYETV_DTT_USB_PID	0x002c
+
+/* nBox: nBox DVB-T Dongle */
+#define AS102_NBOX_DVBT_DONGLE_NAME	"nBox DVB-T Dongle"
+#define NBOX_DVBT_DONGLE_USB_VID	0x0b89
+#define NBOX_DVBT_DONGLE_USB_PID	0x0007
+
+/* Sky Italia: Digital Key (green led) */
+#define AS102_SKY_IT_DIGITAL_KEY_NAME	"Sky IT Digital Key (green led)"
+#define SKY_IT_DIGITAL_KEY_USB_VID	0x2137
+#define SKY_IT_DIGITAL_KEY_USB_PID	0x0001
+
+void as102_urb_stream_irq(struct urb *urb);
+
+struct as10x_usb_token_cmd_t {
+	/* token cmd */
+	struct as10x_cmd_t c;
+	/* token response */
+	struct as10x_cmd_t r;
+};
+#endif
diff --git a/drivers/media/usb/as102/as10x_cmd.c b/drivers/media/usb/as102/as10x_cmd.c
new file mode 100644
index 0000000..8706179
--- /dev/null
+++ b/drivers/media/usb/as102/as10x_cmd.c
@@ -0,0 +1,413 @@
+/*
+ * Abilis Systems Single DVB-T Receiver
+ * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com>
+ * Copyright (C) 2010 Devin Heitmueller <dheitmueller@kernellabs.com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include "as102_drv.h"
+#include "as10x_cmd.h"
+
+/**
+ * as10x_cmd_turn_on - send turn on command to AS10x
+ * @adap:   pointer to AS10x bus adapter
+ *
+ * Return 0 when no error, < 0 in case of error.
+ */
+int as10x_cmd_turn_on(struct as10x_bus_adapter_t *adap)
+{
+	int error = AS10X_CMD_ERROR;
+	struct as10x_cmd_t *pcmd, *prsp;
+
+	pcmd = adap->cmd;
+	prsp = adap->rsp;
+
+	/* prepare command */
+	as10x_cmd_build(pcmd, (++adap->cmd_xid),
+			sizeof(pcmd->body.turn_on.req));
+
+	/* fill command */
+	pcmd->body.turn_on.req.proc_id = cpu_to_le16(CONTROL_PROC_TURNON);
+
+	/* send command */
+	if (adap->ops->xfer_cmd) {
+		error = adap->ops->xfer_cmd(adap, (uint8_t *) pcmd,
+					    sizeof(pcmd->body.turn_on.req) +
+					    HEADER_SIZE,
+					    (uint8_t *) prsp,
+					    sizeof(prsp->body.turn_on.rsp) +
+					    HEADER_SIZE);
+	}
+
+	if (error < 0)
+		goto out;
+
+	/* parse response */
+	error = as10x_rsp_parse(prsp, CONTROL_PROC_TURNON_RSP);
+
+out:
+	return error;
+}
+
+/**
+ * as10x_cmd_turn_off - send turn off command to AS10x
+ * @adap:   pointer to AS10x bus adapter
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_cmd_turn_off(struct as10x_bus_adapter_t *adap)
+{
+	int error = AS10X_CMD_ERROR;
+	struct as10x_cmd_t *pcmd, *prsp;
+
+	pcmd = adap->cmd;
+	prsp = adap->rsp;
+
+	/* prepare command */
+	as10x_cmd_build(pcmd, (++adap->cmd_xid),
+			sizeof(pcmd->body.turn_off.req));
+
+	/* fill command */
+	pcmd->body.turn_off.req.proc_id = cpu_to_le16(CONTROL_PROC_TURNOFF);
+
+	/* send command */
+	if (adap->ops->xfer_cmd) {
+		error = adap->ops->xfer_cmd(
+			adap, (uint8_t *) pcmd,
+			sizeof(pcmd->body.turn_off.req) + HEADER_SIZE,
+			(uint8_t *) prsp,
+			sizeof(prsp->body.turn_off.rsp) + HEADER_SIZE);
+	}
+
+	if (error < 0)
+		goto out;
+
+	/* parse response */
+	error = as10x_rsp_parse(prsp, CONTROL_PROC_TURNOFF_RSP);
+
+out:
+	return error;
+}
+
+/**
+ * as10x_cmd_set_tune - send set tune command to AS10x
+ * @adap:    pointer to AS10x bus adapter
+ * @ptune:   tune parameters
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_cmd_set_tune(struct as10x_bus_adapter_t *adap,
+		       struct as10x_tune_args *ptune)
+{
+	int error = AS10X_CMD_ERROR;
+	struct as10x_cmd_t *preq, *prsp;
+
+	preq = adap->cmd;
+	prsp = adap->rsp;
+
+	/* prepare command */
+	as10x_cmd_build(preq, (++adap->cmd_xid),
+			sizeof(preq->body.set_tune.req));
+
+	/* fill command */
+	preq->body.set_tune.req.proc_id = cpu_to_le16(CONTROL_PROC_SETTUNE);
+	preq->body.set_tune.req.args.freq = (__force __u32)cpu_to_le32(ptune->freq);
+	preq->body.set_tune.req.args.bandwidth = ptune->bandwidth;
+	preq->body.set_tune.req.args.hier_select = ptune->hier_select;
+	preq->body.set_tune.req.args.modulation = ptune->modulation;
+	preq->body.set_tune.req.args.hierarchy = ptune->hierarchy;
+	preq->body.set_tune.req.args.interleaving_mode  =
+		ptune->interleaving_mode;
+	preq->body.set_tune.req.args.code_rate  = ptune->code_rate;
+	preq->body.set_tune.req.args.guard_interval = ptune->guard_interval;
+	preq->body.set_tune.req.args.transmission_mode  =
+		ptune->transmission_mode;
+
+	/* send command */
+	if (adap->ops->xfer_cmd) {
+		error = adap->ops->xfer_cmd(adap,
+					    (uint8_t *) preq,
+					    sizeof(preq->body.set_tune.req)
+					    + HEADER_SIZE,
+					    (uint8_t *) prsp,
+					    sizeof(prsp->body.set_tune.rsp)
+					    + HEADER_SIZE);
+	}
+
+	if (error < 0)
+		goto out;
+
+	/* parse response */
+	error = as10x_rsp_parse(prsp, CONTROL_PROC_SETTUNE_RSP);
+
+out:
+	return error;
+}
+
+/**
+ * as10x_cmd_get_tune_status - send get tune status command to AS10x
+ * @adap: pointer to AS10x bus adapter
+ * @pstatus: pointer to updated status structure of the current tune
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_cmd_get_tune_status(struct as10x_bus_adapter_t *adap,
+			      struct as10x_tune_status *pstatus)
+{
+	int error = AS10X_CMD_ERROR;
+	struct as10x_cmd_t  *preq, *prsp;
+
+	preq = adap->cmd;
+	prsp = adap->rsp;
+
+	/* prepare command */
+	as10x_cmd_build(preq, (++adap->cmd_xid),
+			sizeof(preq->body.get_tune_status.req));
+
+	/* fill command */
+	preq->body.get_tune_status.req.proc_id =
+		cpu_to_le16(CONTROL_PROC_GETTUNESTAT);
+
+	/* send command */
+	if (adap->ops->xfer_cmd) {
+		error = adap->ops->xfer_cmd(
+			adap,
+			(uint8_t *) preq,
+			sizeof(preq->body.get_tune_status.req) + HEADER_SIZE,
+			(uint8_t *) prsp,
+			sizeof(prsp->body.get_tune_status.rsp) + HEADER_SIZE);
+	}
+
+	if (error < 0)
+		goto out;
+
+	/* parse response */
+	error = as10x_rsp_parse(prsp, CONTROL_PROC_GETTUNESTAT_RSP);
+	if (error < 0)
+		goto out;
+
+	/* Response OK -> get response data */
+	pstatus->tune_state = prsp->body.get_tune_status.rsp.sts.tune_state;
+	pstatus->signal_strength  =
+		le16_to_cpu((__force __le16)prsp->body.get_tune_status.rsp.sts.signal_strength);
+	pstatus->PER = le16_to_cpu((__force __le16)prsp->body.get_tune_status.rsp.sts.PER);
+	pstatus->BER = le16_to_cpu((__force __le16)prsp->body.get_tune_status.rsp.sts.BER);
+
+out:
+	return error;
+}
+
+/**
+ * as10x_cmd_get_tps - send get TPS command to AS10x
+ * @adap:      pointer to AS10x handle
+ * @ptps:      pointer to TPS parameters structure
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_cmd_get_tps(struct as10x_bus_adapter_t *adap, struct as10x_tps *ptps)
+{
+	int error = AS10X_CMD_ERROR;
+	struct as10x_cmd_t *pcmd, *prsp;
+
+	pcmd = adap->cmd;
+	prsp = adap->rsp;
+
+	/* prepare command */
+	as10x_cmd_build(pcmd, (++adap->cmd_xid),
+			sizeof(pcmd->body.get_tps.req));
+
+	/* fill command */
+	pcmd->body.get_tune_status.req.proc_id =
+		cpu_to_le16(CONTROL_PROC_GETTPS);
+
+	/* send command */
+	if (adap->ops->xfer_cmd) {
+		error = adap->ops->xfer_cmd(adap,
+					    (uint8_t *) pcmd,
+					    sizeof(pcmd->body.get_tps.req) +
+					    HEADER_SIZE,
+					    (uint8_t *) prsp,
+					    sizeof(prsp->body.get_tps.rsp) +
+					    HEADER_SIZE);
+	}
+
+	if (error < 0)
+		goto out;
+
+	/* parse response */
+	error = as10x_rsp_parse(prsp, CONTROL_PROC_GETTPS_RSP);
+	if (error < 0)
+		goto out;
+
+	/* Response OK -> get response data */
+	ptps->modulation = prsp->body.get_tps.rsp.tps.modulation;
+	ptps->hierarchy = prsp->body.get_tps.rsp.tps.hierarchy;
+	ptps->interleaving_mode = prsp->body.get_tps.rsp.tps.interleaving_mode;
+	ptps->code_rate_HP = prsp->body.get_tps.rsp.tps.code_rate_HP;
+	ptps->code_rate_LP = prsp->body.get_tps.rsp.tps.code_rate_LP;
+	ptps->guard_interval = prsp->body.get_tps.rsp.tps.guard_interval;
+	ptps->transmission_mode  = prsp->body.get_tps.rsp.tps.transmission_mode;
+	ptps->DVBH_mask_HP = prsp->body.get_tps.rsp.tps.DVBH_mask_HP;
+	ptps->DVBH_mask_LP = prsp->body.get_tps.rsp.tps.DVBH_mask_LP;
+	ptps->cell_ID = le16_to_cpu((__force __le16)prsp->body.get_tps.rsp.tps.cell_ID);
+
+out:
+	return error;
+}
+
+/**
+ * as10x_cmd_get_demod_stats - send get demod stats command to AS10x
+ * @adap:          pointer to AS10x bus adapter
+ * @pdemod_stats:  pointer to demod stats parameters structure
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_cmd_get_demod_stats(struct as10x_bus_adapter_t *adap,
+			      struct as10x_demod_stats *pdemod_stats)
+{
+	int error = AS10X_CMD_ERROR;
+	struct as10x_cmd_t *pcmd, *prsp;
+
+	pcmd = adap->cmd;
+	prsp = adap->rsp;
+
+	/* prepare command */
+	as10x_cmd_build(pcmd, (++adap->cmd_xid),
+			sizeof(pcmd->body.get_demod_stats.req));
+
+	/* fill command */
+	pcmd->body.get_demod_stats.req.proc_id =
+		cpu_to_le16(CONTROL_PROC_GET_DEMOD_STATS);
+
+	/* send command */
+	if (adap->ops->xfer_cmd) {
+		error = adap->ops->xfer_cmd(adap,
+				(uint8_t *) pcmd,
+				sizeof(pcmd->body.get_demod_stats.req)
+				+ HEADER_SIZE,
+				(uint8_t *) prsp,
+				sizeof(prsp->body.get_demod_stats.rsp)
+				+ HEADER_SIZE);
+	}
+
+	if (error < 0)
+		goto out;
+
+	/* parse response */
+	error = as10x_rsp_parse(prsp, CONTROL_PROC_GET_DEMOD_STATS_RSP);
+	if (error < 0)
+		goto out;
+
+	/* Response OK -> get response data */
+	pdemod_stats->frame_count =
+		le32_to_cpu((__force __le32)prsp->body.get_demod_stats.rsp.stats.frame_count);
+	pdemod_stats->bad_frame_count =
+		le32_to_cpu((__force __le32)prsp->body.get_demod_stats.rsp.stats.bad_frame_count);
+	pdemod_stats->bytes_fixed_by_rs =
+		le32_to_cpu((__force __le32)prsp->body.get_demod_stats.rsp.stats.bytes_fixed_by_rs);
+	pdemod_stats->mer =
+		le16_to_cpu((__force __le16)prsp->body.get_demod_stats.rsp.stats.mer);
+	pdemod_stats->has_started =
+		prsp->body.get_demod_stats.rsp.stats.has_started;
+
+out:
+	return error;
+}
+
+/**
+ * as10x_cmd_get_impulse_resp - send get impulse response command to AS10x
+ * @adap:     pointer to AS10x bus adapter
+ * @is_ready: pointer to value indicating when impulse
+ *	      response data is ready
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_cmd_get_impulse_resp(struct as10x_bus_adapter_t *adap,
+			       uint8_t *is_ready)
+{
+	int error = AS10X_CMD_ERROR;
+	struct as10x_cmd_t *pcmd, *prsp;
+
+	pcmd = adap->cmd;
+	prsp = adap->rsp;
+
+	/* prepare command */
+	as10x_cmd_build(pcmd, (++adap->cmd_xid),
+			sizeof(pcmd->body.get_impulse_rsp.req));
+
+	/* fill command */
+	pcmd->body.get_impulse_rsp.req.proc_id =
+		cpu_to_le16(CONTROL_PROC_GET_IMPULSE_RESP);
+
+	/* send command */
+	if (adap->ops->xfer_cmd) {
+		error = adap->ops->xfer_cmd(adap,
+					(uint8_t *) pcmd,
+					sizeof(pcmd->body.get_impulse_rsp.req)
+					+ HEADER_SIZE,
+					(uint8_t *) prsp,
+					sizeof(prsp->body.get_impulse_rsp.rsp)
+					+ HEADER_SIZE);
+	}
+
+	if (error < 0)
+		goto out;
+
+	/* parse response */
+	error = as10x_rsp_parse(prsp, CONTROL_PROC_GET_IMPULSE_RESP_RSP);
+	if (error < 0)
+		goto out;
+
+	/* Response OK -> get response data */
+	*is_ready = prsp->body.get_impulse_rsp.rsp.is_ready;
+
+out:
+	return error;
+}
+
+/**
+ * as10x_cmd_build - build AS10x command header
+ * @pcmd:     pointer to AS10x command buffer
+ * @xid:      sequence id of the command
+ * @cmd_len:  length of the command
+ */
+void as10x_cmd_build(struct as10x_cmd_t *pcmd,
+		     uint16_t xid, uint16_t cmd_len)
+{
+	pcmd->header.req_id = cpu_to_le16(xid);
+	pcmd->header.prog = cpu_to_le16(SERVICE_PROG_ID);
+	pcmd->header.version = cpu_to_le16(SERVICE_PROG_VERSION);
+	pcmd->header.data_len = cpu_to_le16(cmd_len);
+}
+
+/**
+ * as10x_rsp_parse - Parse command response
+ * @prsp:       pointer to AS10x command buffer
+ * @proc_id:    id of the command
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_rsp_parse(struct as10x_cmd_t *prsp, uint16_t proc_id)
+{
+	int error;
+
+	/* extract command error code */
+	error = prsp->body.common.rsp.error;
+
+	if ((error == 0) &&
+	    (le16_to_cpu(prsp->body.common.rsp.proc_id) == proc_id)) {
+		return 0;
+	}
+
+	return AS10X_CMD_ERROR;
+}
diff --git a/drivers/media/usb/as102/as10x_cmd.h b/drivers/media/usb/as102/as10x_cmd.h
new file mode 100644
index 0000000..e06b84e
--- /dev/null
+++ b/drivers/media/usb/as102/as10x_cmd.h
@@ -0,0 +1,523 @@
+/*
+ * Abilis Systems Single DVB-T Receiver
+ * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#ifndef _AS10X_CMD_H_
+#define _AS10X_CMD_H_
+
+#include <linux/kernel.h>
+
+#include "as102_fe_types.h"
+
+/*********************************/
+/*       MACRO DEFINITIONS       */
+/*********************************/
+#define AS10X_CMD_ERROR		-1
+
+#define SERVICE_PROG_ID		0x0002
+#define SERVICE_PROG_VERSION	0x0001
+
+#define HIER_NONE		0x00
+#define HIER_LOW_PRIORITY	0x01
+
+#define HEADER_SIZE (sizeof(struct as10x_cmd_header_t))
+
+/* context request types */
+#define GET_CONTEXT_DATA	1
+#define SET_CONTEXT_DATA	2
+
+/* ODSP suspend modes */
+#define CFG_MODE_ODSP_RESUME	0
+#define CFG_MODE_ODSP_SUSPEND	1
+
+/* Dump memory size */
+#define DUMP_BLOCK_SIZE_MAX	0x20
+
+/*********************************/
+/*     TYPE DEFINITION           */
+/*********************************/
+enum control_proc {
+	CONTROL_PROC_TURNON			= 0x0001,
+	CONTROL_PROC_TURNON_RSP			= 0x0100,
+	CONTROL_PROC_SET_REGISTER		= 0x0002,
+	CONTROL_PROC_SET_REGISTER_RSP		= 0x0200,
+	CONTROL_PROC_GET_REGISTER		= 0x0003,
+	CONTROL_PROC_GET_REGISTER_RSP		= 0x0300,
+	CONTROL_PROC_SETTUNE			= 0x000A,
+	CONTROL_PROC_SETTUNE_RSP		= 0x0A00,
+	CONTROL_PROC_GETTUNESTAT		= 0x000B,
+	CONTROL_PROC_GETTUNESTAT_RSP		= 0x0B00,
+	CONTROL_PROC_GETTPS			= 0x000D,
+	CONTROL_PROC_GETTPS_RSP			= 0x0D00,
+	CONTROL_PROC_SETFILTER			= 0x000E,
+	CONTROL_PROC_SETFILTER_RSP		= 0x0E00,
+	CONTROL_PROC_REMOVEFILTER		= 0x000F,
+	CONTROL_PROC_REMOVEFILTER_RSP		= 0x0F00,
+	CONTROL_PROC_GET_IMPULSE_RESP		= 0x0012,
+	CONTROL_PROC_GET_IMPULSE_RESP_RSP	= 0x1200,
+	CONTROL_PROC_START_STREAMING		= 0x0013,
+	CONTROL_PROC_START_STREAMING_RSP	= 0x1300,
+	CONTROL_PROC_STOP_STREAMING		= 0x0014,
+	CONTROL_PROC_STOP_STREAMING_RSP		= 0x1400,
+	CONTROL_PROC_GET_DEMOD_STATS		= 0x0015,
+	CONTROL_PROC_GET_DEMOD_STATS_RSP	= 0x1500,
+	CONTROL_PROC_ELNA_CHANGE_MODE		= 0x0016,
+	CONTROL_PROC_ELNA_CHANGE_MODE_RSP	= 0x1600,
+	CONTROL_PROC_ODSP_CHANGE_MODE		= 0x0017,
+	CONTROL_PROC_ODSP_CHANGE_MODE_RSP	= 0x1700,
+	CONTROL_PROC_AGC_CHANGE_MODE		= 0x0018,
+	CONTROL_PROC_AGC_CHANGE_MODE_RSP	= 0x1800,
+
+	CONTROL_PROC_CONTEXT			= 0x00FC,
+	CONTROL_PROC_CONTEXT_RSP		= 0xFC00,
+	CONTROL_PROC_DUMP_MEMORY		= 0x00FD,
+	CONTROL_PROC_DUMP_MEMORY_RSP		= 0xFD00,
+	CONTROL_PROC_DUMPLOG_MEMORY		= 0x00FE,
+	CONTROL_PROC_DUMPLOG_MEMORY_RSP		= 0xFE00,
+	CONTROL_PROC_TURNOFF			= 0x00FF,
+	CONTROL_PROC_TURNOFF_RSP		= 0xFF00
+};
+
+union as10x_turn_on {
+	/* request */
+	struct {
+		/* request identifier */
+		__le16 proc_id;
+	} __packed req;
+	/* response */
+	struct {
+		/* response identifier */
+		__le16 proc_id;
+		/* error */
+		uint8_t error;
+	} __packed rsp;
+} __packed;
+
+union as10x_turn_off {
+	/* request */
+	struct {
+		/* request identifier */
+		__le16 proc_id;
+	} __packed req;
+	/* response */
+	struct {
+		/* response identifier */
+		__le16 proc_id;
+		/* error */
+		uint8_t err;
+	} __packed rsp;
+} __packed;
+
+union as10x_set_tune {
+	/* request */
+	struct {
+		/* request identifier */
+		__le16 proc_id;
+		/* tune params */
+		struct as10x_tune_args args;
+	} __packed req;
+	/* response */
+	struct {
+		/* response identifier */
+		__le16 proc_id;
+		/* response error */
+		uint8_t error;
+	} __packed rsp;
+} __packed;
+
+union as10x_get_tune_status {
+	/* request */
+	struct {
+		/* request identifier */
+		__le16 proc_id;
+	} __packed req;
+	/* response */
+	struct {
+		/* response identifier */
+		__le16 proc_id;
+		/* response error */
+		uint8_t error;
+		/* tune status */
+		struct as10x_tune_status sts;
+	} __packed rsp;
+} __packed;
+
+union as10x_get_tps {
+	/* request */
+	struct {
+		/* request identifier */
+		__le16 proc_id;
+	} __packed req;
+	/* response */
+	struct {
+		/* response identifier */
+		__le16 proc_id;
+		/* response error */
+		uint8_t error;
+		/* tps details */
+		struct as10x_tps tps;
+	} __packed rsp;
+} __packed;
+
+union as10x_common {
+	/* request */
+	struct {
+		/* request identifier */
+		__le16  proc_id;
+	} __packed req;
+	/* response */
+	struct {
+		/* response identifier */
+		__le16 proc_id;
+		/* response error */
+		uint8_t error;
+	} __packed rsp;
+} __packed;
+
+union as10x_add_pid_filter {
+	/* request */
+	struct {
+		/* request identifier */
+		__le16  proc_id;
+		/* PID to filter */
+		__le16  pid;
+		/* stream type (MPE, PSI/SI or PES )*/
+		uint8_t stream_type;
+		/* PID index in filter table */
+		uint8_t idx;
+	} __packed req;
+	/* response */
+	struct {
+		/* response identifier */
+		__le16 proc_id;
+		/* response error */
+		uint8_t error;
+		/* Filter id */
+		uint8_t filter_id;
+	} __packed rsp;
+} __packed;
+
+union as10x_del_pid_filter {
+	/* request */
+	struct {
+		/* request identifier */
+		__le16  proc_id;
+		/* PID to remove */
+		__le16  pid;
+	} __packed req;
+	/* response */
+	struct {
+		/* response identifier */
+		__le16 proc_id;
+		/* response error */
+		uint8_t error;
+	} __packed rsp;
+} __packed;
+
+union as10x_start_streaming {
+	/* request */
+	struct {
+		/* request identifier */
+		__le16 proc_id;
+	} __packed req;
+	/* response */
+	struct {
+		/* response identifier */
+		__le16 proc_id;
+		/* error */
+		uint8_t error;
+	} __packed rsp;
+} __packed;
+
+union as10x_stop_streaming {
+	/* request */
+	struct {
+		/* request identifier */
+		__le16 proc_id;
+	} __packed req;
+	/* response */
+	struct {
+		/* response identifier */
+		__le16 proc_id;
+		/* error */
+		uint8_t error;
+	} __packed rsp;
+} __packed;
+
+union as10x_get_demod_stats {
+	/* request */
+	struct {
+		/* request identifier */
+		__le16 proc_id;
+	} __packed req;
+	/* response */
+	struct {
+		/* response identifier */
+		__le16 proc_id;
+		/* error */
+		uint8_t error;
+		/* demod stats */
+		struct as10x_demod_stats stats;
+	} __packed rsp;
+} __packed;
+
+union as10x_get_impulse_resp {
+	/* request */
+	struct {
+		/* request identifier */
+		__le16 proc_id;
+	} __packed req;
+	/* response */
+	struct {
+		/* response identifier */
+		__le16 proc_id;
+		/* error */
+		uint8_t error;
+		/* impulse response ready */
+		uint8_t is_ready;
+	} __packed rsp;
+} __packed;
+
+union as10x_fw_context {
+	/* request */
+	struct {
+		/* request identifier */
+		__le16 proc_id;
+		/* value to write (for set context)*/
+		struct as10x_register_value reg_val;
+		/* context tag */
+		__le16 tag;
+		/* context request type */
+		__le16 type;
+	} __packed req;
+	/* response */
+	struct {
+		/* response identifier */
+		__le16 proc_id;
+		/* value read (for get context) */
+		struct as10x_register_value reg_val;
+		/* context request type */
+		__le16 type;
+		/* error */
+		uint8_t error;
+	} __packed rsp;
+} __packed;
+
+union as10x_set_register {
+	/* request */
+	struct {
+		/* response identifier */
+		__le16 proc_id;
+		/* register description */
+		struct as10x_register_addr reg_addr;
+		/* register content */
+		struct as10x_register_value reg_val;
+	} __packed req;
+	/* response */
+	struct {
+		/* response identifier */
+		__le16 proc_id;
+		/* error */
+		uint8_t error;
+	} __packed rsp;
+} __packed;
+
+union as10x_get_register {
+	/* request */
+	struct {
+		/* response identifier */
+		__le16 proc_id;
+		/* register description */
+		struct as10x_register_addr reg_addr;
+	} __packed req;
+	/* response */
+	struct {
+		/* response identifier */
+		__le16 proc_id;
+		/* error */
+		uint8_t error;
+		/* register content */
+		struct as10x_register_value reg_val;
+	} __packed rsp;
+} __packed;
+
+union as10x_cfg_change_mode {
+	/* request */
+	struct {
+		/* request identifier */
+		__le16 proc_id;
+		/* mode */
+		uint8_t mode;
+	} __packed req;
+	/* response */
+	struct {
+		/* response identifier */
+		__le16 proc_id;
+		/* error */
+		uint8_t error;
+	} __packed rsp;
+} __packed;
+
+struct as10x_cmd_header_t {
+	__le16 req_id;
+	__le16 prog;
+	__le16 version;
+	__le16 data_len;
+} __packed;
+
+#define DUMP_BLOCK_SIZE 16
+
+union as10x_dump_memory {
+	/* request */
+	struct {
+		/* request identifier */
+		__le16 proc_id;
+		/* dump memory type request */
+		uint8_t dump_req;
+		/* register description */
+		struct as10x_register_addr reg_addr;
+		/* nb blocks to read */
+		__le16 num_blocks;
+	} __packed req;
+	/* response */
+	struct {
+		/* response identifier */
+		__le16 proc_id;
+		/* error */
+		uint8_t error;
+		/* dump response */
+		uint8_t dump_rsp;
+		/* data */
+		union {
+			uint8_t  data8[DUMP_BLOCK_SIZE];
+			__le16 data16[DUMP_BLOCK_SIZE / sizeof(__le16)];
+			__le32 data32[DUMP_BLOCK_SIZE / sizeof(__le32)];
+		} __packed u;
+	} __packed rsp;
+} __packed;
+
+union as10x_dumplog_memory {
+	struct {
+		/* request identifier */
+		__le16 proc_id;
+		/* dump memory type request */
+		uint8_t dump_req;
+	} __packed req;
+	struct {
+		/* request identifier */
+		__le16 proc_id;
+		/* error */
+		uint8_t error;
+		/* dump response */
+		uint8_t dump_rsp;
+		/* dump data */
+		uint8_t data[DUMP_BLOCK_SIZE];
+	} __packed rsp;
+} __packed;
+
+union as10x_raw_data {
+	/* request */
+	struct {
+		__le16 proc_id;
+		uint8_t data[64 - sizeof(struct as10x_cmd_header_t)
+			     - 2 /* proc_id */];
+	} __packed req;
+	/* response */
+	struct {
+		__le16 proc_id;
+		uint8_t error;
+		uint8_t data[64 - sizeof(struct as10x_cmd_header_t)
+			     - 2 /* proc_id */ - 1 /* rc */];
+	} __packed rsp;
+} __packed;
+
+struct as10x_cmd_t {
+	struct as10x_cmd_header_t header;
+	union {
+		union as10x_turn_on		turn_on;
+		union as10x_turn_off		turn_off;
+		union as10x_set_tune		set_tune;
+		union as10x_get_tune_status	get_tune_status;
+		union as10x_get_tps		get_tps;
+		union as10x_common		common;
+		union as10x_add_pid_filter	add_pid_filter;
+		union as10x_del_pid_filter	del_pid_filter;
+		union as10x_start_streaming	start_streaming;
+		union as10x_stop_streaming	stop_streaming;
+		union as10x_get_demod_stats	get_demod_stats;
+		union as10x_get_impulse_resp	get_impulse_rsp;
+		union as10x_fw_context		context;
+		union as10x_set_register	set_register;
+		union as10x_get_register	get_register;
+		union as10x_cfg_change_mode	cfg_change_mode;
+		union as10x_dump_memory		dump_memory;
+		union as10x_dumplog_memory	dumplog_memory;
+		union as10x_raw_data		raw_data;
+	} __packed body;
+} __packed;
+
+struct as10x_token_cmd_t {
+	/* token cmd */
+	struct as10x_cmd_t c;
+	/* token response */
+	struct as10x_cmd_t r;
+} __packed;
+
+
+/**************************/
+/* FUNCTION DECLARATION   */
+/**************************/
+
+void as10x_cmd_build(struct as10x_cmd_t *pcmd, uint16_t proc_id,
+		      uint16_t cmd_len);
+int as10x_rsp_parse(struct as10x_cmd_t *r, uint16_t proc_id);
+
+/* as10x cmd */
+int as10x_cmd_turn_on(struct as10x_bus_adapter_t *adap);
+int as10x_cmd_turn_off(struct as10x_bus_adapter_t *adap);
+
+int as10x_cmd_set_tune(struct as10x_bus_adapter_t *adap,
+		       struct as10x_tune_args *ptune);
+
+int as10x_cmd_get_tune_status(struct as10x_bus_adapter_t *adap,
+			      struct as10x_tune_status *pstatus);
+
+int as10x_cmd_get_tps(struct as10x_bus_adapter_t *adap,
+		      struct as10x_tps *ptps);
+
+int as10x_cmd_get_demod_stats(struct as10x_bus_adapter_t  *adap,
+			      struct as10x_demod_stats *pdemod_stats);
+
+int as10x_cmd_get_impulse_resp(struct as10x_bus_adapter_t *adap,
+			       uint8_t *is_ready);
+
+/* as10x cmd stream */
+int as10x_cmd_add_PID_filter(struct as10x_bus_adapter_t *adap,
+			     struct as10x_ts_filter *filter);
+int as10x_cmd_del_PID_filter(struct as10x_bus_adapter_t *adap,
+			     uint16_t pid_value);
+
+int as10x_cmd_start_streaming(struct as10x_bus_adapter_t *adap);
+int as10x_cmd_stop_streaming(struct as10x_bus_adapter_t *adap);
+
+/* as10x cmd cfg */
+int as10x_cmd_set_context(struct as10x_bus_adapter_t *adap,
+			  uint16_t tag,
+			  uint32_t value);
+int as10x_cmd_get_context(struct as10x_bus_adapter_t *adap,
+			  uint16_t tag,
+			  uint32_t *pvalue);
+
+int as10x_cmd_eLNA_change_mode(struct as10x_bus_adapter_t *adap, uint8_t mode);
+int as10x_context_rsp_parse(struct as10x_cmd_t *prsp, uint16_t proc_id);
+#endif
diff --git a/drivers/media/usb/as102/as10x_cmd_cfg.c b/drivers/media/usb/as102/as10x_cmd_cfg.c
new file mode 100644
index 0000000..fabbfea
--- /dev/null
+++ b/drivers/media/usb/as102/as10x_cmd_cfg.c
@@ -0,0 +1,201 @@
+/*
+ * Abilis Systems Single DVB-T Receiver
+ * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include "as102_drv.h"
+#include "as10x_cmd.h"
+
+/***************************/
+/* FUNCTION DEFINITION     */
+/***************************/
+
+/**
+ * as10x_cmd_get_context - Send get context command to AS10x
+ * @adap:      pointer to AS10x bus adapter
+ * @tag:       context tag
+ * @pvalue:    pointer where to store context value read
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_cmd_get_context(struct as10x_bus_adapter_t *adap, uint16_t tag,
+			  uint32_t *pvalue)
+{
+	int  error;
+	struct as10x_cmd_t *pcmd, *prsp;
+
+	pcmd = adap->cmd;
+	prsp = adap->rsp;
+
+	/* prepare command */
+	as10x_cmd_build(pcmd, (++adap->cmd_xid),
+			sizeof(pcmd->body.context.req));
+
+	/* fill command */
+	pcmd->body.context.req.proc_id = cpu_to_le16(CONTROL_PROC_CONTEXT);
+	pcmd->body.context.req.tag = cpu_to_le16(tag);
+	pcmd->body.context.req.type = cpu_to_le16(GET_CONTEXT_DATA);
+
+	/* send command */
+	if (adap->ops->xfer_cmd) {
+		error  = adap->ops->xfer_cmd(adap,
+					     (uint8_t *) pcmd,
+					     sizeof(pcmd->body.context.req)
+					     + HEADER_SIZE,
+					     (uint8_t *) prsp,
+					     sizeof(prsp->body.context.rsp)
+					     + HEADER_SIZE);
+	} else {
+		error = AS10X_CMD_ERROR;
+	}
+
+	if (error < 0)
+		goto out;
+
+	/* parse response: context command do not follow the common response */
+	/* structure -> specific handling response parse required            */
+	error = as10x_context_rsp_parse(prsp, CONTROL_PROC_CONTEXT_RSP);
+
+	if (error == 0) {
+		/* Response OK -> get response data */
+		*pvalue = le32_to_cpu((__force __le32)prsp->body.context.rsp.reg_val.u.value32);
+		/* value returned is always a 32-bit value */
+	}
+
+out:
+	return error;
+}
+
+/**
+ * as10x_cmd_set_context - send set context command to AS10x
+ * @adap:      pointer to AS10x bus adapter
+ * @tag:       context tag
+ * @value:     value to set in context
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_cmd_set_context(struct as10x_bus_adapter_t *adap, uint16_t tag,
+			  uint32_t value)
+{
+	int error;
+	struct as10x_cmd_t *pcmd, *prsp;
+
+	pcmd = adap->cmd;
+	prsp = adap->rsp;
+
+	/* prepare command */
+	as10x_cmd_build(pcmd, (++adap->cmd_xid),
+			sizeof(pcmd->body.context.req));
+
+	/* fill command */
+	pcmd->body.context.req.proc_id = cpu_to_le16(CONTROL_PROC_CONTEXT);
+	/* pcmd->body.context.req.reg_val.mode initialization is not required */
+	pcmd->body.context.req.reg_val.u.value32 = (__force u32)cpu_to_le32(value);
+	pcmd->body.context.req.tag = cpu_to_le16(tag);
+	pcmd->body.context.req.type = cpu_to_le16(SET_CONTEXT_DATA);
+
+	/* send command */
+	if (adap->ops->xfer_cmd) {
+		error  = adap->ops->xfer_cmd(adap,
+					     (uint8_t *) pcmd,
+					     sizeof(pcmd->body.context.req)
+					     + HEADER_SIZE,
+					     (uint8_t *) prsp,
+					     sizeof(prsp->body.context.rsp)
+					     + HEADER_SIZE);
+	} else {
+		error = AS10X_CMD_ERROR;
+	}
+
+	if (error < 0)
+		goto out;
+
+	/* parse response: context command do not follow the common response */
+	/* structure -> specific handling response parse required            */
+	error = as10x_context_rsp_parse(prsp, CONTROL_PROC_CONTEXT_RSP);
+
+out:
+	return error;
+}
+
+/**
+ * as10x_cmd_eLNA_change_mode - send eLNA change mode command to AS10x
+ * @adap:      pointer to AS10x bus adapter
+ * @mode:      mode selected:
+ *		- ON    : 0x0 => eLNA always ON
+ *		- OFF   : 0x1 => eLNA always OFF
+ *		- AUTO  : 0x2 => eLNA follow hysteresis parameters
+ *				 to be ON or OFF
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_cmd_eLNA_change_mode(struct as10x_bus_adapter_t *adap, uint8_t mode)
+{
+	int error;
+	struct as10x_cmd_t *pcmd, *prsp;
+
+	pcmd = adap->cmd;
+	prsp = adap->rsp;
+
+	/* prepare command */
+	as10x_cmd_build(pcmd, (++adap->cmd_xid),
+			sizeof(pcmd->body.cfg_change_mode.req));
+
+	/* fill command */
+	pcmd->body.cfg_change_mode.req.proc_id =
+		cpu_to_le16(CONTROL_PROC_ELNA_CHANGE_MODE);
+	pcmd->body.cfg_change_mode.req.mode = mode;
+
+	/* send command */
+	if (adap->ops->xfer_cmd) {
+		error  = adap->ops->xfer_cmd(adap, (uint8_t *) pcmd,
+				sizeof(pcmd->body.cfg_change_mode.req)
+				+ HEADER_SIZE, (uint8_t *) prsp,
+				sizeof(prsp->body.cfg_change_mode.rsp)
+				+ HEADER_SIZE);
+	} else {
+		error = AS10X_CMD_ERROR;
+	}
+
+	if (error < 0)
+		goto out;
+
+	/* parse response */
+	error = as10x_rsp_parse(prsp, CONTROL_PROC_ELNA_CHANGE_MODE_RSP);
+
+out:
+	return error;
+}
+
+/**
+ * as10x_context_rsp_parse - Parse context command response
+ * @prsp:       pointer to AS10x command response buffer
+ * @proc_id:    id of the command
+ *
+ * Since the contex command response does not follow the common
+ * response, a specific parse function is required.
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_context_rsp_parse(struct as10x_cmd_t *prsp, uint16_t proc_id)
+{
+	int err;
+
+	err = prsp->body.context.rsp.error;
+
+	if ((err == 0) &&
+	    (le16_to_cpu(prsp->body.context.rsp.proc_id) == proc_id)) {
+		return 0;
+	}
+	return AS10X_CMD_ERROR;
+}
diff --git a/drivers/media/usb/as102/as10x_cmd_stream.c b/drivers/media/usb/as102/as10x_cmd_stream.c
new file mode 100644
index 0000000..126aea9
--- /dev/null
+++ b/drivers/media/usb/as102/as10x_cmd_stream.c
@@ -0,0 +1,207 @@
+/*
+ * Abilis Systems Single DVB-T Receiver
+ * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include "as102_drv.h"
+#include "as10x_cmd.h"
+
+/**
+ * as10x_cmd_add_PID_filter - send add filter command to AS10x
+ * @adap:      pointer to AS10x bus adapter
+ * @filter:    TSFilter filter for DVB-T
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_cmd_add_PID_filter(struct as10x_bus_adapter_t *adap,
+			     struct as10x_ts_filter *filter)
+{
+	int error;
+	struct as10x_cmd_t *pcmd, *prsp;
+
+	pcmd = adap->cmd;
+	prsp = adap->rsp;
+
+	/* prepare command */
+	as10x_cmd_build(pcmd, (++adap->cmd_xid),
+			sizeof(pcmd->body.add_pid_filter.req));
+
+	/* fill command */
+	pcmd->body.add_pid_filter.req.proc_id =
+		cpu_to_le16(CONTROL_PROC_SETFILTER);
+	pcmd->body.add_pid_filter.req.pid = cpu_to_le16(filter->pid);
+	pcmd->body.add_pid_filter.req.stream_type = filter->type;
+
+	if (filter->idx < 16)
+		pcmd->body.add_pid_filter.req.idx = filter->idx;
+	else
+		pcmd->body.add_pid_filter.req.idx = 0xFF;
+
+	/* send command */
+	if (adap->ops->xfer_cmd) {
+		error = adap->ops->xfer_cmd(adap, (uint8_t *) pcmd,
+				sizeof(pcmd->body.add_pid_filter.req)
+				+ HEADER_SIZE, (uint8_t *) prsp,
+				sizeof(prsp->body.add_pid_filter.rsp)
+				+ HEADER_SIZE);
+	} else {
+		error = AS10X_CMD_ERROR;
+	}
+
+	if (error < 0)
+		goto out;
+
+	/* parse response */
+	error = as10x_rsp_parse(prsp, CONTROL_PROC_SETFILTER_RSP);
+
+	if (error == 0) {
+		/* Response OK -> get response data */
+		filter->idx = prsp->body.add_pid_filter.rsp.filter_id;
+	}
+
+out:
+	return error;
+}
+
+/**
+ * as10x_cmd_del_PID_filter - Send delete filter command to AS10x
+ * @adap:         pointer to AS10x bus adapte
+ * @pid_value:    PID to delete
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_cmd_del_PID_filter(struct as10x_bus_adapter_t *adap,
+			     uint16_t pid_value)
+{
+	int error;
+	struct as10x_cmd_t *pcmd, *prsp;
+
+	pcmd = adap->cmd;
+	prsp = adap->rsp;
+
+	/* prepare command */
+	as10x_cmd_build(pcmd, (++adap->cmd_xid),
+			sizeof(pcmd->body.del_pid_filter.req));
+
+	/* fill command */
+	pcmd->body.del_pid_filter.req.proc_id =
+		cpu_to_le16(CONTROL_PROC_REMOVEFILTER);
+	pcmd->body.del_pid_filter.req.pid = cpu_to_le16(pid_value);
+
+	/* send command */
+	if (adap->ops->xfer_cmd) {
+		error = adap->ops->xfer_cmd(adap, (uint8_t *) pcmd,
+				sizeof(pcmd->body.del_pid_filter.req)
+				+ HEADER_SIZE, (uint8_t *) prsp,
+				sizeof(prsp->body.del_pid_filter.rsp)
+				+ HEADER_SIZE);
+	} else {
+		error = AS10X_CMD_ERROR;
+	}
+
+	if (error < 0)
+		goto out;
+
+	/* parse response */
+	error = as10x_rsp_parse(prsp, CONTROL_PROC_REMOVEFILTER_RSP);
+
+out:
+	return error;
+}
+
+/**
+ * as10x_cmd_start_streaming - Send start streaming command to AS10x
+ * @adap:   pointer to AS10x bus adapter
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_cmd_start_streaming(struct as10x_bus_adapter_t *adap)
+{
+	int error;
+	struct as10x_cmd_t *pcmd, *prsp;
+
+	pcmd = adap->cmd;
+	prsp = adap->rsp;
+
+	/* prepare command */
+	as10x_cmd_build(pcmd, (++adap->cmd_xid),
+			sizeof(pcmd->body.start_streaming.req));
+
+	/* fill command */
+	pcmd->body.start_streaming.req.proc_id =
+		cpu_to_le16(CONTROL_PROC_START_STREAMING);
+
+	/* send command */
+	if (adap->ops->xfer_cmd) {
+		error = adap->ops->xfer_cmd(adap, (uint8_t *) pcmd,
+				sizeof(pcmd->body.start_streaming.req)
+				+ HEADER_SIZE, (uint8_t *) prsp,
+				sizeof(prsp->body.start_streaming.rsp)
+				+ HEADER_SIZE);
+	} else {
+		error = AS10X_CMD_ERROR;
+	}
+
+	if (error < 0)
+		goto out;
+
+	/* parse response */
+	error = as10x_rsp_parse(prsp, CONTROL_PROC_START_STREAMING_RSP);
+
+out:
+	return error;
+}
+
+/**
+ * as10x_cmd_stop_streaming - Send stop streaming command to AS10x
+ * @adap:   pointer to AS10x bus adapter
+ *
+ * Return 0 on success or negative value in case of error.
+ */
+int as10x_cmd_stop_streaming(struct as10x_bus_adapter_t *adap)
+{
+	int8_t error;
+	struct as10x_cmd_t *pcmd, *prsp;
+
+	pcmd = adap->cmd;
+	prsp = adap->rsp;
+
+	/* prepare command */
+	as10x_cmd_build(pcmd, (++adap->cmd_xid),
+			sizeof(pcmd->body.stop_streaming.req));
+
+	/* fill command */
+	pcmd->body.stop_streaming.req.proc_id =
+		cpu_to_le16(CONTROL_PROC_STOP_STREAMING);
+
+	/* send command */
+	if (adap->ops->xfer_cmd) {
+		error = adap->ops->xfer_cmd(adap, (uint8_t *) pcmd,
+				sizeof(pcmd->body.stop_streaming.req)
+				+ HEADER_SIZE, (uint8_t *) prsp,
+				sizeof(prsp->body.stop_streaming.rsp)
+				+ HEADER_SIZE);
+	} else {
+		error = AS10X_CMD_ERROR;
+	}
+
+	if (error < 0)
+		goto out;
+
+	/* parse response */
+	error = as10x_rsp_parse(prsp, CONTROL_PROC_STOP_STREAMING_RSP);
+
+out:
+	return error;
+}
diff --git a/drivers/media/usb/as102/as10x_handle.h b/drivers/media/usb/as102/as10x_handle.h
new file mode 100644
index 0000000..d6b58c7
--- /dev/null
+++ b/drivers/media/usb/as102/as10x_handle.h
@@ -0,0 +1,51 @@
+/*
+ * Abilis Systems Single DVB-T Receiver
+ * Copyright (C) 2008 Pierrick Hascoet <pierrick.hascoet@abilis.com>
+ *
+ * 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, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#ifndef _AS10X_HANDLE_H
+#define _AS10X_HANDLE_H
+struct as10x_bus_adapter_t;
+struct as102_dev_t;
+
+#include "as10x_cmd.h"
+
+/* values for "mode" field */
+#define REGMODE8	8
+#define REGMODE16	16
+#define REGMODE32	32
+
+struct as102_priv_ops_t {
+	int (*upload_fw_pkt)(struct as10x_bus_adapter_t *bus_adap,
+			      unsigned char *buf, int buflen, int swap32);
+
+	int (*send_cmd)(struct as10x_bus_adapter_t *bus_adap,
+			 unsigned char *buf, int buflen);
+
+	int (*xfer_cmd)(struct as10x_bus_adapter_t *bus_adap,
+			 unsigned char *send_buf, int send_buf_len,
+			 unsigned char *recv_buf, int recv_buf_len);
+
+	int (*start_stream)(struct as102_dev_t *dev);
+	void (*stop_stream)(struct as102_dev_t *dev);
+
+	int (*reset_target)(struct as10x_bus_adapter_t *bus_adap);
+
+	int (*read_write)(struct as10x_bus_adapter_t *bus_adap, uint8_t mode,
+			  uint32_t rd_addr, uint16_t rd_len,
+			  uint32_t wr_addr, uint16_t wr_len);
+
+	int (*as102_read_ep2)(struct as10x_bus_adapter_t *bus_adap,
+			       unsigned char *recv_buf,
+			       int recv_buf_len);
+};
+#endif
diff --git a/drivers/media/usb/au0828/Kconfig b/drivers/media/usb/au0828/Kconfig
new file mode 100644
index 0000000..65fc067
--- /dev/null
+++ b/drivers/media/usb/au0828/Kconfig
@@ -0,0 +1,38 @@
+
+config VIDEO_AU0828
+	tristate "Auvitek AU0828 support"
+	depends on I2C && INPUT && DVB_CORE && USB && VIDEO_V4L2
+	select I2C_ALGOBIT
+	select VIDEO_TVEEPROM
+	select VIDEOBUF2_VMALLOC if VIDEO_V4L2
+	select DVB_AU8522_DTV if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_XC5000 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_MXL5007T if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_TDA18271 if MEDIA_SUBDRV_AUTOSELECT
+	---help---
+	  This is a hybrid analog/digital tv capture driver for
+	  Auvitek's AU0828 USB device.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called au0828
+
+config VIDEO_AU0828_V4L2
+	bool "Auvitek AU0828 v4l2 analog video support"
+	depends on VIDEO_AU0828
+	depends on VIDEO_V4L2=y || VIDEO_V4L2=VIDEO_AU0828
+	select DVB_AU8522_V4L if MEDIA_SUBDRV_AUTOSELECT
+	select VIDEO_TUNER
+	default y
+	---help---
+	  This is a video4linux driver for Auvitek's USB device.
+
+	  Choose Y here to include support for v4l2 analog video
+	  capture within the au0828 driver.
+
+config VIDEO_AU0828_RC
+	bool "AU0828 Remote Controller support"
+	depends on RC_CORE
+	depends on !(RC_CORE=m && VIDEO_AU0828=y)
+	depends on VIDEO_AU0828
+	---help---
+	   Enables Remote Controller support on au0828 driver.
diff --git a/drivers/media/usb/au0828/Makefile b/drivers/media/usb/au0828/Makefile
new file mode 100644
index 0000000..5691881
--- /dev/null
+++ b/drivers/media/usb/au0828/Makefile
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0
+au0828-objs	:= au0828-core.o au0828-i2c.o au0828-cards.o au0828-dvb.o
+
+ifeq ($(CONFIG_VIDEO_AU0828_V4L2),y)
+  au0828-objs   += au0828-video.o au0828-vbi.o
+endif
+
+ifeq ($(CONFIG_VIDEO_AU0828_RC),y)
+  au0828-objs   += au0828-input.o
+endif
+
+obj-$(CONFIG_VIDEO_AU0828) += au0828.o
+
+ccflags-y += -Idrivers/media/tuners
+ccflags-y += -Idrivers/media/dvb-frontends
+
+ccflags-y += $(extra-cflags-y) $(extra-cflags-m)
diff --git a/drivers/media/usb/au0828/au0828-cards.c b/drivers/media/usb/au0828/au0828-cards.c
new file mode 100644
index 0000000..43bfa77
--- /dev/null
+++ b/drivers/media/usb/au0828/au0828-cards.c
@@ -0,0 +1,356 @@
+/*
+ *  Driver for the Auvitek USB bridge
+ *
+ *  Copyright (c) 2008 Steven Toth <stoth@linuxtv.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *
+ *  GNU General Public License for more details.
+ */
+
+#include "au0828.h"
+#include "au0828-cards.h"
+#include "au8522.h"
+#include "media/tuner.h"
+#include "media/v4l2-common.h"
+
+static void hvr950q_cs5340_audio(void *priv, int enable)
+{
+	/* Because the HVR-950q shares an i2s bus between the cs5340 and the
+	   au8522, we need to hold cs5340 in reset when using the au8522 */
+	struct au0828_dev *dev = priv;
+	if (enable == 1)
+		au0828_set(dev, REG_000, 0x10);
+	else
+		au0828_clear(dev, REG_000, 0x10);
+}
+
+/*
+ * WARNING: There's a quirks table at sound/usb/quirks-table.h
+ * that should also be updated every time a new device with V4L2 support
+ * is added here.
+ */
+struct au0828_board au0828_boards[] = {
+	[AU0828_BOARD_UNKNOWN] = {
+		.name	= "Unknown board",
+		.tuner_type = -1U,
+		.tuner_addr = ADDR_UNSET,
+	},
+	[AU0828_BOARD_HAUPPAUGE_HVR850] = {
+		.name	= "Hauppauge HVR850",
+		.tuner_type = TUNER_XC5000,
+		.tuner_addr = 0x61,
+		.has_ir_i2c = 1,
+		.has_analog = 1,
+		.i2c_clk_divider = AU0828_I2C_CLK_250KHZ,
+		.input = {
+			{
+				.type = AU0828_VMUX_TELEVISION,
+				.vmux = AU8522_COMPOSITE_CH4_SIF,
+				.amux = AU8522_AUDIO_SIF,
+			},
+			{
+				.type = AU0828_VMUX_COMPOSITE,
+				.vmux = AU8522_COMPOSITE_CH1,
+				.amux = AU8522_AUDIO_NONE,
+				.audio_setup = hvr950q_cs5340_audio,
+			},
+			{
+				.type = AU0828_VMUX_SVIDEO,
+				.vmux = AU8522_SVIDEO_CH13,
+				.amux = AU8522_AUDIO_NONE,
+				.audio_setup = hvr950q_cs5340_audio,
+			},
+		},
+	},
+	[AU0828_BOARD_HAUPPAUGE_HVR950Q] = {
+		.name	= "Hauppauge HVR950Q",
+		.tuner_type = TUNER_XC5000,
+		.tuner_addr = 0x61,
+		.has_ir_i2c = 1,
+		.has_analog = 1,
+		.i2c_clk_divider = AU0828_I2C_CLK_250KHZ,
+		.input = {
+			{
+				.type = AU0828_VMUX_TELEVISION,
+				.vmux = AU8522_COMPOSITE_CH4_SIF,
+				.amux = AU8522_AUDIO_SIF,
+			},
+			{
+				.type = AU0828_VMUX_COMPOSITE,
+				.vmux = AU8522_COMPOSITE_CH1,
+				.amux = AU8522_AUDIO_NONE,
+				.audio_setup = hvr950q_cs5340_audio,
+			},
+			{
+				.type = AU0828_VMUX_SVIDEO,
+				.vmux = AU8522_SVIDEO_CH13,
+				.amux = AU8522_AUDIO_NONE,
+				.audio_setup = hvr950q_cs5340_audio,
+			},
+		},
+	},
+	[AU0828_BOARD_HAUPPAUGE_HVR950Q_MXL] = {
+		.name	= "Hauppauge HVR950Q rev xxF8",
+		.tuner_type = TUNER_XC5000,
+		.tuner_addr = 0x61,
+		.i2c_clk_divider = AU0828_I2C_CLK_250KHZ,
+	},
+	[AU0828_BOARD_DVICO_FUSIONHDTV7] = {
+		.name	= "DViCO FusionHDTV USB",
+		.tuner_type = TUNER_XC5000,
+		.tuner_addr = 0x61,
+		.i2c_clk_divider = AU0828_I2C_CLK_250KHZ,
+	},
+	[AU0828_BOARD_HAUPPAUGE_WOODBURY] = {
+		.name = "Hauppauge Woodbury",
+		.tuner_type = TUNER_NXP_TDA18271,
+		.tuner_addr = 0x60,
+		.i2c_clk_divider = AU0828_I2C_CLK_250KHZ,
+	},
+};
+
+/* Tuner callback function for au0828 boards. Currently only needed
+ * for HVR1500Q, which has an xc5000 tuner.
+ */
+int au0828_tuner_callback(void *priv, int component, int command, int arg)
+{
+	struct au0828_dev *dev = priv;
+
+	dprintk(1, "%s()\n", __func__);
+
+	switch (dev->boardnr) {
+	case AU0828_BOARD_HAUPPAUGE_HVR850:
+	case AU0828_BOARD_HAUPPAUGE_HVR950Q:
+	case AU0828_BOARD_HAUPPAUGE_HVR950Q_MXL:
+	case AU0828_BOARD_DVICO_FUSIONHDTV7:
+		if (command == 0) {
+			/* Tuner Reset Command from xc5000 */
+			/* Drive the tuner into reset and out */
+			au0828_clear(dev, REG_001, 2);
+			mdelay(10);
+			au0828_set(dev, REG_001, 2);
+			mdelay(10);
+			return 0;
+		} else {
+			pr_err("%s(): Unknown command.\n", __func__);
+			return -EINVAL;
+		}
+		break;
+	}
+
+	return 0; /* Should never be here */
+}
+
+static void hauppauge_eeprom(struct au0828_dev *dev, u8 *eeprom_data)
+{
+	struct tveeprom tv;
+
+	tveeprom_hauppauge_analog(&tv, eeprom_data);
+	dev->board.tuner_type = tv.tuner_type;
+
+	/* Make sure we support the board model */
+	switch (tv.model) {
+	case 72000: /* WinTV-HVR950q (Retail, IR, ATSC/QAM */
+	case 72001: /* WinTV-HVR950q (Retail, IR, ATSC/QAM and analog video */
+	case 72101: /* WinTV-HVR950q (Retail, IR, ATSC/QAM and analog video */
+	case 72201: /* WinTV-HVR950q (OEM, IR, ATSC/QAM and analog video */
+	case 72211: /* WinTV-HVR950q (OEM, IR, ATSC/QAM and analog video */
+	case 72221: /* WinTV-HVR950q (OEM, IR, ATSC/QAM and analog video */
+	case 72231: /* WinTV-HVR950q (OEM, IR, ATSC/QAM and analog video */
+	case 72241: /* WinTV-HVR950q (OEM, No IR, ATSC/QAM and analog video */
+	case 72251: /* WinTV-HVR950q (Retail, IR, ATSC/QAM and analog video */
+	case 72261: /* WinTV-HVR950q (OEM, No IR, ATSC/QAM and analog video */
+	case 72271: /* WinTV-HVR950q (OEM, No IR, ATSC/QAM and analog video */
+	case 72281: /* WinTV-HVR950q (OEM, No IR, ATSC/QAM and analog video */
+	case 72301: /* WinTV-HVR850 (Retail, IR, ATSC and analog video */
+	case 72500: /* WinTV-HVR950q (OEM, No IR, ATSC/QAM */
+		break;
+	default:
+		pr_warn("%s: warning: unknown hauppauge model #%d\n",
+			__func__, tv.model);
+		break;
+	}
+
+	pr_info("%s: hauppauge eeprom: model=%d\n",
+	       __func__, tv.model);
+}
+
+void au0828_card_analog_fe_setup(struct au0828_dev *dev);
+
+void au0828_card_setup(struct au0828_dev *dev)
+{
+	static u8 eeprom[256];
+
+	dprintk(1, "%s()\n", __func__);
+
+	if (dev->i2c_rc == 0) {
+		dev->i2c_client.addr = 0xa0 >> 1;
+		tveeprom_read(&dev->i2c_client, eeprom, sizeof(eeprom));
+	}
+
+	switch (dev->boardnr) {
+	case AU0828_BOARD_HAUPPAUGE_HVR850:
+	case AU0828_BOARD_HAUPPAUGE_HVR950Q:
+	case AU0828_BOARD_HAUPPAUGE_HVR950Q_MXL:
+	case AU0828_BOARD_HAUPPAUGE_WOODBURY:
+		if (dev->i2c_rc == 0)
+			hauppauge_eeprom(dev, eeprom+0xa0);
+		break;
+	}
+
+	au0828_card_analog_fe_setup(dev);
+}
+
+void au0828_card_analog_fe_setup(struct au0828_dev *dev)
+{
+#ifdef CONFIG_VIDEO_AU0828_V4L2
+	struct tuner_setup tun_setup;
+	struct v4l2_subdev *sd;
+	unsigned int mode_mask = T_ANALOG_TV;
+
+	if (AUVI_INPUT(0).type != AU0828_VMUX_UNDEFINED) {
+		/* Load the analog demodulator driver (note this would need to
+		   be abstracted out if we ever need to support a different
+		   demod) */
+		sd = v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap,
+				"au8522", 0x8e >> 1, NULL);
+		if (sd == NULL)
+			pr_err("analog subdev registration failed\n");
+	}
+
+	/* Setup tuners */
+	if (dev->board.tuner_type != TUNER_ABSENT && dev->board.has_analog) {
+		/* Load the tuner module, which does the attach */
+		sd = v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap,
+				"tuner", dev->board.tuner_addr, NULL);
+		if (sd == NULL)
+			pr_err("tuner subdev registration fail\n");
+
+		tun_setup.mode_mask      = mode_mask;
+		tun_setup.type           = dev->board.tuner_type;
+		tun_setup.addr           = dev->board.tuner_addr;
+		tun_setup.tuner_callback = au0828_tuner_callback;
+		v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_type_addr,
+				     &tun_setup);
+	}
+#endif
+}
+
+/*
+ * The bridge has between 8 and 12 gpios.
+ * Regs 1 and 0 deal with output enables.
+ * Regs 3 and 2 deal with direction.
+ */
+void au0828_gpio_setup(struct au0828_dev *dev)
+{
+	dprintk(1, "%s()\n", __func__);
+
+	switch (dev->boardnr) {
+	case AU0828_BOARD_HAUPPAUGE_HVR850:
+	case AU0828_BOARD_HAUPPAUGE_HVR950Q:
+	case AU0828_BOARD_HAUPPAUGE_HVR950Q_MXL:
+	case AU0828_BOARD_HAUPPAUGE_WOODBURY:
+		/* GPIO's
+		 * 4 - CS5340
+		 * 5 - AU8522 Demodulator
+		 * 6 - eeprom W/P
+		 * 7 - power supply
+		 * 9 - XC5000 Tuner
+		 */
+
+		/* Set relevant GPIOs as outputs (leave the EEPROM W/P
+		   as an input since we will never touch it and it has
+		   a pullup) */
+		au0828_write(dev, REG_003, 0x02);
+		au0828_write(dev, REG_002, 0x80 | 0x20 | 0x10);
+
+		/* Into reset */
+		au0828_write(dev, REG_001, 0x0);
+		au0828_write(dev, REG_000, 0x0);
+		msleep(50);
+
+		/* Bring power supply out of reset */
+		au0828_write(dev, REG_000, 0x80);
+		msleep(50);
+
+		/* Bring xc5000 and au8522 out of reset (leave the
+		   cs5340 in reset until needed) */
+		au0828_write(dev, REG_001, 0x02); /* xc5000 */
+		au0828_write(dev, REG_000, 0x80 | 0x20); /* PS + au8522 */
+
+		msleep(250);
+		break;
+	case AU0828_BOARD_DVICO_FUSIONHDTV7:
+		/* GPIO's
+		 * 6 - ?
+		 * 8 - AU8522 Demodulator
+		 * 9 - XC5000 Tuner
+		 */
+
+		/* Into reset */
+		au0828_write(dev, REG_003, 0x02);
+		au0828_write(dev, REG_002, 0xa0);
+		au0828_write(dev, REG_001, 0x0);
+		au0828_write(dev, REG_000, 0x0);
+		msleep(100);
+
+		/* Out of reset */
+		au0828_write(dev, REG_003, 0x02);
+		au0828_write(dev, REG_002, 0xa0);
+		au0828_write(dev, REG_001, 0x02);
+		au0828_write(dev, REG_000, 0xa0);
+		msleep(250);
+		break;
+	}
+}
+
+/* table of devices that work with this driver */
+struct usb_device_id au0828_usb_id_table[] = {
+	{ USB_DEVICE(0x2040, 0x7200),
+		.driver_info = AU0828_BOARD_HAUPPAUGE_HVR950Q },
+	{ USB_DEVICE(0x2040, 0x7240),
+		.driver_info = AU0828_BOARD_HAUPPAUGE_HVR850 },
+	{ USB_DEVICE(0x0fe9, 0xd620),
+		.driver_info = AU0828_BOARD_DVICO_FUSIONHDTV7 },
+	{ USB_DEVICE(0x2040, 0x7210),
+		.driver_info = AU0828_BOARD_HAUPPAUGE_HVR950Q },
+	{ USB_DEVICE(0x2040, 0x7217),
+		.driver_info = AU0828_BOARD_HAUPPAUGE_HVR950Q },
+	{ USB_DEVICE(0x2040, 0x721b),
+		.driver_info = AU0828_BOARD_HAUPPAUGE_HVR950Q },
+	{ USB_DEVICE(0x2040, 0x721e),
+		.driver_info = AU0828_BOARD_HAUPPAUGE_HVR950Q },
+	{ USB_DEVICE(0x2040, 0x721f),
+		.driver_info = AU0828_BOARD_HAUPPAUGE_HVR950Q },
+	{ USB_DEVICE(0x2040, 0x7280),
+		.driver_info = AU0828_BOARD_HAUPPAUGE_HVR950Q },
+	{ USB_DEVICE(0x0fd9, 0x0008),
+		.driver_info = AU0828_BOARD_HAUPPAUGE_HVR950Q },
+	{ USB_DEVICE(0x2040, 0x7201),
+		.driver_info = AU0828_BOARD_HAUPPAUGE_HVR950Q_MXL },
+	{ USB_DEVICE(0x2040, 0x7211),
+		.driver_info = AU0828_BOARD_HAUPPAUGE_HVR950Q_MXL },
+	{ USB_DEVICE(0x2040, 0x7281),
+		.driver_info = AU0828_BOARD_HAUPPAUGE_HVR950Q_MXL },
+	{ USB_DEVICE(0x05e1, 0x0480),
+		.driver_info = AU0828_BOARD_HAUPPAUGE_WOODBURY },
+	{ USB_DEVICE(0x2040, 0x8200),
+		.driver_info = AU0828_BOARD_HAUPPAUGE_WOODBURY },
+	{ USB_DEVICE(0x2040, 0x7260),
+		.driver_info = AU0828_BOARD_HAUPPAUGE_HVR950Q },
+	{ USB_DEVICE(0x2040, 0x7213),
+		.driver_info = AU0828_BOARD_HAUPPAUGE_HVR950Q },
+	{ USB_DEVICE(0x2040, 0x7270),
+		.driver_info = AU0828_BOARD_HAUPPAUGE_HVR950Q },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(usb, au0828_usb_id_table);
diff --git a/drivers/media/usb/au0828/au0828-cards.h b/drivers/media/usb/au0828/au0828-cards.h
new file mode 100644
index 0000000..dbd8a90
--- /dev/null
+++ b/drivers/media/usb/au0828/au0828-cards.h
@@ -0,0 +1,23 @@
+/*
+ *  Driver for the Auvitek USB bridge
+ *
+ *  Copyright (c) 2008 Steven Toth <stoth@linuxtv.org>
+ *
+ *  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.
+ */
+
+#define AU0828_BOARD_UNKNOWN		0
+#define AU0828_BOARD_HAUPPAUGE_HVR950Q	1
+#define AU0828_BOARD_HAUPPAUGE_HVR850	2
+#define AU0828_BOARD_DVICO_FUSIONHDTV7	3
+#define AU0828_BOARD_HAUPPAUGE_HVR950Q_MXL	4
+#define AU0828_BOARD_HAUPPAUGE_WOODBURY	5
diff --git a/drivers/media/usb/au0828/au0828-core.c b/drivers/media/usb/au0828/au0828-core.c
new file mode 100644
index 0000000..cd363a2
--- /dev/null
+++ b/drivers/media/usb/au0828/au0828-core.c
@@ -0,0 +1,758 @@
+/*
+ *  Driver for the Auvitek USB bridge
+ *
+ *  Copyright (c) 2008 Steven Toth <stoth@linuxtv.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *
+ *  GNU General Public License for more details.
+ */
+
+#include "au0828.h"
+#include "au8522.h"
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <linux/mutex.h>
+
+/* Due to enum tuner_pad_index */
+#include <media/tuner.h>
+
+/*
+ * 1 = General debug messages
+ * 2 = USB handling
+ * 4 = I2C related
+ * 8 = Bridge related
+ * 16 = IR related
+ */
+int au0828_debug;
+module_param_named(debug, au0828_debug, int, 0644);
+MODULE_PARM_DESC(debug,
+		 "set debug bitmask: 1=general, 2=USB, 4=I2C, 8=bridge, 16=IR");
+
+static unsigned int disable_usb_speed_check;
+module_param(disable_usb_speed_check, int, 0444);
+MODULE_PARM_DESC(disable_usb_speed_check,
+		 "override min bandwidth requirement of 480M bps");
+
+#define _AU0828_BULKPIPE 0x03
+#define _BULKPIPESIZE 0xffff
+
+static int send_control_msg(struct au0828_dev *dev, u16 request, u32 value,
+			    u16 index);
+static int recv_control_msg(struct au0828_dev *dev, u16 request, u32 value,
+	u16 index, unsigned char *cp, u16 size);
+
+/* USB Direction */
+#define CMD_REQUEST_IN		0x00
+#define CMD_REQUEST_OUT		0x01
+
+u32 au0828_readreg(struct au0828_dev *dev, u16 reg)
+{
+	u8 result = 0;
+
+	recv_control_msg(dev, CMD_REQUEST_IN, 0, reg, &result, 1);
+	dprintk(8, "%s(0x%04x) = 0x%02x\n", __func__, reg, result);
+
+	return result;
+}
+
+u32 au0828_writereg(struct au0828_dev *dev, u16 reg, u32 val)
+{
+	dprintk(8, "%s(0x%04x, 0x%02x)\n", __func__, reg, val);
+	return send_control_msg(dev, CMD_REQUEST_OUT, val, reg);
+}
+
+static int send_control_msg(struct au0828_dev *dev, u16 request, u32 value,
+	u16 index)
+{
+	int status = -ENODEV;
+
+	if (dev->usbdev) {
+
+		/* cp must be memory that has been allocated by kmalloc */
+		status = usb_control_msg(dev->usbdev,
+				usb_sndctrlpipe(dev->usbdev, 0),
+				request,
+				USB_DIR_OUT | USB_TYPE_VENDOR |
+					USB_RECIP_DEVICE,
+				value, index, NULL, 0, 1000);
+
+		status = min(status, 0);
+
+		if (status < 0) {
+			pr_err("%s() Failed sending control message, error %d.\n",
+				__func__, status);
+		}
+
+	}
+
+	return status;
+}
+
+static int recv_control_msg(struct au0828_dev *dev, u16 request, u32 value,
+	u16 index, unsigned char *cp, u16 size)
+{
+	int status = -ENODEV;
+	mutex_lock(&dev->mutex);
+	if (dev->usbdev) {
+		status = usb_control_msg(dev->usbdev,
+				usb_rcvctrlpipe(dev->usbdev, 0),
+				request,
+				USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+				value, index,
+				dev->ctrlmsg, size, 1000);
+
+		status = min(status, 0);
+
+		if (status < 0) {
+			pr_err("%s() Failed receiving control message, error %d.\n",
+				__func__, status);
+		}
+
+		/* the host controller requires heap allocated memory, which
+		   is why we didn't just pass "cp" into usb_control_msg */
+		memcpy(cp, dev->ctrlmsg, size);
+	}
+	mutex_unlock(&dev->mutex);
+	return status;
+}
+
+#ifdef CONFIG_MEDIA_CONTROLLER
+static void au0828_media_graph_notify(struct media_entity *new,
+				      void *notify_data);
+#endif
+
+static void au0828_unregister_media_device(struct au0828_dev *dev)
+{
+#ifdef CONFIG_MEDIA_CONTROLLER
+	struct media_device *mdev = dev->media_dev;
+	struct media_entity_notify *notify, *nextp;
+
+	if (!mdev || !media_devnode_is_registered(mdev->devnode))
+		return;
+
+	/* Remove au0828 entity_notify callbacks */
+	list_for_each_entry_safe(notify, nextp, &mdev->entity_notify, list) {
+		if (notify->notify != au0828_media_graph_notify)
+			continue;
+		media_device_unregister_entity_notify(mdev, notify);
+	}
+
+	/* clear enable_source, disable_source */
+	mutex_lock(&mdev->graph_mutex);
+	dev->media_dev->source_priv = NULL;
+	dev->media_dev->enable_source = NULL;
+	dev->media_dev->disable_source = NULL;
+	mutex_unlock(&mdev->graph_mutex);
+
+	media_device_unregister(dev->media_dev);
+	media_device_cleanup(dev->media_dev);
+	kfree(dev->media_dev);
+	dev->media_dev = NULL;
+#endif
+}
+
+void au0828_usb_release(struct au0828_dev *dev)
+{
+	au0828_unregister_media_device(dev);
+
+	/* I2C */
+	au0828_i2c_unregister(dev);
+
+	kfree(dev);
+}
+
+static void au0828_usb_disconnect(struct usb_interface *interface)
+{
+	struct au0828_dev *dev = usb_get_intfdata(interface);
+
+	dprintk(1, "%s()\n", __func__);
+
+	/* there is a small window after disconnect, before
+	   dev->usbdev is NULL, for poll (e.g: IR) try to access
+	   the device and fill the dmesg with error messages.
+	   Set the status so poll routines can check and avoid
+	   access after disconnect.
+	*/
+	set_bit(DEV_DISCONNECTED, &dev->dev_state);
+
+	au0828_rc_unregister(dev);
+	/* Digital TV */
+	au0828_dvb_unregister(dev);
+
+	usb_set_intfdata(interface, NULL);
+	mutex_lock(&dev->mutex);
+	dev->usbdev = NULL;
+	mutex_unlock(&dev->mutex);
+	if (au0828_analog_unregister(dev)) {
+		/*
+		 * No need to call au0828_usb_release() if V4L2 is enabled,
+		 * as this is already called via au0828_usb_v4l2_release()
+		 */
+		return;
+	}
+	au0828_usb_release(dev);
+}
+
+static int au0828_media_device_init(struct au0828_dev *dev,
+				    struct usb_device *udev)
+{
+#ifdef CONFIG_MEDIA_CONTROLLER
+	struct media_device *mdev;
+
+	mdev = kzalloc(sizeof(*mdev), GFP_KERNEL);
+	if (!mdev)
+		return -ENOMEM;
+
+	/* check if media device is already initialized */
+	if (!mdev->dev)
+		media_device_usb_init(mdev, udev, udev->product);
+
+	dev->media_dev = mdev;
+#endif
+	return 0;
+}
+
+#ifdef CONFIG_MEDIA_CONTROLLER
+static void au0828_media_graph_notify(struct media_entity *new,
+				      void *notify_data)
+{
+	struct au0828_dev *dev = (struct au0828_dev *) notify_data;
+	int ret;
+	struct media_entity *entity, *mixer = NULL, *decoder = NULL;
+
+	if (!new) {
+		/*
+		 * Called during au0828 probe time to connect
+		 * entites that were created prior to registering
+		 * the notify handler. Find mixer and decoder.
+		*/
+		media_device_for_each_entity(entity, dev->media_dev) {
+			if (entity->function == MEDIA_ENT_F_AUDIO_MIXER)
+				mixer = entity;
+			else if (entity->function == MEDIA_ENT_F_ATV_DECODER)
+				decoder = entity;
+		}
+		goto create_link;
+	}
+
+	switch (new->function) {
+	case MEDIA_ENT_F_AUDIO_MIXER:
+		mixer = new;
+		if (dev->decoder)
+			decoder = dev->decoder;
+		break;
+	case MEDIA_ENT_F_ATV_DECODER:
+		/* In case, Mixer is added first, find mixer and create link */
+		media_device_for_each_entity(entity, dev->media_dev) {
+			if (entity->function == MEDIA_ENT_F_AUDIO_MIXER)
+				mixer = entity;
+		}
+		decoder = new;
+		break;
+	default:
+		break;
+	}
+
+create_link:
+	if (decoder && mixer) {
+		ret = media_create_pad_link(decoder,
+					    DEMOD_PAD_AUDIO_OUT,
+					    mixer, 0,
+					    MEDIA_LNK_FL_ENABLED);
+		if (ret)
+			dev_err(&dev->usbdev->dev,
+				"Mixer Pad Link Create Error: %d\n", ret);
+	}
+}
+
+/* Callers should hold graph_mutex */
+static int au0828_enable_source(struct media_entity *entity,
+				struct media_pipeline *pipe)
+{
+	struct media_entity  *source, *find_source;
+	struct media_entity *sink;
+	struct media_link *link, *found_link = NULL;
+	int ret = 0;
+	struct media_device *mdev = entity->graph_obj.mdev;
+	struct au0828_dev *dev;
+
+	if (!mdev)
+		return -ENODEV;
+
+	dev = mdev->source_priv;
+
+	/*
+	 * For Audio and V4L2 entity, find the link to which decoder
+	 * is the sink. Look for an active link between decoder and
+	 * source (tuner/s-video/Composite), if one exists, nothing
+	 * to do. If not, look for any  active links between source
+	 * and any other entity. If one exists, source is busy. If
+	 * source is free, setup link and start pipeline from source.
+	 * For DVB FE entity, the source for the link is the tuner.
+	 * Check if tuner is available and setup link and start
+	 * pipeline.
+	*/
+	if (entity->function == MEDIA_ENT_F_DTV_DEMOD) {
+		sink = entity;
+		find_source = dev->tuner;
+	} else {
+		/* Analog isn't configured or register failed */
+		if (!dev->decoder) {
+			ret = -ENODEV;
+			goto end;
+		}
+
+		sink = dev->decoder;
+
+		/*
+		 * Default input is tuner and default input_type
+		 * is AU0828_VMUX_TELEVISION.
+		 * FIXME:
+		 * There is a problem when s_input is called to
+		 * change the default input. s_input will try to
+		 * enable_source before attempting to change the
+		 * input on the device, and will end up enabling
+		 * default source which is tuner.
+		 *
+		 * Additional logic is necessary in au0828
+		 * to detect that the input has changed and
+		 * enable the right source.
+		*/
+
+		if (dev->input_type == AU0828_VMUX_TELEVISION)
+			find_source = dev->tuner;
+		else if (dev->input_type == AU0828_VMUX_SVIDEO ||
+			 dev->input_type == AU0828_VMUX_COMPOSITE)
+			find_source = &dev->input_ent[dev->input_type];
+		else {
+			/* unknown input - let user select input */
+			ret = 0;
+			goto end;
+		}
+	}
+
+	/* Is an active link between sink and source */
+	if (dev->active_link) {
+		/*
+		 * If DVB is using the tuner and calling entity is
+		 * audio/video, the following check will be false,
+		 * since sink is different. Result is Busy.
+		 */
+		if (dev->active_link->sink->entity == sink &&
+		    dev->active_link->source->entity == find_source) {
+			/*
+			 * Either ALSA or Video own tuner. sink is
+			 * the same for both. Prevent Video stepping
+			 * on ALSA when ALSA owns the source.
+			*/
+			if (dev->active_link_owner != entity &&
+			    dev->active_link_owner->function ==
+						MEDIA_ENT_F_AUDIO_CAPTURE) {
+				pr_debug("ALSA has the tuner\n");
+				ret = -EBUSY;
+				goto end;
+			}
+			ret = 0;
+			goto end;
+		} else {
+			ret = -EBUSY;
+			goto end;
+		}
+	}
+
+	list_for_each_entry(link, &sink->links, list) {
+		/* Check sink, and source */
+		if (link->sink->entity == sink &&
+		    link->source->entity == find_source) {
+			found_link = link;
+			break;
+		}
+	}
+
+	if (!found_link) {
+		ret = -ENODEV;
+		goto end;
+	}
+
+	/* activate link between source and sink and start pipeline */
+	source = found_link->source->entity;
+	ret = __media_entity_setup_link(found_link, MEDIA_LNK_FL_ENABLED);
+	if (ret) {
+		pr_err("Activate tuner link %s->%s. Error %d\n",
+			source->name, sink->name, ret);
+		goto end;
+	}
+
+	ret = __media_pipeline_start(entity, pipe);
+	if (ret) {
+		pr_err("Start Pipeline: %s->%s Error %d\n",
+			source->name, entity->name, ret);
+		ret = __media_entity_setup_link(found_link, 0);
+		pr_err("Deactivate link Error %d\n", ret);
+		goto end;
+	}
+	/*
+	 * save active link and active link owner to avoid audio
+	 * deactivating video owned link from disable_source and
+	 * vice versa
+	*/
+	dev->active_link = found_link;
+	dev->active_link_owner = entity;
+	dev->active_source = source;
+	dev->active_sink = sink;
+
+	pr_debug("Enabled Source: %s->%s->%s Ret %d\n",
+		 dev->active_source->name, dev->active_sink->name,
+		 dev->active_link_owner->name, ret);
+end:
+	pr_debug("au0828_enable_source() end %s %d %d\n",
+		 entity->name, entity->function, ret);
+	return ret;
+}
+
+/* Callers should hold graph_mutex */
+static void au0828_disable_source(struct media_entity *entity)
+{
+	int ret = 0;
+	struct media_device *mdev = entity->graph_obj.mdev;
+	struct au0828_dev *dev;
+
+	if (!mdev)
+		return;
+
+	dev = mdev->source_priv;
+
+	if (!dev->active_link)
+		return;
+
+	/* link is active - stop pipeline from source (tuner) */
+	if (dev->active_link->sink->entity == dev->active_sink &&
+	    dev->active_link->source->entity == dev->active_source) {
+		/*
+		 * prevent video from deactivating link when audio
+		 * has active pipeline
+		*/
+		if (dev->active_link_owner != entity)
+			return;
+		__media_pipeline_stop(entity);
+		ret = __media_entity_setup_link(dev->active_link, 0);
+		if (ret)
+			pr_err("Deactivate link Error %d\n", ret);
+
+		pr_debug("Disabled Source: %s->%s->%s Ret %d\n",
+			 dev->active_source->name, dev->active_sink->name,
+			 dev->active_link_owner->name, ret);
+
+		dev->active_link = NULL;
+		dev->active_link_owner = NULL;
+		dev->active_source = NULL;
+		dev->active_sink = NULL;
+	}
+}
+#endif
+
+static int au0828_media_device_register(struct au0828_dev *dev,
+					struct usb_device *udev)
+{
+#ifdef CONFIG_MEDIA_CONTROLLER
+	int ret;
+	struct media_entity *entity, *demod = NULL;
+	struct media_link *link;
+
+	if (!dev->media_dev)
+		return 0;
+
+	if (!media_devnode_is_registered(dev->media_dev->devnode)) {
+
+		/* register media device */
+		ret = media_device_register(dev->media_dev);
+		if (ret) {
+			dev_err(&udev->dev,
+				"Media Device Register Error: %d\n", ret);
+			return ret;
+		}
+	} else {
+		/*
+		 * Call au0828_media_graph_notify() to connect
+		 * audio graph to our graph. In this case, audio
+		 * driver registered the device and there is no
+		 * entity_notify to be called when new entities
+		 * are added. Invoke it now.
+		*/
+		au0828_media_graph_notify(NULL, (void *) dev);
+	}
+
+	/*
+	 * Find tuner, decoder and demod.
+	 *
+	 * The tuner and decoder should be cached, as they'll be used by
+	 *	au0828_enable_source.
+	 *
+	 * It also needs to disable the link between tuner and
+	 * decoder/demod, to avoid disable step when tuner is requested
+	 * by video or audio. Note that this step can't be done until dvb
+	 * graph is created during dvb register.
+	*/
+	media_device_for_each_entity(entity, dev->media_dev) {
+		switch (entity->function) {
+		case MEDIA_ENT_F_TUNER:
+			dev->tuner = entity;
+			break;
+		case MEDIA_ENT_F_ATV_DECODER:
+			dev->decoder = entity;
+			break;
+		case MEDIA_ENT_F_DTV_DEMOD:
+			demod = entity;
+			break;
+		}
+	}
+
+	/* Disable link between tuner->demod and/or tuner->decoder */
+	if (dev->tuner) {
+		list_for_each_entry(link, &dev->tuner->links, list) {
+			if (demod && link->sink->entity == demod)
+				media_entity_setup_link(link, 0);
+			if (dev->decoder && link->sink->entity == dev->decoder)
+				media_entity_setup_link(link, 0);
+		}
+	}
+
+	/* register entity_notify callback */
+	dev->entity_notify.notify_data = (void *) dev;
+	dev->entity_notify.notify = (void *) au0828_media_graph_notify;
+	ret = media_device_register_entity_notify(dev->media_dev,
+						  &dev->entity_notify);
+	if (ret) {
+		dev_err(&udev->dev,
+			"Media Device register entity_notify Error: %d\n",
+			ret);
+		return ret;
+	}
+	/* set enable_source */
+	mutex_lock(&dev->media_dev->graph_mutex);
+	dev->media_dev->source_priv = (void *) dev;
+	dev->media_dev->enable_source = au0828_enable_source;
+	dev->media_dev->disable_source = au0828_disable_source;
+	mutex_unlock(&dev->media_dev->graph_mutex);
+#endif
+	return 0;
+}
+
+static int au0828_usb_probe(struct usb_interface *interface,
+	const struct usb_device_id *id)
+{
+	int ifnum;
+	int retval = 0;
+
+	struct au0828_dev *dev;
+	struct usb_device *usbdev = interface_to_usbdev(interface);
+
+	ifnum = interface->altsetting->desc.bInterfaceNumber;
+
+	if (ifnum != 0)
+		return -ENODEV;
+
+	dprintk(1, "%s() vendor id 0x%x device id 0x%x ifnum:%d\n", __func__,
+		le16_to_cpu(usbdev->descriptor.idVendor),
+		le16_to_cpu(usbdev->descriptor.idProduct),
+		ifnum);
+
+	/*
+	 * Make sure we have 480 Mbps of bandwidth, otherwise things like
+	 * video stream wouldn't likely work, since 12 Mbps is generally
+	 * not enough even for most Digital TV streams.
+	 */
+	if (usbdev->speed != USB_SPEED_HIGH && disable_usb_speed_check == 0) {
+		pr_err("au0828: Device initialization failed.\n");
+		pr_err("au0828: Device must be connected to a high-speed USB 2.0 port.\n");
+		return -ENODEV;
+	}
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (dev == NULL) {
+		pr_err("%s() Unable to allocate memory\n", __func__);
+		return -ENOMEM;
+	}
+
+	mutex_init(&dev->lock);
+	mutex_lock(&dev->lock);
+	mutex_init(&dev->mutex);
+	mutex_init(&dev->dvb.lock);
+	dev->usbdev = usbdev;
+	dev->boardnr = id->driver_info;
+	dev->board = au0828_boards[dev->boardnr];
+
+	/* Initialize the media controller */
+	retval = au0828_media_device_init(dev, usbdev);
+	if (retval) {
+		pr_err("%s() au0828_media_device_init failed\n",
+		       __func__);
+		mutex_unlock(&dev->lock);
+		kfree(dev);
+		return retval;
+	}
+
+	retval = au0828_v4l2_device_register(interface, dev);
+	if (retval) {
+		au0828_usb_v4l2_media_release(dev);
+		mutex_unlock(&dev->lock);
+		kfree(dev);
+		return retval;
+	}
+
+	/* Power Up the bridge */
+	au0828_write(dev, REG_600, 1 << 4);
+
+	/* Bring up the GPIO's and supporting devices */
+	au0828_gpio_setup(dev);
+
+	/* I2C */
+	au0828_i2c_register(dev);
+
+	/* Setup */
+	au0828_card_setup(dev);
+
+	/* Analog TV */
+	retval = au0828_analog_register(dev, interface);
+	if (retval) {
+		pr_err("%s() au0282_dev_register failed to register on V4L2\n",
+			__func__);
+		mutex_unlock(&dev->lock);
+		kfree(dev);
+		goto done;
+	}
+
+	/* Digital TV */
+	retval = au0828_dvb_register(dev);
+	if (retval)
+		pr_err("%s() au0282_dev_register failed\n",
+		       __func__);
+
+	/* Remote controller */
+	au0828_rc_register(dev);
+
+	/*
+	 * Store the pointer to the au0828_dev so it can be accessed in
+	 * au0828_usb_disconnect
+	 */
+	usb_set_intfdata(interface, dev);
+
+	pr_info("Registered device AU0828 [%s]\n",
+		dev->board.name == NULL ? "Unset" : dev->board.name);
+
+	mutex_unlock(&dev->lock);
+
+	retval = au0828_media_device_register(dev, usbdev);
+
+done:
+	if (retval < 0)
+		au0828_usb_disconnect(interface);
+
+	return retval;
+}
+
+static int au0828_suspend(struct usb_interface *interface,
+				pm_message_t message)
+{
+	struct au0828_dev *dev = usb_get_intfdata(interface);
+
+	if (!dev)
+		return 0;
+
+	pr_info("Suspend\n");
+
+	au0828_rc_suspend(dev);
+	au0828_v4l2_suspend(dev);
+	au0828_dvb_suspend(dev);
+
+	/* FIXME: should suspend also ATV/DTV */
+
+	return 0;
+}
+
+static int au0828_resume(struct usb_interface *interface)
+{
+	struct au0828_dev *dev = usb_get_intfdata(interface);
+	if (!dev)
+		return 0;
+
+	pr_info("Resume\n");
+
+	/* Power Up the bridge */
+	au0828_write(dev, REG_600, 1 << 4);
+
+	/* Bring up the GPIO's and supporting devices */
+	au0828_gpio_setup(dev);
+
+	au0828_rc_resume(dev);
+	au0828_v4l2_resume(dev);
+	au0828_dvb_resume(dev);
+
+	/* FIXME: should resume also ATV/DTV */
+
+	return 0;
+}
+
+static struct usb_driver au0828_usb_driver = {
+	.name		= KBUILD_MODNAME,
+	.probe		= au0828_usb_probe,
+	.disconnect	= au0828_usb_disconnect,
+	.id_table	= au0828_usb_id_table,
+	.suspend	= au0828_suspend,
+	.resume		= au0828_resume,
+	.reset_resume	= au0828_resume,
+};
+
+static int __init au0828_init(void)
+{
+	int ret;
+
+	if (au0828_debug & 1)
+		pr_info("%s() Debugging is enabled\n", __func__);
+
+	if (au0828_debug & 2)
+		pr_info("%s() USB Debugging is enabled\n", __func__);
+
+	if (au0828_debug & 4)
+		pr_info("%s() I2C Debugging is enabled\n", __func__);
+
+	if (au0828_debug & 8)
+		pr_info("%s() Bridge Debugging is enabled\n",
+		       __func__);
+
+	if (au0828_debug & 16)
+		pr_info("%s() IR Debugging is enabled\n",
+		       __func__);
+
+	pr_info("au0828 driver loaded\n");
+
+	ret = usb_register(&au0828_usb_driver);
+	if (ret)
+		pr_err("usb_register failed, error = %d\n", ret);
+
+	return ret;
+}
+
+static void __exit au0828_exit(void)
+{
+	usb_deregister(&au0828_usb_driver);
+}
+
+module_init(au0828_init);
+module_exit(au0828_exit);
+
+MODULE_DESCRIPTION("Driver for Auvitek AU0828 based products");
+MODULE_AUTHOR("Steven Toth <stoth@linuxtv.org>");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("0.0.3");
diff --git a/drivers/media/usb/au0828/au0828-dvb.c b/drivers/media/usb/au0828/au0828-dvb.c
new file mode 100644
index 0000000..d9093a3
--- /dev/null
+++ b/drivers/media/usb/au0828/au0828-dvb.c
@@ -0,0 +1,694 @@
+/*
+ *  Driver for the Auvitek USB bridge
+ *
+ *  Copyright (c) 2008 Steven Toth <stoth@linuxtv.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *
+ *  GNU General Public License for more details.
+ */
+
+#include "au0828.h"
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <media/v4l2-common.h>
+#include <media/tuner.h>
+
+#include "au8522.h"
+#include "xc5000.h"
+#include "mxl5007t.h"
+#include "tda18271.h"
+
+static int preallocate_big_buffers;
+module_param_named(preallocate_big_buffers, preallocate_big_buffers, int, 0644);
+MODULE_PARM_DESC(preallocate_big_buffers, "Preallocate the larger transfer buffers at module load time");
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+#define _AU0828_BULKPIPE 0x83
+#define _BULKPIPESIZE 0xe522
+
+static u8 hauppauge_hvr950q_led_states[] = {
+	0x00, /* off */
+	0x02, /* yellow */
+	0x04, /* green */
+};
+
+static struct au8522_led_config hauppauge_hvr950q_led_cfg = {
+	.gpio_output = 0x00e0,
+	.gpio_output_enable  = 0x6006,
+	.gpio_output_disable = 0x0660,
+
+	.gpio_leds = 0x00e2,
+	.led_states  = hauppauge_hvr950q_led_states,
+	.num_led_states = sizeof(hauppauge_hvr950q_led_states),
+
+	.vsb8_strong   = 20 /* dB */ * 10,
+	.qam64_strong  = 25 /* dB */ * 10,
+	.qam256_strong = 32 /* dB */ * 10,
+};
+
+static struct au8522_config hauppauge_hvr950q_config = {
+	.demod_address = 0x8e >> 1,
+	.status_mode   = AU8522_DEMODLOCKING,
+	.qam_if        = AU8522_IF_6MHZ,
+	.vsb_if        = AU8522_IF_6MHZ,
+	.led_cfg       = &hauppauge_hvr950q_led_cfg,
+};
+
+static struct au8522_config fusionhdtv7usb_config = {
+	.demod_address = 0x8e >> 1,
+	.status_mode   = AU8522_DEMODLOCKING,
+	.qam_if        = AU8522_IF_6MHZ,
+	.vsb_if        = AU8522_IF_6MHZ,
+};
+
+static struct au8522_config hauppauge_woodbury_config = {
+	.demod_address = 0x8e >> 1,
+	.status_mode   = AU8522_DEMODLOCKING,
+	.qam_if        = AU8522_IF_4MHZ,
+	.vsb_if        = AU8522_IF_3_25MHZ,
+};
+
+static struct xc5000_config hauppauge_xc5000a_config = {
+	.i2c_address      = 0x61,
+	.if_khz           = 6000,
+	.chip_id          = XC5000A,
+	.output_amp       = 0x8f,
+};
+
+static struct xc5000_config hauppauge_xc5000c_config = {
+	.i2c_address      = 0x61,
+	.if_khz           = 6000,
+	.chip_id          = XC5000C,
+	.output_amp       = 0x8f,
+};
+
+static struct mxl5007t_config mxl5007t_hvr950q_config = {
+	.xtal_freq_hz = MxL_XTAL_24_MHZ,
+	.if_freq_hz = MxL_IF_6_MHZ,
+};
+
+static struct tda18271_config hauppauge_woodbury_tunerconfig = {
+	.gate    = TDA18271_GATE_DIGITAL,
+};
+
+static void au0828_restart_dvb_streaming(struct work_struct *work);
+
+static void au0828_bulk_timeout(struct timer_list *t)
+{
+	struct au0828_dev *dev = from_timer(dev, t, bulk_timeout);
+
+	dprintk(1, "%s called\n", __func__);
+	dev->bulk_timeout_running = 0;
+	schedule_work(&dev->restart_streaming);
+}
+
+/*-------------------------------------------------------------------*/
+static void urb_completion(struct urb *purb)
+{
+	struct au0828_dev *dev = purb->context;
+	int ptype = usb_pipetype(purb->pipe);
+	unsigned char *ptr;
+
+	dprintk(2, "%s: %d\n", __func__, purb->actual_length);
+
+	if (!dev) {
+		dprintk(2, "%s: no dev!\n", __func__);
+		return;
+	}
+
+	if (!dev->urb_streaming) {
+		dprintk(2, "%s: not streaming!\n", __func__);
+		return;
+	}
+
+	if (ptype != PIPE_BULK) {
+		pr_err("%s: Unsupported URB type %d\n",
+		       __func__, ptype);
+		return;
+	}
+
+	/* See if the stream is corrupted (to work around a hardware
+	   bug where the stream gets misaligned */
+	ptr = purb->transfer_buffer;
+	if (purb->actual_length > 0 && ptr[0] != 0x47) {
+		dprintk(1, "Need to restart streaming %02x len=%d!\n",
+			ptr[0], purb->actual_length);
+		schedule_work(&dev->restart_streaming);
+		return;
+	} else if (dev->bulk_timeout_running == 1) {
+		/* The URB handler has fired, so cancel timer which would
+		 * restart endpoint if we hadn't
+		 */
+		dprintk(1, "%s cancelling bulk timeout\n", __func__);
+		dev->bulk_timeout_running = 0;
+		del_timer(&dev->bulk_timeout);
+	}
+
+	/* Feed the transport payload into the kernel demux */
+	dvb_dmx_swfilter_packets(&dev->dvb.demux,
+		purb->transfer_buffer, purb->actual_length / 188);
+
+	/* Clean the buffer before we requeue */
+	memset(purb->transfer_buffer, 0, URB_BUFSIZE);
+
+	/* Requeue URB */
+	usb_submit_urb(purb, GFP_ATOMIC);
+}
+
+static int stop_urb_transfer(struct au0828_dev *dev)
+{
+	int i;
+
+	dprintk(2, "%s()\n", __func__);
+
+	if (!dev->urb_streaming)
+		return 0;
+
+	if (dev->bulk_timeout_running == 1) {
+		dev->bulk_timeout_running = 0;
+		del_timer(&dev->bulk_timeout);
+	}
+
+	dev->urb_streaming = false;
+	for (i = 0; i < URB_COUNT; i++) {
+		if (dev->urbs[i]) {
+			usb_kill_urb(dev->urbs[i]);
+			if (!preallocate_big_buffers)
+				kfree(dev->urbs[i]->transfer_buffer);
+
+			usb_free_urb(dev->urbs[i]);
+		}
+	}
+
+	return 0;
+}
+
+static int start_urb_transfer(struct au0828_dev *dev)
+{
+	struct urb *purb;
+	int i, ret;
+
+	dprintk(2, "%s()\n", __func__);
+
+	if (dev->urb_streaming) {
+		dprintk(2, "%s: bulk xfer already running!\n", __func__);
+		return 0;
+	}
+
+	for (i = 0; i < URB_COUNT; i++) {
+
+		dev->urbs[i] = usb_alloc_urb(0, GFP_KERNEL);
+		if (!dev->urbs[i])
+			return -ENOMEM;
+
+		purb = dev->urbs[i];
+
+		if (preallocate_big_buffers)
+			purb->transfer_buffer = dev->dig_transfer_buffer[i];
+		else
+			purb->transfer_buffer = kzalloc(URB_BUFSIZE,
+					GFP_KERNEL);
+
+		if (!purb->transfer_buffer) {
+			usb_free_urb(purb);
+			dev->urbs[i] = NULL;
+			ret = -ENOMEM;
+			pr_err("%s: failed big buffer allocation, err = %d\n",
+			       __func__, ret);
+			return ret;
+		}
+
+		purb->status = -EINPROGRESS;
+		usb_fill_bulk_urb(purb,
+				  dev->usbdev,
+				  usb_rcvbulkpipe(dev->usbdev,
+					_AU0828_BULKPIPE),
+				  purb->transfer_buffer,
+				  URB_BUFSIZE,
+				  urb_completion,
+				  dev);
+
+	}
+
+	for (i = 0; i < URB_COUNT; i++) {
+		ret = usb_submit_urb(dev->urbs[i], GFP_ATOMIC);
+		if (ret != 0) {
+			stop_urb_transfer(dev);
+			pr_err("%s: failed urb submission, err = %d\n",
+			       __func__, ret);
+			return ret;
+		}
+	}
+
+	dev->urb_streaming = true;
+
+	/* If we don't valid data within 1 second, restart stream */
+	mod_timer(&dev->bulk_timeout, jiffies + (HZ));
+	dev->bulk_timeout_running = 1;
+
+	return 0;
+}
+
+static void au0828_start_transport(struct au0828_dev *dev)
+{
+	au0828_write(dev, 0x608, 0x90);
+	au0828_write(dev, 0x609, 0x72);
+	au0828_write(dev, 0x60a, 0x71);
+	au0828_write(dev, 0x60b, 0x01);
+
+}
+
+static void au0828_stop_transport(struct au0828_dev *dev, int full_stop)
+{
+	if (full_stop) {
+		au0828_write(dev, 0x608, 0x00);
+		au0828_write(dev, 0x609, 0x00);
+		au0828_write(dev, 0x60a, 0x00);
+	}
+	au0828_write(dev, 0x60b, 0x00);
+}
+
+static int au0828_dvb_start_feed(struct dvb_demux_feed *feed)
+{
+	struct dvb_demux *demux = feed->demux;
+	struct au0828_dev *dev = (struct au0828_dev *) demux->priv;
+	struct au0828_dvb *dvb = &dev->dvb;
+	int ret = 0;
+
+	dprintk(1, "%s()\n", __func__);
+
+	if (!demux->dmx.frontend)
+		return -EINVAL;
+
+	if (dvb->frontend) {
+		mutex_lock(&dvb->lock);
+		dvb->start_count++;
+		dprintk(1, "%s(), start_count: %d, stop_count: %d\n", __func__,
+			dvb->start_count, dvb->stop_count);
+		if (dvb->feeding++ == 0) {
+			/* Start transport */
+			au0828_start_transport(dev);
+			ret = start_urb_transfer(dev);
+			if (ret < 0) {
+				au0828_stop_transport(dev, 0);
+				dvb->feeding--;	/* We ran out of memory... */
+			}
+		}
+		mutex_unlock(&dvb->lock);
+	}
+
+	return ret;
+}
+
+static int au0828_dvb_stop_feed(struct dvb_demux_feed *feed)
+{
+	struct dvb_demux *demux = feed->demux;
+	struct au0828_dev *dev = (struct au0828_dev *) demux->priv;
+	struct au0828_dvb *dvb = &dev->dvb;
+	int ret = 0;
+
+	dprintk(1, "%s()\n", __func__);
+
+	if (dvb->frontend) {
+		cancel_work_sync(&dev->restart_streaming);
+
+		mutex_lock(&dvb->lock);
+		dvb->stop_count++;
+		dprintk(1, "%s(), start_count: %d, stop_count: %d\n", __func__,
+			dvb->start_count, dvb->stop_count);
+		if (dvb->feeding > 0) {
+			dvb->feeding--;
+			if (dvb->feeding == 0) {
+				/* Stop transport */
+				ret = stop_urb_transfer(dev);
+				au0828_stop_transport(dev, 0);
+			}
+		}
+		mutex_unlock(&dvb->lock);
+	}
+
+	return ret;
+}
+
+static void au0828_restart_dvb_streaming(struct work_struct *work)
+{
+	struct au0828_dev *dev = container_of(work, struct au0828_dev,
+					      restart_streaming);
+	struct au0828_dvb *dvb = &dev->dvb;
+
+	if (!dev->urb_streaming)
+		return;
+
+	dprintk(1, "Restarting streaming...!\n");
+
+	mutex_lock(&dvb->lock);
+
+	/* Stop transport */
+	stop_urb_transfer(dev);
+	au0828_stop_transport(dev, 1);
+
+	/* Start transport */
+	au0828_start_transport(dev);
+	start_urb_transfer(dev);
+
+	mutex_unlock(&dvb->lock);
+}
+
+static int au0828_set_frontend(struct dvb_frontend *fe)
+{
+	struct au0828_dev *dev = fe->dvb->priv;
+	struct au0828_dvb *dvb = &dev->dvb;
+	int ret, was_streaming;
+
+	mutex_lock(&dvb->lock);
+	was_streaming = dev->urb_streaming;
+	if (was_streaming) {
+		au0828_stop_transport(dev, 1);
+
+		/*
+		 * We can't hold a mutex here, as the restart_streaming
+		 * kthread may also hold it.
+		 */
+		mutex_unlock(&dvb->lock);
+		cancel_work_sync(&dev->restart_streaming);
+		mutex_lock(&dvb->lock);
+
+		stop_urb_transfer(dev);
+	}
+	mutex_unlock(&dvb->lock);
+
+	ret = dvb->set_frontend(fe);
+
+	if (was_streaming) {
+		mutex_lock(&dvb->lock);
+		au0828_start_transport(dev);
+		start_urb_transfer(dev);
+		mutex_unlock(&dvb->lock);
+	}
+
+	return ret;
+}
+
+static int dvb_register(struct au0828_dev *dev)
+{
+	struct au0828_dvb *dvb = &dev->dvb;
+	int result;
+
+	dprintk(1, "%s()\n", __func__);
+
+	if (preallocate_big_buffers) {
+		int i;
+		for (i = 0; i < URB_COUNT; i++) {
+			dev->dig_transfer_buffer[i] = kzalloc(URB_BUFSIZE,
+					GFP_KERNEL);
+
+			if (!dev->dig_transfer_buffer[i]) {
+				result = -ENOMEM;
+
+				pr_err("failed buffer allocation (errno = %d)\n",
+				       result);
+				goto fail_adapter;
+			}
+		}
+	}
+
+	INIT_WORK(&dev->restart_streaming, au0828_restart_dvb_streaming);
+
+	/* register adapter */
+	result = dvb_register_adapter(&dvb->adapter,
+				      KBUILD_MODNAME, THIS_MODULE,
+				      &dev->usbdev->dev, adapter_nr);
+	if (result < 0) {
+		pr_err("dvb_register_adapter failed (errno = %d)\n",
+		       result);
+		goto fail_adapter;
+	}
+
+#ifdef CONFIG_MEDIA_CONTROLLER_DVB
+	dvb->adapter.mdev = dev->media_dev;
+#endif
+
+	dvb->adapter.priv = dev;
+
+	/* register frontend */
+	result = dvb_register_frontend(&dvb->adapter, dvb->frontend);
+	if (result < 0) {
+		pr_err("dvb_register_frontend failed (errno = %d)\n",
+		       result);
+		goto fail_frontend;
+	}
+
+	/* Hook dvb frontend */
+	dvb->set_frontend = dvb->frontend->ops.set_frontend;
+	dvb->frontend->ops.set_frontend = au0828_set_frontend;
+
+	/* register demux stuff */
+	dvb->demux.dmx.capabilities =
+		DMX_TS_FILTERING | DMX_SECTION_FILTERING |
+		DMX_MEMORY_BASED_FILTERING;
+	dvb->demux.priv       = dev;
+	dvb->demux.filternum  = 256;
+	dvb->demux.feednum    = 256;
+	dvb->demux.start_feed = au0828_dvb_start_feed;
+	dvb->demux.stop_feed  = au0828_dvb_stop_feed;
+	result = dvb_dmx_init(&dvb->demux);
+	if (result < 0) {
+		pr_err("dvb_dmx_init failed (errno = %d)\n", result);
+		goto fail_dmx;
+	}
+
+	dvb->dmxdev.filternum    = 256;
+	dvb->dmxdev.demux        = &dvb->demux.dmx;
+	dvb->dmxdev.capabilities = 0;
+	result = dvb_dmxdev_init(&dvb->dmxdev, &dvb->adapter);
+	if (result < 0) {
+		pr_err("dvb_dmxdev_init failed (errno = %d)\n", result);
+		goto fail_dmxdev;
+	}
+
+	dvb->fe_hw.source = DMX_FRONTEND_0;
+	result = dvb->demux.dmx.add_frontend(&dvb->demux.dmx, &dvb->fe_hw);
+	if (result < 0) {
+		pr_err("add_frontend failed (DMX_FRONTEND_0, errno = %d)\n",
+		       result);
+		goto fail_fe_hw;
+	}
+
+	dvb->fe_mem.source = DMX_MEMORY_FE;
+	result = dvb->demux.dmx.add_frontend(&dvb->demux.dmx, &dvb->fe_mem);
+	if (result < 0) {
+		pr_err("add_frontend failed (DMX_MEMORY_FE, errno = %d)\n",
+		       result);
+		goto fail_fe_mem;
+	}
+
+	result = dvb->demux.dmx.connect_frontend(&dvb->demux.dmx, &dvb->fe_hw);
+	if (result < 0) {
+		pr_err("connect_frontend failed (errno = %d)\n", result);
+		goto fail_fe_conn;
+	}
+
+	/* register network adapter */
+	dvb_net_init(&dvb->adapter, &dvb->net, &dvb->demux.dmx);
+
+	dvb->start_count = 0;
+	dvb->stop_count = 0;
+
+	result = dvb_create_media_graph(&dvb->adapter, false);
+	if (result < 0)
+		goto fail_create_graph;
+
+	return 0;
+
+fail_create_graph:
+	dvb_net_release(&dvb->net);
+fail_fe_conn:
+	dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_mem);
+fail_fe_mem:
+	dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_hw);
+fail_fe_hw:
+	dvb_dmxdev_release(&dvb->dmxdev);
+fail_dmxdev:
+	dvb_dmx_release(&dvb->demux);
+fail_dmx:
+	dvb_unregister_frontend(dvb->frontend);
+fail_frontend:
+	dvb_frontend_detach(dvb->frontend);
+	dvb_unregister_adapter(&dvb->adapter);
+fail_adapter:
+
+	if (preallocate_big_buffers) {
+		int i;
+		for (i = 0; i < URB_COUNT; i++)
+			kfree(dev->dig_transfer_buffer[i]);
+	}
+
+	return result;
+}
+
+void au0828_dvb_unregister(struct au0828_dev *dev)
+{
+	struct au0828_dvb *dvb = &dev->dvb;
+
+	dprintk(1, "%s()\n", __func__);
+
+	if (dvb->frontend == NULL)
+		return;
+
+	cancel_work_sync(&dev->restart_streaming);
+
+	dvb_net_release(&dvb->net);
+	dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_mem);
+	dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_hw);
+	dvb_dmxdev_release(&dvb->dmxdev);
+	dvb_dmx_release(&dvb->demux);
+	dvb_unregister_frontend(dvb->frontend);
+	dvb_frontend_detach(dvb->frontend);
+	dvb_unregister_adapter(&dvb->adapter);
+
+	if (preallocate_big_buffers) {
+		int i;
+		for (i = 0; i < URB_COUNT; i++)
+			kfree(dev->dig_transfer_buffer[i]);
+	}
+	dvb->frontend = NULL;
+}
+
+/* All the DVB attach calls go here, this function get's modified
+ * for each new card. No other function in this file needs
+ * to change.
+ */
+int au0828_dvb_register(struct au0828_dev *dev)
+{
+	struct au0828_dvb *dvb = &dev->dvb;
+	int ret;
+
+	dprintk(1, "%s()\n", __func__);
+
+	/* init frontend */
+	switch (dev->boardnr) {
+	case AU0828_BOARD_HAUPPAUGE_HVR850:
+	case AU0828_BOARD_HAUPPAUGE_HVR950Q:
+		dvb->frontend = dvb_attach(au8522_attach,
+				&hauppauge_hvr950q_config,
+				&dev->i2c_adap);
+		if (dvb->frontend != NULL)
+			switch (dev->board.tuner_type) {
+			default:
+			case TUNER_XC5000:
+				dvb_attach(xc5000_attach, dvb->frontend,
+					   &dev->i2c_adap,
+					   &hauppauge_xc5000a_config);
+				break;
+			case TUNER_XC5000C:
+				dvb_attach(xc5000_attach, dvb->frontend,
+					   &dev->i2c_adap,
+					   &hauppauge_xc5000c_config);
+				break;
+			}
+		break;
+	case AU0828_BOARD_HAUPPAUGE_HVR950Q_MXL:
+		dvb->frontend = dvb_attach(au8522_attach,
+				&hauppauge_hvr950q_config,
+				&dev->i2c_adap);
+		if (dvb->frontend != NULL)
+			dvb_attach(mxl5007t_attach, dvb->frontend,
+				   &dev->i2c_adap, 0x60,
+				   &mxl5007t_hvr950q_config);
+		break;
+	case AU0828_BOARD_HAUPPAUGE_WOODBURY:
+		dvb->frontend = dvb_attach(au8522_attach,
+				&hauppauge_woodbury_config,
+				&dev->i2c_adap);
+		if (dvb->frontend != NULL)
+			dvb_attach(tda18271_attach, dvb->frontend,
+				   0x60, &dev->i2c_adap,
+				   &hauppauge_woodbury_tunerconfig);
+		break;
+	case AU0828_BOARD_DVICO_FUSIONHDTV7:
+		dvb->frontend = dvb_attach(au8522_attach,
+				&fusionhdtv7usb_config,
+				&dev->i2c_adap);
+		if (dvb->frontend != NULL) {
+			dvb_attach(xc5000_attach, dvb->frontend,
+				&dev->i2c_adap,
+				&hauppauge_xc5000a_config);
+		}
+		break;
+	default:
+		pr_warn("The frontend of your DVB/ATSC card isn't supported yet\n");
+		break;
+	}
+	if (NULL == dvb->frontend) {
+		pr_err("%s() Frontend initialization failed\n",
+		       __func__);
+		return -1;
+	}
+	/* define general-purpose callback pointer */
+	dvb->frontend->callback = au0828_tuner_callback;
+
+	/* register everything */
+	ret = dvb_register(dev);
+	if (ret < 0) {
+		if (dvb->frontend->ops.release)
+			dvb->frontend->ops.release(dvb->frontend);
+		dvb->frontend = NULL;
+		return ret;
+	}
+
+	timer_setup(&dev->bulk_timeout, au0828_bulk_timeout, 0);
+
+	return 0;
+}
+
+void au0828_dvb_suspend(struct au0828_dev *dev)
+{
+	struct au0828_dvb *dvb = &dev->dvb;
+	int rc;
+
+	if (dvb->frontend) {
+		if (dev->urb_streaming) {
+			cancel_work_sync(&dev->restart_streaming);
+			/* Stop transport */
+			mutex_lock(&dvb->lock);
+			stop_urb_transfer(dev);
+			au0828_stop_transport(dev, 1);
+			mutex_unlock(&dvb->lock);
+			dev->need_urb_start = true;
+		}
+		/* suspend frontend - does tuner and fe to sleep */
+		rc = dvb_frontend_suspend(dvb->frontend);
+		pr_info("au0828_dvb_suspend(): Suspending DVB fe %d\n", rc);
+	}
+}
+
+void au0828_dvb_resume(struct au0828_dev *dev)
+{
+	struct au0828_dvb *dvb = &dev->dvb;
+	int rc;
+
+	if (dvb->frontend) {
+		/* resume frontend - does fe and tuner init */
+		rc = dvb_frontend_resume(dvb->frontend);
+		pr_info("au0828_dvb_resume(): Resuming DVB fe %d\n", rc);
+		if (dev->need_urb_start) {
+			/* Start transport */
+			mutex_lock(&dvb->lock);
+			au0828_start_transport(dev);
+			start_urb_transfer(dev);
+			mutex_unlock(&dvb->lock);
+		}
+	}
+}
diff --git a/drivers/media/usb/au0828/au0828-i2c.c b/drivers/media/usb/au0828/au0828-i2c.c
new file mode 100644
index 0000000..1b8ec5d
--- /dev/null
+++ b/drivers/media/usb/au0828/au0828-i2c.c
@@ -0,0 +1,410 @@
+/*
+ *  Driver for the Auvitek AU0828 USB bridge
+ *
+ *  Copyright (c) 2008 Steven Toth <stoth@linuxtv.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *
+ *  GNU General Public License for more details.
+ */
+
+#include "au0828.h"
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+
+#include "media/tuner.h"
+#include <media/v4l2-common.h>
+
+static int i2c_scan;
+module_param(i2c_scan, int, 0444);
+MODULE_PARM_DESC(i2c_scan, "scan i2c bus at insmod time");
+
+#define I2C_WAIT_DELAY 25
+#define I2C_WAIT_RETRY 1000
+
+static inline int i2c_slave_did_write_ack(struct i2c_adapter *i2c_adap)
+{
+	struct au0828_dev *dev = i2c_adap->algo_data;
+	return au0828_read(dev, AU0828_I2C_STATUS_201) &
+		AU0828_I2C_STATUS_NO_WRITE_ACK ? 0 : 1;
+}
+
+static inline int i2c_slave_did_read_ack(struct i2c_adapter *i2c_adap)
+{
+	struct au0828_dev *dev = i2c_adap->algo_data;
+	return au0828_read(dev, AU0828_I2C_STATUS_201) &
+		AU0828_I2C_STATUS_NO_READ_ACK ? 0 : 1;
+}
+
+static int i2c_wait_read_ack(struct i2c_adapter *i2c_adap)
+{
+	int count;
+
+	for (count = 0; count < I2C_WAIT_RETRY; count++) {
+		if (!i2c_slave_did_read_ack(i2c_adap))
+			break;
+		udelay(I2C_WAIT_DELAY);
+	}
+
+	if (I2C_WAIT_RETRY == count)
+		return 0;
+
+	return 1;
+}
+
+static inline int i2c_is_read_busy(struct i2c_adapter *i2c_adap)
+{
+	struct au0828_dev *dev = i2c_adap->algo_data;
+	return au0828_read(dev, AU0828_I2C_STATUS_201) &
+		AU0828_I2C_STATUS_READ_DONE ? 0 : 1;
+}
+
+static int i2c_wait_read_done(struct i2c_adapter *i2c_adap)
+{
+	int count;
+
+	for (count = 0; count < I2C_WAIT_RETRY; count++) {
+		if (!i2c_is_read_busy(i2c_adap))
+			break;
+		udelay(I2C_WAIT_DELAY);
+	}
+
+	if (I2C_WAIT_RETRY == count)
+		return 0;
+
+	return 1;
+}
+
+static inline int i2c_is_write_done(struct i2c_adapter *i2c_adap)
+{
+	struct au0828_dev *dev = i2c_adap->algo_data;
+	return au0828_read(dev, AU0828_I2C_STATUS_201) &
+		AU0828_I2C_STATUS_WRITE_DONE ? 1 : 0;
+}
+
+static int i2c_wait_write_done(struct i2c_adapter *i2c_adap)
+{
+	int count;
+
+	for (count = 0; count < I2C_WAIT_RETRY; count++) {
+		if (i2c_is_write_done(i2c_adap))
+			break;
+		udelay(I2C_WAIT_DELAY);
+	}
+
+	if (I2C_WAIT_RETRY == count)
+		return 0;
+
+	return 1;
+}
+
+static inline int i2c_is_busy(struct i2c_adapter *i2c_adap)
+{
+	struct au0828_dev *dev = i2c_adap->algo_data;
+	return au0828_read(dev, AU0828_I2C_STATUS_201) &
+		AU0828_I2C_STATUS_BUSY ? 1 : 0;
+}
+
+static int i2c_wait_done(struct i2c_adapter *i2c_adap)
+{
+	int count;
+
+	for (count = 0; count < I2C_WAIT_RETRY; count++) {
+		if (!i2c_is_busy(i2c_adap))
+			break;
+		udelay(I2C_WAIT_DELAY);
+	}
+
+	if (I2C_WAIT_RETRY == count)
+		return 0;
+
+	return 1;
+}
+
+/* FIXME: Implement join handling correctly */
+static int i2c_sendbytes(struct i2c_adapter *i2c_adap,
+	const struct i2c_msg *msg, int joined_rlen)
+{
+	int i, strobe = 0;
+	struct au0828_dev *dev = i2c_adap->algo_data;
+	u8 i2c_speed = dev->board.i2c_clk_divider;
+
+	dprintk(4, "%s()\n", __func__);
+
+	au0828_write(dev, AU0828_I2C_MULTIBYTE_MODE_2FF, 0x01);
+
+	if (((dev->board.tuner_type == TUNER_XC5000) ||
+	     (dev->board.tuner_type == TUNER_XC5000C)) &&
+	    (dev->board.tuner_addr == msg->addr)) {
+		/*
+		 * Due to I2C clock stretch, we need to use a lower speed
+		 * on xc5000 for commands. However, firmware transfer can
+		 * speed up to 400 KHz.
+		 */
+		if (msg->len == 64)
+			i2c_speed = AU0828_I2C_CLK_250KHZ;
+		else
+			i2c_speed = AU0828_I2C_CLK_20KHZ;
+	}
+	/* Set the I2C clock */
+	au0828_write(dev, AU0828_I2C_CLK_DIVIDER_202, i2c_speed);
+
+	/* Hardware needs 8 bit addresses */
+	au0828_write(dev, AU0828_I2C_DEST_ADDR_203, msg->addr << 1);
+
+	dprintk(4, "SEND: %02x\n", msg->addr);
+
+	/* Deal with i2c_scan */
+	if (msg->len == 0) {
+		/* The analog tuner detection code makes use of the SMBUS_QUICK
+		   message (which involves a zero length i2c write).  To avoid
+		   checking the status register when we didn't strobe out any
+		   actual bytes to the bus, just do a read check.  This is
+		   consistent with how I saw i2c device checking done in the
+		   USB trace of the Windows driver */
+		au0828_write(dev, AU0828_I2C_TRIGGER_200,
+			     AU0828_I2C_TRIGGER_READ);
+
+		if (!i2c_wait_done(i2c_adap))
+			return -EIO;
+
+		if (i2c_wait_read_ack(i2c_adap))
+			return -EIO;
+
+		return 0;
+	}
+
+	for (i = 0; i < msg->len;) {
+
+		dprintk(4, " %02x\n", msg->buf[i]);
+
+		au0828_write(dev, AU0828_I2C_WRITE_FIFO_205, msg->buf[i]);
+
+		strobe++;
+		i++;
+
+		if ((strobe >= 4) || (i >= msg->len)) {
+
+			/* Strobe the byte into the bus */
+			if (i < msg->len)
+				au0828_write(dev, AU0828_I2C_TRIGGER_200,
+					     AU0828_I2C_TRIGGER_WRITE |
+					     AU0828_I2C_TRIGGER_HOLD);
+			else
+				au0828_write(dev, AU0828_I2C_TRIGGER_200,
+					     AU0828_I2C_TRIGGER_WRITE);
+
+			/* Reset strobe trigger */
+			strobe = 0;
+
+			if (!i2c_wait_write_done(i2c_adap))
+				return -EIO;
+
+		}
+
+	}
+	if (!i2c_wait_done(i2c_adap))
+		return -EIO;
+
+	dprintk(4, "\n");
+
+	return msg->len;
+}
+
+/* FIXME: Implement join handling correctly */
+static int i2c_readbytes(struct i2c_adapter *i2c_adap,
+	const struct i2c_msg *msg, int joined)
+{
+	struct au0828_dev *dev = i2c_adap->algo_data;
+	u8 i2c_speed = dev->board.i2c_clk_divider;
+	int i;
+
+	dprintk(4, "%s()\n", __func__);
+
+	au0828_write(dev, AU0828_I2C_MULTIBYTE_MODE_2FF, 0x01);
+
+	/*
+	 * Due to xc5000c clock stretch, we cannot use full speed at
+	 * readings from xc5000, as otherwise they'll fail.
+	 */
+	if (((dev->board.tuner_type == TUNER_XC5000) ||
+	     (dev->board.tuner_type == TUNER_XC5000C)) &&
+	    (dev->board.tuner_addr == msg->addr))
+		i2c_speed = AU0828_I2C_CLK_20KHZ;
+
+	/* Set the I2C clock */
+	au0828_write(dev, AU0828_I2C_CLK_DIVIDER_202, i2c_speed);
+
+	/* Hardware needs 8 bit addresses */
+	au0828_write(dev, AU0828_I2C_DEST_ADDR_203, msg->addr << 1);
+
+	dprintk(4, " RECV:\n");
+
+	/* Deal with i2c_scan */
+	if (msg->len == 0) {
+		au0828_write(dev, AU0828_I2C_TRIGGER_200,
+			     AU0828_I2C_TRIGGER_READ);
+
+		if (i2c_wait_read_ack(i2c_adap))
+			return -EIO;
+		return 0;
+	}
+
+	for (i = 0; i < msg->len;) {
+
+		i++;
+
+		if (i < msg->len)
+			au0828_write(dev, AU0828_I2C_TRIGGER_200,
+				     AU0828_I2C_TRIGGER_READ |
+				     AU0828_I2C_TRIGGER_HOLD);
+		else
+			au0828_write(dev, AU0828_I2C_TRIGGER_200,
+				     AU0828_I2C_TRIGGER_READ);
+
+		if (!i2c_wait_read_done(i2c_adap))
+			return -EIO;
+
+		msg->buf[i-1] = au0828_read(dev, AU0828_I2C_READ_FIFO_209) &
+			0xff;
+
+		dprintk(4, " %02x\n", msg->buf[i-1]);
+	}
+	if (!i2c_wait_done(i2c_adap))
+		return -EIO;
+
+	dprintk(4, "\n");
+
+	return msg->len;
+}
+
+static int i2c_xfer(struct i2c_adapter *i2c_adap,
+		    struct i2c_msg *msgs, int num)
+{
+	int i, retval = 0;
+
+	dprintk(4, "%s(num = %d)\n", __func__, num);
+
+	for (i = 0; i < num; i++) {
+		dprintk(4, "%s(num = %d) addr = 0x%02x  len = 0x%x\n",
+			__func__, num, msgs[i].addr, msgs[i].len);
+		if (msgs[i].flags & I2C_M_RD) {
+			/* read */
+			retval = i2c_readbytes(i2c_adap, &msgs[i], 0);
+		} else if (i + 1 < num && (msgs[i + 1].flags & I2C_M_RD) &&
+			   msgs[i].addr == msgs[i + 1].addr) {
+			/* write then read from same address */
+			retval = i2c_sendbytes(i2c_adap, &msgs[i],
+					       msgs[i + 1].len);
+			if (retval < 0)
+				goto err;
+			i++;
+			retval = i2c_readbytes(i2c_adap, &msgs[i], 1);
+		} else {
+			/* write */
+			retval = i2c_sendbytes(i2c_adap, &msgs[i], 0);
+		}
+		if (retval < 0)
+			goto err;
+	}
+	return num;
+
+err:
+	return retval;
+}
+
+static u32 au0828_functionality(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_SMBUS_EMUL | I2C_FUNC_I2C;
+}
+
+static const struct i2c_algorithm au0828_i2c_algo_template = {
+	.master_xfer	= i2c_xfer,
+	.functionality	= au0828_functionality,
+};
+
+/* ----------------------------------------------------------------------- */
+
+static const struct i2c_adapter au0828_i2c_adap_template = {
+	.name              = KBUILD_MODNAME,
+	.owner             = THIS_MODULE,
+	.algo              = &au0828_i2c_algo_template,
+};
+
+static const struct i2c_client au0828_i2c_client_template = {
+	.name	= "au0828 internal",
+};
+
+static char *i2c_devs[128] = {
+	[0x8e >> 1] = "au8522",
+	[0xa0 >> 1] = "eeprom",
+	[0xc2 >> 1] = "tuner/xc5000",
+};
+
+static void do_i2c_scan(char *name, struct i2c_client *c)
+{
+	unsigned char buf;
+	int i, rc;
+
+	for (i = 0; i < 128; i++) {
+		c->addr = i;
+		rc = i2c_master_recv(c, &buf, 0);
+		if (rc < 0)
+			continue;
+		pr_info("%s: i2c scan: found device @ 0x%x  [%s]\n",
+		       name, i << 1, i2c_devs[i] ? i2c_devs[i] : "???");
+	}
+}
+
+/* init + register i2c adapter */
+int au0828_i2c_register(struct au0828_dev *dev)
+{
+	dprintk(1, "%s()\n", __func__);
+
+	dev->i2c_adap = au0828_i2c_adap_template;
+	dev->i2c_algo = au0828_i2c_algo_template;
+	dev->i2c_client = au0828_i2c_client_template;
+
+	dev->i2c_adap.dev.parent = &dev->usbdev->dev;
+
+	strlcpy(dev->i2c_adap.name, KBUILD_MODNAME,
+		sizeof(dev->i2c_adap.name));
+
+	dev->i2c_adap.algo = &dev->i2c_algo;
+	dev->i2c_adap.algo_data = dev;
+#ifdef CONFIG_VIDEO_AU0828_V4L2
+	i2c_set_adapdata(&dev->i2c_adap, &dev->v4l2_dev);
+#else
+	i2c_set_adapdata(&dev->i2c_adap, dev);
+#endif
+	i2c_add_adapter(&dev->i2c_adap);
+
+	dev->i2c_client.adapter = &dev->i2c_adap;
+
+	if (0 == dev->i2c_rc) {
+		pr_info("i2c bus registered\n");
+		if (i2c_scan)
+			do_i2c_scan(KBUILD_MODNAME, &dev->i2c_client);
+	} else
+		pr_info("i2c bus register FAILED\n");
+
+	return dev->i2c_rc;
+}
+
+int au0828_i2c_unregister(struct au0828_dev *dev)
+{
+	i2c_del_adapter(&dev->i2c_adap);
+	return 0;
+}
+
diff --git a/drivers/media/usb/au0828/au0828-input.c b/drivers/media/usb/au0828/au0828-input.c
new file mode 100644
index 0000000..832ed9f
--- /dev/null
+++ b/drivers/media/usb/au0828/au0828-input.c
@@ -0,0 +1,401 @@
+// SPDX-License-Identifier: GPL-2.0+
+// handle au0828 IR remotes via linux kernel input layer.
+//
+// Copyright (c) 2014 Mauro Carvalho Chehab <mchehab@samsung.com>
+// Copyright (c) 2014 Samsung Electronics Co., Ltd.
+//
+// Based on em28xx-input.c.
+
+#include "au0828.h"
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/usb.h>
+#include <linux/slab.h>
+#include <media/rc-core.h>
+
+static int disable_ir;
+module_param(disable_ir,        int, 0444);
+MODULE_PARM_DESC(disable_ir, "disable infrared remote support");
+
+struct au0828_rc {
+	struct au0828_dev *dev;
+	struct rc_dev *rc;
+	char name[32];
+	char phys[32];
+
+	/* poll decoder */
+	int polling;
+	struct delayed_work work;
+
+	/* i2c slave address of external device (if used) */
+	u16 i2c_dev_addr;
+
+	int  (*get_key_i2c)(struct au0828_rc *ir);
+};
+
+/*
+ * AU8522 has a builtin IR receiver. Add functions to get IR from it
+ */
+
+static int au8522_rc_write(struct au0828_rc *ir, u16 reg, u8 data)
+{
+	int rc;
+	char buf[] = { (reg >> 8) | 0x80, reg & 0xff, data };
+	struct i2c_msg msg = { .addr = ir->i2c_dev_addr, .flags = 0,
+			       .buf = buf, .len = sizeof(buf) };
+
+	rc = i2c_transfer(ir->dev->i2c_client.adapter, &msg, 1);
+
+	if (rc < 0)
+		return rc;
+
+	return (rc == 1) ? 0 : -EIO;
+}
+
+static int au8522_rc_read(struct au0828_rc *ir, u16 reg, int val,
+				 char *buf, int size)
+{
+	int rc;
+	char obuf[3];
+	struct i2c_msg msg[2] = { { .addr = ir->i2c_dev_addr, .flags = 0,
+				    .buf = obuf, .len = 2 },
+				  { .addr = ir->i2c_dev_addr, .flags = I2C_M_RD,
+				    .buf = buf, .len = size } };
+
+	obuf[0] = 0x40 | reg >> 8;
+	obuf[1] = reg & 0xff;
+	if (val >= 0) {
+		obuf[2] = val;
+		msg[0].len++;
+	}
+
+	rc = i2c_transfer(ir->dev->i2c_client.adapter, msg, 2);
+
+	if (rc < 0)
+		return rc;
+
+	return (rc == 2) ? 0 : -EIO;
+}
+
+static int au8522_rc_andor(struct au0828_rc *ir, u16 reg, u8 mask, u8 value)
+{
+	int rc;
+	char buf, oldbuf;
+
+	rc = au8522_rc_read(ir, reg, -1, &buf, 1);
+	if (rc < 0)
+		return rc;
+
+	oldbuf = buf;
+	buf = (buf & ~mask) | (value & mask);
+
+	/* Nothing to do, just return */
+	if (buf == oldbuf)
+		return 0;
+
+	return au8522_rc_write(ir, reg, buf);
+}
+
+#define au8522_rc_set(ir, reg, bit) au8522_rc_andor(ir, (reg), (bit), (bit))
+#define au8522_rc_clear(ir, reg, bit) au8522_rc_andor(ir, (reg), (bit), 0)
+
+/* Remote Controller time units */
+
+#define AU8522_UNIT		200000 /* ns */
+#define NEC_START_SPACE		(4500000 / AU8522_UNIT)
+#define NEC_START_PULSE		(562500 * 16)
+#define RC5_START_SPACE		(4 * AU8522_UNIT)
+#define RC5_START_PULSE		888888
+
+static int au0828_get_key_au8522(struct au0828_rc *ir)
+{
+	unsigned char buf[40];
+	DEFINE_IR_RAW_EVENT(rawir);
+	int i, j, rc;
+	int prv_bit, bit, width;
+	bool first = true;
+
+	/* do nothing if device is disconnected */
+	if (test_bit(DEV_DISCONNECTED, &ir->dev->dev_state))
+		return 0;
+
+	/* Check IR int */
+	rc = au8522_rc_read(ir, 0xe1, -1, buf, 1);
+	if (rc < 0 || !(buf[0] & (1 << 4))) {
+		/* Be sure that IR is enabled */
+		au8522_rc_set(ir, 0xe0, 1 << 4);
+		return 0;
+	}
+
+	/* Something arrived. Get the data */
+	rc = au8522_rc_read(ir, 0xe3, 0x11, buf, sizeof(buf));
+
+
+	if (rc < 0)
+		return rc;
+
+	/* Disable IR */
+	au8522_rc_clear(ir, 0xe0, 1 << 4);
+
+	/* Enable IR */
+	au8522_rc_set(ir, 0xe0, 1 << 4);
+
+	dprintk(16, "RC data received: %*ph\n", 40, buf);
+
+	prv_bit = (buf[0] >> 7) & 0x01;
+	width = 0;
+	for (i = 0; i < sizeof(buf); i++) {
+		for (j = 7; j >= 0; j--) {
+			bit = (buf[i] >> j) & 0x01;
+			if (bit == prv_bit) {
+				width++;
+				continue;
+			}
+
+			/*
+			 * Fix an au8522 bug: the first pulse event
+			 * is lost. So, we need to fake it, based on the
+			 * protocol. That means that not all raw decoders
+			 * will work, as we need to add a hack for each
+			 * protocol, based on the first space.
+			 * So, we only support RC5 and NEC.
+			 */
+
+			if (first) {
+				first = false;
+
+				init_ir_raw_event(&rawir);
+				rawir.pulse = true;
+				if (width > NEC_START_SPACE - 2 &&
+				    width < NEC_START_SPACE + 2) {
+					/* NEC protocol */
+					rawir.duration = NEC_START_PULSE;
+					dprintk(16, "Storing NEC start %s with duration %d",
+						rawir.pulse ? "pulse" : "space",
+						rawir.duration);
+				} else {
+					/* RC5 protocol */
+					rawir.duration = RC5_START_PULSE;
+					dprintk(16, "Storing RC5 start %s with duration %d",
+						rawir.pulse ? "pulse" : "space",
+						rawir.duration);
+				}
+				ir_raw_event_store(ir->rc, &rawir);
+			}
+
+			init_ir_raw_event(&rawir);
+			rawir.pulse = prv_bit ? false : true;
+			rawir.duration = AU8522_UNIT * width;
+			dprintk(16, "Storing %s with duration %d",
+				rawir.pulse ? "pulse" : "space",
+				rawir.duration);
+			ir_raw_event_store(ir->rc, &rawir);
+
+			width = 1;
+			prv_bit = bit;
+		}
+	}
+
+	init_ir_raw_event(&rawir);
+	rawir.pulse = prv_bit ? false : true;
+	rawir.duration = AU8522_UNIT * width;
+	dprintk(16, "Storing end %s with duration %d",
+		rawir.pulse ? "pulse" : "space",
+		rawir.duration);
+	ir_raw_event_store(ir->rc, &rawir);
+
+	ir_raw_event_handle(ir->rc);
+
+	return 1;
+}
+
+/*
+ * Generic IR code
+ */
+
+static void au0828_rc_work(struct work_struct *work)
+{
+	struct au0828_rc *ir = container_of(work, struct au0828_rc, work.work);
+	int rc;
+
+	rc = ir->get_key_i2c(ir);
+	if (rc < 0)
+		pr_info("Error while getting RC scancode\n");
+
+	schedule_delayed_work(&ir->work, msecs_to_jiffies(ir->polling));
+}
+
+static int au0828_rc_start(struct rc_dev *rc)
+{
+	struct au0828_rc *ir = rc->priv;
+
+	INIT_DELAYED_WORK(&ir->work, au0828_rc_work);
+
+	/* Enable IR */
+	au8522_rc_set(ir, 0xe0, 1 << 4);
+
+	schedule_delayed_work(&ir->work, msecs_to_jiffies(ir->polling));
+
+	return 0;
+}
+
+static void au0828_rc_stop(struct rc_dev *rc)
+{
+	struct au0828_rc *ir = rc->priv;
+
+	cancel_delayed_work_sync(&ir->work);
+
+	/* do nothing if device is disconnected */
+	if (!test_bit(DEV_DISCONNECTED, &ir->dev->dev_state)) {
+		/* Disable IR */
+		au8522_rc_clear(ir, 0xe0, 1 << 4);
+	}
+}
+
+static int au0828_probe_i2c_ir(struct au0828_dev *dev)
+{
+	int i = 0;
+	static const unsigned short addr_list[] = {
+		 0x47, I2C_CLIENT_END
+	};
+
+	while (addr_list[i] != I2C_CLIENT_END) {
+		if (i2c_probe_func_quick_read(dev->i2c_client.adapter,
+					      addr_list[i]) == 1)
+			return addr_list[i];
+		i++;
+	}
+
+	return -ENODEV;
+}
+
+int au0828_rc_register(struct au0828_dev *dev)
+{
+	struct au0828_rc *ir;
+	struct rc_dev *rc;
+	int err = -ENOMEM;
+	u16 i2c_rc_dev_addr = 0;
+
+	if (!dev->board.has_ir_i2c || disable_ir)
+		return 0;
+
+	i2c_rc_dev_addr = au0828_probe_i2c_ir(dev);
+	if (!i2c_rc_dev_addr)
+		return -ENODEV;
+
+	ir = kzalloc(sizeof(*ir), GFP_KERNEL);
+	rc = rc_allocate_device(RC_DRIVER_IR_RAW);
+	if (!ir || !rc)
+		goto error;
+
+	/* record handles to ourself */
+	ir->dev = dev;
+	dev->ir = ir;
+	ir->rc = rc;
+
+	rc->priv = ir;
+	rc->open = au0828_rc_start;
+	rc->close = au0828_rc_stop;
+
+	if (dev->board.has_ir_i2c) {	/* external i2c device */
+		switch (dev->boardnr) {
+		case AU0828_BOARD_HAUPPAUGE_HVR950Q:
+			rc->map_name = RC_MAP_HAUPPAUGE;
+			ir->get_key_i2c = au0828_get_key_au8522;
+			break;
+		default:
+			err = -ENODEV;
+			goto error;
+		}
+
+		ir->i2c_dev_addr = i2c_rc_dev_addr;
+	}
+
+	/* This is how often we ask the chip for IR information */
+	ir->polling = 100; /* ms */
+
+	/* init input device */
+	snprintf(ir->name, sizeof(ir->name), "au0828 IR (%s)",
+		 dev->board.name);
+
+	usb_make_path(dev->usbdev, ir->phys, sizeof(ir->phys));
+	strlcat(ir->phys, "/input0", sizeof(ir->phys));
+
+	rc->device_name = ir->name;
+	rc->input_phys = ir->phys;
+	rc->input_id.bustype = BUS_USB;
+	rc->input_id.version = 1;
+	rc->input_id.vendor = le16_to_cpu(dev->usbdev->descriptor.idVendor);
+	rc->input_id.product = le16_to_cpu(dev->usbdev->descriptor.idProduct);
+	rc->dev.parent = &dev->usbdev->dev;
+	rc->driver_name = "au0828-input";
+	rc->allowed_protocols = RC_PROTO_BIT_NEC | RC_PROTO_BIT_NECX |
+				RC_PROTO_BIT_NEC32 | RC_PROTO_BIT_RC5;
+
+	/* all done */
+	err = rc_register_device(rc);
+	if (err)
+		goto error;
+
+	pr_info("Remote controller %s initialized\n", ir->name);
+
+	return 0;
+
+error:
+	dev->ir = NULL;
+	rc_free_device(rc);
+	kfree(ir);
+	return err;
+}
+
+void au0828_rc_unregister(struct au0828_dev *dev)
+{
+	struct au0828_rc *ir = dev->ir;
+
+	/* skip detach on non attached boards */
+	if (!ir)
+		return;
+
+	rc_unregister_device(ir->rc);
+
+	/* done */
+	kfree(ir);
+	dev->ir = NULL;
+}
+
+int au0828_rc_suspend(struct au0828_dev *dev)
+{
+	struct au0828_rc *ir = dev->ir;
+
+	if (!ir)
+		return 0;
+
+	pr_info("Stopping RC\n");
+
+	cancel_delayed_work_sync(&ir->work);
+
+	/* Disable IR */
+	au8522_rc_clear(ir, 0xe0, 1 << 4);
+
+	return 0;
+}
+
+int au0828_rc_resume(struct au0828_dev *dev)
+{
+	struct au0828_rc *ir = dev->ir;
+
+	if (!ir)
+		return 0;
+
+	pr_info("Restarting RC\n");
+
+	/* Enable IR */
+	au8522_rc_set(ir, 0xe0, 1 << 4);
+
+	schedule_delayed_work(&ir->work, msecs_to_jiffies(ir->polling));
+
+	return 0;
+}
diff --git a/drivers/media/usb/au0828/au0828-reg.h b/drivers/media/usb/au0828/au0828-reg.h
new file mode 100644
index 0000000..7aaf107
--- /dev/null
+++ b/drivers/media/usb/au0828/au0828-reg.h
@@ -0,0 +1,62 @@
+/*
+ *  Driver for the Auvitek USB bridge
+ *
+ *  Copyright (c) 2008 Steven Toth <stoth@linuxtv.org>
+ *
+ *  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.
+ */
+
+/* We'll start to rename these registers once we have a better
+ * understanding of their meaning.
+ */
+#define REG_000 0x000
+#define REG_001 0x001
+#define REG_002 0x002
+#define REG_003 0x003
+
+#define AU0828_SENSORCTRL_100 0x100
+#define AU0828_SENSORCTRL_VBI_103 0x103
+
+/* I2C registers */
+#define AU0828_I2C_TRIGGER_200		0x200
+#define AU0828_I2C_STATUS_201		0x201
+#define AU0828_I2C_CLK_DIVIDER_202	0x202
+#define AU0828_I2C_DEST_ADDR_203	0x203
+#define AU0828_I2C_WRITE_FIFO_205	0x205
+#define AU0828_I2C_READ_FIFO_209	0x209
+#define AU0828_I2C_MULTIBYTE_MODE_2FF	0x2ff
+
+/* Audio registers */
+#define AU0828_AUDIOCTRL_50C 0x50C
+
+#define REG_600 0x600
+
+/*********************************************************************/
+/* Here are constants for values associated with the above registers */
+
+/* I2C Trigger (Reg 0x200) */
+#define AU0828_I2C_TRIGGER_WRITE	0x01
+#define AU0828_I2C_TRIGGER_READ		0x20
+#define AU0828_I2C_TRIGGER_HOLD		0x40
+
+/* I2C Status (Reg 0x201) */
+#define AU0828_I2C_STATUS_READ_DONE	0x01
+#define AU0828_I2C_STATUS_NO_READ_ACK	0x02
+#define AU0828_I2C_STATUS_WRITE_DONE	0x04
+#define AU0828_I2C_STATUS_NO_WRITE_ACK	0x08
+#define AU0828_I2C_STATUS_BUSY		0x10
+
+/* I2C Clock Divider (Reg 0x202) */
+#define AU0828_I2C_CLK_250KHZ 0x07
+#define AU0828_I2C_CLK_100KHZ 0x14
+#define AU0828_I2C_CLK_30KHZ  0x40
+#define AU0828_I2C_CLK_20KHZ  0x60
diff --git a/drivers/media/usb/au0828/au0828-vbi.c b/drivers/media/usb/au0828/au0828-vbi.c
new file mode 100644
index 0000000..9dd6bdb
--- /dev/null
+++ b/drivers/media/usb/au0828/au0828-vbi.c
@@ -0,0 +1,90 @@
+/*
+   au0828-vbi.c - VBI driver for au0828
+
+   Copyright (C) 2010 Devin Heitmueller <dheitmueller@kernellabs.com>
+
+   This work was sponsored by GetWellNetwork Inc.
+
+   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 "au0828.h"
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+
+/* ------------------------------------------------------------------ */
+
+static int vbi_queue_setup(struct vb2_queue *vq,
+			   unsigned int *nbuffers, unsigned int *nplanes,
+			   unsigned int sizes[], struct device *alloc_devs[])
+{
+	struct au0828_dev *dev = vb2_get_drv_priv(vq);
+	unsigned long size = dev->vbi_width * dev->vbi_height * 2;
+
+	if (*nplanes)
+		return sizes[0] < size ? -EINVAL : 0;
+	*nplanes = 1;
+	sizes[0] = size;
+	return 0;
+}
+
+static int vbi_buffer_prepare(struct vb2_buffer *vb)
+{
+	struct au0828_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
+	unsigned long size;
+
+	size = dev->vbi_width * dev->vbi_height * 2;
+
+	if (vb2_plane_size(vb, 0) < size) {
+		pr_err("%s data will not fit into plane (%lu < %lu)\n",
+			__func__, vb2_plane_size(vb, 0), size);
+		return -EINVAL;
+	}
+	vb2_set_plane_payload(vb, 0, size);
+
+	return 0;
+}
+
+static void
+vbi_buffer_queue(struct vb2_buffer *vb)
+{
+	struct au0828_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct au0828_buffer *buf =
+			container_of(vbuf, struct au0828_buffer, vb);
+	struct au0828_dmaqueue *vbiq = &dev->vbiq;
+	unsigned long flags = 0;
+
+	buf->mem = vb2_plane_vaddr(vb, 0);
+	buf->length = vb2_plane_size(vb, 0);
+
+	spin_lock_irqsave(&dev->slock, flags);
+	list_add_tail(&buf->list, &vbiq->active);
+	spin_unlock_irqrestore(&dev->slock, flags);
+}
+
+const struct vb2_ops au0828_vbi_qops = {
+	.queue_setup     = vbi_queue_setup,
+	.buf_prepare     = vbi_buffer_prepare,
+	.buf_queue       = vbi_buffer_queue,
+	.start_streaming = au0828_start_analog_streaming,
+	.stop_streaming  = au0828_stop_vbi_streaming,
+	.wait_prepare    = vb2_ops_wait_prepare,
+	.wait_finish     = vb2_ops_wait_finish,
+};
diff --git a/drivers/media/usb/au0828/au0828-video.c b/drivers/media/usb/au0828/au0828-video.c
new file mode 100644
index 0000000..62b4506
--- /dev/null
+++ b/drivers/media/usb/au0828/au0828-video.c
@@ -0,0 +1,2043 @@
+/*
+ * Auvitek AU0828 USB Bridge (Analog video support)
+ *
+ * Copyright (C) 2009 Devin Heitmueller <dheitmueller@linuxtv.org>
+ * Copyright (C) 2005-2008 Auvitek International, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * As published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+/* Developer Notes:
+ *
+ * The hardware scaler supported is unimplemented
+ * AC97 audio support is unimplemented (only i2s audio mode)
+ *
+ */
+
+#include "au0828.h"
+#include "au8522.h"
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-mc.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-event.h>
+#include <media/tuner.h>
+#include "au0828-reg.h"
+
+static DEFINE_MUTEX(au0828_sysfs_lock);
+
+/* ------------------------------------------------------------------
+	Videobuf operations
+   ------------------------------------------------------------------*/
+
+static unsigned int isoc_debug;
+module_param(isoc_debug, int, 0644);
+MODULE_PARM_DESC(isoc_debug, "enable debug messages [isoc transfers]");
+
+#define au0828_isocdbg(fmt, arg...) \
+do {\
+	if (isoc_debug) { \
+		pr_info("au0828 %s :"fmt, \
+		       __func__ , ##arg);	   \
+	} \
+  } while (0)
+
+static inline void i2c_gate_ctrl(struct au0828_dev *dev, int val)
+{
+	if (dev->dvb.frontend && dev->dvb.frontend->ops.analog_ops.i2c_gate_ctrl)
+		dev->dvb.frontend->ops.analog_ops.i2c_gate_ctrl(dev->dvb.frontend, val);
+}
+
+static inline void print_err_status(struct au0828_dev *dev,
+				    int packet, int status)
+{
+	char *errmsg = "Unknown";
+
+	switch (status) {
+	case -ENOENT:
+		errmsg = "unlinked synchronously";
+		break;
+	case -ECONNRESET:
+		errmsg = "unlinked asynchronously";
+		break;
+	case -ENOSR:
+		errmsg = "Buffer error (overrun)";
+		break;
+	case -EPIPE:
+		errmsg = "Stalled (device not responding)";
+		break;
+	case -EOVERFLOW:
+		errmsg = "Babble (bad cable?)";
+		break;
+	case -EPROTO:
+		errmsg = "Bit-stuff error (bad cable?)";
+		break;
+	case -EILSEQ:
+		errmsg = "CRC/Timeout (could be anything)";
+		break;
+	case -ETIME:
+		errmsg = "Device does not respond";
+		break;
+	}
+	if (packet < 0) {
+		au0828_isocdbg("URB status %d [%s].\n",	status, errmsg);
+	} else {
+		au0828_isocdbg("URB packet %d, status %d [%s].\n",
+			       packet, status, errmsg);
+	}
+}
+
+static int check_dev(struct au0828_dev *dev)
+{
+	if (test_bit(DEV_DISCONNECTED, &dev->dev_state)) {
+		pr_info("v4l2 ioctl: device not present\n");
+		return -ENODEV;
+	}
+
+	if (test_bit(DEV_MISCONFIGURED, &dev->dev_state)) {
+		pr_info("v4l2 ioctl: device is misconfigured; close and open it again\n");
+		return -EIO;
+	}
+	return 0;
+}
+
+/*
+ * IRQ callback, called by URB callback
+ */
+static void au0828_irq_callback(struct urb *urb)
+{
+	struct au0828_dmaqueue  *dma_q = urb->context;
+	struct au0828_dev *dev = container_of(dma_q, struct au0828_dev, vidq);
+	unsigned long flags = 0;
+	int i;
+
+	switch (urb->status) {
+	case 0:             /* success */
+	case -ETIMEDOUT:    /* NAK */
+		break;
+	case -ECONNRESET:   /* kill */
+	case -ENOENT:
+	case -ESHUTDOWN:
+		au0828_isocdbg("au0828_irq_callback called: status kill\n");
+		return;
+	default:            /* unknown error */
+		au0828_isocdbg("urb completion error %d.\n", urb->status);
+		break;
+	}
+
+	/* Copy data from URB */
+	spin_lock_irqsave(&dev->slock, flags);
+	dev->isoc_ctl.isoc_copy(dev, urb);
+	spin_unlock_irqrestore(&dev->slock, flags);
+
+	/* Reset urb buffers */
+	for (i = 0; i < urb->number_of_packets; i++) {
+		urb->iso_frame_desc[i].status = 0;
+		urb->iso_frame_desc[i].actual_length = 0;
+	}
+	urb->status = 0;
+
+	urb->status = usb_submit_urb(urb, GFP_ATOMIC);
+	if (urb->status) {
+		au0828_isocdbg("urb resubmit failed (error=%i)\n",
+			       urb->status);
+	}
+	dev->stream_state = STREAM_ON;
+}
+
+/*
+ * Stop and Deallocate URBs
+ */
+static void au0828_uninit_isoc(struct au0828_dev *dev)
+{
+	struct urb *urb;
+	int i;
+
+	au0828_isocdbg("au0828: called au0828_uninit_isoc\n");
+
+	dev->isoc_ctl.nfields = -1;
+	for (i = 0; i < dev->isoc_ctl.num_bufs; i++) {
+		urb = dev->isoc_ctl.urb[i];
+		if (urb) {
+			if (!irqs_disabled())
+				usb_kill_urb(urb);
+			else
+				usb_unlink_urb(urb);
+
+			if (dev->isoc_ctl.transfer_buffer[i]) {
+				usb_free_coherent(dev->usbdev,
+					urb->transfer_buffer_length,
+					dev->isoc_ctl.transfer_buffer[i],
+					urb->transfer_dma);
+			}
+			usb_free_urb(urb);
+			dev->isoc_ctl.urb[i] = NULL;
+		}
+		dev->isoc_ctl.transfer_buffer[i] = NULL;
+	}
+
+	kfree(dev->isoc_ctl.urb);
+	kfree(dev->isoc_ctl.transfer_buffer);
+
+	dev->isoc_ctl.urb = NULL;
+	dev->isoc_ctl.transfer_buffer = NULL;
+	dev->isoc_ctl.num_bufs = 0;
+
+	dev->stream_state = STREAM_OFF;
+}
+
+/*
+ * Allocate URBs and start IRQ
+ */
+static int au0828_init_isoc(struct au0828_dev *dev, int max_packets,
+			    int num_bufs, int max_pkt_size,
+			    int (*isoc_copy) (struct au0828_dev *dev, struct urb *urb))
+{
+	struct au0828_dmaqueue *dma_q = &dev->vidq;
+	int i;
+	int sb_size, pipe;
+	struct urb *urb;
+	int j, k;
+	int rc;
+
+	au0828_isocdbg("au0828: called au0828_prepare_isoc\n");
+
+	dev->isoc_ctl.isoc_copy = isoc_copy;
+	dev->isoc_ctl.num_bufs = num_bufs;
+
+	dev->isoc_ctl.urb = kcalloc(num_bufs, sizeof(void *),  GFP_KERNEL);
+	if (!dev->isoc_ctl.urb) {
+		au0828_isocdbg("cannot alloc memory for usb buffers\n");
+		return -ENOMEM;
+	}
+
+	dev->isoc_ctl.transfer_buffer = kcalloc(num_bufs, sizeof(void *),
+						GFP_KERNEL);
+	if (!dev->isoc_ctl.transfer_buffer) {
+		au0828_isocdbg("cannot allocate memory for usb transfer\n");
+		kfree(dev->isoc_ctl.urb);
+		return -ENOMEM;
+	}
+
+	dev->isoc_ctl.max_pkt_size = max_pkt_size;
+	dev->isoc_ctl.buf = NULL;
+
+	sb_size = max_packets * dev->isoc_ctl.max_pkt_size;
+
+	/* allocate urbs and transfer buffers */
+	for (i = 0; i < dev->isoc_ctl.num_bufs; i++) {
+		urb = usb_alloc_urb(max_packets, GFP_KERNEL);
+		if (!urb) {
+			au0828_uninit_isoc(dev);
+			return -ENOMEM;
+		}
+		dev->isoc_ctl.urb[i] = urb;
+
+		dev->isoc_ctl.transfer_buffer[i] = usb_alloc_coherent(dev->usbdev,
+			sb_size, GFP_KERNEL, &urb->transfer_dma);
+		if (!dev->isoc_ctl.transfer_buffer[i]) {
+			printk("unable to allocate %i bytes for transfer buffer %i%s\n",
+					sb_size, i,
+					in_interrupt() ? " while in int" : "");
+			au0828_uninit_isoc(dev);
+			return -ENOMEM;
+		}
+		memset(dev->isoc_ctl.transfer_buffer[i], 0, sb_size);
+
+		pipe = usb_rcvisocpipe(dev->usbdev,
+				       dev->isoc_in_endpointaddr),
+
+		usb_fill_int_urb(urb, dev->usbdev, pipe,
+				 dev->isoc_ctl.transfer_buffer[i], sb_size,
+				 au0828_irq_callback, dma_q, 1);
+
+		urb->number_of_packets = max_packets;
+		urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
+
+		k = 0;
+		for (j = 0; j < max_packets; j++) {
+			urb->iso_frame_desc[j].offset = k;
+			urb->iso_frame_desc[j].length =
+						dev->isoc_ctl.max_pkt_size;
+			k += dev->isoc_ctl.max_pkt_size;
+		}
+	}
+
+	/* submit urbs and enables IRQ */
+	for (i = 0; i < dev->isoc_ctl.num_bufs; i++) {
+		rc = usb_submit_urb(dev->isoc_ctl.urb[i], GFP_ATOMIC);
+		if (rc) {
+			au0828_isocdbg("submit of urb %i failed (error=%i)\n",
+				       i, rc);
+			au0828_uninit_isoc(dev);
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Announces that a buffer were filled and request the next
+ */
+static inline void buffer_filled(struct au0828_dev *dev,
+				 struct au0828_dmaqueue *dma_q,
+				 struct au0828_buffer *buf)
+{
+	struct vb2_v4l2_buffer *vb = &buf->vb;
+	struct vb2_queue *q = vb->vb2_buf.vb2_queue;
+
+	/* Advice that buffer was filled */
+	au0828_isocdbg("[%p/%d] wakeup\n", buf, buf->top_field);
+
+	if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		vb->sequence = dev->frame_count++;
+	else
+		vb->sequence = dev->vbi_frame_count++;
+
+	vb->field = V4L2_FIELD_INTERLACED;
+	vb->vb2_buf.timestamp = ktime_get_ns();
+	vb2_buffer_done(&vb->vb2_buf, VB2_BUF_STATE_DONE);
+}
+
+/*
+ * Identify the buffer header type and properly handles
+ */
+static void au0828_copy_video(struct au0828_dev *dev,
+			      struct au0828_dmaqueue  *dma_q,
+			      struct au0828_buffer *buf,
+			      unsigned char *p,
+			      unsigned char *outp, unsigned long len)
+{
+	void *fieldstart, *startwrite, *startread;
+	int  linesdone, currlinedone, offset, lencopy, remain;
+	int bytesperline = dev->width << 1; /* Assumes 16-bit depth @@@@ */
+
+	if (len == 0)
+		return;
+
+	if (dma_q->pos + len > buf->length)
+		len = buf->length - dma_q->pos;
+
+	startread = p;
+	remain = len;
+
+	/* Interlaces frame */
+	if (buf->top_field)
+		fieldstart = outp;
+	else
+		fieldstart = outp + bytesperline;
+
+	linesdone = dma_q->pos / bytesperline;
+	currlinedone = dma_q->pos % bytesperline;
+	offset = linesdone * bytesperline * 2 + currlinedone;
+	startwrite = fieldstart + offset;
+	lencopy = bytesperline - currlinedone;
+	lencopy = lencopy > remain ? remain : lencopy;
+
+	if ((char *)startwrite + lencopy > (char *)outp + buf->length) {
+		au0828_isocdbg("Overflow of %zi bytes past buffer end (1)\n",
+			       ((char *)startwrite + lencopy) -
+			       ((char *)outp + buf->length));
+		remain = (char *)outp + buf->length - (char *)startwrite;
+		lencopy = remain;
+	}
+	if (lencopy <= 0)
+		return;
+	memcpy(startwrite, startread, lencopy);
+
+	remain -= lencopy;
+
+	while (remain > 0) {
+		startwrite += lencopy + bytesperline;
+		startread += lencopy;
+		if (bytesperline > remain)
+			lencopy = remain;
+		else
+			lencopy = bytesperline;
+
+		if ((char *)startwrite + lencopy > (char *)outp +
+		    buf->length) {
+			au0828_isocdbg("Overflow %zi bytes past buf end (2)\n",
+				       ((char *)startwrite + lencopy) -
+				       ((char *)outp + buf->length));
+			lencopy = remain = (char *)outp + buf->length -
+					   (char *)startwrite;
+		}
+		if (lencopy <= 0)
+			break;
+
+		memcpy(startwrite, startread, lencopy);
+
+		remain -= lencopy;
+	}
+
+	if (offset > 1440) {
+		/* We have enough data to check for greenscreen */
+		if (outp[0] < 0x60 && outp[1440] < 0x60)
+			dev->greenscreen_detected = 1;
+	}
+
+	dma_q->pos += len;
+}
+
+/*
+ * video-buf generic routine to get the next available buffer
+ */
+static inline void get_next_buf(struct au0828_dmaqueue *dma_q,
+				struct au0828_buffer **buf)
+{
+	struct au0828_dev *dev = container_of(dma_q, struct au0828_dev, vidq);
+
+	if (list_empty(&dma_q->active)) {
+		au0828_isocdbg("No active queue to serve\n");
+		dev->isoc_ctl.buf = NULL;
+		*buf = NULL;
+		return;
+	}
+
+	/* Get the next buffer */
+	*buf = list_entry(dma_q->active.next, struct au0828_buffer, list);
+	/* Cleans up buffer - Useful for testing for frame/URB loss */
+	list_del(&(*buf)->list);
+	dma_q->pos = 0;
+	(*buf)->vb_buf = (*buf)->mem;
+	dev->isoc_ctl.buf = *buf;
+
+	return;
+}
+
+static void au0828_copy_vbi(struct au0828_dev *dev,
+			      struct au0828_dmaqueue  *dma_q,
+			      struct au0828_buffer *buf,
+			      unsigned char *p,
+			      unsigned char *outp, unsigned long len)
+{
+	unsigned char *startwrite, *startread;
+	int bytesperline;
+	int i, j = 0;
+
+	if (dev == NULL) {
+		au0828_isocdbg("dev is null\n");
+		return;
+	}
+
+	if (dma_q == NULL) {
+		au0828_isocdbg("dma_q is null\n");
+		return;
+	}
+	if (buf == NULL)
+		return;
+	if (p == NULL) {
+		au0828_isocdbg("p is null\n");
+		return;
+	}
+	if (outp == NULL) {
+		au0828_isocdbg("outp is null\n");
+		return;
+	}
+
+	bytesperline = dev->vbi_width;
+
+	if (dma_q->pos + len > buf->length)
+		len = buf->length - dma_q->pos;
+
+	startread = p;
+	startwrite = outp + (dma_q->pos / 2);
+
+	/* Make sure the bottom field populates the second half of the frame */
+	if (buf->top_field == 0)
+		startwrite += bytesperline * dev->vbi_height;
+
+	for (i = 0; i < len; i += 2)
+		startwrite[j++] = startread[i+1];
+
+	dma_q->pos += len;
+}
+
+
+/*
+ * video-buf generic routine to get the next available VBI buffer
+ */
+static inline void vbi_get_next_buf(struct au0828_dmaqueue *dma_q,
+				    struct au0828_buffer **buf)
+{
+	struct au0828_dev *dev = container_of(dma_q, struct au0828_dev, vbiq);
+
+	if (list_empty(&dma_q->active)) {
+		au0828_isocdbg("No active queue to serve\n");
+		dev->isoc_ctl.vbi_buf = NULL;
+		*buf = NULL;
+		return;
+	}
+
+	/* Get the next buffer */
+	*buf = list_entry(dma_q->active.next, struct au0828_buffer, list);
+	/* Cleans up buffer - Useful for testing for frame/URB loss */
+	list_del(&(*buf)->list);
+	dma_q->pos = 0;
+	(*buf)->vb_buf = (*buf)->mem;
+	dev->isoc_ctl.vbi_buf = *buf;
+	return;
+}
+
+/*
+ * Controls the isoc copy of each urb packet
+ */
+static inline int au0828_isoc_copy(struct au0828_dev *dev, struct urb *urb)
+{
+	struct au0828_buffer    *buf;
+	struct au0828_buffer    *vbi_buf;
+	struct au0828_dmaqueue  *dma_q = urb->context;
+	struct au0828_dmaqueue  *vbi_dma_q = &dev->vbiq;
+	unsigned char *outp = NULL;
+	unsigned char *vbioutp = NULL;
+	int i, len = 0, rc = 1;
+	unsigned char *p;
+	unsigned char fbyte;
+	unsigned int vbi_field_size;
+	unsigned int remain, lencopy;
+
+	if (!dev)
+		return 0;
+
+	if (test_bit(DEV_DISCONNECTED, &dev->dev_state) ||
+	    test_bit(DEV_MISCONFIGURED, &dev->dev_state))
+		return 0;
+
+	if (urb->status < 0) {
+		print_err_status(dev, -1, urb->status);
+		if (urb->status == -ENOENT)
+			return 0;
+	}
+
+	buf = dev->isoc_ctl.buf;
+	if (buf != NULL)
+		outp = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
+
+	vbi_buf = dev->isoc_ctl.vbi_buf;
+	if (vbi_buf != NULL)
+		vbioutp = vb2_plane_vaddr(&vbi_buf->vb.vb2_buf, 0);
+
+	for (i = 0; i < urb->number_of_packets; i++) {
+		int status = urb->iso_frame_desc[i].status;
+
+		if (status < 0) {
+			print_err_status(dev, i, status);
+			if (urb->iso_frame_desc[i].status != -EPROTO)
+				continue;
+		}
+
+		if (urb->iso_frame_desc[i].actual_length <= 0)
+			continue;
+
+		if (urb->iso_frame_desc[i].actual_length >
+						dev->max_pkt_size) {
+			au0828_isocdbg("packet bigger than packet size");
+			continue;
+		}
+
+		p = urb->transfer_buffer + urb->iso_frame_desc[i].offset;
+		fbyte = p[0];
+		len = urb->iso_frame_desc[i].actual_length - 4;
+		p += 4;
+
+		if (fbyte & 0x80) {
+			len -= 4;
+			p += 4;
+			au0828_isocdbg("Video frame %s\n",
+				       (fbyte & 0x40) ? "odd" : "even");
+			if (fbyte & 0x40) {
+				/* VBI */
+				if (vbi_buf != NULL)
+					buffer_filled(dev, vbi_dma_q, vbi_buf);
+				vbi_get_next_buf(vbi_dma_q, &vbi_buf);
+				if (vbi_buf == NULL)
+					vbioutp = NULL;
+				else
+					vbioutp = vb2_plane_vaddr(
+						&vbi_buf->vb.vb2_buf, 0);
+
+				/* Video */
+				if (buf != NULL)
+					buffer_filled(dev, dma_q, buf);
+				get_next_buf(dma_q, &buf);
+				if (buf == NULL)
+					outp = NULL;
+				else
+					outp = vb2_plane_vaddr(
+						&buf->vb.vb2_buf, 0);
+
+				/* As long as isoc traffic is arriving, keep
+				   resetting the timer */
+				if (dev->vid_timeout_running)
+					mod_timer(&dev->vid_timeout,
+						  jiffies + (HZ / 10));
+				if (dev->vbi_timeout_running)
+					mod_timer(&dev->vbi_timeout,
+						  jiffies + (HZ / 10));
+			}
+
+			if (buf != NULL) {
+				if (fbyte & 0x40)
+					buf->top_field = 1;
+				else
+					buf->top_field = 0;
+			}
+
+			if (vbi_buf != NULL) {
+				if (fbyte & 0x40)
+					vbi_buf->top_field = 1;
+				else
+					vbi_buf->top_field = 0;
+			}
+
+			dev->vbi_read = 0;
+			vbi_dma_q->pos = 0;
+			dma_q->pos = 0;
+		}
+
+		vbi_field_size = dev->vbi_width * dev->vbi_height * 2;
+		if (dev->vbi_read < vbi_field_size) {
+			remain  = vbi_field_size - dev->vbi_read;
+			if (len < remain)
+				lencopy = len;
+			else
+				lencopy = remain;
+
+			if (vbi_buf != NULL)
+				au0828_copy_vbi(dev, vbi_dma_q, vbi_buf, p,
+						vbioutp, len);
+
+			len -= lencopy;
+			p += lencopy;
+			dev->vbi_read += lencopy;
+		}
+
+		if (dev->vbi_read >= vbi_field_size && buf != NULL)
+			au0828_copy_video(dev, dma_q, buf, p, outp, len);
+	}
+	return rc;
+}
+
+void au0828_usb_v4l2_media_release(struct au0828_dev *dev)
+{
+#ifdef CONFIG_MEDIA_CONTROLLER
+	int i;
+
+	for (i = 0; i < AU0828_MAX_INPUT; i++) {
+		if (AUVI_INPUT(i).type == AU0828_VMUX_UNDEFINED)
+			return;
+		media_device_unregister_entity(&dev->input_ent[i]);
+	}
+#endif
+}
+
+static void au0828_usb_v4l2_release(struct v4l2_device *v4l2_dev)
+{
+	struct au0828_dev *dev =
+		container_of(v4l2_dev, struct au0828_dev, v4l2_dev);
+
+	v4l2_ctrl_handler_free(&dev->v4l2_ctrl_hdl);
+	v4l2_device_unregister(&dev->v4l2_dev);
+	au0828_usb_v4l2_media_release(dev);
+	au0828_usb_release(dev);
+}
+
+int au0828_v4l2_device_register(struct usb_interface *interface,
+				struct au0828_dev *dev)
+{
+	int retval;
+
+	if (AUVI_INPUT(0).type == AU0828_VMUX_UNDEFINED)
+		return 0;
+
+	/* Create the v4l2_device */
+#ifdef CONFIG_MEDIA_CONTROLLER
+	dev->v4l2_dev.mdev = dev->media_dev;
+#endif
+	retval = v4l2_device_register(&interface->dev, &dev->v4l2_dev);
+	if (retval) {
+		pr_err("%s() v4l2_device_register failed\n",
+		       __func__);
+		return retval;
+	}
+
+	dev->v4l2_dev.release = au0828_usb_v4l2_release;
+
+	/* This control handler will inherit the controls from au8522 */
+	retval = v4l2_ctrl_handler_init(&dev->v4l2_ctrl_hdl, 4);
+	if (retval) {
+		pr_err("%s() v4l2_ctrl_handler_init failed\n",
+		       __func__);
+		return retval;
+	}
+	dev->v4l2_dev.ctrl_handler = &dev->v4l2_ctrl_hdl;
+
+	return 0;
+}
+
+static int queue_setup(struct vb2_queue *vq,
+		       unsigned int *nbuffers, unsigned int *nplanes,
+		       unsigned int sizes[], struct device *alloc_devs[])
+{
+	struct au0828_dev *dev = vb2_get_drv_priv(vq);
+	unsigned long size = dev->height * dev->bytesperline;
+
+	if (*nplanes)
+		return sizes[0] < size ? -EINVAL : 0;
+	*nplanes = 1;
+	sizes[0] = size;
+	return 0;
+}
+
+static int
+buffer_prepare(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct au0828_buffer *buf = container_of(vbuf,
+				struct au0828_buffer, vb);
+	struct au0828_dev    *dev = vb2_get_drv_priv(vb->vb2_queue);
+
+	buf->length = dev->height * dev->bytesperline;
+
+	if (vb2_plane_size(vb, 0) < buf->length) {
+		pr_err("%s data will not fit into plane (%lu < %lu)\n",
+			__func__, vb2_plane_size(vb, 0), buf->length);
+		return -EINVAL;
+	}
+	vb2_set_plane_payload(&buf->vb.vb2_buf, 0, buf->length);
+	return 0;
+}
+
+static void
+buffer_queue(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct au0828_buffer    *buf     = container_of(vbuf,
+							struct au0828_buffer,
+							vb);
+	struct au0828_dev       *dev     = vb2_get_drv_priv(vb->vb2_queue);
+	struct au0828_dmaqueue  *vidq    = &dev->vidq;
+	unsigned long flags = 0;
+
+	buf->mem = vb2_plane_vaddr(vb, 0);
+	buf->length = vb2_plane_size(vb, 0);
+
+	spin_lock_irqsave(&dev->slock, flags);
+	list_add_tail(&buf->list, &vidq->active);
+	spin_unlock_irqrestore(&dev->slock, flags);
+}
+
+static int au0828_i2s_init(struct au0828_dev *dev)
+{
+	/* Enable i2s mode */
+	au0828_writereg(dev, AU0828_AUDIOCTRL_50C, 0x01);
+	return 0;
+}
+
+/*
+ * Auvitek au0828 analog stream enable
+ */
+static int au0828_analog_stream_enable(struct au0828_dev *d)
+{
+	struct usb_interface *iface;
+	int ret, h, w;
+
+	dprintk(1, "au0828_analog_stream_enable called\n");
+
+	iface = usb_ifnum_to_if(d->usbdev, 0);
+	if (iface && iface->cur_altsetting->desc.bAlternateSetting != 5) {
+		dprintk(1, "Changing intf#0 to alt 5\n");
+		/* set au0828 interface0 to AS5 here again */
+		ret = usb_set_interface(d->usbdev, 0, 5);
+		if (ret < 0) {
+			pr_info("Au0828 can't set alt setting to 5!\n");
+			return -EBUSY;
+		}
+	}
+
+	h = d->height / 2 + 2;
+	w = d->width * 2;
+
+	au0828_writereg(d, AU0828_SENSORCTRL_VBI_103, 0x00);
+	au0828_writereg(d, 0x106, 0x00);
+	/* set x position */
+	au0828_writereg(d, 0x110, 0x00);
+	au0828_writereg(d, 0x111, 0x00);
+	au0828_writereg(d, 0x114, w & 0xff);
+	au0828_writereg(d, 0x115, w >> 8);
+	/* set y position */
+	au0828_writereg(d, 0x112, 0x00);
+	au0828_writereg(d, 0x113, 0x00);
+	au0828_writereg(d, 0x116, h & 0xff);
+	au0828_writereg(d, 0x117, h >> 8);
+	au0828_writereg(d, AU0828_SENSORCTRL_100, 0xb3);
+
+	return 0;
+}
+
+static int au0828_analog_stream_disable(struct au0828_dev *d)
+{
+	dprintk(1, "au0828_analog_stream_disable called\n");
+	au0828_writereg(d, AU0828_SENSORCTRL_100, 0x0);
+	return 0;
+}
+
+static void au0828_analog_stream_reset(struct au0828_dev *dev)
+{
+	dprintk(1, "au0828_analog_stream_reset called\n");
+	au0828_writereg(dev, AU0828_SENSORCTRL_100, 0x0);
+	mdelay(30);
+	au0828_writereg(dev, AU0828_SENSORCTRL_100, 0xb3);
+}
+
+/*
+ * Some operations needs to stop current streaming
+ */
+static int au0828_stream_interrupt(struct au0828_dev *dev)
+{
+	dev->stream_state = STREAM_INTERRUPT;
+	if (test_bit(DEV_DISCONNECTED, &dev->dev_state))
+		return -ENODEV;
+	return 0;
+}
+
+int au0828_start_analog_streaming(struct vb2_queue *vq, unsigned int count)
+{
+	struct au0828_dev *dev = vb2_get_drv_priv(vq);
+	int rc = 0;
+
+	dprintk(1, "au0828_start_analog_streaming called %d\n",
+		dev->streaming_users);
+
+	if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		dev->frame_count = 0;
+	else
+		dev->vbi_frame_count = 0;
+
+	if (dev->streaming_users == 0) {
+		/* If we were doing ac97 instead of i2s, it would go here...*/
+		au0828_i2s_init(dev);
+		rc = au0828_init_isoc(dev, AU0828_ISO_PACKETS_PER_URB,
+				   AU0828_MAX_ISO_BUFS, dev->max_pkt_size,
+				   au0828_isoc_copy);
+		if (rc < 0) {
+			pr_info("au0828_init_isoc failed\n");
+			return rc;
+		}
+
+		if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+			v4l2_device_call_all(&dev->v4l2_dev, 0, video,
+						s_stream, 1);
+			dev->vid_timeout_running = 1;
+			mod_timer(&dev->vid_timeout, jiffies + (HZ / 10));
+		} else if (vq->type == V4L2_BUF_TYPE_VBI_CAPTURE) {
+			dev->vbi_timeout_running = 1;
+			mod_timer(&dev->vbi_timeout, jiffies + (HZ / 10));
+		}
+	}
+	dev->streaming_users++;
+	return rc;
+}
+
+static void au0828_stop_streaming(struct vb2_queue *vq)
+{
+	struct au0828_dev *dev = vb2_get_drv_priv(vq);
+	struct au0828_dmaqueue *vidq = &dev->vidq;
+	unsigned long flags = 0;
+
+	dprintk(1, "au0828_stop_streaming called %d\n", dev->streaming_users);
+
+	if (dev->streaming_users-- == 1)
+		au0828_uninit_isoc(dev);
+
+	v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_stream, 0);
+	dev->vid_timeout_running = 0;
+	del_timer_sync(&dev->vid_timeout);
+
+	spin_lock_irqsave(&dev->slock, flags);
+	if (dev->isoc_ctl.buf != NULL) {
+		vb2_buffer_done(&dev->isoc_ctl.buf->vb.vb2_buf,
+				VB2_BUF_STATE_ERROR);
+		dev->isoc_ctl.buf = NULL;
+	}
+	while (!list_empty(&vidq->active)) {
+		struct au0828_buffer *buf;
+
+		buf = list_entry(vidq->active.next, struct au0828_buffer, list);
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+		list_del(&buf->list);
+	}
+	spin_unlock_irqrestore(&dev->slock, flags);
+}
+
+void au0828_stop_vbi_streaming(struct vb2_queue *vq)
+{
+	struct au0828_dev *dev = vb2_get_drv_priv(vq);
+	struct au0828_dmaqueue *vbiq = &dev->vbiq;
+	unsigned long flags = 0;
+
+	dprintk(1, "au0828_stop_vbi_streaming called %d\n",
+		dev->streaming_users);
+
+	if (dev->streaming_users-- == 1)
+		au0828_uninit_isoc(dev);
+
+	spin_lock_irqsave(&dev->slock, flags);
+	if (dev->isoc_ctl.vbi_buf != NULL) {
+		vb2_buffer_done(&dev->isoc_ctl.vbi_buf->vb.vb2_buf,
+				VB2_BUF_STATE_ERROR);
+		dev->isoc_ctl.vbi_buf = NULL;
+	}
+	while (!list_empty(&vbiq->active)) {
+		struct au0828_buffer *buf;
+
+		buf = list_entry(vbiq->active.next, struct au0828_buffer, list);
+		list_del(&buf->list);
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+	}
+	spin_unlock_irqrestore(&dev->slock, flags);
+
+	dev->vbi_timeout_running = 0;
+	del_timer_sync(&dev->vbi_timeout);
+}
+
+static const struct vb2_ops au0828_video_qops = {
+	.queue_setup     = queue_setup,
+	.buf_prepare     = buffer_prepare,
+	.buf_queue       = buffer_queue,
+	.start_streaming = au0828_start_analog_streaming,
+	.stop_streaming  = au0828_stop_streaming,
+	.wait_prepare    = vb2_ops_wait_prepare,
+	.wait_finish     = vb2_ops_wait_finish,
+};
+
+/* ------------------------------------------------------------------
+   V4L2 interface
+   ------------------------------------------------------------------*/
+/*
+ * au0828_analog_unregister
+ * unregister v4l2 devices
+ */
+int au0828_analog_unregister(struct au0828_dev *dev)
+{
+	dprintk(1, "au0828_analog_unregister called\n");
+
+	/* No analog TV */
+	if (AUVI_INPUT(0).type == AU0828_VMUX_UNDEFINED)
+		return 0;
+
+	mutex_lock(&au0828_sysfs_lock);
+	video_unregister_device(&dev->vdev);
+	video_unregister_device(&dev->vbi_dev);
+	mutex_unlock(&au0828_sysfs_lock);
+
+	v4l2_device_disconnect(&dev->v4l2_dev);
+	v4l2_device_put(&dev->v4l2_dev);
+
+	return 1;
+}
+
+/* This function ensures that video frames continue to be delivered even if
+   the ITU-656 input isn't receiving any data (thereby preventing applications
+   such as tvtime from hanging) */
+static void au0828_vid_buffer_timeout(struct timer_list *t)
+{
+	struct au0828_dev *dev = from_timer(dev, t, vid_timeout);
+	struct au0828_dmaqueue *dma_q = &dev->vidq;
+	struct au0828_buffer *buf;
+	unsigned char *vid_data;
+	unsigned long flags = 0;
+
+	spin_lock_irqsave(&dev->slock, flags);
+
+	buf = dev->isoc_ctl.buf;
+	if (buf != NULL) {
+		vid_data = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
+		memset(vid_data, 0x00, buf->length); /* Blank green frame */
+		buffer_filled(dev, dma_q, buf);
+	}
+	get_next_buf(dma_q, &buf);
+
+	if (dev->vid_timeout_running == 1)
+		mod_timer(&dev->vid_timeout, jiffies + (HZ / 10));
+
+	spin_unlock_irqrestore(&dev->slock, flags);
+}
+
+static void au0828_vbi_buffer_timeout(struct timer_list *t)
+{
+	struct au0828_dev *dev = from_timer(dev, t, vbi_timeout);
+	struct au0828_dmaqueue *dma_q = &dev->vbiq;
+	struct au0828_buffer *buf;
+	unsigned char *vbi_data;
+	unsigned long flags = 0;
+
+	spin_lock_irqsave(&dev->slock, flags);
+
+	buf = dev->isoc_ctl.vbi_buf;
+	if (buf != NULL) {
+		vbi_data = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
+		memset(vbi_data, 0x00, buf->length);
+		buffer_filled(dev, dma_q, buf);
+	}
+	vbi_get_next_buf(dma_q, &buf);
+
+	if (dev->vbi_timeout_running == 1)
+		mod_timer(&dev->vbi_timeout, jiffies + (HZ / 10));
+	spin_unlock_irqrestore(&dev->slock, flags);
+}
+
+static int au0828_v4l2_open(struct file *filp)
+{
+	struct au0828_dev *dev = video_drvdata(filp);
+	int ret;
+
+	dprintk(1,
+		"%s called std_set %d dev_state %ld stream users %d users %d\n",
+		__func__, dev->std_set_in_tuner_core, dev->dev_state,
+		dev->streaming_users, dev->users);
+
+	if (mutex_lock_interruptible(&dev->lock))
+		return -ERESTARTSYS;
+
+	ret = v4l2_fh_open(filp);
+	if (ret) {
+		au0828_isocdbg("%s: v4l2_fh_open() returned error %d\n",
+				__func__, ret);
+		mutex_unlock(&dev->lock);
+		return ret;
+	}
+
+	if (dev->users == 0) {
+		au0828_analog_stream_enable(dev);
+		au0828_analog_stream_reset(dev);
+		dev->stream_state = STREAM_OFF;
+		set_bit(DEV_INITIALIZED, &dev->dev_state);
+	}
+	dev->users++;
+	mutex_unlock(&dev->lock);
+	return ret;
+}
+
+static int au0828_v4l2_close(struct file *filp)
+{
+	int ret;
+	struct au0828_dev *dev = video_drvdata(filp);
+	struct video_device *vdev = video_devdata(filp);
+
+	dprintk(1,
+		"%s called std_set %d dev_state %ld stream users %d users %d\n",
+		__func__, dev->std_set_in_tuner_core, dev->dev_state,
+		dev->streaming_users, dev->users);
+
+	mutex_lock(&dev->lock);
+	if (vdev->vfl_type == VFL_TYPE_GRABBER && dev->vid_timeout_running) {
+		/* Cancel timeout thread in case they didn't call streamoff */
+		dev->vid_timeout_running = 0;
+		del_timer_sync(&dev->vid_timeout);
+	} else if (vdev->vfl_type == VFL_TYPE_VBI &&
+			dev->vbi_timeout_running) {
+		/* Cancel timeout thread in case they didn't call streamoff */
+		dev->vbi_timeout_running = 0;
+		del_timer_sync(&dev->vbi_timeout);
+	}
+
+	if (test_bit(DEV_DISCONNECTED, &dev->dev_state))
+		goto end;
+
+	if (dev->users == 1) {
+		/*
+		 * Avoid putting tuner in sleep if DVB or ALSA are
+		 * streaming.
+		 *
+		 * On most USB devices  like au0828 the tuner can
+		 * be safely put in sleep stare here if ALSA isn't
+		 * streaming. Exceptions are some very old USB tuner
+		 * models such as em28xx-based WinTV USB2 which have
+		 * a separate audio output jack. The devices that have
+		 * a separate audio output jack have analog tuners,
+		 * like Philips FM1236. Those devices are always on,
+		 * so the s_power callback are silently ignored.
+		 * So, the current logic here does the following:
+		 * Disable (put tuner to sleep) when
+		 * - ALSA and DVB aren't not streaming;
+		 * - the last V4L2 file handler is closed.
+		 *
+		 * FIXME:
+		 *
+		 * Additionally, this logic could be improved to
+		 * disable the media source if the above conditions
+		 * are met and if the device:
+		 * - doesn't have a separate audio out plug (or
+		 * - doesn't use a silicon tuner like xc2028/3028/4000/5000).
+		 *
+		 * Once this additional logic is in place, a callback
+		 * is needed to enable the media source and power on
+		 * the tuner, for radio to work.
+		*/
+		ret = v4l_enable_media_source(vdev);
+		if (ret == 0)
+			v4l2_device_call_all(&dev->v4l2_dev, 0, tuner,
+					     standby);
+		dev->std_set_in_tuner_core = 0;
+
+		/* When close the device, set the usb intf0 into alt0 to free
+		   USB bandwidth */
+		ret = usb_set_interface(dev->usbdev, 0, 0);
+		if (ret < 0)
+			pr_info("Au0828 can't set alternate to 0!\n");
+	}
+end:
+	_vb2_fop_release(filp, NULL);
+	dev->users--;
+	mutex_unlock(&dev->lock);
+	return 0;
+}
+
+/* Must be called with dev->lock held */
+static void au0828_init_tuner(struct au0828_dev *dev)
+{
+	struct v4l2_frequency f = {
+		.frequency = dev->ctrl_freq,
+		.type = V4L2_TUNER_ANALOG_TV,
+	};
+
+	dprintk(1, "%s called std_set %d dev_state %ld\n", __func__,
+		dev->std_set_in_tuner_core, dev->dev_state);
+
+	if (dev->std_set_in_tuner_core)
+		return;
+	dev->std_set_in_tuner_core = 1;
+	i2c_gate_ctrl(dev, 1);
+	/* If we've never sent the standard in tuner core, do so now.
+	   We don't do this at device probe because we don't want to
+	   incur the cost of a firmware load */
+	v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_std, dev->std);
+	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_frequency, &f);
+	i2c_gate_ctrl(dev, 0);
+}
+
+static int au0828_set_format(struct au0828_dev *dev, unsigned int cmd,
+			     struct v4l2_format *format)
+{
+	int ret;
+	int width = format->fmt.pix.width;
+	int height = format->fmt.pix.height;
+
+	/* If they are demanding a format other than the one we support,
+	   bail out (tvtime asks for UYVY and then retries with YUYV) */
+	if (format->fmt.pix.pixelformat != V4L2_PIX_FMT_UYVY)
+		return -EINVAL;
+
+	/* format->fmt.pix.width only support 720 and height 480 */
+	if (width != 720)
+		width = 720;
+	if (height != 480)
+		height = 480;
+
+	format->fmt.pix.width = width;
+	format->fmt.pix.height = height;
+	format->fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
+	format->fmt.pix.bytesperline = width * 2;
+	format->fmt.pix.sizeimage = width * height * 2;
+	format->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+	format->fmt.pix.field = V4L2_FIELD_INTERLACED;
+	format->fmt.pix.priv = 0;
+
+	if (cmd == VIDIOC_TRY_FMT)
+		return 0;
+
+	/* maybe set new image format, driver current only support 720*480 */
+	dev->width = width;
+	dev->height = height;
+	dev->frame_size = width * height * 2;
+	dev->field_size = width * height;
+	dev->bytesperline = width * 2;
+
+	if (dev->stream_state == STREAM_ON) {
+		dprintk(1, "VIDIOC_SET_FMT: interrupting stream!\n");
+		ret = au0828_stream_interrupt(dev);
+		if (ret != 0) {
+			dprintk(1, "error interrupting video stream!\n");
+			return ret;
+		}
+	}
+
+	au0828_analog_stream_enable(dev);
+
+	return 0;
+}
+
+static int vidioc_querycap(struct file *file, void  *priv,
+			   struct v4l2_capability *cap)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct au0828_dev *dev = video_drvdata(file);
+
+	dprintk(1, "%s called std_set %d dev_state %ld\n", __func__,
+		dev->std_set_in_tuner_core, dev->dev_state);
+
+	strlcpy(cap->driver, "au0828", sizeof(cap->driver));
+	strlcpy(cap->card, dev->board.name, sizeof(cap->card));
+	usb_make_path(dev->usbdev, cap->bus_info, sizeof(cap->bus_info));
+
+	/* set the device capabilities */
+	cap->device_caps = V4L2_CAP_AUDIO |
+		V4L2_CAP_READWRITE |
+		V4L2_CAP_STREAMING |
+		V4L2_CAP_TUNER;
+	if (vdev->vfl_type == VFL_TYPE_GRABBER)
+		cap->device_caps |= V4L2_CAP_VIDEO_CAPTURE;
+	else
+		cap->device_caps |= V4L2_CAP_VBI_CAPTURE;
+	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS |
+		V4L2_CAP_VBI_CAPTURE | V4L2_CAP_VIDEO_CAPTURE;
+	return 0;
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
+					struct v4l2_fmtdesc *f)
+{
+	if (f->index)
+		return -EINVAL;
+
+	dprintk(1, "%s called\n", __func__);
+
+	f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	strcpy(f->description, "Packed YUV2");
+
+	f->flags = 0;
+	f->pixelformat = V4L2_PIX_FMT_UYVY;
+
+	return 0;
+}
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+					struct v4l2_format *f)
+{
+	struct au0828_dev *dev = video_drvdata(file);
+
+	dprintk(1, "%s called std_set %d dev_state %ld\n", __func__,
+		dev->std_set_in_tuner_core, dev->dev_state);
+
+	f->fmt.pix.width = dev->width;
+	f->fmt.pix.height = dev->height;
+	f->fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
+	f->fmt.pix.bytesperline = dev->bytesperline;
+	f->fmt.pix.sizeimage = dev->frame_size;
+	f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; /* NTSC/PAL */
+	f->fmt.pix.field = V4L2_FIELD_INTERLACED;
+	f->fmt.pix.priv = 0;
+	return 0;
+}
+
+static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
+				  struct v4l2_format *f)
+{
+	struct au0828_dev *dev = video_drvdata(file);
+
+	dprintk(1, "%s called std_set %d dev_state %ld\n", __func__,
+		dev->std_set_in_tuner_core, dev->dev_state);
+
+	return au0828_set_format(dev, VIDIOC_TRY_FMT, f);
+}
+
+static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+				struct v4l2_format *f)
+{
+	struct au0828_dev *dev = video_drvdata(file);
+	int rc;
+
+	dprintk(1, "%s called std_set %d dev_state %ld\n", __func__,
+		dev->std_set_in_tuner_core, dev->dev_state);
+
+	rc = check_dev(dev);
+	if (rc < 0)
+		return rc;
+
+	if (vb2_is_busy(&dev->vb_vidq)) {
+		pr_info("%s queue busy\n", __func__);
+		rc = -EBUSY;
+		goto out;
+	}
+
+	rc = au0828_set_format(dev, VIDIOC_S_FMT, f);
+out:
+	return rc;
+}
+
+static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id norm)
+{
+	struct au0828_dev *dev = video_drvdata(file);
+
+	dprintk(1, "%s called std_set %d dev_state %ld\n", __func__,
+		dev->std_set_in_tuner_core, dev->dev_state);
+
+	if (norm == dev->std)
+		return 0;
+
+	if (dev->streaming_users > 0)
+		return -EBUSY;
+
+	dev->std = norm;
+
+	au0828_init_tuner(dev);
+
+	i2c_gate_ctrl(dev, 1);
+
+	/*
+	 * FIXME: when we support something other than 60Hz standards,
+	 * we are going to have to make the au0828 bridge adjust the size
+	 * of its capture buffer, which is currently hardcoded at 720x480
+	 */
+
+	v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_std, norm);
+
+	i2c_gate_ctrl(dev, 0);
+
+	return 0;
+}
+
+static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *norm)
+{
+	struct au0828_dev *dev = video_drvdata(file);
+
+	dprintk(1, "%s called std_set %d dev_state %ld\n", __func__,
+		dev->std_set_in_tuner_core, dev->dev_state);
+
+	*norm = dev->std;
+	return 0;
+}
+
+static int vidioc_enum_input(struct file *file, void *priv,
+				struct v4l2_input *input)
+{
+	struct au0828_dev *dev = video_drvdata(file);
+	unsigned int tmp;
+
+	static const char *inames[] = {
+		[AU0828_VMUX_UNDEFINED] = "Undefined",
+		[AU0828_VMUX_COMPOSITE] = "Composite",
+		[AU0828_VMUX_SVIDEO] = "S-Video",
+		[AU0828_VMUX_CABLE] = "Cable TV",
+		[AU0828_VMUX_TELEVISION] = "Television",
+		[AU0828_VMUX_DVB] = "DVB",
+	};
+
+	dprintk(1, "%s called std_set %d dev_state %ld\n", __func__,
+		dev->std_set_in_tuner_core, dev->dev_state);
+
+	tmp = input->index;
+
+	if (tmp >= AU0828_MAX_INPUT)
+		return -EINVAL;
+	if (AUVI_INPUT(tmp).type == 0)
+		return -EINVAL;
+
+	input->index = tmp;
+	strcpy(input->name, inames[AUVI_INPUT(tmp).type]);
+	if ((AUVI_INPUT(tmp).type == AU0828_VMUX_TELEVISION) ||
+	    (AUVI_INPUT(tmp).type == AU0828_VMUX_CABLE)) {
+		input->type |= V4L2_INPUT_TYPE_TUNER;
+		input->audioset = 1;
+	} else {
+		input->type |= V4L2_INPUT_TYPE_CAMERA;
+		input->audioset = 2;
+	}
+
+	input->std = dev->vdev.tvnorms;
+
+	return 0;
+}
+
+static int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
+{
+	struct au0828_dev *dev = video_drvdata(file);
+
+	dprintk(1, "%s called std_set %d dev_state %ld\n", __func__,
+		dev->std_set_in_tuner_core, dev->dev_state);
+
+	*i = dev->ctrl_input;
+	return 0;
+}
+
+static void au0828_s_input(struct au0828_dev *dev, int index)
+{
+	int i;
+
+	dprintk(1, "%s called std_set %d dev_state %ld\n", __func__,
+		dev->std_set_in_tuner_core, dev->dev_state);
+
+	switch (AUVI_INPUT(index).type) {
+	case AU0828_VMUX_SVIDEO:
+		dev->input_type = AU0828_VMUX_SVIDEO;
+		dev->ctrl_ainput = 1;
+		break;
+	case AU0828_VMUX_COMPOSITE:
+		dev->input_type = AU0828_VMUX_COMPOSITE;
+		dev->ctrl_ainput = 1;
+		break;
+	case AU0828_VMUX_TELEVISION:
+		dev->input_type = AU0828_VMUX_TELEVISION;
+		dev->ctrl_ainput = 0;
+		break;
+	default:
+		dprintk(1, "unknown input type set [%d]\n",
+			AUVI_INPUT(index).type);
+		return;
+	}
+
+	dev->ctrl_input = index;
+
+	v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_routing,
+			AUVI_INPUT(index).vmux, 0, 0);
+
+	for (i = 0; i < AU0828_MAX_INPUT; i++) {
+		int enable = 0;
+		if (AUVI_INPUT(i).audio_setup == NULL)
+			continue;
+
+		if (i == index)
+			enable = 1;
+		else
+			enable = 0;
+		if (enable) {
+			(AUVI_INPUT(i).audio_setup)(dev, enable);
+		} else {
+			/* Make sure we leave it turned on if some
+			   other input is routed to this callback */
+			if ((AUVI_INPUT(i).audio_setup) !=
+			    ((AUVI_INPUT(index).audio_setup))) {
+				(AUVI_INPUT(i).audio_setup)(dev, enable);
+			}
+		}
+	}
+
+	v4l2_device_call_all(&dev->v4l2_dev, 0, audio, s_routing,
+			AUVI_INPUT(index).amux, 0, 0);
+}
+
+static int vidioc_s_input(struct file *file, void *priv, unsigned int index)
+{
+	struct au0828_dev *dev = video_drvdata(file);
+	struct video_device *vfd = video_devdata(file);
+
+	dprintk(1, "VIDIOC_S_INPUT in function %s, input=%d\n", __func__,
+		index);
+	if (index >= AU0828_MAX_INPUT)
+		return -EINVAL;
+	if (AUVI_INPUT(index).type == 0)
+		return -EINVAL;
+
+	if (dev->ctrl_input == index)
+		return 0;
+
+	au0828_s_input(dev, index);
+
+	/*
+	 * Input has been changed. Disable the media source
+	 * associated with the old input and enable source
+	 * for the newly set input
+	 */
+	v4l_disable_media_source(vfd);
+	return v4l_enable_media_source(vfd);
+}
+
+static int vidioc_enumaudio(struct file *file, void *priv, struct v4l2_audio *a)
+{
+	if (a->index > 1)
+		return -EINVAL;
+
+	dprintk(1, "%s called\n", __func__);
+
+	if (a->index == 0)
+		strcpy(a->name, "Television");
+	else
+		strcpy(a->name, "Line in");
+
+	a->capability = V4L2_AUDCAP_STEREO;
+	return 0;
+}
+
+static int vidioc_g_audio(struct file *file, void *priv, struct v4l2_audio *a)
+{
+	struct au0828_dev *dev = video_drvdata(file);
+
+	dprintk(1, "%s called std_set %d dev_state %ld\n", __func__,
+		dev->std_set_in_tuner_core, dev->dev_state);
+
+	a->index = dev->ctrl_ainput;
+	if (a->index == 0)
+		strcpy(a->name, "Television");
+	else
+		strcpy(a->name, "Line in");
+
+	a->capability = V4L2_AUDCAP_STEREO;
+	return 0;
+}
+
+static int vidioc_s_audio(struct file *file, void *priv, const struct v4l2_audio *a)
+{
+	struct au0828_dev *dev = video_drvdata(file);
+
+	if (a->index != dev->ctrl_ainput)
+		return -EINVAL;
+
+	dprintk(1, "%s called std_set %d dev_state %ld\n", __func__,
+		dev->std_set_in_tuner_core, dev->dev_state);
+	return 0;
+}
+
+static int vidioc_g_tuner(struct file *file, void *priv, struct v4l2_tuner *t)
+{
+	struct au0828_dev *dev = video_drvdata(file);
+	struct video_device *vfd = video_devdata(file);
+	int ret;
+
+	if (t->index != 0)
+		return -EINVAL;
+
+	ret = v4l_enable_media_source(vfd);
+	if (ret)
+		return ret;
+
+	dprintk(1, "%s called std_set %d dev_state %ld\n", __func__,
+		dev->std_set_in_tuner_core, dev->dev_state);
+
+	strcpy(t->name, "Auvitek tuner");
+
+	au0828_init_tuner(dev);
+	i2c_gate_ctrl(dev, 1);
+	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, g_tuner, t);
+	i2c_gate_ctrl(dev, 0);
+	return 0;
+}
+
+static int vidioc_s_tuner(struct file *file, void *priv,
+				const struct v4l2_tuner *t)
+{
+	struct au0828_dev *dev = video_drvdata(file);
+
+	if (t->index != 0)
+		return -EINVAL;
+
+	dprintk(1, "%s called std_set %d dev_state %ld\n", __func__,
+		dev->std_set_in_tuner_core, dev->dev_state);
+
+	au0828_init_tuner(dev);
+	i2c_gate_ctrl(dev, 1);
+	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_tuner, t);
+	i2c_gate_ctrl(dev, 0);
+
+	dprintk(1, "VIDIOC_S_TUNER: signal = %x, afc = %x\n", t->signal,
+		t->afc);
+
+	return 0;
+
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+				struct v4l2_frequency *freq)
+{
+	struct au0828_dev *dev = video_drvdata(file);
+
+	if (freq->tuner != 0)
+		return -EINVAL;
+	dprintk(1, "%s called std_set %d dev_state %ld\n", __func__,
+		dev->std_set_in_tuner_core, dev->dev_state);
+	freq->frequency = dev->ctrl_freq;
+	return 0;
+}
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+				const struct v4l2_frequency *freq)
+{
+	struct au0828_dev *dev = video_drvdata(file);
+	struct v4l2_frequency new_freq = *freq;
+
+	if (freq->tuner != 0)
+		return -EINVAL;
+
+	dprintk(1, "%s called std_set %d dev_state %ld\n", __func__,
+		dev->std_set_in_tuner_core, dev->dev_state);
+
+	au0828_init_tuner(dev);
+	i2c_gate_ctrl(dev, 1);
+
+	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_frequency, freq);
+	/* Get the actual set (and possibly clamped) frequency */
+	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, g_frequency, &new_freq);
+	dev->ctrl_freq = new_freq.frequency;
+
+	i2c_gate_ctrl(dev, 0);
+
+	au0828_analog_stream_reset(dev);
+
+	return 0;
+}
+
+
+/* RAW VBI ioctls */
+
+static int vidioc_g_fmt_vbi_cap(struct file *file, void *priv,
+				struct v4l2_format *format)
+{
+	struct au0828_dev *dev = video_drvdata(file);
+
+	dprintk(1, "%s called std_set %d dev_state %ld\n", __func__,
+		dev->std_set_in_tuner_core, dev->dev_state);
+
+	format->fmt.vbi.samples_per_line = dev->vbi_width;
+	format->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY;
+	format->fmt.vbi.offset = 0;
+	format->fmt.vbi.flags = 0;
+	format->fmt.vbi.sampling_rate = 6750000 * 4 / 2;
+
+	format->fmt.vbi.count[0] = dev->vbi_height;
+	format->fmt.vbi.count[1] = dev->vbi_height;
+	format->fmt.vbi.start[0] = 21;
+	format->fmt.vbi.start[1] = 284;
+	memset(format->fmt.vbi.reserved, 0, sizeof(format->fmt.vbi.reserved));
+
+	return 0;
+}
+
+static int vidioc_cropcap(struct file *file, void *priv,
+			  struct v4l2_cropcap *cc)
+{
+	struct au0828_dev *dev = video_drvdata(file);
+
+	if (cc->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	dprintk(1, "%s called std_set %d dev_state %ld\n", __func__,
+		dev->std_set_in_tuner_core, dev->dev_state);
+
+	cc->bounds.left = 0;
+	cc->bounds.top = 0;
+	cc->bounds.width = dev->width;
+	cc->bounds.height = dev->height;
+
+	cc->defrect = cc->bounds;
+
+	cc->pixelaspect.numerator = 54;
+	cc->pixelaspect.denominator = 59;
+
+	return 0;
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int vidioc_g_register(struct file *file, void *priv,
+			     struct v4l2_dbg_register *reg)
+{
+	struct au0828_dev *dev = video_drvdata(file);
+
+	dprintk(1, "%s called std_set %d dev_state %ld\n", __func__,
+		dev->std_set_in_tuner_core, dev->dev_state);
+
+	reg->val = au0828_read(dev, reg->reg);
+	reg->size = 1;
+	return 0;
+}
+
+static int vidioc_s_register(struct file *file, void *priv,
+			     const struct v4l2_dbg_register *reg)
+{
+	struct au0828_dev *dev = video_drvdata(file);
+
+	dprintk(1, "%s called std_set %d dev_state %ld\n", __func__,
+		dev->std_set_in_tuner_core, dev->dev_state);
+
+	return au0828_writereg(dev, reg->reg, reg->val);
+}
+#endif
+
+static int vidioc_log_status(struct file *file, void *fh)
+{
+	struct video_device *vdev = video_devdata(file);
+
+	dprintk(1, "%s called\n", __func__);
+
+	v4l2_ctrl_log_status(file, fh);
+	v4l2_device_call_all(vdev->v4l2_dev, 0, core, log_status);
+	return 0;
+}
+
+void au0828_v4l2_suspend(struct au0828_dev *dev)
+{
+	struct urb *urb;
+	int i;
+
+	pr_info("stopping V4L2\n");
+
+	if (dev->stream_state == STREAM_ON) {
+		pr_info("stopping V4L2 active URBs\n");
+		au0828_analog_stream_disable(dev);
+		/* stop urbs */
+		for (i = 0; i < dev->isoc_ctl.num_bufs; i++) {
+			urb = dev->isoc_ctl.urb[i];
+			if (urb) {
+				if (!irqs_disabled())
+					usb_kill_urb(urb);
+				else
+					usb_unlink_urb(urb);
+			}
+		}
+	}
+
+	if (dev->vid_timeout_running)
+		del_timer_sync(&dev->vid_timeout);
+	if (dev->vbi_timeout_running)
+		del_timer_sync(&dev->vbi_timeout);
+}
+
+void au0828_v4l2_resume(struct au0828_dev *dev)
+{
+	int i, rc;
+
+	pr_info("restarting V4L2\n");
+
+	if (dev->stream_state == STREAM_ON) {
+		au0828_stream_interrupt(dev);
+		au0828_init_tuner(dev);
+	}
+
+	if (dev->vid_timeout_running)
+		mod_timer(&dev->vid_timeout, jiffies + (HZ / 10));
+	if (dev->vbi_timeout_running)
+		mod_timer(&dev->vbi_timeout, jiffies + (HZ / 10));
+
+	/* If we were doing ac97 instead of i2s, it would go here...*/
+	au0828_i2s_init(dev);
+
+	au0828_analog_stream_enable(dev);
+
+	if (!(dev->stream_state == STREAM_ON)) {
+		au0828_analog_stream_reset(dev);
+		/* submit urbs */
+		for (i = 0; i < dev->isoc_ctl.num_bufs; i++) {
+			rc = usb_submit_urb(dev->isoc_ctl.urb[i], GFP_ATOMIC);
+			if (rc) {
+				au0828_isocdbg("submit of urb %i failed (error=%i)\n",
+					       i, rc);
+				au0828_uninit_isoc(dev);
+			}
+		}
+	}
+}
+
+static const struct v4l2_file_operations au0828_v4l_fops = {
+	.owner      = THIS_MODULE,
+	.open       = au0828_v4l2_open,
+	.release    = au0828_v4l2_close,
+	.read       = vb2_fop_read,
+	.poll       = vb2_fop_poll,
+	.mmap       = vb2_fop_mmap,
+	.unlocked_ioctl = video_ioctl2,
+};
+
+static const struct v4l2_ioctl_ops video_ioctl_ops = {
+	.vidioc_querycap            = vidioc_querycap,
+	.vidioc_enum_fmt_vid_cap    = vidioc_enum_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap       = vidioc_g_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap     = vidioc_try_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap       = vidioc_s_fmt_vid_cap,
+	.vidioc_g_fmt_vbi_cap       = vidioc_g_fmt_vbi_cap,
+	.vidioc_try_fmt_vbi_cap     = vidioc_g_fmt_vbi_cap,
+	.vidioc_s_fmt_vbi_cap       = vidioc_g_fmt_vbi_cap,
+	.vidioc_enumaudio           = vidioc_enumaudio,
+	.vidioc_g_audio             = vidioc_g_audio,
+	.vidioc_s_audio             = vidioc_s_audio,
+	.vidioc_cropcap             = vidioc_cropcap,
+
+	.vidioc_reqbufs             = vb2_ioctl_reqbufs,
+	.vidioc_create_bufs         = vb2_ioctl_create_bufs,
+	.vidioc_prepare_buf         = vb2_ioctl_prepare_buf,
+	.vidioc_querybuf            = vb2_ioctl_querybuf,
+	.vidioc_qbuf                = vb2_ioctl_qbuf,
+	.vidioc_dqbuf               = vb2_ioctl_dqbuf,
+	.vidioc_expbuf               = vb2_ioctl_expbuf,
+
+	.vidioc_s_std               = vidioc_s_std,
+	.vidioc_g_std               = vidioc_g_std,
+	.vidioc_enum_input          = vidioc_enum_input,
+	.vidioc_g_input             = vidioc_g_input,
+	.vidioc_s_input             = vidioc_s_input,
+
+	.vidioc_streamon            = vb2_ioctl_streamon,
+	.vidioc_streamoff           = vb2_ioctl_streamoff,
+
+	.vidioc_g_tuner             = vidioc_g_tuner,
+	.vidioc_s_tuner             = vidioc_s_tuner,
+	.vidioc_g_frequency         = vidioc_g_frequency,
+	.vidioc_s_frequency         = vidioc_s_frequency,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.vidioc_g_register          = vidioc_g_register,
+	.vidioc_s_register          = vidioc_s_register,
+#endif
+	.vidioc_log_status	    = vidioc_log_status,
+	.vidioc_subscribe_event     = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event   = v4l2_event_unsubscribe,
+};
+
+static const struct video_device au0828_video_template = {
+	.fops                       = &au0828_v4l_fops,
+	.release                    = video_device_release_empty,
+	.ioctl_ops		    = &video_ioctl_ops,
+	.tvnorms                    = V4L2_STD_NTSC_M | V4L2_STD_PAL_M,
+};
+
+static int au0828_vb2_setup(struct au0828_dev *dev)
+{
+	int rc;
+	struct vb2_queue *q;
+
+	/* Setup Videobuf2 for Video capture */
+	q = &dev->vb_vidq;
+	q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	q->io_modes = VB2_READ | VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
+	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	q->drv_priv = dev;
+	q->buf_struct_size = sizeof(struct au0828_buffer);
+	q->ops = &au0828_video_qops;
+	q->mem_ops = &vb2_vmalloc_memops;
+
+	rc = vb2_queue_init(q);
+	if (rc < 0)
+		return rc;
+
+	/* Setup Videobuf2 for VBI capture */
+	q = &dev->vb_vbiq;
+	q->type = V4L2_BUF_TYPE_VBI_CAPTURE;
+	q->io_modes = VB2_READ | VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
+	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	q->drv_priv = dev;
+	q->buf_struct_size = sizeof(struct au0828_buffer);
+	q->ops = &au0828_vbi_qops;
+	q->mem_ops = &vb2_vmalloc_memops;
+
+	rc = vb2_queue_init(q);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+static void au0828_analog_create_entities(struct au0828_dev *dev)
+{
+#if defined(CONFIG_MEDIA_CONTROLLER)
+	static const char * const inames[] = {
+		[AU0828_VMUX_COMPOSITE] = "Composite",
+		[AU0828_VMUX_SVIDEO] = "S-Video",
+		[AU0828_VMUX_CABLE] = "Cable TV",
+		[AU0828_VMUX_TELEVISION] = "Television",
+		[AU0828_VMUX_DVB] = "DVB",
+	};
+	int ret, i;
+
+	/* Initialize Video and VBI pads */
+	dev->video_pad.flags = MEDIA_PAD_FL_SINK;
+	ret = media_entity_pads_init(&dev->vdev.entity, 1, &dev->video_pad);
+	if (ret < 0)
+		pr_err("failed to initialize video media entity!\n");
+
+	dev->vbi_pad.flags = MEDIA_PAD_FL_SINK;
+	ret = media_entity_pads_init(&dev->vbi_dev.entity, 1, &dev->vbi_pad);
+	if (ret < 0)
+		pr_err("failed to initialize vbi media entity!\n");
+
+	/* Create entities for each input connector */
+	for (i = 0; i < AU0828_MAX_INPUT; i++) {
+		struct media_entity *ent = &dev->input_ent[i];
+
+		if (AUVI_INPUT(i).type == AU0828_VMUX_UNDEFINED)
+			break;
+
+		ent->name = inames[AUVI_INPUT(i).type];
+		ent->flags = MEDIA_ENT_FL_CONNECTOR;
+		dev->input_pad[i].flags = MEDIA_PAD_FL_SOURCE;
+
+		switch (AUVI_INPUT(i).type) {
+		case AU0828_VMUX_COMPOSITE:
+			ent->function = MEDIA_ENT_F_CONN_COMPOSITE;
+			break;
+		case AU0828_VMUX_SVIDEO:
+			ent->function = MEDIA_ENT_F_CONN_SVIDEO;
+			break;
+		case AU0828_VMUX_CABLE:
+		case AU0828_VMUX_TELEVISION:
+		case AU0828_VMUX_DVB:
+		default: /* Just to shut up a warning */
+			ent->function = MEDIA_ENT_F_CONN_RF;
+			break;
+		}
+
+		ret = media_entity_pads_init(ent, 1, &dev->input_pad[i]);
+		if (ret < 0)
+			pr_err("failed to initialize input pad[%d]!\n", i);
+
+		ret = media_device_register_entity(dev->media_dev, ent);
+		if (ret < 0)
+			pr_err("failed to register input entity %d!\n", i);
+	}
+#endif
+}
+
+/**************************************************************************/
+
+int au0828_analog_register(struct au0828_dev *dev,
+			   struct usb_interface *interface)
+{
+	int retval = -ENOMEM;
+	struct usb_host_interface *iface_desc;
+	struct usb_endpoint_descriptor *endpoint;
+	int i, ret;
+
+	dprintk(1, "au0828_analog_register called for intf#%d!\n",
+		interface->cur_altsetting->desc.bInterfaceNumber);
+
+	/* No analog TV */
+	if (AUVI_INPUT(0).type == AU0828_VMUX_UNDEFINED)
+		return 0;
+
+	/* set au0828 usb interface0 to as5 */
+	retval = usb_set_interface(dev->usbdev,
+			interface->cur_altsetting->desc.bInterfaceNumber, 5);
+	if (retval != 0) {
+		pr_info("Failure setting usb interface0 to as5\n");
+		return retval;
+	}
+
+	/* Figure out which endpoint has the isoc interface */
+	iface_desc = interface->cur_altsetting;
+	for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) {
+		endpoint = &iface_desc->endpoint[i].desc;
+		if (((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK)
+		     == USB_DIR_IN) &&
+		    ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
+		     == USB_ENDPOINT_XFER_ISOC)) {
+
+			/* we find our isoc in endpoint */
+			u16 tmp = le16_to_cpu(endpoint->wMaxPacketSize);
+			dev->max_pkt_size = (tmp & 0x07ff) *
+				(((tmp & 0x1800) >> 11) + 1);
+			dev->isoc_in_endpointaddr = endpoint->bEndpointAddress;
+			dprintk(1,
+				"Found isoc endpoint 0x%02x, max size = %d\n",
+				dev->isoc_in_endpointaddr, dev->max_pkt_size);
+		}
+	}
+	if (!(dev->isoc_in_endpointaddr)) {
+		pr_info("Could not locate isoc endpoint\n");
+		return -ENODEV;
+	}
+
+	init_waitqueue_head(&dev->open);
+	spin_lock_init(&dev->slock);
+
+	/* init video dma queues */
+	INIT_LIST_HEAD(&dev->vidq.active);
+	INIT_LIST_HEAD(&dev->vbiq.active);
+
+	timer_setup(&dev->vid_timeout, au0828_vid_buffer_timeout, 0);
+	timer_setup(&dev->vbi_timeout, au0828_vbi_buffer_timeout, 0);
+
+	dev->width = NTSC_STD_W;
+	dev->height = NTSC_STD_H;
+	dev->field_size = dev->width * dev->height;
+	dev->frame_size = dev->field_size << 1;
+	dev->bytesperline = dev->width << 1;
+	dev->vbi_width = 720;
+	dev->vbi_height = 1;
+	dev->ctrl_ainput = 0;
+	dev->ctrl_freq = 960;
+	dev->std = V4L2_STD_NTSC_M;
+	/* Default input is TV Tuner */
+	au0828_s_input(dev, 0);
+
+	mutex_init(&dev->vb_queue_lock);
+	mutex_init(&dev->vb_vbi_queue_lock);
+
+	/* Fill the video capture device struct */
+	dev->vdev = au0828_video_template;
+	dev->vdev.v4l2_dev = &dev->v4l2_dev;
+	dev->vdev.lock = &dev->lock;
+	dev->vdev.queue = &dev->vb_vidq;
+	dev->vdev.queue->lock = &dev->vb_queue_lock;
+	strcpy(dev->vdev.name, "au0828a video");
+
+	/* Setup the VBI device */
+	dev->vbi_dev = au0828_video_template;
+	dev->vbi_dev.v4l2_dev = &dev->v4l2_dev;
+	dev->vbi_dev.lock = &dev->lock;
+	dev->vbi_dev.queue = &dev->vb_vbiq;
+	dev->vbi_dev.queue->lock = &dev->vb_vbi_queue_lock;
+	strcpy(dev->vbi_dev.name, "au0828a vbi");
+
+	/* Init entities at the Media Controller */
+	au0828_analog_create_entities(dev);
+
+	/* initialize videobuf2 stuff */
+	retval = au0828_vb2_setup(dev);
+	if (retval != 0) {
+		dprintk(1, "unable to setup videobuf2 queues (error = %d).\n",
+			retval);
+		return -ENODEV;
+	}
+
+	/* Register the v4l2 device */
+	video_set_drvdata(&dev->vdev, dev);
+	retval = video_register_device(&dev->vdev, VFL_TYPE_GRABBER, -1);
+	if (retval != 0) {
+		dprintk(1, "unable to register video device (error = %d).\n",
+			retval);
+		ret = -ENODEV;
+		goto err_reg_vdev;
+	}
+
+	/* Register the vbi device */
+	video_set_drvdata(&dev->vbi_dev, dev);
+	retval = video_register_device(&dev->vbi_dev, VFL_TYPE_VBI, -1);
+	if (retval != 0) {
+		dprintk(1, "unable to register vbi device (error = %d).\n",
+			retval);
+		ret = -ENODEV;
+		goto err_reg_vbi_dev;
+	}
+
+#ifdef CONFIG_MEDIA_CONTROLLER
+	retval = v4l2_mc_create_media_graph(dev->media_dev);
+	if (retval) {
+		pr_err("%s() au0282_dev_register failed to create graph\n",
+			__func__);
+		ret = -ENODEV;
+		goto err_reg_vbi_dev;
+	}
+#endif
+
+	dprintk(1, "%s completed!\n", __func__);
+
+	return 0;
+
+err_reg_vbi_dev:
+	video_unregister_device(&dev->vdev);
+err_reg_vdev:
+	vb2_queue_release(&dev->vb_vidq);
+	vb2_queue_release(&dev->vb_vbiq);
+	return ret;
+}
+
diff --git a/drivers/media/usb/au0828/au0828.h b/drivers/media/usb/au0828/au0828.h
new file mode 100644
index 0000000..004eade
--- /dev/null
+++ b/drivers/media/usb/au0828/au0828.h
@@ -0,0 +1,379 @@
+/*
+ *  Driver for the Auvitek AU0828 USB bridge
+ *
+ *  Copyright (c) 2008 Steven Toth <stoth@linuxtv.org>
+ *
+ *  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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/bitops.h>
+#include <linux/usb.h>
+#include <linux/i2c.h>
+#include <linux/i2c-algo-bit.h>
+#include <media/tveeprom.h>
+
+/* Analog */
+#include <linux/videodev2.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-vmalloc.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-fh.h>
+#include <media/media-device.h>
+
+/* DVB */
+#include <media/demux.h>
+#include <media/dmxdev.h>
+#include <media/dvb_demux.h>
+#include <media/dvb_frontend.h>
+#include <media/dvb_net.h>
+#include <media/dvbdev.h>
+
+#include "au0828-reg.h"
+#include "au0828-cards.h"
+
+#define URB_COUNT   16
+#define URB_BUFSIZE (0xe522)
+
+/* Analog constants */
+#define NTSC_STD_W      720
+#define NTSC_STD_H      480
+
+#define AU0828_INTERLACED_DEFAULT       1
+
+/* Defination for AU0828 USB transfer */
+#define AU0828_MAX_ISO_BUFS    12  /* maybe resize this value in the future */
+#define AU0828_ISO_PACKETS_PER_URB      128
+
+#define AU0828_MIN_BUF 4
+#define AU0828_DEF_BUF 8
+
+#define AU0828_MAX_INPUT        4
+
+/* au0828 resource types (used for res_get/res_lock etc */
+#define AU0828_RESOURCE_VIDEO 0x01
+#define AU0828_RESOURCE_VBI   0x02
+
+enum au0828_itype {
+	AU0828_VMUX_UNDEFINED = 0,
+	AU0828_VMUX_COMPOSITE,
+	AU0828_VMUX_SVIDEO,
+	AU0828_VMUX_CABLE,
+	AU0828_VMUX_TELEVISION,
+	AU0828_VMUX_DVB,
+};
+
+struct au0828_input {
+	enum au0828_itype type;
+	unsigned int vmux;
+	unsigned int amux;
+	void (*audio_setup) (void *priv, int enable);
+};
+
+struct au0828_board {
+	char *name;
+	unsigned int tuner_type;
+	unsigned char tuner_addr;
+	unsigned char i2c_clk_divider;
+	unsigned char has_ir_i2c:1;
+	unsigned char has_analog:1;
+	struct au0828_input input[AU0828_MAX_INPUT];
+};
+
+struct au0828_dvb {
+	struct mutex lock;
+	struct dvb_adapter adapter;
+	struct dvb_frontend *frontend;
+	struct dvb_demux demux;
+	struct dmxdev dmxdev;
+	struct dmx_frontend fe_hw;
+	struct dmx_frontend fe_mem;
+	struct dvb_net net;
+	int feeding;
+	int start_count;
+	int stop_count;
+
+	int (*set_frontend)(struct dvb_frontend *fe);
+};
+
+enum au0828_stream_state {
+	STREAM_OFF,
+	STREAM_INTERRUPT,
+	STREAM_ON
+};
+
+#define AUVI_INPUT(nr) (dev->board.input[nr])
+
+/* device state */
+enum au0828_dev_state {
+	DEV_INITIALIZED = 0,
+	DEV_DISCONNECTED = 1,
+	DEV_MISCONFIGURED = 2
+};
+
+struct au0828_dev;
+
+struct au0828_usb_isoc_ctl {
+		/* max packet size of isoc transaction */
+	int				max_pkt_size;
+
+		/* number of allocated urbs */
+	int				num_bufs;
+
+		/* urb for isoc transfers */
+	struct urb			**urb;
+
+		/* transfer buffers for isoc transfer */
+	char				**transfer_buffer;
+
+		/* Last buffer command and region */
+	u8				cmd;
+	int				pos, size, pktsize;
+
+		/* Last field: ODD or EVEN? */
+	int				field;
+
+		/* Stores incomplete commands */
+	u32				tmp_buf;
+	int				tmp_buf_len;
+
+		/* Stores already requested buffers */
+	struct au0828_buffer		*buf;
+	struct au0828_buffer		*vbi_buf;
+
+		/* Stores the number of received fields */
+	int				nfields;
+
+		/* isoc urb callback */
+	int (*isoc_copy) (struct au0828_dev *dev, struct urb *urb);
+
+};
+
+/* buffer for one video frame */
+struct au0828_buffer {
+	/* common v4l buffer stuff -- must be first */
+	struct vb2_v4l2_buffer vb;
+	struct list_head list;
+
+	void *mem;
+	unsigned long length;
+	int top_field;
+	/* pointer to vmalloc memory address in vb */
+	char *vb_buf;
+};
+
+struct au0828_dmaqueue {
+	struct list_head       active;
+	/* Counters to control buffer fill */
+	int                    pos;
+};
+
+struct au0828_dev {
+	struct mutex mutex;
+	struct usb_device	*usbdev;
+	int			boardnr;
+	struct au0828_board	board;
+	u8			ctrlmsg[64];
+
+	/* I2C */
+	struct i2c_adapter		i2c_adap;
+	struct i2c_algorithm		i2c_algo;
+	struct i2c_client		i2c_client;
+	u32				i2c_rc;
+
+	/* Digital */
+	struct au0828_dvb		dvb;
+	struct work_struct              restart_streaming;
+	struct timer_list               bulk_timeout;
+	int                             bulk_timeout_running;
+
+#ifdef CONFIG_VIDEO_AU0828_V4L2
+	/* Analog */
+	struct v4l2_device v4l2_dev;
+	struct v4l2_ctrl_handler v4l2_ctrl_hdl;
+#endif
+#ifdef CONFIG_VIDEO_AU0828_RC
+	struct au0828_rc *ir;
+#endif
+
+	struct video_device vdev;
+	struct video_device vbi_dev;
+
+	/* Videobuf2 */
+	struct vb2_queue vb_vidq;
+	struct vb2_queue vb_vbiq;
+	struct mutex vb_queue_lock;
+	struct mutex vb_vbi_queue_lock;
+
+	unsigned int frame_count;
+	unsigned int vbi_frame_count;
+
+	struct timer_list vid_timeout;
+	int vid_timeout_running;
+	struct timer_list vbi_timeout;
+	int vbi_timeout_running;
+
+	int users;
+	int streaming_users;
+
+	int width;
+	int height;
+	int vbi_width;
+	int vbi_height;
+	u32 vbi_read;
+	v4l2_std_id std;
+	u32 field_size;
+	u32 frame_size;
+	u32 bytesperline;
+	int type;
+	u8 ctrl_ainput;
+	__u8 isoc_in_endpointaddr;
+	u8 isoc_init_ok;
+	int greenscreen_detected;
+	int ctrl_freq;
+	int input_type;
+	int std_set_in_tuner_core;
+	unsigned int ctrl_input;
+	long unsigned int dev_state; /* defined at enum au0828_dev_state */;
+	enum au0828_stream_state stream_state;
+	wait_queue_head_t open;
+
+	struct mutex lock;
+
+	/* Isoc control struct */
+	struct au0828_dmaqueue vidq;
+	struct au0828_dmaqueue vbiq;
+	struct au0828_usb_isoc_ctl isoc_ctl;
+	spinlock_t slock;
+
+	/* usb transfer */
+	int alt;		/* alternate */
+	int max_pkt_size;	/* max packet size of isoc transaction */
+	int num_alt;		/* Number of alternative settings */
+	unsigned int *alt_max_pkt_size;	/* array of wMaxPacketSize */
+	struct urb *urb[AU0828_MAX_ISO_BUFS];	/* urb for isoc transfers */
+	char *transfer_buffer[AU0828_MAX_ISO_BUFS];/* transfer buffers for isoc
+						   transfer */
+
+	/* DVB USB / URB Related */
+	bool		urb_streaming, need_urb_start;
+	struct urb	*urbs[URB_COUNT];
+
+	/* Preallocated transfer digital transfer buffers */
+
+	char *dig_transfer_buffer[URB_COUNT];
+
+#ifdef CONFIG_MEDIA_CONTROLLER
+	struct media_device *media_dev;
+	struct media_pad video_pad, vbi_pad;
+	struct media_entity *decoder;
+	struct media_entity input_ent[AU0828_MAX_INPUT];
+	struct media_pad input_pad[AU0828_MAX_INPUT];
+	struct media_entity_notify entity_notify;
+	struct media_entity *tuner;
+	struct media_link *active_link;
+	struct media_entity *active_link_owner;
+	struct media_entity *active_source;
+	struct media_entity *active_sink;
+#endif
+};
+
+
+/* ----------------------------------------------------------- */
+#define au0828_read(dev, reg) au0828_readreg(dev, reg)
+#define au0828_write(dev, reg, value) au0828_writereg(dev, reg, value)
+#define au0828_andor(dev, reg, mask, value)				\
+	 au0828_writereg(dev, reg,					\
+	(au0828_readreg(dev, reg) & ~(mask)) | ((value) & (mask)))
+
+#define au0828_set(dev, reg, bit) au0828_andor(dev, (reg), (bit), (bit))
+#define au0828_clear(dev, reg, bit) au0828_andor(dev, (reg), (bit), 0)
+
+/* ----------------------------------------------------------- */
+/* au0828-core.c */
+extern u32 au0828_read(struct au0828_dev *dev, u16 reg);
+extern u32 au0828_write(struct au0828_dev *dev, u16 reg, u32 val);
+extern void au0828_usb_release(struct au0828_dev *dev);
+extern int au0828_debug;
+
+/* ----------------------------------------------------------- */
+/* au0828-cards.c */
+extern struct au0828_board au0828_boards[];
+extern struct usb_device_id au0828_usb_id_table[];
+extern void au0828_gpio_setup(struct au0828_dev *dev);
+extern int au0828_tuner_callback(void *priv, int component,
+				 int command, int arg);
+extern void au0828_card_setup(struct au0828_dev *dev);
+
+/* ----------------------------------------------------------- */
+/* au0828-i2c.c */
+extern int au0828_i2c_register(struct au0828_dev *dev);
+extern int au0828_i2c_unregister(struct au0828_dev *dev);
+
+/* ----------------------------------------------------------- */
+/* au0828-video.c */
+extern int au0828_start_analog_streaming(struct vb2_queue *vq,
+						unsigned int count);
+extern void au0828_stop_vbi_streaming(struct vb2_queue *vq);
+#ifdef CONFIG_VIDEO_AU0828_V4L2
+extern int au0828_v4l2_device_register(struct usb_interface *interface,
+				      struct au0828_dev *dev);
+
+extern int au0828_analog_register(struct au0828_dev *dev,
+			   struct usb_interface *interface);
+extern int au0828_analog_unregister(struct au0828_dev *dev);
+extern void au0828_usb_v4l2_media_release(struct au0828_dev *dev);
+extern void au0828_v4l2_suspend(struct au0828_dev *dev);
+extern void au0828_v4l2_resume(struct au0828_dev *dev);
+#else
+static inline int au0828_v4l2_device_register(struct usb_interface *interface,
+					      struct au0828_dev *dev)
+{ return 0; };
+static inline int au0828_analog_register(struct au0828_dev *dev,
+				     struct usb_interface *interface)
+{ return 0; };
+static inline int au0828_analog_unregister(struct au0828_dev *dev)
+{ return 0; };
+static inline void au0828_usb_v4l2_media_release(struct au0828_dev *dev) { };
+static inline void au0828_v4l2_suspend(struct au0828_dev *dev) { };
+static inline void au0828_v4l2_resume(struct au0828_dev *dev) { };
+#endif
+
+/* ----------------------------------------------------------- */
+/* au0828-dvb.c */
+extern int au0828_dvb_register(struct au0828_dev *dev);
+extern void au0828_dvb_unregister(struct au0828_dev *dev);
+void au0828_dvb_suspend(struct au0828_dev *dev);
+void au0828_dvb_resume(struct au0828_dev *dev);
+
+/* au0828-vbi.c */
+extern const struct vb2_ops au0828_vbi_qops;
+
+#define dprintk(level, fmt, arg...)\
+	do { if (au0828_debug & level)\
+		printk(KERN_DEBUG pr_fmt(fmt), ## arg);\
+	} while (0)
+
+/* au0828-input.c */
+#ifdef CONFIG_VIDEO_AU0828_RC
+extern int au0828_rc_register(struct au0828_dev *dev);
+extern void au0828_rc_unregister(struct au0828_dev *dev);
+extern int au0828_rc_suspend(struct au0828_dev *dev);
+extern int au0828_rc_resume(struct au0828_dev *dev);
+#else
+static inline int au0828_rc_register(struct au0828_dev *dev) { return 0; }
+static inline void au0828_rc_unregister(struct au0828_dev *dev) { }
+static inline int au0828_rc_suspend(struct au0828_dev *dev) { return 0; }
+static inline int au0828_rc_resume(struct au0828_dev *dev) { return 0; }
+#endif
diff --git a/drivers/media/usb/b2c2/Kconfig b/drivers/media/usb/b2c2/Kconfig
new file mode 100644
index 0000000..a620ae4
--- /dev/null
+++ b/drivers/media/usb/b2c2/Kconfig
@@ -0,0 +1,15 @@
+config DVB_B2C2_FLEXCOP_USB
+	tristate "Technisat/B2C2 Air/Sky/Cable2PC USB"
+	depends on DVB_CORE && I2C
+	help
+	  Support for the Air/Sky/Cable2PC USB1.1 box (DVB/ATSC) by Technisat/B2C2,
+
+	  Say Y if you own such a device and want to use it.
+
+config DVB_B2C2_FLEXCOP_USB_DEBUG
+	bool "Enable debug for the B2C2 FlexCop drivers"
+	depends on DVB_B2C2_FLEXCOP_USB
+	select DVB_B2C2_FLEXCOP_DEBUG
+	help
+	  Say Y if you want to enable the module option to control debug messages
+	  of all B2C2 FlexCop drivers.
diff --git a/drivers/media/usb/b2c2/Makefile b/drivers/media/usb/b2c2/Makefile
new file mode 100644
index 0000000..f3cef05
--- /dev/null
+++ b/drivers/media/usb/b2c2/Makefile
@@ -0,0 +1,4 @@
+b2c2-flexcop-usb-objs := flexcop-usb.o
+obj-$(CONFIG_DVB_B2C2_FLEXCOP_USB) += b2c2-flexcop-usb.o
+
+ccflags-y += -Idrivers/media/common/b2c2/
diff --git a/drivers/media/usb/b2c2/flexcop-usb.c b/drivers/media/usb/b2c2/flexcop-usb.c
new file mode 100644
index 0000000..a8f3169
--- /dev/null
+++ b/drivers/media/usb/b2c2/flexcop-usb.c
@@ -0,0 +1,617 @@
+/*
+ * Linux driver for digital TV devices equipped with B2C2 FlexcopII(b)/III
+ * flexcop-usb.c - covers the USB part
+ * see flexcop.c for copyright information
+ */
+#define FC_LOG_PREFIX "flexcop_usb"
+#include "flexcop-usb.h"
+#include "flexcop-common.h"
+
+/* Version information */
+#define DRIVER_VERSION "0.1"
+#define DRIVER_NAME "Technisat/B2C2 FlexCop II/IIb/III Digital TV USB Driver"
+#define DRIVER_AUTHOR "Patrick Boettcher <patrick.boettcher@posteo.de>"
+
+/* debug */
+#ifdef CONFIG_DVB_B2C2_FLEXCOP_DEBUG
+#define dprintk(level,args...) \
+	do { if ((debug & level)) printk(args); } while (0)
+
+#define debug_dump(b, l, method) do {\
+	int i; \
+	for (i = 0; i < l; i++) \
+		method("%02x ", b[i]); \
+	method("\n"); \
+} while (0)
+
+#define DEBSTATUS ""
+#else
+#define dprintk(level, args...)
+#define debug_dump(b, l, method)
+#define DEBSTATUS " (debugging is not enabled)"
+#endif
+
+static int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "set debugging level (1=info,ts=2,ctrl=4,i2c=8,v8mem=16 (or-able))." DEBSTATUS);
+#undef DEBSTATUS
+
+#define deb_info(args...) dprintk(0x01, args)
+#define deb_ts(args...) dprintk(0x02, args)
+#define deb_ctrl(args...) dprintk(0x04, args)
+#define deb_i2c(args...) dprintk(0x08, args)
+#define deb_v8(args...) dprintk(0x10, args)
+
+/* JLP 111700: we will include the 1 bit gap between the upper and lower 3 bits
+ * in the IBI address, to make the V8 code simpler.
+ * PCI ADDRESS FORMAT: 0x71C -> 0000 0111 0001 1100 (the six bits used)
+ *                  in general: 0000 0HHH 000L LL00
+ * IBI ADDRESS FORMAT:                    RHHH BLLL
+ *
+ * where R is the read(1)/write(0) bit, B is the busy bit
+ * and HHH and LLL are the two sets of three bits from the PCI address.
+ */
+#define B2C2_FLEX_PCIOFFSET_TO_INTERNALADDR(usPCI) (u8) \
+	(((usPCI >> 2) & 0x07) + ((usPCI >> 4) & 0x70))
+#define B2C2_FLEX_INTERNALADDR_TO_PCIOFFSET(ucAddr) (u16) \
+	(((ucAddr & 0x07) << 2) + ((ucAddr & 0x70) << 4))
+
+/*
+ * DKT 020228
+ * - forget about this VENDOR_BUFFER_SIZE, read and write register
+ *   deal with DWORD or 4 bytes, that should be should from now on
+ * - from now on, we don't support anything older than firm 1.00
+ *   I eliminated the write register as a 2 trip of writing hi word and lo word
+ *   and force this to write only 4 bytes at a time.
+ *   NOTE: this should work with all the firmware from 1.00 and newer
+ */
+static int flexcop_usb_readwrite_dw(struct flexcop_device *fc, u16 wRegOffsPCI, u32 *val, u8 read)
+{
+	struct flexcop_usb *fc_usb = fc->bus_specific;
+	u8 request = read ? B2C2_USB_READ_REG : B2C2_USB_WRITE_REG;
+	u8 request_type = (read ? USB_DIR_IN : USB_DIR_OUT) | USB_TYPE_VENDOR;
+	u8 wAddress = B2C2_FLEX_PCIOFFSET_TO_INTERNALADDR(wRegOffsPCI) |
+		(read ? 0x80 : 0);
+	int ret;
+
+	mutex_lock(&fc_usb->data_mutex);
+	if (!read)
+		memcpy(fc_usb->data, val, sizeof(*val));
+
+	ret = usb_control_msg(fc_usb->udev,
+			read ? B2C2_USB_CTRL_PIPE_IN : B2C2_USB_CTRL_PIPE_OUT,
+			request,
+			request_type, /* 0xc0 read or 0x40 write */
+			wAddress,
+			0,
+			fc_usb->data,
+			sizeof(u32),
+			B2C2_WAIT_FOR_OPERATION_RDW * HZ);
+
+	if (ret != sizeof(u32)) {
+		err("error while %s dword from %d (%d).", read ? "reading" :
+				"writing", wAddress, wRegOffsPCI);
+		if (ret >= 0)
+			ret = -EIO;
+	}
+
+	if (read && ret >= 0)
+		memcpy(val, fc_usb->data, sizeof(*val));
+	mutex_unlock(&fc_usb->data_mutex);
+
+	return ret;
+}
+/*
+ * DKT 010817 - add support for V8 memory read/write and flash update
+ */
+static int flexcop_usb_v8_memory_req(struct flexcop_usb *fc_usb,
+		flexcop_usb_request_t req, u8 page, u16 wAddress,
+		u8 *pbBuffer, u32 buflen)
+{
+	u8 request_type = USB_TYPE_VENDOR;
+	u16 wIndex;
+	int nWaitTime, pipe, ret;
+	wIndex = page << 8;
+
+	if (buflen > sizeof(fc_usb->data)) {
+		err("Buffer size bigger than max URB control message\n");
+		return -EIO;
+	}
+
+	switch (req) {
+	case B2C2_USB_READ_V8_MEM:
+		nWaitTime = B2C2_WAIT_FOR_OPERATION_V8READ;
+		request_type |= USB_DIR_IN;
+		pipe = B2C2_USB_CTRL_PIPE_IN;
+		break;
+	case B2C2_USB_WRITE_V8_MEM:
+		wIndex |= pbBuffer[0];
+		request_type |= USB_DIR_OUT;
+		nWaitTime = B2C2_WAIT_FOR_OPERATION_V8WRITE;
+		pipe = B2C2_USB_CTRL_PIPE_OUT;
+		break;
+	case B2C2_USB_FLASH_BLOCK:
+		request_type |= USB_DIR_OUT;
+		nWaitTime = B2C2_WAIT_FOR_OPERATION_V8FLASH;
+		pipe = B2C2_USB_CTRL_PIPE_OUT;
+		break;
+	default:
+		deb_info("unsupported request for v8_mem_req %x.\n", req);
+		return -EINVAL;
+	}
+	deb_v8("v8mem: %02x %02x %04x %04x, len: %d\n", request_type, req,
+			wAddress, wIndex, buflen);
+
+	mutex_lock(&fc_usb->data_mutex);
+
+	if ((request_type & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT)
+		memcpy(fc_usb->data, pbBuffer, buflen);
+
+	ret = usb_control_msg(fc_usb->udev, pipe,
+			req,
+			request_type,
+			wAddress,
+			wIndex,
+			fc_usb->data,
+			buflen,
+			nWaitTime * HZ);
+	if (ret != buflen)
+		ret = -EIO;
+
+	if (ret >= 0) {
+		ret = 0;
+		if ((request_type & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN)
+			memcpy(pbBuffer, fc_usb->data, buflen);
+	}
+
+	mutex_unlock(&fc_usb->data_mutex);
+
+	debug_dump(pbBuffer, ret, deb_v8);
+	return ret;
+}
+
+#define bytes_left_to_read_on_page(paddr,buflen) \
+	((V8_MEMORY_PAGE_SIZE - (paddr & V8_MEMORY_PAGE_MASK)) > buflen \
+	 ? buflen : (V8_MEMORY_PAGE_SIZE - (paddr & V8_MEMORY_PAGE_MASK)))
+
+static int flexcop_usb_memory_req(struct flexcop_usb *fc_usb,
+		flexcop_usb_request_t req, flexcop_usb_mem_page_t page_start,
+		u32 addr, int extended, u8 *buf, u32 len)
+{
+	int i,ret = 0;
+	u16 wMax;
+	u32 pagechunk = 0;
+
+	switch(req) {
+	case B2C2_USB_READ_V8_MEM:
+		wMax = USB_MEM_READ_MAX;
+		break;
+	case B2C2_USB_WRITE_V8_MEM:
+		wMax = USB_MEM_WRITE_MAX;
+		break;
+	case B2C2_USB_FLASH_BLOCK:
+		wMax = USB_FLASH_MAX;
+		break;
+	default:
+		return -EINVAL;
+		break;
+	}
+	for (i = 0; i < len;) {
+		pagechunk =
+			wMax < bytes_left_to_read_on_page(addr, len) ?
+				wMax :
+				bytes_left_to_read_on_page(addr, len);
+		deb_info("%x\n",
+			(addr & V8_MEMORY_PAGE_MASK) |
+				(V8_MEMORY_EXTENDED*extended));
+
+		ret = flexcop_usb_v8_memory_req(fc_usb, req,
+			page_start + (addr / V8_MEMORY_PAGE_SIZE),
+			(addr & V8_MEMORY_PAGE_MASK) |
+				(V8_MEMORY_EXTENDED*extended),
+			&buf[i], pagechunk);
+
+		if (ret < 0)
+			return ret;
+		addr += pagechunk;
+		len -= pagechunk;
+	}
+	return 0;
+}
+
+static int flexcop_usb_get_mac_addr(struct flexcop_device *fc, int extended)
+{
+	return flexcop_usb_memory_req(fc->bus_specific, B2C2_USB_READ_V8_MEM,
+		V8_MEMORY_PAGE_FLASH, 0x1f010, 1,
+		fc->dvb_adapter.proposed_mac, 6);
+}
+
+/* usb i2c stuff */
+static int flexcop_usb_i2c_req(struct flexcop_i2c_adapter *i2c,
+		flexcop_usb_request_t req, flexcop_usb_i2c_function_t func,
+		u8 chipaddr, u8 addr, u8 *buf, u8 buflen)
+{
+	struct flexcop_usb *fc_usb = i2c->fc->bus_specific;
+	u16 wValue, wIndex;
+	int nWaitTime, pipe, ret;
+	u8 request_type = USB_TYPE_VENDOR;
+
+	if (buflen > sizeof(fc_usb->data)) {
+		err("Buffer size bigger than max URB control message\n");
+		return -EIO;
+	}
+
+	switch (func) {
+	case USB_FUNC_I2C_WRITE:
+	case USB_FUNC_I2C_MULTIWRITE:
+	case USB_FUNC_I2C_REPEATWRITE:
+		/* DKT 020208 - add this to support special case of DiSEqC */
+	case USB_FUNC_I2C_CHECKWRITE:
+		pipe = B2C2_USB_CTRL_PIPE_OUT;
+		nWaitTime = 2;
+		request_type |= USB_DIR_OUT;
+		break;
+	case USB_FUNC_I2C_READ:
+	case USB_FUNC_I2C_REPEATREAD:
+		pipe = B2C2_USB_CTRL_PIPE_IN;
+		nWaitTime = 2;
+		request_type |= USB_DIR_IN;
+		break;
+	default:
+		deb_info("unsupported function for i2c_req %x\n", func);
+		return -EINVAL;
+	}
+	wValue = (func << 8) | (i2c->port << 4);
+	wIndex = (chipaddr << 8 ) | addr;
+
+	deb_i2c("i2c %2d: %02x %02x %02x %02x %02x %02x\n",
+			func, request_type, req,
+			wValue & 0xff, wValue >> 8,
+			wIndex & 0xff, wIndex >> 8);
+
+	mutex_lock(&fc_usb->data_mutex);
+
+	if ((request_type & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT)
+		memcpy(fc_usb->data, buf, buflen);
+
+	ret = usb_control_msg(fc_usb->udev, pipe,
+			req,
+			request_type,
+			wValue,
+			wIndex,
+			fc_usb->data,
+			buflen,
+			nWaitTime * HZ);
+
+	if (ret != buflen)
+		ret = -EIO;
+
+	if (ret >= 0) {
+		ret = 0;
+		if ((request_type & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN)
+			memcpy(buf, fc_usb->data, buflen);
+	}
+
+	mutex_unlock(&fc_usb->data_mutex);
+
+	return 0;
+}
+
+/* actual bus specific access functions,
+   make sure prototype are/will be equal to pci */
+static flexcop_ibi_value flexcop_usb_read_ibi_reg(struct flexcop_device *fc,
+	flexcop_ibi_register reg)
+{
+	flexcop_ibi_value val;
+	val.raw = 0;
+	flexcop_usb_readwrite_dw(fc, reg, &val.raw, 1);
+	return val;
+}
+
+static int flexcop_usb_write_ibi_reg(struct flexcop_device *fc,
+		flexcop_ibi_register reg, flexcop_ibi_value val)
+{
+	return flexcop_usb_readwrite_dw(fc, reg, &val.raw, 0);
+}
+
+static int flexcop_usb_i2c_request(struct flexcop_i2c_adapter *i2c,
+		flexcop_access_op_t op, u8 chipaddr, u8 addr, u8 *buf, u16 len)
+{
+	if (op == FC_READ)
+		return flexcop_usb_i2c_req(i2c, B2C2_USB_I2C_REQUEST,
+				USB_FUNC_I2C_READ, chipaddr, addr, buf, len);
+	else
+		return flexcop_usb_i2c_req(i2c, B2C2_USB_I2C_REQUEST,
+				USB_FUNC_I2C_WRITE, chipaddr, addr, buf, len);
+}
+
+static void flexcop_usb_process_frame(struct flexcop_usb *fc_usb,
+	u8 *buffer, int buffer_length)
+{
+	u8 *b;
+	int l;
+
+	deb_ts("tmp_buffer_length=%d, buffer_length=%d\n",
+		fc_usb->tmp_buffer_length, buffer_length);
+
+	if (fc_usb->tmp_buffer_length > 0) {
+		memcpy(fc_usb->tmp_buffer+fc_usb->tmp_buffer_length, buffer,
+				buffer_length);
+		fc_usb->tmp_buffer_length += buffer_length;
+		b = fc_usb->tmp_buffer;
+		l = fc_usb->tmp_buffer_length;
+	} else {
+		b=buffer;
+		l=buffer_length;
+	}
+
+	while (l >= 190) {
+		if (*b == 0xff) {
+			switch (*(b+1) & 0x03) {
+			case 0x01: /* media packet */
+				if (*(b+2) == 0x47)
+					flexcop_pass_dmx_packets(
+							fc_usb->fc_dev, b+2, 1);
+				else
+					deb_ts("not ts packet %*ph\n", 4, b+2);
+				b += 190;
+				l -= 190;
+				break;
+			default:
+				deb_ts("wrong packet type\n");
+				l = 0;
+				break;
+			}
+		} else {
+			deb_ts("wrong header\n");
+			l = 0;
+		}
+	}
+
+	if (l>0)
+		memcpy(fc_usb->tmp_buffer, b, l);
+	fc_usb->tmp_buffer_length = l;
+}
+
+static void flexcop_usb_urb_complete(struct urb *urb)
+{
+	struct flexcop_usb *fc_usb = urb->context;
+	int i;
+
+	if (urb->actual_length > 0)
+		deb_ts("urb completed, bufsize: %d actlen; %d\n",
+			urb->transfer_buffer_length, urb->actual_length);
+
+	for (i = 0; i < urb->number_of_packets; i++) {
+		if (urb->iso_frame_desc[i].status < 0) {
+			err("iso frame descriptor %d has an error: %d\n", i,
+				urb->iso_frame_desc[i].status);
+		} else
+			if (urb->iso_frame_desc[i].actual_length > 0) {
+				deb_ts("passed %d bytes to the demux\n",
+					urb->iso_frame_desc[i].actual_length);
+
+				flexcop_usb_process_frame(fc_usb,
+					urb->transfer_buffer +
+						urb->iso_frame_desc[i].offset,
+					urb->iso_frame_desc[i].actual_length);
+			}
+		urb->iso_frame_desc[i].status = 0;
+		urb->iso_frame_desc[i].actual_length = 0;
+	}
+	usb_submit_urb(urb,GFP_ATOMIC);
+}
+
+static int flexcop_usb_stream_control(struct flexcop_device *fc, int onoff)
+{
+	/* submit/kill iso packets */
+	return 0;
+}
+
+static void flexcop_usb_transfer_exit(struct flexcop_usb *fc_usb)
+{
+	int i;
+	for (i = 0; i < B2C2_USB_NUM_ISO_URB; i++)
+		if (fc_usb->iso_urb[i] != NULL) {
+			deb_ts("unlinking/killing urb no. %d\n",i);
+			usb_kill_urb(fc_usb->iso_urb[i]);
+			usb_free_urb(fc_usb->iso_urb[i]);
+		}
+
+	if (fc_usb->iso_buffer != NULL)
+		usb_free_coherent(fc_usb->udev,
+			fc_usb->buffer_size, fc_usb->iso_buffer,
+			fc_usb->dma_addr);
+}
+
+static int flexcop_usb_transfer_init(struct flexcop_usb *fc_usb)
+{
+	u16 frame_size = le16_to_cpu(
+		fc_usb->uintf->cur_altsetting->endpoint[0].desc.wMaxPacketSize);
+	int bufsize = B2C2_USB_NUM_ISO_URB * B2C2_USB_FRAMES_PER_ISO *
+		frame_size, i, j, ret;
+	int buffer_offset = 0;
+
+	deb_ts("creating %d iso-urbs with %d frames each of %d bytes size = %d.\n",
+	       B2C2_USB_NUM_ISO_URB,
+			B2C2_USB_FRAMES_PER_ISO, frame_size, bufsize);
+
+	fc_usb->iso_buffer = usb_alloc_coherent(fc_usb->udev,
+			bufsize, GFP_KERNEL, &fc_usb->dma_addr);
+	if (fc_usb->iso_buffer == NULL)
+		return -ENOMEM;
+
+	memset(fc_usb->iso_buffer, 0, bufsize);
+	fc_usb->buffer_size = bufsize;
+
+	/* creating iso urbs */
+	for (i = 0; i < B2C2_USB_NUM_ISO_URB; i++) {
+		fc_usb->iso_urb[i] = usb_alloc_urb(B2C2_USB_FRAMES_PER_ISO,
+			GFP_ATOMIC);
+		if (fc_usb->iso_urb[i] == NULL) {
+			ret = -ENOMEM;
+			goto urb_error;
+		}
+	}
+
+	/* initialising and submitting iso urbs */
+	for (i = 0; i < B2C2_USB_NUM_ISO_URB; i++) {
+		int frame_offset = 0;
+		struct urb *urb = fc_usb->iso_urb[i];
+		deb_ts("initializing and submitting urb no. %d (buf_offset: %d).\n",
+		       i, buffer_offset);
+
+		urb->dev = fc_usb->udev;
+		urb->context = fc_usb;
+		urb->complete = flexcop_usb_urb_complete;
+		urb->pipe = B2C2_USB_DATA_PIPE;
+		urb->transfer_flags = URB_ISO_ASAP;
+		urb->interval = 1;
+		urb->number_of_packets = B2C2_USB_FRAMES_PER_ISO;
+		urb->transfer_buffer_length = frame_size * B2C2_USB_FRAMES_PER_ISO;
+		urb->transfer_buffer = fc_usb->iso_buffer + buffer_offset;
+
+		buffer_offset += frame_size * B2C2_USB_FRAMES_PER_ISO;
+		for (j = 0; j < B2C2_USB_FRAMES_PER_ISO; j++) {
+			deb_ts("urb no: %d, frame: %d, frame_offset: %d\n",
+					i, j, frame_offset);
+			urb->iso_frame_desc[j].offset = frame_offset;
+			urb->iso_frame_desc[j].length = frame_size;
+			frame_offset += frame_size;
+		}
+
+		if ((ret = usb_submit_urb(fc_usb->iso_urb[i],GFP_ATOMIC))) {
+			err("submitting urb %d failed with %d.", i, ret);
+			goto urb_error;
+		}
+		deb_ts("submitted urb no. %d.\n",i);
+	}
+
+	/* SRAM */
+	flexcop_sram_set_dest(fc_usb->fc_dev, FC_SRAM_DEST_MEDIA |
+			FC_SRAM_DEST_NET | FC_SRAM_DEST_CAO | FC_SRAM_DEST_CAI,
+			FC_SRAM_DEST_TARGET_WAN_USB);
+	flexcop_wan_set_speed(fc_usb->fc_dev, FC_WAN_SPEED_8MBITS);
+	flexcop_sram_ctrl(fc_usb->fc_dev, 1, 1, 1);
+	return 0;
+
+urb_error:
+	flexcop_usb_transfer_exit(fc_usb);
+	return ret;
+}
+
+static int flexcop_usb_init(struct flexcop_usb *fc_usb)
+{
+	/* use the alternate setting with the larges buffer */
+	usb_set_interface(fc_usb->udev,0,1);
+	switch (fc_usb->udev->speed) {
+	case USB_SPEED_LOW:
+		err("cannot handle USB speed because it is too slow.");
+		return -ENODEV;
+		break;
+	case USB_SPEED_FULL:
+		info("running at FULL speed.");
+		break;
+	case USB_SPEED_HIGH:
+		info("running at HIGH speed.");
+		break;
+	case USB_SPEED_UNKNOWN: /* fall through */
+	default:
+		err("cannot handle USB speed because it is unknown.");
+		return -ENODEV;
+	}
+	usb_set_intfdata(fc_usb->uintf, fc_usb);
+	return 0;
+}
+
+static void flexcop_usb_exit(struct flexcop_usb *fc_usb)
+{
+	usb_set_intfdata(fc_usb->uintf, NULL);
+}
+
+static int flexcop_usb_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	struct usb_device *udev = interface_to_usbdev(intf);
+	struct flexcop_usb *fc_usb = NULL;
+	struct flexcop_device *fc = NULL;
+	int ret;
+
+	if ((fc = flexcop_device_kmalloc(sizeof(struct flexcop_usb))) == NULL) {
+		err("out of memory\n");
+		return -ENOMEM;
+	}
+
+	/* general flexcop init */
+	fc_usb = fc->bus_specific;
+	fc_usb->fc_dev = fc;
+	mutex_init(&fc_usb->data_mutex);
+
+	fc->read_ibi_reg  = flexcop_usb_read_ibi_reg;
+	fc->write_ibi_reg = flexcop_usb_write_ibi_reg;
+	fc->i2c_request = flexcop_usb_i2c_request;
+	fc->get_mac_addr = flexcop_usb_get_mac_addr;
+
+	fc->stream_control = flexcop_usb_stream_control;
+
+	fc->pid_filtering = 1;
+	fc->bus_type = FC_USB;
+
+	fc->dev = &udev->dev;
+	fc->owner = THIS_MODULE;
+
+	/* bus specific part */
+	fc_usb->udev = udev;
+	fc_usb->uintf = intf;
+	if ((ret = flexcop_usb_init(fc_usb)) != 0)
+		goto err_kfree;
+
+	/* init flexcop */
+	if ((ret = flexcop_device_initialize(fc)) != 0)
+		goto err_usb_exit;
+
+	/* xfer init */
+	if ((ret = flexcop_usb_transfer_init(fc_usb)) != 0)
+		goto err_fc_exit;
+
+	info("%s successfully initialized and connected.", DRIVER_NAME);
+	return 0;
+
+err_fc_exit:
+	flexcop_device_exit(fc);
+err_usb_exit:
+	flexcop_usb_exit(fc_usb);
+err_kfree:
+	flexcop_device_kfree(fc);
+	return ret;
+}
+
+static void flexcop_usb_disconnect(struct usb_interface *intf)
+{
+	struct flexcop_usb *fc_usb = usb_get_intfdata(intf);
+	flexcop_usb_transfer_exit(fc_usb);
+	flexcop_device_exit(fc_usb->fc_dev);
+	flexcop_usb_exit(fc_usb);
+	flexcop_device_kfree(fc_usb->fc_dev);
+	info("%s successfully deinitialized and disconnected.", DRIVER_NAME);
+}
+
+static const struct usb_device_id flexcop_usb_table[] = {
+	{ USB_DEVICE(0x0af7, 0x0101) },
+	{ }
+};
+MODULE_DEVICE_TABLE (usb, flexcop_usb_table);
+
+/* usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver flexcop_usb_driver = {
+	.name		= "b2c2_flexcop_usb",
+	.probe		= flexcop_usb_probe,
+	.disconnect = flexcop_usb_disconnect,
+	.id_table	= flexcop_usb_table,
+};
+
+module_usb_driver(flexcop_usb_driver);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_NAME);
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/b2c2/flexcop-usb.h b/drivers/media/usb/b2c2/flexcop-usb.h
new file mode 100644
index 0000000..e86faa0
--- /dev/null
+++ b/drivers/media/usb/b2c2/flexcop-usb.h
@@ -0,0 +1,116 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Linux driver for digital TV devices equipped with B2C2 FlexcopII(b)/III
+ * flexcop-usb.h - header file for the USB part
+ * see flexcop.c for copyright information
+ */
+#ifndef __FLEXCOP_USB_H_INCLUDED__
+#define __FLEXCOP_USB_H_INCLUDED__
+
+#include <linux/usb.h>
+
+/* transfer parameters */
+#define B2C2_USB_FRAMES_PER_ISO 4
+#define B2C2_USB_NUM_ISO_URB 4
+
+#define B2C2_USB_CTRL_PIPE_IN usb_rcvctrlpipe(fc_usb->udev, 0)
+#define B2C2_USB_CTRL_PIPE_OUT usb_sndctrlpipe(fc_usb->udev, 0)
+#define B2C2_USB_DATA_PIPE usb_rcvisocpipe(fc_usb->udev, 0x81)
+
+struct flexcop_usb {
+	struct usb_device *udev;
+	struct usb_interface *uintf;
+
+	u8 *iso_buffer;
+	int buffer_size;
+	dma_addr_t dma_addr;
+
+	struct urb *iso_urb[B2C2_USB_NUM_ISO_URB];
+	struct flexcop_device *fc_dev;
+
+	u8 tmp_buffer[1023+190];
+	int tmp_buffer_length;
+
+	/* for URB control messages */
+	u8 data[80];
+	struct mutex data_mutex;
+};
+
+#if 0
+/* request types TODO What is its use?*/
+typedef enum {
+
+} flexcop_usb_request_type_t;
+#endif
+
+/* request */
+typedef enum {
+	B2C2_USB_WRITE_V8_MEM = 0x04,
+	B2C2_USB_READ_V8_MEM  = 0x05,
+	B2C2_USB_READ_REG     = 0x08,
+	B2C2_USB_WRITE_REG    = 0x0A,
+	B2C2_USB_WRITEREGHI   = 0x0B,
+	B2C2_USB_FLASH_BLOCK  = 0x10,
+	B2C2_USB_I2C_REQUEST  = 0x11,
+	B2C2_USB_UTILITY      = 0x12,
+} flexcop_usb_request_t;
+
+/* function definition for I2C_REQUEST */
+typedef enum {
+	USB_FUNC_I2C_WRITE       = 0x01,
+	USB_FUNC_I2C_MULTIWRITE  = 0x02,
+	USB_FUNC_I2C_READ        = 0x03,
+	USB_FUNC_I2C_REPEATWRITE = 0x04,
+	USB_FUNC_GET_DESCRIPTOR  = 0x05,
+	USB_FUNC_I2C_REPEATREAD  = 0x06,
+	/* DKT 020208 - add this to support special case of DiSEqC */
+	USB_FUNC_I2C_CHECKWRITE  = 0x07,
+	USB_FUNC_I2C_CHECKRESULT = 0x08,
+} flexcop_usb_i2c_function_t;
+
+/* function definition for UTILITY request 0x12
+ * DKT 020304 - new utility function */
+typedef enum {
+	UTILITY_SET_FILTER          = 0x01,
+	UTILITY_DATA_ENABLE         = 0x02,
+	UTILITY_FLEX_MULTIWRITE     = 0x03,
+	UTILITY_SET_BUFFER_SIZE     = 0x04,
+	UTILITY_FLEX_OPERATOR       = 0x05,
+	UTILITY_FLEX_RESET300_START = 0x06,
+	UTILITY_FLEX_RESET300_STOP  = 0x07,
+	UTILITY_FLEX_RESET300       = 0x08,
+	UTILITY_SET_ISO_SIZE        = 0x09,
+	UTILITY_DATA_RESET          = 0x0A,
+	UTILITY_GET_DATA_STATUS     = 0x10,
+	UTILITY_GET_V8_REG          = 0x11,
+	/* DKT 020326 - add function for v1.14 */
+	UTILITY_SRAM_WRITE          = 0x12,
+	UTILITY_SRAM_READ           = 0x13,
+	UTILITY_SRAM_TESTFILL       = 0x14,
+	UTILITY_SRAM_TESTSET        = 0x15,
+	UTILITY_SRAM_TESTVERIFY     = 0x16,
+} flexcop_usb_utility_function_t;
+
+#define B2C2_WAIT_FOR_OPERATION_RW (1*HZ)
+#define B2C2_WAIT_FOR_OPERATION_RDW (3*HZ)
+#define B2C2_WAIT_FOR_OPERATION_WDW (1*HZ)
+
+#define B2C2_WAIT_FOR_OPERATION_V8READ (3*HZ)
+#define B2C2_WAIT_FOR_OPERATION_V8WRITE (3*HZ)
+#define B2C2_WAIT_FOR_OPERATION_V8FLASH (3*HZ)
+
+typedef enum {
+	V8_MEMORY_PAGE_DVB_CI = 0x20,
+	V8_MEMORY_PAGE_DVB_DS = 0x40,
+	V8_MEMORY_PAGE_MULTI2 = 0x60,
+	V8_MEMORY_PAGE_FLASH  = 0x80
+} flexcop_usb_mem_page_t;
+
+#define V8_MEMORY_EXTENDED (1 << 15)
+#define USB_MEM_READ_MAX   32
+#define USB_MEM_WRITE_MAX   1
+#define USB_FLASH_MAX       8
+#define V8_MEMORY_PAGE_SIZE 0x8000 /* 32K */
+#define V8_MEMORY_PAGE_MASK 0x7FFF
+
+#endif
diff --git a/drivers/media/usb/cpia2/Kconfig b/drivers/media/usb/cpia2/Kconfig
new file mode 100644
index 0000000..66e9283
--- /dev/null
+++ b/drivers/media/usb/cpia2/Kconfig
@@ -0,0 +1,9 @@
+config VIDEO_CPIA2
+	tristate "CPiA2 Video For Linux"
+	depends on VIDEO_DEV && USB && VIDEO_V4L2
+	---help---
+	  This is the video4linux driver for cameras based on Vision's CPiA2
+	  (Colour Processor Interface ASIC), such as the Digital Blue QX5
+	  Microscope. If you have one of these cameras, say Y here
+
+	  This driver is also available as a module (cpia2).
diff --git a/drivers/media/usb/cpia2/Makefile b/drivers/media/usb/cpia2/Makefile
new file mode 100644
index 0000000..828cf1b
--- /dev/null
+++ b/drivers/media/usb/cpia2/Makefile
@@ -0,0 +1,3 @@
+cpia2-objs	:= cpia2_v4l.o cpia2_usb.o cpia2_core.o
+
+obj-$(CONFIG_VIDEO_CPIA2) += cpia2.o
diff --git a/drivers/media/usb/cpia2/cpia2.h b/drivers/media/usb/cpia2/cpia2.h
new file mode 100644
index 0000000..ab238ac
--- /dev/null
+++ b/drivers/media/usb/cpia2/cpia2.h
@@ -0,0 +1,483 @@
+/****************************************************************************
+ *
+ *  Filename: cpia2.h
+ *
+ *  Copyright 2001, STMicrolectronics, Inc.
+ *
+ *  Contact:  steve.miller@st.com
+ *
+ *  Description:
+ *     This is a USB driver for CPiA2 based video cameras.
+ *
+ *     This driver is modelled on the cpia usb driver by
+ *     Jochen Scharrlach and Johannes Erdfeldt.
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ ****************************************************************************/
+
+#ifndef __CPIA2_H__
+#define __CPIA2_H__
+
+#include <linux/videodev2.h>
+#include <linux/usb.h>
+#include <linux/poll.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+
+#include "cpia2_registers.h"
+
+/* define for verbose debug output */
+//#define _CPIA2_DEBUG_
+
+/***
+ * Image defines
+ ***/
+
+/*  Misc constants */
+#define ALLOW_CORRUPT 0		/* Causes collater to discard checksum */
+
+/* USB Transfer mode */
+#define XFER_ISOC 0
+#define XFER_BULK 1
+
+/* USB Alternates */
+#define USBIF_CMDONLY 0
+#define USBIF_BULK 1
+#define USBIF_ISO_1 2	/*  128 bytes/ms */
+#define USBIF_ISO_2 3	/*  384 bytes/ms */
+#define USBIF_ISO_3 4	/*  640 bytes/ms */
+#define USBIF_ISO_4 5	/*  768 bytes/ms */
+#define USBIF_ISO_5 6	/*  896 bytes/ms */
+#define USBIF_ISO_6 7	/* 1023 bytes/ms */
+
+/* Flicker Modes */
+#define NEVER_FLICKER   0
+#define FLICKER_60      60
+#define FLICKER_50      50
+
+/* Debug flags */
+#define DEBUG_NONE          0
+#define DEBUG_REG           0x00000001
+#define DEBUG_DUMP_PATCH    0x00000002
+#define DEBUG_DUMP_REGS     0x00000004
+
+/***
+ * Video frame sizes
+ ***/
+enum {
+	VIDEOSIZE_VGA = 0,	/* 640x480 */
+	VIDEOSIZE_CIF,		/* 352x288 */
+	VIDEOSIZE_QVGA,		/* 320x240 */
+	VIDEOSIZE_QCIF,		/* 176x144 */
+	VIDEOSIZE_288_216,
+	VIDEOSIZE_256_192,
+	VIDEOSIZE_224_168,
+	VIDEOSIZE_192_144,
+};
+
+#define STV_IMAGE_CIF_ROWS    288
+#define STV_IMAGE_CIF_COLS    352
+
+#define STV_IMAGE_QCIF_ROWS   144
+#define STV_IMAGE_QCIF_COLS   176
+
+#define STV_IMAGE_VGA_ROWS    480
+#define STV_IMAGE_VGA_COLS    640
+
+#define STV_IMAGE_QVGA_ROWS   240
+#define STV_IMAGE_QVGA_COLS   320
+
+#define JPEG_MARKER_COM (1<<6)	/* Comment segment */
+
+/***
+ * Enums
+ ***/
+/* Sensor types available with cpia2 asics */
+enum sensors {
+	CPIA2_SENSOR_410,
+	CPIA2_SENSOR_500
+};
+
+/* Asic types available in the CPiA2 architecture */
+#define  CPIA2_ASIC_672 0x67
+
+/* Device types (stv672, stv676, etc) */
+#define  DEVICE_STV_672   0x0001
+#define  DEVICE_STV_676   0x0002
+
+enum frame_status {
+	FRAME_EMPTY,
+	FRAME_READING,		/* In the process of being grabbed into */
+	FRAME_READY,		/* Ready to be read */
+	FRAME_ERROR,
+};
+
+/***
+ * Register access (for USB request byte)
+ ***/
+enum {
+	CAMERAACCESS_SYSTEM = 0,
+	CAMERAACCESS_VC,
+	CAMERAACCESS_VP,
+	CAMERAACCESS_IDATA
+};
+
+#define CAMERAACCESS_TYPE_BLOCK    0x00
+#define CAMERAACCESS_TYPE_RANDOM   0x04
+#define CAMERAACCESS_TYPE_MASK     0x08
+#define CAMERAACCESS_TYPE_REPEAT   0x0C
+
+#define TRANSFER_READ 0
+#define TRANSFER_WRITE 1
+
+#define DEFAULT_ALT   USBIF_ISO_6
+#define DEFAULT_BRIGHTNESS 0x46
+#define DEFAULT_CONTRAST 0x93
+#define DEFAULT_SATURATION 0x7f
+
+/* Power state */
+#define HI_POWER_MODE CPIA2_SYSTEM_CONTROL_HIGH_POWER
+#define LO_POWER_MODE CPIA2_SYSTEM_CONTROL_LOW_POWER
+
+
+/********
+ * Commands
+ *******/
+enum {
+	CPIA2_CMD_NONE = 0,
+	CPIA2_CMD_GET_VERSION,
+	CPIA2_CMD_GET_PNP_ID,
+	CPIA2_CMD_GET_ASIC_TYPE,
+	CPIA2_CMD_GET_SENSOR,
+	CPIA2_CMD_GET_VP_DEVICE,
+	CPIA2_CMD_GET_VP_BRIGHTNESS,
+	CPIA2_CMD_SET_VP_BRIGHTNESS,
+	CPIA2_CMD_GET_CONTRAST,
+	CPIA2_CMD_SET_CONTRAST,
+	CPIA2_CMD_GET_VP_SATURATION,
+	CPIA2_CMD_SET_VP_SATURATION,
+	CPIA2_CMD_GET_VP_GPIO_DIRECTION,
+	CPIA2_CMD_SET_VP_GPIO_DIRECTION,
+	CPIA2_CMD_GET_VP_GPIO_DATA,
+	CPIA2_CMD_SET_VP_GPIO_DATA,
+	CPIA2_CMD_GET_VC_MP_GPIO_DIRECTION,
+	CPIA2_CMD_SET_VC_MP_GPIO_DIRECTION,
+	CPIA2_CMD_GET_VC_MP_GPIO_DATA,
+	CPIA2_CMD_SET_VC_MP_GPIO_DATA,
+	CPIA2_CMD_ENABLE_PACKET_CTRL,
+	CPIA2_CMD_GET_FLICKER_MODES,
+	CPIA2_CMD_SET_FLICKER_MODES,
+	CPIA2_CMD_RESET_FIFO,	/* clear fifo and enable stream block */
+	CPIA2_CMD_SET_HI_POWER,
+	CPIA2_CMD_SET_LOW_POWER,
+	CPIA2_CMD_CLEAR_V2W_ERR,
+	CPIA2_CMD_SET_USER_MODE,
+	CPIA2_CMD_GET_USER_MODE,
+	CPIA2_CMD_FRAMERATE_REQ,
+	CPIA2_CMD_SET_COMPRESSION_STATE,
+	CPIA2_CMD_GET_WAKEUP,
+	CPIA2_CMD_SET_WAKEUP,
+	CPIA2_CMD_GET_PW_CONTROL,
+	CPIA2_CMD_SET_PW_CONTROL,
+	CPIA2_CMD_GET_SYSTEM_CTRL,
+	CPIA2_CMD_SET_SYSTEM_CTRL,
+	CPIA2_CMD_GET_VP_SYSTEM_STATE,
+	CPIA2_CMD_GET_VP_SYSTEM_CTRL,
+	CPIA2_CMD_SET_VP_SYSTEM_CTRL,
+	CPIA2_CMD_GET_VP_EXP_MODES,
+	CPIA2_CMD_SET_VP_EXP_MODES,
+	CPIA2_CMD_GET_DEVICE_CONFIG,
+	CPIA2_CMD_SET_DEVICE_CONFIG,
+	CPIA2_CMD_SET_SERIAL_ADDR,
+	CPIA2_CMD_SET_SENSOR_CR1,
+	CPIA2_CMD_GET_VC_CONTROL,
+	CPIA2_CMD_SET_VC_CONTROL,
+	CPIA2_CMD_SET_TARGET_KB,
+	CPIA2_CMD_SET_DEF_JPEG_OPT,
+	CPIA2_CMD_REHASH_VP4,
+	CPIA2_CMD_GET_USER_EFFECTS,
+	CPIA2_CMD_SET_USER_EFFECTS
+};
+
+enum user_cmd {
+	COMMAND_NONE = 0x00000001,
+	COMMAND_SET_FPS = 0x00000002,
+	COMMAND_SET_COLOR_PARAMS = 0x00000004,
+	COMMAND_GET_COLOR_PARAMS = 0x00000008,
+	COMMAND_SET_FORMAT = 0x00000010,	/* size, etc */
+	COMMAND_SET_FLICKER = 0x00000020
+};
+
+/***
+ * Some defines specific to the 676 chip
+ ***/
+#define CAMACC_CIF      0x01
+#define CAMACC_VGA      0x02
+#define CAMACC_QCIF     0x04
+#define CAMACC_QVGA     0x08
+
+
+struct cpia2_register {
+	u8 index;
+	u8 value;
+};
+
+struct cpia2_reg_mask {
+	u8 index;
+	u8 and_mask;
+	u8 or_mask;
+	u8 fill;
+};
+
+struct cpia2_command {
+	u32 command;
+	u8 req_mode;		/* (Block or random) | registerBank */
+	u8 reg_count;
+	u8 direction;
+	u8 start;
+	union reg_types {
+		struct cpia2_register registers[32];
+		struct cpia2_reg_mask masks[16];
+		u8 block_data[64];
+		u8 *patch_data;	/* points to function defined block */
+	} buffer;
+};
+
+struct camera_params {
+	struct {
+		u8 firmware_revision_hi; /* For system register set (bank 0) */
+		u8 firmware_revision_lo;
+		u8 asic_id;	/* Video Compressor set (bank 1) */
+		u8 asic_rev;
+		u8 vp_device_hi;	/* Video Processor set (bank 2) */
+		u8 vp_device_lo;
+		u8 sensor_flags;
+		u8 sensor_rev;
+	} version;
+
+	struct {
+		u32 device_type;     /* enumerated from vendor/product ids.
+				      * Currently, either STV_672 or STV_676 */
+		u16 vendor;
+		u16 product;
+		u16 device_revision;
+	} pnp_id;
+
+	struct {
+		u8 brightness;	/* CPIA2_VP_EXPOSURE_TARGET */
+		u8 contrast;	/* Note: this is CPIA2_VP_YRANGE */
+		u8 saturation;	/*  CPIA2_VP_SATURATION */
+	} color_params;
+
+	struct {
+		u8 cam_register;
+		u8 flicker_mode_req;	/* 1 if flicker on, else never flicker */
+	} flicker_control;
+
+	struct {
+		u8 jpeg_options;
+		u8 creep_period;
+		u8 user_squeeze;
+		u8 inhibit_htables;
+	} compression;
+
+	struct {
+		u8 ohsize;	/* output image size */
+		u8 ovsize;
+		u8 hcrop;	/* cropping start_pos/4 */
+		u8 vcrop;
+		u8 hphase;	/* scaling registers */
+		u8 vphase;
+		u8 hispan;
+		u8 vispan;
+		u8 hicrop;
+		u8 vicrop;
+		u8 hifraction;
+		u8 vifraction;
+	} image_size;
+
+	struct {
+		int width;	/* actual window width */
+		int height;	/* actual window height */
+	} roi;
+
+	struct {
+		u8 video_mode;
+		u8 frame_rate;
+		u8 video_size;	/* Not a register, just a convenience for cropped sizes */
+		u8 gpio_direction;
+		u8 gpio_data;
+		u8 system_ctrl;
+		u8 system_state;
+		u8 lowlight_boost;	/* Bool: 0 = off, 1 = on */
+		u8 device_config;
+		u8 exposure_modes;
+		u8 user_effects;
+	} vp_params;
+
+	struct {
+		u8 pw_control;
+		u8 wakeup;
+		u8 vc_control;
+		u8 vc_mp_direction;
+		u8 vc_mp_data;
+		u8 quality;
+	} vc_params;
+
+	struct {
+		u8 power_mode;
+		u8 system_ctrl;
+		u8 stream_mode;	/* This is the current alternate for usb drivers */
+		u8 allow_corrupt;
+	} camera_state;
+};
+
+#define NUM_SBUF    2
+
+struct cpia2_sbuf {
+	char *data;
+	struct urb *urb;
+};
+
+struct framebuf {
+	struct timeval timestamp;
+	unsigned long seq;
+	int num;
+	int length;
+	int max_length;
+	volatile enum frame_status status;
+	u8 *data;
+	struct framebuf *next;
+};
+
+struct camera_data {
+	/* locks */
+	struct v4l2_device v4l2_dev;
+	struct mutex v4l2_lock;	/* serialize file operations */
+	struct v4l2_ctrl_handler hdl;
+	struct {
+		/* Lights control cluster */
+		struct v4l2_ctrl *top_light;
+		struct v4l2_ctrl *bottom_light;
+	};
+	struct v4l2_ctrl *usb_alt;
+
+	/* camera status */
+	int first_image_seen;
+	enum sensors sensor_type;
+	u8 flush;
+	struct v4l2_fh *stream_fh;
+	u8 mmapped;
+	int streaming;		/* 0 = no, 1 = yes */
+	int xfer_mode;		/* XFER_BULK or XFER_ISOC */
+	struct camera_params params;	/* camera settings */
+
+	/* v4l */
+	int video_size;			/* VIDEO_SIZE_ */
+	struct video_device vdev;	/* v4l videodev */
+	u32 width;
+	u32 height;			/* Its size */
+	__u32 pixelformat;       /* Format fourcc      */
+
+	/* USB */
+	struct usb_device *dev;
+	unsigned char iface;
+	unsigned int cur_alt;
+	unsigned int old_alt;
+	struct cpia2_sbuf sbuf[NUM_SBUF];	/* Double buffering */
+
+	wait_queue_head_t wq_stream;
+
+	/* Buffering */
+	u32 frame_size;
+	int num_frames;
+	unsigned long frame_count;
+	u8 *frame_buffer;	/* frame buffer data */
+	struct framebuf *buffers;
+	struct framebuf * volatile curbuff;
+	struct framebuf *workbuff;
+
+	/* MJPEG Extension */
+	int APPn;		/* Number of APP segment to be written, must be 0..15 */
+	int APP_len;		/* Length of data in JPEG APPn segment */
+	char APP_data[60];	/* Data in the JPEG APPn segment. */
+
+	int COM_len;		/* Length of data in JPEG COM segment */
+	char COM_data[60];	/* Data in JPEG COM segment */
+};
+
+/* v4l */
+int cpia2_register_camera(struct camera_data *cam);
+void cpia2_unregister_camera(struct camera_data *cam);
+void cpia2_camera_release(struct v4l2_device *v4l2_dev);
+
+/* core */
+int cpia2_reset_camera(struct camera_data *cam);
+int cpia2_set_low_power(struct camera_data *cam);
+void cpia2_dbg_dump_registers(struct camera_data *cam);
+int cpia2_match_video_size(int width, int height);
+void cpia2_set_camera_state(struct camera_data *cam);
+void cpia2_save_camera_state(struct camera_data *cam);
+void cpia2_set_color_params(struct camera_data *cam);
+void cpia2_set_brightness(struct camera_data *cam, unsigned char value);
+void cpia2_set_contrast(struct camera_data *cam, unsigned char value);
+void cpia2_set_saturation(struct camera_data *cam, unsigned char value);
+int cpia2_set_flicker_mode(struct camera_data *cam, int mode);
+void cpia2_set_format(struct camera_data *cam);
+int cpia2_send_command(struct camera_data *cam, struct cpia2_command *cmd);
+int cpia2_do_command(struct camera_data *cam,
+		     unsigned int command,
+		     unsigned char direction, unsigned char param);
+struct camera_data *cpia2_init_camera_struct(struct usb_interface *intf);
+int cpia2_init_camera(struct camera_data *cam);
+int cpia2_allocate_buffers(struct camera_data *cam);
+void cpia2_free_buffers(struct camera_data *cam);
+long cpia2_read(struct camera_data *cam,
+		char __user *buf, unsigned long count, int noblock);
+__poll_t cpia2_poll(struct camera_data *cam,
+			struct file *filp, poll_table *wait);
+int cpia2_remap_buffer(struct camera_data *cam, struct vm_area_struct *vma);
+void cpia2_set_property_flip(struct camera_data *cam, int prop_val);
+void cpia2_set_property_mirror(struct camera_data *cam, int prop_val);
+int cpia2_set_gpio(struct camera_data *cam, unsigned char setting);
+int cpia2_set_fps(struct camera_data *cam, int framerate);
+
+/* usb */
+int cpia2_usb_init(void);
+void cpia2_usb_cleanup(void);
+int cpia2_usb_transfer_cmd(struct camera_data *cam, void *registers,
+			   u8 request, u8 start, u8 count, u8 direction);
+int cpia2_usb_stream_start(struct camera_data *cam, unsigned int alternate);
+int cpia2_usb_stream_stop(struct camera_data *cam);
+int cpia2_usb_stream_pause(struct camera_data *cam);
+int cpia2_usb_stream_resume(struct camera_data *cam);
+int cpia2_usb_change_streaming_alternate(struct camera_data *cam,
+					 unsigned int alt);
+
+
+/* ----------------------- debug functions ---------------------- */
+#ifdef _CPIA2_DEBUG_
+#define ALOG(lev, fmt, args...) printk(lev "%s:%d %s(): " fmt, __FILE__, __LINE__, __func__, ## args)
+#define LOG(fmt, args...) ALOG(KERN_INFO, fmt, ## args)
+#define ERR(fmt, args...) ALOG(KERN_ERR, fmt, ## args)
+#define DBG(fmt, args...) ALOG(KERN_DEBUG, fmt, ## args)
+#else
+#define ALOG(fmt,args...) printk(fmt,##args)
+#define LOG(fmt,args...) ALOG(KERN_INFO "cpia2: "fmt,##args)
+#define ERR(fmt,args...) ALOG(KERN_ERR "cpia2: "fmt,##args)
+#define DBG(fmn,args...) do {} while(0)
+#endif
+/* No function or lineno, for shorter lines */
+#define KINFO(fmt, args...) printk(KERN_INFO fmt,##args)
+
+#endif
diff --git a/drivers/media/usb/cpia2/cpia2_core.c b/drivers/media/usb/cpia2/cpia2_core.c
new file mode 100644
index 0000000..3dfbb54
--- /dev/null
+++ b/drivers/media/usb/cpia2/cpia2_core.c
@@ -0,0 +1,2431 @@
+/****************************************************************************
+ *
+ *  Filename: cpia2_core.c
+ *
+ *  Copyright 2001, STMicrolectronics, Inc.
+ *      Contact:  steve.miller@st.com
+ *
+ *  Description:
+ *     This is a USB driver for CPia2 based video cameras.
+ *     The infrastructure of this driver is based on the cpia usb driver by
+ *     Jochen Scharrlach and Johannes Erdfeldt.
+ *
+ *  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.
+ *
+ *  Stripped of 2.4 stuff ready for main kernel submit by
+ *		Alan Cox <alan@lxorguk.ukuu.org.uk>
+ *
+ ****************************************************************************/
+
+#include "cpia2.h"
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/vmalloc.h>
+#include <linux/firmware.h>
+#include <linux/sched/signal.h>
+
+#define FIRMWARE "cpia2/stv0672_vp4.bin"
+MODULE_FIRMWARE(FIRMWARE);
+
+/* #define _CPIA2_DEBUG_ */
+
+#ifdef _CPIA2_DEBUG_
+
+static const char *block_name[] = {
+	"System",
+	"VC",
+	"VP",
+	"IDATA"
+};
+#endif
+
+static unsigned int debugs_on;	/* default 0 - DEBUG_REG */
+
+
+/******************************************************************************
+ *
+ *  Forward Declarations
+ *
+ *****************************************************************************/
+static int apply_vp_patch(struct camera_data *cam);
+static int set_default_user_mode(struct camera_data *cam);
+static int set_vw_size(struct camera_data *cam, int size);
+static int configure_sensor(struct camera_data *cam,
+			    int reqwidth, int reqheight);
+static int config_sensor_410(struct camera_data *cam,
+			    int reqwidth, int reqheight);
+static int config_sensor_500(struct camera_data *cam,
+			    int reqwidth, int reqheight);
+static int set_all_properties(struct camera_data *cam);
+static void wake_system(struct camera_data *cam);
+static void set_lowlight_boost(struct camera_data *cam);
+static void reset_camera_struct(struct camera_data *cam);
+static int cpia2_set_high_power(struct camera_data *cam);
+
+/* Here we want the physical address of the memory.
+ * This is used when initializing the contents of the
+ * area and marking the pages as reserved.
+ */
+static inline unsigned long kvirt_to_pa(unsigned long adr)
+{
+	unsigned long kva, ret;
+
+	kva = (unsigned long) page_address(vmalloc_to_page((void *)adr));
+	kva |= adr & (PAGE_SIZE-1); /* restore the offset */
+	ret = __pa(kva);
+	return ret;
+}
+
+static void *rvmalloc(unsigned long size)
+{
+	void *mem;
+	unsigned long adr;
+
+	/* Round it off to PAGE_SIZE */
+	size = PAGE_ALIGN(size);
+
+	mem = vmalloc_32(size);
+	if (!mem)
+		return NULL;
+
+	memset(mem, 0, size);	/* Clear the ram out, no junk to the user */
+	adr = (unsigned long) mem;
+
+	while ((long)size > 0) {
+		SetPageReserved(vmalloc_to_page((void *)adr));
+		adr += PAGE_SIZE;
+		size -= PAGE_SIZE;
+	}
+	return mem;
+}
+
+static void rvfree(void *mem, unsigned long size)
+{
+	unsigned long adr;
+
+	if (!mem)
+		return;
+
+	size = PAGE_ALIGN(size);
+
+	adr = (unsigned long) mem;
+	while ((long)size > 0) {
+		ClearPageReserved(vmalloc_to_page((void *)adr));
+		adr += PAGE_SIZE;
+		size -= PAGE_SIZE;
+	}
+	vfree(mem);
+}
+
+/******************************************************************************
+ *
+ *  cpia2_do_command
+ *
+ *  Send an arbitrary command to the camera.  For commands that read from
+ *  the camera, copy the buffers into the proper param structures.
+ *****************************************************************************/
+int cpia2_do_command(struct camera_data *cam,
+		     u32 command, u8 direction, u8 param)
+{
+	int retval = 0;
+	struct cpia2_command cmd;
+	unsigned int device = cam->params.pnp_id.device_type;
+
+	cmd.command = command;
+	cmd.reg_count = 2;	/* default */
+	cmd.direction = direction;
+
+	/***
+	 * Set up the command.
+	 ***/
+	switch (command) {
+	case CPIA2_CMD_GET_VERSION:
+		cmd.req_mode =
+		    CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_SYSTEM;
+		cmd.start = CPIA2_SYSTEM_DEVICE_HI;
+		break;
+	case CPIA2_CMD_GET_PNP_ID:
+		cmd.req_mode =
+		    CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_SYSTEM;
+		cmd.reg_count = 8;
+		cmd.start = CPIA2_SYSTEM_DESCRIP_VID_HI;
+		break;
+	case CPIA2_CMD_GET_ASIC_TYPE:
+		cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VC;
+		cmd.start = CPIA2_VC_ASIC_ID;
+		break;
+	case CPIA2_CMD_GET_SENSOR:
+		cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP;
+		cmd.start = CPIA2_VP_SENSOR_FLAGS;
+		break;
+	case CPIA2_CMD_GET_VP_DEVICE:
+		cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP;
+		cmd.start = CPIA2_VP_DEVICEH;
+		break;
+	case CPIA2_CMD_SET_VP_BRIGHTNESS:
+		cmd.buffer.block_data[0] = param;
+		/* fall through */
+	case CPIA2_CMD_GET_VP_BRIGHTNESS:
+		cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP;
+		cmd.reg_count = 1;
+		if (device == DEVICE_STV_672)
+			cmd.start = CPIA2_VP4_EXPOSURE_TARGET;
+		else
+			cmd.start = CPIA2_VP5_EXPOSURE_TARGET;
+		break;
+	case CPIA2_CMD_SET_CONTRAST:
+		cmd.buffer.block_data[0] = param;
+		/* fall through */
+	case CPIA2_CMD_GET_CONTRAST:
+		cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP;
+		cmd.reg_count = 1;
+		cmd.start = CPIA2_VP_YRANGE;
+		break;
+	case CPIA2_CMD_SET_VP_SATURATION:
+		cmd.buffer.block_data[0] = param;
+		/* fall through */
+	case CPIA2_CMD_GET_VP_SATURATION:
+		cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP;
+		cmd.reg_count = 1;
+		if (device == DEVICE_STV_672)
+			cmd.start = CPIA2_VP_SATURATION;
+		else
+			cmd.start = CPIA2_VP5_MCUVSATURATION;
+		break;
+	case CPIA2_CMD_SET_VP_GPIO_DATA:
+		cmd.buffer.block_data[0] = param;
+		/* fall through */
+	case CPIA2_CMD_GET_VP_GPIO_DATA:
+		cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP;
+		cmd.reg_count = 1;
+		cmd.start = CPIA2_VP_GPIO_DATA;
+		break;
+	case CPIA2_CMD_SET_VP_GPIO_DIRECTION:
+		cmd.buffer.block_data[0] = param;
+		/* fall through */
+	case CPIA2_CMD_GET_VP_GPIO_DIRECTION:
+		cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP;
+		cmd.reg_count = 1;
+		cmd.start = CPIA2_VP_GPIO_DIRECTION;
+		break;
+	case CPIA2_CMD_SET_VC_MP_GPIO_DATA:
+		cmd.buffer.block_data[0] = param;
+		/* fall through */
+	case CPIA2_CMD_GET_VC_MP_GPIO_DATA:
+		cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VC;
+		cmd.reg_count = 1;
+		cmd.start = CPIA2_VC_MP_DATA;
+		break;
+	case CPIA2_CMD_SET_VC_MP_GPIO_DIRECTION:
+		cmd.buffer.block_data[0] = param;
+		/*fall through */
+	case CPIA2_CMD_GET_VC_MP_GPIO_DIRECTION:
+		cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VC;
+		cmd.reg_count = 1;
+		cmd.start = CPIA2_VC_MP_DIR;
+		break;
+	case CPIA2_CMD_ENABLE_PACKET_CTRL:
+		cmd.req_mode =
+		    CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_SYSTEM;
+		cmd.start = CPIA2_SYSTEM_INT_PACKET_CTRL;
+		cmd.reg_count = 1;
+		cmd.buffer.block_data[0] = param;
+		break;
+	case CPIA2_CMD_SET_FLICKER_MODES:
+		cmd.buffer.block_data[0] = param;
+		/* fall through */
+	case CPIA2_CMD_GET_FLICKER_MODES:
+		cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP;
+		cmd.reg_count = 1;
+		cmd.start = CPIA2_VP_FLICKER_MODES;
+		break;
+	case CPIA2_CMD_RESET_FIFO:	/* clear fifo and enable stream block */
+		cmd.req_mode = CAMERAACCESS_TYPE_RANDOM | CAMERAACCESS_VC;
+		cmd.reg_count = 2;
+		cmd.start = 0;
+		cmd.buffer.registers[0].index = CPIA2_VC_ST_CTRL;
+		cmd.buffer.registers[0].value = CPIA2_VC_ST_CTRL_SRC_VC |
+		    CPIA2_VC_ST_CTRL_DST_USB | CPIA2_VC_ST_CTRL_EOF_DETECT;
+		cmd.buffer.registers[1].index = CPIA2_VC_ST_CTRL;
+		cmd.buffer.registers[1].value = CPIA2_VC_ST_CTRL_SRC_VC |
+		    CPIA2_VC_ST_CTRL_DST_USB |
+		    CPIA2_VC_ST_CTRL_EOF_DETECT |
+		    CPIA2_VC_ST_CTRL_FIFO_ENABLE;
+		break;
+	case CPIA2_CMD_SET_HI_POWER:
+		cmd.req_mode =
+		    CAMERAACCESS_TYPE_RANDOM | CAMERAACCESS_SYSTEM;
+		cmd.reg_count = 2;
+		cmd.buffer.registers[0].index =
+		    CPIA2_SYSTEM_SYSTEM_CONTROL;
+		cmd.buffer.registers[1].index =
+		    CPIA2_SYSTEM_SYSTEM_CONTROL;
+		cmd.buffer.registers[0].value = CPIA2_SYSTEM_CONTROL_CLEAR_ERR;
+		cmd.buffer.registers[1].value =
+		    CPIA2_SYSTEM_CONTROL_HIGH_POWER;
+		break;
+	case CPIA2_CMD_SET_LOW_POWER:
+		cmd.req_mode =
+		    CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_SYSTEM;
+		cmd.reg_count = 1;
+		cmd.start = CPIA2_SYSTEM_SYSTEM_CONTROL;
+		cmd.buffer.block_data[0] = 0;
+		break;
+	case CPIA2_CMD_CLEAR_V2W_ERR:
+		cmd.req_mode =
+		    CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_SYSTEM;
+		cmd.reg_count = 1;
+		cmd.start = CPIA2_SYSTEM_SYSTEM_CONTROL;
+		cmd.buffer.block_data[0] = CPIA2_SYSTEM_CONTROL_CLEAR_ERR;
+		break;
+	case CPIA2_CMD_SET_USER_MODE:
+		cmd.buffer.block_data[0] = param;
+		/* fall through */
+	case CPIA2_CMD_GET_USER_MODE:
+		cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP;
+		cmd.reg_count = 1;
+		if (device == DEVICE_STV_672)
+			cmd.start = CPIA2_VP4_USER_MODE;
+		else
+			cmd.start = CPIA2_VP5_USER_MODE;
+		break;
+	case CPIA2_CMD_FRAMERATE_REQ:
+		cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP;
+		cmd.reg_count = 1;
+		if (device == DEVICE_STV_672)
+			cmd.start = CPIA2_VP4_FRAMERATE_REQUEST;
+		else
+			cmd.start = CPIA2_VP5_FRAMERATE_REQUEST;
+		cmd.buffer.block_data[0] = param;
+		break;
+	case CPIA2_CMD_SET_WAKEUP:
+		cmd.buffer.block_data[0] = param;
+		/* fall through */
+	case CPIA2_CMD_GET_WAKEUP:
+		cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VC;
+		cmd.reg_count = 1;
+		cmd.start = CPIA2_VC_WAKEUP;
+		break;
+	case CPIA2_CMD_SET_PW_CONTROL:
+		cmd.buffer.block_data[0] = param;
+		/* fall through */
+	case CPIA2_CMD_GET_PW_CONTROL:
+		cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VC;
+		cmd.reg_count = 1;
+		cmd.start = CPIA2_VC_PW_CTRL;
+		break;
+	case CPIA2_CMD_GET_VP_SYSTEM_STATE:
+		cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP;
+		cmd.reg_count = 1;
+		cmd.start = CPIA2_VP_SYSTEMSTATE;
+		break;
+	case CPIA2_CMD_SET_SYSTEM_CTRL:
+		cmd.buffer.block_data[0] = param;
+		/* fall through */
+	case CPIA2_CMD_GET_SYSTEM_CTRL:
+		cmd.req_mode =
+		    CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_SYSTEM;
+		cmd.reg_count = 1;
+		cmd.start = CPIA2_SYSTEM_SYSTEM_CONTROL;
+		break;
+	case CPIA2_CMD_SET_VP_SYSTEM_CTRL:
+		cmd.buffer.block_data[0] = param;
+		/* fall through */
+	case CPIA2_CMD_GET_VP_SYSTEM_CTRL:
+		cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP;
+		cmd.reg_count = 1;
+		cmd.start = CPIA2_VP_SYSTEMCTRL;
+		break;
+	case CPIA2_CMD_SET_VP_EXP_MODES:
+		cmd.buffer.block_data[0] = param;
+		/* fall through */
+	case CPIA2_CMD_GET_VP_EXP_MODES:
+		cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP;
+		cmd.reg_count = 1;
+		cmd.start = CPIA2_VP_EXPOSURE_MODES;
+		break;
+	case CPIA2_CMD_SET_DEVICE_CONFIG:
+		cmd.buffer.block_data[0] = param;
+		/* fall through */
+	case CPIA2_CMD_GET_DEVICE_CONFIG:
+		cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP;
+		cmd.reg_count = 1;
+		cmd.start = CPIA2_VP_DEVICE_CONFIG;
+		break;
+	case CPIA2_CMD_SET_SERIAL_ADDR:
+		cmd.buffer.block_data[0] = param;
+		cmd.req_mode =
+		    CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_SYSTEM;
+		cmd.reg_count = 1;
+		cmd.start = CPIA2_SYSTEM_VP_SERIAL_ADDR;
+		break;
+	case CPIA2_CMD_SET_SENSOR_CR1:
+		cmd.buffer.block_data[0] = param;
+		cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP;
+		cmd.reg_count = 1;
+		cmd.start = CPIA2_SENSOR_CR1;
+		break;
+	case CPIA2_CMD_SET_VC_CONTROL:
+		cmd.buffer.block_data[0] = param;
+		/* fall through */
+	case CPIA2_CMD_GET_VC_CONTROL:
+		cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VC;
+		cmd.reg_count = 1;
+		cmd.start = CPIA2_VC_VC_CTRL;
+		break;
+	case CPIA2_CMD_SET_TARGET_KB:
+		cmd.req_mode = CAMERAACCESS_TYPE_RANDOM | CAMERAACCESS_VC;
+		cmd.reg_count = 1;
+		cmd.buffer.registers[0].index = CPIA2_VC_VC_TARGET_KB;
+		cmd.buffer.registers[0].value = param;
+		break;
+	case CPIA2_CMD_SET_DEF_JPEG_OPT:
+		cmd.req_mode = CAMERAACCESS_TYPE_RANDOM | CAMERAACCESS_VC;
+		cmd.reg_count = 4;
+		cmd.buffer.registers[0].index = CPIA2_VC_VC_JPEG_OPT;
+		cmd.buffer.registers[0].value =
+		    CPIA2_VC_VC_JPEG_OPT_DOUBLE_SQUEEZE;
+		cmd.buffer.registers[1].index = CPIA2_VC_VC_USER_SQUEEZE;
+		cmd.buffer.registers[1].value = 20;
+		cmd.buffer.registers[2].index = CPIA2_VC_VC_CREEP_PERIOD;
+		cmd.buffer.registers[2].value = 2;
+		cmd.buffer.registers[3].index = CPIA2_VC_VC_JPEG_OPT;
+		cmd.buffer.registers[3].value = CPIA2_VC_VC_JPEG_OPT_DEFAULT;
+		break;
+	case CPIA2_CMD_REHASH_VP4:
+		cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP;
+		cmd.reg_count = 1;
+		cmd.start = CPIA2_VP_REHASH_VALUES;
+		cmd.buffer.block_data[0] = param;
+		break;
+	case CPIA2_CMD_SET_USER_EFFECTS:  /* Note: Be careful with this as
+					     this register can also affect
+					     flicker modes */
+		cmd.buffer.block_data[0] = param;
+		/* fall through */
+	case CPIA2_CMD_GET_USER_EFFECTS:
+		cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP;
+		cmd.reg_count = 1;
+		if (device == DEVICE_STV_672)
+			cmd.start = CPIA2_VP4_USER_EFFECTS;
+		else
+			cmd.start = CPIA2_VP5_USER_EFFECTS;
+		break;
+	default:
+		LOG("DoCommand received invalid command\n");
+		return -EINVAL;
+	}
+
+	retval = cpia2_send_command(cam, &cmd);
+	if (retval) {
+		return retval;
+	}
+
+	/***
+	 * Now copy any results from a read into the appropriate param struct.
+	 ***/
+	switch (command) {
+	case CPIA2_CMD_GET_VERSION:
+		cam->params.version.firmware_revision_hi =
+		    cmd.buffer.block_data[0];
+		cam->params.version.firmware_revision_lo =
+		    cmd.buffer.block_data[1];
+		break;
+	case CPIA2_CMD_GET_PNP_ID:
+		cam->params.pnp_id.vendor = (cmd.buffer.block_data[0] << 8) |
+					    cmd.buffer.block_data[1];
+		cam->params.pnp_id.product = (cmd.buffer.block_data[2] << 8) |
+					     cmd.buffer.block_data[3];
+		cam->params.pnp_id.device_revision =
+			(cmd.buffer.block_data[4] << 8) |
+			cmd.buffer.block_data[5];
+		if (cam->params.pnp_id.vendor == 0x553) {
+			if (cam->params.pnp_id.product == 0x100) {
+				cam->params.pnp_id.device_type = DEVICE_STV_672;
+			} else if (cam->params.pnp_id.product == 0x140 ||
+				   cam->params.pnp_id.product == 0x151) {
+				cam->params.pnp_id.device_type = DEVICE_STV_676;
+			}
+		}
+		break;
+	case CPIA2_CMD_GET_ASIC_TYPE:
+		cam->params.version.asic_id = cmd.buffer.block_data[0];
+		cam->params.version.asic_rev = cmd.buffer.block_data[1];
+		break;
+	case CPIA2_CMD_GET_SENSOR:
+		cam->params.version.sensor_flags = cmd.buffer.block_data[0];
+		cam->params.version.sensor_rev = cmd.buffer.block_data[1];
+		break;
+	case CPIA2_CMD_GET_VP_DEVICE:
+		cam->params.version.vp_device_hi = cmd.buffer.block_data[0];
+		cam->params.version.vp_device_lo = cmd.buffer.block_data[1];
+		break;
+	case CPIA2_CMD_GET_VP_GPIO_DATA:
+		cam->params.vp_params.gpio_data = cmd.buffer.block_data[0];
+		break;
+	case CPIA2_CMD_GET_VP_GPIO_DIRECTION:
+		cam->params.vp_params.gpio_direction = cmd.buffer.block_data[0];
+		break;
+	case CPIA2_CMD_GET_VC_MP_GPIO_DIRECTION:
+		cam->params.vc_params.vc_mp_direction =cmd.buffer.block_data[0];
+		break;
+	case CPIA2_CMD_GET_VC_MP_GPIO_DATA:
+		cam->params.vc_params.vc_mp_data = cmd.buffer.block_data[0];
+		break;
+	case CPIA2_CMD_GET_FLICKER_MODES:
+		cam->params.flicker_control.cam_register =
+			cmd.buffer.block_data[0];
+		break;
+	case CPIA2_CMD_GET_WAKEUP:
+		cam->params.vc_params.wakeup = cmd.buffer.block_data[0];
+		break;
+	case CPIA2_CMD_GET_PW_CONTROL:
+		cam->params.vc_params.pw_control = cmd.buffer.block_data[0];
+		break;
+	case CPIA2_CMD_GET_SYSTEM_CTRL:
+		cam->params.camera_state.system_ctrl = cmd.buffer.block_data[0];
+		break;
+	case CPIA2_CMD_GET_VP_SYSTEM_STATE:
+		cam->params.vp_params.system_state = cmd.buffer.block_data[0];
+		break;
+	case CPIA2_CMD_GET_VP_SYSTEM_CTRL:
+		cam->params.vp_params.system_ctrl = cmd.buffer.block_data[0];
+		break;
+	case CPIA2_CMD_GET_VP_EXP_MODES:
+		cam->params.vp_params.exposure_modes = cmd.buffer.block_data[0];
+		break;
+	case CPIA2_CMD_GET_DEVICE_CONFIG:
+		cam->params.vp_params.device_config = cmd.buffer.block_data[0];
+		break;
+	case CPIA2_CMD_GET_VC_CONTROL:
+		cam->params.vc_params.vc_control = cmd.buffer.block_data[0];
+		break;
+	case CPIA2_CMD_GET_USER_MODE:
+		cam->params.vp_params.video_mode = cmd.buffer.block_data[0];
+		break;
+	case CPIA2_CMD_GET_USER_EFFECTS:
+		cam->params.vp_params.user_effects = cmd.buffer.block_data[0];
+		break;
+	default:
+		break;
+	}
+	return retval;
+}
+
+/******************************************************************************
+ *
+ *  cpia2_send_command
+ *
+ *****************************************************************************/
+
+#define DIR(cmd) ((cmd->direction == TRANSFER_WRITE) ? "Write" : "Read")
+#define BINDEX(cmd) (cmd->req_mode & 0x03)
+
+int cpia2_send_command(struct camera_data *cam, struct cpia2_command *cmd)
+{
+	u8 count;
+	u8 start;
+	u8 *buffer;
+	int retval;
+
+	switch (cmd->req_mode & 0x0c) {
+	case CAMERAACCESS_TYPE_RANDOM:
+		count = cmd->reg_count * sizeof(struct cpia2_register);
+		start = 0;
+		buffer = (u8 *) & cmd->buffer;
+		if (debugs_on & DEBUG_REG)
+			DBG("%s Random: Register block %s\n", DIR(cmd),
+			    block_name[BINDEX(cmd)]);
+		break;
+	case CAMERAACCESS_TYPE_BLOCK:
+		count = cmd->reg_count;
+		start = cmd->start;
+		buffer = cmd->buffer.block_data;
+		if (debugs_on & DEBUG_REG)
+			DBG("%s Block: Register block %s\n", DIR(cmd),
+			    block_name[BINDEX(cmd)]);
+		break;
+	case CAMERAACCESS_TYPE_MASK:
+		count = cmd->reg_count * sizeof(struct cpia2_reg_mask);
+		start = 0;
+		buffer = (u8 *) & cmd->buffer;
+		if (debugs_on & DEBUG_REG)
+			DBG("%s Mask: Register block %s\n", DIR(cmd),
+			    block_name[BINDEX(cmd)]);
+		break;
+	case CAMERAACCESS_TYPE_REPEAT:	/* For patch blocks only */
+		count = cmd->reg_count;
+		start = cmd->start;
+		buffer = cmd->buffer.block_data;
+		if (debugs_on & DEBUG_REG)
+			DBG("%s Repeat: Register block %s\n", DIR(cmd),
+			    block_name[BINDEX(cmd)]);
+		break;
+	default:
+		LOG("%s: invalid request mode\n",__func__);
+		return -EINVAL;
+	}
+
+	retval = cpia2_usb_transfer_cmd(cam,
+					buffer,
+					cmd->req_mode,
+					start, count, cmd->direction);
+#ifdef _CPIA2_DEBUG_
+	if (debugs_on & DEBUG_REG) {
+		int i;
+		for (i = 0; i < cmd->reg_count; i++) {
+			if((cmd->req_mode & 0x0c) == CAMERAACCESS_TYPE_BLOCK)
+				KINFO("%s Block: [0x%02X] = 0x%02X\n",
+				    DIR(cmd), start + i, buffer[i]);
+			if((cmd->req_mode & 0x0c) == CAMERAACCESS_TYPE_RANDOM)
+				KINFO("%s Random: [0x%02X] = 0x%02X\n",
+				    DIR(cmd), cmd->buffer.registers[i].index,
+				    cmd->buffer.registers[i].value);
+		}
+	}
+#endif
+
+	return retval;
+};
+
+/*************
+ * Functions to implement camera functionality
+ *************/
+/******************************************************************************
+ *
+ *  cpia2_get_version_info
+ *
+ *****************************************************************************/
+static void cpia2_get_version_info(struct camera_data *cam)
+{
+	cpia2_do_command(cam, CPIA2_CMD_GET_VERSION, TRANSFER_READ, 0);
+	cpia2_do_command(cam, CPIA2_CMD_GET_PNP_ID, TRANSFER_READ, 0);
+	cpia2_do_command(cam, CPIA2_CMD_GET_ASIC_TYPE, TRANSFER_READ, 0);
+	cpia2_do_command(cam, CPIA2_CMD_GET_SENSOR, TRANSFER_READ, 0);
+	cpia2_do_command(cam, CPIA2_CMD_GET_VP_DEVICE, TRANSFER_READ, 0);
+}
+
+/******************************************************************************
+ *
+ *  cpia2_reset_camera
+ *
+ *  Called at least during the open process, sets up initial params.
+ *****************************************************************************/
+int cpia2_reset_camera(struct camera_data *cam)
+{
+	u8 tmp_reg;
+	int retval = 0;
+	int target_kb;
+	int i;
+	struct cpia2_command cmd;
+
+	/***
+	 * VC setup
+	 ***/
+	retval = configure_sensor(cam,
+				  cam->params.roi.width,
+				  cam->params.roi.height);
+	if (retval < 0) {
+		ERR("Couldn't configure sensor, error=%d\n", retval);
+		return retval;
+	}
+
+	/* Clear FIFO and route/enable stream block */
+	cmd.req_mode = CAMERAACCESS_TYPE_RANDOM | CAMERAACCESS_VC;
+	cmd.direction = TRANSFER_WRITE;
+	cmd.reg_count = 2;
+	cmd.buffer.registers[0].index = CPIA2_VC_ST_CTRL;
+	cmd.buffer.registers[0].value = CPIA2_VC_ST_CTRL_SRC_VC |
+		CPIA2_VC_ST_CTRL_DST_USB | CPIA2_VC_ST_CTRL_EOF_DETECT;
+	cmd.buffer.registers[1].index = CPIA2_VC_ST_CTRL;
+	cmd.buffer.registers[1].value = CPIA2_VC_ST_CTRL_SRC_VC |
+		CPIA2_VC_ST_CTRL_DST_USB |
+		CPIA2_VC_ST_CTRL_EOF_DETECT | CPIA2_VC_ST_CTRL_FIFO_ENABLE;
+
+	cpia2_send_command(cam, &cmd);
+
+	cpia2_set_high_power(cam);
+
+	if (cam->params.pnp_id.device_type == DEVICE_STV_672) {
+		/* Enable button notification */
+		cmd.req_mode = CAMERAACCESS_TYPE_RANDOM | CAMERAACCESS_SYSTEM;
+		cmd.buffer.registers[0].index = CPIA2_SYSTEM_INT_PACKET_CTRL;
+		cmd.buffer.registers[0].value =
+			CPIA2_SYSTEM_INT_PACKET_CTRL_ENABLE_SW_XX;
+		cmd.reg_count = 1;
+		cpia2_send_command(cam, &cmd);
+	}
+
+	schedule_timeout_interruptible(msecs_to_jiffies(100));
+
+	if (cam->params.pnp_id.device_type == DEVICE_STV_672)
+		retval = apply_vp_patch(cam);
+
+	/* wait for vp to go to sleep */
+	schedule_timeout_interruptible(msecs_to_jiffies(100));
+
+	/***
+	 * If this is a 676, apply VP5 fixes before we start streaming
+	 ***/
+	if (cam->params.pnp_id.device_type == DEVICE_STV_676) {
+		cmd.req_mode = CAMERAACCESS_TYPE_RANDOM | CAMERAACCESS_VP;
+
+		/* The following writes improve the picture */
+		cmd.buffer.registers[0].index = CPIA2_VP5_MYBLACK_LEVEL;
+		cmd.buffer.registers[0].value = 0; /* reduce from the default
+						    * rec 601 pedestal of 16 */
+		cmd.buffer.registers[1].index = CPIA2_VP5_MCYRANGE;
+		cmd.buffer.registers[1].value = 0x92; /* increase from 100% to
+						       * (256/256 - 31) to fill
+						       * available range */
+		cmd.buffer.registers[2].index = CPIA2_VP5_MYCEILING;
+		cmd.buffer.registers[2].value = 0xFF; /* Increase from the
+						       * default rec 601 ceiling
+						       * of 240 */
+		cmd.buffer.registers[3].index = CPIA2_VP5_MCUVSATURATION;
+		cmd.buffer.registers[3].value = 0xFF; /* Increase from the rec
+						       * 601 100% level (128)
+						       * to 145-192 */
+		cmd.buffer.registers[4].index = CPIA2_VP5_ANTIFLKRSETUP;
+		cmd.buffer.registers[4].value = 0x80;  /* Inhibit the
+							* anti-flicker */
+
+		/* The following 4 writes are a fix to allow QVGA to work at 30 fps */
+		cmd.buffer.registers[5].index = CPIA2_VP_RAM_ADDR_H;
+		cmd.buffer.registers[5].value = 0x01;
+		cmd.buffer.registers[6].index = CPIA2_VP_RAM_ADDR_L;
+		cmd.buffer.registers[6].value = 0xE3;
+		cmd.buffer.registers[7].index = CPIA2_VP_RAM_DATA;
+		cmd.buffer.registers[7].value = 0x02;
+		cmd.buffer.registers[8].index = CPIA2_VP_RAM_DATA;
+		cmd.buffer.registers[8].value = 0xFC;
+
+		cmd.direction = TRANSFER_WRITE;
+		cmd.reg_count = 9;
+
+		cpia2_send_command(cam, &cmd);
+	}
+
+	/* Activate all settings and start the data stream */
+	/* Set user mode */
+	set_default_user_mode(cam);
+
+	/* Give VP time to wake up */
+	schedule_timeout_interruptible(msecs_to_jiffies(100));
+
+	set_all_properties(cam);
+
+	cpia2_do_command(cam, CPIA2_CMD_GET_USER_MODE, TRANSFER_READ, 0);
+	DBG("After SetAllProperties(cam), user mode is 0x%0X\n",
+	    cam->params.vp_params.video_mode);
+
+	/***
+	 * Set audio regulator off.  This and the code to set the compresison
+	 * state are too complex to form a CPIA2_CMD_, and seem to be somewhat
+	 * intertwined.  This stuff came straight from the windows driver.
+	 ***/
+	/* Turn AutoExposure off in VP and enable the serial bridge to the sensor */
+	cpia2_do_command(cam, CPIA2_CMD_GET_VP_SYSTEM_CTRL, TRANSFER_READ, 0);
+	tmp_reg = cam->params.vp_params.system_ctrl;
+	cmd.buffer.registers[0].value = tmp_reg &
+		(tmp_reg & (CPIA2_VP_SYSTEMCTRL_HK_CONTROL ^ 0xFF));
+
+	cpia2_do_command(cam, CPIA2_CMD_GET_DEVICE_CONFIG, TRANSFER_READ, 0);
+	cmd.buffer.registers[1].value = cam->params.vp_params.device_config |
+					CPIA2_VP_DEVICE_CONFIG_SERIAL_BRIDGE;
+	cmd.buffer.registers[0].index = CPIA2_VP_SYSTEMCTRL;
+	cmd.buffer.registers[1].index = CPIA2_VP_DEVICE_CONFIG;
+	cmd.req_mode = CAMERAACCESS_TYPE_RANDOM | CAMERAACCESS_VP;
+	cmd.reg_count = 2;
+	cmd.direction = TRANSFER_WRITE;
+	cmd.start = 0;
+	cpia2_send_command(cam, &cmd);
+
+	/* Set the correct I2C address in the CPiA-2 system register */
+	cpia2_do_command(cam,
+			 CPIA2_CMD_SET_SERIAL_ADDR,
+			 TRANSFER_WRITE,
+			 CPIA2_SYSTEM_VP_SERIAL_ADDR_SENSOR);
+
+	/* Now have sensor access - set bit to turn the audio regulator off */
+	cpia2_do_command(cam,
+			 CPIA2_CMD_SET_SENSOR_CR1,
+			 TRANSFER_WRITE, CPIA2_SENSOR_CR1_DOWN_AUDIO_REGULATOR);
+
+	/* Set the correct I2C address in the CPiA-2 system register */
+	if (cam->params.pnp_id.device_type == DEVICE_STV_672)
+		cpia2_do_command(cam,
+				 CPIA2_CMD_SET_SERIAL_ADDR,
+				 TRANSFER_WRITE,
+				 CPIA2_SYSTEM_VP_SERIAL_ADDR_VP); // 0x88
+	else
+		cpia2_do_command(cam,
+				 CPIA2_CMD_SET_SERIAL_ADDR,
+				 TRANSFER_WRITE,
+				 CPIA2_SYSTEM_VP_SERIAL_ADDR_676_VP); // 0x8a
+
+	/* increase signal drive strength */
+	if (cam->params.pnp_id.device_type == DEVICE_STV_676)
+		cpia2_do_command(cam,
+				 CPIA2_CMD_SET_VP_EXP_MODES,
+				 TRANSFER_WRITE,
+				 CPIA2_VP_EXPOSURE_MODES_COMPILE_EXP);
+
+	/* Start autoexposure */
+	cpia2_do_command(cam, CPIA2_CMD_GET_DEVICE_CONFIG, TRANSFER_READ, 0);
+	cmd.buffer.registers[0].value = cam->params.vp_params.device_config &
+				  (CPIA2_VP_DEVICE_CONFIG_SERIAL_BRIDGE ^ 0xFF);
+
+	cpia2_do_command(cam, CPIA2_CMD_GET_VP_SYSTEM_CTRL, TRANSFER_READ, 0);
+	cmd.buffer.registers[1].value =
+	    cam->params.vp_params.system_ctrl | CPIA2_VP_SYSTEMCTRL_HK_CONTROL;
+
+	cmd.buffer.registers[0].index = CPIA2_VP_DEVICE_CONFIG;
+	cmd.buffer.registers[1].index = CPIA2_VP_SYSTEMCTRL;
+	cmd.req_mode = CAMERAACCESS_TYPE_RANDOM | CAMERAACCESS_VP;
+	cmd.reg_count = 2;
+	cmd.direction = TRANSFER_WRITE;
+
+	cpia2_send_command(cam, &cmd);
+
+	/* Set compression state */
+	cpia2_do_command(cam, CPIA2_CMD_GET_VC_CONTROL, TRANSFER_READ, 0);
+	if (cam->params.compression.inhibit_htables) {
+		tmp_reg = cam->params.vc_params.vc_control |
+			  CPIA2_VC_VC_CTRL_INHIBIT_H_TABLES;
+	} else  {
+		tmp_reg = cam->params.vc_params.vc_control &
+			  ~CPIA2_VC_VC_CTRL_INHIBIT_H_TABLES;
+	}
+	cpia2_do_command(cam, CPIA2_CMD_SET_VC_CONTROL, TRANSFER_WRITE,tmp_reg);
+
+	/* Set target size (kb) on vc
+	   This is a heuristic based on the quality parameter and the raw
+	   framesize in kB divided by 16 (the compression factor when the
+	   quality is 100%) */
+	target_kb = (cam->width * cam->height * 2 / 16384) *
+				cam->params.vc_params.quality / 100;
+	if (target_kb < 1)
+		target_kb = 1;
+	cpia2_do_command(cam, CPIA2_CMD_SET_TARGET_KB,
+			 TRANSFER_WRITE, target_kb);
+
+	/* Wiggle VC Reset */
+	/***
+	 * First read and wait a bit.
+	 ***/
+	for (i = 0; i < 50; i++) {
+		cpia2_do_command(cam, CPIA2_CMD_GET_PW_CONTROL,
+				 TRANSFER_READ, 0);
+	}
+
+	tmp_reg = cam->params.vc_params.pw_control;
+	tmp_reg &= ~CPIA2_VC_PW_CTRL_VC_RESET_N;
+
+	cpia2_do_command(cam, CPIA2_CMD_SET_PW_CONTROL, TRANSFER_WRITE,tmp_reg);
+
+	tmp_reg |= CPIA2_VC_PW_CTRL_VC_RESET_N;
+	cpia2_do_command(cam, CPIA2_CMD_SET_PW_CONTROL, TRANSFER_WRITE,tmp_reg);
+
+	cpia2_do_command(cam, CPIA2_CMD_SET_DEF_JPEG_OPT, TRANSFER_WRITE, 0);
+
+	cpia2_do_command(cam, CPIA2_CMD_GET_USER_MODE, TRANSFER_READ, 0);
+	DBG("After VC RESET, user mode is 0x%0X\n",
+	    cam->params.vp_params.video_mode);
+
+	return retval;
+}
+
+/******************************************************************************
+ *
+ *  cpia2_set_high_power
+ *
+ *****************************************************************************/
+static int cpia2_set_high_power(struct camera_data *cam)
+{
+	int i;
+	for (i = 0; i <= 50; i++) {
+		/* Read system status */
+		cpia2_do_command(cam,CPIA2_CMD_GET_SYSTEM_CTRL,TRANSFER_READ,0);
+
+		/* If there is an error, clear it */
+		if(cam->params.camera_state.system_ctrl &
+		   CPIA2_SYSTEM_CONTROL_V2W_ERR)
+			cpia2_do_command(cam, CPIA2_CMD_CLEAR_V2W_ERR,
+					 TRANSFER_WRITE, 0);
+
+		/* Try to set high power mode */
+		cpia2_do_command(cam, CPIA2_CMD_SET_SYSTEM_CTRL,
+				 TRANSFER_WRITE, 1);
+
+		/* Try to read something in VP to check if everything is awake */
+		cpia2_do_command(cam, CPIA2_CMD_GET_VP_SYSTEM_STATE,
+				 TRANSFER_READ, 0);
+		if (cam->params.vp_params.system_state &
+		    CPIA2_VP_SYSTEMSTATE_HK_ALIVE) {
+			break;
+		} else if (i == 50) {
+			cam->params.camera_state.power_mode = LO_POWER_MODE;
+			ERR("Camera did not wake up\n");
+			return -EIO;
+		}
+	}
+
+	DBG("System now in high power state\n");
+	cam->params.camera_state.power_mode = HI_POWER_MODE;
+	return 0;
+}
+
+/******************************************************************************
+ *
+ *  cpia2_set_low_power
+ *
+ *****************************************************************************/
+int cpia2_set_low_power(struct camera_data *cam)
+{
+	cam->params.camera_state.power_mode = LO_POWER_MODE;
+	cpia2_do_command(cam, CPIA2_CMD_SET_SYSTEM_CTRL, TRANSFER_WRITE, 0);
+	return 0;
+}
+
+/******************************************************************************
+ *
+ *  apply_vp_patch
+ *
+ *****************************************************************************/
+static int cpia2_send_onebyte_command(struct camera_data *cam,
+				      struct cpia2_command *cmd,
+				      u8 start, u8 datum)
+{
+	cmd->buffer.block_data[0] = datum;
+	cmd->start = start;
+	cmd->reg_count = 1;
+	return cpia2_send_command(cam, cmd);
+}
+
+static int apply_vp_patch(struct camera_data *cam)
+{
+	const struct firmware *fw;
+	const char fw_name[] = FIRMWARE;
+	int i, ret;
+	struct cpia2_command cmd;
+
+	ret = request_firmware(&fw, fw_name, &cam->dev->dev);
+	if (ret) {
+		printk(KERN_ERR "cpia2: failed to load VP patch \"%s\"\n",
+		       fw_name);
+		return ret;
+	}
+
+	cmd.req_mode = CAMERAACCESS_TYPE_REPEAT | CAMERAACCESS_VP;
+	cmd.direction = TRANSFER_WRITE;
+
+	/* First send the start address... */
+	cpia2_send_onebyte_command(cam, &cmd, 0x0A, fw->data[0]); /* hi */
+	cpia2_send_onebyte_command(cam, &cmd, 0x0B, fw->data[1]); /* lo */
+
+	/* ... followed by the data payload */
+	for (i = 2; i < fw->size; i += 64) {
+		cmd.start = 0x0C; /* Data */
+		cmd.reg_count = min_t(uint, 64, fw->size - i);
+		memcpy(cmd.buffer.block_data, &fw->data[i], cmd.reg_count);
+		cpia2_send_command(cam, &cmd);
+	}
+
+	/* Next send the start address... */
+	cpia2_send_onebyte_command(cam, &cmd, 0x0A, fw->data[0]); /* hi */
+	cpia2_send_onebyte_command(cam, &cmd, 0x0B, fw->data[1]); /* lo */
+
+	/* ... followed by the 'goto' command */
+	cpia2_send_onebyte_command(cam, &cmd, 0x0D, 1);
+
+	release_firmware(fw);
+	return 0;
+}
+
+/******************************************************************************
+ *
+ *  set_default_user_mode
+ *
+ *****************************************************************************/
+static int set_default_user_mode(struct camera_data *cam)
+{
+	unsigned char user_mode;
+	unsigned char frame_rate;
+	int width = cam->params.roi.width;
+	int height = cam->params.roi.height;
+
+	switch (cam->params.version.sensor_flags) {
+	case CPIA2_VP_SENSOR_FLAGS_404:
+	case CPIA2_VP_SENSOR_FLAGS_407:
+	case CPIA2_VP_SENSOR_FLAGS_409:
+	case CPIA2_VP_SENSOR_FLAGS_410:
+		if ((width > STV_IMAGE_QCIF_COLS)
+		    || (height > STV_IMAGE_QCIF_ROWS)) {
+			user_mode = CPIA2_VP_USER_MODE_CIF;
+		} else {
+			user_mode = CPIA2_VP_USER_MODE_QCIFDS;
+		}
+		frame_rate = CPIA2_VP_FRAMERATE_30;
+		break;
+	case CPIA2_VP_SENSOR_FLAGS_500:
+		if ((width > STV_IMAGE_CIF_COLS)
+		    || (height > STV_IMAGE_CIF_ROWS)) {
+			user_mode = CPIA2_VP_USER_MODE_VGA;
+		} else {
+			user_mode = CPIA2_VP_USER_MODE_QVGADS;
+		}
+		if (cam->params.pnp_id.device_type == DEVICE_STV_672)
+			frame_rate = CPIA2_VP_FRAMERATE_15;
+		else
+			frame_rate = CPIA2_VP_FRAMERATE_30;
+		break;
+	default:
+		LOG("%s: Invalid sensor flag value 0x%0X\n",__func__,
+		    cam->params.version.sensor_flags);
+		return -EINVAL;
+	}
+
+	DBG("Sensor flag = 0x%0x, user mode = 0x%0x, frame rate = 0x%X\n",
+	    cam->params.version.sensor_flags, user_mode, frame_rate);
+	cpia2_do_command(cam, CPIA2_CMD_SET_USER_MODE, TRANSFER_WRITE,
+			 user_mode);
+	if(cam->params.vp_params.frame_rate > 0 &&
+	   frame_rate > cam->params.vp_params.frame_rate)
+		frame_rate = cam->params.vp_params.frame_rate;
+
+	cpia2_set_fps(cam, frame_rate);
+
+//	if (cam->params.pnp_id.device_type == DEVICE_STV_676)
+//		cpia2_do_command(cam,
+//				 CPIA2_CMD_SET_VP_SYSTEM_CTRL,
+//				 TRANSFER_WRITE,
+//				 CPIA2_VP_SYSTEMCTRL_HK_CONTROL |
+//				 CPIA2_VP_SYSTEMCTRL_POWER_CONTROL);
+
+	return 0;
+}
+
+/******************************************************************************
+ *
+ *  cpia2_match_video_size
+ *
+ *  return the best match, where 'best' is as always
+ *  the largest that is not bigger than what is requested.
+ *****************************************************************************/
+int cpia2_match_video_size(int width, int height)
+{
+	if (width >= STV_IMAGE_VGA_COLS && height >= STV_IMAGE_VGA_ROWS)
+		return VIDEOSIZE_VGA;
+
+	if (width >= STV_IMAGE_CIF_COLS && height >= STV_IMAGE_CIF_ROWS)
+		return VIDEOSIZE_CIF;
+
+	if (width >= STV_IMAGE_QVGA_COLS && height >= STV_IMAGE_QVGA_ROWS)
+		return VIDEOSIZE_QVGA;
+
+	if (width >= 288 && height >= 216)
+		return VIDEOSIZE_288_216;
+
+	if (width >= 256 && height >= 192)
+		return VIDEOSIZE_256_192;
+
+	if (width >= 224 && height >= 168)
+		return VIDEOSIZE_224_168;
+
+	if (width >= 192 && height >= 144)
+		return VIDEOSIZE_192_144;
+
+	if (width >= STV_IMAGE_QCIF_COLS && height >= STV_IMAGE_QCIF_ROWS)
+		return VIDEOSIZE_QCIF;
+
+	return -1;
+}
+
+/******************************************************************************
+ *
+ *  SetVideoSize
+ *
+ *****************************************************************************/
+static int set_vw_size(struct camera_data *cam, int size)
+{
+	int retval = 0;
+
+	cam->params.vp_params.video_size = size;
+
+	switch (size) {
+	case VIDEOSIZE_VGA:
+		DBG("Setting size to VGA\n");
+		cam->params.roi.width = STV_IMAGE_VGA_COLS;
+		cam->params.roi.height = STV_IMAGE_VGA_ROWS;
+		cam->width = STV_IMAGE_VGA_COLS;
+		cam->height = STV_IMAGE_VGA_ROWS;
+		break;
+	case VIDEOSIZE_CIF:
+		DBG("Setting size to CIF\n");
+		cam->params.roi.width = STV_IMAGE_CIF_COLS;
+		cam->params.roi.height = STV_IMAGE_CIF_ROWS;
+		cam->width = STV_IMAGE_CIF_COLS;
+		cam->height = STV_IMAGE_CIF_ROWS;
+		break;
+	case VIDEOSIZE_QVGA:
+		DBG("Setting size to QVGA\n");
+		cam->params.roi.width = STV_IMAGE_QVGA_COLS;
+		cam->params.roi.height = STV_IMAGE_QVGA_ROWS;
+		cam->width = STV_IMAGE_QVGA_COLS;
+		cam->height = STV_IMAGE_QVGA_ROWS;
+		break;
+	case VIDEOSIZE_288_216:
+		cam->params.roi.width = 288;
+		cam->params.roi.height = 216;
+		cam->width = 288;
+		cam->height = 216;
+		break;
+	case VIDEOSIZE_256_192:
+		cam->width = 256;
+		cam->height = 192;
+		cam->params.roi.width = 256;
+		cam->params.roi.height = 192;
+		break;
+	case VIDEOSIZE_224_168:
+		cam->width = 224;
+		cam->height = 168;
+		cam->params.roi.width = 224;
+		cam->params.roi.height = 168;
+		break;
+	case VIDEOSIZE_192_144:
+		cam->width = 192;
+		cam->height = 144;
+		cam->params.roi.width = 192;
+		cam->params.roi.height = 144;
+		break;
+	case VIDEOSIZE_QCIF:
+		DBG("Setting size to QCIF\n");
+		cam->params.roi.width = STV_IMAGE_QCIF_COLS;
+		cam->params.roi.height = STV_IMAGE_QCIF_ROWS;
+		cam->width = STV_IMAGE_QCIF_COLS;
+		cam->height = STV_IMAGE_QCIF_ROWS;
+		break;
+	default:
+		retval = -EINVAL;
+	}
+	return retval;
+}
+
+/******************************************************************************
+ *
+ *  configure_sensor
+ *
+ *****************************************************************************/
+static int configure_sensor(struct camera_data *cam,
+			    int req_width, int req_height)
+{
+	int retval;
+
+	switch (cam->params.version.sensor_flags) {
+	case CPIA2_VP_SENSOR_FLAGS_404:
+	case CPIA2_VP_SENSOR_FLAGS_407:
+	case CPIA2_VP_SENSOR_FLAGS_409:
+	case CPIA2_VP_SENSOR_FLAGS_410:
+		retval = config_sensor_410(cam, req_width, req_height);
+		break;
+	case CPIA2_VP_SENSOR_FLAGS_500:
+		retval = config_sensor_500(cam, req_width, req_height);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return retval;
+}
+
+/******************************************************************************
+ *
+ *  config_sensor_410
+ *
+ *****************************************************************************/
+static int config_sensor_410(struct camera_data *cam,
+			    int req_width, int req_height)
+{
+	struct cpia2_command cmd;
+	int i = 0;
+	int image_size;
+	int image_type;
+	int width = req_width;
+	int height = req_height;
+
+	/***
+	 *  Make sure size doesn't exceed CIF.
+	 ***/
+	if (width > STV_IMAGE_CIF_COLS)
+		width = STV_IMAGE_CIF_COLS;
+	if (height > STV_IMAGE_CIF_ROWS)
+		height = STV_IMAGE_CIF_ROWS;
+
+	image_size = cpia2_match_video_size(width, height);
+
+	DBG("Config 410: width = %d, height = %d\n", width, height);
+	DBG("Image size returned is %d\n", image_size);
+	if (image_size >= 0) {
+		set_vw_size(cam, image_size);
+		width = cam->params.roi.width;
+		height = cam->params.roi.height;
+
+		DBG("After set_vw_size(), width = %d, height = %d\n",
+		    width, height);
+		if (width <= 176 && height <= 144) {
+			DBG("image type = VIDEOSIZE_QCIF\n");
+			image_type = VIDEOSIZE_QCIF;
+		}
+		else if (width <= 320 && height <= 240) {
+			DBG("image type = VIDEOSIZE_QVGA\n");
+			image_type = VIDEOSIZE_QVGA;
+		}
+		else {
+			DBG("image type = VIDEOSIZE_CIF\n");
+			image_type = VIDEOSIZE_CIF;
+		}
+	} else {
+		ERR("ConfigSensor410 failed\n");
+		return -EINVAL;
+	}
+
+	cmd.req_mode = CAMERAACCESS_TYPE_RANDOM | CAMERAACCESS_VC;
+	cmd.direction = TRANSFER_WRITE;
+
+	/* VC Format */
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_FORMAT;
+	if (image_type == VIDEOSIZE_CIF) {
+		cmd.buffer.registers[i++].value =
+		    (u8) (CPIA2_VC_VC_FORMAT_UFIRST |
+			  CPIA2_VC_VC_FORMAT_SHORTLINE);
+	} else {
+		cmd.buffer.registers[i++].value =
+		    (u8) CPIA2_VC_VC_FORMAT_UFIRST;
+	}
+
+	/* VC Clocks */
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_CLOCKS;
+	if (image_type == VIDEOSIZE_QCIF) {
+		if (cam->params.pnp_id.device_type == DEVICE_STV_672) {
+			cmd.buffer.registers[i++].value=
+				(u8)(CPIA2_VC_VC_672_CLOCKS_CIF_DIV_BY_3 |
+				     CPIA2_VC_VC_672_CLOCKS_SCALING |
+				     CPIA2_VC_VC_CLOCKS_LOGDIV2);
+			DBG("VC_Clocks (0xc4) should be B\n");
+		}
+		else {
+			cmd.buffer.registers[i++].value=
+				(u8)(CPIA2_VC_VC_676_CLOCKS_CIF_DIV_BY_3 |
+				     CPIA2_VC_VC_CLOCKS_LOGDIV2);
+		}
+	} else {
+		if (cam->params.pnp_id.device_type == DEVICE_STV_672) {
+			cmd.buffer.registers[i++].value =
+			   (u8) (CPIA2_VC_VC_672_CLOCKS_CIF_DIV_BY_3 |
+				 CPIA2_VC_VC_CLOCKS_LOGDIV0);
+		}
+		else {
+			cmd.buffer.registers[i++].value =
+			   (u8) (CPIA2_VC_VC_676_CLOCKS_CIF_DIV_BY_3 |
+				 CPIA2_VC_VC_676_CLOCKS_SCALING |
+				 CPIA2_VC_VC_CLOCKS_LOGDIV0);
+		}
+	}
+	DBG("VC_Clocks (0xc4) = 0x%0X\n", cmd.buffer.registers[i-1].value);
+
+	/* Input reqWidth from VC */
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_IHSIZE_LO;
+	if (image_type == VIDEOSIZE_QCIF)
+		cmd.buffer.registers[i++].value =
+		    (u8) (STV_IMAGE_QCIF_COLS / 4);
+	else
+		cmd.buffer.registers[i++].value =
+		    (u8) (STV_IMAGE_CIF_COLS / 4);
+
+	/* Timings */
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_XLIM_HI;
+	if (image_type == VIDEOSIZE_QCIF)
+		cmd.buffer.registers[i++].value = (u8) 0;
+	else
+		cmd.buffer.registers[i++].value = (u8) 1;
+
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_XLIM_LO;
+	if (image_type == VIDEOSIZE_QCIF)
+		cmd.buffer.registers[i++].value = (u8) 208;
+	else
+		cmd.buffer.registers[i++].value = (u8) 160;
+
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_YLIM_HI;
+	if (image_type == VIDEOSIZE_QCIF)
+		cmd.buffer.registers[i++].value = (u8) 0;
+	else
+		cmd.buffer.registers[i++].value = (u8) 1;
+
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_YLIM_LO;
+	if (image_type == VIDEOSIZE_QCIF)
+		cmd.buffer.registers[i++].value = (u8) 160;
+	else
+		cmd.buffer.registers[i++].value = (u8) 64;
+
+	/* Output Image Size */
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_OHSIZE;
+	cmd.buffer.registers[i++].value = cam->params.roi.width / 4;
+
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_OVSIZE;
+	cmd.buffer.registers[i++].value = cam->params.roi.height / 4;
+
+	/* Cropping */
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_HCROP;
+	if (image_type == VIDEOSIZE_QCIF)
+		cmd.buffer.registers[i++].value =
+		    (u8) (((STV_IMAGE_QCIF_COLS / 4) - (width / 4)) / 2);
+	else
+		cmd.buffer.registers[i++].value =
+		    (u8) (((STV_IMAGE_CIF_COLS / 4) - (width / 4)) / 2);
+
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_VCROP;
+	if (image_type == VIDEOSIZE_QCIF)
+		cmd.buffer.registers[i++].value =
+		    (u8) (((STV_IMAGE_QCIF_ROWS / 4) - (height / 4)) / 2);
+	else
+		cmd.buffer.registers[i++].value =
+		    (u8) (((STV_IMAGE_CIF_ROWS / 4) - (height / 4)) / 2);
+
+	/* Scaling registers (defaults) */
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_HPHASE;
+	cmd.buffer.registers[i++].value = (u8) 0;
+
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_VPHASE;
+	cmd.buffer.registers[i++].value = (u8) 0;
+
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_HISPAN;
+	cmd.buffer.registers[i++].value = (u8) 31;
+
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_VISPAN;
+	cmd.buffer.registers[i++].value = (u8) 31;
+
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_HICROP;
+	cmd.buffer.registers[i++].value = (u8) 0;
+
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_VICROP;
+	cmd.buffer.registers[i++].value = (u8) 0;
+
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_HFRACT;
+	cmd.buffer.registers[i++].value = (u8) 0x81;	/* = 8/1 = 8 (HIBYTE/LOBYTE) */
+
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_VFRACT;
+	cmd.buffer.registers[i++].value = (u8) 0x81;	/* = 8/1 = 8 (HIBYTE/LOBYTE) */
+
+	cmd.reg_count = i;
+
+	cpia2_send_command(cam, &cmd);
+
+	return i;
+}
+
+
+/******************************************************************************
+ *
+ *  config_sensor_500(cam)
+ *
+ *****************************************************************************/
+static int config_sensor_500(struct camera_data *cam,
+			     int req_width, int req_height)
+{
+	struct cpia2_command cmd;
+	int i = 0;
+	int image_size = VIDEOSIZE_CIF;
+	int image_type = VIDEOSIZE_VGA;
+	int width = req_width;
+	int height = req_height;
+	unsigned int device = cam->params.pnp_id.device_type;
+
+	image_size = cpia2_match_video_size(width, height);
+
+	if (width > STV_IMAGE_CIF_COLS || height > STV_IMAGE_CIF_ROWS)
+		image_type = VIDEOSIZE_VGA;
+	else if (width > STV_IMAGE_QVGA_COLS || height > STV_IMAGE_QVGA_ROWS)
+		image_type = VIDEOSIZE_CIF;
+	else if (width > STV_IMAGE_QCIF_COLS || height > STV_IMAGE_QCIF_ROWS)
+		image_type = VIDEOSIZE_QVGA;
+	else
+		image_type = VIDEOSIZE_QCIF;
+
+	if (image_size >= 0) {
+		set_vw_size(cam, image_size);
+		width = cam->params.roi.width;
+		height = cam->params.roi.height;
+	} else {
+		ERR("ConfigSensor500 failed\n");
+		return -EINVAL;
+	}
+
+	DBG("image_size = %d, width = %d, height = %d, type = %d\n",
+	    image_size, width, height, image_type);
+
+	cmd.req_mode = CAMERAACCESS_TYPE_RANDOM | CAMERAACCESS_VC;
+	cmd.direction = TRANSFER_WRITE;
+	i = 0;
+
+	/* VC Format */
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_FORMAT;
+	cmd.buffer.registers[i].value = (u8) CPIA2_VC_VC_FORMAT_UFIRST;
+	if (image_type == VIDEOSIZE_QCIF)
+		cmd.buffer.registers[i].value |= (u8) CPIA2_VC_VC_FORMAT_DECIMATING;
+	i++;
+
+	/* VC Clocks */
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_CLOCKS;
+	if (device == DEVICE_STV_672) {
+		if (image_type == VIDEOSIZE_VGA)
+			cmd.buffer.registers[i].value =
+				(u8)CPIA2_VC_VC_CLOCKS_LOGDIV1;
+		else
+			cmd.buffer.registers[i].value =
+				(u8)(CPIA2_VC_VC_672_CLOCKS_SCALING |
+				     CPIA2_VC_VC_CLOCKS_LOGDIV3);
+	} else {
+		if (image_type == VIDEOSIZE_VGA)
+			cmd.buffer.registers[i].value =
+				(u8)CPIA2_VC_VC_CLOCKS_LOGDIV0;
+		else
+			cmd.buffer.registers[i].value =
+				(u8)(CPIA2_VC_VC_676_CLOCKS_SCALING |
+				     CPIA2_VC_VC_CLOCKS_LOGDIV2);
+	}
+	i++;
+
+	DBG("VC_CLOCKS = 0x%X\n", cmd.buffer.registers[i-1].value);
+
+	/* Input width from VP */
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_IHSIZE_LO;
+	if (image_type == VIDEOSIZE_VGA)
+		cmd.buffer.registers[i].value =
+		    (u8) (STV_IMAGE_VGA_COLS / 4);
+	else
+		cmd.buffer.registers[i].value =
+		    (u8) (STV_IMAGE_QVGA_COLS / 4);
+	i++;
+	DBG("Input width = %d\n", cmd.buffer.registers[i-1].value);
+
+	/* Timings */
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_XLIM_HI;
+	if (image_type == VIDEOSIZE_VGA)
+		cmd.buffer.registers[i++].value = (u8) 2;
+	else
+		cmd.buffer.registers[i++].value = (u8) 1;
+
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_XLIM_LO;
+	if (image_type == VIDEOSIZE_VGA)
+		cmd.buffer.registers[i++].value = (u8) 250;
+	else if (image_type == VIDEOSIZE_QVGA)
+		cmd.buffer.registers[i++].value = (u8) 125;
+	else
+		cmd.buffer.registers[i++].value = (u8) 160;
+
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_YLIM_HI;
+	if (image_type == VIDEOSIZE_VGA)
+		cmd.buffer.registers[i++].value = (u8) 2;
+	else
+		cmd.buffer.registers[i++].value = (u8) 1;
+
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_YLIM_LO;
+	if (image_type == VIDEOSIZE_VGA)
+		cmd.buffer.registers[i++].value = (u8) 12;
+	else if (image_type == VIDEOSIZE_QVGA)
+		cmd.buffer.registers[i++].value = (u8) 64;
+	else
+		cmd.buffer.registers[i++].value = (u8) 6;
+
+	/* Output Image Size */
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_OHSIZE;
+	if (image_type == VIDEOSIZE_QCIF)
+		cmd.buffer.registers[i++].value = STV_IMAGE_CIF_COLS  / 4;
+	else
+		cmd.buffer.registers[i++].value = width / 4;
+
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_OVSIZE;
+	if (image_type == VIDEOSIZE_QCIF)
+		cmd.buffer.registers[i++].value = STV_IMAGE_CIF_ROWS  / 4;
+	else
+		cmd.buffer.registers[i++].value = height / 4;
+
+	/* Cropping */
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_HCROP;
+	if (image_type == VIDEOSIZE_VGA)
+		cmd.buffer.registers[i++].value =
+		    (u8) (((STV_IMAGE_VGA_COLS / 4) - (width / 4)) / 2);
+	else if (image_type == VIDEOSIZE_QVGA)
+		cmd.buffer.registers[i++].value =
+		    (u8) (((STV_IMAGE_QVGA_COLS / 4) - (width / 4)) / 2);
+	else if (image_type == VIDEOSIZE_CIF)
+		cmd.buffer.registers[i++].value =
+		    (u8) (((STV_IMAGE_CIF_COLS / 4) - (width / 4)) / 2);
+	else /*if (image_type == VIDEOSIZE_QCIF)*/
+		cmd.buffer.registers[i++].value =
+			(u8) (((STV_IMAGE_QCIF_COLS / 4) - (width / 4)) / 2);
+
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_VCROP;
+	if (image_type == VIDEOSIZE_VGA)
+		cmd.buffer.registers[i++].value =
+		    (u8) (((STV_IMAGE_VGA_ROWS / 4) - (height / 4)) / 2);
+	else if (image_type == VIDEOSIZE_QVGA)
+		cmd.buffer.registers[i++].value =
+		    (u8) (((STV_IMAGE_QVGA_ROWS / 4) - (height / 4)) / 2);
+	else if (image_type == VIDEOSIZE_CIF)
+		cmd.buffer.registers[i++].value =
+		    (u8) (((STV_IMAGE_CIF_ROWS / 4) - (height / 4)) / 2);
+	else /*if (image_type == VIDEOSIZE_QCIF)*/
+		cmd.buffer.registers[i++].value =
+		    (u8) (((STV_IMAGE_QCIF_ROWS / 4) - (height / 4)) / 2);
+
+	/* Scaling registers (defaults) */
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_HPHASE;
+	if (image_type == VIDEOSIZE_CIF || image_type == VIDEOSIZE_QCIF)
+		cmd.buffer.registers[i++].value = (u8) 36;
+	else
+		cmd.buffer.registers[i++].value = (u8) 0;
+
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_VPHASE;
+	if (image_type == VIDEOSIZE_CIF || image_type == VIDEOSIZE_QCIF)
+		cmd.buffer.registers[i++].value = (u8) 32;
+	else
+		cmd.buffer.registers[i++].value = (u8) 0;
+
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_HISPAN;
+	if (image_type == VIDEOSIZE_CIF || image_type == VIDEOSIZE_QCIF)
+		cmd.buffer.registers[i++].value = (u8) 26;
+	else
+		cmd.buffer.registers[i++].value = (u8) 31;
+
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_VISPAN;
+	if (image_type == VIDEOSIZE_CIF || image_type == VIDEOSIZE_QCIF)
+		cmd.buffer.registers[i++].value = (u8) 21;
+	else
+		cmd.buffer.registers[i++].value = (u8) 31;
+
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_HICROP;
+	cmd.buffer.registers[i++].value = (u8) 0;
+
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_VICROP;
+	cmd.buffer.registers[i++].value = (u8) 0;
+
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_HFRACT;
+	if (image_type == VIDEOSIZE_CIF || image_type == VIDEOSIZE_QCIF)
+		cmd.buffer.registers[i++].value = (u8) 0x2B;	/* 2/11 */
+	else
+		cmd.buffer.registers[i++].value = (u8) 0x81;	/* 8/1 */
+
+	cmd.buffer.registers[i].index = CPIA2_VC_VC_VFRACT;
+	if (image_type == VIDEOSIZE_CIF || image_type == VIDEOSIZE_QCIF)
+		cmd.buffer.registers[i++].value = (u8) 0x13;	/* 1/3 */
+	else
+		cmd.buffer.registers[i++].value = (u8) 0x81;	/* 8/1 */
+
+	cmd.reg_count = i;
+
+	cpia2_send_command(cam, &cmd);
+
+	return i;
+}
+
+
+/******************************************************************************
+ *
+ *  setallproperties
+ *
+ *  This sets all user changeable properties to the values in cam->params.
+ *****************************************************************************/
+static int set_all_properties(struct camera_data *cam)
+{
+	/**
+	 * Don't set target_kb here, it will be set later.
+	 * framerate and user_mode were already set (set_default_user_mode).
+	 **/
+
+	cpia2_usb_change_streaming_alternate(cam,
+					  cam->params.camera_state.stream_mode);
+
+	cpia2_do_command(cam,
+			 CPIA2_CMD_SET_VC_MP_GPIO_DIRECTION,
+			 TRANSFER_WRITE, cam->params.vp_params.gpio_direction);
+	cpia2_do_command(cam, CPIA2_CMD_SET_VC_MP_GPIO_DATA, TRANSFER_WRITE,
+			 cam->params.vp_params.gpio_data);
+
+	v4l2_ctrl_handler_setup(&cam->hdl);
+
+	wake_system(cam);
+
+	set_lowlight_boost(cam);
+
+	return 0;
+}
+
+/******************************************************************************
+ *
+ *  cpia2_save_camera_state
+ *
+ *****************************************************************************/
+void cpia2_save_camera_state(struct camera_data *cam)
+{
+	cpia2_do_command(cam, CPIA2_CMD_GET_USER_EFFECTS, TRANSFER_READ, 0);
+	cpia2_do_command(cam, CPIA2_CMD_GET_VC_MP_GPIO_DIRECTION, TRANSFER_READ,
+			 0);
+	cpia2_do_command(cam, CPIA2_CMD_GET_VC_MP_GPIO_DATA, TRANSFER_READ, 0);
+	/* Don't get framerate or target_kb. Trust the values we already have */
+}
+
+
+/******************************************************************************
+ *
+ *  cpia2_set_flicker_mode
+ *
+ *****************************************************************************/
+int cpia2_set_flicker_mode(struct camera_data *cam, int mode)
+{
+	unsigned char cam_reg;
+	int err = 0;
+
+	if(cam->params.pnp_id.device_type != DEVICE_STV_672)
+		return -EINVAL;
+
+	/* Set the appropriate bits in FLICKER_MODES, preserving the rest */
+	if((err = cpia2_do_command(cam, CPIA2_CMD_GET_FLICKER_MODES,
+				   TRANSFER_READ, 0)))
+		return err;
+	cam_reg = cam->params.flicker_control.cam_register;
+
+	switch(mode) {
+	case NEVER_FLICKER:
+		cam_reg |= CPIA2_VP_FLICKER_MODES_NEVER_FLICKER;
+		cam_reg &= ~CPIA2_VP_FLICKER_MODES_50HZ;
+		break;
+	case FLICKER_60:
+		cam_reg &= ~CPIA2_VP_FLICKER_MODES_NEVER_FLICKER;
+		cam_reg &= ~CPIA2_VP_FLICKER_MODES_50HZ;
+		break;
+	case FLICKER_50:
+		cam_reg &= ~CPIA2_VP_FLICKER_MODES_NEVER_FLICKER;
+		cam_reg |= CPIA2_VP_FLICKER_MODES_50HZ;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if((err = cpia2_do_command(cam, CPIA2_CMD_SET_FLICKER_MODES,
+				   TRANSFER_WRITE, cam_reg)))
+		return err;
+
+	/* Set the appropriate bits in EXP_MODES, preserving the rest */
+	if((err = cpia2_do_command(cam, CPIA2_CMD_GET_VP_EXP_MODES,
+				   TRANSFER_READ, 0)))
+		return err;
+	cam_reg = cam->params.vp_params.exposure_modes;
+
+	if (mode == NEVER_FLICKER) {
+		cam_reg |= CPIA2_VP_EXPOSURE_MODES_INHIBIT_FLICKER;
+	} else {
+		cam_reg &= ~CPIA2_VP_EXPOSURE_MODES_INHIBIT_FLICKER;
+	}
+
+	if((err = cpia2_do_command(cam, CPIA2_CMD_SET_VP_EXP_MODES,
+				   TRANSFER_WRITE, cam_reg)))
+		return err;
+
+	if((err = cpia2_do_command(cam, CPIA2_CMD_REHASH_VP4,
+				   TRANSFER_WRITE, 1)))
+		return err;
+
+	switch(mode) {
+	case NEVER_FLICKER:
+	case FLICKER_60:
+	case FLICKER_50:
+		cam->params.flicker_control.flicker_mode_req = mode;
+		break;
+	default:
+		err = -EINVAL;
+	}
+
+	return err;
+}
+
+/******************************************************************************
+ *
+ *  cpia2_set_property_flip
+ *
+ *****************************************************************************/
+void cpia2_set_property_flip(struct camera_data *cam, int prop_val)
+{
+	unsigned char cam_reg;
+
+	cpia2_do_command(cam, CPIA2_CMD_GET_USER_EFFECTS, TRANSFER_READ, 0);
+	cam_reg = cam->params.vp_params.user_effects;
+
+	if (prop_val)
+	{
+		cam_reg |= CPIA2_VP_USER_EFFECTS_FLIP;
+	}
+	else
+	{
+		cam_reg &= ~CPIA2_VP_USER_EFFECTS_FLIP;
+	}
+	cam->params.vp_params.user_effects = cam_reg;
+	cpia2_do_command(cam, CPIA2_CMD_SET_USER_EFFECTS, TRANSFER_WRITE,
+			 cam_reg);
+}
+
+/******************************************************************************
+ *
+ *  cpia2_set_property_mirror
+ *
+ *****************************************************************************/
+void cpia2_set_property_mirror(struct camera_data *cam, int prop_val)
+{
+	unsigned char cam_reg;
+
+	cpia2_do_command(cam, CPIA2_CMD_GET_USER_EFFECTS, TRANSFER_READ, 0);
+	cam_reg = cam->params.vp_params.user_effects;
+
+	if (prop_val)
+	{
+		cam_reg |= CPIA2_VP_USER_EFFECTS_MIRROR;
+	}
+	else
+	{
+		cam_reg &= ~CPIA2_VP_USER_EFFECTS_MIRROR;
+	}
+	cam->params.vp_params.user_effects = cam_reg;
+	cpia2_do_command(cam, CPIA2_CMD_SET_USER_EFFECTS, TRANSFER_WRITE,
+			 cam_reg);
+}
+
+/******************************************************************************
+ *
+ *  cpia2_set_gpio
+ *
+ *****************************************************************************/
+int cpia2_set_gpio(struct camera_data *cam, unsigned char setting)
+{
+	int ret;
+
+	/* Set the microport direction (register 0x90, should be defined
+	 * already) to 1 (user output), and set the microport data (0x91) to
+	 * the value in the ioctl argument.
+	 */
+
+	ret = cpia2_do_command(cam,
+			       CPIA2_CMD_SET_VC_MP_GPIO_DIRECTION,
+			       CPIA2_VC_MP_DIR_OUTPUT,
+			       255);
+	if (ret < 0)
+		return ret;
+	cam->params.vp_params.gpio_direction = 255;
+
+	ret = cpia2_do_command(cam,
+			       CPIA2_CMD_SET_VC_MP_GPIO_DATA,
+			       CPIA2_VC_MP_DIR_OUTPUT,
+			       setting);
+	if (ret < 0)
+		return ret;
+	cam->params.vp_params.gpio_data = setting;
+
+	return 0;
+}
+
+/******************************************************************************
+ *
+ *  cpia2_set_fps
+ *
+ *****************************************************************************/
+int cpia2_set_fps(struct camera_data *cam, int framerate)
+{
+	int retval;
+
+	switch(framerate) {
+		case CPIA2_VP_FRAMERATE_30:
+		case CPIA2_VP_FRAMERATE_25:
+			if(cam->params.pnp_id.device_type == DEVICE_STV_672 &&
+			   cam->params.version.sensor_flags ==
+						    CPIA2_VP_SENSOR_FLAGS_500) {
+				return -EINVAL;
+			}
+			/* Fall through */
+		case CPIA2_VP_FRAMERATE_15:
+		case CPIA2_VP_FRAMERATE_12_5:
+		case CPIA2_VP_FRAMERATE_7_5:
+		case CPIA2_VP_FRAMERATE_6_25:
+			break;
+		default:
+			return -EINVAL;
+	}
+
+	if (cam->params.pnp_id.device_type == DEVICE_STV_672 &&
+	    framerate == CPIA2_VP_FRAMERATE_15)
+		framerate = 0; /* Work around bug in VP4 */
+
+	retval = cpia2_do_command(cam,
+				 CPIA2_CMD_FRAMERATE_REQ,
+				 TRANSFER_WRITE,
+				 framerate);
+
+	if(retval == 0)
+		cam->params.vp_params.frame_rate = framerate;
+
+	return retval;
+}
+
+/******************************************************************************
+ *
+ *  cpia2_set_brightness
+ *
+ *****************************************************************************/
+void cpia2_set_brightness(struct camera_data *cam, unsigned char value)
+{
+	/***
+	 * Don't let the register be set to zero - bug in VP4 - flash of full
+	 * brightness
+	 ***/
+	if (cam->params.pnp_id.device_type == DEVICE_STV_672 && value == 0)
+		value++;
+	DBG("Setting brightness to %d (0x%0x)\n", value, value);
+	cpia2_do_command(cam, CPIA2_CMD_SET_VP_BRIGHTNESS, TRANSFER_WRITE, value);
+}
+
+/******************************************************************************
+ *
+ *  cpia2_set_contrast
+ *
+ *****************************************************************************/
+void cpia2_set_contrast(struct camera_data *cam, unsigned char value)
+{
+	DBG("Setting contrast to %d (0x%0x)\n", value, value);
+	cpia2_do_command(cam, CPIA2_CMD_SET_CONTRAST, TRANSFER_WRITE, value);
+}
+
+/******************************************************************************
+ *
+ *  cpia2_set_saturation
+ *
+ *****************************************************************************/
+void cpia2_set_saturation(struct camera_data *cam, unsigned char value)
+{
+	DBG("Setting saturation to %d (0x%0x)\n", value, value);
+	cpia2_do_command(cam,CPIA2_CMD_SET_VP_SATURATION, TRANSFER_WRITE,value);
+}
+
+/******************************************************************************
+ *
+ *  wake_system
+ *
+ *****************************************************************************/
+static void wake_system(struct camera_data *cam)
+{
+	cpia2_do_command(cam, CPIA2_CMD_SET_WAKEUP, TRANSFER_WRITE, 0);
+}
+
+/******************************************************************************
+ *
+ *  set_lowlight_boost
+ *
+ *  Valid for STV500 sensor only
+ *****************************************************************************/
+static void set_lowlight_boost(struct camera_data *cam)
+{
+	struct cpia2_command cmd;
+
+	if (cam->params.pnp_id.device_type != DEVICE_STV_672 ||
+	    cam->params.version.sensor_flags != CPIA2_VP_SENSOR_FLAGS_500)
+		return;
+
+	cmd.direction = TRANSFER_WRITE;
+	cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP;
+	cmd.reg_count = 3;
+	cmd.start = CPIA2_VP_RAM_ADDR_H;
+
+	cmd.buffer.block_data[0] = 0;	/* High byte of address to write to */
+	cmd.buffer.block_data[1] = 0x59;	/* Low byte of address to write to */
+	cmd.buffer.block_data[2] = 0;	/* High byte of data to write */
+
+	cpia2_send_command(cam, &cmd);
+
+	if (cam->params.vp_params.lowlight_boost) {
+		cmd.buffer.block_data[0] = 0x02;	/* Low byte data to write */
+	} else {
+		cmd.buffer.block_data[0] = 0x06;
+	}
+	cmd.start = CPIA2_VP_RAM_DATA;
+	cmd.reg_count = 1;
+	cpia2_send_command(cam, &cmd);
+
+	/* Rehash the VP4 values */
+	cpia2_do_command(cam, CPIA2_CMD_REHASH_VP4, TRANSFER_WRITE, 1);
+}
+
+/******************************************************************************
+ *
+ *  cpia2_set_format
+ *
+ *  Assumes that new size is already set in param struct.
+ *****************************************************************************/
+void cpia2_set_format(struct camera_data *cam)
+{
+	cam->flush = true;
+
+	cpia2_usb_stream_pause(cam);
+
+	/* reset camera to new size */
+	cpia2_set_low_power(cam);
+	cpia2_reset_camera(cam);
+	cam->flush = false;
+
+	cpia2_dbg_dump_registers(cam);
+
+	cpia2_usb_stream_resume(cam);
+}
+
+/******************************************************************************
+ *
+ * cpia2_dbg_dump_registers
+ *
+ *****************************************************************************/
+void cpia2_dbg_dump_registers(struct camera_data *cam)
+{
+#ifdef _CPIA2_DEBUG_
+	struct cpia2_command cmd;
+
+	if (!(debugs_on & DEBUG_DUMP_REGS))
+		return;
+
+	cmd.direction = TRANSFER_READ;
+
+	/* Start with bank 0 (SYSTEM) */
+	cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_SYSTEM;
+	cmd.reg_count = 3;
+	cmd.start = 0;
+	cpia2_send_command(cam, &cmd);
+	printk(KERN_DEBUG "System Device Hi      = 0x%X\n",
+	       cmd.buffer.block_data[0]);
+	printk(KERN_DEBUG "System Device Lo      = 0x%X\n",
+	       cmd.buffer.block_data[1]);
+	printk(KERN_DEBUG "System_system control = 0x%X\n",
+	       cmd.buffer.block_data[2]);
+
+	/* Bank 1 (VC) */
+	cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VC;
+	cmd.reg_count = 4;
+	cmd.start = 0x80;
+	cpia2_send_command(cam, &cmd);
+	printk(KERN_DEBUG "ASIC_ID       = 0x%X\n",
+	       cmd.buffer.block_data[0]);
+	printk(KERN_DEBUG "ASIC_REV      = 0x%X\n",
+	       cmd.buffer.block_data[1]);
+	printk(KERN_DEBUG "PW_CONTRL     = 0x%X\n",
+	       cmd.buffer.block_data[2]);
+	printk(KERN_DEBUG "WAKEUP        = 0x%X\n",
+	       cmd.buffer.block_data[3]);
+
+	cmd.start = 0xA0;	/* ST_CTRL */
+	cmd.reg_count = 1;
+	cpia2_send_command(cam, &cmd);
+	printk(KERN_DEBUG "Stream ctrl   = 0x%X\n",
+	       cmd.buffer.block_data[0]);
+
+	cmd.start = 0xA4;	/* Stream status */
+	cpia2_send_command(cam, &cmd);
+	printk(KERN_DEBUG "Stream status = 0x%X\n",
+	       cmd.buffer.block_data[0]);
+
+	cmd.start = 0xA8;	/* USB status */
+	cmd.reg_count = 3;
+	cpia2_send_command(cam, &cmd);
+	printk(KERN_DEBUG "USB_CTRL      = 0x%X\n",
+	       cmd.buffer.block_data[0]);
+	printk(KERN_DEBUG "USB_STRM      = 0x%X\n",
+	       cmd.buffer.block_data[1]);
+	printk(KERN_DEBUG "USB_STATUS    = 0x%X\n",
+	       cmd.buffer.block_data[2]);
+
+	cmd.start = 0xAF;	/* USB settings */
+	cmd.reg_count = 1;
+	cpia2_send_command(cam, &cmd);
+	printk(KERN_DEBUG "USB settings  = 0x%X\n",
+	       cmd.buffer.block_data[0]);
+
+	cmd.start = 0xC0;	/* VC stuff */
+	cmd.reg_count = 26;
+	cpia2_send_command(cam, &cmd);
+	printk(KERN_DEBUG "VC Control    = 0x%0X\n",
+	       cmd.buffer.block_data[0]);
+	printk(KERN_DEBUG "VC Format     = 0x%0X\n",
+	       cmd.buffer.block_data[3]);
+	printk(KERN_DEBUG "VC Clocks     = 0x%0X\n",
+	       cmd.buffer.block_data[4]);
+	printk(KERN_DEBUG "VC IHSize     = 0x%0X\n",
+	       cmd.buffer.block_data[5]);
+	printk(KERN_DEBUG "VC Xlim Hi    = 0x%0X\n",
+	       cmd.buffer.block_data[6]);
+	printk(KERN_DEBUG "VC XLim Lo    = 0x%0X\n",
+	       cmd.buffer.block_data[7]);
+	printk(KERN_DEBUG "VC YLim Hi    = 0x%0X\n",
+	       cmd.buffer.block_data[8]);
+	printk(KERN_DEBUG "VC YLim Lo    = 0x%0X\n",
+	       cmd.buffer.block_data[9]);
+	printk(KERN_DEBUG "VC OHSize     = 0x%0X\n",
+	       cmd.buffer.block_data[10]);
+	printk(KERN_DEBUG "VC OVSize     = 0x%0X\n",
+	       cmd.buffer.block_data[11]);
+	printk(KERN_DEBUG "VC HCrop      = 0x%0X\n",
+	       cmd.buffer.block_data[12]);
+	printk(KERN_DEBUG "VC VCrop      = 0x%0X\n",
+	       cmd.buffer.block_data[13]);
+	printk(KERN_DEBUG "VC HPhase     = 0x%0X\n",
+	       cmd.buffer.block_data[14]);
+	printk(KERN_DEBUG "VC VPhase     = 0x%0X\n",
+	       cmd.buffer.block_data[15]);
+	printk(KERN_DEBUG "VC HIspan     = 0x%0X\n",
+	       cmd.buffer.block_data[16]);
+	printk(KERN_DEBUG "VC VIspan     = 0x%0X\n",
+	       cmd.buffer.block_data[17]);
+	printk(KERN_DEBUG "VC HiCrop     = 0x%0X\n",
+	       cmd.buffer.block_data[18]);
+	printk(KERN_DEBUG "VC ViCrop     = 0x%0X\n",
+	       cmd.buffer.block_data[19]);
+	printk(KERN_DEBUG "VC HiFract    = 0x%0X\n",
+	       cmd.buffer.block_data[20]);
+	printk(KERN_DEBUG "VC ViFract    = 0x%0X\n",
+	       cmd.buffer.block_data[21]);
+	printk(KERN_DEBUG "VC JPeg Opt   = 0x%0X\n",
+	       cmd.buffer.block_data[22]);
+	printk(KERN_DEBUG "VC Creep Per  = 0x%0X\n",
+	       cmd.buffer.block_data[23]);
+	printk(KERN_DEBUG "VC User Sq.   = 0x%0X\n",
+	       cmd.buffer.block_data[24]);
+	printk(KERN_DEBUG "VC Target KB  = 0x%0X\n",
+	       cmd.buffer.block_data[25]);
+
+	/*** VP ***/
+	cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VP;
+	cmd.reg_count = 14;
+	cmd.start = 0;
+	cpia2_send_command(cam, &cmd);
+
+	printk(KERN_DEBUG "VP Dev Hi     = 0x%0X\n",
+	       cmd.buffer.block_data[0]);
+	printk(KERN_DEBUG "VP Dev Lo     = 0x%0X\n",
+	       cmd.buffer.block_data[1]);
+	printk(KERN_DEBUG "VP Sys State  = 0x%0X\n",
+	       cmd.buffer.block_data[2]);
+	printk(KERN_DEBUG "VP Sys Ctrl   = 0x%0X\n",
+	       cmd.buffer.block_data[3]);
+	printk(KERN_DEBUG "VP Sensor flg = 0x%0X\n",
+	       cmd.buffer.block_data[5]);
+	printk(KERN_DEBUG "VP Sensor Rev = 0x%0X\n",
+	       cmd.buffer.block_data[6]);
+	printk(KERN_DEBUG "VP Dev Config = 0x%0X\n",
+	       cmd.buffer.block_data[7]);
+	printk(KERN_DEBUG "VP GPIO_DIR   = 0x%0X\n",
+	       cmd.buffer.block_data[8]);
+	printk(KERN_DEBUG "VP GPIO_DATA  = 0x%0X\n",
+	       cmd.buffer.block_data[9]);
+	printk(KERN_DEBUG "VP Ram ADDR H = 0x%0X\n",
+	       cmd.buffer.block_data[10]);
+	printk(KERN_DEBUG "VP Ram ADDR L = 0x%0X\n",
+	       cmd.buffer.block_data[11]);
+	printk(KERN_DEBUG "VP RAM Data   = 0x%0X\n",
+	       cmd.buffer.block_data[12]);
+	printk(KERN_DEBUG "Do Call       = 0x%0X\n",
+	       cmd.buffer.block_data[13]);
+
+	if (cam->params.pnp_id.device_type == DEVICE_STV_672) {
+		cmd.reg_count = 9;
+		cmd.start = 0x0E;
+		cpia2_send_command(cam, &cmd);
+		printk(KERN_DEBUG "VP Clock Ctrl = 0x%0X\n",
+		       cmd.buffer.block_data[0]);
+		printk(KERN_DEBUG "VP Patch Rev  = 0x%0X\n",
+		       cmd.buffer.block_data[1]);
+		printk(KERN_DEBUG "VP Vid Mode   = 0x%0X\n",
+		       cmd.buffer.block_data[2]);
+		printk(KERN_DEBUG "VP Framerate  = 0x%0X\n",
+		       cmd.buffer.block_data[3]);
+		printk(KERN_DEBUG "VP UserEffect = 0x%0X\n",
+		       cmd.buffer.block_data[4]);
+		printk(KERN_DEBUG "VP White Bal  = 0x%0X\n",
+		       cmd.buffer.block_data[5]);
+		printk(KERN_DEBUG "VP WB thresh  = 0x%0X\n",
+		       cmd.buffer.block_data[6]);
+		printk(KERN_DEBUG "VP Exp Modes  = 0x%0X\n",
+		       cmd.buffer.block_data[7]);
+		printk(KERN_DEBUG "VP Exp Target = 0x%0X\n",
+		       cmd.buffer.block_data[8]);
+
+		cmd.reg_count = 1;
+		cmd.start = 0x1B;
+		cpia2_send_command(cam, &cmd);
+		printk(KERN_DEBUG "VP FlickerMds = 0x%0X\n",
+		       cmd.buffer.block_data[0]);
+	} else {
+		cmd.reg_count = 8 ;
+		cmd.start = 0x0E;
+		cpia2_send_command(cam, &cmd);
+		printk(KERN_DEBUG "VP Clock Ctrl = 0x%0X\n",
+		       cmd.buffer.block_data[0]);
+		printk(KERN_DEBUG "VP Patch Rev  = 0x%0X\n",
+		       cmd.buffer.block_data[1]);
+		printk(KERN_DEBUG "VP Vid Mode   = 0x%0X\n",
+		       cmd.buffer.block_data[5]);
+		printk(KERN_DEBUG "VP Framerate  = 0x%0X\n",
+		       cmd.buffer.block_data[6]);
+		printk(KERN_DEBUG "VP UserEffect = 0x%0X\n",
+		       cmd.buffer.block_data[7]);
+
+		cmd.reg_count = 1;
+		cmd.start = CPIA2_VP5_EXPOSURE_TARGET;
+		cpia2_send_command(cam, &cmd);
+		printk(KERN_DEBUG "VP5 Exp Target= 0x%0X\n",
+		       cmd.buffer.block_data[0]);
+
+		cmd.reg_count = 4;
+		cmd.start = 0x3A;
+		cpia2_send_command(cam, &cmd);
+		printk(KERN_DEBUG "VP5 MY Black  = 0x%0X\n",
+		       cmd.buffer.block_data[0]);
+		printk(KERN_DEBUG "VP5 MCY Range = 0x%0X\n",
+		       cmd.buffer.block_data[1]);
+		printk(KERN_DEBUG "VP5 MYCEILING = 0x%0X\n",
+		       cmd.buffer.block_data[2]);
+		printk(KERN_DEBUG "VP5 MCUV Sat  = 0x%0X\n",
+		       cmd.buffer.block_data[3]);
+	}
+#endif
+}
+
+/******************************************************************************
+ *
+ *  reset_camera_struct
+ *
+ *  Sets all values to the defaults
+ *****************************************************************************/
+static void reset_camera_struct(struct camera_data *cam)
+{
+	/***
+	 * The following parameter values are the defaults from the register map.
+	 ***/
+	cam->params.vp_params.lowlight_boost = 0;
+
+	/* FlickerModes */
+	cam->params.flicker_control.flicker_mode_req = NEVER_FLICKER;
+
+	/* jpeg params */
+	cam->params.compression.jpeg_options = CPIA2_VC_VC_JPEG_OPT_DEFAULT;
+	cam->params.compression.creep_period = 2;
+	cam->params.compression.user_squeeze = 20;
+	cam->params.compression.inhibit_htables = false;
+
+	/* gpio params */
+	cam->params.vp_params.gpio_direction = 0;	/* write, the default safe mode */
+	cam->params.vp_params.gpio_data = 0;
+
+	/* Target kb params */
+	cam->params.vc_params.quality = 100;
+
+	/***
+	 * Set Sensor FPS as fast as possible.
+	 ***/
+	if(cam->params.pnp_id.device_type == DEVICE_STV_672) {
+		if(cam->params.version.sensor_flags == CPIA2_VP_SENSOR_FLAGS_500)
+			cam->params.vp_params.frame_rate = CPIA2_VP_FRAMERATE_15;
+		else
+			cam->params.vp_params.frame_rate = CPIA2_VP_FRAMERATE_30;
+	} else {
+		cam->params.vp_params.frame_rate = CPIA2_VP_FRAMERATE_30;
+	}
+
+	/***
+	 * Set default video mode as large as possible :
+	 * for vga sensor set to vga, for cif sensor set to CIF.
+	 ***/
+	if (cam->params.version.sensor_flags == CPIA2_VP_SENSOR_FLAGS_500) {
+		cam->sensor_type = CPIA2_SENSOR_500;
+		cam->video_size = VIDEOSIZE_VGA;
+		cam->params.roi.width = STV_IMAGE_VGA_COLS;
+		cam->params.roi.height = STV_IMAGE_VGA_ROWS;
+	} else {
+		cam->sensor_type = CPIA2_SENSOR_410;
+		cam->video_size = VIDEOSIZE_CIF;
+		cam->params.roi.width = STV_IMAGE_CIF_COLS;
+		cam->params.roi.height = STV_IMAGE_CIF_ROWS;
+	}
+
+	cam->width = cam->params.roi.width;
+	cam->height = cam->params.roi.height;
+}
+
+/******************************************************************************
+ *
+ *  cpia2_init_camera_struct
+ *
+ *  Initializes camera struct, does not call reset to fill in defaults.
+ *****************************************************************************/
+struct camera_data *cpia2_init_camera_struct(struct usb_interface *intf)
+{
+	struct camera_data *cam;
+
+	cam = kzalloc(sizeof(*cam), GFP_KERNEL);
+
+	if (!cam) {
+		ERR("couldn't kmalloc cpia2 struct\n");
+		return NULL;
+	}
+
+	cam->v4l2_dev.release = cpia2_camera_release;
+	if (v4l2_device_register(&intf->dev, &cam->v4l2_dev) < 0) {
+		v4l2_err(&cam->v4l2_dev, "couldn't register v4l2_device\n");
+		kfree(cam);
+		return NULL;
+	}
+
+	mutex_init(&cam->v4l2_lock);
+	init_waitqueue_head(&cam->wq_stream);
+
+	return cam;
+}
+
+/******************************************************************************
+ *
+ *  cpia2_init_camera
+ *
+ *  Initializes camera.
+ *****************************************************************************/
+int cpia2_init_camera(struct camera_data *cam)
+{
+	DBG("Start\n");
+
+	cam->mmapped = false;
+
+	/* Get sensor and asic types before reset. */
+	cpia2_set_high_power(cam);
+	cpia2_get_version_info(cam);
+	if (cam->params.version.asic_id != CPIA2_ASIC_672) {
+		ERR("Device IO error (asicID has incorrect value of 0x%X\n",
+		    cam->params.version.asic_id);
+		return -ENODEV;
+	}
+
+	/* Set GPIO direction and data to a safe state. */
+	cpia2_do_command(cam, CPIA2_CMD_SET_VC_MP_GPIO_DIRECTION,
+			 TRANSFER_WRITE, 0);
+	cpia2_do_command(cam, CPIA2_CMD_SET_VC_MP_GPIO_DATA,
+			 TRANSFER_WRITE, 0);
+
+	/* resetting struct requires version info for sensor and asic types */
+	reset_camera_struct(cam);
+
+	cpia2_set_low_power(cam);
+
+	DBG("End\n");
+
+	return 0;
+}
+
+/******************************************************************************
+ *
+ *  cpia2_allocate_buffers
+ *
+ *****************************************************************************/
+int cpia2_allocate_buffers(struct camera_data *cam)
+{
+	int i;
+
+	if(!cam->buffers) {
+		u32 size = cam->num_frames*sizeof(struct framebuf);
+		cam->buffers = kmalloc(size, GFP_KERNEL);
+		if(!cam->buffers) {
+			ERR("couldn't kmalloc frame buffer structures\n");
+			return -ENOMEM;
+		}
+	}
+
+	if(!cam->frame_buffer) {
+		cam->frame_buffer = rvmalloc(cam->frame_size*cam->num_frames);
+		if (!cam->frame_buffer) {
+			ERR("couldn't vmalloc frame buffer data area\n");
+			kfree(cam->buffers);
+			cam->buffers = NULL;
+			return -ENOMEM;
+		}
+	}
+
+	for(i=0; i<cam->num_frames-1; ++i) {
+		cam->buffers[i].next = &cam->buffers[i+1];
+		cam->buffers[i].data = cam->frame_buffer +i*cam->frame_size;
+		cam->buffers[i].status = FRAME_EMPTY;
+		cam->buffers[i].length = 0;
+		cam->buffers[i].max_length = 0;
+		cam->buffers[i].num = i;
+	}
+	cam->buffers[i].next = cam->buffers;
+	cam->buffers[i].data = cam->frame_buffer +i*cam->frame_size;
+	cam->buffers[i].status = FRAME_EMPTY;
+	cam->buffers[i].length = 0;
+	cam->buffers[i].max_length = 0;
+	cam->buffers[i].num = i;
+	cam->curbuff = cam->buffers;
+	cam->workbuff = cam->curbuff->next;
+	DBG("buffers=%p, curbuff=%p, workbuff=%p\n", cam->buffers, cam->curbuff,
+	    cam->workbuff);
+	return 0;
+}
+
+/******************************************************************************
+ *
+ *  cpia2_free_buffers
+ *
+ *****************************************************************************/
+void cpia2_free_buffers(struct camera_data *cam)
+{
+	if(cam->buffers) {
+		kfree(cam->buffers);
+		cam->buffers = NULL;
+	}
+	if(cam->frame_buffer) {
+		rvfree(cam->frame_buffer, cam->frame_size*cam->num_frames);
+		cam->frame_buffer = NULL;
+	}
+}
+
+/******************************************************************************
+ *
+ *  cpia2_read
+ *
+ *****************************************************************************/
+long cpia2_read(struct camera_data *cam,
+		char __user *buf, unsigned long count, int noblock)
+{
+	struct framebuf *frame;
+
+	if (!count)
+		return 0;
+
+	if (!buf) {
+		ERR("%s: buffer NULL\n",__func__);
+		return -EINVAL;
+	}
+
+	if (!cam) {
+		ERR("%s: Internal error, camera_data NULL!\n",__func__);
+		return -EINVAL;
+	}
+
+	if (!cam->streaming) {
+		/* Start streaming */
+		cpia2_usb_stream_start(cam,
+				       cam->params.camera_state.stream_mode);
+	}
+
+	/* Copy cam->curbuff in case it changes while we're processing */
+	frame = cam->curbuff;
+	if (noblock && frame->status != FRAME_READY) {
+		return -EAGAIN;
+	}
+
+	if (frame->status != FRAME_READY) {
+		mutex_unlock(&cam->v4l2_lock);
+		wait_event_interruptible(cam->wq_stream,
+			       !video_is_registered(&cam->vdev) ||
+			       (frame = cam->curbuff)->status == FRAME_READY);
+		mutex_lock(&cam->v4l2_lock);
+		if (signal_pending(current))
+			return -ERESTARTSYS;
+		if (!video_is_registered(&cam->vdev))
+			return 0;
+	}
+
+	/* copy data to user space */
+	if (frame->length > count)
+		return -EFAULT;
+	if (copy_to_user(buf, frame->data, frame->length))
+		return -EFAULT;
+
+	count = frame->length;
+
+	frame->status = FRAME_EMPTY;
+
+	return count;
+}
+
+/******************************************************************************
+ *
+ *  cpia2_poll
+ *
+ *****************************************************************************/
+__poll_t cpia2_poll(struct camera_data *cam, struct file *filp,
+			poll_table *wait)
+{
+	__poll_t status = v4l2_ctrl_poll(filp, wait);
+
+	if ((poll_requested_events(wait) & (EPOLLIN | EPOLLRDNORM)) &&
+			!cam->streaming) {
+		/* Start streaming */
+		cpia2_usb_stream_start(cam,
+				       cam->params.camera_state.stream_mode);
+	}
+
+	poll_wait(filp, &cam->wq_stream, wait);
+
+	if (cam->curbuff->status == FRAME_READY)
+		status |= EPOLLIN | EPOLLRDNORM;
+
+	return status;
+}
+
+/******************************************************************************
+ *
+ *  cpia2_remap_buffer
+ *
+ *****************************************************************************/
+int cpia2_remap_buffer(struct camera_data *cam, struct vm_area_struct *vma)
+{
+	const char *adr = (const char *)vma->vm_start;
+	unsigned long size = vma->vm_end-vma->vm_start;
+	unsigned long start_offset = vma->vm_pgoff << PAGE_SHIFT;
+	unsigned long start = (unsigned long) adr;
+	unsigned long page, pos;
+
+	DBG("mmap offset:%ld size:%ld\n", start_offset, size);
+
+	if (!video_is_registered(&cam->vdev))
+		return -ENODEV;
+
+	if (size > cam->frame_size*cam->num_frames  ||
+	    (start_offset % cam->frame_size) != 0 ||
+	    (start_offset+size > cam->frame_size*cam->num_frames))
+		return -EINVAL;
+
+	pos = ((unsigned long) (cam->frame_buffer)) + start_offset;
+	while (size > 0) {
+		page = kvirt_to_pa(pos);
+		if (remap_pfn_range(vma, start, page >> PAGE_SHIFT, PAGE_SIZE, PAGE_SHARED))
+			return -EAGAIN;
+		start += PAGE_SIZE;
+		pos += PAGE_SIZE;
+		if (size > PAGE_SIZE)
+			size -= PAGE_SIZE;
+		else
+			size = 0;
+	}
+
+	cam->mmapped = true;
+	return 0;
+}
diff --git a/drivers/media/usb/cpia2/cpia2_registers.h b/drivers/media/usb/cpia2/cpia2_registers.h
new file mode 100644
index 0000000..eebe46e
--- /dev/null
+++ b/drivers/media/usb/cpia2/cpia2_registers.h
@@ -0,0 +1,472 @@
+/****************************************************************************
+ *
+ *  Filename: cpia2registers.h
+ *
+ *  Copyright 2001, STMicrolectronics, Inc.
+ *
+ *  Description:
+ *     Definitions for the CPia2 register set
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ ****************************************************************************/
+
+#ifndef CPIA2_REGISTER_HEADER
+#define CPIA2_REGISTER_HEADER
+
+/***
+ * System register set (Bank 0)
+ ***/
+#define CPIA2_SYSTEM_DEVICE_HI                     0x00
+#define CPIA2_SYSTEM_DEVICE_LO                     0x01
+
+#define CPIA2_SYSTEM_SYSTEM_CONTROL                0x02
+#define CPIA2_SYSTEM_CONTROL_LOW_POWER       0x00
+#define CPIA2_SYSTEM_CONTROL_HIGH_POWER      0x01
+#define CPIA2_SYSTEM_CONTROL_SUSPEND         0x02
+#define CPIA2_SYSTEM_CONTROL_V2W_ERR         0x10
+#define CPIA2_SYSTEM_CONTROL_RB_ERR          0x10
+#define CPIA2_SYSTEM_CONTROL_CLEAR_ERR       0x80
+
+#define CPIA2_SYSTEM_INT_PACKET_CTRL                0x04
+#define CPIA2_SYSTEM_INT_PACKET_CTRL_ENABLE_SW_XX 0x01
+#define CPIA2_SYSTEM_INT_PACKET_CTRL_ENABLE_EOF   0x02
+#define CPIA2_SYSTEM_INT_PACKET_CTRL_ENABLE_INT1  0x04
+
+#define CPIA2_SYSTEM_CACHE_CTRL                     0x05
+#define CPIA2_SYSTEM_CACHE_CTRL_CACHE_RESET      0x01
+#define CPIA2_SYSTEM_CACHE_CTRL_CACHE_FLUSH      0x02
+
+#define CPIA2_SYSTEM_SERIAL_CTRL                    0x06
+#define CPIA2_SYSTEM_SERIAL_CTRL_NULL_CMD        0x00
+#define CPIA2_SYSTEM_SERIAL_CTRL_START_CMD       0x01
+#define CPIA2_SYSTEM_SERIAL_CTRL_STOP_CMD        0x02
+#define CPIA2_SYSTEM_SERIAL_CTRL_WRITE_CMD       0x03
+#define CPIA2_SYSTEM_SERIAL_CTRL_READ_ACK_CMD    0x04
+#define CPIA2_SYSTEM_SERIAL_CTRL_READ_NACK_CMD   0x05
+
+#define CPIA2_SYSTEM_SERIAL_DATA                     0x07
+
+#define CPIA2_SYSTEM_VP_SERIAL_ADDR                  0x08
+
+/***
+ * I2C addresses for various devices in CPiA2
+ ***/
+#define CPIA2_SYSTEM_VP_SERIAL_ADDR_SENSOR           0x20
+#define CPIA2_SYSTEM_VP_SERIAL_ADDR_VP               0x88
+#define CPIA2_SYSTEM_VP_SERIAL_ADDR_676_VP           0x8A
+
+#define CPIA2_SYSTEM_SPARE_REG1                      0x09
+#define CPIA2_SYSTEM_SPARE_REG2                      0x0A
+#define CPIA2_SYSTEM_SPARE_REG3                      0x0B
+
+#define CPIA2_SYSTEM_MC_PORT_0                       0x0C
+#define CPIA2_SYSTEM_MC_PORT_1                       0x0D
+#define CPIA2_SYSTEM_MC_PORT_2                       0x0E
+#define CPIA2_SYSTEM_MC_PORT_3                       0x0F
+
+#define CPIA2_SYSTEM_STATUS_PKT                      0x20
+#define CPIA2_SYSTEM_STATUS_PKT_END                  0x27
+
+#define CPIA2_SYSTEM_DESCRIP_VID_HI                  0x30
+#define CPIA2_SYSTEM_DESCRIP_VID_LO                  0x31
+#define CPIA2_SYSTEM_DESCRIP_PID_HI                  0x32
+#define CPIA2_SYSTEM_DESCRIP_PID_LO                  0x33
+
+#define CPIA2_SYSTEM_FW_VERSION_HI                   0x34
+#define CPIA2_SYSTEM_FW_VERSION_LO                   0x35
+
+#define CPIA2_SYSTEM_CACHE_START_INDEX               0x80
+#define CPIA2_SYSTEM_CACHE_MAX_WRITES                0x10
+
+/***
+ * VC register set (Bank 1)
+ ***/
+#define CPIA2_VC_ASIC_ID                 0x80
+
+#define CPIA2_VC_ASIC_REV                0x81
+
+#define CPIA2_VC_PW_CTRL                 0x82
+#define CPIA2_VC_PW_CTRL_COLDSTART      0x01
+#define CPIA2_VC_PW_CTRL_CP_CLK_EN      0x02
+#define CPIA2_VC_PW_CTRL_VP_RESET_N     0x04
+#define CPIA2_VC_PW_CTRL_VC_CLK_EN      0x08
+#define CPIA2_VC_PW_CTRL_VC_RESET_N     0x10
+#define CPIA2_VC_PW_CTRL_GOTO_SUSPEND   0x20
+#define CPIA2_VC_PW_CTRL_UDC_SUSPEND    0x40
+#define CPIA2_VC_PW_CTRL_PWR_DOWN       0x80
+
+#define CPIA2_VC_WAKEUP                   0x83
+#define CPIA2_VC_WAKEUP_SW_ENABLE       0x01
+#define CPIA2_VC_WAKEUP_XX_ENABLE       0x02
+#define CPIA2_VC_WAKEUP_SW_ATWAKEUP     0x04
+#define CPIA2_VC_WAKEUP_XX_ATWAKEUP     0x08
+
+#define CPIA2_VC_CLOCK_CTRL               0x84
+#define CPIA2_VC_CLOCK_CTRL_TESTUP72    0x01
+
+#define CPIA2_VC_INT_ENABLE                0x88
+#define CPIA2_VC_INT_ENABLE_XX_IE       0x01
+#define CPIA2_VC_INT_ENABLE_SW_IE       0x02
+#define CPIA2_VC_INT_ENABLE_VC_IE       0x04
+#define CPIA2_VC_INT_ENABLE_USBDATA_IE  0x08
+#define CPIA2_VC_INT_ENABLE_USBSETUP_IE 0x10
+#define CPIA2_VC_INT_ENABLE_USBCFG_IE   0x20
+
+#define CPIA2_VC_INT_FLAG                  0x89
+#define CPIA2_VC_INT_ENABLE_XX_FLAG       0x01
+#define CPIA2_VC_INT_ENABLE_SW_FLAG       0x02
+#define CPIA2_VC_INT_ENABLE_VC_FLAG       0x04
+#define CPIA2_VC_INT_ENABLE_USBDATA_FLAG  0x08
+#define CPIA2_VC_INT_ENABLE_USBSETUP_FLAG 0x10
+#define CPIA2_VC_INT_ENABLE_USBCFG_FLAG   0x20
+#define CPIA2_VC_INT_ENABLE_SET_RESET_BIT 0x80
+
+#define CPIA2_VC_INT_STATE                 0x8A
+#define CPIA2_VC_INT_STATE_XX_STATE     0x01
+#define CPIA2_VC_INT_STATE_SW_STATE     0x02
+
+#define CPIA2_VC_MP_DIR                    0x90
+#define CPIA2_VC_MP_DIR_INPUT           0x00
+#define CPIA2_VC_MP_DIR_OUTPUT          0x01
+
+#define CPIA2_VC_MP_DATA                   0x91
+
+#define CPIA2_VC_DP_CTRL                   0x98
+#define CPIA2_VC_DP_CTRL_MODE_0         0x00
+#define CPIA2_VC_DP_CTRL_MODE_A         0x01
+#define CPIA2_VC_DP_CTRL_MODE_B         0x02
+#define CPIA2_VC_DP_CTRL_MODE_C         0x03
+#define CPIA2_VC_DP_CTRL_FAKE_FST       0x04
+
+#define CPIA2_VC_AD_CTRL                   0x99
+#define CPIA2_VC_AD_CTRL_SRC_0          0x00
+#define CPIA2_VC_AD_CTRL_SRC_DIGI_A     0x01
+#define CPIA2_VC_AD_CTRL_SRC_REG        0x02
+#define CPIA2_VC_AD_CTRL_DST_USB        0x00
+#define CPIA2_VC_AD_CTRL_DST_REG        0x04
+
+#define CPIA2_VC_AD_TEST_IN                0x9B
+
+#define CPIA2_VC_AD_TEST_OUT               0x9C
+
+#define CPIA2_VC_AD_STATUS                 0x9D
+#define CPIA2_VC_AD_STATUS_EMPTY        0x01
+#define CPIA2_VC_AD_STATUS_FULL         0x02
+
+#define CPIA2_VC_DP_DATA                   0x9E
+
+#define CPIA2_VC_ST_CTRL                   0xA0
+#define CPIA2_VC_ST_CTRL_SRC_VC         0x00
+#define CPIA2_VC_ST_CTRL_SRC_DP         0x01
+#define CPIA2_VC_ST_CTRL_SRC_REG        0x02
+
+#define CPIA2_VC_ST_CTRL_RAW_SELECT     0x04
+
+#define CPIA2_VC_ST_CTRL_DST_USB        0x00
+#define CPIA2_VC_ST_CTRL_DST_DP         0x08
+#define CPIA2_VC_ST_CTRL_DST_REG        0x10
+
+#define CPIA2_VC_ST_CTRL_FIFO_ENABLE    0x20
+#define CPIA2_VC_ST_CTRL_EOF_DETECT     0x40
+
+#define CPIA2_VC_ST_TEST                   0xA1
+#define CPIA2_VC_ST_TEST_MODE_MANUAL    0x00
+#define CPIA2_VC_ST_TEST_MODE_INCREMENT 0x02
+
+#define CPIA2_VC_ST_TEST_AUTO_FILL      0x08
+
+#define CPIA2_VC_ST_TEST_REPEAT_FIFO    0x10
+
+#define CPIA2_VC_ST_TEST_IN                0xA2
+
+#define CPIA2_VC_ST_TEST_OUT               0xA3
+
+#define CPIA2_VC_ST_STATUS                 0xA4
+#define CPIA2_VC_ST_STATUS_EMPTY        0x01
+#define CPIA2_VC_ST_STATUS_FULL         0x02
+
+#define CPIA2_VC_ST_FRAME_DETECT_1         0xA5
+
+#define CPIA2_VC_ST_FRAME_DETECT_2         0xA6
+
+#define CPIA2_VC_USB_CTRL                    0xA8
+#define CPIA2_VC_USB_CTRL_CMD_STALLED      0x01
+#define CPIA2_VC_USB_CTRL_CMD_READY        0x02
+#define CPIA2_VC_USB_CTRL_CMD_STATUS       0x04
+#define CPIA2_VC_USB_CTRL_CMD_STATUS_DIR   0x08
+#define CPIA2_VC_USB_CTRL_CMD_NO_CLASH     0x10
+#define CPIA2_VC_USB_CTRL_CMD_MICRO_ACCESS 0x80
+
+#define CPIA2_VC_USB_STRM                  0xA9
+#define CPIA2_VC_USB_STRM_ISO_ENABLE    0x01
+#define CPIA2_VC_USB_STRM_BLK_ENABLE    0x02
+#define CPIA2_VC_USB_STRM_INT_ENABLE    0x04
+#define CPIA2_VC_USB_STRM_AUD_ENABLE    0x08
+
+#define CPIA2_VC_USB_STATUS                   0xAA
+#define CPIA2_VC_USB_STATUS_CMD_IN_PROGRESS  0x01
+#define CPIA2_VC_USB_STATUS_CMD_STATUS_STALL 0x02
+#define CPIA2_VC_USB_STATUS_CMD_HANDSHAKE    0x04
+#define CPIA2_VC_USB_STATUS_CMD_OVERRIDE     0x08
+#define CPIA2_VC_USB_STATUS_CMD_FIFO_BUSY    0x10
+#define CPIA2_VC_USB_STATUS_BULK_REPEAT_TXN  0x20
+#define CPIA2_VC_USB_STATUS_CONFIG_DONE      0x40
+#define CPIA2_VC_USB_STATUS_USB_SUSPEND      0x80
+
+#define CPIA2_VC_USB_CMDW                   0xAB
+
+#define CPIA2_VC_USB_DATARW                 0xAC
+
+#define CPIA2_VC_USB_INFO                   0xAD
+
+#define CPIA2_VC_USB_CONFIG                 0xAE
+
+#define CPIA2_VC_USB_SETTINGS                  0xAF
+#define CPIA2_VC_USB_SETTINGS_CONFIG_MASK    0x03
+#define CPIA2_VC_USB_SETTINGS_INTERFACE_MASK 0x0C
+#define CPIA2_VC_USB_SETTINGS_ALTERNATE_MASK 0x70
+
+#define CPIA2_VC_USB_ISOLIM                  0xB0
+
+#define CPIA2_VC_USB_ISOFAILS                0xB1
+
+#define CPIA2_VC_USB_ISOMAXPKTHI             0xB2
+
+#define CPIA2_VC_USB_ISOMAXPKTLO             0xB3
+
+#define CPIA2_VC_V2W_CTRL                    0xB8
+#define CPIA2_VC_V2W_SELECT               0x01
+
+#define CPIA2_VC_V2W_SCL                     0xB9
+
+#define CPIA2_VC_V2W_SDA                     0xBA
+
+#define CPIA2_VC_VC_CTRL                     0xC0
+#define CPIA2_VC_VC_CTRL_RUN              0x01
+#define CPIA2_VC_VC_CTRL_SINGLESHOT       0x02
+#define CPIA2_VC_VC_CTRL_IDLING           0x04
+#define CPIA2_VC_VC_CTRL_INHIBIT_H_TABLES 0x10
+#define CPIA2_VC_VC_CTRL_INHIBIT_Q_TABLES 0x20
+#define CPIA2_VC_VC_CTRL_INHIBIT_PRIVATE  0x40
+
+#define CPIA2_VC_VC_RESTART_IVAL_HI          0xC1
+
+#define CPIA2_VC_VC_RESTART_IVAL_LO          0xC2
+
+#define CPIA2_VC_VC_FORMAT                   0xC3
+#define CPIA2_VC_VC_FORMAT_UFIRST         0x01
+#define CPIA2_VC_VC_FORMAT_MONO           0x02
+#define CPIA2_VC_VC_FORMAT_DECIMATING     0x04
+#define CPIA2_VC_VC_FORMAT_SHORTLINE      0x08
+#define CPIA2_VC_VC_FORMAT_SELFTEST       0x10
+
+#define CPIA2_VC_VC_CLOCKS                         0xC4
+#define CPIA2_VC_VC_CLOCKS_CLKDIV_MASK        0x03
+#define CPIA2_VC_VC_672_CLOCKS_CIF_DIV_BY_3   0x04
+#define CPIA2_VC_VC_672_CLOCKS_SCALING        0x08
+#define CPIA2_VC_VC_CLOCKS_LOGDIV0        0x00
+#define CPIA2_VC_VC_CLOCKS_LOGDIV1        0x01
+#define CPIA2_VC_VC_CLOCKS_LOGDIV2        0x02
+#define CPIA2_VC_VC_CLOCKS_LOGDIV3        0x03
+#define CPIA2_VC_VC_676_CLOCKS_CIF_DIV_BY_3   0x08
+#define CPIA2_VC_VC_676_CLOCKS_SCALING	      0x10
+
+#define CPIA2_VC_VC_IHSIZE_LO                0xC5
+
+#define CPIA2_VC_VC_XLIM_HI                  0xC6
+
+#define CPIA2_VC_VC_XLIM_LO                  0xC7
+
+#define CPIA2_VC_VC_YLIM_HI                  0xC8
+
+#define CPIA2_VC_VC_YLIM_LO                  0xC9
+
+#define CPIA2_VC_VC_OHSIZE                   0xCA
+
+#define CPIA2_VC_VC_OVSIZE                   0xCB
+
+#define CPIA2_VC_VC_HCROP                    0xCC
+
+#define CPIA2_VC_VC_VCROP                    0xCD
+
+#define CPIA2_VC_VC_HPHASE                   0xCE
+
+#define CPIA2_VC_VC_VPHASE                   0xCF
+
+#define CPIA2_VC_VC_HISPAN                   0xD0
+
+#define CPIA2_VC_VC_VISPAN                   0xD1
+
+#define CPIA2_VC_VC_HICROP                   0xD2
+
+#define CPIA2_VC_VC_VICROP                   0xD3
+
+#define CPIA2_VC_VC_HFRACT                   0xD4
+#define CPIA2_VC_VC_HFRACT_DEN_MASK       0x0F
+#define CPIA2_VC_VC_HFRACT_NUM_MASK       0xF0
+
+#define CPIA2_VC_VC_VFRACT                   0xD5
+#define CPIA2_VC_VC_VFRACT_DEN_MASK       0x0F
+#define CPIA2_VC_VC_VFRACT_NUM_MASK       0xF0
+
+#define CPIA2_VC_VC_JPEG_OPT                      0xD6
+#define CPIA2_VC_VC_JPEG_OPT_DOUBLE_SQUEEZE     0x01
+#define CPIA2_VC_VC_JPEG_OPT_NO_DC_AUTO_SQUEEZE 0x02
+#define CPIA2_VC_VC_JPEG_OPT_AUTO_SQUEEZE       0x04
+#define CPIA2_VC_VC_JPEG_OPT_DEFAULT      (CPIA2_VC_VC_JPEG_OPT_DOUBLE_SQUEEZE|\
+					   CPIA2_VC_VC_JPEG_OPT_AUTO_SQUEEZE)
+
+
+#define CPIA2_VC_VC_CREEP_PERIOD             0xD7
+#define CPIA2_VC_VC_USER_SQUEEZE             0xD8
+#define CPIA2_VC_VC_TARGET_KB                0xD9
+
+#define CPIA2_VC_VC_AUTO_SQUEEZE             0xE6
+
+
+/***
+ * VP register set (Bank 2)
+ ***/
+#define CPIA2_VP_DEVICEH                             0
+#define CPIA2_VP_DEVICEL                             1
+
+#define CPIA2_VP_SYSTEMSTATE                         0x02
+#define CPIA2_VP_SYSTEMSTATE_HK_ALIVE             0x01
+
+#define CPIA2_VP_SYSTEMCTRL                          0x03
+#define CPIA2_VP_SYSTEMCTRL_REQ_CLEAR_ERROR       0x80
+#define CPIA2_VP_SYSTEMCTRL_POWER_DOWN_PLL        0x20
+#define CPIA2_VP_SYSTEMCTRL_REQ_SUSPEND_STATE     0x10
+#define CPIA2_VP_SYSTEMCTRL_REQ_SERIAL_WAKEUP     0x08
+#define CPIA2_VP_SYSTEMCTRL_REQ_AUTOLOAD          0x04
+#define CPIA2_VP_SYSTEMCTRL_HK_CONTROL            0x02
+#define CPIA2_VP_SYSTEMCTRL_POWER_CONTROL         0x01
+
+#define CPIA2_VP_SENSOR_FLAGS                        0x05
+#define CPIA2_VP_SENSOR_FLAGS_404                 0x01
+#define CPIA2_VP_SENSOR_FLAGS_407                 0x02
+#define CPIA2_VP_SENSOR_FLAGS_409                 0x04
+#define CPIA2_VP_SENSOR_FLAGS_410                 0x08
+#define CPIA2_VP_SENSOR_FLAGS_500                 0x10
+
+#define CPIA2_VP_SENSOR_REV                          0x06
+
+#define CPIA2_VP_DEVICE_CONFIG                       0x07
+#define CPIA2_VP_DEVICE_CONFIG_SERIAL_BRIDGE      0x01
+
+#define CPIA2_VP_GPIO_DIRECTION                      0x08
+#define CPIA2_VP_GPIO_READ                        0xFF
+#define CPIA2_VP_GPIO_WRITE                       0x00
+
+#define CPIA2_VP_GPIO_DATA                           0x09
+
+#define CPIA2_VP_RAM_ADDR_H                          0x0A
+#define CPIA2_VP_RAM_ADDR_L                          0x0B
+#define CPIA2_VP_RAM_DATA                            0x0C
+
+#define CPIA2_VP_PATCH_REV                           0x0F
+
+#define CPIA2_VP4_USER_MODE                           0x10
+#define CPIA2_VP5_USER_MODE                           0x13
+#define CPIA2_VP_USER_MODE_CIF                    0x01
+#define CPIA2_VP_USER_MODE_QCIFDS                 0x02
+#define CPIA2_VP_USER_MODE_QCIFPTC                0x04
+#define CPIA2_VP_USER_MODE_QVGADS                 0x08
+#define CPIA2_VP_USER_MODE_QVGAPTC                0x10
+#define CPIA2_VP_USER_MODE_VGA                    0x20
+
+#define CPIA2_VP4_FRAMERATE_REQUEST                    0x11
+#define CPIA2_VP5_FRAMERATE_REQUEST                    0x14
+#define CPIA2_VP_FRAMERATE_60                     0x80
+#define CPIA2_VP_FRAMERATE_50                     0x40
+#define CPIA2_VP_FRAMERATE_30                     0x20
+#define CPIA2_VP_FRAMERATE_25                     0x10
+#define CPIA2_VP_FRAMERATE_15                     0x08
+#define CPIA2_VP_FRAMERATE_12_5                   0x04
+#define CPIA2_VP_FRAMERATE_7_5                    0x02
+#define CPIA2_VP_FRAMERATE_6_25                   0x01
+
+#define CPIA2_VP4_USER_EFFECTS                         0x12
+#define CPIA2_VP5_USER_EFFECTS                         0x15
+#define CPIA2_VP_USER_EFFECTS_COLBARS             0x01
+#define CPIA2_VP_USER_EFFECTS_COLBARS_GRAD        0x02
+#define CPIA2_VP_USER_EFFECTS_MIRROR              0x04
+#define CPIA2_VP_USER_EFFECTS_FLIP                0x40  // VP5 only
+
+/* NOTE: CPIA2_VP_EXPOSURE_MODES shares the same register as VP5 User
+ * Effects */
+#define CPIA2_VP_EXPOSURE_MODES                       0x15
+#define CPIA2_VP_EXPOSURE_MODES_INHIBIT_FLICKER   0x20
+#define CPIA2_VP_EXPOSURE_MODES_COMPILE_EXP       0x10
+
+#define CPIA2_VP4_EXPOSURE_TARGET                     0x16    // VP4
+#define CPIA2_VP5_EXPOSURE_TARGET		      0x20    // VP5
+
+#define CPIA2_VP_FLICKER_MODES                        0x1B
+#define CPIA2_VP_FLICKER_MODES_50HZ               0x80
+#define CPIA2_VP_FLICKER_MODES_CUSTOM_FLT_FFREQ   0x40
+#define CPIA2_VP_FLICKER_MODES_NEVER_FLICKER      0x20
+#define CPIA2_VP_FLICKER_MODES_INHIBIT_RUB        0x10
+#define CPIA2_VP_FLICKER_MODES_ADJUST_LINE_FREQ   0x08
+#define CPIA2_VP_FLICKER_MODES_CUSTOM_INT_FFREQ   0x04
+
+#define CPIA2_VP_UMISC                                0x1D
+#define CPIA2_VP_UMISC_FORCE_MONO                 0x80
+#define CPIA2_VP_UMISC_FORCE_ID_MASK              0x40
+#define CPIA2_VP_UMISC_INHIBIT_AUTO_FGS           0x20
+#define CPIA2_VP_UMISC_INHIBIT_AUTO_DIMS          0x08
+#define CPIA2_VP_UMISC_OPT_FOR_SENSOR_DS          0x04
+#define CPIA2_VP_UMISC_INHIBIT_AUTO_MODE_INT      0x02
+
+#define CPIA2_VP5_ANTIFLKRSETUP                       0x22  //34
+
+#define CPIA2_VP_INTERPOLATION                        0x24
+#define CPIA2_VP_INTERPOLATION_EVEN_FIRST         0x40
+#define CPIA2_VP_INTERPOLATION_HJOG               0x20
+#define CPIA2_VP_INTERPOLATION_VJOG               0x10
+
+#define CPIA2_VP_GAMMA                                0x25
+#define CPIA2_VP_DEFAULT_GAMMA                    0x10
+
+#define CPIA2_VP_YRANGE                               0x26
+
+#define CPIA2_VP_SATURATION                           0x27
+
+#define CPIA2_VP5_MYBLACK_LEVEL                       0x3A   //58
+#define CPIA2_VP5_MCYRANGE                            0x3B   //59
+#define CPIA2_VP5_MYCEILING                           0x3C   //60
+#define CPIA2_VP5_MCUVSATURATION                      0x3D   //61
+
+
+#define CPIA2_VP_REHASH_VALUES                        0x60
+
+
+/***
+ * Common sensor registers
+ ***/
+#define CPIA2_SENSOR_DEVICE_H                         0x00
+#define CPIA2_SENSOR_DEVICE_L                         0x01
+
+#define CPIA2_SENSOR_DATA_FORMAT                      0x16
+#define CPIA2_SENSOR_DATA_FORMAT_HMIRROR      0x08
+#define CPIA2_SENSOR_DATA_FORMAT_VMIRROR      0x10
+
+#define CPIA2_SENSOR_CR1                              0x76
+#define CPIA2_SENSOR_CR1_STAND_BY             0x01
+#define CPIA2_SENSOR_CR1_DOWN_RAMP_GEN        0x02
+#define CPIA2_SENSOR_CR1_DOWN_COLUMN_ADC      0x04
+#define CPIA2_SENSOR_CR1_DOWN_CAB_REGULATOR   0x08
+#define CPIA2_SENSOR_CR1_DOWN_AUDIO_REGULATOR 0x10
+#define CPIA2_SENSOR_CR1_DOWN_VRT_AMP         0x20
+#define CPIA2_SENSOR_CR1_DOWN_BAND_GAP        0x40
+
+#endif
diff --git a/drivers/media/usb/cpia2/cpia2_usb.c b/drivers/media/usb/cpia2/cpia2_usb.c
new file mode 100644
index 0000000..a771e0a
--- /dev/null
+++ b/drivers/media/usb/cpia2/cpia2_usb.c
@@ -0,0 +1,969 @@
+/****************************************************************************
+ *
+ *  Filename: cpia2_usb.c
+ *
+ *  Copyright 2001, STMicrolectronics, Inc.
+ *      Contact:  steve.miller@st.com
+ *
+ *  Description:
+ *     This is a USB driver for CPia2 based video cameras.
+ *     The infrastructure of this driver is based on the cpia usb driver by
+ *     Jochen Scharrlach and Johannes Erdfeldt.
+ *
+ *  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.
+ *
+ *  Stripped of 2.4 stuff ready for main kernel submit by
+ *		Alan Cox <alan@lxorguk.ukuu.org.uk>
+ ****************************************************************************/
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/module.h>
+
+#include "cpia2.h"
+
+static int frame_sizes[] = {
+	0,	// USBIF_CMDONLY
+	0,	// USBIF_BULK
+	128,	// USBIF_ISO_1
+	384,	// USBIF_ISO_2
+	640,	// USBIF_ISO_3
+	768,	// USBIF_ISO_4
+	896,	// USBIF_ISO_5
+	1023,	// USBIF_ISO_6
+};
+
+#define FRAMES_PER_DESC    10
+#define FRAME_SIZE_PER_DESC   frame_sizes[cam->cur_alt]
+
+static void process_frame(struct camera_data *cam);
+static void cpia2_usb_complete(struct urb *urb);
+static int cpia2_usb_probe(struct usb_interface *intf,
+			   const struct usb_device_id *id);
+static void cpia2_usb_disconnect(struct usb_interface *intf);
+static int cpia2_usb_suspend(struct usb_interface *intf, pm_message_t message);
+static int cpia2_usb_resume(struct usb_interface *intf);
+
+static void free_sbufs(struct camera_data *cam);
+static void add_APPn(struct camera_data *cam);
+static void add_COM(struct camera_data *cam);
+static int submit_urbs(struct camera_data *cam);
+static int set_alternate(struct camera_data *cam, unsigned int alt);
+static int configure_transfer_mode(struct camera_data *cam, unsigned int alt);
+
+static const struct usb_device_id cpia2_id_table[] = {
+	{USB_DEVICE(0x0553, 0x0100)},
+	{USB_DEVICE(0x0553, 0x0140)},
+	{USB_DEVICE(0x0553, 0x0151)},  /* STV0676 */
+	{}			/* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, cpia2_id_table);
+
+static struct usb_driver cpia2_driver = {
+	.name		= "cpia2",
+	.probe		= cpia2_usb_probe,
+	.disconnect	= cpia2_usb_disconnect,
+	.suspend	= cpia2_usb_suspend,
+	.resume		= cpia2_usb_resume,
+	.reset_resume	= cpia2_usb_resume,
+	.id_table	= cpia2_id_table
+};
+
+
+/******************************************************************************
+ *
+ *  process_frame
+ *
+ *****************************************************************************/
+static void process_frame(struct camera_data *cam)
+{
+	static int frame_count;
+
+	unsigned char *inbuff = cam->workbuff->data;
+
+	DBG("Processing frame #%d, current:%d\n",
+	    cam->workbuff->num, cam->curbuff->num);
+
+	if(cam->workbuff->length > cam->workbuff->max_length)
+		cam->workbuff->max_length = cam->workbuff->length;
+
+	if ((inbuff[0] == 0xFF) && (inbuff[1] == 0xD8)) {
+		frame_count++;
+	} else {
+		cam->workbuff->status = FRAME_ERROR;
+		DBG("Start of frame not found\n");
+		return;
+	}
+
+	/***
+	 * Now the output buffer should have a JPEG image in it.
+	 ***/
+	if(!cam->first_image_seen) {
+		/* Always skip the first image after streaming
+		 * starts. It is almost certainly corrupt. */
+		cam->first_image_seen = 1;
+		cam->workbuff->status = FRAME_EMPTY;
+		return;
+	}
+	if (cam->workbuff->length > 3) {
+		if(cam->mmapped &&
+		   cam->workbuff->length < cam->workbuff->max_length) {
+			/* No junk in the buffers */
+			memset(cam->workbuff->data+cam->workbuff->length,
+			       0, cam->workbuff->max_length-
+				  cam->workbuff->length);
+		}
+		cam->workbuff->max_length = cam->workbuff->length;
+		cam->workbuff->status = FRAME_READY;
+
+		if(!cam->mmapped && cam->num_frames > 2) {
+			/* During normal reading, the most recent
+			 * frame will be read.  If the current frame
+			 * hasn't started reading yet, it will never
+			 * be read, so mark it empty.  If the buffer is
+			 * mmapped, or we have few buffers, we need to
+			 * wait for the user to free the buffer.
+			 *
+			 * NOTE: This is not entirely foolproof with 3
+			 * buffers, but it would take an EXTREMELY
+			 * overloaded system to cause problems (possible
+			 * image data corruption).  Basically, it would
+			 * need to take more time to execute cpia2_read
+			 * than it would for the camera to send
+			 * cam->num_frames-2 frames before problems
+			 * could occur.
+			 */
+			cam->curbuff->status = FRAME_EMPTY;
+		}
+		cam->curbuff = cam->workbuff;
+		cam->workbuff = cam->workbuff->next;
+		DBG("Changed buffers, work:%d, current:%d\n",
+		    cam->workbuff->num, cam->curbuff->num);
+		return;
+	} else {
+		DBG("Not enough data for an image.\n");
+	}
+
+	cam->workbuff->status = FRAME_ERROR;
+	return;
+}
+
+/******************************************************************************
+ *
+ *  add_APPn
+ *
+ *  Adds a user specified APPn record
+ *****************************************************************************/
+static void add_APPn(struct camera_data *cam)
+{
+	if(cam->APP_len > 0) {
+		cam->workbuff->data[cam->workbuff->length++] = 0xFF;
+		cam->workbuff->data[cam->workbuff->length++] = 0xE0+cam->APPn;
+		cam->workbuff->data[cam->workbuff->length++] = 0;
+		cam->workbuff->data[cam->workbuff->length++] = cam->APP_len+2;
+		memcpy(cam->workbuff->data+cam->workbuff->length,
+		       cam->APP_data, cam->APP_len);
+		cam->workbuff->length += cam->APP_len;
+	}
+}
+
+/******************************************************************************
+ *
+ *  add_COM
+ *
+ *  Adds a user specified COM record
+ *****************************************************************************/
+static void add_COM(struct camera_data *cam)
+{
+	if(cam->COM_len > 0) {
+		cam->workbuff->data[cam->workbuff->length++] = 0xFF;
+		cam->workbuff->data[cam->workbuff->length++] = 0xFE;
+		cam->workbuff->data[cam->workbuff->length++] = 0;
+		cam->workbuff->data[cam->workbuff->length++] = cam->COM_len+2;
+		memcpy(cam->workbuff->data+cam->workbuff->length,
+		       cam->COM_data, cam->COM_len);
+		cam->workbuff->length += cam->COM_len;
+	}
+}
+
+/******************************************************************************
+ *
+ *  cpia2_usb_complete
+ *
+ *  callback when incoming packet is received
+ *****************************************************************************/
+static void cpia2_usb_complete(struct urb *urb)
+{
+	int i;
+	unsigned char *cdata;
+	static bool frame_ready = false;
+	struct camera_data *cam = (struct camera_data *) urb->context;
+
+	if (urb->status!=0) {
+		if (!(urb->status == -ENOENT ||
+		      urb->status == -ECONNRESET ||
+		      urb->status == -ESHUTDOWN))
+		{
+			DBG("urb->status = %d!\n", urb->status);
+		}
+		DBG("Stopping streaming\n");
+		return;
+	}
+
+	if (!cam->streaming || !video_is_registered(&cam->vdev)) {
+		LOG("Will now stop the streaming: streaming = %d, present=%d\n",
+		    cam->streaming, video_is_registered(&cam->vdev));
+		return;
+	}
+
+	/***
+	 * Packet collater
+	 ***/
+	//DBG("Collating %d packets\n", urb->number_of_packets);
+	for (i = 0; i < urb->number_of_packets; i++) {
+		u16 checksum, iso_checksum;
+		int j;
+		int n = urb->iso_frame_desc[i].actual_length;
+		int st = urb->iso_frame_desc[i].status;
+
+		if(cam->workbuff->status == FRAME_READY) {
+			struct framebuf *ptr;
+			/* Try to find an available buffer */
+			DBG("workbuff full, searching\n");
+			for (ptr = cam->workbuff->next;
+			     ptr != cam->workbuff;
+			     ptr = ptr->next)
+			{
+				if (ptr->status == FRAME_EMPTY) {
+					ptr->status = FRAME_READING;
+					ptr->length = 0;
+					break;
+				}
+			}
+			if (ptr == cam->workbuff)
+				break; /* No READING or EMPTY buffers left */
+
+			cam->workbuff = ptr;
+		}
+
+		if (cam->workbuff->status == FRAME_EMPTY ||
+		    cam->workbuff->status == FRAME_ERROR) {
+			cam->workbuff->status = FRAME_READING;
+			cam->workbuff->length = 0;
+		}
+
+		//DBG("   Packet %d length = %d, status = %d\n", i, n, st);
+		cdata = urb->transfer_buffer + urb->iso_frame_desc[i].offset;
+
+		if (st) {
+			LOG("cpia2 data error: [%d] len=%d, status = %d\n",
+			    i, n, st);
+			if(!ALLOW_CORRUPT)
+				cam->workbuff->status = FRAME_ERROR;
+			continue;
+		}
+
+		if(n<=2)
+			continue;
+
+		checksum = 0;
+		for(j=0; j<n-2; ++j)
+			checksum += cdata[j];
+		iso_checksum = cdata[j] + cdata[j+1]*256;
+		if(checksum != iso_checksum) {
+			LOG("checksum mismatch: [%d] len=%d, calculated = %x, checksum = %x\n",
+			    i, n, (int)checksum, (int)iso_checksum);
+			if(!ALLOW_CORRUPT) {
+				cam->workbuff->status = FRAME_ERROR;
+				continue;
+			}
+		}
+		n -= 2;
+
+		if(cam->workbuff->status != FRAME_READING) {
+			if((0xFF == cdata[0] && 0xD8 == cdata[1]) ||
+			   (0xD8 == cdata[0] && 0xFF == cdata[1] &&
+			    0 != cdata[2])) {
+				/* frame is skipped, but increment total
+				 * frame count anyway */
+				cam->frame_count++;
+			}
+			DBG("workbuff not reading, status=%d\n",
+			    cam->workbuff->status);
+			continue;
+		}
+
+		if (cam->frame_size < cam->workbuff->length + n) {
+			ERR("buffer overflow! length: %d, n: %d\n",
+			    cam->workbuff->length, n);
+			cam->workbuff->status = FRAME_ERROR;
+			if(cam->workbuff->length > cam->workbuff->max_length)
+				cam->workbuff->max_length =
+					cam->workbuff->length;
+			continue;
+		}
+
+		if (cam->workbuff->length == 0) {
+			int data_offset;
+			if ((0xD8 == cdata[0]) && (0xFF == cdata[1])) {
+				data_offset = 1;
+			} else if((0xFF == cdata[0]) && (0xD8 == cdata[1])
+				  && (0xFF == cdata[2])) {
+				data_offset = 2;
+			} else {
+				DBG("Ignoring packet, not beginning!\n");
+				continue;
+			}
+			DBG("Start of frame pattern found\n");
+			v4l2_get_timestamp(&cam->workbuff->timestamp);
+			cam->workbuff->seq = cam->frame_count++;
+			cam->workbuff->data[0] = 0xFF;
+			cam->workbuff->data[1] = 0xD8;
+			cam->workbuff->length = 2;
+			add_APPn(cam);
+			add_COM(cam);
+			memcpy(cam->workbuff->data+cam->workbuff->length,
+			       cdata+data_offset, n-data_offset);
+			cam->workbuff->length += n-data_offset;
+		} else if (cam->workbuff->length > 0) {
+			memcpy(cam->workbuff->data + cam->workbuff->length,
+			       cdata, n);
+			cam->workbuff->length += n;
+		}
+
+		if ((cam->workbuff->length >= 3) &&
+		    (cam->workbuff->data[cam->workbuff->length - 3] == 0xFF) &&
+		    (cam->workbuff->data[cam->workbuff->length - 2] == 0xD9) &&
+		    (cam->workbuff->data[cam->workbuff->length - 1] == 0xFF)) {
+			frame_ready = true;
+			cam->workbuff->data[cam->workbuff->length - 1] = 0;
+			cam->workbuff->length -= 1;
+		} else if ((cam->workbuff->length >= 2) &&
+		   (cam->workbuff->data[cam->workbuff->length - 2] == 0xFF) &&
+		   (cam->workbuff->data[cam->workbuff->length - 1] == 0xD9)) {
+			frame_ready = true;
+		}
+
+		if (frame_ready) {
+			DBG("Workbuff image size = %d\n",cam->workbuff->length);
+			process_frame(cam);
+
+			frame_ready = false;
+
+			if (waitqueue_active(&cam->wq_stream))
+				wake_up_interruptible(&cam->wq_stream);
+		}
+	}
+
+	if(cam->streaming) {
+		/* resubmit */
+		urb->dev = cam->dev;
+		if ((i = usb_submit_urb(urb, GFP_ATOMIC)) != 0)
+			ERR("%s: usb_submit_urb ret %d!\n", __func__, i);
+	}
+}
+
+/******************************************************************************
+ *
+ * configure_transfer_mode
+ *
+ *****************************************************************************/
+static int configure_transfer_mode(struct camera_data *cam, unsigned int alt)
+{
+	static unsigned char iso_regs[8][4] = {
+		{0x00, 0x00, 0x00, 0x00},
+		{0x00, 0x00, 0x00, 0x00},
+		{0xB9, 0x00, 0x00, 0x7E},
+		{0xB9, 0x00, 0x01, 0x7E},
+		{0xB9, 0x00, 0x02, 0x7E},
+		{0xB9, 0x00, 0x02, 0xFE},
+		{0xB9, 0x00, 0x03, 0x7E},
+		{0xB9, 0x00, 0x03, 0xFD}
+	};
+	struct cpia2_command cmd;
+	unsigned char reg;
+
+	if (!video_is_registered(&cam->vdev))
+		return -ENODEV;
+
+	/***
+	 * Write the isoc registers according to the alternate selected
+	 ***/
+	cmd.direction = TRANSFER_WRITE;
+	cmd.buffer.block_data[0] = iso_regs[alt][0];
+	cmd.buffer.block_data[1] = iso_regs[alt][1];
+	cmd.buffer.block_data[2] = iso_regs[alt][2];
+	cmd.buffer.block_data[3] = iso_regs[alt][3];
+	cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VC;
+	cmd.start = CPIA2_VC_USB_ISOLIM;
+	cmd.reg_count = 4;
+	cpia2_send_command(cam, &cmd);
+
+	/***
+	 * Enable relevant streams before starting polling.
+	 * First read USB Stream Config Register.
+	 ***/
+	cmd.direction = TRANSFER_READ;
+	cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VC;
+	cmd.start = CPIA2_VC_USB_STRM;
+	cmd.reg_count = 1;
+	cpia2_send_command(cam, &cmd);
+	reg = cmd.buffer.block_data[0];
+
+	/* Clear iso, bulk, and int */
+	reg &= ~(CPIA2_VC_USB_STRM_BLK_ENABLE |
+		 CPIA2_VC_USB_STRM_ISO_ENABLE |
+		 CPIA2_VC_USB_STRM_INT_ENABLE);
+
+	if (alt == USBIF_BULK) {
+		DBG("Enabling bulk xfer\n");
+		reg |= CPIA2_VC_USB_STRM_BLK_ENABLE;	/* Enable Bulk */
+		cam->xfer_mode = XFER_BULK;
+	} else if (alt >= USBIF_ISO_1) {
+		DBG("Enabling ISOC xfer\n");
+		reg |= CPIA2_VC_USB_STRM_ISO_ENABLE;
+		cam->xfer_mode = XFER_ISOC;
+	}
+
+	cmd.buffer.block_data[0] = reg;
+	cmd.direction = TRANSFER_WRITE;
+	cmd.start = CPIA2_VC_USB_STRM;
+	cmd.reg_count = 1;
+	cmd.req_mode = CAMERAACCESS_TYPE_BLOCK | CAMERAACCESS_VC;
+	cpia2_send_command(cam, &cmd);
+
+	return 0;
+}
+
+/******************************************************************************
+ *
+ * cpia2_usb_change_streaming_alternate
+ *
+ *****************************************************************************/
+int cpia2_usb_change_streaming_alternate(struct camera_data *cam,
+					 unsigned int alt)
+{
+	int ret = 0;
+
+	if(alt < USBIF_ISO_1 || alt > USBIF_ISO_6)
+		return -EINVAL;
+
+	if(alt == cam->params.camera_state.stream_mode)
+		return 0;
+
+	cpia2_usb_stream_pause(cam);
+
+	configure_transfer_mode(cam, alt);
+
+	cam->params.camera_state.stream_mode = alt;
+
+	/* Reset the camera to prevent image quality degradation */
+	cpia2_reset_camera(cam);
+
+	cpia2_usb_stream_resume(cam);
+
+	return ret;
+}
+
+/******************************************************************************
+ *
+ * set_alternate
+ *
+ *****************************************************************************/
+static int set_alternate(struct camera_data *cam, unsigned int alt)
+{
+	int ret = 0;
+
+	if(alt == cam->cur_alt)
+		return 0;
+
+	if (cam->cur_alt != USBIF_CMDONLY) {
+		DBG("Changing from alt %d to %d\n", cam->cur_alt, USBIF_CMDONLY);
+		ret = usb_set_interface(cam->dev, cam->iface, USBIF_CMDONLY);
+		if (ret != 0)
+			return ret;
+	}
+	if (alt != USBIF_CMDONLY) {
+		DBG("Changing from alt %d to %d\n", USBIF_CMDONLY, alt);
+		ret = usb_set_interface(cam->dev, cam->iface, alt);
+		if (ret != 0)
+			return ret;
+	}
+
+	cam->old_alt = cam->cur_alt;
+	cam->cur_alt = alt;
+
+	return ret;
+}
+
+/******************************************************************************
+ *
+ * free_sbufs
+ *
+ * Free all cam->sbuf[]. All non-NULL .data and .urb members that are non-NULL
+ * are assumed to be allocated. Non-NULL .urb members are also assumed to be
+ * submitted (and must therefore be killed before they are freed).
+ *****************************************************************************/
+static void free_sbufs(struct camera_data *cam)
+{
+	int i;
+
+	for (i = 0; i < NUM_SBUF; i++) {
+		if(cam->sbuf[i].urb) {
+			usb_kill_urb(cam->sbuf[i].urb);
+			usb_free_urb(cam->sbuf[i].urb);
+			cam->sbuf[i].urb = NULL;
+		}
+		if(cam->sbuf[i].data) {
+			kfree(cam->sbuf[i].data);
+			cam->sbuf[i].data = NULL;
+		}
+	}
+}
+
+/*******
+* Convenience functions
+*******/
+/****************************************************************************
+ *
+ *  write_packet
+ *
+ ***************************************************************************/
+static int write_packet(struct usb_device *udev,
+			u8 request, u8 * registers, u16 start, size_t size)
+{
+	unsigned char *buf;
+	int ret;
+
+	if (!registers || size <= 0)
+		return -EINVAL;
+
+	buf = kmemdup(registers, size, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	ret = usb_control_msg(udev,
+			       usb_sndctrlpipe(udev, 0),
+			       request,
+			       USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			       start,	/* value */
+			       0,	/* index */
+			       buf,	/* buffer */
+			       size,
+			       HZ);
+
+	kfree(buf);
+	return ret;
+}
+
+/****************************************************************************
+ *
+ *  read_packet
+ *
+ ***************************************************************************/
+static int read_packet(struct usb_device *udev,
+		       u8 request, u8 * registers, u16 start, size_t size)
+{
+	unsigned char *buf;
+	int ret;
+
+	if (!registers || size <= 0)
+		return -EINVAL;
+
+	buf = kmalloc(size, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	ret = usb_control_msg(udev,
+			       usb_rcvctrlpipe(udev, 0),
+			       request,
+			       USB_DIR_IN|USB_TYPE_VENDOR|USB_RECIP_DEVICE,
+			       start,	/* value */
+			       0,	/* index */
+			       buf,	/* buffer */
+			       size,
+			       HZ);
+
+	if (ret >= 0)
+		memcpy(registers, buf, size);
+
+	kfree(buf);
+
+	return ret;
+}
+
+/******************************************************************************
+ *
+ *  cpia2_usb_transfer_cmd
+ *
+ *****************************************************************************/
+int cpia2_usb_transfer_cmd(struct camera_data *cam,
+			   void *registers,
+			   u8 request, u8 start, u8 count, u8 direction)
+{
+	int err = 0;
+	struct usb_device *udev = cam->dev;
+
+	if (!udev) {
+		ERR("%s: Internal driver error: udev is NULL\n", __func__);
+		return -EINVAL;
+	}
+
+	if (!registers) {
+		ERR("%s: Internal driver error: register array is NULL\n", __func__);
+		return -EINVAL;
+	}
+
+	if (direction == TRANSFER_READ) {
+		err = read_packet(udev, request, (u8 *)registers, start, count);
+		if (err > 0)
+			err = 0;
+	} else if (direction == TRANSFER_WRITE) {
+		err =write_packet(udev, request, (u8 *)registers, start, count);
+		if (err < 0) {
+			LOG("Control message failed, err val = %d\n", err);
+			LOG("Message: request = 0x%0X, start = 0x%0X\n",
+			    request, start);
+			LOG("Message: count = %d, register[0] = 0x%0X\n",
+			    count, ((unsigned char *) registers)[0]);
+		} else
+			err=0;
+	} else {
+		LOG("Unexpected first byte of direction: %d\n",
+		       direction);
+		return -EINVAL;
+	}
+
+	if(err != 0)
+		LOG("Unexpected error: %d\n", err);
+	return err;
+}
+
+
+/******************************************************************************
+ *
+ *  submit_urbs
+ *
+ *****************************************************************************/
+static int submit_urbs(struct camera_data *cam)
+{
+	struct urb *urb;
+	int fx, err, i, j;
+
+	for(i=0; i<NUM_SBUF; ++i) {
+		if (cam->sbuf[i].data)
+			continue;
+		cam->sbuf[i].data =
+		    kmalloc_array(FRAME_SIZE_PER_DESC, FRAMES_PER_DESC,
+				  GFP_KERNEL);
+		if (!cam->sbuf[i].data) {
+			while (--i >= 0) {
+				kfree(cam->sbuf[i].data);
+				cam->sbuf[i].data = NULL;
+			}
+			return -ENOMEM;
+		}
+	}
+
+	/* We double buffer the Isoc lists, and also know the polling
+	 * interval is every frame (1 == (1 << (bInterval -1))).
+	 */
+	for(i=0; i<NUM_SBUF; ++i) {
+		if(cam->sbuf[i].urb) {
+			continue;
+		}
+		urb = usb_alloc_urb(FRAMES_PER_DESC, GFP_KERNEL);
+		if (!urb) {
+			for (j = 0; j < i; j++)
+				usb_free_urb(cam->sbuf[j].urb);
+			return -ENOMEM;
+		}
+
+		cam->sbuf[i].urb = urb;
+		urb->dev = cam->dev;
+		urb->context = cam;
+		urb->pipe = usb_rcvisocpipe(cam->dev, 1 /*ISOC endpoint*/);
+		urb->transfer_flags = URB_ISO_ASAP;
+		urb->transfer_buffer = cam->sbuf[i].data;
+		urb->complete = cpia2_usb_complete;
+		urb->number_of_packets = FRAMES_PER_DESC;
+		urb->interval = 1;
+		urb->transfer_buffer_length =
+			FRAME_SIZE_PER_DESC * FRAMES_PER_DESC;
+
+		for (fx = 0; fx < FRAMES_PER_DESC; fx++) {
+			urb->iso_frame_desc[fx].offset =
+				FRAME_SIZE_PER_DESC * fx;
+			urb->iso_frame_desc[fx].length = FRAME_SIZE_PER_DESC;
+		}
+	}
+
+
+	/* Queue the ISO urbs, and resubmit in the completion handler */
+	for(i=0; i<NUM_SBUF; ++i) {
+		err = usb_submit_urb(cam->sbuf[i].urb, GFP_KERNEL);
+		if (err) {
+			ERR("usb_submit_urb[%d]() = %d\n", i, err);
+			return err;
+		}
+	}
+
+	return 0;
+}
+
+/******************************************************************************
+ *
+ *  cpia2_usb_stream_start
+ *
+ *****************************************************************************/
+int cpia2_usb_stream_start(struct camera_data *cam, unsigned int alternate)
+{
+	int ret;
+	int old_alt;
+
+	if(cam->streaming)
+		return 0;
+
+	if (cam->flush) {
+		int i;
+		DBG("Flushing buffers\n");
+		for(i=0; i<cam->num_frames; ++i) {
+			cam->buffers[i].status = FRAME_EMPTY;
+			cam->buffers[i].length = 0;
+		}
+		cam->curbuff = &cam->buffers[0];
+		cam->workbuff = cam->curbuff->next;
+		cam->flush = false;
+	}
+
+	old_alt = cam->params.camera_state.stream_mode;
+	cam->params.camera_state.stream_mode = 0;
+	ret = cpia2_usb_change_streaming_alternate(cam, alternate);
+	if (ret < 0) {
+		int ret2;
+		ERR("cpia2_usb_change_streaming_alternate() = %d!\n", ret);
+		cam->params.camera_state.stream_mode = old_alt;
+		ret2 = set_alternate(cam, USBIF_CMDONLY);
+		if (ret2 < 0) {
+			ERR("cpia2_usb_change_streaming_alternate(%d) =%d has already failed. Then tried to call set_alternate(USBIF_CMDONLY) = %d.\n",
+			    alternate, ret, ret2);
+		}
+	} else {
+		cam->frame_count = 0;
+		cam->streaming = 1;
+		ret = cpia2_usb_stream_resume(cam);
+	}
+	return ret;
+}
+
+/******************************************************************************
+ *
+ *  cpia2_usb_stream_pause
+ *
+ *****************************************************************************/
+int cpia2_usb_stream_pause(struct camera_data *cam)
+{
+	int ret = 0;
+	if(cam->streaming) {
+		free_sbufs(cam);
+		ret = set_alternate(cam, USBIF_CMDONLY);
+	}
+	return ret;
+}
+
+/******************************************************************************
+ *
+ *  cpia2_usb_stream_resume
+ *
+ *****************************************************************************/
+int cpia2_usb_stream_resume(struct camera_data *cam)
+{
+	int ret = 0;
+	if(cam->streaming) {
+		cam->first_image_seen = 0;
+		ret = set_alternate(cam, cam->params.camera_state.stream_mode);
+		if(ret == 0) {
+			/* for some reason the user effects need to be set
+			   again when starting streaming. */
+			cpia2_do_command(cam, CPIA2_CMD_SET_USER_EFFECTS, TRANSFER_WRITE,
+					cam->params.vp_params.user_effects);
+			ret = submit_urbs(cam);
+		}
+	}
+	return ret;
+}
+
+/******************************************************************************
+ *
+ *  cpia2_usb_stream_stop
+ *
+ *****************************************************************************/
+int cpia2_usb_stream_stop(struct camera_data *cam)
+{
+	int ret;
+
+	ret = cpia2_usb_stream_pause(cam);
+	cam->streaming = 0;
+	configure_transfer_mode(cam, 0);
+	return ret;
+}
+
+/******************************************************************************
+ *
+ *  cpia2_usb_probe
+ *
+ *  Probe and initialize.
+ *****************************************************************************/
+static int cpia2_usb_probe(struct usb_interface *intf,
+			   const struct usb_device_id *id)
+{
+	struct usb_device *udev = interface_to_usbdev(intf);
+	struct usb_interface_descriptor *interface;
+	struct camera_data *cam;
+	int ret;
+
+	/* A multi-config CPiA2 camera? */
+	if (udev->descriptor.bNumConfigurations != 1)
+		return -ENODEV;
+	interface = &intf->cur_altsetting->desc;
+
+	/* If we get to this point, we found a CPiA2 camera */
+	LOG("CPiA2 USB camera found\n");
+
+	cam = cpia2_init_camera_struct(intf);
+	if (cam == NULL)
+		return -ENOMEM;
+
+	cam->dev = udev;
+	cam->iface = interface->bInterfaceNumber;
+
+	ret = set_alternate(cam, USBIF_CMDONLY);
+	if (ret < 0) {
+		ERR("%s: usb_set_interface error (ret = %d)\n", __func__, ret);
+		kfree(cam);
+		return ret;
+	}
+
+
+	if((ret = cpia2_init_camera(cam)) < 0) {
+		ERR("%s: failed to initialize cpia2 camera (ret = %d)\n", __func__, ret);
+		kfree(cam);
+		return ret;
+	}
+	LOG("  CPiA Version: %d.%02d (%d.%d)\n",
+	       cam->params.version.firmware_revision_hi,
+	       cam->params.version.firmware_revision_lo,
+	       cam->params.version.asic_id,
+	       cam->params.version.asic_rev);
+	LOG("  CPiA PnP-ID: %04x:%04x:%04x\n",
+	       cam->params.pnp_id.vendor,
+	       cam->params.pnp_id.product,
+	       cam->params.pnp_id.device_revision);
+	LOG("  SensorID: %d.(version %d)\n",
+	       cam->params.version.sensor_flags,
+	       cam->params.version.sensor_rev);
+
+	usb_set_intfdata(intf, cam);
+
+	ret = cpia2_register_camera(cam);
+	if (ret < 0) {
+		ERR("%s: Failed to register cpia2 camera (ret = %d)\n", __func__, ret);
+		kfree(cam);
+		return ret;
+	}
+
+	return 0;
+}
+
+/******************************************************************************
+ *
+ *  cpia2_disconnect
+ *
+ *****************************************************************************/
+static void cpia2_usb_disconnect(struct usb_interface *intf)
+{
+	struct camera_data *cam = usb_get_intfdata(intf);
+	usb_set_intfdata(intf, NULL);
+
+	DBG("Stopping stream\n");
+	cpia2_usb_stream_stop(cam);
+
+	mutex_lock(&cam->v4l2_lock);
+	DBG("Unregistering camera\n");
+	cpia2_unregister_camera(cam);
+	v4l2_device_disconnect(&cam->v4l2_dev);
+	mutex_unlock(&cam->v4l2_lock);
+	v4l2_device_put(&cam->v4l2_dev);
+
+	if(cam->buffers) {
+		DBG("Wakeup waiting processes\n");
+		cam->curbuff->status = FRAME_READY;
+		cam->curbuff->length = 0;
+		wake_up_interruptible(&cam->wq_stream);
+	}
+
+	LOG("CPiA2 camera disconnected.\n");
+}
+
+static int cpia2_usb_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	struct camera_data *cam = usb_get_intfdata(intf);
+
+	mutex_lock(&cam->v4l2_lock);
+	if (cam->streaming) {
+		cpia2_usb_stream_stop(cam);
+		cam->streaming = 1;
+	}
+	mutex_unlock(&cam->v4l2_lock);
+
+	dev_info(&intf->dev, "going into suspend..\n");
+	return 0;
+}
+
+/* Resume device - start device. */
+static int cpia2_usb_resume(struct usb_interface *intf)
+{
+	struct camera_data *cam = usb_get_intfdata(intf);
+
+	mutex_lock(&cam->v4l2_lock);
+	v4l2_ctrl_handler_setup(&cam->hdl);
+	if (cam->streaming) {
+		cam->streaming = 0;
+		cpia2_usb_stream_start(cam,
+				cam->params.camera_state.stream_mode);
+	}
+	mutex_unlock(&cam->v4l2_lock);
+
+	dev_info(&intf->dev, "coming out of suspend..\n");
+	return 0;
+}
+
+/******************************************************************************
+ *
+ *  usb_cpia2_init
+ *
+ *****************************************************************************/
+int cpia2_usb_init(void)
+{
+	return usb_register(&cpia2_driver);
+}
+
+/******************************************************************************
+ *
+ *  usb_cpia_cleanup
+ *
+ *****************************************************************************/
+void cpia2_usb_cleanup(void)
+{
+	schedule_timeout(2 * HZ);
+	usb_deregister(&cpia2_driver);
+}
diff --git a/drivers/media/usb/cpia2/cpia2_v4l.c b/drivers/media/usb/cpia2/cpia2_v4l.c
new file mode 100644
index 0000000..99f106b
--- /dev/null
+++ b/drivers/media/usb/cpia2/cpia2_v4l.c
@@ -0,0 +1,1264 @@
+/****************************************************************************
+ *
+ *  Filename: cpia2_v4l.c
+ *
+ *  Copyright 2001, STMicrolectronics, Inc.
+ *      Contact:  steve.miller@st.com
+ *  Copyright 2001,2005, Scott J. Bertin <scottbertin@yahoo.com>
+ *
+ *  Description:
+ *     This is a USB driver for CPia2 based video cameras.
+ *     The infrastructure of this driver is based on the cpia usb driver by
+ *     Jochen Scharrlach and Johannes Erdfeldt.
+ *
+ *  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.
+ *
+ *  Stripped of 2.4 stuff ready for main kernel submit by
+ *		Alan Cox <alan@lxorguk.ukuu.org.uk>
+ ****************************************************************************/
+
+#define CPIA_VERSION "3.0.1"
+
+#include <linux/module.h>
+#include <linux/time.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/videodev2.h>
+#include <linux/stringify.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-event.h>
+
+#include "cpia2.h"
+
+static int video_nr = -1;
+module_param(video_nr, int, 0);
+MODULE_PARM_DESC(video_nr, "video device to register (0=/dev/video0, etc)");
+
+static int buffer_size = 68 * 1024;
+module_param(buffer_size, int, 0);
+MODULE_PARM_DESC(buffer_size, "Size for each frame buffer in bytes (default 68k)");
+
+static int num_buffers = 3;
+module_param(num_buffers, int, 0);
+MODULE_PARM_DESC(num_buffers, "Number of frame buffers (1-"
+		 __stringify(VIDEO_MAX_FRAME) ", default 3)");
+
+static int alternate = DEFAULT_ALT;
+module_param(alternate, int, 0);
+MODULE_PARM_DESC(alternate, "USB Alternate (" __stringify(USBIF_ISO_1) "-"
+		 __stringify(USBIF_ISO_6) ", default "
+		 __stringify(DEFAULT_ALT) ")");
+
+static int flicker_mode;
+module_param(flicker_mode, int, 0);
+MODULE_PARM_DESC(flicker_mode, "Flicker frequency (0 (disabled), " __stringify(50) " or "
+		 __stringify(60) ", default 0)");
+
+MODULE_AUTHOR("Steve Miller (STMicroelectronics) <steve.miller@st.com>");
+MODULE_DESCRIPTION("V4L-driver for STMicroelectronics CPiA2 based cameras");
+MODULE_SUPPORTED_DEVICE("video");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(CPIA_VERSION);
+
+#define ABOUT "V4L-Driver for Vision CPiA2 based cameras"
+#define CPIA2_CID_USB_ALT (V4L2_CID_USER_BASE | 0xf000)
+
+/******************************************************************************
+ *
+ *  cpia2_open
+ *
+ *****************************************************************************/
+static int cpia2_open(struct file *file)
+{
+	struct camera_data *cam = video_drvdata(file);
+	int retval;
+
+	if (mutex_lock_interruptible(&cam->v4l2_lock))
+		return -ERESTARTSYS;
+	retval = v4l2_fh_open(file);
+	if (retval)
+		goto open_unlock;
+
+	if (v4l2_fh_is_singular_file(file)) {
+		if (cpia2_allocate_buffers(cam)) {
+			v4l2_fh_release(file);
+			retval = -ENOMEM;
+			goto open_unlock;
+		}
+
+		/* reset the camera */
+		if (cpia2_reset_camera(cam) < 0) {
+			v4l2_fh_release(file);
+			retval = -EIO;
+			goto open_unlock;
+		}
+
+		cam->APP_len = 0;
+		cam->COM_len = 0;
+	}
+
+	cpia2_dbg_dump_registers(cam);
+open_unlock:
+	mutex_unlock(&cam->v4l2_lock);
+	return retval;
+}
+
+/******************************************************************************
+ *
+ *  cpia2_close
+ *
+ *****************************************************************************/
+static int cpia2_close(struct file *file)
+{
+	struct video_device *dev = video_devdata(file);
+	struct camera_data *cam = video_get_drvdata(dev);
+
+	mutex_lock(&cam->v4l2_lock);
+	if (video_is_registered(&cam->vdev) && v4l2_fh_is_singular_file(file)) {
+		cpia2_usb_stream_stop(cam);
+
+		/* save camera state for later open */
+		cpia2_save_camera_state(cam);
+
+		cpia2_set_low_power(cam);
+		cpia2_free_buffers(cam);
+	}
+
+	if (cam->stream_fh == file->private_data) {
+		cam->stream_fh = NULL;
+		cam->mmapped = 0;
+	}
+	mutex_unlock(&cam->v4l2_lock);
+	return v4l2_fh_release(file);
+}
+
+/******************************************************************************
+ *
+ *  cpia2_v4l_read
+ *
+ *****************************************************************************/
+static ssize_t cpia2_v4l_read(struct file *file, char __user *buf, size_t count,
+			      loff_t *off)
+{
+	struct camera_data *cam = video_drvdata(file);
+	int noblock = file->f_flags&O_NONBLOCK;
+	ssize_t ret;
+
+	if(!cam)
+		return -EINVAL;
+
+	if (mutex_lock_interruptible(&cam->v4l2_lock))
+		return -ERESTARTSYS;
+	ret = cpia2_read(cam, buf, count, noblock);
+	mutex_unlock(&cam->v4l2_lock);
+	return ret;
+}
+
+
+/******************************************************************************
+ *
+ *  cpia2_v4l_poll
+ *
+ *****************************************************************************/
+static __poll_t cpia2_v4l_poll(struct file *filp, struct poll_table_struct *wait)
+{
+	struct camera_data *cam = video_drvdata(filp);
+	__poll_t res;
+
+	mutex_lock(&cam->v4l2_lock);
+	res = cpia2_poll(cam, filp, wait);
+	mutex_unlock(&cam->v4l2_lock);
+	return res;
+}
+
+
+static int sync(struct camera_data *cam, int frame_nr)
+{
+	struct framebuf *frame = &cam->buffers[frame_nr];
+
+	while (1) {
+		if (frame->status == FRAME_READY)
+			return 0;
+
+		if (!cam->streaming) {
+			frame->status = FRAME_READY;
+			frame->length = 0;
+			return 0;
+		}
+
+		mutex_unlock(&cam->v4l2_lock);
+		wait_event_interruptible(cam->wq_stream,
+					 !cam->streaming ||
+					 frame->status == FRAME_READY);
+		mutex_lock(&cam->v4l2_lock);
+		if (signal_pending(current))
+			return -ERESTARTSYS;
+		if (!video_is_registered(&cam->vdev))
+			return -ENOTTY;
+	}
+}
+
+/******************************************************************************
+ *
+ *  ioctl_querycap
+ *
+ *  V4L2 device capabilities
+ *
+ *****************************************************************************/
+
+static int cpia2_querycap(struct file *file, void *fh, struct v4l2_capability *vc)
+{
+	struct camera_data *cam = video_drvdata(file);
+
+	strcpy(vc->driver, "cpia2");
+
+	if (cam->params.pnp_id.product == 0x151)
+		strcpy(vc->card, "QX5 Microscope");
+	else
+		strcpy(vc->card, "CPiA2 Camera");
+	switch (cam->params.pnp_id.device_type) {
+	case DEVICE_STV_672:
+		strcat(vc->card, " (672/");
+		break;
+	case DEVICE_STV_676:
+		strcat(vc->card, " (676/");
+		break;
+	default:
+		strcat(vc->card, " (XXX/");
+		break;
+	}
+	switch (cam->params.version.sensor_flags) {
+	case CPIA2_VP_SENSOR_FLAGS_404:
+		strcat(vc->card, "404)");
+		break;
+	case CPIA2_VP_SENSOR_FLAGS_407:
+		strcat(vc->card, "407)");
+		break;
+	case CPIA2_VP_SENSOR_FLAGS_409:
+		strcat(vc->card, "409)");
+		break;
+	case CPIA2_VP_SENSOR_FLAGS_410:
+		strcat(vc->card, "410)");
+		break;
+	case CPIA2_VP_SENSOR_FLAGS_500:
+		strcat(vc->card, "500)");
+		break;
+	default:
+		strcat(vc->card, "XXX)");
+		break;
+	}
+
+	if (usb_make_path(cam->dev, vc->bus_info, sizeof(vc->bus_info)) <0)
+		memset(vc->bus_info,0, sizeof(vc->bus_info));
+
+	vc->device_caps = V4L2_CAP_VIDEO_CAPTURE |
+			   V4L2_CAP_READWRITE |
+			   V4L2_CAP_STREAMING;
+	vc->capabilities = vc->device_caps |
+			   V4L2_CAP_DEVICE_CAPS;
+
+	return 0;
+}
+
+/******************************************************************************
+ *
+ *  ioctl_input
+ *
+ *  V4L2 input get/set/enumerate
+ *
+ *****************************************************************************/
+
+static int cpia2_enum_input(struct file *file, void *fh, struct v4l2_input *i)
+{
+	if (i->index)
+		return -EINVAL;
+	strcpy(i->name, "Camera");
+	i->type = V4L2_INPUT_TYPE_CAMERA;
+	return 0;
+}
+
+static int cpia2_g_input(struct file *file, void *fh, unsigned int *i)
+{
+	*i = 0;
+	return 0;
+}
+
+static int cpia2_s_input(struct file *file, void *fh, unsigned int i)
+{
+	return i ? -EINVAL : 0;
+}
+
+/******************************************************************************
+ *
+ *  ioctl_enum_fmt
+ *
+ *  V4L2 format enumerate
+ *
+ *****************************************************************************/
+
+static int cpia2_enum_fmt_vid_cap(struct file *file, void *fh,
+					    struct v4l2_fmtdesc *f)
+{
+	int index = f->index;
+
+	if (index < 0 || index > 1)
+	       return -EINVAL;
+
+	memset(f, 0, sizeof(*f));
+	f->index = index;
+	f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	f->flags = V4L2_FMT_FLAG_COMPRESSED;
+	switch(index) {
+	case 0:
+		strcpy(f->description, "MJPEG");
+		f->pixelformat = V4L2_PIX_FMT_MJPEG;
+		break;
+	case 1:
+		strcpy(f->description, "JPEG");
+		f->pixelformat = V4L2_PIX_FMT_JPEG;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/******************************************************************************
+ *
+ *  ioctl_try_fmt
+ *
+ *  V4L2 format try
+ *
+ *****************************************************************************/
+
+static int cpia2_try_fmt_vid_cap(struct file *file, void *fh,
+					  struct v4l2_format *f)
+{
+	struct camera_data *cam = video_drvdata(file);
+
+	if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_MJPEG &&
+	    f->fmt.pix.pixelformat != V4L2_PIX_FMT_JPEG)
+	       return -EINVAL;
+
+	f->fmt.pix.field = V4L2_FIELD_NONE;
+	f->fmt.pix.bytesperline = 0;
+	f->fmt.pix.sizeimage = cam->frame_size;
+	f->fmt.pix.colorspace = V4L2_COLORSPACE_JPEG;
+	f->fmt.pix.priv = 0;
+
+	switch (cpia2_match_video_size(f->fmt.pix.width, f->fmt.pix.height)) {
+	case VIDEOSIZE_VGA:
+		f->fmt.pix.width = 640;
+		f->fmt.pix.height = 480;
+		break;
+	case VIDEOSIZE_CIF:
+		f->fmt.pix.width = 352;
+		f->fmt.pix.height = 288;
+		break;
+	case VIDEOSIZE_QVGA:
+		f->fmt.pix.width = 320;
+		f->fmt.pix.height = 240;
+		break;
+	case VIDEOSIZE_288_216:
+		f->fmt.pix.width = 288;
+		f->fmt.pix.height = 216;
+		break;
+	case VIDEOSIZE_256_192:
+		f->fmt.pix.width = 256;
+		f->fmt.pix.height = 192;
+		break;
+	case VIDEOSIZE_224_168:
+		f->fmt.pix.width = 224;
+		f->fmt.pix.height = 168;
+		break;
+	case VIDEOSIZE_192_144:
+		f->fmt.pix.width = 192;
+		f->fmt.pix.height = 144;
+		break;
+	case VIDEOSIZE_QCIF:
+	default:
+		f->fmt.pix.width = 176;
+		f->fmt.pix.height = 144;
+		break;
+	}
+
+	return 0;
+}
+
+/******************************************************************************
+ *
+ *  ioctl_set_fmt
+ *
+ *  V4L2 format set
+ *
+ *****************************************************************************/
+
+static int cpia2_s_fmt_vid_cap(struct file *file, void *_fh,
+					struct v4l2_format *f)
+{
+	struct camera_data *cam = video_drvdata(file);
+	int err, frame;
+
+	err = cpia2_try_fmt_vid_cap(file, _fh, f);
+	if(err != 0)
+		return err;
+
+	cam->pixelformat = f->fmt.pix.pixelformat;
+
+	/* NOTE: This should be set to 1 for MJPEG, but some apps don't handle
+	 * the missing Huffman table properly. */
+	cam->params.compression.inhibit_htables = 0;
+		/*f->fmt.pix.pixelformat == V4L2_PIX_FMT_MJPEG;*/
+
+	/* we set the video window to something smaller or equal to what
+	 * is requested by the user???
+	 */
+	DBG("Requested width = %d, height = %d\n",
+	    f->fmt.pix.width, f->fmt.pix.height);
+	if (f->fmt.pix.width != cam->width ||
+	    f->fmt.pix.height != cam->height) {
+		cam->width = f->fmt.pix.width;
+		cam->height = f->fmt.pix.height;
+		cam->params.roi.width = f->fmt.pix.width;
+		cam->params.roi.height = f->fmt.pix.height;
+		cpia2_set_format(cam);
+	}
+
+	for (frame = 0; frame < cam->num_frames; ++frame) {
+		if (cam->buffers[frame].status == FRAME_READING)
+			if ((err = sync(cam, frame)) < 0)
+				return err;
+
+		cam->buffers[frame].status = FRAME_EMPTY;
+	}
+
+	return 0;
+}
+
+/******************************************************************************
+ *
+ *  ioctl_get_fmt
+ *
+ *  V4L2 format get
+ *
+ *****************************************************************************/
+
+static int cpia2_g_fmt_vid_cap(struct file *file, void *fh,
+					struct v4l2_format *f)
+{
+	struct camera_data *cam = video_drvdata(file);
+
+	f->fmt.pix.width = cam->width;
+	f->fmt.pix.height = cam->height;
+	f->fmt.pix.pixelformat = cam->pixelformat;
+	f->fmt.pix.field = V4L2_FIELD_NONE;
+	f->fmt.pix.bytesperline = 0;
+	f->fmt.pix.sizeimage = cam->frame_size;
+	f->fmt.pix.colorspace = V4L2_COLORSPACE_JPEG;
+	f->fmt.pix.priv = 0;
+
+	return 0;
+}
+
+/******************************************************************************
+ *
+ *  ioctl_cropcap
+ *
+ *  V4L2 query cropping capabilities
+ *  NOTE: cropping is currently disabled
+ *
+ *****************************************************************************/
+
+static int cpia2_cropcap(struct file *file, void *fh, struct v4l2_cropcap *c)
+{
+	struct camera_data *cam = video_drvdata(file);
+
+	if (c->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+	       return -EINVAL;
+
+	c->bounds.left = 0;
+	c->bounds.top = 0;
+	c->bounds.width = cam->width;
+	c->bounds.height = cam->height;
+	c->defrect.left = 0;
+	c->defrect.top = 0;
+	c->defrect.width = cam->width;
+	c->defrect.height = cam->height;
+	c->pixelaspect.numerator = 1;
+	c->pixelaspect.denominator = 1;
+
+	return 0;
+}
+
+struct framerate_info {
+	int value;
+	struct v4l2_fract period;
+};
+
+static const struct framerate_info framerate_controls[] = {
+	{ CPIA2_VP_FRAMERATE_6_25, { 4, 25 } },
+	{ CPIA2_VP_FRAMERATE_7_5,  { 2, 15 } },
+	{ CPIA2_VP_FRAMERATE_12_5, { 2, 25 } },
+	{ CPIA2_VP_FRAMERATE_15,   { 1, 15 } },
+	{ CPIA2_VP_FRAMERATE_25,   { 1, 25 } },
+	{ CPIA2_VP_FRAMERATE_30,   { 1, 30 } },
+};
+
+static int cpia2_g_parm(struct file *file, void *fh, struct v4l2_streamparm *p)
+{
+	struct camera_data *cam = video_drvdata(file);
+	struct v4l2_captureparm *cap = &p->parm.capture;
+	int i;
+
+	if (p->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	cap->capability = V4L2_CAP_TIMEPERFRAME;
+	cap->readbuffers = cam->num_frames;
+	for (i = 0; i < ARRAY_SIZE(framerate_controls); i++)
+		if (cam->params.vp_params.frame_rate == framerate_controls[i].value) {
+			cap->timeperframe = framerate_controls[i].period;
+			break;
+		}
+	return 0;
+}
+
+static int cpia2_s_parm(struct file *file, void *fh, struct v4l2_streamparm *p)
+{
+	struct camera_data *cam = video_drvdata(file);
+	struct v4l2_captureparm *cap = &p->parm.capture;
+	struct v4l2_fract tpf = cap->timeperframe;
+	int max = ARRAY_SIZE(framerate_controls) - 1;
+	int ret;
+	int i;
+
+	ret = cpia2_g_parm(file, fh, p);
+	if (ret || !tpf.denominator || !tpf.numerator)
+		return ret;
+
+	/* Maximum 15 fps for this model */
+	if (cam->params.pnp_id.device_type == DEVICE_STV_672 &&
+	    cam->params.version.sensor_flags == CPIA2_VP_SENSOR_FLAGS_500)
+		max -= 2;
+	for (i = 0; i <= max; i++) {
+		struct v4l2_fract f1 = tpf;
+		struct v4l2_fract f2 = framerate_controls[i].period;
+
+		f1.numerator *= f2.denominator;
+		f2.numerator *= f1.denominator;
+		if (f1.numerator >= f2.numerator)
+			break;
+	}
+	if (i > max)
+		i = max;
+	cap->timeperframe = framerate_controls[i].period;
+	return cpia2_set_fps(cam, framerate_controls[i].value);
+}
+
+static const struct {
+	u32 width;
+	u32 height;
+} cpia2_framesizes[] = {
+	{ 640, 480 },
+	{ 352, 288 },
+	{ 320, 240 },
+	{ 288, 216 },
+	{ 256, 192 },
+	{ 224, 168 },
+	{ 192, 144 },
+	{ 176, 144 },
+};
+
+static int cpia2_enum_framesizes(struct file *file, void *fh,
+					 struct v4l2_frmsizeenum *fsize)
+{
+
+	if (fsize->pixel_format != V4L2_PIX_FMT_MJPEG &&
+	    fsize->pixel_format != V4L2_PIX_FMT_JPEG)
+		return -EINVAL;
+	if (fsize->index >= ARRAY_SIZE(cpia2_framesizes))
+		return -EINVAL;
+	fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+	fsize->discrete.width = cpia2_framesizes[fsize->index].width;
+	fsize->discrete.height = cpia2_framesizes[fsize->index].height;
+
+	return 0;
+}
+
+static int cpia2_enum_frameintervals(struct file *file, void *fh,
+					   struct v4l2_frmivalenum *fival)
+{
+	struct camera_data *cam = video_drvdata(file);
+	int max = ARRAY_SIZE(framerate_controls) - 1;
+	int i;
+
+	if (fival->pixel_format != V4L2_PIX_FMT_MJPEG &&
+	    fival->pixel_format != V4L2_PIX_FMT_JPEG)
+		return -EINVAL;
+
+	/* Maximum 15 fps for this model */
+	if (cam->params.pnp_id.device_type == DEVICE_STV_672 &&
+	    cam->params.version.sensor_flags == CPIA2_VP_SENSOR_FLAGS_500)
+		max -= 2;
+	if (fival->index > max)
+		return -EINVAL;
+	for (i = 0; i < ARRAY_SIZE(cpia2_framesizes); i++)
+		if (fival->width == cpia2_framesizes[i].width &&
+		    fival->height == cpia2_framesizes[i].height)
+			break;
+	if (i == ARRAY_SIZE(cpia2_framesizes))
+		return -EINVAL;
+	fival->type = V4L2_FRMIVAL_TYPE_DISCRETE;
+	fival->discrete = framerate_controls[fival->index].period;
+	return 0;
+}
+
+/******************************************************************************
+ *
+ *  ioctl_s_ctrl
+ *
+ *  V4L2 set the value of a control variable
+ *
+ *****************************************************************************/
+
+static int cpia2_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct camera_data *cam =
+		container_of(ctrl->handler, struct camera_data, hdl);
+	static const int flicker_table[] = {
+		NEVER_FLICKER,
+		FLICKER_50,
+		FLICKER_60,
+	};
+
+	DBG("Set control id:%d, value:%d\n", ctrl->id, ctrl->val);
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		cpia2_set_brightness(cam, ctrl->val);
+		break;
+	case V4L2_CID_CONTRAST:
+		cpia2_set_contrast(cam, ctrl->val);
+		break;
+	case V4L2_CID_SATURATION:
+		cpia2_set_saturation(cam, ctrl->val);
+		break;
+	case V4L2_CID_HFLIP:
+		cpia2_set_property_mirror(cam, ctrl->val);
+		break;
+	case V4L2_CID_VFLIP:
+		cpia2_set_property_flip(cam, ctrl->val);
+		break;
+	case V4L2_CID_POWER_LINE_FREQUENCY:
+		return cpia2_set_flicker_mode(cam, flicker_table[ctrl->val]);
+	case V4L2_CID_ILLUMINATORS_1:
+		return cpia2_set_gpio(cam, (cam->top_light->val << 6) |
+					   (cam->bottom_light->val << 7));
+	case V4L2_CID_JPEG_ACTIVE_MARKER:
+		cam->params.compression.inhibit_htables =
+			!(ctrl->val & V4L2_JPEG_ACTIVE_MARKER_DHT);
+		break;
+	case V4L2_CID_JPEG_COMPRESSION_QUALITY:
+		cam->params.vc_params.quality = ctrl->val;
+		break;
+	case CPIA2_CID_USB_ALT:
+		cam->params.camera_state.stream_mode = ctrl->val;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/******************************************************************************
+ *
+ *  ioctl_g_jpegcomp
+ *
+ *  V4L2 get the JPEG compression parameters
+ *
+ *****************************************************************************/
+
+static int cpia2_g_jpegcomp(struct file *file, void *fh, struct v4l2_jpegcompression *parms)
+{
+	struct camera_data *cam = video_drvdata(file);
+
+	memset(parms, 0, sizeof(*parms));
+
+	parms->quality = 80; // TODO: Can this be made meaningful?
+
+	parms->jpeg_markers = V4L2_JPEG_MARKER_DQT | V4L2_JPEG_MARKER_DRI;
+	if(!cam->params.compression.inhibit_htables) {
+		parms->jpeg_markers |= V4L2_JPEG_MARKER_DHT;
+	}
+
+	parms->APPn = cam->APPn;
+	parms->APP_len = cam->APP_len;
+	if(cam->APP_len > 0) {
+		memcpy(parms->APP_data, cam->APP_data, cam->APP_len);
+		parms->jpeg_markers |= V4L2_JPEG_MARKER_APP;
+	}
+
+	parms->COM_len = cam->COM_len;
+	if(cam->COM_len > 0) {
+		memcpy(parms->COM_data, cam->COM_data, cam->COM_len);
+		parms->jpeg_markers |= JPEG_MARKER_COM;
+	}
+
+	DBG("G_JPEGCOMP APP_len:%d COM_len:%d\n",
+	    parms->APP_len, parms->COM_len);
+
+	return 0;
+}
+
+/******************************************************************************
+ *
+ *  ioctl_s_jpegcomp
+ *
+ *  V4L2 set the JPEG compression parameters
+ *  NOTE: quality and some jpeg_markers are ignored.
+ *
+ *****************************************************************************/
+
+static int cpia2_s_jpegcomp(struct file *file, void *fh,
+		const struct v4l2_jpegcompression *parms)
+{
+	struct camera_data *cam = video_drvdata(file);
+
+	DBG("S_JPEGCOMP APP_len:%d COM_len:%d\n",
+	    parms->APP_len, parms->COM_len);
+
+	cam->params.compression.inhibit_htables =
+		!(parms->jpeg_markers & V4L2_JPEG_MARKER_DHT);
+
+	if(parms->APP_len != 0) {
+		if(parms->APP_len > 0 &&
+		   parms->APP_len <= sizeof(cam->APP_data) &&
+		   parms->APPn >= 0 && parms->APPn <= 15) {
+			cam->APPn = parms->APPn;
+			cam->APP_len = parms->APP_len;
+			memcpy(cam->APP_data, parms->APP_data, parms->APP_len);
+		} else {
+			LOG("Bad APPn Params n=%d len=%d\n",
+			    parms->APPn, parms->APP_len);
+			return -EINVAL;
+		}
+	} else {
+		cam->APP_len = 0;
+	}
+
+	if(parms->COM_len != 0) {
+		if(parms->COM_len > 0 &&
+		   parms->COM_len <= sizeof(cam->COM_data)) {
+			cam->COM_len = parms->COM_len;
+			memcpy(cam->COM_data, parms->COM_data, parms->COM_len);
+		} else {
+			LOG("Bad COM_len=%d\n", parms->COM_len);
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+/******************************************************************************
+ *
+ *  ioctl_reqbufs
+ *
+ *  V4L2 Initiate memory mapping.
+ *  NOTE: The user's request is ignored. For now the buffers are fixed.
+ *
+ *****************************************************************************/
+
+static int cpia2_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *req)
+{
+	struct camera_data *cam = video_drvdata(file);
+
+	if(req->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
+	   req->memory != V4L2_MEMORY_MMAP)
+		return -EINVAL;
+
+	DBG("REQBUFS requested:%d returning:%d\n", req->count, cam->num_frames);
+	req->count = cam->num_frames;
+	memset(&req->reserved, 0, sizeof(req->reserved));
+
+	return 0;
+}
+
+/******************************************************************************
+ *
+ *  ioctl_querybuf
+ *
+ *  V4L2 Query memory buffer status.
+ *
+ *****************************************************************************/
+
+static int cpia2_querybuf(struct file *file, void *fh, struct v4l2_buffer *buf)
+{
+	struct camera_data *cam = video_drvdata(file);
+
+	if(buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
+	   buf->index >= cam->num_frames)
+		return -EINVAL;
+
+	buf->m.offset = cam->buffers[buf->index].data - cam->frame_buffer;
+	buf->length = cam->frame_size;
+
+	buf->memory = V4L2_MEMORY_MMAP;
+
+	if(cam->mmapped)
+		buf->flags = V4L2_BUF_FLAG_MAPPED;
+	else
+		buf->flags = 0;
+
+	buf->flags |= V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+
+	switch (cam->buffers[buf->index].status) {
+	case FRAME_EMPTY:
+	case FRAME_ERROR:
+	case FRAME_READING:
+		buf->bytesused = 0;
+		buf->flags = V4L2_BUF_FLAG_QUEUED;
+		break;
+	case FRAME_READY:
+		buf->bytesused = cam->buffers[buf->index].length;
+		buf->timestamp = cam->buffers[buf->index].timestamp;
+		buf->sequence = cam->buffers[buf->index].seq;
+		buf->flags = V4L2_BUF_FLAG_DONE;
+		break;
+	}
+
+	DBG("QUERYBUF index:%d offset:%d flags:%d seq:%d bytesused:%d\n",
+	     buf->index, buf->m.offset, buf->flags, buf->sequence,
+	     buf->bytesused);
+
+	return 0;
+}
+
+/******************************************************************************
+ *
+ *  ioctl_qbuf
+ *
+ *  V4L2 User is freeing buffer
+ *
+ *****************************************************************************/
+
+static int cpia2_qbuf(struct file *file, void *fh, struct v4l2_buffer *buf)
+{
+	struct camera_data *cam = video_drvdata(file);
+
+	if(buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
+	   buf->memory != V4L2_MEMORY_MMAP ||
+	   buf->index >= cam->num_frames)
+		return -EINVAL;
+
+	DBG("QBUF #%d\n", buf->index);
+
+	if(cam->buffers[buf->index].status == FRAME_READY)
+		cam->buffers[buf->index].status = FRAME_EMPTY;
+
+	return 0;
+}
+
+/******************************************************************************
+ *
+ *  find_earliest_filled_buffer
+ *
+ *  Helper for ioctl_dqbuf. Find the next ready buffer.
+ *
+ *****************************************************************************/
+
+static int find_earliest_filled_buffer(struct camera_data *cam)
+{
+	int i;
+	int found = -1;
+	for (i=0; i<cam->num_frames; i++) {
+		if(cam->buffers[i].status == FRAME_READY) {
+			if(found < 0) {
+				found = i;
+			} else {
+				/* find which buffer is earlier */
+				struct timeval *tv1, *tv2;
+				tv1 = &cam->buffers[i].timestamp;
+				tv2 = &cam->buffers[found].timestamp;
+				if(tv1->tv_sec < tv2->tv_sec ||
+				   (tv1->tv_sec == tv2->tv_sec &&
+				    tv1->tv_usec < tv2->tv_usec))
+					found = i;
+			}
+		}
+	}
+	return found;
+}
+
+/******************************************************************************
+ *
+ *  ioctl_dqbuf
+ *
+ *  V4L2 User is asking for a filled buffer.
+ *
+ *****************************************************************************/
+
+static int cpia2_dqbuf(struct file *file, void *fh, struct v4l2_buffer *buf)
+{
+	struct camera_data *cam = video_drvdata(file);
+	int frame;
+
+	if(buf->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
+	   buf->memory != V4L2_MEMORY_MMAP)
+		return -EINVAL;
+
+	frame = find_earliest_filled_buffer(cam);
+
+	if(frame < 0 && file->f_flags&O_NONBLOCK)
+		return -EAGAIN;
+
+	if(frame < 0) {
+		/* Wait for a frame to become available */
+		struct framebuf *cb=cam->curbuff;
+		mutex_unlock(&cam->v4l2_lock);
+		wait_event_interruptible(cam->wq_stream,
+					 !video_is_registered(&cam->vdev) ||
+					 (cb=cam->curbuff)->status == FRAME_READY);
+		mutex_lock(&cam->v4l2_lock);
+		if (signal_pending(current))
+			return -ERESTARTSYS;
+		if (!video_is_registered(&cam->vdev))
+			return -ENOTTY;
+		frame = cb->num;
+	}
+
+
+	buf->index = frame;
+	buf->bytesused = cam->buffers[buf->index].length;
+	buf->flags = V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_DONE
+		| V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	buf->field = V4L2_FIELD_NONE;
+	buf->timestamp = cam->buffers[buf->index].timestamp;
+	buf->sequence = cam->buffers[buf->index].seq;
+	buf->m.offset = cam->buffers[buf->index].data - cam->frame_buffer;
+	buf->length = cam->frame_size;
+	buf->reserved2 = 0;
+	buf->reserved = 0;
+	memset(&buf->timecode, 0, sizeof(buf->timecode));
+
+	DBG("DQBUF #%d status:%d seq:%d length:%d\n", buf->index,
+	    cam->buffers[buf->index].status, buf->sequence, buf->bytesused);
+
+	return 0;
+}
+
+static int cpia2_streamon(struct file *file, void *fh, enum v4l2_buf_type type)
+{
+	struct camera_data *cam = video_drvdata(file);
+	int ret = -EINVAL;
+
+	DBG("VIDIOC_STREAMON, streaming=%d\n", cam->streaming);
+	if (!cam->mmapped || type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	if (!cam->streaming) {
+		ret = cpia2_usb_stream_start(cam,
+				cam->params.camera_state.stream_mode);
+		if (!ret)
+			v4l2_ctrl_grab(cam->usb_alt, true);
+	}
+	return ret;
+}
+
+static int cpia2_streamoff(struct file *file, void *fh, enum v4l2_buf_type type)
+{
+	struct camera_data *cam = video_drvdata(file);
+	int ret = -EINVAL;
+
+	DBG("VIDIOC_STREAMOFF, streaming=%d\n", cam->streaming);
+	if (!cam->mmapped || type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	if (cam->streaming) {
+		ret = cpia2_usb_stream_stop(cam);
+		if (!ret)
+			v4l2_ctrl_grab(cam->usb_alt, false);
+	}
+	return ret;
+}
+
+/******************************************************************************
+ *
+ *  cpia2_mmap
+ *
+ *****************************************************************************/
+static int cpia2_mmap(struct file *file, struct vm_area_struct *area)
+{
+	struct camera_data *cam = video_drvdata(file);
+	int retval;
+
+	if (mutex_lock_interruptible(&cam->v4l2_lock))
+		return -ERESTARTSYS;
+	retval = cpia2_remap_buffer(cam, area);
+
+	if(!retval)
+		cam->stream_fh = file->private_data;
+	mutex_unlock(&cam->v4l2_lock);
+	return retval;
+}
+
+/******************************************************************************
+ *
+ *  reset_camera_struct_v4l
+ *
+ *  Sets all values to the defaults
+ *****************************************************************************/
+static void reset_camera_struct_v4l(struct camera_data *cam)
+{
+	cam->width = cam->params.roi.width;
+	cam->height = cam->params.roi.height;
+
+	cam->frame_size = buffer_size;
+	cam->num_frames = num_buffers;
+
+	/* Flicker modes */
+	cam->params.flicker_control.flicker_mode_req = flicker_mode;
+
+	/* stream modes */
+	cam->params.camera_state.stream_mode = alternate;
+
+	cam->pixelformat = V4L2_PIX_FMT_JPEG;
+}
+
+static const struct v4l2_ioctl_ops cpia2_ioctl_ops = {
+	.vidioc_querycap		    = cpia2_querycap,
+	.vidioc_enum_input		    = cpia2_enum_input,
+	.vidioc_g_input			    = cpia2_g_input,
+	.vidioc_s_input			    = cpia2_s_input,
+	.vidioc_enum_fmt_vid_cap	    = cpia2_enum_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap		    = cpia2_g_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap		    = cpia2_s_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap		    = cpia2_try_fmt_vid_cap,
+	.vidioc_g_jpegcomp		    = cpia2_g_jpegcomp,
+	.vidioc_s_jpegcomp		    = cpia2_s_jpegcomp,
+	.vidioc_cropcap			    = cpia2_cropcap,
+	.vidioc_reqbufs			    = cpia2_reqbufs,
+	.vidioc_querybuf		    = cpia2_querybuf,
+	.vidioc_qbuf			    = cpia2_qbuf,
+	.vidioc_dqbuf			    = cpia2_dqbuf,
+	.vidioc_streamon		    = cpia2_streamon,
+	.vidioc_streamoff		    = cpia2_streamoff,
+	.vidioc_s_parm			    = cpia2_s_parm,
+	.vidioc_g_parm			    = cpia2_g_parm,
+	.vidioc_enum_framesizes		    = cpia2_enum_framesizes,
+	.vidioc_enum_frameintervals	    = cpia2_enum_frameintervals,
+	.vidioc_subscribe_event		    = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event	    = v4l2_event_unsubscribe,
+};
+
+/***
+ * The v4l video device structure initialized for this device
+ ***/
+static const struct v4l2_file_operations cpia2_fops = {
+	.owner		= THIS_MODULE,
+	.open		= cpia2_open,
+	.release	= cpia2_close,
+	.read		= cpia2_v4l_read,
+	.poll		= cpia2_v4l_poll,
+	.unlocked_ioctl	= video_ioctl2,
+	.mmap		= cpia2_mmap,
+};
+
+static const struct video_device cpia2_template = {
+	/* I could not find any place for the old .initialize initializer?? */
+	.name =		"CPiA2 Camera",
+	.fops =		&cpia2_fops,
+	.ioctl_ops =	&cpia2_ioctl_ops,
+	.release =	video_device_release_empty,
+};
+
+void cpia2_camera_release(struct v4l2_device *v4l2_dev)
+{
+	struct camera_data *cam =
+		container_of(v4l2_dev, struct camera_data, v4l2_dev);
+
+	v4l2_ctrl_handler_free(&cam->hdl);
+	v4l2_device_unregister(&cam->v4l2_dev);
+	kfree(cam);
+}
+
+static const struct v4l2_ctrl_ops cpia2_ctrl_ops = {
+	.s_ctrl = cpia2_s_ctrl,
+};
+
+/******************************************************************************
+ *
+ *  cpia2_register_camera
+ *
+ *****************************************************************************/
+int cpia2_register_camera(struct camera_data *cam)
+{
+	struct v4l2_ctrl_handler *hdl = &cam->hdl;
+	struct v4l2_ctrl_config cpia2_usb_alt = {
+		.ops = &cpia2_ctrl_ops,
+		.id = CPIA2_CID_USB_ALT,
+		.name = "USB Alternate",
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.min = USBIF_ISO_1,
+		.max = USBIF_ISO_6,
+		.step = 1,
+	};
+	int ret;
+
+	v4l2_ctrl_handler_init(hdl, 12);
+	v4l2_ctrl_new_std(hdl, &cpia2_ctrl_ops,
+			V4L2_CID_BRIGHTNESS,
+			cam->params.pnp_id.device_type == DEVICE_STV_672 ? 1 : 0,
+			255, 1, DEFAULT_BRIGHTNESS);
+	v4l2_ctrl_new_std(hdl, &cpia2_ctrl_ops,
+			V4L2_CID_CONTRAST, 0, 255, 1, DEFAULT_CONTRAST);
+	v4l2_ctrl_new_std(hdl, &cpia2_ctrl_ops,
+			V4L2_CID_SATURATION, 0, 255, 1, DEFAULT_SATURATION);
+	v4l2_ctrl_new_std(hdl, &cpia2_ctrl_ops,
+			V4L2_CID_HFLIP, 0, 1, 1, 0);
+	v4l2_ctrl_new_std(hdl, &cpia2_ctrl_ops,
+			V4L2_CID_JPEG_ACTIVE_MARKER, 0,
+			V4L2_JPEG_ACTIVE_MARKER_DHT, 0,
+			V4L2_JPEG_ACTIVE_MARKER_DHT);
+	v4l2_ctrl_new_std(hdl, &cpia2_ctrl_ops,
+			V4L2_CID_JPEG_COMPRESSION_QUALITY, 1,
+			100, 1, 100);
+	cpia2_usb_alt.def = alternate;
+	cam->usb_alt = v4l2_ctrl_new_custom(hdl, &cpia2_usb_alt, NULL);
+	/* VP5 Only */
+	if (cam->params.pnp_id.device_type != DEVICE_STV_672)
+		v4l2_ctrl_new_std(hdl, &cpia2_ctrl_ops,
+			V4L2_CID_VFLIP, 0, 1, 1, 0);
+	/* Flicker control only valid for 672 */
+	if (cam->params.pnp_id.device_type == DEVICE_STV_672)
+		v4l2_ctrl_new_std_menu(hdl, &cpia2_ctrl_ops,
+			V4L2_CID_POWER_LINE_FREQUENCY,
+			V4L2_CID_POWER_LINE_FREQUENCY_60HZ, 0, 0);
+	/* Light control only valid for the QX5 Microscope */
+	if (cam->params.pnp_id.product == 0x151) {
+		cam->top_light = v4l2_ctrl_new_std(hdl, &cpia2_ctrl_ops,
+				V4L2_CID_ILLUMINATORS_1, 0, 1, 1, 0);
+		cam->bottom_light = v4l2_ctrl_new_std(hdl, &cpia2_ctrl_ops,
+				V4L2_CID_ILLUMINATORS_2, 0, 1, 1, 0);
+		v4l2_ctrl_cluster(2, &cam->top_light);
+	}
+
+	if (hdl->error) {
+		ret = hdl->error;
+		v4l2_ctrl_handler_free(hdl);
+		return ret;
+	}
+
+	cam->vdev = cpia2_template;
+	video_set_drvdata(&cam->vdev, cam);
+	cam->vdev.lock = &cam->v4l2_lock;
+	cam->vdev.ctrl_handler = hdl;
+	cam->vdev.v4l2_dev = &cam->v4l2_dev;
+
+	reset_camera_struct_v4l(cam);
+
+	/* register v4l device */
+	if (video_register_device(&cam->vdev, VFL_TYPE_GRABBER, video_nr) < 0) {
+		ERR("video_register_device failed\n");
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+/******************************************************************************
+ *
+ *  cpia2_unregister_camera
+ *
+ *****************************************************************************/
+void cpia2_unregister_camera(struct camera_data *cam)
+{
+	video_unregister_device(&cam->vdev);
+}
+
+/******************************************************************************
+ *
+ *  check_parameters
+ *
+ *  Make sure that all user-supplied parameters are sensible
+ *****************************************************************************/
+static void __init check_parameters(void)
+{
+	if(buffer_size < PAGE_SIZE) {
+		buffer_size = PAGE_SIZE;
+		LOG("buffer_size too small, setting to %d\n", buffer_size);
+	} else if(buffer_size > 1024*1024) {
+		/* arbitrary upper limiit */
+		buffer_size = 1024*1024;
+		LOG("buffer_size ridiculously large, setting to %d\n",
+		    buffer_size);
+	} else {
+		buffer_size += PAGE_SIZE-1;
+		buffer_size &= ~(PAGE_SIZE-1);
+	}
+
+	if(num_buffers < 1) {
+		num_buffers = 1;
+		LOG("num_buffers too small, setting to %d\n", num_buffers);
+	} else if(num_buffers > VIDEO_MAX_FRAME) {
+		num_buffers = VIDEO_MAX_FRAME;
+		LOG("num_buffers too large, setting to %d\n", num_buffers);
+	}
+
+	if(alternate < USBIF_ISO_1 || alternate > USBIF_ISO_6) {
+		alternate = DEFAULT_ALT;
+		LOG("alternate specified is invalid, using %d\n", alternate);
+	}
+
+	if (flicker_mode != 0 && flicker_mode != FLICKER_50 && flicker_mode != FLICKER_60) {
+		flicker_mode = 0;
+		LOG("Flicker mode specified is invalid, using %d\n",
+		    flicker_mode);
+	}
+
+	DBG("Using %d buffers, each %d bytes, alternate=%d\n",
+	    num_buffers, buffer_size, alternate);
+}
+
+/************   Module Stuff ***************/
+
+
+/******************************************************************************
+ *
+ * cpia2_init/module_init
+ *
+ *****************************************************************************/
+static int __init cpia2_init(void)
+{
+	LOG("%s v%s\n",
+	    ABOUT, CPIA_VERSION);
+	check_parameters();
+	cpia2_usb_init();
+	return 0;
+}
+
+
+/******************************************************************************
+ *
+ * cpia2_exit/module_exit
+ *
+ *****************************************************************************/
+static void __exit cpia2_exit(void)
+{
+	cpia2_usb_cleanup();
+	schedule_timeout(2 * HZ);
+}
+
+module_init(cpia2_init);
+module_exit(cpia2_exit);
diff --git a/drivers/media/usb/cx231xx/Kconfig b/drivers/media/usb/cx231xx/Kconfig
new file mode 100644
index 0000000..9e5b3e7
--- /dev/null
+++ b/drivers/media/usb/cx231xx/Kconfig
@@ -0,0 +1,57 @@
+config VIDEO_CX231XX
+	tristate "Conexant cx231xx USB video capture support"
+	depends on VIDEO_DEV && I2C && I2C_MUX
+	select VIDEO_TUNER
+	select VIDEO_TVEEPROM
+	select VIDEOBUF_VMALLOC
+	select VIDEO_CX25840
+	select VIDEO_CX2341X
+
+	---help---
+	  This is a video4linux driver for Conexant 231xx USB based TV cards.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called cx231xx
+
+config VIDEO_CX231XX_RC
+	bool "Conexant cx231xx Remote Controller additional support"
+	depends on RC_CORE=y || RC_CORE=VIDEO_CX231XX
+	depends on VIDEO_CX231XX
+	default y
+	---help---
+	  cx231xx hardware has a builtin RX/TX support. However, a few
+	  designs opted to not use it, but, instead, some other hardware.
+	  This module enables the usage of those other hardware, like the
+	  ones used with ISDB-T boards.
+
+	  On most cases, all you need for IR is mceusb module.
+
+config VIDEO_CX231XX_ALSA
+	tristate "Conexant Cx231xx ALSA audio module"
+	depends on VIDEO_CX231XX && SND
+	select SND_PCM
+
+	---help---
+	  This is an ALSA driver for Cx231xx USB based TV cards.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called cx231xx-alsa
+
+config VIDEO_CX231XX_DVB
+	tristate "DVB/ATSC Support for Cx231xx based TV cards"
+	depends on VIDEO_CX231XX && DVB_CORE
+	select MEDIA_TUNER_XC5000 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_TDA18271 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_MB86A20S if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_LGDT3305 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_LGDT3306A if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_TDA18271C2DD if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_SI2165 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_SI2168 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_SI2157 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_MN88473 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_R820T if MEDIA_SUBDRV_AUTOSELECT
+
+	---help---
+	  This adds support for DVB cards based on the
+	  Conexant cx231xx chips.
diff --git a/drivers/media/usb/cx231xx/Makefile b/drivers/media/usb/cx231xx/Makefile
new file mode 100644
index 0000000..c023d97
--- /dev/null
+++ b/drivers/media/usb/cx231xx/Makefile
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0
+cx231xx-y += cx231xx-video.o cx231xx-i2c.o cx231xx-cards.o cx231xx-core.o
+cx231xx-y += cx231xx-avcore.o cx231xx-417.o cx231xx-pcb-cfg.o cx231xx-vbi.o
+cx231xx-$(CONFIG_VIDEO_CX231XX_RC) += cx231xx-input.o
+
+cx231xx-alsa-objs := cx231xx-audio.o
+
+obj-$(CONFIG_VIDEO_CX231XX) += cx231xx.o
+obj-$(CONFIG_VIDEO_CX231XX_ALSA) += cx231xx-alsa.o
+obj-$(CONFIG_VIDEO_CX231XX_DVB) += cx231xx-dvb.o
+
+ccflags-y += -Idrivers/media/tuners
+ccflags-y += -Idrivers/media/dvb-frontends
+ccflags-y += -Idrivers/media/usb/dvb-usb
diff --git a/drivers/media/usb/cx231xx/cx231xx-417.c b/drivers/media/usb/cx231xx/cx231xx-417.c
new file mode 100644
index 0000000..2f3b056
--- /dev/null
+++ b/drivers/media/usb/cx231xx/cx231xx-417.c
@@ -0,0 +1,2024 @@
+/*
+ *
+ *  Support for a cx23417 mpeg encoder via cx231xx host port.
+ *
+ *    (c) 2004 Jelle Foks <jelle@foks.us>
+ *    (c) 2004 Gerd Knorr <kraxel@bytesex.org>
+ *    (c) 2008 Steven Toth <stoth@linuxtv.org>
+ *      - CX23885/7/8 support
+ *
+ *  Includes parts from the ivtv driver( http://ivtv.sourceforge.net/),
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ */
+
+#include "cx231xx.h"
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-event.h>
+#include <media/drv-intf/cx2341x.h>
+#include <media/tuner.h>
+
+#define CX231xx_FIRM_IMAGE_SIZE 376836
+#define CX231xx_FIRM_IMAGE_NAME "v4l-cx23885-enc.fw"
+
+/* for polaris ITVC */
+#define ITVC_WRITE_DIR          0x03FDFC00
+#define ITVC_READ_DIR            0x0001FC00
+
+#define  MCI_MEMORY_DATA_BYTE0          0x00
+#define  MCI_MEMORY_DATA_BYTE1          0x08
+#define  MCI_MEMORY_DATA_BYTE2          0x10
+#define  MCI_MEMORY_DATA_BYTE3          0x18
+
+#define  MCI_MEMORY_ADDRESS_BYTE2       0x20
+#define  MCI_MEMORY_ADDRESS_BYTE1       0x28
+#define  MCI_MEMORY_ADDRESS_BYTE0       0x30
+
+#define  MCI_REGISTER_DATA_BYTE0        0x40
+#define  MCI_REGISTER_DATA_BYTE1        0x48
+#define  MCI_REGISTER_DATA_BYTE2        0x50
+#define  MCI_REGISTER_DATA_BYTE3        0x58
+
+#define  MCI_REGISTER_ADDRESS_BYTE0     0x60
+#define  MCI_REGISTER_ADDRESS_BYTE1     0x68
+
+#define  MCI_REGISTER_MODE              0x70
+
+/* Read and write modes for polaris ITVC */
+#define  MCI_MODE_REGISTER_READ         0x000
+#define  MCI_MODE_REGISTER_WRITE        0x100
+#define  MCI_MODE_MEMORY_READ           0x000
+#define  MCI_MODE_MEMORY_WRITE          0x4000
+
+static unsigned int mpegbufs = 8;
+module_param(mpegbufs, int, 0644);
+MODULE_PARM_DESC(mpegbufs, "number of mpeg buffers, range 2-32");
+
+static unsigned int mpeglines = 128;
+module_param(mpeglines, int, 0644);
+MODULE_PARM_DESC(mpeglines, "number of lines in an MPEG buffer, range 2-32");
+
+static unsigned int mpeglinesize = 512;
+module_param(mpeglinesize, int, 0644);
+MODULE_PARM_DESC(mpeglinesize,
+	"number of bytes in each line of an MPEG buffer, range 512-1024");
+
+static unsigned int v4l_debug = 1;
+module_param(v4l_debug, int, 0644);
+MODULE_PARM_DESC(v4l_debug, "enable V4L debug messages");
+
+#define dprintk(level, fmt, arg...)	\
+	do {				\
+		if (v4l_debug >= level) \
+			printk(KERN_DEBUG pr_fmt(fmt), ## arg); \
+	} while (0)
+
+static struct cx231xx_tvnorm cx231xx_tvnorms[] = {
+	{
+		.name      = "NTSC-M",
+		.id        = V4L2_STD_NTSC_M,
+	}, {
+		.name      = "NTSC-JP",
+		.id        = V4L2_STD_NTSC_M_JP,
+	}, {
+		.name      = "PAL-BG",
+		.id        = V4L2_STD_PAL_BG,
+	}, {
+		.name      = "PAL-DK",
+		.id        = V4L2_STD_PAL_DK,
+	}, {
+		.name      = "PAL-I",
+		.id        = V4L2_STD_PAL_I,
+	}, {
+		.name      = "PAL-M",
+		.id        = V4L2_STD_PAL_M,
+	}, {
+		.name      = "PAL-N",
+		.id        = V4L2_STD_PAL_N,
+	}, {
+		.name      = "PAL-Nc",
+		.id        = V4L2_STD_PAL_Nc,
+	}, {
+		.name      = "PAL-60",
+		.id        = V4L2_STD_PAL_60,
+	}, {
+		.name      = "SECAM-L",
+		.id        = V4L2_STD_SECAM_L,
+	}, {
+		.name      = "SECAM-DK",
+		.id        = V4L2_STD_SECAM_DK,
+	}
+};
+
+/* ------------------------------------------------------------------ */
+
+enum cx231xx_capture_type {
+	CX231xx_MPEG_CAPTURE,
+	CX231xx_RAW_CAPTURE,
+	CX231xx_RAW_PASSTHRU_CAPTURE
+};
+
+enum cx231xx_capture_bits {
+	CX231xx_RAW_BITS_NONE             = 0x00,
+	CX231xx_RAW_BITS_YUV_CAPTURE      = 0x01,
+	CX231xx_RAW_BITS_PCM_CAPTURE      = 0x02,
+	CX231xx_RAW_BITS_VBI_CAPTURE      = 0x04,
+	CX231xx_RAW_BITS_PASSTHRU_CAPTURE = 0x08,
+	CX231xx_RAW_BITS_TO_HOST_CAPTURE  = 0x10
+};
+
+enum cx231xx_capture_end {
+	CX231xx_END_AT_GOP, /* stop at the end of gop, generate irq */
+	CX231xx_END_NOW, /* stop immediately, no irq */
+};
+
+enum cx231xx_framerate {
+	CX231xx_FRAMERATE_NTSC_30, /* NTSC: 30fps */
+	CX231xx_FRAMERATE_PAL_25   /* PAL: 25fps */
+};
+
+enum cx231xx_stream_port {
+	CX231xx_OUTPUT_PORT_MEMORY,
+	CX231xx_OUTPUT_PORT_STREAMING,
+	CX231xx_OUTPUT_PORT_SERIAL
+};
+
+enum cx231xx_data_xfer_status {
+	CX231xx_MORE_BUFFERS_FOLLOW,
+	CX231xx_LAST_BUFFER,
+};
+
+enum cx231xx_picture_mask {
+	CX231xx_PICTURE_MASK_NONE,
+	CX231xx_PICTURE_MASK_I_FRAMES,
+	CX231xx_PICTURE_MASK_I_P_FRAMES = 0x3,
+	CX231xx_PICTURE_MASK_ALL_FRAMES = 0x7,
+};
+
+enum cx231xx_vbi_mode_bits {
+	CX231xx_VBI_BITS_SLICED,
+	CX231xx_VBI_BITS_RAW,
+};
+
+enum cx231xx_vbi_insertion_bits {
+	CX231xx_VBI_BITS_INSERT_IN_XTENSION_USR_DATA,
+	CX231xx_VBI_BITS_INSERT_IN_PRIVATE_PACKETS = 0x1 << 1,
+	CX231xx_VBI_BITS_SEPARATE_STREAM = 0x2 << 1,
+	CX231xx_VBI_BITS_SEPARATE_STREAM_USR_DATA = 0x4 << 1,
+	CX231xx_VBI_BITS_SEPARATE_STREAM_PRV_DATA = 0x5 << 1,
+};
+
+enum cx231xx_dma_unit {
+	CX231xx_DMA_BYTES,
+	CX231xx_DMA_FRAMES,
+};
+
+enum cx231xx_dma_transfer_status_bits {
+	CX231xx_DMA_TRANSFER_BITS_DONE = 0x01,
+	CX231xx_DMA_TRANSFER_BITS_ERROR = 0x04,
+	CX231xx_DMA_TRANSFER_BITS_LL_ERROR = 0x10,
+};
+
+enum cx231xx_pause {
+	CX231xx_PAUSE_ENCODING,
+	CX231xx_RESUME_ENCODING,
+};
+
+enum cx231xx_copyright {
+	CX231xx_COPYRIGHT_OFF,
+	CX231xx_COPYRIGHT_ON,
+};
+
+enum cx231xx_notification_type {
+	CX231xx_NOTIFICATION_REFRESH,
+};
+
+enum cx231xx_notification_status {
+	CX231xx_NOTIFICATION_OFF,
+	CX231xx_NOTIFICATION_ON,
+};
+
+enum cx231xx_notification_mailbox {
+	CX231xx_NOTIFICATION_NO_MAILBOX = -1,
+};
+
+enum cx231xx_field1_lines {
+	CX231xx_FIELD1_SAA7114 = 0x00EF, /* 239 */
+	CX231xx_FIELD1_SAA7115 = 0x00F0, /* 240 */
+	CX231xx_FIELD1_MICRONAS = 0x0105, /* 261 */
+};
+
+enum cx231xx_field2_lines {
+	CX231xx_FIELD2_SAA7114 = 0x00EF, /* 239 */
+	CX231xx_FIELD2_SAA7115 = 0x00F0, /* 240 */
+	CX231xx_FIELD2_MICRONAS = 0x0106, /* 262 */
+};
+
+enum cx231xx_custom_data_type {
+	CX231xx_CUSTOM_EXTENSION_USR_DATA,
+	CX231xx_CUSTOM_PRIVATE_PACKET,
+};
+
+enum cx231xx_mute {
+	CX231xx_UNMUTE,
+	CX231xx_MUTE,
+};
+
+enum cx231xx_mute_video_mask {
+	CX231xx_MUTE_VIDEO_V_MASK = 0x0000FF00,
+	CX231xx_MUTE_VIDEO_U_MASK = 0x00FF0000,
+	CX231xx_MUTE_VIDEO_Y_MASK = 0xFF000000,
+};
+
+enum cx231xx_mute_video_shift {
+	CX231xx_MUTE_VIDEO_V_SHIFT = 8,
+	CX231xx_MUTE_VIDEO_U_SHIFT = 16,
+	CX231xx_MUTE_VIDEO_Y_SHIFT = 24,
+};
+
+/* defines below are from ivtv-driver.h */
+#define IVTV_CMD_HW_BLOCKS_RST 0xFFFFFFFF
+
+/* Firmware API commands */
+#define IVTV_API_STD_TIMEOUT 500
+
+/* Registers */
+/* IVTV_REG_OFFSET */
+#define IVTV_REG_ENC_SDRAM_REFRESH (0x07F8)
+#define IVTV_REG_ENC_SDRAM_PRECHARGE (0x07FC)
+#define IVTV_REG_SPU (0x9050)
+#define IVTV_REG_HW_BLOCKS (0x9054)
+#define IVTV_REG_VPU (0x9058)
+#define IVTV_REG_APU (0xA064)
+
+/*
+ * Bit definitions for MC417_RWD and MC417_OEN registers
+ *
+ * bits 31-16
+ *+-----------+
+ *| Reserved  |
+ *|+-----------+
+ *|  bit 15  bit 14  bit 13 bit 12  bit 11  bit 10  bit 9   bit 8
+ *|+-------+-------+-------+-------+-------+-------+-------+-------+
+ *|| MIWR# | MIRD# | MICS# |MIRDY# |MIADDR3|MIADDR2|MIADDR1|MIADDR0|
+ *|+-------+-------+-------+-------+-------+-------+-------+-------+
+ *| bit 7   bit 6   bit 5   bit 4   bit 3   bit 2   bit 1   bit 0
+ *|+-------+-------+-------+-------+-------+-------+-------+-------+
+ *||MIDATA7|MIDATA6|MIDATA5|MIDATA4|MIDATA3|MIDATA2|MIDATA1|MIDATA0|
+ *|+-------+-------+-------+-------+-------+-------+-------+-------+
+ */
+#define MC417_MIWR	0x8000
+#define MC417_MIRD	0x4000
+#define MC417_MICS	0x2000
+#define MC417_MIRDY	0x1000
+#define MC417_MIADDR	0x0F00
+#define MC417_MIDATA	0x00FF
+
+
+/* Bit definitions for MC417_CTL register ****
+ *bits 31-6   bits 5-4   bit 3    bits 2-1       Bit 0
+ *+--------+-------------+--------+--------------+------------+
+ *|Reserved|MC417_SPD_CTL|Reserved|MC417_GPIO_SEL|UART_GPIO_EN|
+ *+--------+-------------+--------+--------------+------------+
+ */
+#define MC417_SPD_CTL(x)	(((x) << 4) & 0x00000030)
+#define MC417_GPIO_SEL(x)	(((x) << 1) & 0x00000006)
+#define MC417_UART_GPIO_EN	0x00000001
+
+/* Values for speed control */
+#define MC417_SPD_CTL_SLOW	0x1
+#define MC417_SPD_CTL_MEDIUM	0x0
+#define MC417_SPD_CTL_FAST	0x3     /* b'1x, but we use b'11 */
+
+/* Values for GPIO select */
+#define MC417_GPIO_SEL_GPIO3	0x3
+#define MC417_GPIO_SEL_GPIO2	0x2
+#define MC417_GPIO_SEL_GPIO1	0x1
+#define MC417_GPIO_SEL_GPIO0	0x0
+
+
+#define CX23417_GPIO_MASK 0xFC0003FF
+
+static int set_itvc_reg(struct cx231xx *dev, u32 gpio_direction, u32 value)
+{
+	int status = 0;
+	u32 _gpio_direction = 0;
+
+	_gpio_direction = _gpio_direction & CX23417_GPIO_MASK;
+	_gpio_direction = _gpio_direction | gpio_direction;
+	status = cx231xx_send_gpio_cmd(dev, _gpio_direction,
+			 (u8 *)&value, 4, 0, 0);
+	return status;
+}
+
+static int get_itvc_reg(struct cx231xx *dev, u32 gpio_direction, u32 *val_ptr)
+{
+	int status = 0;
+	u32 _gpio_direction = 0;
+
+	_gpio_direction = _gpio_direction & CX23417_GPIO_MASK;
+	_gpio_direction = _gpio_direction | gpio_direction;
+
+	status = cx231xx_send_gpio_cmd(dev, _gpio_direction,
+		 (u8 *)val_ptr, 4, 0, 1);
+	return status;
+}
+
+static int wait_for_mci_complete(struct cx231xx *dev)
+{
+	u32 gpio;
+	u32 gpio_direction = 0;
+	u8 count = 0;
+	get_itvc_reg(dev, gpio_direction, &gpio);
+
+	while (!(gpio&0x020000)) {
+		msleep(10);
+
+		get_itvc_reg(dev, gpio_direction, &gpio);
+
+		if (count++ > 100) {
+			dprintk(3, "ERROR: Timeout - gpio=%x\n", gpio);
+			return -EIO;
+		}
+	}
+	return 0;
+}
+
+static int mc417_register_write(struct cx231xx *dev, u16 address, u32 value)
+{
+	u32 temp;
+	int status = 0;
+
+	temp = 0x82 | MCI_REGISTER_DATA_BYTE0 | ((value & 0x000000FF) << 8);
+	temp = temp << 10;
+	status = set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+	if (status < 0)
+		return status;
+	temp = temp | (0x05 << 10);
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+
+	/*write data byte 1;*/
+	temp = 0x82 | MCI_REGISTER_DATA_BYTE1 | (value & 0x0000FF00);
+	temp = temp << 10;
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+	temp = temp | (0x05 << 10);
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+
+	/*write data byte 2;*/
+	temp = 0x82 | MCI_REGISTER_DATA_BYTE2 | ((value & 0x00FF0000) >> 8);
+	temp = temp << 10;
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+	temp = temp | (0x05 << 10);
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+
+	/*write data byte 3;*/
+	temp = 0x82 | MCI_REGISTER_DATA_BYTE3 | ((value & 0xFF000000) >> 16);
+	temp = temp << 10;
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+	temp = temp | (0x05 << 10);
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+
+	/*write address byte 0;*/
+	temp = 0x82 | MCI_REGISTER_ADDRESS_BYTE0 | ((address & 0x000000FF) << 8);
+	temp = temp << 10;
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+	temp = temp | (0x05 << 10);
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+
+	/*write address byte 1;*/
+	temp = 0x82 | MCI_REGISTER_ADDRESS_BYTE1 | (address & 0x0000FF00);
+	temp = temp << 10;
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+	temp = temp | (0x05 << 10);
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+
+	/*Write that the mode is write.*/
+	temp = 0x82 | MCI_REGISTER_MODE | MCI_MODE_REGISTER_WRITE;
+	temp = temp << 10;
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+	temp = temp | (0x05 << 10);
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+
+	return wait_for_mci_complete(dev);
+}
+
+static int mc417_register_read(struct cx231xx *dev, u16 address, u32 *value)
+{
+	/*write address byte 0;*/
+	u32 temp;
+	u32 return_value = 0;
+	int ret = 0;
+
+	temp = 0x82 | MCI_REGISTER_ADDRESS_BYTE0 | ((address & 0x00FF) << 8);
+	temp = temp << 10;
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+	temp = temp | ((0x05) << 10);
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+
+	/*write address byte 1;*/
+	temp = 0x82 | MCI_REGISTER_ADDRESS_BYTE1 | (address & 0xFF00);
+	temp = temp << 10;
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+	temp = temp | ((0x05) << 10);
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+
+	/*write that the mode is read;*/
+	temp = 0x82 | MCI_REGISTER_MODE | MCI_MODE_REGISTER_READ;
+	temp = temp << 10;
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+	temp = temp | ((0x05) << 10);
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+
+	/*wait for the MIRDY line to be asserted ,
+	signalling that the read is done;*/
+	ret = wait_for_mci_complete(dev);
+
+	/*switch the DATA- GPIO to input mode;*/
+
+	/*Read data byte 0;*/
+	temp = (0x82 | MCI_REGISTER_DATA_BYTE0) << 10;
+	set_itvc_reg(dev, ITVC_READ_DIR, temp);
+	temp = ((0x81 | MCI_REGISTER_DATA_BYTE0) << 10);
+	set_itvc_reg(dev, ITVC_READ_DIR, temp);
+	get_itvc_reg(dev, ITVC_READ_DIR, &temp);
+	return_value |= ((temp & 0x03FC0000) >> 18);
+	set_itvc_reg(dev, ITVC_READ_DIR, (0x87 << 10));
+
+	/* Read data byte 1;*/
+	temp = (0x82 | MCI_REGISTER_DATA_BYTE1) << 10;
+	set_itvc_reg(dev, ITVC_READ_DIR, temp);
+	temp = ((0x81 | MCI_REGISTER_DATA_BYTE1) << 10);
+	set_itvc_reg(dev, ITVC_READ_DIR, temp);
+	get_itvc_reg(dev, ITVC_READ_DIR, &temp);
+
+	return_value |= ((temp & 0x03FC0000) >> 10);
+	set_itvc_reg(dev, ITVC_READ_DIR, (0x87 << 10));
+
+	/*Read data byte 2;*/
+	temp = (0x82 | MCI_REGISTER_DATA_BYTE2) << 10;
+	set_itvc_reg(dev, ITVC_READ_DIR, temp);
+	temp = ((0x81 | MCI_REGISTER_DATA_BYTE2) << 10);
+	set_itvc_reg(dev, ITVC_READ_DIR, temp);
+	get_itvc_reg(dev, ITVC_READ_DIR, &temp);
+	return_value |= ((temp & 0x03FC0000) >> 2);
+	set_itvc_reg(dev, ITVC_READ_DIR, (0x87 << 10));
+
+	/*Read data byte 3;*/
+	temp = (0x82 | MCI_REGISTER_DATA_BYTE3) << 10;
+	set_itvc_reg(dev, ITVC_READ_DIR, temp);
+	temp = ((0x81 | MCI_REGISTER_DATA_BYTE3) << 10);
+	set_itvc_reg(dev, ITVC_READ_DIR, temp);
+	get_itvc_reg(dev, ITVC_READ_DIR, &temp);
+	return_value |= ((temp & 0x03FC0000) << 6);
+	set_itvc_reg(dev, ITVC_READ_DIR, (0x87 << 10));
+
+	*value  = return_value;
+	return ret;
+}
+
+static int mc417_memory_write(struct cx231xx *dev, u32 address, u32 value)
+{
+	/*write data byte 0;*/
+
+	u32 temp;
+	int ret = 0;
+
+	temp = 0x82 | MCI_MEMORY_DATA_BYTE0 | ((value & 0x000000FF) << 8);
+	temp = temp << 10;
+	ret = set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+	if (ret < 0)
+		return ret;
+	temp = temp | (0x05 << 10);
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+
+	/*write data byte 1;*/
+	temp = 0x82 | MCI_MEMORY_DATA_BYTE1 | (value & 0x0000FF00);
+	temp = temp << 10;
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+	temp = temp | (0x05 << 10);
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+
+	/*write data byte 2;*/
+	temp = 0x82 | MCI_MEMORY_DATA_BYTE2 | ((value & 0x00FF0000) >> 8);
+	temp = temp << 10;
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+	temp = temp | (0x05 << 10);
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+
+	/*write data byte 3;*/
+	temp = 0x82 | MCI_MEMORY_DATA_BYTE3 | ((value & 0xFF000000) >> 16);
+	temp = temp << 10;
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+	temp = temp | (0x05 << 10);
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+
+	/* write address byte 2;*/
+	temp = 0x82 | MCI_MEMORY_ADDRESS_BYTE2 | MCI_MODE_MEMORY_WRITE |
+		((address & 0x003F0000) >> 8);
+	temp = temp << 10;
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+	temp = temp | (0x05 << 10);
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+
+	/* write address byte 1;*/
+	temp = 0x82 | MCI_MEMORY_ADDRESS_BYTE1 | (address & 0xFF00);
+	temp = temp << 10;
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+	temp = temp | (0x05 << 10);
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+
+	/* write address byte 0;*/
+	temp = 0x82 | MCI_MEMORY_ADDRESS_BYTE0 | ((address & 0x00FF) << 8);
+	temp = temp << 10;
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+	temp = temp | (0x05 << 10);
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+
+	/*wait for MIRDY line;*/
+	wait_for_mci_complete(dev);
+
+	return 0;
+}
+
+static int mc417_memory_read(struct cx231xx *dev, u32 address, u32 *value)
+{
+	u32 temp = 0;
+	u32 return_value = 0;
+	int ret = 0;
+
+	/*write address byte 2;*/
+	temp = 0x82 | MCI_MEMORY_ADDRESS_BYTE2 | MCI_MODE_MEMORY_READ |
+		((address & 0x003F0000) >> 8);
+	temp = temp << 10;
+	ret = set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+	if (ret < 0)
+		return ret;
+	temp = temp | (0x05 << 10);
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+
+	/*write address byte 1*/
+	temp = 0x82 | MCI_MEMORY_ADDRESS_BYTE1 | (address & 0xFF00);
+	temp = temp << 10;
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+	temp = temp | (0x05 << 10);
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+
+	/*write address byte 0*/
+	temp = 0x82 | MCI_MEMORY_ADDRESS_BYTE0 | ((address & 0x00FF) << 8);
+	temp = temp << 10;
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+	temp = temp | (0x05 << 10);
+	set_itvc_reg(dev, ITVC_WRITE_DIR, temp);
+
+	/*Wait for MIRDY line*/
+	ret = wait_for_mci_complete(dev);
+
+
+	/*Read data byte 3;*/
+	temp = (0x82 | MCI_MEMORY_DATA_BYTE3) << 10;
+	set_itvc_reg(dev, ITVC_READ_DIR, temp);
+	temp = ((0x81 | MCI_MEMORY_DATA_BYTE3) << 10);
+	set_itvc_reg(dev, ITVC_READ_DIR, temp);
+	get_itvc_reg(dev, ITVC_READ_DIR, &temp);
+	return_value |= ((temp & 0x03FC0000) << 6);
+	set_itvc_reg(dev, ITVC_READ_DIR, (0x87 << 10));
+
+	/*Read data byte 2;*/
+	temp = (0x82 | MCI_MEMORY_DATA_BYTE2) << 10;
+	set_itvc_reg(dev, ITVC_READ_DIR, temp);
+	temp = ((0x81 | MCI_MEMORY_DATA_BYTE2) << 10);
+	set_itvc_reg(dev, ITVC_READ_DIR, temp);
+	get_itvc_reg(dev, ITVC_READ_DIR, &temp);
+	return_value |= ((temp & 0x03FC0000) >> 2);
+	set_itvc_reg(dev, ITVC_READ_DIR, (0x87 << 10));
+
+	/* Read data byte 1;*/
+	temp = (0x82 | MCI_MEMORY_DATA_BYTE1) << 10;
+	set_itvc_reg(dev, ITVC_READ_DIR, temp);
+	temp = ((0x81 | MCI_MEMORY_DATA_BYTE1) << 10);
+	set_itvc_reg(dev, ITVC_READ_DIR, temp);
+	get_itvc_reg(dev, ITVC_READ_DIR, &temp);
+	return_value |= ((temp & 0x03FC0000) >> 10);
+	set_itvc_reg(dev, ITVC_READ_DIR, (0x87 << 10));
+
+	/*Read data byte 0;*/
+	temp = (0x82 | MCI_MEMORY_DATA_BYTE0) << 10;
+	set_itvc_reg(dev, ITVC_READ_DIR, temp);
+	temp = ((0x81 | MCI_MEMORY_DATA_BYTE0) << 10);
+	set_itvc_reg(dev, ITVC_READ_DIR, temp);
+	get_itvc_reg(dev, ITVC_READ_DIR, &temp);
+	return_value |= ((temp & 0x03FC0000) >> 18);
+	set_itvc_reg(dev, ITVC_READ_DIR, (0x87 << 10));
+
+	*value  = return_value;
+	return ret;
+}
+
+/* ------------------------------------------------------------------ */
+
+/* MPEG encoder API */
+static char *cmd_to_str(int cmd)
+{
+	switch (cmd) {
+	case CX2341X_ENC_PING_FW:
+		return "PING_FW";
+	case CX2341X_ENC_START_CAPTURE:
+		return "START_CAPTURE";
+	case CX2341X_ENC_STOP_CAPTURE:
+		return "STOP_CAPTURE";
+	case CX2341X_ENC_SET_AUDIO_ID:
+		return "SET_AUDIO_ID";
+	case CX2341X_ENC_SET_VIDEO_ID:
+		return "SET_VIDEO_ID";
+	case CX2341X_ENC_SET_PCR_ID:
+		return "SET_PCR_PID";
+	case CX2341X_ENC_SET_FRAME_RATE:
+		return "SET_FRAME_RATE";
+	case CX2341X_ENC_SET_FRAME_SIZE:
+		return "SET_FRAME_SIZE";
+	case CX2341X_ENC_SET_BIT_RATE:
+		return "SET_BIT_RATE";
+	case CX2341X_ENC_SET_GOP_PROPERTIES:
+		return "SET_GOP_PROPERTIES";
+	case CX2341X_ENC_SET_ASPECT_RATIO:
+		return "SET_ASPECT_RATIO";
+	case CX2341X_ENC_SET_DNR_FILTER_MODE:
+		return "SET_DNR_FILTER_PROPS";
+	case CX2341X_ENC_SET_DNR_FILTER_PROPS:
+		return "SET_DNR_FILTER_PROPS";
+	case CX2341X_ENC_SET_CORING_LEVELS:
+		return "SET_CORING_LEVELS";
+	case CX2341X_ENC_SET_SPATIAL_FILTER_TYPE:
+		return "SET_SPATIAL_FILTER_TYPE";
+	case CX2341X_ENC_SET_VBI_LINE:
+		return "SET_VBI_LINE";
+	case CX2341X_ENC_SET_STREAM_TYPE:
+		return "SET_STREAM_TYPE";
+	case CX2341X_ENC_SET_OUTPUT_PORT:
+		return "SET_OUTPUT_PORT";
+	case CX2341X_ENC_SET_AUDIO_PROPERTIES:
+		return "SET_AUDIO_PROPERTIES";
+	case CX2341X_ENC_HALT_FW:
+		return "HALT_FW";
+	case CX2341X_ENC_GET_VERSION:
+		return "GET_VERSION";
+	case CX2341X_ENC_SET_GOP_CLOSURE:
+		return "SET_GOP_CLOSURE";
+	case CX2341X_ENC_GET_SEQ_END:
+		return "GET_SEQ_END";
+	case CX2341X_ENC_SET_PGM_INDEX_INFO:
+		return "SET_PGM_INDEX_INFO";
+	case CX2341X_ENC_SET_VBI_CONFIG:
+		return "SET_VBI_CONFIG";
+	case CX2341X_ENC_SET_DMA_BLOCK_SIZE:
+		return "SET_DMA_BLOCK_SIZE";
+	case CX2341X_ENC_GET_PREV_DMA_INFO_MB_10:
+		return "GET_PREV_DMA_INFO_MB_10";
+	case CX2341X_ENC_GET_PREV_DMA_INFO_MB_9:
+		return "GET_PREV_DMA_INFO_MB_9";
+	case CX2341X_ENC_SCHED_DMA_TO_HOST:
+		return "SCHED_DMA_TO_HOST";
+	case CX2341X_ENC_INITIALIZE_INPUT:
+		return "INITIALIZE_INPUT";
+	case CX2341X_ENC_SET_FRAME_DROP_RATE:
+		return "SET_FRAME_DROP_RATE";
+	case CX2341X_ENC_PAUSE_ENCODER:
+		return "PAUSE_ENCODER";
+	case CX2341X_ENC_REFRESH_INPUT:
+		return "REFRESH_INPUT";
+	case CX2341X_ENC_SET_COPYRIGHT:
+		return "SET_COPYRIGHT";
+	case CX2341X_ENC_SET_EVENT_NOTIFICATION:
+		return "SET_EVENT_NOTIFICATION";
+	case CX2341X_ENC_SET_NUM_VSYNC_LINES:
+		return "SET_NUM_VSYNC_LINES";
+	case CX2341X_ENC_SET_PLACEHOLDER:
+		return "SET_PLACEHOLDER";
+	case CX2341X_ENC_MUTE_VIDEO:
+		return "MUTE_VIDEO";
+	case CX2341X_ENC_MUTE_AUDIO:
+		return "MUTE_AUDIO";
+	case CX2341X_ENC_MISC:
+		return "MISC";
+	default:
+		return "UNKNOWN";
+	}
+}
+
+static int cx231xx_mbox_func(void *priv, u32 command, int in, int out,
+			     u32 data[CX2341X_MBOX_MAX_DATA])
+{
+	struct cx231xx *dev = priv;
+	unsigned long timeout;
+	u32 value, flag, retval = 0;
+	int i;
+
+	dprintk(3, "%s: command(0x%X) = %s\n", __func__, command,
+		cmd_to_str(command));
+
+	/* this may not be 100% safe if we can't read any memory location
+	   without side effects */
+	mc417_memory_read(dev, dev->cx23417_mailbox - 4, &value);
+	if (value != 0x12345678) {
+		dprintk(3, "Firmware and/or mailbox pointer not initialized or corrupted, signature = 0x%x, cmd = %s\n",
+			value, cmd_to_str(command));
+		return -EIO;
+	}
+
+	/* This read looks at 32 bits, but flag is only 8 bits.
+	 * Seems we also bail if CMD or TIMEOUT bytes are set???
+	 */
+	mc417_memory_read(dev, dev->cx23417_mailbox, &flag);
+	if (flag) {
+		dprintk(3, "ERROR: Mailbox appears to be in use (%x), cmd = %s\n",
+				flag, cmd_to_str(command));
+		return -EBUSY;
+	}
+
+	flag |= 1; /* tell 'em we're working on it */
+	mc417_memory_write(dev, dev->cx23417_mailbox, flag);
+
+	/* write command + args + fill remaining with zeros */
+	/* command code */
+	mc417_memory_write(dev, dev->cx23417_mailbox + 1, command);
+	mc417_memory_write(dev, dev->cx23417_mailbox + 3,
+		IVTV_API_STD_TIMEOUT); /* timeout */
+	for (i = 0; i < in; i++) {
+		mc417_memory_write(dev, dev->cx23417_mailbox + 4 + i, data[i]);
+		dprintk(3, "API Input %d = %d\n", i, data[i]);
+	}
+	for (; i < CX2341X_MBOX_MAX_DATA; i++)
+		mc417_memory_write(dev, dev->cx23417_mailbox + 4 + i, 0);
+
+	flag |= 3; /* tell 'em we're done writing */
+	mc417_memory_write(dev, dev->cx23417_mailbox, flag);
+
+	/* wait for firmware to handle the API command */
+	timeout = jiffies + msecs_to_jiffies(10);
+	for (;;) {
+		mc417_memory_read(dev, dev->cx23417_mailbox, &flag);
+		if (0 != (flag & 4))
+			break;
+		if (time_after(jiffies, timeout)) {
+			dprintk(3, "ERROR: API Mailbox timeout\n");
+			return -EIO;
+		}
+		udelay(10);
+	}
+
+	/* read output values */
+	for (i = 0; i < out; i++) {
+		mc417_memory_read(dev, dev->cx23417_mailbox + 4 + i, data + i);
+		dprintk(3, "API Output %d = %d\n", i, data[i]);
+	}
+
+	mc417_memory_read(dev, dev->cx23417_mailbox + 2, &retval);
+	dprintk(3, "API result = %d\n", retval);
+
+	flag = 0;
+	mc417_memory_write(dev, dev->cx23417_mailbox, flag);
+
+	return 0;
+}
+
+/* We don't need to call the API often, so using just one
+ * mailbox will probably suffice
+ */
+static int cx231xx_api_cmd(struct cx231xx *dev, u32 command,
+		u32 inputcnt, u32 outputcnt, ...)
+{
+	u32 data[CX2341X_MBOX_MAX_DATA];
+	va_list vargs;
+	int i, err;
+
+	dprintk(3, "%s() cmds = 0x%08x\n", __func__, command);
+
+	va_start(vargs, outputcnt);
+	for (i = 0; i < inputcnt; i++)
+		data[i] = va_arg(vargs, int);
+
+	err = cx231xx_mbox_func(dev, command, inputcnt, outputcnt, data);
+	for (i = 0; i < outputcnt; i++) {
+		int *vptr = va_arg(vargs, int *);
+		*vptr = data[i];
+	}
+	va_end(vargs);
+
+	return err;
+}
+
+
+static int cx231xx_find_mailbox(struct cx231xx *dev)
+{
+	u32 signature[4] = {
+		0x12345678, 0x34567812, 0x56781234, 0x78123456
+	};
+	int signaturecnt = 0;
+	u32 value;
+	int i;
+	int ret = 0;
+
+	dprintk(2, "%s()\n", __func__);
+
+	for (i = 0; i < 0x100; i++) {/*CX231xx_FIRM_IMAGE_SIZE*/
+		ret = mc417_memory_read(dev, i, &value);
+		if (ret < 0)
+			return ret;
+		if (value == signature[signaturecnt])
+			signaturecnt++;
+		else
+			signaturecnt = 0;
+		if (4 == signaturecnt) {
+			dprintk(1, "Mailbox signature found at 0x%x\n", i + 1);
+			return i + 1;
+		}
+	}
+	dprintk(3, "Mailbox signature values not found!\n");
+	return -EIO;
+}
+
+static void mci_write_memory_to_gpio(struct cx231xx *dev, u32 address, u32 value,
+		u32 *p_fw_image)
+{
+	u32 temp = 0;
+	int i = 0;
+
+	temp = 0x82 | MCI_MEMORY_DATA_BYTE0 | ((value & 0x000000FF) << 8);
+	temp = temp << 10;
+	*p_fw_image = temp;
+	p_fw_image++;
+	temp = temp | (0x05 << 10);
+	*p_fw_image = temp;
+	p_fw_image++;
+
+	/*write data byte 1;*/
+	temp = 0x82 | MCI_MEMORY_DATA_BYTE1 | (value & 0x0000FF00);
+	temp = temp << 10;
+	*p_fw_image = temp;
+	p_fw_image++;
+	temp = temp | (0x05 << 10);
+	*p_fw_image = temp;
+	p_fw_image++;
+
+	/*write data byte 2;*/
+	temp = 0x82 | MCI_MEMORY_DATA_BYTE2 | ((value & 0x00FF0000) >> 8);
+	temp = temp << 10;
+	*p_fw_image = temp;
+	p_fw_image++;
+	temp = temp | (0x05 << 10);
+	*p_fw_image = temp;
+	p_fw_image++;
+
+	/*write data byte 3;*/
+	temp = 0x82 | MCI_MEMORY_DATA_BYTE3 | ((value & 0xFF000000) >> 16);
+	temp = temp << 10;
+	*p_fw_image = temp;
+	p_fw_image++;
+	temp = temp | (0x05 << 10);
+	*p_fw_image = temp;
+	p_fw_image++;
+
+	/* write address byte 2;*/
+	temp = 0x82 | MCI_MEMORY_ADDRESS_BYTE2 | MCI_MODE_MEMORY_WRITE |
+		((address & 0x003F0000) >> 8);
+	temp = temp << 10;
+	*p_fw_image = temp;
+	p_fw_image++;
+	temp = temp | (0x05 << 10);
+	*p_fw_image = temp;
+	p_fw_image++;
+
+	/* write address byte 1;*/
+	temp = 0x82 | MCI_MEMORY_ADDRESS_BYTE1 | (address & 0xFF00);
+	temp = temp << 10;
+	*p_fw_image = temp;
+	p_fw_image++;
+	temp = temp | (0x05 << 10);
+	*p_fw_image = temp;
+	p_fw_image++;
+
+	/* write address byte 0;*/
+	temp = 0x82 | MCI_MEMORY_ADDRESS_BYTE0 | ((address & 0x00FF) << 8);
+	temp = temp << 10;
+	*p_fw_image = temp;
+	p_fw_image++;
+	temp = temp | (0x05 << 10);
+	*p_fw_image = temp;
+	p_fw_image++;
+
+	for (i = 0; i < 6; i++) {
+		*p_fw_image = 0xFFFFFFFF;
+		p_fw_image++;
+	}
+}
+
+
+static int cx231xx_load_firmware(struct cx231xx *dev)
+{
+	static const unsigned char magic[8] = {
+		0xa7, 0x0d, 0x00, 0x00, 0x66, 0xbb, 0x55, 0xaa
+	};
+	const struct firmware *firmware;
+	int i, retval = 0;
+	u32 value = 0;
+	u32 gpio_output = 0;
+	/*u32 checksum = 0;*/
+	/*u32 *dataptr;*/
+	u32 transfer_size = 0;
+	u32 fw_data = 0;
+	u32 address = 0;
+	/*u32 current_fw[800];*/
+	u32 *p_current_fw, *p_fw;
+	u32 *p_fw_data;
+	int frame = 0;
+	u16 _buffer_size = 4096;
+	u8 *p_buffer;
+
+	p_current_fw = vmalloc(1884180 * 4);
+	p_fw = p_current_fw;
+	if (p_current_fw == NULL) {
+		dprintk(2, "FAIL!!!\n");
+		return -ENOMEM;
+	}
+
+	p_buffer = vmalloc(4096);
+	if (p_buffer == NULL) {
+		dprintk(2, "FAIL!!!\n");
+		vfree(p_current_fw);
+		return -ENOMEM;
+	}
+
+	dprintk(2, "%s()\n", __func__);
+
+	/* Save GPIO settings before reset of APU */
+	retval |= mc417_memory_read(dev, 0x9020, &gpio_output);
+	retval |= mc417_memory_read(dev, 0x900C, &value);
+
+	retval  = mc417_register_write(dev,
+		IVTV_REG_VPU, 0xFFFFFFED);
+	retval |= mc417_register_write(dev,
+		IVTV_REG_HW_BLOCKS, IVTV_CMD_HW_BLOCKS_RST);
+	retval |= mc417_register_write(dev,
+		IVTV_REG_ENC_SDRAM_REFRESH, 0x80000800);
+	retval |= mc417_register_write(dev,
+		IVTV_REG_ENC_SDRAM_PRECHARGE, 0x1A);
+	retval |= mc417_register_write(dev,
+		IVTV_REG_APU, 0);
+
+	if (retval != 0) {
+		dev_err(dev->dev,
+			"%s: Error with mc417_register_write\n", __func__);
+		vfree(p_current_fw);
+		vfree(p_buffer);
+		return retval;
+	}
+
+	retval = request_firmware(&firmware, CX231xx_FIRM_IMAGE_NAME,
+				  dev->dev);
+
+	if (retval != 0) {
+		dev_err(dev->dev,
+			"ERROR: Hotplug firmware request failed (%s).\n",
+			CX231xx_FIRM_IMAGE_NAME);
+		dev_err(dev->dev,
+			"Please fix your hotplug setup, the board will not work without firmware loaded!\n");
+		vfree(p_current_fw);
+		vfree(p_buffer);
+		return retval;
+	}
+
+	if (firmware->size != CX231xx_FIRM_IMAGE_SIZE) {
+		dev_err(dev->dev,
+			"ERROR: Firmware size mismatch (have %zd, expected %d)\n",
+			firmware->size, CX231xx_FIRM_IMAGE_SIZE);
+		release_firmware(firmware);
+		vfree(p_current_fw);
+		vfree(p_buffer);
+		return -EINVAL;
+	}
+
+	if (0 != memcmp(firmware->data, magic, 8)) {
+		dev_err(dev->dev,
+			"ERROR: Firmware magic mismatch, wrong file?\n");
+		release_firmware(firmware);
+		vfree(p_current_fw);
+		vfree(p_buffer);
+		return -EINVAL;
+	}
+
+	initGPIO(dev);
+
+	/* transfer to the chip */
+	dprintk(2, "Loading firmware to GPIO...\n");
+	p_fw_data = (u32 *)firmware->data;
+	dprintk(2, "firmware->size=%zd\n", firmware->size);
+	for (transfer_size = 0; transfer_size < firmware->size;
+		 transfer_size += 4) {
+		fw_data = *p_fw_data;
+
+		mci_write_memory_to_gpio(dev, address, fw_data, p_current_fw);
+		address = address + 1;
+		p_current_fw += 20;
+		p_fw_data += 1;
+	}
+
+	/*download the firmware by ep5-out*/
+
+	for (frame = 0; frame < (int)(CX231xx_FIRM_IMAGE_SIZE*20/_buffer_size);
+	     frame++) {
+		for (i = 0; i < _buffer_size; i++) {
+			*(p_buffer + i) = (u8)(*(p_fw + (frame * 128 * 8 + (i / 4))) & 0x000000FF);
+			i++;
+			*(p_buffer + i) = (u8)((*(p_fw + (frame * 128 * 8 + (i / 4))) & 0x0000FF00) >> 8);
+			i++;
+			*(p_buffer + i) = (u8)((*(p_fw + (frame * 128 * 8 + (i / 4))) & 0x00FF0000) >> 16);
+			i++;
+			*(p_buffer + i) = (u8)((*(p_fw + (frame * 128 * 8 + (i / 4))) & 0xFF000000) >> 24);
+		}
+		cx231xx_ep5_bulkout(dev, p_buffer, _buffer_size);
+	}
+
+	p_current_fw = p_fw;
+	vfree(p_current_fw);
+	p_current_fw = NULL;
+	uninitGPIO(dev);
+	release_firmware(firmware);
+	dprintk(1, "Firmware upload successful.\n");
+
+	retval |= mc417_register_write(dev, IVTV_REG_HW_BLOCKS,
+		IVTV_CMD_HW_BLOCKS_RST);
+	if (retval < 0) {
+		dev_err(dev->dev,
+			"%s: Error with mc417_register_write\n",
+			__func__);
+		return retval;
+	}
+	/* F/W power up disturbs the GPIOs, restore state */
+	retval |= mc417_register_write(dev, 0x9020, gpio_output);
+	retval |= mc417_register_write(dev, 0x900C, value);
+
+	retval |= mc417_register_read(dev, IVTV_REG_VPU, &value);
+	retval |= mc417_register_write(dev, IVTV_REG_VPU, value & 0xFFFFFFE8);
+
+	if (retval < 0) {
+		dev_err(dev->dev,
+			"%s: Error with mc417_register_write\n",
+			__func__);
+		return retval;
+	}
+	return 0;
+}
+
+static void cx231xx_417_check_encoder(struct cx231xx *dev)
+{
+	u32 status, seq;
+
+	status = 0;
+	seq = 0;
+	cx231xx_api_cmd(dev, CX2341X_ENC_GET_SEQ_END, 0, 2, &status, &seq);
+	dprintk(1, "%s() status = %d, seq = %d\n", __func__, status, seq);
+}
+
+static void cx231xx_codec_settings(struct cx231xx *dev)
+{
+	dprintk(1, "%s()\n", __func__);
+
+	/* assign frame size */
+	cx231xx_api_cmd(dev, CX2341X_ENC_SET_FRAME_SIZE, 2, 0,
+				dev->ts1.height, dev->ts1.width);
+
+	dev->mpeg_ctrl_handler.width = dev->ts1.width;
+	dev->mpeg_ctrl_handler.height = dev->ts1.height;
+
+	cx2341x_handler_setup(&dev->mpeg_ctrl_handler);
+
+	cx231xx_api_cmd(dev, CX2341X_ENC_MISC, 2, 0, 3, 1);
+	cx231xx_api_cmd(dev, CX2341X_ENC_MISC, 2, 0, 4, 1);
+}
+
+static int cx231xx_initialize_codec(struct cx231xx *dev)
+{
+	int version;
+	int retval;
+	u32 i;
+	u32 val = 0;
+
+	dprintk(1, "%s()\n", __func__);
+	cx231xx_disable656(dev);
+	retval = cx231xx_api_cmd(dev, CX2341X_ENC_PING_FW, 0, 0); /* ping */
+	if (retval < 0) {
+		dprintk(2, "%s: PING OK\n", __func__);
+		retval = cx231xx_load_firmware(dev);
+		if (retval < 0) {
+			dev_err(dev->dev,
+				"%s: f/w load failed\n", __func__);
+			return retval;
+		}
+		retval = cx231xx_find_mailbox(dev);
+		if (retval < 0) {
+			dev_err(dev->dev, "%s: mailbox < 0, error\n",
+				__func__);
+			return retval;
+		}
+		dev->cx23417_mailbox = retval;
+		retval = cx231xx_api_cmd(dev, CX2341X_ENC_PING_FW, 0, 0);
+		if (retval < 0) {
+			dev_err(dev->dev,
+				"ERROR: cx23417 firmware ping failed!\n");
+			return retval;
+		}
+		retval = cx231xx_api_cmd(dev, CX2341X_ENC_GET_VERSION, 0, 1,
+			&version);
+		if (retval < 0) {
+			dev_err(dev->dev,
+				"ERROR: cx23417 firmware get encoder: version failed!\n");
+			return retval;
+		}
+		dprintk(1, "cx23417 firmware version is 0x%08x\n", version);
+		msleep(200);
+	}
+
+	for (i = 0; i < 1; i++) {
+		retval = mc417_register_read(dev, 0x20f8, &val);
+		dprintk(3, "***before enable656() VIM Capture Lines = %d ***\n",
+				 val);
+		if (retval < 0)
+			return retval;
+	}
+
+	cx231xx_enable656(dev);
+
+	/* stop mpeg capture */
+	cx231xx_api_cmd(dev, CX2341X_ENC_STOP_CAPTURE, 3, 0, 1, 3, 4);
+
+	cx231xx_codec_settings(dev);
+	msleep(60);
+
+/*	cx231xx_api_cmd(dev, CX2341X_ENC_SET_NUM_VSYNC_LINES, 2, 0,
+		CX231xx_FIELD1_SAA7115, CX231xx_FIELD2_SAA7115);
+	cx231xx_api_cmd(dev, CX2341X_ENC_SET_PLACEHOLDER, 12, 0,
+		CX231xx_CUSTOM_EXTENSION_USR_DATA, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+		0, 0);
+*/
+
+#if 0
+	/* TODO */
+	u32 data[7];
+
+	/* Setup to capture VBI */
+	data[0] = 0x0001BD00;
+	data[1] = 1;          /* frames per interrupt */
+	data[2] = 4;          /* total bufs */
+	data[3] = 0x91559155; /* start codes */
+	data[4] = 0x206080C0; /* stop codes */
+	data[5] = 6;          /* lines */
+	data[6] = 64;         /* BPL */
+
+	cx231xx_api_cmd(dev, CX2341X_ENC_SET_VBI_CONFIG, 7, 0, data[0], data[1],
+		data[2], data[3], data[4], data[5], data[6]);
+
+	for (i = 2; i <= 24; i++) {
+		int valid;
+
+		valid = ((i >= 19) && (i <= 21));
+		cx231xx_api_cmd(dev, CX2341X_ENC_SET_VBI_LINE, 5, 0, i,
+				valid, 0 , 0, 0);
+		cx231xx_api_cmd(dev, CX2341X_ENC_SET_VBI_LINE, 5, 0,
+				i | 0x80000000, valid, 0, 0, 0);
+	}
+#endif
+/*	cx231xx_api_cmd(dev, CX2341X_ENC_MUTE_AUDIO, 1, 0, CX231xx_UNMUTE);
+	msleep(60);
+*/
+	/* initialize the video input */
+	retval = cx231xx_api_cmd(dev, CX2341X_ENC_INITIALIZE_INPUT, 0, 0);
+	if (retval < 0)
+		return retval;
+	msleep(60);
+
+	/* Enable VIP style pixel invalidation so we work with scaled mode */
+	mc417_memory_write(dev, 2120, 0x00000080);
+
+	/* start capturing to the host interface */
+	retval = cx231xx_api_cmd(dev, CX2341X_ENC_START_CAPTURE, 2, 0,
+		CX231xx_MPEG_CAPTURE, CX231xx_RAW_BITS_NONE);
+	if (retval < 0)
+		return retval;
+	msleep(10);
+
+	for (i = 0; i < 1; i++) {
+		mc417_register_read(dev, 0x20f8, &val);
+		dprintk(3, "***VIM Capture Lines =%d ***\n", val);
+	}
+
+	return 0;
+}
+
+/* ------------------------------------------------------------------ */
+
+static int bb_buf_setup(struct videobuf_queue *q,
+	unsigned int *count, unsigned int *size)
+{
+	struct cx231xx_fh *fh = q->priv_data;
+
+	fh->dev->ts1.ts_packet_size  = mpeglinesize;
+	fh->dev->ts1.ts_packet_count = mpeglines;
+
+	*size = fh->dev->ts1.ts_packet_size * fh->dev->ts1.ts_packet_count;
+	*count = mpegbufs;
+
+	return 0;
+}
+
+static void free_buffer(struct videobuf_queue *vq, struct cx231xx_buffer *buf)
+{
+	struct cx231xx_fh *fh = vq->priv_data;
+	struct cx231xx *dev = fh->dev;
+	unsigned long flags = 0;
+
+	BUG_ON(in_interrupt());
+
+	spin_lock_irqsave(&dev->video_mode.slock, flags);
+	if (dev->USE_ISO) {
+		if (dev->video_mode.isoc_ctl.buf == buf)
+			dev->video_mode.isoc_ctl.buf = NULL;
+	} else {
+		if (dev->video_mode.bulk_ctl.buf == buf)
+			dev->video_mode.bulk_ctl.buf = NULL;
+	}
+	spin_unlock_irqrestore(&dev->video_mode.slock, flags);
+	videobuf_waiton(vq, &buf->vb, 0, 0);
+	videobuf_vmalloc_free(&buf->vb);
+	buf->vb.state = VIDEOBUF_NEEDS_INIT;
+}
+
+static void buffer_copy(struct cx231xx *dev, char *data, int len, struct urb *urb,
+		struct cx231xx_dmaqueue *dma_q)
+{
+	void *vbuf;
+	struct cx231xx_buffer *buf;
+	u32 tail_data = 0;
+	char *p_data;
+
+	if (dma_q->mpeg_buffer_done == 0) {
+		if (list_empty(&dma_q->active))
+			return;
+
+		buf = list_entry(dma_q->active.next,
+				struct cx231xx_buffer, vb.queue);
+		dev->video_mode.isoc_ctl.buf = buf;
+		dma_q->mpeg_buffer_done = 1;
+	}
+	/* Fill buffer */
+	buf = dev->video_mode.isoc_ctl.buf;
+	vbuf = videobuf_to_vmalloc(&buf->vb);
+
+	if ((dma_q->mpeg_buffer_completed+len) <
+			mpeglines*mpeglinesize) {
+		if (dma_q->add_ps_package_head ==
+				CX231XX_NEED_ADD_PS_PACKAGE_HEAD) {
+			memcpy(vbuf+dma_q->mpeg_buffer_completed,
+					dma_q->ps_head, 3);
+			dma_q->mpeg_buffer_completed =
+				dma_q->mpeg_buffer_completed + 3;
+			dma_q->add_ps_package_head =
+				CX231XX_NONEED_PS_PACKAGE_HEAD;
+		}
+		memcpy(vbuf+dma_q->mpeg_buffer_completed, data, len);
+		dma_q->mpeg_buffer_completed =
+			dma_q->mpeg_buffer_completed + len;
+	} else {
+		dma_q->mpeg_buffer_done = 0;
+
+		tail_data =
+			mpeglines*mpeglinesize - dma_q->mpeg_buffer_completed;
+		memcpy(vbuf+dma_q->mpeg_buffer_completed,
+				data, tail_data);
+
+		buf->vb.state = VIDEOBUF_DONE;
+		buf->vb.field_count++;
+		v4l2_get_timestamp(&buf->vb.ts);
+		list_del(&buf->vb.queue);
+		wake_up(&buf->vb.done);
+		dma_q->mpeg_buffer_completed = 0;
+
+		if (len - tail_data > 0) {
+			p_data = data + tail_data;
+			dma_q->left_data_count = len - tail_data;
+			memcpy(dma_q->p_left_data,
+					p_data, len - tail_data);
+		}
+	}
+}
+
+static void buffer_filled(char *data, int len, struct urb *urb,
+		struct cx231xx_dmaqueue *dma_q)
+{
+	void *vbuf;
+	struct cx231xx_buffer *buf;
+
+	if (list_empty(&dma_q->active))
+		return;
+
+	buf = list_entry(dma_q->active.next,
+			struct cx231xx_buffer, vb.queue);
+
+	/* Fill buffer */
+	vbuf = videobuf_to_vmalloc(&buf->vb);
+	memcpy(vbuf, data, len);
+	buf->vb.state = VIDEOBUF_DONE;
+	buf->vb.field_count++;
+	v4l2_get_timestamp(&buf->vb.ts);
+	list_del(&buf->vb.queue);
+	wake_up(&buf->vb.done);
+}
+
+static int cx231xx_isoc_copy(struct cx231xx *dev, struct urb *urb)
+{
+	struct cx231xx_dmaqueue *dma_q = urb->context;
+	unsigned char *p_buffer;
+	u32 buffer_size = 0;
+	u32 i = 0;
+
+	for (i = 0; i < urb->number_of_packets; i++) {
+		if (dma_q->left_data_count > 0) {
+			buffer_copy(dev, dma_q->p_left_data,
+				    dma_q->left_data_count, urb, dma_q);
+			dma_q->mpeg_buffer_completed = dma_q->left_data_count;
+			dma_q->left_data_count = 0;
+		}
+
+		p_buffer = urb->transfer_buffer +
+				urb->iso_frame_desc[i].offset;
+		buffer_size = urb->iso_frame_desc[i].actual_length;
+
+		if (buffer_size > 0)
+			buffer_copy(dev, p_buffer, buffer_size, urb, dma_q);
+	}
+
+	return 0;
+}
+
+static int cx231xx_bulk_copy(struct cx231xx *dev, struct urb *urb)
+{
+	struct cx231xx_dmaqueue *dma_q = urb->context;
+	unsigned char *p_buffer, *buffer;
+	u32 buffer_size = 0;
+
+	p_buffer = urb->transfer_buffer;
+	buffer_size = urb->actual_length;
+
+	buffer = kmalloc(buffer_size, GFP_ATOMIC);
+	if (!buffer)
+		return -ENOMEM;
+
+	memcpy(buffer, dma_q->ps_head, 3);
+	memcpy(buffer+3, p_buffer, buffer_size-3);
+	memcpy(dma_q->ps_head, p_buffer+buffer_size-3, 3);
+
+	p_buffer = buffer;
+	buffer_filled(p_buffer, buffer_size, urb, dma_q);
+
+	kfree(buffer);
+	return 0;
+}
+
+static int bb_buf_prepare(struct videobuf_queue *q,
+	struct videobuf_buffer *vb, enum v4l2_field field)
+{
+	struct cx231xx_fh *fh = q->priv_data;
+	struct cx231xx_buffer *buf =
+	    container_of(vb, struct cx231xx_buffer, vb);
+	struct cx231xx *dev = fh->dev;
+	int rc = 0, urb_init = 0;
+	int size = fh->dev->ts1.ts_packet_size * fh->dev->ts1.ts_packet_count;
+
+	if (0 != buf->vb.baddr  &&  buf->vb.bsize < size)
+		return -EINVAL;
+	buf->vb.width = fh->dev->ts1.ts_packet_size;
+	buf->vb.height = fh->dev->ts1.ts_packet_count;
+	buf->vb.size = size;
+	buf->vb.field = field;
+
+	if (VIDEOBUF_NEEDS_INIT == buf->vb.state) {
+		rc = videobuf_iolock(q, &buf->vb, NULL);
+		if (rc < 0)
+			goto fail;
+	}
+
+	if (dev->USE_ISO) {
+		if (!dev->video_mode.isoc_ctl.num_bufs)
+			urb_init = 1;
+	} else {
+		if (!dev->video_mode.bulk_ctl.num_bufs)
+			urb_init = 1;
+	}
+	dev_dbg(dev->dev,
+		"urb_init=%d dev->video_mode.max_pkt_size=%d\n",
+		urb_init, dev->video_mode.max_pkt_size);
+	dev->mode_tv = 1;
+
+	if (urb_init) {
+		rc = cx231xx_set_mode(dev, CX231XX_DIGITAL_MODE);
+		rc = cx231xx_unmute_audio(dev);
+		if (dev->USE_ISO) {
+			cx231xx_set_alt_setting(dev, INDEX_TS1, 4);
+			rc = cx231xx_init_isoc(dev, mpeglines,
+				       mpegbufs,
+				       dev->ts1_mode.max_pkt_size,
+				       cx231xx_isoc_copy);
+		} else {
+			cx231xx_set_alt_setting(dev, INDEX_TS1, 0);
+			rc = cx231xx_init_bulk(dev, mpeglines,
+				       mpegbufs,
+				       dev->ts1_mode.max_pkt_size,
+				       cx231xx_bulk_copy);
+		}
+		if (rc < 0)
+			goto fail;
+	}
+
+	buf->vb.state = VIDEOBUF_PREPARED;
+	return 0;
+
+fail:
+	free_buffer(q, buf);
+	return rc;
+}
+
+static void bb_buf_queue(struct videobuf_queue *q,
+	struct videobuf_buffer *vb)
+{
+	struct cx231xx_fh *fh = q->priv_data;
+
+	struct cx231xx_buffer *buf =
+	    container_of(vb, struct cx231xx_buffer, vb);
+	struct cx231xx *dev = fh->dev;
+	struct cx231xx_dmaqueue *vidq = &dev->video_mode.vidq;
+
+	buf->vb.state = VIDEOBUF_QUEUED;
+	list_add_tail(&buf->vb.queue, &vidq->active);
+
+}
+
+static void bb_buf_release(struct videobuf_queue *q,
+	struct videobuf_buffer *vb)
+{
+	struct cx231xx_buffer *buf =
+	    container_of(vb, struct cx231xx_buffer, vb);
+	/*struct cx231xx_fh *fh = q->priv_data;*/
+	/*struct cx231xx *dev = (struct cx231xx *)fh->dev;*/
+
+	free_buffer(q, buf);
+}
+
+static const struct videobuf_queue_ops cx231xx_qops = {
+	.buf_setup    = bb_buf_setup,
+	.buf_prepare  = bb_buf_prepare,
+	.buf_queue    = bb_buf_queue,
+	.buf_release  = bb_buf_release,
+};
+
+/* ------------------------------------------------------------------ */
+
+static int vidioc_cropcap(struct file *file, void *priv,
+			  struct v4l2_cropcap *cc)
+{
+	struct cx231xx_fh *fh = priv;
+	struct cx231xx *dev = fh->dev;
+	bool is_50hz = dev->encodernorm.id & V4L2_STD_625_50;
+
+	if (cc->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	cc->bounds.left = 0;
+	cc->bounds.top = 0;
+	cc->bounds.width = dev->ts1.width;
+	cc->bounds.height = dev->ts1.height;
+	cc->defrect = cc->bounds;
+	cc->pixelaspect.numerator = is_50hz ? 54 : 11;
+	cc->pixelaspect.denominator = is_50hz ? 59 : 10;
+
+	return 0;
+}
+
+static int vidioc_g_std(struct file *file, void *fh0, v4l2_std_id *norm)
+{
+	struct cx231xx_fh  *fh  = file->private_data;
+	struct cx231xx *dev = fh->dev;
+
+	*norm = dev->encodernorm.id;
+	return 0;
+}
+
+static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id id)
+{
+	struct cx231xx_fh  *fh  = file->private_data;
+	struct cx231xx *dev = fh->dev;
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(cx231xx_tvnorms); i++)
+		if (id & cx231xx_tvnorms[i].id)
+			break;
+	if (i == ARRAY_SIZE(cx231xx_tvnorms))
+		return -EINVAL;
+	dev->encodernorm = cx231xx_tvnorms[i];
+
+	if (dev->encodernorm.id & 0xb000) {
+		dprintk(3, "encodernorm set to NTSC\n");
+		dev->norm = V4L2_STD_NTSC;
+		dev->ts1.height = 480;
+		cx2341x_handler_set_50hz(&dev->mpeg_ctrl_handler, false);
+	} else {
+		dprintk(3, "encodernorm set to PAL\n");
+		dev->norm = V4L2_STD_PAL_B;
+		dev->ts1.height = 576;
+		cx2341x_handler_set_50hz(&dev->mpeg_ctrl_handler, true);
+	}
+	call_all(dev, video, s_std, dev->norm);
+	/* do mode control overrides */
+	cx231xx_do_mode_ctrl_overrides(dev);
+
+	dprintk(3, "exit vidioc_s_std() i=0x%x\n", i);
+	return 0;
+}
+
+static int vidioc_s_ctrl(struct file *file, void *priv,
+				struct v4l2_control *ctl)
+{
+	struct cx231xx_fh  *fh  = file->private_data;
+	struct cx231xx *dev = fh->dev;
+	struct v4l2_subdev *sd;
+
+	dprintk(3, "enter vidioc_s_ctrl()\n");
+	/* Update the A/V core */
+	v4l2_device_for_each_subdev(sd, &dev->v4l2_dev)
+		v4l2_s_ctrl(NULL, sd->ctrl_handler, ctl);
+	dprintk(3, "exit vidioc_s_ctrl()\n");
+	return 0;
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
+					struct v4l2_fmtdesc *f)
+{
+	if (f->index != 0)
+		return -EINVAL;
+
+	strlcpy(f->description, "MPEG", sizeof(f->description));
+	f->pixelformat = V4L2_PIX_FMT_MPEG;
+
+	return 0;
+}
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+				struct v4l2_format *f)
+{
+	struct cx231xx_fh  *fh  = file->private_data;
+	struct cx231xx *dev = fh->dev;
+
+	dprintk(3, "enter vidioc_g_fmt_vid_cap()\n");
+	f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG;
+	f->fmt.pix.bytesperline = 0;
+	f->fmt.pix.sizeimage = mpeglines * mpeglinesize;
+	f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+	f->fmt.pix.width = dev->ts1.width;
+	f->fmt.pix.height = dev->ts1.height;
+	f->fmt.pix.field = V4L2_FIELD_INTERLACED;
+	dprintk(1, "VIDIOC_G_FMT: w: %d, h: %d\n",
+		dev->ts1.width, dev->ts1.height);
+	dprintk(3, "exit vidioc_g_fmt_vid_cap()\n");
+	return 0;
+}
+
+static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
+				struct v4l2_format *f)
+{
+	struct cx231xx_fh  *fh  = file->private_data;
+	struct cx231xx *dev = fh->dev;
+
+	dprintk(3, "enter vidioc_try_fmt_vid_cap()\n");
+	f->fmt.pix.pixelformat = V4L2_PIX_FMT_MPEG;
+	f->fmt.pix.bytesperline = 0;
+	f->fmt.pix.sizeimage = mpeglines * mpeglinesize;
+	f->fmt.pix.field = V4L2_FIELD_INTERLACED;
+	f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+	dprintk(1, "VIDIOC_TRY_FMT: w: %d, h: %d\n",
+		dev->ts1.width, dev->ts1.height);
+	dprintk(3, "exit vidioc_try_fmt_vid_cap()\n");
+	return 0;
+}
+
+static int vidioc_reqbufs(struct file *file, void *priv,
+				struct v4l2_requestbuffers *p)
+{
+	struct cx231xx_fh  *fh  = file->private_data;
+
+	return videobuf_reqbufs(&fh->vidq, p);
+}
+
+static int vidioc_querybuf(struct file *file, void *priv,
+				struct v4l2_buffer *p)
+{
+	struct cx231xx_fh  *fh  = file->private_data;
+
+	return videobuf_querybuf(&fh->vidq, p);
+}
+
+static int vidioc_qbuf(struct file *file, void *priv,
+				struct v4l2_buffer *p)
+{
+	struct cx231xx_fh  *fh  = file->private_data;
+
+	return videobuf_qbuf(&fh->vidq, p);
+}
+
+static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *b)
+{
+	struct cx231xx_fh  *fh  = priv;
+
+	return videobuf_dqbuf(&fh->vidq, b, file->f_flags & O_NONBLOCK);
+}
+
+
+static int vidioc_streamon(struct file *file, void *priv,
+				enum v4l2_buf_type i)
+{
+	struct cx231xx_fh  *fh  = file->private_data;
+	struct cx231xx *dev = fh->dev;
+
+	dprintk(3, "enter vidioc_streamon()\n");
+	cx231xx_set_alt_setting(dev, INDEX_TS1, 0);
+	cx231xx_set_mode(dev, CX231XX_DIGITAL_MODE);
+	if (dev->USE_ISO)
+		cx231xx_init_isoc(dev, CX231XX_NUM_PACKETS,
+				CX231XX_NUM_BUFS,
+				dev->video_mode.max_pkt_size,
+				cx231xx_isoc_copy);
+	else {
+		cx231xx_init_bulk(dev, 320,
+				5,
+				dev->ts1_mode.max_pkt_size,
+				cx231xx_bulk_copy);
+	}
+	dprintk(3, "exit vidioc_streamon()\n");
+	return videobuf_streamon(&fh->vidq);
+}
+
+static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
+{
+	struct cx231xx_fh  *fh  = file->private_data;
+
+	return videobuf_streamoff(&fh->vidq);
+}
+
+static int vidioc_log_status(struct file *file, void *priv)
+{
+	struct cx231xx_fh  *fh  = priv;
+	struct cx231xx *dev = fh->dev;
+
+	call_all(dev, core, log_status);
+	return v4l2_ctrl_log_status(file, priv);
+}
+
+static int mpeg_open(struct file *file)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct cx231xx *dev = video_drvdata(file);
+	struct cx231xx_fh *fh;
+
+	dprintk(2, "%s()\n", __func__);
+
+	if (mutex_lock_interruptible(&dev->lock))
+		return -ERESTARTSYS;
+
+	/* allocate + initialize per filehandle data */
+	fh = kzalloc(sizeof(*fh), GFP_KERNEL);
+	if (NULL == fh) {
+		mutex_unlock(&dev->lock);
+		return -ENOMEM;
+	}
+
+	file->private_data = fh;
+	v4l2_fh_init(&fh->fh, vdev);
+	fh->dev = dev;
+
+
+	videobuf_queue_vmalloc_init(&fh->vidq, &cx231xx_qops,
+			    NULL, &dev->video_mode.slock,
+			    V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_INTERLACED,
+			    sizeof(struct cx231xx_buffer), fh, &dev->lock);
+/*
+	videobuf_queue_sg_init(&fh->vidq, &cx231xx_qops,
+			    dev->dev, &dev->ts1.slock,
+			    V4L2_BUF_TYPE_VIDEO_CAPTURE,
+			    V4L2_FIELD_INTERLACED,
+			    sizeof(struct cx231xx_buffer),
+			    fh, &dev->lock);
+*/
+
+	cx231xx_set_alt_setting(dev, INDEX_VANC, 1);
+	cx231xx_set_gpio_value(dev, 2, 0);
+
+	cx231xx_initialize_codec(dev);
+
+	mutex_unlock(&dev->lock);
+	v4l2_fh_add(&fh->fh);
+	cx231xx_start_TS1(dev);
+
+	return 0;
+}
+
+static int mpeg_release(struct file *file)
+{
+	struct cx231xx_fh  *fh  = file->private_data;
+	struct cx231xx *dev = fh->dev;
+
+	dprintk(3, "mpeg_release()! dev=0x%p\n", dev);
+
+	mutex_lock(&dev->lock);
+
+	cx231xx_stop_TS1(dev);
+
+	/* do this before setting alternate! */
+	if (dev->USE_ISO)
+		cx231xx_uninit_isoc(dev);
+	else
+		cx231xx_uninit_bulk(dev);
+	cx231xx_set_mode(dev, CX231XX_SUSPEND);
+
+	cx231xx_api_cmd(fh->dev, CX2341X_ENC_STOP_CAPTURE, 3, 0,
+			CX231xx_END_NOW, CX231xx_MPEG_CAPTURE,
+			CX231xx_RAW_BITS_NONE);
+
+	/* FIXME: Review this crap */
+	/* Shut device down on last close */
+	if (atomic_cmpxchg(&fh->v4l_reading, 1, 0) == 1) {
+		if (atomic_dec_return(&dev->v4l_reader_count) == 0) {
+			/* stop mpeg capture */
+
+			msleep(500);
+			cx231xx_417_check_encoder(dev);
+
+		}
+	}
+
+	if (fh->vidq.streaming)
+		videobuf_streamoff(&fh->vidq);
+	if (fh->vidq.reading)
+		videobuf_read_stop(&fh->vidq);
+
+	videobuf_mmap_free(&fh->vidq);
+	v4l2_fh_del(&fh->fh);
+	v4l2_fh_exit(&fh->fh);
+	kfree(fh);
+	mutex_unlock(&dev->lock);
+	return 0;
+}
+
+static ssize_t mpeg_read(struct file *file, char __user *data,
+	size_t count, loff_t *ppos)
+{
+	struct cx231xx_fh *fh = file->private_data;
+	struct cx231xx *dev = fh->dev;
+
+	/* Deal w/ A/V decoder * and mpeg encoder sync issues. */
+	/* Start mpeg encoder on first read. */
+	if (atomic_cmpxchg(&fh->v4l_reading, 0, 1) == 0) {
+		if (atomic_inc_return(&dev->v4l_reader_count) == 1) {
+			if (cx231xx_initialize_codec(dev) < 0)
+				return -EINVAL;
+		}
+	}
+
+	return videobuf_read_stream(&fh->vidq, data, count, ppos, 0,
+				    file->f_flags & O_NONBLOCK);
+}
+
+static __poll_t mpeg_poll(struct file *file,
+	struct poll_table_struct *wait)
+{
+	__poll_t req_events = poll_requested_events(wait);
+	struct cx231xx_fh *fh = file->private_data;
+	struct cx231xx *dev = fh->dev;
+	__poll_t res = 0;
+
+	if (v4l2_event_pending(&fh->fh))
+		res |= EPOLLPRI;
+	else
+		poll_wait(file, &fh->fh.wait, wait);
+
+	if (!(req_events & (EPOLLIN | EPOLLRDNORM)))
+		return res;
+
+	mutex_lock(&dev->lock);
+	res |= videobuf_poll_stream(file, &fh->vidq, wait);
+	mutex_unlock(&dev->lock);
+	return res;
+}
+
+static int mpeg_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	struct cx231xx_fh *fh = file->private_data;
+
+	dprintk(2, "%s()\n", __func__);
+
+	return videobuf_mmap_mapper(&fh->vidq, vma);
+}
+
+static const struct v4l2_file_operations mpeg_fops = {
+	.owner	       = THIS_MODULE,
+	.open	       = mpeg_open,
+	.release       = mpeg_release,
+	.read	       = mpeg_read,
+	.poll          = mpeg_poll,
+	.mmap	       = mpeg_mmap,
+	.unlocked_ioctl = video_ioctl2,
+};
+
+static const struct v4l2_ioctl_ops mpeg_ioctl_ops = {
+	.vidioc_s_std		 = vidioc_s_std,
+	.vidioc_g_std		 = vidioc_g_std,
+	.vidioc_g_tuner          = cx231xx_g_tuner,
+	.vidioc_s_tuner          = cx231xx_s_tuner,
+	.vidioc_g_frequency      = cx231xx_g_frequency,
+	.vidioc_s_frequency      = cx231xx_s_frequency,
+	.vidioc_enum_input	 = cx231xx_enum_input,
+	.vidioc_g_input		 = cx231xx_g_input,
+	.vidioc_s_input		 = cx231xx_s_input,
+	.vidioc_s_ctrl		 = vidioc_s_ctrl,
+	.vidioc_cropcap		 = vidioc_cropcap,
+	.vidioc_querycap	 = cx231xx_querycap,
+	.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap	 = vidioc_g_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap	 = vidioc_try_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap	 = vidioc_try_fmt_vid_cap,
+	.vidioc_reqbufs		 = vidioc_reqbufs,
+	.vidioc_querybuf	 = vidioc_querybuf,
+	.vidioc_qbuf		 = vidioc_qbuf,
+	.vidioc_dqbuf		 = vidioc_dqbuf,
+	.vidioc_streamon	 = vidioc_streamon,
+	.vidioc_streamoff	 = vidioc_streamoff,
+	.vidioc_log_status	 = vidioc_log_status,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.vidioc_g_register	 = cx231xx_g_register,
+	.vidioc_s_register	 = cx231xx_s_register,
+#endif
+	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static struct video_device cx231xx_mpeg_template = {
+	.name          = "cx231xx",
+	.fops          = &mpeg_fops,
+	.ioctl_ops     = &mpeg_ioctl_ops,
+	.minor         = -1,
+	.tvnorms       = V4L2_STD_ALL,
+};
+
+void cx231xx_417_unregister(struct cx231xx *dev)
+{
+	dprintk(1, "%s()\n", __func__);
+	dprintk(3, "%s()\n", __func__);
+
+	if (video_is_registered(&dev->v4l_device)) {
+		video_unregister_device(&dev->v4l_device);
+		v4l2_ctrl_handler_free(&dev->mpeg_ctrl_handler.hdl);
+	}
+}
+
+static int cx231xx_s_video_encoding(struct cx2341x_handler *cxhdl, u32 val)
+{
+	struct cx231xx *dev = container_of(cxhdl, struct cx231xx, mpeg_ctrl_handler);
+	int is_mpeg1 = val == V4L2_MPEG_VIDEO_ENCODING_MPEG_1;
+	struct v4l2_subdev_format format = {
+		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+	};
+
+	/* fix videodecoder resolution */
+	format.format.width = cxhdl->width / (is_mpeg1 ? 2 : 1);
+	format.format.height = cxhdl->height;
+	format.format.code = MEDIA_BUS_FMT_FIXED;
+	v4l2_subdev_call(dev->sd_cx25840, pad, set_fmt, NULL, &format);
+	return 0;
+}
+
+static int cx231xx_s_audio_sampling_freq(struct cx2341x_handler *cxhdl, u32 idx)
+{
+	static const u32 freqs[3] = { 44100, 48000, 32000 };
+	struct cx231xx *dev = container_of(cxhdl, struct cx231xx, mpeg_ctrl_handler);
+
+	/* The audio clock of the digitizer must match the codec sample
+	   rate otherwise you get some very strange effects. */
+	if (idx < ARRAY_SIZE(freqs))
+		call_all(dev, audio, s_clock_freq, freqs[idx]);
+	return 0;
+}
+
+static const struct cx2341x_handler_ops cx231xx_ops = {
+	/* needed for the video clock freq */
+	.s_audio_sampling_freq = cx231xx_s_audio_sampling_freq,
+	/* needed for setting up the video resolution */
+	.s_video_encoding = cx231xx_s_video_encoding,
+};
+
+static void cx231xx_video_dev_init(
+	struct cx231xx *dev,
+	struct usb_device *usbdev,
+	struct video_device *vfd,
+	const struct video_device *template,
+	const char *type)
+{
+	dprintk(1, "%s()\n", __func__);
+	*vfd = *template;
+	snprintf(vfd->name, sizeof(vfd->name), "%s %s (%s)", dev->name,
+		type, cx231xx_boards[dev->model].name);
+
+	vfd->v4l2_dev = &dev->v4l2_dev;
+	vfd->lock = &dev->lock;
+	vfd->release = video_device_release_empty;
+	vfd->ctrl_handler = &dev->mpeg_ctrl_handler.hdl;
+	video_set_drvdata(vfd, dev);
+	if (dev->tuner_type == TUNER_ABSENT) {
+		v4l2_disable_ioctl(vfd, VIDIOC_G_FREQUENCY);
+		v4l2_disable_ioctl(vfd, VIDIOC_S_FREQUENCY);
+		v4l2_disable_ioctl(vfd, VIDIOC_G_TUNER);
+		v4l2_disable_ioctl(vfd, VIDIOC_S_TUNER);
+	}
+}
+
+int cx231xx_417_register(struct cx231xx *dev)
+{
+	/* FIXME: Port1 hardcoded here */
+	int err = -ENODEV;
+	struct cx231xx_tsport *tsport = &dev->ts1;
+
+	dprintk(1, "%s()\n", __func__);
+
+	/* Set default TV standard */
+	dev->encodernorm = cx231xx_tvnorms[0];
+
+	if (dev->encodernorm.id & V4L2_STD_525_60)
+		tsport->height = 480;
+	else
+		tsport->height = 576;
+
+	tsport->width = 720;
+	err = cx2341x_handler_init(&dev->mpeg_ctrl_handler, 50);
+	if (err) {
+		dprintk(3, "%s: can't init cx2341x controls\n", dev->name);
+		return err;
+	}
+	dev->mpeg_ctrl_handler.func = cx231xx_mbox_func;
+	dev->mpeg_ctrl_handler.priv = dev;
+	dev->mpeg_ctrl_handler.ops = &cx231xx_ops;
+	if (dev->sd_cx25840)
+		v4l2_ctrl_add_handler(&dev->mpeg_ctrl_handler.hdl,
+				dev->sd_cx25840->ctrl_handler, NULL);
+	if (dev->mpeg_ctrl_handler.hdl.error) {
+		err = dev->mpeg_ctrl_handler.hdl.error;
+		dprintk(3, "%s: can't add cx25840 controls\n", dev->name);
+		v4l2_ctrl_handler_free(&dev->mpeg_ctrl_handler.hdl);
+		return err;
+	}
+	dev->norm = V4L2_STD_NTSC;
+
+	dev->mpeg_ctrl_handler.port = CX2341X_PORT_SERIAL;
+	cx2341x_handler_set_50hz(&dev->mpeg_ctrl_handler, false);
+
+	/* Allocate and initialize V4L video device */
+	cx231xx_video_dev_init(dev, dev->udev,
+			&dev->v4l_device, &cx231xx_mpeg_template, "mpeg");
+	err = video_register_device(&dev->v4l_device,
+		VFL_TYPE_GRABBER, -1);
+	if (err < 0) {
+		dprintk(3, "%s: can't register mpeg device\n", dev->name);
+		v4l2_ctrl_handler_free(&dev->mpeg_ctrl_handler.hdl);
+		return err;
+	}
+
+	dprintk(3, "%s: registered device video%d [mpeg]\n",
+	       dev->name, dev->v4l_device.num);
+
+	return 0;
+}
+
+MODULE_FIRMWARE(CX231xx_FIRM_IMAGE_NAME);
diff --git a/drivers/media/usb/cx231xx/cx231xx-audio.c b/drivers/media/usb/cx231xx/cx231xx-audio.c
new file mode 100644
index 0000000..32ee7b3
--- /dev/null
+++ b/drivers/media/usb/cx231xx/cx231xx-audio.c
@@ -0,0 +1,791 @@
+/*
+ *  Conexant Cx231xx audio extension
+ *
+ *  Copyright (C) 2008 <srinivasa.deevi at conexant dot com>
+ *       Based on em28xx driver
+ *
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ */
+
+#include "cx231xx.h"
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/sound.h>
+#include <linux/spinlock.h>
+#include <linux/soundcard.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/proc_fs.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/info.h>
+#include <sound/initval.h>
+#include <sound/control.h>
+#include <media/v4l2-common.h>
+
+static int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "activates debug info");
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+
+static int cx231xx_isoc_audio_deinit(struct cx231xx *dev)
+{
+	int i;
+
+	dev_dbg(dev->dev, "Stopping isoc\n");
+
+	for (i = 0; i < CX231XX_AUDIO_BUFS; i++) {
+		if (dev->adev.urb[i]) {
+			if (!irqs_disabled())
+				usb_kill_urb(dev->adev.urb[i]);
+			else
+				usb_unlink_urb(dev->adev.urb[i]);
+
+			usb_free_urb(dev->adev.urb[i]);
+			dev->adev.urb[i] = NULL;
+
+			kfree(dev->adev.transfer_buffer[i]);
+			dev->adev.transfer_buffer[i] = NULL;
+		}
+	}
+
+	return 0;
+}
+
+static int cx231xx_bulk_audio_deinit(struct cx231xx *dev)
+{
+	int i;
+
+	dev_dbg(dev->dev, "Stopping bulk\n");
+
+	for (i = 0; i < CX231XX_AUDIO_BUFS; i++) {
+		if (dev->adev.urb[i]) {
+			if (!irqs_disabled())
+				usb_kill_urb(dev->adev.urb[i]);
+			else
+				usb_unlink_urb(dev->adev.urb[i]);
+
+			usb_free_urb(dev->adev.urb[i]);
+			dev->adev.urb[i] = NULL;
+
+			kfree(dev->adev.transfer_buffer[i]);
+			dev->adev.transfer_buffer[i] = NULL;
+		}
+	}
+
+	return 0;
+}
+
+static void cx231xx_audio_isocirq(struct urb *urb)
+{
+	struct cx231xx *dev = urb->context;
+	int i;
+	unsigned int oldptr;
+	int period_elapsed = 0;
+	int status;
+	unsigned char *cp;
+	unsigned int stride;
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_runtime *runtime;
+
+	if (dev->state & DEV_DISCONNECTED)
+		return;
+
+	switch (urb->status) {
+	case 0:		/* success */
+	case -ETIMEDOUT:	/* NAK */
+		break;
+	case -ECONNRESET:	/* kill */
+	case -ENOENT:
+	case -ESHUTDOWN:
+		return;
+	default:		/* error */
+		dev_dbg(dev->dev, "urb completion error %d.\n",
+			urb->status);
+		break;
+	}
+
+	if (atomic_read(&dev->stream_started) == 0)
+		return;
+
+	if (dev->adev.capture_pcm_substream) {
+		substream = dev->adev.capture_pcm_substream;
+		runtime = substream->runtime;
+		stride = runtime->frame_bits >> 3;
+
+		for (i = 0; i < urb->number_of_packets; i++) {
+			unsigned long flags;
+			int length = urb->iso_frame_desc[i].actual_length /
+				     stride;
+			cp = (unsigned char *)urb->transfer_buffer +
+					      urb->iso_frame_desc[i].offset;
+
+			if (!length)
+				continue;
+
+			oldptr = dev->adev.hwptr_done_capture;
+			if (oldptr + length >= runtime->buffer_size) {
+				unsigned int cnt;
+
+				cnt = runtime->buffer_size - oldptr;
+				memcpy(runtime->dma_area + oldptr * stride, cp,
+				       cnt * stride);
+				memcpy(runtime->dma_area, cp + cnt * stride,
+				       length * stride - cnt * stride);
+			} else {
+				memcpy(runtime->dma_area + oldptr * stride, cp,
+				       length * stride);
+			}
+
+			snd_pcm_stream_lock_irqsave(substream, flags);
+
+			dev->adev.hwptr_done_capture += length;
+			if (dev->adev.hwptr_done_capture >=
+						runtime->buffer_size)
+				dev->adev.hwptr_done_capture -=
+						runtime->buffer_size;
+
+			dev->adev.capture_transfer_done += length;
+			if (dev->adev.capture_transfer_done >=
+				runtime->period_size) {
+				dev->adev.capture_transfer_done -=
+						runtime->period_size;
+				period_elapsed = 1;
+			}
+			snd_pcm_stream_unlock_irqrestore(substream, flags);
+		}
+		if (period_elapsed)
+			snd_pcm_period_elapsed(substream);
+	}
+	urb->status = 0;
+
+	status = usb_submit_urb(urb, GFP_ATOMIC);
+	if (status < 0) {
+		dev_err(dev->dev,
+			"resubmit of audio urb failed (error=%i)\n",
+			status);
+	}
+	return;
+}
+
+static void cx231xx_audio_bulkirq(struct urb *urb)
+{
+	struct cx231xx *dev = urb->context;
+	unsigned int oldptr;
+	int period_elapsed = 0;
+	int status;
+	unsigned char *cp;
+	unsigned int stride;
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_runtime *runtime;
+
+	if (dev->state & DEV_DISCONNECTED)
+		return;
+
+	switch (urb->status) {
+	case 0:		/* success */
+	case -ETIMEDOUT:	/* NAK */
+		break;
+	case -ECONNRESET:	/* kill */
+	case -ENOENT:
+	case -ESHUTDOWN:
+		return;
+	default:		/* error */
+		dev_dbg(dev->dev, "urb completion error %d.\n",
+			urb->status);
+		break;
+	}
+
+	if (atomic_read(&dev->stream_started) == 0)
+		return;
+
+	if (dev->adev.capture_pcm_substream) {
+		substream = dev->adev.capture_pcm_substream;
+		runtime = substream->runtime;
+		stride = runtime->frame_bits >> 3;
+
+		if (1) {
+			unsigned long flags;
+			int length = urb->actual_length /
+				     stride;
+			cp = (unsigned char *)urb->transfer_buffer;
+
+			oldptr = dev->adev.hwptr_done_capture;
+			if (oldptr + length >= runtime->buffer_size) {
+				unsigned int cnt;
+
+				cnt = runtime->buffer_size - oldptr;
+				memcpy(runtime->dma_area + oldptr * stride, cp,
+				       cnt * stride);
+				memcpy(runtime->dma_area, cp + cnt * stride,
+				       length * stride - cnt * stride);
+			} else {
+				memcpy(runtime->dma_area + oldptr * stride, cp,
+				       length * stride);
+			}
+
+			snd_pcm_stream_lock_irqsave(substream, flags);
+
+			dev->adev.hwptr_done_capture += length;
+			if (dev->adev.hwptr_done_capture >=
+						runtime->buffer_size)
+				dev->adev.hwptr_done_capture -=
+						runtime->buffer_size;
+
+			dev->adev.capture_transfer_done += length;
+			if (dev->adev.capture_transfer_done >=
+				runtime->period_size) {
+				dev->adev.capture_transfer_done -=
+						runtime->period_size;
+				period_elapsed = 1;
+			}
+			snd_pcm_stream_unlock_irqrestore(substream, flags);
+		}
+		if (period_elapsed)
+			snd_pcm_period_elapsed(substream);
+	}
+	urb->status = 0;
+
+	status = usb_submit_urb(urb, GFP_ATOMIC);
+	if (status < 0) {
+		dev_err(dev->dev,
+			"resubmit of audio urb failed (error=%i)\n",
+			status);
+	}
+	return;
+}
+
+static int cx231xx_init_audio_isoc(struct cx231xx *dev)
+{
+	int i, errCode;
+	int sb_size;
+
+	dev_dbg(dev->dev,
+		"%s: Starting ISO AUDIO transfers\n", __func__);
+
+	if (dev->state & DEV_DISCONNECTED)
+		return -ENODEV;
+
+	sb_size = CX231XX_ISO_NUM_AUDIO_PACKETS * dev->adev.max_pkt_size;
+
+	for (i = 0; i < CX231XX_AUDIO_BUFS; i++) {
+		struct urb *urb;
+		int j, k;
+
+		dev->adev.transfer_buffer[i] = kmalloc(sb_size, GFP_ATOMIC);
+		if (!dev->adev.transfer_buffer[i])
+			return -ENOMEM;
+
+		memset(dev->adev.transfer_buffer[i], 0x80, sb_size);
+		urb = usb_alloc_urb(CX231XX_ISO_NUM_AUDIO_PACKETS, GFP_ATOMIC);
+		if (!urb) {
+			for (j = 0; j < i; j++) {
+				usb_free_urb(dev->adev.urb[j]);
+				kfree(dev->adev.transfer_buffer[j]);
+			}
+			return -ENOMEM;
+		}
+
+		urb->dev = dev->udev;
+		urb->context = dev;
+		urb->pipe = usb_rcvisocpipe(dev->udev,
+						dev->adev.end_point_addr);
+		urb->transfer_flags = URB_ISO_ASAP;
+		urb->transfer_buffer = dev->adev.transfer_buffer[i];
+		urb->interval = 1;
+		urb->complete = cx231xx_audio_isocirq;
+		urb->number_of_packets = CX231XX_ISO_NUM_AUDIO_PACKETS;
+		urb->transfer_buffer_length = sb_size;
+
+		for (j = k = 0; j < CX231XX_ISO_NUM_AUDIO_PACKETS;
+			j++, k += dev->adev.max_pkt_size) {
+			urb->iso_frame_desc[j].offset = k;
+			urb->iso_frame_desc[j].length = dev->adev.max_pkt_size;
+		}
+		dev->adev.urb[i] = urb;
+	}
+
+	for (i = 0; i < CX231XX_AUDIO_BUFS; i++) {
+		errCode = usb_submit_urb(dev->adev.urb[i], GFP_ATOMIC);
+		if (errCode < 0) {
+			cx231xx_isoc_audio_deinit(dev);
+			return errCode;
+		}
+	}
+
+	return errCode;
+}
+
+static int cx231xx_init_audio_bulk(struct cx231xx *dev)
+{
+	int i, errCode;
+	int sb_size;
+
+	dev_dbg(dev->dev,
+		"%s: Starting BULK AUDIO transfers\n", __func__);
+
+	if (dev->state & DEV_DISCONNECTED)
+		return -ENODEV;
+
+	sb_size = CX231XX_NUM_AUDIO_PACKETS * dev->adev.max_pkt_size;
+
+	for (i = 0; i < CX231XX_AUDIO_BUFS; i++) {
+		struct urb *urb;
+		int j;
+
+		dev->adev.transfer_buffer[i] = kmalloc(sb_size, GFP_ATOMIC);
+		if (!dev->adev.transfer_buffer[i])
+			return -ENOMEM;
+
+		memset(dev->adev.transfer_buffer[i], 0x80, sb_size);
+		urb = usb_alloc_urb(CX231XX_NUM_AUDIO_PACKETS, GFP_ATOMIC);
+		if (!urb) {
+			for (j = 0; j < i; j++) {
+				usb_free_urb(dev->adev.urb[j]);
+				kfree(dev->adev.transfer_buffer[j]);
+			}
+			return -ENOMEM;
+		}
+
+		urb->dev = dev->udev;
+		urb->context = dev;
+		urb->pipe = usb_rcvbulkpipe(dev->udev,
+						dev->adev.end_point_addr);
+		urb->transfer_flags = 0;
+		urb->transfer_buffer = dev->adev.transfer_buffer[i];
+		urb->complete = cx231xx_audio_bulkirq;
+		urb->transfer_buffer_length = sb_size;
+
+		dev->adev.urb[i] = urb;
+
+	}
+
+	for (i = 0; i < CX231XX_AUDIO_BUFS; i++) {
+		errCode = usb_submit_urb(dev->adev.urb[i], GFP_ATOMIC);
+		if (errCode < 0) {
+			cx231xx_bulk_audio_deinit(dev);
+			return errCode;
+		}
+	}
+
+	return errCode;
+}
+
+static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs,
+					size_t size)
+{
+	struct snd_pcm_runtime *runtime = subs->runtime;
+	struct cx231xx *dev = snd_pcm_substream_chip(subs);
+
+	dev_dbg(dev->dev, "Allocating vbuffer\n");
+	if (runtime->dma_area) {
+		if (runtime->dma_bytes > size)
+			return 0;
+
+		vfree(runtime->dma_area);
+	}
+	runtime->dma_area = vmalloc(size);
+	if (!runtime->dma_area)
+		return -ENOMEM;
+
+	runtime->dma_bytes = size;
+
+	return 0;
+}
+
+static const struct snd_pcm_hardware snd_cx231xx_hw_capture = {
+	.info = SNDRV_PCM_INFO_BLOCK_TRANSFER	|
+	    SNDRV_PCM_INFO_MMAP			|
+	    SNDRV_PCM_INFO_INTERLEAVED		|
+	    SNDRV_PCM_INFO_MMAP_VALID,
+
+	.formats = SNDRV_PCM_FMTBIT_S16_LE,
+
+	.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_KNOT,
+
+	.rate_min = 48000,
+	.rate_max = 48000,
+	.channels_min = 2,
+	.channels_max = 2,
+	.buffer_bytes_max = 62720 * 8,	/* just about the value in usbaudio.c */
+	.period_bytes_min = 64,		/* 12544/2, */
+	.period_bytes_max = 12544,
+	.periods_min = 2,
+	.periods_max = 98,		/* 12544, */
+};
+
+static int snd_cx231xx_capture_open(struct snd_pcm_substream *substream)
+{
+	struct cx231xx *dev = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int ret = 0;
+
+	dev_dbg(dev->dev,
+		"opening device and trying to acquire exclusive lock\n");
+
+	if (dev->state & DEV_DISCONNECTED) {
+		dev_err(dev->dev,
+			"Can't open. the device was removed.\n");
+		return -ENODEV;
+	}
+
+	/* set alternate setting for audio interface */
+	/* 1 - 48000 samples per sec */
+	mutex_lock(&dev->lock);
+	if (dev->USE_ISO)
+		ret = cx231xx_set_alt_setting(dev, INDEX_AUDIO, 1);
+	else
+		ret = cx231xx_set_alt_setting(dev, INDEX_AUDIO, 0);
+	mutex_unlock(&dev->lock);
+	if (ret < 0) {
+		dev_err(dev->dev,
+			"failed to set alternate setting !\n");
+
+		return ret;
+	}
+
+	runtime->hw = snd_cx231xx_hw_capture;
+
+	mutex_lock(&dev->lock);
+	/* inform hardware to start streaming */
+	ret = cx231xx_capture_start(dev, 1, Audio);
+
+	dev->adev.users++;
+	mutex_unlock(&dev->lock);
+
+	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+	dev->adev.capture_pcm_substream = substream;
+	runtime->private_data = dev;
+
+	return 0;
+}
+
+static int snd_cx231xx_pcm_close(struct snd_pcm_substream *substream)
+{
+	int ret;
+	struct cx231xx *dev = snd_pcm_substream_chip(substream);
+
+	dev_dbg(dev->dev, "closing device\n");
+
+	/* inform hardware to stop streaming */
+	mutex_lock(&dev->lock);
+	ret = cx231xx_capture_start(dev, 0, Audio);
+
+	/* set alternate setting for audio interface */
+	/* 1 - 48000 samples per sec */
+	ret = cx231xx_set_alt_setting(dev, INDEX_AUDIO, 0);
+	if (ret < 0) {
+		dev_err(dev->dev,
+			"failed to set alternate setting !\n");
+
+		mutex_unlock(&dev->lock);
+		return ret;
+	}
+
+	dev->adev.users--;
+	if (substream->runtime->dma_area) {
+		dev_dbg(dev->dev, "freeing\n");
+		vfree(substream->runtime->dma_area);
+		substream->runtime->dma_area = NULL;
+	}
+	mutex_unlock(&dev->lock);
+
+	if (dev->adev.users == 0 && dev->adev.shutdown == 1) {
+		dev_dbg(dev->dev, "audio users: %d\n", dev->adev.users);
+		dev_dbg(dev->dev, "disabling audio stream!\n");
+		dev->adev.shutdown = 0;
+		dev_dbg(dev->dev, "released lock\n");
+		if (atomic_read(&dev->stream_started) > 0) {
+			atomic_set(&dev->stream_started, 0);
+			schedule_work(&dev->wq_trigger);
+		}
+	}
+	return 0;
+}
+
+static int snd_cx231xx_hw_capture_params(struct snd_pcm_substream *substream,
+					 struct snd_pcm_hw_params *hw_params)
+{
+	struct cx231xx *dev = snd_pcm_substream_chip(substream);
+	int ret;
+
+	dev_dbg(dev->dev, "Setting capture parameters\n");
+
+	ret = snd_pcm_alloc_vmalloc_buffer(substream,
+					   params_buffer_bytes(hw_params));
+#if 0
+	/* TODO: set up cx231xx audio chip to deliver the correct audio format,
+	   current default is 48000hz multiplexed => 96000hz mono
+	   which shouldn't matter since analogue TV only supports mono */
+	unsigned int channels, rate, format;
+
+	format = params_format(hw_params);
+	rate = params_rate(hw_params);
+	channels = params_channels(hw_params);
+#endif
+
+	return ret;
+}
+
+static int snd_cx231xx_hw_capture_free(struct snd_pcm_substream *substream)
+{
+	struct cx231xx *dev = snd_pcm_substream_chip(substream);
+
+	dev_dbg(dev->dev, "Stop capture, if needed\n");
+
+	if (atomic_read(&dev->stream_started) > 0) {
+		atomic_set(&dev->stream_started, 0);
+		schedule_work(&dev->wq_trigger);
+	}
+
+	return 0;
+}
+
+static int snd_cx231xx_prepare(struct snd_pcm_substream *substream)
+{
+	struct cx231xx *dev = snd_pcm_substream_chip(substream);
+
+	dev->adev.hwptr_done_capture = 0;
+	dev->adev.capture_transfer_done = 0;
+
+	return 0;
+}
+
+static void audio_trigger(struct work_struct *work)
+{
+	struct cx231xx *dev = container_of(work, struct cx231xx, wq_trigger);
+
+	if (atomic_read(&dev->stream_started)) {
+		dev_dbg(dev->dev, "starting capture");
+		if (is_fw_load(dev) == 0)
+			cx25840_call(dev, core, load_fw);
+		if (dev->USE_ISO)
+			cx231xx_init_audio_isoc(dev);
+		else
+			cx231xx_init_audio_bulk(dev);
+	} else {
+		dev_dbg(dev->dev, "stopping capture");
+		cx231xx_isoc_audio_deinit(dev);
+	}
+}
+
+static int snd_cx231xx_capture_trigger(struct snd_pcm_substream *substream,
+				       int cmd)
+{
+	struct cx231xx *dev = snd_pcm_substream_chip(substream);
+	int retval = 0;
+
+	if (dev->state & DEV_DISCONNECTED)
+		return -ENODEV;
+
+	spin_lock(&dev->adev.slock);
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		atomic_set(&dev->stream_started, 1);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		atomic_set(&dev->stream_started, 0);
+		break;
+	default:
+		retval = -EINVAL;
+		break;
+	}
+	spin_unlock(&dev->adev.slock);
+
+	schedule_work(&dev->wq_trigger);
+
+	return retval;
+}
+
+static snd_pcm_uframes_t snd_cx231xx_capture_pointer(struct snd_pcm_substream
+						     *substream)
+{
+	struct cx231xx *dev;
+	unsigned long flags;
+	snd_pcm_uframes_t hwptr_done;
+
+	dev = snd_pcm_substream_chip(substream);
+
+	spin_lock_irqsave(&dev->adev.slock, flags);
+	hwptr_done = dev->adev.hwptr_done_capture;
+	spin_unlock_irqrestore(&dev->adev.slock, flags);
+
+	return hwptr_done;
+}
+
+static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs,
+					     unsigned long offset)
+{
+	void *pageptr = subs->runtime->dma_area + offset;
+
+	return vmalloc_to_page(pageptr);
+}
+
+static const struct snd_pcm_ops snd_cx231xx_pcm_capture = {
+	.open = snd_cx231xx_capture_open,
+	.close = snd_cx231xx_pcm_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = snd_cx231xx_hw_capture_params,
+	.hw_free = snd_cx231xx_hw_capture_free,
+	.prepare = snd_cx231xx_prepare,
+	.trigger = snd_cx231xx_capture_trigger,
+	.pointer = snd_cx231xx_capture_pointer,
+	.page = snd_pcm_get_vmalloc_page,
+};
+
+static int cx231xx_audio_init(struct cx231xx *dev)
+{
+	struct cx231xx_audio *adev = &dev->adev;
+	struct snd_pcm *pcm;
+	struct snd_card *card;
+	static int devnr;
+	int err;
+	struct usb_interface *uif;
+	int i, isoc_pipe = 0;
+
+	if (dev->has_alsa_audio != 1) {
+		/* This device does not support the extension (in this case
+		   the device is expecting the snd-usb-audio module or
+		   doesn't have analog audio support at all) */
+		return 0;
+	}
+
+	dev_dbg(dev->dev,
+		"probing for cx231xx non standard usbaudio\n");
+
+	err = snd_card_new(dev->dev, index[devnr], "Cx231xx Audio",
+			   THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	spin_lock_init(&adev->slock);
+	err = snd_pcm_new(card, "Cx231xx Audio", 0, 0, 1, &pcm);
+	if (err < 0)
+		goto err_free_card;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+			&snd_cx231xx_pcm_capture);
+	pcm->info_flags = 0;
+	pcm->private_data = dev;
+	strcpy(pcm->name, "Conexant cx231xx Capture");
+	strcpy(card->driver, "Cx231xx-Audio");
+	strcpy(card->shortname, "Cx231xx Audio");
+	strcpy(card->longname, "Conexant cx231xx Audio");
+
+	INIT_WORK(&dev->wq_trigger, audio_trigger);
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto err_free_card;
+
+	adev->sndcard = card;
+	adev->udev = dev->udev;
+
+	/* compute alternate max packet sizes for Audio */
+	uif =
+	    dev->udev->actconfig->interface[dev->current_pcb_config.
+					    hs_config_info[0].interface_info.
+					    audio_index + 1];
+
+	if (uif->altsetting[0].desc.bNumEndpoints < isoc_pipe + 1) {
+		err = -ENODEV;
+		goto err_free_card;
+	}
+
+	adev->end_point_addr =
+	    uif->altsetting[0].endpoint[isoc_pipe].desc.
+			bEndpointAddress;
+
+	adev->num_alt = uif->num_altsetting;
+	dev_info(dev->dev,
+		"audio EndPoint Addr 0x%x, Alternate settings: %i\n",
+		adev->end_point_addr, adev->num_alt);
+	adev->alt_max_pkt_size = kmalloc_array(32, adev->num_alt, GFP_KERNEL);
+	if (!adev->alt_max_pkt_size) {
+		err = -ENOMEM;
+		goto err_free_card;
+	}
+
+	for (i = 0; i < adev->num_alt; i++) {
+		u16 tmp;
+
+		if (uif->altsetting[i].desc.bNumEndpoints < isoc_pipe + 1) {
+			err = -ENODEV;
+			goto err_free_pkt_size;
+		}
+
+		tmp = le16_to_cpu(uif->altsetting[i].endpoint[isoc_pipe].desc.
+				wMaxPacketSize);
+		adev->alt_max_pkt_size[i] =
+		    (tmp & 0x07ff) * (((tmp & 0x1800) >> 11) + 1);
+		dev_dbg(dev->dev,
+			"audio alternate setting %i, max size= %i\n", i,
+			adev->alt_max_pkt_size[i]);
+	}
+
+	return 0;
+
+err_free_pkt_size:
+	kfree(adev->alt_max_pkt_size);
+err_free_card:
+	snd_card_free(card);
+
+	return err;
+}
+
+static int cx231xx_audio_fini(struct cx231xx *dev)
+{
+	if (dev == NULL)
+		return 0;
+
+	if (dev->has_alsa_audio != 1) {
+		/* This device does not support the extension (in this case
+		   the device is expecting the snd-usb-audio module or
+		   doesn't have analog audio support at all) */
+		return 0;
+	}
+
+	if (dev->adev.sndcard) {
+		snd_card_free(dev->adev.sndcard);
+		kfree(dev->adev.alt_max_pkt_size);
+		dev->adev.sndcard = NULL;
+	}
+
+	return 0;
+}
+
+static struct cx231xx_ops audio_ops = {
+	.id = CX231XX_AUDIO,
+	.name = "Cx231xx Audio Extension",
+	.init = cx231xx_audio_init,
+	.fini = cx231xx_audio_fini,
+};
+
+static int __init cx231xx_alsa_register(void)
+{
+	return cx231xx_register_extension(&audio_ops);
+}
+
+static void __exit cx231xx_alsa_unregister(void)
+{
+	cx231xx_unregister_extension(&audio_ops);
+}
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Srinivasa Deevi <srinivasa.deevi@conexant.com>");
+MODULE_DESCRIPTION("Cx231xx Audio driver");
+
+module_init(cx231xx_alsa_register);
+module_exit(cx231xx_alsa_unregister);
diff --git a/drivers/media/usb/cx231xx/cx231xx-avcore.c b/drivers/media/usb/cx231xx/cx231xx-avcore.c
new file mode 100644
index 0000000..fdd3c22
--- /dev/null
+++ b/drivers/media/usb/cx231xx/cx231xx-avcore.c
@@ -0,0 +1,3115 @@
+/*
+   cx231xx_avcore.c - driver for Conexant Cx23100/101/102
+		      USB video capture devices
+
+   Copyright (C) 2008 <srinivasa.deevi at conexant dot com>
+
+   This program contains the specific code to control the avdecoder chip and
+   other related usb control functions for cx231xx based chipset.
+
+   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.
+ */
+
+#include "cx231xx.h"
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/bitmap.h>
+#include <linux/i2c.h>
+#include <linux/mm.h>
+#include <linux/mutex.h>
+#include <media/tuner.h>
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+
+#include "cx231xx-dif.h"
+
+#define TUNER_MODE_FM_RADIO 0
+/******************************************************************************
+			-: BLOCK ARRANGEMENT :-
+	I2S block ----------------------|
+	[I2S audio]			|
+					|
+	Analog Front End --> Direct IF -|-> Cx25840 --> Audio
+	[video & audio]			|   [Audio]
+					|
+					|-> Cx25840 --> Video
+					    [Video]
+
+*******************************************************************************/
+/******************************************************************************
+ *                    VERVE REGISTER                                          *
+ *									      *
+ ******************************************************************************/
+static int verve_write_byte(struct cx231xx *dev, u8 saddr, u8 data)
+{
+	return cx231xx_write_i2c_data(dev, VERVE_I2C_ADDRESS,
+					saddr, 1, data, 1);
+}
+
+static int verve_read_byte(struct cx231xx *dev, u8 saddr, u8 *data)
+{
+	int status;
+	u32 temp = 0;
+
+	status = cx231xx_read_i2c_data(dev, VERVE_I2C_ADDRESS,
+					saddr, 1, &temp, 1);
+	*data = (u8) temp;
+	return status;
+}
+void initGPIO(struct cx231xx *dev)
+{
+	u32 _gpio_direction = 0;
+	u32 value = 0;
+	u8 val = 0;
+
+	_gpio_direction = _gpio_direction & 0xFC0003FF;
+	_gpio_direction = _gpio_direction | 0x03FDFC00;
+	cx231xx_send_gpio_cmd(dev, _gpio_direction, (u8 *)&value, 4, 0, 0);
+
+	verve_read_byte(dev, 0x07, &val);
+	dev_dbg(dev->dev, "verve_read_byte address0x07=0x%x\n", val);
+	verve_write_byte(dev, 0x07, 0xF4);
+	verve_read_byte(dev, 0x07, &val);
+	dev_dbg(dev->dev, "verve_read_byte address0x07=0x%x\n", val);
+
+	cx231xx_capture_start(dev, 1, Vbi);
+
+	cx231xx_mode_register(dev, EP_MODE_SET, 0x0500FE00);
+	cx231xx_mode_register(dev, GBULK_BIT_EN, 0xFFFDFFFF);
+
+}
+void uninitGPIO(struct cx231xx *dev)
+{
+	u8 value[4] = { 0, 0, 0, 0 };
+
+	cx231xx_capture_start(dev, 0, Vbi);
+	verve_write_byte(dev, 0x07, 0x14);
+	cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER,
+			0x68, value, 4);
+}
+
+/******************************************************************************
+ *                    A F E - B L O C K    C O N T R O L   functions          *
+ *				[ANALOG FRONT END]			      *
+ ******************************************************************************/
+static int afe_write_byte(struct cx231xx *dev, u16 saddr, u8 data)
+{
+	return cx231xx_write_i2c_data(dev, AFE_DEVICE_ADDRESS,
+					saddr, 2, data, 1);
+}
+
+static int afe_read_byte(struct cx231xx *dev, u16 saddr, u8 *data)
+{
+	int status;
+	u32 temp = 0;
+
+	status = cx231xx_read_i2c_data(dev, AFE_DEVICE_ADDRESS,
+					saddr, 2, &temp, 1);
+	*data = (u8) temp;
+	return status;
+}
+
+int cx231xx_afe_init_super_block(struct cx231xx *dev, u32 ref_count)
+{
+	int status = 0;
+	u8 temp = 0;
+	u8 afe_power_status = 0;
+	int i = 0;
+
+	/* super block initialize */
+	temp = (u8) (ref_count & 0xff);
+	status = afe_write_byte(dev, SUP_BLK_TUNE2, temp);
+	if (status < 0)
+		return status;
+
+	status = afe_read_byte(dev, SUP_BLK_TUNE2, &afe_power_status);
+	if (status < 0)
+		return status;
+
+	temp = (u8) ((ref_count & 0x300) >> 8);
+	temp |= 0x40;
+	status = afe_write_byte(dev, SUP_BLK_TUNE1, temp);
+	if (status < 0)
+		return status;
+
+	status = afe_write_byte(dev, SUP_BLK_PLL2, 0x0f);
+	if (status < 0)
+		return status;
+
+	/* enable pll     */
+	while (afe_power_status != 0x18) {
+		status = afe_write_byte(dev, SUP_BLK_PWRDN, 0x18);
+		if (status < 0) {
+			dev_dbg(dev->dev,
+				"%s: Init Super Block failed in send cmd\n",
+				__func__);
+			break;
+		}
+
+		status = afe_read_byte(dev, SUP_BLK_PWRDN, &afe_power_status);
+		afe_power_status &= 0xff;
+		if (status < 0) {
+			dev_dbg(dev->dev,
+				"%s: Init Super Block failed in receive cmd\n",
+				__func__);
+			break;
+		}
+		i++;
+		if (i == 10) {
+			dev_dbg(dev->dev,
+				"%s: Init Super Block force break in loop !!!!\n",
+				__func__);
+			status = -1;
+			break;
+		}
+	}
+
+	if (status < 0)
+		return status;
+
+	/* start tuning filter */
+	status = afe_write_byte(dev, SUP_BLK_TUNE3, 0x40);
+	if (status < 0)
+		return status;
+
+	msleep(5);
+
+	/* exit tuning */
+	status = afe_write_byte(dev, SUP_BLK_TUNE3, 0x00);
+
+	return status;
+}
+
+int cx231xx_afe_init_channels(struct cx231xx *dev)
+{
+	int status = 0;
+
+	/* power up all 3 channels, clear pd_buffer */
+	status = afe_write_byte(dev, ADC_PWRDN_CLAMP_CH1, 0x00);
+	status = afe_write_byte(dev, ADC_PWRDN_CLAMP_CH2, 0x00);
+	status = afe_write_byte(dev, ADC_PWRDN_CLAMP_CH3, 0x00);
+
+	/* Enable quantizer calibration */
+	status = afe_write_byte(dev, ADC_COM_QUANT, 0x02);
+
+	/* channel initialize, force modulator (fb) reset */
+	status = afe_write_byte(dev, ADC_FB_FRCRST_CH1, 0x17);
+	status = afe_write_byte(dev, ADC_FB_FRCRST_CH2, 0x17);
+	status = afe_write_byte(dev, ADC_FB_FRCRST_CH3, 0x17);
+
+	/* start quantilizer calibration  */
+	status = afe_write_byte(dev, ADC_CAL_ATEST_CH1, 0x10);
+	status = afe_write_byte(dev, ADC_CAL_ATEST_CH2, 0x10);
+	status = afe_write_byte(dev, ADC_CAL_ATEST_CH3, 0x10);
+	msleep(5);
+
+	/* exit modulator (fb) reset */
+	status = afe_write_byte(dev, ADC_FB_FRCRST_CH1, 0x07);
+	status = afe_write_byte(dev, ADC_FB_FRCRST_CH2, 0x07);
+	status = afe_write_byte(dev, ADC_FB_FRCRST_CH3, 0x07);
+
+	/* enable the pre_clamp in each channel for single-ended input */
+	status = afe_write_byte(dev, ADC_NTF_PRECLMP_EN_CH1, 0xf0);
+	status = afe_write_byte(dev, ADC_NTF_PRECLMP_EN_CH2, 0xf0);
+	status = afe_write_byte(dev, ADC_NTF_PRECLMP_EN_CH3, 0xf0);
+
+	/* use diode instead of resistor, so set term_en to 0, res_en to 0  */
+	status = cx231xx_reg_mask_write(dev, AFE_DEVICE_ADDRESS, 8,
+				   ADC_QGAIN_RES_TRM_CH1, 3, 7, 0x00);
+	status = cx231xx_reg_mask_write(dev, AFE_DEVICE_ADDRESS, 8,
+				   ADC_QGAIN_RES_TRM_CH2, 3, 7, 0x00);
+	status = cx231xx_reg_mask_write(dev, AFE_DEVICE_ADDRESS, 8,
+				   ADC_QGAIN_RES_TRM_CH3, 3, 7, 0x00);
+
+	/* dynamic element matching off */
+	status = afe_write_byte(dev, ADC_DCSERVO_DEM_CH1, 0x03);
+	status = afe_write_byte(dev, ADC_DCSERVO_DEM_CH2, 0x03);
+	status = afe_write_byte(dev, ADC_DCSERVO_DEM_CH3, 0x03);
+
+	return status;
+}
+
+int cx231xx_afe_setup_AFE_for_baseband(struct cx231xx *dev)
+{
+	u8 c_value = 0;
+	int status = 0;
+
+	status = afe_read_byte(dev, ADC_PWRDN_CLAMP_CH2, &c_value);
+	c_value &= (~(0x50));
+	status = afe_write_byte(dev, ADC_PWRDN_CLAMP_CH2, c_value);
+
+	return status;
+}
+
+/*
+	The Analog Front End in Cx231xx has 3 channels. These
+	channels are used to share between different inputs
+	like tuner, s-video and composite inputs.
+
+	channel 1 ----- pin 1  to pin4(in reg is 1-4)
+	channel 2 ----- pin 5  to pin8(in reg is 5-8)
+	channel 3 ----- pin 9 to pin 12(in reg is 9-11)
+*/
+int cx231xx_afe_set_input_mux(struct cx231xx *dev, u32 input_mux)
+{
+	u8 ch1_setting = (u8) input_mux;
+	u8 ch2_setting = (u8) (input_mux >> 8);
+	u8 ch3_setting = (u8) (input_mux >> 16);
+	int status = 0;
+	u8 value = 0;
+
+	if (ch1_setting != 0) {
+		status = afe_read_byte(dev, ADC_INPUT_CH1, &value);
+		value &= ~INPUT_SEL_MASK;
+		value |= (ch1_setting - 1) << 4;
+		value &= 0xff;
+		status = afe_write_byte(dev, ADC_INPUT_CH1, value);
+	}
+
+	if (ch2_setting != 0) {
+		status = afe_read_byte(dev, ADC_INPUT_CH2, &value);
+		value &= ~INPUT_SEL_MASK;
+		value |= (ch2_setting - 1) << 4;
+		value &= 0xff;
+		status = afe_write_byte(dev, ADC_INPUT_CH2, value);
+	}
+
+	/* For ch3_setting, the value to put in the register is
+	   7 less than the input number */
+	if (ch3_setting != 0) {
+		status = afe_read_byte(dev, ADC_INPUT_CH3, &value);
+		value &= ~INPUT_SEL_MASK;
+		value |= (ch3_setting - 1) << 4;
+		value &= 0xff;
+		status = afe_write_byte(dev, ADC_INPUT_CH3, value);
+	}
+
+	return status;
+}
+
+int cx231xx_afe_set_mode(struct cx231xx *dev, enum AFE_MODE mode)
+{
+	int status = 0;
+
+	/*
+	* FIXME: We need to implement the AFE code for LOW IF and for HI IF.
+	* Currently, only baseband works.
+	*/
+
+	switch (mode) {
+	case AFE_MODE_LOW_IF:
+		cx231xx_Setup_AFE_for_LowIF(dev);
+		break;
+	case AFE_MODE_BASEBAND:
+		status = cx231xx_afe_setup_AFE_for_baseband(dev);
+		break;
+	case AFE_MODE_EU_HI_IF:
+		/* SetupAFEforEuHiIF(); */
+		break;
+	case AFE_MODE_US_HI_IF:
+		/* SetupAFEforUsHiIF(); */
+		break;
+	case AFE_MODE_JAPAN_HI_IF:
+		/* SetupAFEforJapanHiIF(); */
+		break;
+	}
+
+	if ((mode != dev->afe_mode) &&
+		(dev->video_input == CX231XX_VMUX_TELEVISION))
+		status = cx231xx_afe_adjust_ref_count(dev,
+						     CX231XX_VMUX_TELEVISION);
+
+	dev->afe_mode = mode;
+
+	return status;
+}
+
+int cx231xx_afe_update_power_control(struct cx231xx *dev,
+					enum AV_MODE avmode)
+{
+	u8 afe_power_status = 0;
+	int status = 0;
+
+	switch (dev->model) {
+	case CX231XX_BOARD_CNXT_CARRAERA:
+	case CX231XX_BOARD_CNXT_RDE_250:
+	case CX231XX_BOARD_CNXT_SHELBY:
+	case CX231XX_BOARD_CNXT_RDU_250:
+	case CX231XX_BOARD_CNXT_RDE_253S:
+	case CX231XX_BOARD_CNXT_RDU_253S:
+	case CX231XX_BOARD_CNXT_VIDEO_GRABBER:
+	case CX231XX_BOARD_HAUPPAUGE_EXETER:
+	case CX231XX_BOARD_HAUPPAUGE_930C_HD_1113xx:
+	case CX231XX_BOARD_HAUPPAUGE_USBLIVE2:
+	case CX231XX_BOARD_PV_PLAYTV_USB_HYBRID:
+	case CX231XX_BOARD_HAUPPAUGE_USB2_FM_PAL:
+	case CX231XX_BOARD_HAUPPAUGE_USB2_FM_NTSC:
+	case CX231XX_BOARD_OTG102:
+		if (avmode == POLARIS_AVMODE_ANALOGT_TV) {
+			while (afe_power_status != (FLD_PWRDN_TUNING_BIAS |
+						FLD_PWRDN_ENABLE_PLL)) {
+				status = afe_write_byte(dev, SUP_BLK_PWRDN,
+							FLD_PWRDN_TUNING_BIAS |
+							FLD_PWRDN_ENABLE_PLL);
+				status |= afe_read_byte(dev, SUP_BLK_PWRDN,
+							&afe_power_status);
+				if (status < 0)
+					break;
+			}
+
+			status = afe_write_byte(dev, ADC_PWRDN_CLAMP_CH1,
+							0x00);
+			status |= afe_write_byte(dev, ADC_PWRDN_CLAMP_CH2,
+							0x00);
+			status |= afe_write_byte(dev, ADC_PWRDN_CLAMP_CH3,
+							0x00);
+		} else if (avmode == POLARIS_AVMODE_DIGITAL) {
+			status = afe_write_byte(dev, ADC_PWRDN_CLAMP_CH1,
+							0x70);
+			status |= afe_write_byte(dev, ADC_PWRDN_CLAMP_CH2,
+							0x70);
+			status |= afe_write_byte(dev, ADC_PWRDN_CLAMP_CH3,
+							0x70);
+
+			status |= afe_read_byte(dev, SUP_BLK_PWRDN,
+						  &afe_power_status);
+			afe_power_status |= FLD_PWRDN_PD_BANDGAP |
+						FLD_PWRDN_PD_BIAS |
+						FLD_PWRDN_PD_TUNECK;
+			status |= afe_write_byte(dev, SUP_BLK_PWRDN,
+						   afe_power_status);
+		} else if (avmode == POLARIS_AVMODE_ENXTERNAL_AV) {
+			while (afe_power_status != (FLD_PWRDN_TUNING_BIAS |
+						FLD_PWRDN_ENABLE_PLL)) {
+				status = afe_write_byte(dev, SUP_BLK_PWRDN,
+							FLD_PWRDN_TUNING_BIAS |
+							FLD_PWRDN_ENABLE_PLL);
+				status |= afe_read_byte(dev, SUP_BLK_PWRDN,
+							&afe_power_status);
+				if (status < 0)
+					break;
+			}
+
+			status |= afe_write_byte(dev, ADC_PWRDN_CLAMP_CH1,
+						0x00);
+			status |= afe_write_byte(dev, ADC_PWRDN_CLAMP_CH2,
+						0x00);
+			status |= afe_write_byte(dev, ADC_PWRDN_CLAMP_CH3,
+						0x00);
+		} else {
+			dev_dbg(dev->dev, "Invalid AV mode input\n");
+			status = -1;
+		}
+		break;
+	default:
+		if (avmode == POLARIS_AVMODE_ANALOGT_TV) {
+			while (afe_power_status != (FLD_PWRDN_TUNING_BIAS |
+						FLD_PWRDN_ENABLE_PLL)) {
+				status = afe_write_byte(dev, SUP_BLK_PWRDN,
+							FLD_PWRDN_TUNING_BIAS |
+							FLD_PWRDN_ENABLE_PLL);
+				status |= afe_read_byte(dev, SUP_BLK_PWRDN,
+							&afe_power_status);
+				if (status < 0)
+					break;
+			}
+
+			status |= afe_write_byte(dev, ADC_PWRDN_CLAMP_CH1,
+							0x40);
+			status |= afe_write_byte(dev, ADC_PWRDN_CLAMP_CH2,
+							0x40);
+			status |= afe_write_byte(dev, ADC_PWRDN_CLAMP_CH3,
+							0x00);
+		} else if (avmode == POLARIS_AVMODE_DIGITAL) {
+			status = afe_write_byte(dev, ADC_PWRDN_CLAMP_CH1,
+							0x70);
+			status |= afe_write_byte(dev, ADC_PWRDN_CLAMP_CH2,
+							0x70);
+			status |= afe_write_byte(dev, ADC_PWRDN_CLAMP_CH3,
+							0x70);
+
+			status |= afe_read_byte(dev, SUP_BLK_PWRDN,
+						       &afe_power_status);
+			afe_power_status |= FLD_PWRDN_PD_BANDGAP |
+						FLD_PWRDN_PD_BIAS |
+						FLD_PWRDN_PD_TUNECK;
+			status |= afe_write_byte(dev, SUP_BLK_PWRDN,
+							afe_power_status);
+		} else if (avmode == POLARIS_AVMODE_ENXTERNAL_AV) {
+			while (afe_power_status != (FLD_PWRDN_TUNING_BIAS |
+						FLD_PWRDN_ENABLE_PLL)) {
+				status = afe_write_byte(dev, SUP_BLK_PWRDN,
+							FLD_PWRDN_TUNING_BIAS |
+							FLD_PWRDN_ENABLE_PLL);
+				status |= afe_read_byte(dev, SUP_BLK_PWRDN,
+							&afe_power_status);
+				if (status < 0)
+					break;
+			}
+
+			status |= afe_write_byte(dev, ADC_PWRDN_CLAMP_CH1,
+							0x00);
+			status |= afe_write_byte(dev, ADC_PWRDN_CLAMP_CH2,
+							0x00);
+			status |= afe_write_byte(dev, ADC_PWRDN_CLAMP_CH3,
+							0x40);
+		} else {
+			dev_dbg(dev->dev, "Invalid AV mode input\n");
+			status = -1;
+		}
+	}			/* switch  */
+
+	return status;
+}
+
+int cx231xx_afe_adjust_ref_count(struct cx231xx *dev, u32 video_input)
+{
+	u8 input_mode = 0;
+	u8 ntf_mode = 0;
+	int status = 0;
+
+	dev->video_input = video_input;
+
+	if (video_input == CX231XX_VMUX_TELEVISION) {
+		status = afe_read_byte(dev, ADC_INPUT_CH3, &input_mode);
+		status = afe_read_byte(dev, ADC_NTF_PRECLMP_EN_CH3,
+					&ntf_mode);
+	} else {
+		status = afe_read_byte(dev, ADC_INPUT_CH1, &input_mode);
+		status = afe_read_byte(dev, ADC_NTF_PRECLMP_EN_CH1,
+					&ntf_mode);
+	}
+
+	input_mode = (ntf_mode & 0x3) | ((input_mode & 0x6) << 1);
+
+	switch (input_mode) {
+	case SINGLE_ENDED:
+		dev->afe_ref_count = 0x23C;
+		break;
+	case LOW_IF:
+		dev->afe_ref_count = 0x24C;
+		break;
+	case EU_IF:
+		dev->afe_ref_count = 0x258;
+		break;
+	case US_IF:
+		dev->afe_ref_count = 0x260;
+		break;
+	default:
+		break;
+	}
+
+	status = cx231xx_afe_init_super_block(dev, dev->afe_ref_count);
+
+	return status;
+}
+
+/******************************************************************************
+ *     V I D E O / A U D I O    D E C O D E R    C O N T R O L   functions    *
+ ******************************************************************************/
+static int vid_blk_write_byte(struct cx231xx *dev, u16 saddr, u8 data)
+{
+	return cx231xx_write_i2c_data(dev, VID_BLK_I2C_ADDRESS,
+					saddr, 2, data, 1);
+}
+
+static int vid_blk_read_byte(struct cx231xx *dev, u16 saddr, u8 *data)
+{
+	int status;
+	u32 temp = 0;
+
+	status = cx231xx_read_i2c_data(dev, VID_BLK_I2C_ADDRESS,
+					saddr, 2, &temp, 1);
+	*data = (u8) temp;
+	return status;
+}
+
+static int vid_blk_write_word(struct cx231xx *dev, u16 saddr, u32 data)
+{
+	return cx231xx_write_i2c_data(dev, VID_BLK_I2C_ADDRESS,
+					saddr, 2, data, 4);
+}
+
+static int vid_blk_read_word(struct cx231xx *dev, u16 saddr, u32 *data)
+{
+	return cx231xx_read_i2c_data(dev, VID_BLK_I2C_ADDRESS,
+					saddr, 2, data, 4);
+}
+int cx231xx_check_fw(struct cx231xx *dev)
+{
+	u8 temp = 0;
+	int status = 0;
+	status = vid_blk_read_byte(dev, DL_CTL_ADDRESS_LOW, &temp);
+	if (status < 0)
+		return status;
+	else
+		return temp;
+
+}
+
+int cx231xx_set_video_input_mux(struct cx231xx *dev, u8 input)
+{
+	int status = 0;
+
+	switch (INPUT(input)->type) {
+	case CX231XX_VMUX_COMPOSITE1:
+	case CX231XX_VMUX_SVIDEO:
+		if ((dev->current_pcb_config.type == USB_BUS_POWER) &&
+		    (dev->power_mode != POLARIS_AVMODE_ENXTERNAL_AV)) {
+			/* External AV */
+			status = cx231xx_set_power_mode(dev,
+					POLARIS_AVMODE_ENXTERNAL_AV);
+			if (status < 0) {
+				dev_err(dev->dev,
+					"%s: Failed to set Power - errCode [%d]!\n",
+					__func__, status);
+				return status;
+			}
+		}
+		status = cx231xx_set_decoder_video_input(dev,
+							 INPUT(input)->type,
+							 INPUT(input)->vmux);
+		break;
+	case CX231XX_VMUX_TELEVISION:
+	case CX231XX_VMUX_CABLE:
+		if ((dev->current_pcb_config.type == USB_BUS_POWER) &&
+		    (dev->power_mode != POLARIS_AVMODE_ANALOGT_TV)) {
+			/* Tuner */
+			status = cx231xx_set_power_mode(dev,
+						POLARIS_AVMODE_ANALOGT_TV);
+			if (status < 0) {
+				dev_err(dev->dev,
+					"%s: Failed to set Power - errCode [%d]!\n",
+					__func__, status);
+				return status;
+			}
+		}
+		if (dev->tuner_type == TUNER_NXP_TDA18271)
+			status = cx231xx_set_decoder_video_input(dev,
+							CX231XX_VMUX_TELEVISION,
+							INPUT(input)->vmux);
+		else
+			status = cx231xx_set_decoder_video_input(dev,
+							CX231XX_VMUX_COMPOSITE1,
+							INPUT(input)->vmux);
+
+		break;
+	default:
+		dev_err(dev->dev, "%s: Unknown Input %d !\n",
+			__func__, INPUT(input)->type);
+		break;
+	}
+
+	/* save the selection */
+	dev->video_input = input;
+
+	return status;
+}
+
+int cx231xx_set_decoder_video_input(struct cx231xx *dev,
+				u8 pin_type, u8 input)
+{
+	int status = 0;
+	u32 value = 0;
+
+	if (pin_type != dev->video_input) {
+		status = cx231xx_afe_adjust_ref_count(dev, pin_type);
+		if (status < 0) {
+			dev_err(dev->dev,
+				"%s: adjust_ref_count :Failed to set AFE input mux - errCode [%d]!\n",
+				__func__, status);
+			return status;
+		}
+	}
+
+	/* call afe block to set video inputs */
+	status = cx231xx_afe_set_input_mux(dev, input);
+	if (status < 0) {
+		dev_err(dev->dev,
+			"%s: set_input_mux :Failed to set AFE input mux - errCode [%d]!\n",
+			__func__, status);
+		return status;
+	}
+
+	switch (pin_type) {
+	case CX231XX_VMUX_COMPOSITE1:
+		status = vid_blk_read_word(dev, AFE_CTRL, &value);
+		value |= (0 << 13) | (1 << 4);
+		value &= ~(1 << 5);
+
+		/* set [24:23] [22:15] to 0  */
+		value &= (~(0x1ff8000));
+		/* set FUNC_MODE[24:23] = 2 IF_MOD[22:15] = 0  */
+		value |= 0x1000000;
+		status = vid_blk_write_word(dev, AFE_CTRL, value);
+
+		status = vid_blk_read_word(dev, OUT_CTRL1, &value);
+		value |= (1 << 7);
+		status = vid_blk_write_word(dev, OUT_CTRL1, value);
+
+		/* Set output mode */
+		status = cx231xx_read_modify_write_i2c_dword(dev,
+							VID_BLK_I2C_ADDRESS,
+							OUT_CTRL1,
+							FLD_OUT_MODE,
+							dev->board.output_mode);
+
+		/* Tell DIF object to go to baseband mode  */
+		status = cx231xx_dif_set_standard(dev, DIF_USE_BASEBAND);
+		if (status < 0) {
+			dev_err(dev->dev,
+				"%s: cx231xx_dif set to By pass mode- errCode [%d]!\n",
+				__func__, status);
+			return status;
+		}
+
+		/* Read the DFE_CTRL1 register */
+		status = vid_blk_read_word(dev, DFE_CTRL1, &value);
+
+		/* enable the VBI_GATE_EN */
+		value |= FLD_VBI_GATE_EN;
+
+		/* Enable the auto-VGA enable */
+		value |= FLD_VGA_AUTO_EN;
+
+		/* Write it back */
+		status = vid_blk_write_word(dev, DFE_CTRL1, value);
+
+		/* Disable auto config of registers */
+		status = cx231xx_read_modify_write_i2c_dword(dev,
+					VID_BLK_I2C_ADDRESS,
+					MODE_CTRL, FLD_ACFG_DIS,
+					cx231xx_set_field(FLD_ACFG_DIS, 1));
+
+		/* Set CVBS input mode */
+		status = cx231xx_read_modify_write_i2c_dword(dev,
+			VID_BLK_I2C_ADDRESS,
+			MODE_CTRL, FLD_INPUT_MODE,
+			cx231xx_set_field(FLD_INPUT_MODE, INPUT_MODE_CVBS_0));
+		break;
+	case CX231XX_VMUX_SVIDEO:
+		/* Disable the use of  DIF */
+
+		status = vid_blk_read_word(dev, AFE_CTRL, &value);
+
+		/* set [24:23] [22:15] to 0 */
+		value &= (~(0x1ff8000));
+		/* set FUNC_MODE[24:23] = 2
+		IF_MOD[22:15] = 0 DCR_BYP_CH2[4:4] = 1; */
+		value |= 0x1000010;
+		status = vid_blk_write_word(dev, AFE_CTRL, value);
+
+		/* Tell DIF object to go to baseband mode */
+		status = cx231xx_dif_set_standard(dev, DIF_USE_BASEBAND);
+		if (status < 0) {
+			dev_err(dev->dev,
+				"%s: cx231xx_dif set to By pass mode- errCode [%d]!\n",
+				__func__, status);
+			return status;
+		}
+
+		/* Read the DFE_CTRL1 register */
+		status = vid_blk_read_word(dev, DFE_CTRL1, &value);
+
+		/* enable the VBI_GATE_EN */
+		value |= FLD_VBI_GATE_EN;
+
+		/* Enable the auto-VGA enable */
+		value |= FLD_VGA_AUTO_EN;
+
+		/* Write it back */
+		status = vid_blk_write_word(dev, DFE_CTRL1, value);
+
+		/* Disable auto config of registers  */
+		status =  cx231xx_read_modify_write_i2c_dword(dev,
+					VID_BLK_I2C_ADDRESS,
+					MODE_CTRL, FLD_ACFG_DIS,
+					cx231xx_set_field(FLD_ACFG_DIS, 1));
+
+		/* Set YC input mode */
+		status = cx231xx_read_modify_write_i2c_dword(dev,
+			VID_BLK_I2C_ADDRESS,
+			MODE_CTRL,
+			FLD_INPUT_MODE,
+			cx231xx_set_field(FLD_INPUT_MODE, INPUT_MODE_YC_1));
+
+		/* Chroma to ADC2 */
+		status = vid_blk_read_word(dev, AFE_CTRL, &value);
+		value |= FLD_CHROMA_IN_SEL;	/* set the chroma in select */
+
+		/* Clear VGA_SEL_CH2 and VGA_SEL_CH3 (bits 7 and 8)
+		   This sets them to use video
+		   rather than audio.  Only one of the two will be in use. */
+		value &= ~(FLD_VGA_SEL_CH2 | FLD_VGA_SEL_CH3);
+
+		status = vid_blk_write_word(dev, AFE_CTRL, value);
+
+		status = cx231xx_afe_set_mode(dev, AFE_MODE_BASEBAND);
+		break;
+	case CX231XX_VMUX_TELEVISION:
+	case CX231XX_VMUX_CABLE:
+	default:
+		/* TODO: Test if this is also needed for xc2028/xc3028 */
+		if (dev->board.tuner_type == TUNER_XC5000) {
+			/* Disable the use of  DIF   */
+
+			status = vid_blk_read_word(dev, AFE_CTRL, &value);
+			value |= (0 << 13) | (1 << 4);
+			value &= ~(1 << 5);
+
+			/* set [24:23] [22:15] to 0 */
+			value &= (~(0x1FF8000));
+			/* set FUNC_MODE[24:23] = 2 IF_MOD[22:15] = 0 */
+			value |= 0x1000000;
+			status = vid_blk_write_word(dev, AFE_CTRL, value);
+
+			status = vid_blk_read_word(dev, OUT_CTRL1, &value);
+			value |= (1 << 7);
+			status = vid_blk_write_word(dev, OUT_CTRL1, value);
+
+			/* Set output mode */
+			status = cx231xx_read_modify_write_i2c_dword(dev,
+							VID_BLK_I2C_ADDRESS,
+							OUT_CTRL1, FLD_OUT_MODE,
+							dev->board.output_mode);
+
+			/* Tell DIF object to go to baseband mode */
+			status = cx231xx_dif_set_standard(dev,
+							  DIF_USE_BASEBAND);
+			if (status < 0) {
+				dev_err(dev->dev,
+					"%s: cx231xx_dif set to By pass mode- errCode [%d]!\n",
+				       __func__, status);
+				return status;
+			}
+
+			/* Read the DFE_CTRL1 register */
+			status = vid_blk_read_word(dev, DFE_CTRL1, &value);
+
+			/* enable the VBI_GATE_EN */
+			value |= FLD_VBI_GATE_EN;
+
+			/* Enable the auto-VGA enable */
+			value |= FLD_VGA_AUTO_EN;
+
+			/* Write it back */
+			status = vid_blk_write_word(dev, DFE_CTRL1, value);
+
+			/* Disable auto config of registers */
+			status = cx231xx_read_modify_write_i2c_dword(dev,
+					VID_BLK_I2C_ADDRESS,
+					MODE_CTRL, FLD_ACFG_DIS,
+					cx231xx_set_field(FLD_ACFG_DIS, 1));
+
+			/* Set CVBS input mode */
+			status = cx231xx_read_modify_write_i2c_dword(dev,
+				VID_BLK_I2C_ADDRESS,
+				MODE_CTRL, FLD_INPUT_MODE,
+				cx231xx_set_field(FLD_INPUT_MODE,
+						INPUT_MODE_CVBS_0));
+		} else {
+			/* Enable the DIF for the tuner */
+
+			/* Reinitialize the DIF */
+			status = cx231xx_dif_set_standard(dev, dev->norm);
+			if (status < 0) {
+				dev_err(dev->dev,
+					"%s: cx231xx_dif set to By pass mode- errCode [%d]!\n",
+					__func__, status);
+				return status;
+			}
+
+			/* Make sure bypass is cleared */
+			status = vid_blk_read_word(dev, DIF_MISC_CTRL, &value);
+
+			/* Clear the bypass bit */
+			value &= ~FLD_DIF_DIF_BYPASS;
+
+			/* Enable the use of the DIF block */
+			status = vid_blk_write_word(dev, DIF_MISC_CTRL, value);
+
+			/* Read the DFE_CTRL1 register */
+			status = vid_blk_read_word(dev, DFE_CTRL1, &value);
+
+			/* Disable the VBI_GATE_EN */
+			value &= ~FLD_VBI_GATE_EN;
+
+			/* Enable the auto-VGA enable, AGC, and
+			   set the skip count to 2 */
+			value |= FLD_VGA_AUTO_EN | FLD_AGC_AUTO_EN | 0x00200000;
+
+			/* Write it back */
+			status = vid_blk_write_word(dev, DFE_CTRL1, value);
+
+			/* Wait until AGC locks up */
+			msleep(1);
+
+			/* Disable the auto-VGA enable AGC */
+			value &= ~(FLD_VGA_AUTO_EN);
+
+			/* Write it back */
+			status = vid_blk_write_word(dev, DFE_CTRL1, value);
+
+			/* Enable Polaris B0 AGC output */
+			status = vid_blk_read_word(dev, PIN_CTRL, &value);
+			value |= (FLD_OEF_AGC_RF) |
+				 (FLD_OEF_AGC_IFVGA) |
+				 (FLD_OEF_AGC_IF);
+			status = vid_blk_write_word(dev, PIN_CTRL, value);
+
+			/* Set output mode */
+			status = cx231xx_read_modify_write_i2c_dword(dev,
+						VID_BLK_I2C_ADDRESS,
+						OUT_CTRL1, FLD_OUT_MODE,
+						dev->board.output_mode);
+
+			/* Disable auto config of registers */
+			status = cx231xx_read_modify_write_i2c_dword(dev,
+					VID_BLK_I2C_ADDRESS,
+					MODE_CTRL, FLD_ACFG_DIS,
+					cx231xx_set_field(FLD_ACFG_DIS, 1));
+
+			/* Set CVBS input mode */
+			status = cx231xx_read_modify_write_i2c_dword(dev,
+				VID_BLK_I2C_ADDRESS,
+				MODE_CTRL, FLD_INPUT_MODE,
+				cx231xx_set_field(FLD_INPUT_MODE,
+						INPUT_MODE_CVBS_0));
+
+			/* Set some bits in AFE_CTRL so that channel 2 or 3
+			 * is ready to receive audio */
+			/* Clear clamp for channels 2 and 3      (bit 16-17) */
+			/* Clear droop comp                      (bit 19-20) */
+			/* Set VGA_SEL (for audio control)       (bit 7-8) */
+			status = vid_blk_read_word(dev, AFE_CTRL, &value);
+
+			/*Set Func mode:01-DIF 10-baseband 11-YUV*/
+			value &= (~(FLD_FUNC_MODE));
+			value |= 0x800000;
+
+			value |= FLD_VGA_SEL_CH3 | FLD_VGA_SEL_CH2;
+
+			status = vid_blk_write_word(dev, AFE_CTRL, value);
+
+			if (dev->tuner_type == TUNER_NXP_TDA18271) {
+				status = vid_blk_read_word(dev, PIN_CTRL,
+				 &value);
+				status = vid_blk_write_word(dev, PIN_CTRL,
+				 (value & 0xFFFFFFEF));
+			}
+
+			break;
+
+		}
+		break;
+	}
+
+	/* Set raw VBI mode */
+	status = cx231xx_read_modify_write_i2c_dword(dev,
+				VID_BLK_I2C_ADDRESS,
+				OUT_CTRL1, FLD_VBIHACTRAW_EN,
+				cx231xx_set_field(FLD_VBIHACTRAW_EN, 1));
+
+	status = vid_blk_read_word(dev, OUT_CTRL1, &value);
+	if (value & 0x02) {
+		value |= (1 << 19);
+		status = vid_blk_write_word(dev, OUT_CTRL1, value);
+	}
+
+	return status;
+}
+
+void cx231xx_enable656(struct cx231xx *dev)
+{
+	u8 temp = 0;
+	/*enable TS1 data[0:7] as output to export 656*/
+
+	vid_blk_write_byte(dev, TS1_PIN_CTL0, 0xFF);
+
+	/*enable TS1 clock as output to export 656*/
+
+	vid_blk_read_byte(dev, TS1_PIN_CTL1, &temp);
+	temp = temp|0x04;
+
+	vid_blk_write_byte(dev, TS1_PIN_CTL1, temp);
+}
+EXPORT_SYMBOL_GPL(cx231xx_enable656);
+
+void cx231xx_disable656(struct cx231xx *dev)
+{
+	u8 temp = 0;
+
+	vid_blk_write_byte(dev, TS1_PIN_CTL0, 0x00);
+
+	vid_blk_read_byte(dev, TS1_PIN_CTL1, &temp);
+	temp = temp&0xFB;
+
+	vid_blk_write_byte(dev, TS1_PIN_CTL1, temp);
+}
+EXPORT_SYMBOL_GPL(cx231xx_disable656);
+
+/*
+ * Handle any video-mode specific overrides that are different
+ * on a per video standards basis after touching the MODE_CTRL
+ * register which resets many values for autodetect
+ */
+int cx231xx_do_mode_ctrl_overrides(struct cx231xx *dev)
+{
+	int status = 0;
+
+	dev_dbg(dev->dev, "%s: 0x%x\n",
+		__func__, (unsigned int)dev->norm);
+
+	/* Change the DFE_CTRL3 bp_percent to fix flagging */
+	status = vid_blk_write_word(dev, DFE_CTRL3, 0xCD3F0280);
+
+	if (dev->norm & (V4L2_STD_NTSC | V4L2_STD_PAL_M)) {
+		dev_dbg(dev->dev, "%s: NTSC\n", __func__);
+
+		/* Move the close caption lines out of active video,
+		   adjust the active video start point */
+		status = cx231xx_read_modify_write_i2c_dword(dev,
+							VID_BLK_I2C_ADDRESS,
+							VERT_TIM_CTRL,
+							FLD_VBLANK_CNT, 0x18);
+		status = cx231xx_read_modify_write_i2c_dword(dev,
+							VID_BLK_I2C_ADDRESS,
+							VERT_TIM_CTRL,
+							FLD_VACTIVE_CNT,
+							0x1E7000);
+		status = cx231xx_read_modify_write_i2c_dword(dev,
+							VID_BLK_I2C_ADDRESS,
+							VERT_TIM_CTRL,
+							FLD_V656BLANK_CNT,
+							0x1C000000);
+
+		status = cx231xx_read_modify_write_i2c_dword(dev,
+							VID_BLK_I2C_ADDRESS,
+							HORIZ_TIM_CTRL,
+							FLD_HBLANK_CNT,
+							cx231xx_set_field
+							(FLD_HBLANK_CNT, 0x79));
+
+	} else if (dev->norm & V4L2_STD_SECAM) {
+		dev_dbg(dev->dev, "%s: SECAM\n", __func__);
+		status =  cx231xx_read_modify_write_i2c_dword(dev,
+							VID_BLK_I2C_ADDRESS,
+							VERT_TIM_CTRL,
+							FLD_VBLANK_CNT, 0x20);
+		status = cx231xx_read_modify_write_i2c_dword(dev,
+							VID_BLK_I2C_ADDRESS,
+							VERT_TIM_CTRL,
+							FLD_VACTIVE_CNT,
+							cx231xx_set_field
+							(FLD_VACTIVE_CNT,
+							 0x244));
+		status = cx231xx_read_modify_write_i2c_dword(dev,
+							VID_BLK_I2C_ADDRESS,
+							VERT_TIM_CTRL,
+							FLD_V656BLANK_CNT,
+							cx231xx_set_field
+							(FLD_V656BLANK_CNT,
+							0x24));
+		/* Adjust the active video horizontal start point */
+		status = cx231xx_read_modify_write_i2c_dword(dev,
+							VID_BLK_I2C_ADDRESS,
+							HORIZ_TIM_CTRL,
+							FLD_HBLANK_CNT,
+							cx231xx_set_field
+							(FLD_HBLANK_CNT, 0x85));
+	} else {
+		dev_dbg(dev->dev, "%s: PAL\n", __func__);
+		status = cx231xx_read_modify_write_i2c_dword(dev,
+							VID_BLK_I2C_ADDRESS,
+							VERT_TIM_CTRL,
+							FLD_VBLANK_CNT, 0x20);
+		status = cx231xx_read_modify_write_i2c_dword(dev,
+							VID_BLK_I2C_ADDRESS,
+							VERT_TIM_CTRL,
+							FLD_VACTIVE_CNT,
+							cx231xx_set_field
+							(FLD_VACTIVE_CNT,
+							 0x244));
+		status = cx231xx_read_modify_write_i2c_dword(dev,
+							VID_BLK_I2C_ADDRESS,
+							VERT_TIM_CTRL,
+							FLD_V656BLANK_CNT,
+							cx231xx_set_field
+							(FLD_V656BLANK_CNT,
+							0x24));
+		/* Adjust the active video horizontal start point */
+		status = cx231xx_read_modify_write_i2c_dword(dev,
+							VID_BLK_I2C_ADDRESS,
+							HORIZ_TIM_CTRL,
+							FLD_HBLANK_CNT,
+							cx231xx_set_field
+							(FLD_HBLANK_CNT, 0x85));
+
+	}
+
+	return status;
+}
+
+int cx231xx_unmute_audio(struct cx231xx *dev)
+{
+	return vid_blk_write_byte(dev, PATH1_VOL_CTL, 0x24);
+}
+EXPORT_SYMBOL_GPL(cx231xx_unmute_audio);
+
+static int stopAudioFirmware(struct cx231xx *dev)
+{
+	return vid_blk_write_byte(dev, DL_CTL_CONTROL, 0x03);
+}
+
+static int restartAudioFirmware(struct cx231xx *dev)
+{
+	return vid_blk_write_byte(dev, DL_CTL_CONTROL, 0x13);
+}
+
+int cx231xx_set_audio_input(struct cx231xx *dev, u8 input)
+{
+	int status = 0;
+	enum AUDIO_INPUT ainput = AUDIO_INPUT_LINE;
+
+	switch (INPUT(input)->amux) {
+	case CX231XX_AMUX_VIDEO:
+		ainput = AUDIO_INPUT_TUNER_TV;
+		break;
+	case CX231XX_AMUX_LINE_IN:
+		status = cx231xx_i2s_blk_set_audio_input(dev, input);
+		ainput = AUDIO_INPUT_LINE;
+		break;
+	default:
+		break;
+	}
+
+	status = cx231xx_set_audio_decoder_input(dev, ainput);
+
+	return status;
+}
+
+int cx231xx_set_audio_decoder_input(struct cx231xx *dev,
+				    enum AUDIO_INPUT audio_input)
+{
+	u32 dwval;
+	int status;
+	u8 gen_ctrl;
+	u32 value = 0;
+
+	/* Put it in soft reset   */
+	status = vid_blk_read_byte(dev, GENERAL_CTL, &gen_ctrl);
+	gen_ctrl |= 1;
+	status = vid_blk_write_byte(dev, GENERAL_CTL, gen_ctrl);
+
+	switch (audio_input) {
+	case AUDIO_INPUT_LINE:
+		/* setup AUD_IO control from Merlin paralle output */
+		value = cx231xx_set_field(FLD_AUD_CHAN1_SRC,
+					  AUD_CHAN_SRC_PARALLEL);
+		status = vid_blk_write_word(dev, AUD_IO_CTRL, value);
+
+		/* setup input to Merlin, SRC2 connect to AC97
+		   bypass upsample-by-2, slave mode, sony mode, left justify
+		   adr 091c, dat 01000000 */
+		status = vid_blk_read_word(dev, AC97_CTL, &dwval);
+
+		status = vid_blk_write_word(dev, AC97_CTL,
+					   (dwval | FLD_AC97_UP2X_BYPASS));
+
+		/* select the parallel1 and SRC3 */
+		status = vid_blk_write_word(dev, BAND_OUT_SEL,
+				cx231xx_set_field(FLD_SRC3_IN_SEL, 0x0) |
+				cx231xx_set_field(FLD_SRC3_CLK_SEL, 0x0) |
+				cx231xx_set_field(FLD_PARALLEL1_SRC_SEL, 0x0));
+
+		/* unmute all, AC97 in, independence mode
+		   adr 08d0, data 0x00063073 */
+		status = vid_blk_write_word(dev, DL_CTL, 0x3000001);
+		status = vid_blk_write_word(dev, PATH1_CTL1, 0x00063073);
+
+		/* set AVC maximum threshold, adr 08d4, dat ffff0024 */
+		status = vid_blk_read_word(dev, PATH1_VOL_CTL, &dwval);
+		status = vid_blk_write_word(dev, PATH1_VOL_CTL,
+					   (dwval | FLD_PATH1_AVC_THRESHOLD));
+
+		/* set SC maximum threshold, adr 08ec, dat ffffb3a3 */
+		status = vid_blk_read_word(dev, PATH1_SC_CTL, &dwval);
+		status = vid_blk_write_word(dev, PATH1_SC_CTL,
+					   (dwval | FLD_PATH1_SC_THRESHOLD));
+		break;
+
+	case AUDIO_INPUT_TUNER_TV:
+	default:
+		status = stopAudioFirmware(dev);
+		/* Setup SRC sources and clocks */
+		status = vid_blk_write_word(dev, BAND_OUT_SEL,
+			cx231xx_set_field(FLD_SRC6_IN_SEL, 0x00)         |
+			cx231xx_set_field(FLD_SRC6_CLK_SEL, 0x01)        |
+			cx231xx_set_field(FLD_SRC5_IN_SEL, 0x00)         |
+			cx231xx_set_field(FLD_SRC5_CLK_SEL, 0x02)        |
+			cx231xx_set_field(FLD_SRC4_IN_SEL, 0x02)         |
+			cx231xx_set_field(FLD_SRC4_CLK_SEL, 0x03)        |
+			cx231xx_set_field(FLD_SRC3_IN_SEL, 0x00)         |
+			cx231xx_set_field(FLD_SRC3_CLK_SEL, 0x00)        |
+			cx231xx_set_field(FLD_BASEBAND_BYPASS_CTL, 0x00) |
+			cx231xx_set_field(FLD_AC97_SRC_SEL, 0x03)        |
+			cx231xx_set_field(FLD_I2S_SRC_SEL, 0x00)         |
+			cx231xx_set_field(FLD_PARALLEL2_SRC_SEL, 0x02)   |
+			cx231xx_set_field(FLD_PARALLEL1_SRC_SEL, 0x01));
+
+		/* Setup the AUD_IO control */
+		status = vid_blk_write_word(dev, AUD_IO_CTRL,
+			cx231xx_set_field(FLD_I2S_PORT_DIR, 0x00)  |
+			cx231xx_set_field(FLD_I2S_OUT_SRC, 0x00)   |
+			cx231xx_set_field(FLD_AUD_CHAN3_SRC, 0x00) |
+			cx231xx_set_field(FLD_AUD_CHAN2_SRC, 0x00) |
+			cx231xx_set_field(FLD_AUD_CHAN1_SRC, 0x03));
+
+		status = vid_blk_write_word(dev, PATH1_CTL1, 0x1F063870);
+
+		/* setAudioStandard(_audio_standard); */
+		status = vid_blk_write_word(dev, PATH1_CTL1, 0x00063870);
+
+		status = restartAudioFirmware(dev);
+
+		switch (dev->board.tuner_type) {
+		case TUNER_XC5000:
+			/* SIF passthrough at 28.6363 MHz sample rate */
+			status = cx231xx_read_modify_write_i2c_dword(dev,
+					VID_BLK_I2C_ADDRESS,
+					CHIP_CTRL,
+					FLD_SIF_EN,
+					cx231xx_set_field(FLD_SIF_EN, 1));
+			break;
+		case TUNER_NXP_TDA18271:
+			/* Normal mode: SIF passthrough at 14.32 MHz */
+			status = cx231xx_read_modify_write_i2c_dword(dev,
+					VID_BLK_I2C_ADDRESS,
+					CHIP_CTRL,
+					FLD_SIF_EN,
+					cx231xx_set_field(FLD_SIF_EN, 0));
+			break;
+		default:
+			/* This is just a casual suggestion to people adding
+			   new boards in case they use a tuner type we don't
+			   currently know about */
+			dev_info(dev->dev,
+				 "Unknown tuner type configuring SIF");
+			break;
+		}
+		break;
+
+	case AUDIO_INPUT_TUNER_FM:
+		/*  use SIF for FM radio
+		   setupFM();
+		   setAudioStandard(_audio_standard);
+		 */
+		break;
+
+	case AUDIO_INPUT_MUTE:
+		status = vid_blk_write_word(dev, PATH1_CTL1, 0x1F011012);
+		break;
+	}
+
+	/* Take it out of soft reset */
+	status = vid_blk_read_byte(dev, GENERAL_CTL, &gen_ctrl);
+	gen_ctrl &= ~1;
+	status = vid_blk_write_byte(dev, GENERAL_CTL, gen_ctrl);
+
+	return status;
+}
+
+/******************************************************************************
+ *                    C H I P Specific  C O N T R O L   functions             *
+ ******************************************************************************/
+int cx231xx_init_ctrl_pin_status(struct cx231xx *dev)
+{
+	u32 value;
+	int status = 0;
+
+	status = vid_blk_read_word(dev, PIN_CTRL, &value);
+	value |= (~dev->board.ctl_pin_status_mask);
+	status = vid_blk_write_word(dev, PIN_CTRL, value);
+
+	return status;
+}
+
+int cx231xx_set_agc_analog_digital_mux_select(struct cx231xx *dev,
+					      u8 analog_or_digital)
+{
+	int status = 0;
+
+	/* first set the direction to output */
+	status = cx231xx_set_gpio_direction(dev,
+					    dev->board.
+					    agc_analog_digital_select_gpio, 1);
+
+	/* 0 - demod ; 1 - Analog mode */
+	status = cx231xx_set_gpio_value(dev,
+				   dev->board.agc_analog_digital_select_gpio,
+				   analog_or_digital);
+
+	if (status < 0)
+		return status;
+
+	return 0;
+}
+
+int cx231xx_enable_i2c_port_3(struct cx231xx *dev, bool is_port_3)
+{
+	u8 value[4] = { 0, 0, 0, 0 };
+	int status = 0;
+	bool current_is_port_3;
+
+	/*
+	 * Should this code check dev->port_3_switch_enabled first
+	 * to skip unnecessary reading of the register?
+	 * If yes, the flag dev->port_3_switch_enabled must be initialized
+	 * correctly.
+	 */
+
+	status = cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER,
+				       PWR_CTL_EN, value, 4);
+	if (status < 0)
+		return status;
+
+	current_is_port_3 = value[0] & I2C_DEMOD_EN ? true : false;
+
+	/* Just return, if already using the right port */
+	if (current_is_port_3 == is_port_3)
+		return 0;
+
+	if (is_port_3)
+		value[0] |= I2C_DEMOD_EN;
+	else
+		value[0] &= ~I2C_DEMOD_EN;
+
+	status = cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER,
+					PWR_CTL_EN, value, 4);
+
+	/* remember status of the switch for usage in is_tuner */
+	if (status >= 0)
+		dev->port_3_switch_enabled = is_port_3;
+
+	return status;
+
+}
+EXPORT_SYMBOL_GPL(cx231xx_enable_i2c_port_3);
+
+void update_HH_register_after_set_DIF(struct cx231xx *dev)
+{
+/*
+	u8 status = 0;
+	u32 value = 0;
+
+	vid_blk_write_word(dev, PIN_CTRL, 0xA0FFF82F);
+	vid_blk_write_word(dev, DIF_MISC_CTRL, 0x0A203F11);
+	vid_blk_write_word(dev, DIF_SRC_PHASE_INC, 0x1BEFBF06);
+
+	status = vid_blk_read_word(dev, AFE_CTRL_C2HH_SRC_CTRL, &value);
+	vid_blk_write_word(dev, AFE_CTRL_C2HH_SRC_CTRL, 0x4485D390);
+	status = vid_blk_read_word(dev, AFE_CTRL_C2HH_SRC_CTRL,  &value);
+*/
+}
+
+void cx231xx_dump_HH_reg(struct cx231xx *dev)
+{
+	u32 value = 0;
+	u16  i = 0;
+
+	value = 0x45005390;
+	vid_blk_write_word(dev, 0x104, value);
+
+	for (i = 0x100; i < 0x140; i++) {
+		vid_blk_read_word(dev, i, &value);
+		dev_dbg(dev->dev, "reg0x%x=0x%x\n", i, value);
+		i = i+3;
+	}
+
+	for (i = 0x300; i < 0x400; i++) {
+		vid_blk_read_word(dev, i, &value);
+		dev_dbg(dev->dev, "reg0x%x=0x%x\n", i, value);
+		i = i+3;
+	}
+
+	for (i = 0x400; i < 0x440; i++) {
+		vid_blk_read_word(dev, i,  &value);
+		dev_dbg(dev->dev, "reg0x%x=0x%x\n", i, value);
+		i = i+3;
+	}
+
+	vid_blk_read_word(dev, AFE_CTRL_C2HH_SRC_CTRL, &value);
+	dev_dbg(dev->dev, "AFE_CTRL_C2HH_SRC_CTRL=0x%x\n", value);
+	vid_blk_write_word(dev, AFE_CTRL_C2HH_SRC_CTRL, 0x4485D390);
+	vid_blk_read_word(dev, AFE_CTRL_C2HH_SRC_CTRL, &value);
+	dev_dbg(dev->dev, "AFE_CTRL_C2HH_SRC_CTRL=0x%x\n", value);
+}
+
+#if 0
+static void cx231xx_dump_SC_reg(struct cx231xx *dev)
+{
+	u8 value[4] = { 0, 0, 0, 0 };
+	dev_dbg(dev->dev, "%s!\n", __func__);
+
+	cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, BOARD_CFG_STAT,
+				 value, 4);
+	dev_dbg(dev->dev,
+		"reg0x%x=0x%x 0x%x 0x%x 0x%x\n", BOARD_CFG_STAT, value[0],
+		value[1], value[2], value[3]);
+	cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, TS_MODE_REG,
+				 value, 4);
+	dev_dbg(dev->dev,
+		"reg0x%x=0x%x 0x%x 0x%x 0x%x\n", TS_MODE_REG, value[0],
+		 value[1], value[2], value[3]);
+	cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, TS1_CFG_REG,
+				 value, 4);
+	dev_dbg(dev->dev,
+		"reg0x%x=0x%x 0x%x 0x%x 0x%x\n", TS1_CFG_REG, value[0],
+		 value[1], value[2], value[3]);
+	cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, TS1_LENGTH_REG,
+				 value, 4);
+	dev_dbg(dev->dev,
+		"reg0x%x=0x%x 0x%x 0x%x 0x%x\n", TS1_LENGTH_REG, value[0],
+		value[1], value[2], value[3]);
+
+	cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, TS2_CFG_REG,
+				 value, 4);
+	dev_dbg(dev->dev,
+		"reg0x%x=0x%x 0x%x 0x%x 0x%x\n", TS2_CFG_REG, value[0],
+		value[1], value[2], value[3]);
+	cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, TS2_LENGTH_REG,
+				 value, 4);
+	dev_dbg(dev->dev,
+		"reg0x%x=0x%x 0x%x 0x%x 0x%x\n", TS2_LENGTH_REG, value[0],
+		value[1], value[2], value[3]);
+	cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, EP_MODE_SET,
+				 value, 4);
+	dev_dbg(dev->dev,
+		"reg0x%x=0x%x 0x%x 0x%x 0x%x\n", EP_MODE_SET, value[0],
+		 value[1], value[2], value[3]);
+	cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, CIR_PWR_PTN1,
+				 value, 4);
+	dev_dbg(dev->dev,
+		"reg0x%x=0x%x 0x%x 0x%x 0x%x\n", CIR_PWR_PTN1, value[0],
+		value[1], value[2], value[3]);
+
+	cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, CIR_PWR_PTN2,
+				 value, 4);
+	dev_dbg(dev->dev,
+		"reg0x%x=0x%x 0x%x 0x%x 0x%x\n", CIR_PWR_PTN2, value[0],
+		value[1], value[2], value[3]);
+	cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, CIR_PWR_PTN3,
+				 value, 4);
+	dev_dbg(dev->dev,
+		"reg0x%x=0x%x 0x%x 0x%x 0x%x\n", CIR_PWR_PTN3, value[0],
+		value[1], value[2], value[3]);
+	cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, CIR_PWR_MASK0,
+				 value, 4);
+	dev_dbg(dev->dev,
+		"reg0x%x=0x%x 0x%x 0x%x 0x%x\n", CIR_PWR_MASK0, value[0],
+		value[1], value[2], value[3]);
+	cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, CIR_PWR_MASK1,
+				 value, 4);
+	dev_dbg(dev->dev,
+		"reg0x%x=0x%x 0x%x 0x%x 0x%x\n", CIR_PWR_MASK1, value[0],
+		value[1], value[2], value[3]);
+
+	cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, CIR_PWR_MASK2,
+				 value, 4);
+	dev_dbg(dev->dev,
+		"reg0x%x=0x%x 0x%x 0x%x 0x%x\n", CIR_PWR_MASK2, value[0],
+		value[1], value[2], value[3]);
+	cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, CIR_GAIN,
+				 value, 4);
+	dev_dbg(dev->dev,
+		"reg0x%x=0x%x 0x%x 0x%x 0x%x\n", CIR_GAIN, value[0],
+		value[1], value[2], value[3]);
+	cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, CIR_CAR_REG,
+				 value, 4);
+	dev_dbg(dev->dev,
+		"reg0x%x=0x%x 0x%x 0x%x 0x%x\n", CIR_CAR_REG, value[0],
+		value[1], value[2], value[3]);
+	cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, CIR_OT_CFG1,
+				 value, 4);
+	dev_dbg(dev->dev,
+		"reg0x%x=0x%x 0x%x 0x%x 0x%x\n", CIR_OT_CFG1, value[0],
+		value[1], value[2], value[3]);
+
+	cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, CIR_OT_CFG2,
+				 value, 4);
+	dev_dbg(dev->dev,
+		"reg0x%x=0x%x 0x%x 0x%x 0x%x\n", CIR_OT_CFG2, value[0],
+		value[1], value[2], value[3]);
+	cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, PWR_CTL_EN,
+				 value, 4);
+	dev_dbg(dev->dev,
+		"reg0x%x=0x%x 0x%x 0x%x 0x%x\n", PWR_CTL_EN, value[0],
+		value[1], value[2], value[3]);
+}
+#endif
+
+void cx231xx_Setup_AFE_for_LowIF(struct cx231xx *dev)
+
+{
+	u8 value = 0;
+
+	afe_read_byte(dev, ADC_STATUS2_CH3, &value);
+	value = (value & 0xFE)|0x01;
+	afe_write_byte(dev, ADC_STATUS2_CH3, value);
+
+	afe_read_byte(dev, ADC_STATUS2_CH3, &value);
+	value = (value & 0xFE)|0x00;
+	afe_write_byte(dev, ADC_STATUS2_CH3, value);
+
+
+/*
+	config colibri to lo-if mode
+
+	FIXME: ntf_mode = 2'b00 by default. But set 0x1 would reduce
+		the diff IF input by half,
+
+		for low-if agc defect
+*/
+
+	afe_read_byte(dev, ADC_NTF_PRECLMP_EN_CH3, &value);
+	value = (value & 0xFC)|0x00;
+	afe_write_byte(dev, ADC_NTF_PRECLMP_EN_CH3, value);
+
+	afe_read_byte(dev, ADC_INPUT_CH3, &value);
+	value = (value & 0xF9)|0x02;
+	afe_write_byte(dev, ADC_INPUT_CH3, value);
+
+	afe_read_byte(dev, ADC_FB_FRCRST_CH3, &value);
+	value = (value & 0xFB)|0x04;
+	afe_write_byte(dev, ADC_FB_FRCRST_CH3, value);
+
+	afe_read_byte(dev, ADC_DCSERVO_DEM_CH3, &value);
+	value = (value & 0xFC)|0x03;
+	afe_write_byte(dev, ADC_DCSERVO_DEM_CH3, value);
+
+	afe_read_byte(dev, ADC_CTRL_DAC1_CH3, &value);
+	value = (value & 0xFB)|0x04;
+	afe_write_byte(dev, ADC_CTRL_DAC1_CH3, value);
+
+	afe_read_byte(dev, ADC_CTRL_DAC23_CH3, &value);
+	value = (value & 0xF8)|0x06;
+	afe_write_byte(dev, ADC_CTRL_DAC23_CH3, value);
+
+	afe_read_byte(dev, ADC_CTRL_DAC23_CH3, &value);
+	value = (value & 0x8F)|0x40;
+	afe_write_byte(dev, ADC_CTRL_DAC23_CH3, value);
+
+	afe_read_byte(dev, ADC_PWRDN_CLAMP_CH3, &value);
+	value = (value & 0xDF)|0x20;
+	afe_write_byte(dev, ADC_PWRDN_CLAMP_CH3, value);
+}
+
+void cx231xx_set_Colibri_For_LowIF(struct cx231xx *dev, u32 if_freq,
+		 u8 spectral_invert, u32 mode)
+{
+	u32 colibri_carrier_offset = 0;
+	u32 func_mode = 0x01; /* Device has a DIF if this function is called */
+	u32 standard = 0;
+	u8 value[4] = { 0, 0, 0, 0 };
+
+	dev_dbg(dev->dev, "Enter cx231xx_set_Colibri_For_LowIF()\n");
+	value[0] = (u8) 0x6F;
+	value[1] = (u8) 0x6F;
+	value[2] = (u8) 0x6F;
+	value[3] = (u8) 0x6F;
+	cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER,
+					PWR_CTL_EN, value, 4);
+
+	/*Set colibri for low IF*/
+	cx231xx_afe_set_mode(dev, AFE_MODE_LOW_IF);
+
+	/* Set C2HH for low IF operation.*/
+	standard = dev->norm;
+	cx231xx_dif_configure_C2HH_for_low_IF(dev, dev->active_mode,
+						       func_mode, standard);
+
+	/* Get colibri offsets.*/
+	colibri_carrier_offset = cx231xx_Get_Colibri_CarrierOffset(mode,
+								   standard);
+
+	dev_dbg(dev->dev, "colibri_carrier_offset=%d, standard=0x%x\n",
+		     colibri_carrier_offset, standard);
+
+	/* Set the band Pass filter for DIF*/
+	cx231xx_set_DIF_bandpass(dev, (if_freq+colibri_carrier_offset),
+				 spectral_invert, mode);
+}
+
+u32 cx231xx_Get_Colibri_CarrierOffset(u32 mode, u32 standerd)
+{
+	u32 colibri_carrier_offset = 0;
+
+	if (mode == TUNER_MODE_FM_RADIO) {
+		colibri_carrier_offset = 1100000;
+	} else if (standerd & (V4L2_STD_MN | V4L2_STD_NTSC_M_JP)) {
+		colibri_carrier_offset = 4832000;  /*4.83MHz	*/
+	} else if (standerd & (V4L2_STD_PAL_B | V4L2_STD_PAL_G)) {
+		colibri_carrier_offset = 2700000;  /*2.70MHz       */
+	} else if (standerd & (V4L2_STD_PAL_D | V4L2_STD_PAL_I
+			| V4L2_STD_SECAM)) {
+		colibri_carrier_offset = 2100000;  /*2.10MHz	*/
+	}
+
+	return colibri_carrier_offset;
+}
+
+void cx231xx_set_DIF_bandpass(struct cx231xx *dev, u32 if_freq,
+		 u8 spectral_invert, u32 mode)
+{
+	unsigned long pll_freq_word;
+	u32 dif_misc_ctrl_value = 0;
+	u64 pll_freq_u64 = 0;
+	u32 i = 0;
+
+	dev_dbg(dev->dev, "if_freq=%d;spectral_invert=0x%x;mode=0x%x\n",
+		if_freq, spectral_invert, mode);
+
+
+	if (mode == TUNER_MODE_FM_RADIO) {
+		pll_freq_word = 0x905A1CAC;
+		vid_blk_write_word(dev, DIF_PLL_FREQ_WORD,  pll_freq_word);
+
+	} else /*KSPROPERTY_TUNER_MODE_TV*/{
+		/* Calculate the PLL frequency word based on the adjusted if_freq*/
+		pll_freq_word = if_freq;
+		pll_freq_u64 = (u64)pll_freq_word << 28L;
+		do_div(pll_freq_u64, 50000000);
+		pll_freq_word = (u32)pll_freq_u64;
+		/*pll_freq_word = 0x3463497;*/
+		vid_blk_write_word(dev, DIF_PLL_FREQ_WORD,  pll_freq_word);
+
+		if (spectral_invert) {
+			if_freq -= 400000;
+			/* Enable Spectral Invert*/
+			vid_blk_read_word(dev, DIF_MISC_CTRL,
+					  &dif_misc_ctrl_value);
+			dif_misc_ctrl_value = dif_misc_ctrl_value | 0x00200000;
+			vid_blk_write_word(dev, DIF_MISC_CTRL,
+					  dif_misc_ctrl_value);
+		} else {
+			if_freq += 400000;
+			/* Disable Spectral Invert*/
+			vid_blk_read_word(dev, DIF_MISC_CTRL,
+					  &dif_misc_ctrl_value);
+			dif_misc_ctrl_value = dif_misc_ctrl_value & 0xFFDFFFFF;
+			vid_blk_write_word(dev, DIF_MISC_CTRL,
+					  dif_misc_ctrl_value);
+		}
+
+		if_freq = (if_freq / 100000) * 100000;
+
+		if (if_freq < 3000000)
+			if_freq = 3000000;
+
+		if (if_freq > 16000000)
+			if_freq = 16000000;
+	}
+
+	dev_dbg(dev->dev, "Enter IF=%zu\n", ARRAY_SIZE(Dif_set_array));
+	for (i = 0; i < ARRAY_SIZE(Dif_set_array); i++) {
+		if (Dif_set_array[i].if_freq == if_freq) {
+			vid_blk_write_word(dev,
+			Dif_set_array[i].register_address, Dif_set_array[i].value);
+		}
+	}
+}
+
+/******************************************************************************
+ *                 D I F - B L O C K    C O N T R O L   functions             *
+ ******************************************************************************/
+int cx231xx_dif_configure_C2HH_for_low_IF(struct cx231xx *dev, u32 mode,
+					  u32 function_mode, u32 standard)
+{
+	int status = 0;
+
+
+	if (mode == V4L2_TUNER_RADIO) {
+		/* C2HH */
+		/* lo if big signal */
+		status = cx231xx_reg_mask_write(dev,
+				VID_BLK_I2C_ADDRESS, 32,
+				AFE_CTRL_C2HH_SRC_CTRL, 30, 31, 0x1);
+		/* FUNC_MODE = DIF */
+		status = cx231xx_reg_mask_write(dev,
+				VID_BLK_I2C_ADDRESS, 32,
+				AFE_CTRL_C2HH_SRC_CTRL, 23, 24, function_mode);
+		/* IF_MODE */
+		status = cx231xx_reg_mask_write(dev,
+				VID_BLK_I2C_ADDRESS, 32,
+				AFE_CTRL_C2HH_SRC_CTRL, 15, 22, 0xFF);
+		/* no inv */
+		status = cx231xx_reg_mask_write(dev,
+				VID_BLK_I2C_ADDRESS, 32,
+				AFE_CTRL_C2HH_SRC_CTRL, 9, 9, 0x1);
+	} else if (standard != DIF_USE_BASEBAND) {
+		if (standard & V4L2_STD_MN) {
+			/* lo if big signal */
+			status = cx231xx_reg_mask_write(dev,
+					VID_BLK_I2C_ADDRESS, 32,
+					AFE_CTRL_C2HH_SRC_CTRL, 30, 31, 0x1);
+			/* FUNC_MODE = DIF */
+			status = cx231xx_reg_mask_write(dev,
+					VID_BLK_I2C_ADDRESS, 32,
+					AFE_CTRL_C2HH_SRC_CTRL, 23, 24,
+					function_mode);
+			/* IF_MODE */
+			status = cx231xx_reg_mask_write(dev,
+					VID_BLK_I2C_ADDRESS, 32,
+					AFE_CTRL_C2HH_SRC_CTRL, 15, 22, 0xb);
+			/* no inv */
+			status = cx231xx_reg_mask_write(dev,
+					VID_BLK_I2C_ADDRESS, 32,
+					AFE_CTRL_C2HH_SRC_CTRL, 9, 9, 0x1);
+			/* 0x124, AUD_CHAN1_SRC = 0x3 */
+			status = cx231xx_reg_mask_write(dev,
+					VID_BLK_I2C_ADDRESS, 32,
+					AUD_IO_CTRL, 0, 31, 0x00000003);
+		} else if ((standard == V4L2_STD_PAL_I) |
+			(standard & V4L2_STD_PAL_D) |
+			(standard & V4L2_STD_SECAM)) {
+			/* C2HH setup */
+			/* lo if big signal */
+			status = cx231xx_reg_mask_write(dev,
+					VID_BLK_I2C_ADDRESS, 32,
+					AFE_CTRL_C2HH_SRC_CTRL, 30, 31, 0x1);
+			/* FUNC_MODE = DIF */
+			status = cx231xx_reg_mask_write(dev,
+					VID_BLK_I2C_ADDRESS, 32,
+					AFE_CTRL_C2HH_SRC_CTRL, 23, 24,
+					function_mode);
+			/* IF_MODE */
+			status = cx231xx_reg_mask_write(dev,
+					VID_BLK_I2C_ADDRESS, 32,
+					AFE_CTRL_C2HH_SRC_CTRL, 15, 22, 0xF);
+			/* no inv */
+			status = cx231xx_reg_mask_write(dev,
+					VID_BLK_I2C_ADDRESS, 32,
+					AFE_CTRL_C2HH_SRC_CTRL, 9, 9, 0x1);
+		} else {
+			/* default PAL BG */
+			/* C2HH setup */
+			/* lo if big signal */
+			status = cx231xx_reg_mask_write(dev,
+					VID_BLK_I2C_ADDRESS, 32,
+					AFE_CTRL_C2HH_SRC_CTRL, 30, 31, 0x1);
+			/* FUNC_MODE = DIF */
+			status = cx231xx_reg_mask_write(dev,
+					VID_BLK_I2C_ADDRESS, 32,
+					AFE_CTRL_C2HH_SRC_CTRL, 23, 24,
+					function_mode);
+			/* IF_MODE */
+			status = cx231xx_reg_mask_write(dev,
+					VID_BLK_I2C_ADDRESS, 32,
+					AFE_CTRL_C2HH_SRC_CTRL, 15, 22, 0xE);
+			/* no inv */
+			status = cx231xx_reg_mask_write(dev,
+					VID_BLK_I2C_ADDRESS, 32,
+					AFE_CTRL_C2HH_SRC_CTRL, 9, 9, 0x1);
+		}
+	}
+
+	return status;
+}
+
+int cx231xx_dif_set_standard(struct cx231xx *dev, u32 standard)
+{
+	int status = 0;
+	u32 dif_misc_ctrl_value = 0;
+	u32 func_mode = 0;
+
+	dev_dbg(dev->dev, "%s: setStandard to %x\n", __func__, standard);
+
+	status = vid_blk_read_word(dev, DIF_MISC_CTRL, &dif_misc_ctrl_value);
+	if (standard != DIF_USE_BASEBAND)
+		dev->norm = standard;
+
+	switch (dev->model) {
+	case CX231XX_BOARD_CNXT_CARRAERA:
+	case CX231XX_BOARD_CNXT_RDE_250:
+	case CX231XX_BOARD_CNXT_SHELBY:
+	case CX231XX_BOARD_CNXT_RDU_250:
+	case CX231XX_BOARD_CNXT_VIDEO_GRABBER:
+	case CX231XX_BOARD_HAUPPAUGE_EXETER:
+	case CX231XX_BOARD_OTG102:
+		func_mode = 0x03;
+		break;
+	case CX231XX_BOARD_CNXT_RDE_253S:
+	case CX231XX_BOARD_CNXT_RDU_253S:
+	case CX231XX_BOARD_HAUPPAUGE_USB2_FM_PAL:
+	case CX231XX_BOARD_HAUPPAUGE_USB2_FM_NTSC:
+		func_mode = 0x01;
+		break;
+	default:
+		func_mode = 0x01;
+	}
+
+	status = cx231xx_dif_configure_C2HH_for_low_IF(dev, dev->active_mode,
+						  func_mode, standard);
+
+	if (standard == DIF_USE_BASEBAND) {	/* base band */
+		/* There is a different SRC_PHASE_INC value
+		   for baseband vs. DIF */
+		status = vid_blk_write_word(dev, DIF_SRC_PHASE_INC, 0xDF7DF83);
+		status = vid_blk_read_word(dev, DIF_MISC_CTRL,
+						&dif_misc_ctrl_value);
+		dif_misc_ctrl_value |= FLD_DIF_DIF_BYPASS;
+		status = vid_blk_write_word(dev, DIF_MISC_CTRL,
+						dif_misc_ctrl_value);
+	} else if (standard & V4L2_STD_PAL_D) {
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_PLL_CTRL, 0, 31, 0x6503bc0c);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_PLL_CTRL1, 0, 31, 0xbd038c85);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_PLL_CTRL2, 0, 31, 0x1db4640a);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_PLL_CTRL3, 0, 31, 0x00008800);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_IF_REF, 0, 31, 0x444C1380);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_CTRL_IF, 0, 31, 0xDA302600);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_CTRL_INT, 0, 31, 0xDA261700);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_CTRL_RF, 0, 31, 0xDA262600);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_IF_INT_CURRENT, 0, 31,
+					   0x26001700);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_RF_CURRENT, 0, 31,
+					   0x00002660);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_VIDEO_AGC_CTRL, 0, 31,
+					   0x72500800);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_VID_AUD_OVERRIDE, 0, 31,
+					   0x27000100);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AV_SEP_CTRL, 0, 31, 0x3F3934EA);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_COMP_FLT_CTRL, 0, 31,
+					   0x00000000);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_SRC_PHASE_INC, 0, 31,
+					   0x1befbf06);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_SRC_GAIN_CONTROL, 0, 31,
+					   0x000035e8);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_RPT_VARIANCE, 0, 31, 0x00000000);
+		/* Save the Spec Inversion value */
+		dif_misc_ctrl_value &= FLD_DIF_SPEC_INV;
+		dif_misc_ctrl_value |= 0x3a023F11;
+	} else if (standard & V4L2_STD_PAL_I) {
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_PLL_CTRL, 0, 31, 0x6503bc0c);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_PLL_CTRL1, 0, 31, 0xbd038c85);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_PLL_CTRL2, 0, 31, 0x1db4640a);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_PLL_CTRL3, 0, 31, 0x00008800);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_IF_REF, 0, 31, 0x444C1380);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_CTRL_IF, 0, 31, 0xDA302600);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_CTRL_INT, 0, 31, 0xDA261700);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_CTRL_RF, 0, 31, 0xDA262600);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_IF_INT_CURRENT, 0, 31,
+					   0x26001700);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_RF_CURRENT, 0, 31,
+					   0x00002660);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_VIDEO_AGC_CTRL, 0, 31,
+					   0x72500800);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_VID_AUD_OVERRIDE, 0, 31,
+					   0x27000100);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AV_SEP_CTRL, 0, 31, 0x5F39A934);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_COMP_FLT_CTRL, 0, 31,
+					   0x00000000);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_SRC_PHASE_INC, 0, 31,
+					   0x1befbf06);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_SRC_GAIN_CONTROL, 0, 31,
+					   0x000035e8);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_RPT_VARIANCE, 0, 31, 0x00000000);
+		/* Save the Spec Inversion value */
+		dif_misc_ctrl_value &= FLD_DIF_SPEC_INV;
+		dif_misc_ctrl_value |= 0x3a033F11;
+	} else if (standard & V4L2_STD_PAL_M) {
+		/* improved Low Frequency Phase Noise */
+		status = vid_blk_write_word(dev, DIF_PLL_CTRL, 0xFF01FF0C);
+		status = vid_blk_write_word(dev, DIF_PLL_CTRL1, 0xbd038c85);
+		status = vid_blk_write_word(dev, DIF_PLL_CTRL2, 0x1db4640a);
+		status = vid_blk_write_word(dev, DIF_PLL_CTRL3, 0x00008800);
+		status = vid_blk_write_word(dev, DIF_AGC_IF_REF, 0x444C1380);
+		status = vid_blk_write_word(dev, DIF_AGC_IF_INT_CURRENT,
+						0x26001700);
+		status = vid_blk_write_word(dev, DIF_AGC_RF_CURRENT,
+						0x00002660);
+		status = vid_blk_write_word(dev, DIF_VIDEO_AGC_CTRL,
+						0x72500800);
+		status = vid_blk_write_word(dev, DIF_VID_AUD_OVERRIDE,
+						0x27000100);
+		status = vid_blk_write_word(dev, DIF_AV_SEP_CTRL, 0x012c405d);
+		status = vid_blk_write_word(dev, DIF_COMP_FLT_CTRL,
+						0x009f50c1);
+		status = vid_blk_write_word(dev, DIF_SRC_PHASE_INC,
+						0x1befbf06);
+		status = vid_blk_write_word(dev, DIF_SRC_GAIN_CONTROL,
+						0x000035e8);
+		status = vid_blk_write_word(dev, DIF_SOFT_RST_CTRL_REVB,
+						0x00000000);
+		/* Save the Spec Inversion value */
+		dif_misc_ctrl_value &= FLD_DIF_SPEC_INV;
+		dif_misc_ctrl_value |= 0x3A0A3F10;
+	} else if (standard & (V4L2_STD_PAL_N | V4L2_STD_PAL_Nc)) {
+		/* improved Low Frequency Phase Noise */
+		status = vid_blk_write_word(dev, DIF_PLL_CTRL, 0xFF01FF0C);
+		status = vid_blk_write_word(dev, DIF_PLL_CTRL1, 0xbd038c85);
+		status = vid_blk_write_word(dev, DIF_PLL_CTRL2, 0x1db4640a);
+		status = vid_blk_write_word(dev, DIF_PLL_CTRL3, 0x00008800);
+		status = vid_blk_write_word(dev, DIF_AGC_IF_REF, 0x444C1380);
+		status = vid_blk_write_word(dev, DIF_AGC_IF_INT_CURRENT,
+						0x26001700);
+		status = vid_blk_write_word(dev, DIF_AGC_RF_CURRENT,
+						0x00002660);
+		status = vid_blk_write_word(dev, DIF_VIDEO_AGC_CTRL,
+						0x72500800);
+		status = vid_blk_write_word(dev, DIF_VID_AUD_OVERRIDE,
+						0x27000100);
+		status = vid_blk_write_word(dev, DIF_AV_SEP_CTRL,
+						0x012c405d);
+		status = vid_blk_write_word(dev, DIF_COMP_FLT_CTRL,
+						0x009f50c1);
+		status = vid_blk_write_word(dev, DIF_SRC_PHASE_INC,
+						0x1befbf06);
+		status = vid_blk_write_word(dev, DIF_SRC_GAIN_CONTROL,
+						0x000035e8);
+		status = vid_blk_write_word(dev, DIF_SOFT_RST_CTRL_REVB,
+						0x00000000);
+		/* Save the Spec Inversion value */
+		dif_misc_ctrl_value &= FLD_DIF_SPEC_INV;
+		dif_misc_ctrl_value = 0x3A093F10;
+	} else if (standard &
+		  (V4L2_STD_SECAM_B | V4L2_STD_SECAM_D | V4L2_STD_SECAM_G |
+		   V4L2_STD_SECAM_K | V4L2_STD_SECAM_K1)) {
+
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_PLL_CTRL, 0, 31, 0x6503bc0c);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_PLL_CTRL1, 0, 31, 0xbd038c85);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_PLL_CTRL2, 0, 31, 0x1db4640a);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_PLL_CTRL3, 0, 31, 0x00008800);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_IF_REF, 0, 31, 0x888C0380);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_CTRL_IF, 0, 31, 0xe0262600);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_CTRL_INT, 0, 31, 0xc2171700);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_CTRL_RF, 0, 31, 0xc2262600);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_IF_INT_CURRENT, 0, 31,
+					   0x26001700);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_RF_CURRENT, 0, 31,
+					   0x00002660);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_VID_AUD_OVERRIDE, 0, 31,
+					   0x27000100);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AV_SEP_CTRL, 0, 31, 0x3F3530ec);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_COMP_FLT_CTRL, 0, 31,
+					   0x00000000);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_SRC_PHASE_INC, 0, 31,
+					   0x1befbf06);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_SRC_GAIN_CONTROL, 0, 31,
+					   0x000035e8);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_RPT_VARIANCE, 0, 31, 0x00000000);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_VIDEO_AGC_CTRL, 0, 31,
+					   0xf4000000);
+
+		/* Save the Spec Inversion value */
+		dif_misc_ctrl_value &= FLD_DIF_SPEC_INV;
+		dif_misc_ctrl_value |= 0x3a023F11;
+	} else if (standard & (V4L2_STD_SECAM_L | V4L2_STD_SECAM_LC)) {
+		/* Is it SECAM_L1? */
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_PLL_CTRL, 0, 31, 0x6503bc0c);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_PLL_CTRL1, 0, 31, 0xbd038c85);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_PLL_CTRL2, 0, 31, 0x1db4640a);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_PLL_CTRL3, 0, 31, 0x00008800);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_IF_REF, 0, 31, 0x888C0380);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_CTRL_IF, 0, 31, 0xe0262600);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_CTRL_INT, 0, 31, 0xc2171700);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_CTRL_RF, 0, 31, 0xc2262600);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_IF_INT_CURRENT, 0, 31,
+					   0x26001700);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_RF_CURRENT, 0, 31,
+					   0x00002660);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_VID_AUD_OVERRIDE, 0, 31,
+					   0x27000100);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AV_SEP_CTRL, 0, 31, 0x3F3530ec);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_COMP_FLT_CTRL, 0, 31,
+					   0x00000000);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_SRC_PHASE_INC, 0, 31,
+					   0x1befbf06);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_SRC_GAIN_CONTROL, 0, 31,
+					   0x000035e8);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_RPT_VARIANCE, 0, 31, 0x00000000);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_VIDEO_AGC_CTRL, 0, 31,
+					   0xf2560000);
+
+		/* Save the Spec Inversion value */
+		dif_misc_ctrl_value &= FLD_DIF_SPEC_INV;
+		dif_misc_ctrl_value |= 0x3a023F11;
+
+	} else if (standard & V4L2_STD_NTSC_M) {
+		/* V4L2_STD_NTSC_M (75 IRE Setup) Or
+		   V4L2_STD_NTSC_M_JP (Japan,  0 IRE Setup) */
+
+		/* For NTSC the centre frequency of video coming out of
+		   sidewinder is around 7.1MHz or 3.6MHz depending on the
+		   spectral inversion. so for a non spectrally inverted channel
+		   the pll freq word is 0x03420c49
+		 */
+
+		status = vid_blk_write_word(dev, DIF_PLL_CTRL, 0x6503BC0C);
+		status = vid_blk_write_word(dev, DIF_PLL_CTRL1, 0xBD038C85);
+		status = vid_blk_write_word(dev, DIF_PLL_CTRL2, 0x1DB4640A);
+		status = vid_blk_write_word(dev, DIF_PLL_CTRL3, 0x00008800);
+		status = vid_blk_write_word(dev, DIF_AGC_IF_REF, 0x444C0380);
+		status = vid_blk_write_word(dev, DIF_AGC_IF_INT_CURRENT,
+						0x26001700);
+		status = vid_blk_write_word(dev, DIF_AGC_RF_CURRENT,
+						0x00002660);
+		status = vid_blk_write_word(dev, DIF_VIDEO_AGC_CTRL,
+						0x04000800);
+		status = vid_blk_write_word(dev, DIF_VID_AUD_OVERRIDE,
+						0x27000100);
+		status = vid_blk_write_word(dev, DIF_AV_SEP_CTRL, 0x01296e1f);
+
+		status = vid_blk_write_word(dev, DIF_COMP_FLT_CTRL,
+						0x009f50c1);
+		status = vid_blk_write_word(dev, DIF_SRC_PHASE_INC,
+						0x1befbf06);
+		status = vid_blk_write_word(dev, DIF_SRC_GAIN_CONTROL,
+						0x000035e8);
+
+		status = vid_blk_write_word(dev, DIF_AGC_CTRL_IF, 0xC2262600);
+		status = vid_blk_write_word(dev, DIF_AGC_CTRL_INT,
+						0xC2262600);
+		status = vid_blk_write_word(dev, DIF_AGC_CTRL_RF, 0xC2262600);
+
+		/* Save the Spec Inversion value */
+		dif_misc_ctrl_value &= FLD_DIF_SPEC_INV;
+		dif_misc_ctrl_value |= 0x3a003F10;
+	} else {
+		/* default PAL BG */
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_PLL_CTRL, 0, 31, 0x6503bc0c);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_PLL_CTRL1, 0, 31, 0xbd038c85);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_PLL_CTRL2, 0, 31, 0x1db4640a);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_PLL_CTRL3, 0, 31, 0x00008800);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_IF_REF, 0, 31, 0x444C1380);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_CTRL_IF, 0, 31, 0xDA302600);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_CTRL_INT, 0, 31, 0xDA261700);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_CTRL_RF, 0, 31, 0xDA262600);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_IF_INT_CURRENT, 0, 31,
+					   0x26001700);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AGC_RF_CURRENT, 0, 31,
+					   0x00002660);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_VIDEO_AGC_CTRL, 0, 31,
+					   0x72500800);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_VID_AUD_OVERRIDE, 0, 31,
+					   0x27000100);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_AV_SEP_CTRL, 0, 31, 0x3F3530EC);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_COMP_FLT_CTRL, 0, 31,
+					   0x00A653A8);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_SRC_PHASE_INC, 0, 31,
+					   0x1befbf06);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_SRC_GAIN_CONTROL, 0, 31,
+					   0x000035e8);
+		status = cx231xx_reg_mask_write(dev, VID_BLK_I2C_ADDRESS, 32,
+					   DIF_RPT_VARIANCE, 0, 31, 0x00000000);
+		/* Save the Spec Inversion value */
+		dif_misc_ctrl_value &= FLD_DIF_SPEC_INV;
+		dif_misc_ctrl_value |= 0x3a013F11;
+	}
+
+	/* The AGC values should be the same for all standards,
+	   AUD_SRC_SEL[19] should always be disabled    */
+	dif_misc_ctrl_value &= ~FLD_DIF_AUD_SRC_SEL;
+
+	/* It is still possible to get Set Standard calls even when we
+	   are in FM mode.
+	   This is done to override the value for FM. */
+	if (dev->active_mode == V4L2_TUNER_RADIO)
+		dif_misc_ctrl_value = 0x7a080000;
+
+	/* Write the calculated value for misc ontrol register      */
+	status = vid_blk_write_word(dev, DIF_MISC_CTRL, dif_misc_ctrl_value);
+
+	return status;
+}
+
+int cx231xx_tuner_pre_channel_change(struct cx231xx *dev)
+{
+	int status = 0;
+	u32 dwval;
+
+	/* Set the RF and IF k_agc values to 3 */
+	status = vid_blk_read_word(dev, DIF_AGC_IF_REF, &dwval);
+	dwval &= ~(FLD_DIF_K_AGC_RF | FLD_DIF_K_AGC_IF);
+	dwval |= 0x33000000;
+
+	status = vid_blk_write_word(dev, DIF_AGC_IF_REF, dwval);
+
+	return status;
+}
+
+int cx231xx_tuner_post_channel_change(struct cx231xx *dev)
+{
+	int status = 0;
+	u32 dwval;
+	dev_dbg(dev->dev, "%s: dev->tuner_type =0%d\n",
+		__func__, dev->tuner_type);
+	/* Set the RF and IF k_agc values to 4 for PAL/NTSC and 8 for
+	 * SECAM L/B/D standards */
+	status = vid_blk_read_word(dev, DIF_AGC_IF_REF, &dwval);
+	dwval &= ~(FLD_DIF_K_AGC_RF | FLD_DIF_K_AGC_IF);
+
+	if (dev->norm & (V4L2_STD_SECAM_L | V4L2_STD_SECAM_B |
+			 V4L2_STD_SECAM_D)) {
+			if (dev->tuner_type == TUNER_NXP_TDA18271) {
+				dwval &= ~FLD_DIF_IF_REF;
+				dwval |= 0x88000300;
+			} else
+				dwval |= 0x88000000;
+		} else {
+			if (dev->tuner_type == TUNER_NXP_TDA18271) {
+				dwval &= ~FLD_DIF_IF_REF;
+				dwval |= 0xCC000300;
+			} else
+				dwval |= 0x44000000;
+		}
+
+	status = vid_blk_write_word(dev, DIF_AGC_IF_REF, dwval);
+
+	return status == sizeof(dwval) ? 0 : -EIO;
+}
+
+/******************************************************************************
+ *		    I 2 S - B L O C K    C O N T R O L   functions            *
+ ******************************************************************************/
+int cx231xx_i2s_blk_initialize(struct cx231xx *dev)
+{
+	int status = 0;
+	u32 value;
+
+	status = cx231xx_read_i2c_data(dev, I2S_BLK_DEVICE_ADDRESS,
+				       CH_PWR_CTRL1, 1, &value, 1);
+	/* enables clock to delta-sigma and decimation filter */
+	value |= 0x80;
+	status = cx231xx_write_i2c_data(dev, I2S_BLK_DEVICE_ADDRESS,
+					CH_PWR_CTRL1, 1, value, 1);
+	/* power up all channel */
+	status = cx231xx_write_i2c_data(dev, I2S_BLK_DEVICE_ADDRESS,
+					CH_PWR_CTRL2, 1, 0x00, 1);
+
+	return status;
+}
+
+int cx231xx_i2s_blk_update_power_control(struct cx231xx *dev,
+					enum AV_MODE avmode)
+{
+	int status = 0;
+	u32 value = 0;
+
+	if (avmode != POLARIS_AVMODE_ENXTERNAL_AV) {
+		status = cx231xx_read_i2c_data(dev, I2S_BLK_DEVICE_ADDRESS,
+					  CH_PWR_CTRL2, 1, &value, 1);
+		value |= 0xfe;
+		status = cx231xx_write_i2c_data(dev, I2S_BLK_DEVICE_ADDRESS,
+						CH_PWR_CTRL2, 1, value, 1);
+	} else {
+		status = cx231xx_write_i2c_data(dev, I2S_BLK_DEVICE_ADDRESS,
+						CH_PWR_CTRL2, 1, 0x00, 1);
+	}
+
+	return status;
+}
+
+/* set i2s_blk for audio input types */
+int cx231xx_i2s_blk_set_audio_input(struct cx231xx *dev, u8 audio_input)
+{
+	int status = 0;
+
+	switch (audio_input) {
+	case CX231XX_AMUX_LINE_IN:
+		status = cx231xx_write_i2c_data(dev, I2S_BLK_DEVICE_ADDRESS,
+						CH_PWR_CTRL2, 1, 0x00, 1);
+		status = cx231xx_write_i2c_data(dev, I2S_BLK_DEVICE_ADDRESS,
+						CH_PWR_CTRL1, 1, 0x80, 1);
+		break;
+	case CX231XX_AMUX_VIDEO:
+	default:
+		break;
+	}
+
+	dev->ctl_ainput = audio_input;
+
+	return status;
+}
+
+/******************************************************************************
+ *                  P O W E R      C O N T R O L   functions                  *
+ ******************************************************************************/
+int cx231xx_set_power_mode(struct cx231xx *dev, enum AV_MODE mode)
+{
+	u8 value[4] = { 0, 0, 0, 0 };
+	u32 tmp = 0;
+	int status = 0;
+
+	if (dev->power_mode != mode)
+		dev->power_mode = mode;
+	else {
+		dev_dbg(dev->dev, "%s: mode = %d, No Change req.\n",
+			 __func__, mode);
+		return 0;
+	}
+
+	status = cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, PWR_CTL_EN, value,
+				       4);
+	if (status < 0)
+		return status;
+
+	tmp = le32_to_cpu(*((__le32 *) value));
+
+	switch (mode) {
+	case POLARIS_AVMODE_ENXTERNAL_AV:
+
+		tmp &= (~PWR_MODE_MASK);
+
+		tmp |= PWR_AV_EN;
+		value[0] = (u8) tmp;
+		value[1] = (u8) (tmp >> 8);
+		value[2] = (u8) (tmp >> 16);
+		value[3] = (u8) (tmp >> 24);
+		status = cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER,
+						PWR_CTL_EN, value, 4);
+		msleep(PWR_SLEEP_INTERVAL);
+
+		tmp |= PWR_ISO_EN;
+		value[0] = (u8) tmp;
+		value[1] = (u8) (tmp >> 8);
+		value[2] = (u8) (tmp >> 16);
+		value[3] = (u8) (tmp >> 24);
+		status =
+		    cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER, PWR_CTL_EN,
+					   value, 4);
+		msleep(PWR_SLEEP_INTERVAL);
+
+		tmp |= POLARIS_AVMODE_ENXTERNAL_AV;
+		value[0] = (u8) tmp;
+		value[1] = (u8) (tmp >> 8);
+		value[2] = (u8) (tmp >> 16);
+		value[3] = (u8) (tmp >> 24);
+		status = cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER,
+						PWR_CTL_EN, value, 4);
+
+		/* reset state of xceive tuner */
+		dev->xc_fw_load_done = 0;
+		break;
+
+	case POLARIS_AVMODE_ANALOGT_TV:
+
+		tmp |= PWR_DEMOD_EN;
+		value[0] = (u8) tmp;
+		value[1] = (u8) (tmp >> 8);
+		value[2] = (u8) (tmp >> 16);
+		value[3] = (u8) (tmp >> 24);
+		status = cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER,
+						PWR_CTL_EN, value, 4);
+		msleep(PWR_SLEEP_INTERVAL);
+
+		if (!(tmp & PWR_TUNER_EN)) {
+			tmp |= (PWR_TUNER_EN);
+			value[0] = (u8) tmp;
+			value[1] = (u8) (tmp >> 8);
+			value[2] = (u8) (tmp >> 16);
+			value[3] = (u8) (tmp >> 24);
+			status = cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER,
+							PWR_CTL_EN, value, 4);
+			msleep(PWR_SLEEP_INTERVAL);
+		}
+
+		if (!(tmp & PWR_AV_EN)) {
+			tmp |= PWR_AV_EN;
+			value[0] = (u8) tmp;
+			value[1] = (u8) (tmp >> 8);
+			value[2] = (u8) (tmp >> 16);
+			value[3] = (u8) (tmp >> 24);
+			status = cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER,
+							PWR_CTL_EN, value, 4);
+			msleep(PWR_SLEEP_INTERVAL);
+		}
+		if (!(tmp & PWR_ISO_EN)) {
+			tmp |= PWR_ISO_EN;
+			value[0] = (u8) tmp;
+			value[1] = (u8) (tmp >> 8);
+			value[2] = (u8) (tmp >> 16);
+			value[3] = (u8) (tmp >> 24);
+			status = cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER,
+							PWR_CTL_EN, value, 4);
+			msleep(PWR_SLEEP_INTERVAL);
+		}
+
+		if (!(tmp & POLARIS_AVMODE_ANALOGT_TV)) {
+			tmp |= POLARIS_AVMODE_ANALOGT_TV;
+			value[0] = (u8) tmp;
+			value[1] = (u8) (tmp >> 8);
+			value[2] = (u8) (tmp >> 16);
+			value[3] = (u8) (tmp >> 24);
+			status = cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER,
+							PWR_CTL_EN, value, 4);
+			msleep(PWR_SLEEP_INTERVAL);
+		}
+
+		if (dev->board.tuner_type != TUNER_ABSENT) {
+			/* reset the Tuner */
+			if (dev->board.tuner_gpio)
+				cx231xx_gpio_set(dev, dev->board.tuner_gpio);
+
+			if (dev->cx231xx_reset_analog_tuner)
+				dev->cx231xx_reset_analog_tuner(dev);
+		}
+
+		break;
+
+	case POLARIS_AVMODE_DIGITAL:
+		if (!(tmp & PWR_TUNER_EN)) {
+			tmp |= (PWR_TUNER_EN);
+			value[0] = (u8) tmp;
+			value[1] = (u8) (tmp >> 8);
+			value[2] = (u8) (tmp >> 16);
+			value[3] = (u8) (tmp >> 24);
+			status = cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER,
+							PWR_CTL_EN, value, 4);
+			msleep(PWR_SLEEP_INTERVAL);
+		}
+		if (!(tmp & PWR_AV_EN)) {
+			tmp |= PWR_AV_EN;
+			value[0] = (u8) tmp;
+			value[1] = (u8) (tmp >> 8);
+			value[2] = (u8) (tmp >> 16);
+			value[3] = (u8) (tmp >> 24);
+			status = cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER,
+							PWR_CTL_EN, value, 4);
+			msleep(PWR_SLEEP_INTERVAL);
+		}
+		if (!(tmp & PWR_ISO_EN)) {
+			tmp |= PWR_ISO_EN;
+			value[0] = (u8) tmp;
+			value[1] = (u8) (tmp >> 8);
+			value[2] = (u8) (tmp >> 16);
+			value[3] = (u8) (tmp >> 24);
+			status = cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER,
+							PWR_CTL_EN, value, 4);
+			msleep(PWR_SLEEP_INTERVAL);
+		}
+
+		tmp &= (~PWR_AV_MODE);
+		tmp |= POLARIS_AVMODE_DIGITAL;
+		value[0] = (u8) tmp;
+		value[1] = (u8) (tmp >> 8);
+		value[2] = (u8) (tmp >> 16);
+		value[3] = (u8) (tmp >> 24);
+		status = cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER,
+						PWR_CTL_EN, value, 4);
+		msleep(PWR_SLEEP_INTERVAL);
+
+		if (!(tmp & PWR_DEMOD_EN)) {
+			tmp |= PWR_DEMOD_EN;
+			value[0] = (u8) tmp;
+			value[1] = (u8) (tmp >> 8);
+			value[2] = (u8) (tmp >> 16);
+			value[3] = (u8) (tmp >> 24);
+			status = cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER,
+							PWR_CTL_EN, value, 4);
+			msleep(PWR_SLEEP_INTERVAL);
+		}
+
+		if (dev->board.tuner_type != TUNER_ABSENT) {
+			/* reset the Tuner */
+			if (dev->board.tuner_gpio)
+				cx231xx_gpio_set(dev, dev->board.tuner_gpio);
+
+			if (dev->cx231xx_reset_analog_tuner)
+				dev->cx231xx_reset_analog_tuner(dev);
+		}
+		break;
+
+	default:
+		break;
+	}
+
+	msleep(PWR_SLEEP_INTERVAL);
+
+	/* For power saving, only enable Pwr_resetout_n
+	   when digital TV is selected. */
+	if (mode == POLARIS_AVMODE_DIGITAL) {
+		tmp |= PWR_RESETOUT_EN;
+		value[0] = (u8) tmp;
+		value[1] = (u8) (tmp >> 8);
+		value[2] = (u8) (tmp >> 16);
+		value[3] = (u8) (tmp >> 24);
+		status = cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER,
+						PWR_CTL_EN, value, 4);
+		msleep(PWR_SLEEP_INTERVAL);
+	}
+
+	/* update power control for afe */
+	status = cx231xx_afe_update_power_control(dev, mode);
+
+	/* update power control for i2s_blk */
+	status = cx231xx_i2s_blk_update_power_control(dev, mode);
+
+	status = cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, PWR_CTL_EN, value,
+				       4);
+
+	return status;
+}
+
+int cx231xx_power_suspend(struct cx231xx *dev)
+{
+	u8 value[4] = { 0, 0, 0, 0 };
+	u32 tmp = 0;
+	int status = 0;
+
+	status = cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, PWR_CTL_EN,
+				       value, 4);
+	if (status > 0)
+		return status;
+
+	tmp = le32_to_cpu(*((__le32 *) value));
+	tmp &= (~PWR_MODE_MASK);
+
+	value[0] = (u8) tmp;
+	value[1] = (u8) (tmp >> 8);
+	value[2] = (u8) (tmp >> 16);
+	value[3] = (u8) (tmp >> 24);
+	status = cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER, PWR_CTL_EN,
+					value, 4);
+
+	return status;
+}
+
+/******************************************************************************
+ *                  S T R E A M    C O N T R O L   functions                  *
+ ******************************************************************************/
+int cx231xx_start_stream(struct cx231xx *dev, u32 ep_mask)
+{
+	u8 value[4] = { 0x0, 0x0, 0x0, 0x0 };
+	u32 tmp = 0;
+	int status = 0;
+
+	dev_dbg(dev->dev, "%s: ep_mask = %x\n", __func__, ep_mask);
+	status = cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, EP_MODE_SET,
+				       value, 4);
+	if (status < 0)
+		return status;
+
+	tmp = le32_to_cpu(*((__le32 *) value));
+	tmp |= ep_mask;
+	value[0] = (u8) tmp;
+	value[1] = (u8) (tmp >> 8);
+	value[2] = (u8) (tmp >> 16);
+	value[3] = (u8) (tmp >> 24);
+
+	status = cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER, EP_MODE_SET,
+					value, 4);
+
+	return status;
+}
+
+int cx231xx_stop_stream(struct cx231xx *dev, u32 ep_mask)
+{
+	u8 value[4] = { 0x0, 0x0, 0x0, 0x0 };
+	u32 tmp = 0;
+	int status = 0;
+
+	dev_dbg(dev->dev, "%s: ep_mask = %x\n", __func__, ep_mask);
+	status =
+	    cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, EP_MODE_SET, value, 4);
+	if (status < 0)
+		return status;
+
+	tmp = le32_to_cpu(*((__le32 *) value));
+	tmp &= (~ep_mask);
+	value[0] = (u8) tmp;
+	value[1] = (u8) (tmp >> 8);
+	value[2] = (u8) (tmp >> 16);
+	value[3] = (u8) (tmp >> 24);
+
+	status = cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER, EP_MODE_SET,
+					value, 4);
+
+	return status;
+}
+
+int cx231xx_initialize_stream_xfer(struct cx231xx *dev, u32 media_type)
+{
+	int status = 0;
+	u32 value = 0;
+	u8 val[4] = { 0, 0, 0, 0 };
+
+	if (dev->udev->speed == USB_SPEED_HIGH) {
+		switch (media_type) {
+		case Audio:
+			dev_dbg(dev->dev,
+				"%s: Audio enter HANC\n", __func__);
+			status =
+			    cx231xx_mode_register(dev, TS_MODE_REG, 0x9300);
+			break;
+
+		case Vbi:
+			dev_dbg(dev->dev,
+				"%s: set vanc registers\n", __func__);
+			status = cx231xx_mode_register(dev, TS_MODE_REG, 0x300);
+			break;
+
+		case Sliced_cc:
+			dev_dbg(dev->dev,
+				"%s: set hanc registers\n", __func__);
+			status =
+			    cx231xx_mode_register(dev, TS_MODE_REG, 0x1300);
+			break;
+
+		case Raw_Video:
+			dev_dbg(dev->dev,
+				"%s: set video registers\n", __func__);
+			status = cx231xx_mode_register(dev, TS_MODE_REG, 0x100);
+			break;
+
+		case TS1_serial_mode:
+			dev_dbg(dev->dev,
+				"%s: set ts1 registers", __func__);
+
+			if (dev->board.has_417) {
+				dev_dbg(dev->dev,
+					"%s: MPEG\n", __func__);
+				value &= 0xFFFFFFFC;
+				value |= 0x3;
+
+				status = cx231xx_mode_register(dev,
+							 TS_MODE_REG, value);
+
+				val[0] = 0x04;
+				val[1] = 0xA3;
+				val[2] = 0x3B;
+				val[3] = 0x00;
+				status = cx231xx_write_ctrl_reg(dev,
+							VRT_SET_REGISTER,
+							TS1_CFG_REG, val, 4);
+
+				val[0] = 0x00;
+				val[1] = 0x08;
+				val[2] = 0x00;
+				val[3] = 0x08;
+				status = cx231xx_write_ctrl_reg(dev,
+							VRT_SET_REGISTER,
+							TS1_LENGTH_REG, val, 4);
+			} else {
+				dev_dbg(dev->dev, "%s: BDA\n", __func__);
+				status = cx231xx_mode_register(dev,
+							 TS_MODE_REG, 0x101);
+				status = cx231xx_mode_register(dev,
+							TS1_CFG_REG, 0x010);
+			}
+			break;
+
+		case TS1_parallel_mode:
+			dev_dbg(dev->dev,
+				"%s: set ts1 parallel mode registers\n",
+				__func__);
+			status = cx231xx_mode_register(dev, TS_MODE_REG, 0x100);
+			status = cx231xx_mode_register(dev, TS1_CFG_REG, 0x400);
+			break;
+		}
+	} else {
+		status = cx231xx_mode_register(dev, TS_MODE_REG, 0x101);
+	}
+
+	return status;
+}
+
+int cx231xx_capture_start(struct cx231xx *dev, int start, u8 media_type)
+{
+	int rc = -1;
+	u32 ep_mask = -1;
+	struct pcb_config *pcb_config;
+
+	/* get EP for media type */
+	pcb_config = (struct pcb_config *)&dev->current_pcb_config;
+
+	if (pcb_config->config_num) {
+		switch (media_type) {
+		case Raw_Video:
+			ep_mask = ENABLE_EP4;	/* ep4  [00:1000] */
+			break;
+		case Audio:
+			ep_mask = ENABLE_EP3;	/* ep3  [00:0100] */
+			break;
+		case Vbi:
+			ep_mask = ENABLE_EP5;	/* ep5 [01:0000] */
+			break;
+		case Sliced_cc:
+			ep_mask = ENABLE_EP6;	/* ep6 [10:0000] */
+			break;
+		case TS1_serial_mode:
+		case TS1_parallel_mode:
+			ep_mask = ENABLE_EP1;	/* ep1 [00:0001] */
+			break;
+		case TS2:
+			ep_mask = ENABLE_EP2;	/* ep2 [00:0010] */
+			break;
+		}
+	}
+
+	if (start) {
+		rc = cx231xx_initialize_stream_xfer(dev, media_type);
+
+		if (rc < 0)
+			return rc;
+
+		/* enable video capture */
+		if (ep_mask > 0)
+			rc = cx231xx_start_stream(dev, ep_mask);
+	} else {
+		/* disable video capture */
+		if (ep_mask > 0)
+			rc = cx231xx_stop_stream(dev, ep_mask);
+	}
+
+	return rc;
+}
+EXPORT_SYMBOL_GPL(cx231xx_capture_start);
+
+/*****************************************************************************
+*                   G P I O   B I T control functions                        *
+******************************************************************************/
+static int cx231xx_set_gpio_bit(struct cx231xx *dev, u32 gpio_bit, u32 gpio_val)
+{
+	int status = 0;
+
+	gpio_val = (__force u32)cpu_to_le32(gpio_val);
+	status = cx231xx_send_gpio_cmd(dev, gpio_bit, (u8 *)&gpio_val, 4, 0, 0);
+
+	return status;
+}
+
+static int cx231xx_get_gpio_bit(struct cx231xx *dev, u32 gpio_bit, u32 *gpio_val)
+{
+	__le32 tmp;
+	int status = 0;
+
+	status = cx231xx_send_gpio_cmd(dev, gpio_bit, (u8 *)&tmp, 4, 0, 1);
+	*gpio_val = le32_to_cpu(tmp);
+
+	return status;
+}
+
+/*
+* cx231xx_set_gpio_direction
+*      Sets the direction of the GPIO pin to input or output
+*
+* Parameters :
+*      pin_number : The GPIO Pin number to program the direction for
+*                   from 0 to 31
+*      pin_value : The Direction of the GPIO Pin under reference.
+*                      0 = Input direction
+*                      1 = Output direction
+*/
+int cx231xx_set_gpio_direction(struct cx231xx *dev,
+			       int pin_number, int pin_value)
+{
+	int status = 0;
+	u32 value = 0;
+
+	/* Check for valid pin_number - if 32 , bail out */
+	if (pin_number >= 32)
+		return -EINVAL;
+
+	/* input */
+	if (pin_value == 0)
+		value = dev->gpio_dir & (~(1 << pin_number));	/* clear */
+	else
+		value = dev->gpio_dir | (1 << pin_number);
+
+	status = cx231xx_set_gpio_bit(dev, value, dev->gpio_val);
+
+	/* cache the value for future */
+	dev->gpio_dir = value;
+
+	return status;
+}
+
+/*
+* cx231xx_set_gpio_value
+*      Sets the value of the GPIO pin to Logic high or low. The Pin under
+*      reference should ALREADY BE SET IN OUTPUT MODE !!!!!!!!!
+*
+* Parameters :
+*      pin_number : The GPIO Pin number to program the direction for
+*      pin_value : The value of the GPIO Pin under reference.
+*                      0 = set it to 0
+*                      1 = set it to 1
+*/
+int cx231xx_set_gpio_value(struct cx231xx *dev, int pin_number, int pin_value)
+{
+	int status = 0;
+	u32 value = 0;
+
+	/* Check for valid pin_number - if 0xFF , bail out */
+	if (pin_number >= 32)
+		return -EINVAL;
+
+	/* first do a sanity check - if the Pin is not output, make it output */
+	if ((dev->gpio_dir & (1 << pin_number)) == 0x00) {
+		/* It was in input mode */
+		value = dev->gpio_dir | (1 << pin_number);
+		dev->gpio_dir = value;
+		status = cx231xx_set_gpio_bit(dev, dev->gpio_dir,
+					      dev->gpio_val);
+		value = 0;
+	}
+
+	if (pin_value == 0)
+		value = dev->gpio_val & (~(1 << pin_number));
+	else
+		value = dev->gpio_val | (1 << pin_number);
+
+	/* store the value */
+	dev->gpio_val = value;
+
+	/* toggle bit0 of GP_IO */
+	status = cx231xx_set_gpio_bit(dev, dev->gpio_dir, dev->gpio_val);
+
+	return status;
+}
+
+/*****************************************************************************
+*                      G P I O I2C related functions                         *
+******************************************************************************/
+int cx231xx_gpio_i2c_start(struct cx231xx *dev)
+{
+	int status = 0;
+
+	/* set SCL to output 1 ; set SDA to output 1 */
+	dev->gpio_dir |= 1 << dev->board.tuner_scl_gpio;
+	dev->gpio_dir |= 1 << dev->board.tuner_sda_gpio;
+	dev->gpio_val |= 1 << dev->board.tuner_scl_gpio;
+	dev->gpio_val |= 1 << dev->board.tuner_sda_gpio;
+
+	status = cx231xx_set_gpio_bit(dev, dev->gpio_dir, dev->gpio_val);
+	if (status < 0)
+		return -EINVAL;
+
+	/* set SCL to output 1; set SDA to output 0 */
+	dev->gpio_val |= 1 << dev->board.tuner_scl_gpio;
+	dev->gpio_val &= ~(1 << dev->board.tuner_sda_gpio);
+
+	status = cx231xx_set_gpio_bit(dev, dev->gpio_dir, dev->gpio_val);
+	if (status < 0)
+		return -EINVAL;
+
+	/* set SCL to output 0; set SDA to output 0      */
+	dev->gpio_val &= ~(1 << dev->board.tuner_scl_gpio);
+	dev->gpio_val &= ~(1 << dev->board.tuner_sda_gpio);
+
+	status = cx231xx_set_gpio_bit(dev, dev->gpio_dir, dev->gpio_val);
+	if (status < 0)
+		return -EINVAL;
+
+	return status;
+}
+
+int cx231xx_gpio_i2c_end(struct cx231xx *dev)
+{
+	int status = 0;
+
+	/* set SCL to output 0; set SDA to output 0      */
+	dev->gpio_dir |= 1 << dev->board.tuner_scl_gpio;
+	dev->gpio_dir |= 1 << dev->board.tuner_sda_gpio;
+
+	dev->gpio_val &= ~(1 << dev->board.tuner_scl_gpio);
+	dev->gpio_val &= ~(1 << dev->board.tuner_sda_gpio);
+
+	status = cx231xx_set_gpio_bit(dev, dev->gpio_dir, dev->gpio_val);
+	if (status < 0)
+		return -EINVAL;
+
+	/* set SCL to output 1; set SDA to output 0      */
+	dev->gpio_val |= 1 << dev->board.tuner_scl_gpio;
+	dev->gpio_val &= ~(1 << dev->board.tuner_sda_gpio);
+
+	status = cx231xx_set_gpio_bit(dev, dev->gpio_dir, dev->gpio_val);
+	if (status < 0)
+		return -EINVAL;
+
+	/* set SCL to input ,release SCL cable control
+	   set SDA to input ,release SDA cable control */
+	dev->gpio_dir &= ~(1 << dev->board.tuner_scl_gpio);
+	dev->gpio_dir &= ~(1 << dev->board.tuner_sda_gpio);
+
+	status =
+	    cx231xx_set_gpio_bit(dev, dev->gpio_dir, dev->gpio_val);
+	if (status < 0)
+		return -EINVAL;
+
+	return status;
+}
+
+int cx231xx_gpio_i2c_write_byte(struct cx231xx *dev, u8 data)
+{
+	int status = 0;
+	u8 i;
+
+	/* set SCL to output ; set SDA to output */
+	dev->gpio_dir |= 1 << dev->board.tuner_scl_gpio;
+	dev->gpio_dir |= 1 << dev->board.tuner_sda_gpio;
+
+	for (i = 0; i < 8; i++) {
+		if (((data << i) & 0x80) == 0) {
+			/* set SCL to output 0; set SDA to output 0     */
+			dev->gpio_val &= ~(1 << dev->board.tuner_scl_gpio);
+			dev->gpio_val &= ~(1 << dev->board.tuner_sda_gpio);
+			status = cx231xx_set_gpio_bit(dev, dev->gpio_dir,
+						      dev->gpio_val);
+
+			/* set SCL to output 1; set SDA to output 0     */
+			dev->gpio_val |= 1 << dev->board.tuner_scl_gpio;
+			status = cx231xx_set_gpio_bit(dev, dev->gpio_dir,
+						      dev->gpio_val);
+
+			/* set SCL to output 0; set SDA to output 0     */
+			dev->gpio_val &= ~(1 << dev->board.tuner_scl_gpio);
+			status = cx231xx_set_gpio_bit(dev, dev->gpio_dir,
+						      dev->gpio_val);
+		} else {
+			/* set SCL to output 0; set SDA to output 1     */
+			dev->gpio_val &= ~(1 << dev->board.tuner_scl_gpio);
+			dev->gpio_val |= 1 << dev->board.tuner_sda_gpio;
+			status = cx231xx_set_gpio_bit(dev, dev->gpio_dir,
+						      dev->gpio_val);
+
+			/* set SCL to output 1; set SDA to output 1     */
+			dev->gpio_val |= 1 << dev->board.tuner_scl_gpio;
+			status = cx231xx_set_gpio_bit(dev, dev->gpio_dir,
+						      dev->gpio_val);
+
+			/* set SCL to output 0; set SDA to output 1     */
+			dev->gpio_val &= ~(1 << dev->board.tuner_scl_gpio);
+			status = cx231xx_set_gpio_bit(dev, dev->gpio_dir,
+						      dev->gpio_val);
+		}
+	}
+	return status;
+}
+
+int cx231xx_gpio_i2c_read_byte(struct cx231xx *dev, u8 *buf)
+{
+	u8 value = 0;
+	int status = 0;
+	u32 gpio_logic_value = 0;
+	u8 i;
+
+	/* read byte */
+	for (i = 0; i < 8; i++) {	/* send write I2c addr */
+
+		/* set SCL to output 0; set SDA to input */
+		dev->gpio_val &= ~(1 << dev->board.tuner_scl_gpio);
+		status = cx231xx_set_gpio_bit(dev, dev->gpio_dir,
+					      dev->gpio_val);
+
+		/* set SCL to output 1; set SDA to input */
+		dev->gpio_val |= 1 << dev->board.tuner_scl_gpio;
+		status = cx231xx_set_gpio_bit(dev, dev->gpio_dir,
+					      dev->gpio_val);
+
+		/* get SDA data bit */
+		gpio_logic_value = dev->gpio_val;
+		status = cx231xx_get_gpio_bit(dev, dev->gpio_dir,
+					      &dev->gpio_val);
+		if ((dev->gpio_val & (1 << dev->board.tuner_sda_gpio)) != 0)
+			value |= (1 << (8 - i - 1));
+
+		dev->gpio_val = gpio_logic_value;
+	}
+
+	/* set SCL to output 0,finish the read latest SCL signal.
+	   !!!set SDA to input, never to modify SDA direction at
+	   the same times */
+	dev->gpio_val &= ~(1 << dev->board.tuner_scl_gpio);
+	status = cx231xx_set_gpio_bit(dev, dev->gpio_dir, dev->gpio_val);
+
+	/* store the value */
+	*buf = value & 0xff;
+
+	return status;
+}
+
+int cx231xx_gpio_i2c_read_ack(struct cx231xx *dev)
+{
+	int status = 0;
+	u32 gpio_logic_value = 0;
+	int nCnt = 10;
+	int nInit = nCnt;
+
+	/* clock stretch; set SCL to input; set SDA to input;
+	   get SCL value till SCL = 1 */
+	dev->gpio_dir &= ~(1 << dev->board.tuner_sda_gpio);
+	dev->gpio_dir &= ~(1 << dev->board.tuner_scl_gpio);
+
+	gpio_logic_value = dev->gpio_val;
+	status = cx231xx_set_gpio_bit(dev, dev->gpio_dir, dev->gpio_val);
+
+	do {
+		msleep(2);
+		status = cx231xx_get_gpio_bit(dev, dev->gpio_dir,
+					      &dev->gpio_val);
+		nCnt--;
+	} while (((dev->gpio_val &
+			  (1 << dev->board.tuner_scl_gpio)) == 0) &&
+			 (nCnt > 0));
+
+	if (nCnt == 0)
+		dev_dbg(dev->dev,
+			"No ACK after %d msec -GPIO I2C failed!",
+			nInit * 10);
+
+	/*
+	 * readAck
+	 * through clock stretch, slave has given a SCL signal,
+	 * so the SDA data can be directly read.
+	 */
+	status = cx231xx_get_gpio_bit(dev, dev->gpio_dir, &dev->gpio_val);
+
+	if ((dev->gpio_val & 1 << dev->board.tuner_sda_gpio) == 0) {
+		dev->gpio_val = gpio_logic_value;
+		dev->gpio_val &= ~(1 << dev->board.tuner_sda_gpio);
+		status = 0;
+	} else {
+		dev->gpio_val = gpio_logic_value;
+		dev->gpio_val |= (1 << dev->board.tuner_sda_gpio);
+	}
+
+	/* read SDA end, set the SCL to output 0, after this operation,
+	   SDA direction can be changed. */
+	dev->gpio_val = gpio_logic_value;
+	dev->gpio_dir |= (1 << dev->board.tuner_scl_gpio);
+	dev->gpio_val &= ~(1 << dev->board.tuner_scl_gpio);
+	status = cx231xx_set_gpio_bit(dev, dev->gpio_dir, dev->gpio_val);
+
+	return status;
+}
+
+int cx231xx_gpio_i2c_write_ack(struct cx231xx *dev)
+{
+	int status = 0;
+
+	/* set SDA to ouput */
+	dev->gpio_dir |= 1 << dev->board.tuner_sda_gpio;
+	status = cx231xx_set_gpio_bit(dev, dev->gpio_dir, dev->gpio_val);
+
+	/* set SCL = 0 (output); set SDA = 0 (output) */
+	dev->gpio_val &= ~(1 << dev->board.tuner_sda_gpio);
+	dev->gpio_val &= ~(1 << dev->board.tuner_scl_gpio);
+	status = cx231xx_set_gpio_bit(dev, dev->gpio_dir, dev->gpio_val);
+
+	/* set SCL = 1 (output); set SDA = 0 (output) */
+	dev->gpio_val |= 1 << dev->board.tuner_scl_gpio;
+	status = cx231xx_set_gpio_bit(dev, dev->gpio_dir, dev->gpio_val);
+
+	/* set SCL = 0 (output); set SDA = 0 (output) */
+	dev->gpio_val &= ~(1 << dev->board.tuner_scl_gpio);
+	status = cx231xx_set_gpio_bit(dev, dev->gpio_dir, dev->gpio_val);
+
+	/* set SDA to input,and then the slave will read data from SDA. */
+	dev->gpio_dir &= ~(1 << dev->board.tuner_sda_gpio);
+	status = cx231xx_set_gpio_bit(dev, dev->gpio_dir, dev->gpio_val);
+
+	return status;
+}
+
+int cx231xx_gpio_i2c_write_nak(struct cx231xx *dev)
+{
+	int status = 0;
+
+	/* set scl to output ; set sda to input */
+	dev->gpio_dir |= 1 << dev->board.tuner_scl_gpio;
+	dev->gpio_dir &= ~(1 << dev->board.tuner_sda_gpio);
+	status = cx231xx_set_gpio_bit(dev, dev->gpio_dir, dev->gpio_val);
+
+	/* set scl to output 0; set sda to input */
+	dev->gpio_val &= ~(1 << dev->board.tuner_scl_gpio);
+	status = cx231xx_set_gpio_bit(dev, dev->gpio_dir, dev->gpio_val);
+
+	/* set scl to output 1; set sda to input */
+	dev->gpio_val |= 1 << dev->board.tuner_scl_gpio;
+	status = cx231xx_set_gpio_bit(dev, dev->gpio_dir, dev->gpio_val);
+
+	return status;
+}
+
+/*****************************************************************************
+*                      G P I O I2C related functions                         *
+******************************************************************************/
+/* cx231xx_gpio_i2c_read
+ * Function to read data from gpio based I2C interface
+ */
+int cx231xx_gpio_i2c_read(struct cx231xx *dev, u8 dev_addr, u8 *buf, u8 len)
+{
+	int status = 0;
+	int i = 0;
+
+	/* get the lock */
+	mutex_lock(&dev->gpio_i2c_lock);
+
+	/* start */
+	status = cx231xx_gpio_i2c_start(dev);
+
+	/* write dev_addr */
+	status = cx231xx_gpio_i2c_write_byte(dev, (dev_addr << 1) + 1);
+
+	/* readAck */
+	status = cx231xx_gpio_i2c_read_ack(dev);
+
+	/* read data */
+	for (i = 0; i < len; i++) {
+		/* read data */
+		buf[i] = 0;
+		status = cx231xx_gpio_i2c_read_byte(dev, &buf[i]);
+
+		if ((i + 1) != len) {
+			/* only do write ack if we more length */
+			status = cx231xx_gpio_i2c_write_ack(dev);
+		}
+	}
+
+	/* write NAK - inform reads are complete */
+	status = cx231xx_gpio_i2c_write_nak(dev);
+
+	/* write end */
+	status = cx231xx_gpio_i2c_end(dev);
+
+	/* release the lock */
+	mutex_unlock(&dev->gpio_i2c_lock);
+
+	return status;
+}
+
+/* cx231xx_gpio_i2c_write
+ * Function to write data to gpio based I2C interface
+ */
+int cx231xx_gpio_i2c_write(struct cx231xx *dev, u8 dev_addr, u8 *buf, u8 len)
+{
+	int i = 0;
+
+	/* get the lock */
+	mutex_lock(&dev->gpio_i2c_lock);
+
+	/* start */
+	cx231xx_gpio_i2c_start(dev);
+
+	/* write dev_addr */
+	cx231xx_gpio_i2c_write_byte(dev, dev_addr << 1);
+
+	/* read Ack */
+	cx231xx_gpio_i2c_read_ack(dev);
+
+	for (i = 0; i < len; i++) {
+		/* Write data */
+		cx231xx_gpio_i2c_write_byte(dev, buf[i]);
+
+		/* read Ack */
+		cx231xx_gpio_i2c_read_ack(dev);
+	}
+
+	/* write End */
+	cx231xx_gpio_i2c_end(dev);
+
+	/* release the lock */
+	mutex_unlock(&dev->gpio_i2c_lock);
+
+	return 0;
+}
diff --git a/drivers/media/usb/cx231xx/cx231xx-cards.c b/drivers/media/usb/cx231xx/cx231xx-cards.c
new file mode 100644
index 0000000..a431a99
--- /dev/null
+++ b/drivers/media/usb/cx231xx/cx231xx-cards.c
@@ -0,0 +1,1995 @@
+/*
+   cx231xx-cards.c - driver for Conexant Cx23100/101/102
+				USB video capture devices
+
+   Copyright (C) 2008 <srinivasa.deevi at conexant dot com>
+				Based on em28xx driver
+
+   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.
+ */
+
+#include "cx231xx.h"
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <media/tuner.h>
+#include <media/tveeprom.h>
+#include <media/v4l2-common.h>
+
+#include <media/drv-intf/cx25840.h>
+#include <media/dvb-usb-ids.h>
+#include "xc5000.h"
+#include "tda18271.h"
+
+
+static int tuner = -1;
+module_param(tuner, int, 0444);
+MODULE_PARM_DESC(tuner, "tuner type");
+
+static int transfer_mode = 1;
+module_param(transfer_mode, int, 0444);
+MODULE_PARM_DESC(transfer_mode, "transfer mode (1-ISO or 0-BULK)");
+
+static unsigned int disable_ir;
+module_param(disable_ir, int, 0444);
+MODULE_PARM_DESC(disable_ir, "disable infrared remote support");
+
+/* Bitmask marking allocated devices from 0 to CX231XX_MAXBOARDS */
+static unsigned long cx231xx_devused;
+
+/*
+ *  Reset sequences for analog/digital modes
+ */
+
+static struct cx231xx_reg_seq RDE250_XCV_TUNER[] = {
+	{0x03, 0x01, 10},
+	{0x03, 0x00, 30},
+	{0x03, 0x01, 10},
+	{-1, -1, -1},
+};
+
+/*
+ *  Board definitions
+ */
+struct cx231xx_board cx231xx_boards[] = {
+	[CX231XX_BOARD_UNKNOWN] = {
+		.name = "Unknown CX231xx video grabber",
+		.tuner_type = TUNER_ABSENT,
+		.input = {{
+				.type = CX231XX_VMUX_TELEVISION,
+				.vmux = CX231XX_VIN_3_1,
+				.amux = CX231XX_AMUX_VIDEO,
+				.gpio = NULL,
+			}, {
+				.type = CX231XX_VMUX_COMPOSITE1,
+				.vmux = CX231XX_VIN_2_1,
+				.amux = CX231XX_AMUX_LINE_IN,
+				.gpio = NULL,
+			}, {
+				.type = CX231XX_VMUX_SVIDEO,
+				.vmux = CX231XX_VIN_1_1 |
+					(CX231XX_VIN_1_2 << 8) |
+					CX25840_SVIDEO_ON,
+				.amux = CX231XX_AMUX_LINE_IN,
+				.gpio = NULL,
+			}
+		},
+	},
+	[CX231XX_BOARD_CNXT_CARRAERA] = {
+		.name = "Conexant Hybrid TV - CARRAERA",
+		.tuner_type = TUNER_XC5000,
+		.tuner_addr = 0x61,
+		.tuner_gpio = RDE250_XCV_TUNER,
+		.tuner_sif_gpio = 0x05,
+		.tuner_scl_gpio = 0x1a,
+		.tuner_sda_gpio = 0x1b,
+		.decoder = CX231XX_AVDECODER,
+		.output_mode = OUT_MODE_VIP11,
+		.demod_xfer_mode = 0,
+		.ctl_pin_status_mask = 0xFFFFFFC4,
+		.agc_analog_digital_select_gpio = 0x0c,
+		.gpio_pin_status_mask = 0x4001000,
+		.tuner_i2c_master = I2C_1_MUX_3,
+		.demod_i2c_master = I2C_2,
+		.has_dvb = 1,
+		.demod_addr = 0x02,
+		.norm = V4L2_STD_PAL,
+
+		.input = {{
+				.type = CX231XX_VMUX_TELEVISION,
+				.vmux = CX231XX_VIN_3_1,
+				.amux = CX231XX_AMUX_VIDEO,
+				.gpio = NULL,
+			}, {
+				.type = CX231XX_VMUX_COMPOSITE1,
+				.vmux = CX231XX_VIN_2_1,
+				.amux = CX231XX_AMUX_LINE_IN,
+				.gpio = NULL,
+			}, {
+				.type = CX231XX_VMUX_SVIDEO,
+				.vmux = CX231XX_VIN_1_1 |
+					(CX231XX_VIN_1_2 << 8) |
+					CX25840_SVIDEO_ON,
+				.amux = CX231XX_AMUX_LINE_IN,
+				.gpio = NULL,
+			}
+		},
+	},
+	[CX231XX_BOARD_CNXT_SHELBY] = {
+		.name = "Conexant Hybrid TV - SHELBY",
+		.tuner_type = TUNER_XC5000,
+		.tuner_addr = 0x61,
+		.tuner_gpio = RDE250_XCV_TUNER,
+		.tuner_sif_gpio = 0x05,
+		.tuner_scl_gpio = 0x1a,
+		.tuner_sda_gpio = 0x1b,
+		.decoder = CX231XX_AVDECODER,
+		.output_mode = OUT_MODE_VIP11,
+		.demod_xfer_mode = 0,
+		.ctl_pin_status_mask = 0xFFFFFFC4,
+		.agc_analog_digital_select_gpio = 0x0c,
+		.gpio_pin_status_mask = 0x4001000,
+		.tuner_i2c_master = I2C_1_MUX_3,
+		.demod_i2c_master = I2C_2,
+		.has_dvb = 1,
+		.demod_addr = 0x32,
+		.norm = V4L2_STD_NTSC,
+
+		.input = {{
+				.type = CX231XX_VMUX_TELEVISION,
+				.vmux = CX231XX_VIN_3_1,
+				.amux = CX231XX_AMUX_VIDEO,
+				.gpio = NULL,
+			}, {
+				.type = CX231XX_VMUX_COMPOSITE1,
+				.vmux = CX231XX_VIN_2_1,
+				.amux = CX231XX_AMUX_LINE_IN,
+				.gpio = NULL,
+			}, {
+				.type = CX231XX_VMUX_SVIDEO,
+				.vmux = CX231XX_VIN_1_1 |
+					(CX231XX_VIN_1_2 << 8) |
+					CX25840_SVIDEO_ON,
+				.amux = CX231XX_AMUX_LINE_IN,
+				.gpio = NULL,
+			}
+		},
+	},
+	[CX231XX_BOARD_CNXT_RDE_253S] = {
+		.name = "Conexant Hybrid TV - RDE253S",
+		.tuner_type = TUNER_NXP_TDA18271,
+		.tuner_addr = 0x60,
+		.tuner_gpio = RDE250_XCV_TUNER,
+		.tuner_sif_gpio = 0x05,
+		.tuner_scl_gpio = 0x1a,
+		.tuner_sda_gpio = 0x1b,
+		.decoder = CX231XX_AVDECODER,
+		.output_mode = OUT_MODE_VIP11,
+		.demod_xfer_mode = 0,
+		.ctl_pin_status_mask = 0xFFFFFFC4,
+		.agc_analog_digital_select_gpio = 0x1c,
+		.gpio_pin_status_mask = 0x4001000,
+		.tuner_i2c_master = I2C_1_MUX_3,
+		.demod_i2c_master = I2C_2,
+		.has_dvb = 1,
+		.demod_addr = 0x02,
+		.norm = V4L2_STD_PAL,
+
+		.input = {{
+				.type = CX231XX_VMUX_TELEVISION,
+				.vmux = CX231XX_VIN_3_1,
+				.amux = CX231XX_AMUX_VIDEO,
+				.gpio = NULL,
+			}, {
+				.type = CX231XX_VMUX_COMPOSITE1,
+				.vmux = CX231XX_VIN_2_1,
+				.amux = CX231XX_AMUX_LINE_IN,
+				.gpio = NULL,
+			}, {
+				.type = CX231XX_VMUX_SVIDEO,
+				.vmux = CX231XX_VIN_1_1 |
+					(CX231XX_VIN_1_2 << 8) |
+					CX25840_SVIDEO_ON,
+				.amux = CX231XX_AMUX_LINE_IN,
+				.gpio = NULL,
+			}
+		},
+	},
+
+	[CX231XX_BOARD_CNXT_RDU_253S] = {
+		.name = "Conexant Hybrid TV - RDU253S",
+		.tuner_type = TUNER_NXP_TDA18271,
+		.tuner_addr = 0x60,
+		.tuner_gpio = RDE250_XCV_TUNER,
+		.tuner_sif_gpio = 0x05,
+		.tuner_scl_gpio = 0x1a,
+		.tuner_sda_gpio = 0x1b,
+		.decoder = CX231XX_AVDECODER,
+		.output_mode = OUT_MODE_VIP11,
+		.demod_xfer_mode = 0,
+		.ctl_pin_status_mask = 0xFFFFFFC4,
+		.agc_analog_digital_select_gpio = 0x1c,
+		.gpio_pin_status_mask = 0x4001000,
+		.tuner_i2c_master = I2C_1_MUX_3,
+		.demod_i2c_master = I2C_2,
+		.has_dvb = 1,
+		.demod_addr = 0x02,
+		.norm = V4L2_STD_PAL,
+
+		.input = {{
+				.type = CX231XX_VMUX_TELEVISION,
+				.vmux = CX231XX_VIN_3_1,
+				.amux = CX231XX_AMUX_VIDEO,
+				.gpio = NULL,
+			}, {
+				.type = CX231XX_VMUX_COMPOSITE1,
+				.vmux = CX231XX_VIN_2_1,
+				.amux = CX231XX_AMUX_LINE_IN,
+				.gpio = NULL,
+			}, {
+				.type = CX231XX_VMUX_SVIDEO,
+				.vmux = CX231XX_VIN_1_1 |
+					(CX231XX_VIN_1_2 << 8) |
+					CX25840_SVIDEO_ON,
+				.amux = CX231XX_AMUX_LINE_IN,
+				.gpio = NULL,
+			}
+		},
+	},
+	[CX231XX_BOARD_CNXT_VIDEO_GRABBER] = {
+		.name = "Conexant VIDEO GRABBER",
+		.tuner_type = TUNER_ABSENT,
+		.decoder = CX231XX_AVDECODER,
+		.output_mode = OUT_MODE_VIP11,
+		.ctl_pin_status_mask = 0xFFFFFFC4,
+		.agc_analog_digital_select_gpio = 0x1c,
+		.gpio_pin_status_mask = 0x4001000,
+		.norm = V4L2_STD_PAL,
+		.no_alt_vanc = 1,
+		.external_av = 1,
+		/* Actually, it has a 417, but it isn't working correctly.
+		 * So set to 0 for now until someone can manage to get this
+		 * to work reliably. */
+		.has_417 = 0,
+
+		.input = {{
+				.type = CX231XX_VMUX_COMPOSITE1,
+				.vmux = CX231XX_VIN_2_1,
+				.amux = CX231XX_AMUX_LINE_IN,
+				.gpio = NULL,
+			}, {
+				.type = CX231XX_VMUX_SVIDEO,
+				.vmux = CX231XX_VIN_1_1 |
+					(CX231XX_VIN_1_2 << 8) |
+					CX25840_SVIDEO_ON,
+				.amux = CX231XX_AMUX_LINE_IN,
+				.gpio = NULL,
+			}
+		},
+	},
+	[CX231XX_BOARD_CNXT_RDE_250] = {
+		.name = "Conexant Hybrid TV - rde 250",
+		.tuner_type = TUNER_XC5000,
+		.tuner_addr = 0x61,
+		.tuner_gpio = RDE250_XCV_TUNER,
+		.tuner_sif_gpio = 0x05,
+		.tuner_scl_gpio = 0x1a,
+		.tuner_sda_gpio = 0x1b,
+		.decoder = CX231XX_AVDECODER,
+		.output_mode = OUT_MODE_VIP11,
+		.demod_xfer_mode = 0,
+		.ctl_pin_status_mask = 0xFFFFFFC4,
+		.agc_analog_digital_select_gpio = 0x0c,
+		.gpio_pin_status_mask = 0x4001000,
+		.tuner_i2c_master = I2C_1_MUX_3,
+		.demod_i2c_master = I2C_2,
+		.has_dvb = 1,
+		.demod_addr = 0x02,
+		.norm = V4L2_STD_PAL,
+
+		.input = {{
+				.type = CX231XX_VMUX_TELEVISION,
+				.vmux = CX231XX_VIN_2_1,
+				.amux = CX231XX_AMUX_VIDEO,
+				.gpio = NULL,
+			}
+		},
+	},
+	[CX231XX_BOARD_CNXT_RDU_250] = {
+		.name = "Conexant Hybrid TV - RDU 250",
+		.tuner_type = TUNER_XC5000,
+		.tuner_addr = 0x61,
+		.tuner_gpio = RDE250_XCV_TUNER,
+		.tuner_sif_gpio = 0x05,
+		.tuner_scl_gpio = 0x1a,
+		.tuner_sda_gpio = 0x1b,
+		.decoder = CX231XX_AVDECODER,
+		.output_mode = OUT_MODE_VIP11,
+		.demod_xfer_mode = 0,
+		.ctl_pin_status_mask = 0xFFFFFFC4,
+		.agc_analog_digital_select_gpio = 0x0c,
+		.gpio_pin_status_mask = 0x4001000,
+		.tuner_i2c_master = I2C_1_MUX_3,
+		.demod_i2c_master = I2C_2,
+		.has_dvb = 1,
+		.demod_addr = 0x32,
+		.norm = V4L2_STD_NTSC,
+
+		.input = {{
+				.type = CX231XX_VMUX_TELEVISION,
+				.vmux = CX231XX_VIN_2_1,
+				.amux = CX231XX_AMUX_VIDEO,
+				.gpio = NULL,
+			}
+		},
+	},
+	[CX231XX_BOARD_HAUPPAUGE_EXETER] = {
+		.name = "Hauppauge EXETER",
+		.tuner_type = TUNER_NXP_TDA18271,
+		.tuner_addr = 0x60,
+		.tuner_gpio = RDE250_XCV_TUNER,
+		.tuner_sif_gpio = 0x05,
+		.tuner_scl_gpio = 0x1a,
+		.tuner_sda_gpio = 0x1b,
+		.decoder = CX231XX_AVDECODER,
+		.output_mode = OUT_MODE_VIP11,
+		.demod_xfer_mode = 0,
+		.ctl_pin_status_mask = 0xFFFFFFC4,
+		.agc_analog_digital_select_gpio = 0x0c,
+		.gpio_pin_status_mask = 0x4001000,
+		.tuner_i2c_master = I2C_1_MUX_1,
+		.demod_i2c_master = I2C_1_MUX_1,
+		.has_dvb = 1,
+		.demod_addr = 0x0e,
+		.norm = V4L2_STD_NTSC,
+
+		.input = {{
+			.type = CX231XX_VMUX_TELEVISION,
+			.vmux = CX231XX_VIN_3_1,
+			.amux = CX231XX_AMUX_VIDEO,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_COMPOSITE1,
+			.vmux = CX231XX_VIN_2_1,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_SVIDEO,
+			.vmux = CX231XX_VIN_1_1 |
+				(CX231XX_VIN_1_2 << 8) |
+				CX25840_SVIDEO_ON,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		} },
+	},
+	[CX231XX_BOARD_HAUPPAUGE_USBLIVE2] = {
+		.name = "Hauppauge USB Live 2",
+		.tuner_type = TUNER_ABSENT,
+		.decoder = CX231XX_AVDECODER,
+		.output_mode = OUT_MODE_VIP11,
+		.demod_xfer_mode = 0,
+		.ctl_pin_status_mask = 0xFFFFFFC4,
+		.agc_analog_digital_select_gpio = 0x0c,
+		.gpio_pin_status_mask = 0x4001000,
+		.norm = V4L2_STD_NTSC,
+		.no_alt_vanc = 1,
+		.external_av = 1,
+		.input = {{
+			.type = CX231XX_VMUX_COMPOSITE1,
+			.vmux = CX231XX_VIN_2_1,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_SVIDEO,
+			.vmux = CX231XX_VIN_1_1 |
+				(CX231XX_VIN_1_2 << 8) |
+				CX25840_SVIDEO_ON,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		} },
+	},
+	[CX231XX_BOARD_KWORLD_UB430_USB_HYBRID] = {
+		.name = "Kworld UB430 USB Hybrid",
+		.tuner_type = TUNER_NXP_TDA18271,
+		.tuner_addr = 0x60,
+		.decoder = CX231XX_AVDECODER,
+		.output_mode = OUT_MODE_VIP11,
+		.demod_xfer_mode = 0,
+		.ctl_pin_status_mask = 0xFFFFFFC4,
+		.agc_analog_digital_select_gpio = 0x11,	/* According with PV cxPolaris.inf file */
+		.tuner_sif_gpio = -1,
+		.tuner_scl_gpio = -1,
+		.tuner_sda_gpio = -1,
+		.gpio_pin_status_mask = 0x4001000,
+		.tuner_i2c_master = I2C_2,
+		.demod_i2c_master = I2C_1_MUX_3,
+		.ir_i2c_master = I2C_2,
+		.has_dvb = 1,
+		.demod_addr = 0x10,
+		.norm = V4L2_STD_PAL_M,
+		.input = {{
+			.type = CX231XX_VMUX_TELEVISION,
+			.vmux = CX231XX_VIN_3_1,
+			.amux = CX231XX_AMUX_VIDEO,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_COMPOSITE1,
+			.vmux = CX231XX_VIN_2_1,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_SVIDEO,
+			.vmux = CX231XX_VIN_1_1 |
+				(CX231XX_VIN_1_2 << 8) |
+				CX25840_SVIDEO_ON,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		} },
+	},
+	[CX231XX_BOARD_KWORLD_UB445_USB_HYBRID] = {
+		.name = "Kworld UB445 USB Hybrid",
+		.tuner_type = TUNER_NXP_TDA18271,
+		.tuner_addr = 0x60,
+		.decoder = CX231XX_AVDECODER,
+		.output_mode = OUT_MODE_VIP11,
+		.demod_xfer_mode = 0,
+		.ctl_pin_status_mask = 0xFFFFFFC4,
+		.agc_analog_digital_select_gpio = 0x11,	/* According with PV cxPolaris.inf file */
+		.tuner_sif_gpio = -1,
+		.tuner_scl_gpio = -1,
+		.tuner_sda_gpio = -1,
+		.gpio_pin_status_mask = 0x4001000,
+		.tuner_i2c_master = I2C_2,
+		.demod_i2c_master = I2C_1_MUX_3,
+		.ir_i2c_master = I2C_2,
+		.has_dvb = 1,
+		.demod_addr = 0x10,
+		.norm = V4L2_STD_NTSC_M,
+		.input = {{
+			.type = CX231XX_VMUX_TELEVISION,
+			.vmux = CX231XX_VIN_3_1,
+			.amux = CX231XX_AMUX_VIDEO,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_COMPOSITE1,
+			.vmux = CX231XX_VIN_2_1,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_SVIDEO,
+			.vmux = CX231XX_VIN_1_1 |
+				(CX231XX_VIN_1_2 << 8) |
+				CX25840_SVIDEO_ON,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		} },
+	},
+	[CX231XX_BOARD_PV_PLAYTV_USB_HYBRID] = {
+		.name = "Pixelview PlayTV USB Hybrid",
+		.tuner_type = TUNER_NXP_TDA18271,
+		.tuner_addr = 0x60,
+		.decoder = CX231XX_AVDECODER,
+		.output_mode = OUT_MODE_VIP11,
+		.demod_xfer_mode = 0,
+		.ctl_pin_status_mask = 0xFFFFFFC4,
+		.agc_analog_digital_select_gpio = 0x1c,
+		.tuner_sif_gpio = -1,
+		.tuner_scl_gpio = -1,
+		.tuner_sda_gpio = -1,
+		.gpio_pin_status_mask = 0x4001000,
+		.tuner_i2c_master = I2C_2,
+		.demod_i2c_master = I2C_1_MUX_3,
+		.ir_i2c_master = I2C_2,
+		.rc_map_name = RC_MAP_PIXELVIEW_002T,
+		.has_dvb = 1,
+		.demod_addr = 0x10,
+		.norm = V4L2_STD_PAL_M,
+		.input = {{
+			.type = CX231XX_VMUX_TELEVISION,
+			.vmux = CX231XX_VIN_3_1,
+			.amux = CX231XX_AMUX_VIDEO,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_COMPOSITE1,
+			.vmux = CX231XX_VIN_2_1,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_SVIDEO,
+			.vmux = CX231XX_VIN_1_1 |
+				(CX231XX_VIN_1_2 << 8) |
+				CX25840_SVIDEO_ON,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		} },
+	},
+	[CX231XX_BOARD_PV_XCAPTURE_USB] = {
+		.name = "Pixelview Xcapture USB",
+		.tuner_type = TUNER_ABSENT,
+		.decoder = CX231XX_AVDECODER,
+		.output_mode = OUT_MODE_VIP11,
+		.demod_xfer_mode = 0,
+		.ctl_pin_status_mask = 0xFFFFFFC4,
+		.agc_analog_digital_select_gpio = 0x0c,
+		.gpio_pin_status_mask = 0x4001000,
+		.norm = V4L2_STD_NTSC,
+		.no_alt_vanc = 1,
+		.external_av = 1,
+
+		.input = {{
+				.type = CX231XX_VMUX_COMPOSITE1,
+				.vmux = CX231XX_VIN_2_1,
+				.amux = CX231XX_AMUX_LINE_IN,
+				.gpio = NULL,
+			}, {
+				.type = CX231XX_VMUX_SVIDEO,
+				.vmux = CX231XX_VIN_1_1 |
+					(CX231XX_VIN_1_2 << 8) |
+					CX25840_SVIDEO_ON,
+				.amux = CX231XX_AMUX_LINE_IN,
+				.gpio = NULL,
+			}
+		},
+	},
+
+	[CX231XX_BOARD_ICONBIT_U100] = {
+		.name = "Iconbit Analog Stick U100 FM",
+		.tuner_type = TUNER_ABSENT,
+		.decoder = CX231XX_AVDECODER,
+		.output_mode = OUT_MODE_VIP11,
+		.demod_xfer_mode = 0,
+		.ctl_pin_status_mask = 0xFFFFFFC4,
+		.agc_analog_digital_select_gpio = 0x1C,
+		.gpio_pin_status_mask = 0x4001000,
+
+		.input = {{
+			.type = CX231XX_VMUX_COMPOSITE1,
+			.vmux = CX231XX_VIN_2_1,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_SVIDEO,
+			.vmux = CX231XX_VIN_1_1 |
+				(CX231XX_VIN_1_2 << 8) |
+				CX25840_SVIDEO_ON,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		} },
+	},
+	[CX231XX_BOARD_HAUPPAUGE_USB2_FM_PAL] = {
+		.name = "Hauppauge WinTV USB2 FM (PAL)",
+		.tuner_type = TUNER_NXP_TDA18271,
+		.tuner_addr = 0x60,
+		.tuner_gpio = RDE250_XCV_TUNER,
+		.tuner_sif_gpio = 0x05,
+		.tuner_scl_gpio = 0x1a,
+		.tuner_sda_gpio = 0x1b,
+		.decoder = CX231XX_AVDECODER,
+		.output_mode = OUT_MODE_VIP11,
+		.ctl_pin_status_mask = 0xFFFFFFC4,
+		.agc_analog_digital_select_gpio = 0x0c,
+		.gpio_pin_status_mask = 0x4001000,
+		.tuner_i2c_master = I2C_1_MUX_3,
+		.norm = V4L2_STD_PAL,
+
+		.input = {{
+			.type = CX231XX_VMUX_TELEVISION,
+			.vmux = CX231XX_VIN_3_1,
+			.amux = CX231XX_AMUX_VIDEO,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_COMPOSITE1,
+			.vmux = CX231XX_VIN_2_1,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_SVIDEO,
+			.vmux = CX231XX_VIN_1_1 |
+				(CX231XX_VIN_1_2 << 8) |
+				CX25840_SVIDEO_ON,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		} },
+	},
+	[CX231XX_BOARD_HAUPPAUGE_USB2_FM_NTSC] = {
+		.name = "Hauppauge WinTV USB2 FM (NTSC)",
+		.tuner_type = TUNER_NXP_TDA18271,
+		.tuner_addr = 0x60,
+		.tuner_gpio = RDE250_XCV_TUNER,
+		.tuner_sif_gpio = 0x05,
+		.tuner_scl_gpio = 0x1a,
+		.tuner_sda_gpio = 0x1b,
+		.decoder = CX231XX_AVDECODER,
+		.output_mode = OUT_MODE_VIP11,
+		.ctl_pin_status_mask = 0xFFFFFFC4,
+		.agc_analog_digital_select_gpio = 0x0c,
+		.gpio_pin_status_mask = 0x4001000,
+		.tuner_i2c_master = I2C_1_MUX_3,
+		.norm = V4L2_STD_NTSC,
+
+		.input = {{
+			.type = CX231XX_VMUX_TELEVISION,
+			.vmux = CX231XX_VIN_3_1,
+			.amux = CX231XX_AMUX_VIDEO,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_COMPOSITE1,
+			.vmux = CX231XX_VIN_2_1,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_SVIDEO,
+			.vmux = CX231XX_VIN_1_1 |
+				(CX231XX_VIN_1_2 << 8) |
+				CX25840_SVIDEO_ON,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		} },
+	},
+	[CX231XX_BOARD_ELGATO_VIDEO_CAPTURE_V2] = {
+		.name = "Elgato Video Capture V2",
+		.tuner_type = TUNER_ABSENT,
+		.decoder = CX231XX_AVDECODER,
+		.output_mode = OUT_MODE_VIP11,
+		.demod_xfer_mode = 0,
+		.ctl_pin_status_mask = 0xFFFFFFC4,
+		.agc_analog_digital_select_gpio = 0x0c,
+		.gpio_pin_status_mask = 0x4001000,
+		.norm = V4L2_STD_NTSC,
+		.no_alt_vanc = 1,
+		.external_av = 1,
+		.input = {{
+			.type = CX231XX_VMUX_COMPOSITE1,
+			.vmux = CX231XX_VIN_2_1,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_SVIDEO,
+			.vmux = CX231XX_VIN_1_1 |
+				(CX231XX_VIN_1_2 << 8) |
+				CX25840_SVIDEO_ON,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		} },
+	},
+	[CX231XX_BOARD_OTG102] = {
+		.name = "Geniatech OTG102",
+		.tuner_type = TUNER_ABSENT,
+		.decoder = CX231XX_AVDECODER,
+		.output_mode = OUT_MODE_VIP11,
+		.ctl_pin_status_mask = 0xFFFFFFC4,
+		.agc_analog_digital_select_gpio = 0x0c,
+			/* According with PV CxPlrCAP.inf file */
+		.gpio_pin_status_mask = 0x4001000,
+		.norm = V4L2_STD_NTSC,
+		.no_alt_vanc = 1,
+		.external_av = 1,
+		/*.has_417 = 1, */
+		/* This board is believed to have a hardware encoding chip
+		 * supporting mpeg1/2/4, but as the 417 is apparently not
+		 * working for the reference board it is not here either. */
+
+		.input = {{
+				.type = CX231XX_VMUX_COMPOSITE1,
+				.vmux = CX231XX_VIN_2_1,
+				.amux = CX231XX_AMUX_LINE_IN,
+				.gpio = NULL,
+			}, {
+				.type = CX231XX_VMUX_SVIDEO,
+				.vmux = CX231XX_VIN_1_1 |
+					(CX231XX_VIN_1_2 << 8) |
+					CX25840_SVIDEO_ON,
+				.amux = CX231XX_AMUX_LINE_IN,
+				.gpio = NULL,
+			}
+		},
+	},
+	[CX231XX_BOARD_HAUPPAUGE_930C_HD_1113xx] = {
+		.name = "Hauppauge WinTV 930C-HD (1113xx) / HVR-900H (111xxx) / PCTV QuatroStick 521e",
+		.tuner_type = TUNER_NXP_TDA18271,
+		.tuner_addr = 0x60,
+		.tuner_gpio = RDE250_XCV_TUNER,
+		.tuner_sif_gpio = 0x05,
+		.tuner_scl_gpio = 0x1a,
+		.tuner_sda_gpio = 0x1b,
+		.decoder = CX231XX_AVDECODER,
+		.output_mode = OUT_MODE_VIP11,
+		.demod_xfer_mode = 0,
+		.ctl_pin_status_mask = 0xFFFFFFC4,
+		.agc_analog_digital_select_gpio = 0x0c,
+		.gpio_pin_status_mask = 0x4001000,
+		.tuner_i2c_master = I2C_1_MUX_3,
+		.demod_i2c_master = I2C_1_MUX_3,
+		.has_dvb = 1,
+		.demod_addr = 0x64, /* 0xc8 >> 1 */
+		.norm = V4L2_STD_PAL,
+
+		.input = {{
+			.type = CX231XX_VMUX_TELEVISION,
+			.vmux = CX231XX_VIN_3_1,
+			.amux = CX231XX_AMUX_VIDEO,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_COMPOSITE1,
+			.vmux = CX231XX_VIN_2_1,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_SVIDEO,
+			.vmux = CX231XX_VIN_1_1 |
+				(CX231XX_VIN_1_2 << 8) |
+				CX25840_SVIDEO_ON,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		} },
+	},
+	[CX231XX_BOARD_HAUPPAUGE_930C_HD_1114xx] = {
+		.name = "Hauppauge WinTV 930C-HD (1114xx) / HVR-901H (1114xx) / PCTV QuatroStick 522e",
+		.tuner_type = TUNER_ABSENT,
+		.tuner_addr = 0x60,
+		.tuner_gpio = RDE250_XCV_TUNER,
+		.tuner_sif_gpio = 0x05,
+		.tuner_scl_gpio = 0x1a,
+		.tuner_sda_gpio = 0x1b,
+		.decoder = CX231XX_AVDECODER,
+		.output_mode = OUT_MODE_VIP11,
+		.demod_xfer_mode = 0,
+		.ctl_pin_status_mask = 0xFFFFFFC4,
+		.agc_analog_digital_select_gpio = 0x0c,
+		.gpio_pin_status_mask = 0x4001000,
+		.tuner_i2c_master = I2C_1_MUX_3,
+		.demod_i2c_master = I2C_1_MUX_3,
+		.has_dvb = 1,
+		.demod_addr = 0x64, /* 0xc8 >> 1 */
+		.norm = V4L2_STD_PAL,
+
+		.input = {{
+			.type = CX231XX_VMUX_TELEVISION,
+			.vmux = CX231XX_VIN_3_1,
+			.amux = CX231XX_AMUX_VIDEO,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_COMPOSITE1,
+			.vmux = CX231XX_VIN_2_1,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_SVIDEO,
+			.vmux = CX231XX_VIN_1_1 |
+				(CX231XX_VIN_1_2 << 8) |
+				CX25840_SVIDEO_ON,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		} },
+	},
+	[CX231XX_BOARD_HAUPPAUGE_955Q] = {
+		.name = "Hauppauge WinTV-HVR-955Q (111401)",
+		.tuner_type = TUNER_ABSENT,
+		.tuner_addr = 0x60,
+		.tuner_gpio = RDE250_XCV_TUNER,
+		.tuner_sif_gpio = 0x05,
+		.tuner_scl_gpio = 0x1a,
+		.tuner_sda_gpio = 0x1b,
+		.decoder = CX231XX_AVDECODER,
+		.output_mode = OUT_MODE_VIP11,
+		.demod_xfer_mode = 0,
+		.ctl_pin_status_mask = 0xFFFFFFC4,
+		.agc_analog_digital_select_gpio = 0x0c,
+		.gpio_pin_status_mask = 0x4001000,
+		.tuner_i2c_master = I2C_1_MUX_3,
+		.demod_i2c_master = I2C_1_MUX_3,
+		.has_dvb = 1,
+		.demod_addr = 0x59, /* 0xb2 >> 1 */
+		.norm = V4L2_STD_NTSC,
+
+		.input = {{
+			.type = CX231XX_VMUX_TELEVISION,
+			.vmux = CX231XX_VIN_3_1,
+			.amux = CX231XX_AMUX_VIDEO,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_COMPOSITE1,
+			.vmux = CX231XX_VIN_2_1,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_SVIDEO,
+			.vmux = CX231XX_VIN_1_1 |
+				(CX231XX_VIN_1_2 << 8) |
+				CX25840_SVIDEO_ON,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		} },
+	},
+	[CX231XX_BOARD_TERRATEC_GRABBY] = {
+		.name = "Terratec Grabby",
+		.tuner_type = TUNER_ABSENT,
+		.decoder = CX231XX_AVDECODER,
+		.output_mode = OUT_MODE_VIP11,
+		.demod_xfer_mode = 0,
+		.ctl_pin_status_mask = 0xFFFFFFC4,
+		.agc_analog_digital_select_gpio = 0x0c,
+		.gpio_pin_status_mask = 0x4001000,
+		.norm = V4L2_STD_PAL,
+		.no_alt_vanc = 1,
+		.external_av = 1,
+		.input = {{
+			.type = CX231XX_VMUX_COMPOSITE1,
+			.vmux = CX231XX_VIN_2_1,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_SVIDEO,
+			.vmux = CX231XX_VIN_1_1 |
+				(CX231XX_VIN_1_2 << 8) |
+				CX25840_SVIDEO_ON,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		} },
+	},
+	[CX231XX_BOARD_EVROMEDIA_FULL_HYBRID_FULLHD] = {
+		.name = "Evromedia USB Full Hybrid Full HD",
+		.tuner_type = TUNER_ABSENT,
+		.demod_addr = 0x64, /* 0xc8 >> 1 */
+		.demod_i2c_master = I2C_1_MUX_3,
+		.has_dvb = 1,
+		.decoder = CX231XX_AVDECODER,
+		.norm = V4L2_STD_PAL,
+		.output_mode = OUT_MODE_VIP11,
+		.tuner_addr = 0x60, /* 0xc0 >> 1 */
+		.tuner_i2c_master = I2C_2,
+		.input = {{
+			.type = CX231XX_VMUX_TELEVISION,
+			.vmux = 0,
+			.amux = CX231XX_AMUX_VIDEO,
+		}, {
+			.type = CX231XX_VMUX_COMPOSITE1,
+			.vmux = CX231XX_VIN_2_1,
+			.amux = CX231XX_AMUX_LINE_IN,
+		}, {
+			.type = CX231XX_VMUX_SVIDEO,
+			.vmux = CX231XX_VIN_1_1 |
+				(CX231XX_VIN_1_2 << 8) |
+				CX25840_SVIDEO_ON,
+			.amux = CX231XX_AMUX_LINE_IN,
+		} },
+	},
+	[CX231XX_BOARD_ASTROMETA_T2HYBRID] = {
+		.name = "Astrometa T2hybrid",
+		.tuner_type = TUNER_ABSENT,
+		.has_dvb = 1,
+		.decoder = CX231XX_AVDECODER,
+		.output_mode = OUT_MODE_VIP11,
+		.agc_analog_digital_select_gpio = 0x01,
+		.ctl_pin_status_mask = 0xffffffc4,
+		.demod_addr = 0x18, /* 0x30 >> 1 */
+		.demod_i2c_master = I2C_1_MUX_1,
+		.gpio_pin_status_mask = 0xa,
+		.norm = V4L2_STD_NTSC,
+		.tuner_addr = 0x3a, /* 0x74 >> 1 */
+		.tuner_i2c_master = I2C_1_MUX_3,
+		.tuner_scl_gpio = 0x1a,
+		.tuner_sda_gpio = 0x1b,
+		.tuner_sif_gpio = 0x05,
+		.input = {{
+				.type = CX231XX_VMUX_TELEVISION,
+				.vmux = CX231XX_VIN_1_1,
+				.amux = CX231XX_AMUX_VIDEO,
+			}, {
+				.type = CX231XX_VMUX_COMPOSITE1,
+				.vmux = CX231XX_VIN_2_1,
+				.amux = CX231XX_AMUX_LINE_IN,
+			},
+		},
+	},
+	[CX231XX_BOARD_THE_IMAGING_SOURCE_DFG_USB2_PRO] = {
+		.name = "The Imaging Source DFG/USB2pro",
+		.tuner_type = TUNER_ABSENT,
+		.decoder = CX231XX_AVDECODER,
+		.output_mode = OUT_MODE_VIP11,
+		.demod_xfer_mode = 0,
+		.ctl_pin_status_mask = 0xFFFFFFC4,
+		.agc_analog_digital_select_gpio = 0x0c,
+		.gpio_pin_status_mask = 0x4001000,
+		.norm = V4L2_STD_PAL,
+		.no_alt_vanc = 1,
+		.external_av = 1,
+		.input = {{
+			.type = CX231XX_VMUX_COMPOSITE1,
+			.vmux = CX231XX_VIN_1_1,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_SVIDEO,
+			.vmux = CX231XX_VIN_2_1 |
+				(CX231XX_VIN_2_2 << 8) |
+				CX25840_SVIDEO_ON,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		} },
+	},
+	[CX231XX_BOARD_HAUPPAUGE_935C] = {
+		.name = "Hauppauge WinTV-HVR-935C",
+		.tuner_type = TUNER_ABSENT,
+		.tuner_addr = 0x60,
+		.tuner_gpio = RDE250_XCV_TUNER,
+		.tuner_sif_gpio = 0x05,
+		.tuner_scl_gpio = 0x1a,
+		.tuner_sda_gpio = 0x1b,
+		.decoder = CX231XX_AVDECODER,
+		.output_mode = OUT_MODE_VIP11,
+		.demod_xfer_mode = 0,
+		.ctl_pin_status_mask = 0xFFFFFFC4,
+		.agc_analog_digital_select_gpio = 0x0c,
+		.gpio_pin_status_mask = 0x4001000,
+		.tuner_i2c_master = I2C_1_MUX_3,
+		.demod_i2c_master = I2C_1_MUX_3,
+		.has_dvb = 1,
+		.demod_addr = 0x64, /* 0xc8 >> 1 */
+		.norm = V4L2_STD_PAL,
+
+		.input = {{
+			.type = CX231XX_VMUX_TELEVISION,
+			.vmux = CX231XX_VIN_3_1,
+			.amux = CX231XX_AMUX_VIDEO,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_COMPOSITE1,
+			.vmux = CX231XX_VIN_2_1,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_SVIDEO,
+			.vmux = CX231XX_VIN_1_1 |
+				(CX231XX_VIN_1_2 << 8) |
+				CX25840_SVIDEO_ON,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		} },
+	},
+	[CX231XX_BOARD_HAUPPAUGE_975] = {
+		.name = "Hauppauge WinTV-HVR-975",
+		.tuner_type = TUNER_ABSENT,
+		.tuner_addr = 0x60,
+		.tuner_gpio = RDE250_XCV_TUNER,
+		.tuner_sif_gpio = 0x05,
+		.tuner_scl_gpio = 0x1a,
+		.tuner_sda_gpio = 0x1b,
+		.decoder = CX231XX_AVDECODER,
+		.output_mode = OUT_MODE_VIP11,
+		.demod_xfer_mode = 0,
+		.ctl_pin_status_mask = 0xFFFFFFC4,
+		.agc_analog_digital_select_gpio = 0x0c,
+		.gpio_pin_status_mask = 0x4001000,
+		.tuner_i2c_master = I2C_1_MUX_3,
+		.demod_i2c_master = I2C_1_MUX_3,
+		.has_dvb = 1,
+		.demod_addr = 0x59, /* 0xb2 >> 1 */
+		.demod_addr2 = 0x64, /* 0xc8 >> 1 */
+		.norm = V4L2_STD_ALL,
+
+		.input = {{
+			.type = CX231XX_VMUX_TELEVISION,
+			.vmux = CX231XX_VIN_3_1,
+			.amux = CX231XX_AMUX_VIDEO,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_COMPOSITE1,
+			.vmux = CX231XX_VIN_2_1,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		}, {
+			.type = CX231XX_VMUX_SVIDEO,
+			.vmux = CX231XX_VIN_1_1 |
+				(CX231XX_VIN_1_2 << 8) |
+				CX25840_SVIDEO_ON,
+			.amux = CX231XX_AMUX_LINE_IN,
+			.gpio = NULL,
+		} },
+	},
+};
+const unsigned int cx231xx_bcount = ARRAY_SIZE(cx231xx_boards);
+
+/* table of devices that work with this driver */
+struct usb_device_id cx231xx_id_table[] = {
+	{USB_DEVICE(0x1D19, 0x6109),
+	.driver_info = CX231XX_BOARD_PV_XCAPTURE_USB},
+	{USB_DEVICE(0x0572, 0x5A3C),
+	 .driver_info = CX231XX_BOARD_UNKNOWN},
+	{USB_DEVICE(0x0572, 0x58A2),
+	 .driver_info = CX231XX_BOARD_CNXT_CARRAERA},
+	{USB_DEVICE(0x0572, 0x58A1),
+	 .driver_info = CX231XX_BOARD_CNXT_SHELBY},
+	{USB_DEVICE(0x0572, 0x58A4),
+	 .driver_info = CX231XX_BOARD_CNXT_RDE_253S},
+	{USB_DEVICE(0x0572, 0x58A5),
+	 .driver_info = CX231XX_BOARD_CNXT_RDU_253S},
+	{USB_DEVICE(0x0572, 0x58A6),
+	 .driver_info = CX231XX_BOARD_CNXT_VIDEO_GRABBER},
+	{USB_DEVICE(0x0572, 0x589E),
+	 .driver_info = CX231XX_BOARD_CNXT_RDE_250},
+	{USB_DEVICE(0x0572, 0x58A0),
+	 .driver_info = CX231XX_BOARD_CNXT_RDU_250},
+	/* AverMedia DVD EZMaker 7 */
+	{USB_DEVICE(0x07ca, 0xc039),
+	 .driver_info = CX231XX_BOARD_CNXT_VIDEO_GRABBER},
+	{USB_DEVICE(0x2040, 0xb110),
+	 .driver_info = CX231XX_BOARD_HAUPPAUGE_USB2_FM_PAL},
+	{USB_DEVICE(0x2040, 0xb111),
+	 .driver_info = CX231XX_BOARD_HAUPPAUGE_USB2_FM_NTSC},
+	{USB_DEVICE(0x2040, 0xb120),
+	 .driver_info = CX231XX_BOARD_HAUPPAUGE_EXETER},
+	{USB_DEVICE(0x2040, 0xb123),
+	 .driver_info = CX231XX_BOARD_HAUPPAUGE_955Q},
+	{USB_DEVICE(0x2040, 0xb151),
+	 .driver_info = CX231XX_BOARD_HAUPPAUGE_935C},
+	{USB_DEVICE(0x2040, 0xb150),
+	 .driver_info = CX231XX_BOARD_HAUPPAUGE_975},
+	{USB_DEVICE(0x2040, 0xb130),
+	 .driver_info = CX231XX_BOARD_HAUPPAUGE_930C_HD_1113xx},
+	{USB_DEVICE(0x2040, 0xb131),
+	 .driver_info = CX231XX_BOARD_HAUPPAUGE_930C_HD_1114xx},
+	/* Hauppauge WinTV-HVR-900-H */
+	{USB_DEVICE(0x2040, 0xb138),
+	 .driver_info = CX231XX_BOARD_HAUPPAUGE_930C_HD_1113xx},
+	/* Hauppauge WinTV-HVR-901-H */
+	{USB_DEVICE(0x2040, 0xb139),
+	 .driver_info = CX231XX_BOARD_HAUPPAUGE_930C_HD_1114xx},
+	{USB_DEVICE(0x2040, 0xb140),
+	 .driver_info = CX231XX_BOARD_HAUPPAUGE_EXETER},
+	{USB_DEVICE(0x2040, 0xc200),
+	 .driver_info = CX231XX_BOARD_HAUPPAUGE_USBLIVE2},
+	/* PCTV QuatroStick 521e */
+	{USB_DEVICE(0x2013, 0x0259),
+	 .driver_info = CX231XX_BOARD_HAUPPAUGE_930C_HD_1113xx},
+	/* PCTV QuatroStick 522e */
+	{USB_DEVICE(0x2013, 0x025e),
+	 .driver_info = CX231XX_BOARD_HAUPPAUGE_930C_HD_1114xx},
+	{USB_DEVICE_VER(USB_VID_PIXELVIEW, USB_PID_PIXELVIEW_SBTVD, 0x4000, 0x4001),
+	 .driver_info = CX231XX_BOARD_PV_PLAYTV_USB_HYBRID},
+	{USB_DEVICE(USB_VID_PIXELVIEW, 0x5014),
+	 .driver_info = CX231XX_BOARD_PV_XCAPTURE_USB},
+	{USB_DEVICE(0x1b80, 0xe424),
+	 .driver_info = CX231XX_BOARD_KWORLD_UB430_USB_HYBRID},
+	{USB_DEVICE(0x1b80, 0xe421),
+	 .driver_info = CX231XX_BOARD_KWORLD_UB445_USB_HYBRID},
+	{USB_DEVICE(0x1f4d, 0x0237),
+	 .driver_info = CX231XX_BOARD_ICONBIT_U100},
+	{USB_DEVICE(0x0fd9, 0x0037),
+	 .driver_info = CX231XX_BOARD_ELGATO_VIDEO_CAPTURE_V2},
+	{USB_DEVICE(0x1f4d, 0x0102),
+	 .driver_info = CX231XX_BOARD_OTG102},
+	{USB_DEVICE(USB_VID_TERRATEC, 0x00a6),
+	 .driver_info = CX231XX_BOARD_TERRATEC_GRABBY},
+	{USB_DEVICE(0x1b80, 0xd3b2),
+	.driver_info = CX231XX_BOARD_EVROMEDIA_FULL_HYBRID_FULLHD},
+	{USB_DEVICE(0x15f4, 0x0135),
+	.driver_info = CX231XX_BOARD_ASTROMETA_T2HYBRID},
+	{USB_DEVICE(0x199e, 0x8002),
+	 .driver_info = CX231XX_BOARD_THE_IMAGING_SOURCE_DFG_USB2_PRO},
+	{},
+};
+
+MODULE_DEVICE_TABLE(usb, cx231xx_id_table);
+
+/* cx231xx_tuner_callback
+ * will be used to reset XC5000 tuner using GPIO pin
+ */
+
+int cx231xx_tuner_callback(void *ptr, int component, int command, int arg)
+{
+	int rc = 0;
+	struct cx231xx *dev = ptr;
+
+	if (dev->tuner_type == TUNER_XC5000) {
+		if (command == XC5000_TUNER_RESET) {
+			dev_dbg(dev->dev,
+				"Tuner CB: RESET: cmd %d : tuner type %d\n",
+				command, dev->tuner_type);
+			cx231xx_set_gpio_value(dev, dev->board.tuner_gpio->bit,
+					       1);
+			msleep(10);
+			cx231xx_set_gpio_value(dev, dev->board.tuner_gpio->bit,
+					       0);
+			msleep(330);
+			cx231xx_set_gpio_value(dev, dev->board.tuner_gpio->bit,
+					       1);
+			msleep(10);
+		}
+	} else if (dev->tuner_type == TUNER_NXP_TDA18271) {
+		switch (command) {
+		case TDA18271_CALLBACK_CMD_AGC_ENABLE:
+			if (dev->model == CX231XX_BOARD_PV_PLAYTV_USB_HYBRID)
+				rc = cx231xx_set_agc_analog_digital_mux_select(dev, arg);
+			break;
+		default:
+			rc = -EINVAL;
+			break;
+		}
+	}
+	return rc;
+}
+EXPORT_SYMBOL_GPL(cx231xx_tuner_callback);
+
+static void cx231xx_reset_out(struct cx231xx *dev)
+{
+	cx231xx_set_gpio_value(dev, CX23417_RESET, 1);
+	msleep(200);
+	cx231xx_set_gpio_value(dev, CX23417_RESET, 0);
+	msleep(200);
+	cx231xx_set_gpio_value(dev, CX23417_RESET, 1);
+}
+
+static void cx231xx_enable_OSC(struct cx231xx *dev)
+{
+	cx231xx_set_gpio_value(dev, CX23417_OSC_EN, 1);
+}
+
+static void cx231xx_sleep_s5h1432(struct cx231xx *dev)
+{
+	cx231xx_set_gpio_value(dev, SLEEP_S5H1432, 0);
+}
+
+static inline void cx231xx_set_model(struct cx231xx *dev)
+{
+	dev->board = cx231xx_boards[dev->model];
+}
+
+/* Since cx231xx_pre_card_setup() requires a proper dev->model,
+ * this won't work for boards with generic PCI IDs
+ */
+void cx231xx_pre_card_setup(struct cx231xx *dev)
+{
+	dev_info(dev->dev, "Identified as %s (card=%d)\n",
+		dev->board.name, dev->model);
+
+	if (CX231XX_BOARD_ASTROMETA_T2HYBRID == dev->model) {
+		/* turn on demodulator chip */
+		cx231xx_set_gpio_value(dev, 0x03, 0x01);
+	}
+
+	/* set the direction for GPIO pins */
+	if (dev->board.tuner_gpio) {
+		cx231xx_set_gpio_direction(dev, dev->board.tuner_gpio->bit, 1);
+		cx231xx_set_gpio_value(dev, dev->board.tuner_gpio->bit, 1);
+	}
+	if (dev->board.tuner_sif_gpio >= 0)
+		cx231xx_set_gpio_direction(dev, dev->board.tuner_sif_gpio, 1);
+
+	/* request some modules if any required */
+
+	/* set the mode to Analog mode initially */
+	cx231xx_set_mode(dev, CX231XX_ANALOG_MODE);
+
+	/* Unlock device */
+	/* cx231xx_set_mode(dev, CX231XX_SUSPEND); */
+
+}
+
+static void cx231xx_config_tuner(struct cx231xx *dev)
+{
+	struct tuner_setup tun_setup;
+	struct v4l2_frequency f;
+
+	if (dev->tuner_type == TUNER_ABSENT)
+		return;
+
+	tun_setup.mode_mask = T_ANALOG_TV | T_RADIO;
+	tun_setup.type = dev->tuner_type;
+	tun_setup.addr = dev->tuner_addr;
+	tun_setup.tuner_callback = cx231xx_tuner_callback;
+
+	tuner_call(dev, tuner, s_type_addr, &tun_setup);
+
+#if 0
+	if (tun_setup.type == TUNER_XC5000) {
+		static struct xc2028_ctrl ctrl = {
+			.fname = XC5000_DEFAULT_FIRMWARE,
+			.max_len = 64,
+			.demod = 0;
+		};
+		struct v4l2_priv_tun_config cfg = {
+			.tuner = dev->tuner_type,
+			.priv = &ctrl,
+		};
+		tuner_call(dev, tuner, s_config, &cfg);
+	}
+#endif
+	/* configure tuner */
+	f.tuner = 0;
+	f.type = V4L2_TUNER_ANALOG_TV;
+	f.frequency = 9076;	/* just a magic number */
+	dev->ctl_freq = f.frequency;
+	call_all(dev, tuner, s_frequency, &f);
+
+}
+
+static int read_eeprom(struct cx231xx *dev, struct i2c_client *client,
+		       u8 *eedata, int len)
+{
+	int ret;
+	u8 start_offset = 0;
+	int len_todo = len;
+	u8 *eedata_cur = eedata;
+	int i;
+	struct i2c_msg msg_write = { .addr = client->addr, .flags = 0,
+		.buf = &start_offset, .len = 1 };
+	struct i2c_msg msg_read = { .addr = client->addr, .flags = I2C_M_RD };
+
+	/* start reading at offset 0 */
+	ret = i2c_transfer(client->adapter, &msg_write, 1);
+	if (ret < 0) {
+		dev_err(dev->dev, "Can't read eeprom\n");
+		return ret;
+	}
+
+	while (len_todo > 0) {
+		msg_read.len = (len_todo > 64) ? 64 : len_todo;
+		msg_read.buf = eedata_cur;
+
+		ret = i2c_transfer(client->adapter, &msg_read, 1);
+		if (ret < 0) {
+			dev_err(dev->dev, "Can't read eeprom\n");
+			return ret;
+		}
+		eedata_cur += msg_read.len;
+		len_todo -= msg_read.len;
+	}
+
+	for (i = 0; i + 15 < len; i += 16)
+		dev_dbg(dev->dev, "i2c eeprom %02x: %*ph\n",
+			i, 16, &eedata[i]);
+
+	return 0;
+}
+
+void cx231xx_card_setup(struct cx231xx *dev)
+{
+
+	cx231xx_set_model(dev);
+
+	dev->tuner_type = cx231xx_boards[dev->model].tuner_type;
+	if (cx231xx_boards[dev->model].tuner_addr)
+		dev->tuner_addr = cx231xx_boards[dev->model].tuner_addr;
+
+	/* request some modules */
+	if (dev->board.decoder == CX231XX_AVDECODER) {
+		dev->sd_cx25840 = v4l2_i2c_new_subdev(&dev->v4l2_dev,
+					cx231xx_get_i2c_adap(dev, I2C_0),
+					"cx25840", 0x88 >> 1, NULL);
+		if (dev->sd_cx25840 == NULL)
+			dev_err(dev->dev,
+				"cx25840 subdev registration failure\n");
+		cx25840_call(dev, core, load_fw);
+
+	}
+
+	/* Initialize the tuner */
+	if (dev->board.tuner_type != TUNER_ABSENT) {
+		struct i2c_adapter *tuner_i2c = cx231xx_get_i2c_adap(dev,
+						dev->board.tuner_i2c_master);
+		dev->sd_tuner = v4l2_i2c_new_subdev(&dev->v4l2_dev,
+						    tuner_i2c,
+						    "tuner",
+						    dev->tuner_addr, NULL);
+		if (dev->sd_tuner == NULL)
+			dev_err(dev->dev,
+				"tuner subdev registration failure\n");
+		else
+			cx231xx_config_tuner(dev);
+	}
+
+	switch (dev->model) {
+	case CX231XX_BOARD_HAUPPAUGE_930C_HD_1113xx:
+	case CX231XX_BOARD_HAUPPAUGE_930C_HD_1114xx:
+	case CX231XX_BOARD_HAUPPAUGE_955Q:
+	case CX231XX_BOARD_HAUPPAUGE_935C:
+	case CX231XX_BOARD_HAUPPAUGE_975:
+		{
+			struct eeprom {
+				struct tveeprom tvee;
+				u8 eeprom[256];
+				struct i2c_client client;
+			};
+			struct eeprom *e = kzalloc(sizeof(*e), GFP_KERNEL);
+
+			if (e == NULL) {
+				dev_err(dev->dev,
+					"failed to allocate memory to read eeprom\n");
+				break;
+			}
+			e->client.adapter = cx231xx_get_i2c_adap(dev, I2C_1_MUX_1);
+			e->client.addr = 0xa0 >> 1;
+
+			read_eeprom(dev, &e->client, e->eeprom, sizeof(e->eeprom));
+			tveeprom_hauppauge_analog(&e->tvee, e->eeprom + 0xc0);
+			kfree(e);
+			break;
+		}
+	}
+
+}
+
+/*
+ * cx231xx_config()
+ * inits registers with sane defaults
+ */
+int cx231xx_config(struct cx231xx *dev)
+{
+	/* TBD need to add cx231xx specific code */
+
+	return 0;
+}
+
+/*
+ * cx231xx_config_i2c()
+ * configure i2c attached devices
+ */
+void cx231xx_config_i2c(struct cx231xx *dev)
+{
+	/* u32 input = INPUT(dev->video_input)->vmux; */
+
+	call_all(dev, video, s_stream, 1);
+}
+
+static void cx231xx_unregister_media_device(struct cx231xx *dev)
+{
+#ifdef CONFIG_MEDIA_CONTROLLER
+	if (dev->media_dev) {
+		media_device_unregister(dev->media_dev);
+		media_device_cleanup(dev->media_dev);
+		kfree(dev->media_dev);
+		dev->media_dev = NULL;
+	}
+#endif
+}
+
+/*
+ * cx231xx_realease_resources()
+ * unregisters the v4l2,i2c and usb devices
+ * called when the device gets disconected or at module unload
+*/
+void cx231xx_release_resources(struct cx231xx *dev)
+{
+	cx231xx_ir_exit(dev);
+
+	cx231xx_release_analog_resources(dev);
+
+	cx231xx_remove_from_devlist(dev);
+
+	/* Release I2C buses */
+	cx231xx_dev_uninit(dev);
+
+	/* delete v4l2 device */
+	v4l2_device_unregister(&dev->v4l2_dev);
+
+	cx231xx_unregister_media_device(dev);
+
+	usb_put_dev(dev->udev);
+
+	/* Mark device as unused */
+	clear_bit(dev->devno, &cx231xx_devused);
+}
+
+static int cx231xx_media_device_init(struct cx231xx *dev,
+				      struct usb_device *udev)
+{
+#ifdef CONFIG_MEDIA_CONTROLLER
+	struct media_device *mdev;
+
+	mdev = kzalloc(sizeof(*mdev), GFP_KERNEL);
+	if (!mdev)
+		return -ENOMEM;
+
+	media_device_usb_init(mdev, udev, dev->board.name);
+
+	dev->media_dev = mdev;
+#endif
+	return 0;
+}
+
+/*
+ * cx231xx_init_dev()
+ * allocates and inits the device structs, registers i2c bus and v4l device
+ */
+static int cx231xx_init_dev(struct cx231xx *dev, struct usb_device *udev,
+			    int minor)
+{
+	int retval = -ENOMEM;
+	unsigned int maxh, maxw;
+
+	dev->udev = udev;
+	mutex_init(&dev->lock);
+	mutex_init(&dev->ctrl_urb_lock);
+	mutex_init(&dev->gpio_i2c_lock);
+	mutex_init(&dev->i2c_lock);
+
+	spin_lock_init(&dev->video_mode.slock);
+	spin_lock_init(&dev->vbi_mode.slock);
+	spin_lock_init(&dev->sliced_cc_mode.slock);
+
+	init_waitqueue_head(&dev->open);
+	init_waitqueue_head(&dev->wait_frame);
+	init_waitqueue_head(&dev->wait_stream);
+
+	dev->cx231xx_read_ctrl_reg = cx231xx_read_ctrl_reg;
+	dev->cx231xx_write_ctrl_reg = cx231xx_write_ctrl_reg;
+	dev->cx231xx_send_usb_command = cx231xx_send_usb_command;
+	dev->cx231xx_gpio_i2c_read = cx231xx_gpio_i2c_read;
+	dev->cx231xx_gpio_i2c_write = cx231xx_gpio_i2c_write;
+
+	/* Query cx231xx to find what pcb config it is related to */
+	retval = initialize_cx231xx(dev);
+	if (retval < 0) {
+		dev_err(dev->dev, "Failed to read PCB config\n");
+		return retval;
+	}
+
+	/*To workaround error number=-71 on EP0 for VideoGrabber,
+		 need set alt here.*/
+	if (dev->model == CX231XX_BOARD_CNXT_VIDEO_GRABBER ||
+	    dev->model == CX231XX_BOARD_HAUPPAUGE_USBLIVE2) {
+		cx231xx_set_alt_setting(dev, INDEX_VIDEO, 3);
+		cx231xx_set_alt_setting(dev, INDEX_VANC, 1);
+	}
+	/* Cx231xx pre card setup */
+	cx231xx_pre_card_setup(dev);
+
+	retval = cx231xx_config(dev);
+	if (retval) {
+		dev_err(dev->dev, "error configuring device\n");
+		return -ENOMEM;
+	}
+
+	/* set default norm */
+	dev->norm = dev->board.norm;
+
+	/* register i2c bus */
+	retval = cx231xx_dev_init(dev);
+	if (retval) {
+		dev_err(dev->dev,
+			"%s: cx231xx_i2c_register - errCode [%d]!\n",
+			__func__, retval);
+		goto err_dev_init;
+	}
+
+	/* Do board specific init */
+	cx231xx_card_setup(dev);
+
+	/* configure the device */
+	cx231xx_config_i2c(dev);
+
+	maxw = norm_maxw(dev);
+	maxh = norm_maxh(dev);
+
+	/* set default image size */
+	dev->width = maxw;
+	dev->height = maxh;
+	dev->interlaced = 0;
+	dev->video_input = 0;
+
+	retval = cx231xx_config(dev);
+	if (retval) {
+		dev_err(dev->dev, "%s: cx231xx_config - errCode [%d]!\n",
+			__func__, retval);
+		goto err_dev_init;
+	}
+
+	/* init video dma queues */
+	INIT_LIST_HEAD(&dev->video_mode.vidq.active);
+	INIT_LIST_HEAD(&dev->video_mode.vidq.queued);
+
+	/* init vbi dma queues */
+	INIT_LIST_HEAD(&dev->vbi_mode.vidq.active);
+	INIT_LIST_HEAD(&dev->vbi_mode.vidq.queued);
+
+	/* Reset other chips required if they are tied up with GPIO pins */
+	cx231xx_add_into_devlist(dev);
+
+	if (dev->board.has_417) {
+		dev_info(dev->dev, "attach 417 %d\n", dev->model);
+		if (cx231xx_417_register(dev) < 0) {
+			dev_err(dev->dev,
+				"%s() Failed to register 417 on VID_B\n",
+				__func__);
+		}
+	}
+
+	retval = cx231xx_register_analog_devices(dev);
+	if (retval)
+		goto err_analog;
+
+	cx231xx_ir_init(dev);
+
+	cx231xx_init_extension(dev);
+
+	return 0;
+err_analog:
+	cx231xx_unregister_media_device(dev);
+	cx231xx_release_analog_resources(dev);
+	cx231xx_remove_from_devlist(dev);
+err_dev_init:
+	cx231xx_dev_uninit(dev);
+	return retval;
+}
+
+#if defined(CONFIG_MODULES) && defined(MODULE)
+static void request_module_async(struct work_struct *work)
+{
+	struct cx231xx *dev = container_of(work,
+					   struct cx231xx, request_module_wk);
+
+	if (dev->has_alsa_audio)
+		request_module("cx231xx-alsa");
+
+	if (dev->board.has_dvb)
+		request_module("cx231xx-dvb");
+
+}
+
+static void request_modules(struct cx231xx *dev)
+{
+	INIT_WORK(&dev->request_module_wk, request_module_async);
+	schedule_work(&dev->request_module_wk);
+}
+
+static void flush_request_modules(struct cx231xx *dev)
+{
+	flush_work(&dev->request_module_wk);
+}
+#else
+#define request_modules(dev)
+#define flush_request_modules(dev)
+#endif /* CONFIG_MODULES */
+
+static int cx231xx_init_v4l2(struct cx231xx *dev,
+			     struct usb_device *udev,
+			     struct usb_interface *interface,
+			     int isoc_pipe)
+{
+	struct usb_interface *uif;
+	int i, idx;
+
+	/* Video Init */
+
+	/* compute alternate max packet sizes for video */
+	idx = dev->current_pcb_config.hs_config_info[0].interface_info.video_index + 1;
+	if (idx >= dev->max_iad_interface_count) {
+		dev_err(dev->dev,
+			"Video PCB interface #%d doesn't exist\n", idx);
+		return -ENODEV;
+	}
+
+	uif = udev->actconfig->interface[idx];
+
+	if (uif->altsetting[0].desc.bNumEndpoints < isoc_pipe + 1)
+		return -ENODEV;
+
+	dev->video_mode.end_point_addr = uif->altsetting[0].endpoint[isoc_pipe].desc.bEndpointAddress;
+	dev->video_mode.num_alt = uif->num_altsetting;
+
+	dev_info(dev->dev,
+		 "video EndPoint Addr 0x%x, Alternate settings: %i\n",
+		 dev->video_mode.end_point_addr,
+		 dev->video_mode.num_alt);
+
+	dev->video_mode.alt_max_pkt_size = devm_kmalloc_array(&udev->dev, 32, dev->video_mode.num_alt, GFP_KERNEL);
+	if (dev->video_mode.alt_max_pkt_size == NULL)
+		return -ENOMEM;
+
+	for (i = 0; i < dev->video_mode.num_alt; i++) {
+		u16 tmp;
+
+		if (uif->altsetting[i].desc.bNumEndpoints < isoc_pipe + 1)
+			return -ENODEV;
+
+		tmp = le16_to_cpu(uif->altsetting[i].endpoint[isoc_pipe].desc.wMaxPacketSize);
+		dev->video_mode.alt_max_pkt_size[i] = (tmp & 0x07ff) * (((tmp & 0x1800) >> 11) + 1);
+		dev_dbg(dev->dev,
+			"Alternate setting %i, max size= %i\n", i,
+			dev->video_mode.alt_max_pkt_size[i]);
+	}
+
+	/* VBI Init */
+
+	idx = dev->current_pcb_config.hs_config_info[0].interface_info.vanc_index + 1;
+	if (idx >= dev->max_iad_interface_count) {
+		dev_err(dev->dev,
+			"VBI PCB interface #%d doesn't exist\n", idx);
+		return -ENODEV;
+	}
+	uif = udev->actconfig->interface[idx];
+
+	if (uif->altsetting[0].desc.bNumEndpoints < isoc_pipe + 1)
+		return -ENODEV;
+
+	dev->vbi_mode.end_point_addr =
+	    uif->altsetting[0].endpoint[isoc_pipe].desc.
+			bEndpointAddress;
+
+	dev->vbi_mode.num_alt = uif->num_altsetting;
+	dev_info(dev->dev,
+		 "VBI EndPoint Addr 0x%x, Alternate settings: %i\n",
+		 dev->vbi_mode.end_point_addr,
+		 dev->vbi_mode.num_alt);
+
+	/* compute alternate max packet sizes for vbi */
+	dev->vbi_mode.alt_max_pkt_size = devm_kmalloc_array(&udev->dev, 32, dev->vbi_mode.num_alt, GFP_KERNEL);
+	if (dev->vbi_mode.alt_max_pkt_size == NULL)
+		return -ENOMEM;
+
+	for (i = 0; i < dev->vbi_mode.num_alt; i++) {
+		u16 tmp;
+
+		if (uif->altsetting[i].desc.bNumEndpoints < isoc_pipe + 1)
+			return -ENODEV;
+
+		tmp = le16_to_cpu(uif->altsetting[i].endpoint[isoc_pipe].
+				desc.wMaxPacketSize);
+		dev->vbi_mode.alt_max_pkt_size[i] =
+		    (tmp & 0x07ff) * (((tmp & 0x1800) >> 11) + 1);
+		dev_dbg(dev->dev,
+			"Alternate setting %i, max size= %i\n", i,
+			dev->vbi_mode.alt_max_pkt_size[i]);
+	}
+
+	/* Sliced CC VBI init */
+
+	/* compute alternate max packet sizes for sliced CC */
+	idx = dev->current_pcb_config.hs_config_info[0].interface_info.hanc_index + 1;
+	if (idx >= dev->max_iad_interface_count) {
+		dev_err(dev->dev,
+			"Sliced CC PCB interface #%d doesn't exist\n", idx);
+		return -ENODEV;
+	}
+	uif = udev->actconfig->interface[idx];
+
+	if (uif->altsetting[0].desc.bNumEndpoints < isoc_pipe + 1)
+		return -ENODEV;
+
+	dev->sliced_cc_mode.end_point_addr =
+	    uif->altsetting[0].endpoint[isoc_pipe].desc.
+			bEndpointAddress;
+
+	dev->sliced_cc_mode.num_alt = uif->num_altsetting;
+	dev_info(dev->dev,
+		 "sliced CC EndPoint Addr 0x%x, Alternate settings: %i\n",
+		 dev->sliced_cc_mode.end_point_addr,
+		 dev->sliced_cc_mode.num_alt);
+	dev->sliced_cc_mode.alt_max_pkt_size = devm_kmalloc_array(&udev->dev, 32, dev->sliced_cc_mode.num_alt, GFP_KERNEL);
+	if (dev->sliced_cc_mode.alt_max_pkt_size == NULL)
+		return -ENOMEM;
+
+	for (i = 0; i < dev->sliced_cc_mode.num_alt; i++) {
+		u16 tmp;
+
+		if (uif->altsetting[i].desc.bNumEndpoints < isoc_pipe + 1)
+			return -ENODEV;
+
+		tmp = le16_to_cpu(uif->altsetting[i].endpoint[isoc_pipe].
+				desc.wMaxPacketSize);
+		dev->sliced_cc_mode.alt_max_pkt_size[i] =
+		    (tmp & 0x07ff) * (((tmp & 0x1800) >> 11) + 1);
+		dev_dbg(dev->dev,
+			"Alternate setting %i, max size= %i\n", i,
+			dev->sliced_cc_mode.alt_max_pkt_size[i]);
+	}
+
+	return 0;
+}
+
+/*
+ * cx231xx_usb_probe()
+ * checks for supported devices
+ */
+static int cx231xx_usb_probe(struct usb_interface *interface,
+			     const struct usb_device_id *id)
+{
+	struct usb_device *udev;
+	struct device *d = &interface->dev;
+	struct usb_interface *uif;
+	struct cx231xx *dev = NULL;
+	int retval = -ENODEV;
+	int nr = 0, ifnum;
+	int i, isoc_pipe = 0;
+	char *speed;
+	u8 idx;
+	struct usb_interface_assoc_descriptor *assoc_desc;
+
+	ifnum = interface->altsetting[0].desc.bInterfaceNumber;
+
+	/*
+	 * Interface number 0 - IR interface (handled by mceusb driver)
+	 * Interface number 1 - AV interface (handled by this driver)
+	 */
+	if (ifnum != 1)
+		return -ENODEV;
+
+	/* Check to see next free device and mark as used */
+	do {
+		nr = find_first_zero_bit(&cx231xx_devused, CX231XX_MAXBOARDS);
+		if (nr >= CX231XX_MAXBOARDS) {
+			/* No free device slots */
+			dev_err(d,
+				"Supports only %i devices.\n",
+				CX231XX_MAXBOARDS);
+			return -ENOMEM;
+		}
+	} while (test_and_set_bit(nr, &cx231xx_devused));
+
+	udev = usb_get_dev(interface_to_usbdev(interface));
+
+	/* allocate memory for our device state and initialize it */
+	dev = devm_kzalloc(&udev->dev, sizeof(*dev), GFP_KERNEL);
+	if (dev == NULL) {
+		retval = -ENOMEM;
+		goto err_if;
+	}
+
+	snprintf(dev->name, 29, "cx231xx #%d", nr);
+	dev->devno = nr;
+	dev->model = id->driver_info;
+	dev->video_mode.alt = -1;
+	dev->dev = d;
+
+	cx231xx_set_model(dev);
+
+	dev->interface_count++;
+	/* reset gpio dir and value */
+	dev->gpio_dir = 0;
+	dev->gpio_val = 0;
+	dev->xc_fw_load_done = 0;
+	dev->has_alsa_audio = 1;
+	dev->power_mode = -1;
+	atomic_set(&dev->devlist_count, 0);
+
+	/* 0 - vbi ; 1 -sliced cc mode */
+	dev->vbi_or_sliced_cc_mode = 0;
+
+	/* get maximum no.of IAD interfaces */
+	dev->max_iad_interface_count = udev->config->desc.bNumInterfaces;
+
+	/* init CIR module TBD */
+
+	/*mode_tv: digital=1 or analog=0*/
+	dev->mode_tv = 0;
+
+	dev->USE_ISO = transfer_mode;
+
+	switch (udev->speed) {
+	case USB_SPEED_LOW:
+		speed = "1.5";
+		break;
+	case USB_SPEED_UNKNOWN:
+	case USB_SPEED_FULL:
+		speed = "12";
+		break;
+	case USB_SPEED_HIGH:
+		speed = "480";
+		break;
+	default:
+		speed = "unknown";
+	}
+
+	dev_info(d,
+		 "New device %s %s @ %s Mbps (%04x:%04x) with %d interfaces\n",
+		 udev->manufacturer ? udev->manufacturer : "",
+		 udev->product ? udev->product : "",
+		 speed,
+		 le16_to_cpu(udev->descriptor.idVendor),
+		 le16_to_cpu(udev->descriptor.idProduct),
+		 dev->max_iad_interface_count);
+
+	/* increment interface count */
+	dev->interface_count++;
+
+	/* get device number */
+	nr = dev->devno;
+
+	assoc_desc = udev->actconfig->intf_assoc[0];
+	if (!assoc_desc || assoc_desc->bFirstInterface != ifnum) {
+		dev_err(d, "Not found matching IAD interface\n");
+		retval = -ENODEV;
+		goto err_if;
+	}
+
+	dev_dbg(d, "registering interface %d\n", ifnum);
+
+	/* save our data pointer in this interface device */
+	usb_set_intfdata(interface, dev);
+
+	/* Initialize the media controller */
+	retval = cx231xx_media_device_init(dev, udev);
+	if (retval) {
+		dev_err(d, "cx231xx_media_device_init failed\n");
+		goto err_media_init;
+	}
+
+	/* Create v4l2 device */
+#ifdef CONFIG_MEDIA_CONTROLLER
+	dev->v4l2_dev.mdev = dev->media_dev;
+#endif
+	retval = v4l2_device_register(&interface->dev, &dev->v4l2_dev);
+	if (retval) {
+		dev_err(d, "v4l2_device_register failed\n");
+		goto err_v4l2;
+	}
+
+	/* allocate device struct */
+	retval = cx231xx_init_dev(dev, udev, nr);
+	if (retval)
+		goto err_init;
+
+	retval = cx231xx_init_v4l2(dev, udev, interface, isoc_pipe);
+	if (retval)
+		goto err_init;
+
+	if (dev->current_pcb_config.ts1_source != 0xff) {
+		/* compute alternate max packet sizes for TS1 */
+		idx = dev->current_pcb_config.hs_config_info[0].interface_info.ts1_index + 1;
+		if (idx >= dev->max_iad_interface_count) {
+			dev_err(d, "TS1 PCB interface #%d doesn't exist\n",
+				idx);
+			retval = -ENODEV;
+			goto err_video_alt;
+		}
+		uif = udev->actconfig->interface[idx];
+
+		if (uif->altsetting[0].desc.bNumEndpoints < isoc_pipe + 1) {
+			retval = -ENODEV;
+			goto err_video_alt;
+		}
+
+		dev->ts1_mode.end_point_addr =
+		    uif->altsetting[0].endpoint[isoc_pipe].
+				desc.bEndpointAddress;
+
+		dev->ts1_mode.num_alt = uif->num_altsetting;
+		dev_info(d,
+			 "TS EndPoint Addr 0x%x, Alternate settings: %i\n",
+			 dev->ts1_mode.end_point_addr,
+			 dev->ts1_mode.num_alt);
+
+		dev->ts1_mode.alt_max_pkt_size = devm_kmalloc_array(&udev->dev, 32, dev->ts1_mode.num_alt, GFP_KERNEL);
+		if (dev->ts1_mode.alt_max_pkt_size == NULL) {
+			retval = -ENOMEM;
+			goto err_video_alt;
+		}
+
+		for (i = 0; i < dev->ts1_mode.num_alt; i++) {
+			u16 tmp;
+
+			if (uif->altsetting[i].desc.bNumEndpoints < isoc_pipe + 1) {
+				retval = -ENODEV;
+				goto err_video_alt;
+			}
+
+			tmp = le16_to_cpu(uif->altsetting[i].
+						endpoint[isoc_pipe].desc.
+						wMaxPacketSize);
+			dev->ts1_mode.alt_max_pkt_size[i] =
+			    (tmp & 0x07ff) * (((tmp & 0x1800) >> 11) + 1);
+			dev_dbg(d, "Alternate setting %i, max size= %i\n",
+				i, dev->ts1_mode.alt_max_pkt_size[i]);
+		}
+	}
+
+	if (dev->model == CX231XX_BOARD_CNXT_VIDEO_GRABBER) {
+		cx231xx_enable_OSC(dev);
+		cx231xx_reset_out(dev);
+		cx231xx_set_alt_setting(dev, INDEX_VIDEO, 3);
+	}
+
+	if (dev->model == CX231XX_BOARD_CNXT_RDE_253S)
+		cx231xx_sleep_s5h1432(dev);
+
+	/* load other modules required */
+	request_modules(dev);
+
+#ifdef CONFIG_MEDIA_CONTROLLER
+	/* Init entities at the Media Controller */
+	cx231xx_v4l2_create_entities(dev);
+
+	retval = v4l2_mc_create_media_graph(dev->media_dev);
+	if (!retval)
+		retval = media_device_register(dev->media_dev);
+#endif
+	if (retval < 0)
+		cx231xx_release_resources(dev);
+	return retval;
+
+err_video_alt:
+	/* cx231xx_uninit_dev: */
+	cx231xx_close_extension(dev);
+	cx231xx_ir_exit(dev);
+	cx231xx_release_analog_resources(dev);
+	cx231xx_417_unregister(dev);
+	cx231xx_remove_from_devlist(dev);
+	cx231xx_dev_uninit(dev);
+err_init:
+	v4l2_device_unregister(&dev->v4l2_dev);
+err_v4l2:
+	cx231xx_unregister_media_device(dev);
+err_media_init:
+	usb_set_intfdata(interface, NULL);
+err_if:
+	usb_put_dev(udev);
+	clear_bit(nr, &cx231xx_devused);
+	return retval;
+}
+
+/*
+ * cx231xx_usb_disconnect()
+ * called when the device gets diconencted
+ * video device will be unregistered on v4l2_close in case it is still open
+ */
+static void cx231xx_usb_disconnect(struct usb_interface *interface)
+{
+	struct cx231xx *dev;
+
+	dev = usb_get_intfdata(interface);
+	usb_set_intfdata(interface, NULL);
+
+	if (!dev)
+		return;
+
+	if (!dev->udev)
+		return;
+
+	dev->state |= DEV_DISCONNECTED;
+
+	flush_request_modules(dev);
+
+	/* wait until all current v4l2 io is finished then deallocate
+	   resources */
+	mutex_lock(&dev->lock);
+
+	wake_up_interruptible_all(&dev->open);
+
+	if (dev->users) {
+		dev_warn(dev->dev,
+			 "device %s is open! Deregistration and memory deallocation are deferred on close.\n",
+			 video_device_node_name(&dev->vdev));
+
+		/* Even having users, it is safe to remove the RC i2c driver */
+		cx231xx_ir_exit(dev);
+
+		if (dev->USE_ISO)
+			cx231xx_uninit_isoc(dev);
+		else
+			cx231xx_uninit_bulk(dev);
+		wake_up_interruptible(&dev->wait_frame);
+		wake_up_interruptible(&dev->wait_stream);
+	} else {
+	}
+
+	cx231xx_close_extension(dev);
+
+	mutex_unlock(&dev->lock);
+
+	if (!dev->users)
+		cx231xx_release_resources(dev);
+}
+
+static struct usb_driver cx231xx_usb_driver = {
+	.name = "cx231xx",
+	.probe = cx231xx_usb_probe,
+	.disconnect = cx231xx_usb_disconnect,
+	.id_table = cx231xx_id_table,
+};
+
+module_usb_driver(cx231xx_usb_driver);
diff --git a/drivers/media/usb/cx231xx/cx231xx-conf-reg.h b/drivers/media/usb/cx231xx/cx231xx-conf-reg.h
new file mode 100644
index 0000000..25593f2
--- /dev/null
+++ b/drivers/media/usb/cx231xx/cx231xx-conf-reg.h
@@ -0,0 +1,495 @@
+/*
+   cx231xx_conf-reg.h - driver for Conexant Cx23100/101/102 USB
+			video capture devices
+
+   Copyright (C) 2008 <srinivasa.deevi at conexant dot com>
+
+   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 _POLARIS_REG_H_
+#define _POLARIS_REG_H_
+
+#define BOARD_CFG_STAT          0x0
+#define TS_MODE_REG             0x4
+#define TS1_CFG_REG             0x8
+#define TS1_LENGTH_REG          0xc
+#define TS2_CFG_REG             0x10
+#define TS2_LENGTH_REG          0x14
+#define EP_MODE_SET             0x18
+#define CIR_PWR_PTN1            0x1c
+#define CIR_PWR_PTN2            0x20
+#define CIR_PWR_PTN3            0x24
+#define CIR_PWR_MASK0           0x28
+#define CIR_PWR_MASK1           0x2c
+#define CIR_PWR_MASK2           0x30
+#define CIR_GAIN                0x34
+#define CIR_CAR_REG             0x38
+#define CIR_OT_CFG1             0x40
+#define CIR_OT_CFG2             0x44
+#define GBULK_BIT_EN            0x68
+#define PWR_CTL_EN              0x74
+
+/* Polaris Endpoints capture mask for register EP_MODE_SET */
+#define ENABLE_EP1              0x01   /* Bit[0]=1 */
+#define ENABLE_EP2              0x02   /* Bit[1]=1 */
+#define ENABLE_EP3              0x04   /* Bit[2]=1 */
+#define ENABLE_EP4              0x08   /* Bit[3]=1 */
+#define ENABLE_EP5              0x10   /* Bit[4]=1 */
+#define ENABLE_EP6              0x20   /* Bit[5]=1 */
+
+/* Bit definition for register PWR_CTL_EN */
+#define PWR_MODE_MASK           0x17f
+#define PWR_AV_EN               0x08   /* bit3 */
+#define PWR_ISO_EN              0x40   /* bit6 */
+#define PWR_AV_MODE             0x30   /* bit4,5  */
+#define PWR_TUNER_EN            0x04   /* bit2 */
+#define PWR_DEMOD_EN            0x02   /* bit1 */
+#define I2C_DEMOD_EN            0x01   /* bit0 */
+#define PWR_RESETOUT_EN         0x100  /* bit8 */
+
+enum AV_MODE{
+	POLARIS_AVMODE_DEFAULT = 0,
+	POLARIS_AVMODE_DIGITAL = 0x10,
+	POLARIS_AVMODE_ANALOGT_TV = 0x20,
+	POLARIS_AVMODE_ENXTERNAL_AV = 0x30,
+
+};
+
+/* Colibri Registers */
+
+#define SINGLE_ENDED            0x0
+#define LOW_IF                  0x4
+#define EU_IF                   0x9
+#define US_IF                   0xa
+
+#define SUP_BLK_TUNE1           0x00
+#define SUP_BLK_TUNE2           0x01
+#define SUP_BLK_TUNE3           0x02
+#define SUP_BLK_XTAL            0x03
+#define SUP_BLK_PLL1            0x04
+#define SUP_BLK_PLL2            0x05
+#define SUP_BLK_PLL3            0x06
+#define SUP_BLK_REF             0x07
+#define SUP_BLK_PWRDN           0x08
+#define SUP_BLK_TESTPAD         0x09
+#define ADC_COM_INT5_STAB_REF   0x0a
+#define ADC_COM_QUANT           0x0b
+#define ADC_COM_BIAS1           0x0c
+#define ADC_COM_BIAS2           0x0d
+#define ADC_COM_BIAS3           0x0e
+#define TESTBUS_CTRL            0x12
+
+#define FLD_PWRDN_TUNING_BIAS	0x10
+#define FLD_PWRDN_ENABLE_PLL	0x08
+#define FLD_PWRDN_PD_BANDGAP	0x04
+#define FLD_PWRDN_PD_BIAS	0x02
+#define FLD_PWRDN_PD_TUNECK	0x01
+
+
+#define ADC_STATUS_CH1          0x20
+#define ADC_STATUS_CH2          0x40
+#define ADC_STATUS_CH3          0x60
+
+#define ADC_STATUS2_CH1         0x21
+#define ADC_STATUS2_CH2         0x41
+#define ADC_STATUS2_CH3         0x61
+
+#define ADC_CAL_ATEST_CH1       0x22
+#define ADC_CAL_ATEST_CH2       0x42
+#define ADC_CAL_ATEST_CH3       0x62
+
+#define ADC_PWRDN_CLAMP_CH1     0x23
+#define ADC_PWRDN_CLAMP_CH2     0x43
+#define ADC_PWRDN_CLAMP_CH3     0x63
+
+#define ADC_CTRL_DAC23_CH1      0x24
+#define ADC_CTRL_DAC23_CH2      0x44
+#define ADC_CTRL_DAC23_CH3      0x64
+
+#define ADC_CTRL_DAC1_CH1       0x25
+#define ADC_CTRL_DAC1_CH2       0x45
+#define ADC_CTRL_DAC1_CH3       0x65
+
+#define ADC_DCSERVO_DEM_CH1     0x26
+#define ADC_DCSERVO_DEM_CH2     0x46
+#define ADC_DCSERVO_DEM_CH3     0x66
+
+#define ADC_FB_FRCRST_CH1       0x27
+#define ADC_FB_FRCRST_CH2       0x47
+#define ADC_FB_FRCRST_CH3       0x67
+
+#define ADC_INPUT_CH1           0x28
+#define ADC_INPUT_CH2           0x48
+#define ADC_INPUT_CH3           0x68
+#define INPUT_SEL_MASK          0x30   /* [5:4] in_sel */
+
+#define ADC_NTF_PRECLMP_EN_CH1  0x29
+#define ADC_NTF_PRECLMP_EN_CH2  0x49
+#define ADC_NTF_PRECLMP_EN_CH3  0x69
+
+#define ADC_QGAIN_RES_TRM_CH1   0x2a
+#define ADC_QGAIN_RES_TRM_CH2   0x4a
+#define ADC_QGAIN_RES_TRM_CH3   0x6a
+
+#define ADC_SOC_PRECLMP_TERM_CH1    0x2b
+#define ADC_SOC_PRECLMP_TERM_CH2    0x4b
+#define ADC_SOC_PRECLMP_TERM_CH3    0x6b
+
+#define TESTBUS_CTRL_CH1        0x32
+#define TESTBUS_CTRL_CH2        0x52
+#define TESTBUS_CTRL_CH3        0x72
+
+/******************************************************************************
+			    * DIF registers *
+ ******************************************************************************/
+#define      DIRECT_IF_REVB_BASE  0x00300
+
+/*****************************************************************************/
+#define      DIF_PLL_FREQ_WORD        (DIRECT_IF_REVB_BASE + 0x00000000)
+/*****************************************************************************/
+#define      FLD_DIF_PLL_LOCK                           0x80000000
+/*  Reserved                                [30:29] */
+#define      FLD_DIF_PLL_FREE_RUN                       0x10000000
+#define      FLD_DIF_PLL_FREQ                           0x0fffffff
+
+/*****************************************************************************/
+#define      DIF_PLL_CTRL             (DIRECT_IF_REVB_BASE + 0x00000004)
+/*****************************************************************************/
+#define      FLD_DIF_KD_PD                              0xff000000
+/*  Reserved                             [23:20] */
+#define      FLD_DIF_KDS_PD                             0x000f0000
+#define      FLD_DIF_KI_PD                              0x0000ff00
+/*  Reserved                             [7:4] */
+#define      FLD_DIF_KIS_PD                             0x0000000f
+
+/*****************************************************************************/
+#define      DIF_PLL_CTRL1            (DIRECT_IF_REVB_BASE + 0x00000008)
+/*****************************************************************************/
+#define      FLD_DIF_KD_FD                              0xff000000
+/*  Reserved                             [23:20] */
+#define      FLD_DIF_KDS_FD                             0x000f0000
+#define      FLD_DIF_KI_FD                              0x0000ff00
+#define      FLD_DIF_SIG_PROP_SZ                        0x000000f0
+#define      FLD_DIF_KIS_FD                             0x0000000f
+
+/*****************************************************************************/
+#define      DIF_PLL_CTRL2            (DIRECT_IF_REVB_BASE + 0x0000000c)
+/*****************************************************************************/
+#define      FLD_DIF_PLL_AGC_REF                        0xfff00000
+#define      FLD_DIF_PLL_AGC_KI                         0x000f0000
+/*  Reserved                             [15] */
+#define      FLD_DIF_FREQ_LIMIT                         0x00007000
+#define      FLD_DIF_K_FD                               0x00000f00
+#define      FLD_DIF_DOWNSMPL_FD                        0x000000ff
+
+/*****************************************************************************/
+#define      DIF_PLL_CTRL3            (DIRECT_IF_REVB_BASE + 0x00000010)
+/*****************************************************************************/
+/*  Reserved                             [31:16] */
+#define      FLD_DIF_PLL_AGC_EN                         0x00008000
+/*  Reserved                             [14:12] */
+#define      FLD_DIF_PLL_MAN_GAIN                       0x00000fff
+
+/*****************************************************************************/
+#define      DIF_AGC_IF_REF           (DIRECT_IF_REVB_BASE + 0x00000014)
+/*****************************************************************************/
+#define      FLD_DIF_K_AGC_RF                           0xf0000000
+#define      FLD_DIF_K_AGC_IF                           0x0f000000
+#define      FLD_DIF_K_AGC_INT                          0x00f00000
+/*  Reserved                             [19:12] */
+#define      FLD_DIF_IF_REF                             0x00000fff
+
+/*****************************************************************************/
+#define      DIF_AGC_CTRL_IF          (DIRECT_IF_REVB_BASE + 0x00000018)
+/*****************************************************************************/
+#define      FLD_DIF_IF_MAX                             0xff000000
+#define      FLD_DIF_IF_MIN                             0x00ff0000
+#define      FLD_DIF_IF_AGC                             0x0000ffff
+
+/*****************************************************************************/
+#define      DIF_AGC_CTRL_INT         (DIRECT_IF_REVB_BASE + 0x0000001c)
+/*****************************************************************************/
+#define      FLD_DIF_INT_MAX                            0xff000000
+#define      FLD_DIF_INT_MIN                            0x00ff0000
+#define      FLD_DIF_INT_AGC                            0x0000ffff
+
+/*****************************************************************************/
+#define      DIF_AGC_CTRL_RF          (DIRECT_IF_REVB_BASE + 0x00000020)
+/*****************************************************************************/
+#define      FLD_DIF_RF_MAX                             0xff000000
+#define      FLD_DIF_RF_MIN                             0x00ff0000
+#define      FLD_DIF_RF_AGC                             0x0000ffff
+
+/*****************************************************************************/
+#define      DIF_AGC_IF_INT_CURRENT   (DIRECT_IF_REVB_BASE + 0x00000024)
+/*****************************************************************************/
+#define      FLD_DIF_IF_AGC_IN                          0xffff0000
+#define      FLD_DIF_INT_AGC_IN                         0x0000ffff
+
+/*****************************************************************************/
+#define      DIF_AGC_RF_CURRENT       (DIRECT_IF_REVB_BASE + 0x00000028)
+/*****************************************************************************/
+/*  Reserved                            [31:16] */
+#define      FLD_DIF_RF_AGC_IN                          0x0000ffff
+
+/*****************************************************************************/
+#define      DIF_VIDEO_AGC_CTRL       (DIRECT_IF_REVB_BASE + 0x0000002c)
+/*****************************************************************************/
+#define      FLD_DIF_AFD                                0xc0000000
+#define      FLD_DIF_K_VID_AGC                          0x30000000
+#define      FLD_DIF_LINE_LENGTH                        0x0fff0000
+#define      FLD_DIF_AGC_GAIN                           0x0000ffff
+
+/*****************************************************************************/
+#define      DIF_VID_AUD_OVERRIDE     (DIRECT_IF_REVB_BASE + 0x00000030)
+/*****************************************************************************/
+#define      FLD_DIF_AUDIO_AGC_OVERRIDE                 0x80000000
+/*  Reserved                             [30:30] */
+#define      FLD_DIF_AUDIO_MAN_GAIN                     0x3f000000
+/*  Reserved                             [23:17] */
+#define      FLD_DIF_VID_AGC_OVERRIDE                   0x00010000
+#define      FLD_DIF_VID_MAN_GAIN                       0x0000ffff
+
+/*****************************************************************************/
+#define      DIF_AV_SEP_CTRL          (DIRECT_IF_REVB_BASE + 0x00000034)
+/*****************************************************************************/
+#define      FLD_DIF_LPF_FREQ                           0xc0000000
+#define      FLD_DIF_AV_PHASE_INC                       0x3f000000
+#define      FLD_DIF_AUDIO_FREQ                         0x00ffffff
+
+/*****************************************************************************/
+#define      DIF_COMP_FLT_CTRL        (DIRECT_IF_REVB_BASE + 0x00000038)
+/*****************************************************************************/
+/*  Reserved                            [31:24] */
+#define      FLD_DIF_IIR23_R2                           0x00ff0000
+#define      FLD_DIF_IIR23_R1                           0x0000ff00
+#define      FLD_DIF_IIR1_R1                            0x000000ff
+
+/*****************************************************************************/
+#define      DIF_MISC_CTRL            (DIRECT_IF_REVB_BASE + 0x0000003c)
+/*****************************************************************************/
+#define      FLD_DIF_DIF_BYPASS                         0x80000000
+#define      FLD_DIF_FM_NYQ_GAIN                        0x40000000
+#define      FLD_DIF_RF_AGC_ENA                         0x20000000
+#define      FLD_DIF_INT_AGC_ENA                        0x10000000
+#define      FLD_DIF_IF_AGC_ENA                         0x08000000
+#define      FLD_DIF_FORCE_RF_IF_LOCK                   0x04000000
+#define      FLD_DIF_VIDEO_AGC_ENA                      0x02000000
+#define      FLD_DIF_RF_AGC_INV                         0x01000000
+#define      FLD_DIF_INT_AGC_INV                        0x00800000
+#define      FLD_DIF_IF_AGC_INV                         0x00400000
+#define      FLD_DIF_SPEC_INV                           0x00200000
+#define      FLD_DIF_AUD_FULL_BW                        0x00100000
+#define      FLD_DIF_AUD_SRC_SEL                        0x00080000
+/*  Reserved                             [18] */
+#define      FLD_DIF_IF_FREQ                            0x00030000
+/*  Reserved                             [15:14] */
+#define      FLD_DIF_TIP_OFFSET                         0x00003f00
+/*  Reserved                             [7:5] */
+#define      FLD_DIF_DITHER_ENA                         0x00000010
+/*  Reserved                             [3:1] */
+#define      FLD_DIF_RF_IF_LOCK                         0x00000001
+
+/*****************************************************************************/
+#define      DIF_SRC_PHASE_INC        (DIRECT_IF_REVB_BASE + 0x00000040)
+/*****************************************************************************/
+/*  Reserved                             [31:29] */
+#define      FLD_DIF_PHASE_INC                          0x1fffffff
+
+/*****************************************************************************/
+#define      DIF_SRC_GAIN_CONTROL     (DIRECT_IF_REVB_BASE + 0x00000044)
+/*****************************************************************************/
+/*  Reserved                             [31:16] */
+#define      FLD_DIF_SRC_KI                             0x0000ff00
+#define      FLD_DIF_SRC_KD                             0x000000ff
+
+/*****************************************************************************/
+#define      DIF_BPF_COEFF01          (DIRECT_IF_REVB_BASE + 0x00000048)
+/*****************************************************************************/
+/*  Reserved                             [31:19] */
+#define      FLD_DIF_BPF_COEFF_0                        0x00070000
+/*  Reserved                             [15:4] */
+#define      FLD_DIF_BPF_COEFF_1                        0x0000000f
+
+/*****************************************************************************/
+#define      DIF_BPF_COEFF23          (DIRECT_IF_REVB_BASE + 0x0000004c)
+/*****************************************************************************/
+/*  Reserved                             [31:22] */
+#define      FLD_DIF_BPF_COEFF_2                        0x003f0000
+/*  Reserved                             [15:7] */
+#define      FLD_DIF_BPF_COEFF_3                        0x0000007f
+
+/*****************************************************************************/
+#define      DIF_BPF_COEFF45          (DIRECT_IF_REVB_BASE + 0x00000050)
+/*****************************************************************************/
+/*  Reserved                             [31:24] */
+#define      FLD_DIF_BPF_COEFF_4                        0x00ff0000
+/*  Reserved                             [15:8] */
+#define      FLD_DIF_BPF_COEFF_5                        0x000000ff
+
+/*****************************************************************************/
+#define      DIF_BPF_COEFF67          (DIRECT_IF_REVB_BASE + 0x00000054)
+/*****************************************************************************/
+/*  Reserved                             [31:25] */
+#define      FLD_DIF_BPF_COEFF_6                        0x01ff0000
+/*  Reserved                             [15:9] */
+#define      FLD_DIF_BPF_COEFF_7                        0x000001ff
+
+/*****************************************************************************/
+#define      DIF_BPF_COEFF89          (DIRECT_IF_REVB_BASE + 0x00000058)
+/*****************************************************************************/
+/*  Reserved                             [31:26] */
+#define      FLD_DIF_BPF_COEFF_8                        0x03ff0000
+/*  Reserved                             [15:10] */
+#define      FLD_DIF_BPF_COEFF_9                        0x000003ff
+
+/*****************************************************************************/
+#define      DIF_BPF_COEFF1011        (DIRECT_IF_REVB_BASE + 0x0000005c)
+/*****************************************************************************/
+/*  Reserved                             [31:27] */
+#define      FLD_DIF_BPF_COEFF_10                       0x07ff0000
+/*  Reserved                             [15:11] */
+#define      FLD_DIF_BPF_COEFF_11                       0x000007ff
+
+/*****************************************************************************/
+#define      DIF_BPF_COEFF1213        (DIRECT_IF_REVB_BASE + 0x00000060)
+/*****************************************************************************/
+/*  Reserved                             [31:27] */
+#define      FLD_DIF_BPF_COEFF_12                       0x07ff0000
+/*  Reserved                             [15:12] */
+#define      FLD_DIF_BPF_COEFF_13                       0x00000fff
+
+/*****************************************************************************/
+#define      DIF_BPF_COEFF1415        (DIRECT_IF_REVB_BASE + 0x00000064)
+/*****************************************************************************/
+/*  Reserved                             [31:28] */
+#define      FLD_DIF_BPF_COEFF_14                       0x0fff0000
+/*  Reserved                             [15:12] */
+#define      FLD_DIF_BPF_COEFF_15                       0x00000fff
+
+/*****************************************************************************/
+#define      DIF_BPF_COEFF1617        (DIRECT_IF_REVB_BASE + 0x00000068)
+/*****************************************************************************/
+/*  Reserved                             [31:29] */
+#define      FLD_DIF_BPF_COEFF_16                       0x1fff0000
+/*  Reserved                             [15:13] */
+#define      FLD_DIF_BPF_COEFF_17                       0x00001fff
+
+/*****************************************************************************/
+#define      DIF_BPF_COEFF1819        (DIRECT_IF_REVB_BASE + 0x0000006c)
+/*****************************************************************************/
+/*  Reserved                             [31:29] */
+#define      FLD_DIF_BPF_COEFF_18                       0x1fff0000
+/*  Reserved                             [15:13] */
+#define      FLD_DIF_BPF_COEFF_19                       0x00001fff
+
+/*****************************************************************************/
+#define      DIF_BPF_COEFF2021        (DIRECT_IF_REVB_BASE + 0x00000070)
+/*****************************************************************************/
+/*  Reserved                             [31:29] */
+#define      FLD_DIF_BPF_COEFF_20                       0x1fff0000
+/*  Reserved                             [15:14] */
+#define      FLD_DIF_BPF_COEFF_21                       0x00003fff
+
+/*****************************************************************************/
+#define      DIF_BPF_COEFF2223        (DIRECT_IF_REVB_BASE + 0x00000074)
+/*****************************************************************************/
+/*  Reserved                             [31:30] */
+#define      FLD_DIF_BPF_COEFF_22                       0x3fff0000
+/*  Reserved                             [15:14] */
+#define      FLD_DIF_BPF_COEFF_23                       0x00003fff
+
+/*****************************************************************************/
+#define      DIF_BPF_COEFF2425        (DIRECT_IF_REVB_BASE + 0x00000078)
+/*****************************************************************************/
+/*  Reserved                             [31:30] */
+#define      FLD_DIF_BPF_COEFF_24                       0x3fff0000
+/*  Reserved                             [15:14] */
+#define      FLD_DIF_BPF_COEFF_25                       0x00003fff
+
+/*****************************************************************************/
+#define      DIF_BPF_COEFF2627        (DIRECT_IF_REVB_BASE + 0x0000007c)
+/*****************************************************************************/
+/*  Reserved                             [31:30] */
+#define      FLD_DIF_BPF_COEFF_26                       0x3fff0000
+/*  Reserved                             [15:14] */
+#define      FLD_DIF_BPF_COEFF_27                       0x00003fff
+
+/*****************************************************************************/
+#define      DIF_BPF_COEFF2829        (DIRECT_IF_REVB_BASE + 0x00000080)
+/*****************************************************************************/
+/*  Reserved                             [31:30] */
+#define      FLD_DIF_BPF_COEFF_28                       0x3fff0000
+/*  Reserved                             [15:14] */
+#define      FLD_DIF_BPF_COEFF_29                       0x00003fff
+
+/*****************************************************************************/
+#define      DIF_BPF_COEFF3031        (DIRECT_IF_REVB_BASE + 0x00000084)
+/*****************************************************************************/
+/*  Reserved                             [31:30] */
+#define      FLD_DIF_BPF_COEFF_30                       0x3fff0000
+/*  Reserved                             [15:14] */
+#define      FLD_DIF_BPF_COEFF_31                       0x00003fff
+
+/*****************************************************************************/
+#define      DIF_BPF_COEFF3233        (DIRECT_IF_REVB_BASE + 0x00000088)
+/*****************************************************************************/
+/*  Reserved                             [31:30] */
+#define      FLD_DIF_BPF_COEFF_32                       0x3fff0000
+/*  Reserved                             [15:14] */
+#define      FLD_DIF_BPF_COEFF_33                       0x00003fff
+
+/*****************************************************************************/
+#define      DIF_BPF_COEFF3435        (DIRECT_IF_REVB_BASE + 0x0000008c)
+/*****************************************************************************/
+/*  Reserved                             [31:30] */
+#define      FLD_DIF_BPF_COEFF_34                       0x3fff0000
+/*  Reserved                             [15:14] */
+#define      FLD_DIF_BPF_COEFF_35                       0x00003fff
+
+/*****************************************************************************/
+#define      DIF_BPF_COEFF36          (DIRECT_IF_REVB_BASE + 0x00000090)
+/*****************************************************************************/
+/*  Reserved                             [31:30] */
+#define      FLD_DIF_BPF_COEFF_36                       0x3fff0000
+/*  Reserved                             [15:0] */
+
+/*****************************************************************************/
+#define      DIF_RPT_VARIANCE         (DIRECT_IF_REVB_BASE + 0x00000094)
+/*****************************************************************************/
+/*  Reserved                             [31:20] */
+#define      FLD_DIF_RPT_VARIANCE                       0x000fffff
+
+/*****************************************************************************/
+#define      DIF_SOFT_RST_CTRL_REVB       (DIRECT_IF_REVB_BASE + 0x00000098)
+/*****************************************************************************/
+/*  Reserved                             [31:8] */
+#define      FLD_DIF_DIF_SOFT_RST                       0x00000080
+#define      FLD_DIF_DIF_REG_RST_MSK                    0x00000040
+#define      FLD_DIF_AGC_RST_MSK                        0x00000020
+#define      FLD_DIF_CMP_RST_MSK                        0x00000010
+#define      FLD_DIF_AVS_RST_MSK                        0x00000008
+#define      FLD_DIF_NYQ_RST_MSK                        0x00000004
+#define      FLD_DIF_DIF_SRC_RST_MSK                    0x00000002
+#define      FLD_DIF_PLL_RST_MSK                        0x00000001
+
+/*****************************************************************************/
+#define      DIF_PLL_FREQ_ERR         (DIRECT_IF_REVB_BASE + 0x0000009c)
+/*****************************************************************************/
+/*  Reserved                             [31:25] */
+#define      FLD_DIF_CTL_IP                             0x01ffffff
+
+#endif
diff --git a/drivers/media/usb/cx231xx/cx231xx-core.c b/drivers/media/usb/cx231xx/cx231xx-core.c
new file mode 100644
index 0000000..493c2dc
--- /dev/null
+++ b/drivers/media/usb/cx231xx/cx231xx-core.c
@@ -0,0 +1,1800 @@
+/*
+   cx231xx-core.c - driver for Conexant Cx23100/101/102
+				USB video capture devices
+
+   Copyright (C) 2008 <srinivasa.deevi at conexant dot com>
+				Based on em28xx driver
+
+   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.
+ */
+
+#include "cx231xx.h"
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <media/v4l2-common.h>
+#include <media/tuner.h>
+
+#include "cx231xx-reg.h"
+
+/* #define ENABLE_DEBUG_ISOC_FRAMES */
+
+static unsigned int core_debug;
+module_param(core_debug, int, 0644);
+MODULE_PARM_DESC(core_debug, "enable debug messages [core]");
+
+#define cx231xx_coredbg(fmt, arg...) do {\
+	if (core_debug) \
+		printk(KERN_INFO "%s %s :"fmt, \
+			 dev->name, __func__ , ##arg); } while (0)
+
+static unsigned int reg_debug;
+module_param(reg_debug, int, 0644);
+MODULE_PARM_DESC(reg_debug, "enable debug messages [URB reg]");
+
+static int alt = CX231XX_PINOUT;
+module_param(alt, int, 0644);
+MODULE_PARM_DESC(alt, "alternate setting to use for video endpoint");
+
+#define cx231xx_isocdbg(fmt, arg...) do {\
+	if (core_debug) \
+		printk(KERN_INFO "%s %s :"fmt, \
+			 dev->name, __func__ , ##arg); } while (0)
+
+/*****************************************************************
+*             Device control list functions					 *
+******************************************************************/
+
+LIST_HEAD(cx231xx_devlist);
+static DEFINE_MUTEX(cx231xx_devlist_mutex);
+
+/*
+ * cx231xx_realease_resources()
+ * unregisters the v4l2,i2c and usb devices
+ * called when the device gets disconected or at module unload
+*/
+void cx231xx_remove_from_devlist(struct cx231xx *dev)
+{
+	if (dev == NULL)
+		return;
+	if (dev->udev == NULL)
+		return;
+
+	if (atomic_read(&dev->devlist_count) > 0) {
+		mutex_lock(&cx231xx_devlist_mutex);
+		list_del(&dev->devlist);
+		atomic_dec(&dev->devlist_count);
+		mutex_unlock(&cx231xx_devlist_mutex);
+	}
+};
+
+void cx231xx_add_into_devlist(struct cx231xx *dev)
+{
+	mutex_lock(&cx231xx_devlist_mutex);
+	list_add_tail(&dev->devlist, &cx231xx_devlist);
+	atomic_inc(&dev->devlist_count);
+	mutex_unlock(&cx231xx_devlist_mutex);
+};
+
+static LIST_HEAD(cx231xx_extension_devlist);
+
+int cx231xx_register_extension(struct cx231xx_ops *ops)
+{
+	struct cx231xx *dev = NULL;
+
+	mutex_lock(&cx231xx_devlist_mutex);
+	list_add_tail(&ops->next, &cx231xx_extension_devlist);
+	list_for_each_entry(dev, &cx231xx_devlist, devlist) {
+		ops->init(dev);
+		dev_info(dev->dev, "%s initialized\n", ops->name);
+	}
+	mutex_unlock(&cx231xx_devlist_mutex);
+	return 0;
+}
+EXPORT_SYMBOL(cx231xx_register_extension);
+
+void cx231xx_unregister_extension(struct cx231xx_ops *ops)
+{
+	struct cx231xx *dev = NULL;
+
+	mutex_lock(&cx231xx_devlist_mutex);
+	list_for_each_entry(dev, &cx231xx_devlist, devlist) {
+		ops->fini(dev);
+		dev_info(dev->dev, "%s removed\n", ops->name);
+	}
+
+	list_del(&ops->next);
+	mutex_unlock(&cx231xx_devlist_mutex);
+}
+EXPORT_SYMBOL(cx231xx_unregister_extension);
+
+void cx231xx_init_extension(struct cx231xx *dev)
+{
+	struct cx231xx_ops *ops = NULL;
+
+	mutex_lock(&cx231xx_devlist_mutex);
+	if (!list_empty(&cx231xx_extension_devlist)) {
+		list_for_each_entry(ops, &cx231xx_extension_devlist, next) {
+			if (ops->init)
+				ops->init(dev);
+		}
+	}
+	mutex_unlock(&cx231xx_devlist_mutex);
+}
+
+void cx231xx_close_extension(struct cx231xx *dev)
+{
+	struct cx231xx_ops *ops = NULL;
+
+	mutex_lock(&cx231xx_devlist_mutex);
+	if (!list_empty(&cx231xx_extension_devlist)) {
+		list_for_each_entry(ops, &cx231xx_extension_devlist, next) {
+			if (ops->fini)
+				ops->fini(dev);
+		}
+	}
+	mutex_unlock(&cx231xx_devlist_mutex);
+}
+
+/****************************************************************
+*               U S B related functions                         *
+*****************************************************************/
+int cx231xx_send_usb_command(struct cx231xx_i2c *i2c_bus,
+			     struct cx231xx_i2c_xfer_data *req_data)
+{
+	int status = 0;
+	struct cx231xx *dev = i2c_bus->dev;
+	struct VENDOR_REQUEST_IN ven_req;
+
+	u8 saddr_len = 0;
+	u8 _i2c_period = 0;
+	u8 _i2c_nostop = 0;
+	u8 _i2c_reserve = 0;
+
+	if (dev->state & DEV_DISCONNECTED)
+		return -ENODEV;
+
+	/* Get the I2C period, nostop and reserve parameters */
+	_i2c_period = i2c_bus->i2c_period;
+	_i2c_nostop = i2c_bus->i2c_nostop;
+	_i2c_reserve = i2c_bus->i2c_reserve;
+
+	saddr_len = req_data->saddr_len;
+
+	/* Set wValue */
+	ven_req.wValue = (req_data->dev_addr << 9 | _i2c_period << 4 |
+			  saddr_len << 2 | _i2c_nostop << 1 | I2C_SYNC |
+			  _i2c_reserve << 6);
+
+	/* set channel number */
+	if (req_data->direction & I2C_M_RD) {
+		/* channel number, for read,spec required channel_num +4 */
+		ven_req.bRequest = i2c_bus->nr + 4;
+	} else
+		ven_req.bRequest = i2c_bus->nr;	/* channel number,  */
+
+	/* set index value */
+	switch (saddr_len) {
+	case 0:
+		ven_req.wIndex = 0;	/* need check */
+		break;
+	case 1:
+		ven_req.wIndex = (req_data->saddr_dat & 0xff);
+		break;
+	case 2:
+		ven_req.wIndex = req_data->saddr_dat;
+		break;
+	}
+
+	/* set wLength value */
+	ven_req.wLength = req_data->buf_size;
+
+	/* set bData value */
+	ven_req.bData = 0;
+
+	/* set the direction */
+	if (req_data->direction) {
+		ven_req.direction = USB_DIR_IN;
+		memset(req_data->p_buffer, 0x00, ven_req.wLength);
+	} else
+		ven_req.direction = USB_DIR_OUT;
+
+	/* set the buffer for read / write */
+	ven_req.pBuff = req_data->p_buffer;
+
+
+	/* call common vendor command request */
+	status = cx231xx_send_vendor_cmd(dev, &ven_req);
+	if (status < 0 && !dev->i2c_scan_running) {
+		dev_err(dev->dev, "%s: failed with status -%d\n",
+			__func__, status);
+	}
+
+	return status;
+}
+EXPORT_SYMBOL_GPL(cx231xx_send_usb_command);
+
+/*
+ * Sends/Receives URB control messages, assuring to use a kalloced buffer
+ * for all operations (dev->urb_buf), to avoid using stacked buffers, as
+ * they aren't safe for usage with USB, due to DMA restrictions.
+ * Also implements the debug code for control URB's.
+ */
+static int __usb_control_msg(struct cx231xx *dev, unsigned int pipe,
+	__u8 request, __u8 requesttype, __u16 value, __u16 index,
+	void *data, __u16 size, int timeout)
+{
+	int rc, i;
+
+	if (reg_debug) {
+		printk(KERN_DEBUG "%s: (pipe 0x%08x): %s:  %02x %02x %02x %02x %02x %02x %02x %02x ",
+				dev->name,
+				pipe,
+				(requesttype & USB_DIR_IN) ? "IN" : "OUT",
+				requesttype,
+				request,
+				value & 0xff, value >> 8,
+				index & 0xff, index >> 8,
+				size & 0xff, size >> 8);
+		if (!(requesttype & USB_DIR_IN)) {
+			printk(KERN_CONT ">>>");
+			for (i = 0; i < size; i++)
+				printk(KERN_CONT " %02x",
+				       ((unsigned char *)data)[i]);
+		}
+	}
+
+	/* Do the real call to usb_control_msg */
+	mutex_lock(&dev->ctrl_urb_lock);
+	if (!(requesttype & USB_DIR_IN) && size)
+		memcpy(dev->urb_buf, data, size);
+	rc = usb_control_msg(dev->udev, pipe, request, requesttype, value,
+			     index, dev->urb_buf, size, timeout);
+	if ((requesttype & USB_DIR_IN) && size)
+		memcpy(data, dev->urb_buf, size);
+	mutex_unlock(&dev->ctrl_urb_lock);
+
+	if (reg_debug) {
+		if (unlikely(rc < 0)) {
+			printk(KERN_CONT "FAILED!\n");
+			return rc;
+		}
+
+		if ((requesttype & USB_DIR_IN)) {
+			printk(KERN_CONT "<<<");
+			for (i = 0; i < size; i++)
+				printk(KERN_CONT " %02x",
+				       ((unsigned char *)data)[i]);
+		}
+		printk(KERN_CONT "\n");
+	}
+
+	return rc;
+}
+
+
+/*
+ * cx231xx_read_ctrl_reg()
+ * reads data from the usb device specifying bRequest and wValue
+ */
+int cx231xx_read_ctrl_reg(struct cx231xx *dev, u8 req, u16 reg,
+			  char *buf, int len)
+{
+	u8 val = 0;
+	int ret;
+	int pipe = usb_rcvctrlpipe(dev->udev, 0);
+
+	if (dev->state & DEV_DISCONNECTED)
+		return -ENODEV;
+
+	if (len > URB_MAX_CTRL_SIZE)
+		return -EINVAL;
+
+	switch (len) {
+	case 1:
+		val = ENABLE_ONE_BYTE;
+		break;
+	case 2:
+		val = ENABLE_TWE_BYTE;
+		break;
+	case 3:
+		val = ENABLE_THREE_BYTE;
+		break;
+	case 4:
+		val = ENABLE_FOUR_BYTE;
+		break;
+	default:
+		val = 0xFF;	/* invalid option */
+	}
+
+	if (val == 0xFF)
+		return -EINVAL;
+
+	ret = __usb_control_msg(dev, pipe, req,
+			      USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      val, reg, buf, len, HZ);
+	return ret;
+}
+
+int cx231xx_send_vendor_cmd(struct cx231xx *dev,
+				struct VENDOR_REQUEST_IN *ven_req)
+{
+	int ret;
+	int pipe = 0;
+	int unsend_size = 0;
+	u8 *pdata;
+
+	if (dev->state & DEV_DISCONNECTED)
+		return -ENODEV;
+
+	if ((ven_req->wLength > URB_MAX_CTRL_SIZE))
+		return -EINVAL;
+
+	if (ven_req->direction)
+		pipe = usb_rcvctrlpipe(dev->udev, 0);
+	else
+		pipe = usb_sndctrlpipe(dev->udev, 0);
+
+	/*
+	 * If the cx23102 read more than 4 bytes with i2c bus,
+	 * need chop to 4 byte per request
+	 */
+	if ((ven_req->wLength > 4) && ((ven_req->bRequest == 0x4) ||
+					(ven_req->bRequest == 0x5) ||
+					(ven_req->bRequest == 0x6) ||
+
+					/* Internal Master 3 Bus can send
+					 * and receive only 4 bytes per time
+					 */
+					(ven_req->bRequest == 0x2))) {
+		unsend_size = 0;
+		pdata = ven_req->pBuff;
+
+
+		unsend_size = ven_req->wLength;
+
+		/* the first package */
+		ven_req->wValue = ven_req->wValue & 0xFFFB;
+		ven_req->wValue = (ven_req->wValue & 0xFFBD) | 0x2;
+		ret = __usb_control_msg(dev, pipe, ven_req->bRequest,
+			ven_req->direction | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			ven_req->wValue, ven_req->wIndex, pdata,
+			0x0004, HZ);
+		unsend_size = unsend_size - 4;
+
+		/* the middle package */
+		ven_req->wValue = (ven_req->wValue & 0xFFBD) | 0x42;
+		while (unsend_size - 4 > 0) {
+			pdata = pdata + 4;
+			ret = __usb_control_msg(dev, pipe,
+				ven_req->bRequest,
+				ven_req->direction | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+				ven_req->wValue, ven_req->wIndex, pdata,
+				0x0004, HZ);
+			unsend_size = unsend_size - 4;
+		}
+
+		/* the last package */
+		ven_req->wValue = (ven_req->wValue & 0xFFBD) | 0x40;
+		pdata = pdata + 4;
+		ret = __usb_control_msg(dev, pipe, ven_req->bRequest,
+			ven_req->direction | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			ven_req->wValue, ven_req->wIndex, pdata,
+			unsend_size, HZ);
+	} else {
+		ret = __usb_control_msg(dev, pipe, ven_req->bRequest,
+				ven_req->direction | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+				ven_req->wValue, ven_req->wIndex,
+				ven_req->pBuff, ven_req->wLength, HZ);
+	}
+
+	return ret;
+}
+
+/*
+ * cx231xx_write_ctrl_reg()
+ * sends data to the usb device, specifying bRequest
+ */
+int cx231xx_write_ctrl_reg(struct cx231xx *dev, u8 req, u16 reg, char *buf,
+			   int len)
+{
+	u8 val = 0;
+	int ret;
+	int pipe = usb_sndctrlpipe(dev->udev, 0);
+
+	if (dev->state & DEV_DISCONNECTED)
+		return -ENODEV;
+
+	if ((len < 1) || (len > URB_MAX_CTRL_SIZE))
+		return -EINVAL;
+
+	switch (len) {
+	case 1:
+		val = ENABLE_ONE_BYTE;
+		break;
+	case 2:
+		val = ENABLE_TWE_BYTE;
+		break;
+	case 3:
+		val = ENABLE_THREE_BYTE;
+		break;
+	case 4:
+		val = ENABLE_FOUR_BYTE;
+		break;
+	default:
+		val = 0xFF;	/* invalid option */
+	}
+
+	if (val == 0xFF)
+		return -EINVAL;
+
+	if (reg_debug) {
+		int byte;
+
+		cx231xx_isocdbg("(pipe 0x%08x): OUT: %02x %02x %02x %02x %02x %02x %02x %02x >>>",
+			pipe,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			req, 0, val, reg & 0xff,
+			reg >> 8, len & 0xff, len >> 8);
+
+		for (byte = 0; byte < len; byte++)
+			cx231xx_isocdbg(" %02x", (unsigned char)buf[byte]);
+		cx231xx_isocdbg("\n");
+	}
+
+	ret = __usb_control_msg(dev, pipe, req,
+			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      val, reg, buf, len, HZ);
+
+	return ret;
+}
+
+/****************************************************************
+*           USB Alternate Setting functions                     *
+*****************************************************************/
+
+int cx231xx_set_video_alternate(struct cx231xx *dev)
+{
+	int errCode, prev_alt = dev->video_mode.alt;
+	unsigned int min_pkt_size = dev->width * 2 + 4;
+	u32 usb_interface_index = 0;
+
+	/* When image size is bigger than a certain value,
+	   the frame size should be increased, otherwise, only
+	   green screen will be received.
+	 */
+	if (dev->width * 2 * dev->height > 720 * 240 * 2)
+		min_pkt_size *= 2;
+
+	if (dev->width > 360) {
+		/* resolutions: 720,704,640 */
+		dev->video_mode.alt = 3;
+	} else if (dev->width > 180) {
+		/* resolutions: 360,352,320,240 */
+		dev->video_mode.alt = 2;
+	} else if (dev->width > 0) {
+		/* resolutions: 180,176,160,128,88 */
+		dev->video_mode.alt = 1;
+	} else {
+		/* Change to alt0 BULK to release USB bandwidth */
+		dev->video_mode.alt = 0;
+	}
+
+	if (dev->USE_ISO == 0)
+		dev->video_mode.alt = 0;
+
+	cx231xx_coredbg("dev->video_mode.alt= %d\n", dev->video_mode.alt);
+
+	/* Get the correct video interface Index */
+	usb_interface_index =
+	    dev->current_pcb_config.hs_config_info[0].interface_info.
+	    video_index + 1;
+
+	if (dev->video_mode.alt != prev_alt) {
+		cx231xx_coredbg("minimum isoc packet size: %u (alt=%d)\n",
+				min_pkt_size, dev->video_mode.alt);
+
+		if (dev->video_mode.alt_max_pkt_size != NULL)
+			dev->video_mode.max_pkt_size =
+			dev->video_mode.alt_max_pkt_size[dev->video_mode.alt];
+		cx231xx_coredbg("setting alternate %d with wMaxPacketSize=%u\n",
+				dev->video_mode.alt,
+				dev->video_mode.max_pkt_size);
+		errCode =
+		    usb_set_interface(dev->udev, usb_interface_index,
+				      dev->video_mode.alt);
+		if (errCode < 0) {
+			dev_err(dev->dev,
+				"cannot change alt number to %d (error=%i)\n",
+				dev->video_mode.alt, errCode);
+			return errCode;
+		}
+	}
+	return 0;
+}
+
+int cx231xx_set_alt_setting(struct cx231xx *dev, u8 index, u8 alt)
+{
+	int status = 0;
+	u32 usb_interface_index = 0;
+	u32 max_pkt_size = 0;
+
+	switch (index) {
+	case INDEX_TS1:
+		usb_interface_index =
+		    dev->current_pcb_config.hs_config_info[0].interface_info.
+		    ts1_index + 1;
+		dev->ts1_mode.alt = alt;
+		if (dev->ts1_mode.alt_max_pkt_size != NULL)
+			max_pkt_size = dev->ts1_mode.max_pkt_size =
+			    dev->ts1_mode.alt_max_pkt_size[dev->ts1_mode.alt];
+		break;
+	case INDEX_TS2:
+		usb_interface_index =
+		    dev->current_pcb_config.hs_config_info[0].interface_info.
+		    ts2_index + 1;
+		break;
+	case INDEX_AUDIO:
+		usb_interface_index =
+		    dev->current_pcb_config.hs_config_info[0].interface_info.
+		    audio_index + 1;
+		dev->adev.alt = alt;
+		if (dev->adev.alt_max_pkt_size != NULL)
+			max_pkt_size = dev->adev.max_pkt_size =
+			    dev->adev.alt_max_pkt_size[dev->adev.alt];
+		break;
+	case INDEX_VIDEO:
+		usb_interface_index =
+		    dev->current_pcb_config.hs_config_info[0].interface_info.
+		    video_index + 1;
+		dev->video_mode.alt = alt;
+		if (dev->video_mode.alt_max_pkt_size != NULL)
+			max_pkt_size = dev->video_mode.max_pkt_size =
+			    dev->video_mode.alt_max_pkt_size[dev->video_mode.
+							     alt];
+		break;
+	case INDEX_VANC:
+		if (dev->board.no_alt_vanc)
+			return 0;
+		usb_interface_index =
+		    dev->current_pcb_config.hs_config_info[0].interface_info.
+		    vanc_index + 1;
+		dev->vbi_mode.alt = alt;
+		if (dev->vbi_mode.alt_max_pkt_size != NULL)
+			max_pkt_size = dev->vbi_mode.max_pkt_size =
+			    dev->vbi_mode.alt_max_pkt_size[dev->vbi_mode.alt];
+		break;
+	case INDEX_HANC:
+		usb_interface_index =
+		    dev->current_pcb_config.hs_config_info[0].interface_info.
+		    hanc_index + 1;
+		dev->sliced_cc_mode.alt = alt;
+		if (dev->sliced_cc_mode.alt_max_pkt_size != NULL)
+			max_pkt_size = dev->sliced_cc_mode.max_pkt_size =
+			    dev->sliced_cc_mode.alt_max_pkt_size[dev->
+								 sliced_cc_mode.
+								 alt];
+		break;
+	default:
+		break;
+	}
+
+	if (alt > 0 && max_pkt_size == 0) {
+		dev_err(dev->dev,
+			"can't change interface %d alt no. to %d: Max. Pkt size = 0\n",
+			usb_interface_index, alt);
+		/*To workaround error number=-71 on EP0 for videograbber,
+		 need add following codes.*/
+		if (dev->board.no_alt_vanc)
+			return -1;
+	}
+
+	cx231xx_coredbg("setting alternate %d with wMaxPacketSize=%u,Interface = %d\n",
+			alt, max_pkt_size,
+			usb_interface_index);
+
+	if (usb_interface_index > 0) {
+		status = usb_set_interface(dev->udev, usb_interface_index, alt);
+		if (status < 0) {
+			dev_err(dev->dev,
+				"can't change interface %d alt no. to %d (err=%i)\n",
+				usb_interface_index, alt, status);
+			return status;
+		}
+	}
+
+	return status;
+}
+EXPORT_SYMBOL_GPL(cx231xx_set_alt_setting);
+
+int cx231xx_gpio_set(struct cx231xx *dev, struct cx231xx_reg_seq *gpio)
+{
+	int rc = 0;
+
+	if (!gpio)
+		return rc;
+
+	/* Send GPIO reset sequences specified at board entry */
+	while (gpio->sleep >= 0) {
+		rc = cx231xx_set_gpio_value(dev, gpio->bit, gpio->val);
+		if (rc < 0)
+			return rc;
+
+		if (gpio->sleep > 0)
+			msleep(gpio->sleep);
+
+		gpio++;
+	}
+	return rc;
+}
+
+int cx231xx_demod_reset(struct cx231xx *dev)
+{
+
+	u8 status = 0;
+	u8 value[4] = { 0, 0, 0, 0 };
+
+	status = cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, PWR_CTL_EN,
+				 value, 4);
+
+	cx231xx_coredbg("reg0x%x=0x%x 0x%x 0x%x 0x%x\n", PWR_CTL_EN,
+			value[0], value[1], value[2], value[3]);
+
+	cx231xx_coredbg("Enter cx231xx_demod_reset()\n");
+
+	value[1] = (u8) 0x3;
+	status = cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER,
+					PWR_CTL_EN, value, 4);
+	msleep(10);
+
+	value[1] = (u8) 0x0;
+	status = cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER,
+					PWR_CTL_EN, value, 4);
+	msleep(10);
+
+	value[1] = (u8) 0x3;
+	status = cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER,
+					PWR_CTL_EN, value, 4);
+	msleep(10);
+
+	status = cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, PWR_CTL_EN,
+				 value, 4);
+
+	cx231xx_coredbg("reg0x%x=0x%x 0x%x 0x%x 0x%x\n", PWR_CTL_EN,
+			value[0], value[1], value[2], value[3]);
+
+	return status;
+}
+EXPORT_SYMBOL_GPL(cx231xx_demod_reset);
+int is_fw_load(struct cx231xx *dev)
+{
+	return cx231xx_check_fw(dev);
+}
+EXPORT_SYMBOL_GPL(is_fw_load);
+
+int cx231xx_set_mode(struct cx231xx *dev, enum cx231xx_mode set_mode)
+{
+	int errCode = 0;
+
+	if (dev->mode == set_mode)
+		return 0;
+
+	if (set_mode == CX231XX_SUSPEND) {
+		/* Set the chip in power saving mode */
+		dev->mode = set_mode;
+	}
+
+	/* Resource is locked */
+	if (dev->mode != CX231XX_SUSPEND)
+		return -EINVAL;
+
+	dev->mode = set_mode;
+
+	if (dev->mode == CX231XX_DIGITAL_MODE)/* Set Digital power mode */ {
+	/* set AGC mode to Digital */
+		switch (dev->model) {
+		case CX231XX_BOARD_CNXT_CARRAERA:
+		case CX231XX_BOARD_CNXT_RDE_250:
+		case CX231XX_BOARD_CNXT_SHELBY:
+		case CX231XX_BOARD_CNXT_RDU_250:
+		errCode = cx231xx_set_agc_analog_digital_mux_select(dev, 0);
+			break;
+		case CX231XX_BOARD_CNXT_RDE_253S:
+		case CX231XX_BOARD_CNXT_RDU_253S:
+		case CX231XX_BOARD_PV_PLAYTV_USB_HYBRID:
+			errCode = cx231xx_set_agc_analog_digital_mux_select(dev, 1);
+			break;
+		case CX231XX_BOARD_HAUPPAUGE_EXETER:
+		case CX231XX_BOARD_HAUPPAUGE_930C_HD_1113xx:
+			errCode = cx231xx_set_power_mode(dev,
+						POLARIS_AVMODE_DIGITAL);
+			break;
+		default:
+			break;
+		}
+	} else/* Set Analog Power mode */ {
+	/* set AGC mode to Analog */
+		switch (dev->model) {
+		case CX231XX_BOARD_CNXT_CARRAERA:
+		case CX231XX_BOARD_CNXT_RDE_250:
+		case CX231XX_BOARD_CNXT_SHELBY:
+		case CX231XX_BOARD_CNXT_RDU_250:
+		errCode = cx231xx_set_agc_analog_digital_mux_select(dev, 1);
+			break;
+		case CX231XX_BOARD_CNXT_RDE_253S:
+		case CX231XX_BOARD_CNXT_RDU_253S:
+		case CX231XX_BOARD_HAUPPAUGE_EXETER:
+		case CX231XX_BOARD_HAUPPAUGE_930C_HD_1113xx:
+		case CX231XX_BOARD_PV_PLAYTV_USB_HYBRID:
+		case CX231XX_BOARD_HAUPPAUGE_USB2_FM_PAL:
+		case CX231XX_BOARD_HAUPPAUGE_USB2_FM_NTSC:
+			errCode = cx231xx_set_agc_analog_digital_mux_select(dev, 0);
+			break;
+		default:
+			break;
+		}
+	}
+
+	if (errCode < 0) {
+		dev_err(dev->dev, "Failed to set devmode to %s: error: %i",
+			dev->mode == CX231XX_DIGITAL_MODE ? "digital" : "analog",
+			errCode);
+		return errCode;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(cx231xx_set_mode);
+
+int cx231xx_ep5_bulkout(struct cx231xx *dev, u8 *firmware, u16 size)
+{
+	int errCode = 0;
+	int actlen = -1;
+	int ret = -ENOMEM;
+	u32 *buffer;
+
+	buffer = kzalloc(4096, GFP_KERNEL);
+	if (buffer == NULL)
+		return -ENOMEM;
+	memcpy(&buffer[0], firmware, 4096);
+
+	ret = usb_bulk_msg(dev->udev, usb_sndbulkpipe(dev->udev, 5),
+			buffer, 4096, &actlen, 2000);
+
+	if (ret)
+		dev_err(dev->dev,
+			"bulk message failed: %d (%d/%d)", ret,
+			size, actlen);
+	else {
+		errCode = actlen != size ? -1 : 0;
+	}
+	kfree(buffer);
+	return errCode;
+}
+
+/*****************************************************************
+*                URB Streaming functions                         *
+******************************************************************/
+
+/*
+ * IRQ callback, called by URB callback
+ */
+static void cx231xx_isoc_irq_callback(struct urb *urb)
+{
+	struct cx231xx_dmaqueue *dma_q = urb->context;
+	struct cx231xx_video_mode *vmode =
+	    container_of(dma_q, struct cx231xx_video_mode, vidq);
+	struct cx231xx *dev = container_of(vmode, struct cx231xx, video_mode);
+	unsigned long flags;
+	int i;
+
+	switch (urb->status) {
+	case 0:		/* success */
+	case -ETIMEDOUT:	/* NAK */
+		break;
+	case -ECONNRESET:	/* kill */
+	case -ENOENT:
+	case -ESHUTDOWN:
+		return;
+	default:		/* error */
+		cx231xx_isocdbg("urb completion error %d.\n", urb->status);
+		break;
+	}
+
+	/* Copy data from URB */
+	spin_lock_irqsave(&dev->video_mode.slock, flags);
+	dev->video_mode.isoc_ctl.isoc_copy(dev, urb);
+	spin_unlock_irqrestore(&dev->video_mode.slock, flags);
+
+	/* Reset urb buffers */
+	for (i = 0; i < urb->number_of_packets; i++) {
+		urb->iso_frame_desc[i].status = 0;
+		urb->iso_frame_desc[i].actual_length = 0;
+	}
+
+	urb->status = usb_submit_urb(urb, GFP_ATOMIC);
+	if (urb->status) {
+		cx231xx_isocdbg("urb resubmit failed (error=%i)\n",
+				urb->status);
+	}
+}
+/*****************************************************************
+*                URB Streaming functions                         *
+******************************************************************/
+
+/*
+ * IRQ callback, called by URB callback
+ */
+static void cx231xx_bulk_irq_callback(struct urb *urb)
+{
+	struct cx231xx_dmaqueue *dma_q = urb->context;
+	struct cx231xx_video_mode *vmode =
+	    container_of(dma_q, struct cx231xx_video_mode, vidq);
+	struct cx231xx *dev = container_of(vmode, struct cx231xx, video_mode);
+	unsigned long flags;
+
+	switch (urb->status) {
+	case 0:		/* success */
+	case -ETIMEDOUT:	/* NAK */
+		break;
+	case -ECONNRESET:	/* kill */
+	case -ENOENT:
+	case -ESHUTDOWN:
+		return;
+	case -EPIPE:		/* stall */
+		cx231xx_isocdbg("urb completion error - device is stalled.\n");
+		return;
+	default:		/* error */
+		cx231xx_isocdbg("urb completion error %d.\n", urb->status);
+		break;
+	}
+
+	/* Copy data from URB */
+	spin_lock_irqsave(&dev->video_mode.slock, flags);
+	dev->video_mode.bulk_ctl.bulk_copy(dev, urb);
+	spin_unlock_irqrestore(&dev->video_mode.slock, flags);
+
+	/* Reset urb buffers */
+	urb->status = usb_submit_urb(urb, GFP_ATOMIC);
+	if (urb->status) {
+		cx231xx_isocdbg("urb resubmit failed (error=%i)\n",
+				urb->status);
+	}
+}
+/*
+ * Stop and Deallocate URBs
+ */
+void cx231xx_uninit_isoc(struct cx231xx *dev)
+{
+	struct cx231xx_dmaqueue *dma_q = &dev->video_mode.vidq;
+	struct urb *urb;
+	int i;
+	bool broken_pipe = false;
+
+	cx231xx_isocdbg("cx231xx: called cx231xx_uninit_isoc\n");
+
+	dev->video_mode.isoc_ctl.nfields = -1;
+	for (i = 0; i < dev->video_mode.isoc_ctl.num_bufs; i++) {
+		urb = dev->video_mode.isoc_ctl.urb[i];
+		if (urb) {
+			if (!irqs_disabled())
+				usb_kill_urb(urb);
+			else
+				usb_unlink_urb(urb);
+
+			if (dev->video_mode.isoc_ctl.transfer_buffer[i]) {
+				usb_free_coherent(dev->udev,
+						  urb->transfer_buffer_length,
+						  dev->video_mode.isoc_ctl.
+						  transfer_buffer[i],
+						  urb->transfer_dma);
+			}
+			if (urb->status == -EPIPE) {
+				broken_pipe = true;
+			}
+			usb_free_urb(urb);
+			dev->video_mode.isoc_ctl.urb[i] = NULL;
+		}
+		dev->video_mode.isoc_ctl.transfer_buffer[i] = NULL;
+	}
+
+	if (broken_pipe) {
+		cx231xx_isocdbg("Reset endpoint to recover broken pipe.");
+		usb_reset_endpoint(dev->udev, dev->video_mode.end_point_addr);
+	}
+	kfree(dev->video_mode.isoc_ctl.urb);
+	kfree(dev->video_mode.isoc_ctl.transfer_buffer);
+	kfree(dma_q->p_left_data);
+
+	dev->video_mode.isoc_ctl.urb = NULL;
+	dev->video_mode.isoc_ctl.transfer_buffer = NULL;
+	dev->video_mode.isoc_ctl.num_bufs = 0;
+	dma_q->p_left_data = NULL;
+
+	if (dev->mode_tv == 0)
+		cx231xx_capture_start(dev, 0, Raw_Video);
+	else
+		cx231xx_capture_start(dev, 0, TS1_serial_mode);
+
+
+}
+EXPORT_SYMBOL_GPL(cx231xx_uninit_isoc);
+
+/*
+ * Stop and Deallocate URBs
+ */
+void cx231xx_uninit_bulk(struct cx231xx *dev)
+{
+	struct cx231xx_dmaqueue *dma_q = &dev->video_mode.vidq;
+	struct urb *urb;
+	int i;
+	bool broken_pipe = false;
+
+	cx231xx_isocdbg("cx231xx: called cx231xx_uninit_bulk\n");
+
+	dev->video_mode.bulk_ctl.nfields = -1;
+	for (i = 0; i < dev->video_mode.bulk_ctl.num_bufs; i++) {
+		urb = dev->video_mode.bulk_ctl.urb[i];
+		if (urb) {
+			if (!irqs_disabled())
+				usb_kill_urb(urb);
+			else
+				usb_unlink_urb(urb);
+
+			if (dev->video_mode.bulk_ctl.transfer_buffer[i]) {
+				usb_free_coherent(dev->udev,
+						urb->transfer_buffer_length,
+						dev->video_mode.bulk_ctl.
+						transfer_buffer[i],
+						urb->transfer_dma);
+			}
+			if (urb->status == -EPIPE) {
+				broken_pipe = true;
+			}
+			usb_free_urb(urb);
+			dev->video_mode.bulk_ctl.urb[i] = NULL;
+		}
+		dev->video_mode.bulk_ctl.transfer_buffer[i] = NULL;
+	}
+
+	if (broken_pipe) {
+		cx231xx_isocdbg("Reset endpoint to recover broken pipe.");
+		usb_reset_endpoint(dev->udev, dev->video_mode.end_point_addr);
+	}
+	kfree(dev->video_mode.bulk_ctl.urb);
+	kfree(dev->video_mode.bulk_ctl.transfer_buffer);
+	kfree(dma_q->p_left_data);
+
+	dev->video_mode.bulk_ctl.urb = NULL;
+	dev->video_mode.bulk_ctl.transfer_buffer = NULL;
+	dev->video_mode.bulk_ctl.num_bufs = 0;
+	dma_q->p_left_data = NULL;
+
+	if (dev->mode_tv == 0)
+		cx231xx_capture_start(dev, 0, Raw_Video);
+	else
+		cx231xx_capture_start(dev, 0, TS1_serial_mode);
+
+
+}
+EXPORT_SYMBOL_GPL(cx231xx_uninit_bulk);
+
+/*
+ * Allocate URBs and start IRQ
+ */
+int cx231xx_init_isoc(struct cx231xx *dev, int max_packets,
+		      int num_bufs, int max_pkt_size,
+		      int (*isoc_copy) (struct cx231xx *dev, struct urb *urb))
+{
+	struct cx231xx_dmaqueue *dma_q = &dev->video_mode.vidq;
+	int i;
+	int sb_size, pipe;
+	struct urb *urb;
+	int j, k;
+	int rc;
+
+	/* De-allocates all pending stuff */
+	cx231xx_uninit_isoc(dev);
+
+	dma_q->p_left_data = kzalloc(4096, GFP_KERNEL);
+	if (dma_q->p_left_data == NULL)
+		return -ENOMEM;
+
+	dev->video_mode.isoc_ctl.isoc_copy = isoc_copy;
+	dev->video_mode.isoc_ctl.num_bufs = num_bufs;
+	dma_q->pos = 0;
+	dma_q->is_partial_line = 0;
+	dma_q->last_sav = 0;
+	dma_q->current_field = -1;
+	dma_q->field1_done = 0;
+	dma_q->lines_per_field = dev->height / 2;
+	dma_q->bytes_left_in_line = dev->width << 1;
+	dma_q->lines_completed = 0;
+	dma_q->mpeg_buffer_done = 0;
+	dma_q->left_data_count = 0;
+	dma_q->mpeg_buffer_completed = 0;
+	dma_q->add_ps_package_head = CX231XX_NEED_ADD_PS_PACKAGE_HEAD;
+	dma_q->ps_head[0] = 0x00;
+	dma_q->ps_head[1] = 0x00;
+	dma_q->ps_head[2] = 0x01;
+	dma_q->ps_head[3] = 0xBA;
+	for (i = 0; i < 8; i++)
+		dma_q->partial_buf[i] = 0;
+
+	dev->video_mode.isoc_ctl.urb =
+	    kcalloc(num_bufs, sizeof(void *), GFP_KERNEL);
+	if (!dev->video_mode.isoc_ctl.urb) {
+		dev_err(dev->dev,
+			"cannot alloc memory for usb buffers\n");
+		return -ENOMEM;
+	}
+
+	dev->video_mode.isoc_ctl.transfer_buffer =
+	    kcalloc(num_bufs, sizeof(void *), GFP_KERNEL);
+	if (!dev->video_mode.isoc_ctl.transfer_buffer) {
+		dev_err(dev->dev,
+			"cannot allocate memory for usbtransfer\n");
+		kfree(dev->video_mode.isoc_ctl.urb);
+		return -ENOMEM;
+	}
+
+	dev->video_mode.isoc_ctl.max_pkt_size = max_pkt_size;
+	dev->video_mode.isoc_ctl.buf = NULL;
+
+	sb_size = max_packets * dev->video_mode.isoc_ctl.max_pkt_size;
+
+	if (dev->mode_tv == 1)
+		dev->video_mode.end_point_addr = 0x81;
+	else
+		dev->video_mode.end_point_addr = 0x84;
+
+
+	/* allocate urbs and transfer buffers */
+	for (i = 0; i < dev->video_mode.isoc_ctl.num_bufs; i++) {
+		urb = usb_alloc_urb(max_packets, GFP_KERNEL);
+		if (!urb) {
+			cx231xx_uninit_isoc(dev);
+			return -ENOMEM;
+		}
+		dev->video_mode.isoc_ctl.urb[i] = urb;
+
+		dev->video_mode.isoc_ctl.transfer_buffer[i] =
+		    usb_alloc_coherent(dev->udev, sb_size, GFP_KERNEL,
+				       &urb->transfer_dma);
+		if (!dev->video_mode.isoc_ctl.transfer_buffer[i]) {
+			dev_err(dev->dev,
+				"unable to allocate %i bytes for transfer buffer %i%s\n",
+				sb_size, i,
+				in_interrupt() ? " while in int" : "");
+			cx231xx_uninit_isoc(dev);
+			return -ENOMEM;
+		}
+		memset(dev->video_mode.isoc_ctl.transfer_buffer[i], 0, sb_size);
+
+		pipe =
+		    usb_rcvisocpipe(dev->udev, dev->video_mode.end_point_addr);
+
+		usb_fill_int_urb(urb, dev->udev, pipe,
+				 dev->video_mode.isoc_ctl.transfer_buffer[i],
+				 sb_size, cx231xx_isoc_irq_callback, dma_q, 1);
+
+		urb->number_of_packets = max_packets;
+		urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
+
+		k = 0;
+		for (j = 0; j < max_packets; j++) {
+			urb->iso_frame_desc[j].offset = k;
+			urb->iso_frame_desc[j].length =
+			    dev->video_mode.isoc_ctl.max_pkt_size;
+			k += dev->video_mode.isoc_ctl.max_pkt_size;
+		}
+	}
+
+	init_waitqueue_head(&dma_q->wq);
+
+	/* submit urbs and enables IRQ */
+	for (i = 0; i < dev->video_mode.isoc_ctl.num_bufs; i++) {
+		rc = usb_submit_urb(dev->video_mode.isoc_ctl.urb[i],
+				    GFP_ATOMIC);
+		if (rc) {
+			dev_err(dev->dev,
+				"submit of urb %i failed (error=%i)\n", i,
+				rc);
+			cx231xx_uninit_isoc(dev);
+			return rc;
+		}
+	}
+
+	if (dev->mode_tv == 0)
+		cx231xx_capture_start(dev, 1, Raw_Video);
+	else
+		cx231xx_capture_start(dev, 1, TS1_serial_mode);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(cx231xx_init_isoc);
+
+/*
+ * Allocate URBs and start IRQ
+ */
+int cx231xx_init_bulk(struct cx231xx *dev, int max_packets,
+		      int num_bufs, int max_pkt_size,
+		      int (*bulk_copy) (struct cx231xx *dev, struct urb *urb))
+{
+	struct cx231xx_dmaqueue *dma_q = &dev->video_mode.vidq;
+	int i;
+	int sb_size, pipe;
+	struct urb *urb;
+	int rc;
+
+	dev->video_input = dev->video_input > 2 ? 2 : dev->video_input;
+
+	cx231xx_coredbg("Setting Video mux to %d\n", dev->video_input);
+
+	video_mux(dev, dev->video_input);
+
+	/* De-allocates all pending stuff */
+	cx231xx_uninit_bulk(dev);
+
+	dev->video_mode.bulk_ctl.bulk_copy = bulk_copy;
+	dev->video_mode.bulk_ctl.num_bufs = num_bufs;
+	dma_q->pos = 0;
+	dma_q->is_partial_line = 0;
+	dma_q->last_sav = 0;
+	dma_q->current_field = -1;
+	dma_q->field1_done = 0;
+	dma_q->lines_per_field = dev->height / 2;
+	dma_q->bytes_left_in_line = dev->width << 1;
+	dma_q->lines_completed = 0;
+	dma_q->mpeg_buffer_done = 0;
+	dma_q->left_data_count = 0;
+	dma_q->mpeg_buffer_completed = 0;
+	dma_q->ps_head[0] = 0x00;
+	dma_q->ps_head[1] = 0x00;
+	dma_q->ps_head[2] = 0x01;
+	dma_q->ps_head[3] = 0xBA;
+	for (i = 0; i < 8; i++)
+		dma_q->partial_buf[i] = 0;
+
+	dev->video_mode.bulk_ctl.urb =
+	    kcalloc(num_bufs, sizeof(void *), GFP_KERNEL);
+	if (!dev->video_mode.bulk_ctl.urb) {
+		dev_err(dev->dev,
+			"cannot alloc memory for usb buffers\n");
+		return -ENOMEM;
+	}
+
+	dev->video_mode.bulk_ctl.transfer_buffer =
+	    kcalloc(num_bufs, sizeof(void *), GFP_KERNEL);
+	if (!dev->video_mode.bulk_ctl.transfer_buffer) {
+		dev_err(dev->dev,
+			"cannot allocate memory for usbtransfer\n");
+		kfree(dev->video_mode.bulk_ctl.urb);
+		return -ENOMEM;
+	}
+
+	dev->video_mode.bulk_ctl.max_pkt_size = max_pkt_size;
+	dev->video_mode.bulk_ctl.buf = NULL;
+
+	sb_size = max_packets * dev->video_mode.bulk_ctl.max_pkt_size;
+
+	if (dev->mode_tv == 1)
+		dev->video_mode.end_point_addr = 0x81;
+	else
+		dev->video_mode.end_point_addr = 0x84;
+
+
+	/* allocate urbs and transfer buffers */
+	for (i = 0; i < dev->video_mode.bulk_ctl.num_bufs; i++) {
+		urb = usb_alloc_urb(0, GFP_KERNEL);
+		if (!urb) {
+			cx231xx_uninit_bulk(dev);
+			return -ENOMEM;
+		}
+		dev->video_mode.bulk_ctl.urb[i] = urb;
+		urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+
+		dev->video_mode.bulk_ctl.transfer_buffer[i] =
+		    usb_alloc_coherent(dev->udev, sb_size, GFP_KERNEL,
+				     &urb->transfer_dma);
+		if (!dev->video_mode.bulk_ctl.transfer_buffer[i]) {
+			dev_err(dev->dev,
+				"unable to allocate %i bytes for transfer buffer %i%s\n",
+				sb_size, i,
+				in_interrupt() ? " while in int" : "");
+			cx231xx_uninit_bulk(dev);
+			return -ENOMEM;
+		}
+		memset(dev->video_mode.bulk_ctl.transfer_buffer[i], 0, sb_size);
+
+		pipe = usb_rcvbulkpipe(dev->udev,
+				 dev->video_mode.end_point_addr);
+		usb_fill_bulk_urb(urb, dev->udev, pipe,
+				  dev->video_mode.bulk_ctl.transfer_buffer[i],
+				  sb_size, cx231xx_bulk_irq_callback, dma_q);
+	}
+
+	/* clear halt */
+	rc = usb_clear_halt(dev->udev, dev->video_mode.bulk_ctl.urb[0]->pipe);
+	if (rc < 0) {
+		dev_err(dev->dev,
+			"failed to clear USB bulk endpoint stall/halt condition (error=%i)\n",
+			rc);
+		cx231xx_uninit_bulk(dev);
+		return rc;
+	}
+
+	init_waitqueue_head(&dma_q->wq);
+
+	/* submit urbs and enables IRQ */
+	for (i = 0; i < dev->video_mode.bulk_ctl.num_bufs; i++) {
+		rc = usb_submit_urb(dev->video_mode.bulk_ctl.urb[i],
+				    GFP_ATOMIC);
+		if (rc) {
+			dev_err(dev->dev,
+				"submit of urb %i failed (error=%i)\n", i, rc);
+			cx231xx_uninit_bulk(dev);
+			return rc;
+		}
+	}
+
+	if (dev->mode_tv == 0)
+		cx231xx_capture_start(dev, 1, Raw_Video);
+	else
+		cx231xx_capture_start(dev, 1, TS1_serial_mode);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(cx231xx_init_bulk);
+void cx231xx_stop_TS1(struct cx231xx *dev)
+{
+	u8 val[4] = { 0, 0, 0, 0 };
+
+	val[0] = 0x00;
+	val[1] = 0x03;
+	val[2] = 0x00;
+	val[3] = 0x00;
+	cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER,
+			TS_MODE_REG, val, 4);
+
+	val[0] = 0x00;
+	val[1] = 0x70;
+	val[2] = 0x04;
+	val[3] = 0x00;
+	cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER,
+			TS1_CFG_REG, val, 4);
+}
+/* EXPORT_SYMBOL_GPL(cx231xx_stop_TS1); */
+void cx231xx_start_TS1(struct cx231xx *dev)
+{
+	u8 val[4] = { 0, 0, 0, 0 };
+
+	val[0] = 0x03;
+	val[1] = 0x03;
+	val[2] = 0x00;
+	val[3] = 0x00;
+	cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER,
+			TS_MODE_REG, val, 4);
+
+	val[0] = 0x04;
+	val[1] = 0xA3;
+	val[2] = 0x3B;
+	val[3] = 0x00;
+	cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER,
+			TS1_CFG_REG, val, 4);
+}
+/* EXPORT_SYMBOL_GPL(cx231xx_start_TS1); */
+/*****************************************************************
+*             Device Init/UnInit functions                       *
+******************************************************************/
+int cx231xx_dev_init(struct cx231xx *dev)
+{
+	int errCode = 0;
+
+	/* Initialize I2C bus */
+
+	/* External Master 1 Bus */
+	dev->i2c_bus[0].nr = 0;
+	dev->i2c_bus[0].dev = dev;
+	dev->i2c_bus[0].i2c_period = I2C_SPEED_100K;	/* 100 KHz */
+	dev->i2c_bus[0].i2c_nostop = 0;
+	dev->i2c_bus[0].i2c_reserve = 0;
+	dev->i2c_bus[0].i2c_rc = -ENODEV;
+
+	/* External Master 2 Bus */
+	dev->i2c_bus[1].nr = 1;
+	dev->i2c_bus[1].dev = dev;
+	dev->i2c_bus[1].i2c_period = I2C_SPEED_100K;	/* 100 KHz */
+	dev->i2c_bus[1].i2c_nostop = 0;
+	dev->i2c_bus[1].i2c_reserve = 0;
+	dev->i2c_bus[1].i2c_rc = -ENODEV;
+
+	/* Internal Master 3 Bus */
+	dev->i2c_bus[2].nr = 2;
+	dev->i2c_bus[2].dev = dev;
+	dev->i2c_bus[2].i2c_period = I2C_SPEED_100K;	/* 100kHz */
+	dev->i2c_bus[2].i2c_nostop = 0;
+	dev->i2c_bus[2].i2c_reserve = 0;
+	dev->i2c_bus[2].i2c_rc = -ENODEV;
+
+	/* register I2C buses */
+	errCode = cx231xx_i2c_register(&dev->i2c_bus[0]);
+	if (errCode < 0)
+		return errCode;
+	errCode = cx231xx_i2c_register(&dev->i2c_bus[1]);
+	if (errCode < 0)
+		return errCode;
+	errCode = cx231xx_i2c_register(&dev->i2c_bus[2]);
+	if (errCode < 0)
+		return errCode;
+
+	errCode = cx231xx_i2c_mux_create(dev);
+	if (errCode < 0) {
+		dev_err(dev->dev,
+			"%s: Failed to create I2C mux\n", __func__);
+		return errCode;
+	}
+	errCode = cx231xx_i2c_mux_register(dev, 0);
+	if (errCode < 0)
+		return errCode;
+
+	errCode = cx231xx_i2c_mux_register(dev, 1);
+	if (errCode < 0)
+		return errCode;
+
+	/* scan the real bus segments in the order of physical port numbers */
+	cx231xx_do_i2c_scan(dev, I2C_0);
+	cx231xx_do_i2c_scan(dev, I2C_1_MUX_1);
+	cx231xx_do_i2c_scan(dev, I2C_2);
+	cx231xx_do_i2c_scan(dev, I2C_1_MUX_3);
+
+	/* init hardware */
+	/* Note : with out calling set power mode function,
+	afe can not be set up correctly */
+	if (dev->board.external_av) {
+		errCode = cx231xx_set_power_mode(dev,
+				 POLARIS_AVMODE_ENXTERNAL_AV);
+		if (errCode < 0) {
+			dev_err(dev->dev,
+				"%s: Failed to set Power - errCode [%d]!\n",
+				__func__, errCode);
+			return errCode;
+		}
+	} else {
+		errCode = cx231xx_set_power_mode(dev,
+				 POLARIS_AVMODE_ANALOGT_TV);
+		if (errCode < 0) {
+			dev_err(dev->dev,
+				"%s: Failed to set Power - errCode [%d]!\n",
+				__func__, errCode);
+			return errCode;
+		}
+	}
+
+	/* reset the Tuner, if it is a Xceive tuner */
+	if ((dev->board.tuner_type == TUNER_XC5000) ||
+	    (dev->board.tuner_type == TUNER_XC2028))
+			cx231xx_gpio_set(dev, dev->board.tuner_gpio);
+
+	/* initialize Colibri block */
+	errCode = cx231xx_afe_init_super_block(dev, 0x23c);
+	if (errCode < 0) {
+		dev_err(dev->dev,
+			"%s: cx231xx_afe init super block - errCode [%d]!\n",
+			__func__, errCode);
+		return errCode;
+	}
+	errCode = cx231xx_afe_init_channels(dev);
+	if (errCode < 0) {
+		dev_err(dev->dev,
+			"%s: cx231xx_afe init channels - errCode [%d]!\n",
+			__func__, errCode);
+		return errCode;
+	}
+
+	/* Set DIF in By pass mode */
+	errCode = cx231xx_dif_set_standard(dev, DIF_USE_BASEBAND);
+	if (errCode < 0) {
+		dev_err(dev->dev,
+			"%s: cx231xx_dif set to By pass mode - errCode [%d]!\n",
+			__func__, errCode);
+		return errCode;
+	}
+
+	/* I2S block related functions */
+	errCode = cx231xx_i2s_blk_initialize(dev);
+	if (errCode < 0) {
+		dev_err(dev->dev,
+			"%s: cx231xx_i2s block initialize - errCode [%d]!\n",
+			__func__, errCode);
+		return errCode;
+	}
+
+	/* init control pins */
+	errCode = cx231xx_init_ctrl_pin_status(dev);
+	if (errCode < 0) {
+		dev_err(dev->dev,
+			"%s: cx231xx_init ctrl pins - errCode [%d]!\n",
+			__func__, errCode);
+		return errCode;
+	}
+
+	/* set AGC mode to Analog */
+	switch (dev->model) {
+	case CX231XX_BOARD_CNXT_CARRAERA:
+	case CX231XX_BOARD_CNXT_RDE_250:
+	case CX231XX_BOARD_CNXT_SHELBY:
+	case CX231XX_BOARD_CNXT_RDU_250:
+	errCode = cx231xx_set_agc_analog_digital_mux_select(dev, 1);
+		break;
+	case CX231XX_BOARD_CNXT_RDE_253S:
+	case CX231XX_BOARD_CNXT_RDU_253S:
+	case CX231XX_BOARD_HAUPPAUGE_EXETER:
+	case CX231XX_BOARD_HAUPPAUGE_930C_HD_1113xx:
+	case CX231XX_BOARD_PV_PLAYTV_USB_HYBRID:
+	case CX231XX_BOARD_HAUPPAUGE_USB2_FM_PAL:
+	case CX231XX_BOARD_HAUPPAUGE_USB2_FM_NTSC:
+	errCode = cx231xx_set_agc_analog_digital_mux_select(dev, 0);
+		break;
+	default:
+		break;
+	}
+	if (errCode < 0) {
+		dev_err(dev->dev,
+			"%s: cx231xx_AGC mode to Analog - errCode [%d]!\n",
+			__func__, errCode);
+		return errCode;
+	}
+
+	/* set all alternate settings to zero initially */
+	cx231xx_set_alt_setting(dev, INDEX_VIDEO, 0);
+	cx231xx_set_alt_setting(dev, INDEX_VANC, 0);
+	cx231xx_set_alt_setting(dev, INDEX_HANC, 0);
+	if (dev->board.has_dvb)
+		cx231xx_set_alt_setting(dev, INDEX_TS1, 0);
+
+	errCode = 0;
+	return errCode;
+}
+EXPORT_SYMBOL_GPL(cx231xx_dev_init);
+
+void cx231xx_dev_uninit(struct cx231xx *dev)
+{
+	/* Un Initialize I2C bus */
+	cx231xx_i2c_mux_unregister(dev);
+	cx231xx_i2c_unregister(&dev->i2c_bus[2]);
+	cx231xx_i2c_unregister(&dev->i2c_bus[1]);
+	cx231xx_i2c_unregister(&dev->i2c_bus[0]);
+}
+EXPORT_SYMBOL_GPL(cx231xx_dev_uninit);
+
+/*****************************************************************
+*              G P I O related functions                         *
+******************************************************************/
+int cx231xx_send_gpio_cmd(struct cx231xx *dev, u32 gpio_bit, u8 *gpio_val,
+			  u8 len, u8 request, u8 direction)
+{
+	int status = 0;
+	struct VENDOR_REQUEST_IN ven_req;
+
+	/* Set wValue */
+	ven_req.wValue = (u16) (gpio_bit >> 16 & 0xffff);
+
+	/* set request */
+	if (!request) {
+		if (direction)
+			ven_req.bRequest = VRT_GET_GPIO;	/* 0x9 gpio */
+		else
+			ven_req.bRequest = VRT_SET_GPIO;	/* 0x8 gpio */
+	} else {
+		if (direction)
+			ven_req.bRequest = VRT_GET_GPIE;	/* 0xb gpie */
+		else
+			ven_req.bRequest = VRT_SET_GPIE;	/* 0xa gpie */
+	}
+
+	/* set index value */
+	ven_req.wIndex = (u16) (gpio_bit & 0xffff);
+
+	/* set wLength value */
+	ven_req.wLength = len;
+
+	/* set bData value */
+	ven_req.bData = 0;
+
+	/* set the buffer for read / write */
+	ven_req.pBuff = gpio_val;
+
+	/* set the direction */
+	if (direction) {
+		ven_req.direction = USB_DIR_IN;
+		memset(ven_req.pBuff, 0x00, ven_req.wLength);
+	} else
+		ven_req.direction = USB_DIR_OUT;
+
+
+	/* call common vendor command request */
+	status = cx231xx_send_vendor_cmd(dev, &ven_req);
+	if (status < 0) {
+		dev_err(dev->dev, "%s: failed with status -%d\n",
+			__func__, status);
+	}
+
+	return status;
+}
+EXPORT_SYMBOL_GPL(cx231xx_send_gpio_cmd);
+
+/*****************************************************************
+ *    C O N T R O L - Register R E A D / W R I T E functions     *
+ *****************************************************************/
+int cx231xx_mode_register(struct cx231xx *dev, u16 address, u32 mode)
+{
+	u8 value[4] = { 0x0, 0x0, 0x0, 0x0 };
+	u32 tmp = 0;
+	int status = 0;
+
+	status =
+	    cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, address, value, 4);
+	if (status < 0)
+		return status;
+
+	tmp = le32_to_cpu(*((__le32 *) value));
+	tmp |= mode;
+
+	value[0] = (u8) tmp;
+	value[1] = (u8) (tmp >> 8);
+	value[2] = (u8) (tmp >> 16);
+	value[3] = (u8) (tmp >> 24);
+
+	status =
+	    cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER, address, value, 4);
+
+	return status;
+}
+
+/*****************************************************************
+ *            I 2 C Internal C O N T R O L   functions           *
+ *****************************************************************/
+int cx231xx_read_i2c_master(struct cx231xx *dev, u8 dev_addr, u16 saddr,
+			  u8 saddr_len, u32 *data, u8 data_len, int master)
+{
+	int status = 0;
+	struct cx231xx_i2c_xfer_data req_data;
+	u8 value[64] = "0";
+
+	if (saddr_len == 0)
+		saddr = 0;
+	else if (saddr_len == 1)
+		saddr &= 0xff;
+
+	/* prepare xfer_data struct */
+	req_data.dev_addr = dev_addr >> 1;
+	req_data.direction = I2C_M_RD;
+	req_data.saddr_len = saddr_len;
+	req_data.saddr_dat = saddr;
+	req_data.buf_size = data_len;
+	req_data.p_buffer = (u8 *) value;
+
+	/* usb send command */
+	if (master == 0)
+		status = dev->cx231xx_send_usb_command(&dev->i2c_bus[0],
+					 &req_data);
+	else if (master == 1)
+		status = dev->cx231xx_send_usb_command(&dev->i2c_bus[1],
+					 &req_data);
+	else if (master == 2)
+		status = dev->cx231xx_send_usb_command(&dev->i2c_bus[2],
+					 &req_data);
+
+	if (status >= 0) {
+		/* Copy the data read back to main buffer */
+		if (data_len == 1)
+			*data = value[0];
+		else if (data_len == 4)
+			*data =
+			    value[0] | value[1] << 8 | value[2] << 16 | value[3]
+			    << 24;
+		else if (data_len > 4)
+			*data = value[saddr];
+	}
+
+	return status;
+}
+
+int cx231xx_write_i2c_master(struct cx231xx *dev, u8 dev_addr, u16 saddr,
+			   u8 saddr_len, u32 data, u8 data_len, int master)
+{
+	int status = 0;
+	u8 value[4] = { 0, 0, 0, 0 };
+	struct cx231xx_i2c_xfer_data req_data;
+
+	value[0] = (u8) data;
+	value[1] = (u8) (data >> 8);
+	value[2] = (u8) (data >> 16);
+	value[3] = (u8) (data >> 24);
+
+	if (saddr_len == 0)
+		saddr = 0;
+	else if (saddr_len == 1)
+		saddr &= 0xff;
+
+	/* prepare xfer_data struct */
+	req_data.dev_addr = dev_addr >> 1;
+	req_data.direction = 0;
+	req_data.saddr_len = saddr_len;
+	req_data.saddr_dat = saddr;
+	req_data.buf_size = data_len;
+	req_data.p_buffer = value;
+
+	/* usb send command */
+	if (master == 0)
+		status = dev->cx231xx_send_usb_command(&dev->i2c_bus[0],
+				 &req_data);
+	else if (master == 1)
+		status = dev->cx231xx_send_usb_command(&dev->i2c_bus[1],
+				 &req_data);
+	else if (master == 2)
+		status = dev->cx231xx_send_usb_command(&dev->i2c_bus[2],
+				 &req_data);
+
+	return status;
+}
+
+int cx231xx_read_i2c_data(struct cx231xx *dev, u8 dev_addr, u16 saddr,
+			  u8 saddr_len, u32 *data, u8 data_len)
+{
+	int status = 0;
+	struct cx231xx_i2c_xfer_data req_data;
+	u8 value[4] = { 0, 0, 0, 0 };
+
+	if (saddr_len == 0)
+		saddr = 0;
+	else if (saddr_len == 1)
+		saddr &= 0xff;
+
+	/* prepare xfer_data struct */
+	req_data.dev_addr = dev_addr >> 1;
+	req_data.direction = I2C_M_RD;
+	req_data.saddr_len = saddr_len;
+	req_data.saddr_dat = saddr;
+	req_data.buf_size = data_len;
+	req_data.p_buffer = (u8 *) value;
+
+	/* usb send command */
+	status = dev->cx231xx_send_usb_command(&dev->i2c_bus[0], &req_data);
+
+	if (status >= 0) {
+		/* Copy the data read back to main buffer */
+		if (data_len == 1)
+			*data = value[0];
+		else
+			*data =
+			    value[0] | value[1] << 8 | value[2] << 16 | value[3]
+			    << 24;
+	}
+
+	return status;
+}
+
+int cx231xx_write_i2c_data(struct cx231xx *dev, u8 dev_addr, u16 saddr,
+			   u8 saddr_len, u32 data, u8 data_len)
+{
+	int status = 0;
+	u8 value[4] = { 0, 0, 0, 0 };
+	struct cx231xx_i2c_xfer_data req_data;
+
+	value[0] = (u8) data;
+	value[1] = (u8) (data >> 8);
+	value[2] = (u8) (data >> 16);
+	value[3] = (u8) (data >> 24);
+
+	if (saddr_len == 0)
+		saddr = 0;
+	else if (saddr_len == 1)
+		saddr &= 0xff;
+
+	/* prepare xfer_data struct */
+	req_data.dev_addr = dev_addr >> 1;
+	req_data.direction = 0;
+	req_data.saddr_len = saddr_len;
+	req_data.saddr_dat = saddr;
+	req_data.buf_size = data_len;
+	req_data.p_buffer = value;
+
+	/* usb send command */
+	status = dev->cx231xx_send_usb_command(&dev->i2c_bus[0], &req_data);
+
+	return status;
+}
+
+int cx231xx_reg_mask_write(struct cx231xx *dev, u8 dev_addr, u8 size,
+			   u16 register_address, u8 bit_start, u8 bit_end,
+			   u32 value)
+{
+	int status = 0;
+	u32 tmp;
+	u32 mask = 0;
+	int i;
+
+	if (bit_start > (size - 1) || bit_end > (size - 1))
+		return -1;
+
+	if (size == 8) {
+		status =
+		    cx231xx_read_i2c_data(dev, dev_addr, register_address, 2,
+					  &tmp, 1);
+	} else {
+		status =
+		    cx231xx_read_i2c_data(dev, dev_addr, register_address, 2,
+					  &tmp, 4);
+	}
+
+	if (status < 0)
+		return status;
+
+	mask = 1 << bit_end;
+	for (i = bit_end; i > bit_start && i > 0; i--)
+		mask = mask + (1 << (i - 1));
+
+	value <<= bit_start;
+
+	if (size == 8) {
+		tmp &= ~mask;
+		tmp |= value;
+		tmp &= 0xff;
+		status =
+		    cx231xx_write_i2c_data(dev, dev_addr, register_address, 2,
+					   tmp, 1);
+	} else {
+		tmp &= ~mask;
+		tmp |= value;
+		status =
+		    cx231xx_write_i2c_data(dev, dev_addr, register_address, 2,
+					   tmp, 4);
+	}
+
+	return status;
+}
+
+int cx231xx_read_modify_write_i2c_dword(struct cx231xx *dev, u8 dev_addr,
+					u16 saddr, u32 mask, u32 value)
+{
+	u32 temp;
+	int status = 0;
+
+	status = cx231xx_read_i2c_data(dev, dev_addr, saddr, 2, &temp, 4);
+
+	if (status < 0)
+		return status;
+
+	temp &= ~mask;
+	temp |= value;
+
+	status = cx231xx_write_i2c_data(dev, dev_addr, saddr, 2, temp, 4);
+
+	return status;
+}
+
+u32 cx231xx_set_field(u32 field_mask, u32 data)
+{
+	u32 temp;
+
+	for (temp = field_mask; (temp & 1) == 0; temp >>= 1)
+		data <<= 1;
+
+	return data;
+}
diff --git a/drivers/media/usb/cx231xx/cx231xx-dif.h b/drivers/media/usb/cx231xx/cx231xx-dif.h
new file mode 100644
index 0000000..2b9eb9f
--- /dev/null
+++ b/drivers/media/usb/cx231xx/cx231xx-dif.h
@@ -0,0 +1,3174 @@
+/*
+ *  cx231xx-dif.h - driver for Conexant Cx23100/101/102 USB video capture devices
+ *
+ *  Copyright {C} 2009 <Bill.Liu@conexant.com>
+ *
+ *  This program is free software, you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY, without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ */
+
+#ifndef _CX231XX_DIF_H
+#define _CX231XX_DIF_H
+
+#include "cx231xx-reg.h"
+
+struct dif_settings{
+	u32 if_freq;
+	u32 register_address;
+	u32 value;
+};
+
+static struct dif_settings Dif_set_array[] = {
+
+/*case 3000000:*/
+/* BEGIN - DIF BPF register values from 30_quant.dat*/
+{3000000, DIF_BPF_COEFF01,    0x00000002},
+{3000000, DIF_BPF_COEFF23,    0x00080012},
+{3000000, DIF_BPF_COEFF45,    0x001e0024},
+{3000000, DIF_BPF_COEFF67,    0x001bfff8},
+{3000000, DIF_BPF_COEFF89,    0xffb4ff50},
+{3000000, DIF_BPF_COEFF1011,  0xfed8fe68},
+{3000000, DIF_BPF_COEFF1213,  0xfe24fe34},
+{3000000, DIF_BPF_COEFF1415,  0xfebaffc7},
+{3000000, DIF_BPF_COEFF1617,  0x014d031f},
+{3000000, DIF_BPF_COEFF1819,  0x04f0065d},
+{3000000, DIF_BPF_COEFF2021,  0x07010688},
+{3000000, DIF_BPF_COEFF2223,  0x04c901d6},
+{3000000, DIF_BPF_COEFF2425,  0xfe00f9d3},
+{3000000, DIF_BPF_COEFF2627,  0xf600f342},
+{3000000, DIF_BPF_COEFF2829,  0xf235f337},
+{3000000, DIF_BPF_COEFF3031,  0xf64efb22},
+{3000000, DIF_BPF_COEFF3233,  0x0105070f},
+{3000000, DIF_BPF_COEFF3435,  0x0c460fce},
+{3000000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 30_quant.dat*/
+
+
+/*case 3100000:*/
+/* BEGIN - DIF BPF register values from 31_quant.dat*/
+{3100000, DIF_BPF_COEFF01,    0x00000001},
+{3100000, DIF_BPF_COEFF23,    0x00070012},
+{3100000, DIF_BPF_COEFF45,    0x00220032},
+{3100000, DIF_BPF_COEFF67,    0x00370026},
+{3100000, DIF_BPF_COEFF89,    0xfff0ff91},
+{3100000, DIF_BPF_COEFF1011,  0xff0efe7c},
+{3100000, DIF_BPF_COEFF1213,  0xfe01fdcc},
+{3100000, DIF_BPF_COEFF1415,  0xfe0afedb},
+{3100000, DIF_BPF_COEFF1617,  0x00440224},
+{3100000, DIF_BPF_COEFF1819,  0x0434060c},
+{3100000, DIF_BPF_COEFF2021,  0x0738074e},
+{3100000, DIF_BPF_COEFF2223,  0x06090361},
+{3100000, DIF_BPF_COEFF2425,  0xff99fb39},
+{3100000, DIF_BPF_COEFF2627,  0xf6fef3b6},
+{3100000, DIF_BPF_COEFF2829,  0xf21af2a5},
+{3100000, DIF_BPF_COEFF3031,  0xf573fa33},
+{3100000, DIF_BPF_COEFF3233,  0x0034067d},
+{3100000, DIF_BPF_COEFF3435,  0x0bfb0fb9},
+{3100000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 31_quant.dat*/
+
+
+/*case 3200000:*/
+/* BEGIN - DIF BPF register values from 32_quant.dat*/
+{3200000, DIF_BPF_COEFF01,    0x00000000},
+{3200000, DIF_BPF_COEFF23,    0x0004000e},
+{3200000, DIF_BPF_COEFF45,    0x00200038},
+{3200000, DIF_BPF_COEFF67,    0x004c004f},
+{3200000, DIF_BPF_COEFF89,    0x002fffdf},
+{3200000, DIF_BPF_COEFF1011,  0xff5cfeb6},
+{3200000, DIF_BPF_COEFF1213,  0xfe0dfd92},
+{3200000, DIF_BPF_COEFF1415,  0xfd7ffe03},
+{3200000, DIF_BPF_COEFF1617,  0xff36010a},
+{3200000, DIF_BPF_COEFF1819,  0x03410575},
+{3200000, DIF_BPF_COEFF2021,  0x072607d2},
+{3200000, DIF_BPF_COEFF2223,  0x071804d5},
+{3200000, DIF_BPF_COEFF2425,  0x0134fcb7},
+{3200000, DIF_BPF_COEFF2627,  0xf81ff451},
+{3200000, DIF_BPF_COEFF2829,  0xf223f22e},
+{3200000, DIF_BPF_COEFF3031,  0xf4a7f94b},
+{3200000, DIF_BPF_COEFF3233,  0xff6405e8},
+{3200000, DIF_BPF_COEFF3435,  0x0bae0fa4},
+{3200000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 32_quant.dat*/
+
+
+/*case 3300000:*/
+/* BEGIN - DIF BPF register values from 33_quant.dat*/
+{3300000, DIF_BPF_COEFF01,    0x0000ffff},
+{3300000, DIF_BPF_COEFF23,    0x00000008},
+{3300000, DIF_BPF_COEFF45,    0x001a0036},
+{3300000, DIF_BPF_COEFF67,    0x0056006d},
+{3300000, DIF_BPF_COEFF89,    0x00670030},
+{3300000, DIF_BPF_COEFF1011,  0xffbdff10},
+{3300000, DIF_BPF_COEFF1213,  0xfe46fd8d},
+{3300000, DIF_BPF_COEFF1415,  0xfd25fd4f},
+{3300000, DIF_BPF_COEFF1617,  0xfe35ffe0},
+{3300000, DIF_BPF_COEFF1819,  0x0224049f},
+{3300000, DIF_BPF_COEFF2021,  0x06c9080e},
+{3300000, DIF_BPF_COEFF2223,  0x07ef0627},
+{3300000, DIF_BPF_COEFF2425,  0x02c9fe45},
+{3300000, DIF_BPF_COEFF2627,  0xf961f513},
+{3300000, DIF_BPF_COEFF2829,  0xf250f1d2},
+{3300000, DIF_BPF_COEFF3031,  0xf3ecf869},
+{3300000, DIF_BPF_COEFF3233,  0xfe930552},
+{3300000, DIF_BPF_COEFF3435,  0x0b5f0f8f},
+{3300000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 33_quant.dat*/
+
+
+/*case 3400000:*/
+/* BEGIN - DIF BPF register values from 34_quant.dat*/
+{3400000, DIF_BPF_COEFF01,    0xfffffffe},
+{3400000, DIF_BPF_COEFF23,    0xfffd0001},
+{3400000, DIF_BPF_COEFF45,    0x000f002c},
+{3400000, DIF_BPF_COEFF67,    0x0054007d},
+{3400000, DIF_BPF_COEFF89,    0x0093007c},
+{3400000, DIF_BPF_COEFF1011,  0x0024ff82},
+{3400000, DIF_BPF_COEFF1213,  0xfea6fdbb},
+{3400000, DIF_BPF_COEFF1415,  0xfd03fcca},
+{3400000, DIF_BPF_COEFF1617,  0xfd51feb9},
+{3400000, DIF_BPF_COEFF1819,  0x00eb0392},
+{3400000, DIF_BPF_COEFF2021,  0x06270802},
+{3400000, DIF_BPF_COEFF2223,  0x08880750},
+{3400000, DIF_BPF_COEFF2425,  0x044dffdb},
+{3400000, DIF_BPF_COEFF2627,  0xfabdf5f8},
+{3400000, DIF_BPF_COEFF2829,  0xf2a0f193},
+{3400000, DIF_BPF_COEFF3031,  0xf342f78f},
+{3400000, DIF_BPF_COEFF3233,  0xfdc404b9},
+{3400000, DIF_BPF_COEFF3435,  0x0b0e0f78},
+{3400000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 34_quant.dat*/
+
+
+/*case 3500000:*/
+/* BEGIN - DIF BPF register values from 35_quant.dat*/
+{3500000, DIF_BPF_COEFF01,    0xfffffffd},
+{3500000, DIF_BPF_COEFF23,    0xfffafff9},
+{3500000, DIF_BPF_COEFF45,    0x0002001b},
+{3500000, DIF_BPF_COEFF67,    0x0046007d},
+{3500000, DIF_BPF_COEFF89,    0x00ad00ba},
+{3500000, DIF_BPF_COEFF1011,  0x00870000},
+{3500000, DIF_BPF_COEFF1213,  0xff26fe1a},
+{3500000, DIF_BPF_COEFF1415,  0xfd1bfc7e},
+{3500000, DIF_BPF_COEFF1617,  0xfc99fda4},
+{3500000, DIF_BPF_COEFF1819,  0xffa5025c},
+{3500000, DIF_BPF_COEFF2021,  0x054507ad},
+{3500000, DIF_BPF_COEFF2223,  0x08dd0847},
+{3500000, DIF_BPF_COEFF2425,  0x05b80172},
+{3500000, DIF_BPF_COEFF2627,  0xfc2ef6ff},
+{3500000, DIF_BPF_COEFF2829,  0xf313f170},
+{3500000, DIF_BPF_COEFF3031,  0xf2abf6bd},
+{3500000, DIF_BPF_COEFF3233,  0xfcf6041f},
+{3500000, DIF_BPF_COEFF3435,  0x0abc0f61},
+{3500000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 35_quant.dat*/
+
+
+/*case 3600000:*/
+/* BEGIN - DIF BPF register values from 36_quant.dat*/
+{3600000, DIF_BPF_COEFF01,    0xfffffffd},
+{3600000, DIF_BPF_COEFF23,    0xfff8fff3},
+{3600000, DIF_BPF_COEFF45,    0xfff50006},
+{3600000, DIF_BPF_COEFF67,    0x002f006c},
+{3600000, DIF_BPF_COEFF89,    0x00b200e3},
+{3600000, DIF_BPF_COEFF1011,  0x00dc007e},
+{3600000, DIF_BPF_COEFF1213,  0xffb9fea0},
+{3600000, DIF_BPF_COEFF1415,  0xfd6bfc71},
+{3600000, DIF_BPF_COEFF1617,  0xfc17fcb1},
+{3600000, DIF_BPF_COEFF1819,  0xfe65010b},
+{3600000, DIF_BPF_COEFF2021,  0x042d0713},
+{3600000, DIF_BPF_COEFF2223,  0x08ec0906},
+{3600000, DIF_BPF_COEFF2425,  0x07020302},
+{3600000, DIF_BPF_COEFF2627,  0xfdaff823},
+{3600000, DIF_BPF_COEFF2829,  0xf3a7f16a},
+{3600000, DIF_BPF_COEFF3031,  0xf228f5f5},
+{3600000, DIF_BPF_COEFF3233,  0xfc2a0384},
+{3600000, DIF_BPF_COEFF3435,  0x0a670f4a},
+{3600000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 36_quant.dat*/
+
+
+/*case 3700000:*/
+/* BEGIN - DIF BPF register values from 37_quant.dat*/
+{3700000, DIF_BPF_COEFF01,    0x0000fffd},
+{3700000, DIF_BPF_COEFF23,    0xfff7ffef},
+{3700000, DIF_BPF_COEFF45,    0xffe9fff1},
+{3700000, DIF_BPF_COEFF67,    0x0010004d},
+{3700000, DIF_BPF_COEFF89,    0x00a100f2},
+{3700000, DIF_BPF_COEFF1011,  0x011a00f0},
+{3700000, DIF_BPF_COEFF1213,  0x0053ff44},
+{3700000, DIF_BPF_COEFF1415,  0xfdedfca2},
+{3700000, DIF_BPF_COEFF1617,  0xfbd3fbef},
+{3700000, DIF_BPF_COEFF1819,  0xfd39ffae},
+{3700000, DIF_BPF_COEFF2021,  0x02ea0638},
+{3700000, DIF_BPF_COEFF2223,  0x08b50987},
+{3700000, DIF_BPF_COEFF2425,  0x08230483},
+{3700000, DIF_BPF_COEFF2627,  0xff39f960},
+{3700000, DIF_BPF_COEFF2829,  0xf45bf180},
+{3700000, DIF_BPF_COEFF3031,  0xf1b8f537},
+{3700000, DIF_BPF_COEFF3233,  0xfb6102e7},
+{3700000, DIF_BPF_COEFF3435,  0x0a110f32},
+{3700000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 37_quant.dat*/
+
+
+/*case 3800000:*/
+/* BEGIN - DIF BPF register values from 38_quant.dat*/
+{3800000, DIF_BPF_COEFF01,    0x0000fffe},
+{3800000, DIF_BPF_COEFF23,    0xfff9ffee},
+{3800000, DIF_BPF_COEFF45,    0xffe1ffdd},
+{3800000, DIF_BPF_COEFF67,    0xfff00024},
+{3800000, DIF_BPF_COEFF89,    0x007c00e5},
+{3800000, DIF_BPF_COEFF1011,  0x013a014a},
+{3800000, DIF_BPF_COEFF1213,  0x00e6fff8},
+{3800000, DIF_BPF_COEFF1415,  0xfe98fd0f},
+{3800000, DIF_BPF_COEFF1617,  0xfbd3fb67},
+{3800000, DIF_BPF_COEFF1819,  0xfc32fe54},
+{3800000, DIF_BPF_COEFF2021,  0x01880525},
+{3800000, DIF_BPF_COEFF2223,  0x083909c7},
+{3800000, DIF_BPF_COEFF2425,  0x091505ee},
+{3800000, DIF_BPF_COEFF2627,  0x00c7fab3},
+{3800000, DIF_BPF_COEFF2829,  0xf52df1b4},
+{3800000, DIF_BPF_COEFF3031,  0xf15df484},
+{3800000, DIF_BPF_COEFF3233,  0xfa9b0249},
+{3800000, DIF_BPF_COEFF3435,  0x09ba0f19},
+{3800000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 38_quant.dat*/
+
+
+/*case 3900000:*/
+/* BEGIN - DIF BPF register values from 39_quant.dat*/
+{3900000, DIF_BPF_COEFF01,    0x00000000},
+{3900000, DIF_BPF_COEFF23,    0xfffbfff0},
+{3900000, DIF_BPF_COEFF45,    0xffdeffcf},
+{3900000, DIF_BPF_COEFF67,    0xffd1fff6},
+{3900000, DIF_BPF_COEFF89,    0x004800be},
+{3900000, DIF_BPF_COEFF1011,  0x01390184},
+{3900000, DIF_BPF_COEFF1213,  0x016300ac},
+{3900000, DIF_BPF_COEFF1415,  0xff5efdb1},
+{3900000, DIF_BPF_COEFF1617,  0xfc17fb23},
+{3900000, DIF_BPF_COEFF1819,  0xfb5cfd0d},
+{3900000, DIF_BPF_COEFF2021,  0x001703e4},
+{3900000, DIF_BPF_COEFF2223,  0x077b09c4},
+{3900000, DIF_BPF_COEFF2425,  0x09d2073c},
+{3900000, DIF_BPF_COEFF2627,  0x0251fc18},
+{3900000, DIF_BPF_COEFF2829,  0xf61cf203},
+{3900000, DIF_BPF_COEFF3031,  0xf118f3dc},
+{3900000, DIF_BPF_COEFF3233,  0xf9d801aa},
+{3900000, DIF_BPF_COEFF3435,  0x09600eff},
+{3900000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 39_quant.dat*/
+
+
+/*case 4000000:*/
+/* BEGIN - DIF BPF register values from 40_quant.dat*/
+{4000000, DIF_BPF_COEFF01,    0x00000001},
+{4000000, DIF_BPF_COEFF23,    0xfffefff4},
+{4000000, DIF_BPF_COEFF45,    0xffe1ffc8},
+{4000000, DIF_BPF_COEFF67,    0xffbaffca},
+{4000000, DIF_BPF_COEFF89,    0x000b0082},
+{4000000, DIF_BPF_COEFF1011,  0x01170198},
+{4000000, DIF_BPF_COEFF1213,  0x01c10152},
+{4000000, DIF_BPF_COEFF1415,  0x0030fe7b},
+{4000000, DIF_BPF_COEFF1617,  0xfc99fb24},
+{4000000, DIF_BPF_COEFF1819,  0xfac3fbe9},
+{4000000, DIF_BPF_COEFF2021,  0xfea5027f},
+{4000000, DIF_BPF_COEFF2223,  0x0683097f},
+{4000000, DIF_BPF_COEFF2425,  0x0a560867},
+{4000000, DIF_BPF_COEFF2627,  0x03d2fd89},
+{4000000, DIF_BPF_COEFF2829,  0xf723f26f},
+{4000000, DIF_BPF_COEFF3031,  0xf0e8f341},
+{4000000, DIF_BPF_COEFF3233,  0xf919010a},
+{4000000, DIF_BPF_COEFF3435,  0x09060ee5},
+{4000000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 40_quant.dat*/
+
+
+/*case 4100000:*/
+/* BEGIN - DIF BPF register values from 41_quant.dat*/
+{4100000, DIF_BPF_COEFF01,    0x00010002},
+{4100000, DIF_BPF_COEFF23,    0x0002fffb},
+{4100000, DIF_BPF_COEFF45,    0xffe8ffca},
+{4100000, DIF_BPF_COEFF67,    0xffacffa4},
+{4100000, DIF_BPF_COEFF89,    0xffcd0036},
+{4100000, DIF_BPF_COEFF1011,  0x00d70184},
+{4100000, DIF_BPF_COEFF1213,  0x01f601dc},
+{4100000, DIF_BPF_COEFF1415,  0x00ffff60},
+{4100000, DIF_BPF_COEFF1617,  0xfd51fb6d},
+{4100000, DIF_BPF_COEFF1819,  0xfa6efaf5},
+{4100000, DIF_BPF_COEFF2021,  0xfd410103},
+{4100000, DIF_BPF_COEFF2223,  0x055708f9},
+{4100000, DIF_BPF_COEFF2425,  0x0a9e0969},
+{4100000, DIF_BPF_COEFF2627,  0x0543ff02},
+{4100000, DIF_BPF_COEFF2829,  0xf842f2f5},
+{4100000, DIF_BPF_COEFF3031,  0xf0cef2b2},
+{4100000, DIF_BPF_COEFF3233,  0xf85e006b},
+{4100000, DIF_BPF_COEFF3435,  0x08aa0ecb},
+{4100000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 41_quant.dat*/
+
+
+/*case 4200000:*/
+/* BEGIN - DIF BPF register values from 42_quant.dat*/
+{4200000, DIF_BPF_COEFF01,    0x00010003},
+{4200000, DIF_BPF_COEFF23,    0x00050003},
+{4200000, DIF_BPF_COEFF45,    0xfff3ffd3},
+{4200000, DIF_BPF_COEFF67,    0xffaaff8b},
+{4200000, DIF_BPF_COEFF89,    0xff95ffe5},
+{4200000, DIF_BPF_COEFF1011,  0x0080014a},
+{4200000, DIF_BPF_COEFF1213,  0x01fe023f},
+{4200000, DIF_BPF_COEFF1415,  0x01ba0050},
+{4200000, DIF_BPF_COEFF1617,  0xfe35fbf8},
+{4200000, DIF_BPF_COEFF1819,  0xfa62fa3b},
+{4200000, DIF_BPF_COEFF2021,  0xfbf9ff7e},
+{4200000, DIF_BPF_COEFF2223,  0x04010836},
+{4200000, DIF_BPF_COEFF2425,  0x0aa90a3d},
+{4200000, DIF_BPF_COEFF2627,  0x069f007f},
+{4200000, DIF_BPF_COEFF2829,  0xf975f395},
+{4200000, DIF_BPF_COEFF3031,  0xf0cbf231},
+{4200000, DIF_BPF_COEFF3233,  0xf7a9ffcb},
+{4200000, DIF_BPF_COEFF3435,  0x084c0eaf},
+{4200000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 42_quant.dat*/
+
+
+/*case 4300000:*/
+/* BEGIN - DIF BPF register values from 43_quant.dat*/
+{4300000, DIF_BPF_COEFF01,    0x00010003},
+{4300000, DIF_BPF_COEFF23,    0x0008000a},
+{4300000, DIF_BPF_COEFF45,    0x0000ffe4},
+{4300000, DIF_BPF_COEFF67,    0xffb4ff81},
+{4300000, DIF_BPF_COEFF89,    0xff6aff96},
+{4300000, DIF_BPF_COEFF1011,  0x001c00f0},
+{4300000, DIF_BPF_COEFF1213,  0x01d70271},
+{4300000, DIF_BPF_COEFF1415,  0x0254013b},
+{4300000, DIF_BPF_COEFF1617,  0xff36fcbd},
+{4300000, DIF_BPF_COEFF1819,  0xfa9ff9c5},
+{4300000, DIF_BPF_COEFF2021,  0xfadbfdfe},
+{4300000, DIF_BPF_COEFF2223,  0x028c073b},
+{4300000, DIF_BPF_COEFF2425,  0x0a750adf},
+{4300000, DIF_BPF_COEFF2627,  0x07e101fa},
+{4300000, DIF_BPF_COEFF2829,  0xfab8f44e},
+{4300000, DIF_BPF_COEFF3031,  0xf0ddf1be},
+{4300000, DIF_BPF_COEFF3233,  0xf6f9ff2b},
+{4300000, DIF_BPF_COEFF3435,  0x07ed0e94},
+{4300000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 43_quant.dat*/
+
+
+/*case 4400000:*/
+/* BEGIN - DIF BPF register values from 44_quant.dat*/
+{4400000, DIF_BPF_COEFF01,    0x00000003},
+{4400000, DIF_BPF_COEFF23,    0x0009000f},
+{4400000, DIF_BPF_COEFF45,    0x000efff8},
+{4400000, DIF_BPF_COEFF67,    0xffc9ff87},
+{4400000, DIF_BPF_COEFF89,    0xff52ff54},
+{4400000, DIF_BPF_COEFF1011,  0xffb5007e},
+{4400000, DIF_BPF_COEFF1213,  0x01860270},
+{4400000, DIF_BPF_COEFF1415,  0x02c00210},
+{4400000, DIF_BPF_COEFF1617,  0x0044fdb2},
+{4400000, DIF_BPF_COEFF1819,  0xfb22f997},
+{4400000, DIF_BPF_COEFF2021,  0xf9f2fc90},
+{4400000, DIF_BPF_COEFF2223,  0x0102060f},
+{4400000, DIF_BPF_COEFF2425,  0x0a050b4c},
+{4400000, DIF_BPF_COEFF2627,  0x0902036e},
+{4400000, DIF_BPF_COEFF2829,  0xfc0af51e},
+{4400000, DIF_BPF_COEFF3031,  0xf106f15a},
+{4400000, DIF_BPF_COEFF3233,  0xf64efe8b},
+{4400000, DIF_BPF_COEFF3435,  0x078d0e77},
+{4400000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 44_quant.dat*/
+
+
+/*case 4500000:*/
+/* BEGIN - DIF BPF register values from 45_quant.dat*/
+{4500000, DIF_BPF_COEFF01,    0x00000002},
+{4500000, DIF_BPF_COEFF23,    0x00080012},
+{4500000, DIF_BPF_COEFF45,    0x0019000e},
+{4500000, DIF_BPF_COEFF67,    0xffe5ff9e},
+{4500000, DIF_BPF_COEFF89,    0xff4fff25},
+{4500000, DIF_BPF_COEFF1011,  0xff560000},
+{4500000, DIF_BPF_COEFF1213,  0x0112023b},
+{4500000, DIF_BPF_COEFF1415,  0x02f702c0},
+{4500000, DIF_BPF_COEFF1617,  0x014dfec8},
+{4500000, DIF_BPF_COEFF1819,  0xfbe5f9b3},
+{4500000, DIF_BPF_COEFF2021,  0xf947fb41},
+{4500000, DIF_BPF_COEFF2223,  0xff7004b9},
+{4500000, DIF_BPF_COEFF2425,  0x095a0b81},
+{4500000, DIF_BPF_COEFF2627,  0x0a0004d8},
+{4500000, DIF_BPF_COEFF2829,  0xfd65f603},
+{4500000, DIF_BPF_COEFF3031,  0xf144f104},
+{4500000, DIF_BPF_COEFF3233,  0xf5aafdec},
+{4500000, DIF_BPF_COEFF3435,  0x072b0e5a},
+{4500000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 45_quant.dat*/
+
+
+/*case 4600000:*/
+/* BEGIN - DIF BPF register values from 46_quant.dat*/
+{4600000, DIF_BPF_COEFF01,    0x00000001},
+{4600000, DIF_BPF_COEFF23,    0x00060012},
+{4600000, DIF_BPF_COEFF45,    0x00200022},
+{4600000, DIF_BPF_COEFF67,    0x0005ffc1},
+{4600000, DIF_BPF_COEFF89,    0xff61ff10},
+{4600000, DIF_BPF_COEFF1011,  0xff09ff82},
+{4600000, DIF_BPF_COEFF1213,  0x008601d7},
+{4600000, DIF_BPF_COEFF1415,  0x02f50340},
+{4600000, DIF_BPF_COEFF1617,  0x0241fff0},
+{4600000, DIF_BPF_COEFF1819,  0xfcddfa19},
+{4600000, DIF_BPF_COEFF2021,  0xf8e2fa1e},
+{4600000, DIF_BPF_COEFF2223,  0xfde30343},
+{4600000, DIF_BPF_COEFF2425,  0x08790b7f},
+{4600000, DIF_BPF_COEFF2627,  0x0ad50631},
+{4600000, DIF_BPF_COEFF2829,  0xfec7f6fc},
+{4600000, DIF_BPF_COEFF3031,  0xf198f0bd},
+{4600000, DIF_BPF_COEFF3233,  0xf50dfd4e},
+{4600000, DIF_BPF_COEFF3435,  0x06c90e3d},
+{4600000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 46_quant.dat*/
+
+
+/*case 4700000:*/
+/* BEGIN - DIF BPF register values from 47_quant.dat*/
+{4700000, DIF_BPF_COEFF01,    0x0000ffff},
+{4700000, DIF_BPF_COEFF23,    0x0003000f},
+{4700000, DIF_BPF_COEFF45,    0x00220030},
+{4700000, DIF_BPF_COEFF67,    0x0025ffed},
+{4700000, DIF_BPF_COEFF89,    0xff87ff15},
+{4700000, DIF_BPF_COEFF1011,  0xfed6ff10},
+{4700000, DIF_BPF_COEFF1213,  0xffed014c},
+{4700000, DIF_BPF_COEFF1415,  0x02b90386},
+{4700000, DIF_BPF_COEFF1617,  0x03110119},
+{4700000, DIF_BPF_COEFF1819,  0xfdfefac4},
+{4700000, DIF_BPF_COEFF2021,  0xf8c6f92f},
+{4700000, DIF_BPF_COEFF2223,  0xfc6701b7},
+{4700000, DIF_BPF_COEFF2425,  0x07670b44},
+{4700000, DIF_BPF_COEFF2627,  0x0b7e0776},
+{4700000, DIF_BPF_COEFF2829,  0x002df807},
+{4700000, DIF_BPF_COEFF3031,  0xf200f086},
+{4700000, DIF_BPF_COEFF3233,  0xf477fcb1},
+{4700000, DIF_BPF_COEFF3435,  0x06650e1e},
+{4700000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 47_quant.dat*/
+
+
+/*case 4800000:*/
+/* BEGIN - DIF BPF register values from 48_quant.dat*/
+{4800000, DIF_BPF_COEFF01,    0xfffffffe},
+{4800000, DIF_BPF_COEFF23,    0xffff0009},
+{4800000, DIF_BPF_COEFF45,    0x001e0038},
+{4800000, DIF_BPF_COEFF67,    0x003f001b},
+{4800000, DIF_BPF_COEFF89,    0xffbcff36},
+{4800000, DIF_BPF_COEFF1011,  0xfec2feb6},
+{4800000, DIF_BPF_COEFF1213,  0xff5600a5},
+{4800000, DIF_BPF_COEFF1415,  0x0248038d},
+{4800000, DIF_BPF_COEFF1617,  0x03b00232},
+{4800000, DIF_BPF_COEFF1819,  0xff39fbab},
+{4800000, DIF_BPF_COEFF2021,  0xf8f4f87f},
+{4800000, DIF_BPF_COEFF2223,  0xfb060020},
+{4800000, DIF_BPF_COEFF2425,  0x062a0ad2},
+{4800000, DIF_BPF_COEFF2627,  0x0bf908a3},
+{4800000, DIF_BPF_COEFF2829,  0x0192f922},
+{4800000, DIF_BPF_COEFF3031,  0xf27df05e},
+{4800000, DIF_BPF_COEFF3233,  0xf3e8fc14},
+{4800000, DIF_BPF_COEFF3435,  0x06000e00},
+{4800000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 48_quant.dat*/
+
+
+/*case 4900000:*/
+/* BEGIN - DIF BPF register values from 49_quant.dat*/
+{4900000, DIF_BPF_COEFF01,    0xfffffffd},
+{4900000, DIF_BPF_COEFF23,    0xfffc0002},
+{4900000, DIF_BPF_COEFF45,    0x00160037},
+{4900000, DIF_BPF_COEFF67,    0x00510046},
+{4900000, DIF_BPF_COEFF89,    0xfff9ff6d},
+{4900000, DIF_BPF_COEFF1011,  0xfed0fe7c},
+{4900000, DIF_BPF_COEFF1213,  0xfecefff0},
+{4900000, DIF_BPF_COEFF1415,  0x01aa0356},
+{4900000, DIF_BPF_COEFF1617,  0x0413032b},
+{4900000, DIF_BPF_COEFF1819,  0x007ffcc5},
+{4900000, DIF_BPF_COEFF2021,  0xf96cf812},
+{4900000, DIF_BPF_COEFF2223,  0xf9cefe87},
+{4900000, DIF_BPF_COEFF2425,  0x04c90a2c},
+{4900000, DIF_BPF_COEFF2627,  0x0c4309b4},
+{4900000, DIF_BPF_COEFF2829,  0x02f3fa4a},
+{4900000, DIF_BPF_COEFF3031,  0xf30ef046},
+{4900000, DIF_BPF_COEFF3233,  0xf361fb7a},
+{4900000, DIF_BPF_COEFF3435,  0x059b0de0},
+{4900000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 49_quant.dat*/
+
+
+/*case 5000000:*/
+/* BEGIN - DIF BPF register values from 50_quant.dat*/
+{5000000, DIF_BPF_COEFF01,    0xfffffffd},
+{5000000, DIF_BPF_COEFF23,    0xfff9fffa},
+{5000000, DIF_BPF_COEFF45,    0x000a002d},
+{5000000, DIF_BPF_COEFF67,    0x00570067},
+{5000000, DIF_BPF_COEFF89,    0x0037ffb5},
+{5000000, DIF_BPF_COEFF1011,  0xfefffe68},
+{5000000, DIF_BPF_COEFF1213,  0xfe62ff3d},
+{5000000, DIF_BPF_COEFF1415,  0x00ec02e3},
+{5000000, DIF_BPF_COEFF1617,  0x043503f6},
+{5000000, DIF_BPF_COEFF1819,  0x01befe05},
+{5000000, DIF_BPF_COEFF2021,  0xfa27f7ee},
+{5000000, DIF_BPF_COEFF2223,  0xf8c6fcf8},
+{5000000, DIF_BPF_COEFF2425,  0x034c0954},
+{5000000, DIF_BPF_COEFF2627,  0x0c5c0aa4},
+{5000000, DIF_BPF_COEFF2829,  0x044cfb7e},
+{5000000, DIF_BPF_COEFF3031,  0xf3b1f03f},
+{5000000, DIF_BPF_COEFF3233,  0xf2e2fae1},
+{5000000, DIF_BPF_COEFF3435,  0x05340dc0},
+{5000000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 50_quant.dat*/
+
+
+/*case 5100000:*/
+/* BEGIN - DIF BPF register values from 51_quant.dat*/
+{5100000, DIF_BPF_COEFF01,    0x0000fffd},
+{5100000, DIF_BPF_COEFF23,    0xfff8fff4},
+{5100000, DIF_BPF_COEFF45,    0xfffd001e},
+{5100000, DIF_BPF_COEFF67,    0x0051007b},
+{5100000, DIF_BPF_COEFF89,    0x006e0006},
+{5100000, DIF_BPF_COEFF1011,  0xff48fe7c},
+{5100000, DIF_BPF_COEFF1213,  0xfe1bfe9a},
+{5100000, DIF_BPF_COEFF1415,  0x001d023e},
+{5100000, DIF_BPF_COEFF1617,  0x04130488},
+{5100000, DIF_BPF_COEFF1819,  0x02e6ff5b},
+{5100000, DIF_BPF_COEFF2021,  0xfb1ef812},
+{5100000, DIF_BPF_COEFF2223,  0xf7f7fb7f},
+{5100000, DIF_BPF_COEFF2425,  0x01bc084e},
+{5100000, DIF_BPF_COEFF2627,  0x0c430b72},
+{5100000, DIF_BPF_COEFF2829,  0x059afcba},
+{5100000, DIF_BPF_COEFF3031,  0xf467f046},
+{5100000, DIF_BPF_COEFF3233,  0xf26cfa4a},
+{5100000, DIF_BPF_COEFF3435,  0x04cd0da0},
+{5100000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 51_quant.dat*/
+
+
+/*case 5200000:*/
+/* BEGIN - DIF BPF register values from 52_quant.dat*/
+{5200000, DIF_BPF_COEFF01,    0x0000fffe},
+{5200000, DIF_BPF_COEFF23,    0xfff8ffef},
+{5200000, DIF_BPF_COEFF45,    0xfff00009},
+{5200000, DIF_BPF_COEFF67,    0x003f007f},
+{5200000, DIF_BPF_COEFF89,    0x00980056},
+{5200000, DIF_BPF_COEFF1011,  0xffa5feb6},
+{5200000, DIF_BPF_COEFF1213,  0xfe00fe15},
+{5200000, DIF_BPF_COEFF1415,  0xff4b0170},
+{5200000, DIF_BPF_COEFF1617,  0x03b004d7},
+{5200000, DIF_BPF_COEFF1819,  0x03e800b9},
+{5200000, DIF_BPF_COEFF2021,  0xfc48f87f},
+{5200000, DIF_BPF_COEFF2223,  0xf768fa23},
+{5200000, DIF_BPF_COEFF2425,  0x0022071f},
+{5200000, DIF_BPF_COEFF2627,  0x0bf90c1b},
+{5200000, DIF_BPF_COEFF2829,  0x06dafdfd},
+{5200000, DIF_BPF_COEFF3031,  0xf52df05e},
+{5200000, DIF_BPF_COEFF3233,  0xf1fef9b5},
+{5200000, DIF_BPF_COEFF3435,  0x04640d7f},
+{5200000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 52_quant.dat*/
+
+
+/*case 5300000:*/
+/* BEGIN - DIF BPF register values from 53_quant.dat*/
+{5300000, DIF_BPF_COEFF01,    0x0000ffff},
+{5300000, DIF_BPF_COEFF23,    0xfff9ffee},
+{5300000, DIF_BPF_COEFF45,    0xffe6fff3},
+{5300000, DIF_BPF_COEFF67,    0x00250072},
+{5300000, DIF_BPF_COEFF89,    0x00af009c},
+{5300000, DIF_BPF_COEFF1011,  0x000cff10},
+{5300000, DIF_BPF_COEFF1213,  0xfe13fdb8},
+{5300000, DIF_BPF_COEFF1415,  0xfe870089},
+{5300000, DIF_BPF_COEFF1617,  0x031104e1},
+{5300000, DIF_BPF_COEFF1819,  0x04b8020f},
+{5300000, DIF_BPF_COEFF2021,  0xfd98f92f},
+{5300000, DIF_BPF_COEFF2223,  0xf71df8f0},
+{5300000, DIF_BPF_COEFF2425,  0xfe8805ce},
+{5300000, DIF_BPF_COEFF2627,  0x0b7e0c9c},
+{5300000, DIF_BPF_COEFF2829,  0x0808ff44},
+{5300000, DIF_BPF_COEFF3031,  0xf603f086},
+{5300000, DIF_BPF_COEFF3233,  0xf19af922},
+{5300000, DIF_BPF_COEFF3435,  0x03fb0d5e},
+{5300000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 53_quant.dat*/
+
+
+/*case 5400000:*/
+/* BEGIN - DIF BPF register values from 54_quant.dat*/
+{5400000, DIF_BPF_COEFF01,    0x00000001},
+{5400000, DIF_BPF_COEFF23,    0xfffcffef},
+{5400000, DIF_BPF_COEFF45,    0xffe0ffe0},
+{5400000, DIF_BPF_COEFF67,    0x00050056},
+{5400000, DIF_BPF_COEFF89,    0x00b000d1},
+{5400000, DIF_BPF_COEFF1011,  0x0071ff82},
+{5400000, DIF_BPF_COEFF1213,  0xfe53fd8c},
+{5400000, DIF_BPF_COEFF1415,  0xfddfff99},
+{5400000, DIF_BPF_COEFF1617,  0x024104a3},
+{5400000, DIF_BPF_COEFF1819,  0x054a034d},
+{5400000, DIF_BPF_COEFF2021,  0xff01fa1e},
+{5400000, DIF_BPF_COEFF2223,  0xf717f7ed},
+{5400000, DIF_BPF_COEFF2425,  0xfcf50461},
+{5400000, DIF_BPF_COEFF2627,  0x0ad50cf4},
+{5400000, DIF_BPF_COEFF2829,  0x0921008d},
+{5400000, DIF_BPF_COEFF3031,  0xf6e7f0bd},
+{5400000, DIF_BPF_COEFF3233,  0xf13ff891},
+{5400000, DIF_BPF_COEFF3435,  0x03920d3b},
+{5400000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 54_quant.dat*/
+
+
+/*case 5500000:*/
+/* BEGIN - DIF BPF register values from 55_quant.dat*/
+{5500000, DIF_BPF_COEFF01,    0x00010002},
+{5500000, DIF_BPF_COEFF23,    0xfffffff3},
+{5500000, DIF_BPF_COEFF45,    0xffdeffd1},
+{5500000, DIF_BPF_COEFF67,    0xffe5002f},
+{5500000, DIF_BPF_COEFF89,    0x009c00ed},
+{5500000, DIF_BPF_COEFF1011,  0x00cb0000},
+{5500000, DIF_BPF_COEFF1213,  0xfebafd94},
+{5500000, DIF_BPF_COEFF1415,  0xfd61feb0},
+{5500000, DIF_BPF_COEFF1617,  0x014d0422},
+{5500000, DIF_BPF_COEFF1819,  0x05970464},
+{5500000, DIF_BPF_COEFF2021,  0x0074fb41},
+{5500000, DIF_BPF_COEFF2223,  0xf759f721},
+{5500000, DIF_BPF_COEFF2425,  0xfb7502de},
+{5500000, DIF_BPF_COEFF2627,  0x0a000d21},
+{5500000, DIF_BPF_COEFF2829,  0x0a2201d4},
+{5500000, DIF_BPF_COEFF3031,  0xf7d9f104},
+{5500000, DIF_BPF_COEFF3233,  0xf0edf804},
+{5500000, DIF_BPF_COEFF3435,  0x03280d19},
+{5500000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 55_quant.dat*/
+
+
+/*case 5600000:*/
+/* BEGIN - DIF BPF register values from 56_quant.dat*/
+{5600000, DIF_BPF_COEFF01,    0x00010003},
+{5600000, DIF_BPF_COEFF23,    0x0003fffa},
+{5600000, DIF_BPF_COEFF45,    0xffe3ffc9},
+{5600000, DIF_BPF_COEFF67,    0xffc90002},
+{5600000, DIF_BPF_COEFF89,    0x007500ef},
+{5600000, DIF_BPF_COEFF1011,  0x010e007e},
+{5600000, DIF_BPF_COEFF1213,  0xff3dfdcf},
+{5600000, DIF_BPF_COEFF1415,  0xfd16fddd},
+{5600000, DIF_BPF_COEFF1617,  0x00440365},
+{5600000, DIF_BPF_COEFF1819,  0x059b0548},
+{5600000, DIF_BPF_COEFF2021,  0x01e3fc90},
+{5600000, DIF_BPF_COEFF2223,  0xf7dff691},
+{5600000, DIF_BPF_COEFF2425,  0xfa0f014d},
+{5600000, DIF_BPF_COEFF2627,  0x09020d23},
+{5600000, DIF_BPF_COEFF2829,  0x0b0a0318},
+{5600000, DIF_BPF_COEFF3031,  0xf8d7f15a},
+{5600000, DIF_BPF_COEFF3233,  0xf0a5f779},
+{5600000, DIF_BPF_COEFF3435,  0x02bd0cf6},
+{5600000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 56_quant.dat*/
+
+
+/*case 5700000:*/
+/* BEGIN - DIF BPF register values from 57_quant.dat*/
+{5700000, DIF_BPF_COEFF01,    0x00010003},
+{5700000, DIF_BPF_COEFF23,    0x00060001},
+{5700000, DIF_BPF_COEFF45,    0xffecffc9},
+{5700000, DIF_BPF_COEFF67,    0xffb4ffd4},
+{5700000, DIF_BPF_COEFF89,    0x004000d5},
+{5700000, DIF_BPF_COEFF1011,  0x013600f0},
+{5700000, DIF_BPF_COEFF1213,  0xffd3fe39},
+{5700000, DIF_BPF_COEFF1415,  0xfd04fd31},
+{5700000, DIF_BPF_COEFF1617,  0xff360277},
+{5700000, DIF_BPF_COEFF1819,  0x055605ef},
+{5700000, DIF_BPF_COEFF2021,  0x033efdfe},
+{5700000, DIF_BPF_COEFF2223,  0xf8a5f642},
+{5700000, DIF_BPF_COEFF2425,  0xf8cbffb6},
+{5700000, DIF_BPF_COEFF2627,  0x07e10cfb},
+{5700000, DIF_BPF_COEFF2829,  0x0bd50456},
+{5700000, DIF_BPF_COEFF3031,  0xf9dff1be},
+{5700000, DIF_BPF_COEFF3233,  0xf067f6f2},
+{5700000, DIF_BPF_COEFF3435,  0x02520cd2},
+{5700000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 57_quant.dat*/
+
+
+/*case 5800000:*/
+/* BEGIN - DIF BPF register values from 58_quant.dat*/
+{5800000, DIF_BPF_COEFF01,    0x00000003},
+{5800000, DIF_BPF_COEFF23,    0x00080009},
+{5800000, DIF_BPF_COEFF45,    0xfff8ffd2},
+{5800000, DIF_BPF_COEFF67,    0xffaaffac},
+{5800000, DIF_BPF_COEFF89,    0x000200a3},
+{5800000, DIF_BPF_COEFF1011,  0x013c014a},
+{5800000, DIF_BPF_COEFF1213,  0x006dfec9},
+{5800000, DIF_BPF_COEFF1415,  0xfd2bfcb7},
+{5800000, DIF_BPF_COEFF1617,  0xfe350165},
+{5800000, DIF_BPF_COEFF1819,  0x04cb0651},
+{5800000, DIF_BPF_COEFF2021,  0x0477ff7e},
+{5800000, DIF_BPF_COEFF2223,  0xf9a5f635},
+{5800000, DIF_BPF_COEFF2425,  0xf7b1fe20},
+{5800000, DIF_BPF_COEFF2627,  0x069f0ca8},
+{5800000, DIF_BPF_COEFF2829,  0x0c81058b},
+{5800000, DIF_BPF_COEFF3031,  0xfaf0f231},
+{5800000, DIF_BPF_COEFF3233,  0xf033f66d},
+{5800000, DIF_BPF_COEFF3435,  0x01e60cae},
+{5800000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 58_quant.dat*/
+
+
+/*case 5900000:*/
+/* BEGIN - DIF BPF register values from 59_quant.dat*/
+{5900000, DIF_BPF_COEFF01,    0x00000002},
+{5900000, DIF_BPF_COEFF23,    0x0009000e},
+{5900000, DIF_BPF_COEFF45,    0x0005ffe1},
+{5900000, DIF_BPF_COEFF67,    0xffacff90},
+{5900000, DIF_BPF_COEFF89,    0xffc5005f},
+{5900000, DIF_BPF_COEFF1011,  0x01210184},
+{5900000, DIF_BPF_COEFF1213,  0x00fcff72},
+{5900000, DIF_BPF_COEFF1415,  0xfd8afc77},
+{5900000, DIF_BPF_COEFF1617,  0xfd51003f},
+{5900000, DIF_BPF_COEFF1819,  0x04020669},
+{5900000, DIF_BPF_COEFF2021,  0x05830103},
+{5900000, DIF_BPF_COEFF2223,  0xfad7f66b},
+{5900000, DIF_BPF_COEFF2425,  0xf6c8fc93},
+{5900000, DIF_BPF_COEFF2627,  0x05430c2b},
+{5900000, DIF_BPF_COEFF2829,  0x0d0d06b5},
+{5900000, DIF_BPF_COEFF3031,  0xfc08f2b2},
+{5900000, DIF_BPF_COEFF3233,  0xf00af5ec},
+{5900000, DIF_BPF_COEFF3435,  0x017b0c89},
+{5900000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 59_quant.dat*/
+
+
+/*case 6000000:*/
+/* BEGIN - DIF BPF register values from 60_quant.dat*/
+{6000000, DIF_BPF_COEFF01,    0x00000001},
+{6000000, DIF_BPF_COEFF23,    0x00070012},
+{6000000, DIF_BPF_COEFF45,    0x0012fff5},
+{6000000, DIF_BPF_COEFF67,    0xffbaff82},
+{6000000, DIF_BPF_COEFF89,    0xff8e000f},
+{6000000, DIF_BPF_COEFF1011,  0x00e80198},
+{6000000, DIF_BPF_COEFF1213,  0x01750028},
+{6000000, DIF_BPF_COEFF1415,  0xfe18fc75},
+{6000000, DIF_BPF_COEFF1617,  0xfc99ff15},
+{6000000, DIF_BPF_COEFF1819,  0x03050636},
+{6000000, DIF_BPF_COEFF2021,  0x0656027f},
+{6000000, DIF_BPF_COEFF2223,  0xfc32f6e2},
+{6000000, DIF_BPF_COEFF2425,  0xf614fb17},
+{6000000, DIF_BPF_COEFF2627,  0x03d20b87},
+{6000000, DIF_BPF_COEFF2829,  0x0d7707d2},
+{6000000, DIF_BPF_COEFF3031,  0xfd26f341},
+{6000000, DIF_BPF_COEFF3233,  0xefeaf56f},
+{6000000, DIF_BPF_COEFF3435,  0x010f0c64},
+{6000000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 60_quant.dat*/
+
+
+/*case 6100000:*/
+/* BEGIN - DIF BPF register values from 61_quant.dat*/
+{6100000, DIF_BPF_COEFF01,    0xffff0000},
+{6100000, DIF_BPF_COEFF23,    0x00050012},
+{6100000, DIF_BPF_COEFF45,    0x001c000b},
+{6100000, DIF_BPF_COEFF67,    0xffd1ff84},
+{6100000, DIF_BPF_COEFF89,    0xff66ffbe},
+{6100000, DIF_BPF_COEFF1011,  0x00960184},
+{6100000, DIF_BPF_COEFF1213,  0x01cd00da},
+{6100000, DIF_BPF_COEFF1415,  0xfeccfcb2},
+{6100000, DIF_BPF_COEFF1617,  0xfc17fdf9},
+{6100000, DIF_BPF_COEFF1819,  0x01e005bc},
+{6100000, DIF_BPF_COEFF2021,  0x06e703e4},
+{6100000, DIF_BPF_COEFF2223,  0xfdabf798},
+{6100000, DIF_BPF_COEFF2425,  0xf599f9b3},
+{6100000, DIF_BPF_COEFF2627,  0x02510abd},
+{6100000, DIF_BPF_COEFF2829,  0x0dbf08df},
+{6100000, DIF_BPF_COEFF3031,  0xfe48f3dc},
+{6100000, DIF_BPF_COEFF3233,  0xefd5f4f6},
+{6100000, DIF_BPF_COEFF3435,  0x00a20c3e},
+{6100000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 61_quant.dat*/
+
+
+/*case 6200000:*/
+/* BEGIN - DIF BPF register values from 62_quant.dat*/
+{6200000, DIF_BPF_COEFF01,    0xfffffffe},
+{6200000, DIF_BPF_COEFF23,    0x0002000f},
+{6200000, DIF_BPF_COEFF45,    0x0021001f},
+{6200000, DIF_BPF_COEFF67,    0xfff0ff97},
+{6200000, DIF_BPF_COEFF89,    0xff50ff74},
+{6200000, DIF_BPF_COEFF1011,  0x0034014a},
+{6200000, DIF_BPF_COEFF1213,  0x01fa0179},
+{6200000, DIF_BPF_COEFF1415,  0xff97fd2a},
+{6200000, DIF_BPF_COEFF1617,  0xfbd3fcfa},
+{6200000, DIF_BPF_COEFF1819,  0x00a304fe},
+{6200000, DIF_BPF_COEFF2021,  0x07310525},
+{6200000, DIF_BPF_COEFF2223,  0xff37f886},
+{6200000, DIF_BPF_COEFF2425,  0xf55cf86e},
+{6200000, DIF_BPF_COEFF2627,  0x00c709d0},
+{6200000, DIF_BPF_COEFF2829,  0x0de209db},
+{6200000, DIF_BPF_COEFF3031,  0xff6df484},
+{6200000, DIF_BPF_COEFF3233,  0xefcbf481},
+{6200000, DIF_BPF_COEFF3435,  0x00360c18},
+{6200000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 62_quant.dat*/
+
+
+/*case 6300000:*/
+/* BEGIN - DIF BPF register values from 63_quant.dat*/
+{6300000, DIF_BPF_COEFF01,    0xfffffffd},
+{6300000, DIF_BPF_COEFF23,    0xfffe000a},
+{6300000, DIF_BPF_COEFF45,    0x0021002f},
+{6300000, DIF_BPF_COEFF67,    0x0010ffb8},
+{6300000, DIF_BPF_COEFF89,    0xff50ff3b},
+{6300000, DIF_BPF_COEFF1011,  0xffcc00f0},
+{6300000, DIF_BPF_COEFF1213,  0x01fa01fa},
+{6300000, DIF_BPF_COEFF1415,  0x0069fdd4},
+{6300000, DIF_BPF_COEFF1617,  0xfbd3fc26},
+{6300000, DIF_BPF_COEFF1819,  0xff5d0407},
+{6300000, DIF_BPF_COEFF2021,  0x07310638},
+{6300000, DIF_BPF_COEFF2223,  0x00c9f9a8},
+{6300000, DIF_BPF_COEFF2425,  0xf55cf74e},
+{6300000, DIF_BPF_COEFF2627,  0xff3908c3},
+{6300000, DIF_BPF_COEFF2829,  0x0de20ac3},
+{6300000, DIF_BPF_COEFF3031,  0x0093f537},
+{6300000, DIF_BPF_COEFF3233,  0xefcbf410},
+{6300000, DIF_BPF_COEFF3435,  0xffca0bf2},
+{6300000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 63_quant.dat*/
+
+
+/*case 6400000:*/
+/* BEGIN - DIF BPF register values from 64_quant.dat*/
+{6400000, DIF_BPF_COEFF01,    0xfffffffd},
+{6400000, DIF_BPF_COEFF23,    0xfffb0003},
+{6400000, DIF_BPF_COEFF45,    0x001c0037},
+{6400000, DIF_BPF_COEFF67,    0x002fffe2},
+{6400000, DIF_BPF_COEFF89,    0xff66ff17},
+{6400000, DIF_BPF_COEFF1011,  0xff6a007e},
+{6400000, DIF_BPF_COEFF1213,  0x01cd0251},
+{6400000, DIF_BPF_COEFF1415,  0x0134fea5},
+{6400000, DIF_BPF_COEFF1617,  0xfc17fb8b},
+{6400000, DIF_BPF_COEFF1819,  0xfe2002e0},
+{6400000, DIF_BPF_COEFF2021,  0x06e70713},
+{6400000, DIF_BPF_COEFF2223,  0x0255faf5},
+{6400000, DIF_BPF_COEFF2425,  0xf599f658},
+{6400000, DIF_BPF_COEFF2627,  0xfdaf0799},
+{6400000, DIF_BPF_COEFF2829,  0x0dbf0b96},
+{6400000, DIF_BPF_COEFF3031,  0x01b8f5f5},
+{6400000, DIF_BPF_COEFF3233,  0xefd5f3a3},
+{6400000, DIF_BPF_COEFF3435,  0xff5e0bca},
+{6400000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 64_quant.dat*/
+
+
+/*case 6500000:*/
+/* BEGIN - DIF BPF register values from 65_quant.dat*/
+{6500000, DIF_BPF_COEFF01,    0x0000fffd},
+{6500000, DIF_BPF_COEFF23,    0xfff9fffb},
+{6500000, DIF_BPF_COEFF45,    0x00120037},
+{6500000, DIF_BPF_COEFF67,    0x00460010},
+{6500000, DIF_BPF_COEFF89,    0xff8eff0f},
+{6500000, DIF_BPF_COEFF1011,  0xff180000},
+{6500000, DIF_BPF_COEFF1213,  0x01750276},
+{6500000, DIF_BPF_COEFF1415,  0x01e8ff8d},
+{6500000, DIF_BPF_COEFF1617,  0xfc99fb31},
+{6500000, DIF_BPF_COEFF1819,  0xfcfb0198},
+{6500000, DIF_BPF_COEFF2021,  0x065607ad},
+{6500000, DIF_BPF_COEFF2223,  0x03cefc64},
+{6500000, DIF_BPF_COEFF2425,  0xf614f592},
+{6500000, DIF_BPF_COEFF2627,  0xfc2e0656},
+{6500000, DIF_BPF_COEFF2829,  0x0d770c52},
+{6500000, DIF_BPF_COEFF3031,  0x02daf6bd},
+{6500000, DIF_BPF_COEFF3233,  0xefeaf33b},
+{6500000, DIF_BPF_COEFF3435,  0xfef10ba3},
+{6500000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 65_quant.dat*/
+
+
+/*case 6600000:*/
+/* BEGIN - DIF BPF register values from 66_quant.dat*/
+{6600000, DIF_BPF_COEFF01,    0x0000fffe},
+{6600000, DIF_BPF_COEFF23,    0xfff7fff5},
+{6600000, DIF_BPF_COEFF45,    0x0005002f},
+{6600000, DIF_BPF_COEFF67,    0x0054003c},
+{6600000, DIF_BPF_COEFF89,    0xffc5ff22},
+{6600000, DIF_BPF_COEFF1011,  0xfedfff82},
+{6600000, DIF_BPF_COEFF1213,  0x00fc0267},
+{6600000, DIF_BPF_COEFF1415,  0x0276007e},
+{6600000, DIF_BPF_COEFF1617,  0xfd51fb1c},
+{6600000, DIF_BPF_COEFF1819,  0xfbfe003e},
+{6600000, DIF_BPF_COEFF2021,  0x05830802},
+{6600000, DIF_BPF_COEFF2223,  0x0529fdec},
+{6600000, DIF_BPF_COEFF2425,  0xf6c8f4fe},
+{6600000, DIF_BPF_COEFF2627,  0xfabd04ff},
+{6600000, DIF_BPF_COEFF2829,  0x0d0d0cf6},
+{6600000, DIF_BPF_COEFF3031,  0x03f8f78f},
+{6600000, DIF_BPF_COEFF3233,  0xf00af2d7},
+{6600000, DIF_BPF_COEFF3435,  0xfe850b7b},
+{6600000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 66_quant.dat*/
+
+
+/*case 6700000:*/
+/* BEGIN - DIF BPF register values from 67_quant.dat*/
+{6700000, DIF_BPF_COEFF01,    0x0000ffff},
+{6700000, DIF_BPF_COEFF23,    0xfff8fff0},
+{6700000, DIF_BPF_COEFF45,    0xfff80020},
+{6700000, DIF_BPF_COEFF67,    0x00560060},
+{6700000, DIF_BPF_COEFF89,    0x0002ff4e},
+{6700000, DIF_BPF_COEFF1011,  0xfec4ff10},
+{6700000, DIF_BPF_COEFF1213,  0x006d0225},
+{6700000, DIF_BPF_COEFF1415,  0x02d50166},
+{6700000, DIF_BPF_COEFF1617,  0xfe35fb4e},
+{6700000, DIF_BPF_COEFF1819,  0xfb35fee1},
+{6700000, DIF_BPF_COEFF2021,  0x0477080e},
+{6700000, DIF_BPF_COEFF2223,  0x065bff82},
+{6700000, DIF_BPF_COEFF2425,  0xf7b1f4a0},
+{6700000, DIF_BPF_COEFF2627,  0xf9610397},
+{6700000, DIF_BPF_COEFF2829,  0x0c810d80},
+{6700000, DIF_BPF_COEFF3031,  0x0510f869},
+{6700000, DIF_BPF_COEFF3233,  0xf033f278},
+{6700000, DIF_BPF_COEFF3435,  0xfe1a0b52},
+{6700000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 67_quant.dat*/
+
+
+/*case 6800000:*/
+/* BEGIN - DIF BPF register values from 68_quant.dat*/
+{6800000, DIF_BPF_COEFF01,    0x00010000},
+{6800000, DIF_BPF_COEFF23,    0xfffaffee},
+{6800000, DIF_BPF_COEFF45,    0xffec000c},
+{6800000, DIF_BPF_COEFF67,    0x004c0078},
+{6800000, DIF_BPF_COEFF89,    0x0040ff8e},
+{6800000, DIF_BPF_COEFF1011,  0xfecafeb6},
+{6800000, DIF_BPF_COEFF1213,  0xffd301b6},
+{6800000, DIF_BPF_COEFF1415,  0x02fc0235},
+{6800000, DIF_BPF_COEFF1617,  0xff36fbc5},
+{6800000, DIF_BPF_COEFF1819,  0xfaaafd90},
+{6800000, DIF_BPF_COEFF2021,  0x033e07d2},
+{6800000, DIF_BPF_COEFF2223,  0x075b011b},
+{6800000, DIF_BPF_COEFF2425,  0xf8cbf47a},
+{6800000, DIF_BPF_COEFF2627,  0xf81f0224},
+{6800000, DIF_BPF_COEFF2829,  0x0bd50def},
+{6800000, DIF_BPF_COEFF3031,  0x0621f94b},
+{6800000, DIF_BPF_COEFF3233,  0xf067f21e},
+{6800000, DIF_BPF_COEFF3435,  0xfdae0b29},
+{6800000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 68_quant.dat*/
+
+
+/*case 6900000:*/
+/* BEGIN - DIF BPF register values from 69_quant.dat*/
+{6900000, DIF_BPF_COEFF01,    0x00010001},
+{6900000, DIF_BPF_COEFF23,    0xfffdffef},
+{6900000, DIF_BPF_COEFF45,    0xffe3fff6},
+{6900000, DIF_BPF_COEFF67,    0x0037007f},
+{6900000, DIF_BPF_COEFF89,    0x0075ffdc},
+{6900000, DIF_BPF_COEFF1011,  0xfef2fe7c},
+{6900000, DIF_BPF_COEFF1213,  0xff3d0122},
+{6900000, DIF_BPF_COEFF1415,  0x02ea02dd},
+{6900000, DIF_BPF_COEFF1617,  0x0044fc79},
+{6900000, DIF_BPF_COEFF1819,  0xfa65fc5d},
+{6900000, DIF_BPF_COEFF2021,  0x01e3074e},
+{6900000, DIF_BPF_COEFF2223,  0x082102ad},
+{6900000, DIF_BPF_COEFF2425,  0xfa0ff48c},
+{6900000, DIF_BPF_COEFF2627,  0xf6fe00a9},
+{6900000, DIF_BPF_COEFF2829,  0x0b0a0e43},
+{6900000, DIF_BPF_COEFF3031,  0x0729fa33},
+{6900000, DIF_BPF_COEFF3233,  0xf0a5f1c9},
+{6900000, DIF_BPF_COEFF3435,  0xfd430b00},
+{6900000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 69_quant.dat*/
+
+
+/*case 7000000:*/
+/* BEGIN - DIF BPF register values from 70_quant.dat*/
+{7000000, DIF_BPF_COEFF01,    0x00010002},
+{7000000, DIF_BPF_COEFF23,    0x0001fff3},
+{7000000, DIF_BPF_COEFF45,    0xffdeffe2},
+{7000000, DIF_BPF_COEFF67,    0x001b0076},
+{7000000, DIF_BPF_COEFF89,    0x009c002d},
+{7000000, DIF_BPF_COEFF1011,  0xff35fe68},
+{7000000, DIF_BPF_COEFF1213,  0xfeba0076},
+{7000000, DIF_BPF_COEFF1415,  0x029f0352},
+{7000000, DIF_BPF_COEFF1617,  0x014dfd60},
+{7000000, DIF_BPF_COEFF1819,  0xfa69fb53},
+{7000000, DIF_BPF_COEFF2021,  0x00740688},
+{7000000, DIF_BPF_COEFF2223,  0x08a7042d},
+{7000000, DIF_BPF_COEFF2425,  0xfb75f4d6},
+{7000000, DIF_BPF_COEFF2627,  0xf600ff2d},
+{7000000, DIF_BPF_COEFF2829,  0x0a220e7a},
+{7000000, DIF_BPF_COEFF3031,  0x0827fb22},
+{7000000, DIF_BPF_COEFF3233,  0xf0edf17a},
+{7000000, DIF_BPF_COEFF3435,  0xfcd80ad6},
+{7000000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 70_quant.dat*/
+
+
+/*case 7100000:*/
+/* BEGIN - DIF BPF register values from 71_quant.dat*/
+{7100000, DIF_BPF_COEFF01,    0x00000003},
+{7100000, DIF_BPF_COEFF23,    0x0004fff9},
+{7100000, DIF_BPF_COEFF45,    0xffe0ffd2},
+{7100000, DIF_BPF_COEFF67,    0xfffb005e},
+{7100000, DIF_BPF_COEFF89,    0x00b0007a},
+{7100000, DIF_BPF_COEFF1011,  0xff8ffe7c},
+{7100000, DIF_BPF_COEFF1213,  0xfe53ffc1},
+{7100000, DIF_BPF_COEFF1415,  0x0221038c},
+{7100000, DIF_BPF_COEFF1617,  0x0241fe6e},
+{7100000, DIF_BPF_COEFF1819,  0xfab6fa80},
+{7100000, DIF_BPF_COEFF2021,  0xff010587},
+{7100000, DIF_BPF_COEFF2223,  0x08e90590},
+{7100000, DIF_BPF_COEFF2425,  0xfcf5f556},
+{7100000, DIF_BPF_COEFF2627,  0xf52bfdb3},
+{7100000, DIF_BPF_COEFF2829,  0x09210e95},
+{7100000, DIF_BPF_COEFF3031,  0x0919fc15},
+{7100000, DIF_BPF_COEFF3233,  0xf13ff12f},
+{7100000, DIF_BPF_COEFF3435,  0xfc6e0aab},
+{7100000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 71_quant.dat*/
+
+
+/*case 7200000:*/
+/* BEGIN - DIF BPF register values from 72_quant.dat*/
+{7200000, DIF_BPF_COEFF01,    0x00000003},
+{7200000, DIF_BPF_COEFF23,    0x00070000},
+{7200000, DIF_BPF_COEFF45,    0xffe6ffc9},
+{7200000, DIF_BPF_COEFF67,    0xffdb0039},
+{7200000, DIF_BPF_COEFF89,    0x00af00b8},
+{7200000, DIF_BPF_COEFF1011,  0xfff4feb6},
+{7200000, DIF_BPF_COEFF1213,  0xfe13ff10},
+{7200000, DIF_BPF_COEFF1415,  0x01790388},
+{7200000, DIF_BPF_COEFF1617,  0x0311ff92},
+{7200000, DIF_BPF_COEFF1819,  0xfb48f9ed},
+{7200000, DIF_BPF_COEFF2021,  0xfd980453},
+{7200000, DIF_BPF_COEFF2223,  0x08e306cd},
+{7200000, DIF_BPF_COEFF2425,  0xfe88f60a},
+{7200000, DIF_BPF_COEFF2627,  0xf482fc40},
+{7200000, DIF_BPF_COEFF2829,  0x08080e93},
+{7200000, DIF_BPF_COEFF3031,  0x09fdfd0c},
+{7200000, DIF_BPF_COEFF3233,  0xf19af0ea},
+{7200000, DIF_BPF_COEFF3435,  0xfc050a81},
+{7200000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 72_quant.dat*/
+
+
+/*case 7300000:*/
+/* BEGIN - DIF BPF register values from 73_quant.dat*/
+{7300000, DIF_BPF_COEFF01,    0x00000002},
+{7300000, DIF_BPF_COEFF23,    0x00080008},
+{7300000, DIF_BPF_COEFF45,    0xfff0ffc9},
+{7300000, DIF_BPF_COEFF67,    0xffc1000d},
+{7300000, DIF_BPF_COEFF89,    0x009800e2},
+{7300000, DIF_BPF_COEFF1011,  0x005bff10},
+{7300000, DIF_BPF_COEFF1213,  0xfe00fe74},
+{7300000, DIF_BPF_COEFF1415,  0x00b50345},
+{7300000, DIF_BPF_COEFF1617,  0x03b000bc},
+{7300000, DIF_BPF_COEFF1819,  0xfc18f9a1},
+{7300000, DIF_BPF_COEFF2021,  0xfc4802f9},
+{7300000, DIF_BPF_COEFF2223,  0x089807dc},
+{7300000, DIF_BPF_COEFF2425,  0x0022f6f0},
+{7300000, DIF_BPF_COEFF2627,  0xf407fada},
+{7300000, DIF_BPF_COEFF2829,  0x06da0e74},
+{7300000, DIF_BPF_COEFF3031,  0x0ad3fe06},
+{7300000, DIF_BPF_COEFF3233,  0xf1fef0ab},
+{7300000, DIF_BPF_COEFF3435,  0xfb9c0a55},
+{7300000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 73_quant.dat*/
+
+
+/*case 7400000:*/
+/* BEGIN - DIF BPF register values from 74_quant.dat*/
+{7400000, DIF_BPF_COEFF01,    0x00000001},
+{7400000, DIF_BPF_COEFF23,    0x0008000e},
+{7400000, DIF_BPF_COEFF45,    0xfffdffd0},
+{7400000, DIF_BPF_COEFF67,    0xffafffdf},
+{7400000, DIF_BPF_COEFF89,    0x006e00f2},
+{7400000, DIF_BPF_COEFF1011,  0x00b8ff82},
+{7400000, DIF_BPF_COEFF1213,  0xfe1bfdf8},
+{7400000, DIF_BPF_COEFF1415,  0xffe302c8},
+{7400000, DIF_BPF_COEFF1617,  0x041301dc},
+{7400000, DIF_BPF_COEFF1819,  0xfd1af99e},
+{7400000, DIF_BPF_COEFF2021,  0xfb1e0183},
+{7400000, DIF_BPF_COEFF2223,  0x080908b5},
+{7400000, DIF_BPF_COEFF2425,  0x01bcf801},
+{7400000, DIF_BPF_COEFF2627,  0xf3bdf985},
+{7400000, DIF_BPF_COEFF2829,  0x059a0e38},
+{7400000, DIF_BPF_COEFF3031,  0x0b99ff03},
+{7400000, DIF_BPF_COEFF3233,  0xf26cf071},
+{7400000, DIF_BPF_COEFF3435,  0xfb330a2a},
+{7400000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 74_quant.dat*/
+
+
+/*case 7500000:*/
+/* BEGIN - DIF BPF register values from 75_quant.dat*/
+{7500000, DIF_BPF_COEFF01,    0xffff0000},
+{7500000, DIF_BPF_COEFF23,    0x00070011},
+{7500000, DIF_BPF_COEFF45,    0x000affdf},
+{7500000, DIF_BPF_COEFF67,    0xffa9ffb5},
+{7500000, DIF_BPF_COEFF89,    0x003700e6},
+{7500000, DIF_BPF_COEFF1011,  0x01010000},
+{7500000, DIF_BPF_COEFF1213,  0xfe62fda8},
+{7500000, DIF_BPF_COEFF1415,  0xff140219},
+{7500000, DIF_BPF_COEFF1617,  0x043502e1},
+{7500000, DIF_BPF_COEFF1819,  0xfe42f9e6},
+{7500000, DIF_BPF_COEFF2021,  0xfa270000},
+{7500000, DIF_BPF_COEFF2223,  0x073a0953},
+{7500000, DIF_BPF_COEFF2425,  0x034cf939},
+{7500000, DIF_BPF_COEFF2627,  0xf3a4f845},
+{7500000, DIF_BPF_COEFF2829,  0x044c0de1},
+{7500000, DIF_BPF_COEFF3031,  0x0c4f0000},
+{7500000, DIF_BPF_COEFF3233,  0xf2e2f03c},
+{7500000, DIF_BPF_COEFF3435,  0xfacc09fe},
+{7500000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 75_quant.dat*/
+
+
+/*case 7600000:*/
+/* BEGIN - DIF BPF register values from 76_quant.dat*/
+{7600000, DIF_BPF_COEFF01,    0xffffffff},
+{7600000, DIF_BPF_COEFF23,    0x00040012},
+{7600000, DIF_BPF_COEFF45,    0x0016fff3},
+{7600000, DIF_BPF_COEFF67,    0xffafff95},
+{7600000, DIF_BPF_COEFF89,    0xfff900c0},
+{7600000, DIF_BPF_COEFF1011,  0x0130007e},
+{7600000, DIF_BPF_COEFF1213,  0xfecefd89},
+{7600000, DIF_BPF_COEFF1415,  0xfe560146},
+{7600000, DIF_BPF_COEFF1617,  0x041303bc},
+{7600000, DIF_BPF_COEFF1819,  0xff81fa76},
+{7600000, DIF_BPF_COEFF2021,  0xf96cfe7d},
+{7600000, DIF_BPF_COEFF2223,  0x063209b1},
+{7600000, DIF_BPF_COEFF2425,  0x04c9fa93},
+{7600000, DIF_BPF_COEFF2627,  0xf3bdf71e},
+{7600000, DIF_BPF_COEFF2829,  0x02f30d6e},
+{7600000, DIF_BPF_COEFF3031,  0x0cf200fd},
+{7600000, DIF_BPF_COEFF3233,  0xf361f00e},
+{7600000, DIF_BPF_COEFF3435,  0xfa6509d1},
+{7600000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 76_quant.dat*/
+
+
+/*case 7700000:*/
+/* BEGIN - DIF BPF register values from 77_quant.dat*/
+{7700000, DIF_BPF_COEFF01,    0xfffffffe},
+{7700000, DIF_BPF_COEFF23,    0x00010010},
+{7700000, DIF_BPF_COEFF45,    0x001e0008},
+{7700000, DIF_BPF_COEFF67,    0xffc1ff84},
+{7700000, DIF_BPF_COEFF89,    0xffbc0084},
+{7700000, DIF_BPF_COEFF1011,  0x013e00f0},
+{7700000, DIF_BPF_COEFF1213,  0xff56fd9f},
+{7700000, DIF_BPF_COEFF1415,  0xfdb8005c},
+{7700000, DIF_BPF_COEFF1617,  0x03b00460},
+{7700000, DIF_BPF_COEFF1819,  0x00c7fb45},
+{7700000, DIF_BPF_COEFF2021,  0xf8f4fd07},
+{7700000, DIF_BPF_COEFF2223,  0x04fa09ce},
+{7700000, DIF_BPF_COEFF2425,  0x062afc07},
+{7700000, DIF_BPF_COEFF2627,  0xf407f614},
+{7700000, DIF_BPF_COEFF2829,  0x01920ce0},
+{7700000, DIF_BPF_COEFF3031,  0x0d8301fa},
+{7700000, DIF_BPF_COEFF3233,  0xf3e8efe5},
+{7700000, DIF_BPF_COEFF3435,  0xfa0009a4},
+{7700000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 77_quant.dat*/
+
+
+/*case 7800000:*/
+/* BEGIN - DIF BPF register values from 78_quant.dat*/
+{7800000, DIF_BPF_COEFF01,    0x0000fffd},
+{7800000, DIF_BPF_COEFF23,    0xfffd000b},
+{7800000, DIF_BPF_COEFF45,    0x0022001d},
+{7800000, DIF_BPF_COEFF67,    0xffdbff82},
+{7800000, DIF_BPF_COEFF89,    0xff870039},
+{7800000, DIF_BPF_COEFF1011,  0x012a014a},
+{7800000, DIF_BPF_COEFF1213,  0xffedfde7},
+{7800000, DIF_BPF_COEFF1415,  0xfd47ff6b},
+{7800000, DIF_BPF_COEFF1617,  0x031104c6},
+{7800000, DIF_BPF_COEFF1819,  0x0202fc4c},
+{7800000, DIF_BPF_COEFF2021,  0xf8c6fbad},
+{7800000, DIF_BPF_COEFF2223,  0x039909a7},
+{7800000, DIF_BPF_COEFF2425,  0x0767fd8e},
+{7800000, DIF_BPF_COEFF2627,  0xf482f52b},
+{7800000, DIF_BPF_COEFF2829,  0x002d0c39},
+{7800000, DIF_BPF_COEFF3031,  0x0e0002f4},
+{7800000, DIF_BPF_COEFF3233,  0xf477efc2},
+{7800000, DIF_BPF_COEFF3435,  0xf99b0977},
+{7800000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 78_quant.dat*/
+
+
+/*case 7900000:*/
+/* BEGIN - DIF BPF register values from 79_quant.dat*/
+{7900000, DIF_BPF_COEFF01,    0x0000fffd},
+{7900000, DIF_BPF_COEFF23,    0xfffa0004},
+{7900000, DIF_BPF_COEFF45,    0x0020002d},
+{7900000, DIF_BPF_COEFF67,    0xfffbff91},
+{7900000, DIF_BPF_COEFF89,    0xff61ffe8},
+{7900000, DIF_BPF_COEFF1011,  0x00f70184},
+{7900000, DIF_BPF_COEFF1213,  0x0086fe5c},
+{7900000, DIF_BPF_COEFF1415,  0xfd0bfe85},
+{7900000, DIF_BPF_COEFF1617,  0x024104e5},
+{7900000, DIF_BPF_COEFF1819,  0x0323fd7d},
+{7900000, DIF_BPF_COEFF2021,  0xf8e2fa79},
+{7900000, DIF_BPF_COEFF2223,  0x021d093f},
+{7900000, DIF_BPF_COEFF2425,  0x0879ff22},
+{7900000, DIF_BPF_COEFF2627,  0xf52bf465},
+{7900000, DIF_BPF_COEFF2829,  0xfec70b79},
+{7900000, DIF_BPF_COEFF3031,  0x0e6803eb},
+{7900000, DIF_BPF_COEFF3233,  0xf50defa5},
+{7900000, DIF_BPF_COEFF3435,  0xf937094a},
+{7900000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 79_quant.dat*/
+
+
+/*case 8000000:*/
+/* BEGIN - DIF BPF register values from 80_quant.dat*/
+{8000000, DIF_BPF_COEFF01,    0x0000fffe},
+{8000000, DIF_BPF_COEFF23,    0xfff8fffd},
+{8000000, DIF_BPF_COEFF45,    0x00190036},
+{8000000, DIF_BPF_COEFF67,    0x001bffaf},
+{8000000, DIF_BPF_COEFF89,    0xff4fff99},
+{8000000, DIF_BPF_COEFF1011,  0x00aa0198},
+{8000000, DIF_BPF_COEFF1213,  0x0112fef3},
+{8000000, DIF_BPF_COEFF1415,  0xfd09fdb9},
+{8000000, DIF_BPF_COEFF1617,  0x014d04be},
+{8000000, DIF_BPF_COEFF1819,  0x041bfecc},
+{8000000, DIF_BPF_COEFF2021,  0xf947f978},
+{8000000, DIF_BPF_COEFF2223,  0x00900897},
+{8000000, DIF_BPF_COEFF2425,  0x095a00b9},
+{8000000, DIF_BPF_COEFF2627,  0xf600f3c5},
+{8000000, DIF_BPF_COEFF2829,  0xfd650aa3},
+{8000000, DIF_BPF_COEFF3031,  0x0ebc04de},
+{8000000, DIF_BPF_COEFF3233,  0xf5aaef8e},
+{8000000, DIF_BPF_COEFF3435,  0xf8d5091c},
+{8000000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 80_quant.dat*/
+
+
+/*case 8100000:*/
+/* BEGIN - DIF BPF register values from 81_quant.dat*/
+{8100000, DIF_BPF_COEFF01,    0x0000ffff},
+{8100000, DIF_BPF_COEFF23,    0xfff7fff6},
+{8100000, DIF_BPF_COEFF45,    0x000e0038},
+{8100000, DIF_BPF_COEFF67,    0x0037ffd7},
+{8100000, DIF_BPF_COEFF89,    0xff52ff56},
+{8100000, DIF_BPF_COEFF1011,  0x004b0184},
+{8100000, DIF_BPF_COEFF1213,  0x0186ffa1},
+{8100000, DIF_BPF_COEFF1415,  0xfd40fd16},
+{8100000, DIF_BPF_COEFF1617,  0x00440452},
+{8100000, DIF_BPF_COEFF1819,  0x04de0029},
+{8100000, DIF_BPF_COEFF2021,  0xf9f2f8b2},
+{8100000, DIF_BPF_COEFF2223,  0xfefe07b5},
+{8100000, DIF_BPF_COEFF2425,  0x0a05024d},
+{8100000, DIF_BPF_COEFF2627,  0xf6fef34d},
+{8100000, DIF_BPF_COEFF2829,  0xfc0a09b8},
+{8100000, DIF_BPF_COEFF3031,  0x0efa05cd},
+{8100000, DIF_BPF_COEFF3233,  0xf64eef7d},
+{8100000, DIF_BPF_COEFF3435,  0xf87308ed},
+{8100000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 81_quant.dat*/
+
+
+/*case 8200000:*/
+/* BEGIN - DIF BPF register values from 82_quant.dat*/
+{8200000, DIF_BPF_COEFF01,    0x00010000},
+{8200000, DIF_BPF_COEFF23,    0xfff8fff0},
+{8200000, DIF_BPF_COEFF45,    0x00000031},
+{8200000, DIF_BPF_COEFF67,    0x004c0005},
+{8200000, DIF_BPF_COEFF89,    0xff6aff27},
+{8200000, DIF_BPF_COEFF1011,  0xffe4014a},
+{8200000, DIF_BPF_COEFF1213,  0x01d70057},
+{8200000, DIF_BPF_COEFF1415,  0xfdacfca6},
+{8200000, DIF_BPF_COEFF1617,  0xff3603a7},
+{8200000, DIF_BPF_COEFF1819,  0x05610184},
+{8200000, DIF_BPF_COEFF2021,  0xfadbf82e},
+{8200000, DIF_BPF_COEFF2223,  0xfd74069f},
+{8200000, DIF_BPF_COEFF2425,  0x0a7503d6},
+{8200000, DIF_BPF_COEFF2627,  0xf81ff2ff},
+{8200000, DIF_BPF_COEFF2829,  0xfab808b9},
+{8200000, DIF_BPF_COEFF3031,  0x0f2306b5},
+{8200000, DIF_BPF_COEFF3233,  0xf6f9ef72},
+{8200000, DIF_BPF_COEFF3435,  0xf81308bf},
+{8200000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 82_quant.dat*/
+
+
+/*case 8300000:*/
+/* BEGIN - DIF BPF register values from 83_quant.dat*/
+{8300000, DIF_BPF_COEFF01,    0x00010001},
+{8300000, DIF_BPF_COEFF23,    0xfffbffee},
+{8300000, DIF_BPF_COEFF45,    0xfff30022},
+{8300000, DIF_BPF_COEFF67,    0x00560032},
+{8300000, DIF_BPF_COEFF89,    0xff95ff10},
+{8300000, DIF_BPF_COEFF1011,  0xff8000f0},
+{8300000, DIF_BPF_COEFF1213,  0x01fe0106},
+{8300000, DIF_BPF_COEFF1415,  0xfe46fc71},
+{8300000, DIF_BPF_COEFF1617,  0xfe3502c7},
+{8300000, DIF_BPF_COEFF1819,  0x059e02ce},
+{8300000, DIF_BPF_COEFF2021,  0xfbf9f7f2},
+{8300000, DIF_BPF_COEFF2223,  0xfbff055b},
+{8300000, DIF_BPF_COEFF2425,  0x0aa9054c},
+{8300000, DIF_BPF_COEFF2627,  0xf961f2db},
+{8300000, DIF_BPF_COEFF2829,  0xf97507aa},
+{8300000, DIF_BPF_COEFF3031,  0x0f350797},
+{8300000, DIF_BPF_COEFF3233,  0xf7a9ef6d},
+{8300000, DIF_BPF_COEFF3435,  0xf7b40890},
+{8300000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 83_quant.dat*/
+
+
+/*case 8400000:*/
+/* BEGIN - DIF BPF register values from 84_quant.dat*/
+{8400000, DIF_BPF_COEFF01,    0x00010002},
+{8400000, DIF_BPF_COEFF23,    0xfffeffee},
+{8400000, DIF_BPF_COEFF45,    0xffe8000f},
+{8400000, DIF_BPF_COEFF67,    0x00540058},
+{8400000, DIF_BPF_COEFF89,    0xffcdff14},
+{8400000, DIF_BPF_COEFF1011,  0xff29007e},
+{8400000, DIF_BPF_COEFF1213,  0x01f6019e},
+{8400000, DIF_BPF_COEFF1415,  0xff01fc7c},
+{8400000, DIF_BPF_COEFF1617,  0xfd5101bf},
+{8400000, DIF_BPF_COEFF1819,  0x059203f6},
+{8400000, DIF_BPF_COEFF2021,  0xfd41f7fe},
+{8400000, DIF_BPF_COEFF2223,  0xfaa903f3},
+{8400000, DIF_BPF_COEFF2425,  0x0a9e06a9},
+{8400000, DIF_BPF_COEFF2627,  0xfabdf2e2},
+{8400000, DIF_BPF_COEFF2829,  0xf842068b},
+{8400000, DIF_BPF_COEFF3031,  0x0f320871},
+{8400000, DIF_BPF_COEFF3233,  0xf85eef6e},
+{8400000, DIF_BPF_COEFF3435,  0xf7560860},
+{8400000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 84_quant.dat*/
+
+
+/*case 8500000:*/
+/* BEGIN - DIF BPF register values from 85_quant.dat*/
+{8500000, DIF_BPF_COEFF01,    0x00000003},
+{8500000, DIF_BPF_COEFF23,    0x0002fff2},
+{8500000, DIF_BPF_COEFF45,    0xffe1fff9},
+{8500000, DIF_BPF_COEFF67,    0x00460073},
+{8500000, DIF_BPF_COEFF89,    0x000bff34},
+{8500000, DIF_BPF_COEFF1011,  0xfee90000},
+{8500000, DIF_BPF_COEFF1213,  0x01c10215},
+{8500000, DIF_BPF_COEFF1415,  0xffd0fcc5},
+{8500000, DIF_BPF_COEFF1617,  0xfc99009d},
+{8500000, DIF_BPF_COEFF1819,  0x053d04f1},
+{8500000, DIF_BPF_COEFF2021,  0xfea5f853},
+{8500000, DIF_BPF_COEFF2223,  0xf97d0270},
+{8500000, DIF_BPF_COEFF2425,  0x0a5607e4},
+{8500000, DIF_BPF_COEFF2627,  0xfc2ef314},
+{8500000, DIF_BPF_COEFF2829,  0xf723055f},
+{8500000, DIF_BPF_COEFF3031,  0x0f180943},
+{8500000, DIF_BPF_COEFF3233,  0xf919ef75},
+{8500000, DIF_BPF_COEFF3435,  0xf6fa0830},
+{8500000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 85_quant.dat*/
+
+
+/*case 8600000:*/
+/* BEGIN - DIF BPF register values from 86_quant.dat*/
+{8600000, DIF_BPF_COEFF01,    0x00000003},
+{8600000, DIF_BPF_COEFF23,    0x0005fff8},
+{8600000, DIF_BPF_COEFF45,    0xffdeffe4},
+{8600000, DIF_BPF_COEFF67,    0x002f007f},
+{8600000, DIF_BPF_COEFF89,    0x0048ff6b},
+{8600000, DIF_BPF_COEFF1011,  0xfec7ff82},
+{8600000, DIF_BPF_COEFF1213,  0x0163025f},
+{8600000, DIF_BPF_COEFF1415,  0x00a2fd47},
+{8600000, DIF_BPF_COEFF1617,  0xfc17ff73},
+{8600000, DIF_BPF_COEFF1819,  0x04a405b2},
+{8600000, DIF_BPF_COEFF2021,  0x0017f8ed},
+{8600000, DIF_BPF_COEFF2223,  0xf88500dc},
+{8600000, DIF_BPF_COEFF2425,  0x09d208f9},
+{8600000, DIF_BPF_COEFF2627,  0xfdaff370},
+{8600000, DIF_BPF_COEFF2829,  0xf61c0429},
+{8600000, DIF_BPF_COEFF3031,  0x0ee80a0b},
+{8600000, DIF_BPF_COEFF3233,  0xf9d8ef82},
+{8600000, DIF_BPF_COEFF3435,  0xf6a00800},
+{8600000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 86_quant.dat*/
+
+
+/*case 8700000:*/
+/* BEGIN - DIF BPF register values from 87_quant.dat*/
+{8700000, DIF_BPF_COEFF01,    0x00000003},
+{8700000, DIF_BPF_COEFF23,    0x0007ffff},
+{8700000, DIF_BPF_COEFF45,    0xffe1ffd4},
+{8700000, DIF_BPF_COEFF67,    0x0010007a},
+{8700000, DIF_BPF_COEFF89,    0x007cffb2},
+{8700000, DIF_BPF_COEFF1011,  0xfec6ff10},
+{8700000, DIF_BPF_COEFF1213,  0x00e60277},
+{8700000, DIF_BPF_COEFF1415,  0x0168fdf9},
+{8700000, DIF_BPF_COEFF1617,  0xfbd3fe50},
+{8700000, DIF_BPF_COEFF1819,  0x03ce0631},
+{8700000, DIF_BPF_COEFF2021,  0x0188f9c8},
+{8700000, DIF_BPF_COEFF2223,  0xf7c7ff43},
+{8700000, DIF_BPF_COEFF2425,  0x091509e3},
+{8700000, DIF_BPF_COEFF2627,  0xff39f3f6},
+{8700000, DIF_BPF_COEFF2829,  0xf52d02ea},
+{8700000, DIF_BPF_COEFF3031,  0x0ea30ac9},
+{8700000, DIF_BPF_COEFF3233,  0xfa9bef95},
+{8700000, DIF_BPF_COEFF3435,  0xf64607d0},
+{8700000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 87_quant.dat*/
+
+
+/*case 8800000:*/
+/* BEGIN - DIF BPF register values from 88_quant.dat*/
+{8800000, DIF_BPF_COEFF01,    0x00000002},
+{8800000, DIF_BPF_COEFF23,    0x00090007},
+{8800000, DIF_BPF_COEFF45,    0xffe9ffca},
+{8800000, DIF_BPF_COEFF67,    0xfff00065},
+{8800000, DIF_BPF_COEFF89,    0x00a10003},
+{8800000, DIF_BPF_COEFF1011,  0xfee6feb6},
+{8800000, DIF_BPF_COEFF1213,  0x0053025b},
+{8800000, DIF_BPF_COEFF1415,  0x0213fed0},
+{8800000, DIF_BPF_COEFF1617,  0xfbd3fd46},
+{8800000, DIF_BPF_COEFF1819,  0x02c70668},
+{8800000, DIF_BPF_COEFF2021,  0x02eafadb},
+{8800000, DIF_BPF_COEFF2223,  0xf74bfdae},
+{8800000, DIF_BPF_COEFF2425,  0x08230a9c},
+{8800000, DIF_BPF_COEFF2627,  0x00c7f4a3},
+{8800000, DIF_BPF_COEFF2829,  0xf45b01a6},
+{8800000, DIF_BPF_COEFF3031,  0x0e480b7c},
+{8800000, DIF_BPF_COEFF3233,  0xfb61efae},
+{8800000, DIF_BPF_COEFF3435,  0xf5ef079f},
+{8800000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 88_quant.dat*/
+
+
+/*case 8900000:*/
+/* BEGIN - DIF BPF register values from 89_quant.dat*/
+{8900000, DIF_BPF_COEFF01,    0xffff0000},
+{8900000, DIF_BPF_COEFF23,    0x0008000d},
+{8900000, DIF_BPF_COEFF45,    0xfff5ffc8},
+{8900000, DIF_BPF_COEFF67,    0xffd10043},
+{8900000, DIF_BPF_COEFF89,    0x00b20053},
+{8900000, DIF_BPF_COEFF1011,  0xff24fe7c},
+{8900000, DIF_BPF_COEFF1213,  0xffb9020c},
+{8900000, DIF_BPF_COEFF1415,  0x0295ffbb},
+{8900000, DIF_BPF_COEFF1617,  0xfc17fc64},
+{8900000, DIF_BPF_COEFF1819,  0x019b0654},
+{8900000, DIF_BPF_COEFF2021,  0x042dfc1c},
+{8900000, DIF_BPF_COEFF2223,  0xf714fc2a},
+{8900000, DIF_BPF_COEFF2425,  0x07020b21},
+{8900000, DIF_BPF_COEFF2627,  0x0251f575},
+{8900000, DIF_BPF_COEFF2829,  0xf3a7005e},
+{8900000, DIF_BPF_COEFF3031,  0x0dd80c24},
+{8900000, DIF_BPF_COEFF3233,  0xfc2aefcd},
+{8900000, DIF_BPF_COEFF3435,  0xf599076e},
+{8900000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 89_quant.dat*/
+
+
+/*case 9000000:*/
+/* BEGIN - DIF BPF register values from 90_quant.dat*/
+{9000000, DIF_BPF_COEFF01,    0xffffffff},
+{9000000, DIF_BPF_COEFF23,    0x00060011},
+{9000000, DIF_BPF_COEFF45,    0x0002ffcf},
+{9000000, DIF_BPF_COEFF67,    0xffba0018},
+{9000000, DIF_BPF_COEFF89,    0x00ad009a},
+{9000000, DIF_BPF_COEFF1011,  0xff79fe68},
+{9000000, DIF_BPF_COEFF1213,  0xff260192},
+{9000000, DIF_BPF_COEFF1415,  0x02e500ab},
+{9000000, DIF_BPF_COEFF1617,  0xfc99fbb6},
+{9000000, DIF_BPF_COEFF1819,  0x005b05f7},
+{9000000, DIF_BPF_COEFF2021,  0x0545fd81},
+{9000000, DIF_BPF_COEFF2223,  0xf723fabf},
+{9000000, DIF_BPF_COEFF2425,  0x05b80b70},
+{9000000, DIF_BPF_COEFF2627,  0x03d2f669},
+{9000000, DIF_BPF_COEFF2829,  0xf313ff15},
+{9000000, DIF_BPF_COEFF3031,  0x0d550cbf},
+{9000000, DIF_BPF_COEFF3233,  0xfcf6eff2},
+{9000000, DIF_BPF_COEFF3435,  0xf544073d},
+{9000000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 90_quant.dat*/
+
+
+/*case 9100000:*/
+/* BEGIN - DIF BPF register values from 91_quant.dat*/
+{9100000, DIF_BPF_COEFF01,    0xfffffffe},
+{9100000, DIF_BPF_COEFF23,    0x00030012},
+{9100000, DIF_BPF_COEFF45,    0x000fffdd},
+{9100000, DIF_BPF_COEFF67,    0xffacffea},
+{9100000, DIF_BPF_COEFF89,    0x009300cf},
+{9100000, DIF_BPF_COEFF1011,  0xffdcfe7c},
+{9100000, DIF_BPF_COEFF1213,  0xfea600f7},
+{9100000, DIF_BPF_COEFF1415,  0x02fd0190},
+{9100000, DIF_BPF_COEFF1617,  0xfd51fb46},
+{9100000, DIF_BPF_COEFF1819,  0xff150554},
+{9100000, DIF_BPF_COEFF2021,  0x0627fefd},
+{9100000, DIF_BPF_COEFF2223,  0xf778f978},
+{9100000, DIF_BPF_COEFF2425,  0x044d0b87},
+{9100000, DIF_BPF_COEFF2627,  0x0543f77d},
+{9100000, DIF_BPF_COEFF2829,  0xf2a0fdcf},
+{9100000, DIF_BPF_COEFF3031,  0x0cbe0d4e},
+{9100000, DIF_BPF_COEFF3233,  0xfdc4f01d},
+{9100000, DIF_BPF_COEFF3435,  0xf4f2070b},
+{9100000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 91_quant.dat*/
+
+
+/*case 9200000:*/
+/* BEGIN - DIF BPF register values from 92_quant.dat*/
+{9200000, DIF_BPF_COEFF01,    0x0000fffd},
+{9200000, DIF_BPF_COEFF23,    0x00000010},
+{9200000, DIF_BPF_COEFF45,    0x001afff0},
+{9200000, DIF_BPF_COEFF67,    0xffaaffbf},
+{9200000, DIF_BPF_COEFF89,    0x006700ed},
+{9200000, DIF_BPF_COEFF1011,  0x0043feb6},
+{9200000, DIF_BPF_COEFF1213,  0xfe460047},
+{9200000, DIF_BPF_COEFF1415,  0x02db0258},
+{9200000, DIF_BPF_COEFF1617,  0xfe35fb1b},
+{9200000, DIF_BPF_COEFF1819,  0xfddc0473},
+{9200000, DIF_BPF_COEFF2021,  0x06c90082},
+{9200000, DIF_BPF_COEFF2223,  0xf811f85e},
+{9200000, DIF_BPF_COEFF2425,  0x02c90b66},
+{9200000, DIF_BPF_COEFF2627,  0x069ff8ad},
+{9200000, DIF_BPF_COEFF2829,  0xf250fc8d},
+{9200000, DIF_BPF_COEFF3031,  0x0c140dcf},
+{9200000, DIF_BPF_COEFF3233,  0xfe93f04d},
+{9200000, DIF_BPF_COEFF3435,  0xf4a106d9},
+{9200000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 92_quant.dat*/
+
+
+/*case 9300000:*/
+/* BEGIN - DIF BPF register values from 93_quant.dat*/
+{9300000, DIF_BPF_COEFF01,    0x0000fffd},
+{9300000, DIF_BPF_COEFF23,    0xfffc000c},
+{9300000, DIF_BPF_COEFF45,    0x00200006},
+{9300000, DIF_BPF_COEFF67,    0xffb4ff9c},
+{9300000, DIF_BPF_COEFF89,    0x002f00ef},
+{9300000, DIF_BPF_COEFF1011,  0x00a4ff10},
+{9300000, DIF_BPF_COEFF1213,  0xfe0dff92},
+{9300000, DIF_BPF_COEFF1415,  0x028102f7},
+{9300000, DIF_BPF_COEFF1617,  0xff36fb37},
+{9300000, DIF_BPF_COEFF1819,  0xfcbf035e},
+{9300000, DIF_BPF_COEFF2021,  0x07260202},
+{9300000, DIF_BPF_COEFF2223,  0xf8e8f778},
+{9300000, DIF_BPF_COEFF2425,  0x01340b0d},
+{9300000, DIF_BPF_COEFF2627,  0x07e1f9f4},
+{9300000, DIF_BPF_COEFF2829,  0xf223fb51},
+{9300000, DIF_BPF_COEFF3031,  0x0b590e42},
+{9300000, DIF_BPF_COEFF3233,  0xff64f083},
+{9300000, DIF_BPF_COEFF3435,  0xf45206a7},
+{9300000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 93_quant.dat*/
+
+
+/*case 9400000:*/
+/* BEGIN - DIF BPF register values from 94_quant.dat*/
+{9400000, DIF_BPF_COEFF01,    0x0000fffd},
+{9400000, DIF_BPF_COEFF23,    0xfff90005},
+{9400000, DIF_BPF_COEFF45,    0x0022001a},
+{9400000, DIF_BPF_COEFF67,    0xffc9ff86},
+{9400000, DIF_BPF_COEFF89,    0xfff000d7},
+{9400000, DIF_BPF_COEFF1011,  0x00f2ff82},
+{9400000, DIF_BPF_COEFF1213,  0xfe01fee5},
+{9400000, DIF_BPF_COEFF1415,  0x01f60362},
+{9400000, DIF_BPF_COEFF1617,  0x0044fb99},
+{9400000, DIF_BPF_COEFF1819,  0xfbcc0222},
+{9400000, DIF_BPF_COEFF2021,  0x07380370},
+{9400000, DIF_BPF_COEFF2223,  0xf9f7f6cc},
+{9400000, DIF_BPF_COEFF2425,  0xff990a7e},
+{9400000, DIF_BPF_COEFF2627,  0x0902fb50},
+{9400000, DIF_BPF_COEFF2829,  0xf21afa1f},
+{9400000, DIF_BPF_COEFF3031,  0x0a8d0ea6},
+{9400000, DIF_BPF_COEFF3233,  0x0034f0bf},
+{9400000, DIF_BPF_COEFF3435,  0xf4050675},
+{9400000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 94_quant.dat*/
+
+
+/*case 9500000:*/
+/* BEGIN - DIF BPF register values from 95_quant.dat*/
+{9500000, DIF_BPF_COEFF01,    0x0000fffe},
+{9500000, DIF_BPF_COEFF23,    0xfff8fffe},
+{9500000, DIF_BPF_COEFF45,    0x001e002b},
+{9500000, DIF_BPF_COEFF67,    0xffe5ff81},
+{9500000, DIF_BPF_COEFF89,    0xffb400a5},
+{9500000, DIF_BPF_COEFF1011,  0x01280000},
+{9500000, DIF_BPF_COEFF1213,  0xfe24fe50},
+{9500000, DIF_BPF_COEFF1415,  0x01460390},
+{9500000, DIF_BPF_COEFF1617,  0x014dfc3a},
+{9500000, DIF_BPF_COEFF1819,  0xfb1000ce},
+{9500000, DIF_BPF_COEFF2021,  0x070104bf},
+{9500000, DIF_BPF_COEFF2223,  0xfb37f65f},
+{9500000, DIF_BPF_COEFF2425,  0xfe0009bc},
+{9500000, DIF_BPF_COEFF2627,  0x0a00fcbb},
+{9500000, DIF_BPF_COEFF2829,  0xf235f8f8},
+{9500000, DIF_BPF_COEFF3031,  0x09b20efc},
+{9500000, DIF_BPF_COEFF3233,  0x0105f101},
+{9500000, DIF_BPF_COEFF3435,  0xf3ba0642},
+{9500000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 95_quant.dat*/
+
+
+/*case 9600000:*/
+/* BEGIN - DIF BPF register values from 96_quant.dat*/
+{9600000, DIF_BPF_COEFF01,    0x0001ffff},
+{9600000, DIF_BPF_COEFF23,    0xfff8fff7},
+{9600000, DIF_BPF_COEFF45,    0x00150036},
+{9600000, DIF_BPF_COEFF67,    0x0005ff8c},
+{9600000, DIF_BPF_COEFF89,    0xff810061},
+{9600000, DIF_BPF_COEFF1011,  0x013d007e},
+{9600000, DIF_BPF_COEFF1213,  0xfe71fddf},
+{9600000, DIF_BPF_COEFF1415,  0x007c0380},
+{9600000, DIF_BPF_COEFF1617,  0x0241fd13},
+{9600000, DIF_BPF_COEFF1819,  0xfa94ff70},
+{9600000, DIF_BPF_COEFF2021,  0x068005e2},
+{9600000, DIF_BPF_COEFF2223,  0xfc9bf633},
+{9600000, DIF_BPF_COEFF2425,  0xfc7308ca},
+{9600000, DIF_BPF_COEFF2627,  0x0ad5fe30},
+{9600000, DIF_BPF_COEFF2829,  0xf274f7e0},
+{9600000, DIF_BPF_COEFF3031,  0x08c90f43},
+{9600000, DIF_BPF_COEFF3233,  0x01d4f147},
+{9600000, DIF_BPF_COEFF3435,  0xf371060f},
+{9600000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 96_quant.dat*/
+
+
+/*case 9700000:*/
+/* BEGIN - DIF BPF register values from 97_quant.dat*/
+{9700000, DIF_BPF_COEFF01,    0x00010001},
+{9700000, DIF_BPF_COEFF23,    0xfff9fff1},
+{9700000, DIF_BPF_COEFF45,    0x00090038},
+{9700000, DIF_BPF_COEFF67,    0x0025ffa7},
+{9700000, DIF_BPF_COEFF89,    0xff5e0012},
+{9700000, DIF_BPF_COEFF1011,  0x013200f0},
+{9700000, DIF_BPF_COEFF1213,  0xfee3fd9b},
+{9700000, DIF_BPF_COEFF1415,  0xffaa0331},
+{9700000, DIF_BPF_COEFF1617,  0x0311fe15},
+{9700000, DIF_BPF_COEFF1819,  0xfa60fe18},
+{9700000, DIF_BPF_COEFF2021,  0x05bd06d1},
+{9700000, DIF_BPF_COEFF2223,  0xfe1bf64a},
+{9700000, DIF_BPF_COEFF2425,  0xfafa07ae},
+{9700000, DIF_BPF_COEFF2627,  0x0b7effab},
+{9700000, DIF_BPF_COEFF2829,  0xf2d5f6d7},
+{9700000, DIF_BPF_COEFF3031,  0x07d30f7a},
+{9700000, DIF_BPF_COEFF3233,  0x02a3f194},
+{9700000, DIF_BPF_COEFF3435,  0xf32905dc},
+{9700000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 97_quant.dat*/
+
+
+/*case 9800000:*/
+/* BEGIN - DIF BPF register values from 98_quant.dat*/
+{9800000, DIF_BPF_COEFF01,    0x00010002},
+{9800000, DIF_BPF_COEFF23,    0xfffcffee},
+{9800000, DIF_BPF_COEFF45,    0xfffb0032},
+{9800000, DIF_BPF_COEFF67,    0x003fffcd},
+{9800000, DIF_BPF_COEFF89,    0xff4effc1},
+{9800000, DIF_BPF_COEFF1011,  0x0106014a},
+{9800000, DIF_BPF_COEFF1213,  0xff6efd8a},
+{9800000, DIF_BPF_COEFF1415,  0xfedd02aa},
+{9800000, DIF_BPF_COEFF1617,  0x03b0ff34},
+{9800000, DIF_BPF_COEFF1819,  0xfa74fcd7},
+{9800000, DIF_BPF_COEFF2021,  0x04bf0781},
+{9800000, DIF_BPF_COEFF2223,  0xffaaf6a3},
+{9800000, DIF_BPF_COEFF2425,  0xf99e066b},
+{9800000, DIF_BPF_COEFF2627,  0x0bf90128},
+{9800000, DIF_BPF_COEFF2829,  0xf359f5e1},
+{9800000, DIF_BPF_COEFF3031,  0x06d20fa2},
+{9800000, DIF_BPF_COEFF3233,  0x0370f1e5},
+{9800000, DIF_BPF_COEFF3435,  0xf2e405a8},
+{9800000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 98_quant.dat*/
+
+
+/*case 9900000:*/
+/* BEGIN - DIF BPF register values from 99_quant.dat*/
+{9900000, DIF_BPF_COEFF01,    0x00000003},
+{9900000, DIF_BPF_COEFF23,    0xffffffee},
+{9900000, DIF_BPF_COEFF45,    0xffef0024},
+{9900000, DIF_BPF_COEFF67,    0x0051fffa},
+{9900000, DIF_BPF_COEFF89,    0xff54ff77},
+{9900000, DIF_BPF_COEFF1011,  0x00be0184},
+{9900000, DIF_BPF_COEFF1213,  0x0006fdad},
+{9900000, DIF_BPF_COEFF1415,  0xfe2701f3},
+{9900000, DIF_BPF_COEFF1617,  0x0413005e},
+{9900000, DIF_BPF_COEFF1819,  0xfad1fbba},
+{9900000, DIF_BPF_COEFF2021,  0x039007ee},
+{9900000, DIF_BPF_COEFF2223,  0x013bf73d},
+{9900000, DIF_BPF_COEFF2425,  0xf868050a},
+{9900000, DIF_BPF_COEFF2627,  0x0c4302a1},
+{9900000, DIF_BPF_COEFF2829,  0xf3fdf4fe},
+{9900000, DIF_BPF_COEFF3031,  0x05c70fba},
+{9900000, DIF_BPF_COEFF3233,  0x043bf23c},
+{9900000, DIF_BPF_COEFF3435,  0xf2a10575},
+{9900000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 99_quant.dat*/
+
+
+/*case 10000000:*/
+/* BEGIN - DIF BPF register values from 100_quant.dat*/
+{10000000, DIF_BPF_COEFF01,    0x00000003},
+{10000000, DIF_BPF_COEFF23,    0x0003fff1},
+{10000000, DIF_BPF_COEFF45,    0xffe50011},
+{10000000, DIF_BPF_COEFF67,    0x00570027},
+{10000000, DIF_BPF_COEFF89,    0xff70ff3c},
+{10000000, DIF_BPF_COEFF1011,  0x00620198},
+{10000000, DIF_BPF_COEFF1213,  0x009efe01},
+{10000000, DIF_BPF_COEFF1415,  0xfd95011a},
+{10000000, DIF_BPF_COEFF1617,  0x04350183},
+{10000000, DIF_BPF_COEFF1819,  0xfb71fad0},
+{10000000, DIF_BPF_COEFF2021,  0x023c0812},
+{10000000, DIF_BPF_COEFF2223,  0x02c3f811},
+{10000000, DIF_BPF_COEFF2425,  0xf75e0390},
+{10000000, DIF_BPF_COEFF2627,  0x0c5c0411},
+{10000000, DIF_BPF_COEFF2829,  0xf4c1f432},
+{10000000, DIF_BPF_COEFF3031,  0x04b30fc1},
+{10000000, DIF_BPF_COEFF3233,  0x0503f297},
+{10000000, DIF_BPF_COEFF3435,  0xf2610541},
+{10000000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 100_quant.dat*/
+
+
+/*case 10100000:*/
+/* BEGIN - DIF BPF register values from 101_quant.dat*/
+{10100000, DIF_BPF_COEFF01,    0x00000003},
+{10100000, DIF_BPF_COEFF23,    0x0006fff7},
+{10100000, DIF_BPF_COEFF45,    0xffdffffc},
+{10100000, DIF_BPF_COEFF67,    0x00510050},
+{10100000, DIF_BPF_COEFF89,    0xff9dff18},
+{10100000, DIF_BPF_COEFF1011,  0xfffc0184},
+{10100000, DIF_BPF_COEFF1213,  0x0128fe80},
+{10100000, DIF_BPF_COEFF1415,  0xfd32002e},
+{10100000, DIF_BPF_COEFF1617,  0x04130292},
+{10100000, DIF_BPF_COEFF1819,  0xfc4dfa21},
+{10100000, DIF_BPF_COEFF2021,  0x00d107ee},
+{10100000, DIF_BPF_COEFF2223,  0x0435f91c},
+{10100000, DIF_BPF_COEFF2425,  0xf6850205},
+{10100000, DIF_BPF_COEFF2627,  0x0c430573},
+{10100000, DIF_BPF_COEFF2829,  0xf5a1f37d},
+{10100000, DIF_BPF_COEFF3031,  0x03990fba},
+{10100000, DIF_BPF_COEFF3233,  0x05c7f2f8},
+{10100000, DIF_BPF_COEFF3435,  0xf222050d},
+{10100000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 101_quant.dat*/
+
+
+/*case 10200000:*/
+/* BEGIN - DIF BPF register values from 102_quant.dat*/
+{10200000, DIF_BPF_COEFF01,    0x00000002},
+{10200000, DIF_BPF_COEFF23,    0x0008fffe},
+{10200000, DIF_BPF_COEFF45,    0xffdfffe7},
+{10200000, DIF_BPF_COEFF67,    0x003f006e},
+{10200000, DIF_BPF_COEFF89,    0xffd6ff0f},
+{10200000, DIF_BPF_COEFF1011,  0xff96014a},
+{10200000, DIF_BPF_COEFF1213,  0x0197ff1f},
+{10200000, DIF_BPF_COEFF1415,  0xfd05ff3e},
+{10200000, DIF_BPF_COEFF1617,  0x03b0037c},
+{10200000, DIF_BPF_COEFF1819,  0xfd59f9b7},
+{10200000, DIF_BPF_COEFF2021,  0xff5d0781},
+{10200000, DIF_BPF_COEFF2223,  0x0585fa56},
+{10200000, DIF_BPF_COEFF2425,  0xf5e4006f},
+{10200000, DIF_BPF_COEFF2627,  0x0bf906c4},
+{10200000, DIF_BPF_COEFF2829,  0xf69df2e0},
+{10200000, DIF_BPF_COEFF3031,  0x02790fa2},
+{10200000, DIF_BPF_COEFF3233,  0x0688f35d},
+{10200000, DIF_BPF_COEFF3435,  0xf1e604d8},
+{10200000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 102_quant.dat*/
+
+
+/*case 10300000:*/
+/* BEGIN - DIF BPF register values from 103_quant.dat*/
+{10300000, DIF_BPF_COEFF01,    0xffff0001},
+{10300000, DIF_BPF_COEFF23,    0x00090005},
+{10300000, DIF_BPF_COEFF45,    0xffe4ffd6},
+{10300000, DIF_BPF_COEFF67,    0x0025007e},
+{10300000, DIF_BPF_COEFF89,    0x0014ff20},
+{10300000, DIF_BPF_COEFF1011,  0xff3c00f0},
+{10300000, DIF_BPF_COEFF1213,  0x01e1ffd0},
+{10300000, DIF_BPF_COEFF1415,  0xfd12fe5c},
+{10300000, DIF_BPF_COEFF1617,  0x03110433},
+{10300000, DIF_BPF_COEFF1819,  0xfe88f996},
+{10300000, DIF_BPF_COEFF2021,  0xfdf106d1},
+{10300000, DIF_BPF_COEFF2223,  0x06aafbb7},
+{10300000, DIF_BPF_COEFF2425,  0xf57efed8},
+{10300000, DIF_BPF_COEFF2627,  0x0b7e07ff},
+{10300000, DIF_BPF_COEFF2829,  0xf7b0f25e},
+{10300000, DIF_BPF_COEFF3031,  0x01560f7a},
+{10300000, DIF_BPF_COEFF3233,  0x0745f3c7},
+{10300000, DIF_BPF_COEFF3435,  0xf1ac04a4},
+{10300000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 103_quant.dat*/
+
+
+/*case 10400000:*/
+/* BEGIN - DIF BPF register values from 104_quant.dat*/
+{10400000, DIF_BPF_COEFF01,    0xffffffff},
+{10400000, DIF_BPF_COEFF23,    0x0008000c},
+{10400000, DIF_BPF_COEFF45,    0xffedffcb},
+{10400000, DIF_BPF_COEFF67,    0x0005007d},
+{10400000, DIF_BPF_COEFF89,    0x0050ff4c},
+{10400000, DIF_BPF_COEFF1011,  0xfef6007e},
+{10400000, DIF_BPF_COEFF1213,  0x01ff0086},
+{10400000, DIF_BPF_COEFF1415,  0xfd58fd97},
+{10400000, DIF_BPF_COEFF1617,  0x024104ad},
+{10400000, DIF_BPF_COEFF1819,  0xffcaf9c0},
+{10400000, DIF_BPF_COEFF2021,  0xfc9905e2},
+{10400000, DIF_BPF_COEFF2223,  0x079afd35},
+{10400000, DIF_BPF_COEFF2425,  0xf555fd46},
+{10400000, DIF_BPF_COEFF2627,  0x0ad50920},
+{10400000, DIF_BPF_COEFF2829,  0xf8d9f1f6},
+{10400000, DIF_BPF_COEFF3031,  0x00310f43},
+{10400000, DIF_BPF_COEFF3233,  0x07fdf435},
+{10400000, DIF_BPF_COEFF3435,  0xf174046f},
+{10400000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 104_quant.dat*/
+
+
+/*case 10500000:*/
+/* BEGIN - DIF BPF register values from 105_quant.dat*/
+{10500000, DIF_BPF_COEFF01,    0xfffffffe},
+{10500000, DIF_BPF_COEFF23,    0x00050011},
+{10500000, DIF_BPF_COEFF45,    0xfffaffc8},
+{10500000, DIF_BPF_COEFF67,    0xffe5006b},
+{10500000, DIF_BPF_COEFF89,    0x0082ff8c},
+{10500000, DIF_BPF_COEFF1011,  0xfecc0000},
+{10500000, DIF_BPF_COEFF1213,  0x01f00130},
+{10500000, DIF_BPF_COEFF1415,  0xfdd2fcfc},
+{10500000, DIF_BPF_COEFF1617,  0x014d04e3},
+{10500000, DIF_BPF_COEFF1819,  0x010efa32},
+{10500000, DIF_BPF_COEFF2021,  0xfb6404bf},
+{10500000, DIF_BPF_COEFF2223,  0x084efec5},
+{10500000, DIF_BPF_COEFF2425,  0xf569fbc2},
+{10500000, DIF_BPF_COEFF2627,  0x0a000a23},
+{10500000, DIF_BPF_COEFF2829,  0xfa15f1ab},
+{10500000, DIF_BPF_COEFF3031,  0xff0b0efc},
+{10500000, DIF_BPF_COEFF3233,  0x08b0f4a7},
+{10500000, DIF_BPF_COEFF3435,  0xf13f043a},
+{10500000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 105_quant.dat*/
+
+
+/*case 10600000:*/
+/* BEGIN - DIF BPF register values from 106_quant.dat*/
+{10600000, DIF_BPF_COEFF01,    0x0000fffd},
+{10600000, DIF_BPF_COEFF23,    0x00020012},
+{10600000, DIF_BPF_COEFF45,    0x0007ffcd},
+{10600000, DIF_BPF_COEFF67,    0xffc9004c},
+{10600000, DIF_BPF_COEFF89,    0x00a4ffd9},
+{10600000, DIF_BPF_COEFF1011,  0xfec3ff82},
+{10600000, DIF_BPF_COEFF1213,  0x01b401c1},
+{10600000, DIF_BPF_COEFF1415,  0xfe76fc97},
+{10600000, DIF_BPF_COEFF1617,  0x004404d2},
+{10600000, DIF_BPF_COEFF1819,  0x0245fae8},
+{10600000, DIF_BPF_COEFF2021,  0xfa5f0370},
+{10600000, DIF_BPF_COEFF2223,  0x08c1005f},
+{10600000, DIF_BPF_COEFF2425,  0xf5bcfa52},
+{10600000, DIF_BPF_COEFF2627,  0x09020b04},
+{10600000, DIF_BPF_COEFF2829,  0xfb60f17b},
+{10600000, DIF_BPF_COEFF3031,  0xfde70ea6},
+{10600000, DIF_BPF_COEFF3233,  0x095df51e},
+{10600000, DIF_BPF_COEFF3435,  0xf10c0405},
+{10600000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 106_quant.dat*/
+
+
+/*case 10700000:*/
+/* BEGIN - DIF BPF register values from 107_quant.dat*/
+{10700000, DIF_BPF_COEFF01,    0x0000fffd},
+{10700000, DIF_BPF_COEFF23,    0xffff0011},
+{10700000, DIF_BPF_COEFF45,    0x0014ffdb},
+{10700000, DIF_BPF_COEFF67,    0xffb40023},
+{10700000, DIF_BPF_COEFF89,    0x00b2002a},
+{10700000, DIF_BPF_COEFF1011,  0xfedbff10},
+{10700000, DIF_BPF_COEFF1213,  0x0150022d},
+{10700000, DIF_BPF_COEFF1415,  0xff38fc6f},
+{10700000, DIF_BPF_COEFF1617,  0xff36047b},
+{10700000, DIF_BPF_COEFF1819,  0x035efbda},
+{10700000, DIF_BPF_COEFF2021,  0xf9940202},
+{10700000, DIF_BPF_COEFF2223,  0x08ee01f5},
+{10700000, DIF_BPF_COEFF2425,  0xf649f8fe},
+{10700000, DIF_BPF_COEFF2627,  0x07e10bc2},
+{10700000, DIF_BPF_COEFF2829,  0xfcb6f169},
+{10700000, DIF_BPF_COEFF3031,  0xfcc60e42},
+{10700000, DIF_BPF_COEFF3233,  0x0a04f599},
+{10700000, DIF_BPF_COEFF3435,  0xf0db03d0},
+{10700000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 107_quant.dat*/
+
+
+/*case 10800000:*/
+/* BEGIN - DIF BPF register values from 108_quant.dat*/
+{10800000, DIF_BPF_COEFF01,    0x0000fffd},
+{10800000, DIF_BPF_COEFF23,    0xfffb000d},
+{10800000, DIF_BPF_COEFF45,    0x001dffed},
+{10800000, DIF_BPF_COEFF67,    0xffaafff5},
+{10800000, DIF_BPF_COEFF89,    0x00aa0077},
+{10800000, DIF_BPF_COEFF1011,  0xff13feb6},
+{10800000, DIF_BPF_COEFF1213,  0x00ce026b},
+{10800000, DIF_BPF_COEFF1415,  0x000afc85},
+{10800000, DIF_BPF_COEFF1617,  0xfe3503e3},
+{10800000, DIF_BPF_COEFF1819,  0x044cfcfb},
+{10800000, DIF_BPF_COEFF2021,  0xf90c0082},
+{10800000, DIF_BPF_COEFF2223,  0x08d5037f},
+{10800000, DIF_BPF_COEFF2425,  0xf710f7cc},
+{10800000, DIF_BPF_COEFF2627,  0x069f0c59},
+{10800000, DIF_BPF_COEFF2829,  0xfe16f173},
+{10800000, DIF_BPF_COEFF3031,  0xfbaa0dcf},
+{10800000, DIF_BPF_COEFF3233,  0x0aa5f617},
+{10800000, DIF_BPF_COEFF3435,  0xf0ad039b},
+{10800000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 108_quant.dat*/
+
+
+/*case 10900000:*/
+/* BEGIN - DIF BPF register values from 109_quant.dat*/
+{10900000, DIF_BPF_COEFF01,    0x0000fffe},
+{10900000, DIF_BPF_COEFF23,    0xfff90006},
+{10900000, DIF_BPF_COEFF45,    0x00210003},
+{10900000, DIF_BPF_COEFF67,    0xffacffc8},
+{10900000, DIF_BPF_COEFF89,    0x008e00b6},
+{10900000, DIF_BPF_COEFF1011,  0xff63fe7c},
+{10900000, DIF_BPF_COEFF1213,  0x003a0275},
+{10900000, DIF_BPF_COEFF1415,  0x00dafcda},
+{10900000, DIF_BPF_COEFF1617,  0xfd510313},
+{10900000, DIF_BPF_COEFF1819,  0x0501fe40},
+{10900000, DIF_BPF_COEFF2021,  0xf8cbfefd},
+{10900000, DIF_BPF_COEFF2223,  0x087604f0},
+{10900000, DIF_BPF_COEFF2425,  0xf80af6c2},
+{10900000, DIF_BPF_COEFF2627,  0x05430cc8},
+{10900000, DIF_BPF_COEFF2829,  0xff7af19a},
+{10900000, DIF_BPF_COEFF3031,  0xfa940d4e},
+{10900000, DIF_BPF_COEFF3233,  0x0b3ff699},
+{10900000, DIF_BPF_COEFF3435,  0xf0810365},
+{10900000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 109_quant.dat*/
+
+
+/*case 11000000:*/
+/* BEGIN - DIF BPF register values from 110_quant.dat*/
+{11000000, DIF_BPF_COEFF01,    0x0001ffff},
+{11000000, DIF_BPF_COEFF23,    0xfff8ffff},
+{11000000, DIF_BPF_COEFF45,    0x00210018},
+{11000000, DIF_BPF_COEFF67,    0xffbaffa3},
+{11000000, DIF_BPF_COEFF89,    0x006000e1},
+{11000000, DIF_BPF_COEFF1011,  0xffc4fe68},
+{11000000, DIF_BPF_COEFF1213,  0xffa0024b},
+{11000000, DIF_BPF_COEFF1415,  0x019afd66},
+{11000000, DIF_BPF_COEFF1617,  0xfc990216},
+{11000000, DIF_BPF_COEFF1819,  0x0575ff99},
+{11000000, DIF_BPF_COEFF2021,  0xf8d4fd81},
+{11000000, DIF_BPF_COEFF2223,  0x07d40640},
+{11000000, DIF_BPF_COEFF2425,  0xf932f5e6},
+{11000000, DIF_BPF_COEFF2627,  0x03d20d0d},
+{11000000, DIF_BPF_COEFF2829,  0x00dff1de},
+{11000000, DIF_BPF_COEFF3031,  0xf9860cbf},
+{11000000, DIF_BPF_COEFF3233,  0x0bd1f71e},
+{11000000, DIF_BPF_COEFF3435,  0xf058032f},
+{11000000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 110_quant.dat*/
+
+
+/*case 11100000:*/
+/* BEGIN - DIF BPF register values from 111_quant.dat*/
+{11100000, DIF_BPF_COEFF01,    0x00010000},
+{11100000, DIF_BPF_COEFF23,    0xfff8fff8},
+{11100000, DIF_BPF_COEFF45,    0x001b0029},
+{11100000, DIF_BPF_COEFF67,    0xffd1ff8a},
+{11100000, DIF_BPF_COEFF89,    0x002600f2},
+{11100000, DIF_BPF_COEFF1011,  0x002cfe7c},
+{11100000, DIF_BPF_COEFF1213,  0xff0f01f0},
+{11100000, DIF_BPF_COEFF1415,  0x023bfe20},
+{11100000, DIF_BPF_COEFF1617,  0xfc1700fa},
+{11100000, DIF_BPF_COEFF1819,  0x05a200f7},
+{11100000, DIF_BPF_COEFF2021,  0xf927fc1c},
+{11100000, DIF_BPF_COEFF2223,  0x06f40765},
+{11100000, DIF_BPF_COEFF2425,  0xfa82f53b},
+{11100000, DIF_BPF_COEFF2627,  0x02510d27},
+{11100000, DIF_BPF_COEFF2829,  0x0243f23d},
+{11100000, DIF_BPF_COEFF3031,  0xf8810c24},
+{11100000, DIF_BPF_COEFF3233,  0x0c5cf7a7},
+{11100000, DIF_BPF_COEFF3435,  0xf03102fa},
+{11100000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 111_quant.dat*/
+
+
+/*case 11200000:*/
+/* BEGIN - DIF BPF register values from 112_quant.dat*/
+{11200000, DIF_BPF_COEFF01,    0x00010002},
+{11200000, DIF_BPF_COEFF23,    0xfffafff2},
+{11200000, DIF_BPF_COEFF45,    0x00110035},
+{11200000, DIF_BPF_COEFF67,    0xfff0ff81},
+{11200000, DIF_BPF_COEFF89,    0xffe700e7},
+{11200000, DIF_BPF_COEFF1011,  0x008ffeb6},
+{11200000, DIF_BPF_COEFF1213,  0xfe94016d},
+{11200000, DIF_BPF_COEFF1415,  0x02b0fefb},
+{11200000, DIF_BPF_COEFF1617,  0xfbd3ffd1},
+{11200000, DIF_BPF_COEFF1819,  0x05850249},
+{11200000, DIF_BPF_COEFF2021,  0xf9c1fadb},
+{11200000, DIF_BPF_COEFF2223,  0x05de0858},
+{11200000, DIF_BPF_COEFF2425,  0xfbf2f4c4},
+{11200000, DIF_BPF_COEFF2627,  0x00c70d17},
+{11200000, DIF_BPF_COEFF2829,  0x03a0f2b8},
+{11200000, DIF_BPF_COEFF3031,  0xf7870b7c},
+{11200000, DIF_BPF_COEFF3233,  0x0cdff833},
+{11200000, DIF_BPF_COEFF3435,  0xf00d02c4},
+{11200000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 112_quant.dat*/
+
+
+/*case 11300000:*/
+/* BEGIN - DIF BPF register values from 113_quant.dat*/
+{11300000, DIF_BPF_COEFF01,    0x00000003},
+{11300000, DIF_BPF_COEFF23,    0xfffdffee},
+{11300000, DIF_BPF_COEFF45,    0x00040038},
+{11300000, DIF_BPF_COEFF67,    0x0010ff88},
+{11300000, DIF_BPF_COEFF89,    0xffac00c2},
+{11300000, DIF_BPF_COEFF1011,  0x00e2ff10},
+{11300000, DIF_BPF_COEFF1213,  0xfe3900cb},
+{11300000, DIF_BPF_COEFF1415,  0x02f1ffe9},
+{11300000, DIF_BPF_COEFF1617,  0xfbd3feaa},
+{11300000, DIF_BPF_COEFF1819,  0x05210381},
+{11300000, DIF_BPF_COEFF2021,  0xfa9cf9c8},
+{11300000, DIF_BPF_COEFF2223,  0x04990912},
+{11300000, DIF_BPF_COEFF2425,  0xfd7af484},
+{11300000, DIF_BPF_COEFF2627,  0xff390cdb},
+{11300000, DIF_BPF_COEFF2829,  0x04f4f34d},
+{11300000, DIF_BPF_COEFF3031,  0xf69a0ac9},
+{11300000, DIF_BPF_COEFF3233,  0x0d5af8c1},
+{11300000, DIF_BPF_COEFF3435,  0xefec028e},
+{11300000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 113_quant.dat*/
+
+
+/*case 11400000:*/
+/* BEGIN - DIF BPF register values from 114_quant.dat*/
+{11400000, DIF_BPF_COEFF01,    0x00000003},
+{11400000, DIF_BPF_COEFF23,    0x0000ffee},
+{11400000, DIF_BPF_COEFF45,    0xfff60033},
+{11400000, DIF_BPF_COEFF67,    0x002fff9f},
+{11400000, DIF_BPF_COEFF89,    0xff7b0087},
+{11400000, DIF_BPF_COEFF1011,  0x011eff82},
+{11400000, DIF_BPF_COEFF1213,  0xfe080018},
+{11400000, DIF_BPF_COEFF1415,  0x02f900d8},
+{11400000, DIF_BPF_COEFF1617,  0xfc17fd96},
+{11400000, DIF_BPF_COEFF1819,  0x04790490},
+{11400000, DIF_BPF_COEFF2021,  0xfbadf8ed},
+{11400000, DIF_BPF_COEFF2223,  0x032f098e},
+{11400000, DIF_BPF_COEFF2425,  0xff10f47d},
+{11400000, DIF_BPF_COEFF2627,  0xfdaf0c75},
+{11400000, DIF_BPF_COEFF2829,  0x063cf3fc},
+{11400000, DIF_BPF_COEFF3031,  0xf5ba0a0b},
+{11400000, DIF_BPF_COEFF3233,  0x0dccf952},
+{11400000, DIF_BPF_COEFF3435,  0xefcd0258},
+{11400000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 114_quant.dat*/
+
+
+/*case 11500000:*/
+/* BEGIN - DIF BPF register values from 115_quant.dat*/
+{11500000, DIF_BPF_COEFF01,    0x00000003},
+{11500000, DIF_BPF_COEFF23,    0x0004fff1},
+{11500000, DIF_BPF_COEFF45,    0xffea0026},
+{11500000, DIF_BPF_COEFF67,    0x0046ffc3},
+{11500000, DIF_BPF_COEFF89,    0xff5a003c},
+{11500000, DIF_BPF_COEFF1011,  0x013b0000},
+{11500000, DIF_BPF_COEFF1213,  0xfe04ff63},
+{11500000, DIF_BPF_COEFF1415,  0x02c801b8},
+{11500000, DIF_BPF_COEFF1617,  0xfc99fca6},
+{11500000, DIF_BPF_COEFF1819,  0x0397056a},
+{11500000, DIF_BPF_COEFF2021,  0xfcecf853},
+{11500000, DIF_BPF_COEFF2223,  0x01ad09c9},
+{11500000, DIF_BPF_COEFF2425,  0x00acf4ad},
+{11500000, DIF_BPF_COEFF2627,  0xfc2e0be7},
+{11500000, DIF_BPF_COEFF2829,  0x0773f4c2},
+{11500000, DIF_BPF_COEFF3031,  0xf4e90943},
+{11500000, DIF_BPF_COEFF3233,  0x0e35f9e6},
+{11500000, DIF_BPF_COEFF3435,  0xefb10221},
+{11500000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 115_quant.dat*/
+
+
+/*case 11600000:*/
+/* BEGIN - DIF BPF register values from 116_quant.dat*/
+{11600000, DIF_BPF_COEFF01,    0x00000002},
+{11600000, DIF_BPF_COEFF23,    0x0007fff6},
+{11600000, DIF_BPF_COEFF45,    0xffe20014},
+{11600000, DIF_BPF_COEFF67,    0x0054ffee},
+{11600000, DIF_BPF_COEFF89,    0xff4effeb},
+{11600000, DIF_BPF_COEFF1011,  0x0137007e},
+{11600000, DIF_BPF_COEFF1213,  0xfe2efebb},
+{11600000, DIF_BPF_COEFF1415,  0x0260027a},
+{11600000, DIF_BPF_COEFF1617,  0xfd51fbe6},
+{11600000, DIF_BPF_COEFF1819,  0x02870605},
+{11600000, DIF_BPF_COEFF2021,  0xfe4af7fe},
+{11600000, DIF_BPF_COEFF2223,  0x001d09c1},
+{11600000, DIF_BPF_COEFF2425,  0x0243f515},
+{11600000, DIF_BPF_COEFF2627,  0xfabd0b32},
+{11600000, DIF_BPF_COEFF2829,  0x0897f59e},
+{11600000, DIF_BPF_COEFF3031,  0xf4280871},
+{11600000, DIF_BPF_COEFF3233,  0x0e95fa7c},
+{11600000, DIF_BPF_COEFF3435,  0xef9701eb},
+{11600000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 116_quant.dat*/
+
+
+/*case 11700000:*/
+/* BEGIN - DIF BPF register values from 117_quant.dat*/
+{11700000, DIF_BPF_COEFF01,    0xffff0001},
+{11700000, DIF_BPF_COEFF23,    0x0008fffd},
+{11700000, DIF_BPF_COEFF45,    0xffdeffff},
+{11700000, DIF_BPF_COEFF67,    0x0056001d},
+{11700000, DIF_BPF_COEFF89,    0xff57ff9c},
+{11700000, DIF_BPF_COEFF1011,  0x011300f0},
+{11700000, DIF_BPF_COEFF1213,  0xfe82fe2e},
+{11700000, DIF_BPF_COEFF1415,  0x01ca0310},
+{11700000, DIF_BPF_COEFF1617,  0xfe35fb62},
+{11700000, DIF_BPF_COEFF1819,  0x0155065a},
+{11700000, DIF_BPF_COEFF2021,  0xffbaf7f2},
+{11700000, DIF_BPF_COEFF2223,  0xfe8c0977},
+{11700000, DIF_BPF_COEFF2425,  0x03cef5b2},
+{11700000, DIF_BPF_COEFF2627,  0xf9610a58},
+{11700000, DIF_BPF_COEFF2829,  0x09a5f68f},
+{11700000, DIF_BPF_COEFF3031,  0xf3790797},
+{11700000, DIF_BPF_COEFF3233,  0x0eebfb14},
+{11700000, DIF_BPF_COEFF3435,  0xef8001b5},
+{11700000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 117_quant.dat*/
+
+
+/*case 11800000:*/
+/* BEGIN - DIF BPF register values from 118_quant.dat*/
+{11800000, DIF_BPF_COEFF01,    0xffff0000},
+{11800000, DIF_BPF_COEFF23,    0x00080004},
+{11800000, DIF_BPF_COEFF45,    0xffe0ffe9},
+{11800000, DIF_BPF_COEFF67,    0x004c0047},
+{11800000, DIF_BPF_COEFF89,    0xff75ff58},
+{11800000, DIF_BPF_COEFF1011,  0x00d1014a},
+{11800000, DIF_BPF_COEFF1213,  0xfef9fdc8},
+{11800000, DIF_BPF_COEFF1415,  0x0111036f},
+{11800000, DIF_BPF_COEFF1617,  0xff36fb21},
+{11800000, DIF_BPF_COEFF1819,  0x00120665},
+{11800000, DIF_BPF_COEFF2021,  0x012df82e},
+{11800000, DIF_BPF_COEFF2223,  0xfd0708ec},
+{11800000, DIF_BPF_COEFF2425,  0x0542f682},
+{11800000, DIF_BPF_COEFF2627,  0xf81f095c},
+{11800000, DIF_BPF_COEFF2829,  0x0a9af792},
+{11800000, DIF_BPF_COEFF3031,  0xf2db06b5},
+{11800000, DIF_BPF_COEFF3233,  0x0f38fbad},
+{11800000, DIF_BPF_COEFF3435,  0xef6c017e},
+{11800000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 118_quant.dat*/
+
+
+/*case 11900000:*/
+/* BEGIN - DIF BPF register values from 119_quant.dat*/
+{11900000, DIF_BPF_COEFF01,    0xffffffff},
+{11900000, DIF_BPF_COEFF23,    0x0007000b},
+{11900000, DIF_BPF_COEFF45,    0xffe7ffd8},
+{11900000, DIF_BPF_COEFF67,    0x00370068},
+{11900000, DIF_BPF_COEFF89,    0xffa4ff28},
+{11900000, DIF_BPF_COEFF1011,  0x00790184},
+{11900000, DIF_BPF_COEFF1213,  0xff87fd91},
+{11900000, DIF_BPF_COEFF1415,  0x00430392},
+{11900000, DIF_BPF_COEFF1617,  0x0044fb26},
+{11900000, DIF_BPF_COEFF1819,  0xfece0626},
+{11900000, DIF_BPF_COEFF2021,  0x0294f8b2},
+{11900000, DIF_BPF_COEFF2223,  0xfb990825},
+{11900000, DIF_BPF_COEFF2425,  0x0698f77f},
+{11900000, DIF_BPF_COEFF2627,  0xf6fe0842},
+{11900000, DIF_BPF_COEFF2829,  0x0b73f8a7},
+{11900000, DIF_BPF_COEFF3031,  0xf25105cd},
+{11900000, DIF_BPF_COEFF3233,  0x0f7bfc48},
+{11900000, DIF_BPF_COEFF3435,  0xef5a0148},
+{11900000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 119_quant.dat*/
+
+
+/*case 12000000:*/
+/* BEGIN - DIF BPF register values from 120_quant.dat*/
+{12000000, DIF_BPF_COEFF01,    0x0000fffe},
+{12000000, DIF_BPF_COEFF23,    0x00050010},
+{12000000, DIF_BPF_COEFF45,    0xfff2ffcc},
+{12000000, DIF_BPF_COEFF67,    0x001b007b},
+{12000000, DIF_BPF_COEFF89,    0xffdfff10},
+{12000000, DIF_BPF_COEFF1011,  0x00140198},
+{12000000, DIF_BPF_COEFF1213,  0x0020fd8e},
+{12000000, DIF_BPF_COEFF1415,  0xff710375},
+{12000000, DIF_BPF_COEFF1617,  0x014dfb73},
+{12000000, DIF_BPF_COEFF1819,  0xfd9a059f},
+{12000000, DIF_BPF_COEFF2021,  0x03e0f978},
+{12000000, DIF_BPF_COEFF2223,  0xfa4e0726},
+{12000000, DIF_BPF_COEFF2425,  0x07c8f8a7},
+{12000000, DIF_BPF_COEFF2627,  0xf600070c},
+{12000000, DIF_BPF_COEFF2829,  0x0c2ff9c9},
+{12000000, DIF_BPF_COEFF3031,  0xf1db04de},
+{12000000, DIF_BPF_COEFF3233,  0x0fb4fce5},
+{12000000, DIF_BPF_COEFF3435,  0xef4b0111},
+{12000000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 120_quant.dat*/
+
+
+/*case 12100000:*/
+/* BEGIN - DIF BPF register values from 121_quant.dat*/
+{12100000, DIF_BPF_COEFF01,    0x0000fffd},
+{12100000, DIF_BPF_COEFF23,    0x00010012},
+{12100000, DIF_BPF_COEFF45,    0xffffffc8},
+{12100000, DIF_BPF_COEFF67,    0xfffb007e},
+{12100000, DIF_BPF_COEFF89,    0x001dff14},
+{12100000, DIF_BPF_COEFF1011,  0xffad0184},
+{12100000, DIF_BPF_COEFF1213,  0x00b7fdbe},
+{12100000, DIF_BPF_COEFF1415,  0xfea9031b},
+{12100000, DIF_BPF_COEFF1617,  0x0241fc01},
+{12100000, DIF_BPF_COEFF1819,  0xfc8504d6},
+{12100000, DIF_BPF_COEFF2021,  0x0504fa79},
+{12100000, DIF_BPF_COEFF2223,  0xf93005f6},
+{12100000, DIF_BPF_COEFF2425,  0x08caf9f2},
+{12100000, DIF_BPF_COEFF2627,  0xf52b05c0},
+{12100000, DIF_BPF_COEFF2829,  0x0ccbfaf9},
+{12100000, DIF_BPF_COEFF3031,  0xf17903eb},
+{12100000, DIF_BPF_COEFF3233,  0x0fe3fd83},
+{12100000, DIF_BPF_COEFF3435,  0xef3f00db},
+{12100000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 121_quant.dat*/
+
+
+/*case 12200000:*/
+/* BEGIN - DIF BPF register values from 122_quant.dat*/
+{12200000, DIF_BPF_COEFF01,    0x0000fffd},
+{12200000, DIF_BPF_COEFF23,    0xfffe0011},
+{12200000, DIF_BPF_COEFF45,    0x000cffcc},
+{12200000, DIF_BPF_COEFF67,    0xffdb0071},
+{12200000, DIF_BPF_COEFF89,    0x0058ff32},
+{12200000, DIF_BPF_COEFF1011,  0xff4f014a},
+{12200000, DIF_BPF_COEFF1213,  0x013cfe1f},
+{12200000, DIF_BPF_COEFF1415,  0xfdfb028a},
+{12200000, DIF_BPF_COEFF1617,  0x0311fcc9},
+{12200000, DIF_BPF_COEFF1819,  0xfb9d03d6},
+{12200000, DIF_BPF_COEFF2021,  0x05f4fbad},
+{12200000, DIF_BPF_COEFF2223,  0xf848049d},
+{12200000, DIF_BPF_COEFF2425,  0x0999fb5b},
+{12200000, DIF_BPF_COEFF2627,  0xf4820461},
+{12200000, DIF_BPF_COEFF2829,  0x0d46fc32},
+{12200000, DIF_BPF_COEFF3031,  0xf12d02f4},
+{12200000, DIF_BPF_COEFF3233,  0x1007fe21},
+{12200000, DIF_BPF_COEFF3435,  0xef3600a4},
+{12200000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 122_quant.dat*/
+
+
+/*case 12300000:*/
+/* BEGIN - DIF BPF register values from 123_quant.dat*/
+{12300000, DIF_BPF_COEFF01,    0x0000fffe},
+{12300000, DIF_BPF_COEFF23,    0xfffa000e},
+{12300000, DIF_BPF_COEFF45,    0x0017ffd9},
+{12300000, DIF_BPF_COEFF67,    0xffc10055},
+{12300000, DIF_BPF_COEFF89,    0x0088ff68},
+{12300000, DIF_BPF_COEFF1011,  0xff0400f0},
+{12300000, DIF_BPF_COEFF1213,  0x01a6fea7},
+{12300000, DIF_BPF_COEFF1415,  0xfd7501cc},
+{12300000, DIF_BPF_COEFF1617,  0x03b0fdc0},
+{12300000, DIF_BPF_COEFF1819,  0xfaef02a8},
+{12300000, DIF_BPF_COEFF2021,  0x06a7fd07},
+{12300000, DIF_BPF_COEFF2223,  0xf79d0326},
+{12300000, DIF_BPF_COEFF2425,  0x0a31fcda},
+{12300000, DIF_BPF_COEFF2627,  0xf40702f3},
+{12300000, DIF_BPF_COEFF2829,  0x0d9ffd72},
+{12300000, DIF_BPF_COEFF3031,  0xf0f601fa},
+{12300000, DIF_BPF_COEFF3233,  0x1021fec0},
+{12300000, DIF_BPF_COEFF3435,  0xef2f006d},
+{12300000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 123_quant.dat*/
+
+
+/*case 12400000:*/
+/* BEGIN - DIF BPF register values from 124_quant.dat*/
+{12400000, DIF_BPF_COEFF01,    0x0001ffff},
+{12400000, DIF_BPF_COEFF23,    0xfff80007},
+{12400000, DIF_BPF_COEFF45,    0x001fffeb},
+{12400000, DIF_BPF_COEFF67,    0xffaf002d},
+{12400000, DIF_BPF_COEFF89,    0x00a8ffb0},
+{12400000, DIF_BPF_COEFF1011,  0xfed3007e},
+{12400000, DIF_BPF_COEFF1213,  0x01e9ff4c},
+{12400000, DIF_BPF_COEFF1415,  0xfd2000ee},
+{12400000, DIF_BPF_COEFF1617,  0x0413fed8},
+{12400000, DIF_BPF_COEFF1819,  0xfa82015c},
+{12400000, DIF_BPF_COEFF2021,  0x0715fe7d},
+{12400000, DIF_BPF_COEFF2223,  0xf7340198},
+{12400000, DIF_BPF_COEFF2425,  0x0a8dfe69},
+{12400000, DIF_BPF_COEFF2627,  0xf3bd017c},
+{12400000, DIF_BPF_COEFF2829,  0x0dd5feb8},
+{12400000, DIF_BPF_COEFF3031,  0xf0d500fd},
+{12400000, DIF_BPF_COEFF3233,  0x1031ff60},
+{12400000, DIF_BPF_COEFF3435,  0xef2b0037},
+{12400000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 124_quant.dat*/
+
+
+/*case 12500000:*/
+/* BEGIN - DIF BPF register values from 125_quant.dat*/
+{12500000, DIF_BPF_COEFF01,    0x00010000},
+{12500000, DIF_BPF_COEFF23,    0xfff70000},
+{12500000, DIF_BPF_COEFF45,    0x00220000},
+{12500000, DIF_BPF_COEFF67,    0xffa90000},
+{12500000, DIF_BPF_COEFF89,    0x00b30000},
+{12500000, DIF_BPF_COEFF1011,  0xfec20000},
+{12500000, DIF_BPF_COEFF1213,  0x02000000},
+{12500000, DIF_BPF_COEFF1415,  0xfd030000},
+{12500000, DIF_BPF_COEFF1617,  0x04350000},
+{12500000, DIF_BPF_COEFF1819,  0xfa5e0000},
+{12500000, DIF_BPF_COEFF2021,  0x073b0000},
+{12500000, DIF_BPF_COEFF2223,  0xf7110000},
+{12500000, DIF_BPF_COEFF2425,  0x0aac0000},
+{12500000, DIF_BPF_COEFF2627,  0xf3a40000},
+{12500000, DIF_BPF_COEFF2829,  0x0de70000},
+{12500000, DIF_BPF_COEFF3031,  0xf0c90000},
+{12500000, DIF_BPF_COEFF3233,  0x10360000},
+{12500000, DIF_BPF_COEFF3435,  0xef290000},
+{12500000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 125_quant.dat*/
+
+
+/*case 12600000:*/
+/* BEGIN - DIF BPF register values from 126_quant.dat*/
+{12600000, DIF_BPF_COEFF01,    0x00010001},
+{12600000, DIF_BPF_COEFF23,    0xfff8fff9},
+{12600000, DIF_BPF_COEFF45,    0x001f0015},
+{12600000, DIF_BPF_COEFF67,    0xffafffd3},
+{12600000, DIF_BPF_COEFF89,    0x00a80050},
+{12600000, DIF_BPF_COEFF1011,  0xfed3ff82},
+{12600000, DIF_BPF_COEFF1213,  0x01e900b4},
+{12600000, DIF_BPF_COEFF1415,  0xfd20ff12},
+{12600000, DIF_BPF_COEFF1617,  0x04130128},
+{12600000, DIF_BPF_COEFF1819,  0xfa82fea4},
+{12600000, DIF_BPF_COEFF2021,  0x07150183},
+{12600000, DIF_BPF_COEFF2223,  0xf734fe68},
+{12600000, DIF_BPF_COEFF2425,  0x0a8d0197},
+{12600000, DIF_BPF_COEFF2627,  0xf3bdfe84},
+{12600000, DIF_BPF_COEFF2829,  0x0dd50148},
+{12600000, DIF_BPF_COEFF3031,  0xf0d5ff03},
+{12600000, DIF_BPF_COEFF3233,  0x103100a0},
+{12600000, DIF_BPF_COEFF3435,  0xef2bffc9},
+{12600000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 126_quant.dat*/
+
+
+/*case 12700000:*/
+/* BEGIN - DIF BPF register values from 127_quant.dat*/
+{12700000, DIF_BPF_COEFF01,    0x00000002},
+{12700000, DIF_BPF_COEFF23,    0xfffafff2},
+{12700000, DIF_BPF_COEFF45,    0x00170027},
+{12700000, DIF_BPF_COEFF67,    0xffc1ffab},
+{12700000, DIF_BPF_COEFF89,    0x00880098},
+{12700000, DIF_BPF_COEFF1011,  0xff04ff10},
+{12700000, DIF_BPF_COEFF1213,  0x01a60159},
+{12700000, DIF_BPF_COEFF1415,  0xfd75fe34},
+{12700000, DIF_BPF_COEFF1617,  0x03b00240},
+{12700000, DIF_BPF_COEFF1819,  0xfaeffd58},
+{12700000, DIF_BPF_COEFF2021,  0x06a702f9},
+{12700000, DIF_BPF_COEFF2223,  0xf79dfcda},
+{12700000, DIF_BPF_COEFF2425,  0x0a310326},
+{12700000, DIF_BPF_COEFF2627,  0xf407fd0d},
+{12700000, DIF_BPF_COEFF2829,  0x0d9f028e},
+{12700000, DIF_BPF_COEFF3031,  0xf0f6fe06},
+{12700000, DIF_BPF_COEFF3233,  0x10210140},
+{12700000, DIF_BPF_COEFF3435,  0xef2fff93},
+{12700000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 127_quant.dat*/
+
+
+/*case 12800000:*/
+/* BEGIN - DIF BPF register values from 128_quant.dat*/
+{12800000, DIF_BPF_COEFF01,    0x00000003},
+{12800000, DIF_BPF_COEFF23,    0xfffeffef},
+{12800000, DIF_BPF_COEFF45,    0x000c0034},
+{12800000, DIF_BPF_COEFF67,    0xffdbff8f},
+{12800000, DIF_BPF_COEFF89,    0x005800ce},
+{12800000, DIF_BPF_COEFF1011,  0xff4ffeb6},
+{12800000, DIF_BPF_COEFF1213,  0x013c01e1},
+{12800000, DIF_BPF_COEFF1415,  0xfdfbfd76},
+{12800000, DIF_BPF_COEFF1617,  0x03110337},
+{12800000, DIF_BPF_COEFF1819,  0xfb9dfc2a},
+{12800000, DIF_BPF_COEFF2021,  0x05f40453},
+{12800000, DIF_BPF_COEFF2223,  0xf848fb63},
+{12800000, DIF_BPF_COEFF2425,  0x099904a5},
+{12800000, DIF_BPF_COEFF2627,  0xf482fb9f},
+{12800000, DIF_BPF_COEFF2829,  0x0d4603ce},
+{12800000, DIF_BPF_COEFF3031,  0xf12dfd0c},
+{12800000, DIF_BPF_COEFF3233,  0x100701df},
+{12800000, DIF_BPF_COEFF3435,  0xef36ff5c},
+{12800000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 128_quant.dat*/
+
+
+/*case 12900000:*/
+/* BEGIN - DIF BPF register values from 129_quant.dat*/
+{12900000, DIF_BPF_COEFF01,    0x00000003},
+{12900000, DIF_BPF_COEFF23,    0x0001ffee},
+{12900000, DIF_BPF_COEFF45,    0xffff0038},
+{12900000, DIF_BPF_COEFF67,    0xfffbff82},
+{12900000, DIF_BPF_COEFF89,    0x001d00ec},
+{12900000, DIF_BPF_COEFF1011,  0xffadfe7c},
+{12900000, DIF_BPF_COEFF1213,  0x00b70242},
+{12900000, DIF_BPF_COEFF1415,  0xfea9fce5},
+{12900000, DIF_BPF_COEFF1617,  0x024103ff},
+{12900000, DIF_BPF_COEFF1819,  0xfc85fb2a},
+{12900000, DIF_BPF_COEFF2021,  0x05040587},
+{12900000, DIF_BPF_COEFF2223,  0xf930fa0a},
+{12900000, DIF_BPF_COEFF2425,  0x08ca060e},
+{12900000, DIF_BPF_COEFF2627,  0xf52bfa40},
+{12900000, DIF_BPF_COEFF2829,  0x0ccb0507},
+{12900000, DIF_BPF_COEFF3031,  0xf179fc15},
+{12900000, DIF_BPF_COEFF3233,  0x0fe3027d},
+{12900000, DIF_BPF_COEFF3435,  0xef3fff25},
+{12900000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 129_quant.dat*/
+
+
+/*case 113000000:*/
+/* BEGIN - DIF BPF register values from 130_quant.dat*/
+{13000000, DIF_BPF_COEFF01,    0x00000002},
+{13000000, DIF_BPF_COEFF23,    0x0005fff0},
+{13000000, DIF_BPF_COEFF45,    0xfff20034},
+{13000000, DIF_BPF_COEFF67,    0x001bff85},
+{13000000, DIF_BPF_COEFF89,    0xffdf00f0},
+{13000000, DIF_BPF_COEFF1011,  0x0014fe68},
+{13000000, DIF_BPF_COEFF1213,  0x00200272},
+{13000000, DIF_BPF_COEFF1415,  0xff71fc8b},
+{13000000, DIF_BPF_COEFF1617,  0x014d048d},
+{13000000, DIF_BPF_COEFF1819,  0xfd9afa61},
+{13000000, DIF_BPF_COEFF2021,  0x03e00688},
+{13000000, DIF_BPF_COEFF2223,  0xfa4ef8da},
+{13000000, DIF_BPF_COEFF2425,  0x07c80759},
+{13000000, DIF_BPF_COEFF2627,  0xf600f8f4},
+{13000000, DIF_BPF_COEFF2829,  0x0c2f0637},
+{13000000, DIF_BPF_COEFF3031,  0xf1dbfb22},
+{13000000, DIF_BPF_COEFF3233,  0x0fb4031b},
+{13000000, DIF_BPF_COEFF3435,  0xef4bfeef},
+{13000000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 130_quant.dat*/
+
+
+/*case 13100000:*/
+/* BEGIN - DIF BPF register values from 131_quant.dat*/
+{13100000, DIF_BPF_COEFF01,    0xffff0001},
+{13100000, DIF_BPF_COEFF23,    0x0007fff5},
+{13100000, DIF_BPF_COEFF45,    0xffe70028},
+{13100000, DIF_BPF_COEFF67,    0x0037ff98},
+{13100000, DIF_BPF_COEFF89,    0xffa400d8},
+{13100000, DIF_BPF_COEFF1011,  0x0079fe7c},
+{13100000, DIF_BPF_COEFF1213,  0xff87026f},
+{13100000, DIF_BPF_COEFF1415,  0x0043fc6e},
+{13100000, DIF_BPF_COEFF1617,  0x004404da},
+{13100000, DIF_BPF_COEFF1819,  0xfecef9da},
+{13100000, DIF_BPF_COEFF2021,  0x0294074e},
+{13100000, DIF_BPF_COEFF2223,  0xfb99f7db},
+{13100000, DIF_BPF_COEFF2425,  0x06980881},
+{13100000, DIF_BPF_COEFF2627,  0xf6fef7be},
+{13100000, DIF_BPF_COEFF2829,  0x0b730759},
+{13100000, DIF_BPF_COEFF3031,  0xf251fa33},
+{13100000, DIF_BPF_COEFF3233,  0x0f7b03b8},
+{13100000, DIF_BPF_COEFF3435,  0xef5afeb8},
+{13100000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 131_quant.dat*/
+
+
+/*case 13200000:*/
+/* BEGIN - DIF BPF register values from 132_quant.dat*/
+{13200000, DIF_BPF_COEFF01,    0xffff0000},
+{13200000, DIF_BPF_COEFF23,    0x0008fffc},
+{13200000, DIF_BPF_COEFF45,    0xffe00017},
+{13200000, DIF_BPF_COEFF67,    0x004cffb9},
+{13200000, DIF_BPF_COEFF89,    0xff7500a8},
+{13200000, DIF_BPF_COEFF1011,  0x00d1feb6},
+{13200000, DIF_BPF_COEFF1213,  0xfef90238},
+{13200000, DIF_BPF_COEFF1415,  0x0111fc91},
+{13200000, DIF_BPF_COEFF1617,  0xff3604df},
+{13200000, DIF_BPF_COEFF1819,  0x0012f99b},
+{13200000, DIF_BPF_COEFF2021,  0x012d07d2},
+{13200000, DIF_BPF_COEFF2223,  0xfd07f714},
+{13200000, DIF_BPF_COEFF2425,  0x0542097e},
+{13200000, DIF_BPF_COEFF2627,  0xf81ff6a4},
+{13200000, DIF_BPF_COEFF2829,  0x0a9a086e},
+{13200000, DIF_BPF_COEFF3031,  0xf2dbf94b},
+{13200000, DIF_BPF_COEFF3233,  0x0f380453},
+{13200000, DIF_BPF_COEFF3435,  0xef6cfe82},
+{13200000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 132_quant.dat*/
+
+
+/*case 13300000:*/
+/* BEGIN - DIF BPF register values from 133_quant.dat*/
+{13300000, DIF_BPF_COEFF01,    0xffffffff},
+{13300000, DIF_BPF_COEFF23,    0x00080003},
+{13300000, DIF_BPF_COEFF45,    0xffde0001},
+{13300000, DIF_BPF_COEFF67,    0x0056ffe3},
+{13300000, DIF_BPF_COEFF89,    0xff570064},
+{13300000, DIF_BPF_COEFF1011,  0x0113ff10},
+{13300000, DIF_BPF_COEFF1213,  0xfe8201d2},
+{13300000, DIF_BPF_COEFF1415,  0x01cafcf0},
+{13300000, DIF_BPF_COEFF1617,  0xfe35049e},
+{13300000, DIF_BPF_COEFF1819,  0x0155f9a6},
+{13300000, DIF_BPF_COEFF2021,  0xffba080e},
+{13300000, DIF_BPF_COEFF2223,  0xfe8cf689},
+{13300000, DIF_BPF_COEFF2425,  0x03ce0a4e},
+{13300000, DIF_BPF_COEFF2627,  0xf961f5a8},
+{13300000, DIF_BPF_COEFF2829,  0x09a50971},
+{13300000, DIF_BPF_COEFF3031,  0xf379f869},
+{13300000, DIF_BPF_COEFF3233,  0x0eeb04ec},
+{13300000, DIF_BPF_COEFF3435,  0xef80fe4b},
+{13300000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 133_quant.dat*/
+
+
+/*case 13400000:*/
+/* BEGIN - DIF BPF register values from 134_quant.dat*/
+{13400000, DIF_BPF_COEFF01,    0x0000fffe},
+{13400000, DIF_BPF_COEFF23,    0x0007000a},
+{13400000, DIF_BPF_COEFF45,    0xffe2ffec},
+{13400000, DIF_BPF_COEFF67,    0x00540012},
+{13400000, DIF_BPF_COEFF89,    0xff4e0015},
+{13400000, DIF_BPF_COEFF1011,  0x0137ff82},
+{13400000, DIF_BPF_COEFF1213,  0xfe2e0145},
+{13400000, DIF_BPF_COEFF1415,  0x0260fd86},
+{13400000, DIF_BPF_COEFF1617,  0xfd51041a},
+{13400000, DIF_BPF_COEFF1819,  0x0287f9fb},
+{13400000, DIF_BPF_COEFF2021,  0xfe4a0802},
+{13400000, DIF_BPF_COEFF2223,  0x001df63f},
+{13400000, DIF_BPF_COEFF2425,  0x02430aeb},
+{13400000, DIF_BPF_COEFF2627,  0xfabdf4ce},
+{13400000, DIF_BPF_COEFF2829,  0x08970a62},
+{13400000, DIF_BPF_COEFF3031,  0xf428f78f},
+{13400000, DIF_BPF_COEFF3233,  0x0e950584},
+{13400000, DIF_BPF_COEFF3435,  0xef97fe15},
+{13400000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 134_quant.dat*/
+
+
+/*case 13500000:*/
+/* BEGIN - DIF BPF register values from 135_quant.dat*/
+{13500000, DIF_BPF_COEFF01,    0x0000fffd},
+{13500000, DIF_BPF_COEFF23,    0x0004000f},
+{13500000, DIF_BPF_COEFF45,    0xffeaffda},
+{13500000, DIF_BPF_COEFF67,    0x0046003d},
+{13500000, DIF_BPF_COEFF89,    0xff5affc4},
+{13500000, DIF_BPF_COEFF1011,  0x013b0000},
+{13500000, DIF_BPF_COEFF1213,  0xfe04009d},
+{13500000, DIF_BPF_COEFF1415,  0x02c8fe48},
+{13500000, DIF_BPF_COEFF1617,  0xfc99035a},
+{13500000, DIF_BPF_COEFF1819,  0x0397fa96},
+{13500000, DIF_BPF_COEFF2021,  0xfcec07ad},
+{13500000, DIF_BPF_COEFF2223,  0x01adf637},
+{13500000, DIF_BPF_COEFF2425,  0x00ac0b53},
+{13500000, DIF_BPF_COEFF2627,  0xfc2ef419},
+{13500000, DIF_BPF_COEFF2829,  0x07730b3e},
+{13500000, DIF_BPF_COEFF3031,  0xf4e9f6bd},
+{13500000, DIF_BPF_COEFF3233,  0x0e35061a},
+{13500000, DIF_BPF_COEFF3435,  0xefb1fddf},
+{13500000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 135_quant.dat*/
+
+
+/*case 13600000:*/
+/* BEGIN - DIF BPF register values from 136_quant.dat*/
+{13600000, DIF_BPF_COEFF01,    0x0000fffd},
+{13600000, DIF_BPF_COEFF23,    0x00000012},
+{13600000, DIF_BPF_COEFF45,    0xfff6ffcd},
+{13600000, DIF_BPF_COEFF67,    0x002f0061},
+{13600000, DIF_BPF_COEFF89,    0xff7bff79},
+{13600000, DIF_BPF_COEFF1011,  0x011e007e},
+{13600000, DIF_BPF_COEFF1213,  0xfe08ffe8},
+{13600000, DIF_BPF_COEFF1415,  0x02f9ff28},
+{13600000, DIF_BPF_COEFF1617,  0xfc17026a},
+{13600000, DIF_BPF_COEFF1819,  0x0479fb70},
+{13600000, DIF_BPF_COEFF2021,  0xfbad0713},
+{13600000, DIF_BPF_COEFF2223,  0x032ff672},
+{13600000, DIF_BPF_COEFF2425,  0xff100b83},
+{13600000, DIF_BPF_COEFF2627,  0xfdaff38b},
+{13600000, DIF_BPF_COEFF2829,  0x063c0c04},
+{13600000, DIF_BPF_COEFF3031,  0xf5baf5f5},
+{13600000, DIF_BPF_COEFF3233,  0x0dcc06ae},
+{13600000, DIF_BPF_COEFF3435,  0xefcdfda8},
+{13600000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 136_quant.dat*/
+
+
+/*case 13700000:*/
+/* BEGIN - DIF BPF register values from 137_quant.dat*/
+{13700000, DIF_BPF_COEFF01,    0x0000fffd},
+{13700000, DIF_BPF_COEFF23,    0xfffd0012},
+{13700000, DIF_BPF_COEFF45,    0x0004ffc8},
+{13700000, DIF_BPF_COEFF67,    0x00100078},
+{13700000, DIF_BPF_COEFF89,    0xffacff3e},
+{13700000, DIF_BPF_COEFF1011,  0x00e200f0},
+{13700000, DIF_BPF_COEFF1213,  0xfe39ff35},
+{13700000, DIF_BPF_COEFF1415,  0x02f10017},
+{13700000, DIF_BPF_COEFF1617,  0xfbd30156},
+{13700000, DIF_BPF_COEFF1819,  0x0521fc7f},
+{13700000, DIF_BPF_COEFF2021,  0xfa9c0638},
+{13700000, DIF_BPF_COEFF2223,  0x0499f6ee},
+{13700000, DIF_BPF_COEFF2425,  0xfd7a0b7c},
+{13700000, DIF_BPF_COEFF2627,  0xff39f325},
+{13700000, DIF_BPF_COEFF2829,  0x04f40cb3},
+{13700000, DIF_BPF_COEFF3031,  0xf69af537},
+{13700000, DIF_BPF_COEFF3233,  0x0d5a073f},
+{13700000, DIF_BPF_COEFF3435,  0xefecfd72},
+{13700000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 137_quant.dat*/
+
+
+/*case 13800000:*/
+/* BEGIN - DIF BPF register values from 138_quant.dat*/
+{13800000, DIF_BPF_COEFF01,    0x0001fffe},
+{13800000, DIF_BPF_COEFF23,    0xfffa000e},
+{13800000, DIF_BPF_COEFF45,    0x0011ffcb},
+{13800000, DIF_BPF_COEFF67,    0xfff0007f},
+{13800000, DIF_BPF_COEFF89,    0xffe7ff19},
+{13800000, DIF_BPF_COEFF1011,  0x008f014a},
+{13800000, DIF_BPF_COEFF1213,  0xfe94fe93},
+{13800000, DIF_BPF_COEFF1415,  0x02b00105},
+{13800000, DIF_BPF_COEFF1617,  0xfbd3002f},
+{13800000, DIF_BPF_COEFF1819,  0x0585fdb7},
+{13800000, DIF_BPF_COEFF2021,  0xf9c10525},
+{13800000, DIF_BPF_COEFF2223,  0x05def7a8},
+{13800000, DIF_BPF_COEFF2425,  0xfbf20b3c},
+{13800000, DIF_BPF_COEFF2627,  0x00c7f2e9},
+{13800000, DIF_BPF_COEFF2829,  0x03a00d48},
+{13800000, DIF_BPF_COEFF3031,  0xf787f484},
+{13800000, DIF_BPF_COEFF3233,  0x0cdf07cd},
+{13800000, DIF_BPF_COEFF3435,  0xf00dfd3c},
+{13800000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 138_quant.dat*/
+
+
+/*case 13900000:*/
+/* BEGIN - DIF BPF register values from 139_quant.dat*/
+{13900000, DIF_BPF_COEFF01,    0x00010000},
+{13900000, DIF_BPF_COEFF23,    0xfff80008},
+{13900000, DIF_BPF_COEFF45,    0x001bffd7},
+{13900000, DIF_BPF_COEFF67,    0xffd10076},
+{13900000, DIF_BPF_COEFF89,    0x0026ff0e},
+{13900000, DIF_BPF_COEFF1011,  0x002c0184},
+{13900000, DIF_BPF_COEFF1213,  0xff0ffe10},
+{13900000, DIF_BPF_COEFF1415,  0x023b01e0},
+{13900000, DIF_BPF_COEFF1617,  0xfc17ff06},
+{13900000, DIF_BPF_COEFF1819,  0x05a2ff09},
+{13900000, DIF_BPF_COEFF2021,  0xf92703e4},
+{13900000, DIF_BPF_COEFF2223,  0x06f4f89b},
+{13900000, DIF_BPF_COEFF2425,  0xfa820ac5},
+{13900000, DIF_BPF_COEFF2627,  0x0251f2d9},
+{13900000, DIF_BPF_COEFF2829,  0x02430dc3},
+{13900000, DIF_BPF_COEFF3031,  0xf881f3dc},
+{13900000, DIF_BPF_COEFF3233,  0x0c5c0859},
+{13900000, DIF_BPF_COEFF3435,  0xf031fd06},
+{13900000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 139_quant.dat*/
+
+
+/*case 14000000:*/
+/* BEGIN - DIF BPF register values from 140_quant.dat*/
+{14000000, DIF_BPF_COEFF01,    0x00010001},
+{14000000, DIF_BPF_COEFF23,    0xfff80001},
+{14000000, DIF_BPF_COEFF45,    0x0021ffe8},
+{14000000, DIF_BPF_COEFF67,    0xffba005d},
+{14000000, DIF_BPF_COEFF89,    0x0060ff1f},
+{14000000, DIF_BPF_COEFF1011,  0xffc40198},
+{14000000, DIF_BPF_COEFF1213,  0xffa0fdb5},
+{14000000, DIF_BPF_COEFF1415,  0x019a029a},
+{14000000, DIF_BPF_COEFF1617,  0xfc99fdea},
+{14000000, DIF_BPF_COEFF1819,  0x05750067},
+{14000000, DIF_BPF_COEFF2021,  0xf8d4027f},
+{14000000, DIF_BPF_COEFF2223,  0x07d4f9c0},
+{14000000, DIF_BPF_COEFF2425,  0xf9320a1a},
+{14000000, DIF_BPF_COEFF2627,  0x03d2f2f3},
+{14000000, DIF_BPF_COEFF2829,  0x00df0e22},
+{14000000, DIF_BPF_COEFF3031,  0xf986f341},
+{14000000, DIF_BPF_COEFF3233,  0x0bd108e2},
+{14000000, DIF_BPF_COEFF3435,  0xf058fcd1},
+{14000000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 140_quant.dat*/
+
+
+/*case 14100000:*/
+/* BEGIN - DIF BPF register values from 141_quant.dat*/
+{14100000, DIF_BPF_COEFF01,    0x00000002},
+{14100000, DIF_BPF_COEFF23,    0xfff9fffa},
+{14100000, DIF_BPF_COEFF45,    0x0021fffd},
+{14100000, DIF_BPF_COEFF67,    0xffac0038},
+{14100000, DIF_BPF_COEFF89,    0x008eff4a},
+{14100000, DIF_BPF_COEFF1011,  0xff630184},
+{14100000, DIF_BPF_COEFF1213,  0x003afd8b},
+{14100000, DIF_BPF_COEFF1415,  0x00da0326},
+{14100000, DIF_BPF_COEFF1617,  0xfd51fced},
+{14100000, DIF_BPF_COEFF1819,  0x050101c0},
+{14100000, DIF_BPF_COEFF2021,  0xf8cb0103},
+{14100000, DIF_BPF_COEFF2223,  0x0876fb10},
+{14100000, DIF_BPF_COEFF2425,  0xf80a093e},
+{14100000, DIF_BPF_COEFF2627,  0x0543f338},
+{14100000, DIF_BPF_COEFF2829,  0xff7a0e66},
+{14100000, DIF_BPF_COEFF3031,  0xfa94f2b2},
+{14100000, DIF_BPF_COEFF3233,  0x0b3f0967},
+{14100000, DIF_BPF_COEFF3435,  0xf081fc9b},
+{14100000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 141_quant.dat*/
+
+
+/*case 14200000:*/
+/* BEGIN - DIF BPF register values from 142_quant.dat*/
+{14200000, DIF_BPF_COEFF01,    0x00000003},
+{14200000, DIF_BPF_COEFF23,    0xfffbfff3},
+{14200000, DIF_BPF_COEFF45,    0x001d0013},
+{14200000, DIF_BPF_COEFF67,    0xffaa000b},
+{14200000, DIF_BPF_COEFF89,    0x00aaff89},
+{14200000, DIF_BPF_COEFF1011,  0xff13014a},
+{14200000, DIF_BPF_COEFF1213,  0x00cefd95},
+{14200000, DIF_BPF_COEFF1415,  0x000a037b},
+{14200000, DIF_BPF_COEFF1617,  0xfe35fc1d},
+{14200000, DIF_BPF_COEFF1819,  0x044c0305},
+{14200000, DIF_BPF_COEFF2021,  0xf90cff7e},
+{14200000, DIF_BPF_COEFF2223,  0x08d5fc81},
+{14200000, DIF_BPF_COEFF2425,  0xf7100834},
+{14200000, DIF_BPF_COEFF2627,  0x069ff3a7},
+{14200000, DIF_BPF_COEFF2829,  0xfe160e8d},
+{14200000, DIF_BPF_COEFF3031,  0xfbaaf231},
+{14200000, DIF_BPF_COEFF3233,  0x0aa509e9},
+{14200000, DIF_BPF_COEFF3435,  0xf0adfc65},
+{14200000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 142_quant.dat*/
+
+
+/*case 14300000:*/
+/* BEGIN - DIF BPF register values from 143_quant.dat*/
+{14300000, DIF_BPF_COEFF01,    0x00000003},
+{14300000, DIF_BPF_COEFF23,    0xffffffef},
+{14300000, DIF_BPF_COEFF45,    0x00140025},
+{14300000, DIF_BPF_COEFF67,    0xffb4ffdd},
+{14300000, DIF_BPF_COEFF89,    0x00b2ffd6},
+{14300000, DIF_BPF_COEFF1011,  0xfedb00f0},
+{14300000, DIF_BPF_COEFF1213,  0x0150fdd3},
+{14300000, DIF_BPF_COEFF1415,  0xff380391},
+{14300000, DIF_BPF_COEFF1617,  0xff36fb85},
+{14300000, DIF_BPF_COEFF1819,  0x035e0426},
+{14300000, DIF_BPF_COEFF2021,  0xf994fdfe},
+{14300000, DIF_BPF_COEFF2223,  0x08eefe0b},
+{14300000, DIF_BPF_COEFF2425,  0xf6490702},
+{14300000, DIF_BPF_COEFF2627,  0x07e1f43e},
+{14300000, DIF_BPF_COEFF2829,  0xfcb60e97},
+{14300000, DIF_BPF_COEFF3031,  0xfcc6f1be},
+{14300000, DIF_BPF_COEFF3233,  0x0a040a67},
+{14300000, DIF_BPF_COEFF3435,  0xf0dbfc30},
+{14300000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 143_quant.dat*/
+
+
+/*case 14400000:*/
+/* BEGIN - DIF BPF register values from 144_quant.dat*/
+{14400000, DIF_BPF_COEFF01,    0x00000003},
+{14400000, DIF_BPF_COEFF23,    0x0002ffee},
+{14400000, DIF_BPF_COEFF45,    0x00070033},
+{14400000, DIF_BPF_COEFF67,    0xffc9ffb4},
+{14400000, DIF_BPF_COEFF89,    0x00a40027},
+{14400000, DIF_BPF_COEFF1011,  0xfec3007e},
+{14400000, DIF_BPF_COEFF1213,  0x01b4fe3f},
+{14400000, DIF_BPF_COEFF1415,  0xfe760369},
+{14400000, DIF_BPF_COEFF1617,  0x0044fb2e},
+{14400000, DIF_BPF_COEFF1819,  0x02450518},
+{14400000, DIF_BPF_COEFF2021,  0xfa5ffc90},
+{14400000, DIF_BPF_COEFF2223,  0x08c1ffa1},
+{14400000, DIF_BPF_COEFF2425,  0xf5bc05ae},
+{14400000, DIF_BPF_COEFF2627,  0x0902f4fc},
+{14400000, DIF_BPF_COEFF2829,  0xfb600e85},
+{14400000, DIF_BPF_COEFF3031,  0xfde7f15a},
+{14400000, DIF_BPF_COEFF3233,  0x095d0ae2},
+{14400000, DIF_BPF_COEFF3435,  0xf10cfbfb},
+{14400000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 144_quant.dat*/
+
+
+/*case 14500000:*/
+/* BEGIN - DIF BPF register values from 145_quant.dat*/
+{14500000, DIF_BPF_COEFF01,    0xffff0002},
+{14500000, DIF_BPF_COEFF23,    0x0005ffef},
+{14500000, DIF_BPF_COEFF45,    0xfffa0038},
+{14500000, DIF_BPF_COEFF67,    0xffe5ff95},
+{14500000, DIF_BPF_COEFF89,    0x00820074},
+{14500000, DIF_BPF_COEFF1011,  0xfecc0000},
+{14500000, DIF_BPF_COEFF1213,  0x01f0fed0},
+{14500000, DIF_BPF_COEFF1415,  0xfdd20304},
+{14500000, DIF_BPF_COEFF1617,  0x014dfb1d},
+{14500000, DIF_BPF_COEFF1819,  0x010e05ce},
+{14500000, DIF_BPF_COEFF2021,  0xfb64fb41},
+{14500000, DIF_BPF_COEFF2223,  0x084e013b},
+{14500000, DIF_BPF_COEFF2425,  0xf569043e},
+{14500000, DIF_BPF_COEFF2627,  0x0a00f5dd},
+{14500000, DIF_BPF_COEFF2829,  0xfa150e55},
+{14500000, DIF_BPF_COEFF3031,  0xff0bf104},
+{14500000, DIF_BPF_COEFF3233,  0x08b00b59},
+{14500000, DIF_BPF_COEFF3435,  0xf13ffbc6},
+{14500000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 145_quant.dat*/
+
+
+/*case 14600000:*/
+/* BEGIN - DIF BPF register values from 146_quant.dat*/
+{14600000, DIF_BPF_COEFF01,    0xffff0001},
+{14600000, DIF_BPF_COEFF23,    0x0008fff4},
+{14600000, DIF_BPF_COEFF45,    0xffed0035},
+{14600000, DIF_BPF_COEFF67,    0x0005ff83},
+{14600000, DIF_BPF_COEFF89,    0x005000b4},
+{14600000, DIF_BPF_COEFF1011,  0xfef6ff82},
+{14600000, DIF_BPF_COEFF1213,  0x01ffff7a},
+{14600000, DIF_BPF_COEFF1415,  0xfd580269},
+{14600000, DIF_BPF_COEFF1617,  0x0241fb53},
+{14600000, DIF_BPF_COEFF1819,  0xffca0640},
+{14600000, DIF_BPF_COEFF2021,  0xfc99fa1e},
+{14600000, DIF_BPF_COEFF2223,  0x079a02cb},
+{14600000, DIF_BPF_COEFF2425,  0xf55502ba},
+{14600000, DIF_BPF_COEFF2627,  0x0ad5f6e0},
+{14600000, DIF_BPF_COEFF2829,  0xf8d90e0a},
+{14600000, DIF_BPF_COEFF3031,  0x0031f0bd},
+{14600000, DIF_BPF_COEFF3233,  0x07fd0bcb},
+{14600000, DIF_BPF_COEFF3435,  0xf174fb91},
+{14600000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 146_quant.dat*/
+
+
+/*case 14700000:*/
+/* BEGIN - DIF BPF register values from 147_quant.dat*/
+{14700000, DIF_BPF_COEFF01,    0xffffffff},
+{14700000, DIF_BPF_COEFF23,    0x0009fffb},
+{14700000, DIF_BPF_COEFF45,    0xffe4002a},
+{14700000, DIF_BPF_COEFF67,    0x0025ff82},
+{14700000, DIF_BPF_COEFF89,    0x001400e0},
+{14700000, DIF_BPF_COEFF1011,  0xff3cff10},
+{14700000, DIF_BPF_COEFF1213,  0x01e10030},
+{14700000, DIF_BPF_COEFF1415,  0xfd1201a4},
+{14700000, DIF_BPF_COEFF1617,  0x0311fbcd},
+{14700000, DIF_BPF_COEFF1819,  0xfe88066a},
+{14700000, DIF_BPF_COEFF2021,  0xfdf1f92f},
+{14700000, DIF_BPF_COEFF2223,  0x06aa0449},
+{14700000, DIF_BPF_COEFF2425,  0xf57e0128},
+{14700000, DIF_BPF_COEFF2627,  0x0b7ef801},
+{14700000, DIF_BPF_COEFF2829,  0xf7b00da2},
+{14700000, DIF_BPF_COEFF3031,  0x0156f086},
+{14700000, DIF_BPF_COEFF3233,  0x07450c39},
+{14700000, DIF_BPF_COEFF3435,  0xf1acfb5c},
+{14700000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 147_quant.dat*/
+
+
+/*case 14800000:*/
+/* BEGIN - DIF BPF register values from 148_quant.dat*/
+{14800000, DIF_BPF_COEFF01,    0x0000fffe},
+{14800000, DIF_BPF_COEFF23,    0x00080002},
+{14800000, DIF_BPF_COEFF45,    0xffdf0019},
+{14800000, DIF_BPF_COEFF67,    0x003fff92},
+{14800000, DIF_BPF_COEFF89,    0xffd600f1},
+{14800000, DIF_BPF_COEFF1011,  0xff96feb6},
+{14800000, DIF_BPF_COEFF1213,  0x019700e1},
+{14800000, DIF_BPF_COEFF1415,  0xfd0500c2},
+{14800000, DIF_BPF_COEFF1617,  0x03b0fc84},
+{14800000, DIF_BPF_COEFF1819,  0xfd590649},
+{14800000, DIF_BPF_COEFF2021,  0xff5df87f},
+{14800000, DIF_BPF_COEFF2223,  0x058505aa},
+{14800000, DIF_BPF_COEFF2425,  0xf5e4ff91},
+{14800000, DIF_BPF_COEFF2627,  0x0bf9f93c},
+{14800000, DIF_BPF_COEFF2829,  0xf69d0d20},
+{14800000, DIF_BPF_COEFF3031,  0x0279f05e},
+{14800000, DIF_BPF_COEFF3233,  0x06880ca3},
+{14800000, DIF_BPF_COEFF3435,  0xf1e6fb28},
+{14800000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 148_quant.dat*/
+
+
+/*case 14900000:*/
+/* BEGIN - DIF BPF register values from 149_quant.dat*/
+{14900000, DIF_BPF_COEFF01,    0x0000fffd},
+{14900000, DIF_BPF_COEFF23,    0x00060009},
+{14900000, DIF_BPF_COEFF45,    0xffdf0004},
+{14900000, DIF_BPF_COEFF67,    0x0051ffb0},
+{14900000, DIF_BPF_COEFF89,    0xff9d00e8},
+{14900000, DIF_BPF_COEFF1011,  0xfffcfe7c},
+{14900000, DIF_BPF_COEFF1213,  0x01280180},
+{14900000, DIF_BPF_COEFF1415,  0xfd32ffd2},
+{14900000, DIF_BPF_COEFF1617,  0x0413fd6e},
+{14900000, DIF_BPF_COEFF1819,  0xfc4d05df},
+{14900000, DIF_BPF_COEFF2021,  0x00d1f812},
+{14900000, DIF_BPF_COEFF2223,  0x043506e4},
+{14900000, DIF_BPF_COEFF2425,  0xf685fdfb},
+{14900000, DIF_BPF_COEFF2627,  0x0c43fa8d},
+{14900000, DIF_BPF_COEFF2829,  0xf5a10c83},
+{14900000, DIF_BPF_COEFF3031,  0x0399f046},
+{14900000, DIF_BPF_COEFF3233,  0x05c70d08},
+{14900000, DIF_BPF_COEFF3435,  0xf222faf3},
+{14900000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 149_quant.dat*/
+
+
+/*case 15000000:*/
+/* BEGIN - DIF BPF register values from 150_quant.dat*/
+{15000000, DIF_BPF_COEFF01,    0x0000fffd},
+{15000000, DIF_BPF_COEFF23,    0x0003000f},
+{15000000, DIF_BPF_COEFF45,    0xffe5ffef},
+{15000000, DIF_BPF_COEFF67,    0x0057ffd9},
+{15000000, DIF_BPF_COEFF89,    0xff7000c4},
+{15000000, DIF_BPF_COEFF1011,  0x0062fe68},
+{15000000, DIF_BPF_COEFF1213,  0x009e01ff},
+{15000000, DIF_BPF_COEFF1415,  0xfd95fee6},
+{15000000, DIF_BPF_COEFF1617,  0x0435fe7d},
+{15000000, DIF_BPF_COEFF1819,  0xfb710530},
+{15000000, DIF_BPF_COEFF2021,  0x023cf7ee},
+{15000000, DIF_BPF_COEFF2223,  0x02c307ef},
+{15000000, DIF_BPF_COEFF2425,  0xf75efc70},
+{15000000, DIF_BPF_COEFF2627,  0x0c5cfbef},
+{15000000, DIF_BPF_COEFF2829,  0xf4c10bce},
+{15000000, DIF_BPF_COEFF3031,  0x04b3f03f},
+{15000000, DIF_BPF_COEFF3233,  0x05030d69},
+{15000000, DIF_BPF_COEFF3435,  0xf261fabf},
+{15000000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 150_quant.dat*/
+
+
+/*case 15100000:*/
+/* BEGIN - DIF BPF register values from 151_quant.dat*/
+{15100000, DIF_BPF_COEFF01,    0x0000fffd},
+{15100000, DIF_BPF_COEFF23,    0xffff0012},
+{15100000, DIF_BPF_COEFF45,    0xffefffdc},
+{15100000, DIF_BPF_COEFF67,    0x00510006},
+{15100000, DIF_BPF_COEFF89,    0xff540089},
+{15100000, DIF_BPF_COEFF1011,  0x00befe7c},
+{15100000, DIF_BPF_COEFF1213,  0x00060253},
+{15100000, DIF_BPF_COEFF1415,  0xfe27fe0d},
+{15100000, DIF_BPF_COEFF1617,  0x0413ffa2},
+{15100000, DIF_BPF_COEFF1819,  0xfad10446},
+{15100000, DIF_BPF_COEFF2021,  0x0390f812},
+{15100000, DIF_BPF_COEFF2223,  0x013b08c3},
+{15100000, DIF_BPF_COEFF2425,  0xf868faf6},
+{15100000, DIF_BPF_COEFF2627,  0x0c43fd5f},
+{15100000, DIF_BPF_COEFF2829,  0xf3fd0b02},
+{15100000, DIF_BPF_COEFF3031,  0x05c7f046},
+{15100000, DIF_BPF_COEFF3233,  0x043b0dc4},
+{15100000, DIF_BPF_COEFF3435,  0xf2a1fa8b},
+{15100000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 151_quant.dat*/
+
+
+/*case 15200000:*/
+/* BEGIN - DIF BPF register values from 152_quant.dat*/
+{15200000, DIF_BPF_COEFF01,    0x0001fffe},
+{15200000, DIF_BPF_COEFF23,    0xfffc0012},
+{15200000, DIF_BPF_COEFF45,    0xfffbffce},
+{15200000, DIF_BPF_COEFF67,    0x003f0033},
+{15200000, DIF_BPF_COEFF89,    0xff4e003f},
+{15200000, DIF_BPF_COEFF1011,  0x0106feb6},
+{15200000, DIF_BPF_COEFF1213,  0xff6e0276},
+{15200000, DIF_BPF_COEFF1415,  0xfeddfd56},
+{15200000, DIF_BPF_COEFF1617,  0x03b000cc},
+{15200000, DIF_BPF_COEFF1819,  0xfa740329},
+{15200000, DIF_BPF_COEFF2021,  0x04bff87f},
+{15200000, DIF_BPF_COEFF2223,  0xffaa095d},
+{15200000, DIF_BPF_COEFF2425,  0xf99ef995},
+{15200000, DIF_BPF_COEFF2627,  0x0bf9fed8},
+{15200000, DIF_BPF_COEFF2829,  0xf3590a1f},
+{15200000, DIF_BPF_COEFF3031,  0x06d2f05e},
+{15200000, DIF_BPF_COEFF3233,  0x03700e1b},
+{15200000, DIF_BPF_COEFF3435,  0xf2e4fa58},
+{15200000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 152_quant.dat*/
+
+
+/*case 115300000:*/
+/* BEGIN - DIF BPF register values from 153_quant.dat*/
+{15300000, DIF_BPF_COEFF01,    0x0001ffff},
+{15300000, DIF_BPF_COEFF23,    0xfff9000f},
+{15300000, DIF_BPF_COEFF45,    0x0009ffc8},
+{15300000, DIF_BPF_COEFF67,    0x00250059},
+{15300000, DIF_BPF_COEFF89,    0xff5effee},
+{15300000, DIF_BPF_COEFF1011,  0x0132ff10},
+{15300000, DIF_BPF_COEFF1213,  0xfee30265},
+{15300000, DIF_BPF_COEFF1415,  0xffaafccf},
+{15300000, DIF_BPF_COEFF1617,  0x031101eb},
+{15300000, DIF_BPF_COEFF1819,  0xfa6001e8},
+{15300000, DIF_BPF_COEFF2021,  0x05bdf92f},
+{15300000, DIF_BPF_COEFF2223,  0xfe1b09b6},
+{15300000, DIF_BPF_COEFF2425,  0xfafaf852},
+{15300000, DIF_BPF_COEFF2627,  0x0b7e0055},
+{15300000, DIF_BPF_COEFF2829,  0xf2d50929},
+{15300000, DIF_BPF_COEFF3031,  0x07d3f086},
+{15300000, DIF_BPF_COEFF3233,  0x02a30e6c},
+{15300000, DIF_BPF_COEFF3435,  0xf329fa24},
+{15300000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 153_quant.dat*/
+
+
+/*case 115400000:*/
+/* BEGIN - DIF BPF register values from 154_quant.dat*/
+{15400000, DIF_BPF_COEFF01,    0x00010001},
+{15400000, DIF_BPF_COEFF23,    0xfff80009},
+{15400000, DIF_BPF_COEFF45,    0x0015ffca},
+{15400000, DIF_BPF_COEFF67,    0x00050074},
+{15400000, DIF_BPF_COEFF89,    0xff81ff9f},
+{15400000, DIF_BPF_COEFF1011,  0x013dff82},
+{15400000, DIF_BPF_COEFF1213,  0xfe710221},
+{15400000, DIF_BPF_COEFF1415,  0x007cfc80},
+{15400000, DIF_BPF_COEFF1617,  0x024102ed},
+{15400000, DIF_BPF_COEFF1819,  0xfa940090},
+{15400000, DIF_BPF_COEFF2021,  0x0680fa1e},
+{15400000, DIF_BPF_COEFF2223,  0xfc9b09cd},
+{15400000, DIF_BPF_COEFF2425,  0xfc73f736},
+{15400000, DIF_BPF_COEFF2627,  0x0ad501d0},
+{15400000, DIF_BPF_COEFF2829,  0xf2740820},
+{15400000, DIF_BPF_COEFF3031,  0x08c9f0bd},
+{15400000, DIF_BPF_COEFF3233,  0x01d40eb9},
+{15400000, DIF_BPF_COEFF3435,  0xf371f9f1},
+{15400000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 154_quant.dat*/
+
+
+/*case 115500000:*/
+/* BEGIN - DIF BPF register values from 155_quant.dat*/
+{15500000, DIF_BPF_COEFF01,    0x00000002},
+{15500000, DIF_BPF_COEFF23,    0xfff80002},
+{15500000, DIF_BPF_COEFF45,    0x001effd5},
+{15500000, DIF_BPF_COEFF67,    0xffe5007f},
+{15500000, DIF_BPF_COEFF89,    0xffb4ff5b},
+{15500000, DIF_BPF_COEFF1011,  0x01280000},
+{15500000, DIF_BPF_COEFF1213,  0xfe2401b0},
+{15500000, DIF_BPF_COEFF1415,  0x0146fc70},
+{15500000, DIF_BPF_COEFF1617,  0x014d03c6},
+{15500000, DIF_BPF_COEFF1819,  0xfb10ff32},
+{15500000, DIF_BPF_COEFF2021,  0x0701fb41},
+{15500000, DIF_BPF_COEFF2223,  0xfb3709a1},
+{15500000, DIF_BPF_COEFF2425,  0xfe00f644},
+{15500000, DIF_BPF_COEFF2627,  0x0a000345},
+{15500000, DIF_BPF_COEFF2829,  0xf2350708},
+{15500000, DIF_BPF_COEFF3031,  0x09b2f104},
+{15500000, DIF_BPF_COEFF3233,  0x01050eff},
+{15500000, DIF_BPF_COEFF3435,  0xf3baf9be},
+{15500000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 155_quant.dat*/
+
+
+/*case 115600000:*/
+/* BEGIN - DIF BPF register values from 156_quant.dat*/
+{15600000, DIF_BPF_COEFF01,    0x00000003},
+{15600000, DIF_BPF_COEFF23,    0xfff9fffb},
+{15600000, DIF_BPF_COEFF45,    0x0022ffe6},
+{15600000, DIF_BPF_COEFF67,    0xffc9007a},
+{15600000, DIF_BPF_COEFF89,    0xfff0ff29},
+{15600000, DIF_BPF_COEFF1011,  0x00f2007e},
+{15600000, DIF_BPF_COEFF1213,  0xfe01011b},
+{15600000, DIF_BPF_COEFF1415,  0x01f6fc9e},
+{15600000, DIF_BPF_COEFF1617,  0x00440467},
+{15600000, DIF_BPF_COEFF1819,  0xfbccfdde},
+{15600000, DIF_BPF_COEFF2021,  0x0738fc90},
+{15600000, DIF_BPF_COEFF2223,  0xf9f70934},
+{15600000, DIF_BPF_COEFF2425,  0xff99f582},
+{15600000, DIF_BPF_COEFF2627,  0x090204b0},
+{15600000, DIF_BPF_COEFF2829,  0xf21a05e1},
+{15600000, DIF_BPF_COEFF3031,  0x0a8df15a},
+{15600000, DIF_BPF_COEFF3233,  0x00340f41},
+{15600000, DIF_BPF_COEFF3435,  0xf405f98b},
+{15600000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 156_quant.dat*/
+
+
+/*case 115700000:*/
+/* BEGIN - DIF BPF register values from 157_quant.dat*/
+{15700000, DIF_BPF_COEFF01,    0x00000003},
+{15700000, DIF_BPF_COEFF23,    0xfffcfff4},
+{15700000, DIF_BPF_COEFF45,    0x0020fffa},
+{15700000, DIF_BPF_COEFF67,    0xffb40064},
+{15700000, DIF_BPF_COEFF89,    0x002fff11},
+{15700000, DIF_BPF_COEFF1011,  0x00a400f0},
+{15700000, DIF_BPF_COEFF1213,  0xfe0d006e},
+{15700000, DIF_BPF_COEFF1415,  0x0281fd09},
+{15700000, DIF_BPF_COEFF1617,  0xff3604c9},
+{15700000, DIF_BPF_COEFF1819,  0xfcbffca2},
+{15700000, DIF_BPF_COEFF2021,  0x0726fdfe},
+{15700000, DIF_BPF_COEFF2223,  0xf8e80888},
+{15700000, DIF_BPF_COEFF2425,  0x0134f4f3},
+{15700000, DIF_BPF_COEFF2627,  0x07e1060c},
+{15700000, DIF_BPF_COEFF2829,  0xf22304af},
+{15700000, DIF_BPF_COEFF3031,  0x0b59f1be},
+{15700000, DIF_BPF_COEFF3233,  0xff640f7d},
+{15700000, DIF_BPF_COEFF3435,  0xf452f959},
+{15700000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 157_quant.dat*/
+
+
+/*case 115800000:*/
+/* BEGIN - DIF BPF register values from 158_quant.dat*/
+{15800000, DIF_BPF_COEFF01,    0x00000003},
+{15800000, DIF_BPF_COEFF23,    0x0000fff0},
+{15800000, DIF_BPF_COEFF45,    0x001a0010},
+{15800000, DIF_BPF_COEFF67,    0xffaa0041},
+{15800000, DIF_BPF_COEFF89,    0x0067ff13},
+{15800000, DIF_BPF_COEFF1011,  0x0043014a},
+{15800000, DIF_BPF_COEFF1213,  0xfe46ffb9},
+{15800000, DIF_BPF_COEFF1415,  0x02dbfda8},
+{15800000, DIF_BPF_COEFF1617,  0xfe3504e5},
+{15800000, DIF_BPF_COEFF1819,  0xfddcfb8d},
+{15800000, DIF_BPF_COEFF2021,  0x06c9ff7e},
+{15800000, DIF_BPF_COEFF2223,  0xf81107a2},
+{15800000, DIF_BPF_COEFF2425,  0x02c9f49a},
+{15800000, DIF_BPF_COEFF2627,  0x069f0753},
+{15800000, DIF_BPF_COEFF2829,  0xf2500373},
+{15800000, DIF_BPF_COEFF3031,  0x0c14f231},
+{15800000, DIF_BPF_COEFF3233,  0xfe930fb3},
+{15800000, DIF_BPF_COEFF3435,  0xf4a1f927},
+{15800000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 158_quant.dat*/
+
+
+/*case 115900000:*/
+/* BEGIN - DIF BPF register values from 159_quant.dat*/
+{15900000, DIF_BPF_COEFF01,    0xffff0002},
+{15900000, DIF_BPF_COEFF23,    0x0003ffee},
+{15900000, DIF_BPF_COEFF45,    0x000f0023},
+{15900000, DIF_BPF_COEFF67,    0xffac0016},
+{15900000, DIF_BPF_COEFF89,    0x0093ff31},
+{15900000, DIF_BPF_COEFF1011,  0xffdc0184},
+{15900000, DIF_BPF_COEFF1213,  0xfea6ff09},
+{15900000, DIF_BPF_COEFF1415,  0x02fdfe70},
+{15900000, DIF_BPF_COEFF1617,  0xfd5104ba},
+{15900000, DIF_BPF_COEFF1819,  0xff15faac},
+{15900000, DIF_BPF_COEFF2021,  0x06270103},
+{15900000, DIF_BPF_COEFF2223,  0xf7780688},
+{15900000, DIF_BPF_COEFF2425,  0x044df479},
+{15900000, DIF_BPF_COEFF2627,  0x05430883},
+{15900000, DIF_BPF_COEFF2829,  0xf2a00231},
+{15900000, DIF_BPF_COEFF3031,  0x0cbef2b2},
+{15900000, DIF_BPF_COEFF3233,  0xfdc40fe3},
+{15900000, DIF_BPF_COEFF3435,  0xf4f2f8f5},
+{15900000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 159_quant.dat*/
+
+
+/*case 116000000:*/
+/* BEGIN - DIF BPF register values from 160_quant.dat*/
+{16000000, DIF_BPF_COEFF01,    0xffff0001},
+{16000000, DIF_BPF_COEFF23,    0x0006ffef},
+{16000000, DIF_BPF_COEFF45,    0x00020031},
+{16000000, DIF_BPF_COEFF67,    0xffbaffe8},
+{16000000, DIF_BPF_COEFF89,    0x00adff66},
+{16000000, DIF_BPF_COEFF1011,  0xff790198},
+{16000000, DIF_BPF_COEFF1213,  0xff26fe6e},
+{16000000, DIF_BPF_COEFF1415,  0x02e5ff55},
+{16000000, DIF_BPF_COEFF1617,  0xfc99044a},
+{16000000, DIF_BPF_COEFF1819,  0x005bfa09},
+{16000000, DIF_BPF_COEFF2021,  0x0545027f},
+{16000000, DIF_BPF_COEFF2223,  0xf7230541},
+{16000000, DIF_BPF_COEFF2425,  0x05b8f490},
+{16000000, DIF_BPF_COEFF2627,  0x03d20997},
+{16000000, DIF_BPF_COEFF2829,  0xf31300eb},
+{16000000, DIF_BPF_COEFF3031,  0x0d55f341},
+{16000000, DIF_BPF_COEFF3233,  0xfcf6100e},
+{16000000, DIF_BPF_COEFF3435,  0xf544f8c3},
+{16000000, DIF_BPF_COEFF36,    0x110d0000},
+/* END - DIF BPF register values from 160_quant.dat*/
+};
+
+#endif
diff --git a/drivers/media/usb/cx231xx/cx231xx-dvb.c b/drivers/media/usb/cx231xx/cx231xx-dvb.c
new file mode 100644
index 0000000..89357cb
--- /dev/null
+++ b/drivers/media/usb/cx231xx/cx231xx-dvb.c
@@ -0,0 +1,1186 @@
+/*
+ DVB device driver for cx231xx
+
+ Copyright (C) 2008 <srinivasa.deevi at conexant dot com>
+		Based on em28xx driver
+
+ 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.
+ */
+
+#include "cx231xx.h"
+#include <linux/kernel.h>
+#include <linux/slab.h>
+
+#include <media/dvbdev.h>
+#include <media/dmxdev.h>
+#include <media/dvb_demux.h>
+#include <media/dvb_net.h>
+#include <media/dvb_frontend.h>
+#include <media/v4l2-common.h>
+#include <media/tuner.h>
+
+#include "xc5000.h"
+#include "s5h1432.h"
+#include "tda18271.h"
+#include "s5h1411.h"
+#include "lgdt3305.h"
+#include "si2165.h"
+#include "si2168.h"
+#include "mb86a20s.h"
+#include "si2157.h"
+#include "lgdt3306a.h"
+#include "r820t.h"
+#include "mn88473.h"
+
+MODULE_DESCRIPTION("driver for cx231xx based DVB cards");
+MODULE_AUTHOR("Srinivasa Deevi <srinivasa.deevi@conexant.com>");
+MODULE_LICENSE("GPL");
+
+static unsigned int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "enable debug messages [dvb]");
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+#define CX231XX_DVB_NUM_BUFS 5
+#define CX231XX_DVB_MAX_PACKETSIZE 564
+#define CX231XX_DVB_MAX_PACKETS 64
+#define CX231XX_DVB_MAX_FRONTENDS 2
+
+struct cx231xx_dvb {
+	struct dvb_frontend *frontend[CX231XX_DVB_MAX_FRONTENDS];
+
+	/* feed count management */
+	struct mutex lock;
+	int nfeeds;
+
+	/* general boilerplate stuff */
+	struct dvb_adapter adapter;
+	struct dvb_demux demux;
+	struct dmxdev dmxdev;
+	struct dmx_frontend fe_hw;
+	struct dmx_frontend fe_mem;
+	struct dvb_net net;
+	struct i2c_client *i2c_client_demod[2];
+	struct i2c_client *i2c_client_tuner;
+};
+
+static struct s5h1432_config dvico_s5h1432_config = {
+	.output_mode   = S5H1432_SERIAL_OUTPUT,
+	.gpio          = S5H1432_GPIO_ON,
+	.qam_if        = S5H1432_IF_4000,
+	.vsb_if        = S5H1432_IF_4000,
+	.inversion     = S5H1432_INVERSION_OFF,
+	.status_mode   = S5H1432_DEMODLOCKING,
+	.mpeg_timing   = S5H1432_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK,
+};
+
+static struct tda18271_std_map cnxt_rde253s_tda18271_std_map = {
+	.dvbt_6   = { .if_freq = 4000, .agc_mode = 3, .std = 4,
+		      .if_lvl = 1, .rfagc_top = 0x37, },
+	.dvbt_7   = { .if_freq = 4000, .agc_mode = 3, .std = 5,
+		      .if_lvl = 1, .rfagc_top = 0x37, },
+	.dvbt_8   = { .if_freq = 4000, .agc_mode = 3, .std = 6,
+		      .if_lvl = 1, .rfagc_top = 0x37, },
+};
+
+static struct tda18271_std_map mb86a20s_tda18271_config = {
+	.dvbt_6   = { .if_freq = 4000, .agc_mode = 3, .std = 4,
+		      .if_lvl = 0, .rfagc_top = 0x37, },
+};
+
+static struct tda18271_config cnxt_rde253s_tunerconfig = {
+	.std_map = &cnxt_rde253s_tda18271_std_map,
+	.gate    = TDA18271_GATE_ANALOG,
+};
+
+static struct s5h1411_config tda18271_s5h1411_config = {
+	.output_mode   = S5H1411_SERIAL_OUTPUT,
+	.gpio          = S5H1411_GPIO_OFF,
+	.vsb_if        = S5H1411_IF_3250,
+	.qam_if        = S5H1411_IF_4000,
+	.inversion     = S5H1411_INVERSION_ON,
+	.status_mode   = S5H1411_DEMODLOCKING,
+	.mpeg_timing   = S5H1411_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK,
+};
+static struct s5h1411_config xc5000_s5h1411_config = {
+	.output_mode   = S5H1411_SERIAL_OUTPUT,
+	.gpio          = S5H1411_GPIO_OFF,
+	.vsb_if        = S5H1411_IF_3250,
+	.qam_if        = S5H1411_IF_3250,
+	.inversion     = S5H1411_INVERSION_OFF,
+	.status_mode   = S5H1411_DEMODLOCKING,
+	.mpeg_timing   = S5H1411_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK,
+};
+
+static struct lgdt3305_config hcw_lgdt3305_config = {
+	.i2c_addr           = 0x0e,
+	.mpeg_mode          = LGDT3305_MPEG_SERIAL,
+	.tpclk_edge         = LGDT3305_TPCLK_FALLING_EDGE,
+	.tpvalid_polarity   = LGDT3305_TP_VALID_HIGH,
+	.deny_i2c_rptr      = 1,
+	.spectral_inversion = 1,
+	.qam_if_khz         = 4000,
+	.vsb_if_khz         = 3250,
+};
+
+static struct tda18271_std_map hauppauge_tda18271_std_map = {
+	.atsc_6   = { .if_freq = 3250, .agc_mode = 3, .std = 4,
+		      .if_lvl = 1, .rfagc_top = 0x58, },
+	.qam_6    = { .if_freq = 4000, .agc_mode = 3, .std = 5,
+		      .if_lvl = 1, .rfagc_top = 0x58, },
+};
+
+static struct tda18271_config hcw_tda18271_config = {
+	.std_map = &hauppauge_tda18271_std_map,
+	.gate    = TDA18271_GATE_DIGITAL,
+};
+
+static const struct mb86a20s_config pv_mb86a20s_config = {
+	.demod_address = 0x10,
+	.is_serial = true,
+};
+
+static struct tda18271_config pv_tda18271_config = {
+	.std_map = &mb86a20s_tda18271_config,
+	.gate    = TDA18271_GATE_DIGITAL,
+	.small_i2c = TDA18271_03_BYTE_CHUNK_INIT,
+};
+
+static struct lgdt3306a_config hauppauge_955q_lgdt3306a_config = {
+	.qam_if_khz         = 4000,
+	.vsb_if_khz         = 3250,
+	.spectral_inversion = 1,
+	.mpeg_mode          = LGDT3306A_MPEG_SERIAL,
+	.tpclk_edge         = LGDT3306A_TPCLK_RISING_EDGE,
+	.tpvalid_polarity   = LGDT3306A_TP_VALID_HIGH,
+	.xtalMHz            = 25,
+};
+
+static struct r820t_config astrometa_t2hybrid_r820t_config = {
+	.i2c_addr		= 0x3a, /* 0x74 >> 1 */
+	.xtal			= 16000000,
+	.rafael_chip		= CHIP_R828D,
+	.max_i2c_msg_len	= 2,
+};
+
+static inline void print_err_status(struct cx231xx *dev, int packet, int status)
+{
+	char *errmsg = "Unknown";
+
+	switch (status) {
+	case -ENOENT:
+		errmsg = "unlinked synchronously";
+		break;
+	case -ECONNRESET:
+		errmsg = "unlinked asynchronously";
+		break;
+	case -ENOSR:
+		errmsg = "Buffer error (overrun)";
+		break;
+	case -EPIPE:
+		errmsg = "Stalled (device not responding)";
+		break;
+	case -EOVERFLOW:
+		errmsg = "Babble (bad cable?)";
+		break;
+	case -EPROTO:
+		errmsg = "Bit-stuff error (bad cable?)";
+		break;
+	case -EILSEQ:
+		errmsg = "CRC/Timeout (could be anything)";
+		break;
+	case -ETIME:
+		errmsg = "Device does not respond";
+		break;
+	}
+	if (packet < 0) {
+		dev_dbg(dev->dev,
+			"URB status %d [%s].\n", status, errmsg);
+	} else {
+		dev_dbg(dev->dev,
+			"URB packet %d, status %d [%s].\n",
+			packet, status, errmsg);
+	}
+}
+
+static inline int dvb_isoc_copy(struct cx231xx *dev, struct urb *urb)
+{
+	int i;
+
+	if (!dev)
+		return 0;
+
+	if (dev->state & DEV_DISCONNECTED)
+		return 0;
+
+	if (urb->status < 0) {
+		print_err_status(dev, -1, urb->status);
+		if (urb->status == -ENOENT)
+			return 0;
+	}
+
+	for (i = 0; i < urb->number_of_packets; i++) {
+		int status = urb->iso_frame_desc[i].status;
+
+		if (status < 0) {
+			print_err_status(dev, i, status);
+			if (urb->iso_frame_desc[i].status != -EPROTO)
+				continue;
+		}
+
+		dvb_dmx_swfilter(&dev->dvb->demux,
+				 urb->transfer_buffer +
+				urb->iso_frame_desc[i].offset,
+				urb->iso_frame_desc[i].actual_length);
+	}
+
+	return 0;
+}
+
+static inline int dvb_bulk_copy(struct cx231xx *dev, struct urb *urb)
+{
+	if (!dev)
+		return 0;
+
+	if (dev->state & DEV_DISCONNECTED)
+		return 0;
+
+	if (urb->status < 0) {
+		print_err_status(dev, -1, urb->status);
+		if (urb->status == -ENOENT)
+			return 0;
+	}
+
+	/* Feed the transport payload into the kernel demux */
+	dvb_dmx_swfilter(&dev->dvb->demux,
+		urb->transfer_buffer, urb->actual_length);
+
+	return 0;
+}
+
+static int start_streaming(struct cx231xx_dvb *dvb)
+{
+	int rc;
+	struct cx231xx *dev = dvb->adapter.priv;
+
+	if (dev->USE_ISO) {
+		dev_dbg(dev->dev, "DVB transfer mode is ISO.\n");
+		cx231xx_set_alt_setting(dev, INDEX_TS1, 5);
+		rc = cx231xx_set_mode(dev, CX231XX_DIGITAL_MODE);
+		if (rc < 0)
+			return rc;
+		dev->mode_tv = 1;
+		return cx231xx_init_isoc(dev, CX231XX_DVB_MAX_PACKETS,
+					CX231XX_DVB_NUM_BUFS,
+					dev->ts1_mode.max_pkt_size,
+					dvb_isoc_copy);
+	} else {
+		dev_dbg(dev->dev, "DVB transfer mode is BULK.\n");
+		cx231xx_set_alt_setting(dev, INDEX_TS1, 0);
+		rc = cx231xx_set_mode(dev, CX231XX_DIGITAL_MODE);
+		if (rc < 0)
+			return rc;
+		dev->mode_tv = 1;
+		return cx231xx_init_bulk(dev, CX231XX_DVB_MAX_PACKETS,
+					CX231XX_DVB_NUM_BUFS,
+					dev->ts1_mode.max_pkt_size,
+					dvb_bulk_copy);
+	}
+
+}
+
+static int stop_streaming(struct cx231xx_dvb *dvb)
+{
+	struct cx231xx *dev = dvb->adapter.priv;
+
+	if (dev->USE_ISO)
+		cx231xx_uninit_isoc(dev);
+	else
+		cx231xx_uninit_bulk(dev);
+
+	cx231xx_set_mode(dev, CX231XX_SUSPEND);
+
+	return 0;
+}
+
+static int start_feed(struct dvb_demux_feed *feed)
+{
+	struct dvb_demux *demux = feed->demux;
+	struct cx231xx_dvb *dvb = demux->priv;
+	int rc, ret;
+
+	if (!demux->dmx.frontend)
+		return -EINVAL;
+
+	mutex_lock(&dvb->lock);
+	dvb->nfeeds++;
+	rc = dvb->nfeeds;
+
+	if (dvb->nfeeds == 1) {
+		ret = start_streaming(dvb);
+		if (ret < 0)
+			rc = ret;
+	}
+
+	mutex_unlock(&dvb->lock);
+	return rc;
+}
+
+static int stop_feed(struct dvb_demux_feed *feed)
+{
+	struct dvb_demux *demux = feed->demux;
+	struct cx231xx_dvb *dvb = demux->priv;
+	int err = 0;
+
+	mutex_lock(&dvb->lock);
+	dvb->nfeeds--;
+
+	if (0 == dvb->nfeeds)
+		err = stop_streaming(dvb);
+
+	mutex_unlock(&dvb->lock);
+	return err;
+}
+
+/* ------------------------------------------------------------------ */
+static int cx231xx_dvb_bus_ctrl(struct dvb_frontend *fe, int acquire)
+{
+	struct cx231xx *dev = fe->dvb->priv;
+
+	if (acquire)
+		return cx231xx_set_mode(dev, CX231XX_DIGITAL_MODE);
+	else
+		return cx231xx_set_mode(dev, CX231XX_SUSPEND);
+}
+
+/* ------------------------------------------------------------------ */
+
+static struct xc5000_config cnxt_rde250_tunerconfig = {
+	.i2c_address = 0x61,
+	.if_khz = 4000,
+};
+static struct xc5000_config cnxt_rdu250_tunerconfig = {
+	.i2c_address = 0x61,
+	.if_khz = 3250,
+};
+
+/* ------------------------------------------------------------------ */
+#if 0
+static int attach_xc5000(u8 addr, struct cx231xx *dev)
+{
+
+	struct dvb_frontend *fe;
+	struct xc5000_config cfg;
+
+	memset(&cfg, 0, sizeof(cfg));
+	cfg.i2c_adap = cx231xx_get_i2c_adap(dev, dev->board.tuner_i2c_master);
+	cfg.i2c_addr = addr;
+
+	if (!dev->dvb->frontend[0]) {
+		dev_err(dev->dev, "%s/2: dvb frontend not attached. Can't attach xc5000\n",
+			dev->name);
+		return -EINVAL;
+	}
+
+	fe = dvb_attach(xc5000_attach, dev->dvb->frontend[0], &cfg);
+	if (!fe) {
+		dev_err(dev->dev, "%s/2: xc5000 attach failed\n", dev->name);
+		dvb_frontend_detach(dev->dvb->frontend[0]);
+		dev->dvb->frontend[0] = NULL;
+		return -EINVAL;
+	}
+
+	dev_info(dev->dev, "%s/2: xc5000 attached\n", dev->name);
+
+	return 0;
+}
+#endif
+
+int cx231xx_set_analog_freq(struct cx231xx *dev, u32 freq)
+{
+	if (dev->dvb && dev->dvb->frontend[0]) {
+
+		struct dvb_tuner_ops *dops = &dev->dvb->frontend[0]->ops.tuner_ops;
+
+		if (dops->set_analog_params != NULL) {
+			struct analog_parameters params;
+
+			params.frequency = freq;
+			params.std = dev->norm;
+			params.mode = 0;	/* 0- Air; 1 - cable */
+			/*params.audmode = ;       */
+
+			/* Set the analog parameters to set the frequency */
+			dops->set_analog_params(dev->dvb->frontend[0], &params);
+		}
+
+	}
+
+	return 0;
+}
+
+int cx231xx_reset_analog_tuner(struct cx231xx *dev)
+{
+	int status = 0;
+
+	if (dev->dvb && dev->dvb->frontend[0]) {
+
+		struct dvb_tuner_ops *dops = &dev->dvb->frontend[0]->ops.tuner_ops;
+
+		if (dops->init != NULL && !dev->xc_fw_load_done) {
+
+			dev_dbg(dev->dev,
+				"Reloading firmware for XC5000\n");
+			status = dops->init(dev->dvb->frontend[0]);
+			if (status == 0) {
+				dev->xc_fw_load_done = 1;
+				dev_dbg(dev->dev,
+					"XC5000 firmware download completed\n");
+			} else {
+				dev->xc_fw_load_done = 0;
+				dev_dbg(dev->dev,
+					"XC5000 firmware download failed !!!\n");
+			}
+		}
+
+	}
+
+	return status;
+}
+
+/* ------------------------------------------------------------------ */
+
+static int register_dvb(struct cx231xx_dvb *dvb,
+			struct module *module,
+			struct cx231xx *dev, struct device *device)
+{
+	int result;
+
+	mutex_init(&dvb->lock);
+
+
+	/* register adapter */
+	result = dvb_register_adapter(&dvb->adapter, dev->name, module, device,
+				      adapter_nr);
+	if (result < 0) {
+		dev_warn(dev->dev,
+		       "%s: dvb_register_adapter failed (errno = %d)\n",
+		       dev->name, result);
+		goto fail_adapter;
+	}
+	dvb_register_media_controller(&dvb->adapter, dev->media_dev);
+
+	/* Ensure all frontends negotiate bus access */
+	dvb->frontend[0]->ops.ts_bus_ctrl = cx231xx_dvb_bus_ctrl;
+	if (dvb->frontend[1])
+		dvb->frontend[1]->ops.ts_bus_ctrl = cx231xx_dvb_bus_ctrl;
+
+	dvb->adapter.priv = dev;
+
+	/* register frontend */
+	result = dvb_register_frontend(&dvb->adapter, dvb->frontend[0]);
+	if (result < 0) {
+		dev_warn(dev->dev,
+		       "%s: dvb_register_frontend failed (errno = %d)\n",
+		       dev->name, result);
+		goto fail_frontend0;
+	}
+
+	if (dvb->frontend[1]) {
+		result = dvb_register_frontend(&dvb->adapter, dvb->frontend[1]);
+		if (result < 0) {
+			dev_warn(dev->dev,
+				 "%s: 2nd dvb_register_frontend failed (errno = %d)\n",
+				dev->name, result);
+			goto fail_frontend1;
+		}
+
+		/* MFE lock */
+		dvb->adapter.mfe_shared = 1;
+	}
+
+	/* register demux stuff */
+	dvb->demux.dmx.capabilities =
+	    DMX_TS_FILTERING | DMX_SECTION_FILTERING |
+	    DMX_MEMORY_BASED_FILTERING;
+	dvb->demux.priv = dvb;
+	dvb->demux.filternum = 256;
+	dvb->demux.feednum = 256;
+	dvb->demux.start_feed = start_feed;
+	dvb->demux.stop_feed = stop_feed;
+
+	result = dvb_dmx_init(&dvb->demux);
+	if (result < 0) {
+		dev_warn(dev->dev,
+			 "%s: dvb_dmx_init failed (errno = %d)\n",
+		       dev->name, result);
+		goto fail_dmx;
+	}
+
+	dvb->dmxdev.filternum = 256;
+	dvb->dmxdev.demux = &dvb->demux.dmx;
+	dvb->dmxdev.capabilities = 0;
+	result = dvb_dmxdev_init(&dvb->dmxdev, &dvb->adapter);
+	if (result < 0) {
+		dev_warn(dev->dev,
+			 "%s: dvb_dmxdev_init failed (errno = %d)\n",
+			 dev->name, result);
+		goto fail_dmxdev;
+	}
+
+	dvb->fe_hw.source = DMX_FRONTEND_0;
+	result = dvb->demux.dmx.add_frontend(&dvb->demux.dmx, &dvb->fe_hw);
+	if (result < 0) {
+		dev_warn(dev->dev,
+		       "%s: add_frontend failed (DMX_FRONTEND_0, errno = %d)\n",
+		       dev->name, result);
+		goto fail_fe_hw;
+	}
+
+	dvb->fe_mem.source = DMX_MEMORY_FE;
+	result = dvb->demux.dmx.add_frontend(&dvb->demux.dmx, &dvb->fe_mem);
+	if (result < 0) {
+		dev_warn(dev->dev,
+			 "%s: add_frontend failed (DMX_MEMORY_FE, errno = %d)\n",
+			 dev->name, result);
+		goto fail_fe_mem;
+	}
+
+	result = dvb->demux.dmx.connect_frontend(&dvb->demux.dmx, &dvb->fe_hw);
+	if (result < 0) {
+		dev_warn(dev->dev,
+			 "%s: connect_frontend failed (errno = %d)\n",
+			 dev->name, result);
+		goto fail_fe_conn;
+	}
+
+	/* register network adapter */
+	dvb_net_init(&dvb->adapter, &dvb->net, &dvb->demux.dmx);
+	result = dvb_create_media_graph(&dvb->adapter,
+					dev->tuner_type == TUNER_ABSENT);
+	if (result < 0)
+		goto fail_create_graph;
+
+	return 0;
+
+fail_create_graph:
+	dvb_net_release(&dvb->net);
+fail_fe_conn:
+	dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_mem);
+fail_fe_mem:
+	dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_hw);
+fail_fe_hw:
+	dvb_dmxdev_release(&dvb->dmxdev);
+fail_dmxdev:
+	dvb_dmx_release(&dvb->demux);
+fail_dmx:
+	if (dvb->frontend[1])
+		dvb_unregister_frontend(dvb->frontend[1]);
+	dvb_unregister_frontend(dvb->frontend[0]);
+fail_frontend1:
+	if (dvb->frontend[1])
+		dvb_frontend_detach(dvb->frontend[1]);
+fail_frontend0:
+	dvb_frontend_detach(dvb->frontend[0]);
+	dvb_unregister_adapter(&dvb->adapter);
+fail_adapter:
+	return result;
+}
+
+static void unregister_dvb(struct cx231xx_dvb *dvb)
+{
+	dvb_net_release(&dvb->net);
+	dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_mem);
+	dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_hw);
+	dvb_dmxdev_release(&dvb->dmxdev);
+	dvb_dmx_release(&dvb->demux);
+	if (dvb->frontend[1])
+		dvb_unregister_frontend(dvb->frontend[1]);
+	dvb_unregister_frontend(dvb->frontend[0]);
+	if (dvb->frontend[1])
+		dvb_frontend_detach(dvb->frontend[1]);
+	dvb_frontend_detach(dvb->frontend[0]);
+	dvb_unregister_adapter(&dvb->adapter);
+
+	/* remove I2C tuner */
+	dvb_module_release(dvb->i2c_client_tuner);
+	dvb->i2c_client_tuner = NULL;
+	/* remove I2C demod(s) */
+	dvb_module_release(dvb->i2c_client_demod[1]);
+	dvb->i2c_client_demod[1] = NULL;
+	dvb_module_release(dvb->i2c_client_demod[0]);
+	dvb->i2c_client_demod[0] = NULL;
+}
+
+static int dvb_init(struct cx231xx *dev)
+{
+	int result;
+	struct cx231xx_dvb *dvb;
+	struct i2c_adapter *tuner_i2c;
+	struct i2c_adapter *demod_i2c;
+	struct i2c_client *client;
+	struct i2c_adapter *adapter;
+
+	if (!dev->board.has_dvb) {
+		/* This device does not support the extension */
+		return 0;
+	}
+
+	dvb = kzalloc(sizeof(struct cx231xx_dvb), GFP_KERNEL);
+
+	if (dvb == NULL) {
+		dev_info(dev->dev,
+			 "cx231xx_dvb: memory allocation failed\n");
+		return -ENOMEM;
+	}
+	dev->dvb = dvb;
+	dev->cx231xx_set_analog_freq = cx231xx_set_analog_freq;
+	dev->cx231xx_reset_analog_tuner = cx231xx_reset_analog_tuner;
+
+	tuner_i2c = cx231xx_get_i2c_adap(dev, dev->board.tuner_i2c_master);
+	demod_i2c = cx231xx_get_i2c_adap(dev, dev->board.demod_i2c_master);
+	mutex_lock(&dev->lock);
+	cx231xx_set_mode(dev, CX231XX_DIGITAL_MODE);
+	cx231xx_demod_reset(dev);
+	/* init frontend */
+	switch (dev->model) {
+	case CX231XX_BOARD_CNXT_CARRAERA:
+	case CX231XX_BOARD_CNXT_RDE_250:
+
+		dev->dvb->frontend[0] = dvb_attach(s5h1432_attach,
+					&dvico_s5h1432_config,
+					demod_i2c);
+
+		if (!dev->dvb->frontend[0]) {
+			dev_err(dev->dev,
+				"Failed to attach s5h1432 front end\n");
+			result = -EINVAL;
+			goto out_free;
+		}
+
+		/* define general-purpose callback pointer */
+		dvb->frontend[0]->callback = cx231xx_tuner_callback;
+
+		if (!dvb_attach(xc5000_attach, dev->dvb->frontend[0],
+			       tuner_i2c,
+			       &cnxt_rde250_tunerconfig)) {
+			result = -EINVAL;
+			goto out_free;
+		}
+
+		break;
+	case CX231XX_BOARD_CNXT_SHELBY:
+	case CX231XX_BOARD_CNXT_RDU_250:
+
+		dev->dvb->frontend[0] = dvb_attach(s5h1411_attach,
+					       &xc5000_s5h1411_config,
+					       demod_i2c);
+
+		if (!dev->dvb->frontend[0]) {
+			dev_err(dev->dev,
+				"Failed to attach s5h1411 front end\n");
+			result = -EINVAL;
+			goto out_free;
+		}
+
+		/* define general-purpose callback pointer */
+		dvb->frontend[0]->callback = cx231xx_tuner_callback;
+
+		if (!dvb_attach(xc5000_attach, dev->dvb->frontend[0],
+			       tuner_i2c,
+			       &cnxt_rdu250_tunerconfig)) {
+			result = -EINVAL;
+			goto out_free;
+		}
+		break;
+	case CX231XX_BOARD_CNXT_RDE_253S:
+
+		dev->dvb->frontend[0] = dvb_attach(s5h1432_attach,
+					&dvico_s5h1432_config,
+					demod_i2c);
+
+		if (!dev->dvb->frontend[0]) {
+			dev_err(dev->dev,
+				"Failed to attach s5h1432 front end\n");
+			result = -EINVAL;
+			goto out_free;
+		}
+
+		/* define general-purpose callback pointer */
+		dvb->frontend[0]->callback = cx231xx_tuner_callback;
+
+		if (!dvb_attach(tda18271_attach, dev->dvb->frontend[0],
+			       dev->board.tuner_addr, tuner_i2c,
+			       &cnxt_rde253s_tunerconfig)) {
+			result = -EINVAL;
+			goto out_free;
+		}
+		break;
+	case CX231XX_BOARD_CNXT_RDU_253S:
+	case CX231XX_BOARD_KWORLD_UB445_USB_HYBRID:
+
+		dev->dvb->frontend[0] = dvb_attach(s5h1411_attach,
+					       &tda18271_s5h1411_config,
+					       demod_i2c);
+
+		if (!dev->dvb->frontend[0]) {
+			dev_err(dev->dev,
+				"Failed to attach s5h1411 front end\n");
+			result = -EINVAL;
+			goto out_free;
+		}
+
+		/* define general-purpose callback pointer */
+		dvb->frontend[0]->callback = cx231xx_tuner_callback;
+
+		if (!dvb_attach(tda18271_attach, dev->dvb->frontend[0],
+			       dev->board.tuner_addr, tuner_i2c,
+			       &cnxt_rde253s_tunerconfig)) {
+			result = -EINVAL;
+			goto out_free;
+		}
+		break;
+	case CX231XX_BOARD_HAUPPAUGE_EXETER:
+
+		dev_info(dev->dev,
+			 "%s: looking for tuner / demod on i2c bus: %d\n",
+		       __func__, i2c_adapter_id(tuner_i2c));
+
+		dev->dvb->frontend[0] = dvb_attach(lgdt3305_attach,
+						&hcw_lgdt3305_config,
+						demod_i2c);
+
+		if (!dev->dvb->frontend[0]) {
+			dev_err(dev->dev,
+				"Failed to attach LG3305 front end\n");
+			result = -EINVAL;
+			goto out_free;
+		}
+
+		/* define general-purpose callback pointer */
+		dvb->frontend[0]->callback = cx231xx_tuner_callback;
+
+		dvb_attach(tda18271_attach, dev->dvb->frontend[0],
+			   dev->board.tuner_addr, tuner_i2c,
+			   &hcw_tda18271_config);
+		break;
+
+	case CX231XX_BOARD_HAUPPAUGE_930C_HD_1113xx:
+	{
+		struct si2165_platform_data si2165_pdata = {};
+
+		/* attach demod */
+		si2165_pdata.fe = &dev->dvb->frontend[0];
+		si2165_pdata.chip_mode = SI2165_MODE_PLL_XTAL;
+		si2165_pdata.ref_freq_hz = 16000000;
+
+		/* perform probe/init/attach */
+		client = dvb_module_probe("si2165", NULL, demod_i2c,
+						dev->board.demod_addr,
+						&si2165_pdata);
+		if (!client) {
+			result = -ENODEV;
+			goto out_free;
+		}
+		dvb->i2c_client_demod[0] = client;
+
+		dev->dvb->frontend[0]->ops.i2c_gate_ctrl = NULL;
+
+		/* define general-purpose callback pointer */
+		dvb->frontend[0]->callback = cx231xx_tuner_callback;
+
+		dvb_attach(tda18271_attach, dev->dvb->frontend[0],
+			dev->board.tuner_addr, tuner_i2c,
+			&hcw_tda18271_config);
+
+		dev->cx231xx_reset_analog_tuner = NULL;
+		break;
+	}
+	case CX231XX_BOARD_HAUPPAUGE_930C_HD_1114xx:
+	{
+		struct si2165_platform_data si2165_pdata = {};
+		struct si2157_config si2157_config = {};
+
+		/* attach demod */
+		si2165_pdata.fe = &dev->dvb->frontend[0];
+		si2165_pdata.chip_mode = SI2165_MODE_PLL_EXT;
+		si2165_pdata.ref_freq_hz = 24000000;
+
+		/* perform probe/init/attach */
+		client = dvb_module_probe("si2165", NULL, demod_i2c,
+						dev->board.demod_addr,
+						&si2165_pdata);
+		if (!client) {
+			result = -ENODEV;
+			goto out_free;
+		}
+		dvb->i2c_client_demod[0] = client;
+
+		dev->dvb->frontend[0]->ops.i2c_gate_ctrl = NULL;
+
+		/* define general-purpose callback pointer */
+		dvb->frontend[0]->callback = cx231xx_tuner_callback;
+
+		/* attach tuner */
+		si2157_config.fe = dev->dvb->frontend[0];
+#ifdef CONFIG_MEDIA_CONTROLLER_DVB
+		si2157_config.mdev = dev->media_dev;
+#endif
+		si2157_config.if_port = 1;
+		si2157_config.inversion = true;
+
+		/* perform probe/init/attach */
+		client = dvb_module_probe("si2157", NULL, tuner_i2c,
+						dev->board.tuner_addr,
+						&si2157_config);
+		if (!client) {
+			result = -ENODEV;
+			goto out_free;
+		}
+		dev->cx231xx_reset_analog_tuner = NULL;
+
+		dev->dvb->i2c_client_tuner = client;
+		break;
+	}
+	case CX231XX_BOARD_HAUPPAUGE_955Q:
+	{
+		struct si2157_config si2157_config = {};
+		struct lgdt3306a_config lgdt3306a_config = {};
+
+		lgdt3306a_config = hauppauge_955q_lgdt3306a_config;
+		lgdt3306a_config.fe = &dev->dvb->frontend[0];
+		lgdt3306a_config.i2c_adapter = &adapter;
+
+		/* perform probe/init/attach */
+		client = dvb_module_probe("lgdt3306a", NULL, demod_i2c,
+						dev->board.demod_addr,
+						&lgdt3306a_config);
+		if (!client) {
+			result = -ENODEV;
+			goto out_free;
+		}
+		dvb->i2c_client_demod[0] = client;
+
+		dev->dvb->frontend[0]->ops.i2c_gate_ctrl = NULL;
+
+		/* define general-purpose callback pointer */
+		dvb->frontend[0]->callback = cx231xx_tuner_callback;
+
+		/* attach tuner */
+		si2157_config.fe = dev->dvb->frontend[0];
+#ifdef CONFIG_MEDIA_CONTROLLER_DVB
+		si2157_config.mdev = dev->media_dev;
+#endif
+		si2157_config.if_port = 1;
+		si2157_config.inversion = true;
+
+		/* perform probe/init/attach */
+		client = dvb_module_probe("si2157", NULL, tuner_i2c,
+						dev->board.tuner_addr,
+						&si2157_config);
+		if (!client) {
+			result = -ENODEV;
+			goto out_free;
+		}
+		dev->cx231xx_reset_analog_tuner = NULL;
+
+		dev->dvb->i2c_client_tuner = client;
+		break;
+	}
+	case CX231XX_BOARD_PV_PLAYTV_USB_HYBRID:
+	case CX231XX_BOARD_KWORLD_UB430_USB_HYBRID:
+
+		dev_info(dev->dev,
+			 "%s: looking for demod on i2c bus: %d\n",
+			 __func__, i2c_adapter_id(tuner_i2c));
+
+		dev->dvb->frontend[0] = dvb_attach(mb86a20s_attach,
+						&pv_mb86a20s_config,
+						demod_i2c);
+
+		if (!dev->dvb->frontend[0]) {
+			dev_err(dev->dev,
+				"Failed to attach mb86a20s demod\n");
+			result = -EINVAL;
+			goto out_free;
+		}
+
+		/* define general-purpose callback pointer */
+		dvb->frontend[0]->callback = cx231xx_tuner_callback;
+
+		dvb_attach(tda18271_attach, dev->dvb->frontend[0],
+			   dev->board.tuner_addr, tuner_i2c,
+			   &pv_tda18271_config);
+		break;
+
+	case CX231XX_BOARD_EVROMEDIA_FULL_HYBRID_FULLHD:
+	{
+		struct si2157_config si2157_config = {};
+		struct si2168_config si2168_config = {};
+
+		/* attach demodulator chip */
+		si2168_config.ts_mode = SI2168_TS_SERIAL; /* from *.inf file */
+		si2168_config.fe = &dev->dvb->frontend[0];
+		si2168_config.i2c_adapter = &adapter;
+		si2168_config.ts_clock_inv = true;
+
+		/* perform probe/init/attach */
+		client = dvb_module_probe("si2168", NULL, demod_i2c,
+						dev->board.demod_addr,
+						&si2168_config);
+		if (!client) {
+			result = -ENODEV;
+			goto out_free;
+		}
+		dvb->i2c_client_demod[0] = client;
+
+		/* attach tuner chip */
+		si2157_config.fe = dev->dvb->frontend[0];
+#ifdef CONFIG_MEDIA_CONTROLLER_DVB
+		si2157_config.mdev = dev->media_dev;
+#endif
+		si2157_config.if_port = 1;
+		si2157_config.inversion = false;
+
+		/* perform probe/init/attach */
+		client = dvb_module_probe("si2157", NULL, tuner_i2c,
+						dev->board.tuner_addr,
+						&si2157_config);
+		if (!client) {
+			result = -ENODEV;
+			goto out_free;
+		}
+		dev->cx231xx_reset_analog_tuner = NULL;
+		dev->dvb->i2c_client_tuner = client;
+		break;
+	}
+	case CX231XX_BOARD_ASTROMETA_T2HYBRID:
+	{
+		struct mn88473_config mn88473_config = {};
+
+		/* attach demodulator chip */
+		mn88473_config.i2c_wr_max = 16;
+		mn88473_config.xtal = 25000000;
+		mn88473_config.fe = &dev->dvb->frontend[0];
+
+		/* perform probe/init/attach */
+		client = dvb_module_probe("mn88473", NULL, demod_i2c,
+						dev->board.demod_addr,
+						&mn88473_config);
+		if (!client) {
+			result = -ENODEV;
+			goto out_free;
+		}
+		dvb->i2c_client_demod[0] = client;
+
+		/* define general-purpose callback pointer */
+		dvb->frontend[0]->callback = cx231xx_tuner_callback;
+
+		/* attach tuner chip */
+		dvb_attach(r820t_attach, dev->dvb->frontend[0],
+			   tuner_i2c,
+			   &astrometa_t2hybrid_r820t_config);
+		break;
+	}
+	case CX231XX_BOARD_HAUPPAUGE_935C:
+	{
+		struct si2157_config si2157_config = {};
+		struct si2168_config si2168_config = {};
+
+		/* attach demodulator chip */
+		si2168_config.ts_mode = SI2168_TS_SERIAL;
+		si2168_config.fe = &dev->dvb->frontend[0];
+		si2168_config.i2c_adapter = &adapter;
+		si2168_config.ts_clock_inv = true;
+
+		/* perform probe/init/attach */
+		client = dvb_module_probe("si2168", NULL, demod_i2c,
+						dev->board.demod_addr,
+						&si2168_config);
+		if (!client) {
+			result = -ENODEV;
+			goto out_free;
+		}
+		dvb->i2c_client_demod[0] = client;
+		dev->dvb->frontend[0]->ops.i2c_gate_ctrl = NULL;
+
+		/* define general-purpose callback pointer */
+		dvb->frontend[0]->callback = cx231xx_tuner_callback;
+
+		/* attach tuner */
+		si2157_config.fe = dev->dvb->frontend[0];
+#ifdef CONFIG_MEDIA_CONTROLLER_DVB
+		si2157_config.mdev = dev->media_dev;
+#endif
+		si2157_config.if_port = 1;
+		si2157_config.inversion = true;
+
+		/* perform probe/init/attach */
+		client = dvb_module_probe("si2157", NULL, tuner_i2c,
+						dev->board.tuner_addr,
+						&si2157_config);
+		if (!client) {
+			result = -ENODEV;
+			goto out_free;
+		}
+		dev->cx231xx_reset_analog_tuner = NULL;
+		dev->dvb->i2c_client_tuner = client;
+		break;
+	}
+	case CX231XX_BOARD_HAUPPAUGE_975:
+	{
+		struct i2c_adapter *adapter2;
+		struct si2157_config si2157_config = {};
+		struct lgdt3306a_config lgdt3306a_config = {};
+		struct si2168_config si2168_config = {};
+
+		/* attach first demodulator chip */
+		lgdt3306a_config = hauppauge_955q_lgdt3306a_config;
+		lgdt3306a_config.fe = &dev->dvb->frontend[0];
+		lgdt3306a_config.i2c_adapter = &adapter;
+
+		/* perform probe/init/attach */
+		client = dvb_module_probe("lgdt3306a", NULL, demod_i2c,
+						dev->board.demod_addr,
+						&lgdt3306a_config);
+		if (!client) {
+			result = -ENODEV;
+			goto out_free;
+		}
+		dvb->i2c_client_demod[0] = client;
+
+		/* attach second demodulator chip */
+		si2168_config.ts_mode = SI2168_TS_SERIAL;
+		si2168_config.fe = &dev->dvb->frontend[1];
+		si2168_config.i2c_adapter = &adapter2;
+		si2168_config.ts_clock_inv = true;
+
+		/* perform probe/init/attach */
+		client = dvb_module_probe("si2168", NULL, adapter,
+						dev->board.demod_addr2,
+						&si2168_config);
+		if (!client) {
+			result = -ENODEV;
+			goto out_free;
+		}
+		dvb->i2c_client_demod[1] = client;
+		dvb->frontend[1]->id = 1;
+
+		/* define general-purpose callback pointer */
+		dvb->frontend[0]->callback = cx231xx_tuner_callback;
+		dvb->frontend[1]->callback = cx231xx_tuner_callback;
+
+		/* attach tuner */
+		si2157_config.fe = dev->dvb->frontend[0];
+#ifdef CONFIG_MEDIA_CONTROLLER_DVB
+		si2157_config.mdev = dev->media_dev;
+#endif
+		si2157_config.if_port = 1;
+		si2157_config.inversion = true;
+
+		/* perform probe/init/attach */
+		client = dvb_module_probe("si2157", NULL, adapter,
+						dev->board.tuner_addr,
+						&si2157_config);
+		if (!client) {
+			result = -ENODEV;
+			goto out_free;
+		}
+		dev->cx231xx_reset_analog_tuner = NULL;
+		dvb->i2c_client_tuner = client;
+
+		dvb->frontend[1]->tuner_priv = dvb->frontend[0]->tuner_priv;
+
+		memcpy(&dvb->frontend[1]->ops.tuner_ops,
+			&dvb->frontend[0]->ops.tuner_ops,
+			sizeof(struct dvb_tuner_ops));
+		break;
+	}
+	default:
+		dev_err(dev->dev,
+			"%s/2: The frontend of your DVB/ATSC card isn't supported yet\n",
+			dev->name);
+		break;
+	}
+	if (!dvb->frontend[0]) {
+		dev_err(dev->dev,
+		       "%s/2: frontend initialization failed\n", dev->name);
+		result = -EINVAL;
+		goto out_free;
+	}
+
+	/* register everything */
+	result = register_dvb(dvb, THIS_MODULE, dev, dev->dev);
+
+	if (result < 0)
+		goto out_free;
+
+
+	dev_info(dev->dev, "Successfully loaded cx231xx-dvb\n");
+
+ret:
+	cx231xx_set_mode(dev, CX231XX_SUSPEND);
+	mutex_unlock(&dev->lock);
+	return result;
+
+out_free:
+	/* remove I2C tuner */
+	dvb_module_release(dvb->i2c_client_tuner);
+	dvb->i2c_client_tuner = NULL;
+	/* remove I2C demod(s) */
+	dvb_module_release(dvb->i2c_client_demod[1]);
+	dvb->i2c_client_demod[1] = NULL;
+	dvb_module_release(dvb->i2c_client_demod[0]);
+	dvb->i2c_client_demod[0] = NULL;
+	kfree(dvb);
+	dev->dvb = NULL;
+	goto ret;
+}
+
+static int dvb_fini(struct cx231xx *dev)
+{
+	if (!dev->board.has_dvb) {
+		/* This device does not support the extension */
+		return 0;
+	}
+
+	if (dev->dvb) {
+		unregister_dvb(dev->dvb);
+		dev->dvb = NULL;
+	}
+
+	return 0;
+}
+
+static struct cx231xx_ops dvb_ops = {
+	.id = CX231XX_DVB,
+	.name = "Cx231xx dvb Extension",
+	.init = dvb_init,
+	.fini = dvb_fini,
+};
+
+static int __init cx231xx_dvb_register(void)
+{
+	return cx231xx_register_extension(&dvb_ops);
+}
+
+static void __exit cx231xx_dvb_unregister(void)
+{
+	cx231xx_unregister_extension(&dvb_ops);
+}
+
+module_init(cx231xx_dvb_register);
+module_exit(cx231xx_dvb_unregister);
diff --git a/drivers/media/usb/cx231xx/cx231xx-i2c.c b/drivers/media/usb/cx231xx/cx231xx-i2c.c
new file mode 100644
index 0000000..15a9116
--- /dev/null
+++ b/drivers/media/usb/cx231xx/cx231xx-i2c.c
@@ -0,0 +1,609 @@
+/*
+   cx231xx-i2c.c - driver for Conexant Cx23100/101/102 USB video capture devices
+
+   Copyright (C) 2008 <srinivasa.deevi at conexant dot com>
+		Based on em28xx driver
+		Based on Cx23885 driver
+
+   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.
+ */
+
+#include "cx231xx.h"
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/i2c.h>
+#include <linux/i2c-mux.h>
+#include <media/v4l2-common.h>
+#include <media/tuner.h>
+
+
+/* ----------------------------------------------------------- */
+
+static unsigned int i2c_scan;
+module_param(i2c_scan, int, 0444);
+MODULE_PARM_DESC(i2c_scan, "scan i2c bus at insmod time");
+
+static unsigned int i2c_debug;
+module_param(i2c_debug, int, 0644);
+MODULE_PARM_DESC(i2c_debug, "enable debug messages [i2c]");
+
+#define dprintk1(lvl, fmt, args...)			\
+do {							\
+	if (i2c_debug >= lvl) {				\
+		printk(fmt, ##args);			\
+		}					\
+} while (0)
+
+#define dprintk2(lvl, fmt, args...)			\
+do {							\
+	if (i2c_debug >= lvl) {				\
+		printk(KERN_DEBUG "%s at %s: " fmt,	\
+		       dev->name, __func__ , ##args);	\
+      }							\
+} while (0)
+
+static inline int get_real_i2c_port(struct cx231xx *dev, int bus_nr)
+{
+	if (bus_nr == 1)
+		return dev->port_3_switch_enabled ? I2C_1_MUX_3 : I2C_1_MUX_1;
+	return bus_nr;
+}
+
+static inline bool is_tuner(struct cx231xx *dev, struct cx231xx_i2c *bus,
+			const struct i2c_msg *msg, int tuner_type)
+{
+	int i2c_port = get_real_i2c_port(dev, bus->nr);
+
+	if (i2c_port != dev->board.tuner_i2c_master)
+		return false;
+
+	if (msg->addr != dev->board.tuner_addr)
+		return false;
+
+	if (dev->tuner_type != tuner_type)
+		return false;
+
+	return true;
+}
+
+/*
+ * cx231xx_i2c_send_bytes()
+ */
+static int cx231xx_i2c_send_bytes(struct i2c_adapter *i2c_adap,
+				  const struct i2c_msg *msg)
+{
+	struct cx231xx_i2c *bus = i2c_adap->algo_data;
+	struct cx231xx *dev = bus->dev;
+	struct cx231xx_i2c_xfer_data req_data;
+	int status = 0;
+	u16 size = 0;
+	u8 loop = 0;
+	u8 saddr_len = 1;
+	u8 *buf_ptr = NULL;
+	u16 saddr = 0;
+	u8 need_gpio = 0;
+
+	if (is_tuner(dev, bus, msg, TUNER_XC5000)) {
+		size = msg->len;
+
+		if (size == 2) {	/* register write sub addr */
+			/* Just writing sub address will cause problem
+			* to XC5000. So ignore the request */
+			return 0;
+		} else if (size == 4) {	/* register write with sub addr */
+			if (msg->len >= 2)
+				saddr = msg->buf[0] << 8 | msg->buf[1];
+			else if (msg->len == 1)
+				saddr = msg->buf[0];
+
+			switch (saddr) {
+			case 0x0000:	/* start tuner calibration mode */
+				need_gpio = 1;
+				/* FW Loading is done */
+				dev->xc_fw_load_done = 1;
+				break;
+			case 0x000D:	/* Set signal source */
+			case 0x0001:	/* Set TV standard - Video */
+			case 0x0002:	/* Set TV standard - Audio */
+			case 0x0003:	/* Set RF Frequency */
+				need_gpio = 1;
+				break;
+			default:
+				if (dev->xc_fw_load_done)
+					need_gpio = 1;
+				break;
+			}
+
+			if (need_gpio) {
+				dprintk1(1,
+				"GPIO WRITE: addr 0x%x, len %d, saddr 0x%x\n",
+				msg->addr, msg->len, saddr);
+
+				return dev->cx231xx_gpio_i2c_write(dev,
+								   msg->addr,
+								   msg->buf,
+								   msg->len);
+			}
+		}
+
+		/* special case for Xc5000 tuner case */
+		saddr_len = 1;
+
+		/* adjust the length to correct length */
+		size -= saddr_len;
+		buf_ptr = (u8 *) (msg->buf + 1);
+
+		do {
+			/* prepare xfer_data struct */
+			req_data.dev_addr = msg->addr;
+			req_data.direction = msg->flags;
+			req_data.saddr_len = saddr_len;
+			req_data.saddr_dat = msg->buf[0];
+			req_data.buf_size = size > 16 ? 16 : size;
+			req_data.p_buffer = (u8 *) (buf_ptr + loop * 16);
+
+			bus->i2c_nostop = (size > 16) ? 1 : 0;
+			bus->i2c_reserve = (loop == 0) ? 0 : 1;
+
+			/* usb send command */
+			status = dev->cx231xx_send_usb_command(bus, &req_data);
+			loop++;
+
+			if (size >= 16)
+				size -= 16;
+			else
+				size = 0;
+
+		} while (size > 0);
+
+		bus->i2c_nostop = 0;
+		bus->i2c_reserve = 0;
+
+	} else {		/* regular case */
+
+		/* prepare xfer_data struct */
+		req_data.dev_addr = msg->addr;
+		req_data.direction = msg->flags;
+		req_data.saddr_len = 0;
+		req_data.saddr_dat = 0;
+		req_data.buf_size = msg->len;
+		req_data.p_buffer = msg->buf;
+
+		/* usb send command */
+		status = dev->cx231xx_send_usb_command(bus, &req_data);
+	}
+
+	return status < 0 ? status : 0;
+}
+
+/*
+ * cx231xx_i2c_recv_bytes()
+ * read a byte from the i2c device
+ */
+static int cx231xx_i2c_recv_bytes(struct i2c_adapter *i2c_adap,
+				  const struct i2c_msg *msg)
+{
+	struct cx231xx_i2c *bus = i2c_adap->algo_data;
+	struct cx231xx *dev = bus->dev;
+	struct cx231xx_i2c_xfer_data req_data;
+	int status = 0;
+	u16 saddr = 0;
+	u8 need_gpio = 0;
+
+	if (is_tuner(dev, bus, msg, TUNER_XC5000)) {
+		if (msg->len == 2)
+			saddr = msg->buf[0] << 8 | msg->buf[1];
+		else if (msg->len == 1)
+			saddr = msg->buf[0];
+
+		if (dev->xc_fw_load_done) {
+
+			switch (saddr) {
+			case 0x0009:	/* BUSY check */
+				dprintk1(1,
+				"GPIO R E A D: Special case BUSY check \n");
+				/*Try read BUSY register, just set it to zero*/
+				msg->buf[0] = 0;
+				if (msg->len == 2)
+					msg->buf[1] = 0;
+				return 0;
+			case 0x0004:	/* read Lock status */
+				need_gpio = 1;
+				break;
+
+			}
+
+			if (need_gpio) {
+				/* this is a special case to handle Xceive tuner
+				clock stretch issue with gpio based I2C */
+
+				dprintk1(1,
+				"GPIO R E A D: addr 0x%x, len %d, saddr 0x%x\n",
+				msg->addr, msg->len,
+				msg->buf[0] << 8 | msg->buf[1]);
+
+				status =
+				    dev->cx231xx_gpio_i2c_write(dev, msg->addr,
+								msg->buf,
+								msg->len);
+				status =
+				    dev->cx231xx_gpio_i2c_read(dev, msg->addr,
+							       msg->buf,
+							       msg->len);
+				return status;
+			}
+		}
+
+		/* prepare xfer_data struct */
+		req_data.dev_addr = msg->addr;
+		req_data.direction = msg->flags;
+		req_data.saddr_len = msg->len;
+		req_data.saddr_dat = msg->buf[0] << 8 | msg->buf[1];
+		req_data.buf_size = msg->len;
+		req_data.p_buffer = msg->buf;
+
+		/* usb send command */
+		status = dev->cx231xx_send_usb_command(bus, &req_data);
+
+	} else {
+
+		/* prepare xfer_data struct */
+		req_data.dev_addr = msg->addr;
+		req_data.direction = msg->flags;
+		req_data.saddr_len = 0;
+		req_data.saddr_dat = 0;
+		req_data.buf_size = msg->len;
+		req_data.p_buffer = msg->buf;
+
+		/* usb send command */
+		status = dev->cx231xx_send_usb_command(bus, &req_data);
+	}
+
+	return status < 0 ? status : 0;
+}
+
+/*
+ * cx231xx_i2c_recv_bytes_with_saddr()
+ * read a byte from the i2c device
+ */
+static int cx231xx_i2c_recv_bytes_with_saddr(struct i2c_adapter *i2c_adap,
+					     const struct i2c_msg *msg1,
+					     const struct i2c_msg *msg2)
+{
+	struct cx231xx_i2c *bus = i2c_adap->algo_data;
+	struct cx231xx *dev = bus->dev;
+	struct cx231xx_i2c_xfer_data req_data;
+	int status = 0;
+	u16 saddr = 0;
+	u8 need_gpio = 0;
+
+	if (msg1->len == 2)
+		saddr = msg1->buf[0] << 8 | msg1->buf[1];
+	else if (msg1->len == 1)
+		saddr = msg1->buf[0];
+
+	if (is_tuner(dev, bus, msg2, TUNER_XC5000)) {
+		if ((msg2->len < 16)) {
+
+			dprintk1(1,
+			"i2c_read: addr 0x%x, len %d, saddr 0x%x, len %d\n",
+			msg2->addr, msg2->len, saddr, msg1->len);
+
+			switch (saddr) {
+			case 0x0008:	/* read FW load status */
+				need_gpio = 1;
+				break;
+			case 0x0004:	/* read Lock status */
+				need_gpio = 1;
+				break;
+			}
+
+			if (need_gpio) {
+				status =
+				    dev->cx231xx_gpio_i2c_write(dev, msg1->addr,
+								msg1->buf,
+								msg1->len);
+				status =
+				    dev->cx231xx_gpio_i2c_read(dev, msg2->addr,
+							       msg2->buf,
+							       msg2->len);
+				return status;
+			}
+		}
+	}
+
+	/* prepare xfer_data struct */
+	req_data.dev_addr = msg2->addr;
+	req_data.direction = msg2->flags;
+	req_data.saddr_len = msg1->len;
+	req_data.saddr_dat = saddr;
+	req_data.buf_size = msg2->len;
+	req_data.p_buffer = msg2->buf;
+
+	/* usb send command */
+	status = dev->cx231xx_send_usb_command(bus, &req_data);
+
+	return status < 0 ? status : 0;
+}
+
+/*
+ * cx231xx_i2c_check_for_device()
+ * check if there is a i2c_device at the supplied address
+ */
+static int cx231xx_i2c_check_for_device(struct i2c_adapter *i2c_adap,
+					const struct i2c_msg *msg)
+{
+	struct cx231xx_i2c *bus = i2c_adap->algo_data;
+	struct cx231xx *dev = bus->dev;
+	struct cx231xx_i2c_xfer_data req_data;
+	int status = 0;
+	u8 buf[1];
+
+	/* prepare xfer_data struct */
+	req_data.dev_addr = msg->addr;
+	req_data.direction = I2C_M_RD;
+	req_data.saddr_len = 0;
+	req_data.saddr_dat = 0;
+	req_data.buf_size = 1;
+	req_data.p_buffer = buf;
+
+	/* usb send command */
+	status = dev->cx231xx_send_usb_command(bus, &req_data);
+
+	return status < 0 ? status : 0;
+}
+
+/*
+ * cx231xx_i2c_xfer()
+ * the main i2c transfer function
+ */
+static int cx231xx_i2c_xfer(struct i2c_adapter *i2c_adap,
+			    struct i2c_msg msgs[], int num)
+{
+	struct cx231xx_i2c *bus = i2c_adap->algo_data;
+	struct cx231xx *dev = bus->dev;
+	int addr, rc, i, byte;
+
+	mutex_lock(&dev->i2c_lock);
+	for (i = 0; i < num; i++) {
+
+		addr = msgs[i].addr;
+
+		dprintk2(2, "%s %s addr=0x%x len=%d:",
+			 (msgs[i].flags & I2C_M_RD) ? "read" : "write",
+			 i == num - 1 ? "stop" : "nonstop", addr, msgs[i].len);
+		if (!msgs[i].len) {
+			/* no len: check only for device presence */
+			rc = cx231xx_i2c_check_for_device(i2c_adap, &msgs[i]);
+			if (rc < 0) {
+				dprintk2(2, " no device\n");
+				mutex_unlock(&dev->i2c_lock);
+				return rc;
+			}
+
+		} else if (msgs[i].flags & I2C_M_RD) {
+			/* read bytes */
+			rc = cx231xx_i2c_recv_bytes(i2c_adap, &msgs[i]);
+			if (i2c_debug >= 2) {
+				for (byte = 0; byte < msgs[i].len; byte++)
+					printk(KERN_CONT " %02x", msgs[i].buf[byte]);
+			}
+		} else if (i + 1 < num && (msgs[i + 1].flags & I2C_M_RD) &&
+			   msgs[i].addr == msgs[i + 1].addr
+			   && (msgs[i].len <= 2) && (bus->nr < 3)) {
+			/* write bytes */
+			if (i2c_debug >= 2) {
+				for (byte = 0; byte < msgs[i].len; byte++)
+					printk(KERN_CONT " %02x", msgs[i].buf[byte]);
+				printk(KERN_CONT "\n");
+			}
+			/* read bytes */
+			dprintk2(2, "plus %s %s addr=0x%x len=%d:",
+				(msgs[i+1].flags & I2C_M_RD) ? "read" : "write",
+				i+1 == num - 1 ? "stop" : "nonstop", addr, msgs[i+1].len);
+			rc = cx231xx_i2c_recv_bytes_with_saddr(i2c_adap,
+							       &msgs[i],
+							       &msgs[i + 1]);
+			if (i2c_debug >= 2) {
+				for (byte = 0; byte < msgs[i+1].len; byte++)
+					printk(KERN_CONT " %02x", msgs[i+1].buf[byte]);
+			}
+			i++;
+		} else {
+			/* write bytes */
+			if (i2c_debug >= 2) {
+				for (byte = 0; byte < msgs[i].len; byte++)
+					printk(KERN_CONT " %02x", msgs[i].buf[byte]);
+			}
+			rc = cx231xx_i2c_send_bytes(i2c_adap, &msgs[i]);
+		}
+		if (rc < 0)
+			goto err;
+		if (i2c_debug >= 2)
+			printk(KERN_CONT "\n");
+	}
+	mutex_unlock(&dev->i2c_lock);
+	return num;
+err:
+	dprintk2(2, " ERROR: %i\n", rc);
+	mutex_unlock(&dev->i2c_lock);
+	return rc;
+}
+
+/* ----------------------------------------------------------- */
+
+/*
+ * functionality()
+ */
+static u32 functionality(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_SMBUS_EMUL | I2C_FUNC_I2C;
+}
+
+static const struct i2c_algorithm cx231xx_algo = {
+	.master_xfer = cx231xx_i2c_xfer,
+	.functionality = functionality,
+};
+
+static const struct i2c_adapter cx231xx_adap_template = {
+	.owner = THIS_MODULE,
+	.name = "cx231xx",
+	.algo = &cx231xx_algo,
+};
+
+/* ----------------------------------------------------------- */
+
+/*
+ * i2c_devs
+ * incomplete list of known devices
+ */
+static const char *i2c_devs[128] = {
+	[0x20 >> 1] = "demod",
+	[0x60 >> 1] = "colibri",
+	[0x88 >> 1] = "hammerhead",
+	[0x8e >> 1] = "CIR",
+	[0x32 >> 1] = "GeminiIII",
+	[0x02 >> 1] = "Aquarius",
+	[0xa0 >> 1] = "eeprom",
+	[0xc0 >> 1] = "tuner",
+	[0xc2 >> 1] = "tuner",
+};
+
+/*
+ * cx231xx_do_i2c_scan()
+ * check i2c address range for devices
+ */
+void cx231xx_do_i2c_scan(struct cx231xx *dev, int i2c_port)
+{
+	unsigned char buf;
+	int i, rc;
+	struct i2c_adapter *adap;
+	struct i2c_msg msg = {
+		.flags = I2C_M_RD,
+		.len = 1,
+		.buf = &buf,
+	};
+
+	if (!i2c_scan)
+		return;
+
+	/* Don't generate I2C errors during scan */
+	dev->i2c_scan_running = true;
+	adap = cx231xx_get_i2c_adap(dev, i2c_port);
+
+	for (i = 0; i < 128; i++) {
+		msg.addr = i;
+		rc = i2c_transfer(adap, &msg, 1);
+
+		if (rc < 0)
+			continue;
+		dev_info(dev->dev,
+			 "i2c scan: found device @ port %d addr 0x%x  [%s]\n",
+			 i2c_port,
+			 i << 1,
+			 i2c_devs[i] ? i2c_devs[i] : "???");
+	}
+
+	dev->i2c_scan_running = false;
+}
+
+/*
+ * cx231xx_i2c_register()
+ * register i2c bus
+ */
+int cx231xx_i2c_register(struct cx231xx_i2c *bus)
+{
+	struct cx231xx *dev = bus->dev;
+
+	BUG_ON(!dev->cx231xx_send_usb_command);
+
+	bus->i2c_adap = cx231xx_adap_template;
+	bus->i2c_adap.dev.parent = dev->dev;
+
+	snprintf(bus->i2c_adap.name, sizeof(bus->i2c_adap.name), "%s-%d", bus->dev->name, bus->nr);
+
+	bus->i2c_adap.algo_data = bus;
+	i2c_set_adapdata(&bus->i2c_adap, &dev->v4l2_dev);
+	bus->i2c_rc = i2c_add_adapter(&bus->i2c_adap);
+
+	if (0 != bus->i2c_rc)
+		dev_warn(dev->dev,
+			 "i2c bus %d register FAILED\n", bus->nr);
+
+	return bus->i2c_rc;
+}
+
+/*
+ * cx231xx_i2c_unregister()
+ * unregister i2c_bus
+ */
+void cx231xx_i2c_unregister(struct cx231xx_i2c *bus)
+{
+	if (!bus->i2c_rc)
+		i2c_del_adapter(&bus->i2c_adap);
+}
+
+/*
+ * cx231xx_i2c_mux_select()
+ * switch i2c master number 1 between port1 and port3
+ */
+static int cx231xx_i2c_mux_select(struct i2c_mux_core *muxc, u32 chan_id)
+{
+	struct cx231xx *dev = i2c_mux_priv(muxc);
+
+	return cx231xx_enable_i2c_port_3(dev, chan_id);
+}
+
+int cx231xx_i2c_mux_create(struct cx231xx *dev)
+{
+	dev->muxc = i2c_mux_alloc(&dev->i2c_bus[1].i2c_adap, dev->dev, 2, 0, 0,
+				  cx231xx_i2c_mux_select, NULL);
+	if (!dev->muxc)
+		return -ENOMEM;
+	dev->muxc->priv = dev;
+	return 0;
+}
+
+int cx231xx_i2c_mux_register(struct cx231xx *dev, int mux_no)
+{
+	return i2c_mux_add_adapter(dev->muxc,
+				   0,
+				   mux_no /* chan_id */,
+				   0 /* class */);
+}
+
+void cx231xx_i2c_mux_unregister(struct cx231xx *dev)
+{
+	i2c_mux_del_adapters(dev->muxc);
+}
+
+struct i2c_adapter *cx231xx_get_i2c_adap(struct cx231xx *dev, int i2c_port)
+{
+	switch (i2c_port) {
+	case I2C_0:
+		return &dev->i2c_bus[0].i2c_adap;
+	case I2C_1:
+		return &dev->i2c_bus[1].i2c_adap;
+	case I2C_2:
+		return &dev->i2c_bus[2].i2c_adap;
+	case I2C_1_MUX_1:
+		return dev->muxc->adapter[0];
+	case I2C_1_MUX_3:
+		return dev->muxc->adapter[1];
+	default:
+		BUG();
+	}
+}
+EXPORT_SYMBOL_GPL(cx231xx_get_i2c_adap);
diff --git a/drivers/media/usb/cx231xx/cx231xx-input.c b/drivers/media/usb/cx231xx/cx231xx-input.c
new file mode 100644
index 0000000..3e9b73a
--- /dev/null
+++ b/drivers/media/usb/cx231xx/cx231xx-input.c
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0
+// cx231xx IR glue driver
+//
+// Copyright (c) 2010 Mauro Carvalho Chehab <mchehab@kernel.org>
+//
+// Polaris (cx231xx) has its support for IR's with a design close to MCE.
+// however, a few designs are using an external I2C chip for IR, instead
+// of using the one provided by the chip.
+// This driver provides support for those extra devices
+
+#include "cx231xx.h"
+#include <linux/slab.h>
+#include <linux/bitrev.h>
+
+#define MODULE_NAME "cx231xx-input"
+
+static int get_key_isdbt(struct IR_i2c *ir, enum rc_proto *protocol,
+			 u32 *pscancode, u8 *toggle)
+{
+	int	rc;
+	u8	cmd, scancode;
+
+	dev_dbg(&ir->rc->dev, "%s\n", __func__);
+
+		/* poll IR chip */
+	rc = i2c_master_recv(ir->c, &cmd, 1);
+	if (rc < 0)
+		return rc;
+	if (rc != 1)
+		return -EIO;
+
+	/* it seems that 0xFE indicates that a button is still hold
+	   down, while 0xff indicates that no button is hold
+	   down. 0xfe sequences are sometimes interrupted by 0xFF */
+
+	if (cmd == 0xff)
+		return 0;
+
+	scancode = bitrev8(cmd);
+
+	dev_dbg(&ir->rc->dev, "cmd %02x, scan = %02x\n", cmd, scancode);
+
+	*protocol = RC_PROTO_OTHER;
+	*pscancode = scancode;
+	*toggle = 0;
+	return 1;
+}
+
+int cx231xx_ir_init(struct cx231xx *dev)
+{
+	struct i2c_board_info info;
+	u8 ir_i2c_bus;
+
+	dev_dbg(dev->dev, "%s\n", __func__);
+
+	/* Only initialize if a rc keycode map is defined */
+	if (!cx231xx_boards[dev->model].rc_map_name)
+		return -ENODEV;
+
+	request_module("ir-kbd-i2c");
+
+	memset(&info, 0, sizeof(struct i2c_board_info));
+	memset(&dev->init_data, 0, sizeof(dev->init_data));
+	dev->init_data.rc_dev = rc_allocate_device(RC_DRIVER_SCANCODE);
+	if (!dev->init_data.rc_dev)
+		return -ENOMEM;
+
+	dev->init_data.name = cx231xx_boards[dev->model].name;
+
+	strlcpy(info.type, "ir_video", I2C_NAME_SIZE);
+	info.platform_data = &dev->init_data;
+
+	/*
+	 * Board-dependent values
+	 *
+	 * For now, there's just one type of hardware design using
+	 * an i2c device.
+	 */
+	dev->init_data.get_key = get_key_isdbt;
+	dev->init_data.ir_codes = cx231xx_boards[dev->model].rc_map_name;
+	/* The i2c micro-controller only outputs the cmd part of NEC protocol */
+	dev->init_data.rc_dev->scancode_mask = 0xff;
+	dev->init_data.rc_dev->driver_name = "cx231xx";
+	dev->init_data.type = RC_PROTO_BIT_NEC;
+	info.addr = 0x30;
+
+	/* Load and bind ir-kbd-i2c */
+	ir_i2c_bus = cx231xx_boards[dev->model].ir_i2c_master;
+	dev_dbg(dev->dev, "Trying to bind ir at bus %d, addr 0x%02x\n",
+		ir_i2c_bus, info.addr);
+	dev->ir_i2c_client = i2c_new_device(
+		cx231xx_get_i2c_adap(dev, ir_i2c_bus), &info);
+
+	return 0;
+}
+
+void cx231xx_ir_exit(struct cx231xx *dev)
+{
+	if (dev->ir_i2c_client)
+		i2c_unregister_device(dev->ir_i2c_client);
+	dev->ir_i2c_client = NULL;
+}
diff --git a/drivers/media/usb/cx231xx/cx231xx-pcb-cfg.c b/drivers/media/usb/cx231xx/cx231xx-pcb-cfg.c
new file mode 100644
index 0000000..746c34a
--- /dev/null
+++ b/drivers/media/usb/cx231xx/cx231xx-pcb-cfg.c
@@ -0,0 +1,810 @@
+/*
+   cx231xx-pcb-config.c - driver for Conexant
+		Cx23100/101/102 USB video capture devices
+
+   Copyright (C) 2008 <srinivasa.deevi at conexant dot com>
+
+   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.
+ */
+
+#include "cx231xx.h"
+#include "cx231xx-conf-reg.h"
+
+static unsigned int pcb_debug;
+module_param(pcb_debug, int, 0644);
+MODULE_PARM_DESC(pcb_debug, "enable pcb config debug messages [video]");
+
+/******************************************************************************/
+
+static struct pcb_config cx231xx_Scenario[] = {
+	{
+	 INDEX_SELFPOWER_DIGITAL_ONLY,	/* index */
+	 USB_SELF_POWER,	/* power_type */
+	 0,			/* speed , not decide yet */
+	 MOD_DIGITAL,		/* mode */
+	 SOURCE_TS_BDA,		/* ts1_source, digital tv only */
+	 NOT_SUPPORTED,		/* ts2_source  */
+	 NOT_SUPPORTED,		/* analog source */
+
+	 0,			/* digital_index  */
+	 0,			/* analog index */
+	 0,			/* dif_index   */
+	 0,			/* external_index */
+
+	 1,			/* only one configuration */
+	 {
+	  {
+	   0,			/* config index */
+	   {
+	    0,			/* interrupt ep index */
+	    1,			/* ts1 index */
+	    NOT_SUPPORTED,	/* TS2 index */
+	    NOT_SUPPORTED,	/* AUDIO */
+	    NOT_SUPPORTED,	/* VIDEO */
+	    NOT_SUPPORTED,	/* VANC */
+	    NOT_SUPPORTED,	/* HANC */
+	    NOT_SUPPORTED	/* ir_index */
+	    }
+	   ,
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  }
+	 ,
+	 /* full-speed config */
+	 {
+	  {
+	   0,			/* config index */
+	   {
+	    0,			/* interrupt ep index */
+	    1,			/* ts1 index */
+	    NOT_SUPPORTED,	/* TS2 index */
+	    NOT_SUPPORTED,	/* AUDIO */
+	    NOT_SUPPORTED,	/* VIDEO */
+	    NOT_SUPPORTED,	/* VANC */
+	    NOT_SUPPORTED,	/* HANC */
+	    NOT_SUPPORTED	/* ir_index */
+	    }
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  }
+	 }
+	,
+
+	{
+	 INDEX_SELFPOWER_DUAL_DIGITAL,	/* index */
+	 USB_SELF_POWER,	/* power_type */
+	 0,			/* speed , not decide yet */
+	 MOD_DIGITAL,		/* mode */
+	 SOURCE_TS_BDA,		/* ts1_source, digital tv only */
+	 0,			/* ts2_source,need update from register */
+	 NOT_SUPPORTED,		/* analog source */
+	 0,			/* digital_index  */
+	 0,			/* analog index */
+	 0,			/* dif_index */
+	 0,			/* external_index */
+
+	 1,			/* only one configuration */
+	 {
+	  {
+	   0,			/* config index */
+	   {
+	    0,			/* interrupt ep index */
+	    1,			/* ts1 index */
+	    2,			/* TS2 index */
+	    NOT_SUPPORTED,	/* AUDIO */
+	    NOT_SUPPORTED,	/* VIDEO */
+	    NOT_SUPPORTED,	/* VANC */
+	    NOT_SUPPORTED,	/* HANC */
+	    NOT_SUPPORTED	/* ir_index */
+	    }
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  }
+	 ,
+	 /* full-speed */
+	 {
+	  {
+	   0,			/* config index */
+	   {
+	    0,			/* interrupt ep index */
+	    1,			/* ts1 index */
+	    2,			/* TS2 index */
+	    NOT_SUPPORTED,	/* AUDIO */
+	    NOT_SUPPORTED,	/* VIDEO */
+	    NOT_SUPPORTED,	/* VANC */
+	    NOT_SUPPORTED,	/* HANC */
+	    NOT_SUPPORTED	/* ir_index */
+	    }
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  }
+	 }
+	,
+
+	{
+	 INDEX_SELFPOWER_ANALOG_ONLY,	/* index */
+	 USB_SELF_POWER,	/* power_type */
+	 0,			/* speed , not decide yet */
+	 MOD_ANALOG | MOD_DIF | MOD_EXTERNAL,	/* mode ,analog tv only */
+	 NOT_SUPPORTED,		/* ts1_source, NOT SUPPORT */
+	 NOT_SUPPORTED,		/* ts2_source,NOT SUPPORT */
+	 0,			/* analog source, need update */
+
+	 0,			/* digital_index  */
+	 0,			/* analog index */
+	 0,			/* dif_index */
+	 0,			/* external_index */
+
+	 1,			/* only one configuration */
+	 {
+	  {
+	   0,			/* config index */
+	   {
+	    0,			/* interrupt ep index */
+	    NOT_SUPPORTED,	/* ts1 index */
+	    NOT_SUPPORTED,	/* TS2 index */
+	    1,			/* AUDIO */
+	    2,			/* VIDEO */
+	    3,			/* VANC */
+	    4,			/* HANC */
+	    NOT_SUPPORTED	/* ir_index */
+	    }
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  }
+	 ,
+	 /* full-speed */
+	 {
+	  {
+	   0,			/* config index */
+	   {
+	    0,			/* interrupt ep index */
+	    NOT_SUPPORTED,	/* ts1 index */
+	    NOT_SUPPORTED,	/* TS2 index */
+	    1,			/* AUDIO */
+	    2,			/* VIDEO */
+	    NOT_SUPPORTED,	/* VANC */
+	    NOT_SUPPORTED,	/* HANC */
+	    NOT_SUPPORTED	/* ir_index */
+	    }
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  }
+	 }
+	,
+
+	{
+	 INDEX_SELFPOWER_DUAL,	/* index */
+	 USB_SELF_POWER,	/* power_type */
+	 0,			/* speed , not decide yet */
+	 /* mode ,analog tv and digital path */
+	 MOD_ANALOG | MOD_DIF | MOD_DIGITAL | MOD_EXTERNAL,
+	 0,			/* ts1_source,will update in register */
+	 NOT_SUPPORTED,		/* ts2_source,NOT SUPPORT */
+	 0,			/* analog source need update */
+	 0,			/* digital_index  */
+	 0,			/* analog index */
+	 0,			/* dif_index */
+	 0,			/* external_index */
+	 1,			/* only one configuration */
+	 {
+	  {
+	   0,			/* config index */
+	   {
+	    0,			/* interrupt ep index */
+	    1,			/* ts1 index */
+	    NOT_SUPPORTED,	/* TS2 index */
+	    2,			/* AUDIO */
+	    3,			/* VIDEO */
+	    4,			/* VANC */
+	    5,			/* HANC */
+	    NOT_SUPPORTED	/* ir_index */
+	    }
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  }
+	 ,
+	 /* full-speed */
+	 {
+	  {
+	   0,			/* config index */
+	   {
+	    0,			/* interrupt ep index */
+	    1,			/* ts1 index */
+	    NOT_SUPPORTED,	/* TS2 index */
+	    2,			/* AUDIO */
+	    3,			/* VIDEO */
+	    NOT_SUPPORTED,	/* VANC */
+	    NOT_SUPPORTED,	/* HANC */
+	    NOT_SUPPORTED	/* ir_index */
+	    }
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  }
+	 }
+	,
+
+	{
+	 INDEX_SELFPOWER_TRIPLE,	/* index */
+	 USB_SELF_POWER,	/* power_type */
+	 0,			/* speed , not decide yet */
+	 /* mode ,analog tv and digital path */
+	 MOD_ANALOG | MOD_DIF | MOD_DIGITAL | MOD_EXTERNAL,
+	 0,			/* ts1_source, update in register */
+	 0,			/* ts2_source,update in register */
+	 0,			/* analog source, need update */
+
+	 0,			/* digital_index  */
+	 0,			/* analog index */
+	 0,			/* dif_index */
+	 0,			/* external_index */
+	 1,			/* only one configuration */
+	 {
+	  {
+	   0,			/* config index */
+	   {
+	    0,			/* interrupt ep index */
+	    1,			/* ts1 index */
+	    2,			/* TS2 index */
+	    3,			/* AUDIO */
+	    4,			/* VIDEO */
+	    5,			/* VANC */
+	    6,			/* HANC */
+	    NOT_SUPPORTED	/* ir_index */
+	    }
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  }
+	 ,
+	 /* full-speed */
+	 {
+	  {
+	   0,			/* config index */
+	   {
+	    0,			/* interrupt ep index */
+	    1,			/* ts1 index */
+	    2,			/* TS2 index */
+	    3,			/* AUDIO */
+	    4,			/* VIDEO */
+	    NOT_SUPPORTED,	/* VANC */
+	    NOT_SUPPORTED,	/* HANC */
+	    NOT_SUPPORTED	/* ir_index */
+	    }
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  }
+	 }
+	,
+
+	{
+	 INDEX_SELFPOWER_COMPRESSOR,	/* index */
+	 USB_SELF_POWER,	/* power_type */
+	 0,			/* speed , not decide yet */
+	 /* mode ,analog tv AND DIGITAL path */
+	 MOD_ANALOG | MOD_DIF | MOD_DIGITAL | MOD_EXTERNAL,
+	 NOT_SUPPORTED,		/* ts1_source, disable */
+	 SOURCE_TS_BDA,		/* ts2_source */
+	 0,			/* analog source,need update */
+	 0,			/* digital_index  */
+	 0,			/* analog index */
+	 0,			/* dif_index */
+	 0,			/* external_index */
+	 1,			/* only one configuration */
+	 {
+	  {
+	   0,			/* config index */
+	   {
+	    0,			/* interrupt ep index */
+	    NOT_SUPPORTED,	/* ts1 index */
+	    1,			/* TS2 index */
+	    2,			/* AUDIO */
+	    3,			/* VIDEO */
+	    4,			/* VANC */
+	    5,			/* HANC */
+	    NOT_SUPPORTED	/* ir_index */
+	    }
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  }
+	 ,
+	 /* full-speed  */
+	 {
+	  {
+	   0,			/* config index */
+	   {
+	    0,			/* interrupt ep index */
+	    NOT_SUPPORTED,	/* ts1 index */
+	    1,			/* TS2 index */
+	    2,			/* AUDIO */
+	    3,			/* VIDEO */
+	    NOT_SUPPORTED,	/* VANC */
+	    NOT_SUPPORTED,	/* HANC */
+	    NOT_SUPPORTED	/* ir_index */
+	    }
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  }
+	 }
+	,
+
+	{
+	 INDEX_BUSPOWER_DIGITAL_ONLY,	/* index */
+	 USB_BUS_POWER,		/* power_type */
+	 0,			/* speed , not decide yet */
+	 MOD_DIGITAL,		/* mode ,analog tv AND DIGITAL path */
+	 SOURCE_TS_BDA,		/* ts1_source, disable */
+	 NOT_SUPPORTED,		/* ts2_source */
+	 NOT_SUPPORTED,		/* analog source */
+
+	 0,			/* digital_index  */
+	 0,			/* analog index */
+	 0,			/* dif_index */
+	 0,			/* external_index */
+
+	 1,			/* only one configuration */
+	 {
+	  {
+	   0,			/* config index */
+	   {
+	    0,			/* interrupt ep index  = 2 */
+	    1,			/* ts1 index */
+	    NOT_SUPPORTED,	/* TS2 index */
+	    NOT_SUPPORTED,	/* AUDIO */
+	    NOT_SUPPORTED,	/* VIDEO */
+	    NOT_SUPPORTED,	/* VANC */
+	    NOT_SUPPORTED,	/* HANC */
+	    NOT_SUPPORTED	/* ir_index */
+	    }
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  }
+	 ,
+	 /* full-speed */
+	 {
+	  {
+	   0,			/* config index */
+	   {
+	    0,			/* interrupt ep index  = 2 */
+	    1,			/* ts1 index */
+	    NOT_SUPPORTED,	/* TS2 index */
+	    NOT_SUPPORTED,	/* AUDIO */
+	    NOT_SUPPORTED,	/* VIDEO */
+	    NOT_SUPPORTED,	/* VANC */
+	    NOT_SUPPORTED,	/* HANC */
+	    NOT_SUPPORTED	/* ir_index */
+	    }
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  }
+	 }
+	,
+	{
+	 INDEX_BUSPOWER_ANALOG_ONLY,	/* index */
+	 USB_BUS_POWER,		/* power_type */
+	 0,			/* speed , not decide yet */
+	 MOD_ANALOG,		/* mode ,analog tv AND DIGITAL path */
+	 NOT_SUPPORTED,		/* ts1_source, disable */
+	 NOT_SUPPORTED,		/* ts2_source */
+	 SOURCE_ANALOG,		/* analog source--analog */
+	 0,			/* digital_index  */
+	 0,			/* analog index */
+	 0,			/* dif_index */
+	 0,			/* external_index */
+	 1,			/* only one configuration */
+	 {
+	  {
+	   0,			/* config index */
+	   {
+	    0,			/* interrupt ep index */
+	    NOT_SUPPORTED,	/* ts1 index */
+	    NOT_SUPPORTED,	/* TS2 index */
+	    1,			/* AUDIO */
+	    2,			/* VIDEO */
+	    3,			/* VANC */
+	    4,			/* HANC */
+	    NOT_SUPPORTED	/* ir_index */
+	    }
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  }
+	 ,
+	 {			/* full-speed */
+	  {
+	   0,			/* config index */
+	   {
+	    0,			/* interrupt ep index */
+	    NOT_SUPPORTED,	/* ts1 index */
+	    NOT_SUPPORTED,	/* TS2 index */
+	    1,			/* AUDIO */
+	    2,			/* VIDEO */
+	    NOT_SUPPORTED,	/* VANC */
+	    NOT_SUPPORTED,	/* HANC */
+	    NOT_SUPPORTED	/* ir_index */
+	    }
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  }
+	 }
+	,
+	{
+	 INDEX_BUSPOWER_DIF_ONLY,	/* index */
+	 USB_BUS_POWER,		/* power_type */
+	 0,			/* speed , not decide yet */
+	 /* mode ,analog tv AND DIGITAL path */
+	 MOD_DIF | MOD_ANALOG | MOD_DIGITAL | MOD_EXTERNAL,
+	 SOURCE_TS_BDA,		/* ts1_source, disable */
+	 NOT_SUPPORTED,		/* ts2_source */
+	 SOURCE_DIF | SOURCE_ANALOG | SOURCE_EXTERNAL,	/* analog source, dif */
+	 0,			/* digital_index  */
+	 0,			/* analog index */
+	 0,			/* dif_index */
+	 0,			/* external_index */
+	 1,			/* only one configuration */
+	 {
+	  {
+	   0,			/* config index */
+	   {
+	    0,			/* interrupt ep index */
+	    1,			/* ts1 index */
+	    NOT_SUPPORTED,	/* TS2 index */
+	    2,			/* AUDIO */
+	    3,			/* VIDEO */
+	    4,			/* VANC */
+	    5,			/* HANC */
+	    NOT_SUPPORTED	/* ir_index */
+	    }
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  }
+	 ,
+	 {			/* full speed */
+	  {
+	   0,			/* config index */
+	   {
+	    0,			/* interrupt ep index */
+	    1,			/* ts1 index */
+	    NOT_SUPPORTED,	/* TS2 index */
+	    2,			/* AUDIO */
+	    3,			/* VIDEO */
+	    NOT_SUPPORTED,	/* VANC */
+	    NOT_SUPPORTED,	/* HANC */
+	    NOT_SUPPORTED	/* ir_index */
+	    }
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  ,
+	  {NOT_SUPPORTED, {NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED, NOT_SUPPORTED, NOT_SUPPORTED,
+			   NOT_SUPPORTED}
+	   }
+	  }
+	 }
+	,
+
+};
+
+/*****************************************************************/
+
+int initialize_cx231xx(struct cx231xx *dev)
+{
+	int retval;
+	u32 config_info = 0;
+	struct pcb_config *p_pcb_info;
+	u8 usb_speed = 1;	/* from register,1--HS, 0--FS  */
+	u8 data[4] = { 0, 0, 0, 0 };
+	u32 ts1_source = 0;
+	u32 ts2_source = 0;
+	u32 analog_source = 0;
+	u8 _current_scenario_idx = 0xff;
+
+	ts1_source = SOURCE_TS_BDA;
+	ts2_source = SOURCE_TS_BDA;
+
+	/* read board config register to find out which
+	pcb config it is related to */
+	retval = cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER, BOARD_CFG_STAT,
+				       data, 4);
+	if (retval < 0)
+		return retval;
+
+	config_info = le32_to_cpu(*((__le32 *)data));
+	usb_speed = (u8) (config_info & 0x1);
+
+	/* Verify this device belongs to Bus power or Self power device */
+	if (config_info & BUS_POWER) {	/* bus-power */
+		switch (config_info & BUSPOWER_MASK) {
+		case TS1_PORT | BUS_POWER:
+			cx231xx_Scenario[INDEX_BUSPOWER_DIGITAL_ONLY].speed =
+			    usb_speed;
+			p_pcb_info =
+			    &cx231xx_Scenario[INDEX_BUSPOWER_DIGITAL_ONLY];
+			_current_scenario_idx = INDEX_BUSPOWER_DIGITAL_ONLY;
+			break;
+		case AVDEC_ENABLE | BUS_POWER:
+			cx231xx_Scenario[INDEX_BUSPOWER_ANALOG_ONLY].speed =
+			    usb_speed;
+			p_pcb_info =
+			    &cx231xx_Scenario[INDEX_BUSPOWER_ANALOG_ONLY];
+			_current_scenario_idx = INDEX_BUSPOWER_ANALOG_ONLY;
+			break;
+		case AVDEC_ENABLE | BUS_POWER | TS1_PORT:
+			cx231xx_Scenario[INDEX_BUSPOWER_DIF_ONLY].speed =
+			    usb_speed;
+			p_pcb_info = &cx231xx_Scenario[INDEX_BUSPOWER_DIF_ONLY];
+			_current_scenario_idx = INDEX_BUSPOWER_DIF_ONLY;
+			break;
+		default:
+			dev_err(dev->dev,
+				"bad config in buspower!!!!\nconfig_info=%x\n",
+				config_info & BUSPOWER_MASK);
+			return 1;
+		}
+	} else {		/* self-power */
+
+		switch (config_info & SELFPOWER_MASK) {
+		case TS1_PORT | SELF_POWER:
+			cx231xx_Scenario[INDEX_SELFPOWER_DIGITAL_ONLY].speed =
+			    usb_speed;
+			p_pcb_info =
+			    &cx231xx_Scenario[INDEX_SELFPOWER_DIGITAL_ONLY];
+			_current_scenario_idx = INDEX_SELFPOWER_DIGITAL_ONLY;
+			break;
+		case TS1_TS2_PORT | SELF_POWER:
+			cx231xx_Scenario[INDEX_SELFPOWER_DUAL_DIGITAL].speed =
+			    usb_speed;
+			cx231xx_Scenario[INDEX_SELFPOWER_DUAL_DIGITAL].
+			    ts2_source = ts2_source;
+			p_pcb_info =
+			    &cx231xx_Scenario[INDEX_SELFPOWER_DUAL_DIGITAL];
+			_current_scenario_idx = INDEX_SELFPOWER_DUAL_DIGITAL;
+			break;
+		case AVDEC_ENABLE | SELF_POWER:
+			cx231xx_Scenario[INDEX_SELFPOWER_ANALOG_ONLY].speed =
+			    usb_speed;
+			cx231xx_Scenario[INDEX_SELFPOWER_ANALOG_ONLY].
+			    analog_source = analog_source;
+			p_pcb_info =
+			    &cx231xx_Scenario[INDEX_SELFPOWER_ANALOG_ONLY];
+			_current_scenario_idx = INDEX_SELFPOWER_ANALOG_ONLY;
+			break;
+		case AVDEC_ENABLE | TS1_PORT | SELF_POWER:
+			cx231xx_Scenario[INDEX_SELFPOWER_DUAL].speed =
+			    usb_speed;
+			cx231xx_Scenario[INDEX_SELFPOWER_DUAL].ts1_source =
+			    ts1_source;
+			cx231xx_Scenario[INDEX_SELFPOWER_DUAL].analog_source =
+			    analog_source;
+			p_pcb_info = &cx231xx_Scenario[INDEX_SELFPOWER_DUAL];
+			_current_scenario_idx = INDEX_SELFPOWER_DUAL;
+			break;
+		case AVDEC_ENABLE | TS1_TS2_PORT | SELF_POWER:
+			cx231xx_Scenario[INDEX_SELFPOWER_TRIPLE].speed =
+			    usb_speed;
+			cx231xx_Scenario[INDEX_SELFPOWER_TRIPLE].ts1_source =
+			    ts1_source;
+			cx231xx_Scenario[INDEX_SELFPOWER_TRIPLE].ts2_source =
+			    ts2_source;
+			cx231xx_Scenario[INDEX_SELFPOWER_TRIPLE].analog_source =
+			    analog_source;
+			p_pcb_info = &cx231xx_Scenario[INDEX_SELFPOWER_TRIPLE];
+			_current_scenario_idx = INDEX_SELFPOWER_TRIPLE;
+			break;
+		case AVDEC_ENABLE | TS1VIP_TS2_PORT | SELF_POWER:
+			cx231xx_Scenario[INDEX_SELFPOWER_COMPRESSOR].speed =
+			    usb_speed;
+			cx231xx_Scenario[INDEX_SELFPOWER_COMPRESSOR].
+			    analog_source = analog_source;
+			p_pcb_info =
+			    &cx231xx_Scenario[INDEX_SELFPOWER_COMPRESSOR];
+			_current_scenario_idx = INDEX_SELFPOWER_COMPRESSOR;
+			break;
+		default:
+			dev_err(dev->dev,
+				"bad scenario!!!!!\nconfig_info=%x\n",
+				config_info & SELFPOWER_MASK);
+			return -ENODEV;
+		}
+	}
+
+	dev->current_scenario_idx = _current_scenario_idx;
+
+	memcpy(&dev->current_pcb_config, p_pcb_info,
+		   sizeof(struct pcb_config));
+
+	if (pcb_debug) {
+		dev_info(dev->dev,
+			 "SC(0x00) register = 0x%x\n", config_info);
+		dev_info(dev->dev,
+			 "scenario %d\n",
+			 (dev->current_pcb_config.index) + 1);
+		dev_info(dev->dev,
+			"type=%x\n",
+			 dev->current_pcb_config.type);
+		dev_info(dev->dev,
+			 "mode=%x\n",
+			 dev->current_pcb_config.mode);
+		dev_info(dev->dev,
+			 "speed=%x\n",
+			 dev->current_pcb_config.speed);
+		dev_info(dev->dev,
+			 "ts1_source=%x\n",
+			 dev->current_pcb_config.ts1_source);
+		dev_info(dev->dev,
+			 "ts2_source=%x\n",
+			 dev->current_pcb_config.ts2_source);
+		dev_info(dev->dev,
+			 "analog_source=%x\n",
+			 dev->current_pcb_config.analog_source);
+	}
+
+	return 0;
+}
diff --git a/drivers/media/usb/cx231xx/cx231xx-pcb-cfg.h b/drivers/media/usb/cx231xx/cx231xx-pcb-cfg.h
new file mode 100644
index 0000000..8f00b1d
--- /dev/null
+++ b/drivers/media/usb/cx231xx/cx231xx-pcb-cfg.h
@@ -0,0 +1,226 @@
+/*
+   cx231xx-pcb-cfg.h - driver for Conexant
+		Cx23100/101/102 USB video capture devices
+
+   Copyright (C) 2008 <srinivasa.deevi at conexant dot com>
+
+   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 _PCB_CONFIG_H_
+#define _PCB_CONFIG_H_
+
+#include <linux/init.h>
+#include <linux/module.h>
+
+/***************************************************************************
+				* Class Information *
+***************************************************************************/
+#define CLASS_DEFAULT       0xFF
+
+enum VENDOR_REQUEST_TYPE {
+	/* Set/Get I2C */
+	VRT_SET_I2C0 = 0x0,
+	VRT_SET_I2C1 = 0x1,
+	VRT_SET_I2C2 = 0x2,
+	VRT_GET_I2C0 = 0x4,
+	VRT_GET_I2C1 = 0x5,
+	VRT_GET_I2C2 = 0x6,
+
+	/* Set/Get GPIO */
+	VRT_SET_GPIO = 0x8,
+	VRT_GET_GPIO = 0x9,
+
+	/* Set/Get GPIE */
+	VRT_SET_GPIE = 0xA,
+	VRT_GET_GPIE = 0xB,
+
+	/* Set/Get Register Control/Status */
+	VRT_SET_REGISTER = 0xC,
+	VRT_GET_REGISTER = 0xD,
+
+	/* Get Extended Compat ID Descriptor */
+	VRT_GET_EXTCID_DESC = 0xFF,
+};
+
+enum BYTE_ENABLE_MASK {
+	ENABLE_ONE_BYTE = 0x1,
+	ENABLE_TWE_BYTE = 0x3,
+	ENABLE_THREE_BYTE = 0x7,
+	ENABLE_FOUR_BYTE = 0xF,
+};
+
+#define SPEED_MASK      0x1
+enum USB_SPEED{
+	FULL_SPEED = 0x0,	/* 0: full speed */
+	HIGH_SPEED = 0x1	/* 1: high speed */
+};
+
+#define TS_MASK         0x6
+enum TS_PORT{
+	NO_TS_PORT = 0x0,	/* 2'b00: Neither port used. PCB not a Hybrid,
+				   only offers Analog TV or Video */
+	TS1_PORT = 0x4,		/* 2'b10: TS1 Input (Hybrid mode :
+				Digital or External Analog/Compressed source) */
+	TS1_TS2_PORT = 0x6,	/* 2'b11: TS1 & TS2 Inputs
+				(Dual inputs from Digital and/or
+				External Analog/Compressed sources) */
+	TS1_EXT_CLOCK = 0x6,	/* 2'b11: TS1 & TS2 as selector
+						to external clock */
+	TS1VIP_TS2_PORT = 0x2	/* 2'b01: TS1 used as 656/VIP Output,
+				   TS2 Input (from Compressor) */
+};
+
+#define EAVP_MASK       0x8
+enum EAV_PRESENT{
+	NO_EXTERNAL_AV = 0x0,	/* 0: No External A/V inputs
+						(no need for i2s blcok),
+						Analog Tuner must be present */
+	EXTERNAL_AV = 0x8	/* 1: External A/V inputs
+						present (requires i2s blk) */
+};
+
+#define ATM_MASK        0x30
+enum AT_MODE{
+	DIF_TUNER = 0x30,	/* 2'b11: IF Tuner (requires use of DIF) */
+	BASEBAND_SOUND = 0x20,	/* 2'b10: Baseband Composite &
+						Sound-IF Signals present */
+	NO_TUNER = 0x10		/* 2'b0x: No Analog Tuner present */
+};
+
+#define PWR_SEL_MASK    0x40
+enum POWE_TYPE{
+	SELF_POWER = 0x0,	/* 0: self power */
+	BUS_POWER = 0x40	/* 1: bus power */
+};
+
+enum USB_POWE_TYPE{
+	USB_SELF_POWER = 0,
+	USB_BUS_POWER
+};
+
+#define BO_0_MASK       0x80
+enum AVDEC_STATUS{
+	AVDEC_DISABLE = 0x0,	/* 0: A/V Decoder Disabled */
+	AVDEC_ENABLE = 0x80	/* 1: A/V Decoder Enabled */
+};
+
+#define BO_1_MASK       0x100
+
+#define BUSPOWER_MASK   0xC4	/* for Polaris spec 0.8 */
+#define SELFPOWER_MASK  0x86
+
+/***************************************************************************/
+#define NOT_DECIDE_YET  0xFE
+#define NOT_SUPPORTED   0xFF
+
+/***************************************************************************
+				* for mod field use *
+***************************************************************************/
+#define MOD_DIGITAL     0x1
+#define MOD_ANALOG      0x2
+#define MOD_DIF         0x4
+#define MOD_EXTERNAL    0x8
+#define CAP_ALL_MOD     0x0f
+
+/***************************************************************************
+				* source define *
+***************************************************************************/
+#define SOURCE_DIGITAL          0x1
+#define SOURCE_ANALOG           0x2
+#define SOURCE_DIF              0x4
+#define SOURCE_EXTERNAL         0x8
+#define SOURCE_TS_BDA			0x10
+#define SOURCE_TS_ENCODE		0x20
+#define SOURCE_TS_EXTERNAL	0x40
+
+/***************************************************************************
+				* interface information define *
+***************************************************************************/
+struct INTERFACE_INFO {
+	u8 interrupt_index;
+	u8 ts1_index;
+	u8 ts2_index;
+	u8 audio_index;
+	u8 video_index;
+	u8 vanc_index;		/* VBI */
+	u8 hanc_index;		/* Sliced CC */
+	u8 ir_index;
+};
+
+enum INDEX_INTERFACE_INFO{
+	INDEX_INTERRUPT = 0x0,
+	INDEX_TS1,
+	INDEX_TS2,
+	INDEX_AUDIO,
+	INDEX_VIDEO,
+	INDEX_VANC,
+	INDEX_HANC,
+	INDEX_IR,
+};
+
+/***************************************************************************
+				* configuration information define *
+***************************************************************************/
+struct CONFIG_INFO {
+	u8 config_index;
+	struct INTERFACE_INFO interface_info;
+};
+
+struct pcb_config {
+	u8 index;
+	u8 type;		/* bus power or self power,
+					   self power--0, bus_power--1 */
+	u8 speed;		/* usb speed, 2.0--1, 1.1--0 */
+	u8 mode;		/* digital , anlog, dif or external A/V */
+	u32 ts1_source;		/* three source -- BDA,External,encode */
+	u32 ts2_source;
+	u32 analog_source;
+	u8 digital_index;	/* bus-power used */
+	u8 analog_index;	/* bus-power used */
+	u8 dif_index;		/* bus-power used */
+	u8 external_index;	/* bus-power used */
+	u8 config_num;		/* current config num, 0,1,2,
+						   for self-power, always 0 */
+	struct CONFIG_INFO hs_config_info[3];
+	struct CONFIG_INFO fs_config_info[3];
+};
+
+enum INDEX_PCB_CONFIG{
+	INDEX_SELFPOWER_DIGITAL_ONLY = 0x0,
+	INDEX_SELFPOWER_DUAL_DIGITAL,
+	INDEX_SELFPOWER_ANALOG_ONLY,
+	INDEX_SELFPOWER_DUAL,
+	INDEX_SELFPOWER_TRIPLE,
+	INDEX_SELFPOWER_COMPRESSOR,
+	INDEX_BUSPOWER_DIGITAL_ONLY,
+	INDEX_BUSPOWER_ANALOG_ONLY,
+	INDEX_BUSPOWER_DIF_ONLY,
+	INDEX_BUSPOWER_EXTERNAL_ONLY,
+	INDEX_BUSPOWER_EXTERNAL_ANALOG,
+	INDEX_BUSPOWER_EXTERNAL_DIF,
+	INDEX_BUSPOWER_EXTERNAL_DIGITAL,
+	INDEX_BUSPOWER_DIGITAL_ANALOG,
+	INDEX_BUSPOWER_DIGITAL_DIF,
+	INDEX_BUSPOWER_DIGITAL_ANALOG_EXTERNAL,
+	INDEX_BUSPOWER_DIGITAL_DIF_EXTERNAL,
+};
+
+/***************************************************************************/
+struct cx231xx;
+
+int initialize_cx231xx(struct cx231xx *p_dev);
+
+#endif
diff --git a/drivers/media/usb/cx231xx/cx231xx-reg.h b/drivers/media/usb/cx231xx/cx231xx-reg.h
new file mode 100644
index 0000000..db5af8d
--- /dev/null
+++ b/drivers/media/usb/cx231xx/cx231xx-reg.h
@@ -0,0 +1,1564 @@
+/*
+   cx231xx-reg.h - driver for Conexant Cx23100/101/102
+	       USB video capture devices
+
+   Copyright (C) 2008 <srinivasa.deevi at conexant dot com>
+
+   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 _CX231XX_REG_H
+#define _CX231XX_REG_H
+
+/*****************************************************************************
+				* VBI codes *
+*****************************************************************************/
+
+#define SAV_ACTIVE_VIDEO_FIELD1		0x80
+#define EAV_ACTIVE_VIDEO_FIELD1		0x90
+
+#define SAV_ACTIVE_VIDEO_FIELD2		0xc0
+#define EAV_ACTIVE_VIDEO_FIELD2		0xd0
+
+#define SAV_VBLANK_FIELD1		0xa0
+#define EAV_VBLANK_FIELD1		0xb0
+
+#define SAV_VBLANK_FIELD2		0xe0
+#define EAV_VBLANK_FIELD2		0xf0
+
+#define SAV_VBI_FIELD1			0x20
+#define EAV_VBI_FIELD1			0x30
+
+#define SAV_VBI_FIELD2			0x60
+#define EAV_VBI_FIELD2			0x70
+
+/*****************************************************************************/
+/* Audio ADC Registers */
+#define CH_PWR_CTRL1			0x0000000e
+#define CH_PWR_CTRL2			0x0000000f
+/*****************************************************************************/
+
+#define      HOST_REG1                0x000
+#define      FLD_FORCE_CHIP_SEL       0x80
+#define      FLD_AUTO_INC_DIS         0x20
+#define      FLD_PREFETCH_EN          0x10
+/* Reserved [2:3] */
+#define      FLD_DIGITAL_PWR_DN       0x02
+#define      FLD_SLEEP                0x01
+
+/*****************************************************************************/
+#define      HOST_REG2                0x001
+
+/*****************************************************************************/
+#define      HOST_REG3                0x002
+
+/*****************************************************************************/
+/* added for polaris */
+#define      GPIO_PIN_CTL0            0x3
+#define      GPIO_PIN_CTL1            0x4
+#define      GPIO_PIN_CTL2            0x5
+#define      GPIO_PIN_CTL3            0x6
+#define      TS1_PIN_CTL0             0x7
+#define      TS1_PIN_CTL1             0x8
+/*****************************************************************************/
+
+#define      FLD_CLK_IN_EN            0x80
+#define      FLD_XTAL_CTRL            0x70
+#define      FLD_BB_CLK_MODE          0x0C
+#define      FLD_REF_DIV_PLL          0x02
+#define      FLD_REF_SEL_PLL1         0x01
+
+/*****************************************************************************/
+#define      CHIP_CTRL                0x100
+/* Reserved [27] */
+/* Reserved [31:21] */
+#define      FLD_CHIP_ACFG_DIS        0x00100000
+/* Reserved [19] */
+#define      FLD_DUAL_MODE_ADC2       0x00040000
+#define      FLD_SIF_EN               0x00020000
+#define      FLD_SOFT_RST             0x00010000
+#define      FLD_DEVICE_ID            0x0000ffff
+
+/*****************************************************************************/
+#define      AFE_CTRL                 0x104
+#define      AFE_CTRL_C2HH_SRC_CTRL   0x104
+#define      FLD_DIF_OUT_SEL          0xc0000000
+#define      FLD_AUX_PLL_CLK_ALT_SEL  0x3c000000
+#define      FLD_UV_ORDER_MODE        0x02000000
+#define      FLD_FUNC_MODE            0x01800000
+#define      FLD_ROT1_PHASE_CTL       0x007f8000
+#define      FLD_AUD_IN_SEL           0x00004000
+#define      FLD_LUMA_IN_SEL          0x00002000
+#define      FLD_CHROMA_IN_SEL        0x00001000
+/* reserve [11:10] */
+#define      FLD_INV_SPEC_DIS         0x00000200
+#define      FLD_VGA_SEL_CH3          0x00000100
+#define      FLD_VGA_SEL_CH2          0x00000080
+#define      FLD_VGA_SEL_CH1          0x00000040
+#define      FLD_DCR_BYP_CH1          0x00000020
+#define      FLD_DCR_BYP_CH2          0x00000010
+#define      FLD_DCR_BYP_CH3          0x00000008
+#define      FLD_EN_12DB_CH3          0x00000004
+#define      FLD_EN_12DB_CH2          0x00000002
+#define      FLD_EN_12DB_CH1          0x00000001
+
+/* redefine in Cx231xx */
+/*****************************************************************************/
+#define      DC_CTRL1                 0x108
+/* reserve [31:30] */
+#define      FLD_CLAMP_LVL_CH1        0x3fff8000
+#define      FLD_CLAMP_LVL_CH2        0x00007fff
+/*****************************************************************************/
+
+/*****************************************************************************/
+#define      DC_CTRL2                 0x10c
+/* reserve [31:28] */
+#define      FLD_CLAMP_LVL_CH3        0x00fffe00
+#define      FLD_CLAMP_WIND_LENTH     0x000001e0
+#define      FLD_C2HH_SAT_MIN         0x0000001e
+#define      FLD_FLT_BYP_SEL          0x00000001
+/*****************************************************************************/
+
+/*****************************************************************************/
+#define      DC_CTRL3                 0x110
+/* reserve [31:16] */
+#define      FLD_ERR_GAIN_CTL         0x00070000
+#define      FLD_LPF_MIN              0x0000ffff
+/*****************************************************************************/
+
+/*****************************************************************************/
+#define      DC_CTRL4                 0x114
+/* reserve [31:31] */
+#define      FLD_INTG_CH1             0x7fffffff
+/*****************************************************************************/
+
+/*****************************************************************************/
+#define      DC_CTRL5                 0x118
+/* reserve [31:31] */
+#define      FLD_INTG_CH2             0x7fffffff
+/*****************************************************************************/
+
+/*****************************************************************************/
+#define      DC_CTRL6                 0x11c
+/* reserve [31:31] */
+#define      FLD_INTG_CH3             0x7fffffff
+/*****************************************************************************/
+
+/*****************************************************************************/
+#define      PIN_CTRL                 0x120
+#define      FLD_OEF_AGC_RF           0x00000001
+#define      FLD_OEF_AGC_IFVGA        0x00000002
+#define      FLD_OEF_AGC_IF           0x00000004
+#define      FLD_REG_BO_PUD           0x80000000
+#define      FLD_IR_IRQ_STAT          0x40000000
+#define      FLD_AUD_IRQ_STAT         0x20000000
+#define      FLD_VID_IRQ_STAT         0x10000000
+/* Reserved [27:26] */
+#define      FLD_IRQ_N_OUT_EN         0x02000000
+#define      FLD_IRQ_N_POLAR          0x01000000
+/* Reserved [23:6] */
+#define      FLD_OE_AUX_PLL_CLK       0x00000020
+#define      FLD_OE_I2S_BCLK          0x00000010
+#define      FLD_OE_I2S_WCLK          0x00000008
+#define      FLD_OE_AGC_IF            0x00000004
+#define      FLD_OE_AGC_IFVGA         0x00000002
+#define      FLD_OE_AGC_RF            0x00000001
+
+/*****************************************************************************/
+#define      AUD_IO_CTRL              0x124
+/* Reserved [31:8] */
+#define      FLD_I2S_PORT_DIR         0x00000080
+#define      FLD_I2S_OUT_SRC          0x00000040
+#define      FLD_AUD_CHAN3_SRC        0x00000030
+#define      FLD_AUD_CHAN2_SRC        0x0000000c
+#define      FLD_AUD_CHAN1_SRC        0x00000003
+
+/*****************************************************************************/
+#define      AUD_LOCK1                0x128
+#define      FLD_AUD_LOCK_KI_SHIFT    0xc0000000
+#define      FLD_AUD_LOCK_KD_SHIFT    0x30000000
+/* Reserved [27:25] */
+#define      FLD_EN_AV_LOCK           0x01000000
+#define      FLD_VID_COUNT            0x00ffffff
+
+/*****************************************************************************/
+#define      AUD_LOCK2                0x12c
+#define      FLD_AUD_LOCK_KI_MULT     0xf0000000
+#define      FLD_AUD_LOCK_KD_MULT     0x0F000000
+/* Reserved [23:22] */
+#define      FLD_AUD_LOCK_FREQ_SHIFT  0x00300000
+#define      FLD_AUD_COUNT            0x000fffff
+
+/*****************************************************************************/
+#define      AFE_DIAG_CTRL1           0x134
+/* Reserved [31:16] */
+#define      FLD_CUV_DLY_LENGTH       0x0000ff00
+#define      FLD_YC_DLY_LENGTH        0x000000ff
+
+/*****************************************************************************/
+/* Poalris redefine */
+#define      AFE_DIAG_CTRL3           0x138
+/* Reserved [31:26] */
+#define      FLD_AUD_DUAL_FLAG_POL    0x02000000
+#define      FLD_VID_DUAL_FLAG_POL    0x01000000
+/* Reserved [23:23] */
+#define      FLD_COL_CLAMP_DIS_CH1    0x00400000
+#define      FLD_COL_CLAMP_DIS_CH2    0x00200000
+#define      FLD_COL_CLAMP_DIS_CH3    0x00100000
+
+#define      TEST_CTRL1               0x144
+/* Reserved [31:29] */
+#define      FLD_LBIST_EN             0x10000000
+/* Reserved [27:10] */
+#define      FLD_FI_BIST_INTR_R       0x0000200
+#define      FLD_FI_BIST_INTR_L       0x0000100
+#define      FLD_BIST_FAIL_AUD_PLL    0x0000080
+#define      FLD_BIST_INTR_AUD_PLL    0x0000040
+#define      FLD_BIST_FAIL_VID_PLL    0x0000020
+#define      FLD_BIST_INTR_VID_PLL    0x0000010
+/* Reserved [3:1] */
+#define      FLD_CIR_TEST_DIS         0x00000001
+
+/*****************************************************************************/
+#define      TEST_CTRL2               0x148
+#define      FLD_TSXCLK_POL_CTL       0x80000000
+#define      FLD_ISO_CTL_SEL          0x40000000
+#define      FLD_ISO_CTL_EN           0x20000000
+#define      FLD_BIST_DEBUGZ          0x10000000
+#define      FLD_AUD_BIST_TEST_H      0x0f000000
+/* Reserved [23:22] */
+#define      FLD_FLTRN_BIST_TEST_H    0x00020000
+#define      FLD_VID_BIST_TEST_H      0x00010000
+/* Reserved [19:17] */
+#define      FLD_BIST_TEST_H          0x00010000
+/* Reserved [15:13] */
+#define      FLD_TAB_EN               0x00001000
+/* Reserved [11:0] */
+
+/*****************************************************************************/
+#define      BIST_STAT                0x14c
+#define      FLD_AUD_BIST_FAIL_H      0xfff00000
+#define      FLD_FLTRN_BIST_FAIL_H    0x00180000
+#define      FLD_VID_BIST_FAIL_H      0x00070000
+#define      FLD_AUD_BIST_TST_DONE    0x0000fff0
+#define      FLD_FLTRN_BIST_TST_DONE  0x00000008
+#define      FLD_VID_BIST_TST_DONE    0x00000007
+
+/*****************************************************************************/
+/* DirectIF registers definition have been moved to DIF_reg.h                */
+/*****************************************************************************/
+#define      MODE_CTRL                0x400
+#define      FLD_AFD_PAL60_DIS        0x20000000
+#define      FLD_AFD_FORCE_SECAM      0x10000000
+#define      FLD_AFD_FORCE_PALNC      0x08000000
+#define      FLD_AFD_FORCE_PAL        0x04000000
+#define      FLD_AFD_PALM_SEL         0x03000000
+#define      FLD_CKILL_MODE           0x00300000
+#define      FLD_COMB_NOTCH_MODE      0x00c00000       /* bit[19:18] */
+#define      FLD_CLR_LOCK_STAT        0x00020000
+#define      FLD_FAST_LOCK_MD         0x00010000
+#define      FLD_WCEN                 0x00008000
+#define      FLD_CAGCEN               0x00004000
+#define      FLD_CKILLEN              0x00002000
+#define      FLD_AUTO_SC_LOCK         0x00001000
+#define      FLD_MAN_SC_FAST_LOCK     0x00000800
+#define      FLD_INPUT_MODE           0x00000600
+#define      FLD_AFD_ACQUIRE          0x00000100
+#define      FLD_AFD_NTSC_SEL         0x00000080
+#define      FLD_AFD_PAL_SEL          0x00000040
+#define      FLD_ACFG_DIS             0x00000020
+#define      FLD_SQ_PIXEL             0x00000010
+#define      FLD_VID_FMT_SEL          0x0000000f
+
+/*****************************************************************************/
+#define      OUT_CTRL1                0x404
+#define      FLD_POLAR                0x7f000000
+/* Reserved [23] */
+#define      FLD_RND_MODE             0x00600000
+#define      FLD_VIPCLAMP_EN          0x00100000
+#define      FLD_VIPBLANK_EN          0x00080000
+#define      FLD_VIP_OPT_AL           0x00040000
+#define      FLD_IDID0_SOURCE         0x00020000
+#define      FLD_DCMODE               0x00010000
+#define      FLD_CLK_GATING           0x0000c000
+#define      FLD_CLK_INVERT           0x00002000
+#define      FLD_HSFMT                0x00001000
+#define      FLD_VALIDFMT             0x00000800
+#define      FLD_ACTFMT               0x00000400
+#define      FLD_SWAPRAW              0x00000200
+#define      FLD_CLAMPRAW_EN          0x00000100
+#define      FLD_BLUE_FIELD_EN        0x00000080
+#define      FLD_BLUE_FIELD_ACT       0x00000040
+#define      FLD_TASKBIT_VAL          0x00000020
+#define      FLD_ANC_DATA_EN          0x00000010
+#define      FLD_VBIHACTRAW_EN        0x00000008
+#define      FLD_MODE10B              0x00000004
+#define      FLD_OUT_MODE             0x00000003
+
+/*****************************************************************************/
+#define      OUT_CTRL2                0x408
+#define      FLD_AUD_GRP              0xc0000000
+#define      FLD_SAMPLE_RATE          0x30000000
+#define      FLD_AUD_ANC_EN           0x08000000
+#define      FLD_EN_C                 0x04000000
+#define      FLD_EN_B                 0x02000000
+#define      FLD_EN_A                 0x01000000
+/* Reserved [23:20] */
+#define      FLD_IDID1_LSB            0x000c0000
+#define      FLD_IDID0_LSB            0x00030000
+#define      FLD_IDID1_MSB            0x0000ff00
+#define      FLD_IDID0_MSB            0x000000ff
+
+/*****************************************************************************/
+#define      GEN_STAT                 0x40c
+#define      FLD_VCR_DETECT           0x00800000
+#define      FLD_SPECIAL_PLAY_N       0x00400000
+#define      FLD_VPRES                0x00200000
+#define      FLD_AGC_LOCK             0x00100000
+#define      FLD_CSC_LOCK             0x00080000
+#define      FLD_VLOCK                0x00040000
+#define      FLD_SRC_LOCK             0x00020000
+#define      FLD_HLOCK                0x00010000
+#define      FLD_VSYNC_N              0x00008000
+#define      FLD_SRC_FIFO_UFLOW       0x00004000
+#define      FLD_SRC_FIFO_OFLOW       0x00002000
+#define      FLD_FIELD                0x00001000
+#define      FLD_AFD_FMT_STAT         0x00000f00
+#define      FLD_MV_TYPE2_PAIR        0x00000080
+#define      FLD_MV_T3CS              0x00000040
+#define      FLD_MV_CS                0x00000020
+#define      FLD_MV_PSP               0x00000010
+/* Reserved [3] */
+#define      FLD_MV_CDAT              0x00000003
+
+/*****************************************************************************/
+#define      INT_STAT_MASK            0x410
+#define      FLD_COMB_3D_FIFO_MSK     0x80000000
+#define      FLD_WSS_DAT_AVAIL_MSK    0x40000000
+#define      FLD_GS2_DAT_AVAIL_MSK    0x20000000
+#define      FLD_GS1_DAT_AVAIL_MSK    0x10000000
+#define      FLD_CC_DAT_AVAIL_MSK     0x08000000
+#define      FLD_VPRES_CHANGE_MSK     0x04000000
+#define      FLD_MV_CHANGE_MSK        0x02000000
+#define      FLD_END_VBI_EVEN_MSK     0x01000000
+#define      FLD_END_VBI_ODD_MSK      0x00800000
+#define      FLD_FMT_CHANGE_MSK       0x00400000
+#define      FLD_VSYNC_TRAIL_MSK      0x00200000
+#define      FLD_HLOCK_CHANGE_MSK     0x00100000
+#define      FLD_VLOCK_CHANGE_MSK     0x00080000
+#define      FLD_CSC_LOCK_CHANGE_MSK  0x00040000
+#define      FLD_SRC_FIFO_UFLOW_MSK   0x00020000
+#define      FLD_SRC_FIFO_OFLOW_MSK   0x00010000
+#define      FLD_COMB_3D_FIFO_STAT    0x00008000
+#define      FLD_WSS_DAT_AVAIL_STAT   0x00004000
+#define      FLD_GS2_DAT_AVAIL_STAT   0x00002000
+#define      FLD_GS1_DAT_AVAIL_STAT   0x00001000
+#define      FLD_CC_DAT_AVAIL_STAT    0x00000800
+#define      FLD_VPRES_CHANGE_STAT    0x00000400
+#define      FLD_MV_CHANGE_STAT       0x00000200
+#define      FLD_END_VBI_EVEN_STAT    0x00000100
+#define      FLD_END_VBI_ODD_STAT     0x00000080
+#define      FLD_FMT_CHANGE_STAT      0x00000040
+#define      FLD_VSYNC_TRAIL_STAT     0x00000020
+#define      FLD_HLOCK_CHANGE_STAT    0x00000010
+#define      FLD_VLOCK_CHANGE_STAT    0x00000008
+#define      FLD_CSC_LOCK_CHANGE_STAT 0x00000004
+#define      FLD_SRC_FIFO_UFLOW_STAT  0x00000002
+#define      FLD_SRC_FIFO_OFLOW_STAT  0x00000001
+
+/*****************************************************************************/
+#define      LUMA_CTRL                0x414
+#define      BRIGHTNESS_CTRL_BYTE     0x414
+#define      CONTRAST_CTRL_BYTE       0x415
+#define      LUMA_CTRL_BYTE_3         0x416
+#define      FLD_LUMA_CORE_SEL        0x00c00000
+#define      FLD_RANGE                0x00300000
+/* Reserved [19] */
+#define      FLD_PEAK_EN              0x00040000
+#define      FLD_PEAK_SEL             0x00030000
+#define      FLD_CNTRST               0x0000ff00
+#define      FLD_BRITE                0x000000ff
+
+/*****************************************************************************/
+#define      HSCALE_CTRL              0x418
+#define      FLD_HFILT                0x03000000
+#define      FLD_HSCALE               0x00ffffff
+
+/*****************************************************************************/
+#define      VSCALE_CTRL              0x41c
+#define      FLD_LINE_AVG_DIS         0x01000000
+/* Reserved [23:20] */
+#define      FLD_VS_INTRLACE          0x00080000
+#define      FLD_VFILT                0x00070000
+/* Reserved [15:13] */
+#define      FLD_VSCALE               0x00001fff
+
+/*****************************************************************************/
+#define      CHROMA_CTRL              0x420
+#define      USAT_CTRL_BYTE           0x420
+#define      VSAT_CTRL_BYTE           0x421
+#define      HUE_CTRL_BYTE            0x422
+#define      FLD_C_LPF_EN             0x20000000
+#define      FLD_CHR_DELAY            0x1c000000
+#define      FLD_C_CORE_SEL           0x03000000
+#define      FLD_HUE                  0x00ff0000
+#define      FLD_VSAT                 0x0000ff00
+#define      FLD_USAT                 0x000000ff
+
+/*****************************************************************************/
+#define      VBI_LINE_CTRL1           0x424
+#define      FLD_VBI_MD_LINE4         0xff000000
+#define      FLD_VBI_MD_LINE3         0x00ff0000
+#define      FLD_VBI_MD_LINE2         0x0000ff00
+#define      FLD_VBI_MD_LINE1         0x000000ff
+
+/*****************************************************************************/
+#define      VBI_LINE_CTRL2           0x428
+#define      FLD_VBI_MD_LINE8         0xff000000
+#define      FLD_VBI_MD_LINE7         0x00ff0000
+#define      FLD_VBI_MD_LINE6         0x0000ff00
+#define      FLD_VBI_MD_LINE5         0x000000ff
+
+/*****************************************************************************/
+#define      VBI_LINE_CTRL3           0x42c
+#define      FLD_VBI_MD_LINE12        0xff000000
+#define      FLD_VBI_MD_LINE11        0x00ff0000
+#define      FLD_VBI_MD_LINE10        0x0000ff00
+#define      FLD_VBI_MD_LINE9         0x000000ff
+
+/*****************************************************************************/
+#define      VBI_LINE_CTRL4           0x430
+#define      FLD_VBI_MD_LINE16        0xff000000
+#define      FLD_VBI_MD_LINE15        0x00ff0000
+#define      FLD_VBI_MD_LINE14        0x0000ff00
+#define      FLD_VBI_MD_LINE13        0x000000ff
+
+/*****************************************************************************/
+#define      VBI_LINE_CTRL5           0x434
+#define      FLD_VBI_MD_LINE17        0x000000ff
+
+/*****************************************************************************/
+#define      VBI_FC_CFG               0x438
+#define      FLD_FC_ALT2              0xff000000
+#define      FLD_FC_ALT1              0x00ff0000
+#define      FLD_FC_ALT2_TYPE         0x0000f000
+#define      FLD_FC_ALT1_TYPE         0x00000f00
+/* Reserved [7:1] */
+#define      FLD_FC_SEARCH_MODE       0x00000001
+
+/*****************************************************************************/
+#define      VBI_MISC_CFG1            0x43c
+#define      FLD_TTX_PKTADRU          0xfff00000
+#define      FLD_TTX_PKTADRL          0x000fff00
+/* Reserved [7:6] */
+#define      FLD_MOJI_PACK_DIS        0x00000020
+#define      FLD_VPS_DEC_DIS          0x00000010
+#define      FLD_CRI_MARG_SCALE       0x0000000c
+#define      FLD_EDGE_RESYNC_EN       0x00000002
+#define      FLD_ADAPT_SLICE_DIS      0x00000001
+
+/*****************************************************************************/
+#define      VBI_MISC_CFG2            0x440
+#define      FLD_HAMMING_TYPE         0x0f000000
+/* Reserved [23:20] */
+#define      FLD_WSS_FIFO_RST         0x00080000
+#define      FLD_GS2_FIFO_RST         0x00040000
+#define      FLD_GS1_FIFO_RST         0x00020000
+#define      FLD_CC_FIFO_RST          0x00010000
+/* Reserved [15:12] */
+#define      FLD_VBI3_SDID            0x00000f00
+#define      FLD_VBI2_SDID            0x000000f0
+#define      FLD_VBI1_SDID            0x0000000f
+
+/*****************************************************************************/
+#define      VBI_PAY1                 0x444
+#define      FLD_GS1_FIFO_DAT         0xFF000000
+#define      FLD_GS1_STAT             0x00FF0000
+#define      FLD_CC_FIFO_DAT          0x0000FF00
+#define      FLD_CC_STAT              0x000000FF
+
+/*****************************************************************************/
+#define      VBI_PAY2                 0x448
+#define      FLD_WSS_FIFO_DAT         0xff000000
+#define      FLD_WSS_STAT             0x00ff0000
+#define      FLD_GS2_FIFO_DAT         0x0000ff00
+#define      FLD_GS2_STAT             0x000000ff
+
+/*****************************************************************************/
+#define      VBI_CUST1_CFG1           0x44c
+/* Reserved [31] */
+#define      FLD_VBI1_CRIWIN          0x7f000000
+#define      FLD_VBI1_SLICE_DIST      0x00f00000
+#define      FLD_VBI1_BITINC          0x000fff00
+#define      FLD_VBI1_HDELAY          0x000000ff
+
+/*****************************************************************************/
+#define      VBI_CUST1_CFG2           0x450
+#define      FLD_VBI1_FC_LENGTH       0x1f000000
+#define      FLD_VBI1_FRAME_CODE      0x00ffffff
+
+/*****************************************************************************/
+#define      VBI_CUST1_CFG3           0x454
+#define      FLD_VBI1_HAM_EN          0x80000000
+#define      FLD_VBI1_FIFO_MODE       0x70000000
+#define      FLD_VBI1_FORMAT_TYPE     0x0f000000
+#define      FLD_VBI1_PAYLD_LENGTH    0x00ff0000
+#define      FLD_VBI1_CRI_LENGTH      0x0000f000
+#define      FLD_VBI1_CRI_MARGIN      0x00000f00
+#define      FLD_VBI1_CRI_TIME        0x000000ff
+
+/*****************************************************************************/
+#define      VBI_CUST2_CFG1           0x458
+/* Reserved [31] */
+#define      FLD_VBI2_CRIWIN          0x7f000000
+#define      FLD_VBI2_SLICE_DIST      0x00f00000
+#define      FLD_VBI2_BITINC          0x000fff00
+#define      FLD_VBI2_HDELAY          0x000000ff
+
+/*****************************************************************************/
+#define      VBI_CUST2_CFG2           0x45c
+#define      FLD_VBI2_FC_LENGTH       0x1f000000
+#define      FLD_VBI2_FRAME_CODE      0x00ffffff
+
+/*****************************************************************************/
+#define      VBI_CUST2_CFG3           0x460
+#define      FLD_VBI2_HAM_EN          0x80000000
+#define      FLD_VBI2_FIFO_MODE       0x70000000
+#define      FLD_VBI2_FORMAT_TYPE     0x0f000000
+#define      FLD_VBI2_PAYLD_LENGTH    0x00ff0000
+#define      FLD_VBI2_CRI_LENGTH      0x0000f000
+#define      FLD_VBI2_CRI_MARGIN      0x00000f00
+#define      FLD_VBI2_CRI_TIME        0x000000ff
+
+/*****************************************************************************/
+#define      VBI_CUST3_CFG1           0x464
+/* Reserved [31] */
+#define      FLD_VBI3_CRIWIN          0x7f000000
+#define      FLD_VBI3_SLICE_DIST      0x00f00000
+#define      FLD_VBI3_BITINC          0x000fff00
+#define      FLD_VBI3_HDELAY          0x000000ff
+
+/*****************************************************************************/
+#define      VBI_CUST3_CFG2           0x468
+#define      FLD_VBI3_FC_LENGTH       0x1f000000
+#define      FLD_VBI3_FRAME_CODE      0x00ffffff
+
+/*****************************************************************************/
+#define      VBI_CUST3_CFG3           0x46c
+#define      FLD_VBI3_HAM_EN          0x80000000
+#define      FLD_VBI3_FIFO_MODE       0x70000000
+#define      FLD_VBI3_FORMAT_TYPE     0x0f000000
+#define      FLD_VBI3_PAYLD_LENGTH    0x00ff0000
+#define      FLD_VBI3_CRI_LENGTH      0x0000f000
+#define      FLD_VBI3_CRI_MARGIN      0x00000f00
+#define      FLD_VBI3_CRI_TIME        0x000000ff
+
+/*****************************************************************************/
+#define      HORIZ_TIM_CTRL           0x470
+#define      FLD_BGDEL_CNT            0xff000000
+/* Reserved [23:22] */
+#define      FLD_HACTIVE_CNT          0x003ff000
+/* Reserved [11:10] */
+#define      FLD_HBLANK_CNT           0x000003ff
+
+/*****************************************************************************/
+#define      VERT_TIM_CTRL            0x474
+#define      FLD_V656BLANK_CNT        0xff000000
+/* Reserved [23:22] */
+#define      FLD_VACTIVE_CNT          0x003ff000
+/* Reserved [11:10] */
+#define      FLD_VBLANK_CNT           0x000003ff
+
+/*****************************************************************************/
+#define      SRC_COMB_CFG             0x478
+#define      FLD_CCOMB_2LN_CHECK      0x80000000
+#define      FLD_CCOMB_3LN_EN         0x40000000
+#define      FLD_CCOMB_2LN_EN         0x20000000
+#define      FLD_CCOMB_3D_EN          0x10000000
+/* Reserved [27] */
+#define      FLD_LCOMB_3LN_EN         0x04000000
+#define      FLD_LCOMB_2LN_EN         0x02000000
+#define      FLD_LCOMB_3D_EN          0x01000000
+#define      FLD_LUMA_LPF_SEL         0x00c00000
+#define      FLD_UV_LPF_SEL           0x00300000
+#define      FLD_BLEND_SLOPE          0x000f0000
+#define      FLD_CCOMB_REDUCE_EN      0x00008000
+/* Reserved [14:10] */
+#define      FLD_SRC_DECIM_RATIO      0x000003ff
+
+/*****************************************************************************/
+#define      CHROMA_VBIOFF_CFG        0x47c
+#define      FLD_VBI_VOFFSET          0x1f000000
+/* Reserved [23:20] */
+#define      FLD_SC_STEP              0x000fffff
+
+/*****************************************************************************/
+#define      FIELD_COUNT              0x480
+#define      FLD_FIELD_COUNT_FLD      0x000003ff
+
+/*****************************************************************************/
+#define      MISC_TIM_CTRL            0x484
+#define      FLD_DEBOUNCE_COUNT       0xc0000000
+#define      FLD_VT_LINE_CNT_HYST     0x30000000
+/* Reserved [27] */
+#define      FLD_AFD_STAT             0x07ff0000
+#define      FLD_VPRES_VERT_EN        0x00008000
+/* Reserved [14:12] */
+#define      FLD_HR32                 0x00000800
+#define      FLD_TDALGN               0x00000400
+#define      FLD_TDFIELD              0x00000200
+/* Reserved [8:6] */
+#define      FLD_TEMPDEC              0x0000003f
+
+/*****************************************************************************/
+#define      DFE_CTRL1                0x488
+#define      FLD_CLAMP_AUTO_EN        0x80000000
+#define      FLD_AGC_AUTO_EN          0x40000000
+#define      FLD_VGA_CRUSH_EN         0x20000000
+#define      FLD_VGA_AUTO_EN          0x10000000
+#define      FLD_VBI_GATE_EN          0x08000000
+#define      FLD_CLAMP_LEVEL          0x07000000
+/* Reserved [23:22] */
+#define      FLD_CLAMP_SKIP_CNT       0x00300000
+#define      FLD_AGC_GAIN             0x000fff00
+/* Reserved [7:6] */
+#define      FLD_VGA_GAIN             0x0000003f
+
+/*****************************************************************************/
+#define      DFE_CTRL2                0x48c
+#define      FLD_VGA_ACQUIRE_RANGE    0x00ff0000
+#define      FLD_VGA_TRACK_RANGE      0x0000ff00
+#define      FLD_VGA_SYNC             0x000000ff
+
+/*****************************************************************************/
+#define      DFE_CTRL3                0x490
+#define      FLD_BP_PERCENT           0xff000000
+#define      FLD_DFT_THRESHOLD        0x00ff0000
+/* Reserved [15:12] */
+#define      FLD_SYNC_WIDTH_SEL       0x00000600
+#define      FLD_BP_LOOP_GAIN         0x00000300
+#define      FLD_SYNC_LOOP_GAIN       0x000000c0
+/* Reserved [5:4] */
+#define      FLD_AGC_LOOP_GAIN        0x0000000c
+#define      FLD_DCC_LOOP_GAIN        0x00000003
+
+/*****************************************************************************/
+#define      PLL_CTRL                 0x494
+#define      FLD_PLL_KD               0xff000000
+#define      FLD_PLL_KI               0x00ff0000
+#define      FLD_PLL_MAX_OFFSET       0x0000ffff
+
+/*****************************************************************************/
+#define      HTL_CTRL                 0x498
+/* Reserved [31:24] */
+#define      FLD_AUTO_LOCK_SPD        0x00080000
+#define      FLD_MAN_FAST_LOCK        0x00040000
+#define      FLD_HTL_15K_EN           0x00020000
+#define      FLD_HTL_500K_EN          0x00010000
+#define      FLD_HTL_KD               0x0000ff00
+#define      FLD_HTL_KI               0x000000ff
+
+/*****************************************************************************/
+#define      COMB_CTRL                0x49c
+#define      FLD_COMB_PHASE_LIMIT     0xff000000
+#define      FLD_CCOMB_ERR_LIMIT      0x00ff0000
+#define      FLD_LUMA_THRESHOLD       0x0000ff00
+#define      FLD_LCOMB_ERR_LIMIT      0x000000ff
+
+/*****************************************************************************/
+#define      CRUSH_CTRL               0x4a0
+#define      FLD_WTW_EN               0x00400000
+#define      FLD_CRUSH_FREQ           0x00200000
+#define      FLD_MAJ_SEL_EN           0x00100000
+#define      FLD_MAJ_SEL              0x000c0000
+/* Reserved [17:15] */
+#define      FLD_SYNC_TIP_REDUCE      0x00007e00
+/* Reserved [8:6] */
+#define      FLD_SYNC_TIP_INC         0x0000003f
+
+/*****************************************************************************/
+#define      SOFT_RST_CTRL            0x4a4
+#define      FLD_VD_SOFT_RST          0x00008000
+/* Reserved [14:12] */
+#define      FLD_REG_RST_MSK          0x00000800
+#define      FLD_VOF_RST_MSK          0x00000400
+#define      FLD_MVDET_RST_MSK        0x00000200
+#define      FLD_VBI_RST_MSK          0x00000100
+#define      FLD_SCALE_RST_MSK        0x00000080
+#define      FLD_CHROMA_RST_MSK       0x00000040
+#define      FLD_LUMA_RST_MSK         0x00000020
+#define      FLD_VTG_RST_MSK          0x00000010
+#define      FLD_YCSEP_RST_MSK        0x00000008
+#define      FLD_SRC_RST_MSK          0x00000004
+#define      FLD_DFE_RST_MSK          0x00000002
+/* Reserved [0] */
+
+/*****************************************************************************/
+#define      MV_DT_CTRL1              0x4a8
+/* Reserved [31:29] */
+#define      FLD_PSP_STOP_LINE        0x1f000000
+/* Reserved [23:21] */
+#define      FLD_PSP_STRT_LINE        0x001f0000
+/* Reserved [15] */
+#define      FLD_PSP_LLIMW            0x00007f00
+/* Reserved [7] */
+#define      FLD_PSP_ULIMW            0x0000007f
+
+/*****************************************************************************/
+#define      MV_DT_CTRL2              0x4aC
+#define      FLD_CS_STOPWIN           0xff000000
+#define      FLD_CS_STRTWIN           0x00ff0000
+#define      FLD_CS_WIDTH             0x0000ff00
+#define      FLD_PSP_SPEC_VAL         0x000000ff
+
+/*****************************************************************************/
+#define      MV_DT_CTRL3              0x4B0
+#define      FLD_AUTO_RATE_DIS        0x80000000
+#define      FLD_HLOCK_DIS            0x40000000
+#define      FLD_SEL_FIELD_CNT        0x20000000
+#define      FLD_CS_TYPE2_SEL         0x10000000
+#define      FLD_CS_LINE_THRSH_SEL    0x08000000
+#define      FLD_CS_ATHRESH_SEL       0x04000000
+#define      FLD_PSP_SPEC_SEL         0x02000000
+#define      FLD_PSP_LINES_SEL        0x01000000
+#define      FLD_FIELD_CNT            0x00f00000
+#define      FLD_CS_TYPE2_CNT         0x000fc000
+#define      FLD_CS_LINE_CNT          0x00003f00
+#define      FLD_CS_ATHRESH_LEV       0x000000ff
+
+/*****************************************************************************/
+#define      CHIP_VERSION             0x4b4
+/* Cx231xx redefine  */
+#define      VERSION                  0x4b4
+#define      FLD_REV_ID               0x000000ff
+
+/*****************************************************************************/
+#define      MISC_DIAG_CTRL           0x4b8
+/* Reserved [31:24] */
+#define      FLD_SC_CONVERGE_THRESH   0x00ff0000
+#define      FLD_CCOMB_ERR_LIMIT_3D   0x0000ff00
+#define      FLD_LCOMB_ERR_LIMIT_3D   0x000000ff
+
+/*****************************************************************************/
+#define      VBI_PASS_CTRL            0x4bc
+#define      FLD_VBI_PASS_MD          0x00200000
+#define      FLD_VBI_SETUP_DIS        0x00100000
+#define      FLD_PASS_LINE_CTRL       0x000fffff
+
+/*****************************************************************************/
+/* Cx231xx redefine */
+#define      VCR_DET_CTRL             0x4c0
+#define      FLD_EN_FIELD_PHASE_DET   0x80000000
+#define      FLD_EN_HEAD_SW_DET       0x40000000
+#define      FLD_FIELD_PHASE_LENGTH   0x01ff0000
+/* Reserved [29:25] */
+#define      FLD_FIELD_PHASE_DELAY    0x0000ff00
+#define      FLD_FIELD_PHASE_LIMIT    0x000000f0
+#define      FLD_HEAD_SW_DET_LIMIT    0x0000000f
+
+/*****************************************************************************/
+#define      DL_CTL                   0x800
+#define      DL_CTL_ADDRESS_LOW       0x800    /* Byte 1 in DL_CTL */
+#define      DL_CTL_ADDRESS_HIGH      0x801    /* Byte 2 in DL_CTL */
+#define      DL_CTL_DATA              0x802    /* Byte 3 in DL_CTL */
+#define      DL_CTL_CONTROL           0x803    /* Byte 4 in DL_CTL */
+/* Reserved [31:5] */
+#define      FLD_START_8051           0x10000000
+#define      FLD_DL_ENABLE            0x08000000
+#define      FLD_DL_AUTO_INC          0x04000000
+#define      FLD_DL_MAP               0x03000000
+
+/*****************************************************************************/
+#define      STD_DET_STATUS           0x804
+#define      FLD_SPARE_STATUS1        0xff000000
+#define      FLD_SPARE_STATUS0        0x00ff0000
+#define      FLD_MOD_DET_STATUS1      0x0000ff00
+#define      FLD_MOD_DET_STATUS0      0x000000ff
+
+/*****************************************************************************/
+#define      AUD_BUILD_NUM            0x806
+#define      AUD_VER_NUM              0x807
+#define      STD_DET_CTL              0x808
+#define      STD_DET_CTL_AUD_CTL      0x808    /* Byte 1 in STD_DET_CTL */
+#define      STD_DET_CTL_PREF_MODE    0x809    /* Byte 2 in STD_DET_CTL */
+#define      FLD_SPARE_CTL0           0xff000000
+#define      FLD_DIS_DBX              0x00800000
+#define      FLD_DIS_BTSC             0x00400000
+#define      FLD_DIS_NICAM_A2         0x00200000
+#define      FLD_VIDEO_PRESENT        0x00100000
+#define      FLD_DW8051_VIDEO_FORMAT  0x000f0000
+#define      FLD_PREF_DEC_MODE        0x0000ff00
+#define      FLD_AUD_CONFIG           0x000000ff
+
+/*****************************************************************************/
+#define      DW8051_INT               0x80c
+#define      FLD_VIDEO_PRESENT_CHANGE 0x80000000
+#define      FLD_VIDEO_CHANGE         0x40000000
+#define      FLD_RDS_READY            0x20000000
+#define      FLD_AC97_INT             0x10000000
+#define      FLD_NICAM_BIT_ERROR_TOO_HIGH         0x08000000
+#define      FLD_NICAM_LOCK           0x04000000
+#define      FLD_NICAM_UNLOCK         0x02000000
+#define      FLD_DFT4_TH_CMP          0x01000000
+/* Reserved [23:22] */
+#define      FLD_LOCK_IND_INT         0x00200000
+#define      FLD_DFT3_TH_CMP          0x00100000
+#define      FLD_DFT2_TH_CMP          0x00080000
+#define      FLD_DFT1_TH_CMP          0x00040000
+#define      FLD_FM2_DFT_TH_CMP       0x00020000
+#define      FLD_FM1_DFT_TH_CMP       0x00010000
+#define      FLD_VIDEO_PRESENT_EN     0x00008000
+#define      FLD_VIDEO_CHANGE_EN      0x00004000
+#define      FLD_RDS_READY_EN         0x00002000
+#define      FLD_AC97_INT_EN          0x00001000
+#define      FLD_NICAM_BIT_ERROR_TOO_HIGH_EN      0x00000800
+#define      FLD_NICAM_LOCK_EN        0x00000400
+#define      FLD_NICAM_UNLOCK_EN      0x00000200
+#define      FLD_DFT4_TH_CMP_EN       0x00000100
+/* Reserved [7] */
+#define      FLD_DW8051_INT6_CTL1     0x00000040
+#define      FLD_DW8051_INT5_CTL1     0x00000020
+#define      FLD_DW8051_INT4_CTL1     0x00000010
+#define      FLD_DW8051_INT3_CTL1     0x00000008
+#define      FLD_DW8051_INT2_CTL1     0x00000004
+#define      FLD_DW8051_INT1_CTL1     0x00000002
+#define      FLD_DW8051_INT0_CTL1     0x00000001
+
+/*****************************************************************************/
+#define      GENERAL_CTL              0x810
+#define      FLD_RDS_INT              0x80000000
+#define      FLD_NBER_INT             0x40000000
+#define      FLD_NLL_INT              0x20000000
+#define      FLD_IFL_INT              0x10000000
+#define      FLD_FDL_INT              0x08000000
+#define      FLD_AFC_INT              0x04000000
+#define      FLD_AMC_INT              0x02000000
+#define      FLD_AC97_INT_CTL         0x01000000
+#define      FLD_RDS_INT_DIS          0x00800000
+#define      FLD_NBER_INT_DIS         0x00400000
+#define      FLD_NLL_INT_DIS          0x00200000
+#define      FLD_IFL_INT_DIS          0x00100000
+#define      FLD_FDL_INT_DIS          0x00080000
+#define      FLD_FC_INT_DIS           0x00040000
+#define      FLD_AMC_INT_DIS          0x00020000
+#define      FLD_AC97_INT_DIS         0x00010000
+#define      FLD_REV_NUM              0x0000ff00
+/* Reserved [7:5] */
+#define      FLD_DBX_SOFT_RESET_REG   0x00000010
+#define      FLD_AD_SOFT_RESET_REG    0x00000008
+#define      FLD_SRC_SOFT_RESET_REG   0x00000004
+#define      FLD_CDMOD_SOFT_RESET     0x00000002
+#define      FLD_8051_SOFT_RESET      0x00000001
+
+/*****************************************************************************/
+#define      AAGC_CTL                 0x814
+#define      FLD_AFE_12DB_EN          0x80000000
+#define      FLD_AAGC_DEFAULT_EN      0x40000000
+#define      FLD_AAGC_DEFAULT         0x3f000000
+/* Reserved [23] */
+#define      FLD_AAGC_GAIN            0x00600000
+#define      FLD_AAGC_TH              0x001f0000
+/* Reserved [15:14] */
+#define      FLD_AAGC_HYST2           0x00003f00
+/* Reserved [7:6] */
+#define      FLD_AAGC_HYST1           0x0000003f
+
+/*****************************************************************************/
+#define      IF_SRC_CTL               0x818
+#define      FLD_DBX_BYPASS           0x80000000
+/* Reserved [30:25] */
+#define      FLD_IF_SRC_MODE          0x01000000
+/* Reserved [23:18] */
+#define      FLD_IF_SRC_PHASE_INC     0x0001ffff
+
+/*****************************************************************************/
+#define      ANALOG_DEMOD_CTL         0x81c
+#define      FLD_ROT1_PHACC_PROG      0xffff0000
+/* Reserved [15] */
+#define      FLD_FM1_DELAY_FIX        0x00007000
+#define      FLD_PDF4_SHIFT           0x00000c00
+#define      FLD_PDF3_SHIFT           0x00000300
+#define      FLD_PDF2_SHIFT           0x000000c0
+#define      FLD_PDF1_SHIFT           0x00000030
+#define      FLD_FMBYPASS_MODE2       0x00000008
+#define      FLD_FMBYPASS_MODE1       0x00000004
+#define      FLD_NICAM_MODE           0x00000002
+#define      FLD_BTSC_FMRADIO_MODE    0x00000001
+
+/*****************************************************************************/
+#define      ROT_FREQ_CTL             0x820
+#define      FLD_ROT3_PHACC_PROG      0xffff0000
+#define      FLD_ROT2_PHACC_PROG      0x0000ffff
+
+/*****************************************************************************/
+#define      FM_CTL                   0x824
+#define      FLD_FM2_DC_FB_SHIFT      0xf0000000
+#define      FLD_FM2_DC_INT_SHIFT     0x0f000000
+#define      FLD_FM2_AFC_RESET        0x00800000
+#define      FLD_FM2_DC_PASS_IN       0x00400000
+#define      FLD_FM2_DAGC_SHIFT       0x00380000
+#define      FLD_FM2_CORDIC_SHIFT     0x00070000
+#define      FLD_FM1_DC_FB_SHIFT      0x0000f000
+#define      FLD_FM1_DC_INT_SHIFT     0x00000f00
+#define      FLD_FM1_AFC_RESET        0x00000080
+#define      FLD_FM1_DC_PASS_IN       0x00000040
+#define      FLD_FM1_DAGC_SHIFT       0x00000038
+#define      FLD_FM1_CORDIC_SHIFT     0x00000007
+
+/*****************************************************************************/
+#define      LPF_PDF_CTL              0x828
+/* Reserved [31:30] */
+#define      FLD_LPF32_SHIFT1         0x30000000
+#define      FLD_LPF32_SHIFT2         0x0c000000
+#define      FLD_LPF160_SHIFTA        0x03000000
+#define      FLD_LPF160_SHIFTB        0x00c00000
+#define      FLD_LPF160_SHIFTC        0x00300000
+#define      FLD_LPF32_COEF_SEL2      0x000c0000
+#define      FLD_LPF32_COEF_SEL1      0x00030000
+#define      FLD_LPF160_COEF_SELC     0x0000c000
+#define      FLD_LPF160_COEF_SELB     0x00003000
+#define      FLD_LPF160_COEF_SELA     0x00000c00
+#define      FLD_LPF160_IN_EN_REG     0x00000300
+#define      FLD_PDF4_PDF_SEL         0x000000c0
+#define      FLD_PDF3_PDF_SEL         0x00000030
+#define      FLD_PDF2_PDF_SEL         0x0000000c
+#define      FLD_PDF1_PDF_SEL         0x00000003
+
+/*****************************************************************************/
+#define      DFT1_CTL1                0x82c
+#define      FLD_DFT1_DWELL           0xffff0000
+#define      FLD_DFT1_FREQ            0x0000ffff
+
+/*****************************************************************************/
+#define      DFT1_CTL2                0x830
+#define      FLD_DFT1_THRESHOLD       0xffffff00
+#define      FLD_DFT1_CMP_CTL         0x00000080
+#define      FLD_DFT1_AVG             0x00000070
+/* Reserved [3:1] */
+#define      FLD_DFT1_START           0x00000001
+
+/*****************************************************************************/
+#define      DFT1_STATUS              0x834
+#define      FLD_DFT1_DONE            0x80000000
+#define      FLD_DFT1_TH_CMP_STAT     0x40000000
+#define      FLD_DFT1_RESULT          0x3fffffff
+
+/*****************************************************************************/
+#define      DFT2_CTL1                0x838
+#define      FLD_DFT2_DWELL           0xffff0000
+#define      FLD_DFT2_FREQ            0x0000ffff
+
+/*****************************************************************************/
+#define      DFT2_CTL2                0x83C
+#define      FLD_DFT2_THRESHOLD       0xffffff00
+#define      FLD_DFT2_CMP_CTL         0x00000080
+#define      FLD_DFT2_AVG             0x00000070
+/* Reserved [3:1] */
+#define      FLD_DFT2_START           0x00000001
+
+/*****************************************************************************/
+#define      DFT2_STATUS              0x840
+#define      FLD_DFT2_DONE            0x80000000
+#define      FLD_DFT2_TH_CMP_STAT     0x40000000
+#define      FLD_DFT2_RESULT          0x3fffffff
+
+/*****************************************************************************/
+#define      DFT3_CTL1                0x844
+#define      FLD_DFT3_DWELL           0xffff0000
+#define      FLD_DFT3_FREQ            0x0000ffff
+
+/*****************************************************************************/
+#define      DFT3_CTL2                0x848
+#define      FLD_DFT3_THRESHOLD       0xffffff00
+#define      FLD_DFT3_CMP_CTL         0x00000080
+#define      FLD_DFT3_AVG             0x00000070
+/* Reserved [3:1] */
+#define      FLD_DFT3_START           0x00000001
+
+/*****************************************************************************/
+#define      DFT3_STATUS              0x84c
+#define      FLD_DFT3_DONE            0x80000000
+#define      FLD_DFT3_TH_CMP_STAT     0x40000000
+#define      FLD_DFT3_RESULT          0x3fffffff
+
+/*****************************************************************************/
+#define      DFT4_CTL1                0x850
+#define      FLD_DFT4_DWELL           0xffff0000
+#define      FLD_DFT4_FREQ            0x0000ffff
+
+/*****************************************************************************/
+#define      DFT4_CTL2                0x854
+#define      FLD_DFT4_THRESHOLD       0xffffff00
+#define      FLD_DFT4_CMP_CTL         0x00000080
+#define      FLD_DFT4_AVG             0x00000070
+/* Reserved [3:1] */
+#define      FLD_DFT4_START           0x00000001
+
+/*****************************************************************************/
+#define      DFT4_STATUS              0x858
+#define      FLD_DFT4_DONE            0x80000000
+#define      FLD_DFT4_TH_CMP_STAT     0x40000000
+#define      FLD_DFT4_RESULT          0x3fffffff
+
+/*****************************************************************************/
+#define      AM_MTS_DET               0x85c
+#define      FLD_AM_MTS_MODE          0x80000000
+/* Reserved [30:26] */
+#define      FLD_AM_SUB               0x02000000
+#define      FLD_AM_GAIN_EN           0x01000000
+/* Reserved [23:16] */
+#define      FLD_AMMTS_GAIN_SCALE     0x0000e000
+#define      FLD_MTS_PDF_SHIFT        0x00001800
+#define      FLD_AM_REG_GAIN          0x00000700
+#define      FLD_AGC_REF              0x000000ff
+
+/*****************************************************************************/
+#define      ANALOG_MUX_CTL           0x860
+/* Reserved [31:29] */
+#define      FLD_MUX21_SEL            0x10000000
+#define      FLD_MUX20_SEL            0x08000000
+#define      FLD_MUX19_SEL            0x04000000
+#define      FLD_MUX18_SEL            0x02000000
+#define      FLD_MUX17_SEL            0x01000000
+#define      FLD_MUX16_SEL            0x00800000
+#define      FLD_MUX15_SEL            0x00400000
+#define      FLD_MUX14_SEL            0x00300000
+#define      FLD_MUX13_SEL            0x000C0000
+#define      FLD_MUX12_SEL            0x00020000
+#define      FLD_MUX11_SEL            0x00018000
+#define      FLD_MUX10_SEL            0x00004000
+#define      FLD_MUX9_SEL             0x00002000
+#define      FLD_MUX8_SEL             0x00001000
+#define      FLD_MUX7_SEL             0x00000800
+#define      FLD_MUX6_SEL             0x00000600
+#define      FLD_MUX5_SEL             0x00000100
+#define      FLD_MUX4_SEL             0x000000c0
+#define      FLD_MUX3_SEL             0x00000030
+#define      FLD_MUX2_SEL             0x0000000c
+#define      FLD_MUX1_SEL             0x00000003
+
+/*****************************************************************************/
+/* Cx231xx redefine */
+#define      DPLL_CTRL1               0x864
+#define      DIG_PLL_CTL1             0x864
+
+#define      FLD_PLL_STATUS           0x07000000
+#define      FLD_BANDWIDTH_SELECT     0x00030000
+#define      FLD_PLL_SHIFT_REG        0x00007000
+#define      FLD_PHASE_SHIFT          0x000007ff
+
+/*****************************************************************************/
+/* Cx231xx redefine */
+#define      DPLL_CTRL2               0x868
+#define      DIG_PLL_CTL2             0x868
+#define      FLD_PLL_UNLOCK_THR       0xff000000
+#define      FLD_PLL_LOCK_THR         0x00ff0000
+/* Reserved [15:8] */
+#define      FLD_AM_PDF_SEL2          0x000000c0
+#define      FLD_AM_PDF_SEL1          0x00000030
+#define      FLD_DPLL_FSM_CTRL        0x0000000c
+/* Reserved [1] */
+#define      FLD_PLL_PILOT_DET        0x00000001
+
+/*****************************************************************************/
+/* Cx231xx redefine */
+#define      DPLL_CTRL3               0x86c
+#define      DIG_PLL_CTL3             0x86c
+#define      FLD_DISABLE_LOOP         0x01000000
+#define      FLD_A1_DS1_SEL           0x000c0000
+#define      FLD_A1_DS2_SEL           0x00030000
+#define      FLD_A1_KI                0x0000ff00
+#define      FLD_A1_KD                0x000000ff
+
+/*****************************************************************************/
+/* Cx231xx redefine */
+#define      DPLL_CTRL4               0x870
+#define      DIG_PLL_CTL4             0x870
+#define      FLD_A2_DS1_SEL           0x000c0000
+#define      FLD_A2_DS2_SEL           0x00030000
+#define      FLD_A2_KI                0x0000ff00
+#define      FLD_A2_KD                0x000000ff
+
+/*****************************************************************************/
+/* Cx231xx redefine */
+#define      DPLL_CTRL5               0x874
+#define      DIG_PLL_CTL5             0x874
+#define      FLD_TRK_DS1_SEL          0x000c0000
+#define      FLD_TRK_DS2_SEL          0x00030000
+#define      FLD_TRK_KI               0x0000ff00
+#define      FLD_TRK_KD               0x000000ff
+
+/*****************************************************************************/
+#define      DEEMPH_GAIN_CTL          0x878
+#define      FLD_DEEMPH2_GAIN         0xFFFF0000
+#define      FLD_DEEMPH1_GAIN         0x0000FFFF
+
+/*****************************************************************************/
+/* Cx231xx redefine */
+#define      DEEMPH_COEFF1            0x87c
+#define      DEEMPH_COEF1             0x87c
+#define      FLD_DEEMPH_B0            0xffff0000
+#define      FLD_DEEMPH_A0            0x0000ffff
+
+/*****************************************************************************/
+/* Cx231xx redefine */
+#define      DEEMPH_COEFF2            0x880
+#define      DEEMPH_COEF2             0x880
+#define      FLD_DEEMPH_B1            0xFFFF0000
+#define      FLD_DEEMPH_A1            0x0000FFFF
+
+/*****************************************************************************/
+#define      DBX1_CTL1                0x884
+#define      FLD_DBX1_WBE_GAIN        0xffff0000
+#define      FLD_DBX1_IN_GAIN         0x0000ffff
+
+/*****************************************************************************/
+#define      DBX1_CTL2                0x888
+#define      FLD_DBX1_SE_BYPASS       0xffff0000
+#define      FLD_DBX1_SE_GAIN         0x0000ffff
+
+/*****************************************************************************/
+#define      DBX1_RMS_SE              0x88C
+#define      FLD_DBX1_RMS_WBE         0xffff0000
+#define      FLD_DBX1_RMS_SE_FLD      0x0000ffff
+
+/*****************************************************************************/
+#define      DBX2_CTL1                0x890
+#define      FLD_DBX2_WBE_GAIN        0xffff0000
+#define      FLD_DBX2_IN_GAIN         0x0000ffff
+
+/*****************************************************************************/
+#define      DBX2_CTL2                0x894
+#define      FLD_DBX2_SE_BYPASS       0xffff0000
+#define      FLD_DBX2_SE_GAIN         0x0000ffff
+
+/*****************************************************************************/
+#define      DBX2_RMS_SE              0x898
+#define      FLD_DBX2_RMS_WBE         0xffff0000
+#define      FLD_DBX2_RMS_SE_FLD      0x0000ffff
+
+/*****************************************************************************/
+#define      AM_FM_DIFF               0x89c
+/* Reserved [31] */
+#define      FLD_FM_DIFF_OUT          0x7fff0000
+/* Reserved [15] */
+#define      FLD_AM_DIFF_OUT          0x00007fff
+
+/*****************************************************************************/
+#define      NICAM_FAW                0x8a0
+#define      FLD_FAWDETWINEND         0xFc000000
+#define      FLD_FAWDETWINSTR         0x03ff0000
+/* Reserved [15:12] */
+#define      FLD_FAWDETTHRSHLD3       0x00000f00
+#define      FLD_FAWDETTHRSHLD2       0x000000f0
+#define      FLD_FAWDETTHRSHLD1       0x0000000f
+
+/*****************************************************************************/
+/* Cx231xx redefine */
+#define      DEEMPH_GAIN              0x8a4
+#define      NICAM_DEEMPHGAIN         0x8a4
+/* Reserved [31:18] */
+#define      FLD_DEEMPHGAIN           0x0003ffff
+
+/*****************************************************************************/
+/* Cx231xx redefine */
+#define      DEEMPH_NUMER1            0x8a8
+#define      NICAM_DEEMPHNUMER1       0x8a8
+/* Reserved [31:18] */
+#define      FLD_DEEMPHNUMER1         0x0003ffff
+
+/*****************************************************************************/
+/* Cx231xx redefine */
+#define      DEEMPH_NUMER2            0x8ac
+#define      NICAM_DEEMPHNUMER2       0x8ac
+/* Reserved [31:18] */
+#define      FLD_DEEMPHNUMER2         0x0003ffff
+
+/*****************************************************************************/
+/* Cx231xx redefine */
+#define      DEEMPH_DENOM1            0x8b0
+#define      NICAM_DEEMPHDENOM1       0x8b0
+/* Reserved [31:18] */
+#define      FLD_DEEMPHDENOM1         0x0003ffff
+
+/*****************************************************************************/
+/* Cx231xx redefine */
+#define      DEEMPH_DENOM2            0x8b4
+#define      NICAM_DEEMPHDENOM2       0x8b4
+/* Reserved [31:18] */
+#define      FLD_DEEMPHDENOM2         0x0003ffff
+
+/*****************************************************************************/
+#define      NICAM_ERRLOG_CTL1        0x8B8
+/* Reserved [31:28] */
+#define      FLD_ERRINTRPTTHSHLD1     0x0fff0000
+/* Reserved [15:12] */
+#define      FLD_ERRLOGPERIOD         0x00000fff
+
+/*****************************************************************************/
+#define      NICAM_ERRLOG_CTL2        0x8bc
+/* Reserved [31:28] */
+#define      FLD_ERRINTRPTTHSHLD3     0x0fff0000
+/* Reserved [15:12] */
+#define      FLD_ERRINTRPTTHSHLD2     0x00000fff
+
+/*****************************************************************************/
+#define      NICAM_ERRLOG_STS1        0x8c0
+/* Reserved [31:28] */
+#define      FLD_ERRLOG2              0x0fff0000
+/* Reserved [15:12] */
+#define      FLD_ERRLOG1              0x00000fff
+
+/*****************************************************************************/
+#define      NICAM_ERRLOG_STS2        0x8c4
+/* Reserved [31:12] */
+#define      FLD_ERRLOG3              0x00000fff
+
+/*****************************************************************************/
+#define      NICAM_STATUS             0x8c8
+/* Reserved [31:20] */
+#define      FLD_NICAM_CIB            0x000c0000
+#define      FLD_NICAM_LOCK_STAT      0x00020000
+#define      FLD_NICAM_MUTE           0x00010000
+#define      FLD_NICAMADDIT_DATA      0x0000ffe0
+#define      FLD_NICAMCNTRL           0x0000001f
+
+/*****************************************************************************/
+#define      DEMATRIX_CTL             0x8cc
+#define      FLD_AC97_IN_SHIFT        0xf0000000
+#define      FLD_I2S_IN_SHIFT         0x0f000000
+#define      FLD_DEMATRIX_SEL_CTL     0x00ff0000
+/* Reserved [15:11] */
+#define      FLD_DMTRX_BYPASS         0x00000400
+#define      FLD_DEMATRIX_MODE        0x00000300
+/* Reserved [7:6] */
+#define      FLD_PH_DBX_SEL           0x00000020
+#define      FLD_PH_CH_SEL            0x00000010
+#define      FLD_PHASE_FIX            0x0000000f
+
+/*****************************************************************************/
+#define      PATH1_CTL1               0x8d0
+/* Reserved [31:29] */
+#define      FLD_PATH1_MUTE_CTL       0x1f000000
+/* Reserved [23:22] */
+#define      FLD_PATH1_AVC_CG         0x00300000
+#define      FLD_PATH1_AVC_RT         0x000f0000
+#define      FLD_PATH1_AVC_AT         0x0000f000
+#define      FLD_PATH1_AVC_STEREO     0x00000800
+#define      FLD_PATH1_AVC_CR         0x00000700
+#define      FLD_PATH1_AVC_RMS_CON    0x000000f0
+#define      FLD_PATH1_SEL_CTL        0x0000000f
+
+/*****************************************************************************/
+#define      PATH1_VOL_CTL            0x8d4
+#define      FLD_PATH1_AVC_THRESHOLD  0x7fff0000
+#define      FLD_PATH1_BAL_LEFT       0x00008000
+#define      FLD_PATH1_BAL_LEVEL      0x00007f00
+#define      FLD_PATH1_VOLUME         0x000000ff
+
+/*****************************************************************************/
+#define      PATH1_EQ_CTL             0x8d8
+/* Reserved [31:30] */
+#define      FLD_PATH1_EQ_TREBLE_VOL  0x3f000000
+/* Reserved [23:22] */
+#define      FLD_PATH1_EQ_MID_VOL     0x003f0000
+/* Reserved [15:14] */
+#define      FLD_PATH1_EQ_BASS_VOL    0x00003f00
+/* Reserved [7:1] */
+#define      FLD_PATH1_EQ_BAND_SEL    0x00000001
+
+/*****************************************************************************/
+#define      PATH1_SC_CTL             0x8dc
+#define      FLD_PATH1_SC_THRESHOLD   0x7fff0000
+#define      FLD_PATH1_SC_RT          0x0000f000
+#define      FLD_PATH1_SC_AT          0x00000f00
+#define      FLD_PATH1_SC_STEREO      0x00000080
+#define      FLD_PATH1_SC_CR          0x00000070
+#define      FLD_PATH1_SC_RMS_CON     0x0000000f
+
+/*****************************************************************************/
+#define      PATH2_CTL1               0x8e0
+/* Reserved [31:26] */
+#define      FLD_PATH2_MUTE_CTL       0x03000000
+/* Reserved [23:22] */
+#define      FLD_PATH2_AVC_CG         0x00300000
+#define      FLD_PATH2_AVC_RT         0x000f0000
+#define      FLD_PATH2_AVC_AT         0x0000f000
+#define      FLD_PATH2_AVC_STEREO     0x00000800
+#define      FLD_PATH2_AVC_CR         0x00000700
+#define      FLD_PATH2_AVC_RMS_CON    0x000000f0
+#define      FLD_PATH2_SEL_CTL        0x0000000f
+
+/*****************************************************************************/
+#define      PATH2_VOL_CTL            0x8e4
+#define      FLD_PATH2_AVC_THRESHOLD  0xffff0000
+#define      FLD_PATH2_BAL_LEFT       0x00008000
+#define      FLD_PATH2_BAL_LEVEL      0x00007f00
+#define      FLD_PATH2_VOLUME         0x000000ff
+
+/*****************************************************************************/
+#define      PATH2_EQ_CTL             0x8e8
+/* Reserved [31:30] */
+#define      FLD_PATH2_EQ_TREBLE_VOL  0x3f000000
+/* Reserved [23:22] */
+#define      FLD_PATH2_EQ_MID_VOL     0x003f0000
+/* Reserved [15:14] */
+#define      FLD_PATH2_EQ_BASS_VOL    0x00003f00
+/* Reserved [7:1] */
+#define      FLD_PATH2_EQ_BAND_SEL    0x00000001
+
+/*****************************************************************************/
+#define      PATH2_SC_CTL             0x8eC
+#define      FLD_PATH2_SC_THRESHOLD   0xffff0000
+#define      FLD_PATH2_SC_RT          0x0000f000
+#define      FLD_PATH2_SC_AT          0x00000f00
+#define      FLD_PATH2_SC_STEREO      0x00000080
+#define      FLD_PATH2_SC_CR          0x00000070
+#define      FLD_PATH2_SC_RMS_CON     0x0000000f
+
+/*****************************************************************************/
+#define      SRC_CTL                  0x8f0
+#define      FLD_SRC_STATUS           0xffffff00
+#define      FLD_FIFO_LF_EN           0x000000fc
+#define      FLD_BYPASS_LI            0x00000002
+#define      FLD_BYPASS_PF            0x00000001
+
+/*****************************************************************************/
+#define      SRC_LF_COEF              0x8f4
+#define      FLD_LOOP_FILTER_COEF2    0xffff0000
+#define      FLD_LOOP_FILTER_COEF1    0x0000ffff
+
+/*****************************************************************************/
+#define      SRC1_CTL                 0x8f8
+/* Reserved [31:28] */
+#define      FLD_SRC1_FIFO_RD_TH      0x0f000000
+/* Reserved [23:18] */
+#define      FLD_SRC1_PHASE_INC       0x0003ffff
+
+/*****************************************************************************/
+#define      SRC2_CTL                 0x8fc
+/* Reserved [31:28] */
+#define      FLD_SRC2_FIFO_RD_TH      0x0f000000
+/* Reserved [23:18] */
+#define      FLD_SRC2_PHASE_INC       0x0003ffff
+
+/*****************************************************************************/
+#define      SRC3_CTL                 0x900
+/* Reserved [31:28] */
+#define      FLD_SRC3_FIFO_RD_TH      0x0f000000
+/* Reserved [23:18] */
+#define      FLD_SRC3_PHASE_INC       0x0003ffff
+
+/*****************************************************************************/
+#define      SRC4_CTL                 0x904
+/* Reserved [31:28] */
+#define      FLD_SRC4_FIFO_RD_TH      0x0f000000
+/* Reserved [23:18] */
+#define      FLD_SRC4_PHASE_INC       0x0003ffff
+
+/*****************************************************************************/
+#define      SRC5_CTL                 0x908
+/* Reserved [31:28] */
+#define      FLD_SRC5_FIFO_RD_TH      0x0f000000
+/* Reserved [23:18] */
+#define      FLD_SRC5_PHASE_INC       0x0003ffff
+
+/*****************************************************************************/
+#define      SRC6_CTL                 0x90c
+/* Reserved [31:28] */
+#define      FLD_SRC6_FIFO_RD_TH      0x0f000000
+/* Reserved [23:18] */
+#define      FLD_SRC6_PHASE_INC       0x0003ffff
+
+/*****************************************************************************/
+#define      BAND_OUT_SEL             0x910
+#define      FLD_SRC6_IN_SEL          0xc0000000
+#define      FLD_SRC6_CLK_SEL         0x30000000
+#define      FLD_SRC5_IN_SEL          0x0c000000
+#define      FLD_SRC5_CLK_SEL         0x03000000
+#define      FLD_SRC4_IN_SEL          0x00c00000
+#define      FLD_SRC4_CLK_SEL         0x00300000
+#define      FLD_SRC3_IN_SEL          0x000c0000
+#define      FLD_SRC3_CLK_SEL         0x00030000
+#define      FLD_BASEBAND_BYPASS_CTL  0x0000ff00
+#define      FLD_AC97_SRC_SEL         0x000000c0
+#define      FLD_I2S_SRC_SEL          0x00000030
+#define      FLD_PARALLEL2_SRC_SEL    0x0000000c
+#define      FLD_PARALLEL1_SRC_SEL    0x00000003
+
+/*****************************************************************************/
+#define      I2S_IN_CTL               0x914
+/* Reserved [31:11] */
+#define      FLD_I2S_UP2X_BW20K       0x00000400
+#define      FLD_I2S_UP2X_BYPASS      0x00000200
+#define      FLD_I2S_IN_MASTER_MODE   0x00000100
+#define      FLD_I2S_IN_SONY_MODE     0x00000080
+#define      FLD_I2S_IN_RIGHT_JUST    0x00000040
+#define      FLD_I2S_IN_WS_SEL        0x00000020
+#define      FLD_I2S_IN_BCN_DEL       0x0000001f
+
+/*****************************************************************************/
+#define      I2S_OUT_CTL              0x918
+/* Reserved [31:17] */
+#define      FLD_I2S_OUT_SOFT_RESET_EN  0x00010000
+/* Reserved [15:9] */
+#define      FLD_I2S_OUT_MASTER_MODE  0x00000100
+#define      FLD_I2S_OUT_SONY_MODE    0x00000080
+#define      FLD_I2S_OUT_RIGHT_JUST   0x00000040
+#define      FLD_I2S_OUT_WS_SEL       0x00000020
+#define      FLD_I2S_OUT_BCN_DEL      0x0000001f
+
+/*****************************************************************************/
+#define      AC97_CTL                 0x91c
+/* Reserved [31:26] */
+#define      FLD_AC97_UP2X_BW20K      0x02000000
+#define      FLD_AC97_UP2X_BYPASS     0x01000000
+/* Reserved [23:17] */
+#define      FLD_AC97_RST_ACL         0x00010000
+/* Reserved [15:9] */
+#define      FLD_AC97_WAKE_UP_SYNC    0x00000100
+/* Reserved [7:1] */
+#define      FLD_AC97_SHUTDOWN        0x00000001
+
+/* Cx231xx redefine */
+#define      QPSK_IAGC_CTL1		0x94c
+#define      QPSK_IAGC_CTL2		0x950
+#define      QPSK_FEPR_FREQ		0x954
+#define      QPSK_BTL_CTL1		0x958
+#define      QPSK_BTL_CTL2		0x95c
+#define      QPSK_CTL_CTL1		0x960
+#define      QPSK_CTL_CTL2		0x964
+#define      QPSK_MF_FAGC_CTL		0x968
+#define      QPSK_EQ_CTL		0x96c
+#define      QPSK_LOCK_CTL		0x970
+
+/*****************************************************************************/
+#define      FM1_DFT_CTL              0x9a8
+#define      FLD_FM1_DFT_THRESHOLD    0xffff0000
+/* Reserved [15:8] */
+#define      FLD_FM1_DFT_CMP_CTL      0x00000080
+#define      FLD_FM1_DFT_AVG          0x00000070
+/* Reserved [3:1] */
+#define      FLD_FM1_DFT_START        0x00000001
+
+/*****************************************************************************/
+#define      FM1_DFT_STATUS           0x9ac
+#define      FLD_FM1_DFT_DONE         0x80000000
+/* Reserved [30:19] */
+#define      FLD_FM_DFT_TH_CMP        0x00040000
+#define      FLD_FM1_DFT              0x0003ffff
+
+/*****************************************************************************/
+#define      FM2_DFT_CTL              0x9b0
+#define      FLD_FM2_DFT_THRESHOLD    0xffff0000
+/* Reserved [15:8] */
+#define      FLD_FM2_DFT_CMP_CTL      0x00000080
+#define      FLD_FM2_DFT_AVG          0x00000070
+/* Reserved [3:1] */
+#define      FLD_FM2_DFT_START        0x00000001
+
+/*****************************************************************************/
+#define      FM2_DFT_STATUS           0x9b4
+#define      FLD_FM2_DFT_DONE         0x80000000
+/* Reserved [30:19] */
+#define      FLD_FM2_DFT_TH_CMP_STAT  0x00040000
+#define      FLD_FM2_DFT              0x0003ffff
+
+/*****************************************************************************/
+/* Cx231xx redefine */
+#define      AAGC_STATUS_REG          0x9b8
+#define      AAGC_STATUS              0x9b8
+/* Reserved [31:27] */
+#define      FLD_FM2_DAGC_OUT         0x07000000
+/* Reserved [23:19] */
+#define      FLD_FM1_DAGC_OUT         0x00070000
+/* Reserved [15:6] */
+#define      FLD_AFE_VGA_OUT          0x0000003f
+
+/*****************************************************************************/
+#define      MTS_GAIN_STATUS          0x9bc
+/* Reserved [31:14] */
+#define      FLD_MTS_GAIN             0x00003fff
+
+#define      RDS_OUT                  0x9c0
+#define      FLD_RDS_Q                0xffff0000
+#define      FLD_RDS_I                0x0000ffff
+
+/*****************************************************************************/
+#define      AUTOCONFIG_REG           0x9c4
+/* Reserved [31:4] */
+#define      FLD_AUTOCONFIG_MODE      0x0000000f
+
+#define      FM_AFC                   0x9c8
+#define      FLD_FM2_AFC              0xffff0000
+#define      FLD_FM1_AFC              0x0000ffff
+
+/*****************************************************************************/
+/* Cx231xx redefine */
+#define      NEW_SPARE                0x9cc
+#define      NEW_SPARE_REG            0x9cc
+
+/*****************************************************************************/
+#define      DBX_ADJ                  0x9d0
+/* Reserved [31:28] */
+#define      FLD_DBX2_ADJ             0x0fff0000
+/* Reserved [15:12] */
+#define      FLD_DBX1_ADJ             0x00000fff
+
+#define      VID_FMT_AUTO              0
+#define      VID_FMT_NTSC_M            1
+#define      VID_FMT_NTSC_J            2
+#define      VID_FMT_NTSC_443          3
+#define      VID_FMT_PAL_BDGHI         4
+#define      VID_FMT_PAL_M             5
+#define      VID_FMT_PAL_N             6
+#define      VID_FMT_PAL_NC            7
+#define      VID_FMT_PAL_60            8
+#define      VID_FMT_SECAM             12
+#define      VID_FMT_SECAM_60          13
+
+#define      INPUT_MODE_CVBS_0         0       /* INPUT_MODE_VALUE(0) */
+#define      INPUT_MODE_YC_1           1       /* INPUT_MODE_VALUE(1) */
+#define      INPUT_MODE_YC2_2          2       /* INPUT_MODE_VALUE(2) */
+#define      INPUT_MODE_YUV_3          3       /* INPUT_MODE_VALUE(3) */
+
+#define      LUMA_LPF_LOW_BANDPASS     0       /* 0.6Mhz LPF BW */
+#define      LUMA_LPF_MEDIUM_BANDPASS  1       /* 1.0Mhz LPF BW */
+#define      LUMA_LPF_HIGH_BANDPASS    2       /* 1.5Mhz LPF BW */
+
+#define      UV_LPF_LOW_BANDPASS       0       /* 0.6Mhz LPF BW */
+#define      UV_LPF_MEDIUM_BANDPASS    1       /* 1.0Mhz LPF BW */
+#define      UV_LPF_HIGH_BANDPASS      2       /* 1.5Mhz LPF BW */
+
+#define      TWO_TAP_FILT              0
+#define      THREE_TAP_FILT            1
+#define      FOUR_TAP_FILT             2
+#define      FIVE_TAP_FILT             3
+
+#define      AUD_CHAN_SRC_PARALLEL     0
+#define      AUD_CHAN_SRC_I2S_INPUT    1
+#define      AUD_CHAN_SRC_FLATIRON     2
+#define      AUD_CHAN_SRC_PARALLEL3    3
+
+#define      OUT_MODE_601              0
+#define      OUT_MODE_656              1
+#define      OUT_MODE_VIP11            2
+#define      OUT_MODE_VIP20            3
+
+#define      PHASE_INC_49MHZ          0x0df22
+#define      PHASE_INC_56MHZ          0x0fa5b
+#define      PHASE_INC_28MHZ          0x010000
+
+#endif
diff --git a/drivers/media/usb/cx231xx/cx231xx-vbi.c b/drivers/media/usb/cx231xx/cx231xx-vbi.c
new file mode 100644
index 0000000..10b2eb7
--- /dev/null
+++ b/drivers/media/usb/cx231xx/cx231xx-vbi.c
@@ -0,0 +1,708 @@
+/*
+   cx231xx_vbi.c - driver for Conexant Cx23100/101/102 USB video capture devices
+
+   Copyright (C) 2008 <srinivasa.deevi at conexant dot com>
+	Based on cx88 driver
+
+   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.
+ */
+
+#include "cx231xx.h"
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/bitmap.h>
+#include <linux/i2c.h>
+#include <linux/mm.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/drv-intf/msp3400.h>
+#include <media/tuner.h>
+
+#include "cx231xx-vbi.h"
+
+static inline void print_err_status(struct cx231xx *dev, int packet, int status)
+{
+	char *errmsg = "Unknown";
+
+	switch (status) {
+	case -ENOENT:
+		errmsg = "unlinked synchronously";
+		break;
+	case -ECONNRESET:
+		errmsg = "unlinked asynchronously";
+		break;
+	case -ENOSR:
+		errmsg = "Buffer error (overrun)";
+		break;
+	case -EPIPE:
+		errmsg = "Stalled (device not responding)";
+		break;
+	case -EOVERFLOW:
+		errmsg = "Babble (bad cable?)";
+		break;
+	case -EPROTO:
+		errmsg = "Bit-stuff error (bad cable?)";
+		break;
+	case -EILSEQ:
+		errmsg = "CRC/Timeout (could be anything)";
+		break;
+	case -ETIME:
+		errmsg = "Device does not respond";
+		break;
+	}
+	if (packet < 0) {
+		dev_err(dev->dev,
+			"URB status %d [%s].\n", status, errmsg);
+	} else {
+		dev_err(dev->dev,
+			"URB packet %d, status %d [%s].\n",
+			packet, status, errmsg);
+	}
+}
+
+/*
+ * Controls the isoc copy of each urb packet
+ */
+static inline int cx231xx_isoc_vbi_copy(struct cx231xx *dev, struct urb *urb)
+{
+	struct cx231xx_dmaqueue *dma_q = urb->context;
+	int rc = 1;
+	unsigned char *p_buffer;
+	u32 bytes_parsed = 0, buffer_size = 0;
+	u8 sav_eav = 0;
+
+	if (!dev)
+		return 0;
+
+	if (dev->state & DEV_DISCONNECTED)
+		return 0;
+
+	if (urb->status < 0) {
+		print_err_status(dev, -1, urb->status);
+		if (urb->status == -ENOENT)
+			return 0;
+	}
+
+	/* get buffer pointer and length */
+	p_buffer = urb->transfer_buffer;
+	buffer_size = urb->actual_length;
+
+	if (buffer_size > 0) {
+		bytes_parsed = 0;
+
+		if (dma_q->is_partial_line) {
+			/* Handle the case where we were working on a partial
+			   line */
+			sav_eav = dma_q->last_sav;
+		} else {
+			/* Check for a SAV/EAV overlapping the
+			   buffer boundary */
+
+			sav_eav = cx231xx_find_boundary_SAV_EAV(p_buffer,
+							  dma_q->partial_buf,
+							  &bytes_parsed);
+		}
+
+		sav_eav &= 0xF0;
+		/* Get the first line if we have some portion of an SAV/EAV from
+		   the last buffer or a partial line */
+		if (sav_eav) {
+			bytes_parsed += cx231xx_get_vbi_line(dev, dma_q,
+				sav_eav,		       /* SAV/EAV */
+				p_buffer + bytes_parsed,       /* p_buffer */
+				buffer_size - bytes_parsed);   /* buffer size */
+		}
+
+		/* Now parse data that is completely in this buffer */
+		dma_q->is_partial_line = 0;
+
+		while (bytes_parsed < buffer_size) {
+			u32 bytes_used = 0;
+
+			sav_eav = cx231xx_find_next_SAV_EAV(
+				p_buffer + bytes_parsed,	/* p_buffer */
+				buffer_size - bytes_parsed, /* buffer size */
+				&bytes_used);	/* bytes used to get SAV/EAV */
+
+			bytes_parsed += bytes_used;
+
+			sav_eav &= 0xF0;
+			if (sav_eav && (bytes_parsed < buffer_size)) {
+				bytes_parsed += cx231xx_get_vbi_line(dev,
+					dma_q, sav_eav,	/* SAV/EAV */
+					p_buffer+bytes_parsed, /* p_buffer */
+					buffer_size-bytes_parsed);/*buf size*/
+			}
+		}
+
+		/* Save the last four bytes of the buffer so we can
+		check the buffer boundary condition next time */
+		memcpy(dma_q->partial_buf, p_buffer + buffer_size - 4, 4);
+		bytes_parsed = 0;
+	}
+
+	return rc;
+}
+
+/* ------------------------------------------------------------------
+	Vbi buf operations
+   ------------------------------------------------------------------*/
+
+static int
+vbi_buffer_setup(struct videobuf_queue *vq, unsigned int *count,
+		 unsigned int *size)
+{
+	struct cx231xx_fh *fh = vq->priv_data;
+	struct cx231xx *dev = fh->dev;
+	u32 height = 0;
+
+	height = ((dev->norm & V4L2_STD_625_50) ?
+		  PAL_VBI_LINES : NTSC_VBI_LINES);
+
+	*size = (dev->width * height * 2 * 2);
+	if (0 == *count)
+		*count = CX231XX_DEF_VBI_BUF;
+
+	if (*count < CX231XX_MIN_BUF)
+		*count = CX231XX_MIN_BUF;
+
+	return 0;
+}
+
+/* This is called *without* dev->slock held; please keep it that way */
+static void free_buffer(struct videobuf_queue *vq, struct cx231xx_buffer *buf)
+{
+	struct cx231xx_fh *fh = vq->priv_data;
+	struct cx231xx *dev = fh->dev;
+	unsigned long flags = 0;
+	BUG_ON(in_interrupt());
+
+	/* We used to wait for the buffer to finish here, but this didn't work
+	   because, as we were keeping the state as VIDEOBUF_QUEUED,
+	   videobuf_queue_cancel marked it as finished for us.
+	   (Also, it could wedge forever if the hardware was misconfigured.)
+
+	   This should be safe; by the time we get here, the buffer isn't
+	   queued anymore. If we ever start marking the buffers as
+	   VIDEOBUF_ACTIVE, it won't be, though.
+	 */
+	spin_lock_irqsave(&dev->vbi_mode.slock, flags);
+	if (dev->vbi_mode.bulk_ctl.buf == buf)
+		dev->vbi_mode.bulk_ctl.buf = NULL;
+	spin_unlock_irqrestore(&dev->vbi_mode.slock, flags);
+
+	videobuf_vmalloc_free(&buf->vb);
+	buf->vb.state = VIDEOBUF_NEEDS_INIT;
+}
+
+static int
+vbi_buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb,
+		   enum v4l2_field field)
+{
+	struct cx231xx_fh *fh = vq->priv_data;
+	struct cx231xx_buffer *buf =
+	    container_of(vb, struct cx231xx_buffer, vb);
+	struct cx231xx *dev = fh->dev;
+	int rc = 0, urb_init = 0;
+	u32 height = 0;
+
+	height = ((dev->norm & V4L2_STD_625_50) ?
+		  PAL_VBI_LINES : NTSC_VBI_LINES);
+	buf->vb.size = ((dev->width << 1) * height * 2);
+
+	if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size)
+		return -EINVAL;
+
+	buf->vb.width = dev->width;
+	buf->vb.height = height;
+	buf->vb.field = field;
+	buf->vb.field = V4L2_FIELD_SEQ_TB;
+
+	if (VIDEOBUF_NEEDS_INIT == buf->vb.state) {
+		rc = videobuf_iolock(vq, &buf->vb, NULL);
+		if (rc < 0)
+			goto fail;
+	}
+
+	if (!dev->vbi_mode.bulk_ctl.num_bufs)
+		urb_init = 1;
+
+	if (urb_init) {
+		rc = cx231xx_init_vbi_isoc(dev, CX231XX_NUM_VBI_PACKETS,
+					   CX231XX_NUM_VBI_BUFS,
+					   dev->vbi_mode.alt_max_pkt_size[0],
+					   cx231xx_isoc_vbi_copy);
+		if (rc < 0)
+			goto fail;
+	}
+
+	buf->vb.state = VIDEOBUF_PREPARED;
+	return 0;
+
+fail:
+	free_buffer(vq, buf);
+	return rc;
+}
+
+static void
+vbi_buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb)
+{
+	struct cx231xx_buffer *buf =
+	    container_of(vb, struct cx231xx_buffer, vb);
+	struct cx231xx_fh *fh = vq->priv_data;
+	struct cx231xx *dev = fh->dev;
+	struct cx231xx_dmaqueue *vidq = &dev->vbi_mode.vidq;
+
+	buf->vb.state = VIDEOBUF_QUEUED;
+	list_add_tail(&buf->vb.queue, &vidq->active);
+
+}
+
+static void vbi_buffer_release(struct videobuf_queue *vq,
+			       struct videobuf_buffer *vb)
+{
+	struct cx231xx_buffer *buf =
+	    container_of(vb, struct cx231xx_buffer, vb);
+
+
+	free_buffer(vq, buf);
+}
+
+const struct videobuf_queue_ops cx231xx_vbi_qops = {
+	.buf_setup   = vbi_buffer_setup,
+	.buf_prepare = vbi_buffer_prepare,
+	.buf_queue   = vbi_buffer_queue,
+	.buf_release = vbi_buffer_release,
+};
+
+/* ------------------------------------------------------------------
+	URB control
+   ------------------------------------------------------------------*/
+
+/*
+ * IRQ callback, called by URB callback
+ */
+static void cx231xx_irq_vbi_callback(struct urb *urb)
+{
+	struct cx231xx_dmaqueue *dma_q = urb->context;
+	struct cx231xx_video_mode *vmode =
+	    container_of(dma_q, struct cx231xx_video_mode, vidq);
+	struct cx231xx *dev = container_of(vmode, struct cx231xx, vbi_mode);
+	unsigned long flags;
+
+	switch (urb->status) {
+	case 0:		/* success */
+	case -ETIMEDOUT:	/* NAK */
+		break;
+	case -ECONNRESET:	/* kill */
+	case -ENOENT:
+	case -ESHUTDOWN:
+		return;
+	default:		/* error */
+		dev_err(dev->dev,
+			"urb completion error %d.\n", urb->status);
+		break;
+	}
+
+	/* Copy data from URB */
+	spin_lock_irqsave(&dev->vbi_mode.slock, flags);
+	dev->vbi_mode.bulk_ctl.bulk_copy(dev, urb);
+	spin_unlock_irqrestore(&dev->vbi_mode.slock, flags);
+
+	/* Reset status */
+	urb->status = 0;
+
+	urb->status = usb_submit_urb(urb, GFP_ATOMIC);
+	if (urb->status) {
+		dev_err(dev->dev, "urb resubmit failed (error=%i)\n",
+			urb->status);
+	}
+}
+
+/*
+ * Stop and Deallocate URBs
+ */
+void cx231xx_uninit_vbi_isoc(struct cx231xx *dev)
+{
+	struct urb *urb;
+	int i;
+
+	dev_dbg(dev->dev, "called cx231xx_uninit_vbi_isoc\n");
+
+	dev->vbi_mode.bulk_ctl.nfields = -1;
+	for (i = 0; i < dev->vbi_mode.bulk_ctl.num_bufs; i++) {
+		urb = dev->vbi_mode.bulk_ctl.urb[i];
+		if (urb) {
+			if (!irqs_disabled())
+				usb_kill_urb(urb);
+			else
+				usb_unlink_urb(urb);
+
+			if (dev->vbi_mode.bulk_ctl.transfer_buffer[i]) {
+
+				kfree(dev->vbi_mode.bulk_ctl.
+				      transfer_buffer[i]);
+				dev->vbi_mode.bulk_ctl.transfer_buffer[i] =
+				    NULL;
+			}
+			usb_free_urb(urb);
+			dev->vbi_mode.bulk_ctl.urb[i] = NULL;
+		}
+		dev->vbi_mode.bulk_ctl.transfer_buffer[i] = NULL;
+	}
+
+	kfree(dev->vbi_mode.bulk_ctl.urb);
+	kfree(dev->vbi_mode.bulk_ctl.transfer_buffer);
+
+	dev->vbi_mode.bulk_ctl.urb = NULL;
+	dev->vbi_mode.bulk_ctl.transfer_buffer = NULL;
+	dev->vbi_mode.bulk_ctl.num_bufs = 0;
+
+	cx231xx_capture_start(dev, 0, Vbi);
+}
+EXPORT_SYMBOL_GPL(cx231xx_uninit_vbi_isoc);
+
+/*
+ * Allocate URBs and start IRQ
+ */
+int cx231xx_init_vbi_isoc(struct cx231xx *dev, int max_packets,
+			  int num_bufs, int max_pkt_size,
+			  int (*bulk_copy) (struct cx231xx *dev,
+					    struct urb *urb))
+{
+	struct cx231xx_dmaqueue *dma_q = &dev->vbi_mode.vidq;
+	int i;
+	int sb_size, pipe;
+	struct urb *urb;
+	int rc;
+
+	dev_dbg(dev->dev, "called cx231xx_vbi_isoc\n");
+
+	/* De-allocates all pending stuff */
+	cx231xx_uninit_vbi_isoc(dev);
+
+	/* clear if any halt */
+	usb_clear_halt(dev->udev,
+		       usb_rcvbulkpipe(dev->udev,
+				       dev->vbi_mode.end_point_addr));
+
+	dev->vbi_mode.bulk_ctl.bulk_copy = bulk_copy;
+	dev->vbi_mode.bulk_ctl.num_bufs = num_bufs;
+	dma_q->pos = 0;
+	dma_q->is_partial_line = 0;
+	dma_q->last_sav = 0;
+	dma_q->current_field = -1;
+	dma_q->bytes_left_in_line = dev->width << 1;
+	dma_q->lines_per_field = ((dev->norm & V4L2_STD_625_50) ?
+				  PAL_VBI_LINES : NTSC_VBI_LINES);
+	dma_q->lines_completed = 0;
+	for (i = 0; i < 8; i++)
+		dma_q->partial_buf[i] = 0;
+
+	dev->vbi_mode.bulk_ctl.urb = kcalloc(num_bufs, sizeof(void *),
+					     GFP_KERNEL);
+	if (!dev->vbi_mode.bulk_ctl.urb) {
+		dev_err(dev->dev,
+			"cannot alloc memory for usb buffers\n");
+		return -ENOMEM;
+	}
+
+	dev->vbi_mode.bulk_ctl.transfer_buffer =
+	    kcalloc(num_bufs, sizeof(void *), GFP_KERNEL);
+	if (!dev->vbi_mode.bulk_ctl.transfer_buffer) {
+		dev_err(dev->dev,
+			"cannot allocate memory for usbtransfer\n");
+		kfree(dev->vbi_mode.bulk_ctl.urb);
+		return -ENOMEM;
+	}
+
+	dev->vbi_mode.bulk_ctl.max_pkt_size = max_pkt_size;
+	dev->vbi_mode.bulk_ctl.buf = NULL;
+
+	sb_size = max_packets * dev->vbi_mode.bulk_ctl.max_pkt_size;
+
+	/* allocate urbs and transfer buffers */
+	for (i = 0; i < dev->vbi_mode.bulk_ctl.num_bufs; i++) {
+
+		urb = usb_alloc_urb(0, GFP_KERNEL);
+		if (!urb) {
+			cx231xx_uninit_vbi_isoc(dev);
+			return -ENOMEM;
+		}
+		dev->vbi_mode.bulk_ctl.urb[i] = urb;
+		urb->transfer_flags = 0;
+
+		dev->vbi_mode.bulk_ctl.transfer_buffer[i] =
+		    kzalloc(sb_size, GFP_KERNEL);
+		if (!dev->vbi_mode.bulk_ctl.transfer_buffer[i]) {
+			dev_err(dev->dev,
+				"unable to allocate %i bytes for transfer buffer %i%s\n",
+				sb_size, i,
+				in_interrupt() ? " while in int" : "");
+			cx231xx_uninit_vbi_isoc(dev);
+			return -ENOMEM;
+		}
+
+		pipe = usb_rcvbulkpipe(dev->udev, dev->vbi_mode.end_point_addr);
+		usb_fill_bulk_urb(urb, dev->udev, pipe,
+				  dev->vbi_mode.bulk_ctl.transfer_buffer[i],
+				  sb_size, cx231xx_irq_vbi_callback, dma_q);
+	}
+
+	init_waitqueue_head(&dma_q->wq);
+
+	/* submit urbs and enables IRQ */
+	for (i = 0; i < dev->vbi_mode.bulk_ctl.num_bufs; i++) {
+		rc = usb_submit_urb(dev->vbi_mode.bulk_ctl.urb[i], GFP_ATOMIC);
+		if (rc) {
+			dev_err(dev->dev,
+				"submit of urb %i failed (error=%i)\n", i, rc);
+			cx231xx_uninit_vbi_isoc(dev);
+			return rc;
+		}
+	}
+
+	cx231xx_capture_start(dev, 1, Vbi);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(cx231xx_init_vbi_isoc);
+
+u32 cx231xx_get_vbi_line(struct cx231xx *dev, struct cx231xx_dmaqueue *dma_q,
+			 u8 sav_eav, u8 *p_buffer, u32 buffer_size)
+{
+	u32 bytes_copied = 0;
+	int current_field = -1;
+
+	switch (sav_eav) {
+
+	case SAV_VBI_FIELD1:
+		current_field = 1;
+		break;
+
+	case SAV_VBI_FIELD2:
+		current_field = 2;
+		break;
+	default:
+		break;
+	}
+
+	if (current_field < 0)
+		return bytes_copied;
+
+	dma_q->last_sav = sav_eav;
+
+	bytes_copied =
+	    cx231xx_copy_vbi_line(dev, dma_q, p_buffer, buffer_size,
+				  current_field);
+
+	return bytes_copied;
+}
+
+/*
+ * Announces that a buffer were filled and request the next
+ */
+static inline void vbi_buffer_filled(struct cx231xx *dev,
+				     struct cx231xx_dmaqueue *dma_q,
+				     struct cx231xx_buffer *buf)
+{
+	/* Advice that buffer was filled */
+	/* dev_dbg(dev->dev, "[%p/%d] wakeup\n", buf, buf->vb.i); */
+
+	buf->vb.state = VIDEOBUF_DONE;
+	buf->vb.field_count++;
+	v4l2_get_timestamp(&buf->vb.ts);
+
+	dev->vbi_mode.bulk_ctl.buf = NULL;
+
+	list_del(&buf->vb.queue);
+	wake_up(&buf->vb.done);
+}
+
+u32 cx231xx_copy_vbi_line(struct cx231xx *dev, struct cx231xx_dmaqueue *dma_q,
+			  u8 *p_line, u32 length, int field_number)
+{
+	u32 bytes_to_copy;
+	struct cx231xx_buffer *buf;
+	u32 _line_size = dev->width * 2;
+
+	if (dma_q->current_field == -1) {
+		/* Just starting up */
+		cx231xx_reset_vbi_buffer(dev, dma_q);
+	}
+
+	if (dma_q->current_field != field_number)
+		dma_q->lines_completed = 0;
+
+	/* get the buffer pointer */
+	buf = dev->vbi_mode.bulk_ctl.buf;
+
+	/* Remember the field number for next time */
+	dma_q->current_field = field_number;
+
+	bytes_to_copy = dma_q->bytes_left_in_line;
+	if (bytes_to_copy > length)
+		bytes_to_copy = length;
+
+	if (dma_q->lines_completed >= dma_q->lines_per_field) {
+		dma_q->bytes_left_in_line -= bytes_to_copy;
+		dma_q->is_partial_line =
+		    (dma_q->bytes_left_in_line == 0) ? 0 : 1;
+		return 0;
+	}
+
+	dma_q->is_partial_line = 1;
+
+	/* If we don't have a buffer, just return the number of bytes we would
+	   have copied if we had a buffer. */
+	if (!buf) {
+		dma_q->bytes_left_in_line -= bytes_to_copy;
+		dma_q->is_partial_line =
+		    (dma_q->bytes_left_in_line == 0) ? 0 : 1;
+		return bytes_to_copy;
+	}
+
+	/* copy the data to video buffer */
+	cx231xx_do_vbi_copy(dev, dma_q, p_line, bytes_to_copy);
+
+	dma_q->pos += bytes_to_copy;
+	dma_q->bytes_left_in_line -= bytes_to_copy;
+
+	if (dma_q->bytes_left_in_line == 0) {
+
+		dma_q->bytes_left_in_line = _line_size;
+		dma_q->lines_completed++;
+		dma_q->is_partial_line = 0;
+
+		if (cx231xx_is_vbi_buffer_done(dev, dma_q) && buf) {
+
+			vbi_buffer_filled(dev, dma_q, buf);
+
+			dma_q->pos = 0;
+			dma_q->lines_completed = 0;
+			cx231xx_reset_vbi_buffer(dev, dma_q);
+		}
+	}
+
+	return bytes_to_copy;
+}
+
+/*
+ * video-buf generic routine to get the next available buffer
+ */
+static inline void get_next_vbi_buf(struct cx231xx_dmaqueue *dma_q,
+				    struct cx231xx_buffer **buf)
+{
+	struct cx231xx_video_mode *vmode =
+	    container_of(dma_q, struct cx231xx_video_mode, vidq);
+	struct cx231xx *dev = container_of(vmode, struct cx231xx, vbi_mode);
+	char *outp;
+
+	if (list_empty(&dma_q->active)) {
+		dev_err(dev->dev, "No active queue to serve\n");
+		dev->vbi_mode.bulk_ctl.buf = NULL;
+		*buf = NULL;
+		return;
+	}
+
+	/* Get the next buffer */
+	*buf = list_entry(dma_q->active.next, struct cx231xx_buffer, vb.queue);
+
+	/* Cleans up buffer - Useful for testing for frame/URB loss */
+	outp = videobuf_to_vmalloc(&(*buf)->vb);
+	memset(outp, 0, (*buf)->vb.size);
+
+	dev->vbi_mode.bulk_ctl.buf = *buf;
+
+	return;
+}
+
+void cx231xx_reset_vbi_buffer(struct cx231xx *dev,
+			      struct cx231xx_dmaqueue *dma_q)
+{
+	struct cx231xx_buffer *buf;
+
+	buf = dev->vbi_mode.bulk_ctl.buf;
+
+	if (buf == NULL) {
+		/* first try to get the buffer */
+		get_next_vbi_buf(dma_q, &buf);
+
+		dma_q->pos = 0;
+		dma_q->current_field = -1;
+	}
+
+	dma_q->bytes_left_in_line = dev->width << 1;
+	dma_q->lines_completed = 0;
+}
+
+int cx231xx_do_vbi_copy(struct cx231xx *dev, struct cx231xx_dmaqueue *dma_q,
+			u8 *p_buffer, u32 bytes_to_copy)
+{
+	u8 *p_out_buffer = NULL;
+	u32 current_line_bytes_copied = 0;
+	struct cx231xx_buffer *buf;
+	u32 _line_size = dev->width << 1;
+	void *startwrite;
+	int offset, lencopy;
+
+	buf = dev->vbi_mode.bulk_ctl.buf;
+
+	if (buf == NULL)
+		return -EINVAL;
+
+	p_out_buffer = videobuf_to_vmalloc(&buf->vb);
+
+	if (dma_q->bytes_left_in_line != _line_size) {
+		current_line_bytes_copied =
+		    _line_size - dma_q->bytes_left_in_line;
+	}
+
+	offset = (dma_q->lines_completed * _line_size) +
+		 current_line_bytes_copied;
+
+	if (dma_q->current_field == 2) {
+		/* Populate the second half of the frame */
+		offset += (dev->width * 2 * dma_q->lines_per_field);
+	}
+
+	/* prepare destination address */
+	startwrite = p_out_buffer + offset;
+
+	lencopy = dma_q->bytes_left_in_line > bytes_to_copy ?
+		  bytes_to_copy : dma_q->bytes_left_in_line;
+
+	memcpy(startwrite, p_buffer, lencopy);
+
+	return 0;
+}
+
+u8 cx231xx_is_vbi_buffer_done(struct cx231xx *dev,
+			      struct cx231xx_dmaqueue *dma_q)
+{
+	u32 height = 0;
+
+	height = ((dev->norm & V4L2_STD_625_50) ?
+		  PAL_VBI_LINES : NTSC_VBI_LINES);
+	if (dma_q->lines_completed == height && dma_q->current_field == 2)
+		return 1;
+	else
+		return 0;
+}
diff --git a/drivers/media/usb/cx231xx/cx231xx-vbi.h b/drivers/media/usb/cx231xx/cx231xx-vbi.h
new file mode 100644
index 0000000..b33d2bd
--- /dev/null
+++ b/drivers/media/usb/cx231xx/cx231xx-vbi.h
@@ -0,0 +1,65 @@
+/*
+   cx231xx_vbi.h - driver for Conexant Cx23100/101/102 USB video capture devices
+
+   Copyright (C) 2008 <srinivasa.deevi at conexant dot com>
+		Based on cx88 driver
+
+   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 _CX231XX_VBI_H
+#define _CX231XX_VBI_H
+
+extern const struct videobuf_queue_ops cx231xx_vbi_qops;
+
+#define   NTSC_VBI_START_LINE 10	/* line 10 - 21 */
+#define   NTSC_VBI_END_LINE   21
+#define   NTSC_VBI_LINES	  (NTSC_VBI_END_LINE-NTSC_VBI_START_LINE+1)
+
+#define   PAL_VBI_START_LINE  6
+#define   PAL_VBI_END_LINE    23
+#define   PAL_VBI_LINES       (PAL_VBI_END_LINE-PAL_VBI_START_LINE+1)
+
+#define   VBI_STRIDE            1440
+#define   VBI_SAMPLES_PER_LINE  1440
+
+#define   CX231XX_NUM_VBI_PACKETS       4
+#define   CX231XX_NUM_VBI_BUFS          5
+
+/* stream functions */
+int cx231xx_init_vbi_isoc(struct cx231xx *dev, int max_packets,
+			  int num_bufs, int max_pkt_size,
+			  int (*bulk_copy) (struct cx231xx *dev,
+					    struct urb *urb));
+
+void cx231xx_uninit_vbi_isoc(struct cx231xx *dev);
+
+/* vbi data copy functions */
+u32 cx231xx_get_vbi_line(struct cx231xx *dev, struct cx231xx_dmaqueue *dma_q,
+			 u8 sav_eav, u8 *p_buffer, u32 buffer_size);
+
+u32 cx231xx_copy_vbi_line(struct cx231xx *dev, struct cx231xx_dmaqueue *dma_q,
+			  u8 *p_line, u32 length, int field_number);
+
+void cx231xx_reset_vbi_buffer(struct cx231xx *dev,
+			      struct cx231xx_dmaqueue *dma_q);
+
+int cx231xx_do_vbi_copy(struct cx231xx *dev, struct cx231xx_dmaqueue *dma_q,
+			u8 *p_buffer, u32 bytes_to_copy);
+
+u8 cx231xx_is_vbi_buffer_done(struct cx231xx *dev,
+			      struct cx231xx_dmaqueue *dma_q);
+
+#endif
diff --git a/drivers/media/usb/cx231xx/cx231xx-video.c b/drivers/media/usb/cx231xx/cx231xx-video.c
new file mode 100644
index 0000000..f7fcd73
--- /dev/null
+++ b/drivers/media/usb/cx231xx/cx231xx-video.c
@@ -0,0 +1,2284 @@
+/*
+   cx231xx-video.c - driver for Conexant Cx23100/101/102
+		     USB video capture devices
+
+   Copyright (C) 2008 <srinivasa.deevi at conexant dot com>
+	Based on em28xx driver
+	Based on cx23885 driver
+	Based on cx88 driver
+
+   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.
+ */
+
+#include "cx231xx.h"
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/bitmap.h>
+#include <linux/i2c.h>
+#include <linux/mm.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-event.h>
+#include <media/drv-intf/msp3400.h>
+#include <media/tuner.h>
+
+#include <media/dvb_frontend.h>
+
+#include "cx231xx-vbi.h"
+
+#define CX231XX_VERSION "0.0.3"
+
+#define DRIVER_AUTHOR   "Srinivasa Deevi <srinivasa.deevi@conexant.com>"
+#define DRIVER_DESC     "Conexant cx231xx based USB video device driver"
+
+#define cx231xx_videodbg(fmt, arg...) do {\
+	if (video_debug) \
+		printk(KERN_INFO "%s %s :"fmt, \
+			 dev->name, __func__ , ##arg); } while (0)
+
+static unsigned int isoc_debug;
+module_param(isoc_debug, int, 0644);
+MODULE_PARM_DESC(isoc_debug, "enable debug messages [isoc transfers]");
+
+#define cx231xx_isocdbg(fmt, arg...) \
+do {\
+	if (isoc_debug) { \
+		printk(KERN_INFO "%s %s :"fmt, \
+			 dev->name, __func__ , ##arg); \
+	} \
+  } while (0)
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+MODULE_VERSION(CX231XX_VERSION);
+
+static unsigned int card[]     = {[0 ... (CX231XX_MAXBOARDS - 1)] = UNSET };
+static unsigned int video_nr[] = {[0 ... (CX231XX_MAXBOARDS - 1)] = UNSET };
+static unsigned int vbi_nr[]   = {[0 ... (CX231XX_MAXBOARDS - 1)] = UNSET };
+static unsigned int radio_nr[] = {[0 ... (CX231XX_MAXBOARDS - 1)] = UNSET };
+
+module_param_array(card, int, NULL, 0444);
+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_PARM_DESC(card, "card type");
+MODULE_PARM_DESC(video_nr, "video device numbers");
+MODULE_PARM_DESC(vbi_nr, "vbi device numbers");
+MODULE_PARM_DESC(radio_nr, "radio device numbers");
+
+static unsigned int video_debug;
+module_param(video_debug, int, 0644);
+MODULE_PARM_DESC(video_debug, "enable debug messages [video]");
+
+/* supported video standards */
+static struct cx231xx_fmt format[] = {
+	{
+	 .name = "16bpp YUY2, 4:2:2, packed",
+	 .fourcc = V4L2_PIX_FMT_YUYV,
+	 .depth = 16,
+	 .reg = 0,
+	 },
+};
+
+
+static int cx231xx_enable_analog_tuner(struct cx231xx *dev)
+{
+#ifdef CONFIG_MEDIA_CONTROLLER
+	struct media_device *mdev = dev->media_dev;
+	struct media_entity  *entity, *decoder = NULL, *source;
+	struct media_link *link, *found_link = NULL;
+	int ret, active_links = 0;
+
+	if (!mdev)
+		return 0;
+
+	/*
+	 * This will find the tuner that is connected into the decoder.
+	 * Technically, this is not 100% correct, as the device may be
+	 * using an analog input instead of the tuner. However, as we can't
+	 * do DVB streaming while the DMA engine is being used for V4L2,
+	 * this should be enough for the actual needs.
+	 */
+	media_device_for_each_entity(entity, mdev) {
+		if (entity->function == MEDIA_ENT_F_ATV_DECODER) {
+			decoder = entity;
+			break;
+		}
+	}
+	if (!decoder)
+		return 0;
+
+	list_for_each_entry(link, &decoder->links, list) {
+		if (link->sink->entity == decoder) {
+			found_link = link;
+			if (link->flags & MEDIA_LNK_FL_ENABLED)
+				active_links++;
+			break;
+		}
+	}
+
+	if (active_links == 1 || !found_link)
+		return 0;
+
+	source = found_link->source->entity;
+	list_for_each_entry(link, &source->links, list) {
+		struct media_entity *sink;
+		int flags = 0;
+
+		sink = link->sink->entity;
+
+		if (sink == entity)
+			flags = MEDIA_LNK_FL_ENABLED;
+
+		ret = media_entity_setup_link(link, flags);
+		if (ret) {
+			dev_err(dev->dev,
+				"Couldn't change link %s->%s to %s. Error %d\n",
+				source->name, sink->name,
+				flags ? "enabled" : "disabled",
+				ret);
+			return ret;
+		} else
+			dev_dbg(dev->dev,
+				"link %s->%s was %s\n",
+				source->name, sink->name,
+				flags ? "ENABLED" : "disabled");
+	}
+#endif
+	return 0;
+}
+
+/* ------------------------------------------------------------------
+	Video buffer and parser functions
+   ------------------------------------------------------------------*/
+
+/*
+ * Announces that a buffer were filled and request the next
+ */
+static inline void buffer_filled(struct cx231xx *dev,
+				 struct cx231xx_dmaqueue *dma_q,
+				 struct cx231xx_buffer *buf)
+{
+	/* Advice that buffer was filled */
+	cx231xx_isocdbg("[%p/%d] wakeup\n", buf, buf->vb.i);
+	buf->vb.state = VIDEOBUF_DONE;
+	buf->vb.field_count++;
+	v4l2_get_timestamp(&buf->vb.ts);
+
+	if (dev->USE_ISO)
+		dev->video_mode.isoc_ctl.buf = NULL;
+	else
+		dev->video_mode.bulk_ctl.buf = NULL;
+
+	list_del(&buf->vb.queue);
+	wake_up(&buf->vb.done);
+}
+
+static inline void print_err_status(struct cx231xx *dev, int packet, int status)
+{
+	char *errmsg = "Unknown";
+
+	switch (status) {
+	case -ENOENT:
+		errmsg = "unlinked synchronously";
+		break;
+	case -ECONNRESET:
+		errmsg = "unlinked asynchronously";
+		break;
+	case -ENOSR:
+		errmsg = "Buffer error (overrun)";
+		break;
+	case -EPIPE:
+		errmsg = "Stalled (device not responding)";
+		break;
+	case -EOVERFLOW:
+		errmsg = "Babble (bad cable?)";
+		break;
+	case -EPROTO:
+		errmsg = "Bit-stuff error (bad cable?)";
+		break;
+	case -EILSEQ:
+		errmsg = "CRC/Timeout (could be anything)";
+		break;
+	case -ETIME:
+		errmsg = "Device does not respond";
+		break;
+	}
+	if (packet < 0) {
+		cx231xx_isocdbg("URB status %d [%s].\n", status, errmsg);
+	} else {
+		cx231xx_isocdbg("URB packet %d, status %d [%s].\n",
+				packet, status, errmsg);
+	}
+}
+
+/*
+ * video-buf generic routine to get the next available buffer
+ */
+static inline void get_next_buf(struct cx231xx_dmaqueue *dma_q,
+				struct cx231xx_buffer **buf)
+{
+	struct cx231xx_video_mode *vmode =
+	    container_of(dma_q, struct cx231xx_video_mode, vidq);
+	struct cx231xx *dev = container_of(vmode, struct cx231xx, video_mode);
+
+	char *outp;
+
+	if (list_empty(&dma_q->active)) {
+		cx231xx_isocdbg("No active queue to serve\n");
+		if (dev->USE_ISO)
+			dev->video_mode.isoc_ctl.buf = NULL;
+		else
+			dev->video_mode.bulk_ctl.buf = NULL;
+		*buf = NULL;
+		return;
+	}
+
+	/* Get the next buffer */
+	*buf = list_entry(dma_q->active.next, struct cx231xx_buffer, vb.queue);
+
+	/* Cleans up buffer - Useful for testing for frame/URB loss */
+	outp = videobuf_to_vmalloc(&(*buf)->vb);
+	memset(outp, 0, (*buf)->vb.size);
+
+	if (dev->USE_ISO)
+		dev->video_mode.isoc_ctl.buf = *buf;
+	else
+		dev->video_mode.bulk_ctl.buf = *buf;
+
+	return;
+}
+
+/*
+ * Controls the isoc copy of each urb packet
+ */
+static inline int cx231xx_isoc_copy(struct cx231xx *dev, struct urb *urb)
+{
+	struct cx231xx_dmaqueue *dma_q = urb->context;
+	int i;
+	unsigned char *p_buffer;
+	u32 bytes_parsed = 0, buffer_size = 0;
+	u8 sav_eav = 0;
+
+	if (!dev)
+		return 0;
+
+	if (dev->state & DEV_DISCONNECTED)
+		return 0;
+
+	if (urb->status < 0) {
+		print_err_status(dev, -1, urb->status);
+		if (urb->status == -ENOENT)
+			return 0;
+	}
+
+	for (i = 0; i < urb->number_of_packets; i++) {
+		int status = urb->iso_frame_desc[i].status;
+
+		if (status < 0) {
+			print_err_status(dev, i, status);
+			if (urb->iso_frame_desc[i].status != -EPROTO)
+				continue;
+		}
+
+		if (urb->iso_frame_desc[i].actual_length <= 0) {
+			/* cx231xx_isocdbg("packet %d is empty",i); - spammy */
+			continue;
+		}
+		if (urb->iso_frame_desc[i].actual_length >
+		    dev->video_mode.max_pkt_size) {
+			cx231xx_isocdbg("packet bigger than packet size");
+			continue;
+		}
+
+		/*  get buffer pointer and length */
+		p_buffer = urb->transfer_buffer + urb->iso_frame_desc[i].offset;
+		buffer_size = urb->iso_frame_desc[i].actual_length;
+		bytes_parsed = 0;
+
+		if (dma_q->is_partial_line) {
+			/* Handle the case of a partial line */
+			sav_eav = dma_q->last_sav;
+		} else {
+			/* Check for a SAV/EAV overlapping
+				the buffer boundary */
+			sav_eav =
+			    cx231xx_find_boundary_SAV_EAV(p_buffer,
+							  dma_q->partial_buf,
+							  &bytes_parsed);
+		}
+
+		sav_eav &= 0xF0;
+		/* Get the first line if we have some portion of an SAV/EAV from
+		   the last buffer or a partial line  */
+		if (sav_eav) {
+			bytes_parsed += cx231xx_get_video_line(dev, dma_q,
+				sav_eav,	/* SAV/EAV */
+				p_buffer + bytes_parsed,	/* p_buffer */
+				buffer_size - bytes_parsed);/* buf size */
+		}
+
+		/* Now parse data that is completely in this buffer */
+		/* dma_q->is_partial_line = 0;  */
+
+		while (bytes_parsed < buffer_size) {
+			u32 bytes_used = 0;
+
+			sav_eav = cx231xx_find_next_SAV_EAV(
+				p_buffer + bytes_parsed,	/* p_buffer */
+				buffer_size - bytes_parsed,	/* buf size */
+				&bytes_used);/* bytes used to get SAV/EAV */
+
+			bytes_parsed += bytes_used;
+
+			sav_eav &= 0xF0;
+			if (sav_eav && (bytes_parsed < buffer_size)) {
+				bytes_parsed += cx231xx_get_video_line(dev,
+					dma_q, sav_eav,	/* SAV/EAV */
+					p_buffer + bytes_parsed,/* p_buffer */
+					buffer_size - bytes_parsed);/*buf size*/
+			}
+		}
+
+		/* Save the last four bytes of the buffer so we can check the
+		   buffer boundary condition next time */
+		memcpy(dma_q->partial_buf, p_buffer + buffer_size - 4, 4);
+		bytes_parsed = 0;
+
+	}
+	return 1;
+}
+
+static inline int cx231xx_bulk_copy(struct cx231xx *dev, struct urb *urb)
+{
+	struct cx231xx_dmaqueue *dma_q = urb->context;
+	unsigned char *p_buffer;
+	u32 bytes_parsed = 0, buffer_size = 0;
+	u8 sav_eav = 0;
+
+	if (!dev)
+		return 0;
+
+	if (dev->state & DEV_DISCONNECTED)
+		return 0;
+
+	if (urb->status < 0) {
+		print_err_status(dev, -1, urb->status);
+		if (urb->status == -ENOENT)
+			return 0;
+	}
+
+	if (1) {
+
+		/*  get buffer pointer and length */
+		p_buffer = urb->transfer_buffer;
+		buffer_size = urb->actual_length;
+		bytes_parsed = 0;
+
+		if (dma_q->is_partial_line) {
+			/* Handle the case of a partial line */
+			sav_eav = dma_q->last_sav;
+		} else {
+			/* Check for a SAV/EAV overlapping
+				the buffer boundary */
+			sav_eav =
+			    cx231xx_find_boundary_SAV_EAV(p_buffer,
+							  dma_q->partial_buf,
+							  &bytes_parsed);
+		}
+
+		sav_eav &= 0xF0;
+		/* Get the first line if we have some portion of an SAV/EAV from
+		   the last buffer or a partial line  */
+		if (sav_eav) {
+			bytes_parsed += cx231xx_get_video_line(dev, dma_q,
+				sav_eav,	/* SAV/EAV */
+				p_buffer + bytes_parsed,	/* p_buffer */
+				buffer_size - bytes_parsed);/* buf size */
+		}
+
+		/* Now parse data that is completely in this buffer */
+		/* dma_q->is_partial_line = 0;  */
+
+		while (bytes_parsed < buffer_size) {
+			u32 bytes_used = 0;
+
+			sav_eav = cx231xx_find_next_SAV_EAV(
+				p_buffer + bytes_parsed,	/* p_buffer */
+				buffer_size - bytes_parsed,	/* buf size */
+				&bytes_used);/* bytes used to get SAV/EAV */
+
+			bytes_parsed += bytes_used;
+
+			sav_eav &= 0xF0;
+			if (sav_eav && (bytes_parsed < buffer_size)) {
+				bytes_parsed += cx231xx_get_video_line(dev,
+					dma_q, sav_eav,	/* SAV/EAV */
+					p_buffer + bytes_parsed,/* p_buffer */
+					buffer_size - bytes_parsed);/*buf size*/
+			}
+		}
+
+		/* Save the last four bytes of the buffer so we can check the
+		   buffer boundary condition next time */
+		memcpy(dma_q->partial_buf, p_buffer + buffer_size - 4, 4);
+		bytes_parsed = 0;
+
+	}
+	return 1;
+}
+
+
+u8 cx231xx_find_boundary_SAV_EAV(u8 *p_buffer, u8 *partial_buf,
+				 u32 *p_bytes_used)
+{
+	u32 bytes_used;
+	u8 boundary_bytes[8];
+	u8 sav_eav = 0;
+
+	*p_bytes_used = 0;
+
+	/* Create an array of the last 4 bytes of the last buffer and the first
+	   4 bytes of the current buffer. */
+
+	memcpy(boundary_bytes, partial_buf, 4);
+	memcpy(boundary_bytes + 4, p_buffer, 4);
+
+	/* Check for the SAV/EAV in the boundary buffer */
+	sav_eav = cx231xx_find_next_SAV_EAV((u8 *)&boundary_bytes, 8,
+					    &bytes_used);
+
+	if (sav_eav) {
+		/* found a boundary SAV/EAV.  Updates the bytes used to reflect
+		   only those used in the new buffer */
+		*p_bytes_used = bytes_used - 4;
+	}
+
+	return sav_eav;
+}
+
+u8 cx231xx_find_next_SAV_EAV(u8 *p_buffer, u32 buffer_size, u32 *p_bytes_used)
+{
+	u32 i;
+	u8 sav_eav = 0;
+
+	/*
+	 * Don't search if the buffer size is less than 4.  It causes a page
+	 * fault since buffer_size - 4 evaluates to a large number in that
+	 * case.
+	 */
+	if (buffer_size < 4) {
+		*p_bytes_used = buffer_size;
+		return 0;
+	}
+
+	for (i = 0; i < (buffer_size - 3); i++) {
+
+		if ((p_buffer[i] == 0xFF) &&
+		    (p_buffer[i + 1] == 0x00) && (p_buffer[i + 2] == 0x00)) {
+
+			*p_bytes_used = i + 4;
+			sav_eav = p_buffer[i + 3];
+			return sav_eav;
+		}
+	}
+
+	*p_bytes_used = buffer_size;
+	return 0;
+}
+
+u32 cx231xx_get_video_line(struct cx231xx *dev,
+			   struct cx231xx_dmaqueue *dma_q, u8 sav_eav,
+			   u8 *p_buffer, u32 buffer_size)
+{
+	u32 bytes_copied = 0;
+	int current_field = -1;
+
+	switch (sav_eav) {
+	case SAV_ACTIVE_VIDEO_FIELD1:
+		/* looking for skipped line which occurred in PAL 720x480 mode.
+		   In this case, there will be no active data contained
+		   between the SAV and EAV */
+		if ((buffer_size > 3) && (p_buffer[0] == 0xFF) &&
+		    (p_buffer[1] == 0x00) && (p_buffer[2] == 0x00) &&
+		    ((p_buffer[3] == EAV_ACTIVE_VIDEO_FIELD1) ||
+		     (p_buffer[3] == EAV_ACTIVE_VIDEO_FIELD2) ||
+		     (p_buffer[3] == EAV_VBLANK_FIELD1) ||
+		     (p_buffer[3] == EAV_VBLANK_FIELD2)))
+			return bytes_copied;
+		current_field = 1;
+		break;
+
+	case SAV_ACTIVE_VIDEO_FIELD2:
+		/* looking for skipped line which occurred in PAL 720x480 mode.
+		   In this case, there will be no active data contained between
+		   the SAV and EAV */
+		if ((buffer_size > 3) && (p_buffer[0] == 0xFF) &&
+		    (p_buffer[1] == 0x00) && (p_buffer[2] == 0x00) &&
+		    ((p_buffer[3] == EAV_ACTIVE_VIDEO_FIELD1) ||
+		     (p_buffer[3] == EAV_ACTIVE_VIDEO_FIELD2) ||
+		     (p_buffer[3] == EAV_VBLANK_FIELD1)       ||
+		     (p_buffer[3] == EAV_VBLANK_FIELD2)))
+			return bytes_copied;
+		current_field = 2;
+		break;
+	}
+
+	dma_q->last_sav = sav_eav;
+
+	bytes_copied = cx231xx_copy_video_line(dev, dma_q, p_buffer,
+					       buffer_size, current_field);
+
+	return bytes_copied;
+}
+
+u32 cx231xx_copy_video_line(struct cx231xx *dev,
+			    struct cx231xx_dmaqueue *dma_q, u8 *p_line,
+			    u32 length, int field_number)
+{
+	u32 bytes_to_copy;
+	struct cx231xx_buffer *buf;
+	u32 _line_size = dev->width * 2;
+
+	if (dma_q->current_field != field_number)
+		cx231xx_reset_video_buffer(dev, dma_q);
+
+	/* get the buffer pointer */
+	if (dev->USE_ISO)
+		buf = dev->video_mode.isoc_ctl.buf;
+	else
+		buf = dev->video_mode.bulk_ctl.buf;
+
+	/* Remember the field number for next time */
+	dma_q->current_field = field_number;
+
+	bytes_to_copy = dma_q->bytes_left_in_line;
+	if (bytes_to_copy > length)
+		bytes_to_copy = length;
+
+	if (dma_q->lines_completed >= dma_q->lines_per_field) {
+		dma_q->bytes_left_in_line -= bytes_to_copy;
+		dma_q->is_partial_line = (dma_q->bytes_left_in_line == 0) ?
+					  0 : 1;
+		return 0;
+	}
+
+	dma_q->is_partial_line = 1;
+
+	/* If we don't have a buffer, just return the number of bytes we would
+	   have copied if we had a buffer. */
+	if (!buf) {
+		dma_q->bytes_left_in_line -= bytes_to_copy;
+		dma_q->is_partial_line = (dma_q->bytes_left_in_line == 0)
+					 ? 0 : 1;
+		return bytes_to_copy;
+	}
+
+	/* copy the data to video buffer */
+	cx231xx_do_copy(dev, dma_q, p_line, bytes_to_copy);
+
+	dma_q->pos += bytes_to_copy;
+	dma_q->bytes_left_in_line -= bytes_to_copy;
+
+	if (dma_q->bytes_left_in_line == 0) {
+		dma_q->bytes_left_in_line = _line_size;
+		dma_q->lines_completed++;
+		dma_q->is_partial_line = 0;
+
+		if (cx231xx_is_buffer_done(dev, dma_q) && buf) {
+			buffer_filled(dev, dma_q, buf);
+
+			dma_q->pos = 0;
+			buf = NULL;
+			dma_q->lines_completed = 0;
+		}
+	}
+
+	return bytes_to_copy;
+}
+
+void cx231xx_reset_video_buffer(struct cx231xx *dev,
+				struct cx231xx_dmaqueue *dma_q)
+{
+	struct cx231xx_buffer *buf;
+
+	/* handle the switch from field 1 to field 2 */
+	if (dma_q->current_field == 1) {
+		if (dma_q->lines_completed >= dma_q->lines_per_field)
+			dma_q->field1_done = 1;
+		else
+			dma_q->field1_done = 0;
+	}
+
+	if (dev->USE_ISO)
+		buf = dev->video_mode.isoc_ctl.buf;
+	else
+		buf = dev->video_mode.bulk_ctl.buf;
+
+	if (buf == NULL) {
+		/* first try to get the buffer */
+		get_next_buf(dma_q, &buf);
+
+		dma_q->pos = 0;
+		dma_q->field1_done = 0;
+		dma_q->current_field = -1;
+	}
+
+	/* reset the counters */
+	dma_q->bytes_left_in_line = dev->width << 1;
+	dma_q->lines_completed = 0;
+}
+
+int cx231xx_do_copy(struct cx231xx *dev, struct cx231xx_dmaqueue *dma_q,
+		    u8 *p_buffer, u32 bytes_to_copy)
+{
+	u8 *p_out_buffer = NULL;
+	u32 current_line_bytes_copied = 0;
+	struct cx231xx_buffer *buf;
+	u32 _line_size = dev->width << 1;
+	void *startwrite;
+	int offset, lencopy;
+
+	if (dev->USE_ISO)
+		buf = dev->video_mode.isoc_ctl.buf;
+	else
+		buf = dev->video_mode.bulk_ctl.buf;
+
+	if (buf == NULL)
+		return -1;
+
+	p_out_buffer = videobuf_to_vmalloc(&buf->vb);
+
+	current_line_bytes_copied = _line_size - dma_q->bytes_left_in_line;
+
+	/* Offset field 2 one line from the top of the buffer */
+	offset = (dma_q->current_field == 1) ? 0 : _line_size;
+
+	/* Offset for field 2 */
+	startwrite = p_out_buffer + offset;
+
+	/* lines already completed in the current field */
+	startwrite += (dma_q->lines_completed * _line_size * 2);
+
+	/* bytes already completed in the current line */
+	startwrite += current_line_bytes_copied;
+
+	lencopy = dma_q->bytes_left_in_line > bytes_to_copy ?
+		  bytes_to_copy : dma_q->bytes_left_in_line;
+
+	if ((u8 *)(startwrite + lencopy) > (u8 *)(p_out_buffer + buf->vb.size))
+		return 0;
+
+	/* The below copies the UYVY data straight into video buffer */
+	cx231xx_swab((u16 *) p_buffer, (u16 *) startwrite, (u16) lencopy);
+
+	return 0;
+}
+
+void cx231xx_swab(u16 *from, u16 *to, u16 len)
+{
+	u16 i;
+
+	if (len <= 0)
+		return;
+
+	for (i = 0; i < len / 2; i++)
+		to[i] = (from[i] << 8) | (from[i] >> 8);
+}
+
+u8 cx231xx_is_buffer_done(struct cx231xx *dev, struct cx231xx_dmaqueue *dma_q)
+{
+	u8 buffer_complete = 0;
+
+	/* Dual field stream */
+	buffer_complete = ((dma_q->current_field == 2) &&
+			   (dma_q->lines_completed >= dma_q->lines_per_field) &&
+			    dma_q->field1_done);
+
+	return buffer_complete;
+}
+
+/* ------------------------------------------------------------------
+	Videobuf operations
+   ------------------------------------------------------------------*/
+
+static int
+buffer_setup(struct videobuf_queue *vq, unsigned int *count, unsigned int *size)
+{
+	struct cx231xx_fh *fh = vq->priv_data;
+	struct cx231xx *dev = fh->dev;
+
+	*size = (fh->dev->width * fh->dev->height * dev->format->depth + 7)>>3;
+	if (0 == *count)
+		*count = CX231XX_DEF_BUF;
+
+	if (*count < CX231XX_MIN_BUF)
+		*count = CX231XX_MIN_BUF;
+
+
+	cx231xx_enable_analog_tuner(dev);
+
+	return 0;
+}
+
+/* This is called *without* dev->slock held; please keep it that way */
+static void free_buffer(struct videobuf_queue *vq, struct cx231xx_buffer *buf)
+{
+	struct cx231xx_fh *fh = vq->priv_data;
+	struct cx231xx *dev = fh->dev;
+	unsigned long flags = 0;
+
+	BUG_ON(in_interrupt());
+
+	/* We used to wait for the buffer to finish here, but this didn't work
+	   because, as we were keeping the state as VIDEOBUF_QUEUED,
+	   videobuf_queue_cancel marked it as finished for us.
+	   (Also, it could wedge forever if the hardware was misconfigured.)
+
+	   This should be safe; by the time we get here, the buffer isn't
+	   queued anymore. If we ever start marking the buffers as
+	   VIDEOBUF_ACTIVE, it won't be, though.
+	 */
+	spin_lock_irqsave(&dev->video_mode.slock, flags);
+	if (dev->USE_ISO) {
+		if (dev->video_mode.isoc_ctl.buf == buf)
+			dev->video_mode.isoc_ctl.buf = NULL;
+	} else {
+		if (dev->video_mode.bulk_ctl.buf == buf)
+			dev->video_mode.bulk_ctl.buf = NULL;
+	}
+	spin_unlock_irqrestore(&dev->video_mode.slock, flags);
+
+	videobuf_vmalloc_free(&buf->vb);
+	buf->vb.state = VIDEOBUF_NEEDS_INIT;
+}
+
+static int
+buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb,
+	       enum v4l2_field field)
+{
+	struct cx231xx_fh *fh = vq->priv_data;
+	struct cx231xx_buffer *buf =
+	    container_of(vb, struct cx231xx_buffer, vb);
+	struct cx231xx *dev = fh->dev;
+	int rc = 0, urb_init = 0;
+
+	/* The only currently supported format is 16 bits/pixel */
+	buf->vb.size = (fh->dev->width * fh->dev->height * dev->format->depth
+			+ 7) >> 3;
+	if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size)
+		return -EINVAL;
+
+	buf->vb.width = dev->width;
+	buf->vb.height = dev->height;
+	buf->vb.field = field;
+
+	if (VIDEOBUF_NEEDS_INIT == buf->vb.state) {
+		rc = videobuf_iolock(vq, &buf->vb, NULL);
+		if (rc < 0)
+			goto fail;
+	}
+
+	if (dev->USE_ISO) {
+		if (!dev->video_mode.isoc_ctl.num_bufs)
+			urb_init = 1;
+	} else {
+		if (!dev->video_mode.bulk_ctl.num_bufs)
+			urb_init = 1;
+	}
+	dev_dbg(dev->dev,
+		"urb_init=%d dev->video_mode.max_pkt_size=%d\n",
+		urb_init, dev->video_mode.max_pkt_size);
+	if (urb_init) {
+		dev->mode_tv = 0;
+		if (dev->USE_ISO)
+			rc = cx231xx_init_isoc(dev, CX231XX_NUM_PACKETS,
+				       CX231XX_NUM_BUFS,
+				       dev->video_mode.max_pkt_size,
+				       cx231xx_isoc_copy);
+		else
+			rc = cx231xx_init_bulk(dev, CX231XX_NUM_PACKETS,
+				       CX231XX_NUM_BUFS,
+				       dev->video_mode.max_pkt_size,
+				       cx231xx_bulk_copy);
+		if (rc < 0)
+			goto fail;
+	}
+
+	buf->vb.state = VIDEOBUF_PREPARED;
+
+	return 0;
+
+fail:
+	free_buffer(vq, buf);
+	return rc;
+}
+
+static void buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb)
+{
+	struct cx231xx_buffer *buf =
+	    container_of(vb, struct cx231xx_buffer, vb);
+	struct cx231xx_fh *fh = vq->priv_data;
+	struct cx231xx *dev = fh->dev;
+	struct cx231xx_dmaqueue *vidq = &dev->video_mode.vidq;
+
+	buf->vb.state = VIDEOBUF_QUEUED;
+	list_add_tail(&buf->vb.queue, &vidq->active);
+
+}
+
+static void buffer_release(struct videobuf_queue *vq,
+			   struct videobuf_buffer *vb)
+{
+	struct cx231xx_buffer *buf =
+	    container_of(vb, struct cx231xx_buffer, vb);
+	struct cx231xx_fh *fh = vq->priv_data;
+	struct cx231xx *dev = (struct cx231xx *)fh->dev;
+
+	cx231xx_isocdbg("cx231xx: called buffer_release\n");
+
+	free_buffer(vq, buf);
+}
+
+static const struct videobuf_queue_ops cx231xx_video_qops = {
+	.buf_setup = buffer_setup,
+	.buf_prepare = buffer_prepare,
+	.buf_queue = buffer_queue,
+	.buf_release = buffer_release,
+};
+
+/*********************  v4l2 interface  **************************************/
+
+void video_mux(struct cx231xx *dev, int index)
+{
+	dev->video_input = index;
+	dev->ctl_ainput = INPUT(index)->amux;
+
+	cx231xx_set_video_input_mux(dev, index);
+
+	cx25840_call(dev, video, s_routing, INPUT(index)->vmux, 0, 0);
+
+	cx231xx_set_audio_input(dev, dev->ctl_ainput);
+
+	dev_dbg(dev->dev, "video_mux : %d\n", index);
+
+	/* do mode control overrides if required */
+	cx231xx_do_mode_ctrl_overrides(dev);
+}
+
+/* Usage lock check functions */
+static int res_get(struct cx231xx_fh *fh)
+{
+	struct cx231xx *dev = fh->dev;
+	int rc = 0;
+
+	/* This instance already has stream_on */
+	if (fh->stream_on)
+		return rc;
+
+	if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+		if (dev->stream_on)
+			return -EBUSY;
+		dev->stream_on = 1;
+	} else if (fh->type == V4L2_BUF_TYPE_VBI_CAPTURE) {
+		if (dev->vbi_stream_on)
+			return -EBUSY;
+		dev->vbi_stream_on = 1;
+	} else
+		return -EINVAL;
+
+	fh->stream_on = 1;
+
+	return rc;
+}
+
+static int res_check(struct cx231xx_fh *fh)
+{
+	return fh->stream_on;
+}
+
+static void res_free(struct cx231xx_fh *fh)
+{
+	struct cx231xx *dev = fh->dev;
+
+	fh->stream_on = 0;
+
+	if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		dev->stream_on = 0;
+	if (fh->type == V4L2_BUF_TYPE_VBI_CAPTURE)
+		dev->vbi_stream_on = 0;
+}
+
+static int check_dev(struct cx231xx *dev)
+{
+	if (dev->state & DEV_DISCONNECTED) {
+		dev_err(dev->dev, "v4l2 ioctl: device not present\n");
+		return -ENODEV;
+	}
+	return 0;
+}
+
+/* ------------------------------------------------------------------
+	IOCTL vidioc handling
+   ------------------------------------------------------------------*/
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+				struct v4l2_format *f)
+{
+	struct cx231xx_fh *fh = priv;
+	struct cx231xx *dev = fh->dev;
+
+	f->fmt.pix.width = dev->width;
+	f->fmt.pix.height = dev->height;
+	f->fmt.pix.pixelformat = dev->format->fourcc;
+	f->fmt.pix.bytesperline = (dev->width * dev->format->depth + 7) >> 3;
+	f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * dev->height;
+	f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+
+	f->fmt.pix.field = V4L2_FIELD_INTERLACED;
+
+	return 0;
+}
+
+static struct cx231xx_fmt *format_by_fourcc(unsigned int fourcc)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(format); i++)
+		if (format[i].fourcc == fourcc)
+			return &format[i];
+
+	return NULL;
+}
+
+static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
+				  struct v4l2_format *f)
+{
+	struct cx231xx_fh *fh = priv;
+	struct cx231xx *dev = fh->dev;
+	unsigned int width = f->fmt.pix.width;
+	unsigned int height = f->fmt.pix.height;
+	unsigned int maxw = norm_maxw(dev);
+	unsigned int maxh = norm_maxh(dev);
+	struct cx231xx_fmt *fmt;
+
+	fmt = format_by_fourcc(f->fmt.pix.pixelformat);
+	if (!fmt) {
+		cx231xx_videodbg("Fourcc format (%08x) invalid.\n",
+				 f->fmt.pix.pixelformat);
+		return -EINVAL;
+	}
+
+	/* width must even because of the YUYV format
+	   height must be even because of interlacing */
+	v4l_bound_align_image(&width, 48, maxw, 1, &height, 32, maxh, 1, 0);
+
+	f->fmt.pix.width = width;
+	f->fmt.pix.height = height;
+	f->fmt.pix.pixelformat = fmt->fourcc;
+	f->fmt.pix.bytesperline = (width * fmt->depth + 7) >> 3;
+	f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * height;
+	f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+	f->fmt.pix.field = V4L2_FIELD_INTERLACED;
+
+	return 0;
+}
+
+static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+				struct v4l2_format *f)
+{
+	struct cx231xx_fh *fh = priv;
+	struct cx231xx *dev = fh->dev;
+	int rc;
+	struct cx231xx_fmt *fmt;
+	struct v4l2_subdev_format format = {
+		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+	};
+
+	rc = check_dev(dev);
+	if (rc < 0)
+		return rc;
+
+	vidioc_try_fmt_vid_cap(file, priv, f);
+
+	fmt = format_by_fourcc(f->fmt.pix.pixelformat);
+	if (!fmt)
+		return -EINVAL;
+
+	if (videobuf_queue_is_busy(&fh->vb_vidq)) {
+		dev_err(dev->dev, "%s: queue busy\n", __func__);
+		return -EBUSY;
+	}
+
+	if (dev->stream_on && !fh->stream_on) {
+		dev_err(dev->dev,
+			"%s: device in use by another fh\n", __func__);
+		return -EBUSY;
+	}
+
+	/* set new image size */
+	dev->width = f->fmt.pix.width;
+	dev->height = f->fmt.pix.height;
+	dev->format = fmt;
+
+	v4l2_fill_mbus_format(&format.format, &f->fmt.pix, MEDIA_BUS_FMT_FIXED);
+	call_all(dev, pad, set_fmt, NULL, &format);
+	v4l2_fill_pix_format(&f->fmt.pix, &format.format);
+
+	return rc;
+}
+
+static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *id)
+{
+	struct cx231xx_fh *fh = priv;
+	struct cx231xx *dev = fh->dev;
+
+	*id = dev->norm;
+	return 0;
+}
+
+static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id norm)
+{
+	struct cx231xx_fh *fh = priv;
+	struct cx231xx *dev = fh->dev;
+	struct v4l2_subdev_format format = {
+		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+	};
+	int rc;
+
+	rc = check_dev(dev);
+	if (rc < 0)
+		return rc;
+
+	if (dev->norm == norm)
+		return 0;
+
+	if (videobuf_queue_is_busy(&fh->vb_vidq))
+		return -EBUSY;
+
+	dev->norm = norm;
+
+	/* Adjusts width/height, if needed */
+	dev->width = 720;
+	dev->height = (dev->norm & V4L2_STD_625_50) ? 576 : 480;
+
+	call_all(dev, video, s_std, dev->norm);
+
+	/* We need to reset basic properties in the decoder related to
+	   resolution (since a standard change effects things like the number
+	   of lines in VACT, etc) */
+	format.format.code = MEDIA_BUS_FMT_FIXED;
+	format.format.width = dev->width;
+	format.format.height = dev->height;
+	call_all(dev, pad, set_fmt, NULL, &format);
+
+	/* do mode control overrides */
+	cx231xx_do_mode_ctrl_overrides(dev);
+
+	return 0;
+}
+
+static const char *iname[] = {
+	[CX231XX_VMUX_COMPOSITE1] = "Composite1",
+	[CX231XX_VMUX_SVIDEO]     = "S-Video",
+	[CX231XX_VMUX_TELEVISION] = "Television",
+	[CX231XX_VMUX_CABLE]      = "Cable TV",
+	[CX231XX_VMUX_DVB]        = "DVB",
+};
+
+void cx231xx_v4l2_create_entities(struct cx231xx *dev)
+{
+#if defined(CONFIG_MEDIA_CONTROLLER)
+	int ret, i;
+
+	/* Create entities for each input connector */
+	for (i = 0; i < MAX_CX231XX_INPUT; i++) {
+		struct media_entity *ent = &dev->input_ent[i];
+
+		if (!INPUT(i)->type)
+			break;
+
+		ent->name = iname[INPUT(i)->type];
+		ent->flags = MEDIA_ENT_FL_CONNECTOR;
+		dev->input_pad[i].flags = MEDIA_PAD_FL_SOURCE;
+
+		switch (INPUT(i)->type) {
+		case CX231XX_VMUX_COMPOSITE1:
+			ent->function = MEDIA_ENT_F_CONN_COMPOSITE;
+			break;
+		case CX231XX_VMUX_SVIDEO:
+			ent->function = MEDIA_ENT_F_CONN_SVIDEO;
+			break;
+		case CX231XX_VMUX_TELEVISION:
+		case CX231XX_VMUX_CABLE:
+		case CX231XX_VMUX_DVB:
+			/* The DVB core will handle it */
+			if (dev->tuner_type == TUNER_ABSENT)
+				continue;
+			/* fall through */
+		default: /* just to shut up a gcc warning */
+			ent->function = MEDIA_ENT_F_CONN_RF;
+			break;
+		}
+
+		ret = media_entity_pads_init(ent, 1, &dev->input_pad[i]);
+		if (ret < 0)
+			pr_err("failed to initialize input pad[%d]!\n", i);
+
+		ret = media_device_register_entity(dev->media_dev, ent);
+		if (ret < 0)
+			pr_err("failed to register input entity %d!\n", i);
+	}
+#endif
+}
+
+int cx231xx_enum_input(struct file *file, void *priv,
+			     struct v4l2_input *i)
+{
+	struct cx231xx_fh *fh = priv;
+	struct cx231xx *dev = fh->dev;
+	u32 gen_stat;
+	unsigned int n;
+	int ret;
+
+	n = i->index;
+	if (n >= MAX_CX231XX_INPUT)
+		return -EINVAL;
+	if (0 == INPUT(n)->type)
+		return -EINVAL;
+
+	i->index = n;
+	i->type = V4L2_INPUT_TYPE_CAMERA;
+
+	strcpy(i->name, iname[INPUT(n)->type]);
+
+	if ((CX231XX_VMUX_TELEVISION == INPUT(n)->type) ||
+	    (CX231XX_VMUX_CABLE == INPUT(n)->type))
+		i->type = V4L2_INPUT_TYPE_TUNER;
+
+	i->std = dev->vdev.tvnorms;
+
+	/* If they are asking about the active input, read signal status */
+	if (n == dev->video_input) {
+		ret = cx231xx_read_i2c_data(dev, VID_BLK_I2C_ADDRESS,
+					    GEN_STAT, 2, &gen_stat, 4);
+		if (ret > 0) {
+			if ((gen_stat & FLD_VPRES) == 0x00)
+				i->status |= V4L2_IN_ST_NO_SIGNAL;
+			if ((gen_stat & FLD_HLOCK) == 0x00)
+				i->status |= V4L2_IN_ST_NO_H_LOCK;
+		}
+	}
+
+	return 0;
+}
+
+int cx231xx_g_input(struct file *file, void *priv, unsigned int *i)
+{
+	struct cx231xx_fh *fh = priv;
+	struct cx231xx *dev = fh->dev;
+
+	*i = dev->video_input;
+
+	return 0;
+}
+
+int cx231xx_s_input(struct file *file, void *priv, unsigned int i)
+{
+	struct cx231xx_fh *fh = priv;
+	struct cx231xx *dev = fh->dev;
+	int rc;
+
+	dev->mode_tv = 0;
+	rc = check_dev(dev);
+	if (rc < 0)
+		return rc;
+
+	if (i >= MAX_CX231XX_INPUT)
+		return -EINVAL;
+	if (0 == INPUT(i)->type)
+		return -EINVAL;
+
+	video_mux(dev, i);
+
+	if (INPUT(i)->type == CX231XX_VMUX_TELEVISION ||
+	    INPUT(i)->type == CX231XX_VMUX_CABLE) {
+		/* There's a tuner, so reset the standard and put it on the
+		   last known frequency (since it was probably powered down
+		   until now */
+		call_all(dev, video, s_std, dev->norm);
+	}
+
+	return 0;
+}
+
+int cx231xx_g_tuner(struct file *file, void *priv, struct v4l2_tuner *t)
+{
+	struct cx231xx_fh *fh = priv;
+	struct cx231xx *dev = fh->dev;
+	int rc;
+
+	rc = check_dev(dev);
+	if (rc < 0)
+		return rc;
+
+	if (0 != t->index)
+		return -EINVAL;
+
+	strcpy(t->name, "Tuner");
+
+	t->type = V4L2_TUNER_ANALOG_TV;
+	t->capability = V4L2_TUNER_CAP_NORM;
+	t->rangehigh = 0xffffffffUL;
+	t->signal = 0xffff;	/* LOCKED */
+	call_all(dev, tuner, g_tuner, t);
+
+	return 0;
+}
+
+int cx231xx_s_tuner(struct file *file, void *priv, const struct v4l2_tuner *t)
+{
+	struct cx231xx_fh *fh = priv;
+	struct cx231xx *dev = fh->dev;
+	int rc;
+
+	rc = check_dev(dev);
+	if (rc < 0)
+		return rc;
+
+	if (0 != t->index)
+		return -EINVAL;
+#if 0
+	call_all(dev, tuner, s_tuner, t);
+#endif
+	return 0;
+}
+
+int cx231xx_g_frequency(struct file *file, void *priv,
+			      struct v4l2_frequency *f)
+{
+	struct cx231xx_fh *fh = priv;
+	struct cx231xx *dev = fh->dev;
+
+	if (f->tuner)
+		return -EINVAL;
+
+	f->frequency = dev->ctl_freq;
+
+	return 0;
+}
+
+int cx231xx_s_frequency(struct file *file, void *priv,
+			      const struct v4l2_frequency *f)
+{
+	struct cx231xx_fh *fh = priv;
+	struct cx231xx *dev = fh->dev;
+	struct v4l2_frequency new_freq = *f;
+	int rc;
+	u32 if_frequency = 5400000;
+
+	dev_dbg(dev->dev,
+		"Enter vidioc_s_frequency()f->frequency=%d;f->type=%d\n",
+		f->frequency, f->type);
+
+	rc = check_dev(dev);
+	if (rc < 0)
+		return rc;
+
+	if (0 != f->tuner)
+		return -EINVAL;
+
+	/* set pre channel change settings in DIF first */
+	rc = cx231xx_tuner_pre_channel_change(dev);
+
+	call_all(dev, tuner, s_frequency, f);
+	call_all(dev, tuner, g_frequency, &new_freq);
+	dev->ctl_freq = new_freq.frequency;
+
+	/* set post channel change settings in DIF first */
+	rc = cx231xx_tuner_post_channel_change(dev);
+
+	if (dev->tuner_type == TUNER_NXP_TDA18271) {
+		if (dev->norm & (V4L2_STD_MN | V4L2_STD_NTSC_443))
+			if_frequency = 5400000;  /*5.4MHz	*/
+		else if (dev->norm & V4L2_STD_B)
+			if_frequency = 6000000;  /*6.0MHz	*/
+		else if (dev->norm & (V4L2_STD_PAL_DK | V4L2_STD_SECAM_DK))
+			if_frequency = 6900000;  /*6.9MHz	*/
+		else if (dev->norm & V4L2_STD_GH)
+			if_frequency = 7100000;  /*7.1MHz	*/
+		else if (dev->norm & V4L2_STD_PAL_I)
+			if_frequency = 7250000;  /*7.25MHz	*/
+		else if (dev->norm & V4L2_STD_SECAM_L)
+			if_frequency = 6900000;  /*6.9MHz	*/
+		else if (dev->norm & V4L2_STD_SECAM_LC)
+			if_frequency = 1250000;  /*1.25MHz	*/
+
+		dev_dbg(dev->dev,
+			"if_frequency is set to %d\n", if_frequency);
+		cx231xx_set_Colibri_For_LowIF(dev, if_frequency, 1, 1);
+
+		update_HH_register_after_set_DIF(dev);
+	}
+
+	dev_dbg(dev->dev, "Set New FREQUENCY to %d\n", f->frequency);
+
+	return rc;
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+
+int cx231xx_g_chip_info(struct file *file, void *fh,
+			struct v4l2_dbg_chip_info *chip)
+{
+	switch (chip->match.addr) {
+	case 0:	/* Cx231xx - internal registers */
+		return 0;
+	case 1:	/* AFE - read byte */
+		strlcpy(chip->name, "AFE (byte)", sizeof(chip->name));
+		return 0;
+	case 2:	/* Video Block - read byte */
+		strlcpy(chip->name, "Video (byte)", sizeof(chip->name));
+		return 0;
+	case 3:	/* I2S block - read byte */
+		strlcpy(chip->name, "I2S (byte)", sizeof(chip->name));
+		return 0;
+	case 4: /* AFE - read dword */
+		strlcpy(chip->name, "AFE (dword)", sizeof(chip->name));
+		return 0;
+	case 5: /* Video Block - read dword */
+		strlcpy(chip->name, "Video (dword)", sizeof(chip->name));
+		return 0;
+	case 6: /* I2S Block - read dword */
+		strlcpy(chip->name, "I2S (dword)", sizeof(chip->name));
+		return 0;
+	}
+	return -EINVAL;
+}
+
+int cx231xx_g_register(struct file *file, void *priv,
+			     struct v4l2_dbg_register *reg)
+{
+	struct cx231xx_fh *fh = priv;
+	struct cx231xx *dev = fh->dev;
+	int ret;
+	u8 value[4] = { 0, 0, 0, 0 };
+	u32 data = 0;
+
+	switch (reg->match.addr) {
+	case 0:	/* Cx231xx - internal registers */
+		ret = cx231xx_read_ctrl_reg(dev, VRT_GET_REGISTER,
+				(u16)reg->reg, value, 4);
+		reg->val = value[0] | value[1] << 8 |
+			value[2] << 16 | value[3] << 24;
+		reg->size = 4;
+		break;
+	case 1:	/* AFE - read byte */
+		ret = cx231xx_read_i2c_data(dev, AFE_DEVICE_ADDRESS,
+				(u16)reg->reg, 2, &data, 1);
+		reg->val = data;
+		reg->size = 1;
+		break;
+	case 2:	/* Video Block - read byte */
+		ret = cx231xx_read_i2c_data(dev, VID_BLK_I2C_ADDRESS,
+				(u16)reg->reg, 2, &data, 1);
+		reg->val = data;
+		reg->size = 1;
+		break;
+	case 3:	/* I2S block - read byte */
+		ret = cx231xx_read_i2c_data(dev, I2S_BLK_DEVICE_ADDRESS,
+				(u16)reg->reg, 1, &data, 1);
+		reg->val = data;
+		reg->size = 1;
+		break;
+	case 4: /* AFE - read dword */
+		ret = cx231xx_read_i2c_data(dev, AFE_DEVICE_ADDRESS,
+				(u16)reg->reg, 2, &data, 4);
+		reg->val = data;
+		reg->size = 4;
+		break;
+	case 5: /* Video Block - read dword */
+		ret = cx231xx_read_i2c_data(dev, VID_BLK_I2C_ADDRESS,
+				(u16)reg->reg, 2, &data, 4);
+		reg->val = data;
+		reg->size = 4;
+		break;
+	case 6: /* I2S Block - read dword */
+		ret = cx231xx_read_i2c_data(dev, I2S_BLK_DEVICE_ADDRESS,
+				(u16)reg->reg, 1, &data, 4);
+		reg->val = data;
+		reg->size = 4;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return ret < 0 ? ret : 0;
+}
+
+int cx231xx_s_register(struct file *file, void *priv,
+			     const struct v4l2_dbg_register *reg)
+{
+	struct cx231xx_fh *fh = priv;
+	struct cx231xx *dev = fh->dev;
+	int ret;
+	u8 data[4] = { 0, 0, 0, 0 };
+
+	switch (reg->match.addr) {
+	case 0:	/* cx231xx internal registers */
+		data[0] = (u8) reg->val;
+		data[1] = (u8) (reg->val >> 8);
+		data[2] = (u8) (reg->val >> 16);
+		data[3] = (u8) (reg->val >> 24);
+		ret = cx231xx_write_ctrl_reg(dev, VRT_SET_REGISTER,
+				(u16)reg->reg, data, 4);
+		break;
+	case 1:	/* AFE - write byte */
+		ret = cx231xx_write_i2c_data(dev, AFE_DEVICE_ADDRESS,
+				(u16)reg->reg, 2, reg->val, 1);
+		break;
+	case 2:	/* Video Block - write byte */
+		ret = cx231xx_write_i2c_data(dev, VID_BLK_I2C_ADDRESS,
+				(u16)reg->reg, 2, reg->val, 1);
+		break;
+	case 3:	/* I2S block - write byte */
+		ret = cx231xx_write_i2c_data(dev, I2S_BLK_DEVICE_ADDRESS,
+				(u16)reg->reg, 1, reg->val, 1);
+		break;
+	case 4: /* AFE - write dword */
+		ret = cx231xx_write_i2c_data(dev, AFE_DEVICE_ADDRESS,
+				(u16)reg->reg, 2, reg->val, 4);
+		break;
+	case 5: /* Video Block - write dword */
+		ret = cx231xx_write_i2c_data(dev, VID_BLK_I2C_ADDRESS,
+				(u16)reg->reg, 2, reg->val, 4);
+		break;
+	case 6: /* I2S block - write dword */
+		ret = cx231xx_write_i2c_data(dev, I2S_BLK_DEVICE_ADDRESS,
+				(u16)reg->reg, 1, reg->val, 4);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return ret < 0 ? ret : 0;
+}
+#endif
+
+static int vidioc_cropcap(struct file *file, void *priv,
+			  struct v4l2_cropcap *cc)
+{
+	struct cx231xx_fh *fh = priv;
+	struct cx231xx *dev = fh->dev;
+	bool is_50hz = dev->norm & V4L2_STD_625_50;
+
+	if (cc->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	cc->bounds.left = 0;
+	cc->bounds.top = 0;
+	cc->bounds.width = dev->width;
+	cc->bounds.height = dev->height;
+	cc->defrect = cc->bounds;
+	cc->pixelaspect.numerator = is_50hz ? 54 : 11;
+	cc->pixelaspect.denominator = is_50hz ? 59 : 10;
+
+	return 0;
+}
+
+static int vidioc_streamon(struct file *file, void *priv,
+			   enum v4l2_buf_type type)
+{
+	struct cx231xx_fh *fh = priv;
+	struct cx231xx *dev = fh->dev;
+	int rc;
+
+	rc = check_dev(dev);
+	if (rc < 0)
+		return rc;
+
+	rc = res_get(fh);
+
+	if (likely(rc >= 0))
+		rc = videobuf_streamon(&fh->vb_vidq);
+
+	call_all(dev, video, s_stream, 1);
+
+	return rc;
+}
+
+static int vidioc_streamoff(struct file *file, void *priv,
+			    enum v4l2_buf_type type)
+{
+	struct cx231xx_fh *fh = priv;
+	struct cx231xx *dev = fh->dev;
+	int rc;
+
+	rc = check_dev(dev);
+	if (rc < 0)
+		return rc;
+
+	if (type != fh->type)
+		return -EINVAL;
+
+	cx25840_call(dev, video, s_stream, 0);
+
+	videobuf_streamoff(&fh->vb_vidq);
+	res_free(fh);
+
+	return 0;
+}
+
+int cx231xx_querycap(struct file *file, void *priv,
+			   struct v4l2_capability *cap)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct cx231xx_fh *fh = priv;
+	struct cx231xx *dev = fh->dev;
+
+	strlcpy(cap->driver, "cx231xx", sizeof(cap->driver));
+	strlcpy(cap->card, cx231xx_boards[dev->model].name, sizeof(cap->card));
+	usb_make_path(dev->udev, cap->bus_info, sizeof(cap->bus_info));
+
+	if (vdev->vfl_type == VFL_TYPE_RADIO)
+		cap->device_caps = V4L2_CAP_RADIO;
+	else {
+		cap->device_caps = V4L2_CAP_READWRITE | V4L2_CAP_STREAMING;
+		if (vdev->vfl_type == VFL_TYPE_VBI)
+			cap->device_caps |= V4L2_CAP_VBI_CAPTURE;
+		else
+			cap->device_caps |= V4L2_CAP_VIDEO_CAPTURE;
+	}
+	if (dev->tuner_type != TUNER_ABSENT)
+		cap->device_caps |= V4L2_CAP_TUNER;
+	cap->capabilities = cap->device_caps | V4L2_CAP_READWRITE |
+		V4L2_CAP_VBI_CAPTURE | V4L2_CAP_VIDEO_CAPTURE |
+		V4L2_CAP_STREAMING | V4L2_CAP_DEVICE_CAPS;
+	if (video_is_registered(&dev->radio_dev))
+		cap->capabilities |= V4L2_CAP_RADIO;
+
+	return 0;
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
+				   struct v4l2_fmtdesc *f)
+{
+	if (unlikely(f->index >= ARRAY_SIZE(format)))
+		return -EINVAL;
+
+	strlcpy(f->description, format[f->index].name, sizeof(f->description));
+	f->pixelformat = format[f->index].fourcc;
+
+	return 0;
+}
+
+/* RAW VBI ioctls */
+
+static int vidioc_g_fmt_vbi_cap(struct file *file, void *priv,
+				struct v4l2_format *f)
+{
+	struct cx231xx_fh *fh = priv;
+	struct cx231xx *dev = fh->dev;
+
+	f->fmt.vbi.sampling_rate = 6750000 * 4;
+	f->fmt.vbi.samples_per_line = VBI_LINE_LENGTH;
+	f->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY;
+	f->fmt.vbi.offset = 0;
+	f->fmt.vbi.start[0] = (dev->norm & V4L2_STD_625_50) ?
+	    PAL_VBI_START_LINE : NTSC_VBI_START_LINE;
+	f->fmt.vbi.count[0] = (dev->norm & V4L2_STD_625_50) ?
+	    PAL_VBI_LINES : NTSC_VBI_LINES;
+	f->fmt.vbi.start[1] = (dev->norm & V4L2_STD_625_50) ?
+	    PAL_VBI_START_LINE + 312 : NTSC_VBI_START_LINE + 263;
+	f->fmt.vbi.count[1] = f->fmt.vbi.count[0];
+	memset(f->fmt.vbi.reserved, 0, sizeof(f->fmt.vbi.reserved));
+
+	return 0;
+
+}
+
+static int vidioc_try_fmt_vbi_cap(struct file *file, void *priv,
+				  struct v4l2_format *f)
+{
+	struct cx231xx_fh *fh = priv;
+	struct cx231xx *dev = fh->dev;
+
+	f->fmt.vbi.sampling_rate = 6750000 * 4;
+	f->fmt.vbi.samples_per_line = VBI_LINE_LENGTH;
+	f->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY;
+	f->fmt.vbi.offset = 0;
+	f->fmt.vbi.flags = 0;
+	f->fmt.vbi.start[0] = (dev->norm & V4L2_STD_625_50) ?
+	    PAL_VBI_START_LINE : NTSC_VBI_START_LINE;
+	f->fmt.vbi.count[0] = (dev->norm & V4L2_STD_625_50) ?
+	    PAL_VBI_LINES : NTSC_VBI_LINES;
+	f->fmt.vbi.start[1] = (dev->norm & V4L2_STD_625_50) ?
+	    PAL_VBI_START_LINE + 312 : NTSC_VBI_START_LINE + 263;
+	f->fmt.vbi.count[1] = f->fmt.vbi.count[0];
+	memset(f->fmt.vbi.reserved, 0, sizeof(f->fmt.vbi.reserved));
+
+	return 0;
+
+}
+
+static int vidioc_s_fmt_vbi_cap(struct file *file, void *priv,
+				  struct v4l2_format *f)
+{
+	struct cx231xx_fh *fh = priv;
+	struct cx231xx *dev = fh->dev;
+
+	if (dev->vbi_stream_on && !fh->stream_on) {
+		dev_err(dev->dev,
+			"%s device in use by another fh\n", __func__);
+		return -EBUSY;
+	}
+	return vidioc_try_fmt_vbi_cap(file, priv, f);
+}
+
+static int vidioc_reqbufs(struct file *file, void *priv,
+			  struct v4l2_requestbuffers *rb)
+{
+	struct cx231xx_fh *fh = priv;
+	struct cx231xx *dev = fh->dev;
+	int rc;
+
+	rc = check_dev(dev);
+	if (rc < 0)
+		return rc;
+
+	return videobuf_reqbufs(&fh->vb_vidq, rb);
+}
+
+static int vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *b)
+{
+	struct cx231xx_fh *fh = priv;
+	struct cx231xx *dev = fh->dev;
+	int rc;
+
+	rc = check_dev(dev);
+	if (rc < 0)
+		return rc;
+
+	return videobuf_querybuf(&fh->vb_vidq, b);
+}
+
+static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *b)
+{
+	struct cx231xx_fh *fh = priv;
+	struct cx231xx *dev = fh->dev;
+	int rc;
+
+	rc = check_dev(dev);
+	if (rc < 0)
+		return rc;
+
+	return videobuf_qbuf(&fh->vb_vidq, b);
+}
+
+static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *b)
+{
+	struct cx231xx_fh *fh = priv;
+	struct cx231xx *dev = fh->dev;
+	int rc;
+
+	rc = check_dev(dev);
+	if (rc < 0)
+		return rc;
+
+	return videobuf_dqbuf(&fh->vb_vidq, b, file->f_flags & O_NONBLOCK);
+}
+
+/* ----------------------------------------------------------- */
+/* RADIO ESPECIFIC IOCTLS                                      */
+/* ----------------------------------------------------------- */
+
+static int radio_g_tuner(struct file *file, void *priv, struct v4l2_tuner *t)
+{
+	struct cx231xx *dev = ((struct cx231xx_fh *)priv)->dev;
+
+	if (t->index)
+		return -EINVAL;
+
+	strcpy(t->name, "Radio");
+
+	call_all(dev, tuner, g_tuner, t);
+
+	return 0;
+}
+static int radio_s_tuner(struct file *file, void *priv, const struct v4l2_tuner *t)
+{
+	struct cx231xx *dev = ((struct cx231xx_fh *)priv)->dev;
+
+	if (t->index)
+		return -EINVAL;
+
+	call_all(dev, tuner, s_tuner, t);
+
+	return 0;
+}
+
+/*
+ * cx231xx_v4l2_open()
+ * inits the device and starts isoc transfer
+ */
+static int cx231xx_v4l2_open(struct file *filp)
+{
+	int radio = 0;
+	struct video_device *vdev = video_devdata(filp);
+	struct cx231xx *dev = video_drvdata(filp);
+	struct cx231xx_fh *fh;
+	enum v4l2_buf_type fh_type = 0;
+
+	switch (vdev->vfl_type) {
+	case VFL_TYPE_GRABBER:
+		fh_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+		break;
+	case VFL_TYPE_VBI:
+		fh_type = V4L2_BUF_TYPE_VBI_CAPTURE;
+		break;
+	case VFL_TYPE_RADIO:
+		radio = 1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	cx231xx_videodbg("open dev=%s type=%s users=%d\n",
+			 video_device_node_name(vdev), v4l2_type_names[fh_type],
+			 dev->users);
+
+#if 0
+	errCode = cx231xx_set_mode(dev, CX231XX_ANALOG_MODE);
+	if (errCode < 0) {
+		dev_err(dev->dev,
+			"Device locked on digital mode. Can't open analog\n");
+		return -EBUSY;
+	}
+#endif
+
+	fh = kzalloc(sizeof(struct cx231xx_fh), GFP_KERNEL);
+	if (!fh)
+		return -ENOMEM;
+	if (mutex_lock_interruptible(&dev->lock)) {
+		kfree(fh);
+		return -ERESTARTSYS;
+	}
+	fh->dev = dev;
+	fh->type = fh_type;
+	filp->private_data = fh;
+	v4l2_fh_init(&fh->fh, vdev);
+
+	if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE && dev->users == 0) {
+		/* Power up in Analog TV mode */
+		if (dev->board.external_av)
+			cx231xx_set_power_mode(dev,
+				 POLARIS_AVMODE_ENXTERNAL_AV);
+		else
+			cx231xx_set_power_mode(dev, POLARIS_AVMODE_ANALOGT_TV);
+
+#if 0
+		cx231xx_set_mode(dev, CX231XX_ANALOG_MODE);
+#endif
+
+		/* set video alternate setting */
+		cx231xx_set_video_alternate(dev);
+
+		/* Needed, since GPIO might have disabled power of
+		   some i2c device */
+		cx231xx_config_i2c(dev);
+
+		/* device needs to be initialized before isoc transfer */
+		dev->video_input = dev->video_input > 2 ? 2 : dev->video_input;
+
+	}
+	if (radio) {
+		cx231xx_videodbg("video_open: setting radio device\n");
+
+		/* cx231xx_start_radio(dev); */
+
+		call_all(dev, tuner, s_radio);
+	}
+
+	dev->users++;
+
+	if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		videobuf_queue_vmalloc_init(&fh->vb_vidq, &cx231xx_video_qops,
+					    NULL, &dev->video_mode.slock,
+					    fh->type, V4L2_FIELD_INTERLACED,
+					    sizeof(struct cx231xx_buffer),
+					    fh, &dev->lock);
+	if (fh->type == V4L2_BUF_TYPE_VBI_CAPTURE) {
+		/* Set the required alternate setting  VBI interface works in
+		   Bulk mode only */
+		cx231xx_set_alt_setting(dev, INDEX_VANC, 0);
+
+		videobuf_queue_vmalloc_init(&fh->vb_vidq, &cx231xx_vbi_qops,
+					    NULL, &dev->vbi_mode.slock,
+					    fh->type, V4L2_FIELD_SEQ_TB,
+					    sizeof(struct cx231xx_buffer),
+					    fh, &dev->lock);
+	}
+	mutex_unlock(&dev->lock);
+	v4l2_fh_add(&fh->fh);
+
+	return 0;
+}
+
+/*
+ * cx231xx_realease_resources()
+ * unregisters the v4l2,i2c and usb devices
+ * called when the device gets disconected or at module unload
+*/
+void cx231xx_release_analog_resources(struct cx231xx *dev)
+{
+
+	/*FIXME: I2C IR should be disconnected */
+
+	if (video_is_registered(&dev->radio_dev))
+		video_unregister_device(&dev->radio_dev);
+	if (video_is_registered(&dev->vbi_dev)) {
+		dev_info(dev->dev, "V4L2 device %s deregistered\n",
+			video_device_node_name(&dev->vbi_dev));
+		video_unregister_device(&dev->vbi_dev);
+	}
+	if (video_is_registered(&dev->vdev)) {
+		dev_info(dev->dev, "V4L2 device %s deregistered\n",
+			video_device_node_name(&dev->vdev));
+
+		if (dev->board.has_417)
+			cx231xx_417_unregister(dev);
+
+		video_unregister_device(&dev->vdev);
+	}
+	v4l2_ctrl_handler_free(&dev->ctrl_handler);
+	v4l2_ctrl_handler_free(&dev->radio_ctrl_handler);
+}
+
+/*
+ * cx231xx_close()
+ * stops streaming and deallocates all resources allocated by the v4l2
+ * calls and ioctls
+ */
+static int cx231xx_close(struct file *filp)
+{
+	struct cx231xx_fh *fh = filp->private_data;
+	struct cx231xx *dev = fh->dev;
+
+	cx231xx_videodbg("users=%d\n", dev->users);
+
+	cx231xx_videodbg("users=%d\n", dev->users);
+	if (res_check(fh))
+		res_free(fh);
+
+	/*
+	 * To workaround error number=-71 on EP0 for VideoGrabber,
+	 *	 need exclude following.
+	 * FIXME: It is probably safe to remove most of these, as we're
+	 * now avoiding the alternate setting for INDEX_VANC
+	 */
+	if (!dev->board.no_alt_vanc)
+		if (fh->type == V4L2_BUF_TYPE_VBI_CAPTURE) {
+			videobuf_stop(&fh->vb_vidq);
+			videobuf_mmap_free(&fh->vb_vidq);
+
+			/* the device is already disconnect,
+			   free the remaining resources */
+			if (dev->state & DEV_DISCONNECTED) {
+				if (atomic_read(&dev->devlist_count) > 0) {
+					cx231xx_release_resources(dev);
+					fh->dev = NULL;
+					return 0;
+				}
+				return 0;
+			}
+
+			/* do this before setting alternate! */
+			cx231xx_uninit_vbi_isoc(dev);
+
+			/* set alternate 0 */
+			if (!dev->vbi_or_sliced_cc_mode)
+				cx231xx_set_alt_setting(dev, INDEX_VANC, 0);
+			else
+				cx231xx_set_alt_setting(dev, INDEX_HANC, 0);
+
+			v4l2_fh_del(&fh->fh);
+			v4l2_fh_exit(&fh->fh);
+			kfree(fh);
+			dev->users--;
+			wake_up_interruptible(&dev->open);
+			return 0;
+		}
+
+	v4l2_fh_del(&fh->fh);
+	dev->users--;
+	if (!dev->users) {
+		videobuf_stop(&fh->vb_vidq);
+		videobuf_mmap_free(&fh->vb_vidq);
+
+		/* the device is already disconnect,
+		   free the remaining resources */
+		if (dev->state & DEV_DISCONNECTED) {
+			cx231xx_release_resources(dev);
+			fh->dev = NULL;
+			return 0;
+		}
+
+		/* Save some power by putting tuner to sleep */
+		call_all(dev, tuner, standby);
+
+		/* do this before setting alternate! */
+		if (dev->USE_ISO)
+			cx231xx_uninit_isoc(dev);
+		else
+			cx231xx_uninit_bulk(dev);
+		cx231xx_set_mode(dev, CX231XX_SUSPEND);
+
+		/* set alternate 0 */
+		cx231xx_set_alt_setting(dev, INDEX_VIDEO, 0);
+	}
+	v4l2_fh_exit(&fh->fh);
+	kfree(fh);
+	wake_up_interruptible(&dev->open);
+	return 0;
+}
+
+static int cx231xx_v4l2_close(struct file *filp)
+{
+	struct cx231xx_fh *fh = filp->private_data;
+	struct cx231xx *dev = fh->dev;
+	int rc;
+
+	mutex_lock(&dev->lock);
+	rc = cx231xx_close(filp);
+	mutex_unlock(&dev->lock);
+	return rc;
+}
+
+/*
+ * cx231xx_v4l2_read()
+ * will allocate buffers when called for the first time
+ */
+static ssize_t
+cx231xx_v4l2_read(struct file *filp, char __user *buf, size_t count,
+		  loff_t *pos)
+{
+	struct cx231xx_fh *fh = filp->private_data;
+	struct cx231xx *dev = fh->dev;
+	int rc;
+
+	rc = check_dev(dev);
+	if (rc < 0)
+		return rc;
+
+	if ((fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) ||
+	    (fh->type == V4L2_BUF_TYPE_VBI_CAPTURE)) {
+		rc = res_get(fh);
+
+		if (unlikely(rc < 0))
+			return rc;
+
+		if (mutex_lock_interruptible(&dev->lock))
+			return -ERESTARTSYS;
+		rc = videobuf_read_stream(&fh->vb_vidq, buf, count, pos, 0,
+					    filp->f_flags & O_NONBLOCK);
+		mutex_unlock(&dev->lock);
+		return rc;
+	}
+	return 0;
+}
+
+/*
+ * cx231xx_v4l2_poll()
+ * will allocate buffers when called for the first time
+ */
+static __poll_t cx231xx_v4l2_poll(struct file *filp, poll_table *wait)
+{
+	__poll_t req_events = poll_requested_events(wait);
+	struct cx231xx_fh *fh = filp->private_data;
+	struct cx231xx *dev = fh->dev;
+	__poll_t res = 0;
+	int rc;
+
+	rc = check_dev(dev);
+	if (rc < 0)
+		return EPOLLERR;
+
+	rc = res_get(fh);
+
+	if (unlikely(rc < 0))
+		return EPOLLERR;
+
+	if (v4l2_event_pending(&fh->fh))
+		res |= EPOLLPRI;
+	else
+		poll_wait(filp, &fh->fh.wait, wait);
+
+	if (!(req_events & (EPOLLIN | EPOLLRDNORM)))
+		return res;
+
+	if ((V4L2_BUF_TYPE_VIDEO_CAPTURE == fh->type) ||
+	    (V4L2_BUF_TYPE_VBI_CAPTURE == fh->type)) {
+		mutex_lock(&dev->lock);
+		res |= videobuf_poll_stream(filp, &fh->vb_vidq, wait);
+		mutex_unlock(&dev->lock);
+		return res;
+	}
+	return res | EPOLLERR;
+}
+
+/*
+ * cx231xx_v4l2_mmap()
+ */
+static int cx231xx_v4l2_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+	struct cx231xx_fh *fh = filp->private_data;
+	struct cx231xx *dev = fh->dev;
+	int rc;
+
+	rc = check_dev(dev);
+	if (rc < 0)
+		return rc;
+
+	rc = res_get(fh);
+
+	if (unlikely(rc < 0))
+		return rc;
+
+	if (mutex_lock_interruptible(&dev->lock))
+		return -ERESTARTSYS;
+	rc = videobuf_mmap_mapper(&fh->vb_vidq, vma);
+	mutex_unlock(&dev->lock);
+
+	cx231xx_videodbg("vma start=0x%08lx, size=%ld, ret=%d\n",
+			 (unsigned long)vma->vm_start,
+			 (unsigned long)vma->vm_end -
+			 (unsigned long)vma->vm_start, rc);
+
+	return rc;
+}
+
+static const struct v4l2_file_operations cx231xx_v4l_fops = {
+	.owner   = THIS_MODULE,
+	.open    = cx231xx_v4l2_open,
+	.release = cx231xx_v4l2_close,
+	.read    = cx231xx_v4l2_read,
+	.poll    = cx231xx_v4l2_poll,
+	.mmap    = cx231xx_v4l2_mmap,
+	.unlocked_ioctl   = video_ioctl2,
+};
+
+static const struct v4l2_ioctl_ops video_ioctl_ops = {
+	.vidioc_querycap               = cx231xx_querycap,
+	.vidioc_enum_fmt_vid_cap       = vidioc_enum_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap          = vidioc_g_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap        = vidioc_try_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap          = vidioc_s_fmt_vid_cap,
+	.vidioc_g_fmt_vbi_cap          = vidioc_g_fmt_vbi_cap,
+	.vidioc_try_fmt_vbi_cap        = vidioc_try_fmt_vbi_cap,
+	.vidioc_s_fmt_vbi_cap          = vidioc_s_fmt_vbi_cap,
+	.vidioc_cropcap                = vidioc_cropcap,
+	.vidioc_reqbufs                = vidioc_reqbufs,
+	.vidioc_querybuf               = vidioc_querybuf,
+	.vidioc_qbuf                   = vidioc_qbuf,
+	.vidioc_dqbuf                  = vidioc_dqbuf,
+	.vidioc_s_std                  = vidioc_s_std,
+	.vidioc_g_std                  = vidioc_g_std,
+	.vidioc_enum_input             = cx231xx_enum_input,
+	.vidioc_g_input                = cx231xx_g_input,
+	.vidioc_s_input                = cx231xx_s_input,
+	.vidioc_streamon               = vidioc_streamon,
+	.vidioc_streamoff              = vidioc_streamoff,
+	.vidioc_g_tuner                = cx231xx_g_tuner,
+	.vidioc_s_tuner                = cx231xx_s_tuner,
+	.vidioc_g_frequency            = cx231xx_g_frequency,
+	.vidioc_s_frequency            = cx231xx_s_frequency,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.vidioc_g_chip_info            = cx231xx_g_chip_info,
+	.vidioc_g_register             = cx231xx_g_register,
+	.vidioc_s_register             = cx231xx_s_register,
+#endif
+	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static struct video_device cx231xx_vbi_template;
+
+static const struct video_device cx231xx_video_template = {
+	.fops         = &cx231xx_v4l_fops,
+	.release      = video_device_release_empty,
+	.ioctl_ops    = &video_ioctl_ops,
+	.tvnorms      = V4L2_STD_ALL,
+};
+
+static const struct v4l2_file_operations radio_fops = {
+	.owner   = THIS_MODULE,
+	.open   = cx231xx_v4l2_open,
+	.release = cx231xx_v4l2_close,
+	.poll = v4l2_ctrl_poll,
+	.unlocked_ioctl = video_ioctl2,
+};
+
+static const struct v4l2_ioctl_ops radio_ioctl_ops = {
+	.vidioc_querycap    = cx231xx_querycap,
+	.vidioc_g_tuner     = radio_g_tuner,
+	.vidioc_s_tuner     = radio_s_tuner,
+	.vidioc_g_frequency = cx231xx_g_frequency,
+	.vidioc_s_frequency = cx231xx_s_frequency,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.vidioc_g_chip_info = cx231xx_g_chip_info,
+	.vidioc_g_register  = cx231xx_g_register,
+	.vidioc_s_register  = cx231xx_s_register,
+#endif
+	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static struct video_device cx231xx_radio_template = {
+	.name      = "cx231xx-radio",
+	.fops      = &radio_fops,
+	.ioctl_ops = &radio_ioctl_ops,
+};
+
+/******************************** usb interface ******************************/
+
+static void cx231xx_vdev_init(struct cx231xx *dev,
+		struct video_device *vfd,
+		const struct video_device *template,
+		const char *type_name)
+{
+	*vfd = *template;
+	vfd->v4l2_dev = &dev->v4l2_dev;
+	vfd->release = video_device_release_empty;
+	vfd->lock = &dev->lock;
+
+	snprintf(vfd->name, sizeof(vfd->name), "%s %s", dev->name, type_name);
+
+	video_set_drvdata(vfd, dev);
+	if (dev->tuner_type == TUNER_ABSENT) {
+		v4l2_disable_ioctl(vfd, VIDIOC_G_FREQUENCY);
+		v4l2_disable_ioctl(vfd, VIDIOC_S_FREQUENCY);
+		v4l2_disable_ioctl(vfd, VIDIOC_G_TUNER);
+		v4l2_disable_ioctl(vfd, VIDIOC_S_TUNER);
+	}
+}
+
+int cx231xx_register_analog_devices(struct cx231xx *dev)
+{
+	int ret;
+
+	dev_info(dev->dev, "v4l2 driver version %s\n", CX231XX_VERSION);
+
+	/* set default norm */
+	dev->norm = V4L2_STD_PAL;
+	dev->width = norm_maxw(dev);
+	dev->height = norm_maxh(dev);
+	dev->interlaced = 0;
+
+	/* Analog specific initialization */
+	dev->format = &format[0];
+
+	/* Set the initial input */
+	video_mux(dev, dev->video_input);
+
+	call_all(dev, video, s_std, dev->norm);
+
+	v4l2_ctrl_handler_init(&dev->ctrl_handler, 10);
+	v4l2_ctrl_handler_init(&dev->radio_ctrl_handler, 5);
+
+	if (dev->sd_cx25840) {
+		v4l2_ctrl_add_handler(&dev->ctrl_handler,
+				dev->sd_cx25840->ctrl_handler, NULL);
+		v4l2_ctrl_add_handler(&dev->radio_ctrl_handler,
+				dev->sd_cx25840->ctrl_handler,
+				v4l2_ctrl_radio_filter);
+	}
+
+	if (dev->ctrl_handler.error)
+		return dev->ctrl_handler.error;
+	if (dev->radio_ctrl_handler.error)
+		return dev->radio_ctrl_handler.error;
+
+	/* enable vbi capturing */
+	/* write code here...  */
+
+	/* allocate and fill video video_device struct */
+	cx231xx_vdev_init(dev, &dev->vdev, &cx231xx_video_template, "video");
+#if defined(CONFIG_MEDIA_CONTROLLER)
+	dev->video_pad.flags = MEDIA_PAD_FL_SINK;
+	ret = media_entity_pads_init(&dev->vdev.entity, 1, &dev->video_pad);
+	if (ret < 0)
+		dev_err(dev->dev, "failed to initialize video media entity!\n");
+#endif
+	dev->vdev.ctrl_handler = &dev->ctrl_handler;
+	/* register v4l2 video video_device */
+	ret = video_register_device(&dev->vdev, VFL_TYPE_GRABBER,
+				    video_nr[dev->devno]);
+	if (ret) {
+		dev_err(dev->dev,
+			"unable to register video device (error=%i).\n",
+			ret);
+		return ret;
+	}
+
+	dev_info(dev->dev, "Registered video device %s [v4l2]\n",
+		video_device_node_name(&dev->vdev));
+
+	/* Initialize VBI template */
+	cx231xx_vbi_template = cx231xx_video_template;
+	strcpy(cx231xx_vbi_template.name, "cx231xx-vbi");
+
+	/* Allocate and fill vbi video_device struct */
+	cx231xx_vdev_init(dev, &dev->vbi_dev, &cx231xx_vbi_template, "vbi");
+
+#if defined(CONFIG_MEDIA_CONTROLLER)
+	dev->vbi_pad.flags = MEDIA_PAD_FL_SINK;
+	ret = media_entity_pads_init(&dev->vbi_dev.entity, 1, &dev->vbi_pad);
+	if (ret < 0)
+		dev_err(dev->dev, "failed to initialize vbi media entity!\n");
+#endif
+	dev->vbi_dev.ctrl_handler = &dev->ctrl_handler;
+	/* register v4l2 vbi video_device */
+	ret = video_register_device(&dev->vbi_dev, VFL_TYPE_VBI,
+				    vbi_nr[dev->devno]);
+	if (ret < 0) {
+		dev_err(dev->dev, "unable to register vbi device\n");
+		return ret;
+	}
+
+	dev_info(dev->dev, "Registered VBI device %s\n",
+		video_device_node_name(&dev->vbi_dev));
+
+	if (cx231xx_boards[dev->model].radio.type == CX231XX_RADIO) {
+		cx231xx_vdev_init(dev, &dev->radio_dev,
+				&cx231xx_radio_template, "radio");
+		dev->radio_dev.ctrl_handler = &dev->radio_ctrl_handler;
+		ret = video_register_device(&dev->radio_dev, VFL_TYPE_RADIO,
+					    radio_nr[dev->devno]);
+		if (ret < 0) {
+			dev_err(dev->dev,
+				"can't register radio device\n");
+			return ret;
+		}
+		dev_info(dev->dev, "Registered radio device as %s\n",
+			video_device_node_name(&dev->radio_dev));
+	}
+
+	return 0;
+}
diff --git a/drivers/media/usb/cx231xx/cx231xx.h b/drivers/media/usb/cx231xx/cx231xx.h
new file mode 100644
index 0000000..fa640bf
--- /dev/null
+++ b/drivers/media/usb/cx231xx/cx231xx.h
@@ -0,0 +1,1011 @@
+/*
+   cx231xx.h - driver for Conexant Cx23100/101/102 USB video capture devices
+
+   Copyright (C) 2008 <srinivasa.deevi at conexant dot com>
+	Based on em28xx driver
+
+   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 _CX231XX_H
+#define _CX231XX_H
+
+#include <linux/videodev2.h>
+#include <linux/types.h>
+#include <linux/ioctl.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+#include <linux/mutex.h>
+#include <linux/usb.h>
+
+#include <media/drv-intf/cx2341x.h>
+
+#include <media/videobuf-vmalloc.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-fh.h>
+#include <media/rc-core.h>
+#include <media/i2c/ir-kbd-i2c.h>
+
+#include "cx231xx-reg.h"
+#include "cx231xx-pcb-cfg.h"
+#include "cx231xx-conf-reg.h"
+
+#define DRIVER_NAME                     "cx231xx"
+#define PWR_SLEEP_INTERVAL              10
+
+/* I2C addresses for control block in Cx231xx */
+#define     AFE_DEVICE_ADDRESS		0x60
+#define     I2S_BLK_DEVICE_ADDRESS	0x98
+#define     VID_BLK_I2C_ADDRESS		0x88
+#define     VERVE_I2C_ADDRESS           0x40
+#define     DIF_USE_BASEBAND            0xFFFFFFFF
+
+/* Boards supported by driver */
+#define CX231XX_BOARD_UNKNOWN		    0
+#define CX231XX_BOARD_CNXT_CARRAERA	1
+#define CX231XX_BOARD_CNXT_SHELBY	2
+#define CX231XX_BOARD_CNXT_RDE_253S	3
+#define CX231XX_BOARD_CNXT_RDU_253S	4
+#define CX231XX_BOARD_CNXT_VIDEO_GRABBER	5
+#define CX231XX_BOARD_CNXT_RDE_250	6
+#define CX231XX_BOARD_CNXT_RDU_250	7
+#define CX231XX_BOARD_HAUPPAUGE_EXETER  8
+#define CX231XX_BOARD_HAUPPAUGE_USBLIVE2 9
+#define CX231XX_BOARD_PV_PLAYTV_USB_HYBRID 10
+#define CX231XX_BOARD_PV_XCAPTURE_USB 11
+#define CX231XX_BOARD_KWORLD_UB430_USB_HYBRID 12
+#define CX231XX_BOARD_ICONBIT_U100 13
+#define CX231XX_BOARD_HAUPPAUGE_USB2_FM_PAL 14
+#define CX231XX_BOARD_HAUPPAUGE_USB2_FM_NTSC 15
+#define CX231XX_BOARD_ELGATO_VIDEO_CAPTURE_V2 16
+#define CX231XX_BOARD_OTG102 17
+#define CX231XX_BOARD_KWORLD_UB445_USB_HYBRID 18
+#define CX231XX_BOARD_HAUPPAUGE_930C_HD_1113xx 19
+#define CX231XX_BOARD_HAUPPAUGE_930C_HD_1114xx 20
+#define CX231XX_BOARD_HAUPPAUGE_955Q 21
+#define CX231XX_BOARD_TERRATEC_GRABBY 22
+#define CX231XX_BOARD_EVROMEDIA_FULL_HYBRID_FULLHD 23
+#define CX231XX_BOARD_ASTROMETA_T2HYBRID 24
+#define CX231XX_BOARD_THE_IMAGING_SOURCE_DFG_USB2_PRO 25
+#define CX231XX_BOARD_HAUPPAUGE_935C 26
+#define CX231XX_BOARD_HAUPPAUGE_975 27
+
+/* Limits minimum and default number of buffers */
+#define CX231XX_MIN_BUF                 4
+#define CX231XX_DEF_BUF                 12
+#define CX231XX_DEF_VBI_BUF             6
+
+#define VBI_LINE_COUNT                  17
+#define VBI_LINE_LENGTH                 1440
+
+/*Limits the max URB message size */
+#define URB_MAX_CTRL_SIZE               80
+
+/* Params for validated field */
+#define CX231XX_BOARD_NOT_VALIDATED     1
+#define CX231XX_BOARD_VALIDATED		0
+
+/* maximum number of cx231xx boards */
+#define CX231XX_MAXBOARDS               8
+
+/* maximum number of frames that can be queued */
+#define CX231XX_NUM_FRAMES              5
+
+/* number of buffers for isoc transfers */
+#define CX231XX_NUM_BUFS                8
+
+/* number of packets for each buffer
+   windows requests only 40 packets .. so we better do the same
+   this is what I found out for all alternate numbers there!
+ */
+#define CX231XX_NUM_PACKETS             40
+
+/* default alternate; 0 means choose the best */
+#define CX231XX_PINOUT                  0
+
+#define CX231XX_INTERLACED_DEFAULT      1
+
+/* time to wait when stopping the isoc transfer */
+#define CX231XX_URB_TIMEOUT		\
+		msecs_to_jiffies(CX231XX_NUM_BUFS * CX231XX_NUM_PACKETS)
+
+#define CX231xx_NORMS (\
+	V4L2_STD_NTSC_M |  V4L2_STD_NTSC_M_JP |  V4L2_STD_NTSC_443 | \
+	V4L2_STD_PAL_BG |  V4L2_STD_PAL_DK    |  V4L2_STD_PAL_I    | \
+	V4L2_STD_PAL_M  |  V4L2_STD_PAL_N     |  V4L2_STD_PAL_Nc   | \
+	V4L2_STD_PAL_60 |  V4L2_STD_SECAM_L   |  V4L2_STD_SECAM_DK)
+
+#define SLEEP_S5H1432    30
+#define CX23417_OSC_EN   8
+#define CX23417_RESET    9
+
+struct cx23417_fmt {
+	char  *name;
+	u32   fourcc;          /* v4l2 format id */
+	int   depth;
+	int   flags;
+	u32   cxformat;
+};
+enum cx231xx_mode {
+	CX231XX_SUSPEND,
+	CX231XX_ANALOG_MODE,
+	CX231XX_DIGITAL_MODE,
+};
+
+enum cx231xx_std_mode {
+	CX231XX_TV_AIR = 0,
+	CX231XX_TV_CABLE
+};
+
+enum cx231xx_stream_state {
+	STREAM_OFF,
+	STREAM_INTERRUPT,
+	STREAM_ON,
+};
+
+struct cx231xx;
+
+struct cx231xx_isoc_ctl {
+	/* max packet size of isoc transaction */
+	int max_pkt_size;
+
+	/* number of allocated urbs */
+	int num_bufs;
+
+	/* urb for isoc transfers */
+	struct urb **urb;
+
+	/* transfer buffers for isoc transfer */
+	char **transfer_buffer;
+
+	/* Last buffer command and region */
+	u8 cmd;
+	int pos, size, pktsize;
+
+	/* Last field: ODD or EVEN? */
+	int field;
+
+	/* Stores incomplete commands */
+	u32 tmp_buf;
+	int tmp_buf_len;
+
+	/* Stores already requested buffers */
+	struct cx231xx_buffer *buf;
+
+	/* Stores the number of received fields */
+	int nfields;
+
+	/* isoc urb callback */
+	int (*isoc_copy) (struct cx231xx *dev, struct urb *urb);
+};
+
+struct cx231xx_bulk_ctl {
+	/* max packet size of bulk transaction */
+	int max_pkt_size;
+
+	/* number of allocated urbs */
+	int num_bufs;
+
+	/* urb for bulk transfers */
+	struct urb **urb;
+
+	/* transfer buffers for bulk transfer */
+	char **transfer_buffer;
+
+	/* Last buffer command and region */
+	u8 cmd;
+	int pos, size, pktsize;
+
+	/* Last field: ODD or EVEN? */
+	int field;
+
+	/* Stores incomplete commands */
+	u32 tmp_buf;
+	int tmp_buf_len;
+
+	/* Stores already requested buffers */
+	struct cx231xx_buffer *buf;
+
+	/* Stores the number of received fields */
+	int nfields;
+
+	/* bulk urb callback */
+	int (*bulk_copy) (struct cx231xx *dev, struct urb *urb);
+};
+
+struct cx231xx_fmt {
+	char *name;
+	u32 fourcc;		/* v4l2 format id */
+	int depth;
+	int reg;
+};
+
+/* buffer for one video frame */
+struct cx231xx_buffer {
+	/* common v4l buffer stuff -- must be first */
+	struct videobuf_buffer vb;
+
+	struct list_head frame;
+	int top_field;
+	int receiving;
+};
+
+enum ps_package_head {
+	CX231XX_NEED_ADD_PS_PACKAGE_HEAD = 0,
+	CX231XX_NONEED_PS_PACKAGE_HEAD
+};
+
+struct cx231xx_dmaqueue {
+	struct list_head active;
+	struct list_head queued;
+
+	wait_queue_head_t wq;
+
+	/* Counters to control buffer fill */
+	int pos;
+	u8 is_partial_line;
+	u8 partial_buf[8];
+	u8 last_sav;
+	int current_field;
+	u32 bytes_left_in_line;
+	u32 lines_completed;
+	u8 field1_done;
+	u32 lines_per_field;
+
+	/*Mpeg2 control buffer*/
+	u8 *p_left_data;
+	u32 left_data_count;
+	u8 mpeg_buffer_done;
+	u32 mpeg_buffer_completed;
+	enum ps_package_head add_ps_package_head;
+	char ps_head[10];
+};
+
+/* inputs */
+
+#define MAX_CX231XX_INPUT               4
+
+enum cx231xx_itype {
+	CX231XX_VMUX_COMPOSITE1 = 1,
+	CX231XX_VMUX_SVIDEO,
+	CX231XX_VMUX_TELEVISION,
+	CX231XX_VMUX_CABLE,
+	CX231XX_RADIO,
+	CX231XX_VMUX_DVB,
+};
+
+enum cx231xx_v_input {
+	CX231XX_VIN_1_1 = 0x1,
+	CX231XX_VIN_2_1,
+	CX231XX_VIN_3_1,
+	CX231XX_VIN_4_1,
+	CX231XX_VIN_1_2 = 0x01,
+	CX231XX_VIN_2_2,
+	CX231XX_VIN_3_2,
+	CX231XX_VIN_1_3 = 0x1,
+	CX231XX_VIN_2_3,
+	CX231XX_VIN_3_3,
+};
+
+/* cx231xx has two audio inputs: tuner and line in */
+enum cx231xx_amux {
+	/* This is the only entry for cx231xx tuner input */
+	CX231XX_AMUX_VIDEO,	/* cx231xx tuner */
+	CX231XX_AMUX_LINE_IN,	/* Line In */
+};
+
+struct cx231xx_reg_seq {
+	unsigned char bit;
+	unsigned char val;
+	int sleep;
+};
+
+struct cx231xx_input {
+	enum cx231xx_itype type;
+	unsigned int vmux;
+	enum cx231xx_amux amux;
+	struct cx231xx_reg_seq *gpio;
+};
+
+#define INPUT(nr) (&cx231xx_boards[dev->model].input[nr])
+
+enum cx231xx_decoder {
+	CX231XX_NODECODER,
+	CX231XX_AVDECODER
+};
+
+enum CX231XX_I2C_MASTER_PORT {
+	I2C_0 = 0,       /* master 0 - internal connection */
+	I2C_1 = 1,       /* master 1 - used with mux */
+	I2C_2 = 2,       /* master 2 */
+	I2C_1_MUX_1 = 3, /* master 1 - port 1 (I2C_DEMOD_EN = 0) */
+	I2C_1_MUX_3 = 4  /* master 1 - port 3 (I2C_DEMOD_EN = 1) */
+};
+
+struct cx231xx_board {
+	char *name;
+	int vchannels;
+	int tuner_type;
+	int tuner_addr;
+	v4l2_std_id norm;	/* tv norm */
+
+	/* demod related */
+	int demod_addr;
+	int demod_addr2;
+	u8 demod_xfer_mode;	/* 0 - Serial; 1 - parallel */
+
+	/* GPIO Pins */
+	struct cx231xx_reg_seq *dvb_gpio;
+	struct cx231xx_reg_seq *suspend_gpio;
+	struct cx231xx_reg_seq *tuner_gpio;
+		/* Negative means don't use it */
+	s8 tuner_sif_gpio;
+	s8 tuner_scl_gpio;
+	s8 tuner_sda_gpio;
+
+	/* PIN ctrl */
+	u32 ctl_pin_status_mask;
+	u8 agc_analog_digital_select_gpio;
+	u32 gpio_pin_status_mask;
+
+	/* i2c masters */
+	u8 tuner_i2c_master;
+	u8 demod_i2c_master;
+	u8 ir_i2c_master;
+
+	/* for devices with I2C chips for IR */
+	char *rc_map_name;
+
+	unsigned int max_range_640_480:1;
+	unsigned int has_dvb:1;
+	unsigned int has_417:1;
+	unsigned int valid:1;
+	unsigned int no_alt_vanc:1;
+	unsigned int external_av:1;
+
+	unsigned char xclk, i2c_speed;
+
+	enum cx231xx_decoder decoder;
+	int output_mode;
+
+	struct cx231xx_input input[MAX_CX231XX_INPUT];
+	struct cx231xx_input radio;
+	struct rc_map *ir_codes;
+};
+
+/* device states */
+enum cx231xx_dev_state {
+	DEV_INITIALIZED = 0x01,
+	DEV_DISCONNECTED = 0x02,
+};
+
+enum AFE_MODE {
+	AFE_MODE_LOW_IF,
+	AFE_MODE_BASEBAND,
+	AFE_MODE_EU_HI_IF,
+	AFE_MODE_US_HI_IF,
+	AFE_MODE_JAPAN_HI_IF
+};
+
+enum AUDIO_INPUT {
+	AUDIO_INPUT_MUTE,
+	AUDIO_INPUT_LINE,
+	AUDIO_INPUT_TUNER_TV,
+	AUDIO_INPUT_SPDIF,
+	AUDIO_INPUT_TUNER_FM
+};
+
+#define CX231XX_AUDIO_BUFS              5
+#define CX231XX_NUM_AUDIO_PACKETS       16
+#define CX231XX_ISO_NUM_AUDIO_PACKETS	64
+
+/* cx231xx extensions */
+#define CX231XX_AUDIO                   0x10
+#define CX231XX_DVB                     0x20
+
+struct cx231xx_audio {
+	char name[50];
+	char *transfer_buffer[CX231XX_AUDIO_BUFS];
+	struct urb *urb[CX231XX_AUDIO_BUFS];
+	struct usb_device *udev;
+	unsigned int capture_transfer_done;
+	struct snd_pcm_substream *capture_pcm_substream;
+
+	unsigned int hwptr_done_capture;
+	struct snd_card *sndcard;
+
+	int users, shutdown;
+	/* locks */
+	spinlock_t slock;
+
+	int alt;		/* alternate */
+	int max_pkt_size;	/* max packet size of isoc transaction */
+	int num_alt;		/* Number of alternative settings */
+	unsigned int *alt_max_pkt_size;	/* array of wMaxPacketSize */
+	u16 end_point_addr;
+};
+
+struct cx231xx;
+
+struct cx231xx_fh {
+	struct v4l2_fh fh;
+	struct cx231xx *dev;
+	unsigned int stream_on:1;	/* Locks streams */
+	enum v4l2_buf_type type;
+
+	struct videobuf_queue vb_vidq;
+
+	/* vbi capture */
+	struct videobuf_queue      vidq;
+	struct videobuf_queue      vbiq;
+
+	/* MPEG Encoder specifics ONLY */
+
+	atomic_t                   v4l_reading;
+};
+
+/*****************************************************************/
+/* set/get i2c */
+/* 00--1Mb/s, 01-400kb/s, 10--100kb/s, 11--5Mb/s */
+#define I2C_SPEED_1M            0x0
+#define I2C_SPEED_400K          0x1
+#define I2C_SPEED_100K          0x2
+#define I2C_SPEED_5M            0x3
+
+/* 0-- STOP transaction */
+#define I2C_STOP                0x0
+/* 1-- do not transmit STOP at end of transaction */
+#define I2C_NOSTOP              0x1
+/* 1--allow slave to insert clock wait states */
+#define I2C_SYNC                0x1
+
+struct cx231xx_i2c {
+	struct cx231xx *dev;
+
+	int nr;
+
+	/* i2c i/o */
+	struct i2c_adapter i2c_adap;
+	int i2c_rc;
+
+	/* different settings for each bus */
+	u8 i2c_period;
+	u8 i2c_nostop;
+	u8 i2c_reserve;
+};
+
+struct cx231xx_i2c_xfer_data {
+	u8 dev_addr;
+	u8 direction;		/* 1 - IN, 0 - OUT */
+	u8 saddr_len;		/* sub address len */
+	u16 saddr_dat;		/* sub addr data */
+	u8 buf_size;		/* buffer size */
+	u8 *p_buffer;		/* pointer to the buffer */
+};
+
+struct VENDOR_REQUEST_IN {
+	u8 bRequest;
+	u16 wValue;
+	u16 wIndex;
+	u16 wLength;
+	u8 direction;
+	u8 bData;
+	u8 *pBuff;
+};
+
+struct cx231xx_tvnorm {
+	char		*name;
+	v4l2_std_id	id;
+	u32		cxiformat;
+	u32		cxoformat;
+};
+
+enum TRANSFER_TYPE {
+	Raw_Video = 0,
+	Audio,
+	Vbi,			/* VANC */
+	Sliced_cc,		/* HANC */
+	TS1_serial_mode,
+	TS2,
+	TS1_parallel_mode
+} ;
+
+struct cx231xx_video_mode {
+	/* Isoc control struct */
+	struct cx231xx_dmaqueue vidq;
+	struct cx231xx_isoc_ctl isoc_ctl;
+	struct cx231xx_bulk_ctl bulk_ctl;
+	/* locks */
+	spinlock_t slock;
+
+	/* usb transfer */
+	int alt;		/* alternate */
+	int max_pkt_size;	/* max packet size of isoc transaction */
+	int num_alt;		/* Number of alternative settings */
+	unsigned int *alt_max_pkt_size;	/* array of wMaxPacketSize */
+	u16 end_point_addr;
+};
+
+struct cx231xx_tsport {
+	struct cx231xx *dev;
+
+	int                        nr;
+	int                        sram_chno;
+
+	/* dma queues */
+
+	u32                        ts_packet_size;
+	u32                        ts_packet_count;
+
+	int                        width;
+	int                        height;
+
+	/* locks */
+	spinlock_t                 slock;
+
+	/* registers */
+	u32                        reg_gpcnt;
+	u32                        reg_gpcnt_ctl;
+	u32                        reg_dma_ctl;
+	u32                        reg_lngth;
+	u32                        reg_hw_sop_ctrl;
+	u32                        reg_gen_ctrl;
+	u32                        reg_bd_pkt_status;
+	u32                        reg_sop_status;
+	u32                        reg_fifo_ovfl_stat;
+	u32                        reg_vld_misc;
+	u32                        reg_ts_clk_en;
+	u32                        reg_ts_int_msk;
+	u32                        reg_ts_int_stat;
+	u32                        reg_src_sel;
+
+	/* Default register vals */
+	int                        pci_irqmask;
+	u32                        dma_ctl_val;
+	u32                        ts_int_msk_val;
+	u32                        gen_ctrl_val;
+	u32                        ts_clk_en_val;
+	u32                        src_sel_val;
+	u32                        vld_misc_val;
+	u32                        hw_sop_ctrl_val;
+
+	/* Allow a single tsport to have multiple frontends */
+	u32                        num_frontends;
+	void                       *port_priv;
+};
+
+/* main device struct */
+struct cx231xx {
+	/* generic device properties */
+	char name[30];		/* name (including minor) of the device */
+	int model;		/* index in the device_data struct */
+	int devno;		/* marks the number of this device */
+	struct device *dev;	/* pointer to USB interface's dev */
+
+	struct cx231xx_board board;
+
+	/* For I2C IR support */
+	struct IR_i2c_init_data    init_data;
+	struct i2c_client          *ir_i2c_client;
+
+	unsigned int stream_on:1;	/* Locks streams */
+	unsigned int vbi_stream_on:1;	/* Locks streams for VBI */
+	unsigned int has_audio_class:1;
+	unsigned int has_alsa_audio:1;
+
+	unsigned int i2c_scan_running:1; /* true only during i2c_scan */
+
+	struct cx231xx_fmt *format;
+
+	struct v4l2_device v4l2_dev;
+	struct v4l2_subdev *sd_cx25840;
+	struct v4l2_subdev *sd_tuner;
+	struct v4l2_ctrl_handler ctrl_handler;
+	struct v4l2_ctrl_handler radio_ctrl_handler;
+	struct cx2341x_handler mpeg_ctrl_handler;
+
+	struct work_struct wq_trigger;		/* Trigger to start/stop audio for alsa module */
+	atomic_t	   stream_started;	/* stream should be running if true */
+
+	struct list_head devlist;
+
+	int tuner_type;		/* type of the tuner */
+	int tuner_addr;		/* tuner address */
+
+	/* I2C adapters: Master 1 & 2 (External) & Master 3 (Internal only) */
+	struct cx231xx_i2c i2c_bus[3];
+	struct i2c_mux_core *muxc;
+	struct i2c_adapter *i2c_mux_adap[2];
+
+	unsigned int xc_fw_load_done:1;
+	unsigned int port_3_switch_enabled:1;
+	/* locks */
+	struct mutex gpio_i2c_lock;
+	struct mutex i2c_lock;
+
+	/* video for linux */
+	int users;		/* user count for exclusive use */
+	struct video_device vdev;	/* video for linux device struct */
+	v4l2_std_id norm;	/* selected tv norm */
+	int ctl_freq;		/* selected frequency */
+	unsigned int ctl_ainput;	/* selected audio input */
+
+	/* frame properties */
+	int width;		/* current frame width */
+	int height;		/* current frame height */
+	int interlaced;		/* 1=interlace fileds, 0=just top fileds */
+
+	struct cx231xx_audio adev;
+
+	/* states */
+	enum cx231xx_dev_state state;
+
+	struct work_struct request_module_wk;
+
+	/* locks */
+	struct mutex lock;
+	struct mutex ctrl_urb_lock;	/* protects urb_buf */
+	struct list_head inqueue, outqueue;
+	wait_queue_head_t open, wait_frame, wait_stream;
+	struct video_device vbi_dev;
+	struct video_device radio_dev;
+
+#if defined(CONFIG_MEDIA_CONTROLLER)
+	struct media_device *media_dev;
+	struct media_pad video_pad, vbi_pad;
+	struct media_entity input_ent[MAX_CX231XX_INPUT];
+	struct media_pad input_pad[MAX_CX231XX_INPUT];
+#endif
+
+	unsigned char eedata[256];
+
+	struct cx231xx_video_mode video_mode;
+	struct cx231xx_video_mode vbi_mode;
+	struct cx231xx_video_mode sliced_cc_mode;
+	struct cx231xx_video_mode ts1_mode;
+
+	atomic_t devlist_count;
+
+	struct usb_device *udev;	/* the usb device */
+	char urb_buf[URB_MAX_CTRL_SIZE];	/* urb control msg buffer */
+
+	/* helper funcs that call usb_control_msg */
+	int (*cx231xx_read_ctrl_reg) (struct cx231xx *dev, u8 req, u16 reg,
+				      char *buf, int len);
+	int (*cx231xx_write_ctrl_reg) (struct cx231xx *dev, u8 req, u16 reg,
+				       char *buf, int len);
+	int (*cx231xx_send_usb_command) (struct cx231xx_i2c *i2c_bus,
+				struct cx231xx_i2c_xfer_data *req_data);
+	int (*cx231xx_gpio_i2c_read) (struct cx231xx *dev, u8 dev_addr,
+				      u8 *buf, u8 len);
+	int (*cx231xx_gpio_i2c_write) (struct cx231xx *dev, u8 dev_addr,
+				       u8 *buf, u8 len);
+
+	int (*cx231xx_set_analog_freq) (struct cx231xx *dev, u32 freq);
+	int (*cx231xx_reset_analog_tuner) (struct cx231xx *dev);
+
+	enum cx231xx_mode mode;
+
+	struct cx231xx_dvb *dvb;
+
+	/* Cx231xx supported PCB config's */
+	struct pcb_config current_pcb_config;
+	u8 current_scenario_idx;
+	u8 interface_count;
+	u8 max_iad_interface_count;
+
+	/* GPIO related register direction and values */
+	u32 gpio_dir;
+	u32 gpio_val;
+
+	/* Power Modes */
+	int power_mode;
+
+	/* afe parameters */
+	enum AFE_MODE afe_mode;
+	u32 afe_ref_count;
+
+	/* video related parameters */
+	u32 video_input;
+	u32 active_mode;
+	u8 vbi_or_sliced_cc_mode;	/* 0 - vbi ; 1 - sliced cc mode */
+	enum cx231xx_std_mode std_mode;	/* 0 - Air; 1 - cable */
+
+	/*mode: digital=1 or analog=0*/
+	u8 mode_tv;
+
+	u8 USE_ISO;
+	struct cx231xx_tvnorm      encodernorm;
+	struct cx231xx_tsport      ts1, ts2;
+	struct video_device        v4l_device;
+	atomic_t                   v4l_reader_count;
+	u32                        freq;
+	unsigned int               input;
+	u32                        cx23417_mailbox;
+	u32                        __iomem *lmmio;
+	u8                         __iomem *bmmio;
+};
+
+extern struct list_head cx231xx_devlist;
+
+#define cx25840_call(cx231xx, o, f, args...) \
+	v4l2_subdev_call(cx231xx->sd_cx25840, o, f, ##args)
+#define tuner_call(cx231xx, o, f, args...) \
+	v4l2_subdev_call(cx231xx->sd_tuner, o, f, ##args)
+#define call_all(dev, o, f, args...) \
+	v4l2_device_call_until_err(&dev->v4l2_dev, 0, o, f, ##args)
+
+struct cx231xx_ops {
+	struct list_head next;
+	char *name;
+	int id;
+	int (*init) (struct cx231xx *);
+	int (*fini) (struct cx231xx *);
+};
+
+/* call back functions in dvb module */
+int cx231xx_set_analog_freq(struct cx231xx *dev, u32 freq);
+int cx231xx_reset_analog_tuner(struct cx231xx *dev);
+
+/* Provided by cx231xx-i2c.c */
+void cx231xx_do_i2c_scan(struct cx231xx *dev, int i2c_port);
+int cx231xx_i2c_register(struct cx231xx_i2c *bus);
+void cx231xx_i2c_unregister(struct cx231xx_i2c *bus);
+int cx231xx_i2c_mux_create(struct cx231xx *dev);
+int cx231xx_i2c_mux_register(struct cx231xx *dev, int mux_no);
+void cx231xx_i2c_mux_unregister(struct cx231xx *dev);
+struct i2c_adapter *cx231xx_get_i2c_adap(struct cx231xx *dev, int i2c_port);
+
+/* Internal block control functions */
+int cx231xx_read_i2c_master(struct cx231xx *dev, u8 dev_addr, u16 saddr,
+		 u8 saddr_len, u32 *data, u8 data_len, int master);
+int cx231xx_write_i2c_master(struct cx231xx *dev, u8 dev_addr, u16 saddr,
+		 u8 saddr_len, u32 data, u8 data_len, int master);
+int cx231xx_read_i2c_data(struct cx231xx *dev, u8 dev_addr,
+			  u16 saddr, u8 saddr_len, u32 *data, u8 data_len);
+int cx231xx_write_i2c_data(struct cx231xx *dev, u8 dev_addr,
+			   u16 saddr, u8 saddr_len, u32 data, u8 data_len);
+int cx231xx_reg_mask_write(struct cx231xx *dev, u8 dev_addr, u8 size,
+			   u16 register_address, u8 bit_start, u8 bit_end,
+			   u32 value);
+int cx231xx_read_modify_write_i2c_dword(struct cx231xx *dev, u8 dev_addr,
+					u16 saddr, u32 mask, u32 value);
+u32 cx231xx_set_field(u32 field_mask, u32 data);
+
+/*verve r/w*/
+void initGPIO(struct cx231xx *dev);
+void uninitGPIO(struct cx231xx *dev);
+/* afe related functions */
+int cx231xx_afe_init_super_block(struct cx231xx *dev, u32 ref_count);
+int cx231xx_afe_init_channels(struct cx231xx *dev);
+int cx231xx_afe_setup_AFE_for_baseband(struct cx231xx *dev);
+int cx231xx_afe_set_input_mux(struct cx231xx *dev, u32 input_mux);
+int cx231xx_afe_set_mode(struct cx231xx *dev, enum AFE_MODE mode);
+int cx231xx_afe_update_power_control(struct cx231xx *dev,
+					enum AV_MODE avmode);
+int cx231xx_afe_adjust_ref_count(struct cx231xx *dev, u32 video_input);
+
+/* i2s block related functions */
+int cx231xx_i2s_blk_initialize(struct cx231xx *dev);
+int cx231xx_i2s_blk_update_power_control(struct cx231xx *dev,
+					enum AV_MODE avmode);
+int cx231xx_i2s_blk_set_audio_input(struct cx231xx *dev, u8 audio_input);
+
+/* DIF related functions */
+int cx231xx_dif_configure_C2HH_for_low_IF(struct cx231xx *dev, u32 mode,
+					  u32 function_mode, u32 standard);
+void cx231xx_set_Colibri_For_LowIF(struct cx231xx *dev, u32 if_freq,
+					 u8 spectral_invert, u32 mode);
+u32 cx231xx_Get_Colibri_CarrierOffset(u32 mode, u32 standerd);
+void cx231xx_set_DIF_bandpass(struct cx231xx *dev, u32 if_freq,
+					 u8 spectral_invert, u32 mode);
+void cx231xx_Setup_AFE_for_LowIF(struct cx231xx *dev);
+void reset_s5h1432_demod(struct cx231xx *dev);
+void cx231xx_dump_HH_reg(struct cx231xx *dev);
+void update_HH_register_after_set_DIF(struct cx231xx *dev);
+
+
+
+int cx231xx_dif_set_standard(struct cx231xx *dev, u32 standard);
+int cx231xx_tuner_pre_channel_change(struct cx231xx *dev);
+int cx231xx_tuner_post_channel_change(struct cx231xx *dev);
+
+/* video parser functions */
+u8 cx231xx_find_next_SAV_EAV(u8 *p_buffer, u32 buffer_size,
+			     u32 *p_bytes_used);
+u8 cx231xx_find_boundary_SAV_EAV(u8 *p_buffer, u8 *partial_buf,
+				 u32 *p_bytes_used);
+int cx231xx_do_copy(struct cx231xx *dev, struct cx231xx_dmaqueue *dma_q,
+		    u8 *p_buffer, u32 bytes_to_copy);
+void cx231xx_reset_video_buffer(struct cx231xx *dev,
+				struct cx231xx_dmaqueue *dma_q);
+u8 cx231xx_is_buffer_done(struct cx231xx *dev, struct cx231xx_dmaqueue *dma_q);
+u32 cx231xx_copy_video_line(struct cx231xx *dev, struct cx231xx_dmaqueue *dma_q,
+			    u8 *p_line, u32 length, int field_number);
+u32 cx231xx_get_video_line(struct cx231xx *dev, struct cx231xx_dmaqueue *dma_q,
+			   u8 sav_eav, u8 *p_buffer, u32 buffer_size);
+void cx231xx_swab(u16 *from, u16 *to, u16 len);
+
+/* Provided by cx231xx-core.c */
+
+u32 cx231xx_request_buffers(struct cx231xx *dev, u32 count);
+void cx231xx_queue_unusedframes(struct cx231xx *dev);
+void cx231xx_release_buffers(struct cx231xx *dev);
+
+/* read from control pipe */
+int cx231xx_read_ctrl_reg(struct cx231xx *dev, u8 req, u16 reg,
+			  char *buf, int len);
+
+/* write to control pipe */
+int cx231xx_write_ctrl_reg(struct cx231xx *dev, u8 req, u16 reg,
+			   char *buf, int len);
+int cx231xx_mode_register(struct cx231xx *dev, u16 address, u32 mode);
+
+int cx231xx_send_vendor_cmd(struct cx231xx *dev,
+				struct VENDOR_REQUEST_IN *ven_req);
+int cx231xx_send_usb_command(struct cx231xx_i2c *i2c_bus,
+				struct cx231xx_i2c_xfer_data *req_data);
+
+/* Gpio related functions */
+int cx231xx_send_gpio_cmd(struct cx231xx *dev, u32 gpio_bit, u8 *gpio_val,
+			  u8 len, u8 request, u8 direction);
+int cx231xx_set_gpio_value(struct cx231xx *dev, int pin_number, int pin_value);
+int cx231xx_set_gpio_direction(struct cx231xx *dev, int pin_number,
+			       int pin_value);
+
+int cx231xx_gpio_i2c_start(struct cx231xx *dev);
+int cx231xx_gpio_i2c_end(struct cx231xx *dev);
+int cx231xx_gpio_i2c_write_byte(struct cx231xx *dev, u8 data);
+int cx231xx_gpio_i2c_read_byte(struct cx231xx *dev, u8 *buf);
+int cx231xx_gpio_i2c_read_ack(struct cx231xx *dev);
+int cx231xx_gpio_i2c_write_ack(struct cx231xx *dev);
+int cx231xx_gpio_i2c_write_nak(struct cx231xx *dev);
+
+int cx231xx_gpio_i2c_read(struct cx231xx *dev, u8 dev_addr, u8 *buf, u8 len);
+int cx231xx_gpio_i2c_write(struct cx231xx *dev, u8 dev_addr, u8 *buf, u8 len);
+
+/* audio related functions */
+int cx231xx_set_audio_decoder_input(struct cx231xx *dev,
+				    enum AUDIO_INPUT audio_input);
+
+int cx231xx_capture_start(struct cx231xx *dev, int start, u8 media_type);
+int cx231xx_set_video_alternate(struct cx231xx *dev);
+int cx231xx_set_alt_setting(struct cx231xx *dev, u8 index, u8 alt);
+int is_fw_load(struct cx231xx *dev);
+int cx231xx_check_fw(struct cx231xx *dev);
+int cx231xx_init_isoc(struct cx231xx *dev, int max_packets,
+		      int num_bufs, int max_pkt_size,
+		      int (*isoc_copy) (struct cx231xx *dev,
+					struct urb *urb));
+int cx231xx_init_bulk(struct cx231xx *dev, int max_packets,
+		      int num_bufs, int max_pkt_size,
+		      int (*bulk_copy) (struct cx231xx *dev,
+					struct urb *urb));
+void cx231xx_stop_TS1(struct cx231xx *dev);
+void cx231xx_start_TS1(struct cx231xx *dev);
+void cx231xx_uninit_isoc(struct cx231xx *dev);
+void cx231xx_uninit_bulk(struct cx231xx *dev);
+int cx231xx_set_mode(struct cx231xx *dev, enum cx231xx_mode set_mode);
+int cx231xx_unmute_audio(struct cx231xx *dev);
+int cx231xx_ep5_bulkout(struct cx231xx *dev, u8 *firmware, u16 size);
+void cx231xx_disable656(struct cx231xx *dev);
+void cx231xx_enable656(struct cx231xx *dev);
+int cx231xx_demod_reset(struct cx231xx *dev);
+int cx231xx_gpio_set(struct cx231xx *dev, struct cx231xx_reg_seq *gpio);
+
+/* Device list functions */
+void cx231xx_release_resources(struct cx231xx *dev);
+void cx231xx_release_analog_resources(struct cx231xx *dev);
+int cx231xx_register_analog_devices(struct cx231xx *dev);
+void cx231xx_remove_from_devlist(struct cx231xx *dev);
+void cx231xx_add_into_devlist(struct cx231xx *dev);
+void cx231xx_init_extension(struct cx231xx *dev);
+void cx231xx_close_extension(struct cx231xx *dev);
+
+/* hardware init functions */
+int cx231xx_dev_init(struct cx231xx *dev);
+void cx231xx_dev_uninit(struct cx231xx *dev);
+void cx231xx_config_i2c(struct cx231xx *dev);
+int cx231xx_config(struct cx231xx *dev);
+
+/* Stream control functions */
+int cx231xx_start_stream(struct cx231xx *dev, u32 ep_mask);
+int cx231xx_stop_stream(struct cx231xx *dev, u32 ep_mask);
+
+int cx231xx_initialize_stream_xfer(struct cx231xx *dev, u32 media_type);
+
+/* Power control functions */
+int cx231xx_set_power_mode(struct cx231xx *dev, enum AV_MODE mode);
+int cx231xx_power_suspend(struct cx231xx *dev);
+
+/* chip specific control functions */
+int cx231xx_init_ctrl_pin_status(struct cx231xx *dev);
+int cx231xx_set_agc_analog_digital_mux_select(struct cx231xx *dev,
+					      u8 analog_or_digital);
+int cx231xx_enable_i2c_port_3(struct cx231xx *dev, bool is_port_3);
+
+/* video audio decoder related functions */
+void video_mux(struct cx231xx *dev, int index);
+int cx231xx_set_video_input_mux(struct cx231xx *dev, u8 input);
+int cx231xx_set_decoder_video_input(struct cx231xx *dev, u8 pin_type, u8 input);
+int cx231xx_do_mode_ctrl_overrides(struct cx231xx *dev);
+int cx231xx_set_audio_input(struct cx231xx *dev, u8 input);
+
+/* Provided by cx231xx-video.c */
+int cx231xx_register_extension(struct cx231xx_ops *dev);
+void cx231xx_unregister_extension(struct cx231xx_ops *dev);
+void cx231xx_init_extension(struct cx231xx *dev);
+void cx231xx_close_extension(struct cx231xx *dev);
+void cx231xx_v4l2_create_entities(struct cx231xx *dev);
+int cx231xx_querycap(struct file *file, void *priv,
+			   struct v4l2_capability *cap);
+int cx231xx_g_tuner(struct file *file, void *priv, struct v4l2_tuner *t);
+int cx231xx_s_tuner(struct file *file, void *priv, const struct v4l2_tuner *t);
+int cx231xx_g_frequency(struct file *file, void *priv,
+			      struct v4l2_frequency *f);
+int cx231xx_s_frequency(struct file *file, void *priv,
+			      const struct v4l2_frequency *f);
+int cx231xx_enum_input(struct file *file, void *priv,
+			     struct v4l2_input *i);
+int cx231xx_g_input(struct file *file, void *priv, unsigned int *i);
+int cx231xx_s_input(struct file *file, void *priv, unsigned int i);
+int cx231xx_g_chip_info(struct file *file, void *fh, struct v4l2_dbg_chip_info *chip);
+int cx231xx_g_register(struct file *file, void *priv,
+			     struct v4l2_dbg_register *reg);
+int cx231xx_s_register(struct file *file, void *priv,
+			     const struct v4l2_dbg_register *reg);
+
+/* Provided by cx231xx-cards.c */
+extern void cx231xx_pre_card_setup(struct cx231xx *dev);
+extern void cx231xx_card_setup(struct cx231xx *dev);
+extern struct cx231xx_board cx231xx_boards[];
+extern struct usb_device_id cx231xx_id_table[];
+extern const unsigned int cx231xx_bcount;
+int cx231xx_tuner_callback(void *ptr, int component, int command, int arg);
+
+/* cx23885-417.c                                               */
+extern int cx231xx_417_register(struct cx231xx *dev);
+extern void cx231xx_417_unregister(struct cx231xx *dev);
+
+/* cx23885-input.c                                             */
+
+#if defined(CONFIG_VIDEO_CX231XX_RC)
+int cx231xx_ir_init(struct cx231xx *dev);
+void cx231xx_ir_exit(struct cx231xx *dev);
+#else
+static inline int cx231xx_ir_init(struct cx231xx *dev)
+{
+	return 0;
+}
+static inline void cx231xx_ir_exit(struct cx231xx *dev) {}
+#endif
+
+static inline unsigned int norm_maxw(struct cx231xx *dev)
+{
+	if (dev->board.max_range_640_480)
+		return 640;
+	else
+		return 720;
+}
+
+static inline unsigned int norm_maxh(struct cx231xx *dev)
+{
+	if (dev->board.max_range_640_480)
+		return 480;
+	else
+		return (dev->norm & V4L2_STD_625_50) ? 576 : 480;
+}
+#endif
diff --git a/drivers/media/usb/dvb-usb-v2/Kconfig b/drivers/media/usb/dvb-usb-v2/Kconfig
new file mode 100644
index 0000000..df44122
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/Kconfig
@@ -0,0 +1,170 @@
+config DVB_USB_V2
+	tristate "Support for various USB DVB devices v2"
+	depends on DVB_CORE && USB && I2C && (RC_CORE || RC_CORE=n)
+	help
+	  By enabling this you will be able to choose the various supported
+	  USB1.1 and USB2.0 DVB devices.
+
+	  Almost every USB device needs a firmware, please look into
+	  <file:Documentation/media/dvb-drivers/dvb-usb.rst>.
+
+	  For a complete list of supported USB devices see the LinuxTV DVB Wiki:
+	  <https://linuxtv.org/wiki/index.php/DVB_USB>
+
+	  Say Y if you own a USB DVB device.
+
+config DVB_USB_AF9015
+	tristate "Afatech AF9015 DVB-T USB2.0 support"
+	depends on DVB_USB_V2 && I2C_MUX
+	select REGMAP
+	select DVB_AF9013
+	select DVB_PLL              if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_MT2060   if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_QT1010   if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_TDA18271 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_MXL5005S if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_MC44S803 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_TDA18218 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_MXL5007T if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y here to support the Afatech AF9015 based DVB-T USB2.0 receiver
+
+config DVB_USB_AF9035
+	tristate "Afatech AF9035 DVB-T USB2.0 support"
+	depends on DVB_USB_V2
+	select DVB_AF9033
+	select MEDIA_TUNER_TUA9001 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_FC0011 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_MXL5007T if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_TDA18218 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_FC2580 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_IT913X if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y here to support the Afatech AF9035 based DVB USB receiver.
+
+config DVB_USB_ANYSEE
+	tristate "Anysee DVB-T/C USB2.0 support"
+	depends on DVB_USB_V2
+	select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_MT352 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_ZL10353 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_TDA10023 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_TDA18212 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_CX24116 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_STV0900 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_STV6110 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_ISL6423 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_CXD2820R if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y here to support the Anysee E30, Anysee E30 Plus or
+	  Anysee E30 C Plus DVB USB2.0 receiver.
+
+config DVB_USB_AU6610
+	tristate "Alcor Micro AU6610 USB2.0 support"
+	depends on DVB_USB_V2
+	select DVB_ZL10353 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_QT1010 if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y here to support the Sigmatek DVB-110 DVB-T USB2.0 receiver.
+
+config DVB_USB_AZ6007
+	tristate "AzureWave 6007 and clones DVB-T/C USB2.0 support"
+	depends on DVB_USB_V2
+	select CYPRESS_FIRMWARE
+	select DVB_DRXK if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_MT2063 if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y here to support the AZ6007 receivers like Terratec H7.
+
+config DVB_USB_CE6230
+	tristate "Intel CE6230 DVB-T USB2.0 support"
+	depends on DVB_USB_V2
+	select DVB_ZL10353
+	select MEDIA_TUNER_MXL5005S if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y here to support the Intel CE6230 DVB-T USB2.0 receiver
+
+config DVB_USB_EC168
+	tristate "E3C EC168 DVB-T USB2.0 support"
+	depends on DVB_USB_V2
+	select DVB_EC100
+	select MEDIA_TUNER_MXL5005S if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y here to support the E3C EC168 DVB-T USB2.0 receiver.
+
+config DVB_USB_GL861
+	tristate "Genesys Logic GL861 USB2.0 support"
+	depends on DVB_USB_V2
+	select DVB_ZL10353 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_TC90522 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_QT1010 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y here to support the MSI Megasky 580 (55801) DVB-T USB2.0
+	  receiver with USB ID 0db0:5581, Friio White ISDB-T receiver
+	  with USB ID 0x7a69:0001.
+
+config DVB_USB_LME2510
+	tristate "LME DM04/QQBOX DVB-S USB2.0 support"
+	depends on DVB_USB_V2
+	depends on RC_CORE
+	select DVB_TDA10086 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_TDA826X if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_STV0288 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_IX2505V if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_M88RS2000 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_TS2020 if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y here to support the LME DM04/QQBOX DVB-S USB2.0
+
+config DVB_USB_MXL111SF
+	tristate "MxL111SF DTV USB2.0 support"
+	depends on DVB_USB_V2
+	select DVB_LGDT3305 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_LG2160 if MEDIA_SUBDRV_AUTOSELECT
+	select VIDEO_TVEEPROM
+	help
+	  Say Y here to support the MxL111SF USB2.0 DTV receiver.
+
+config DVB_USB_RTL28XXU
+	tristate "Realtek RTL28xxU DVB USB support"
+	depends on DVB_USB_V2 && I2C_MUX
+	select DVB_MN88472 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_MN88473 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_RTL2830
+	select DVB_RTL2832
+	select DVB_RTL2832_SDR if (MEDIA_SUBDRV_AUTOSELECT && MEDIA_SDR_SUPPORT)
+	select DVB_SI2168 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_E4000 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_FC0012 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_FC0013 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_FC2580 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_MT2060 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_MXL5005S if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_QT1010 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_R820T if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_SI2157 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_TUA9001 if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y here to support the Realtek RTL28xxU DVB USB receiver.
+
+config DVB_USB_DVBSKY
+	tristate "DVBSky USB support"
+	depends on DVB_USB_V2
+	select DVB_M88DS3103 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_SI2168 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_TS2020 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_SI2157 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_SP2 if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y here to support the USB receivers from DVBSky.
+
+config DVB_USB_ZD1301
+	tristate "ZyDAS ZD1301"
+	depends on DVB_USB_V2
+	select DVB_ZD1301_DEMOD if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_MT2060 if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y here to support the ZyDAS ZD1301 DVB USB receiver.
diff --git a/drivers/media/usb/dvb-usb-v2/Makefile b/drivers/media/usb/dvb-usb-v2/Makefile
new file mode 100644
index 0000000..58c0140
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/Makefile
@@ -0,0 +1,49 @@
+# SPDX-License-Identifier: GPL-2.0
+dvb_usb_v2-objs := dvb_usb_core.o dvb_usb_urb.o usb_urb.o
+obj-$(CONFIG_DVB_USB_V2) += dvb_usb_v2.o
+
+dvb-usb-af9015-objs := af9015.o
+obj-$(CONFIG_DVB_USB_AF9015) += dvb-usb-af9015.o
+
+dvb-usb-af9035-objs := af9035.o
+obj-$(CONFIG_DVB_USB_AF9035) += dvb-usb-af9035.o
+
+dvb-usb-anysee-objs := anysee.o
+obj-$(CONFIG_DVB_USB_ANYSEE) += dvb-usb-anysee.o
+
+dvb-usb-au6610-objs := au6610.o
+obj-$(CONFIG_DVB_USB_AU6610) += dvb-usb-au6610.o
+
+dvb-usb-az6007-objs := az6007.o
+obj-$(CONFIG_DVB_USB_AZ6007) += dvb-usb-az6007.o
+
+dvb-usb-ce6230-objs := ce6230.o
+obj-$(CONFIG_DVB_USB_CE6230) += dvb-usb-ce6230.o
+
+dvb-usb-ec168-objs := ec168.o
+obj-$(CONFIG_DVB_USB_EC168) += dvb-usb-ec168.o
+
+dvb-usb-lmedm04-objs := lmedm04.o
+obj-$(CONFIG_DVB_USB_LME2510) += dvb-usb-lmedm04.o
+
+dvb-usb-gl861-objs := gl861.o
+obj-$(CONFIG_DVB_USB_GL861) += dvb-usb-gl861.o
+
+dvb-usb-mxl111sf-objs += mxl111sf.o mxl111sf-phy.o mxl111sf-i2c.o
+dvb-usb-mxl111sf-objs += mxl111sf-gpio.o
+obj-$(CONFIG_DVB_USB_MXL111SF) += dvb-usb-mxl111sf.o
+obj-$(CONFIG_DVB_USB_MXL111SF) += mxl111sf-demod.o
+obj-$(CONFIG_DVB_USB_MXL111SF) += mxl111sf-tuner.o
+
+dvb-usb-rtl28xxu-objs := rtl28xxu.o
+obj-$(CONFIG_DVB_USB_RTL28XXU) += dvb-usb-rtl28xxu.o
+
+dvb-usb-dvbsky-objs := dvbsky.o
+obj-$(CONFIG_DVB_USB_DVBSKY) += dvb-usb-dvbsky.o
+
+dvb-usb-zd1301-objs := zd1301.o
+obj-$(CONFIG_DVB_USB_ZD1301) += zd1301.o
+
+ccflags-y += -I$(srctree)/drivers/media/dvb-frontends
+ccflags-y += -I$(srctree)/drivers/media/tuners
+ccflags-y += -I$(srctree)/drivers/media/common
diff --git a/drivers/media/usb/dvb-usb-v2/af9015.c b/drivers/media/usb/dvb-usb-v2/af9015.c
new file mode 100644
index 0000000..39f9ffc
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/af9015.c
@@ -0,0 +1,1565 @@
+/*
+ * DVB USB Linux driver for Afatech AF9015 DVB-T USB2.0 receiver
+ *
+ * Copyright (C) 2007 Antti Palosaari <crope@iki.fi>
+ *
+ * Thanks to Afatech who kindly provided information.
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ */
+
+#include "af9015.h"
+
+static int dvb_usb_af9015_remote;
+module_param_named(remote, dvb_usb_af9015_remote, int, 0644);
+MODULE_PARM_DESC(remote, "select remote");
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static int af9015_ctrl_msg(struct dvb_usb_device *d, struct req_t *req)
+{
+#define REQ_HDR_LEN 8 /* send header size */
+#define ACK_HDR_LEN 2 /* rece header size */
+	struct af9015_state *state = d_to_priv(d);
+	struct usb_interface *intf = d->intf;
+	int ret, wlen, rlen;
+	u8 write = 1;
+
+	mutex_lock(&d->usb_mutex);
+
+	state->buf[0] = req->cmd;
+	state->buf[1] = state->seq++;
+	state->buf[2] = req->i2c_addr << 1;
+	state->buf[3] = req->addr >> 8;
+	state->buf[4] = req->addr & 0xff;
+	state->buf[5] = req->mbox;
+	state->buf[6] = req->addr_len;
+	state->buf[7] = req->data_len;
+
+	switch (req->cmd) {
+	case GET_CONFIG:
+	case READ_MEMORY:
+	case RECONNECT_USB:
+		write = 0;
+		break;
+	case READ_I2C:
+		write = 0;
+		state->buf[2] |= 0x01; /* set I2C direction */
+		/* fall through */
+	case WRITE_I2C:
+		state->buf[0] = READ_WRITE_I2C;
+		break;
+	case WRITE_MEMORY:
+		if (((req->addr & 0xff00) == 0xff00) ||
+		    ((req->addr & 0xff00) == 0xae00))
+			state->buf[0] = WRITE_VIRTUAL_MEMORY;
+	case WRITE_VIRTUAL_MEMORY:
+	case COPY_FIRMWARE:
+	case DOWNLOAD_FIRMWARE:
+	case BOOT:
+		break;
+	default:
+		dev_err(&intf->dev, "unknown cmd %d\n", req->cmd);
+		ret = -EIO;
+		goto error;
+	}
+
+	/* Buffer overflow check */
+	if ((write && (req->data_len > BUF_LEN - REQ_HDR_LEN)) ||
+	    (!write && (req->data_len > BUF_LEN - ACK_HDR_LEN))) {
+		dev_err(&intf->dev, "too much data, cmd %u, len %u\n",
+			req->cmd, req->data_len);
+		ret = -EINVAL;
+		goto error;
+	}
+
+	/*
+	 * Write receives seq + status = 2 bytes
+	 * Read receives seq + status + data = 2 + N bytes
+	 */
+	wlen = REQ_HDR_LEN;
+	rlen = ACK_HDR_LEN;
+	if (write) {
+		wlen += req->data_len;
+		memcpy(&state->buf[REQ_HDR_LEN], req->data, req->data_len);
+	} else {
+		rlen += req->data_len;
+	}
+
+	/* no ack for these packets */
+	if (req->cmd == DOWNLOAD_FIRMWARE || req->cmd == RECONNECT_USB)
+		rlen = 0;
+
+	ret = dvb_usbv2_generic_rw_locked(d, state->buf, wlen,
+					  state->buf, rlen);
+	if (ret)
+		goto error;
+
+	/* check status */
+	if (rlen && state->buf[1]) {
+		dev_err(&intf->dev, "cmd failed %u\n", state->buf[1]);
+		ret = -EIO;
+		goto error;
+	}
+
+	/* read request, copy returned data to return buf */
+	if (!write)
+		memcpy(req->data, &state->buf[ACK_HDR_LEN], req->data_len);
+error:
+	mutex_unlock(&d->usb_mutex);
+
+	return ret;
+}
+
+static int af9015_write_reg_i2c(struct dvb_usb_device *d, u8 addr, u16 reg,
+				u8 val)
+{
+	struct af9015_state *state = d_to_priv(d);
+	struct req_t req = {WRITE_I2C, addr, reg, 1, 1, 1, &val};
+
+	if (addr == state->af9013_i2c_addr[0] ||
+	    addr == state->af9013_i2c_addr[1])
+		req.addr_len = 3;
+
+	return af9015_ctrl_msg(d, &req);
+}
+
+static int af9015_read_reg_i2c(struct dvb_usb_device *d, u8 addr, u16 reg,
+			       u8 *val)
+{
+	struct af9015_state *state = d_to_priv(d);
+	struct req_t req = {READ_I2C, addr, reg, 0, 1, 1, val};
+
+	if (addr == state->af9013_i2c_addr[0] ||
+	    addr == state->af9013_i2c_addr[1])
+		req.addr_len = 3;
+
+	return af9015_ctrl_msg(d, &req);
+}
+
+static int af9015_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[],
+			   int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	struct af9015_state *state = d_to_priv(d);
+	struct usb_interface *intf = d->intf;
+	int ret;
+	u16 addr;
+	u8 mbox, addr_len;
+	struct req_t req;
+
+	/*
+	 * I2C multiplexing:
+	 * There could be two tuners, both using same I2C address. Demodulator
+	 * I2C-gate is only possibility to select correct tuner.
+	 *
+	 * ...........................................
+	 * . AF9015 integrates AF9013 demodulator    .
+	 * . ____________               ____________ .             ____________
+	 * .|   USB IF   |             |   demod    |.            |   tuner    |
+	 * .|------------|             |------------|.            |------------|
+	 * .|   AF9015   |             |   AF9013   |.            |   MXL5003  |
+	 * .|            |--+--I2C-----|-----/ -----|.----I2C-----|            |
+	 * .|            |  |          | addr 0x1c  |.            |  addr 0x63 |
+	 * .|____________|  |          |____________|.            |____________|
+	 * .................|.........................
+	 *                  |           ____________               ____________
+	 *                  |          |   demod    |             |   tuner    |
+	 *                  |          |------------|             |------------|
+	 *                  |          |   AF9013   |             |   MXL5003  |
+	 *                  +--I2C-----|-----/ -----|-----I2C-----|            |
+	 *                             | addr 0x1d  |             |  addr 0x63 |
+	 *                             |____________|             |____________|
+	 */
+
+	if (msg[0].len == 0 || msg[0].flags & I2C_M_RD) {
+		addr = 0x0000;
+		mbox = 0;
+		addr_len = 0;
+	} else if (msg[0].len == 1) {
+		addr = msg[0].buf[0];
+		mbox = 0;
+		addr_len = 1;
+	} else if (msg[0].len == 2) {
+		addr = msg[0].buf[0] << 8 | msg[0].buf[1] << 0;
+		mbox = 0;
+		addr_len = 2;
+	} else {
+		addr = msg[0].buf[0] << 8 | msg[0].buf[1] << 0;
+		mbox = msg[0].buf[2];
+		addr_len = 3;
+	}
+
+	if (num == 1 && !(msg[0].flags & I2C_M_RD)) {
+		/* i2c write */
+		if (msg[0].len > 21) {
+			ret = -EOPNOTSUPP;
+			goto err;
+		}
+		if (msg[0].addr == state->af9013_i2c_addr[0])
+			req.cmd = WRITE_MEMORY;
+		else
+			req.cmd = WRITE_I2C;
+		req.i2c_addr = msg[0].addr;
+		req.addr = addr;
+		req.mbox = mbox;
+		req.addr_len = addr_len;
+		req.data_len = msg[0].len - addr_len;
+		req.data = &msg[0].buf[addr_len];
+		ret = af9015_ctrl_msg(d, &req);
+	} else if (num == 2 && !(msg[0].flags & I2C_M_RD) &&
+		   (msg[1].flags & I2C_M_RD)) {
+		/* i2c write + read */
+		if (msg[0].len > 3 || msg[1].len > 61) {
+			ret = -EOPNOTSUPP;
+			goto err;
+		}
+		if (msg[0].addr == state->af9013_i2c_addr[0])
+			req.cmd = READ_MEMORY;
+		else
+			req.cmd = READ_I2C;
+		req.i2c_addr = msg[0].addr;
+		req.addr = addr;
+		req.mbox = mbox;
+		req.addr_len = addr_len;
+		req.data_len = msg[1].len;
+		req.data = &msg[1].buf[0];
+		ret = af9015_ctrl_msg(d, &req);
+	} else if (num == 1 && (msg[0].flags & I2C_M_RD)) {
+		/* i2c read */
+		if (msg[0].len > 61) {
+			ret = -EOPNOTSUPP;
+			goto err;
+		}
+		if (msg[0].addr == state->af9013_i2c_addr[0]) {
+			ret = -EINVAL;
+			goto err;
+		}
+		req.cmd = READ_I2C;
+		req.i2c_addr = msg[0].addr;
+		req.addr = addr;
+		req.mbox = mbox;
+		req.addr_len = addr_len;
+		req.data_len = msg[0].len;
+		req.data = &msg[0].buf[0];
+		ret = af9015_ctrl_msg(d, &req);
+	} else {
+		ret = -EOPNOTSUPP;
+		dev_dbg(&intf->dev, "unknown msg, num %u\n", num);
+	}
+	if (ret)
+		goto err;
+
+	return num;
+err:
+	dev_dbg(&intf->dev, "failed %d\n", ret);
+	return ret;
+}
+
+static u32 af9015_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm af9015_i2c_algo = {
+	.master_xfer = af9015_i2c_xfer,
+	.functionality = af9015_i2c_func,
+};
+
+static int af9015_identify_state(struct dvb_usb_device *d, const char **name)
+{
+	struct usb_interface *intf = d->intf;
+	int ret;
+	u8 reply;
+	struct req_t req = {GET_CONFIG, 0, 0, 0, 0, 1, &reply};
+
+	ret = af9015_ctrl_msg(d, &req);
+	if (ret)
+		return ret;
+
+	dev_dbg(&intf->dev, "reply %02x\n", reply);
+
+	if (reply == 0x02)
+		ret = WARM;
+	else
+		ret = COLD;
+
+	return ret;
+}
+
+static int af9015_download_firmware(struct dvb_usb_device *d,
+				    const struct firmware *firmware)
+{
+	struct af9015_state *state = d_to_priv(d);
+	struct usb_interface *intf = d->intf;
+	int ret, i, rem;
+	struct req_t req = {DOWNLOAD_FIRMWARE, 0, 0, 0, 0, 0, NULL};
+	u16 checksum;
+
+	dev_dbg(&intf->dev, "\n");
+
+	/* Calc checksum, we need it when copy firmware to slave demod */
+	for (i = 0, checksum = 0; i < firmware->size; i++)
+		checksum += firmware->data[i];
+
+	state->firmware_size = firmware->size;
+	state->firmware_checksum = checksum;
+
+	#define LEN_MAX (BUF_LEN - REQ_HDR_LEN) /* Max payload size */
+	for (rem = firmware->size; rem > 0; rem -= LEN_MAX) {
+		req.data_len = min(LEN_MAX, rem);
+		req.data = (u8 *)&firmware->data[firmware->size - rem];
+		req.addr = 0x5100 + firmware->size - rem;
+		ret = af9015_ctrl_msg(d, &req);
+		if (ret) {
+			dev_err(&intf->dev, "firmware download failed %d\n",
+				ret);
+			goto err;
+		}
+	}
+
+	req.cmd = BOOT;
+	req.data_len = 0;
+	ret = af9015_ctrl_msg(d, &req);
+	if (ret) {
+		dev_err(&intf->dev, "firmware boot failed %d\n", ret);
+		goto err;
+	}
+
+	return 0;
+err:
+	dev_dbg(&intf->dev, "failed %d\n", ret);
+	return ret;
+}
+
+#define AF9015_EEPROM_SIZE 256
+/* 2^31 + 2^29 - 2^25 + 2^22 - 2^19 - 2^16 + 1 */
+#define GOLDEN_RATIO_PRIME_32 0x9e370001UL
+
+/* hash (and dump) eeprom */
+static int af9015_eeprom_hash(struct dvb_usb_device *d)
+{
+	struct af9015_state *state = d_to_priv(d);
+	struct usb_interface *intf = d->intf;
+	int ret, i;
+	u8 buf[AF9015_EEPROM_SIZE];
+	struct req_t req = {READ_I2C, AF9015_I2C_EEPROM, 0, 0, 1, 1, NULL};
+
+	/* read eeprom */
+	for (i = 0; i < AF9015_EEPROM_SIZE; i++) {
+		req.addr = i;
+		req.data = &buf[i];
+		ret = af9015_ctrl_msg(d, &req);
+		if (ret < 0)
+			goto err;
+	}
+
+	/* calculate checksum */
+	for (i = 0; i < AF9015_EEPROM_SIZE / sizeof(u32); i++) {
+		state->eeprom_sum *= GOLDEN_RATIO_PRIME_32;
+		state->eeprom_sum += le32_to_cpu(((__le32 *)buf)[i]);
+	}
+
+	for (i = 0; i < AF9015_EEPROM_SIZE; i += 16)
+		dev_dbg(&intf->dev, "%*ph\n", 16, buf + i);
+
+	dev_dbg(&intf->dev, "eeprom sum %.8x\n", state->eeprom_sum);
+	return 0;
+err:
+	dev_dbg(&intf->dev, "failed %d\n", ret);
+	return ret;
+}
+
+static int af9015_read_config(struct dvb_usb_device *d)
+{
+	struct af9015_state *state = d_to_priv(d);
+	struct usb_interface *intf = d->intf;
+	int ret;
+	u8 val, i, offset = 0;
+	struct req_t req = {READ_I2C, AF9015_I2C_EEPROM, 0, 0, 1, 1, &val};
+
+	dev_dbg(&intf->dev, "\n");
+
+	/* IR remote controller */
+	req.addr = AF9015_EEPROM_IR_MODE;
+	/* first message will timeout often due to possible hw bug */
+	for (i = 0; i < 4; i++) {
+		ret = af9015_ctrl_msg(d, &req);
+		if (!ret)
+			break;
+	}
+	if (ret)
+		goto error;
+
+	ret = af9015_eeprom_hash(d);
+	if (ret)
+		goto error;
+
+	state->ir_mode = val;
+	dev_dbg(&intf->dev, "ir mode %02x\n", val);
+
+	/* TS mode - one or two receivers */
+	req.addr = AF9015_EEPROM_TS_MODE;
+	ret = af9015_ctrl_msg(d, &req);
+	if (ret)
+		goto error;
+
+	state->dual_mode = val;
+	dev_dbg(&intf->dev, "ts mode %02x\n", state->dual_mode);
+
+	state->af9013_i2c_addr[0] = AF9015_I2C_DEMOD;
+
+	if (state->dual_mode) {
+		/* read 2nd demodulator I2C address */
+		req.addr = AF9015_EEPROM_DEMOD2_I2C;
+		ret = af9015_ctrl_msg(d, &req);
+		if (ret)
+			goto error;
+
+		state->af9013_i2c_addr[1] = val >> 1;
+	}
+
+	for (i = 0; i < state->dual_mode + 1; i++) {
+		if (i == 1)
+			offset = AF9015_EEPROM_OFFSET;
+		/* xtal */
+		req.addr = AF9015_EEPROM_XTAL_TYPE1 + offset;
+		ret = af9015_ctrl_msg(d, &req);
+		if (ret)
+			goto error;
+		switch (val) {
+		case 0:
+			state->af9013_pdata[i].clk = 28800000;
+			break;
+		case 1:
+			state->af9013_pdata[i].clk = 20480000;
+			break;
+		case 2:
+			state->af9013_pdata[i].clk = 28000000;
+			break;
+		case 3:
+			state->af9013_pdata[i].clk = 25000000;
+			break;
+		}
+		dev_dbg(&intf->dev, "[%d] xtal %02x, clk %u\n",
+			i, val, state->af9013_pdata[i].clk);
+
+		/* IF frequency */
+		req.addr = AF9015_EEPROM_IF1H + offset;
+		ret = af9015_ctrl_msg(d, &req);
+		if (ret)
+			goto error;
+
+		state->af9013_pdata[i].if_frequency = val << 8;
+
+		req.addr = AF9015_EEPROM_IF1L + offset;
+		ret = af9015_ctrl_msg(d, &req);
+		if (ret)
+			goto error;
+
+		state->af9013_pdata[i].if_frequency += val;
+		state->af9013_pdata[i].if_frequency *= 1000;
+		dev_dbg(&intf->dev, "[%d] if frequency %u\n",
+			i, state->af9013_pdata[i].if_frequency);
+
+		/* MT2060 IF1 */
+		req.addr = AF9015_EEPROM_MT2060_IF1H  + offset;
+		ret = af9015_ctrl_msg(d, &req);
+		if (ret)
+			goto error;
+		state->mt2060_if1[i] = val << 8;
+		req.addr = AF9015_EEPROM_MT2060_IF1L + offset;
+		ret = af9015_ctrl_msg(d, &req);
+		if (ret)
+			goto error;
+		state->mt2060_if1[i] += val;
+		dev_dbg(&intf->dev, "[%d] MT2060 IF1 %u\n",
+			i, state->mt2060_if1[i]);
+
+		/* tuner */
+		req.addr =  AF9015_EEPROM_TUNER_ID1 + offset;
+		ret = af9015_ctrl_msg(d, &req);
+		if (ret)
+			goto error;
+		switch (val) {
+		case AF9013_TUNER_ENV77H11D5:
+		case AF9013_TUNER_MT2060:
+		case AF9013_TUNER_QT1010:
+		case AF9013_TUNER_UNKNOWN:
+		case AF9013_TUNER_MT2060_2:
+		case AF9013_TUNER_TDA18271:
+		case AF9013_TUNER_QT1010A:
+		case AF9013_TUNER_TDA18218:
+			state->af9013_pdata[i].spec_inv = 1;
+			break;
+		case AF9013_TUNER_MXL5003D:
+		case AF9013_TUNER_MXL5005D:
+		case AF9013_TUNER_MXL5005R:
+		case AF9013_TUNER_MXL5007T:
+			state->af9013_pdata[i].spec_inv = 0;
+			break;
+		case AF9013_TUNER_MC44S803:
+			state->af9013_pdata[i].gpio[1] = AF9013_GPIO_LO;
+			state->af9013_pdata[i].spec_inv = 1;
+			break;
+		default:
+			dev_err(&intf->dev,
+				"tuner id %02x not supported, please report!\n",
+				val);
+			return -ENODEV;
+		}
+
+		state->af9013_pdata[i].tuner = val;
+		dev_dbg(&intf->dev, "[%d] tuner id %02x\n", i, val);
+	}
+
+error:
+	if (ret)
+		dev_err(&intf->dev, "eeprom read failed %d\n", ret);
+
+	/*
+	 * AverMedia AVerTV Volar Black HD (A850) device have bad EEPROM
+	 * content :-( Override some wrong values here. Ditto for the
+	 * AVerTV Red HD+ (A850T) device.
+	 */
+	if (le16_to_cpu(d->udev->descriptor.idVendor) == USB_VID_AVERMEDIA &&
+	    ((le16_to_cpu(d->udev->descriptor.idProduct) == USB_PID_AVERMEDIA_A850) ||
+	    (le16_to_cpu(d->udev->descriptor.idProduct) == USB_PID_AVERMEDIA_A850T))) {
+		dev_dbg(&intf->dev, "AverMedia A850: overriding config\n");
+		/* disable dual mode */
+		state->dual_mode = 0;
+
+		/* set correct IF */
+		state->af9013_pdata[0].if_frequency = 4570000;
+	}
+
+	return ret;
+}
+
+static int af9015_get_stream_config(struct dvb_frontend *fe, u8 *ts_type,
+				    struct usb_data_stream_properties *stream)
+{
+	struct dvb_usb_device *d = fe_to_d(fe);
+	struct usb_interface *intf = d->intf;
+
+	dev_dbg(&intf->dev, "adap %u\n", fe_to_adap(fe)->id);
+
+	if (d->udev->speed == USB_SPEED_FULL)
+		stream->u.bulk.buffersize = 5 * 188;
+
+	return 0;
+}
+
+static int af9015_streaming_ctrl(struct dvb_frontend *fe, int onoff)
+{
+	struct dvb_usb_device *d = fe_to_d(fe);
+	struct af9015_state *state = d_to_priv(d);
+	struct usb_interface *intf = d->intf;
+	int ret;
+	unsigned int utmp1, utmp2, reg1, reg2;
+	u8 buf[2];
+	const unsigned int adap_id = fe_to_adap(fe)->id;
+
+	dev_dbg(&intf->dev, "adap id %d, onoff %d\n", adap_id, onoff);
+
+	if (!state->usb_ts_if_configured[adap_id]) {
+		dev_dbg(&intf->dev, "set usb and ts interface\n");
+
+		/* USB IF stream settings */
+		utmp1 = (d->udev->speed == USB_SPEED_FULL ? 5 : 87) * 188 / 4;
+		utmp2 = (d->udev->speed == USB_SPEED_FULL ? 64 : 512) / 4;
+
+		buf[0] = (utmp1 >> 0) & 0xff;
+		buf[1] = (utmp1 >> 8) & 0xff;
+		if (adap_id == 0) {
+			/* 1st USB IF (EP4) stream settings */
+			reg1 = 0xdd88;
+			reg2 = 0xdd0c;
+		} else {
+			/* 2nd USB IF (EP5) stream settings */
+			reg1 = 0xdd8a;
+			reg2 = 0xdd0d;
+		}
+		ret = regmap_bulk_write(state->regmap, reg1, buf, 2);
+		if (ret)
+			goto err;
+		ret = regmap_write(state->regmap, reg2, utmp2);
+		if (ret)
+			goto err;
+
+		/* TS IF settings */
+		if (state->dual_mode) {
+			utmp1 = 0x01;
+			utmp2 = 0x10;
+		} else {
+			utmp1 = 0x00;
+			utmp2 = 0x00;
+		}
+		ret = regmap_update_bits(state->regmap, 0xd50b, 0x01, utmp1);
+		if (ret)
+			goto err;
+		ret = regmap_update_bits(state->regmap, 0xd520, 0x10, utmp2);
+		if (ret)
+			goto err;
+
+		state->usb_ts_if_configured[adap_id] = true;
+	}
+
+	if (adap_id == 0 && onoff) {
+		/* Adapter 0 stream on. EP4: clear NAK, enable, clear reset */
+		ret = regmap_update_bits(state->regmap, 0xdd13, 0x20, 0x00);
+		if (ret)
+			goto err;
+		ret = regmap_update_bits(state->regmap, 0xdd11, 0x20, 0x20);
+		if (ret)
+			goto err;
+		ret = regmap_update_bits(state->regmap, 0xd507, 0x04, 0x00);
+		if (ret)
+			goto err;
+	} else if (adap_id == 1 && onoff) {
+		/* Adapter 1 stream on. EP5: clear NAK, enable, clear reset */
+		ret = regmap_update_bits(state->regmap, 0xdd13, 0x40, 0x00);
+		if (ret)
+			goto err;
+		ret = regmap_update_bits(state->regmap, 0xdd11, 0x40, 0x40);
+		if (ret)
+			goto err;
+		ret = regmap_update_bits(state->regmap, 0xd50b, 0x02, 0x00);
+		if (ret)
+			goto err;
+	} else if (adap_id == 0 && !onoff) {
+		/* Adapter 0 stream off. EP4: set reset, disable, set NAK */
+		ret = regmap_update_bits(state->regmap, 0xd507, 0x04, 0x04);
+		if (ret)
+			goto err;
+		ret = regmap_update_bits(state->regmap, 0xdd11, 0x20, 0x00);
+		if (ret)
+			goto err;
+		ret = regmap_update_bits(state->regmap, 0xdd13, 0x20, 0x20);
+		if (ret)
+			goto err;
+	} else if (adap_id == 1 && !onoff) {
+		/* Adapter 1 stream off. EP5: set reset, disable, set NAK */
+		ret = regmap_update_bits(state->regmap, 0xd50b, 0x02, 0x02);
+		if (ret)
+			goto err;
+		ret = regmap_update_bits(state->regmap, 0xdd11, 0x40, 0x00);
+		if (ret)
+			goto err;
+		ret = regmap_update_bits(state->regmap, 0xdd13, 0x40, 0x40);
+		if (ret)
+			goto err;
+	}
+
+	return 0;
+err:
+	dev_dbg(&intf->dev, "failed %d\n", ret);
+	return ret;
+}
+
+static int af9015_get_adapter_count(struct dvb_usb_device *d)
+{
+	struct af9015_state *state = d_to_priv(d);
+
+	return state->dual_mode + 1;
+}
+
+/* override demod callbacks for resource locking */
+static int af9015_af9013_set_frontend(struct dvb_frontend *fe)
+{
+	int ret;
+	struct af9015_state *state = fe_to_priv(fe);
+
+	if (mutex_lock_interruptible(&state->fe_mutex))
+		return -EAGAIN;
+
+	ret = state->set_frontend[fe_to_adap(fe)->id](fe);
+
+	mutex_unlock(&state->fe_mutex);
+
+	return ret;
+}
+
+/* override demod callbacks for resource locking */
+static int af9015_af9013_read_status(struct dvb_frontend *fe,
+				     enum fe_status *status)
+{
+	int ret;
+	struct af9015_state *state = fe_to_priv(fe);
+
+	if (mutex_lock_interruptible(&state->fe_mutex))
+		return -EAGAIN;
+
+	ret = state->read_status[fe_to_adap(fe)->id](fe, status);
+
+	mutex_unlock(&state->fe_mutex);
+
+	return ret;
+}
+
+/* override demod callbacks for resource locking */
+static int af9015_af9013_init(struct dvb_frontend *fe)
+{
+	int ret;
+	struct af9015_state *state = fe_to_priv(fe);
+
+	if (mutex_lock_interruptible(&state->fe_mutex))
+		return -EAGAIN;
+
+	ret = state->init[fe_to_adap(fe)->id](fe);
+
+	mutex_unlock(&state->fe_mutex);
+
+	return ret;
+}
+
+/* override demod callbacks for resource locking */
+static int af9015_af9013_sleep(struct dvb_frontend *fe)
+{
+	int ret;
+	struct af9015_state *state = fe_to_priv(fe);
+
+	if (mutex_lock_interruptible(&state->fe_mutex))
+		return -EAGAIN;
+
+	ret = state->sleep[fe_to_adap(fe)->id](fe);
+
+	mutex_unlock(&state->fe_mutex);
+
+	return ret;
+}
+
+/* override tuner callbacks for resource locking */
+static int af9015_tuner_init(struct dvb_frontend *fe)
+{
+	int ret;
+	struct af9015_state *state = fe_to_priv(fe);
+
+	if (mutex_lock_interruptible(&state->fe_mutex))
+		return -EAGAIN;
+
+	ret = state->tuner_init[fe_to_adap(fe)->id](fe);
+
+	mutex_unlock(&state->fe_mutex);
+
+	return ret;
+}
+
+/* override tuner callbacks for resource locking */
+static int af9015_tuner_sleep(struct dvb_frontend *fe)
+{
+	int ret;
+	struct af9015_state *state = fe_to_priv(fe);
+
+	if (mutex_lock_interruptible(&state->fe_mutex))
+		return -EAGAIN;
+
+	ret = state->tuner_sleep[fe_to_adap(fe)->id](fe);
+
+	mutex_unlock(&state->fe_mutex);
+
+	return ret;
+}
+
+static int af9015_copy_firmware(struct dvb_usb_device *d)
+{
+	struct af9015_state *state = d_to_priv(d);
+	struct usb_interface *intf = d->intf;
+	int ret;
+	unsigned long timeout;
+	u8 val, firmware_info[4];
+	struct req_t req = {COPY_FIRMWARE, 0, 0x5100, 0, 0, 4, firmware_info};
+
+	dev_dbg(&intf->dev, "\n");
+
+	firmware_info[0] = (state->firmware_size >> 8) & 0xff;
+	firmware_info[1] = (state->firmware_size >> 0) & 0xff;
+	firmware_info[2] = (state->firmware_checksum >> 8) & 0xff;
+	firmware_info[3] = (state->firmware_checksum >> 0) & 0xff;
+
+	/* Check whether firmware is already running */
+	ret = af9015_read_reg_i2c(d, state->af9013_i2c_addr[1], 0x98be, &val);
+	if (ret)
+		goto err;
+
+	dev_dbg(&intf->dev, "firmware status %02x\n", val);
+
+	if (val == 0x0c)
+		return 0;
+
+	/* Set i2c clock to 625kHz to speed up firmware copy */
+	ret = regmap_write(state->regmap, 0xd416, 0x04);
+	if (ret)
+		goto err;
+
+	/* Copy firmware from master demod to slave demod */
+	ret = af9015_ctrl_msg(d, &req);
+	if (ret) {
+		dev_err(&intf->dev, "firmware copy cmd failed %d\n", ret);
+		goto err;
+	}
+
+	/* Set i2c clock to 125kHz */
+	ret = regmap_write(state->regmap, 0xd416, 0x14);
+	if (ret)
+		goto err;
+
+	/* Boot firmware */
+	ret = af9015_write_reg_i2c(d, state->af9013_i2c_addr[1], 0xe205, 0x01);
+	if (ret)
+		goto err;
+
+	/* Poll firmware ready */
+	for (val = 0x00, timeout = jiffies + msecs_to_jiffies(1000);
+	     !time_after(jiffies, timeout) && val != 0x0c && val != 0x04;) {
+		msleep(20);
+
+		/* Check firmware status. 0c=OK, 04=fail */
+		ret = af9015_read_reg_i2c(d, state->af9013_i2c_addr[1],
+					  0x98be, &val);
+		if (ret)
+			goto err;
+
+		dev_dbg(&intf->dev, "firmware status %02x\n", val);
+	}
+
+	dev_dbg(&intf->dev, "firmware boot took %u ms\n",
+		jiffies_to_msecs(jiffies) - (jiffies_to_msecs(timeout) - 1000));
+
+	if (val == 0x04) {
+		ret = -ENODEV;
+		dev_err(&intf->dev, "firmware did not run\n");
+		goto err;
+	} else if (val != 0x0c) {
+		ret = -ETIMEDOUT;
+		dev_err(&intf->dev, "firmware boot timeout\n");
+		goto err;
+	}
+
+	return 0;
+err:
+	dev_dbg(&intf->dev, "failed %d\n", ret);
+	return ret;
+}
+
+static int af9015_af9013_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct af9015_state *state = adap_to_priv(adap);
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct usb_interface *intf = d->intf;
+	struct i2c_client *client;
+	int ret;
+
+	dev_dbg(&intf->dev, "adap id %u\n", adap->id);
+
+	if (adap->id == 0) {
+		state->af9013_pdata[0].ts_mode = AF9013_TS_MODE_USB;
+		memcpy(state->af9013_pdata[0].api_version, "\x0\x1\x9\x0", 4);
+		state->af9013_pdata[0].gpio[0] = AF9013_GPIO_HI;
+		state->af9013_pdata[0].gpio[3] = AF9013_GPIO_TUNER_ON;
+	} else if (adap->id == 1) {
+		state->af9013_pdata[1].ts_mode = AF9013_TS_MODE_SERIAL;
+		state->af9013_pdata[1].ts_output_pin = 7;
+		memcpy(state->af9013_pdata[1].api_version, "\x0\x1\x9\x0", 4);
+		state->af9013_pdata[1].gpio[0] = AF9013_GPIO_TUNER_ON;
+		state->af9013_pdata[1].gpio[1] = AF9013_GPIO_LO;
+
+		/* copy firmware to 2nd demodulator */
+		if (state->dual_mode) {
+			/* Wait 2nd demodulator ready */
+			msleep(100);
+
+			ret = af9015_copy_firmware(adap_to_d(adap));
+			if (ret) {
+				dev_err(&intf->dev,
+					"firmware copy to 2nd frontend failed, will disable it\n");
+				state->dual_mode = 0;
+				goto err;
+			}
+		} else {
+			ret = -ENODEV;
+			goto err;
+		}
+	}
+
+	/* Add I2C demod */
+	client = dvb_module_probe("af9013", NULL, &d->i2c_adap,
+				  state->af9013_i2c_addr[adap->id],
+				  &state->af9013_pdata[adap->id]);
+	if (!client) {
+		ret = -ENODEV;
+		goto err;
+	}
+	adap->fe[0] = state->af9013_pdata[adap->id].get_dvb_frontend(client);
+	state->demod_i2c_client[adap->id] = client;
+
+	/*
+	 * AF9015 firmware does not like if it gets interrupted by I2C adapter
+	 * request on some critical phases. During normal operation I2C adapter
+	 * is used only 2nd demodulator and tuner on dual tuner devices.
+	 * Override demodulator callbacks and use mutex for limit access to
+	 * those "critical" paths to keep AF9015 happy.
+	 */
+	if (adap->fe[0]) {
+		state->set_frontend[adap->id] = adap->fe[0]->ops.set_frontend;
+		adap->fe[0]->ops.set_frontend = af9015_af9013_set_frontend;
+		state->read_status[adap->id] = adap->fe[0]->ops.read_status;
+		adap->fe[0]->ops.read_status = af9015_af9013_read_status;
+		state->init[adap->id] = adap->fe[0]->ops.init;
+		adap->fe[0]->ops.init = af9015_af9013_init;
+		state->sleep[adap->id] = adap->fe[0]->ops.sleep;
+		adap->fe[0]->ops.sleep = af9015_af9013_sleep;
+	}
+
+	return 0;
+err:
+	dev_dbg(&intf->dev, "failed %d\n", ret);
+	return ret;
+}
+
+static int af9015_frontend_detach(struct dvb_usb_adapter *adap)
+{
+	struct af9015_state *state = adap_to_priv(adap);
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct usb_interface *intf = d->intf;
+	struct i2c_client *client;
+
+	dev_dbg(&intf->dev, "adap id %u\n", adap->id);
+
+	/* Remove I2C demod */
+	client = state->demod_i2c_client[adap->id];
+	dvb_module_release(client);
+
+	return 0;
+}
+
+static struct mt2060_config af9015_mt2060_config = {
+	.i2c_address = 0x60,
+	.clock_out = 0,
+};
+
+static struct qt1010_config af9015_qt1010_config = {
+	.i2c_address = 0x62,
+};
+
+static struct tda18271_config af9015_tda18271_config = {
+	.gate = TDA18271_GATE_DIGITAL,
+	.small_i2c = TDA18271_16_BYTE_CHUNK_INIT,
+};
+
+static struct mxl5005s_config af9015_mxl5003_config = {
+	.i2c_address     = 0x63,
+	.if_freq         = IF_FREQ_4570000HZ,
+	.xtal_freq       = CRYSTAL_FREQ_16000000HZ,
+	.agc_mode        = MXL_SINGLE_AGC,
+	.tracking_filter = MXL_TF_DEFAULT,
+	.rssi_enable     = MXL_RSSI_ENABLE,
+	.cap_select      = MXL_CAP_SEL_ENABLE,
+	.div_out         = MXL_DIV_OUT_4,
+	.clock_out       = MXL_CLOCK_OUT_DISABLE,
+	.output_load     = MXL5005S_IF_OUTPUT_LOAD_200_OHM,
+	.top		 = MXL5005S_TOP_25P2,
+	.mod_mode        = MXL_DIGITAL_MODE,
+	.if_mode         = MXL_ZERO_IF,
+	.AgcMasterByte   = 0x00,
+};
+
+static struct mxl5005s_config af9015_mxl5005_config = {
+	.i2c_address     = 0x63,
+	.if_freq         = IF_FREQ_4570000HZ,
+	.xtal_freq       = CRYSTAL_FREQ_16000000HZ,
+	.agc_mode        = MXL_SINGLE_AGC,
+	.tracking_filter = MXL_TF_OFF,
+	.rssi_enable     = MXL_RSSI_ENABLE,
+	.cap_select      = MXL_CAP_SEL_ENABLE,
+	.div_out         = MXL_DIV_OUT_4,
+	.clock_out       = MXL_CLOCK_OUT_DISABLE,
+	.output_load     = MXL5005S_IF_OUTPUT_LOAD_200_OHM,
+	.top		 = MXL5005S_TOP_25P2,
+	.mod_mode        = MXL_DIGITAL_MODE,
+	.if_mode         = MXL_ZERO_IF,
+	.AgcMasterByte   = 0x00,
+};
+
+static struct mc44s803_config af9015_mc44s803_config = {
+	.i2c_address = 0x60,
+	.dig_out = 1,
+};
+
+static struct tda18218_config af9015_tda18218_config = {
+	.i2c_address = 0x60,
+	.i2c_wr_max = 21, /* max wr bytes AF9015 I2C adap can handle at once */
+};
+
+static struct mxl5007t_config af9015_mxl5007t_config = {
+	.xtal_freq_hz = MxL_XTAL_24_MHZ,
+	.if_freq_hz = MxL_IF_4_57_MHZ,
+};
+
+static int af9015_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct af9015_state *state = d_to_priv(d);
+	struct usb_interface *intf = d->intf;
+	struct i2c_client *client;
+	struct i2c_adapter *adapter;
+	int ret;
+
+	dev_dbg(&intf->dev, "adap id %u\n", adap->id);
+
+	client = state->demod_i2c_client[adap->id];
+	adapter = state->af9013_pdata[adap->id].get_i2c_adapter(client);
+
+	switch (state->af9013_pdata[adap->id].tuner) {
+	case AF9013_TUNER_MT2060:
+	case AF9013_TUNER_MT2060_2:
+		ret = dvb_attach(mt2060_attach, adap->fe[0], adapter,
+				 &af9015_mt2060_config,
+				 state->mt2060_if1[adap->id]) == NULL ? -ENODEV : 0;
+		break;
+	case AF9013_TUNER_QT1010:
+	case AF9013_TUNER_QT1010A:
+		ret = dvb_attach(qt1010_attach, adap->fe[0], adapter,
+				 &af9015_qt1010_config) == NULL ? -ENODEV : 0;
+		break;
+	case AF9013_TUNER_TDA18271:
+		ret = dvb_attach(tda18271_attach, adap->fe[0], 0x60, adapter,
+				 &af9015_tda18271_config) == NULL ? -ENODEV : 0;
+		break;
+	case AF9013_TUNER_TDA18218:
+		ret = dvb_attach(tda18218_attach, adap->fe[0], adapter,
+				 &af9015_tda18218_config) == NULL ? -ENODEV : 0;
+		break;
+	case AF9013_TUNER_MXL5003D:
+		ret = dvb_attach(mxl5005s_attach, adap->fe[0], adapter,
+				 &af9015_mxl5003_config) == NULL ? -ENODEV : 0;
+		break;
+	case AF9013_TUNER_MXL5005D:
+	case AF9013_TUNER_MXL5005R:
+		ret = dvb_attach(mxl5005s_attach, adap->fe[0], adapter,
+				 &af9015_mxl5005_config) == NULL ? -ENODEV : 0;
+		break;
+	case AF9013_TUNER_ENV77H11D5:
+		ret = dvb_attach(dvb_pll_attach, adap->fe[0], 0x60, adapter,
+				 DVB_PLL_TDA665X) == NULL ? -ENODEV : 0;
+		break;
+	case AF9013_TUNER_MC44S803:
+		ret = dvb_attach(mc44s803_attach, adap->fe[0], adapter,
+				 &af9015_mc44s803_config) == NULL ? -ENODEV : 0;
+		break;
+	case AF9013_TUNER_MXL5007T:
+		ret = dvb_attach(mxl5007t_attach, adap->fe[0], adapter,
+				 0x60, &af9015_mxl5007t_config) == NULL ? -ENODEV : 0;
+		break;
+	case AF9013_TUNER_UNKNOWN:
+	default:
+		dev_err(&intf->dev, "unknown tuner, tuner id %02x\n",
+			state->af9013_pdata[adap->id].tuner);
+		ret = -ENODEV;
+	}
+
+	if (adap->fe[0]->ops.tuner_ops.init) {
+		state->tuner_init[adap->id] =
+			adap->fe[0]->ops.tuner_ops.init;
+		adap->fe[0]->ops.tuner_ops.init = af9015_tuner_init;
+	}
+
+	if (adap->fe[0]->ops.tuner_ops.sleep) {
+		state->tuner_sleep[adap->id] =
+			adap->fe[0]->ops.tuner_ops.sleep;
+		adap->fe[0]->ops.tuner_ops.sleep = af9015_tuner_sleep;
+	}
+
+	return ret;
+}
+
+static int af9015_pid_filter_ctrl(struct dvb_usb_adapter *adap, int onoff)
+{
+	struct af9015_state *state = adap_to_priv(adap);
+	struct af9013_platform_data *pdata = &state->af9013_pdata[adap->id];
+	int ret;
+
+	mutex_lock(&state->fe_mutex);
+	ret = pdata->pid_filter_ctrl(adap->fe[0], onoff);
+	mutex_unlock(&state->fe_mutex);
+
+	return ret;
+}
+
+static int af9015_pid_filter(struct dvb_usb_adapter *adap, int index,
+			     u16 pid, int onoff)
+{
+	struct af9015_state *state = adap_to_priv(adap);
+	struct af9013_platform_data *pdata = &state->af9013_pdata[adap->id];
+	int ret;
+
+	mutex_lock(&state->fe_mutex);
+	ret = pdata->pid_filter(adap->fe[0], index, pid, onoff);
+	mutex_unlock(&state->fe_mutex);
+
+	return ret;
+}
+
+static int af9015_init(struct dvb_usb_device *d)
+{
+	struct af9015_state *state = d_to_priv(d);
+	struct usb_interface *intf = d->intf;
+	int ret;
+
+	dev_dbg(&intf->dev, "\n");
+
+	mutex_init(&state->fe_mutex);
+
+	/* init RC canary */
+	ret = regmap_write(state->regmap, 0x98e9, 0xff);
+	if (ret)
+		goto error;
+
+error:
+	return ret;
+}
+
+#if IS_ENABLED(CONFIG_RC_CORE)
+struct af9015_rc_setup {
+	unsigned int id;
+	char *rc_codes;
+};
+
+static char *af9015_rc_setup_match(unsigned int id,
+				   const struct af9015_rc_setup *table)
+{
+	for (; table->rc_codes; table++)
+		if (table->id == id)
+			return table->rc_codes;
+	return NULL;
+}
+
+static const struct af9015_rc_setup af9015_rc_setup_modparam[] = {
+	{ AF9015_REMOTE_A_LINK_DTU_M, RC_MAP_ALINK_DTU_M },
+	{ AF9015_REMOTE_MSI_DIGIVOX_MINI_II_V3, RC_MAP_MSI_DIGIVOX_II },
+	{ AF9015_REMOTE_MYGICTV_U718, RC_MAP_TOTAL_MEDIA_IN_HAND },
+	{ AF9015_REMOTE_DIGITTRADE_DVB_T, RC_MAP_DIGITTRADE },
+	{ AF9015_REMOTE_AVERMEDIA_KS, RC_MAP_AVERMEDIA_RM_KS },
+	{ }
+};
+
+static const struct af9015_rc_setup af9015_rc_setup_hashes[] = {
+	{ 0xb8feb708, RC_MAP_MSI_DIGIVOX_II },
+	{ 0xa3703d00, RC_MAP_ALINK_DTU_M },
+	{ 0x9b7dc64e, RC_MAP_TOTAL_MEDIA_IN_HAND }, /* MYGICTV U718 */
+	{ 0x5d49e3db, RC_MAP_DIGITTRADE }, /* LC-Power LC-USB-DVBT */
+	{ }
+};
+
+static int af9015_rc_query(struct dvb_usb_device *d)
+{
+	struct af9015_state *state = d_to_priv(d);
+	struct usb_interface *intf = d->intf;
+	int ret;
+	u8 buf[17];
+
+	/* read registers needed to detect remote controller code */
+	ret = regmap_bulk_read(state->regmap, 0x98d9, buf, sizeof(buf));
+	if (ret)
+		goto error;
+
+	/* If any of these are non-zero, assume invalid data */
+	if (buf[1] || buf[2] || buf[3]) {
+		dev_dbg(&intf->dev, "invalid data\n");
+		return ret;
+	}
+
+	/* Check for repeat of previous code */
+	if ((state->rc_repeat != buf[6] || buf[0]) &&
+	    !memcmp(&buf[12], state->rc_last, 4)) {
+		dev_dbg(&intf->dev, "key repeated\n");
+		rc_repeat(d->rc_dev);
+		state->rc_repeat = buf[6];
+		return ret;
+	}
+
+	/* Only process key if canary killed */
+	if (buf[16] != 0xff && buf[0] != 0x01) {
+		enum rc_proto proto;
+
+		dev_dbg(&intf->dev, "key pressed %*ph\n", 4, buf + 12);
+
+		/* Reset the canary */
+		ret = regmap_write(state->regmap, 0x98e9, 0xff);
+		if (ret)
+			goto error;
+
+		/* Remember this key */
+		memcpy(state->rc_last, &buf[12], 4);
+		if (buf[14] == (u8)~buf[15]) {
+			if (buf[12] == (u8)~buf[13]) {
+				/* NEC */
+				state->rc_keycode = RC_SCANCODE_NEC(buf[12],
+								    buf[14]);
+				proto = RC_PROTO_NEC;
+			} else {
+				/* NEC extended*/
+				state->rc_keycode = RC_SCANCODE_NECX(buf[12] << 8 |
+								     buf[13],
+								     buf[14]);
+				proto = RC_PROTO_NECX;
+			}
+		} else {
+			/* 32 bit NEC */
+			state->rc_keycode = RC_SCANCODE_NEC32(buf[12] << 24 |
+							      buf[13] << 16 |
+							      buf[14] << 8  |
+							      buf[15]);
+			proto = RC_PROTO_NEC32;
+		}
+		rc_keydown(d->rc_dev, proto, state->rc_keycode, 0);
+	} else {
+		dev_dbg(&intf->dev, "no key press\n");
+		/* Invalidate last keypress */
+		/* Not really needed, but helps with debug */
+		state->rc_last[2] = state->rc_last[3];
+	}
+
+	state->rc_repeat = buf[6];
+	state->rc_failed = false;
+
+error:
+	if (ret) {
+		dev_warn(&intf->dev, "rc query failed %d\n", ret);
+
+		/* allow random errors as dvb-usb will stop polling on error */
+		if (!state->rc_failed)
+			ret = 0;
+
+		state->rc_failed = true;
+	}
+
+	return ret;
+}
+
+static int af9015_get_rc_config(struct dvb_usb_device *d, struct dvb_usb_rc *rc)
+{
+	struct af9015_state *state = d_to_priv(d);
+	u16 vid = le16_to_cpu(d->udev->descriptor.idVendor);
+
+	if (state->ir_mode == AF9015_IR_MODE_DISABLED)
+		return 0;
+
+	/* try to load remote based module param */
+	if (!rc->map_name)
+		rc->map_name = af9015_rc_setup_match(dvb_usb_af9015_remote,
+						     af9015_rc_setup_modparam);
+
+	/* try to load remote based eeprom hash */
+	if (!rc->map_name)
+		rc->map_name = af9015_rc_setup_match(state->eeprom_sum,
+						     af9015_rc_setup_hashes);
+
+	/* try to load remote based USB iManufacturer string */
+	if (!rc->map_name && vid == USB_VID_AFATECH) {
+		/*
+		 * Check USB manufacturer and product strings and try
+		 * to determine correct remote in case of chip vendor
+		 * reference IDs are used.
+		 * DO NOT ADD ANYTHING NEW HERE. Use hashes instead.
+		 */
+		char manufacturer[10];
+
+		memset(manufacturer, 0, sizeof(manufacturer));
+		usb_string(d->udev, d->udev->descriptor.iManufacturer,
+			   manufacturer, sizeof(manufacturer));
+		if (!strcmp("MSI", manufacturer)) {
+			/*
+			 * iManufacturer 1 MSI
+			 * iProduct      2 MSI K-VOX
+			 */
+			rc->map_name = af9015_rc_setup_match(AF9015_REMOTE_MSI_DIGIVOX_MINI_II_V3,
+							     af9015_rc_setup_modparam);
+		}
+	}
+
+	/* load empty to enable rc */
+	if (!rc->map_name)
+		rc->map_name = RC_MAP_EMPTY;
+
+	rc->allowed_protos = RC_PROTO_BIT_NEC | RC_PROTO_BIT_NECX |
+						RC_PROTO_BIT_NEC32;
+	rc->query = af9015_rc_query;
+	rc->interval = 500;
+
+	return 0;
+}
+#else
+	#define af9015_get_rc_config NULL
+#endif
+
+static int af9015_regmap_write(void *context, const void *data, size_t count)
+{
+	struct dvb_usb_device *d = context;
+	struct usb_interface *intf = d->intf;
+	int ret;
+	u16 reg = ((u8 *)data)[0] << 8 | ((u8 *)data)[1] << 0;
+	u8 *val = &((u8 *)data)[2];
+	const unsigned int len = count - 2;
+	struct req_t req = {WRITE_MEMORY, 0, reg, 0, 0, len, val};
+
+	ret = af9015_ctrl_msg(d, &req);
+	if (ret)
+		goto err;
+
+	return 0;
+err:
+	dev_dbg(&intf->dev, "failed %d\n", ret);
+	return ret;
+}
+
+static int af9015_regmap_read(void *context, const void *reg_buf,
+			      size_t reg_size, void *val_buf, size_t val_size)
+{
+	struct dvb_usb_device *d = context;
+	struct usb_interface *intf = d->intf;
+	int ret;
+	u16 reg = ((u8 *)reg_buf)[0] << 8 | ((u8 *)reg_buf)[1] << 0;
+	u8 *val = &((u8 *)val_buf)[0];
+	const unsigned int len = val_size;
+	struct req_t req = {READ_MEMORY, 0, reg, 0, 0, len, val};
+
+	ret = af9015_ctrl_msg(d, &req);
+	if (ret)
+		goto err;
+
+	return 0;
+err:
+	dev_dbg(&intf->dev, "failed %d\n", ret);
+	return ret;
+}
+
+static int af9015_probe(struct dvb_usb_device *d)
+{
+	struct af9015_state *state = d_to_priv(d);
+	struct usb_interface *intf = d->intf;
+	struct usb_device *udev = interface_to_usbdev(intf);
+	int ret;
+	char manufacturer[sizeof("ITE Technologies, Inc.")];
+	static const struct regmap_config regmap_config = {
+		.reg_bits    =  16,
+		.val_bits    =  8,
+	};
+	static const struct regmap_bus regmap_bus = {
+		.read = af9015_regmap_read,
+		.write = af9015_regmap_write,
+	};
+
+	dev_dbg(&intf->dev, "\n");
+
+	memset(manufacturer, 0, sizeof(manufacturer));
+	usb_string(udev, udev->descriptor.iManufacturer,
+		   manufacturer, sizeof(manufacturer));
+	/*
+	 * There is two devices having same ID but different chipset. One uses
+	 * AF9015 and the other IT9135 chipset. Only difference seen on lsusb
+	 * is iManufacturer string.
+	 *
+	 * idVendor           0x0ccd TerraTec Electronic GmbH
+	 * idProduct          0x0099
+	 * bcdDevice            2.00
+	 * iManufacturer           1 Afatech
+	 * iProduct                2 DVB-T 2
+	 *
+	 * idVendor           0x0ccd TerraTec Electronic GmbH
+	 * idProduct          0x0099
+	 * bcdDevice            2.00
+	 * iManufacturer           1 ITE Technologies, Inc.
+	 * iProduct                2 DVB-T TV Stick
+	 */
+	if ((le16_to_cpu(udev->descriptor.idVendor) == USB_VID_TERRATEC) &&
+	    (le16_to_cpu(udev->descriptor.idProduct) == 0x0099)) {
+		if (!strcmp("ITE Technologies, Inc.", manufacturer)) {
+			ret = -ENODEV;
+			dev_dbg(&intf->dev, "rejecting device\n");
+			goto err;
+		}
+	}
+
+	state->regmap = regmap_init(&intf->dev, &regmap_bus, d, &regmap_config);
+	if (IS_ERR(state->regmap)) {
+		ret = PTR_ERR(state->regmap);
+		goto err;
+	}
+
+	return 0;
+err:
+	dev_dbg(&intf->dev, "failed %d\n", ret);
+	return ret;
+}
+
+static void af9015_disconnect(struct dvb_usb_device *d)
+{
+	struct af9015_state *state = d_to_priv(d);
+	struct usb_interface *intf = d->intf;
+
+	dev_dbg(&intf->dev, "\n");
+
+	regmap_exit(state->regmap);
+}
+
+/*
+ * Interface 0 is used by DVB-T receiver and
+ * interface 1 is for remote controller (HID)
+ */
+static const struct dvb_usb_device_properties af9015_props = {
+	.driver_name = KBUILD_MODNAME,
+	.owner = THIS_MODULE,
+	.adapter_nr = adapter_nr,
+	.size_of_priv = sizeof(struct af9015_state),
+
+	.generic_bulk_ctrl_endpoint = 0x02,
+	.generic_bulk_ctrl_endpoint_response = 0x81,
+
+	.probe = af9015_probe,
+	.disconnect = af9015_disconnect,
+	.identify_state = af9015_identify_state,
+	.firmware = AF9015_FIRMWARE,
+	.download_firmware = af9015_download_firmware,
+
+	.i2c_algo = &af9015_i2c_algo,
+	.read_config = af9015_read_config,
+	.frontend_attach = af9015_af9013_frontend_attach,
+	.frontend_detach = af9015_frontend_detach,
+	.tuner_attach = af9015_tuner_attach,
+	.init = af9015_init,
+	.get_rc_config = af9015_get_rc_config,
+	.get_stream_config = af9015_get_stream_config,
+	.streaming_ctrl = af9015_streaming_ctrl,
+
+	.get_adapter_count = af9015_get_adapter_count,
+	.adapter = {
+		{
+			.caps = DVB_USB_ADAP_HAS_PID_FILTER |
+				DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+			.pid_filter_count = 32,
+			.pid_filter = af9015_pid_filter,
+			.pid_filter_ctrl = af9015_pid_filter_ctrl,
+
+			.stream = DVB_USB_STREAM_BULK(0x84, 6, 87 * 188),
+		}, {
+			.caps = DVB_USB_ADAP_HAS_PID_FILTER |
+				DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+			.pid_filter_count = 32,
+			.pid_filter = af9015_pid_filter,
+			.pid_filter_ctrl = af9015_pid_filter_ctrl,
+
+			.stream = DVB_USB_STREAM_BULK(0x85, 6, 87 * 188),
+		},
+	},
+};
+
+static const struct usb_device_id af9015_id_table[] = {
+	{ DVB_USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9015_9015,
+		&af9015_props, "Afatech AF9015 reference design", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9015_9016,
+		&af9015_props, "Afatech AF9015 reference design", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_LEADTEK, USB_PID_WINFAST_DTV_DONGLE_GOLD,
+		&af9015_props, "Leadtek WinFast DTV Dongle Gold", RC_MAP_LEADTEK_Y04G0051) },
+	{ DVB_USB_DEVICE(USB_VID_PINNACLE, USB_PID_PINNACLE_PCTV71E,
+		&af9015_props, "Pinnacle PCTV 71e", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_399U,
+		&af9015_props, "KWorld PlusTV Dual DVB-T Stick (DVB-T 399U)", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_VISIONPLUS, USB_PID_TINYTWIN,
+		&af9015_props, "DigitalNow TinyTwin", RC_MAP_AZUREWAVE_AD_TU700) },
+	{ DVB_USB_DEVICE(USB_VID_VISIONPLUS, USB_PID_AZUREWAVE_AD_TU700,
+		&af9015_props, "TwinHan AzureWave AD-TU700(704J)", RC_MAP_AZUREWAVE_AD_TU700) },
+	{ DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_CINERGY_T_USB_XE_REV2,
+		&af9015_props, "TerraTec Cinergy T USB XE", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_PC160_2T,
+		&af9015_props, "KWorld PlusTV Dual DVB-T PCI (DVB-T PC160-2T)", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_VOLAR_X,
+		&af9015_props, "AVerMedia AVerTV DVB-T Volar X", RC_MAP_AVERMEDIA_M135A) },
+	{ DVB_USB_DEVICE(USB_VID_XTENSIONS, USB_PID_XTENSIONS_XD_380,
+		&af9015_props, "Xtensions XD-380", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_MSI_2, USB_PID_MSI_DIGIVOX_DUO,
+		&af9015_props, "MSI DIGIVOX Duo", RC_MAP_MSI_DIGIVOX_III) },
+	{ DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_VOLAR_X_2,
+		&af9015_props, "Fujitsu-Siemens Slim Mobile USB DVB-T", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_TELESTAR,  USB_PID_TELESTAR_STARSTICK_2,
+		&af9015_props, "Telestar Starstick 2", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A309,
+		&af9015_props, "AVerMedia A309", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_MSI_2, USB_PID_MSI_DIGI_VOX_MINI_III,
+		&af9015_props, "MSI Digi VOX mini III", RC_MAP_MSI_DIGIVOX_III) },
+	{ DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_395U,
+		&af9015_props, "KWorld USB DVB-T TV Stick II (VS-DVB-T 395U)", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_395U_2,
+		&af9015_props, "KWorld USB DVB-T TV Stick II (VS-DVB-T 395U)", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_395U_3,
+		&af9015_props, "KWorld USB DVB-T TV Stick II (VS-DVB-T 395U)", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_AFATECH, USB_PID_TREKSTOR_DVBT,
+		&af9015_props, "TrekStor DVB-T USB Stick", RC_MAP_TREKSTOR) },
+	{ DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A850,
+		&af9015_props, "AverMedia AVerTV Volar Black HD (A850)", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A805,
+		&af9015_props, "AverMedia AVerTV Volar GPS 805 (A805)", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_CONCEPTRONIC_CTVDIGRCU,
+		&af9015_props, "Conceptronic USB2.0 DVB-T CTVDIGRCU V3.0", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_MC810,
+		&af9015_props, "KWorld Digital MC-810", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_KYE, USB_PID_GENIUS_TVGO_DVB_T03,
+		&af9015_props, "Genius TVGo DVB-T03", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_399U_2,
+		&af9015_props, "KWorld PlusTV Dual DVB-T Stick (DVB-T 399U)", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_PC160_T,
+		&af9015_props, "KWorld PlusTV DVB-T PCI Pro Card (DVB-T PC160-T)", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_SVEON_STV20,
+		&af9015_props, "Sveon STV20 Tuner USB DVB-T HDTV", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_TINYTWIN_2,
+		&af9015_props, "DigitalNow TinyTwin v2", RC_MAP_DIGITALNOW_TINYTWIN) },
+	{ DVB_USB_DEVICE(USB_VID_LEADTEK, USB_PID_WINFAST_DTV2000DS,
+		&af9015_props, "Leadtek WinFast DTV2000DS", RC_MAP_LEADTEK_Y04G0051) },
+	{ DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_UB383_T,
+		&af9015_props, "KWorld USB DVB-T Stick Mobile (UB383-T)", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_395U_4,
+		&af9015_props, "KWorld USB DVB-T TV Stick II (VS-DVB-T 395U)", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A815M,
+		&af9015_props, "AverMedia AVerTV Volar M (A815Mac)", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_CINERGY_T_STICK_RC,
+		&af9015_props, "TerraTec Cinergy T Stick RC", RC_MAP_TERRATEC_SLIM_2) },
+	/* XXX: that same ID [0ccd:0099] is used by af9035 driver too */
+	{ DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_CINERGY_T_STICK_DUAL_RC,
+		&af9015_props, "TerraTec Cinergy T Stick Dual RC", RC_MAP_TERRATEC_SLIM) },
+	{ DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A850T,
+		&af9015_props, "AverMedia AVerTV Red HD+ (A850T)", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_GTEK, USB_PID_TINYTWIN_3,
+		&af9015_props, "DigitalNow TinyTwin v3", RC_MAP_DIGITALNOW_TINYTWIN) },
+	{ DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_SVEON_STV22,
+		&af9015_props, "Sveon STV22 Dual USB DVB-T Tuner HDTV", RC_MAP_MSI_DIGIVOX_III) },
+	{ }
+};
+MODULE_DEVICE_TABLE(usb, af9015_id_table);
+
+/* usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver af9015_usb_driver = {
+	.name = KBUILD_MODNAME,
+	.id_table = af9015_id_table,
+	.probe = dvb_usbv2_probe,
+	.disconnect = dvb_usbv2_disconnect,
+	.suspend = dvb_usbv2_suspend,
+	.resume = dvb_usbv2_resume,
+	.reset_resume = dvb_usbv2_reset_resume,
+	.no_dynamic_id = 1,
+	.soft_unbind = 1,
+};
+
+module_usb_driver(af9015_usb_driver);
+
+MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>");
+MODULE_DESCRIPTION("Afatech AF9015 driver");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(AF9015_FIRMWARE);
diff --git a/drivers/media/usb/dvb-usb-v2/af9015.h b/drivers/media/usb/dvb-usb-v2/af9015.h
new file mode 100644
index 0000000..ad2b045
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/af9015.h
@@ -0,0 +1,141 @@
+/*
+ * DVB USB Linux driver for Afatech AF9015 DVB-T USB2.0 receiver
+ *
+ * Copyright (C) 2007 Antti Palosaari <crope@iki.fi>
+ *
+ * Thanks to Afatech who kindly provided information.
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ */
+
+#ifndef AF9015_H
+#define AF9015_H
+
+#include <linux/hash.h>
+#include <linux/regmap.h>
+#include "dvb_usb.h"
+#include "af9013.h"
+#include "dvb-pll.h"
+#include "mt2060.h"
+#include "qt1010.h"
+#include "tda18271.h"
+#include "mxl5005s.h"
+#include "mc44s803.h"
+#include "tda18218.h"
+#include "mxl5007t.h"
+
+#define AF9015_FIRMWARE "dvb-usb-af9015.fw"
+
+#define AF9015_I2C_EEPROM  0x50
+#define AF9015_I2C_DEMOD   0x1c
+#define AF9015_USB_TIMEOUT 2000
+
+/* EEPROM locations */
+#define AF9015_EEPROM_IR_MODE        0x18
+#define AF9015_EEPROM_IR_REMOTE_TYPE 0x34
+#define AF9015_EEPROM_TS_MODE        0x31
+#define AF9015_EEPROM_DEMOD2_I2C     0x32
+
+#define AF9015_EEPROM_SAW_BW1        0x35
+#define AF9015_EEPROM_XTAL_TYPE1     0x36
+#define AF9015_EEPROM_SPEC_INV1      0x37
+#define AF9015_EEPROM_IF1L           0x38
+#define AF9015_EEPROM_IF1H           0x39
+#define AF9015_EEPROM_MT2060_IF1L    0x3a
+#define AF9015_EEPROM_MT2060_IF1H    0x3b
+#define AF9015_EEPROM_TUNER_ID1      0x3c
+
+#define AF9015_EEPROM_SAW_BW2        0x45
+#define AF9015_EEPROM_XTAL_TYPE2     0x46
+#define AF9015_EEPROM_SPEC_INV2      0x47
+#define AF9015_EEPROM_IF2L           0x48
+#define AF9015_EEPROM_IF2H           0x49
+#define AF9015_EEPROM_MT2060_IF2L    0x4a
+#define AF9015_EEPROM_MT2060_IF2H    0x4b
+#define AF9015_EEPROM_TUNER_ID2      0x4c
+
+#define AF9015_EEPROM_OFFSET (AF9015_EEPROM_SAW_BW2 - AF9015_EEPROM_SAW_BW1)
+
+struct req_t {
+	u8  cmd;       /* [0] */
+	/*  seq */     /* [1] */
+	u8  i2c_addr;  /* [2] */
+	u16 addr;      /* [3|4] */
+	u8  mbox;      /* [5] */
+	u8  addr_len;  /* [6] */
+	u8  data_len;  /* [7] */
+	u8  *data;
+};
+
+enum af9015_cmd {
+	GET_CONFIG           = 0x10,
+	DOWNLOAD_FIRMWARE    = 0x11,
+	BOOT                 = 0x13,
+	READ_MEMORY          = 0x20,
+	WRITE_MEMORY         = 0x21,
+	READ_WRITE_I2C       = 0x22,
+	COPY_FIRMWARE        = 0x23,
+	RECONNECT_USB        = 0x5a,
+	WRITE_VIRTUAL_MEMORY = 0x26,
+	GET_IR_CODE          = 0x27,
+	READ_I2C,
+	WRITE_I2C,
+};
+
+enum af9015_ir_mode {
+	AF9015_IR_MODE_DISABLED = 0,
+	AF9015_IR_MODE_HID,
+	AF9015_IR_MODE_RLC,
+	AF9015_IR_MODE_RC6,
+	AF9015_IR_MODE_POLLING, /* just guess */
+};
+
+#define BUF_LEN 63
+struct af9015_state {
+	struct regmap *regmap;
+	u8 buf[BUF_LEN]; /* bulk USB control message */
+	u8 ir_mode;
+	u8 rc_repeat;
+	u32 rc_keycode;
+	u8 rc_last[4];
+	bool rc_failed;
+	u8 dual_mode;
+	u8 seq; /* packet sequence number */
+	u16 mt2060_if1[2];
+	u16 firmware_size;
+	u16 firmware_checksum;
+	u32 eeprom_sum;
+	struct af9013_platform_data af9013_pdata[2];
+	struct i2c_client *demod_i2c_client[2];
+	u8 af9013_i2c_addr[2];
+	bool usb_ts_if_configured[2];
+
+	/* for demod callback override */
+	int (*set_frontend[2]) (struct dvb_frontend *fe);
+	int (*read_status[2]) (struct dvb_frontend *fe, enum fe_status *status);
+	int (*init[2]) (struct dvb_frontend *fe);
+	int (*sleep[2]) (struct dvb_frontend *fe);
+	int (*tuner_init[2]) (struct dvb_frontend *fe);
+	int (*tuner_sleep[2]) (struct dvb_frontend *fe);
+	struct mutex fe_mutex;
+};
+
+enum af9015_remote {
+	AF9015_REMOTE_NONE                    = 0,
+/* 1 */	AF9015_REMOTE_A_LINK_DTU_M,
+	AF9015_REMOTE_MSI_DIGIVOX_MINI_II_V3,
+	AF9015_REMOTE_MYGICTV_U718,
+	AF9015_REMOTE_DIGITTRADE_DVB_T,
+/* 5 */	AF9015_REMOTE_AVERMEDIA_KS,
+};
+
+#endif
diff --git a/drivers/media/usb/dvb-usb-v2/af9035.c b/drivers/media/usb/dvb-usb-v2/af9035.c
new file mode 100644
index 0000000..1f6c1ee
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/af9035.c
@@ -0,0 +1,2155 @@
+/*
+ * Afatech AF9035 DVB USB driver
+ *
+ * Copyright (C) 2009 Antti Palosaari <crope@iki.fi>
+ * Copyright (C) 2012 Antti Palosaari <crope@iki.fi>
+ *
+ *    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 "af9035.h"
+
+/* Max transfer size done by I2C transfer functions */
+#define MAX_XFER_SIZE  64
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static u16 af9035_checksum(const u8 *buf, size_t len)
+{
+	size_t i;
+	u16 checksum = 0;
+
+	for (i = 1; i < len; i++) {
+		if (i % 2)
+			checksum += buf[i] << 8;
+		else
+			checksum += buf[i];
+	}
+	checksum = ~checksum;
+
+	return checksum;
+}
+
+static int af9035_ctrl_msg(struct dvb_usb_device *d, struct usb_req *req)
+{
+#define REQ_HDR_LEN 4 /* send header size */
+#define ACK_HDR_LEN 3 /* rece header size */
+#define CHECKSUM_LEN 2
+#define USB_TIMEOUT 2000
+	struct state *state = d_to_priv(d);
+	struct usb_interface *intf = d->intf;
+	int ret, wlen, rlen;
+	u16 checksum, tmp_checksum;
+
+	mutex_lock(&d->usb_mutex);
+
+	/* buffer overflow check */
+	if (req->wlen > (BUF_LEN - REQ_HDR_LEN - CHECKSUM_LEN) ||
+			req->rlen > (BUF_LEN - ACK_HDR_LEN - CHECKSUM_LEN)) {
+		dev_err(&intf->dev, "too much data wlen=%d rlen=%d\n",
+			req->wlen, req->rlen);
+		ret = -EINVAL;
+		goto exit;
+	}
+
+	state->buf[0] = REQ_HDR_LEN + req->wlen + CHECKSUM_LEN - 1;
+	state->buf[1] = req->mbox;
+	state->buf[2] = req->cmd;
+	state->buf[3] = state->seq++;
+	memcpy(&state->buf[REQ_HDR_LEN], req->wbuf, req->wlen);
+
+	wlen = REQ_HDR_LEN + req->wlen + CHECKSUM_LEN;
+	rlen = ACK_HDR_LEN + req->rlen + CHECKSUM_LEN;
+
+	/* calc and add checksum */
+	checksum = af9035_checksum(state->buf, state->buf[0] - 1);
+	state->buf[state->buf[0] - 1] = (checksum >> 8);
+	state->buf[state->buf[0] - 0] = (checksum & 0xff);
+
+	/* no ack for these packets */
+	if (req->cmd == CMD_FW_DL)
+		rlen = 0;
+
+	ret = dvb_usbv2_generic_rw_locked(d,
+			state->buf, wlen, state->buf, rlen);
+	if (ret)
+		goto exit;
+
+	/* no ack for those packets */
+	if (req->cmd == CMD_FW_DL)
+		goto exit;
+
+	/* verify checksum */
+	checksum = af9035_checksum(state->buf, rlen - 2);
+	tmp_checksum = (state->buf[rlen - 2] << 8) | state->buf[rlen - 1];
+	if (tmp_checksum != checksum) {
+		dev_err(&intf->dev, "command=%02x checksum mismatch (%04x != %04x)\n",
+			req->cmd, tmp_checksum, checksum);
+		ret = -EIO;
+		goto exit;
+	}
+
+	/* check status */
+	if (state->buf[2]) {
+		/* fw returns status 1 when IR code was not received */
+		if (req->cmd == CMD_IR_GET || state->buf[2] == 1) {
+			ret = 1;
+			goto exit;
+		}
+
+		dev_dbg(&intf->dev, "command=%02x failed fw error=%d\n",
+			req->cmd, state->buf[2]);
+		ret = -EIO;
+		goto exit;
+	}
+
+	/* read request, copy returned data to return buf */
+	if (req->rlen)
+		memcpy(req->rbuf, &state->buf[ACK_HDR_LEN], req->rlen);
+exit:
+	mutex_unlock(&d->usb_mutex);
+	if (ret < 0)
+		dev_dbg(&intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+/* write multiple registers */
+static int af9035_wr_regs(struct dvb_usb_device *d, u32 reg, u8 *val, int len)
+{
+	struct usb_interface *intf = d->intf;
+	u8 wbuf[MAX_XFER_SIZE];
+	u8 mbox = (reg >> 16) & 0xff;
+	struct usb_req req = { CMD_MEM_WR, mbox, 6 + len, wbuf, 0, NULL };
+
+	if (6 + len > sizeof(wbuf)) {
+		dev_warn(&intf->dev, "i2c wr: len=%d is too big!\n", len);
+		return -EOPNOTSUPP;
+	}
+
+	wbuf[0] = len;
+	wbuf[1] = 2;
+	wbuf[2] = 0;
+	wbuf[3] = 0;
+	wbuf[4] = (reg >> 8) & 0xff;
+	wbuf[5] = (reg >> 0) & 0xff;
+	memcpy(&wbuf[6], val, len);
+
+	return af9035_ctrl_msg(d, &req);
+}
+
+/* read multiple registers */
+static int af9035_rd_regs(struct dvb_usb_device *d, u32 reg, u8 *val, int len)
+{
+	u8 wbuf[] = { len, 2, 0, 0, (reg >> 8) & 0xff, reg & 0xff };
+	u8 mbox = (reg >> 16) & 0xff;
+	struct usb_req req = { CMD_MEM_RD, mbox, sizeof(wbuf), wbuf, len, val };
+
+	return af9035_ctrl_msg(d, &req);
+}
+
+/* write single register */
+static int af9035_wr_reg(struct dvb_usb_device *d, u32 reg, u8 val)
+{
+	return af9035_wr_regs(d, reg, &val, 1);
+}
+
+/* read single register */
+static int af9035_rd_reg(struct dvb_usb_device *d, u32 reg, u8 *val)
+{
+	return af9035_rd_regs(d, reg, val, 1);
+}
+
+/* write single register with mask */
+static int af9035_wr_reg_mask(struct dvb_usb_device *d, u32 reg, u8 val,
+		u8 mask)
+{
+	int ret;
+	u8 tmp;
+
+	/* no need for read if whole reg is written */
+	if (mask != 0xff) {
+		ret = af9035_rd_regs(d, reg, &tmp, 1);
+		if (ret)
+			return ret;
+
+		val &= mask;
+		tmp &= ~mask;
+		val |= tmp;
+	}
+
+	return af9035_wr_regs(d, reg, &val, 1);
+}
+
+static int af9035_add_i2c_dev(struct dvb_usb_device *d, const char *type,
+		u8 addr, void *platform_data, struct i2c_adapter *adapter)
+{
+	int ret, num;
+	struct state *state = d_to_priv(d);
+	struct usb_interface *intf = d->intf;
+	struct i2c_client *client;
+	struct i2c_board_info board_info = {
+		.addr = addr,
+		.platform_data = platform_data,
+	};
+
+	strlcpy(board_info.type, type, I2C_NAME_SIZE);
+
+	/* find first free client */
+	for (num = 0; num < AF9035_I2C_CLIENT_MAX; num++) {
+		if (state->i2c_client[num] == NULL)
+			break;
+	}
+
+	dev_dbg(&intf->dev, "num=%d\n", num);
+
+	if (num == AF9035_I2C_CLIENT_MAX) {
+		dev_err(&intf->dev, "I2C client out of index\n");
+		ret = -ENODEV;
+		goto err;
+	}
+
+	request_module("%s", board_info.type);
+
+	/* register I2C device */
+	client = i2c_new_device(adapter, &board_info);
+	if (client == NULL || client->dev.driver == NULL) {
+		ret = -ENODEV;
+		goto err;
+	}
+
+	/* increase I2C driver usage count */
+	if (!try_module_get(client->dev.driver->owner)) {
+		i2c_unregister_device(client);
+		ret = -ENODEV;
+		goto err;
+	}
+
+	state->i2c_client[num] = client;
+	return 0;
+err:
+	dev_dbg(&intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static void af9035_del_i2c_dev(struct dvb_usb_device *d)
+{
+	int num;
+	struct state *state = d_to_priv(d);
+	struct usb_interface *intf = d->intf;
+	struct i2c_client *client;
+
+	/* find last used client */
+	num = AF9035_I2C_CLIENT_MAX;
+	while (num--) {
+		if (state->i2c_client[num] != NULL)
+			break;
+	}
+
+	dev_dbg(&intf->dev, "num=%d\n", num);
+
+	if (num == -1) {
+		dev_err(&intf->dev, "I2C client out of index\n");
+		goto err;
+	}
+
+	client = state->i2c_client[num];
+
+	/* decrease I2C driver usage count */
+	module_put(client->dev.driver->owner);
+
+	/* unregister I2C device */
+	i2c_unregister_device(client);
+
+	state->i2c_client[num] = NULL;
+	return;
+err:
+	dev_dbg(&intf->dev, "failed\n");
+}
+
+static int af9035_i2c_master_xfer(struct i2c_adapter *adap,
+		struct i2c_msg msg[], int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	struct state *state = d_to_priv(d);
+	int ret;
+
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	/*
+	 * AF9035 I2C sub header is 5 bytes long. Meaning of those bytes are:
+	 * 0: data len
+	 * 1: I2C addr << 1
+	 * 2: reg addr len
+	 *    byte 3 and 4 can be used as reg addr
+	 * 3: reg addr MSB
+	 *    used when reg addr len is set to 2
+	 * 4: reg addr LSB
+	 *    used when reg addr len is set to 1 or 2
+	 *
+	 * For the simplify we do not use register addr at all.
+	 * NOTE: As a firmware knows tuner type there is very small possibility
+	 * there could be some tuner I2C hacks done by firmware and this may
+	 * lead problems if firmware expects those bytes are used.
+	 *
+	 * TODO: Here is few hacks. AF9035 chip integrates AF9033 demodulator.
+	 * IT9135 chip integrates AF9033 demodulator and RF tuner. For dual
+	 * tuner devices, there is also external AF9033 demodulator connected
+	 * via external I2C bus. All AF9033 demod I2C traffic, both single and
+	 * dual tuner configuration, is covered by firmware - actual USB IO
+	 * looks just like a memory access.
+	 * In case of IT913x chip, there is own tuner driver. It is implemented
+	 * currently as a I2C driver, even tuner IP block is likely build
+	 * directly into the demodulator memory space and there is no own I2C
+	 * bus. I2C subsystem does not allow register multiple devices to same
+	 * bus, having same slave address. Due to that we reuse demod address,
+	 * shifted by one bit, on that case.
+	 *
+	 * For IT930x we use a different command and the sub header is
+	 * different as well:
+	 * 0: data len
+	 * 1: I2C bus (0x03 seems to be only value used)
+	 * 2: I2C addr << 1
+	 */
+#define AF9035_IS_I2C_XFER_WRITE_READ(_msg, _num) \
+	(_num == 2 && !(_msg[0].flags & I2C_M_RD) && (_msg[1].flags & I2C_M_RD))
+#define AF9035_IS_I2C_XFER_WRITE(_msg, _num) \
+	(_num == 1 && !(_msg[0].flags & I2C_M_RD))
+#define AF9035_IS_I2C_XFER_READ(_msg, _num) \
+	(_num == 1 && (_msg[0].flags & I2C_M_RD))
+
+	if (AF9035_IS_I2C_XFER_WRITE_READ(msg, num)) {
+		if (msg[0].len > 40 || msg[1].len > 40) {
+			/* TODO: correct limits > 40 */
+			ret = -EOPNOTSUPP;
+		} else if ((msg[0].addr == state->af9033_i2c_addr[0]) ||
+			   (msg[0].addr == state->af9033_i2c_addr[1])) {
+			/* demod access via firmware interface */
+			u32 reg = msg[0].buf[0] << 16 | msg[0].buf[1] << 8 |
+					msg[0].buf[2];
+
+			if (msg[0].addr == state->af9033_i2c_addr[1])
+				reg |= 0x100000;
+
+			ret = af9035_rd_regs(d, reg, &msg[1].buf[0],
+					msg[1].len);
+		} else if (state->no_read) {
+			memset(msg[1].buf, 0, msg[1].len);
+			ret = 0;
+		} else {
+			/* I2C write + read */
+			u8 buf[MAX_XFER_SIZE];
+			struct usb_req req = { CMD_I2C_RD, 0, 5 + msg[0].len,
+					buf, msg[1].len, msg[1].buf };
+
+			if (state->chip_type == 0x9306) {
+				req.cmd = CMD_GENERIC_I2C_RD;
+				req.wlen = 3 + msg[0].len;
+			}
+			req.mbox |= ((msg[0].addr & 0x80)  >>  3);
+
+			buf[0] = msg[1].len;
+			if (state->chip_type == 0x9306) {
+				buf[1] = 0x03; /* I2C bus */
+				buf[2] = msg[0].addr << 1;
+				memcpy(&buf[3], msg[0].buf, msg[0].len);
+			} else {
+				buf[1] = msg[0].addr << 1;
+				buf[3] = 0x00; /* reg addr MSB */
+				buf[4] = 0x00; /* reg addr LSB */
+
+				/* Keep prev behavior for write req len > 2*/
+				if (msg[0].len > 2) {
+					buf[2] = 0x00; /* reg addr len */
+					memcpy(&buf[5], msg[0].buf, msg[0].len);
+
+				/* Use reg addr fields if write req len <= 2 */
+				} else {
+					req.wlen = 5;
+					buf[2] = msg[0].len;
+					if (msg[0].len == 2) {
+						buf[3] = msg[0].buf[0];
+						buf[4] = msg[0].buf[1];
+					} else if (msg[0].len == 1) {
+						buf[4] = msg[0].buf[0];
+					}
+				}
+			}
+			ret = af9035_ctrl_msg(d, &req);
+		}
+	} else if (AF9035_IS_I2C_XFER_WRITE(msg, num)) {
+		if (msg[0].len > 40) {
+			/* TODO: correct limits > 40 */
+			ret = -EOPNOTSUPP;
+		} else if ((msg[0].addr == state->af9033_i2c_addr[0]) ||
+			   (msg[0].addr == state->af9033_i2c_addr[1])) {
+			/* demod access via firmware interface */
+			u32 reg = msg[0].buf[0] << 16 | msg[0].buf[1] << 8 |
+					msg[0].buf[2];
+
+			if (msg[0].addr == state->af9033_i2c_addr[1])
+				reg |= 0x100000;
+
+			ret = (msg[0].len >= 3) ? af9035_wr_regs(d, reg,
+							         &msg[0].buf[3],
+							         msg[0].len - 3)
+					        : -EOPNOTSUPP;
+		} else {
+			/* I2C write */
+			u8 buf[MAX_XFER_SIZE];
+			struct usb_req req = { CMD_I2C_WR, 0, 5 + msg[0].len,
+					buf, 0, NULL };
+
+			if (state->chip_type == 0x9306) {
+				req.cmd = CMD_GENERIC_I2C_WR;
+				req.wlen = 3 + msg[0].len;
+			}
+
+			req.mbox |= ((msg[0].addr & 0x80)  >>  3);
+			buf[0] = msg[0].len;
+			if (state->chip_type == 0x9306) {
+				buf[1] = 0x03; /* I2C bus */
+				buf[2] = msg[0].addr << 1;
+				memcpy(&buf[3], msg[0].buf, msg[0].len);
+			} else {
+				buf[1] = msg[0].addr << 1;
+				buf[2] = 0x00; /* reg addr len */
+				buf[3] = 0x00; /* reg addr MSB */
+				buf[4] = 0x00; /* reg addr LSB */
+				memcpy(&buf[5], msg[0].buf, msg[0].len);
+			}
+			ret = af9035_ctrl_msg(d, &req);
+		}
+	} else if (AF9035_IS_I2C_XFER_READ(msg, num)) {
+		if (msg[0].len > 40) {
+			/* TODO: correct limits > 40 */
+			ret = -EOPNOTSUPP;
+		} else if (state->no_read) {
+			memset(msg[0].buf, 0, msg[0].len);
+			ret = 0;
+		} else {
+			/* I2C read */
+			u8 buf[5];
+			struct usb_req req = { CMD_I2C_RD, 0, sizeof(buf),
+						buf, msg[0].len, msg[0].buf };
+
+			if (state->chip_type == 0x9306) {
+				req.cmd = CMD_GENERIC_I2C_RD;
+				req.wlen = 3;
+			}
+			req.mbox |= ((msg[0].addr & 0x80)  >>  3);
+			buf[0] = msg[0].len;
+			if (state->chip_type == 0x9306) {
+				buf[1] = 0x03; /* I2C bus */
+				buf[2] = msg[0].addr << 1;
+			} else {
+				buf[1] = msg[0].addr << 1;
+				buf[2] = 0x00; /* reg addr len */
+				buf[3] = 0x00; /* reg addr MSB */
+				buf[4] = 0x00; /* reg addr LSB */
+			}
+			ret = af9035_ctrl_msg(d, &req);
+		}
+	} else {
+		/*
+		 * We support only three kind of I2C transactions:
+		 * 1) 1 x write + 1 x read (repeated start)
+		 * 2) 1 x write
+		 * 3) 1 x read
+		 */
+		ret = -EOPNOTSUPP;
+	}
+
+	mutex_unlock(&d->i2c_mutex);
+
+	if (ret < 0)
+		return ret;
+	else
+		return num;
+}
+
+static u32 af9035_i2c_functionality(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm af9035_i2c_algo = {
+	.master_xfer = af9035_i2c_master_xfer,
+	.functionality = af9035_i2c_functionality,
+};
+
+static int af9035_identify_state(struct dvb_usb_device *d, const char **name)
+{
+	struct state *state = d_to_priv(d);
+	struct usb_interface *intf = d->intf;
+	int ret, i, ts_mode_invalid;
+	unsigned int utmp, eeprom_addr;
+	u8 tmp;
+	u8 wbuf[1] = { 1 };
+	u8 rbuf[4];
+	struct usb_req req = { CMD_FW_QUERYINFO, 0, sizeof(wbuf), wbuf,
+			sizeof(rbuf), rbuf };
+
+	ret = af9035_rd_regs(d, 0x1222, rbuf, 3);
+	if (ret < 0)
+		goto err;
+
+	state->chip_version = rbuf[0];
+	state->chip_type = rbuf[2] << 8 | rbuf[1] << 0;
+
+	ret = af9035_rd_reg(d, 0x384f, &state->prechip_version);
+	if (ret < 0)
+		goto err;
+
+	dev_info(&intf->dev, "prechip_version=%02x chip_version=%02x chip_type=%04x\n",
+		 state->prechip_version, state->chip_version, state->chip_type);
+
+	if (state->chip_type == 0x9135) {
+		if (state->chip_version == 0x02) {
+			*name = AF9035_FIRMWARE_IT9135_V2;
+			utmp = 0x00461d;
+		} else {
+			*name = AF9035_FIRMWARE_IT9135_V1;
+			utmp = 0x00461b;
+		}
+
+		/* Check if eeprom exists */
+		ret = af9035_rd_reg(d, utmp, &tmp);
+		if (ret < 0)
+			goto err;
+
+		if (tmp == 0x00) {
+			dev_dbg(&intf->dev, "no eeprom\n");
+			state->no_eeprom = true;
+			goto check_firmware_status;
+		}
+
+		eeprom_addr = EEPROM_BASE_IT9135;
+	} else if (state->chip_type == 0x9306) {
+		*name = AF9035_FIRMWARE_IT9303;
+		state->no_eeprom = true;
+		goto check_firmware_status;
+	} else {
+		*name = AF9035_FIRMWARE_AF9035;
+		eeprom_addr = EEPROM_BASE_AF9035;
+	}
+
+	/* Read and store eeprom */
+	for (i = 0; i < 256; i += 32) {
+		ret = af9035_rd_regs(d, eeprom_addr + i, &state->eeprom[i], 32);
+		if (ret < 0)
+			goto err;
+	}
+
+	dev_dbg(&intf->dev, "eeprom dump:\n");
+	for (i = 0; i < 256; i += 16)
+		dev_dbg(&intf->dev, "%*ph\n", 16, &state->eeprom[i]);
+
+	/* check for dual tuner mode */
+	tmp = state->eeprom[EEPROM_TS_MODE];
+	ts_mode_invalid = 0;
+	switch (tmp) {
+	case 0:
+		break;
+	case 1:
+	case 3:
+		state->dual_mode = true;
+		break;
+	case 5:
+		if (state->chip_type != 0x9135 && state->chip_type != 0x9306)
+			state->dual_mode = true;	/* AF9035 */
+		else
+			ts_mode_invalid = 1;
+		break;
+	default:
+		ts_mode_invalid = 1;
+	}
+
+	dev_dbg(&intf->dev, "ts mode=%d dual mode=%d\n", tmp, state->dual_mode);
+
+	if (ts_mode_invalid)
+		dev_info(&intf->dev, "ts mode=%d not supported, defaulting to single tuner mode!", tmp);
+
+check_firmware_status:
+	ret = af9035_ctrl_msg(d, &req);
+	if (ret < 0)
+		goto err;
+
+	dev_dbg(&intf->dev, "reply=%*ph\n", 4, rbuf);
+	if (rbuf[0] || rbuf[1] || rbuf[2] || rbuf[3])
+		ret = WARM;
+	else
+		ret = COLD;
+
+	return ret;
+
+err:
+	dev_dbg(&intf->dev, "failed=%d\n", ret);
+
+	return ret;
+}
+
+static int af9035_download_firmware_old(struct dvb_usb_device *d,
+		const struct firmware *fw)
+{
+	struct usb_interface *intf = d->intf;
+	int ret, i, j, len;
+	u8 wbuf[1];
+	struct usb_req req = { 0, 0, 0, NULL, 0, NULL };
+	struct usb_req req_fw_dl = { CMD_FW_DL, 0, 0, wbuf, 0, NULL };
+	u8 hdr_core;
+	u16 hdr_addr, hdr_data_len, hdr_checksum;
+	#define MAX_DATA 58
+	#define HDR_SIZE 7
+
+	/*
+	 * Thanks to Daniel Glöckner <daniel-gl@gmx.net> about that info!
+	 *
+	 * byte 0: MCS 51 core
+	 *  There are two inside the AF9035 (1=Link and 2=OFDM) with separate
+	 *  address spaces
+	 * byte 1-2: Big endian destination address
+	 * byte 3-4: Big endian number of data bytes following the header
+	 * byte 5-6: Big endian header checksum, apparently ignored by the chip
+	 *  Calculated as ~(h[0]*256+h[1]+h[2]*256+h[3]+h[4]*256)
+	 */
+
+	for (i = fw->size; i > HDR_SIZE;) {
+		hdr_core = fw->data[fw->size - i + 0];
+		hdr_addr = fw->data[fw->size - i + 1] << 8;
+		hdr_addr |= fw->data[fw->size - i + 2] << 0;
+		hdr_data_len = fw->data[fw->size - i + 3] << 8;
+		hdr_data_len |= fw->data[fw->size - i + 4] << 0;
+		hdr_checksum = fw->data[fw->size - i + 5] << 8;
+		hdr_checksum |= fw->data[fw->size - i + 6] << 0;
+
+		dev_dbg(&intf->dev, "core=%d addr=%04x data_len=%d checksum=%04x\n",
+			hdr_core, hdr_addr, hdr_data_len, hdr_checksum);
+
+		if (((hdr_core != 1) && (hdr_core != 2)) ||
+				(hdr_data_len > i)) {
+			dev_dbg(&intf->dev, "bad firmware\n");
+			break;
+		}
+
+		/* download begin packet */
+		req.cmd = CMD_FW_DL_BEGIN;
+		ret = af9035_ctrl_msg(d, &req);
+		if (ret < 0)
+			goto err;
+
+		/* download firmware packet(s) */
+		for (j = HDR_SIZE + hdr_data_len; j > 0; j -= MAX_DATA) {
+			len = j;
+			if (len > MAX_DATA)
+				len = MAX_DATA;
+			req_fw_dl.wlen = len;
+			req_fw_dl.wbuf = (u8 *) &fw->data[fw->size - i +
+					HDR_SIZE + hdr_data_len - j];
+			ret = af9035_ctrl_msg(d, &req_fw_dl);
+			if (ret < 0)
+				goto err;
+		}
+
+		/* download end packet */
+		req.cmd = CMD_FW_DL_END;
+		ret = af9035_ctrl_msg(d, &req);
+		if (ret < 0)
+			goto err;
+
+		i -= hdr_data_len + HDR_SIZE;
+
+		dev_dbg(&intf->dev, "data uploaded=%zu\n", fw->size - i);
+	}
+
+	/* print warn if firmware is bad, continue and see what happens */
+	if (i)
+		dev_warn(&intf->dev, "bad firmware\n");
+
+	return 0;
+
+err:
+	dev_dbg(&intf->dev, "failed=%d\n", ret);
+
+	return ret;
+}
+
+static int af9035_download_firmware_new(struct dvb_usb_device *d,
+		const struct firmware *fw)
+{
+	struct usb_interface *intf = d->intf;
+	int ret, i, i_prev;
+	struct usb_req req_fw_dl = { CMD_FW_SCATTER_WR, 0, 0, NULL, 0, NULL };
+	#define HDR_SIZE 7
+
+	/*
+	 * There seems to be following firmware header. Meaning of bytes 0-3
+	 * is unknown.
+	 *
+	 * 0: 3
+	 * 1: 0, 1
+	 * 2: 0
+	 * 3: 1, 2, 3
+	 * 4: addr MSB
+	 * 5: addr LSB
+	 * 6: count of data bytes ?
+	 */
+	for (i = HDR_SIZE, i_prev = 0; i <= fw->size; i++) {
+		if (i == fw->size ||
+				(fw->data[i + 0] == 0x03 &&
+				(fw->data[i + 1] == 0x00 ||
+				fw->data[i + 1] == 0x01) &&
+				fw->data[i + 2] == 0x00)) {
+			req_fw_dl.wlen = i - i_prev;
+			req_fw_dl.wbuf = (u8 *) &fw->data[i_prev];
+			i_prev = i;
+			ret = af9035_ctrl_msg(d, &req_fw_dl);
+			if (ret < 0)
+				goto err;
+
+			dev_dbg(&intf->dev, "data uploaded=%d\n", i);
+		}
+	}
+
+	return 0;
+
+err:
+	dev_dbg(&intf->dev, "failed=%d\n", ret);
+
+	return ret;
+}
+
+static int af9035_download_firmware(struct dvb_usb_device *d,
+		const struct firmware *fw)
+{
+	struct usb_interface *intf = d->intf;
+	struct state *state = d_to_priv(d);
+	int ret;
+	u8 wbuf[1];
+	u8 rbuf[4];
+	u8 tmp;
+	struct usb_req req = { 0, 0, 0, NULL, 0, NULL };
+	struct usb_req req_fw_ver = { CMD_FW_QUERYINFO, 0, 1, wbuf, 4, rbuf };
+
+	dev_dbg(&intf->dev, "\n");
+
+	/*
+	 * In case of dual tuner configuration we need to do some extra
+	 * initialization in order to download firmware to slave demod too,
+	 * which is done by master demod.
+	 * Master feeds also clock and controls power via GPIO.
+	 */
+	if (state->dual_mode) {
+		/* configure gpioh1, reset & power slave demod */
+		ret = af9035_wr_reg_mask(d, 0x00d8b0, 0x01, 0x01);
+		if (ret < 0)
+			goto err;
+
+		ret = af9035_wr_reg_mask(d, 0x00d8b1, 0x01, 0x01);
+		if (ret < 0)
+			goto err;
+
+		ret = af9035_wr_reg_mask(d, 0x00d8af, 0x00, 0x01);
+		if (ret < 0)
+			goto err;
+
+		usleep_range(10000, 50000);
+
+		ret = af9035_wr_reg_mask(d, 0x00d8af, 0x01, 0x01);
+		if (ret < 0)
+			goto err;
+
+		/* tell the slave I2C address */
+		tmp = state->eeprom[EEPROM_2ND_DEMOD_ADDR];
+
+		/* Use default I2C address if eeprom has no address set */
+		if (!tmp)
+			tmp = 0x1d << 1; /* 8-bit format used by chip */
+
+		if ((state->chip_type == 0x9135) ||
+				(state->chip_type == 0x9306)) {
+			ret = af9035_wr_reg(d, 0x004bfb, tmp);
+			if (ret < 0)
+				goto err;
+		} else {
+			ret = af9035_wr_reg(d, 0x00417f, tmp);
+			if (ret < 0)
+				goto err;
+
+			/* enable clock out */
+			ret = af9035_wr_reg_mask(d, 0x00d81a, 0x01, 0x01);
+			if (ret < 0)
+				goto err;
+		}
+	}
+
+	if (fw->data[0] == 0x01)
+		ret = af9035_download_firmware_old(d, fw);
+	else
+		ret = af9035_download_firmware_new(d, fw);
+	if (ret < 0)
+		goto err;
+
+	/* firmware loaded, request boot */
+	req.cmd = CMD_FW_BOOT;
+	ret = af9035_ctrl_msg(d, &req);
+	if (ret < 0)
+		goto err;
+
+	/* ensure firmware starts */
+	wbuf[0] = 1;
+	ret = af9035_ctrl_msg(d, &req_fw_ver);
+	if (ret < 0)
+		goto err;
+
+	if (!(rbuf[0] || rbuf[1] || rbuf[2] || rbuf[3])) {
+		dev_err(&intf->dev, "firmware did not run\n");
+		ret = -ENODEV;
+		goto err;
+	}
+
+	dev_info(&intf->dev, "firmware version=%d.%d.%d.%d",
+		 rbuf[0], rbuf[1], rbuf[2], rbuf[3]);
+
+	return 0;
+
+err:
+	dev_dbg(&intf->dev, "failed=%d\n", ret);
+
+	return ret;
+}
+
+static int af9035_read_config(struct dvb_usb_device *d)
+{
+	struct usb_interface *intf = d->intf;
+	struct state *state = d_to_priv(d);
+	int ret, i;
+	u8 tmp;
+	u16 tmp16;
+
+	/* Demod I2C address */
+	state->af9033_i2c_addr[0] = 0x1c;
+	state->af9033_i2c_addr[1] = 0x1d;
+	state->af9033_config[0].adc_multiplier = AF9033_ADC_MULTIPLIER_2X;
+	state->af9033_config[1].adc_multiplier = AF9033_ADC_MULTIPLIER_2X;
+	state->af9033_config[0].ts_mode = AF9033_TS_MODE_USB;
+	state->af9033_config[1].ts_mode = AF9033_TS_MODE_SERIAL;
+
+	if (state->chip_type == 0x9135) {
+		/* feed clock for integrated RF tuner */
+		state->af9033_config[0].dyn0_clk = true;
+		state->af9033_config[1].dyn0_clk = true;
+
+		if (state->chip_version == 0x02) {
+			state->af9033_config[0].tuner = AF9033_TUNER_IT9135_60;
+			state->af9033_config[1].tuner = AF9033_TUNER_IT9135_60;
+		} else {
+			state->af9033_config[0].tuner = AF9033_TUNER_IT9135_38;
+			state->af9033_config[1].tuner = AF9033_TUNER_IT9135_38;
+		}
+
+		if (state->no_eeprom) {
+			/* Remote controller to NEC polling by default */
+			state->ir_mode = 0x05;
+			state->ir_type = 0x00;
+
+			goto skip_eeprom;
+		}
+	} else if (state->chip_type == 0x9306) {
+		/*
+		 * IT930x is an USB bridge, only single demod-single tuner
+		 * configurations seen so far.
+		 */
+		return 0;
+	}
+
+	/* Remote controller */
+	state->ir_mode = state->eeprom[EEPROM_IR_MODE];
+	state->ir_type = state->eeprom[EEPROM_IR_TYPE];
+
+	if (state->dual_mode) {
+		/* Read 2nd demodulator I2C address. 8-bit format on eeprom */
+		tmp = state->eeprom[EEPROM_2ND_DEMOD_ADDR];
+		if (tmp)
+			state->af9033_i2c_addr[1] = tmp >> 1;
+
+		dev_dbg(&intf->dev, "2nd demod I2C addr=%02x\n",
+			state->af9033_i2c_addr[1]);
+	}
+
+	for (i = 0; i < state->dual_mode + 1; i++) {
+		unsigned int eeprom_offset = 0;
+
+		/* tuner */
+		tmp = state->eeprom[EEPROM_1_TUNER_ID + eeprom_offset];
+		dev_dbg(&intf->dev, "[%d]tuner=%02x\n", i, tmp);
+
+		/* tuner sanity check */
+		if (state->chip_type == 0x9135) {
+			if (state->chip_version == 0x02) {
+				/* IT9135 BX (v2) */
+				switch (tmp) {
+				case AF9033_TUNER_IT9135_60:
+				case AF9033_TUNER_IT9135_61:
+				case AF9033_TUNER_IT9135_62:
+					state->af9033_config[i].tuner = tmp;
+					break;
+				}
+			} else {
+				/* IT9135 AX (v1) */
+				switch (tmp) {
+				case AF9033_TUNER_IT9135_38:
+				case AF9033_TUNER_IT9135_51:
+				case AF9033_TUNER_IT9135_52:
+					state->af9033_config[i].tuner = tmp;
+					break;
+				}
+			}
+		} else {
+			/* AF9035 */
+			state->af9033_config[i].tuner = tmp;
+		}
+
+		if (state->af9033_config[i].tuner != tmp) {
+			dev_info(&intf->dev, "[%d] overriding tuner from %02x to %02x\n",
+				 i, tmp, state->af9033_config[i].tuner);
+		}
+
+		switch (state->af9033_config[i].tuner) {
+		case AF9033_TUNER_TUA9001:
+		case AF9033_TUNER_FC0011:
+		case AF9033_TUNER_MXL5007T:
+		case AF9033_TUNER_TDA18218:
+		case AF9033_TUNER_FC2580:
+		case AF9033_TUNER_FC0012:
+			state->af9033_config[i].spec_inv = 1;
+			break;
+		case AF9033_TUNER_IT9135_38:
+		case AF9033_TUNER_IT9135_51:
+		case AF9033_TUNER_IT9135_52:
+		case AF9033_TUNER_IT9135_60:
+		case AF9033_TUNER_IT9135_61:
+		case AF9033_TUNER_IT9135_62:
+			break;
+		default:
+			dev_warn(&intf->dev, "tuner id=%02x not supported, please report!",
+				 tmp);
+		}
+
+		/* disable dual mode if driver does not support it */
+		if (i == 1)
+			switch (state->af9033_config[i].tuner) {
+			case AF9033_TUNER_FC0012:
+			case AF9033_TUNER_IT9135_38:
+			case AF9033_TUNER_IT9135_51:
+			case AF9033_TUNER_IT9135_52:
+			case AF9033_TUNER_IT9135_60:
+			case AF9033_TUNER_IT9135_61:
+			case AF9033_TUNER_IT9135_62:
+			case AF9033_TUNER_MXL5007T:
+				break;
+			default:
+				state->dual_mode = false;
+				dev_info(&intf->dev, "driver does not support 2nd tuner and will disable it");
+		}
+
+		/* tuner IF frequency */
+		tmp = state->eeprom[EEPROM_1_IF_L + eeprom_offset];
+		tmp16 = tmp << 0;
+		tmp = state->eeprom[EEPROM_1_IF_H + eeprom_offset];
+		tmp16 |= tmp << 8;
+		dev_dbg(&intf->dev, "[%d]IF=%d\n", i, tmp16);
+
+		eeprom_offset += 0x10; /* shift for the 2nd tuner params */
+	}
+
+skip_eeprom:
+	/* get demod clock */
+	ret = af9035_rd_reg(d, 0x00d800, &tmp);
+	if (ret < 0)
+		goto err;
+
+	tmp = (tmp >> 0) & 0x0f;
+
+	for (i = 0; i < ARRAY_SIZE(state->af9033_config); i++) {
+		if (state->chip_type == 0x9135)
+			state->af9033_config[i].clock = clock_lut_it9135[tmp];
+		else
+			state->af9033_config[i].clock = clock_lut_af9035[tmp];
+	}
+
+	state->no_read = false;
+	/* Some MXL5007T devices cannot properly handle tuner I2C read ops. */
+	if (state->af9033_config[0].tuner == AF9033_TUNER_MXL5007T &&
+		le16_to_cpu(d->udev->descriptor.idVendor) == USB_VID_AVERMEDIA)
+
+		switch (le16_to_cpu(d->udev->descriptor.idProduct)) {
+		case USB_PID_AVERMEDIA_A867:
+		case USB_PID_AVERMEDIA_TWINSTAR:
+			dev_info(&intf->dev,
+				 "Device may have issues with I2C read operations. Enabling fix.\n");
+			state->no_read = true;
+			break;
+		}
+
+	return 0;
+
+err:
+	dev_dbg(&intf->dev, "failed=%d\n", ret);
+
+	return ret;
+}
+
+static int af9035_tua9001_tuner_callback(struct dvb_usb_device *d,
+		int cmd, int arg)
+{
+	struct usb_interface *intf = d->intf;
+	int ret;
+	u8 val;
+
+	dev_dbg(&intf->dev, "cmd=%d arg=%d\n", cmd, arg);
+
+	/*
+	 * CEN     always enabled by hardware wiring
+	 * RESETN  GPIOT3
+	 * RXEN    GPIOT2
+	 */
+
+	switch (cmd) {
+	case TUA9001_CMD_RESETN:
+		if (arg)
+			val = 0x00;
+		else
+			val = 0x01;
+
+		ret = af9035_wr_reg_mask(d, 0x00d8e7, val, 0x01);
+		if (ret < 0)
+			goto err;
+		break;
+	case TUA9001_CMD_RXEN:
+		if (arg)
+			val = 0x01;
+		else
+			val = 0x00;
+
+		ret = af9035_wr_reg_mask(d, 0x00d8eb, val, 0x01);
+		if (ret < 0)
+			goto err;
+		break;
+	}
+
+	return 0;
+
+err:
+	dev_dbg(&intf->dev, "failed=%d\n", ret);
+
+	return ret;
+}
+
+
+static int af9035_fc0011_tuner_callback(struct dvb_usb_device *d,
+		int cmd, int arg)
+{
+	struct usb_interface *intf = d->intf;
+	int ret;
+
+	switch (cmd) {
+	case FC0011_FE_CALLBACK_POWER:
+		/* Tuner enable */
+		ret = af9035_wr_reg_mask(d, 0xd8eb, 1, 1);
+		if (ret < 0)
+			goto err;
+
+		ret = af9035_wr_reg_mask(d, 0xd8ec, 1, 1);
+		if (ret < 0)
+			goto err;
+
+		ret = af9035_wr_reg_mask(d, 0xd8ed, 1, 1);
+		if (ret < 0)
+			goto err;
+
+		/* LED */
+		ret = af9035_wr_reg_mask(d, 0xd8d0, 1, 1);
+		if (ret < 0)
+			goto err;
+
+		ret = af9035_wr_reg_mask(d, 0xd8d1, 1, 1);
+		if (ret < 0)
+			goto err;
+
+		usleep_range(10000, 50000);
+		break;
+	case FC0011_FE_CALLBACK_RESET:
+		ret = af9035_wr_reg(d, 0xd8e9, 1);
+		if (ret < 0)
+			goto err;
+
+		ret = af9035_wr_reg(d, 0xd8e8, 1);
+		if (ret < 0)
+			goto err;
+
+		ret = af9035_wr_reg(d, 0xd8e7, 1);
+		if (ret < 0)
+			goto err;
+
+		usleep_range(10000, 20000);
+
+		ret = af9035_wr_reg(d, 0xd8e7, 0);
+		if (ret < 0)
+			goto err;
+
+		usleep_range(10000, 20000);
+		break;
+	default:
+		ret = -EINVAL;
+		goto err;
+	}
+
+	return 0;
+
+err:
+	dev_dbg(&intf->dev, "failed=%d\n", ret);
+
+	return ret;
+}
+
+static int af9035_tuner_callback(struct dvb_usb_device *d, int cmd, int arg)
+{
+	struct state *state = d_to_priv(d);
+
+	switch (state->af9033_config[0].tuner) {
+	case AF9033_TUNER_FC0011:
+		return af9035_fc0011_tuner_callback(d, cmd, arg);
+	case AF9033_TUNER_TUA9001:
+		return af9035_tua9001_tuner_callback(d, cmd, arg);
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int af9035_frontend_callback(void *adapter_priv, int component,
+				    int cmd, int arg)
+{
+	struct i2c_adapter *adap = adapter_priv;
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	struct usb_interface *intf = d->intf;
+
+	dev_dbg(&intf->dev, "component=%d cmd=%d arg=%d\n",
+		component, cmd, arg);
+
+	switch (component) {
+	case DVB_FRONTEND_COMPONENT_TUNER:
+		return af9035_tuner_callback(d, cmd, arg);
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int af9035_get_adapter_count(struct dvb_usb_device *d)
+{
+	struct state *state = d_to_priv(d);
+
+	return state->dual_mode + 1;
+}
+
+static int af9035_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct state *state = adap_to_priv(adap);
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct usb_interface *intf = d->intf;
+	int ret;
+
+	dev_dbg(&intf->dev, "adap->id=%d\n", adap->id);
+
+	if (!state->af9033_config[adap->id].tuner) {
+		/* unsupported tuner */
+		ret = -ENODEV;
+		goto err;
+	}
+
+	state->af9033_config[adap->id].fe = &adap->fe[0];
+	state->af9033_config[adap->id].ops = &state->ops;
+	ret = af9035_add_i2c_dev(d, "af9033", state->af9033_i2c_addr[adap->id],
+			&state->af9033_config[adap->id], &d->i2c_adap);
+	if (ret)
+		goto err;
+
+	if (adap->fe[0] == NULL) {
+		ret = -ENODEV;
+		goto err;
+	}
+
+	/* disable I2C-gate */
+	adap->fe[0]->ops.i2c_gate_ctrl = NULL;
+	adap->fe[0]->callback = af9035_frontend_callback;
+
+	return 0;
+
+err:
+	dev_dbg(&intf->dev, "failed=%d\n", ret);
+
+	return ret;
+}
+
+static int it930x_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct state *state = adap_to_priv(adap);
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct usb_interface *intf = d->intf;
+	int ret;
+	struct si2168_config si2168_config;
+	struct i2c_adapter *adapter;
+
+	dev_dbg(&intf->dev, "adap->id=%d\n", adap->id);
+
+	memset(&si2168_config, 0, sizeof(si2168_config));
+	si2168_config.i2c_adapter = &adapter;
+	si2168_config.fe = &adap->fe[0];
+	si2168_config.ts_mode = SI2168_TS_SERIAL;
+
+	state->af9033_config[adap->id].fe = &adap->fe[0];
+	state->af9033_config[adap->id].ops = &state->ops;
+	ret = af9035_add_i2c_dev(d, "si2168", 0x67, &si2168_config,
+				&d->i2c_adap);
+	if (ret)
+		goto err;
+
+	if (adap->fe[0] == NULL) {
+		ret = -ENODEV;
+		goto err;
+	}
+	state->i2c_adapter_demod = adapter;
+
+	return 0;
+
+err:
+	dev_dbg(&intf->dev, "failed=%d\n", ret);
+
+	return ret;
+}
+
+static int af9035_frontend_detach(struct dvb_usb_adapter *adap)
+{
+	struct state *state = adap_to_priv(adap);
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct usb_interface *intf = d->intf;
+
+	dev_dbg(&intf->dev, "adap->id=%d\n", adap->id);
+
+	if (adap->id == 1) {
+		if (state->i2c_client[1])
+			af9035_del_i2c_dev(d);
+	} else if (adap->id == 0) {
+		if (state->i2c_client[0])
+			af9035_del_i2c_dev(d);
+	}
+
+	return 0;
+}
+
+static const struct fc0011_config af9035_fc0011_config = {
+	.i2c_address = 0x60,
+};
+
+static struct mxl5007t_config af9035_mxl5007t_config[] = {
+	{
+		.xtal_freq_hz = MxL_XTAL_24_MHZ,
+		.if_freq_hz = MxL_IF_4_57_MHZ,
+		.invert_if = 0,
+		.loop_thru_enable = 0,
+		.clk_out_enable = 0,
+		.clk_out_amp = MxL_CLKOUT_AMP_0_94V,
+	}, {
+		.xtal_freq_hz = MxL_XTAL_24_MHZ,
+		.if_freq_hz = MxL_IF_4_57_MHZ,
+		.invert_if = 0,
+		.loop_thru_enable = 1,
+		.clk_out_enable = 1,
+		.clk_out_amp = MxL_CLKOUT_AMP_0_94V,
+	}
+};
+
+static struct tda18218_config af9035_tda18218_config = {
+	.i2c_address = 0x60,
+	.i2c_wr_max = 21,
+};
+
+static const struct fc0012_config af9035_fc0012_config[] = {
+	{
+		.i2c_address = 0x63,
+		.xtal_freq = FC_XTAL_36_MHZ,
+		.dual_master = true,
+		.loop_through = true,
+		.clock_out = true,
+	}, {
+		.i2c_address = 0x63 | 0x80, /* I2C bus select hack */
+		.xtal_freq = FC_XTAL_36_MHZ,
+		.dual_master = true,
+	}
+};
+
+static int af9035_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct state *state = adap_to_priv(adap);
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct usb_interface *intf = d->intf;
+	int ret;
+	struct dvb_frontend *fe;
+	struct i2c_msg msg[1];
+	u8 tuner_addr;
+
+	dev_dbg(&intf->dev, "adap->id=%d\n", adap->id);
+
+	/*
+	 * XXX: Hack used in that function: we abuse unused I2C address bit [7]
+	 * to carry info about used I2C bus for dual tuner configuration.
+	 */
+
+	switch (state->af9033_config[adap->id].tuner) {
+	case AF9033_TUNER_TUA9001: {
+		struct tua9001_platform_data tua9001_pdata = {
+			.dvb_frontend = adap->fe[0],
+		};
+
+		/*
+		 * AF9035 gpiot3 = TUA9001 RESETN
+		 * AF9035 gpiot2 = TUA9001 RXEN
+		 */
+
+		/* configure gpiot2 and gpiot2 as output */
+		ret = af9035_wr_reg_mask(d, 0x00d8ec, 0x01, 0x01);
+		if (ret < 0)
+			goto err;
+
+		ret = af9035_wr_reg_mask(d, 0x00d8ed, 0x01, 0x01);
+		if (ret < 0)
+			goto err;
+
+		ret = af9035_wr_reg_mask(d, 0x00d8e8, 0x01, 0x01);
+		if (ret < 0)
+			goto err;
+
+		ret = af9035_wr_reg_mask(d, 0x00d8e9, 0x01, 0x01);
+		if (ret < 0)
+			goto err;
+
+		/* attach tuner */
+		ret = af9035_add_i2c_dev(d, "tua9001", 0x60, &tua9001_pdata,
+					 &d->i2c_adap);
+		if (ret)
+			goto err;
+
+		fe = adap->fe[0];
+		break;
+	}
+	case AF9033_TUNER_FC0011:
+		fe = dvb_attach(fc0011_attach, adap->fe[0],
+				&d->i2c_adap, &af9035_fc0011_config);
+		break;
+	case AF9033_TUNER_MXL5007T:
+		if (adap->id == 0) {
+			ret = af9035_wr_reg(d, 0x00d8e0, 1);
+			if (ret < 0)
+				goto err;
+
+			ret = af9035_wr_reg(d, 0x00d8e1, 1);
+			if (ret < 0)
+				goto err;
+
+			ret = af9035_wr_reg(d, 0x00d8df, 0);
+			if (ret < 0)
+				goto err;
+
+			msleep(30);
+
+			ret = af9035_wr_reg(d, 0x00d8df, 1);
+			if (ret < 0)
+				goto err;
+
+			msleep(300);
+
+			ret = af9035_wr_reg(d, 0x00d8c0, 1);
+			if (ret < 0)
+				goto err;
+
+			ret = af9035_wr_reg(d, 0x00d8c1, 1);
+			if (ret < 0)
+				goto err;
+
+			ret = af9035_wr_reg(d, 0x00d8bf, 0);
+			if (ret < 0)
+				goto err;
+
+			ret = af9035_wr_reg(d, 0x00d8b4, 1);
+			if (ret < 0)
+				goto err;
+
+			ret = af9035_wr_reg(d, 0x00d8b5, 1);
+			if (ret < 0)
+				goto err;
+
+			ret = af9035_wr_reg(d, 0x00d8b3, 1);
+			if (ret < 0)
+				goto err;
+
+			tuner_addr = 0x60;
+		} else {
+			tuner_addr = 0x60 | 0x80; /* I2C bus hack */
+		}
+
+		/* attach tuner */
+		fe = dvb_attach(mxl5007t_attach, adap->fe[0], &d->i2c_adap,
+				tuner_addr, &af9035_mxl5007t_config[adap->id]);
+		break;
+	case AF9033_TUNER_TDA18218:
+		/* attach tuner */
+		fe = dvb_attach(tda18218_attach, adap->fe[0],
+				&d->i2c_adap, &af9035_tda18218_config);
+		break;
+	case AF9033_TUNER_FC2580: {
+		struct fc2580_platform_data fc2580_pdata = {
+			.dvb_frontend = adap->fe[0],
+		};
+
+		/* Tuner enable using gpiot2_o, gpiot2_en and gpiot2_on  */
+		ret = af9035_wr_reg_mask(d, 0xd8eb, 0x01, 0x01);
+		if (ret < 0)
+			goto err;
+
+		ret = af9035_wr_reg_mask(d, 0xd8ec, 0x01, 0x01);
+		if (ret < 0)
+			goto err;
+
+		ret = af9035_wr_reg_mask(d, 0xd8ed, 0x01, 0x01);
+		if (ret < 0)
+			goto err;
+
+		usleep_range(10000, 50000);
+		/* attach tuner */
+		ret = af9035_add_i2c_dev(d, "fc2580", 0x56, &fc2580_pdata,
+					 &d->i2c_adap);
+		if (ret)
+			goto err;
+
+		fe = adap->fe[0];
+		break;
+	}
+	case AF9033_TUNER_FC0012:
+		/*
+		 * AF9035 gpiot2 = FC0012 enable
+		 * XXX: there seems to be something on gpioh8 too, but on my
+		 * my test I didn't find any difference.
+		 */
+
+		if (adap->id == 0) {
+			/* configure gpiot2 as output and high */
+			ret = af9035_wr_reg_mask(d, 0xd8eb, 0x01, 0x01);
+			if (ret < 0)
+				goto err;
+
+			ret = af9035_wr_reg_mask(d, 0xd8ec, 0x01, 0x01);
+			if (ret < 0)
+				goto err;
+
+			ret = af9035_wr_reg_mask(d, 0xd8ed, 0x01, 0x01);
+			if (ret < 0)
+				goto err;
+		} else {
+			/*
+			 * FIXME: That belongs for the FC0012 driver.
+			 * Write 02 to FC0012 master tuner register 0d directly
+			 * in order to make slave tuner working.
+			 */
+			msg[0].addr = 0x63;
+			msg[0].flags = 0;
+			msg[0].len = 2;
+			msg[0].buf = "\x0d\x02";
+			ret = i2c_transfer(&d->i2c_adap, msg, 1);
+			if (ret < 0)
+				goto err;
+		}
+
+		usleep_range(10000, 50000);
+
+		fe = dvb_attach(fc0012_attach, adap->fe[0], &d->i2c_adap,
+				&af9035_fc0012_config[adap->id]);
+		break;
+	case AF9033_TUNER_IT9135_38:
+	case AF9033_TUNER_IT9135_51:
+	case AF9033_TUNER_IT9135_52:
+	case AF9033_TUNER_IT9135_60:
+	case AF9033_TUNER_IT9135_61:
+	case AF9033_TUNER_IT9135_62:
+	{
+		struct platform_device *pdev;
+		const char *name;
+		struct it913x_platform_data it913x_pdata = {
+			.regmap = state->af9033_config[adap->id].regmap,
+			.fe = adap->fe[0],
+		};
+
+		switch (state->af9033_config[adap->id].tuner) {
+		case AF9033_TUNER_IT9135_38:
+		case AF9033_TUNER_IT9135_51:
+		case AF9033_TUNER_IT9135_52:
+			name = "it9133ax-tuner";
+			break;
+		case AF9033_TUNER_IT9135_60:
+		case AF9033_TUNER_IT9135_61:
+		case AF9033_TUNER_IT9135_62:
+			name = "it9133bx-tuner";
+			break;
+		default:
+			ret = -ENODEV;
+			goto err;
+		}
+
+		if (state->dual_mode) {
+			if (adap->id == 0)
+				it913x_pdata.role = IT913X_ROLE_DUAL_MASTER;
+			else
+				it913x_pdata.role = IT913X_ROLE_DUAL_SLAVE;
+		} else {
+			it913x_pdata.role = IT913X_ROLE_SINGLE;
+		}
+
+		request_module("%s", "it913x");
+		pdev = platform_device_register_data(&d->intf->dev, name,
+						     PLATFORM_DEVID_AUTO,
+						     &it913x_pdata,
+						     sizeof(it913x_pdata));
+		if (IS_ERR(pdev) || !pdev->dev.driver) {
+			ret = -ENODEV;
+			goto err;
+		}
+		if (!try_module_get(pdev->dev.driver->owner)) {
+			platform_device_unregister(pdev);
+			ret = -ENODEV;
+			goto err;
+		}
+
+		state->platform_device_tuner[adap->id] = pdev;
+		fe = adap->fe[0];
+		break;
+	}
+	default:
+		fe = NULL;
+	}
+
+	if (fe == NULL) {
+		ret = -ENODEV;
+		goto err;
+	}
+
+	return 0;
+
+err:
+	dev_dbg(&intf->dev, "failed=%d\n", ret);
+
+	return ret;
+}
+
+static int it930x_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct state *state = adap_to_priv(adap);
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct usb_interface *intf = d->intf;
+	int ret;
+	struct si2157_config si2157_config;
+
+	dev_dbg(&intf->dev, "adap->id=%d\n", adap->id);
+
+	/* I2C master bus 2 clock speed 300k */
+	ret = af9035_wr_reg(d, 0x00f6a7, 0x07);
+	if (ret < 0)
+		goto err;
+
+	/* I2C master bus 1,3 clock speed 300k */
+	ret = af9035_wr_reg(d, 0x00f103, 0x07);
+	if (ret < 0)
+		goto err;
+
+	/* set gpio11 low */
+	ret = af9035_wr_reg_mask(d, 0xd8d4, 0x01, 0x01);
+	if (ret < 0)
+		goto err;
+
+	ret = af9035_wr_reg_mask(d, 0xd8d5, 0x01, 0x01);
+	if (ret < 0)
+		goto err;
+
+	ret = af9035_wr_reg_mask(d, 0xd8d3, 0x01, 0x01);
+	if (ret < 0)
+		goto err;
+
+	/* Tuner enable using gpiot2_en, gpiot2_on and gpiot2_o (reset) */
+	ret = af9035_wr_reg_mask(d, 0xd8b8, 0x01, 0x01);
+	if (ret < 0)
+		goto err;
+
+	ret = af9035_wr_reg_mask(d, 0xd8b9, 0x01, 0x01);
+	if (ret < 0)
+		goto err;
+
+	ret = af9035_wr_reg_mask(d, 0xd8b7, 0x00, 0x01);
+	if (ret < 0)
+		goto err;
+
+	msleep(200);
+
+	ret = af9035_wr_reg_mask(d, 0xd8b7, 0x01, 0x01);
+	if (ret < 0)
+		goto err;
+
+	memset(&si2157_config, 0, sizeof(si2157_config));
+	si2157_config.fe = adap->fe[0];
+	si2157_config.if_port = 1;
+	ret = af9035_add_i2c_dev(d, "si2157", 0x63,
+			&si2157_config, state->i2c_adapter_demod);
+
+	if (ret)
+		goto err;
+
+	return 0;
+
+err:
+	dev_dbg(&intf->dev, "failed=%d\n", ret);
+
+	return ret;
+}
+
+
+static int it930x_tuner_detach(struct dvb_usb_adapter *adap)
+{
+	struct state *state = adap_to_priv(adap);
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct usb_interface *intf = d->intf;
+
+	dev_dbg(&intf->dev, "adap->id=%d\n", adap->id);
+
+	if (adap->id == 1) {
+		if (state->i2c_client[3])
+			af9035_del_i2c_dev(d);
+	} else if (adap->id == 0) {
+		if (state->i2c_client[1])
+			af9035_del_i2c_dev(d);
+	}
+
+	return 0;
+}
+
+
+static int af9035_tuner_detach(struct dvb_usb_adapter *adap)
+{
+	struct state *state = adap_to_priv(adap);
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct usb_interface *intf = d->intf;
+
+	dev_dbg(&intf->dev, "adap->id=%d\n", adap->id);
+
+	switch (state->af9033_config[adap->id].tuner) {
+	case AF9033_TUNER_TUA9001:
+	case AF9033_TUNER_FC2580:
+		if (adap->id == 1) {
+			if (state->i2c_client[3])
+				af9035_del_i2c_dev(d);
+		} else if (adap->id == 0) {
+			if (state->i2c_client[1])
+				af9035_del_i2c_dev(d);
+		}
+		break;
+	case AF9033_TUNER_IT9135_38:
+	case AF9033_TUNER_IT9135_51:
+	case AF9033_TUNER_IT9135_52:
+	case AF9033_TUNER_IT9135_60:
+	case AF9033_TUNER_IT9135_61:
+	case AF9033_TUNER_IT9135_62:
+	{
+		struct platform_device *pdev;
+
+		pdev = state->platform_device_tuner[adap->id];
+		if (pdev) {
+			module_put(pdev->dev.driver->owner);
+			platform_device_unregister(pdev);
+		}
+		break;
+	}
+	}
+
+	return 0;
+}
+
+static int af9035_init(struct dvb_usb_device *d)
+{
+	struct state *state = d_to_priv(d);
+	struct usb_interface *intf = d->intf;
+	int ret, i;
+	u16 frame_size = (d->udev->speed == USB_SPEED_FULL ? 5 : 87) * 188 / 4;
+	u8 packet_size = (d->udev->speed == USB_SPEED_FULL ? 64 : 512) / 4;
+	struct reg_val_mask tab[] = {
+		{ 0x80f99d, 0x01, 0x01 },
+		{ 0x80f9a4, 0x01, 0x01 },
+		{ 0x00dd11, 0x00, 0x20 },
+		{ 0x00dd11, 0x00, 0x40 },
+		{ 0x00dd13, 0x00, 0x20 },
+		{ 0x00dd13, 0x00, 0x40 },
+		{ 0x00dd11, 0x20, 0x20 },
+		{ 0x00dd88, (frame_size >> 0) & 0xff, 0xff},
+		{ 0x00dd89, (frame_size >> 8) & 0xff, 0xff},
+		{ 0x00dd0c, packet_size, 0xff},
+		{ 0x00dd11, state->dual_mode << 6, 0x40 },
+		{ 0x00dd8a, (frame_size >> 0) & 0xff, 0xff},
+		{ 0x00dd8b, (frame_size >> 8) & 0xff, 0xff},
+		{ 0x00dd0d, packet_size, 0xff },
+		{ 0x80f9a3, state->dual_mode, 0x01 },
+		{ 0x80f9cd, state->dual_mode, 0x01 },
+		{ 0x80f99d, 0x00, 0x01 },
+		{ 0x80f9a4, 0x00, 0x01 },
+	};
+
+	dev_dbg(&intf->dev, "USB speed=%d frame_size=%04x packet_size=%02x\n",
+		d->udev->speed, frame_size, packet_size);
+
+	/* init endpoints */
+	for (i = 0; i < ARRAY_SIZE(tab); i++) {
+		ret = af9035_wr_reg_mask(d, tab[i].reg, tab[i].val,
+				tab[i].mask);
+		if (ret < 0)
+			goto err;
+	}
+
+	return 0;
+
+err:
+	dev_dbg(&intf->dev, "failed=%d\n", ret);
+
+	return ret;
+}
+
+static int it930x_init(struct dvb_usb_device *d)
+{
+	struct state *state = d_to_priv(d);
+	struct usb_interface *intf = d->intf;
+	int ret, i;
+	u16 frame_size = (d->udev->speed == USB_SPEED_FULL ? 5 : 816) * 188 / 4;
+	u8 packet_size = (d->udev->speed == USB_SPEED_FULL ? 64 : 512) / 4;
+	struct reg_val_mask tab[] = {
+		{ 0x00da1a, 0x00, 0x01 }, /* ignore_sync_byte */
+		{ 0x00f41f, 0x04, 0x04 }, /* dvbt_inten */
+		{ 0x00da10, 0x00, 0x01 }, /* mpeg_full_speed */
+		{ 0x00f41a, 0x01, 0x01 }, /* dvbt_en */
+		{ 0x00da1d, 0x01, 0x01 }, /* mp2_sw_rst, reset EP4 */
+		{ 0x00dd11, 0x00, 0x20 }, /* ep4_tx_en, disable EP4 */
+		{ 0x00dd13, 0x00, 0x20 }, /* ep4_tx_nak, disable EP4 NAK */
+		{ 0x00dd11, 0x20, 0x20 }, /* ep4_tx_en, enable EP4 */
+		{ 0x00dd11, 0x00, 0x40 }, /* ep5_tx_en, disable EP5 */
+		{ 0x00dd13, 0x00, 0x40 }, /* ep5_tx_nak, disable EP5 NAK */
+		{ 0x00dd11, state->dual_mode << 6, 0x40 }, /* enable EP5 */
+		{ 0x00dd88, (frame_size >> 0) & 0xff, 0xff},
+		{ 0x00dd89, (frame_size >> 8) & 0xff, 0xff},
+		{ 0x00dd0c, packet_size, 0xff},
+		{ 0x00dd8a, (frame_size >> 0) & 0xff, 0xff},
+		{ 0x00dd8b, (frame_size >> 8) & 0xff, 0xff},
+		{ 0x00dd0d, packet_size, 0xff },
+		{ 0x00da1d, 0x00, 0x01 }, /* mp2_sw_rst, disable */
+		{ 0x00d833, 0x01, 0xff }, /* slew rate ctrl: slew rate boosts */
+		{ 0x00d830, 0x00, 0xff }, /* Bit 0 of output driving control */
+		{ 0x00d831, 0x01, 0xff }, /* Bit 1 of output driving control */
+		{ 0x00d832, 0x00, 0xff }, /* Bit 2 of output driving control */
+
+		/* suspend gpio1 for TS-C */
+		{ 0x00d8b0, 0x01, 0xff }, /* gpio1 */
+		{ 0x00d8b1, 0x01, 0xff }, /* gpio1 */
+		{ 0x00d8af, 0x00, 0xff }, /* gpio1 */
+
+		/* suspend gpio7 for TS-D */
+		{ 0x00d8c4, 0x01, 0xff }, /* gpio7 */
+		{ 0x00d8c5, 0x01, 0xff }, /* gpio7 */
+		{ 0x00d8c3, 0x00, 0xff }, /* gpio7 */
+
+		/* suspend gpio13 for TS-B */
+		{ 0x00d8dc, 0x01, 0xff }, /* gpio13 */
+		{ 0x00d8dd, 0x01, 0xff }, /* gpio13 */
+		{ 0x00d8db, 0x00, 0xff }, /* gpio13 */
+
+		/* suspend gpio14 for TS-E */
+		{ 0x00d8e4, 0x01, 0xff }, /* gpio14 */
+		{ 0x00d8e5, 0x01, 0xff }, /* gpio14 */
+		{ 0x00d8e3, 0x00, 0xff }, /* gpio14 */
+
+		/* suspend gpio15 for TS-A */
+		{ 0x00d8e8, 0x01, 0xff }, /* gpio15 */
+		{ 0x00d8e9, 0x01, 0xff }, /* gpio15 */
+		{ 0x00d8e7, 0x00, 0xff }, /* gpio15 */
+
+		{ 0x00da58, 0x00, 0x01 }, /* ts_in_src, serial */
+		{ 0x00da73, 0x01, 0xff }, /* ts0_aggre_mode */
+		{ 0x00da78, 0x47, 0xff }, /* ts0_sync_byte */
+		{ 0x00da4c, 0x01, 0xff }, /* ts0_en */
+		{ 0x00da5a, 0x1f, 0xff }, /* ts_fail_ignore */
+	};
+
+	dev_dbg(&intf->dev, "USB speed=%d frame_size=%04x packet_size=%02x\n",
+		d->udev->speed, frame_size, packet_size);
+
+	/* init endpoints */
+	for (i = 0; i < ARRAY_SIZE(tab); i++) {
+		ret = af9035_wr_reg_mask(d, tab[i].reg,
+				tab[i].val, tab[i].mask);
+
+		if (ret < 0)
+			goto err;
+	}
+
+	return 0;
+err:
+	dev_dbg(&intf->dev, "failed=%d\n", ret);
+
+	return ret;
+}
+
+
+#if IS_ENABLED(CONFIG_RC_CORE)
+static int af9035_rc_query(struct dvb_usb_device *d)
+{
+	struct usb_interface *intf = d->intf;
+	int ret;
+	enum rc_proto proto;
+	u32 key;
+	u8 buf[4];
+	struct usb_req req = { CMD_IR_GET, 0, 0, NULL, 4, buf };
+
+	ret = af9035_ctrl_msg(d, &req);
+	if (ret == 1)
+		return 0;
+	else if (ret < 0)
+		goto err;
+
+	if ((buf[2] + buf[3]) == 0xff) {
+		if ((buf[0] + buf[1]) == 0xff) {
+			/* NEC standard 16bit */
+			key = RC_SCANCODE_NEC(buf[0], buf[2]);
+			proto = RC_PROTO_NEC;
+		} else {
+			/* NEC extended 24bit */
+			key = RC_SCANCODE_NECX(buf[0] << 8 | buf[1], buf[2]);
+			proto = RC_PROTO_NECX;
+		}
+	} else {
+		/* NEC full code 32bit */
+		key = RC_SCANCODE_NEC32(buf[0] << 24 | buf[1] << 16 |
+					buf[2] << 8  | buf[3]);
+		proto = RC_PROTO_NEC32;
+	}
+
+	dev_dbg(&intf->dev, "%*ph\n", 4, buf);
+
+	rc_keydown(d->rc_dev, proto, key, 0);
+
+	return 0;
+
+err:
+	dev_dbg(&intf->dev, "failed=%d\n", ret);
+
+	return ret;
+}
+
+static int af9035_get_rc_config(struct dvb_usb_device *d, struct dvb_usb_rc *rc)
+{
+	struct state *state = d_to_priv(d);
+	struct usb_interface *intf = d->intf;
+
+	dev_dbg(&intf->dev, "ir_mode=%02x ir_type=%02x\n",
+		state->ir_mode, state->ir_type);
+
+	/* don't activate rc if in HID mode or if not available */
+	if (state->ir_mode == 0x05) {
+		switch (state->ir_type) {
+		case 0: /* NEC */
+		default:
+			rc->allowed_protos = RC_PROTO_BIT_NEC |
+					RC_PROTO_BIT_NECX | RC_PROTO_BIT_NEC32;
+			break;
+		case 1: /* RC6 */
+			rc->allowed_protos = RC_PROTO_BIT_RC6_MCE;
+			break;
+		}
+
+		rc->query = af9035_rc_query;
+		rc->interval = 500;
+
+		/* load empty to enable rc */
+		if (!rc->map_name)
+			rc->map_name = RC_MAP_EMPTY;
+	}
+
+	return 0;
+}
+#else
+	#define af9035_get_rc_config NULL
+#endif
+
+static int af9035_get_stream_config(struct dvb_frontend *fe, u8 *ts_type,
+		struct usb_data_stream_properties *stream)
+{
+	struct dvb_usb_device *d = fe_to_d(fe);
+	struct usb_interface *intf = d->intf;
+
+	dev_dbg(&intf->dev, "adap=%d\n", fe_to_adap(fe)->id);
+
+	if (d->udev->speed == USB_SPEED_FULL)
+		stream->u.bulk.buffersize = 5 * 188;
+
+	return 0;
+}
+
+static int af9035_pid_filter_ctrl(struct dvb_usb_adapter *adap, int onoff)
+{
+	struct state *state = adap_to_priv(adap);
+
+	return state->ops.pid_filter_ctrl(adap->fe[0], onoff);
+}
+
+static int af9035_pid_filter(struct dvb_usb_adapter *adap, int index, u16 pid,
+		int onoff)
+{
+	struct state *state = adap_to_priv(adap);
+
+	return state->ops.pid_filter(adap->fe[0], index, pid, onoff);
+}
+
+static int af9035_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	struct usb_device *udev = interface_to_usbdev(intf);
+	char manufacturer[sizeof("Afatech")];
+
+	memset(manufacturer, 0, sizeof(manufacturer));
+	usb_string(udev, udev->descriptor.iManufacturer,
+			manufacturer, sizeof(manufacturer));
+	/*
+	 * There is two devices having same ID but different chipset. One uses
+	 * AF9015 and the other IT9135 chipset. Only difference seen on lsusb
+	 * is iManufacturer string.
+	 *
+	 * idVendor           0x0ccd TerraTec Electronic GmbH
+	 * idProduct          0x0099
+	 * bcdDevice            2.00
+	 * iManufacturer           1 Afatech
+	 * iProduct                2 DVB-T 2
+	 *
+	 * idVendor           0x0ccd TerraTec Electronic GmbH
+	 * idProduct          0x0099
+	 * bcdDevice            2.00
+	 * iManufacturer           1 ITE Technologies, Inc.
+	 * iProduct                2 DVB-T TV Stick
+	 */
+	if ((le16_to_cpu(udev->descriptor.idVendor) == USB_VID_TERRATEC) &&
+			(le16_to_cpu(udev->descriptor.idProduct) == 0x0099)) {
+		if (!strcmp("Afatech", manufacturer)) {
+			dev_dbg(&udev->dev, "rejecting device\n");
+			return -ENODEV;
+		}
+	}
+
+	return dvb_usbv2_probe(intf, id);
+}
+
+/* interface 0 is used by DVB-T receiver and
+   interface 1 is for remote controller (HID) */
+static const struct dvb_usb_device_properties af9035_props = {
+	.driver_name = KBUILD_MODNAME,
+	.owner = THIS_MODULE,
+	.adapter_nr = adapter_nr,
+	.size_of_priv = sizeof(struct state),
+
+	.generic_bulk_ctrl_endpoint = 0x02,
+	.generic_bulk_ctrl_endpoint_response = 0x81,
+
+	.identify_state = af9035_identify_state,
+	.download_firmware = af9035_download_firmware,
+
+	.i2c_algo = &af9035_i2c_algo,
+	.read_config = af9035_read_config,
+	.frontend_attach = af9035_frontend_attach,
+	.frontend_detach = af9035_frontend_detach,
+	.tuner_attach = af9035_tuner_attach,
+	.tuner_detach = af9035_tuner_detach,
+	.init = af9035_init,
+	.get_rc_config = af9035_get_rc_config,
+	.get_stream_config = af9035_get_stream_config,
+
+	.get_adapter_count = af9035_get_adapter_count,
+	.adapter = {
+		{
+			.caps = DVB_USB_ADAP_HAS_PID_FILTER |
+				DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+
+			.pid_filter_count = 32,
+			.pid_filter_ctrl = af9035_pid_filter_ctrl,
+			.pid_filter = af9035_pid_filter,
+
+			.stream = DVB_USB_STREAM_BULK(0x84, 6, 87 * 188),
+		}, {
+			.caps = DVB_USB_ADAP_HAS_PID_FILTER |
+				DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+
+			.pid_filter_count = 32,
+			.pid_filter_ctrl = af9035_pid_filter_ctrl,
+			.pid_filter = af9035_pid_filter,
+
+			.stream = DVB_USB_STREAM_BULK(0x85, 6, 87 * 188),
+		},
+	},
+};
+
+static const struct dvb_usb_device_properties it930x_props = {
+	.driver_name = KBUILD_MODNAME,
+	.owner = THIS_MODULE,
+	.adapter_nr = adapter_nr,
+	.size_of_priv = sizeof(struct state),
+
+	.generic_bulk_ctrl_endpoint = 0x02,
+	.generic_bulk_ctrl_endpoint_response = 0x81,
+
+	.identify_state = af9035_identify_state,
+	.download_firmware = af9035_download_firmware,
+
+	.i2c_algo = &af9035_i2c_algo,
+	.read_config = af9035_read_config,
+	.frontend_attach = it930x_frontend_attach,
+	.frontend_detach = af9035_frontend_detach,
+	.tuner_attach = it930x_tuner_attach,
+	.tuner_detach = it930x_tuner_detach,
+	.init = it930x_init,
+	.get_stream_config = af9035_get_stream_config,
+
+	.get_adapter_count = af9035_get_adapter_count,
+	.adapter = {
+		{
+			.stream = DVB_USB_STREAM_BULK(0x84, 4, 816 * 188),
+		}, {
+			.stream = DVB_USB_STREAM_BULK(0x85, 4, 816 * 188),
+		},
+	},
+};
+
+static const struct usb_device_id af9035_id_table[] = {
+	/* AF9035 devices */
+	{ DVB_USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9035_9035,
+		&af9035_props, "Afatech AF9035 reference design", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9035_1000,
+		&af9035_props, "Afatech AF9035 reference design", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9035_1001,
+		&af9035_props, "Afatech AF9035 reference design", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9035_1002,
+		&af9035_props, "Afatech AF9035 reference design", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_AFATECH, USB_PID_AFATECH_AF9035_1003,
+		&af9035_props, "Afatech AF9035 reference design", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_CINERGY_T_STICK,
+		&af9035_props, "TerraTec Cinergy T Stick", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A835,
+		&af9035_props, "AVerMedia AVerTV Volar HD/PRO (A835)", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_B835,
+		&af9035_props, "AVerMedia AVerTV Volar HD/PRO (A835)", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_1867,
+		&af9035_props, "AVerMedia HD Volar (A867)", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A867,
+		&af9035_props, "AVerMedia HD Volar (A867)", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_TWINSTAR,
+		&af9035_props, "AVerMedia Twinstar (A825)", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_ASUS, USB_PID_ASUS_U3100MINI_PLUS,
+		&af9035_props, "Asus U3100Mini Plus", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_TERRATEC, 0x00aa,
+		&af9035_props, "TerraTec Cinergy T Stick (rev. 2)", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_AVERMEDIA, 0x0337,
+		&af9035_props, "AVerMedia HD Volar (A867)", NULL) },
+       { DVB_USB_DEVICE(USB_VID_GTEK, USB_PID_EVOLVEO_XTRATV_STICK,
+	       &af9035_props, "EVOLVEO XtraTV stick", NULL) },
+
+	/* IT9135 devices */
+	{ DVB_USB_DEVICE(USB_VID_ITETECH, USB_PID_ITETECH_IT9135,
+		&af9035_props, "ITE 9135 Generic", RC_MAP_IT913X_V1) },
+	{ DVB_USB_DEVICE(USB_VID_ITETECH, USB_PID_ITETECH_IT9135_9005,
+		&af9035_props, "ITE 9135(9005) Generic", RC_MAP_IT913X_V2) },
+	{ DVB_USB_DEVICE(USB_VID_ITETECH, USB_PID_ITETECH_IT9135_9006,
+		&af9035_props, "ITE 9135(9006) Generic", RC_MAP_IT913X_V1) },
+	{ DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A835B_1835,
+		&af9035_props, "Avermedia A835B(1835)", RC_MAP_IT913X_V2) },
+	{ DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A835B_2835,
+		&af9035_props, "Avermedia A835B(2835)", RC_MAP_IT913X_V2) },
+	{ DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A835B_3835,
+		&af9035_props, "Avermedia A835B(3835)", RC_MAP_IT913X_V2) },
+	{ DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A835B_4835,
+		&af9035_props, "Avermedia A835B(4835)",	RC_MAP_IT913X_V2) },
+	{ DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_TD110,
+		&af9035_props, "Avermedia AverTV Volar HD 2 (TD110)", RC_MAP_AVERMEDIA_RM_KS) },
+	{ DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_H335,
+		&af9035_props, "Avermedia H335", RC_MAP_IT913X_V2) },
+	{ DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_KWORLD_UB499_2T_T09,
+		&af9035_props, "Kworld UB499-2T T09", RC_MAP_IT913X_V1) },
+	{ DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_SVEON_STV22_IT9137,
+		&af9035_props, "Sveon STV22 Dual DVB-T HDTV",
+							RC_MAP_IT913X_V1) },
+	{ DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_CTVDIGDUAL_V2,
+		&af9035_props, "Digital Dual TV Receiver CTVDIGDUAL_V2",
+							RC_MAP_IT913X_V1) },
+	{ DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_T1,
+		&af9035_props, "TerraTec T1", RC_MAP_IT913X_V1) },
+	/* XXX: that same ID [0ccd:0099] is used by af9015 driver too */
+	{ DVB_USB_DEVICE(USB_VID_TERRATEC, 0x0099,
+		&af9035_props, "TerraTec Cinergy T Stick Dual RC (rev. 2)",
+		NULL) },
+	{ DVB_USB_DEVICE(USB_VID_LEADTEK, 0x6a05,
+		&af9035_props, "Leadtek WinFast DTV Dongle Dual", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xf900,
+		&af9035_props, "Hauppauge WinTV-MiniStick 2", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_PCTV, USB_PID_PCTV_78E,
+		&af9035_props, "PCTV AndroiDTV (78e)", RC_MAP_IT913X_V1) },
+	{ DVB_USB_DEVICE(USB_VID_PCTV, USB_PID_PCTV_79E,
+		&af9035_props, "PCTV microStick (79e)", RC_MAP_IT913X_V2) },
+
+	/* IT930x devices */
+	{ DVB_USB_DEVICE(USB_VID_ITETECH, USB_PID_ITETECH_IT9303,
+		&it930x_props, "ITE 9303 Generic", NULL) },
+	{ }
+};
+MODULE_DEVICE_TABLE(usb, af9035_id_table);
+
+static struct usb_driver af9035_usb_driver = {
+	.name = KBUILD_MODNAME,
+	.id_table = af9035_id_table,
+	.probe = af9035_probe,
+	.disconnect = dvb_usbv2_disconnect,
+	.suspend = dvb_usbv2_suspend,
+	.resume = dvb_usbv2_resume,
+	.reset_resume = dvb_usbv2_reset_resume,
+	.no_dynamic_id = 1,
+	.soft_unbind = 1,
+};
+
+module_usb_driver(af9035_usb_driver);
+
+MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>");
+MODULE_DESCRIPTION("Afatech AF9035 driver");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(AF9035_FIRMWARE_AF9035);
+MODULE_FIRMWARE(AF9035_FIRMWARE_IT9135_V1);
+MODULE_FIRMWARE(AF9035_FIRMWARE_IT9135_V2);
+MODULE_FIRMWARE(AF9035_FIRMWARE_IT9303);
diff --git a/drivers/media/usb/dvb-usb-v2/af9035.h b/drivers/media/usb/dvb-usb-v2/af9035.h
new file mode 100644
index 0000000..a76e6bf
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/af9035.h
@@ -0,0 +1,157 @@
+/*
+ * Afatech AF9035 DVB USB driver
+ *
+ * Copyright (C) 2009 Antti Palosaari <crope@iki.fi>
+ * Copyright (C) 2012 Antti Palosaari <crope@iki.fi>
+ *
+ *    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.
+ */
+
+#ifndef AF9035_H
+#define AF9035_H
+
+#include <linux/platform_device.h>
+#include "dvb_usb.h"
+#include "af9033.h"
+#include "tua9001.h"
+#include "fc0011.h"
+#include "fc0012.h"
+#include "mxl5007t.h"
+#include "tda18218.h"
+#include "fc2580.h"
+#include "it913x.h"
+#include "si2168.h"
+#include "si2157.h"
+
+struct reg_val {
+	u32 reg;
+	u8  val;
+};
+
+struct reg_val_mask {
+	u32 reg;
+	u8  val;
+	u8  mask;
+};
+
+struct usb_req {
+	u8  cmd;
+	u8  mbox;
+	u8  wlen;
+	u8  *wbuf;
+	u8  rlen;
+	u8  *rbuf;
+};
+
+struct state {
+#define BUF_LEN 64
+	u8 buf[BUF_LEN];
+	u8 seq; /* packet sequence number */
+	u8 prechip_version;
+	u8 chip_version;
+	u16 chip_type;
+	u8 eeprom[256];
+	bool no_eeprom;
+	u8 ir_mode;
+	u8 ir_type;
+	u8 dual_mode:1;
+	u8 no_read:1;
+	u8 af9033_i2c_addr[2];
+	struct af9033_config af9033_config[2];
+	struct af9033_ops ops;
+	#define AF9035_I2C_CLIENT_MAX 4
+	struct i2c_client *i2c_client[AF9035_I2C_CLIENT_MAX];
+	struct i2c_adapter *i2c_adapter_demod;
+	struct platform_device *platform_device_tuner[2];
+};
+
+static const u32 clock_lut_af9035[] = {
+	20480000, /*      FPGA */
+	16384000, /* 16.38 MHz */
+	20480000, /* 20.48 MHz */
+	36000000, /* 36.00 MHz */
+	30000000, /* 30.00 MHz */
+	26000000, /* 26.00 MHz */
+	28000000, /* 28.00 MHz */
+	32000000, /* 32.00 MHz */
+	34000000, /* 34.00 MHz */
+	24000000, /* 24.00 MHz */
+	22000000, /* 22.00 MHz */
+	12000000, /* 12.00 MHz */
+};
+
+static const u32 clock_lut_it9135[] = {
+	12000000, /* 12.00 MHz */
+	20480000, /* 20.48 MHz */
+	36000000, /* 36.00 MHz */
+	30000000, /* 30.00 MHz */
+	26000000, /* 26.00 MHz */
+	28000000, /* 28.00 MHz */
+	32000000, /* 32.00 MHz */
+	34000000, /* 34.00 MHz */
+	24000000, /* 24.00 MHz */
+	22000000, /* 22.00 MHz */
+};
+
+#define AF9035_FIRMWARE_AF9035 "dvb-usb-af9035-02.fw"
+#define AF9035_FIRMWARE_IT9135_V1 "dvb-usb-it9135-01.fw"
+#define AF9035_FIRMWARE_IT9135_V2 "dvb-usb-it9135-02.fw"
+#define AF9035_FIRMWARE_IT9303 "dvb-usb-it9303-01.fw"
+
+/*
+ * eeprom is memory mapped as read only. Writing that memory mapped address
+ * will not corrupt eeprom.
+ *
+ * TS mode:
+ * 0  TS
+ * 1  DCA + PIP
+ * 3  PIP
+ * 5  DCA + PIP (AF9035 only)
+ * n  DCA
+ *
+ * Values 0, 3 and 5 are seen to this day. 0 for single TS and 3/5 for dual TS.
+ */
+
+#define EEPROM_BASE_AF9035        0x42f5
+#define EEPROM_BASE_IT9135        0x4994
+#define EEPROM_SHIFT                0x10
+
+#define EEPROM_IR_MODE              0x18
+#define EEPROM_TS_MODE              0x31
+#define EEPROM_2ND_DEMOD_ADDR       0x32
+#define EEPROM_IR_TYPE              0x34
+#define EEPROM_1_IF_L               0x38
+#define EEPROM_1_IF_H               0x39
+#define EEPROM_1_TUNER_ID           0x3c
+#define EEPROM_2_IF_L               0x48
+#define EEPROM_2_IF_H               0x49
+#define EEPROM_2_TUNER_ID           0x4c
+
+/* USB commands */
+#define CMD_MEM_RD                  0x00
+#define CMD_MEM_WR                  0x01
+#define CMD_I2C_RD                  0x02
+#define CMD_I2C_WR                  0x03
+#define CMD_IR_GET                  0x18
+#define CMD_FW_DL                   0x21
+#define CMD_FW_QUERYINFO            0x22
+#define CMD_FW_BOOT                 0x23
+#define CMD_FW_DL_BEGIN             0x24
+#define CMD_FW_DL_END               0x25
+#define CMD_FW_SCATTER_WR           0x29
+#define CMD_GENERIC_I2C_RD          0x2a
+#define CMD_GENERIC_I2C_WR          0x2b
+
+#endif
diff --git a/drivers/media/usb/dvb-usb-v2/anysee.c b/drivers/media/usb/dvb-usb-v2/anysee.c
new file mode 100644
index 0000000..20ee7ee
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/anysee.c
@@ -0,0 +1,1438 @@
+/*
+ * DVB USB Linux driver for Anysee E30 DVB-C & DVB-T USB2.0 receiver
+ *
+ * Copyright (C) 2007 Antti Palosaari <crope@iki.fi>
+ *
+ *    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.
+ *
+ * TODO:
+ * - add smart card reader support for Conditional Access (CA)
+ *
+ * Card reader in Anysee is nothing more than ISO 7816 card reader.
+ * There is no hardware CAM in any Anysee device sold.
+ * In my understanding it should be implemented by making own module
+ * for ISO 7816 card reader, like dvb_ca_en50221 is implemented. This
+ * module registers serial interface that can be used to communicate
+ * with any ISO 7816 smart card.
+ *
+ * Any help according to implement serial smart card reader support
+ * is highly welcome!
+ */
+
+#include "anysee.h"
+#include "dvb-pll.h"
+#include "tda1002x.h"
+#include "mt352.h"
+#include "mt352_priv.h"
+#include "zl10353.h"
+#include "tda18212.h"
+#include "cx24116.h"
+#include "stv0900.h"
+#include "stv6110.h"
+#include "isl6423.h"
+#include "cxd2820r.h"
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static int anysee_ctrl_msg(struct dvb_usb_device *d,
+		u8 *sbuf, u8 slen, u8 *rbuf, u8 rlen)
+{
+	struct anysee_state *state = d_to_priv(d);
+	int act_len, ret, i;
+
+	mutex_lock(&d->usb_mutex);
+
+	memcpy(&state->buf[0], sbuf, slen);
+	state->buf[60] = state->seq++;
+
+	dev_dbg(&d->udev->dev, "%s: >>> %*ph\n", __func__, slen, state->buf);
+
+	/* We need receive one message more after dvb_usb_generic_rw due
+	   to weird transaction flow, which is 1 x send + 2 x receive. */
+	ret = dvb_usbv2_generic_rw_locked(d, state->buf, sizeof(state->buf),
+			state->buf, sizeof(state->buf));
+	if (ret)
+		goto error_unlock;
+
+	/* TODO FIXME: dvb_usb_generic_rw() fails rarely with error code -32
+	 * (EPIPE, Broken pipe). Function supports currently msleep() as a
+	 * parameter but I would not like to use it, since according to
+	 * Documentation/timers/timers-howto.txt it should not be used such
+	 * short, under < 20ms, sleeps. Repeating failed message would be
+	 * better choice as not to add unwanted delays...
+	 * Fixing that correctly is one of those or both;
+	 * 1) use repeat if possible
+	 * 2) add suitable delay
+	 */
+
+	/* get answer, retry few times if error returned */
+	for (i = 0; i < 3; i++) {
+		/* receive 2nd answer */
+		ret = usb_bulk_msg(d->udev, usb_rcvbulkpipe(d->udev,
+				d->props->generic_bulk_ctrl_endpoint),
+				state->buf, sizeof(state->buf), &act_len, 2000);
+		if (ret) {
+			dev_dbg(&d->udev->dev,
+					"%s: recv bulk message failed=%d\n",
+					__func__, ret);
+		} else {
+			dev_dbg(&d->udev->dev, "%s: <<< %*ph\n", __func__,
+					rlen, state->buf);
+
+			if (state->buf[63] != 0x4f)
+				dev_dbg(&d->udev->dev,
+						"%s: cmd failed\n", __func__);
+			break;
+		}
+	}
+
+	if (ret) {
+		/* all retries failed, it is fatal */
+		dev_err(&d->udev->dev, "%s: recv bulk message failed=%d\n",
+				KBUILD_MODNAME, ret);
+		goto error_unlock;
+	}
+
+	/* read request, copy returned data to return buf */
+	if (rbuf && rlen)
+		memcpy(rbuf, state->buf, rlen);
+
+error_unlock:
+	mutex_unlock(&d->usb_mutex);
+	return ret;
+}
+
+static int anysee_read_reg(struct dvb_usb_device *d, u16 reg, u8 *val)
+{
+	u8 buf[] = {CMD_REG_READ, reg >> 8, reg & 0xff, 0x01};
+	int ret;
+	ret = anysee_ctrl_msg(d, buf, sizeof(buf), val, 1);
+	dev_dbg(&d->udev->dev, "%s: reg=%04x val=%02x\n", __func__, reg, *val);
+	return ret;
+}
+
+static int anysee_write_reg(struct dvb_usb_device *d, u16 reg, u8 val)
+{
+	u8 buf[] = {CMD_REG_WRITE, reg >> 8, reg & 0xff, 0x01, val};
+	dev_dbg(&d->udev->dev, "%s: reg=%04x val=%02x\n", __func__, reg, val);
+	return anysee_ctrl_msg(d, buf, sizeof(buf), NULL, 0);
+}
+
+/* write single register with mask */
+static int anysee_wr_reg_mask(struct dvb_usb_device *d, u16 reg, u8 val,
+	u8 mask)
+{
+	int ret;
+	u8 tmp;
+
+	/* no need for read if whole reg is written */
+	if (mask != 0xff) {
+		ret = anysee_read_reg(d, reg, &tmp);
+		if (ret)
+			return ret;
+
+		val &= mask;
+		tmp &= ~mask;
+		val |= tmp;
+	}
+
+	return anysee_write_reg(d, reg, val);
+}
+
+/* read single register with mask */
+static int anysee_rd_reg_mask(struct dvb_usb_device *d, u16 reg, u8 *val,
+	u8 mask)
+{
+	int ret, i;
+	u8 tmp;
+
+	ret = anysee_read_reg(d, reg, &tmp);
+	if (ret)
+		return ret;
+
+	tmp &= mask;
+
+	/* find position of the first bit */
+	for (i = 0; i < 8; i++) {
+		if ((mask >> i) & 0x01)
+			break;
+	}
+	*val = tmp >> i;
+
+	return 0;
+}
+
+static int anysee_get_hw_info(struct dvb_usb_device *d, u8 *id)
+{
+	u8 buf[] = {CMD_GET_HW_INFO};
+	return anysee_ctrl_msg(d, buf, sizeof(buf), id, 3);
+}
+
+static int anysee_streaming_ctrl(struct dvb_frontend *fe, int onoff)
+{
+	u8 buf[] = {CMD_STREAMING_CTRL, (u8)onoff, 0x00};
+	dev_dbg(&fe_to_d(fe)->udev->dev, "%s: onoff=%d\n", __func__, onoff);
+	return anysee_ctrl_msg(fe_to_d(fe), buf, sizeof(buf), NULL, 0);
+}
+
+static int anysee_led_ctrl(struct dvb_usb_device *d, u8 mode, u8 interval)
+{
+	u8 buf[] = {CMD_LED_AND_IR_CTRL, 0x01, mode, interval};
+	dev_dbg(&d->udev->dev, "%s: state=%d interval=%d\n", __func__,
+			mode, interval);
+	return anysee_ctrl_msg(d, buf, sizeof(buf), NULL, 0);
+}
+
+static int anysee_ir_ctrl(struct dvb_usb_device *d, u8 onoff)
+{
+	u8 buf[] = {CMD_LED_AND_IR_CTRL, 0x02, onoff};
+	dev_dbg(&d->udev->dev, "%s: onoff=%d\n", __func__, onoff);
+	return anysee_ctrl_msg(d, buf, sizeof(buf), NULL, 0);
+}
+
+/* I2C */
+static int anysee_master_xfer(struct i2c_adapter *adap, struct i2c_msg *msg,
+	int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	int ret = 0, inc, i = 0;
+	u8 buf[52]; /* 4 + 48 (I2C WR USB command header + I2C WR max) */
+
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	while (i < num) {
+		if (num > i + 1 && (msg[i+1].flags & I2C_M_RD)) {
+			if (msg[i].len > 2 || msg[i+1].len > 60) {
+				ret = -EOPNOTSUPP;
+				break;
+			}
+			buf[0] = CMD_I2C_READ;
+			buf[1] = (msg[i].addr << 1) | 0x01;
+			buf[2] = msg[i].buf[0];
+			buf[3] = msg[i].buf[1];
+			buf[4] = msg[i].len-1;
+			buf[5] = msg[i+1].len;
+			ret = anysee_ctrl_msg(d, buf, 6, msg[i+1].buf,
+				msg[i+1].len);
+			inc = 2;
+		} else {
+			if (msg[i].len > 48) {
+				ret = -EOPNOTSUPP;
+				break;
+			}
+			buf[0] = CMD_I2C_WRITE;
+			buf[1] = (msg[i].addr << 1);
+			buf[2] = msg[i].len;
+			buf[3] = 0x01;
+			memcpy(&buf[4], msg[i].buf, msg[i].len);
+			ret = anysee_ctrl_msg(d, buf, 4 + msg[i].len, NULL, 0);
+			inc = 1;
+		}
+		if (ret)
+			break;
+
+		i += inc;
+	}
+
+	mutex_unlock(&d->i2c_mutex);
+
+	return ret ? ret : i;
+}
+
+static u32 anysee_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm anysee_i2c_algo = {
+	.master_xfer   = anysee_master_xfer,
+	.functionality = anysee_i2c_func,
+};
+
+static int anysee_mt352_demod_init(struct dvb_frontend *fe)
+{
+	static u8 clock_config[]   = { CLOCK_CTL,  0x38, 0x28 };
+	static u8 reset[]          = { RESET,      0x80 };
+	static u8 adc_ctl_1_cfg[]  = { ADC_CTL_1,  0x40 };
+	static u8 agc_cfg[]        = { AGC_TARGET, 0x28, 0x20 };
+	static u8 gpp_ctl_cfg[]    = { GPP_CTL,    0x33 };
+	static u8 capt_range_cfg[] = { CAPT_RANGE, 0x32 };
+
+	mt352_write(fe, clock_config,   sizeof(clock_config));
+	udelay(200);
+	mt352_write(fe, reset,          sizeof(reset));
+	mt352_write(fe, adc_ctl_1_cfg,  sizeof(adc_ctl_1_cfg));
+
+	mt352_write(fe, agc_cfg,        sizeof(agc_cfg));
+	mt352_write(fe, gpp_ctl_cfg,    sizeof(gpp_ctl_cfg));
+	mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg));
+
+	return 0;
+}
+
+/* Callbacks for DVB USB */
+static struct tda10023_config anysee_tda10023_config = {
+	.demod_address = (0x1a >> 1),
+	.invert = 0,
+	.xtal   = 16000000,
+	.pll_m  = 11,
+	.pll_p  = 3,
+	.pll_n  = 1,
+	.output_mode = TDA10023_OUTPUT_MODE_PARALLEL_C,
+	.deltaf = 0xfeeb,
+};
+
+static struct mt352_config anysee_mt352_config = {
+	.demod_address = (0x1e >> 1),
+	.demod_init    = anysee_mt352_demod_init,
+};
+
+static struct zl10353_config anysee_zl10353_config = {
+	.demod_address = (0x1e >> 1),
+	.parallel_ts = 1,
+};
+
+static struct zl10353_config anysee_zl10353_tda18212_config2 = {
+	.demod_address = (0x1e >> 1),
+	.parallel_ts = 1,
+	.disable_i2c_gate_ctrl = 1,
+	.no_tuner = 1,
+	.if2 = 41500,
+};
+
+static struct zl10353_config anysee_zl10353_tda18212_config = {
+	.demod_address = (0x18 >> 1),
+	.parallel_ts = 1,
+	.disable_i2c_gate_ctrl = 1,
+	.no_tuner = 1,
+	.if2 = 41500,
+};
+
+static struct tda10023_config anysee_tda10023_tda18212_config = {
+	.demod_address = (0x1a >> 1),
+	.xtal   = 16000000,
+	.pll_m  = 12,
+	.pll_p  = 3,
+	.pll_n  = 1,
+	.output_mode = TDA10023_OUTPUT_MODE_PARALLEL_B,
+	.deltaf = 0xba02,
+};
+
+static struct tda18212_config anysee_tda18212_config = {
+	.if_dvbt_6 = 4150,
+	.if_dvbt_7 = 4150,
+	.if_dvbt_8 = 4150,
+	.if_dvbc = 5000,
+};
+
+static struct tda18212_config anysee_tda18212_config2 = {
+	.if_dvbt_6 = 3550,
+	.if_dvbt_7 = 3700,
+	.if_dvbt_8 = 4150,
+	.if_dvbt2_6 = 3250,
+	.if_dvbt2_7 = 4000,
+	.if_dvbt2_8 = 4000,
+	.if_dvbc = 5000,
+};
+
+static struct cx24116_config anysee_cx24116_config = {
+	.demod_address = (0xaa >> 1),
+	.mpg_clk_pos_pol = 0x00,
+	.i2c_wr_max = 48,
+};
+
+static struct stv0900_config anysee_stv0900_config = {
+	.demod_address = (0xd0 >> 1),
+	.demod_mode = 0,
+	.xtal = 8000000,
+	.clkmode = 3,
+	.diseqc_mode = 2,
+	.tun1_maddress = 0,
+	.tun1_adc = 1, /* 1 Vpp */
+	.path1_mode = 3,
+};
+
+static struct stv6110_config anysee_stv6110_config = {
+	.i2c_address = (0xc0 >> 1),
+	.mclk = 16000000,
+	.clk_div = 1,
+};
+
+static struct isl6423_config anysee_isl6423_config = {
+	.current_max = SEC_CURRENT_800m,
+	.curlim  = SEC_CURRENT_LIM_OFF,
+	.mod_extern = 1,
+	.addr = (0x10 >> 1),
+};
+
+static struct cxd2820r_config anysee_cxd2820r_config = {
+	.i2c_address = 0x6d, /* (0xda >> 1) */
+	.ts_mode = 0x38,
+};
+
+/*
+ * New USB device strings: Mfr=1, Product=2, SerialNumber=0
+ * Manufacturer: AMT.CO.KR
+ *
+ * E30 VID=04b4 PID=861f HW=2 FW=2.1 Product=????????
+ * PCB: ?
+ * parts: DNOS404ZH102A(MT352, DTT7579(?))
+ *
+ * E30 VID=04b4 PID=861f HW=2 FW=2.1 "anysee-T(LP)"
+ * PCB: PCB 507T (rev1.61)
+ * parts: DNOS404ZH103A(ZL10353, DTT7579(?))
+ * OEA=0a OEB=00 OEC=00 OED=ff OEE=00
+ * IOA=45 IOB=ff IOC=00 IOD=ff IOE=00
+ *
+ * E30 Plus VID=04b4 PID=861f HW=6 FW=1.0 "anysee"
+ * PCB: 507CD (rev1.1)
+ * parts: DNOS404ZH103A(ZL10353, DTT7579(?)), CST56I01
+ * OEA=80 OEB=00 OEC=00 OED=ff OEE=fe
+ * IOA=4f IOB=ff IOC=00 IOD=06 IOE=01
+ * IOD[0] ZL10353 1=enabled
+ * IOA[7] TS 0=enabled
+ * tuner is not behind ZL10353 I2C-gate (no care if gate disabled or not)
+ *
+ * E30 C Plus VID=04b4 PID=861f HW=10 FW=1.0 "anysee-DC(LP)"
+ * PCB: 507DC (rev0.2)
+ * parts: TDA10023, DTOS403IH102B TM, CST56I01
+ * OEA=80 OEB=00 OEC=00 OED=ff OEE=fe
+ * IOA=4f IOB=ff IOC=00 IOD=26 IOE=01
+ * IOD[0] TDA10023 1=enabled
+ *
+ * E30 S2 Plus VID=04b4 PID=861f HW=11 FW=0.1 "anysee-S2(LP)"
+ * PCB: 507SI (rev2.1)
+ * parts: BS2N10WCC01(CX24116, CX24118), ISL6423, TDA8024
+ * OEA=80 OEB=00 OEC=ff OED=ff OEE=fe
+ * IOA=4d IOB=ff IOC=00 IOD=26 IOE=01
+ * IOD[0] CX24116 1=enabled
+ *
+ * E30 C Plus VID=1c73 PID=861f HW=15 FW=1.2 "anysee-FA(LP)"
+ * PCB: 507FA (rev0.4)
+ * parts: TDA10023, DTOS403IH102B TM, TDA8024
+ * OEA=80 OEB=00 OEC=ff OED=ff OEE=ff
+ * IOA=4d IOB=ff IOC=00 IOD=00 IOE=c0
+ * IOD[5] TDA10023 1=enabled
+ * IOE[0] tuner 1=enabled
+ *
+ * E30 Combo Plus VID=1c73 PID=861f HW=15 FW=1.2 "anysee-FA(LP)"
+ * PCB: 507FA (rev1.1)
+ * parts: ZL10353, TDA10023, DTOS403IH102B TM, TDA8024
+ * OEA=80 OEB=00 OEC=ff OED=ff OEE=ff
+ * IOA=4d IOB=ff IOC=00 IOD=00 IOE=c0
+ * DVB-C:
+ * IOD[5] TDA10023 1=enabled
+ * IOE[0] tuner 1=enabled
+ * DVB-T:
+ * IOD[0] ZL10353 1=enabled
+ * IOE[0] tuner 0=enabled
+ * tuner is behind ZL10353 I2C-gate
+ * tuner is behind TDA10023 I2C-gate
+ *
+ * E7 TC VID=1c73 PID=861f HW=18 FW=0.7 AMTCI=0.5 "anysee-E7TC(LP)"
+ * PCB: 508TC (rev0.6)
+ * parts: ZL10353, TDA10023, DNOD44CDH086A(TDA18212)
+ * OEA=80 OEB=00 OEC=03 OED=f7 OEE=ff
+ * IOA=4d IOB=00 IOC=cc IOD=48 IOE=e4
+ * IOA[7] TS 1=enabled
+ * IOE[4] TDA18212 1=enabled
+ * DVB-C:
+ * IOD[6] ZL10353 0=disabled
+ * IOD[5] TDA10023 1=enabled
+ * IOE[0] IF 1=enabled
+ * DVB-T:
+ * IOD[5] TDA10023 0=disabled
+ * IOD[6] ZL10353 1=enabled
+ * IOE[0] IF 0=enabled
+ *
+ * E7 S2 VID=1c73 PID=861f HW=19 FW=0.4 AMTCI=0.5 "anysee-E7S2(LP)"
+ * PCB: 508S2 (rev0.7)
+ * parts: DNBU10512IST(STV0903, STV6110), ISL6423
+ * OEA=80 OEB=00 OEC=03 OED=f7 OEE=ff
+ * IOA=4d IOB=00 IOC=c4 IOD=08 IOE=e4
+ * IOA[7] TS 1=enabled
+ * IOE[5] STV0903 1=enabled
+ *
+ * E7 T2C VID=1c73 PID=861f HW=20 FW=0.1 AMTCI=0.5 "anysee-E7T2C(LP)"
+ * PCB: 508T2C (rev0.3)
+ * parts: DNOQ44QCH106A(CXD2820R, TDA18212), TDA8024
+ * OEA=80 OEB=00 OEC=03 OED=f7 OEE=ff
+ * IOA=4d IOB=00 IOC=cc IOD=48 IOE=e4
+ * IOA[7] TS 1=enabled
+ * IOE[5] CXD2820R 1=enabled
+ *
+ * E7 PTC VID=1c73 PID=861f HW=21 FW=0.1 AMTCI=?? "anysee-E7PTC(LP)"
+ * PCB: 508PTC (rev0.5)
+ * parts: ZL10353, TDA10023, DNOD44CDH086A(TDA18212)
+ * OEA=80 OEB=00 OEC=03 OED=f7 OEE=ff
+ * IOA=4d IOB=00 IOC=cc IOD=48 IOE=e4
+ * IOA[7] TS 1=enabled
+ * IOE[4] TDA18212 1=enabled
+ * DVB-C:
+ * IOD[6] ZL10353 0=disabled
+ * IOD[5] TDA10023 1=enabled
+ * IOE[0] IF 1=enabled
+ * DVB-T:
+ * IOD[5] TDA10023 0=disabled
+ * IOD[6] ZL10353 1=enabled
+ * IOE[0] IF 0=enabled
+ *
+ * E7 PS2 VID=1c73 PID=861f HW=22 FW=0.1 AMTCI=?? "anysee-E7PS2(LP)"
+ * PCB: 508PS2 (rev0.4)
+ * parts: DNBU10512IST(STV0903, STV6110), ISL6423
+ * OEA=80 OEB=00 OEC=03 OED=f7 OEE=ff
+ * IOA=4d IOB=00 IOC=c4 IOD=08 IOE=e4
+ * IOA[7] TS 1=enabled
+ * IOE[5] STV0903 1=enabled
+ */
+
+static int anysee_read_config(struct dvb_usb_device *d)
+{
+	struct anysee_state *state = d_to_priv(d);
+	int ret;
+	u8 hw_info[3];
+
+	/*
+	 * Check which hardware we have.
+	 * We must do this call two times to get reliable values (hw/fw bug).
+	 */
+	ret = anysee_get_hw_info(d, hw_info);
+	if (ret)
+		goto error;
+
+	ret = anysee_get_hw_info(d, hw_info);
+	if (ret)
+		goto error;
+
+	/*
+	 * Meaning of these info bytes are guessed.
+	 */
+	dev_info(&d->udev->dev, "%s: firmware version %d.%d hardware id %d\n",
+			KBUILD_MODNAME, hw_info[1], hw_info[2], hw_info[0]);
+
+	state->hw = hw_info[0];
+error:
+	return ret;
+}
+
+/* external I2C gate used for DNOD44CDH086A(TDA18212) tuner module */
+static int anysee_i2c_gate_ctrl(struct dvb_frontend *fe, int enable)
+{
+	/* enable / disable tuner access on IOE[4] */
+	return anysee_wr_reg_mask(fe_to_d(fe), REG_IOE, (enable << 4), 0x10);
+}
+
+static int anysee_frontend_ctrl(struct dvb_frontend *fe, int onoff)
+{
+	struct anysee_state *state = fe_to_priv(fe);
+	struct dvb_usb_device *d = fe_to_d(fe);
+	int ret;
+	dev_dbg(&d->udev->dev, "%s: fe=%d onoff=%d\n", __func__, fe->id, onoff);
+
+	/* no frontend sleep control */
+	if (onoff == 0)
+		return 0;
+
+	switch (state->hw) {
+	case ANYSEE_HW_507FA: /* 15 */
+		/* E30 Combo Plus */
+		/* E30 C Plus */
+
+		if (fe->id == 0)  {
+			/* disable DVB-T demod on IOD[0] */
+			ret = anysee_wr_reg_mask(d, REG_IOD, (0 << 0), 0x01);
+			if (ret)
+				goto error;
+
+			/* enable DVB-C demod on IOD[5] */
+			ret = anysee_wr_reg_mask(d, REG_IOD, (1 << 5), 0x20);
+			if (ret)
+				goto error;
+
+			/* enable DVB-C tuner on IOE[0] */
+			ret = anysee_wr_reg_mask(d, REG_IOE, (1 << 0), 0x01);
+			if (ret)
+				goto error;
+		} else {
+			/* disable DVB-C demod on IOD[5] */
+			ret = anysee_wr_reg_mask(d, REG_IOD, (0 << 5), 0x20);
+			if (ret)
+				goto error;
+
+			/* enable DVB-T demod on IOD[0] */
+			ret = anysee_wr_reg_mask(d, REG_IOD, (1 << 0), 0x01);
+			if (ret)
+				goto error;
+
+			/* enable DVB-T tuner on IOE[0] */
+			ret = anysee_wr_reg_mask(d, REG_IOE, (0 << 0), 0x01);
+			if (ret)
+				goto error;
+		}
+
+		break;
+	case ANYSEE_HW_508TC: /* 18 */
+	case ANYSEE_HW_508PTC: /* 21 */
+		/* E7 TC */
+		/* E7 PTC */
+
+		if (fe->id == 0)  {
+			/* disable DVB-T demod on IOD[6] */
+			ret = anysee_wr_reg_mask(d, REG_IOD, (0 << 6), 0x40);
+			if (ret)
+				goto error;
+
+			/* enable DVB-C demod on IOD[5] */
+			ret = anysee_wr_reg_mask(d, REG_IOD, (1 << 5), 0x20);
+			if (ret)
+				goto error;
+
+			/* enable IF route on IOE[0] */
+			ret = anysee_wr_reg_mask(d, REG_IOE, (1 << 0), 0x01);
+			if (ret)
+				goto error;
+		} else {
+			/* disable DVB-C demod on IOD[5] */
+			ret = anysee_wr_reg_mask(d, REG_IOD, (0 << 5), 0x20);
+			if (ret)
+				goto error;
+
+			/* enable DVB-T demod on IOD[6] */
+			ret = anysee_wr_reg_mask(d, REG_IOD, (1 << 6), 0x40);
+			if (ret)
+				goto error;
+
+			/* enable IF route on IOE[0] */
+			ret = anysee_wr_reg_mask(d, REG_IOE, (0 << 0), 0x01);
+			if (ret)
+				goto error;
+		}
+
+		break;
+	default:
+		ret = 0;
+	}
+
+error:
+	return ret;
+}
+
+static int anysee_add_i2c_dev(struct dvb_usb_device *d, const char *type,
+		u8 addr, void *platform_data)
+{
+	int ret, num;
+	struct anysee_state *state = d_to_priv(d);
+	struct i2c_client *client;
+	struct i2c_adapter *adapter = &d->i2c_adap;
+	struct i2c_board_info board_info = {
+		.addr = addr,
+		.platform_data = platform_data,
+	};
+
+	strlcpy(board_info.type, type, I2C_NAME_SIZE);
+
+	/* find first free client */
+	for (num = 0; num < ANYSEE_I2C_CLIENT_MAX; num++) {
+		if (state->i2c_client[num] == NULL)
+			break;
+	}
+
+	dev_dbg(&d->udev->dev, "%s: num=%d\n", __func__, num);
+
+	if (num == ANYSEE_I2C_CLIENT_MAX) {
+		dev_err(&d->udev->dev, "%s: I2C client out of index\n",
+				KBUILD_MODNAME);
+		ret = -ENODEV;
+		goto err;
+	}
+
+	request_module("%s", board_info.type);
+
+	/* register I2C device */
+	client = i2c_new_device(adapter, &board_info);
+	if (client == NULL || client->dev.driver == NULL) {
+		ret = -ENODEV;
+		goto err;
+	}
+
+	/* increase I2C driver usage count */
+	if (!try_module_get(client->dev.driver->owner)) {
+		i2c_unregister_device(client);
+		ret = -ENODEV;
+		goto err;
+	}
+
+	state->i2c_client[num] = client;
+	return 0;
+err:
+	dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+
+static void anysee_del_i2c_dev(struct dvb_usb_device *d)
+{
+	int num;
+	struct anysee_state *state = d_to_priv(d);
+	struct i2c_client *client;
+
+	/* find last used client */
+	num = ANYSEE_I2C_CLIENT_MAX;
+	while (num--) {
+		if (state->i2c_client[num] != NULL)
+			break;
+	}
+
+	dev_dbg(&d->udev->dev, "%s: num=%d\n", __func__, num);
+
+	if (num == -1) {
+		dev_err(&d->udev->dev, "%s: I2C client out of index\n",
+				KBUILD_MODNAME);
+		goto err;
+	}
+
+	client = state->i2c_client[num];
+
+	/* decrease I2C driver usage count */
+	module_put(client->dev.driver->owner);
+
+	/* unregister I2C device */
+	i2c_unregister_device(client);
+
+	state->i2c_client[num] = NULL;
+err:
+	dev_dbg(&d->udev->dev, "%s: failed\n", __func__);
+}
+
+static int anysee_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct anysee_state *state = adap_to_priv(adap);
+	struct dvb_usb_device *d = adap_to_d(adap);
+	int ret = 0;
+	u8 tmp;
+	struct i2c_msg msg[2] = {
+		{
+			.addr = 0x60,
+			.flags = 0,
+			.len = 1,
+			.buf = "\x00",
+		}, {
+			.addr = 0x60,
+			.flags = I2C_M_RD,
+			.len = 1,
+			.buf = &tmp,
+		}
+	};
+
+	switch (state->hw) {
+	case ANYSEE_HW_507T: /* 2 */
+		/* E30 */
+
+		/* attach demod */
+		adap->fe[0] = dvb_attach(mt352_attach, &anysee_mt352_config,
+				&d->i2c_adap);
+		if (adap->fe[0])
+			break;
+
+		/* attach demod */
+		adap->fe[0] = dvb_attach(zl10353_attach, &anysee_zl10353_config,
+				&d->i2c_adap);
+
+		break;
+	case ANYSEE_HW_507CD: /* 6 */
+		/* E30 Plus */
+
+		/* enable DVB-T demod on IOD[0] */
+		ret = anysee_wr_reg_mask(d, REG_IOD, (1 << 0), 0x01);
+		if (ret)
+			goto error;
+
+		/* enable transport stream on IOA[7] */
+		ret = anysee_wr_reg_mask(d, REG_IOA, (0 << 7), 0x80);
+		if (ret)
+			goto error;
+
+		/* attach demod */
+		adap->fe[0] = dvb_attach(zl10353_attach, &anysee_zl10353_config,
+				&d->i2c_adap);
+
+		break;
+	case ANYSEE_HW_507DC: /* 10 */
+		/* E30 C Plus */
+
+		/* enable DVB-C demod on IOD[0] */
+		ret = anysee_wr_reg_mask(d, REG_IOD, (1 << 0), 0x01);
+		if (ret)
+			goto error;
+
+		/* attach demod */
+		adap->fe[0] = dvb_attach(tda10023_attach,
+				&anysee_tda10023_config, &d->i2c_adap, 0x48);
+
+		break;
+	case ANYSEE_HW_507SI: /* 11 */
+		/* E30 S2 Plus */
+
+		/* enable DVB-S/S2 demod on IOD[0] */
+		ret = anysee_wr_reg_mask(d, REG_IOD, (1 << 0), 0x01);
+		if (ret)
+			goto error;
+
+		/* attach demod */
+		adap->fe[0] = dvb_attach(cx24116_attach, &anysee_cx24116_config,
+				&d->i2c_adap);
+
+		break;
+	case ANYSEE_HW_507FA: /* 15 */
+		/* E30 Combo Plus */
+		/* E30 C Plus */
+
+		/* enable tuner on IOE[4] */
+		ret = anysee_wr_reg_mask(d, REG_IOE, (1 << 4), 0x10);
+		if (ret)
+			goto error;
+
+		/* probe TDA18212 */
+		tmp = 0;
+		ret = i2c_transfer(&d->i2c_adap, msg, 2);
+		if (ret == 2 && tmp == 0xc7) {
+			dev_dbg(&d->udev->dev, "%s: TDA18212 found\n",
+					__func__);
+			state->has_tda18212 = true;
+		}
+		else
+			tmp = 0;
+
+		/* disable tuner on IOE[4] */
+		ret = anysee_wr_reg_mask(d, REG_IOE, (0 << 4), 0x10);
+		if (ret)
+			goto error;
+
+		/* disable DVB-T demod on IOD[0] */
+		ret = anysee_wr_reg_mask(d, REG_IOD, (0 << 0), 0x01);
+		if (ret)
+			goto error;
+
+		/* enable DVB-C demod on IOD[5] */
+		ret = anysee_wr_reg_mask(d, REG_IOD, (1 << 5), 0x20);
+		if (ret)
+			goto error;
+
+		/* attach demod */
+		if (tmp == 0xc7) {
+			/* TDA18212 config */
+			adap->fe[0] = dvb_attach(tda10023_attach,
+					&anysee_tda10023_tda18212_config,
+					&d->i2c_adap, 0x48);
+
+			/* I2C gate for DNOD44CDH086A(TDA18212) tuner module */
+			if (adap->fe[0])
+				adap->fe[0]->ops.i2c_gate_ctrl =
+						anysee_i2c_gate_ctrl;
+		} else {
+			/* PLL config */
+			adap->fe[0] = dvb_attach(tda10023_attach,
+					&anysee_tda10023_config,
+					&d->i2c_adap, 0x48);
+		}
+
+		/* break out if first frontend attaching fails */
+		if (!adap->fe[0])
+			break;
+
+		/* disable DVB-C demod on IOD[5] */
+		ret = anysee_wr_reg_mask(d, REG_IOD, (0 << 5), 0x20);
+		if (ret)
+			goto error;
+
+		/* enable DVB-T demod on IOD[0] */
+		ret = anysee_wr_reg_mask(d, REG_IOD, (1 << 0), 0x01);
+		if (ret)
+			goto error;
+
+		/* attach demod */
+		if (tmp == 0xc7) {
+			/* TDA18212 config */
+			adap->fe[1] = dvb_attach(zl10353_attach,
+					&anysee_zl10353_tda18212_config2,
+					&d->i2c_adap);
+
+			/* I2C gate for DNOD44CDH086A(TDA18212) tuner module */
+			if (adap->fe[1])
+				adap->fe[1]->ops.i2c_gate_ctrl =
+						anysee_i2c_gate_ctrl;
+		} else {
+			/* PLL config */
+			adap->fe[1] = dvb_attach(zl10353_attach,
+					&anysee_zl10353_config,
+					&d->i2c_adap);
+		}
+
+		break;
+	case ANYSEE_HW_508TC: /* 18 */
+	case ANYSEE_HW_508PTC: /* 21 */
+		/* E7 TC */
+		/* E7 PTC */
+
+		/* disable DVB-T demod on IOD[6] */
+		ret = anysee_wr_reg_mask(d, REG_IOD, (0 << 6), 0x40);
+		if (ret)
+			goto error;
+
+		/* enable DVB-C demod on IOD[5] */
+		ret = anysee_wr_reg_mask(d, REG_IOD, (1 << 5), 0x20);
+		if (ret)
+			goto error;
+
+		/* attach demod */
+		adap->fe[0] = dvb_attach(tda10023_attach,
+				&anysee_tda10023_tda18212_config,
+				&d->i2c_adap, 0x48);
+
+		/* I2C gate for DNOD44CDH086A(TDA18212) tuner module */
+		if (adap->fe[0])
+			adap->fe[0]->ops.i2c_gate_ctrl = anysee_i2c_gate_ctrl;
+
+		/* break out if first frontend attaching fails */
+		if (!adap->fe[0])
+			break;
+
+		/* disable DVB-C demod on IOD[5] */
+		ret = anysee_wr_reg_mask(d, REG_IOD, (0 << 5), 0x20);
+		if (ret)
+			goto error;
+
+		/* enable DVB-T demod on IOD[6] */
+		ret = anysee_wr_reg_mask(d, REG_IOD, (1 << 6), 0x40);
+		if (ret)
+			goto error;
+
+		/* attach demod */
+		adap->fe[1] = dvb_attach(zl10353_attach,
+				&anysee_zl10353_tda18212_config,
+				&d->i2c_adap);
+
+		/* I2C gate for DNOD44CDH086A(TDA18212) tuner module */
+		if (adap->fe[1])
+			adap->fe[1]->ops.i2c_gate_ctrl = anysee_i2c_gate_ctrl;
+
+		state->has_ci = true;
+
+		break;
+	case ANYSEE_HW_508S2: /* 19 */
+	case ANYSEE_HW_508PS2: /* 22 */
+		/* E7 S2 */
+		/* E7 PS2 */
+
+		/* enable DVB-S/S2 demod on IOE[5] */
+		ret = anysee_wr_reg_mask(d, REG_IOE, (1 << 5), 0x20);
+		if (ret)
+			goto error;
+
+		/* attach demod */
+		adap->fe[0] = dvb_attach(stv0900_attach,
+				&anysee_stv0900_config, &d->i2c_adap, 0);
+
+		state->has_ci = true;
+
+		break;
+	case ANYSEE_HW_508T2C: /* 20 */
+		/* E7 T2C */
+
+		/* enable DVB-T/T2/C demod on IOE[5] */
+		ret = anysee_wr_reg_mask(d, REG_IOE, (1 << 5), 0x20);
+		if (ret)
+			goto error;
+
+		/* attach demod */
+		adap->fe[0] = dvb_attach(cxd2820r_attach,
+				&anysee_cxd2820r_config, &d->i2c_adap, NULL);
+
+		state->has_ci = true;
+
+		break;
+	}
+
+	if (!adap->fe[0]) {
+		/* we have no frontend :-( */
+		ret = -ENODEV;
+		dev_err(&d->udev->dev,
+				"%s: Unsupported Anysee version. Please report to <linux-media@vger.kernel.org>.\n",
+				KBUILD_MODNAME);
+	}
+error:
+	return ret;
+}
+
+static int anysee_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct anysee_state *state = adap_to_priv(adap);
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct dvb_frontend *fe;
+	int ret;
+	dev_dbg(&d->udev->dev, "%s:\n", __func__);
+
+	switch (state->hw) {
+	case ANYSEE_HW_507T: /* 2 */
+		/* E30 */
+
+		/* attach tuner */
+		fe = dvb_attach(dvb_pll_attach, adap->fe[0], (0xc2 >> 1), NULL,
+				DVB_PLL_THOMSON_DTT7579);
+
+		break;
+	case ANYSEE_HW_507CD: /* 6 */
+		/* E30 Plus */
+
+		/* attach tuner */
+		fe = dvb_attach(dvb_pll_attach, adap->fe[0], (0xc2 >> 1),
+				&d->i2c_adap, DVB_PLL_THOMSON_DTT7579);
+
+		break;
+	case ANYSEE_HW_507DC: /* 10 */
+		/* E30 C Plus */
+
+		/* attach tuner */
+		fe = dvb_attach(dvb_pll_attach, adap->fe[0], (0xc0 >> 1),
+				&d->i2c_adap, DVB_PLL_SAMSUNG_DTOS403IH102A);
+
+		break;
+	case ANYSEE_HW_507SI: /* 11 */
+		/* E30 S2 Plus */
+
+		/* attach LNB controller */
+		fe = dvb_attach(isl6423_attach, adap->fe[0], &d->i2c_adap,
+				&anysee_isl6423_config);
+
+		break;
+	case ANYSEE_HW_507FA: /* 15 */
+		/* E30 Combo Plus */
+		/* E30 C Plus */
+
+		/* Try first attach TDA18212 silicon tuner on IOE[4], if that
+		 * fails attach old simple PLL. */
+
+		/* attach tuner */
+		if (state->has_tda18212) {
+			struct tda18212_config tda18212_config =
+					anysee_tda18212_config;
+
+			tda18212_config.fe = adap->fe[0];
+			ret = anysee_add_i2c_dev(d, "tda18212", 0x60,
+					&tda18212_config);
+			if (ret)
+				goto err;
+
+			/* copy tuner ops for 2nd FE as tuner is shared */
+			if (adap->fe[1]) {
+				adap->fe[1]->tuner_priv =
+						adap->fe[0]->tuner_priv;
+				memcpy(&adap->fe[1]->ops.tuner_ops,
+						&adap->fe[0]->ops.tuner_ops,
+						sizeof(struct dvb_tuner_ops));
+			}
+
+			return 0;
+		} else {
+			/* attach tuner */
+			fe = dvb_attach(dvb_pll_attach, adap->fe[0],
+					(0xc0 >> 1), &d->i2c_adap,
+					DVB_PLL_SAMSUNG_DTOS403IH102A);
+
+			if (fe && adap->fe[1]) {
+				/* attach tuner for 2nd FE */
+				fe = dvb_attach(dvb_pll_attach, adap->fe[1],
+						(0xc0 >> 1), &d->i2c_adap,
+						DVB_PLL_SAMSUNG_DTOS403IH102A);
+			}
+		}
+
+		break;
+	case ANYSEE_HW_508TC: /* 18 */
+	case ANYSEE_HW_508PTC: /* 21 */
+	{
+		/* E7 TC */
+		/* E7 PTC */
+		struct tda18212_config tda18212_config = anysee_tda18212_config;
+
+		tda18212_config.fe = adap->fe[0];
+		ret = anysee_add_i2c_dev(d, "tda18212", 0x60, &tda18212_config);
+		if (ret)
+			goto err;
+
+		/* copy tuner ops for 2nd FE as tuner is shared */
+		if (adap->fe[1]) {
+			adap->fe[1]->tuner_priv = adap->fe[0]->tuner_priv;
+			memcpy(&adap->fe[1]->ops.tuner_ops,
+					&adap->fe[0]->ops.tuner_ops,
+					sizeof(struct dvb_tuner_ops));
+		}
+
+		return 0;
+	}
+	case ANYSEE_HW_508S2: /* 19 */
+	case ANYSEE_HW_508PS2: /* 22 */
+		/* E7 S2 */
+		/* E7 PS2 */
+
+		/* attach tuner */
+		fe = dvb_attach(stv6110_attach, adap->fe[0],
+				&anysee_stv6110_config, &d->i2c_adap);
+
+		if (fe) {
+			/* attach LNB controller */
+			fe = dvb_attach(isl6423_attach, adap->fe[0],
+					&d->i2c_adap, &anysee_isl6423_config);
+		}
+
+		break;
+
+	case ANYSEE_HW_508T2C: /* 20 */
+	{
+		/* E7 T2C */
+		struct tda18212_config tda18212_config =
+				anysee_tda18212_config2;
+
+		tda18212_config.fe = adap->fe[0];
+		ret = anysee_add_i2c_dev(d, "tda18212", 0x60, &tda18212_config);
+		if (ret)
+			goto err;
+
+		return 0;
+	}
+	default:
+		fe = NULL;
+	}
+
+	if (fe)
+		ret = 0;
+	else
+		ret = -ENODEV;
+err:
+	return ret;
+}
+
+#if IS_ENABLED(CONFIG_RC_CORE)
+static int anysee_rc_query(struct dvb_usb_device *d)
+{
+	u8 buf[] = {CMD_GET_IR_CODE};
+	u8 ircode[2];
+	int ret;
+
+	/* Remote controller is basic NEC using address byte 0x08.
+	   Anysee device RC query returns only two bytes, status and code,
+	   address byte is dropped. Also it does not return any value for
+	   NEC RCs having address byte other than 0x08. Due to that, we
+	   cannot use that device as standard NEC receiver.
+	   It could be possible make hack which reads whole code directly
+	   from device memory... */
+
+	ret = anysee_ctrl_msg(d, buf, sizeof(buf), ircode, sizeof(ircode));
+	if (ret)
+		return ret;
+
+	if (ircode[0]) {
+		dev_dbg(&d->udev->dev, "%s: key pressed %02x\n", __func__,
+				ircode[1]);
+		rc_keydown(d->rc_dev, RC_PROTO_NEC,
+			   RC_SCANCODE_NEC(0x08, ircode[1]), 0);
+	}
+
+	return 0;
+}
+
+static int anysee_get_rc_config(struct dvb_usb_device *d, struct dvb_usb_rc *rc)
+{
+	rc->allowed_protos = RC_PROTO_BIT_NEC;
+	rc->query          = anysee_rc_query;
+	rc->interval       = 250;  /* windows driver uses 500ms */
+
+	return 0;
+}
+#else
+	#define anysee_get_rc_config NULL
+#endif
+
+static int anysee_ci_read_attribute_mem(struct dvb_ca_en50221 *ci, int slot,
+	int addr)
+{
+	struct dvb_usb_device *d = ci->data;
+	int ret;
+	u8 buf[] = {CMD_CI, 0x02, 0x40 | addr >> 8, addr & 0xff, 0x00, 1};
+	u8 val;
+
+	ret = anysee_ctrl_msg(d, buf, sizeof(buf), &val, 1);
+	if (ret)
+		return ret;
+
+	return val;
+}
+
+static int anysee_ci_write_attribute_mem(struct dvb_ca_en50221 *ci, int slot,
+	int addr, u8 val)
+{
+	struct dvb_usb_device *d = ci->data;
+	int ret;
+	u8 buf[] = {CMD_CI, 0x03, 0x40 | addr >> 8, addr & 0xff, 0x00, 1, val};
+
+	ret = anysee_ctrl_msg(d, buf, sizeof(buf), NULL, 0);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int anysee_ci_read_cam_control(struct dvb_ca_en50221 *ci, int slot,
+	u8 addr)
+{
+	struct dvb_usb_device *d = ci->data;
+	int ret;
+	u8 buf[] = {CMD_CI, 0x04, 0x40, addr, 0x00, 1};
+	u8 val;
+
+	ret = anysee_ctrl_msg(d, buf, sizeof(buf), &val, 1);
+	if (ret)
+		return ret;
+
+	return val;
+}
+
+static int anysee_ci_write_cam_control(struct dvb_ca_en50221 *ci, int slot,
+	u8 addr, u8 val)
+{
+	struct dvb_usb_device *d = ci->data;
+	int ret;
+	u8 buf[] = {CMD_CI, 0x05, 0x40, addr, 0x00, 1, val};
+
+	ret = anysee_ctrl_msg(d, buf, sizeof(buf), NULL, 0);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int anysee_ci_slot_reset(struct dvb_ca_en50221 *ci, int slot)
+{
+	struct dvb_usb_device *d = ci->data;
+	int ret;
+	struct anysee_state *state = d_to_priv(d);
+
+	state->ci_cam_ready = jiffies + msecs_to_jiffies(1000);
+
+	ret = anysee_wr_reg_mask(d, REG_IOA, (0 << 7), 0x80);
+	if (ret)
+		return ret;
+
+	msleep(300);
+
+	ret = anysee_wr_reg_mask(d, REG_IOA, (1 << 7), 0x80);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int anysee_ci_slot_shutdown(struct dvb_ca_en50221 *ci, int slot)
+{
+	struct dvb_usb_device *d = ci->data;
+	int ret;
+
+	ret = anysee_wr_reg_mask(d, REG_IOA, (0 << 7), 0x80);
+	if (ret)
+		return ret;
+
+	msleep(30);
+
+	ret = anysee_wr_reg_mask(d, REG_IOA, (1 << 7), 0x80);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int anysee_ci_slot_ts_enable(struct dvb_ca_en50221 *ci, int slot)
+{
+	struct dvb_usb_device *d = ci->data;
+	int ret;
+
+	ret = anysee_wr_reg_mask(d, REG_IOD, (0 << 1), 0x02);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int anysee_ci_poll_slot_status(struct dvb_ca_en50221 *ci, int slot,
+	int open)
+{
+	struct dvb_usb_device *d = ci->data;
+	struct anysee_state *state = d_to_priv(d);
+	int ret;
+	u8 tmp = 0;
+
+	ret = anysee_rd_reg_mask(d, REG_IOC, &tmp, 0x40);
+	if (ret)
+		return ret;
+
+	if (tmp == 0) {
+		ret = DVB_CA_EN50221_POLL_CAM_PRESENT;
+		if (time_after(jiffies, state->ci_cam_ready))
+			ret |= DVB_CA_EN50221_POLL_CAM_READY;
+	}
+
+	return ret;
+}
+
+static int anysee_ci_init(struct dvb_usb_device *d)
+{
+	struct anysee_state *state = d_to_priv(d);
+	int ret;
+
+	state->ci.owner               = THIS_MODULE;
+	state->ci.read_attribute_mem  = anysee_ci_read_attribute_mem;
+	state->ci.write_attribute_mem = anysee_ci_write_attribute_mem;
+	state->ci.read_cam_control    = anysee_ci_read_cam_control;
+	state->ci.write_cam_control   = anysee_ci_write_cam_control;
+	state->ci.slot_reset          = anysee_ci_slot_reset;
+	state->ci.slot_shutdown       = anysee_ci_slot_shutdown;
+	state->ci.slot_ts_enable      = anysee_ci_slot_ts_enable;
+	state->ci.poll_slot_status    = anysee_ci_poll_slot_status;
+	state->ci.data                = d;
+
+	ret = anysee_wr_reg_mask(d, REG_IOA, (1 << 7), 0x80);
+	if (ret)
+		return ret;
+
+	ret = anysee_wr_reg_mask(d, REG_IOD, (0 << 2)|(0 << 1)|(0 << 0), 0x07);
+	if (ret)
+		return ret;
+
+	ret = anysee_wr_reg_mask(d, REG_IOD, (1 << 2)|(1 << 1)|(1 << 0), 0x07);
+	if (ret)
+		return ret;
+
+	ret = dvb_ca_en50221_init(&d->adapter[0].dvb_adap, &state->ci, 0, 1);
+	if (ret)
+		return ret;
+
+	state->ci_attached = true;
+
+	return 0;
+}
+
+static void anysee_ci_release(struct dvb_usb_device *d)
+{
+	struct anysee_state *state = d_to_priv(d);
+
+	/* detach CI */
+	if (state->ci_attached)
+		dvb_ca_en50221_release(&state->ci);
+
+	return;
+}
+
+static int anysee_init(struct dvb_usb_device *d)
+{
+	struct anysee_state *state = d_to_priv(d);
+	int ret;
+
+	/* There is one interface with two alternate settings.
+	   Alternate setting 0 is for bulk transfer.
+	   Alternate setting 1 is for isochronous transfer.
+	   We use bulk transfer (alternate setting 0). */
+	ret = usb_set_interface(d->udev, 0, 0);
+	if (ret)
+		return ret;
+
+	/* LED light */
+	ret = anysee_led_ctrl(d, 0x01, 0x03);
+	if (ret)
+		return ret;
+
+	/* enable IR */
+	ret = anysee_ir_ctrl(d, 1);
+	if (ret)
+		return ret;
+
+	/* attach CI */
+	if (state->has_ci) {
+		ret = anysee_ci_init(d);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static void anysee_exit(struct dvb_usb_device *d)
+{
+	struct anysee_state *state = d_to_priv(d);
+
+	if (state->i2c_client[0])
+		anysee_del_i2c_dev(d);
+
+	return anysee_ci_release(d);
+}
+
+/* DVB USB Driver stuff */
+static struct dvb_usb_device_properties anysee_props = {
+	.driver_name = KBUILD_MODNAME,
+	.owner = THIS_MODULE,
+	.adapter_nr = adapter_nr,
+	.size_of_priv = sizeof(struct anysee_state),
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+	.generic_bulk_ctrl_endpoint_response = 0x81,
+
+	.i2c_algo         = &anysee_i2c_algo,
+	.read_config      = anysee_read_config,
+	.frontend_attach  = anysee_frontend_attach,
+	.tuner_attach     = anysee_tuner_attach,
+	.init             = anysee_init,
+	.get_rc_config    = anysee_get_rc_config,
+	.frontend_ctrl    = anysee_frontend_ctrl,
+	.streaming_ctrl   = anysee_streaming_ctrl,
+	.exit             = anysee_exit,
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+			.stream = DVB_USB_STREAM_BULK(0x82, 8, 16 * 512),
+		}
+	}
+};
+
+static const struct usb_device_id anysee_id_table[] = {
+	{ DVB_USB_DEVICE(USB_VID_CYPRESS, USB_PID_ANYSEE,
+		&anysee_props, "Anysee", RC_MAP_ANYSEE) },
+	{ DVB_USB_DEVICE(USB_VID_AMT, USB_PID_ANYSEE,
+		&anysee_props, "Anysee", RC_MAP_ANYSEE) },
+	{ }
+};
+MODULE_DEVICE_TABLE(usb, anysee_id_table);
+
+static struct usb_driver anysee_usb_driver = {
+	.name = KBUILD_MODNAME,
+	.id_table = anysee_id_table,
+	.probe = dvb_usbv2_probe,
+	.disconnect = dvb_usbv2_disconnect,
+	.suspend = dvb_usbv2_suspend,
+	.resume = dvb_usbv2_resume,
+	.reset_resume = dvb_usbv2_reset_resume,
+	.no_dynamic_id = 1,
+	.soft_unbind = 1,
+};
+
+module_usb_driver(anysee_usb_driver);
+
+MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>");
+MODULE_DESCRIPTION("Driver Anysee E30 DVB-C & DVB-T USB2.0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb-v2/anysee.h b/drivers/media/usb/dvb-usb-v2/anysee.h
new file mode 100644
index 0000000..2312c55
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/anysee.h
@@ -0,0 +1,326 @@
+/*
+ * DVB USB Linux driver for Anysee E30 DVB-C & DVB-T USB2.0 receiver
+ *
+ * Copyright (C) 2007 Antti Palosaari <crope@iki.fi>
+ *
+ *    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.
+ *
+ * TODO:
+ * - add smart card reader support for Conditional Access (CA)
+ *
+ * Card reader in Anysee is nothing more than ISO 7816 card reader.
+ * There is no hardware CAM in any Anysee device sold.
+ * In my understanding it should be implemented by making own module
+ * for ISO 7816 card reader, like dvb_ca_en50221 is implemented. This
+ * module registers serial interface that can be used to communicate
+ * with any ISO 7816 smart card.
+ *
+ * Any help according to implement serial smart card reader support
+ * is highly welcome!
+ */
+
+#ifndef _DVB_USB_ANYSEE_H_
+#define _DVB_USB_ANYSEE_H_
+
+#define DVB_USB_LOG_PREFIX "anysee"
+#include "dvb_usb.h"
+#include <media/dvb_ca_en50221.h>
+
+enum cmd {
+	CMD_I2C_READ            = 0x33,
+	CMD_I2C_WRITE           = 0x31,
+	CMD_REG_READ            = 0xb0,
+	CMD_REG_WRITE           = 0xb1,
+	CMD_STREAMING_CTRL      = 0x12,
+	CMD_LED_AND_IR_CTRL     = 0x16,
+	CMD_GET_IR_CODE         = 0x41,
+	CMD_GET_HW_INFO         = 0x19,
+	CMD_SMARTCARD           = 0x34,
+	CMD_CI                  = 0x37,
+};
+
+struct anysee_state {
+	u8 buf[64];
+	u8 seq;
+	u8 hw; /* PCB ID */
+	#define ANYSEE_I2C_CLIENT_MAX 1
+	struct i2c_client *i2c_client[ANYSEE_I2C_CLIENT_MAX];
+	u8 fe_id:1; /* frondend ID */
+	u8 has_ci:1;
+	u8 has_tda18212:1;
+	u8 ci_attached:1;
+	struct dvb_ca_en50221 ci;
+	unsigned long ci_cam_ready; /* jiffies */
+};
+
+#define ANYSEE_HW_507T    2 /* E30 */
+#define ANYSEE_HW_507CD   6 /* E30 Plus */
+#define ANYSEE_HW_507DC  10 /* E30 C Plus */
+#define ANYSEE_HW_507SI  11 /* E30 S2 Plus */
+#define ANYSEE_HW_507FA  15 /* E30 Combo Plus / E30 C Plus */
+#define ANYSEE_HW_508TC  18 /* E7 TC */
+#define ANYSEE_HW_508S2  19 /* E7 S2 */
+#define ANYSEE_HW_508T2C 20 /* E7 T2C */
+#define ANYSEE_HW_508PTC 21 /* E7 PTC Plus */
+#define ANYSEE_HW_508PS2 22 /* E7 PS2 Plus */
+
+#define REG_IOA       0x80 /* Port A (bit addressable) */
+#define REG_IOB       0x90 /* Port B (bit addressable) */
+#define REG_IOC       0xa0 /* Port C (bit addressable) */
+#define REG_IOD       0xb0 /* Port D (bit addressable) */
+#define REG_IOE       0xb1 /* Port E (NOT bit addressable) */
+#define REG_OEA       0xb2 /* Port A Output Enable */
+#define REG_OEB       0xb3 /* Port B Output Enable */
+#define REG_OEC       0xb4 /* Port C Output Enable */
+#define REG_OED       0xb5 /* Port D Output Enable */
+#define REG_OEE       0xb6 /* Port E Output Enable */
+
+#endif
+
+/***************************************************************************
+ * USB API description (reverse engineered)
+ ***************************************************************************
+
+Transaction flow:
+=================
+BULK[00001] >>> REQUEST PACKET 64 bytes
+BULK[00081] <<< REPLY PACKET #1 64 bytes (PREVIOUS TRANSACTION REPLY)
+BULK[00081] <<< REPLY PACKET #2 64 bytes (CURRENT TRANSACTION REPLY)
+
+General reply packet(s) are always used if not own reply defined.
+
+============================================================================
+| 00-63 | GENERAL REPLY PACKET #1 (PREVIOUS REPLY)
+============================================================================
+|    00 | reply data (if any) from previous transaction
+|       | Just same reply packet as returned during previous transaction.
+|       | Needed only if reply is missed in previous transaction.
+|       | Just skip normally.
+----------------------------------------------------------------------------
+| 01-59 | don't care
+----------------------------------------------------------------------------
+|    60 | packet sequence number
+----------------------------------------------------------------------------
+| 61-63 | don't care
+----------------------------------------------------------------------------
+
+============================================================================
+| 00-63 | GENERAL REPLY PACKET #2 (CURRENT REPLY)
+============================================================================
+|    00 | reply data (if any)
+----------------------------------------------------------------------------
+| 01-59 | don't care
+----------------------------------------------------------------------------
+|    60 | packet sequence number
+----------------------------------------------------------------------------
+| 61-63 | don't care
+----------------------------------------------------------------------------
+
+============================================================================
+| 00-63 | I2C WRITE REQUEST PACKET
+============================================================================
+|    00 | 0x31 I2C write command
+----------------------------------------------------------------------------
+|    01 | i2c address
+----------------------------------------------------------------------------
+|    02 | data length
+|       | 0x02 (for typical I2C reg / val pair)
+----------------------------------------------------------------------------
+|    03 | 0x01
+----------------------------------------------------------------------------
+| 04-   | data
+----------------------------------------------------------------------------
+|   -59 | don't care
+----------------------------------------------------------------------------
+|    60 | packet sequence number
+----------------------------------------------------------------------------
+| 61-63 | don't care
+----------------------------------------------------------------------------
+
+============================================================================
+| 00-63 | I2C READ REQUEST PACKET
+============================================================================
+|    00 | 0x33 I2C read command
+----------------------------------------------------------------------------
+|    01 | i2c address + 1
+----------------------------------------------------------------------------
+|    02 | register
+----------------------------------------------------------------------------
+|    03 | 0x00
+----------------------------------------------------------------------------
+|    04 | 0x00
+----------------------------------------------------------------------------
+|    05 | data length
+----------------------------------------------------------------------------
+| 06-59 | don't care
+----------------------------------------------------------------------------
+|    60 | packet sequence number
+----------------------------------------------------------------------------
+| 61-63 | don't care
+----------------------------------------------------------------------------
+
+============================================================================
+| 00-63 | USB CONTROLLER REGISTER WRITE REQUEST PACKET
+============================================================================
+|    00 | 0xb1 register write command
+----------------------------------------------------------------------------
+| 01-02 | register
+----------------------------------------------------------------------------
+|    03 | 0x01
+----------------------------------------------------------------------------
+|    04 | value
+----------------------------------------------------------------------------
+| 05-59 | don't care
+----------------------------------------------------------------------------
+|    60 | packet sequence number
+----------------------------------------------------------------------------
+| 61-63 | don't care
+----------------------------------------------------------------------------
+
+============================================================================
+| 00-63 | USB CONTROLLER REGISTER READ REQUEST PACKET
+============================================================================
+|    00 | 0xb0 register read command
+----------------------------------------------------------------------------
+| 01-02 | register
+----------------------------------------------------------------------------
+|    03 | 0x01
+----------------------------------------------------------------------------
+| 04-59 | don't care
+----------------------------------------------------------------------------
+|    60 | packet sequence number
+----------------------------------------------------------------------------
+| 61-63 | don't care
+----------------------------------------------------------------------------
+
+============================================================================
+| 00-63 | LED CONTROL REQUEST PACKET
+============================================================================
+|    00 | 0x16 LED and IR control command
+----------------------------------------------------------------------------
+|    01 | 0x01 (LED)
+----------------------------------------------------------------------------
+|    03 | 0x00 blink
+|       | 0x01 lights continuously
+----------------------------------------------------------------------------
+|    04 | blink interval
+|       | 0x00 fastest (looks like LED lights continuously)
+|       | 0xff slowest
+----------------------------------------------------------------------------
+| 05-59 | don't care
+----------------------------------------------------------------------------
+|    60 | packet sequence number
+----------------------------------------------------------------------------
+| 61-63 | don't care
+----------------------------------------------------------------------------
+
+============================================================================
+| 00-63 | IR CONTROL REQUEST PACKET
+============================================================================
+|    00 | 0x16 LED and IR control command
+----------------------------------------------------------------------------
+|    01 | 0x02 (IR)
+----------------------------------------------------------------------------
+|    03 | 0x00 IR disabled
+|       | 0x01 IR enabled
+----------------------------------------------------------------------------
+| 04-59 | don't care
+----------------------------------------------------------------------------
+|    60 | packet sequence number
+----------------------------------------------------------------------------
+| 61-63 | don't care
+----------------------------------------------------------------------------
+
+============================================================================
+| 00-63 | STREAMING CONTROL REQUEST PACKET
+============================================================================
+|    00 | 0x12 streaming control command
+----------------------------------------------------------------------------
+|    01 | 0x00 streaming disabled
+|       | 0x01 streaming enabled
+----------------------------------------------------------------------------
+|    02 | 0x00
+----------------------------------------------------------------------------
+| 03-59 | don't care
+----------------------------------------------------------------------------
+|    60 | packet sequence number
+----------------------------------------------------------------------------
+| 61-63 | don't care
+----------------------------------------------------------------------------
+
+============================================================================
+| 00-63 | REMOTE CONTROL REQUEST PACKET
+============================================================================
+|    00 | 0x41 remote control command
+----------------------------------------------------------------------------
+| 01-59 | don't care
+----------------------------------------------------------------------------
+|    60 | packet sequence number
+----------------------------------------------------------------------------
+| 61-63 | don't care
+----------------------------------------------------------------------------
+
+============================================================================
+| 00-63 | REMOTE CONTROL REPLY PACKET
+============================================================================
+|    00 | 0x00 code not received
+|       | 0x01 code received
+----------------------------------------------------------------------------
+|    01 | remote control code
+----------------------------------------------------------------------------
+| 02-59 | don't care
+----------------------------------------------------------------------------
+|    60 | packet sequence number
+----------------------------------------------------------------------------
+| 61-63 | don't care
+----------------------------------------------------------------------------
+
+============================================================================
+| 00-63 | GET HARDWARE INFO REQUEST PACKET
+============================================================================
+|    00 | 0x19 get hardware info command
+----------------------------------------------------------------------------
+| 01-59 | don't care
+----------------------------------------------------------------------------
+|    60 | packet sequence number
+----------------------------------------------------------------------------
+| 61-63 | don't care
+----------------------------------------------------------------------------
+
+============================================================================
+| 00-63 | GET HARDWARE INFO REPLY PACKET
+============================================================================
+|    00 | hardware id
+----------------------------------------------------------------------------
+| 01-02 | firmware version
+----------------------------------------------------------------------------
+| 03-59 | don't care
+----------------------------------------------------------------------------
+|    60 | packet sequence number
+----------------------------------------------------------------------------
+| 61-63 | don't care
+----------------------------------------------------------------------------
+
+============================================================================
+| 00-63 | SMART CARD READER PACKET
+============================================================================
+|    00 | 0x34 smart card reader command
+----------------------------------------------------------------------------
+|    xx |
+----------------------------------------------------------------------------
+| xx-59 | don't care
+----------------------------------------------------------------------------
+|    60 | packet sequence number
+----------------------------------------------------------------------------
+| 61-63 | don't care
+----------------------------------------------------------------------------
+
+*/
diff --git a/drivers/media/usb/dvb-usb-v2/au6610.c b/drivers/media/usb/dvb-usb-v2/au6610.c
new file mode 100644
index 0000000..6ee01cb
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/au6610.c
@@ -0,0 +1,209 @@
+/*
+ * DVB USB Linux driver for Alcor Micro AU6610 DVB-T USB2.0.
+ *
+ * Copyright (C) 2006 Antti Palosaari <crope@iki.fi>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ */
+
+#include "au6610.h"
+#include "zl10353.h"
+#include "qt1010.h"
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static int au6610_usb_msg(struct dvb_usb_device *d, u8 operation, u8 addr,
+			  u8 *wbuf, u16 wlen, u8 *rbuf, u16 rlen)
+{
+	int ret;
+	u16 index;
+	u8 *usb_buf;
+
+	/*
+	 * allocate enough for all known requests,
+	 * read returns 5 and write 6 bytes
+	 */
+	usb_buf = kmalloc(6, GFP_KERNEL);
+	if (!usb_buf)
+		return -ENOMEM;
+
+	switch (wlen) {
+	case 1:
+		index = wbuf[0] << 8;
+		break;
+	case 2:
+		index = wbuf[0] << 8;
+		index += wbuf[1];
+		break;
+	default:
+		dev_err(&d->udev->dev, "%s: wlen=%d, aborting\n",
+				KBUILD_MODNAME, wlen);
+		ret = -EINVAL;
+		goto error;
+	}
+
+	ret = usb_control_msg(d->udev, usb_rcvctrlpipe(d->udev, 0), operation,
+			      USB_TYPE_VENDOR|USB_DIR_IN, addr << 1, index,
+			      usb_buf, 6, AU6610_USB_TIMEOUT);
+
+	dvb_usb_dbg_usb_control_msg(d->udev, operation,
+			(USB_TYPE_VENDOR|USB_DIR_IN), addr << 1, index,
+			usb_buf, 6);
+
+	if (ret < 0)
+		goto error;
+
+	switch (operation) {
+	case AU6610_REQ_I2C_READ:
+	case AU6610_REQ_USB_READ:
+		/* requested value is always 5th byte in buffer */
+		rbuf[0] = usb_buf[4];
+	}
+error:
+	kfree(usb_buf);
+	return ret;
+}
+
+static int au6610_i2c_msg(struct dvb_usb_device *d, u8 addr,
+			  u8 *wbuf, u16 wlen, u8 *rbuf, u16 rlen)
+{
+	u8 request;
+	u8 wo = (rbuf == NULL || rlen == 0); /* write-only */
+
+	if (wo) {
+		request = AU6610_REQ_I2C_WRITE;
+	} else { /* rw */
+		request = AU6610_REQ_I2C_READ;
+	}
+
+	return au6610_usb_msg(d, request, addr, wbuf, wlen, rbuf, rlen);
+}
+
+
+/* I2C */
+static int au6610_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[],
+			   int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	int i;
+
+	if (num > 2)
+		return -EINVAL;
+
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	for (i = 0; i < num; i++) {
+		/* write/read request */
+		if (i+1 < num && (msg[i+1].flags & I2C_M_RD)) {
+			if (au6610_i2c_msg(d, msg[i].addr, msg[i].buf,
+					   msg[i].len, msg[i+1].buf,
+					   msg[i+1].len) < 0)
+				break;
+			i++;
+		} else if (au6610_i2c_msg(d, msg[i].addr, msg[i].buf,
+					       msg[i].len, NULL, 0) < 0)
+				break;
+	}
+
+	mutex_unlock(&d->i2c_mutex);
+	return i;
+}
+
+
+static u32 au6610_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm au6610_i2c_algo = {
+	.master_xfer   = au6610_i2c_xfer,
+	.functionality = au6610_i2c_func,
+};
+
+/* Callbacks for DVB USB */
+static struct zl10353_config au6610_zl10353_config = {
+	.demod_address = 0x0f,
+	.no_tuner = 1,
+	.parallel_ts = 1,
+};
+
+static int au6610_zl10353_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	adap->fe[0] = dvb_attach(zl10353_attach, &au6610_zl10353_config,
+			&adap_to_d(adap)->i2c_adap);
+	if (adap->fe[0] == NULL)
+		return -ENODEV;
+
+	return 0;
+}
+
+static struct qt1010_config au6610_qt1010_config = {
+	.i2c_address = 0x62
+};
+
+static int au6610_qt1010_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	return dvb_attach(qt1010_attach, adap->fe[0],
+			&adap_to_d(adap)->i2c_adap,
+			&au6610_qt1010_config) == NULL ? -ENODEV : 0;
+}
+
+static int au6610_init(struct dvb_usb_device *d)
+{
+	/* TODO: this functionality belongs likely to the streaming control */
+	/* bInterfaceNumber 0, bAlternateSetting 5 */
+	return usb_set_interface(d->udev, 0, 5);
+}
+
+static struct dvb_usb_device_properties au6610_props = {
+	.driver_name = KBUILD_MODNAME,
+	.owner = THIS_MODULE,
+	.adapter_nr = adapter_nr,
+
+	.i2c_algo = &au6610_i2c_algo,
+	.frontend_attach = au6610_zl10353_frontend_attach,
+	.tuner_attach = au6610_qt1010_tuner_attach,
+	.init = au6610_init,
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+			.stream = DVB_USB_STREAM_ISOC(0x82, 5, 40, 942, 1),
+		},
+	},
+};
+
+static const struct usb_device_id au6610_id_table[] = {
+	{ DVB_USB_DEVICE(USB_VID_ALCOR_MICRO, USB_PID_SIGMATEK_DVB_110,
+		&au6610_props, "Sigmatek DVB-110", NULL) },
+	{ }
+};
+MODULE_DEVICE_TABLE(usb, au6610_id_table);
+
+static struct usb_driver au6610_driver = {
+	.name = KBUILD_MODNAME,
+	.id_table = au6610_id_table,
+	.probe = dvb_usbv2_probe,
+	.disconnect = dvb_usbv2_disconnect,
+	.suspend = dvb_usbv2_suspend,
+	.resume = dvb_usbv2_resume,
+	.reset_resume = dvb_usbv2_reset_resume,
+	.no_dynamic_id = 1,
+	.soft_unbind = 1,
+};
+
+module_usb_driver(au6610_driver);
+
+MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>");
+MODULE_DESCRIPTION("Driver for Alcor Micro AU6610 DVB-T USB2.0");
+MODULE_VERSION("0.1");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb-v2/au6610.h b/drivers/media/usb/dvb-usb-v2/au6610.h
new file mode 100644
index 0000000..aacfcc6
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/au6610.h
@@ -0,0 +1,28 @@
+/*
+ * DVB USB Linux driver for Alcor Micro AU6610 DVB-T USB2.0.
+ *
+ * Copyright (C) 2006 Antti Palosaari <crope@iki.fi>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ */
+
+#ifndef AU6610_H
+#define AU6610_H
+#include "dvb_usb.h"
+
+#define AU6610_REQ_I2C_WRITE	0x14
+#define AU6610_REQ_I2C_READ	0x13
+#define AU6610_REQ_USB_WRITE	0x16
+#define AU6610_REQ_USB_READ	0x15
+
+#define AU6610_USB_TIMEOUT 1000
+
+#endif
diff --git a/drivers/media/usb/dvb-usb-v2/az6007.c b/drivers/media/usb/dvb-usb-v2/az6007.c
new file mode 100644
index 0000000..7469263
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/az6007.c
@@ -0,0 +1,991 @@
+/*
+ * Driver for AzureWave 6007 DVB-C/T USB2.0 and clones
+ *
+ * Copyright (c) Henry Wang <Henry.wang@AzureWave.com>
+ *
+ * This driver was made publicly available by Terratec, at:
+ *	http://linux.terratec.de/files/TERRATEC_H7/20110323_TERRATEC_H7_Linux.tar.gz
+ * The original driver's license is GPL, as declared with MODULE_LICENSE()
+ *
+ * Copyright (c) 2010-2012 Mauro Carvalho Chehab
+ *	Driver modified by in order to work with upstream drxk driver, and
+ *	tons of bugs got fixed, and converted to use dvb-usb-v2.
+ *
+ * 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 under version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "drxk.h"
+#include "mt2063.h"
+#include <media/dvb_ca_en50221.h>
+#include "dvb_usb.h"
+#include "cypress_firmware.h"
+
+#define AZ6007_FIRMWARE "dvb-usb-terratec-h7-az6007.fw"
+
+static int az6007_xfer_debug;
+module_param_named(xfer_debug, az6007_xfer_debug, int, 0644);
+MODULE_PARM_DESC(xfer_debug, "Enable xfer debug");
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+/* Known requests (Cypress FX2 firmware + az6007 "private" ones*/
+
+#define FX2_OED			0xb5
+#define AZ6007_READ_DATA	0xb7
+#define AZ6007_I2C_RD		0xb9
+#define AZ6007_POWER		0xbc
+#define AZ6007_I2C_WR		0xbd
+#define FX2_SCON1		0xc0
+#define AZ6007_TS_THROUGH	0xc7
+#define AZ6007_READ_IR		0xb4
+
+struct az6007_device_state {
+	struct mutex		mutex;
+	struct mutex		ca_mutex;
+	struct dvb_ca_en50221	ca;
+	unsigned		warm:1;
+	int			(*gate_ctrl) (struct dvb_frontend *, int);
+	unsigned char		data[4096];
+};
+
+static struct drxk_config terratec_h7_drxk = {
+	.adr = 0x29,
+	.parallel_ts = true,
+	.dynamic_clk = true,
+	.single_master = true,
+	.enable_merr_cfg = true,
+	.no_i2c_bridge = false,
+	.chunk_size = 64,
+	.mpeg_out_clk_strength = 0x02,
+	.qam_demod_parameter_count = 2,
+	.microcode_name = "dvb-usb-terratec-h7-drxk.fw",
+};
+
+static struct drxk_config cablestar_hdci_drxk = {
+	.adr = 0x29,
+	.parallel_ts = true,
+	.dynamic_clk = true,
+	.single_master = true,
+	.enable_merr_cfg = true,
+	.no_i2c_bridge = false,
+	.chunk_size = 64,
+	.mpeg_out_clk_strength = 0x02,
+	.qam_demod_parameter_count = 2,
+	.microcode_name = "dvb-usb-technisat-cablestar-hdci-drxk.fw",
+};
+
+static int drxk_gate_ctrl(struct dvb_frontend *fe, int enable)
+{
+	struct az6007_device_state *st = fe_to_priv(fe);
+	struct dvb_usb_adapter *adap = fe->sec_priv;
+	int status = 0;
+
+	pr_debug("%s: %s\n", __func__, enable ? "enable" : "disable");
+
+	if (!adap || !st)
+		return -EINVAL;
+
+	if (enable)
+		status = st->gate_ctrl(fe, 1);
+	else
+		status = st->gate_ctrl(fe, 0);
+
+	return status;
+}
+
+static struct mt2063_config az6007_mt2063_config = {
+	.tuner_address = 0x60,
+	.refclock = 36125000,
+};
+
+static int __az6007_read(struct usb_device *udev, u8 req, u16 value,
+			    u16 index, u8 *b, int blen)
+{
+	int ret;
+
+	ret = usb_control_msg(udev,
+			      usb_rcvctrlpipe(udev, 0),
+			      req,
+			      USB_TYPE_VENDOR | USB_DIR_IN,
+			      value, index, b, blen, 5000);
+	if (ret < 0) {
+		pr_warn("usb read operation failed. (%d)\n", ret);
+		return -EIO;
+	}
+
+	if (az6007_xfer_debug) {
+		printk(KERN_DEBUG "az6007: IN  req: %02x, value: %04x, index: %04x\n",
+		       req, value, index);
+		print_hex_dump_bytes("az6007: payload: ",
+				     DUMP_PREFIX_NONE, b, blen);
+	}
+
+	return ret;
+}
+
+static int az6007_read(struct dvb_usb_device *d, u8 req, u16 value,
+			    u16 index, u8 *b, int blen)
+{
+	struct az6007_device_state *st = d->priv;
+	int ret;
+
+	if (mutex_lock_interruptible(&st->mutex) < 0)
+		return -EAGAIN;
+
+	ret = __az6007_read(d->udev, req, value, index, b, blen);
+
+	mutex_unlock(&st->mutex);
+
+	return ret;
+}
+
+static int __az6007_write(struct usb_device *udev, u8 req, u16 value,
+			     u16 index, u8 *b, int blen)
+{
+	int ret;
+
+	if (az6007_xfer_debug) {
+		printk(KERN_DEBUG "az6007: OUT req: %02x, value: %04x, index: %04x\n",
+		       req, value, index);
+		print_hex_dump_bytes("az6007: payload: ",
+				     DUMP_PREFIX_NONE, b, blen);
+	}
+
+	if (blen > 64) {
+		pr_err("az6007: tried to write %d bytes, but I2C max size is 64 bytes\n",
+		       blen);
+		return -EOPNOTSUPP;
+	}
+
+	ret = usb_control_msg(udev,
+			      usb_sndctrlpipe(udev, 0),
+			      req,
+			      USB_TYPE_VENDOR | USB_DIR_OUT,
+			      value, index, b, blen, 5000);
+	if (ret != blen) {
+		pr_err("usb write operation failed. (%d)\n", ret);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int az6007_write(struct dvb_usb_device *d, u8 req, u16 value,
+			    u16 index, u8 *b, int blen)
+{
+	struct az6007_device_state *st = d->priv;
+	int ret;
+
+	if (mutex_lock_interruptible(&st->mutex) < 0)
+		return -EAGAIN;
+
+	ret = __az6007_write(d->udev, req, value, index, b, blen);
+
+	mutex_unlock(&st->mutex);
+
+	return ret;
+}
+
+static int az6007_streaming_ctrl(struct dvb_frontend *fe, int onoff)
+{
+	struct dvb_usb_device *d = fe_to_d(fe);
+
+	pr_debug("%s: %s\n", __func__, onoff ? "enable" : "disable");
+
+	return az6007_write(d, 0xbc, onoff, 0, NULL, 0);
+}
+
+#if IS_ENABLED(CONFIG_RC_CORE)
+/* remote control stuff (does not work with my box) */
+static int az6007_rc_query(struct dvb_usb_device *d)
+{
+	struct az6007_device_state *st = d_to_priv(d);
+	unsigned code;
+	enum rc_proto proto;
+
+	az6007_read(d, AZ6007_READ_IR, 0, 0, st->data, 10);
+
+	if (st->data[1] == 0x44)
+		return 0;
+
+	if ((st->data[3] ^ st->data[4]) == 0xff) {
+		if ((st->data[1] ^ st->data[2]) == 0xff) {
+			code = RC_SCANCODE_NEC(st->data[1], st->data[3]);
+			proto = RC_PROTO_NEC;
+		} else {
+			code = RC_SCANCODE_NECX(st->data[1] << 8 | st->data[2],
+						st->data[3]);
+			proto = RC_PROTO_NECX;
+		}
+	} else {
+		code = RC_SCANCODE_NEC32(st->data[1] << 24 |
+					 st->data[2] << 16 |
+					 st->data[3] << 8  |
+					 st->data[4]);
+		proto = RC_PROTO_NEC32;
+	}
+
+	rc_keydown(d->rc_dev, proto, code, st->data[5]);
+
+	return 0;
+}
+
+static int az6007_get_rc_config(struct dvb_usb_device *d, struct dvb_usb_rc *rc)
+{
+	pr_debug("Getting az6007 Remote Control properties\n");
+
+	rc->allowed_protos = RC_PROTO_BIT_NEC | RC_PROTO_BIT_NECX |
+						RC_PROTO_BIT_NEC32;
+	rc->query          = az6007_rc_query;
+	rc->interval       = 400;
+
+	return 0;
+}
+#else
+	#define az6007_get_rc_config NULL
+#endif
+
+static int az6007_ci_read_attribute_mem(struct dvb_ca_en50221 *ca,
+					int slot,
+					int address)
+{
+	struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data;
+	struct az6007_device_state *state = d_to_priv(d);
+
+	int ret;
+	u8 req;
+	u16 value;
+	u16 index;
+	int blen;
+	u8 *b;
+
+	if (slot != 0)
+		return -EINVAL;
+
+	b = kmalloc(12, GFP_KERNEL);
+	if (!b)
+		return -ENOMEM;
+
+	mutex_lock(&state->ca_mutex);
+
+	req = 0xC1;
+	value = address;
+	index = 0;
+	blen = 1;
+
+	ret = az6007_read(d, req, value, index, b, blen);
+	if (ret < 0) {
+		pr_warn("usb in operation failed. (%d)\n", ret);
+		ret = -EINVAL;
+	} else {
+		ret = b[0];
+	}
+
+	mutex_unlock(&state->ca_mutex);
+	kfree(b);
+	return ret;
+}
+
+static int az6007_ci_write_attribute_mem(struct dvb_ca_en50221 *ca,
+					 int slot,
+					 int address,
+					 u8 value)
+{
+	struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data;
+	struct az6007_device_state *state = d_to_priv(d);
+
+	int ret;
+	u8 req;
+	u16 value1;
+	u16 index;
+	int blen;
+
+	pr_debug("%s(), slot %d\n", __func__, slot);
+	if (slot != 0)
+		return -EINVAL;
+
+	mutex_lock(&state->ca_mutex);
+	req = 0xC2;
+	value1 = address;
+	index = value;
+	blen = 0;
+
+	ret = az6007_write(d, req, value1, index, NULL, blen);
+	if (ret != 0)
+		pr_warn("usb out operation failed. (%d)\n", ret);
+
+	mutex_unlock(&state->ca_mutex);
+	return ret;
+}
+
+static int az6007_ci_read_cam_control(struct dvb_ca_en50221 *ca,
+				      int slot,
+				      u8 address)
+{
+	struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data;
+	struct az6007_device_state *state = d_to_priv(d);
+
+	int ret;
+	u8 req;
+	u16 value;
+	u16 index;
+	int blen;
+	u8 *b;
+
+	if (slot != 0)
+		return -EINVAL;
+
+	b = kmalloc(12, GFP_KERNEL);
+	if (!b)
+		return -ENOMEM;
+
+	mutex_lock(&state->ca_mutex);
+
+	req = 0xC3;
+	value = address;
+	index = 0;
+	blen = 2;
+
+	ret = az6007_read(d, req, value, index, b, blen);
+	if (ret < 0) {
+		pr_warn("usb in operation failed. (%d)\n", ret);
+		ret = -EINVAL;
+	} else {
+		if (b[0] == 0)
+			pr_warn("Read CI IO error\n");
+
+		ret = b[1];
+		pr_debug("read cam data = %x from 0x%x\n", b[1], value);
+	}
+
+	mutex_unlock(&state->ca_mutex);
+	kfree(b);
+	return ret;
+}
+
+static int az6007_ci_write_cam_control(struct dvb_ca_en50221 *ca,
+				       int slot,
+				       u8 address,
+				       u8 value)
+{
+	struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data;
+	struct az6007_device_state *state = d_to_priv(d);
+
+	int ret;
+	u8 req;
+	u16 value1;
+	u16 index;
+	int blen;
+
+	if (slot != 0)
+		return -EINVAL;
+
+	mutex_lock(&state->ca_mutex);
+	req = 0xC4;
+	value1 = address;
+	index = value;
+	blen = 0;
+
+	ret = az6007_write(d, req, value1, index, NULL, blen);
+	if (ret != 0) {
+		pr_warn("usb out operation failed. (%d)\n", ret);
+		goto failed;
+	}
+
+failed:
+	mutex_unlock(&state->ca_mutex);
+	return ret;
+}
+
+static int CI_CamReady(struct dvb_ca_en50221 *ca, int slot)
+{
+	struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data;
+
+	int ret;
+	u8 req;
+	u16 value;
+	u16 index;
+	int blen;
+	u8 *b;
+
+	b = kmalloc(12, GFP_KERNEL);
+	if (!b)
+		return -ENOMEM;
+
+	req = 0xC8;
+	value = 0;
+	index = 0;
+	blen = 1;
+
+	ret = az6007_read(d, req, value, index, b, blen);
+	if (ret < 0) {
+		pr_warn("usb in operation failed. (%d)\n", ret);
+		ret = -EIO;
+	} else{
+		ret = b[0];
+	}
+	kfree(b);
+	return ret;
+}
+
+static int az6007_ci_slot_reset(struct dvb_ca_en50221 *ca, int slot)
+{
+	struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data;
+	struct az6007_device_state *state = d_to_priv(d);
+
+	int ret, i;
+	u8 req;
+	u16 value;
+	u16 index;
+	int blen;
+
+	mutex_lock(&state->ca_mutex);
+
+	req = 0xC6;
+	value = 1;
+	index = 0;
+	blen = 0;
+
+	ret = az6007_write(d, req, value, index, NULL, blen);
+	if (ret != 0) {
+		pr_warn("usb out operation failed. (%d)\n", ret);
+		goto failed;
+	}
+
+	msleep(500);
+	req = 0xC6;
+	value = 0;
+	index = 0;
+	blen = 0;
+
+	ret = az6007_write(d, req, value, index, NULL, blen);
+	if (ret != 0) {
+		pr_warn("usb out operation failed. (%d)\n", ret);
+		goto failed;
+	}
+
+	for (i = 0; i < 15; i++) {
+		msleep(100);
+
+		if (CI_CamReady(ca, slot)) {
+			pr_debug("CAM Ready\n");
+			break;
+		}
+	}
+	msleep(5000);
+
+failed:
+	mutex_unlock(&state->ca_mutex);
+	return ret;
+}
+
+static int az6007_ci_slot_shutdown(struct dvb_ca_en50221 *ca, int slot)
+{
+	return 0;
+}
+
+static int az6007_ci_slot_ts_enable(struct dvb_ca_en50221 *ca, int slot)
+{
+	struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data;
+	struct az6007_device_state *state = d_to_priv(d);
+
+	int ret;
+	u8 req;
+	u16 value;
+	u16 index;
+	int blen;
+
+	pr_debug("%s()\n", __func__);
+	mutex_lock(&state->ca_mutex);
+	req = 0xC7;
+	value = 1;
+	index = 0;
+	blen = 0;
+
+	ret = az6007_write(d, req, value, index, NULL, blen);
+	if (ret != 0) {
+		pr_warn("usb out operation failed. (%d)\n", ret);
+		goto failed;
+	}
+
+failed:
+	mutex_unlock(&state->ca_mutex);
+	return ret;
+}
+
+static int az6007_ci_poll_slot_status(struct dvb_ca_en50221 *ca, int slot, int open)
+{
+	struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data;
+	struct az6007_device_state *state = d_to_priv(d);
+	int ret;
+	u8 req;
+	u16 value;
+	u16 index;
+	int blen;
+	u8 *b;
+
+	b = kmalloc(12, GFP_KERNEL);
+	if (!b)
+		return -ENOMEM;
+	mutex_lock(&state->ca_mutex);
+
+	req = 0xC5;
+	value = 0;
+	index = 0;
+	blen = 1;
+
+	ret = az6007_read(d, req, value, index, b, blen);
+	if (ret < 0) {
+		pr_warn("usb in operation failed. (%d)\n", ret);
+		ret = -EIO;
+	} else
+		ret = 0;
+
+	if (!ret && b[0] == 1) {
+		ret = DVB_CA_EN50221_POLL_CAM_PRESENT |
+		      DVB_CA_EN50221_POLL_CAM_READY;
+	}
+
+	mutex_unlock(&state->ca_mutex);
+	kfree(b);
+	return ret;
+}
+
+
+static void az6007_ci_uninit(struct dvb_usb_device *d)
+{
+	struct az6007_device_state *state;
+
+	pr_debug("%s()\n", __func__);
+
+	if (NULL == d)
+		return;
+
+	state = d_to_priv(d);
+	if (NULL == state)
+		return;
+
+	if (NULL == state->ca.data)
+		return;
+
+	dvb_ca_en50221_release(&state->ca);
+
+	memset(&state->ca, 0, sizeof(state->ca));
+}
+
+
+static int az6007_ci_init(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct az6007_device_state *state = adap_to_priv(adap);
+	int ret;
+
+	pr_debug("%s()\n", __func__);
+
+	mutex_init(&state->ca_mutex);
+	state->ca.owner			= THIS_MODULE;
+	state->ca.read_attribute_mem	= az6007_ci_read_attribute_mem;
+	state->ca.write_attribute_mem	= az6007_ci_write_attribute_mem;
+	state->ca.read_cam_control	= az6007_ci_read_cam_control;
+	state->ca.write_cam_control	= az6007_ci_write_cam_control;
+	state->ca.slot_reset		= az6007_ci_slot_reset;
+	state->ca.slot_shutdown		= az6007_ci_slot_shutdown;
+	state->ca.slot_ts_enable	= az6007_ci_slot_ts_enable;
+	state->ca.poll_slot_status	= az6007_ci_poll_slot_status;
+	state->ca.data			= d;
+
+	ret = dvb_ca_en50221_init(&adap->dvb_adap,
+				  &state->ca,
+				  0, /* flags */
+				  1);/* n_slots */
+	if (ret != 0) {
+		pr_err("Cannot initialize CI: Error %d.\n", ret);
+		memset(&state->ca, 0, sizeof(state->ca));
+		return ret;
+	}
+
+	pr_debug("CI initialized.\n");
+
+	return 0;
+}
+
+static int az6007_read_mac_addr(struct dvb_usb_adapter *adap, u8 mac[6])
+{
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct az6007_device_state *st = adap_to_priv(adap);
+	int ret;
+
+	ret = az6007_read(d, AZ6007_READ_DATA, 6, 0, st->data, 6);
+	memcpy(mac, st->data, 6);
+
+	if (ret > 0)
+		pr_debug("%s: mac is %pM\n", __func__, mac);
+
+	return ret;
+}
+
+static int az6007_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct az6007_device_state *st = adap_to_priv(adap);
+	struct dvb_usb_device *d = adap_to_d(adap);
+
+	pr_debug("attaching demod drxk\n");
+
+	adap->fe[0] = dvb_attach(drxk_attach, &terratec_h7_drxk,
+				 &d->i2c_adap);
+	if (!adap->fe[0])
+		return -EINVAL;
+
+	adap->fe[0]->sec_priv = adap;
+	st->gate_ctrl = adap->fe[0]->ops.i2c_gate_ctrl;
+	adap->fe[0]->ops.i2c_gate_ctrl = drxk_gate_ctrl;
+
+	az6007_ci_init(adap);
+
+	return 0;
+}
+
+static int az6007_cablestar_hdci_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct az6007_device_state *st = adap_to_priv(adap);
+	struct dvb_usb_device *d = adap_to_d(adap);
+
+	pr_debug("attaching demod drxk\n");
+
+	adap->fe[0] = dvb_attach(drxk_attach, &cablestar_hdci_drxk,
+				 &d->i2c_adap);
+	if (!adap->fe[0])
+		return -EINVAL;
+
+	adap->fe[0]->sec_priv = adap;
+	st->gate_ctrl = adap->fe[0]->ops.i2c_gate_ctrl;
+	adap->fe[0]->ops.i2c_gate_ctrl = drxk_gate_ctrl;
+
+	az6007_ci_init(adap);
+
+	return 0;
+}
+
+static int az6007_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *d = adap_to_d(adap);
+
+	pr_debug("attaching tuner mt2063\n");
+
+	/* Attach mt2063 to DVB-C frontend */
+	if (adap->fe[0]->ops.i2c_gate_ctrl)
+		adap->fe[0]->ops.i2c_gate_ctrl(adap->fe[0], 1);
+	if (!dvb_attach(mt2063_attach, adap->fe[0],
+			&az6007_mt2063_config,
+			&d->i2c_adap))
+		return -EINVAL;
+
+	if (adap->fe[0]->ops.i2c_gate_ctrl)
+		adap->fe[0]->ops.i2c_gate_ctrl(adap->fe[0], 0);
+
+	return 0;
+}
+
+static int az6007_power_ctrl(struct dvb_usb_device *d, int onoff)
+{
+	struct az6007_device_state *state = d_to_priv(d);
+	int ret;
+
+	pr_debug("%s()\n", __func__);
+
+	if (!state->warm) {
+		mutex_init(&state->mutex);
+
+		ret = az6007_write(d, AZ6007_POWER, 0, 2, NULL, 0);
+		if (ret < 0)
+			return ret;
+		msleep(60);
+		ret = az6007_write(d, AZ6007_POWER, 1, 4, NULL, 0);
+		if (ret < 0)
+			return ret;
+		msleep(100);
+		ret = az6007_write(d, AZ6007_POWER, 1, 3, NULL, 0);
+		if (ret < 0)
+			return ret;
+		msleep(20);
+		ret = az6007_write(d, AZ6007_POWER, 1, 4, NULL, 0);
+		if (ret < 0)
+			return ret;
+
+		msleep(400);
+		ret = az6007_write(d, FX2_SCON1, 0, 3, NULL, 0);
+		if (ret < 0)
+			return ret;
+		msleep(150);
+		ret = az6007_write(d, FX2_SCON1, 1, 3, NULL, 0);
+		if (ret < 0)
+			return ret;
+		msleep(430);
+		ret = az6007_write(d, AZ6007_POWER, 0, 0, NULL, 0);
+		if (ret < 0)
+			return ret;
+
+		state->warm = true;
+
+		return 0;
+	}
+
+	if (!onoff)
+		return 0;
+
+	az6007_write(d, AZ6007_POWER, 0, 0, NULL, 0);
+	az6007_write(d, AZ6007_TS_THROUGH, 0, 0, NULL, 0);
+
+	return 0;
+}
+
+/* I2C */
+static int az6007_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[],
+			   int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	struct az6007_device_state *st = d_to_priv(d);
+	int i, j, len;
+	int ret = 0;
+	u16 index;
+	u16 value;
+	int length;
+	u8 req, addr;
+
+	if (mutex_lock_interruptible(&st->mutex) < 0)
+		return -EAGAIN;
+
+	for (i = 0; i < num; i++) {
+		addr = msgs[i].addr << 1;
+		if (((i + 1) < num)
+		    && (msgs[i].len == 1)
+		    && ((msgs[i].flags & I2C_M_RD) != I2C_M_RD)
+		    && (msgs[i + 1].flags & I2C_M_RD)
+		    && (msgs[i].addr == msgs[i + 1].addr)) {
+			/*
+			 * A write + read xfer for the same address, where
+			 * the first xfer has just 1 byte length.
+			 * Need to join both into one operation
+			 */
+			if (az6007_xfer_debug)
+				printk(KERN_DEBUG "az6007: I2C W/R addr=0x%x len=%d/%d\n",
+				       addr, msgs[i].len, msgs[i + 1].len);
+			req = AZ6007_I2C_RD;
+			index = msgs[i].buf[0];
+			value = addr | (1 << 8);
+			length = 6 + msgs[i + 1].len;
+			len = msgs[i + 1].len;
+			ret = __az6007_read(d->udev, req, value, index,
+					    st->data, length);
+			if (ret >= len) {
+				for (j = 0; j < len; j++)
+					msgs[i + 1].buf[j] = st->data[j + 5];
+			} else
+				ret = -EIO;
+			i++;
+		} else if (!(msgs[i].flags & I2C_M_RD)) {
+			/* write bytes */
+			if (az6007_xfer_debug)
+				printk(KERN_DEBUG "az6007: I2C W addr=0x%x len=%d\n",
+				       addr, msgs[i].len);
+			req = AZ6007_I2C_WR;
+			index = msgs[i].buf[0];
+			value = addr | (1 << 8);
+			length = msgs[i].len - 1;
+			len = msgs[i].len - 1;
+			for (j = 0; j < len; j++)
+				st->data[j] = msgs[i].buf[j + 1];
+			ret =  __az6007_write(d->udev, req, value, index,
+					      st->data, length);
+		} else {
+			/* read bytes */
+			if (az6007_xfer_debug)
+				printk(KERN_DEBUG "az6007: I2C R addr=0x%x len=%d\n",
+				       addr, msgs[i].len);
+			req = AZ6007_I2C_RD;
+			index = msgs[i].buf[0];
+			value = addr;
+			length = msgs[i].len + 6;
+			len = msgs[i].len;
+			ret = __az6007_read(d->udev, req, value, index,
+					    st->data, length);
+			for (j = 0; j < len; j++)
+				msgs[i].buf[j] = st->data[j + 5];
+		}
+		if (ret < 0)
+			goto err;
+	}
+err:
+	mutex_unlock(&st->mutex);
+
+	if (ret < 0) {
+		pr_info("%s ERROR: %i\n", __func__, ret);
+		return ret;
+	}
+	return num;
+}
+
+static u32 az6007_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm az6007_i2c_algo = {
+	.master_xfer = az6007_i2c_xfer,
+	.functionality = az6007_i2c_func,
+};
+
+static int az6007_identify_state(struct dvb_usb_device *d, const char **name)
+{
+	int ret;
+	u8 *mac;
+
+	pr_debug("Identifying az6007 state\n");
+
+	mac = kmalloc(6, GFP_ATOMIC);
+	if (!mac)
+		return -ENOMEM;
+
+	/* Try to read the mac address */
+	ret = __az6007_read(d->udev, AZ6007_READ_DATA, 6, 0, mac, 6);
+	if (ret == 6)
+		ret = WARM;
+	else
+		ret = COLD;
+
+	kfree(mac);
+
+	if (ret == COLD) {
+		__az6007_write(d->udev, 0x09, 1, 0, NULL, 0);
+		__az6007_write(d->udev, 0x00, 0, 0, NULL, 0);
+		__az6007_write(d->udev, 0x00, 0, 0, NULL, 0);
+	}
+
+	pr_debug("Device is on %s state\n",
+		 ret == WARM ? "warm" : "cold");
+	return ret;
+}
+
+static void az6007_usb_disconnect(struct usb_interface *intf)
+{
+	struct dvb_usb_device *d = usb_get_intfdata(intf);
+	az6007_ci_uninit(d);
+	dvb_usbv2_disconnect(intf);
+}
+
+static int az6007_download_firmware(struct dvb_usb_device *d,
+	const struct firmware *fw)
+{
+	pr_debug("Loading az6007 firmware\n");
+
+	return cypress_load_firmware(d->udev, fw, CYPRESS_FX2);
+}
+
+/* DVB USB Driver stuff */
+static struct dvb_usb_device_properties az6007_props = {
+	.driver_name         = KBUILD_MODNAME,
+	.owner               = THIS_MODULE,
+	.firmware            = AZ6007_FIRMWARE,
+
+	.adapter_nr          = adapter_nr,
+	.size_of_priv        = sizeof(struct az6007_device_state),
+	.i2c_algo            = &az6007_i2c_algo,
+	.tuner_attach        = az6007_tuner_attach,
+	.frontend_attach     = az6007_frontend_attach,
+	.streaming_ctrl      = az6007_streaming_ctrl,
+	.get_rc_config       = az6007_get_rc_config,
+	.read_mac_address    = az6007_read_mac_addr,
+	.download_firmware   = az6007_download_firmware,
+	.identify_state	     = az6007_identify_state,
+	.power_ctrl          = az6007_power_ctrl,
+	.num_adapters        = 1,
+	.adapter             = {
+		{ .stream = DVB_USB_STREAM_BULK(0x02, 10, 4096), }
+	}
+};
+
+static struct dvb_usb_device_properties az6007_cablestar_hdci_props = {
+	.driver_name         = KBUILD_MODNAME,
+	.owner               = THIS_MODULE,
+	.firmware            = AZ6007_FIRMWARE,
+
+	.adapter_nr          = adapter_nr,
+	.size_of_priv        = sizeof(struct az6007_device_state),
+	.i2c_algo            = &az6007_i2c_algo,
+	.tuner_attach        = az6007_tuner_attach,
+	.frontend_attach     = az6007_cablestar_hdci_frontend_attach,
+	.streaming_ctrl      = az6007_streaming_ctrl,
+/* ditch get_rc_config as it can't work (TS35 remote, I believe it's rc5) */
+	.get_rc_config       = NULL,
+	.read_mac_address    = az6007_read_mac_addr,
+	.download_firmware   = az6007_download_firmware,
+	.identify_state	     = az6007_identify_state,
+	.power_ctrl          = az6007_power_ctrl,
+	.num_adapters        = 1,
+	.adapter             = {
+		{ .stream = DVB_USB_STREAM_BULK(0x02, 10, 4096), }
+	}
+};
+
+static const struct usb_device_id az6007_usb_table[] = {
+	{DVB_USB_DEVICE(USB_VID_AZUREWAVE, USB_PID_AZUREWAVE_6007,
+		&az6007_props, "Azurewave 6007", RC_MAP_EMPTY)},
+	{DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_H7,
+		&az6007_props, "Terratec H7", RC_MAP_NEC_TERRATEC_CINERGY_XS)},
+	{DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_H7_2,
+		&az6007_props, "Terratec H7", RC_MAP_NEC_TERRATEC_CINERGY_XS)},
+	{DVB_USB_DEVICE(USB_VID_TECHNISAT, USB_PID_TECHNISAT_USB2_CABLESTAR_HDCI,
+		&az6007_cablestar_hdci_props, "Technisat CableStar Combo HD CI", RC_MAP_EMPTY)},
+	{0},
+};
+
+MODULE_DEVICE_TABLE(usb, az6007_usb_table);
+
+static int az6007_suspend(struct usb_interface *intf, pm_message_t msg)
+{
+	struct dvb_usb_device *d = usb_get_intfdata(intf);
+
+	az6007_ci_uninit(d);
+	return dvb_usbv2_suspend(intf, msg);
+}
+
+static int az6007_resume(struct usb_interface *intf)
+{
+	struct dvb_usb_device *d = usb_get_intfdata(intf);
+	struct dvb_usb_adapter *adap = &d->adapter[0];
+
+	az6007_ci_init(adap);
+	return dvb_usbv2_resume(intf);
+}
+
+/* usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver az6007_usb_driver = {
+	.name		= KBUILD_MODNAME,
+	.id_table	= az6007_usb_table,
+	.probe		= dvb_usbv2_probe,
+	.disconnect	= az6007_usb_disconnect,
+	.no_dynamic_id	= 1,
+	.soft_unbind	= 1,
+	/*
+	 * FIXME: need to implement reset_resume, likely with
+	 * dvb-usb-v2 core support
+	 */
+	.suspend	= az6007_suspend,
+	.resume		= az6007_resume,
+};
+
+module_usb_driver(az6007_usb_driver);
+
+MODULE_AUTHOR("Henry Wang <Henry.wang@AzureWave.com>");
+MODULE_AUTHOR("Mauro Carvalho Chehab");
+MODULE_DESCRIPTION("Driver for AzureWave 6007 DVB-C/T USB2.0 and clones");
+MODULE_VERSION("2.0");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(AZ6007_FIRMWARE);
diff --git a/drivers/media/usb/dvb-usb-v2/ce6230.c b/drivers/media/usb/dvb-usb-v2/ce6230.c
new file mode 100644
index 0000000..e596031
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/ce6230.c
@@ -0,0 +1,288 @@
+/*
+ * Intel CE6230 DVB USB driver
+ *
+ * Copyright (C) 2009 Antti Palosaari <crope@iki.fi>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ */
+
+#include "ce6230.h"
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static int ce6230_ctrl_msg(struct dvb_usb_device *d, struct usb_req *req)
+{
+	int ret;
+	unsigned int pipe;
+	u8 request;
+	u8 requesttype;
+	u16 value;
+	u16 index;
+	u8 *buf;
+
+	request = req->cmd;
+	value = req->value;
+	index = req->index;
+
+	switch (req->cmd) {
+	case I2C_READ:
+	case DEMOD_READ:
+	case REG_READ:
+		requesttype = (USB_TYPE_VENDOR | USB_DIR_IN);
+		break;
+	case I2C_WRITE:
+	case DEMOD_WRITE:
+	case REG_WRITE:
+		requesttype = (USB_TYPE_VENDOR | USB_DIR_OUT);
+		break;
+	default:
+		dev_err(&d->udev->dev, "%s: unknown command=%02x\n",
+				KBUILD_MODNAME, req->cmd);
+		ret = -EINVAL;
+		goto error;
+	}
+
+	buf = kmalloc(req->data_len, GFP_KERNEL);
+	if (!buf) {
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	if (requesttype == (USB_TYPE_VENDOR | USB_DIR_OUT)) {
+		/* write */
+		memcpy(buf, req->data, req->data_len);
+		pipe = usb_sndctrlpipe(d->udev, 0);
+	} else {
+		/* read */
+		pipe = usb_rcvctrlpipe(d->udev, 0);
+	}
+
+	msleep(1); /* avoid I2C errors */
+
+	ret = usb_control_msg(d->udev, pipe, request, requesttype, value, index,
+			buf, req->data_len, CE6230_USB_TIMEOUT);
+
+	dvb_usb_dbg_usb_control_msg(d->udev, request, requesttype, value, index,
+			buf, req->data_len);
+
+	if (ret < 0)
+		dev_err(&d->udev->dev, "%s: usb_control_msg() failed=%d\n",
+				KBUILD_MODNAME, ret);
+	else
+		ret = 0;
+
+	/* read request, copy returned data to return buf */
+	if (!ret && requesttype == (USB_TYPE_VENDOR | USB_DIR_IN))
+		memcpy(req->data, buf, req->data_len);
+
+	kfree(buf);
+error:
+	return ret;
+}
+
+/* I2C */
+static struct zl10353_config ce6230_zl10353_config;
+
+static int ce6230_i2c_master_xfer(struct i2c_adapter *adap,
+		struct i2c_msg msg[], int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	int ret = 0, i = 0;
+	struct usb_req req;
+
+	if (num > 2)
+		return -EOPNOTSUPP;
+
+	memset(&req, 0, sizeof(req));
+
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	while (i < num) {
+		if (num > i + 1 && (msg[i+1].flags & I2C_M_RD)) {
+			if (msg[i].addr ==
+				ce6230_zl10353_config.demod_address) {
+				req.cmd = DEMOD_READ;
+				req.value = msg[i].addr >> 1;
+				req.index = msg[i].buf[0];
+				req.data_len = msg[i+1].len;
+				req.data = &msg[i+1].buf[0];
+				ret = ce6230_ctrl_msg(d, &req);
+			} else {
+				dev_err(&d->udev->dev, "%s: I2C read not " \
+						"implemented\n",
+						KBUILD_MODNAME);
+				ret = -EOPNOTSUPP;
+			}
+			i += 2;
+		} else {
+			if (msg[i].addr ==
+				ce6230_zl10353_config.demod_address) {
+				req.cmd = DEMOD_WRITE;
+				req.value = msg[i].addr >> 1;
+				req.index = msg[i].buf[0];
+				req.data_len = msg[i].len-1;
+				req.data = &msg[i].buf[1];
+				ret = ce6230_ctrl_msg(d, &req);
+			} else {
+				req.cmd = I2C_WRITE;
+				req.value = 0x2000 + (msg[i].addr >> 1);
+				req.index = 0x0000;
+				req.data_len = msg[i].len;
+				req.data = &msg[i].buf[0];
+				ret = ce6230_ctrl_msg(d, &req);
+			}
+			i += 1;
+		}
+		if (ret)
+			break;
+	}
+
+	mutex_unlock(&d->i2c_mutex);
+	return ret ? ret : i;
+}
+
+static u32 ce6230_i2c_functionality(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm ce6230_i2c_algorithm = {
+	.master_xfer   = ce6230_i2c_master_xfer,
+	.functionality = ce6230_i2c_functionality,
+};
+
+/* Callbacks for DVB USB */
+static struct zl10353_config ce6230_zl10353_config = {
+	.demod_address = 0x1e,
+	.adc_clock = 450000,
+	.if2 = 45700,
+	.no_tuner = 1,
+	.parallel_ts = 1,
+	.clock_ctl_1 = 0x34,
+	.pll_0 = 0x0e,
+};
+
+static int ce6230_zl10353_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *d = adap_to_d(adap);
+
+	dev_dbg(&d->udev->dev, "%s:\n", __func__);
+
+	adap->fe[0] = dvb_attach(zl10353_attach, &ce6230_zl10353_config,
+			&d->i2c_adap);
+	if (adap->fe[0] == NULL)
+		return -ENODEV;
+
+	return 0;
+}
+
+static struct mxl5005s_config ce6230_mxl5003s_config = {
+	.i2c_address     = 0xc6,
+	.if_freq         = IF_FREQ_4570000HZ,
+	.xtal_freq       = CRYSTAL_FREQ_16000000HZ,
+	.agc_mode        = MXL_SINGLE_AGC,
+	.tracking_filter = MXL_TF_DEFAULT,
+	.rssi_enable     = MXL_RSSI_ENABLE,
+	.cap_select      = MXL_CAP_SEL_ENABLE,
+	.div_out         = MXL_DIV_OUT_4,
+	.clock_out       = MXL_CLOCK_OUT_DISABLE,
+	.output_load     = MXL5005S_IF_OUTPUT_LOAD_200_OHM,
+	.top		 = MXL5005S_TOP_25P2,
+	.mod_mode        = MXL_DIGITAL_MODE,
+	.if_mode         = MXL_ZERO_IF,
+	.AgcMasterByte   = 0x00,
+};
+
+static int ce6230_mxl5003s_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *d = adap_to_d(adap);
+	int ret;
+
+	dev_dbg(&d->udev->dev, "%s:\n", __func__);
+
+	ret = dvb_attach(mxl5005s_attach, adap->fe[0], &d->i2c_adap,
+			&ce6230_mxl5003s_config) == NULL ? -ENODEV : 0;
+	return ret;
+}
+
+static int ce6230_power_ctrl(struct dvb_usb_device *d, int onoff)
+{
+	int ret;
+
+	dev_dbg(&d->udev->dev, "%s: onoff=%d\n", __func__, onoff);
+
+	/* InterfaceNumber 1 / AlternateSetting 0     idle
+	   InterfaceNumber 1 / AlternateSetting 1     streaming */
+	ret = usb_set_interface(d->udev, 1, onoff);
+	if (ret)
+		dev_err(&d->udev->dev, "%s: usb_set_interface() failed=%d\n",
+				KBUILD_MODNAME, ret);
+
+	return ret;
+}
+
+/* DVB USB Driver stuff */
+static struct dvb_usb_device_properties ce6230_props = {
+	.driver_name = KBUILD_MODNAME,
+	.owner = THIS_MODULE,
+	.adapter_nr = adapter_nr,
+	.bInterfaceNumber = 1,
+
+	.i2c_algo = &ce6230_i2c_algorithm,
+	.power_ctrl = ce6230_power_ctrl,
+	.frontend_attach = ce6230_zl10353_frontend_attach,
+	.tuner_attach = ce6230_mxl5003s_tuner_attach,
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+			.stream = {
+				.type = USB_BULK,
+				.count = 6,
+				.endpoint = 0x82,
+				.u = {
+					.bulk = {
+						.buffersize = (16 * 512),
+					}
+				}
+			},
+		}
+	},
+};
+
+static const struct usb_device_id ce6230_id_table[] = {
+	{ DVB_USB_DEVICE(USB_VID_INTEL, USB_PID_INTEL_CE9500,
+		&ce6230_props, "Intel CE9500 reference design", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_A310,
+		&ce6230_props, "AVerMedia A310 USB 2.0 DVB-T tuner", NULL) },
+	{ }
+};
+MODULE_DEVICE_TABLE(usb, ce6230_id_table);
+
+static struct usb_driver ce6230_usb_driver = {
+	.name = KBUILD_MODNAME,
+	.id_table = ce6230_id_table,
+	.probe = dvb_usbv2_probe,
+	.disconnect = dvb_usbv2_disconnect,
+	.suspend = dvb_usbv2_suspend,
+	.resume = dvb_usbv2_resume,
+	.reset_resume = dvb_usbv2_reset_resume,
+	.no_dynamic_id = 1,
+	.soft_unbind = 1,
+};
+
+module_usb_driver(ce6230_usb_driver);
+
+MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>");
+MODULE_DESCRIPTION("Intel CE6230 driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb-v2/ce6230.h b/drivers/media/usb/dvb-usb-v2/ce6230.h
new file mode 100644
index 0000000..b25b3b9
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/ce6230.h
@@ -0,0 +1,46 @@
+/*
+ * Intel CE6230 DVB USB driver
+ *
+ * Copyright (C) 2009 Antti Palosaari <crope@iki.fi>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ */
+
+#ifndef CE6230_H
+#define CE6230_H
+
+#include "dvb_usb.h"
+#include "zl10353.h"
+#include "mxl5005s.h"
+
+#define CE6230_USB_TIMEOUT 1000
+
+struct usb_req {
+	u8  cmd;       /* [1] */
+	u16 value;     /* [2|3] */
+	u16 index;     /* [4|5] */
+	u16 data_len;  /* [6|7] */
+	u8  *data;
+};
+
+enum ce6230_cmd {
+	CONFIG_READ          = 0xd0, /* rd 0 (unclear) */
+	UNKNOWN_WRITE        = 0xc7, /* wr 7 (unclear) */
+	I2C_READ             = 0xd9, /* rd 9 (unclear) */
+	I2C_WRITE            = 0xca, /* wr a */
+	DEMOD_READ           = 0xdb, /* rd b */
+	DEMOD_WRITE          = 0xcc, /* wr c */
+	REG_READ             = 0xde, /* rd e */
+	REG_WRITE            = 0xcf, /* wr f */
+};
+
+#endif
diff --git a/drivers/media/usb/dvb-usb-v2/dvb_usb.h b/drivers/media/usb/dvb-usb-v2/dvb_usb.h
new file mode 100644
index 0000000..3fd6cc0
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/dvb_usb.h
@@ -0,0 +1,416 @@
+/*
+ * DVB USB framework
+ *
+ * Copyright (C) 2004-6 Patrick Boettcher <patrick.boettcher@posteo.de>
+ * Copyright (C) 2012 Antti Palosaari <crope@iki.fi>
+ *
+ *    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.
+ */
+
+#ifndef DVB_USB_H
+#define DVB_USB_H
+
+#include <linux/usb/input.h>
+#include <linux/firmware.h>
+#include <media/rc-core.h>
+#include <media/media-device.h>
+
+#include <media/dvb_frontend.h>
+#include <media/dvb_demux.h>
+#include <media/dvb_net.h>
+#include <media/dmxdev.h>
+#include <media/dvb-usb-ids.h>
+
+/*
+ * device file: /dev/dvb/adapter[0-1]/frontend[0-2]
+ *
+ * |-- device
+ * |   |-- adapter0
+ * |   |   |-- frontend0
+ * |   |   |-- frontend1
+ * |   |   `-- frontend2
+ * |   `-- adapter1
+ * |       |-- frontend0
+ * |       |-- frontend1
+ * |       `-- frontend2
+ *
+ *
+ * Commonly used variable names:
+ * d = pointer to device (struct dvb_usb_device *)
+ * adap = pointer to adapter (struct dvb_usb_adapter *)
+ * fe = pointer to frontend (struct dvb_frontend *)
+ *
+ * Use macros defined in that file to resolve needed pointers.
+ */
+
+/* helper macros for every DVB USB driver use */
+#define adap_to_d(adap) (container_of(adap, struct dvb_usb_device, \
+		adapter[adap->id]))
+#define adap_to_priv(adap) (adap_to_d(adap)->priv)
+#define fe_to_adap(fe) ((struct dvb_usb_adapter *) ((fe)->dvb->priv))
+#define fe_to_d(fe) (adap_to_d(fe_to_adap(fe)))
+#define fe_to_priv(fe) (fe_to_d(fe)->priv)
+#define d_to_priv(d) (d->priv)
+
+#define dvb_usb_dbg_usb_control_msg(udev, r, t, v, i, b, l) { \
+	char *direction; \
+	if (t == (USB_TYPE_VENDOR | USB_DIR_OUT)) \
+		direction = ">>>"; \
+	else \
+		direction = "<<<"; \
+	dev_dbg(&udev->dev, "%s: %02x %02x %02x %02x %02x %02x %02x %02x " \
+			"%s %*ph\n",  __func__, t, r, v & 0xff, v >> 8, \
+			i & 0xff, i >> 8, l & 0xff, l >> 8, direction, l, b); \
+}
+
+#define DVB_USB_STREAM_BULK(endpoint_, count_, size_) { \
+	.type = USB_BULK, \
+	.count = count_, \
+	.endpoint = endpoint_, \
+	.u = { \
+		.bulk = { \
+			.buffersize = size_, \
+		} \
+	} \
+}
+
+#define DVB_USB_STREAM_ISOC(endpoint_, count_, frames_, size_, interval_) { \
+	.type = USB_ISOC, \
+	.count = count_, \
+	.endpoint = endpoint_, \
+	.u = { \
+		.isoc = { \
+			.framesperurb = frames_, \
+			.framesize = size_,\
+			.interval = interval_, \
+		} \
+	} \
+}
+
+#define DVB_USB_DEVICE(vend, prod, props_, name_, rc) \
+	.match_flags = USB_DEVICE_ID_MATCH_DEVICE, \
+	.idVendor = (vend), \
+	.idProduct = (prod), \
+	.driver_info = (kernel_ulong_t) &((const struct dvb_usb_driver_info) { \
+		.props = (props_), \
+		.name = (name_), \
+		.rc_map = (rc), \
+	})
+
+struct dvb_usb_device;
+struct dvb_usb_adapter;
+
+/**
+ * structure for carrying all needed data from the device driver to the general
+ * dvb usb routines
+ * @name: device name
+ * @rc_map: name of rc codes table
+ * @props: structure containing all device properties
+ */
+struct dvb_usb_driver_info {
+	const char *name;
+	const char *rc_map;
+	const struct dvb_usb_device_properties *props;
+};
+
+/**
+ * structure for remote controller configuration
+ * @map_name: name of rc codes table
+ * @allowed_protos: protocol(s) supported by the driver
+ * @change_protocol: callback to change protocol
+ * @query: called to query an event from the device
+ * @interval: time in ms between two queries
+ * @driver_type: used to point if a device supports raw mode
+ * @bulk_mode: device supports bulk mode for rc (disable polling mode)
+ */
+struct dvb_usb_rc {
+	const char *map_name;
+	u64 allowed_protos;
+	int (*change_protocol)(struct rc_dev *dev, u64 *rc_proto);
+	int (*query) (struct dvb_usb_device *d);
+	unsigned int interval;
+	enum rc_driver_type driver_type;
+	bool bulk_mode;
+};
+
+/**
+ * usb streaming configration for adapter
+ * @type: urb type
+ * @count: count of used urbs
+ * @endpoint: stream usb endpoint number
+ */
+struct usb_data_stream_properties {
+#define USB_BULK  1
+#define USB_ISOC  2
+	u8 type;
+	u8 count;
+	u8 endpoint;
+
+	union {
+		struct {
+			unsigned int buffersize; /* per URB */
+		} bulk;
+		struct {
+			int framesperurb;
+			int framesize;
+			int interval;
+		} isoc;
+	} u;
+};
+
+/**
+ * properties of dvb usb device adapter
+ * @caps: adapter capabilities
+ * @pid_filter_count: pid count of adapter pid-filter
+ * @pid_filter_ctrl: called to enable/disable pid-filter
+ * @pid_filter: called to set/unset pid for filtering
+ * @stream: adapter usb stream configuration
+ */
+#define MAX_NO_OF_FE_PER_ADAP 3
+struct dvb_usb_adapter_properties {
+#define DVB_USB_ADAP_HAS_PID_FILTER               0x01
+#define DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF 0x02
+#define DVB_USB_ADAP_NEED_PID_FILTERING           0x04
+	u8 caps;
+
+	u8 pid_filter_count;
+	int (*pid_filter_ctrl) (struct dvb_usb_adapter *, int);
+	int (*pid_filter) (struct dvb_usb_adapter *, int, u16, int);
+
+	struct usb_data_stream_properties stream;
+};
+
+/**
+ * struct dvb_usb_device_properties - properties of a dvb-usb-device
+ * @driver_name: name of the owning driver module
+ * @owner: owner of the dvb_adapter
+ * @adapter_nr: values from the DVB_DEFINE_MOD_OPT_ADAPTER_NR() macro
+ * @bInterfaceNumber: usb interface number driver binds
+ * @size_of_priv: bytes allocated for the driver private data
+ * @generic_bulk_ctrl_endpoint: bulk control endpoint number for sent
+ * @generic_bulk_ctrl_endpoint_response: bulk control endpoint number for
+ *  receive
+ * @generic_bulk_ctrl_delay: delay between bulk control sent and receive message
+ * @probe: like probe on driver model
+ * @disconnect: like disconnect on driver model
+ * @identify_state: called to determine the firmware state (cold or warm) and
+ *  return possible firmware file name to be loaded
+ * @firmware: name of the firmware file to be loaded
+ * @download_firmware: called to download the firmware
+ * @i2c_algo: i2c_algorithm if the device has i2c-adapter
+ * @num_adapters: dvb usb device adapter count
+ * @get_adapter_count: called to resolve adapter count
+ * @adapter: array of all adapter properties of device
+ * @power_ctrl: called to enable/disable power of the device
+ * @read_config: called to resolve device configuration
+ * @read_mac_address: called to resolve adapter mac-address
+ * @frontend_attach: called to attach the possible frontends
+ * @frontend_detach: called to detach the possible frontends
+ * @tuner_attach: called to attach the possible tuners
+ * @frontend_ctrl: called to power on/off active frontend
+ * @streaming_ctrl: called to start/stop the usb streaming of adapter
+ * @init: called after adapters are created in order to finalize device
+ *  configuration
+ * @exit: called when driver is unloaded
+ * @get_rc_config: called to resolve used remote controller configuration
+ * @get_stream_config: called to resolve input and output stream configuration
+ *  of the adapter just before streaming is started. input stream is transport
+ *  stream from the demodulator and output stream is usb stream to host.
+ */
+#define MAX_NO_OF_ADAPTER_PER_DEVICE 2
+struct dvb_usb_device_properties {
+	const char *driver_name;
+	struct module *owner;
+	short *adapter_nr;
+
+	u8 bInterfaceNumber;
+	unsigned int size_of_priv;
+	u8 generic_bulk_ctrl_endpoint;
+	u8 generic_bulk_ctrl_endpoint_response;
+	unsigned int generic_bulk_ctrl_delay;
+
+	int (*probe)(struct dvb_usb_device *);
+	void (*disconnect)(struct dvb_usb_device *);
+#define WARM                  0
+#define COLD                  1
+	int (*identify_state) (struct dvb_usb_device *, const char **);
+	const char *firmware;
+#define RECONNECTS_USB        1
+	int (*download_firmware) (struct dvb_usb_device *,
+			const struct firmware *);
+
+	struct i2c_algorithm *i2c_algo;
+
+	unsigned int num_adapters;
+	int (*get_adapter_count) (struct dvb_usb_device *);
+	struct dvb_usb_adapter_properties adapter[MAX_NO_OF_ADAPTER_PER_DEVICE];
+	int (*power_ctrl) (struct dvb_usb_device *, int);
+	int (*read_config) (struct dvb_usb_device *d);
+	int (*read_mac_address) (struct dvb_usb_adapter *, u8 []);
+	int (*frontend_attach) (struct dvb_usb_adapter *);
+	int (*frontend_detach)(struct dvb_usb_adapter *);
+	int (*tuner_attach) (struct dvb_usb_adapter *);
+	int (*tuner_detach)(struct dvb_usb_adapter *);
+	int (*frontend_ctrl) (struct dvb_frontend *, int);
+	int (*streaming_ctrl) (struct dvb_frontend *, int);
+	int (*init) (struct dvb_usb_device *);
+	void (*exit) (struct dvb_usb_device *);
+	int (*get_rc_config) (struct dvb_usb_device *, struct dvb_usb_rc *);
+#define DVB_USB_FE_TS_TYPE_188        0
+#define DVB_USB_FE_TS_TYPE_204        1
+#define DVB_USB_FE_TS_TYPE_RAW        2
+	int (*get_stream_config) (struct dvb_frontend *,  u8 *,
+			struct usb_data_stream_properties *);
+};
+
+/**
+ * generic object of an usb stream
+ * @buf_num: number of buffer allocated
+ * @buf_size: size of each buffer in buf_list
+ * @buf_list: array containing all allocate buffers for streaming
+ * @dma_addr: list of dma_addr_t for each buffer in buf_list
+ *
+ * @urbs_initialized: number of URBs initialized
+ * @urbs_submitted: number of URBs submitted
+ */
+#define MAX_NO_URBS_FOR_DATA_STREAM 10
+struct usb_data_stream {
+	struct usb_device *udev;
+	struct usb_data_stream_properties props;
+
+#define USB_STATE_INIT    0x00
+#define USB_STATE_URB_BUF 0x01
+	u8 state;
+
+	void (*complete) (struct usb_data_stream *, u8 *, size_t);
+
+	struct urb    *urb_list[MAX_NO_URBS_FOR_DATA_STREAM];
+	int            buf_num;
+	unsigned long  buf_size;
+	u8            *buf_list[MAX_NO_URBS_FOR_DATA_STREAM];
+	dma_addr_t     dma_addr[MAX_NO_URBS_FOR_DATA_STREAM];
+
+	int urbs_initialized;
+	int urbs_submitted;
+
+	void *user_priv;
+};
+
+/**
+ * dvb adapter object on dvb usb device
+ * @props: pointer to adapter properties
+ * @stream: adapter the usb data stream
+ * @id: index of this adapter (starting with 0)
+ * @ts_type: transport stream, input stream, type
+ * @suspend_resume_active: set when there is ongoing suspend / resume
+ * @pid_filtering: is hardware pid_filtering used or not
+ * @feed_count: current feed count
+ * @max_feed_count: maimum feed count device can handle
+ * @dvb_adap: adapter dvb_adapter
+ * @dmxdev: adapter dmxdev
+ * @demux: adapter software demuxer
+ * @dvb_net: adapter dvb_net interfaces
+ * @sync_mutex: mutex used to sync control and streaming of the adapter
+ * @fe: adapter frontends
+ * @fe_init: rerouted frontend-init function
+ * @fe_sleep: rerouted frontend-sleep function
+ */
+struct dvb_usb_adapter {
+	const struct dvb_usb_adapter_properties *props;
+	struct usb_data_stream stream;
+	u8 id;
+	u8 ts_type;
+	bool suspend_resume_active;
+	bool pid_filtering;
+	u8 feed_count;
+	u8 max_feed_count;
+	s8 active_fe;
+#define ADAP_INIT                0
+#define ADAP_SLEEP               1
+#define ADAP_STREAMING           2
+	unsigned long state_bits;
+
+	/* dvb */
+	struct dvb_adapter   dvb_adap;
+	struct dmxdev        dmxdev;
+	struct dvb_demux     demux;
+	struct dvb_net       dvb_net;
+
+	struct dvb_frontend *fe[MAX_NO_OF_FE_PER_ADAP];
+	int (*fe_init[MAX_NO_OF_FE_PER_ADAP]) (struct dvb_frontend *);
+	int (*fe_sleep[MAX_NO_OF_FE_PER_ADAP]) (struct dvb_frontend *);
+};
+
+/**
+ * dvb usb device object
+ * @props: device properties
+ * @name: device name
+ * @rc_map: name of rc codes table
+ * @rc_polling_active: set when RC polling is active
+ * @intf: pointer to the device's struct usb_interface
+ * @udev: pointer to the device's struct usb_device
+ * @rc: remote controller configuration
+ * @powered: indicated whether the device is power or not
+ * @usb_mutex: mutex for usb control messages
+ * @i2c_mutex: mutex for i2c-transfers
+ * @i2c_adap: device's i2c-adapter
+ * @rc_dev: rc device for the remote control
+ * @rc_query_work: work for polling remote
+ * @priv: private data of the actual driver (allocate by dvb usb, size defined
+ *  in size_of_priv of dvb_usb_properties).
+ */
+struct dvb_usb_device {
+	const struct dvb_usb_device_properties *props;
+	const char *name;
+	const char *rc_map;
+	bool rc_polling_active;
+	struct usb_interface *intf;
+	struct usb_device *udev;
+	struct dvb_usb_rc rc;
+	int powered;
+
+	/* locking */
+	struct mutex usb_mutex;
+
+	/* i2c */
+	struct mutex i2c_mutex;
+	struct i2c_adapter i2c_adap;
+
+	struct dvb_usb_adapter adapter[MAX_NO_OF_ADAPTER_PER_DEVICE];
+
+	/* remote control */
+	struct rc_dev *rc_dev;
+	char rc_phys[64];
+	struct delayed_work rc_query_work;
+
+	void *priv;
+};
+
+extern int dvb_usbv2_probe(struct usb_interface *,
+		const struct usb_device_id *);
+extern void dvb_usbv2_disconnect(struct usb_interface *);
+extern int dvb_usbv2_suspend(struct usb_interface *, pm_message_t);
+extern int dvb_usbv2_resume(struct usb_interface *);
+extern int dvb_usbv2_reset_resume(struct usb_interface *);
+
+/* the generic read/write method for device control */
+extern int dvb_usbv2_generic_rw(struct dvb_usb_device *, u8 *, u16, u8 *, u16);
+extern int dvb_usbv2_generic_write(struct dvb_usb_device *, u8 *, u16);
+/* caller must hold lock when locked versions are called */
+extern int dvb_usbv2_generic_rw_locked(struct dvb_usb_device *,
+		u8 *, u16, u8 *, u16);
+extern int dvb_usbv2_generic_write_locked(struct dvb_usb_device *, u8 *, u16);
+
+#endif
diff --git a/drivers/media/usb/dvb-usb-v2/dvb_usb_common.h b/drivers/media/usb/dvb-usb-v2/dvb_usb_common.h
new file mode 100644
index 0000000..a1622bd
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/dvb_usb_common.h
@@ -0,0 +1,35 @@
+/*
+ * DVB USB framework
+ *
+ * Copyright (C) 2004-6 Patrick Boettcher <patrick.boettcher@posteo.de>
+ * Copyright (C) 2012 Antti Palosaari <crope@iki.fi>
+ *
+ *    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.
+ */
+
+#ifndef DVB_USB_COMMON_H
+#define DVB_USB_COMMON_H
+
+#include "dvb_usb.h"
+
+/* commonly used  methods */
+extern int usb_urb_initv2(struct usb_data_stream *stream,
+		const struct usb_data_stream_properties *props);
+extern int usb_urb_exitv2(struct usb_data_stream *stream);
+extern int usb_urb_submitv2(struct usb_data_stream *stream,
+		struct usb_data_stream_properties *props);
+extern int usb_urb_killv2(struct usb_data_stream *stream);
+
+#endif
diff --git a/drivers/media/usb/dvb-usb-v2/dvb_usb_core.c b/drivers/media/usb/dvb-usb-v2/dvb_usb_core.c
new file mode 100644
index 0000000..955318a
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/dvb_usb_core.c
@@ -0,0 +1,1140 @@
+/*
+ * DVB USB framework
+ *
+ * Copyright (C) 2004-6 Patrick Boettcher <patrick.boettcher@posteo.de>
+ * Copyright (C) 2012 Antti Palosaari <crope@iki.fi>
+ *
+ *    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 "dvb_usb_common.h"
+#include <media/media-device.h>
+
+static int dvb_usbv2_disable_rc_polling;
+module_param_named(disable_rc_polling, dvb_usbv2_disable_rc_polling, int, 0644);
+MODULE_PARM_DESC(disable_rc_polling,
+		"disable remote control polling (default: 0)");
+static int dvb_usb_force_pid_filter_usage;
+module_param_named(force_pid_filter_usage, dvb_usb_force_pid_filter_usage,
+		int, 0444);
+MODULE_PARM_DESC(force_pid_filter_usage,
+		"force all DVB USB devices to use a PID filter, if any (default: 0)");
+
+static int dvb_usbv2_download_firmware(struct dvb_usb_device *d,
+		const char *name)
+{
+	int ret;
+	const struct firmware *fw;
+	dev_dbg(&d->udev->dev, "%s:\n", __func__);
+
+	if (!d->props->download_firmware) {
+		ret = -EINVAL;
+		goto err;
+	}
+
+	ret = request_firmware(&fw, name, &d->udev->dev);
+	if (ret < 0) {
+		dev_err(&d->udev->dev,
+				"%s: Did not find the firmware file '%s' (status %d). You can use <kernel_dir>/scripts/get_dvb_firmware to get the firmware\n",
+				KBUILD_MODNAME, name, ret);
+		goto err;
+	}
+
+	dev_info(&d->udev->dev, "%s: downloading firmware from file '%s'\n",
+			KBUILD_MODNAME, name);
+
+	ret = d->props->download_firmware(d, fw);
+	release_firmware(fw);
+	if (ret < 0)
+		goto err;
+
+	return ret;
+err:
+	dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+
+static int dvb_usbv2_i2c_init(struct dvb_usb_device *d)
+{
+	int ret;
+	dev_dbg(&d->udev->dev, "%s:\n", __func__);
+
+	if (!d->props->i2c_algo)
+		return 0;
+
+	strlcpy(d->i2c_adap.name, d->name, sizeof(d->i2c_adap.name));
+	d->i2c_adap.algo = d->props->i2c_algo;
+	d->i2c_adap.dev.parent = &d->udev->dev;
+	i2c_set_adapdata(&d->i2c_adap, d);
+
+	ret = i2c_add_adapter(&d->i2c_adap);
+	if (ret < 0) {
+		d->i2c_adap.algo = NULL;
+		goto err;
+	}
+
+	return 0;
+err:
+	dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+
+static int dvb_usbv2_i2c_exit(struct dvb_usb_device *d)
+{
+	dev_dbg(&d->udev->dev, "%s:\n", __func__);
+
+	if (d->i2c_adap.algo)
+		i2c_del_adapter(&d->i2c_adap);
+
+	return 0;
+}
+
+#if IS_ENABLED(CONFIG_RC_CORE)
+static void dvb_usb_read_remote_control(struct work_struct *work)
+{
+	struct dvb_usb_device *d = container_of(work,
+			struct dvb_usb_device, rc_query_work.work);
+	int ret;
+
+	/*
+	 * When the parameter has been set to 1 via sysfs while the
+	 * driver was running, or when bulk mode is enabled after IR init.
+	 */
+	if (dvb_usbv2_disable_rc_polling || d->rc.bulk_mode) {
+		d->rc_polling_active = false;
+		return;
+	}
+
+	ret = d->rc.query(d);
+	if (ret < 0) {
+		dev_err(&d->udev->dev, "%s: rc.query() failed=%d\n",
+				KBUILD_MODNAME, ret);
+		d->rc_polling_active = false;
+		return; /* stop polling */
+	}
+
+	schedule_delayed_work(&d->rc_query_work,
+			msecs_to_jiffies(d->rc.interval));
+}
+
+static int dvb_usbv2_remote_init(struct dvb_usb_device *d)
+{
+	int ret;
+	struct rc_dev *dev;
+	dev_dbg(&d->udev->dev, "%s:\n", __func__);
+
+	if (dvb_usbv2_disable_rc_polling || !d->props->get_rc_config)
+		return 0;
+
+	d->rc.map_name = d->rc_map;
+	ret = d->props->get_rc_config(d, &d->rc);
+	if (ret < 0)
+		goto err;
+
+	/* disable rc when there is no keymap defined */
+	if (!d->rc.map_name)
+		return 0;
+
+	dev = rc_allocate_device(d->rc.driver_type);
+	if (!dev) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	dev->dev.parent = &d->udev->dev;
+	dev->device_name = d->name;
+	usb_make_path(d->udev, d->rc_phys, sizeof(d->rc_phys));
+	strlcat(d->rc_phys, "/ir0", sizeof(d->rc_phys));
+	dev->input_phys = d->rc_phys;
+	usb_to_input_id(d->udev, &dev->input_id);
+	dev->driver_name = d->props->driver_name;
+	dev->map_name = d->rc.map_name;
+	dev->allowed_protocols = d->rc.allowed_protos;
+	dev->change_protocol = d->rc.change_protocol;
+	dev->priv = d;
+
+	ret = rc_register_device(dev);
+	if (ret < 0) {
+		rc_free_device(dev);
+		goto err;
+	}
+
+	d->rc_dev = dev;
+
+	/* start polling if needed */
+	if (d->rc.query && !d->rc.bulk_mode) {
+		/* initialize a work queue for handling polling */
+		INIT_DELAYED_WORK(&d->rc_query_work,
+				dvb_usb_read_remote_control);
+		dev_info(&d->udev->dev,
+				"%s: schedule remote query interval to %d msecs\n",
+				KBUILD_MODNAME, d->rc.interval);
+		schedule_delayed_work(&d->rc_query_work,
+				msecs_to_jiffies(d->rc.interval));
+		d->rc_polling_active = true;
+	}
+
+	return 0;
+err:
+	dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+
+static int dvb_usbv2_remote_exit(struct dvb_usb_device *d)
+{
+	dev_dbg(&d->udev->dev, "%s:\n", __func__);
+
+	if (d->rc_dev) {
+		cancel_delayed_work_sync(&d->rc_query_work);
+		rc_unregister_device(d->rc_dev);
+		d->rc_dev = NULL;
+	}
+
+	return 0;
+}
+#else
+	#define dvb_usbv2_remote_init(args...) 0
+	#define dvb_usbv2_remote_exit(args...)
+#endif
+
+static void dvb_usb_data_complete(struct usb_data_stream *stream, u8 *buf,
+		size_t len)
+{
+	struct dvb_usb_adapter *adap = stream->user_priv;
+	dvb_dmx_swfilter(&adap->demux, buf, len);
+}
+
+static void dvb_usb_data_complete_204(struct usb_data_stream *stream, u8 *buf,
+		size_t len)
+{
+	struct dvb_usb_adapter *adap = stream->user_priv;
+	dvb_dmx_swfilter_204(&adap->demux, buf, len);
+}
+
+static void dvb_usb_data_complete_raw(struct usb_data_stream *stream, u8 *buf,
+		size_t len)
+{
+	struct dvb_usb_adapter *adap = stream->user_priv;
+	dvb_dmx_swfilter_raw(&adap->demux, buf, len);
+}
+
+static int dvb_usbv2_adapter_stream_init(struct dvb_usb_adapter *adap)
+{
+	dev_dbg(&adap_to_d(adap)->udev->dev, "%s: adap=%d\n", __func__,
+			adap->id);
+
+	adap->stream.udev = adap_to_d(adap)->udev;
+	adap->stream.user_priv = adap;
+	adap->stream.complete = dvb_usb_data_complete;
+
+	return usb_urb_initv2(&adap->stream, &adap->props->stream);
+}
+
+static int dvb_usbv2_adapter_stream_exit(struct dvb_usb_adapter *adap)
+{
+	dev_dbg(&adap_to_d(adap)->udev->dev, "%s: adap=%d\n", __func__,
+			adap->id);
+
+	return usb_urb_exitv2(&adap->stream);
+}
+
+static int dvb_usb_start_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+	struct dvb_usb_adapter *adap = dvbdmxfeed->demux->priv;
+	struct dvb_usb_device *d = adap_to_d(adap);
+	int ret = 0;
+	struct usb_data_stream_properties stream_props;
+	dev_dbg(&d->udev->dev,
+			"%s: adap=%d active_fe=%d feed_type=%d setting pid [%s]: %04x (%04d) at index %d\n",
+			__func__, adap->id, adap->active_fe, dvbdmxfeed->type,
+			adap->pid_filtering ? "yes" : "no", dvbdmxfeed->pid,
+			dvbdmxfeed->pid, dvbdmxfeed->index);
+
+	/* wait init is done */
+	wait_on_bit(&adap->state_bits, ADAP_INIT, TASK_UNINTERRUPTIBLE);
+
+	if (adap->active_fe == -1)
+		return -EINVAL;
+
+	/* skip feed setup if we are already feeding */
+	if (adap->feed_count++ > 0)
+		goto skip_feed_start;
+
+	/* set 'streaming' status bit */
+	set_bit(ADAP_STREAMING, &adap->state_bits);
+
+	/* resolve input and output streaming parameters */
+	if (d->props->get_stream_config) {
+		memcpy(&stream_props, &adap->props->stream,
+				sizeof(struct usb_data_stream_properties));
+		ret = d->props->get_stream_config(adap->fe[adap->active_fe],
+				&adap->ts_type, &stream_props);
+		if (ret)
+			dev_err(&d->udev->dev,
+					"%s: get_stream_config() failed=%d\n",
+					KBUILD_MODNAME, ret);
+	} else {
+		stream_props = adap->props->stream;
+	}
+
+	switch (adap->ts_type) {
+	case DVB_USB_FE_TS_TYPE_204:
+		adap->stream.complete = dvb_usb_data_complete_204;
+		break;
+	case DVB_USB_FE_TS_TYPE_RAW:
+		adap->stream.complete = dvb_usb_data_complete_raw;
+		break;
+	case DVB_USB_FE_TS_TYPE_188:
+	default:
+		adap->stream.complete = dvb_usb_data_complete;
+		break;
+	}
+
+	/* submit USB streaming packets */
+	usb_urb_submitv2(&adap->stream, &stream_props);
+
+	/* enable HW PID filter */
+	if (adap->pid_filtering && adap->props->pid_filter_ctrl) {
+		ret = adap->props->pid_filter_ctrl(adap, 1);
+		if (ret)
+			dev_err(&d->udev->dev,
+					"%s: pid_filter_ctrl() failed=%d\n",
+					KBUILD_MODNAME, ret);
+	}
+
+	/* ask device to start streaming */
+	if (d->props->streaming_ctrl) {
+		ret = d->props->streaming_ctrl(adap->fe[adap->active_fe], 1);
+		if (ret)
+			dev_err(&d->udev->dev,
+					"%s: streaming_ctrl() failed=%d\n",
+					KBUILD_MODNAME, ret);
+	}
+skip_feed_start:
+
+	/* add PID to device HW PID filter */
+	if (adap->pid_filtering && adap->props->pid_filter) {
+		ret = adap->props->pid_filter(adap, dvbdmxfeed->index,
+				dvbdmxfeed->pid, 1);
+		if (ret)
+			dev_err(&d->udev->dev, "%s: pid_filter() failed=%d\n",
+					KBUILD_MODNAME, ret);
+	}
+
+	if (ret)
+		dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+
+static int dvb_usb_stop_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+	struct dvb_usb_adapter *adap = dvbdmxfeed->demux->priv;
+	struct dvb_usb_device *d = adap_to_d(adap);
+	int ret = 0;
+	dev_dbg(&d->udev->dev,
+			"%s: adap=%d active_fe=%d feed_type=%d setting pid [%s]: %04x (%04d) at index %d\n",
+			__func__, adap->id, adap->active_fe, dvbdmxfeed->type,
+			adap->pid_filtering ? "yes" : "no", dvbdmxfeed->pid,
+			dvbdmxfeed->pid, dvbdmxfeed->index);
+
+	if (adap->active_fe == -1)
+		return -EINVAL;
+
+	/* remove PID from device HW PID filter */
+	if (adap->pid_filtering && adap->props->pid_filter) {
+		ret = adap->props->pid_filter(adap, dvbdmxfeed->index,
+				dvbdmxfeed->pid, 0);
+		if (ret)
+			dev_err(&d->udev->dev, "%s: pid_filter() failed=%d\n",
+					KBUILD_MODNAME, ret);
+	}
+
+	/* we cannot stop streaming until last PID is removed */
+	if (--adap->feed_count > 0)
+		goto skip_feed_stop;
+
+	/* ask device to stop streaming */
+	if (d->props->streaming_ctrl) {
+		ret = d->props->streaming_ctrl(adap->fe[adap->active_fe], 0);
+		if (ret)
+			dev_err(&d->udev->dev,
+					"%s: streaming_ctrl() failed=%d\n",
+					KBUILD_MODNAME, ret);
+	}
+
+	/* disable HW PID filter */
+	if (adap->pid_filtering && adap->props->pid_filter_ctrl) {
+		ret = adap->props->pid_filter_ctrl(adap, 0);
+		if (ret)
+			dev_err(&d->udev->dev,
+					"%s: pid_filter_ctrl() failed=%d\n",
+					KBUILD_MODNAME, ret);
+	}
+
+	/* kill USB streaming packets */
+	usb_urb_killv2(&adap->stream);
+
+	/* clear 'streaming' status bit */
+	clear_bit(ADAP_STREAMING, &adap->state_bits);
+	smp_mb__after_atomic();
+	wake_up_bit(&adap->state_bits, ADAP_STREAMING);
+skip_feed_stop:
+
+	if (ret)
+		dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+
+static int dvb_usbv2_media_device_init(struct dvb_usb_adapter *adap)
+{
+#ifdef CONFIG_MEDIA_CONTROLLER_DVB
+	struct media_device *mdev;
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct usb_device *udev = d->udev;
+
+	mdev = kzalloc(sizeof(*mdev), GFP_KERNEL);
+	if (!mdev)
+		return -ENOMEM;
+
+	media_device_usb_init(mdev, udev, d->name);
+
+	dvb_register_media_controller(&adap->dvb_adap, mdev);
+
+	dev_info(&d->udev->dev, "media controller created\n");
+#endif
+	return 0;
+}
+
+static int dvb_usbv2_media_device_register(struct dvb_usb_adapter *adap)
+{
+#ifdef CONFIG_MEDIA_CONTROLLER_DVB
+	return media_device_register(adap->dvb_adap.mdev);
+#else
+	return 0;
+#endif
+}
+
+static void dvb_usbv2_media_device_unregister(struct dvb_usb_adapter *adap)
+{
+#ifdef CONFIG_MEDIA_CONTROLLER_DVB
+
+	if (!adap->dvb_adap.mdev)
+		return;
+
+	media_device_unregister(adap->dvb_adap.mdev);
+	media_device_cleanup(adap->dvb_adap.mdev);
+	kfree(adap->dvb_adap.mdev);
+	adap->dvb_adap.mdev = NULL;
+
+#endif
+}
+
+static int dvb_usbv2_adapter_dvb_init(struct dvb_usb_adapter *adap)
+{
+	int ret;
+	struct dvb_usb_device *d = adap_to_d(adap);
+
+	dev_dbg(&d->udev->dev, "%s: adap=%d\n", __func__, adap->id);
+
+	ret = dvb_register_adapter(&adap->dvb_adap, d->name, d->props->owner,
+			&d->udev->dev, d->props->adapter_nr);
+	if (ret < 0) {
+		dev_dbg(&d->udev->dev, "%s: dvb_register_adapter() failed=%d\n",
+				__func__, ret);
+		goto err_dvb_register_adapter;
+	}
+
+	adap->dvb_adap.priv = adap;
+
+	ret = dvb_usbv2_media_device_init(adap);
+	if (ret < 0) {
+		dev_dbg(&d->udev->dev, "%s: dvb_usbv2_media_device_init() failed=%d\n",
+				__func__, ret);
+		goto err_dvb_register_mc;
+	}
+
+	if (d->props->read_mac_address) {
+		ret = d->props->read_mac_address(adap,
+				adap->dvb_adap.proposed_mac);
+		if (ret < 0)
+			goto err_dvb_dmx_init;
+
+		dev_info(&d->udev->dev, "%s: MAC address: %pM\n",
+				KBUILD_MODNAME, adap->dvb_adap.proposed_mac);
+	}
+
+	adap->demux.dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING;
+	adap->demux.priv             = adap;
+	adap->demux.filternum        = 0;
+	adap->demux.filternum        = adap->max_feed_count;
+	adap->demux.feednum          = adap->demux.filternum;
+	adap->demux.start_feed       = dvb_usb_start_feed;
+	adap->demux.stop_feed        = dvb_usb_stop_feed;
+	adap->demux.write_to_decoder = NULL;
+	ret = dvb_dmx_init(&adap->demux);
+	if (ret < 0) {
+		dev_err(&d->udev->dev, "%s: dvb_dmx_init() failed=%d\n",
+				KBUILD_MODNAME, ret);
+		goto err_dvb_dmx_init;
+	}
+
+	adap->dmxdev.filternum       = adap->demux.filternum;
+	adap->dmxdev.demux           = &adap->demux.dmx;
+	adap->dmxdev.capabilities    = 0;
+	ret = dvb_dmxdev_init(&adap->dmxdev, &adap->dvb_adap);
+	if (ret < 0) {
+		dev_err(&d->udev->dev, "%s: dvb_dmxdev_init() failed=%d\n",
+				KBUILD_MODNAME, ret);
+		goto err_dvb_dmxdev_init;
+	}
+
+	ret = dvb_net_init(&adap->dvb_adap, &adap->dvb_net, &adap->demux.dmx);
+	if (ret < 0) {
+		dev_err(&d->udev->dev, "%s: dvb_net_init() failed=%d\n",
+				KBUILD_MODNAME, ret);
+		goto err_dvb_net_init;
+	}
+
+	return 0;
+err_dvb_net_init:
+	dvb_dmxdev_release(&adap->dmxdev);
+err_dvb_dmxdev_init:
+	dvb_dmx_release(&adap->demux);
+err_dvb_dmx_init:
+	dvb_usbv2_media_device_unregister(adap);
+err_dvb_register_mc:
+	dvb_unregister_adapter(&adap->dvb_adap);
+err_dvb_register_adapter:
+	adap->dvb_adap.priv = NULL;
+	return ret;
+}
+
+static int dvb_usbv2_adapter_dvb_exit(struct dvb_usb_adapter *adap)
+{
+	dev_dbg(&adap_to_d(adap)->udev->dev, "%s: adap=%d\n", __func__,
+			adap->id);
+
+	if (adap->dvb_adap.priv) {
+		dvb_net_release(&adap->dvb_net);
+		adap->demux.dmx.close(&adap->demux.dmx);
+		dvb_dmxdev_release(&adap->dmxdev);
+		dvb_dmx_release(&adap->demux);
+		dvb_unregister_adapter(&adap->dvb_adap);
+	}
+
+	return 0;
+}
+
+static int dvb_usbv2_device_power_ctrl(struct dvb_usb_device *d, int onoff)
+{
+	int ret;
+
+	if (onoff)
+		d->powered++;
+	else
+		d->powered--;
+
+	if (d->powered == 0 || (onoff && d->powered == 1)) {
+		/* when switching from 1 to 0 or from 0 to 1 */
+		dev_dbg(&d->udev->dev, "%s: power=%d\n", __func__, onoff);
+		if (d->props->power_ctrl) {
+			ret = d->props->power_ctrl(d, onoff);
+			if (ret < 0)
+				goto err;
+		}
+	}
+
+	return 0;
+err:
+	dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+
+static int dvb_usb_fe_init(struct dvb_frontend *fe)
+{
+	int ret;
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct dvb_usb_device *d = adap_to_d(adap);
+	dev_dbg(&d->udev->dev, "%s: adap=%d fe=%d\n", __func__, adap->id,
+			fe->id);
+
+	if (!adap->suspend_resume_active) {
+		adap->active_fe = fe->id;
+		set_bit(ADAP_INIT, &adap->state_bits);
+	}
+
+	ret = dvb_usbv2_device_power_ctrl(d, 1);
+	if (ret < 0)
+		goto err;
+
+	if (d->props->frontend_ctrl) {
+		ret = d->props->frontend_ctrl(fe, 1);
+		if (ret < 0)
+			goto err;
+	}
+
+	if (adap->fe_init[fe->id]) {
+		ret = adap->fe_init[fe->id](fe);
+		if (ret < 0)
+			goto err;
+	}
+err:
+	if (!adap->suspend_resume_active) {
+		clear_bit(ADAP_INIT, &adap->state_bits);
+		smp_mb__after_atomic();
+		wake_up_bit(&adap->state_bits, ADAP_INIT);
+	}
+
+	dev_dbg(&d->udev->dev, "%s: ret=%d\n", __func__, ret);
+	return ret;
+}
+
+static int dvb_usb_fe_sleep(struct dvb_frontend *fe)
+{
+	int ret;
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct dvb_usb_device *d = adap_to_d(adap);
+	dev_dbg(&d->udev->dev, "%s: adap=%d fe=%d\n", __func__, adap->id,
+			fe->id);
+
+	if (!adap->suspend_resume_active) {
+		set_bit(ADAP_SLEEP, &adap->state_bits);
+		wait_on_bit(&adap->state_bits, ADAP_STREAMING,
+				TASK_UNINTERRUPTIBLE);
+	}
+
+	if (adap->fe_sleep[fe->id]) {
+		ret = adap->fe_sleep[fe->id](fe);
+		if (ret < 0)
+			goto err;
+	}
+
+	if (d->props->frontend_ctrl) {
+		ret = d->props->frontend_ctrl(fe, 0);
+		if (ret < 0)
+			goto err;
+	}
+
+	ret = dvb_usbv2_device_power_ctrl(d, 0);
+
+err:
+	if (!adap->suspend_resume_active) {
+		adap->active_fe = -1;
+		clear_bit(ADAP_SLEEP, &adap->state_bits);
+		smp_mb__after_atomic();
+		wake_up_bit(&adap->state_bits, ADAP_SLEEP);
+	}
+
+	dev_dbg(&d->udev->dev, "%s: ret=%d\n", __func__, ret);
+	return ret;
+}
+
+static int dvb_usbv2_adapter_frontend_init(struct dvb_usb_adapter *adap)
+{
+	int ret, i, count_registered = 0;
+	struct dvb_usb_device *d = adap_to_d(adap);
+	dev_dbg(&d->udev->dev, "%s: adap=%d\n", __func__, adap->id);
+
+	memset(adap->fe, 0, sizeof(adap->fe));
+	adap->active_fe = -1;
+
+	if (d->props->frontend_attach) {
+		ret = d->props->frontend_attach(adap);
+		if (ret < 0) {
+			dev_dbg(&d->udev->dev,
+					"%s: frontend_attach() failed=%d\n",
+					__func__, ret);
+			goto err_dvb_frontend_detach;
+		}
+	} else {
+		dev_dbg(&d->udev->dev, "%s: frontend_attach() do not exists\n",
+				__func__);
+		ret = 0;
+		goto err;
+	}
+
+	for (i = 0; i < MAX_NO_OF_FE_PER_ADAP && adap->fe[i]; i++) {
+		adap->fe[i]->id = i;
+		/* re-assign sleep and wakeup functions */
+		adap->fe_init[i] = adap->fe[i]->ops.init;
+		adap->fe[i]->ops.init = dvb_usb_fe_init;
+		adap->fe_sleep[i] = adap->fe[i]->ops.sleep;
+		adap->fe[i]->ops.sleep = dvb_usb_fe_sleep;
+
+		ret = dvb_register_frontend(&adap->dvb_adap, adap->fe[i]);
+		if (ret < 0) {
+			dev_err(&d->udev->dev,
+					"%s: frontend%d registration failed\n",
+					KBUILD_MODNAME, i);
+			goto err_dvb_unregister_frontend;
+		}
+
+		count_registered++;
+	}
+
+	if (d->props->tuner_attach) {
+		ret = d->props->tuner_attach(adap);
+		if (ret < 0) {
+			dev_dbg(&d->udev->dev, "%s: tuner_attach() failed=%d\n",
+					__func__, ret);
+			goto err_dvb_unregister_frontend;
+		}
+	}
+
+	ret = dvb_create_media_graph(&adap->dvb_adap, true);
+	if (ret < 0)
+		goto err_dvb_unregister_frontend;
+
+	ret = dvb_usbv2_media_device_register(adap);
+
+	return ret;
+
+err_dvb_unregister_frontend:
+	for (i = count_registered - 1; i >= 0; i--)
+		dvb_unregister_frontend(adap->fe[i]);
+
+err_dvb_frontend_detach:
+	for (i = MAX_NO_OF_FE_PER_ADAP - 1; i >= 0; i--) {
+		if (adap->fe[i]) {
+			dvb_frontend_detach(adap->fe[i]);
+			adap->fe[i] = NULL;
+		}
+	}
+
+err:
+	dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+
+static int dvb_usbv2_adapter_frontend_exit(struct dvb_usb_adapter *adap)
+{
+	int ret, i;
+	struct dvb_usb_device *d = adap_to_d(adap);
+
+	dev_dbg(&d->udev->dev, "%s: adap=%d\n", __func__, adap->id);
+
+	for (i = MAX_NO_OF_FE_PER_ADAP - 1; i >= 0; i--) {
+		if (adap->fe[i]) {
+			dvb_unregister_frontend(adap->fe[i]);
+			dvb_frontend_detach(adap->fe[i]);
+		}
+	}
+
+	if (d->props->tuner_detach) {
+		ret = d->props->tuner_detach(adap);
+		if (ret < 0) {
+			dev_dbg(&d->udev->dev, "%s: tuner_detach() failed=%d\n",
+					__func__, ret);
+		}
+	}
+
+	if (d->props->frontend_detach) {
+		ret = d->props->frontend_detach(adap);
+		if (ret < 0) {
+			dev_dbg(&d->udev->dev,
+					"%s: frontend_detach() failed=%d\n",
+					__func__, ret);
+		}
+	}
+
+	return 0;
+}
+
+static int dvb_usbv2_adapter_init(struct dvb_usb_device *d)
+{
+	struct dvb_usb_adapter *adap;
+	int ret, i, adapter_count;
+
+	/* resolve adapter count */
+	adapter_count = d->props->num_adapters;
+	if (d->props->get_adapter_count) {
+		ret = d->props->get_adapter_count(d);
+		if (ret < 0)
+			goto err;
+
+		adapter_count = ret;
+	}
+
+	for (i = 0; i < adapter_count; i++) {
+		adap = &d->adapter[i];
+		adap->id = i;
+		adap->props = &d->props->adapter[i];
+
+		/* speed - when running at FULL speed we need a HW PID filter */
+		if (d->udev->speed == USB_SPEED_FULL &&
+				!(adap->props->caps & DVB_USB_ADAP_HAS_PID_FILTER)) {
+			dev_err(&d->udev->dev,
+					"%s: this USB2.0 device cannot be run on a USB1.1 port (it lacks a hardware PID filter)\n",
+					KBUILD_MODNAME);
+			ret = -ENODEV;
+			goto err;
+		} else if ((d->udev->speed == USB_SPEED_FULL &&
+				adap->props->caps & DVB_USB_ADAP_HAS_PID_FILTER) ||
+				(adap->props->caps & DVB_USB_ADAP_NEED_PID_FILTERING)) {
+			dev_info(&d->udev->dev,
+					"%s: will use the device's hardware PID filter (table count: %d)\n",
+					KBUILD_MODNAME,
+					adap->props->pid_filter_count);
+			adap->pid_filtering  = 1;
+			adap->max_feed_count = adap->props->pid_filter_count;
+		} else {
+			dev_info(&d->udev->dev,
+					"%s: will pass the complete MPEG2 transport stream to the software demuxer\n",
+					KBUILD_MODNAME);
+			adap->pid_filtering  = 0;
+			adap->max_feed_count = 255;
+		}
+
+		if (!adap->pid_filtering && dvb_usb_force_pid_filter_usage &&
+				adap->props->caps & DVB_USB_ADAP_HAS_PID_FILTER) {
+			dev_info(&d->udev->dev,
+					"%s: PID filter enabled by module option\n",
+					KBUILD_MODNAME);
+			adap->pid_filtering  = 1;
+			adap->max_feed_count = adap->props->pid_filter_count;
+		}
+
+		ret = dvb_usbv2_adapter_stream_init(adap);
+		if (ret)
+			goto err;
+
+		ret = dvb_usbv2_adapter_dvb_init(adap);
+		if (ret)
+			goto err;
+
+		ret = dvb_usbv2_adapter_frontend_init(adap);
+		if (ret)
+			goto err;
+
+		/* use exclusive FE lock if there is multiple shared FEs */
+		if (adap->fe[1])
+			adap->dvb_adap.mfe_shared = 1;
+	}
+
+	return 0;
+err:
+	dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+
+static int dvb_usbv2_adapter_exit(struct dvb_usb_device *d)
+{
+	int i;
+	dev_dbg(&d->udev->dev, "%s:\n", __func__);
+
+	for (i = MAX_NO_OF_ADAPTER_PER_DEVICE - 1; i >= 0; i--) {
+		if (d->adapter[i].props) {
+			dvb_usbv2_adapter_dvb_exit(&d->adapter[i]);
+			dvb_usbv2_adapter_stream_exit(&d->adapter[i]);
+			dvb_usbv2_adapter_frontend_exit(&d->adapter[i]);
+			dvb_usbv2_media_device_unregister(&d->adapter[i]);
+		}
+	}
+
+	return 0;
+}
+
+/* general initialization functions */
+static int dvb_usbv2_exit(struct dvb_usb_device *d)
+{
+	dev_dbg(&d->udev->dev, "%s:\n", __func__);
+
+	dvb_usbv2_remote_exit(d);
+	dvb_usbv2_adapter_exit(d);
+	dvb_usbv2_i2c_exit(d);
+
+	return 0;
+}
+
+static int dvb_usbv2_init(struct dvb_usb_device *d)
+{
+	int ret;
+	dev_dbg(&d->udev->dev, "%s:\n", __func__);
+
+	dvb_usbv2_device_power_ctrl(d, 1);
+
+	if (d->props->read_config) {
+		ret = d->props->read_config(d);
+		if (ret < 0)
+			goto err;
+	}
+
+	ret = dvb_usbv2_i2c_init(d);
+	if (ret < 0)
+		goto err;
+
+	ret = dvb_usbv2_adapter_init(d);
+	if (ret < 0)
+		goto err;
+
+	if (d->props->init) {
+		ret = d->props->init(d);
+		if (ret < 0)
+			goto err;
+	}
+
+	ret = dvb_usbv2_remote_init(d);
+	if (ret < 0)
+		goto err;
+
+	dvb_usbv2_device_power_ctrl(d, 0);
+
+	return 0;
+err:
+	dvb_usbv2_device_power_ctrl(d, 0);
+	dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+
+int dvb_usbv2_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	int ret;
+	struct dvb_usb_device *d;
+	struct usb_device *udev = interface_to_usbdev(intf);
+	struct dvb_usb_driver_info *driver_info =
+			(struct dvb_usb_driver_info *) id->driver_info;
+
+	dev_dbg(&udev->dev, "%s: bInterfaceNumber=%d\n", __func__,
+			intf->cur_altsetting->desc.bInterfaceNumber);
+
+	if (!id->driver_info) {
+		dev_err(&udev->dev, "%s: driver_info failed\n", KBUILD_MODNAME);
+		ret = -ENODEV;
+		goto err;
+	}
+
+	d = kzalloc(sizeof(struct dvb_usb_device), GFP_KERNEL);
+	if (!d) {
+		dev_err(&udev->dev, "%s: kzalloc() failed\n", KBUILD_MODNAME);
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	d->intf = intf;
+	d->name = driver_info->name;
+	d->rc_map = driver_info->rc_map;
+	d->udev = udev;
+	d->props = driver_info->props;
+
+	if (intf->cur_altsetting->desc.bInterfaceNumber !=
+			d->props->bInterfaceNumber) {
+		ret = -ENODEV;
+		goto err_kfree_d;
+	}
+
+	mutex_init(&d->usb_mutex);
+	mutex_init(&d->i2c_mutex);
+
+	if (d->props->size_of_priv) {
+		d->priv = kzalloc(d->props->size_of_priv, GFP_KERNEL);
+		if (!d->priv) {
+			dev_err(&d->udev->dev, "%s: kzalloc() failed\n",
+					KBUILD_MODNAME);
+			ret = -ENOMEM;
+			goto err_kfree_d;
+		}
+	}
+
+	if (d->props->probe) {
+		ret = d->props->probe(d);
+		if (ret)
+			goto err_kfree_priv;
+	}
+
+	if (d->props->identify_state) {
+		const char *name = NULL;
+		ret = d->props->identify_state(d, &name);
+		if (ret == 0) {
+			;
+		} else if (ret == COLD) {
+			dev_info(&d->udev->dev,
+					"%s: found a '%s' in cold state\n",
+					KBUILD_MODNAME, d->name);
+
+			if (!name)
+				name = d->props->firmware;
+
+			ret = dvb_usbv2_download_firmware(d, name);
+			if (ret == 0) {
+				/* device is warm, continue initialization */
+				;
+			} else if (ret == RECONNECTS_USB) {
+				/*
+				 * USB core will call disconnect() and then
+				 * probe() as device reconnects itself from the
+				 * USB bus. disconnect() will release all driver
+				 * resources and probe() is called for 'new'
+				 * device. As 'new' device is warm we should
+				 * never go here again.
+				 */
+				goto exit;
+			} else {
+				goto err_free_all;
+			}
+		} else {
+			goto err_free_all;
+		}
+	}
+
+	dev_info(&d->udev->dev, "%s: found a '%s' in warm state\n",
+			KBUILD_MODNAME, d->name);
+
+	ret = dvb_usbv2_init(d);
+	if (ret < 0)
+		goto err_free_all;
+
+	dev_info(&d->udev->dev,
+			"%s: '%s' successfully initialized and connected\n",
+			KBUILD_MODNAME, d->name);
+exit:
+	usb_set_intfdata(intf, d);
+
+	return 0;
+err_free_all:
+	dvb_usbv2_exit(d);
+	if (d->props->disconnect)
+		d->props->disconnect(d);
+err_kfree_priv:
+	kfree(d->priv);
+err_kfree_d:
+	kfree(d);
+err:
+	dev_dbg(&udev->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+EXPORT_SYMBOL(dvb_usbv2_probe);
+
+void dvb_usbv2_disconnect(struct usb_interface *intf)
+{
+	struct dvb_usb_device *d = usb_get_intfdata(intf);
+	const char *devname = kstrdup(dev_name(&d->udev->dev), GFP_KERNEL);
+	const char *drvname = d->name;
+
+	dev_dbg(&d->udev->dev, "%s: bInterfaceNumber=%d\n", __func__,
+			intf->cur_altsetting->desc.bInterfaceNumber);
+
+	if (d->props->exit)
+		d->props->exit(d);
+
+	dvb_usbv2_exit(d);
+
+	if (d->props->disconnect)
+		d->props->disconnect(d);
+
+	kfree(d->priv);
+	kfree(d);
+
+	pr_info("%s: '%s:%s' successfully deinitialized and disconnected\n",
+		KBUILD_MODNAME, drvname, devname);
+	kfree(devname);
+}
+EXPORT_SYMBOL(dvb_usbv2_disconnect);
+
+int dvb_usbv2_suspend(struct usb_interface *intf, pm_message_t msg)
+{
+	struct dvb_usb_device *d = usb_get_intfdata(intf);
+	int ret = 0, i, active_fe;
+	struct dvb_frontend *fe;
+	dev_dbg(&d->udev->dev, "%s:\n", __func__);
+
+	/* stop remote controller poll */
+	if (d->rc_polling_active)
+		cancel_delayed_work_sync(&d->rc_query_work);
+
+	for (i = MAX_NO_OF_ADAPTER_PER_DEVICE - 1; i >= 0; i--) {
+		active_fe = d->adapter[i].active_fe;
+		if (d->adapter[i].dvb_adap.priv && active_fe != -1) {
+			fe = d->adapter[i].fe[active_fe];
+			d->adapter[i].suspend_resume_active = true;
+
+			if (d->props->streaming_ctrl)
+				d->props->streaming_ctrl(fe, 0);
+
+			/* stop usb streaming */
+			usb_urb_killv2(&d->adapter[i].stream);
+
+			ret = dvb_frontend_suspend(fe);
+		}
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL(dvb_usbv2_suspend);
+
+static int dvb_usbv2_resume_common(struct dvb_usb_device *d)
+{
+	int ret = 0, i, active_fe;
+	struct dvb_frontend *fe;
+	dev_dbg(&d->udev->dev, "%s:\n", __func__);
+
+	for (i = 0; i < MAX_NO_OF_ADAPTER_PER_DEVICE; i++) {
+		active_fe = d->adapter[i].active_fe;
+		if (d->adapter[i].dvb_adap.priv && active_fe != -1) {
+			fe = d->adapter[i].fe[active_fe];
+
+			ret = dvb_frontend_resume(fe);
+
+			/* resume usb streaming */
+			usb_urb_submitv2(&d->adapter[i].stream, NULL);
+
+			if (d->props->streaming_ctrl)
+				d->props->streaming_ctrl(fe, 1);
+
+			d->adapter[i].suspend_resume_active = false;
+		}
+	}
+
+	/* start remote controller poll */
+	if (d->rc_polling_active)
+		schedule_delayed_work(&d->rc_query_work,
+				msecs_to_jiffies(d->rc.interval));
+
+	return ret;
+}
+
+int dvb_usbv2_resume(struct usb_interface *intf)
+{
+	struct dvb_usb_device *d = usb_get_intfdata(intf);
+	dev_dbg(&d->udev->dev, "%s:\n", __func__);
+
+	return dvb_usbv2_resume_common(d);
+}
+EXPORT_SYMBOL(dvb_usbv2_resume);
+
+int dvb_usbv2_reset_resume(struct usb_interface *intf)
+{
+	struct dvb_usb_device *d = usb_get_intfdata(intf);
+	int ret;
+	dev_dbg(&d->udev->dev, "%s:\n", __func__);
+
+	dvb_usbv2_device_power_ctrl(d, 1);
+
+	if (d->props->init)
+		d->props->init(d);
+
+	ret = dvb_usbv2_resume_common(d);
+
+	dvb_usbv2_device_power_ctrl(d, 0);
+
+	return ret;
+}
+EXPORT_SYMBOL(dvb_usbv2_reset_resume);
+
+MODULE_VERSION("2.0");
+MODULE_AUTHOR("Patrick Boettcher <patrick.boettcher@posteo.de>");
+MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>");
+MODULE_DESCRIPTION("DVB USB common");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb-v2/dvb_usb_urb.c b/drivers/media/usb/dvb-usb-v2/dvb_usb_urb.c
new file mode 100644
index 0000000..5bafeb6
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/dvb_usb_urb.c
@@ -0,0 +1,104 @@
+/*
+ * DVB USB framework
+ *
+ * Copyright (C) 2004-6 Patrick Boettcher <patrick.boettcher@posteo.de>
+ * Copyright (C) 2012 Antti Palosaari <crope@iki.fi>
+ *
+ *    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 "dvb_usb_common.h"
+
+static int dvb_usb_v2_generic_io(struct dvb_usb_device *d,
+		u8 *wbuf, u16 wlen, u8 *rbuf, u16 rlen)
+{
+	int ret, actual_length;
+
+	if (!wbuf || !wlen || !d->props->generic_bulk_ctrl_endpoint ||
+			!d->props->generic_bulk_ctrl_endpoint_response) {
+		dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, -EINVAL);
+		return -EINVAL;
+	}
+
+	dev_dbg(&d->udev->dev, "%s: >>> %*ph\n", __func__, wlen, wbuf);
+
+	ret = usb_bulk_msg(d->udev, usb_sndbulkpipe(d->udev,
+			d->props->generic_bulk_ctrl_endpoint), wbuf, wlen,
+			&actual_length, 2000);
+	if (ret < 0)
+		dev_err(&d->udev->dev, "%s: usb_bulk_msg() failed=%d\n",
+				KBUILD_MODNAME, ret);
+	else
+		ret = actual_length != wlen ? -EIO : 0;
+
+	/* an answer is expected, and no error before */
+	if (!ret && rbuf && rlen) {
+		if (d->props->generic_bulk_ctrl_delay)
+			usleep_range(d->props->generic_bulk_ctrl_delay,
+					d->props->generic_bulk_ctrl_delay
+					+ 20000);
+
+		ret = usb_bulk_msg(d->udev, usb_rcvbulkpipe(d->udev,
+				d->props->generic_bulk_ctrl_endpoint_response),
+				rbuf, rlen, &actual_length, 2000);
+		if (ret)
+			dev_err(&d->udev->dev,
+					"%s: 2nd usb_bulk_msg() failed=%d\n",
+					KBUILD_MODNAME, ret);
+
+		dev_dbg(&d->udev->dev, "%s: <<< %*ph\n", __func__,
+				actual_length, rbuf);
+	}
+
+	return ret;
+}
+
+int dvb_usbv2_generic_rw(struct dvb_usb_device *d,
+		u8 *wbuf, u16 wlen, u8 *rbuf, u16 rlen)
+{
+	int ret;
+
+	mutex_lock(&d->usb_mutex);
+	ret = dvb_usb_v2_generic_io(d, wbuf, wlen, rbuf, rlen);
+	mutex_unlock(&d->usb_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL(dvb_usbv2_generic_rw);
+
+int dvb_usbv2_generic_write(struct dvb_usb_device *d, u8 *buf, u16 len)
+{
+	int ret;
+
+	mutex_lock(&d->usb_mutex);
+	ret = dvb_usb_v2_generic_io(d, buf, len, NULL, 0);
+	mutex_unlock(&d->usb_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL(dvb_usbv2_generic_write);
+
+int dvb_usbv2_generic_rw_locked(struct dvb_usb_device *d,
+		u8 *wbuf, u16 wlen, u8 *rbuf, u16 rlen)
+{
+	return dvb_usb_v2_generic_io(d, wbuf, wlen, rbuf, rlen);
+}
+EXPORT_SYMBOL(dvb_usbv2_generic_rw_locked);
+
+int dvb_usbv2_generic_write_locked(struct dvb_usb_device *d, u8 *buf, u16 len)
+{
+	return dvb_usb_v2_generic_io(d, buf, len, NULL, 0);
+}
+EXPORT_SYMBOL(dvb_usbv2_generic_write_locked);
diff --git a/drivers/media/usb/dvb-usb-v2/dvbsky.c b/drivers/media/usb/dvb-usb-v2/dvbsky.c
new file mode 100644
index 0000000..e28bd88
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/dvbsky.c
@@ -0,0 +1,820 @@
+/*
+ * Driver for DVBSky USB2.0 receiver
+ *
+ * Copyright (C) 2013 Max nibble <nibble.max@gmail.com>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ */
+
+#include "dvb_usb.h"
+#include "m88ds3103.h"
+#include "ts2020.h"
+#include "sp2.h"
+#include "si2168.h"
+#include "si2157.h"
+
+#define DVBSKY_MSG_DELAY	0/*2000*/
+#define DVBSKY_BUF_LEN	64
+
+static int dvb_usb_dvbsky_disable_rc;
+module_param_named(disable_rc, dvb_usb_dvbsky_disable_rc, int, 0644);
+MODULE_PARM_DESC(disable_rc, "Disable inbuilt IR receiver.");
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+struct dvbsky_state {
+	struct mutex stream_mutex;
+	u8 ibuf[DVBSKY_BUF_LEN];
+	u8 obuf[DVBSKY_BUF_LEN];
+	u8 last_lock;
+	struct i2c_client *i2c_client_demod;
+	struct i2c_client *i2c_client_tuner;
+	struct i2c_client *i2c_client_ci;
+
+	/* fe hook functions*/
+	int (*fe_set_voltage)(struct dvb_frontend *fe,
+		enum fe_sec_voltage voltage);
+	int (*fe_read_status)(struct dvb_frontend *fe,
+		enum fe_status *status);
+};
+
+static int dvbsky_usb_generic_rw(struct dvb_usb_device *d,
+		u8 *wbuf, u16 wlen, u8 *rbuf, u16 rlen)
+{
+	int ret;
+	struct dvbsky_state *state = d_to_priv(d);
+
+	mutex_lock(&d->usb_mutex);
+	if (wlen != 0)
+		memcpy(state->obuf, wbuf, wlen);
+
+	ret = dvb_usbv2_generic_rw_locked(d, state->obuf, wlen,
+			state->ibuf, rlen);
+
+	if (!ret && (rlen != 0))
+		memcpy(rbuf, state->ibuf, rlen);
+
+	mutex_unlock(&d->usb_mutex);
+	return ret;
+}
+
+static int dvbsky_stream_ctrl(struct dvb_usb_device *d, u8 onoff)
+{
+	struct dvbsky_state *state = d_to_priv(d);
+	int ret;
+	u8 obuf_pre[3] = { 0x37, 0, 0 };
+	u8 obuf_post[3] = { 0x36, 3, 0 };
+
+	mutex_lock(&state->stream_mutex);
+	ret = dvbsky_usb_generic_rw(d, obuf_pre, 3, NULL, 0);
+	if (!ret && onoff) {
+		msleep(20);
+		ret = dvbsky_usb_generic_rw(d, obuf_post, 3, NULL, 0);
+	}
+	mutex_unlock(&state->stream_mutex);
+	return ret;
+}
+
+static int dvbsky_streaming_ctrl(struct dvb_frontend *fe, int onoff)
+{
+	struct dvb_usb_device *d = fe_to_d(fe);
+
+	return dvbsky_stream_ctrl(d, (onoff == 0) ? 0 : 1);
+}
+
+/* GPIO */
+static int dvbsky_gpio_ctrl(struct dvb_usb_device *d, u8 gport, u8 value)
+{
+	int ret;
+	u8 obuf[3], ibuf[2];
+
+	obuf[0] = 0x0e;
+	obuf[1] = gport;
+	obuf[2] = value;
+	ret = dvbsky_usb_generic_rw(d, obuf, 3, ibuf, 1);
+	if (ret)
+		dev_err(&d->udev->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+/* I2C */
+static int dvbsky_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[],
+	int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	int ret = 0;
+	u8 ibuf[64], obuf[64];
+
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	if (num > 2) {
+		dev_err(&d->udev->dev,
+		"too many i2c messages[%d], max 2.", num);
+		ret = -EOPNOTSUPP;
+		goto i2c_error;
+	}
+
+	if (num == 1) {
+		if (msg[0].len > 60) {
+			dev_err(&d->udev->dev,
+			"too many i2c bytes[%d], max 60.",
+			msg[0].len);
+			ret = -EOPNOTSUPP;
+			goto i2c_error;
+		}
+		if (msg[0].flags & I2C_M_RD) {
+			/* single read */
+			obuf[0] = 0x09;
+			obuf[1] = 0;
+			obuf[2] = msg[0].len;
+			obuf[3] = msg[0].addr;
+			ret = dvbsky_usb_generic_rw(d, obuf, 4,
+					ibuf, msg[0].len + 1);
+			if (ret)
+				dev_err(&d->udev->dev, "failed=%d\n", ret);
+			if (!ret)
+				memcpy(msg[0].buf, &ibuf[1], msg[0].len);
+		} else {
+			/* write */
+			obuf[0] = 0x08;
+			obuf[1] = msg[0].addr;
+			obuf[2] = msg[0].len;
+			memcpy(&obuf[3], msg[0].buf, msg[0].len);
+			ret = dvbsky_usb_generic_rw(d, obuf,
+					msg[0].len + 3, ibuf, 1);
+			if (ret)
+				dev_err(&d->udev->dev, "failed=%d\n", ret);
+		}
+	} else {
+		if ((msg[0].len > 60) || (msg[1].len > 60)) {
+			dev_err(&d->udev->dev,
+			"too many i2c bytes[w-%d][r-%d], max 60.",
+			msg[0].len, msg[1].len);
+			ret = -EOPNOTSUPP;
+			goto i2c_error;
+		}
+		/* write then read */
+		obuf[0] = 0x09;
+		obuf[1] = msg[0].len;
+		obuf[2] = msg[1].len;
+		obuf[3] = msg[0].addr;
+		memcpy(&obuf[4], msg[0].buf, msg[0].len);
+		ret = dvbsky_usb_generic_rw(d, obuf,
+			msg[0].len + 4, ibuf, msg[1].len + 1);
+		if (ret)
+			dev_err(&d->udev->dev, "failed=%d\n", ret);
+
+		if (!ret)
+			memcpy(msg[1].buf, &ibuf[1], msg[1].len);
+	}
+i2c_error:
+	mutex_unlock(&d->i2c_mutex);
+	return (ret) ? ret : num;
+}
+
+static u32 dvbsky_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm dvbsky_i2c_algo = {
+	.master_xfer   = dvbsky_i2c_xfer,
+	.functionality = dvbsky_i2c_func,
+};
+
+#if IS_ENABLED(CONFIG_RC_CORE)
+static int dvbsky_rc_query(struct dvb_usb_device *d)
+{
+	u32 code = 0xffff, scancode;
+	u8 rc5_command, rc5_system;
+	u8 obuf[2], ibuf[2], toggle;
+	int ret;
+
+	obuf[0] = 0x10;
+	ret = dvbsky_usb_generic_rw(d, obuf, 1, ibuf, 2);
+	if (ret)
+		dev_err(&d->udev->dev, "failed=%d\n", ret);
+	if (ret == 0)
+		code = (ibuf[0] << 8) | ibuf[1];
+	if (code != 0xffff) {
+		dev_dbg(&d->udev->dev, "rc code: %x\n", code);
+		rc5_command = code & 0x3F;
+		rc5_system = (code & 0x7C0) >> 6;
+		toggle = (code & 0x800) ? 1 : 0;
+		scancode = rc5_system << 8 | rc5_command;
+		rc_keydown(d->rc_dev, RC_PROTO_RC5, scancode, toggle);
+	}
+	return 0;
+}
+
+static int dvbsky_get_rc_config(struct dvb_usb_device *d, struct dvb_usb_rc *rc)
+{
+	if (dvb_usb_dvbsky_disable_rc) {
+		rc->map_name = NULL;
+		return 0;
+	}
+
+	rc->allowed_protos = RC_PROTO_BIT_RC5;
+	rc->query          = dvbsky_rc_query;
+	rc->interval       = 300;
+	return 0;
+}
+#else
+	#define dvbsky_get_rc_config NULL
+#endif
+
+static int dvbsky_usb_set_voltage(struct dvb_frontend *fe,
+	enum fe_sec_voltage voltage)
+{
+	struct dvb_usb_device *d = fe_to_d(fe);
+	struct dvbsky_state *state = d_to_priv(d);
+	u8 value;
+
+	if (voltage == SEC_VOLTAGE_OFF)
+		value = 0;
+	else
+		value = 1;
+	dvbsky_gpio_ctrl(d, 0x80, value);
+
+	return state->fe_set_voltage(fe, voltage);
+}
+
+static int dvbsky_read_mac_addr(struct dvb_usb_adapter *adap, u8 mac[6])
+{
+	struct dvb_usb_device *d = adap_to_d(adap);
+	u8 obuf[] = { 0x1e, 0x00 };
+	u8 ibuf[6] = { 0 };
+	struct i2c_msg msg[] = {
+		{
+			.addr = 0x51,
+			.flags = 0,
+			.buf = obuf,
+			.len = 2,
+		}, {
+			.addr = 0x51,
+			.flags = I2C_M_RD,
+			.buf = ibuf,
+			.len = 6,
+		}
+	};
+
+	if (i2c_transfer(&d->i2c_adap, msg, 2) == 2)
+		memcpy(mac, ibuf, 6);
+
+	return 0;
+}
+
+static int dvbsky_usb_read_status(struct dvb_frontend *fe,
+				  enum fe_status *status)
+{
+	struct dvb_usb_device *d = fe_to_d(fe);
+	struct dvbsky_state *state = d_to_priv(d);
+	int ret;
+
+	ret = state->fe_read_status(fe, status);
+
+	/* it need resync slave fifo when signal change from unlock to lock.*/
+	if ((*status & FE_HAS_LOCK) && (!state->last_lock))
+		dvbsky_stream_ctrl(d, 1);
+
+	state->last_lock = (*status & FE_HAS_LOCK) ? 1 : 0;
+	return ret;
+}
+
+static int dvbsky_s960_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvbsky_state *state = adap_to_priv(adap);
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct i2c_adapter *i2c_adapter;
+	struct m88ds3103_platform_data m88ds3103_pdata = {};
+	struct ts2020_config ts2020_config = {};
+
+	/* attach demod */
+	m88ds3103_pdata.clk = 27000000;
+	m88ds3103_pdata.i2c_wr_max = 33;
+	m88ds3103_pdata.clk_out = 0;
+	m88ds3103_pdata.ts_mode = M88DS3103_TS_CI;
+	m88ds3103_pdata.ts_clk = 16000;
+	m88ds3103_pdata.ts_clk_pol = 0;
+	m88ds3103_pdata.agc = 0x99;
+	m88ds3103_pdata.lnb_hv_pol = 1,
+	m88ds3103_pdata.lnb_en_pol = 1,
+
+	state->i2c_client_demod = dvb_module_probe("m88ds3103", NULL,
+						   &d->i2c_adap,
+						   0x68, &m88ds3103_pdata);
+	if (!state->i2c_client_demod)
+		return -ENODEV;
+
+	adap->fe[0] = m88ds3103_pdata.get_dvb_frontend(state->i2c_client_demod);
+	i2c_adapter = m88ds3103_pdata.get_i2c_adapter(state->i2c_client_demod);
+
+	/* attach tuner */
+	ts2020_config.fe = adap->fe[0];
+	ts2020_config.get_agc_pwm = m88ds3103_get_agc_pwm;
+
+	state->i2c_client_tuner = dvb_module_probe("ts2020", NULL,
+						   i2c_adapter,
+						   0x60, &ts2020_config);
+	if (!state->i2c_client_tuner) {
+		dvb_module_release(state->i2c_client_demod);
+		return -ENODEV;
+	}
+
+	/* delegate signal strength measurement to tuner */
+	adap->fe[0]->ops.read_signal_strength =
+			adap->fe[0]->ops.tuner_ops.get_rf_strength;
+
+	/* hook fe: need to resync the slave fifo when signal locks. */
+	state->fe_read_status = adap->fe[0]->ops.read_status;
+	adap->fe[0]->ops.read_status = dvbsky_usb_read_status;
+
+	/* hook fe: LNB off/on is control by Cypress usb chip. */
+	state->fe_set_voltage = adap->fe[0]->ops.set_voltage;
+	adap->fe[0]->ops.set_voltage = dvbsky_usb_set_voltage;
+
+	return 0;
+}
+
+static int dvbsky_usb_ci_set_voltage(struct dvb_frontend *fe,
+	enum fe_sec_voltage voltage)
+{
+	struct dvb_usb_device *d = fe_to_d(fe);
+	struct dvbsky_state *state = d_to_priv(d);
+	u8 value;
+
+	if (voltage == SEC_VOLTAGE_OFF)
+		value = 0;
+	else
+		value = 1;
+	dvbsky_gpio_ctrl(d, 0x00, value);
+
+	return state->fe_set_voltage(fe, voltage);
+}
+
+static int dvbsky_ci_ctrl(void *priv, u8 read, int addr,
+					u8 data, int *mem)
+{
+	struct dvb_usb_device *d = priv;
+	int ret = 0;
+	u8 command[4], respond[2], command_size, respond_size;
+
+	command[1] = (u8)((addr >> 8) & 0xff); /*high part of address*/
+	command[2] = (u8)(addr & 0xff); /*low part of address*/
+	if (read) {
+		command[0] = 0x71;
+		command_size = 3;
+		respond_size = 2;
+	} else {
+		command[0] = 0x70;
+		command[3] = data;
+		command_size = 4;
+		respond_size = 1;
+	}
+	ret = dvbsky_usb_generic_rw(d, command, command_size,
+			respond, respond_size);
+	if (ret)
+		goto err;
+	if (read)
+		*mem = respond[1];
+	return ret;
+err:
+	dev_err(&d->udev->dev, "ci control failed=%d\n", ret);
+	return ret;
+}
+
+static int dvbsky_s960c_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvbsky_state *state = adap_to_priv(adap);
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct i2c_adapter *i2c_adapter;
+	struct m88ds3103_platform_data m88ds3103_pdata = {};
+	struct ts2020_config ts2020_config = {};
+	struct sp2_config sp2_config = {};
+
+	/* attach demod */
+	m88ds3103_pdata.clk = 27000000,
+	m88ds3103_pdata.i2c_wr_max = 33,
+	m88ds3103_pdata.clk_out = 0,
+	m88ds3103_pdata.ts_mode = M88DS3103_TS_CI,
+	m88ds3103_pdata.ts_clk = 10000,
+	m88ds3103_pdata.ts_clk_pol = 1,
+	m88ds3103_pdata.agc = 0x99,
+	m88ds3103_pdata.lnb_hv_pol = 0,
+	m88ds3103_pdata.lnb_en_pol = 1,
+
+	state->i2c_client_demod = dvb_module_probe("m88ds3103", NULL,
+						   &d->i2c_adap,
+						   0x68, &m88ds3103_pdata);
+	if (!state->i2c_client_demod)
+		return -ENODEV;
+
+	adap->fe[0] = m88ds3103_pdata.get_dvb_frontend(state->i2c_client_demod);
+	i2c_adapter = m88ds3103_pdata.get_i2c_adapter(state->i2c_client_demod);
+
+	/* attach tuner */
+	ts2020_config.fe = adap->fe[0];
+	ts2020_config.get_agc_pwm = m88ds3103_get_agc_pwm;
+
+	state->i2c_client_tuner = dvb_module_probe("ts2020", NULL,
+						   i2c_adapter,
+						   0x60, &ts2020_config);
+	if (!state->i2c_client_tuner) {
+		dvb_module_release(state->i2c_client_demod);
+		return -ENODEV;
+	}
+
+	/* attach ci controller */
+	sp2_config.dvb_adap = &adap->dvb_adap;
+	sp2_config.priv = d;
+	sp2_config.ci_control = dvbsky_ci_ctrl;
+
+	state->i2c_client_ci = dvb_module_probe("sp2", NULL,
+						&d->i2c_adap,
+						0x40, &sp2_config);
+
+	if (!state->i2c_client_ci) {
+		dvb_module_release(state->i2c_client_tuner);
+		dvb_module_release(state->i2c_client_demod);
+		return -ENODEV;
+	}
+
+	/* delegate signal strength measurement to tuner */
+	adap->fe[0]->ops.read_signal_strength =
+			adap->fe[0]->ops.tuner_ops.get_rf_strength;
+
+	/* hook fe: need to resync the slave fifo when signal locks. */
+	state->fe_read_status = adap->fe[0]->ops.read_status;
+	adap->fe[0]->ops.read_status = dvbsky_usb_read_status;
+
+	/* hook fe: LNB off/on is control by Cypress usb chip. */
+	state->fe_set_voltage = adap->fe[0]->ops.set_voltage;
+	adap->fe[0]->ops.set_voltage = dvbsky_usb_ci_set_voltage;
+
+	return 0;
+}
+
+static int dvbsky_t680c_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvbsky_state *state = adap_to_priv(adap);
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct i2c_adapter *i2c_adapter;
+	struct si2168_config si2168_config = {};
+	struct si2157_config si2157_config = {};
+	struct sp2_config sp2_config = {};
+
+	/* attach demod */
+	si2168_config.i2c_adapter = &i2c_adapter;
+	si2168_config.fe = &adap->fe[0];
+	si2168_config.ts_mode = SI2168_TS_PARALLEL;
+
+	state->i2c_client_demod = dvb_module_probe("si2168", NULL,
+						   &d->i2c_adap,
+						   0x64, &si2168_config);
+	if (!state->i2c_client_demod)
+		return -ENODEV;
+
+	/* attach tuner */
+	si2157_config.fe = adap->fe[0];
+	si2157_config.if_port = 1;
+
+	state->i2c_client_tuner = dvb_module_probe("si2157", NULL,
+						   i2c_adapter,
+						   0x60, &si2157_config);
+	if (!state->i2c_client_tuner) {
+		dvb_module_release(state->i2c_client_demod);
+		return -ENODEV;
+	}
+
+	/* attach ci controller */
+	sp2_config.dvb_adap = &adap->dvb_adap;
+	sp2_config.priv = d;
+	sp2_config.ci_control = dvbsky_ci_ctrl;
+
+	state->i2c_client_ci = dvb_module_probe("sp2", NULL,
+						&d->i2c_adap,
+						0x40, &sp2_config);
+
+	if (!state->i2c_client_ci) {
+		dvb_module_release(state->i2c_client_tuner);
+		dvb_module_release(state->i2c_client_demod);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int dvbsky_t330_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvbsky_state *state = adap_to_priv(adap);
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct i2c_adapter *i2c_adapter;
+	struct si2168_config si2168_config = {};
+	struct si2157_config si2157_config = {};
+
+	/* attach demod */
+	si2168_config.i2c_adapter = &i2c_adapter;
+	si2168_config.fe = &adap->fe[0];
+	si2168_config.ts_mode = SI2168_TS_PARALLEL;
+	si2168_config.ts_clock_gapped = true;
+
+	state->i2c_client_demod = dvb_module_probe("si2168", NULL,
+						   &d->i2c_adap,
+						   0x64, &si2168_config);
+	if (!state->i2c_client_demod)
+		return -ENODEV;
+
+	/* attach tuner */
+	si2157_config.fe = adap->fe[0];
+	si2157_config.if_port = 1;
+
+	state->i2c_client_tuner = dvb_module_probe("si2157", NULL,
+						   i2c_adapter,
+						   0x60, &si2157_config);
+	if (!state->i2c_client_tuner) {
+		dvb_module_release(state->i2c_client_demod);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int dvbsky_mygica_t230c_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvbsky_state *state = adap_to_priv(adap);
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct i2c_adapter *i2c_adapter;
+	struct si2168_config si2168_config = {};
+	struct si2157_config si2157_config = {};
+
+	/* attach demod */
+	si2168_config.i2c_adapter = &i2c_adapter;
+	si2168_config.fe = &adap->fe[0];
+	si2168_config.ts_mode = SI2168_TS_PARALLEL;
+	si2168_config.ts_clock_inv = 1;
+
+	state->i2c_client_demod = dvb_module_probe("si2168", NULL,
+						   &d->i2c_adap,
+						   0x64, &si2168_config);
+	if (!state->i2c_client_demod)
+		return -ENODEV;
+
+	/* attach tuner */
+	si2157_config.fe = adap->fe[0];
+	si2157_config.if_port = 0;
+
+	state->i2c_client_tuner = dvb_module_probe("si2157", "si2141",
+						   i2c_adapter,
+						   0x60, &si2157_config);
+	if (!state->i2c_client_tuner) {
+		dvb_module_release(state->i2c_client_demod);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+
+static int dvbsky_identify_state(struct dvb_usb_device *d, const char **name)
+{
+	dvbsky_gpio_ctrl(d, 0x04, 1);
+	msleep(20);
+	dvbsky_gpio_ctrl(d, 0x83, 0);
+	dvbsky_gpio_ctrl(d, 0xc0, 1);
+	msleep(100);
+	dvbsky_gpio_ctrl(d, 0x83, 1);
+	dvbsky_gpio_ctrl(d, 0xc0, 0);
+	msleep(50);
+
+	return WARM;
+}
+
+static int dvbsky_init(struct dvb_usb_device *d)
+{
+	struct dvbsky_state *state = d_to_priv(d);
+
+	/* use default interface */
+	/*
+	ret = usb_set_interface(d->udev, 0, 0);
+	if (ret)
+		return ret;
+	*/
+	mutex_init(&state->stream_mutex);
+
+	state->last_lock = 0;
+
+	return 0;
+}
+
+static void dvbsky_exit(struct dvb_usb_device *d)
+{
+	struct dvbsky_state *state = d_to_priv(d);
+	struct dvb_usb_adapter *adap = &d->adapter[0];
+
+	dvb_module_release(state->i2c_client_tuner);
+	dvb_module_release(state->i2c_client_demod);
+	dvb_module_release(state->i2c_client_ci);
+
+	adap->fe[0] = NULL;
+}
+
+/* DVB USB Driver stuff */
+static struct dvb_usb_device_properties dvbsky_s960_props = {
+	.driver_name = KBUILD_MODNAME,
+	.owner = THIS_MODULE,
+	.adapter_nr = adapter_nr,
+	.size_of_priv = sizeof(struct dvbsky_state),
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+	.generic_bulk_ctrl_endpoint_response = 0x81,
+	.generic_bulk_ctrl_delay = DVBSKY_MSG_DELAY,
+
+	.i2c_algo         = &dvbsky_i2c_algo,
+	.frontend_attach  = dvbsky_s960_attach,
+	.init             = dvbsky_init,
+	.get_rc_config    = dvbsky_get_rc_config,
+	.streaming_ctrl   = dvbsky_streaming_ctrl,
+	.identify_state	  = dvbsky_identify_state,
+	.exit             = dvbsky_exit,
+	.read_mac_address = dvbsky_read_mac_addr,
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+			.stream = DVB_USB_STREAM_BULK(0x82, 8, 4096),
+		}
+	}
+};
+
+static struct dvb_usb_device_properties dvbsky_s960c_props = {
+	.driver_name = KBUILD_MODNAME,
+	.owner = THIS_MODULE,
+	.adapter_nr = adapter_nr,
+	.size_of_priv = sizeof(struct dvbsky_state),
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+	.generic_bulk_ctrl_endpoint_response = 0x81,
+	.generic_bulk_ctrl_delay = DVBSKY_MSG_DELAY,
+
+	.i2c_algo         = &dvbsky_i2c_algo,
+	.frontend_attach  = dvbsky_s960c_attach,
+	.init             = dvbsky_init,
+	.get_rc_config    = dvbsky_get_rc_config,
+	.streaming_ctrl   = dvbsky_streaming_ctrl,
+	.identify_state	  = dvbsky_identify_state,
+	.exit             = dvbsky_exit,
+	.read_mac_address = dvbsky_read_mac_addr,
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+			.stream = DVB_USB_STREAM_BULK(0x82, 8, 4096),
+		}
+	}
+};
+
+static struct dvb_usb_device_properties dvbsky_t680c_props = {
+	.driver_name = KBUILD_MODNAME,
+	.owner = THIS_MODULE,
+	.adapter_nr = adapter_nr,
+	.size_of_priv = sizeof(struct dvbsky_state),
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+	.generic_bulk_ctrl_endpoint_response = 0x81,
+	.generic_bulk_ctrl_delay = DVBSKY_MSG_DELAY,
+
+	.i2c_algo         = &dvbsky_i2c_algo,
+	.frontend_attach  = dvbsky_t680c_attach,
+	.init             = dvbsky_init,
+	.get_rc_config    = dvbsky_get_rc_config,
+	.streaming_ctrl   = dvbsky_streaming_ctrl,
+	.identify_state	  = dvbsky_identify_state,
+	.exit             = dvbsky_exit,
+	.read_mac_address = dvbsky_read_mac_addr,
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+			.stream = DVB_USB_STREAM_BULK(0x82, 8, 4096),
+		}
+	}
+};
+
+static struct dvb_usb_device_properties dvbsky_t330_props = {
+	.driver_name = KBUILD_MODNAME,
+	.owner = THIS_MODULE,
+	.adapter_nr = adapter_nr,
+	.size_of_priv = sizeof(struct dvbsky_state),
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+	.generic_bulk_ctrl_endpoint_response = 0x81,
+	.generic_bulk_ctrl_delay = DVBSKY_MSG_DELAY,
+
+	.i2c_algo         = &dvbsky_i2c_algo,
+	.frontend_attach  = dvbsky_t330_attach,
+	.init             = dvbsky_init,
+	.get_rc_config    = dvbsky_get_rc_config,
+	.streaming_ctrl   = dvbsky_streaming_ctrl,
+	.identify_state	  = dvbsky_identify_state,
+	.exit             = dvbsky_exit,
+	.read_mac_address = dvbsky_read_mac_addr,
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+			.stream = DVB_USB_STREAM_BULK(0x82, 8, 4096),
+		}
+	}
+};
+
+static struct dvb_usb_device_properties mygica_t230c_props = {
+	.driver_name = KBUILD_MODNAME,
+	.owner = THIS_MODULE,
+	.adapter_nr = adapter_nr,
+	.size_of_priv = sizeof(struct dvbsky_state),
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+	.generic_bulk_ctrl_endpoint_response = 0x81,
+	.generic_bulk_ctrl_delay = DVBSKY_MSG_DELAY,
+
+	.i2c_algo         = &dvbsky_i2c_algo,
+	.frontend_attach  = dvbsky_mygica_t230c_attach,
+	.init             = dvbsky_init,
+	.get_rc_config    = dvbsky_get_rc_config,
+	.streaming_ctrl   = dvbsky_streaming_ctrl,
+	.identify_state	  = dvbsky_identify_state,
+	.exit             = dvbsky_exit,
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+			.stream = DVB_USB_STREAM_BULK(0x82, 8, 4096),
+		}
+	}
+};
+
+static const struct usb_device_id dvbsky_id_table[] = {
+	{ DVB_USB_DEVICE(0x0572, 0x6831,
+		&dvbsky_s960_props, "DVBSky S960/S860", RC_MAP_DVBSKY) },
+	{ DVB_USB_DEVICE(0x0572, 0x960c,
+		&dvbsky_s960c_props, "DVBSky S960CI", RC_MAP_DVBSKY) },
+	{ DVB_USB_DEVICE(0x0572, 0x680c,
+		&dvbsky_t680c_props, "DVBSky T680CI", RC_MAP_DVBSKY) },
+	{ DVB_USB_DEVICE(0x0572, 0x0320,
+		&dvbsky_t330_props, "DVBSky T330", RC_MAP_DVBSKY) },
+	{ DVB_USB_DEVICE(USB_VID_TECHNOTREND,
+		USB_PID_TECHNOTREND_TVSTICK_CT2_4400,
+		&dvbsky_t330_props, "TechnoTrend TVStick CT2-4400",
+		RC_MAP_TT_1500) },
+	{ DVB_USB_DEVICE(USB_VID_TECHNOTREND,
+		USB_PID_TECHNOTREND_CONNECT_CT2_4650_CI,
+		&dvbsky_t680c_props, "TechnoTrend TT-connect CT2-4650 CI",
+		RC_MAP_TT_1500) },
+	{ DVB_USB_DEVICE(USB_VID_TECHNOTREND,
+		USB_PID_TECHNOTREND_CONNECT_CT2_4650_CI_2,
+		&dvbsky_t680c_props, "TechnoTrend TT-connect CT2-4650 CI v1.1",
+		RC_MAP_TT_1500) },
+	{ DVB_USB_DEVICE(USB_VID_TECHNOTREND,
+		USB_PID_TECHNOTREND_CONNECT_S2_4650_CI,
+		&dvbsky_s960c_props, "TechnoTrend TT-connect S2-4650 CI",
+		RC_MAP_TT_1500) },
+	{ DVB_USB_DEVICE(USB_VID_TERRATEC,
+		USB_PID_TERRATEC_H7_3,
+		&dvbsky_t680c_props, "Terratec H7 Rev.4",
+		RC_MAP_TT_1500) },
+	{ DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_CINERGY_S2_R4,
+		&dvbsky_s960_props, "Terratec Cinergy S2 Rev.4",
+		RC_MAP_DVBSKY) },
+	{ DVB_USB_DEVICE(USB_VID_CONEXANT, USB_PID_MYGICA_T230C,
+		&mygica_t230c_props, "MyGica Mini DVB-T2 USB Stick T230C",
+		RC_MAP_TOTAL_MEDIA_IN_HAND_02) },
+	{ }
+};
+MODULE_DEVICE_TABLE(usb, dvbsky_id_table);
+
+static struct usb_driver dvbsky_usb_driver = {
+	.name = KBUILD_MODNAME,
+	.id_table = dvbsky_id_table,
+	.probe = dvb_usbv2_probe,
+	.disconnect = dvb_usbv2_disconnect,
+	.suspend = dvb_usbv2_suspend,
+	.resume = dvb_usbv2_resume,
+	.reset_resume = dvb_usbv2_reset_resume,
+	.no_dynamic_id = 1,
+	.soft_unbind = 1,
+};
+
+module_usb_driver(dvbsky_usb_driver);
+
+MODULE_AUTHOR("Max nibble <nibble.max@gmail.com>");
+MODULE_DESCRIPTION("Driver for DVBSky USB");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb-v2/ec168.c b/drivers/media/usb/dvb-usb-v2/ec168.c
new file mode 100644
index 0000000..1db8aee
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/ec168.c
@@ -0,0 +1,381 @@
+/*
+ * E3C EC168 DVB USB driver
+ *
+ * Copyright (C) 2009 Antti Palosaari <crope@iki.fi>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ */
+
+#include "ec168.h"
+#include "ec100.h"
+#include "mxl5005s.h"
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static int ec168_ctrl_msg(struct dvb_usb_device *d, struct ec168_req *req)
+{
+	int ret;
+	unsigned int pipe;
+	u8 request, requesttype;
+	u8 *buf;
+
+	switch (req->cmd) {
+	case DOWNLOAD_FIRMWARE:
+	case GPIO:
+	case WRITE_I2C:
+	case STREAMING_CTRL:
+		requesttype = (USB_TYPE_VENDOR | USB_DIR_OUT);
+		request = req->cmd;
+		break;
+	case READ_I2C:
+		requesttype = (USB_TYPE_VENDOR | USB_DIR_IN);
+		request = req->cmd;
+		break;
+	case GET_CONFIG:
+		requesttype = (USB_TYPE_VENDOR | USB_DIR_IN);
+		request = CONFIG;
+		break;
+	case SET_CONFIG:
+		requesttype = (USB_TYPE_VENDOR | USB_DIR_OUT);
+		request = CONFIG;
+		break;
+	case WRITE_DEMOD:
+		requesttype = (USB_TYPE_VENDOR | USB_DIR_OUT);
+		request = DEMOD_RW;
+		break;
+	case READ_DEMOD:
+		requesttype = (USB_TYPE_VENDOR | USB_DIR_IN);
+		request = DEMOD_RW;
+		break;
+	default:
+		dev_err(&d->udev->dev, "%s: unknown command=%02x\n",
+				KBUILD_MODNAME, req->cmd);
+		ret = -EINVAL;
+		goto error;
+	}
+
+	buf = kmalloc(req->size, GFP_KERNEL);
+	if (!buf) {
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	if (requesttype == (USB_TYPE_VENDOR | USB_DIR_OUT)) {
+		/* write */
+		memcpy(buf, req->data, req->size);
+		pipe = usb_sndctrlpipe(d->udev, 0);
+	} else {
+		/* read */
+		pipe = usb_rcvctrlpipe(d->udev, 0);
+	}
+
+	msleep(1); /* avoid I2C errors */
+
+	ret = usb_control_msg(d->udev, pipe, request, requesttype, req->value,
+		req->index, buf, req->size, EC168_USB_TIMEOUT);
+
+	dvb_usb_dbg_usb_control_msg(d->udev, request, requesttype, req->value,
+			req->index, buf, req->size);
+
+	if (ret < 0)
+		goto err_dealloc;
+	else
+		ret = 0;
+
+	/* read request, copy returned data to return buf */
+	if (!ret && requesttype == (USB_TYPE_VENDOR | USB_DIR_IN))
+		memcpy(req->data, buf, req->size);
+
+	kfree(buf);
+	return ret;
+
+err_dealloc:
+	kfree(buf);
+error:
+	dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+
+/* I2C */
+static struct ec100_config ec168_ec100_config;
+
+static int ec168_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[],
+	int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	struct ec168_req req;
+	int i = 0;
+	int ret;
+
+	if (num > 2)
+		return -EINVAL;
+
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	while (i < num) {
+		if (num > i + 1 && (msg[i+1].flags & I2C_M_RD)) {
+			if (msg[i].addr == ec168_ec100_config.demod_address) {
+				req.cmd = READ_DEMOD;
+				req.value = 0;
+				req.index = 0xff00 + msg[i].buf[0]; /* reg */
+				req.size = msg[i+1].len; /* bytes to read */
+				req.data = &msg[i+1].buf[0];
+				ret = ec168_ctrl_msg(d, &req);
+				i += 2;
+			} else {
+				dev_err(&d->udev->dev, "%s: I2C read not " \
+						"implemented\n",
+						KBUILD_MODNAME);
+				ret = -EOPNOTSUPP;
+				i += 2;
+			}
+		} else {
+			if (msg[i].addr == ec168_ec100_config.demod_address) {
+				req.cmd = WRITE_DEMOD;
+				req.value = msg[i].buf[1]; /* val */
+				req.index = 0xff00 + msg[i].buf[0]; /* reg */
+				req.size = 0;
+				req.data = NULL;
+				ret = ec168_ctrl_msg(d, &req);
+				i += 1;
+			} else {
+				req.cmd = WRITE_I2C;
+				req.value = msg[i].buf[0]; /* val */
+				req.index = 0x0100 + msg[i].addr; /* I2C addr */
+				req.size = msg[i].len-1;
+				req.data = &msg[i].buf[1];
+				ret = ec168_ctrl_msg(d, &req);
+				i += 1;
+			}
+		}
+		if (ret)
+			goto error;
+
+	}
+	ret = i;
+
+error:
+	mutex_unlock(&d->i2c_mutex);
+	return ret;
+}
+
+static u32 ec168_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm ec168_i2c_algo = {
+	.master_xfer   = ec168_i2c_xfer,
+	.functionality = ec168_i2c_func,
+};
+
+/* Callbacks for DVB USB */
+static int ec168_identify_state(struct dvb_usb_device *d, const char **name)
+{
+	int ret;
+	u8 reply;
+	struct ec168_req req = {GET_CONFIG, 0, 1, sizeof(reply), &reply};
+	dev_dbg(&d->udev->dev, "%s:\n", __func__);
+
+	ret = ec168_ctrl_msg(d, &req);
+	if (ret)
+		goto error;
+
+	dev_dbg(&d->udev->dev, "%s: reply=%02x\n", __func__, reply);
+
+	if (reply == 0x01)
+		ret = WARM;
+	else
+		ret = COLD;
+
+	return ret;
+error:
+	dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+
+static int ec168_download_firmware(struct dvb_usb_device *d,
+		const struct firmware *fw)
+{
+	int ret, len, remaining;
+	struct ec168_req req = {DOWNLOAD_FIRMWARE, 0, 0, 0, NULL};
+	dev_dbg(&d->udev->dev, "%s:\n", __func__);
+
+	#define LEN_MAX 2048 /* max packet size */
+	for (remaining = fw->size; remaining > 0; remaining -= LEN_MAX) {
+		len = remaining;
+		if (len > LEN_MAX)
+			len = LEN_MAX;
+
+		req.size = len;
+		req.data = (u8 *) &fw->data[fw->size - remaining];
+		req.index = fw->size - remaining;
+
+		ret = ec168_ctrl_msg(d, &req);
+		if (ret) {
+			dev_err(&d->udev->dev,
+					"%s: firmware download failed=%d\n",
+					KBUILD_MODNAME, ret);
+			goto error;
+		}
+	}
+
+	req.size = 0;
+
+	/* set "warm"? */
+	req.cmd = SET_CONFIG;
+	req.value = 0;
+	req.index = 0x0001;
+	ret = ec168_ctrl_msg(d, &req);
+	if (ret)
+		goto error;
+
+	/* really needed - no idea what does */
+	req.cmd = GPIO;
+	req.value = 0;
+	req.index = 0x0206;
+	ret = ec168_ctrl_msg(d, &req);
+	if (ret)
+		goto error;
+
+	/* activate tuner I2C? */
+	req.cmd = WRITE_I2C;
+	req.value = 0;
+	req.index = 0x00c6;
+	ret = ec168_ctrl_msg(d, &req);
+	if (ret)
+		goto error;
+
+	return ret;
+error:
+	dev_dbg(&d->udev->dev, "%s: failed=%d\n", __func__, ret);
+	return ret;
+}
+
+static struct ec100_config ec168_ec100_config = {
+	.demod_address = 0xff, /* not real address, demod is integrated */
+};
+
+static int ec168_ec100_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *d = adap_to_d(adap);
+	dev_dbg(&d->udev->dev, "%s:\n", __func__);
+
+	adap->fe[0] = dvb_attach(ec100_attach, &ec168_ec100_config,
+			&d->i2c_adap);
+	if (adap->fe[0] == NULL)
+		return -ENODEV;
+
+	return 0;
+}
+
+static struct mxl5005s_config ec168_mxl5003s_config = {
+	.i2c_address     = 0xc6,
+	.if_freq         = IF_FREQ_4570000HZ,
+	.xtal_freq       = CRYSTAL_FREQ_16000000HZ,
+	.agc_mode        = MXL_SINGLE_AGC,
+	.tracking_filter = MXL_TF_OFF,
+	.rssi_enable     = MXL_RSSI_ENABLE,
+	.cap_select      = MXL_CAP_SEL_ENABLE,
+	.div_out         = MXL_DIV_OUT_4,
+	.clock_out       = MXL_CLOCK_OUT_DISABLE,
+	.output_load     = MXL5005S_IF_OUTPUT_LOAD_200_OHM,
+	.top		 = MXL5005S_TOP_25P2,
+	.mod_mode        = MXL_DIGITAL_MODE,
+	.if_mode         = MXL_ZERO_IF,
+	.AgcMasterByte   = 0x00,
+};
+
+static int ec168_mxl5003s_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *d = adap_to_d(adap);
+	dev_dbg(&d->udev->dev, "%s:\n", __func__);
+
+	return dvb_attach(mxl5005s_attach, adap->fe[0], &d->i2c_adap,
+			&ec168_mxl5003s_config) == NULL ? -ENODEV : 0;
+}
+
+static int ec168_streaming_ctrl(struct dvb_frontend *fe, int onoff)
+{
+	struct dvb_usb_device *d = fe_to_d(fe);
+	struct ec168_req req = {STREAMING_CTRL, 0x7f01, 0x0202, 0, NULL};
+	dev_dbg(&d->udev->dev, "%s: onoff=%d\n", __func__, onoff);
+
+	if (onoff)
+		req.index = 0x0102;
+	return ec168_ctrl_msg(d, &req);
+}
+
+/* DVB USB Driver stuff */
+/* bInterfaceNumber 0 is HID
+ * bInterfaceNumber 1 is DVB-T */
+static struct dvb_usb_device_properties ec168_props = {
+	.driver_name = KBUILD_MODNAME,
+	.owner = THIS_MODULE,
+	.adapter_nr = adapter_nr,
+	.bInterfaceNumber = 1,
+
+	.identify_state = ec168_identify_state,
+	.firmware = EC168_FIRMWARE,
+	.download_firmware = ec168_download_firmware,
+
+	.i2c_algo = &ec168_i2c_algo,
+	.frontend_attach = ec168_ec100_frontend_attach,
+	.tuner_attach = ec168_mxl5003s_tuner_attach,
+	.streaming_ctrl = ec168_streaming_ctrl,
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+			.stream = DVB_USB_STREAM_BULK(0x82, 6, 32 * 512),
+		}
+	},
+};
+
+static const struct dvb_usb_driver_info ec168_driver_info = {
+	.name = "E3C EC168 reference design",
+	.props = &ec168_props,
+};
+
+static const struct usb_device_id ec168_id[] = {
+	{ USB_DEVICE(USB_VID_E3C, USB_PID_E3C_EC168),
+		.driver_info = (kernel_ulong_t) &ec168_driver_info },
+	{ USB_DEVICE(USB_VID_E3C, USB_PID_E3C_EC168_2),
+		.driver_info = (kernel_ulong_t) &ec168_driver_info },
+	{ USB_DEVICE(USB_VID_E3C, USB_PID_E3C_EC168_3),
+		.driver_info = (kernel_ulong_t) &ec168_driver_info },
+	{ USB_DEVICE(USB_VID_E3C, USB_PID_E3C_EC168_4),
+		.driver_info = (kernel_ulong_t) &ec168_driver_info },
+	{ USB_DEVICE(USB_VID_E3C, USB_PID_E3C_EC168_5),
+		.driver_info = (kernel_ulong_t) &ec168_driver_info },
+	{}
+};
+MODULE_DEVICE_TABLE(usb, ec168_id);
+
+static struct usb_driver ec168_driver = {
+	.name = KBUILD_MODNAME,
+	.id_table = ec168_id,
+	.probe = dvb_usbv2_probe,
+	.disconnect = dvb_usbv2_disconnect,
+	.suspend = dvb_usbv2_suspend,
+	.resume = dvb_usbv2_resume,
+	.no_dynamic_id = 1,
+	.soft_unbind = 1,
+};
+
+module_usb_driver(ec168_driver);
+
+MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>");
+MODULE_DESCRIPTION("E3C EC168 driver");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(EC168_FIRMWARE);
diff --git a/drivers/media/usb/dvb-usb-v2/ec168.h b/drivers/media/usb/dvb-usb-v2/ec168.h
new file mode 100644
index 0000000..704955b
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/ec168.h
@@ -0,0 +1,49 @@
+/*
+ * E3C EC168 DVB USB driver
+ *
+ * Copyright (C) 2009 Antti Palosaari <crope@iki.fi>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ */
+
+#ifndef EC168_H
+#define EC168_H
+
+#include "dvb_usb.h"
+
+#define EC168_USB_TIMEOUT 1000
+#define EC168_FIRMWARE "dvb-usb-ec168.fw"
+
+struct ec168_req {
+	u8  cmd;       /* [1] */
+	u16 value;     /* [2|3] */
+	u16 index;     /* [4|5] */
+	u16 size;      /* [6|7] */
+	u8  *data;
+};
+
+enum ec168_cmd {
+	DOWNLOAD_FIRMWARE    = 0x00,
+	CONFIG               = 0x01,
+	DEMOD_RW             = 0x03,
+	GPIO                 = 0x04,
+	STREAMING_CTRL       = 0x10,
+	READ_I2C             = 0x20,
+	WRITE_I2C            = 0x21,
+	HID_DOWNLOAD         = 0x30,
+	GET_CONFIG,
+	SET_CONFIG,
+	READ_DEMOD,
+	WRITE_DEMOD,
+};
+
+#endif
diff --git a/drivers/media/usb/dvb-usb-v2/gl861.c b/drivers/media/usb/dvb-usb-v2/gl861.c
new file mode 100644
index 0000000..3338b21
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/gl861.c
@@ -0,0 +1,661 @@
+/* DVB USB compliant linux driver for GL861 USB2.0 devices.
+ *
+ *	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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#include <linux/string.h>
+
+#include "gl861.h"
+
+#include "zl10353.h"
+#include "qt1010.h"
+#include "tc90522.h"
+#include "dvb-pll.h"
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static int gl861_i2c_msg(struct dvb_usb_device *d, u8 addr,
+			 u8 *wbuf, u16 wlen, u8 *rbuf, u16 rlen)
+{
+	u16 index;
+	u16 value = addr << (8 + 1);
+	int wo = (rbuf == NULL || rlen == 0); /* write-only */
+	u8 req, type;
+	u8 *buf;
+	int ret;
+
+	if (wo) {
+		req = GL861_REQ_I2C_WRITE;
+		type = GL861_WRITE;
+		buf = kmemdup(wbuf, wlen, GFP_KERNEL);
+	} else { /* rw */
+		req = GL861_REQ_I2C_READ;
+		type = GL861_READ;
+		buf = kmalloc(rlen, GFP_KERNEL);
+	}
+	if (!buf)
+		return -ENOMEM;
+
+	switch (wlen) {
+	case 1:
+		index = wbuf[0];
+		break;
+	case 2:
+		index = wbuf[0];
+		value = value + wbuf[1];
+		break;
+	default:
+		dev_err(&d->udev->dev, "%s: wlen=%d, aborting\n",
+				KBUILD_MODNAME, wlen);
+		kfree(buf);
+		return -EINVAL;
+	}
+
+	usleep_range(1000, 2000); /* avoid I2C errors */
+
+	ret = usb_control_msg(d->udev, usb_rcvctrlpipe(d->udev, 0), req, type,
+			      value, index, buf, rlen, 2000);
+
+	if (!wo && ret > 0)
+		memcpy(rbuf, buf, rlen);
+
+	kfree(buf);
+	return ret;
+}
+
+/* I2C */
+static int gl861_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[],
+			  int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	int i;
+
+	if (num > 2)
+		return -EINVAL;
+
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	for (i = 0; i < num; i++) {
+		/* write/read request */
+		if (i+1 < num && (msg[i+1].flags & I2C_M_RD)) {
+			if (gl861_i2c_msg(d, msg[i].addr, msg[i].buf,
+				msg[i].len, msg[i+1].buf, msg[i+1].len) < 0)
+				break;
+			i++;
+		} else
+			if (gl861_i2c_msg(d, msg[i].addr, msg[i].buf,
+					  msg[i].len, NULL, 0) < 0)
+				break;
+	}
+
+	mutex_unlock(&d->i2c_mutex);
+	return i;
+}
+
+static u32 gl861_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm gl861_i2c_algo = {
+	.master_xfer   = gl861_i2c_xfer,
+	.functionality = gl861_i2c_func,
+};
+
+/* Callbacks for DVB USB */
+static struct zl10353_config gl861_zl10353_config = {
+	.demod_address = 0x0f,
+	.no_tuner = 1,
+	.parallel_ts = 1,
+};
+
+static int gl861_frontend_attach(struct dvb_usb_adapter *adap)
+{
+
+	adap->fe[0] = dvb_attach(zl10353_attach, &gl861_zl10353_config,
+		&adap_to_d(adap)->i2c_adap);
+	if (adap->fe[0] == NULL)
+		return -EIO;
+
+	return 0;
+}
+
+static struct qt1010_config gl861_qt1010_config = {
+	.i2c_address = 0x62
+};
+
+static int gl861_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	return dvb_attach(qt1010_attach,
+			  adap->fe[0], &adap_to_d(adap)->i2c_adap,
+			  &gl861_qt1010_config) == NULL ? -ENODEV : 0;
+}
+
+static int gl861_init(struct dvb_usb_device *d)
+{
+	/*
+	 * There is 2 interfaces. Interface 0 is for TV and interface 1 is
+	 * for HID remote controller. Interface 0 has 2 alternate settings.
+	 * For some reason we need to set interface explicitly, defaulted
+	 * as alternate setting 1?
+	 */
+	return usb_set_interface(d->udev, 0, 0);
+}
+
+/* DVB USB Driver stuff */
+static struct dvb_usb_device_properties gl861_props = {
+	.driver_name = KBUILD_MODNAME,
+	.owner = THIS_MODULE,
+	.adapter_nr = adapter_nr,
+
+	.i2c_algo = &gl861_i2c_algo,
+	.frontend_attach = gl861_frontend_attach,
+	.tuner_attach = gl861_tuner_attach,
+	.init = gl861_init,
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+			.stream = DVB_USB_STREAM_BULK(0x81, 7, 512),
+		}
+	}
+};
+
+
+/*
+ * For Friio
+ */
+
+struct friio_priv {
+	struct i2c_adapter *demod_sub_i2c;
+	struct i2c_client  *i2c_client_demod;
+	struct i2c_client  *i2c_client_tuner;
+	struct i2c_adapter tuner_adap;
+};
+
+struct friio_config {
+	struct i2c_board_info demod_info;
+	struct tc90522_config demod_cfg;
+
+	struct i2c_board_info tuner_info;
+	struct dvb_pll_config tuner_cfg;
+};
+
+static const struct friio_config friio_config = {
+	.demod_info = { I2C_BOARD_INFO(TC90522_I2C_DEV_TER, 0x18), },
+	.tuner_info = { I2C_BOARD_INFO("tua6034_friio", 0x60), },
+};
+
+/* For another type of I2C:
+ * message sent by a USB control-read/write transaction with data stage.
+ * Used in init/config of Friio.
+ */
+static int
+gl861_i2c_write_ex(struct dvb_usb_device *d, u8 addr, u8 *wbuf, u16 wlen)
+{
+	u8 *buf;
+	int ret;
+
+	buf = kmalloc(wlen, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	memcpy(buf, wbuf, wlen);
+	ret = usb_control_msg(d->udev, usb_sndctrlpipe(d->udev, 0),
+				 GL861_REQ_I2C_RAW, GL861_WRITE,
+				 addr << (8 + 1), 0x0100, buf, wlen, 2000);
+	kfree(buf);
+	return ret;
+}
+
+static int
+gl861_i2c_read_ex(struct dvb_usb_device *d, u8 addr, u8 *rbuf, u16 rlen)
+{
+	u8 *buf;
+	int ret;
+
+	buf = kmalloc(rlen, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	ret = usb_control_msg(d->udev, usb_rcvctrlpipe(d->udev, 0),
+				 GL861_REQ_I2C_READ, GL861_READ,
+				 addr << (8 + 1), 0x0100, buf, rlen, 2000);
+	if (ret > 0 && rlen > 0)
+		memcpy(buf, rbuf, rlen);
+	kfree(buf);
+	return ret;
+}
+
+/* For I2C transactions to the tuner of Friio (dvb_pll).
+ *
+ * Friio uses irregular USB encapsulation for tuner i2c transactions:
+ * write transacions are encapsulated with a different USB 'request' value.
+ *
+ * Although all transactions are sent via the demod(tc90522)
+ * and the demod provides an i2c adapter for them, it cannot be used in Friio
+ * since it assumes using the same parent adapter with the demod,
+ * which does not use the request value and uses same one for both read/write.
+ * So we define a dedicated i2c adapter here.
+ */
+
+static int
+friio_i2c_tuner_read(struct dvb_usb_device *d, struct i2c_msg *msg)
+{
+	struct friio_priv *priv;
+	u8 addr;
+
+	priv = d_to_priv(d);
+	addr = priv->i2c_client_demod->addr;
+	return gl861_i2c_read_ex(d, addr, msg->buf, msg->len);
+}
+
+static int
+friio_i2c_tuner_write(struct dvb_usb_device *d, struct i2c_msg *msg)
+{
+	u8 *buf;
+	int ret;
+	struct friio_priv *priv;
+
+	priv = d_to_priv(d);
+
+	if (msg->len < 1)
+		return -EINVAL;
+
+	buf = kmalloc(msg->len + 1, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+	buf[0] = msg->addr << 1;
+	memcpy(buf + 1, msg->buf, msg->len);
+
+	ret = usb_control_msg(d->udev, usb_sndctrlpipe(d->udev, 0),
+				 GL861_REQ_I2C_RAW, GL861_WRITE,
+				 priv->i2c_client_demod->addr << (8 + 1),
+				 0xFE, buf, msg->len + 1, 2000);
+	kfree(buf);
+	return ret;
+}
+
+static int friio_tuner_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[],
+				int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	int i;
+
+	if (num > 2)
+		return -EINVAL;
+
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	for (i = 0; i < num; i++) {
+		int ret;
+
+		if (msg[i].flags & I2C_M_RD)
+			ret = friio_i2c_tuner_read(d, &msg[i]);
+		else
+			ret = friio_i2c_tuner_write(d, &msg[i]);
+
+		if (ret < 0)
+			break;
+
+		usleep_range(1000, 2000); /* avoid I2C errors */
+	}
+
+	mutex_unlock(&d->i2c_mutex);
+	return i;
+}
+
+static struct i2c_algorithm friio_tuner_i2c_algo = {
+	.master_xfer   = friio_tuner_i2c_xfer,
+	.functionality = gl861_i2c_func,
+};
+
+/* GPIO control in Friio */
+
+#define FRIIO_CTL_LNB (1 << 0)
+#define FRIIO_CTL_STROBE (1 << 1)
+#define FRIIO_CTL_CLK (1 << 2)
+#define FRIIO_CTL_LED (1 << 3)
+
+#define FRIIO_LED_RUNNING 0x6400ff64
+#define FRIIO_LED_STOPPED 0x96ff00ff
+
+/* control PIC16F676 attached to Friio */
+static int friio_ext_ctl(struct dvb_usb_device *d,
+			    u32 sat_color, int power_on)
+{
+	int i, ret;
+	struct i2c_msg msg;
+	u8 *buf;
+	u32 mask;
+	u8 power = (power_on) ? FRIIO_CTL_LNB : 0;
+
+	buf = kmalloc(2, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	msg.addr = 0x00;
+	msg.flags = 0;
+	msg.len = 2;
+	msg.buf = buf;
+	buf[0] = 0x00;
+
+	/* send 2bit header (&B10) */
+	buf[1] = power | FRIIO_CTL_LED | FRIIO_CTL_STROBE;
+	ret = i2c_transfer(&d->i2c_adap, &msg, 1);
+	buf[1] |= FRIIO_CTL_CLK;
+	ret += i2c_transfer(&d->i2c_adap, &msg, 1);
+
+	buf[1] = power | FRIIO_CTL_STROBE;
+	ret += i2c_transfer(&d->i2c_adap, &msg, 1);
+	buf[1] |= FRIIO_CTL_CLK;
+	ret += i2c_transfer(&d->i2c_adap, &msg, 1);
+
+	/* send 32bit(satur, R, G, B) data in serial */
+	mask = 1 << 31;
+	for (i = 0; i < 32; i++) {
+		buf[1] = power | FRIIO_CTL_STROBE;
+		if (sat_color & mask)
+			buf[1] |= FRIIO_CTL_LED;
+		ret += i2c_transfer(&d->i2c_adap, &msg, 1);
+		buf[1] |= FRIIO_CTL_CLK;
+		ret += i2c_transfer(&d->i2c_adap, &msg, 1);
+		mask >>= 1;
+	}
+
+	/* set the strobe off */
+	buf[1] = power;
+	ret += i2c_transfer(&d->i2c_adap, &msg, 1);
+	buf[1] |= FRIIO_CTL_CLK;
+	ret += i2c_transfer(&d->i2c_adap, &msg, 1);
+
+	kfree(buf);
+	return (ret == 70) ? 0 : -EREMOTEIO;
+}
+
+/* init/config of gl861 for Friio */
+/* NOTE:
+ * This function cannot be moved to friio_init()/dvb_usbv2_init(),
+ * because the init defined here must be done before any activities like I2C,
+ * but friio_init() is called by dvb-usbv2 after {_frontend, _tuner}_attach(),
+ * where I2C communication is used.
+ * Thus this function is set to be called from _power_ctl().
+ *
+ * Since it will be called on the early init stage
+ * where the i2c adapter is not initialized yet,
+ * we cannot use i2c_transfer() here.
+ */
+static int friio_reset(struct dvb_usb_device *d)
+{
+	int i, ret;
+	u8 wbuf[2], rbuf[2];
+
+	static const u8 friio_init_cmds[][2] = {
+		{0x33, 0x08}, {0x37, 0x40}, {0x3a, 0x1f}, {0x3b, 0xff},
+		{0x3c, 0x1f}, {0x3d, 0xff}, {0x38, 0x00}, {0x35, 0x00},
+		{0x39, 0x00}, {0x36, 0x00},
+	};
+
+	ret = usb_set_interface(d->udev, 0, 0);
+	if (ret < 0)
+		return ret;
+
+	wbuf[0] = 0x11;
+	wbuf[1] = 0x02;
+	ret = gl861_i2c_msg(d, 0x00, wbuf, 2, NULL, 0);
+	if (ret < 0)
+		return ret;
+	usleep_range(2000, 3000);
+
+	wbuf[0] = 0x11;
+	wbuf[1] = 0x00;
+	ret = gl861_i2c_msg(d, 0x00, wbuf, 2, NULL, 0);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * Check if the dev is really a Friio White, since it might be
+	 * another device, Friio Black, with the same VID/PID.
+	 */
+
+	usleep_range(1000, 2000);
+	wbuf[0] = 0x03;
+	wbuf[1] = 0x80;
+	ret = gl861_i2c_write_ex(d, 0x09, wbuf, 2);
+	if (ret < 0)
+		return ret;
+
+	usleep_range(2000, 3000);
+	ret = gl861_i2c_read_ex(d, 0x09, rbuf, 2);
+	if (ret < 0)
+		return ret;
+	if (rbuf[0] != 0xff || rbuf[1] != 0xff)
+		return -ENODEV;
+
+
+	usleep_range(1000, 2000);
+	ret = gl861_i2c_write_ex(d, 0x48, wbuf, 2);
+	if (ret < 0)
+		return ret;
+
+	usleep_range(2000, 3000);
+	ret = gl861_i2c_read_ex(d, 0x48, rbuf, 2);
+	if (ret < 0)
+		return ret;
+	if (rbuf[0] != 0xff || rbuf[1] != 0xff)
+		return -ENODEV;
+
+	wbuf[0] = 0x30;
+	wbuf[1] = 0x04;
+	ret = gl861_i2c_msg(d, 0x00, wbuf, 2, NULL, 0);
+	if (ret < 0)
+		return ret;
+
+	wbuf[0] = 0x00;
+	wbuf[1] = 0x01;
+	ret = gl861_i2c_msg(d, 0x00, wbuf, 2, NULL, 0);
+	if (ret < 0)
+		return ret;
+
+	wbuf[0] = 0x06;
+	wbuf[1] = 0x0f;
+	ret = gl861_i2c_msg(d, 0x00, wbuf, 2, NULL, 0);
+	if (ret < 0)
+		return ret;
+
+	for (i = 0; i < ARRAY_SIZE(friio_init_cmds); i++) {
+		ret = gl861_i2c_msg(d, 0x00, (u8 *)friio_init_cmds[i], 2,
+				      NULL, 0);
+		if (ret < 0)
+			return ret;
+	}
+	return 0;
+}
+
+/*
+ * DVB callbacks for Friio
+ */
+
+static int friio_power_ctrl(struct dvb_usb_device *d, int onoff)
+{
+	return onoff ? friio_reset(d) : 0;
+}
+
+static int friio_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	const struct i2c_board_info *info;
+	struct dvb_usb_device *d;
+	struct tc90522_config cfg;
+	struct i2c_client *cl;
+	struct friio_priv *priv;
+
+	info = &friio_config.demod_info;
+	d = adap_to_d(adap);
+	cl = dvb_module_probe("tc90522", info->type,
+			      &d->i2c_adap, info->addr, &cfg);
+	if (!cl)
+		return -ENODEV;
+	adap->fe[0] = cfg.fe;
+
+	/* ignore cfg.tuner_i2c and create new one */
+	priv = adap_to_priv(adap);
+	priv->i2c_client_demod = cl;
+	priv->tuner_adap.algo = &friio_tuner_i2c_algo;
+	priv->tuner_adap.dev.parent = &d->udev->dev;
+	strlcpy(priv->tuner_adap.name, d->name, sizeof(priv->tuner_adap.name));
+	strlcat(priv->tuner_adap.name, "-tuner", sizeof(priv->tuner_adap.name));
+	priv->demod_sub_i2c = &priv->tuner_adap;
+	i2c_set_adapdata(&priv->tuner_adap, d);
+
+	return i2c_add_adapter(&priv->tuner_adap);
+}
+
+static int friio_frontend_detach(struct dvb_usb_adapter *adap)
+{
+	struct friio_priv *priv;
+
+	priv = adap_to_priv(adap);
+	i2c_del_adapter(&priv->tuner_adap);
+	dvb_module_release(priv->i2c_client_demod);
+	return 0;
+}
+
+static int friio_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	const struct i2c_board_info *info;
+	struct dvb_pll_config cfg;
+	struct i2c_client *cl;
+	struct friio_priv *priv;
+
+	priv = adap_to_priv(adap);
+	info = &friio_config.tuner_info;
+	cfg = friio_config.tuner_cfg;
+	cfg.fe = adap->fe[0];
+
+	cl = dvb_module_probe("dvb_pll", info->type,
+			      priv->demod_sub_i2c, info->addr, &cfg);
+	if (!cl)
+		return -ENODEV;
+	priv->i2c_client_tuner = cl;
+	return 0;
+}
+
+static int friio_tuner_detach(struct dvb_usb_adapter *adap)
+{
+	struct friio_priv *priv;
+
+	priv = adap_to_priv(adap);
+	dvb_module_release(priv->i2c_client_tuner);
+	return 0;
+}
+
+static int friio_init(struct dvb_usb_device *d)
+{
+	int i;
+	int ret;
+	struct friio_priv *priv;
+
+	static const u8 demod_init[][2] = {
+		{0x01, 0x40}, {0x04, 0x38}, {0x05, 0x40}, {0x07, 0x40},
+		{0x0f, 0x4f}, {0x11, 0x21}, {0x12, 0x0b}, {0x13, 0x2f},
+		{0x14, 0x31}, {0x16, 0x02}, {0x21, 0xc4}, {0x22, 0x20},
+		{0x2c, 0x79}, {0x2d, 0x34}, {0x2f, 0x00}, {0x30, 0x28},
+		{0x31, 0x31}, {0x32, 0xdf}, {0x38, 0x01}, {0x39, 0x78},
+		{0x3b, 0x33}, {0x3c, 0x33}, {0x48, 0x90}, {0x51, 0x68},
+		{0x5e, 0x38}, {0x71, 0x00}, {0x72, 0x08}, {0x77, 0x00},
+		{0xc0, 0x21}, {0xc1, 0x10}, {0xe4, 0x1a}, {0xea, 0x1f},
+		{0x77, 0x00}, {0x71, 0x00}, {0x71, 0x00}, {0x76, 0x0c},
+	};
+
+	/* power on LNA? */
+	ret = friio_ext_ctl(d, FRIIO_LED_STOPPED, true);
+	if (ret < 0)
+		return ret;
+	msleep(20);
+
+	/* init/config demod */
+	priv = d_to_priv(d);
+	for (i = 0; i < ARRAY_SIZE(demod_init); i++) {
+		int ret;
+
+		ret = i2c_master_send(priv->i2c_client_demod, demod_init[i], 2);
+		if (ret < 0)
+			return ret;
+	}
+	msleep(100);
+	return 0;
+}
+
+static void friio_exit(struct dvb_usb_device *d)
+{
+	friio_ext_ctl(d, FRIIO_LED_STOPPED, false);
+}
+
+static int friio_streaming_ctrl(struct dvb_frontend *fe, int onoff)
+{
+	u32 led_color;
+
+	led_color = onoff ? FRIIO_LED_RUNNING : FRIIO_LED_STOPPED;
+	return friio_ext_ctl(fe_to_d(fe), led_color, true);
+}
+
+
+static struct dvb_usb_device_properties friio_props = {
+	.driver_name = KBUILD_MODNAME,
+	.owner = THIS_MODULE,
+	.adapter_nr = adapter_nr,
+
+	.size_of_priv = sizeof(struct friio_priv),
+
+	.i2c_algo = &gl861_i2c_algo,
+	.power_ctrl = friio_power_ctrl,
+	.frontend_attach = friio_frontend_attach,
+	.frontend_detach = friio_frontend_detach,
+	.tuner_attach = friio_tuner_attach,
+	.tuner_detach = friio_tuner_detach,
+	.init = friio_init,
+	.exit = friio_exit,
+	.streaming_ctrl = friio_streaming_ctrl,
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+			.stream = DVB_USB_STREAM_BULK(0x01, 8, 16384),
+		}
+	}
+};
+
+static const struct usb_device_id gl861_id_table[] = {
+	{ DVB_USB_DEVICE(USB_VID_MSI, USB_PID_MSI_MEGASKY580_55801,
+		&gl861_props, "MSI Mega Sky 55801 DVB-T USB2.0", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_ALINK, USB_VID_ALINK_DTU,
+		&gl861_props, "A-LINK DTU DVB-T USB2.0", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_774, USB_PID_FRIIO_WHITE,
+		&friio_props, "774 Friio White ISDB-T USB2.0", NULL) },
+	{ }
+};
+MODULE_DEVICE_TABLE(usb, gl861_id_table);
+
+static struct usb_driver gl861_usb_driver = {
+	.name = KBUILD_MODNAME,
+	.id_table = gl861_id_table,
+	.probe = dvb_usbv2_probe,
+	.disconnect = dvb_usbv2_disconnect,
+	.suspend = dvb_usbv2_suspend,
+	.resume = dvb_usbv2_resume,
+	.reset_resume = dvb_usbv2_reset_resume,
+	.no_dynamic_id = 1,
+	.soft_unbind = 1,
+};
+
+module_usb_driver(gl861_usb_driver);
+
+MODULE_AUTHOR("Carl Lundqvist <comabug@gmail.com>");
+MODULE_DESCRIPTION("Driver MSI Mega Sky 580 DVB-T USB2.0 / GL861");
+MODULE_VERSION("0.1");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb-v2/gl861.h b/drivers/media/usb/dvb-usb-v2/gl861.h
new file mode 100644
index 0000000..02c00e1
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/gl861.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _DVB_USB_GL861_H_
+#define _DVB_USB_GL861_H_
+
+#include "dvb_usb.h"
+
+#define GL861_WRITE		0x40
+#define GL861_READ		0xc0
+
+#define GL861_REQ_I2C_WRITE	0x01
+#define GL861_REQ_I2C_READ	0x02
+#define GL861_REQ_I2C_RAW	0x03
+
+#endif
diff --git a/drivers/media/usb/dvb-usb-v2/lmedm04.c b/drivers/media/usb/dvb-usb-v2/lmedm04.c
new file mode 100644
index 0000000..0750a97
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/lmedm04.c
@@ -0,0 +1,1355 @@
+/* DVB USB compliant linux driver for
+ *
+ * DM04/QQBOX DVB-S USB BOX	LME2510C + SHARP:BS2F7HZ7395
+ *				LME2510C + LG TDQY-P001F
+ *				LME2510C + BS2F7HZ0194
+ *				LME2510 + LG TDQY-P001F
+ *				LME2510 + BS2F7HZ0194
+ *
+ * MVB7395 (LME2510C+SHARP:BS2F7HZ7395)
+ * SHARP:BS2F7HZ7395 = (STV0288+Sharp IX2505V)
+ *
+ * MV001F (LME2510+LGTDQY-P001F)
+ * LG TDQY - P001F =(TDA8263 + TDA10086H)
+ *
+ * MVB0001F (LME2510C+LGTDQT-P001F)
+ *
+ * MV0194 (LME2510+SHARP:BS2F7HZ0194)
+ * SHARP:BS2F7HZ0194 = (STV0299+IX2410)
+ *
+ * MVB0194 (LME2510C+SHARP0194)
+ *
+ * LME2510C + M88RS2000
+ *
+ * For firmware see Documentation/media/dvb-drivers/lmedm04.rst
+ *
+ * I2C addresses:
+ * 0xd0 - STV0288	- Demodulator
+ * 0xc0 - Sharp IX2505V	- Tuner
+ * --
+ * 0x1c - TDA10086   - Demodulator
+ * 0xc0 - TDA8263    - Tuner
+ * --
+ * 0xd0 - STV0299	- Demodulator
+ * 0xc0 - IX2410	- Tuner
+ *
+ *
+ * VID = 3344  PID LME2510=1122 LME2510C=1120
+ *
+ * Copyright (C) 2010 Malcolm Priestley (tvboxspy@gmail.com)
+ * LME2510(C)(C) Leaguerme (Shenzhen) MicroElectronics Co., Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License Version 2, as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ *
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ *
+ * Known Issues :
+ *	LME2510: Non Intel USB chipsets fail to maintain High Speed on
+ * Boot or Hot Plug.
+ *
+ * QQbox suffers from noise on LNB voltage.
+ *
+ *	LME2510: SHARP:BS2F7HZ0194(MV0194) cannot cold reset and share system
+ * with other tuners. After a cold reset streaming will not start.
+ *
+ * M88RS2000 suffers from loss of lock.
+ */
+#define DVB_USB_LOG_PREFIX "LME2510(C)"
+#include <linux/usb.h>
+#include <linux/usb/input.h>
+#include <media/rc-core.h>
+
+#include "dvb_usb.h"
+#include "lmedm04.h"
+#include "tda826x.h"
+#include "tda10086.h"
+#include "stv0288.h"
+#include "ix2505v.h"
+#include "stv0299.h"
+#include "dvb-pll.h"
+#include "z0194a.h"
+#include "m88rs2000.h"
+#include "ts2020.h"
+
+
+#define LME2510_C_S7395	"dvb-usb-lme2510c-s7395.fw";
+#define LME2510_C_LG	"dvb-usb-lme2510c-lg.fw";
+#define LME2510_C_S0194	"dvb-usb-lme2510c-s0194.fw";
+#define LME2510_C_RS2000 "dvb-usb-lme2510c-rs2000.fw";
+#define LME2510_LG	"dvb-usb-lme2510-lg.fw";
+#define LME2510_S0194	"dvb-usb-lme2510-s0194.fw";
+
+/* debug */
+static int dvb_usb_lme2510_debug;
+#define lme_debug(var, level, args...) do { \
+	if ((var >= level)) \
+		pr_debug(DVB_USB_LOG_PREFIX": " args); \
+} while (0)
+#define deb_info(level, args...) lme_debug(dvb_usb_lme2510_debug, level, args)
+#define debug_data_snipet(level, name, p) \
+	 deb_info(level, name" (%8phN)", p);
+#define info(args...) pr_info(DVB_USB_LOG_PREFIX": "args)
+
+module_param_named(debug, dvb_usb_lme2510_debug, int, 0644);
+MODULE_PARM_DESC(debug, "set debugging level (1=info (or-able)).");
+
+static int dvb_usb_lme2510_firmware;
+module_param_named(firmware, dvb_usb_lme2510_firmware, int, 0644);
+MODULE_PARM_DESC(firmware, "set default firmware 0=Sharp7395 1=LG");
+
+static int pid_filter;
+module_param_named(pid, pid_filter, int, 0644);
+MODULE_PARM_DESC(pid, "set default 0=default 1=off 2=on");
+
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+#define TUNER_DEFAULT	0x0
+#define TUNER_LG	0x1
+#define TUNER_S7395	0x2
+#define TUNER_S0194	0x3
+#define TUNER_RS2000	0x4
+
+struct lme2510_state {
+	unsigned long int_urb_due;
+	enum fe_status lock_status;
+	u8 id;
+	u8 tuner_config;
+	u8 signal_level;
+	u8 signal_sn;
+	u8 time_key;
+	u8 i2c_talk_onoff;
+	u8 i2c_gate;
+	u8 i2c_tuner_gate_w;
+	u8 i2c_tuner_gate_r;
+	u8 i2c_tuner_addr;
+	u8 stream_on;
+	u8 pid_size;
+	u8 pid_off;
+	void *buffer;
+	struct urb *lme_urb;
+	void *usb_buffer;
+	/* Frontend original calls */
+	int (*fe_read_status)(struct dvb_frontend *, enum fe_status *);
+	int (*fe_read_signal_strength)(struct dvb_frontend *, u16 *);
+	int (*fe_read_snr)(struct dvb_frontend *, u16 *);
+	int (*fe_read_ber)(struct dvb_frontend *, u32 *);
+	int (*fe_read_ucblocks)(struct dvb_frontend *, u32 *);
+	int (*fe_set_voltage)(struct dvb_frontend *, enum fe_sec_voltage);
+	u8 dvb_usb_lme2510_firmware;
+};
+
+static int lme2510_bulk_write(struct usb_device *dev,
+				u8 *snd, int len, u8 pipe)
+{
+	int actual_l;
+
+	return usb_bulk_msg(dev, usb_sndbulkpipe(dev, pipe),
+			    snd, len, &actual_l, 100);
+}
+
+static int lme2510_bulk_read(struct usb_device *dev,
+				u8 *rev, int len, u8 pipe)
+{
+	int actual_l;
+
+	return usb_bulk_msg(dev, usb_rcvbulkpipe(dev, pipe),
+			    rev, len, &actual_l, 200);
+}
+
+static int lme2510_usb_talk(struct dvb_usb_device *d,
+		u8 *wbuf, int wlen, u8 *rbuf, int rlen)
+{
+	struct lme2510_state *st = d->priv;
+	u8 *buff;
+	int ret = 0;
+
+	if (st->usb_buffer == NULL) {
+		st->usb_buffer = kmalloc(64, GFP_KERNEL);
+		if (st->usb_buffer == NULL) {
+			info("MEM Error no memory");
+			return -ENOMEM;
+		}
+	}
+	buff = st->usb_buffer;
+
+	ret = mutex_lock_interruptible(&d->usb_mutex);
+
+	if (ret < 0)
+		return -EAGAIN;
+
+	/* the read/write capped at 64 */
+	memcpy(buff, wbuf, (wlen < 64) ? wlen : 64);
+
+	ret |= lme2510_bulk_write(d->udev, buff, wlen , 0x01);
+
+	ret |= lme2510_bulk_read(d->udev, buff, (rlen < 64) ?
+			rlen : 64 , 0x01);
+
+	if (rlen > 0)
+		memcpy(rbuf, buff, rlen);
+
+	mutex_unlock(&d->usb_mutex);
+
+	return (ret < 0) ? -ENODEV : 0;
+}
+
+static int lme2510_stream_restart(struct dvb_usb_device *d)
+{
+	struct lme2510_state *st = d->priv;
+	u8 all_pids[] = LME_ALL_PIDS;
+	u8 stream_on[] = LME_ST_ON_W;
+	u8 rbuff[1];
+	if (st->pid_off)
+		lme2510_usb_talk(d, all_pids, sizeof(all_pids),
+				 rbuff, sizeof(rbuff));
+	/*Restart Stream Command*/
+	return lme2510_usb_talk(d, stream_on, sizeof(stream_on),
+				rbuff, sizeof(rbuff));
+}
+
+static int lme2510_enable_pid(struct dvb_usb_device *d, u8 index, u16 pid_out)
+{
+	struct lme2510_state *st = d->priv;
+	static u8 pid_buff[] = LME_ZERO_PID;
+	static u8 rbuf[1];
+	u8 pid_no = index * 2;
+	u8 pid_len = pid_no + 2;
+	int ret = 0;
+	deb_info(1, "PID Setting Pid %04x", pid_out);
+
+	if (st->pid_size == 0)
+		ret |= lme2510_stream_restart(d);
+
+	pid_buff[2] = pid_no;
+	pid_buff[3] = (u8)pid_out & 0xff;
+	pid_buff[4] = pid_no + 1;
+	pid_buff[5] = (u8)(pid_out >> 8);
+
+	if (pid_len > st->pid_size)
+		st->pid_size = pid_len;
+	pid_buff[7] = 0x80 + st->pid_size;
+
+	ret |= lme2510_usb_talk(d, pid_buff ,
+		sizeof(pid_buff) , rbuf, sizeof(rbuf));
+
+	if (st->stream_on)
+		ret |= lme2510_stream_restart(d);
+
+	return ret;
+}
+
+/* Convert range from 0x00-0xff to 0x0000-0xffff */
+#define reg_to_16bits(x)	((x) | ((x) << 8))
+
+static void lme2510_update_stats(struct dvb_usb_adapter *adap)
+{
+	struct lme2510_state *st = adap_to_priv(adap);
+	struct dvb_frontend *fe = adap->fe[0];
+	struct dtv_frontend_properties *c;
+	u32 s_tmp = 0, c_tmp = 0;
+
+	if (!fe)
+		return;
+
+	c = &fe->dtv_property_cache;
+
+	c->block_count.len = 1;
+	c->block_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
+	c->block_error.len = 1;
+	c->block_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
+	c->post_bit_count.len = 1;
+	c->post_bit_count.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
+	c->post_bit_error.len = 1;
+	c->post_bit_error.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
+
+	if (st->i2c_talk_onoff) {
+		c->strength.len = 1;
+		c->strength.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
+		c->cnr.len = 1;
+		c->cnr.stat[0].scale = FE_SCALE_NOT_AVAILABLE;
+		return;
+	}
+
+	switch (st->tuner_config) {
+	case TUNER_LG:
+		s_tmp = reg_to_16bits(0xff - st->signal_level);
+		c_tmp = reg_to_16bits(0xff - st->signal_sn);
+		break;
+	case TUNER_S7395:
+	case TUNER_S0194:
+		s_tmp = 0xffff - (((st->signal_level * 2) << 8) * 5 / 4);
+		c_tmp = reg_to_16bits((0xff - st->signal_sn - 0xa1) * 3);
+		break;
+	case TUNER_RS2000:
+		s_tmp = reg_to_16bits(st->signal_level);
+		c_tmp = reg_to_16bits(st->signal_sn);
+	}
+
+	c->strength.len = 1;
+	c->strength.stat[0].scale = FE_SCALE_RELATIVE;
+	c->strength.stat[0].uvalue = (u64)s_tmp;
+
+	c->cnr.len = 1;
+	c->cnr.stat[0].scale = FE_SCALE_RELATIVE;
+	c->cnr.stat[0].uvalue = (u64)c_tmp;
+}
+
+static void lme2510_int_response(struct urb *lme_urb)
+{
+	struct dvb_usb_adapter *adap = lme_urb->context;
+	struct lme2510_state *st = adap_to_priv(adap);
+	u8 *ibuf, *rbuf;
+	int i = 0, offset;
+	u32 key;
+	u8 signal_lock = 0;
+
+	switch (lme_urb->status) {
+	case 0:
+	case -ETIMEDOUT:
+		break;
+	case -ECONNRESET:
+	case -ENOENT:
+	case -ESHUTDOWN:
+		return;
+	default:
+		info("Error %x", lme_urb->status);
+		break;
+	}
+
+	rbuf = (u8 *) lme_urb->transfer_buffer;
+
+	offset = ((lme_urb->actual_length/8) > 4)
+			? 4 : (lme_urb->actual_length/8) ;
+
+	for (i = 0; i < offset; ++i) {
+		ibuf = (u8 *)&rbuf[i*8];
+		deb_info(5, "INT O/S C =%02x C/O=%02x Type =%02x%02x",
+		offset, i, ibuf[0], ibuf[1]);
+
+		switch (ibuf[0]) {
+		case 0xaa:
+			debug_data_snipet(1, "INT Remote data snipet", ibuf);
+			if (!adap_to_d(adap)->rc_dev)
+				break;
+
+			key = RC_SCANCODE_NEC32(ibuf[2] << 24 |
+						ibuf[3] << 16 |
+						ibuf[4] << 8  |
+						ibuf[5]);
+
+			deb_info(1, "INT Key = 0x%08x", key);
+			rc_keydown(adap_to_d(adap)->rc_dev, RC_PROTO_NEC32, key,
+				   0);
+			break;
+		case 0xbb:
+			switch (st->tuner_config) {
+			case TUNER_LG:
+				signal_lock = ibuf[2] & BIT(5);
+				st->signal_level = ibuf[4];
+				st->signal_sn = ibuf[3];
+				st->time_key = ibuf[7];
+				break;
+			case TUNER_S7395:
+			case TUNER_S0194:
+				/* Tweak for earlier firmware*/
+				if (ibuf[1] == 0x03) {
+					signal_lock = ibuf[2] & BIT(4);
+					st->signal_level = ibuf[3];
+					st->signal_sn = ibuf[4];
+				} else {
+					st->signal_level = ibuf[4];
+					st->signal_sn = ibuf[5];
+				}
+				break;
+			case TUNER_RS2000:
+				signal_lock = ibuf[2] & 0xee;
+				st->signal_level = ibuf[5];
+				st->signal_sn = ibuf[4];
+				st->time_key = ibuf[7];
+			default:
+				break;
+			}
+
+			/* Interrupt will also throw just BIT 0 as lock */
+			signal_lock |= ibuf[2] & BIT(0);
+
+			if (!signal_lock)
+				st->lock_status &= ~FE_HAS_LOCK;
+
+			lme2510_update_stats(adap);
+
+			debug_data_snipet(5, "INT Remote data snipet in", ibuf);
+		break;
+		case 0xcc:
+			debug_data_snipet(1, "INT Control data snipet", ibuf);
+			break;
+		default:
+			debug_data_snipet(1, "INT Unknown data snipet", ibuf);
+		break;
+		}
+	}
+
+	usb_submit_urb(lme_urb, GFP_ATOMIC);
+
+	/* Interrupt urb is due every 48 msecs while streaming the buffer
+	 * stores up to 4 periods if missed. Allow 200 msec for next interrupt.
+	 */
+	st->int_urb_due = jiffies + msecs_to_jiffies(200);
+}
+
+static int lme2510_int_read(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct lme2510_state *lme_int = adap_to_priv(adap);
+	struct usb_host_endpoint *ep;
+
+	lme_int->lme_urb = usb_alloc_urb(0, GFP_ATOMIC);
+
+	if (lme_int->lme_urb == NULL)
+			return -ENOMEM;
+
+	lme_int->buffer = usb_alloc_coherent(d->udev, 128, GFP_ATOMIC,
+					&lme_int->lme_urb->transfer_dma);
+
+	if (lme_int->buffer == NULL)
+			return -ENOMEM;
+
+	usb_fill_int_urb(lme_int->lme_urb,
+				d->udev,
+				usb_rcvintpipe(d->udev, 0xa),
+				lme_int->buffer,
+				128,
+				lme2510_int_response,
+				adap,
+				8);
+
+	/* Quirk of pipe reporting PIPE_BULK but behaves as interrupt */
+	ep = usb_pipe_endpoint(d->udev, lme_int->lme_urb->pipe);
+
+	if (usb_endpoint_type(&ep->desc) == USB_ENDPOINT_XFER_BULK)
+		lme_int->lme_urb->pipe = usb_rcvbulkpipe(d->udev, 0xa),
+
+	lme_int->lme_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+	usb_submit_urb(lme_int->lme_urb, GFP_ATOMIC);
+	info("INT Interrupt Service Started");
+
+	return 0;
+}
+
+static int lme2510_pid_filter_ctrl(struct dvb_usb_adapter *adap, int onoff)
+{
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct lme2510_state *st = adap_to_priv(adap);
+	static u8 clear_pid_reg[] = LME_ALL_PIDS;
+	static u8 rbuf[1];
+	int ret = 0;
+
+	deb_info(1, "PID Clearing Filter");
+
+	mutex_lock(&d->i2c_mutex);
+
+	if (!onoff) {
+		ret |= lme2510_usb_talk(d, clear_pid_reg,
+			sizeof(clear_pid_reg), rbuf, sizeof(rbuf));
+		st->pid_off = true;
+	} else
+		st->pid_off = false;
+
+	st->pid_size = 0;
+
+	mutex_unlock(&d->i2c_mutex);
+
+	return 0;
+}
+
+static int lme2510_pid_filter(struct dvb_usb_adapter *adap, int index, u16 pid,
+	int onoff)
+{
+	struct dvb_usb_device *d = adap_to_d(adap);
+	int ret = 0;
+
+	deb_info(3, "%s PID=%04x Index=%04x onoff=%02x", __func__,
+		pid, index, onoff);
+
+	if (onoff) {
+		mutex_lock(&d->i2c_mutex);
+		ret |= lme2510_enable_pid(d, index, pid);
+		mutex_unlock(&d->i2c_mutex);
+	}
+
+
+	return ret;
+}
+
+
+static int lme2510_return_status(struct dvb_usb_device *d)
+{
+	int ret;
+	u8 *data;
+
+	data = kzalloc(6, GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	ret = usb_control_msg(d->udev, usb_rcvctrlpipe(d->udev, 0),
+			      0x06, 0x80, 0x0302, 0x00,
+			      data, 0x6, 200);
+	if (ret != 6)
+		ret = -EINVAL;
+	else
+		ret = data[2];
+
+	info("Firmware Status: %6ph", data);
+
+	kfree(data);
+	return ret;
+}
+
+static int lme2510_msg(struct dvb_usb_device *d,
+		u8 *wbuf, int wlen, u8 *rbuf, int rlen)
+{
+	struct lme2510_state *st = d->priv;
+
+	st->i2c_talk_onoff = 1;
+
+	return lme2510_usb_talk(d, wbuf, wlen, rbuf, rlen);
+}
+
+static int lme2510_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[],
+				 int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	struct lme2510_state *st = d->priv;
+	static u8 obuf[64], ibuf[64];
+	int i, read, read_o;
+	u16 len;
+	u8 gate = st->i2c_gate;
+
+	mutex_lock(&d->i2c_mutex);
+
+	if (gate == 0)
+		gate = 5;
+
+	for (i = 0; i < num; i++) {
+		read_o = msg[i].flags & I2C_M_RD;
+		read = i + 1 < num && msg[i + 1].flags & I2C_M_RD;
+		read |= read_o;
+		gate = (msg[i].addr == st->i2c_tuner_addr)
+			? (read)	? st->i2c_tuner_gate_r
+					: st->i2c_tuner_gate_w
+			: st->i2c_gate;
+		obuf[0] = gate | (read << 7);
+
+		if (gate == 5)
+			obuf[1] = (read) ? 2 : msg[i].len + 1;
+		else
+			obuf[1] = msg[i].len + read + 1;
+
+		obuf[2] = msg[i].addr << 1;
+
+		if (read) {
+			if (read_o)
+				len = 3;
+			else {
+				memcpy(&obuf[3], msg[i].buf, msg[i].len);
+				obuf[msg[i].len+3] = msg[i+1].len;
+				len = msg[i].len+4;
+			}
+		} else {
+			memcpy(&obuf[3], msg[i].buf, msg[i].len);
+			len = msg[i].len+3;
+		}
+
+		if (lme2510_msg(d, obuf, len, ibuf, 64) < 0) {
+			deb_info(1, "i2c transfer failed.");
+			mutex_unlock(&d->i2c_mutex);
+			return -EAGAIN;
+		}
+
+		if (read) {
+			if (read_o)
+				memcpy(msg[i].buf, &ibuf[1], msg[i].len);
+			else {
+				memcpy(msg[i+1].buf, &ibuf[1], msg[i+1].len);
+				i++;
+			}
+		}
+	}
+
+	mutex_unlock(&d->i2c_mutex);
+	return i;
+}
+
+static u32 lme2510_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm lme2510_i2c_algo = {
+	.master_xfer   = lme2510_i2c_xfer,
+	.functionality = lme2510_i2c_func,
+};
+
+static int lme2510_streaming_ctrl(struct dvb_frontend *fe, int onoff)
+{
+	struct dvb_usb_adapter *adap = fe_to_adap(fe);
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct lme2510_state *st = adap_to_priv(adap);
+	static u8 clear_reg_3[] = LME_ALL_PIDS;
+	static u8 rbuf[1];
+	int ret = 0, rlen = sizeof(rbuf);
+
+	deb_info(1, "STM  (%02x)", onoff);
+
+	/* Streaming is started by FE_HAS_LOCK */
+	if (onoff == 1)
+		st->stream_on = 1;
+	else {
+		deb_info(1, "STM Steam Off");
+		/* mutex is here only to avoid collision with I2C */
+		mutex_lock(&d->i2c_mutex);
+
+		ret = lme2510_usb_talk(d, clear_reg_3,
+				sizeof(clear_reg_3), rbuf, rlen);
+		st->stream_on = 0;
+		st->i2c_talk_onoff = 1;
+
+		mutex_unlock(&d->i2c_mutex);
+	}
+
+	return (ret < 0) ? -ENODEV : 0;
+}
+
+static u8 check_sum(u8 *p, u8 len)
+{
+	u8 sum = 0;
+	while (len--)
+		sum += *p++;
+	return sum;
+}
+
+static int lme2510_download_firmware(struct dvb_usb_device *d,
+					const struct firmware *fw)
+{
+	int ret = 0;
+	u8 *data;
+	u16 j, wlen, len_in, start, end;
+	u8 packet_size, dlen, i;
+	u8 *fw_data;
+
+	packet_size = 0x31;
+	len_in = 1;
+
+	data = kzalloc(128, GFP_KERNEL);
+	if (!data) {
+		info("FRM Could not start Firmware Download"\
+			"(Buffer allocation failed)");
+		return -ENOMEM;
+	}
+
+	info("FRM Starting Firmware Download");
+
+	for (i = 1; i < 3; i++) {
+		start = (i == 1) ? 0 : 512;
+		end = (i == 1) ? 512 : fw->size;
+		for (j = start; j < end; j += (packet_size+1)) {
+			fw_data = (u8 *)(fw->data + j);
+			if ((end - j) > packet_size) {
+				data[0] = i;
+				dlen = packet_size;
+			} else {
+				data[0] = i | 0x80;
+				dlen = (u8)(end - j)-1;
+			}
+			data[1] = dlen;
+			memcpy(&data[2], fw_data, dlen+1);
+			wlen = (u8) dlen + 4;
+			data[wlen-1] = check_sum(fw_data, dlen+1);
+			deb_info(1, "Data S=%02x:E=%02x CS= %02x", data[3],
+				data[dlen+2], data[dlen+3]);
+			lme2510_usb_talk(d, data, wlen, data, len_in);
+			ret |= (data[0] == 0x88) ? 0 : -1;
+		}
+	}
+
+	data[0] = 0x8a;
+	len_in = 1;
+	msleep(2000);
+	lme2510_usb_talk(d, data, len_in, data, len_in);
+	msleep(400);
+
+	if (ret < 0)
+		info("FRM Firmware Download Failed (%04x)" , ret);
+	else
+		info("FRM Firmware Download Completed - Resetting Device");
+
+	kfree(data);
+	return RECONNECTS_USB;
+}
+
+static void lme_coldreset(struct dvb_usb_device *d)
+{
+	u8 data[1] = {0};
+	data[0] = 0x0a;
+	info("FRM Firmware Cold Reset");
+
+	lme2510_usb_talk(d, data, sizeof(data), data, sizeof(data));
+
+	return;
+}
+
+static const char fw_c_s7395[] = LME2510_C_S7395;
+static const char fw_c_lg[] = LME2510_C_LG;
+static const char fw_c_s0194[] = LME2510_C_S0194;
+static const char fw_c_rs2000[] = LME2510_C_RS2000;
+static const char fw_lg[] = LME2510_LG;
+static const char fw_s0194[] = LME2510_S0194;
+
+static const char *lme_firmware_switch(struct dvb_usb_device *d, int cold)
+{
+	struct lme2510_state *st = d->priv;
+	struct usb_device *udev = d->udev;
+	const struct firmware *fw = NULL;
+	const char *fw_lme;
+	int ret = 0;
+
+	cold = (cold > 0) ? (cold & 1) : 0;
+
+	switch (le16_to_cpu(udev->descriptor.idProduct)) {
+	case 0x1122:
+		switch (st->dvb_usb_lme2510_firmware) {
+		default:
+		case TUNER_S0194:
+			fw_lme = fw_s0194;
+			ret = request_firmware(&fw, fw_lme, &udev->dev);
+			if (ret == 0) {
+				st->dvb_usb_lme2510_firmware = TUNER_S0194;
+				cold = 0;
+				break;
+			}
+			/* fall through */
+		case TUNER_LG:
+			fw_lme = fw_lg;
+			ret = request_firmware(&fw, fw_lme, &udev->dev);
+			if (ret == 0) {
+				st->dvb_usb_lme2510_firmware = TUNER_LG;
+				break;
+			}
+			st->dvb_usb_lme2510_firmware = TUNER_DEFAULT;
+			break;
+		}
+		break;
+	case 0x1120:
+		switch (st->dvb_usb_lme2510_firmware) {
+		default:
+		case TUNER_S7395:
+			fw_lme = fw_c_s7395;
+			ret = request_firmware(&fw, fw_lme, &udev->dev);
+			if (ret == 0) {
+				st->dvb_usb_lme2510_firmware = TUNER_S7395;
+				cold = 0;
+				break;
+			}
+			/* fall through */
+		case TUNER_LG:
+			fw_lme = fw_c_lg;
+			ret = request_firmware(&fw, fw_lme, &udev->dev);
+			if (ret == 0) {
+				st->dvb_usb_lme2510_firmware = TUNER_LG;
+				break;
+			}
+			/* fall through */
+		case TUNER_S0194:
+			fw_lme = fw_c_s0194;
+			ret = request_firmware(&fw, fw_lme, &udev->dev);
+			if (ret == 0) {
+				st->dvb_usb_lme2510_firmware = TUNER_S0194;
+				break;
+			}
+			st->dvb_usb_lme2510_firmware = TUNER_DEFAULT;
+			cold = 0;
+			break;
+		}
+		break;
+	case 0x22f0:
+		fw_lme = fw_c_rs2000;
+		st->dvb_usb_lme2510_firmware = TUNER_RS2000;
+		break;
+	default:
+		fw_lme = fw_c_s7395;
+	}
+
+	release_firmware(fw);
+
+	if (cold) {
+		dvb_usb_lme2510_firmware = st->dvb_usb_lme2510_firmware;
+		info("FRM Changing to %s firmware", fw_lme);
+		lme_coldreset(d);
+		return NULL;
+	}
+
+	return fw_lme;
+}
+
+static int lme2510_kill_urb(struct usb_data_stream *stream)
+{
+	int i;
+
+	for (i = 0; i < stream->urbs_submitted; i++) {
+		deb_info(3, "killing URB no. %d.", i);
+		/* stop the URB */
+		usb_kill_urb(stream->urb_list[i]);
+	}
+	stream->urbs_submitted = 0;
+
+	return 0;
+}
+
+static struct tda10086_config tda10086_config = {
+	.demod_address = 0x0e,
+	.invert = 0,
+	.diseqc_tone = 1,
+	.xtal_freq = TDA10086_XTAL_16M,
+};
+
+static struct stv0288_config lme_config = {
+	.demod_address = 0x68,
+	.min_delay_ms = 15,
+	.inittab = s7395_inittab,
+};
+
+static struct ix2505v_config lme_tuner = {
+	.tuner_address = 0x60,
+	.min_delay_ms = 100,
+	.tuner_gain = 0x0,
+	.tuner_chargepump = 0x3,
+};
+
+static struct stv0299_config sharp_z0194_config = {
+	.demod_address = 0x68,
+	.inittab = sharp_z0194a_inittab,
+	.mclk = 88000000UL,
+	.invert = 0,
+	.skip_reinit = 0,
+	.lock_output = STV0299_LOCKOUTPUT_1,
+	.volt13_op0_op1 = STV0299_VOLT13_OP1,
+	.min_delay_ms = 100,
+	.set_symbol_rate = sharp_z0194a_set_symbol_rate,
+};
+
+static struct m88rs2000_config m88rs2000_config = {
+	.demod_addr = 0x68
+};
+
+static struct ts2020_config ts2020_config = {
+	.tuner_address = 0x60,
+	.clk_out_div = 7,
+	.dont_poll = true
+};
+
+static int dm04_lme2510_set_voltage(struct dvb_frontend *fe,
+				    enum fe_sec_voltage voltage)
+{
+	struct dvb_usb_device *d = fe_to_d(fe);
+	struct lme2510_state *st = fe_to_priv(fe);
+	static u8 voltage_low[] = LME_VOLTAGE_L;
+	static u8 voltage_high[] = LME_VOLTAGE_H;
+	static u8 rbuf[1];
+	int ret = 0, len = 3, rlen = 1;
+
+	mutex_lock(&d->i2c_mutex);
+
+	switch (voltage) {
+	case SEC_VOLTAGE_18:
+		ret |= lme2510_usb_talk(d,
+			voltage_high, len, rbuf, rlen);
+		break;
+
+	case SEC_VOLTAGE_OFF:
+	case SEC_VOLTAGE_13:
+	default:
+		ret |= lme2510_usb_talk(d,
+				voltage_low, len, rbuf, rlen);
+		break;
+	}
+
+	mutex_unlock(&d->i2c_mutex);
+
+	if (st->tuner_config == TUNER_RS2000)
+		if (st->fe_set_voltage)
+			st->fe_set_voltage(fe, voltage);
+
+
+	return (ret < 0) ? -ENODEV : 0;
+}
+
+static int dm04_read_status(struct dvb_frontend *fe, enum fe_status *status)
+{
+	struct dvb_usb_device *d = fe_to_d(fe);
+	struct lme2510_state *st = d->priv;
+	int ret = 0;
+
+	if (st->i2c_talk_onoff) {
+		if (st->fe_read_status) {
+			ret = st->fe_read_status(fe, status);
+			if (ret < 0)
+				return ret;
+		}
+
+		st->lock_status = *status;
+
+		if (*status & FE_HAS_LOCK && st->stream_on) {
+			mutex_lock(&d->i2c_mutex);
+
+			st->i2c_talk_onoff = 0;
+			ret = lme2510_stream_restart(d);
+
+			mutex_unlock(&d->i2c_mutex);
+		}
+
+		return ret;
+	}
+
+	/* Timeout of interrupt reached on RS2000 */
+	if (st->tuner_config == TUNER_RS2000 &&
+	    time_after(jiffies, st->int_urb_due))
+		st->lock_status &= ~FE_HAS_LOCK;
+
+	*status = st->lock_status;
+
+	if (!(*status & FE_HAS_LOCK)) {
+		struct dvb_usb_adapter *adap = fe_to_adap(fe);
+
+		st->i2c_talk_onoff = 1;
+
+		lme2510_update_stats(adap);
+	}
+
+	return ret;
+}
+
+static int dm04_read_signal_strength(struct dvb_frontend *fe, u16 *strength)
+{
+	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+	struct lme2510_state *st = fe_to_priv(fe);
+
+	if (st->fe_read_signal_strength && !st->stream_on)
+		return st->fe_read_signal_strength(fe, strength);
+
+	if (c->strength.stat[0].scale == FE_SCALE_RELATIVE)
+		*strength = (u16)c->strength.stat[0].uvalue;
+	else
+		*strength = 0;
+
+	return 0;
+}
+
+static int dm04_read_snr(struct dvb_frontend *fe, u16 *snr)
+{
+	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+	struct lme2510_state *st = fe_to_priv(fe);
+
+	if (st->fe_read_snr && !st->stream_on)
+		return st->fe_read_snr(fe, snr);
+
+	if (c->cnr.stat[0].scale == FE_SCALE_RELATIVE)
+		*snr = (u16)c->cnr.stat[0].uvalue;
+	else
+		*snr = 0;
+
+	return 0;
+}
+
+static int dm04_read_ber(struct dvb_frontend *fe, u32 *ber)
+{
+	struct lme2510_state *st = fe_to_priv(fe);
+
+	if (st->fe_read_ber && !st->stream_on)
+		return st->fe_read_ber(fe, ber);
+
+	*ber = 0;
+
+	return 0;
+}
+
+static int dm04_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks)
+{
+	struct lme2510_state *st = fe_to_priv(fe);
+
+	if (st->fe_read_ucblocks && !st->stream_on)
+		return st->fe_read_ucblocks(fe, ucblocks);
+
+	*ucblocks = 0;
+
+	return 0;
+}
+
+static int lme_name(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct lme2510_state *st = adap_to_priv(adap);
+	const char *desc = d->name;
+	static const char * const fe_name[] = {
+		"", " LG TDQY-P001F", " SHARP:BS2F7HZ7395",
+		" SHARP:BS2F7HZ0194", " RS2000"};
+	char *name = adap->fe[0]->ops.info.name;
+
+	strlcpy(name, desc, 128);
+	strlcat(name, fe_name[st->tuner_config], 128);
+
+	return 0;
+}
+
+static int dm04_lme2510_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct lme2510_state *st = d->priv;
+	int ret = 0;
+
+	st->i2c_talk_onoff = 1;
+	switch (le16_to_cpu(d->udev->descriptor.idProduct)) {
+	case 0x1122:
+	case 0x1120:
+		st->i2c_gate = 4;
+		adap->fe[0] = dvb_attach(tda10086_attach,
+			&tda10086_config, &d->i2c_adap);
+		if (adap->fe[0]) {
+			info("TUN Found Frontend TDA10086");
+			st->i2c_tuner_gate_w = 4;
+			st->i2c_tuner_gate_r = 4;
+			st->i2c_tuner_addr = 0x60;
+			st->tuner_config = TUNER_LG;
+			if (st->dvb_usb_lme2510_firmware != TUNER_LG) {
+				st->dvb_usb_lme2510_firmware = TUNER_LG;
+				ret = lme_firmware_switch(d, 1) ? 0 : -ENODEV;
+			}
+			break;
+		}
+
+		st->i2c_gate = 4;
+		adap->fe[0] = dvb_attach(stv0299_attach,
+				&sharp_z0194_config, &d->i2c_adap);
+		if (adap->fe[0]) {
+			info("FE Found Stv0299");
+			st->i2c_tuner_gate_w = 4;
+			st->i2c_tuner_gate_r = 5;
+			st->i2c_tuner_addr = 0x60;
+			st->tuner_config = TUNER_S0194;
+			if (st->dvb_usb_lme2510_firmware != TUNER_S0194) {
+				st->dvb_usb_lme2510_firmware = TUNER_S0194;
+				ret = lme_firmware_switch(d, 1) ? 0 : -ENODEV;
+			}
+			break;
+		}
+
+		st->i2c_gate = 5;
+		adap->fe[0] = dvb_attach(stv0288_attach, &lme_config,
+			&d->i2c_adap);
+
+		if (adap->fe[0]) {
+			info("FE Found Stv0288");
+			st->i2c_tuner_gate_w = 4;
+			st->i2c_tuner_gate_r = 5;
+			st->i2c_tuner_addr = 0x60;
+			st->tuner_config = TUNER_S7395;
+			if (st->dvb_usb_lme2510_firmware != TUNER_S7395) {
+				st->dvb_usb_lme2510_firmware = TUNER_S7395;
+				ret = lme_firmware_switch(d, 1) ? 0 : -ENODEV;
+			}
+			break;
+		}
+		/* fall through */
+	case 0x22f0:
+		st->i2c_gate = 5;
+		adap->fe[0] = dvb_attach(m88rs2000_attach,
+			&m88rs2000_config, &d->i2c_adap);
+
+		if (adap->fe[0]) {
+			info("FE Found M88RS2000");
+			st->i2c_tuner_gate_w = 5;
+			st->i2c_tuner_gate_r = 5;
+			st->i2c_tuner_addr = 0x60;
+			st->tuner_config = TUNER_RS2000;
+			st->fe_set_voltage =
+				adap->fe[0]->ops.set_voltage;
+		}
+		break;
+	}
+
+	if (adap->fe[0] == NULL) {
+		info("DM04/QQBOX Not Powered up or not Supported");
+		return -ENODEV;
+	}
+
+	if (ret) {
+		if (adap->fe[0]) {
+			dvb_frontend_detach(adap->fe[0]);
+			adap->fe[0] = NULL;
+		}
+		d->rc_map = NULL;
+		return -ENODEV;
+	}
+
+	st->fe_read_status = adap->fe[0]->ops.read_status;
+	st->fe_read_signal_strength = adap->fe[0]->ops.read_signal_strength;
+	st->fe_read_snr = adap->fe[0]->ops.read_snr;
+	st->fe_read_ber = adap->fe[0]->ops.read_ber;
+	st->fe_read_ucblocks = adap->fe[0]->ops.read_ucblocks;
+
+	adap->fe[0]->ops.read_status = dm04_read_status;
+	adap->fe[0]->ops.read_signal_strength = dm04_read_signal_strength;
+	adap->fe[0]->ops.read_snr = dm04_read_snr;
+	adap->fe[0]->ops.read_ber = dm04_read_ber;
+	adap->fe[0]->ops.read_ucblocks = dm04_read_ucblocks;
+	adap->fe[0]->ops.set_voltage = dm04_lme2510_set_voltage;
+
+	ret = lme_name(adap);
+	return ret;
+}
+
+static int dm04_lme2510_tuner(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct lme2510_state *st = adap_to_priv(adap);
+	static const char * const tun_msg[] = {"", "TDA8263", "IX2505V", "DVB_PLL_OPERA", "RS2000"};
+	int ret = 0;
+
+	switch (st->tuner_config) {
+	case TUNER_LG:
+		if (dvb_attach(tda826x_attach, adap->fe[0], 0x60,
+			&d->i2c_adap, 1))
+			ret = st->tuner_config;
+		break;
+	case TUNER_S7395:
+		if (dvb_attach(ix2505v_attach , adap->fe[0], &lme_tuner,
+			&d->i2c_adap))
+			ret = st->tuner_config;
+		break;
+	case TUNER_S0194:
+		if (dvb_attach(dvb_pll_attach , adap->fe[0], 0x60,
+			&d->i2c_adap, DVB_PLL_OPERA1))
+			ret = st->tuner_config;
+		break;
+	case TUNER_RS2000:
+		if (dvb_attach(ts2020_attach, adap->fe[0],
+			       &ts2020_config, &d->i2c_adap))
+			ret = st->tuner_config;
+		break;
+	default:
+		break;
+	}
+
+	if (ret) {
+		info("TUN Found %s tuner", tun_msg[ret]);
+	} else {
+		info("TUN No tuner found");
+		return -ENODEV;
+	}
+
+	/* Start the Interrupt*/
+	ret = lme2510_int_read(adap);
+	if (ret < 0) {
+		info("INT Unable to start Interrupt Service");
+		return -ENODEV;
+	}
+
+	return ret;
+}
+
+static int lme2510_powerup(struct dvb_usb_device *d, int onoff)
+{
+	struct lme2510_state *st = d->priv;
+	static u8 lnb_on[] = LNB_ON;
+	static u8 lnb_off[] = LNB_OFF;
+	static u8 rbuf[1];
+	int ret = 0, len = 3, rlen = 1;
+
+	mutex_lock(&d->i2c_mutex);
+
+	ret = lme2510_usb_talk(d, onoff ? lnb_on : lnb_off, len, rbuf, rlen);
+
+	st->i2c_talk_onoff = 1;
+
+	mutex_unlock(&d->i2c_mutex);
+
+	return ret;
+}
+
+static int lme2510_get_adapter_count(struct dvb_usb_device *d)
+{
+	return 1;
+}
+
+static int lme2510_identify_state(struct dvb_usb_device *d, const char **name)
+{
+	struct lme2510_state *st = d->priv;
+	int status;
+
+	usb_reset_configuration(d->udev);
+
+	usb_set_interface(d->udev,
+		d->props->bInterfaceNumber, 1);
+
+	st->dvb_usb_lme2510_firmware = dvb_usb_lme2510_firmware;
+
+	status = lme2510_return_status(d);
+	if (status == 0x44) {
+		*name = lme_firmware_switch(d, 0);
+		return COLD;
+	}
+
+	if (status != 0x47)
+		return -EINVAL;
+
+	return WARM;
+}
+
+static int lme2510_get_stream_config(struct dvb_frontend *fe, u8 *ts_type,
+		struct usb_data_stream_properties *stream)
+{
+	struct dvb_usb_adapter *adap = fe_to_adap(fe);
+	struct dvb_usb_device *d;
+
+	if (adap == NULL)
+		return 0;
+
+	d = adap_to_d(adap);
+
+	/* Turn PID filter on the fly by module option */
+	if (pid_filter == 2) {
+		adap->pid_filtering  = true;
+		adap->max_feed_count = 15;
+	}
+
+	if (!(le16_to_cpu(d->udev->descriptor.idProduct)
+		== 0x1122))
+		stream->endpoint = 0x8;
+
+	return 0;
+}
+
+static int lme2510_get_rc_config(struct dvb_usb_device *d,
+	struct dvb_usb_rc *rc)
+{
+	rc->allowed_protos = RC_PROTO_BIT_NEC32;
+	return 0;
+}
+
+static void *lme2510_exit_int(struct dvb_usb_device *d)
+{
+	struct lme2510_state *st = d->priv;
+	struct dvb_usb_adapter *adap = &d->adapter[0];
+	void *buffer = NULL;
+
+	if (adap != NULL) {
+		lme2510_kill_urb(&adap->stream);
+	}
+
+	if (st->usb_buffer != NULL) {
+		st->i2c_talk_onoff = 1;
+		st->signal_level = 0;
+		st->signal_sn = 0;
+		buffer = st->usb_buffer;
+	}
+
+	if (st->lme_urb != NULL) {
+		usb_kill_urb(st->lme_urb);
+		usb_free_coherent(d->udev, 128, st->buffer,
+				  st->lme_urb->transfer_dma);
+		info("Interrupt Service Stopped");
+	}
+
+	return buffer;
+}
+
+static void lme2510_exit(struct dvb_usb_device *d)
+{
+	void *usb_buffer;
+
+	if (d != NULL) {
+		usb_buffer = lme2510_exit_int(d);
+		kfree(usb_buffer);
+	}
+}
+
+static struct dvb_usb_device_properties lme2510_props = {
+	.driver_name = KBUILD_MODNAME,
+	.owner = THIS_MODULE,
+	.bInterfaceNumber = 0,
+	.adapter_nr = adapter_nr,
+	.size_of_priv = sizeof(struct lme2510_state),
+
+	.download_firmware = lme2510_download_firmware,
+
+	.power_ctrl       = lme2510_powerup,
+	.identify_state   = lme2510_identify_state,
+	.i2c_algo         = &lme2510_i2c_algo,
+
+	.frontend_attach  = dm04_lme2510_frontend_attach,
+	.tuner_attach = dm04_lme2510_tuner,
+	.get_stream_config = lme2510_get_stream_config,
+	.get_adapter_count = lme2510_get_adapter_count,
+	.streaming_ctrl   = lme2510_streaming_ctrl,
+
+	.get_rc_config = lme2510_get_rc_config,
+
+	.exit = lme2510_exit,
+	.adapter = {
+		{
+			.caps = DVB_USB_ADAP_HAS_PID_FILTER|
+				DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+			.pid_filter_count = 15,
+			.pid_filter = lme2510_pid_filter,
+			.pid_filter_ctrl  = lme2510_pid_filter_ctrl,
+			.stream =
+			DVB_USB_STREAM_BULK(0x86, 10, 4096),
+		},
+		{
+		}
+	},
+};
+
+static const struct usb_device_id lme2510_id_table[] = {
+	{	DVB_USB_DEVICE(0x3344, 0x1122, &lme2510_props,
+		"DM04_LME2510_DVB-S", RC_MAP_LME2510)	},
+	{	DVB_USB_DEVICE(0x3344, 0x1120, &lme2510_props,
+		"DM04_LME2510C_DVB-S", RC_MAP_LME2510)	},
+	{	DVB_USB_DEVICE(0x3344, 0x22f0, &lme2510_props,
+		"DM04_LME2510C_DVB-S RS2000", RC_MAP_LME2510)	},
+	{}		/* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, lme2510_id_table);
+
+static struct usb_driver lme2510_driver = {
+	.name		= KBUILD_MODNAME,
+	.probe		= dvb_usbv2_probe,
+	.disconnect	= dvb_usbv2_disconnect,
+	.id_table	= lme2510_id_table,
+	.no_dynamic_id = 1,
+	.soft_unbind = 1,
+};
+
+module_usb_driver(lme2510_driver);
+
+MODULE_AUTHOR("Malcolm Priestley <tvboxspy@gmail.com>");
+MODULE_DESCRIPTION("LME2510(C) DVB-S USB2.0");
+MODULE_VERSION("2.07");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(LME2510_C_S7395);
+MODULE_FIRMWARE(LME2510_C_LG);
+MODULE_FIRMWARE(LME2510_C_S0194);
+MODULE_FIRMWARE(LME2510_C_RS2000);
+MODULE_FIRMWARE(LME2510_LG);
+MODULE_FIRMWARE(LME2510_S0194);
+
diff --git a/drivers/media/usb/dvb-usb-v2/lmedm04.h b/drivers/media/usb/dvb-usb-v2/lmedm04.h
new file mode 100644
index 0000000..c4ae37c
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/lmedm04.h
@@ -0,0 +1,175 @@
+/* DVB USB compliant linux driver for
+ *
+ * DM04/QQBOX DVB-S USB BOX	LME2510C + SHARP:BS2F7HZ7395
+ *				LME2510C + LG TDQY-P001F
+ *				LME2510 + LG TDQY-P001F
+ *
+ * MVB7395 (LME2510C+SHARP:BS2F7HZ7395)
+ * SHARP:BS2F7HZ7395 = (STV0288+Sharp IX2505V)
+ *
+ * MVB001F (LME2510+LGTDQT-P001F)
+ * LG TDQY - P001F =(TDA8263 + TDA10086H)
+ *
+ * MVB0001F (LME2510C+LGTDQT-P001F)
+ *
+ * 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,  version 2.
+ * *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#ifndef _DVB_USB_LME2510_H_
+#define _DVB_USB_LME2510_H_
+
+/* Streamer &  PID
+ *
+ * Note:	These commands do not actually stop the streaming
+ *		but form some kind of packet filtering/stream count
+ *		or tuning related functions.
+ *  06 XX
+ *  offset 1 = 00 Enable Streaming
+ *
+ *
+ *  PID
+ *  03 XX XX  ----> reg number ---> setting....20 XX
+ *  offset 1 = length
+ *  offset 2 = start of data
+ *  end byte -1 = 20
+ *  end byte = clear pid always a0, other wise 9c, 9a ??
+ *
+*/
+#define LME_ST_ON_W	{0x06, 0x00}
+#define LME_CLEAR_PID   {0x03, 0x02, 0x20, 0xa0}
+#define LME_ZERO_PID	{0x03, 0x06, 0x00, 0x00, 0x01, 0x00, 0x20, 0x9c}
+#define LME_ALL_PIDS	{0x03, 0x06, 0x00, 0xff, 0x01, 0x1f, 0x20, 0x81}
+
+/*  LNB Voltage
+ *  07 XX XX
+ *  offset 1 = 01
+ *  offset 2 = 00=Voltage low 01=Voltage high
+ *
+ *  LNB Power
+ *  03 01 XX
+ *  offset 2 = 00=ON 01=OFF
+ */
+
+#define LME_VOLTAGE_L	{0x07, 0x01, 0x00}
+#define LME_VOLTAGE_H	{0x07, 0x01, 0x01}
+#define LNB_ON		{0x3a, 0x01, 0x00}
+#define LNB_OFF		{0x3a, 0x01, 0x01}
+
+/* Initial stv0288 settings for 7395 Frontend */
+static u8 s7395_inittab[] = {
+	0x01, 0x15,
+	0x02, 0x20,
+	0x03, 0xa0,
+	0x04, 0xa0,
+	0x05, 0x12,
+	0x06, 0x00,
+	0x09, 0x00,
+	0x0a, 0x04,
+	0x0b, 0x00,
+	0x0c, 0x00,
+	0x0d, 0x00,
+	0x0e, 0xc1,
+	0x0f, 0x54,
+	0x11, 0x7a,
+	0x12, 0x03,
+	0x13, 0x48,
+	0x14, 0x84,
+	0x15, 0xc5,
+	0x16, 0xb8,
+	0x17, 0x9c,
+	0x18, 0x00,
+	0x19, 0xa6,
+	0x1a, 0x88,
+	0x1b, 0x8f,
+	0x1c, 0xf0,
+	0x20, 0x0b,
+	0x21, 0x54,
+	0x22, 0xff,
+	0x23, 0x01,
+	0x28, 0x46,
+	0x29, 0x66,
+	0x2a, 0x90,
+	0x2b, 0xfa,
+	0x2c, 0xd9,
+	0x30, 0x0,
+	0x31, 0x1e,
+	0x32, 0x14,
+	0x33, 0x0f,
+	0x34, 0x09,
+	0x35, 0x0c,
+	0x36, 0x05,
+	0x37, 0x2f,
+	0x38, 0x16,
+	0x39, 0xbd,
+	0x3a, 0x0,
+	0x3b, 0x13,
+	0x3c, 0x11,
+	0x3d, 0x30,
+	0x40, 0x63,
+	0x41, 0x04,
+	0x42, 0x20,
+	0x43, 0x00,
+	0x44, 0x00,
+	0x45, 0x00,
+	0x46, 0x00,
+	0x47, 0x00,
+	0x4a, 0x00,
+	0x50, 0x10,
+	0x51, 0x36,
+	0x52, 0x21,
+	0x53, 0x94,
+	0x54, 0xb2,
+	0x55, 0x29,
+	0x56, 0x64,
+	0x57, 0x2b,
+	0x58, 0x54,
+	0x59, 0x86,
+	0x5a, 0x00,
+	0x5b, 0x9b,
+	0x5c, 0x08,
+	0x5d, 0x7f,
+	0x5e, 0xff,
+	0x5f, 0x8d,
+	0x70, 0x0,
+	0x71, 0x0,
+	0x72, 0x0,
+	0x74, 0x0,
+	0x75, 0x0,
+	0x76, 0x0,
+	0x81, 0x0,
+	0x82, 0x3f,
+	0x83, 0x3f,
+	0x84, 0x0,
+	0x85, 0x0,
+	0x88, 0x0,
+	0x89, 0x0,
+	0x8a, 0x0,
+	0x8b, 0x0,
+	0x8c, 0x0,
+	0x90, 0x0,
+	0x91, 0x0,
+	0x92, 0x0,
+	0x93, 0x0,
+	0x94, 0x1c,
+	0x97, 0x0,
+	0xa0, 0x48,
+	0xa1, 0x0,
+	0xb0, 0xb8,
+	0xb1, 0x3a,
+	0xb2, 0x10,
+	0xb3, 0x82,
+	0xb4, 0x80,
+	0xb5, 0x82,
+	0xb6, 0x82,
+	0xb7, 0x82,
+	0xb8, 0x20,
+	0xb9, 0x0,
+	0xf0, 0x0,
+	0xf1, 0x0,
+	0xf2, 0xc0,
+	0xff, 0xff,
+};
+#endif
diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf-demod.c b/drivers/media/usb/dvb-usb-v2/mxl111sf-demod.c
new file mode 100644
index 0000000..9f74453
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/mxl111sf-demod.c
@@ -0,0 +1,607 @@
+/*
+ *  mxl111sf-demod.c - driver for the MaxLinear MXL111SF DVB-T demodulator
+ *
+ *  Copyright (C) 2010-2014 Michael Krufky <mkrufky@linuxtv.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ */
+
+#include "mxl111sf-demod.h"
+#include "mxl111sf-reg.h"
+
+/* debug */
+static int mxl111sf_demod_debug;
+module_param_named(debug, mxl111sf_demod_debug, int, 0644);
+MODULE_PARM_DESC(debug, "set debugging level (1=info (or-able)).");
+
+#define mxl_dbg(fmt, arg...) \
+	if (mxl111sf_demod_debug) \
+		mxl_printk(KERN_DEBUG, fmt, ##arg)
+
+/* ------------------------------------------------------------------------ */
+
+struct mxl111sf_demod_state {
+	struct mxl111sf_state *mxl_state;
+
+	const struct mxl111sf_demod_config *cfg;
+
+	struct dvb_frontend fe;
+};
+
+/* ------------------------------------------------------------------------ */
+
+static int mxl111sf_demod_read_reg(struct mxl111sf_demod_state *state,
+				   u8 addr, u8 *data)
+{
+	return (state->cfg->read_reg) ?
+		state->cfg->read_reg(state->mxl_state, addr, data) :
+		-EINVAL;
+}
+
+static int mxl111sf_demod_write_reg(struct mxl111sf_demod_state *state,
+				    u8 addr, u8 data)
+{
+	return (state->cfg->write_reg) ?
+		state->cfg->write_reg(state->mxl_state, addr, data) :
+		-EINVAL;
+}
+
+static
+int mxl111sf_demod_program_regs(struct mxl111sf_demod_state *state,
+				struct mxl111sf_reg_ctrl_info *ctrl_reg_info)
+{
+	return (state->cfg->program_regs) ?
+		state->cfg->program_regs(state->mxl_state, ctrl_reg_info) :
+		-EINVAL;
+}
+
+/* ------------------------------------------------------------------------ */
+/* TPS */
+
+static
+int mxl1x1sf_demod_get_tps_code_rate(struct mxl111sf_demod_state *state,
+				     enum fe_code_rate *code_rate)
+{
+	u8 val;
+	int ret = mxl111sf_demod_read_reg(state, V6_CODE_RATE_TPS_REG, &val);
+	/* bit<2:0> - 000:1/2, 001:2/3, 010:3/4, 011:5/6, 100:7/8 */
+	if (mxl_fail(ret))
+		goto fail;
+
+	switch (val & V6_CODE_RATE_TPS_MASK) {
+	case 0:
+		*code_rate = FEC_1_2;
+		break;
+	case 1:
+		*code_rate = FEC_2_3;
+		break;
+	case 2:
+		*code_rate = FEC_3_4;
+		break;
+	case 3:
+		*code_rate = FEC_5_6;
+		break;
+	case 4:
+		*code_rate = FEC_7_8;
+		break;
+	}
+fail:
+	return ret;
+}
+
+static
+int mxl1x1sf_demod_get_tps_modulation(struct mxl111sf_demod_state *state,
+				      enum fe_modulation *modulation)
+{
+	u8 val;
+	int ret = mxl111sf_demod_read_reg(state, V6_MODORDER_TPS_REG, &val);
+	/* Constellation, 00 : QPSK, 01 : 16QAM, 10:64QAM */
+	if (mxl_fail(ret))
+		goto fail;
+
+	switch ((val & V6_PARAM_CONSTELLATION_MASK) >> 4) {
+	case 0:
+		*modulation = QPSK;
+		break;
+	case 1:
+		*modulation = QAM_16;
+		break;
+	case 2:
+		*modulation = QAM_64;
+		break;
+	}
+fail:
+	return ret;
+}
+
+static
+int mxl1x1sf_demod_get_tps_guard_fft_mode(struct mxl111sf_demod_state *state,
+					  enum fe_transmit_mode *fft_mode)
+{
+	u8 val;
+	int ret = mxl111sf_demod_read_reg(state, V6_MODE_TPS_REG, &val);
+	/* FFT Mode, 00:2K, 01:8K, 10:4K */
+	if (mxl_fail(ret))
+		goto fail;
+
+	switch ((val & V6_PARAM_FFT_MODE_MASK) >> 2) {
+	case 0:
+		*fft_mode = TRANSMISSION_MODE_2K;
+		break;
+	case 1:
+		*fft_mode = TRANSMISSION_MODE_8K;
+		break;
+	case 2:
+		*fft_mode = TRANSMISSION_MODE_4K;
+		break;
+	}
+fail:
+	return ret;
+}
+
+static
+int mxl1x1sf_demod_get_tps_guard_interval(struct mxl111sf_demod_state *state,
+					  enum fe_guard_interval *guard)
+{
+	u8 val;
+	int ret = mxl111sf_demod_read_reg(state, V6_CP_TPS_REG, &val);
+	/* 00:1/32, 01:1/16, 10:1/8, 11:1/4 */
+	if (mxl_fail(ret))
+		goto fail;
+
+	switch ((val & V6_PARAM_GI_MASK) >> 4) {
+	case 0:
+		*guard = GUARD_INTERVAL_1_32;
+		break;
+	case 1:
+		*guard = GUARD_INTERVAL_1_16;
+		break;
+	case 2:
+		*guard = GUARD_INTERVAL_1_8;
+		break;
+	case 3:
+		*guard = GUARD_INTERVAL_1_4;
+		break;
+	}
+fail:
+	return ret;
+}
+
+static
+int mxl1x1sf_demod_get_tps_hierarchy(struct mxl111sf_demod_state *state,
+				     enum fe_hierarchy *hierarchy)
+{
+	u8 val;
+	int ret = mxl111sf_demod_read_reg(state, V6_TPS_HIERACHY_REG, &val);
+	/* bit<6:4> - 000:Non hierarchy, 001:1, 010:2, 011:4 */
+	if (mxl_fail(ret))
+		goto fail;
+
+	switch ((val & V6_TPS_HIERARCHY_INFO_MASK) >> 6) {
+	case 0:
+		*hierarchy = HIERARCHY_NONE;
+		break;
+	case 1:
+		*hierarchy = HIERARCHY_1;
+		break;
+	case 2:
+		*hierarchy = HIERARCHY_2;
+		break;
+	case 3:
+		*hierarchy = HIERARCHY_4;
+		break;
+	}
+fail:
+	return ret;
+}
+
+/* ------------------------------------------------------------------------ */
+/* LOCKS */
+
+static
+int mxl1x1sf_demod_get_sync_lock_status(struct mxl111sf_demod_state *state,
+					int *sync_lock)
+{
+	u8 val = 0;
+	int ret = mxl111sf_demod_read_reg(state, V6_SYNC_LOCK_REG, &val);
+	if (mxl_fail(ret))
+		goto fail;
+	*sync_lock = (val & SYNC_LOCK_MASK) >> 4;
+fail:
+	return ret;
+}
+
+static
+int mxl1x1sf_demod_get_rs_lock_status(struct mxl111sf_demod_state *state,
+				      int *rs_lock)
+{
+	u8 val = 0;
+	int ret = mxl111sf_demod_read_reg(state, V6_RS_LOCK_DET_REG, &val);
+	if (mxl_fail(ret))
+		goto fail;
+	*rs_lock = (val & RS_LOCK_DET_MASK) >> 3;
+fail:
+	return ret;
+}
+
+static
+int mxl1x1sf_demod_get_tps_lock_status(struct mxl111sf_demod_state *state,
+				       int *tps_lock)
+{
+	u8 val = 0;
+	int ret = mxl111sf_demod_read_reg(state, V6_TPS_LOCK_REG, &val);
+	if (mxl_fail(ret))
+		goto fail;
+	*tps_lock = (val & V6_PARAM_TPS_LOCK_MASK) >> 6;
+fail:
+	return ret;
+}
+
+static
+int mxl1x1sf_demod_get_fec_lock_status(struct mxl111sf_demod_state *state,
+				       int *fec_lock)
+{
+	u8 val = 0;
+	int ret = mxl111sf_demod_read_reg(state, V6_IRQ_STATUS_REG, &val);
+	if (mxl_fail(ret))
+		goto fail;
+	*fec_lock = (val & IRQ_MASK_FEC_LOCK) >> 4;
+fail:
+	return ret;
+}
+
+#if 0
+static
+int mxl1x1sf_demod_get_cp_lock_status(struct mxl111sf_demod_state *state,
+				      int *cp_lock)
+{
+	u8 val = 0;
+	int ret = mxl111sf_demod_read_reg(state, V6_CP_LOCK_DET_REG, &val);
+	if (mxl_fail(ret))
+		goto fail;
+	*cp_lock = (val & V6_CP_LOCK_DET_MASK) >> 2;
+fail:
+	return ret;
+}
+#endif
+
+static int mxl1x1sf_demod_reset_irq_status(struct mxl111sf_demod_state *state)
+{
+	return mxl111sf_demod_write_reg(state, 0x0e, 0xff);
+}
+
+/* ------------------------------------------------------------------------ */
+
+static int mxl111sf_demod_set_frontend(struct dvb_frontend *fe)
+{
+	struct mxl111sf_demod_state *state = fe->demodulator_priv;
+	int ret = 0;
+
+	struct mxl111sf_reg_ctrl_info phy_pll_patch[] = {
+		{0x00, 0xff, 0x01}, /* change page to 1 */
+		{0x40, 0xff, 0x05},
+		{0x40, 0xff, 0x01},
+		{0x41, 0xff, 0xca},
+		{0x41, 0xff, 0xc0},
+		{0x00, 0xff, 0x00}, /* change page to 0 */
+		{0,    0,    0}
+	};
+
+	mxl_dbg("()");
+
+	if (fe->ops.tuner_ops.set_params) {
+		ret = fe->ops.tuner_ops.set_params(fe);
+		if (mxl_fail(ret))
+			goto fail;
+		msleep(50);
+	}
+	ret = mxl111sf_demod_program_regs(state, phy_pll_patch);
+	mxl_fail(ret);
+	msleep(50);
+	ret = mxl1x1sf_demod_reset_irq_status(state);
+	mxl_fail(ret);
+	msleep(100);
+fail:
+	return ret;
+}
+
+/* ------------------------------------------------------------------------ */
+
+#if 0
+/* resets TS Packet error count */
+/* After setting 7th bit of V5_PER_COUNT_RESET_REG, it should be reset to 0. */
+static
+int mxl1x1sf_demod_reset_packet_error_count(struct mxl111sf_demod_state *state)
+{
+	struct mxl111sf_reg_ctrl_info reset_per_count[] = {
+		{0x20, 0x01, 0x01},
+		{0x20, 0x01, 0x00},
+		{0,    0,    0}
+	};
+	return mxl111sf_demod_program_regs(state, reset_per_count);
+}
+#endif
+
+/* returns TS Packet error count */
+/* PER Count = FEC_PER_COUNT * (2 ** (FEC_PER_SCALE * 4)) */
+static int mxl111sf_demod_read_ucblocks(struct dvb_frontend *fe, u32 *ucblocks)
+{
+	struct mxl111sf_demod_state *state = fe->demodulator_priv;
+	u32 fec_per_count, fec_per_scale;
+	u8 val;
+	int ret;
+
+	*ucblocks = 0;
+
+	/* FEC_PER_COUNT Register */
+	ret = mxl111sf_demod_read_reg(state, V6_FEC_PER_COUNT_REG, &val);
+	if (mxl_fail(ret))
+		goto fail;
+
+	fec_per_count = val;
+
+	/* FEC_PER_SCALE Register */
+	ret = mxl111sf_demod_read_reg(state, V6_FEC_PER_SCALE_REG, &val);
+	if (mxl_fail(ret))
+		goto fail;
+
+	val &= V6_FEC_PER_SCALE_MASK;
+	val *= 4;
+
+	fec_per_scale = 1 << val;
+
+	fec_per_count *= fec_per_scale;
+
+	*ucblocks = fec_per_count;
+fail:
+	return ret;
+}
+
+#ifdef MXL111SF_DEMOD_ENABLE_CALCULATIONS
+/* FIXME: leaving this enabled breaks the build on some architectures,
+ * and we shouldn't have any floating point math in the kernel, anyway.
+ *
+ * These macros need to be re-written, but it's harmless to simply
+ * return zero for now. */
+#define CALCULATE_BER(avg_errors, count) \
+	((u32)(avg_errors * 4)/(count*64*188*8))
+#define CALCULATE_SNR(data) \
+	((u32)((10 * (u32)data / 64) - 2.5))
+#else
+#define CALCULATE_BER(avg_errors, count) 0
+#define CALCULATE_SNR(data) 0
+#endif
+
+static int mxl111sf_demod_read_ber(struct dvb_frontend *fe, u32 *ber)
+{
+	struct mxl111sf_demod_state *state = fe->demodulator_priv;
+	u8 val1, val2, val3;
+	int ret;
+
+	*ber = 0;
+
+	ret = mxl111sf_demod_read_reg(state, V6_RS_AVG_ERRORS_LSB_REG, &val1);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_demod_read_reg(state, V6_RS_AVG_ERRORS_MSB_REG, &val2);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_demod_read_reg(state, V6_N_ACCUMULATE_REG, &val3);
+	if (mxl_fail(ret))
+		goto fail;
+
+	*ber = CALCULATE_BER((val1 | (val2 << 8)), val3);
+fail:
+	return ret;
+}
+
+static int mxl111sf_demod_calc_snr(struct mxl111sf_demod_state *state,
+				   u16 *snr)
+{
+	u8 val1, val2;
+	int ret;
+
+	*snr = 0;
+
+	ret = mxl111sf_demod_read_reg(state, V6_SNR_RB_LSB_REG, &val1);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_demod_read_reg(state, V6_SNR_RB_MSB_REG, &val2);
+	if (mxl_fail(ret))
+		goto fail;
+
+	*snr = CALCULATE_SNR(val1 | ((val2 & 0x03) << 8));
+fail:
+	return ret;
+}
+
+static int mxl111sf_demod_read_snr(struct dvb_frontend *fe, u16 *snr)
+{
+	struct mxl111sf_demod_state *state = fe->demodulator_priv;
+
+	int ret = mxl111sf_demod_calc_snr(state, snr);
+	if (mxl_fail(ret))
+		goto fail;
+
+	*snr /= 10; /* 0.1 dB */
+fail:
+	return ret;
+}
+
+static int mxl111sf_demod_read_status(struct dvb_frontend *fe,
+				      enum fe_status *status)
+{
+	struct mxl111sf_demod_state *state = fe->demodulator_priv;
+	int ret, locked, cr_lock, sync_lock, fec_lock;
+
+	*status = 0;
+
+	ret = mxl1x1sf_demod_get_rs_lock_status(state, &locked);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl1x1sf_demod_get_tps_lock_status(state, &cr_lock);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl1x1sf_demod_get_sync_lock_status(state, &sync_lock);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl1x1sf_demod_get_fec_lock_status(state, &fec_lock);
+	if (mxl_fail(ret))
+		goto fail;
+
+	if (locked)
+		*status |= FE_HAS_SIGNAL;
+	if (cr_lock)
+		*status |= FE_HAS_CARRIER;
+	if (sync_lock)
+		*status |= FE_HAS_SYNC;
+	if (fec_lock) /* false positives? */
+		*status |= FE_HAS_VITERBI;
+
+	if ((locked) && (cr_lock) && (sync_lock))
+		*status |= FE_HAS_LOCK;
+fail:
+	return ret;
+}
+
+static int mxl111sf_demod_read_signal_strength(struct dvb_frontend *fe,
+					       u16 *signal_strength)
+{
+	struct mxl111sf_demod_state *state = fe->demodulator_priv;
+	enum fe_modulation modulation;
+	int ret;
+	u16 snr;
+
+	ret = mxl111sf_demod_calc_snr(state, &snr);
+	if (ret < 0)
+		return ret;
+	ret = mxl1x1sf_demod_get_tps_modulation(state, &modulation);
+	if (ret < 0)
+		return ret;
+
+	switch (modulation) {
+	case QPSK:
+		*signal_strength = (snr >= 1300) ?
+			min(65535, snr * 44) : snr * 38;
+		break;
+	case QAM_16:
+		*signal_strength = (snr >= 1500) ?
+			min(65535, snr * 38) : snr * 33;
+		break;
+	case QAM_64:
+		*signal_strength = (snr >= 2000) ?
+			min(65535, snr * 29) : snr * 25;
+		break;
+	default:
+		*signal_strength = 0;
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int mxl111sf_demod_get_frontend(struct dvb_frontend *fe,
+				       struct dtv_frontend_properties *p)
+{
+	struct mxl111sf_demod_state *state = fe->demodulator_priv;
+
+	mxl_dbg("()");
+#if 0
+	p->inversion = /* FIXME */ ? INVERSION_ON : INVERSION_OFF;
+#endif
+	if (fe->ops.tuner_ops.get_bandwidth)
+		fe->ops.tuner_ops.get_bandwidth(fe, &p->bandwidth_hz);
+	if (fe->ops.tuner_ops.get_frequency)
+		fe->ops.tuner_ops.get_frequency(fe, &p->frequency);
+	mxl1x1sf_demod_get_tps_code_rate(state, &p->code_rate_HP);
+	mxl1x1sf_demod_get_tps_code_rate(state, &p->code_rate_LP);
+	mxl1x1sf_demod_get_tps_modulation(state, &p->modulation);
+	mxl1x1sf_demod_get_tps_guard_fft_mode(state,
+					      &p->transmission_mode);
+	mxl1x1sf_demod_get_tps_guard_interval(state,
+					      &p->guard_interval);
+	mxl1x1sf_demod_get_tps_hierarchy(state,
+					 &p->hierarchy);
+
+	return 0;
+}
+
+static
+int mxl111sf_demod_get_tune_settings(struct dvb_frontend *fe,
+				     struct dvb_frontend_tune_settings *tune)
+{
+	tune->min_delay_ms = 1000;
+	return 0;
+}
+
+static void mxl111sf_demod_release(struct dvb_frontend *fe)
+{
+	struct mxl111sf_demod_state *state = fe->demodulator_priv;
+	mxl_dbg("()");
+	kfree(state);
+	fe->demodulator_priv = NULL;
+}
+
+static const struct dvb_frontend_ops mxl111sf_demod_ops = {
+	.delsys = { SYS_DVBT },
+	.info = {
+		.name               = "MaxLinear MxL111SF DVB-T demodulator",
+		.frequency_min_hz      = 177 * MHz,
+		.frequency_max_hz      = 858 * MHz,
+		.frequency_stepsize_hz = 166666,
+		.caps = FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 |
+			FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO |
+			FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 |
+			FE_CAN_QAM_AUTO |
+			FE_CAN_HIERARCHY_AUTO | FE_CAN_GUARD_INTERVAL_AUTO |
+			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_RECOVER
+	},
+	.release              = mxl111sf_demod_release,
+#if 0
+	.init                 = mxl111sf_init,
+	.i2c_gate_ctrl        = mxl111sf_i2c_gate_ctrl,
+#endif
+	.set_frontend         = mxl111sf_demod_set_frontend,
+	.get_frontend         = mxl111sf_demod_get_frontend,
+	.get_tune_settings    = mxl111sf_demod_get_tune_settings,
+	.read_status          = mxl111sf_demod_read_status,
+	.read_signal_strength = mxl111sf_demod_read_signal_strength,
+	.read_ber             = mxl111sf_demod_read_ber,
+	.read_snr             = mxl111sf_demod_read_snr,
+	.read_ucblocks        = mxl111sf_demod_read_ucblocks,
+};
+
+struct dvb_frontend *mxl111sf_demod_attach(struct mxl111sf_state *mxl_state,
+				   const struct mxl111sf_demod_config *cfg)
+{
+	struct mxl111sf_demod_state *state = NULL;
+
+	mxl_dbg("()");
+
+	state = kzalloc(sizeof(struct mxl111sf_demod_state), GFP_KERNEL);
+	if (state == NULL)
+		return NULL;
+
+	state->mxl_state = mxl_state;
+	state->cfg = cfg;
+
+	memcpy(&state->fe.ops, &mxl111sf_demod_ops,
+	       sizeof(struct dvb_frontend_ops));
+
+	state->fe.demodulator_priv = state;
+	return &state->fe;
+}
+EXPORT_SYMBOL_GPL(mxl111sf_demod_attach);
+
+MODULE_DESCRIPTION("MaxLinear MxL111SF DVB-T demodulator driver");
+MODULE_AUTHOR("Michael Krufky <mkrufky@linuxtv.org>");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("0.1");
diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf-demod.h b/drivers/media/usb/dvb-usb-v2/mxl111sf-demod.h
new file mode 100644
index 0000000..95888b8
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/mxl111sf-demod.h
@@ -0,0 +1,44 @@
+/*
+ *  mxl111sf-demod.h - driver for the MaxLinear MXL111SF DVB-T demodulator
+ *
+ *  Copyright (C) 2010-2014 Michael Krufky <mkrufky@linuxtv.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ */
+
+#ifndef __MXL111SF_DEMOD_H__
+#define __MXL111SF_DEMOD_H__
+
+#include <media/dvb_frontend.h>
+#include "mxl111sf.h"
+
+struct mxl111sf_demod_config {
+	int (*read_reg)(struct mxl111sf_state *state, u8 addr, u8 *data);
+	int (*write_reg)(struct mxl111sf_state *state, u8 addr, u8 data);
+	int (*program_regs)(struct mxl111sf_state *state,
+			    struct mxl111sf_reg_ctrl_info *ctrl_reg_info);
+};
+
+#if IS_ENABLED(CONFIG_DVB_USB_MXL111SF)
+extern
+struct dvb_frontend *mxl111sf_demod_attach(struct mxl111sf_state *mxl_state,
+				   const struct mxl111sf_demod_config *cfg);
+#else
+static inline
+struct dvb_frontend *mxl111sf_demod_attach(struct mxl111sf_state *mxl_state,
+				   const struct mxl111sf_demod_config *cfg)
+{
+	printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__);
+	return NULL;
+}
+#endif /* CONFIG_DVB_USB_MXL111SF */
+
+#endif /* __MXL111SF_DEMOD_H__ */
diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf-gpio.c b/drivers/media/usb/dvb-usb-v2/mxl111sf-gpio.c
new file mode 100644
index 0000000..c66861c
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/mxl111sf-gpio.c
@@ -0,0 +1,753 @@
+/*
+ *  mxl111sf-gpio.c - driver for the MaxLinear MXL111SF
+ *
+ *  Copyright (C) 2010-2014 Michael Krufky <mkrufky@linuxtv.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ */
+
+#include "mxl111sf-gpio.h"
+#include "mxl111sf-i2c.h"
+#include "mxl111sf.h"
+
+/* ------------------------------------------------------------------------- */
+
+#define MXL_GPIO_MUX_REG_0 0x84
+#define MXL_GPIO_MUX_REG_1 0x89
+#define MXL_GPIO_MUX_REG_2 0x82
+
+#define MXL_GPIO_DIR_INPUT  0
+#define MXL_GPIO_DIR_OUTPUT 1
+
+
+static int mxl111sf_set_gpo_state(struct mxl111sf_state *state, u8 pin, u8 val)
+{
+	int ret;
+	u8 tmp;
+
+	mxl_debug_adv("(%d, %d)", pin, val);
+
+	if ((pin > 0) && (pin < 8)) {
+		ret = mxl111sf_read_reg(state, 0x19, &tmp);
+		if (mxl_fail(ret))
+			goto fail;
+		tmp &= ~(1 << (pin - 1));
+		tmp |= (val << (pin - 1));
+		ret = mxl111sf_write_reg(state, 0x19, tmp);
+		if (mxl_fail(ret))
+			goto fail;
+	} else if (pin <= 10) {
+		if (pin == 0)
+			pin += 7;
+		ret = mxl111sf_read_reg(state, 0x30, &tmp);
+		if (mxl_fail(ret))
+			goto fail;
+		tmp &= ~(1 << (pin - 3));
+		tmp |= (val << (pin - 3));
+		ret = mxl111sf_write_reg(state, 0x30, tmp);
+		if (mxl_fail(ret))
+			goto fail;
+	} else
+		ret = -EINVAL;
+fail:
+	return ret;
+}
+
+static int mxl111sf_get_gpi_state(struct mxl111sf_state *state, u8 pin, u8 *val)
+{
+	int ret;
+	u8 tmp;
+
+	mxl_debug("(0x%02x)", pin);
+
+	*val = 0;
+
+	switch (pin) {
+	case 0:
+	case 1:
+	case 2:
+	case 3:
+		ret = mxl111sf_read_reg(state, 0x23, &tmp);
+		if (mxl_fail(ret))
+			goto fail;
+		*val = (tmp >> (pin + 4)) & 0x01;
+		break;
+	case 4:
+	case 5:
+	case 6:
+	case 7:
+		ret = mxl111sf_read_reg(state, 0x2f, &tmp);
+		if (mxl_fail(ret))
+			goto fail;
+		*val = (tmp >> pin) & 0x01;
+		break;
+	case 8:
+	case 9:
+	case 10:
+		ret = mxl111sf_read_reg(state, 0x22, &tmp);
+		if (mxl_fail(ret))
+			goto fail;
+		*val = (tmp >> (pin - 3)) & 0x01;
+		break;
+	default:
+		return -EINVAL; /* invalid pin */
+	}
+fail:
+	return ret;
+}
+
+struct mxl_gpio_cfg {
+	u8 pin;
+	u8 dir;
+	u8 val;
+};
+
+static int mxl111sf_config_gpio_pins(struct mxl111sf_state *state,
+				     struct mxl_gpio_cfg *gpio_cfg)
+{
+	int ret;
+	u8 tmp;
+
+	mxl_debug_adv("(%d, %d)", gpio_cfg->pin, gpio_cfg->dir);
+
+	switch (gpio_cfg->pin) {
+	case 0:
+	case 1:
+	case 2:
+	case 3:
+		ret = mxl111sf_read_reg(state, MXL_GPIO_MUX_REG_0, &tmp);
+		if (mxl_fail(ret))
+			goto fail;
+		tmp &= ~(1 << (gpio_cfg->pin + 4));
+		tmp |= (gpio_cfg->dir << (gpio_cfg->pin + 4));
+		ret = mxl111sf_write_reg(state, MXL_GPIO_MUX_REG_0, tmp);
+		if (mxl_fail(ret))
+			goto fail;
+		break;
+	case 4:
+	case 5:
+	case 6:
+	case 7:
+		ret = mxl111sf_read_reg(state, MXL_GPIO_MUX_REG_1, &tmp);
+		if (mxl_fail(ret))
+			goto fail;
+		tmp &= ~(1 << gpio_cfg->pin);
+		tmp |= (gpio_cfg->dir << gpio_cfg->pin);
+		ret = mxl111sf_write_reg(state, MXL_GPIO_MUX_REG_1, tmp);
+		if (mxl_fail(ret))
+			goto fail;
+		break;
+	case 8:
+	case 9:
+	case 10:
+		ret = mxl111sf_read_reg(state, MXL_GPIO_MUX_REG_2, &tmp);
+		if (mxl_fail(ret))
+			goto fail;
+		tmp &= ~(1 << (gpio_cfg->pin - 3));
+		tmp |= (gpio_cfg->dir << (gpio_cfg->pin - 3));
+		ret = mxl111sf_write_reg(state, MXL_GPIO_MUX_REG_2, tmp);
+		if (mxl_fail(ret))
+			goto fail;
+		break;
+	default:
+		return -EINVAL; /* invalid pin */
+	}
+
+	ret = (MXL_GPIO_DIR_OUTPUT == gpio_cfg->dir) ?
+		mxl111sf_set_gpo_state(state,
+				       gpio_cfg->pin, gpio_cfg->val) :
+		mxl111sf_get_gpi_state(state,
+				       gpio_cfg->pin, &gpio_cfg->val);
+	mxl_fail(ret);
+fail:
+	return ret;
+}
+
+static int mxl111sf_hw_do_set_gpio(struct mxl111sf_state *state,
+				   int gpio, int direction, int val)
+{
+	struct mxl_gpio_cfg gpio_config = {
+		.pin = gpio,
+		.dir = direction,
+		.val = val,
+	};
+
+	mxl_debug("(%d, %d, %d)", gpio, direction, val);
+
+	return mxl111sf_config_gpio_pins(state, &gpio_config);
+}
+
+/* ------------------------------------------------------------------------- */
+
+#define PIN_MUX_MPEG_MODE_MASK          0x40   /* 0x17 <6> */
+#define PIN_MUX_MPEG_PAR_EN_MASK        0x01   /* 0x18 <0> */
+#define PIN_MUX_MPEG_SER_EN_MASK        0x02   /* 0x18 <1> */
+#define PIN_MUX_MPG_IN_MUX_MASK         0x80   /* 0x3D <7> */
+#define PIN_MUX_BT656_ENABLE_MASK       0x04   /* 0x12 <2> */
+#define PIN_MUX_I2S_ENABLE_MASK         0x40   /* 0x15 <6> */
+#define PIN_MUX_SPI_MODE_MASK           0x10   /* 0x3D <4> */
+#define PIN_MUX_MCLK_EN_CTRL_MASK       0x10   /* 0x82 <4> */
+#define PIN_MUX_MPSYN_EN_CTRL_MASK      0x20   /* 0x82 <5> */
+#define PIN_MUX_MDVAL_EN_CTRL_MASK      0x40   /* 0x82 <6> */
+#define PIN_MUX_MPERR_EN_CTRL_MASK      0x80   /* 0x82 <7> */
+#define PIN_MUX_MDAT_EN_0_MASK          0x10   /* 0x84 <4> */
+#define PIN_MUX_MDAT_EN_1_MASK          0x20   /* 0x84 <5> */
+#define PIN_MUX_MDAT_EN_2_MASK          0x40   /* 0x84 <6> */
+#define PIN_MUX_MDAT_EN_3_MASK          0x80   /* 0x84 <7> */
+#define PIN_MUX_MDAT_EN_4_MASK          0x10   /* 0x89 <4> */
+#define PIN_MUX_MDAT_EN_5_MASK          0x20   /* 0x89 <5> */
+#define PIN_MUX_MDAT_EN_6_MASK          0x40   /* 0x89 <6> */
+#define PIN_MUX_MDAT_EN_7_MASK          0x80   /* 0x89 <7> */
+
+int mxl111sf_config_pin_mux_modes(struct mxl111sf_state *state,
+				  enum mxl111sf_mux_config pin_mux_config)
+{
+	u8 r12, r15, r17, r18, r3D, r82, r84, r89;
+	int ret;
+
+	mxl_debug("(%d)", pin_mux_config);
+
+	ret = mxl111sf_read_reg(state, 0x17, &r17);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_read_reg(state, 0x18, &r18);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_read_reg(state, 0x12, &r12);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_read_reg(state, 0x15, &r15);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_read_reg(state, 0x82, &r82);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_read_reg(state, 0x84, &r84);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_read_reg(state, 0x89, &r89);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_read_reg(state, 0x3D, &r3D);
+	if (mxl_fail(ret))
+		goto fail;
+
+	switch (pin_mux_config) {
+	case PIN_MUX_TS_OUT_PARALLEL:
+		/* mpeg_mode = 1 */
+		r17 |= PIN_MUX_MPEG_MODE_MASK;
+		/* mpeg_par_en = 1 */
+		r18 |= PIN_MUX_MPEG_PAR_EN_MASK;
+		/* mpeg_ser_en = 0 */
+		r18 &= ~PIN_MUX_MPEG_SER_EN_MASK;
+		/* mpg_in_mux = 0 */
+		r3D &= ~PIN_MUX_MPG_IN_MUX_MASK;
+		/* bt656_enable = 0 */
+		r12 &= ~PIN_MUX_BT656_ENABLE_MASK;
+		/* i2s_enable = 0 */
+		r15 &= ~PIN_MUX_I2S_ENABLE_MASK;
+		/* spi_mode = 0 */
+		r3D &= ~PIN_MUX_SPI_MODE_MASK;
+		/* mclk_en_ctrl = 1 */
+		r82 |= PIN_MUX_MCLK_EN_CTRL_MASK;
+		/* mperr_en_ctrl = 1 */
+		r82 |= PIN_MUX_MPERR_EN_CTRL_MASK;
+		/* mdval_en_ctrl = 1 */
+		r82 |= PIN_MUX_MDVAL_EN_CTRL_MASK;
+		/* mpsyn_en_ctrl = 1 */
+		r82 |= PIN_MUX_MPSYN_EN_CTRL_MASK;
+		/* mdat_en_ctrl[3:0] = 0xF */
+		r84 |= 0xF0;
+		/* mdat_en_ctrl[7:4] = 0xF */
+		r89 |= 0xF0;
+		break;
+	case PIN_MUX_TS_OUT_SERIAL:
+		/* mpeg_mode = 1 */
+		r17 |= PIN_MUX_MPEG_MODE_MASK;
+		/* mpeg_par_en = 0 */
+		r18 &= ~PIN_MUX_MPEG_PAR_EN_MASK;
+		/* mpeg_ser_en = 1 */
+		r18 |= PIN_MUX_MPEG_SER_EN_MASK;
+		/* mpg_in_mux = 0 */
+		r3D &= ~PIN_MUX_MPG_IN_MUX_MASK;
+		/* bt656_enable = 0 */
+		r12 &= ~PIN_MUX_BT656_ENABLE_MASK;
+		/* i2s_enable = 0 */
+		r15 &= ~PIN_MUX_I2S_ENABLE_MASK;
+		/* spi_mode = 0 */
+		r3D &= ~PIN_MUX_SPI_MODE_MASK;
+		/* mclk_en_ctrl = 1 */
+		r82 |= PIN_MUX_MCLK_EN_CTRL_MASK;
+		/* mperr_en_ctrl = 1 */
+		r82 |= PIN_MUX_MPERR_EN_CTRL_MASK;
+		/* mdval_en_ctrl = 1 */
+		r82 |= PIN_MUX_MDVAL_EN_CTRL_MASK;
+		/* mpsyn_en_ctrl = 1 */
+		r82 |= PIN_MUX_MPSYN_EN_CTRL_MASK;
+		/* mdat_en_ctrl[3:0] = 0xF */
+		r84 |= 0xF0;
+		/* mdat_en_ctrl[7:4] = 0xF */
+		r89 |= 0xF0;
+		break;
+	case PIN_MUX_GPIO_MODE:
+		/* mpeg_mode = 0 */
+		r17 &= ~PIN_MUX_MPEG_MODE_MASK;
+		/* mpeg_par_en = 0 */
+		r18 &= ~PIN_MUX_MPEG_PAR_EN_MASK;
+		/* mpeg_ser_en = 0 */
+		r18 &= ~PIN_MUX_MPEG_SER_EN_MASK;
+		/* mpg_in_mux = 0 */
+		r3D &= ~PIN_MUX_MPG_IN_MUX_MASK;
+		/* bt656_enable = 0 */
+		r12 &= ~PIN_MUX_BT656_ENABLE_MASK;
+		/* i2s_enable = 0 */
+		r15 &= ~PIN_MUX_I2S_ENABLE_MASK;
+		/* spi_mode = 0 */
+		r3D &= ~PIN_MUX_SPI_MODE_MASK;
+		/* mclk_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MCLK_EN_CTRL_MASK;
+		/* mperr_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MPERR_EN_CTRL_MASK;
+		/* mdval_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MDVAL_EN_CTRL_MASK;
+		/* mpsyn_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MPSYN_EN_CTRL_MASK;
+		/* mdat_en_ctrl[3:0] = 0x0 */
+		r84 &= 0x0F;
+		/* mdat_en_ctrl[7:4] = 0x0 */
+		r89 &= 0x0F;
+		break;
+	case PIN_MUX_TS_SERIAL_IN_MODE_0:
+		/* mpeg_mode = 0 */
+		r17 &= ~PIN_MUX_MPEG_MODE_MASK;
+		/* mpeg_par_en = 0 */
+		r18 &= ~PIN_MUX_MPEG_PAR_EN_MASK;
+		/* mpeg_ser_en = 1 */
+		r18 |= PIN_MUX_MPEG_SER_EN_MASK;
+		/* mpg_in_mux = 0 */
+		r3D &= ~PIN_MUX_MPG_IN_MUX_MASK;
+		/* bt656_enable = 0 */
+		r12 &= ~PIN_MUX_BT656_ENABLE_MASK;
+		/* i2s_enable = 0 */
+		r15 &= ~PIN_MUX_I2S_ENABLE_MASK;
+		/* spi_mode = 0 */
+		r3D &= ~PIN_MUX_SPI_MODE_MASK;
+		/* mclk_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MCLK_EN_CTRL_MASK;
+		/* mperr_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MPERR_EN_CTRL_MASK;
+		/* mdval_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MDVAL_EN_CTRL_MASK;
+		/* mpsyn_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MPSYN_EN_CTRL_MASK;
+		/* mdat_en_ctrl[3:0] = 0x0 */
+		r84 &= 0x0F;
+		/* mdat_en_ctrl[7:4] = 0x0 */
+		r89 &= 0x0F;
+		break;
+	case PIN_MUX_TS_SERIAL_IN_MODE_1:
+		/* mpeg_mode = 0 */
+		r17 &= ~PIN_MUX_MPEG_MODE_MASK;
+		/* mpeg_par_en = 0 */
+		r18 &= ~PIN_MUX_MPEG_PAR_EN_MASK;
+		/* mpeg_ser_en = 1 */
+		r18 |= PIN_MUX_MPEG_SER_EN_MASK;
+		/* mpg_in_mux = 1 */
+		r3D |= PIN_MUX_MPG_IN_MUX_MASK;
+		/* bt656_enable = 0 */
+		r12 &= ~PIN_MUX_BT656_ENABLE_MASK;
+		/* i2s_enable = 0 */
+		r15 &= ~PIN_MUX_I2S_ENABLE_MASK;
+		/* spi_mode = 0 */
+		r3D &= ~PIN_MUX_SPI_MODE_MASK;
+		/* mclk_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MCLK_EN_CTRL_MASK;
+		/* mperr_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MPERR_EN_CTRL_MASK;
+		/* mdval_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MDVAL_EN_CTRL_MASK;
+		/* mpsyn_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MPSYN_EN_CTRL_MASK;
+		/* mdat_en_ctrl[3:0] = 0x0 */
+		r84 &= 0x0F;
+		/* mdat_en_ctrl[7:4] = 0x0 */
+		r89 &= 0x0F;
+		break;
+	case PIN_MUX_TS_SPI_IN_MODE_1:
+		/* mpeg_mode = 0 */
+		r17 &= ~PIN_MUX_MPEG_MODE_MASK;
+		/* mpeg_par_en = 0 */
+		r18 &= ~PIN_MUX_MPEG_PAR_EN_MASK;
+		/* mpeg_ser_en = 1 */
+		r18 |= PIN_MUX_MPEG_SER_EN_MASK;
+		/* mpg_in_mux = 1 */
+		r3D |= PIN_MUX_MPG_IN_MUX_MASK;
+		/* bt656_enable = 0 */
+		r12 &= ~PIN_MUX_BT656_ENABLE_MASK;
+		/* i2s_enable = 1 */
+		r15 |= PIN_MUX_I2S_ENABLE_MASK;
+		/* spi_mode = 1 */
+		r3D |= PIN_MUX_SPI_MODE_MASK;
+		/* mclk_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MCLK_EN_CTRL_MASK;
+		/* mperr_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MPERR_EN_CTRL_MASK;
+		/* mdval_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MDVAL_EN_CTRL_MASK;
+		/* mpsyn_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MPSYN_EN_CTRL_MASK;
+		/* mdat_en_ctrl[3:0] = 0x0 */
+		r84 &= 0x0F;
+		/* mdat_en_ctrl[7:4] = 0x0 */
+		r89 &= 0x0F;
+		break;
+	case PIN_MUX_TS_SPI_IN_MODE_0:
+		/* mpeg_mode = 0 */
+		r17 &= ~PIN_MUX_MPEG_MODE_MASK;
+		/* mpeg_par_en = 0 */
+		r18 &= ~PIN_MUX_MPEG_PAR_EN_MASK;
+		/* mpeg_ser_en = 1 */
+		r18 |= PIN_MUX_MPEG_SER_EN_MASK;
+		/* mpg_in_mux = 0 */
+		r3D &= ~PIN_MUX_MPG_IN_MUX_MASK;
+		/* bt656_enable = 0 */
+		r12 &= ~PIN_MUX_BT656_ENABLE_MASK;
+		/* i2s_enable = 1 */
+		r15 |= PIN_MUX_I2S_ENABLE_MASK;
+		/* spi_mode = 1 */
+		r3D |= PIN_MUX_SPI_MODE_MASK;
+		/* mclk_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MCLK_EN_CTRL_MASK;
+		/* mperr_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MPERR_EN_CTRL_MASK;
+		/* mdval_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MDVAL_EN_CTRL_MASK;
+		/* mpsyn_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MPSYN_EN_CTRL_MASK;
+		/* mdat_en_ctrl[3:0] = 0x0 */
+		r84 &= 0x0F;
+		/* mdat_en_ctrl[7:4] = 0x0 */
+		r89 &= 0x0F;
+		break;
+	case PIN_MUX_TS_PARALLEL_IN:
+		/* mpeg_mode = 0 */
+		r17 &= ~PIN_MUX_MPEG_MODE_MASK;
+		/* mpeg_par_en = 1 */
+		r18 |= PIN_MUX_MPEG_PAR_EN_MASK;
+		/* mpeg_ser_en = 0 */
+		r18 &= ~PIN_MUX_MPEG_SER_EN_MASK;
+		/* mpg_in_mux = 0 */
+		r3D &= ~PIN_MUX_MPG_IN_MUX_MASK;
+		/* bt656_enable = 0 */
+		r12 &= ~PIN_MUX_BT656_ENABLE_MASK;
+		/* i2s_enable = 0 */
+		r15 &= ~PIN_MUX_I2S_ENABLE_MASK;
+		/* spi_mode = 0 */
+		r3D &= ~PIN_MUX_SPI_MODE_MASK;
+		/* mclk_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MCLK_EN_CTRL_MASK;
+		/* mperr_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MPERR_EN_CTRL_MASK;
+		/* mdval_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MDVAL_EN_CTRL_MASK;
+		/* mpsyn_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MPSYN_EN_CTRL_MASK;
+		/* mdat_en_ctrl[3:0] = 0x0 */
+		r84 &= 0x0F;
+		/* mdat_en_ctrl[7:4] = 0x0 */
+		r89 &= 0x0F;
+		break;
+	case PIN_MUX_BT656_I2S_MODE:
+		/* mpeg_mode = 0 */
+		r17 &= ~PIN_MUX_MPEG_MODE_MASK;
+		/* mpeg_par_en = 0 */
+		r18 &= ~PIN_MUX_MPEG_PAR_EN_MASK;
+		/* mpeg_ser_en = 0 */
+		r18 &= ~PIN_MUX_MPEG_SER_EN_MASK;
+		/* mpg_in_mux = 0 */
+		r3D &= ~PIN_MUX_MPG_IN_MUX_MASK;
+		/* bt656_enable = 1 */
+		r12 |= PIN_MUX_BT656_ENABLE_MASK;
+		/* i2s_enable = 1 */
+		r15 |= PIN_MUX_I2S_ENABLE_MASK;
+		/* spi_mode = 0 */
+		r3D &= ~PIN_MUX_SPI_MODE_MASK;
+		/* mclk_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MCLK_EN_CTRL_MASK;
+		/* mperr_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MPERR_EN_CTRL_MASK;
+		/* mdval_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MDVAL_EN_CTRL_MASK;
+		/* mpsyn_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MPSYN_EN_CTRL_MASK;
+		/* mdat_en_ctrl[3:0] = 0x0 */
+		r84 &= 0x0F;
+		/* mdat_en_ctrl[7:4] = 0x0 */
+		r89 &= 0x0F;
+		break;
+	case PIN_MUX_DEFAULT:
+	default:
+		/* mpeg_mode = 1 */
+		r17 |= PIN_MUX_MPEG_MODE_MASK;
+		/* mpeg_par_en = 0 */
+		r18 &= ~PIN_MUX_MPEG_PAR_EN_MASK;
+		/* mpeg_ser_en = 0 */
+		r18 &= ~PIN_MUX_MPEG_SER_EN_MASK;
+		/* mpg_in_mux = 0 */
+		r3D &= ~PIN_MUX_MPG_IN_MUX_MASK;
+		/* bt656_enable = 0 */
+		r12 &= ~PIN_MUX_BT656_ENABLE_MASK;
+		/* i2s_enable = 0 */
+		r15 &= ~PIN_MUX_I2S_ENABLE_MASK;
+		/* spi_mode = 0 */
+		r3D &= ~PIN_MUX_SPI_MODE_MASK;
+		/* mclk_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MCLK_EN_CTRL_MASK;
+		/* mperr_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MPERR_EN_CTRL_MASK;
+		/* mdval_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MDVAL_EN_CTRL_MASK;
+		/* mpsyn_en_ctrl = 0 */
+		r82 &= ~PIN_MUX_MPSYN_EN_CTRL_MASK;
+		/* mdat_en_ctrl[3:0] = 0x0 */
+		r84 &= 0x0F;
+		/* mdat_en_ctrl[7:4] = 0x0 */
+		r89 &= 0x0F;
+		break;
+	}
+
+	ret = mxl111sf_write_reg(state, 0x17, r17);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_write_reg(state, 0x18, r18);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_write_reg(state, 0x12, r12);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_write_reg(state, 0x15, r15);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_write_reg(state, 0x82, r82);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_write_reg(state, 0x84, r84);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_write_reg(state, 0x89, r89);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_write_reg(state, 0x3D, r3D);
+	if (mxl_fail(ret))
+		goto fail;
+fail:
+	return ret;
+}
+
+/* ------------------------------------------------------------------------- */
+
+static int mxl111sf_hw_set_gpio(struct mxl111sf_state *state, int gpio, int val)
+{
+	return mxl111sf_hw_do_set_gpio(state, gpio, MXL_GPIO_DIR_OUTPUT, val);
+}
+
+static int mxl111sf_hw_gpio_initialize(struct mxl111sf_state *state)
+{
+	u8 gpioval = 0x07; /* write protect enabled, signal LEDs off */
+	int i, ret;
+
+	mxl_debug("()");
+
+	for (i = 3; i < 8; i++) {
+		ret = mxl111sf_hw_set_gpio(state, i, (gpioval >> i) & 0x01);
+		if (mxl_fail(ret))
+			break;
+	}
+
+	return ret;
+}
+
+#define PCA9534_I2C_ADDR (0x40 >> 1)
+static int pca9534_set_gpio(struct mxl111sf_state *state, int gpio, int val)
+{
+	u8 w[2] = { 1, 0 };
+	u8 r = 0;
+	struct i2c_msg msg[] = {
+		{ .addr = PCA9534_I2C_ADDR,
+		  .flags = 0, .buf = w, .len = 1 },
+		{ .addr = PCA9534_I2C_ADDR,
+		  .flags = I2C_M_RD, .buf = &r, .len = 1 },
+	};
+
+	mxl_debug("(%d, %d)", gpio, val);
+
+	/* read current GPIO levels from flip-flop */
+	i2c_transfer(&state->d->i2c_adap, msg, 2);
+
+	/* prepare write buffer with current GPIO levels */
+	msg[0].len = 2;
+#if 0
+	w[0] = 1;
+#endif
+	w[1] = r;
+
+	/* clear the desired GPIO */
+	w[1] &= ~(1 << gpio);
+
+	/* set the desired GPIO value */
+	w[1] |= ((val ? 1 : 0) << gpio);
+
+	/* write new GPIO levels to flip-flop */
+	i2c_transfer(&state->d->i2c_adap, &msg[0], 1);
+
+	return 0;
+}
+
+static int pca9534_init_port_expander(struct mxl111sf_state *state)
+{
+	u8 w[2] = { 1, 0x07 }; /* write protect enabled, signal LEDs off */
+
+	struct i2c_msg msg = {
+		.addr = PCA9534_I2C_ADDR,
+		.flags = 0, .buf = w, .len = 2
+	};
+
+	mxl_debug("()");
+
+	i2c_transfer(&state->d->i2c_adap, &msg, 1);
+
+	/* configure all pins as outputs */
+	w[0] = 3;
+	w[1] = 0;
+
+	i2c_transfer(&state->d->i2c_adap, &msg, 1);
+
+	return 0;
+}
+
+int mxl111sf_set_gpio(struct mxl111sf_state *state, int gpio, int val)
+{
+	mxl_debug("(%d, %d)", gpio, val);
+
+	switch (state->gpio_port_expander) {
+	default:
+		mxl_printk(KERN_ERR,
+			   "gpio_port_expander undefined, assuming PCA9534");
+		/* fall-thru */
+	case mxl111sf_PCA9534:
+		return pca9534_set_gpio(state, gpio, val);
+	case mxl111sf_gpio_hw:
+		return mxl111sf_hw_set_gpio(state, gpio, val);
+	}
+}
+
+static int mxl111sf_probe_port_expander(struct mxl111sf_state *state)
+{
+	int ret;
+	u8 w = 1;
+	u8 r = 0;
+	struct i2c_msg msg[] = {
+		{ .flags = 0,        .buf = &w, .len = 1 },
+		{ .flags = I2C_M_RD, .buf = &r, .len = 1 },
+	};
+
+	mxl_debug("()");
+
+	msg[0].addr = 0x70 >> 1;
+	msg[1].addr = 0x70 >> 1;
+
+	/* read current GPIO levels from flip-flop */
+	ret = i2c_transfer(&state->d->i2c_adap, msg, 2);
+	if (ret == 2) {
+		state->port_expander_addr = msg[0].addr;
+		state->gpio_port_expander = mxl111sf_PCA9534;
+		mxl_debug("found port expander at 0x%02x",
+			  state->port_expander_addr);
+		return 0;
+	}
+
+	msg[0].addr = 0x40 >> 1;
+	msg[1].addr = 0x40 >> 1;
+
+	ret = i2c_transfer(&state->d->i2c_adap, msg, 2);
+	if (ret == 2) {
+		state->port_expander_addr = msg[0].addr;
+		state->gpio_port_expander = mxl111sf_PCA9534;
+		mxl_debug("found port expander at 0x%02x",
+			  state->port_expander_addr);
+		return 0;
+	}
+	state->port_expander_addr = 0xff;
+	state->gpio_port_expander = mxl111sf_gpio_hw;
+	mxl_debug("using hardware gpio");
+	return 0;
+}
+
+int mxl111sf_init_port_expander(struct mxl111sf_state *state)
+{
+	mxl_debug("()");
+
+	if (0x00 == state->port_expander_addr)
+		mxl111sf_probe_port_expander(state);
+
+	switch (state->gpio_port_expander) {
+	default:
+		mxl_printk(KERN_ERR,
+			   "gpio_port_expander undefined, assuming PCA9534");
+		/* fall-thru */
+	case mxl111sf_PCA9534:
+		return pca9534_init_port_expander(state);
+	case mxl111sf_gpio_hw:
+		return mxl111sf_hw_gpio_initialize(state);
+	}
+}
+
+/* ------------------------------------------------------------------------ */
+
+int mxl111sf_gpio_mode_switch(struct mxl111sf_state *state, unsigned int mode)
+{
+/*	GPO:
+ *	3 - ATSC/MH#   | 1 = ATSC transport, 0 = MH transport      | default 0
+ *	4 - ATSC_RST## | 1 = ATSC enable, 0 = ATSC Reset           | default 0
+ *	5 - ATSC_EN    | 1 = ATSC power enable, 0 = ATSC power off | default 0
+ *	6 - MH_RESET#  | 1 = MH enable, 0 = MH Reset               | default 0
+ *	7 - MH_EN      | 1 = MH power enable, 0 = MH power off     | default 0
+ */
+	mxl_debug("(%d)", mode);
+
+	switch (mode) {
+	case MXL111SF_GPIO_MOD_MH:
+		mxl111sf_set_gpio(state, 4, 0);
+		mxl111sf_set_gpio(state, 5, 0);
+		msleep(50);
+		mxl111sf_set_gpio(state, 7, 1);
+		msleep(50);
+		mxl111sf_set_gpio(state, 6, 1);
+		msleep(50);
+
+		mxl111sf_set_gpio(state, 3, 0);
+		break;
+	case MXL111SF_GPIO_MOD_ATSC:
+		mxl111sf_set_gpio(state, 6, 0);
+		mxl111sf_set_gpio(state, 7, 0);
+		msleep(50);
+		mxl111sf_set_gpio(state, 5, 1);
+		msleep(50);
+		mxl111sf_set_gpio(state, 4, 1);
+		msleep(50);
+		mxl111sf_set_gpio(state, 3, 1);
+		break;
+	default: /* DVBT / STANDBY */
+		mxl111sf_init_port_expander(state);
+		break;
+	}
+	return 0;
+}
diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf-gpio.h b/drivers/media/usb/dvb-usb-v2/mxl111sf-gpio.h
new file mode 100644
index 0000000..af2c7bc
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/mxl111sf-gpio.h
@@ -0,0 +1,46 @@
+/*
+ *  mxl111sf-gpio.h - driver for the MaxLinear MXL111SF
+ *
+ *  Copyright (C) 2010-2014 Michael Krufky <mkrufky@linuxtv.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ */
+
+#ifndef _DVB_USB_MXL111SF_GPIO_H_
+#define _DVB_USB_MXL111SF_GPIO_H_
+
+#include "mxl111sf.h"
+
+int mxl111sf_set_gpio(struct mxl111sf_state *state, int gpio, int val);
+int mxl111sf_init_port_expander(struct mxl111sf_state *state);
+
+#define MXL111SF_GPIO_MOD_DVBT	0
+#define MXL111SF_GPIO_MOD_MH	1
+#define MXL111SF_GPIO_MOD_ATSC	2
+int mxl111sf_gpio_mode_switch(struct mxl111sf_state *state, unsigned int mode);
+
+enum mxl111sf_mux_config {
+	PIN_MUX_DEFAULT = 0,
+	PIN_MUX_TS_OUT_PARALLEL,
+	PIN_MUX_TS_OUT_SERIAL,
+	PIN_MUX_GPIO_MODE,
+	PIN_MUX_TS_SERIAL_IN_MODE_0,
+	PIN_MUX_TS_SERIAL_IN_MODE_1,
+	PIN_MUX_TS_SPI_IN_MODE_0,
+	PIN_MUX_TS_SPI_IN_MODE_1,
+	PIN_MUX_TS_PARALLEL_IN,
+	PIN_MUX_BT656_I2S_MODE,
+};
+
+int mxl111sf_config_pin_mux_modes(struct mxl111sf_state *state,
+				  enum mxl111sf_mux_config pin_mux_config);
+
+#endif /* _DVB_USB_MXL111SF_GPIO_H_ */
diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf-i2c.c b/drivers/media/usb/dvb-usb-v2/mxl111sf-i2c.c
new file mode 100644
index 0000000..a221bb8
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/mxl111sf-i2c.c
@@ -0,0 +1,837 @@
+/*
+ *  mxl111sf-i2c.c - driver for the MaxLinear MXL111SF
+ *
+ *  Copyright (C) 2010-2014 Michael Krufky <mkrufky@linuxtv.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ */
+
+#include "mxl111sf-i2c.h"
+#include "mxl111sf.h"
+
+/* SW-I2C ----------------------------------------------------------------- */
+
+#define SW_I2C_ADDR		0x1a
+#define SW_I2C_EN		0x02
+#define SW_SCL_OUT		0x04
+#define SW_SDA_OUT		0x08
+#define SW_SDA_IN		0x04
+
+#define SW_I2C_BUSY_ADDR	0x2f
+#define SW_I2C_BUSY		0x02
+
+static int mxl111sf_i2c_bitbang_sendbyte(struct mxl111sf_state *state,
+					 u8 byte)
+{
+	int i, ret;
+	u8 data = 0;
+
+	mxl_i2c("(0x%02x)", byte);
+
+	ret = mxl111sf_read_reg(state, SW_I2C_BUSY_ADDR, &data);
+	if (mxl_fail(ret))
+		goto fail;
+
+	for (i = 0; i < 8; i++) {
+
+		data = (byte & (0x80 >> i)) ? SW_SDA_OUT : 0;
+
+		ret = mxl111sf_write_reg(state, SW_I2C_ADDR,
+					 0x10 | SW_I2C_EN | data);
+		if (mxl_fail(ret))
+			goto fail;
+
+		ret = mxl111sf_write_reg(state, SW_I2C_ADDR,
+					 0x10 | SW_I2C_EN | data | SW_SCL_OUT);
+		if (mxl_fail(ret))
+			goto fail;
+
+		ret = mxl111sf_write_reg(state, SW_I2C_ADDR,
+					 0x10 | SW_I2C_EN | data);
+		if (mxl_fail(ret))
+			goto fail;
+	}
+
+	/* last bit was 0 so we need to release SDA */
+	if (!(byte & 1)) {
+		ret = mxl111sf_write_reg(state, SW_I2C_ADDR,
+					 0x10 | SW_I2C_EN | SW_SDA_OUT);
+		if (mxl_fail(ret))
+			goto fail;
+	}
+
+	/* CLK high for ACK readback */
+	ret = mxl111sf_write_reg(state, SW_I2C_ADDR,
+				 0x10 | SW_I2C_EN | SW_SCL_OUT | SW_SDA_OUT);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl111sf_read_reg(state, SW_I2C_BUSY_ADDR, &data);
+	if (mxl_fail(ret))
+		goto fail;
+
+	/* drop the CLK after getting ACK, SDA will go high right away */
+	ret = mxl111sf_write_reg(state, SW_I2C_ADDR,
+				 0x10 | SW_I2C_EN | SW_SDA_OUT);
+	if (mxl_fail(ret))
+		goto fail;
+
+	if (data & SW_SDA_IN)
+		ret = -EIO;
+fail:
+	return ret;
+}
+
+static int mxl111sf_i2c_bitbang_recvbyte(struct mxl111sf_state *state,
+					 u8 *pbyte)
+{
+	int i, ret;
+	u8 byte = 0;
+	u8 data = 0;
+
+	mxl_i2c("()");
+
+	*pbyte = 0;
+
+	ret = mxl111sf_write_reg(state, SW_I2C_ADDR,
+				 0x10 | SW_I2C_EN | SW_SDA_OUT);
+	if (mxl_fail(ret))
+		goto fail;
+
+	for (i = 0; i < 8; i++) {
+		ret = mxl111sf_write_reg(state, SW_I2C_ADDR,
+					 0x10 | SW_I2C_EN |
+					 SW_SCL_OUT | SW_SDA_OUT);
+		if (mxl_fail(ret))
+			goto fail;
+
+		ret = mxl111sf_read_reg(state, SW_I2C_BUSY_ADDR, &data);
+		if (mxl_fail(ret))
+			goto fail;
+
+		if (data & SW_SDA_IN)
+			byte |= (0x80 >> i);
+
+		ret = mxl111sf_write_reg(state, SW_I2C_ADDR,
+					 0x10 | SW_I2C_EN | SW_SDA_OUT);
+		if (mxl_fail(ret))
+			goto fail;
+	}
+	*pbyte = byte;
+fail:
+	return ret;
+}
+
+static int mxl111sf_i2c_start(struct mxl111sf_state *state)
+{
+	int ret;
+
+	mxl_i2c("()");
+
+	ret = mxl111sf_write_reg(state, SW_I2C_ADDR,
+				 0x10 | SW_I2C_EN | SW_SCL_OUT | SW_SDA_OUT);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl111sf_write_reg(state, SW_I2C_ADDR,
+				 0x10 | SW_I2C_EN | SW_SCL_OUT);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl111sf_write_reg(state, SW_I2C_ADDR,
+				 0x10 | SW_I2C_EN); /* start */
+	mxl_fail(ret);
+fail:
+	return ret;
+}
+
+static int mxl111sf_i2c_stop(struct mxl111sf_state *state)
+{
+	int ret;
+
+	mxl_i2c("()");
+
+	ret = mxl111sf_write_reg(state, SW_I2C_ADDR,
+				 0x10 | SW_I2C_EN); /* stop */
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl111sf_write_reg(state, SW_I2C_ADDR,
+				 0x10 | SW_I2C_EN | SW_SCL_OUT);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl111sf_write_reg(state, SW_I2C_ADDR,
+				 0x10 | SW_I2C_EN | SW_SCL_OUT | SW_SDA_OUT);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl111sf_write_reg(state, SW_I2C_ADDR,
+				 0x10 | SW_SCL_OUT | SW_SDA_OUT);
+	mxl_fail(ret);
+fail:
+	return ret;
+}
+
+static int mxl111sf_i2c_ack(struct mxl111sf_state *state)
+{
+	int ret;
+	u8 b = 0;
+
+	mxl_i2c("()");
+
+	ret = mxl111sf_read_reg(state, SW_I2C_BUSY_ADDR, &b);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl111sf_write_reg(state, SW_I2C_ADDR,
+				 0x10 | SW_I2C_EN);
+	if (mxl_fail(ret))
+		goto fail;
+
+	/* pull SDA low */
+	ret = mxl111sf_write_reg(state, SW_I2C_ADDR,
+				 0x10 | SW_I2C_EN | SW_SCL_OUT);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl111sf_write_reg(state, SW_I2C_ADDR,
+				 0x10 | SW_I2C_EN | SW_SDA_OUT);
+	mxl_fail(ret);
+fail:
+	return ret;
+}
+
+static int mxl111sf_i2c_nack(struct mxl111sf_state *state)
+{
+	int ret;
+
+	mxl_i2c("()");
+
+	/* SDA high to signal last byte read from slave */
+	ret = mxl111sf_write_reg(state, SW_I2C_ADDR,
+				 0x10 | SW_I2C_EN | SW_SCL_OUT | SW_SDA_OUT);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl111sf_write_reg(state, SW_I2C_ADDR,
+				 0x10 | SW_I2C_EN | SW_SDA_OUT);
+	mxl_fail(ret);
+fail:
+	return ret;
+}
+
+/* ------------------------------------------------------------------------ */
+
+static int mxl111sf_i2c_sw_xfer_msg(struct mxl111sf_state *state,
+				    struct i2c_msg *msg)
+{
+	int i, ret;
+
+	mxl_i2c("()");
+
+	if (msg->flags & I2C_M_RD) {
+
+		ret = mxl111sf_i2c_start(state);
+		if (mxl_fail(ret))
+			goto fail;
+
+		ret = mxl111sf_i2c_bitbang_sendbyte(state,
+						    (msg->addr << 1) | 0x01);
+		if (mxl_fail(ret)) {
+			mxl111sf_i2c_stop(state);
+			goto fail;
+		}
+
+		for (i = 0; i < msg->len; i++) {
+			ret = mxl111sf_i2c_bitbang_recvbyte(state,
+							    &msg->buf[i]);
+			if (mxl_fail(ret)) {
+				mxl111sf_i2c_stop(state);
+				goto fail;
+			}
+
+			if (i < msg->len - 1)
+				mxl111sf_i2c_ack(state);
+		}
+
+		mxl111sf_i2c_nack(state);
+
+		ret = mxl111sf_i2c_stop(state);
+		if (mxl_fail(ret))
+			goto fail;
+
+	} else {
+
+		ret = mxl111sf_i2c_start(state);
+		if (mxl_fail(ret))
+			goto fail;
+
+		ret = mxl111sf_i2c_bitbang_sendbyte(state,
+						    (msg->addr << 1) & 0xfe);
+		if (mxl_fail(ret)) {
+			mxl111sf_i2c_stop(state);
+			goto fail;
+		}
+
+		for (i = 0; i < msg->len; i++) {
+			ret = mxl111sf_i2c_bitbang_sendbyte(state,
+							    msg->buf[i]);
+			if (mxl_fail(ret)) {
+				mxl111sf_i2c_stop(state);
+				goto fail;
+			}
+		}
+
+		/* FIXME: we only want to do this on the last transaction */
+		mxl111sf_i2c_stop(state);
+	}
+fail:
+	return ret;
+}
+
+/* HW-I2C ----------------------------------------------------------------- */
+
+#define USB_WRITE_I2C_CMD     0x99
+#define USB_READ_I2C_CMD      0xdd
+#define USB_END_I2C_CMD       0xfe
+
+#define USB_WRITE_I2C_CMD_LEN   26
+#define USB_READ_I2C_CMD_LEN    24
+
+#define I2C_MUX_REG           0x30
+#define I2C_CONTROL_REG       0x00
+#define I2C_SLAVE_ADDR_REG    0x08
+#define I2C_DATA_REG          0x0c
+#define I2C_INT_STATUS_REG    0x10
+
+static int mxl111sf_i2c_send_data(struct mxl111sf_state *state,
+				  u8 index, u8 *wdata)
+{
+	int ret = mxl111sf_ctrl_msg(state, wdata[0],
+				    &wdata[1], 25, NULL, 0);
+	mxl_fail(ret);
+
+	return ret;
+}
+
+static int mxl111sf_i2c_get_data(struct mxl111sf_state *state,
+				 u8 index, u8 *wdata, u8 *rdata)
+{
+	int ret = mxl111sf_ctrl_msg(state, wdata[0],
+				    &wdata[1], 25, rdata, 24);
+	mxl_fail(ret);
+
+	return ret;
+}
+
+static u8 mxl111sf_i2c_check_status(struct mxl111sf_state *state)
+{
+	u8 status = 0;
+	u8 buf[26];
+
+	mxl_i2c_adv("()");
+
+	buf[0] = USB_READ_I2C_CMD;
+	buf[1] = 0x00;
+
+	buf[2] = I2C_INT_STATUS_REG;
+	buf[3] = 0x00;
+	buf[4] = 0x00;
+
+	buf[5] = USB_END_I2C_CMD;
+
+	mxl111sf_i2c_get_data(state, 0, buf, buf);
+
+	if (buf[1] & 0x04)
+		status = 1;
+
+	return status;
+}
+
+static u8 mxl111sf_i2c_check_fifo(struct mxl111sf_state *state)
+{
+	u8 status = 0;
+	u8 buf[26];
+
+	mxl_i2c("()");
+
+	buf[0] = USB_READ_I2C_CMD;
+	buf[1] = 0x00;
+
+	buf[2] = I2C_MUX_REG;
+	buf[3] = 0x00;
+	buf[4] = 0x00;
+
+	buf[5] = I2C_INT_STATUS_REG;
+	buf[6] = 0x00;
+	buf[7] = 0x00;
+	buf[8] = USB_END_I2C_CMD;
+
+	mxl111sf_i2c_get_data(state, 0, buf, buf);
+
+	if (0x08 == (buf[1] & 0x08))
+		status = 1;
+
+	if ((buf[5] & 0x02) == 0x02)
+		mxl_i2c("(buf[5] & 0x02) == 0x02"); /* FIXME */
+
+	return status;
+}
+
+static int mxl111sf_i2c_readagain(struct mxl111sf_state *state,
+				  u8 count, u8 *rbuf)
+{
+	u8 i2c_w_data[26];
+	u8 i2c_r_data[24];
+	u8 i = 0;
+	u8 fifo_status = 0;
+	int status = 0;
+
+	mxl_i2c("read %d bytes", count);
+
+	while ((fifo_status == 0) && (i++ < 5))
+		fifo_status = mxl111sf_i2c_check_fifo(state);
+
+	i2c_w_data[0] = 0xDD;
+	i2c_w_data[1] = 0x00;
+
+	for (i = 2; i < 26; i++)
+		i2c_w_data[i] = 0xFE;
+
+	for (i = 0; i < count; i++) {
+		i2c_w_data[2+(i*3)] = 0x0C;
+		i2c_w_data[3+(i*3)] = 0x00;
+		i2c_w_data[4+(i*3)] = 0x00;
+	}
+
+	mxl111sf_i2c_get_data(state, 0, i2c_w_data, i2c_r_data);
+
+	/* Check for I2C NACK status */
+	if (mxl111sf_i2c_check_status(state) == 1) {
+		mxl_i2c("error!");
+	} else {
+		for (i = 0; i < count; i++) {
+			rbuf[i] = i2c_r_data[(i*3)+1];
+			mxl_i2c("%02x\t %02x",
+				i2c_r_data[(i*3)+1],
+				i2c_r_data[(i*3)+2]);
+		}
+
+		status = 1;
+	}
+
+	return status;
+}
+
+#define HWI2C400 1
+static int mxl111sf_i2c_hw_xfer_msg(struct mxl111sf_state *state,
+				    struct i2c_msg *msg)
+{
+	int i, k, ret = 0;
+	u16 index = 0;
+	u8 buf[26];
+	u8 i2c_r_data[24];
+	u16 block_len;
+	u16 left_over_len;
+	u8 rd_status[8];
+	u8 ret_status;
+	u8 readbuff[26];
+
+	mxl_i2c("addr: 0x%02x, read buff len: %d, write buff len: %d",
+		msg->addr, (msg->flags & I2C_M_RD) ? msg->len : 0,
+		(!(msg->flags & I2C_M_RD)) ? msg->len : 0);
+
+	for (index = 0; index < 26; index++)
+		buf[index] = USB_END_I2C_CMD;
+
+	/* command to indicate data payload is destined for I2C interface */
+	buf[0] = USB_WRITE_I2C_CMD;
+	buf[1] = 0x00;
+
+	/* enable I2C interface */
+	buf[2] = I2C_MUX_REG;
+	buf[3] = 0x80;
+	buf[4] = 0x00;
+
+	/* enable I2C interface */
+	buf[5] = I2C_MUX_REG;
+	buf[6] = 0x81;
+	buf[7] = 0x00;
+
+	/* set Timeout register on I2C interface */
+	buf[8] = 0x14;
+	buf[9] = 0xff;
+	buf[10] = 0x00;
+#if 0
+	/* enable Interrupts on I2C interface */
+	buf[8] = 0x24;
+	buf[9] = 0xF7;
+	buf[10] = 0x00;
+#endif
+	buf[11] = 0x24;
+	buf[12] = 0xF7;
+	buf[13] = 0x00;
+
+	ret = mxl111sf_i2c_send_data(state, 0, buf);
+
+	/* write data on I2C bus */
+	if (!(msg->flags & I2C_M_RD) && (msg->len > 0)) {
+		mxl_i2c("%d\t%02x", msg->len, msg->buf[0]);
+
+		/* control register on I2C interface to initialize I2C bus */
+		buf[2] = I2C_CONTROL_REG;
+		buf[3] = 0x5E;
+		buf[4] = (HWI2C400) ? 0x03 : 0x0D;
+
+		/* I2C Slave device Address */
+		buf[5] = I2C_SLAVE_ADDR_REG;
+		buf[6] = (msg->addr);
+		buf[7] = 0x00;
+		buf[8] = USB_END_I2C_CMD;
+		ret = mxl111sf_i2c_send_data(state, 0, buf);
+
+		/* check for slave device status */
+		if (mxl111sf_i2c_check_status(state) == 1) {
+			mxl_i2c("NACK writing slave address %02x",
+				msg->addr);
+			/* if NACK, stop I2C bus and exit */
+			buf[2] = I2C_CONTROL_REG;
+			buf[3] = 0x4E;
+			buf[4] = (HWI2C400) ? 0x03 : 0x0D;
+			ret = -EIO;
+			goto exit;
+		}
+
+		/* I2C interface can do I2C operations in block of 8 bytes of
+		   I2C data. calculation to figure out number of blocks of i2c
+		   data required to program */
+		block_len = (msg->len / 8);
+		left_over_len = (msg->len % 8);
+
+		mxl_i2c("block_len %d, left_over_len %d",
+			block_len, left_over_len);
+
+		for (index = 0; index < block_len; index++) {
+			for (i = 0; i < 8; i++) {
+				/* write data on I2C interface */
+				buf[2+(i*3)] = I2C_DATA_REG;
+				buf[3+(i*3)] = msg->buf[(index*8)+i];
+				buf[4+(i*3)] = 0x00;
+			}
+
+			ret = mxl111sf_i2c_send_data(state, 0, buf);
+
+			/* check for I2C NACK status */
+			if (mxl111sf_i2c_check_status(state) == 1) {
+				mxl_i2c("NACK writing slave address %02x",
+					msg->addr);
+
+				/* if NACK, stop I2C bus and exit */
+				buf[2] = I2C_CONTROL_REG;
+				buf[3] = 0x4E;
+				buf[4] = (HWI2C400) ? 0x03 : 0x0D;
+				ret = -EIO;
+				goto exit;
+			}
+
+		}
+
+		if (left_over_len) {
+			for (k = 0; k < 26; k++)
+				buf[k] = USB_END_I2C_CMD;
+
+			buf[0] = 0x99;
+			buf[1] = 0x00;
+
+			for (i = 0; i < left_over_len; i++) {
+				buf[2+(i*3)] = I2C_DATA_REG;
+				buf[3+(i*3)] = msg->buf[(index*8)+i];
+				mxl_i2c("index = %d %d data %d",
+					index, i, msg->buf[(index*8)+i]);
+				buf[4+(i*3)] = 0x00;
+			}
+			ret = mxl111sf_i2c_send_data(state, 0, buf);
+
+			/* check for I2C NACK status */
+			if (mxl111sf_i2c_check_status(state) == 1) {
+				mxl_i2c("NACK writing slave address %02x",
+					msg->addr);
+
+				/* if NACK, stop I2C bus and exit */
+				buf[2] = I2C_CONTROL_REG;
+				buf[3] = 0x4E;
+				buf[4] = (HWI2C400) ? 0x03 : 0x0D;
+				ret = -EIO;
+				goto exit;
+			}
+
+		}
+
+		/* issue I2C STOP after write */
+		buf[2] = I2C_CONTROL_REG;
+		buf[3] = 0x4E;
+		buf[4] = (HWI2C400) ? 0x03 : 0x0D;
+
+	}
+
+	/* read data from I2C bus */
+	if ((msg->flags & I2C_M_RD) && (msg->len > 0)) {
+		mxl_i2c("read buf len %d", msg->len);
+
+		/* command to indicate data payload is
+		   destined for I2C interface */
+		buf[2] = I2C_CONTROL_REG;
+		buf[3] = 0xDF;
+		buf[4] = (HWI2C400) ? 0x03 : 0x0D;
+
+		/* I2C xfer length */
+		buf[5] = 0x14;
+		buf[6] = (msg->len & 0xFF);
+		buf[7] = 0;
+
+		/* I2C slave device Address */
+		buf[8] = I2C_SLAVE_ADDR_REG;
+		buf[9] = msg->addr;
+		buf[10] = 0x00;
+		buf[11] = USB_END_I2C_CMD;
+		ret = mxl111sf_i2c_send_data(state, 0, buf);
+
+		/* check for I2C NACK status */
+		if (mxl111sf_i2c_check_status(state) == 1) {
+			mxl_i2c("NACK reading slave address %02x",
+				msg->addr);
+
+			/* if NACK, stop I2C bus and exit */
+			buf[2] = I2C_CONTROL_REG;
+			buf[3] = 0xC7;
+			buf[4] = (HWI2C400) ? 0x03 : 0x0D;
+			ret = -EIO;
+			goto exit;
+		}
+
+		/* I2C interface can do I2C operations in block of 8 bytes of
+		   I2C data. calculation to figure out number of blocks of
+		   i2c data required to program */
+		block_len = ((msg->len) / 8);
+		left_over_len = ((msg->len) % 8);
+		index = 0;
+
+		mxl_i2c("block_len %d, left_over_len %d",
+			block_len, left_over_len);
+
+		/* command to read data from I2C interface */
+		buf[0] = USB_READ_I2C_CMD;
+		buf[1] = 0x00;
+
+		for (index = 0; index < block_len; index++) {
+			/* setup I2C read request packet on I2C interface */
+			for (i = 0; i < 8; i++) {
+				buf[2+(i*3)] = I2C_DATA_REG;
+				buf[3+(i*3)] = 0x00;
+				buf[4+(i*3)] = 0x00;
+			}
+
+			ret = mxl111sf_i2c_get_data(state, 0, buf, i2c_r_data);
+
+			/* check for I2C NACK status */
+			if (mxl111sf_i2c_check_status(state) == 1) {
+				mxl_i2c("NACK reading slave address %02x",
+					msg->addr);
+
+				/* if NACK, stop I2C bus and exit */
+				buf[2] = I2C_CONTROL_REG;
+				buf[3] = 0xC7;
+				buf[4] = (HWI2C400) ? 0x03 : 0x0D;
+				ret = -EIO;
+				goto exit;
+			}
+
+			/* copy data from i2c data payload to read buffer */
+			for (i = 0; i < 8; i++) {
+				rd_status[i] = i2c_r_data[(i*3)+2];
+
+				if (rd_status[i] == 0x04) {
+					if (i < 7) {
+						mxl_i2c("i2c fifo empty! @ %d",
+							i);
+						msg->buf[(index*8)+i] =
+							i2c_r_data[(i*3)+1];
+						/* read again */
+						ret_status =
+							mxl111sf_i2c_readagain(
+								state, 8-(i+1),
+								readbuff);
+						if (ret_status == 1) {
+							for (k = 0;
+							     k < 8-(i+1);
+							     k++) {
+
+					msg->buf[(index*8)+(k+i+1)] =
+						readbuff[k];
+					mxl_i2c("read data: %02x\t %02x",
+						msg->buf[(index*8)+(k+i)],
+						(index*8)+(k+i));
+					mxl_i2c("read data: %02x\t %02x",
+						msg->buf[(index*8)+(k+i+1)],
+						readbuff[k]);
+
+							}
+							goto stop_copy;
+						} else {
+							mxl_i2c("readagain ERROR!");
+						}
+					} else {
+						msg->buf[(index*8)+i] =
+							i2c_r_data[(i*3)+1];
+					}
+				} else {
+					msg->buf[(index*8)+i] =
+						i2c_r_data[(i*3)+1];
+				}
+			}
+stop_copy:
+			;
+
+		}
+
+		if (left_over_len) {
+			for (k = 0; k < 26; k++)
+				buf[k] = USB_END_I2C_CMD;
+
+			buf[0] = 0xDD;
+			buf[1] = 0x00;
+
+			for (i = 0; i < left_over_len; i++) {
+				buf[2+(i*3)] = I2C_DATA_REG;
+				buf[3+(i*3)] = 0x00;
+				buf[4+(i*3)] = 0x00;
+			}
+			ret = mxl111sf_i2c_get_data(state, 0, buf,
+						    i2c_r_data);
+
+			/* check for I2C NACK status */
+			if (mxl111sf_i2c_check_status(state) == 1) {
+				mxl_i2c("NACK reading slave address %02x",
+					msg->addr);
+
+				/* if NACK, stop I2C bus and exit */
+				buf[2] = I2C_CONTROL_REG;
+				buf[3] = 0xC7;
+				buf[4] = (HWI2C400) ? 0x03 : 0x0D;
+				ret = -EIO;
+				goto exit;
+			}
+
+			for (i = 0; i < left_over_len; i++) {
+				msg->buf[(block_len*8)+i] =
+					i2c_r_data[(i*3)+1];
+				mxl_i2c("read data: %02x\t %02x",
+					i2c_r_data[(i*3)+1],
+					i2c_r_data[(i*3)+2]);
+			}
+		}
+
+		/* indicate I2C interface to issue NACK
+		   after next I2C read op */
+		buf[0] = USB_WRITE_I2C_CMD;
+		buf[1] = 0x00;
+
+		/* control register */
+		buf[2] = I2C_CONTROL_REG;
+		buf[3] = 0x17;
+		buf[4] = (HWI2C400) ? 0x03 : 0x0D;
+
+		buf[5] = USB_END_I2C_CMD;
+		ret = mxl111sf_i2c_send_data(state, 0, buf);
+
+		/* control register */
+		buf[2] = I2C_CONTROL_REG;
+		buf[3] = 0xC7;
+		buf[4] = (HWI2C400) ? 0x03 : 0x0D;
+
+	}
+exit:
+	/* STOP and disable I2C MUX */
+	buf[0] = USB_WRITE_I2C_CMD;
+	buf[1] = 0x00;
+
+	/* de-initilize I2C BUS */
+	buf[5] = USB_END_I2C_CMD;
+	mxl111sf_i2c_send_data(state, 0, buf);
+
+	/* Control Register */
+	buf[2] = I2C_CONTROL_REG;
+	buf[3] = 0xDF;
+	buf[4] = 0x03;
+
+	/* disable I2C interface */
+	buf[5] = I2C_MUX_REG;
+	buf[6] = 0x00;
+	buf[7] = 0x00;
+
+	/* de-initilize I2C BUS */
+	buf[8] = USB_END_I2C_CMD;
+	mxl111sf_i2c_send_data(state, 0, buf);
+
+	/* disable I2C interface */
+	buf[2] = I2C_MUX_REG;
+	buf[3] = 0x81;
+	buf[4] = 0x00;
+
+	/* disable I2C interface */
+	buf[5] = I2C_MUX_REG;
+	buf[6] = 0x00;
+	buf[7] = 0x00;
+
+	/* disable I2C interface */
+	buf[8] = I2C_MUX_REG;
+	buf[9] = 0x00;
+	buf[10] = 0x00;
+
+	buf[11] = USB_END_I2C_CMD;
+	mxl111sf_i2c_send_data(state, 0, buf);
+
+	return ret;
+}
+
+/* ------------------------------------------------------------------------ */
+
+int mxl111sf_i2c_xfer(struct i2c_adapter *adap,
+		      struct i2c_msg msg[], int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	struct mxl111sf_state *state = d->priv;
+	int hwi2c = (state->chip_rev > MXL111SF_V6);
+	int i, ret;
+
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	for (i = 0; i < num; i++) {
+		ret = (hwi2c) ?
+			mxl111sf_i2c_hw_xfer_msg(state, &msg[i]) :
+			mxl111sf_i2c_sw_xfer_msg(state, &msg[i]);
+		if (mxl_fail(ret)) {
+			mxl_debug_adv("failed with error %d on i2c transaction %d of %d, %sing %d bytes to/from 0x%02x",
+				      ret, i+1, num,
+				      (msg[i].flags & I2C_M_RD) ?
+				      "read" : "writ",
+				      msg[i].len, msg[i].addr);
+
+			break;
+		}
+	}
+
+	mutex_unlock(&d->i2c_mutex);
+
+	return i == num ? num : -EREMOTEIO;
+}
diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf-i2c.h b/drivers/media/usb/dvb-usb-v2/mxl111sf-i2c.h
new file mode 100644
index 0000000..28877c7
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/mxl111sf-i2c.h
@@ -0,0 +1,25 @@
+/*
+ *  mxl111sf-i2c.h - driver for the MaxLinear MXL111SF
+ *
+ *  Copyright (C) 2010-2014 Michael Krufky <mkrufky@linuxtv.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ */
+
+#ifndef _DVB_USB_MXL111SF_I2C_H_
+#define _DVB_USB_MXL111SF_I2C_H_
+
+#include <linux/i2c.h>
+
+int mxl111sf_i2c_xfer(struct i2c_adapter *adap,
+		      struct i2c_msg msg[], int num);
+
+#endif /* _DVB_USB_MXL111SF_I2C_H_ */
diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf-phy.c b/drivers/media/usb/dvb-usb-v2/mxl111sf-phy.c
new file mode 100644
index 0000000..ffb6e7c
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/mxl111sf-phy.c
@@ -0,0 +1,333 @@
+/*
+ *  mxl111sf-phy.c - driver for the MaxLinear MXL111SF
+ *
+ *  Copyright (C) 2010-2014 Michael Krufky <mkrufky@linuxtv.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ */
+
+#include "mxl111sf-phy.h"
+#include "mxl111sf-reg.h"
+
+int mxl111sf_init_tuner_demod(struct mxl111sf_state *state)
+{
+	struct mxl111sf_reg_ctrl_info mxl_111_overwrite_default[] = {
+		{0x07, 0xff, 0x0c},
+		{0x58, 0xff, 0x9d},
+		{0x09, 0xff, 0x00},
+		{0x06, 0xff, 0x06},
+		{0xc8, 0xff, 0x40}, /* ED_LE_WIN_OLD = 0 */
+		{0x8d, 0x01, 0x01}, /* NEGATE_Q */
+		{0x32, 0xff, 0xac}, /* DIG_RFREFSELECT = 12 */
+		{0x42, 0xff, 0x43}, /* DIG_REG_AMP = 4 */
+		{0x74, 0xff, 0xc4}, /* SSPUR_FS_PRIO = 4 */
+		{0x71, 0xff, 0xe6}, /* SPUR_ROT_PRIO_VAL = 1 */
+		{0x83, 0xff, 0x64}, /* INF_FILT1_THD_SC = 100 */
+		{0x85, 0xff, 0x64}, /* INF_FILT2_THD_SC = 100 */
+		{0x88, 0xff, 0xf0}, /* INF_THD = 240 */
+		{0x6f, 0xf0, 0xb0}, /* DFE_DLY = 11 */
+		{0x00, 0xff, 0x01}, /* Change to page 1 */
+		{0x81, 0xff, 0x11}, /* DSM_FERR_BYPASS = 1 */
+		{0xf4, 0xff, 0x07}, /* DIG_FREQ_CORR = 1 */
+		{0xd4, 0x1f, 0x0f}, /* SPUR_TEST_NOISE_TH = 15 */
+		{0xd6, 0xff, 0x0c}, /* SPUR_TEST_NOISE_PAPR = 12 */
+		{0x00, 0xff, 0x00}, /* Change to page 0 */
+		{0,    0,    0}
+	};
+
+	mxl_debug("()");
+
+	return mxl111sf_ctrl_program_regs(state, mxl_111_overwrite_default);
+}
+
+int mxl1x1sf_soft_reset(struct mxl111sf_state *state)
+{
+	int ret;
+	mxl_debug("()");
+
+	ret = mxl111sf_write_reg(state, 0xff, 0x00); /* AIC */
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_write_reg(state, 0x02, 0x01); /* get out of reset */
+	mxl_fail(ret);
+fail:
+	return ret;
+}
+
+int mxl1x1sf_set_device_mode(struct mxl111sf_state *state, int mode)
+{
+	int ret;
+
+	mxl_debug("(%s)", MXL_SOC_MODE == mode ?
+		"MXL_SOC_MODE" : "MXL_TUNER_MODE");
+
+	/* set device mode */
+	ret = mxl111sf_write_reg(state, 0x03,
+				 MXL_SOC_MODE == mode ? 0x01 : 0x00);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl111sf_write_reg_mask(state,
+				      0x7d, 0x40, MXL_SOC_MODE == mode ?
+				      0x00 : /* enable impulse noise filter,
+						INF_BYP = 0 */
+				      0x40); /* disable impulse noise filter,
+						INF_BYP = 1 */
+	if (mxl_fail(ret))
+		goto fail;
+
+	state->device_mode = mode;
+fail:
+	return ret;
+}
+
+/* power up tuner */
+int mxl1x1sf_top_master_ctrl(struct mxl111sf_state *state, int onoff)
+{
+	mxl_debug("(%d)", onoff);
+
+	return mxl111sf_write_reg(state, 0x01, onoff ? 0x01 : 0x00);
+}
+
+int mxl111sf_disable_656_port(struct mxl111sf_state *state)
+{
+	mxl_debug("()");
+
+	return mxl111sf_write_reg_mask(state, 0x12, 0x04, 0x00);
+}
+
+int mxl111sf_enable_usb_output(struct mxl111sf_state *state)
+{
+	mxl_debug("()");
+
+	return mxl111sf_write_reg_mask(state, 0x17, 0x40, 0x00);
+}
+
+/* initialize TSIF as input port of MxL1X1SF for MPEG2 data transfer */
+int mxl111sf_config_mpeg_in(struct mxl111sf_state *state,
+			    unsigned int parallel_serial,
+			    unsigned int msb_lsb_1st,
+			    unsigned int clock_phase,
+			    unsigned int mpeg_valid_pol,
+			    unsigned int mpeg_sync_pol)
+{
+	int ret;
+	u8 mode, tmp;
+
+	mxl_debug("(%u,%u,%u,%u,%u)", parallel_serial, msb_lsb_1st,
+		  clock_phase, mpeg_valid_pol, mpeg_sync_pol);
+
+	/* Enable PIN MUX */
+	ret = mxl111sf_write_reg(state, V6_PIN_MUX_MODE_REG, V6_ENABLE_PIN_MUX);
+	mxl_fail(ret);
+
+	/* Configure MPEG Clock phase */
+	mxl111sf_read_reg(state, V6_MPEG_IN_CLK_INV_REG, &mode);
+
+	if (clock_phase == TSIF_NORMAL)
+		mode &= ~V6_INVERTED_CLK_PHASE;
+	else
+		mode |= V6_INVERTED_CLK_PHASE;
+
+	ret = mxl111sf_write_reg(state, V6_MPEG_IN_CLK_INV_REG, mode);
+	mxl_fail(ret);
+
+	/* Configure data input mode, MPEG Valid polarity, MPEG Sync polarity
+	 * Get current configuration */
+	ret = mxl111sf_read_reg(state, V6_MPEG_IN_CTRL_REG, &mode);
+	mxl_fail(ret);
+
+	/* Data Input mode */
+	if (parallel_serial == TSIF_INPUT_PARALLEL) {
+		/* Disable serial mode */
+		mode &= ~V6_MPEG_IN_DATA_SERIAL;
+
+		/* Enable Parallel mode */
+		mode |= V6_MPEG_IN_DATA_PARALLEL;
+	} else {
+		/* Disable Parallel mode */
+		mode &= ~V6_MPEG_IN_DATA_PARALLEL;
+
+		/* Enable Serial Mode */
+		mode |= V6_MPEG_IN_DATA_SERIAL;
+
+		/* If serial interface is chosen, configure
+		   MSB or LSB order in transmission */
+		ret = mxl111sf_read_reg(state,
+					V6_MPEG_INOUT_BIT_ORDER_CTRL_REG,
+					&tmp);
+		mxl_fail(ret);
+
+		if (msb_lsb_1st == MPEG_SER_MSB_FIRST_ENABLED)
+			tmp |= V6_MPEG_SER_MSB_FIRST;
+		else
+			tmp &= ~V6_MPEG_SER_MSB_FIRST;
+
+		ret = mxl111sf_write_reg(state,
+					 V6_MPEG_INOUT_BIT_ORDER_CTRL_REG,
+					 tmp);
+		mxl_fail(ret);
+	}
+
+	/* MPEG Sync polarity */
+	if (mpeg_sync_pol == TSIF_NORMAL)
+		mode &= ~V6_INVERTED_MPEG_SYNC;
+	else
+		mode |= V6_INVERTED_MPEG_SYNC;
+
+	/* MPEG Valid polarity */
+	if (mpeg_valid_pol == 0)
+		mode &= ~V6_INVERTED_MPEG_VALID;
+	else
+		mode |= V6_INVERTED_MPEG_VALID;
+
+	ret = mxl111sf_write_reg(state, V6_MPEG_IN_CTRL_REG, mode);
+	mxl_fail(ret);
+
+	return ret;
+}
+
+int mxl111sf_init_i2s_port(struct mxl111sf_state *state, u8 sample_size)
+{
+	static struct mxl111sf_reg_ctrl_info init_i2s[] = {
+		{0x1b, 0xff, 0x1e}, /* pin mux mode, Choose 656/I2S input */
+		{0x15, 0x60, 0x60}, /* Enable I2S */
+		{0x17, 0xe0, 0x20}, /* Input, MPEG MODE USB,
+				       Inverted 656 Clock, I2S_SOFT_RESET,
+				       0 : Normal operation, 1 : Reset State */
+#if 0
+		{0x12, 0x01, 0x00}, /* AUDIO_IRQ_CLR (Overflow Indicator) */
+#endif
+		{0x00, 0xff, 0x02}, /* Change to Control Page */
+		{0x26, 0x0d, 0x0d}, /* I2S_MODE & BT656_SRC_SEL for FPGA only */
+		{0x00, 0xff, 0x00},
+		{0,    0,    0}
+	};
+	int ret;
+
+	mxl_debug("(0x%02x)", sample_size);
+
+	ret = mxl111sf_ctrl_program_regs(state, init_i2s);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl111sf_write_reg(state, V6_I2S_NUM_SAMPLES_REG, sample_size);
+	mxl_fail(ret);
+fail:
+	return ret;
+}
+
+int mxl111sf_disable_i2s_port(struct mxl111sf_state *state)
+{
+	static struct mxl111sf_reg_ctrl_info disable_i2s[] = {
+		{0x15, 0x40, 0x00},
+		{0,    0,    0}
+	};
+
+	mxl_debug("()");
+
+	return mxl111sf_ctrl_program_regs(state, disable_i2s);
+}
+
+int mxl111sf_config_i2s(struct mxl111sf_state *state,
+			u8 msb_start_pos, u8 data_width)
+{
+	int ret;
+	u8 tmp;
+
+	mxl_debug("(0x%02x, 0x%02x)", msb_start_pos, data_width);
+
+	ret = mxl111sf_read_reg(state, V6_I2S_STREAM_START_BIT_REG, &tmp);
+	if (mxl_fail(ret))
+		goto fail;
+
+	tmp &= 0xe0;
+	tmp |= msb_start_pos;
+	ret = mxl111sf_write_reg(state, V6_I2S_STREAM_START_BIT_REG, tmp);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl111sf_read_reg(state, V6_I2S_STREAM_END_BIT_REG, &tmp);
+	if (mxl_fail(ret))
+		goto fail;
+
+	tmp &= 0xe0;
+	tmp |= data_width;
+	ret = mxl111sf_write_reg(state, V6_I2S_STREAM_END_BIT_REG, tmp);
+	mxl_fail(ret);
+fail:
+	return ret;
+}
+
+int mxl111sf_config_spi(struct mxl111sf_state *state, int onoff)
+{
+	u8 val;
+	int ret;
+
+	mxl_debug("(%d)", onoff);
+
+	ret = mxl111sf_write_reg(state, 0x00, 0x02);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl111sf_read_reg(state, V8_SPI_MODE_REG, &val);
+	if (mxl_fail(ret))
+		goto fail;
+
+	if (onoff)
+		val |= 0x04;
+	else
+		val &= ~0x04;
+
+	ret = mxl111sf_write_reg(state, V8_SPI_MODE_REG, val);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl111sf_write_reg(state, 0x00, 0x00);
+	mxl_fail(ret);
+fail:
+	return ret;
+}
+
+int mxl111sf_idac_config(struct mxl111sf_state *state,
+			 u8 control_mode, u8 current_setting,
+			 u8 current_value, u8 hysteresis_value)
+{
+	int ret;
+	u8 val;
+	/* current value will be set for both automatic & manual IDAC control */
+	val = current_value;
+
+	if (control_mode == IDAC_MANUAL_CONTROL) {
+		/* enable manual control of IDAC */
+		val |= IDAC_MANUAL_CONTROL_BIT_MASK;
+
+		if (current_setting == IDAC_CURRENT_SINKING_ENABLE)
+			/* enable current sinking in manual mode */
+			val |= IDAC_CURRENT_SINKING_BIT_MASK;
+		else
+			/* disable current sinking in manual mode */
+			val &= ~IDAC_CURRENT_SINKING_BIT_MASK;
+	} else {
+		/* disable manual control of IDAC */
+		val &= ~IDAC_MANUAL_CONTROL_BIT_MASK;
+
+		/* set hysteresis value  reg: 0x0B<5:0> */
+		ret = mxl111sf_write_reg(state, V6_IDAC_HYSTERESIS_REG,
+					 (hysteresis_value & 0x3F));
+		mxl_fail(ret);
+	}
+
+	ret = mxl111sf_write_reg(state, V6_IDAC_SETTINGS_REG, val);
+	mxl_fail(ret);
+
+	return ret;
+}
diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf-phy.h b/drivers/media/usb/dvb-usb-v2/mxl111sf-phy.h
new file mode 100644
index 0000000..0a61e8a
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/mxl111sf-phy.h
@@ -0,0 +1,43 @@
+/*
+ *  mxl111sf-phy.h - driver for the MaxLinear MXL111SF
+ *
+ *  Copyright (C) 2010-2014 Michael Krufky <mkrufky@linuxtv.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ */
+
+#ifndef _DVB_USB_MXL111SF_PHY_H_
+#define _DVB_USB_MXL111SF_PHY_H_
+
+#include "mxl111sf.h"
+
+int mxl1x1sf_soft_reset(struct mxl111sf_state *state);
+int mxl1x1sf_set_device_mode(struct mxl111sf_state *state, int mode);
+int mxl1x1sf_top_master_ctrl(struct mxl111sf_state *state, int onoff);
+int mxl111sf_disable_656_port(struct mxl111sf_state *state);
+int mxl111sf_init_tuner_demod(struct mxl111sf_state *state);
+int mxl111sf_enable_usb_output(struct mxl111sf_state *state);
+int mxl111sf_config_mpeg_in(struct mxl111sf_state *state,
+			    unsigned int parallel_serial,
+			    unsigned int msb_lsb_1st,
+			    unsigned int clock_phase,
+			    unsigned int mpeg_valid_pol,
+			    unsigned int mpeg_sync_pol);
+int mxl111sf_config_i2s(struct mxl111sf_state *state,
+			u8 msb_start_pos, u8 data_width);
+int mxl111sf_init_i2s_port(struct mxl111sf_state *state, u8 sample_size);
+int mxl111sf_disable_i2s_port(struct mxl111sf_state *state);
+int mxl111sf_config_spi(struct mxl111sf_state *state, int onoff);
+int mxl111sf_idac_config(struct mxl111sf_state *state,
+			 u8 control_mode, u8 current_setting,
+			 u8 current_value, u8 hysteresis_value);
+
+#endif /* _DVB_USB_MXL111SF_PHY_H_ */
diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf-reg.h b/drivers/media/usb/dvb-usb-v2/mxl111sf-reg.h
new file mode 100644
index 0000000..ad3f806
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/mxl111sf-reg.h
@@ -0,0 +1,169 @@
+/*
+ *  mxl111sf-reg.h - driver for the MaxLinear MXL111SF
+ *
+ *  Copyright (C) 2010-2014 Michael Krufky <mkrufky@linuxtv.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ */
+
+#ifndef _DVB_USB_MXL111SF_REG_H_
+#define _DVB_USB_MXL111SF_REG_H_
+
+#define CHIP_ID_REG                  0xFC
+#define TOP_CHIP_REV_ID_REG          0xFA
+
+#define V6_SNR_RB_LSB_REG            0x27
+#define V6_SNR_RB_MSB_REG            0x28
+
+#define V6_N_ACCUMULATE_REG          0x11
+#define V6_RS_AVG_ERRORS_LSB_REG     0x2C
+#define V6_RS_AVG_ERRORS_MSB_REG     0x2D
+
+#define V6_IRQ_STATUS_REG            0x24
+#define  IRQ_MASK_FEC_LOCK       0x10
+
+#define V6_SYNC_LOCK_REG             0x28
+#define SYNC_LOCK_MASK           0x10
+
+#define V6_RS_LOCK_DET_REG           0x28
+#define  RS_LOCK_DET_MASK        0x08
+
+#define V6_INITACQ_NODETECT_REG    0x20
+#define V6_FORCE_NFFT_CPSIZE_REG   0x20
+
+#define V6_CODE_RATE_TPS_REG       0x29
+#define V6_CODE_RATE_TPS_MASK      0x07
+
+
+#define V6_CP_LOCK_DET_REG        0x28
+#define V6_CP_LOCK_DET_MASK       0x04
+
+#define V6_TPS_HIERACHY_REG        0x29
+#define V6_TPS_HIERARCHY_INFO_MASK  0x40
+
+#define V6_MODORDER_TPS_REG        0x2A
+#define V6_PARAM_CONSTELLATION_MASK   0x30
+
+#define V6_MODE_TPS_REG            0x2A
+#define V6_PARAM_FFT_MODE_MASK        0x0C
+
+
+#define V6_CP_TPS_REG             0x29
+#define V6_PARAM_GI_MASK              0x30
+
+#define V6_TPS_LOCK_REG           0x2A
+#define V6_PARAM_TPS_LOCK_MASK        0x40
+
+#define V6_FEC_PER_COUNT_REG      0x2E
+#define V6_FEC_PER_SCALE_REG      0x2B
+#define V6_FEC_PER_SCALE_MASK        0x03
+#define V6_FEC_PER_CLR_REG        0x20
+#define V6_FEC_PER_CLR_MASK          0x01
+
+#define V6_PIN_MUX_MODE_REG       0x1B
+#define V6_ENABLE_PIN_MUX            0x1E
+
+#define V6_I2S_NUM_SAMPLES_REG    0x16
+
+#define V6_MPEG_IN_CLK_INV_REG    0x17
+#define V6_MPEG_IN_CTRL_REG       0x18
+
+#define V6_INVERTED_CLK_PHASE       0x20
+#define V6_MPEG_IN_DATA_PARALLEL    0x01
+#define V6_MPEG_IN_DATA_SERIAL      0x02
+
+#define V6_INVERTED_MPEG_SYNC       0x04
+#define V6_INVERTED_MPEG_VALID      0x08
+
+#define TSIF_INPUT_PARALLEL         0
+#define TSIF_INPUT_SERIAL           1
+#define TSIF_NORMAL                 0
+
+#define V6_MPEG_INOUT_BIT_ORDER_CTRL_REG  0x19
+#define V6_MPEG_SER_MSB_FIRST                0x80
+#define MPEG_SER_MSB_FIRST_ENABLED        0x01
+
+#define V6_656_I2S_BUFF_STATUS_REG   0x2F
+#define V6_656_OVERFLOW_MASK_BIT         0x08
+#define V6_I2S_OVERFLOW_MASK_BIT         0x01
+
+#define V6_I2S_STREAM_START_BIT_REG  0x14
+#define V6_I2S_STREAM_END_BIT_REG    0x15
+#define I2S_RIGHT_JUSTIFIED     0
+#define I2S_LEFT_JUSTIFIED      1
+#define I2S_DATA_FORMAT         2
+
+#define V6_TUNER_LOOP_THRU_CONTROL_REG  0x09
+#define V6_ENABLE_LOOP_THRU               0x01
+
+#define TOTAL_NUM_IF_OUTPUT_FREQ       16
+
+#define TUNER_NORMAL_IF_SPECTRUM       0x0
+#define TUNER_INVERT_IF_SPECTRUM       0x10
+
+#define V6_TUNER_IF_SEL_REG              0x06
+#define V6_TUNER_IF_FCW_REG              0x3C
+#define V6_TUNER_IF_FCW_BYP_REG          0x3D
+#define V6_RF_LOCK_STATUS_REG            0x23
+
+#define NUM_DIG_TV_CHANNEL     1000
+
+#define V6_DIG_CLK_FREQ_SEL_REG  0x07
+#define V6_REF_SYNTH_INT_REG     0x5C
+#define V6_REF_SYNTH_REMAIN_REG  0x58
+#define V6_DIG_RFREFSELECT_REG   0x32
+#define V6_XTAL_CLK_OUT_GAIN_REG   0x31
+#define V6_TUNER_LOOP_THRU_CTRL_REG      0x09
+#define V6_DIG_XTAL_ENABLE_REG  0x06
+#define V6_DIG_XTAL_BIAS_REG  0x66
+#define V6_XTAL_CAP_REG    0x08
+
+#define V6_GPO_CTRL_REG     0x18
+#define MXL_GPO_0           0x00
+#define MXL_GPO_1           0x01
+#define V6_GPO_0_MASK       0x10
+#define V6_GPO_1_MASK       0x20
+
+#define V6_111SF_GPO_CTRL_REG     0x19
+#define MXL_111SF_GPO_1               0x00
+#define MXL_111SF_GPO_2               0x01
+#define MXL_111SF_GPO_3               0x02
+#define MXL_111SF_GPO_4               0x03
+#define MXL_111SF_GPO_5               0x04
+#define MXL_111SF_GPO_6               0x05
+#define MXL_111SF_GPO_7               0x06
+
+#define MXL_111SF_GPO_0_MASK          0x01
+#define MXL_111SF_GPO_1_MASK          0x02
+#define MXL_111SF_GPO_2_MASK          0x04
+#define MXL_111SF_GPO_3_MASK          0x08
+#define MXL_111SF_GPO_4_MASK          0x10
+#define MXL_111SF_GPO_5_MASK          0x20
+#define MXL_111SF_GPO_6_MASK          0x40
+
+#define V6_ATSC_CONFIG_REG  0x0A
+
+#define MXL_MODE_REG    0x03
+#define START_TUNE_REG  0x1C
+
+#define V6_IDAC_HYSTERESIS_REG    0x0B
+#define V6_IDAC_SETTINGS_REG      0x0C
+#define IDAC_MANUAL_CONTROL             1
+#define IDAC_CURRENT_SINKING_ENABLE     1
+#define IDAC_MANUAL_CONTROL_BIT_MASK      0x80
+#define IDAC_CURRENT_SINKING_BIT_MASK     0x40
+
+#define V8_SPI_MODE_REG  0xE9
+
+#define V6_DIG_RF_PWR_LSB_REG  0x46
+#define V6_DIG_RF_PWR_MSB_REG  0x47
+
+#endif /* _DVB_USB_MXL111SF_REG_H_ */
diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf-tuner.c b/drivers/media/usb/dvb-usb-v2/mxl111sf-tuner.c
new file mode 100644
index 0000000..92b3b92
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/mxl111sf-tuner.c
@@ -0,0 +1,512 @@
+/*
+ *  mxl111sf-tuner.c - driver for the MaxLinear MXL111SF CMOS tuner
+ *
+ *  Copyright (C) 2010-2014 Michael Krufky <mkrufky@linuxtv.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ */
+
+#include "mxl111sf-tuner.h"
+#include "mxl111sf-phy.h"
+#include "mxl111sf-reg.h"
+
+/* debug */
+static int mxl111sf_tuner_debug;
+module_param_named(debug, mxl111sf_tuner_debug, int, 0644);
+MODULE_PARM_DESC(debug, "set debugging level (1=info (or-able)).");
+
+#define mxl_dbg(fmt, arg...) \
+	if (mxl111sf_tuner_debug) \
+		mxl_printk(KERN_DEBUG, fmt, ##arg)
+
+/* ------------------------------------------------------------------------ */
+
+struct mxl111sf_tuner_state {
+	struct mxl111sf_state *mxl_state;
+
+	const struct mxl111sf_tuner_config *cfg;
+
+	enum mxl_if_freq if_freq;
+
+	u32 frequency;
+	u32 bandwidth;
+};
+
+static int mxl111sf_tuner_read_reg(struct mxl111sf_tuner_state *state,
+				   u8 addr, u8 *data)
+{
+	return (state->cfg->read_reg) ?
+		state->cfg->read_reg(state->mxl_state, addr, data) :
+		-EINVAL;
+}
+
+static int mxl111sf_tuner_write_reg(struct mxl111sf_tuner_state *state,
+				    u8 addr, u8 data)
+{
+	return (state->cfg->write_reg) ?
+		state->cfg->write_reg(state->mxl_state, addr, data) :
+		-EINVAL;
+}
+
+static int mxl111sf_tuner_program_regs(struct mxl111sf_tuner_state *state,
+			       struct mxl111sf_reg_ctrl_info *ctrl_reg_info)
+{
+	return (state->cfg->program_regs) ?
+		state->cfg->program_regs(state->mxl_state, ctrl_reg_info) :
+		-EINVAL;
+}
+
+static int mxl1x1sf_tuner_top_master_ctrl(struct mxl111sf_tuner_state *state,
+					  int onoff)
+{
+	return (state->cfg->top_master_ctrl) ?
+		state->cfg->top_master_ctrl(state->mxl_state, onoff) :
+		-EINVAL;
+}
+
+/* ------------------------------------------------------------------------ */
+
+static struct mxl111sf_reg_ctrl_info mxl_phy_tune_rf[] = {
+	{0x1d, 0x7f, 0x00}, /* channel bandwidth section 1/2/3,
+			       DIG_MODEINDEX, _A, _CSF, */
+	{0x1e, 0xff, 0x00}, /* channel frequency (lo and fractional) */
+	{0x1f, 0xff, 0x00}, /* channel frequency (hi for integer portion) */
+	{0,    0,    0}
+};
+
+/* ------------------------------------------------------------------------ */
+
+static struct mxl111sf_reg_ctrl_info *mxl111sf_calc_phy_tune_regs(u32 freq,
+								  u8 bw)
+{
+	u8 filt_bw;
+
+	/* set channel bandwidth */
+	switch (bw) {
+	case 0: /* ATSC */
+		filt_bw = 25;
+		break;
+	case 1: /* QAM */
+		filt_bw = 69;
+		break;
+	case 6:
+		filt_bw = 21;
+		break;
+	case 7:
+		filt_bw = 42;
+		break;
+	case 8:
+		filt_bw = 63;
+		break;
+	default:
+		pr_err("%s: invalid bandwidth setting!", __func__);
+		return NULL;
+	}
+
+	/* calculate RF channel */
+	freq /= 1000000;
+
+	freq *= 64;
+#if 0
+	/* do round */
+	freq += 0.5;
+#endif
+	/* set bandwidth */
+	mxl_phy_tune_rf[0].data = filt_bw;
+
+	/* set RF */
+	mxl_phy_tune_rf[1].data = (freq & 0xff);
+	mxl_phy_tune_rf[2].data = (freq >> 8) & 0xff;
+
+	/* start tune */
+	return mxl_phy_tune_rf;
+}
+
+static int mxl1x1sf_tuner_set_if_output_freq(struct mxl111sf_tuner_state *state)
+{
+	int ret;
+	u8 ctrl;
+#if 0
+	u16 iffcw;
+	u32 if_freq;
+#endif
+	mxl_dbg("(IF polarity = %d, IF freq = 0x%02x)",
+		state->cfg->invert_spectrum, state->cfg->if_freq);
+
+	/* set IF polarity */
+	ctrl = state->cfg->invert_spectrum;
+
+	ctrl |= state->cfg->if_freq;
+
+	ret = mxl111sf_tuner_write_reg(state, V6_TUNER_IF_SEL_REG, ctrl);
+	if (mxl_fail(ret))
+		goto fail;
+
+#if 0
+	if_freq /= 1000000;
+
+	/* do round */
+	if_freq += 0.5;
+
+	if (MXL_IF_LO == state->cfg->if_freq) {
+		ctrl = 0x08;
+		iffcw = (u16)(if_freq / (108 * 4096));
+	} else if (MXL_IF_HI == state->cfg->if_freq) {
+		ctrl = 0x08;
+		iffcw = (u16)(if_freq / (216 * 4096));
+	} else {
+		ctrl = 0;
+		iffcw = 0;
+	}
+
+	ctrl |= (iffcw >> 8);
+#endif
+	ret = mxl111sf_tuner_read_reg(state, V6_TUNER_IF_FCW_BYP_REG, &ctrl);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ctrl &= 0xf0;
+	ctrl |= 0x90;
+
+	ret = mxl111sf_tuner_write_reg(state, V6_TUNER_IF_FCW_BYP_REG, ctrl);
+	if (mxl_fail(ret))
+		goto fail;
+
+#if 0
+	ctrl = iffcw & 0x00ff;
+#endif
+	ret = mxl111sf_tuner_write_reg(state, V6_TUNER_IF_FCW_REG, ctrl);
+	if (mxl_fail(ret))
+		goto fail;
+
+	state->if_freq = state->cfg->if_freq;
+fail:
+	return ret;
+}
+
+static int mxl1x1sf_tune_rf(struct dvb_frontend *fe, u32 freq, u8 bw)
+{
+	struct mxl111sf_tuner_state *state = fe->tuner_priv;
+	static struct mxl111sf_reg_ctrl_info *reg_ctrl_array;
+	int ret;
+	u8 mxl_mode;
+
+	mxl_dbg("(freq = %d, bw = 0x%x)", freq, bw);
+
+	/* stop tune */
+	ret = mxl111sf_tuner_write_reg(state, START_TUNE_REG, 0);
+	if (mxl_fail(ret))
+		goto fail;
+
+	/* check device mode */
+	ret = mxl111sf_tuner_read_reg(state, MXL_MODE_REG, &mxl_mode);
+	if (mxl_fail(ret))
+		goto fail;
+
+	/* Fill out registers for channel tune */
+	reg_ctrl_array = mxl111sf_calc_phy_tune_regs(freq, bw);
+	if (!reg_ctrl_array)
+		return -EINVAL;
+
+	ret = mxl111sf_tuner_program_regs(state, reg_ctrl_array);
+	if (mxl_fail(ret))
+		goto fail;
+
+	if ((mxl_mode & MXL_DEV_MODE_MASK) == MXL_TUNER_MODE) {
+		/* IF tuner mode only */
+		mxl1x1sf_tuner_top_master_ctrl(state, 0);
+		mxl1x1sf_tuner_top_master_ctrl(state, 1);
+		mxl1x1sf_tuner_set_if_output_freq(state);
+	}
+
+	ret = mxl111sf_tuner_write_reg(state, START_TUNE_REG, 1);
+	if (mxl_fail(ret))
+		goto fail;
+
+	if (state->cfg->ant_hunt)
+		state->cfg->ant_hunt(fe);
+fail:
+	return ret;
+}
+
+static int mxl1x1sf_tuner_get_lock_status(struct mxl111sf_tuner_state *state,
+					  int *rf_synth_lock,
+					  int *ref_synth_lock)
+{
+	int ret;
+	u8 data;
+
+	*rf_synth_lock = 0;
+	*ref_synth_lock = 0;
+
+	ret = mxl111sf_tuner_read_reg(state, V6_RF_LOCK_STATUS_REG, &data);
+	if (mxl_fail(ret))
+		goto fail;
+
+	*ref_synth_lock = ((data & 0x03) == 0x03) ? 1 : 0;
+	*rf_synth_lock  = ((data & 0x0c) == 0x0c) ? 1 : 0;
+fail:
+	return ret;
+}
+
+#if 0
+static int mxl1x1sf_tuner_loop_thru_ctrl(struct mxl111sf_tuner_state *state,
+					 int onoff)
+{
+	return mxl111sf_tuner_write_reg(state, V6_TUNER_LOOP_THRU_CTRL_REG,
+					onoff ? 1 : 0);
+}
+#endif
+
+/* ------------------------------------------------------------------------ */
+
+static int mxl111sf_tuner_set_params(struct dvb_frontend *fe)
+{
+	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+	u32 delsys  = c->delivery_system;
+	struct mxl111sf_tuner_state *state = fe->tuner_priv;
+	int ret;
+	u8 bw;
+
+	mxl_dbg("()");
+
+	switch (delsys) {
+	case SYS_ATSC:
+	case SYS_ATSCMH:
+		bw = 0; /* ATSC */
+		break;
+	case SYS_DVBC_ANNEX_B:
+		bw = 1; /* US CABLE */
+		break;
+	case SYS_DVBT:
+		switch (c->bandwidth_hz) {
+		case 6000000:
+			bw = 6;
+			break;
+		case 7000000:
+			bw = 7;
+			break;
+		case 8000000:
+			bw = 8;
+			break;
+		default:
+			pr_err("%s: bandwidth not set!", __func__);
+			return -EINVAL;
+		}
+		break;
+	default:
+		pr_err("%s: modulation type not supported!", __func__);
+		return -EINVAL;
+	}
+	ret = mxl1x1sf_tune_rf(fe, c->frequency, bw);
+	if (mxl_fail(ret))
+		goto fail;
+
+	state->frequency = c->frequency;
+	state->bandwidth = c->bandwidth_hz;
+fail:
+	return ret;
+}
+
+/* ------------------------------------------------------------------------ */
+
+#if 0
+static int mxl111sf_tuner_init(struct dvb_frontend *fe)
+{
+	struct mxl111sf_tuner_state *state = fe->tuner_priv;
+	int ret;
+
+	/* wake from standby handled by usb driver */
+
+	return ret;
+}
+
+static int mxl111sf_tuner_sleep(struct dvb_frontend *fe)
+{
+	struct mxl111sf_tuner_state *state = fe->tuner_priv;
+	int ret;
+
+	/* enter standby mode handled by usb driver */
+
+	return ret;
+}
+#endif
+
+/* ------------------------------------------------------------------------ */
+
+static int mxl111sf_tuner_get_status(struct dvb_frontend *fe, u32 *status)
+{
+	struct mxl111sf_tuner_state *state = fe->tuner_priv;
+	int rf_locked, ref_locked, ret;
+
+	*status = 0;
+
+	ret = mxl1x1sf_tuner_get_lock_status(state, &rf_locked, &ref_locked);
+	if (mxl_fail(ret))
+		goto fail;
+	mxl_info("%s%s", rf_locked ? "rf locked " : "",
+		 ref_locked ? "ref locked" : "");
+
+	if ((rf_locked) || (ref_locked))
+		*status |= TUNER_STATUS_LOCKED;
+fail:
+	return ret;
+}
+
+static int mxl111sf_get_rf_strength(struct dvb_frontend *fe, u16 *strength)
+{
+	struct mxl111sf_tuner_state *state = fe->tuner_priv;
+	u8 val1, val2;
+	int ret;
+
+	*strength = 0;
+
+	ret = mxl111sf_tuner_write_reg(state, 0x00, 0x02);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_tuner_read_reg(state, V6_DIG_RF_PWR_LSB_REG, &val1);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_tuner_read_reg(state, V6_DIG_RF_PWR_MSB_REG, &val2);
+	if (mxl_fail(ret))
+		goto fail;
+
+	*strength = val1 | ((val2 & 0x07) << 8);
+fail:
+	ret = mxl111sf_tuner_write_reg(state, 0x00, 0x00);
+	mxl_fail(ret);
+
+	return ret;
+}
+
+/* ------------------------------------------------------------------------ */
+
+static int mxl111sf_tuner_get_frequency(struct dvb_frontend *fe, u32 *frequency)
+{
+	struct mxl111sf_tuner_state *state = fe->tuner_priv;
+	*frequency = state->frequency;
+	return 0;
+}
+
+static int mxl111sf_tuner_get_bandwidth(struct dvb_frontend *fe, u32 *bandwidth)
+{
+	struct mxl111sf_tuner_state *state = fe->tuner_priv;
+	*bandwidth = state->bandwidth;
+	return 0;
+}
+
+static int mxl111sf_tuner_get_if_frequency(struct dvb_frontend *fe,
+					   u32 *frequency)
+{
+	struct mxl111sf_tuner_state *state = fe->tuner_priv;
+
+	*frequency = 0;
+
+	switch (state->if_freq) {
+	case MXL_IF_4_0:   /* 4.0   MHz */
+		*frequency = 4000000;
+		break;
+	case MXL_IF_4_5:   /* 4.5   MHz */
+		*frequency = 4500000;
+		break;
+	case MXL_IF_4_57:  /* 4.57  MHz */
+		*frequency = 4570000;
+		break;
+	case MXL_IF_5_0:   /* 5.0   MHz */
+		*frequency = 5000000;
+		break;
+	case MXL_IF_5_38:  /* 5.38  MHz */
+		*frequency = 5380000;
+		break;
+	case MXL_IF_6_0:   /* 6.0   MHz */
+		*frequency = 6000000;
+		break;
+	case MXL_IF_6_28:  /* 6.28  MHz */
+		*frequency = 6280000;
+		break;
+	case MXL_IF_7_2:   /* 7.2   MHz */
+		*frequency = 7200000;
+		break;
+	case MXL_IF_35_25: /* 35.25 MHz */
+		*frequency = 35250000;
+		break;
+	case MXL_IF_36:    /* 36    MHz */
+		*frequency = 36000000;
+		break;
+	case MXL_IF_36_15: /* 36.15 MHz */
+		*frequency = 36150000;
+		break;
+	case MXL_IF_44:    /* 44    MHz */
+		*frequency = 44000000;
+		break;
+	}
+	return 0;
+}
+
+static void mxl111sf_tuner_release(struct dvb_frontend *fe)
+{
+	struct mxl111sf_tuner_state *state = fe->tuner_priv;
+	mxl_dbg("()");
+	kfree(state);
+	fe->tuner_priv = NULL;
+}
+
+/* ------------------------------------------------------------------------- */
+
+static const struct dvb_tuner_ops mxl111sf_tuner_tuner_ops = {
+	.info = {
+		.name = "MaxLinear MxL111SF",
+#if 0
+		.frequency_min_hz  = ,
+		.frequency_max_hz  = ,
+		.frequency_step_hz = ,
+#endif
+	},
+#if 0
+	.init              = mxl111sf_tuner_init,
+	.sleep             = mxl111sf_tuner_sleep,
+#endif
+	.set_params        = mxl111sf_tuner_set_params,
+	.get_status        = mxl111sf_tuner_get_status,
+	.get_rf_strength   = mxl111sf_get_rf_strength,
+	.get_frequency     = mxl111sf_tuner_get_frequency,
+	.get_bandwidth     = mxl111sf_tuner_get_bandwidth,
+	.get_if_frequency  = mxl111sf_tuner_get_if_frequency,
+	.release           = mxl111sf_tuner_release,
+};
+
+struct dvb_frontend *mxl111sf_tuner_attach(struct dvb_frontend *fe,
+				struct mxl111sf_state *mxl_state,
+				const struct mxl111sf_tuner_config *cfg)
+{
+	struct mxl111sf_tuner_state *state = NULL;
+
+	mxl_dbg("()");
+
+	state = kzalloc(sizeof(struct mxl111sf_tuner_state), GFP_KERNEL);
+	if (state == NULL)
+		return NULL;
+
+	state->mxl_state = mxl_state;
+	state->cfg = cfg;
+
+	memcpy(&fe->ops.tuner_ops, &mxl111sf_tuner_tuner_ops,
+	       sizeof(struct dvb_tuner_ops));
+
+	fe->tuner_priv = state;
+	return fe;
+}
+EXPORT_SYMBOL_GPL(mxl111sf_tuner_attach);
+
+MODULE_DESCRIPTION("MaxLinear MxL111SF CMOS tuner driver");
+MODULE_AUTHOR("Michael Krufky <mkrufky@linuxtv.org>");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("0.1");
diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf-tuner.h b/drivers/media/usb/dvb-usb-v2/mxl111sf-tuner.h
new file mode 100644
index 0000000..87c1b16
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/mxl111sf-tuner.h
@@ -0,0 +1,74 @@
+/*
+ *  mxl111sf-tuner.h - driver for the MaxLinear MXL111SF CMOS tuner
+ *
+ *  Copyright (C) 2010-2014 Michael Krufky <mkrufky@linuxtv.org>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ */
+
+#ifndef __MXL111SF_TUNER_H__
+#define __MXL111SF_TUNER_H__
+
+#include <media/dvb_frontend.h>
+#include "mxl111sf.h"
+
+enum mxl_if_freq {
+#if 0
+	MXL_IF_LO    = 0x00, /* other IF < 9MHz */
+#endif
+	MXL_IF_4_0   = 0x01, /* 4.0   MHz */
+	MXL_IF_4_5   = 0x02, /* 4.5   MHz */
+	MXL_IF_4_57  = 0x03, /* 4.57  MHz */
+	MXL_IF_5_0   = 0x04, /* 5.0   MHz */
+	MXL_IF_5_38  = 0x05, /* 5.38  MHz */
+	MXL_IF_6_0   = 0x06, /* 6.0   MHz */
+	MXL_IF_6_28  = 0x07, /* 6.28  MHz */
+	MXL_IF_7_2   = 0x08, /* 7.2   MHz */
+	MXL_IF_35_25 = 0x09, /* 35.25 MHz */
+	MXL_IF_36    = 0x0a, /* 36    MHz */
+	MXL_IF_36_15 = 0x0b, /* 36.15 MHz */
+	MXL_IF_44    = 0x0c, /* 44    MHz */
+#if 0
+	MXL_IF_HI    = 0x0f, /* other IF > 35 MHz and < 45 MHz */
+#endif
+};
+
+struct mxl111sf_tuner_config {
+	enum mxl_if_freq if_freq;
+	unsigned int invert_spectrum:1;
+
+	int (*read_reg)(struct mxl111sf_state *state, u8 addr, u8 *data);
+	int (*write_reg)(struct mxl111sf_state *state, u8 addr, u8 data);
+	int (*program_regs)(struct mxl111sf_state *state,
+			    struct mxl111sf_reg_ctrl_info *ctrl_reg_info);
+	int (*top_master_ctrl)(struct mxl111sf_state *state, int onoff);
+	int (*ant_hunt)(struct dvb_frontend *fe);
+};
+
+/* ------------------------------------------------------------------------ */
+
+#if IS_ENABLED(CONFIG_DVB_USB_MXL111SF)
+extern
+struct dvb_frontend *mxl111sf_tuner_attach(struct dvb_frontend *fe,
+				struct mxl111sf_state *mxl_state,
+				const struct mxl111sf_tuner_config *cfg);
+#else
+static inline
+struct dvb_frontend *mxl111sf_tuner_attach(struct dvb_frontend *fe,
+				struct mxl111sf_state *mxl_state,
+				const struct mxl111sf_tuner_config *cfg)
+{
+	printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__);
+	return NULL;
+}
+#endif
+
+#endif /* __MXL111SF_TUNER_H__ */
diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf.c b/drivers/media/usb/dvb-usb-v2/mxl111sf.c
new file mode 100644
index 0000000..4713ba6
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/mxl111sf.c
@@ -0,0 +1,1455 @@
+/*
+ * Copyright (C) 2010-2014 Michael Krufky (mkrufky@linuxtv.org)
+ *
+ *   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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+
+#include <linux/vmalloc.h>
+#include <linux/i2c.h>
+#include <media/tuner.h>
+
+#include "mxl111sf.h"
+#include "mxl111sf-reg.h"
+#include "mxl111sf-phy.h"
+#include "mxl111sf-i2c.h"
+#include "mxl111sf-gpio.h"
+
+#include "mxl111sf-demod.h"
+#include "mxl111sf-tuner.h"
+
+#include "lgdt3305.h"
+#include "lg2160.h"
+
+int dvb_usb_mxl111sf_debug;
+module_param_named(debug, dvb_usb_mxl111sf_debug, int, 0644);
+MODULE_PARM_DESC(debug, "set debugging level (1=info, 2=xfer, 4=i2c, 8=reg, 16=adv (or-able)).");
+
+static int dvb_usb_mxl111sf_isoc;
+module_param_named(isoc, dvb_usb_mxl111sf_isoc, int, 0644);
+MODULE_PARM_DESC(isoc, "enable usb isoc xfer (0=bulk, 1=isoc).");
+
+static int dvb_usb_mxl111sf_spi;
+module_param_named(spi, dvb_usb_mxl111sf_spi, int, 0644);
+MODULE_PARM_DESC(spi, "use spi rather than tp for data xfer (0=tp, 1=spi).");
+
+#define ANT_PATH_AUTO 0
+#define ANT_PATH_EXTERNAL 1
+#define ANT_PATH_INTERNAL 2
+
+static int dvb_usb_mxl111sf_rfswitch =
+#if 0
+		ANT_PATH_AUTO;
+#else
+		ANT_PATH_EXTERNAL;
+#endif
+
+module_param_named(rfswitch, dvb_usb_mxl111sf_rfswitch, int, 0644);
+MODULE_PARM_DESC(rfswitch, "force rf switch position (0=auto, 1=ext, 2=int).");
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+int mxl111sf_ctrl_msg(struct mxl111sf_state *state,
+		      u8 cmd, u8 *wbuf, int wlen, u8 *rbuf, int rlen)
+{
+	struct dvb_usb_device *d = state->d;
+	int wo = (rbuf == NULL || rlen == 0); /* write-only */
+	int ret;
+
+	if (1 + wlen > MXL_MAX_XFER_SIZE) {
+		pr_warn("%s: len=%d is too big!\n", __func__, wlen);
+		return -EOPNOTSUPP;
+	}
+
+	pr_debug("%s(wlen = %d, rlen = %d)\n", __func__, wlen, rlen);
+
+	mutex_lock(&state->msg_lock);
+	memset(state->sndbuf, 0, 1+wlen);
+	memset(state->rcvbuf, 0, rlen);
+
+	state->sndbuf[0] = cmd;
+	memcpy(&state->sndbuf[1], wbuf, wlen);
+
+	ret = (wo) ? dvb_usbv2_generic_write(d, state->sndbuf, 1+wlen) :
+		dvb_usbv2_generic_rw(d, state->sndbuf, 1+wlen, state->rcvbuf,
+				     rlen);
+
+	if (rbuf)
+		memcpy(rbuf, state->rcvbuf, rlen);
+
+	mutex_unlock(&state->msg_lock);
+
+	mxl_fail(ret);
+
+	return ret;
+}
+
+/* ------------------------------------------------------------------------ */
+
+#define MXL_CMD_REG_READ	0xaa
+#define MXL_CMD_REG_WRITE	0x55
+
+int mxl111sf_read_reg(struct mxl111sf_state *state, u8 addr, u8 *data)
+{
+	u8 buf[2];
+	int ret;
+
+	ret = mxl111sf_ctrl_msg(state, MXL_CMD_REG_READ, &addr, 1, buf, 2);
+	if (mxl_fail(ret)) {
+		mxl_debug("error reading reg: 0x%02x", addr);
+		goto fail;
+	}
+
+	if (buf[0] == addr)
+		*data = buf[1];
+	else {
+		pr_err("invalid response reading reg: 0x%02x != 0x%02x, 0x%02x",
+		    addr, buf[0], buf[1]);
+		ret = -EINVAL;
+	}
+
+	pr_debug("R: (0x%02x, 0x%02x)\n", addr, buf[1]);
+fail:
+	return ret;
+}
+
+int mxl111sf_write_reg(struct mxl111sf_state *state, u8 addr, u8 data)
+{
+	u8 buf[] = { addr, data };
+	int ret;
+
+	pr_debug("W: (0x%02x, 0x%02x)\n", addr, data);
+
+	ret = mxl111sf_ctrl_msg(state, MXL_CMD_REG_WRITE, buf, 2, NULL, 0);
+	if (mxl_fail(ret))
+		pr_err("error writing reg: 0x%02x, val: 0x%02x", addr, data);
+	return ret;
+}
+
+/* ------------------------------------------------------------------------ */
+
+int mxl111sf_write_reg_mask(struct mxl111sf_state *state,
+				   u8 addr, u8 mask, u8 data)
+{
+	int ret;
+	u8 val = 0;
+
+	if (mask != 0xff) {
+		ret = mxl111sf_read_reg(state, addr, &val);
+#if 1
+		/* dont know why this usually errors out on the first try */
+		if (mxl_fail(ret))
+			pr_err("error writing addr: 0x%02x, mask: 0x%02x, data: 0x%02x, retrying...",
+			       addr, mask, data);
+
+		ret = mxl111sf_read_reg(state, addr, &val);
+#endif
+		if (mxl_fail(ret))
+			goto fail;
+	}
+	val &= ~mask;
+	val |= data;
+
+	ret = mxl111sf_write_reg(state, addr, val);
+	mxl_fail(ret);
+fail:
+	return ret;
+}
+
+/* ------------------------------------------------------------------------ */
+
+int mxl111sf_ctrl_program_regs(struct mxl111sf_state *state,
+			       struct mxl111sf_reg_ctrl_info *ctrl_reg_info)
+{
+	int i, ret = 0;
+
+	for (i = 0;  ctrl_reg_info[i].addr |
+		     ctrl_reg_info[i].mask |
+		     ctrl_reg_info[i].data;  i++) {
+
+		ret = mxl111sf_write_reg_mask(state,
+					      ctrl_reg_info[i].addr,
+					      ctrl_reg_info[i].mask,
+					      ctrl_reg_info[i].data);
+		if (mxl_fail(ret)) {
+			pr_err("failed on reg #%d (0x%02x)", i,
+			    ctrl_reg_info[i].addr);
+			break;
+		}
+	}
+	return ret;
+}
+
+/* ------------------------------------------------------------------------ */
+
+static int mxl1x1sf_get_chip_info(struct mxl111sf_state *state)
+{
+	int ret;
+	u8 id, ver;
+	char *mxl_chip, *mxl_rev;
+
+	if ((state->chip_id) && (state->chip_ver))
+		return 0;
+
+	ret = mxl111sf_read_reg(state, CHIP_ID_REG, &id);
+	if (mxl_fail(ret))
+		goto fail;
+	state->chip_id = id;
+
+	ret = mxl111sf_read_reg(state, TOP_CHIP_REV_ID_REG, &ver);
+	if (mxl_fail(ret))
+		goto fail;
+	state->chip_ver = ver;
+
+	switch (id) {
+	case 0x61:
+		mxl_chip = "MxL101SF";
+		break;
+	case 0x63:
+		mxl_chip = "MxL111SF";
+		break;
+	default:
+		mxl_chip = "UNKNOWN MxL1X1";
+		break;
+	}
+	switch (ver) {
+	case 0x36:
+		state->chip_rev = MXL111SF_V6;
+		mxl_rev = "v6";
+		break;
+	case 0x08:
+		state->chip_rev = MXL111SF_V8_100;
+		mxl_rev = "v8_100";
+		break;
+	case 0x18:
+		state->chip_rev = MXL111SF_V8_200;
+		mxl_rev = "v8_200";
+		break;
+	default:
+		state->chip_rev = 0;
+		mxl_rev = "UNKNOWN REVISION";
+		break;
+	}
+	pr_info("%s detected, %s (0x%x)", mxl_chip, mxl_rev, ver);
+fail:
+	return ret;
+}
+
+#define get_chip_info(state)						\
+({									\
+	int ___ret;							\
+	___ret = mxl1x1sf_get_chip_info(state);				\
+	if (mxl_fail(___ret)) {						\
+		mxl_debug("failed to get chip info"			\
+			  " on first probe attempt");			\
+		___ret = mxl1x1sf_get_chip_info(state);			\
+		if (mxl_fail(___ret))					\
+			pr_err("failed to get chip info during probe");	\
+		else							\
+			mxl_debug("probe needed a retry "		\
+				  "in order to succeed.");		\
+	}								\
+	___ret;								\
+})
+
+/* ------------------------------------------------------------------------ */
+#if 0
+static int mxl111sf_power_ctrl(struct dvb_usb_device *d, int onoff)
+{
+	/* power control depends on which adapter is being woken:
+	 * save this for init, instead, via mxl111sf_adap_fe_init */
+	return 0;
+}
+#endif
+
+static int mxl111sf_adap_fe_init(struct dvb_frontend *fe)
+{
+	struct dvb_usb_device *d = fe_to_d(fe);
+	struct mxl111sf_state *state = fe_to_priv(fe);
+	struct mxl111sf_adap_state *adap_state = &state->adap_state[fe->id];
+	int err;
+
+	/* exit if we didn't initialize the driver yet */
+	if (!state->chip_id) {
+		mxl_debug("driver not yet initialized, exit.");
+		goto fail;
+	}
+
+	pr_debug("%s()\n", __func__);
+
+	mutex_lock(&state->fe_lock);
+
+	state->alt_mode = adap_state->alt_mode;
+
+	if (usb_set_interface(d->udev, 0, state->alt_mode) < 0)
+		pr_err("set interface failed");
+
+	err = mxl1x1sf_soft_reset(state);
+	mxl_fail(err);
+	err = mxl111sf_init_tuner_demod(state);
+	mxl_fail(err);
+	err = mxl1x1sf_set_device_mode(state, adap_state->device_mode);
+
+	mxl_fail(err);
+	err = mxl111sf_enable_usb_output(state);
+	mxl_fail(err);
+	err = mxl1x1sf_top_master_ctrl(state, 1);
+	mxl_fail(err);
+
+	if ((MXL111SF_GPIO_MOD_DVBT != adap_state->gpio_mode) &&
+	    (state->chip_rev > MXL111SF_V6)) {
+		mxl111sf_config_pin_mux_modes(state,
+					      PIN_MUX_TS_SPI_IN_MODE_1);
+		mxl_fail(err);
+	}
+	err = mxl111sf_init_port_expander(state);
+	if (!mxl_fail(err)) {
+		state->gpio_mode = adap_state->gpio_mode;
+		err = mxl111sf_gpio_mode_switch(state, state->gpio_mode);
+		mxl_fail(err);
+#if 0
+		err = fe->ops.init(fe);
+#endif
+		msleep(100); /* add short delay after enabling
+			      * the demod before touching it */
+	}
+
+	return (adap_state->fe_init) ? adap_state->fe_init(fe) : 0;
+fail:
+	return -ENODEV;
+}
+
+static int mxl111sf_adap_fe_sleep(struct dvb_frontend *fe)
+{
+	struct mxl111sf_state *state = fe_to_priv(fe);
+	struct mxl111sf_adap_state *adap_state = &state->adap_state[fe->id];
+	int err;
+
+	/* exit if we didn't initialize the driver yet */
+	if (!state->chip_id) {
+		mxl_debug("driver not yet initialized, exit.");
+		goto fail;
+	}
+
+	pr_debug("%s()\n", __func__);
+
+	err = (adap_state->fe_sleep) ? adap_state->fe_sleep(fe) : 0;
+
+	mutex_unlock(&state->fe_lock);
+
+	return err;
+fail:
+	return -ENODEV;
+}
+
+
+static int mxl111sf_ep6_streaming_ctrl(struct dvb_frontend *fe, int onoff)
+{
+	struct mxl111sf_state *state = fe_to_priv(fe);
+	struct mxl111sf_adap_state *adap_state = &state->adap_state[fe->id];
+	int ret = 0;
+
+	pr_debug("%s(%d)\n", __func__, onoff);
+
+	if (onoff) {
+		ret = mxl111sf_enable_usb_output(state);
+		mxl_fail(ret);
+		ret = mxl111sf_config_mpeg_in(state, 1, 1,
+					      adap_state->ep6_clockphase,
+					      0, 0);
+		mxl_fail(ret);
+#if 0
+	} else {
+		ret = mxl111sf_disable_656_port(state);
+		mxl_fail(ret);
+#endif
+	}
+
+	return ret;
+}
+
+static int mxl111sf_ep5_streaming_ctrl(struct dvb_frontend *fe, int onoff)
+{
+	struct mxl111sf_state *state = fe_to_priv(fe);
+	int ret = 0;
+
+	pr_debug("%s(%d)\n", __func__, onoff);
+
+	if (onoff) {
+		ret = mxl111sf_enable_usb_output(state);
+		mxl_fail(ret);
+
+		ret = mxl111sf_init_i2s_port(state, 200);
+		mxl_fail(ret);
+		ret = mxl111sf_config_i2s(state, 0, 15);
+		mxl_fail(ret);
+	} else {
+		ret = mxl111sf_disable_i2s_port(state);
+		mxl_fail(ret);
+	}
+	if (state->chip_rev > MXL111SF_V6)
+		ret = mxl111sf_config_spi(state, onoff);
+	mxl_fail(ret);
+
+	return ret;
+}
+
+static int mxl111sf_ep4_streaming_ctrl(struct dvb_frontend *fe, int onoff)
+{
+	struct mxl111sf_state *state = fe_to_priv(fe);
+	int ret = 0;
+
+	pr_debug("%s(%d)\n", __func__, onoff);
+
+	if (onoff) {
+		ret = mxl111sf_enable_usb_output(state);
+		mxl_fail(ret);
+	}
+
+	return ret;
+}
+
+/* ------------------------------------------------------------------------ */
+
+static struct lgdt3305_config hauppauge_lgdt3305_config = {
+	.i2c_addr           = 0xb2 >> 1,
+	.mpeg_mode          = LGDT3305_MPEG_SERIAL,
+	.tpclk_edge         = LGDT3305_TPCLK_RISING_EDGE,
+	.tpvalid_polarity   = LGDT3305_TP_VALID_HIGH,
+	.deny_i2c_rptr      = 1,
+	.spectral_inversion = 0,
+	.qam_if_khz         = 6000,
+	.vsb_if_khz         = 6000,
+};
+
+static int mxl111sf_lgdt3305_frontend_attach(struct dvb_usb_adapter *adap, u8 fe_id)
+{
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct mxl111sf_state *state = d_to_priv(d);
+	struct mxl111sf_adap_state *adap_state = &state->adap_state[fe_id];
+	int ret;
+
+	pr_debug("%s()\n", __func__);
+
+	/* save a pointer to the dvb_usb_device in device state */
+	state->d = d;
+	adap_state->alt_mode = (dvb_usb_mxl111sf_isoc) ? 2 : 1;
+	state->alt_mode = adap_state->alt_mode;
+
+	if (usb_set_interface(d->udev, 0, state->alt_mode) < 0)
+		pr_err("set interface failed");
+
+	state->gpio_mode = MXL111SF_GPIO_MOD_ATSC;
+	adap_state->gpio_mode = state->gpio_mode;
+	adap_state->device_mode = MXL_TUNER_MODE;
+	adap_state->ep6_clockphase = 1;
+
+	ret = mxl1x1sf_soft_reset(state);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_init_tuner_demod(state);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl1x1sf_set_device_mode(state, adap_state->device_mode);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl111sf_enable_usb_output(state);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl1x1sf_top_master_ctrl(state, 1);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl111sf_init_port_expander(state);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_gpio_mode_switch(state, state->gpio_mode);
+	if (mxl_fail(ret))
+		goto fail;
+
+	adap->fe[fe_id] = dvb_attach(lgdt3305_attach,
+				 &hauppauge_lgdt3305_config,
+				 &d->i2c_adap);
+	if (adap->fe[fe_id]) {
+		state->num_frontends++;
+		adap_state->fe_init = adap->fe[fe_id]->ops.init;
+		adap->fe[fe_id]->ops.init = mxl111sf_adap_fe_init;
+		adap_state->fe_sleep = adap->fe[fe_id]->ops.sleep;
+		adap->fe[fe_id]->ops.sleep = mxl111sf_adap_fe_sleep;
+		return 0;
+	}
+	ret = -EIO;
+fail:
+	return ret;
+}
+
+static struct lg2160_config hauppauge_lg2160_config = {
+	.lg_chip            = LG2160,
+	.i2c_addr           = 0x1c >> 1,
+	.deny_i2c_rptr      = 1,
+	.spectral_inversion = 0,
+	.if_khz             = 6000,
+};
+
+static int mxl111sf_lg2160_frontend_attach(struct dvb_usb_adapter *adap, u8 fe_id)
+{
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct mxl111sf_state *state = d_to_priv(d);
+	struct mxl111sf_adap_state *adap_state = &state->adap_state[fe_id];
+	int ret;
+
+	pr_debug("%s()\n", __func__);
+
+	/* save a pointer to the dvb_usb_device in device state */
+	state->d = d;
+	adap_state->alt_mode = (dvb_usb_mxl111sf_isoc) ? 2 : 1;
+	state->alt_mode = adap_state->alt_mode;
+
+	if (usb_set_interface(d->udev, 0, state->alt_mode) < 0)
+		pr_err("set interface failed");
+
+	state->gpio_mode = MXL111SF_GPIO_MOD_MH;
+	adap_state->gpio_mode = state->gpio_mode;
+	adap_state->device_mode = MXL_TUNER_MODE;
+	adap_state->ep6_clockphase = 1;
+
+	ret = mxl1x1sf_soft_reset(state);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_init_tuner_demod(state);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl1x1sf_set_device_mode(state, adap_state->device_mode);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl111sf_enable_usb_output(state);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl1x1sf_top_master_ctrl(state, 1);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl111sf_init_port_expander(state);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_gpio_mode_switch(state, state->gpio_mode);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = get_chip_info(state);
+	if (mxl_fail(ret))
+		goto fail;
+
+	adap->fe[fe_id] = dvb_attach(lg2160_attach,
+			      &hauppauge_lg2160_config,
+			      &d->i2c_adap);
+	if (adap->fe[fe_id]) {
+		state->num_frontends++;
+		adap_state->fe_init = adap->fe[fe_id]->ops.init;
+		adap->fe[fe_id]->ops.init = mxl111sf_adap_fe_init;
+		adap_state->fe_sleep = adap->fe[fe_id]->ops.sleep;
+		adap->fe[fe_id]->ops.sleep = mxl111sf_adap_fe_sleep;
+		return 0;
+	}
+	ret = -EIO;
+fail:
+	return ret;
+}
+
+static struct lg2160_config hauppauge_lg2161_1019_config = {
+	.lg_chip            = LG2161_1019,
+	.i2c_addr           = 0x1c >> 1,
+	.deny_i2c_rptr      = 1,
+	.spectral_inversion = 0,
+	.if_khz             = 6000,
+	.output_if          = 2, /* LG2161_OIF_SPI_MAS */
+};
+
+static struct lg2160_config hauppauge_lg2161_1040_config = {
+	.lg_chip            = LG2161_1040,
+	.i2c_addr           = 0x1c >> 1,
+	.deny_i2c_rptr      = 1,
+	.spectral_inversion = 0,
+	.if_khz             = 6000,
+	.output_if          = 4, /* LG2161_OIF_SPI_MAS */
+};
+
+static int mxl111sf_lg2161_frontend_attach(struct dvb_usb_adapter *adap, u8 fe_id)
+{
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct mxl111sf_state *state = d_to_priv(d);
+	struct mxl111sf_adap_state *adap_state = &state->adap_state[fe_id];
+	int ret;
+
+	pr_debug("%s()\n", __func__);
+
+	/* save a pointer to the dvb_usb_device in device state */
+	state->d = d;
+	adap_state->alt_mode = (dvb_usb_mxl111sf_isoc) ? 2 : 1;
+	state->alt_mode = adap_state->alt_mode;
+
+	if (usb_set_interface(d->udev, 0, state->alt_mode) < 0)
+		pr_err("set interface failed");
+
+	state->gpio_mode = MXL111SF_GPIO_MOD_MH;
+	adap_state->gpio_mode = state->gpio_mode;
+	adap_state->device_mode = MXL_TUNER_MODE;
+	adap_state->ep6_clockphase = 1;
+
+	ret = mxl1x1sf_soft_reset(state);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_init_tuner_demod(state);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl1x1sf_set_device_mode(state, adap_state->device_mode);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl111sf_enable_usb_output(state);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl1x1sf_top_master_ctrl(state, 1);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl111sf_init_port_expander(state);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_gpio_mode_switch(state, state->gpio_mode);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = get_chip_info(state);
+	if (mxl_fail(ret))
+		goto fail;
+
+	adap->fe[fe_id] = dvb_attach(lg2160_attach,
+			      (MXL111SF_V8_200 == state->chip_rev) ?
+			      &hauppauge_lg2161_1040_config :
+			      &hauppauge_lg2161_1019_config,
+			      &d->i2c_adap);
+	if (adap->fe[fe_id]) {
+		state->num_frontends++;
+		adap_state->fe_init = adap->fe[fe_id]->ops.init;
+		adap->fe[fe_id]->ops.init = mxl111sf_adap_fe_init;
+		adap_state->fe_sleep = adap->fe[fe_id]->ops.sleep;
+		adap->fe[fe_id]->ops.sleep = mxl111sf_adap_fe_sleep;
+		return 0;
+	}
+	ret = -EIO;
+fail:
+	return ret;
+}
+
+static struct lg2160_config hauppauge_lg2161_1019_ep6_config = {
+	.lg_chip            = LG2161_1019,
+	.i2c_addr           = 0x1c >> 1,
+	.deny_i2c_rptr      = 1,
+	.spectral_inversion = 0,
+	.if_khz             = 6000,
+	.output_if          = 1, /* LG2161_OIF_SERIAL_TS */
+};
+
+static struct lg2160_config hauppauge_lg2161_1040_ep6_config = {
+	.lg_chip            = LG2161_1040,
+	.i2c_addr           = 0x1c >> 1,
+	.deny_i2c_rptr      = 1,
+	.spectral_inversion = 0,
+	.if_khz             = 6000,
+	.output_if          = 7, /* LG2161_OIF_SERIAL_TS */
+};
+
+static int mxl111sf_lg2161_ep6_frontend_attach(struct dvb_usb_adapter *adap, u8 fe_id)
+{
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct mxl111sf_state *state = d_to_priv(d);
+	struct mxl111sf_adap_state *adap_state = &state->adap_state[fe_id];
+	int ret;
+
+	pr_debug("%s()\n", __func__);
+
+	/* save a pointer to the dvb_usb_device in device state */
+	state->d = d;
+	adap_state->alt_mode = (dvb_usb_mxl111sf_isoc) ? 2 : 1;
+	state->alt_mode = adap_state->alt_mode;
+
+	if (usb_set_interface(d->udev, 0, state->alt_mode) < 0)
+		pr_err("set interface failed");
+
+	state->gpio_mode = MXL111SF_GPIO_MOD_MH;
+	adap_state->gpio_mode = state->gpio_mode;
+	adap_state->device_mode = MXL_TUNER_MODE;
+	adap_state->ep6_clockphase = 0;
+
+	ret = mxl1x1sf_soft_reset(state);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_init_tuner_demod(state);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl1x1sf_set_device_mode(state, adap_state->device_mode);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl111sf_enable_usb_output(state);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl1x1sf_top_master_ctrl(state, 1);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl111sf_init_port_expander(state);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_gpio_mode_switch(state, state->gpio_mode);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = get_chip_info(state);
+	if (mxl_fail(ret))
+		goto fail;
+
+	adap->fe[fe_id] = dvb_attach(lg2160_attach,
+			      (MXL111SF_V8_200 == state->chip_rev) ?
+			      &hauppauge_lg2161_1040_ep6_config :
+			      &hauppauge_lg2161_1019_ep6_config,
+			      &d->i2c_adap);
+	if (adap->fe[fe_id]) {
+		state->num_frontends++;
+		adap_state->fe_init = adap->fe[fe_id]->ops.init;
+		adap->fe[fe_id]->ops.init = mxl111sf_adap_fe_init;
+		adap_state->fe_sleep = adap->fe[fe_id]->ops.sleep;
+		adap->fe[fe_id]->ops.sleep = mxl111sf_adap_fe_sleep;
+		return 0;
+	}
+	ret = -EIO;
+fail:
+	return ret;
+}
+
+static const struct mxl111sf_demod_config mxl_demod_config = {
+	.read_reg        = mxl111sf_read_reg,
+	.write_reg       = mxl111sf_write_reg,
+	.program_regs    = mxl111sf_ctrl_program_regs,
+};
+
+static int mxl111sf_attach_demod(struct dvb_usb_adapter *adap, u8 fe_id)
+{
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct mxl111sf_state *state = d_to_priv(d);
+	struct mxl111sf_adap_state *adap_state = &state->adap_state[fe_id];
+	int ret;
+
+	pr_debug("%s()\n", __func__);
+
+	/* save a pointer to the dvb_usb_device in device state */
+	state->d = d;
+	adap_state->alt_mode = (dvb_usb_mxl111sf_isoc) ? 1 : 2;
+	state->alt_mode = adap_state->alt_mode;
+
+	if (usb_set_interface(d->udev, 0, state->alt_mode) < 0)
+		pr_err("set interface failed");
+
+	state->gpio_mode = MXL111SF_GPIO_MOD_DVBT;
+	adap_state->gpio_mode = state->gpio_mode;
+	adap_state->device_mode = MXL_SOC_MODE;
+	adap_state->ep6_clockphase = 1;
+
+	ret = mxl1x1sf_soft_reset(state);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl111sf_init_tuner_demod(state);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl1x1sf_set_device_mode(state, adap_state->device_mode);
+	if (mxl_fail(ret))
+		goto fail;
+
+	ret = mxl111sf_enable_usb_output(state);
+	if (mxl_fail(ret))
+		goto fail;
+	ret = mxl1x1sf_top_master_ctrl(state, 1);
+	if (mxl_fail(ret))
+		goto fail;
+
+	/* dont care if this fails */
+	mxl111sf_init_port_expander(state);
+
+	adap->fe[fe_id] = dvb_attach(mxl111sf_demod_attach, state,
+			      &mxl_demod_config);
+	if (adap->fe[fe_id]) {
+		state->num_frontends++;
+		adap_state->fe_init = adap->fe[fe_id]->ops.init;
+		adap->fe[fe_id]->ops.init = mxl111sf_adap_fe_init;
+		adap_state->fe_sleep = adap->fe[fe_id]->ops.sleep;
+		adap->fe[fe_id]->ops.sleep = mxl111sf_adap_fe_sleep;
+		return 0;
+	}
+	ret = -EIO;
+fail:
+	return ret;
+}
+
+static inline int mxl111sf_set_ant_path(struct mxl111sf_state *state,
+					int antpath)
+{
+	return mxl111sf_idac_config(state, 1, 1,
+				    (antpath == ANT_PATH_INTERNAL) ?
+				    0x3f : 0x00, 0);
+}
+
+#define DbgAntHunt(x, pwr0, pwr1, pwr2, pwr3) \
+	pr_err("%s(%d) FINAL input set to %s rxPwr:%d|%d|%d|%d\n", \
+	    __func__, __LINE__, \
+	    (ANT_PATH_EXTERNAL == x) ? "EXTERNAL" : "INTERNAL", \
+	    pwr0, pwr1, pwr2, pwr3)
+
+#define ANT_HUNT_SLEEP 90
+#define ANT_EXT_TWEAK 0
+
+static int mxl111sf_ant_hunt(struct dvb_frontend *fe)
+{
+	struct mxl111sf_state *state = fe_to_priv(fe);
+	int antctrl = dvb_usb_mxl111sf_rfswitch;
+
+	u16 rxPwrA, rxPwr0, rxPwr1, rxPwr2;
+
+	/* FIXME: must force EXTERNAL for QAM - done elsewhere */
+	mxl111sf_set_ant_path(state, antctrl == ANT_PATH_AUTO ?
+			      ANT_PATH_EXTERNAL : antctrl);
+
+	if (antctrl == ANT_PATH_AUTO) {
+#if 0
+		msleep(ANT_HUNT_SLEEP);
+#endif
+		fe->ops.tuner_ops.get_rf_strength(fe, &rxPwrA);
+
+		mxl111sf_set_ant_path(state, ANT_PATH_EXTERNAL);
+		msleep(ANT_HUNT_SLEEP);
+		fe->ops.tuner_ops.get_rf_strength(fe, &rxPwr0);
+
+		mxl111sf_set_ant_path(state, ANT_PATH_EXTERNAL);
+		msleep(ANT_HUNT_SLEEP);
+		fe->ops.tuner_ops.get_rf_strength(fe, &rxPwr1);
+
+		mxl111sf_set_ant_path(state, ANT_PATH_INTERNAL);
+		msleep(ANT_HUNT_SLEEP);
+		fe->ops.tuner_ops.get_rf_strength(fe, &rxPwr2);
+
+		if (rxPwr1+ANT_EXT_TWEAK >= rxPwr2) {
+			/* return with EXTERNAL enabled */
+			mxl111sf_set_ant_path(state, ANT_PATH_EXTERNAL);
+			DbgAntHunt(ANT_PATH_EXTERNAL, rxPwrA,
+				   rxPwr0, rxPwr1, rxPwr2);
+		} else {
+			/* return with INTERNAL enabled */
+			DbgAntHunt(ANT_PATH_INTERNAL, rxPwrA,
+				   rxPwr0, rxPwr1, rxPwr2);
+		}
+	}
+	return 0;
+}
+
+static const struct mxl111sf_tuner_config mxl_tuner_config = {
+	.if_freq         = MXL_IF_6_0, /* applies to external IF output, only */
+	.invert_spectrum = 0,
+	.read_reg        = mxl111sf_read_reg,
+	.write_reg       = mxl111sf_write_reg,
+	.program_regs    = mxl111sf_ctrl_program_regs,
+	.top_master_ctrl = mxl1x1sf_top_master_ctrl,
+	.ant_hunt        = mxl111sf_ant_hunt,
+};
+
+static int mxl111sf_attach_tuner(struct dvb_usb_adapter *adap)
+{
+	struct mxl111sf_state *state = adap_to_priv(adap);
+#ifdef CONFIG_MEDIA_CONTROLLER_DVB
+	struct media_device *mdev = dvb_get_media_controller(&adap->dvb_adap);
+	int ret;
+#endif
+	int i;
+
+	pr_debug("%s()\n", __func__);
+
+	for (i = 0; i < state->num_frontends; i++) {
+		if (dvb_attach(mxl111sf_tuner_attach, adap->fe[i], state,
+				&mxl_tuner_config) == NULL)
+			return -EIO;
+		adap->fe[i]->ops.read_signal_strength = adap->fe[i]->ops.tuner_ops.get_rf_strength;
+	}
+
+#ifdef CONFIG_MEDIA_CONTROLLER_DVB
+	state->tuner.function = MEDIA_ENT_F_TUNER;
+	state->tuner.name = "mxl111sf tuner";
+	state->tuner_pads[TUNER_PAD_RF_INPUT].flags = MEDIA_PAD_FL_SINK;
+	state->tuner_pads[TUNER_PAD_OUTPUT].flags = MEDIA_PAD_FL_SOURCE;
+
+	ret = media_entity_pads_init(&state->tuner,
+				     TUNER_NUM_PADS, state->tuner_pads);
+	if (ret)
+		return ret;
+
+	ret = media_device_register_entity(mdev, &state->tuner);
+	if (ret)
+		return ret;
+#endif
+	return 0;
+}
+
+static u32 mxl111sf_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm mxl111sf_i2c_algo = {
+	.master_xfer   = mxl111sf_i2c_xfer,
+	.functionality = mxl111sf_i2c_func,
+#ifdef NEED_ALGO_CONTROL
+	.algo_control = dummy_algo_control,
+#endif
+};
+
+static int mxl111sf_init(struct dvb_usb_device *d)
+{
+	struct mxl111sf_state *state = d_to_priv(d);
+	int ret;
+	static u8 eeprom[256];
+	u8 reg = 0;
+	struct i2c_msg msg[2] = {
+		{ .addr = 0xa0 >> 1, .len = 1, .buf = &reg },
+		{ .addr = 0xa0 >> 1, .flags = I2C_M_RD,
+		  .len = sizeof(eeprom), .buf = eeprom },
+	};
+
+	mutex_init(&state->msg_lock);
+
+	ret = get_chip_info(state);
+	if (mxl_fail(ret))
+		pr_err("failed to get chip info during probe");
+
+	mutex_init(&state->fe_lock);
+
+	if (state->chip_rev > MXL111SF_V6)
+		mxl111sf_config_pin_mux_modes(state, PIN_MUX_TS_SPI_IN_MODE_1);
+
+	ret = i2c_transfer(&d->i2c_adap, msg, 2);
+	if (mxl_fail(ret))
+		return 0;
+	tveeprom_hauppauge_analog(&state->tv, (0x84 == eeprom[0xa0]) ?
+				  eeprom + 0xa0 : eeprom + 0x80);
+#if 0
+	switch (state->tv.model) {
+	case 117001:
+	case 126001:
+	case 138001:
+		break;
+	default:
+		printk(KERN_WARNING "%s: warning: unknown hauppauge model #%d\n",
+		       __func__, state->tv.model);
+	}
+#endif
+	return 0;
+}
+
+static int mxl111sf_frontend_attach_dvbt(struct dvb_usb_adapter *adap)
+{
+	return mxl111sf_attach_demod(adap, 0);
+}
+
+static int mxl111sf_frontend_attach_atsc(struct dvb_usb_adapter *adap)
+{
+	return mxl111sf_lgdt3305_frontend_attach(adap, 0);
+}
+
+static int mxl111sf_frontend_attach_mh(struct dvb_usb_adapter *adap)
+{
+	return mxl111sf_lg2160_frontend_attach(adap, 0);
+}
+
+static int mxl111sf_frontend_attach_atsc_mh(struct dvb_usb_adapter *adap)
+{
+	int ret;
+	pr_debug("%s\n", __func__);
+
+	ret = mxl111sf_lgdt3305_frontend_attach(adap, 0);
+	if (ret < 0)
+		return ret;
+
+	ret = mxl111sf_attach_demod(adap, 1);
+	if (ret < 0)
+		return ret;
+
+	ret = mxl111sf_lg2160_frontend_attach(adap, 2);
+	if (ret < 0)
+		return ret;
+
+	return ret;
+}
+
+static int mxl111sf_frontend_attach_mercury(struct dvb_usb_adapter *adap)
+{
+	int ret;
+	pr_debug("%s\n", __func__);
+
+	ret = mxl111sf_lgdt3305_frontend_attach(adap, 0);
+	if (ret < 0)
+		return ret;
+
+	ret = mxl111sf_attach_demod(adap, 1);
+	if (ret < 0)
+		return ret;
+
+	ret = mxl111sf_lg2161_ep6_frontend_attach(adap, 2);
+	if (ret < 0)
+		return ret;
+
+	return ret;
+}
+
+static int mxl111sf_frontend_attach_mercury_mh(struct dvb_usb_adapter *adap)
+{
+	int ret;
+	pr_debug("%s\n", __func__);
+
+	ret = mxl111sf_attach_demod(adap, 0);
+	if (ret < 0)
+		return ret;
+
+	if (dvb_usb_mxl111sf_spi)
+		ret = mxl111sf_lg2161_frontend_attach(adap, 1);
+	else
+		ret = mxl111sf_lg2161_ep6_frontend_attach(adap, 1);
+
+	return ret;
+}
+
+static void mxl111sf_stream_config_bulk(struct usb_data_stream_properties *stream, u8 endpoint)
+{
+	pr_debug("%s: endpoint=%d size=8192\n", __func__, endpoint);
+	stream->type = USB_BULK;
+	stream->count = 5;
+	stream->endpoint = endpoint;
+	stream->u.bulk.buffersize = 8192;
+}
+
+static void mxl111sf_stream_config_isoc(struct usb_data_stream_properties *stream,
+		u8 endpoint, int framesperurb, int framesize)
+{
+	pr_debug("%s: endpoint=%d size=%d\n", __func__, endpoint,
+			framesperurb * framesize);
+	stream->type = USB_ISOC;
+	stream->count = 5;
+	stream->endpoint = endpoint;
+	stream->u.isoc.framesperurb = framesperurb;
+	stream->u.isoc.framesize = framesize;
+	stream->u.isoc.interval = 1;
+}
+
+/* DVB USB Driver stuff */
+
+/* dvbt       mxl111sf
+ * bulk       EP4/BULK/5/8192
+ * isoc       EP4/ISOC/5/96/564
+ */
+static int mxl111sf_get_stream_config_dvbt(struct dvb_frontend *fe,
+		u8 *ts_type, struct usb_data_stream_properties *stream)
+{
+	pr_debug("%s: fe=%d\n", __func__, fe->id);
+
+	*ts_type = DVB_USB_FE_TS_TYPE_188;
+	if (dvb_usb_mxl111sf_isoc)
+		mxl111sf_stream_config_isoc(stream, 4, 96, 564);
+	else
+		mxl111sf_stream_config_bulk(stream, 4);
+	return 0;
+}
+
+static struct dvb_usb_device_properties mxl111sf_props_dvbt = {
+	.driver_name = KBUILD_MODNAME,
+	.owner = THIS_MODULE,
+	.adapter_nr = adapter_nr,
+	.size_of_priv = sizeof(struct mxl111sf_state),
+
+	.generic_bulk_ctrl_endpoint = 0x02,
+	.generic_bulk_ctrl_endpoint_response = 0x81,
+
+	.i2c_algo          = &mxl111sf_i2c_algo,
+	.frontend_attach   = mxl111sf_frontend_attach_dvbt,
+	.tuner_attach      = mxl111sf_attach_tuner,
+	.init              = mxl111sf_init,
+	.streaming_ctrl    = mxl111sf_ep4_streaming_ctrl,
+	.get_stream_config = mxl111sf_get_stream_config_dvbt,
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+			.stream = DVB_USB_STREAM_ISOC(6, 5, 24, 3072, 1),
+		}
+	}
+};
+
+/* atsc       lgdt3305
+ * bulk       EP6/BULK/5/8192
+ * isoc       EP6/ISOC/5/24/3072
+ */
+static int mxl111sf_get_stream_config_atsc(struct dvb_frontend *fe,
+		u8 *ts_type, struct usb_data_stream_properties *stream)
+{
+	pr_debug("%s: fe=%d\n", __func__, fe->id);
+
+	*ts_type = DVB_USB_FE_TS_TYPE_188;
+	if (dvb_usb_mxl111sf_isoc)
+		mxl111sf_stream_config_isoc(stream, 6, 24, 3072);
+	else
+		mxl111sf_stream_config_bulk(stream, 6);
+	return 0;
+}
+
+static struct dvb_usb_device_properties mxl111sf_props_atsc = {
+	.driver_name = KBUILD_MODNAME,
+	.owner = THIS_MODULE,
+	.adapter_nr = adapter_nr,
+	.size_of_priv = sizeof(struct mxl111sf_state),
+
+	.generic_bulk_ctrl_endpoint = 0x02,
+	.generic_bulk_ctrl_endpoint_response = 0x81,
+
+	.i2c_algo          = &mxl111sf_i2c_algo,
+	.frontend_attach   = mxl111sf_frontend_attach_atsc,
+	.tuner_attach      = mxl111sf_attach_tuner,
+	.init              = mxl111sf_init,
+	.streaming_ctrl    = mxl111sf_ep6_streaming_ctrl,
+	.get_stream_config = mxl111sf_get_stream_config_atsc,
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+			.stream = DVB_USB_STREAM_ISOC(6, 5, 24, 3072, 1),
+		}
+	}
+};
+
+/* mh         lg2160
+ * bulk       EP5/BULK/5/8192/RAW
+ * isoc       EP5/ISOC/5/96/200/RAW
+ */
+static int mxl111sf_get_stream_config_mh(struct dvb_frontend *fe,
+		u8 *ts_type, struct usb_data_stream_properties *stream)
+{
+	pr_debug("%s: fe=%d\n", __func__, fe->id);
+
+	*ts_type = DVB_USB_FE_TS_TYPE_RAW;
+	if (dvb_usb_mxl111sf_isoc)
+		mxl111sf_stream_config_isoc(stream, 5, 96, 200);
+	else
+		mxl111sf_stream_config_bulk(stream, 5);
+	return 0;
+}
+
+static struct dvb_usb_device_properties mxl111sf_props_mh = {
+	.driver_name = KBUILD_MODNAME,
+	.owner = THIS_MODULE,
+	.adapter_nr = adapter_nr,
+	.size_of_priv = sizeof(struct mxl111sf_state),
+
+	.generic_bulk_ctrl_endpoint = 0x02,
+	.generic_bulk_ctrl_endpoint_response = 0x81,
+
+	.i2c_algo          = &mxl111sf_i2c_algo,
+	.frontend_attach   = mxl111sf_frontend_attach_mh,
+	.tuner_attach      = mxl111sf_attach_tuner,
+	.init              = mxl111sf_init,
+	.streaming_ctrl    = mxl111sf_ep5_streaming_ctrl,
+	.get_stream_config = mxl111sf_get_stream_config_mh,
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+			.stream = DVB_USB_STREAM_ISOC(6, 5, 24, 3072, 1),
+		}
+	}
+};
+
+/* atsc mh    lgdt3305           mxl111sf          lg2160
+ * bulk       EP6/BULK/5/8192    EP4/BULK/5/8192   EP5/BULK/5/8192/RAW
+ * isoc       EP6/ISOC/5/24/3072 EP4/ISOC/5/96/564 EP5/ISOC/5/96/200/RAW
+ */
+static int mxl111sf_get_stream_config_atsc_mh(struct dvb_frontend *fe,
+		u8 *ts_type, struct usb_data_stream_properties *stream)
+{
+	pr_debug("%s: fe=%d\n", __func__, fe->id);
+
+	if (fe->id == 0) {
+		*ts_type = DVB_USB_FE_TS_TYPE_188;
+		if (dvb_usb_mxl111sf_isoc)
+			mxl111sf_stream_config_isoc(stream, 6, 24, 3072);
+		else
+			mxl111sf_stream_config_bulk(stream, 6);
+	} else if (fe->id == 1) {
+		*ts_type = DVB_USB_FE_TS_TYPE_188;
+		if (dvb_usb_mxl111sf_isoc)
+			mxl111sf_stream_config_isoc(stream, 4, 96, 564);
+		else
+			mxl111sf_stream_config_bulk(stream, 4);
+	} else if (fe->id == 2) {
+		*ts_type = DVB_USB_FE_TS_TYPE_RAW;
+		if (dvb_usb_mxl111sf_isoc)
+			mxl111sf_stream_config_isoc(stream, 5, 96, 200);
+		else
+			mxl111sf_stream_config_bulk(stream, 5);
+	}
+	return 0;
+}
+
+static int mxl111sf_streaming_ctrl_atsc_mh(struct dvb_frontend *fe, int onoff)
+{
+	pr_debug("%s: fe=%d onoff=%d\n", __func__, fe->id, onoff);
+
+	if (fe->id == 0)
+		return mxl111sf_ep6_streaming_ctrl(fe, onoff);
+	else if (fe->id == 1)
+		return mxl111sf_ep4_streaming_ctrl(fe, onoff);
+	else if (fe->id == 2)
+		return mxl111sf_ep5_streaming_ctrl(fe, onoff);
+	return 0;
+}
+
+static struct dvb_usb_device_properties mxl111sf_props_atsc_mh = {
+	.driver_name = KBUILD_MODNAME,
+	.owner = THIS_MODULE,
+	.adapter_nr = adapter_nr,
+	.size_of_priv = sizeof(struct mxl111sf_state),
+
+	.generic_bulk_ctrl_endpoint = 0x02,
+	.generic_bulk_ctrl_endpoint_response = 0x81,
+
+	.i2c_algo          = &mxl111sf_i2c_algo,
+	.frontend_attach   = mxl111sf_frontend_attach_atsc_mh,
+	.tuner_attach      = mxl111sf_attach_tuner,
+	.init              = mxl111sf_init,
+	.streaming_ctrl    = mxl111sf_streaming_ctrl_atsc_mh,
+	.get_stream_config = mxl111sf_get_stream_config_atsc_mh,
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+			.stream = DVB_USB_STREAM_ISOC(6, 5, 24, 3072, 1),
+		}
+	}
+};
+
+/* mercury    lgdt3305           mxl111sf          lg2161
+ * tp bulk    EP6/BULK/5/8192    EP4/BULK/5/8192   EP6/BULK/5/8192/RAW
+ * tp isoc    EP6/ISOC/5/24/3072 EP4/ISOC/5/96/564 EP6/ISOC/5/24/3072/RAW
+ * spi bulk   EP6/BULK/5/8192    EP4/BULK/5/8192   EP5/BULK/5/8192/RAW
+ * spi isoc   EP6/ISOC/5/24/3072 EP4/ISOC/5/96/564 EP5/ISOC/5/96/200/RAW
+ */
+static int mxl111sf_get_stream_config_mercury(struct dvb_frontend *fe,
+		u8 *ts_type, struct usb_data_stream_properties *stream)
+{
+	pr_debug("%s: fe=%d\n", __func__, fe->id);
+
+	if (fe->id == 0) {
+		*ts_type = DVB_USB_FE_TS_TYPE_188;
+		if (dvb_usb_mxl111sf_isoc)
+			mxl111sf_stream_config_isoc(stream, 6, 24, 3072);
+		else
+			mxl111sf_stream_config_bulk(stream, 6);
+	} else if (fe->id == 1) {
+		*ts_type = DVB_USB_FE_TS_TYPE_188;
+		if (dvb_usb_mxl111sf_isoc)
+			mxl111sf_stream_config_isoc(stream, 4, 96, 564);
+		else
+			mxl111sf_stream_config_bulk(stream, 4);
+	} else if (fe->id == 2 && dvb_usb_mxl111sf_spi) {
+		*ts_type = DVB_USB_FE_TS_TYPE_RAW;
+		if (dvb_usb_mxl111sf_isoc)
+			mxl111sf_stream_config_isoc(stream, 5, 96, 200);
+		else
+			mxl111sf_stream_config_bulk(stream, 5);
+	} else if (fe->id == 2 && !dvb_usb_mxl111sf_spi) {
+		*ts_type = DVB_USB_FE_TS_TYPE_RAW;
+		if (dvb_usb_mxl111sf_isoc)
+			mxl111sf_stream_config_isoc(stream, 6, 24, 3072);
+		else
+			mxl111sf_stream_config_bulk(stream, 6);
+	}
+	return 0;
+}
+
+static int mxl111sf_streaming_ctrl_mercury(struct dvb_frontend *fe, int onoff)
+{
+	pr_debug("%s: fe=%d onoff=%d\n", __func__, fe->id, onoff);
+
+	if (fe->id == 0)
+		return mxl111sf_ep6_streaming_ctrl(fe, onoff);
+	else if (fe->id == 1)
+		return mxl111sf_ep4_streaming_ctrl(fe, onoff);
+	else if (fe->id == 2 && dvb_usb_mxl111sf_spi)
+		return mxl111sf_ep5_streaming_ctrl(fe, onoff);
+	else if (fe->id == 2 && !dvb_usb_mxl111sf_spi)
+		return mxl111sf_ep6_streaming_ctrl(fe, onoff);
+	return 0;
+}
+
+static struct dvb_usb_device_properties mxl111sf_props_mercury = {
+	.driver_name = KBUILD_MODNAME,
+	.owner = THIS_MODULE,
+	.adapter_nr = adapter_nr,
+	.size_of_priv = sizeof(struct mxl111sf_state),
+
+	.generic_bulk_ctrl_endpoint = 0x02,
+	.generic_bulk_ctrl_endpoint_response = 0x81,
+
+	.i2c_algo          = &mxl111sf_i2c_algo,
+	.frontend_attach   = mxl111sf_frontend_attach_mercury,
+	.tuner_attach      = mxl111sf_attach_tuner,
+	.init              = mxl111sf_init,
+	.streaming_ctrl    = mxl111sf_streaming_ctrl_mercury,
+	.get_stream_config = mxl111sf_get_stream_config_mercury,
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+			.stream = DVB_USB_STREAM_ISOC(6, 5, 24, 3072, 1),
+		}
+	}
+};
+
+/* mercury mh mxl111sf          lg2161
+ * tp bulk    EP4/BULK/5/8192   EP6/BULK/5/8192/RAW
+ * tp isoc    EP4/ISOC/5/96/564 EP6/ISOC/5/24/3072/RAW
+ * spi bulk   EP4/BULK/5/8192   EP5/BULK/5/8192/RAW
+ * spi isoc   EP4/ISOC/5/96/564 EP5/ISOC/5/96/200/RAW
+ */
+static int mxl111sf_get_stream_config_mercury_mh(struct dvb_frontend *fe,
+		u8 *ts_type, struct usb_data_stream_properties *stream)
+{
+	pr_debug("%s: fe=%d\n", __func__, fe->id);
+
+	if (fe->id == 0) {
+		*ts_type = DVB_USB_FE_TS_TYPE_188;
+		if (dvb_usb_mxl111sf_isoc)
+			mxl111sf_stream_config_isoc(stream, 4, 96, 564);
+		else
+			mxl111sf_stream_config_bulk(stream, 4);
+	} else if (fe->id == 1 && dvb_usb_mxl111sf_spi) {
+		*ts_type = DVB_USB_FE_TS_TYPE_RAW;
+		if (dvb_usb_mxl111sf_isoc)
+			mxl111sf_stream_config_isoc(stream, 5, 96, 200);
+		else
+			mxl111sf_stream_config_bulk(stream, 5);
+	} else if (fe->id == 1 && !dvb_usb_mxl111sf_spi) {
+		*ts_type = DVB_USB_FE_TS_TYPE_RAW;
+		if (dvb_usb_mxl111sf_isoc)
+			mxl111sf_stream_config_isoc(stream, 6, 24, 3072);
+		else
+			mxl111sf_stream_config_bulk(stream, 6);
+	}
+	return 0;
+}
+
+static int mxl111sf_streaming_ctrl_mercury_mh(struct dvb_frontend *fe, int onoff)
+{
+	pr_debug("%s: fe=%d onoff=%d\n", __func__, fe->id, onoff);
+
+	if (fe->id == 0)
+		return mxl111sf_ep4_streaming_ctrl(fe, onoff);
+	else if (fe->id == 1  && dvb_usb_mxl111sf_spi)
+		return mxl111sf_ep5_streaming_ctrl(fe, onoff);
+	else if (fe->id == 1 && !dvb_usb_mxl111sf_spi)
+		return mxl111sf_ep6_streaming_ctrl(fe, onoff);
+	return 0;
+}
+
+static struct dvb_usb_device_properties mxl111sf_props_mercury_mh = {
+	.driver_name = KBUILD_MODNAME,
+	.owner = THIS_MODULE,
+	.adapter_nr = adapter_nr,
+	.size_of_priv = sizeof(struct mxl111sf_state),
+
+	.generic_bulk_ctrl_endpoint = 0x02,
+	.generic_bulk_ctrl_endpoint_response = 0x81,
+
+	.i2c_algo          = &mxl111sf_i2c_algo,
+	.frontend_attach   = mxl111sf_frontend_attach_mercury_mh,
+	.tuner_attach      = mxl111sf_attach_tuner,
+	.init              = mxl111sf_init,
+	.streaming_ctrl    = mxl111sf_streaming_ctrl_mercury_mh,
+	.get_stream_config = mxl111sf_get_stream_config_mercury_mh,
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+			.stream = DVB_USB_STREAM_ISOC(6, 5, 24, 3072, 1),
+		}
+	}
+};
+
+static const struct usb_device_id mxl111sf_id_table[] = {
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc600, &mxl111sf_props_atsc_mh, "Hauppauge 126xxx ATSC+", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc601, &mxl111sf_props_atsc, "Hauppauge 126xxx ATSC", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc602, &mxl111sf_props_mh, "HCW 126xxx", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc603, &mxl111sf_props_atsc_mh, "Hauppauge 126xxx ATSC+", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc604, &mxl111sf_props_dvbt, "Hauppauge 126xxx DVBT", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc609, &mxl111sf_props_atsc, "Hauppauge 126xxx ATSC", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc60a, &mxl111sf_props_mh, "HCW 126xxx", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc60b, &mxl111sf_props_atsc_mh, "Hauppauge 126xxx ATSC+", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc60c, &mxl111sf_props_dvbt, "Hauppauge 126xxx DVBT", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc653, &mxl111sf_props_atsc_mh, "Hauppauge 126xxx ATSC+", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc65b, &mxl111sf_props_atsc_mh, "Hauppauge 126xxx ATSC+", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb700, &mxl111sf_props_atsc_mh, "Hauppauge 117xxx ATSC+", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb701, &mxl111sf_props_atsc, "Hauppauge 126xxx ATSC", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb702, &mxl111sf_props_mh, "HCW 117xxx", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb703, &mxl111sf_props_atsc_mh, "Hauppauge 117xxx ATSC+", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb704, &mxl111sf_props_dvbt, "Hauppauge 117xxx DVBT", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb753, &mxl111sf_props_atsc_mh, "Hauppauge 117xxx ATSC+", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb763, &mxl111sf_props_atsc_mh, "Hauppauge 117xxx ATSC+", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb764, &mxl111sf_props_dvbt, "Hauppauge 117xxx DVBT", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd853, &mxl111sf_props_mercury, "Hauppauge Mercury", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd854, &mxl111sf_props_dvbt, "Hauppauge 138xxx DVBT", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd863, &mxl111sf_props_mercury, "Hauppauge Mercury", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd864, &mxl111sf_props_dvbt, "Hauppauge 138xxx DVBT", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd8d3, &mxl111sf_props_mercury, "Hauppauge Mercury", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd8d4, &mxl111sf_props_dvbt, "Hauppauge 138xxx DVBT", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd8e3, &mxl111sf_props_mercury, "Hauppauge Mercury", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd8e4, &mxl111sf_props_dvbt, "Hauppauge 138xxx DVBT", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xd8ff, &mxl111sf_props_mercury, "Hauppauge Mercury", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc612, &mxl111sf_props_mercury_mh, "Hauppauge 126xxx", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc613, &mxl111sf_props_mercury, "Hauppauge WinTV-Aero-M", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc61a, &mxl111sf_props_mercury_mh, "Hauppauge 126xxx", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xc61b, &mxl111sf_props_mercury, "Hauppauge WinTV-Aero-M", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb757, &mxl111sf_props_atsc_mh, "Hauppauge 117xxx ATSC+", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_HAUPPAUGE, 0xb767, &mxl111sf_props_atsc_mh, "Hauppauge 117xxx ATSC+", NULL) },
+	{ }
+};
+MODULE_DEVICE_TABLE(usb, mxl111sf_id_table);
+
+static struct usb_driver mxl111sf_usb_driver = {
+	.name = KBUILD_MODNAME,
+	.id_table = mxl111sf_id_table,
+	.probe = dvb_usbv2_probe,
+	.disconnect = dvb_usbv2_disconnect,
+	.suspend = dvb_usbv2_suspend,
+	.resume = dvb_usbv2_resume,
+	.no_dynamic_id = 1,
+	.soft_unbind = 1,
+};
+
+module_usb_driver(mxl111sf_usb_driver);
+
+MODULE_AUTHOR("Michael Krufky <mkrufky@linuxtv.org>");
+MODULE_DESCRIPTION("Driver for MaxLinear MxL111SF");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb-v2/mxl111sf.h b/drivers/media/usb/dvb-usb-v2/mxl111sf.h
new file mode 100644
index 0000000..22253d4
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/mxl111sf.h
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2010-2014 Michael Krufky (mkrufky@linuxtv.org)
+ *
+ *   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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+
+#ifndef _DVB_USB_MXL111SF_H_
+#define _DVB_USB_MXL111SF_H_
+
+#ifdef DVB_USB_LOG_PREFIX
+#undef DVB_USB_LOG_PREFIX
+#endif
+#define DVB_USB_LOG_PREFIX "mxl111sf"
+#include "dvb_usb.h"
+#include <media/tveeprom.h>
+#include <media/media-entity.h>
+
+/* Max transfer size done by I2C transfer functions */
+#define MXL_MAX_XFER_SIZE  64
+
+#define MXL_EP1_REG_READ     1
+#define MXL_EP2_REG_WRITE    2
+#define MXL_EP3_INTERRUPT    3
+#define MXL_EP4_MPEG2        4
+#define MXL_EP5_I2S          5
+#define MXL_EP6_656          6
+#define MXL_EP6_MPEG2        6
+
+#ifdef USING_ENUM_mxl111sf_current_mode
+enum mxl111sf_current_mode {
+	mxl_mode_dvbt = MXL_EP4_MPEG2,
+	mxl_mode_mh   = MXL_EP5_I2S,
+	mxl_mode_atsc = MXL_EP6_MPEG2,
+};
+#endif
+
+enum mxl111sf_gpio_port_expander {
+	mxl111sf_gpio_hw,
+	mxl111sf_PCA9534,
+};
+
+struct mxl111sf_adap_state {
+	int alt_mode;
+	int gpio_mode;
+	int device_mode;
+	int ep6_clockphase;
+	int (*fe_init)(struct dvb_frontend *);
+	int (*fe_sleep)(struct dvb_frontend *);
+};
+
+struct mxl111sf_state {
+	struct dvb_usb_device *d;
+
+	enum mxl111sf_gpio_port_expander gpio_port_expander;
+	u8 port_expander_addr;
+
+	u8 chip_id;
+	u8 chip_ver;
+#define MXL111SF_V6     1
+#define MXL111SF_V8_100 2
+#define MXL111SF_V8_200 3
+	u8 chip_rev;
+
+#ifdef USING_ENUM_mxl111sf_current_mode
+	enum mxl111sf_current_mode current_mode;
+#endif
+
+#define MXL_TUNER_MODE         0
+#define MXL_SOC_MODE           1
+#define MXL_DEV_MODE_MASK      0x01
+#if 1
+	int device_mode;
+#endif
+	/* use usb alt setting 1 for EP4 ISOC transfer (dvb-t),
+				     EP5 BULK transfer (atsc-mh),
+				     EP6 BULK transfer (atsc/qam),
+	   use usb alt setting 2 for EP4 BULK transfer (dvb-t),
+				     EP5 ISOC transfer (atsc-mh),
+				     EP6 ISOC transfer (atsc/qam),
+	 */
+	int alt_mode;
+	int gpio_mode;
+	struct tveeprom tv;
+
+	struct mutex fe_lock;
+	u8 num_frontends;
+	struct mxl111sf_adap_state adap_state[3];
+	u8 sndbuf[MXL_MAX_XFER_SIZE];
+	u8 rcvbuf[MXL_MAX_XFER_SIZE];
+	struct mutex msg_lock;
+#ifdef CONFIG_MEDIA_CONTROLLER_DVB
+	struct media_entity tuner;
+	struct media_pad tuner_pads[2];
+#endif
+};
+
+int mxl111sf_read_reg(struct mxl111sf_state *state, u8 addr, u8 *data);
+int mxl111sf_write_reg(struct mxl111sf_state *state, u8 addr, u8 data);
+
+struct mxl111sf_reg_ctrl_info {
+	u8 addr;
+	u8 mask;
+	u8 data;
+};
+
+int mxl111sf_write_reg_mask(struct mxl111sf_state *state,
+			    u8 addr, u8 mask, u8 data);
+int mxl111sf_ctrl_program_regs(struct mxl111sf_state *state,
+			       struct mxl111sf_reg_ctrl_info *ctrl_reg_info);
+
+/* needed for hardware i2c functions in mxl111sf-i2c.c:
+ * mxl111sf_i2c_send_data / mxl111sf_i2c_get_data */
+int mxl111sf_ctrl_msg(struct mxl111sf_state *state,
+		      u8 cmd, u8 *wbuf, int wlen, u8 *rbuf, int rlen);
+
+#define mxl_printk(kern, fmt, arg...) \
+	printk(kern "%s: " fmt "\n", __func__, ##arg)
+
+#define mxl_info(fmt, arg...) \
+	mxl_printk(KERN_INFO, fmt, ##arg)
+
+extern int dvb_usb_mxl111sf_debug;
+#define mxl_debug(fmt, arg...) \
+	if (dvb_usb_mxl111sf_debug) \
+		mxl_printk(KERN_DEBUG, fmt, ##arg)
+
+#define MXL_I2C_DBG 0x04
+#define MXL_ADV_DBG 0x10
+#define mxl_debug_adv(fmt, arg...) \
+	if (dvb_usb_mxl111sf_debug & MXL_ADV_DBG) \
+		mxl_printk(KERN_DEBUG, fmt, ##arg)
+
+#define mxl_i2c(fmt, arg...) \
+	if (dvb_usb_mxl111sf_debug & MXL_I2C_DBG) \
+		mxl_printk(KERN_DEBUG, fmt, ##arg)
+
+#define mxl_i2c_adv(fmt, arg...) \
+	if ((dvb_usb_mxl111sf_debug & (MXL_I2C_DBG | MXL_ADV_DBG)) == \
+		(MXL_I2C_DBG | MXL_ADV_DBG)) \
+			mxl_printk(KERN_DEBUG, fmt, ##arg)
+
+/* The following allows the mxl_fail() macro defined below to work
+ * in externel modules, such as mxl111sf-tuner.ko, even though
+ * dvb_usb_mxl111sf_debug is not defined within those modules */
+#if (defined(__MXL111SF_TUNER_H__)) || (defined(__MXL111SF_DEMOD_H__))
+#define MXL_ADV_DEBUG_ENABLED MXL_ADV_DBG
+#else
+#define MXL_ADV_DEBUG_ENABLED dvb_usb_mxl111sf_debug
+#endif
+
+#define mxl_fail(ret)							\
+({									\
+	int __ret;							\
+	__ret = (ret < 0);						\
+	if ((__ret) && (MXL_ADV_DEBUG_ENABLED & MXL_ADV_DBG))		\
+		mxl_printk(KERN_ERR, "error %d on line %d",		\
+			   ret, __LINE__);				\
+	__ret;								\
+})
+
+#endif /* _DVB_USB_MXL111SF_H_ */
diff --git a/drivers/media/usb/dvb-usb-v2/rtl28xxu.c b/drivers/media/usb/dvb-usb-v2/rtl28xxu.c
new file mode 100644
index 0000000..a970224
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/rtl28xxu.c
@@ -0,0 +1,1963 @@
+/*
+ * Realtek RTL28xxU DVB USB driver
+ *
+ * Copyright (C) 2009 Antti Palosaari <crope@iki.fi>
+ * Copyright (C) 2011 Antti Palosaari <crope@iki.fi>
+ * Copyright (C) 2012 Thomas Mair <thomas.mair86@googlemail.com>
+ *
+ *    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 "rtl28xxu.h"
+
+static int rtl28xxu_disable_rc;
+module_param_named(disable_rc, rtl28xxu_disable_rc, int, 0644);
+MODULE_PARM_DESC(disable_rc, "disable RTL2832U remote controller");
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static int rtl28xxu_ctrl_msg(struct dvb_usb_device *d, struct rtl28xxu_req *req)
+{
+	struct rtl28xxu_dev *dev = d->priv;
+	int ret;
+	unsigned int pipe;
+	u8 requesttype;
+
+	mutex_lock(&d->usb_mutex);
+
+	if (req->size > sizeof(dev->buf)) {
+		dev_err(&d->intf->dev, "too large message %u\n", req->size);
+		ret = -EINVAL;
+		goto err_mutex_unlock;
+	}
+
+	if (req->index & CMD_WR_FLAG) {
+		/* write */
+		memcpy(dev->buf, req->data, req->size);
+		requesttype = (USB_TYPE_VENDOR | USB_DIR_OUT);
+		pipe = usb_sndctrlpipe(d->udev, 0);
+	} else {
+		/* read */
+		requesttype = (USB_TYPE_VENDOR | USB_DIR_IN);
+		pipe = usb_rcvctrlpipe(d->udev, 0);
+	}
+
+	ret = usb_control_msg(d->udev, pipe, 0, requesttype, req->value,
+			req->index, dev->buf, req->size, 1000);
+	dvb_usb_dbg_usb_control_msg(d->udev, 0, requesttype, req->value,
+			req->index, dev->buf, req->size);
+	if (ret < 0)
+		goto err_mutex_unlock;
+
+	/* read request, copy returned data to return buf */
+	if (requesttype == (USB_TYPE_VENDOR | USB_DIR_IN))
+		memcpy(req->data, dev->buf, req->size);
+
+	mutex_unlock(&d->usb_mutex);
+
+	return 0;
+err_mutex_unlock:
+	mutex_unlock(&d->usb_mutex);
+	dev_dbg(&d->intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static int rtl28xxu_wr_regs(struct dvb_usb_device *d, u16 reg, u8 *val, int len)
+{
+	struct rtl28xxu_req req;
+
+	if (reg < 0x3000)
+		req.index = CMD_USB_WR;
+	else if (reg < 0x4000)
+		req.index = CMD_SYS_WR;
+	else
+		req.index = CMD_IR_WR;
+
+	req.value = reg;
+	req.size = len;
+	req.data = val;
+
+	return rtl28xxu_ctrl_msg(d, &req);
+}
+
+static int rtl28xxu_rd_regs(struct dvb_usb_device *d, u16 reg, u8 *val, int len)
+{
+	struct rtl28xxu_req req;
+
+	if (reg < 0x3000)
+		req.index = CMD_USB_RD;
+	else if (reg < 0x4000)
+		req.index = CMD_SYS_RD;
+	else
+		req.index = CMD_IR_RD;
+
+	req.value = reg;
+	req.size = len;
+	req.data = val;
+
+	return rtl28xxu_ctrl_msg(d, &req);
+}
+
+static int rtl28xxu_wr_reg(struct dvb_usb_device *d, u16 reg, u8 val)
+{
+	return rtl28xxu_wr_regs(d, reg, &val, 1);
+}
+
+static int rtl28xxu_rd_reg(struct dvb_usb_device *d, u16 reg, u8 *val)
+{
+	return rtl28xxu_rd_regs(d, reg, val, 1);
+}
+
+static int rtl28xxu_wr_reg_mask(struct dvb_usb_device *d, u16 reg, u8 val,
+		u8 mask)
+{
+	int ret;
+	u8 tmp;
+
+	/* no need for read if whole reg is written */
+	if (mask != 0xff) {
+		ret = rtl28xxu_rd_reg(d, reg, &tmp);
+		if (ret)
+			return ret;
+
+		val &= mask;
+		tmp &= ~mask;
+		val |= tmp;
+	}
+
+	return rtl28xxu_wr_reg(d, reg, val);
+}
+
+/* I2C */
+static int rtl28xxu_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[],
+	int num)
+{
+	int ret;
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	struct rtl28xxu_dev *dev = d->priv;
+	struct rtl28xxu_req req;
+
+	/*
+	 * It is not known which are real I2C bus xfer limits, but testing
+	 * with RTL2831U + MT2060 gives max RD 24 and max WR 22 bytes.
+	 * TODO: find out RTL2832U lens
+	 */
+
+	/*
+	 * I2C adapter logic looks rather complicated due to fact it handles
+	 * three different access methods. Those methods are;
+	 * 1) integrated demod access
+	 * 2) old I2C access
+	 * 3) new I2C access
+	 *
+	 * Used method is selected in order 1, 2, 3. Method 3 can handle all
+	 * requests but there is two reasons why not use it always;
+	 * 1) It is most expensive, usually two USB messages are needed
+	 * 2) At least RTL2831U does not support it
+	 *
+	 * Method 3 is needed in case of I2C write+read (typical register read)
+	 * where write is more than one byte.
+	 */
+
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	if (num == 2 && !(msg[0].flags & I2C_M_RD) &&
+		(msg[1].flags & I2C_M_RD)) {
+		if (msg[0].len > 24 || msg[1].len > 24) {
+			/* TODO: check msg[0].len max */
+			ret = -EOPNOTSUPP;
+			goto err_mutex_unlock;
+		} else if (msg[0].addr == 0x10) {
+			/* method 1 - integrated demod */
+			if (msg[0].buf[0] == 0x00) {
+				/* return demod page from driver cache */
+				msg[1].buf[0] = dev->page;
+				ret = 0;
+			} else {
+				req.value = (msg[0].buf[0] << 8) | (msg[0].addr << 1);
+				req.index = CMD_DEMOD_RD | dev->page;
+				req.size = msg[1].len;
+				req.data = &msg[1].buf[0];
+				ret = rtl28xxu_ctrl_msg(d, &req);
+			}
+		} else if (msg[0].len < 2) {
+			/* method 2 - old I2C */
+			req.value = (msg[0].buf[0] << 8) | (msg[0].addr << 1);
+			req.index = CMD_I2C_RD;
+			req.size = msg[1].len;
+			req.data = &msg[1].buf[0];
+			ret = rtl28xxu_ctrl_msg(d, &req);
+		} else {
+			/* method 3 - new I2C */
+			req.value = (msg[0].addr << 1);
+			req.index = CMD_I2C_DA_WR;
+			req.size = msg[0].len;
+			req.data = msg[0].buf;
+			ret = rtl28xxu_ctrl_msg(d, &req);
+			if (ret)
+				goto err_mutex_unlock;
+
+			req.value = (msg[0].addr << 1);
+			req.index = CMD_I2C_DA_RD;
+			req.size = msg[1].len;
+			req.data = msg[1].buf;
+			ret = rtl28xxu_ctrl_msg(d, &req);
+		}
+	} else if (num == 1 && !(msg[0].flags & I2C_M_RD)) {
+		if (msg[0].len > 22) {
+			/* TODO: check msg[0].len max */
+			ret = -EOPNOTSUPP;
+			goto err_mutex_unlock;
+		} else if (msg[0].addr == 0x10) {
+			/* method 1 - integrated demod */
+			if (msg[0].buf[0] == 0x00) {
+				/* save demod page for later demod access */
+				dev->page = msg[0].buf[1];
+				ret = 0;
+			} else {
+				req.value = (msg[0].buf[0] << 8) |
+					(msg[0].addr << 1);
+				req.index = CMD_DEMOD_WR | dev->page;
+				req.size = msg[0].len-1;
+				req.data = &msg[0].buf[1];
+				ret = rtl28xxu_ctrl_msg(d, &req);
+			}
+		} else if ((msg[0].len < 23) && (!dev->new_i2c_write)) {
+			/* method 2 - old I2C */
+			req.value = (msg[0].buf[0] << 8) | (msg[0].addr << 1);
+			req.index = CMD_I2C_WR;
+			req.size = msg[0].len-1;
+			req.data = &msg[0].buf[1];
+			ret = rtl28xxu_ctrl_msg(d, &req);
+		} else {
+			/* method 3 - new I2C */
+			req.value = (msg[0].addr << 1);
+			req.index = CMD_I2C_DA_WR;
+			req.size = msg[0].len;
+			req.data = msg[0].buf;
+			ret = rtl28xxu_ctrl_msg(d, &req);
+		}
+	} else if (num == 1 && (msg[0].flags & I2C_M_RD)) {
+		req.value = (msg[0].addr << 1);
+		req.index = CMD_I2C_DA_RD;
+		req.size = msg[0].len;
+		req.data = msg[0].buf;
+		ret = rtl28xxu_ctrl_msg(d, &req);
+	} else {
+		ret = -EOPNOTSUPP;
+	}
+
+	/* Retry failed I2C messages */
+	if (ret == -EPIPE)
+		ret = -EAGAIN;
+
+err_mutex_unlock:
+	mutex_unlock(&d->i2c_mutex);
+
+	return ret ? ret : num;
+}
+
+static u32 rtl28xxu_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm rtl28xxu_i2c_algo = {
+	.master_xfer   = rtl28xxu_i2c_xfer,
+	.functionality = rtl28xxu_i2c_func,
+};
+
+static int rtl2831u_read_config(struct dvb_usb_device *d)
+{
+	struct rtl28xxu_dev *dev = d_to_priv(d);
+	int ret;
+	u8 buf[1];
+	/* open RTL2831U/RTL2830 I2C gate */
+	struct rtl28xxu_req req_gate_open = {0x0120, 0x0011, 0x0001, "\x08"};
+	/* tuner probes */
+	struct rtl28xxu_req req_mt2060 = {0x00c0, CMD_I2C_RD, 1, buf};
+	struct rtl28xxu_req req_qt1010 = {0x0fc4, CMD_I2C_RD, 1, buf};
+
+	dev_dbg(&d->intf->dev, "\n");
+
+	/*
+	 * RTL2831U GPIOs
+	 * =========================================================
+	 * GPIO0 | tuner#0 | 0 off | 1 on  | MXL5005S (?)
+	 * GPIO2 | LED     | 0 off | 1 on  |
+	 * GPIO4 | tuner#1 | 0 on  | 1 off | MT2060
+	 */
+
+	/* GPIO direction */
+	ret = rtl28xxu_wr_reg(d, SYS_GPIO_DIR, 0x0a);
+	if (ret)
+		goto err;
+
+	/* enable as output GPIO0, GPIO2, GPIO4 */
+	ret = rtl28xxu_wr_reg(d, SYS_GPIO_OUT_EN, 0x15);
+	if (ret)
+		goto err;
+
+	/*
+	 * Probe used tuner. We need to know used tuner before demod attach
+	 * since there is some demod params needed to set according to tuner.
+	 */
+
+	/* demod needs some time to wake up */
+	msleep(20);
+
+	dev->tuner_name = "NONE";
+
+	/* open demod I2C gate */
+	ret = rtl28xxu_ctrl_msg(d, &req_gate_open);
+	if (ret)
+		goto err;
+
+	/* check QT1010 ID(?) register; reg=0f val=2c */
+	ret = rtl28xxu_ctrl_msg(d, &req_qt1010);
+	if (ret == 0 && buf[0] == 0x2c) {
+		dev->tuner = TUNER_RTL2830_QT1010;
+		dev->tuner_name = "QT1010";
+		goto found;
+	}
+
+	/* open demod I2C gate */
+	ret = rtl28xxu_ctrl_msg(d, &req_gate_open);
+	if (ret)
+		goto err;
+
+	/* check MT2060 ID register; reg=00 val=63 */
+	ret = rtl28xxu_ctrl_msg(d, &req_mt2060);
+	if (ret == 0 && buf[0] == 0x63) {
+		dev->tuner = TUNER_RTL2830_MT2060;
+		dev->tuner_name = "MT2060";
+		goto found;
+	}
+
+	/* assume MXL5005S */
+	dev->tuner = TUNER_RTL2830_MXL5005S;
+	dev->tuner_name = "MXL5005S";
+	goto found;
+
+found:
+	dev_dbg(&d->intf->dev, "tuner=%s\n", dev->tuner_name);
+
+	return 0;
+err:
+	dev_dbg(&d->intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static int rtl2832u_read_config(struct dvb_usb_device *d)
+{
+	struct rtl28xxu_dev *dev = d_to_priv(d);
+	int ret;
+	u8 buf[2];
+	/* open RTL2832U/RTL2832 I2C gate */
+	struct rtl28xxu_req req_gate_open = {0x0120, 0x0011, 0x0001, "\x18"};
+	/* close RTL2832U/RTL2832 I2C gate */
+	struct rtl28xxu_req req_gate_close = {0x0120, 0x0011, 0x0001, "\x10"};
+	/* tuner probes */
+	struct rtl28xxu_req req_fc0012 = {0x00c6, CMD_I2C_RD, 1, buf};
+	struct rtl28xxu_req req_fc0013 = {0x00c6, CMD_I2C_RD, 1, buf};
+	struct rtl28xxu_req req_mt2266 = {0x00c0, CMD_I2C_RD, 1, buf};
+	struct rtl28xxu_req req_fc2580 = {0x01ac, CMD_I2C_RD, 1, buf};
+	struct rtl28xxu_req req_mt2063 = {0x00c0, CMD_I2C_RD, 1, buf};
+	struct rtl28xxu_req req_max3543 = {0x00c0, CMD_I2C_RD, 1, buf};
+	struct rtl28xxu_req req_tua9001 = {0x7ec0, CMD_I2C_RD, 2, buf};
+	struct rtl28xxu_req req_mxl5007t = {0xd9c0, CMD_I2C_RD, 1, buf};
+	struct rtl28xxu_req req_e4000 = {0x02c8, CMD_I2C_RD, 1, buf};
+	struct rtl28xxu_req req_tda18272 = {0x00c0, CMD_I2C_RD, 2, buf};
+	struct rtl28xxu_req req_r820t = {0x0034, CMD_I2C_RD, 1, buf};
+	struct rtl28xxu_req req_r828d = {0x0074, CMD_I2C_RD, 1, buf};
+	struct rtl28xxu_req req_mn88472 = {0xff38, CMD_I2C_RD, 1, buf};
+	struct rtl28xxu_req req_mn88473 = {0xff38, CMD_I2C_RD, 1, buf};
+	struct rtl28xxu_req req_si2157 = {0x00c0, CMD_I2C_RD, 1, buf};
+	struct rtl28xxu_req req_si2168 = {0x00c8, CMD_I2C_RD, 1, buf};
+
+	dev_dbg(&d->intf->dev, "\n");
+
+	/* enable GPIO3 and GPIO6 as output */
+	ret = rtl28xxu_wr_reg_mask(d, SYS_GPIO_DIR, 0x00, 0x40);
+	if (ret)
+		goto err;
+
+	ret = rtl28xxu_wr_reg_mask(d, SYS_GPIO_OUT_EN, 0x48, 0x48);
+	if (ret)
+		goto err;
+
+	/*
+	 * Probe used tuner. We need to know used tuner before demod attach
+	 * since there is some demod params needed to set according to tuner.
+	 */
+
+	/* open demod I2C gate */
+	ret = rtl28xxu_ctrl_msg(d, &req_gate_open);
+	if (ret)
+		goto err;
+
+	dev->tuner_name = "NONE";
+
+	/* check FC0012 ID register; reg=00 val=a1 */
+	ret = rtl28xxu_ctrl_msg(d, &req_fc0012);
+	if (ret == 0 && buf[0] == 0xa1) {
+		dev->tuner = TUNER_RTL2832_FC0012;
+		dev->tuner_name = "FC0012";
+		goto tuner_found;
+	}
+
+	/* check FC0013 ID register; reg=00 val=a3 */
+	ret = rtl28xxu_ctrl_msg(d, &req_fc0013);
+	if (ret == 0 && buf[0] == 0xa3) {
+		dev->tuner = TUNER_RTL2832_FC0013;
+		dev->tuner_name = "FC0013";
+		goto tuner_found;
+	}
+
+	/* check MT2266 ID register; reg=00 val=85 */
+	ret = rtl28xxu_ctrl_msg(d, &req_mt2266);
+	if (ret == 0 && buf[0] == 0x85) {
+		dev->tuner = TUNER_RTL2832_MT2266;
+		dev->tuner_name = "MT2266";
+		goto tuner_found;
+	}
+
+	/* check FC2580 ID register; reg=01 val=56 */
+	ret = rtl28xxu_ctrl_msg(d, &req_fc2580);
+	if (ret == 0 && buf[0] == 0x56) {
+		dev->tuner = TUNER_RTL2832_FC2580;
+		dev->tuner_name = "FC2580";
+		goto tuner_found;
+	}
+
+	/* check MT2063 ID register; reg=00 val=9e || 9c */
+	ret = rtl28xxu_ctrl_msg(d, &req_mt2063);
+	if (ret == 0 && (buf[0] == 0x9e || buf[0] == 0x9c)) {
+		dev->tuner = TUNER_RTL2832_MT2063;
+		dev->tuner_name = "MT2063";
+		goto tuner_found;
+	}
+
+	/* check MAX3543 ID register; reg=00 val=38 */
+	ret = rtl28xxu_ctrl_msg(d, &req_max3543);
+	if (ret == 0 && buf[0] == 0x38) {
+		dev->tuner = TUNER_RTL2832_MAX3543;
+		dev->tuner_name = "MAX3543";
+		goto tuner_found;
+	}
+
+	/* check TUA9001 ID register; reg=7e val=2328 */
+	ret = rtl28xxu_ctrl_msg(d, &req_tua9001);
+	if (ret == 0 && buf[0] == 0x23 && buf[1] == 0x28) {
+		dev->tuner = TUNER_RTL2832_TUA9001;
+		dev->tuner_name = "TUA9001";
+		goto tuner_found;
+	}
+
+	/* check MXL5007R ID register; reg=d9 val=14 */
+	ret = rtl28xxu_ctrl_msg(d, &req_mxl5007t);
+	if (ret == 0 && buf[0] == 0x14) {
+		dev->tuner = TUNER_RTL2832_MXL5007T;
+		dev->tuner_name = "MXL5007T";
+		goto tuner_found;
+	}
+
+	/* check E4000 ID register; reg=02 val=40 */
+	ret = rtl28xxu_ctrl_msg(d, &req_e4000);
+	if (ret == 0 && buf[0] == 0x40) {
+		dev->tuner = TUNER_RTL2832_E4000;
+		dev->tuner_name = "E4000";
+		goto tuner_found;
+	}
+
+	/* check TDA18272 ID register; reg=00 val=c760  */
+	ret = rtl28xxu_ctrl_msg(d, &req_tda18272);
+	if (ret == 0 && (buf[0] == 0xc7 || buf[1] == 0x60)) {
+		dev->tuner = TUNER_RTL2832_TDA18272;
+		dev->tuner_name = "TDA18272";
+		goto tuner_found;
+	}
+
+	/* check R820T ID register; reg=00 val=69 */
+	ret = rtl28xxu_ctrl_msg(d, &req_r820t);
+	if (ret == 0 && buf[0] == 0x69) {
+		dev->tuner = TUNER_RTL2832_R820T;
+		dev->tuner_name = "R820T";
+		goto tuner_found;
+	}
+
+	/* check R828D ID register; reg=00 val=69 */
+	ret = rtl28xxu_ctrl_msg(d, &req_r828d);
+	if (ret == 0 && buf[0] == 0x69) {
+		dev->tuner = TUNER_RTL2832_R828D;
+		dev->tuner_name = "R828D";
+		goto tuner_found;
+	}
+
+	/* GPIO0 and GPIO5 to reset Si2157/Si2168 tuner and demod */
+	ret = rtl28xxu_wr_reg_mask(d, SYS_GPIO_OUT_VAL, 0x00, 0x21);
+	if (ret)
+		goto err;
+
+	ret = rtl28xxu_wr_reg_mask(d, SYS_GPIO_OUT_EN, 0x00, 0x21);
+	if (ret)
+		goto err;
+
+	msleep(50);
+
+	ret = rtl28xxu_wr_reg_mask(d, SYS_GPIO_OUT_VAL, 0x21, 0x21);
+	if (ret)
+		goto err;
+
+	ret = rtl28xxu_wr_reg_mask(d, SYS_GPIO_OUT_EN, 0x21, 0x21);
+	if (ret)
+		goto err;
+
+	msleep(50);
+
+	/* check Si2157 ID register; reg=c0 val=80 */
+	ret = rtl28xxu_ctrl_msg(d, &req_si2157);
+	if (ret == 0 && ((buf[0] & 0x80) == 0x80)) {
+		dev->tuner = TUNER_RTL2832_SI2157;
+		dev->tuner_name = "SI2157";
+		goto tuner_found;
+	}
+
+tuner_found:
+	dev_dbg(&d->intf->dev, "tuner=%s\n", dev->tuner_name);
+
+	/* probe slave demod */
+	if (dev->tuner == TUNER_RTL2832_R828D) {
+		/* power on MN88472 demod on GPIO0 */
+		ret = rtl28xxu_wr_reg_mask(d, SYS_GPIO_OUT_VAL, 0x01, 0x01);
+		if (ret)
+			goto err;
+
+		ret = rtl28xxu_wr_reg_mask(d, SYS_GPIO_DIR, 0x00, 0x01);
+		if (ret)
+			goto err;
+
+		ret = rtl28xxu_wr_reg_mask(d, SYS_GPIO_OUT_EN, 0x01, 0x01);
+		if (ret)
+			goto err;
+
+		/* check MN88472 answers */
+		ret = rtl28xxu_ctrl_msg(d, &req_mn88472);
+		if (ret == 0 && buf[0] == 0x02) {
+			dev_dbg(&d->intf->dev, "MN88472 found\n");
+			dev->slave_demod = SLAVE_DEMOD_MN88472;
+			goto demod_found;
+		}
+
+		ret = rtl28xxu_ctrl_msg(d, &req_mn88473);
+		if (ret == 0 && buf[0] == 0x03) {
+			dev_dbg(&d->intf->dev, "MN88473 found\n");
+			dev->slave_demod = SLAVE_DEMOD_MN88473;
+			goto demod_found;
+		}
+	}
+	if (dev->tuner == TUNER_RTL2832_SI2157) {
+		/* check Si2168 ID register; reg=c8 val=80 */
+		ret = rtl28xxu_ctrl_msg(d, &req_si2168);
+		if (ret == 0 && ((buf[0] & 0x80) == 0x80)) {
+			dev_dbg(&d->intf->dev, "Si2168 found\n");
+			dev->slave_demod = SLAVE_DEMOD_SI2168;
+			goto demod_found;
+		}
+	}
+
+demod_found:
+	/* close demod I2C gate */
+	ret = rtl28xxu_ctrl_msg(d, &req_gate_close);
+	if (ret < 0)
+		goto err;
+
+	return 0;
+err:
+	dev_dbg(&d->intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static int rtl28xxu_read_config(struct dvb_usb_device *d)
+{
+	struct rtl28xxu_dev *dev = d_to_priv(d);
+
+	if (dev->chip_id == CHIP_ID_RTL2831U)
+		return rtl2831u_read_config(d);
+	else
+		return rtl2832u_read_config(d);
+}
+
+static int rtl28xxu_identify_state(struct dvb_usb_device *d, const char **name)
+{
+	struct rtl28xxu_dev *dev = d_to_priv(d);
+	int ret;
+	struct rtl28xxu_req req_demod_i2c = {0x0020, CMD_I2C_DA_RD, 0, NULL};
+
+	dev_dbg(&d->intf->dev, "\n");
+
+	/*
+	 * Detect chip type using I2C command that is not supported
+	 * by old RTL2831U.
+	 */
+	ret = rtl28xxu_ctrl_msg(d, &req_demod_i2c);
+	if (ret == -EPIPE) {
+		dev->chip_id = CHIP_ID_RTL2831U;
+	} else if (ret == 0) {
+		dev->chip_id = CHIP_ID_RTL2832U;
+	} else {
+		dev_err(&d->intf->dev, "chip type detection failed %d\n", ret);
+		goto err;
+	}
+	dev_dbg(&d->intf->dev, "chip_id=%u\n", dev->chip_id);
+
+	/* Retry failed I2C messages */
+	d->i2c_adap.retries = 3;
+	d->i2c_adap.timeout = msecs_to_jiffies(10);
+
+	return WARM;
+err:
+	dev_dbg(&d->intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static const struct rtl2830_platform_data rtl2830_mt2060_platform_data = {
+	.clk = 28800000,
+	.spec_inv = 1,
+	.vtop = 0x20,
+	.krf = 0x04,
+	.agc_targ_val = 0x2d,
+
+};
+
+static const struct rtl2830_platform_data rtl2830_qt1010_platform_data = {
+	.clk = 28800000,
+	.spec_inv = 1,
+	.vtop = 0x20,
+	.krf = 0x04,
+	.agc_targ_val = 0x2d,
+};
+
+static const struct rtl2830_platform_data rtl2830_mxl5005s_platform_data = {
+	.clk = 28800000,
+	.spec_inv = 0,
+	.vtop = 0x3f,
+	.krf = 0x04,
+	.agc_targ_val = 0x3e,
+};
+
+static int rtl2831u_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct rtl28xxu_dev *dev = d_to_priv(d);
+	struct rtl2830_platform_data *pdata = &dev->rtl2830_platform_data;
+	struct i2c_board_info board_info;
+	struct i2c_client *client;
+	int ret;
+
+	dev_dbg(&d->intf->dev, "\n");
+
+	switch (dev->tuner) {
+	case TUNER_RTL2830_QT1010:
+		*pdata = rtl2830_qt1010_platform_data;
+		break;
+	case TUNER_RTL2830_MT2060:
+		*pdata = rtl2830_mt2060_platform_data;
+		break;
+	case TUNER_RTL2830_MXL5005S:
+		*pdata = rtl2830_mxl5005s_platform_data;
+		break;
+	default:
+		dev_err(&d->intf->dev, "unknown tuner %s\n", dev->tuner_name);
+		ret = -ENODEV;
+		goto err;
+	}
+
+	/* attach demodulator */
+	memset(&board_info, 0, sizeof(board_info));
+	strlcpy(board_info.type, "rtl2830", I2C_NAME_SIZE);
+	board_info.addr = 0x10;
+	board_info.platform_data = pdata;
+	request_module("%s", board_info.type);
+	client = i2c_new_device(&d->i2c_adap, &board_info);
+	if (client == NULL || client->dev.driver == NULL) {
+		ret = -ENODEV;
+		goto err;
+	}
+
+	if (!try_module_get(client->dev.driver->owner)) {
+		i2c_unregister_device(client);
+		ret = -ENODEV;
+		goto err;
+	}
+
+	adap->fe[0] = pdata->get_dvb_frontend(client);
+	dev->demod_i2c_adapter = pdata->get_i2c_adapter(client);
+
+	dev->i2c_client_demod = client;
+
+	return 0;
+err:
+	dev_dbg(&d->intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static const struct rtl2832_platform_data rtl2832_fc2580_platform_data = {
+	.clk = 28800000,
+	.tuner = TUNER_RTL2832_FC2580,
+};
+
+static const struct rtl2832_platform_data rtl2832_fc0012_platform_data = {
+	.clk = 28800000,
+	.tuner = TUNER_RTL2832_FC0012
+};
+
+static const struct rtl2832_platform_data rtl2832_fc0013_platform_data = {
+	.clk = 28800000,
+	.tuner = TUNER_RTL2832_FC0013
+};
+
+static const struct rtl2832_platform_data rtl2832_tua9001_platform_data = {
+	.clk = 28800000,
+	.tuner = TUNER_RTL2832_TUA9001,
+};
+
+static const struct rtl2832_platform_data rtl2832_e4000_platform_data = {
+	.clk = 28800000,
+	.tuner = TUNER_RTL2832_E4000,
+};
+
+static const struct rtl2832_platform_data rtl2832_r820t_platform_data = {
+	.clk = 28800000,
+	.tuner = TUNER_RTL2832_R820T,
+};
+
+static const struct rtl2832_platform_data rtl2832_si2157_platform_data = {
+	.clk = 28800000,
+	.tuner = TUNER_RTL2832_SI2157,
+};
+
+static int rtl2832u_fc0012_tuner_callback(struct dvb_usb_device *d,
+		int cmd, int arg)
+{
+	int ret;
+	u8 val;
+
+	dev_dbg(&d->intf->dev, "cmd=%d arg=%d\n", cmd, arg);
+
+	switch (cmd) {
+	case FC_FE_CALLBACK_VHF_ENABLE:
+		/* set output values */
+		ret = rtl28xxu_rd_reg(d, SYS_GPIO_OUT_VAL, &val);
+		if (ret)
+			goto err;
+
+		if (arg)
+			val &= 0xbf; /* set GPIO6 low */
+		else
+			val |= 0x40; /* set GPIO6 high */
+
+
+		ret = rtl28xxu_wr_reg(d, SYS_GPIO_OUT_VAL, val);
+		if (ret)
+			goto err;
+		break;
+	default:
+		ret = -EINVAL;
+		goto err;
+	}
+	return 0;
+err:
+	dev_dbg(&d->intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static int rtl2832u_tua9001_tuner_callback(struct dvb_usb_device *d,
+		int cmd, int arg)
+{
+	int ret;
+	u8 val;
+
+	dev_dbg(&d->intf->dev, "cmd=%d arg=%d\n", cmd, arg);
+
+	/*
+	 * CEN     always enabled by hardware wiring
+	 * RESETN  GPIO4
+	 * RXEN    GPIO1
+	 */
+
+	switch (cmd) {
+	case TUA9001_CMD_RESETN:
+		if (arg)
+			val = (1 << 4);
+		else
+			val = (0 << 4);
+
+		ret = rtl28xxu_wr_reg_mask(d, SYS_GPIO_OUT_VAL, val, 0x10);
+		if (ret)
+			goto err;
+		break;
+	case TUA9001_CMD_RXEN:
+		if (arg)
+			val = (1 << 1);
+		else
+			val = (0 << 1);
+
+		ret = rtl28xxu_wr_reg_mask(d, SYS_GPIO_OUT_VAL, val, 0x02);
+		if (ret)
+			goto err;
+		break;
+	}
+
+	return 0;
+err:
+	dev_dbg(&d->intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static int rtl2832u_frontend_callback(void *adapter_priv, int component,
+		int cmd, int arg)
+{
+	struct i2c_adapter *adapter = adapter_priv;
+	struct device *parent = adapter->dev.parent;
+	struct i2c_adapter *parent_adapter;
+	struct dvb_usb_device *d;
+	struct rtl28xxu_dev *dev;
+
+	/*
+	 * All tuners are connected to demod muxed I2C adapter. We have to
+	 * resolve its parent adapter in order to get handle for this driver
+	 * private data. That is a bit hackish solution, GPIO or direct driver
+	 * callback would be better...
+	 */
+	if (parent != NULL && parent->type == &i2c_adapter_type)
+		parent_adapter = to_i2c_adapter(parent);
+	else
+		return -EINVAL;
+
+	d = i2c_get_adapdata(parent_adapter);
+	dev = d->priv;
+
+	dev_dbg(&d->intf->dev, "component=%d cmd=%d arg=%d\n",
+		component, cmd, arg);
+
+	switch (component) {
+	case DVB_FRONTEND_COMPONENT_TUNER:
+		switch (dev->tuner) {
+		case TUNER_RTL2832_FC0012:
+			return rtl2832u_fc0012_tuner_callback(d, cmd, arg);
+		case TUNER_RTL2832_TUA9001:
+			return rtl2832u_tua9001_tuner_callback(d, cmd, arg);
+		}
+	}
+
+	return 0;
+}
+
+static int rtl2832u_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct rtl28xxu_dev *dev = d_to_priv(d);
+	struct rtl2832_platform_data *pdata = &dev->rtl2832_platform_data;
+	struct i2c_board_info board_info;
+	struct i2c_client *client;
+	int ret;
+
+	dev_dbg(&d->intf->dev, "\n");
+
+	switch (dev->tuner) {
+	case TUNER_RTL2832_FC0012:
+		*pdata = rtl2832_fc0012_platform_data;
+		break;
+	case TUNER_RTL2832_FC0013:
+		*pdata = rtl2832_fc0013_platform_data;
+		break;
+	case TUNER_RTL2832_FC2580:
+		*pdata = rtl2832_fc2580_platform_data;
+		break;
+	case TUNER_RTL2832_TUA9001:
+		*pdata = rtl2832_tua9001_platform_data;
+		break;
+	case TUNER_RTL2832_E4000:
+		*pdata = rtl2832_e4000_platform_data;
+		break;
+	case TUNER_RTL2832_R820T:
+	case TUNER_RTL2832_R828D:
+		*pdata = rtl2832_r820t_platform_data;
+		break;
+	case TUNER_RTL2832_SI2157:
+		*pdata = rtl2832_si2157_platform_data;
+		break;
+	default:
+		dev_err(&d->intf->dev, "unknown tuner %s\n", dev->tuner_name);
+		ret = -ENODEV;
+		goto err;
+	}
+
+	/* attach demodulator */
+	memset(&board_info, 0, sizeof(board_info));
+	strlcpy(board_info.type, "rtl2832", I2C_NAME_SIZE);
+	board_info.addr = 0x10;
+	board_info.platform_data = pdata;
+	request_module("%s", board_info.type);
+	client = i2c_new_device(&d->i2c_adap, &board_info);
+	if (client == NULL || client->dev.driver == NULL) {
+		ret = -ENODEV;
+		goto err;
+	}
+
+	if (!try_module_get(client->dev.driver->owner)) {
+		i2c_unregister_device(client);
+		ret = -ENODEV;
+		goto err;
+	}
+
+	adap->fe[0] = pdata->get_dvb_frontend(client);
+	dev->demod_i2c_adapter = pdata->get_i2c_adapter(client);
+
+	dev->i2c_client_demod = client;
+
+	/* set fe callback */
+	adap->fe[0]->callback = rtl2832u_frontend_callback;
+
+	if (dev->slave_demod) {
+		struct i2c_board_info info = {};
+
+		/*
+		 * We continue on reduced mode, without DVB-T2/C, using master
+		 * demod, when slave demod fails.
+		 */
+		ret = 0;
+
+		/* attach slave demodulator */
+		if (dev->slave_demod == SLAVE_DEMOD_MN88472) {
+			struct mn88472_config mn88472_config = {};
+
+			mn88472_config.fe = &adap->fe[1];
+			mn88472_config.i2c_wr_max = 22,
+			strlcpy(info.type, "mn88472", I2C_NAME_SIZE);
+			mn88472_config.xtal = 20500000;
+			mn88472_config.ts_mode = SERIAL_TS_MODE;
+			mn88472_config.ts_clock = VARIABLE_TS_CLOCK;
+			info.addr = 0x18;
+			info.platform_data = &mn88472_config;
+			request_module(info.type);
+			client = i2c_new_device(&d->i2c_adap, &info);
+			if (client == NULL || client->dev.driver == NULL) {
+				dev->slave_demod = SLAVE_DEMOD_NONE;
+				goto err_slave_demod_failed;
+			}
+
+			if (!try_module_get(client->dev.driver->owner)) {
+				i2c_unregister_device(client);
+				dev->slave_demod = SLAVE_DEMOD_NONE;
+				goto err_slave_demod_failed;
+			}
+
+			dev->i2c_client_slave_demod = client;
+		} else if (dev->slave_demod == SLAVE_DEMOD_MN88473) {
+			struct mn88473_config mn88473_config = {};
+
+			mn88473_config.fe = &adap->fe[1];
+			mn88473_config.i2c_wr_max = 22,
+			strlcpy(info.type, "mn88473", I2C_NAME_SIZE);
+			info.addr = 0x18;
+			info.platform_data = &mn88473_config;
+			request_module(info.type);
+			client = i2c_new_device(&d->i2c_adap, &info);
+			if (client == NULL || client->dev.driver == NULL) {
+				dev->slave_demod = SLAVE_DEMOD_NONE;
+				goto err_slave_demod_failed;
+			}
+
+			if (!try_module_get(client->dev.driver->owner)) {
+				i2c_unregister_device(client);
+				dev->slave_demod = SLAVE_DEMOD_NONE;
+				goto err_slave_demod_failed;
+			}
+
+			dev->i2c_client_slave_demod = client;
+		} else {
+			struct si2168_config si2168_config = {};
+			struct i2c_adapter *adapter;
+
+			si2168_config.i2c_adapter = &adapter;
+			si2168_config.fe = &adap->fe[1];
+			si2168_config.ts_mode = SI2168_TS_SERIAL;
+			si2168_config.ts_clock_inv = false;
+			si2168_config.ts_clock_gapped = true;
+			strlcpy(info.type, "si2168", I2C_NAME_SIZE);
+			info.addr = 0x64;
+			info.platform_data = &si2168_config;
+			request_module(info.type);
+			client = i2c_new_device(&d->i2c_adap, &info);
+			if (client == NULL || client->dev.driver == NULL) {
+				dev->slave_demod = SLAVE_DEMOD_NONE;
+				goto err_slave_demod_failed;
+			}
+
+			if (!try_module_get(client->dev.driver->owner)) {
+				i2c_unregister_device(client);
+				dev->slave_demod = SLAVE_DEMOD_NONE;
+				goto err_slave_demod_failed;
+			}
+
+			dev->i2c_client_slave_demod = client;
+
+			/* for Si2168 devices use only new I2C write method */
+			dev->new_i2c_write = true;
+		}
+	}
+	return 0;
+err_slave_demod_failed:
+err:
+	dev_dbg(&d->intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static int rtl28xxu_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct rtl28xxu_dev *dev = adap_to_priv(adap);
+
+	if (dev->chip_id == CHIP_ID_RTL2831U)
+		return rtl2831u_frontend_attach(adap);
+	else
+		return rtl2832u_frontend_attach(adap);
+}
+
+static int rtl28xxu_frontend_detach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct rtl28xxu_dev *dev = d_to_priv(d);
+	struct i2c_client *client;
+
+	dev_dbg(&d->intf->dev, "\n");
+
+	/* remove I2C slave demod */
+	client = dev->i2c_client_slave_demod;
+	if (client) {
+		module_put(client->dev.driver->owner);
+		i2c_unregister_device(client);
+	}
+
+	/* remove I2C demod */
+	client = dev->i2c_client_demod;
+	if (client) {
+		module_put(client->dev.driver->owner);
+		i2c_unregister_device(client);
+	}
+
+	return 0;
+}
+
+static struct qt1010_config rtl28xxu_qt1010_config = {
+	.i2c_address = 0x62, /* 0xc4 */
+};
+
+static struct mt2060_config rtl28xxu_mt2060_config = {
+	.i2c_address = 0x60, /* 0xc0 */
+	.clock_out = 0,
+};
+
+static struct mxl5005s_config rtl28xxu_mxl5005s_config = {
+	.i2c_address     = 0x63, /* 0xc6 */
+	.if_freq         = IF_FREQ_4570000HZ,
+	.xtal_freq       = CRYSTAL_FREQ_16000000HZ,
+	.agc_mode        = MXL_SINGLE_AGC,
+	.tracking_filter = MXL_TF_C_H,
+	.rssi_enable     = MXL_RSSI_ENABLE,
+	.cap_select      = MXL_CAP_SEL_ENABLE,
+	.div_out         = MXL_DIV_OUT_4,
+	.clock_out       = MXL_CLOCK_OUT_DISABLE,
+	.output_load     = MXL5005S_IF_OUTPUT_LOAD_200_OHM,
+	.top		 = MXL5005S_TOP_25P2,
+	.mod_mode        = MXL_DIGITAL_MODE,
+	.if_mode         = MXL_ZERO_IF,
+	.AgcMasterByte   = 0x00,
+};
+
+static int rtl2831u_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	int ret;
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct rtl28xxu_dev *dev = d_to_priv(d);
+	struct dvb_frontend *fe;
+
+	dev_dbg(&d->intf->dev, "\n");
+
+	switch (dev->tuner) {
+	case TUNER_RTL2830_QT1010:
+		fe = dvb_attach(qt1010_attach, adap->fe[0],
+				dev->demod_i2c_adapter,
+				&rtl28xxu_qt1010_config);
+		break;
+	case TUNER_RTL2830_MT2060:
+		fe = dvb_attach(mt2060_attach, adap->fe[0],
+				dev->demod_i2c_adapter,
+				&rtl28xxu_mt2060_config, 1220);
+		break;
+	case TUNER_RTL2830_MXL5005S:
+		fe = dvb_attach(mxl5005s_attach, adap->fe[0],
+				dev->demod_i2c_adapter,
+				&rtl28xxu_mxl5005s_config);
+		break;
+	default:
+		fe = NULL;
+		dev_err(&d->intf->dev, "unknown tuner %d\n", dev->tuner);
+	}
+
+	if (fe == NULL) {
+		ret = -ENODEV;
+		goto err;
+	}
+
+	return 0;
+err:
+	dev_dbg(&d->intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static const struct fc0012_config rtl2832u_fc0012_config = {
+	.i2c_address = 0x63, /* 0xc6 >> 1 */
+	.xtal_freq = FC_XTAL_28_8_MHZ,
+};
+
+static const struct r820t_config rtl2832u_r820t_config = {
+	.i2c_addr = 0x1a,
+	.xtal = 28800000,
+	.max_i2c_msg_len = 2,
+	.rafael_chip = CHIP_R820T,
+};
+
+static const struct r820t_config rtl2832u_r828d_config = {
+	.i2c_addr = 0x3a,
+	.xtal = 16000000,
+	.max_i2c_msg_len = 2,
+	.rafael_chip = CHIP_R828D,
+};
+
+static int rtl2832u_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	int ret;
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct rtl28xxu_dev *dev = d_to_priv(d);
+	struct dvb_frontend *fe = NULL;
+	struct i2c_board_info info;
+	struct i2c_client *client;
+	struct v4l2_subdev *subdev = NULL;
+	struct platform_device *pdev;
+	struct rtl2832_sdr_platform_data pdata;
+
+	dev_dbg(&d->intf->dev, "\n");
+
+	memset(&info, 0, sizeof(struct i2c_board_info));
+	memset(&pdata, 0, sizeof(pdata));
+
+	switch (dev->tuner) {
+	case TUNER_RTL2832_FC0012:
+		fe = dvb_attach(fc0012_attach, adap->fe[0],
+			dev->demod_i2c_adapter, &rtl2832u_fc0012_config);
+
+		/* since fc0012 includs reading the signal strength delegate
+		 * that to the tuner driver */
+		adap->fe[0]->ops.read_signal_strength =
+				adap->fe[0]->ops.tuner_ops.get_rf_strength;
+		break;
+	case TUNER_RTL2832_FC0013:
+		fe = dvb_attach(fc0013_attach, adap->fe[0],
+			dev->demod_i2c_adapter, 0xc6>>1, 0, FC_XTAL_28_8_MHZ);
+
+		/* fc0013 also supports signal strength reading */
+		adap->fe[0]->ops.read_signal_strength =
+				adap->fe[0]->ops.tuner_ops.get_rf_strength;
+		break;
+	case TUNER_RTL2832_E4000: {
+			struct e4000_config e4000_config = {
+				.fe = adap->fe[0],
+				.clock = 28800000,
+			};
+
+			strlcpy(info.type, "e4000", I2C_NAME_SIZE);
+			info.addr = 0x64;
+			info.platform_data = &e4000_config;
+
+			request_module(info.type);
+			client = i2c_new_device(dev->demod_i2c_adapter, &info);
+			if (client == NULL || client->dev.driver == NULL)
+				break;
+
+			if (!try_module_get(client->dev.driver->owner)) {
+				i2c_unregister_device(client);
+				break;
+			}
+
+			dev->i2c_client_tuner = client;
+			subdev = i2c_get_clientdata(client);
+		}
+		break;
+	case TUNER_RTL2832_FC2580: {
+			struct fc2580_platform_data fc2580_pdata = {
+				.dvb_frontend = adap->fe[0],
+			};
+			struct i2c_board_info board_info = {};
+
+			strlcpy(board_info.type, "fc2580", I2C_NAME_SIZE);
+			board_info.addr = 0x56;
+			board_info.platform_data = &fc2580_pdata;
+			request_module("fc2580");
+			client = i2c_new_device(dev->demod_i2c_adapter,
+						&board_info);
+			if (client == NULL || client->dev.driver == NULL)
+				break;
+			if (!try_module_get(client->dev.driver->owner)) {
+				i2c_unregister_device(client);
+				break;
+			}
+			dev->i2c_client_tuner = client;
+			subdev = fc2580_pdata.get_v4l2_subdev(client);
+		}
+		break;
+	case TUNER_RTL2832_TUA9001: {
+		struct tua9001_platform_data tua9001_pdata = {
+			.dvb_frontend = adap->fe[0],
+		};
+		struct i2c_board_info board_info = {};
+
+		/* enable GPIO1 and GPIO4 as output */
+		ret = rtl28xxu_wr_reg_mask(d, SYS_GPIO_DIR, 0x00, 0x12);
+		if (ret)
+			goto err;
+
+		ret = rtl28xxu_wr_reg_mask(d, SYS_GPIO_OUT_EN, 0x12, 0x12);
+		if (ret)
+			goto err;
+
+		strlcpy(board_info.type, "tua9001", I2C_NAME_SIZE);
+		board_info.addr = 0x60;
+		board_info.platform_data = &tua9001_pdata;
+		request_module("tua9001");
+		client = i2c_new_device(dev->demod_i2c_adapter, &board_info);
+		if (client == NULL || client->dev.driver == NULL)
+			break;
+		if (!try_module_get(client->dev.driver->owner)) {
+			i2c_unregister_device(client);
+			break;
+		}
+		dev->i2c_client_tuner = client;
+		break;
+	}
+	case TUNER_RTL2832_R820T:
+		fe = dvb_attach(r820t_attach, adap->fe[0],
+				dev->demod_i2c_adapter,
+				&rtl2832u_r820t_config);
+
+		/* Use tuner to get the signal strength */
+		adap->fe[0]->ops.read_signal_strength =
+				adap->fe[0]->ops.tuner_ops.get_rf_strength;
+		break;
+	case TUNER_RTL2832_R828D:
+		fe = dvb_attach(r820t_attach, adap->fe[0],
+				dev->demod_i2c_adapter,
+				&rtl2832u_r828d_config);
+		adap->fe[0]->ops.read_signal_strength =
+				adap->fe[0]->ops.tuner_ops.get_rf_strength;
+
+		if (adap->fe[1]) {
+			fe = dvb_attach(r820t_attach, adap->fe[1],
+					dev->demod_i2c_adapter,
+					&rtl2832u_r828d_config);
+			adap->fe[1]->ops.read_signal_strength =
+					adap->fe[1]->ops.tuner_ops.get_rf_strength;
+		}
+		break;
+	case TUNER_RTL2832_SI2157: {
+			struct si2157_config si2157_config = {
+				.fe = adap->fe[0],
+				.if_port = 0,
+				.inversion = false,
+			};
+
+			strlcpy(info.type, "si2157", I2C_NAME_SIZE);
+			info.addr = 0x60;
+			info.platform_data = &si2157_config;
+			request_module(info.type);
+			client = i2c_new_device(&d->i2c_adap, &info);
+			if (client == NULL || client->dev.driver == NULL)
+				break;
+
+			if (!try_module_get(client->dev.driver->owner)) {
+				i2c_unregister_device(client);
+				break;
+			}
+
+			dev->i2c_client_tuner = client;
+			subdev = i2c_get_clientdata(client);
+
+			/* copy tuner ops for 2nd FE as tuner is shared */
+			if (adap->fe[1]) {
+				adap->fe[1]->tuner_priv =
+						adap->fe[0]->tuner_priv;
+				memcpy(&adap->fe[1]->ops.tuner_ops,
+						&adap->fe[0]->ops.tuner_ops,
+						sizeof(struct dvb_tuner_ops));
+			}
+		}
+		break;
+	default:
+		dev_err(&d->intf->dev, "unknown tuner %d\n", dev->tuner);
+	}
+	if (fe == NULL && dev->i2c_client_tuner == NULL) {
+		ret = -ENODEV;
+		goto err;
+	}
+
+	/* register SDR */
+	switch (dev->tuner) {
+	case TUNER_RTL2832_FC2580:
+	case TUNER_RTL2832_FC0012:
+	case TUNER_RTL2832_FC0013:
+	case TUNER_RTL2832_E4000:
+	case TUNER_RTL2832_R820T:
+	case TUNER_RTL2832_R828D:
+		pdata.clk = dev->rtl2832_platform_data.clk;
+		pdata.tuner = dev->tuner;
+		pdata.regmap = dev->rtl2832_platform_data.regmap;
+		pdata.dvb_frontend = adap->fe[0];
+		pdata.dvb_usb_device = d;
+		pdata.v4l2_subdev = subdev;
+
+		request_module("%s", "rtl2832_sdr");
+		pdev = platform_device_register_data(&d->intf->dev,
+						     "rtl2832_sdr",
+						     PLATFORM_DEVID_AUTO,
+						     &pdata, sizeof(pdata));
+		if (IS_ERR(pdev) || pdev->dev.driver == NULL)
+			break;
+		dev->platform_device_sdr = pdev;
+		break;
+	default:
+		dev_dbg(&d->intf->dev, "no SDR for tuner=%d\n", dev->tuner);
+	}
+
+	return 0;
+err:
+	dev_dbg(&d->intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static int rtl28xxu_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct rtl28xxu_dev *dev = adap_to_priv(adap);
+
+	if (dev->chip_id == CHIP_ID_RTL2831U)
+		return rtl2831u_tuner_attach(adap);
+	else
+		return rtl2832u_tuner_attach(adap);
+}
+
+static int rtl28xxu_tuner_detach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct rtl28xxu_dev *dev = d_to_priv(d);
+	struct i2c_client *client;
+	struct platform_device *pdev;
+
+	dev_dbg(&d->intf->dev, "\n");
+
+	/* remove platform SDR */
+	pdev = dev->platform_device_sdr;
+	if (pdev)
+		platform_device_unregister(pdev);
+
+	/* remove I2C tuner */
+	client = dev->i2c_client_tuner;
+	if (client) {
+		module_put(client->dev.driver->owner);
+		i2c_unregister_device(client);
+	}
+
+	return 0;
+}
+
+static int rtl28xxu_init(struct dvb_usb_device *d)
+{
+	int ret;
+	u8 val;
+
+	dev_dbg(&d->intf->dev, "\n");
+
+	/* init USB endpoints */
+	ret = rtl28xxu_rd_reg(d, USB_SYSCTL_0, &val);
+	if (ret)
+		goto err;
+
+	/* enable DMA and Full Packet Mode*/
+	val |= 0x09;
+	ret = rtl28xxu_wr_reg(d, USB_SYSCTL_0, val);
+	if (ret)
+		goto err;
+
+	/* set EPA maximum packet size to 0x0200 */
+	ret = rtl28xxu_wr_regs(d, USB_EPA_MAXPKT, "\x00\x02\x00\x00", 4);
+	if (ret)
+		goto err;
+
+	/* change EPA FIFO length */
+	ret = rtl28xxu_wr_regs(d, USB_EPA_FIFO_CFG, "\x14\x00\x00\x00", 4);
+	if (ret)
+		goto err;
+
+	return ret;
+err:
+	dev_dbg(&d->intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static int rtl2831u_power_ctrl(struct dvb_usb_device *d, int onoff)
+{
+	int ret;
+	u8 gpio, sys0, epa_ctl[2];
+
+	dev_dbg(&d->intf->dev, "onoff=%d\n", onoff);
+
+	/* demod adc */
+	ret = rtl28xxu_rd_reg(d, SYS_SYS0, &sys0);
+	if (ret)
+		goto err;
+
+	/* tuner power, read GPIOs */
+	ret = rtl28xxu_rd_reg(d, SYS_GPIO_OUT_VAL, &gpio);
+	if (ret)
+		goto err;
+
+	dev_dbg(&d->intf->dev, "RD SYS0=%02x GPIO_OUT_VAL=%02x\n", sys0, gpio);
+
+	if (onoff) {
+		gpio |= 0x01; /* GPIO0 = 1 */
+		gpio &= (~0x10); /* GPIO4 = 0 */
+		gpio |= 0x04; /* GPIO2 = 1, LED on */
+		sys0 = sys0 & 0x0f;
+		sys0 |= 0xe0;
+		epa_ctl[0] = 0x00; /* clear stall */
+		epa_ctl[1] = 0x00; /* clear reset */
+	} else {
+		gpio &= (~0x01); /* GPIO0 = 0 */
+		gpio |= 0x10; /* GPIO4 = 1 */
+		gpio &= (~0x04); /* GPIO2 = 1, LED off */
+		sys0 = sys0 & (~0xc0);
+		epa_ctl[0] = 0x10; /* set stall */
+		epa_ctl[1] = 0x02; /* set reset */
+	}
+
+	dev_dbg(&d->intf->dev, "WR SYS0=%02x GPIO_OUT_VAL=%02x\n", sys0, gpio);
+
+	/* demod adc */
+	ret = rtl28xxu_wr_reg(d, SYS_SYS0, sys0);
+	if (ret)
+		goto err;
+
+	/* tuner power, write GPIOs */
+	ret = rtl28xxu_wr_reg(d, SYS_GPIO_OUT_VAL, gpio);
+	if (ret)
+		goto err;
+
+	/* streaming EP: stall & reset */
+	ret = rtl28xxu_wr_regs(d, USB_EPA_CTL, epa_ctl, 2);
+	if (ret)
+		goto err;
+
+	if (onoff)
+		usb_clear_halt(d->udev, usb_rcvbulkpipe(d->udev, 0x81));
+
+	return ret;
+err:
+	dev_dbg(&d->intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static int rtl2832u_power_ctrl(struct dvb_usb_device *d, int onoff)
+{
+	int ret;
+
+	dev_dbg(&d->intf->dev, "onoff=%d\n", onoff);
+
+	if (onoff) {
+		/* GPIO3=1, GPIO4=0 */
+		ret = rtl28xxu_wr_reg_mask(d, SYS_GPIO_OUT_VAL, 0x08, 0x18);
+		if (ret)
+			goto err;
+
+		/* suspend? */
+		ret = rtl28xxu_wr_reg_mask(d, SYS_DEMOD_CTL1, 0x00, 0x10);
+		if (ret)
+			goto err;
+
+		/* enable PLL */
+		ret = rtl28xxu_wr_reg_mask(d, SYS_DEMOD_CTL, 0x80, 0x80);
+		if (ret)
+			goto err;
+
+		/* disable reset */
+		ret = rtl28xxu_wr_reg_mask(d, SYS_DEMOD_CTL, 0x20, 0x20);
+		if (ret)
+			goto err;
+
+		/* streaming EP: clear stall & reset */
+		ret = rtl28xxu_wr_regs(d, USB_EPA_CTL, "\x00\x00", 2);
+		if (ret)
+			goto err;
+
+		ret = usb_clear_halt(d->udev, usb_rcvbulkpipe(d->udev, 0x81));
+		if (ret)
+			goto err;
+	} else {
+		/* GPIO4=1 */
+		ret = rtl28xxu_wr_reg_mask(d, SYS_GPIO_OUT_VAL, 0x10, 0x10);
+		if (ret)
+			goto err;
+
+		/* disable PLL */
+		ret = rtl28xxu_wr_reg_mask(d, SYS_DEMOD_CTL, 0x00, 0x80);
+		if (ret)
+			goto err;
+
+		/* streaming EP: set stall & reset */
+		ret = rtl28xxu_wr_regs(d, USB_EPA_CTL, "\x10\x02", 2);
+		if (ret)
+			goto err;
+	}
+
+	return ret;
+err:
+	dev_dbg(&d->intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static int rtl28xxu_power_ctrl(struct dvb_usb_device *d, int onoff)
+{
+	struct rtl28xxu_dev *dev = d_to_priv(d);
+
+	if (dev->chip_id == CHIP_ID_RTL2831U)
+		return rtl2831u_power_ctrl(d, onoff);
+	else
+		return rtl2832u_power_ctrl(d, onoff);
+}
+
+static int rtl28xxu_frontend_ctrl(struct dvb_frontend *fe, int onoff)
+{
+	struct dvb_usb_device *d = fe_to_d(fe);
+	struct rtl28xxu_dev *dev = fe_to_priv(fe);
+	struct rtl2832_platform_data *pdata = &dev->rtl2832_platform_data;
+	int ret;
+	u8 val;
+
+	dev_dbg(&d->intf->dev, "fe=%d onoff=%d\n", fe->id, onoff);
+
+	if (dev->chip_id == CHIP_ID_RTL2831U)
+		return 0;
+
+	if (fe->id == 0) {
+		/* control internal demod ADC */
+		if (onoff)
+			val = 0x48; /* enable ADC */
+		else
+			val = 0x00; /* disable ADC */
+
+		ret = rtl28xxu_wr_reg_mask(d, SYS_DEMOD_CTL, val, 0x48);
+		if (ret)
+			goto err;
+	} else if (fe->id == 1) {
+		/* bypass slave demod TS through master demod */
+		ret = pdata->slave_ts_ctrl(dev->i2c_client_demod, onoff);
+		if (ret)
+			goto err;
+	}
+
+	return 0;
+err:
+	dev_dbg(&d->intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+#if IS_ENABLED(CONFIG_RC_CORE)
+static int rtl2831u_rc_query(struct dvb_usb_device *d)
+{
+	int ret, i;
+	struct rtl28xxu_dev *dev = d->priv;
+	u8 buf[5];
+	u32 rc_code;
+	static const struct rtl28xxu_reg_val rc_nec_tab[] = {
+		{ 0x3033, 0x80 },
+		{ 0x3020, 0x43 },
+		{ 0x3021, 0x16 },
+		{ 0x3022, 0x16 },
+		{ 0x3023, 0x5a },
+		{ 0x3024, 0x2d },
+		{ 0x3025, 0x16 },
+		{ 0x3026, 0x01 },
+		{ 0x3028, 0xb0 },
+		{ 0x3029, 0x04 },
+		{ 0x302c, 0x88 },
+		{ 0x302e, 0x13 },
+		{ 0x3030, 0xdf },
+		{ 0x3031, 0x05 },
+	};
+
+	/* init remote controller */
+	if (!dev->rc_active) {
+		for (i = 0; i < ARRAY_SIZE(rc_nec_tab); i++) {
+			ret = rtl28xxu_wr_reg(d, rc_nec_tab[i].reg,
+					rc_nec_tab[i].val);
+			if (ret)
+				goto err;
+		}
+		dev->rc_active = true;
+	}
+
+	ret = rtl28xxu_rd_regs(d, SYS_IRRC_RP, buf, 5);
+	if (ret)
+		goto err;
+
+	if (buf[4] & 0x01) {
+		enum rc_proto proto;
+
+		if (buf[2] == (u8) ~buf[3]) {
+			if (buf[0] == (u8) ~buf[1]) {
+				/* NEC standard (16 bit) */
+				rc_code = RC_SCANCODE_NEC(buf[0], buf[2]);
+				proto = RC_PROTO_NEC;
+			} else {
+				/* NEC extended (24 bit) */
+				rc_code = RC_SCANCODE_NECX(buf[0] << 8 | buf[1],
+							   buf[2]);
+				proto = RC_PROTO_NECX;
+			}
+		} else {
+			/* NEC full (32 bit) */
+			rc_code = RC_SCANCODE_NEC32(buf[0] << 24 | buf[1] << 16 |
+						    buf[2] << 8  | buf[3]);
+			proto = RC_PROTO_NEC32;
+		}
+
+		rc_keydown(d->rc_dev, proto, rc_code, 0);
+
+		ret = rtl28xxu_wr_reg(d, SYS_IRRC_SR, 1);
+		if (ret)
+			goto err;
+
+		/* repeated intentionally to avoid extra keypress */
+		ret = rtl28xxu_wr_reg(d, SYS_IRRC_SR, 1);
+		if (ret)
+			goto err;
+	}
+
+	return ret;
+err:
+	dev_dbg(&d->intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static int rtl2831u_get_rc_config(struct dvb_usb_device *d,
+		struct dvb_usb_rc *rc)
+{
+	rc->map_name = RC_MAP_EMPTY;
+	rc->allowed_protos = RC_PROTO_BIT_NEC | RC_PROTO_BIT_NECX |
+							RC_PROTO_BIT_NEC32;
+	rc->query = rtl2831u_rc_query;
+	rc->interval = 400;
+
+	return 0;
+}
+
+static int rtl2832u_rc_query(struct dvb_usb_device *d)
+{
+	int ret, i, len;
+	struct rtl28xxu_dev *dev = d->priv;
+	struct ir_raw_event ev;
+	u8 buf[128];
+	static const struct rtl28xxu_reg_val_mask refresh_tab[] = {
+		{IR_RX_IF,               0x03, 0xff},
+		{IR_RX_BUF_CTRL,         0x80, 0xff},
+		{IR_RX_CTRL,             0x80, 0xff},
+	};
+
+	/* init remote controller */
+	if (!dev->rc_active) {
+		static const struct rtl28xxu_reg_val_mask init_tab[] = {
+			{SYS_DEMOD_CTL1,         0x00, 0x04},
+			{SYS_DEMOD_CTL1,         0x00, 0x08},
+			{USB_CTRL,               0x20, 0x20},
+			{SYS_GPIO_DIR,           0x00, 0x08},
+			{SYS_GPIO_OUT_EN,        0x08, 0x08},
+			{SYS_GPIO_OUT_VAL,       0x08, 0x08},
+			{IR_MAX_DURATION0,       0xd0, 0xff},
+			{IR_MAX_DURATION1,       0x07, 0xff},
+			{IR_IDLE_LEN0,           0xc0, 0xff},
+			{IR_IDLE_LEN1,           0x00, 0xff},
+			{IR_GLITCH_LEN,          0x03, 0xff},
+			{IR_RX_CLK,              0x09, 0xff},
+			{IR_RX_CFG,              0x1c, 0xff},
+			{IR_MAX_H_TOL_LEN,       0x1e, 0xff},
+			{IR_MAX_L_TOL_LEN,       0x1e, 0xff},
+			{IR_RX_CTRL,             0x80, 0xff},
+		};
+
+		for (i = 0; i < ARRAY_SIZE(init_tab); i++) {
+			ret = rtl28xxu_wr_reg_mask(d, init_tab[i].reg,
+					init_tab[i].val, init_tab[i].mask);
+			if (ret)
+				goto err;
+		}
+
+		dev->rc_active = true;
+	}
+
+	ret = rtl28xxu_rd_reg(d, IR_RX_IF, &buf[0]);
+	if (ret)
+		goto err;
+
+	if (buf[0] != 0x83)
+		goto exit;
+
+	ret = rtl28xxu_rd_reg(d, IR_RX_BC, &buf[0]);
+	if (ret || buf[0] > sizeof(buf))
+		goto err;
+
+	len = buf[0];
+
+	/* read raw code from hw */
+	ret = rtl28xxu_rd_regs(d, IR_RX_BUF, buf, len);
+	if (ret)
+		goto err;
+
+	/* let hw receive new code */
+	for (i = 0; i < ARRAY_SIZE(refresh_tab); i++) {
+		ret = rtl28xxu_wr_reg_mask(d, refresh_tab[i].reg,
+				refresh_tab[i].val, refresh_tab[i].mask);
+		if (ret)
+			goto err;
+	}
+
+	/* pass data to Kernel IR decoder */
+	init_ir_raw_event(&ev);
+
+	for (i = 0; i < len; i++) {
+		ev.pulse = buf[i] >> 7;
+		ev.duration = 50800 * (buf[i] & 0x7f);
+		ir_raw_event_store_with_filter(d->rc_dev, &ev);
+	}
+
+	/* 'flush' ir_raw_event_store_with_filter() */
+	ir_raw_event_set_idle(d->rc_dev, true);
+	ir_raw_event_handle(d->rc_dev);
+exit:
+	return ret;
+err:
+	dev_dbg(&d->intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static int rtl2832u_get_rc_config(struct dvb_usb_device *d,
+		struct dvb_usb_rc *rc)
+{
+	/* disable IR interrupts in order to avoid SDR sample loss */
+	if (rtl28xxu_disable_rc)
+		return rtl28xxu_wr_reg(d, IR_RX_IE, 0x00);
+
+	/* load empty to enable rc */
+	if (!rc->map_name)
+		rc->map_name = RC_MAP_EMPTY;
+	rc->allowed_protos = RC_PROTO_BIT_ALL_IR_DECODER;
+	rc->driver_type = RC_DRIVER_IR_RAW;
+	rc->query = rtl2832u_rc_query;
+	rc->interval = 200;
+
+	return 0;
+}
+
+static int rtl28xxu_get_rc_config(struct dvb_usb_device *d,
+		struct dvb_usb_rc *rc)
+{
+	struct rtl28xxu_dev *dev = d_to_priv(d);
+
+	if (dev->chip_id == CHIP_ID_RTL2831U)
+		return rtl2831u_get_rc_config(d, rc);
+	else
+		return rtl2832u_get_rc_config(d, rc);
+}
+#else
+#define rtl28xxu_get_rc_config NULL
+#endif
+
+static int rtl28xxu_pid_filter_ctrl(struct dvb_usb_adapter *adap, int onoff)
+{
+	struct rtl28xxu_dev *dev = adap_to_priv(adap);
+
+	if (dev->chip_id == CHIP_ID_RTL2831U) {
+		struct rtl2830_platform_data *pdata = &dev->rtl2830_platform_data;
+
+		return pdata->pid_filter_ctrl(adap->fe[0], onoff);
+	} else {
+		struct rtl2832_platform_data *pdata = &dev->rtl2832_platform_data;
+
+		return pdata->pid_filter_ctrl(adap->fe[0], onoff);
+	}
+}
+
+static int rtl28xxu_pid_filter(struct dvb_usb_adapter *adap, int index,
+			       u16 pid, int onoff)
+{
+	struct rtl28xxu_dev *dev = adap_to_priv(adap);
+
+	if (dev->chip_id == CHIP_ID_RTL2831U) {
+		struct rtl2830_platform_data *pdata = &dev->rtl2830_platform_data;
+
+		return pdata->pid_filter(adap->fe[0], index, pid, onoff);
+	} else {
+		struct rtl2832_platform_data *pdata = &dev->rtl2832_platform_data;
+
+		return pdata->pid_filter(adap->fe[0], index, pid, onoff);
+	}
+}
+
+static const struct dvb_usb_device_properties rtl28xxu_props = {
+	.driver_name = KBUILD_MODNAME,
+	.owner = THIS_MODULE,
+	.adapter_nr = adapter_nr,
+	.size_of_priv = sizeof(struct rtl28xxu_dev),
+
+	.identify_state = rtl28xxu_identify_state,
+	.power_ctrl = rtl28xxu_power_ctrl,
+	.frontend_ctrl = rtl28xxu_frontend_ctrl,
+	.i2c_algo = &rtl28xxu_i2c_algo,
+	.read_config = rtl28xxu_read_config,
+	.frontend_attach = rtl28xxu_frontend_attach,
+	.frontend_detach = rtl28xxu_frontend_detach,
+	.tuner_attach = rtl28xxu_tuner_attach,
+	.tuner_detach = rtl28xxu_tuner_detach,
+	.init = rtl28xxu_init,
+	.get_rc_config = rtl28xxu_get_rc_config,
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+			.caps = DVB_USB_ADAP_HAS_PID_FILTER |
+				DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+
+			.pid_filter_count = 32,
+			.pid_filter_ctrl = rtl28xxu_pid_filter_ctrl,
+			.pid_filter = rtl28xxu_pid_filter,
+
+			.stream = DVB_USB_STREAM_BULK(0x81, 6, 8 * 512),
+		},
+	},
+};
+
+static const struct usb_device_id rtl28xxu_id_table[] = {
+	/* RTL2831U devices: */
+	{ DVB_USB_DEVICE(USB_VID_REALTEK, USB_PID_REALTEK_RTL2831U,
+		&rtl28xxu_props, "Realtek RTL2831U reference design", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_WIDEVIEW, USB_PID_FREECOM_DVBT,
+		&rtl28xxu_props, "Freecom USB2.0 DVB-T", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_WIDEVIEW, USB_PID_FREECOM_DVBT_2,
+		&rtl28xxu_props, "Freecom USB2.0 DVB-T", NULL) },
+
+	/* RTL2832U devices: */
+	{ DVB_USB_DEVICE(USB_VID_REALTEK, 0x2832,
+		&rtl28xxu_props, "Realtek RTL2832U reference design", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_REALTEK, 0x2838,
+		&rtl28xxu_props, "Realtek RTL2832U reference design", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_CINERGY_T_STICK_BLACK_REV1,
+		&rtl28xxu_props, "TerraTec Cinergy T Stick Black", RC_MAP_TERRATEC_SLIM) },
+	{ DVB_USB_DEVICE(USB_VID_GTEK, USB_PID_DELOCK_USB2_DVBT,
+		&rtl28xxu_props, "G-Tek Electronics Group Lifeview LV5TDLX DVB-T", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_NOXON_DAB_STICK,
+		&rtl28xxu_props, "TerraTec NOXON DAB Stick", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_NOXON_DAB_STICK_REV2,
+		&rtl28xxu_props, "TerraTec NOXON DAB Stick (rev 2)", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_TERRATEC, USB_PID_NOXON_DAB_STICK_REV3,
+		&rtl28xxu_props, "TerraTec NOXON DAB Stick (rev 3)", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_GTEK, USB_PID_TREKSTOR_TERRES_2_0,
+		&rtl28xxu_props, "Trekstor DVB-T Stick Terres 2.0", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_DEXATEK, 0x1101,
+		&rtl28xxu_props, "Dexatek DK DVB-T Dongle", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_LEADTEK, 0x6680,
+		&rtl28xxu_props, "DigitalNow Quad DVB-T Receiver", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_LEADTEK, USB_PID_WINFAST_DTV_DONGLE_MINID,
+		&rtl28xxu_props, "Leadtek Winfast DTV Dongle Mini D", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_LEADTEK, USB_PID_WINFAST_DTV2000DS_PLUS,
+		&rtl28xxu_props, "Leadtek WinFast DTV2000DS Plus", RC_MAP_LEADTEK_Y04G0051) },
+	{ DVB_USB_DEVICE(USB_VID_TERRATEC, 0x00d3,
+		&rtl28xxu_props, "TerraTec Cinergy T Stick RC (Rev. 3)", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_DEXATEK, 0x1102,
+		&rtl28xxu_props, "Dexatek DK mini DVB-T Dongle", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_TERRATEC, 0x00d7,
+		&rtl28xxu_props, "TerraTec Cinergy T Stick+", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_KWORLD_2, 0xd3a8,
+		&rtl28xxu_props, "ASUS My Cinema-U3100Mini Plus V2", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_KWORLD_2, 0xd393,
+		&rtl28xxu_props, "GIGABYTE U7300", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_DEXATEK, 0x1104,
+		&rtl28xxu_props, "MSI DIGIVOX Micro HD", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_COMPRO, 0x0620,
+		&rtl28xxu_props, "Compro VideoMate U620F", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_COMPRO, 0x0650,
+		&rtl28xxu_props, "Compro VideoMate U650F", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_KWORLD_2, 0xd394,
+		&rtl28xxu_props, "MaxMedia HU394-T", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_LEADTEK, 0x6a03,
+		&rtl28xxu_props, "Leadtek WinFast DTV Dongle mini", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_GTEK, USB_PID_CPYTO_REDI_PC50A,
+		&rtl28xxu_props, "Crypto ReDi PC 50 A", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_KYE, 0x707f,
+		&rtl28xxu_props, "Genius TVGo DVB-T03", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_KWORLD_2, 0xd395,
+		&rtl28xxu_props, "Peak DVB-T USB", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_SVEON_STV20_RTL2832U,
+		&rtl28xxu_props, "Sveon STV20", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_SVEON_STV21,
+		&rtl28xxu_props, "Sveon STV21", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_SVEON_STV27,
+		&rtl28xxu_props, "Sveon STV27", NULL) },
+	{ DVB_USB_DEVICE(USB_VID_KWORLD_2, USB_PID_TURBOX_DTT_2000,
+		&rtl28xxu_props, "TURBO-X Pure TV Tuner DTT-2000", NULL) },
+
+	/* RTL2832P devices: */
+	{ DVB_USB_DEVICE(USB_VID_HANFTEK, 0x0131,
+		&rtl28xxu_props, "Astrometa DVB-T2", NULL) },
+	{ DVB_USB_DEVICE(0x5654, 0xca42,
+		&rtl28xxu_props, "GoTView MasterHD 3", NULL) },
+	{ }
+};
+MODULE_DEVICE_TABLE(usb, rtl28xxu_id_table);
+
+static struct usb_driver rtl28xxu_usb_driver = {
+	.name = KBUILD_MODNAME,
+	.id_table = rtl28xxu_id_table,
+	.probe = dvb_usbv2_probe,
+	.disconnect = dvb_usbv2_disconnect,
+	.suspend = dvb_usbv2_suspend,
+	.resume = dvb_usbv2_resume,
+	.reset_resume = dvb_usbv2_reset_resume,
+	.no_dynamic_id = 1,
+	.soft_unbind = 1,
+};
+
+module_usb_driver(rtl28xxu_usb_driver);
+
+MODULE_DESCRIPTION("Realtek RTL28xxU DVB USB driver");
+MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>");
+MODULE_AUTHOR("Thomas Mair <thomas.mair86@googlemail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb-v2/rtl28xxu.h b/drivers/media/usb/dvb-usb-v2/rtl28xxu.h
new file mode 100644
index 0000000..1380629
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/rtl28xxu.h
@@ -0,0 +1,290 @@
+/*
+ * Realtek RTL28xxU DVB USB driver
+ *
+ * Copyright (C) 2009 Antti Palosaari <crope@iki.fi>
+ * Copyright (C) 2011 Antti Palosaari <crope@iki.fi>
+ *
+ *    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.
+ */
+
+#ifndef RTL28XXU_H
+#define RTL28XXU_H
+
+#include <linux/platform_device.h>
+
+#include "dvb_usb.h"
+
+#include "rtl2830.h"
+#include "rtl2832.h"
+#include "rtl2832_sdr.h"
+#include "mn88472.h"
+#include "mn88473.h"
+
+#include "qt1010.h"
+#include "mt2060.h"
+#include "mxl5005s.h"
+#include "fc0012.h"
+#include "fc0013.h"
+#include "e4000.h"
+#include "fc2580.h"
+#include "tua9001.h"
+#include "r820t.h"
+#include "si2168.h"
+#include "si2157.h"
+
+/*
+ * USB commands
+ * (usb_control_msg() index parameter)
+ */
+
+#define DEMOD            0x0000
+#define USB              0x0100
+#define SYS              0x0200
+#define I2C              0x0300
+#define I2C_DA           0x0600
+
+#define CMD_WR_FLAG      0x0010
+#define CMD_DEMOD_RD     0x0000
+#define CMD_DEMOD_WR     0x0010
+#define CMD_USB_RD       0x0100
+#define CMD_USB_WR       0x0110
+#define CMD_SYS_RD       0x0200
+#define CMD_IR_RD        0x0201
+#define CMD_IR_WR        0x0211
+#define CMD_SYS_WR       0x0210
+#define CMD_I2C_RD       0x0300
+#define CMD_I2C_WR       0x0310
+#define CMD_I2C_DA_RD    0x0600
+#define CMD_I2C_DA_WR    0x0610
+
+
+struct rtl28xxu_dev {
+	u8 buf[128];
+	u8 chip_id;
+	u8 tuner;
+	char *tuner_name;
+	u8 page; /* integrated demod active register page */
+	struct i2c_adapter *demod_i2c_adapter;
+	bool rc_active;
+	bool new_i2c_write;
+	struct i2c_client *i2c_client_demod;
+	struct i2c_client *i2c_client_tuner;
+	struct i2c_client *i2c_client_slave_demod;
+	struct platform_device *platform_device_sdr;
+	#define SLAVE_DEMOD_NONE           0
+	#define SLAVE_DEMOD_MN88472        1
+	#define SLAVE_DEMOD_MN88473        2
+	#define SLAVE_DEMOD_SI2168         3
+	unsigned int slave_demod:2;
+	union {
+		struct rtl2830_platform_data rtl2830_platform_data;
+		struct rtl2832_platform_data rtl2832_platform_data;
+	};
+};
+
+enum rtl28xxu_chip_id {
+	CHIP_ID_NONE,
+	CHIP_ID_RTL2831U,
+	CHIP_ID_RTL2832U,
+};
+
+/* XXX: Hack. This must be keep sync with rtl2832 demod driver. */
+enum rtl28xxu_tuner {
+	TUNER_NONE,
+
+	TUNER_RTL2830_QT1010          = 0x10,
+	TUNER_RTL2830_MT2060,
+	TUNER_RTL2830_MXL5005S,
+
+	TUNER_RTL2832_MT2266          = 0x20,
+	TUNER_RTL2832_FC2580,
+	TUNER_RTL2832_MT2063,
+	TUNER_RTL2832_MAX3543,
+	TUNER_RTL2832_TUA9001,
+	TUNER_RTL2832_MXL5007T,
+	TUNER_RTL2832_FC0012,
+	TUNER_RTL2832_E4000,
+	TUNER_RTL2832_TDA18272,
+	TUNER_RTL2832_FC0013,
+	TUNER_RTL2832_R820T,
+	TUNER_RTL2832_R828D,
+	TUNER_RTL2832_SI2157,
+};
+
+struct rtl28xxu_req {
+	u16 value;
+	u16 index;
+	u16 size;
+	u8 *data;
+};
+
+struct rtl28xxu_reg_val {
+	u16 reg;
+	u8 val;
+};
+
+struct rtl28xxu_reg_val_mask {
+	u16 reg;
+	u8 val;
+	u8 mask;
+};
+
+/*
+ * memory map
+ *
+ * 0x0000 DEMOD : demodulator
+ * 0x2000 USB   : SIE, USB endpoint, debug, DMA
+ * 0x3000 SYS   : system
+ * 0xfc00 RC    : remote controller (not RTL2831U)
+ */
+
+/*
+ * USB registers
+ */
+/* SIE Control Registers */
+#define USB_SYSCTL         0x2000 /* USB system control */
+#define USB_SYSCTL_0       0x2000 /* USB system control */
+#define USB_SYSCTL_1       0x2001 /* USB system control */
+#define USB_SYSCTL_2       0x2002 /* USB system control */
+#define USB_SYSCTL_3       0x2003 /* USB system control */
+#define USB_IRQSTAT        0x2008 /* SIE interrupt status */
+#define USB_IRQEN          0x200C /* SIE interrupt enable */
+#define USB_CTRL           0x2010 /* USB control */
+#define USB_STAT           0x2014 /* USB status */
+#define USB_DEVADDR        0x2018 /* USB device address */
+#define USB_TEST           0x201C /* USB test mode */
+#define USB_FRAME_NUMBER   0x2020 /* frame number */
+#define USB_FIFO_ADDR      0x2028 /* address of SIE FIFO RAM */
+#define USB_FIFO_CMD       0x202A /* SIE FIFO RAM access command */
+#define USB_FIFO_DATA      0x2030 /* SIE FIFO RAM data */
+/* Endpoint Registers */
+#define EP0_SETUPA         0x20F8 /* EP 0 setup packet lower byte */
+#define EP0_SETUPB         0x20FC /* EP 0 setup packet higher byte */
+#define USB_EP0_CFG        0x2104 /* EP 0 configure */
+#define USB_EP0_CTL        0x2108 /* EP 0 control */
+#define USB_EP0_STAT       0x210C /* EP 0 status */
+#define USB_EP0_IRQSTAT    0x2110 /* EP 0 interrupt status */
+#define USB_EP0_IRQEN      0x2114 /* EP 0 interrupt enable */
+#define USB_EP0_MAXPKT     0x2118 /* EP 0 max packet size */
+#define USB_EP0_BC         0x2120 /* EP 0 FIFO byte counter */
+#define USB_EPA_CFG        0x2144 /* EP A configure */
+#define USB_EPA_CFG_0      0x2144 /* EP A configure */
+#define USB_EPA_CFG_1      0x2145 /* EP A configure */
+#define USB_EPA_CFG_2      0x2146 /* EP A configure */
+#define USB_EPA_CFG_3      0x2147 /* EP A configure */
+#define USB_EPA_CTL        0x2148 /* EP A control */
+#define USB_EPA_CTL_0      0x2148 /* EP A control */
+#define USB_EPA_CTL_1      0x2149 /* EP A control */
+#define USB_EPA_CTL_2      0x214A /* EP A control */
+#define USB_EPA_CTL_3      0x214B /* EP A control */
+#define USB_EPA_STAT       0x214C /* EP A status */
+#define USB_EPA_IRQSTAT    0x2150 /* EP A interrupt status */
+#define USB_EPA_IRQEN      0x2154 /* EP A interrupt enable */
+#define USB_EPA_MAXPKT     0x2158 /* EP A max packet size */
+#define USB_EPA_MAXPKT_0   0x2158 /* EP A max packet size */
+#define USB_EPA_MAXPKT_1   0x2159 /* EP A max packet size */
+#define USB_EPA_MAXPKT_2   0x215A /* EP A max packet size */
+#define USB_EPA_MAXPKT_3   0x215B /* EP A max packet size */
+#define USB_EPA_FIFO_CFG   0x2160 /* EP A FIFO configure */
+#define USB_EPA_FIFO_CFG_0 0x2160 /* EP A FIFO configure */
+#define USB_EPA_FIFO_CFG_1 0x2161 /* EP A FIFO configure */
+#define USB_EPA_FIFO_CFG_2 0x2162 /* EP A FIFO configure */
+#define USB_EPA_FIFO_CFG_3 0x2163 /* EP A FIFO configure */
+/* Debug Registers */
+#define USB_PHYTSTDIS      0x2F04 /* PHY test disable */
+#define USB_TOUT_VAL       0x2F08 /* USB time-out time */
+#define USB_VDRCTRL        0x2F10 /* UTMI vendor signal control */
+#define USB_VSTAIN         0x2F14 /* UTMI vendor signal status in */
+#define USB_VLOADM         0x2F18 /* UTMI load vendor signal status in */
+#define USB_VSTAOUT        0x2F1C /* UTMI vendor signal status out */
+#define USB_UTMI_TST       0x2F80 /* UTMI test */
+#define USB_UTMI_STATUS    0x2F84 /* UTMI status */
+#define USB_TSTCTL         0x2F88 /* test control */
+#define USB_TSTCTL2        0x2F8C /* test control 2 */
+#define USB_PID_FORCE      0x2F90 /* force PID */
+#define USB_PKTERR_CNT     0x2F94 /* packet error counter */
+#define USB_RXERR_CNT      0x2F98 /* RX error counter */
+#define USB_MEM_BIST       0x2F9C /* MEM BIST test */
+#define USB_SLBBIST        0x2FA0 /* self-loop-back BIST */
+#define USB_CNTTEST        0x2FA4 /* counter test */
+#define USB_PHYTST         0x2FC0 /* USB PHY test */
+#define USB_DBGIDX         0x2FF0 /* select individual block debug signal */
+#define USB_DBGMUX         0x2FF4 /* debug signal module mux */
+
+/*
+ * SYS registers
+ */
+/* demod control registers */
+#define SYS_SYS0           0x3000 /* include DEMOD_CTL, GPO, GPI, GPOE */
+#define SYS_DEMOD_CTL      0x3000 /* control register for DVB-T demodulator */
+/* GPIO registers */
+#define SYS_GPIO_OUT_VAL   0x3001 /* output value of GPIO */
+#define SYS_GPIO_IN_VAL    0x3002 /* input value of GPIO */
+#define SYS_GPIO_OUT_EN    0x3003 /* output enable of GPIO */
+#define SYS_SYS1           0x3004 /* include GPD, SYSINTE, SYSINTS, GP_CFG0 */
+#define SYS_GPIO_DIR       0x3004 /* direction control for GPIO */
+#define SYS_SYSINTE        0x3005 /* system interrupt enable */
+#define SYS_SYSINTS        0x3006 /* system interrupt status */
+#define SYS_GPIO_CFG0      0x3007 /* PAD configuration for GPIO0-GPIO3 */
+#define SYS_SYS2           0x3008 /* include GP_CFG1 and 3 reserved bytes */
+#define SYS_GPIO_CFG1      0x3008 /* PAD configuration for GPIO4 */
+#define SYS_DEMOD_CTL1     0x300B
+
+/* IrDA registers */
+#define SYS_IRRC_PSR       0x3020 /* IR protocol selection */
+#define SYS_IRRC_PER       0x3024 /* IR protocol extension */
+#define SYS_IRRC_SF        0x3028 /* IR sampling frequency */
+#define SYS_IRRC_DPIR      0x302C /* IR data package interval */
+#define SYS_IRRC_CR        0x3030 /* IR control */
+#define SYS_IRRC_RP        0x3034 /* IR read port */
+#define SYS_IRRC_SR        0x3038 /* IR status */
+/* I2C master registers */
+#define SYS_I2CCR          0x3040 /* I2C clock */
+#define SYS_I2CMCR         0x3044 /* I2C master control */
+#define SYS_I2CMSTR        0x3048 /* I2C master SCL timing */
+#define SYS_I2CMSR         0x304C /* I2C master status */
+#define SYS_I2CMFR         0x3050 /* I2C master FIFO */
+
+/*
+ * IR registers
+ */
+#define IR_RX_BUF          0xFC00
+#define IR_RX_IE           0xFD00
+#define IR_RX_IF           0xFD01
+#define IR_RX_CTRL         0xFD02
+#define IR_RX_CFG          0xFD03
+#define IR_MAX_DURATION0   0xFD04
+#define IR_MAX_DURATION1   0xFD05
+#define IR_IDLE_LEN0       0xFD06
+#define IR_IDLE_LEN1       0xFD07
+#define IR_GLITCH_LEN      0xFD08
+#define IR_RX_BUF_CTRL     0xFD09
+#define IR_RX_BUF_DATA     0xFD0A
+#define IR_RX_BC           0xFD0B
+#define IR_RX_CLK          0xFD0C
+#define IR_RX_C_COUNT_L    0xFD0D
+#define IR_RX_C_COUNT_H    0xFD0E
+#define IR_SUSPEND_CTRL    0xFD10
+#define IR_ERR_TOL_CTRL    0xFD11
+#define IR_UNIT_LEN        0xFD12
+#define IR_ERR_TOL_LEN     0xFD13
+#define IR_MAX_H_TOL_LEN   0xFD14
+#define IR_MAX_L_TOL_LEN   0xFD15
+#define IR_MASK_CTRL       0xFD16
+#define IR_MASK_DATA       0xFD17
+#define IR_RES_MASK_ADDR   0xFD18
+#define IR_RES_MASK_T_LEN  0xFD19
+
+#endif
diff --git a/drivers/media/usb/dvb-usb-v2/usb_urb.c b/drivers/media/usb/dvb-usb-v2/usb_urb.c
new file mode 100644
index 0000000..024c751
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/usb_urb.c
@@ -0,0 +1,354 @@
+// SPDX-License-Identifier: GPL-2.0
+/* usb-urb.c is part of the DVB USB library.
+ *
+ * Copyright (C) 2004-6 Patrick Boettcher (patrick.boettcher@posteo.de)
+ * see dvb-usb-init.c for copyright information.
+ *
+ * This file keeps functions for initializing and handling the
+ * BULK and ISOC USB data transfers in a generic way.
+ * Can be used for DVB-only and also, that's the plan, for
+ * Hybrid USB devices (analog and DVB).
+ */
+#include "dvb_usb_common.h"
+
+/* URB stuff for streaming */
+
+int usb_urb_reconfig(struct usb_data_stream *stream,
+		struct usb_data_stream_properties *props);
+
+static void usb_urb_complete(struct urb *urb)
+{
+	struct usb_data_stream *stream = urb->context;
+	int ptype = usb_pipetype(urb->pipe);
+	int i;
+	u8 *b;
+
+	dev_dbg_ratelimited(&stream->udev->dev,
+			"%s: %s urb completed status=%d length=%d/%d pack_num=%d errors=%d\n",
+			__func__, ptype == PIPE_ISOCHRONOUS ? "isoc" : "bulk",
+			urb->status, urb->actual_length,
+			urb->transfer_buffer_length,
+			urb->number_of_packets, urb->error_count);
+
+	switch (urb->status) {
+	case 0:         /* success */
+	case -ETIMEDOUT:    /* NAK */
+		break;
+	case -ECONNRESET:   /* kill */
+	case -ENOENT:
+	case -ESHUTDOWN:
+		return;
+	default:        /* error */
+		dev_dbg_ratelimited(&stream->udev->dev,
+				"%s: urb completion failed=%d\n",
+				__func__, urb->status);
+		break;
+	}
+
+	b = (u8 *) urb->transfer_buffer;
+	switch (ptype) {
+	case PIPE_ISOCHRONOUS:
+		for (i = 0; i < urb->number_of_packets; i++) {
+			if (urb->iso_frame_desc[i].status != 0)
+				dev_dbg(&stream->udev->dev,
+						"%s: iso frame descriptor has an error=%d\n",
+						__func__,
+						urb->iso_frame_desc[i].status);
+			else if (urb->iso_frame_desc[i].actual_length > 0)
+				stream->complete(stream,
+					b + urb->iso_frame_desc[i].offset,
+					urb->iso_frame_desc[i].actual_length);
+
+			urb->iso_frame_desc[i].status = 0;
+			urb->iso_frame_desc[i].actual_length = 0;
+		}
+		break;
+	case PIPE_BULK:
+		if (urb->actual_length > 0)
+			stream->complete(stream, b, urb->actual_length);
+		break;
+	default:
+		dev_err(&stream->udev->dev,
+				"%s: unknown endpoint type in completion handler\n",
+				KBUILD_MODNAME);
+		return;
+	}
+	usb_submit_urb(urb, GFP_ATOMIC);
+}
+
+int usb_urb_killv2(struct usb_data_stream *stream)
+{
+	int i;
+	for (i = 0; i < stream->urbs_submitted; i++) {
+		dev_dbg(&stream->udev->dev, "%s: kill urb=%d\n", __func__, i);
+		/* stop the URB */
+		usb_kill_urb(stream->urb_list[i]);
+	}
+	stream->urbs_submitted = 0;
+	return 0;
+}
+
+int usb_urb_submitv2(struct usb_data_stream *stream,
+		struct usb_data_stream_properties *props)
+{
+	int i, ret;
+
+	if (props) {
+		ret = usb_urb_reconfig(stream, props);
+		if (ret < 0)
+			return ret;
+	}
+
+	for (i = 0; i < stream->urbs_initialized; i++) {
+		dev_dbg(&stream->udev->dev, "%s: submit urb=%d\n", __func__, i);
+		ret = usb_submit_urb(stream->urb_list[i], GFP_ATOMIC);
+		if (ret) {
+			dev_err(&stream->udev->dev,
+					"%s: could not submit urb no. %d - get them all back\n",
+					KBUILD_MODNAME, i);
+			usb_urb_killv2(stream);
+			return ret;
+		}
+		stream->urbs_submitted++;
+	}
+	return 0;
+}
+
+static int usb_urb_free_urbs(struct usb_data_stream *stream)
+{
+	int i;
+
+	usb_urb_killv2(stream);
+
+	for (i = stream->urbs_initialized - 1; i >= 0; i--) {
+		if (stream->urb_list[i]) {
+			dev_dbg(&stream->udev->dev, "%s: free urb=%d\n",
+					__func__, i);
+			/* free the URBs */
+			usb_free_urb(stream->urb_list[i]);
+		}
+	}
+	stream->urbs_initialized = 0;
+
+	return 0;
+}
+
+static int usb_urb_alloc_bulk_urbs(struct usb_data_stream *stream)
+{
+	int i, j;
+
+	/* allocate the URBs */
+	for (i = 0; i < stream->props.count; i++) {
+		dev_dbg(&stream->udev->dev, "%s: alloc urb=%d\n", __func__, i);
+		stream->urb_list[i] = usb_alloc_urb(0, GFP_ATOMIC);
+		if (!stream->urb_list[i]) {
+			dev_dbg(&stream->udev->dev, "%s: failed\n", __func__);
+			for (j = 0; j < i; j++)
+				usb_free_urb(stream->urb_list[j]);
+			return -ENOMEM;
+		}
+		usb_fill_bulk_urb(stream->urb_list[i],
+				stream->udev,
+				usb_rcvbulkpipe(stream->udev,
+						stream->props.endpoint),
+				stream->buf_list[i],
+				stream->props.u.bulk.buffersize,
+				usb_urb_complete, stream);
+
+		stream->urb_list[i]->transfer_flags = URB_FREE_BUFFER;
+		stream->urbs_initialized++;
+	}
+	return 0;
+}
+
+static int usb_urb_alloc_isoc_urbs(struct usb_data_stream *stream)
+{
+	int i, j;
+
+	/* allocate the URBs */
+	for (i = 0; i < stream->props.count; i++) {
+		struct urb *urb;
+		int frame_offset = 0;
+		dev_dbg(&stream->udev->dev, "%s: alloc urb=%d\n", __func__, i);
+		stream->urb_list[i] = usb_alloc_urb(
+				stream->props.u.isoc.framesperurb, GFP_ATOMIC);
+		if (!stream->urb_list[i]) {
+			dev_dbg(&stream->udev->dev, "%s: failed\n", __func__);
+			for (j = 0; j < i; j++)
+				usb_free_urb(stream->urb_list[j]);
+			return -ENOMEM;
+		}
+
+		urb = stream->urb_list[i];
+
+		urb->dev = stream->udev;
+		urb->context = stream;
+		urb->complete = usb_urb_complete;
+		urb->pipe = usb_rcvisocpipe(stream->udev,
+				stream->props.endpoint);
+		urb->transfer_flags = URB_ISO_ASAP | URB_FREE_BUFFER;
+		urb->interval = stream->props.u.isoc.interval;
+		urb->number_of_packets = stream->props.u.isoc.framesperurb;
+		urb->transfer_buffer_length = stream->props.u.isoc.framesize *
+				stream->props.u.isoc.framesperurb;
+		urb->transfer_buffer = stream->buf_list[i];
+
+		for (j = 0; j < stream->props.u.isoc.framesperurb; j++) {
+			urb->iso_frame_desc[j].offset = frame_offset;
+			urb->iso_frame_desc[j].length =
+					stream->props.u.isoc.framesize;
+			frame_offset += stream->props.u.isoc.framesize;
+		}
+
+		stream->urbs_initialized++;
+	}
+	return 0;
+}
+
+static int usb_free_stream_buffers(struct usb_data_stream *stream)
+{
+	if (stream->state & USB_STATE_URB_BUF) {
+		while (stream->buf_num) {
+			stream->buf_num--;
+			stream->buf_list[stream->buf_num] = NULL;
+		}
+	}
+
+	stream->state &= ~USB_STATE_URB_BUF;
+
+	return 0;
+}
+
+static int usb_alloc_stream_buffers(struct usb_data_stream *stream, int num,
+				    unsigned long size)
+{
+	stream->buf_num = 0;
+	stream->buf_size = size;
+
+	dev_dbg(&stream->udev->dev,
+			"%s: all in all I will use %lu bytes for streaming\n",
+			__func__,  num * size);
+
+	for (stream->buf_num = 0; stream->buf_num < num; stream->buf_num++) {
+		stream->buf_list[stream->buf_num] = kzalloc(size, GFP_ATOMIC);
+		if (!stream->buf_list[stream->buf_num]) {
+			dev_dbg(&stream->udev->dev, "%s: alloc buf=%d failed\n",
+					__func__, stream->buf_num);
+			usb_free_stream_buffers(stream);
+			return -ENOMEM;
+		}
+
+		dev_dbg(&stream->udev->dev, "%s: alloc buf=%d %p (dma %llu)\n",
+				__func__, stream->buf_num,
+				stream->buf_list[stream->buf_num],
+				(long long)stream->dma_addr[stream->buf_num]);
+		stream->state |= USB_STATE_URB_BUF;
+	}
+
+	return 0;
+}
+
+int usb_urb_reconfig(struct usb_data_stream *stream,
+		struct usb_data_stream_properties *props)
+{
+	int buf_size;
+
+	if (!props)
+		return 0;
+
+	/* check allocated buffers are large enough for the request */
+	if (props->type == USB_BULK) {
+		buf_size = stream->props.u.bulk.buffersize;
+	} else if (props->type == USB_ISOC) {
+		buf_size = props->u.isoc.framesize * props->u.isoc.framesperurb;
+	} else {
+		dev_err(&stream->udev->dev, "%s: invalid endpoint type=%d\n",
+				KBUILD_MODNAME, props->type);
+		return -EINVAL;
+	}
+
+	if (stream->buf_num < props->count || stream->buf_size < buf_size) {
+		dev_err(&stream->udev->dev,
+				"%s: cannot reconfigure as allocated buffers are too small\n",
+				KBUILD_MODNAME);
+		return -EINVAL;
+	}
+
+	/* check if all fields are same */
+	if (stream->props.type == props->type &&
+			stream->props.count == props->count &&
+			stream->props.endpoint == props->endpoint) {
+		if (props->type == USB_BULK &&
+				props->u.bulk.buffersize ==
+				stream->props.u.bulk.buffersize)
+			return 0;
+		else if (props->type == USB_ISOC &&
+				props->u.isoc.framesperurb ==
+				stream->props.u.isoc.framesperurb &&
+				props->u.isoc.framesize ==
+				stream->props.u.isoc.framesize &&
+				props->u.isoc.interval ==
+				stream->props.u.isoc.interval)
+			return 0;
+	}
+
+	dev_dbg(&stream->udev->dev, "%s: re-alloc urbs\n", __func__);
+
+	usb_urb_free_urbs(stream);
+	memcpy(&stream->props, props, sizeof(*props));
+	if (props->type == USB_BULK)
+		return usb_urb_alloc_bulk_urbs(stream);
+	else if (props->type == USB_ISOC)
+		return usb_urb_alloc_isoc_urbs(stream);
+
+	return 0;
+}
+
+int usb_urb_initv2(struct usb_data_stream *stream,
+		const struct usb_data_stream_properties *props)
+{
+	int ret;
+
+	if (!stream || !props)
+		return -EINVAL;
+
+	memcpy(&stream->props, props, sizeof(*props));
+
+	if (!stream->complete) {
+		dev_err(&stream->udev->dev,
+				"%s: there is no data callback - this doesn't make sense\n",
+				KBUILD_MODNAME);
+		return -EINVAL;
+	}
+
+	switch (stream->props.type) {
+	case USB_BULK:
+		ret = usb_alloc_stream_buffers(stream, stream->props.count,
+				stream->props.u.bulk.buffersize);
+		if (ret < 0)
+			return ret;
+
+		return usb_urb_alloc_bulk_urbs(stream);
+	case USB_ISOC:
+		ret = usb_alloc_stream_buffers(stream, stream->props.count,
+				stream->props.u.isoc.framesize *
+				stream->props.u.isoc.framesperurb);
+		if (ret < 0)
+			return ret;
+
+		return usb_urb_alloc_isoc_urbs(stream);
+	default:
+		dev_err(&stream->udev->dev,
+				"%s: unknown urb-type for data transfer\n",
+				KBUILD_MODNAME);
+		return -EINVAL;
+	}
+}
+
+int usb_urb_exitv2(struct usb_data_stream *stream)
+{
+	usb_urb_free_urbs(stream);
+	usb_free_stream_buffers(stream);
+
+	return 0;
+}
diff --git a/drivers/media/usb/dvb-usb-v2/zd1301.c b/drivers/media/usb/dvb-usb-v2/zd1301.c
new file mode 100644
index 0000000..d1eb4b7
--- /dev/null
+++ b/drivers/media/usb/dvb-usb-v2/zd1301.c
@@ -0,0 +1,298 @@
+/*
+ * ZyDAS ZD1301 driver (USB interface)
+ *
+ * Copyright (C) 2015 Antti Palosaari <crope@iki.fi>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ */
+
+#include "dvb_usb.h"
+#include "zd1301_demod.h"
+#include "mt2060.h"
+#include <linux/i2c.h>
+#include <linux/platform_device.h>
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+struct zd1301_dev {
+	#define BUF_LEN 8
+	u8 buf[BUF_LEN]; /* bulk USB control message */
+	struct zd1301_demod_platform_data demod_pdata;
+	struct mt2060_platform_data mt2060_pdata;
+	struct platform_device *platform_device_demod;
+	struct i2c_client *i2c_client_tuner;
+};
+
+static int zd1301_ctrl_msg(struct dvb_usb_device *d, const u8 *wbuf,
+			   unsigned int wlen, u8 *rbuf, unsigned int rlen)
+{
+	struct zd1301_dev *dev = d_to_priv(d);
+	struct usb_interface *intf = d->intf;
+	int ret, actual_length;
+
+	mutex_lock(&d->usb_mutex);
+
+	memcpy(&dev->buf, wbuf, wlen);
+
+	dev_dbg(&intf->dev, ">>> %*ph\n", wlen, dev->buf);
+
+	ret = usb_bulk_msg(d->udev, usb_sndbulkpipe(d->udev, 0x04), dev->buf,
+			   wlen, &actual_length, 1000);
+	if (ret) {
+		dev_err(&intf->dev, "1st usb_bulk_msg() failed %d\n", ret);
+		goto err_mutex_unlock;
+	}
+
+	if (rlen) {
+		ret = usb_bulk_msg(d->udev, usb_rcvbulkpipe(d->udev, 0x83),
+				   dev->buf, rlen, &actual_length, 1000);
+		if (ret) {
+			dev_err(&intf->dev,
+				"2nd usb_bulk_msg() failed %d\n", ret);
+			goto err_mutex_unlock;
+		}
+
+		dev_dbg(&intf->dev, "<<< %*ph\n", actual_length, dev->buf);
+
+		if (actual_length != rlen) {
+			/*
+			 * Chip replies often with 3 byte len stub. On that case
+			 * we have to query new reply.
+			 */
+			dev_dbg(&intf->dev, "repeating reply message\n");
+
+			ret = usb_bulk_msg(d->udev,
+					   usb_rcvbulkpipe(d->udev, 0x83),
+					   dev->buf, rlen, &actual_length,
+					   1000);
+			if (ret) {
+				dev_err(&intf->dev,
+					"3rd usb_bulk_msg() failed %d\n", ret);
+				goto err_mutex_unlock;
+			}
+
+			dev_dbg(&intf->dev,
+				"<<< %*ph\n", actual_length, dev->buf);
+		}
+
+		memcpy(rbuf, dev->buf, rlen);
+	}
+
+err_mutex_unlock:
+	mutex_unlock(&d->usb_mutex);
+	return ret;
+}
+
+static int zd1301_demod_wreg(void *reg_priv, u16 reg, u8 val)
+{
+	struct dvb_usb_device *d = reg_priv;
+	struct usb_interface *intf = d->intf;
+	int ret;
+	u8 buf[7] = {0x07, 0x00, 0x03, 0x01,
+		     (reg >> 0) & 0xff, (reg >> 8) & 0xff, val};
+
+	ret = zd1301_ctrl_msg(d, buf, 7, NULL, 0);
+	if (ret)
+		goto err;
+
+	return 0;
+err:
+	dev_dbg(&intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static int zd1301_demod_rreg(void *reg_priv, u16 reg, u8 *val)
+{
+	struct dvb_usb_device *d = reg_priv;
+	struct usb_interface *intf = d->intf;
+	int ret;
+	u8 buf[7] = {0x07, 0x00, 0x04, 0x01,
+		     (reg >> 0) & 0xff, (reg >> 8) & 0xff, 0};
+
+	ret = zd1301_ctrl_msg(d, buf, 7, buf, 7);
+	if (ret)
+		goto err;
+
+	*val = buf[6];
+
+	return 0;
+err:
+	dev_dbg(&intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static int zd1301_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct zd1301_dev *dev = adap_to_priv(adap);
+	struct usb_interface *intf = d->intf;
+	struct platform_device *pdev;
+	struct i2c_client *client;
+	struct i2c_board_info board_info;
+	struct i2c_adapter *adapter;
+	struct dvb_frontend *frontend;
+	int ret;
+
+	dev_dbg(&intf->dev, "\n");
+
+	/* Add platform demod */
+	dev->demod_pdata.reg_priv = d;
+	dev->demod_pdata.reg_read = zd1301_demod_rreg;
+	dev->demod_pdata.reg_write = zd1301_demod_wreg;
+	request_module("%s", "zd1301_demod");
+	pdev = platform_device_register_data(&intf->dev,
+					     "zd1301_demod",
+					     PLATFORM_DEVID_AUTO,
+					     &dev->demod_pdata,
+					     sizeof(dev->demod_pdata));
+	if (IS_ERR(pdev)) {
+		ret = PTR_ERR(pdev);
+		goto err;
+	}
+	if (!pdev->dev.driver) {
+		ret = -ENODEV;
+		goto err;
+	}
+	if (!try_module_get(pdev->dev.driver->owner)) {
+		ret = -ENODEV;
+		goto err_platform_device_unregister;
+	}
+
+	adapter = zd1301_demod_get_i2c_adapter(pdev);
+	frontend = zd1301_demod_get_dvb_frontend(pdev);
+	if (!adapter || !frontend) {
+		ret = -ENODEV;
+		goto err_module_put_demod;
+	}
+
+	/* Add I2C tuner */
+	dev->mt2060_pdata.i2c_write_max = 9;
+	dev->mt2060_pdata.dvb_frontend = frontend;
+	memset(&board_info, 0, sizeof(board_info));
+	strlcpy(board_info.type, "mt2060", I2C_NAME_SIZE);
+	board_info.addr = 0x60;
+	board_info.platform_data = &dev->mt2060_pdata;
+	request_module("%s", "mt2060");
+	client = i2c_new_device(adapter, &board_info);
+	if (!client || !client->dev.driver) {
+		ret = -ENODEV;
+		goto err_module_put_demod;
+	}
+	if (!try_module_get(client->dev.driver->owner)) {
+		ret = -ENODEV;
+		goto err_i2c_unregister_device;
+	}
+
+	dev->platform_device_demod = pdev;
+	dev->i2c_client_tuner = client;
+	adap->fe[0] = frontend;
+
+	return 0;
+err_i2c_unregister_device:
+	i2c_unregister_device(client);
+err_module_put_demod:
+	module_put(pdev->dev.driver->owner);
+err_platform_device_unregister:
+	platform_device_unregister(pdev);
+err:
+	dev_dbg(&intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static int zd1301_frontend_detach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *d = adap_to_d(adap);
+	struct zd1301_dev *dev = d_to_priv(d);
+	struct usb_interface *intf = d->intf;
+	struct platform_device *pdev;
+	struct i2c_client *client;
+
+	dev_dbg(&intf->dev, "\n");
+
+	client = dev->i2c_client_tuner;
+	pdev = dev->platform_device_demod;
+
+	/* Remove I2C tuner */
+	if (client) {
+		module_put(client->dev.driver->owner);
+		i2c_unregister_device(client);
+	}
+
+	/* Remove platform demod */
+	if (pdev) {
+		module_put(pdev->dev.driver->owner);
+		platform_device_unregister(pdev);
+	}
+
+	return 0;
+}
+
+static int zd1301_streaming_ctrl(struct dvb_frontend *fe, int onoff)
+{
+	struct dvb_usb_device *d = fe_to_d(fe);
+	struct usb_interface *intf = d->intf;
+	int ret;
+	u8 buf[3] = {0x03, 0x00, onoff ? 0x07 : 0x08};
+
+	dev_dbg(&intf->dev, "onoff=%d\n", onoff);
+
+	ret = zd1301_ctrl_msg(d, buf, 3, NULL, 0);
+	if (ret)
+		goto err;
+
+	return 0;
+err:
+	dev_dbg(&intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static const struct dvb_usb_device_properties zd1301_props = {
+	.driver_name = KBUILD_MODNAME,
+	.owner = THIS_MODULE,
+	.adapter_nr = adapter_nr,
+	.size_of_priv = sizeof(struct zd1301_dev),
+
+	.frontend_attach = zd1301_frontend_attach,
+	.frontend_detach = zd1301_frontend_detach,
+	.streaming_ctrl  = zd1301_streaming_ctrl,
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+			.stream = DVB_USB_STREAM_BULK(0x81, 6, 21 * 188),
+		},
+	},
+};
+
+static const struct usb_device_id zd1301_id_table[] = {
+	{DVB_USB_DEVICE(USB_VID_ZYDAS, 0x13a1, &zd1301_props,
+			"ZyDAS ZD1301 reference design", NULL)},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, zd1301_id_table);
+
+/* Usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver zd1301_usb_driver = {
+	.name = KBUILD_MODNAME,
+	.id_table = zd1301_id_table,
+	.probe = dvb_usbv2_probe,
+	.disconnect = dvb_usbv2_disconnect,
+	.suspend = dvb_usbv2_suspend,
+	.resume = dvb_usbv2_resume,
+	.reset_resume = dvb_usbv2_reset_resume,
+	.no_dynamic_id = 1,
+	.soft_unbind = 1,
+};
+module_usb_driver(zd1301_usb_driver);
+
+MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>");
+MODULE_DESCRIPTION("ZyDAS ZD1301 driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/Kconfig b/drivers/media/usb/dvb-usb/Kconfig
new file mode 100644
index 0000000..513df95
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/Kconfig
@@ -0,0 +1,329 @@
+config DVB_USB
+	tristate "Support for various USB DVB devices"
+	depends on DVB_CORE && USB && I2C && RC_CORE
+	help
+	  By enabling this you will be able to choose the various supported
+	  USB1.1 and USB2.0 DVB devices.
+
+	  Almost every USB device needs a firmware, please look into
+	  <file:Documentation/media/dvb-drivers/dvb-usb.rst>.
+
+	  For a complete list of supported USB devices see the LinuxTV DVB Wiki:
+	  <https://linuxtv.org/wiki/index.php/DVB_USB>
+
+	  Say Y if you own a USB DVB device.
+
+config DVB_USB_DEBUG
+	bool "Enable extended debug support for all DVB-USB devices"
+	depends on DVB_USB
+	help
+	  Say Y if you want to enable debugging. See modinfo dvb-usb (and the
+	  appropriate drivers) for debug levels.
+
+config DVB_USB_DIB3000MC
+	tristate
+	depends on DVB_USB
+	select DVB_DIB3000MC
+	help
+	  This is a module with helper functions for accessing the
+	  DIB3000MC from USB DVB devices. It must be a separate module
+	  in case DVB_USB is built-in and DVB_DIB3000MC is a module,
+	  and gets selected automatically when needed.
+
+config DVB_USB_A800
+	tristate "AVerMedia AverTV DVB-T USB 2.0 (A800)"
+	depends on DVB_USB
+	select DVB_USB_DIB3000MC
+	select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_MT2060 if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y here to support the AVerMedia AverTV DVB-T USB 2.0 (A800) receiver.
+
+config DVB_USB_DIBUSB_MB
+	tristate "DiBcom USB DVB-T devices (based on the DiB3000M-B) (see help for device list)"
+	depends on DVB_USB
+	select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_DIB3000MB
+	depends on DVB_DIB3000MC || !DVB_DIB3000MC
+	select MEDIA_TUNER_MT2060 if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Support for USB 1.1 and 2.0 DVB-T receivers based on reference designs made by
+	  DiBcom (<http://www.dibcom.fr>) equipped with a DiB3000M-B demodulator.
+
+	  For an up-to-date list of devices supported by this driver, have a look
+	  on the Linux-DVB Wiki at www.linuxtv.org.
+
+	  Say Y if you own such a device and want to use it. You should build it as
+	  a module.
+
+config DVB_USB_DIBUSB_MB_FAULTY
+	bool "Support faulty USB IDs"
+	depends on DVB_USB_DIBUSB_MB
+	help
+	  Support for faulty USB IDs due to an invalid EEPROM on some Artec devices.
+
+config DVB_USB_DIBUSB_MC
+	tristate "DiBcom USB DVB-T devices (based on the DiB3000M-C/P) (see help for device list)"
+	depends on DVB_USB
+	select DVB_USB_DIB3000MC
+	select MEDIA_TUNER_MT2060 if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Support for USB2.0 DVB-T receivers based on reference designs made by
+	  DiBcom (<http://www.dibcom.fr>) equipped with a DiB3000M-C/P demodulator.
+
+	  For an up-to-date list of devices supported by this driver, have a look
+	  on the Linux-DVB Wiki at www.linuxtv.org.
+
+	  Say Y if you own such a device and want to use it. You should build it as
+	  a module.
+
+config DVB_USB_DIB0700
+	tristate "DiBcom DiB0700 USB DVB devices (see help for supported devices)"
+	depends on DVB_USB
+	select DVB_DIB7000P if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_DIB7000M if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_DIB8000 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_USB_DIB3000MC if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_S5H1411 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_LGDT3305 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_MN88472 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_TUNER_DIB0070 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_TUNER_DIB0090 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_MT2060 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_MT2266 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_XC2028 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_XC5000 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_XC4000 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_MXL5007T if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_TDA18250 if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Support for USB2.0/1.1 DVB receivers based on the DiB0700 USB bridge. The
+	  USB bridge is also present in devices having the DiB7700 DVB-T-USB
+	  silicon. This chip can be found in devices offered by Hauppauge,
+	  Avermedia and other big and small companies.
+
+	  For an up-to-date list of devices supported by this driver, have a look
+	  on the LinuxTV Wiki at www.linuxtv.org.
+
+	  Say Y if you own such a device and want to use it. You should build it as
+	  a module.
+
+config DVB_USB_UMT_010
+	tristate "HanfTek UMT-010 DVB-T USB2.0 support"
+	depends on DVB_USB
+	select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_USB_DIB3000MC
+	select MEDIA_TUNER_MT2060 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_MT352 if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y here to support the HanfTek UMT-010 USB2.0 stick-sized DVB-T receiver.
+
+config DVB_USB_CXUSB
+	tristate "Conexant USB2.0 hybrid reference design support"
+	depends on DVB_USB
+	select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_CX22702 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_LGDT330X if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_MT352 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_ZL10353 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_DIB7000P if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_TUNER_DIB0070 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_ATBM8830 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_LGS8GXX if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_SI2168 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_SIMPLE if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_XC2028 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_MXL5005S if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_MAX2165 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_SI2157 if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y here to support the Conexant USB2.0 hybrid reference design.
+	  Currently, only DVB and ATSC modes are supported, analog mode
+	  shall be added in the future. Devices that require this module:
+
+	  Medion MD95700 hybrid USB2.0 device.
+	  DViCO FusionHDTV (Bluebird) USB2.0 devices
+
+config DVB_USB_M920X
+	tristate "Uli m920x DVB-T USB2.0 support"
+	depends on DVB_USB
+	select DVB_MT352 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_TDA1004X if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_QT1010 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_TDA827X if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_SIMPLE if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y here to support the MSI Mega Sky 580 USB2.0 DVB-T receiver.
+	  Currently, only devices with a product id of
+	  "DTV USB MINI" (in cold state) are supported.
+	  Firmware required.
+
+config DVB_USB_DIGITV
+	tristate "Nebula Electronics uDigiTV DVB-T USB2.0 support"
+	depends on DVB_USB
+	select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_NXT6000 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_MT352 if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y here to support the Nebula Electronics uDigitV USB2.0 DVB-T receiver.
+
+config DVB_USB_VP7045
+	tristate "TwinhanDTV Alpha/MagicBoxII, DNTV tinyUSB2, Beetle USB2.0 support"
+	depends on DVB_USB
+	help
+	  Say Y here to support the
+
+	    TwinhanDTV Alpha (stick) (VP-7045),
+		TwinhanDTV MagicBox II (VP-7046),
+		DigitalNow TinyUSB 2 DVB-t,
+		DigitalRise USB 2.0 Ter (Beetle) and
+		TYPHOON DVB-T USB DRIVE
+
+	  DVB-T USB2.0 receivers.
+
+config DVB_USB_VP702X
+	tristate "TwinhanDTV StarBox and clones DVB-S USB2.0 support"
+	depends on DVB_USB
+	help
+	  Say Y here to support the
+
+	    TwinhanDTV StarBox,
+		DigitalRise USB Starbox and
+		TYPHOON DVB-S USB 2.0 BOX
+
+	  DVB-S USB2.0 receivers.
+
+config DVB_USB_GP8PSK
+	tristate "GENPIX 8PSK->USB module support"
+	depends on DVB_USB
+	help
+	  Say Y here to support the
+	    GENPIX 8psk module
+
+	  DVB-S USB2.0 receivers.
+
+config DVB_USB_NOVA_T_USB2
+	tristate "Hauppauge WinTV-NOVA-T usb2 DVB-T USB2.0 support"
+	depends on DVB_USB
+	select DVB_USB_DIB3000MC
+	select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_MT2060 if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y here to support the Hauppauge WinTV-NOVA-T usb2 DVB-T USB2.0 receiver.
+
+config DVB_USB_TTUSB2
+	tristate "Pinnacle 400e DVB-S USB2.0 support"
+	depends on DVB_USB
+	select DVB_TDA10086 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_LNBP21 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_TDA826X if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_TDA10023 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_TDA10048 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_TDA827X if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y here to support the Pinnacle 400e DVB-S USB2.0 receiver and
+	  the TechnoTrend CT-3650 CI DVB-C/T USB2.0 receiver. The
+	  firmware protocol used by this module is similar to the one used by the
+	  old ttusb-driver - that's why the module is called dvb-usb-ttusb2.
+
+config DVB_USB_DTT200U
+	tristate "WideView WT-200U and WT-220U (pen) DVB-T USB2.0 support (Yakumo/Hama/Typhoon/Yuan)"
+	depends on DVB_USB
+	help
+	  Say Y here to support the WideView/Yakumo/Hama/Typhoon/Yuan DVB-T USB2.0 receiver.
+
+	  The receivers are also known as DTT200U (Yakumo) and UB300 (Yuan).
+
+	  The WT-220U and its clones are pen-sized.
+
+config DVB_USB_OPERA1
+	tristate "Opera1 DVB-S USB2.0 receiver"
+	depends on DVB_USB
+	select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y here to support the Opera DVB-S USB2.0 receiver.
+
+config DVB_USB_AF9005
+	tristate "Afatech AF9005 DVB-T USB1.1 support"
+	depends on DVB_USB
+	select MEDIA_TUNER_MT2060 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_QT1010 if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y here to support the Afatech AF9005 based DVB-T USB1.1 receiver
+	  and the TerraTec Cinergy T USB XE (Rev.1)
+
+config DVB_USB_AF9005_REMOTE
+	tristate "Afatech AF9005 default remote control support"
+	depends on DVB_USB_AF9005
+	help
+	  Say Y here to support the default remote control decoding for the
+	  Afatech AF9005 based receiver.
+
+config DVB_USB_PCTV452E
+	tristate "Pinnacle PCTV HDTV Pro USB device/TT Connect S2-3600"
+	depends on DVB_USB
+	select TTPCI_EEPROM
+	select DVB_LNBP22 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_STB0899 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_STB6100 if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Support for external USB adapter designed by Pinnacle,
+	  shipped under the brand name 'PCTV HDTV Pro USB'.
+	  Also supports TT Connect S2-3600/3650 cards.
+	  Say Y if you own such a device and want to use it.
+
+config DVB_USB_DW2102
+	tristate "DvbWorld & TeVii DVB-S/S2 USB2.0 support"
+	depends on DVB_USB
+	select DVB_PLL if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_STV0288 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_STB6000 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_CX24116 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_SI21XX if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_TDA10023 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_MT312 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_ZL10039 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_DS3000 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_TS2020 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_STB6100 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_STV6110 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_STV0900 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_M88RS2000 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_M88DS3103 if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y here to support the DvbWorld, TeVii, Prof, TechnoTrend
+	  DVB-S/S2 USB2.0 receivers.
+
+config DVB_USB_CINERGY_T2
+	tristate "Terratec CinergyT2/qanu USB 2.0 DVB-T receiver"
+	depends on DVB_USB
+	help
+	  Support for "TerraTec CinergyT2" USB2.0 Highspeed DVB Receivers
+
+	  Say Y if you own such a device and want to use it.
+
+config DVB_USB_DTV5100
+	tristate "AME DTV-5100 USB2.0 DVB-T support"
+	depends on DVB_USB
+	select DVB_ZL10353 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_QT1010 if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y here to support the AME DTV-5100 USB2.0 DVB-T receiver.
+
+config DVB_USB_AZ6027
+	tristate "Azurewave DVB-S/S2 USB2.0 AZ6027 support"
+	depends on DVB_USB
+	select DVB_STB0899 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_STB6100 if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y here to support the AZ6027 device
+
+config DVB_USB_TECHNISAT_USB2
+	tristate "Technisat DVB-S/S2 USB2.0 support"
+	depends on DVB_USB
+	select DVB_STV090x if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_STV6110x if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Say Y here to support the Technisat USB2 DVB-S/S2 device
diff --git a/drivers/media/usb/dvb-usb/Makefile b/drivers/media/usb/dvb-usb/Makefile
new file mode 100644
index 0000000..407d90c
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/Makefile
@@ -0,0 +1,83 @@
+# SPDX-License-Identifier: GPL-2.0
+dvb-usb-objs += dvb-usb-firmware.o dvb-usb-init.o dvb-usb-urb.o dvb-usb-i2c.o
+dvb-usb-objs += dvb-usb-dvb.o dvb-usb-remote.o usb-urb.o
+obj-$(CONFIG_DVB_USB) += dvb-usb.o
+
+dvb-usb-vp7045-objs := vp7045.o vp7045-fe.o
+obj-$(CONFIG_DVB_USB_VP7045) += dvb-usb-vp7045.o
+
+dvb-usb-vp702x-objs := vp702x.o vp702x-fe.o
+obj-$(CONFIG_DVB_USB_VP702X) += dvb-usb-vp702x.o
+
+dvb-usb-gp8psk-objs := gp8psk.o
+obj-$(CONFIG_DVB_USB_GP8PSK) += dvb-usb-gp8psk.o
+
+dvb-usb-dtt200u-objs := dtt200u.o dtt200u-fe.o
+obj-$(CONFIG_DVB_USB_DTT200U) += dvb-usb-dtt200u.o
+
+dvb-usb-dibusb-common-objs := dibusb-common.o
+
+dvb-usb-dibusb-mc-common-objs := dibusb-mc-common.o
+obj-$(CONFIG_DVB_USB_DIB3000MC)	+= dvb-usb-dibusb-common.o dvb-usb-dibusb-mc-common.o
+
+dvb-usb-a800-objs := a800.o
+obj-$(CONFIG_DVB_USB_A800) += dvb-usb-a800.o
+
+dvb-usb-dibusb-mb-objs := dibusb-mb.o
+obj-$(CONFIG_DVB_USB_DIBUSB_MB) += dvb-usb-dibusb-common.o dvb-usb-dibusb-mb.o
+
+dvb-usb-dibusb-mc-objs := dibusb-mc.o
+obj-$(CONFIG_DVB_USB_DIBUSB_MC) += dvb-usb-dibusb-mc.o
+
+dvb-usb-nova-t-usb2-objs := nova-t-usb2.o
+obj-$(CONFIG_DVB_USB_NOVA_T_USB2) += dvb-usb-nova-t-usb2.o
+
+dvb-usb-umt-010-objs := umt-010.o
+obj-$(CONFIG_DVB_USB_UMT_010) += dvb-usb-umt-010.o
+
+dvb-usb-m920x-objs := m920x.o
+obj-$(CONFIG_DVB_USB_M920X) += dvb-usb-m920x.o
+
+dvb-usb-digitv-objs := digitv.o
+obj-$(CONFIG_DVB_USB_DIGITV) += dvb-usb-digitv.o
+
+dvb-usb-cxusb-objs := cxusb.o
+obj-$(CONFIG_DVB_USB_CXUSB) += dvb-usb-cxusb.o
+
+dvb-usb-ttusb2-objs := ttusb2.o
+obj-$(CONFIG_DVB_USB_TTUSB2) += dvb-usb-ttusb2.o
+
+dvb-usb-dib0700-objs := dib0700_core.o dib0700_devices.o
+obj-$(CONFIG_DVB_USB_DIB0700) += dvb-usb-dib0700.o
+
+dvb-usb-opera-objs := opera1.o
+obj-$(CONFIG_DVB_USB_OPERA1) += dvb-usb-opera.o
+
+dvb-usb-af9005-objs := af9005.o af9005-fe.o
+obj-$(CONFIG_DVB_USB_AF9005) += dvb-usb-af9005.o
+
+dvb-usb-af9005-remote-objs := af9005-remote.o
+obj-$(CONFIG_DVB_USB_AF9005_REMOTE) += dvb-usb-af9005-remote.o
+
+dvb-usb-pctv452e-objs := pctv452e.o
+obj-$(CONFIG_DVB_USB_PCTV452E) += dvb-usb-pctv452e.o
+
+dvb-usb-dw2102-objs := dw2102.o
+obj-$(CONFIG_DVB_USB_DW2102) += dvb-usb-dw2102.o
+
+dvb-usb-dtv5100-objs := dtv5100.o
+obj-$(CONFIG_DVB_USB_DTV5100) += dvb-usb-dtv5100.o
+
+dvb-usb-cinergyT2-objs := cinergyT2-core.o cinergyT2-fe.o
+obj-$(CONFIG_DVB_USB_CINERGY_T2) += dvb-usb-cinergyT2.o
+
+dvb-usb-az6027-objs := az6027.o
+obj-$(CONFIG_DVB_USB_AZ6027) += dvb-usb-az6027.o
+
+dvb-usb-technisat-usb2-objs := technisat-usb2.o
+obj-$(CONFIG_DVB_USB_TECHNISAT_USB2) += dvb-usb-technisat-usb2.o
+
+ccflags-y += -I$(srctree)/drivers/media/dvb-frontends/
+# due to tuner-xc3028
+ccflags-y += -I$(srctree)/drivers/media/tuners
+ccflags-y += -I$(srctree)/drivers/media/pci/ttpci
diff --git a/drivers/media/usb/dvb-usb/a800.c b/drivers/media/usb/dvb-usb/a800.c
new file mode 100644
index 0000000..198bd5e
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/a800.c
@@ -0,0 +1,154 @@
+/* DVB USB framework compliant Linux driver for the AVerMedia AverTV DVB-T
+ * USB2.0 (A800) DVB-T receiver.
+ *
+ * Copyright (C) 2005 Patrick Boettcher (patrick.boettcher@posteo.de)
+ *
+ * Thanks to
+ *   - AVerMedia who kindly provided information and
+ *   - Glen Harris who suffered from my mistakes during development.
+ *
+ *	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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#include "dibusb.h"
+
+static int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "set debugging level (rc=1 (or-able))." DVB_USB_DEBUG_STATUS);
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+#define deb_rc(args...)   dprintk(debug,0x01,args)
+
+static int a800_power_ctrl(struct dvb_usb_device *d, int onoff)
+{
+	/* do nothing for the AVerMedia */
+	return 0;
+}
+
+/* assure to put cold to 0 for iManufacturer == 1 */
+static int a800_identify_state(struct usb_device *udev, struct dvb_usb_device_properties *props,
+	struct dvb_usb_device_description **desc, int *cold)
+{
+	*cold = udev->descriptor.iManufacturer != 1;
+	return 0;
+}
+
+static int a800_rc_query(struct dvb_usb_device *d)
+{
+	int ret = 0;
+	u8 *key = kmalloc(5, GFP_KERNEL);
+	if (!key)
+		return -ENOMEM;
+
+	if (usb_control_msg(d->udev,usb_rcvctrlpipe(d->udev,0),
+				0x04, USB_TYPE_VENDOR | USB_DIR_IN, 0, 0, key, 5,
+				2000) != 5) {
+		ret = -ENODEV;
+		goto out;
+	}
+
+	/* Note that extended nec and nec32 are dropped */
+	if (key[0] == 1)
+		rc_keydown(d->rc_dev, RC_PROTO_NEC,
+			   RC_SCANCODE_NEC(key[1], key[3]), 0);
+	else if (key[0] == 2)
+		rc_repeat(d->rc_dev);
+out:
+	kfree(key);
+	return ret;
+}
+
+/* USB Driver stuff */
+static struct dvb_usb_device_properties a800_properties;
+
+static int a800_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	return dvb_usb_device_init(intf, &a800_properties,
+				   THIS_MODULE, NULL, adapter_nr);
+}
+
+/* do not change the order of the ID table */
+static struct usb_device_id a800_table [] = {
+/* 00 */	{ USB_DEVICE(USB_VID_AVERMEDIA,     USB_PID_AVERMEDIA_DVBT_USB2_COLD) },
+/* 01 */	{ USB_DEVICE(USB_VID_AVERMEDIA,     USB_PID_AVERMEDIA_DVBT_USB2_WARM) },
+			{ }		/* Terminating entry */
+};
+MODULE_DEVICE_TABLE (usb, a800_table);
+
+static struct dvb_usb_device_properties a800_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl = CYPRESS_FX2,
+	.firmware = "dvb-usb-avertv-a800-02.fw",
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.caps = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+			.pid_filter_count = 32,
+			.streaming_ctrl   = dibusb2_0_streaming_ctrl,
+			.pid_filter       = dibusb_pid_filter,
+			.pid_filter_ctrl  = dibusb_pid_filter_ctrl,
+
+			.frontend_attach  = dibusb_dib3000mc_frontend_attach,
+			.tuner_attach     = dibusb_dib3000mc_tuner_attach,
+
+			/* parameter for the MPEG2-data transfer */
+					.stream = {
+						.type = USB_BULK,
+				.count = 7,
+				.endpoint = 0x06,
+				.u = {
+					.bulk = {
+						.buffersize = 4096,
+					}
+				}
+			},
+		}},
+			.size_of_priv     = sizeof(struct dibusb_state),
+		},
+	},
+
+	.power_ctrl       = a800_power_ctrl,
+	.identify_state   = a800_identify_state,
+
+	.rc.core = {
+		.rc_interval	= DEFAULT_RC_INTERVAL,
+		.rc_codes	= RC_MAP_AVERMEDIA_M135A,
+		.module_name	= KBUILD_MODNAME,
+		.rc_query	= a800_rc_query,
+		.allowed_protos = RC_PROTO_BIT_NEC,
+	},
+
+	.i2c_algo         = &dibusb_i2c_algo,
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+	.num_device_descs = 1,
+	.devices = {
+		{   "AVerMedia AverTV DVB-T USB 2.0 (A800)",
+			{ &a800_table[0], NULL },
+			{ &a800_table[1], NULL },
+		},
+	}
+};
+
+static struct usb_driver a800_driver = {
+	.name		= "dvb_usb_a800",
+	.probe		= a800_probe,
+	.disconnect = dvb_usb_device_exit,
+	.id_table	= a800_table,
+};
+
+module_usb_driver(a800_driver);
+
+MODULE_AUTHOR("Patrick Boettcher <patrick.boettcher@posteo.de>");
+MODULE_DESCRIPTION("AVerMedia AverTV DVB-T USB 2.0 (A800)");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/af9005-fe.c b/drivers/media/usb/dvb-usb/af9005-fe.c
new file mode 100644
index 0000000..09cc3a2
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/af9005-fe.c
@@ -0,0 +1,1484 @@
+/* Frontend part of the Linux driver for the Afatech 9005
+ * USB1.1 DVB-T receiver.
+ *
+ * Copyright (C) 2007 Luca Olivetti (luca@ventoso.org)
+ *
+ * Thanks to Afatech who kindly provided information.
+ *
+ * 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.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#include "af9005.h"
+#include "af9005-script.h"
+#include "mt2060.h"
+#include "qt1010.h"
+#include <asm/div64.h>
+
+struct af9005_fe_state {
+	struct dvb_usb_device *d;
+	enum fe_status stat;
+
+	/* retraining parameters */
+	u32 original_fcw;
+	u16 original_rf_top;
+	u16 original_if_top;
+	u16 original_if_min;
+	u16 original_aci0_if_top;
+	u16 original_aci1_if_top;
+	u16 original_aci0_if_min;
+	u8 original_if_unplug_th;
+	u8 original_rf_unplug_th;
+	u8 original_dtop_if_unplug_th;
+	u8 original_dtop_rf_unplug_th;
+
+	/* statistics */
+	u32 pre_vit_error_count;
+	u32 pre_vit_bit_count;
+	u32 ber;
+	u32 post_vit_error_count;
+	u32 post_vit_bit_count;
+	u32 unc;
+	u16 abort_count;
+
+	int opened;
+	int strong;
+	unsigned long next_status_check;
+	struct dvb_frontend frontend;
+};
+
+static int af9005_write_word_agc(struct dvb_usb_device *d, u16 reghi,
+				 u16 reglo, u8 pos, u8 len, u16 value)
+{
+	int ret;
+
+	if ((ret = af9005_write_ofdm_register(d, reglo, (u8) (value & 0xff))))
+		return ret;
+	return af9005_write_register_bits(d, reghi, pos, len,
+					  (u8) ((value & 0x300) >> 8));
+}
+
+static int af9005_read_word_agc(struct dvb_usb_device *d, u16 reghi,
+				u16 reglo, u8 pos, u8 len, u16 * value)
+{
+	int ret;
+	u8 temp0, temp1;
+
+	if ((ret = af9005_read_ofdm_register(d, reglo, &temp0)))
+		return ret;
+	if ((ret = af9005_read_ofdm_register(d, reghi, &temp1)))
+		return ret;
+	switch (pos) {
+	case 0:
+		*value = ((u16) (temp1 & 0x03) << 8) + (u16) temp0;
+		break;
+	case 2:
+		*value = ((u16) (temp1 & 0x0C) << 6) + (u16) temp0;
+		break;
+	case 4:
+		*value = ((u16) (temp1 & 0x30) << 4) + (u16) temp0;
+		break;
+	case 6:
+		*value = ((u16) (temp1 & 0xC0) << 2) + (u16) temp0;
+		break;
+	default:
+		err("invalid pos in read word agc");
+		return -EINVAL;
+	}
+	return 0;
+
+}
+
+static int af9005_is_fecmon_available(struct dvb_frontend *fe, int *available)
+{
+	struct af9005_fe_state *state = fe->demodulator_priv;
+	int ret;
+	u8 temp;
+
+	*available = false;
+
+	ret = af9005_read_register_bits(state->d, xd_p_fec_vtb_rsd_mon_en,
+					fec_vtb_rsd_mon_en_pos,
+					fec_vtb_rsd_mon_en_len, &temp);
+	if (ret)
+		return ret;
+	if (temp & 1) {
+		ret =
+		    af9005_read_register_bits(state->d,
+					      xd_p_reg_ofsm_read_rbc_en,
+					      reg_ofsm_read_rbc_en_pos,
+					      reg_ofsm_read_rbc_en_len, &temp);
+		if (ret)
+			return ret;
+		if ((temp & 1) == 0)
+			*available = true;
+
+	}
+	return 0;
+}
+
+static int af9005_get_post_vit_err_cw_count(struct dvb_frontend *fe,
+					    u32 * post_err_count,
+					    u32 * post_cw_count,
+					    u16 * abort_count)
+{
+	struct af9005_fe_state *state = fe->demodulator_priv;
+	int ret;
+	u32 err_count;
+	u32 cw_count;
+	u8 temp, temp0, temp1, temp2;
+	u16 loc_abort_count;
+
+	*post_err_count = 0;
+	*post_cw_count = 0;
+
+	/* check if error bit count is ready */
+	ret =
+	    af9005_read_register_bits(state->d, xd_r_fec_rsd_ber_rdy,
+				      fec_rsd_ber_rdy_pos, fec_rsd_ber_rdy_len,
+				      &temp);
+	if (ret)
+		return ret;
+	if (!temp) {
+		deb_info("rsd counter not ready\n");
+		return 100;
+	}
+	/* get abort count */
+	ret =
+	    af9005_read_ofdm_register(state->d,
+				      xd_r_fec_rsd_abort_packet_cnt_7_0,
+				      &temp0);
+	if (ret)
+		return ret;
+	ret =
+	    af9005_read_ofdm_register(state->d,
+				      xd_r_fec_rsd_abort_packet_cnt_15_8,
+				      &temp1);
+	if (ret)
+		return ret;
+	loc_abort_count = ((u16) temp1 << 8) + temp0;
+
+	/* get error count */
+	ret =
+	    af9005_read_ofdm_register(state->d, xd_r_fec_rsd_bit_err_cnt_7_0,
+				      &temp0);
+	if (ret)
+		return ret;
+	ret =
+	    af9005_read_ofdm_register(state->d, xd_r_fec_rsd_bit_err_cnt_15_8,
+				      &temp1);
+	if (ret)
+		return ret;
+	ret =
+	    af9005_read_ofdm_register(state->d, xd_r_fec_rsd_bit_err_cnt_23_16,
+				      &temp2);
+	if (ret)
+		return ret;
+	err_count = ((u32) temp2 << 16) + ((u32) temp1 << 8) + temp0;
+	*post_err_count = err_count - (u32) loc_abort_count *8 * 8;
+
+	/* get RSD packet number */
+	ret =
+	    af9005_read_ofdm_register(state->d, xd_p_fec_rsd_packet_unit_7_0,
+				      &temp0);
+	if (ret)
+		return ret;
+	ret =
+	    af9005_read_ofdm_register(state->d, xd_p_fec_rsd_packet_unit_15_8,
+				      &temp1);
+	if (ret)
+		return ret;
+	cw_count = ((u32) temp1 << 8) + temp0;
+	if (cw_count == 0) {
+		err("wrong RSD packet count");
+		return -EIO;
+	}
+	deb_info("POST abort count %d err count %d rsd packets %d\n",
+		 loc_abort_count, err_count, cw_count);
+	*post_cw_count = cw_count - (u32) loc_abort_count;
+	*abort_count = loc_abort_count;
+	return 0;
+
+}
+
+static int af9005_get_post_vit_ber(struct dvb_frontend *fe,
+				   u32 * post_err_count, u32 * post_cw_count,
+				   u16 * abort_count)
+{
+	u32 loc_cw_count = 0, loc_err_count;
+	u16 loc_abort_count = 0;
+	int ret;
+
+	ret =
+	    af9005_get_post_vit_err_cw_count(fe, &loc_err_count, &loc_cw_count,
+					     &loc_abort_count);
+	if (ret)
+		return ret;
+	*post_err_count = loc_err_count;
+	*post_cw_count = loc_cw_count * 204 * 8;
+	*abort_count = loc_abort_count;
+
+	return 0;
+}
+
+static int af9005_get_pre_vit_err_bit_count(struct dvb_frontend *fe,
+					    u32 * pre_err_count,
+					    u32 * pre_bit_count)
+{
+	struct af9005_fe_state *state = fe->demodulator_priv;
+	u8 temp, temp0, temp1, temp2;
+	u32 super_frame_count, x, bits;
+	int ret;
+
+	ret =
+	    af9005_read_register_bits(state->d, xd_r_fec_vtb_ber_rdy,
+				      fec_vtb_ber_rdy_pos, fec_vtb_ber_rdy_len,
+				      &temp);
+	if (ret)
+		return ret;
+	if (!temp) {
+		deb_info("viterbi counter not ready\n");
+		return 101;	/* ERR_APO_VTB_COUNTER_NOT_READY; */
+	}
+	ret =
+	    af9005_read_ofdm_register(state->d, xd_r_fec_vtb_err_bit_cnt_7_0,
+				      &temp0);
+	if (ret)
+		return ret;
+	ret =
+	    af9005_read_ofdm_register(state->d, xd_r_fec_vtb_err_bit_cnt_15_8,
+				      &temp1);
+	if (ret)
+		return ret;
+	ret =
+	    af9005_read_ofdm_register(state->d, xd_r_fec_vtb_err_bit_cnt_23_16,
+				      &temp2);
+	if (ret)
+		return ret;
+	*pre_err_count = ((u32) temp2 << 16) + ((u32) temp1 << 8) + temp0;
+
+	ret =
+	    af9005_read_ofdm_register(state->d, xd_p_fec_super_frm_unit_7_0,
+				      &temp0);
+	if (ret)
+		return ret;
+	ret =
+	    af9005_read_ofdm_register(state->d, xd_p_fec_super_frm_unit_15_8,
+				      &temp1);
+	if (ret)
+		return ret;
+	super_frame_count = ((u32) temp1 << 8) + temp0;
+	if (super_frame_count == 0) {
+		deb_info("super frame count 0\n");
+		return 102;
+	}
+
+	/* read fft mode */
+	ret =
+	    af9005_read_register_bits(state->d, xd_g_reg_tpsd_txmod,
+				      reg_tpsd_txmod_pos, reg_tpsd_txmod_len,
+				      &temp);
+	if (ret)
+		return ret;
+	if (temp == 0) {
+		/* 2K */
+		x = 1512;
+	} else if (temp == 1) {
+		/* 8k */
+		x = 6048;
+	} else {
+		err("Invalid fft mode");
+		return -EINVAL;
+	}
+
+	/* read modulation mode */
+	ret =
+	    af9005_read_register_bits(state->d, xd_g_reg_tpsd_const,
+				      reg_tpsd_const_pos, reg_tpsd_const_len,
+				      &temp);
+	if (ret)
+		return ret;
+	switch (temp) {
+	case 0:		/* QPSK */
+		bits = 2;
+		break;
+	case 1:		/* QAM_16 */
+		bits = 4;
+		break;
+	case 2:		/* QAM_64 */
+		bits = 6;
+		break;
+	default:
+		err("invalid modulation mode");
+		return -EINVAL;
+	}
+	*pre_bit_count = super_frame_count * 68 * 4 * x * bits;
+	deb_info("PRE err count %d frame count %d bit count %d\n",
+		 *pre_err_count, super_frame_count, *pre_bit_count);
+	return 0;
+}
+
+static int af9005_reset_pre_viterbi(struct dvb_frontend *fe)
+{
+	struct af9005_fe_state *state = fe->demodulator_priv;
+	int ret;
+
+	/* set super frame count to 1 */
+	ret =
+	    af9005_write_ofdm_register(state->d, xd_p_fec_super_frm_unit_7_0,
+				       1 & 0xff);
+	if (ret)
+		return ret;
+	ret = af9005_write_ofdm_register(state->d, xd_p_fec_super_frm_unit_15_8,
+					 1 >> 8);
+	if (ret)
+		return ret;
+	/* reset pre viterbi error count */
+	ret =
+	    af9005_write_register_bits(state->d, xd_p_fec_vtb_ber_rst,
+				       fec_vtb_ber_rst_pos, fec_vtb_ber_rst_len,
+				       1);
+
+	return ret;
+}
+
+static int af9005_reset_post_viterbi(struct dvb_frontend *fe)
+{
+	struct af9005_fe_state *state = fe->demodulator_priv;
+	int ret;
+
+	/* set packet unit */
+	ret =
+	    af9005_write_ofdm_register(state->d, xd_p_fec_rsd_packet_unit_7_0,
+				       10000 & 0xff);
+	if (ret)
+		return ret;
+	ret =
+	    af9005_write_ofdm_register(state->d, xd_p_fec_rsd_packet_unit_15_8,
+				       10000 >> 8);
+	if (ret)
+		return ret;
+	/* reset post viterbi error count */
+	ret =
+	    af9005_write_register_bits(state->d, xd_p_fec_rsd_ber_rst,
+				       fec_rsd_ber_rst_pos, fec_rsd_ber_rst_len,
+				       1);
+
+	return ret;
+}
+
+static int af9005_get_statistic(struct dvb_frontend *fe)
+{
+	struct af9005_fe_state *state = fe->demodulator_priv;
+	int ret, fecavailable;
+	u64 numerator, denominator;
+
+	deb_info("GET STATISTIC\n");
+	ret = af9005_is_fecmon_available(fe, &fecavailable);
+	if (ret)
+		return ret;
+	if (!fecavailable) {
+		deb_info("fecmon not available\n");
+		return 0;
+	}
+
+	ret = af9005_get_pre_vit_err_bit_count(fe, &state->pre_vit_error_count,
+					       &state->pre_vit_bit_count);
+	if (ret == 0) {
+		af9005_reset_pre_viterbi(fe);
+		if (state->pre_vit_bit_count > 0) {
+			/* according to v 0.0.4 of the dvb api ber should be a multiple
+			   of 10E-9 so we have to multiply the error count by
+			   10E9=1000000000 */
+			numerator =
+			    (u64) state->pre_vit_error_count * (u64) 1000000000;
+			denominator = (u64) state->pre_vit_bit_count;
+			state->ber = do_div(numerator, denominator);
+		} else {
+			state->ber = 0xffffffff;
+		}
+	}
+
+	ret = af9005_get_post_vit_ber(fe, &state->post_vit_error_count,
+				      &state->post_vit_bit_count,
+				      &state->abort_count);
+	if (ret == 0) {
+		ret = af9005_reset_post_viterbi(fe);
+		state->unc += state->abort_count;
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static int af9005_fe_refresh_state(struct dvb_frontend *fe)
+{
+	struct af9005_fe_state *state = fe->demodulator_priv;
+	if (time_after(jiffies, state->next_status_check)) {
+		deb_info("REFRESH STATE\n");
+
+		/* statistics */
+		if (af9005_get_statistic(fe))
+			err("get_statistic_failed");
+		state->next_status_check = jiffies + 250 * HZ / 1000;
+	}
+	return 0;
+}
+
+static int af9005_fe_read_status(struct dvb_frontend *fe,
+				 enum fe_status *stat)
+{
+	struct af9005_fe_state *state = fe->demodulator_priv;
+	u8 temp;
+	int ret;
+
+	if (fe->ops.tuner_ops.release == NULL)
+		return -ENODEV;
+
+	*stat = 0;
+	ret = af9005_read_register_bits(state->d, xd_p_agc_lock,
+					agc_lock_pos, agc_lock_len, &temp);
+	if (ret)
+		return ret;
+	if (temp)
+		*stat |= FE_HAS_SIGNAL;
+
+	ret = af9005_read_register_bits(state->d, xd_p_fd_tpsd_lock,
+					fd_tpsd_lock_pos, fd_tpsd_lock_len,
+					&temp);
+	if (ret)
+		return ret;
+	if (temp)
+		*stat |= FE_HAS_CARRIER;
+
+	ret = af9005_read_register_bits(state->d,
+					xd_r_mp2if_sync_byte_locked,
+					mp2if_sync_byte_locked_pos,
+					mp2if_sync_byte_locked_pos, &temp);
+	if (ret)
+		return ret;
+	if (temp)
+		*stat |= FE_HAS_SYNC | FE_HAS_VITERBI | FE_HAS_LOCK;
+	if (state->opened)
+		af9005_led_control(state->d, *stat & FE_HAS_LOCK);
+
+	ret =
+	    af9005_read_register_bits(state->d, xd_p_reg_strong_sginal_detected,
+				      reg_strong_sginal_detected_pos,
+				      reg_strong_sginal_detected_len, &temp);
+	if (ret)
+		return ret;
+	if (temp != state->strong) {
+		deb_info("adjust for strong signal %d\n", temp);
+		state->strong = temp;
+	}
+	return 0;
+}
+
+static int af9005_fe_read_ber(struct dvb_frontend *fe, u32 * ber)
+{
+	struct af9005_fe_state *state = fe->demodulator_priv;
+	if (fe->ops.tuner_ops.release  == NULL)
+		return -ENODEV;
+	af9005_fe_refresh_state(fe);
+	*ber = state->ber;
+	return 0;
+}
+
+static int af9005_fe_read_unc_blocks(struct dvb_frontend *fe, u32 * unc)
+{
+	struct af9005_fe_state *state = fe->demodulator_priv;
+	if (fe->ops.tuner_ops.release == NULL)
+		return -ENODEV;
+	af9005_fe_refresh_state(fe);
+	*unc = state->unc;
+	return 0;
+}
+
+static int af9005_fe_read_signal_strength(struct dvb_frontend *fe,
+					  u16 * strength)
+{
+	struct af9005_fe_state *state = fe->demodulator_priv;
+	int ret;
+	u8 if_gain, rf_gain;
+
+	if (fe->ops.tuner_ops.release == NULL)
+		return -ENODEV;
+	ret =
+	    af9005_read_ofdm_register(state->d, xd_r_reg_aagc_rf_gain,
+				      &rf_gain);
+	if (ret)
+		return ret;
+	ret =
+	    af9005_read_ofdm_register(state->d, xd_r_reg_aagc_if_gain,
+				      &if_gain);
+	if (ret)
+		return ret;
+	/* this value has no real meaning, but i don't have the tables that relate
+	   the rf and if gain with the dbm, so I just scale the value */
+	*strength = (512 - rf_gain - if_gain) << 7;
+	return 0;
+}
+
+static int af9005_fe_read_snr(struct dvb_frontend *fe, u16 * snr)
+{
+	/* the snr can be derived from the ber and the modulation
+	   but I don't think this kind of complex calculations belong
+	   in the driver. I may be wrong.... */
+	return -ENOSYS;
+}
+
+static int af9005_fe_program_cfoe(struct dvb_usb_device *d, u32 bw)
+{
+	u8 temp0, temp1, temp2, temp3, buf[4];
+	int ret;
+	u32 NS_coeff1_2048Nu;
+	u32 NS_coeff1_8191Nu;
+	u32 NS_coeff1_8192Nu;
+	u32 NS_coeff1_8193Nu;
+	u32 NS_coeff2_2k;
+	u32 NS_coeff2_8k;
+
+	switch (bw) {
+	case 6000000:
+		NS_coeff1_2048Nu = 0x2ADB6DC;
+		NS_coeff1_8191Nu = 0xAB7313;
+		NS_coeff1_8192Nu = 0xAB6DB7;
+		NS_coeff1_8193Nu = 0xAB685C;
+		NS_coeff2_2k = 0x156DB6E;
+		NS_coeff2_8k = 0x55B6DC;
+		break;
+
+	case 7000000:
+		NS_coeff1_2048Nu = 0x3200001;
+		NS_coeff1_8191Nu = 0xC80640;
+		NS_coeff1_8192Nu = 0xC80000;
+		NS_coeff1_8193Nu = 0xC7F9C0;
+		NS_coeff2_2k = 0x1900000;
+		NS_coeff2_8k = 0x640000;
+		break;
+
+	case 8000000:
+		NS_coeff1_2048Nu = 0x3924926;
+		NS_coeff1_8191Nu = 0xE4996E;
+		NS_coeff1_8192Nu = 0xE49249;
+		NS_coeff1_8193Nu = 0xE48B25;
+		NS_coeff2_2k = 0x1C92493;
+		NS_coeff2_8k = 0x724925;
+		break;
+	default:
+		err("Invalid bandwidth %d.", bw);
+		return -EINVAL;
+	}
+
+	/*
+	 *  write NS_coeff1_2048Nu
+	 */
+
+	temp0 = (u8) (NS_coeff1_2048Nu & 0x000000FF);
+	temp1 = (u8) ((NS_coeff1_2048Nu & 0x0000FF00) >> 8);
+	temp2 = (u8) ((NS_coeff1_2048Nu & 0x00FF0000) >> 16);
+	temp3 = (u8) ((NS_coeff1_2048Nu & 0x03000000) >> 24);
+
+	/*  big endian to make 8051 happy */
+	buf[0] = temp3;
+	buf[1] = temp2;
+	buf[2] = temp1;
+	buf[3] = temp0;
+
+	/*  cfoe_NS_2k_coeff1_25_24 */
+	ret = af9005_write_ofdm_register(d, 0xAE00, buf[0]);
+	if (ret)
+		return ret;
+
+	/*  cfoe_NS_2k_coeff1_23_16 */
+	ret = af9005_write_ofdm_register(d, 0xAE01, buf[1]);
+	if (ret)
+		return ret;
+
+	/*  cfoe_NS_2k_coeff1_15_8 */
+	ret = af9005_write_ofdm_register(d, 0xAE02, buf[2]);
+	if (ret)
+		return ret;
+
+	/*  cfoe_NS_2k_coeff1_7_0 */
+	ret = af9005_write_ofdm_register(d, 0xAE03, buf[3]);
+	if (ret)
+		return ret;
+
+	/*
+	 *  write NS_coeff2_2k
+	 */
+
+	temp0 = (u8) ((NS_coeff2_2k & 0x0000003F));
+	temp1 = (u8) ((NS_coeff2_2k & 0x00003FC0) >> 6);
+	temp2 = (u8) ((NS_coeff2_2k & 0x003FC000) >> 14);
+	temp3 = (u8) ((NS_coeff2_2k & 0x01C00000) >> 22);
+
+	/*  big endian to make 8051 happy */
+	buf[0] = temp3;
+	buf[1] = temp2;
+	buf[2] = temp1;
+	buf[3] = temp0;
+
+	ret = af9005_write_ofdm_register(d, 0xAE04, buf[0]);
+	if (ret)
+		return ret;
+
+	ret = af9005_write_ofdm_register(d, 0xAE05, buf[1]);
+	if (ret)
+		return ret;
+
+	ret = af9005_write_ofdm_register(d, 0xAE06, buf[2]);
+	if (ret)
+		return ret;
+
+	ret = af9005_write_ofdm_register(d, 0xAE07, buf[3]);
+	if (ret)
+		return ret;
+
+	/*
+	 *  write NS_coeff1_8191Nu
+	 */
+
+	temp0 = (u8) ((NS_coeff1_8191Nu & 0x000000FF));
+	temp1 = (u8) ((NS_coeff1_8191Nu & 0x0000FF00) >> 8);
+	temp2 = (u8) ((NS_coeff1_8191Nu & 0x00FFC000) >> 16);
+	temp3 = (u8) ((NS_coeff1_8191Nu & 0x03000000) >> 24);
+
+	/*  big endian to make 8051 happy */
+	buf[0] = temp3;
+	buf[1] = temp2;
+	buf[2] = temp1;
+	buf[3] = temp0;
+
+	ret = af9005_write_ofdm_register(d, 0xAE08, buf[0]);
+	if (ret)
+		return ret;
+
+	ret = af9005_write_ofdm_register(d, 0xAE09, buf[1]);
+	if (ret)
+		return ret;
+
+	ret = af9005_write_ofdm_register(d, 0xAE0A, buf[2]);
+	if (ret)
+		return ret;
+
+	ret = af9005_write_ofdm_register(d, 0xAE0B, buf[3]);
+	if (ret)
+		return ret;
+
+	/*
+	 *  write NS_coeff1_8192Nu
+	 */
+
+	temp0 = (u8) (NS_coeff1_8192Nu & 0x000000FF);
+	temp1 = (u8) ((NS_coeff1_8192Nu & 0x0000FF00) >> 8);
+	temp2 = (u8) ((NS_coeff1_8192Nu & 0x00FFC000) >> 16);
+	temp3 = (u8) ((NS_coeff1_8192Nu & 0x03000000) >> 24);
+
+	/*  big endian to make 8051 happy */
+	buf[0] = temp3;
+	buf[1] = temp2;
+	buf[2] = temp1;
+	buf[3] = temp0;
+
+	ret = af9005_write_ofdm_register(d, 0xAE0C, buf[0]);
+	if (ret)
+		return ret;
+
+	ret = af9005_write_ofdm_register(d, 0xAE0D, buf[1]);
+	if (ret)
+		return ret;
+
+	ret = af9005_write_ofdm_register(d, 0xAE0E, buf[2]);
+	if (ret)
+		return ret;
+
+	ret = af9005_write_ofdm_register(d, 0xAE0F, buf[3]);
+	if (ret)
+		return ret;
+
+	/*
+	 *  write NS_coeff1_8193Nu
+	 */
+
+	temp0 = (u8) ((NS_coeff1_8193Nu & 0x000000FF));
+	temp1 = (u8) ((NS_coeff1_8193Nu & 0x0000FF00) >> 8);
+	temp2 = (u8) ((NS_coeff1_8193Nu & 0x00FFC000) >> 16);
+	temp3 = (u8) ((NS_coeff1_8193Nu & 0x03000000) >> 24);
+
+	/*  big endian to make 8051 happy */
+	buf[0] = temp3;
+	buf[1] = temp2;
+	buf[2] = temp1;
+	buf[3] = temp0;
+
+	ret = af9005_write_ofdm_register(d, 0xAE10, buf[0]);
+	if (ret)
+		return ret;
+
+	ret = af9005_write_ofdm_register(d, 0xAE11, buf[1]);
+	if (ret)
+		return ret;
+
+	ret = af9005_write_ofdm_register(d, 0xAE12, buf[2]);
+	if (ret)
+		return ret;
+
+	ret = af9005_write_ofdm_register(d, 0xAE13, buf[3]);
+	if (ret)
+		return ret;
+
+	/*
+	 *  write NS_coeff2_8k
+	 */
+
+	temp0 = (u8) ((NS_coeff2_8k & 0x0000003F));
+	temp1 = (u8) ((NS_coeff2_8k & 0x00003FC0) >> 6);
+	temp2 = (u8) ((NS_coeff2_8k & 0x003FC000) >> 14);
+	temp3 = (u8) ((NS_coeff2_8k & 0x01C00000) >> 22);
+
+	/*  big endian to make 8051 happy */
+	buf[0] = temp3;
+	buf[1] = temp2;
+	buf[2] = temp1;
+	buf[3] = temp0;
+
+	ret = af9005_write_ofdm_register(d, 0xAE14, buf[0]);
+	if (ret)
+		return ret;
+
+	ret = af9005_write_ofdm_register(d, 0xAE15, buf[1]);
+	if (ret)
+		return ret;
+
+	ret = af9005_write_ofdm_register(d, 0xAE16, buf[2]);
+	if (ret)
+		return ret;
+
+	ret = af9005_write_ofdm_register(d, 0xAE17, buf[3]);
+	return ret;
+
+}
+
+static int af9005_fe_select_bw(struct dvb_usb_device *d, u32 bw)
+{
+	u8 temp;
+	switch (bw) {
+	case 6000000:
+		temp = 0;
+		break;
+	case 7000000:
+		temp = 1;
+		break;
+	case 8000000:
+		temp = 2;
+		break;
+	default:
+		err("Invalid bandwidth %d.", bw);
+		return -EINVAL;
+	}
+	return af9005_write_register_bits(d, xd_g_reg_bw, reg_bw_pos,
+					  reg_bw_len, temp);
+}
+
+static int af9005_fe_power(struct dvb_frontend *fe, int on)
+{
+	struct af9005_fe_state *state = fe->demodulator_priv;
+	u8 temp = on;
+	int ret;
+	deb_info("power %s tuner\n", on ? "on" : "off");
+	ret = af9005_send_command(state->d, 0x03, &temp, 1, NULL, 0);
+	return ret;
+}
+
+static struct mt2060_config af9005_mt2060_config = {
+	0xC0
+};
+
+static struct qt1010_config af9005_qt1010_config = {
+	0xC4
+};
+
+static int af9005_fe_init(struct dvb_frontend *fe)
+{
+	struct af9005_fe_state *state = fe->demodulator_priv;
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	int ret, i, scriptlen;
+	u8 temp, temp0 = 0, temp1 = 0, temp2 = 0;
+	u8 buf[2];
+	u16 if1;
+
+	deb_info("in af9005_fe_init\n");
+
+	/* reset */
+	deb_info("reset\n");
+	if ((ret =
+	     af9005_write_register_bits(state->d, xd_I2C_reg_ofdm_rst_en,
+					4, 1, 0x01)))
+		return ret;
+	if ((ret = af9005_write_ofdm_register(state->d, APO_REG_RESET, 0)))
+		return ret;
+	/* clear ofdm reset */
+	deb_info("clear ofdm reset\n");
+	for (i = 0; i < 150; i++) {
+		if ((ret =
+		     af9005_read_ofdm_register(state->d,
+					       xd_I2C_reg_ofdm_rst, &temp)))
+			return ret;
+		if (temp & (regmask[reg_ofdm_rst_len - 1] << reg_ofdm_rst_pos))
+			break;
+		msleep(10);
+	}
+	if (i == 150)
+		return -ETIMEDOUT;
+
+	/*FIXME in the dump
+	   write B200 A9
+	   write xd_g_reg_ofsm_clk 7
+	   read eepr c6 (2)
+	   read eepr c7 (2)
+	   misc ctrl 3 -> 1
+	   read eepr ca (6)
+	   write xd_g_reg_ofsm_clk 0
+	   write B200 a1
+	 */
+	ret = af9005_write_ofdm_register(state->d, 0xb200, 0xa9);
+	if (ret)
+		return ret;
+	ret = af9005_write_ofdm_register(state->d, xd_g_reg_ofsm_clk, 0x07);
+	if (ret)
+		return ret;
+	temp = 0x01;
+	ret = af9005_send_command(state->d, 0x03, &temp, 1, NULL, 0);
+	if (ret)
+		return ret;
+	ret = af9005_write_ofdm_register(state->d, xd_g_reg_ofsm_clk, 0x00);
+	if (ret)
+		return ret;
+	ret = af9005_write_ofdm_register(state->d, 0xb200, 0xa1);
+	if (ret)
+		return ret;
+
+	temp = regmask[reg_ofdm_rst_len - 1] << reg_ofdm_rst_pos;
+	if ((ret =
+	     af9005_write_register_bits(state->d, xd_I2C_reg_ofdm_rst,
+					reg_ofdm_rst_pos, reg_ofdm_rst_len, 1)))
+		return ret;
+	ret = af9005_write_register_bits(state->d, xd_I2C_reg_ofdm_rst,
+					 reg_ofdm_rst_pos, reg_ofdm_rst_len, 0);
+
+	if (ret)
+		return ret;
+	/* don't know what register aefc is, but this is what the windows driver does */
+	ret = af9005_write_ofdm_register(state->d, 0xaefc, 0);
+	if (ret)
+		return ret;
+
+	/* set stand alone chip */
+	deb_info("set stand alone chip\n");
+	if ((ret =
+	     af9005_write_register_bits(state->d, xd_p_reg_dca_stand_alone,
+					reg_dca_stand_alone_pos,
+					reg_dca_stand_alone_len, 1)))
+		return ret;
+
+	/* set dca upper & lower chip */
+	deb_info("set dca upper & lower chip\n");
+	if ((ret =
+	     af9005_write_register_bits(state->d, xd_p_reg_dca_upper_chip,
+					reg_dca_upper_chip_pos,
+					reg_dca_upper_chip_len, 0)))
+		return ret;
+	if ((ret =
+	     af9005_write_register_bits(state->d, xd_p_reg_dca_lower_chip,
+					reg_dca_lower_chip_pos,
+					reg_dca_lower_chip_len, 0)))
+		return ret;
+
+	/* set 2wire master clock to 0x14 (for 60KHz) */
+	deb_info("set 2wire master clock to 0x14 (for 60KHz)\n");
+	if ((ret =
+	     af9005_write_ofdm_register(state->d, xd_I2C_i2c_m_period, 0x14)))
+		return ret;
+
+	/* clear dca enable chip */
+	deb_info("clear dca enable chip\n");
+	if ((ret =
+	     af9005_write_register_bits(state->d, xd_p_reg_dca_en,
+					reg_dca_en_pos, reg_dca_en_len, 0)))
+		return ret;
+	/* FIXME these are register bits, but I don't know which ones */
+	ret = af9005_write_ofdm_register(state->d, 0xa16c, 1);
+	if (ret)
+		return ret;
+	ret = af9005_write_ofdm_register(state->d, 0xa3c1, 0);
+	if (ret)
+		return ret;
+
+	/* init other parameters: program cfoe and select bandwidth */
+	deb_info("program cfoe\n");
+	ret = af9005_fe_program_cfoe(state->d, 6000000);
+	if (ret)
+		return ret;
+	/* set read-update bit for modulation */
+	deb_info("set read-update bit for modulation\n");
+	if ((ret =
+	     af9005_write_register_bits(state->d, xd_p_reg_feq_read_update,
+					reg_feq_read_update_pos,
+					reg_feq_read_update_len, 1)))
+		return ret;
+
+	/* sample code has a set MPEG TS code here
+	   but sniffing reveals that it doesn't do it */
+
+	/* set read-update bit to 1 for DCA modulation */
+	deb_info("set read-update bit 1 for DCA modulation\n");
+	if ((ret =
+	     af9005_write_register_bits(state->d, xd_p_reg_dca_read_update,
+					reg_dca_read_update_pos,
+					reg_dca_read_update_len, 1)))
+		return ret;
+
+	/* enable fec monitor */
+	deb_info("enable fec monitor\n");
+	if ((ret =
+	     af9005_write_register_bits(state->d, xd_p_fec_vtb_rsd_mon_en,
+					fec_vtb_rsd_mon_en_pos,
+					fec_vtb_rsd_mon_en_len, 1)))
+		return ret;
+
+	/* FIXME should be register bits, I don't know which ones */
+	ret = af9005_write_ofdm_register(state->d, 0xa601, 0);
+
+	/* set api_retrain_never_freeze */
+	deb_info("set api_retrain_never_freeze\n");
+	if ((ret = af9005_write_ofdm_register(state->d, 0xaefb, 0x01)))
+		return ret;
+
+	/* load init script */
+	deb_info("load init script\n");
+	scriptlen = sizeof(script) / sizeof(RegDesc);
+	for (i = 0; i < scriptlen; i++) {
+		if ((ret =
+		     af9005_write_register_bits(state->d, script[i].reg,
+						script[i].pos,
+						script[i].len, script[i].val)))
+			return ret;
+		/* save 3 bytes of original fcw */
+		if (script[i].reg == 0xae18)
+			temp2 = script[i].val;
+		if (script[i].reg == 0xae19)
+			temp1 = script[i].val;
+		if (script[i].reg == 0xae1a)
+			temp0 = script[i].val;
+
+		/* save original unplug threshold */
+		if (script[i].reg == xd_p_reg_unplug_th)
+			state->original_if_unplug_th = script[i].val;
+		if (script[i].reg == xd_p_reg_unplug_rf_gain_th)
+			state->original_rf_unplug_th = script[i].val;
+		if (script[i].reg == xd_p_reg_unplug_dtop_if_gain_th)
+			state->original_dtop_if_unplug_th = script[i].val;
+		if (script[i].reg == xd_p_reg_unplug_dtop_rf_gain_th)
+			state->original_dtop_rf_unplug_th = script[i].val;
+
+	}
+	state->original_fcw =
+	    ((u32) temp2 << 16) + ((u32) temp1 << 8) + (u32) temp0;
+
+
+	/* save original TOPs */
+	deb_info("save original TOPs\n");
+
+	/*  RF TOP */
+	ret =
+	    af9005_read_word_agc(state->d,
+				 xd_p_reg_aagc_rf_top_numerator_9_8,
+				 xd_p_reg_aagc_rf_top_numerator_7_0, 0, 2,
+				 &state->original_rf_top);
+	if (ret)
+		return ret;
+
+	/*  IF TOP */
+	ret =
+	    af9005_read_word_agc(state->d,
+				 xd_p_reg_aagc_if_top_numerator_9_8,
+				 xd_p_reg_aagc_if_top_numerator_7_0, 0, 2,
+				 &state->original_if_top);
+	if (ret)
+		return ret;
+
+	/*  ACI 0 IF TOP */
+	ret =
+	    af9005_read_word_agc(state->d, 0xA60E, 0xA60A, 4, 2,
+				 &state->original_aci0_if_top);
+	if (ret)
+		return ret;
+
+	/*  ACI 1 IF TOP */
+	ret =
+	    af9005_read_word_agc(state->d, 0xA60E, 0xA60B, 6, 2,
+				 &state->original_aci1_if_top);
+	if (ret)
+		return ret;
+
+	/* attach tuner and init */
+	if (fe->ops.tuner_ops.release == NULL) {
+		/* read tuner and board id from eeprom */
+		ret = af9005_read_eeprom(adap->dev, 0xc6, buf, 2);
+		if (ret) {
+			err("Impossible to read EEPROM\n");
+			return ret;
+		}
+		deb_info("Tuner id %d, board id %d\n", buf[0], buf[1]);
+		switch (buf[0]) {
+		case 2:	/* MT2060 */
+			/* read if1 from eeprom */
+			ret = af9005_read_eeprom(adap->dev, 0xc8, buf, 2);
+			if (ret) {
+				err("Impossible to read EEPROM\n");
+				return ret;
+			}
+			if1 = (u16) (buf[0] << 8) + buf[1];
+			if (dvb_attach(mt2060_attach, fe, &adap->dev->i2c_adap,
+					 &af9005_mt2060_config, if1) == NULL) {
+				deb_info("MT2060 attach failed\n");
+				return -ENODEV;
+			}
+			break;
+		case 3:	/* QT1010 */
+		case 9:	/* QT1010B */
+			if (dvb_attach(qt1010_attach, fe, &adap->dev->i2c_adap,
+					&af9005_qt1010_config) ==NULL) {
+				deb_info("QT1010 attach failed\n");
+				return -ENODEV;
+			}
+			break;
+		default:
+			err("Unsupported tuner type %d", buf[0]);
+			return -ENODEV;
+		}
+		ret = fe->ops.tuner_ops.init(fe);
+		if (ret)
+			return ret;
+	}
+
+	deb_info("profit!\n");
+	return 0;
+}
+
+static int af9005_fe_sleep(struct dvb_frontend *fe)
+{
+	return af9005_fe_power(fe, 0);
+}
+
+static int af9005_ts_bus_ctrl(struct dvb_frontend *fe, int acquire)
+{
+	struct af9005_fe_state *state = fe->demodulator_priv;
+
+	if (acquire) {
+		state->opened++;
+	} else {
+
+		state->opened--;
+		if (!state->opened)
+			af9005_led_control(state->d, 0);
+	}
+	return 0;
+}
+
+static int af9005_fe_set_frontend(struct dvb_frontend *fe)
+{
+	struct dtv_frontend_properties *fep = &fe->dtv_property_cache;
+	struct af9005_fe_state *state = fe->demodulator_priv;
+	int ret;
+	u8 temp, temp0, temp1, temp2;
+
+	deb_info("af9005_fe_set_frontend freq %d bw %d\n", fep->frequency,
+		 fep->bandwidth_hz);
+	if (fe->ops.tuner_ops.release == NULL) {
+		err("Tuner not attached");
+		return -ENODEV;
+	}
+
+	deb_info("turn off led\n");
+	/* not in the log */
+	ret = af9005_led_control(state->d, 0);
+	if (ret)
+		return ret;
+	/* not sure about the bits */
+	ret = af9005_write_register_bits(state->d, XD_MP2IF_MISC, 2, 1, 0);
+	if (ret)
+		return ret;
+
+	/* set FCW to default value */
+	deb_info("set FCW to default value\n");
+	temp0 = (u8) (state->original_fcw & 0x000000ff);
+	temp1 = (u8) ((state->original_fcw & 0x0000ff00) >> 8);
+	temp2 = (u8) ((state->original_fcw & 0x00ff0000) >> 16);
+	ret = af9005_write_ofdm_register(state->d, 0xae1a, temp0);
+	if (ret)
+		return ret;
+	ret = af9005_write_ofdm_register(state->d, 0xae19, temp1);
+	if (ret)
+		return ret;
+	ret = af9005_write_ofdm_register(state->d, 0xae18, temp2);
+	if (ret)
+		return ret;
+
+	/* restore original TOPs */
+	deb_info("restore original TOPs\n");
+	ret =
+	    af9005_write_word_agc(state->d,
+				  xd_p_reg_aagc_rf_top_numerator_9_8,
+				  xd_p_reg_aagc_rf_top_numerator_7_0, 0, 2,
+				  state->original_rf_top);
+	if (ret)
+		return ret;
+	ret =
+	    af9005_write_word_agc(state->d,
+				  xd_p_reg_aagc_if_top_numerator_9_8,
+				  xd_p_reg_aagc_if_top_numerator_7_0, 0, 2,
+				  state->original_if_top);
+	if (ret)
+		return ret;
+	ret =
+	    af9005_write_word_agc(state->d, 0xA60E, 0xA60A, 4, 2,
+				  state->original_aci0_if_top);
+	if (ret)
+		return ret;
+	ret =
+	    af9005_write_word_agc(state->d, 0xA60E, 0xA60B, 6, 2,
+				  state->original_aci1_if_top);
+	if (ret)
+		return ret;
+
+	/* select bandwidth */
+	deb_info("select bandwidth");
+	ret = af9005_fe_select_bw(state->d, fep->bandwidth_hz);
+	if (ret)
+		return ret;
+	ret = af9005_fe_program_cfoe(state->d, fep->bandwidth_hz);
+	if (ret)
+		return ret;
+
+	/* clear easy mode flag */
+	deb_info("clear easy mode flag\n");
+	ret = af9005_write_ofdm_register(state->d, 0xaefd, 0);
+	if (ret)
+		return ret;
+
+	/* set unplug threshold to original value */
+	deb_info("set unplug threshold to original value\n");
+	ret =
+	    af9005_write_ofdm_register(state->d, xd_p_reg_unplug_th,
+				       state->original_if_unplug_th);
+	if (ret)
+		return ret;
+	/* set tuner */
+	deb_info("set tuner\n");
+	ret = fe->ops.tuner_ops.set_params(fe);
+	if (ret)
+		return ret;
+
+	/* trigger ofsm */
+	deb_info("trigger ofsm\n");
+	temp = 0;
+	ret = af9005_write_tuner_registers(state->d, 0xffff, &temp, 1);
+	if (ret)
+		return ret;
+
+	/* clear retrain and freeze flag */
+	deb_info("clear retrain and freeze flag\n");
+	ret =
+	    af9005_write_register_bits(state->d,
+				       xd_p_reg_api_retrain_request,
+				       reg_api_retrain_request_pos, 2, 0);
+	if (ret)
+		return ret;
+
+	/* reset pre viterbi and post viterbi registers and statistics */
+	af9005_reset_pre_viterbi(fe);
+	af9005_reset_post_viterbi(fe);
+	state->pre_vit_error_count = 0;
+	state->pre_vit_bit_count = 0;
+	state->ber = 0;
+	state->post_vit_error_count = 0;
+	/* state->unc = 0; commented out since it should be ever increasing */
+	state->abort_count = 0;
+
+	state->next_status_check = jiffies;
+	state->strong = -1;
+
+	return 0;
+}
+
+static int af9005_fe_get_frontend(struct dvb_frontend *fe,
+				  struct dtv_frontend_properties *fep)
+{
+	struct af9005_fe_state *state = fe->demodulator_priv;
+	int ret;
+	u8 temp;
+
+	/* mode */
+	ret =
+	    af9005_read_register_bits(state->d, xd_g_reg_tpsd_const,
+				      reg_tpsd_const_pos, reg_tpsd_const_len,
+				      &temp);
+	if (ret)
+		return ret;
+	deb_info("===== fe_get_frontend_legacy = =============\n");
+	deb_info("CONSTELLATION ");
+	switch (temp) {
+	case 0:
+		fep->modulation = QPSK;
+		deb_info("QPSK\n");
+		break;
+	case 1:
+		fep->modulation = QAM_16;
+		deb_info("QAM_16\n");
+		break;
+	case 2:
+		fep->modulation = QAM_64;
+		deb_info("QAM_64\n");
+		break;
+	}
+
+	/* tps hierarchy and alpha value */
+	ret =
+	    af9005_read_register_bits(state->d, xd_g_reg_tpsd_hier,
+				      reg_tpsd_hier_pos, reg_tpsd_hier_len,
+				      &temp);
+	if (ret)
+		return ret;
+	deb_info("HIERARCHY ");
+	switch (temp) {
+	case 0:
+		fep->hierarchy = HIERARCHY_NONE;
+		deb_info("NONE\n");
+		break;
+	case 1:
+		fep->hierarchy = HIERARCHY_1;
+		deb_info("1\n");
+		break;
+	case 2:
+		fep->hierarchy = HIERARCHY_2;
+		deb_info("2\n");
+		break;
+	case 3:
+		fep->hierarchy = HIERARCHY_4;
+		deb_info("4\n");
+		break;
+	}
+
+	/*  high/low priority     */
+	ret =
+	    af9005_read_register_bits(state->d, xd_g_reg_dec_pri,
+				      reg_dec_pri_pos, reg_dec_pri_len, &temp);
+	if (ret)
+		return ret;
+	/* if temp is set = high priority */
+	deb_info("PRIORITY %s\n", temp ? "high" : "low");
+
+	/* high coderate */
+	ret =
+	    af9005_read_register_bits(state->d, xd_g_reg_tpsd_hpcr,
+				      reg_tpsd_hpcr_pos, reg_tpsd_hpcr_len,
+				      &temp);
+	if (ret)
+		return ret;
+	deb_info("CODERATE HP ");
+	switch (temp) {
+	case 0:
+		fep->code_rate_HP = FEC_1_2;
+		deb_info("FEC_1_2\n");
+		break;
+	case 1:
+		fep->code_rate_HP = FEC_2_3;
+		deb_info("FEC_2_3\n");
+		break;
+	case 2:
+		fep->code_rate_HP = FEC_3_4;
+		deb_info("FEC_3_4\n");
+		break;
+	case 3:
+		fep->code_rate_HP = FEC_5_6;
+		deb_info("FEC_5_6\n");
+		break;
+	case 4:
+		fep->code_rate_HP = FEC_7_8;
+		deb_info("FEC_7_8\n");
+		break;
+	}
+
+	/* low coderate */
+	ret =
+	    af9005_read_register_bits(state->d, xd_g_reg_tpsd_lpcr,
+				      reg_tpsd_lpcr_pos, reg_tpsd_lpcr_len,
+				      &temp);
+	if (ret)
+		return ret;
+	deb_info("CODERATE LP ");
+	switch (temp) {
+	case 0:
+		fep->code_rate_LP = FEC_1_2;
+		deb_info("FEC_1_2\n");
+		break;
+	case 1:
+		fep->code_rate_LP = FEC_2_3;
+		deb_info("FEC_2_3\n");
+		break;
+	case 2:
+		fep->code_rate_LP = FEC_3_4;
+		deb_info("FEC_3_4\n");
+		break;
+	case 3:
+		fep->code_rate_LP = FEC_5_6;
+		deb_info("FEC_5_6\n");
+		break;
+	case 4:
+		fep->code_rate_LP = FEC_7_8;
+		deb_info("FEC_7_8\n");
+		break;
+	}
+
+	/* guard interval */
+	ret =
+	    af9005_read_register_bits(state->d, xd_g_reg_tpsd_gi,
+				      reg_tpsd_gi_pos, reg_tpsd_gi_len, &temp);
+	if (ret)
+		return ret;
+	deb_info("GUARD INTERVAL ");
+	switch (temp) {
+	case 0:
+		fep->guard_interval = GUARD_INTERVAL_1_32;
+		deb_info("1_32\n");
+		break;
+	case 1:
+		fep->guard_interval = GUARD_INTERVAL_1_16;
+		deb_info("1_16\n");
+		break;
+	case 2:
+		fep->guard_interval = GUARD_INTERVAL_1_8;
+		deb_info("1_8\n");
+		break;
+	case 3:
+		fep->guard_interval = GUARD_INTERVAL_1_4;
+		deb_info("1_4\n");
+		break;
+	}
+
+	/* fft */
+	ret =
+	    af9005_read_register_bits(state->d, xd_g_reg_tpsd_txmod,
+				      reg_tpsd_txmod_pos, reg_tpsd_txmod_len,
+				      &temp);
+	if (ret)
+		return ret;
+	deb_info("TRANSMISSION MODE ");
+	switch (temp) {
+	case 0:
+		fep->transmission_mode = TRANSMISSION_MODE_2K;
+		deb_info("2K\n");
+		break;
+	case 1:
+		fep->transmission_mode = TRANSMISSION_MODE_8K;
+		deb_info("8K\n");
+		break;
+	}
+
+	/* bandwidth      */
+	ret =
+	    af9005_read_register_bits(state->d, xd_g_reg_bw, reg_bw_pos,
+				      reg_bw_len, &temp);
+	deb_info("BANDWIDTH ");
+	switch (temp) {
+	case 0:
+		fep->bandwidth_hz = 6000000;
+		deb_info("6\n");
+		break;
+	case 1:
+		fep->bandwidth_hz = 7000000;
+		deb_info("7\n");
+		break;
+	case 2:
+		fep->bandwidth_hz = 8000000;
+		deb_info("8\n");
+		break;
+	}
+	return 0;
+}
+
+static void af9005_fe_release(struct dvb_frontend *fe)
+{
+	struct af9005_fe_state *state =
+	    (struct af9005_fe_state *)fe->demodulator_priv;
+	kfree(state);
+}
+
+static const struct dvb_frontend_ops af9005_fe_ops;
+
+struct dvb_frontend *af9005_fe_attach(struct dvb_usb_device *d)
+{
+	struct af9005_fe_state *state = NULL;
+
+	/* allocate memory for the internal state */
+	state = kzalloc(sizeof(struct af9005_fe_state), GFP_KERNEL);
+	if (state == NULL)
+		goto error;
+
+	deb_info("attaching frontend af9005\n");
+
+	state->d = d;
+	state->opened = 0;
+
+	memcpy(&state->frontend.ops, &af9005_fe_ops,
+	       sizeof(struct dvb_frontend_ops));
+	state->frontend.demodulator_priv = state;
+
+	return &state->frontend;
+      error:
+	return NULL;
+}
+
+static const struct dvb_frontend_ops af9005_fe_ops = {
+	.delsys = { SYS_DVBT },
+	.info = {
+		 .name = "AF9005 USB DVB-T",
+		 .frequency_min_hz =    44250 * kHz,
+		 .frequency_max_hz =   867250 * kHz,
+		 .frequency_stepsize_hz = 250 * kHz,
+		 .caps = FE_CAN_INVERSION_AUTO |
+		 FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 |
+		 FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO |
+		 FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 |
+		 FE_CAN_QAM_AUTO | FE_CAN_TRANSMISSION_MODE_AUTO |
+		 FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_RECOVER |
+		 FE_CAN_HIERARCHY_AUTO,
+		 },
+
+	.release = af9005_fe_release,
+
+	.init = af9005_fe_init,
+	.sleep = af9005_fe_sleep,
+	.ts_bus_ctrl = af9005_ts_bus_ctrl,
+
+	.set_frontend = af9005_fe_set_frontend,
+	.get_frontend = af9005_fe_get_frontend,
+
+	.read_status = af9005_fe_read_status,
+	.read_ber = af9005_fe_read_ber,
+	.read_signal_strength = af9005_fe_read_signal_strength,
+	.read_snr = af9005_fe_read_snr,
+	.read_ucblocks = af9005_fe_read_unc_blocks,
+};
diff --git a/drivers/media/usb/dvb-usb/af9005-remote.c b/drivers/media/usb/dvb-usb/af9005-remote.c
new file mode 100644
index 0000000..f7cdcc8
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/af9005-remote.c
@@ -0,0 +1,153 @@
+/* DVB USB compliant Linux driver for the Afatech 9005
+ * USB1.1 DVB-T receiver.
+ *
+ * Standard remote decode function
+ *
+ * Copyright (C) 2007 Luca Olivetti (luca@ventoso.org)
+ *
+ * Thanks to Afatech who kindly provided information.
+ *
+ * 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.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#include "af9005.h"
+/* debug */
+static int dvb_usb_af9005_remote_debug;
+module_param_named(debug, dvb_usb_af9005_remote_debug, int, 0644);
+MODULE_PARM_DESC(debug,
+		 "enable (1) or disable (0) debug messages."
+		 DVB_USB_DEBUG_STATUS);
+
+#define deb_decode(args...)   dprintk(dvb_usb_af9005_remote_debug,0x01,args)
+
+struct rc_map_table rc_map_af9005_table[] = {
+
+	{0x01b7, KEY_POWER},
+	{0x01a7, KEY_VOLUMEUP},
+	{0x0187, KEY_CHANNELUP},
+	{0x017f, KEY_MUTE},
+	{0x01bf, KEY_VOLUMEDOWN},
+	{0x013f, KEY_CHANNELDOWN},
+	{0x01df, KEY_1},
+	{0x015f, KEY_2},
+	{0x019f, KEY_3},
+	{0x011f, KEY_4},
+	{0x01ef, KEY_5},
+	{0x016f, KEY_6},
+	{0x01af, KEY_7},
+	{0x0127, KEY_8},
+	{0x0107, KEY_9},
+	{0x01cf, KEY_ZOOM},
+	{0x014f, KEY_0},
+	{0x018f, KEY_GOTO},	/* marked jump on the remote */
+
+	{0x00bd, KEY_POWER},
+	{0x007d, KEY_VOLUMEUP},
+	{0x00fd, KEY_CHANNELUP},
+	{0x009d, KEY_MUTE},
+	{0x005d, KEY_VOLUMEDOWN},
+	{0x00dd, KEY_CHANNELDOWN},
+	{0x00ad, KEY_1},
+	{0x006d, KEY_2},
+	{0x00ed, KEY_3},
+	{0x008d, KEY_4},
+	{0x004d, KEY_5},
+	{0x00cd, KEY_6},
+	{0x00b5, KEY_7},
+	{0x0075, KEY_8},
+	{0x00f5, KEY_9},
+	{0x0095, KEY_ZOOM},
+	{0x0055, KEY_0},
+	{0x00d5, KEY_GOTO},	/* marked jump on the remote */
+};
+
+int rc_map_af9005_table_size = ARRAY_SIZE(rc_map_af9005_table);
+
+static int repeatable_keys[] = {
+	KEY_VOLUMEUP,
+	KEY_VOLUMEDOWN,
+	KEY_CHANNELUP,
+	KEY_CHANNELDOWN
+};
+
+int af9005_rc_decode(struct dvb_usb_device *d, u8 * data, int len, u32 * event,
+		     int *state)
+{
+	u16 mark, space;
+	u32 result;
+	u8 cust, dat, invdat;
+	int i;
+
+	if (len >= 6) {
+		mark = (u16) (data[0] << 8) + data[1];
+		space = (u16) (data[2] << 8) + data[3];
+		if (space * 3 < mark) {
+			for (i = 0; i < ARRAY_SIZE(repeatable_keys); i++) {
+				if (d->last_event == repeatable_keys[i]) {
+					*state = REMOTE_KEY_REPEAT;
+					*event = d->last_event;
+					deb_decode("repeat key, event %x\n",
+						   *event);
+					return 0;
+				}
+			}
+			deb_decode("repeated key ignored (non repeatable)\n");
+			return 0;
+		} else if (len >= 33 * 4) {	/*32 bits + start code */
+			result = 0;
+			for (i = 4; i < 4 + 32 * 4; i += 4) {
+				result <<= 1;
+				mark = (u16) (data[i] << 8) + data[i + 1];
+				mark >>= 1;
+				space = (u16) (data[i + 2] << 8) + data[i + 3];
+				space >>= 1;
+				if (mark * 2 > space)
+					result += 1;
+			}
+			deb_decode("key pressed, raw value %x\n", result);
+			if ((result & 0xff000000) != 0xfe000000) {
+				deb_decode
+				    ("doesn't start with 0xfe, ignored\n");
+				return 0;
+			}
+			cust = (result >> 16) & 0xff;
+			dat = (result >> 8) & 0xff;
+			invdat = (~result) & 0xff;
+			if (dat != invdat) {
+				deb_decode("code != inverted code\n");
+				return 0;
+			}
+			for (i = 0; i < rc_map_af9005_table_size; i++) {
+				if (rc5_custom(&rc_map_af9005_table[i]) == cust
+				    && rc5_data(&rc_map_af9005_table[i]) == dat) {
+					*event = rc_map_af9005_table[i].keycode;
+					*state = REMOTE_KEY_PRESSED;
+					deb_decode
+					    ("key pressed, event %x\n", *event);
+					return 0;
+				}
+			}
+			deb_decode("not found in table\n");
+		}
+	}
+	return 0;
+}
+
+EXPORT_SYMBOL(rc_map_af9005_table);
+EXPORT_SYMBOL(rc_map_af9005_table_size);
+EXPORT_SYMBOL(af9005_rc_decode);
+
+MODULE_AUTHOR("Luca Olivetti <luca@ventoso.org>");
+MODULE_DESCRIPTION
+    ("Standard remote control decoder for Afatech 9005 DVB-T USB1.1 stick");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/af9005-script.h b/drivers/media/usb/dvb-usb/af9005-script.h
new file mode 100644
index 0000000..870cb59
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/af9005-script.h
@@ -0,0 +1,204 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+File automatically generated by createinit.py using data
+extracted from AF05BDA.sys (windows driver):
+
+dd if=AF05BDA.sys of=initsequence bs=1 skip=88316 count=1110
+python createinit.py > af9005-script.h
+
+*/
+
+typedef struct {
+	u16 reg;
+	u8 pos;
+	u8 len;
+	u8 val;
+} RegDesc;
+
+static RegDesc script[] = {
+	{0xa180, 0x0, 0x8, 0xa},
+	{0xa181, 0x0, 0x8, 0xd7},
+	{0xa182, 0x0, 0x8, 0xa3},
+	{0xa0a0, 0x0, 0x8, 0x0},
+	{0xa0a1, 0x0, 0x5, 0x0},
+	{0xa0a1, 0x5, 0x1, 0x1},
+	{0xa0c0, 0x0, 0x4, 0x1},
+	{0xa20e, 0x4, 0x4, 0xa},
+	{0xa20f, 0x0, 0x8, 0x40},
+	{0xa210, 0x0, 0x8, 0x8},
+	{0xa32a, 0x0, 0x4, 0xa},
+	{0xa32c, 0x0, 0x8, 0x20},
+	{0xa32b, 0x0, 0x8, 0x15},
+	{0xa1a0, 0x1, 0x1, 0x1},
+	{0xa000, 0x0, 0x1, 0x1},
+	{0xa000, 0x1, 0x1, 0x0},
+	{0xa001, 0x1, 0x1, 0x1},
+	{0xa001, 0x0, 0x1, 0x0},
+	{0xa001, 0x5, 0x1, 0x0},
+	{0xa00e, 0x0, 0x5, 0x10},
+	{0xa00f, 0x0, 0x3, 0x4},
+	{0xa00f, 0x3, 0x3, 0x5},
+	{0xa010, 0x0, 0x3, 0x4},
+	{0xa010, 0x3, 0x3, 0x5},
+	{0xa016, 0x4, 0x4, 0x3},
+	{0xa01f, 0x0, 0x6, 0xa},
+	{0xa020, 0x0, 0x6, 0xa},
+	{0xa2bc, 0x0, 0x1, 0x1},
+	{0xa2bc, 0x5, 0x1, 0x1},
+	{0xa015, 0x0, 0x8, 0x50},
+	{0xa016, 0x0, 0x1, 0x0},
+	{0xa02a, 0x0, 0x8, 0x50},
+	{0xa029, 0x0, 0x8, 0x4b},
+	{0xa614, 0x0, 0x8, 0x46},
+	{0xa002, 0x0, 0x5, 0x19},
+	{0xa003, 0x0, 0x5, 0x1a},
+	{0xa004, 0x0, 0x5, 0x19},
+	{0xa005, 0x0, 0x5, 0x1a},
+	{0xa008, 0x0, 0x8, 0x69},
+	{0xa009, 0x0, 0x2, 0x2},
+	{0xae1b, 0x0, 0x8, 0x69},
+	{0xae1c, 0x0, 0x8, 0x2},
+	{0xae1d, 0x0, 0x8, 0x2a},
+	{0xa022, 0x0, 0x8, 0xaa},
+	{0xa006, 0x0, 0x8, 0xc8},
+	{0xa007, 0x0, 0x2, 0x0},
+	{0xa00c, 0x0, 0x8, 0xba},
+	{0xa00d, 0x0, 0x2, 0x2},
+	{0xa608, 0x0, 0x8, 0xba},
+	{0xa60e, 0x0, 0x2, 0x2},
+	{0xa609, 0x0, 0x8, 0x80},
+	{0xa60e, 0x2, 0x2, 0x3},
+	{0xa00a, 0x0, 0x8, 0xb6},
+	{0xa00b, 0x0, 0x2, 0x0},
+	{0xa011, 0x0, 0x8, 0xb9},
+	{0xa012, 0x0, 0x2, 0x0},
+	{0xa013, 0x0, 0x8, 0xbd},
+	{0xa014, 0x0, 0x2, 0x2},
+	{0xa366, 0x0, 0x1, 0x1},
+	{0xa2bc, 0x3, 0x1, 0x0},
+	{0xa2bd, 0x0, 0x8, 0xa},
+	{0xa2be, 0x0, 0x8, 0x14},
+	{0xa2bf, 0x0, 0x8, 0x8},
+	{0xa60a, 0x0, 0x8, 0xbd},
+	{0xa60e, 0x4, 0x2, 0x2},
+	{0xa60b, 0x0, 0x8, 0x86},
+	{0xa60e, 0x6, 0x2, 0x3},
+	{0xa001, 0x2, 0x2, 0x1},
+	{0xa1c7, 0x0, 0x8, 0xf5},
+	{0xa03d, 0x0, 0x8, 0xb1},
+	{0xa616, 0x0, 0x8, 0xff},
+	{0xa617, 0x0, 0x8, 0xad},
+	{0xa618, 0x0, 0x8, 0xad},
+	{0xa61e, 0x3, 0x1, 0x1},
+	{0xae1a, 0x0, 0x8, 0x0},
+	{0xae19, 0x0, 0x8, 0xc8},
+	{0xae18, 0x0, 0x8, 0x61},
+	{0xa140, 0x0, 0x8, 0x0},
+	{0xa141, 0x0, 0x8, 0xc8},
+	{0xa142, 0x0, 0x7, 0x61},
+	{0xa023, 0x0, 0x8, 0xff},
+	{0xa021, 0x0, 0x8, 0xad},
+	{0xa026, 0x0, 0x1, 0x0},
+	{0xa024, 0x0, 0x8, 0xff},
+	{0xa025, 0x0, 0x8, 0xff},
+	{0xa1c8, 0x0, 0x8, 0xf},
+	{0xa2bc, 0x1, 0x1, 0x0},
+	{0xa60c, 0x0, 0x4, 0x5},
+	{0xa60c, 0x4, 0x4, 0x6},
+	{0xa60d, 0x0, 0x8, 0xa},
+	{0xa371, 0x0, 0x1, 0x1},
+	{0xa366, 0x1, 0x3, 0x7},
+	{0xa338, 0x0, 0x8, 0x10},
+	{0xa339, 0x0, 0x6, 0x7},
+	{0xa33a, 0x0, 0x6, 0x1f},
+	{0xa33b, 0x0, 0x8, 0xf6},
+	{0xa33c, 0x3, 0x5, 0x4},
+	{0xa33d, 0x4, 0x4, 0x0},
+	{0xa33d, 0x1, 0x1, 0x1},
+	{0xa33d, 0x2, 0x1, 0x1},
+	{0xa33d, 0x3, 0x1, 0x1},
+	{0xa16d, 0x0, 0x4, 0xf},
+	{0xa161, 0x0, 0x5, 0x5},
+	{0xa162, 0x0, 0x4, 0x5},
+	{0xa165, 0x0, 0x8, 0xff},
+	{0xa166, 0x0, 0x8, 0x9c},
+	{0xa2c3, 0x0, 0x4, 0x5},
+	{0xa61a, 0x0, 0x6, 0xf},
+	{0xb200, 0x0, 0x8, 0xa1},
+	{0xb201, 0x0, 0x8, 0x7},
+	{0xa093, 0x0, 0x1, 0x0},
+	{0xa093, 0x1, 0x5, 0xf},
+	{0xa094, 0x0, 0x8, 0xff},
+	{0xa095, 0x0, 0x8, 0xf},
+	{0xa080, 0x2, 0x5, 0x3},
+	{0xa081, 0x0, 0x4, 0x0},
+	{0xa081, 0x4, 0x4, 0x9},
+	{0xa082, 0x0, 0x5, 0x1f},
+	{0xa08d, 0x0, 0x8, 0x1},
+	{0xa083, 0x0, 0x8, 0x32},
+	{0xa084, 0x0, 0x1, 0x0},
+	{0xa08e, 0x0, 0x8, 0x3},
+	{0xa085, 0x0, 0x8, 0x32},
+	{0xa086, 0x0, 0x3, 0x0},
+	{0xa087, 0x0, 0x8, 0x6e},
+	{0xa088, 0x0, 0x5, 0x15},
+	{0xa089, 0x0, 0x8, 0x0},
+	{0xa08a, 0x0, 0x5, 0x19},
+	{0xa08b, 0x0, 0x8, 0x92},
+	{0xa08c, 0x0, 0x5, 0x1c},
+	{0xa120, 0x0, 0x8, 0x0},
+	{0xa121, 0x0, 0x5, 0x10},
+	{0xa122, 0x0, 0x8, 0x0},
+	{0xa123, 0x0, 0x7, 0x40},
+	{0xa123, 0x7, 0x1, 0x0},
+	{0xa124, 0x0, 0x8, 0x13},
+	{0xa125, 0x0, 0x7, 0x10},
+	{0xa1c0, 0x0, 0x8, 0x0},
+	{0xa1c1, 0x0, 0x5, 0x4},
+	{0xa1c2, 0x0, 0x8, 0x0},
+	{0xa1c3, 0x0, 0x5, 0x10},
+	{0xa1c3, 0x5, 0x3, 0x0},
+	{0xa1c4, 0x0, 0x6, 0x0},
+	{0xa1c5, 0x0, 0x7, 0x10},
+	{0xa100, 0x0, 0x8, 0x0},
+	{0xa101, 0x0, 0x5, 0x10},
+	{0xa102, 0x0, 0x8, 0x0},
+	{0xa103, 0x0, 0x7, 0x40},
+	{0xa103, 0x7, 0x1, 0x0},
+	{0xa104, 0x0, 0x8, 0x18},
+	{0xa105, 0x0, 0x7, 0xa},
+	{0xa106, 0x0, 0x8, 0x20},
+	{0xa107, 0x0, 0x8, 0x40},
+	{0xa108, 0x0, 0x4, 0x0},
+	{0xa38c, 0x0, 0x8, 0xfc},
+	{0xa38d, 0x0, 0x8, 0x0},
+	{0xa38e, 0x0, 0x8, 0x7e},
+	{0xa38f, 0x0, 0x8, 0x0},
+	{0xa390, 0x0, 0x8, 0x2f},
+	{0xa60f, 0x5, 0x1, 0x1},
+	{0xa170, 0x0, 0x8, 0xdc},
+	{0xa171, 0x0, 0x2, 0x0},
+	{0xa2ae, 0x0, 0x1, 0x1},
+	{0xa2ae, 0x1, 0x1, 0x1},
+	{0xa392, 0x0, 0x1, 0x1},
+	{0xa391, 0x2, 0x1, 0x0},
+	{0xabc1, 0x0, 0x8, 0xff},
+	{0xabc2, 0x0, 0x8, 0x0},
+	{0xabc8, 0x0, 0x8, 0x8},
+	{0xabca, 0x0, 0x8, 0x10},
+	{0xabcb, 0x0, 0x1, 0x0},
+	{0xabc3, 0x5, 0x3, 0x7},
+	{0xabc0, 0x6, 0x1, 0x0},
+	{0xabc0, 0x4, 0x2, 0x0},
+	{0xa344, 0x4, 0x4, 0x1},
+	{0xabc0, 0x7, 0x1, 0x1},
+	{0xabc0, 0x2, 0x1, 0x1},
+	{0xa345, 0x0, 0x8, 0x66},
+	{0xa346, 0x0, 0x8, 0x66},
+	{0xa347, 0x0, 0x4, 0x0},
+	{0xa343, 0x0, 0x4, 0xa},
+	{0xa347, 0x4, 0x4, 0x2},
+	{0xa348, 0x0, 0x4, 0xc},
+	{0xa348, 0x4, 0x4, 0x7},
+	{0xa349, 0x0, 0x6, 0x2},
+};
diff --git a/drivers/media/usb/dvb-usb/af9005.c b/drivers/media/usb/dvb-usb/af9005.c
new file mode 100644
index 0000000..16e946e
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/af9005.c
@@ -0,0 +1,1145 @@
+/* DVB USB compliant Linux driver for the Afatech 9005
+ * USB1.1 DVB-T receiver.
+ *
+ * Copyright (C) 2007 Luca Olivetti (luca@ventoso.org)
+ *
+ * Thanks to Afatech who kindly provided information.
+ *
+ * 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.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#include "af9005.h"
+
+/* debug */
+int dvb_usb_af9005_debug;
+module_param_named(debug, dvb_usb_af9005_debug, int, 0644);
+MODULE_PARM_DESC(debug,
+		 "set debugging level (1=info,xfer=2,rc=4,reg=8,i2c=16,fw=32 (or-able))."
+		 DVB_USB_DEBUG_STATUS);
+/* enable obnoxious led */
+bool dvb_usb_af9005_led = true;
+module_param_named(led, dvb_usb_af9005_led, bool, 0644);
+MODULE_PARM_DESC(led, "enable led (default: 1).");
+
+/* eeprom dump */
+static int dvb_usb_af9005_dump_eeprom;
+module_param_named(dump_eeprom, dvb_usb_af9005_dump_eeprom, int, 0);
+MODULE_PARM_DESC(dump_eeprom, "dump contents of the eeprom.");
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+/* remote control decoder */
+static int (*rc_decode) (struct dvb_usb_device *d, u8 *data, int len,
+		u32 *event, int *state);
+static void *rc_keys;
+static int *rc_keys_size;
+
+u8 regmask[8] = { 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff };
+
+struct af9005_device_state {
+	u8 sequence;
+	int led_state;
+	unsigned char data[256];
+};
+
+static int af9005_generic_read_write(struct dvb_usb_device *d, u16 reg,
+			      int readwrite, int type, u8 * values, int len)
+{
+	struct af9005_device_state *st = d->priv;
+	u8 command, seq;
+	int i, ret;
+
+	if (len < 1) {
+		err("generic read/write, less than 1 byte. Makes no sense.");
+		return -EINVAL;
+	}
+	if (len > 8) {
+		err("generic read/write, more than 8 bytes. Not supported.");
+		return -EINVAL;
+	}
+
+	mutex_lock(&d->data_mutex);
+	st->data[0] = 14;		/* rest of buffer length low */
+	st->data[1] = 0;		/* rest of buffer length high */
+
+	st->data[2] = AF9005_REGISTER_RW;	/* register operation */
+	st->data[3] = 12;		/* rest of buffer length */
+
+	st->data[4] = seq = st->sequence++;	/* sequence number */
+
+	st->data[5] = (u8) (reg >> 8);	/* register address */
+	st->data[6] = (u8) (reg & 0xff);
+
+	if (type == AF9005_OFDM_REG) {
+		command = AF9005_CMD_OFDM_REG;
+	} else {
+		command = AF9005_CMD_TUNER;
+	}
+
+	if (len > 1)
+		command |=
+		    AF9005_CMD_BURST | AF9005_CMD_AUTOINC | (len - 1) << 3;
+	command |= readwrite;
+	if (readwrite == AF9005_CMD_WRITE)
+		for (i = 0; i < len; i++)
+			st->data[8 + i] = values[i];
+	else if (type == AF9005_TUNER_REG)
+		/* read command for tuner, the first byte contains the i2c address */
+		st->data[8] = values[0];
+	st->data[7] = command;
+
+	ret = dvb_usb_generic_rw(d, st->data, 16, st->data, 17, 0);
+	if (ret)
+		goto ret;
+
+	/* sanity check */
+	if (st->data[2] != AF9005_REGISTER_RW_ACK) {
+		err("generic read/write, wrong reply code.");
+		ret = -EIO;
+		goto ret;
+	}
+	if (st->data[3] != 0x0d) {
+		err("generic read/write, wrong length in reply.");
+		ret = -EIO;
+		goto ret;
+	}
+	if (st->data[4] != seq) {
+		err("generic read/write, wrong sequence in reply.");
+		ret = -EIO;
+		goto ret;
+	}
+	/*
+	 * In thesis, both input and output buffers should have
+	 * identical values for st->data[5] to st->data[8].
+	 * However, windows driver doesn't check these fields, in fact
+	 * sometimes the register in the reply is different that what
+	 * has been sent
+	 */
+	if (st->data[16] != 0x01) {
+		err("generic read/write wrong status code in reply.");
+		ret = -EIO;
+		goto ret;
+	}
+
+	if (readwrite == AF9005_CMD_READ)
+		for (i = 0; i < len; i++)
+			values[i] = st->data[8 + i];
+
+ret:
+	mutex_unlock(&d->data_mutex);
+	return ret;
+
+}
+
+int af9005_read_ofdm_register(struct dvb_usb_device *d, u16 reg, u8 * value)
+{
+	int ret;
+	deb_reg("read register %x ", reg);
+	ret = af9005_generic_read_write(d, reg,
+					AF9005_CMD_READ, AF9005_OFDM_REG,
+					value, 1);
+	if (ret)
+		deb_reg("failed\n");
+	else
+		deb_reg("value %x\n", *value);
+	return ret;
+}
+
+int af9005_read_ofdm_registers(struct dvb_usb_device *d, u16 reg,
+			       u8 * values, int len)
+{
+	int ret;
+	deb_reg("read %d registers %x ", len, reg);
+	ret = af9005_generic_read_write(d, reg,
+					AF9005_CMD_READ, AF9005_OFDM_REG,
+					values, len);
+	if (ret)
+		deb_reg("failed\n");
+	else
+		debug_dump(values, len, deb_reg);
+	return ret;
+}
+
+int af9005_write_ofdm_register(struct dvb_usb_device *d, u16 reg, u8 value)
+{
+	int ret;
+	u8 temp = value;
+	deb_reg("write register %x value %x ", reg, value);
+	ret = af9005_generic_read_write(d, reg,
+					AF9005_CMD_WRITE, AF9005_OFDM_REG,
+					&temp, 1);
+	if (ret)
+		deb_reg("failed\n");
+	else
+		deb_reg("ok\n");
+	return ret;
+}
+
+int af9005_write_ofdm_registers(struct dvb_usb_device *d, u16 reg,
+				u8 * values, int len)
+{
+	int ret;
+	deb_reg("write %d registers %x values ", len, reg);
+	debug_dump(values, len, deb_reg);
+
+	ret = af9005_generic_read_write(d, reg,
+					AF9005_CMD_WRITE, AF9005_OFDM_REG,
+					values, len);
+	if (ret)
+		deb_reg("failed\n");
+	else
+		deb_reg("ok\n");
+	return ret;
+}
+
+int af9005_read_register_bits(struct dvb_usb_device *d, u16 reg, u8 pos,
+			      u8 len, u8 * value)
+{
+	u8 temp;
+	int ret;
+	deb_reg("read bits %x %x %x", reg, pos, len);
+	ret = af9005_read_ofdm_register(d, reg, &temp);
+	if (ret) {
+		deb_reg(" failed\n");
+		return ret;
+	}
+	*value = (temp >> pos) & regmask[len - 1];
+	deb_reg(" value %x\n", *value);
+	return 0;
+
+}
+
+int af9005_write_register_bits(struct dvb_usb_device *d, u16 reg, u8 pos,
+			       u8 len, u8 value)
+{
+	u8 temp, mask;
+	int ret;
+	deb_reg("write bits %x %x %x value %x\n", reg, pos, len, value);
+	if (pos == 0 && len == 8)
+		return af9005_write_ofdm_register(d, reg, value);
+	ret = af9005_read_ofdm_register(d, reg, &temp);
+	if (ret)
+		return ret;
+	mask = regmask[len - 1] << pos;
+	temp = (temp & ~mask) | ((value << pos) & mask);
+	return af9005_write_ofdm_register(d, reg, temp);
+
+}
+
+static int af9005_usb_read_tuner_registers(struct dvb_usb_device *d,
+					   u16 reg, u8 * values, int len)
+{
+	return af9005_generic_read_write(d, reg,
+					 AF9005_CMD_READ, AF9005_TUNER_REG,
+					 values, len);
+}
+
+static int af9005_usb_write_tuner_registers(struct dvb_usb_device *d,
+					    u16 reg, u8 * values, int len)
+{
+	return af9005_generic_read_write(d, reg,
+					 AF9005_CMD_WRITE,
+					 AF9005_TUNER_REG, values, len);
+}
+
+int af9005_write_tuner_registers(struct dvb_usb_device *d, u16 reg,
+				 u8 * values, int len)
+{
+	/* don't let the name of this function mislead you: it's just used
+	   as an interface from the firmware to the i2c bus. The actual
+	   i2c addresses are contained in the data */
+	int ret, i, done = 0, fail = 0;
+	u8 temp;
+	ret = af9005_usb_write_tuner_registers(d, reg, values, len);
+	if (ret)
+		return ret;
+	if (reg != 0xffff) {
+		/* check if write done (0xa40d bit 1) or fail (0xa40d bit 2) */
+		for (i = 0; i < 200; i++) {
+			ret =
+			    af9005_read_ofdm_register(d,
+						      xd_I2C_i2c_m_status_wdat_done,
+						      &temp);
+			if (ret)
+				return ret;
+			done = temp & (regmask[i2c_m_status_wdat_done_len - 1]
+				       << i2c_m_status_wdat_done_pos);
+			if (done)
+				break;
+			fail = temp & (regmask[i2c_m_status_wdat_fail_len - 1]
+				       << i2c_m_status_wdat_fail_pos);
+			if (fail)
+				break;
+			msleep(50);
+		}
+		if (i == 200)
+			return -ETIMEDOUT;
+		if (fail) {
+			/* clear write fail bit */
+			af9005_write_register_bits(d,
+						   xd_I2C_i2c_m_status_wdat_fail,
+						   i2c_m_status_wdat_fail_pos,
+						   i2c_m_status_wdat_fail_len,
+						   1);
+			return -EIO;
+		}
+		/* clear write done bit */
+		ret =
+		    af9005_write_register_bits(d,
+					       xd_I2C_i2c_m_status_wdat_fail,
+					       i2c_m_status_wdat_done_pos,
+					       i2c_m_status_wdat_done_len, 1);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+int af9005_read_tuner_registers(struct dvb_usb_device *d, u16 reg, u8 addr,
+				u8 * values, int len)
+{
+	/* don't let the name of this function mislead you: it's just used
+	   as an interface from the firmware to the i2c bus. The actual
+	   i2c addresses are contained in the data */
+	int ret, i;
+	u8 temp, buf[2];
+
+	buf[0] = addr;		/* tuner i2c address */
+	buf[1] = values[0];	/* tuner register */
+
+	values[0] = addr + 0x01;	/* i2c read address */
+
+	if (reg == APO_REG_I2C_RW_SILICON_TUNER) {
+		/* write tuner i2c address to tuner, 0c00c0 undocumented, found by sniffing */
+		ret = af9005_write_tuner_registers(d, 0x00c0, buf, 2);
+		if (ret)
+			return ret;
+	}
+
+	/* send read command to ofsm */
+	ret = af9005_usb_read_tuner_registers(d, reg, values, 1);
+	if (ret)
+		return ret;
+
+	/* check if read done */
+	for (i = 0; i < 200; i++) {
+		ret = af9005_read_ofdm_register(d, 0xa408, &temp);
+		if (ret)
+			return ret;
+		if (temp & 0x01)
+			break;
+		msleep(50);
+	}
+	if (i == 200)
+		return -ETIMEDOUT;
+
+	/* clear read done bit (by writing 1) */
+	ret = af9005_write_ofdm_register(d, xd_I2C_i2c_m_data8, 1);
+	if (ret)
+		return ret;
+
+	/* get read data (available from 0xa400) */
+	for (i = 0; i < len; i++) {
+		ret = af9005_read_ofdm_register(d, 0xa400 + i, &temp);
+		if (ret)
+			return ret;
+		values[i] = temp;
+	}
+	return 0;
+}
+
+static int af9005_i2c_write(struct dvb_usb_device *d, u8 i2caddr, u8 reg,
+			    u8 * data, int len)
+{
+	int ret, i;
+	u8 buf[3];
+	deb_i2c("i2c_write i2caddr %x, reg %x, len %d data ", i2caddr,
+		reg, len);
+	debug_dump(data, len, deb_i2c);
+
+	for (i = 0; i < len; i++) {
+		buf[0] = i2caddr;
+		buf[1] = reg + (u8) i;
+		buf[2] = data[i];
+		ret =
+		    af9005_write_tuner_registers(d,
+						 APO_REG_I2C_RW_SILICON_TUNER,
+						 buf, 3);
+		if (ret) {
+			deb_i2c("i2c_write failed\n");
+			return ret;
+		}
+	}
+	deb_i2c("i2c_write ok\n");
+	return 0;
+}
+
+static int af9005_i2c_read(struct dvb_usb_device *d, u8 i2caddr, u8 reg,
+			   u8 * data, int len)
+{
+	int ret, i;
+	u8 temp;
+	deb_i2c("i2c_read i2caddr %x, reg %x, len %d\n ", i2caddr, reg, len);
+	for (i = 0; i < len; i++) {
+		temp = reg + i;
+		ret =
+		    af9005_read_tuner_registers(d,
+						APO_REG_I2C_RW_SILICON_TUNER,
+						i2caddr, &temp, 1);
+		if (ret) {
+			deb_i2c("i2c_read failed\n");
+			return ret;
+		}
+		data[i] = temp;
+	}
+	deb_i2c("i2c data read: ");
+	debug_dump(data, len, deb_i2c);
+	return 0;
+}
+
+static int af9005_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[],
+			   int num)
+{
+	/* only implements what the mt2060 module does, don't know how
+	   to make it really generic */
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	int ret;
+	u8 reg, addr;
+	u8 *value;
+
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	if (num > 2)
+		warn("more than 2 i2c messages at a time is not handled yet. TODO.");
+
+	if (num == 2) {
+		/* reads a single register */
+		reg = *msg[0].buf;
+		addr = msg[0].addr;
+		value = msg[1].buf;
+		ret = af9005_i2c_read(d, addr, reg, value, 1);
+		if (ret == 0)
+			ret = 2;
+	} else {
+		/* write one or more registers */
+		reg = msg[0].buf[0];
+		addr = msg[0].addr;
+		value = &msg[0].buf[1];
+		ret = af9005_i2c_write(d, addr, reg, value, msg[0].len - 1);
+		if (ret == 0)
+			ret = 1;
+	}
+
+	mutex_unlock(&d->i2c_mutex);
+	return ret;
+}
+
+static u32 af9005_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm af9005_i2c_algo = {
+	.master_xfer = af9005_i2c_xfer,
+	.functionality = af9005_i2c_func,
+};
+
+int af9005_send_command(struct dvb_usb_device *d, u8 command, u8 * wbuf,
+			int wlen, u8 * rbuf, int rlen)
+{
+	struct af9005_device_state *st = d->priv;
+
+	int ret, i, packet_len;
+	u8 seq;
+
+	if (wlen < 0) {
+		err("send command, wlen less than 0 bytes. Makes no sense.");
+		return -EINVAL;
+	}
+	if (wlen > 54) {
+		err("send command, wlen more than 54 bytes. Not supported.");
+		return -EINVAL;
+	}
+	if (rlen > 54) {
+		err("send command, rlen more than 54 bytes. Not supported.");
+		return -EINVAL;
+	}
+	packet_len = wlen + 5;
+
+	mutex_lock(&d->data_mutex);
+
+	st->data[0] = (u8) (packet_len & 0xff);
+	st->data[1] = (u8) ((packet_len & 0xff00) >> 8);
+
+	st->data[2] = 0x26;		/* packet type */
+	st->data[3] = wlen + 3;
+	st->data[4] = seq = st->sequence++;
+	st->data[5] = command;
+	st->data[6] = wlen;
+	for (i = 0; i < wlen; i++)
+		st->data[7 + i] = wbuf[i];
+	ret = dvb_usb_generic_rw(d, st->data, wlen + 7, st->data, rlen + 7, 0);
+	if (st->data[2] != 0x27) {
+		err("send command, wrong reply code.");
+		ret = -EIO;
+	} else if (st->data[4] != seq) {
+		err("send command, wrong sequence in reply.");
+		ret = -EIO;
+	} else if (st->data[5] != 0x01) {
+		err("send command, wrong status code in reply.");
+		ret = -EIO;
+	} else if (st->data[6] != rlen) {
+		err("send command, invalid data length in reply.");
+		ret = -EIO;
+	}
+	if (!ret) {
+		for (i = 0; i < rlen; i++)
+			rbuf[i] = st->data[i + 7];
+	}
+
+	mutex_unlock(&d->data_mutex);
+	return ret;
+}
+
+int af9005_read_eeprom(struct dvb_usb_device *d, u8 address, u8 * values,
+		       int len)
+{
+	struct af9005_device_state *st = d->priv;
+	u8 seq;
+	int ret, i;
+
+	mutex_lock(&d->data_mutex);
+
+	memset(st->data, 0, sizeof(st->data));
+
+	st->data[0] = 14;		/* length of rest of packet low */
+	st->data[1] = 0;		/* length of rest of packer high */
+
+	st->data[2] = 0x2a;		/* read/write eeprom */
+
+	st->data[3] = 12;		/* size */
+
+	st->data[4] = seq = st->sequence++;
+
+	st->data[5] = 0;		/* read */
+
+	st->data[6] = len;
+	st->data[7] = address;
+	ret = dvb_usb_generic_rw(d, st->data, 16, st->data, 14, 0);
+	if (st->data[2] != 0x2b) {
+		err("Read eeprom, invalid reply code");
+		ret = -EIO;
+	} else if (st->data[3] != 10) {
+		err("Read eeprom, invalid reply length");
+		ret = -EIO;
+	} else if (st->data[4] != seq) {
+		err("Read eeprom, wrong sequence in reply ");
+		ret = -EIO;
+	} else if (st->data[5] != 1) {
+		err("Read eeprom, wrong status in reply ");
+		ret = -EIO;
+	}
+
+	if (!ret) {
+		for (i = 0; i < len; i++)
+			values[i] = st->data[6 + i];
+	}
+	mutex_unlock(&d->data_mutex);
+
+	return ret;
+}
+
+static int af9005_boot_packet(struct usb_device *udev, int type, u8 *reply,
+			      u8 *buf, int size)
+{
+	u16 checksum;
+	int act_len, i, ret;
+
+	memset(buf, 0, size);
+	buf[0] = (u8) (FW_BULKOUT_SIZE & 0xff);
+	buf[1] = (u8) ((FW_BULKOUT_SIZE >> 8) & 0xff);
+	switch (type) {
+	case FW_CONFIG:
+		buf[2] = 0x11;
+		buf[3] = 0x04;
+		buf[4] = 0x00;	/* sequence number, original driver doesn't increment it here */
+		buf[5] = 0x03;
+		checksum = buf[4] + buf[5];
+		buf[6] = (u8) ((checksum >> 8) & 0xff);
+		buf[7] = (u8) (checksum & 0xff);
+		break;
+	case FW_CONFIRM:
+		buf[2] = 0x11;
+		buf[3] = 0x04;
+		buf[4] = 0x00;	/* sequence number, original driver doesn't increment it here */
+		buf[5] = 0x01;
+		checksum = buf[4] + buf[5];
+		buf[6] = (u8) ((checksum >> 8) & 0xff);
+		buf[7] = (u8) (checksum & 0xff);
+		break;
+	case FW_BOOT:
+		buf[2] = 0x10;
+		buf[3] = 0x08;
+		buf[4] = 0x00;	/* sequence number, original driver doesn't increment it here */
+		buf[5] = 0x97;
+		buf[6] = 0xaa;
+		buf[7] = 0x55;
+		buf[8] = 0xa5;
+		buf[9] = 0x5a;
+		checksum = 0;
+		for (i = 4; i <= 9; i++)
+			checksum += buf[i];
+		buf[10] = (u8) ((checksum >> 8) & 0xff);
+		buf[11] = (u8) (checksum & 0xff);
+		break;
+	default:
+		err("boot packet invalid boot packet type");
+		return -EINVAL;
+	}
+	deb_fw(">>> ");
+	debug_dump(buf, FW_BULKOUT_SIZE + 2, deb_fw);
+
+	ret = usb_bulk_msg(udev,
+			   usb_sndbulkpipe(udev, 0x02),
+			   buf, FW_BULKOUT_SIZE + 2, &act_len, 2000);
+	if (ret)
+		err("boot packet bulk message failed: %d (%d/%d)", ret,
+		    FW_BULKOUT_SIZE + 2, act_len);
+	else
+		ret = act_len != FW_BULKOUT_SIZE + 2 ? -1 : 0;
+	if (ret)
+		return ret;
+	memset(buf, 0, 9);
+	ret = usb_bulk_msg(udev,
+			   usb_rcvbulkpipe(udev, 0x01), buf, 9, &act_len, 2000);
+	if (ret) {
+		err("boot packet recv bulk message failed: %d", ret);
+		return ret;
+	}
+	deb_fw("<<< ");
+	debug_dump(buf, act_len, deb_fw);
+	checksum = 0;
+	switch (type) {
+	case FW_CONFIG:
+		if (buf[2] != 0x11) {
+			err("boot bad config header.");
+			return -EIO;
+		}
+		if (buf[3] != 0x05) {
+			err("boot bad config size.");
+			return -EIO;
+		}
+		if (buf[4] != 0x00) {
+			err("boot bad config sequence.");
+			return -EIO;
+		}
+		if (buf[5] != 0x04) {
+			err("boot bad config subtype.");
+			return -EIO;
+		}
+		for (i = 4; i <= 6; i++)
+			checksum += buf[i];
+		if (buf[7] * 256 + buf[8] != checksum) {
+			err("boot bad config checksum.");
+			return -EIO;
+		}
+		*reply = buf[6];
+		break;
+	case FW_CONFIRM:
+		if (buf[2] != 0x11) {
+			err("boot bad confirm header.");
+			return -EIO;
+		}
+		if (buf[3] != 0x05) {
+			err("boot bad confirm size.");
+			return -EIO;
+		}
+		if (buf[4] != 0x00) {
+			err("boot bad confirm sequence.");
+			return -EIO;
+		}
+		if (buf[5] != 0x02) {
+			err("boot bad confirm subtype.");
+			return -EIO;
+		}
+		for (i = 4; i <= 6; i++)
+			checksum += buf[i];
+		if (buf[7] * 256 + buf[8] != checksum) {
+			err("boot bad confirm checksum.");
+			return -EIO;
+		}
+		*reply = buf[6];
+		break;
+	case FW_BOOT:
+		if (buf[2] != 0x10) {
+			err("boot bad boot header.");
+			return -EIO;
+		}
+		if (buf[3] != 0x05) {
+			err("boot bad boot size.");
+			return -EIO;
+		}
+		if (buf[4] != 0x00) {
+			err("boot bad boot sequence.");
+			return -EIO;
+		}
+		if (buf[5] != 0x01) {
+			err("boot bad boot pattern 01.");
+			return -EIO;
+		}
+		if (buf[6] != 0x10) {
+			err("boot bad boot pattern 10.");
+			return -EIO;
+		}
+		for (i = 4; i <= 6; i++)
+			checksum += buf[i];
+		if (buf[7] * 256 + buf[8] != checksum) {
+			err("boot bad boot checksum.");
+			return -EIO;
+		}
+		break;
+
+	}
+
+	return 0;
+}
+
+static int af9005_download_firmware(struct usb_device *udev, const struct firmware *fw)
+{
+	int i, packets, ret, act_len;
+
+	u8 *buf;
+	u8 reply;
+
+	buf = kmalloc(FW_BULKOUT_SIZE + 2, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	ret = af9005_boot_packet(udev, FW_CONFIG, &reply, buf,
+				 FW_BULKOUT_SIZE + 2);
+	if (ret)
+		goto err;
+	if (reply != 0x01) {
+		err("before downloading firmware, FW_CONFIG expected 0x01, received 0x%x", reply);
+		ret = -EIO;
+		goto err;
+	}
+	packets = fw->size / FW_BULKOUT_SIZE;
+	buf[0] = (u8) (FW_BULKOUT_SIZE & 0xff);
+	buf[1] = (u8) ((FW_BULKOUT_SIZE >> 8) & 0xff);
+	for (i = 0; i < packets; i++) {
+		memcpy(&buf[2], fw->data + i * FW_BULKOUT_SIZE,
+		       FW_BULKOUT_SIZE);
+		deb_fw(">>> ");
+		debug_dump(buf, FW_BULKOUT_SIZE + 2, deb_fw);
+		ret = usb_bulk_msg(udev,
+				   usb_sndbulkpipe(udev, 0x02),
+				   buf, FW_BULKOUT_SIZE + 2, &act_len, 1000);
+		if (ret) {
+			err("firmware download failed at packet %d with code %d", i, ret);
+			goto err;
+		}
+	}
+	ret = af9005_boot_packet(udev, FW_CONFIRM, &reply,
+				 buf, FW_BULKOUT_SIZE + 2);
+	if (ret)
+		goto err;
+	if (reply != (u8) (packets & 0xff)) {
+		err("after downloading firmware, FW_CONFIRM expected 0x%x, received 0x%x", packets & 0xff, reply);
+		ret = -EIO;
+		goto err;
+	}
+	ret = af9005_boot_packet(udev, FW_BOOT, &reply, buf,
+				 FW_BULKOUT_SIZE + 2);
+	if (ret)
+		goto err;
+	ret = af9005_boot_packet(udev, FW_CONFIG, &reply, buf,
+				 FW_BULKOUT_SIZE + 2);
+	if (ret)
+		goto err;
+	if (reply != 0x02) {
+		err("after downloading firmware, FW_CONFIG expected 0x02, received 0x%x", reply);
+		ret = -EIO;
+		goto err;
+	}
+
+err:
+	kfree(buf);
+	return ret;
+
+}
+
+int af9005_led_control(struct dvb_usb_device *d, int onoff)
+{
+	struct af9005_device_state *st = d->priv;
+	int temp, ret;
+
+	if (onoff && dvb_usb_af9005_led)
+		temp = 1;
+	else
+		temp = 0;
+	if (st->led_state != temp) {
+		ret =
+		    af9005_write_register_bits(d, xd_p_reg_top_locken1,
+					       reg_top_locken1_pos,
+					       reg_top_locken1_len, temp);
+		if (ret)
+			return ret;
+		ret =
+		    af9005_write_register_bits(d, xd_p_reg_top_lock1,
+					       reg_top_lock1_pos,
+					       reg_top_lock1_len, temp);
+		if (ret)
+			return ret;
+		st->led_state = temp;
+	}
+	return 0;
+}
+
+static int af9005_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	u8 buf[8];
+	int i;
+
+	/* without these calls the first commands after downloading
+	   the firmware fail. I put these calls here to simulate
+	   what it is done in dvb-usb-init.c.
+	 */
+	struct usb_device *udev = adap->dev->udev;
+	usb_clear_halt(udev, usb_sndbulkpipe(udev, 2));
+	usb_clear_halt(udev, usb_rcvbulkpipe(udev, 1));
+	if (dvb_usb_af9005_dump_eeprom) {
+		printk("EEPROM DUMP\n");
+		for (i = 0; i < 255; i += 8) {
+			af9005_read_eeprom(adap->dev, i, buf, 8);
+			debug_dump(buf, 8, printk);
+		}
+	}
+	adap->fe_adap[0].fe = af9005_fe_attach(adap->dev);
+	return 0;
+}
+
+static int af9005_rc_query(struct dvb_usb_device *d, u32 * event, int *state)
+{
+	struct af9005_device_state *st = d->priv;
+	int ret, len;
+	u8 seq;
+
+	*state = REMOTE_NO_KEY_PRESSED;
+	if (rc_decode == NULL) {
+		/* it shouldn't never come here */
+		return 0;
+	}
+
+	mutex_lock(&d->data_mutex);
+
+	/* deb_info("rc_query\n"); */
+	st->data[0] = 3;		/* rest of packet length low */
+	st->data[1] = 0;		/* rest of packet lentgh high */
+	st->data[2] = 0x40;		/* read remote */
+	st->data[3] = 1;		/* rest of packet length */
+	st->data[4] = seq = st->sequence++;	/* sequence number */
+	ret = dvb_usb_generic_rw(d, st->data, 5, st->data, 256, 0);
+	if (ret) {
+		err("rc query failed");
+		goto ret;
+	}
+	if (st->data[2] != 0x41) {
+		err("rc query bad header.");
+		ret = -EIO;
+		goto ret;
+	} else if (st->data[4] != seq) {
+		err("rc query bad sequence.");
+		ret = -EIO;
+		goto ret;
+	}
+	len = st->data[5];
+	if (len > 246) {
+		err("rc query invalid length");
+		ret = -EIO;
+		goto ret;
+	}
+	if (len > 0) {
+		deb_rc("rc data (%d) ", len);
+		debug_dump((st->data + 6), len, deb_rc);
+		ret = rc_decode(d, &st->data[6], len, event, state);
+		if (ret) {
+			err("rc_decode failed");
+			goto ret;
+		} else {
+			deb_rc("rc_decode state %x event %x\n", *state, *event);
+			if (*state == REMOTE_KEY_REPEAT)
+				*event = d->last_event;
+		}
+	}
+
+ret:
+	mutex_unlock(&d->data_mutex);
+	return ret;
+}
+
+static int af9005_power_ctrl(struct dvb_usb_device *d, int onoff)
+{
+
+	return 0;
+}
+
+static int af9005_pid_filter_control(struct dvb_usb_adapter *adap, int onoff)
+{
+	int ret;
+	deb_info("pid filter control  onoff %d\n", onoff);
+	if (onoff) {
+		ret =
+		    af9005_write_ofdm_register(adap->dev, XD_MP2IF_DMX_CTRL, 1);
+		if (ret)
+			return ret;
+		ret =
+		    af9005_write_register_bits(adap->dev,
+					       XD_MP2IF_DMX_CTRL, 1, 1, 1);
+		if (ret)
+			return ret;
+		ret =
+		    af9005_write_ofdm_register(adap->dev, XD_MP2IF_DMX_CTRL, 1);
+	} else
+		ret =
+		    af9005_write_ofdm_register(adap->dev, XD_MP2IF_DMX_CTRL, 0);
+	if (ret)
+		return ret;
+	deb_info("pid filter control ok\n");
+	return 0;
+}
+
+static int af9005_pid_filter(struct dvb_usb_adapter *adap, int index,
+			     u16 pid, int onoff)
+{
+	u8 cmd = index & 0x1f;
+	int ret;
+	deb_info("set pid filter, index %d, pid %x, onoff %d\n", index,
+		 pid, onoff);
+	if (onoff) {
+		/* cannot use it as pid_filter_ctrl since it has to be done
+		   before setting the first pid */
+		if (adap->feedcount == 1) {
+			deb_info("first pid set, enable pid table\n");
+			ret = af9005_pid_filter_control(adap, onoff);
+			if (ret)
+				return ret;
+		}
+		ret =
+		    af9005_write_ofdm_register(adap->dev,
+					       XD_MP2IF_PID_DATA_L,
+					       (u8) (pid & 0xff));
+		if (ret)
+			return ret;
+		ret =
+		    af9005_write_ofdm_register(adap->dev,
+					       XD_MP2IF_PID_DATA_H,
+					       (u8) (pid >> 8));
+		if (ret)
+			return ret;
+		cmd |= 0x20 | 0x40;
+	} else {
+		if (adap->feedcount == 0) {
+			deb_info("last pid unset, disable pid table\n");
+			ret = af9005_pid_filter_control(adap, onoff);
+			if (ret)
+				return ret;
+		}
+	}
+	ret = af9005_write_ofdm_register(adap->dev, XD_MP2IF_PID_IDX, cmd);
+	if (ret)
+		return ret;
+	deb_info("set pid ok\n");
+	return 0;
+}
+
+static int af9005_identify_state(struct usb_device *udev,
+				 struct dvb_usb_device_properties *props,
+				 struct dvb_usb_device_description **desc,
+				 int *cold)
+{
+	int ret;
+	u8 reply, *buf;
+
+	buf = kmalloc(FW_BULKOUT_SIZE + 2, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	ret = af9005_boot_packet(udev, FW_CONFIG, &reply,
+				 buf, FW_BULKOUT_SIZE + 2);
+	if (ret)
+		goto err;
+	deb_info("result of FW_CONFIG in identify state %d\n", reply);
+	if (reply == 0x01)
+		*cold = 1;
+	else if (reply == 0x02)
+		*cold = 0;
+	else
+		return -EIO;
+	deb_info("Identify state cold = %d\n", *cold);
+
+err:
+	kfree(buf);
+	return ret;
+}
+
+static struct dvb_usb_device_properties af9005_properties;
+
+static int af9005_usb_probe(struct usb_interface *intf,
+			    const struct usb_device_id *id)
+{
+	return dvb_usb_device_init(intf, &af9005_properties,
+				  THIS_MODULE, NULL, adapter_nr);
+}
+
+enum af9005_usb_table_entry {
+	AFATECH_AF9005,
+	TERRATEC_AF9005,
+	ANSONIC_AF9005,
+};
+
+static struct usb_device_id af9005_usb_table[] = {
+	[AFATECH_AF9005] = {USB_DEVICE(USB_VID_AFATECH,
+				USB_PID_AFATECH_AF9005)},
+	[TERRATEC_AF9005] = {USB_DEVICE(USB_VID_TERRATEC,
+				USB_PID_TERRATEC_CINERGY_T_USB_XE)},
+	[ANSONIC_AF9005] = {USB_DEVICE(USB_VID_ANSONIC,
+				USB_PID_ANSONIC_DVBT_USB)},
+	{ }
+};
+
+MODULE_DEVICE_TABLE(usb, af9005_usb_table);
+
+static struct dvb_usb_device_properties af9005_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl = DEVICE_SPECIFIC,
+	.firmware = "af9005.fw",
+	.download_firmware = af9005_download_firmware,
+	.no_reconnect = 1,
+
+	.size_of_priv = sizeof(struct af9005_device_state),
+
+	.num_adapters = 1,
+	.adapter = {
+		    {
+		    .num_frontends = 1,
+		    .fe = {{
+		     .caps =
+		     DVB_USB_ADAP_HAS_PID_FILTER |
+		     DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+		     .pid_filter_count = 32,
+		     .pid_filter = af9005_pid_filter,
+		     /* .pid_filter_ctrl = af9005_pid_filter_control, */
+		     .frontend_attach = af9005_frontend_attach,
+		     /* .tuner_attach     = af9005_tuner_attach, */
+		     /* parameter for the MPEG2-data transfer */
+		     .stream = {
+				.type = USB_BULK,
+				.count = 10,
+				.endpoint = 0x04,
+				.u = {
+				      .bulk = {
+					       .buffersize = 4096,	/* actual size seen is 3948 */
+					       }
+				      }
+				},
+		     }},
+		     }
+		    },
+	.power_ctrl = af9005_power_ctrl,
+	.identify_state = af9005_identify_state,
+
+	.i2c_algo = &af9005_i2c_algo,
+
+	.rc.legacy = {
+		.rc_interval = 200,
+		.rc_map_table = NULL,
+		.rc_map_size = 0,
+		.rc_query = af9005_rc_query,
+	},
+
+	.generic_bulk_ctrl_endpoint          = 2,
+	.generic_bulk_ctrl_endpoint_response = 1,
+
+	.num_device_descs = 3,
+	.devices = {
+		    {.name = "Afatech DVB-T USB1.1 stick",
+		     .cold_ids = {&af9005_usb_table[AFATECH_AF9005], NULL},
+		     .warm_ids = {NULL},
+		     },
+		    {.name = "TerraTec Cinergy T USB XE",
+		     .cold_ids = {&af9005_usb_table[TERRATEC_AF9005], NULL},
+		     .warm_ids = {NULL},
+		     },
+		    {.name = "Ansonic DVB-T USB1.1 stick",
+		     .cold_ids = {&af9005_usb_table[ANSONIC_AF9005], NULL},
+		     .warm_ids = {NULL},
+		     },
+		    {NULL},
+		    }
+};
+
+/* usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver af9005_usb_driver = {
+	.name = "dvb_usb_af9005",
+	.probe = af9005_usb_probe,
+	.disconnect = dvb_usb_device_exit,
+	.id_table = af9005_usb_table,
+};
+
+/* module stuff */
+static int __init af9005_usb_module_init(void)
+{
+	int result;
+	if ((result = usb_register(&af9005_usb_driver))) {
+		err("usb_register failed. (%d)", result);
+		return result;
+	}
+#if IS_MODULE(CONFIG_DVB_USB_AF9005) || defined(CONFIG_DVB_USB_AF9005_REMOTE)
+	/* FIXME: convert to todays kernel IR infrastructure */
+	rc_decode = symbol_request(af9005_rc_decode);
+	rc_keys = symbol_request(rc_map_af9005_table);
+	rc_keys_size = symbol_request(rc_map_af9005_table_size);
+#endif
+	if (rc_decode == NULL || rc_keys == NULL || rc_keys_size == NULL) {
+		err("af9005_rc_decode function not found, disabling remote");
+		af9005_properties.rc.legacy.rc_query = NULL;
+	} else {
+		af9005_properties.rc.legacy.rc_map_table = rc_keys;
+		af9005_properties.rc.legacy.rc_map_size = *rc_keys_size;
+	}
+
+	return 0;
+}
+
+static void __exit af9005_usb_module_exit(void)
+{
+	/* release rc decode symbols */
+	if (rc_decode != NULL)
+		symbol_put(af9005_rc_decode);
+	if (rc_keys != NULL)
+		symbol_put(rc_map_af9005_table);
+	if (rc_keys_size != NULL)
+		symbol_put(rc_map_af9005_table_size);
+	/* deregister this driver from the USB subsystem */
+	usb_deregister(&af9005_usb_driver);
+}
+
+module_init(af9005_usb_module_init);
+module_exit(af9005_usb_module_exit);
+
+MODULE_AUTHOR("Luca Olivetti <luca@ventoso.org>");
+MODULE_DESCRIPTION("Driver for Afatech 9005 DVB-T USB1.1 stick");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/af9005.h b/drivers/media/usb/dvb-usb/af9005.h
new file mode 100644
index 0000000..7ae4dc3
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/af9005.h
@@ -0,0 +1,3492 @@
+/* Common header-file of the Linux driver for the Afatech 9005
+ * USB1.1 DVB-T receiver.
+ *
+ * Copyright (C) 2007 Luca Olivetti (luca@ventoso.org)
+ *
+ * Thanks to Afatech who kindly provided information.
+ *
+ * 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.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#ifndef _DVB_USB_AF9005_H_
+#define _DVB_USB_AF9005_H_
+
+#define DVB_USB_LOG_PREFIX "af9005"
+#include "dvb-usb.h"
+
+extern int dvb_usb_af9005_debug;
+#define deb_info(args...) dprintk(dvb_usb_af9005_debug,0x01,args)
+#define deb_xfer(args...) dprintk(dvb_usb_af9005_debug,0x02,args)
+#define deb_rc(args...)   dprintk(dvb_usb_af9005_debug,0x04,args)
+#define deb_reg(args...)  dprintk(dvb_usb_af9005_debug,0x08,args)
+#define deb_i2c(args...)  dprintk(dvb_usb_af9005_debug,0x10,args)
+#define deb_fw(args...)   dprintk(dvb_usb_af9005_debug,0x20,args)
+
+extern bool dvb_usb_af9005_led;
+
+/* firmware */
+#define FW_BULKOUT_SIZE 250
+enum {
+	FW_CONFIG,
+	FW_CONFIRM,
+	FW_BOOT
+};
+
+/* af9005 commands */
+#define AF9005_OFDM_REG  0
+#define AF9005_TUNER_REG 1
+
+#define AF9005_REGISTER_RW     0x20
+#define AF9005_REGISTER_RW_ACK 0x21
+
+#define AF9005_CMD_OFDM_REG 0x00
+#define AF9005_CMD_TUNER    0x80
+#define AF9005_CMD_BURST    0x02
+#define AF9005_CMD_AUTOINC  0x04
+#define AF9005_CMD_READ     0x00
+#define AF9005_CMD_WRITE    0x01
+
+/* af9005 registers */
+#define APO_REG_RESET					0xAEFF
+
+#define APO_REG_I2C_RW_CAN_TUNER            0xF000
+#define APO_REG_I2C_RW_SILICON_TUNER        0xF001
+#define APO_REG_GPIO_RW_SILICON_TUNER       0xFFFE	/*  also for OFSM */
+#define APO_REG_TRIGGER_OFSM                0xFFFF	/*  also for OFSM */
+
+/***********************************************************************
+ *  Apollo Registers from VLSI					       *
+ ***********************************************************************/
+#define xd_p_reg_aagc_inverted_agc	0xA000
+#define	reg_aagc_inverted_agc_pos 0
+#define	reg_aagc_inverted_agc_len 1
+#define	reg_aagc_inverted_agc_lsb 0
+#define xd_p_reg_aagc_sign_only	0xA000
+#define	reg_aagc_sign_only_pos 1
+#define	reg_aagc_sign_only_len 1
+#define	reg_aagc_sign_only_lsb 0
+#define xd_p_reg_aagc_slow_adc_en	0xA000
+#define	reg_aagc_slow_adc_en_pos 2
+#define	reg_aagc_slow_adc_en_len 1
+#define	reg_aagc_slow_adc_en_lsb 0
+#define xd_p_reg_aagc_slow_adc_scale	0xA000
+#define	reg_aagc_slow_adc_scale_pos 3
+#define	reg_aagc_slow_adc_scale_len 5
+#define	reg_aagc_slow_adc_scale_lsb 0
+#define xd_p_reg_aagc_check_slow_adc_lock	0xA001
+#define	reg_aagc_check_slow_adc_lock_pos 0
+#define	reg_aagc_check_slow_adc_lock_len 1
+#define	reg_aagc_check_slow_adc_lock_lsb 0
+#define xd_p_reg_aagc_init_control	0xA001
+#define	reg_aagc_init_control_pos 1
+#define	reg_aagc_init_control_len 1
+#define	reg_aagc_init_control_lsb 0
+#define xd_p_reg_aagc_total_gain_sel	0xA001
+#define	reg_aagc_total_gain_sel_pos 2
+#define	reg_aagc_total_gain_sel_len 2
+#define	reg_aagc_total_gain_sel_lsb 0
+#define xd_p_reg_aagc_out_inv	0xA001
+#define	reg_aagc_out_inv_pos 5
+#define	reg_aagc_out_inv_len 1
+#define	reg_aagc_out_inv_lsb 0
+#define xd_p_reg_aagc_int_en	0xA001
+#define	reg_aagc_int_en_pos 6
+#define	reg_aagc_int_en_len 1
+#define	reg_aagc_int_en_lsb 0
+#define xd_p_reg_aagc_lock_change_flag	0xA001
+#define	reg_aagc_lock_change_flag_pos 7
+#define	reg_aagc_lock_change_flag_len 1
+#define	reg_aagc_lock_change_flag_lsb 0
+#define xd_p_reg_aagc_rf_loop_bw_scale_acquire	0xA002
+#define	reg_aagc_rf_loop_bw_scale_acquire_pos 0
+#define	reg_aagc_rf_loop_bw_scale_acquire_len 5
+#define	reg_aagc_rf_loop_bw_scale_acquire_lsb 0
+#define xd_p_reg_aagc_rf_loop_bw_scale_track	0xA003
+#define	reg_aagc_rf_loop_bw_scale_track_pos 0
+#define	reg_aagc_rf_loop_bw_scale_track_len 5
+#define	reg_aagc_rf_loop_bw_scale_track_lsb 0
+#define xd_p_reg_aagc_if_loop_bw_scale_acquire	0xA004
+#define	reg_aagc_if_loop_bw_scale_acquire_pos 0
+#define	reg_aagc_if_loop_bw_scale_acquire_len 5
+#define	reg_aagc_if_loop_bw_scale_acquire_lsb 0
+#define xd_p_reg_aagc_if_loop_bw_scale_track	0xA005
+#define	reg_aagc_if_loop_bw_scale_track_pos 0
+#define	reg_aagc_if_loop_bw_scale_track_len 5
+#define	reg_aagc_if_loop_bw_scale_track_lsb 0
+#define xd_p_reg_aagc_max_rf_agc_7_0	0xA006
+#define	reg_aagc_max_rf_agc_7_0_pos 0
+#define	reg_aagc_max_rf_agc_7_0_len 8
+#define	reg_aagc_max_rf_agc_7_0_lsb 0
+#define xd_p_reg_aagc_max_rf_agc_9_8	0xA007
+#define	reg_aagc_max_rf_agc_9_8_pos 0
+#define	reg_aagc_max_rf_agc_9_8_len 2
+#define	reg_aagc_max_rf_agc_9_8_lsb 8
+#define xd_p_reg_aagc_min_rf_agc_7_0	0xA008
+#define	reg_aagc_min_rf_agc_7_0_pos 0
+#define	reg_aagc_min_rf_agc_7_0_len 8
+#define	reg_aagc_min_rf_agc_7_0_lsb 0
+#define xd_p_reg_aagc_min_rf_agc_9_8	0xA009
+#define	reg_aagc_min_rf_agc_9_8_pos 0
+#define	reg_aagc_min_rf_agc_9_8_len 2
+#define	reg_aagc_min_rf_agc_9_8_lsb 8
+#define xd_p_reg_aagc_max_if_agc_7_0	0xA00A
+#define	reg_aagc_max_if_agc_7_0_pos 0
+#define	reg_aagc_max_if_agc_7_0_len 8
+#define	reg_aagc_max_if_agc_7_0_lsb 0
+#define xd_p_reg_aagc_max_if_agc_9_8	0xA00B
+#define	reg_aagc_max_if_agc_9_8_pos 0
+#define	reg_aagc_max_if_agc_9_8_len 2
+#define	reg_aagc_max_if_agc_9_8_lsb 8
+#define xd_p_reg_aagc_min_if_agc_7_0	0xA00C
+#define	reg_aagc_min_if_agc_7_0_pos 0
+#define	reg_aagc_min_if_agc_7_0_len 8
+#define	reg_aagc_min_if_agc_7_0_lsb 0
+#define xd_p_reg_aagc_min_if_agc_9_8	0xA00D
+#define	reg_aagc_min_if_agc_9_8_pos 0
+#define	reg_aagc_min_if_agc_9_8_len 2
+#define	reg_aagc_min_if_agc_9_8_lsb 8
+#define xd_p_reg_aagc_lock_sample_scale	0xA00E
+#define	reg_aagc_lock_sample_scale_pos 0
+#define	reg_aagc_lock_sample_scale_len 5
+#define	reg_aagc_lock_sample_scale_lsb 0
+#define xd_p_reg_aagc_rf_agc_lock_scale_acquire	0xA00F
+#define	reg_aagc_rf_agc_lock_scale_acquire_pos 0
+#define	reg_aagc_rf_agc_lock_scale_acquire_len 3
+#define	reg_aagc_rf_agc_lock_scale_acquire_lsb 0
+#define xd_p_reg_aagc_rf_agc_lock_scale_track	0xA00F
+#define	reg_aagc_rf_agc_lock_scale_track_pos 3
+#define	reg_aagc_rf_agc_lock_scale_track_len 3
+#define	reg_aagc_rf_agc_lock_scale_track_lsb 0
+#define xd_p_reg_aagc_if_agc_lock_scale_acquire	0xA010
+#define	reg_aagc_if_agc_lock_scale_acquire_pos 0
+#define	reg_aagc_if_agc_lock_scale_acquire_len 3
+#define	reg_aagc_if_agc_lock_scale_acquire_lsb 0
+#define xd_p_reg_aagc_if_agc_lock_scale_track	0xA010
+#define	reg_aagc_if_agc_lock_scale_track_pos 3
+#define	reg_aagc_if_agc_lock_scale_track_len 3
+#define	reg_aagc_if_agc_lock_scale_track_lsb 0
+#define xd_p_reg_aagc_rf_top_numerator_7_0	0xA011
+#define	reg_aagc_rf_top_numerator_7_0_pos 0
+#define	reg_aagc_rf_top_numerator_7_0_len 8
+#define	reg_aagc_rf_top_numerator_7_0_lsb 0
+#define xd_p_reg_aagc_rf_top_numerator_9_8	0xA012
+#define	reg_aagc_rf_top_numerator_9_8_pos 0
+#define	reg_aagc_rf_top_numerator_9_8_len 2
+#define	reg_aagc_rf_top_numerator_9_8_lsb 8
+#define xd_p_reg_aagc_if_top_numerator_7_0	0xA013
+#define	reg_aagc_if_top_numerator_7_0_pos 0
+#define	reg_aagc_if_top_numerator_7_0_len 8
+#define	reg_aagc_if_top_numerator_7_0_lsb 0
+#define xd_p_reg_aagc_if_top_numerator_9_8	0xA014
+#define	reg_aagc_if_top_numerator_9_8_pos 0
+#define	reg_aagc_if_top_numerator_9_8_len 2
+#define	reg_aagc_if_top_numerator_9_8_lsb 8
+#define xd_p_reg_aagc_adc_out_desired_7_0	0xA015
+#define	reg_aagc_adc_out_desired_7_0_pos 0
+#define	reg_aagc_adc_out_desired_7_0_len 8
+#define	reg_aagc_adc_out_desired_7_0_lsb 0
+#define xd_p_reg_aagc_adc_out_desired_8	0xA016
+#define	reg_aagc_adc_out_desired_8_pos 0
+#define	reg_aagc_adc_out_desired_8_len 1
+#define	reg_aagc_adc_out_desired_8_lsb 0
+#define xd_p_reg_aagc_fixed_gain	0xA016
+#define	reg_aagc_fixed_gain_pos 3
+#define	reg_aagc_fixed_gain_len 1
+#define	reg_aagc_fixed_gain_lsb 0
+#define xd_p_reg_aagc_lock_count_th	0xA016
+#define	reg_aagc_lock_count_th_pos 4
+#define	reg_aagc_lock_count_th_len 4
+#define	reg_aagc_lock_count_th_lsb 0
+#define xd_p_reg_aagc_fixed_rf_agc_control_7_0	0xA017
+#define	reg_aagc_fixed_rf_agc_control_7_0_pos 0
+#define	reg_aagc_fixed_rf_agc_control_7_0_len 8
+#define	reg_aagc_fixed_rf_agc_control_7_0_lsb 0
+#define xd_p_reg_aagc_fixed_rf_agc_control_15_8	0xA018
+#define	reg_aagc_fixed_rf_agc_control_15_8_pos 0
+#define	reg_aagc_fixed_rf_agc_control_15_8_len 8
+#define	reg_aagc_fixed_rf_agc_control_15_8_lsb 8
+#define xd_p_reg_aagc_fixed_rf_agc_control_23_16	0xA019
+#define	reg_aagc_fixed_rf_agc_control_23_16_pos 0
+#define	reg_aagc_fixed_rf_agc_control_23_16_len 8
+#define	reg_aagc_fixed_rf_agc_control_23_16_lsb 16
+#define xd_p_reg_aagc_fixed_rf_agc_control_30_24	0xA01A
+#define	reg_aagc_fixed_rf_agc_control_30_24_pos 0
+#define	reg_aagc_fixed_rf_agc_control_30_24_len 7
+#define	reg_aagc_fixed_rf_agc_control_30_24_lsb 24
+#define xd_p_reg_aagc_fixed_if_agc_control_7_0	0xA01B
+#define	reg_aagc_fixed_if_agc_control_7_0_pos 0
+#define	reg_aagc_fixed_if_agc_control_7_0_len 8
+#define	reg_aagc_fixed_if_agc_control_7_0_lsb 0
+#define xd_p_reg_aagc_fixed_if_agc_control_15_8	0xA01C
+#define	reg_aagc_fixed_if_agc_control_15_8_pos 0
+#define	reg_aagc_fixed_if_agc_control_15_8_len 8
+#define	reg_aagc_fixed_if_agc_control_15_8_lsb 8
+#define xd_p_reg_aagc_fixed_if_agc_control_23_16	0xA01D
+#define	reg_aagc_fixed_if_agc_control_23_16_pos 0
+#define	reg_aagc_fixed_if_agc_control_23_16_len 8
+#define	reg_aagc_fixed_if_agc_control_23_16_lsb 16
+#define xd_p_reg_aagc_fixed_if_agc_control_30_24	0xA01E
+#define	reg_aagc_fixed_if_agc_control_30_24_pos 0
+#define	reg_aagc_fixed_if_agc_control_30_24_len 7
+#define	reg_aagc_fixed_if_agc_control_30_24_lsb 24
+#define xd_p_reg_aagc_rf_agc_unlock_numerator	0xA01F
+#define	reg_aagc_rf_agc_unlock_numerator_pos 0
+#define	reg_aagc_rf_agc_unlock_numerator_len 6
+#define	reg_aagc_rf_agc_unlock_numerator_lsb 0
+#define xd_p_reg_aagc_if_agc_unlock_numerator	0xA020
+#define	reg_aagc_if_agc_unlock_numerator_pos 0
+#define	reg_aagc_if_agc_unlock_numerator_len 6
+#define	reg_aagc_if_agc_unlock_numerator_lsb 0
+#define xd_p_reg_unplug_th	0xA021
+#define	reg_unplug_th_pos 0
+#define	reg_unplug_th_len 8
+#define	reg_aagc_rf_x0_lsb 0
+#define xd_p_reg_weak_signal_rfagc_thr 0xA022
+#define	reg_weak_signal_rfagc_thr_pos 0
+#define	reg_weak_signal_rfagc_thr_len 8
+#define	reg_weak_signal_rfagc_thr_lsb 0
+#define xd_p_reg_unplug_rf_gain_th 0xA023
+#define	reg_unplug_rf_gain_th_pos 0
+#define	reg_unplug_rf_gain_th_len 8
+#define	reg_unplug_rf_gain_th_lsb 0
+#define xd_p_reg_unplug_dtop_rf_gain_th 0xA024
+#define	reg_unplug_dtop_rf_gain_th_pos 0
+#define	reg_unplug_dtop_rf_gain_th_len 8
+#define	reg_unplug_dtop_rf_gain_th_lsb 0
+#define xd_p_reg_unplug_dtop_if_gain_th 0xA025
+#define	reg_unplug_dtop_if_gain_th_pos 0
+#define	reg_unplug_dtop_if_gain_th_len 8
+#define	reg_unplug_dtop_if_gain_th_lsb 0
+#define xd_p_reg_top_recover_at_unplug_en 0xA026
+#define	reg_top_recover_at_unplug_en_pos 0
+#define	reg_top_recover_at_unplug_en_len 1
+#define	reg_top_recover_at_unplug_en_lsb 0
+#define xd_p_reg_aagc_rf_x6	0xA027
+#define	reg_aagc_rf_x6_pos 0
+#define	reg_aagc_rf_x6_len 8
+#define	reg_aagc_rf_x6_lsb 0
+#define xd_p_reg_aagc_rf_x7	0xA028
+#define	reg_aagc_rf_x7_pos 0
+#define	reg_aagc_rf_x7_len 8
+#define	reg_aagc_rf_x7_lsb 0
+#define xd_p_reg_aagc_rf_x8	0xA029
+#define	reg_aagc_rf_x8_pos 0
+#define	reg_aagc_rf_x8_len 8
+#define	reg_aagc_rf_x8_lsb 0
+#define xd_p_reg_aagc_rf_x9	0xA02A
+#define	reg_aagc_rf_x9_pos 0
+#define	reg_aagc_rf_x9_len 8
+#define	reg_aagc_rf_x9_lsb 0
+#define xd_p_reg_aagc_rf_x10	0xA02B
+#define	reg_aagc_rf_x10_pos 0
+#define	reg_aagc_rf_x10_len 8
+#define	reg_aagc_rf_x10_lsb 0
+#define xd_p_reg_aagc_rf_x11	0xA02C
+#define	reg_aagc_rf_x11_pos 0
+#define	reg_aagc_rf_x11_len 8
+#define	reg_aagc_rf_x11_lsb 0
+#define xd_p_reg_aagc_rf_x12	0xA02D
+#define	reg_aagc_rf_x12_pos 0
+#define	reg_aagc_rf_x12_len 8
+#define	reg_aagc_rf_x12_lsb 0
+#define xd_p_reg_aagc_rf_x13	0xA02E
+#define	reg_aagc_rf_x13_pos 0
+#define	reg_aagc_rf_x13_len 8
+#define	reg_aagc_rf_x13_lsb 0
+#define xd_p_reg_aagc_if_x0	0xA02F
+#define	reg_aagc_if_x0_pos 0
+#define	reg_aagc_if_x0_len 8
+#define	reg_aagc_if_x0_lsb 0
+#define xd_p_reg_aagc_if_x1	0xA030
+#define	reg_aagc_if_x1_pos 0
+#define	reg_aagc_if_x1_len 8
+#define	reg_aagc_if_x1_lsb 0
+#define xd_p_reg_aagc_if_x2	0xA031
+#define	reg_aagc_if_x2_pos 0
+#define	reg_aagc_if_x2_len 8
+#define	reg_aagc_if_x2_lsb 0
+#define xd_p_reg_aagc_if_x3	0xA032
+#define	reg_aagc_if_x3_pos 0
+#define	reg_aagc_if_x3_len 8
+#define	reg_aagc_if_x3_lsb 0
+#define xd_p_reg_aagc_if_x4	0xA033
+#define	reg_aagc_if_x4_pos 0
+#define	reg_aagc_if_x4_len 8
+#define	reg_aagc_if_x4_lsb 0
+#define xd_p_reg_aagc_if_x5	0xA034
+#define	reg_aagc_if_x5_pos 0
+#define	reg_aagc_if_x5_len 8
+#define	reg_aagc_if_x5_lsb 0
+#define xd_p_reg_aagc_if_x6	0xA035
+#define	reg_aagc_if_x6_pos 0
+#define	reg_aagc_if_x6_len 8
+#define	reg_aagc_if_x6_lsb 0
+#define xd_p_reg_aagc_if_x7	0xA036
+#define	reg_aagc_if_x7_pos 0
+#define	reg_aagc_if_x7_len 8
+#define	reg_aagc_if_x7_lsb 0
+#define xd_p_reg_aagc_if_x8	0xA037
+#define	reg_aagc_if_x8_pos 0
+#define	reg_aagc_if_x8_len 8
+#define	reg_aagc_if_x8_lsb 0
+#define xd_p_reg_aagc_if_x9	0xA038
+#define	reg_aagc_if_x9_pos 0
+#define	reg_aagc_if_x9_len 8
+#define	reg_aagc_if_x9_lsb 0
+#define xd_p_reg_aagc_if_x10	0xA039
+#define	reg_aagc_if_x10_pos 0
+#define	reg_aagc_if_x10_len 8
+#define	reg_aagc_if_x10_lsb 0
+#define xd_p_reg_aagc_if_x11	0xA03A
+#define	reg_aagc_if_x11_pos 0
+#define	reg_aagc_if_x11_len 8
+#define	reg_aagc_if_x11_lsb 0
+#define xd_p_reg_aagc_if_x12	0xA03B
+#define	reg_aagc_if_x12_pos 0
+#define	reg_aagc_if_x12_len 8
+#define	reg_aagc_if_x12_lsb 0
+#define xd_p_reg_aagc_if_x13	0xA03C
+#define	reg_aagc_if_x13_pos 0
+#define	reg_aagc_if_x13_len 8
+#define	reg_aagc_if_x13_lsb 0
+#define xd_p_reg_aagc_min_rf_ctl_8bit_for_dca	0xA03D
+#define	reg_aagc_min_rf_ctl_8bit_for_dca_pos 0
+#define	reg_aagc_min_rf_ctl_8bit_for_dca_len 8
+#define	reg_aagc_min_rf_ctl_8bit_for_dca_lsb 0
+#define xd_p_reg_aagc_min_if_ctl_8bit_for_dca	0xA03E
+#define	reg_aagc_min_if_ctl_8bit_for_dca_pos 0
+#define	reg_aagc_min_if_ctl_8bit_for_dca_len 8
+#define	reg_aagc_min_if_ctl_8bit_for_dca_lsb 0
+#define xd_r_reg_aagc_total_gain_7_0	0xA070
+#define	reg_aagc_total_gain_7_0_pos 0
+#define	reg_aagc_total_gain_7_0_len 8
+#define	reg_aagc_total_gain_7_0_lsb 0
+#define xd_r_reg_aagc_total_gain_15_8	0xA071
+#define	reg_aagc_total_gain_15_8_pos 0
+#define	reg_aagc_total_gain_15_8_len 8
+#define	reg_aagc_total_gain_15_8_lsb 8
+#define xd_p_reg_aagc_in_sat_cnt_7_0	0xA074
+#define	reg_aagc_in_sat_cnt_7_0_pos 0
+#define	reg_aagc_in_sat_cnt_7_0_len 8
+#define	reg_aagc_in_sat_cnt_7_0_lsb 0
+#define xd_p_reg_aagc_in_sat_cnt_15_8	0xA075
+#define	reg_aagc_in_sat_cnt_15_8_pos 0
+#define	reg_aagc_in_sat_cnt_15_8_len 8
+#define	reg_aagc_in_sat_cnt_15_8_lsb 8
+#define xd_p_reg_aagc_in_sat_cnt_23_16	0xA076
+#define	reg_aagc_in_sat_cnt_23_16_pos 0
+#define	reg_aagc_in_sat_cnt_23_16_len 8
+#define	reg_aagc_in_sat_cnt_23_16_lsb 16
+#define xd_p_reg_aagc_in_sat_cnt_31_24	0xA077
+#define	reg_aagc_in_sat_cnt_31_24_pos 0
+#define	reg_aagc_in_sat_cnt_31_24_len 8
+#define	reg_aagc_in_sat_cnt_31_24_lsb 24
+#define xd_r_reg_aagc_digital_rf_volt_7_0	0xA078
+#define	reg_aagc_digital_rf_volt_7_0_pos 0
+#define	reg_aagc_digital_rf_volt_7_0_len 8
+#define	reg_aagc_digital_rf_volt_7_0_lsb 0
+#define xd_r_reg_aagc_digital_rf_volt_9_8	0xA079
+#define	reg_aagc_digital_rf_volt_9_8_pos 0
+#define	reg_aagc_digital_rf_volt_9_8_len 2
+#define	reg_aagc_digital_rf_volt_9_8_lsb 8
+#define xd_r_reg_aagc_digital_if_volt_7_0	0xA07A
+#define	reg_aagc_digital_if_volt_7_0_pos 0
+#define	reg_aagc_digital_if_volt_7_0_len 8
+#define	reg_aagc_digital_if_volt_7_0_lsb 0
+#define xd_r_reg_aagc_digital_if_volt_9_8	0xA07B
+#define	reg_aagc_digital_if_volt_9_8_pos 0
+#define	reg_aagc_digital_if_volt_9_8_len 2
+#define	reg_aagc_digital_if_volt_9_8_lsb 8
+#define xd_r_reg_aagc_rf_gain	0xA07C
+#define	reg_aagc_rf_gain_pos 0
+#define	reg_aagc_rf_gain_len 8
+#define	reg_aagc_rf_gain_lsb 0
+#define xd_r_reg_aagc_if_gain	0xA07D
+#define	reg_aagc_if_gain_pos 0
+#define	reg_aagc_if_gain_len 8
+#define	reg_aagc_if_gain_lsb 0
+#define xd_p_tinr_imp_indicator	0xA080
+#define	tinr_imp_indicator_pos 0
+#define	tinr_imp_indicator_len 2
+#define	tinr_imp_indicator_lsb 0
+#define xd_p_reg_tinr_fifo_size	0xA080
+#define	reg_tinr_fifo_size_pos 2
+#define	reg_tinr_fifo_size_len 5
+#define	reg_tinr_fifo_size_lsb 0
+#define xd_p_reg_tinr_saturation_cnt_th	0xA081
+#define	reg_tinr_saturation_cnt_th_pos 0
+#define	reg_tinr_saturation_cnt_th_len 4
+#define	reg_tinr_saturation_cnt_th_lsb 0
+#define xd_p_reg_tinr_saturation_th_3_0	0xA081
+#define	reg_tinr_saturation_th_3_0_pos 4
+#define	reg_tinr_saturation_th_3_0_len 4
+#define	reg_tinr_saturation_th_3_0_lsb 0
+#define xd_p_reg_tinr_saturation_th_8_4	0xA082
+#define	reg_tinr_saturation_th_8_4_pos 0
+#define	reg_tinr_saturation_th_8_4_len 5
+#define	reg_tinr_saturation_th_8_4_lsb 4
+#define xd_p_reg_tinr_imp_duration_th_2k_7_0	0xA083
+#define	reg_tinr_imp_duration_th_2k_7_0_pos 0
+#define	reg_tinr_imp_duration_th_2k_7_0_len 8
+#define	reg_tinr_imp_duration_th_2k_7_0_lsb 0
+#define xd_p_reg_tinr_imp_duration_th_2k_8	0xA084
+#define	reg_tinr_imp_duration_th_2k_8_pos 0
+#define	reg_tinr_imp_duration_th_2k_8_len 1
+#define	reg_tinr_imp_duration_th_2k_8_lsb 0
+#define xd_p_reg_tinr_imp_duration_th_8k_7_0	0xA085
+#define	reg_tinr_imp_duration_th_8k_7_0_pos 0
+#define	reg_tinr_imp_duration_th_8k_7_0_len 8
+#define	reg_tinr_imp_duration_th_8k_7_0_lsb 0
+#define xd_p_reg_tinr_imp_duration_th_8k_10_8	0xA086
+#define	reg_tinr_imp_duration_th_8k_10_8_pos 0
+#define	reg_tinr_imp_duration_th_8k_10_8_len 3
+#define	reg_tinr_imp_duration_th_8k_10_8_lsb 8
+#define xd_p_reg_tinr_freq_ratio_6m_7_0	0xA087
+#define	reg_tinr_freq_ratio_6m_7_0_pos 0
+#define	reg_tinr_freq_ratio_6m_7_0_len 8
+#define	reg_tinr_freq_ratio_6m_7_0_lsb 0
+#define xd_p_reg_tinr_freq_ratio_6m_12_8	0xA088
+#define	reg_tinr_freq_ratio_6m_12_8_pos 0
+#define	reg_tinr_freq_ratio_6m_12_8_len 5
+#define	reg_tinr_freq_ratio_6m_12_8_lsb 8
+#define xd_p_reg_tinr_freq_ratio_7m_7_0	0xA089
+#define	reg_tinr_freq_ratio_7m_7_0_pos 0
+#define	reg_tinr_freq_ratio_7m_7_0_len 8
+#define	reg_tinr_freq_ratio_7m_7_0_lsb 0
+#define xd_p_reg_tinr_freq_ratio_7m_12_8	0xA08A
+#define	reg_tinr_freq_ratio_7m_12_8_pos 0
+#define	reg_tinr_freq_ratio_7m_12_8_len 5
+#define	reg_tinr_freq_ratio_7m_12_8_lsb 8
+#define xd_p_reg_tinr_freq_ratio_8m_7_0	0xA08B
+#define	reg_tinr_freq_ratio_8m_7_0_pos 0
+#define	reg_tinr_freq_ratio_8m_7_0_len 8
+#define	reg_tinr_freq_ratio_8m_7_0_lsb 0
+#define xd_p_reg_tinr_freq_ratio_8m_12_8	0xA08C
+#define	reg_tinr_freq_ratio_8m_12_8_pos 0
+#define	reg_tinr_freq_ratio_8m_12_8_len 5
+#define	reg_tinr_freq_ratio_8m_12_8_lsb 8
+#define xd_p_reg_tinr_imp_duration_th_low_2k	0xA08D
+#define	reg_tinr_imp_duration_th_low_2k_pos 0
+#define	reg_tinr_imp_duration_th_low_2k_len 8
+#define	reg_tinr_imp_duration_th_low_2k_lsb 0
+#define xd_p_reg_tinr_imp_duration_th_low_8k	0xA08E
+#define	reg_tinr_imp_duration_th_low_8k_pos 0
+#define	reg_tinr_imp_duration_th_low_8k_len 8
+#define	reg_tinr_imp_duration_th_low_8k_lsb 0
+#define xd_r_reg_tinr_counter_7_0	0xA090
+#define	reg_tinr_counter_7_0_pos 0
+#define	reg_tinr_counter_7_0_len 8
+#define	reg_tinr_counter_7_0_lsb 0
+#define xd_r_reg_tinr_counter_15_8	0xA091
+#define	reg_tinr_counter_15_8_pos 0
+#define	reg_tinr_counter_15_8_len 8
+#define	reg_tinr_counter_15_8_lsb 8
+#define xd_p_reg_tinr_adative_tinr_en	0xA093
+#define	reg_tinr_adative_tinr_en_pos 0
+#define	reg_tinr_adative_tinr_en_len 1
+#define	reg_tinr_adative_tinr_en_lsb 0
+#define xd_p_reg_tinr_peak_fifo_size	0xA093
+#define	reg_tinr_peak_fifo_size_pos 1
+#define	reg_tinr_peak_fifo_size_len 5
+#define	reg_tinr_peak_fifo_size_lsb 0
+#define xd_p_reg_tinr_counter_rst	0xA093
+#define	reg_tinr_counter_rst_pos 6
+#define	reg_tinr_counter_rst_len 1
+#define	reg_tinr_counter_rst_lsb 0
+#define xd_p_reg_tinr_search_period_7_0	0xA094
+#define	reg_tinr_search_period_7_0_pos 0
+#define	reg_tinr_search_period_7_0_len 8
+#define	reg_tinr_search_period_7_0_lsb 0
+#define xd_p_reg_tinr_search_period_15_8	0xA095
+#define	reg_tinr_search_period_15_8_pos 0
+#define	reg_tinr_search_period_15_8_len 8
+#define	reg_tinr_search_period_15_8_lsb 8
+#define xd_p_reg_ccifs_fcw_7_0	0xA0A0
+#define	reg_ccifs_fcw_7_0_pos 0
+#define	reg_ccifs_fcw_7_0_len 8
+#define	reg_ccifs_fcw_7_0_lsb 0
+#define xd_p_reg_ccifs_fcw_12_8	0xA0A1
+#define	reg_ccifs_fcw_12_8_pos 0
+#define	reg_ccifs_fcw_12_8_len 5
+#define	reg_ccifs_fcw_12_8_lsb 8
+#define xd_p_reg_ccifs_spec_inv	0xA0A1
+#define	reg_ccifs_spec_inv_pos 5
+#define	reg_ccifs_spec_inv_len 1
+#define	reg_ccifs_spec_inv_lsb 0
+#define xd_p_reg_gp_trigger	0xA0A2
+#define	reg_gp_trigger_pos 0
+#define	reg_gp_trigger_len 1
+#define	reg_gp_trigger_lsb 0
+#define xd_p_reg_trigger_sel	0xA0A2
+#define	reg_trigger_sel_pos 1
+#define	reg_trigger_sel_len 2
+#define	reg_trigger_sel_lsb 0
+#define xd_p_reg_debug_ofdm	0xA0A2
+#define	reg_debug_ofdm_pos 3
+#define	reg_debug_ofdm_len 2
+#define	reg_debug_ofdm_lsb 0
+#define xd_p_reg_trigger_module_sel	0xA0A3
+#define	reg_trigger_module_sel_pos 0
+#define	reg_trigger_module_sel_len 6
+#define	reg_trigger_module_sel_lsb 0
+#define xd_p_reg_trigger_set_sel	0xA0A4
+#define	reg_trigger_set_sel_pos 0
+#define	reg_trigger_set_sel_len 6
+#define	reg_trigger_set_sel_lsb 0
+#define xd_p_reg_fw_int_mask_n	0xA0A4
+#define	reg_fw_int_mask_n_pos 6
+#define	reg_fw_int_mask_n_len 1
+#define	reg_fw_int_mask_n_lsb 0
+#define xd_p_reg_debug_group	0xA0A5
+#define	reg_debug_group_pos 0
+#define	reg_debug_group_len 4
+#define	reg_debug_group_lsb 0
+#define xd_p_reg_odbg_clk_sel	0xA0A5
+#define	reg_odbg_clk_sel_pos 4
+#define	reg_odbg_clk_sel_len 2
+#define	reg_odbg_clk_sel_lsb 0
+#define xd_p_reg_ccif_sc	0xA0C0
+#define	reg_ccif_sc_pos 0
+#define	reg_ccif_sc_len 4
+#define	reg_ccif_sc_lsb 0
+#define xd_r_reg_ccif_saturate	0xA0C1
+#define	reg_ccif_saturate_pos 0
+#define	reg_ccif_saturate_len 2
+#define	reg_ccif_saturate_lsb 0
+#define xd_r_reg_antif_saturate	0xA0C1
+#define	reg_antif_saturate_pos 2
+#define	reg_antif_saturate_len 4
+#define	reg_antif_saturate_lsb 0
+#define xd_r_reg_acif_saturate	0xA0C2
+#define	reg_acif_saturate_pos 0
+#define	reg_acif_saturate_len 8
+#define	reg_acif_saturate_lsb 0
+#define xd_p_reg_tmr_timer0_threshold_7_0	0xA0C8
+#define	reg_tmr_timer0_threshold_7_0_pos 0
+#define	reg_tmr_timer0_threshold_7_0_len 8
+#define	reg_tmr_timer0_threshold_7_0_lsb 0
+#define xd_p_reg_tmr_timer0_threshold_15_8	0xA0C9
+#define	reg_tmr_timer0_threshold_15_8_pos 0
+#define	reg_tmr_timer0_threshold_15_8_len 8
+#define	reg_tmr_timer0_threshold_15_8_lsb 8
+#define xd_p_reg_tmr_timer0_enable	0xA0CA
+#define	reg_tmr_timer0_enable_pos 0
+#define	reg_tmr_timer0_enable_len 1
+#define	reg_tmr_timer0_enable_lsb 0
+#define xd_p_reg_tmr_timer0_clk_sel	0xA0CA
+#define	reg_tmr_timer0_clk_sel_pos 1
+#define	reg_tmr_timer0_clk_sel_len 1
+#define	reg_tmr_timer0_clk_sel_lsb 0
+#define xd_p_reg_tmr_timer0_int	0xA0CA
+#define	reg_tmr_timer0_int_pos 2
+#define	reg_tmr_timer0_int_len 1
+#define	reg_tmr_timer0_int_lsb 0
+#define xd_p_reg_tmr_timer0_rst	0xA0CA
+#define	reg_tmr_timer0_rst_pos 3
+#define	reg_tmr_timer0_rst_len 1
+#define	reg_tmr_timer0_rst_lsb 0
+#define xd_r_reg_tmr_timer0_count_7_0	0xA0CB
+#define	reg_tmr_timer0_count_7_0_pos 0
+#define	reg_tmr_timer0_count_7_0_len 8
+#define	reg_tmr_timer0_count_7_0_lsb 0
+#define xd_r_reg_tmr_timer0_count_15_8	0xA0CC
+#define	reg_tmr_timer0_count_15_8_pos 0
+#define	reg_tmr_timer0_count_15_8_len 8
+#define	reg_tmr_timer0_count_15_8_lsb 8
+#define xd_p_reg_suspend	0xA0CD
+#define	reg_suspend_pos 0
+#define	reg_suspend_len 1
+#define	reg_suspend_lsb 0
+#define xd_p_reg_suspend_rdy	0xA0CD
+#define	reg_suspend_rdy_pos 1
+#define	reg_suspend_rdy_len 1
+#define	reg_suspend_rdy_lsb 0
+#define xd_p_reg_resume	0xA0CD
+#define	reg_resume_pos 2
+#define	reg_resume_len 1
+#define	reg_resume_lsb 0
+#define xd_p_reg_resume_rdy	0xA0CD
+#define	reg_resume_rdy_pos 3
+#define	reg_resume_rdy_len 1
+#define	reg_resume_rdy_lsb 0
+#define xd_p_reg_fmf	0xA0CE
+#define	reg_fmf_pos 0
+#define	reg_fmf_len 8
+#define	reg_fmf_lsb 0
+#define xd_p_ccid_accumulate_num_2k_7_0	0xA100
+#define	ccid_accumulate_num_2k_7_0_pos 0
+#define	ccid_accumulate_num_2k_7_0_len 8
+#define	ccid_accumulate_num_2k_7_0_lsb 0
+#define xd_p_ccid_accumulate_num_2k_12_8	0xA101
+#define	ccid_accumulate_num_2k_12_8_pos 0
+#define	ccid_accumulate_num_2k_12_8_len 5
+#define	ccid_accumulate_num_2k_12_8_lsb 8
+#define xd_p_ccid_accumulate_num_8k_7_0	0xA102
+#define	ccid_accumulate_num_8k_7_0_pos 0
+#define	ccid_accumulate_num_8k_7_0_len 8
+#define	ccid_accumulate_num_8k_7_0_lsb 0
+#define xd_p_ccid_accumulate_num_8k_14_8	0xA103
+#define	ccid_accumulate_num_8k_14_8_pos 0
+#define	ccid_accumulate_num_8k_14_8_len 7
+#define	ccid_accumulate_num_8k_14_8_lsb 8
+#define xd_p_ccid_desired_level_0	0xA103
+#define	ccid_desired_level_0_pos 7
+#define	ccid_desired_level_0_len 1
+#define	ccid_desired_level_0_lsb 0
+#define xd_p_ccid_desired_level_8_1	0xA104
+#define	ccid_desired_level_8_1_pos 0
+#define	ccid_desired_level_8_1_len 8
+#define	ccid_desired_level_8_1_lsb 1
+#define xd_p_ccid_apply_delay	0xA105
+#define	ccid_apply_delay_pos 0
+#define	ccid_apply_delay_len 7
+#define	ccid_apply_delay_lsb 0
+#define xd_p_ccid_CCID_Threshold1	0xA106
+#define	ccid_CCID_Threshold1_pos 0
+#define	ccid_CCID_Threshold1_len 8
+#define	ccid_CCID_Threshold1_lsb 0
+#define xd_p_ccid_CCID_Threshold2	0xA107
+#define	ccid_CCID_Threshold2_pos 0
+#define	ccid_CCID_Threshold2_len 8
+#define	ccid_CCID_Threshold2_lsb 0
+#define xd_p_reg_ccid_gain_scale	0xA108
+#define	reg_ccid_gain_scale_pos 0
+#define	reg_ccid_gain_scale_len 4
+#define	reg_ccid_gain_scale_lsb 0
+#define xd_p_reg_ccid2_passband_gain_set	0xA108
+#define	reg_ccid2_passband_gain_set_pos 4
+#define	reg_ccid2_passband_gain_set_len 4
+#define	reg_ccid2_passband_gain_set_lsb 0
+#define xd_r_ccid_multiplier_7_0	0xA109
+#define	ccid_multiplier_7_0_pos 0
+#define	ccid_multiplier_7_0_len 8
+#define	ccid_multiplier_7_0_lsb 0
+#define xd_r_ccid_multiplier_15_8	0xA10A
+#define	ccid_multiplier_15_8_pos 0
+#define	ccid_multiplier_15_8_len 8
+#define	ccid_multiplier_15_8_lsb 8
+#define xd_r_ccid_right_shift_bits	0xA10B
+#define	ccid_right_shift_bits_pos 0
+#define	ccid_right_shift_bits_len 4
+#define	ccid_right_shift_bits_lsb 0
+#define xd_r_reg_ccid_sx_7_0	0xA10C
+#define	reg_ccid_sx_7_0_pos 0
+#define	reg_ccid_sx_7_0_len 8
+#define	reg_ccid_sx_7_0_lsb 0
+#define xd_r_reg_ccid_sx_15_8	0xA10D
+#define	reg_ccid_sx_15_8_pos 0
+#define	reg_ccid_sx_15_8_len 8
+#define	reg_ccid_sx_15_8_lsb 8
+#define xd_r_reg_ccid_sx_21_16	0xA10E
+#define	reg_ccid_sx_21_16_pos 0
+#define	reg_ccid_sx_21_16_len 6
+#define	reg_ccid_sx_21_16_lsb 16
+#define xd_r_reg_ccid_sy_7_0	0xA110
+#define	reg_ccid_sy_7_0_pos 0
+#define	reg_ccid_sy_7_0_len 8
+#define	reg_ccid_sy_7_0_lsb 0
+#define xd_r_reg_ccid_sy_15_8	0xA111
+#define	reg_ccid_sy_15_8_pos 0
+#define	reg_ccid_sy_15_8_len 8
+#define	reg_ccid_sy_15_8_lsb 8
+#define xd_r_reg_ccid_sy_23_16	0xA112
+#define	reg_ccid_sy_23_16_pos 0
+#define	reg_ccid_sy_23_16_len 8
+#define	reg_ccid_sy_23_16_lsb 16
+#define xd_r_reg_ccid2_sz_7_0	0xA114
+#define	reg_ccid2_sz_7_0_pos 0
+#define	reg_ccid2_sz_7_0_len 8
+#define	reg_ccid2_sz_7_0_lsb 0
+#define xd_r_reg_ccid2_sz_15_8	0xA115
+#define	reg_ccid2_sz_15_8_pos 0
+#define	reg_ccid2_sz_15_8_len 8
+#define	reg_ccid2_sz_15_8_lsb 8
+#define xd_r_reg_ccid2_sz_23_16	0xA116
+#define	reg_ccid2_sz_23_16_pos 0
+#define	reg_ccid2_sz_23_16_len 8
+#define	reg_ccid2_sz_23_16_lsb 16
+#define xd_r_reg_ccid2_sz_25_24	0xA117
+#define	reg_ccid2_sz_25_24_pos 0
+#define	reg_ccid2_sz_25_24_len 2
+#define	reg_ccid2_sz_25_24_lsb 24
+#define xd_r_reg_ccid2_sy_7_0	0xA118
+#define	reg_ccid2_sy_7_0_pos 0
+#define	reg_ccid2_sy_7_0_len 8
+#define	reg_ccid2_sy_7_0_lsb 0
+#define xd_r_reg_ccid2_sy_15_8	0xA119
+#define	reg_ccid2_sy_15_8_pos 0
+#define	reg_ccid2_sy_15_8_len 8
+#define	reg_ccid2_sy_15_8_lsb 8
+#define xd_r_reg_ccid2_sy_23_16	0xA11A
+#define	reg_ccid2_sy_23_16_pos 0
+#define	reg_ccid2_sy_23_16_len 8
+#define	reg_ccid2_sy_23_16_lsb 16
+#define xd_r_reg_ccid2_sy_25_24	0xA11B
+#define	reg_ccid2_sy_25_24_pos 0
+#define	reg_ccid2_sy_25_24_len 2
+#define	reg_ccid2_sy_25_24_lsb 24
+#define xd_p_dagc1_accumulate_num_2k_7_0	0xA120
+#define	dagc1_accumulate_num_2k_7_0_pos 0
+#define	dagc1_accumulate_num_2k_7_0_len 8
+#define	dagc1_accumulate_num_2k_7_0_lsb 0
+#define xd_p_dagc1_accumulate_num_2k_12_8	0xA121
+#define	dagc1_accumulate_num_2k_12_8_pos 0
+#define	dagc1_accumulate_num_2k_12_8_len 5
+#define	dagc1_accumulate_num_2k_12_8_lsb 8
+#define xd_p_dagc1_accumulate_num_8k_7_0	0xA122
+#define	dagc1_accumulate_num_8k_7_0_pos 0
+#define	dagc1_accumulate_num_8k_7_0_len 8
+#define	dagc1_accumulate_num_8k_7_0_lsb 0
+#define xd_p_dagc1_accumulate_num_8k_14_8	0xA123
+#define	dagc1_accumulate_num_8k_14_8_pos 0
+#define	dagc1_accumulate_num_8k_14_8_len 7
+#define	dagc1_accumulate_num_8k_14_8_lsb 8
+#define xd_p_dagc1_desired_level_0	0xA123
+#define	dagc1_desired_level_0_pos 7
+#define	dagc1_desired_level_0_len 1
+#define	dagc1_desired_level_0_lsb 0
+#define xd_p_dagc1_desired_level_8_1	0xA124
+#define	dagc1_desired_level_8_1_pos 0
+#define	dagc1_desired_level_8_1_len 8
+#define	dagc1_desired_level_8_1_lsb 1
+#define xd_p_dagc1_apply_delay	0xA125
+#define	dagc1_apply_delay_pos 0
+#define	dagc1_apply_delay_len 7
+#define	dagc1_apply_delay_lsb 0
+#define xd_p_dagc1_bypass_scale_ctl	0xA126
+#define	dagc1_bypass_scale_ctl_pos 0
+#define	dagc1_bypass_scale_ctl_len 2
+#define	dagc1_bypass_scale_ctl_lsb 0
+#define xd_p_reg_dagc1_in_sat_cnt_7_0	0xA127
+#define	reg_dagc1_in_sat_cnt_7_0_pos 0
+#define	reg_dagc1_in_sat_cnt_7_0_len 8
+#define	reg_dagc1_in_sat_cnt_7_0_lsb 0
+#define xd_p_reg_dagc1_in_sat_cnt_15_8	0xA128
+#define	reg_dagc1_in_sat_cnt_15_8_pos 0
+#define	reg_dagc1_in_sat_cnt_15_8_len 8
+#define	reg_dagc1_in_sat_cnt_15_8_lsb 8
+#define xd_p_reg_dagc1_in_sat_cnt_23_16	0xA129
+#define	reg_dagc1_in_sat_cnt_23_16_pos 0
+#define	reg_dagc1_in_sat_cnt_23_16_len 8
+#define	reg_dagc1_in_sat_cnt_23_16_lsb 16
+#define xd_p_reg_dagc1_in_sat_cnt_31_24	0xA12A
+#define	reg_dagc1_in_sat_cnt_31_24_pos 0
+#define	reg_dagc1_in_sat_cnt_31_24_len 8
+#define	reg_dagc1_in_sat_cnt_31_24_lsb 24
+#define xd_p_reg_dagc1_out_sat_cnt_7_0	0xA12B
+#define	reg_dagc1_out_sat_cnt_7_0_pos 0
+#define	reg_dagc1_out_sat_cnt_7_0_len 8
+#define	reg_dagc1_out_sat_cnt_7_0_lsb 0
+#define xd_p_reg_dagc1_out_sat_cnt_15_8	0xA12C
+#define	reg_dagc1_out_sat_cnt_15_8_pos 0
+#define	reg_dagc1_out_sat_cnt_15_8_len 8
+#define	reg_dagc1_out_sat_cnt_15_8_lsb 8
+#define xd_p_reg_dagc1_out_sat_cnt_23_16	0xA12D
+#define	reg_dagc1_out_sat_cnt_23_16_pos 0
+#define	reg_dagc1_out_sat_cnt_23_16_len 8
+#define	reg_dagc1_out_sat_cnt_23_16_lsb 16
+#define xd_p_reg_dagc1_out_sat_cnt_31_24	0xA12E
+#define	reg_dagc1_out_sat_cnt_31_24_pos 0
+#define	reg_dagc1_out_sat_cnt_31_24_len 8
+#define	reg_dagc1_out_sat_cnt_31_24_lsb 24
+#define xd_r_dagc1_multiplier_7_0	0xA136
+#define	dagc1_multiplier_7_0_pos 0
+#define	dagc1_multiplier_7_0_len 8
+#define	dagc1_multiplier_7_0_lsb 0
+#define xd_r_dagc1_multiplier_15_8	0xA137
+#define	dagc1_multiplier_15_8_pos 0
+#define	dagc1_multiplier_15_8_len 8
+#define	dagc1_multiplier_15_8_lsb 8
+#define xd_r_dagc1_right_shift_bits	0xA138
+#define	dagc1_right_shift_bits_pos 0
+#define	dagc1_right_shift_bits_len 4
+#define	dagc1_right_shift_bits_lsb 0
+#define xd_p_reg_bfs_fcw_7_0	0xA140
+#define	reg_bfs_fcw_7_0_pos 0
+#define	reg_bfs_fcw_7_0_len 8
+#define	reg_bfs_fcw_7_0_lsb 0
+#define xd_p_reg_bfs_fcw_15_8	0xA141
+#define	reg_bfs_fcw_15_8_pos 0
+#define	reg_bfs_fcw_15_8_len 8
+#define	reg_bfs_fcw_15_8_lsb 8
+#define xd_p_reg_bfs_fcw_22_16	0xA142
+#define	reg_bfs_fcw_22_16_pos 0
+#define	reg_bfs_fcw_22_16_len 7
+#define	reg_bfs_fcw_22_16_lsb 16
+#define xd_p_reg_antif_sf_7_0	0xA144
+#define	reg_antif_sf_7_0_pos 0
+#define	reg_antif_sf_7_0_len 8
+#define	reg_antif_sf_7_0_lsb 0
+#define xd_p_reg_antif_sf_11_8	0xA145
+#define	reg_antif_sf_11_8_pos 0
+#define	reg_antif_sf_11_8_len 4
+#define	reg_antif_sf_11_8_lsb 8
+#define xd_r_bfs_fcw_q_7_0	0xA150
+#define	bfs_fcw_q_7_0_pos 0
+#define	bfs_fcw_q_7_0_len 8
+#define	bfs_fcw_q_7_0_lsb 0
+#define xd_r_bfs_fcw_q_15_8	0xA151
+#define	bfs_fcw_q_15_8_pos 0
+#define	bfs_fcw_q_15_8_len 8
+#define	bfs_fcw_q_15_8_lsb 8
+#define xd_r_bfs_fcw_q_22_16	0xA152
+#define	bfs_fcw_q_22_16_pos 0
+#define	bfs_fcw_q_22_16_len 7
+#define	bfs_fcw_q_22_16_lsb 16
+#define xd_p_reg_dca_enu	0xA160
+#define	reg_dca_enu_pos 0
+#define	reg_dca_enu_len 1
+#define	reg_dca_enu_lsb 0
+#define xd_p_reg_dca_enl	0xA160
+#define	reg_dca_enl_pos 1
+#define	reg_dca_enl_len 1
+#define	reg_dca_enl_lsb 0
+#define xd_p_reg_dca_lower_chip	0xA160
+#define	reg_dca_lower_chip_pos 2
+#define	reg_dca_lower_chip_len 1
+#define	reg_dca_lower_chip_lsb 0
+#define xd_p_reg_dca_upper_chip	0xA160
+#define	reg_dca_upper_chip_pos 3
+#define	reg_dca_upper_chip_len 1
+#define	reg_dca_upper_chip_lsb 0
+#define xd_p_reg_dca_platch	0xA160
+#define	reg_dca_platch_pos 4
+#define	reg_dca_platch_len 1
+#define	reg_dca_platch_lsb 0
+#define xd_p_reg_dca_th	0xA161
+#define	reg_dca_th_pos 0
+#define	reg_dca_th_len 5
+#define	reg_dca_th_lsb 0
+#define xd_p_reg_dca_scale	0xA162
+#define	reg_dca_scale_pos 0
+#define	reg_dca_scale_len 4
+#define	reg_dca_scale_lsb 0
+#define xd_p_reg_dca_tone_7_0	0xA163
+#define	reg_dca_tone_7_0_pos 0
+#define	reg_dca_tone_7_0_len 8
+#define	reg_dca_tone_7_0_lsb 0
+#define xd_p_reg_dca_tone_12_8	0xA164
+#define	reg_dca_tone_12_8_pos 0
+#define	reg_dca_tone_12_8_len 5
+#define	reg_dca_tone_12_8_lsb 8
+#define xd_p_reg_dca_time_7_0	0xA165
+#define	reg_dca_time_7_0_pos 0
+#define	reg_dca_time_7_0_len 8
+#define	reg_dca_time_7_0_lsb 0
+#define xd_p_reg_dca_time_15_8	0xA166
+#define	reg_dca_time_15_8_pos 0
+#define	reg_dca_time_15_8_len 8
+#define	reg_dca_time_15_8_lsb 8
+#define xd_r_dcasm	0xA167
+#define	dcasm_pos 0
+#define	dcasm_len 3
+#define	dcasm_lsb 0
+#define xd_p_reg_qnt_valuew_7_0	0xA168
+#define	reg_qnt_valuew_7_0_pos 0
+#define	reg_qnt_valuew_7_0_len 8
+#define	reg_qnt_valuew_7_0_lsb 0
+#define xd_p_reg_qnt_valuew_10_8	0xA169
+#define	reg_qnt_valuew_10_8_pos 0
+#define	reg_qnt_valuew_10_8_len 3
+#define	reg_qnt_valuew_10_8_lsb 8
+#define xd_p_dca_sbx_gain_diff_7_0	0xA16A
+#define	dca_sbx_gain_diff_7_0_pos 0
+#define	dca_sbx_gain_diff_7_0_len 8
+#define	dca_sbx_gain_diff_7_0_lsb 0
+#define xd_p_dca_sbx_gain_diff_9_8	0xA16B
+#define	dca_sbx_gain_diff_9_8_pos 0
+#define	dca_sbx_gain_diff_9_8_len 2
+#define	dca_sbx_gain_diff_9_8_lsb 8
+#define xd_p_reg_dca_stand_alone	0xA16C
+#define	reg_dca_stand_alone_pos 0
+#define	reg_dca_stand_alone_len 1
+#define	reg_dca_stand_alone_lsb 0
+#define xd_p_reg_dca_upper_out_en	0xA16C
+#define	reg_dca_upper_out_en_pos 1
+#define	reg_dca_upper_out_en_len 1
+#define	reg_dca_upper_out_en_lsb 0
+#define xd_p_reg_dca_rc_en	0xA16C
+#define	reg_dca_rc_en_pos 2
+#define	reg_dca_rc_en_len 1
+#define	reg_dca_rc_en_lsb 0
+#define xd_p_reg_dca_retrain_send	0xA16C
+#define	reg_dca_retrain_send_pos 3
+#define	reg_dca_retrain_send_len 1
+#define	reg_dca_retrain_send_lsb 0
+#define xd_p_reg_dca_retrain_rec	0xA16C
+#define	reg_dca_retrain_rec_pos 4
+#define	reg_dca_retrain_rec_len 1
+#define	reg_dca_retrain_rec_lsb 0
+#define xd_p_reg_dca_api_tpsrdy	0xA16C
+#define	reg_dca_api_tpsrdy_pos 5
+#define	reg_dca_api_tpsrdy_len 1
+#define	reg_dca_api_tpsrdy_lsb 0
+#define xd_p_reg_dca_symbol_gap	0xA16D
+#define	reg_dca_symbol_gap_pos 0
+#define	reg_dca_symbol_gap_len 4
+#define	reg_dca_symbol_gap_lsb 0
+#define xd_p_reg_qnt_nfvaluew_7_0	0xA16E
+#define	reg_qnt_nfvaluew_7_0_pos 0
+#define	reg_qnt_nfvaluew_7_0_len 8
+#define	reg_qnt_nfvaluew_7_0_lsb 0
+#define xd_p_reg_qnt_nfvaluew_10_8	0xA16F
+#define	reg_qnt_nfvaluew_10_8_pos 0
+#define	reg_qnt_nfvaluew_10_8_len 3
+#define	reg_qnt_nfvaluew_10_8_lsb 8
+#define xd_p_reg_qnt_flatness_thr_7_0	0xA170
+#define	reg_qnt_flatness_thr_7_0_pos 0
+#define	reg_qnt_flatness_thr_7_0_len 8
+#define	reg_qnt_flatness_thr_7_0_lsb 0
+#define xd_p_reg_qnt_flatness_thr_9_8	0xA171
+#define	reg_qnt_flatness_thr_9_8_pos 0
+#define	reg_qnt_flatness_thr_9_8_len 2
+#define	reg_qnt_flatness_thr_9_8_lsb 8
+#define xd_p_reg_dca_tone_idx_5_0	0xA171
+#define	reg_dca_tone_idx_5_0_pos 2
+#define	reg_dca_tone_idx_5_0_len 6
+#define	reg_dca_tone_idx_5_0_lsb 0
+#define xd_p_reg_dca_tone_idx_12_6	0xA172
+#define	reg_dca_tone_idx_12_6_pos 0
+#define	reg_dca_tone_idx_12_6_len 7
+#define	reg_dca_tone_idx_12_6_lsb 6
+#define xd_p_reg_dca_data_vld	0xA173
+#define	reg_dca_data_vld_pos 0
+#define	reg_dca_data_vld_len 1
+#define	reg_dca_data_vld_lsb 0
+#define xd_p_reg_dca_read_update	0xA173
+#define	reg_dca_read_update_pos 1
+#define	reg_dca_read_update_len 1
+#define	reg_dca_read_update_lsb 0
+#define xd_r_reg_dca_data_re_5_0	0xA173
+#define	reg_dca_data_re_5_0_pos 2
+#define	reg_dca_data_re_5_0_len 6
+#define	reg_dca_data_re_5_0_lsb 0
+#define xd_r_reg_dca_data_re_10_6	0xA174
+#define	reg_dca_data_re_10_6_pos 0
+#define	reg_dca_data_re_10_6_len 5
+#define	reg_dca_data_re_10_6_lsb 6
+#define xd_r_reg_dca_data_im_7_0	0xA175
+#define	reg_dca_data_im_7_0_pos 0
+#define	reg_dca_data_im_7_0_len 8
+#define	reg_dca_data_im_7_0_lsb 0
+#define xd_r_reg_dca_data_im_10_8	0xA176
+#define	reg_dca_data_im_10_8_pos 0
+#define	reg_dca_data_im_10_8_len 3
+#define	reg_dca_data_im_10_8_lsb 8
+#define xd_r_reg_dca_data_h2_7_0	0xA178
+#define	reg_dca_data_h2_7_0_pos 0
+#define	reg_dca_data_h2_7_0_len 8
+#define	reg_dca_data_h2_7_0_lsb 0
+#define xd_r_reg_dca_data_h2_9_8	0xA179
+#define	reg_dca_data_h2_9_8_pos 0
+#define	reg_dca_data_h2_9_8_len 2
+#define	reg_dca_data_h2_9_8_lsb 8
+#define xd_p_reg_f_adc_7_0	0xA180
+#define	reg_f_adc_7_0_pos 0
+#define	reg_f_adc_7_0_len 8
+#define	reg_f_adc_7_0_lsb 0
+#define xd_p_reg_f_adc_15_8	0xA181
+#define	reg_f_adc_15_8_pos 0
+#define	reg_f_adc_15_8_len 8
+#define	reg_f_adc_15_8_lsb 8
+#define xd_p_reg_f_adc_23_16	0xA182
+#define	reg_f_adc_23_16_pos 0
+#define	reg_f_adc_23_16_len 8
+#define	reg_f_adc_23_16_lsb 16
+#define xd_r_intp_mu_7_0	0xA190
+#define	intp_mu_7_0_pos 0
+#define	intp_mu_7_0_len 8
+#define	intp_mu_7_0_lsb 0
+#define xd_r_intp_mu_15_8	0xA191
+#define	intp_mu_15_8_pos 0
+#define	intp_mu_15_8_len 8
+#define	intp_mu_15_8_lsb 8
+#define xd_r_intp_mu_19_16	0xA192
+#define	intp_mu_19_16_pos 0
+#define	intp_mu_19_16_len 4
+#define	intp_mu_19_16_lsb 16
+#define xd_p_reg_agc_rst	0xA1A0
+#define	reg_agc_rst_pos 0
+#define	reg_agc_rst_len 1
+#define	reg_agc_rst_lsb 0
+#define xd_p_rf_agc_en	0xA1A0
+#define	rf_agc_en_pos 1
+#define	rf_agc_en_len 1
+#define	rf_agc_en_lsb 0
+#define xd_p_rf_agc_dis	0xA1A0
+#define	rf_agc_dis_pos 2
+#define	rf_agc_dis_len 1
+#define	rf_agc_dis_lsb 0
+#define xd_p_if_agc_rst	0xA1A0
+#define	if_agc_rst_pos 3
+#define	if_agc_rst_len 1
+#define	if_agc_rst_lsb 0
+#define xd_p_if_agc_en	0xA1A0
+#define	if_agc_en_pos 4
+#define	if_agc_en_len 1
+#define	if_agc_en_lsb 0
+#define xd_p_if_agc_dis	0xA1A0
+#define	if_agc_dis_pos 5
+#define	if_agc_dis_len 1
+#define	if_agc_dis_lsb 0
+#define xd_p_agc_lock	0xA1A0
+#define	agc_lock_pos 6
+#define	agc_lock_len 1
+#define	agc_lock_lsb 0
+#define xd_p_reg_tinr_rst	0xA1A1
+#define	reg_tinr_rst_pos 0
+#define	reg_tinr_rst_len 1
+#define	reg_tinr_rst_lsb 0
+#define xd_p_reg_tinr_en	0xA1A1
+#define	reg_tinr_en_pos 1
+#define	reg_tinr_en_len 1
+#define	reg_tinr_en_lsb 0
+#define xd_p_reg_ccifs_en	0xA1A2
+#define	reg_ccifs_en_pos 0
+#define	reg_ccifs_en_len 1
+#define	reg_ccifs_en_lsb 0
+#define xd_p_reg_ccifs_dis	0xA1A2
+#define	reg_ccifs_dis_pos 1
+#define	reg_ccifs_dis_len 1
+#define	reg_ccifs_dis_lsb 0
+#define xd_p_reg_ccifs_rst	0xA1A2
+#define	reg_ccifs_rst_pos 2
+#define	reg_ccifs_rst_len 1
+#define	reg_ccifs_rst_lsb 0
+#define xd_p_reg_ccifs_byp	0xA1A2
+#define	reg_ccifs_byp_pos 3
+#define	reg_ccifs_byp_len 1
+#define	reg_ccifs_byp_lsb 0
+#define xd_p_reg_ccif_en	0xA1A3
+#define	reg_ccif_en_pos 0
+#define	reg_ccif_en_len 1
+#define	reg_ccif_en_lsb 0
+#define xd_p_reg_ccif_dis	0xA1A3
+#define	reg_ccif_dis_pos 1
+#define	reg_ccif_dis_len 1
+#define	reg_ccif_dis_lsb 0
+#define xd_p_reg_ccif_rst	0xA1A3
+#define	reg_ccif_rst_pos 2
+#define	reg_ccif_rst_len 1
+#define	reg_ccif_rst_lsb 0
+#define xd_p_reg_ccif_byp	0xA1A3
+#define	reg_ccif_byp_pos 3
+#define	reg_ccif_byp_len 1
+#define	reg_ccif_byp_lsb 0
+#define xd_p_dagc1_rst	0xA1A4
+#define	dagc1_rst_pos 0
+#define	dagc1_rst_len 1
+#define	dagc1_rst_lsb 0
+#define xd_p_dagc1_en	0xA1A4
+#define	dagc1_en_pos 1
+#define	dagc1_en_len 1
+#define	dagc1_en_lsb 0
+#define xd_p_dagc1_mode	0xA1A4
+#define	dagc1_mode_pos 2
+#define	dagc1_mode_len 2
+#define	dagc1_mode_lsb 0
+#define xd_p_dagc1_done	0xA1A4
+#define	dagc1_done_pos 4
+#define	dagc1_done_len 1
+#define	dagc1_done_lsb 0
+#define xd_p_ccid_rst	0xA1A5
+#define	ccid_rst_pos 0
+#define	ccid_rst_len 1
+#define	ccid_rst_lsb 0
+#define xd_p_ccid_en	0xA1A5
+#define	ccid_en_pos 1
+#define	ccid_en_len 1
+#define	ccid_en_lsb 0
+#define xd_p_ccid_mode	0xA1A5
+#define	ccid_mode_pos 2
+#define	ccid_mode_len 2
+#define	ccid_mode_lsb 0
+#define xd_p_ccid_done	0xA1A5
+#define	ccid_done_pos 4
+#define	ccid_done_len 1
+#define	ccid_done_lsb 0
+#define xd_r_ccid_deted	0xA1A5
+#define	ccid_deted_pos 5
+#define	ccid_deted_len 1
+#define	ccid_deted_lsb 0
+#define xd_p_ccid2_en	0xA1A5
+#define	ccid2_en_pos 6
+#define	ccid2_en_len 1
+#define	ccid2_en_lsb 0
+#define xd_p_ccid2_done	0xA1A5
+#define	ccid2_done_pos 7
+#define	ccid2_done_len 1
+#define	ccid2_done_lsb 0
+#define xd_p_reg_bfs_en	0xA1A6
+#define	reg_bfs_en_pos 0
+#define	reg_bfs_en_len 1
+#define	reg_bfs_en_lsb 0
+#define xd_p_reg_bfs_dis	0xA1A6
+#define	reg_bfs_dis_pos 1
+#define	reg_bfs_dis_len 1
+#define	reg_bfs_dis_lsb 0
+#define xd_p_reg_bfs_rst	0xA1A6
+#define	reg_bfs_rst_pos 2
+#define	reg_bfs_rst_len 1
+#define	reg_bfs_rst_lsb 0
+#define xd_p_reg_bfs_byp	0xA1A6
+#define	reg_bfs_byp_pos 3
+#define	reg_bfs_byp_len 1
+#define	reg_bfs_byp_lsb 0
+#define xd_p_reg_antif_en	0xA1A7
+#define	reg_antif_en_pos 0
+#define	reg_antif_en_len 1
+#define	reg_antif_en_lsb 0
+#define xd_p_reg_antif_dis	0xA1A7
+#define	reg_antif_dis_pos 1
+#define	reg_antif_dis_len 1
+#define	reg_antif_dis_lsb 0
+#define xd_p_reg_antif_rst	0xA1A7
+#define	reg_antif_rst_pos 2
+#define	reg_antif_rst_len 1
+#define	reg_antif_rst_lsb 0
+#define xd_p_reg_antif_byp	0xA1A7
+#define	reg_antif_byp_pos 3
+#define	reg_antif_byp_len 1
+#define	reg_antif_byp_lsb 0
+#define xd_p_intp_en	0xA1A8
+#define	intp_en_pos 0
+#define	intp_en_len 1
+#define	intp_en_lsb 0
+#define xd_p_intp_dis	0xA1A8
+#define	intp_dis_pos 1
+#define	intp_dis_len 1
+#define	intp_dis_lsb 0
+#define xd_p_intp_rst	0xA1A8
+#define	intp_rst_pos 2
+#define	intp_rst_len 1
+#define	intp_rst_lsb 0
+#define xd_p_intp_byp	0xA1A8
+#define	intp_byp_pos 3
+#define	intp_byp_len 1
+#define	intp_byp_lsb 0
+#define xd_p_reg_acif_en	0xA1A9
+#define	reg_acif_en_pos 0
+#define	reg_acif_en_len 1
+#define	reg_acif_en_lsb 0
+#define xd_p_reg_acif_dis	0xA1A9
+#define	reg_acif_dis_pos 1
+#define	reg_acif_dis_len 1
+#define	reg_acif_dis_lsb 0
+#define xd_p_reg_acif_rst	0xA1A9
+#define	reg_acif_rst_pos 2
+#define	reg_acif_rst_len 1
+#define	reg_acif_rst_lsb 0
+#define xd_p_reg_acif_byp	0xA1A9
+#define	reg_acif_byp_pos 3
+#define	reg_acif_byp_len 1
+#define	reg_acif_byp_lsb 0
+#define xd_p_reg_acif_sync_mode	0xA1A9
+#define	reg_acif_sync_mode_pos 4
+#define	reg_acif_sync_mode_len 1
+#define	reg_acif_sync_mode_lsb 0
+#define xd_p_dagc2_rst	0xA1AA
+#define	dagc2_rst_pos 0
+#define	dagc2_rst_len 1
+#define	dagc2_rst_lsb 0
+#define xd_p_dagc2_en	0xA1AA
+#define	dagc2_en_pos 1
+#define	dagc2_en_len 1
+#define	dagc2_en_lsb 0
+#define xd_p_dagc2_mode	0xA1AA
+#define	dagc2_mode_pos 2
+#define	dagc2_mode_len 2
+#define	dagc2_mode_lsb 0
+#define xd_p_dagc2_done	0xA1AA
+#define	dagc2_done_pos 4
+#define	dagc2_done_len 1
+#define	dagc2_done_lsb 0
+#define xd_p_reg_dca_en	0xA1AB
+#define	reg_dca_en_pos 0
+#define	reg_dca_en_len 1
+#define	reg_dca_en_lsb 0
+#define xd_p_dagc2_accumulate_num_2k_7_0	0xA1C0
+#define	dagc2_accumulate_num_2k_7_0_pos 0
+#define	dagc2_accumulate_num_2k_7_0_len 8
+#define	dagc2_accumulate_num_2k_7_0_lsb 0
+#define xd_p_dagc2_accumulate_num_2k_12_8	0xA1C1
+#define	dagc2_accumulate_num_2k_12_8_pos 0
+#define	dagc2_accumulate_num_2k_12_8_len 5
+#define	dagc2_accumulate_num_2k_12_8_lsb 8
+#define xd_p_dagc2_accumulate_num_8k_7_0	0xA1C2
+#define	dagc2_accumulate_num_8k_7_0_pos 0
+#define	dagc2_accumulate_num_8k_7_0_len 8
+#define	dagc2_accumulate_num_8k_7_0_lsb 0
+#define xd_p_dagc2_accumulate_num_8k_12_8	0xA1C3
+#define	dagc2_accumulate_num_8k_12_8_pos 0
+#define	dagc2_accumulate_num_8k_12_8_len 5
+#define	dagc2_accumulate_num_8k_12_8_lsb 8
+#define xd_p_dagc2_desired_level_2_0	0xA1C3
+#define	dagc2_desired_level_2_0_pos 5
+#define	dagc2_desired_level_2_0_len 3
+#define	dagc2_desired_level_2_0_lsb 0
+#define xd_p_dagc2_desired_level_8_3	0xA1C4
+#define	dagc2_desired_level_8_3_pos 0
+#define	dagc2_desired_level_8_3_len 6
+#define	dagc2_desired_level_8_3_lsb 3
+#define xd_p_dagc2_apply_delay	0xA1C5
+#define	dagc2_apply_delay_pos 0
+#define	dagc2_apply_delay_len 7
+#define	dagc2_apply_delay_lsb 0
+#define xd_p_dagc2_bypass_scale_ctl	0xA1C6
+#define	dagc2_bypass_scale_ctl_pos 0
+#define	dagc2_bypass_scale_ctl_len 3
+#define	dagc2_bypass_scale_ctl_lsb 0
+#define xd_p_dagc2_programmable_shift1	0xA1C7
+#define	dagc2_programmable_shift1_pos 0
+#define	dagc2_programmable_shift1_len 8
+#define	dagc2_programmable_shift1_lsb 0
+#define xd_p_dagc2_programmable_shift2	0xA1C8
+#define	dagc2_programmable_shift2_pos 0
+#define	dagc2_programmable_shift2_len 8
+#define	dagc2_programmable_shift2_lsb 0
+#define xd_p_reg_dagc2_in_sat_cnt_7_0	0xA1C9
+#define	reg_dagc2_in_sat_cnt_7_0_pos 0
+#define	reg_dagc2_in_sat_cnt_7_0_len 8
+#define	reg_dagc2_in_sat_cnt_7_0_lsb 0
+#define xd_p_reg_dagc2_in_sat_cnt_15_8	0xA1CA
+#define	reg_dagc2_in_sat_cnt_15_8_pos 0
+#define	reg_dagc2_in_sat_cnt_15_8_len 8
+#define	reg_dagc2_in_sat_cnt_15_8_lsb 8
+#define xd_p_reg_dagc2_in_sat_cnt_23_16	0xA1CB
+#define	reg_dagc2_in_sat_cnt_23_16_pos 0
+#define	reg_dagc2_in_sat_cnt_23_16_len 8
+#define	reg_dagc2_in_sat_cnt_23_16_lsb 16
+#define xd_p_reg_dagc2_in_sat_cnt_31_24	0xA1CC
+#define	reg_dagc2_in_sat_cnt_31_24_pos 0
+#define	reg_dagc2_in_sat_cnt_31_24_len 8
+#define	reg_dagc2_in_sat_cnt_31_24_lsb 24
+#define xd_p_reg_dagc2_out_sat_cnt_7_0	0xA1CD
+#define	reg_dagc2_out_sat_cnt_7_0_pos 0
+#define	reg_dagc2_out_sat_cnt_7_0_len 8
+#define	reg_dagc2_out_sat_cnt_7_0_lsb 0
+#define xd_p_reg_dagc2_out_sat_cnt_15_8	0xA1CE
+#define	reg_dagc2_out_sat_cnt_15_8_pos 0
+#define	reg_dagc2_out_sat_cnt_15_8_len 8
+#define	reg_dagc2_out_sat_cnt_15_8_lsb 8
+#define xd_p_reg_dagc2_out_sat_cnt_23_16	0xA1CF
+#define	reg_dagc2_out_sat_cnt_23_16_pos 0
+#define	reg_dagc2_out_sat_cnt_23_16_len 8
+#define	reg_dagc2_out_sat_cnt_23_16_lsb 16
+#define xd_p_reg_dagc2_out_sat_cnt_31_24	0xA1D0
+#define	reg_dagc2_out_sat_cnt_31_24_pos 0
+#define	reg_dagc2_out_sat_cnt_31_24_len 8
+#define	reg_dagc2_out_sat_cnt_31_24_lsb 24
+#define xd_r_dagc2_multiplier_7_0	0xA1D6
+#define	dagc2_multiplier_7_0_pos 0
+#define	dagc2_multiplier_7_0_len 8
+#define	dagc2_multiplier_7_0_lsb 0
+#define xd_r_dagc2_multiplier_15_8	0xA1D7
+#define	dagc2_multiplier_15_8_pos 0
+#define	dagc2_multiplier_15_8_len 8
+#define	dagc2_multiplier_15_8_lsb 8
+#define xd_r_dagc2_right_shift_bits	0xA1D8
+#define	dagc2_right_shift_bits_pos 0
+#define	dagc2_right_shift_bits_len 4
+#define	dagc2_right_shift_bits_lsb 0
+#define xd_p_cfoe_NS_coeff1_7_0	0xA200
+#define	cfoe_NS_coeff1_7_0_pos 0
+#define	cfoe_NS_coeff1_7_0_len 8
+#define	cfoe_NS_coeff1_7_0_lsb 0
+#define xd_p_cfoe_NS_coeff1_15_8	0xA201
+#define	cfoe_NS_coeff1_15_8_pos 0
+#define	cfoe_NS_coeff1_15_8_len 8
+#define	cfoe_NS_coeff1_15_8_lsb 8
+#define xd_p_cfoe_NS_coeff1_23_16	0xA202
+#define	cfoe_NS_coeff1_23_16_pos 0
+#define	cfoe_NS_coeff1_23_16_len 8
+#define	cfoe_NS_coeff1_23_16_lsb 16
+#define xd_p_cfoe_NS_coeff1_25_24	0xA203
+#define	cfoe_NS_coeff1_25_24_pos 0
+#define	cfoe_NS_coeff1_25_24_len 2
+#define	cfoe_NS_coeff1_25_24_lsb 24
+#define xd_p_cfoe_NS_coeff2_5_0	0xA203
+#define	cfoe_NS_coeff2_5_0_pos 2
+#define	cfoe_NS_coeff2_5_0_len 6
+#define	cfoe_NS_coeff2_5_0_lsb 0
+#define xd_p_cfoe_NS_coeff2_13_6	0xA204
+#define	cfoe_NS_coeff2_13_6_pos 0
+#define	cfoe_NS_coeff2_13_6_len 8
+#define	cfoe_NS_coeff2_13_6_lsb 6
+#define xd_p_cfoe_NS_coeff2_21_14	0xA205
+#define	cfoe_NS_coeff2_21_14_pos 0
+#define	cfoe_NS_coeff2_21_14_len 8
+#define	cfoe_NS_coeff2_21_14_lsb 14
+#define xd_p_cfoe_NS_coeff2_24_22	0xA206
+#define	cfoe_NS_coeff2_24_22_pos 0
+#define	cfoe_NS_coeff2_24_22_len 3
+#define	cfoe_NS_coeff2_24_22_lsb 22
+#define xd_p_cfoe_lf_c1_4_0	0xA206
+#define	cfoe_lf_c1_4_0_pos 3
+#define	cfoe_lf_c1_4_0_len 5
+#define	cfoe_lf_c1_4_0_lsb 0
+#define xd_p_cfoe_lf_c1_12_5	0xA207
+#define	cfoe_lf_c1_12_5_pos 0
+#define	cfoe_lf_c1_12_5_len 8
+#define	cfoe_lf_c1_12_5_lsb 5
+#define xd_p_cfoe_lf_c1_20_13	0xA208
+#define	cfoe_lf_c1_20_13_pos 0
+#define	cfoe_lf_c1_20_13_len 8
+#define	cfoe_lf_c1_20_13_lsb 13
+#define xd_p_cfoe_lf_c1_25_21	0xA209
+#define	cfoe_lf_c1_25_21_pos 0
+#define	cfoe_lf_c1_25_21_len 5
+#define	cfoe_lf_c1_25_21_lsb 21
+#define xd_p_cfoe_lf_c2_2_0	0xA209
+#define	cfoe_lf_c2_2_0_pos 5
+#define	cfoe_lf_c2_2_0_len 3
+#define	cfoe_lf_c2_2_0_lsb 0
+#define xd_p_cfoe_lf_c2_10_3	0xA20A
+#define	cfoe_lf_c2_10_3_pos 0
+#define	cfoe_lf_c2_10_3_len 8
+#define	cfoe_lf_c2_10_3_lsb 3
+#define xd_p_cfoe_lf_c2_18_11	0xA20B
+#define	cfoe_lf_c2_18_11_pos 0
+#define	cfoe_lf_c2_18_11_len 8
+#define	cfoe_lf_c2_18_11_lsb 11
+#define xd_p_cfoe_lf_c2_25_19	0xA20C
+#define	cfoe_lf_c2_25_19_pos 0
+#define	cfoe_lf_c2_25_19_len 7
+#define	cfoe_lf_c2_25_19_lsb 19
+#define xd_p_cfoe_ifod_7_0	0xA20D
+#define	cfoe_ifod_7_0_pos 0
+#define	cfoe_ifod_7_0_len 8
+#define	cfoe_ifod_7_0_lsb 0
+#define xd_p_cfoe_ifod_10_8	0xA20E
+#define	cfoe_ifod_10_8_pos 0
+#define	cfoe_ifod_10_8_len 3
+#define	cfoe_ifod_10_8_lsb 8
+#define xd_p_cfoe_Divg_ctr_th	0xA20E
+#define	cfoe_Divg_ctr_th_pos 4
+#define	cfoe_Divg_ctr_th_len 4
+#define	cfoe_Divg_ctr_th_lsb 0
+#define xd_p_cfoe_FOT_divg_th	0xA20F
+#define	cfoe_FOT_divg_th_pos 0
+#define	cfoe_FOT_divg_th_len 8
+#define	cfoe_FOT_divg_th_lsb 0
+#define xd_p_cfoe_FOT_cnvg_th	0xA210
+#define	cfoe_FOT_cnvg_th_pos 0
+#define	cfoe_FOT_cnvg_th_len 8
+#define	cfoe_FOT_cnvg_th_lsb 0
+#define xd_p_reg_cfoe_offset_7_0	0xA211
+#define	reg_cfoe_offset_7_0_pos 0
+#define	reg_cfoe_offset_7_0_len 8
+#define	reg_cfoe_offset_7_0_lsb 0
+#define xd_p_reg_cfoe_offset_9_8	0xA212
+#define	reg_cfoe_offset_9_8_pos 0
+#define	reg_cfoe_offset_9_8_len 2
+#define	reg_cfoe_offset_9_8_lsb 8
+#define xd_p_reg_cfoe_ifoe_sign_corr	0xA212
+#define	reg_cfoe_ifoe_sign_corr_pos 2
+#define	reg_cfoe_ifoe_sign_corr_len 1
+#define	reg_cfoe_ifoe_sign_corr_lsb 0
+#define xd_r_cfoe_fot_LF_output_7_0	0xA218
+#define	cfoe_fot_LF_output_7_0_pos 0
+#define	cfoe_fot_LF_output_7_0_len 8
+#define	cfoe_fot_LF_output_7_0_lsb 0
+#define xd_r_cfoe_fot_LF_output_15_8	0xA219
+#define	cfoe_fot_LF_output_15_8_pos 0
+#define	cfoe_fot_LF_output_15_8_len 8
+#define	cfoe_fot_LF_output_15_8_lsb 8
+#define xd_r_cfoe_ifo_metric_7_0	0xA21A
+#define	cfoe_ifo_metric_7_0_pos 0
+#define	cfoe_ifo_metric_7_0_len 8
+#define	cfoe_ifo_metric_7_0_lsb 0
+#define xd_r_cfoe_ifo_metric_15_8	0xA21B
+#define	cfoe_ifo_metric_15_8_pos 0
+#define	cfoe_ifo_metric_15_8_len 8
+#define	cfoe_ifo_metric_15_8_lsb 8
+#define xd_r_cfoe_ifo_metric_23_16	0xA21C
+#define	cfoe_ifo_metric_23_16_pos 0
+#define	cfoe_ifo_metric_23_16_len 8
+#define	cfoe_ifo_metric_23_16_lsb 16
+#define xd_p_ste_Nu	0xA220
+#define	ste_Nu_pos 0
+#define	ste_Nu_len 2
+#define	ste_Nu_lsb 0
+#define xd_p_ste_GI	0xA220
+#define	ste_GI_pos 2
+#define	ste_GI_len 3
+#define	ste_GI_lsb 0
+#define xd_p_ste_symbol_num	0xA221
+#define	ste_symbol_num_pos 0
+#define	ste_symbol_num_len 2
+#define	ste_symbol_num_lsb 0
+#define xd_p_ste_sample_num	0xA221
+#define	ste_sample_num_pos 2
+#define	ste_sample_num_len 2
+#define	ste_sample_num_lsb 0
+#define xd_p_reg_ste_buf_en	0xA221
+#define	reg_ste_buf_en_pos 7
+#define	reg_ste_buf_en_len 1
+#define	reg_ste_buf_en_lsb 0
+#define xd_p_ste_FFT_offset_7_0	0xA222
+#define	ste_FFT_offset_7_0_pos 0
+#define	ste_FFT_offset_7_0_len 8
+#define	ste_FFT_offset_7_0_lsb 0
+#define xd_p_ste_FFT_offset_11_8	0xA223
+#define	ste_FFT_offset_11_8_pos 0
+#define	ste_FFT_offset_11_8_len 4
+#define	ste_FFT_offset_11_8_lsb 8
+#define xd_p_reg_ste_tstmod	0xA223
+#define	reg_ste_tstmod_pos 5
+#define	reg_ste_tstmod_len 1
+#define	reg_ste_tstmod_lsb 0
+#define xd_p_ste_adv_start_7_0	0xA224
+#define	ste_adv_start_7_0_pos 0
+#define	ste_adv_start_7_0_len 8
+#define	ste_adv_start_7_0_lsb 0
+#define xd_p_ste_adv_start_10_8	0xA225
+#define	ste_adv_start_10_8_pos 0
+#define	ste_adv_start_10_8_len 3
+#define	ste_adv_start_10_8_lsb 8
+#define xd_p_ste_adv_stop	0xA226
+#define	ste_adv_stop_pos 0
+#define	ste_adv_stop_len 8
+#define	ste_adv_stop_lsb 0
+#define xd_r_ste_P_value_7_0	0xA228
+#define	ste_P_value_7_0_pos 0
+#define	ste_P_value_7_0_len 8
+#define	ste_P_value_7_0_lsb 0
+#define xd_r_ste_P_value_10_8	0xA229
+#define	ste_P_value_10_8_pos 0
+#define	ste_P_value_10_8_len 3
+#define	ste_P_value_10_8_lsb 8
+#define xd_r_ste_M_value_7_0	0xA22A
+#define	ste_M_value_7_0_pos 0
+#define	ste_M_value_7_0_len 8
+#define	ste_M_value_7_0_lsb 0
+#define xd_r_ste_M_value_10_8	0xA22B
+#define	ste_M_value_10_8_pos 0
+#define	ste_M_value_10_8_len 3
+#define	ste_M_value_10_8_lsb 8
+#define xd_r_ste_H1	0xA22C
+#define	ste_H1_pos 0
+#define	ste_H1_len 7
+#define	ste_H1_lsb 0
+#define xd_r_ste_H2	0xA22D
+#define	ste_H2_pos 0
+#define	ste_H2_len 7
+#define	ste_H2_lsb 0
+#define xd_r_ste_H3	0xA22E
+#define	ste_H3_pos 0
+#define	ste_H3_len 7
+#define	ste_H3_lsb 0
+#define xd_r_ste_H4	0xA22F
+#define	ste_H4_pos 0
+#define	ste_H4_len 7
+#define	ste_H4_lsb 0
+#define xd_r_ste_Corr_value_I_7_0	0xA230
+#define	ste_Corr_value_I_7_0_pos 0
+#define	ste_Corr_value_I_7_0_len 8
+#define	ste_Corr_value_I_7_0_lsb 0
+#define xd_r_ste_Corr_value_I_15_8	0xA231
+#define	ste_Corr_value_I_15_8_pos 0
+#define	ste_Corr_value_I_15_8_len 8
+#define	ste_Corr_value_I_15_8_lsb 8
+#define xd_r_ste_Corr_value_I_23_16	0xA232
+#define	ste_Corr_value_I_23_16_pos 0
+#define	ste_Corr_value_I_23_16_len 8
+#define	ste_Corr_value_I_23_16_lsb 16
+#define xd_r_ste_Corr_value_I_27_24	0xA233
+#define	ste_Corr_value_I_27_24_pos 0
+#define	ste_Corr_value_I_27_24_len 4
+#define	ste_Corr_value_I_27_24_lsb 24
+#define xd_r_ste_Corr_value_Q_7_0	0xA234
+#define	ste_Corr_value_Q_7_0_pos 0
+#define	ste_Corr_value_Q_7_0_len 8
+#define	ste_Corr_value_Q_7_0_lsb 0
+#define xd_r_ste_Corr_value_Q_15_8	0xA235
+#define	ste_Corr_value_Q_15_8_pos 0
+#define	ste_Corr_value_Q_15_8_len 8
+#define	ste_Corr_value_Q_15_8_lsb 8
+#define xd_r_ste_Corr_value_Q_23_16	0xA236
+#define	ste_Corr_value_Q_23_16_pos 0
+#define	ste_Corr_value_Q_23_16_len 8
+#define	ste_Corr_value_Q_23_16_lsb 16
+#define xd_r_ste_Corr_value_Q_27_24	0xA237
+#define	ste_Corr_value_Q_27_24_pos 0
+#define	ste_Corr_value_Q_27_24_len 4
+#define	ste_Corr_value_Q_27_24_lsb 24
+#define xd_r_ste_J_num_7_0	0xA238
+#define	ste_J_num_7_0_pos 0
+#define	ste_J_num_7_0_len 8
+#define	ste_J_num_7_0_lsb 0
+#define xd_r_ste_J_num_15_8	0xA239
+#define	ste_J_num_15_8_pos 0
+#define	ste_J_num_15_8_len 8
+#define	ste_J_num_15_8_lsb 8
+#define xd_r_ste_J_num_23_16	0xA23A
+#define	ste_J_num_23_16_pos 0
+#define	ste_J_num_23_16_len 8
+#define	ste_J_num_23_16_lsb 16
+#define xd_r_ste_J_num_31_24	0xA23B
+#define	ste_J_num_31_24_pos 0
+#define	ste_J_num_31_24_len 8
+#define	ste_J_num_31_24_lsb 24
+#define xd_r_ste_J_den_7_0	0xA23C
+#define	ste_J_den_7_0_pos 0
+#define	ste_J_den_7_0_len 8
+#define	ste_J_den_7_0_lsb 0
+#define xd_r_ste_J_den_15_8	0xA23D
+#define	ste_J_den_15_8_pos 0
+#define	ste_J_den_15_8_len 8
+#define	ste_J_den_15_8_lsb 8
+#define xd_r_ste_J_den_18_16	0xA23E
+#define	ste_J_den_18_16_pos 0
+#define	ste_J_den_18_16_len 3
+#define	ste_J_den_18_16_lsb 16
+#define xd_r_ste_Beacon_Indicator	0xA23E
+#define	ste_Beacon_Indicator_pos 4
+#define	ste_Beacon_Indicator_len 1
+#define	ste_Beacon_Indicator_lsb 0
+#define xd_r_tpsd_Frame_Num	0xA250
+#define	tpsd_Frame_Num_pos 0
+#define	tpsd_Frame_Num_len 2
+#define	tpsd_Frame_Num_lsb 0
+#define xd_r_tpsd_Constel	0xA250
+#define	tpsd_Constel_pos 2
+#define	tpsd_Constel_len 2
+#define	tpsd_Constel_lsb 0
+#define xd_r_tpsd_GI	0xA250
+#define	tpsd_GI_pos 4
+#define	tpsd_GI_len 2
+#define	tpsd_GI_lsb 0
+#define xd_r_tpsd_Mode	0xA250
+#define	tpsd_Mode_pos 6
+#define	tpsd_Mode_len 2
+#define	tpsd_Mode_lsb 0
+#define xd_r_tpsd_CR_HP	0xA251
+#define	tpsd_CR_HP_pos 0
+#define	tpsd_CR_HP_len 3
+#define	tpsd_CR_HP_lsb 0
+#define xd_r_tpsd_CR_LP	0xA251
+#define	tpsd_CR_LP_pos 3
+#define	tpsd_CR_LP_len 3
+#define	tpsd_CR_LP_lsb 0
+#define xd_r_tpsd_Hie	0xA252
+#define	tpsd_Hie_pos 0
+#define	tpsd_Hie_len 3
+#define	tpsd_Hie_lsb 0
+#define xd_r_tpsd_Res_Bits	0xA252
+#define	tpsd_Res_Bits_pos 3
+#define	tpsd_Res_Bits_len 5
+#define	tpsd_Res_Bits_lsb 0
+#define xd_r_tpsd_Res_Bits_0	0xA253
+#define	tpsd_Res_Bits_0_pos 0
+#define	tpsd_Res_Bits_0_len 1
+#define	tpsd_Res_Bits_0_lsb 0
+#define xd_r_tpsd_LengthInd	0xA253
+#define	tpsd_LengthInd_pos 1
+#define	tpsd_LengthInd_len 6
+#define	tpsd_LengthInd_lsb 0
+#define xd_r_tpsd_Cell_Id_7_0	0xA254
+#define	tpsd_Cell_Id_7_0_pos 0
+#define	tpsd_Cell_Id_7_0_len 8
+#define	tpsd_Cell_Id_7_0_lsb 0
+#define xd_r_tpsd_Cell_Id_15_8	0xA255
+#define	tpsd_Cell_Id_15_8_pos 0
+#define	tpsd_Cell_Id_15_8_len 8
+#define	tpsd_Cell_Id_15_8_lsb 0
+#define xd_p_reg_fft_mask_tone0_7_0	0xA260
+#define	reg_fft_mask_tone0_7_0_pos 0
+#define	reg_fft_mask_tone0_7_0_len 8
+#define	reg_fft_mask_tone0_7_0_lsb 0
+#define xd_p_reg_fft_mask_tone0_12_8	0xA261
+#define	reg_fft_mask_tone0_12_8_pos 0
+#define	reg_fft_mask_tone0_12_8_len 5
+#define	reg_fft_mask_tone0_12_8_lsb 8
+#define xd_p_reg_fft_mask_tone1_7_0	0xA262
+#define	reg_fft_mask_tone1_7_0_pos 0
+#define	reg_fft_mask_tone1_7_0_len 8
+#define	reg_fft_mask_tone1_7_0_lsb 0
+#define xd_p_reg_fft_mask_tone1_12_8	0xA263
+#define	reg_fft_mask_tone1_12_8_pos 0
+#define	reg_fft_mask_tone1_12_8_len 5
+#define	reg_fft_mask_tone1_12_8_lsb 8
+#define xd_p_reg_fft_mask_tone2_7_0	0xA264
+#define	reg_fft_mask_tone2_7_0_pos 0
+#define	reg_fft_mask_tone2_7_0_len 8
+#define	reg_fft_mask_tone2_7_0_lsb 0
+#define xd_p_reg_fft_mask_tone2_12_8	0xA265
+#define	reg_fft_mask_tone2_12_8_pos 0
+#define	reg_fft_mask_tone2_12_8_len 5
+#define	reg_fft_mask_tone2_12_8_lsb 8
+#define xd_p_reg_fft_mask_tone3_7_0	0xA266
+#define	reg_fft_mask_tone3_7_0_pos 0
+#define	reg_fft_mask_tone3_7_0_len 8
+#define	reg_fft_mask_tone3_7_0_lsb 0
+#define xd_p_reg_fft_mask_tone3_12_8	0xA267
+#define	reg_fft_mask_tone3_12_8_pos 0
+#define	reg_fft_mask_tone3_12_8_len 5
+#define	reg_fft_mask_tone3_12_8_lsb 8
+#define xd_p_reg_fft_mask_from0_7_0	0xA268
+#define	reg_fft_mask_from0_7_0_pos 0
+#define	reg_fft_mask_from0_7_0_len 8
+#define	reg_fft_mask_from0_7_0_lsb 0
+#define xd_p_reg_fft_mask_from0_12_8	0xA269
+#define	reg_fft_mask_from0_12_8_pos 0
+#define	reg_fft_mask_from0_12_8_len 5
+#define	reg_fft_mask_from0_12_8_lsb 8
+#define xd_p_reg_fft_mask_to0_7_0	0xA26A
+#define	reg_fft_mask_to0_7_0_pos 0
+#define	reg_fft_mask_to0_7_0_len 8
+#define	reg_fft_mask_to0_7_0_lsb 0
+#define xd_p_reg_fft_mask_to0_12_8	0xA26B
+#define	reg_fft_mask_to0_12_8_pos 0
+#define	reg_fft_mask_to0_12_8_len 5
+#define	reg_fft_mask_to0_12_8_lsb 8
+#define xd_p_reg_fft_mask_from1_7_0	0xA26C
+#define	reg_fft_mask_from1_7_0_pos 0
+#define	reg_fft_mask_from1_7_0_len 8
+#define	reg_fft_mask_from1_7_0_lsb 0
+#define xd_p_reg_fft_mask_from1_12_8	0xA26D
+#define	reg_fft_mask_from1_12_8_pos 0
+#define	reg_fft_mask_from1_12_8_len 5
+#define	reg_fft_mask_from1_12_8_lsb 8
+#define xd_p_reg_fft_mask_to1_7_0	0xA26E
+#define	reg_fft_mask_to1_7_0_pos 0
+#define	reg_fft_mask_to1_7_0_len 8
+#define	reg_fft_mask_to1_7_0_lsb 0
+#define xd_p_reg_fft_mask_to1_12_8	0xA26F
+#define	reg_fft_mask_to1_12_8_pos 0
+#define	reg_fft_mask_to1_12_8_len 5
+#define	reg_fft_mask_to1_12_8_lsb 8
+#define xd_p_reg_cge_idx0_7_0	0xA280
+#define	reg_cge_idx0_7_0_pos 0
+#define	reg_cge_idx0_7_0_len 8
+#define	reg_cge_idx0_7_0_lsb 0
+#define xd_p_reg_cge_idx0_12_8	0xA281
+#define	reg_cge_idx0_12_8_pos 0
+#define	reg_cge_idx0_12_8_len 5
+#define	reg_cge_idx0_12_8_lsb 8
+#define xd_p_reg_cge_idx1_7_0	0xA282
+#define	reg_cge_idx1_7_0_pos 0
+#define	reg_cge_idx1_7_0_len 8
+#define	reg_cge_idx1_7_0_lsb 0
+#define xd_p_reg_cge_idx1_12_8	0xA283
+#define	reg_cge_idx1_12_8_pos 0
+#define	reg_cge_idx1_12_8_len 5
+#define	reg_cge_idx1_12_8_lsb 8
+#define xd_p_reg_cge_idx2_7_0	0xA284
+#define	reg_cge_idx2_7_0_pos 0
+#define	reg_cge_idx2_7_0_len 8
+#define	reg_cge_idx2_7_0_lsb 0
+#define xd_p_reg_cge_idx2_12_8	0xA285
+#define	reg_cge_idx2_12_8_pos 0
+#define	reg_cge_idx2_12_8_len 5
+#define	reg_cge_idx2_12_8_lsb 8
+#define xd_p_reg_cge_idx3_7_0	0xA286
+#define	reg_cge_idx3_7_0_pos 0
+#define	reg_cge_idx3_7_0_len 8
+#define	reg_cge_idx3_7_0_lsb 0
+#define xd_p_reg_cge_idx3_12_8	0xA287
+#define	reg_cge_idx3_12_8_pos 0
+#define	reg_cge_idx3_12_8_len 5
+#define	reg_cge_idx3_12_8_lsb 8
+#define xd_p_reg_cge_idx4_7_0	0xA288
+#define	reg_cge_idx4_7_0_pos 0
+#define	reg_cge_idx4_7_0_len 8
+#define	reg_cge_idx4_7_0_lsb 0
+#define xd_p_reg_cge_idx4_12_8	0xA289
+#define	reg_cge_idx4_12_8_pos 0
+#define	reg_cge_idx4_12_8_len 5
+#define	reg_cge_idx4_12_8_lsb 8
+#define xd_p_reg_cge_idx5_7_0	0xA28A
+#define	reg_cge_idx5_7_0_pos 0
+#define	reg_cge_idx5_7_0_len 8
+#define	reg_cge_idx5_7_0_lsb 0
+#define xd_p_reg_cge_idx5_12_8	0xA28B
+#define	reg_cge_idx5_12_8_pos 0
+#define	reg_cge_idx5_12_8_len 5
+#define	reg_cge_idx5_12_8_lsb 8
+#define xd_p_reg_cge_idx6_7_0	0xA28C
+#define	reg_cge_idx6_7_0_pos 0
+#define	reg_cge_idx6_7_0_len 8
+#define	reg_cge_idx6_7_0_lsb 0
+#define xd_p_reg_cge_idx6_12_8	0xA28D
+#define	reg_cge_idx6_12_8_pos 0
+#define	reg_cge_idx6_12_8_len 5
+#define	reg_cge_idx6_12_8_lsb 8
+#define xd_p_reg_cge_idx7_7_0	0xA28E
+#define	reg_cge_idx7_7_0_pos 0
+#define	reg_cge_idx7_7_0_len 8
+#define	reg_cge_idx7_7_0_lsb 0
+#define xd_p_reg_cge_idx7_12_8	0xA28F
+#define	reg_cge_idx7_12_8_pos 0
+#define	reg_cge_idx7_12_8_len 5
+#define	reg_cge_idx7_12_8_lsb 8
+#define xd_p_reg_cge_idx8_7_0	0xA290
+#define	reg_cge_idx8_7_0_pos 0
+#define	reg_cge_idx8_7_0_len 8
+#define	reg_cge_idx8_7_0_lsb 0
+#define xd_p_reg_cge_idx8_12_8	0xA291
+#define	reg_cge_idx8_12_8_pos 0
+#define	reg_cge_idx8_12_8_len 5
+#define	reg_cge_idx8_12_8_lsb 8
+#define xd_p_reg_cge_idx9_7_0	0xA292
+#define	reg_cge_idx9_7_0_pos 0
+#define	reg_cge_idx9_7_0_len 8
+#define	reg_cge_idx9_7_0_lsb 0
+#define xd_p_reg_cge_idx9_12_8	0xA293
+#define	reg_cge_idx9_12_8_pos 0
+#define	reg_cge_idx9_12_8_len 5
+#define	reg_cge_idx9_12_8_lsb 8
+#define xd_p_reg_cge_idx10_7_0	0xA294
+#define	reg_cge_idx10_7_0_pos 0
+#define	reg_cge_idx10_7_0_len 8
+#define	reg_cge_idx10_7_0_lsb 0
+#define xd_p_reg_cge_idx10_12_8	0xA295
+#define	reg_cge_idx10_12_8_pos 0
+#define	reg_cge_idx10_12_8_len 5
+#define	reg_cge_idx10_12_8_lsb 8
+#define xd_p_reg_cge_idx11_7_0	0xA296
+#define	reg_cge_idx11_7_0_pos 0
+#define	reg_cge_idx11_7_0_len 8
+#define	reg_cge_idx11_7_0_lsb 0
+#define xd_p_reg_cge_idx11_12_8	0xA297
+#define	reg_cge_idx11_12_8_pos 0
+#define	reg_cge_idx11_12_8_len 5
+#define	reg_cge_idx11_12_8_lsb 8
+#define xd_p_reg_cge_idx12_7_0	0xA298
+#define	reg_cge_idx12_7_0_pos 0
+#define	reg_cge_idx12_7_0_len 8
+#define	reg_cge_idx12_7_0_lsb 0
+#define xd_p_reg_cge_idx12_12_8	0xA299
+#define	reg_cge_idx12_12_8_pos 0
+#define	reg_cge_idx12_12_8_len 5
+#define	reg_cge_idx12_12_8_lsb 8
+#define xd_p_reg_cge_idx13_7_0	0xA29A
+#define	reg_cge_idx13_7_0_pos 0
+#define	reg_cge_idx13_7_0_len 8
+#define	reg_cge_idx13_7_0_lsb 0
+#define xd_p_reg_cge_idx13_12_8	0xA29B
+#define	reg_cge_idx13_12_8_pos 0
+#define	reg_cge_idx13_12_8_len 5
+#define	reg_cge_idx13_12_8_lsb 8
+#define xd_p_reg_cge_idx14_7_0	0xA29C
+#define	reg_cge_idx14_7_0_pos 0
+#define	reg_cge_idx14_7_0_len 8
+#define	reg_cge_idx14_7_0_lsb 0
+#define xd_p_reg_cge_idx14_12_8	0xA29D
+#define	reg_cge_idx14_12_8_pos 0
+#define	reg_cge_idx14_12_8_len 5
+#define	reg_cge_idx14_12_8_lsb 8
+#define xd_p_reg_cge_idx15_7_0	0xA29E
+#define	reg_cge_idx15_7_0_pos 0
+#define	reg_cge_idx15_7_0_len 8
+#define	reg_cge_idx15_7_0_lsb 0
+#define xd_p_reg_cge_idx15_12_8	0xA29F
+#define	reg_cge_idx15_12_8_pos 0
+#define	reg_cge_idx15_12_8_len 5
+#define	reg_cge_idx15_12_8_lsb 8
+#define xd_r_reg_fft_crc	0xA2A8
+#define	reg_fft_crc_pos 0
+#define	reg_fft_crc_len 8
+#define	reg_fft_crc_lsb 0
+#define xd_p_fd_fft_shift_max	0xA2A9
+#define	fd_fft_shift_max_pos 0
+#define	fd_fft_shift_max_len 4
+#define	fd_fft_shift_max_lsb 0
+#define xd_r_fd_fft_shift	0xA2A9
+#define	fd_fft_shift_pos 4
+#define	fd_fft_shift_len 4
+#define	fd_fft_shift_lsb 0
+#define xd_r_fd_fft_frame_num	0xA2AA
+#define	fd_fft_frame_num_pos 0
+#define	fd_fft_frame_num_len 2
+#define	fd_fft_frame_num_lsb 0
+#define xd_r_fd_fft_symbol_count	0xA2AB
+#define	fd_fft_symbol_count_pos 0
+#define	fd_fft_symbol_count_len 7
+#define	fd_fft_symbol_count_lsb 0
+#define xd_r_reg_fft_idx_max_7_0	0xA2AC
+#define	reg_fft_idx_max_7_0_pos 0
+#define	reg_fft_idx_max_7_0_len 8
+#define	reg_fft_idx_max_7_0_lsb 0
+#define xd_r_reg_fft_idx_max_12_8	0xA2AD
+#define	reg_fft_idx_max_12_8_pos 0
+#define	reg_fft_idx_max_12_8_len 5
+#define	reg_fft_idx_max_12_8_lsb 8
+#define xd_p_reg_cge_program	0xA2AE
+#define	reg_cge_program_pos 0
+#define	reg_cge_program_len 1
+#define	reg_cge_program_lsb 0
+#define xd_p_reg_cge_fixed	0xA2AE
+#define	reg_cge_fixed_pos 1
+#define	reg_cge_fixed_len 1
+#define	reg_cge_fixed_lsb 0
+#define xd_p_reg_fft_rotate_en	0xA2AE
+#define	reg_fft_rotate_en_pos 2
+#define	reg_fft_rotate_en_len 1
+#define	reg_fft_rotate_en_lsb 0
+#define xd_p_reg_fft_rotate_base_4_0	0xA2AE
+#define	reg_fft_rotate_base_4_0_pos 3
+#define	reg_fft_rotate_base_4_0_len 5
+#define	reg_fft_rotate_base_4_0_lsb 0
+#define xd_p_reg_fft_rotate_base_12_5	0xA2AF
+#define	reg_fft_rotate_base_12_5_pos 0
+#define	reg_fft_rotate_base_12_5_len 8
+#define	reg_fft_rotate_base_12_5_lsb 5
+#define xd_p_reg_gp_trigger_fd	0xA2B8
+#define	reg_gp_trigger_fd_pos 0
+#define	reg_gp_trigger_fd_len 1
+#define	reg_gp_trigger_fd_lsb 0
+#define xd_p_reg_trigger_sel_fd	0xA2B8
+#define	reg_trigger_sel_fd_pos 1
+#define	reg_trigger_sel_fd_len 2
+#define	reg_trigger_sel_fd_lsb 0
+#define xd_p_reg_trigger_module_sel_fd	0xA2B9
+#define	reg_trigger_module_sel_fd_pos 0
+#define	reg_trigger_module_sel_fd_len 6
+#define	reg_trigger_module_sel_fd_lsb 0
+#define xd_p_reg_trigger_set_sel_fd	0xA2BA
+#define	reg_trigger_set_sel_fd_pos 0
+#define	reg_trigger_set_sel_fd_len 6
+#define	reg_trigger_set_sel_fd_lsb 0
+#define xd_p_reg_fd_noname_7_0	0xA2BC
+#define	reg_fd_noname_7_0_pos 0
+#define	reg_fd_noname_7_0_len 8
+#define	reg_fd_noname_7_0_lsb 0
+#define xd_p_reg_fd_noname_15_8	0xA2BD
+#define	reg_fd_noname_15_8_pos 0
+#define	reg_fd_noname_15_8_len 8
+#define	reg_fd_noname_15_8_lsb 8
+#define xd_p_reg_fd_noname_23_16	0xA2BE
+#define	reg_fd_noname_23_16_pos 0
+#define	reg_fd_noname_23_16_len 8
+#define	reg_fd_noname_23_16_lsb 16
+#define xd_p_reg_fd_noname_31_24	0xA2BF
+#define	reg_fd_noname_31_24_pos 0
+#define	reg_fd_noname_31_24_len 8
+#define	reg_fd_noname_31_24_lsb 24
+#define xd_r_fd_fpcc_cp_corr_signn	0xA2C0
+#define	fd_fpcc_cp_corr_signn_pos 0
+#define	fd_fpcc_cp_corr_signn_len 8
+#define	fd_fpcc_cp_corr_signn_lsb 0
+#define xd_p_reg_feq_s1	0xA2C1
+#define	reg_feq_s1_pos 0
+#define	reg_feq_s1_len 5
+#define	reg_feq_s1_lsb 0
+#define xd_p_fd_fpcc_cp_corr_tone_th	0xA2C2
+#define	fd_fpcc_cp_corr_tone_th_pos 0
+#define	fd_fpcc_cp_corr_tone_th_len 6
+#define	fd_fpcc_cp_corr_tone_th_lsb 0
+#define xd_p_fd_fpcc_cp_corr_symbol_log_th	0xA2C3
+#define	fd_fpcc_cp_corr_symbol_log_th_pos 0
+#define	fd_fpcc_cp_corr_symbol_log_th_len 4
+#define	fd_fpcc_cp_corr_symbol_log_th_lsb 0
+#define xd_p_fd_fpcc_cp_corr_int	0xA2C4
+#define	fd_fpcc_cp_corr_int_pos 0
+#define	fd_fpcc_cp_corr_int_len 1
+#define	fd_fpcc_cp_corr_int_lsb 0
+#define xd_p_reg_sfoe_ns_7_0	0xA320
+#define	reg_sfoe_ns_7_0_pos 0
+#define	reg_sfoe_ns_7_0_len 8
+#define	reg_sfoe_ns_7_0_lsb 0
+#define xd_p_reg_sfoe_ns_14_8	0xA321
+#define	reg_sfoe_ns_14_8_pos 0
+#define	reg_sfoe_ns_14_8_len 7
+#define	reg_sfoe_ns_14_8_lsb 8
+#define xd_p_reg_sfoe_c1_7_0	0xA322
+#define	reg_sfoe_c1_7_0_pos 0
+#define	reg_sfoe_c1_7_0_len 8
+#define	reg_sfoe_c1_7_0_lsb 0
+#define xd_p_reg_sfoe_c1_15_8	0xA323
+#define	reg_sfoe_c1_15_8_pos 0
+#define	reg_sfoe_c1_15_8_len 8
+#define	reg_sfoe_c1_15_8_lsb 8
+#define xd_p_reg_sfoe_c1_17_16	0xA324
+#define	reg_sfoe_c1_17_16_pos 0
+#define	reg_sfoe_c1_17_16_len 2
+#define	reg_sfoe_c1_17_16_lsb 16
+#define xd_p_reg_sfoe_c2_7_0	0xA325
+#define	reg_sfoe_c2_7_0_pos 0
+#define	reg_sfoe_c2_7_0_len 8
+#define	reg_sfoe_c2_7_0_lsb 0
+#define xd_p_reg_sfoe_c2_15_8	0xA326
+#define	reg_sfoe_c2_15_8_pos 0
+#define	reg_sfoe_c2_15_8_len 8
+#define	reg_sfoe_c2_15_8_lsb 8
+#define xd_p_reg_sfoe_c2_17_16	0xA327
+#define	reg_sfoe_c2_17_16_pos 0
+#define	reg_sfoe_c2_17_16_len 2
+#define	reg_sfoe_c2_17_16_lsb 16
+#define xd_r_reg_sfoe_out_9_2	0xA328
+#define	reg_sfoe_out_9_2_pos 0
+#define	reg_sfoe_out_9_2_len 8
+#define	reg_sfoe_out_9_2_lsb 0
+#define xd_r_reg_sfoe_out_1_0	0xA329
+#define	reg_sfoe_out_1_0_pos 0
+#define	reg_sfoe_out_1_0_len 2
+#define	reg_sfoe_out_1_0_lsb 0
+#define xd_p_reg_sfoe_lm_counter_th	0xA32A
+#define	reg_sfoe_lm_counter_th_pos 0
+#define	reg_sfoe_lm_counter_th_len 4
+#define	reg_sfoe_lm_counter_th_lsb 0
+#define xd_p_reg_sfoe_convg_th	0xA32B
+#define	reg_sfoe_convg_th_pos 0
+#define	reg_sfoe_convg_th_len 8
+#define	reg_sfoe_convg_th_lsb 0
+#define xd_p_reg_sfoe_divg_th	0xA32C
+#define	reg_sfoe_divg_th_pos 0
+#define	reg_sfoe_divg_th_len 8
+#define	reg_sfoe_divg_th_lsb 0
+#define xd_p_fd_tpsd_en	0xA330
+#define	fd_tpsd_en_pos 0
+#define	fd_tpsd_en_len 1
+#define	fd_tpsd_en_lsb 0
+#define xd_p_fd_tpsd_dis	0xA330
+#define	fd_tpsd_dis_pos 1
+#define	fd_tpsd_dis_len 1
+#define	fd_tpsd_dis_lsb 0
+#define xd_p_fd_tpsd_rst	0xA330
+#define	fd_tpsd_rst_pos 2
+#define	fd_tpsd_rst_len 1
+#define	fd_tpsd_rst_lsb 0
+#define xd_p_fd_tpsd_lock	0xA330
+#define	fd_tpsd_lock_pos 3
+#define	fd_tpsd_lock_len 1
+#define	fd_tpsd_lock_lsb 0
+#define xd_r_fd_tpsd_s19	0xA330
+#define	fd_tpsd_s19_pos 4
+#define	fd_tpsd_s19_len 1
+#define	fd_tpsd_s19_lsb 0
+#define xd_r_fd_tpsd_s17	0xA330
+#define	fd_tpsd_s17_pos 5
+#define	fd_tpsd_s17_len 1
+#define	fd_tpsd_s17_lsb 0
+#define xd_p_fd_sfr_ste_en	0xA331
+#define	fd_sfr_ste_en_pos 0
+#define	fd_sfr_ste_en_len 1
+#define	fd_sfr_ste_en_lsb 0
+#define xd_p_fd_sfr_ste_dis	0xA331
+#define	fd_sfr_ste_dis_pos 1
+#define	fd_sfr_ste_dis_len 1
+#define	fd_sfr_ste_dis_lsb 0
+#define xd_p_fd_sfr_ste_rst	0xA331
+#define	fd_sfr_ste_rst_pos 2
+#define	fd_sfr_ste_rst_len 1
+#define	fd_sfr_ste_rst_lsb 0
+#define xd_p_fd_sfr_ste_mode	0xA331
+#define	fd_sfr_ste_mode_pos 3
+#define	fd_sfr_ste_mode_len 1
+#define	fd_sfr_ste_mode_lsb 0
+#define xd_p_fd_sfr_ste_done	0xA331
+#define	fd_sfr_ste_done_pos 4
+#define	fd_sfr_ste_done_len 1
+#define	fd_sfr_ste_done_lsb 0
+#define xd_p_reg_cfoe_ffoe_en	0xA332
+#define	reg_cfoe_ffoe_en_pos 0
+#define	reg_cfoe_ffoe_en_len 1
+#define	reg_cfoe_ffoe_en_lsb 0
+#define xd_p_reg_cfoe_ffoe_dis	0xA332
+#define	reg_cfoe_ffoe_dis_pos 1
+#define	reg_cfoe_ffoe_dis_len 1
+#define	reg_cfoe_ffoe_dis_lsb 0
+#define xd_p_reg_cfoe_ffoe_rst	0xA332
+#define	reg_cfoe_ffoe_rst_pos 2
+#define	reg_cfoe_ffoe_rst_len 1
+#define	reg_cfoe_ffoe_rst_lsb 0
+#define xd_p_reg_cfoe_ifoe_en	0xA332
+#define	reg_cfoe_ifoe_en_pos 3
+#define	reg_cfoe_ifoe_en_len 1
+#define	reg_cfoe_ifoe_en_lsb 0
+#define xd_p_reg_cfoe_ifoe_dis	0xA332
+#define	reg_cfoe_ifoe_dis_pos 4
+#define	reg_cfoe_ifoe_dis_len 1
+#define	reg_cfoe_ifoe_dis_lsb 0
+#define xd_p_reg_cfoe_ifoe_rst	0xA332
+#define	reg_cfoe_ifoe_rst_pos 5
+#define	reg_cfoe_ifoe_rst_len 1
+#define	reg_cfoe_ifoe_rst_lsb 0
+#define xd_p_reg_cfoe_fot_en	0xA332
+#define	reg_cfoe_fot_en_pos 6
+#define	reg_cfoe_fot_en_len 1
+#define	reg_cfoe_fot_en_lsb 0
+#define xd_p_reg_cfoe_fot_lm_en	0xA332
+#define	reg_cfoe_fot_lm_en_pos 7
+#define	reg_cfoe_fot_lm_en_len 1
+#define	reg_cfoe_fot_lm_en_lsb 0
+#define xd_p_reg_cfoe_fot_rst	0xA333
+#define	reg_cfoe_fot_rst_pos 0
+#define	reg_cfoe_fot_rst_len 1
+#define	reg_cfoe_fot_rst_lsb 0
+#define xd_r_fd_cfoe_ffoe_done	0xA333
+#define	fd_cfoe_ffoe_done_pos 1
+#define	fd_cfoe_ffoe_done_len 1
+#define	fd_cfoe_ffoe_done_lsb 0
+#define xd_p_fd_cfoe_metric_vld	0xA333
+#define	fd_cfoe_metric_vld_pos 2
+#define	fd_cfoe_metric_vld_len 1
+#define	fd_cfoe_metric_vld_lsb 0
+#define xd_p_reg_cfoe_ifod_vld	0xA333
+#define	reg_cfoe_ifod_vld_pos 3
+#define	reg_cfoe_ifod_vld_len 1
+#define	reg_cfoe_ifod_vld_lsb 0
+#define xd_r_fd_cfoe_ifoe_done	0xA333
+#define	fd_cfoe_ifoe_done_pos 4
+#define	fd_cfoe_ifoe_done_len 1
+#define	fd_cfoe_ifoe_done_lsb 0
+#define xd_r_fd_cfoe_fot_valid	0xA333
+#define	fd_cfoe_fot_valid_pos 5
+#define	fd_cfoe_fot_valid_len 1
+#define	fd_cfoe_fot_valid_lsb 0
+#define xd_p_reg_cfoe_divg_int	0xA333
+#define	reg_cfoe_divg_int_pos 6
+#define	reg_cfoe_divg_int_len 1
+#define	reg_cfoe_divg_int_lsb 0
+#define xd_r_reg_cfoe_divg_flag	0xA333
+#define	reg_cfoe_divg_flag_pos 7
+#define	reg_cfoe_divg_flag_len 1
+#define	reg_cfoe_divg_flag_lsb 0
+#define xd_p_reg_sfoe_en	0xA334
+#define	reg_sfoe_en_pos 0
+#define	reg_sfoe_en_len 1
+#define	reg_sfoe_en_lsb 0
+#define xd_p_reg_sfoe_dis	0xA334
+#define	reg_sfoe_dis_pos 1
+#define	reg_sfoe_dis_len 1
+#define	reg_sfoe_dis_lsb 0
+#define xd_p_reg_sfoe_rst	0xA334
+#define	reg_sfoe_rst_pos 2
+#define	reg_sfoe_rst_len 1
+#define	reg_sfoe_rst_lsb 0
+#define xd_p_reg_sfoe_vld_int	0xA334
+#define	reg_sfoe_vld_int_pos 3
+#define	reg_sfoe_vld_int_len 1
+#define	reg_sfoe_vld_int_lsb 0
+#define xd_p_reg_sfoe_lm_en	0xA334
+#define	reg_sfoe_lm_en_pos 4
+#define	reg_sfoe_lm_en_len 1
+#define	reg_sfoe_lm_en_lsb 0
+#define xd_p_reg_sfoe_divg_int	0xA334
+#define	reg_sfoe_divg_int_pos 5
+#define	reg_sfoe_divg_int_len 1
+#define	reg_sfoe_divg_int_lsb 0
+#define xd_r_reg_sfoe_divg_flag	0xA334
+#define	reg_sfoe_divg_flag_pos 6
+#define	reg_sfoe_divg_flag_len 1
+#define	reg_sfoe_divg_flag_lsb 0
+#define xd_p_reg_fft_rst	0xA335
+#define	reg_fft_rst_pos 0
+#define	reg_fft_rst_len 1
+#define	reg_fft_rst_lsb 0
+#define xd_p_reg_fft_fast_beacon	0xA335
+#define	reg_fft_fast_beacon_pos 1
+#define	reg_fft_fast_beacon_len 1
+#define	reg_fft_fast_beacon_lsb 0
+#define xd_p_reg_fft_fast_valid	0xA335
+#define	reg_fft_fast_valid_pos 2
+#define	reg_fft_fast_valid_len 1
+#define	reg_fft_fast_valid_lsb 0
+#define xd_p_reg_fft_mask_en	0xA335
+#define	reg_fft_mask_en_pos 3
+#define	reg_fft_mask_en_len 1
+#define	reg_fft_mask_en_lsb 0
+#define xd_p_reg_fft_crc_en	0xA335
+#define	reg_fft_crc_en_pos 4
+#define	reg_fft_crc_en_len 1
+#define	reg_fft_crc_en_lsb 0
+#define xd_p_reg_finr_en	0xA336
+#define	reg_finr_en_pos 0
+#define	reg_finr_en_len 1
+#define	reg_finr_en_lsb 0
+#define xd_p_fd_fste_en	0xA337
+#define	fd_fste_en_pos 1
+#define	fd_fste_en_len 1
+#define	fd_fste_en_lsb 0
+#define xd_p_fd_sqi_tps_level_shift	0xA338
+#define	fd_sqi_tps_level_shift_pos 0
+#define	fd_sqi_tps_level_shift_len 8
+#define	fd_sqi_tps_level_shift_lsb 0
+#define xd_p_fd_pilot_ma_len	0xA339
+#define	fd_pilot_ma_len_pos 0
+#define	fd_pilot_ma_len_len 6
+#define	fd_pilot_ma_len_lsb 0
+#define xd_p_fd_tps_ma_len	0xA33A
+#define	fd_tps_ma_len_pos 0
+#define	fd_tps_ma_len_len 6
+#define	fd_tps_ma_len_lsb 0
+#define xd_p_fd_sqi_s3	0xA33B
+#define	fd_sqi_s3_pos 0
+#define	fd_sqi_s3_len 8
+#define	fd_sqi_s3_lsb 0
+#define xd_p_fd_sqi_dummy_reg_0	0xA33C
+#define	fd_sqi_dummy_reg_0_pos 0
+#define	fd_sqi_dummy_reg_0_len 1
+#define	fd_sqi_dummy_reg_0_lsb 0
+#define xd_p_fd_sqi_debug_sel	0xA33C
+#define	fd_sqi_debug_sel_pos 1
+#define	fd_sqi_debug_sel_len 2
+#define	fd_sqi_debug_sel_lsb 0
+#define xd_p_fd_sqi_s2	0xA33C
+#define	fd_sqi_s2_pos 3
+#define	fd_sqi_s2_len 5
+#define	fd_sqi_s2_lsb 0
+#define xd_p_fd_sqi_dummy_reg_1	0xA33D
+#define	fd_sqi_dummy_reg_1_pos 0
+#define	fd_sqi_dummy_reg_1_len 1
+#define	fd_sqi_dummy_reg_1_lsb 0
+#define xd_p_fd_inr_ignore	0xA33D
+#define	fd_inr_ignore_pos 1
+#define	fd_inr_ignore_len 1
+#define	fd_inr_ignore_lsb 0
+#define xd_p_fd_pilot_ignore	0xA33D
+#define	fd_pilot_ignore_pos 2
+#define	fd_pilot_ignore_len 1
+#define	fd_pilot_ignore_lsb 0
+#define xd_p_fd_etps_ignore	0xA33D
+#define	fd_etps_ignore_pos 3
+#define	fd_etps_ignore_len 1
+#define	fd_etps_ignore_lsb 0
+#define xd_p_fd_sqi_s1	0xA33D
+#define	fd_sqi_s1_pos 4
+#define	fd_sqi_s1_len 4
+#define	fd_sqi_s1_lsb 0
+#define xd_p_reg_fste_ehw_7_0	0xA33E
+#define	reg_fste_ehw_7_0_pos 0
+#define	reg_fste_ehw_7_0_len 8
+#define	reg_fste_ehw_7_0_lsb 0
+#define xd_p_reg_fste_ehw_9_8	0xA33F
+#define	reg_fste_ehw_9_8_pos 0
+#define	reg_fste_ehw_9_8_len 2
+#define	reg_fste_ehw_9_8_lsb 8
+#define xd_p_reg_fste_i_adj_vld	0xA33F
+#define	reg_fste_i_adj_vld_pos 2
+#define	reg_fste_i_adj_vld_len 1
+#define	reg_fste_i_adj_vld_lsb 0
+#define xd_p_reg_fste_phase_ini_7_0	0xA340
+#define	reg_fste_phase_ini_7_0_pos 0
+#define	reg_fste_phase_ini_7_0_len 8
+#define	reg_fste_phase_ini_7_0_lsb 0
+#define xd_p_reg_fste_phase_ini_11_8	0xA341
+#define	reg_fste_phase_ini_11_8_pos 0
+#define	reg_fste_phase_ini_11_8_len 4
+#define	reg_fste_phase_ini_11_8_lsb 8
+#define xd_p_reg_fste_phase_inc_3_0	0xA341
+#define	reg_fste_phase_inc_3_0_pos 4
+#define	reg_fste_phase_inc_3_0_len 4
+#define	reg_fste_phase_inc_3_0_lsb 0
+#define xd_p_reg_fste_phase_inc_11_4	0xA342
+#define	reg_fste_phase_inc_11_4_pos 0
+#define	reg_fste_phase_inc_11_4_len 8
+#define	reg_fste_phase_inc_11_4_lsb 4
+#define xd_p_reg_fste_acum_cost_cnt_max	0xA343
+#define	reg_fste_acum_cost_cnt_max_pos 0
+#define	reg_fste_acum_cost_cnt_max_len 4
+#define	reg_fste_acum_cost_cnt_max_lsb 0
+#define xd_p_reg_fste_step_size_std	0xA343
+#define	reg_fste_step_size_std_pos 4
+#define	reg_fste_step_size_std_len 4
+#define	reg_fste_step_size_std_lsb 0
+#define xd_p_reg_fste_step_size_max	0xA344
+#define	reg_fste_step_size_max_pos 0
+#define	reg_fste_step_size_max_len 4
+#define	reg_fste_step_size_max_lsb 0
+#define xd_p_reg_fste_step_size_min	0xA344
+#define	reg_fste_step_size_min_pos 4
+#define	reg_fste_step_size_min_len 4
+#define	reg_fste_step_size_min_lsb 0
+#define xd_p_reg_fste_frac_step_size_7_0	0xA345
+#define	reg_fste_frac_step_size_7_0_pos 0
+#define	reg_fste_frac_step_size_7_0_len 8
+#define	reg_fste_frac_step_size_7_0_lsb 0
+#define xd_p_reg_fste_frac_step_size_15_8	0xA346
+#define	reg_fste_frac_step_size_15_8_pos 0
+#define	reg_fste_frac_step_size_15_8_len 8
+#define	reg_fste_frac_step_size_15_8_lsb 8
+#define xd_p_reg_fste_frac_step_size_19_16	0xA347
+#define	reg_fste_frac_step_size_19_16_pos 0
+#define	reg_fste_frac_step_size_19_16_len 4
+#define	reg_fste_frac_step_size_19_16_lsb 16
+#define xd_p_reg_fste_rpd_dir_cnt_max	0xA347
+#define	reg_fste_rpd_dir_cnt_max_pos 4
+#define	reg_fste_rpd_dir_cnt_max_len 4
+#define	reg_fste_rpd_dir_cnt_max_lsb 0
+#define xd_p_reg_fste_ehs	0xA348
+#define	reg_fste_ehs_pos 0
+#define	reg_fste_ehs_len 4
+#define	reg_fste_ehs_lsb 0
+#define xd_p_reg_fste_frac_cost_cnt_max_3_0	0xA348
+#define	reg_fste_frac_cost_cnt_max_3_0_pos 4
+#define	reg_fste_frac_cost_cnt_max_3_0_len 4
+#define	reg_fste_frac_cost_cnt_max_3_0_lsb 0
+#define xd_p_reg_fste_frac_cost_cnt_max_9_4	0xA349
+#define	reg_fste_frac_cost_cnt_max_9_4_pos 0
+#define	reg_fste_frac_cost_cnt_max_9_4_len 6
+#define	reg_fste_frac_cost_cnt_max_9_4_lsb 4
+#define xd_p_reg_fste_w0_7_0	0xA34A
+#define	reg_fste_w0_7_0_pos 0
+#define	reg_fste_w0_7_0_len 8
+#define	reg_fste_w0_7_0_lsb 0
+#define xd_p_reg_fste_w0_11_8	0xA34B
+#define	reg_fste_w0_11_8_pos 0
+#define	reg_fste_w0_11_8_len 4
+#define	reg_fste_w0_11_8_lsb 8
+#define xd_p_reg_fste_w1_3_0	0xA34B
+#define	reg_fste_w1_3_0_pos 4
+#define	reg_fste_w1_3_0_len 4
+#define	reg_fste_w1_3_0_lsb 0
+#define xd_p_reg_fste_w1_11_4	0xA34C
+#define	reg_fste_w1_11_4_pos 0
+#define	reg_fste_w1_11_4_len 8
+#define	reg_fste_w1_11_4_lsb 4
+#define xd_p_reg_fste_w2_7_0	0xA34D
+#define	reg_fste_w2_7_0_pos 0
+#define	reg_fste_w2_7_0_len 8
+#define	reg_fste_w2_7_0_lsb 0
+#define xd_p_reg_fste_w2_11_8	0xA34E
+#define	reg_fste_w2_11_8_pos 0
+#define	reg_fste_w2_11_8_len 4
+#define	reg_fste_w2_11_8_lsb 8
+#define xd_p_reg_fste_w3_3_0	0xA34E
+#define	reg_fste_w3_3_0_pos 4
+#define	reg_fste_w3_3_0_len 4
+#define	reg_fste_w3_3_0_lsb 0
+#define xd_p_reg_fste_w3_11_4	0xA34F
+#define	reg_fste_w3_11_4_pos 0
+#define	reg_fste_w3_11_4_len 8
+#define	reg_fste_w3_11_4_lsb 4
+#define xd_p_reg_fste_w4_7_0	0xA350
+#define	reg_fste_w4_7_0_pos 0
+#define	reg_fste_w4_7_0_len 8
+#define	reg_fste_w4_7_0_lsb 0
+#define xd_p_reg_fste_w4_11_8	0xA351
+#define	reg_fste_w4_11_8_pos 0
+#define	reg_fste_w4_11_8_len 4
+#define	reg_fste_w4_11_8_lsb 8
+#define xd_p_reg_fste_w5_3_0	0xA351
+#define	reg_fste_w5_3_0_pos 4
+#define	reg_fste_w5_3_0_len 4
+#define	reg_fste_w5_3_0_lsb 0
+#define xd_p_reg_fste_w5_11_4	0xA352
+#define	reg_fste_w5_11_4_pos 0
+#define	reg_fste_w5_11_4_len 8
+#define	reg_fste_w5_11_4_lsb 4
+#define xd_p_reg_fste_w6_7_0	0xA353
+#define	reg_fste_w6_7_0_pos 0
+#define	reg_fste_w6_7_0_len 8
+#define	reg_fste_w6_7_0_lsb 0
+#define xd_p_reg_fste_w6_11_8	0xA354
+#define	reg_fste_w6_11_8_pos 0
+#define	reg_fste_w6_11_8_len 4
+#define	reg_fste_w6_11_8_lsb 8
+#define xd_p_reg_fste_w7_3_0	0xA354
+#define	reg_fste_w7_3_0_pos 4
+#define	reg_fste_w7_3_0_len 4
+#define	reg_fste_w7_3_0_lsb 0
+#define xd_p_reg_fste_w7_11_4	0xA355
+#define	reg_fste_w7_11_4_pos 0
+#define	reg_fste_w7_11_4_len 8
+#define	reg_fste_w7_11_4_lsb 4
+#define xd_p_reg_fste_w8_7_0	0xA356
+#define	reg_fste_w8_7_0_pos 0
+#define	reg_fste_w8_7_0_len 8
+#define	reg_fste_w8_7_0_lsb 0
+#define xd_p_reg_fste_w8_11_8	0xA357
+#define	reg_fste_w8_11_8_pos 0
+#define	reg_fste_w8_11_8_len 4
+#define	reg_fste_w8_11_8_lsb 8
+#define xd_p_reg_fste_w9_3_0	0xA357
+#define	reg_fste_w9_3_0_pos 4
+#define	reg_fste_w9_3_0_len 4
+#define	reg_fste_w9_3_0_lsb 0
+#define xd_p_reg_fste_w9_11_4	0xA358
+#define	reg_fste_w9_11_4_pos 0
+#define	reg_fste_w9_11_4_len 8
+#define	reg_fste_w9_11_4_lsb 4
+#define xd_p_reg_fste_wa_7_0	0xA359
+#define	reg_fste_wa_7_0_pos 0
+#define	reg_fste_wa_7_0_len 8
+#define	reg_fste_wa_7_0_lsb 0
+#define xd_p_reg_fste_wa_11_8	0xA35A
+#define	reg_fste_wa_11_8_pos 0
+#define	reg_fste_wa_11_8_len 4
+#define	reg_fste_wa_11_8_lsb 8
+#define xd_p_reg_fste_wb_3_0	0xA35A
+#define	reg_fste_wb_3_0_pos 4
+#define	reg_fste_wb_3_0_len 4
+#define	reg_fste_wb_3_0_lsb 0
+#define xd_p_reg_fste_wb_11_4	0xA35B
+#define	reg_fste_wb_11_4_pos 0
+#define	reg_fste_wb_11_4_len 8
+#define	reg_fste_wb_11_4_lsb 4
+#define xd_r_fd_fste_i_adj	0xA35C
+#define	fd_fste_i_adj_pos 0
+#define	fd_fste_i_adj_len 5
+#define	fd_fste_i_adj_lsb 0
+#define xd_r_fd_fste_f_adj_7_0	0xA35D
+#define	fd_fste_f_adj_7_0_pos 0
+#define	fd_fste_f_adj_7_0_len 8
+#define	fd_fste_f_adj_7_0_lsb 0
+#define xd_r_fd_fste_f_adj_15_8	0xA35E
+#define	fd_fste_f_adj_15_8_pos 0
+#define	fd_fste_f_adj_15_8_len 8
+#define	fd_fste_f_adj_15_8_lsb 8
+#define xd_r_fd_fste_f_adj_19_16	0xA35F
+#define	fd_fste_f_adj_19_16_pos 0
+#define	fd_fste_f_adj_19_16_len 4
+#define	fd_fste_f_adj_19_16_lsb 16
+#define xd_p_reg_feq_Leak_Bypass	0xA366
+#define	reg_feq_Leak_Bypass_pos 0
+#define	reg_feq_Leak_Bypass_len 1
+#define	reg_feq_Leak_Bypass_lsb 0
+#define xd_p_reg_feq_Leak_Mneg1	0xA366
+#define	reg_feq_Leak_Mneg1_pos 1
+#define	reg_feq_Leak_Mneg1_len 3
+#define	reg_feq_Leak_Mneg1_lsb 0
+#define xd_p_reg_feq_Leak_B_ShiftQ	0xA366
+#define	reg_feq_Leak_B_ShiftQ_pos 4
+#define	reg_feq_Leak_B_ShiftQ_len 4
+#define	reg_feq_Leak_B_ShiftQ_lsb 0
+#define xd_p_reg_feq_Leak_B_Float0	0xA367
+#define	reg_feq_Leak_B_Float0_pos 0
+#define	reg_feq_Leak_B_Float0_len 8
+#define	reg_feq_Leak_B_Float0_lsb 0
+#define xd_p_reg_feq_Leak_B_Float1	0xA368
+#define	reg_feq_Leak_B_Float1_pos 0
+#define	reg_feq_Leak_B_Float1_len 8
+#define	reg_feq_Leak_B_Float1_lsb 0
+#define xd_p_reg_feq_Leak_B_Float2	0xA369
+#define	reg_feq_Leak_B_Float2_pos 0
+#define	reg_feq_Leak_B_Float2_len 8
+#define	reg_feq_Leak_B_Float2_lsb 0
+#define xd_p_reg_feq_Leak_B_Float3	0xA36A
+#define	reg_feq_Leak_B_Float3_pos 0
+#define	reg_feq_Leak_B_Float3_len 8
+#define	reg_feq_Leak_B_Float3_lsb 0
+#define xd_p_reg_feq_Leak_B_Float4	0xA36B
+#define	reg_feq_Leak_B_Float4_pos 0
+#define	reg_feq_Leak_B_Float4_len 8
+#define	reg_feq_Leak_B_Float4_lsb 0
+#define xd_p_reg_feq_Leak_B_Float5	0xA36C
+#define	reg_feq_Leak_B_Float5_pos 0
+#define	reg_feq_Leak_B_Float5_len 8
+#define	reg_feq_Leak_B_Float5_lsb 0
+#define xd_p_reg_feq_Leak_B_Float6	0xA36D
+#define	reg_feq_Leak_B_Float6_pos 0
+#define	reg_feq_Leak_B_Float6_len 8
+#define	reg_feq_Leak_B_Float6_lsb 0
+#define xd_p_reg_feq_Leak_B_Float7	0xA36E
+#define	reg_feq_Leak_B_Float7_pos 0
+#define	reg_feq_Leak_B_Float7_len 8
+#define	reg_feq_Leak_B_Float7_lsb 0
+#define xd_r_reg_feq_data_h2_7_0	0xA36F
+#define	reg_feq_data_h2_7_0_pos 0
+#define	reg_feq_data_h2_7_0_len 8
+#define	reg_feq_data_h2_7_0_lsb 0
+#define xd_r_reg_feq_data_h2_9_8	0xA370
+#define	reg_feq_data_h2_9_8_pos 0
+#define	reg_feq_data_h2_9_8_len 2
+#define	reg_feq_data_h2_9_8_lsb 8
+#define xd_p_reg_feq_leak_use_slice_tps	0xA371
+#define	reg_feq_leak_use_slice_tps_pos 0
+#define	reg_feq_leak_use_slice_tps_len 1
+#define	reg_feq_leak_use_slice_tps_lsb 0
+#define xd_p_reg_feq_read_update	0xA371
+#define	reg_feq_read_update_pos 1
+#define	reg_feq_read_update_len 1
+#define	reg_feq_read_update_lsb 0
+#define xd_p_reg_feq_data_vld	0xA371
+#define	reg_feq_data_vld_pos 2
+#define	reg_feq_data_vld_len 1
+#define	reg_feq_data_vld_lsb 0
+#define xd_p_reg_feq_tone_idx_4_0	0xA371
+#define	reg_feq_tone_idx_4_0_pos 3
+#define	reg_feq_tone_idx_4_0_len 5
+#define	reg_feq_tone_idx_4_0_lsb 0
+#define xd_p_reg_feq_tone_idx_12_5	0xA372
+#define	reg_feq_tone_idx_12_5_pos 0
+#define	reg_feq_tone_idx_12_5_len 8
+#define	reg_feq_tone_idx_12_5_lsb 5
+#define xd_r_reg_feq_data_re_7_0	0xA373
+#define	reg_feq_data_re_7_0_pos 0
+#define	reg_feq_data_re_7_0_len 8
+#define	reg_feq_data_re_7_0_lsb 0
+#define xd_r_reg_feq_data_re_10_8	0xA374
+#define	reg_feq_data_re_10_8_pos 0
+#define	reg_feq_data_re_10_8_len 3
+#define	reg_feq_data_re_10_8_lsb 8
+#define xd_r_reg_feq_data_im_7_0	0xA375
+#define	reg_feq_data_im_7_0_pos 0
+#define	reg_feq_data_im_7_0_len 8
+#define	reg_feq_data_im_7_0_lsb 0
+#define xd_r_reg_feq_data_im_10_8	0xA376
+#define	reg_feq_data_im_10_8_pos 0
+#define	reg_feq_data_im_10_8_len 3
+#define	reg_feq_data_im_10_8_lsb 8
+#define xd_r_reg_feq_y_re	0xA377
+#define	reg_feq_y_re_pos 0
+#define	reg_feq_y_re_len 8
+#define	reg_feq_y_re_lsb 0
+#define xd_r_reg_feq_y_im	0xA378
+#define	reg_feq_y_im_pos 0
+#define	reg_feq_y_im_len 8
+#define	reg_feq_y_im_lsb 0
+#define xd_r_reg_feq_h_re_7_0	0xA379
+#define	reg_feq_h_re_7_0_pos 0
+#define	reg_feq_h_re_7_0_len 8
+#define	reg_feq_h_re_7_0_lsb 0
+#define xd_r_reg_feq_h_re_8	0xA37A
+#define	reg_feq_h_re_8_pos 0
+#define	reg_feq_h_re_8_len 1
+#define	reg_feq_h_re_8_lsb 0
+#define xd_r_reg_feq_h_im_7_0	0xA37B
+#define	reg_feq_h_im_7_0_pos 0
+#define	reg_feq_h_im_7_0_len 8
+#define	reg_feq_h_im_7_0_lsb 0
+#define xd_r_reg_feq_h_im_8	0xA37C
+#define	reg_feq_h_im_8_pos 0
+#define	reg_feq_h_im_8_len 1
+#define	reg_feq_h_im_8_lsb 0
+#define xd_p_fec_super_frm_unit_7_0	0xA380
+#define	fec_super_frm_unit_7_0_pos 0
+#define	fec_super_frm_unit_7_0_len 8
+#define	fec_super_frm_unit_7_0_lsb 0
+#define xd_p_fec_super_frm_unit_15_8	0xA381
+#define	fec_super_frm_unit_15_8_pos 0
+#define	fec_super_frm_unit_15_8_len 8
+#define	fec_super_frm_unit_15_8_lsb 8
+#define xd_r_fec_vtb_err_bit_cnt_7_0	0xA382
+#define	fec_vtb_err_bit_cnt_7_0_pos 0
+#define	fec_vtb_err_bit_cnt_7_0_len 8
+#define	fec_vtb_err_bit_cnt_7_0_lsb 0
+#define xd_r_fec_vtb_err_bit_cnt_15_8	0xA383
+#define	fec_vtb_err_bit_cnt_15_8_pos 0
+#define	fec_vtb_err_bit_cnt_15_8_len 8
+#define	fec_vtb_err_bit_cnt_15_8_lsb 8
+#define xd_r_fec_vtb_err_bit_cnt_23_16	0xA384
+#define	fec_vtb_err_bit_cnt_23_16_pos 0
+#define	fec_vtb_err_bit_cnt_23_16_len 8
+#define	fec_vtb_err_bit_cnt_23_16_lsb 16
+#define xd_p_fec_rsd_packet_unit_7_0	0xA385
+#define	fec_rsd_packet_unit_7_0_pos 0
+#define	fec_rsd_packet_unit_7_0_len 8
+#define	fec_rsd_packet_unit_7_0_lsb 0
+#define xd_p_fec_rsd_packet_unit_15_8	0xA386
+#define	fec_rsd_packet_unit_15_8_pos 0
+#define	fec_rsd_packet_unit_15_8_len 8
+#define	fec_rsd_packet_unit_15_8_lsb 8
+#define xd_r_fec_rsd_bit_err_cnt_7_0	0xA387
+#define	fec_rsd_bit_err_cnt_7_0_pos 0
+#define	fec_rsd_bit_err_cnt_7_0_len 8
+#define	fec_rsd_bit_err_cnt_7_0_lsb 0
+#define xd_r_fec_rsd_bit_err_cnt_15_8	0xA388
+#define	fec_rsd_bit_err_cnt_15_8_pos 0
+#define	fec_rsd_bit_err_cnt_15_8_len 8
+#define	fec_rsd_bit_err_cnt_15_8_lsb 8
+#define xd_r_fec_rsd_bit_err_cnt_23_16	0xA389
+#define	fec_rsd_bit_err_cnt_23_16_pos 0
+#define	fec_rsd_bit_err_cnt_23_16_len 8
+#define	fec_rsd_bit_err_cnt_23_16_lsb 16
+#define xd_r_fec_rsd_abort_packet_cnt_7_0	0xA38A
+#define	fec_rsd_abort_packet_cnt_7_0_pos 0
+#define	fec_rsd_abort_packet_cnt_7_0_len 8
+#define	fec_rsd_abort_packet_cnt_7_0_lsb 0
+#define xd_r_fec_rsd_abort_packet_cnt_15_8	0xA38B
+#define	fec_rsd_abort_packet_cnt_15_8_pos 0
+#define	fec_rsd_abort_packet_cnt_15_8_len 8
+#define	fec_rsd_abort_packet_cnt_15_8_lsb 8
+#define xd_p_fec_RSD_PKT_NUM_PER_UNIT_7_0	0xA38C
+#define	fec_RSD_PKT_NUM_PER_UNIT_7_0_pos 0
+#define	fec_RSD_PKT_NUM_PER_UNIT_7_0_len 8
+#define	fec_RSD_PKT_NUM_PER_UNIT_7_0_lsb 0
+#define xd_p_fec_RSD_PKT_NUM_PER_UNIT_15_8	0xA38D
+#define	fec_RSD_PKT_NUM_PER_UNIT_15_8_pos 0
+#define	fec_RSD_PKT_NUM_PER_UNIT_15_8_len 8
+#define	fec_RSD_PKT_NUM_PER_UNIT_15_8_lsb 8
+#define xd_p_fec_RS_TH_1_7_0	0xA38E
+#define	fec_RS_TH_1_7_0_pos 0
+#define	fec_RS_TH_1_7_0_len 8
+#define	fec_RS_TH_1_7_0_lsb 0
+#define xd_p_fec_RS_TH_1_15_8	0xA38F
+#define	fec_RS_TH_1_15_8_pos 0
+#define	fec_RS_TH_1_15_8_len 8
+#define	fec_RS_TH_1_15_8_lsb 8
+#define xd_p_fec_RS_TH_2	0xA390
+#define	fec_RS_TH_2_pos 0
+#define	fec_RS_TH_2_len 8
+#define	fec_RS_TH_2_lsb 0
+#define xd_p_fec_mon_en	0xA391
+#define	fec_mon_en_pos 0
+#define	fec_mon_en_len 1
+#define	fec_mon_en_lsb 0
+#define xd_p_reg_b8to47	0xA391
+#define	reg_b8to47_pos 1
+#define	reg_b8to47_len 1
+#define	reg_b8to47_lsb 0
+#define xd_p_reg_rsd_sync_rep	0xA391
+#define	reg_rsd_sync_rep_pos 2
+#define	reg_rsd_sync_rep_len 1
+#define	reg_rsd_sync_rep_lsb 0
+#define xd_p_fec_rsd_retrain_rst	0xA391
+#define	fec_rsd_retrain_rst_pos 3
+#define	fec_rsd_retrain_rst_len 1
+#define	fec_rsd_retrain_rst_lsb 0
+#define xd_r_fec_rsd_ber_rdy	0xA391
+#define	fec_rsd_ber_rdy_pos 4
+#define	fec_rsd_ber_rdy_len 1
+#define	fec_rsd_ber_rdy_lsb 0
+#define xd_p_fec_rsd_ber_rst	0xA391
+#define	fec_rsd_ber_rst_pos 5
+#define	fec_rsd_ber_rst_len 1
+#define	fec_rsd_ber_rst_lsb 0
+#define xd_r_fec_vtb_ber_rdy	0xA391
+#define	fec_vtb_ber_rdy_pos 6
+#define	fec_vtb_ber_rdy_len 1
+#define	fec_vtb_ber_rdy_lsb 0
+#define xd_p_fec_vtb_ber_rst	0xA391
+#define	fec_vtb_ber_rst_pos 7
+#define	fec_vtb_ber_rst_len 1
+#define	fec_vtb_ber_rst_lsb 0
+#define xd_p_reg_vtb_clk40en	0xA392
+#define	reg_vtb_clk40en_pos 0
+#define	reg_vtb_clk40en_len 1
+#define	reg_vtb_clk40en_lsb 0
+#define xd_p_fec_vtb_rsd_mon_en	0xA392
+#define	fec_vtb_rsd_mon_en_pos 1
+#define	fec_vtb_rsd_mon_en_len 1
+#define	fec_vtb_rsd_mon_en_lsb 0
+#define xd_p_reg_fec_data_en	0xA392
+#define	reg_fec_data_en_pos 2
+#define	reg_fec_data_en_len 1
+#define	reg_fec_data_en_lsb 0
+#define xd_p_fec_dummy_reg_2	0xA392
+#define	fec_dummy_reg_2_pos 3
+#define	fec_dummy_reg_2_len 3
+#define	fec_dummy_reg_2_lsb 0
+#define xd_p_reg_sync_chk	0xA392
+#define	reg_sync_chk_pos 6
+#define	reg_sync_chk_len 1
+#define	reg_sync_chk_lsb 0
+#define xd_p_fec_rsd_bypass	0xA392
+#define	fec_rsd_bypass_pos 7
+#define	fec_rsd_bypass_len 1
+#define	fec_rsd_bypass_lsb 0
+#define xd_p_fec_sw_rst	0xA393
+#define	fec_sw_rst_pos 0
+#define	fec_sw_rst_len 1
+#define	fec_sw_rst_lsb 0
+#define xd_r_fec_vtb_pm_crc	0xA394
+#define	fec_vtb_pm_crc_pos 0
+#define	fec_vtb_pm_crc_len 8
+#define	fec_vtb_pm_crc_lsb 0
+#define xd_r_fec_vtb_tb_7_crc	0xA395
+#define	fec_vtb_tb_7_crc_pos 0
+#define	fec_vtb_tb_7_crc_len 8
+#define	fec_vtb_tb_7_crc_lsb 0
+#define xd_r_fec_vtb_tb_6_crc	0xA396
+#define	fec_vtb_tb_6_crc_pos 0
+#define	fec_vtb_tb_6_crc_len 8
+#define	fec_vtb_tb_6_crc_lsb 0
+#define xd_r_fec_vtb_tb_5_crc	0xA397
+#define	fec_vtb_tb_5_crc_pos 0
+#define	fec_vtb_tb_5_crc_len 8
+#define	fec_vtb_tb_5_crc_lsb 0
+#define xd_r_fec_vtb_tb_4_crc	0xA398
+#define	fec_vtb_tb_4_crc_pos 0
+#define	fec_vtb_tb_4_crc_len 8
+#define	fec_vtb_tb_4_crc_lsb 0
+#define xd_r_fec_vtb_tb_3_crc	0xA399
+#define	fec_vtb_tb_3_crc_pos 0
+#define	fec_vtb_tb_3_crc_len 8
+#define	fec_vtb_tb_3_crc_lsb 0
+#define xd_r_fec_vtb_tb_2_crc	0xA39A
+#define	fec_vtb_tb_2_crc_pos 0
+#define	fec_vtb_tb_2_crc_len 8
+#define	fec_vtb_tb_2_crc_lsb 0
+#define xd_r_fec_vtb_tb_1_crc	0xA39B
+#define	fec_vtb_tb_1_crc_pos 0
+#define	fec_vtb_tb_1_crc_len 8
+#define	fec_vtb_tb_1_crc_lsb 0
+#define xd_r_fec_vtb_tb_0_crc	0xA39C
+#define	fec_vtb_tb_0_crc_pos 0
+#define	fec_vtb_tb_0_crc_len 8
+#define	fec_vtb_tb_0_crc_lsb 0
+#define xd_r_fec_rsd_bank0_crc	0xA39D
+#define	fec_rsd_bank0_crc_pos 0
+#define	fec_rsd_bank0_crc_len 8
+#define	fec_rsd_bank0_crc_lsb 0
+#define xd_r_fec_rsd_bank1_crc	0xA39E
+#define	fec_rsd_bank1_crc_pos 0
+#define	fec_rsd_bank1_crc_len 8
+#define	fec_rsd_bank1_crc_lsb 0
+#define xd_r_fec_idi_vtb_crc	0xA39F
+#define	fec_idi_vtb_crc_pos 0
+#define	fec_idi_vtb_crc_len 8
+#define	fec_idi_vtb_crc_lsb 0
+#define xd_g_reg_tpsd_txmod	0xA3C0
+#define	reg_tpsd_txmod_pos 0
+#define	reg_tpsd_txmod_len 2
+#define	reg_tpsd_txmod_lsb 0
+#define xd_g_reg_tpsd_gi	0xA3C0
+#define	reg_tpsd_gi_pos 2
+#define	reg_tpsd_gi_len 2
+#define	reg_tpsd_gi_lsb 0
+#define xd_g_reg_tpsd_hier	0xA3C0
+#define	reg_tpsd_hier_pos 4
+#define	reg_tpsd_hier_len 3
+#define	reg_tpsd_hier_lsb 0
+#define xd_g_reg_bw	0xA3C1
+#define	reg_bw_pos 2
+#define	reg_bw_len 2
+#define	reg_bw_lsb 0
+#define xd_g_reg_dec_pri	0xA3C1
+#define	reg_dec_pri_pos 4
+#define	reg_dec_pri_len 1
+#define	reg_dec_pri_lsb 0
+#define xd_g_reg_tpsd_const	0xA3C1
+#define	reg_tpsd_const_pos 6
+#define	reg_tpsd_const_len 2
+#define	reg_tpsd_const_lsb 0
+#define xd_g_reg_tpsd_hpcr	0xA3C2
+#define	reg_tpsd_hpcr_pos 0
+#define	reg_tpsd_hpcr_len 3
+#define	reg_tpsd_hpcr_lsb 0
+#define xd_g_reg_tpsd_lpcr	0xA3C2
+#define	reg_tpsd_lpcr_pos 3
+#define	reg_tpsd_lpcr_len 3
+#define	reg_tpsd_lpcr_lsb 0
+#define xd_g_reg_ofsm_clk	0xA3D0
+#define	reg_ofsm_clk_pos 0
+#define	reg_ofsm_clk_len 3
+#define	reg_ofsm_clk_lsb 0
+#define xd_g_reg_fclk_cfg	0xA3D1
+#define	reg_fclk_cfg_pos 0
+#define	reg_fclk_cfg_len 1
+#define	reg_fclk_cfg_lsb 0
+#define xd_g_reg_fclk_idi	0xA3D1
+#define	reg_fclk_idi_pos 1
+#define	reg_fclk_idi_len 1
+#define	reg_fclk_idi_lsb 0
+#define xd_g_reg_fclk_odi	0xA3D1
+#define	reg_fclk_odi_pos 2
+#define	reg_fclk_odi_len 1
+#define	reg_fclk_odi_lsb 0
+#define xd_g_reg_fclk_rsd	0xA3D1
+#define	reg_fclk_rsd_pos 3
+#define	reg_fclk_rsd_len 1
+#define	reg_fclk_rsd_lsb 0
+#define xd_g_reg_fclk_vtb	0xA3D1
+#define	reg_fclk_vtb_pos 4
+#define	reg_fclk_vtb_len 1
+#define	reg_fclk_vtb_lsb 0
+#define xd_g_reg_fclk_cste	0xA3D1
+#define	reg_fclk_cste_pos 5
+#define	reg_fclk_cste_len 1
+#define	reg_fclk_cste_lsb 0
+#define xd_g_reg_fclk_mp2if	0xA3D1
+#define	reg_fclk_mp2if_pos 6
+#define	reg_fclk_mp2if_len 1
+#define	reg_fclk_mp2if_lsb 0
+#define xd_I2C_i2c_m_slave_addr	0xA400
+#define	i2c_m_slave_addr_pos 0
+#define	i2c_m_slave_addr_len 8
+#define	i2c_m_slave_addr_lsb 0
+#define xd_I2C_i2c_m_data1	0xA401
+#define	i2c_m_data1_pos 0
+#define	i2c_m_data1_len 8
+#define	i2c_m_data1_lsb 0
+#define xd_I2C_i2c_m_data2	0xA402
+#define	i2c_m_data2_pos 0
+#define	i2c_m_data2_len 8
+#define	i2c_m_data2_lsb 0
+#define xd_I2C_i2c_m_data3	0xA403
+#define	i2c_m_data3_pos 0
+#define	i2c_m_data3_len 8
+#define	i2c_m_data3_lsb 0
+#define xd_I2C_i2c_m_data4	0xA404
+#define	i2c_m_data4_pos 0
+#define	i2c_m_data4_len 8
+#define	i2c_m_data4_lsb 0
+#define xd_I2C_i2c_m_data5	0xA405
+#define	i2c_m_data5_pos 0
+#define	i2c_m_data5_len 8
+#define	i2c_m_data5_lsb 0
+#define xd_I2C_i2c_m_data6	0xA406
+#define	i2c_m_data6_pos 0
+#define	i2c_m_data6_len 8
+#define	i2c_m_data6_lsb 0
+#define xd_I2C_i2c_m_data7	0xA407
+#define	i2c_m_data7_pos 0
+#define	i2c_m_data7_len 8
+#define	i2c_m_data7_lsb 0
+#define xd_I2C_i2c_m_data8	0xA408
+#define	i2c_m_data8_pos 0
+#define	i2c_m_data8_len 8
+#define	i2c_m_data8_lsb 0
+#define xd_I2C_i2c_m_data9	0xA409
+#define	i2c_m_data9_pos 0
+#define	i2c_m_data9_len 8
+#define	i2c_m_data9_lsb 0
+#define xd_I2C_i2c_m_data10	0xA40A
+#define	i2c_m_data10_pos 0
+#define	i2c_m_data10_len 8
+#define	i2c_m_data10_lsb 0
+#define xd_I2C_i2c_m_data11	0xA40B
+#define	i2c_m_data11_pos 0
+#define	i2c_m_data11_len 8
+#define	i2c_m_data11_lsb 0
+#define xd_I2C_i2c_m_cmd_rw	0xA40C
+#define	i2c_m_cmd_rw_pos 0
+#define	i2c_m_cmd_rw_len 1
+#define	i2c_m_cmd_rw_lsb 0
+#define xd_I2C_i2c_m_cmd_rwlen	0xA40C
+#define	i2c_m_cmd_rwlen_pos 3
+#define	i2c_m_cmd_rwlen_len 4
+#define	i2c_m_cmd_rwlen_lsb 0
+#define xd_I2C_i2c_m_status_cmd_exe	0xA40D
+#define	i2c_m_status_cmd_exe_pos 0
+#define	i2c_m_status_cmd_exe_len 1
+#define	i2c_m_status_cmd_exe_lsb 0
+#define xd_I2C_i2c_m_status_wdat_done	0xA40D
+#define	i2c_m_status_wdat_done_pos 1
+#define	i2c_m_status_wdat_done_len 1
+#define	i2c_m_status_wdat_done_lsb 0
+#define xd_I2C_i2c_m_status_wdat_fail	0xA40D
+#define	i2c_m_status_wdat_fail_pos 2
+#define	i2c_m_status_wdat_fail_len 1
+#define	i2c_m_status_wdat_fail_lsb 0
+#define xd_I2C_i2c_m_period	0xA40E
+#define	i2c_m_period_pos 0
+#define	i2c_m_period_len 8
+#define	i2c_m_period_lsb 0
+#define xd_I2C_i2c_m_reg_msb_lsb	0xA40F
+#define	i2c_m_reg_msb_lsb_pos 0
+#define	i2c_m_reg_msb_lsb_len 1
+#define	i2c_m_reg_msb_lsb_lsb 0
+#define xd_I2C_reg_ofdm_rst	0xA40F
+#define	reg_ofdm_rst_pos 1
+#define	reg_ofdm_rst_len 1
+#define	reg_ofdm_rst_lsb 0
+#define xd_I2C_reg_sample_period_on_tuner	0xA40F
+#define	reg_sample_period_on_tuner_pos 2
+#define	reg_sample_period_on_tuner_len 1
+#define	reg_sample_period_on_tuner_lsb 0
+#define xd_I2C_reg_rst_i2c	0xA40F
+#define	reg_rst_i2c_pos 3
+#define	reg_rst_i2c_len 1
+#define	reg_rst_i2c_lsb 0
+#define xd_I2C_reg_ofdm_rst_en	0xA40F
+#define	reg_ofdm_rst_en_pos 4
+#define	reg_ofdm_rst_en_len 1
+#define	reg_ofdm_rst_en_lsb 0
+#define xd_I2C_reg_tuner_sda_sync_on	0xA40F
+#define	reg_tuner_sda_sync_on_pos 5
+#define	reg_tuner_sda_sync_on_len 1
+#define	reg_tuner_sda_sync_on_lsb 0
+#define xd_p_mp2if_data_access_disable_ofsm	0xA500
+#define	mp2if_data_access_disable_ofsm_pos 0
+#define	mp2if_data_access_disable_ofsm_len 1
+#define	mp2if_data_access_disable_ofsm_lsb 0
+#define xd_p_reg_mp2_sw_rst_ofsm	0xA500
+#define	reg_mp2_sw_rst_ofsm_pos 1
+#define	reg_mp2_sw_rst_ofsm_len 1
+#define	reg_mp2_sw_rst_ofsm_lsb 0
+#define xd_p_reg_mp2if_clk_en_ofsm	0xA500
+#define	reg_mp2if_clk_en_ofsm_pos 2
+#define	reg_mp2if_clk_en_ofsm_len 1
+#define	reg_mp2if_clk_en_ofsm_lsb 0
+#define xd_r_mp2if_sync_byte_locked	0xA500
+#define	mp2if_sync_byte_locked_pos 3
+#define	mp2if_sync_byte_locked_len 1
+#define	mp2if_sync_byte_locked_lsb 0
+#define xd_r_mp2if_ts_not_188	0xA500
+#define	mp2if_ts_not_188_pos 4
+#define	mp2if_ts_not_188_len 1
+#define	mp2if_ts_not_188_lsb 0
+#define xd_r_mp2if_psb_empty	0xA500
+#define	mp2if_psb_empty_pos 5
+#define	mp2if_psb_empty_len 1
+#define	mp2if_psb_empty_lsb 0
+#define xd_r_mp2if_psb_overflow	0xA500
+#define	mp2if_psb_overflow_pos 6
+#define	mp2if_psb_overflow_len 1
+#define	mp2if_psb_overflow_lsb 0
+#define xd_p_mp2if_keep_sf_sync_byte_ofsm	0xA500
+#define	mp2if_keep_sf_sync_byte_ofsm_pos 7
+#define	mp2if_keep_sf_sync_byte_ofsm_len 1
+#define	mp2if_keep_sf_sync_byte_ofsm_lsb 0
+#define xd_r_mp2if_psb_mp2if_num_pkt	0xA501
+#define	mp2if_psb_mp2if_num_pkt_pos 0
+#define	mp2if_psb_mp2if_num_pkt_len 6
+#define	mp2if_psb_mp2if_num_pkt_lsb 0
+#define xd_p_reg_mpeg_full_speed_ofsm	0xA501
+#define	reg_mpeg_full_speed_ofsm_pos 6
+#define	reg_mpeg_full_speed_ofsm_len 1
+#define	reg_mpeg_full_speed_ofsm_lsb 0
+#define xd_p_mp2if_mpeg_ser_mode_ofsm	0xA501
+#define	mp2if_mpeg_ser_mode_ofsm_pos 7
+#define	mp2if_mpeg_ser_mode_ofsm_len 1
+#define	mp2if_mpeg_ser_mode_ofsm_lsb 0
+#define xd_p_reg_sw_mon51	0xA600
+#define	reg_sw_mon51_pos 0
+#define	reg_sw_mon51_len 8
+#define	reg_sw_mon51_lsb 0
+#define xd_p_reg_top_pcsel	0xA601
+#define	reg_top_pcsel_pos 0
+#define	reg_top_pcsel_len 1
+#define	reg_top_pcsel_lsb 0
+#define xd_p_reg_top_rs232	0xA601
+#define	reg_top_rs232_pos 1
+#define	reg_top_rs232_len 1
+#define	reg_top_rs232_lsb 0
+#define xd_p_reg_top_pcout	0xA601
+#define	reg_top_pcout_pos 2
+#define	reg_top_pcout_len 1
+#define	reg_top_pcout_lsb 0
+#define xd_p_reg_top_debug	0xA601
+#define	reg_top_debug_pos 3
+#define	reg_top_debug_len 1
+#define	reg_top_debug_lsb 0
+#define xd_p_reg_top_adcdly	0xA601
+#define	reg_top_adcdly_pos 4
+#define	reg_top_adcdly_len 2
+#define	reg_top_adcdly_lsb 0
+#define xd_p_reg_top_pwrdw	0xA601
+#define	reg_top_pwrdw_pos 6
+#define	reg_top_pwrdw_len 1
+#define	reg_top_pwrdw_lsb 0
+#define xd_p_reg_top_pwrdw_inv	0xA601
+#define	reg_top_pwrdw_inv_pos 7
+#define	reg_top_pwrdw_inv_len 1
+#define	reg_top_pwrdw_inv_lsb 0
+#define xd_p_reg_top_int_inv	0xA602
+#define	reg_top_int_inv_pos 0
+#define	reg_top_int_inv_len 1
+#define	reg_top_int_inv_lsb 0
+#define xd_p_reg_top_dio_sel	0xA602
+#define	reg_top_dio_sel_pos 1
+#define	reg_top_dio_sel_len 1
+#define	reg_top_dio_sel_lsb 0
+#define xd_p_reg_top_gpioon0	0xA603
+#define	reg_top_gpioon0_pos 0
+#define	reg_top_gpioon0_len 1
+#define	reg_top_gpioon0_lsb 0
+#define xd_p_reg_top_gpioon1	0xA603
+#define	reg_top_gpioon1_pos 1
+#define	reg_top_gpioon1_len 1
+#define	reg_top_gpioon1_lsb 0
+#define xd_p_reg_top_gpioon2	0xA603
+#define	reg_top_gpioon2_pos 2
+#define	reg_top_gpioon2_len 1
+#define	reg_top_gpioon2_lsb 0
+#define xd_p_reg_top_gpioon3	0xA603
+#define	reg_top_gpioon3_pos 3
+#define	reg_top_gpioon3_len 1
+#define	reg_top_gpioon3_lsb 0
+#define xd_p_reg_top_lockon1	0xA603
+#define	reg_top_lockon1_pos 4
+#define	reg_top_lockon1_len 1
+#define	reg_top_lockon1_lsb 0
+#define xd_p_reg_top_lockon2	0xA603
+#define	reg_top_lockon2_pos 5
+#define	reg_top_lockon2_len 1
+#define	reg_top_lockon2_lsb 0
+#define xd_p_reg_top_gpioo0	0xA604
+#define	reg_top_gpioo0_pos 0
+#define	reg_top_gpioo0_len 1
+#define	reg_top_gpioo0_lsb 0
+#define xd_p_reg_top_gpioo1	0xA604
+#define	reg_top_gpioo1_pos 1
+#define	reg_top_gpioo1_len 1
+#define	reg_top_gpioo1_lsb 0
+#define xd_p_reg_top_gpioo2	0xA604
+#define	reg_top_gpioo2_pos 2
+#define	reg_top_gpioo2_len 1
+#define	reg_top_gpioo2_lsb 0
+#define xd_p_reg_top_gpioo3	0xA604
+#define	reg_top_gpioo3_pos 3
+#define	reg_top_gpioo3_len 1
+#define	reg_top_gpioo3_lsb 0
+#define xd_p_reg_top_lock1	0xA604
+#define	reg_top_lock1_pos 4
+#define	reg_top_lock1_len 1
+#define	reg_top_lock1_lsb 0
+#define xd_p_reg_top_lock2	0xA604
+#define	reg_top_lock2_pos 5
+#define	reg_top_lock2_len 1
+#define	reg_top_lock2_lsb 0
+#define xd_p_reg_top_gpioen0	0xA605
+#define	reg_top_gpioen0_pos 0
+#define	reg_top_gpioen0_len 1
+#define	reg_top_gpioen0_lsb 0
+#define xd_p_reg_top_gpioen1	0xA605
+#define	reg_top_gpioen1_pos 1
+#define	reg_top_gpioen1_len 1
+#define	reg_top_gpioen1_lsb 0
+#define xd_p_reg_top_gpioen2	0xA605
+#define	reg_top_gpioen2_pos 2
+#define	reg_top_gpioen2_len 1
+#define	reg_top_gpioen2_lsb 0
+#define xd_p_reg_top_gpioen3	0xA605
+#define	reg_top_gpioen3_pos 3
+#define	reg_top_gpioen3_len 1
+#define	reg_top_gpioen3_lsb 0
+#define xd_p_reg_top_locken1	0xA605
+#define	reg_top_locken1_pos 4
+#define	reg_top_locken1_len 1
+#define	reg_top_locken1_lsb 0
+#define xd_p_reg_top_locken2	0xA605
+#define	reg_top_locken2_pos 5
+#define	reg_top_locken2_len 1
+#define	reg_top_locken2_lsb 0
+#define xd_r_reg_top_gpioi0	0xA606
+#define	reg_top_gpioi0_pos 0
+#define	reg_top_gpioi0_len 1
+#define	reg_top_gpioi0_lsb 0
+#define xd_r_reg_top_gpioi1	0xA606
+#define	reg_top_gpioi1_pos 1
+#define	reg_top_gpioi1_len 1
+#define	reg_top_gpioi1_lsb 0
+#define xd_r_reg_top_gpioi2	0xA606
+#define	reg_top_gpioi2_pos 2
+#define	reg_top_gpioi2_len 1
+#define	reg_top_gpioi2_lsb 0
+#define xd_r_reg_top_gpioi3	0xA606
+#define	reg_top_gpioi3_pos 3
+#define	reg_top_gpioi3_len 1
+#define	reg_top_gpioi3_lsb 0
+#define xd_r_reg_top_locki1	0xA606
+#define	reg_top_locki1_pos 4
+#define	reg_top_locki1_len 1
+#define	reg_top_locki1_lsb 0
+#define xd_r_reg_top_locki2	0xA606
+#define	reg_top_locki2_pos 5
+#define	reg_top_locki2_len 1
+#define	reg_top_locki2_lsb 0
+#define xd_p_reg_dummy_7_0	0xA608
+#define	reg_dummy_7_0_pos 0
+#define	reg_dummy_7_0_len 8
+#define	reg_dummy_7_0_lsb 0
+#define xd_p_reg_dummy_15_8	0xA609
+#define	reg_dummy_15_8_pos 0
+#define	reg_dummy_15_8_len 8
+#define	reg_dummy_15_8_lsb 8
+#define xd_p_reg_dummy_23_16	0xA60A
+#define	reg_dummy_23_16_pos 0
+#define	reg_dummy_23_16_len 8
+#define	reg_dummy_23_16_lsb 16
+#define xd_p_reg_dummy_31_24	0xA60B
+#define	reg_dummy_31_24_pos 0
+#define	reg_dummy_31_24_len 8
+#define	reg_dummy_31_24_lsb 24
+#define xd_p_reg_dummy_39_32	0xA60C
+#define	reg_dummy_39_32_pos 0
+#define	reg_dummy_39_32_len 8
+#define	reg_dummy_39_32_lsb 32
+#define xd_p_reg_dummy_47_40	0xA60D
+#define	reg_dummy_47_40_pos 0
+#define	reg_dummy_47_40_len 8
+#define	reg_dummy_47_40_lsb 40
+#define xd_p_reg_dummy_55_48	0xA60E
+#define	reg_dummy_55_48_pos 0
+#define	reg_dummy_55_48_len 8
+#define	reg_dummy_55_48_lsb 48
+#define xd_p_reg_dummy_63_56	0xA60F
+#define	reg_dummy_63_56_pos 0
+#define	reg_dummy_63_56_len 8
+#define	reg_dummy_63_56_lsb 56
+#define xd_p_reg_dummy_71_64	0xA610
+#define	reg_dummy_71_64_pos 0
+#define	reg_dummy_71_64_len 8
+#define	reg_dummy_71_64_lsb 64
+#define xd_p_reg_dummy_79_72	0xA611
+#define	reg_dummy_79_72_pos 0
+#define	reg_dummy_79_72_len 8
+#define	reg_dummy_79_72_lsb 72
+#define xd_p_reg_dummy_87_80	0xA612
+#define	reg_dummy_87_80_pos 0
+#define	reg_dummy_87_80_len 8
+#define	reg_dummy_87_80_lsb 80
+#define xd_p_reg_dummy_95_88	0xA613
+#define	reg_dummy_95_88_pos 0
+#define	reg_dummy_95_88_len 8
+#define	reg_dummy_95_88_lsb 88
+#define xd_p_reg_dummy_103_96	0xA614
+#define	reg_dummy_103_96_pos 0
+#define	reg_dummy_103_96_len 8
+#define	reg_dummy_103_96_lsb 96
+
+#define xd_p_reg_unplug_flag	0xA615
+#define	reg_unplug_flag_pos 0
+#define	reg_unplug_flag_len 1
+#define	reg_unplug_flag_lsb 104
+
+#define xd_p_reg_api_dca_stes_request   0xA615
+#define reg_api_dca_stes_request_pos 1
+#define reg_api_dca_stes_request_len 1
+#define reg_api_dca_stes_request_lsb 0
+
+#define xd_p_reg_back_to_dca_flag	0xA615
+#define	reg_back_to_dca_flag_pos 2
+#define	reg_back_to_dca_flag_len 1
+#define	reg_back_to_dca_flag_lsb 106
+
+#define xd_p_reg_api_retrain_request    0xA615
+#define reg_api_retrain_request_pos 3
+#define reg_api_retrain_request_len 1
+#define reg_api_retrain_request_lsb 0
+
+#define xd_p_reg_Dyn_Top_Try_flag	0xA615
+#define	reg_Dyn_Top_Try_flag_pos 3
+#define	reg_Dyn_Top_Try_flag_len 1
+#define	reg_Dyn_Top_Try_flag_lsb 107
+
+#define xd_p_reg_API_retrain_freeze_flag	0xA615
+#define	reg_API_retrain_freeze_flag_pos 4
+#define	reg_API_retrain_freeze_flag_len 1
+#define	reg_API_retrain_freeze_flag_lsb 108
+
+#define xd_p_reg_dummy_111_104	0xA615
+#define	reg_dummy_111_104_pos 0
+#define	reg_dummy_111_104_len 8
+#define	reg_dummy_111_104_lsb 104
+#define xd_p_reg_dummy_119_112	0xA616
+#define	reg_dummy_119_112_pos 0
+#define	reg_dummy_119_112_len 8
+#define	reg_dummy_119_112_lsb 112
+#define xd_p_reg_dummy_127_120	0xA617
+#define	reg_dummy_127_120_pos 0
+#define	reg_dummy_127_120_len 8
+#define	reg_dummy_127_120_lsb 120
+#define xd_p_reg_dummy_135_128	0xA618
+#define	reg_dummy_135_128_pos 0
+#define	reg_dummy_135_128_len 8
+#define	reg_dummy_135_128_lsb 128
+
+#define xd_p_reg_dummy_143_136	0xA619
+#define	reg_dummy_143_136_pos 0
+#define	reg_dummy_143_136_len 8
+#define	reg_dummy_143_136_lsb 136
+
+#define xd_p_reg_CCIR_dis	0xA619
+#define	reg_CCIR_dis_pos 0
+#define	reg_CCIR_dis_len 1
+#define	reg_CCIR_dis_lsb 0
+
+#define xd_p_reg_dummy_151_144	0xA61A
+#define	reg_dummy_151_144_pos 0
+#define	reg_dummy_151_144_len 8
+#define	reg_dummy_151_144_lsb 144
+
+#define xd_p_reg_dummy_159_152	0xA61B
+#define	reg_dummy_159_152_pos 0
+#define	reg_dummy_159_152_len 8
+#define	reg_dummy_159_152_lsb 152
+
+#define xd_p_reg_dummy_167_160	0xA61C
+#define	reg_dummy_167_160_pos 0
+#define	reg_dummy_167_160_len 8
+#define	reg_dummy_167_160_lsb 160
+
+#define xd_p_reg_dummy_175_168	0xA61D
+#define	reg_dummy_175_168_pos 0
+#define	reg_dummy_175_168_len 8
+#define	reg_dummy_175_168_lsb 168
+
+#define xd_p_reg_dummy_183_176	0xA61E
+#define	reg_dummy_183_176_pos 0
+#define	reg_dummy_183_176_len 8
+#define	reg_dummy_183_176_lsb 176
+
+#define xd_p_reg_ofsm_read_rbc_en  0xA61E
+#define reg_ofsm_read_rbc_en_pos 2
+#define reg_ofsm_read_rbc_en_len 1
+#define reg_ofsm_read_rbc_en_lsb 0
+
+#define xd_p_reg_ce_filter_selection_dis  0xA61E
+#define reg_ce_filter_selection_dis_pos 1
+#define reg_ce_filter_selection_dis_len 1
+#define reg_ce_filter_selection_dis_lsb 0
+
+#define xd_p_reg_OFSM_version_control_7_0  0xA611
+#define reg_OFSM_version_control_7_0_pos 0
+#define reg_OFSM_version_control_7_0_len 8
+#define reg_OFSM_version_control_7_0_lsb 0
+
+#define xd_p_reg_OFSM_version_control_15_8  0xA61F
+#define reg_OFSM_version_control_15_8_pos 0
+#define reg_OFSM_version_control_15_8_len 8
+#define reg_OFSM_version_control_15_8_lsb 0
+
+#define xd_p_reg_OFSM_version_control_23_16  0xA620
+#define reg_OFSM_version_control_23_16_pos 0
+#define reg_OFSM_version_control_23_16_len 8
+#define reg_OFSM_version_control_23_16_lsb 0
+
+#define xd_p_reg_dummy_191_184	0xA61F
+#define	reg_dummy_191_184_pos 0
+#define	reg_dummy_191_184_len 8
+#define	reg_dummy_191_184_lsb 184
+
+#define xd_p_reg_dummy_199_192	0xA620
+#define	reg_dummy_199_192_pos 0
+#define	reg_dummy_199_192_len 8
+#define	reg_dummy_199_192_lsb 192
+
+#define xd_p_reg_ce_en	0xABC0
+#define	reg_ce_en_pos 0
+#define	reg_ce_en_len 1
+#define	reg_ce_en_lsb 0
+#define xd_p_reg_ce_fctrl_en	0xABC0
+#define	reg_ce_fctrl_en_pos 1
+#define	reg_ce_fctrl_en_len 1
+#define	reg_ce_fctrl_en_lsb 0
+#define xd_p_reg_ce_fste_tdi	0xABC0
+#define	reg_ce_fste_tdi_pos 2
+#define	reg_ce_fste_tdi_len 1
+#define	reg_ce_fste_tdi_lsb 0
+#define xd_p_reg_ce_dynamic	0xABC0
+#define	reg_ce_dynamic_pos 3
+#define	reg_ce_dynamic_len 1
+#define	reg_ce_dynamic_lsb 0
+#define xd_p_reg_ce_conf	0xABC0
+#define	reg_ce_conf_pos 4
+#define	reg_ce_conf_len 2
+#define	reg_ce_conf_lsb 0
+#define xd_p_reg_ce_dyn12	0xABC0
+#define	reg_ce_dyn12_pos 6
+#define	reg_ce_dyn12_len 1
+#define	reg_ce_dyn12_lsb 0
+#define xd_p_reg_ce_derot_en	0xABC0
+#define	reg_ce_derot_en_pos 7
+#define	reg_ce_derot_en_len 1
+#define	reg_ce_derot_en_lsb 0
+#define xd_p_reg_ce_dynamic_th_7_0	0xABC1
+#define	reg_ce_dynamic_th_7_0_pos 0
+#define	reg_ce_dynamic_th_7_0_len 8
+#define	reg_ce_dynamic_th_7_0_lsb 0
+#define xd_p_reg_ce_dynamic_th_15_8	0xABC2
+#define	reg_ce_dynamic_th_15_8_pos 0
+#define	reg_ce_dynamic_th_15_8_len 8
+#define	reg_ce_dynamic_th_15_8_lsb 8
+#define xd_p_reg_ce_s1	0xABC3
+#define	reg_ce_s1_pos 0
+#define	reg_ce_s1_len 5
+#define	reg_ce_s1_lsb 0
+#define xd_p_reg_ce_var_forced_value	0xABC3
+#define	reg_ce_var_forced_value_pos 5
+#define	reg_ce_var_forced_value_len 3
+#define	reg_ce_var_forced_value_lsb 0
+#define xd_p_reg_ce_data_im_7_0	0xABC4
+#define	reg_ce_data_im_7_0_pos 0
+#define	reg_ce_data_im_7_0_len 8
+#define	reg_ce_data_im_7_0_lsb 0
+#define xd_p_reg_ce_data_im_8	0xABC5
+#define	reg_ce_data_im_8_pos 0
+#define	reg_ce_data_im_8_len 1
+#define	reg_ce_data_im_8_lsb 0
+#define xd_p_reg_ce_data_re_6_0	0xABC5
+#define	reg_ce_data_re_6_0_pos 1
+#define	reg_ce_data_re_6_0_len 7
+#define	reg_ce_data_re_6_0_lsb 0
+#define xd_p_reg_ce_data_re_8_7	0xABC6
+#define	reg_ce_data_re_8_7_pos 0
+#define	reg_ce_data_re_8_7_len 2
+#define	reg_ce_data_re_8_7_lsb 7
+#define xd_p_reg_ce_tone_5_0	0xABC6
+#define	reg_ce_tone_5_0_pos 2
+#define	reg_ce_tone_5_0_len 6
+#define	reg_ce_tone_5_0_lsb 0
+#define xd_p_reg_ce_tone_12_6	0xABC7
+#define	reg_ce_tone_12_6_pos 0
+#define	reg_ce_tone_12_6_len 7
+#define	reg_ce_tone_12_6_lsb 6
+#define xd_p_reg_ce_centroid_drift_th	0xABC8
+#define	reg_ce_centroid_drift_th_pos 0
+#define	reg_ce_centroid_drift_th_len 8
+#define	reg_ce_centroid_drift_th_lsb 0
+#define xd_p_reg_ce_centroid_count_max	0xABC9
+#define	reg_ce_centroid_count_max_pos 0
+#define	reg_ce_centroid_count_max_len 4
+#define	reg_ce_centroid_count_max_lsb 0
+#define xd_p_reg_ce_centroid_bias_inc_7_0	0xABCA
+#define	reg_ce_centroid_bias_inc_7_0_pos 0
+#define	reg_ce_centroid_bias_inc_7_0_len 8
+#define	reg_ce_centroid_bias_inc_7_0_lsb 0
+#define xd_p_reg_ce_centroid_bias_inc_8	0xABCB
+#define	reg_ce_centroid_bias_inc_8_pos 0
+#define	reg_ce_centroid_bias_inc_8_len 1
+#define	reg_ce_centroid_bias_inc_8_lsb 0
+#define xd_p_reg_ce_var_th0_7_0	0xABCC
+#define	reg_ce_var_th0_7_0_pos 0
+#define	reg_ce_var_th0_7_0_len 8
+#define	reg_ce_var_th0_7_0_lsb 0
+#define xd_p_reg_ce_var_th0_15_8	0xABCD
+#define	reg_ce_var_th0_15_8_pos 0
+#define	reg_ce_var_th0_15_8_len 8
+#define	reg_ce_var_th0_15_8_lsb 8
+#define xd_p_reg_ce_var_th1_7_0	0xABCE
+#define	reg_ce_var_th1_7_0_pos 0
+#define	reg_ce_var_th1_7_0_len 8
+#define	reg_ce_var_th1_7_0_lsb 0
+#define xd_p_reg_ce_var_th1_15_8	0xABCF
+#define	reg_ce_var_th1_15_8_pos 0
+#define	reg_ce_var_th1_15_8_len 8
+#define	reg_ce_var_th1_15_8_lsb 8
+#define xd_p_reg_ce_var_th2_7_0	0xABD0
+#define	reg_ce_var_th2_7_0_pos 0
+#define	reg_ce_var_th2_7_0_len 8
+#define	reg_ce_var_th2_7_0_lsb 0
+#define xd_p_reg_ce_var_th2_15_8	0xABD1
+#define	reg_ce_var_th2_15_8_pos 0
+#define	reg_ce_var_th2_15_8_len 8
+#define	reg_ce_var_th2_15_8_lsb 8
+#define xd_p_reg_ce_var_th3_7_0	0xABD2
+#define	reg_ce_var_th3_7_0_pos 0
+#define	reg_ce_var_th3_7_0_len 8
+#define	reg_ce_var_th3_7_0_lsb 0
+#define xd_p_reg_ce_var_th3_15_8	0xABD3
+#define	reg_ce_var_th3_15_8_pos 0
+#define	reg_ce_var_th3_15_8_len 8
+#define	reg_ce_var_th3_15_8_lsb 8
+#define xd_p_reg_ce_var_th4_7_0	0xABD4
+#define	reg_ce_var_th4_7_0_pos 0
+#define	reg_ce_var_th4_7_0_len 8
+#define	reg_ce_var_th4_7_0_lsb 0
+#define xd_p_reg_ce_var_th4_15_8	0xABD5
+#define	reg_ce_var_th4_15_8_pos 0
+#define	reg_ce_var_th4_15_8_len 8
+#define	reg_ce_var_th4_15_8_lsb 8
+#define xd_p_reg_ce_var_th5_7_0	0xABD6
+#define	reg_ce_var_th5_7_0_pos 0
+#define	reg_ce_var_th5_7_0_len 8
+#define	reg_ce_var_th5_7_0_lsb 0
+#define xd_p_reg_ce_var_th5_15_8	0xABD7
+#define	reg_ce_var_th5_15_8_pos 0
+#define	reg_ce_var_th5_15_8_len 8
+#define	reg_ce_var_th5_15_8_lsb 8
+#define xd_p_reg_ce_var_th6_7_0	0xABD8
+#define	reg_ce_var_th6_7_0_pos 0
+#define	reg_ce_var_th6_7_0_len 8
+#define	reg_ce_var_th6_7_0_lsb 0
+#define xd_p_reg_ce_var_th6_15_8	0xABD9
+#define	reg_ce_var_th6_15_8_pos 0
+#define	reg_ce_var_th6_15_8_len 8
+#define	reg_ce_var_th6_15_8_lsb 8
+#define xd_p_reg_ce_fctrl_reset	0xABDA
+#define	reg_ce_fctrl_reset_pos 0
+#define	reg_ce_fctrl_reset_len 1
+#define	reg_ce_fctrl_reset_lsb 0
+#define xd_p_reg_ce_cent_auto_clr_en	0xABDA
+#define	reg_ce_cent_auto_clr_en_pos 1
+#define	reg_ce_cent_auto_clr_en_len 1
+#define	reg_ce_cent_auto_clr_en_lsb 0
+#define xd_p_reg_ce_fctrl_auto_reset_en	0xABDA
+#define	reg_ce_fctrl_auto_reset_en_pos 2
+#define	reg_ce_fctrl_auto_reset_en_len 1
+#define	reg_ce_fctrl_auto_reset_en_lsb 0
+#define xd_p_reg_ce_var_forced_en	0xABDA
+#define	reg_ce_var_forced_en_pos 3
+#define	reg_ce_var_forced_en_len 1
+#define	reg_ce_var_forced_en_lsb 0
+#define xd_p_reg_ce_cent_forced_en	0xABDA
+#define	reg_ce_cent_forced_en_pos 4
+#define	reg_ce_cent_forced_en_len 1
+#define	reg_ce_cent_forced_en_lsb 0
+#define xd_p_reg_ce_var_max	0xABDA
+#define	reg_ce_var_max_pos 5
+#define	reg_ce_var_max_len 3
+#define	reg_ce_var_max_lsb 0
+#define xd_p_reg_ce_cent_forced_value_7_0	0xABDB
+#define	reg_ce_cent_forced_value_7_0_pos 0
+#define	reg_ce_cent_forced_value_7_0_len 8
+#define	reg_ce_cent_forced_value_7_0_lsb 0
+#define xd_p_reg_ce_cent_forced_value_11_8	0xABDC
+#define	reg_ce_cent_forced_value_11_8_pos 0
+#define	reg_ce_cent_forced_value_11_8_len 4
+#define	reg_ce_cent_forced_value_11_8_lsb 8
+#define xd_p_reg_ce_fctrl_rd	0xABDD
+#define	reg_ce_fctrl_rd_pos 0
+#define	reg_ce_fctrl_rd_len 1
+#define	reg_ce_fctrl_rd_lsb 0
+#define xd_p_reg_ce_centroid_max_6_0	0xABDD
+#define	reg_ce_centroid_max_6_0_pos 1
+#define	reg_ce_centroid_max_6_0_len 7
+#define	reg_ce_centroid_max_6_0_lsb 0
+#define xd_p_reg_ce_centroid_max_11_7	0xABDE
+#define	reg_ce_centroid_max_11_7_pos 0
+#define	reg_ce_centroid_max_11_7_len 5
+#define	reg_ce_centroid_max_11_7_lsb 7
+#define xd_p_reg_ce_var	0xABDF
+#define	reg_ce_var_pos 0
+#define	reg_ce_var_len 3
+#define	reg_ce_var_lsb 0
+#define xd_p_reg_ce_fctrl_rdy	0xABDF
+#define	reg_ce_fctrl_rdy_pos 3
+#define	reg_ce_fctrl_rdy_len 1
+#define	reg_ce_fctrl_rdy_lsb 0
+#define xd_p_reg_ce_centroid_out_3_0	0xABDF
+#define	reg_ce_centroid_out_3_0_pos 4
+#define	reg_ce_centroid_out_3_0_len 4
+#define	reg_ce_centroid_out_3_0_lsb 0
+#define xd_p_reg_ce_centroid_out_11_4	0xABE0
+#define	reg_ce_centroid_out_11_4_pos 0
+#define	reg_ce_centroid_out_11_4_len 8
+#define	reg_ce_centroid_out_11_4_lsb 4
+#define xd_p_reg_ce_bias_7_0	0xABE1
+#define	reg_ce_bias_7_0_pos 0
+#define	reg_ce_bias_7_0_len 8
+#define	reg_ce_bias_7_0_lsb 0
+#define xd_p_reg_ce_bias_11_8	0xABE2
+#define	reg_ce_bias_11_8_pos 0
+#define	reg_ce_bias_11_8_len 4
+#define	reg_ce_bias_11_8_lsb 8
+#define xd_p_reg_ce_m1_3_0	0xABE2
+#define	reg_ce_m1_3_0_pos 4
+#define	reg_ce_m1_3_0_len 4
+#define	reg_ce_m1_3_0_lsb 0
+#define xd_p_reg_ce_m1_11_4	0xABE3
+#define	reg_ce_m1_11_4_pos 0
+#define	reg_ce_m1_11_4_len 8
+#define	reg_ce_m1_11_4_lsb 4
+#define xd_p_reg_ce_rh0_7_0	0xABE4
+#define	reg_ce_rh0_7_0_pos 0
+#define	reg_ce_rh0_7_0_len 8
+#define	reg_ce_rh0_7_0_lsb 0
+#define xd_p_reg_ce_rh0_15_8	0xABE5
+#define	reg_ce_rh0_15_8_pos 0
+#define	reg_ce_rh0_15_8_len 8
+#define	reg_ce_rh0_15_8_lsb 8
+#define xd_p_reg_ce_rh0_23_16	0xABE6
+#define	reg_ce_rh0_23_16_pos 0
+#define	reg_ce_rh0_23_16_len 8
+#define	reg_ce_rh0_23_16_lsb 16
+#define xd_p_reg_ce_rh0_31_24	0xABE7
+#define	reg_ce_rh0_31_24_pos 0
+#define	reg_ce_rh0_31_24_len 8
+#define	reg_ce_rh0_31_24_lsb 24
+#define xd_p_reg_ce_rh3_real_7_0	0xABE8
+#define	reg_ce_rh3_real_7_0_pos 0
+#define	reg_ce_rh3_real_7_0_len 8
+#define	reg_ce_rh3_real_7_0_lsb 0
+#define xd_p_reg_ce_rh3_real_15_8	0xABE9
+#define	reg_ce_rh3_real_15_8_pos 0
+#define	reg_ce_rh3_real_15_8_len 8
+#define	reg_ce_rh3_real_15_8_lsb 8
+#define xd_p_reg_ce_rh3_real_23_16	0xABEA
+#define	reg_ce_rh3_real_23_16_pos 0
+#define	reg_ce_rh3_real_23_16_len 8
+#define	reg_ce_rh3_real_23_16_lsb 16
+#define xd_p_reg_ce_rh3_real_31_24	0xABEB
+#define	reg_ce_rh3_real_31_24_pos 0
+#define	reg_ce_rh3_real_31_24_len 8
+#define	reg_ce_rh3_real_31_24_lsb 24
+#define xd_p_reg_ce_rh3_imag_7_0	0xABEC
+#define	reg_ce_rh3_imag_7_0_pos 0
+#define	reg_ce_rh3_imag_7_0_len 8
+#define	reg_ce_rh3_imag_7_0_lsb 0
+#define xd_p_reg_ce_rh3_imag_15_8	0xABED
+#define	reg_ce_rh3_imag_15_8_pos 0
+#define	reg_ce_rh3_imag_15_8_len 8
+#define	reg_ce_rh3_imag_15_8_lsb 8
+#define xd_p_reg_ce_rh3_imag_23_16	0xABEE
+#define	reg_ce_rh3_imag_23_16_pos 0
+#define	reg_ce_rh3_imag_23_16_len 8
+#define	reg_ce_rh3_imag_23_16_lsb 16
+#define xd_p_reg_ce_rh3_imag_31_24	0xABEF
+#define	reg_ce_rh3_imag_31_24_pos 0
+#define	reg_ce_rh3_imag_31_24_len 8
+#define	reg_ce_rh3_imag_31_24_lsb 24
+#define xd_p_reg_feq_fix_eh2_7_0	0xABF0
+#define	reg_feq_fix_eh2_7_0_pos 0
+#define	reg_feq_fix_eh2_7_0_len 8
+#define	reg_feq_fix_eh2_7_0_lsb 0
+#define xd_p_reg_feq_fix_eh2_15_8	0xABF1
+#define	reg_feq_fix_eh2_15_8_pos 0
+#define	reg_feq_fix_eh2_15_8_len 8
+#define	reg_feq_fix_eh2_15_8_lsb 8
+#define xd_p_reg_feq_fix_eh2_23_16	0xABF2
+#define	reg_feq_fix_eh2_23_16_pos 0
+#define	reg_feq_fix_eh2_23_16_len 8
+#define	reg_feq_fix_eh2_23_16_lsb 16
+#define xd_p_reg_feq_fix_eh2_31_24	0xABF3
+#define	reg_feq_fix_eh2_31_24_pos 0
+#define	reg_feq_fix_eh2_31_24_len 8
+#define	reg_feq_fix_eh2_31_24_lsb 24
+#define xd_p_reg_ce_m2_central_7_0	0xABF4
+#define	reg_ce_m2_central_7_0_pos 0
+#define	reg_ce_m2_central_7_0_len 8
+#define	reg_ce_m2_central_7_0_lsb 0
+#define xd_p_reg_ce_m2_central_15_8	0xABF5
+#define	reg_ce_m2_central_15_8_pos 0
+#define	reg_ce_m2_central_15_8_len 8
+#define	reg_ce_m2_central_15_8_lsb 8
+#define xd_p_reg_ce_fftshift	0xABF6
+#define	reg_ce_fftshift_pos 0
+#define	reg_ce_fftshift_len 4
+#define	reg_ce_fftshift_lsb 0
+#define xd_p_reg_ce_fftshift1	0xABF6
+#define	reg_ce_fftshift1_pos 4
+#define	reg_ce_fftshift1_len 4
+#define	reg_ce_fftshift1_lsb 0
+#define xd_p_reg_ce_fftshift2	0xABF7
+#define	reg_ce_fftshift2_pos 0
+#define	reg_ce_fftshift2_len 4
+#define	reg_ce_fftshift2_lsb 0
+#define xd_p_reg_ce_top_mobile	0xABF7
+#define	reg_ce_top_mobile_pos 4
+#define	reg_ce_top_mobile_len 1
+#define	reg_ce_top_mobile_lsb 0
+#define xd_p_reg_strong_sginal_detected 0xA2BC
+#define reg_strong_sginal_detected_pos 2
+#define reg_strong_sginal_detected_len 1
+#define reg_strong_sginal_detected_lsb 0
+
+#define XD_MP2IF_BASE                           0xB000
+#define XD_MP2IF_CSR                        (0x00 + XD_MP2IF_BASE)
+#define XD_MP2IF_DMX_CTRL                       (0x03 + XD_MP2IF_BASE)
+#define XD_MP2IF_PID_IDX                        (0x04 + XD_MP2IF_BASE)
+#define XD_MP2IF_PID_DATA_L                     (0x05 + XD_MP2IF_BASE)
+#define XD_MP2IF_PID_DATA_H                     (0x06 + XD_MP2IF_BASE)
+#define XD_MP2IF_MISC                       (0x07 + XD_MP2IF_BASE)
+
+extern struct dvb_frontend *af9005_fe_attach(struct dvb_usb_device *d);
+extern int af9005_read_ofdm_register(struct dvb_usb_device *d, u16 reg,
+				     u8 * value);
+extern int af9005_read_ofdm_registers(struct dvb_usb_device *d, u16 reg,
+				      u8 * values, int len);
+extern int af9005_write_ofdm_register(struct dvb_usb_device *d, u16 reg,
+				      u8 value);
+extern int af9005_write_ofdm_registers(struct dvb_usb_device *d, u16 reg,
+				       u8 * values, int len);
+extern int af9005_read_tuner_registers(struct dvb_usb_device *d, u16 reg,
+				       u8 addr, u8 * values, int len);
+extern int af9005_write_tuner_registers(struct dvb_usb_device *d, u16 reg,
+					u8 * values, int len);
+extern int af9005_read_register_bits(struct dvb_usb_device *d, u16 reg,
+				     u8 pos, u8 len, u8 * value);
+extern int af9005_write_register_bits(struct dvb_usb_device *d, u16 reg,
+				      u8 pos, u8 len, u8 value);
+extern int af9005_send_command(struct dvb_usb_device *d, u8 command,
+			       u8 * wbuf, int wlen, u8 * rbuf, int rlen);
+extern int af9005_read_eeprom(struct dvb_usb_device *d, u8 address,
+			      u8 * values, int len);
+extern int af9005_tuner_attach(struct dvb_usb_adapter *adap);
+extern int af9005_led_control(struct dvb_usb_device *d, int onoff);
+
+extern u8 regmask[8];
+
+/* remote control decoder */
+extern int af9005_rc_decode(struct dvb_usb_device *d, u8 * data, int len,
+			    u32 * event, int *state);
+extern struct rc_map_table rc_map_af9005_table[];
+extern int rc_map_af9005_table_size;
+
+#endif
diff --git a/drivers/media/usb/dvb-usb/az6027.c b/drivers/media/usb/dvb-usb/az6027.c
new file mode 100644
index 0000000..6321b8e
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/az6027.c
@@ -0,0 +1,1194 @@
+/* DVB USB compliant Linux driver for the AZUREWAVE DVB-S/S2 USB2.0 (AZ6027)
+ * receiver.
+ *
+ * Copyright (C) 2009 Adams.Xu <adams.xu@azwave.com.cn>
+ *
+ *	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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#include "az6027.h"
+
+#include "stb0899_drv.h"
+#include "stb0899_reg.h"
+#include "stb0899_cfg.h"
+
+#include "stb6100.h"
+#include "stb6100_cfg.h"
+#include <media/dvb_ca_en50221.h>
+
+int dvb_usb_az6027_debug;
+module_param_named(debug, dvb_usb_az6027_debug, int, 0644);
+MODULE_PARM_DESC(debug, "set debugging level (1=info,xfer=2,rc=4 (or-able))." DVB_USB_DEBUG_STATUS);
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+struct az6027_device_state {
+	struct dvb_ca_en50221 ca;
+	struct mutex ca_mutex;
+	u8 power_state;
+};
+
+static const struct stb0899_s1_reg az6027_stb0899_s1_init_1[] = {
+
+	/* 0x0000000b, SYSREG */
+	{ STB0899_DEV_ID		, 0x30 },
+	{ STB0899_DISCNTRL1		, 0x32 },
+	{ STB0899_DISCNTRL2		, 0x80 },
+	{ STB0899_DISRX_ST0		, 0x04 },
+	{ STB0899_DISRX_ST1		, 0x00 },
+	{ STB0899_DISPARITY		, 0x00 },
+	{ STB0899_DISSTATUS		, 0x20 },
+	{ STB0899_DISF22		, 0x99 },
+	{ STB0899_DISF22RX		, 0xa8 },
+	/* SYSREG ? */
+	{ STB0899_ACRPRESC		, 0x11 },
+	{ STB0899_ACRDIV1		, 0x0a },
+	{ STB0899_ACRDIV2		, 0x05 },
+	{ STB0899_DACR1			, 0x00 },
+	{ STB0899_DACR2			, 0x00 },
+	{ STB0899_OUTCFG		, 0x00 },
+	{ STB0899_MODECFG		, 0x00 },
+	{ STB0899_IRQSTATUS_3		, 0xfe },
+	{ STB0899_IRQSTATUS_2		, 0x03 },
+	{ STB0899_IRQSTATUS_1		, 0x7c },
+	{ STB0899_IRQSTATUS_0		, 0xf4 },
+	{ STB0899_IRQMSK_3		, 0xf3 },
+	{ STB0899_IRQMSK_2		, 0xfc },
+	{ STB0899_IRQMSK_1		, 0xff },
+	{ STB0899_IRQMSK_0		, 0xff },
+	{ STB0899_IRQCFG		, 0x00 },
+	{ STB0899_I2CCFG		, 0x88 },
+	{ STB0899_I2CRPT		, 0x58 },
+	{ STB0899_IOPVALUE5		, 0x00 },
+	{ STB0899_IOPVALUE4		, 0x33 },
+	{ STB0899_IOPVALUE3		, 0x6d },
+	{ STB0899_IOPVALUE2		, 0x90 },
+	{ STB0899_IOPVALUE1		, 0x60 },
+	{ STB0899_IOPVALUE0		, 0x00 },
+	{ STB0899_GPIO00CFG		, 0x82 },
+	{ STB0899_GPIO01CFG		, 0x82 },
+	{ STB0899_GPIO02CFG		, 0x82 },
+	{ STB0899_GPIO03CFG		, 0x82 },
+	{ STB0899_GPIO04CFG		, 0x82 },
+	{ STB0899_GPIO05CFG		, 0x82 },
+	{ STB0899_GPIO06CFG		, 0x82 },
+	{ STB0899_GPIO07CFG		, 0x82 },
+	{ STB0899_GPIO08CFG		, 0x82 },
+	{ STB0899_GPIO09CFG		, 0x82 },
+	{ STB0899_GPIO10CFG		, 0x82 },
+	{ STB0899_GPIO11CFG		, 0x82 },
+	{ STB0899_GPIO12CFG		, 0x82 },
+	{ STB0899_GPIO13CFG		, 0x82 },
+	{ STB0899_GPIO14CFG		, 0x82 },
+	{ STB0899_GPIO15CFG		, 0x82 },
+	{ STB0899_GPIO16CFG		, 0x82 },
+	{ STB0899_GPIO17CFG		, 0x82 },
+	{ STB0899_GPIO18CFG		, 0x82 },
+	{ STB0899_GPIO19CFG		, 0x82 },
+	{ STB0899_GPIO20CFG		, 0x82 },
+	{ STB0899_SDATCFG		, 0xb8 },
+	{ STB0899_SCLTCFG		, 0xba },
+	{ STB0899_AGCRFCFG		, 0x1c }, /* 0x11 */
+	{ STB0899_GPIO22		, 0x82 }, /* AGCBB2CFG */
+	{ STB0899_GPIO21		, 0x91 }, /* AGCBB1CFG */
+	{ STB0899_DIRCLKCFG		, 0x82 },
+	{ STB0899_CLKOUT27CFG		, 0x7e },
+	{ STB0899_STDBYCFG		, 0x82 },
+	{ STB0899_CS0CFG		, 0x82 },
+	{ STB0899_CS1CFG		, 0x82 },
+	{ STB0899_DISEQCOCFG		, 0x20 },
+	{ STB0899_GPIO32CFG		, 0x82 },
+	{ STB0899_GPIO33CFG		, 0x82 },
+	{ STB0899_GPIO34CFG		, 0x82 },
+	{ STB0899_GPIO35CFG		, 0x82 },
+	{ STB0899_GPIO36CFG		, 0x82 },
+	{ STB0899_GPIO37CFG		, 0x82 },
+	{ STB0899_GPIO38CFG		, 0x82 },
+	{ STB0899_GPIO39CFG		, 0x82 },
+	{ STB0899_NCOARSE		, 0x17 }, /* 0x15 = 27 Mhz Clock, F/3 = 198MHz, F/6 = 99MHz */
+	{ STB0899_SYNTCTRL		, 0x02 }, /* 0x00 = CLK from CLKI, 0x02 = CLK from XTALI */
+	{ STB0899_FILTCTRL		, 0x00 },
+	{ STB0899_SYSCTRL		, 0x01 },
+	{ STB0899_STOPCLK1		, 0x20 },
+	{ STB0899_STOPCLK2		, 0x00 },
+	{ STB0899_INTBUFSTATUS		, 0x00 },
+	{ STB0899_INTBUFCTRL		, 0x0a },
+	{ 0xffff			, 0xff },
+};
+
+static const struct stb0899_s1_reg az6027_stb0899_s1_init_3[] = {
+	{ STB0899_DEMOD			, 0x00 },
+	{ STB0899_RCOMPC		, 0xc9 },
+	{ STB0899_AGC1CN		, 0x01 },
+	{ STB0899_AGC1REF		, 0x10 },
+	{ STB0899_RTC			, 0x23 },
+	{ STB0899_TMGCFG		, 0x4e },
+	{ STB0899_AGC2REF		, 0x34 },
+	{ STB0899_TLSR			, 0x84 },
+	{ STB0899_CFD			, 0xf7 },
+	{ STB0899_ACLC			, 0x87 },
+	{ STB0899_BCLC			, 0x94 },
+	{ STB0899_EQON			, 0x41 },
+	{ STB0899_LDT			, 0xf1 },
+	{ STB0899_LDT2			, 0xe3 },
+	{ STB0899_EQUALREF		, 0xb4 },
+	{ STB0899_TMGRAMP		, 0x10 },
+	{ STB0899_TMGTHD		, 0x30 },
+	{ STB0899_IDCCOMP		, 0xfd },
+	{ STB0899_QDCCOMP		, 0xff },
+	{ STB0899_POWERI		, 0x0c },
+	{ STB0899_POWERQ		, 0x0f },
+	{ STB0899_RCOMP			, 0x6c },
+	{ STB0899_AGCIQIN		, 0x80 },
+	{ STB0899_AGC2I1		, 0x06 },
+	{ STB0899_AGC2I2		, 0x00 },
+	{ STB0899_TLIR			, 0x30 },
+	{ STB0899_RTF			, 0x7f },
+	{ STB0899_DSTATUS		, 0x00 },
+	{ STB0899_LDI			, 0xbc },
+	{ STB0899_CFRM			, 0xea },
+	{ STB0899_CFRL			, 0x31 },
+	{ STB0899_NIRM			, 0x2b },
+	{ STB0899_NIRL			, 0x80 },
+	{ STB0899_ISYMB			, 0x1d },
+	{ STB0899_QSYMB			, 0xa6 },
+	{ STB0899_SFRH			, 0x2f },
+	{ STB0899_SFRM			, 0x68 },
+	{ STB0899_SFRL			, 0x40 },
+	{ STB0899_SFRUPH		, 0x2f },
+	{ STB0899_SFRUPM		, 0x68 },
+	{ STB0899_SFRUPL		, 0x40 },
+	{ STB0899_EQUAI1		, 0x02 },
+	{ STB0899_EQUAQ1		, 0xff },
+	{ STB0899_EQUAI2		, 0x04 },
+	{ STB0899_EQUAQ2		, 0x05 },
+	{ STB0899_EQUAI3		, 0x02 },
+	{ STB0899_EQUAQ3		, 0xfd },
+	{ STB0899_EQUAI4		, 0x03 },
+	{ STB0899_EQUAQ4		, 0x07 },
+	{ STB0899_EQUAI5		, 0x08 },
+	{ STB0899_EQUAQ5		, 0xf5 },
+	{ STB0899_DSTATUS2		, 0x00 },
+	{ STB0899_VSTATUS		, 0x00 },
+	{ STB0899_VERROR		, 0x86 },
+	{ STB0899_IQSWAP		, 0x2a },
+	{ STB0899_ECNT1M		, 0x00 },
+	{ STB0899_ECNT1L		, 0x00 },
+	{ STB0899_ECNT2M		, 0x00 },
+	{ STB0899_ECNT2L		, 0x00 },
+	{ STB0899_ECNT3M		, 0x0a },
+	{ STB0899_ECNT3L		, 0xad },
+	{ STB0899_FECAUTO1		, 0x06 },
+	{ STB0899_FECM			, 0x01 },
+	{ STB0899_VTH12			, 0xb0 },
+	{ STB0899_VTH23			, 0x7a },
+	{ STB0899_VTH34			, 0x58 },
+	{ STB0899_VTH56			, 0x38 },
+	{ STB0899_VTH67			, 0x34 },
+	{ STB0899_VTH78			, 0x24 },
+	{ STB0899_PRVIT			, 0xff },
+	{ STB0899_VITSYNC		, 0x19 },
+	{ STB0899_RSULC			, 0xb1 }, /* DVB = 0xb1, DSS = 0xa1 */
+	{ STB0899_TSULC			, 0x42 },
+	{ STB0899_RSLLC			, 0x41 },
+	{ STB0899_TSLPL			, 0x12 },
+	{ STB0899_TSCFGH		, 0x0c },
+	{ STB0899_TSCFGM		, 0x00 },
+	{ STB0899_TSCFGL		, 0x00 },
+	{ STB0899_TSOUT			, 0x69 }, /* 0x0d for CAM */
+	{ STB0899_RSSYNCDEL		, 0x00 },
+	{ STB0899_TSINHDELH		, 0x02 },
+	{ STB0899_TSINHDELM		, 0x00 },
+	{ STB0899_TSINHDELL		, 0x00 },
+	{ STB0899_TSLLSTKM		, 0x1b },
+	{ STB0899_TSLLSTKL		, 0xb3 },
+	{ STB0899_TSULSTKM		, 0x00 },
+	{ STB0899_TSULSTKL		, 0x00 },
+	{ STB0899_PCKLENUL		, 0xbc },
+	{ STB0899_PCKLENLL		, 0xcc },
+	{ STB0899_RSPCKLEN		, 0xbd },
+	{ STB0899_TSSTATUS		, 0x90 },
+	{ STB0899_ERRCTRL1		, 0xb6 },
+	{ STB0899_ERRCTRL2		, 0x95 },
+	{ STB0899_ERRCTRL3		, 0x8d },
+	{ STB0899_DMONMSK1		, 0x27 },
+	{ STB0899_DMONMSK0		, 0x03 },
+	{ STB0899_DEMAPVIT		, 0x5c },
+	{ STB0899_PLPARM		, 0x19 },
+	{ STB0899_PDELCTRL		, 0x48 },
+	{ STB0899_PDELCTRL2		, 0x00 },
+	{ STB0899_BBHCTRL1		, 0x00 },
+	{ STB0899_BBHCTRL2		, 0x00 },
+	{ STB0899_HYSTTHRESH		, 0x77 },
+	{ STB0899_MATCSTM		, 0x00 },
+	{ STB0899_MATCSTL		, 0x00 },
+	{ STB0899_UPLCSTM		, 0x00 },
+	{ STB0899_UPLCSTL		, 0x00 },
+	{ STB0899_DFLCSTM		, 0x00 },
+	{ STB0899_DFLCSTL		, 0x00 },
+	{ STB0899_SYNCCST		, 0x00 },
+	{ STB0899_SYNCDCSTM		, 0x00 },
+	{ STB0899_SYNCDCSTL		, 0x00 },
+	{ STB0899_ISI_ENTRY		, 0x00 },
+	{ STB0899_ISI_BIT_EN		, 0x00 },
+	{ STB0899_MATSTRM		, 0xf0 },
+	{ STB0899_MATSTRL		, 0x02 },
+	{ STB0899_UPLSTRM		, 0x45 },
+	{ STB0899_UPLSTRL		, 0x60 },
+	{ STB0899_DFLSTRM		, 0xe3 },
+	{ STB0899_DFLSTRL		, 0x00 },
+	{ STB0899_SYNCSTR		, 0x47 },
+	{ STB0899_SYNCDSTRM		, 0x05 },
+	{ STB0899_SYNCDSTRL		, 0x18 },
+	{ STB0899_CFGPDELSTATUS1	, 0x19 },
+	{ STB0899_CFGPDELSTATUS2	, 0x2b },
+	{ STB0899_BBFERRORM		, 0x00 },
+	{ STB0899_BBFERRORL		, 0x01 },
+	{ STB0899_UPKTERRORM		, 0x00 },
+	{ STB0899_UPKTERRORL		, 0x00 },
+	{ 0xffff			, 0xff },
+};
+
+
+
+static struct stb0899_config az6027_stb0899_config = {
+	.init_dev		= az6027_stb0899_s1_init_1,
+	.init_s2_demod		= stb0899_s2_init_2,
+	.init_s1_demod		= az6027_stb0899_s1_init_3,
+	.init_s2_fec		= stb0899_s2_init_4,
+	.init_tst		= stb0899_s1_init_5,
+
+	.demod_address		= 0xd0, /* 0x68, 0xd0 >> 1 */
+
+	.xtal_freq		= 27000000,
+	.inversion		= IQ_SWAP_ON,
+
+	.lo_clk			= 76500000,
+	.hi_clk			= 99000000,
+
+	.esno_ave		= STB0899_DVBS2_ESNO_AVE,
+	.esno_quant		= STB0899_DVBS2_ESNO_QUANT,
+	.avframes_coarse	= STB0899_DVBS2_AVFRAMES_COARSE,
+	.avframes_fine		= STB0899_DVBS2_AVFRAMES_FINE,
+	.miss_threshold		= STB0899_DVBS2_MISS_THRESHOLD,
+	.uwp_threshold_acq	= STB0899_DVBS2_UWP_THRESHOLD_ACQ,
+	.uwp_threshold_track	= STB0899_DVBS2_UWP_THRESHOLD_TRACK,
+	.uwp_threshold_sof	= STB0899_DVBS2_UWP_THRESHOLD_SOF,
+	.sof_search_timeout	= STB0899_DVBS2_SOF_SEARCH_TIMEOUT,
+
+	.btr_nco_bits		= STB0899_DVBS2_BTR_NCO_BITS,
+	.btr_gain_shift_offset	= STB0899_DVBS2_BTR_GAIN_SHIFT_OFFSET,
+	.crl_nco_bits		= STB0899_DVBS2_CRL_NCO_BITS,
+	.ldpc_max_iter		= STB0899_DVBS2_LDPC_MAX_ITER,
+
+	.tuner_get_frequency	= stb6100_get_frequency,
+	.tuner_set_frequency	= stb6100_set_frequency,
+	.tuner_set_bandwidth	= stb6100_set_bandwidth,
+	.tuner_get_bandwidth	= stb6100_get_bandwidth,
+	.tuner_set_rfsiggain	= NULL,
+};
+
+static struct stb6100_config az6027_stb6100_config = {
+	.tuner_address	= 0xc0,
+	.refclock	= 27000000,
+};
+
+
+/* check for mutex FIXME */
+static int az6027_usb_in_op(struct dvb_usb_device *d, u8 req,
+			    u16 value, u16 index, u8 *b, int blen)
+{
+	int ret = -1;
+	if (mutex_lock_interruptible(&d->usb_mutex))
+		return -EAGAIN;
+
+	ret = usb_control_msg(d->udev,
+			      usb_rcvctrlpipe(d->udev, 0),
+			      req,
+			      USB_TYPE_VENDOR | USB_DIR_IN,
+			      value,
+			      index,
+			      b,
+			      blen,
+			      2000);
+
+	if (ret < 0) {
+		warn("usb in operation failed. (%d)", ret);
+		ret = -EIO;
+	} else
+		ret = 0;
+
+	deb_xfer("in: req. %02x, val: %04x, ind: %04x, buffer: ", req, value, index);
+	debug_dump(b, blen, deb_xfer);
+
+	mutex_unlock(&d->usb_mutex);
+	return ret;
+}
+
+static int az6027_usb_out_op(struct dvb_usb_device *d,
+			     u8 req,
+			     u16 value,
+			     u16 index,
+			     u8 *b,
+			     int blen)
+{
+	int ret;
+
+	deb_xfer("out: req. %02x, val: %04x, ind: %04x, buffer: ", req, value, index);
+	debug_dump(b, blen, deb_xfer);
+
+	if (mutex_lock_interruptible(&d->usb_mutex))
+		return -EAGAIN;
+
+	ret = usb_control_msg(d->udev,
+			      usb_sndctrlpipe(d->udev, 0),
+			      req,
+			      USB_TYPE_VENDOR | USB_DIR_OUT,
+			      value,
+			      index,
+			      b,
+			      blen,
+			      2000);
+
+	if (ret != blen) {
+		warn("usb out operation failed. (%d)", ret);
+		mutex_unlock(&d->usb_mutex);
+		return -EIO;
+	} else{
+		mutex_unlock(&d->usb_mutex);
+		return 0;
+	}
+}
+
+static int az6027_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff)
+{
+	int ret;
+	u8 req;
+	u16 value;
+	u16 index;
+	int blen;
+
+	deb_info("%s %d", __func__, onoff);
+
+	req = 0xBC;
+	value = onoff;
+	index = 0;
+	blen = 0;
+
+	ret = az6027_usb_out_op(adap->dev, req, value, index, NULL, blen);
+	if (ret != 0)
+		warn("usb out operation failed. (%d)", ret);
+
+	return ret;
+}
+
+/* keys for the enclosed remote control */
+static struct rc_map_table rc_map_az6027_table[] = {
+	{ 0x01, KEY_1 },
+	{ 0x02, KEY_2 },
+};
+
+/* remote control stuff (does not work with my box) */
+static int az6027_rc_query(struct dvb_usb_device *d, u32 *event, int *state)
+{
+	return 0;
+}
+
+/*
+int az6027_power_ctrl(struct dvb_usb_device *d, int onoff)
+{
+	u8 v = onoff;
+	return az6027_usb_out_op(d,0xBC,v,3,NULL,1);
+}
+*/
+
+static int az6027_ci_read_attribute_mem(struct dvb_ca_en50221 *ca,
+					int slot,
+					int address)
+{
+	struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data;
+	struct az6027_device_state *state = (struct az6027_device_state *)d->priv;
+
+	int ret;
+	u8 req;
+	u16 value;
+	u16 index;
+	int blen;
+	u8 *b;
+
+	if (slot != 0)
+		return -EINVAL;
+
+	b = kmalloc(12, GFP_KERNEL);
+	if (!b)
+		return -ENOMEM;
+
+	mutex_lock(&state->ca_mutex);
+
+	req = 0xC1;
+	value = address;
+	index = 0;
+	blen = 1;
+
+	ret = az6027_usb_in_op(d, req, value, index, b, blen);
+	if (ret < 0) {
+		warn("usb in operation failed. (%d)", ret);
+		ret = -EINVAL;
+	} else {
+		ret = b[0];
+	}
+
+	mutex_unlock(&state->ca_mutex);
+	kfree(b);
+	return ret;
+}
+
+static int az6027_ci_write_attribute_mem(struct dvb_ca_en50221 *ca,
+					 int slot,
+					 int address,
+					 u8 value)
+{
+	struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data;
+	struct az6027_device_state *state = (struct az6027_device_state *)d->priv;
+
+	int ret;
+	u8 req;
+	u16 value1;
+	u16 index;
+	int blen;
+
+	deb_info("%s %d", __func__, slot);
+	if (slot != 0)
+		return -EINVAL;
+
+	mutex_lock(&state->ca_mutex);
+	req = 0xC2;
+	value1 = address;
+	index = value;
+	blen = 0;
+
+	ret = az6027_usb_out_op(d, req, value1, index, NULL, blen);
+	if (ret != 0)
+		warn("usb out operation failed. (%d)", ret);
+
+	mutex_unlock(&state->ca_mutex);
+	return ret;
+}
+
+static int az6027_ci_read_cam_control(struct dvb_ca_en50221 *ca,
+				      int slot,
+				      u8 address)
+{
+	struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data;
+	struct az6027_device_state *state = (struct az6027_device_state *)d->priv;
+
+	int ret;
+	u8 req;
+	u16 value;
+	u16 index;
+	int blen;
+	u8 *b;
+
+	if (slot != 0)
+		return -EINVAL;
+
+	b = kmalloc(12, GFP_KERNEL);
+	if (!b)
+		return -ENOMEM;
+
+	mutex_lock(&state->ca_mutex);
+
+	req = 0xC3;
+	value = address;
+	index = 0;
+	blen = 2;
+
+	ret = az6027_usb_in_op(d, req, value, index, b, blen);
+	if (ret < 0) {
+		warn("usb in operation failed. (%d)", ret);
+		ret = -EINVAL;
+	} else {
+		if (b[0] == 0)
+			warn("Read CI IO error");
+
+		ret = b[1];
+		deb_info("read cam data = %x from 0x%x", b[1], value);
+	}
+
+	mutex_unlock(&state->ca_mutex);
+	kfree(b);
+	return ret;
+}
+
+static int az6027_ci_write_cam_control(struct dvb_ca_en50221 *ca,
+				       int slot,
+				       u8 address,
+				       u8 value)
+{
+	struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data;
+	struct az6027_device_state *state = (struct az6027_device_state *)d->priv;
+
+	int ret;
+	u8 req;
+	u16 value1;
+	u16 index;
+	int blen;
+
+	if (slot != 0)
+		return -EINVAL;
+
+	mutex_lock(&state->ca_mutex);
+	req = 0xC4;
+	value1 = address;
+	index = value;
+	blen = 0;
+
+	ret = az6027_usb_out_op(d, req, value1, index, NULL, blen);
+	if (ret != 0) {
+		warn("usb out operation failed. (%d)", ret);
+		goto failed;
+	}
+
+failed:
+	mutex_unlock(&state->ca_mutex);
+	return ret;
+}
+
+static int CI_CamReady(struct dvb_ca_en50221 *ca, int slot)
+{
+	struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data;
+
+	int ret;
+	u8 req;
+	u16 value;
+	u16 index;
+	int blen;
+	u8 *b;
+
+	b = kmalloc(12, GFP_KERNEL);
+	if (!b)
+		return -ENOMEM;
+
+	req = 0xC8;
+	value = 0;
+	index = 0;
+	blen = 1;
+
+	ret = az6027_usb_in_op(d, req, value, index, b, blen);
+	if (ret < 0) {
+		warn("usb in operation failed. (%d)", ret);
+		ret = -EIO;
+	} else{
+		ret = b[0];
+	}
+	kfree(b);
+	return ret;
+}
+
+static int az6027_ci_slot_reset(struct dvb_ca_en50221 *ca, int slot)
+{
+	struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data;
+	struct az6027_device_state *state = (struct az6027_device_state *)d->priv;
+
+	int ret, i;
+	u8 req;
+	u16 value;
+	u16 index;
+	int blen;
+
+	mutex_lock(&state->ca_mutex);
+
+	req = 0xC6;
+	value = 1;
+	index = 0;
+	blen = 0;
+
+	ret = az6027_usb_out_op(d, req, value, index, NULL, blen);
+	if (ret != 0) {
+		warn("usb out operation failed. (%d)", ret);
+		goto failed;
+	}
+
+	msleep(500);
+	req = 0xC6;
+	value = 0;
+	index = 0;
+	blen = 0;
+
+	ret = az6027_usb_out_op(d, req, value, index, NULL, blen);
+	if (ret != 0) {
+		warn("usb out operation failed. (%d)", ret);
+		goto failed;
+	}
+
+	for (i = 0; i < 15; i++) {
+		msleep(100);
+
+		if (CI_CamReady(ca, slot)) {
+			deb_info("CAM Ready");
+			break;
+		}
+	}
+	msleep(5000);
+
+failed:
+	mutex_unlock(&state->ca_mutex);
+	return ret;
+}
+
+static int az6027_ci_slot_shutdown(struct dvb_ca_en50221 *ca, int slot)
+{
+	return 0;
+}
+
+static int az6027_ci_slot_ts_enable(struct dvb_ca_en50221 *ca, int slot)
+{
+	struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data;
+	struct az6027_device_state *state = (struct az6027_device_state *)d->priv;
+
+	int ret;
+	u8 req;
+	u16 value;
+	u16 index;
+	int blen;
+
+	deb_info("%s", __func__);
+	mutex_lock(&state->ca_mutex);
+	req = 0xC7;
+	value = 1;
+	index = 0;
+	blen = 0;
+
+	ret = az6027_usb_out_op(d, req, value, index, NULL, blen);
+	if (ret != 0) {
+		warn("usb out operation failed. (%d)", ret);
+		goto failed;
+	}
+
+failed:
+	mutex_unlock(&state->ca_mutex);
+	return ret;
+}
+
+static int az6027_ci_poll_slot_status(struct dvb_ca_en50221 *ca, int slot, int open)
+{
+	struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data;
+	struct az6027_device_state *state = (struct az6027_device_state *)d->priv;
+	int ret;
+	u8 req;
+	u16 value;
+	u16 index;
+	int blen;
+	u8 *b;
+
+	b = kmalloc(12, GFP_KERNEL);
+	if (!b)
+		return -ENOMEM;
+	mutex_lock(&state->ca_mutex);
+
+	req = 0xC5;
+	value = 0;
+	index = 0;
+	blen = 1;
+
+	ret = az6027_usb_in_op(d, req, value, index, b, blen);
+	if (ret < 0) {
+		warn("usb in operation failed. (%d)", ret);
+		ret = -EIO;
+	} else
+		ret = 0;
+
+	if (!ret && b[0] == 1) {
+		ret = DVB_CA_EN50221_POLL_CAM_PRESENT |
+		      DVB_CA_EN50221_POLL_CAM_READY;
+	}
+
+	mutex_unlock(&state->ca_mutex);
+	kfree(b);
+	return ret;
+}
+
+
+static void az6027_ci_uninit(struct dvb_usb_device *d)
+{
+	struct az6027_device_state *state;
+
+	deb_info("%s", __func__);
+
+	if (NULL == d)
+		return;
+
+	state = (struct az6027_device_state *)d->priv;
+	if (NULL == state)
+		return;
+
+	if (NULL == state->ca.data)
+		return;
+
+	dvb_ca_en50221_release(&state->ca);
+
+	memset(&state->ca, 0, sizeof(state->ca));
+}
+
+
+static int az6027_ci_init(struct dvb_usb_adapter *a)
+{
+	struct dvb_usb_device *d = a->dev;
+	struct az6027_device_state *state = (struct az6027_device_state *)d->priv;
+	int ret;
+
+	deb_info("%s", __func__);
+
+	mutex_init(&state->ca_mutex);
+
+	state->ca.owner			= THIS_MODULE;
+	state->ca.read_attribute_mem	= az6027_ci_read_attribute_mem;
+	state->ca.write_attribute_mem	= az6027_ci_write_attribute_mem;
+	state->ca.read_cam_control	= az6027_ci_read_cam_control;
+	state->ca.write_cam_control	= az6027_ci_write_cam_control;
+	state->ca.slot_reset		= az6027_ci_slot_reset;
+	state->ca.slot_shutdown		= az6027_ci_slot_shutdown;
+	state->ca.slot_ts_enable	= az6027_ci_slot_ts_enable;
+	state->ca.poll_slot_status	= az6027_ci_poll_slot_status;
+	state->ca.data			= d;
+
+	ret = dvb_ca_en50221_init(&a->dvb_adap,
+				  &state->ca,
+				  0, /* flags */
+				  1);/* n_slots */
+	if (ret != 0) {
+		err("Cannot initialize CI: Error %d.", ret);
+		memset(&state->ca, 0, sizeof(state->ca));
+		return ret;
+	}
+
+	deb_info("CI initialized.");
+
+	return 0;
+}
+
+/*
+static int az6027_read_mac_addr(struct dvb_usb_device *d, u8 mac[6])
+{
+	az6027_usb_in_op(d, 0xb7, 6, 0, &mac[0], 6);
+	return 0;
+}
+*/
+
+static int az6027_set_voltage(struct dvb_frontend *fe,
+			      enum fe_sec_voltage voltage)
+{
+
+	u8 buf;
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+
+	struct i2c_msg i2c_msg = {
+		.addr	= 0x99,
+		.flags	= 0,
+		.buf	= &buf,
+		.len	= 1
+	};
+
+	/*
+	 * 2   --18v
+	 * 1   --13v
+	 * 0   --off
+	 */
+	switch (voltage) {
+	case SEC_VOLTAGE_13:
+		buf = 1;
+		i2c_transfer(&adap->dev->i2c_adap, &i2c_msg, 1);
+		break;
+
+	case SEC_VOLTAGE_18:
+		buf = 2;
+		i2c_transfer(&adap->dev->i2c_adap, &i2c_msg, 1);
+		break;
+
+	case SEC_VOLTAGE_OFF:
+		buf = 0;
+		i2c_transfer(&adap->dev->i2c_adap, &i2c_msg, 1);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+
+static int az6027_frontend_poweron(struct dvb_usb_adapter *adap)
+{
+	int ret;
+	u8 req;
+	u16 value;
+	u16 index;
+	int blen;
+
+	req = 0xBC;
+	value = 1; /* power on */
+	index = 3;
+	blen = 0;
+
+	ret = az6027_usb_out_op(adap->dev, req, value, index, NULL, blen);
+	if (ret != 0)
+		return -EIO;
+
+	return 0;
+}
+static int az6027_frontend_reset(struct dvb_usb_adapter *adap)
+{
+	int ret;
+	u8 req;
+	u16 value;
+	u16 index;
+	int blen;
+
+	/* reset demodulator */
+	req = 0xC0;
+	value = 1; /* high */
+	index = 3;
+	blen = 0;
+
+	ret = az6027_usb_out_op(adap->dev, req, value, index, NULL, blen);
+	if (ret != 0)
+		return -EIO;
+
+	req = 0xC0;
+	value = 0; /* low */
+	index = 3;
+	blen = 0;
+	msleep_interruptible(200);
+
+	ret = az6027_usb_out_op(adap->dev, req, value, index, NULL, blen);
+	if (ret != 0)
+		return -EIO;
+
+	msleep_interruptible(200);
+
+	req = 0xC0;
+	value = 1; /*high */
+	index = 3;
+	blen = 0;
+
+	ret = az6027_usb_out_op(adap->dev, req, value, index, NULL, blen);
+	if (ret != 0)
+		return -EIO;
+
+	msleep_interruptible(200);
+	return 0;
+}
+
+static int az6027_frontend_tsbypass(struct dvb_usb_adapter *adap, int onoff)
+{
+	int ret;
+	u8 req;
+	u16 value;
+	u16 index;
+	int blen;
+
+	/* TS passthrough */
+	req = 0xC7;
+	value = onoff;
+	index = 0;
+	blen = 0;
+
+	ret = az6027_usb_out_op(adap->dev, req, value, index, NULL, blen);
+	if (ret != 0)
+		return -EIO;
+
+	return 0;
+}
+
+static int az6027_frontend_attach(struct dvb_usb_adapter *adap)
+{
+
+	az6027_frontend_poweron(adap);
+	az6027_frontend_reset(adap);
+
+	deb_info("adap = %p, dev = %p\n", adap, adap->dev);
+	adap->fe_adap[0].fe = stb0899_attach(&az6027_stb0899_config, &adap->dev->i2c_adap);
+
+	if (adap->fe_adap[0].fe) {
+		deb_info("found STB0899 DVB-S/DVB-S2 frontend @0x%02x", az6027_stb0899_config.demod_address);
+		if (stb6100_attach(adap->fe_adap[0].fe, &az6027_stb6100_config, &adap->dev->i2c_adap)) {
+			deb_info("found STB6100 DVB-S/DVB-S2 frontend @0x%02x", az6027_stb6100_config.tuner_address);
+			adap->fe_adap[0].fe->ops.set_voltage = az6027_set_voltage;
+			az6027_ci_init(adap);
+		} else {
+			adap->fe_adap[0].fe = NULL;
+		}
+	} else
+		warn("no front-end attached\n");
+
+	az6027_frontend_tsbypass(adap, 0);
+
+	return 0;
+}
+
+static struct dvb_usb_device_properties az6027_properties;
+
+static void az6027_usb_disconnect(struct usb_interface *intf)
+{
+	struct dvb_usb_device *d = usb_get_intfdata(intf);
+	az6027_ci_uninit(d);
+	dvb_usb_device_exit(intf);
+}
+
+
+static int az6027_usb_probe(struct usb_interface *intf,
+			    const struct usb_device_id *id)
+{
+	return dvb_usb_device_init(intf,
+				   &az6027_properties,
+				   THIS_MODULE,
+				   NULL,
+				   adapter_nr);
+}
+
+/* I2C */
+static int az6027_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	int i = 0, j = 0, len = 0;
+	u16 index;
+	u16 value;
+	int length;
+	u8 req;
+	u8 *data;
+
+	data = kmalloc(256, GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0) {
+		kfree(data);
+		return -EAGAIN;
+	}
+
+	if (num > 2)
+		warn("more than 2 i2c messages at a time is not handled yet. TODO.");
+
+	for (i = 0; i < num; i++) {
+
+		if (msg[i].addr == 0x99) {
+			req = 0xBE;
+			index = 0;
+			value = msg[i].buf[0] & 0x00ff;
+			length = 1;
+			az6027_usb_out_op(d, req, value, index, data, length);
+		}
+
+		if (msg[i].addr == 0xd0) {
+			/* write/read request */
+			if (i + 1 < num && (msg[i + 1].flags & I2C_M_RD)) {
+				req = 0xB9;
+				index = (((msg[i].buf[0] << 8) & 0xff00) | (msg[i].buf[1] & 0x00ff));
+				value = msg[i].addr + (msg[i].len << 8);
+				length = msg[i + 1].len + 6;
+				az6027_usb_in_op(d, req, value, index, data, length);
+				len = msg[i + 1].len;
+				for (j = 0; j < len; j++)
+					msg[i + 1].buf[j] = data[j + 5];
+
+				i++;
+			} else {
+
+				/* demod 16bit addr */
+				req = 0xBD;
+				index = (((msg[i].buf[0] << 8) & 0xff00) | (msg[i].buf[1] & 0x00ff));
+				value = msg[i].addr + (2 << 8);
+				length = msg[i].len - 2;
+				len = msg[i].len - 2;
+				for (j = 0; j < len; j++)
+					data[j] = msg[i].buf[j + 2];
+				az6027_usb_out_op(d, req, value, index, data, length);
+			}
+		}
+
+		if (msg[i].addr == 0xc0) {
+			if (msg[i].flags & I2C_M_RD) {
+
+				req = 0xB9;
+				index = 0x0;
+				value = msg[i].addr;
+				length = msg[i].len + 6;
+				az6027_usb_in_op(d, req, value, index, data, length);
+				len = msg[i].len;
+				for (j = 0; j < len; j++)
+					msg[i].buf[j] = data[j + 5];
+
+			} else {
+
+				req = 0xBD;
+				index = msg[i].buf[0] & 0x00FF;
+				value = msg[i].addr + (1 << 8);
+				length = msg[i].len - 1;
+				len = msg[i].len - 1;
+
+				for (j = 0; j < len; j++)
+					data[j] = msg[i].buf[j + 1];
+
+				az6027_usb_out_op(d, req, value, index, data, length);
+			}
+		}
+	}
+	mutex_unlock(&d->i2c_mutex);
+	kfree(data);
+
+	return i;
+}
+
+
+static u32 az6027_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm az6027_i2c_algo = {
+	.master_xfer   = az6027_i2c_xfer,
+	.functionality = az6027_i2c_func,
+};
+
+static int az6027_identify_state(struct usb_device *udev,
+				 struct dvb_usb_device_properties *props,
+				 struct dvb_usb_device_description **desc,
+				 int *cold)
+{
+	u8 *b;
+	s16 ret;
+
+	b = kmalloc(16, GFP_KERNEL);
+	if (!b)
+		return -ENOMEM;
+
+	ret = usb_control_msg(udev,
+				  usb_rcvctrlpipe(udev, 0),
+				  0xb7,
+				  USB_TYPE_VENDOR | USB_DIR_IN,
+				  6,
+				  0,
+				  b,
+				  6,
+				  USB_CTRL_GET_TIMEOUT);
+
+	*cold = ret <= 0;
+	kfree(b);
+	deb_info("cold: %d\n", *cold);
+	return 0;
+}
+
+
+static struct usb_device_id az6027_usb_table[] = {
+	{ USB_DEVICE(USB_VID_AZUREWAVE, USB_PID_AZUREWAVE_AZ6027) },
+	{ USB_DEVICE(USB_VID_TERRATEC,  USB_PID_TERRATEC_DVBS2CI_V1) },
+	{ USB_DEVICE(USB_VID_TERRATEC,  USB_PID_TERRATEC_DVBS2CI_V2) },
+	{ USB_DEVICE(USB_VID_TECHNISAT, USB_PID_TECHNISAT_USB2_HDCI_V1) },
+	{ USB_DEVICE(USB_VID_TECHNISAT, USB_PID_TECHNISAT_USB2_HDCI_V2) },
+	{ USB_DEVICE(USB_VID_ELGATO, USB_PID_ELGATO_EYETV_SAT) },
+	{ USB_DEVICE(USB_VID_ELGATO, USB_PID_ELGATO_EYETV_SAT_V2) },
+	{ USB_DEVICE(USB_VID_ELGATO, USB_PID_ELGATO_EYETV_SAT_V3) },
+	{ },
+};
+
+MODULE_DEVICE_TABLE(usb, az6027_usb_table);
+
+static struct dvb_usb_device_properties az6027_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+	.usb_ctrl = CYPRESS_FX2,
+	.firmware            = "dvb-usb-az6027-03.fw",
+	.no_reconnect        = 1,
+
+	.size_of_priv     = sizeof(struct az6027_device_state),
+	.identify_state		= az6027_identify_state,
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.streaming_ctrl   = az6027_streaming_ctrl,
+			.frontend_attach  = az6027_frontend_attach,
+
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+				.count = 10,
+				.endpoint = 0x02,
+				.u = {
+					.bulk = {
+						.buffersize = 4096,
+					}
+				}
+			},
+		}},
+		}
+	},
+/*
+	.power_ctrl       = az6027_power_ctrl,
+	.read_mac_address = az6027_read_mac_addr,
+ */
+	.rc.legacy = {
+		.rc_map_table     = rc_map_az6027_table,
+		.rc_map_size      = ARRAY_SIZE(rc_map_az6027_table),
+		.rc_interval      = 400,
+		.rc_query         = az6027_rc_query,
+	},
+
+	.i2c_algo         = &az6027_i2c_algo,
+
+	.num_device_descs = 8,
+	.devices = {
+		{
+			.name = "AZUREWAVE DVB-S/S2 USB2.0 (AZ6027)",
+			.cold_ids = { &az6027_usb_table[0], NULL },
+			.warm_ids = { NULL },
+		}, {
+			.name = "TERRATEC S7",
+			.cold_ids = { &az6027_usb_table[1], NULL },
+			.warm_ids = { NULL },
+		}, {
+			.name = "TERRATEC S7 MKII",
+			.cold_ids = { &az6027_usb_table[2], NULL },
+			.warm_ids = { NULL },
+		}, {
+			.name = "Technisat SkyStar USB 2 HD CI",
+			.cold_ids = { &az6027_usb_table[3], NULL },
+			.warm_ids = { NULL },
+		}, {
+			.name = "Technisat SkyStar USB 2 HD CI",
+			.cold_ids = { &az6027_usb_table[4], NULL },
+			.warm_ids = { NULL },
+		}, {
+			.name = "Elgato EyeTV Sat",
+			.cold_ids = { &az6027_usb_table[5], NULL },
+			.warm_ids = { NULL },
+		}, {
+			.name = "Elgato EyeTV Sat",
+			.cold_ids = { &az6027_usb_table[6], NULL },
+			.warm_ids = { NULL },
+		}, {
+			.name = "Elgato EyeTV Sat",
+			.cold_ids = { &az6027_usb_table[7], NULL },
+			.warm_ids = { NULL },
+		},
+		{ NULL },
+	}
+};
+
+/* usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver az6027_usb_driver = {
+	.name		= "dvb_usb_az6027",
+	.probe		= az6027_usb_probe,
+	.disconnect	= az6027_usb_disconnect,
+	.id_table	= az6027_usb_table,
+};
+
+module_usb_driver(az6027_usb_driver);
+
+MODULE_AUTHOR("Adams Xu <Adams.xu@azwave.com.cn>");
+MODULE_DESCRIPTION("Driver for AZUREWAVE DVB-S/S2 USB2.0 (AZ6027)");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/az6027.h b/drivers/media/usb/dvb-usb/az6027.h
new file mode 100644
index 0000000..95b056b
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/az6027.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _DVB_USB_VP6027_H_
+#define _DVB_USB_VP6027_H_
+
+#define DVB_USB_LOG_PREFIX "az6027"
+#include "dvb-usb.h"
+
+
+extern int dvb_usb_az6027_debug;
+#define deb_info(args...) dprintk(dvb_usb_az6027_debug, 0x01, args)
+#define deb_xfer(args...) dprintk(dvb_usb_az6027_debug, 0x02, args)
+#define deb_rc(args...)   dprintk(dvb_usb_az6027_debug, 0x04, args)
+#define deb_fe(args...)   dprintk(dvb_usb_az6027_debug, 0x08, args)
+
+#endif
diff --git a/drivers/media/usb/dvb-usb/cinergyT2-core.c b/drivers/media/usb/dvb-usb/cinergyT2-core.c
new file mode 100644
index 0000000..6131aa7
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/cinergyT2-core.c
@@ -0,0 +1,274 @@
+/*
+ * TerraTec Cinergy T2/qanu USB2 DVB-T adapter.
+ *
+ * Copyright (C) 2007 Tomi Orava (tomimo@ncircle.nullnet.fi)
+ *
+ * Based on the dvb-usb-framework code and the
+ * original Terratec Cinergy T2 driver by:
+ *
+ * Copyright (C) 2004 Daniel Mack <daniel@qanu.de> and
+ *		    Holger Waechtler <holger@qanu.de>
+ *
+ *  Protocol Spec published on http://qanu.de/specs/terratec_cinergyT2.pdf
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include "cinergyT2.h"
+
+
+/* debug */
+int dvb_usb_cinergyt2_debug;
+
+module_param_named(debug, dvb_usb_cinergyt2_debug, int, 0644);
+MODULE_PARM_DESC(debug, "set debugging level (1=info, xfer=2, rc=4 (or-able)).");
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+struct cinergyt2_state {
+	u8 rc_counter;
+	unsigned char data[64];
+};
+
+/* We are missing a release hook with usb_device data */
+static struct dvb_usb_device *cinergyt2_usb_device;
+
+static struct dvb_usb_device_properties cinergyt2_properties;
+
+static int cinergyt2_streaming_ctrl(struct dvb_usb_adapter *adap, int enable)
+{
+	struct dvb_usb_device *d = adap->dev;
+	struct cinergyt2_state *st = d->priv;
+	int ret;
+
+	mutex_lock(&d->data_mutex);
+	st->data[0] = CINERGYT2_EP1_CONTROL_STREAM_TRANSFER;
+	st->data[1] = enable ? 1 : 0;
+
+	ret = dvb_usb_generic_rw(d, st->data, 2, st->data, 64, 0);
+	mutex_unlock(&d->data_mutex);
+
+	return ret;
+}
+
+static int cinergyt2_power_ctrl(struct dvb_usb_device *d, int enable)
+{
+	struct cinergyt2_state *st = d->priv;
+	int ret;
+
+	mutex_lock(&d->data_mutex);
+	st->data[0] = CINERGYT2_EP1_SLEEP_MODE;
+	st->data[1] = enable ? 0 : 1;
+
+	ret = dvb_usb_generic_rw(d, st->data, 2, st->data, 3, 0);
+	mutex_unlock(&d->data_mutex);
+
+	return ret;
+}
+
+static int cinergyt2_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *d = adap->dev;
+	struct cinergyt2_state *st = d->priv;
+	int ret;
+
+	adap->fe_adap[0].fe = cinergyt2_fe_attach(adap->dev);
+
+	mutex_lock(&d->data_mutex);
+	st->data[0] = CINERGYT2_EP1_GET_FIRMWARE_VERSION;
+
+	ret = dvb_usb_generic_rw(d, st->data, 1, st->data, 3, 0);
+	if (ret < 0) {
+		deb_rc("cinergyt2_power_ctrl() Failed to retrieve sleep state info\n");
+	}
+	mutex_unlock(&d->data_mutex);
+
+	/* Copy this pointer as we are gonna need it in the release phase */
+	cinergyt2_usb_device = adap->dev;
+
+	return ret;
+}
+
+static struct rc_map_table rc_map_cinergyt2_table[] = {
+	{ 0x0401, KEY_POWER },
+	{ 0x0402, KEY_1 },
+	{ 0x0403, KEY_2 },
+	{ 0x0404, KEY_3 },
+	{ 0x0405, KEY_4 },
+	{ 0x0406, KEY_5 },
+	{ 0x0407, KEY_6 },
+	{ 0x0408, KEY_7 },
+	{ 0x0409, KEY_8 },
+	{ 0x040a, KEY_9 },
+	{ 0x040c, KEY_0 },
+	{ 0x040b, KEY_VIDEO },
+	{ 0x040d, KEY_REFRESH },
+	{ 0x040e, KEY_SELECT },
+	{ 0x040f, KEY_EPG },
+	{ 0x0410, KEY_UP },
+	{ 0x0414, KEY_DOWN },
+	{ 0x0411, KEY_LEFT },
+	{ 0x0413, KEY_RIGHT },
+	{ 0x0412, KEY_OK },
+	{ 0x0415, KEY_TEXT },
+	{ 0x0416, KEY_INFO },
+	{ 0x0417, KEY_RED },
+	{ 0x0418, KEY_GREEN },
+	{ 0x0419, KEY_YELLOW },
+	{ 0x041a, KEY_BLUE },
+	{ 0x041c, KEY_VOLUMEUP },
+	{ 0x041e, KEY_VOLUMEDOWN },
+	{ 0x041d, KEY_MUTE },
+	{ 0x041b, KEY_CHANNELUP },
+	{ 0x041f, KEY_CHANNELDOWN },
+	{ 0x0440, KEY_PAUSE },
+	{ 0x044c, KEY_PLAY },
+	{ 0x0458, KEY_RECORD },
+	{ 0x0454, KEY_PREVIOUS },
+	{ 0x0448, KEY_STOP },
+	{ 0x045c, KEY_NEXT }
+};
+
+/* Number of keypresses to ignore before detect repeating */
+#define RC_REPEAT_DELAY 3
+
+static int repeatable_keys[] = {
+	KEY_UP,
+	KEY_DOWN,
+	KEY_LEFT,
+	KEY_RIGHT,
+	KEY_VOLUMEUP,
+	KEY_VOLUMEDOWN,
+	KEY_CHANNELUP,
+	KEY_CHANNELDOWN
+};
+
+static int cinergyt2_rc_query(struct dvb_usb_device *d, u32 *event, int *state)
+{
+	struct cinergyt2_state *st = d->priv;
+	int i, ret;
+
+	*state = REMOTE_NO_KEY_PRESSED;
+
+	mutex_lock(&d->data_mutex);
+	st->data[0] = CINERGYT2_EP1_GET_RC_EVENTS;
+
+	ret = dvb_usb_generic_rw(d, st->data, 1, st->data, 5, 0);
+	if (ret < 0)
+		goto ret;
+
+	if (st->data[4] == 0xff) {
+		/* key repeat */
+		st->rc_counter++;
+		if (st->rc_counter > RC_REPEAT_DELAY) {
+			for (i = 0; i < ARRAY_SIZE(repeatable_keys); i++) {
+				if (d->last_event == repeatable_keys[i]) {
+					*state = REMOTE_KEY_REPEAT;
+					*event = d->last_event;
+					deb_rc("repeat key, event %x\n",
+						   *event);
+					goto ret;
+				}
+			}
+			deb_rc("repeated key (non repeatable)\n");
+		}
+		goto ret;
+	}
+
+	/* hack to pass checksum on the custom field */
+	st->data[2] = ~st->data[1];
+	dvb_usb_nec_rc_key_to_event(d, st->data, event, state);
+	if (st->data[0] != 0) {
+		if (*event != d->last_event)
+			st->rc_counter = 0;
+
+		deb_rc("key: %*ph\n", 5, st->data);
+	}
+
+ret:
+	mutex_unlock(&d->data_mutex);
+	return ret;
+}
+
+static int cinergyt2_usb_probe(struct usb_interface *intf,
+				const struct usb_device_id *id)
+{
+	return dvb_usb_device_init(intf, &cinergyt2_properties,
+				   THIS_MODULE, NULL, adapter_nr);
+}
+
+static struct usb_device_id cinergyt2_usb_table[] = {
+	{ USB_DEVICE(USB_VID_TERRATEC, 0x0038) },
+	{ 0 }
+};
+
+MODULE_DEVICE_TABLE(usb, cinergyt2_usb_table);
+
+static struct dvb_usb_device_properties cinergyt2_properties = {
+	.size_of_priv = sizeof(struct cinergyt2_state),
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.streaming_ctrl   = cinergyt2_streaming_ctrl,
+			.frontend_attach  = cinergyt2_frontend_attach,
+
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+				.count = 5,
+				.endpoint = 0x02,
+				.u = {
+					.bulk = {
+						.buffersize = 512,
+					}
+				}
+			},
+		}},
+		}
+	},
+
+	.power_ctrl       = cinergyt2_power_ctrl,
+
+	.rc.legacy = {
+		.rc_interval      = 50,
+		.rc_map_table     = rc_map_cinergyt2_table,
+		.rc_map_size      = ARRAY_SIZE(rc_map_cinergyt2_table),
+		.rc_query         = cinergyt2_rc_query,
+	},
+
+	.generic_bulk_ctrl_endpoint = 1,
+
+	.num_device_descs = 1,
+	.devices = {
+		{ .name = "TerraTec/qanu USB2.0 Highspeed DVB-T Receiver",
+		  .cold_ids = {NULL},
+		  .warm_ids = { &cinergyt2_usb_table[0], NULL },
+		},
+		{ NULL },
+	}
+};
+
+
+static struct usb_driver cinergyt2_driver = {
+	.name		= "cinergyT2",
+	.probe		= cinergyt2_usb_probe,
+	.disconnect	= dvb_usb_device_exit,
+	.id_table	= cinergyt2_usb_table
+};
+
+module_usb_driver(cinergyt2_driver);
+
+MODULE_DESCRIPTION("Terratec Cinergy T2 DVB-T driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Tomi Orava");
diff --git a/drivers/media/usb/dvb-usb/cinergyT2-fe.c b/drivers/media/usb/dvb-usb/cinergyT2-fe.c
new file mode 100644
index 0000000..df71df7
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/cinergyT2-fe.c
@@ -0,0 +1,327 @@
+/*
+ * TerraTec Cinergy T2/qanu USB2 DVB-T adapter.
+ *
+ * Copyright (C) 2007 Tomi Orava (tomimo@ncircle.nullnet.fi)
+ *
+ * Based on the dvb-usb-framework code and the
+ * original Terratec Cinergy T2 driver by:
+ *
+ * Copyright (C) 2004 Daniel Mack <daniel@qanu.de> and
+ *                  Holger Waechtler <holger@qanu.de>
+ *
+ *  Protocol Spec published on http://qanu.de/specs/terratec_cinergyT2.pdf
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include "cinergyT2.h"
+
+
+/*
+ *  convert linux-dvb frontend parameter set into TPS.
+ *  See ETSI ETS-300744, section 4.6.2, table 9 for details.
+ *
+ *  This function is probably reusable and may better get placed in a support
+ *  library.
+ *
+ *  We replace errornous fields by default TPS fields (the ones with value 0).
+ */
+
+static uint16_t compute_tps(struct dtv_frontend_properties *op)
+{
+	uint16_t tps = 0;
+
+	switch (op->code_rate_HP) {
+	case FEC_2_3:
+		tps |= (1 << 7);
+		break;
+	case FEC_3_4:
+		tps |= (2 << 7);
+		break;
+	case FEC_5_6:
+		tps |= (3 << 7);
+		break;
+	case FEC_7_8:
+		tps |= (4 << 7);
+		break;
+	case FEC_1_2:
+	case FEC_AUTO:
+	default:
+		/* tps |= (0 << 7) */;
+	}
+
+	switch (op->code_rate_LP) {
+	case FEC_2_3:
+		tps |= (1 << 4);
+		break;
+	case FEC_3_4:
+		tps |= (2 << 4);
+		break;
+	case FEC_5_6:
+		tps |= (3 << 4);
+		break;
+	case FEC_7_8:
+		tps |= (4 << 4);
+		break;
+	case FEC_1_2:
+	case FEC_AUTO:
+	default:
+		/* tps |= (0 << 4) */;
+	}
+
+	switch (op->modulation) {
+	case QAM_16:
+		tps |= (1 << 13);
+		break;
+	case QAM_64:
+		tps |= (2 << 13);
+		break;
+	case QPSK:
+	default:
+		/* tps |= (0 << 13) */;
+	}
+
+	switch (op->transmission_mode) {
+	case TRANSMISSION_MODE_8K:
+		tps |= (1 << 0);
+		break;
+	case TRANSMISSION_MODE_2K:
+	default:
+		/* tps |= (0 << 0) */;
+	}
+
+	switch (op->guard_interval) {
+	case GUARD_INTERVAL_1_16:
+		tps |= (1 << 2);
+		break;
+	case GUARD_INTERVAL_1_8:
+		tps |= (2 << 2);
+		break;
+	case GUARD_INTERVAL_1_4:
+		tps |= (3 << 2);
+		break;
+	case GUARD_INTERVAL_1_32:
+	default:
+		/* tps |= (0 << 2) */;
+	}
+
+	switch (op->hierarchy) {
+	case HIERARCHY_1:
+		tps |= (1 << 10);
+		break;
+	case HIERARCHY_2:
+		tps |= (2 << 10);
+		break;
+	case HIERARCHY_4:
+		tps |= (3 << 10);
+		break;
+	case HIERARCHY_NONE:
+	default:
+		/* tps |= (0 << 10) */;
+	}
+
+	return tps;
+}
+
+struct cinergyt2_fe_state {
+	struct dvb_frontend fe;
+	struct dvb_usb_device *d;
+
+	unsigned char data[64];
+	struct mutex data_mutex;
+
+	struct dvbt_get_status_msg status;
+};
+
+static int cinergyt2_fe_read_status(struct dvb_frontend *fe,
+				    enum fe_status *status)
+{
+	struct cinergyt2_fe_state *state = fe->demodulator_priv;
+	int ret;
+
+	mutex_lock(&state->data_mutex);
+	state->data[0] = CINERGYT2_EP1_GET_TUNER_STATUS;
+
+	ret = dvb_usb_generic_rw(state->d, state->data, 1,
+				 state->data, sizeof(state->status), 0);
+	if (!ret)
+		memcpy(&state->status, state->data, sizeof(state->status));
+	mutex_unlock(&state->data_mutex);
+
+	if (ret < 0)
+		return ret;
+
+	*status = 0;
+
+	if (0xffff - le16_to_cpu(state->status.gain) > 30)
+		*status |= FE_HAS_SIGNAL;
+	if (state->status.lock_bits & (1 << 6))
+		*status |= FE_HAS_LOCK;
+	if (state->status.lock_bits & (1 << 5))
+		*status |= FE_HAS_SYNC;
+	if (state->status.lock_bits & (1 << 4))
+		*status |= FE_HAS_CARRIER;
+	if (state->status.lock_bits & (1 << 1))
+		*status |= FE_HAS_VITERBI;
+
+	if ((*status & (FE_HAS_CARRIER | FE_HAS_VITERBI | FE_HAS_SYNC)) !=
+			(FE_HAS_CARRIER | FE_HAS_VITERBI | FE_HAS_SYNC))
+		*status &= ~FE_HAS_LOCK;
+
+	return 0;
+}
+
+static int cinergyt2_fe_read_ber(struct dvb_frontend *fe, u32 *ber)
+{
+	struct cinergyt2_fe_state *state = fe->demodulator_priv;
+
+	*ber = le32_to_cpu(state->status.viterbi_error_rate);
+	return 0;
+}
+
+static int cinergyt2_fe_read_unc_blocks(struct dvb_frontend *fe, u32 *unc)
+{
+	struct cinergyt2_fe_state *state = fe->demodulator_priv;
+
+	*unc = le32_to_cpu(state->status.uncorrected_block_count);
+	return 0;
+}
+
+static int cinergyt2_fe_read_signal_strength(struct dvb_frontend *fe,
+						u16 *strength)
+{
+	struct cinergyt2_fe_state *state = fe->demodulator_priv;
+
+	*strength = (0xffff - le16_to_cpu(state->status.gain));
+	return 0;
+}
+
+static int cinergyt2_fe_read_snr(struct dvb_frontend *fe, u16 *snr)
+{
+	struct cinergyt2_fe_state *state = fe->demodulator_priv;
+
+	*snr = (state->status.snr << 8) | state->status.snr;
+	return 0;
+}
+
+static int cinergyt2_fe_init(struct dvb_frontend *fe)
+{
+	return 0;
+}
+
+static int cinergyt2_fe_sleep(struct dvb_frontend *fe)
+{
+	deb_info("cinergyt2_fe_sleep() Called\n");
+	return 0;
+}
+
+static int cinergyt2_fe_get_tune_settings(struct dvb_frontend *fe,
+				struct dvb_frontend_tune_settings *tune)
+{
+	tune->min_delay_ms = 800;
+	return 0;
+}
+
+static int cinergyt2_fe_set_frontend(struct dvb_frontend *fe)
+{
+	struct dtv_frontend_properties *fep = &fe->dtv_property_cache;
+	struct cinergyt2_fe_state *state = fe->demodulator_priv;
+	struct dvbt_set_parameters_msg *param;
+	int err;
+
+	mutex_lock(&state->data_mutex);
+
+	param = (void *)state->data;
+	param->cmd = CINERGYT2_EP1_SET_TUNER_PARAMETERS;
+	param->tps = cpu_to_le16(compute_tps(fep));
+	param->freq = cpu_to_le32(fep->frequency / 1000);
+	param->flags = 0;
+
+	switch (fep->bandwidth_hz) {
+	default:
+	case 8000000:
+		param->bandwidth = 8;
+		break;
+	case 7000000:
+		param->bandwidth = 7;
+		break;
+	case 6000000:
+		param->bandwidth = 6;
+		break;
+	}
+
+	err = dvb_usb_generic_rw(state->d, state->data, sizeof(*param),
+				 state->data, 2, 0);
+	if (err < 0)
+		err("cinergyt2_fe_set_frontend() Failed! err=%d\n", err);
+
+	mutex_unlock(&state->data_mutex);
+	return (err < 0) ? err : 0;
+}
+
+static void cinergyt2_fe_release(struct dvb_frontend *fe)
+{
+	struct cinergyt2_fe_state *state = fe->demodulator_priv;
+	kfree(state);
+}
+
+static const struct dvb_frontend_ops cinergyt2_fe_ops;
+
+struct dvb_frontend *cinergyt2_fe_attach(struct dvb_usb_device *d)
+{
+	struct cinergyt2_fe_state *s = kzalloc(sizeof(
+					struct cinergyt2_fe_state), GFP_KERNEL);
+	if (s == NULL)
+		return NULL;
+
+	s->d = d;
+	memcpy(&s->fe.ops, &cinergyt2_fe_ops, sizeof(struct dvb_frontend_ops));
+	s->fe.demodulator_priv = s;
+	mutex_init(&s->data_mutex);
+	return &s->fe;
+}
+
+
+static const struct dvb_frontend_ops cinergyt2_fe_ops = {
+	.delsys = { SYS_DVBT },
+	.info = {
+		.name			= DRIVER_NAME,
+		.frequency_min_hz	= 174 * MHz,
+		.frequency_max_hz	= 862 * MHz,
+		.frequency_stepsize_hz	= 166667,
+		.caps = FE_CAN_INVERSION_AUTO | FE_CAN_FEC_1_2
+			| FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4
+			| FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8
+			| FE_CAN_FEC_AUTO | FE_CAN_QPSK
+			| FE_CAN_QAM_16 | FE_CAN_QAM_64
+			| FE_CAN_QAM_AUTO
+			| FE_CAN_TRANSMISSION_MODE_AUTO
+			| FE_CAN_GUARD_INTERVAL_AUTO
+			| FE_CAN_HIERARCHY_AUTO
+			| FE_CAN_RECOVER
+			| FE_CAN_MUTE_TS
+	},
+
+	.release		= cinergyt2_fe_release,
+
+	.init			= cinergyt2_fe_init,
+	.sleep			= cinergyt2_fe_sleep,
+
+	.set_frontend		= cinergyt2_fe_set_frontend,
+	.get_tune_settings	= cinergyt2_fe_get_tune_settings,
+
+	.read_status		= cinergyt2_fe_read_status,
+	.read_ber		= cinergyt2_fe_read_ber,
+	.read_signal_strength	= cinergyt2_fe_read_signal_strength,
+	.read_snr		= cinergyt2_fe_read_snr,
+	.read_ucblocks		= cinergyt2_fe_read_unc_blocks,
+};
diff --git a/drivers/media/usb/dvb-usb/cinergyT2.h b/drivers/media/usb/dvb-usb/cinergyT2.h
new file mode 100644
index 0000000..c04b819
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/cinergyT2.h
@@ -0,0 +1,91 @@
+/*
+ * TerraTec Cinergy T2/qanu USB2 DVB-T adapter.
+ *
+ * Copyright (C) 2007 Tomi Orava (tomimo@ncircle.nullnet.fi)
+ *
+ * Based on the dvb-usb-framework code and the
+ * original Terratec Cinergy T2 driver by:
+ *
+ * Copyright (C) 2004 Daniel Mack <daniel@qanu.de> and
+ *                  Holger Waechtler <holger@qanu.de>
+ *
+ *  Protocol Spec published on http://qanu.de/specs/terratec_cinergyT2.pdf
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License,  or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef _DVB_USB_CINERGYT2_H_
+#define _DVB_USB_CINERGYT2_H_
+
+#include <linux/usb/input.h>
+
+#define DVB_USB_LOG_PREFIX "cinergyT2"
+#include "dvb-usb.h"
+
+#define DRIVER_NAME "TerraTec/qanu USB2.0 Highspeed DVB-T Receiver"
+
+extern int dvb_usb_cinergyt2_debug;
+
+#define deb_info(args...)  dprintk(dvb_usb_cinergyt2_debug,  0x001, args)
+#define deb_xfer(args...)  dprintk(dvb_usb_cinergyt2_debug,  0x002, args)
+#define deb_pll(args...)   dprintk(dvb_usb_cinergyt2_debug,  0x004, args)
+#define deb_ts(args...)    dprintk(dvb_usb_cinergyt2_debug,  0x008, args)
+#define deb_err(args...)   dprintk(dvb_usb_cinergyt2_debug,  0x010, args)
+#define deb_rc(args...)    dprintk(dvb_usb_cinergyt2_debug,  0x020, args)
+#define deb_fw(args...)    dprintk(dvb_usb_cinergyt2_debug,  0x040, args)
+#define deb_mem(args...)   dprintk(dvb_usb_cinergyt2_debug,  0x080, args)
+#define deb_uxfer(args...) dprintk(dvb_usb_cinergyt2_debug,  0x100, args)
+
+
+
+enum cinergyt2_ep1_cmd {
+	CINERGYT2_EP1_PID_TABLE_RESET		= 0x01,
+	CINERGYT2_EP1_PID_SETUP			= 0x02,
+	CINERGYT2_EP1_CONTROL_STREAM_TRANSFER	= 0x03,
+	CINERGYT2_EP1_SET_TUNER_PARAMETERS	= 0x04,
+	CINERGYT2_EP1_GET_TUNER_STATUS		= 0x05,
+	CINERGYT2_EP1_START_SCAN		= 0x06,
+	CINERGYT2_EP1_CONTINUE_SCAN		= 0x07,
+	CINERGYT2_EP1_GET_RC_EVENTS		= 0x08,
+	CINERGYT2_EP1_SLEEP_MODE		= 0x09,
+	CINERGYT2_EP1_GET_FIRMWARE_VERSION	= 0x0A
+};
+
+
+struct dvbt_get_status_msg {
+	uint32_t freq;
+	uint8_t bandwidth;
+	uint16_t tps;
+	uint8_t flags;
+	__le16 gain;
+	uint8_t snr;
+	__le32 viterbi_error_rate;
+	uint32_t rs_error_rate;
+	__le32 uncorrected_block_count;
+	uint8_t lock_bits;
+	uint8_t prev_lock_bits;
+} __attribute__((packed));
+
+
+struct dvbt_set_parameters_msg {
+	uint8_t cmd;
+	__le32 freq;
+	uint8_t bandwidth;
+	__le16 tps;
+	uint8_t flags;
+} __attribute__((packed));
+
+
+extern struct dvb_frontend *cinergyt2_fe_attach(struct dvb_usb_device *d);
+
+#endif /* _DVB_USB_CINERGYT2_H_ */
+
diff --git a/drivers/media/usb/dvb-usb/cxusb.c b/drivers/media/usb/dvb-usb/cxusb.c
new file mode 100644
index 0000000..5b51ed7
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/cxusb.c
@@ -0,0 +1,2203 @@
+/* DVB USB compliant linux driver for Conexant USB reference design.
+ *
+ * The Conexant reference design I saw on their website was only for analogue
+ * capturing (using the cx25842). The box I took to write this driver (reverse
+ * engineered) is the one labeled Medion MD95700. In addition to the cx25842
+ * for analogue capturing it also has a cx22702 DVB-T demodulator on the main
+ * board. Besides it has a atiremote (X10) and a USB2.0 hub onboard.
+ *
+ * Maybe it is a little bit premature to call this driver cxusb, but I assume
+ * the USB protocol is identical or at least inherited from the reference
+ * design, so it can be reused for the "analogue-only" device (if it will
+ * appear at all).
+ *
+ * TODO: Use the cx25840-driver for the analogue part
+ *
+ * Copyright (C) 2005 Patrick Boettcher (patrick.boettcher@posteo.de)
+ * Copyright (C) 2006 Michael Krufky (mkrufky@linuxtv.org)
+ * Copyright (C) 2006, 2007 Chris Pascoe (c.pascoe@itee.uq.edu.au)
+ *
+ *   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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#include <media/tuner.h>
+#include <linux/vmalloc.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+
+#include "cxusb.h"
+
+#include "cx22702.h"
+#include "lgdt330x.h"
+#include "mt352.h"
+#include "mt352_priv.h"
+#include "zl10353.h"
+#include "tuner-xc2028.h"
+#include "tuner-simple.h"
+#include "mxl5005s.h"
+#include "max2165.h"
+#include "dib7000p.h"
+#include "dib0070.h"
+#include "lgs8gxx.h"
+#include "atbm8830.h"
+#include "si2168.h"
+#include "si2157.h"
+
+/* debug */
+static int dvb_usb_cxusb_debug;
+module_param_named(debug, dvb_usb_cxusb_debug, int, 0644);
+MODULE_PARM_DESC(debug, "set debugging level (1=rc (or-able))." DVB_USB_DEBUG_STATUS);
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+#define deb_info(args...)   dprintk(dvb_usb_cxusb_debug, 0x03, args)
+#define deb_i2c(args...)    dprintk(dvb_usb_cxusb_debug, 0x02, args)
+
+static int cxusb_ctrl_msg(struct dvb_usb_device *d,
+			  u8 cmd, const u8 *wbuf, int wlen, u8 *rbuf, int rlen)
+{
+	struct cxusb_state *st = d->priv;
+	int ret;
+
+	if (1 + wlen > MAX_XFER_SIZE) {
+		warn("i2c wr: len=%d is too big!\n", wlen);
+		return -EOPNOTSUPP;
+	}
+
+	if (rlen > MAX_XFER_SIZE) {
+		warn("i2c rd: len=%d is too big!\n", rlen);
+		return -EOPNOTSUPP;
+	}
+
+	mutex_lock(&d->data_mutex);
+	st->data[0] = cmd;
+	memcpy(&st->data[1], wbuf, wlen);
+	ret = dvb_usb_generic_rw(d, st->data, 1 + wlen, st->data, rlen, 0);
+	if (!ret && rbuf && rlen)
+		memcpy(rbuf, st->data, rlen);
+
+	mutex_unlock(&d->data_mutex);
+	return ret;
+}
+
+/* GPIO */
+static void cxusb_gpio_tuner(struct dvb_usb_device *d, int onoff)
+{
+	struct cxusb_state *st = d->priv;
+	u8 o[2], i;
+
+	if (st->gpio_write_state[GPIO_TUNER] == onoff)
+		return;
+
+	o[0] = GPIO_TUNER;
+	o[1] = onoff;
+	cxusb_ctrl_msg(d, CMD_GPIO_WRITE, o, 2, &i, 1);
+
+	if (i != 0x01)
+		deb_info("gpio_write failed.\n");
+
+	st->gpio_write_state[GPIO_TUNER] = onoff;
+}
+
+static int cxusb_bluebird_gpio_rw(struct dvb_usb_device *d, u8 changemask,
+				 u8 newval)
+{
+	u8 o[2], gpio_state;
+	int rc;
+
+	o[0] = 0xff & ~changemask;	/* mask of bits to keep */
+	o[1] = newval & changemask;	/* new values for bits  */
+
+	rc = cxusb_ctrl_msg(d, CMD_BLUEBIRD_GPIO_RW, o, 2, &gpio_state, 1);
+	if (rc < 0 || (gpio_state & changemask) != (newval & changemask))
+		deb_info("bluebird_gpio_write failed.\n");
+
+	return rc < 0 ? rc : gpio_state;
+}
+
+static void cxusb_bluebird_gpio_pulse(struct dvb_usb_device *d, u8 pin, int low)
+{
+	cxusb_bluebird_gpio_rw(d, pin, low ? 0 : pin);
+	msleep(5);
+	cxusb_bluebird_gpio_rw(d, pin, low ? pin : 0);
+}
+
+static void cxusb_nano2_led(struct dvb_usb_device *d, int onoff)
+{
+	cxusb_bluebird_gpio_rw(d, 0x40, onoff ? 0 : 0x40);
+}
+
+static int cxusb_d680_dmb_gpio_tuner(struct dvb_usb_device *d,
+		u8 addr, int onoff)
+{
+	u8  o[2] = {addr, onoff};
+	u8  i;
+	int rc;
+
+	rc = cxusb_ctrl_msg(d, CMD_GPIO_WRITE, o, 2, &i, 1);
+
+	if (rc < 0)
+		return rc;
+	if (i == 0x01)
+		return 0;
+	else {
+		deb_info("gpio_write failed.\n");
+		return -EIO;
+	}
+}
+
+/* I2C */
+static int cxusb_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[],
+			  int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	int ret;
+	int i;
+
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	for (i = 0; i < num; i++) {
+
+		if (le16_to_cpu(d->udev->descriptor.idVendor) == USB_VID_MEDION)
+			switch (msg[i].addr) {
+			case 0x63:
+				cxusb_gpio_tuner(d, 0);
+				break;
+			default:
+				cxusb_gpio_tuner(d, 1);
+				break;
+			}
+
+		if (msg[i].flags & I2C_M_RD) {
+			/* read only */
+			u8 obuf[3], ibuf[MAX_XFER_SIZE];
+
+			if (1 + msg[i].len > sizeof(ibuf)) {
+				warn("i2c rd: len=%d is too big!\n",
+				     msg[i].len);
+				ret = -EOPNOTSUPP;
+				goto unlock;
+			}
+			obuf[0] = 0;
+			obuf[1] = msg[i].len;
+			obuf[2] = msg[i].addr;
+			if (cxusb_ctrl_msg(d, CMD_I2C_READ,
+					   obuf, 3,
+					   ibuf, 1+msg[i].len) < 0) {
+				warn("i2c read failed");
+				break;
+			}
+			memcpy(msg[i].buf, &ibuf[1], msg[i].len);
+		} else if (i+1 < num && (msg[i+1].flags & I2C_M_RD) &&
+			   msg[i].addr == msg[i+1].addr) {
+			/* write to then read from same address */
+			u8 obuf[MAX_XFER_SIZE], ibuf[MAX_XFER_SIZE];
+
+			if (3 + msg[i].len > sizeof(obuf)) {
+				warn("i2c wr: len=%d is too big!\n",
+				     msg[i].len);
+				ret = -EOPNOTSUPP;
+				goto unlock;
+			}
+			if (1 + msg[i + 1].len > sizeof(ibuf)) {
+				warn("i2c rd: len=%d is too big!\n",
+				     msg[i + 1].len);
+				ret = -EOPNOTSUPP;
+				goto unlock;
+			}
+			obuf[0] = msg[i].len;
+			obuf[1] = msg[i+1].len;
+			obuf[2] = msg[i].addr;
+			memcpy(&obuf[3], msg[i].buf, msg[i].len);
+
+			if (cxusb_ctrl_msg(d, CMD_I2C_READ,
+					   obuf, 3+msg[i].len,
+					   ibuf, 1+msg[i+1].len) < 0)
+				break;
+
+			if (ibuf[0] != 0x08)
+				deb_i2c("i2c read may have failed\n");
+
+			memcpy(msg[i+1].buf, &ibuf[1], msg[i+1].len);
+
+			i++;
+		} else {
+			/* write only */
+			u8 obuf[MAX_XFER_SIZE], ibuf;
+
+			if (2 + msg[i].len > sizeof(obuf)) {
+				warn("i2c wr: len=%d is too big!\n",
+				     msg[i].len);
+				ret = -EOPNOTSUPP;
+				goto unlock;
+			}
+			obuf[0] = msg[i].addr;
+			obuf[1] = msg[i].len;
+			memcpy(&obuf[2], msg[i].buf, msg[i].len);
+
+			if (cxusb_ctrl_msg(d, CMD_I2C_WRITE, obuf,
+					   2+msg[i].len, &ibuf,1) < 0)
+				break;
+			if (ibuf != 0x08)
+				deb_i2c("i2c write may have failed\n");
+		}
+	}
+
+	if (i == num)
+		ret = num;
+	else
+		ret = -EREMOTEIO;
+
+unlock:
+	mutex_unlock(&d->i2c_mutex);
+	return ret;
+}
+
+static u32 cxusb_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm cxusb_i2c_algo = {
+	.master_xfer   = cxusb_i2c_xfer,
+	.functionality = cxusb_i2c_func,
+};
+
+static int cxusb_power_ctrl(struct dvb_usb_device *d, int onoff)
+{
+	u8 b = 0;
+	if (onoff)
+		return cxusb_ctrl_msg(d, CMD_POWER_ON, &b, 1, NULL, 0);
+	else
+		return cxusb_ctrl_msg(d, CMD_POWER_OFF, &b, 1, NULL, 0);
+}
+
+static int cxusb_aver_power_ctrl(struct dvb_usb_device *d, int onoff)
+{
+	int ret;
+	if (!onoff)
+		return cxusb_ctrl_msg(d, CMD_POWER_OFF, NULL, 0, NULL, 0);
+	if (d->state == DVB_USB_STATE_INIT &&
+	    usb_set_interface(d->udev, 0, 0) < 0)
+		err("set interface failed");
+	do {} while (!(ret = cxusb_ctrl_msg(d, CMD_POWER_ON, NULL, 0, NULL, 0)) &&
+		   !(ret = cxusb_ctrl_msg(d, 0x15, NULL, 0, NULL, 0)) &&
+		   !(ret = cxusb_ctrl_msg(d, 0x17, NULL, 0, NULL, 0)) && 0);
+	if (!ret) {
+		/* FIXME: We don't know why, but we need to configure the
+		 * lgdt3303 with the register settings below on resume */
+		int i;
+		u8 buf;
+		static const u8 bufs[] = {
+			0x0e, 0x2, 0x00, 0x7f,
+			0x0e, 0x2, 0x02, 0xfe,
+			0x0e, 0x2, 0x02, 0x01,
+			0x0e, 0x2, 0x00, 0x03,
+			0x0e, 0x2, 0x0d, 0x40,
+			0x0e, 0x2, 0x0e, 0x87,
+			0x0e, 0x2, 0x0f, 0x8e,
+			0x0e, 0x2, 0x10, 0x01,
+			0x0e, 0x2, 0x14, 0xd7,
+			0x0e, 0x2, 0x47, 0x88,
+		};
+		msleep(20);
+		for (i = 0; i < ARRAY_SIZE(bufs); i += 4 / sizeof(u8)) {
+			ret = cxusb_ctrl_msg(d, CMD_I2C_WRITE,
+					     bufs+i, 4, &buf, 1);
+			if (ret)
+				break;
+			if (buf != 0x8)
+				return -EREMOTEIO;
+		}
+	}
+	return ret;
+}
+
+static int cxusb_bluebird_power_ctrl(struct dvb_usb_device *d, int onoff)
+{
+	u8 b = 0;
+	if (onoff)
+		return cxusb_ctrl_msg(d, CMD_POWER_ON, &b, 1, NULL, 0);
+	else
+		return 0;
+}
+
+static int cxusb_nano2_power_ctrl(struct dvb_usb_device *d, int onoff)
+{
+	int rc = 0;
+
+	rc = cxusb_power_ctrl(d, onoff);
+	if (!onoff)
+		cxusb_nano2_led(d, 0);
+
+	return rc;
+}
+
+static int cxusb_d680_dmb_power_ctrl(struct dvb_usb_device *d, int onoff)
+{
+	int ret;
+	u8  b;
+	ret = cxusb_power_ctrl(d, onoff);
+	if (!onoff)
+		return ret;
+
+	msleep(128);
+	cxusb_ctrl_msg(d, CMD_DIGITAL, NULL, 0, &b, 1);
+	msleep(100);
+	return ret;
+}
+
+static int cxusb_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff)
+{
+	u8 buf[2] = { 0x03, 0x00 };
+	if (onoff)
+		cxusb_ctrl_msg(adap->dev, CMD_STREAMING_ON, buf, 2, NULL, 0);
+	else
+		cxusb_ctrl_msg(adap->dev, CMD_STREAMING_OFF, NULL, 0, NULL, 0);
+
+	return 0;
+}
+
+static int cxusb_aver_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff)
+{
+	if (onoff)
+		cxusb_ctrl_msg(adap->dev, CMD_AVER_STREAM_ON, NULL, 0, NULL, 0);
+	else
+		cxusb_ctrl_msg(adap->dev, CMD_AVER_STREAM_OFF,
+			       NULL, 0, NULL, 0);
+	return 0;
+}
+
+static int cxusb_read_status(struct dvb_frontend *fe,
+				  enum fe_status *status)
+{
+	struct dvb_usb_adapter *adap = (struct dvb_usb_adapter *)fe->dvb->priv;
+	struct cxusb_state *state = (struct cxusb_state *)adap->dev->priv;
+	int ret;
+
+	ret = state->fe_read_status(fe, status);
+
+	/* it need resync slave fifo when signal change from unlock to lock.*/
+	if ((*status & FE_HAS_LOCK) && (!state->last_lock)) {
+		mutex_lock(&state->stream_mutex);
+		cxusb_streaming_ctrl(adap, 1);
+		mutex_unlock(&state->stream_mutex);
+	}
+
+	state->last_lock = (*status & FE_HAS_LOCK) ? 1 : 0;
+	return ret;
+}
+
+static void cxusb_d680_dmb_drain_message(struct dvb_usb_device *d)
+{
+	int       ep = d->props.generic_bulk_ctrl_endpoint;
+	const int timeout = 100;
+	const int junk_len = 32;
+	u8        *junk;
+	int       rd_count;
+
+	/* Discard remaining data in video pipe */
+	junk = kmalloc(junk_len, GFP_KERNEL);
+	if (!junk)
+		return;
+	while (1) {
+		if (usb_bulk_msg(d->udev,
+			usb_rcvbulkpipe(d->udev, ep),
+			junk, junk_len, &rd_count, timeout) < 0)
+			break;
+		if (!rd_count)
+			break;
+	}
+	kfree(junk);
+}
+
+static void cxusb_d680_dmb_drain_video(struct dvb_usb_device *d)
+{
+	struct usb_data_stream_properties *p = &d->props.adapter[0].fe[0].stream;
+	const int timeout = 100;
+	const int junk_len = p->u.bulk.buffersize;
+	u8        *junk;
+	int       rd_count;
+
+	/* Discard remaining data in video pipe */
+	junk = kmalloc(junk_len, GFP_KERNEL);
+	if (!junk)
+		return;
+	while (1) {
+		if (usb_bulk_msg(d->udev,
+			usb_rcvbulkpipe(d->udev, p->endpoint),
+			junk, junk_len, &rd_count, timeout) < 0)
+			break;
+		if (!rd_count)
+			break;
+	}
+	kfree(junk);
+}
+
+static int cxusb_d680_dmb_streaming_ctrl(
+		struct dvb_usb_adapter *adap, int onoff)
+{
+	if (onoff) {
+		u8 buf[2] = { 0x03, 0x00 };
+		cxusb_d680_dmb_drain_video(adap->dev);
+		return cxusb_ctrl_msg(adap->dev, CMD_STREAMING_ON,
+			buf, sizeof(buf), NULL, 0);
+	} else {
+		int ret = cxusb_ctrl_msg(adap->dev,
+			CMD_STREAMING_OFF, NULL, 0, NULL, 0);
+		return ret;
+	}
+}
+
+static int cxusb_rc_query(struct dvb_usb_device *d)
+{
+	u8 ircode[4];
+
+	cxusb_ctrl_msg(d, CMD_GET_IR_CODE, NULL, 0, ircode, 4);
+
+	if (ircode[2] || ircode[3])
+		rc_keydown(d->rc_dev, RC_PROTO_NEC,
+			   RC_SCANCODE_NEC(~ircode[2] & 0xff, ircode[3]), 0);
+	return 0;
+}
+
+static int cxusb_bluebird2_rc_query(struct dvb_usb_device *d)
+{
+	u8 ircode[4];
+	struct i2c_msg msg = { .addr = 0x6b, .flags = I2C_M_RD,
+			       .buf = ircode, .len = 4 };
+
+	if (cxusb_i2c_xfer(&d->i2c_adap, &msg, 1) != 1)
+		return 0;
+
+	if (ircode[1] || ircode[2])
+		rc_keydown(d->rc_dev, RC_PROTO_NEC,
+			   RC_SCANCODE_NEC(~ircode[1] & 0xff, ircode[2]), 0);
+	return 0;
+}
+
+static int cxusb_d680_dmb_rc_query(struct dvb_usb_device *d)
+{
+	u8 ircode[2];
+
+	if (cxusb_ctrl_msg(d, 0x10, NULL, 0, ircode, 2) < 0)
+		return 0;
+
+	if (ircode[0] || ircode[1])
+		rc_keydown(d->rc_dev, RC_PROTO_UNKNOWN,
+			   RC_SCANCODE_RC5(ircode[0], ircode[1]), 0);
+	return 0;
+}
+
+static int cxusb_dee1601_demod_init(struct dvb_frontend* fe)
+{
+	static u8 clock_config []  = { CLOCK_CTL,  0x38, 0x28 };
+	static u8 reset []         = { RESET,      0x80 };
+	static u8 adc_ctl_1_cfg [] = { ADC_CTL_1,  0x40 };
+	static u8 agc_cfg []       = { AGC_TARGET, 0x28, 0x20 };
+	static u8 gpp_ctl_cfg []   = { GPP_CTL,    0x33 };
+	static u8 capt_range_cfg[] = { CAPT_RANGE, 0x32 };
+
+	mt352_write(fe, clock_config,   sizeof(clock_config));
+	udelay(200);
+	mt352_write(fe, reset,          sizeof(reset));
+	mt352_write(fe, adc_ctl_1_cfg,  sizeof(adc_ctl_1_cfg));
+
+	mt352_write(fe, agc_cfg,        sizeof(agc_cfg));
+	mt352_write(fe, gpp_ctl_cfg,    sizeof(gpp_ctl_cfg));
+	mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg));
+
+	return 0;
+}
+
+static int cxusb_mt352_demod_init(struct dvb_frontend* fe)
+{	/* used in both lgz201 and th7579 */
+	static u8 clock_config []  = { CLOCK_CTL,  0x38, 0x29 };
+	static u8 reset []         = { RESET,      0x80 };
+	static u8 adc_ctl_1_cfg [] = { ADC_CTL_1,  0x40 };
+	static u8 agc_cfg []       = { AGC_TARGET, 0x24, 0x20 };
+	static u8 gpp_ctl_cfg []   = { GPP_CTL,    0x33 };
+	static u8 capt_range_cfg[] = { CAPT_RANGE, 0x32 };
+
+	mt352_write(fe, clock_config,   sizeof(clock_config));
+	udelay(200);
+	mt352_write(fe, reset,          sizeof(reset));
+	mt352_write(fe, adc_ctl_1_cfg,  sizeof(adc_ctl_1_cfg));
+
+	mt352_write(fe, agc_cfg,        sizeof(agc_cfg));
+	mt352_write(fe, gpp_ctl_cfg,    sizeof(gpp_ctl_cfg));
+	mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg));
+	return 0;
+}
+
+static struct cx22702_config cxusb_cx22702_config = {
+	.demod_address = 0x63,
+	.output_mode = CX22702_PARALLEL_OUTPUT,
+};
+
+static struct lgdt330x_config cxusb_lgdt3303_config = {
+	.demod_chip    = LGDT3303,
+};
+
+static struct lgdt330x_config cxusb_aver_lgdt3303_config = {
+	.demod_chip          = LGDT3303,
+	.clock_polarity_flip = 2,
+};
+
+static struct mt352_config cxusb_dee1601_config = {
+	.demod_address = 0x0f,
+	.demod_init    = cxusb_dee1601_demod_init,
+};
+
+static struct zl10353_config cxusb_zl10353_dee1601_config = {
+	.demod_address = 0x0f,
+	.parallel_ts = 1,
+};
+
+static struct mt352_config cxusb_mt352_config = {
+	/* used in both lgz201 and th7579 */
+	.demod_address = 0x0f,
+	.demod_init    = cxusb_mt352_demod_init,
+};
+
+static struct zl10353_config cxusb_zl10353_xc3028_config = {
+	.demod_address = 0x0f,
+	.if2 = 45600,
+	.no_tuner = 1,
+	.parallel_ts = 1,
+};
+
+static struct zl10353_config cxusb_zl10353_xc3028_config_no_i2c_gate = {
+	.demod_address = 0x0f,
+	.if2 = 45600,
+	.no_tuner = 1,
+	.parallel_ts = 1,
+	.disable_i2c_gate_ctrl = 1,
+};
+
+static struct mt352_config cxusb_mt352_xc3028_config = {
+	.demod_address = 0x0f,
+	.if2 = 4560,
+	.no_tuner = 1,
+	.demod_init = cxusb_mt352_demod_init,
+};
+
+/* FIXME: needs tweaking */
+static struct mxl5005s_config aver_a868r_tuner = {
+	.i2c_address     = 0x63,
+	.if_freq         = 6000000UL,
+	.xtal_freq       = CRYSTAL_FREQ_16000000HZ,
+	.agc_mode        = MXL_SINGLE_AGC,
+	.tracking_filter = MXL_TF_C,
+	.rssi_enable     = MXL_RSSI_ENABLE,
+	.cap_select      = MXL_CAP_SEL_ENABLE,
+	.div_out         = MXL_DIV_OUT_4,
+	.clock_out       = MXL_CLOCK_OUT_DISABLE,
+	.output_load     = MXL5005S_IF_OUTPUT_LOAD_200_OHM,
+	.top		 = MXL5005S_TOP_25P2,
+	.mod_mode        = MXL_DIGITAL_MODE,
+	.if_mode         = MXL_ZERO_IF,
+	.AgcMasterByte   = 0x00,
+};
+
+/* FIXME: needs tweaking */
+static struct mxl5005s_config d680_dmb_tuner = {
+	.i2c_address     = 0x63,
+	.if_freq         = 36125000UL,
+	.xtal_freq       = CRYSTAL_FREQ_16000000HZ,
+	.agc_mode        = MXL_SINGLE_AGC,
+	.tracking_filter = MXL_TF_C,
+	.rssi_enable     = MXL_RSSI_ENABLE,
+	.cap_select      = MXL_CAP_SEL_ENABLE,
+	.div_out         = MXL_DIV_OUT_4,
+	.clock_out       = MXL_CLOCK_OUT_DISABLE,
+	.output_load     = MXL5005S_IF_OUTPUT_LOAD_200_OHM,
+	.top		 = MXL5005S_TOP_25P2,
+	.mod_mode        = MXL_DIGITAL_MODE,
+	.if_mode         = MXL_ZERO_IF,
+	.AgcMasterByte   = 0x00,
+};
+
+static struct max2165_config mygica_d689_max2165_cfg = {
+	.i2c_address = 0x60,
+	.osc_clk = 20
+};
+
+/* Callbacks for DVB USB */
+static int cxusb_fmd1216me_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	dvb_attach(simple_tuner_attach, adap->fe_adap[0].fe,
+		   &adap->dev->i2c_adap, 0x61,
+		   TUNER_PHILIPS_FMD1216ME_MK3);
+	return 0;
+}
+
+static int cxusb_dee1601_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	dvb_attach(dvb_pll_attach, adap->fe_adap[0].fe, 0x61,
+		   NULL, DVB_PLL_THOMSON_DTT7579);
+	return 0;
+}
+
+static int cxusb_lgz201_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	dvb_attach(dvb_pll_attach, adap->fe_adap[0].fe, 0x61, NULL, DVB_PLL_LG_Z201);
+	return 0;
+}
+
+static int cxusb_dtt7579_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	dvb_attach(dvb_pll_attach, adap->fe_adap[0].fe, 0x60,
+		   NULL, DVB_PLL_THOMSON_DTT7579);
+	return 0;
+}
+
+static int cxusb_lgh064f_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	dvb_attach(simple_tuner_attach, adap->fe_adap[0].fe,
+		   &adap->dev->i2c_adap, 0x61, TUNER_LG_TDVS_H06XF);
+	return 0;
+}
+
+static int dvico_bluebird_xc2028_callback(void *ptr, int component,
+					  int command, int arg)
+{
+	struct dvb_usb_adapter *adap = ptr;
+	struct dvb_usb_device *d = adap->dev;
+
+	switch (command) {
+	case XC2028_TUNER_RESET:
+		deb_info("%s: XC2028_TUNER_RESET %d\n", __func__, arg);
+		cxusb_bluebird_gpio_pulse(d, 0x01, 1);
+		break;
+	case XC2028_RESET_CLK:
+		deb_info("%s: XC2028_RESET_CLK %d\n", __func__, arg);
+		break;
+	case XC2028_I2C_FLUSH:
+		break;
+	default:
+		deb_info("%s: unknown command %d, arg %d\n", __func__,
+			 command, arg);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int cxusb_dvico_xc3028_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_frontend	 *fe;
+	struct xc2028_config	  cfg = {
+		.i2c_adap  = &adap->dev->i2c_adap,
+		.i2c_addr  = 0x61,
+	};
+	static struct xc2028_ctrl ctl = {
+		.fname       = XC2028_DEFAULT_FIRMWARE,
+		.max_len     = 64,
+		.demod       = XC3028_FE_ZARLINK456,
+	};
+
+	/* FIXME: generalize & move to common area */
+	adap->fe_adap[0].fe->callback = dvico_bluebird_xc2028_callback;
+
+	fe = dvb_attach(xc2028_attach, adap->fe_adap[0].fe, &cfg);
+	if (fe == NULL || fe->ops.tuner_ops.set_config == NULL)
+		return -EIO;
+
+	fe->ops.tuner_ops.set_config(fe, &ctl);
+
+	return 0;
+}
+
+static int cxusb_mxl5003s_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	dvb_attach(mxl5005s_attach, adap->fe_adap[0].fe,
+		   &adap->dev->i2c_adap, &aver_a868r_tuner);
+	return 0;
+}
+
+static int cxusb_d680_dmb_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_frontend *fe;
+	fe = dvb_attach(mxl5005s_attach, adap->fe_adap[0].fe,
+			&adap->dev->i2c_adap, &d680_dmb_tuner);
+	return (fe == NULL) ? -EIO : 0;
+}
+
+static int cxusb_mygica_d689_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_frontend *fe;
+	fe = dvb_attach(max2165_attach, adap->fe_adap[0].fe,
+			&adap->dev->i2c_adap, &mygica_d689_max2165_cfg);
+	return (fe == NULL) ? -EIO : 0;
+}
+
+static int cxusb_cx22702_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	u8 b;
+	if (usb_set_interface(adap->dev->udev, 0, 6) < 0)
+		err("set interface failed");
+
+	cxusb_ctrl_msg(adap->dev, CMD_DIGITAL, NULL, 0, &b, 1);
+
+	adap->fe_adap[0].fe = dvb_attach(cx22702_attach, &cxusb_cx22702_config,
+					 &adap->dev->i2c_adap);
+	if ((adap->fe_adap[0].fe) != NULL)
+		return 0;
+
+	return -EIO;
+}
+
+static int cxusb_lgdt3303_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	if (usb_set_interface(adap->dev->udev, 0, 7) < 0)
+		err("set interface failed");
+
+	cxusb_ctrl_msg(adap->dev, CMD_DIGITAL, NULL, 0, NULL, 0);
+
+	adap->fe_adap[0].fe = dvb_attach(lgdt330x_attach,
+					 &cxusb_lgdt3303_config,
+					 0x0e,
+					 &adap->dev->i2c_adap);
+	if ((adap->fe_adap[0].fe) != NULL)
+		return 0;
+
+	return -EIO;
+}
+
+static int cxusb_aver_lgdt3303_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	adap->fe_adap[0].fe = dvb_attach(lgdt330x_attach,
+					 &cxusb_aver_lgdt3303_config,
+					 0x0e,
+					 &adap->dev->i2c_adap);
+	if (adap->fe_adap[0].fe != NULL)
+		return 0;
+
+	return -EIO;
+}
+
+static int cxusb_mt352_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	/* used in both lgz201 and th7579 */
+	if (usb_set_interface(adap->dev->udev, 0, 0) < 0)
+		err("set interface failed");
+
+	cxusb_ctrl_msg(adap->dev, CMD_DIGITAL, NULL, 0, NULL, 0);
+
+	adap->fe_adap[0].fe = dvb_attach(mt352_attach, &cxusb_mt352_config,
+					 &adap->dev->i2c_adap);
+	if ((adap->fe_adap[0].fe) != NULL)
+		return 0;
+
+	return -EIO;
+}
+
+static int cxusb_dee1601_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	if (usb_set_interface(adap->dev->udev, 0, 0) < 0)
+		err("set interface failed");
+
+	cxusb_ctrl_msg(adap->dev, CMD_DIGITAL, NULL, 0, NULL, 0);
+
+	adap->fe_adap[0].fe = dvb_attach(mt352_attach, &cxusb_dee1601_config,
+					 &adap->dev->i2c_adap);
+	if ((adap->fe_adap[0].fe) != NULL)
+		return 0;
+
+	adap->fe_adap[0].fe = dvb_attach(zl10353_attach,
+					 &cxusb_zl10353_dee1601_config,
+					 &adap->dev->i2c_adap);
+	if ((adap->fe_adap[0].fe) != NULL)
+		return 0;
+
+	return -EIO;
+}
+
+static int cxusb_dualdig4_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	u8 ircode[4];
+	int i;
+	struct i2c_msg msg = { .addr = 0x6b, .flags = I2C_M_RD,
+			       .buf = ircode, .len = 4 };
+
+	if (usb_set_interface(adap->dev->udev, 0, 1) < 0)
+		err("set interface failed");
+
+	cxusb_ctrl_msg(adap->dev, CMD_DIGITAL, NULL, 0, NULL, 0);
+
+	/* reset the tuner and demodulator */
+	cxusb_bluebird_gpio_rw(adap->dev, 0x04, 0);
+	cxusb_bluebird_gpio_pulse(adap->dev, 0x01, 1);
+	cxusb_bluebird_gpio_pulse(adap->dev, 0x02, 1);
+
+	adap->fe_adap[0].fe =
+		dvb_attach(zl10353_attach,
+			   &cxusb_zl10353_xc3028_config_no_i2c_gate,
+			   &adap->dev->i2c_adap);
+	if ((adap->fe_adap[0].fe) == NULL)
+		return -EIO;
+
+	/* try to determine if there is no IR decoder on the I2C bus */
+	for (i = 0; adap->dev->props.rc.core.rc_codes && i < 5; i++) {
+		msleep(20);
+		if (cxusb_i2c_xfer(&adap->dev->i2c_adap, &msg, 1) != 1)
+			goto no_IR;
+		if (ircode[0] == 0 && ircode[1] == 0)
+			continue;
+		if (ircode[2] + ircode[3] != 0xff) {
+no_IR:
+			adap->dev->props.rc.core.rc_codes = NULL;
+			info("No IR receiver detected on this device.");
+			break;
+		}
+	}
+
+	return 0;
+}
+
+static struct dibx000_agc_config dib7070_agc_config = {
+	.band_caps = BAND_UHF | BAND_VHF | BAND_LBAND | BAND_SBAND,
+
+	/*
+	 * P_agc_use_sd_mod1=0, P_agc_use_sd_mod2=0, P_agc_freq_pwm_div=5,
+	 * P_agc_inv_pwm1=0, P_agc_inv_pwm2=0, P_agc_inh_dc_rv_est=0,
+	 * P_agc_time_est=3, P_agc_freeze=0, P_agc_nb_est=5, P_agc_write=0
+	 */
+	.setup = (0 << 15) | (0 << 14) | (5 << 11) | (0 << 10) | (0 << 9) |
+		 (0 << 8) | (3 << 5) | (0 << 4) | (5 << 1) | (0 << 0),
+	.inv_gain = 600,
+	.time_stabiliz = 10,
+	.alpha_level = 0,
+	.thlock = 118,
+	.wbd_inv = 0,
+	.wbd_ref = 3530,
+	.wbd_sel = 1,
+	.wbd_alpha = 5,
+	.agc1_max = 65535,
+	.agc1_min = 0,
+	.agc2_max = 65535,
+	.agc2_min = 0,
+	.agc1_pt1 = 0,
+	.agc1_pt2 = 40,
+	.agc1_pt3 = 183,
+	.agc1_slope1 = 206,
+	.agc1_slope2 = 255,
+	.agc2_pt1 = 72,
+	.agc2_pt2 = 152,
+	.agc2_slope1 = 88,
+	.agc2_slope2 = 90,
+	.alpha_mant = 17,
+	.alpha_exp = 27,
+	.beta_mant = 23,
+	.beta_exp = 51,
+	.perform_agc_softsplit = 0,
+};
+
+static struct dibx000_bandwidth_config dib7070_bw_config_12_mhz = {
+	.internal = 60000,
+	.sampling = 15000,
+	.pll_prediv = 1,
+	.pll_ratio = 20,
+	.pll_range = 3,
+	.pll_reset = 1,
+	.pll_bypass = 0,
+	.enable_refdiv = 0,
+	.bypclk_div = 0,
+	.IO_CLK_en_core = 1,
+	.ADClkSrc = 1,
+	.modulo = 2,
+	/* refsel, sel, freq_15k */
+	.sad_cfg = (3 << 14) | (1 << 12) | (524 << 0),
+	.ifreq = (0 << 25) | 0,
+	.timf = 20452225,
+	.xtal_hz = 12000000,
+};
+
+static struct dib7000p_config cxusb_dualdig4_rev2_config = {
+	.output_mode = OUTMODE_MPEG2_PAR_GATED_CLK,
+	.output_mpeg2_in_188_bytes = 1,
+
+	.agc_config_count = 1,
+	.agc = &dib7070_agc_config,
+	.bw  = &dib7070_bw_config_12_mhz,
+	.tuner_is_baseband = 1,
+	.spur_protect = 1,
+
+	.gpio_dir = 0xfcef,
+	.gpio_val = 0x0110,
+
+	.gpio_pwm_pos = DIB7000P_GPIO_DEFAULT_PWM_POS,
+
+	.hostbus_diversity = 1,
+};
+
+struct dib0700_adapter_state {
+	int (*set_param_save)(struct dvb_frontend *);
+	struct dib7000p_ops dib7000p_ops;
+};
+
+static int cxusb_dualdig4_rev2_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_adapter_state *state = adap->priv;
+
+	if (usb_set_interface(adap->dev->udev, 0, 1) < 0)
+		err("set interface failed");
+
+	cxusb_ctrl_msg(adap->dev, CMD_DIGITAL, NULL, 0, NULL, 0);
+
+	cxusb_bluebird_gpio_pulse(adap->dev, 0x02, 1);
+
+	if (!dvb_attach(dib7000p_attach, &state->dib7000p_ops))
+		return -ENODEV;
+
+	if (state->dib7000p_ops.i2c_enumeration(&adap->dev->i2c_adap, 1, 18,
+				       &cxusb_dualdig4_rev2_config) < 0) {
+		printk(KERN_WARNING "Unable to enumerate dib7000p\n");
+		return -ENODEV;
+	}
+
+	adap->fe_adap[0].fe = state->dib7000p_ops.init(&adap->dev->i2c_adap, 0x80,
+					      &cxusb_dualdig4_rev2_config);
+	if (adap->fe_adap[0].fe == NULL)
+		return -EIO;
+
+	return 0;
+}
+
+static int dib7070_tuner_reset(struct dvb_frontend *fe, int onoff)
+{
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	return state->dib7000p_ops.set_gpio(fe, 8, 0, !onoff);
+}
+
+static int dib7070_tuner_sleep(struct dvb_frontend *fe, int onoff)
+{
+	return 0;
+}
+
+static struct dib0070_config dib7070p_dib0070_config = {
+	.i2c_address = DEFAULT_DIB0070_I2C_ADDRESS,
+	.reset = dib7070_tuner_reset,
+	.sleep = dib7070_tuner_sleep,
+	.clock_khz = 12000,
+};
+
+static int dib7070_set_param_override(struct dvb_frontend *fe)
+{
+	struct dtv_frontend_properties *p = &fe->dtv_property_cache;
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	u16 offset;
+	u8 band = BAND_OF_FREQUENCY(p->frequency/1000);
+	switch (band) {
+	case BAND_VHF: offset = 950; break;
+	default:
+	case BAND_UHF: offset = 550; break;
+	}
+
+	state->dib7000p_ops.set_wbd_ref(fe, offset + dib0070_wbd_offset(fe));
+
+	return state->set_param_save(fe);
+}
+
+static int cxusb_dualdig4_rev2_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_adapter_state *st = adap->priv;
+	struct i2c_adapter *tun_i2c;
+
+	/*
+	 * No need to call dvb7000p_attach here, as it was called
+	 * already, as frontend_attach method is called first, and
+	 * tuner_attach is only called on sucess.
+	 */
+	tun_i2c = st->dib7000p_ops.get_i2c_master(adap->fe_adap[0].fe,
+					DIBX000_I2C_INTERFACE_TUNER, 1);
+
+	if (dvb_attach(dib0070_attach, adap->fe_adap[0].fe, tun_i2c,
+	    &dib7070p_dib0070_config) == NULL)
+		return -ENODEV;
+
+	st->set_param_save = adap->fe_adap[0].fe->ops.tuner_ops.set_params;
+	adap->fe_adap[0].fe->ops.tuner_ops.set_params = dib7070_set_param_override;
+	return 0;
+}
+
+static int cxusb_nano2_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	if (usb_set_interface(adap->dev->udev, 0, 1) < 0)
+		err("set interface failed");
+
+	cxusb_ctrl_msg(adap->dev, CMD_DIGITAL, NULL, 0, NULL, 0);
+
+	/* reset the tuner and demodulator */
+	cxusb_bluebird_gpio_rw(adap->dev, 0x04, 0);
+	cxusb_bluebird_gpio_pulse(adap->dev, 0x01, 1);
+	cxusb_bluebird_gpio_pulse(adap->dev, 0x02, 1);
+
+	adap->fe_adap[0].fe = dvb_attach(zl10353_attach,
+					 &cxusb_zl10353_xc3028_config,
+					 &adap->dev->i2c_adap);
+	if ((adap->fe_adap[0].fe) != NULL)
+		return 0;
+
+	adap->fe_adap[0].fe = dvb_attach(mt352_attach,
+					 &cxusb_mt352_xc3028_config,
+					 &adap->dev->i2c_adap);
+	if ((adap->fe_adap[0].fe) != NULL)
+		return 0;
+
+	return -EIO;
+}
+
+static struct lgs8gxx_config d680_lgs8gl5_cfg = {
+	.prod = LGS8GXX_PROD_LGS8GL5,
+	.demod_address = 0x19,
+	.serial_ts = 0,
+	.ts_clk_pol = 0,
+	.ts_clk_gated = 1,
+	.if_clk_freq = 30400, /* 30.4 MHz */
+	.if_freq = 5725, /* 5.725 MHz */
+	.if_neg_center = 0,
+	.ext_adc = 0,
+	.adc_signed = 0,
+	.if_neg_edge = 0,
+};
+
+static int cxusb_d680_dmb_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *d = adap->dev;
+	int n;
+
+	/* Select required USB configuration */
+	if (usb_set_interface(d->udev, 0, 0) < 0)
+		err("set interface failed");
+
+	/* Unblock all USB pipes */
+	usb_clear_halt(d->udev,
+		usb_sndbulkpipe(d->udev, d->props.generic_bulk_ctrl_endpoint));
+	usb_clear_halt(d->udev,
+		usb_rcvbulkpipe(d->udev, d->props.generic_bulk_ctrl_endpoint));
+	usb_clear_halt(d->udev,
+		usb_rcvbulkpipe(d->udev, d->props.adapter[0].fe[0].stream.endpoint));
+
+	/* Drain USB pipes to avoid hang after reboot */
+	for (n = 0;  n < 5;  n++) {
+		cxusb_d680_dmb_drain_message(d);
+		cxusb_d680_dmb_drain_video(d);
+		msleep(200);
+	}
+
+	/* Reset the tuner */
+	if (cxusb_d680_dmb_gpio_tuner(d, 0x07, 0) < 0) {
+		err("clear tuner gpio failed");
+		return -EIO;
+	}
+	msleep(100);
+	if (cxusb_d680_dmb_gpio_tuner(d, 0x07, 1) < 0) {
+		err("set tuner gpio failed");
+		return -EIO;
+	}
+	msleep(100);
+
+	/* Attach frontend */
+	adap->fe_adap[0].fe = dvb_attach(lgs8gxx_attach, &d680_lgs8gl5_cfg, &d->i2c_adap);
+	if (adap->fe_adap[0].fe == NULL)
+		return -EIO;
+
+	return 0;
+}
+
+static struct atbm8830_config mygica_d689_atbm8830_cfg = {
+	.prod = ATBM8830_PROD_8830,
+	.demod_address = 0x40,
+	.serial_ts = 0,
+	.ts_sampling_edge = 1,
+	.ts_clk_gated = 0,
+	.osc_clk_freq = 30400, /* in kHz */
+	.if_freq = 0, /* zero IF */
+	.zif_swap_iq = 1,
+	.agc_min = 0x2E,
+	.agc_max = 0x90,
+	.agc_hold_loop = 0,
+};
+
+static int cxusb_mygica_d689_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *d = adap->dev;
+
+	/* Select required USB configuration */
+	if (usb_set_interface(d->udev, 0, 0) < 0)
+		err("set interface failed");
+
+	/* Unblock all USB pipes */
+	usb_clear_halt(d->udev,
+		usb_sndbulkpipe(d->udev, d->props.generic_bulk_ctrl_endpoint));
+	usb_clear_halt(d->udev,
+		usb_rcvbulkpipe(d->udev, d->props.generic_bulk_ctrl_endpoint));
+	usb_clear_halt(d->udev,
+		usb_rcvbulkpipe(d->udev, d->props.adapter[0].fe[0].stream.endpoint));
+
+
+	/* Reset the tuner */
+	if (cxusb_d680_dmb_gpio_tuner(d, 0x07, 0) < 0) {
+		err("clear tuner gpio failed");
+		return -EIO;
+	}
+	msleep(100);
+	if (cxusb_d680_dmb_gpio_tuner(d, 0x07, 1) < 0) {
+		err("set tuner gpio failed");
+		return -EIO;
+	}
+	msleep(100);
+
+	/* Attach frontend */
+	adap->fe_adap[0].fe = dvb_attach(atbm8830_attach, &mygica_d689_atbm8830_cfg,
+		&d->i2c_adap);
+	if (adap->fe_adap[0].fe == NULL)
+		return -EIO;
+
+	return 0;
+}
+
+static int cxusb_mygica_t230_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *d = adap->dev;
+	struct cxusb_state *st = d->priv;
+	struct i2c_adapter *adapter;
+	struct i2c_client *client_demod;
+	struct i2c_client *client_tuner;
+	struct i2c_board_info info;
+	struct si2168_config si2168_config;
+	struct si2157_config si2157_config;
+
+	/* Select required USB configuration */
+	if (usb_set_interface(d->udev, 0, 0) < 0)
+		err("set interface failed");
+
+	/* Unblock all USB pipes */
+	usb_clear_halt(d->udev,
+		usb_sndbulkpipe(d->udev, d->props.generic_bulk_ctrl_endpoint));
+	usb_clear_halt(d->udev,
+		usb_rcvbulkpipe(d->udev, d->props.generic_bulk_ctrl_endpoint));
+	usb_clear_halt(d->udev,
+		usb_rcvbulkpipe(d->udev, d->props.adapter[0].fe[0].stream.endpoint));
+
+	/* attach frontend */
+	si2168_config.i2c_adapter = &adapter;
+	si2168_config.fe = &adap->fe_adap[0].fe;
+	si2168_config.ts_mode = SI2168_TS_PARALLEL;
+	si2168_config.ts_clock_inv = 1;
+	memset(&info, 0, sizeof(struct i2c_board_info));
+	strlcpy(info.type, "si2168", I2C_NAME_SIZE);
+	info.addr = 0x64;
+	info.platform_data = &si2168_config;
+	request_module(info.type);
+	client_demod = i2c_new_device(&d->i2c_adap, &info);
+	if (client_demod == NULL || client_demod->dev.driver == NULL)
+		return -ENODEV;
+
+	if (!try_module_get(client_demod->dev.driver->owner)) {
+		i2c_unregister_device(client_demod);
+		return -ENODEV;
+	}
+
+	st->i2c_client_demod = client_demod;
+
+	/* attach tuner */
+	memset(&si2157_config, 0, sizeof(si2157_config));
+	si2157_config.fe = adap->fe_adap[0].fe;
+	si2157_config.if_port = 1;
+	memset(&info, 0, sizeof(struct i2c_board_info));
+	strlcpy(info.type, "si2157", I2C_NAME_SIZE);
+	info.addr = 0x60;
+	info.platform_data = &si2157_config;
+	request_module(info.type);
+	client_tuner = i2c_new_device(adapter, &info);
+	if (client_tuner == NULL || client_tuner->dev.driver == NULL) {
+		module_put(client_demod->dev.driver->owner);
+		i2c_unregister_device(client_demod);
+		return -ENODEV;
+	}
+	if (!try_module_get(client_tuner->dev.driver->owner)) {
+		i2c_unregister_device(client_tuner);
+		module_put(client_demod->dev.driver->owner);
+		i2c_unregister_device(client_demod);
+		return -ENODEV;
+	}
+
+	st->i2c_client_tuner = client_tuner;
+
+	/* hook fe: need to resync the slave fifo when signal locks. */
+	mutex_init(&st->stream_mutex);
+	st->last_lock = 0;
+	st->fe_read_status = adap->fe_adap[0].fe->ops.read_status;
+	adap->fe_adap[0].fe->ops.read_status = cxusb_read_status;
+
+	return 0;
+}
+
+/*
+ * DViCO has shipped two devices with the same USB ID, but only one of them
+ * needs a firmware download.  Check the device class details to see if they
+ * have non-default values to decide whether the device is actually cold or
+ * not, and forget a match if it turns out we selected the wrong device.
+ */
+static int bluebird_fx2_identify_state(struct usb_device *udev,
+				       struct dvb_usb_device_properties *props,
+				       struct dvb_usb_device_description **desc,
+				       int *cold)
+{
+	int wascold = *cold;
+
+	*cold = udev->descriptor.bDeviceClass == 0xff &&
+		udev->descriptor.bDeviceSubClass == 0xff &&
+		udev->descriptor.bDeviceProtocol == 0xff;
+
+	if (*cold && !wascold)
+		*desc = NULL;
+
+	return 0;
+}
+
+/*
+ * DViCO bluebird firmware needs the "warm" product ID to be patched into the
+ * firmware file before download.
+ */
+
+static const int dvico_firmware_id_offsets[] = { 6638, 3204 };
+static int bluebird_patch_dvico_firmware_download(struct usb_device *udev,
+						  const struct firmware *fw)
+{
+	int pos;
+
+	for (pos = 0; pos < ARRAY_SIZE(dvico_firmware_id_offsets); pos++) {
+		int idoff = dvico_firmware_id_offsets[pos];
+
+		if (fw->size < idoff + 4)
+			continue;
+
+		if (fw->data[idoff] == (USB_VID_DVICO & 0xff) &&
+		    fw->data[idoff + 1] == USB_VID_DVICO >> 8) {
+			struct firmware new_fw;
+			u8 *new_fw_data = vmalloc(fw->size);
+			int ret;
+
+			if (!new_fw_data)
+				return -ENOMEM;
+
+			memcpy(new_fw_data, fw->data, fw->size);
+			new_fw.size = fw->size;
+			new_fw.data = new_fw_data;
+
+			new_fw_data[idoff + 2] =
+				le16_to_cpu(udev->descriptor.idProduct) + 1;
+			new_fw_data[idoff + 3] =
+				le16_to_cpu(udev->descriptor.idProduct) >> 8;
+
+			ret = usb_cypress_load_firmware(udev, &new_fw,
+							CYPRESS_FX2);
+			vfree(new_fw_data);
+			return ret;
+		}
+	}
+
+	return -EINVAL;
+}
+
+/* DVB USB Driver stuff */
+static struct dvb_usb_device_properties cxusb_medion_properties;
+static struct dvb_usb_device_properties cxusb_bluebird_lgh064f_properties;
+static struct dvb_usb_device_properties cxusb_bluebird_dee1601_properties;
+static struct dvb_usb_device_properties cxusb_bluebird_lgz201_properties;
+static struct dvb_usb_device_properties cxusb_bluebird_dtt7579_properties;
+static struct dvb_usb_device_properties cxusb_bluebird_dualdig4_properties;
+static struct dvb_usb_device_properties cxusb_bluebird_dualdig4_rev2_properties;
+static struct dvb_usb_device_properties cxusb_bluebird_nano2_properties;
+static struct dvb_usb_device_properties cxusb_bluebird_nano2_needsfirmware_properties;
+static struct dvb_usb_device_properties cxusb_aver_a868r_properties;
+static struct dvb_usb_device_properties cxusb_d680_dmb_properties;
+static struct dvb_usb_device_properties cxusb_mygica_d689_properties;
+static struct dvb_usb_device_properties cxusb_mygica_t230_properties;
+
+static int cxusb_probe(struct usb_interface *intf,
+		       const struct usb_device_id *id)
+{
+	if (0 == dvb_usb_device_init(intf, &cxusb_medion_properties,
+				     THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, &cxusb_bluebird_lgh064f_properties,
+				     THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, &cxusb_bluebird_dee1601_properties,
+				     THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, &cxusb_bluebird_lgz201_properties,
+				     THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, &cxusb_bluebird_dtt7579_properties,
+				     THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, &cxusb_bluebird_dualdig4_properties,
+				     THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, &cxusb_bluebird_nano2_properties,
+				     THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf,
+				&cxusb_bluebird_nano2_needsfirmware_properties,
+				     THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, &cxusb_aver_a868r_properties,
+				     THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf,
+				     &cxusb_bluebird_dualdig4_rev2_properties,
+				     THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, &cxusb_d680_dmb_properties,
+				     THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, &cxusb_mygica_d689_properties,
+				     THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, &cxusb_mygica_t230_properties,
+				     THIS_MODULE, NULL, adapter_nr) ||
+	    0)
+		return 0;
+
+	return -EINVAL;
+}
+
+static void cxusb_disconnect(struct usb_interface *intf)
+{
+	struct dvb_usb_device *d = usb_get_intfdata(intf);
+	struct cxusb_state *st = d->priv;
+	struct i2c_client *client;
+
+	/* remove I2C client for tuner */
+	client = st->i2c_client_tuner;
+	if (client) {
+		module_put(client->dev.driver->owner);
+		i2c_unregister_device(client);
+	}
+
+	/* remove I2C client for demodulator */
+	client = st->i2c_client_demod;
+	if (client) {
+		module_put(client->dev.driver->owner);
+		i2c_unregister_device(client);
+	}
+
+	dvb_usb_device_exit(intf);
+}
+
+enum cxusb_table_index {
+	MEDION_MD95700,
+	DVICO_BLUEBIRD_LG064F_COLD,
+	DVICO_BLUEBIRD_LG064F_WARM,
+	DVICO_BLUEBIRD_DUAL_1_COLD,
+	DVICO_BLUEBIRD_DUAL_1_WARM,
+	DVICO_BLUEBIRD_LGZ201_COLD,
+	DVICO_BLUEBIRD_LGZ201_WARM,
+	DVICO_BLUEBIRD_TH7579_COLD,
+	DVICO_BLUEBIRD_TH7579_WARM,
+	DIGITALNOW_BLUEBIRD_DUAL_1_COLD,
+	DIGITALNOW_BLUEBIRD_DUAL_1_WARM,
+	DVICO_BLUEBIRD_DUAL_2_COLD,
+	DVICO_BLUEBIRD_DUAL_2_WARM,
+	DVICO_BLUEBIRD_DUAL_4,
+	DVICO_BLUEBIRD_DVB_T_NANO_2,
+	DVICO_BLUEBIRD_DVB_T_NANO_2_NFW_WARM,
+	AVERMEDIA_VOLAR_A868R,
+	DVICO_BLUEBIRD_DUAL_4_REV_2,
+	CONEXANT_D680_DMB,
+	MYGICA_D689,
+	MYGICA_T230,
+	NR__cxusb_table_index
+};
+
+static struct usb_device_id cxusb_table[NR__cxusb_table_index + 1] = {
+	[MEDION_MD95700] = {
+		USB_DEVICE(USB_VID_MEDION, USB_PID_MEDION_MD95700)
+	},
+	[DVICO_BLUEBIRD_LG064F_COLD] = {
+		USB_DEVICE(USB_VID_DVICO, USB_PID_DVICO_BLUEBIRD_LG064F_COLD)
+	},
+	[DVICO_BLUEBIRD_LG064F_WARM] = {
+		USB_DEVICE(USB_VID_DVICO, USB_PID_DVICO_BLUEBIRD_LG064F_WARM)
+	},
+	[DVICO_BLUEBIRD_DUAL_1_COLD] = {
+		USB_DEVICE(USB_VID_DVICO, USB_PID_DVICO_BLUEBIRD_DUAL_1_COLD)
+	},
+	[DVICO_BLUEBIRD_DUAL_1_WARM] = {
+		USB_DEVICE(USB_VID_DVICO, USB_PID_DVICO_BLUEBIRD_DUAL_1_WARM)
+	},
+	[DVICO_BLUEBIRD_LGZ201_COLD] = {
+		USB_DEVICE(USB_VID_DVICO, USB_PID_DVICO_BLUEBIRD_LGZ201_COLD)
+	},
+	[DVICO_BLUEBIRD_LGZ201_WARM] = {
+		USB_DEVICE(USB_VID_DVICO, USB_PID_DVICO_BLUEBIRD_LGZ201_WARM)
+	},
+	[DVICO_BLUEBIRD_TH7579_COLD] = {
+		USB_DEVICE(USB_VID_DVICO, USB_PID_DVICO_BLUEBIRD_TH7579_COLD)
+	},
+	[DVICO_BLUEBIRD_TH7579_WARM] = {
+		USB_DEVICE(USB_VID_DVICO, USB_PID_DVICO_BLUEBIRD_TH7579_WARM)
+	},
+	[DIGITALNOW_BLUEBIRD_DUAL_1_COLD] = {
+		USB_DEVICE(USB_VID_DVICO, USB_PID_DIGITALNOW_BLUEBIRD_DUAL_1_COLD)
+	},
+	[DIGITALNOW_BLUEBIRD_DUAL_1_WARM] = {
+		USB_DEVICE(USB_VID_DVICO, USB_PID_DIGITALNOW_BLUEBIRD_DUAL_1_WARM)
+	},
+	[DVICO_BLUEBIRD_DUAL_2_COLD] = {
+		USB_DEVICE(USB_VID_DVICO, USB_PID_DVICO_BLUEBIRD_DUAL_2_COLD)
+	},
+	[DVICO_BLUEBIRD_DUAL_2_WARM] = {
+		USB_DEVICE(USB_VID_DVICO, USB_PID_DVICO_BLUEBIRD_DUAL_2_WARM)
+	},
+	[DVICO_BLUEBIRD_DUAL_4] = {
+		USB_DEVICE(USB_VID_DVICO, USB_PID_DVICO_BLUEBIRD_DUAL_4)
+	},
+	[DVICO_BLUEBIRD_DVB_T_NANO_2] = {
+		USB_DEVICE(USB_VID_DVICO, USB_PID_DVICO_BLUEBIRD_DVB_T_NANO_2)
+	},
+	[DVICO_BLUEBIRD_DVB_T_NANO_2_NFW_WARM] = {
+		USB_DEVICE(USB_VID_DVICO, USB_PID_DVICO_BLUEBIRD_DVB_T_NANO_2_NFW_WARM)
+	},
+	[AVERMEDIA_VOLAR_A868R] = {
+		USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_VOLAR_A868R)
+	},
+	[DVICO_BLUEBIRD_DUAL_4_REV_2] = {
+		USB_DEVICE(USB_VID_DVICO, USB_PID_DVICO_BLUEBIRD_DUAL_4_REV_2)
+	},
+	[CONEXANT_D680_DMB] = {
+		USB_DEVICE(USB_VID_CONEXANT, USB_PID_CONEXANT_D680_DMB)
+	},
+	[MYGICA_D689] = {
+		USB_DEVICE(USB_VID_CONEXANT, USB_PID_MYGICA_D689)
+	},
+	[MYGICA_T230] = {
+		USB_DEVICE(USB_VID_CONEXANT, USB_PID_MYGICA_T230)
+	},
+	{}		/* Terminating entry */
+};
+MODULE_DEVICE_TABLE (usb, cxusb_table);
+
+static struct dvb_usb_device_properties cxusb_medion_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl = CYPRESS_FX2,
+
+	.size_of_priv     = sizeof(struct cxusb_state),
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.streaming_ctrl   = cxusb_streaming_ctrl,
+			.frontend_attach  = cxusb_cx22702_frontend_attach,
+			.tuner_attach     = cxusb_fmd1216me_tuner_attach,
+			/* parameter for the MPEG2-data transfer */
+					.stream = {
+						.type = USB_BULK,
+				.count = 5,
+				.endpoint = 0x02,
+				.u = {
+					.bulk = {
+						.buffersize = 8192,
+					}
+				}
+			},
+		}},
+		},
+	},
+	.power_ctrl       = cxusb_power_ctrl,
+
+	.i2c_algo         = &cxusb_i2c_algo,
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.num_device_descs = 1,
+	.devices = {
+		{   "Medion MD95700 (MDUSBTV-HYBRID)",
+			{ NULL },
+			{ &cxusb_table[MEDION_MD95700], NULL },
+		},
+	}
+};
+
+static struct dvb_usb_device_properties cxusb_bluebird_lgh064f_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl          = DEVICE_SPECIFIC,
+	.firmware          = "dvb-usb-bluebird-01.fw",
+	.download_firmware = bluebird_patch_dvico_firmware_download,
+	/* use usb alt setting 0 for EP4 transfer (dvb-t),
+	   use usb alt setting 7 for EP2 transfer (atsc) */
+
+	.size_of_priv     = sizeof(struct cxusb_state),
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.streaming_ctrl   = cxusb_streaming_ctrl,
+			.frontend_attach  = cxusb_lgdt3303_frontend_attach,
+			.tuner_attach     = cxusb_lgh064f_tuner_attach,
+
+			/* parameter for the MPEG2-data transfer */
+					.stream = {
+						.type = USB_BULK,
+				.count = 5,
+				.endpoint = 0x02,
+				.u = {
+					.bulk = {
+						.buffersize = 8192,
+					}
+				}
+			},
+		}},
+		},
+	},
+
+	.power_ctrl       = cxusb_bluebird_power_ctrl,
+
+	.i2c_algo         = &cxusb_i2c_algo,
+
+	.rc.core = {
+		.rc_interval	= 100,
+		.rc_codes	= RC_MAP_DVICO_PORTABLE,
+		.module_name	= KBUILD_MODNAME,
+		.rc_query	= cxusb_rc_query,
+		.allowed_protos = RC_PROTO_BIT_NEC,
+	},
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.num_device_descs = 1,
+	.devices = {
+		{   "DViCO FusionHDTV5 USB Gold",
+			{ &cxusb_table[DVICO_BLUEBIRD_LG064F_COLD], NULL },
+			{ &cxusb_table[DVICO_BLUEBIRD_LG064F_WARM], NULL },
+		},
+	}
+};
+
+static struct dvb_usb_device_properties cxusb_bluebird_dee1601_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl          = DEVICE_SPECIFIC,
+	.firmware          = "dvb-usb-bluebird-01.fw",
+	.download_firmware = bluebird_patch_dvico_firmware_download,
+	/* use usb alt setting 0 for EP4 transfer (dvb-t),
+	   use usb alt setting 7 for EP2 transfer (atsc) */
+
+	.size_of_priv     = sizeof(struct cxusb_state),
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.streaming_ctrl   = cxusb_streaming_ctrl,
+			.frontend_attach  = cxusb_dee1601_frontend_attach,
+			.tuner_attach     = cxusb_dee1601_tuner_attach,
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+				.count = 5,
+				.endpoint = 0x04,
+				.u = {
+					.bulk = {
+						.buffersize = 8192,
+					}
+				}
+			},
+		}},
+		},
+	},
+
+	.power_ctrl       = cxusb_bluebird_power_ctrl,
+
+	.i2c_algo         = &cxusb_i2c_algo,
+
+	.rc.core = {
+		.rc_interval	= 100,
+		.rc_codes	= RC_MAP_DVICO_MCE,
+		.module_name	= KBUILD_MODNAME,
+		.rc_query	= cxusb_rc_query,
+		.allowed_protos = RC_PROTO_BIT_NEC,
+	},
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.num_device_descs = 3,
+	.devices = {
+		{   "DViCO FusionHDTV DVB-T Dual USB",
+			{ &cxusb_table[DVICO_BLUEBIRD_DUAL_1_COLD], NULL },
+			{ &cxusb_table[DVICO_BLUEBIRD_DUAL_1_WARM], NULL },
+		},
+		{   "DigitalNow DVB-T Dual USB",
+			{ &cxusb_table[DIGITALNOW_BLUEBIRD_DUAL_1_COLD],  NULL },
+			{ &cxusb_table[DIGITALNOW_BLUEBIRD_DUAL_1_WARM], NULL },
+		},
+		{   "DViCO FusionHDTV DVB-T Dual Digital 2",
+			{ &cxusb_table[DVICO_BLUEBIRD_DUAL_2_COLD], NULL },
+			{ &cxusb_table[DVICO_BLUEBIRD_DUAL_2_WARM], NULL },
+		},
+	}
+};
+
+static struct dvb_usb_device_properties cxusb_bluebird_lgz201_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl          = DEVICE_SPECIFIC,
+	.firmware          = "dvb-usb-bluebird-01.fw",
+	.download_firmware = bluebird_patch_dvico_firmware_download,
+	/* use usb alt setting 0 for EP4 transfer (dvb-t),
+	   use usb alt setting 7 for EP2 transfer (atsc) */
+
+	.size_of_priv     = sizeof(struct cxusb_state),
+
+	.num_adapters = 2,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.streaming_ctrl   = cxusb_streaming_ctrl,
+			.frontend_attach  = cxusb_mt352_frontend_attach,
+			.tuner_attach     = cxusb_lgz201_tuner_attach,
+
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+				.count = 5,
+				.endpoint = 0x04,
+				.u = {
+					.bulk = {
+						.buffersize = 8192,
+					}
+				}
+			},
+		}},
+		},
+	},
+	.power_ctrl       = cxusb_bluebird_power_ctrl,
+
+	.i2c_algo         = &cxusb_i2c_algo,
+
+	.rc.core = {
+		.rc_interval	= 100,
+		.rc_codes	= RC_MAP_DVICO_PORTABLE,
+		.module_name	= KBUILD_MODNAME,
+		.rc_query	= cxusb_rc_query,
+		.allowed_protos = RC_PROTO_BIT_NEC,
+	},
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+	.num_device_descs = 1,
+	.devices = {
+		{   "DViCO FusionHDTV DVB-T USB (LGZ201)",
+			{ &cxusb_table[DVICO_BLUEBIRD_LGZ201_COLD], NULL },
+			{ &cxusb_table[DVICO_BLUEBIRD_LGZ201_WARM], NULL },
+		},
+	}
+};
+
+static struct dvb_usb_device_properties cxusb_bluebird_dtt7579_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl          = DEVICE_SPECIFIC,
+	.firmware          = "dvb-usb-bluebird-01.fw",
+	.download_firmware = bluebird_patch_dvico_firmware_download,
+	/* use usb alt setting 0 for EP4 transfer (dvb-t),
+	   use usb alt setting 7 for EP2 transfer (atsc) */
+
+	.size_of_priv     = sizeof(struct cxusb_state),
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.streaming_ctrl   = cxusb_streaming_ctrl,
+			.frontend_attach  = cxusb_mt352_frontend_attach,
+			.tuner_attach     = cxusb_dtt7579_tuner_attach,
+
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+				.count = 5,
+				.endpoint = 0x04,
+				.u = {
+					.bulk = {
+						.buffersize = 8192,
+					}
+				}
+			},
+		}},
+		},
+	},
+	.power_ctrl       = cxusb_bluebird_power_ctrl,
+
+	.i2c_algo         = &cxusb_i2c_algo,
+
+	.rc.core = {
+		.rc_interval	= 100,
+		.rc_codes	= RC_MAP_DVICO_PORTABLE,
+		.module_name	= KBUILD_MODNAME,
+		.rc_query	= cxusb_rc_query,
+		.allowed_protos = RC_PROTO_BIT_NEC,
+	},
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.num_device_descs = 1,
+	.devices = {
+		{   "DViCO FusionHDTV DVB-T USB (TH7579)",
+			{ &cxusb_table[DVICO_BLUEBIRD_TH7579_COLD], NULL },
+			{ &cxusb_table[DVICO_BLUEBIRD_TH7579_WARM], NULL },
+		},
+	}
+};
+
+static struct dvb_usb_device_properties cxusb_bluebird_dualdig4_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl         = CYPRESS_FX2,
+
+	.size_of_priv     = sizeof(struct cxusb_state),
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.streaming_ctrl   = cxusb_streaming_ctrl,
+			.frontend_attach  = cxusb_dualdig4_frontend_attach,
+			.tuner_attach     = cxusb_dvico_xc3028_tuner_attach,
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+				.count = 5,
+				.endpoint = 0x02,
+				.u = {
+					.bulk = {
+						.buffersize = 8192,
+					}
+				}
+			},
+		}},
+		},
+	},
+
+	.power_ctrl       = cxusb_power_ctrl,
+
+	.i2c_algo         = &cxusb_i2c_algo,
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.rc.core = {
+		.rc_interval	= 100,
+		.rc_codes	= RC_MAP_DVICO_MCE,
+		.module_name	= KBUILD_MODNAME,
+		.rc_query	= cxusb_bluebird2_rc_query,
+		.allowed_protos = RC_PROTO_BIT_NEC,
+	},
+
+	.num_device_descs = 1,
+	.devices = {
+		{   "DViCO FusionHDTV DVB-T Dual Digital 4",
+			{ NULL },
+			{ &cxusb_table[DVICO_BLUEBIRD_DUAL_4], NULL },
+		},
+	}
+};
+
+static struct dvb_usb_device_properties cxusb_bluebird_nano2_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl         = CYPRESS_FX2,
+	.identify_state   = bluebird_fx2_identify_state,
+
+	.size_of_priv     = sizeof(struct cxusb_state),
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.streaming_ctrl   = cxusb_streaming_ctrl,
+			.frontend_attach  = cxusb_nano2_frontend_attach,
+			.tuner_attach     = cxusb_dvico_xc3028_tuner_attach,
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+				.count = 5,
+				.endpoint = 0x02,
+				.u = {
+					.bulk = {
+						.buffersize = 8192,
+					}
+				}
+			},
+		}},
+		},
+	},
+
+	.power_ctrl       = cxusb_nano2_power_ctrl,
+
+	.i2c_algo         = &cxusb_i2c_algo,
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.rc.core = {
+		.rc_interval	= 100,
+		.rc_codes	= RC_MAP_DVICO_PORTABLE,
+		.module_name	= KBUILD_MODNAME,
+		.rc_query       = cxusb_bluebird2_rc_query,
+		.allowed_protos = RC_PROTO_BIT_NEC,
+	},
+
+	.num_device_descs = 1,
+	.devices = {
+		{   "DViCO FusionHDTV DVB-T NANO2",
+			{ NULL },
+			{ &cxusb_table[DVICO_BLUEBIRD_DVB_T_NANO_2], NULL },
+		},
+	}
+};
+
+static struct dvb_usb_device_properties cxusb_bluebird_nano2_needsfirmware_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl          = DEVICE_SPECIFIC,
+	.firmware          = "dvb-usb-bluebird-02.fw",
+	.download_firmware = bluebird_patch_dvico_firmware_download,
+	.identify_state    = bluebird_fx2_identify_state,
+
+	.size_of_priv      = sizeof(struct cxusb_state),
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.streaming_ctrl   = cxusb_streaming_ctrl,
+			.frontend_attach  = cxusb_nano2_frontend_attach,
+			.tuner_attach     = cxusb_dvico_xc3028_tuner_attach,
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+				.count = 5,
+				.endpoint = 0x02,
+				.u = {
+					.bulk = {
+						.buffersize = 8192,
+					}
+				}
+			},
+		}},
+		},
+	},
+
+	.power_ctrl       = cxusb_nano2_power_ctrl,
+
+	.i2c_algo         = &cxusb_i2c_algo,
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.rc.core = {
+		.rc_interval	= 100,
+		.rc_codes	= RC_MAP_DVICO_PORTABLE,
+		.module_name	= KBUILD_MODNAME,
+		.rc_query	= cxusb_rc_query,
+		.allowed_protos = RC_PROTO_BIT_NEC,
+	},
+
+	.num_device_descs = 1,
+	.devices = {
+		{   "DViCO FusionHDTV DVB-T NANO2 w/o firmware",
+			{ &cxusb_table[DVICO_BLUEBIRD_DVB_T_NANO_2], NULL },
+			{ &cxusb_table[DVICO_BLUEBIRD_DVB_T_NANO_2_NFW_WARM], NULL },
+		},
+	}
+};
+
+static struct dvb_usb_device_properties cxusb_aver_a868r_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl         = CYPRESS_FX2,
+
+	.size_of_priv     = sizeof(struct cxusb_state),
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.streaming_ctrl   = cxusb_aver_streaming_ctrl,
+			.frontend_attach  = cxusb_aver_lgdt3303_frontend_attach,
+			.tuner_attach     = cxusb_mxl5003s_tuner_attach,
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+				.count = 5,
+				.endpoint = 0x04,
+				.u = {
+					.bulk = {
+						.buffersize = 8192,
+					}
+				}
+			},
+		}},
+		},
+	},
+	.power_ctrl       = cxusb_aver_power_ctrl,
+
+	.i2c_algo         = &cxusb_i2c_algo,
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.num_device_descs = 1,
+	.devices = {
+		{   "AVerMedia AVerTVHD Volar (A868R)",
+			{ NULL },
+			{ &cxusb_table[AVERMEDIA_VOLAR_A868R], NULL },
+		},
+	}
+};
+
+static
+struct dvb_usb_device_properties cxusb_bluebird_dualdig4_rev2_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl         = CYPRESS_FX2,
+
+	.size_of_priv     = sizeof(struct cxusb_state),
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.size_of_priv    = sizeof(struct dib0700_adapter_state),
+		.num_frontends = 1,
+		.fe = {{
+			.streaming_ctrl  = cxusb_streaming_ctrl,
+			.frontend_attach = cxusb_dualdig4_rev2_frontend_attach,
+			.tuner_attach    = cxusb_dualdig4_rev2_tuner_attach,
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+				.count = 7,
+				.endpoint = 0x02,
+				.u = {
+					.bulk = {
+						.buffersize = 4096,
+					}
+				}
+			},
+		}},
+		},
+	},
+
+	.power_ctrl       = cxusb_bluebird_power_ctrl,
+
+	.i2c_algo         = &cxusb_i2c_algo,
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.rc.core = {
+		.rc_interval	= 100,
+		.rc_codes	= RC_MAP_DVICO_MCE,
+		.module_name	= KBUILD_MODNAME,
+		.rc_query	= cxusb_rc_query,
+		.allowed_protos = RC_PROTO_BIT_NEC,
+	},
+
+	.num_device_descs = 1,
+	.devices = {
+		{   "DViCO FusionHDTV DVB-T Dual Digital 4 (rev 2)",
+			{ NULL },
+			{ &cxusb_table[DVICO_BLUEBIRD_DUAL_4_REV_2], NULL },
+		},
+	}
+};
+
+static struct dvb_usb_device_properties cxusb_d680_dmb_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl         = CYPRESS_FX2,
+
+	.size_of_priv     = sizeof(struct cxusb_state),
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.streaming_ctrl   = cxusb_d680_dmb_streaming_ctrl,
+			.frontend_attach  = cxusb_d680_dmb_frontend_attach,
+			.tuner_attach     = cxusb_d680_dmb_tuner_attach,
+
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+				.count = 5,
+				.endpoint = 0x02,
+				.u = {
+					.bulk = {
+						.buffersize = 8192,
+					}
+				}
+			},
+		}},
+		},
+	},
+
+	.power_ctrl       = cxusb_d680_dmb_power_ctrl,
+
+	.i2c_algo         = &cxusb_i2c_algo,
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.rc.core = {
+		.rc_interval	= 100,
+		.rc_codes	= RC_MAP_TOTAL_MEDIA_IN_HAND_02,
+		.module_name	= KBUILD_MODNAME,
+		.rc_query       = cxusb_d680_dmb_rc_query,
+		.allowed_protos = RC_PROTO_BIT_UNKNOWN,
+	},
+
+	.num_device_descs = 1,
+	.devices = {
+		{
+			"Conexant DMB-TH Stick",
+			{ NULL },
+			{ &cxusb_table[CONEXANT_D680_DMB], NULL },
+		},
+	}
+};
+
+static struct dvb_usb_device_properties cxusb_mygica_d689_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl         = CYPRESS_FX2,
+
+	.size_of_priv     = sizeof(struct cxusb_state),
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.streaming_ctrl   = cxusb_d680_dmb_streaming_ctrl,
+			.frontend_attach  = cxusb_mygica_d689_frontend_attach,
+			.tuner_attach     = cxusb_mygica_d689_tuner_attach,
+
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+				.count = 5,
+				.endpoint = 0x02,
+				.u = {
+					.bulk = {
+						.buffersize = 8192,
+					}
+				}
+			},
+		}},
+		},
+	},
+
+	.power_ctrl       = cxusb_d680_dmb_power_ctrl,
+
+	.i2c_algo         = &cxusb_i2c_algo,
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.rc.core = {
+		.rc_interval	= 100,
+		.rc_codes	= RC_MAP_D680_DMB,
+		.module_name	= KBUILD_MODNAME,
+		.rc_query       = cxusb_d680_dmb_rc_query,
+		.allowed_protos = RC_PROTO_BIT_UNKNOWN,
+	},
+
+	.num_device_descs = 1,
+	.devices = {
+		{
+			"Mygica D689 DMB-TH",
+			{ NULL },
+			{ &cxusb_table[MYGICA_D689], NULL },
+		},
+	}
+};
+
+static struct dvb_usb_device_properties cxusb_mygica_t230_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl         = CYPRESS_FX2,
+
+	.size_of_priv     = sizeof(struct cxusb_state),
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.streaming_ctrl   = cxusb_streaming_ctrl,
+			.frontend_attach  = cxusb_mygica_t230_frontend_attach,
+
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+				.count = 5,
+				.endpoint = 0x02,
+				.u = {
+					.bulk = {
+						.buffersize = 8192,
+					}
+				}
+			},
+		} },
+		},
+	},
+
+	.power_ctrl       = cxusb_d680_dmb_power_ctrl,
+
+	.i2c_algo         = &cxusb_i2c_algo,
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.rc.core = {
+		.rc_interval	= 100,
+		.rc_codes	= RC_MAP_D680_DMB,
+		.module_name	= KBUILD_MODNAME,
+		.rc_query       = cxusb_d680_dmb_rc_query,
+		.allowed_protos = RC_PROTO_BIT_UNKNOWN,
+	},
+
+	.num_device_descs = 1,
+	.devices = {
+		{
+			"Mygica T230 DVB-T/T2/C",
+			{ NULL },
+			{ &cxusb_table[MYGICA_T230], NULL },
+		},
+	}
+};
+
+static struct usb_driver cxusb_driver = {
+	.name		= "dvb_usb_cxusb",
+	.probe		= cxusb_probe,
+	.disconnect     = cxusb_disconnect,
+	.id_table	= cxusb_table,
+};
+
+module_usb_driver(cxusb_driver);
+
+MODULE_AUTHOR("Patrick Boettcher <patrick.boettcher@posteo.de>");
+MODULE_AUTHOR("Michael Krufky <mkrufky@linuxtv.org>");
+MODULE_AUTHOR("Chris Pascoe <c.pascoe@itee.uq.edu.au>");
+MODULE_DESCRIPTION("Driver for Conexant USB2.0 hybrid reference design");
+MODULE_VERSION("1.0-alpha");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/cxusb.h b/drivers/media/usb/dvb-usb/cxusb.h
new file mode 100644
index 0000000..88f9b98
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/cxusb.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _DVB_USB_CXUSB_H_
+#define _DVB_USB_CXUSB_H_
+
+#define DVB_USB_LOG_PREFIX "cxusb"
+#include "dvb-usb.h"
+
+/* usb commands - some of it are guesses, don't have a reference yet */
+#define CMD_BLUEBIRD_GPIO_RW 0x05
+
+#define CMD_I2C_WRITE     0x08
+#define CMD_I2C_READ      0x09
+
+#define CMD_GPIO_READ     0x0d
+#define CMD_GPIO_WRITE    0x0e
+#define     GPIO_TUNER         0x02
+
+#define CMD_POWER_OFF     0xdc
+#define CMD_POWER_ON      0xde
+
+#define CMD_STREAMING_ON  0x36
+#define CMD_STREAMING_OFF 0x37
+
+#define CMD_AVER_STREAM_ON  0x18
+#define CMD_AVER_STREAM_OFF 0x19
+
+#define CMD_GET_IR_CODE   0x47
+
+#define CMD_ANALOG        0x50
+#define CMD_DIGITAL       0x51
+
+/* Max transfer size done by I2C transfer functions */
+#define MAX_XFER_SIZE  80
+
+struct cxusb_state {
+	u8 gpio_write_state[3];
+	struct i2c_client *i2c_client_demod;
+	struct i2c_client *i2c_client_tuner;
+
+	unsigned char data[MAX_XFER_SIZE];
+
+	struct mutex stream_mutex;
+	u8 last_lock;
+	int (*fe_read_status)(struct dvb_frontend *fe,
+		enum fe_status *status);
+};
+
+#endif
diff --git a/drivers/media/usb/dvb-usb/dib0700.h b/drivers/media/usb/dvb-usb/dib0700.h
new file mode 100644
index 0000000..3a9d4c2
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/dib0700.h
@@ -0,0 +1,77 @@
+/* Linux driver for devices based on the DiBcom DiB0700 USB bridge
+ *
+ *	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, version 2.
+ *
+ *  Copyright (C) 2005-6 DiBcom, SA
+ */
+#ifndef _DIB0700_H_
+#define _DIB0700_H_
+
+#define DVB_USB_LOG_PREFIX "dib0700"
+#include "dvb-usb.h"
+
+#include "dib07x0.h"
+
+extern int dvb_usb_dib0700_debug;
+#define deb_info(args...)   dprintk(dvb_usb_dib0700_debug,0x01,args)
+#define deb_fw(args...)     dprintk(dvb_usb_dib0700_debug,0x02,args)
+#define deb_fwdata(args...) dprintk(dvb_usb_dib0700_debug,0x04,args)
+#define deb_data(args...)   dprintk(dvb_usb_dib0700_debug,0x08,args)
+
+#define REQUEST_SET_USB_XFER_LEN    0x0 /* valid only for firmware version */
+					/* higher than 1.21 */
+#define REQUEST_I2C_READ            0x2
+#define REQUEST_I2C_WRITE           0x3
+#define REQUEST_POLL_RC             0x4 /* deprecated in firmware v1.20 */
+#define REQUEST_JUMPRAM             0x8
+#define REQUEST_SET_CLOCK           0xB
+#define REQUEST_SET_GPIO            0xC
+#define REQUEST_ENABLE_VIDEO        0xF
+	// 1 Byte: 4MSB(1 = enable streaming, 0 = disable streaming) 4LSB(Video Mode: 0 = MPEG2 188Bytes, 1 = Analog)
+	// 2 Byte: MPEG2 mode:  4MSB(1 = Master Mode, 0 = Slave Mode) 4LSB(Channel 1 = bit0, Channel 2 = bit1)
+	// 2 Byte: Analog mode: 4MSB(0 = 625 lines, 1 = 525 lines)    4LSB(     "                "           )
+#define REQUEST_SET_I2C_PARAM       0x10
+#define REQUEST_SET_RC              0x11
+#define REQUEST_NEW_I2C_READ        0x12
+#define REQUEST_NEW_I2C_WRITE       0x13
+#define REQUEST_GET_VERSION         0x15
+
+struct dib0700_state {
+	u8 channel_state;
+	u16 mt2060_if1[2];
+	u8 rc_toggle;
+	u8 rc_counter;
+	u8 is_dib7000pc;
+	u8 fw_use_new_i2c_api;
+	u8 disable_streaming_master_mode;
+	u32 fw_version;
+	u32 nb_packet_buffer_size;
+	int (*read_status)(struct dvb_frontend *, enum fe_status *);
+	int (*sleep)(struct dvb_frontend* fe);
+	u8 buf[255];
+	struct i2c_client *i2c_client_demod;
+	struct i2c_client *i2c_client_tuner;
+};
+
+extern int dib0700_get_version(struct dvb_usb_device *d, u32 *hwversion,
+			       u32 *romversion, u32 *ramversion, u32 *fwtype);
+extern int dib0700_set_gpio(struct dvb_usb_device *, enum dib07x0_gpios gpio, u8 gpio_dir, u8 gpio_val);
+extern int dib0700_ctrl_clock(struct dvb_usb_device *d, u32 clk_MHz, u8 clock_out_gp3);
+extern int dib0700_ctrl_rd(struct dvb_usb_device *d, u8 *tx, u8 txlen, u8 *rx, u8 rxlen);
+extern int dib0700_download_firmware(struct usb_device *udev, const struct firmware *fw);
+extern int dib0700_rc_setup(struct dvb_usb_device *d, struct usb_interface *intf);
+extern int dib0700_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff);
+extern struct i2c_algorithm dib0700_i2c_algo;
+extern int dib0700_identify_state(struct usb_device *udev, struct dvb_usb_device_properties *props,
+			struct dvb_usb_device_description **desc, int *cold);
+extern int dib0700_change_protocol(struct rc_dev *dev, u64 *rc_proto);
+extern int dib0700_set_i2c_speed(struct dvb_usb_device *d, u16 scl_kHz);
+
+extern int dib0700_device_count;
+extern int dvb_usb_dib0700_ir_proto;
+extern struct dvb_usb_device_properties dib0700_devices[];
+extern struct usb_device_id dib0700_usb_id_table[];
+
+#endif
diff --git a/drivers/media/usb/dvb-usb/dib0700_core.c b/drivers/media/usb/dvb-usb/dib0700_core.c
new file mode 100644
index 0000000..94bd176
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/dib0700_core.c
@@ -0,0 +1,951 @@
+/* Linux driver for devices based on the DiBcom DiB0700 USB bridge
+ *
+ *	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, version 2.
+ *
+ *  Copyright (C) 2005-6 DiBcom, SA
+ */
+#include "dib0700.h"
+
+/* debug */
+int dvb_usb_dib0700_debug;
+module_param_named(debug,dvb_usb_dib0700_debug, int, 0644);
+MODULE_PARM_DESC(debug, "set debugging level (1=info,2=fw,4=fwdata,8=data (or-able))." DVB_USB_DEBUG_STATUS);
+
+static int nb_packet_buffer_size = 21;
+module_param(nb_packet_buffer_size, int, 0644);
+MODULE_PARM_DESC(nb_packet_buffer_size,
+	"Set the dib0700 driver data buffer size. This parameter corresponds to the number of TS packets. The actual size of the data buffer corresponds to this parameter multiplied by 188 (default: 21)");
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+
+int dib0700_get_version(struct dvb_usb_device *d, u32 *hwversion,
+			u32 *romversion, u32 *ramversion, u32 *fwtype)
+{
+	struct dib0700_state *st = d->priv;
+	int ret;
+
+	if (mutex_lock_interruptible(&d->usb_mutex) < 0) {
+		err("could not acquire lock");
+		return -EINTR;
+	}
+
+	ret = usb_control_msg(d->udev, usb_rcvctrlpipe(d->udev, 0),
+				  REQUEST_GET_VERSION,
+				  USB_TYPE_VENDOR | USB_DIR_IN, 0, 0,
+				  st->buf, 16, USB_CTRL_GET_TIMEOUT);
+	if (hwversion != NULL)
+		*hwversion  = (st->buf[0] << 24)  | (st->buf[1] << 16)  |
+			(st->buf[2] << 8)  | st->buf[3];
+	if (romversion != NULL)
+		*romversion = (st->buf[4] << 24)  | (st->buf[5] << 16)  |
+			(st->buf[6] << 8)  | st->buf[7];
+	if (ramversion != NULL)
+		*ramversion = (st->buf[8] << 24)  | (st->buf[9] << 16)  |
+			(st->buf[10] << 8) | st->buf[11];
+	if (fwtype != NULL)
+		*fwtype     = (st->buf[12] << 24) | (st->buf[13] << 16) |
+			(st->buf[14] << 8) | st->buf[15];
+	mutex_unlock(&d->usb_mutex);
+	return ret;
+}
+
+/* expecting rx buffer: request data[0] data[1] ... data[2] */
+static int dib0700_ctrl_wr(struct dvb_usb_device *d, u8 *tx, u8 txlen)
+{
+	int status;
+
+	deb_data(">>> ");
+	debug_dump(tx, txlen, deb_data);
+
+	status = usb_control_msg(d->udev, usb_sndctrlpipe(d->udev,0),
+		tx[0], USB_TYPE_VENDOR | USB_DIR_OUT, 0, 0, tx, txlen,
+		USB_CTRL_GET_TIMEOUT);
+
+	if (status != txlen)
+		deb_data("ep 0 write error (status = %d, len: %d)\n",status,txlen);
+
+	return status < 0 ? status : 0;
+}
+
+/* expecting tx buffer: request data[0] ... data[n] (n <= 4) */
+int dib0700_ctrl_rd(struct dvb_usb_device *d, u8 *tx, u8 txlen, u8 *rx, u8 rxlen)
+{
+	u16 index, value;
+	int status;
+
+	if (txlen < 2) {
+		err("tx buffer length is smaller than 2. Makes no sense.");
+		return -EINVAL;
+	}
+	if (txlen > 4) {
+		err("tx buffer length is larger than 4. Not supported.");
+		return -EINVAL;
+	}
+
+	deb_data(">>> ");
+	debug_dump(tx,txlen,deb_data);
+
+	value = ((txlen - 2) << 8) | tx[1];
+	index = 0;
+	if (txlen > 2)
+		index |= (tx[2] << 8);
+	if (txlen > 3)
+		index |= tx[3];
+
+	status = usb_control_msg(d->udev, usb_rcvctrlpipe(d->udev,0), tx[0],
+			USB_TYPE_VENDOR | USB_DIR_IN, value, index, rx, rxlen,
+			USB_CTRL_GET_TIMEOUT);
+
+	if (status < 0)
+		deb_info("ep 0 read error (status = %d)\n",status);
+
+	deb_data("<<< ");
+	debug_dump(rx, rxlen, deb_data);
+
+	return status; /* length in case of success */
+}
+
+int dib0700_set_gpio(struct dvb_usb_device *d, enum dib07x0_gpios gpio, u8 gpio_dir, u8 gpio_val)
+{
+	struct dib0700_state *st = d->priv;
+	int ret;
+
+	if (mutex_lock_interruptible(&d->usb_mutex) < 0) {
+		err("could not acquire lock");
+		return -EINTR;
+	}
+
+	st->buf[0] = REQUEST_SET_GPIO;
+	st->buf[1] = gpio;
+	st->buf[2] = ((gpio_dir & 0x01) << 7) | ((gpio_val & 0x01) << 6);
+
+	ret = dib0700_ctrl_wr(d, st->buf, 3);
+
+	mutex_unlock(&d->usb_mutex);
+	return ret;
+}
+
+static int dib0700_set_usb_xfer_len(struct dvb_usb_device *d, u16 nb_ts_packets)
+{
+	struct dib0700_state *st = d->priv;
+	int ret;
+
+	if (st->fw_version >= 0x10201) {
+		if (mutex_lock_interruptible(&d->usb_mutex) < 0) {
+			err("could not acquire lock");
+			return -EINTR;
+		}
+
+		st->buf[0] = REQUEST_SET_USB_XFER_LEN;
+		st->buf[1] = (nb_ts_packets >> 8) & 0xff;
+		st->buf[2] = nb_ts_packets & 0xff;
+
+		deb_info("set the USB xfer len to %i Ts packet\n", nb_ts_packets);
+
+		ret = dib0700_ctrl_wr(d, st->buf, 3);
+		mutex_unlock(&d->usb_mutex);
+	} else {
+		deb_info("this firmware does not allow to change the USB xfer len\n");
+		ret = -EIO;
+	}
+
+	return ret;
+}
+
+/*
+ * I2C master xfer function (supported in 1.20 firmware)
+ */
+static int dib0700_i2c_xfer_new(struct i2c_adapter *adap, struct i2c_msg *msg,
+				int num)
+{
+	/* The new i2c firmware messages are more reliable and in particular
+	   properly support i2c read calls not preceded by a write */
+
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	struct dib0700_state *st = d->priv;
+	uint8_t bus_mode = 1;  /* 0=eeprom bus, 1=frontend bus */
+	uint8_t gen_mode = 0; /* 0=master i2c, 1=gpio i2c */
+	uint8_t en_start = 0;
+	uint8_t en_stop = 0;
+	int result, i;
+
+	/* Ensure nobody else hits the i2c bus while we're sending our
+	   sequence of messages, (such as the remote control thread) */
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EINTR;
+
+	for (i = 0; i < num; i++) {
+		if (i == 0) {
+			/* First message in the transaction */
+			en_start = 1;
+		} else if (!(msg[i].flags & I2C_M_NOSTART)) {
+			/* Device supports repeated-start */
+			en_start = 1;
+		} else {
+			/* Not the first packet and device doesn't support
+			   repeated start */
+			en_start = 0;
+		}
+		if (i == (num - 1)) {
+			/* Last message in the transaction */
+			en_stop = 1;
+		}
+
+		if (msg[i].flags & I2C_M_RD) {
+			/* Read request */
+			u16 index, value;
+			uint8_t i2c_dest;
+
+			i2c_dest = (msg[i].addr << 1);
+			value = ((en_start << 7) | (en_stop << 6) |
+				 (msg[i].len & 0x3F)) << 8 | i2c_dest;
+			/* I2C ctrl + FE bus; */
+			index = ((gen_mode << 6) & 0xC0) |
+				((bus_mode << 4) & 0x30);
+
+			result = usb_control_msg(d->udev,
+						 usb_rcvctrlpipe(d->udev, 0),
+						 REQUEST_NEW_I2C_READ,
+						 USB_TYPE_VENDOR | USB_DIR_IN,
+						 value, index, st->buf,
+						 msg[i].len,
+						 USB_CTRL_GET_TIMEOUT);
+			if (result < 0) {
+				deb_info("i2c read error (status = %d)\n", result);
+				goto unlock;
+			}
+
+			if (msg[i].len > sizeof(st->buf)) {
+				deb_info("buffer too small to fit %d bytes\n",
+					 msg[i].len);
+				result = -EIO;
+				goto unlock;
+			}
+
+			memcpy(msg[i].buf, st->buf, msg[i].len);
+
+			deb_data("<<< ");
+			debug_dump(msg[i].buf, msg[i].len, deb_data);
+
+		} else {
+			/* Write request */
+			if (mutex_lock_interruptible(&d->usb_mutex) < 0) {
+				err("could not acquire lock");
+				result = -EINTR;
+				goto unlock;
+			}
+			st->buf[0] = REQUEST_NEW_I2C_WRITE;
+			st->buf[1] = msg[i].addr << 1;
+			st->buf[2] = (en_start << 7) | (en_stop << 6) |
+				(msg[i].len & 0x3F);
+			/* I2C ctrl + FE bus; */
+			st->buf[3] = ((gen_mode << 6) & 0xC0) |
+				 ((bus_mode << 4) & 0x30);
+
+			if (msg[i].len > sizeof(st->buf) - 4) {
+				deb_info("i2c message to big: %d\n",
+					 msg[i].len);
+				mutex_unlock(&d->usb_mutex);
+				result = -EIO;
+				goto unlock;
+			}
+
+			/* The Actual i2c payload */
+			memcpy(&st->buf[4], msg[i].buf, msg[i].len);
+
+			deb_data(">>> ");
+			debug_dump(st->buf, msg[i].len + 4, deb_data);
+
+			result = usb_control_msg(d->udev,
+						 usb_sndctrlpipe(d->udev, 0),
+						 REQUEST_NEW_I2C_WRITE,
+						 USB_TYPE_VENDOR | USB_DIR_OUT,
+						 0, 0, st->buf, msg[i].len + 4,
+						 USB_CTRL_GET_TIMEOUT);
+			mutex_unlock(&d->usb_mutex);
+			if (result < 0) {
+				deb_info("i2c write error (status = %d)\n", result);
+				break;
+			}
+		}
+	}
+	result = i;
+
+unlock:
+	mutex_unlock(&d->i2c_mutex);
+	return result;
+}
+
+/*
+ * I2C master xfer function (pre-1.20 firmware)
+ */
+static int dib0700_i2c_xfer_legacy(struct i2c_adapter *adap,
+				   struct i2c_msg *msg, int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	struct dib0700_state *st = d->priv;
+	int i, len, result;
+
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EINTR;
+	if (mutex_lock_interruptible(&d->usb_mutex) < 0) {
+		err("could not acquire lock");
+		mutex_unlock(&d->i2c_mutex);
+		return -EINTR;
+	}
+
+	for (i = 0; i < num; i++) {
+		/* fill in the address */
+		st->buf[1] = msg[i].addr << 1;
+		/* fill the buffer */
+		if (msg[i].len > sizeof(st->buf) - 2) {
+			deb_info("i2c xfer to big: %d\n",
+				msg[i].len);
+			result = -EIO;
+			goto unlock;
+		}
+		memcpy(&st->buf[2], msg[i].buf, msg[i].len);
+
+		/* write/read request */
+		if (i+1 < num && (msg[i+1].flags & I2C_M_RD)) {
+			st->buf[0] = REQUEST_I2C_READ;
+			st->buf[1] |= 1;
+
+			/* special thing in the current firmware: when length is zero the read-failed */
+			len = dib0700_ctrl_rd(d, st->buf, msg[i].len + 2,
+					      st->buf, msg[i + 1].len);
+			if (len <= 0) {
+				deb_info("I2C read failed on address 0x%02x\n",
+						msg[i].addr);
+				result = -EIO;
+				goto unlock;
+			}
+
+			if (msg[i + 1].len > sizeof(st->buf)) {
+				deb_info("i2c xfer buffer to small for %d\n",
+					msg[i].len);
+				result = -EIO;
+				goto unlock;
+			}
+			memcpy(msg[i + 1].buf, st->buf, msg[i + 1].len);
+
+			msg[i+1].len = len;
+
+			i++;
+		} else {
+			st->buf[0] = REQUEST_I2C_WRITE;
+			result = dib0700_ctrl_wr(d, st->buf, msg[i].len + 2);
+			if (result < 0)
+				goto unlock;
+		}
+	}
+	result = i;
+unlock:
+	mutex_unlock(&d->usb_mutex);
+	mutex_unlock(&d->i2c_mutex);
+
+	return result;
+}
+
+static int dib0700_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msg,
+			    int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	struct dib0700_state *st = d->priv;
+
+	if (st->fw_use_new_i2c_api == 1) {
+		/* User running at least fw 1.20 */
+		return dib0700_i2c_xfer_new(adap, msg, num);
+	} else {
+		/* Use legacy calls */
+		return dib0700_i2c_xfer_legacy(adap, msg, num);
+	}
+}
+
+static u32 dib0700_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+struct i2c_algorithm dib0700_i2c_algo = {
+	.master_xfer   = dib0700_i2c_xfer,
+	.functionality = dib0700_i2c_func,
+};
+
+int dib0700_identify_state(struct usb_device *udev, struct dvb_usb_device_properties *props,
+			struct dvb_usb_device_description **desc, int *cold)
+{
+	s16 ret;
+	u8 *b;
+
+	b = kmalloc(16, GFP_KERNEL);
+	if (!b)
+		return	-ENOMEM;
+
+
+	ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+		REQUEST_GET_VERSION, USB_TYPE_VENDOR | USB_DIR_IN, 0, 0, b, 16, USB_CTRL_GET_TIMEOUT);
+
+	deb_info("FW GET_VERSION length: %d\n",ret);
+
+	*cold = ret <= 0;
+	deb_info("cold: %d\n", *cold);
+
+	kfree(b);
+	return 0;
+}
+
+static int dib0700_set_clock(struct dvb_usb_device *d, u8 en_pll,
+	u8 pll_src, u8 pll_range, u8 clock_gpio3, u16 pll_prediv,
+	u16 pll_loopdiv, u16 free_div, u16 dsuScaler)
+{
+	struct dib0700_state *st = d->priv;
+	int ret;
+
+	if (mutex_lock_interruptible(&d->usb_mutex) < 0) {
+		err("could not acquire lock");
+		return -EINTR;
+	}
+
+	st->buf[0] = REQUEST_SET_CLOCK;
+	st->buf[1] = (en_pll << 7) | (pll_src << 6) |
+		(pll_range << 5) | (clock_gpio3 << 4);
+	st->buf[2] = (pll_prediv >> 8)  & 0xff; /* MSB */
+	st->buf[3] =  pll_prediv        & 0xff; /* LSB */
+	st->buf[4] = (pll_loopdiv >> 8) & 0xff; /* MSB */
+	st->buf[5] =  pll_loopdiv       & 0xff; /* LSB */
+	st->buf[6] = (free_div >> 8)    & 0xff; /* MSB */
+	st->buf[7] =  free_div          & 0xff; /* LSB */
+	st->buf[8] = (dsuScaler >> 8)   & 0xff; /* MSB */
+	st->buf[9] =  dsuScaler         & 0xff; /* LSB */
+
+	ret = dib0700_ctrl_wr(d, st->buf, 10);
+	mutex_unlock(&d->usb_mutex);
+
+	return ret;
+}
+
+int dib0700_set_i2c_speed(struct dvb_usb_device *d, u16 scl_kHz)
+{
+	struct dib0700_state *st = d->priv;
+	u16 divider;
+	int ret;
+
+	if (scl_kHz == 0)
+		return -EINVAL;
+
+	if (mutex_lock_interruptible(&d->usb_mutex) < 0) {
+		err("could not acquire lock");
+		return -EINTR;
+	}
+
+	st->buf[0] = REQUEST_SET_I2C_PARAM;
+	divider = (u16) (30000 / scl_kHz);
+	st->buf[1] = 0;
+	st->buf[2] = (u8) (divider >> 8);
+	st->buf[3] = (u8) (divider & 0xff);
+	divider = (u16) (72000 / scl_kHz);
+	st->buf[4] = (u8) (divider >> 8);
+	st->buf[5] = (u8) (divider & 0xff);
+	divider = (u16) (72000 / scl_kHz); /* clock: 72MHz */
+	st->buf[6] = (u8) (divider >> 8);
+	st->buf[7] = (u8) (divider & 0xff);
+
+	deb_info("setting I2C speed: %04x %04x %04x (%d kHz).",
+		(st->buf[2] << 8) | (st->buf[3]), (st->buf[4] << 8) |
+		st->buf[5], (st->buf[6] << 8) | st->buf[7], scl_kHz);
+
+	ret = dib0700_ctrl_wr(d, st->buf, 8);
+	mutex_unlock(&d->usb_mutex);
+
+	return ret;
+}
+
+
+int dib0700_ctrl_clock(struct dvb_usb_device *d, u32 clk_MHz, u8 clock_out_gp3)
+{
+	switch (clk_MHz) {
+		case 72: dib0700_set_clock(d, 1, 0, 1, clock_out_gp3, 2, 24, 0, 0x4c); break;
+		default: return -EINVAL;
+	}
+	return 0;
+}
+
+static int dib0700_jumpram(struct usb_device *udev, u32 address)
+{
+	int ret = 0, actlen;
+	u8 *buf;
+
+	buf = kmalloc(8, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+	buf[0] = REQUEST_JUMPRAM;
+	buf[1] = 0;
+	buf[2] = 0;
+	buf[3] = 0;
+	buf[4] = (address >> 24) & 0xff;
+	buf[5] = (address >> 16) & 0xff;
+	buf[6] = (address >> 8)  & 0xff;
+	buf[7] =  address        & 0xff;
+
+	if ((ret = usb_bulk_msg(udev, usb_sndbulkpipe(udev, 0x01),buf,8,&actlen,1000)) < 0) {
+		deb_fw("jumpram to 0x%x failed\n",address);
+		goto out;
+	}
+	if (actlen != 8) {
+		deb_fw("jumpram to 0x%x failed\n",address);
+		ret = -EIO;
+		goto out;
+	}
+out:
+	kfree(buf);
+	return ret;
+}
+
+int dib0700_download_firmware(struct usb_device *udev, const struct firmware *fw)
+{
+	struct hexline hx;
+	int pos = 0, ret, act_len, i, adap_num;
+	u8 *buf;
+	u32 fw_version;
+
+	buf = kmalloc(260, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	while ((ret = dvb_usb_get_hexline(fw, &hx, &pos)) > 0) {
+		deb_fwdata("writing to address 0x%08x (buffer: 0x%02x %02x)\n",
+				hx.addr, hx.len, hx.chk);
+
+		buf[0] = hx.len;
+		buf[1] = (hx.addr >> 8) & 0xff;
+		buf[2] =  hx.addr       & 0xff;
+		buf[3] = hx.type;
+		memcpy(&buf[4],hx.data,hx.len);
+		buf[4+hx.len] = hx.chk;
+
+		ret = usb_bulk_msg(udev,
+			usb_sndbulkpipe(udev, 0x01),
+			buf,
+			hx.len + 5,
+			&act_len,
+			1000);
+
+		if (ret < 0) {
+			err("firmware download failed at %d with %d",pos,ret);
+			goto out;
+		}
+	}
+
+	if (ret == 0) {
+		/* start the firmware */
+		if ((ret = dib0700_jumpram(udev, 0x70000000)) == 0) {
+			info("firmware started successfully.");
+			msleep(500);
+		}
+	} else
+		ret = -EIO;
+
+	/* the number of ts packet has to be at least 1 */
+	if (nb_packet_buffer_size < 1)
+		nb_packet_buffer_size = 1;
+
+	/* get the firmware version */
+	usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+				  REQUEST_GET_VERSION,
+				  USB_TYPE_VENDOR | USB_DIR_IN, 0, 0,
+				  buf, 16, USB_CTRL_GET_TIMEOUT);
+	fw_version = (buf[8] << 24) | (buf[9] << 16) | (buf[10] << 8) | buf[11];
+
+	/* set the buffer size - DVB-USB is allocating URB buffers
+	 * only after the firwmare download was successful */
+	for (i = 0; i < dib0700_device_count; i++) {
+		for (adap_num = 0; adap_num < dib0700_devices[i].num_adapters;
+				adap_num++) {
+			if (fw_version >= 0x10201) {
+				dib0700_devices[i].adapter[adap_num].fe[0].stream.u.bulk.buffersize = 188*nb_packet_buffer_size;
+			} else {
+				/* for fw version older than 1.20.1,
+				 * the buffersize has to be n times 512 */
+				dib0700_devices[i].adapter[adap_num].fe[0].stream.u.bulk.buffersize = ((188*nb_packet_buffer_size+188/2)/512)*512;
+				if (dib0700_devices[i].adapter[adap_num].fe[0].stream.u.bulk.buffersize < 512)
+					dib0700_devices[i].adapter[adap_num].fe[0].stream.u.bulk.buffersize = 512;
+			}
+		}
+	}
+out:
+	kfree(buf);
+	return ret;
+}
+
+int dib0700_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff)
+{
+	struct dib0700_state *st = adap->dev->priv;
+	int ret;
+
+	if ((onoff != 0) && (st->fw_version >= 0x10201)) {
+		/* for firmware later than 1.20.1,
+		 * the USB xfer length can be set  */
+		ret = dib0700_set_usb_xfer_len(adap->dev,
+			st->nb_packet_buffer_size);
+		if (ret < 0) {
+			deb_info("can not set the USB xfer len\n");
+			return ret;
+		}
+	}
+
+	mutex_lock(&adap->dev->usb_mutex);
+
+	st->buf[0] = REQUEST_ENABLE_VIDEO;
+	/* this bit gives a kind of command,
+	 * rather than enabling something or not */
+	st->buf[1] = (onoff << 4) | 0x00;
+
+	if (st->disable_streaming_master_mode == 1)
+		st->buf[2] = 0x00;
+	else
+		st->buf[2] = 0x01 << 4; /* Master mode */
+
+	st->buf[3] = 0x00;
+
+	deb_info("modifying (%d) streaming state for %d\n", onoff, adap->id);
+
+	st->channel_state &= ~0x3;
+	if ((adap->fe_adap[0].stream.props.endpoint != 2)
+			&& (adap->fe_adap[0].stream.props.endpoint != 3)) {
+		deb_info("the endpoint number (%i) is not correct, use the adapter id instead", adap->fe_adap[0].stream.props.endpoint);
+		if (onoff)
+			st->channel_state |=	1 << (adap->id);
+		else
+			st->channel_state |=	1 << ~(adap->id);
+	} else {
+		if (onoff)
+			st->channel_state |=	1 << (adap->fe_adap[0].stream.props.endpoint-2);
+		else
+			st->channel_state |=	1 << (3-adap->fe_adap[0].stream.props.endpoint);
+	}
+
+	st->buf[2] |= st->channel_state;
+
+	deb_info("data for streaming: %x %x\n", st->buf[1], st->buf[2]);
+
+	ret = dib0700_ctrl_wr(adap->dev, st->buf, 4);
+	mutex_unlock(&adap->dev->usb_mutex);
+
+	return ret;
+}
+
+int dib0700_change_protocol(struct rc_dev *rc, u64 *rc_proto)
+{
+	struct dvb_usb_device *d = rc->priv;
+	struct dib0700_state *st = d->priv;
+	int new_proto, ret;
+
+	if (mutex_lock_interruptible(&d->usb_mutex) < 0) {
+		err("could not acquire lock");
+		return -EINTR;
+	}
+
+	st->buf[0] = REQUEST_SET_RC;
+	st->buf[1] = 0;
+	st->buf[2] = 0;
+
+	/* Set the IR mode */
+	if (*rc_proto & RC_PROTO_BIT_RC5) {
+		new_proto = 1;
+		*rc_proto = RC_PROTO_BIT_RC5;
+	} else if (*rc_proto & RC_PROTO_BIT_NEC) {
+		new_proto = 0;
+		*rc_proto = RC_PROTO_BIT_NEC;
+	} else if (*rc_proto & RC_PROTO_BIT_RC6_MCE) {
+		if (st->fw_version < 0x10200) {
+			ret = -EINVAL;
+			goto out;
+		}
+		new_proto = 2;
+		*rc_proto = RC_PROTO_BIT_RC6_MCE;
+	} else {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	st->buf[1] = new_proto;
+
+	ret = dib0700_ctrl_wr(d, st->buf, 3);
+	if (ret < 0) {
+		err("ir protocol setup failed");
+		goto out;
+	}
+
+	d->props.rc.core.protocol = *rc_proto;
+
+out:
+	mutex_unlock(&d->usb_mutex);
+	return ret;
+}
+
+/* This is the structure of the RC response packet starting in firmware 1.20 */
+struct dib0700_rc_response {
+	u8 report_id;
+	u8 data_state;
+	union {
+		struct {
+			u8 system;
+			u8 not_system;
+			u8 data;
+			u8 not_data;
+		} nec;
+		struct {
+			u8 not_used;
+			u8 system;
+			u8 data;
+			u8 not_data;
+		} rc5;
+	};
+};
+#define RC_MSG_SIZE_V1_20 6
+
+static void dib0700_rc_urb_completion(struct urb *purb)
+{
+	struct dvb_usb_device *d = purb->context;
+	struct dib0700_rc_response *poll_reply;
+	enum rc_proto protocol;
+	u32 keycode;
+	u8 toggle;
+
+	deb_info("%s()\n", __func__);
+	if (d->rc_dev == NULL) {
+		/* This will occur if disable_rc_polling=1 */
+		kfree(purb->transfer_buffer);
+		usb_free_urb(purb);
+		return;
+	}
+
+	poll_reply = purb->transfer_buffer;
+
+	if (purb->status < 0) {
+		deb_info("discontinuing polling\n");
+		kfree(purb->transfer_buffer);
+		usb_free_urb(purb);
+		return;
+	}
+
+	if (purb->actual_length != RC_MSG_SIZE_V1_20) {
+		deb_info("malformed rc msg size=%d\n", purb->actual_length);
+		goto resubmit;
+	}
+
+	deb_data("IR ID = %02X state = %02X System = %02X %02X Cmd = %02X %02X (len %d)\n",
+		 poll_reply->report_id, poll_reply->data_state,
+		 poll_reply->nec.system, poll_reply->nec.not_system,
+		 poll_reply->nec.data, poll_reply->nec.not_data,
+		 purb->actual_length);
+
+	switch (d->props.rc.core.protocol) {
+	case RC_PROTO_BIT_NEC:
+		toggle = 0;
+
+		/* NEC protocol sends repeat code as 0 0 0 FF */
+		if (poll_reply->nec.system     == 0x00 &&
+		    poll_reply->nec.not_system == 0x00 &&
+		    poll_reply->nec.data       == 0x00 &&
+		    poll_reply->nec.not_data   == 0xff) {
+			poll_reply->data_state = 2;
+			rc_repeat(d->rc_dev);
+			goto resubmit;
+		}
+
+		if ((poll_reply->nec.data ^ poll_reply->nec.not_data) != 0xff) {
+			deb_data("NEC32 protocol\n");
+			keycode = RC_SCANCODE_NEC32(poll_reply->nec.system     << 24 |
+						     poll_reply->nec.not_system << 16 |
+						     poll_reply->nec.data       << 8  |
+						     poll_reply->nec.not_data);
+			protocol = RC_PROTO_NEC32;
+		} else if ((poll_reply->nec.system ^ poll_reply->nec.not_system) != 0xff) {
+			deb_data("NEC extended protocol\n");
+			keycode = RC_SCANCODE_NECX(poll_reply->nec.system << 8 |
+						    poll_reply->nec.not_system,
+						    poll_reply->nec.data);
+
+			protocol = RC_PROTO_NECX;
+		} else {
+			deb_data("NEC normal protocol\n");
+			keycode = RC_SCANCODE_NEC(poll_reply->nec.system,
+						   poll_reply->nec.data);
+			protocol = RC_PROTO_NEC;
+		}
+
+		break;
+	default:
+		deb_data("RC5 protocol\n");
+		protocol = RC_PROTO_RC5;
+		toggle = poll_reply->report_id;
+		keycode = RC_SCANCODE_RC5(poll_reply->rc5.system, poll_reply->rc5.data);
+
+		if ((poll_reply->rc5.data ^ poll_reply->rc5.not_data) != 0xff) {
+			/* Key failed integrity check */
+			err("key failed integrity check: %02x %02x %02x %02x",
+			    poll_reply->rc5.not_used, poll_reply->rc5.system,
+			    poll_reply->rc5.data, poll_reply->rc5.not_data);
+			goto resubmit;
+		}
+
+		break;
+	}
+
+	rc_keydown(d->rc_dev, protocol, keycode, toggle);
+
+resubmit:
+	/* Clean the buffer before we requeue */
+	memset(purb->transfer_buffer, 0, RC_MSG_SIZE_V1_20);
+
+	/* Requeue URB */
+	usb_submit_urb(purb, GFP_ATOMIC);
+}
+
+int dib0700_rc_setup(struct dvb_usb_device *d, struct usb_interface *intf)
+{
+	struct dib0700_state *st = d->priv;
+	struct urb *purb;
+	const struct usb_endpoint_descriptor *e;
+	int ret, rc_ep = 1;
+	unsigned int pipe = 0;
+
+	/* Poll-based. Don't initialize bulk mode */
+	if (st->fw_version < 0x10200 || !intf)
+		return 0;
+
+	/* Starting in firmware 1.20, the RC info is provided on a bulk pipe */
+
+	if (intf->altsetting[0].desc.bNumEndpoints < rc_ep + 1)
+		return -ENODEV;
+
+	purb = usb_alloc_urb(0, GFP_KERNEL);
+	if (purb == NULL)
+		return -ENOMEM;
+
+	purb->transfer_buffer = kzalloc(RC_MSG_SIZE_V1_20, GFP_KERNEL);
+	if (purb->transfer_buffer == NULL) {
+		err("rc kzalloc failed");
+		usb_free_urb(purb);
+		return -ENOMEM;
+	}
+
+	purb->status = -EINPROGRESS;
+
+	/*
+	 * Some devices like the Hauppauge NovaTD model 52009 use an interrupt
+	 * endpoint, while others use a bulk one.
+	 */
+	e = &intf->altsetting[0].endpoint[rc_ep].desc;
+	if (usb_endpoint_dir_in(e)) {
+		if (usb_endpoint_xfer_bulk(e)) {
+			pipe = usb_rcvbulkpipe(d->udev, rc_ep);
+			usb_fill_bulk_urb(purb, d->udev, pipe,
+					  purb->transfer_buffer,
+					  RC_MSG_SIZE_V1_20,
+					  dib0700_rc_urb_completion, d);
+
+		} else if (usb_endpoint_xfer_int(e)) {
+			pipe = usb_rcvintpipe(d->udev, rc_ep);
+			usb_fill_int_urb(purb, d->udev, pipe,
+					  purb->transfer_buffer,
+					  RC_MSG_SIZE_V1_20,
+					  dib0700_rc_urb_completion, d, 1);
+		}
+	}
+
+	if (!pipe) {
+		err("There's no endpoint for remote controller");
+		kfree(purb->transfer_buffer);
+		usb_free_urb(purb);
+		return 0;
+	}
+
+	ret = usb_submit_urb(purb, GFP_ATOMIC);
+	if (ret) {
+		err("rc submit urb failed");
+		kfree(purb->transfer_buffer);
+		usb_free_urb(purb);
+	}
+
+	return ret;
+}
+
+static int dib0700_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	int i;
+	struct dvb_usb_device *dev;
+
+	for (i = 0; i < dib0700_device_count; i++)
+		if (dvb_usb_device_init(intf, &dib0700_devices[i], THIS_MODULE,
+		    &dev, adapter_nr) == 0) {
+			struct dib0700_state *st = dev->priv;
+			u32 hwversion, romversion, fw_version, fwtype;
+
+			dib0700_get_version(dev, &hwversion, &romversion,
+				&fw_version, &fwtype);
+
+			deb_info("Firmware version: %x, %d, 0x%x, %d\n",
+				hwversion, romversion, fw_version, fwtype);
+
+			st->fw_version = fw_version;
+			st->nb_packet_buffer_size = (u32)nb_packet_buffer_size;
+
+			/* Disable polling mode on newer firmwares */
+			if (st->fw_version >= 0x10200)
+				dev->props.rc.core.bulk_mode = true;
+			else
+				dev->props.rc.core.bulk_mode = false;
+
+			dib0700_rc_setup(dev, intf);
+
+			return 0;
+		}
+
+	return -ENODEV;
+}
+
+static void dib0700_disconnect(struct usb_interface *intf)
+{
+	struct dvb_usb_device *d = usb_get_intfdata(intf);
+	struct dib0700_state *st = d->priv;
+	struct i2c_client *client;
+
+	/* remove I2C client for tuner */
+	client = st->i2c_client_tuner;
+	if (client) {
+		module_put(client->dev.driver->owner);
+		i2c_unregister_device(client);
+	}
+
+	/* remove I2C client for demodulator */
+	client = st->i2c_client_demod;
+	if (client) {
+		module_put(client->dev.driver->owner);
+		i2c_unregister_device(client);
+	}
+
+	dvb_usb_device_exit(intf);
+}
+
+
+static struct usb_driver dib0700_driver = {
+	.name       = "dvb_usb_dib0700",
+	.probe      = dib0700_probe,
+	.disconnect = dib0700_disconnect,
+	.id_table   = dib0700_usb_id_table,
+};
+
+module_usb_driver(dib0700_driver);
+
+MODULE_FIRMWARE("dvb-usb-dib0700-1.20.fw");
+MODULE_AUTHOR("Patrick Boettcher <patrick.boettcher@posteo.de>");
+MODULE_DESCRIPTION("Driver for devices based on DiBcom DiB0700 - USB bridge");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/dib0700_devices.c b/drivers/media/usb/dvb-usb/dib0700_devices.c
new file mode 100644
index 0000000..091389f
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/dib0700_devices.c
@@ -0,0 +1,5155 @@
+/* Linux driver for devices based on the DiBcom DiB0700 USB bridge
+ *
+ *	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, version 2.
+ *
+ *  Copyright (C) 2005-9 DiBcom, SA et al
+ */
+#include "dib0700.h"
+
+#include "dib3000mc.h"
+#include "dib7000m.h"
+#include "dib7000p.h"
+#include "dib8000.h"
+#include "dib9000.h"
+#include "mt2060.h"
+#include "mt2266.h"
+#include "tuner-xc2028.h"
+#include "xc5000.h"
+#include "xc4000.h"
+#include "s5h1411.h"
+#include "dib0070.h"
+#include "dib0090.h"
+#include "lgdt3305.h"
+#include "mxl5007t.h"
+#include "mn88472.h"
+#include "tda18250.h"
+
+
+static int force_lna_activation;
+module_param(force_lna_activation, int, 0644);
+MODULE_PARM_DESC(force_lna_activation, "force the activation of Low-Noise-Amplifyer(s) (LNA), if applicable for the device (default: 0=automatic/off).");
+
+struct dib0700_adapter_state {
+	int (*set_param_save) (struct dvb_frontend *);
+	const struct firmware *frontend_firmware;
+	struct dib7000p_ops dib7000p_ops;
+	struct dib8000_ops dib8000_ops;
+};
+
+/* Hauppauge Nova-T 500 (aka Bristol)
+ *  has a LNA on GPIO0 which is enabled by setting 1 */
+static struct mt2060_config bristol_mt2060_config[2] = {
+	{
+		.i2c_address = 0x60,
+		.clock_out   = 3,
+	}, {
+		.i2c_address = 0x61,
+	}
+};
+
+
+static struct dibx000_agc_config bristol_dib3000p_mt2060_agc_config = {
+	.band_caps = BAND_VHF | BAND_UHF,
+	.setup     = (1 << 8) | (5 << 5) | (0 << 4) | (0 << 3) | (0 << 2) | (2 << 0),
+
+	.agc1_max = 42598,
+	.agc1_min = 17694,
+	.agc2_max = 45875,
+	.agc2_min = 0,
+
+	.agc1_pt1 = 0,
+	.agc1_pt2 = 59,
+
+	.agc1_slope1 = 0,
+	.agc1_slope2 = 69,
+
+	.agc2_pt1 = 0,
+	.agc2_pt2 = 59,
+
+	.agc2_slope1 = 111,
+	.agc2_slope2 = 28,
+};
+
+static struct dib3000mc_config bristol_dib3000mc_config[2] = {
+	{	.agc          = &bristol_dib3000p_mt2060_agc_config,
+		.max_time     = 0x196,
+		.ln_adc_level = 0x1cc7,
+		.output_mpeg2_in_188_bytes = 1,
+	},
+	{	.agc          = &bristol_dib3000p_mt2060_agc_config,
+		.max_time     = 0x196,
+		.ln_adc_level = 0x1cc7,
+		.output_mpeg2_in_188_bytes = 1,
+	}
+};
+
+static int bristol_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_state *st = adap->dev->priv;
+	if (adap->id == 0) {
+		dib0700_set_gpio(adap->dev, GPIO6,  GPIO_OUT, 0); msleep(10);
+		dib0700_set_gpio(adap->dev, GPIO6,  GPIO_OUT, 1); msleep(10);
+		dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0); msleep(10);
+		dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1); msleep(10);
+
+		if (force_lna_activation)
+			dib0700_set_gpio(adap->dev, GPIO0, GPIO_OUT, 1);
+		else
+			dib0700_set_gpio(adap->dev, GPIO0, GPIO_OUT, 0);
+
+		if (dib3000mc_i2c_enumeration(&adap->dev->i2c_adap, 2, DEFAULT_DIB3000P_I2C_ADDRESS, bristol_dib3000mc_config) != 0) {
+			dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 0); msleep(10);
+			return -ENODEV;
+		}
+	}
+	st->mt2060_if1[adap->id] = 1220;
+	return (adap->fe_adap[0].fe = dvb_attach(dib3000mc_attach, &adap->dev->i2c_adap,
+		(10 + adap->id) << 1, &bristol_dib3000mc_config[adap->id])) == NULL ? -ENODEV : 0;
+}
+
+static int eeprom_read(struct i2c_adapter *adap,u8 adrs,u8 *pval)
+{
+	struct i2c_msg msg[2] = {
+		{ .addr = 0x50, .flags = 0,        .buf = &adrs, .len = 1 },
+		{ .addr = 0x50, .flags = I2C_M_RD, .buf = pval,  .len = 1 },
+	};
+	if (i2c_transfer(adap, msg, 2) != 2) return -EREMOTEIO;
+	return 0;
+}
+
+static int bristol_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct i2c_adapter *prim_i2c = &adap->dev->i2c_adap;
+	struct i2c_adapter *tun_i2c = dib3000mc_get_tuner_i2c_master(adap->fe_adap[0].fe, 1);
+	s8 a;
+	int if1=1220;
+	if (adap->dev->udev->descriptor.idVendor  == cpu_to_le16(USB_VID_HAUPPAUGE) &&
+		adap->dev->udev->descriptor.idProduct == cpu_to_le16(USB_PID_HAUPPAUGE_NOVA_T_500_2)) {
+		if (!eeprom_read(prim_i2c,0x59 + adap->id,&a)) if1=1220+a;
+	}
+	return dvb_attach(mt2060_attach, adap->fe_adap[0].fe, tun_i2c,
+			  &bristol_mt2060_config[adap->id], if1) == NULL ?
+			  -ENODEV : 0;
+}
+
+/* STK7700D: Pinnacle/Terratec/Hauppauge Dual DVB-T Diversity */
+
+/* MT226x */
+static struct dibx000_agc_config stk7700d_7000p_mt2266_agc_config[2] = {
+	{
+		BAND_UHF,
+
+		/* P_agc_use_sd_mod1=0, P_agc_use_sd_mod2=0, P_agc_freq_pwm_div=1, P_agc_inv_pwm1=1, P_agc_inv_pwm2=1,
+		* P_agc_inh_dc_rv_est=0, P_agc_time_est=3, P_agc_freeze=0, P_agc_nb_est=2, P_agc_write=0 */
+		(0 << 15) | (0 << 14) | (1 << 11) | (1 << 10) | (1 << 9) | (0 << 8)
+	    | (3 << 5) | (0 << 4) | (5 << 1) | (0 << 0),
+
+		1130,
+		21,
+
+		0,
+		118,
+
+		0,
+		3530,
+		1,
+		0,
+
+		65535,
+		33770,
+		65535,
+		23592,
+
+		0,
+		62,
+		255,
+		64,
+		64,
+		132,
+		192,
+		80,
+		80,
+
+		17,
+		27,
+		23,
+		51,
+
+		1,
+	}, {
+		BAND_VHF | BAND_LBAND,
+
+		/* P_agc_use_sd_mod1=0, P_agc_use_sd_mod2=0, P_agc_freq_pwm_div=1, P_agc_inv_pwm1=1, P_agc_inv_pwm2=1,
+		* P_agc_inh_dc_rv_est=0, P_agc_time_est=3, P_agc_freeze=0, P_agc_nb_est=2, P_agc_write=0 */
+		(0 << 15) | (0 << 14) | (1 << 11) | (1 << 10) | (1 << 9) | (0 << 8)
+	    | (3 << 5) | (0 << 4) | (2 << 1) | (0 << 0),
+
+		2372,
+		21,
+
+		0,
+		118,
+
+		0,
+		3530,
+		1,
+		0,
+
+		65535,
+		0,
+		65535,
+		23592,
+
+		0,
+		128,
+		128,
+		128,
+		0,
+		128,
+		253,
+		81,
+		0,
+
+		17,
+		27,
+		23,
+		51,
+
+		1,
+	}
+};
+
+static struct dibx000_bandwidth_config stk7700d_mt2266_pll_config = {
+	.internal = 60000,
+	.sampling = 30000,
+	.pll_prediv = 1,
+	.pll_ratio = 8,
+	.pll_range = 3,
+	.pll_reset = 1,
+	.pll_bypass = 0,
+	.enable_refdiv = 0,
+	.bypclk_div = 0,
+	.IO_CLK_en_core = 1,
+	.ADClkSrc = 1,
+	.modulo = 2,
+	.sad_cfg = (3 << 14) | (1 << 12) | (524 << 0),
+	.ifreq = 0,
+	.timf = 20452225,
+};
+
+static struct dib7000p_config stk7700d_dib7000p_mt2266_config[] = {
+	{	.output_mpeg2_in_188_bytes = 1,
+		.hostbus_diversity = 1,
+		.tuner_is_baseband = 1,
+
+		.agc_config_count = 2,
+		.agc = stk7700d_7000p_mt2266_agc_config,
+		.bw  = &stk7700d_mt2266_pll_config,
+
+		.gpio_dir = DIB7000P_GPIO_DEFAULT_DIRECTIONS,
+		.gpio_val = DIB7000P_GPIO_DEFAULT_VALUES,
+		.gpio_pwm_pos = DIB7000P_GPIO_DEFAULT_PWM_POS,
+	},
+	{	.output_mpeg2_in_188_bytes = 1,
+		.hostbus_diversity = 1,
+		.tuner_is_baseband = 1,
+
+		.agc_config_count = 2,
+		.agc = stk7700d_7000p_mt2266_agc_config,
+		.bw  = &stk7700d_mt2266_pll_config,
+
+		.gpio_dir = DIB7000P_GPIO_DEFAULT_DIRECTIONS,
+		.gpio_val = DIB7000P_GPIO_DEFAULT_VALUES,
+		.gpio_pwm_pos = DIB7000P_GPIO_DEFAULT_PWM_POS,
+	}
+};
+
+static struct mt2266_config stk7700d_mt2266_config[2] = {
+	{	.i2c_address = 0x60
+	},
+	{	.i2c_address = 0x60
+	}
+};
+
+static int stk7700P2_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_adapter_state *state = adap->priv;
+
+	if (!dvb_attach(dib7000p_attach, &state->dib7000p_ops))
+		return -ENODEV;
+
+	if (adap->id == 0) {
+		dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 1);
+		msleep(10);
+		dib0700_set_gpio(adap->dev, GPIO9, GPIO_OUT, 1);
+		dib0700_set_gpio(adap->dev, GPIO4, GPIO_OUT, 1);
+		dib0700_set_gpio(adap->dev, GPIO7, GPIO_OUT, 1);
+		dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0);
+		msleep(10);
+		dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1);
+		msleep(10);
+		if (state->dib7000p_ops.i2c_enumeration(&adap->dev->i2c_adap, 1, 18,
+					     stk7700d_dib7000p_mt2266_config)
+		    != 0) {
+			err("%s: state->dib7000p_ops.i2c_enumeration failed.  Cannot continue\n", __func__);
+			dvb_detach(state->dib7000p_ops.set_wbd_ref);
+			return -ENODEV;
+		}
+	}
+
+	adap->fe_adap[0].fe = state->dib7000p_ops.init(&adap->dev->i2c_adap,
+			   0x80 + (adap->id << 1),
+			   &stk7700d_dib7000p_mt2266_config[adap->id]);
+
+	return adap->fe_adap[0].fe == NULL ? -ENODEV : 0;
+}
+
+static int stk7700d_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_adapter_state *state = adap->priv;
+
+	if (!dvb_attach(dib7000p_attach, &state->dib7000p_ops))
+		return -ENODEV;
+
+	if (adap->id == 0) {
+		dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 1);
+		msleep(10);
+		dib0700_set_gpio(adap->dev, GPIO9, GPIO_OUT, 1);
+		dib0700_set_gpio(adap->dev, GPIO4, GPIO_OUT, 1);
+		dib0700_set_gpio(adap->dev, GPIO7, GPIO_OUT, 1);
+		dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0);
+		msleep(10);
+		dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1);
+		msleep(10);
+		dib0700_set_gpio(adap->dev, GPIO0, GPIO_OUT, 1);
+		if (state->dib7000p_ops.i2c_enumeration(&adap->dev->i2c_adap, 2, 18,
+					     stk7700d_dib7000p_mt2266_config)
+		    != 0) {
+			err("%s: state->dib7000p_ops.i2c_enumeration failed.  Cannot continue\n", __func__);
+			dvb_detach(state->dib7000p_ops.set_wbd_ref);
+			return -ENODEV;
+		}
+	}
+
+	adap->fe_adap[0].fe = state->dib7000p_ops.init(&adap->dev->i2c_adap,
+			   0x80 + (adap->id << 1),
+			   &stk7700d_dib7000p_mt2266_config[adap->id]);
+
+	return adap->fe_adap[0].fe == NULL ? -ENODEV : 0;
+}
+
+static int stk7700d_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct i2c_adapter *tun_i2c;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	tun_i2c = state->dib7000p_ops.get_i2c_master(adap->fe_adap[0].fe,
+					    DIBX000_I2C_INTERFACE_TUNER, 1);
+	return dvb_attach(mt2266_attach, adap->fe_adap[0].fe, tun_i2c,
+		&stk7700d_mt2266_config[adap->id]) == NULL ? -ENODEV : 0;
+}
+
+/* STK7700-PH: Digital/Analog Hybrid Tuner, e.h. Cinergy HT USB HE */
+static struct dibx000_agc_config xc3028_agc_config = {
+	.band_caps = BAND_VHF | BAND_UHF,
+	/* P_agc_use_sd_mod1=0, P_agc_use_sd_mod2=0, P_agc_freq_pwm_div=0,
+	 * P_agc_inv_pwm1=0, P_agc_inv_pwm2=0, P_agc_inh_dc_rv_est=0,
+	 * P_agc_time_est=3, P_agc_freeze=0, P_agc_nb_est=2, P_agc_write=0 */
+	.setup = (0 << 15) | (0 << 14) | (0 << 11) | (0 << 10) | (0 << 9) | (0 << 8) | (3 << 5) | (0 << 4) | (2 << 1) | (0 << 0),
+	.inv_gain = 712,
+	.time_stabiliz = 21,
+	.alpha_level = 0,
+	.thlock = 118,
+	.wbd_inv = 0,
+	.wbd_ref = 2867,
+	.wbd_sel = 0,
+	.wbd_alpha = 2,
+	.agc1_max = 0,
+	.agc1_min = 0,
+	.agc2_max = 39718,
+	.agc2_min = 9930,
+	.agc1_pt1 = 0,
+	.agc1_pt2 = 0,
+	.agc1_pt3 = 0,
+	.agc1_slope1 = 0,
+	.agc1_slope2 = 0,
+	.agc2_pt1 = 0,
+	.agc2_pt2 = 128,
+	.agc2_slope1 = 29,
+	.agc2_slope2 = 29,
+	.alpha_mant = 17,
+	.alpha_exp = 27,
+	.beta_mant = 23,
+	.beta_exp = 51,
+	.perform_agc_softsplit = 1,
+};
+
+/* PLL Configuration for COFDM BW_MHz = 8.00 with external clock = 30.00 */
+static struct dibx000_bandwidth_config xc3028_bw_config = {
+	.internal = 60000,
+	.sampling = 30000,
+	.pll_prediv = 1,
+	.pll_ratio = 8,
+	.pll_range = 3,
+	.pll_reset = 1,
+	.pll_bypass = 0,
+	.enable_refdiv = 0,
+	.bypclk_div = 0,
+	.IO_CLK_en_core = 1,
+	.ADClkSrc = 1,
+	.modulo = 0,
+	.sad_cfg = (3 << 14) | (1 << 12) | (524 << 0), /* sad_cfg: refsel, sel, freq_15k */
+	.ifreq = (1 << 25) | 5816102,  /* ifreq = 5.200000 MHz */
+	.timf = 20452225,
+	.xtal_hz = 30000000,
+};
+
+static struct dib7000p_config stk7700ph_dib7700_xc3028_config = {
+	.output_mpeg2_in_188_bytes = 1,
+	.tuner_is_baseband = 1,
+
+	.agc_config_count = 1,
+	.agc = &xc3028_agc_config,
+	.bw  = &xc3028_bw_config,
+
+	.gpio_dir = DIB7000P_GPIO_DEFAULT_DIRECTIONS,
+	.gpio_val = DIB7000P_GPIO_DEFAULT_VALUES,
+	.gpio_pwm_pos = DIB7000P_GPIO_DEFAULT_PWM_POS,
+};
+
+static int stk7700ph_xc3028_callback(void *ptr, int component,
+				     int command, int arg)
+{
+	struct dvb_usb_adapter *adap = ptr;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	switch (command) {
+	case XC2028_TUNER_RESET:
+		/* Send the tuner in then out of reset */
+		state->dib7000p_ops.set_gpio(adap->fe_adap[0].fe, 8, 0, 0);
+		msleep(10);
+		state->dib7000p_ops.set_gpio(adap->fe_adap[0].fe, 8, 0, 1);
+		break;
+	case XC2028_RESET_CLK:
+	case XC2028_I2C_FLUSH:
+		break;
+	default:
+		err("%s: unknown command %d, arg %d\n", __func__,
+			command, arg);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static struct xc2028_ctrl stk7700ph_xc3028_ctrl = {
+	.fname = XC2028_DEFAULT_FIRMWARE,
+	.max_len = 64,
+	.demod = XC3028_FE_DIBCOM52,
+};
+
+static struct xc2028_config stk7700ph_xc3028_config = {
+	.i2c_addr = 0x61,
+	.ctrl = &stk7700ph_xc3028_ctrl,
+};
+
+static int stk7700ph_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct usb_device_descriptor *desc = &adap->dev->udev->descriptor;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	if (!dvb_attach(dib7000p_attach, &state->dib7000p_ops))
+		return -ENODEV;
+
+	if (desc->idVendor  == cpu_to_le16(USB_VID_PINNACLE) &&
+	    desc->idProduct == cpu_to_le16(USB_PID_PINNACLE_EXPRESSCARD_320CX))
+		dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 0);
+	else
+		dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 1);
+	msleep(20);
+	dib0700_set_gpio(adap->dev, GPIO9, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO4, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO7, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0);
+	msleep(10);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1);
+	msleep(20);
+	dib0700_set_gpio(adap->dev, GPIO0, GPIO_OUT, 1);
+	msleep(10);
+
+	if (state->dib7000p_ops.i2c_enumeration(&adap->dev->i2c_adap, 1, 18,
+				     &stk7700ph_dib7700_xc3028_config) != 0) {
+		err("%s: state->dib7000p_ops.i2c_enumeration failed.  Cannot continue\n",
+		    __func__);
+		dvb_detach(state->dib7000p_ops.set_wbd_ref);
+		return -ENODEV;
+	}
+
+	adap->fe_adap[0].fe = state->dib7000p_ops.init(&adap->dev->i2c_adap, 0x80,
+		&stk7700ph_dib7700_xc3028_config);
+
+	return adap->fe_adap[0].fe == NULL ? -ENODEV : 0;
+}
+
+static int stk7700ph_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct i2c_adapter *tun_i2c;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	tun_i2c = state->dib7000p_ops.get_i2c_master(adap->fe_adap[0].fe,
+		DIBX000_I2C_INTERFACE_TUNER, 1);
+
+	stk7700ph_xc3028_config.i2c_adap = tun_i2c;
+
+	/* FIXME: generalize & move to common area */
+	adap->fe_adap[0].fe->callback = stk7700ph_xc3028_callback;
+
+	return dvb_attach(xc2028_attach, adap->fe_adap[0].fe, &stk7700ph_xc3028_config)
+		== NULL ? -ENODEV : 0;
+}
+
+#define DEFAULT_RC_INTERVAL 50
+
+/*
+ * This function is used only when firmware is < 1.20 version. Newer
+ * firmwares use bulk mode, with functions implemented at dib0700_core,
+ * at dib0700_rc_urb_completion()
+ */
+static int dib0700_rc_query_old_firmware(struct dvb_usb_device *d)
+{
+	enum rc_proto protocol;
+	u32 scancode;
+	u8 toggle;
+	int i;
+	struct dib0700_state *st = d->priv;
+
+	if (st->fw_version >= 0x10200) {
+		/* For 1.20 firmware , We need to keep the RC polling
+		   callback so we can reuse the input device setup in
+		   dvb-usb-remote.c.  However, the actual work is being done
+		   in the bulk URB completion handler. */
+		return 0;
+	}
+
+	st->buf[0] = REQUEST_POLL_RC;
+	st->buf[1] = 0;
+
+	i = dib0700_ctrl_rd(d, st->buf, 2, st->buf, 4);
+	if (i <= 0) {
+		err("RC Query Failed");
+		return -EIO;
+	}
+
+	/* losing half of KEY_0 events from Philipps rc5 remotes.. */
+	if (st->buf[0] == 0 && st->buf[1] == 0
+	    && st->buf[2] == 0 && st->buf[3] == 0)
+		return 0;
+
+	/* info("%d: %2X %2X %2X %2X",dvb_usb_dib0700_ir_proto,(int)st->buf[3 - 2],(int)st->buf[3 - 3],(int)st->buf[3 - 1],(int)st->buf[3]);  */
+
+	dib0700_rc_setup(d, NULL); /* reset ir sensor data to prevent false events */
+
+	switch (d->props.rc.core.protocol) {
+	case RC_PROTO_BIT_NEC:
+		/* NEC protocol sends repeat code as 0 0 0 FF */
+		if ((st->buf[3 - 2] == 0x00) && (st->buf[3 - 3] == 0x00) &&
+		    (st->buf[3] == 0xff)) {
+			rc_repeat(d->rc_dev);
+			return 0;
+		}
+
+		protocol = RC_PROTO_NEC;
+		scancode = RC_SCANCODE_NEC(st->buf[3 - 2], st->buf[3 - 3]);
+		toggle = 0;
+		break;
+
+	default:
+		/* RC-5 protocol changes toggle bit on new keypress */
+		protocol = RC_PROTO_RC5;
+		scancode = RC_SCANCODE_RC5(st->buf[3 - 2], st->buf[3 - 3]);
+		toggle = st->buf[3 - 1];
+		break;
+	}
+
+	rc_keydown(d->rc_dev, protocol, scancode, toggle);
+	return 0;
+}
+
+/* STK7700P: Hauppauge Nova-T Stick, AVerMedia Volar */
+static struct dibx000_agc_config stk7700p_7000m_mt2060_agc_config = {
+	BAND_UHF | BAND_VHF,
+
+	/* P_agc_use_sd_mod1=0, P_agc_use_sd_mod2=0, P_agc_freq_pwm_div=5, P_agc_inv_pwm1=0, P_agc_inv_pwm2=0,
+	 * P_agc_inh_dc_rv_est=0, P_agc_time_est=3, P_agc_freeze=0, P_agc_nb_est=2, P_agc_write=0 */
+	(0 << 15) | (0 << 14) | (5 << 11) | (0 << 10) | (0 << 9) | (0 << 8)
+	| (3 << 5) | (0 << 4) | (2 << 1) | (0 << 0),
+
+	712,
+	41,
+
+	0,
+	118,
+
+	0,
+	4095,
+	0,
+	0,
+
+	42598,
+	17694,
+	45875,
+	2621,
+	0,
+	76,
+	139,
+	52,
+	59,
+	107,
+	172,
+	57,
+	70,
+
+	21,
+	25,
+	28,
+	48,
+
+	1,
+	{  0,
+	   107,
+	   51800,
+	   24700
+	},
+};
+
+static struct dibx000_agc_config stk7700p_7000p_mt2060_agc_config = {
+	.band_caps = BAND_UHF | BAND_VHF,
+	/* P_agc_use_sd_mod1=0, P_agc_use_sd_mod2=0, P_agc_freq_pwm_div=5, P_agc_inv_pwm1=0, P_agc_inv_pwm2=0,
+	 * P_agc_inh_dc_rv_est=0, P_agc_time_est=3, P_agc_freeze=0, P_agc_nb_est=2, P_agc_write=0 */
+	.setup = (0 << 15) | (0 << 14) | (5 << 11) | (0 << 10) | (0 << 9) | (0 << 8) | (3 << 5) | (0 << 4) | (2 << 1) | (0 << 0),
+	.inv_gain = 712,
+	.time_stabiliz = 41,
+	.alpha_level = 0,
+	.thlock = 118,
+	.wbd_inv = 0,
+	.wbd_ref = 4095,
+	.wbd_sel = 0,
+	.wbd_alpha = 0,
+	.agc1_max = 42598,
+	.agc1_min = 16384,
+	.agc2_max = 42598,
+	.agc2_min = 0,
+	.agc1_pt1 = 0,
+	.agc1_pt2 = 137,
+	.agc1_pt3 = 255,
+	.agc1_slope1 = 0,
+	.agc1_slope2 = 255,
+	.agc2_pt1 = 0,
+	.agc2_pt2 = 0,
+	.agc2_slope1 = 0,
+	.agc2_slope2 = 41,
+	.alpha_mant = 15,
+	.alpha_exp = 25,
+	.beta_mant = 28,
+	.beta_exp = 48,
+	.perform_agc_softsplit = 0,
+};
+
+static struct dibx000_bandwidth_config stk7700p_pll_config = {
+	.internal = 60000,
+	.sampling = 30000,
+	.pll_prediv = 1,
+	.pll_ratio = 8,
+	.pll_range = 3,
+	.pll_reset = 1,
+	.pll_bypass = 0,
+	.enable_refdiv = 0,
+	.bypclk_div = 0,
+	.IO_CLK_en_core = 1,
+	.ADClkSrc = 1,
+	.modulo = 0,
+	.sad_cfg = (3 << 14) | (1 << 12) | (524 << 0),
+	.ifreq = 60258167,
+	.timf = 20452225,
+	.xtal_hz = 30000000,
+};
+
+static struct dib7000m_config stk7700p_dib7000m_config = {
+	.dvbt_mode = 1,
+	.output_mpeg2_in_188_bytes = 1,
+	.quartz_direct = 1,
+
+	.agc_config_count = 1,
+	.agc = &stk7700p_7000m_mt2060_agc_config,
+	.bw  = &stk7700p_pll_config,
+
+	.gpio_dir = DIB7000M_GPIO_DEFAULT_DIRECTIONS,
+	.gpio_val = DIB7000M_GPIO_DEFAULT_VALUES,
+	.gpio_pwm_pos = DIB7000M_GPIO_DEFAULT_PWM_POS,
+};
+
+static struct dib7000p_config stk7700p_dib7000p_config = {
+	.output_mpeg2_in_188_bytes = 1,
+
+	.agc_config_count = 1,
+	.agc = &stk7700p_7000p_mt2060_agc_config,
+	.bw  = &stk7700p_pll_config,
+
+	.gpio_dir = DIB7000M_GPIO_DEFAULT_DIRECTIONS,
+	.gpio_val = DIB7000M_GPIO_DEFAULT_VALUES,
+	.gpio_pwm_pos = DIB7000M_GPIO_DEFAULT_PWM_POS,
+};
+
+static int stk7700p_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_state *st = adap->dev->priv;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	if (!dvb_attach(dib7000p_attach, &state->dib7000p_ops))
+		return -ENODEV;
+
+	/* unless there is no real power management in DVB - we leave the device on GPIO6 */
+
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0);
+	dib0700_set_gpio(adap->dev, GPIO6,  GPIO_OUT, 0); msleep(50);
+
+	dib0700_set_gpio(adap->dev, GPIO6,  GPIO_OUT, 1); msleep(10);
+	dib0700_set_gpio(adap->dev, GPIO9,  GPIO_OUT, 1);
+
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0); msleep(10);
+	dib0700_ctrl_clock(adap->dev, 72, 1);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1); msleep(100);
+
+	dib0700_set_gpio(adap->dev,  GPIO0, GPIO_OUT, 1);
+
+	st->mt2060_if1[0] = 1220;
+
+	if (state->dib7000p_ops.dib7000pc_detection(&adap->dev->i2c_adap)) {
+		adap->fe_adap[0].fe = state->dib7000p_ops.init(&adap->dev->i2c_adap, 18, &stk7700p_dib7000p_config);
+		st->is_dib7000pc = 1;
+	} else {
+		memset(&state->dib7000p_ops, 0, sizeof(state->dib7000p_ops));
+		adap->fe_adap[0].fe = dvb_attach(dib7000m_attach, &adap->dev->i2c_adap, 18, &stk7700p_dib7000m_config);
+	}
+
+	return adap->fe_adap[0].fe == NULL ? -ENODEV : 0;
+}
+
+static struct mt2060_config stk7700p_mt2060_config = {
+	0x60
+};
+
+static int stk7700p_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct i2c_adapter *prim_i2c = &adap->dev->i2c_adap;
+	struct dib0700_state *st = adap->dev->priv;
+	struct i2c_adapter *tun_i2c;
+	struct dib0700_adapter_state *state = adap->priv;
+	s8 a;
+	int if1=1220;
+
+	if (adap->dev->udev->descriptor.idVendor  == cpu_to_le16(USB_VID_HAUPPAUGE) &&
+		adap->dev->udev->descriptor.idProduct == cpu_to_le16(USB_PID_HAUPPAUGE_NOVA_T_STICK)) {
+		if (!eeprom_read(prim_i2c,0x58,&a)) if1=1220+a;
+	}
+	if (st->is_dib7000pc)
+		tun_i2c = state->dib7000p_ops.get_i2c_master(adap->fe_adap[0].fe, DIBX000_I2C_INTERFACE_TUNER, 1);
+	else
+		tun_i2c = dib7000m_get_i2c_master(adap->fe_adap[0].fe, DIBX000_I2C_INTERFACE_TUNER, 1);
+
+	return dvb_attach(mt2060_attach, adap->fe_adap[0].fe, tun_i2c, &stk7700p_mt2060_config,
+		if1) == NULL ? -ENODEV : 0;
+}
+
+/* DIB7070 generic */
+static struct dibx000_agc_config dib7070_agc_config = {
+	.band_caps = BAND_UHF | BAND_VHF | BAND_LBAND | BAND_SBAND,
+	/* P_agc_use_sd_mod1=0, P_agc_use_sd_mod2=0, P_agc_freq_pwm_div=5, P_agc_inv_pwm1=0, P_agc_inv_pwm2=0,
+	 * P_agc_inh_dc_rv_est=0, P_agc_time_est=3, P_agc_freeze=0, P_agc_nb_est=5, P_agc_write=0 */
+	.setup = (0 << 15) | (0 << 14) | (5 << 11) | (0 << 10) | (0 << 9) | (0 << 8) | (3 << 5) | (0 << 4) | (5 << 1) | (0 << 0),
+	.inv_gain = 600,
+	.time_stabiliz = 10,
+	.alpha_level = 0,
+	.thlock = 118,
+	.wbd_inv = 0,
+	.wbd_ref = 3530,
+	.wbd_sel = 1,
+	.wbd_alpha = 5,
+	.agc1_max = 65535,
+	.agc1_min = 0,
+	.agc2_max = 65535,
+	.agc2_min = 0,
+	.agc1_pt1 = 0,
+	.agc1_pt2 = 40,
+	.agc1_pt3 = 183,
+	.agc1_slope1 = 206,
+	.agc1_slope2 = 255,
+	.agc2_pt1 = 72,
+	.agc2_pt2 = 152,
+	.agc2_slope1 = 88,
+	.agc2_slope2 = 90,
+	.alpha_mant = 17,
+	.alpha_exp = 27,
+	.beta_mant = 23,
+	.beta_exp = 51,
+	.perform_agc_softsplit = 0,
+};
+
+static int dib7070_tuner_reset(struct dvb_frontend *fe, int onoff)
+{
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	deb_info("reset: %d", onoff);
+	return state->dib7000p_ops.set_gpio(fe, 8, 0, !onoff);
+}
+
+static int dib7070_tuner_sleep(struct dvb_frontend *fe, int onoff)
+{
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	deb_info("sleep: %d", onoff);
+	return state->dib7000p_ops.set_gpio(fe, 9, 0, onoff);
+}
+
+static struct dib0070_config dib7070p_dib0070_config[2] = {
+	{
+		.i2c_address = DEFAULT_DIB0070_I2C_ADDRESS,
+		.reset = dib7070_tuner_reset,
+		.sleep = dib7070_tuner_sleep,
+		.clock_khz = 12000,
+		.clock_pad_drive = 4,
+		.charge_pump = 2,
+	}, {
+		.i2c_address = DEFAULT_DIB0070_I2C_ADDRESS,
+		.reset = dib7070_tuner_reset,
+		.sleep = dib7070_tuner_sleep,
+		.clock_khz = 12000,
+		.charge_pump = 2,
+	}
+};
+
+static struct dib0070_config dib7770p_dib0070_config = {
+	 .i2c_address = DEFAULT_DIB0070_I2C_ADDRESS,
+	 .reset = dib7070_tuner_reset,
+	 .sleep = dib7070_tuner_sleep,
+	 .clock_khz = 12000,
+	 .clock_pad_drive = 0,
+	 .flip_chip = 1,
+	 .charge_pump = 2,
+};
+
+static int dib7070_set_param_override(struct dvb_frontend *fe)
+{
+	struct dtv_frontend_properties *p = &fe->dtv_property_cache;
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	u16 offset;
+	u8 band = BAND_OF_FREQUENCY(p->frequency/1000);
+	switch (band) {
+		case BAND_VHF: offset = 950; break;
+		case BAND_UHF:
+		default: offset = 550; break;
+	}
+	deb_info("WBD for DiB7000P: %d\n", offset + dib0070_wbd_offset(fe));
+	state->dib7000p_ops.set_wbd_ref(fe, offset + dib0070_wbd_offset(fe));
+	return state->set_param_save(fe);
+}
+
+static int dib7770_set_param_override(struct dvb_frontend *fe)
+{
+	struct dtv_frontend_properties *p = &fe->dtv_property_cache;
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	u16 offset;
+	u8 band = BAND_OF_FREQUENCY(p->frequency/1000);
+	switch (band) {
+	case BAND_VHF:
+		state->dib7000p_ops.set_gpio(fe, 0, 0, 1);
+		offset = 850;
+		break;
+	case BAND_UHF:
+	default:
+		state->dib7000p_ops.set_gpio(fe, 0, 0, 0);
+		offset = 250;
+		break;
+	}
+	deb_info("WBD for DiB7000P: %d\n", offset + dib0070_wbd_offset(fe));
+	state->dib7000p_ops.set_wbd_ref(fe, offset + dib0070_wbd_offset(fe));
+	return state->set_param_save(fe);
+}
+
+static int dib7770p_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_adapter_state *st = adap->priv;
+	struct i2c_adapter *tun_i2c = st->dib7000p_ops.get_i2c_master(adap->fe_adap[0].fe,
+			 DIBX000_I2C_INTERFACE_TUNER, 1);
+
+	if (dvb_attach(dib0070_attach, adap->fe_adap[0].fe, tun_i2c,
+		       &dib7770p_dib0070_config) == NULL)
+		return -ENODEV;
+
+	st->set_param_save = adap->fe_adap[0].fe->ops.tuner_ops.set_params;
+	adap->fe_adap[0].fe->ops.tuner_ops.set_params = dib7770_set_param_override;
+	return 0;
+}
+
+static int dib7070p_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_adapter_state *st = adap->priv;
+	struct i2c_adapter *tun_i2c = st->dib7000p_ops.get_i2c_master(adap->fe_adap[0].fe, DIBX000_I2C_INTERFACE_TUNER, 1);
+
+	if (adap->id == 0) {
+		if (dvb_attach(dib0070_attach, adap->fe_adap[0].fe, tun_i2c, &dib7070p_dib0070_config[0]) == NULL)
+			return -ENODEV;
+	} else {
+		if (dvb_attach(dib0070_attach, adap->fe_adap[0].fe, tun_i2c, &dib7070p_dib0070_config[1]) == NULL)
+			return -ENODEV;
+	}
+
+	st->set_param_save = adap->fe_adap[0].fe->ops.tuner_ops.set_params;
+	adap->fe_adap[0].fe->ops.tuner_ops.set_params = dib7070_set_param_override;
+	return 0;
+}
+
+static int stk7700p_pid_filter(struct dvb_usb_adapter *adapter, int index,
+		u16 pid, int onoff)
+{
+	struct dib0700_adapter_state *state = adapter->priv;
+	struct dib0700_state *st = adapter->dev->priv;
+
+	if (st->is_dib7000pc)
+		return state->dib7000p_ops.pid_filter(adapter->fe_adap[0].fe, index, pid, onoff);
+	return dib7000m_pid_filter(adapter->fe_adap[0].fe, index, pid, onoff);
+}
+
+static int stk7700p_pid_filter_ctrl(struct dvb_usb_adapter *adapter, int onoff)
+{
+	struct dib0700_state *st = adapter->dev->priv;
+	struct dib0700_adapter_state *state = adapter->priv;
+	if (st->is_dib7000pc)
+		return state->dib7000p_ops.pid_filter_ctrl(adapter->fe_adap[0].fe, onoff);
+	return dib7000m_pid_filter_ctrl(adapter->fe_adap[0].fe, onoff);
+}
+
+static int stk70x0p_pid_filter(struct dvb_usb_adapter *adapter, int index, u16 pid, int onoff)
+{
+	struct dib0700_adapter_state *state = adapter->priv;
+	return state->dib7000p_ops.pid_filter(adapter->fe_adap[0].fe, index, pid, onoff);
+}
+
+static int stk70x0p_pid_filter_ctrl(struct dvb_usb_adapter *adapter, int onoff)
+{
+	struct dib0700_adapter_state *state = adapter->priv;
+	return state->dib7000p_ops.pid_filter_ctrl(adapter->fe_adap[0].fe, onoff);
+}
+
+static struct dibx000_bandwidth_config dib7070_bw_config_12_mhz = {
+	.internal = 60000,
+	.sampling = 15000,
+	.pll_prediv = 1,
+	.pll_ratio = 20,
+	.pll_range = 3,
+	.pll_reset = 1,
+	.pll_bypass = 0,
+	.enable_refdiv = 0,
+	.bypclk_div = 0,
+	.IO_CLK_en_core = 1,
+	.ADClkSrc = 1,
+	.modulo = 2,
+	.sad_cfg = (3 << 14) | (1 << 12) | (524 << 0),
+	.ifreq = (0 << 25) | 0,
+	.timf = 20452225,
+	.xtal_hz = 12000000,
+};
+
+static struct dib7000p_config dib7070p_dib7000p_config = {
+	.output_mpeg2_in_188_bytes = 1,
+
+	.agc_config_count = 1,
+	.agc = &dib7070_agc_config,
+	.bw  = &dib7070_bw_config_12_mhz,
+	.tuner_is_baseband = 1,
+	.spur_protect = 1,
+
+	.gpio_dir = DIB7000P_GPIO_DEFAULT_DIRECTIONS,
+	.gpio_val = DIB7000P_GPIO_DEFAULT_VALUES,
+	.gpio_pwm_pos = DIB7000P_GPIO_DEFAULT_PWM_POS,
+
+	.hostbus_diversity = 1,
+};
+
+/* STK7070P */
+static int stk7070p_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct usb_device_descriptor *p = &adap->dev->udev->descriptor;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	if (!dvb_attach(dib7000p_attach, &state->dib7000p_ops))
+		return -ENODEV;
+
+	if (p->idVendor  == cpu_to_le16(USB_VID_PINNACLE) &&
+	    p->idProduct == cpu_to_le16(USB_PID_PINNACLE_PCTV72E))
+		dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 0);
+	else
+		dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 1);
+	msleep(10);
+	dib0700_set_gpio(adap->dev, GPIO9, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO4, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO7, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0);
+
+	dib0700_ctrl_clock(adap->dev, 72, 1);
+
+	msleep(10);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1);
+	msleep(10);
+	dib0700_set_gpio(adap->dev, GPIO0, GPIO_OUT, 1);
+
+	if (state->dib7000p_ops.i2c_enumeration(&adap->dev->i2c_adap, 1, 18,
+				     &dib7070p_dib7000p_config) != 0) {
+		err("%s: state->dib7000p_ops.i2c_enumeration failed.  Cannot continue\n",
+		    __func__);
+		dvb_detach(state->dib7000p_ops.set_wbd_ref);
+		return -ENODEV;
+	}
+
+	adap->fe_adap[0].fe = state->dib7000p_ops.init(&adap->dev->i2c_adap, 0x80,
+		&dib7070p_dib7000p_config);
+	return adap->fe_adap[0].fe == NULL ? -ENODEV : 0;
+}
+
+/* STK7770P */
+static struct dib7000p_config dib7770p_dib7000p_config = {
+	.output_mpeg2_in_188_bytes = 1,
+
+	.agc_config_count = 1,
+	.agc = &dib7070_agc_config,
+	.bw  = &dib7070_bw_config_12_mhz,
+	.tuner_is_baseband = 1,
+	.spur_protect = 1,
+
+	.gpio_dir = DIB7000P_GPIO_DEFAULT_DIRECTIONS,
+	.gpio_val = DIB7000P_GPIO_DEFAULT_VALUES,
+	.gpio_pwm_pos = DIB7000P_GPIO_DEFAULT_PWM_POS,
+
+	.hostbus_diversity = 1,
+	.enable_current_mirror = 1,
+	.disable_sample_and_hold = 0,
+};
+
+static int stk7770p_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct usb_device_descriptor *p = &adap->dev->udev->descriptor;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	if (!dvb_attach(dib7000p_attach, &state->dib7000p_ops))
+		return -ENODEV;
+
+	if (p->idVendor  == cpu_to_le16(USB_VID_PINNACLE) &&
+	    p->idProduct == cpu_to_le16(USB_PID_PINNACLE_PCTV72E))
+		dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 0);
+	else
+		dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 1);
+	msleep(10);
+	dib0700_set_gpio(adap->dev, GPIO9, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO4, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO7, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0);
+
+	dib0700_ctrl_clock(adap->dev, 72, 1);
+
+	msleep(10);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1);
+	msleep(10);
+	dib0700_set_gpio(adap->dev, GPIO0, GPIO_OUT, 1);
+
+	if (state->dib7000p_ops.i2c_enumeration(&adap->dev->i2c_adap, 1, 18,
+				     &dib7770p_dib7000p_config) != 0) {
+		err("%s: state->dib7000p_ops.i2c_enumeration failed.  Cannot continue\n",
+		    __func__);
+		dvb_detach(state->dib7000p_ops.set_wbd_ref);
+		return -ENODEV;
+	}
+
+	adap->fe_adap[0].fe = state->dib7000p_ops.init(&adap->dev->i2c_adap, 0x80,
+		&dib7770p_dib7000p_config);
+	return adap->fe_adap[0].fe == NULL ? -ENODEV : 0;
+}
+
+/* DIB807x generic */
+static struct dibx000_agc_config dib807x_agc_config[2] = {
+	{
+		BAND_VHF,
+		/* P_agc_use_sd_mod1=0, P_agc_use_sd_mod2=0,
+		 * P_agc_freq_pwm_div=1, P_agc_inv_pwm1=0,
+		 * P_agc_inv_pwm2=0,P_agc_inh_dc_rv_est=0,
+		 * P_agc_time_est=3, P_agc_freeze=0, P_agc_nb_est=5,
+		 * P_agc_write=0 */
+		(0 << 15) | (0 << 14) | (7 << 11) | (0 << 10) | (0 << 9) |
+			(0 << 8) | (3 << 5) | (0 << 4) | (5 << 1) |
+			(0 << 0), /* setup*/
+
+		600, /* inv_gain*/
+		10,  /* time_stabiliz*/
+
+		0,  /* alpha_level*/
+		118,  /* thlock*/
+
+		0,     /* wbd_inv*/
+		3530,  /* wbd_ref*/
+		1,     /* wbd_sel*/
+		5,     /* wbd_alpha*/
+
+		65535,  /* agc1_max*/
+		0,  /* agc1_min*/
+
+		65535,  /* agc2_max*/
+		0,      /* agc2_min*/
+
+		0,      /* agc1_pt1*/
+		40,     /* agc1_pt2*/
+		183,    /* agc1_pt3*/
+		206,    /* agc1_slope1*/
+		255,    /* agc1_slope2*/
+		72,     /* agc2_pt1*/
+		152,    /* agc2_pt2*/
+		88,     /* agc2_slope1*/
+		90,     /* agc2_slope2*/
+
+		17,  /* alpha_mant*/
+		27,  /* alpha_exp*/
+		23,  /* beta_mant*/
+		51,  /* beta_exp*/
+
+		0,  /* perform_agc_softsplit*/
+	}, {
+		BAND_UHF,
+		/* P_agc_use_sd_mod1=0, P_agc_use_sd_mod2=0,
+		 * P_agc_freq_pwm_div=1, P_agc_inv_pwm1=0,
+		 * P_agc_inv_pwm2=0, P_agc_inh_dc_rv_est=0,
+		 * P_agc_time_est=3, P_agc_freeze=0, P_agc_nb_est=5,
+		 * P_agc_write=0 */
+		(0 << 15) | (0 << 14) | (1 << 11) | (0 << 10) | (0 << 9) |
+			(0 << 8) | (3 << 5) | (0 << 4) | (5 << 1) |
+			(0 << 0), /* setup */
+
+		600, /* inv_gain*/
+		10,  /* time_stabiliz*/
+
+		0,  /* alpha_level*/
+		118,  /* thlock*/
+
+		0,     /* wbd_inv*/
+		3530,  /* wbd_ref*/
+		1,     /* wbd_sel*/
+		5,     /* wbd_alpha*/
+
+		65535,  /* agc1_max*/
+		0,  /* agc1_min*/
+
+		65535,  /* agc2_max*/
+		0,      /* agc2_min*/
+
+		0,      /* agc1_pt1*/
+		40,     /* agc1_pt2*/
+		183,    /* agc1_pt3*/
+		206,    /* agc1_slope1*/
+		255,    /* agc1_slope2*/
+		72,     /* agc2_pt1*/
+		152,    /* agc2_pt2*/
+		88,     /* agc2_slope1*/
+		90,     /* agc2_slope2*/
+
+		17,  /* alpha_mant*/
+		27,  /* alpha_exp*/
+		23,  /* beta_mant*/
+		51,  /* beta_exp*/
+
+		0,  /* perform_agc_softsplit*/
+	}
+};
+
+static struct dibx000_bandwidth_config dib807x_bw_config_12_mhz = {
+	.internal = 60000,
+	.sampling = 15000,
+	.pll_prediv = 1,
+	.pll_ratio = 20,
+	.pll_range = 3,
+	.pll_reset = 1,
+	.pll_bypass = 0,
+	.enable_refdiv = 0,
+	.bypclk_div = 0,
+	.IO_CLK_en_core = 1,
+	.ADClkSrc = 1,
+	.modulo = 2,
+	.sad_cfg = (3 << 14) | (1 << 12) | (599 << 0),	/* sad_cfg: refsel, sel, freq_15k*/
+	.ifreq = (0 << 25) | 0,				/* ifreq = 0.000000 MHz*/
+	.timf = 18179755,
+	.xtal_hz = 12000000,
+};
+
+static struct dib8000_config dib807x_dib8000_config[2] = {
+	{
+		.output_mpeg2_in_188_bytes = 1,
+
+		.agc_config_count = 2,
+		.agc = dib807x_agc_config,
+		.pll = &dib807x_bw_config_12_mhz,
+		.tuner_is_baseband = 1,
+
+		.gpio_dir = DIB8000_GPIO_DEFAULT_DIRECTIONS,
+		.gpio_val = DIB8000_GPIO_DEFAULT_VALUES,
+		.gpio_pwm_pos = DIB8000_GPIO_DEFAULT_PWM_POS,
+
+		.hostbus_diversity = 1,
+		.div_cfg = 1,
+		.agc_control = &dib0070_ctrl_agc_filter,
+		.output_mode = OUTMODE_MPEG2_FIFO,
+		.drives = 0x2d98,
+	}, {
+		.output_mpeg2_in_188_bytes = 1,
+
+		.agc_config_count = 2,
+		.agc = dib807x_agc_config,
+		.pll = &dib807x_bw_config_12_mhz,
+		.tuner_is_baseband = 1,
+
+		.gpio_dir = DIB8000_GPIO_DEFAULT_DIRECTIONS,
+		.gpio_val = DIB8000_GPIO_DEFAULT_VALUES,
+		.gpio_pwm_pos = DIB8000_GPIO_DEFAULT_PWM_POS,
+
+		.hostbus_diversity = 1,
+		.agc_control = &dib0070_ctrl_agc_filter,
+		.output_mode = OUTMODE_MPEG2_FIFO,
+		.drives = 0x2d98,
+	}
+};
+
+static int dib80xx_tuner_reset(struct dvb_frontend *fe, int onoff)
+{
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	return state->dib8000_ops.set_gpio(fe, 5, 0, !onoff);
+}
+
+static int dib80xx_tuner_sleep(struct dvb_frontend *fe, int onoff)
+{
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	return state->dib8000_ops.set_gpio(fe, 0, 0, onoff);
+}
+
+static const struct dib0070_wbd_gain_cfg dib8070_wbd_gain_cfg[] = {
+    { 240,      7},
+    { 0xffff,   6},
+};
+
+static struct dib0070_config dib807x_dib0070_config[2] = {
+	{
+		.i2c_address = DEFAULT_DIB0070_I2C_ADDRESS,
+		.reset = dib80xx_tuner_reset,
+		.sleep = dib80xx_tuner_sleep,
+		.clock_khz = 12000,
+		.clock_pad_drive = 4,
+		.vga_filter = 1,
+		.force_crystal_mode = 1,
+		.enable_third_order_filter = 1,
+		.charge_pump = 0,
+		.wbd_gain = dib8070_wbd_gain_cfg,
+		.osc_buffer_state = 0,
+		.freq_offset_khz_uhf = -100,
+		.freq_offset_khz_vhf = -100,
+	}, {
+		.i2c_address = DEFAULT_DIB0070_I2C_ADDRESS,
+		.reset = dib80xx_tuner_reset,
+		.sleep = dib80xx_tuner_sleep,
+		.clock_khz = 12000,
+		.clock_pad_drive = 2,
+		.vga_filter = 1,
+		.force_crystal_mode = 1,
+		.enable_third_order_filter = 1,
+		.charge_pump = 0,
+		.wbd_gain = dib8070_wbd_gain_cfg,
+		.osc_buffer_state = 0,
+		.freq_offset_khz_uhf = -25,
+		.freq_offset_khz_vhf = -25,
+	}
+};
+
+static int dib807x_set_param_override(struct dvb_frontend *fe)
+{
+	struct dtv_frontend_properties *p = &fe->dtv_property_cache;
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	u16 offset = dib0070_wbd_offset(fe);
+	u8 band = BAND_OF_FREQUENCY(p->frequency/1000);
+	switch (band) {
+	case BAND_VHF:
+		offset += 750;
+		break;
+	case BAND_UHF:  /* fall-thru wanted */
+	default:
+		offset += 250; break;
+	}
+	deb_info("WBD for DiB8000: %d\n", offset);
+	state->dib8000_ops.set_wbd_ref(fe, offset);
+
+	return state->set_param_save(fe);
+}
+
+static int dib807x_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_adapter_state *st = adap->priv;
+	struct i2c_adapter *tun_i2c = st->dib8000_ops.get_i2c_master(adap->fe_adap[0].fe,
+			DIBX000_I2C_INTERFACE_TUNER, 1);
+
+	if (adap->id == 0) {
+		if (dvb_attach(dib0070_attach, adap->fe_adap[0].fe, tun_i2c,
+				&dib807x_dib0070_config[0]) == NULL)
+			return -ENODEV;
+	} else {
+		if (dvb_attach(dib0070_attach, adap->fe_adap[0].fe, tun_i2c,
+				&dib807x_dib0070_config[1]) == NULL)
+			return -ENODEV;
+	}
+
+	st->set_param_save = adap->fe_adap[0].fe->ops.tuner_ops.set_params;
+	adap->fe_adap[0].fe->ops.tuner_ops.set_params = dib807x_set_param_override;
+	return 0;
+}
+
+static int stk80xx_pid_filter(struct dvb_usb_adapter *adapter, int index,
+	u16 pid, int onoff)
+{
+	struct dib0700_adapter_state *state = adapter->priv;
+
+	return state->dib8000_ops.pid_filter(adapter->fe_adap[0].fe, index, pid, onoff);
+}
+
+static int stk80xx_pid_filter_ctrl(struct dvb_usb_adapter *adapter,
+		int onoff)
+{
+	struct dib0700_adapter_state *state = adapter->priv;
+
+	return state->dib8000_ops.pid_filter_ctrl(adapter->fe_adap[0].fe, onoff);
+}
+
+/* STK807x */
+static int stk807x_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_adapter_state *state = adap->priv;
+
+	if (!dvb_attach(dib8000_attach, &state->dib8000_ops))
+		return -ENODEV;
+
+	dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 1);
+	msleep(10);
+	dib0700_set_gpio(adap->dev, GPIO9, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO4, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO7, GPIO_OUT, 1);
+
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0);
+
+	dib0700_ctrl_clock(adap->dev, 72, 1);
+
+	msleep(10);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1);
+	msleep(10);
+	dib0700_set_gpio(adap->dev, GPIO0, GPIO_OUT, 1);
+
+	state->dib8000_ops.i2c_enumeration(&adap->dev->i2c_adap, 1, 18,
+				0x80, 0);
+
+	adap->fe_adap[0].fe = state->dib8000_ops.init(&adap->dev->i2c_adap, 0x80,
+			      &dib807x_dib8000_config[0]);
+
+	return adap->fe_adap[0].fe == NULL ?  -ENODEV : 0;
+}
+
+/* STK807xPVR */
+static int stk807xpvr_frontend_attach0(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_adapter_state *state = adap->priv;
+
+	if (!dvb_attach(dib8000_attach, &state->dib8000_ops))
+		return -ENODEV;
+
+	dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 0);
+	msleep(30);
+	dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 1);
+	msleep(500);
+	dib0700_set_gpio(adap->dev, GPIO9, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO4, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO7, GPIO_OUT, 1);
+
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0);
+
+	dib0700_ctrl_clock(adap->dev, 72, 1);
+
+	msleep(10);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1);
+	msleep(10);
+	dib0700_set_gpio(adap->dev, GPIO0, GPIO_OUT, 1);
+
+	/* initialize IC 0 */
+	state->dib8000_ops.i2c_enumeration(&adap->dev->i2c_adap, 1, 0x22, 0x80, 0);
+
+	adap->fe_adap[0].fe = state->dib8000_ops.init(&adap->dev->i2c_adap, 0x80,
+			      &dib807x_dib8000_config[0]);
+
+	return adap->fe_adap[0].fe == NULL ? -ENODEV : 0;
+}
+
+static int stk807xpvr_frontend_attach1(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_adapter_state *state = adap->priv;
+
+	if (!dvb_attach(dib8000_attach, &state->dib8000_ops))
+		return -ENODEV;
+
+	/* initialize IC 1 */
+	state->dib8000_ops.i2c_enumeration(&adap->dev->i2c_adap, 1, 0x12, 0x82, 0);
+
+	adap->fe_adap[0].fe = state->dib8000_ops.init(&adap->dev->i2c_adap, 0x82,
+			      &dib807x_dib8000_config[1]);
+
+	return adap->fe_adap[0].fe == NULL ? -ENODEV : 0;
+}
+
+/* STK8096GP */
+static struct dibx000_agc_config dib8090_agc_config[2] = {
+	{
+	.band_caps = BAND_UHF | BAND_VHF | BAND_LBAND | BAND_SBAND,
+	/* P_agc_use_sd_mod1=0, P_agc_use_sd_mod2=0, P_agc_freq_pwm_div=1,
+	 * P_agc_inv_pwm1=0, P_agc_inv_pwm2=0, P_agc_inh_dc_rv_est=0,
+	 * P_agc_time_est=3, P_agc_freeze=0, P_agc_nb_est=5, P_agc_write=0 */
+	.setup = (0 << 15) | (0 << 14) | (5 << 11) | (0 << 10) | (0 << 9) | (0 << 8)
+	| (3 << 5) | (0 << 4) | (5 << 1) | (0 << 0),
+
+	.inv_gain = 787,
+	.time_stabiliz = 10,
+
+	.alpha_level = 0,
+	.thlock = 118,
+
+	.wbd_inv = 0,
+	.wbd_ref = 3530,
+	.wbd_sel = 1,
+	.wbd_alpha = 5,
+
+	.agc1_max = 65535,
+	.agc1_min = 0,
+
+	.agc2_max = 65535,
+	.agc2_min = 0,
+
+	.agc1_pt1 = 0,
+	.agc1_pt2 = 32,
+	.agc1_pt3 = 114,
+	.agc1_slope1 = 143,
+	.agc1_slope2 = 144,
+	.agc2_pt1 = 114,
+	.agc2_pt2 = 227,
+	.agc2_slope1 = 116,
+	.agc2_slope2 = 117,
+
+	.alpha_mant = 28,
+	.alpha_exp = 26,
+	.beta_mant = 31,
+	.beta_exp = 51,
+
+	.perform_agc_softsplit = 0,
+	},
+	{
+	.band_caps = BAND_CBAND,
+	/* P_agc_use_sd_mod1=0, P_agc_use_sd_mod2=0, P_agc_freq_pwm_div=1,
+	 * P_agc_inv_pwm1=0, P_agc_inv_pwm2=0, P_agc_inh_dc_rv_est=0,
+	 * P_agc_time_est=3, P_agc_freeze=0, P_agc_nb_est=5, P_agc_write=0 */
+	.setup = (0 << 15) | (0 << 14) | (5 << 11) | (0 << 10) | (0 << 9) | (0 << 8)
+	| (3 << 5) | (0 << 4) | (5 << 1) | (0 << 0),
+
+	.inv_gain = 787,
+	.time_stabiliz = 10,
+
+	.alpha_level = 0,
+	.thlock = 118,
+
+	.wbd_inv = 0,
+	.wbd_ref = 3530,
+	.wbd_sel = 1,
+	.wbd_alpha = 5,
+
+	.agc1_max = 0,
+	.agc1_min = 0,
+
+	.agc2_max = 65535,
+	.agc2_min = 0,
+
+	.agc1_pt1 = 0,
+	.agc1_pt2 = 32,
+	.agc1_pt3 = 114,
+	.agc1_slope1 = 143,
+	.agc1_slope2 = 144,
+	.agc2_pt1 = 114,
+	.agc2_pt2 = 227,
+	.agc2_slope1 = 116,
+	.agc2_slope2 = 117,
+
+	.alpha_mant = 28,
+	.alpha_exp = 26,
+	.beta_mant = 31,
+	.beta_exp = 51,
+
+	.perform_agc_softsplit = 0,
+	}
+};
+
+static struct dibx000_bandwidth_config dib8090_pll_config_12mhz = {
+	.internal = 54000,
+	.sampling = 13500,
+
+	.pll_prediv = 1,
+	.pll_ratio = 18,
+	.pll_range = 3,
+	.pll_reset = 1,
+	.pll_bypass = 0,
+
+	.enable_refdiv = 0,
+	.bypclk_div = 0,
+	.IO_CLK_en_core = 1,
+	.ADClkSrc = 1,
+	.modulo = 2,
+
+	.sad_cfg = (3 << 14) | (1 << 12) | (599 << 0),
+
+	.ifreq = (0 << 25) | 0,
+	.timf = 20199727,
+
+	.xtal_hz = 12000000,
+};
+
+static int dib8090_get_adc_power(struct dvb_frontend *fe)
+{
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	return state->dib8000_ops.get_adc_power(fe, 1);
+}
+
+static void dib8090_agc_control(struct dvb_frontend *fe, u8 restart)
+{
+	deb_info("AGC control callback: %i\n", restart);
+	dib0090_dcc_freq(fe, restart);
+
+	if (restart == 0) /* before AGC startup */
+		dib0090_set_dc_servo(fe, 1);
+}
+
+static struct dib8000_config dib809x_dib8000_config[2] = {
+	{
+	.output_mpeg2_in_188_bytes = 1,
+
+	.agc_config_count = 2,
+	.agc = dib8090_agc_config,
+	.agc_control = dib8090_agc_control,
+	.pll = &dib8090_pll_config_12mhz,
+	.tuner_is_baseband = 1,
+
+	.gpio_dir = DIB8000_GPIO_DEFAULT_DIRECTIONS,
+	.gpio_val = DIB8000_GPIO_DEFAULT_VALUES,
+	.gpio_pwm_pos = DIB8000_GPIO_DEFAULT_PWM_POS,
+
+	.hostbus_diversity = 1,
+	.div_cfg = 0x31,
+	.output_mode = OUTMODE_MPEG2_FIFO,
+	.drives = 0x2d98,
+	.diversity_delay = 48,
+	.refclksel = 3,
+	}, {
+	.output_mpeg2_in_188_bytes = 1,
+
+	.agc_config_count = 2,
+	.agc = dib8090_agc_config,
+	.agc_control = dib8090_agc_control,
+	.pll = &dib8090_pll_config_12mhz,
+	.tuner_is_baseband = 1,
+
+	.gpio_dir = DIB8000_GPIO_DEFAULT_DIRECTIONS,
+	.gpio_val = DIB8000_GPIO_DEFAULT_VALUES,
+	.gpio_pwm_pos = DIB8000_GPIO_DEFAULT_PWM_POS,
+
+	.hostbus_diversity = 1,
+	.div_cfg = 0x31,
+	.output_mode = OUTMODE_DIVERSITY,
+	.drives = 0x2d08,
+	.diversity_delay = 1,
+	.refclksel = 3,
+	}
+};
+
+static struct dib0090_wbd_slope dib8090_wbd_table[] = {
+	/* max freq ; cold slope ; cold offset ; warm slope ; warm offset ; wbd gain */
+	{ 120,     0, 500,  0,   500, 4 }, /* CBAND */
+	{ 170,     0, 450,  0,   450, 4 }, /* CBAND */
+	{ 380,    48, 373, 28,   259, 6 }, /* VHF */
+	{ 860,    34, 700, 36,   616, 6 }, /* high UHF */
+	{ 0xFFFF, 34, 700, 36,   616, 6 }, /* default */
+};
+
+static struct dib0090_config dib809x_dib0090_config = {
+	.io.pll_bypass = 1,
+	.io.pll_range = 1,
+	.io.pll_prediv = 1,
+	.io.pll_loopdiv = 20,
+	.io.adc_clock_ratio = 8,
+	.io.pll_int_loop_filt = 0,
+	.io.clock_khz = 12000,
+	.reset = dib80xx_tuner_reset,
+	.sleep = dib80xx_tuner_sleep,
+	.clkouttobamse = 1,
+	.analog_output = 1,
+	.i2c_address = DEFAULT_DIB0090_I2C_ADDRESS,
+	.use_pwm_agc = 1,
+	.clkoutdrive = 1,
+	.get_adc_power = dib8090_get_adc_power,
+	.freq_offset_khz_uhf = -63,
+	.freq_offset_khz_vhf = -143,
+	.wbd = dib8090_wbd_table,
+	.fref_clock_ratio = 6,
+};
+
+static u8 dib8090_compute_pll_parameters(struct dvb_frontend *fe)
+{
+	u8 optimal_pll_ratio = 20;
+	u32 freq_adc, ratio, rest, max = 0;
+	u8 pll_ratio;
+
+	for (pll_ratio = 17; pll_ratio <= 20; pll_ratio++) {
+		freq_adc = 12 * pll_ratio * (1 << 8) / 16;
+		ratio = ((fe->dtv_property_cache.frequency / 1000) * (1 << 8) / 1000) / freq_adc;
+		rest = ((fe->dtv_property_cache.frequency / 1000) * (1 << 8) / 1000) - ratio * freq_adc;
+
+		if (rest > freq_adc / 2)
+			rest = freq_adc - rest;
+		deb_info("PLL ratio=%i rest=%i\n", pll_ratio, rest);
+		if ((rest > max) && (rest > 717)) {
+			optimal_pll_ratio = pll_ratio;
+			max = rest;
+		}
+	}
+	deb_info("optimal PLL ratio=%i\n", optimal_pll_ratio);
+
+	return optimal_pll_ratio;
+}
+
+static int dib8096_set_param_override(struct dvb_frontend *fe)
+{
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct dib0700_adapter_state *state = adap->priv;
+	u8 pll_ratio, band = BAND_OF_FREQUENCY(fe->dtv_property_cache.frequency / 1000);
+	u16 target, ltgain, rf_gain_limit;
+	u32 timf;
+	int ret = 0;
+	enum frontend_tune_state tune_state = CT_SHUTDOWN;
+
+	switch (band) {
+	default:
+			deb_info("Warning : Rf frequency  (%iHz) is not in the supported range, using VHF switch ", fe->dtv_property_cache.frequency);
+			/* fall through */
+	case BAND_VHF:
+			state->dib8000_ops.set_gpio(fe, 3, 0, 1);
+			break;
+	case BAND_UHF:
+			state->dib8000_ops.set_gpio(fe, 3, 0, 0);
+			break;
+	}
+
+	ret = state->set_param_save(fe);
+	if (ret < 0)
+		return ret;
+
+	if (fe->dtv_property_cache.bandwidth_hz != 6000000) {
+		deb_info("only 6MHz bandwidth is supported\n");
+		return -EINVAL;
+	}
+
+	/* Update PLL if needed ratio */
+	state->dib8000_ops.update_pll(fe, &dib8090_pll_config_12mhz, fe->dtv_property_cache.bandwidth_hz / 1000, 0);
+
+	/* Get optimize PLL ratio to remove spurious */
+	pll_ratio = dib8090_compute_pll_parameters(fe);
+	if (pll_ratio == 17)
+		timf = 21387946;
+	else if (pll_ratio == 18)
+		timf = 20199727;
+	else if (pll_ratio == 19)
+		timf = 19136583;
+	else
+		timf = 18179756;
+
+	/* Update ratio */
+	state->dib8000_ops.update_pll(fe, &dib8090_pll_config_12mhz, fe->dtv_property_cache.bandwidth_hz / 1000, pll_ratio);
+
+	state->dib8000_ops.ctrl_timf(fe, DEMOD_TIMF_SET, timf);
+
+	if (band != BAND_CBAND) {
+		/* dib0090_get_wbd_target is returning any possible temperature compensated wbd-target */
+		target = (dib0090_get_wbd_target(fe) * 8 * 18 / 33 + 1) / 2;
+		state->dib8000_ops.set_wbd_ref(fe, target);
+	}
+
+	if (band == BAND_CBAND) {
+		deb_info("tuning in CBAND - soft-AGC startup\n");
+		dib0090_set_tune_state(fe, CT_AGC_START);
+
+		do {
+			ret = dib0090_gain_control(fe);
+			msleep(ret);
+			tune_state = dib0090_get_tune_state(fe);
+			if (tune_state == CT_AGC_STEP_0)
+				state->dib8000_ops.set_gpio(fe, 6, 0, 1);
+			else if (tune_state == CT_AGC_STEP_1) {
+				dib0090_get_current_gain(fe, NULL, NULL, &rf_gain_limit, &ltgain);
+				if (rf_gain_limit < 2000) /* activate the external attenuator in case of very high input power */
+					state->dib8000_ops.set_gpio(fe, 6, 0, 0);
+			}
+		} while (tune_state < CT_AGC_STOP);
+
+		deb_info("switching to PWM AGC\n");
+		dib0090_pwm_gain_reset(fe);
+		state->dib8000_ops.pwm_agc_reset(fe);
+		state->dib8000_ops.set_tune_state(fe, CT_DEMOD_START);
+	} else {
+		/* for everything else than CBAND we are using standard AGC */
+		deb_info("not tuning in CBAND - standard AGC startup\n");
+		dib0090_pwm_gain_reset(fe);
+	}
+
+	return 0;
+}
+
+static int dib809x_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_adapter_state *st = adap->priv;
+	struct i2c_adapter *tun_i2c = st->dib8000_ops.get_i2c_master(adap->fe_adap[0].fe, DIBX000_I2C_INTERFACE_TUNER, 1);
+
+	if (adap->id == 0) {
+		if (dvb_attach(dib0090_register, adap->fe_adap[0].fe, tun_i2c, &dib809x_dib0090_config) == NULL)
+			return -ENODEV;
+	} else {
+		/* FIXME: check if it is fe_adap[1] */
+		if (dvb_attach(dib0090_register, adap->fe_adap[0].fe, tun_i2c, &dib809x_dib0090_config) == NULL)
+			return -ENODEV;
+	}
+
+	st->set_param_save = adap->fe_adap[0].fe->ops.tuner_ops.set_params;
+	adap->fe_adap[0].fe->ops.tuner_ops.set_params = dib8096_set_param_override;
+	return 0;
+}
+
+static int stk809x_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_adapter_state *state = adap->priv;
+
+	if (!dvb_attach(dib8000_attach, &state->dib8000_ops))
+		return -ENODEV;
+
+	dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 1);
+	msleep(10);
+	dib0700_set_gpio(adap->dev, GPIO9, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO4, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO7, GPIO_OUT, 1);
+
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0);
+
+	dib0700_ctrl_clock(adap->dev, 72, 1);
+
+	msleep(10);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1);
+	msleep(10);
+	dib0700_set_gpio(adap->dev, GPIO0, GPIO_OUT, 1);
+
+	state->dib8000_ops.i2c_enumeration(&adap->dev->i2c_adap, 1, 18, 0x80, 0);
+
+	adap->fe_adap[0].fe = state->dib8000_ops.init(&adap->dev->i2c_adap, 0x80, &dib809x_dib8000_config[0]);
+
+	return adap->fe_adap[0].fe == NULL ?  -ENODEV : 0;
+}
+
+static int stk809x_frontend1_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_adapter_state *state = adap->priv;
+
+	if (!dvb_attach(dib8000_attach, &state->dib8000_ops))
+		return -ENODEV;
+
+	state->dib8000_ops.i2c_enumeration(&adap->dev->i2c_adap, 1, 0x10, 0x82, 0);
+
+	adap->fe_adap[0].fe = state->dib8000_ops.init(&adap->dev->i2c_adap, 0x82, &dib809x_dib8000_config[1]);
+
+	return adap->fe_adap[0].fe == NULL ?  -ENODEV : 0;
+}
+
+static int nim8096md_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_adapter_state *st = adap->priv;
+	struct i2c_adapter *tun_i2c;
+	struct dvb_frontend *fe_slave  = st->dib8000_ops.get_slave_frontend(adap->fe_adap[0].fe, 1);
+
+	if (fe_slave) {
+		tun_i2c = st->dib8000_ops.get_i2c_master(fe_slave, DIBX000_I2C_INTERFACE_TUNER, 1);
+		if (dvb_attach(dib0090_register, fe_slave, tun_i2c, &dib809x_dib0090_config) == NULL)
+			return -ENODEV;
+		fe_slave->dvb = adap->fe_adap[0].fe->dvb;
+		fe_slave->ops.tuner_ops.set_params = dib8096_set_param_override;
+	}
+	tun_i2c = st->dib8000_ops.get_i2c_master(adap->fe_adap[0].fe, DIBX000_I2C_INTERFACE_TUNER, 1);
+	if (dvb_attach(dib0090_register, adap->fe_adap[0].fe, tun_i2c, &dib809x_dib0090_config) == NULL)
+		return -ENODEV;
+
+	st->set_param_save = adap->fe_adap[0].fe->ops.tuner_ops.set_params;
+	adap->fe_adap[0].fe->ops.tuner_ops.set_params = dib8096_set_param_override;
+
+	return 0;
+}
+
+static int nim8096md_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_frontend *fe_slave;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	if (!dvb_attach(dib8000_attach, &state->dib8000_ops))
+		return -ENODEV;
+
+	dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 0);
+	msleep(20);
+	dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 1);
+	msleep(1000);
+	dib0700_set_gpio(adap->dev, GPIO9, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO4, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO7, GPIO_OUT, 1);
+
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0);
+
+	dib0700_ctrl_clock(adap->dev, 72, 1);
+
+	msleep(20);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1);
+	msleep(20);
+	dib0700_set_gpio(adap->dev, GPIO0, GPIO_OUT, 1);
+
+	state->dib8000_ops.i2c_enumeration(&adap->dev->i2c_adap, 2, 18, 0x80, 0);
+
+	adap->fe_adap[0].fe = state->dib8000_ops.init(&adap->dev->i2c_adap, 0x80, &dib809x_dib8000_config[0]);
+	if (adap->fe_adap[0].fe == NULL)
+		return -ENODEV;
+
+	/* Needed to increment refcount */
+	if (!dvb_attach(dib8000_attach, &state->dib8000_ops))
+		return -ENODEV;
+
+	fe_slave = state->dib8000_ops.init(&adap->dev->i2c_adap, 0x82, &dib809x_dib8000_config[1]);
+	state->dib8000_ops.set_slave_frontend(adap->fe_adap[0].fe, fe_slave);
+
+	return fe_slave == NULL ?  -ENODEV : 0;
+}
+
+/* TFE8096P */
+static struct dibx000_agc_config dib8096p_agc_config[2] = {
+	{
+		.band_caps		= BAND_UHF,
+		/* P_agc_use_sd_mod1=0, P_agc_use_sd_mod2=0,
+		   P_agc_freq_pwm_div=1, P_agc_inv_pwm1=0,
+		   P_agc_inv_pwm2=0, P_agc_inh_dc_rv_est=0,
+		   P_agc_time_est=3, P_agc_freeze=0, P_agc_nb_est=5,
+		   P_agc_write=0 */
+		.setup			= (0 << 15) | (0 << 14) | (5 << 11)
+			| (0 << 10) | (0 << 9) | (0 << 8) | (3 << 5)
+			| (0 << 4) | (5 << 1) | (0 << 0),
+
+		.inv_gain		= 684,
+		.time_stabiliz	= 10,
+
+		.alpha_level	= 0,
+		.thlock			= 118,
+
+		.wbd_inv		= 0,
+		.wbd_ref		= 1200,
+		.wbd_sel		= 3,
+		.wbd_alpha		= 5,
+
+		.agc1_max		= 65535,
+		.agc1_min		= 0,
+
+		.agc2_max		= 32767,
+		.agc2_min		= 0,
+
+		.agc1_pt1		= 0,
+		.agc1_pt2		= 0,
+		.agc1_pt3		= 105,
+		.agc1_slope1	= 0,
+		.agc1_slope2	= 156,
+		.agc2_pt1		= 105,
+		.agc2_pt2		= 255,
+		.agc2_slope1	= 54,
+		.agc2_slope2	= 0,
+
+		.alpha_mant		= 28,
+		.alpha_exp		= 26,
+		.beta_mant		= 31,
+		.beta_exp		= 51,
+
+		.perform_agc_softsplit = 0,
+	} , {
+		.band_caps		= BAND_FM | BAND_VHF | BAND_CBAND,
+		/* P_agc_use_sd_mod1=0, P_agc_use_sd_mod2=0,
+		   P_agc_freq_pwm_div=1, P_agc_inv_pwm1=0,
+		   P_agc_inv_pwm2=0, P_agc_inh_dc_rv_est=0,
+		   P_agc_time_est=3, P_agc_freeze=0, P_agc_nb_est=5,
+		   P_agc_write=0 */
+		.setup			= (0 << 15) | (0 << 14) | (5 << 11)
+			| (0 << 10) | (0 << 9) | (0 << 8) | (3 << 5)
+			| (0 << 4) | (5 << 1) | (0 << 0),
+
+		.inv_gain		= 732,
+		.time_stabiliz  = 10,
+
+		.alpha_level	= 0,
+		.thlock			= 118,
+
+		.wbd_inv		= 0,
+		.wbd_ref		= 1200,
+		.wbd_sel		= 3,
+		.wbd_alpha		= 5,
+
+		.agc1_max		= 65535,
+		.agc1_min		= 0,
+
+		.agc2_max		= 32767,
+		.agc2_min		= 0,
+
+		.agc1_pt1		= 0,
+		.agc1_pt2		= 0,
+		.agc1_pt3		= 98,
+		.agc1_slope1	= 0,
+		.agc1_slope2	= 167,
+		.agc2_pt1		= 98,
+		.agc2_pt2		= 255,
+		.agc2_slope1	= 52,
+		.agc2_slope2	= 0,
+
+		.alpha_mant		= 28,
+		.alpha_exp		= 26,
+		.beta_mant		= 31,
+		.beta_exp		= 51,
+
+		.perform_agc_softsplit = 0,
+	}
+};
+
+static struct dibx000_bandwidth_config dib8096p_clock_config_12_mhz = {
+	.internal = 108000,
+	.sampling = 13500,
+	.pll_prediv = 1,
+	.pll_ratio = 9,
+	.pll_range = 1,
+	.pll_reset = 0,
+	.pll_bypass = 0,
+	.enable_refdiv = 0,
+	.bypclk_div = 0,
+	.IO_CLK_en_core = 0,
+	.ADClkSrc = 0,
+	.modulo = 2,
+	.sad_cfg = (3 << 14) | (1 << 12) | (524 << 0),
+	.ifreq = (0 << 25) | 0,
+	.timf = 20199729,
+	.xtal_hz = 12000000,
+};
+
+static struct dib8000_config tfe8096p_dib8000_config = {
+	.output_mpeg2_in_188_bytes	= 1,
+	.hostbus_diversity			= 1,
+	.update_lna					= NULL,
+
+	.agc_config_count			= 2,
+	.agc						= dib8096p_agc_config,
+	.pll						= &dib8096p_clock_config_12_mhz,
+
+	.gpio_dir					= DIB8000_GPIO_DEFAULT_DIRECTIONS,
+	.gpio_val					= DIB8000_GPIO_DEFAULT_VALUES,
+	.gpio_pwm_pos				= DIB8000_GPIO_DEFAULT_PWM_POS,
+
+	.agc_control				= NULL,
+	.diversity_delay			= 48,
+	.output_mode				= OUTMODE_MPEG2_FIFO,
+	.enMpegOutput				= 1,
+};
+
+static struct dib0090_wbd_slope dib8096p_wbd_table[] = {
+	{ 380, 81, 850, 64, 540, 4},
+	{ 860, 51, 866, 21, 375, 4},
+	{1700, 0, 250, 0, 100, 6},
+	{2600, 0, 250, 0, 100, 6},
+	{ 0xFFFF, 0, 0, 0, 0, 0},
+};
+
+static struct dib0090_config tfe8096p_dib0090_config = {
+	.io.clock_khz			= 12000,
+	.io.pll_bypass			= 0,
+	.io.pll_range			= 0,
+	.io.pll_prediv			= 3,
+	.io.pll_loopdiv			= 6,
+	.io.adc_clock_ratio		= 0,
+	.io.pll_int_loop_filt	= 0,
+
+	.freq_offset_khz_uhf	= -143,
+	.freq_offset_khz_vhf	= -143,
+
+	.get_adc_power			= dib8090_get_adc_power,
+
+	.clkouttobamse			= 1,
+	.analog_output			= 0,
+
+	.wbd_vhf_offset			= 0,
+	.wbd_cband_offset		= 0,
+	.use_pwm_agc			= 1,
+	.clkoutdrive			= 0,
+
+	.fref_clock_ratio		= 1,
+
+	.ls_cfg_pad_drv			= 0,
+	.data_tx_drv			= 0,
+	.low_if					= NULL,
+	.in_soc					= 1,
+	.force_cband_input		= 0,
+};
+
+struct dibx090p_adc {
+	u32 freq;			/* RF freq MHz */
+	u32 timf;			/* New Timf */
+	u32 pll_loopdiv;	/* New prediv */
+	u32 pll_prediv;		/* New loopdiv */
+};
+
+struct dibx090p_best_adc {
+	u32 timf;
+	u32 pll_loopdiv;
+	u32 pll_prediv;
+};
+
+static int dib8096p_get_best_sampling(struct dvb_frontend *fe, struct dibx090p_best_adc *adc)
+{
+	u8 spur = 0, prediv = 0, loopdiv = 0, min_prediv = 1, max_prediv = 1;
+	u16 xtal = 12000;
+	u16 fcp_min = 1900;  /* PLL, Minimum Frequency of phase comparator (KHz) */
+	u16 fcp_max = 20000; /* PLL, Maximum Frequency of phase comparator (KHz) */
+	u32 fmem_max = 140000; /* 140MHz max SDRAM freq */
+	u32 fdem_min = 66000;
+	u32 fcp = 0, fs = 0, fdem = 0, fmem = 0;
+	u32 harmonic_id = 0;
+
+	adc->timf = 0;
+	adc->pll_loopdiv = loopdiv;
+	adc->pll_prediv = prediv;
+
+	deb_info("bandwidth = %d", fe->dtv_property_cache.bandwidth_hz);
+
+	/* Find Min and Max prediv */
+	while ((xtal / max_prediv) >= fcp_min)
+		max_prediv++;
+
+	max_prediv--;
+	min_prediv = max_prediv;
+	while ((xtal / min_prediv) <= fcp_max) {
+		min_prediv--;
+		if (min_prediv == 1)
+			break;
+	}
+	deb_info("MIN prediv = %d : MAX prediv = %d", min_prediv, max_prediv);
+
+	min_prediv = 1;
+
+	for (prediv = min_prediv; prediv < max_prediv; prediv++) {
+		fcp = xtal / prediv;
+		if (fcp > fcp_min && fcp < fcp_max) {
+			for (loopdiv = 1; loopdiv < 64; loopdiv++) {
+				fmem = ((xtal/prediv) * loopdiv);
+				fdem = fmem / 2;
+				fs   = fdem / 4;
+
+				/* test min/max system restrictions */
+				if ((fdem >= fdem_min) && (fmem <= fmem_max) && (fs >= fe->dtv_property_cache.bandwidth_hz / 1000)) {
+					spur = 0;
+					/* test fs harmonics positions */
+					for (harmonic_id = (fe->dtv_property_cache.frequency / (1000 * fs));  harmonic_id <= ((fe->dtv_property_cache.frequency / (1000 * fs)) + 1); harmonic_id++) {
+						if (((fs * harmonic_id) >= (fe->dtv_property_cache.frequency / 1000 - (fe->dtv_property_cache.bandwidth_hz / 2000))) &&  ((fs * harmonic_id) <= (fe->dtv_property_cache.frequency / 1000 + (fe->dtv_property_cache.bandwidth_hz / 2000)))) {
+							spur = 1;
+							break;
+						}
+					}
+
+					if (!spur) {
+						adc->pll_loopdiv = loopdiv;
+						adc->pll_prediv = prediv;
+						adc->timf = (4260880253U / fdem) * (1 << 8);
+						adc->timf += ((4260880253U % fdem) << 8) / fdem;
+
+						deb_info("RF %6d; BW %6d; Xtal %6d; Fmem %6d; Fdem %6d; Fs %6d; Prediv %2d; Loopdiv %2d; Timf %8d;", fe->dtv_property_cache.frequency, fe->dtv_property_cache.bandwidth_hz, xtal, fmem, fdem, fs, prediv, loopdiv, adc->timf);
+						break;
+					}
+				}
+			}
+		}
+		if (!spur)
+			break;
+	}
+
+	if (adc->pll_loopdiv == 0 && adc->pll_prediv == 0)
+		return -EINVAL;
+	return 0;
+}
+
+static int dib8096p_agc_startup(struct dvb_frontend *fe)
+{
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct dib0700_adapter_state *state = adap->priv;
+	struct dibx000_bandwidth_config pll;
+	struct dibx090p_best_adc adc;
+	u16 target;
+	int ret;
+
+	ret = state->set_param_save(fe);
+	if (ret < 0)
+		return ret;
+	memset(&pll, 0, sizeof(struct dibx000_bandwidth_config));
+
+	dib0090_pwm_gain_reset(fe);
+	/* dib0090_get_wbd_target is returning any possible
+	   temperature compensated wbd-target */
+	target = (dib0090_get_wbd_target(fe) * 8  + 1) / 2;
+	state->dib8000_ops.set_wbd_ref(fe, target);
+
+	if (dib8096p_get_best_sampling(fe, &adc) == 0) {
+		pll.pll_ratio  = adc.pll_loopdiv;
+		pll.pll_prediv = adc.pll_prediv;
+
+		dib0700_set_i2c_speed(adap->dev, 200);
+		state->dib8000_ops.update_pll(fe, &pll, fe->dtv_property_cache.bandwidth_hz / 1000, 0);
+		state->dib8000_ops.ctrl_timf(fe, DEMOD_TIMF_SET, adc.timf);
+		dib0700_set_i2c_speed(adap->dev, 1000);
+	}
+	return 0;
+}
+
+static int tfe8096p_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_state *st = adap->dev->priv;
+	u32 fw_version;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	if (!dvb_attach(dib8000_attach, &state->dib8000_ops))
+		return -ENODEV;
+
+	dib0700_get_version(adap->dev, NULL, NULL, &fw_version, NULL);
+	if (fw_version >= 0x10200)
+		st->fw_use_new_i2c_api = 1;
+
+	dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 1);
+	msleep(20);
+	dib0700_set_gpio(adap->dev, GPIO9, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO4, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO7, GPIO_OUT, 1);
+
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0);
+
+	dib0700_ctrl_clock(adap->dev, 72, 1);
+
+	msleep(20);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1);
+	msleep(20);
+	dib0700_set_gpio(adap->dev, GPIO0, GPIO_OUT, 1);
+
+	state->dib8000_ops.i2c_enumeration(&adap->dev->i2c_adap, 1, 0x10, 0x80, 1);
+
+	adap->fe_adap[0].fe = state->dib8000_ops.init(&adap->dev->i2c_adap,
+					     0x80, &tfe8096p_dib8000_config);
+
+	return adap->fe_adap[0].fe == NULL ?  -ENODEV : 0;
+}
+
+static int tfe8096p_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_adapter_state *st = adap->priv;
+	struct i2c_adapter *tun_i2c = st->dib8000_ops.get_i2c_tuner(adap->fe_adap[0].fe);
+
+	tfe8096p_dib0090_config.reset = st->dib8000_ops.tuner_sleep;
+	tfe8096p_dib0090_config.sleep = st->dib8000_ops.tuner_sleep;
+	tfe8096p_dib0090_config.wbd = dib8096p_wbd_table;
+
+	if (dvb_attach(dib0090_register, adap->fe_adap[0].fe, tun_i2c,
+				&tfe8096p_dib0090_config) == NULL)
+		return -ENODEV;
+
+	st->dib8000_ops.set_gpio(adap->fe_adap[0].fe, 8, 0, 1);
+
+	st->set_param_save = adap->fe_adap[0].fe->ops.tuner_ops.set_params;
+	adap->fe_adap[0].fe->ops.tuner_ops.set_params = dib8096p_agc_startup;
+	return 0;
+}
+
+/* STK9090M */
+static int dib90x0_pid_filter(struct dvb_usb_adapter *adapter, int index, u16 pid, int onoff)
+{
+	return dib9000_fw_pid_filter(adapter->fe_adap[0].fe, index, pid, onoff);
+}
+
+static int dib90x0_pid_filter_ctrl(struct dvb_usb_adapter *adapter, int onoff)
+{
+	return dib9000_fw_pid_filter_ctrl(adapter->fe_adap[0].fe, onoff);
+}
+
+static int dib90x0_tuner_reset(struct dvb_frontend *fe, int onoff)
+{
+	return dib9000_set_gpio(fe, 5, 0, !onoff);
+}
+
+static int dib90x0_tuner_sleep(struct dvb_frontend *fe, int onoff)
+{
+	return dib9000_set_gpio(fe, 0, 0, onoff);
+}
+
+static int dib01x0_pmu_update(struct i2c_adapter *i2c, u16 *data, u8 len)
+{
+	u8 wb[4] = { 0xc >> 8, 0xc & 0xff, 0, 0 };
+	u8 rb[2];
+	struct i2c_msg msg[2] = {
+		{.addr = 0x1e >> 1, .flags = 0, .buf = wb, .len = 2},
+		{.addr = 0x1e >> 1, .flags = I2C_M_RD, .buf = rb, .len = 2},
+	};
+	u8 index_data;
+
+	dibx000_i2c_set_speed(i2c, 250);
+
+	if (i2c_transfer(i2c, msg, 2) != 2)
+		return -EIO;
+
+	switch (rb[0] << 8 | rb[1]) {
+	case 0:
+			deb_info("Found DiB0170 rev1: This version of DiB0170 is not supported any longer.\n");
+			return -EIO;
+	case 1:
+			deb_info("Found DiB0170 rev2");
+			break;
+	case 2:
+			deb_info("Found DiB0190 rev2");
+			break;
+	default:
+			deb_info("DiB01x0 not found");
+			return -EIO;
+	}
+
+	for (index_data = 0; index_data < len; index_data += 2) {
+		wb[2] = (data[index_data + 1] >> 8) & 0xff;
+		wb[3] = (data[index_data + 1]) & 0xff;
+
+		if (data[index_data] == 0) {
+			wb[0] = (data[index_data] >> 8) & 0xff;
+			wb[1] = (data[index_data]) & 0xff;
+			msg[0].len = 2;
+			if (i2c_transfer(i2c, msg, 2) != 2)
+				return -EIO;
+			wb[2] |= rb[0];
+			wb[3] |= rb[1] & ~(3 << 4);
+		}
+
+		wb[0] = (data[index_data] >> 8)&0xff;
+		wb[1] = (data[index_data])&0xff;
+		msg[0].len = 4;
+		if (i2c_transfer(i2c, &msg[0], 1) != 1)
+			return -EIO;
+	}
+	return 0;
+}
+
+static struct dib9000_config stk9090m_config = {
+	.output_mpeg2_in_188_bytes = 1,
+	.output_mode = OUTMODE_MPEG2_FIFO,
+	.vcxo_timer = 279620,
+	.timing_frequency = 20452225,
+	.demod_clock_khz = 60000,
+	.xtal_clock_khz = 30000,
+	.if_drives = (0 << 15) | (1 << 13) | (0 << 12) | (3 << 10) | (0 << 9) | (1 << 7) | (0 << 6) | (0 << 4) | (1 << 3) | (1 << 1) | (0),
+	.subband = {
+		2,
+		{
+			{ 240, { BOARD_GPIO_COMPONENT_DEMOD, BOARD_GPIO_FUNCTION_SUBBAND_GPIO, 0x0008, 0x0000, 0x0008 } }, /* GPIO 3 to 1 for VHF */
+			{ 890, { BOARD_GPIO_COMPONENT_DEMOD, BOARD_GPIO_FUNCTION_SUBBAND_GPIO, 0x0008, 0x0000, 0x0000 } }, /* GPIO 3 to 0 for UHF */
+			{ 0 },
+		},
+	},
+	.gpio_function = {
+		{ .component = BOARD_GPIO_COMPONENT_DEMOD, .function = BOARD_GPIO_FUNCTION_COMPONENT_ON, .mask = 0x10 | 0x21, .direction = 0 & ~0x21, .value = (0x10 & ~0x1) | 0x20 },
+		{ .component = BOARD_GPIO_COMPONENT_DEMOD, .function = BOARD_GPIO_FUNCTION_COMPONENT_OFF, .mask = 0x10 | 0x21, .direction = 0 & ~0x21, .value = 0 | 0x21 },
+	},
+};
+
+static struct dib9000_config nim9090md_config[2] = {
+	{
+		.output_mpeg2_in_188_bytes = 1,
+		.output_mode = OUTMODE_MPEG2_FIFO,
+		.vcxo_timer = 279620,
+		.timing_frequency = 20452225,
+		.demod_clock_khz = 60000,
+		.xtal_clock_khz = 30000,
+		.if_drives = (0 << 15) | (1 << 13) | (0 << 12) | (3 << 10) | (0 << 9) | (1 << 7) | (0 << 6) | (0 << 4) | (1 << 3) | (1 << 1) | (0),
+	}, {
+		.output_mpeg2_in_188_bytes = 1,
+		.output_mode = OUTMODE_DIVERSITY,
+		.vcxo_timer = 279620,
+		.timing_frequency = 20452225,
+		.demod_clock_khz = 60000,
+		.xtal_clock_khz = 30000,
+		.if_drives = (0 << 15) | (1 << 13) | (0 << 12) | (3 << 10) | (0 << 9) | (1 << 7) | (0 << 6) | (0 << 4) | (1 << 3) | (1 << 1) | (0),
+		.subband = {
+			2,
+			{
+				{ 240, { BOARD_GPIO_COMPONENT_DEMOD, BOARD_GPIO_FUNCTION_SUBBAND_GPIO, 0x0006, 0x0000, 0x0006 } }, /* GPIO 1 and 2 to 1 for VHF */
+				{ 890, { BOARD_GPIO_COMPONENT_DEMOD, BOARD_GPIO_FUNCTION_SUBBAND_GPIO, 0x0006, 0x0000, 0x0000 } }, /* GPIO 1 and 2 to 0 for UHF */
+				{ 0 },
+			},
+		},
+		.gpio_function = {
+			{ .component = BOARD_GPIO_COMPONENT_DEMOD, .function = BOARD_GPIO_FUNCTION_COMPONENT_ON, .mask = 0x10 | 0x21, .direction = 0 & ~0x21, .value = (0x10 & ~0x1) | 0x20 },
+			{ .component = BOARD_GPIO_COMPONENT_DEMOD, .function = BOARD_GPIO_FUNCTION_COMPONENT_OFF, .mask = 0x10 | 0x21, .direction = 0 & ~0x21, .value = 0 | 0x21 },
+		},
+	}
+};
+
+static struct dib0090_config dib9090_dib0090_config = {
+	.io.pll_bypass = 0,
+	.io.pll_range = 1,
+	.io.pll_prediv = 1,
+	.io.pll_loopdiv = 8,
+	.io.adc_clock_ratio = 8,
+	.io.pll_int_loop_filt = 0,
+	.io.clock_khz = 30000,
+	.reset = dib90x0_tuner_reset,
+	.sleep = dib90x0_tuner_sleep,
+	.clkouttobamse = 0,
+	.analog_output = 0,
+	.use_pwm_agc = 0,
+	.clkoutdrive = 0,
+	.freq_offset_khz_uhf = 0,
+	.freq_offset_khz_vhf = 0,
+};
+
+static struct dib0090_config nim9090md_dib0090_config[2] = {
+	{
+		.io.pll_bypass = 0,
+		.io.pll_range = 1,
+		.io.pll_prediv = 1,
+		.io.pll_loopdiv = 8,
+		.io.adc_clock_ratio = 8,
+		.io.pll_int_loop_filt = 0,
+		.io.clock_khz = 30000,
+		.reset = dib90x0_tuner_reset,
+		.sleep = dib90x0_tuner_sleep,
+		.clkouttobamse = 1,
+		.analog_output = 0,
+		.use_pwm_agc = 0,
+		.clkoutdrive = 0,
+		.freq_offset_khz_uhf = 0,
+		.freq_offset_khz_vhf = 0,
+	}, {
+		.io.pll_bypass = 0,
+		.io.pll_range = 1,
+		.io.pll_prediv = 1,
+		.io.pll_loopdiv = 8,
+		.io.adc_clock_ratio = 8,
+		.io.pll_int_loop_filt = 0,
+		.io.clock_khz = 30000,
+		.reset = dib90x0_tuner_reset,
+		.sleep = dib90x0_tuner_sleep,
+		.clkouttobamse = 0,
+		.analog_output = 0,
+		.use_pwm_agc = 0,
+		.clkoutdrive = 0,
+		.freq_offset_khz_uhf = 0,
+		.freq_offset_khz_vhf = 0,
+	}
+};
+
+
+static int stk9090m_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_adapter_state *state = adap->priv;
+	struct dib0700_state *st = adap->dev->priv;
+	u32 fw_version;
+
+	/* Make use of the new i2c functions from FW 1.20 */
+	dib0700_get_version(adap->dev, NULL, NULL, &fw_version, NULL);
+	if (fw_version >= 0x10200)
+		st->fw_use_new_i2c_api = 1;
+	dib0700_set_i2c_speed(adap->dev, 340);
+
+	dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 1);
+	msleep(20);
+	dib0700_set_gpio(adap->dev, GPIO9, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO4, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO7, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0);
+
+	dib0700_ctrl_clock(adap->dev, 72, 1);
+
+	msleep(20);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1);
+	msleep(20);
+	dib0700_set_gpio(adap->dev, GPIO0, GPIO_OUT, 1);
+
+	dib9000_i2c_enumeration(&adap->dev->i2c_adap, 1, 0x10, 0x80);
+
+	if (request_firmware(&state->frontend_firmware, "dib9090.fw", &adap->dev->udev->dev)) {
+		deb_info("%s: Upload failed. (file not found?)\n", __func__);
+		return -ENODEV;
+	} else {
+		deb_info("%s: firmware read %zu bytes.\n", __func__, state->frontend_firmware->size);
+	}
+	stk9090m_config.microcode_B_fe_size = state->frontend_firmware->size;
+	stk9090m_config.microcode_B_fe_buffer = state->frontend_firmware->data;
+
+	adap->fe_adap[0].fe = dvb_attach(dib9000_attach, &adap->dev->i2c_adap, 0x80, &stk9090m_config);
+
+	return adap->fe_adap[0].fe == NULL ?  -ENODEV : 0;
+}
+
+static int dib9090_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_adapter_state *state = adap->priv;
+	struct i2c_adapter *i2c = dib9000_get_tuner_interface(adap->fe_adap[0].fe);
+	u16 data_dib190[10] = {
+		1, 0x1374,
+		2, 0x01a2,
+		7, 0x0020,
+		0, 0x00ef,
+		8, 0x0486,
+	};
+
+	if (dvb_attach(dib0090_fw_register, adap->fe_adap[0].fe, i2c, &dib9090_dib0090_config) == NULL)
+		return -ENODEV;
+	i2c = dib9000_get_i2c_master(adap->fe_adap[0].fe, DIBX000_I2C_INTERFACE_GPIO_1_2, 0);
+	if (dib01x0_pmu_update(i2c, data_dib190, 10) != 0)
+		return -ENODEV;
+	dib0700_set_i2c_speed(adap->dev, 1500);
+	if (dib9000_firmware_post_pll_init(adap->fe_adap[0].fe) < 0)
+		return -ENODEV;
+	release_firmware(state->frontend_firmware);
+	return 0;
+}
+
+static int nim9090md_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_adapter_state *state = adap->priv;
+	struct dib0700_state *st = adap->dev->priv;
+	struct i2c_adapter *i2c;
+	struct dvb_frontend *fe_slave;
+	u32 fw_version;
+
+	/* Make use of the new i2c functions from FW 1.20 */
+	dib0700_get_version(adap->dev, NULL, NULL, &fw_version, NULL);
+	if (fw_version >= 0x10200)
+		st->fw_use_new_i2c_api = 1;
+	dib0700_set_i2c_speed(adap->dev, 340);
+
+	dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 1);
+	msleep(20);
+	dib0700_set_gpio(adap->dev, GPIO9, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO4, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO7, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0);
+
+	dib0700_ctrl_clock(adap->dev, 72, 1);
+
+	msleep(20);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1);
+	msleep(20);
+	dib0700_set_gpio(adap->dev, GPIO0, GPIO_OUT, 1);
+
+	if (request_firmware(&state->frontend_firmware, "dib9090.fw", &adap->dev->udev->dev)) {
+		deb_info("%s: Upload failed. (file not found?)\n", __func__);
+		return -EIO;
+	} else {
+		deb_info("%s: firmware read %zu bytes.\n", __func__, state->frontend_firmware->size);
+	}
+	nim9090md_config[0].microcode_B_fe_size = state->frontend_firmware->size;
+	nim9090md_config[0].microcode_B_fe_buffer = state->frontend_firmware->data;
+	nim9090md_config[1].microcode_B_fe_size = state->frontend_firmware->size;
+	nim9090md_config[1].microcode_B_fe_buffer = state->frontend_firmware->data;
+
+	dib9000_i2c_enumeration(&adap->dev->i2c_adap, 1, 0x20, 0x80);
+	adap->fe_adap[0].fe = dvb_attach(dib9000_attach, &adap->dev->i2c_adap, 0x80, &nim9090md_config[0]);
+
+	if (adap->fe_adap[0].fe == NULL)
+		return -ENODEV;
+
+	i2c = dib9000_get_i2c_master(adap->fe_adap[0].fe, DIBX000_I2C_INTERFACE_GPIO_3_4, 0);
+	dib9000_i2c_enumeration(i2c, 1, 0x12, 0x82);
+
+	fe_slave = dvb_attach(dib9000_attach, i2c, 0x82, &nim9090md_config[1]);
+	dib9000_set_slave_frontend(adap->fe_adap[0].fe, fe_slave);
+
+	return fe_slave == NULL ?  -ENODEV : 0;
+}
+
+static int nim9090md_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_adapter_state *state = adap->priv;
+	struct i2c_adapter *i2c;
+	struct dvb_frontend *fe_slave;
+	u16 data_dib190[10] = {
+		1, 0x5374,
+		2, 0x01ae,
+		7, 0x0020,
+		0, 0x00ef,
+		8, 0x0406,
+	};
+	i2c = dib9000_get_tuner_interface(adap->fe_adap[0].fe);
+	if (dvb_attach(dib0090_fw_register, adap->fe_adap[0].fe, i2c, &nim9090md_dib0090_config[0]) == NULL)
+		return -ENODEV;
+	i2c = dib9000_get_i2c_master(adap->fe_adap[0].fe, DIBX000_I2C_INTERFACE_GPIO_1_2, 0);
+	if (dib01x0_pmu_update(i2c, data_dib190, 10) < 0)
+		return -ENODEV;
+
+	dib0700_set_i2c_speed(adap->dev, 1500);
+	if (dib9000_firmware_post_pll_init(adap->fe_adap[0].fe) < 0)
+		return -ENODEV;
+
+	fe_slave = dib9000_get_slave_frontend(adap->fe_adap[0].fe, 1);
+	if (fe_slave != NULL) {
+		i2c = dib9000_get_component_bus_interface(adap->fe_adap[0].fe);
+		dib9000_set_i2c_adapter(fe_slave, i2c);
+
+		i2c = dib9000_get_tuner_interface(fe_slave);
+		if (dvb_attach(dib0090_fw_register, fe_slave, i2c, &nim9090md_dib0090_config[1]) == NULL)
+			return -ENODEV;
+		fe_slave->dvb = adap->fe_adap[0].fe->dvb;
+		dib9000_fw_set_component_bus_speed(adap->fe_adap[0].fe, 1500);
+		if (dib9000_firmware_post_pll_init(fe_slave) < 0)
+			return -ENODEV;
+	}
+	release_firmware(state->frontend_firmware);
+
+	return 0;
+}
+
+/* NIM7090 */
+static int dib7090p_get_best_sampling(struct dvb_frontend *fe , struct dibx090p_best_adc *adc)
+{
+	u8 spur = 0, prediv = 0, loopdiv = 0, min_prediv = 1, max_prediv = 1;
+
+	u16 xtal = 12000;
+	u32 fcp_min = 1900;  /* PLL Minimum Frequency comparator KHz */
+	u32 fcp_max = 20000; /* PLL Maximum Frequency comparator KHz */
+	u32 fdem_max = 76000;
+	u32 fdem_min = 69500;
+	u32 fcp = 0, fs = 0, fdem = 0;
+	u32 harmonic_id = 0;
+
+	adc->pll_loopdiv = loopdiv;
+	adc->pll_prediv = prediv;
+	adc->timf = 0;
+
+	deb_info("bandwidth = %d fdem_min =%d", fe->dtv_property_cache.bandwidth_hz, fdem_min);
+
+	/* Find Min and Max prediv */
+	while ((xtal/max_prediv) >= fcp_min)
+		max_prediv++;
+
+	max_prediv--;
+	min_prediv = max_prediv;
+	while ((xtal/min_prediv) <= fcp_max) {
+		min_prediv--;
+		if (min_prediv == 1)
+			break;
+	}
+	deb_info("MIN prediv = %d : MAX prediv = %d", min_prediv, max_prediv);
+
+	min_prediv = 2;
+
+	for (prediv = min_prediv ; prediv < max_prediv; prediv++) {
+		fcp = xtal / prediv;
+		if (fcp > fcp_min && fcp < fcp_max) {
+			for (loopdiv = 1 ; loopdiv < 64 ; loopdiv++) {
+				fdem = ((xtal/prediv) * loopdiv);
+				fs   = fdem / 4;
+				/* test min/max system restrictions */
+
+				if ((fdem >= fdem_min) && (fdem <= fdem_max) && (fs >= fe->dtv_property_cache.bandwidth_hz/1000)) {
+					spur = 0;
+					/* test fs harmonics positions */
+					for (harmonic_id = (fe->dtv_property_cache.frequency / (1000*fs)) ;  harmonic_id <= ((fe->dtv_property_cache.frequency / (1000*fs))+1) ; harmonic_id++) {
+						if (((fs*harmonic_id) >= ((fe->dtv_property_cache.frequency/1000) - (fe->dtv_property_cache.bandwidth_hz/2000))) &&  ((fs*harmonic_id) <= ((fe->dtv_property_cache.frequency/1000) + (fe->dtv_property_cache.bandwidth_hz/2000)))) {
+							spur = 1;
+							break;
+						}
+					}
+
+					if (!spur) {
+						adc->pll_loopdiv = loopdiv;
+						adc->pll_prediv = prediv;
+						adc->timf = 2396745143UL/fdem*(1 << 9);
+						adc->timf += ((2396745143UL%fdem) << 9)/fdem;
+						deb_info("loopdiv=%i prediv=%i timf=%i", loopdiv, prediv, adc->timf);
+						break;
+					}
+				}
+			}
+		}
+		if (!spur)
+			break;
+	}
+
+
+	if (adc->pll_loopdiv == 0 && adc->pll_prediv == 0)
+		return -EINVAL;
+	else
+		return 0;
+}
+
+static int dib7090_agc_startup(struct dvb_frontend *fe)
+{
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct dib0700_adapter_state *state = adap->priv;
+	struct dibx000_bandwidth_config pll;
+	u16 target;
+	struct dibx090p_best_adc adc;
+	int ret;
+
+	ret = state->set_param_save(fe);
+	if (ret < 0)
+		return ret;
+
+	memset(&pll, 0, sizeof(struct dibx000_bandwidth_config));
+	dib0090_pwm_gain_reset(fe);
+	target = (dib0090_get_wbd_target(fe) * 8 + 1) / 2;
+	state->dib7000p_ops.set_wbd_ref(fe, target);
+
+	if (dib7090p_get_best_sampling(fe, &adc) == 0) {
+		pll.pll_ratio  = adc.pll_loopdiv;
+		pll.pll_prediv = adc.pll_prediv;
+
+		state->dib7000p_ops.update_pll(fe, &pll);
+		state->dib7000p_ops.ctrl_timf(fe, DEMOD_TIMF_SET, adc.timf);
+	}
+	return 0;
+}
+
+static int dib7090_agc_restart(struct dvb_frontend *fe, u8 restart)
+{
+	deb_info("AGC restart callback: %d", restart);
+	if (restart == 0) /* before AGC startup */
+		dib0090_set_dc_servo(fe, 1);
+	return 0;
+}
+
+static int tfe7790p_update_lna(struct dvb_frontend *fe, u16 agc_global)
+{
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	deb_info("update LNA: agc global=%i", agc_global);
+
+	if (agc_global < 25000) {
+		state->dib7000p_ops.set_gpio(fe, 8, 0, 0);
+		state->dib7000p_ops.set_agc1_min(fe, 0);
+	} else {
+		state->dib7000p_ops.set_gpio(fe, 8, 0, 1);
+		state->dib7000p_ops.set_agc1_min(fe, 32768);
+	}
+
+	return 0;
+}
+
+static struct dib0090_wbd_slope dib7090_wbd_table[] = {
+	{ 380,   81, 850, 64, 540,  4},
+	{ 860,   51, 866, 21,  375, 4},
+	{1700,    0, 250, 0,   100, 6},
+	{2600,    0, 250, 0,   100, 6},
+	{ 0xFFFF, 0,   0, 0,   0,   0},
+};
+
+static struct dibx000_agc_config dib7090_agc_config[2] = {
+	{
+		.band_caps      = BAND_UHF,
+		/* P_agc_use_sd_mod1=0, P_agc_use_sd_mod2=0, P_agc_freq_pwm_div=1, P_agc_inv_pwm1=0, P_agc_inv_pwm2=0,
+		* P_agc_inh_dc_rv_est=0, P_agc_time_est=3, P_agc_freeze=0, P_agc_nb_est=5, P_agc_write=0 */
+		.setup          = (0 << 15) | (0 << 14) | (5 << 11) | (0 << 10) | (0 << 9) | (0 << 8) | (3 << 5) | (0 << 4) | (5 << 1) | (0 << 0),
+
+		.inv_gain       = 687,
+		.time_stabiliz  = 10,
+
+		.alpha_level    = 0,
+		.thlock         = 118,
+
+		.wbd_inv        = 0,
+		.wbd_ref        = 1200,
+		.wbd_sel        = 3,
+		.wbd_alpha      = 5,
+
+		.agc1_max       = 65535,
+		.agc1_min       = 32768,
+
+		.agc2_max       = 65535,
+		.agc2_min       = 0,
+
+		.agc1_pt1       = 0,
+		.agc1_pt2       = 32,
+		.agc1_pt3       = 114,
+		.agc1_slope1    = 143,
+		.agc1_slope2    = 144,
+		.agc2_pt1       = 114,
+		.agc2_pt2       = 227,
+		.agc2_slope1    = 116,
+		.agc2_slope2    = 117,
+
+		.alpha_mant     = 18,
+		.alpha_exp      = 0,
+		.beta_mant      = 20,
+		.beta_exp       = 59,
+
+		.perform_agc_softsplit = 0,
+	} , {
+		.band_caps      = BAND_FM | BAND_VHF | BAND_CBAND,
+		/* P_agc_use_sd_mod1=0, P_agc_use_sd_mod2=0, P_agc_freq_pwm_div=1, P_agc_inv_pwm1=0, P_agc_inv_pwm2=0,
+		* P_agc_inh_dc_rv_est=0, P_agc_time_est=3, P_agc_freeze=0, P_agc_nb_est=5, P_agc_write=0 */
+		.setup          = (0 << 15) | (0 << 14) | (5 << 11) | (0 << 10) | (0 << 9) | (0 << 8) | (3 << 5) | (0 << 4) | (5 << 1) | (0 << 0),
+
+		.inv_gain       = 732,
+		.time_stabiliz  = 10,
+
+		.alpha_level    = 0,
+		.thlock         = 118,
+
+		.wbd_inv        = 0,
+		.wbd_ref        = 1200,
+		.wbd_sel        = 3,
+		.wbd_alpha      = 5,
+
+		.agc1_max       = 65535,
+		.agc1_min       = 0,
+
+		.agc2_max       = 65535,
+		.agc2_min       = 0,
+
+		.agc1_pt1       = 0,
+		.agc1_pt2       = 0,
+		.agc1_pt3       = 98,
+		.agc1_slope1    = 0,
+		.agc1_slope2    = 167,
+		.agc2_pt1       = 98,
+		.agc2_pt2       = 255,
+		.agc2_slope1    = 104,
+		.agc2_slope2    = 0,
+
+		.alpha_mant     = 18,
+		.alpha_exp      = 0,
+		.beta_mant      = 20,
+		.beta_exp       = 59,
+
+		.perform_agc_softsplit = 0,
+	}
+};
+
+static struct dibx000_bandwidth_config dib7090_clock_config_12_mhz = {
+	.internal = 60000,
+	.sampling = 15000,
+	.pll_prediv = 1,
+	.pll_ratio = 5,
+	.pll_range = 0,
+	.pll_reset = 0,
+	.pll_bypass = 0,
+	.enable_refdiv = 0,
+	.bypclk_div = 0,
+	.IO_CLK_en_core = 1,
+	.ADClkSrc = 1,
+	.modulo = 2,
+	.sad_cfg = (3 << 14) | (1 << 12) | (524 << 0),
+	.ifreq = (0 << 25) | 0,
+	.timf = 20452225,
+	.xtal_hz = 15000000,
+};
+
+static struct dib7000p_config nim7090_dib7000p_config = {
+	.output_mpeg2_in_188_bytes  = 1,
+	.hostbus_diversity			= 1,
+	.tuner_is_baseband			= 1,
+	.update_lna					= tfe7790p_update_lna, /* GPIO used is the same as TFE7790 */
+
+	.agc_config_count			= 2,
+	.agc						= dib7090_agc_config,
+
+	.bw							= &dib7090_clock_config_12_mhz,
+
+	.gpio_dir					= DIB7000P_GPIO_DEFAULT_DIRECTIONS,
+	.gpio_val					= DIB7000P_GPIO_DEFAULT_VALUES,
+	.gpio_pwm_pos				= DIB7000P_GPIO_DEFAULT_PWM_POS,
+
+	.pwm_freq_div				= 0,
+
+	.agc_control				= dib7090_agc_restart,
+
+	.spur_protect				= 0,
+	.disable_sample_and_hold	= 0,
+	.enable_current_mirror		= 0,
+	.diversity_delay			= 0,
+
+	.output_mode				= OUTMODE_MPEG2_FIFO,
+	.enMpegOutput				= 1,
+};
+
+static int tfe7090p_pvr_update_lna(struct dvb_frontend *fe, u16 agc_global)
+{
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	deb_info("TFE7090P-PVR update LNA: agc global=%i", agc_global);
+	if (agc_global < 25000) {
+		state->dib7000p_ops.set_gpio(fe, 5, 0, 0);
+		state->dib7000p_ops.set_agc1_min(fe, 0);
+	} else {
+		state->dib7000p_ops.set_gpio(fe, 5, 0, 1);
+		state->dib7000p_ops.set_agc1_min(fe, 32768);
+	}
+
+	return 0;
+}
+
+static struct dib7000p_config tfe7090pvr_dib7000p_config[2] = {
+	{
+		.output_mpeg2_in_188_bytes  = 1,
+		.hostbus_diversity			= 1,
+		.tuner_is_baseband			= 1,
+		.update_lna					= tfe7090p_pvr_update_lna,
+
+		.agc_config_count			= 2,
+		.agc						= dib7090_agc_config,
+
+		.bw							= &dib7090_clock_config_12_mhz,
+
+		.gpio_dir					= DIB7000P_GPIO_DEFAULT_DIRECTIONS,
+		.gpio_val					= DIB7000P_GPIO_DEFAULT_VALUES,
+		.gpio_pwm_pos				= DIB7000P_GPIO_DEFAULT_PWM_POS,
+
+		.pwm_freq_div				= 0,
+
+		.agc_control				= dib7090_agc_restart,
+
+		.spur_protect				= 0,
+		.disable_sample_and_hold	= 0,
+		.enable_current_mirror		= 0,
+		.diversity_delay			= 0,
+
+		.output_mode				= OUTMODE_MPEG2_PAR_GATED_CLK,
+		.default_i2c_addr			= 0x90,
+		.enMpegOutput				= 1,
+	}, {
+		.output_mpeg2_in_188_bytes  = 1,
+		.hostbus_diversity			= 1,
+		.tuner_is_baseband			= 1,
+		.update_lna					= tfe7090p_pvr_update_lna,
+
+		.agc_config_count			= 2,
+		.agc						= dib7090_agc_config,
+
+		.bw							= &dib7090_clock_config_12_mhz,
+
+		.gpio_dir					= DIB7000P_GPIO_DEFAULT_DIRECTIONS,
+		.gpio_val					= DIB7000P_GPIO_DEFAULT_VALUES,
+		.gpio_pwm_pos				= DIB7000P_GPIO_DEFAULT_PWM_POS,
+
+		.pwm_freq_div				= 0,
+
+		.agc_control				= dib7090_agc_restart,
+
+		.spur_protect				= 0,
+		.disable_sample_and_hold	= 0,
+		.enable_current_mirror		= 0,
+		.diversity_delay			= 0,
+
+		.output_mode				= OUTMODE_MPEG2_PAR_GATED_CLK,
+		.default_i2c_addr			= 0x92,
+		.enMpegOutput				= 0,
+	}
+};
+
+static struct dib0090_config nim7090_dib0090_config = {
+	.io.clock_khz = 12000,
+	.io.pll_bypass = 0,
+	.io.pll_range = 0,
+	.io.pll_prediv = 3,
+	.io.pll_loopdiv = 6,
+	.io.adc_clock_ratio = 0,
+	.io.pll_int_loop_filt = 0,
+
+	.freq_offset_khz_uhf = 0,
+	.freq_offset_khz_vhf = 0,
+
+	.clkouttobamse = 1,
+	.analog_output = 0,
+
+	.wbd_vhf_offset = 0,
+	.wbd_cband_offset = 0,
+	.use_pwm_agc = 1,
+	.clkoutdrive = 0,
+
+	.fref_clock_ratio = 0,
+
+	.wbd = dib7090_wbd_table,
+
+	.ls_cfg_pad_drv = 0,
+	.data_tx_drv = 0,
+	.low_if = NULL,
+	.in_soc = 1,
+};
+
+static struct dib7000p_config tfe7790p_dib7000p_config = {
+	.output_mpeg2_in_188_bytes  = 1,
+	.hostbus_diversity			= 1,
+	.tuner_is_baseband			= 1,
+	.update_lna					= tfe7790p_update_lna,
+
+	.agc_config_count			= 2,
+	.agc						= dib7090_agc_config,
+
+	.bw							= &dib7090_clock_config_12_mhz,
+
+	.gpio_dir					= DIB7000P_GPIO_DEFAULT_DIRECTIONS,
+	.gpio_val					= DIB7000P_GPIO_DEFAULT_VALUES,
+	.gpio_pwm_pos				= DIB7000P_GPIO_DEFAULT_PWM_POS,
+
+	.pwm_freq_div				= 0,
+
+	.agc_control				= dib7090_agc_restart,
+
+	.spur_protect				= 0,
+	.disable_sample_and_hold	= 0,
+	.enable_current_mirror		= 0,
+	.diversity_delay			= 0,
+
+	.output_mode				= OUTMODE_MPEG2_PAR_GATED_CLK,
+	.enMpegOutput				= 1,
+};
+
+static struct dib0090_config tfe7790p_dib0090_config = {
+	.io.clock_khz = 12000,
+	.io.pll_bypass = 0,
+	.io.pll_range = 0,
+	.io.pll_prediv = 3,
+	.io.pll_loopdiv = 6,
+	.io.adc_clock_ratio = 0,
+	.io.pll_int_loop_filt = 0,
+
+	.freq_offset_khz_uhf = 0,
+	.freq_offset_khz_vhf = 0,
+
+	.clkouttobamse = 1,
+	.analog_output = 0,
+
+	.wbd_vhf_offset = 0,
+	.wbd_cband_offset = 0,
+	.use_pwm_agc = 1,
+	.clkoutdrive = 0,
+
+	.fref_clock_ratio = 0,
+
+	.wbd = dib7090_wbd_table,
+
+	.ls_cfg_pad_drv = 0,
+	.data_tx_drv = 0,
+	.low_if = NULL,
+	.in_soc = 1,
+	.force_cband_input = 0,
+	.is_dib7090e = 0,
+	.force_crystal_mode = 1,
+};
+
+static struct dib0090_config tfe7090pvr_dib0090_config[2] = {
+	{
+		.io.clock_khz = 12000,
+		.io.pll_bypass = 0,
+		.io.pll_range = 0,
+		.io.pll_prediv = 3,
+		.io.pll_loopdiv = 6,
+		.io.adc_clock_ratio = 0,
+		.io.pll_int_loop_filt = 0,
+
+		.freq_offset_khz_uhf = 50,
+		.freq_offset_khz_vhf = 70,
+
+		.clkouttobamse = 1,
+		.analog_output = 0,
+
+		.wbd_vhf_offset = 0,
+		.wbd_cband_offset = 0,
+		.use_pwm_agc = 1,
+		.clkoutdrive = 0,
+
+		.fref_clock_ratio = 0,
+
+		.wbd = dib7090_wbd_table,
+
+		.ls_cfg_pad_drv = 0,
+		.data_tx_drv = 0,
+		.low_if = NULL,
+		.in_soc = 1,
+	}, {
+		.io.clock_khz = 12000,
+		.io.pll_bypass = 0,
+		.io.pll_range = 0,
+		.io.pll_prediv = 3,
+		.io.pll_loopdiv = 6,
+		.io.adc_clock_ratio = 0,
+		.io.pll_int_loop_filt = 0,
+
+		.freq_offset_khz_uhf = -50,
+		.freq_offset_khz_vhf = -70,
+
+		.clkouttobamse = 1,
+		.analog_output = 0,
+
+		.wbd_vhf_offset = 0,
+		.wbd_cband_offset = 0,
+		.use_pwm_agc = 1,
+		.clkoutdrive = 0,
+
+		.fref_clock_ratio = 0,
+
+		.wbd = dib7090_wbd_table,
+
+		.ls_cfg_pad_drv = 0,
+		.data_tx_drv = 0,
+		.low_if = NULL,
+		.in_soc = 1,
+	}
+};
+
+static int nim7090_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_adapter_state *state = adap->priv;
+
+	if (!dvb_attach(dib7000p_attach, &state->dib7000p_ops))
+		return -ENODEV;
+
+	dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 1);
+	msleep(20);
+	dib0700_set_gpio(adap->dev, GPIO9, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO4, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO7, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0);
+
+	msleep(20);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1);
+	msleep(20);
+	dib0700_set_gpio(adap->dev, GPIO0, GPIO_OUT, 1);
+
+	if (state->dib7000p_ops.i2c_enumeration(&adap->dev->i2c_adap, 1, 0x10, &nim7090_dib7000p_config) != 0) {
+		err("%s: state->dib7000p_ops.i2c_enumeration failed.  Cannot continue\n", __func__);
+		dvb_detach(state->dib7000p_ops.set_wbd_ref);
+		return -ENODEV;
+	}
+	adap->fe_adap[0].fe = state->dib7000p_ops.init(&adap->dev->i2c_adap, 0x80, &nim7090_dib7000p_config);
+
+	return adap->fe_adap[0].fe == NULL ?  -ENODEV : 0;
+}
+
+static int nim7090_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_adapter_state *st = adap->priv;
+	struct i2c_adapter *tun_i2c = st->dib7000p_ops.get_i2c_tuner(adap->fe_adap[0].fe);
+
+	nim7090_dib0090_config.reset = st->dib7000p_ops.tuner_sleep,
+	nim7090_dib0090_config.sleep = st->dib7000p_ops.tuner_sleep,
+	nim7090_dib0090_config.get_adc_power = st->dib7000p_ops.get_adc_power;
+
+	if (dvb_attach(dib0090_register, adap->fe_adap[0].fe, tun_i2c, &nim7090_dib0090_config) == NULL)
+		return -ENODEV;
+
+	st->dib7000p_ops.set_gpio(adap->fe_adap[0].fe, 8, 0, 1);
+
+	st->set_param_save = adap->fe_adap[0].fe->ops.tuner_ops.set_params;
+	adap->fe_adap[0].fe->ops.tuner_ops.set_params = dib7090_agc_startup;
+	return 0;
+}
+
+static int tfe7090pvr_frontend0_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_state *st = adap->dev->priv;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	if (!dvb_attach(dib7000p_attach, &state->dib7000p_ops))
+		return -ENODEV;
+
+	/* The TFE7090 requires the dib0700 to not be in master mode */
+	st->disable_streaming_master_mode = 1;
+
+	dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 1);
+	msleep(20);
+	dib0700_set_gpio(adap->dev, GPIO9, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO4, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO7, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0);
+
+	msleep(20);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1);
+	msleep(20);
+	dib0700_set_gpio(adap->dev, GPIO0, GPIO_OUT, 1);
+
+	/* initialize IC 0 */
+	if (state->dib7000p_ops.i2c_enumeration(&adap->dev->i2c_adap, 1, 0x20, &tfe7090pvr_dib7000p_config[0]) != 0) {
+		err("%s: state->dib7000p_ops.i2c_enumeration failed.  Cannot continue\n", __func__);
+		dvb_detach(state->dib7000p_ops.set_wbd_ref);
+		return -ENODEV;
+	}
+
+	dib0700_set_i2c_speed(adap->dev, 340);
+	adap->fe_adap[0].fe = state->dib7000p_ops.init(&adap->dev->i2c_adap, 0x90, &tfe7090pvr_dib7000p_config[0]);
+	if (adap->fe_adap[0].fe == NULL)
+		return -ENODEV;
+
+	state->dib7000p_ops.slave_reset(adap->fe_adap[0].fe);
+
+	return 0;
+}
+
+static int tfe7090pvr_frontend1_attach(struct dvb_usb_adapter *adap)
+{
+	struct i2c_adapter *i2c;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	if (adap->dev->adapter[0].fe_adap[0].fe == NULL) {
+		err("the master dib7090 has to be initialized first");
+		return -ENODEV; /* the master device has not been initialized */
+	}
+
+	if (!dvb_attach(dib7000p_attach, &state->dib7000p_ops))
+		return -ENODEV;
+
+	i2c = state->dib7000p_ops.get_i2c_master(adap->dev->adapter[0].fe_adap[0].fe, DIBX000_I2C_INTERFACE_GPIO_6_7, 1);
+	if (state->dib7000p_ops.i2c_enumeration(i2c, 1, 0x10, &tfe7090pvr_dib7000p_config[1]) != 0) {
+		err("%s: state->dib7000p_ops.i2c_enumeration failed.  Cannot continue\n", __func__);
+		dvb_detach(state->dib7000p_ops.set_wbd_ref);
+		return -ENODEV;
+	}
+
+	adap->fe_adap[0].fe = state->dib7000p_ops.init(i2c, 0x92, &tfe7090pvr_dib7000p_config[1]);
+	dib0700_set_i2c_speed(adap->dev, 200);
+
+	return adap->fe_adap[0].fe == NULL ? -ENODEV : 0;
+}
+
+static int tfe7090pvr_tuner0_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_adapter_state *st = adap->priv;
+	struct i2c_adapter *tun_i2c = st->dib7000p_ops.get_i2c_tuner(adap->fe_adap[0].fe);
+
+	tfe7090pvr_dib0090_config[0].reset = st->dib7000p_ops.tuner_sleep;
+	tfe7090pvr_dib0090_config[0].sleep = st->dib7000p_ops.tuner_sleep;
+	tfe7090pvr_dib0090_config[0].get_adc_power = st->dib7000p_ops.get_adc_power;
+
+	if (dvb_attach(dib0090_register, adap->fe_adap[0].fe, tun_i2c, &tfe7090pvr_dib0090_config[0]) == NULL)
+		return -ENODEV;
+
+	st->dib7000p_ops.set_gpio(adap->fe_adap[0].fe, 8, 0, 1);
+
+	st->set_param_save = adap->fe_adap[0].fe->ops.tuner_ops.set_params;
+	adap->fe_adap[0].fe->ops.tuner_ops.set_params = dib7090_agc_startup;
+	return 0;
+}
+
+static int tfe7090pvr_tuner1_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_adapter_state *st = adap->priv;
+	struct i2c_adapter *tun_i2c = st->dib7000p_ops.get_i2c_tuner(adap->fe_adap[0].fe);
+
+	tfe7090pvr_dib0090_config[1].reset = st->dib7000p_ops.tuner_sleep;
+	tfe7090pvr_dib0090_config[1].sleep = st->dib7000p_ops.tuner_sleep;
+	tfe7090pvr_dib0090_config[1].get_adc_power = st->dib7000p_ops.get_adc_power;
+
+	if (dvb_attach(dib0090_register, adap->fe_adap[0].fe, tun_i2c, &tfe7090pvr_dib0090_config[1]) == NULL)
+		return -ENODEV;
+
+	st->dib7000p_ops.set_gpio(adap->fe_adap[0].fe, 8, 0, 1);
+
+	st->set_param_save = adap->fe_adap[0].fe->ops.tuner_ops.set_params;
+	adap->fe_adap[0].fe->ops.tuner_ops.set_params = dib7090_agc_startup;
+	return 0;
+}
+
+static int tfe7790p_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_state *st = adap->dev->priv;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	if (!dvb_attach(dib7000p_attach, &state->dib7000p_ops))
+		return -ENODEV;
+
+	/* The TFE7790P requires the dib0700 to not be in master mode */
+	st->disable_streaming_master_mode = 1;
+
+	dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 1);
+	msleep(20);
+	dib0700_set_gpio(adap->dev, GPIO9, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO4, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO7, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0);
+	msleep(20);
+	dib0700_ctrl_clock(adap->dev, 72, 1);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1);
+	msleep(20);
+	dib0700_set_gpio(adap->dev, GPIO0, GPIO_OUT, 1);
+
+	if (state->dib7000p_ops.i2c_enumeration(&adap->dev->i2c_adap,
+				1, 0x10, &tfe7790p_dib7000p_config) != 0) {
+		err("%s: state->dib7000p_ops.i2c_enumeration failed.  Cannot continue\n",
+				__func__);
+		dvb_detach(state->dib7000p_ops.set_wbd_ref);
+		return -ENODEV;
+	}
+	adap->fe_adap[0].fe = state->dib7000p_ops.init(&adap->dev->i2c_adap,
+			0x80, &tfe7790p_dib7000p_config);
+
+	return adap->fe_adap[0].fe == NULL ?  -ENODEV : 0;
+}
+
+static int tfe7790p_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_adapter_state *st = adap->priv;
+	struct i2c_adapter *tun_i2c =
+		st->dib7000p_ops.get_i2c_tuner(adap->fe_adap[0].fe);
+
+
+	tfe7790p_dib0090_config.reset = st->dib7000p_ops.tuner_sleep;
+	tfe7790p_dib0090_config.sleep = st->dib7000p_ops.tuner_sleep;
+	tfe7790p_dib0090_config.get_adc_power = st->dib7000p_ops.get_adc_power;
+
+	if (dvb_attach(dib0090_register, adap->fe_adap[0].fe, tun_i2c,
+				&tfe7790p_dib0090_config) == NULL)
+		return -ENODEV;
+
+	st->dib7000p_ops.set_gpio(adap->fe_adap[0].fe, 8, 0, 1);
+
+	st->set_param_save = adap->fe_adap[0].fe->ops.tuner_ops.set_params;
+	adap->fe_adap[0].fe->ops.tuner_ops.set_params = dib7090_agc_startup;
+	return 0;
+}
+
+/* STK7070PD */
+static struct dib7000p_config stk7070pd_dib7000p_config[2] = {
+	{
+		.output_mpeg2_in_188_bytes = 1,
+
+		.agc_config_count = 1,
+		.agc = &dib7070_agc_config,
+		.bw  = &dib7070_bw_config_12_mhz,
+		.tuner_is_baseband = 1,
+		.spur_protect = 1,
+
+		.gpio_dir = DIB7000P_GPIO_DEFAULT_DIRECTIONS,
+		.gpio_val = DIB7000P_GPIO_DEFAULT_VALUES,
+		.gpio_pwm_pos = DIB7000P_GPIO_DEFAULT_PWM_POS,
+
+		.hostbus_diversity = 1,
+	}, {
+		.output_mpeg2_in_188_bytes = 1,
+
+		.agc_config_count = 1,
+		.agc = &dib7070_agc_config,
+		.bw  = &dib7070_bw_config_12_mhz,
+		.tuner_is_baseband = 1,
+		.spur_protect = 1,
+
+		.gpio_dir = DIB7000P_GPIO_DEFAULT_DIRECTIONS,
+		.gpio_val = DIB7000P_GPIO_DEFAULT_VALUES,
+		.gpio_pwm_pos = DIB7000P_GPIO_DEFAULT_PWM_POS,
+
+		.hostbus_diversity = 1,
+	}
+};
+
+static void stk7070pd_init(struct dvb_usb_device *dev)
+{
+	dib0700_set_gpio(dev, GPIO6, GPIO_OUT, 1);
+	msleep(10);
+	dib0700_set_gpio(dev, GPIO9, GPIO_OUT, 1);
+	dib0700_set_gpio(dev, GPIO4, GPIO_OUT, 1);
+	dib0700_set_gpio(dev, GPIO7, GPIO_OUT, 1);
+	dib0700_set_gpio(dev, GPIO10, GPIO_OUT, 0);
+
+	dib0700_ctrl_clock(dev, 72, 1);
+
+	msleep(10);
+	dib0700_set_gpio(dev, GPIO10, GPIO_OUT, 1);
+}
+
+static int stk7070pd_frontend_attach0(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_adapter_state *state = adap->priv;
+
+	if (!dvb_attach(dib7000p_attach, &state->dib7000p_ops))
+		return -ENODEV;
+
+	stk7070pd_init(adap->dev);
+
+	msleep(10);
+	dib0700_set_gpio(adap->dev, GPIO0, GPIO_OUT, 1);
+
+	if (state->dib7000p_ops.i2c_enumeration(&adap->dev->i2c_adap, 2, 18,
+				     stk7070pd_dib7000p_config) != 0) {
+		err("%s: state->dib7000p_ops.i2c_enumeration failed.  Cannot continue\n",
+		    __func__);
+		dvb_detach(state->dib7000p_ops.set_wbd_ref);
+		return -ENODEV;
+	}
+
+	adap->fe_adap[0].fe = state->dib7000p_ops.init(&adap->dev->i2c_adap, 0x80, &stk7070pd_dib7000p_config[0]);
+	return adap->fe_adap[0].fe == NULL ? -ENODEV : 0;
+}
+
+static int stk7070pd_frontend_attach1(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_adapter_state *state = adap->priv;
+
+	if (!dvb_attach(dib7000p_attach, &state->dib7000p_ops))
+		return -ENODEV;
+
+	adap->fe_adap[0].fe = state->dib7000p_ops.init(&adap->dev->i2c_adap, 0x82, &stk7070pd_dib7000p_config[1]);
+	return adap->fe_adap[0].fe == NULL ? -ENODEV : 0;
+}
+
+static int novatd_read_status_override(struct dvb_frontend *fe,
+				       enum fe_status *stat)
+{
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct dvb_usb_device *dev = adap->dev;
+	struct dib0700_state *state = dev->priv;
+	int ret;
+
+	ret = state->read_status(fe, stat);
+
+	if (!ret)
+		dib0700_set_gpio(dev, adap->id == 0 ? GPIO1 : GPIO0, GPIO_OUT,
+				!!(*stat & FE_HAS_LOCK));
+
+	return ret;
+}
+
+static int novatd_sleep_override(struct dvb_frontend* fe)
+{
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct dvb_usb_device *dev = adap->dev;
+	struct dib0700_state *state = dev->priv;
+
+	/* turn off LED */
+	dib0700_set_gpio(dev, adap->id == 0 ? GPIO1 : GPIO0, GPIO_OUT, 0);
+
+	return state->sleep(fe);
+}
+
+/*
+ * novatd_frontend_attach - Nova-TD specific attach
+ *
+ * Nova-TD has GPIO0, 1 and 2 for LEDs. So do not fiddle with them except for
+ * information purposes.
+ */
+static int novatd_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *dev = adap->dev;
+	struct dib0700_state *st = dev->priv;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	if (!dvb_attach(dib7000p_attach, &state->dib7000p_ops))
+		return -ENODEV;
+
+	if (adap->id == 0) {
+		stk7070pd_init(dev);
+
+		/* turn the power LED on, the other two off (just in case) */
+		dib0700_set_gpio(dev, GPIO0, GPIO_OUT, 0);
+		dib0700_set_gpio(dev, GPIO1, GPIO_OUT, 0);
+		dib0700_set_gpio(dev, GPIO2, GPIO_OUT, 1);
+
+		if (state->dib7000p_ops.i2c_enumeration(&dev->i2c_adap, 2, 18,
+					     stk7070pd_dib7000p_config) != 0) {
+			err("%s: state->dib7000p_ops.i2c_enumeration failed.  Cannot continue\n",
+			    __func__);
+			dvb_detach(state->dib7000p_ops.set_wbd_ref);
+			return -ENODEV;
+		}
+	}
+
+	adap->fe_adap[0].fe = state->dib7000p_ops.init(&dev->i2c_adap,
+			adap->id == 0 ? 0x80 : 0x82,
+			&stk7070pd_dib7000p_config[adap->id]);
+
+	if (adap->fe_adap[0].fe == NULL)
+		return -ENODEV;
+
+	st->read_status = adap->fe_adap[0].fe->ops.read_status;
+	adap->fe_adap[0].fe->ops.read_status = novatd_read_status_override;
+	st->sleep = adap->fe_adap[0].fe->ops.sleep;
+	adap->fe_adap[0].fe->ops.sleep = novatd_sleep_override;
+
+	return 0;
+}
+
+/* S5H1411 */
+static struct s5h1411_config pinnacle_801e_config = {
+	.output_mode   = S5H1411_PARALLEL_OUTPUT,
+	.gpio          = S5H1411_GPIO_OFF,
+	.mpeg_timing   = S5H1411_MPEGTIMING_NONCONTINUOUS_NONINVERTING_CLOCK,
+	.qam_if        = S5H1411_IF_44000,
+	.vsb_if        = S5H1411_IF_44000,
+	.inversion     = S5H1411_INVERSION_OFF,
+	.status_mode   = S5H1411_DEMODLOCKING
+};
+
+/* Pinnacle PCTV HD Pro 801e GPIOs map:
+   GPIO0  - currently unknown
+   GPIO1  - xc5000 tuner reset
+   GPIO2  - CX25843 sleep
+   GPIO3  - currently unknown
+   GPIO4  - currently unknown
+   GPIO6  - currently unknown
+   GPIO7  - currently unknown
+   GPIO9  - currently unknown
+   GPIO10 - CX25843 reset
+ */
+static int s5h1411_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_state *st = adap->dev->priv;
+
+	/* Make use of the new i2c functions from FW 1.20 */
+	st->fw_use_new_i2c_api = 1;
+
+	/* The s5h1411 requires the dib0700 to not be in master mode */
+	st->disable_streaming_master_mode = 1;
+
+	/* All msleep values taken from Windows USB trace */
+	dib0700_set_gpio(adap->dev, GPIO0, GPIO_OUT, 0);
+	dib0700_set_gpio(adap->dev, GPIO3, GPIO_OUT, 0);
+	dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 1);
+	msleep(400);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0);
+	msleep(60);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1);
+	msleep(30);
+	dib0700_set_gpio(adap->dev, GPIO0, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO9, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO4, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO7, GPIO_OUT, 1);
+	dib0700_set_gpio(adap->dev, GPIO2, GPIO_OUT, 0);
+	msleep(30);
+
+	/* Put the CX25843 to sleep for now since we're in digital mode */
+	dib0700_set_gpio(adap->dev, GPIO2, GPIO_OUT, 1);
+
+	/* GPIOs are initialized, do the attach */
+	adap->fe_adap[0].fe = dvb_attach(s5h1411_attach, &pinnacle_801e_config,
+			      &adap->dev->i2c_adap);
+	return adap->fe_adap[0].fe == NULL ? -ENODEV : 0;
+}
+
+static int dib0700_xc5000_tuner_callback(void *priv, int component,
+					 int command, int arg)
+{
+	struct dvb_usb_adapter *adap = priv;
+
+	if (command == XC5000_TUNER_RESET) {
+		/* Reset the tuner */
+		dib0700_set_gpio(adap->dev, GPIO1, GPIO_OUT, 0);
+		msleep(10);
+		dib0700_set_gpio(adap->dev, GPIO1, GPIO_OUT, 1);
+		msleep(10);
+	} else {
+		err("xc5000: unknown tuner callback command: %d\n", command);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static struct xc5000_config s5h1411_xc5000_tunerconfig = {
+	.i2c_address      = 0x64,
+	.if_khz           = 5380,
+};
+
+static int xc5000_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	/* FIXME: generalize & move to common area */
+	adap->fe_adap[0].fe->callback = dib0700_xc5000_tuner_callback;
+
+	return dvb_attach(xc5000_attach, adap->fe_adap[0].fe, &adap->dev->i2c_adap,
+			  &s5h1411_xc5000_tunerconfig)
+		== NULL ? -ENODEV : 0;
+}
+
+static int dib0700_xc4000_tuner_callback(void *priv, int component,
+					 int command, int arg)
+{
+	struct dvb_usb_adapter *adap = priv;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	if (command == XC4000_TUNER_RESET) {
+		/* Reset the tuner */
+		state->dib7000p_ops.set_gpio(adap->fe_adap[0].fe, 8, 0, 0);
+		msleep(10);
+		state->dib7000p_ops.set_gpio(adap->fe_adap[0].fe, 8, 0, 1);
+	} else {
+		err("xc4000: unknown tuner callback command: %d\n", command);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static struct dibx000_agc_config stk7700p_7000p_xc4000_agc_config = {
+	.band_caps = BAND_UHF | BAND_VHF,
+	.setup = 0x64,
+	.inv_gain = 0x02c8,
+	.time_stabiliz = 0x15,
+	.alpha_level = 0x00,
+	.thlock = 0x76,
+	.wbd_inv = 0x01,
+	.wbd_ref = 0x0b33,
+	.wbd_sel = 0x00,
+	.wbd_alpha = 0x02,
+	.agc1_max = 0x00,
+	.agc1_min = 0x00,
+	.agc2_max = 0x9b26,
+	.agc2_min = 0x26ca,
+	.agc1_pt1 = 0x00,
+	.agc1_pt2 = 0x00,
+	.agc1_pt3 = 0x00,
+	.agc1_slope1 = 0x00,
+	.agc1_slope2 = 0x00,
+	.agc2_pt1 = 0x00,
+	.agc2_pt2 = 0x80,
+	.agc2_slope1 = 0x1d,
+	.agc2_slope2 = 0x1d,
+	.alpha_mant = 0x11,
+	.alpha_exp = 0x1b,
+	.beta_mant = 0x17,
+	.beta_exp = 0x33,
+	.perform_agc_softsplit = 0x00,
+};
+
+static struct dibx000_bandwidth_config stk7700p_xc4000_pll_config = {
+	.internal = 60000,
+	.sampling = 30000,
+	.pll_prediv = 1,
+	.pll_ratio = 8,
+	.pll_range = 3,
+	.pll_reset = 1,
+	.pll_bypass = 0,
+	.enable_refdiv = 0,
+	.bypclk_div = 0,
+	.IO_CLK_en_core = 1,
+	.ADClkSrc = 1,
+	.modulo = 0,
+	.sad_cfg = (3 << 14) | (1 << 12) | 524, /* sad_cfg: refsel, sel, freq_15k */
+	.ifreq = 39370534,
+	.timf = 20452225,
+	.xtal_hz = 30000000
+};
+
+/* FIXME: none of these inputs are validated yet */
+static struct dib7000p_config pctv_340e_config = {
+	.output_mpeg2_in_188_bytes = 1,
+
+	.agc_config_count = 1,
+	.agc = &stk7700p_7000p_xc4000_agc_config,
+	.bw  = &stk7700p_xc4000_pll_config,
+
+	.gpio_dir = DIB7000M_GPIO_DEFAULT_DIRECTIONS,
+	.gpio_val = DIB7000M_GPIO_DEFAULT_VALUES,
+	.gpio_pwm_pos = DIB7000M_GPIO_DEFAULT_PWM_POS,
+};
+
+/* PCTV 340e GPIOs map:
+   dib0700:
+   GPIO2  - CX25843 sleep
+   GPIO3  - CS5340 reset
+   GPIO5  - IRD
+   GPIO6  - Power Supply
+   GPIO8  - LNA (1=off 0=on)
+   GPIO10 - CX25843 reset
+   dib7000:
+   GPIO8  - xc4000 reset
+ */
+static int pctv340e_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_state *st = adap->dev->priv;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	if (!dvb_attach(dib7000p_attach, &state->dib7000p_ops))
+		return -ENODEV;
+
+	/* Power Supply on */
+	dib0700_set_gpio(adap->dev, GPIO6,  GPIO_OUT, 0);
+	msleep(50);
+	dib0700_set_gpio(adap->dev, GPIO6,  GPIO_OUT, 1);
+	msleep(100); /* Allow power supply to settle before probing */
+
+	/* cx25843 reset */
+	dib0700_set_gpio(adap->dev, GPIO10,  GPIO_OUT, 0);
+	msleep(1); /* cx25843 datasheet say 350us required */
+	dib0700_set_gpio(adap->dev, GPIO10,  GPIO_OUT, 1);
+
+	/* LNA off for now */
+	dib0700_set_gpio(adap->dev, GPIO8,  GPIO_OUT, 1);
+
+	/* Put the CX25843 to sleep for now since we're in digital mode */
+	dib0700_set_gpio(adap->dev, GPIO2, GPIO_OUT, 1);
+
+	/* FIXME: not verified yet */
+	dib0700_ctrl_clock(adap->dev, 72, 1);
+
+	msleep(500);
+
+	if (state->dib7000p_ops.dib7000pc_detection(&adap->dev->i2c_adap) == 0) {
+		/* Demodulator not found for some reason? */
+		dvb_detach(state->dib7000p_ops.set_wbd_ref);
+		return -ENODEV;
+	}
+
+	adap->fe_adap[0].fe = state->dib7000p_ops.init(&adap->dev->i2c_adap, 0x12,
+			      &pctv_340e_config);
+	st->is_dib7000pc = 1;
+
+	return adap->fe_adap[0].fe == NULL ? -ENODEV : 0;
+}
+
+static struct xc4000_config dib7000p_xc4000_tunerconfig = {
+	.i2c_address	  = 0x61,
+	.default_pm	  = 1,
+	.dvb_amplitude	  = 0,
+	.set_smoothedcvbs = 0,
+	.if_khz		  = 5400
+};
+
+static int xc4000_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct i2c_adapter *tun_i2c;
+	struct dib0700_adapter_state *state = adap->priv;
+
+	/* The xc4000 is not on the main i2c bus */
+	tun_i2c = state->dib7000p_ops.get_i2c_master(adap->fe_adap[0].fe,
+					  DIBX000_I2C_INTERFACE_TUNER, 1);
+	if (tun_i2c == NULL) {
+		printk(KERN_ERR "Could not reach tuner i2c bus\n");
+		return 0;
+	}
+
+	/* Setup the reset callback */
+	adap->fe_adap[0].fe->callback = dib0700_xc4000_tuner_callback;
+
+	return dvb_attach(xc4000_attach, adap->fe_adap[0].fe, tun_i2c,
+			  &dib7000p_xc4000_tunerconfig)
+		== NULL ? -ENODEV : 0;
+}
+
+static struct lgdt3305_config hcw_lgdt3305_config = {
+	.i2c_addr           = 0x0e,
+	.mpeg_mode          = LGDT3305_MPEG_PARALLEL,
+	.tpclk_edge         = LGDT3305_TPCLK_FALLING_EDGE,
+	.tpvalid_polarity   = LGDT3305_TP_VALID_LOW,
+	.deny_i2c_rptr      = 0,
+	.spectral_inversion = 1,
+	.qam_if_khz         = 6000,
+	.vsb_if_khz         = 6000,
+	.usref_8vsb         = 0x0500,
+};
+
+static struct mxl5007t_config hcw_mxl5007t_config = {
+	.xtal_freq_hz = MxL_XTAL_25_MHZ,
+	.if_freq_hz = MxL_IF_6_MHZ,
+	.invert_if = 1,
+};
+
+/* TIGER-ATSC map:
+   GPIO0  - LNA_CTR  (H: LNA power enabled, L: LNA power disabled)
+   GPIO1  - ANT_SEL  (H: VPA, L: MCX)
+   GPIO4  - SCL2
+   GPIO6  - EN_TUNER
+   GPIO7  - SDA2
+   GPIO10 - DEM_RST
+
+   MXL is behind LG's i2c repeater.  LG is on SCL2/SDA2 gpios on the DIB
+ */
+static int lgdt3305_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_state *st = adap->dev->priv;
+
+	/* Make use of the new i2c functions from FW 1.20 */
+	st->fw_use_new_i2c_api = 1;
+
+	st->disable_streaming_master_mode = 1;
+
+	/* fe power enable */
+	dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 0);
+	msleep(30);
+	dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 1);
+	msleep(30);
+
+	/* demod reset */
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1);
+	msleep(30);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0);
+	msleep(30);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1);
+	msleep(30);
+
+	adap->fe_adap[0].fe = dvb_attach(lgdt3305_attach,
+			      &hcw_lgdt3305_config,
+			      &adap->dev->i2c_adap);
+
+	return adap->fe_adap[0].fe == NULL ? -ENODEV : 0;
+}
+
+static int mxl5007t_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	return dvb_attach(mxl5007t_attach, adap->fe_adap[0].fe,
+			  &adap->dev->i2c_adap, 0x60,
+			  &hcw_mxl5007t_config) == NULL ? -ENODEV : 0;
+}
+
+static int xbox_one_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib0700_state *st = adap->dev->priv;
+	struct i2c_client *client_demod, *client_tuner;
+	struct dvb_usb_device *d = adap->dev;
+	struct mn88472_config mn88472_config = { };
+	struct tda18250_config tda18250_config;
+	struct i2c_board_info info;
+
+	st->fw_use_new_i2c_api = 1;
+	st->disable_streaming_master_mode = 1;
+
+	/* fe power enable */
+	dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 0);
+	msleep(30);
+	dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 1);
+	msleep(30);
+
+	/* demod reset */
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1);
+	msleep(30);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0);
+	msleep(30);
+	dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1);
+	msleep(30);
+
+	/* attach demod */
+	mn88472_config.fe = &adap->fe_adap[0].fe;
+	mn88472_config.i2c_wr_max = 22;
+	mn88472_config.xtal = 20500000;
+	mn88472_config.ts_mode = PARALLEL_TS_MODE;
+	mn88472_config.ts_clock = FIXED_TS_CLOCK;
+	memset(&info, 0, sizeof(struct i2c_board_info));
+	strlcpy(info.type, "mn88472", I2C_NAME_SIZE);
+	info.addr = 0x18;
+	info.platform_data = &mn88472_config;
+	request_module(info.type);
+	client_demod = i2c_new_device(&d->i2c_adap, &info);
+	if (client_demod == NULL || client_demod->dev.driver == NULL)
+		goto fail_demod_device;
+	if (!try_module_get(client_demod->dev.driver->owner))
+		goto fail_demod_module;
+
+	st->i2c_client_demod = client_demod;
+
+	adap->fe_adap[0].fe = mn88472_config.get_dvb_frontend(client_demod);
+
+	/* attach tuner */
+	memset(&tda18250_config, 0, sizeof(tda18250_config));
+	tda18250_config.if_dvbt_6 = 3950;
+	tda18250_config.if_dvbt_7 = 4450;
+	tda18250_config.if_dvbt_8 = 4950;
+	tda18250_config.if_dvbc_6 = 4950;
+	tda18250_config.if_dvbc_8 = 4950;
+	tda18250_config.if_atsc = 4079;
+	tda18250_config.loopthrough = true;
+	tda18250_config.xtal_freq = TDA18250_XTAL_FREQ_27MHZ;
+	tda18250_config.fe = adap->fe_adap[0].fe;
+
+	memset(&info, 0, sizeof(struct i2c_board_info));
+	strlcpy(info.type, "tda18250", I2C_NAME_SIZE);
+	info.addr = 0x60;
+	info.platform_data = &tda18250_config;
+
+	request_module(info.type);
+	client_tuner = i2c_new_device(&adap->dev->i2c_adap, &info);
+	if (client_tuner == NULL || client_tuner->dev.driver == NULL)
+		goto fail_tuner_device;
+	if (!try_module_get(client_tuner->dev.driver->owner))
+		goto fail_tuner_module;
+
+	st->i2c_client_tuner = client_tuner;
+	return 0;
+
+fail_tuner_module:
+	i2c_unregister_device(client_tuner);
+fail_tuner_device:
+	module_put(client_demod->dev.driver->owner);
+fail_demod_module:
+	i2c_unregister_device(client_demod);
+fail_demod_device:
+	return -ENODEV;
+}
+
+
+/* DVB-USB and USB stuff follows */
+struct usb_device_id dib0700_usb_id_table[] = {
+/* 0 */	{ USB_DEVICE(USB_VID_DIBCOM,    USB_PID_DIBCOM_STK7700P) },
+	{ USB_DEVICE(USB_VID_DIBCOM,    USB_PID_DIBCOM_STK7700P_PC) },
+	{ USB_DEVICE(USB_VID_HAUPPAUGE, USB_PID_HAUPPAUGE_NOVA_T_500) },
+	{ USB_DEVICE(USB_VID_HAUPPAUGE, USB_PID_HAUPPAUGE_NOVA_T_500_2) },
+	{ USB_DEVICE(USB_VID_HAUPPAUGE, USB_PID_HAUPPAUGE_NOVA_T_STICK) },
+/* 5 */	{ USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_VOLAR) },
+	{ USB_DEVICE(USB_VID_COMPRO,    USB_PID_COMPRO_VIDEOMATE_U500) },
+	{ USB_DEVICE(USB_VID_UNIWILL,   USB_PID_UNIWILL_STK7700P) },
+	{ USB_DEVICE(USB_VID_LEADTEK,   USB_PID_WINFAST_DTV_DONGLE_STK7700P) },
+	{ USB_DEVICE(USB_VID_HAUPPAUGE, USB_PID_HAUPPAUGE_NOVA_T_STICK_2) },
+/* 10 */{ USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_VOLAR_2) },
+	{ USB_DEVICE(USB_VID_PINNACLE,  USB_PID_PINNACLE_PCTV2000E) },
+	{ USB_DEVICE(USB_VID_TERRATEC,
+			USB_PID_TERRATEC_CINERGY_DT_XS_DIVERSITY) },
+	{ USB_DEVICE(USB_VID_HAUPPAUGE, USB_PID_HAUPPAUGE_NOVA_TD_STICK) },
+	{ USB_DEVICE(USB_VID_DIBCOM,    USB_PID_DIBCOM_STK7700D) },
+/* 15 */{ USB_DEVICE(USB_VID_DIBCOM,    USB_PID_DIBCOM_STK7070P) },
+	{ USB_DEVICE(USB_VID_PINNACLE,  USB_PID_PINNACLE_PCTV_DVB_T_FLASH) },
+	{ USB_DEVICE(USB_VID_DIBCOM,    USB_PID_DIBCOM_STK7070PD) },
+	{ USB_DEVICE(USB_VID_PINNACLE,
+			USB_PID_PINNACLE_PCTV_DUAL_DIVERSITY_DVB_T) },
+	{ USB_DEVICE(USB_VID_COMPRO,    USB_PID_COMPRO_VIDEOMATE_U500_PC) },
+/* 20 */{ USB_DEVICE(USB_VID_AVERMEDIA, USB_PID_AVERMEDIA_EXPRESS) },
+	{ USB_DEVICE(USB_VID_GIGABYTE,  USB_PID_GIGABYTE_U7000) },
+	{ USB_DEVICE(USB_VID_ULTIMA_ELECTRONIC, USB_PID_ARTEC_T14BR) },
+	{ USB_DEVICE(USB_VID_ASUS,      USB_PID_ASUS_U3000) },
+	{ USB_DEVICE(USB_VID_ASUS,      USB_PID_ASUS_U3100) },
+/* 25 */{ USB_DEVICE(USB_VID_HAUPPAUGE, USB_PID_HAUPPAUGE_NOVA_T_STICK_3) },
+	{ USB_DEVICE(USB_VID_HAUPPAUGE, USB_PID_HAUPPAUGE_MYTV_T) },
+	{ USB_DEVICE(USB_VID_TERRATEC,  USB_PID_TERRATEC_CINERGY_HT_USB_XE) },
+	{ USB_DEVICE(USB_VID_PINNACLE,	USB_PID_PINNACLE_EXPRESSCARD_320CX) },
+	{ USB_DEVICE(USB_VID_PINNACLE,	USB_PID_PINNACLE_PCTV72E) },
+/* 30 */{ USB_DEVICE(USB_VID_PINNACLE,	USB_PID_PINNACLE_PCTV73E) },
+	{ USB_DEVICE(USB_VID_YUAN,	USB_PID_YUAN_EC372S) },
+	{ USB_DEVICE(USB_VID_TERRATEC,	USB_PID_TERRATEC_CINERGY_HT_EXPRESS) },
+	{ USB_DEVICE(USB_VID_TERRATEC,	USB_PID_TERRATEC_CINERGY_T_XXS) },
+	{ USB_DEVICE(USB_VID_LEADTEK,   USB_PID_WINFAST_DTV_DONGLE_STK7700P_2) },
+/* 35 */{ USB_DEVICE(USB_VID_HAUPPAUGE, USB_PID_HAUPPAUGE_NOVA_TD_STICK_52009) },
+	{ USB_DEVICE(USB_VID_HAUPPAUGE, USB_PID_HAUPPAUGE_NOVA_T_500_3) },
+	{ USB_DEVICE(USB_VID_GIGABYTE,  USB_PID_GIGABYTE_U8000) },
+	{ USB_DEVICE(USB_VID_YUAN,      USB_PID_YUAN_STK7700PH) },
+	{ USB_DEVICE(USB_VID_ASUS,	USB_PID_ASUS_U3000H) },
+/* 40 */{ USB_DEVICE(USB_VID_PINNACLE,  USB_PID_PINNACLE_PCTV801E) },
+	{ USB_DEVICE(USB_VID_PINNACLE,  USB_PID_PINNACLE_PCTV801E_SE) },
+	{ USB_DEVICE(USB_VID_TERRATEC,	USB_PID_TERRATEC_CINERGY_T_EXPRESS) },
+	{ USB_DEVICE(USB_VID_TERRATEC,
+			USB_PID_TERRATEC_CINERGY_DT_XS_DIVERSITY_2) },
+	{ USB_DEVICE(USB_VID_SONY,	USB_PID_SONY_PLAYTV) },
+/* 45 */{ USB_DEVICE(USB_VID_YUAN,      USB_PID_YUAN_PD378S) },
+	{ USB_DEVICE(USB_VID_HAUPPAUGE, USB_PID_HAUPPAUGE_TIGER_ATSC) },
+	{ USB_DEVICE(USB_VID_HAUPPAUGE, USB_PID_HAUPPAUGE_TIGER_ATSC_B210) },
+	{ USB_DEVICE(USB_VID_YUAN,	USB_PID_YUAN_MC770) },
+	{ USB_DEVICE(USB_VID_ELGATO,	USB_PID_ELGATO_EYETV_DTT) },
+/* 50 */{ USB_DEVICE(USB_VID_ELGATO,	USB_PID_ELGATO_EYETV_DTT_Dlx) },
+	{ USB_DEVICE(USB_VID_LEADTEK,   USB_PID_WINFAST_DTV_DONGLE_H) },
+	{ USB_DEVICE(USB_VID_TERRATEC,	USB_PID_TERRATEC_T3) },
+	{ USB_DEVICE(USB_VID_TERRATEC,	USB_PID_TERRATEC_T5) },
+	{ USB_DEVICE(USB_VID_YUAN,      USB_PID_YUAN_STK7700D) },
+/* 55 */{ USB_DEVICE(USB_VID_YUAN,	USB_PID_YUAN_STK7700D_2) },
+	{ USB_DEVICE(USB_VID_PINNACLE,	USB_PID_PINNACLE_PCTV73A) },
+	{ USB_DEVICE(USB_VID_PCTV,	USB_PID_PINNACLE_PCTV73ESE) },
+	{ USB_DEVICE(USB_VID_PCTV,	USB_PID_PINNACLE_PCTV282E) },
+	{ USB_DEVICE(USB_VID_DIBCOM,	USB_PID_DIBCOM_STK7770P) },
+/* 60 */{ USB_DEVICE(USB_VID_TERRATEC,	USB_PID_TERRATEC_CINERGY_T_XXS_2) },
+	{ USB_DEVICE(USB_VID_DIBCOM,    USB_PID_DIBCOM_STK807XPVR) },
+	{ USB_DEVICE(USB_VID_DIBCOM,    USB_PID_DIBCOM_STK807XP) },
+	{ USB_DEVICE_VER(USB_VID_PIXELVIEW, USB_PID_PIXELVIEW_SBTVD, 0x000, 0x3f00) },
+	{ USB_DEVICE(USB_VID_EVOLUTEPC, USB_PID_TVWAY_PLUS) },
+/* 65 */{ USB_DEVICE(USB_VID_PINNACLE,	USB_PID_PINNACLE_PCTV73ESE) },
+	{ USB_DEVICE(USB_VID_PINNACLE,	USB_PID_PINNACLE_PCTV282E) },
+	{ USB_DEVICE(USB_VID_DIBCOM,    USB_PID_DIBCOM_STK8096GP) },
+	{ USB_DEVICE(USB_VID_ELGATO,    USB_PID_ELGATO_EYETV_DIVERSITY) },
+	{ USB_DEVICE(USB_VID_DIBCOM,    USB_PID_DIBCOM_NIM9090M) },
+/* 70 */{ USB_DEVICE(USB_VID_DIBCOM,    USB_PID_DIBCOM_NIM8096MD) },
+	{ USB_DEVICE(USB_VID_DIBCOM,    USB_PID_DIBCOM_NIM9090MD) },
+	{ USB_DEVICE(USB_VID_DIBCOM,    USB_PID_DIBCOM_NIM7090) },
+	{ USB_DEVICE(USB_VID_DIBCOM,    USB_PID_DIBCOM_TFE7090PVR) },
+	{ USB_DEVICE(USB_VID_TECHNISAT, USB_PID_TECHNISAT_AIRSTAR_TELESTICK_2) },
+/* 75 */{ USB_DEVICE(USB_VID_MEDION,    USB_PID_CREATIX_CTX1921) },
+	{ USB_DEVICE(USB_VID_PINNACLE,  USB_PID_PINNACLE_PCTV340E) },
+	{ USB_DEVICE(USB_VID_PINNACLE,  USB_PID_PINNACLE_PCTV340E_SE) },
+	{ USB_DEVICE(USB_VID_DIBCOM,    USB_PID_DIBCOM_TFE7790P) },
+	{ USB_DEVICE(USB_VID_DIBCOM,    USB_PID_DIBCOM_TFE8096P) },
+/* 80 */{ USB_DEVICE(USB_VID_ELGATO,	USB_PID_ELGATO_EYETV_DTT_2) },
+	{ USB_DEVICE(USB_VID_PCTV,      USB_PID_PCTV_2002E) },
+	{ USB_DEVICE(USB_VID_PCTV,      USB_PID_PCTV_2002E_SE) },
+	{ USB_DEVICE(USB_VID_PCTV,      USB_PID_DIBCOM_STK8096PVR) },
+	{ USB_DEVICE(USB_VID_DIBCOM,    USB_PID_DIBCOM_STK8096PVR) },
+/* 85 */{ USB_DEVICE(USB_VID_HAMA,	USB_PID_HAMA_DVBT_HYBRID) },
+	{ USB_DEVICE(USB_VID_MICROSOFT,	USB_PID_XBOX_ONE_TUNER) },
+	{ 0 }		/* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, dib0700_usb_id_table);
+
+#define DIB0700_DEFAULT_DEVICE_PROPERTIES \
+	.caps              = DVB_USB_IS_AN_I2C_ADAPTER, \
+	.usb_ctrl          = DEVICE_SPECIFIC, \
+	.firmware          = "dvb-usb-dib0700-1.20.fw", \
+	.download_firmware = dib0700_download_firmware, \
+	.no_reconnect      = 1, \
+	.size_of_priv      = sizeof(struct dib0700_state), \
+	.i2c_algo          = &dib0700_i2c_algo, \
+	.identify_state    = dib0700_identify_state
+
+#define DIB0700_DEFAULT_STREAMING_CONFIG(ep) \
+	.streaming_ctrl   = dib0700_streaming_ctrl, \
+	.stream = { \
+		.type = USB_BULK, \
+		.count = 4, \
+		.endpoint = ep, \
+		.u = { \
+			.bulk = { \
+				.buffersize = 39480, \
+			} \
+		} \
+	}
+
+#define DIB0700_NUM_FRONTENDS(n) \
+	.num_frontends = n, \
+	.size_of_priv     = sizeof(struct dib0700_adapter_state)
+
+struct dvb_usb_device_properties dib0700_devices[] = {
+	{
+		DIB0700_DEFAULT_DEVICE_PROPERTIES,
+
+		.num_adapters = 1,
+		.adapter = {
+			{
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.caps = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+				.pid_filter_count = 32,
+				.pid_filter       = stk7700p_pid_filter,
+				.pid_filter_ctrl  = stk7700p_pid_filter_ctrl,
+				.frontend_attach  = stk7700p_frontend_attach,
+				.tuner_attach     = stk7700p_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x02),
+			}},
+			},
+		},
+
+		.num_device_descs = 8,
+		.devices = {
+			{   "DiBcom STK7700P reference design",
+				{ &dib0700_usb_id_table[0], &dib0700_usb_id_table[1] },
+				{ NULL },
+			},
+			{   "Hauppauge Nova-T Stick",
+				{ &dib0700_usb_id_table[4], &dib0700_usb_id_table[9], NULL },
+				{ NULL },
+			},
+			{   "AVerMedia AVerTV DVB-T Volar",
+				{ &dib0700_usb_id_table[5], &dib0700_usb_id_table[10] },
+				{ NULL },
+			},
+			{   "Compro Videomate U500",
+				{ &dib0700_usb_id_table[6], &dib0700_usb_id_table[19] },
+				{ NULL },
+			},
+			{   "Uniwill STK7700P based (Hama and others)",
+				{ &dib0700_usb_id_table[7], NULL },
+				{ NULL },
+			},
+			{   "Leadtek Winfast DTV Dongle (STK7700P based)",
+				{ &dib0700_usb_id_table[8], &dib0700_usb_id_table[34] },
+				{ NULL },
+			},
+			{   "AVerMedia AVerTV DVB-T Express",
+				{ &dib0700_usb_id_table[20] },
+				{ NULL },
+			},
+			{   "Gigabyte U7000",
+				{ &dib0700_usb_id_table[21], NULL },
+				{ NULL },
+			}
+		},
+
+		.rc.core = {
+			.rc_interval      = DEFAULT_RC_INTERVAL,
+			.rc_codes         = RC_MAP_DIB0700_RC5_TABLE,
+			.rc_query         = dib0700_rc_query_old_firmware,
+			.allowed_protos   = RC_PROTO_BIT_RC5 |
+					    RC_PROTO_BIT_RC6_MCE |
+					    RC_PROTO_BIT_NEC,
+			.change_protocol  = dib0700_change_protocol,
+		},
+	}, { DIB0700_DEFAULT_DEVICE_PROPERTIES,
+
+		.num_adapters = 2,
+		.adapter = {
+			{
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.frontend_attach  = bristol_frontend_attach,
+				.tuner_attach     = bristol_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x02),
+			}},
+			}, {
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.frontend_attach  = bristol_frontend_attach,
+				.tuner_attach     = bristol_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x03),
+			}},
+			}
+		},
+
+		.num_device_descs = 1,
+		.devices = {
+			{   "Hauppauge Nova-T 500 Dual DVB-T",
+				{ &dib0700_usb_id_table[2], &dib0700_usb_id_table[3], NULL },
+				{ NULL },
+			},
+		},
+
+		.rc.core = {
+			.rc_interval      = DEFAULT_RC_INTERVAL,
+			.rc_codes         = RC_MAP_DIB0700_RC5_TABLE,
+			.rc_query         = dib0700_rc_query_old_firmware,
+			.allowed_protos   = RC_PROTO_BIT_RC5 |
+					    RC_PROTO_BIT_RC6_MCE |
+					    RC_PROTO_BIT_NEC,
+			.change_protocol = dib0700_change_protocol,
+		},
+	}, { DIB0700_DEFAULT_DEVICE_PROPERTIES,
+
+		.num_adapters = 2,
+		.adapter = {
+			{
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.caps = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+				.pid_filter_count = 32,
+				.pid_filter       = stk70x0p_pid_filter,
+				.pid_filter_ctrl  = stk70x0p_pid_filter_ctrl,
+				.frontend_attach  = stk7700d_frontend_attach,
+				.tuner_attach     = stk7700d_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x02),
+			}},
+			}, {
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.caps = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+				.pid_filter_count = 32,
+				.pid_filter       = stk70x0p_pid_filter,
+				.pid_filter_ctrl  = stk70x0p_pid_filter_ctrl,
+				.frontend_attach  = stk7700d_frontend_attach,
+				.tuner_attach     = stk7700d_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x03),
+			}},
+			}
+		},
+
+		.num_device_descs = 5,
+		.devices = {
+			{   "Pinnacle PCTV 2000e",
+				{ &dib0700_usb_id_table[11], NULL },
+				{ NULL },
+			},
+			{   "Terratec Cinergy DT XS Diversity",
+				{ &dib0700_usb_id_table[12], NULL },
+				{ NULL },
+			},
+			{   "Hauppauge Nova-TD Stick/Elgato Eye-TV Diversity",
+				{ &dib0700_usb_id_table[13], NULL },
+				{ NULL },
+			},
+			{   "DiBcom STK7700D reference design",
+				{ &dib0700_usb_id_table[14], NULL },
+				{ NULL },
+			},
+			{   "YUAN High-Tech DiBcom STK7700D",
+				{ &dib0700_usb_id_table[55], NULL },
+				{ NULL },
+			},
+
+		},
+
+		.rc.core = {
+			.rc_interval      = DEFAULT_RC_INTERVAL,
+			.rc_codes         = RC_MAP_DIB0700_RC5_TABLE,
+			.rc_query         = dib0700_rc_query_old_firmware,
+			.allowed_protos   = RC_PROTO_BIT_RC5 |
+					    RC_PROTO_BIT_RC6_MCE |
+					    RC_PROTO_BIT_NEC,
+			.change_protocol = dib0700_change_protocol,
+		},
+	}, { DIB0700_DEFAULT_DEVICE_PROPERTIES,
+
+		.num_adapters = 1,
+		.adapter = {
+			{
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.caps = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+				.pid_filter_count = 32,
+				.pid_filter       = stk70x0p_pid_filter,
+				.pid_filter_ctrl  = stk70x0p_pid_filter_ctrl,
+				.frontend_attach  = stk7700P2_frontend_attach,
+				.tuner_attach     = stk7700d_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x02),
+			}},
+			},
+		},
+
+		.num_device_descs = 3,
+		.devices = {
+			{   "ASUS My Cinema U3000 Mini DVBT Tuner",
+				{ &dib0700_usb_id_table[23], NULL },
+				{ NULL },
+			},
+			{   "Yuan EC372S",
+				{ &dib0700_usb_id_table[31], NULL },
+				{ NULL },
+			},
+			{   "Terratec Cinergy T Express",
+				{ &dib0700_usb_id_table[42], NULL },
+				{ NULL },
+			}
+		},
+
+		.rc.core = {
+			.rc_interval      = DEFAULT_RC_INTERVAL,
+			.rc_codes         = RC_MAP_DIB0700_RC5_TABLE,
+			.module_name	  = "dib0700",
+			.rc_query         = dib0700_rc_query_old_firmware,
+			.allowed_protos   = RC_PROTO_BIT_RC5 |
+					    RC_PROTO_BIT_RC6_MCE |
+					    RC_PROTO_BIT_NEC,
+			.change_protocol = dib0700_change_protocol,
+		},
+	}, { DIB0700_DEFAULT_DEVICE_PROPERTIES,
+
+		.num_adapters = 1,
+		.adapter = {
+			{
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.caps = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+				.pid_filter_count = 32,
+				.pid_filter       = stk70x0p_pid_filter,
+				.pid_filter_ctrl  = stk70x0p_pid_filter_ctrl,
+				.frontend_attach  = stk7070p_frontend_attach,
+				.tuner_attach     = dib7070p_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x02),
+			}},
+			},
+		},
+
+		.num_device_descs = 12,
+		.devices = {
+			{   "DiBcom STK7070P reference design",
+				{ &dib0700_usb_id_table[15], NULL },
+				{ NULL },
+			},
+			{   "Pinnacle PCTV DVB-T Flash Stick",
+				{ &dib0700_usb_id_table[16], NULL },
+				{ NULL },
+			},
+			{   "Artec T14BR DVB-T",
+				{ &dib0700_usb_id_table[22], NULL },
+				{ NULL },
+			},
+			{   "ASUS My Cinema U3100 Mini DVBT Tuner",
+				{ &dib0700_usb_id_table[24], NULL },
+				{ NULL },
+			},
+			{   "Hauppauge Nova-T Stick",
+				{ &dib0700_usb_id_table[25], NULL },
+				{ NULL },
+			},
+			{   "Hauppauge Nova-T MyTV.t",
+				{ &dib0700_usb_id_table[26], NULL },
+				{ NULL },
+			},
+			{   "Pinnacle PCTV 72e",
+				{ &dib0700_usb_id_table[29], NULL },
+				{ NULL },
+			},
+			{   "Pinnacle PCTV 73e",
+				{ &dib0700_usb_id_table[30], NULL },
+				{ NULL },
+			},
+			{   "Elgato EyeTV DTT",
+				{ &dib0700_usb_id_table[49], NULL },
+				{ NULL },
+			},
+			{   "Yuan PD378S",
+				{ &dib0700_usb_id_table[45], NULL },
+				{ NULL },
+			},
+			{   "Elgato EyeTV Dtt Dlx PD378S",
+				{ &dib0700_usb_id_table[50], NULL },
+				{ NULL },
+			},
+			{   "Elgato EyeTV DTT rev. 2",
+				{ &dib0700_usb_id_table[80], NULL },
+				{ NULL },
+			},
+		},
+
+		.rc.core = {
+			.rc_interval      = DEFAULT_RC_INTERVAL,
+			.rc_codes         = RC_MAP_DIB0700_RC5_TABLE,
+			.module_name	  = "dib0700",
+			.rc_query         = dib0700_rc_query_old_firmware,
+			.allowed_protos   = RC_PROTO_BIT_RC5 |
+					    RC_PROTO_BIT_RC6_MCE |
+					    RC_PROTO_BIT_NEC,
+			.change_protocol  = dib0700_change_protocol,
+		},
+	}, { DIB0700_DEFAULT_DEVICE_PROPERTIES,
+
+		.num_adapters = 1,
+		.adapter = {
+			{
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.caps = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+				.pid_filter_count = 32,
+				.pid_filter       = stk70x0p_pid_filter,
+				.pid_filter_ctrl  = stk70x0p_pid_filter_ctrl,
+				.frontend_attach  = stk7070p_frontend_attach,
+				.tuner_attach     = dib7070p_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x02),
+			}},
+			},
+		},
+
+		.num_device_descs = 3,
+		.devices = {
+			{   "Pinnacle PCTV 73A",
+				{ &dib0700_usb_id_table[56], NULL },
+				{ NULL },
+			},
+			{   "Pinnacle PCTV 73e SE",
+				{ &dib0700_usb_id_table[57], &dib0700_usb_id_table[65], NULL },
+				{ NULL },
+			},
+			{   "Pinnacle PCTV 282e",
+				{ &dib0700_usb_id_table[58], &dib0700_usb_id_table[66], NULL },
+				{ NULL },
+			},
+		},
+
+		.rc.core = {
+			.rc_interval      = DEFAULT_RC_INTERVAL,
+			.rc_codes         = RC_MAP_DIB0700_RC5_TABLE,
+			.module_name	  = "dib0700",
+			.rc_query         = dib0700_rc_query_old_firmware,
+			.allowed_protos   = RC_PROTO_BIT_RC5 |
+					    RC_PROTO_BIT_RC6_MCE |
+					    RC_PROTO_BIT_NEC,
+			.change_protocol  = dib0700_change_protocol,
+		},
+	}, { DIB0700_DEFAULT_DEVICE_PROPERTIES,
+
+		.num_adapters = 2,
+		.adapter = {
+			{
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.caps = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+				.pid_filter_count = 32,
+				.pid_filter       = stk70x0p_pid_filter,
+				.pid_filter_ctrl  = stk70x0p_pid_filter_ctrl,
+				.frontend_attach  = novatd_frontend_attach,
+				.tuner_attach     = dib7070p_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x02),
+			}},
+			}, {
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.caps = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+				.pid_filter_count = 32,
+				.pid_filter       = stk70x0p_pid_filter,
+				.pid_filter_ctrl  = stk70x0p_pid_filter_ctrl,
+				.frontend_attach  = novatd_frontend_attach,
+				.tuner_attach     = dib7070p_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x03),
+			}},
+			}
+		},
+
+		.num_device_descs = 3,
+		.devices = {
+			{   "Hauppauge Nova-TD Stick (52009)",
+				{ &dib0700_usb_id_table[35], NULL },
+				{ NULL },
+			},
+			{   "PCTV 2002e",
+				{ &dib0700_usb_id_table[81], NULL },
+				{ NULL },
+			},
+			{   "PCTV 2002e SE",
+				{ &dib0700_usb_id_table[82], NULL },
+				{ NULL },
+			},
+		},
+
+		.rc.core = {
+			.rc_interval      = DEFAULT_RC_INTERVAL,
+			.rc_codes         = RC_MAP_DIB0700_RC5_TABLE,
+			.module_name	  = "dib0700",
+			.rc_query         = dib0700_rc_query_old_firmware,
+			.allowed_protos   = RC_PROTO_BIT_RC5 |
+					    RC_PROTO_BIT_RC6_MCE |
+					    RC_PROTO_BIT_NEC,
+			.change_protocol = dib0700_change_protocol,
+		},
+	}, { DIB0700_DEFAULT_DEVICE_PROPERTIES,
+
+		.num_adapters = 2,
+		.adapter = {
+			{
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.caps = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+				.pid_filter_count = 32,
+				.pid_filter       = stk70x0p_pid_filter,
+				.pid_filter_ctrl  = stk70x0p_pid_filter_ctrl,
+				.frontend_attach  = stk7070pd_frontend_attach0,
+				.tuner_attach     = dib7070p_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x02),
+			}},
+			}, {
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.caps = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+				.pid_filter_count = 32,
+				.pid_filter       = stk70x0p_pid_filter,
+				.pid_filter_ctrl  = stk70x0p_pid_filter_ctrl,
+				.frontend_attach  = stk7070pd_frontend_attach1,
+				.tuner_attach     = dib7070p_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x03),
+			}},
+			}
+		},
+
+		.num_device_descs = 5,
+		.devices = {
+			{   "DiBcom STK7070PD reference design",
+				{ &dib0700_usb_id_table[17], NULL },
+				{ NULL },
+			},
+			{   "Pinnacle PCTV Dual DVB-T Diversity Stick",
+				{ &dib0700_usb_id_table[18], NULL },
+				{ NULL },
+			},
+			{   "Hauppauge Nova-TD-500 (84xxx)",
+				{ &dib0700_usb_id_table[36], NULL },
+				{ NULL },
+			},
+			{  "Terratec Cinergy DT USB XS Diversity/ T5",
+				{ &dib0700_usb_id_table[43],
+					&dib0700_usb_id_table[53], NULL},
+				{ NULL },
+			},
+			{  "Sony PlayTV",
+				{ &dib0700_usb_id_table[44], NULL },
+				{ NULL },
+			},
+		},
+
+		.rc.core = {
+			.rc_interval      = DEFAULT_RC_INTERVAL,
+			.rc_codes         = RC_MAP_DIB0700_RC5_TABLE,
+			.module_name	  = "dib0700",
+			.rc_query         = dib0700_rc_query_old_firmware,
+			.allowed_protos   = RC_PROTO_BIT_RC5 |
+					    RC_PROTO_BIT_RC6_MCE |
+					    RC_PROTO_BIT_NEC,
+			.change_protocol = dib0700_change_protocol,
+		},
+	}, { DIB0700_DEFAULT_DEVICE_PROPERTIES,
+
+		.num_adapters = 2,
+		.adapter = {
+			{
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.caps = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+				.pid_filter_count = 32,
+				.pid_filter       = stk70x0p_pid_filter,
+				.pid_filter_ctrl  = stk70x0p_pid_filter_ctrl,
+				.frontend_attach  = stk7070pd_frontend_attach0,
+				.tuner_attach     = dib7070p_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x02),
+			}},
+			}, {
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.caps = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+				.pid_filter_count = 32,
+				.pid_filter       = stk70x0p_pid_filter,
+				.pid_filter_ctrl  = stk70x0p_pid_filter_ctrl,
+				.frontend_attach  = stk7070pd_frontend_attach1,
+				.tuner_attach     = dib7070p_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x03),
+			}},
+			}
+		},
+
+		.num_device_descs = 1,
+		.devices = {
+			{   "Elgato EyeTV Diversity",
+				{ &dib0700_usb_id_table[68], NULL },
+				{ NULL },
+			},
+		},
+
+		.rc.core = {
+			.rc_interval      = DEFAULT_RC_INTERVAL,
+			.rc_codes         = RC_MAP_DIB0700_NEC_TABLE,
+			.module_name	  = "dib0700",
+			.rc_query         = dib0700_rc_query_old_firmware,
+			.allowed_protos   = RC_PROTO_BIT_RC5 |
+					    RC_PROTO_BIT_RC6_MCE |
+					    RC_PROTO_BIT_NEC,
+			.change_protocol  = dib0700_change_protocol,
+		},
+	}, { DIB0700_DEFAULT_DEVICE_PROPERTIES,
+
+		.num_adapters = 1,
+		.adapter = {
+			{
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.caps = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+				.pid_filter_count = 32,
+				.pid_filter       = stk70x0p_pid_filter,
+				.pid_filter_ctrl  = stk70x0p_pid_filter_ctrl,
+				.frontend_attach  = stk7700ph_frontend_attach,
+				.tuner_attach     = stk7700ph_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x02),
+			}},
+			},
+		},
+
+		.num_device_descs = 10,
+		.devices = {
+			{   "Terratec Cinergy HT USB XE",
+				{ &dib0700_usb_id_table[27], NULL },
+				{ NULL },
+			},
+			{   "Pinnacle Expresscard 320cx",
+				{ &dib0700_usb_id_table[28], NULL },
+				{ NULL },
+			},
+			{   "Terratec Cinergy HT Express",
+				{ &dib0700_usb_id_table[32], NULL },
+				{ NULL },
+			},
+			{   "Gigabyte U8000-RH",
+				{ &dib0700_usb_id_table[37], NULL },
+				{ NULL },
+			},
+			{   "YUAN High-Tech STK7700PH",
+				{ &dib0700_usb_id_table[38], NULL },
+				{ NULL },
+			},
+			{   "Asus My Cinema-U3000Hybrid",
+				{ &dib0700_usb_id_table[39], NULL },
+				{ NULL },
+			},
+			{   "YUAN High-Tech MC770",
+				{ &dib0700_usb_id_table[48], NULL },
+				{ NULL },
+			},
+			{   "Leadtek WinFast DTV Dongle H",
+				{ &dib0700_usb_id_table[51], NULL },
+				{ NULL },
+			},
+			{   "YUAN High-Tech STK7700D",
+				{ &dib0700_usb_id_table[54], NULL },
+				{ NULL },
+			},
+			{   "Hama DVB=T Hybrid USB Stick",
+				{ &dib0700_usb_id_table[85], NULL },
+				{ NULL },
+			},
+		},
+
+		.rc.core = {
+			.rc_interval      = DEFAULT_RC_INTERVAL,
+			.rc_codes         = RC_MAP_DIB0700_RC5_TABLE,
+			.module_name	  = "dib0700",
+			.rc_query         = dib0700_rc_query_old_firmware,
+			.allowed_protos   = RC_PROTO_BIT_RC5 |
+					    RC_PROTO_BIT_RC6_MCE |
+					    RC_PROTO_BIT_NEC,
+			.change_protocol  = dib0700_change_protocol,
+		},
+	}, { DIB0700_DEFAULT_DEVICE_PROPERTIES,
+		.num_adapters = 1,
+		.adapter = {
+			{
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.frontend_attach  = s5h1411_frontend_attach,
+				.tuner_attach     = xc5000_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x02),
+			}},
+			},
+		},
+
+		.num_device_descs = 2,
+		.devices = {
+			{   "Pinnacle PCTV HD Pro USB Stick",
+				{ &dib0700_usb_id_table[40], NULL },
+				{ NULL },
+			},
+			{   "Pinnacle PCTV HD USB Stick",
+				{ &dib0700_usb_id_table[41], NULL },
+				{ NULL },
+			},
+		},
+
+		.rc.core = {
+			.rc_interval      = DEFAULT_RC_INTERVAL,
+			.rc_codes         = RC_MAP_DIB0700_RC5_TABLE,
+			.module_name	  = "dib0700",
+			.rc_query         = dib0700_rc_query_old_firmware,
+			.allowed_protos   = RC_PROTO_BIT_RC5 |
+					    RC_PROTO_BIT_RC6_MCE |
+					    RC_PROTO_BIT_NEC,
+			.change_protocol  = dib0700_change_protocol,
+		},
+	}, { DIB0700_DEFAULT_DEVICE_PROPERTIES,
+		.num_adapters = 1,
+		.adapter = {
+			{
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.frontend_attach  = lgdt3305_frontend_attach,
+				.tuner_attach     = mxl5007t_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x02),
+			}},
+			},
+		},
+
+		.num_device_descs = 2,
+		.devices = {
+			{   "Hauppauge ATSC MiniCard (B200)",
+				{ &dib0700_usb_id_table[46], NULL },
+				{ NULL },
+			},
+			{   "Hauppauge ATSC MiniCard (B210)",
+				{ &dib0700_usb_id_table[47], NULL },
+				{ NULL },
+			},
+		},
+	}, { DIB0700_DEFAULT_DEVICE_PROPERTIES,
+
+		.num_adapters = 1,
+		.adapter = {
+			{
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.caps = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+				.pid_filter_count = 32,
+				.pid_filter       = stk70x0p_pid_filter,
+				.pid_filter_ctrl  = stk70x0p_pid_filter_ctrl,
+				.frontend_attach  = stk7770p_frontend_attach,
+				.tuner_attach     = dib7770p_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x02),
+			}},
+			},
+		},
+
+		.num_device_descs = 4,
+		.devices = {
+			{   "DiBcom STK7770P reference design",
+				{ &dib0700_usb_id_table[59], NULL },
+				{ NULL },
+			},
+			{   "Terratec Cinergy T USB XXS (HD)/ T3",
+				{ &dib0700_usb_id_table[33],
+					&dib0700_usb_id_table[52],
+					&dib0700_usb_id_table[60], NULL},
+				{ NULL },
+			},
+			{   "TechniSat AirStar TeleStick 2",
+				{ &dib0700_usb_id_table[74], NULL },
+				{ NULL },
+			},
+			{   "Medion CTX1921 DVB-T USB",
+				{ &dib0700_usb_id_table[75], NULL },
+				{ NULL },
+			},
+		},
+
+		.rc.core = {
+			.rc_interval      = DEFAULT_RC_INTERVAL,
+			.rc_codes         = RC_MAP_DIB0700_RC5_TABLE,
+			.module_name	  = "dib0700",
+			.rc_query         = dib0700_rc_query_old_firmware,
+			.allowed_protos   = RC_PROTO_BIT_RC5 |
+					    RC_PROTO_BIT_RC6_MCE |
+					    RC_PROTO_BIT_NEC,
+			.change_protocol  = dib0700_change_protocol,
+		},
+	}, { DIB0700_DEFAULT_DEVICE_PROPERTIES,
+		.num_adapters = 1,
+		.adapter = {
+			{
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.caps  = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+				.pid_filter_count = 32,
+				.pid_filter = stk80xx_pid_filter,
+				.pid_filter_ctrl = stk80xx_pid_filter_ctrl,
+				.frontend_attach  = stk807x_frontend_attach,
+				.tuner_attach     = dib807x_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x02),
+			}},
+			},
+		},
+
+		.num_device_descs = 3,
+		.devices = {
+			{   "DiBcom STK807xP reference design",
+				{ &dib0700_usb_id_table[62], NULL },
+				{ NULL },
+			},
+			{   "Prolink Pixelview SBTVD",
+				{ &dib0700_usb_id_table[63], NULL },
+				{ NULL },
+			},
+			{   "EvolutePC TVWay+",
+				{ &dib0700_usb_id_table[64], NULL },
+				{ NULL },
+			},
+		},
+
+		.rc.core = {
+			.rc_interval      = DEFAULT_RC_INTERVAL,
+			.rc_codes         = RC_MAP_DIB0700_NEC_TABLE,
+			.module_name	  = "dib0700",
+			.rc_query         = dib0700_rc_query_old_firmware,
+			.allowed_protos   = RC_PROTO_BIT_RC5 |
+					    RC_PROTO_BIT_RC6_MCE |
+					    RC_PROTO_BIT_NEC,
+			.change_protocol  = dib0700_change_protocol,
+		},
+	}, { DIB0700_DEFAULT_DEVICE_PROPERTIES,
+		.num_adapters = 2,
+		.adapter = {
+			{
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.caps  = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+				.pid_filter_count = 32,
+				.pid_filter = stk80xx_pid_filter,
+				.pid_filter_ctrl = stk80xx_pid_filter_ctrl,
+				.frontend_attach  = stk807xpvr_frontend_attach0,
+				.tuner_attach     = dib807x_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x02),
+			}},
+			},
+			{
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.caps  = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+				.pid_filter_count = 32,
+				.pid_filter = stk80xx_pid_filter,
+				.pid_filter_ctrl = stk80xx_pid_filter_ctrl,
+				.frontend_attach  = stk807xpvr_frontend_attach1,
+				.tuner_attach     = dib807x_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x03),
+			}},
+			},
+		},
+
+		.num_device_descs = 1,
+		.devices = {
+			{   "DiBcom STK807xPVR reference design",
+				{ &dib0700_usb_id_table[61], NULL },
+				{ NULL },
+			},
+		},
+
+		.rc.core = {
+			.rc_interval      = DEFAULT_RC_INTERVAL,
+			.rc_codes         = RC_MAP_DIB0700_RC5_TABLE,
+			.module_name	  = "dib0700",
+			.rc_query         = dib0700_rc_query_old_firmware,
+			.allowed_protos   = RC_PROTO_BIT_RC5 |
+					    RC_PROTO_BIT_RC6_MCE |
+					    RC_PROTO_BIT_NEC,
+			.change_protocol  = dib0700_change_protocol,
+		},
+	}, { DIB0700_DEFAULT_DEVICE_PROPERTIES,
+		.num_adapters = 1,
+		.adapter = {
+			{
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.caps  = DVB_USB_ADAP_HAS_PID_FILTER |
+					DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+				.pid_filter_count = 32,
+				.pid_filter = stk80xx_pid_filter,
+				.pid_filter_ctrl = stk80xx_pid_filter_ctrl,
+				.frontend_attach  = stk809x_frontend_attach,
+				.tuner_attach     = dib809x_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x02),
+			}},
+			},
+		},
+
+		.num_device_descs = 1,
+		.devices = {
+			{   "DiBcom STK8096GP reference design",
+				{ &dib0700_usb_id_table[67], NULL },
+				{ NULL },
+			},
+		},
+
+		.rc.core = {
+			.rc_interval      = DEFAULT_RC_INTERVAL,
+			.rc_codes         = RC_MAP_DIB0700_RC5_TABLE,
+			.module_name	  = "dib0700",
+			.rc_query         = dib0700_rc_query_old_firmware,
+			.allowed_protos   = RC_PROTO_BIT_RC5 |
+					    RC_PROTO_BIT_RC6_MCE |
+					    RC_PROTO_BIT_NEC,
+			.change_protocol  = dib0700_change_protocol,
+		},
+	}, { DIB0700_DEFAULT_DEVICE_PROPERTIES,
+		.num_adapters = 1,
+		.adapter = {
+			{
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.caps  = DVB_USB_ADAP_HAS_PID_FILTER |
+					DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+				.pid_filter_count = 32,
+				.pid_filter = dib90x0_pid_filter,
+				.pid_filter_ctrl = dib90x0_pid_filter_ctrl,
+				.frontend_attach  = stk9090m_frontend_attach,
+				.tuner_attach     = dib9090_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x02),
+			}},
+			},
+		},
+
+		.num_device_descs = 1,
+		.devices = {
+			{   "DiBcom STK9090M reference design",
+				{ &dib0700_usb_id_table[69], NULL },
+				{ NULL },
+			},
+		},
+
+		.rc.core = {
+			.rc_interval      = DEFAULT_RC_INTERVAL,
+			.rc_codes         = RC_MAP_DIB0700_RC5_TABLE,
+			.module_name	  = "dib0700",
+			.rc_query         = dib0700_rc_query_old_firmware,
+			.allowed_protos   = RC_PROTO_BIT_RC5 |
+					    RC_PROTO_BIT_RC6_MCE |
+					    RC_PROTO_BIT_NEC,
+			.change_protocol  = dib0700_change_protocol,
+		},
+	}, { DIB0700_DEFAULT_DEVICE_PROPERTIES,
+		.num_adapters = 1,
+		.adapter = {
+			{
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.caps  = DVB_USB_ADAP_HAS_PID_FILTER |
+					DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+				.pid_filter_count = 32,
+				.pid_filter = stk80xx_pid_filter,
+				.pid_filter_ctrl = stk80xx_pid_filter_ctrl,
+				.frontend_attach  = nim8096md_frontend_attach,
+				.tuner_attach     = nim8096md_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x02),
+			}},
+			},
+		},
+
+		.num_device_descs = 1,
+		.devices = {
+			{   "DiBcom NIM8096MD reference design",
+				{ &dib0700_usb_id_table[70], NULL },
+				{ NULL },
+			},
+		},
+
+		.rc.core = {
+			.rc_interval      = DEFAULT_RC_INTERVAL,
+			.rc_codes         = RC_MAP_DIB0700_RC5_TABLE,
+			.module_name	  = "dib0700",
+			.rc_query         = dib0700_rc_query_old_firmware,
+			.allowed_protos   = RC_PROTO_BIT_RC5 |
+					    RC_PROTO_BIT_RC6_MCE |
+					    RC_PROTO_BIT_NEC,
+			.change_protocol  = dib0700_change_protocol,
+		},
+	}, { DIB0700_DEFAULT_DEVICE_PROPERTIES,
+		.num_adapters = 1,
+		.adapter = {
+			{
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.caps  = DVB_USB_ADAP_HAS_PID_FILTER |
+					DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+				.pid_filter_count = 32,
+				.pid_filter = dib90x0_pid_filter,
+				.pid_filter_ctrl = dib90x0_pid_filter_ctrl,
+				.frontend_attach  = nim9090md_frontend_attach,
+				.tuner_attach     = nim9090md_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x02),
+			}},
+			},
+		},
+
+		.num_device_descs = 1,
+		.devices = {
+			{   "DiBcom NIM9090MD reference design",
+				{ &dib0700_usb_id_table[71], NULL },
+				{ NULL },
+			},
+		},
+
+		.rc.core = {
+			.rc_interval      = DEFAULT_RC_INTERVAL,
+			.rc_codes         = RC_MAP_DIB0700_RC5_TABLE,
+			.module_name	  = "dib0700",
+			.rc_query         = dib0700_rc_query_old_firmware,
+			.allowed_protos   = RC_PROTO_BIT_RC5 |
+					    RC_PROTO_BIT_RC6_MCE |
+					    RC_PROTO_BIT_NEC,
+			.change_protocol  = dib0700_change_protocol,
+		},
+	}, { DIB0700_DEFAULT_DEVICE_PROPERTIES,
+		.num_adapters = 1,
+		.adapter = {
+			{
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.caps  = DVB_USB_ADAP_HAS_PID_FILTER |
+					DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+				.pid_filter_count = 32,
+				.pid_filter = stk70x0p_pid_filter,
+				.pid_filter_ctrl = stk70x0p_pid_filter_ctrl,
+				.frontend_attach  = nim7090_frontend_attach,
+				.tuner_attach     = nim7090_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x02),
+			}},
+			},
+		},
+
+		.num_device_descs = 1,
+		.devices = {
+			{   "DiBcom NIM7090 reference design",
+				{ &dib0700_usb_id_table[72], NULL },
+				{ NULL },
+			},
+		},
+
+		.rc.core = {
+			.rc_interval      = DEFAULT_RC_INTERVAL,
+			.rc_codes         = RC_MAP_DIB0700_RC5_TABLE,
+			.module_name	  = "dib0700",
+			.rc_query         = dib0700_rc_query_old_firmware,
+			.allowed_protos   = RC_PROTO_BIT_RC5 |
+					    RC_PROTO_BIT_RC6_MCE |
+					    RC_PROTO_BIT_NEC,
+			.change_protocol  = dib0700_change_protocol,
+		},
+	}, { DIB0700_DEFAULT_DEVICE_PROPERTIES,
+		.num_adapters = 2,
+		.adapter = {
+			{
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.caps  = DVB_USB_ADAP_HAS_PID_FILTER |
+					DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+				.pid_filter_count = 32,
+				.pid_filter = stk70x0p_pid_filter,
+				.pid_filter_ctrl = stk70x0p_pid_filter_ctrl,
+				.frontend_attach  = tfe7090pvr_frontend0_attach,
+				.tuner_attach     = tfe7090pvr_tuner0_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x03),
+			}},
+			},
+			{
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.caps  = DVB_USB_ADAP_HAS_PID_FILTER |
+					DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+				.pid_filter_count = 32,
+				.pid_filter = stk70x0p_pid_filter,
+				.pid_filter_ctrl = stk70x0p_pid_filter_ctrl,
+				.frontend_attach  = tfe7090pvr_frontend1_attach,
+				.tuner_attach     = tfe7090pvr_tuner1_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x02),
+			}},
+			},
+		},
+
+		.num_device_descs = 1,
+		.devices = {
+			{   "DiBcom TFE7090PVR reference design",
+				{ &dib0700_usb_id_table[73], NULL },
+				{ NULL },
+			},
+		},
+
+		.rc.core = {
+			.rc_interval      = DEFAULT_RC_INTERVAL,
+			.rc_codes         = RC_MAP_DIB0700_RC5_TABLE,
+			.module_name	  = "dib0700",
+			.rc_query         = dib0700_rc_query_old_firmware,
+			.allowed_protos   = RC_PROTO_BIT_RC5 |
+					    RC_PROTO_BIT_RC6_MCE |
+					    RC_PROTO_BIT_NEC,
+			.change_protocol  = dib0700_change_protocol,
+		},
+	}, { DIB0700_DEFAULT_DEVICE_PROPERTIES,
+		.num_adapters = 1,
+		.adapter = {
+			{
+			DIB0700_NUM_FRONTENDS(1),
+			.fe = {{
+				.frontend_attach  = pctv340e_frontend_attach,
+				.tuner_attach     = xc4000_tuner_attach,
+
+				DIB0700_DEFAULT_STREAMING_CONFIG(0x02),
+			}},
+			},
+		},
+
+		.num_device_descs = 2,
+		.devices = {
+			{   "Pinnacle PCTV 340e HD Pro USB Stick",
+				{ &dib0700_usb_id_table[76], NULL },
+				{ NULL },
+			},
+			{   "Pinnacle PCTV Hybrid Stick Solo",
+				{ &dib0700_usb_id_table[77], NULL },
+				{ NULL },
+			},
+		},
+		.rc.core = {
+			.rc_interval      = DEFAULT_RC_INTERVAL,
+			.rc_codes         = RC_MAP_DIB0700_RC5_TABLE,
+			.module_name	  = "dib0700",
+			.rc_query         = dib0700_rc_query_old_firmware,
+			.allowed_protos   = RC_PROTO_BIT_RC5 |
+					    RC_PROTO_BIT_RC6_MCE |
+					    RC_PROTO_BIT_NEC,
+			.change_protocol  = dib0700_change_protocol,
+		},
+	}, { DIB0700_DEFAULT_DEVICE_PROPERTIES,
+		.num_adapters = 1,
+		.adapter = {
+			{
+				DIB0700_NUM_FRONTENDS(1),
+				.fe = {{
+					.caps  = DVB_USB_ADAP_HAS_PID_FILTER |
+						DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+					.pid_filter_count = 32,
+					.pid_filter = stk70x0p_pid_filter,
+					.pid_filter_ctrl = stk70x0p_pid_filter_ctrl,
+					.frontend_attach  = tfe7790p_frontend_attach,
+					.tuner_attach     = tfe7790p_tuner_attach,
+
+					DIB0700_DEFAULT_STREAMING_CONFIG(0x03),
+				} },
+			},
+		},
+
+		.num_device_descs = 1,
+		.devices = {
+			{   "DiBcom TFE7790P reference design",
+				{ &dib0700_usb_id_table[78], NULL },
+				{ NULL },
+			},
+		},
+
+		.rc.core = {
+			.rc_interval      = DEFAULT_RC_INTERVAL,
+			.rc_codes         = RC_MAP_DIB0700_RC5_TABLE,
+			.module_name	  = "dib0700",
+			.rc_query         = dib0700_rc_query_old_firmware,
+			.allowed_protos   = RC_PROTO_BIT_RC5 |
+					    RC_PROTO_BIT_RC6_MCE |
+					    RC_PROTO_BIT_NEC,
+			.change_protocol  = dib0700_change_protocol,
+		},
+	}, { DIB0700_DEFAULT_DEVICE_PROPERTIES,
+		.num_adapters = 1,
+		.adapter = {
+			{
+				DIB0700_NUM_FRONTENDS(1),
+				.fe = {{
+					.caps  = DVB_USB_ADAP_HAS_PID_FILTER |
+						DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+					.pid_filter_count = 32,
+					.pid_filter = stk80xx_pid_filter,
+					.pid_filter_ctrl = stk80xx_pid_filter_ctrl,
+					.frontend_attach  = tfe8096p_frontend_attach,
+					.tuner_attach     = tfe8096p_tuner_attach,
+
+					DIB0700_DEFAULT_STREAMING_CONFIG(0x02),
+
+				} },
+			},
+		},
+
+		.num_device_descs = 1,
+		.devices = {
+			{   "DiBcom TFE8096P reference design",
+				{ &dib0700_usb_id_table[79], NULL },
+				{ NULL },
+			},
+		},
+
+		.rc.core = {
+			.rc_interval      = DEFAULT_RC_INTERVAL,
+			.rc_codes         = RC_MAP_DIB0700_RC5_TABLE,
+			.module_name	  = "dib0700",
+			.rc_query         = dib0700_rc_query_old_firmware,
+			.allowed_protos   = RC_PROTO_BIT_RC5 |
+					    RC_PROTO_BIT_RC6_MCE |
+					    RC_PROTO_BIT_NEC,
+			.change_protocol  = dib0700_change_protocol,
+		},
+	}, { DIB0700_DEFAULT_DEVICE_PROPERTIES,
+		.num_adapters = 2,
+		.adapter = {
+			{
+				.num_frontends = 1,
+				.fe = {{
+					.caps  = DVB_USB_ADAP_HAS_PID_FILTER |
+						DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+					.pid_filter_count = 32,
+					.pid_filter = stk80xx_pid_filter,
+					.pid_filter_ctrl = stk80xx_pid_filter_ctrl,
+					.frontend_attach  = stk809x_frontend_attach,
+					.tuner_attach     = dib809x_tuner_attach,
+
+					DIB0700_DEFAULT_STREAMING_CONFIG(0x02),
+				} },
+				.size_of_priv =
+					sizeof(struct dib0700_adapter_state),
+			}, {
+				.num_frontends = 1,
+				.fe = { {
+					.caps  = DVB_USB_ADAP_HAS_PID_FILTER |
+						DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+					.pid_filter_count = 32,
+					.pid_filter = stk80xx_pid_filter,
+					.pid_filter_ctrl = stk80xx_pid_filter_ctrl,
+					.frontend_attach  = stk809x_frontend1_attach,
+					.tuner_attach     = dib809x_tuner_attach,
+
+					DIB0700_DEFAULT_STREAMING_CONFIG(0x03),
+				} },
+				.size_of_priv =
+					sizeof(struct dib0700_adapter_state),
+			},
+		},
+		.num_device_descs = 1,
+		.devices = {
+			{   "DiBcom STK8096-PVR reference design",
+				{ &dib0700_usb_id_table[83],
+					&dib0700_usb_id_table[84], NULL},
+				{ NULL },
+			},
+		},
+
+		.rc.core = {
+			.rc_interval      = DEFAULT_RC_INTERVAL,
+			.rc_codes         = RC_MAP_DIB0700_RC5_TABLE,
+			.module_name  = "dib0700",
+			.rc_query         = dib0700_rc_query_old_firmware,
+			.allowed_protos   = RC_PROTO_BIT_RC5 |
+				RC_PROTO_BIT_RC6_MCE |
+				RC_PROTO_BIT_NEC,
+			.change_protocol  = dib0700_change_protocol,
+		},
+	}, { DIB0700_DEFAULT_DEVICE_PROPERTIES,
+		.num_adapters = 1,
+		.adapter = {
+			{
+				DIB0700_NUM_FRONTENDS(1),
+				.fe = {{
+					.frontend_attach = xbox_one_attach,
+
+					DIB0700_DEFAULT_STREAMING_CONFIG(0x82),
+				} },
+			},
+		},
+		.num_device_descs = 1,
+		.devices = {
+			{ "Microsoft Xbox One Digital TV Tuner",
+				{ &dib0700_usb_id_table[86], NULL },
+				{ NULL },
+			},
+		},
+	},
+};
+
+int dib0700_device_count = ARRAY_SIZE(dib0700_devices);
diff --git a/drivers/media/usb/dvb-usb/dib07x0.h b/drivers/media/usb/dvb-usb/dib07x0.h
new file mode 100644
index 0000000..2e67f79
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/dib07x0.h
@@ -0,0 +1,22 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _DIB07X0_H_
+#define _DIB07X0_H_
+
+enum dib07x0_gpios {
+	GPIO0  =  0,
+	GPIO1  =  2,
+	GPIO2  =  3,
+	GPIO3  =  4,
+	GPIO4  =  5,
+	GPIO5  =  6,
+	GPIO6  =  8,
+	GPIO7  = 10,
+	GPIO8  = 11,
+	GPIO9  = 14,
+	GPIO10 = 15,
+};
+
+#define GPIO_IN  0
+#define GPIO_OUT 1
+
+#endif
diff --git a/drivers/media/usb/dvb-usb/dibusb-common.c b/drivers/media/usb/dvb-usb/dibusb-common.c
new file mode 100644
index 0000000..fb1b4f2
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/dibusb-common.c
@@ -0,0 +1,402 @@
+/* Common methods for dibusb-based-receivers.
+ *
+ * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@posteo.de)
+ *
+ *	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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+
+#include "dibusb.h"
+
+/* Max transfer size done by I2C transfer functions */
+#define MAX_XFER_SIZE  64
+
+static int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "set debugging level (1=info (|-able))." DVB_USB_DEBUG_STATUS);
+MODULE_LICENSE("GPL");
+
+#define deb_info(args...) dprintk(debug,0x01,args)
+
+/* common stuff used by the different dibusb modules */
+int dibusb_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff)
+{
+	if (adap->priv != NULL) {
+		struct dibusb_state *st = adap->priv;
+		if (st->ops.fifo_ctrl != NULL)
+			if (st->ops.fifo_ctrl(adap->fe_adap[0].fe, onoff)) {
+				err("error while controlling the fifo of the demod.");
+				return -ENODEV;
+			}
+	}
+	return 0;
+}
+EXPORT_SYMBOL(dibusb_streaming_ctrl);
+
+int dibusb_pid_filter(struct dvb_usb_adapter *adap, int index, u16 pid, int onoff)
+{
+	if (adap->priv != NULL) {
+		struct dibusb_state *st = adap->priv;
+		if (st->ops.pid_ctrl != NULL)
+			st->ops.pid_ctrl(adap->fe_adap[0].fe,
+					 index, pid, onoff);
+	}
+	return 0;
+}
+EXPORT_SYMBOL(dibusb_pid_filter);
+
+int dibusb_pid_filter_ctrl(struct dvb_usb_adapter *adap, int onoff)
+{
+	if (adap->priv != NULL) {
+		struct dibusb_state *st = adap->priv;
+		if (st->ops.pid_parse != NULL)
+			if (st->ops.pid_parse(adap->fe_adap[0].fe, onoff) < 0)
+				err("could not handle pid_parser");
+	}
+	return 0;
+}
+EXPORT_SYMBOL(dibusb_pid_filter_ctrl);
+
+int dibusb_power_ctrl(struct dvb_usb_device *d, int onoff)
+{
+	u8 *b;
+	int ret;
+
+	b = kmalloc(3, GFP_KERNEL);
+	if (!b)
+		return -ENOMEM;
+
+	b[0] = DIBUSB_REQ_SET_IOCTL;
+	b[1] = DIBUSB_IOCTL_CMD_POWER_MODE;
+	b[2] = onoff ? DIBUSB_IOCTL_POWER_WAKEUP : DIBUSB_IOCTL_POWER_SLEEP;
+
+	ret = dvb_usb_generic_write(d, b, 3);
+
+	kfree(b);
+
+	msleep(10);
+
+	return ret;
+}
+EXPORT_SYMBOL(dibusb_power_ctrl);
+
+int dibusb2_0_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff)
+{
+	int ret;
+	u8 *b;
+
+	b = kmalloc(3, GFP_KERNEL);
+	if (!b)
+		return -ENOMEM;
+
+	if ((ret = dibusb_streaming_ctrl(adap,onoff)) < 0)
+		goto ret;
+
+	if (onoff) {
+		b[0] = DIBUSB_REQ_SET_STREAMING_MODE;
+		b[1] = 0x00;
+		ret = dvb_usb_generic_write(adap->dev, b, 2);
+		if (ret  < 0)
+			goto ret;
+	}
+
+	b[0] = DIBUSB_REQ_SET_IOCTL;
+	b[1] = onoff ? DIBUSB_IOCTL_CMD_ENABLE_STREAM : DIBUSB_IOCTL_CMD_DISABLE_STREAM;
+	ret = dvb_usb_generic_write(adap->dev, b, 3);
+
+ret:
+	kfree(b);
+	return ret;
+}
+EXPORT_SYMBOL(dibusb2_0_streaming_ctrl);
+
+int dibusb2_0_power_ctrl(struct dvb_usb_device *d, int onoff)
+{
+	u8 *b;
+	int ret;
+
+	if (!onoff)
+		return 0;
+
+	b = kmalloc(3, GFP_KERNEL);
+	if (!b)
+		return -ENOMEM;
+
+	b[0] = DIBUSB_REQ_SET_IOCTL;
+	b[1] = DIBUSB_IOCTL_CMD_POWER_MODE;
+	b[2] = DIBUSB_IOCTL_POWER_WAKEUP;
+
+	ret = dvb_usb_generic_write(d, b, 3);
+
+	kfree(b);
+
+	return ret;
+}
+EXPORT_SYMBOL(dibusb2_0_power_ctrl);
+
+static int dibusb_i2c_msg(struct dvb_usb_device *d, u8 addr,
+			  u8 *wbuf, u16 wlen, u8 *rbuf, u16 rlen)
+{
+	u8 *sndbuf;
+	int ret, wo, len;
+
+	/* write only ? */
+	wo = (rbuf == NULL || rlen == 0);
+
+	len = 2 + wlen + (wo ? 0 : 2);
+
+	sndbuf = kmalloc(MAX_XFER_SIZE, GFP_KERNEL);
+	if (!sndbuf)
+		return -ENOMEM;
+
+	if (4 + wlen > MAX_XFER_SIZE) {
+		warn("i2c wr: len=%d is too big!\n", wlen);
+		ret = -EOPNOTSUPP;
+		goto ret;
+	}
+
+	sndbuf[0] = wo ? DIBUSB_REQ_I2C_WRITE : DIBUSB_REQ_I2C_READ;
+	sndbuf[1] = (addr << 1) | (wo ? 0 : 1);
+
+	memcpy(&sndbuf[2], wbuf, wlen);
+
+	if (!wo) {
+		sndbuf[wlen + 2] = (rlen >> 8) & 0xff;
+		sndbuf[wlen + 3] = rlen & 0xff;
+	}
+
+	ret = dvb_usb_generic_rw(d, sndbuf, len, rbuf, rlen, 0);
+
+ret:
+	kfree(sndbuf);
+	return ret;
+}
+
+/*
+ * I2C master xfer function
+ */
+static int dibusb_i2c_xfer(struct i2c_adapter *adap,struct i2c_msg msg[],int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	int i;
+
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	for (i = 0; i < num; i++) {
+		/* write/read request */
+		if (i+1 < num && (msg[i].flags & I2C_M_RD) == 0
+					  && (msg[i+1].flags & I2C_M_RD)) {
+			if (dibusb_i2c_msg(d, msg[i].addr, msg[i].buf,msg[i].len,
+						msg[i+1].buf,msg[i+1].len) < 0)
+				break;
+			i++;
+		} else if ((msg[i].flags & I2C_M_RD) == 0) {
+			if (dibusb_i2c_msg(d, msg[i].addr, msg[i].buf,msg[i].len,NULL,0) < 0)
+				break;
+		} else if (msg[i].addr != 0x50) {
+			/* 0x50 is the address of the eeprom - we need to protect it
+			 * from dibusb's bad i2c implementation: reads without
+			 * writing the offset before are forbidden */
+			if (dibusb_i2c_msg(d, msg[i].addr, NULL, 0, msg[i].buf, msg[i].len) < 0)
+				break;
+		}
+	}
+
+	mutex_unlock(&d->i2c_mutex);
+	return i;
+}
+
+static u32 dibusb_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+struct i2c_algorithm dibusb_i2c_algo = {
+	.master_xfer   = dibusb_i2c_xfer,
+	.functionality = dibusb_i2c_func,
+};
+EXPORT_SYMBOL(dibusb_i2c_algo);
+
+int dibusb_read_eeprom_byte(struct dvb_usb_device *d, u8 offs, u8 *val)
+{
+	u8 *buf;
+	int rc;
+
+	buf = kmalloc(2, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	buf[0] = offs;
+
+	rc = dibusb_i2c_msg(d, 0x50, &buf[0], 1, &buf[1], 1);
+	*val = buf[1];
+	kfree(buf);
+
+	return rc;
+}
+EXPORT_SYMBOL(dibusb_read_eeprom_byte);
+
+/*
+ * common remote control stuff
+ */
+struct rc_map_table rc_map_dibusb_table[] = {
+	/* Key codes for the little Artec T1/Twinhan/HAMA/ remote. */
+	{ 0x0016, KEY_POWER },
+	{ 0x0010, KEY_MUTE },
+	{ 0x0003, KEY_1 },
+	{ 0x0001, KEY_2 },
+	{ 0x0006, KEY_3 },
+	{ 0x0009, KEY_4 },
+	{ 0x001d, KEY_5 },
+	{ 0x001f, KEY_6 },
+	{ 0x000d, KEY_7 },
+	{ 0x0019, KEY_8 },
+	{ 0x001b, KEY_9 },
+	{ 0x0015, KEY_0 },
+	{ 0x0005, KEY_CHANNELUP },
+	{ 0x0002, KEY_CHANNELDOWN },
+	{ 0x001e, KEY_VOLUMEUP },
+	{ 0x000a, KEY_VOLUMEDOWN },
+	{ 0x0011, KEY_RECORD },
+	{ 0x0017, KEY_FAVORITES }, /* Heart symbol - Channel list. */
+	{ 0x0014, KEY_PLAY },
+	{ 0x001a, KEY_STOP },
+	{ 0x0040, KEY_REWIND },
+	{ 0x0012, KEY_FASTFORWARD },
+	{ 0x000e, KEY_PREVIOUS }, /* Recall - Previous channel. */
+	{ 0x004c, KEY_PAUSE },
+	{ 0x004d, KEY_SCREEN }, /* Full screen mode. */
+	{ 0x0054, KEY_AUDIO }, /* MTS - Switch to secondary audio. */
+	/* additional keys TwinHan VisionPlus, the Artec seemingly not have */
+	{ 0x000c, KEY_CANCEL }, /* Cancel */
+	{ 0x001c, KEY_EPG }, /* EPG */
+	{ 0x0000, KEY_TAB }, /* Tab */
+	{ 0x0048, KEY_INFO }, /* Preview */
+	{ 0x0004, KEY_LIST }, /* RecordList */
+	{ 0x000f, KEY_TEXT }, /* Teletext */
+	/* Key codes for the KWorld/ADSTech/JetWay remote. */
+	{ 0x8612, KEY_POWER },
+	{ 0x860f, KEY_SELECT }, /* source */
+	{ 0x860c, KEY_UNKNOWN }, /* scan */
+	{ 0x860b, KEY_EPG },
+	{ 0x8610, KEY_MUTE },
+	{ 0x8601, KEY_1 },
+	{ 0x8602, KEY_2 },
+	{ 0x8603, KEY_3 },
+	{ 0x8604, KEY_4 },
+	{ 0x8605, KEY_5 },
+	{ 0x8606, KEY_6 },
+	{ 0x8607, KEY_7 },
+	{ 0x8608, KEY_8 },
+	{ 0x8609, KEY_9 },
+	{ 0x860a, KEY_0 },
+	{ 0x8618, KEY_ZOOM },
+	{ 0x861c, KEY_UNKNOWN }, /* preview */
+	{ 0x8613, KEY_UNKNOWN }, /* snap */
+	{ 0x8600, KEY_UNDO },
+	{ 0x861d, KEY_RECORD },
+	{ 0x860d, KEY_STOP },
+	{ 0x860e, KEY_PAUSE },
+	{ 0x8616, KEY_PLAY },
+	{ 0x8611, KEY_BACK },
+	{ 0x8619, KEY_FORWARD },
+	{ 0x8614, KEY_UNKNOWN }, /* pip */
+	{ 0x8615, KEY_ESC },
+	{ 0x861a, KEY_UP },
+	{ 0x861e, KEY_DOWN },
+	{ 0x861f, KEY_LEFT },
+	{ 0x861b, KEY_RIGHT },
+
+	/* Key codes for the DiBcom MOD3000 remote. */
+	{ 0x8000, KEY_MUTE },
+	{ 0x8001, KEY_TEXT },
+	{ 0x8002, KEY_HOME },
+	{ 0x8003, KEY_POWER },
+
+	{ 0x8004, KEY_RED },
+	{ 0x8005, KEY_GREEN },
+	{ 0x8006, KEY_YELLOW },
+	{ 0x8007, KEY_BLUE },
+
+	{ 0x8008, KEY_DVD },
+	{ 0x8009, KEY_AUDIO },
+	{ 0x800a, KEY_IMAGES },      /* Pictures */
+	{ 0x800b, KEY_VIDEO },
+
+	{ 0x800c, KEY_BACK },
+	{ 0x800d, KEY_UP },
+	{ 0x800e, KEY_RADIO },
+	{ 0x800f, KEY_EPG },
+
+	{ 0x8010, KEY_LEFT },
+	{ 0x8011, KEY_OK },
+	{ 0x8012, KEY_RIGHT },
+	{ 0x8013, KEY_UNKNOWN },    /* SAP */
+
+	{ 0x8014, KEY_TV },
+	{ 0x8015, KEY_DOWN },
+	{ 0x8016, KEY_MENU },       /* DVD Menu */
+	{ 0x8017, KEY_LAST },
+
+	{ 0x8018, KEY_RECORD },
+	{ 0x8019, KEY_STOP },
+	{ 0x801a, KEY_PAUSE },
+	{ 0x801b, KEY_PLAY },
+
+	{ 0x801c, KEY_PREVIOUS },
+	{ 0x801d, KEY_REWIND },
+	{ 0x801e, KEY_FASTFORWARD },
+	{ 0x801f, KEY_NEXT},
+
+	{ 0x8040, KEY_1 },
+	{ 0x8041, KEY_2 },
+	{ 0x8042, KEY_3 },
+	{ 0x8043, KEY_CHANNELUP },
+
+	{ 0x8044, KEY_4 },
+	{ 0x8045, KEY_5 },
+	{ 0x8046, KEY_6 },
+	{ 0x8047, KEY_CHANNELDOWN },
+
+	{ 0x8048, KEY_7 },
+	{ 0x8049, KEY_8 },
+	{ 0x804a, KEY_9 },
+	{ 0x804b, KEY_VOLUMEUP },
+
+	{ 0x804c, KEY_CLEAR },
+	{ 0x804d, KEY_0 },
+	{ 0x804e, KEY_ENTER },
+	{ 0x804f, KEY_VOLUMEDOWN },
+};
+EXPORT_SYMBOL(rc_map_dibusb_table);
+
+int dibusb_rc_query(struct dvb_usb_device *d, u32 *event, int *state)
+{
+	u8 *buf;
+	int ret;
+
+	buf = kmalloc(5, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	buf[0] = DIBUSB_REQ_POLL_REMOTE;
+
+	ret = dvb_usb_generic_rw(d, buf, 1, buf, 5, 0);
+	if (ret < 0)
+		goto ret;
+
+	dvb_usb_nec_rc_key_to_event(d, buf, event, state);
+
+	if (buf[0] != 0)
+		deb_info("key: %*ph\n", 5, buf);
+
+ret:
+	kfree(buf);
+
+	return ret;
+}
+EXPORT_SYMBOL(dibusb_rc_query);
diff --git a/drivers/media/usb/dvb-usb/dibusb-mb.c b/drivers/media/usb/dvb-usb/dibusb-mb.c
new file mode 100644
index 0000000..4089205
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/dibusb-mb.c
@@ -0,0 +1,471 @@
+/* DVB USB compliant linux driver for mobile DVB-T USB devices based on
+ * reference designs made by DiBcom (http://www.dibcom.fr/) (DiB3000M-B)
+ *
+ * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@posteo.de)
+ *
+ * based on GPL code from DiBcom, which has
+ * Copyright (C) 2004 Amaury Demol for DiBcom
+ *
+ *	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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#include "dibusb.h"
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static int dib3000mb_i2c_gate_ctrl(struct dvb_frontend* fe, int enable)
+{
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	struct dibusb_state *st = adap->priv;
+
+	return st->ops.tuner_pass_ctrl(fe, enable, st->tuner_addr);
+}
+
+static int dibusb_dib3000mb_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dib3000_config demod_cfg;
+	struct dibusb_state *st = adap->priv;
+
+	demod_cfg.demod_address = 0x8;
+
+	adap->fe_adap[0].fe = dvb_attach(dib3000mb_attach, &demod_cfg,
+					 &adap->dev->i2c_adap, &st->ops);
+	if ((adap->fe_adap[0].fe) == NULL)
+		return -ENODEV;
+
+	adap->fe_adap[0].fe->ops.i2c_gate_ctrl = dib3000mb_i2c_gate_ctrl;
+
+	return 0;
+}
+
+static int dibusb_thomson_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct dibusb_state *st = adap->priv;
+
+	st->tuner_addr = 0x61;
+
+	dvb_attach(dvb_pll_attach, adap->fe_adap[0].fe, 0x61, &adap->dev->i2c_adap,
+		   DVB_PLL_TUA6010XS);
+	return 0;
+}
+
+static int dibusb_panasonic_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct dibusb_state *st = adap->priv;
+
+	st->tuner_addr = 0x60;
+
+	dvb_attach(dvb_pll_attach, adap->fe_adap[0].fe, 0x60, &adap->dev->i2c_adap,
+		   DVB_PLL_TDA665X);
+	return 0;
+}
+
+/* Some of the Artec 1.1 device aren't equipped with the default tuner
+ * (Thomson Cable), but with a Panasonic ENV77H11D5.  This function figures
+ * this out. */
+static int dibusb_tuner_probe_and_attach(struct dvb_usb_adapter *adap)
+{
+	u8 b[2] = { 0,0 }, b2[1];
+	int ret = 0;
+	struct i2c_msg msg[2] = {
+		{ .flags = 0,        .buf = b,  .len = 2 },
+		{ .flags = I2C_M_RD, .buf = b2, .len = 1 },
+	};
+	struct dibusb_state *st = adap->priv;
+
+	/* the Panasonic sits on I2C addrass 0x60, the Thomson on 0x61 */
+	msg[0].addr = msg[1].addr = st->tuner_addr = 0x60;
+
+	if (adap->fe_adap[0].fe->ops.i2c_gate_ctrl)
+		adap->fe_adap[0].fe->ops.i2c_gate_ctrl(adap->fe_adap[0].fe, 1);
+
+	if (i2c_transfer(&adap->dev->i2c_adap, msg, 2) != 2) {
+		err("tuner i2c write failed.");
+		ret = -EREMOTEIO;
+	}
+
+	if (adap->fe_adap[0].fe->ops.i2c_gate_ctrl)
+		adap->fe_adap[0].fe->ops.i2c_gate_ctrl(adap->fe_adap[0].fe, 0);
+
+	if (b2[0] == 0xfe) {
+		info("This device has the Thomson Cable onboard. Which is default.");
+		ret = dibusb_thomson_tuner_attach(adap);
+	} else {
+		info("This device has the Panasonic ENV77H11D5 onboard.");
+		ret = dibusb_panasonic_tuner_attach(adap);
+	}
+
+	return ret;
+}
+
+/* USB Driver stuff */
+static struct dvb_usb_device_properties dibusb1_1_properties;
+static struct dvb_usb_device_properties dibusb1_1_an2235_properties;
+static struct dvb_usb_device_properties dibusb2_0b_properties;
+static struct dvb_usb_device_properties artec_t1_usb2_properties;
+
+static int dibusb_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	if (0 == dvb_usb_device_init(intf, &dibusb1_1_properties,
+				     THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, &dibusb1_1_an2235_properties,
+				     THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, &dibusb2_0b_properties,
+				     THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, &artec_t1_usb2_properties,
+				     THIS_MODULE, NULL, adapter_nr))
+		return 0;
+
+	return -EINVAL;
+}
+
+/* do not change the order of the ID table */
+static struct usb_device_id dibusb_dib3000mb_table [] = {
+/* 00 */	{ USB_DEVICE(USB_VID_WIDEVIEW,		USB_PID_AVERMEDIA_DVBT_USB_COLD) },
+/* 01 */	{ USB_DEVICE(USB_VID_WIDEVIEW,		USB_PID_AVERMEDIA_DVBT_USB_WARM) },
+/* 02 */	{ USB_DEVICE(USB_VID_COMPRO,		USB_PID_COMPRO_DVBU2000_COLD) },
+/* 03 */	{ USB_DEVICE(USB_VID_COMPRO,		USB_PID_COMPRO_DVBU2000_WARM) },
+/* 04 */	{ USB_DEVICE(USB_VID_COMPRO_UNK,	USB_PID_COMPRO_DVBU2000_UNK_COLD) },
+/* 05 */	{ USB_DEVICE(USB_VID_DIBCOM,		USB_PID_DIBCOM_MOD3000_COLD) },
+/* 06 */	{ USB_DEVICE(USB_VID_DIBCOM,		USB_PID_DIBCOM_MOD3000_WARM) },
+/* 07 */	{ USB_DEVICE(USB_VID_EMPIA,		USB_PID_KWORLD_VSTREAM_COLD) },
+/* 08 */	{ USB_DEVICE(USB_VID_EMPIA,		USB_PID_KWORLD_VSTREAM_WARM) },
+/* 09 */	{ USB_DEVICE(USB_VID_GRANDTEC,		USB_PID_GRANDTEC_DVBT_USB_COLD) },
+/* 10 */	{ USB_DEVICE(USB_VID_GRANDTEC,		USB_PID_GRANDTEC_DVBT_USB_WARM) },
+/* 11 */	{ USB_DEVICE(USB_VID_GRANDTEC,		USB_PID_DIBCOM_MOD3000_COLD) },
+/* 12 */	{ USB_DEVICE(USB_VID_GRANDTEC,		USB_PID_DIBCOM_MOD3000_WARM) },
+/* 13 */	{ USB_DEVICE(USB_VID_HYPER_PALTEK,	USB_PID_UNK_HYPER_PALTEK_COLD) },
+/* 14 */	{ USB_DEVICE(USB_VID_HYPER_PALTEK,	USB_PID_UNK_HYPER_PALTEK_WARM) },
+/* 15 */	{ USB_DEVICE(USB_VID_VISIONPLUS,	USB_PID_TWINHAN_VP7041_COLD) },
+/* 16 */	{ USB_DEVICE(USB_VID_VISIONPLUS,	USB_PID_TWINHAN_VP7041_WARM) },
+/* 17 */	{ USB_DEVICE(USB_VID_TWINHAN,		USB_PID_TWINHAN_VP7041_COLD) },
+/* 18 */	{ USB_DEVICE(USB_VID_TWINHAN,		USB_PID_TWINHAN_VP7041_WARM) },
+/* 19 */	{ USB_DEVICE(USB_VID_ULTIMA_ELECTRONIC,	USB_PID_ULTIMA_TVBOX_COLD) },
+/* 20 */	{ USB_DEVICE(USB_VID_ULTIMA_ELECTRONIC,	USB_PID_ULTIMA_TVBOX_WARM) },
+/* 21 */	{ USB_DEVICE(USB_VID_ULTIMA_ELECTRONIC,	USB_PID_ULTIMA_TVBOX_AN2235_COLD) },
+/* 22 */	{ USB_DEVICE(USB_VID_ULTIMA_ELECTRONIC,	USB_PID_ULTIMA_TVBOX_AN2235_WARM) },
+/* 23 */	{ USB_DEVICE(USB_VID_ADSTECH,		USB_PID_ADSTECH_USB2_COLD) },
+
+/* device ID with default DIBUSB2_0-firmware and with the hacked firmware */
+/* 24 */	{ USB_DEVICE(USB_VID_ADSTECH,		USB_PID_ADSTECH_USB2_WARM) },
+/* 25 */	{ USB_DEVICE(USB_VID_KYE,		USB_PID_KYE_DVB_T_COLD) },
+/* 26 */	{ USB_DEVICE(USB_VID_KYE,		USB_PID_KYE_DVB_T_WARM) },
+
+/* 27 */	{ USB_DEVICE(USB_VID_KWORLD,		USB_PID_KWORLD_VSTREAM_COLD) },
+
+/* 28 */	{ USB_DEVICE(USB_VID_ULTIMA_ELECTRONIC,	USB_PID_ULTIMA_TVBOX_USB2_COLD) },
+/* 29 */	{ USB_DEVICE(USB_VID_ULTIMA_ELECTRONIC,	USB_PID_ULTIMA_TVBOX_USB2_WARM) },
+
+/*
+ * XXX: As Artec just 'forgot' to program the EEPROM on some Artec T1 devices
+ *      we don't catch these faulty IDs (namely 'Cypress FX1 USB controller') that
+ *      have been left on the device. If you don't have such a device but an Artec
+ *      device that's supposed to work with this driver but is not detected by it,
+ *      free to enable CONFIG_DVB_USB_DIBUSB_MB_FAULTY via your kernel config.
+ */
+
+#ifdef CONFIG_DVB_USB_DIBUSB_MB_FAULTY
+/* 30 */	{ USB_DEVICE(USB_VID_ANCHOR,		USB_PID_ULTIMA_TVBOX_ANCHOR_COLD) },
+#endif
+
+			{ }		/* Terminating entry */
+};
+MODULE_DEVICE_TABLE (usb, dibusb_dib3000mb_table);
+
+static struct dvb_usb_device_properties dibusb1_1_properties = {
+	.caps =  DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl = CYPRESS_AN2135,
+
+	.firmware = "dvb-usb-dibusb-5.0.0.11.fw",
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.caps = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+			.pid_filter_count = 16,
+
+			.streaming_ctrl   = dibusb_streaming_ctrl,
+			.pid_filter       = dibusb_pid_filter,
+			.pid_filter_ctrl  = dibusb_pid_filter_ctrl,
+			.frontend_attach  = dibusb_dib3000mb_frontend_attach,
+			.tuner_attach     = dibusb_tuner_probe_and_attach,
+
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+				.count = 7,
+				.endpoint = 0x02,
+				.u = {
+					.bulk = {
+						.buffersize = 4096,
+					}
+				}
+			},
+		}},
+			.size_of_priv     = sizeof(struct dibusb_state),
+		}
+	},
+
+	.power_ctrl       = dibusb_power_ctrl,
+
+	.rc.legacy = {
+		.rc_interval      = DEFAULT_RC_INTERVAL,
+		.rc_map_table     = rc_map_dibusb_table,
+		.rc_map_size      = 111, /* wow, that is ugly ... I want to load it to the driver dynamically */
+		.rc_query         = dibusb_rc_query,
+	},
+
+	.i2c_algo         = &dibusb_i2c_algo,
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.num_device_descs = 9,
+	.devices = {
+		{	"AVerMedia AverTV DVBT USB1.1",
+			{ &dibusb_dib3000mb_table[0],  NULL },
+			{ &dibusb_dib3000mb_table[1],  NULL },
+		},
+		{	"Compro Videomate DVB-U2000 - DVB-T USB1.1 (please confirm to linux-dvb)",
+			{ &dibusb_dib3000mb_table[2], &dibusb_dib3000mb_table[4], NULL},
+			{ &dibusb_dib3000mb_table[3], NULL },
+		},
+		{	"DiBcom USB1.1 DVB-T reference design (MOD3000)",
+			{ &dibusb_dib3000mb_table[5],  NULL },
+			{ &dibusb_dib3000mb_table[6],  NULL },
+		},
+		{	"KWorld V-Stream XPERT DTV - DVB-T USB1.1",
+			{ &dibusb_dib3000mb_table[7], NULL },
+			{ &dibusb_dib3000mb_table[8], NULL },
+		},
+		{	"Grandtec USB1.1 DVB-T",
+			{ &dibusb_dib3000mb_table[9],  &dibusb_dib3000mb_table[11], NULL },
+			{ &dibusb_dib3000mb_table[10], &dibusb_dib3000mb_table[12], NULL },
+		},
+		{	"Unknown USB1.1 DVB-T device ???? please report the name to the author",
+			{ &dibusb_dib3000mb_table[13], NULL },
+			{ &dibusb_dib3000mb_table[14], NULL },
+		},
+		{	"TwinhanDTV USB-Ter USB1.1 / Magic Box I / HAMA USB1.1 DVB-T device",
+			{ &dibusb_dib3000mb_table[15], &dibusb_dib3000mb_table[17], NULL},
+			{ &dibusb_dib3000mb_table[16], &dibusb_dib3000mb_table[18], NULL},
+		},
+		{	"Artec T1 USB1.1 TVBOX with AN2135",
+			{ &dibusb_dib3000mb_table[19], NULL },
+			{ &dibusb_dib3000mb_table[20], NULL },
+		},
+		{	"VideoWalker DVB-T USB",
+			{ &dibusb_dib3000mb_table[25], NULL },
+			{ &dibusb_dib3000mb_table[26], NULL },
+		},
+	}
+};
+
+static struct dvb_usb_device_properties dibusb1_1_an2235_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+	.usb_ctrl = CYPRESS_AN2235,
+
+	.firmware = "dvb-usb-dibusb-an2235-01.fw",
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.caps = DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF | DVB_USB_ADAP_HAS_PID_FILTER,
+			.pid_filter_count = 16,
+
+			.streaming_ctrl   = dibusb_streaming_ctrl,
+			.pid_filter       = dibusb_pid_filter,
+			.pid_filter_ctrl  = dibusb_pid_filter_ctrl,
+			.frontend_attach  = dibusb_dib3000mb_frontend_attach,
+			.tuner_attach     = dibusb_tuner_probe_and_attach,
+
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+				.count = 7,
+				.endpoint = 0x02,
+				.u = {
+					.bulk = {
+						.buffersize = 4096,
+					}
+				}
+			},
+		}},
+			.size_of_priv     = sizeof(struct dibusb_state),
+		},
+	},
+	.power_ctrl       = dibusb_power_ctrl,
+
+	.rc.legacy = {
+		.rc_interval      = DEFAULT_RC_INTERVAL,
+		.rc_map_table     = rc_map_dibusb_table,
+		.rc_map_size      = 111, /* wow, that is ugly ... I want to load it to the driver dynamically */
+		.rc_query         = dibusb_rc_query,
+	},
+
+	.i2c_algo         = &dibusb_i2c_algo,
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+#ifdef CONFIG_DVB_USB_DIBUSB_MB_FAULTY
+	.num_device_descs = 2,
+#else
+	.num_device_descs = 1,
+#endif
+	.devices = {
+		{	"Artec T1 USB1.1 TVBOX with AN2235",
+			{ &dibusb_dib3000mb_table[21], NULL },
+			{ &dibusb_dib3000mb_table[22], NULL },
+		},
+#ifdef CONFIG_DVB_USB_DIBUSB_MB_FAULTY
+		{	"Artec T1 USB1.1 TVBOX with AN2235 (faulty USB IDs)",
+			{ &dibusb_dib3000mb_table[30], NULL },
+			{ NULL },
+		},
+		{ NULL },
+#endif
+	}
+};
+
+static struct dvb_usb_device_properties dibusb2_0b_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl = CYPRESS_FX2,
+
+	.firmware = "dvb-usb-adstech-usb2-02.fw",
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.caps = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+			.pid_filter_count = 16,
+
+			.streaming_ctrl   = dibusb2_0_streaming_ctrl,
+			.pid_filter       = dibusb_pid_filter,
+			.pid_filter_ctrl  = dibusb_pid_filter_ctrl,
+			.frontend_attach  = dibusb_dib3000mb_frontend_attach,
+			.tuner_attach     = dibusb_thomson_tuner_attach,
+
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+				.count = 7,
+				.endpoint = 0x06,
+				.u = {
+					.bulk = {
+						.buffersize = 4096,
+					}
+				}
+			},
+		}},
+			.size_of_priv     = sizeof(struct dibusb_state),
+		}
+	},
+	.power_ctrl       = dibusb2_0_power_ctrl,
+
+	.rc.legacy = {
+		.rc_interval      = DEFAULT_RC_INTERVAL,
+		.rc_map_table     = rc_map_dibusb_table,
+		.rc_map_size      = 111, /* wow, that is ugly ... I want to load it to the driver dynamically */
+		.rc_query         = dibusb_rc_query,
+	},
+
+	.i2c_algo         = &dibusb_i2c_algo,
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.num_device_descs = 2,
+	.devices = {
+		{	"KWorld/ADSTech Instant DVB-T USB2.0",
+			{ &dibusb_dib3000mb_table[23], NULL },
+			{ &dibusb_dib3000mb_table[24], NULL },
+		},
+		{	"KWorld Xpert DVB-T USB2.0",
+			{ &dibusb_dib3000mb_table[27], NULL },
+			{ NULL }
+		},
+		{ NULL },
+	}
+};
+
+static struct dvb_usb_device_properties artec_t1_usb2_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl = CYPRESS_FX2,
+
+	.firmware = "dvb-usb-dibusb-6.0.0.8.fw",
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.caps = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+			.pid_filter_count = 16,
+
+			.streaming_ctrl   = dibusb2_0_streaming_ctrl,
+			.pid_filter       = dibusb_pid_filter,
+			.pid_filter_ctrl  = dibusb_pid_filter_ctrl,
+			.frontend_attach  = dibusb_dib3000mb_frontend_attach,
+			.tuner_attach     = dibusb_tuner_probe_and_attach,
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+				.count = 7,
+				.endpoint = 0x06,
+				.u = {
+					.bulk = {
+						.buffersize = 4096,
+					}
+				}
+			},
+		}},
+			.size_of_priv     = sizeof(struct dibusb_state),
+		}
+	},
+	.power_ctrl       = dibusb2_0_power_ctrl,
+
+	.rc.legacy = {
+		.rc_interval      = DEFAULT_RC_INTERVAL,
+		.rc_map_table     = rc_map_dibusb_table,
+		.rc_map_size      = 111, /* wow, that is ugly ... I want to load it to the driver dynamically */
+		.rc_query         = dibusb_rc_query,
+	},
+
+	.i2c_algo         = &dibusb_i2c_algo,
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.num_device_descs = 1,
+	.devices = {
+		{	"Artec T1 USB2.0",
+			{ &dibusb_dib3000mb_table[28], NULL },
+			{ &dibusb_dib3000mb_table[29], NULL },
+		},
+		{ NULL },
+	}
+};
+
+static struct usb_driver dibusb_driver = {
+	.name		= "dvb_usb_dibusb_mb",
+	.probe		= dibusb_probe,
+	.disconnect = dvb_usb_device_exit,
+	.id_table	= dibusb_dib3000mb_table,
+};
+
+module_usb_driver(dibusb_driver);
+
+MODULE_AUTHOR("Patrick Boettcher <patrick.boettcher@posteo.de>");
+MODULE_DESCRIPTION("Driver for DiBcom USB DVB-T devices (DiB3000M-B based)");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/dibusb-mc-common.c b/drivers/media/usb/dvb-usb/dibusb-mc-common.c
new file mode 100644
index 0000000..ec3a20a
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/dibusb-mc-common.c
@@ -0,0 +1,169 @@
+/* Common methods for dibusb-based-receivers.
+ *
+ * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@desy.de)
+ *
+ *	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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+
+#include "dibusb.h"
+
+MODULE_LICENSE("GPL");
+
+/* 3000MC/P stuff */
+// Config Adjacent channels  Perf -cal22
+static struct dibx000_agc_config dib3000p_mt2060_agc_config = {
+	.band_caps = BAND_VHF | BAND_UHF,
+	.setup     = (1 << 8) | (5 << 5) | (1 << 4) | (1 << 3) | (0 << 2) | (2 << 0),
+
+	.agc1_max = 48497,
+	.agc1_min = 23593,
+	.agc2_max = 46531,
+	.agc2_min = 24904,
+
+	.agc1_pt1 = 0x65,
+	.agc1_pt2 = 0x69,
+
+	.agc1_slope1 = 0x51,
+	.agc1_slope2 = 0x27,
+
+	.agc2_pt1 = 0,
+	.agc2_pt2 = 0x33,
+
+	.agc2_slope1 = 0x35,
+	.agc2_slope2 = 0x37,
+};
+
+static struct dib3000mc_config stk3000p_dib3000p_config = {
+	&dib3000p_mt2060_agc_config,
+
+	.max_time     = 0x196,
+	.ln_adc_level = 0x1cc7,
+
+	.output_mpeg2_in_188_bytes = 1,
+
+	.agc_command1 = 1,
+	.agc_command2 = 1,
+};
+
+static struct dibx000_agc_config dib3000p_panasonic_agc_config = {
+	.band_caps = BAND_VHF | BAND_UHF,
+	.setup     = (1 << 8) | (5 << 5) | (1 << 4) | (1 << 3) | (0 << 2) | (2 << 0),
+
+	.agc1_max = 56361,
+	.agc1_min = 22282,
+	.agc2_max = 47841,
+	.agc2_min = 36045,
+
+	.agc1_pt1 = 0x3b,
+	.agc1_pt2 = 0x6b,
+
+	.agc1_slope1 = 0x55,
+	.agc1_slope2 = 0x1d,
+
+	.agc2_pt1 = 0,
+	.agc2_pt2 = 0x0a,
+
+	.agc2_slope1 = 0x95,
+	.agc2_slope2 = 0x1e,
+};
+
+static struct dib3000mc_config mod3000p_dib3000p_config = {
+	&dib3000p_panasonic_agc_config,
+
+	.max_time     = 0x51,
+	.ln_adc_level = 0x1cc7,
+
+	.output_mpeg2_in_188_bytes = 1,
+
+	.agc_command1 = 1,
+	.agc_command2 = 1,
+};
+
+int dibusb_dib3000mc_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	if (le16_to_cpu(adap->dev->udev->descriptor.idVendor) == USB_VID_LITEON &&
+	    le16_to_cpu(adap->dev->udev->descriptor.idProduct) ==
+			USB_PID_LITEON_DVB_T_WARM) {
+		msleep(1000);
+	}
+
+	adap->fe_adap[0].fe = dvb_attach(dib3000mc_attach,
+					 &adap->dev->i2c_adap,
+					 DEFAULT_DIB3000P_I2C_ADDRESS,
+					 &mod3000p_dib3000p_config);
+	if ((adap->fe_adap[0].fe) == NULL)
+		adap->fe_adap[0].fe = dvb_attach(dib3000mc_attach,
+						 &adap->dev->i2c_adap,
+						 DEFAULT_DIB3000MC_I2C_ADDRESS,
+						 &mod3000p_dib3000p_config);
+	if ((adap->fe_adap[0].fe) != NULL) {
+		if (adap->priv != NULL) {
+			struct dibusb_state *st = adap->priv;
+			st->ops.pid_parse = dib3000mc_pid_parse;
+			st->ops.pid_ctrl  = dib3000mc_pid_control;
+		}
+		return 0;
+	}
+	return -ENODEV;
+}
+EXPORT_SYMBOL(dibusb_dib3000mc_frontend_attach);
+
+static struct mt2060_config stk3000p_mt2060_config = {
+	0x60
+};
+
+int dibusb_dib3000mc_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct dibusb_state *st = adap->priv;
+	u8 a,b;
+	u16 if1 = 1220;
+	struct i2c_adapter *tun_i2c;
+
+	// First IF calibration for Liteon Sticks
+	if (le16_to_cpu(adap->dev->udev->descriptor.idVendor) == USB_VID_LITEON &&
+	    le16_to_cpu(adap->dev->udev->descriptor.idProduct) == USB_PID_LITEON_DVB_T_WARM) {
+
+		dibusb_read_eeprom_byte(adap->dev,0x7E,&a);
+		dibusb_read_eeprom_byte(adap->dev,0x7F,&b);
+
+		if (a == 0x00)
+			if1 += b;
+		else if (a == 0x80)
+			if1 -= b;
+		else
+			warn("LITE-ON DVB-T: Strange IF1 calibration :%2X %2X\n", a, b);
+
+	} else if (le16_to_cpu(adap->dev->udev->descriptor.idVendor) == USB_VID_DIBCOM &&
+		   le16_to_cpu(adap->dev->udev->descriptor.idProduct) == USB_PID_DIBCOM_MOD3001_WARM) {
+		u8 desc;
+		dibusb_read_eeprom_byte(adap->dev, 7, &desc);
+		if (desc == 2) {
+			a = 127;
+			do {
+				dibusb_read_eeprom_byte(adap->dev, a, &desc);
+				a--;
+			} while (a > 7 && (desc == 0xff || desc == 0x00));
+			if (desc & 0x80)
+				if1 -= (0xff - desc);
+			else
+				if1 += desc;
+		}
+	}
+
+	tun_i2c = dib3000mc_get_tuner_i2c_master(adap->fe_adap[0].fe, 1);
+	if (dvb_attach(mt2060_attach, adap->fe_adap[0].fe, tun_i2c, &stk3000p_mt2060_config, if1) == NULL) {
+		/* not found - use panasonic pll parameters */
+		if (dvb_attach(dvb_pll_attach, adap->fe_adap[0].fe, 0x60, tun_i2c, DVB_PLL_ENV57H1XD5) == NULL)
+			return -ENOMEM;
+	} else {
+		st->mt2060_present = 1;
+		/* set the correct parameters for the dib3000p */
+		dib3000mc_set_config(adap->fe_adap[0].fe, &stk3000p_dib3000p_config);
+	}
+	return 0;
+}
+EXPORT_SYMBOL(dibusb_dib3000mc_tuner_attach);
diff --git a/drivers/media/usb/dvb-usb/dibusb-mc.c b/drivers/media/usb/dvb-usb/dibusb-mc.c
new file mode 100644
index 0000000..bce8ffe
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/dibusb-mc.c
@@ -0,0 +1,149 @@
+/* DVB USB compliant linux driver for mobile DVB-T USB devices based on
+ * reference designs made by DiBcom (http://www.dibcom.fr/) (DiB3000M-C/P)
+ *
+ * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@posteo.de)
+ *
+ * based on GPL code from DiBcom, which has
+ * Copyright (C) 2004 Amaury Demol for DiBcom
+ *
+ *	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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#include "dibusb.h"
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+/* USB Driver stuff */
+static struct dvb_usb_device_properties dibusb_mc_properties;
+
+static int dibusb_mc_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	return dvb_usb_device_init(intf, &dibusb_mc_properties, THIS_MODULE,
+				   NULL, adapter_nr);
+}
+
+/* do not change the order of the ID table */
+static struct usb_device_id dibusb_dib3000mc_table [] = {
+/* 00 */	{ USB_DEVICE(USB_VID_DIBCOM,		USB_PID_DIBCOM_MOD3001_COLD) },
+/* 01 */	{ USB_DEVICE(USB_VID_DIBCOM,		USB_PID_DIBCOM_MOD3001_WARM) },
+/* 02 */	{ USB_DEVICE(USB_VID_ULTIMA_ELECTRONIC,	USB_PID_ULTIMA_TVBOX_USB2_COLD) },
+/* 03 */	{ USB_DEVICE(USB_VID_ULTIMA_ELECTRONIC,	USB_PID_ULTIMA_TVBOX_USB2_WARM) }, // ( ? )
+/* 04 */	{ USB_DEVICE(USB_VID_LITEON,		USB_PID_LITEON_DVB_T_COLD) },
+/* 05 */	{ USB_DEVICE(USB_VID_LITEON,		USB_PID_LITEON_DVB_T_WARM) },
+/* 06 */	{ USB_DEVICE(USB_VID_EMPIA,		USB_PID_DIGIVOX_MINI_SL_COLD) },
+/* 07 */	{ USB_DEVICE(USB_VID_EMPIA,		USB_PID_DIGIVOX_MINI_SL_WARM) },
+/* 08 */	{ USB_DEVICE(USB_VID_GRANDTEC,          USB_PID_GRANDTEC_DVBT_USB2_COLD) },
+/* 09 */	{ USB_DEVICE(USB_VID_GRANDTEC,          USB_PID_GRANDTEC_DVBT_USB2_WARM) },
+/* 10 */	{ USB_DEVICE(USB_VID_ULTIMA_ELECTRONIC,	USB_PID_ARTEC_T14_COLD) },
+/* 11 */	{ USB_DEVICE(USB_VID_ULTIMA_ELECTRONIC,	USB_PID_ARTEC_T14_WARM) },
+/* 12 */	{ USB_DEVICE(USB_VID_LEADTEK,		USB_PID_WINFAST_DTV_DONGLE_COLD) },
+/* 13 */	{ USB_DEVICE(USB_VID_LEADTEK,		USB_PID_WINFAST_DTV_DONGLE_WARM) },
+/* 14 */	{ USB_DEVICE(USB_VID_HUMAX_COEX,	USB_PID_DVB_T_USB_STICK_HIGH_SPEED_COLD) },
+/* 15 */	{ USB_DEVICE(USB_VID_HUMAX_COEX,	USB_PID_DVB_T_USB_STICK_HIGH_SPEED_WARM) },
+			{ }		/* Terminating entry */
+};
+MODULE_DEVICE_TABLE (usb, dibusb_dib3000mc_table);
+
+static struct dvb_usb_device_properties dibusb_mc_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl = CYPRESS_FX2,
+	.firmware = "dvb-usb-dibusb-6.0.0.8.fw",
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.caps = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+			.pid_filter_count = 32,
+			.streaming_ctrl   = dibusb2_0_streaming_ctrl,
+			.pid_filter       = dibusb_pid_filter,
+			.pid_filter_ctrl  = dibusb_pid_filter_ctrl,
+			.frontend_attach  = dibusb_dib3000mc_frontend_attach,
+			.tuner_attach     = dibusb_dib3000mc_tuner_attach,
+
+	/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+				.count = 8,
+				.endpoint = 0x06,
+				.u = {
+					.bulk = {
+						.buffersize = 4096,
+					}
+				}
+			},
+		}},
+			.size_of_priv     = sizeof(struct dibusb_state),
+		}
+	},
+	.power_ctrl       = dibusb2_0_power_ctrl,
+
+	.rc.legacy = {
+		.rc_interval      = DEFAULT_RC_INTERVAL,
+		.rc_map_table     = rc_map_dibusb_table,
+		.rc_map_size      = 111, /* FIXME */
+		.rc_query         = dibusb_rc_query,
+	},
+
+	.i2c_algo         = &dibusb_i2c_algo,
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.num_device_descs = 8,
+	.devices = {
+		{   "DiBcom USB2.0 DVB-T reference design (MOD3000P)",
+			{ &dibusb_dib3000mc_table[0], NULL },
+			{ &dibusb_dib3000mc_table[1], NULL },
+		},
+		{   "Artec T1 USB2.0 TVBOX (please check the warm ID)",
+			{ &dibusb_dib3000mc_table[2], NULL },
+			{ &dibusb_dib3000mc_table[3], NULL },
+		},
+		{   "LITE-ON USB2.0 DVB-T Tuner",
+		    /* Also rebranded as Intuix S800, Toshiba */
+			{ &dibusb_dib3000mc_table[4], NULL },
+			{ &dibusb_dib3000mc_table[5], NULL },
+		},
+		{   "MSI Digivox Mini SL",
+			{ &dibusb_dib3000mc_table[6], NULL },
+			{ &dibusb_dib3000mc_table[7], NULL },
+		},
+		{   "GRAND - USB2.0 DVB-T adapter",
+			{ &dibusb_dib3000mc_table[8], NULL },
+			{ &dibusb_dib3000mc_table[9], NULL },
+		},
+		{   "Artec T14 - USB2.0 DVB-T",
+			{ &dibusb_dib3000mc_table[10], NULL },
+			{ &dibusb_dib3000mc_table[11], NULL },
+		},
+		{   "Leadtek - USB2.0 Winfast DTV dongle",
+			{ &dibusb_dib3000mc_table[12], NULL },
+			{ &dibusb_dib3000mc_table[13], NULL },
+		},
+		{   "Humax/Coex DVB-T USB Stick 2.0 High Speed",
+			{ &dibusb_dib3000mc_table[14], NULL },
+			{ &dibusb_dib3000mc_table[15], NULL },
+		},
+		{ NULL },
+	}
+};
+
+static struct usb_driver dibusb_mc_driver = {
+	.name		= "dvb_usb_dibusb_mc",
+	.probe		= dibusb_mc_probe,
+	.disconnect = dvb_usb_device_exit,
+	.id_table	= dibusb_dib3000mc_table,
+};
+
+module_usb_driver(dibusb_mc_driver);
+
+MODULE_AUTHOR("Patrick Boettcher <patrick.boettcher@posteo.de>");
+MODULE_DESCRIPTION("Driver for DiBcom USB2.0 DVB-T (DiB3000M-C/P based) devices");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/dibusb.h b/drivers/media/usb/dvb-usb/dibusb.h
new file mode 100644
index 0000000..943df57
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/dibusb.h
@@ -0,0 +1,134 @@
+/* Header file for all dibusb-based-receivers.
+ *
+ * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@posteo.de)
+ *
+ *	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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#ifndef _DVB_USB_DIBUSB_H_
+#define _DVB_USB_DIBUSB_H_
+
+#ifndef DVB_USB_LOG_PREFIX
+ #define DVB_USB_LOG_PREFIX "dibusb"
+#endif
+#include "dvb-usb.h"
+
+#include "dib3000.h"
+#include "dib3000mc.h"
+#include "mt2060.h"
+
+/*
+ * protocol of all dibusb related devices
+ */
+
+/*
+ * bulk msg to/from endpoint 0x01
+ *
+ * general structure:
+ * request_byte parameter_bytes
+ */
+
+#define DIBUSB_REQ_START_READ			0x00
+#define DIBUSB_REQ_START_DEMOD			0x01
+
+/*
+ * i2c read
+ * bulk write: 0x02 ((7bit i2c_addr << 1) | 0x01) register_bytes length_word
+ * bulk read:  byte_buffer (length_word bytes)
+ */
+#define DIBUSB_REQ_I2C_READ			0x02
+
+/*
+ * i2c write
+ * bulk write: 0x03 (7bit i2c_addr << 1) register_bytes value_bytes
+ */
+#define DIBUSB_REQ_I2C_WRITE			0x03
+
+/*
+ * polling the value of the remote control
+ * bulk write: 0x04
+ * bulk read:  byte_buffer (5 bytes)
+ */
+#define DIBUSB_REQ_POLL_REMOTE       0x04
+
+/* additional status values for Hauppauge Remote Control Protocol */
+#define DIBUSB_RC_HAUPPAUGE_KEY_PRESSED	0x01
+#define DIBUSB_RC_HAUPPAUGE_KEY_EMPTY	0x03
+
+/* streaming mode:
+ * bulk write: 0x05 mode_byte
+ *
+ * mode_byte is mostly 0x00
+ */
+#define DIBUSB_REQ_SET_STREAMING_MODE	0x05
+
+/* interrupt the internal read loop, when blocking */
+#define DIBUSB_REQ_INTR_READ			0x06
+
+/* io control
+ * 0x07 cmd_byte param_bytes
+ *
+ * param_bytes can be up to 32 bytes
+ *
+ * cmd_byte function    parameter name
+ * 0x00     power mode
+ *                      0x00      sleep
+ *                      0x01      wakeup
+ *
+ * 0x01     enable streaming
+ * 0x02     disable streaming
+ *
+ *
+ */
+#define DIBUSB_REQ_SET_IOCTL			0x07
+
+/* IOCTL commands */
+
+/* change the power mode in firmware */
+#define DIBUSB_IOCTL_CMD_POWER_MODE		0x00
+#define DIBUSB_IOCTL_POWER_SLEEP			0x00
+#define DIBUSB_IOCTL_POWER_WAKEUP			0x01
+
+/* modify streaming of the FX2 */
+#define DIBUSB_IOCTL_CMD_ENABLE_STREAM	0x01
+#define DIBUSB_IOCTL_CMD_DISABLE_STREAM	0x02
+
+/* Max transfer size done by I2C transfer functions */
+#define MAX_XFER_SIZE  64
+
+struct dibusb_state {
+	struct dib_fe_xfer_ops ops;
+	int mt2060_present;
+	u8 tuner_addr;
+};
+
+struct dibusb_device_state {
+	/* for RC5 remote control */
+	int old_toggle;
+	int last_repeat_count;
+};
+
+extern struct i2c_algorithm dibusb_i2c_algo;
+
+extern int dibusb_dib3000mc_frontend_attach(struct dvb_usb_adapter *);
+extern int dibusb_dib3000mc_tuner_attach (struct dvb_usb_adapter *);
+
+extern int dibusb_streaming_ctrl(struct dvb_usb_adapter *, int);
+extern int dibusb_pid_filter(struct dvb_usb_adapter *, int, u16, int);
+extern int dibusb_pid_filter_ctrl(struct dvb_usb_adapter *, int);
+extern int dibusb2_0_streaming_ctrl(struct dvb_usb_adapter *, int);
+
+extern int dibusb_power_ctrl(struct dvb_usb_device *, int);
+extern int dibusb2_0_power_ctrl(struct dvb_usb_device *, int);
+
+#define DEFAULT_RC_INTERVAL 150
+//#define DEFAULT_RC_INTERVAL 100000
+
+extern struct rc_map_table rc_map_dibusb_table[];
+extern int dibusb_rc_query(struct dvb_usb_device *, u32 *, int *);
+extern int dibusb_read_eeprom_byte(struct dvb_usb_device *, u8, u8 *);
+
+#endif
diff --git a/drivers/media/usb/dvb-usb/digitv.c b/drivers/media/usb/dvb-usb/digitv.c
new file mode 100644
index 0000000..49b9d63
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/digitv.c
@@ -0,0 +1,361 @@
+/* DVB USB compliant linux driver for Nebula Electronics uDigiTV DVB-T USB2.0
+ * receiver
+ *
+ * Copyright (C) 2005 Patrick Boettcher (patrick.boettcher@posteo.de)
+ *
+ * partly based on the SDK published by Nebula Electronics
+ *
+ *	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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#include "digitv.h"
+
+#include "mt352.h"
+#include "nxt6000.h"
+
+/* debug */
+static int dvb_usb_digitv_debug;
+module_param_named(debug,dvb_usb_digitv_debug, int, 0644);
+MODULE_PARM_DESC(debug, "set debugging level (1=rc (or-able))." DVB_USB_DEBUG_STATUS);
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+#define deb_rc(args...)   dprintk(dvb_usb_digitv_debug,0x01,args)
+
+static int digitv_ctrl_msg(struct dvb_usb_device *d,
+		u8 cmd, u8 vv, u8 *wbuf, int wlen, u8 *rbuf, int rlen)
+{
+	struct digitv_state *st = d->priv;
+	int ret, wo;
+
+	wo = (rbuf == NULL || rlen == 0); /* write-only */
+
+	if (wlen > 4 || rlen > 4)
+		return -EIO;
+
+	memset(st->sndbuf, 0, 7);
+	memset(st->rcvbuf, 0, 7);
+
+	st->sndbuf[0] = cmd;
+	st->sndbuf[1] = vv;
+	st->sndbuf[2] = wo ? wlen : rlen;
+
+	if (wo) {
+		memcpy(&st->sndbuf[3], wbuf, wlen);
+		ret = dvb_usb_generic_write(d, st->sndbuf, 7);
+	} else {
+		ret = dvb_usb_generic_rw(d, st->sndbuf, 7, st->rcvbuf, 7, 10);
+		memcpy(rbuf, &st->rcvbuf[3], rlen);
+	}
+	return ret;
+}
+
+/* I2C */
+static int digitv_i2c_xfer(struct i2c_adapter *adap,struct i2c_msg msg[],int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	int i;
+
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	if (num > 2)
+		warn("more than 2 i2c messages at a time is not handled yet. TODO.");
+
+	for (i = 0; i < num; i++) {
+		/* write/read request */
+		if (i+1 < num && (msg[i+1].flags & I2C_M_RD)) {
+			if (digitv_ctrl_msg(d, USB_READ_COFDM, msg[i].buf[0], NULL, 0,
+						msg[i+1].buf,msg[i+1].len) < 0)
+				break;
+			i++;
+		} else
+			if (digitv_ctrl_msg(d,USB_WRITE_COFDM, msg[i].buf[0],
+						&msg[i].buf[1],msg[i].len-1,NULL,0) < 0)
+				break;
+	}
+
+	mutex_unlock(&d->i2c_mutex);
+	return i;
+}
+
+static u32 digitv_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm digitv_i2c_algo = {
+	.master_xfer   = digitv_i2c_xfer,
+	.functionality = digitv_i2c_func,
+};
+
+/* Callbacks for DVB USB */
+static int digitv_identify_state (struct usb_device *udev, struct
+		dvb_usb_device_properties *props, struct dvb_usb_device_description **desc,
+		int *cold)
+{
+	*cold = udev->descriptor.iManufacturer == 0 && udev->descriptor.iProduct == 0;
+	return 0;
+}
+
+static int digitv_mt352_demod_init(struct dvb_frontend *fe)
+{
+	static u8 reset_buf[] = { 0x89, 0x38,  0x8a, 0x2d, 0x50, 0x80 };
+	static u8 init_buf[] = { 0x68, 0xa0,  0x8e, 0x40,  0x53, 0x50,
+			0x67, 0x20,  0x7d, 0x01,  0x7c, 0x00,  0x7a, 0x00,
+			0x79, 0x20,  0x57, 0x05,  0x56, 0x31,  0x88, 0x0f,
+			0x75, 0x32 };
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(reset_buf); i += 2)
+		mt352_write(fe, &reset_buf[i], 2);
+
+	msleep(1);
+
+	for (i = 0; i < ARRAY_SIZE(init_buf); i += 2)
+		mt352_write(fe, &init_buf[i], 2);
+
+	return 0;
+}
+
+static struct mt352_config digitv_mt352_config = {
+	.demod_init = digitv_mt352_demod_init,
+};
+
+static int digitv_nxt6000_tuner_set_params(struct dvb_frontend *fe)
+{
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+	u8 b[5];
+
+	fe->ops.tuner_ops.calc_regs(fe, b, sizeof(b));
+	if (fe->ops.i2c_gate_ctrl)
+		fe->ops.i2c_gate_ctrl(fe, 1);
+	return digitv_ctrl_msg(adap->dev, USB_WRITE_TUNER, 0, &b[1], 4, NULL, 0);
+}
+
+static struct nxt6000_config digitv_nxt6000_config = {
+	.clock_inversion = 1,
+};
+
+static int digitv_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct digitv_state *st = adap->dev->priv;
+
+	adap->fe_adap[0].fe = dvb_attach(mt352_attach, &digitv_mt352_config,
+					 &adap->dev->i2c_adap);
+	if ((adap->fe_adap[0].fe) != NULL) {
+		st->is_nxt6000 = 0;
+		return 0;
+	}
+	adap->fe_adap[0].fe = dvb_attach(nxt6000_attach,
+					 &digitv_nxt6000_config,
+					 &adap->dev->i2c_adap);
+	if ((adap->fe_adap[0].fe) != NULL) {
+		st->is_nxt6000 = 1;
+		return 0;
+	}
+	return -EIO;
+}
+
+static int digitv_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	struct digitv_state *st = adap->dev->priv;
+
+	if (!dvb_attach(dvb_pll_attach, adap->fe_adap[0].fe, 0x60, NULL, DVB_PLL_TDED4))
+		return -ENODEV;
+
+	if (st->is_nxt6000)
+		adap->fe_adap[0].fe->ops.tuner_ops.set_params = digitv_nxt6000_tuner_set_params;
+
+	return 0;
+}
+
+static struct rc_map_table rc_map_digitv_table[] = {
+	{ 0x5f55, KEY_0 },
+	{ 0x6f55, KEY_1 },
+	{ 0x9f55, KEY_2 },
+	{ 0xaf55, KEY_3 },
+	{ 0x5f56, KEY_4 },
+	{ 0x6f56, KEY_5 },
+	{ 0x9f56, KEY_6 },
+	{ 0xaf56, KEY_7 },
+	{ 0x5f59, KEY_8 },
+	{ 0x6f59, KEY_9 },
+	{ 0x9f59, KEY_TV },
+	{ 0xaf59, KEY_AUX },
+	{ 0x5f5a, KEY_DVD },
+	{ 0x6f5a, KEY_POWER },
+	{ 0x9f5a, KEY_CAMERA },     /* labelled 'Picture' */
+	{ 0xaf5a, KEY_AUDIO },
+	{ 0x5f65, KEY_INFO },
+	{ 0x6f65, KEY_F13 },     /* 16:9 */
+	{ 0x9f65, KEY_F14 },     /* 14:9 */
+	{ 0xaf65, KEY_EPG },
+	{ 0x5f66, KEY_EXIT },
+	{ 0x6f66, KEY_MENU },
+	{ 0x9f66, KEY_UP },
+	{ 0xaf66, KEY_DOWN },
+	{ 0x5f69, KEY_LEFT },
+	{ 0x6f69, KEY_RIGHT },
+	{ 0x9f69, KEY_ENTER },
+	{ 0xaf69, KEY_CHANNELUP },
+	{ 0x5f6a, KEY_CHANNELDOWN },
+	{ 0x6f6a, KEY_VOLUMEUP },
+	{ 0x9f6a, KEY_VOLUMEDOWN },
+	{ 0xaf6a, KEY_RED },
+	{ 0x5f95, KEY_GREEN },
+	{ 0x6f95, KEY_YELLOW },
+	{ 0x9f95, KEY_BLUE },
+	{ 0xaf95, KEY_SUBTITLE },
+	{ 0x5f96, KEY_F15 },     /* AD */
+	{ 0x6f96, KEY_TEXT },
+	{ 0x9f96, KEY_MUTE },
+	{ 0xaf96, KEY_REWIND },
+	{ 0x5f99, KEY_STOP },
+	{ 0x6f99, KEY_PLAY },
+	{ 0x9f99, KEY_FASTFORWARD },
+	{ 0xaf99, KEY_F16 },     /* chapter */
+	{ 0x5f9a, KEY_PAUSE },
+	{ 0x6f9a, KEY_PLAY },
+	{ 0x9f9a, KEY_RECORD },
+	{ 0xaf9a, KEY_F17 },     /* picture in picture */
+	{ 0x5fa5, KEY_KPPLUS },  /* zoom in */
+	{ 0x6fa5, KEY_KPMINUS }, /* zoom out */
+	{ 0x9fa5, KEY_F18 },     /* capture */
+	{ 0xafa5, KEY_F19 },     /* web */
+	{ 0x5fa6, KEY_EMAIL },
+	{ 0x6fa6, KEY_PHONE },
+	{ 0x9fa6, KEY_PC },
+};
+
+static int digitv_rc_query(struct dvb_usb_device *d, u32 *event, int *state)
+{
+	int i;
+	u8 key[5];
+	u8 b[4] = { 0 };
+
+	*event = 0;
+	*state = REMOTE_NO_KEY_PRESSED;
+
+	digitv_ctrl_msg(d,USB_READ_REMOTE,0,NULL,0,&key[1],4);
+
+	/* Tell the device we've read the remote. Not sure how necessary
+	   this is, but the Nebula SDK does it. */
+	digitv_ctrl_msg(d,USB_WRITE_REMOTE,0,b,4,NULL,0);
+
+	/* if something is inside the buffer, simulate key press */
+	if (key[1] != 0)
+	{
+		  for (i = 0; i < d->props.rc.legacy.rc_map_size; i++) {
+			if (rc5_custom(&d->props.rc.legacy.rc_map_table[i]) == key[1] &&
+			    rc5_data(&d->props.rc.legacy.rc_map_table[i]) == key[2]) {
+				*event = d->props.rc.legacy.rc_map_table[i].keycode;
+				*state = REMOTE_KEY_PRESSED;
+				return 0;
+			}
+		}
+	}
+
+	if (key[0] != 0)
+		deb_rc("key: %*ph\n", 5, key);
+	return 0;
+}
+
+/* DVB USB Driver stuff */
+static struct dvb_usb_device_properties digitv_properties;
+
+static int digitv_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	struct dvb_usb_device *d;
+	int ret = dvb_usb_device_init(intf, &digitv_properties, THIS_MODULE, &d,
+				      adapter_nr);
+	if (ret == 0) {
+		u8 b[4] = { 0 };
+
+		if (d != NULL) { /* do that only when the firmware is loaded */
+			b[0] = 1;
+			digitv_ctrl_msg(d,USB_WRITE_REMOTE_TYPE,0,b,4,NULL,0);
+
+			b[0] = 0;
+			digitv_ctrl_msg(d,USB_WRITE_REMOTE,0,b,4,NULL,0);
+		}
+	}
+	return ret;
+}
+
+static struct usb_device_id digitv_table [] = {
+		{ USB_DEVICE(USB_VID_ANCHOR, USB_PID_NEBULA_DIGITV) },
+		{ }		/* Terminating entry */
+};
+MODULE_DEVICE_TABLE (usb, digitv_table);
+
+static struct dvb_usb_device_properties digitv_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl = CYPRESS_FX2,
+	.firmware = "dvb-usb-digitv-02.fw",
+
+	.size_of_priv = sizeof(struct digitv_state),
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.frontend_attach  = digitv_frontend_attach,
+			.tuner_attach     = digitv_tuner_attach,
+
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+				.count = 7,
+				.endpoint = 0x02,
+				.u = {
+					.bulk = {
+						.buffersize = 4096,
+					}
+				}
+			},
+		}},
+		}
+	},
+	.identify_state   = digitv_identify_state,
+
+	.rc.legacy = {
+		.rc_interval      = 1000,
+		.rc_map_table     = rc_map_digitv_table,
+		.rc_map_size      = ARRAY_SIZE(rc_map_digitv_table),
+		.rc_query         = digitv_rc_query,
+	},
+
+	.i2c_algo         = &digitv_i2c_algo,
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.num_device_descs = 1,
+	.devices = {
+		{   "Nebula Electronics uDigiTV DVB-T USB2.0)",
+			{ &digitv_table[0], NULL },
+			{ NULL },
+		},
+		{ NULL },
+	}
+};
+
+static struct usb_driver digitv_driver = {
+	.name		= "dvb_usb_digitv",
+	.probe		= digitv_probe,
+	.disconnect = dvb_usb_device_exit,
+	.id_table	= digitv_table,
+};
+
+module_usb_driver(digitv_driver);
+
+MODULE_AUTHOR("Patrick Boettcher <patrick.boettcher@posteo.de>");
+MODULE_DESCRIPTION("Driver for Nebula Electronics uDigiTV DVB-T USB2.0");
+MODULE_VERSION("1.0-alpha");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/digitv.h b/drivers/media/usb/dvb-usb/digitv.h
new file mode 100644
index 0000000..2af9fed
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/digitv.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _DVB_USB_DIGITV_H_
+#define _DVB_USB_DIGITV_H_
+
+#define DVB_USB_LOG_PREFIX "digitv"
+#include "dvb-usb.h"
+
+struct digitv_state {
+	int is_nxt6000;
+
+	unsigned char sndbuf[7];
+	unsigned char rcvbuf[7];
+};
+
+/* protocol (from usblogging and the SDK:
+ *
+ * Always 7 bytes bulk message(s) for controlling
+ *
+ * First byte describes the command. Reads are 2 consecutive transfer (as always).
+ *
+ * General structure:
+ *
+ * write or first message of a read:
+ * <cmdbyte> VV <len> B0 B1 B2 B3
+ *
+ * second message of a read
+ * <cmdbyte> VV <len> R0 R1 R2 R3
+ *
+ * whereas 0 < len <= 4
+ *
+ * I2C address is stored somewhere inside the device.
+ *
+ * 0x01 read from EEPROM
+ *  VV = offset; B* = 0; R* = value(s)
+ *
+ * 0x02 read register of the COFDM
+ *  VV = register; B* = 0; R* = value(s)
+ *
+ * 0x05 write register of the COFDM
+ *  VV = register; B* = value(s);
+ *
+ * 0x06 write to the tuner (only for NXT6000)
+ *  VV = 0; B* = PLL data; len = 4;
+ *
+ * 0x03 read remote control
+ *  VV = 0; B* = 0; len = 4; R* = key
+ *
+ * 0x07 write to the remote (don't know why one should this, resetting ?)
+ *  VV = 0; B* = key; len = 4;
+ *
+ * 0x08 write remote type
+ *  VV = 0; B[0] = 0x01, len = 4
+ *
+ * 0x09 write device init
+ *  TODO
+ */
+#define USB_READ_EEPROM         1
+
+#define USB_READ_COFDM          2
+#define USB_WRITE_COFDM         5
+
+#define USB_WRITE_TUNER         6
+
+#define USB_READ_REMOTE         3
+#define USB_WRITE_REMOTE        7
+#define USB_WRITE_REMOTE_TYPE   8
+
+#define USB_DEV_INIT            9
+
+#endif
diff --git a/drivers/media/usb/dvb-usb/dtt200u-fe.c b/drivers/media/usb/dvb-usb/dtt200u-fe.c
new file mode 100644
index 0000000..1ca3a51
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/dtt200u-fe.c
@@ -0,0 +1,260 @@
+/* Frontend part of the Linux driver for the WideView/ Yakumo/ Hama/
+ * Typhoon/ Yuan DVB-T USB2.0 receiver.
+ *
+ * Copyright (C) 2005 Patrick Boettcher <patrick.boettcher@posteo.de>
+ *
+ *	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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#include "dtt200u.h"
+
+struct dtt200u_fe_state {
+	struct dvb_usb_device *d;
+
+	enum fe_status stat;
+
+	struct dtv_frontend_properties fep;
+	struct dvb_frontend frontend;
+
+	unsigned char data[80];
+	struct mutex data_mutex;
+};
+
+static int dtt200u_fe_read_status(struct dvb_frontend *fe,
+				  enum fe_status *stat)
+{
+	struct dtt200u_fe_state *state = fe->demodulator_priv;
+	int ret;
+
+	mutex_lock(&state->data_mutex);
+	state->data[0] = GET_TUNE_STATUS;
+
+	ret = dvb_usb_generic_rw(state->d, state->data, 1, state->data, 3, 0);
+	if (ret < 0) {
+		*stat = 0;
+		mutex_unlock(&state->data_mutex);
+		return ret;
+	}
+
+	switch (state->data[0]) {
+		case 0x01:
+			*stat = FE_HAS_SIGNAL | FE_HAS_CARRIER |
+				FE_HAS_VITERBI | FE_HAS_SYNC | FE_HAS_LOCK;
+			break;
+		case 0x00: /* pending */
+			*stat = FE_TIMEDOUT; /* during set_frontend */
+			break;
+		default:
+		case 0x02: /* failed */
+			*stat = 0;
+			break;
+	}
+	mutex_unlock(&state->data_mutex);
+	return 0;
+}
+
+static int dtt200u_fe_read_ber(struct dvb_frontend* fe, u32 *ber)
+{
+	struct dtt200u_fe_state *state = fe->demodulator_priv;
+	int ret;
+
+	mutex_lock(&state->data_mutex);
+	state->data[0] = GET_VIT_ERR_CNT;
+
+	ret = dvb_usb_generic_rw(state->d, state->data, 1, state->data, 3, 0);
+	if (ret >= 0)
+		*ber = (state->data[0] << 16) | (state->data[1] << 8) | state->data[2];
+
+	mutex_unlock(&state->data_mutex);
+	return ret;
+}
+
+static int dtt200u_fe_read_unc_blocks(struct dvb_frontend* fe, u32 *unc)
+{
+	struct dtt200u_fe_state *state = fe->demodulator_priv;
+	int ret;
+
+	mutex_lock(&state->data_mutex);
+	state->data[0] = GET_RS_UNCOR_BLK_CNT;
+
+	ret = dvb_usb_generic_rw(state->d, state->data, 1, state->data, 2, 0);
+	if (ret >= 0)
+		*unc = (state->data[0] << 8) | state->data[1];
+
+	mutex_unlock(&state->data_mutex);
+	return ret;
+}
+
+static int dtt200u_fe_read_signal_strength(struct dvb_frontend* fe, u16 *strength)
+{
+	struct dtt200u_fe_state *state = fe->demodulator_priv;
+	int ret;
+
+	mutex_lock(&state->data_mutex);
+	state->data[0] = GET_AGC;
+
+	ret = dvb_usb_generic_rw(state->d, state->data, 1, state->data, 1, 0);
+	if (ret >= 0)
+		*strength = (state->data[0] << 8) | state->data[0];
+
+	mutex_unlock(&state->data_mutex);
+	return ret;
+}
+
+static int dtt200u_fe_read_snr(struct dvb_frontend* fe, u16 *snr)
+{
+	struct dtt200u_fe_state *state = fe->demodulator_priv;
+	int ret;
+
+	mutex_lock(&state->data_mutex);
+	state->data[0] = GET_SNR;
+
+	ret = dvb_usb_generic_rw(state->d, state->data, 1, state->data, 1, 0);
+	if (ret >= 0)
+		*snr = ~((state->data[0] << 8) | state->data[0]);
+
+	mutex_unlock(&state->data_mutex);
+	return ret;
+}
+
+static int dtt200u_fe_init(struct dvb_frontend* fe)
+{
+	struct dtt200u_fe_state *state = fe->demodulator_priv;
+	int ret;
+
+	mutex_lock(&state->data_mutex);
+	state->data[0] = SET_INIT;
+
+	ret = dvb_usb_generic_write(state->d, state->data, 1);
+	mutex_unlock(&state->data_mutex);
+
+	return ret;
+}
+
+static int dtt200u_fe_sleep(struct dvb_frontend* fe)
+{
+	return dtt200u_fe_init(fe);
+}
+
+static int dtt200u_fe_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings *tune)
+{
+	tune->min_delay_ms = 1500;
+	tune->step_size = 0;
+	tune->max_drift = 0;
+	return 0;
+}
+
+static int dtt200u_fe_set_frontend(struct dvb_frontend *fe)
+{
+	struct dtv_frontend_properties *fep = &fe->dtv_property_cache;
+	struct dtt200u_fe_state *state = fe->demodulator_priv;
+	int ret;
+	u16 freq = fep->frequency / 250000;
+
+	mutex_lock(&state->data_mutex);
+	state->data[0] = SET_BANDWIDTH;
+	switch (fep->bandwidth_hz) {
+	case 8000000:
+		state->data[1] = 8;
+		break;
+	case 7000000:
+		state->data[1] = 7;
+		break;
+	case 6000000:
+		state->data[1] = 6;
+		break;
+	default:
+		ret = -EINVAL;
+		goto ret;
+	}
+
+	ret = dvb_usb_generic_write(state->d, state->data, 2);
+	if (ret < 0)
+		goto ret;
+
+	state->data[0] = SET_RF_FREQ;
+	state->data[1] = freq & 0xff;
+	state->data[2] = (freq >> 8) & 0xff;
+	ret = dvb_usb_generic_write(state->d, state->data, 3);
+	if (ret < 0)
+		goto ret;
+
+ret:
+	mutex_unlock(&state->data_mutex);
+	return ret;
+}
+
+static int dtt200u_fe_get_frontend(struct dvb_frontend* fe,
+				   struct dtv_frontend_properties *fep)
+{
+	struct dtt200u_fe_state *state = fe->demodulator_priv;
+
+	memcpy(fep, &state->fep, sizeof(struct dtv_frontend_properties));
+	return 0;
+}
+
+static void dtt200u_fe_release(struct dvb_frontend* fe)
+{
+	struct dtt200u_fe_state *state = (struct dtt200u_fe_state*) fe->demodulator_priv;
+	kfree(state);
+}
+
+static const struct dvb_frontend_ops dtt200u_fe_ops;
+
+struct dvb_frontend* dtt200u_fe_attach(struct dvb_usb_device *d)
+{
+	struct dtt200u_fe_state* state = NULL;
+
+	/* allocate memory for the internal state */
+	state = kzalloc(sizeof(struct dtt200u_fe_state), GFP_KERNEL);
+	if (state == NULL)
+		goto error;
+
+	deb_info("attaching frontend dtt200u\n");
+
+	state->d = d;
+	mutex_init(&state->data_mutex);
+
+	memcpy(&state->frontend.ops,&dtt200u_fe_ops,sizeof(struct dvb_frontend_ops));
+	state->frontend.demodulator_priv = state;
+
+	return &state->frontend;
+error:
+	return NULL;
+}
+
+static const struct dvb_frontend_ops dtt200u_fe_ops = {
+	.delsys = { SYS_DVBT },
+	.info = {
+		.name			= "WideView USB DVB-T",
+		.frequency_min_hz	=  44250 * kHz,
+		.frequency_max_hz	= 867250 * kHz,
+		.frequency_stepsize_hz	=    250 * kHz,
+		.caps = FE_CAN_INVERSION_AUTO |
+				FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 |
+				FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO |
+				FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO |
+				FE_CAN_TRANSMISSION_MODE_AUTO |
+				FE_CAN_GUARD_INTERVAL_AUTO |
+				FE_CAN_RECOVER |
+				FE_CAN_HIERARCHY_AUTO,
+	},
+
+	.release = dtt200u_fe_release,
+
+	.init = dtt200u_fe_init,
+	.sleep = dtt200u_fe_sleep,
+
+	.set_frontend = dtt200u_fe_set_frontend,
+	.get_frontend = dtt200u_fe_get_frontend,
+	.get_tune_settings = dtt200u_fe_get_tune_settings,
+
+	.read_status = dtt200u_fe_read_status,
+	.read_ber = dtt200u_fe_read_ber,
+	.read_signal_strength = dtt200u_fe_read_signal_strength,
+	.read_snr = dtt200u_fe_read_snr,
+	.read_ucblocks = dtt200u_fe_read_unc_blocks,
+};
diff --git a/drivers/media/usb/dvb-usb/dtt200u.c b/drivers/media/usb/dvb-usb/dtt200u.c
new file mode 100644
index 0000000..f03d269
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/dtt200u.c
@@ -0,0 +1,421 @@
+/* DVB USB library compliant Linux driver for the WideView/ Yakumo/ Hama/
+ * Typhoon/ Yuan/ Miglia DVB-T USB2.0 receiver.
+ *
+ * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@posteo.de)
+ *
+ * Thanks to Steve Chang from WideView for providing support for the WT-220U.
+ *
+ *	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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#include "dtt200u.h"
+
+/* debug */
+int dvb_usb_dtt200u_debug;
+module_param_named(debug,dvb_usb_dtt200u_debug, int, 0644);
+MODULE_PARM_DESC(debug, "set debugging level (1=info,xfer=2 (or-able))." DVB_USB_DEBUG_STATUS);
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+struct dtt200u_state {
+	unsigned char data[80];
+};
+
+static int dtt200u_power_ctrl(struct dvb_usb_device *d, int onoff)
+{
+	struct dtt200u_state *st = d->priv;
+	int ret = 0;
+
+	mutex_lock(&d->data_mutex);
+
+	st->data[0] = SET_INIT;
+
+	if (onoff)
+		ret = dvb_usb_generic_write(d, st->data, 2);
+
+	mutex_unlock(&d->data_mutex);
+	return ret;
+}
+
+static int dtt200u_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff)
+{
+	struct dvb_usb_device *d = adap->dev;
+	struct dtt200u_state *st = d->priv;
+	int ret;
+
+	mutex_lock(&d->data_mutex);
+	st->data[0] = SET_STREAMING;
+	st->data[1] = onoff;
+
+	ret = dvb_usb_generic_write(adap->dev, st->data, 2);
+	if (ret < 0)
+		goto ret;
+
+	if (onoff)
+		goto ret;
+
+	st->data[0] = RESET_PID_FILTER;
+	ret = dvb_usb_generic_write(adap->dev, st->data, 1);
+
+ret:
+	mutex_unlock(&d->data_mutex);
+
+	return ret;
+}
+
+static int dtt200u_pid_filter(struct dvb_usb_adapter *adap, int index, u16 pid, int onoff)
+{
+	struct dvb_usb_device *d = adap->dev;
+	struct dtt200u_state *st = d->priv;
+	int ret;
+
+	pid = onoff ? pid : 0;
+
+	mutex_lock(&d->data_mutex);
+	st->data[0] = SET_PID_FILTER;
+	st->data[1] = index;
+	st->data[2] = pid & 0xff;
+	st->data[3] = (pid >> 8) & 0x1f;
+
+	ret = dvb_usb_generic_write(adap->dev, st->data, 4);
+	mutex_unlock(&d->data_mutex);
+
+	return ret;
+}
+
+static int dtt200u_rc_query(struct dvb_usb_device *d)
+{
+	struct dtt200u_state *st = d->priv;
+	u32 scancode;
+	int ret;
+
+	mutex_lock(&d->data_mutex);
+	st->data[0] = GET_RC_CODE;
+
+	ret = dvb_usb_generic_rw(d, st->data, 1, st->data, 5, 0);
+	if (ret < 0)
+		goto ret;
+
+	if (st->data[0] == 1) {
+		enum rc_proto proto = RC_PROTO_NEC;
+
+		scancode = st->data[1];
+		if ((u8) ~st->data[1] != st->data[2]) {
+			/* Extended NEC */
+			scancode = scancode << 8;
+			scancode |= st->data[2];
+			proto = RC_PROTO_NECX;
+		}
+		scancode = scancode << 8;
+		scancode |= st->data[3];
+
+		/* Check command checksum is ok */
+		if ((u8) ~st->data[3] == st->data[4])
+			rc_keydown(d->rc_dev, proto, scancode, 0);
+		else
+			rc_keyup(d->rc_dev);
+	} else if (st->data[0] == 2) {
+		rc_repeat(d->rc_dev);
+	} else {
+		rc_keyup(d->rc_dev);
+	}
+
+	if (st->data[0] != 0)
+		deb_info("st->data: %*ph\n", 5, st->data);
+
+ret:
+	mutex_unlock(&d->data_mutex);
+	return ret;
+}
+
+static int dtt200u_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	adap->fe_adap[0].fe = dtt200u_fe_attach(adap->dev);
+	return 0;
+}
+
+static struct dvb_usb_device_properties dtt200u_properties;
+static struct dvb_usb_device_properties wt220u_fc_properties;
+static struct dvb_usb_device_properties wt220u_properties;
+static struct dvb_usb_device_properties wt220u_zl0353_properties;
+static struct dvb_usb_device_properties wt220u_miglia_properties;
+
+static int dtt200u_usb_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	if (0 == dvb_usb_device_init(intf, &dtt200u_properties,
+				     THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, &wt220u_properties,
+				     THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, &wt220u_fc_properties,
+				     THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, &wt220u_zl0353_properties,
+				     THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, &wt220u_miglia_properties,
+				     THIS_MODULE, NULL, adapter_nr))
+		return 0;
+
+	return -ENODEV;
+}
+
+static struct usb_device_id dtt200u_usb_table [] = {
+	{ USB_DEVICE(USB_VID_WIDEVIEW, USB_PID_DTT200U_COLD) },
+	{ USB_DEVICE(USB_VID_WIDEVIEW, USB_PID_DTT200U_WARM) },
+	{ USB_DEVICE(USB_VID_WIDEVIEW, USB_PID_WT220U_COLD)  },
+	{ USB_DEVICE(USB_VID_WIDEVIEW, USB_PID_WT220U_WARM)  },
+	{ USB_DEVICE(USB_VID_WIDEVIEW, USB_PID_WT220U_ZL0353_COLD)  },
+	{ USB_DEVICE(USB_VID_WIDEVIEW, USB_PID_WT220U_ZL0353_WARM)  },
+	{ USB_DEVICE(USB_VID_WIDEVIEW, USB_PID_WT220U_FC_COLD)  },
+	{ USB_DEVICE(USB_VID_WIDEVIEW, USB_PID_WT220U_FC_WARM)  },
+	{ USB_DEVICE(USB_VID_WIDEVIEW, USB_PID_WT220U_ZAP250_COLD)  },
+	{ USB_DEVICE(USB_VID_MIGLIA, USB_PID_WT220U_ZAP250_COLD)  },
+	{ 0 },
+};
+MODULE_DEVICE_TABLE(usb, dtt200u_usb_table);
+
+static struct dvb_usb_device_properties dtt200u_properties = {
+	.usb_ctrl = CYPRESS_FX2,
+	.firmware = "dvb-usb-dtt200u-01.fw",
+
+	.size_of_priv     = sizeof(struct dtt200u_state),
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.caps = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_NEED_PID_FILTERING,
+			.pid_filter_count = 15,
+
+	.streaming_ctrl  = dtt200u_streaming_ctrl,
+	.pid_filter      = dtt200u_pid_filter,
+	.frontend_attach = dtt200u_frontend_attach,
+	/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+		.count = 7,
+		.endpoint = 0x02,
+		.u = {
+			.bulk = {
+				.buffersize = 4096,
+			}
+		}
+	},
+		}},
+		}
+	},
+	.power_ctrl      = dtt200u_power_ctrl,
+
+	.rc.core = {
+		.rc_interval     = 300,
+		.rc_codes        = RC_MAP_DTT200U,
+		.rc_query        = dtt200u_rc_query,
+		.allowed_protos  = RC_PROTO_BIT_NEC,
+	},
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.num_device_descs = 1,
+	.devices = {
+		{ .name = "WideView/Yuan/Yakumo/Hama/Typhoon DVB-T USB2.0 (WT-200U)",
+		  .cold_ids = { &dtt200u_usb_table[0], NULL },
+		  .warm_ids = { &dtt200u_usb_table[1], NULL },
+		},
+		{ NULL },
+	}
+};
+
+static struct dvb_usb_device_properties wt220u_properties = {
+	.usb_ctrl = CYPRESS_FX2,
+	.firmware = "dvb-usb-wt220u-02.fw",
+
+	.size_of_priv     = sizeof(struct dtt200u_state),
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.caps = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_NEED_PID_FILTERING,
+			.pid_filter_count = 15,
+
+	.streaming_ctrl  = dtt200u_streaming_ctrl,
+	.pid_filter      = dtt200u_pid_filter,
+	.frontend_attach = dtt200u_frontend_attach,
+	/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+		.count = 7,
+		.endpoint = 0x02,
+		.u = {
+			.bulk = {
+				.buffersize = 4096,
+			}
+		}
+	},
+		}},
+		}
+	},
+	.power_ctrl      = dtt200u_power_ctrl,
+
+	.rc.core = {
+		.rc_interval     = 300,
+		.rc_codes        = RC_MAP_DTT200U,
+		.rc_query        = dtt200u_rc_query,
+		.allowed_protos  = RC_PROTO_BIT_NEC,
+	},
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.num_device_descs = 1,
+	.devices = {
+		{ .name = "WideView WT-220U PenType Receiver (Typhoon/Freecom)",
+		  .cold_ids = { &dtt200u_usb_table[2], &dtt200u_usb_table[8], NULL },
+		  .warm_ids = { &dtt200u_usb_table[3], NULL },
+		},
+		{ NULL },
+	}
+};
+
+static struct dvb_usb_device_properties wt220u_fc_properties = {
+	.usb_ctrl = CYPRESS_FX2,
+	.firmware = "dvb-usb-wt220u-fc03.fw",
+
+	.size_of_priv     = sizeof(struct dtt200u_state),
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.caps = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_NEED_PID_FILTERING,
+			.pid_filter_count = 15,
+
+	.streaming_ctrl  = dtt200u_streaming_ctrl,
+	.pid_filter      = dtt200u_pid_filter,
+	.frontend_attach = dtt200u_frontend_attach,
+	/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+		.count = 7,
+				.endpoint = 0x06,
+		.u = {
+			.bulk = {
+				.buffersize = 4096,
+			}
+		}
+	},
+		}},
+		}
+	},
+	.power_ctrl      = dtt200u_power_ctrl,
+
+	.rc.core = {
+		.rc_interval     = 300,
+		.rc_codes        = RC_MAP_DTT200U,
+		.rc_query        = dtt200u_rc_query,
+		.allowed_protos  = RC_PROTO_BIT_NEC,
+	},
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.num_device_descs = 1,
+	.devices = {
+		{ .name = "WideView WT-220U PenType Receiver (Typhoon/Freecom)",
+		  .cold_ids = { &dtt200u_usb_table[6], NULL },
+		  .warm_ids = { &dtt200u_usb_table[7], NULL },
+		},
+		{ NULL },
+	}
+};
+
+static struct dvb_usb_device_properties wt220u_zl0353_properties = {
+	.usb_ctrl = CYPRESS_FX2,
+	.firmware = "dvb-usb-wt220u-zl0353-01.fw",
+
+	.size_of_priv     = sizeof(struct dtt200u_state),
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.caps = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_NEED_PID_FILTERING,
+			.pid_filter_count = 15,
+
+			.streaming_ctrl  = dtt200u_streaming_ctrl,
+			.pid_filter      = dtt200u_pid_filter,
+			.frontend_attach = dtt200u_frontend_attach,
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+				.count = 7,
+				.endpoint = 0x02,
+				.u = {
+					.bulk = {
+						.buffersize = 4096,
+					}
+				}
+			},
+		}},
+		}
+	},
+	.power_ctrl      = dtt200u_power_ctrl,
+
+	.rc.core = {
+		.rc_interval     = 300,
+		.rc_codes        = RC_MAP_DTT200U,
+		.rc_query        = dtt200u_rc_query,
+		.allowed_protos  = RC_PROTO_BIT_NEC,
+	},
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.num_device_descs = 1,
+	.devices = {
+		{ .name = "WideView WT-220U PenType Receiver (based on ZL353)",
+		  .cold_ids = { &dtt200u_usb_table[4], NULL },
+		  .warm_ids = { &dtt200u_usb_table[5], NULL },
+		},
+		{ NULL },
+	}
+};
+
+static struct dvb_usb_device_properties wt220u_miglia_properties = {
+	.usb_ctrl = CYPRESS_FX2,
+	.firmware = "dvb-usb-wt220u-miglia-01.fw",
+
+	.size_of_priv     = sizeof(struct dtt200u_state),
+
+	.num_adapters = 1,
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.num_device_descs = 1,
+	.devices = {
+		{ .name = "WideView WT-220U PenType Receiver (Miglia)",
+		  .cold_ids = { &dtt200u_usb_table[9], NULL },
+		  /* This device turns into WT220U_ZL0353_WARM when fw
+		     has been uploaded */
+		  .warm_ids = { NULL },
+		},
+		{ NULL },
+	}
+};
+
+/* usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver dtt200u_usb_driver = {
+	.name		= "dvb_usb_dtt200u",
+	.probe		= dtt200u_usb_probe,
+	.disconnect = dvb_usb_device_exit,
+	.id_table	= dtt200u_usb_table,
+};
+
+module_usb_driver(dtt200u_usb_driver);
+
+MODULE_AUTHOR("Patrick Boettcher <patrick.boettcher@posteo.de>");
+MODULE_DESCRIPTION("Driver for the WideView/Yakumo/Hama/Typhoon/Club3D/Miglia DVB-T USB2.0 devices");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/dtt200u.h b/drivers/media/usb/dvb-usb/dtt200u.h
new file mode 100644
index 0000000..ea2a096
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/dtt200u.h
@@ -0,0 +1,57 @@
+/* Common header file of Linux driver for the WideView/ Yakumo/ Hama/
+ * Typhoon/ Yuan DVB-T USB2.0 receiver.
+ *
+ * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@posteo.de)
+ *
+ *	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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#ifndef _DVB_USB_DTT200U_H_
+#define _DVB_USB_DTT200U_H_
+
+#define DVB_USB_LOG_PREFIX "dtt200u"
+
+#include "dvb-usb.h"
+
+extern int dvb_usb_dtt200u_debug;
+#define deb_info(args...) dprintk(dvb_usb_dtt200u_debug,0x01,args)
+#define deb_xfer(args...) dprintk(dvb_usb_dtt200u_debug,0x02,args)
+
+/* guessed protocol description (reverse engineered):
+ * read
+ *  00 - USB type 0x02 for usb2.0, 0x01 for usb1.1
+ *  88 - locking 2 bytes (0x80 0x40 == no signal, 0x89 0x20 == nice signal)
+ */
+
+#define GET_SPEED		0x00
+#define GET_TUNE_STATUS		0x81
+#define GET_RC_CODE		0x84
+#define GET_CONFIGURATION	0x88
+#define GET_AGC			0x89
+#define GET_SNR			0x8a
+#define GET_VIT_ERR_CNT		0x8c
+#define GET_RS_ERR_CNT		0x8d
+#define GET_RS_UNCOR_BLK_CNT	0x8e
+
+/* write
+ *  01 - init
+ *  02 - frequency (divided by 250000)
+ *  03 - bandwidth
+ *  04 - pid table (index pid(7:0) pid(12:8))
+ *  05 - reset the pid table
+ *  08 - transfer switch
+ */
+
+#define SET_INIT		0x01
+#define SET_RF_FREQ		0x02
+#define SET_BANDWIDTH		0x03
+#define SET_PID_FILTER		0x04
+#define RESET_PID_FILTER	0x05
+#define SET_STREAMING		0x08
+
+extern struct dvb_frontend * dtt200u_fe_attach(struct dvb_usb_device *d);
+
+#endif
diff --git a/drivers/media/usb/dvb-usb/dtv5100.c b/drivers/media/usb/dvb-usb/dtv5100.c
new file mode 100644
index 0000000..2fa2abd
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/dtv5100.c
@@ -0,0 +1,226 @@
+/*
+ * DVB USB Linux driver for AME DTV-5100 USB2.0 DVB-T
+ *
+ * Copyright (C) 2008  Antoine Jacquet <royale@zerezo.com>
+ * http://royale.zerezo.com/dtv5100/
+ *
+ * Inspired by gl861.c and au6610.c drivers
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "dtv5100.h"
+#include "zl10353.h"
+#include "qt1010.h"
+
+/* debug */
+static int dvb_usb_dtv5100_debug;
+module_param_named(debug, dvb_usb_dtv5100_debug, int, 0644);
+MODULE_PARM_DESC(debug, "set debugging level" DVB_USB_DEBUG_STATUS);
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+struct dtv5100_state {
+	unsigned char data[80];
+};
+
+static int dtv5100_i2c_msg(struct dvb_usb_device *d, u8 addr,
+			   u8 *wbuf, u16 wlen, u8 *rbuf, u16 rlen)
+{
+	struct dtv5100_state *st = d->priv;
+	u8 request;
+	u8 type;
+	u16 value;
+	u16 index;
+
+	switch (wlen) {
+	case 1:
+		/* write { reg }, read { value } */
+		request = (addr == DTV5100_DEMOD_ADDR ? DTV5100_DEMOD_READ :
+							DTV5100_TUNER_READ);
+		type = USB_TYPE_VENDOR | USB_DIR_IN;
+		value = 0;
+		break;
+	case 2:
+		/* write { reg, value } */
+		request = (addr == DTV5100_DEMOD_ADDR ? DTV5100_DEMOD_WRITE :
+							DTV5100_TUNER_WRITE);
+		type = USB_TYPE_VENDOR | USB_DIR_OUT;
+		value = wbuf[1];
+		break;
+	default:
+		warn("wlen = %x, aborting.", wlen);
+		return -EINVAL;
+	}
+	index = (addr << 8) + wbuf[0];
+
+	memcpy(st->data, rbuf, rlen);
+	msleep(1); /* avoid I2C errors */
+	return usb_control_msg(d->udev, usb_rcvctrlpipe(d->udev, 0), request,
+			       type, value, index, st->data, rlen,
+			       DTV5100_USB_TIMEOUT);
+}
+
+/* I2C */
+static int dtv5100_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[],
+			    int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	int i;
+
+	if (num > 2)
+		return -EINVAL;
+
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	for (i = 0; i < num; i++) {
+		/* write/read request */
+		if (i+1 < num && (msg[i+1].flags & I2C_M_RD)) {
+			if (dtv5100_i2c_msg(d, msg[i].addr, msg[i].buf,
+					    msg[i].len, msg[i+1].buf,
+					    msg[i+1].len) < 0)
+				break;
+			i++;
+		} else if (dtv5100_i2c_msg(d, msg[i].addr, msg[i].buf,
+					   msg[i].len, NULL, 0) < 0)
+				break;
+	}
+
+	mutex_unlock(&d->i2c_mutex);
+	return i;
+}
+
+static u32 dtv5100_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm dtv5100_i2c_algo = {
+	.master_xfer   = dtv5100_i2c_xfer,
+	.functionality = dtv5100_i2c_func,
+};
+
+/* Callbacks for DVB USB */
+static struct zl10353_config dtv5100_zl10353_config = {
+	.demod_address = DTV5100_DEMOD_ADDR,
+	.no_tuner = 1,
+	.parallel_ts = 1,
+};
+
+static int dtv5100_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	adap->fe_adap[0].fe = dvb_attach(zl10353_attach, &dtv5100_zl10353_config,
+			      &adap->dev->i2c_adap);
+	if (adap->fe_adap[0].fe == NULL)
+		return -EIO;
+
+	/* disable i2c gate, or it won't work... is this safe? */
+	adap->fe_adap[0].fe->ops.i2c_gate_ctrl = NULL;
+
+	return 0;
+}
+
+static struct qt1010_config dtv5100_qt1010_config = {
+	.i2c_address = DTV5100_TUNER_ADDR
+};
+
+static int dtv5100_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	return dvb_attach(qt1010_attach,
+			  adap->fe_adap[0].fe, &adap->dev->i2c_adap,
+			  &dtv5100_qt1010_config) == NULL ? -ENODEV : 0;
+}
+
+/* DVB USB Driver stuff */
+static struct dvb_usb_device_properties dtv5100_properties;
+
+static int dtv5100_probe(struct usb_interface *intf,
+			 const struct usb_device_id *id)
+{
+	int i, ret;
+	struct usb_device *udev = interface_to_usbdev(intf);
+
+	/* initialize non qt1010/zl10353 part? */
+	for (i = 0; dtv5100_init[i].request; i++) {
+		ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+				      dtv5100_init[i].request,
+				      USB_TYPE_VENDOR | USB_DIR_OUT,
+				      dtv5100_init[i].value,
+				      dtv5100_init[i].index, NULL, 0,
+				      DTV5100_USB_TIMEOUT);
+		if (ret)
+			return ret;
+	}
+
+	ret = dvb_usb_device_init(intf, &dtv5100_properties,
+				  THIS_MODULE, NULL, adapter_nr);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static struct usb_device_id dtv5100_table[] = {
+	{ USB_DEVICE(0x06be, 0xa232) },
+	{ }		/* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, dtv5100_table);
+
+static struct dvb_usb_device_properties dtv5100_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+	.usb_ctrl = DEVICE_SPECIFIC,
+
+	.size_of_priv = sizeof(struct dtv5100_state),
+
+	.num_adapters = 1,
+	.adapter = {{
+		.num_frontends = 1,
+		.fe = {{
+		.frontend_attach = dtv5100_frontend_attach,
+		.tuner_attach    = dtv5100_tuner_attach,
+
+		.stream = {
+			.type = USB_BULK,
+			.count = 8,
+			.endpoint = 0x82,
+			.u = {
+				.bulk = {
+					.buffersize = 4096,
+				}
+			}
+		},
+		}},
+	} },
+
+	.i2c_algo = &dtv5100_i2c_algo,
+
+	.num_device_descs = 1,
+	.devices = {
+		{
+			.name = "AME DTV-5100 USB2.0 DVB-T",
+			.cold_ids = { NULL },
+			.warm_ids = { &dtv5100_table[0], NULL },
+		},
+	}
+};
+
+static struct usb_driver dtv5100_driver = {
+	.name		= "dvb_usb_dtv5100",
+	.probe		= dtv5100_probe,
+	.disconnect	= dvb_usb_device_exit,
+	.id_table	= dtv5100_table,
+};
+
+module_usb_driver(dtv5100_driver);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/dtv5100.h b/drivers/media/usb/dvb-usb/dtv5100.h
new file mode 100644
index 0000000..1ab1eaf
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/dtv5100.h
@@ -0,0 +1,47 @@
+/*
+ * DVB USB Linux driver for AME DTV-5100 USB2.0 DVB-T
+ *
+ * Copyright (C) 2008  Antoine Jacquet <royale@zerezo.com>
+ * http://royale.zerezo.com/dtv5100/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _DVB_USB_DTV5100_H_
+#define _DVB_USB_DTV5100_H_
+
+#define DVB_USB_LOG_PREFIX "dtv5100"
+#include "dvb-usb.h"
+
+#define DTV5100_USB_TIMEOUT 500
+
+#define DTV5100_DEMOD_ADDR	0x00
+#define DTV5100_DEMOD_WRITE	0xc0
+#define DTV5100_DEMOD_READ	0xc1
+
+#define DTV5100_TUNER_ADDR	0xc4
+#define DTV5100_TUNER_WRITE	0xc7
+#define DTV5100_TUNER_READ	0xc8
+
+#define DRIVER_AUTHOR "Antoine Jacquet, http://royale.zerezo.com/"
+#define DRIVER_DESC "AME DTV-5100 USB2.0 DVB-T"
+
+static struct {
+	u8 request;
+	u8 value;
+	u16 index;
+} dtv5100_init[] = {
+	{ 0x000000c5, 0x00000000, 0x00000001 },
+	{ 0x000000c5, 0x00000001, 0x00000001 },
+	{ }		/* Terminating entry */
+};
+
+#endif
diff --git a/drivers/media/usb/dvb-usb/dvb-usb-common.h b/drivers/media/usb/dvb-usb/dvb-usb-common.h
new file mode 100644
index 0000000..8c51ac4
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/dvb-usb-common.h
@@ -0,0 +1,53 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* dvb-usb-common.h is part of the DVB USB library.
+ *
+ * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@posteo.de)
+ * see dvb-usb-init.c for copyright information.
+ *
+ * a header file containing prototypes and types for internal use of the dvb-usb-lib
+ */
+#ifndef _DVB_USB_COMMON_H_
+#define _DVB_USB_COMMON_H_
+
+#define DVB_USB_LOG_PREFIX "dvb-usb"
+#include "dvb-usb.h"
+
+extern int dvb_usb_debug;
+extern int dvb_usb_disable_rc_polling;
+
+#define deb_info(args...)  dprintk(dvb_usb_debug,0x001,args)
+#define deb_xfer(args...)  dprintk(dvb_usb_debug,0x002,args)
+#define deb_pll(args...)   dprintk(dvb_usb_debug,0x004,args)
+#define deb_ts(args...)    dprintk(dvb_usb_debug,0x008,args)
+#define deb_err(args...)   dprintk(dvb_usb_debug,0x010,args)
+#define deb_rc(args...)    dprintk(dvb_usb_debug,0x020,args)
+#define deb_fw(args...)    dprintk(dvb_usb_debug,0x040,args)
+#define deb_mem(args...)   dprintk(dvb_usb_debug,0x080,args)
+#define deb_uxfer(args...) dprintk(dvb_usb_debug,0x100,args)
+
+/* commonly used  methods */
+extern int dvb_usb_download_firmware(struct usb_device *, struct dvb_usb_device_properties *);
+
+extern int dvb_usb_device_power_ctrl(struct dvb_usb_device *d, int onoff);
+
+extern int usb_urb_init(struct usb_data_stream *stream, struct usb_data_stream_properties *props);
+extern int usb_urb_exit(struct usb_data_stream *stream);
+extern int usb_urb_submit(struct usb_data_stream *stream);
+extern int usb_urb_kill(struct usb_data_stream *stream);
+
+extern int dvb_usb_adapter_stream_init(struct dvb_usb_adapter *adap);
+extern int dvb_usb_adapter_stream_exit(struct dvb_usb_adapter *adap);
+
+extern int dvb_usb_i2c_init(struct dvb_usb_device *);
+extern int dvb_usb_i2c_exit(struct dvb_usb_device *);
+
+extern int dvb_usb_adapter_dvb_init(struct dvb_usb_adapter *adap,
+				    short *adapter_nums);
+extern int dvb_usb_adapter_dvb_exit(struct dvb_usb_adapter *adap);
+extern int dvb_usb_adapter_frontend_init(struct dvb_usb_adapter *adap);
+extern int dvb_usb_adapter_frontend_exit(struct dvb_usb_adapter *adap);
+
+extern int dvb_usb_remote_init(struct dvb_usb_device *);
+extern int dvb_usb_remote_exit(struct dvb_usb_device *);
+
+#endif
diff --git a/drivers/media/usb/dvb-usb/dvb-usb-dvb.c b/drivers/media/usb/dvb-usb/dvb-usb-dvb.c
new file mode 100644
index 0000000..8056053
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/dvb-usb-dvb.c
@@ -0,0 +1,351 @@
+// SPDX-License-Identifier: GPL-2.0
+/* dvb-usb-dvb.c is part of the DVB USB library.
+ *
+ * Copyright (C) 2004-6 Patrick Boettcher (patrick.boettcher@posteo.de)
+ * see dvb-usb-init.c for copyright information.
+ *
+ * This file contains functions for initializing and handling the
+ * linux-dvb API.
+ */
+#include "dvb-usb-common.h"
+#include <media/media-device.h>
+
+/* does the complete input transfer handling */
+static int dvb_usb_ctrl_feed(struct dvb_demux_feed *dvbdmxfeed, int onoff)
+{
+	struct dvb_usb_adapter *adap = dvbdmxfeed->demux->priv;
+	int newfeedcount, ret;
+
+	if (adap == NULL)
+		return -ENODEV;
+
+	if ((adap->active_fe < 0) ||
+	    (adap->active_fe >= adap->num_frontends_initialized)) {
+		return -EINVAL;
+	}
+
+	newfeedcount = adap->feedcount + (onoff ? 1 : -1);
+
+	/* stop feed before setting a new pid if there will be no pid anymore */
+	if (newfeedcount == 0) {
+		deb_ts("stop feeding\n");
+		usb_urb_kill(&adap->fe_adap[adap->active_fe].stream);
+
+		if (adap->props.fe[adap->active_fe].streaming_ctrl != NULL) {
+			ret = adap->props.fe[adap->active_fe].streaming_ctrl(adap, 0);
+			if (ret < 0) {
+				err("error while stopping stream.");
+				return ret;
+			}
+		}
+	}
+
+	adap->feedcount = newfeedcount;
+
+	/* activate the pid on the device specific pid_filter */
+	deb_ts("setting pid (%s): %5d %04x at index %d '%s'\n",
+		adap->fe_adap[adap->active_fe].pid_filtering ?
+		"yes" : "no", dvbdmxfeed->pid, dvbdmxfeed->pid,
+		dvbdmxfeed->index, onoff ? "on" : "off");
+	if (adap->props.fe[adap->active_fe].caps & DVB_USB_ADAP_HAS_PID_FILTER &&
+		adap->fe_adap[adap->active_fe].pid_filtering &&
+		adap->props.fe[adap->active_fe].pid_filter != NULL)
+		adap->props.fe[adap->active_fe].pid_filter(adap, dvbdmxfeed->index, dvbdmxfeed->pid, onoff);
+
+	/* start the feed if this was the first feed and there is still a feed
+	 * for reception.
+	 */
+	if (adap->feedcount == onoff && adap->feedcount > 0) {
+		deb_ts("submitting all URBs\n");
+		usb_urb_submit(&adap->fe_adap[adap->active_fe].stream);
+
+		deb_ts("controlling pid parser\n");
+		if (adap->props.fe[adap->active_fe].caps & DVB_USB_ADAP_HAS_PID_FILTER &&
+			adap->props.fe[adap->active_fe].caps &
+			DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF &&
+			adap->props.fe[adap->active_fe].pid_filter_ctrl != NULL) {
+			ret = adap->props.fe[adap->active_fe].pid_filter_ctrl(adap,
+				adap->fe_adap[adap->active_fe].pid_filtering);
+			if (ret < 0) {
+				err("could not handle pid_parser");
+				return ret;
+			}
+		}
+		deb_ts("start feeding\n");
+		if (adap->props.fe[adap->active_fe].streaming_ctrl != NULL) {
+			ret = adap->props.fe[adap->active_fe].streaming_ctrl(adap, 1);
+			if (ret < 0) {
+				err("error while enabling fifo.");
+				return ret;
+			}
+		}
+
+	}
+	return 0;
+}
+
+static int dvb_usb_start_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+	deb_ts("start pid: 0x%04x, feedtype: %d\n", dvbdmxfeed->pid,
+	       dvbdmxfeed->type);
+	return dvb_usb_ctrl_feed(dvbdmxfeed, 1);
+}
+
+static int dvb_usb_stop_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+	deb_ts("stop pid: 0x%04x, feedtype: %d\n", dvbdmxfeed->pid, dvbdmxfeed->type);
+	return dvb_usb_ctrl_feed(dvbdmxfeed, 0);
+}
+
+static int dvb_usb_media_device_init(struct dvb_usb_adapter *adap)
+{
+#ifdef CONFIG_MEDIA_CONTROLLER_DVB
+	struct media_device *mdev;
+	struct dvb_usb_device *d = adap->dev;
+	struct usb_device *udev = d->udev;
+
+	mdev = kzalloc(sizeof(*mdev), GFP_KERNEL);
+	if (!mdev)
+		return -ENOMEM;
+
+	media_device_usb_init(mdev, udev, d->desc->name);
+
+	dvb_register_media_controller(&adap->dvb_adap, mdev);
+
+	dev_info(&d->udev->dev, "media controller created\n");
+#endif
+	return 0;
+}
+
+static int  dvb_usb_media_device_register(struct dvb_usb_adapter *adap)
+{
+#ifdef CONFIG_MEDIA_CONTROLLER_DVB
+	return media_device_register(adap->dvb_adap.mdev);
+#else
+	return 0;
+#endif
+}
+
+static void dvb_usb_media_device_unregister(struct dvb_usb_adapter *adap)
+{
+#ifdef CONFIG_MEDIA_CONTROLLER_DVB
+	if (!adap->dvb_adap.mdev)
+		return;
+
+	mutex_lock(&adap->dvb_adap.mdev_lock);
+
+	media_device_unregister(adap->dvb_adap.mdev);
+	media_device_cleanup(adap->dvb_adap.mdev);
+	kfree(adap->dvb_adap.mdev);
+	adap->dvb_adap.mdev = NULL;
+
+	mutex_unlock(&adap->dvb_adap.mdev_lock);
+#endif
+}
+
+int dvb_usb_adapter_dvb_init(struct dvb_usb_adapter *adap, short *adapter_nums)
+{
+	int i;
+	int ret = dvb_register_adapter(&adap->dvb_adap, adap->dev->desc->name,
+				       adap->dev->owner, &adap->dev->udev->dev,
+				       adapter_nums);
+
+	if (ret < 0) {
+		deb_info("dvb_register_adapter failed: error %d", ret);
+		goto err;
+	}
+	adap->dvb_adap.priv = adap;
+
+	ret = dvb_usb_media_device_init(adap);
+	if (ret < 0) {
+		deb_info("dvb_usb_media_device_init failed: error %d", ret);
+		goto err_mc;
+	}
+
+	if (adap->dev->props.read_mac_address) {
+		if (adap->dev->props.read_mac_address(adap->dev, adap->dvb_adap.proposed_mac) == 0)
+			info("MAC address: %pM", adap->dvb_adap.proposed_mac);
+		else
+			err("MAC address reading failed.");
+	}
+
+
+	adap->demux.dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING;
+	adap->demux.priv             = adap;
+
+	adap->demux.filternum        = 0;
+	for (i = 0; i < adap->props.num_frontends; i++) {
+		if (adap->demux.filternum < adap->fe_adap[i].max_feed_count)
+			adap->demux.filternum = adap->fe_adap[i].max_feed_count;
+	}
+	adap->demux.feednum          = adap->demux.filternum;
+	adap->demux.start_feed       = dvb_usb_start_feed;
+	adap->demux.stop_feed        = dvb_usb_stop_feed;
+	adap->demux.write_to_decoder = NULL;
+	if ((ret = dvb_dmx_init(&adap->demux)) < 0) {
+		err("dvb_dmx_init failed: error %d", ret);
+		goto err_dmx;
+	}
+
+	adap->dmxdev.filternum       = adap->demux.filternum;
+	adap->dmxdev.demux           = &adap->demux.dmx;
+	adap->dmxdev.capabilities    = 0;
+	if ((ret = dvb_dmxdev_init(&adap->dmxdev, &adap->dvb_adap)) < 0) {
+		err("dvb_dmxdev_init failed: error %d", ret);
+		goto err_dmx_dev;
+	}
+
+	if ((ret = dvb_net_init(&adap->dvb_adap, &adap->dvb_net,
+						&adap->demux.dmx)) < 0) {
+		err("dvb_net_init failed: error %d", ret);
+		goto err_net_init;
+	}
+
+	adap->state |= DVB_USB_ADAP_STATE_DVB;
+	return 0;
+
+err_net_init:
+	dvb_dmxdev_release(&adap->dmxdev);
+err_dmx_dev:
+	dvb_dmx_release(&adap->demux);
+err_dmx:
+	dvb_usb_media_device_unregister(adap);
+err_mc:
+	dvb_unregister_adapter(&adap->dvb_adap);
+err:
+	return ret;
+}
+
+int dvb_usb_adapter_dvb_exit(struct dvb_usb_adapter *adap)
+{
+	if (adap->state & DVB_USB_ADAP_STATE_DVB) {
+		deb_info("unregistering DVB part\n");
+		dvb_net_release(&adap->dvb_net);
+		adap->demux.dmx.close(&adap->demux.dmx);
+		dvb_dmxdev_release(&adap->dmxdev);
+		dvb_dmx_release(&adap->demux);
+		dvb_usb_media_device_unregister(adap);
+		dvb_unregister_adapter(&adap->dvb_adap);
+		adap->state &= ~DVB_USB_ADAP_STATE_DVB;
+	}
+	return 0;
+}
+
+static int dvb_usb_set_active_fe(struct dvb_frontend *fe, int onoff)
+{
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+
+	int ret = (adap->props.frontend_ctrl) ?
+		adap->props.frontend_ctrl(fe, onoff) : 0;
+
+	if (ret < 0) {
+		err("frontend_ctrl request failed");
+		return ret;
+	}
+	if (onoff)
+		adap->active_fe = fe->id;
+
+	return 0;
+}
+
+static int dvb_usb_fe_wakeup(struct dvb_frontend *fe)
+{
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+
+	dvb_usb_device_power_ctrl(adap->dev, 1);
+
+	dvb_usb_set_active_fe(fe, 1);
+
+	if (adap->fe_adap[fe->id].fe_init)
+		adap->fe_adap[fe->id].fe_init(fe);
+
+	return 0;
+}
+
+static int dvb_usb_fe_sleep(struct dvb_frontend *fe)
+{
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+
+	if (adap->fe_adap[fe->id].fe_sleep)
+		adap->fe_adap[fe->id].fe_sleep(fe);
+
+	dvb_usb_set_active_fe(fe, 0);
+
+	return dvb_usb_device_power_ctrl(adap->dev, 0);
+}
+
+int dvb_usb_adapter_frontend_init(struct dvb_usb_adapter *adap)
+{
+	int ret, i;
+
+	/* register all given adapter frontends */
+	for (i = 0; i < adap->props.num_frontends; i++) {
+
+		if (adap->props.fe[i].frontend_attach == NULL) {
+			err("strange: '%s' #%d,%d doesn't want to attach a frontend.",
+			    adap->dev->desc->name, adap->id, i);
+
+			return 0;
+		}
+
+		ret = adap->props.fe[i].frontend_attach(adap);
+		if (ret || adap->fe_adap[i].fe == NULL) {
+			/* only print error when there is no FE at all */
+			if (i == 0)
+				err("no frontend was attached by '%s'",
+					adap->dev->desc->name);
+
+			return 0;
+		}
+
+		adap->fe_adap[i].fe->id = i;
+
+		/* re-assign sleep and wakeup functions */
+		adap->fe_adap[i].fe_init = adap->fe_adap[i].fe->ops.init;
+		adap->fe_adap[i].fe->ops.init  = dvb_usb_fe_wakeup;
+		adap->fe_adap[i].fe_sleep = adap->fe_adap[i].fe->ops.sleep;
+		adap->fe_adap[i].fe->ops.sleep = dvb_usb_fe_sleep;
+
+		if (dvb_register_frontend(&adap->dvb_adap, adap->fe_adap[i].fe)) {
+			err("Frontend %d registration failed.", i);
+			dvb_frontend_detach(adap->fe_adap[i].fe);
+			adap->fe_adap[i].fe = NULL;
+			/* In error case, do not try register more FEs,
+			 * still leaving already registered FEs alive. */
+			if (i == 0)
+				return -ENODEV;
+			else
+				return 0;
+		}
+
+		/* only attach the tuner if the demod is there */
+		if (adap->props.fe[i].tuner_attach != NULL)
+			adap->props.fe[i].tuner_attach(adap);
+
+		adap->num_frontends_initialized++;
+	}
+
+	ret = dvb_create_media_graph(&adap->dvb_adap, true);
+	if (ret)
+		return ret;
+
+	ret = dvb_usb_media_device_register(adap);
+
+	return ret;
+}
+
+int dvb_usb_adapter_frontend_exit(struct dvb_usb_adapter *adap)
+{
+	int i = adap->num_frontends_initialized - 1;
+
+	/* unregister all given adapter frontends */
+	for (; i >= 0; i--) {
+		if (adap->fe_adap[i].fe != NULL) {
+			dvb_unregister_frontend(adap->fe_adap[i].fe);
+			dvb_frontend_detach(adap->fe_adap[i].fe);
+		}
+	}
+	adap->num_frontends_initialized = 0;
+
+	return 0;
+}
diff --git a/drivers/media/usb/dvb-usb/dvb-usb-firmware.c b/drivers/media/usb/dvb-usb/dvb-usb-firmware.c
new file mode 100644
index 0000000..42c207a
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/dvb-usb-firmware.c
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: GPL-2.0
+/* dvb-usb-firmware.c is part of the DVB USB library.
+ *
+ * Copyright (C) 2004-6 Patrick Boettcher (patrick.boettcher@posteo.de)
+ * see dvb-usb-init.c for copyright information.
+ *
+ * This file contains functions for downloading the firmware to Cypress FX 1 and 2 based devices.
+ *
+ * FIXME: This part does actually not belong to dvb-usb, but to the usb-subsystem.
+ */
+#include "dvb-usb-common.h"
+
+#include <linux/usb.h>
+
+struct usb_cypress_controller {
+	int id;
+	const char *name;       /* name of the usb controller */
+	u16 cpu_cs_register;    /* needs to be restarted, when the firmware has been downloaded. */
+};
+
+static struct usb_cypress_controller cypress[] = {
+	{ .id = DEVICE_SPECIFIC, .name = "Device specific", .cpu_cs_register = 0 },
+	{ .id = CYPRESS_AN2135,  .name = "Cypress AN2135",  .cpu_cs_register = 0x7f92 },
+	{ .id = CYPRESS_AN2235,  .name = "Cypress AN2235",  .cpu_cs_register = 0x7f92 },
+	{ .id = CYPRESS_FX2,     .name = "Cypress FX2",     .cpu_cs_register = 0xe600 },
+};
+
+/*
+ * load a firmware packet to the device
+ */
+static int usb_cypress_writemem(struct usb_device *udev,u16 addr,u8 *data, u8 len)
+{
+	return usb_control_msg(udev, usb_sndctrlpipe(udev,0),
+			0xa0, USB_TYPE_VENDOR, addr, 0x00, data, len, 5000);
+}
+
+int usb_cypress_load_firmware(struct usb_device *udev, const struct firmware *fw, int type)
+{
+	struct hexline *hx;
+	u8 *buf;
+	int ret, pos = 0;
+	u16 cpu_cs_register = cypress[type].cpu_cs_register;
+
+	buf = kmalloc(sizeof(*hx), GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+	hx = (struct hexline *)buf;
+
+	/* stop the CPU */
+	buf[0] = 1;
+	if (usb_cypress_writemem(udev, cpu_cs_register, buf, 1) != 1)
+		err("could not stop the USB controller CPU.");
+
+	while ((ret = dvb_usb_get_hexline(fw, hx, &pos)) > 0) {
+		deb_fw("writing to address 0x%04x (buffer: 0x%02x %02x)\n", hx->addr, hx->len, hx->chk);
+		ret = usb_cypress_writemem(udev, hx->addr, hx->data, hx->len);
+
+		if (ret != hx->len) {
+			err("error while transferring firmware (transferred size: %d, block size: %d)",
+				ret, hx->len);
+			ret = -EINVAL;
+			break;
+		}
+	}
+	if (ret < 0) {
+		err("firmware download failed at %d with %d",pos,ret);
+		kfree(buf);
+		return ret;
+	}
+
+	if (ret == 0) {
+		/* restart the CPU */
+		buf[0] = 0;
+		if (usb_cypress_writemem(udev, cpu_cs_register, buf, 1) != 1) {
+			err("could not restart the USB controller CPU.");
+			ret = -EINVAL;
+		}
+	} else
+		ret = -EIO;
+
+	kfree(buf);
+
+	return ret;
+}
+EXPORT_SYMBOL(usb_cypress_load_firmware);
+
+int dvb_usb_download_firmware(struct usb_device *udev, struct dvb_usb_device_properties *props)
+{
+	int ret;
+	const struct firmware *fw = NULL;
+
+	if ((ret = request_firmware(&fw, props->firmware, &udev->dev)) != 0) {
+		err("did not find the firmware file '%s' (status %d). You can use <kernel_dir>/scripts/get_dvb_firmware to get the firmware",
+			props->firmware,ret);
+		return ret;
+	}
+
+	info("downloading firmware from file '%s'",props->firmware);
+
+	switch (props->usb_ctrl) {
+		case CYPRESS_AN2135:
+		case CYPRESS_AN2235:
+		case CYPRESS_FX2:
+			ret = usb_cypress_load_firmware(udev, fw, props->usb_ctrl);
+			break;
+		case DEVICE_SPECIFIC:
+			if (props->download_firmware)
+				ret = props->download_firmware(udev,fw);
+			else {
+				err("BUG: driver didn't specified a download_firmware-callback, although it claims to have a DEVICE_SPECIFIC one.");
+				ret = -EINVAL;
+			}
+			break;
+		default:
+			ret = -EINVAL;
+			break;
+	}
+
+	release_firmware(fw);
+	return ret;
+}
+
+int dvb_usb_get_hexline(const struct firmware *fw, struct hexline *hx,
+			       int *pos)
+{
+	u8 *b = (u8 *) &fw->data[*pos];
+	int data_offs = 4;
+	if (*pos >= fw->size)
+		return 0;
+
+	memset(hx,0,sizeof(struct hexline));
+
+	hx->len  = b[0];
+
+	if ((*pos + hx->len + 4) >= fw->size)
+		return -EINVAL;
+
+	hx->addr = b[1] | (b[2] << 8);
+	hx->type = b[3];
+
+	if (hx->type == 0x04) {
+		/* b[4] and b[5] are the Extended linear address record data field */
+		hx->addr |= (b[4] << 24) | (b[5] << 16);
+/*		hx->len -= 2;
+		data_offs += 2; */
+	}
+	memcpy(hx->data,&b[data_offs],hx->len);
+	hx->chk = b[hx->len + data_offs];
+
+	*pos += hx->len + 5;
+
+	return *pos;
+}
+EXPORT_SYMBOL(dvb_usb_get_hexline);
diff --git a/drivers/media/usb/dvb-usb/dvb-usb-i2c.c b/drivers/media/usb/dvb-usb/dvb-usb-i2c.c
new file mode 100644
index 0000000..ca0b734
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/dvb-usb-i2c.c
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0
+/* dvb-usb-i2c.c is part of the DVB USB library.
+ *
+ * Copyright (C) 2004-6 Patrick Boettcher (patrick.boettcher@posteo.de)
+ * see dvb-usb-init.c for copyright information.
+ *
+ * This file contains functions for (de-)initializing an I2C adapter.
+ */
+#include "dvb-usb-common.h"
+
+int dvb_usb_i2c_init(struct dvb_usb_device *d)
+{
+	int ret = 0;
+
+	if (!(d->props.caps & DVB_USB_IS_AN_I2C_ADAPTER))
+		return 0;
+
+	if (d->props.i2c_algo == NULL) {
+		err("no i2c algorithm specified");
+		return -EINVAL;
+	}
+
+	strlcpy(d->i2c_adap.name, d->desc->name, sizeof(d->i2c_adap.name));
+	d->i2c_adap.algo      = d->props.i2c_algo;
+	d->i2c_adap.algo_data = NULL;
+	d->i2c_adap.dev.parent = &d->udev->dev;
+
+	i2c_set_adapdata(&d->i2c_adap, d);
+
+	if ((ret = i2c_add_adapter(&d->i2c_adap)) < 0)
+		err("could not add i2c adapter");
+
+	d->state |= DVB_USB_STATE_I2C;
+
+	return ret;
+}
+
+int dvb_usb_i2c_exit(struct dvb_usb_device *d)
+{
+	if (d->state & DVB_USB_STATE_I2C)
+		i2c_del_adapter(&d->i2c_adap);
+	d->state &= ~DVB_USB_STATE_I2C;
+	return 0;
+}
diff --git a/drivers/media/usb/dvb-usb/dvb-usb-init.c b/drivers/media/usb/dvb-usb/dvb-usb-init.c
new file mode 100644
index 0000000..40ca4ea
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/dvb-usb-init.c
@@ -0,0 +1,305 @@
+/*
+ * DVB USB library - provides a generic interface for a DVB USB device driver.
+ *
+ * dvb-usb-init.c
+ *
+ * Copyright (C) 2004-6 Patrick Boettcher (patrick.boettcher@posteo.de)
+ *
+ *	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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#include "dvb-usb-common.h"
+
+/* debug */
+int dvb_usb_debug;
+module_param_named(debug, dvb_usb_debug, int, 0644);
+MODULE_PARM_DESC(debug, "set debugging level (1=info,xfer=2,pll=4,ts=8,err=16,rc=32,fw=64,mem=128,uxfer=256  (or-able))." DVB_USB_DEBUG_STATUS);
+
+int dvb_usb_disable_rc_polling;
+module_param_named(disable_rc_polling, dvb_usb_disable_rc_polling, int, 0644);
+MODULE_PARM_DESC(disable_rc_polling, "disable remote control polling (default: 0).");
+
+static int dvb_usb_force_pid_filter_usage;
+module_param_named(force_pid_filter_usage, dvb_usb_force_pid_filter_usage, int, 0444);
+MODULE_PARM_DESC(force_pid_filter_usage, "force all dvb-usb-devices to use a PID filter, if any (default: 0).");
+
+static int dvb_usb_adapter_init(struct dvb_usb_device *d, short *adapter_nrs)
+{
+	struct dvb_usb_adapter *adap;
+	int ret, n, o;
+
+	for (n = 0; n < d->props.num_adapters; n++) {
+		adap = &d->adapter[n];
+		adap->dev = d;
+		adap->id  = n;
+
+		memcpy(&adap->props, &d->props.adapter[n], sizeof(struct dvb_usb_adapter_properties));
+
+		for (o = 0; o < adap->props.num_frontends; o++) {
+			struct dvb_usb_adapter_fe_properties *props = &adap->props.fe[o];
+			/* speed - when running at FULL speed we need a HW PID filter */
+			if (d->udev->speed == USB_SPEED_FULL && !(props->caps & DVB_USB_ADAP_HAS_PID_FILTER)) {
+				err("This USB2.0 device cannot be run on a USB1.1 port. (it lacks a hardware PID filter)");
+				return -ENODEV;
+			}
+
+			if ((d->udev->speed == USB_SPEED_FULL && props->caps & DVB_USB_ADAP_HAS_PID_FILTER) ||
+				(props->caps & DVB_USB_ADAP_NEED_PID_FILTERING)) {
+				info("will use the device's hardware PID filter (table count: %d).", props->pid_filter_count);
+				adap->fe_adap[o].pid_filtering  = 1;
+				adap->fe_adap[o].max_feed_count = props->pid_filter_count;
+			} else {
+				info("will pass the complete MPEG2 transport stream to the software demuxer.");
+				adap->fe_adap[o].pid_filtering  = 0;
+				adap->fe_adap[o].max_feed_count = 255;
+			}
+
+			if (!adap->fe_adap[o].pid_filtering &&
+				dvb_usb_force_pid_filter_usage &&
+				props->caps & DVB_USB_ADAP_HAS_PID_FILTER) {
+				info("pid filter enabled by module option.");
+				adap->fe_adap[o].pid_filtering  = 1;
+				adap->fe_adap[o].max_feed_count = props->pid_filter_count;
+			}
+
+			if (props->size_of_priv > 0) {
+				adap->fe_adap[o].priv = kzalloc(props->size_of_priv, GFP_KERNEL);
+				if (adap->fe_adap[o].priv == NULL) {
+					err("no memory for priv for adapter %d fe %d.", n, o);
+					return -ENOMEM;
+				}
+			}
+		}
+
+		if (adap->props.size_of_priv > 0) {
+			adap->priv = kzalloc(adap->props.size_of_priv, GFP_KERNEL);
+			if (adap->priv == NULL) {
+				err("no memory for priv for adapter %d.", n);
+				return -ENOMEM;
+			}
+		}
+
+		if ((ret = dvb_usb_adapter_stream_init(adap)) ||
+			(ret = dvb_usb_adapter_dvb_init(adap, adapter_nrs)) ||
+			(ret = dvb_usb_adapter_frontend_init(adap))) {
+			return ret;
+		}
+
+		/* use exclusive FE lock if there is multiple shared FEs */
+		if (adap->fe_adap[1].fe)
+			adap->dvb_adap.mfe_shared = 1;
+
+		d->num_adapters_initialized++;
+		d->state |= DVB_USB_STATE_DVB;
+	}
+
+	/*
+	 * when reloading the driver w/o replugging the device
+	 * sometimes a timeout occures, this helps
+	 */
+	if (d->props.generic_bulk_ctrl_endpoint != 0) {
+		usb_clear_halt(d->udev, usb_sndbulkpipe(d->udev, d->props.generic_bulk_ctrl_endpoint));
+		usb_clear_halt(d->udev, usb_rcvbulkpipe(d->udev, d->props.generic_bulk_ctrl_endpoint));
+	}
+
+	return 0;
+}
+
+static int dvb_usb_adapter_exit(struct dvb_usb_device *d)
+{
+	int n;
+
+	for (n = 0; n < d->num_adapters_initialized; n++) {
+		dvb_usb_adapter_frontend_exit(&d->adapter[n]);
+		dvb_usb_adapter_dvb_exit(&d->adapter[n]);
+		dvb_usb_adapter_stream_exit(&d->adapter[n]);
+		kfree(d->adapter[n].priv);
+	}
+	d->num_adapters_initialized = 0;
+	d->state &= ~DVB_USB_STATE_DVB;
+	return 0;
+}
+
+
+/* general initialization functions */
+static int dvb_usb_exit(struct dvb_usb_device *d)
+{
+	deb_info("state before exiting everything: %x\n", d->state);
+	dvb_usb_remote_exit(d);
+	dvb_usb_adapter_exit(d);
+	dvb_usb_i2c_exit(d);
+	deb_info("state should be zero now: %x\n", d->state);
+	d->state = DVB_USB_STATE_INIT;
+	kfree(d->priv);
+	kfree(d);
+	return 0;
+}
+
+static int dvb_usb_init(struct dvb_usb_device *d, short *adapter_nums)
+{
+	int ret = 0;
+
+	mutex_init(&d->data_mutex);
+	mutex_init(&d->usb_mutex);
+	mutex_init(&d->i2c_mutex);
+
+	d->state = DVB_USB_STATE_INIT;
+
+	if (d->props.size_of_priv > 0) {
+		d->priv = kzalloc(d->props.size_of_priv, GFP_KERNEL);
+		if (d->priv == NULL) {
+			err("no memory for priv in 'struct dvb_usb_device'");
+			return -ENOMEM;
+		}
+	}
+
+	/* check the capabilities and set appropriate variables */
+	dvb_usb_device_power_ctrl(d, 1);
+
+	if ((ret = dvb_usb_i2c_init(d)) ||
+		(ret = dvb_usb_adapter_init(d, adapter_nums))) {
+		dvb_usb_exit(d);
+		return ret;
+	}
+
+	if ((ret = dvb_usb_remote_init(d)))
+		err("could not initialize remote control.");
+
+	dvb_usb_device_power_ctrl(d, 0);
+
+	return 0;
+}
+
+/* determine the name and the state of the just found USB device */
+static struct dvb_usb_device_description *dvb_usb_find_device(struct usb_device *udev, struct dvb_usb_device_properties *props, int *cold)
+{
+	int i, j;
+	struct dvb_usb_device_description *desc = NULL;
+
+	*cold = -1;
+
+	for (i = 0; i < props->num_device_descs; i++) {
+
+		for (j = 0; j < DVB_USB_ID_MAX_NUM && props->devices[i].cold_ids[j] != NULL; j++) {
+			deb_info("check for cold %x %x\n", props->devices[i].cold_ids[j]->idVendor, props->devices[i].cold_ids[j]->idProduct);
+			if (props->devices[i].cold_ids[j]->idVendor  == le16_to_cpu(udev->descriptor.idVendor) &&
+				props->devices[i].cold_ids[j]->idProduct == le16_to_cpu(udev->descriptor.idProduct)) {
+				*cold = 1;
+				desc = &props->devices[i];
+				break;
+			}
+		}
+
+		if (desc != NULL)
+			break;
+
+		for (j = 0; j < DVB_USB_ID_MAX_NUM && props->devices[i].warm_ids[j] != NULL; j++) {
+			deb_info("check for warm %x %x\n", props->devices[i].warm_ids[j]->idVendor, props->devices[i].warm_ids[j]->idProduct);
+			if (props->devices[i].warm_ids[j]->idVendor == le16_to_cpu(udev->descriptor.idVendor) &&
+				props->devices[i].warm_ids[j]->idProduct == le16_to_cpu(udev->descriptor.idProduct)) {
+				*cold = 0;
+				desc = &props->devices[i];
+				break;
+			}
+		}
+	}
+
+	if (desc != NULL && props->identify_state != NULL)
+		props->identify_state(udev, props, &desc, cold);
+
+	return desc;
+}
+
+int dvb_usb_device_power_ctrl(struct dvb_usb_device *d, int onoff)
+{
+	if (onoff)
+		d->powered++;
+	else
+		d->powered--;
+
+	if (d->powered == 0 || (onoff && d->powered == 1)) { /* when switching from 1 to 0 or from 0 to 1 */
+		deb_info("power control: %d\n", onoff);
+		if (d->props.power_ctrl)
+			return d->props.power_ctrl(d, onoff);
+	}
+	return 0;
+}
+
+/*
+ * USB
+ */
+int dvb_usb_device_init(struct usb_interface *intf,
+			struct dvb_usb_device_properties *props,
+			struct module *owner, struct dvb_usb_device **du,
+			short *adapter_nums)
+{
+	struct usb_device *udev = interface_to_usbdev(intf);
+	struct dvb_usb_device *d = NULL;
+	struct dvb_usb_device_description *desc = NULL;
+
+	int ret = -ENOMEM, cold = 0;
+
+	if (du != NULL)
+		*du = NULL;
+
+	if ((desc = dvb_usb_find_device(udev, props, &cold)) == NULL) {
+		deb_err("something went very wrong, device was not found in current device list - let's see what comes next.\n");
+		return -ENODEV;
+	}
+
+	if (cold) {
+		info("found a '%s' in cold state, will try to load a firmware", desc->name);
+		ret = dvb_usb_download_firmware(udev, props);
+		if (!props->no_reconnect || ret != 0)
+			return ret;
+	}
+
+	info("found a '%s' in warm state.", desc->name);
+	d = kzalloc(sizeof(struct dvb_usb_device), GFP_KERNEL);
+	if (d == NULL) {
+		err("no memory for 'struct dvb_usb_device'");
+		return -ENOMEM;
+	}
+
+	d->udev = udev;
+	memcpy(&d->props, props, sizeof(struct dvb_usb_device_properties));
+	d->desc = desc;
+	d->owner = owner;
+
+	usb_set_intfdata(intf, d);
+
+	if (du != NULL)
+		*du = d;
+
+	ret = dvb_usb_init(d, adapter_nums);
+
+	if (ret == 0)
+		info("%s successfully initialized and connected.", desc->name);
+	else
+		info("%s error while loading driver (%d)", desc->name, ret);
+	return ret;
+}
+EXPORT_SYMBOL(dvb_usb_device_init);
+
+void dvb_usb_device_exit(struct usb_interface *intf)
+{
+	struct dvb_usb_device *d = usb_get_intfdata(intf);
+	const char *name = "generic DVB-USB module";
+
+	usb_set_intfdata(intf, NULL);
+	if (d != NULL && d->desc != NULL) {
+		name = d->desc->name;
+		dvb_usb_exit(d);
+	}
+	info("%s successfully deinitialized and disconnected.", name);
+
+}
+EXPORT_SYMBOL(dvb_usb_device_exit);
+
+MODULE_VERSION("1.0");
+MODULE_AUTHOR("Patrick Boettcher <patrick.boettcher@posteo.de>");
+MODULE_DESCRIPTION("A library module containing commonly used USB and DVB function USB DVB devices");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/dvb-usb-remote.c b/drivers/media/usb/dvb-usb/dvb-usb-remote.c
new file mode 100644
index 0000000..65e2c9e
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/dvb-usb-remote.c
@@ -0,0 +1,397 @@
+// SPDX-License-Identifier: GPL-2.0
+/* dvb-usb-remote.c is part of the DVB USB library.
+ *
+ * Copyright (C) 2004-6 Patrick Boettcher (patrick.boettcher@posteo.de)
+ * see dvb-usb-init.c for copyright information.
+ *
+ * This file contains functions for initializing the input-device and for handling remote-control-queries.
+ */
+#include "dvb-usb-common.h"
+#include <linux/usb/input.h>
+
+static unsigned int
+legacy_dvb_usb_get_keymap_index(const struct input_keymap_entry *ke,
+				struct rc_map_table *keymap,
+				unsigned int keymap_size)
+{
+	unsigned int index;
+	unsigned int scancode;
+
+	if (ke->flags & INPUT_KEYMAP_BY_INDEX) {
+		index = ke->index;
+	} else {
+		if (input_scancode_to_scalar(ke, &scancode))
+			return keymap_size;
+
+		/* See if we can match the raw key code. */
+		for (index = 0; index < keymap_size; index++)
+			if (keymap[index].scancode == scancode)
+				break;
+
+		/* See if there is an unused hole in the map */
+		if (index >= keymap_size) {
+			for (index = 0; index < keymap_size; index++) {
+				if (keymap[index].keycode == KEY_RESERVED ||
+				    keymap[index].keycode == KEY_UNKNOWN) {
+					break;
+				}
+			}
+		}
+	}
+
+	return index;
+}
+
+static int legacy_dvb_usb_getkeycode(struct input_dev *dev,
+				     struct input_keymap_entry *ke)
+{
+	struct dvb_usb_device *d = input_get_drvdata(dev);
+	struct rc_map_table *keymap = d->props.rc.legacy.rc_map_table;
+	unsigned int keymap_size = d->props.rc.legacy.rc_map_size;
+	unsigned int index;
+
+	index = legacy_dvb_usb_get_keymap_index(ke, keymap, keymap_size);
+	if (index >= keymap_size)
+		return -EINVAL;
+
+	ke->keycode = keymap[index].keycode;
+	if (ke->keycode == KEY_UNKNOWN)
+		ke->keycode = KEY_RESERVED;
+	ke->len = sizeof(keymap[index].scancode);
+	memcpy(&ke->scancode, &keymap[index].scancode, ke->len);
+	ke->index = index;
+
+	return 0;
+}
+
+static int legacy_dvb_usb_setkeycode(struct input_dev *dev,
+				     const struct input_keymap_entry *ke,
+				     unsigned int *old_keycode)
+{
+	struct dvb_usb_device *d = input_get_drvdata(dev);
+	struct rc_map_table *keymap = d->props.rc.legacy.rc_map_table;
+	unsigned int keymap_size = d->props.rc.legacy.rc_map_size;
+	unsigned int index;
+
+	index = legacy_dvb_usb_get_keymap_index(ke, keymap, keymap_size);
+	/*
+	 * FIXME: Currently, it is not possible to increase the size of
+	 * scancode table. For it to happen, one possibility
+	 * would be to allocate a table with key_map_size + 1,
+	 * copying data, appending the new key on it, and freeing
+	 * the old one - or maybe just allocating some spare space
+	 */
+	if (index >= keymap_size)
+		return -EINVAL;
+
+	*old_keycode = keymap[index].keycode;
+	keymap->keycode = ke->keycode;
+	__set_bit(ke->keycode, dev->keybit);
+
+	if (*old_keycode != KEY_RESERVED) {
+		__clear_bit(*old_keycode, dev->keybit);
+		for (index = 0; index < keymap_size; index++) {
+			if (keymap[index].keycode == *old_keycode) {
+				__set_bit(*old_keycode, dev->keybit);
+				break;
+			}
+		}
+	}
+
+	return 0;
+}
+
+/* Remote-control poll function - called every dib->rc_query_interval ms to see
+ * whether the remote control has received anything.
+ *
+ * TODO: Fix the repeat rate of the input device.
+ */
+static void legacy_dvb_usb_read_remote_control(struct work_struct *work)
+{
+	struct dvb_usb_device *d =
+		container_of(work, struct dvb_usb_device, rc_query_work.work);
+	u32 event;
+	int state;
+
+	/* TODO: need a lock here.  We can simply skip checking for the remote control
+	   if we're busy. */
+
+	/* when the parameter has been set to 1 via sysfs while the driver was running */
+	if (dvb_usb_disable_rc_polling)
+		return;
+
+	if (d->props.rc.legacy.rc_query(d,&event,&state)) {
+		err("error while querying for an remote control event.");
+		goto schedule;
+	}
+
+
+	switch (state) {
+		case REMOTE_NO_KEY_PRESSED:
+			break;
+		case REMOTE_KEY_PRESSED:
+			deb_rc("key pressed\n");
+			d->last_event = event;
+			input_event(d->input_dev, EV_KEY, event, 1);
+			input_sync(d->input_dev);
+			input_event(d->input_dev, EV_KEY, d->last_event, 0);
+			input_sync(d->input_dev);
+			break;
+		case REMOTE_KEY_REPEAT:
+			deb_rc("key repeated\n");
+			input_event(d->input_dev, EV_KEY, event, 1);
+			input_sync(d->input_dev);
+			input_event(d->input_dev, EV_KEY, d->last_event, 0);
+			input_sync(d->input_dev);
+			break;
+		default:
+			break;
+	}
+
+/* improved repeat handling ???
+	switch (state) {
+		case REMOTE_NO_KEY_PRESSED:
+			deb_rc("NO KEY PRESSED\n");
+			if (d->last_state != REMOTE_NO_KEY_PRESSED) {
+				deb_rc("releasing event %d\n",d->last_event);
+				input_event(d->rc_input_dev, EV_KEY, d->last_event, 0);
+				input_sync(d->rc_input_dev);
+			}
+			d->last_state = REMOTE_NO_KEY_PRESSED;
+			d->last_event = 0;
+			break;
+		case REMOTE_KEY_PRESSED:
+			deb_rc("KEY PRESSED\n");
+			deb_rc("pressing event %d\n",event);
+
+			input_event(d->rc_input_dev, EV_KEY, event, 1);
+			input_sync(d->rc_input_dev);
+
+			d->last_event = event;
+			d->last_state = REMOTE_KEY_PRESSED;
+			break;
+		case REMOTE_KEY_REPEAT:
+			deb_rc("KEY_REPEAT\n");
+			if (d->last_state != REMOTE_NO_KEY_PRESSED) {
+				deb_rc("repeating event %d\n",d->last_event);
+				input_event(d->rc_input_dev, EV_KEY, d->last_event, 2);
+				input_sync(d->rc_input_dev);
+				d->last_state = REMOTE_KEY_REPEAT;
+			}
+		default:
+			break;
+	}
+*/
+
+schedule:
+	schedule_delayed_work(&d->rc_query_work,msecs_to_jiffies(d->props.rc.legacy.rc_interval));
+}
+
+static int legacy_dvb_usb_remote_init(struct dvb_usb_device *d)
+{
+	int i, err, rc_interval;
+	struct input_dev *input_dev;
+
+	input_dev = input_allocate_device();
+	if (!input_dev)
+		return -ENOMEM;
+
+	input_dev->evbit[0] = BIT_MASK(EV_KEY);
+	input_dev->name = "IR-receiver inside an USB DVB receiver";
+	input_dev->phys = d->rc_phys;
+	usb_to_input_id(d->udev, &input_dev->id);
+	input_dev->dev.parent = &d->udev->dev;
+	d->input_dev = input_dev;
+	d->rc_dev = NULL;
+
+	input_dev->getkeycode = legacy_dvb_usb_getkeycode;
+	input_dev->setkeycode = legacy_dvb_usb_setkeycode;
+
+	/* set the bits for the keys */
+	deb_rc("key map size: %d\n", d->props.rc.legacy.rc_map_size);
+	for (i = 0; i < d->props.rc.legacy.rc_map_size; i++) {
+		deb_rc("setting bit for event %d item %d\n",
+			d->props.rc.legacy.rc_map_table[i].keycode, i);
+		set_bit(d->props.rc.legacy.rc_map_table[i].keycode, input_dev->keybit);
+	}
+
+	/* setting these two values to non-zero, we have to manage key repeats */
+	input_dev->rep[REP_PERIOD] = d->props.rc.legacy.rc_interval;
+	input_dev->rep[REP_DELAY]  = d->props.rc.legacy.rc_interval + 150;
+
+	input_set_drvdata(input_dev, d);
+
+	err = input_register_device(input_dev);
+	if (err)
+		input_free_device(input_dev);
+
+	rc_interval = d->props.rc.legacy.rc_interval;
+
+	INIT_DELAYED_WORK(&d->rc_query_work, legacy_dvb_usb_read_remote_control);
+
+	info("schedule remote query interval to %d msecs.", rc_interval);
+	schedule_delayed_work(&d->rc_query_work,
+			      msecs_to_jiffies(rc_interval));
+
+	d->state |= DVB_USB_STATE_REMOTE;
+
+	return err;
+}
+
+/* Remote-control poll function - called every dib->rc_query_interval ms to see
+ * whether the remote control has received anything.
+ *
+ * TODO: Fix the repeat rate of the input device.
+ */
+static void dvb_usb_read_remote_control(struct work_struct *work)
+{
+	struct dvb_usb_device *d =
+		container_of(work, struct dvb_usb_device, rc_query_work.work);
+	int err;
+
+	/* TODO: need a lock here.  We can simply skip checking for the remote control
+	   if we're busy. */
+
+	/* when the parameter has been set to 1 via sysfs while the
+	 * driver was running, or when bulk mode is enabled after IR init
+	 */
+	if (dvb_usb_disable_rc_polling || d->props.rc.core.bulk_mode)
+		return;
+
+	err = d->props.rc.core.rc_query(d);
+	if (err)
+		err("error %d while querying for an remote control event.", err);
+
+	schedule_delayed_work(&d->rc_query_work,
+			      msecs_to_jiffies(d->props.rc.core.rc_interval));
+}
+
+static int rc_core_dvb_usb_remote_init(struct dvb_usb_device *d)
+{
+	int err, rc_interval;
+	struct rc_dev *dev;
+
+	dev = rc_allocate_device(d->props.rc.core.driver_type);
+	if (!dev)
+		return -ENOMEM;
+
+	dev->driver_name = d->props.rc.core.module_name;
+	dev->map_name = d->props.rc.core.rc_codes;
+	dev->change_protocol = d->props.rc.core.change_protocol;
+	dev->allowed_protocols = d->props.rc.core.allowed_protos;
+	usb_to_input_id(d->udev, &dev->input_id);
+	dev->device_name = d->desc->name;
+	dev->input_phys = d->rc_phys;
+	dev->dev.parent = &d->udev->dev;
+	dev->priv = d;
+	dev->scancode_mask = d->props.rc.core.scancode_mask;
+
+	err = rc_register_device(dev);
+	if (err < 0) {
+		rc_free_device(dev);
+		return err;
+	}
+
+	d->input_dev = NULL;
+	d->rc_dev = dev;
+
+	if (!d->props.rc.core.rc_query || d->props.rc.core.bulk_mode)
+		return 0;
+
+	/* Polling mode - initialize a work queue for handling it */
+	INIT_DELAYED_WORK(&d->rc_query_work, dvb_usb_read_remote_control);
+
+	rc_interval = d->props.rc.core.rc_interval;
+
+	info("schedule remote query interval to %d msecs.", rc_interval);
+	schedule_delayed_work(&d->rc_query_work,
+			      msecs_to_jiffies(rc_interval));
+
+	return 0;
+}
+
+int dvb_usb_remote_init(struct dvb_usb_device *d)
+{
+	int err;
+
+	if (dvb_usb_disable_rc_polling)
+		return 0;
+
+	if (d->props.rc.legacy.rc_map_table && d->props.rc.legacy.rc_query)
+		d->props.rc.mode = DVB_RC_LEGACY;
+	else if (d->props.rc.core.rc_codes)
+		d->props.rc.mode = DVB_RC_CORE;
+	else
+		return 0;
+
+	usb_make_path(d->udev, d->rc_phys, sizeof(d->rc_phys));
+	strlcat(d->rc_phys, "/ir0", sizeof(d->rc_phys));
+
+	/* Start the remote-control polling. */
+	if (d->props.rc.legacy.rc_interval < 40)
+		d->props.rc.legacy.rc_interval = 100; /* default */
+
+	if (d->props.rc.mode == DVB_RC_LEGACY)
+		err = legacy_dvb_usb_remote_init(d);
+	else
+		err = rc_core_dvb_usb_remote_init(d);
+	if (err)
+		return err;
+
+	d->state |= DVB_USB_STATE_REMOTE;
+
+	return 0;
+}
+
+int dvb_usb_remote_exit(struct dvb_usb_device *d)
+{
+	if (d->state & DVB_USB_STATE_REMOTE) {
+		cancel_delayed_work_sync(&d->rc_query_work);
+		if (d->props.rc.mode == DVB_RC_LEGACY)
+			input_unregister_device(d->input_dev);
+		else
+			rc_unregister_device(d->rc_dev);
+	}
+	d->state &= ~DVB_USB_STATE_REMOTE;
+	return 0;
+}
+
+#define DVB_USB_RC_NEC_EMPTY           0x00
+#define DVB_USB_RC_NEC_KEY_PRESSED     0x01
+#define DVB_USB_RC_NEC_KEY_REPEATED    0x02
+int dvb_usb_nec_rc_key_to_event(struct dvb_usb_device *d,
+		u8 keybuf[5], u32 *event, int *state)
+{
+	int i;
+	struct rc_map_table *keymap = d->props.rc.legacy.rc_map_table;
+	*event = 0;
+	*state = REMOTE_NO_KEY_PRESSED;
+	switch (keybuf[0]) {
+		case DVB_USB_RC_NEC_EMPTY:
+			break;
+		case DVB_USB_RC_NEC_KEY_PRESSED:
+			if ((u8) ~keybuf[1] != keybuf[2] ||
+				(u8) ~keybuf[3] != keybuf[4]) {
+				deb_err("remote control checksum failed.\n");
+				break;
+			}
+			/* See if we can match the raw key code. */
+			for (i = 0; i < d->props.rc.legacy.rc_map_size; i++)
+				if (rc5_custom(&keymap[i]) == keybuf[1] &&
+					rc5_data(&keymap[i]) == keybuf[3]) {
+					*event = keymap[i].keycode;
+					*state = REMOTE_KEY_PRESSED;
+					return 0;
+				}
+			deb_err("key mapping failed - no appropriate key found in keymapping\n");
+			break;
+		case DVB_USB_RC_NEC_KEY_REPEATED:
+			*state = REMOTE_KEY_REPEAT;
+			break;
+		default:
+			deb_err("unknown type of remote status: %d\n",keybuf[0]);
+			break;
+	}
+	return 0;
+}
+EXPORT_SYMBOL(dvb_usb_nec_rc_key_to_event);
diff --git a/drivers/media/usb/dvb-usb/dvb-usb-urb.c b/drivers/media/usb/dvb-usb/dvb-usb-urb.c
new file mode 100644
index 0000000..c1b4e94
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/dvb-usb-urb.c
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0
+/* dvb-usb-urb.c is part of the DVB USB library.
+ *
+ * Copyright (C) 2004-6 Patrick Boettcher (patrick.boettcher@posteo.de)
+ * see dvb-usb-init.c for copyright information.
+ *
+ * This file keeps functions for initializing and handling the
+ * USB and URB stuff.
+ */
+#include "dvb-usb-common.h"
+
+int dvb_usb_generic_rw(struct dvb_usb_device *d, u8 *wbuf, u16 wlen, u8 *rbuf,
+	u16 rlen, int delay_ms)
+{
+	int actlen,ret = -ENOMEM;
+
+	if (!d || wbuf == NULL || wlen == 0)
+		return -EINVAL;
+
+	if (d->props.generic_bulk_ctrl_endpoint == 0) {
+		err("endpoint for generic control not specified.");
+		return -EINVAL;
+	}
+
+	if ((ret = mutex_lock_interruptible(&d->usb_mutex)))
+		return ret;
+
+	deb_xfer(">>> ");
+	debug_dump(wbuf,wlen,deb_xfer);
+
+	ret = usb_bulk_msg(d->udev,usb_sndbulkpipe(d->udev,
+			d->props.generic_bulk_ctrl_endpoint), wbuf,wlen,&actlen,
+			2000);
+
+	if (ret)
+		err("bulk message failed: %d (%d/%d)",ret,wlen,actlen);
+	else
+		ret = actlen != wlen ? -1 : 0;
+
+	/* an answer is expected, and no error before */
+	if (!ret && rbuf && rlen) {
+		if (delay_ms)
+			msleep(delay_ms);
+
+		ret = usb_bulk_msg(d->udev,usb_rcvbulkpipe(d->udev,
+				d->props.generic_bulk_ctrl_endpoint_response ?
+				d->props.generic_bulk_ctrl_endpoint_response :
+				d->props.generic_bulk_ctrl_endpoint),rbuf,rlen,&actlen,
+				2000);
+
+		if (ret)
+			err("recv bulk message failed: %d",ret);
+		else {
+			deb_xfer("<<< ");
+			debug_dump(rbuf,actlen,deb_xfer);
+		}
+	}
+
+	mutex_unlock(&d->usb_mutex);
+	return ret;
+}
+EXPORT_SYMBOL(dvb_usb_generic_rw);
+
+int dvb_usb_generic_write(struct dvb_usb_device *d, u8 *buf, u16 len)
+{
+	return dvb_usb_generic_rw(d,buf,len,NULL,0,0);
+}
+EXPORT_SYMBOL(dvb_usb_generic_write);
+
+static void dvb_usb_data_complete(struct usb_data_stream *stream, u8 *buffer, size_t length)
+{
+	struct dvb_usb_adapter *adap = stream->user_priv;
+	if (adap->feedcount > 0 && adap->state & DVB_USB_ADAP_STATE_DVB)
+		dvb_dmx_swfilter(&adap->demux, buffer, length);
+}
+
+static void dvb_usb_data_complete_204(struct usb_data_stream *stream, u8 *buffer, size_t length)
+{
+	struct dvb_usb_adapter *adap = stream->user_priv;
+	if (adap->feedcount > 0 && adap->state & DVB_USB_ADAP_STATE_DVB)
+		dvb_dmx_swfilter_204(&adap->demux, buffer, length);
+}
+
+static void dvb_usb_data_complete_raw(struct usb_data_stream *stream,
+				      u8 *buffer, size_t length)
+{
+	struct dvb_usb_adapter *adap = stream->user_priv;
+	if (adap->feedcount > 0 && adap->state & DVB_USB_ADAP_STATE_DVB)
+		dvb_dmx_swfilter_raw(&adap->demux, buffer, length);
+}
+
+int dvb_usb_adapter_stream_init(struct dvb_usb_adapter *adap)
+{
+	int i, ret = 0;
+	for (i = 0; i < adap->props.num_frontends; i++) {
+
+		adap->fe_adap[i].stream.udev      = adap->dev->udev;
+		if (adap->props.fe[i].caps & DVB_USB_ADAP_RECEIVES_204_BYTE_TS)
+			adap->fe_adap[i].stream.complete =
+				dvb_usb_data_complete_204;
+		else
+		if (adap->props.fe[i].caps & DVB_USB_ADAP_RECEIVES_RAW_PAYLOAD)
+			adap->fe_adap[i].stream.complete =
+				dvb_usb_data_complete_raw;
+		else
+		adap->fe_adap[i].stream.complete  = dvb_usb_data_complete;
+		adap->fe_adap[i].stream.user_priv = adap;
+		ret = usb_urb_init(&adap->fe_adap[i].stream,
+				   &adap->props.fe[i].stream);
+		if (ret < 0)
+			break;
+	}
+	return ret;
+}
+
+int dvb_usb_adapter_stream_exit(struct dvb_usb_adapter *adap)
+{
+	int i;
+	for (i = 0; i < adap->props.num_frontends; i++)
+		usb_urb_exit(&adap->fe_adap[i].stream);
+	return 0;
+}
diff --git a/drivers/media/usb/dvb-usb/dvb-usb.h b/drivers/media/usb/dvb-usb/dvb-usb.h
new file mode 100644
index 0000000..317ed6a
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/dvb-usb.h
@@ -0,0 +1,492 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* dvb-usb.h is part of the DVB USB library.
+ *
+ * Copyright (C) 2004-6 Patrick Boettcher (patrick.boettcher@posteo.de)
+ * see dvb-usb-init.c for copyright information.
+ *
+ * the headerfile, all dvb-usb-drivers have to include.
+ *
+ * TODO: clean-up the structures for unused fields and update the comments
+ */
+#ifndef __DVB_USB_H__
+#define __DVB_USB_H__
+
+#include <linux/input.h>
+#include <linux/usb.h>
+#include <linux/firmware.h>
+#include <linux/mutex.h>
+#include <media/rc-core.h>
+
+#include <media/dvb_frontend.h>
+#include <media/dvb_demux.h>
+#include <media/dvb_net.h>
+#include <media/dmxdev.h>
+
+#include "dvb-pll.h"
+
+#include <media/dvb-usb-ids.h>
+
+/* debug */
+#ifdef CONFIG_DVB_USB_DEBUG
+#define dprintk(var,level,args...) \
+	    do { if ((var & level)) { printk(args); } } while (0)
+
+#define debug_dump(b,l,func) {\
+	int loop_; \
+	for (loop_ = 0; loop_ < l; loop_++) func("%02x ", b[loop_]); \
+	func("\n");\
+}
+#define DVB_USB_DEBUG_STATUS
+#else
+#define dprintk(args...)
+#define debug_dump(b,l,func)
+
+#define DVB_USB_DEBUG_STATUS " (debugging is not enabled)"
+
+#endif
+
+/* generic log methods - taken from usb.h */
+#ifndef DVB_USB_LOG_PREFIX
+ #define DVB_USB_LOG_PREFIX "dvb-usb (please define a log prefix)"
+#endif
+
+#undef err
+#define err(format, arg...)  printk(KERN_ERR     DVB_USB_LOG_PREFIX ": " format "\n" , ## arg)
+#undef info
+#define info(format, arg...) printk(KERN_INFO    DVB_USB_LOG_PREFIX ": " format "\n" , ## arg)
+#undef warn
+#define warn(format, arg...) printk(KERN_WARNING DVB_USB_LOG_PREFIX ": " format "\n" , ## arg)
+
+/**
+ * struct dvb_usb_device_description - name and its according USB IDs
+ * @name: real name of the box, regardless which DVB USB device class is in use
+ * @cold_ids: array of struct usb_device_id which describe the device in
+ *  pre-firmware state
+ * @warm_ids: array of struct usb_device_id which describe the device in
+ *  post-firmware state
+ *
+ * Each DVB USB device class can have one or more actual devices, this struct
+ * assigns a name to it.
+ */
+struct dvb_usb_device_description {
+	const char *name;
+
+#define DVB_USB_ID_MAX_NUM 15
+	struct usb_device_id *cold_ids[DVB_USB_ID_MAX_NUM];
+	struct usb_device_id *warm_ids[DVB_USB_ID_MAX_NUM];
+};
+
+static inline u8 rc5_custom(struct rc_map_table *key)
+{
+	return (key->scancode >> 8) & 0xff;
+}
+
+static inline u8 rc5_data(struct rc_map_table *key)
+{
+	return key->scancode & 0xff;
+}
+
+static inline u16 rc5_scan(struct rc_map_table *key)
+{
+	return key->scancode & 0xffff;
+}
+
+struct dvb_usb_device;
+struct dvb_usb_adapter;
+struct usb_data_stream;
+
+/**
+ * Properties of USB streaming - TODO this structure should be somewhere else
+ * describes the kind of USB transfer used for data-streaming.
+ *  (BULK or ISOC)
+ */
+struct usb_data_stream_properties {
+#define USB_BULK  1
+#define USB_ISOC  2
+	int type;
+	int count;
+	int endpoint;
+
+	union {
+		struct {
+			int buffersize; /* per URB */
+		} bulk;
+		struct {
+			int framesperurb;
+			int framesize;
+			int interval;
+		} isoc;
+	} u;
+};
+
+/**
+ * struct dvb_usb_adapter_properties - properties of a dvb-usb-adapter.
+ *    A DVB-USB-Adapter is basically a dvb_adapter which is present on a USB-device.
+ * @caps: capabilities of the DVB USB device.
+ * @pid_filter_count: number of PID filter position in the optional hardware
+ *  PID-filter.
+ * @num_frontends: number of frontends of the DVB USB adapter.
+ * @frontend_ctrl: called to power on/off active frontend.
+ * @streaming_ctrl: called to start and stop the MPEG2-TS streaming of the
+ *  device (not URB submitting/killing).
+ * @pid_filter_ctrl: called to en/disable the PID filter, if any.
+ * @pid_filter: called to set/unset a PID for filtering.
+ * @frontend_attach: called to attach the possible frontends (fill fe-field
+ *  of struct dvb_usb_device).
+ * @tuner_attach: called to attach the correct tuner and to fill pll_addr,
+ *  pll_desc and pll_init_buf of struct dvb_usb_device).
+ * @stream: configuration of the USB streaming
+ */
+struct dvb_usb_adapter_fe_properties {
+#define DVB_USB_ADAP_HAS_PID_FILTER               0x01
+#define DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF 0x02
+#define DVB_USB_ADAP_NEED_PID_FILTERING           0x04
+#define DVB_USB_ADAP_RECEIVES_204_BYTE_TS         0x08
+#define DVB_USB_ADAP_RECEIVES_RAW_PAYLOAD         0x10
+	int caps;
+	int pid_filter_count;
+
+	int (*streaming_ctrl)  (struct dvb_usb_adapter *, int);
+	int (*pid_filter_ctrl) (struct dvb_usb_adapter *, int);
+	int (*pid_filter)      (struct dvb_usb_adapter *, int, u16, int);
+
+	int (*frontend_attach) (struct dvb_usb_adapter *);
+	int (*tuner_attach)    (struct dvb_usb_adapter *);
+
+	struct usb_data_stream_properties stream;
+
+	int size_of_priv;
+};
+
+#define MAX_NO_OF_FE_PER_ADAP 3
+struct dvb_usb_adapter_properties {
+	int size_of_priv;
+
+	int (*frontend_ctrl)   (struct dvb_frontend *, int);
+
+	int num_frontends;
+	struct dvb_usb_adapter_fe_properties fe[MAX_NO_OF_FE_PER_ADAP];
+};
+
+/**
+ * struct dvb_rc_legacy - old properties of remote controller
+ * @rc_map_table: a hard-wired array of struct rc_map_table (NULL to disable
+ *  remote control handling).
+ * @rc_map_size: number of items in @rc_map_table.
+ * @rc_query: called to query an event event.
+ * @rc_interval: time in ms between two queries.
+ */
+struct dvb_rc_legacy {
+/* remote control properties */
+#define REMOTE_NO_KEY_PRESSED      0x00
+#define REMOTE_KEY_PRESSED         0x01
+#define REMOTE_KEY_REPEAT          0x02
+	struct rc_map_table  *rc_map_table;
+	int rc_map_size;
+	int (*rc_query) (struct dvb_usb_device *, u32 *, int *);
+	int rc_interval;
+};
+
+/**
+ * struct dvb_rc properties of remote controller, using rc-core
+ * @rc_codes: name of rc codes table
+ * @protocol: type of protocol(s) currently used by the driver
+ * @allowed_protos: protocol(s) supported by the driver
+ * @driver_type: Used to point if a device supports raw mode
+ * @change_protocol: callback to change protocol
+ * @rc_query: called to query an event event.
+ * @rc_interval: time in ms between two queries.
+ * @bulk_mode: device supports bulk mode for RC (disable polling mode)
+ */
+struct dvb_rc {
+	char *rc_codes;
+	u64 protocol;
+	u64 allowed_protos;
+	enum rc_driver_type driver_type;
+	int (*change_protocol)(struct rc_dev *dev, u64 *rc_proto);
+	char *module_name;
+	int (*rc_query) (struct dvb_usb_device *d);
+	int rc_interval;
+	bool bulk_mode;				/* uses bulk mode */
+	u32 scancode_mask;
+};
+
+/**
+ * enum dvb_usb_mode - Specifies if it is using a legacy driver or a new one
+ *		       based on rc-core
+ * This is initialized/used only inside dvb-usb-remote.c.
+ * It shouldn't be set by the drivers.
+ */
+enum dvb_usb_mode {
+	DVB_RC_LEGACY,
+	DVB_RC_CORE,
+};
+
+/**
+ * struct dvb_usb_device_properties - properties of a dvb-usb-device
+ * @usb_ctrl: which USB device-side controller is in use. Needed for firmware
+ *  download.
+ * @firmware: name of the firmware file.
+ * @download_firmware: called to download the firmware when the usb_ctrl is
+ *  DEVICE_SPECIFIC.
+ * @no_reconnect: device doesn't do a reconnect after downloading the firmware,
+ *  so do the warm initialization right after it
+ *
+ * @size_of_priv: how many bytes shall be allocated for the private field
+ *  of struct dvb_usb_device.
+ *
+ * @power_ctrl: called to enable/disable power of the device.
+ * @read_mac_address: called to read the MAC address of the device.
+ * @identify_state: called to determine the state (cold or warm), when it
+ *  is not distinguishable by the USB IDs.
+ *
+ * @rc: remote controller properties
+ *
+ * @i2c_algo: i2c_algorithm if the device has I2CoverUSB.
+ *
+ * @generic_bulk_ctrl_endpoint: most of the DVB USB devices have a generic
+ *  endpoint which received control messages with bulk transfers. When this
+ *  is non-zero, one can use dvb_usb_generic_rw and dvb_usb_generic_write-
+ *  helper functions.
+ *
+ * @generic_bulk_ctrl_endpoint_response: some DVB USB devices use a separate
+ *  endpoint for responses to control messages sent with bulk transfers via
+ *  the generic_bulk_ctrl_endpoint. When this is non-zero, this will be used
+ *  instead of the generic_bulk_ctrl_endpoint when reading usb responses in
+ *  the dvb_usb_generic_rw helper function.
+ *
+ * @num_device_descs: number of struct dvb_usb_device_description in @devices
+ * @devices: array of struct dvb_usb_device_description compatibles with these
+ *  properties.
+ */
+#define MAX_NO_OF_ADAPTER_PER_DEVICE 2
+struct dvb_usb_device_properties {
+
+#define DVB_USB_IS_AN_I2C_ADAPTER            0x01
+	int caps;
+
+#define DEVICE_SPECIFIC 0
+#define CYPRESS_AN2135  1
+#define CYPRESS_AN2235  2
+#define CYPRESS_FX2     3
+	int        usb_ctrl;
+	int        (*download_firmware) (struct usb_device *, const struct firmware *);
+	const char *firmware;
+	int        no_reconnect;
+
+	int size_of_priv;
+
+	int num_adapters;
+	struct dvb_usb_adapter_properties adapter[MAX_NO_OF_ADAPTER_PER_DEVICE];
+
+	int (*power_ctrl)       (struct dvb_usb_device *, int);
+	int (*read_mac_address) (struct dvb_usb_device *, u8 []);
+	int (*identify_state)   (struct usb_device *, struct dvb_usb_device_properties *,
+			struct dvb_usb_device_description **, int *);
+
+	struct {
+		enum dvb_usb_mode mode;	/* Drivers shouldn't touch on it */
+		struct dvb_rc_legacy legacy;
+		struct dvb_rc core;
+	} rc;
+
+	struct i2c_algorithm *i2c_algo;
+
+	int generic_bulk_ctrl_endpoint;
+	int generic_bulk_ctrl_endpoint_response;
+
+	int num_device_descs;
+	struct dvb_usb_device_description devices[12];
+};
+
+/**
+ * struct usb_data_stream - generic object of an USB stream
+ * @buf_num: number of buffer allocated.
+ * @buf_size: size of each buffer in buf_list.
+ * @buf_list: array containing all allocate buffers for streaming.
+ * @dma_addr: list of dma_addr_t for each buffer in buf_list.
+ *
+ * @urbs_initialized: number of URBs initialized.
+ * @urbs_submitted: number of URBs submitted.
+ */
+#define MAX_NO_URBS_FOR_DATA_STREAM 10
+struct usb_data_stream {
+	struct usb_device                 *udev;
+	struct usb_data_stream_properties  props;
+
+#define USB_STATE_INIT    0x00
+#define USB_STATE_URB_BUF 0x01
+	int state;
+
+	void (*complete) (struct usb_data_stream *, u8 *, size_t);
+
+	struct urb    *urb_list[MAX_NO_URBS_FOR_DATA_STREAM];
+	int            buf_num;
+	unsigned long  buf_size;
+	u8            *buf_list[MAX_NO_URBS_FOR_DATA_STREAM];
+	dma_addr_t     dma_addr[MAX_NO_URBS_FOR_DATA_STREAM];
+
+	int urbs_initialized;
+	int urbs_submitted;
+
+	void *user_priv;
+};
+
+/**
+ * struct dvb_usb_adapter - a DVB adapter on a USB device
+ * @id: index of this adapter (starting with 0).
+ *
+ * @feedcount: number of reqested feeds (used for streaming-activation)
+ * @pid_filtering: is hardware pid_filtering used or not.
+ *
+ * @pll_addr: I2C address of the tuner for programming
+ * @pll_init: array containing the initialization buffer
+ * @pll_desc: pointer to the appropriate struct dvb_pll_desc
+ * @tuner_pass_ctrl: called to (de)activate tuner passthru of the demod or the board
+ *
+ * @dvb_adap: device's dvb_adapter.
+ * @dmxdev: device's dmxdev.
+ * @demux: device's software demuxer.
+ * @dvb_net: device's dvb_net interfaces.
+ * @dvb_frontend: device's frontend.
+ * @max_feed_count: how many feeds can be handled simultaneously by this
+ *  device
+ *
+ * @fe_init:  rerouted frontend-init (wakeup) function.
+ * @fe_sleep: rerouted frontend-sleep function.
+ *
+ * @stream: the usb data stream.
+ */
+struct dvb_usb_fe_adapter {
+	struct dvb_frontend *fe;
+
+	int (*fe_init)  (struct dvb_frontend *);
+	int (*fe_sleep) (struct dvb_frontend *);
+
+	struct usb_data_stream stream;
+
+	int pid_filtering;
+	int max_feed_count;
+
+	void *priv;
+};
+
+struct dvb_usb_adapter {
+	struct dvb_usb_device *dev;
+	struct dvb_usb_adapter_properties props;
+
+#define DVB_USB_ADAP_STATE_INIT 0x000
+#define DVB_USB_ADAP_STATE_DVB  0x001
+	int state;
+
+	u8  id;
+
+	int feedcount;
+
+	/* dvb */
+	struct dvb_adapter   dvb_adap;
+	struct dmxdev        dmxdev;
+	struct dvb_demux     demux;
+	struct dvb_net       dvb_net;
+
+	struct dvb_usb_fe_adapter fe_adap[MAX_NO_OF_FE_PER_ADAP];
+	int active_fe;
+	int num_frontends_initialized;
+
+	void *priv;
+};
+
+/**
+ * struct dvb_usb_device - object of a DVB USB device
+ * @props: copy of the struct dvb_usb_properties this device belongs to.
+ * @desc: pointer to the device's struct dvb_usb_device_description.
+ * @state: initialization and runtime state of the device.
+ *
+ * @powered: indicated whether the device is power or not.
+ *  Powered is in/decremented for each call to modify the state.
+ * @udev: pointer to the device's struct usb_device.
+ *
+ * @data_mutex: mutex to protect the data structure used to store URB data
+ * @usb_mutex: mutex of USB control messages (reading needs two messages).
+ *	Please notice that this mutex is used internally at the generic
+ *	URB control functions. So, drivers using dvb_usb_generic_rw() and
+ *	derivated functions should not lock it internally.
+ * @i2c_mutex: mutex for i2c-transfers
+ *
+ * @i2c_adap: device's i2c_adapter if it uses I2CoverUSB
+ *
+ * @rc_dev: rc device for the remote control (rc-core mode)
+ * @input_dev: input device for the remote control (legacy mode)
+ * @rc_query_work: struct work_struct frequent rc queries
+ * @last_event: last triggered event
+ * @last_state: last state (no, pressed, repeat)
+ * @owner: owner of the dvb_adapter
+ * @priv: private data of the actual driver (allocate by dvb-usb, size defined
+ *  in size_of_priv of dvb_usb_properties).
+ */
+struct dvb_usb_device {
+	struct dvb_usb_device_properties props;
+	struct dvb_usb_device_description *desc;
+
+	struct usb_device *udev;
+
+#define DVB_USB_STATE_INIT        0x000
+#define DVB_USB_STATE_I2C         0x001
+#define DVB_USB_STATE_DVB         0x002
+#define DVB_USB_STATE_REMOTE      0x004
+	int state;
+
+	int powered;
+
+	/* locking */
+	struct mutex data_mutex;
+	struct mutex usb_mutex;
+
+	/* i2c */
+	struct mutex i2c_mutex;
+	struct i2c_adapter i2c_adap;
+
+	int                    num_adapters_initialized;
+	struct dvb_usb_adapter adapter[MAX_NO_OF_ADAPTER_PER_DEVICE];
+
+	/* remote control */
+	struct rc_dev *rc_dev;
+	struct input_dev *input_dev;
+	char rc_phys[64];
+	struct delayed_work rc_query_work;
+	u32 last_event;
+	int last_state;
+
+	struct module *owner;
+
+	void *priv;
+};
+
+extern int dvb_usb_device_init(struct usb_interface *,
+			       struct dvb_usb_device_properties *,
+			       struct module *, struct dvb_usb_device **,
+			       short *adapter_nums);
+extern void dvb_usb_device_exit(struct usb_interface *);
+
+/* the generic read/write method for device control */
+extern int __must_check
+dvb_usb_generic_rw(struct dvb_usb_device *, u8 *, u16, u8 *, u16, int);
+extern int __must_check
+dvb_usb_generic_write(struct dvb_usb_device *, u8 *, u16);
+
+/* commonly used remote control parsing */
+extern int dvb_usb_nec_rc_key_to_event(struct dvb_usb_device *, u8[], u32 *, int *);
+
+/* commonly used firmware download types and function */
+struct hexline {
+	u8 len;
+	u32 addr;
+	u8 type;
+	u8 data[255];
+	u8 chk;
+};
+extern int usb_cypress_load_firmware(struct usb_device *udev, const struct firmware *fw, int type);
+extern int dvb_usb_get_hexline(const struct firmware *fw, struct hexline *hx, int *pos);
+
+
+#endif
diff --git a/drivers/media/usb/dvb-usb/dw2102.c b/drivers/media/usb/dvb-usb/dw2102.c
new file mode 100644
index 0000000..9ce8b4d
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/dw2102.c
@@ -0,0 +1,2472 @@
+/* DVB USB framework compliant Linux driver for the
+ *	DVBWorld DVB-S 2101, 2102, DVB-S2 2104, DVB-C 3101,
+ *	TeVii S421, S480, S482, S600, S630, S632, S650, S660, S662,
+ *	Prof 1100, 7500,
+ *	Geniatech SU3000, T220,
+ *	TechnoTrend S2-4600,
+ *	Terratec Cinergy S2 cards
+ * Copyright (C) 2008-2012 Igor M. Liplianin (liplianin@me.by)
+ *
+ *	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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#include <media/dvb-usb-ids.h>
+#include "dw2102.h"
+#include "si21xx.h"
+#include "stv0299.h"
+#include "z0194a.h"
+#include "stv0288.h"
+#include "stb6000.h"
+#include "eds1547.h"
+#include "cx24116.h"
+#include "tda1002x.h"
+#include "mt312.h"
+#include "zl10039.h"
+#include "ts2020.h"
+#include "ds3000.h"
+#include "stv0900.h"
+#include "stv6110.h"
+#include "stb6100.h"
+#include "stb6100_proc.h"
+#include "m88rs2000.h"
+#include "tda18271.h"
+#include "cxd2820r.h"
+#include "m88ds3103.h"
+
+/* Max transfer size done by I2C transfer functions */
+#define MAX_XFER_SIZE  64
+
+
+#define DW210X_READ_MSG 0
+#define DW210X_WRITE_MSG 1
+
+#define REG_1F_SYMBOLRATE_BYTE0 0x1f
+#define REG_20_SYMBOLRATE_BYTE1 0x20
+#define REG_21_SYMBOLRATE_BYTE2 0x21
+/* on my own*/
+#define DW2102_VOLTAGE_CTRL (0x1800)
+#define SU3000_STREAM_CTRL (0x1900)
+#define DW2102_RC_QUERY (0x1a00)
+#define DW2102_LED_CTRL (0x1b00)
+
+#define DW2101_FIRMWARE "dvb-usb-dw2101.fw"
+#define DW2102_FIRMWARE "dvb-usb-dw2102.fw"
+#define DW2104_FIRMWARE "dvb-usb-dw2104.fw"
+#define DW3101_FIRMWARE "dvb-usb-dw3101.fw"
+#define S630_FIRMWARE   "dvb-usb-s630.fw"
+#define S660_FIRMWARE   "dvb-usb-s660.fw"
+#define P1100_FIRMWARE  "dvb-usb-p1100.fw"
+#define P7500_FIRMWARE  "dvb-usb-p7500.fw"
+
+#define	err_str "did not find the firmware file '%s'. You can use <kernel_dir>/scripts/get_dvb_firmware to get the firmware"
+
+struct dw2102_state {
+	u8 initialized;
+	u8 last_lock;
+	u8 data[MAX_XFER_SIZE + 4];
+	struct i2c_client *i2c_client_demod;
+	struct i2c_client *i2c_client_tuner;
+
+	/* fe hook functions*/
+	int (*old_set_voltage)(struct dvb_frontend *f, enum fe_sec_voltage v);
+	int (*fe_read_status)(struct dvb_frontend *fe,
+			      enum fe_status *status);
+};
+
+/* debug */
+static int dvb_usb_dw2102_debug;
+module_param_named(debug, dvb_usb_dw2102_debug, int, 0644);
+MODULE_PARM_DESC(debug, "set debugging level (1=info 2=xfer 4=rc(or-able))."
+						DVB_USB_DEBUG_STATUS);
+
+/* demod probe */
+static int demod_probe = 1;
+module_param_named(demod, demod_probe, int, 0644);
+MODULE_PARM_DESC(demod, "demod to probe (1=cx24116 2=stv0903+stv6110 4=stv0903+stb6100(or-able)).");
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static int dw210x_op_rw(struct usb_device *dev, u8 request, u16 value,
+			u16 index, u8 * data, u16 len, int flags)
+{
+	int ret;
+	u8 *u8buf;
+	unsigned int pipe = (flags == DW210X_READ_MSG) ?
+				usb_rcvctrlpipe(dev, 0) : usb_sndctrlpipe(dev, 0);
+	u8 request_type = (flags == DW210X_READ_MSG) ? USB_DIR_IN : USB_DIR_OUT;
+
+	u8buf = kmalloc(len, GFP_KERNEL);
+	if (!u8buf)
+		return -ENOMEM;
+
+
+	if (flags == DW210X_WRITE_MSG)
+		memcpy(u8buf, data, len);
+	ret = usb_control_msg(dev, pipe, request, request_type | USB_TYPE_VENDOR,
+				value, index , u8buf, len, 2000);
+
+	if (flags == DW210X_READ_MSG)
+		memcpy(data, u8buf, len);
+
+	kfree(u8buf);
+	return ret;
+}
+
+/* I2C */
+static int dw2102_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[],
+		int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	int i = 0;
+	u8 buf6[] = {0x2c, 0x05, 0xc0, 0, 0, 0, 0};
+	u16 value;
+
+	if (!d)
+		return -ENODEV;
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	switch (num) {
+	case 2:
+		/* read stv0299 register */
+		value = msg[0].buf[0];/* register */
+		for (i = 0; i < msg[1].len; i++) {
+			dw210x_op_rw(d->udev, 0xb5, value + i, 0,
+					buf6, 2, DW210X_READ_MSG);
+			msg[1].buf[i] = buf6[0];
+		}
+		break;
+	case 1:
+		switch (msg[0].addr) {
+		case 0x68:
+			/* write to stv0299 register */
+			buf6[0] = 0x2a;
+			buf6[1] = msg[0].buf[0];
+			buf6[2] = msg[0].buf[1];
+			dw210x_op_rw(d->udev, 0xb2, 0, 0,
+					buf6, 3, DW210X_WRITE_MSG);
+			break;
+		case 0x60:
+			if (msg[0].flags == 0) {
+			/* write to tuner pll */
+				buf6[0] = 0x2c;
+				buf6[1] = 5;
+				buf6[2] = 0xc0;
+				buf6[3] = msg[0].buf[0];
+				buf6[4] = msg[0].buf[1];
+				buf6[5] = msg[0].buf[2];
+				buf6[6] = msg[0].buf[3];
+				dw210x_op_rw(d->udev, 0xb2, 0, 0,
+						buf6, 7, DW210X_WRITE_MSG);
+			} else {
+			/* read from tuner */
+				dw210x_op_rw(d->udev, 0xb5, 0, 0,
+						buf6, 1, DW210X_READ_MSG);
+				msg[0].buf[0] = buf6[0];
+			}
+			break;
+		case (DW2102_RC_QUERY):
+			dw210x_op_rw(d->udev, 0xb8, 0, 0,
+					buf6, 2, DW210X_READ_MSG);
+			msg[0].buf[0] = buf6[0];
+			msg[0].buf[1] = buf6[1];
+			break;
+		case (DW2102_VOLTAGE_CTRL):
+			buf6[0] = 0x30;
+			buf6[1] = msg[0].buf[0];
+			dw210x_op_rw(d->udev, 0xb2, 0, 0,
+					buf6, 2, DW210X_WRITE_MSG);
+			break;
+		}
+
+		break;
+	}
+
+	mutex_unlock(&d->i2c_mutex);
+	return num;
+}
+
+static int dw2102_serit_i2c_transfer(struct i2c_adapter *adap,
+						struct i2c_msg msg[], int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	u8 buf6[] = {0, 0, 0, 0, 0, 0, 0};
+
+	if (!d)
+		return -ENODEV;
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	switch (num) {
+	case 2:
+		if (msg[0].len != 1) {
+			warn("i2c rd: len=%d is not 1!\n",
+			     msg[0].len);
+			num = -EOPNOTSUPP;
+			break;
+		}
+
+		if (2 + msg[1].len > sizeof(buf6)) {
+			warn("i2c rd: len=%d is too big!\n",
+			     msg[1].len);
+			num = -EOPNOTSUPP;
+			break;
+		}
+
+		/* read si2109 register by number */
+		buf6[0] = msg[0].addr << 1;
+		buf6[1] = msg[0].len;
+		buf6[2] = msg[0].buf[0];
+		dw210x_op_rw(d->udev, 0xc2, 0, 0,
+				buf6, msg[0].len + 2, DW210X_WRITE_MSG);
+		/* read si2109 register */
+		dw210x_op_rw(d->udev, 0xc3, 0xd0, 0,
+				buf6, msg[1].len + 2, DW210X_READ_MSG);
+		memcpy(msg[1].buf, buf6 + 2, msg[1].len);
+
+		break;
+	case 1:
+		switch (msg[0].addr) {
+		case 0x68:
+			if (2 + msg[0].len > sizeof(buf6)) {
+				warn("i2c wr: len=%d is too big!\n",
+				     msg[0].len);
+				num = -EOPNOTSUPP;
+				break;
+			}
+
+			/* write to si2109 register */
+			buf6[0] = msg[0].addr << 1;
+			buf6[1] = msg[0].len;
+			memcpy(buf6 + 2, msg[0].buf, msg[0].len);
+			dw210x_op_rw(d->udev, 0xc2, 0, 0, buf6,
+					msg[0].len + 2, DW210X_WRITE_MSG);
+			break;
+		case(DW2102_RC_QUERY):
+			dw210x_op_rw(d->udev, 0xb8, 0, 0,
+					buf6, 2, DW210X_READ_MSG);
+			msg[0].buf[0] = buf6[0];
+			msg[0].buf[1] = buf6[1];
+			break;
+		case(DW2102_VOLTAGE_CTRL):
+			buf6[0] = 0x30;
+			buf6[1] = msg[0].buf[0];
+			dw210x_op_rw(d->udev, 0xb2, 0, 0,
+					buf6, 2, DW210X_WRITE_MSG);
+			break;
+		}
+		break;
+	}
+
+	mutex_unlock(&d->i2c_mutex);
+	return num;
+}
+
+static int dw2102_earda_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[], int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	int ret;
+
+	if (!d)
+		return -ENODEV;
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	switch (num) {
+	case 2: {
+		/* read */
+		/* first write first register number */
+		u8 ibuf[MAX_XFER_SIZE], obuf[3];
+
+		if (2 + msg[0].len != sizeof(obuf)) {
+			warn("i2c rd: len=%d is not 1!\n",
+			     msg[0].len);
+			ret = -EOPNOTSUPP;
+			goto unlock;
+		}
+
+		if (2 + msg[1].len > sizeof(ibuf)) {
+			warn("i2c rd: len=%d is too big!\n",
+			     msg[1].len);
+			ret = -EOPNOTSUPP;
+			goto unlock;
+		}
+
+		obuf[0] = msg[0].addr << 1;
+		obuf[1] = msg[0].len;
+		obuf[2] = msg[0].buf[0];
+		dw210x_op_rw(d->udev, 0xc2, 0, 0,
+				obuf, msg[0].len + 2, DW210X_WRITE_MSG);
+		/* second read registers */
+		dw210x_op_rw(d->udev, 0xc3, 0xd1 , 0,
+				ibuf, msg[1].len + 2, DW210X_READ_MSG);
+		memcpy(msg[1].buf, ibuf + 2, msg[1].len);
+
+		break;
+	}
+	case 1:
+		switch (msg[0].addr) {
+		case 0x68: {
+			/* write to register */
+			u8 obuf[MAX_XFER_SIZE];
+
+			if (2 + msg[0].len > sizeof(obuf)) {
+				warn("i2c wr: len=%d is too big!\n",
+				     msg[1].len);
+				ret = -EOPNOTSUPP;
+				goto unlock;
+			}
+
+			obuf[0] = msg[0].addr << 1;
+			obuf[1] = msg[0].len;
+			memcpy(obuf + 2, msg[0].buf, msg[0].len);
+			dw210x_op_rw(d->udev, 0xc2, 0, 0,
+					obuf, msg[0].len + 2, DW210X_WRITE_MSG);
+			break;
+		}
+		case 0x61: {
+			/* write to tuner */
+			u8 obuf[MAX_XFER_SIZE];
+
+			if (2 + msg[0].len > sizeof(obuf)) {
+				warn("i2c wr: len=%d is too big!\n",
+				     msg[1].len);
+				ret = -EOPNOTSUPP;
+				goto unlock;
+			}
+
+			obuf[0] = msg[0].addr << 1;
+			obuf[1] = msg[0].len;
+			memcpy(obuf + 2, msg[0].buf, msg[0].len);
+			dw210x_op_rw(d->udev, 0xc2, 0, 0,
+					obuf, msg[0].len + 2, DW210X_WRITE_MSG);
+			break;
+		}
+		case(DW2102_RC_QUERY): {
+			u8 ibuf[2];
+			dw210x_op_rw(d->udev, 0xb8, 0, 0,
+					ibuf, 2, DW210X_READ_MSG);
+			memcpy(msg[0].buf, ibuf , 2);
+			break;
+		}
+		case(DW2102_VOLTAGE_CTRL): {
+			u8 obuf[2];
+			obuf[0] = 0x30;
+			obuf[1] = msg[0].buf[0];
+			dw210x_op_rw(d->udev, 0xb2, 0, 0,
+					obuf, 2, DW210X_WRITE_MSG);
+			break;
+		}
+		}
+
+		break;
+	}
+	ret = num;
+
+unlock:
+	mutex_unlock(&d->i2c_mutex);
+	return ret;
+}
+
+static int dw2104_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[], int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	int len, i, j, ret;
+
+	if (!d)
+		return -ENODEV;
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	for (j = 0; j < num; j++) {
+		switch (msg[j].addr) {
+		case(DW2102_RC_QUERY): {
+			u8 ibuf[2];
+			dw210x_op_rw(d->udev, 0xb8, 0, 0,
+					ibuf, 2, DW210X_READ_MSG);
+			memcpy(msg[j].buf, ibuf , 2);
+			break;
+		}
+		case(DW2102_VOLTAGE_CTRL): {
+			u8 obuf[2];
+			obuf[0] = 0x30;
+			obuf[1] = msg[j].buf[0];
+			dw210x_op_rw(d->udev, 0xb2, 0, 0,
+					obuf, 2, DW210X_WRITE_MSG);
+			break;
+		}
+		/*case 0x55: cx24116
+		case 0x6a: stv0903
+		case 0x68: ds3000, stv0903
+		case 0x60: ts2020, stv6110, stb6100 */
+		default: {
+			if (msg[j].flags == I2C_M_RD) {
+				/* read registers */
+				u8  ibuf[MAX_XFER_SIZE];
+
+				if (2 + msg[j].len > sizeof(ibuf)) {
+					warn("i2c rd: len=%d is too big!\n",
+					     msg[j].len);
+					ret = -EOPNOTSUPP;
+					goto unlock;
+				}
+
+				dw210x_op_rw(d->udev, 0xc3,
+						(msg[j].addr << 1) + 1, 0,
+						ibuf, msg[j].len + 2,
+						DW210X_READ_MSG);
+				memcpy(msg[j].buf, ibuf + 2, msg[j].len);
+				mdelay(10);
+			} else if (((msg[j].buf[0] == 0xb0) &&
+						(msg[j].addr == 0x68)) ||
+						((msg[j].buf[0] == 0xf7) &&
+						(msg[j].addr == 0x55))) {
+				/* write firmware */
+				u8 obuf[19];
+				obuf[0] = msg[j].addr << 1;
+				obuf[1] = (msg[j].len > 15 ? 17 : msg[j].len);
+				obuf[2] = msg[j].buf[0];
+				len = msg[j].len - 1;
+				i = 1;
+				do {
+					memcpy(obuf + 3, msg[j].buf + i,
+							(len > 16 ? 16 : len));
+					dw210x_op_rw(d->udev, 0xc2, 0, 0,
+						obuf, (len > 16 ? 16 : len) + 3,
+						DW210X_WRITE_MSG);
+					i += 16;
+					len -= 16;
+				} while (len > 0);
+			} else {
+				/* write registers */
+				u8 obuf[MAX_XFER_SIZE];
+
+				if (2 + msg[j].len > sizeof(obuf)) {
+					warn("i2c wr: len=%d is too big!\n",
+					     msg[j].len);
+					ret = -EOPNOTSUPP;
+					goto unlock;
+				}
+
+				obuf[0] = msg[j].addr << 1;
+				obuf[1] = msg[j].len;
+				memcpy(obuf + 2, msg[j].buf, msg[j].len);
+				dw210x_op_rw(d->udev, 0xc2, 0, 0,
+						obuf, msg[j].len + 2,
+						DW210X_WRITE_MSG);
+			}
+			break;
+		}
+		}
+
+	}
+	ret = num;
+
+unlock:
+	mutex_unlock(&d->i2c_mutex);
+	return ret;
+}
+
+static int dw3101_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[],
+								int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	int ret;
+	int i;
+
+	if (!d)
+		return -ENODEV;
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	switch (num) {
+	case 2: {
+		/* read */
+		/* first write first register number */
+		u8 ibuf[MAX_XFER_SIZE], obuf[3];
+
+		if (2 + msg[0].len != sizeof(obuf)) {
+			warn("i2c rd: len=%d is not 1!\n",
+			     msg[0].len);
+			ret = -EOPNOTSUPP;
+			goto unlock;
+		}
+		if (2 + msg[1].len > sizeof(ibuf)) {
+			warn("i2c rd: len=%d is too big!\n",
+			     msg[1].len);
+			ret = -EOPNOTSUPP;
+			goto unlock;
+		}
+		obuf[0] = msg[0].addr << 1;
+		obuf[1] = msg[0].len;
+		obuf[2] = msg[0].buf[0];
+		dw210x_op_rw(d->udev, 0xc2, 0, 0,
+				obuf, msg[0].len + 2, DW210X_WRITE_MSG);
+		/* second read registers */
+		dw210x_op_rw(d->udev, 0xc3, 0x19 , 0,
+				ibuf, msg[1].len + 2, DW210X_READ_MSG);
+		memcpy(msg[1].buf, ibuf + 2, msg[1].len);
+
+		break;
+	}
+	case 1:
+		switch (msg[0].addr) {
+		case 0x60:
+		case 0x0c: {
+			/* write to register */
+			u8 obuf[MAX_XFER_SIZE];
+
+			if (2 + msg[0].len > sizeof(obuf)) {
+				warn("i2c wr: len=%d is too big!\n",
+				     msg[0].len);
+				ret = -EOPNOTSUPP;
+				goto unlock;
+			}
+			obuf[0] = msg[0].addr << 1;
+			obuf[1] = msg[0].len;
+			memcpy(obuf + 2, msg[0].buf, msg[0].len);
+			dw210x_op_rw(d->udev, 0xc2, 0, 0,
+					obuf, msg[0].len + 2, DW210X_WRITE_MSG);
+			break;
+		}
+		case(DW2102_RC_QUERY): {
+			u8 ibuf[2];
+			dw210x_op_rw(d->udev, 0xb8, 0, 0,
+					ibuf, 2, DW210X_READ_MSG);
+			memcpy(msg[0].buf, ibuf , 2);
+			break;
+		}
+		}
+
+		break;
+	}
+
+	for (i = 0; i < num; i++) {
+		deb_xfer("%02x:%02x: %s ", i, msg[i].addr,
+				msg[i].flags == 0 ? ">>>" : "<<<");
+		debug_dump(msg[i].buf, msg[i].len, deb_xfer);
+	}
+	ret = num;
+
+unlock:
+	mutex_unlock(&d->i2c_mutex);
+	return ret;
+}
+
+static int s6x0_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[],
+								int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	struct usb_device *udev;
+	int len, i, j, ret;
+
+	if (!d)
+		return -ENODEV;
+	udev = d->udev;
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	for (j = 0; j < num; j++) {
+		switch (msg[j].addr) {
+		case (DW2102_RC_QUERY): {
+			u8 ibuf[5];
+			dw210x_op_rw(d->udev, 0xb8, 0, 0,
+					ibuf, 5, DW210X_READ_MSG);
+			memcpy(msg[j].buf, ibuf + 3, 2);
+			break;
+		}
+		case (DW2102_VOLTAGE_CTRL): {
+			u8 obuf[2];
+
+			obuf[0] = 1;
+			obuf[1] = msg[j].buf[1];/* off-on */
+			dw210x_op_rw(d->udev, 0x8a, 0, 0,
+					obuf, 2, DW210X_WRITE_MSG);
+			obuf[0] = 3;
+			obuf[1] = msg[j].buf[0];/* 13v-18v */
+			dw210x_op_rw(d->udev, 0x8a, 0, 0,
+					obuf, 2, DW210X_WRITE_MSG);
+			break;
+		}
+		case (DW2102_LED_CTRL): {
+			u8 obuf[2];
+
+			obuf[0] = 5;
+			obuf[1] = msg[j].buf[0];
+			dw210x_op_rw(d->udev, 0x8a, 0, 0,
+					obuf, 2, DW210X_WRITE_MSG);
+			break;
+		}
+		/*case 0x55: cx24116
+		case 0x6a: stv0903
+		case 0x68: ds3000, stv0903, rs2000
+		case 0x60: ts2020, stv6110, stb6100
+		case 0xa0: eeprom */
+		default: {
+			if (msg[j].flags == I2C_M_RD) {
+				/* read registers */
+				u8 ibuf[MAX_XFER_SIZE];
+
+				if (msg[j].len > sizeof(ibuf)) {
+					warn("i2c rd: len=%d is too big!\n",
+					     msg[j].len);
+					ret = -EOPNOTSUPP;
+					goto unlock;
+				}
+
+				dw210x_op_rw(d->udev, 0x91, 0, 0,
+						ibuf, msg[j].len,
+						DW210X_READ_MSG);
+				memcpy(msg[j].buf, ibuf, msg[j].len);
+				break;
+			} else if ((msg[j].buf[0] == 0xb0) &&
+						(msg[j].addr == 0x68)) {
+				/* write firmware */
+				u8 obuf[19];
+				obuf[0] = (msg[j].len > 16 ?
+						18 : msg[j].len + 1);
+				obuf[1] = msg[j].addr << 1;
+				obuf[2] = msg[j].buf[0];
+				len = msg[j].len - 1;
+				i = 1;
+				do {
+					memcpy(obuf + 3, msg[j].buf + i,
+							(len > 16 ? 16 : len));
+					dw210x_op_rw(d->udev, 0x80, 0, 0,
+						obuf, (len > 16 ? 16 : len) + 3,
+						DW210X_WRITE_MSG);
+					i += 16;
+					len -= 16;
+				} while (len > 0);
+			} else if (j < (num - 1)) {
+				/* write register addr before read */
+				u8 obuf[MAX_XFER_SIZE];
+
+				if (2 + msg[j].len > sizeof(obuf)) {
+					warn("i2c wr: len=%d is too big!\n",
+					     msg[j].len);
+					ret = -EOPNOTSUPP;
+					goto unlock;
+				}
+
+				obuf[0] = msg[j + 1].len;
+				obuf[1] = (msg[j].addr << 1);
+				memcpy(obuf + 2, msg[j].buf, msg[j].len);
+				dw210x_op_rw(d->udev,
+						le16_to_cpu(udev->descriptor.idProduct) ==
+						0x7500 ? 0x92 : 0x90, 0, 0,
+						obuf, msg[j].len + 2,
+						DW210X_WRITE_MSG);
+				break;
+			} else {
+				/* write registers */
+				u8 obuf[MAX_XFER_SIZE];
+
+				if (2 + msg[j].len > sizeof(obuf)) {
+					warn("i2c wr: len=%d is too big!\n",
+					     msg[j].len);
+					ret = -EOPNOTSUPP;
+					goto unlock;
+				}
+				obuf[0] = msg[j].len + 1;
+				obuf[1] = (msg[j].addr << 1);
+				memcpy(obuf + 2, msg[j].buf, msg[j].len);
+				dw210x_op_rw(d->udev, 0x80, 0, 0,
+						obuf, msg[j].len + 2,
+						DW210X_WRITE_MSG);
+				break;
+			}
+			break;
+		}
+		}
+	}
+	ret = num;
+
+unlock:
+	mutex_unlock(&d->i2c_mutex);
+	return ret;
+}
+
+static int su3000_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg msg[],
+								int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	struct dw2102_state *state;
+
+	if (!d)
+		return -ENODEV;
+
+	state = d->priv;
+
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+	if (mutex_lock_interruptible(&d->data_mutex) < 0) {
+		mutex_unlock(&d->i2c_mutex);
+		return -EAGAIN;
+	}
+
+	switch (num) {
+	case 1:
+		switch (msg[0].addr) {
+		case SU3000_STREAM_CTRL:
+			state->data[0] = msg[0].buf[0] + 0x36;
+			state->data[1] = 3;
+			state->data[2] = 0;
+			if (dvb_usb_generic_rw(d, state->data, 3,
+					state->data, 0, 0) < 0)
+				err("i2c transfer failed.");
+			break;
+		case DW2102_RC_QUERY:
+			state->data[0] = 0x10;
+			if (dvb_usb_generic_rw(d, state->data, 1,
+					state->data, 2, 0) < 0)
+				err("i2c transfer failed.");
+			msg[0].buf[1] = state->data[0];
+			msg[0].buf[0] = state->data[1];
+			break;
+		default:
+			if (3 + msg[0].len > sizeof(state->data)) {
+				warn("i2c wr: len=%d is too big!\n",
+				     msg[0].len);
+				num = -EOPNOTSUPP;
+				break;
+			}
+
+			/* always i2c write*/
+			state->data[0] = 0x08;
+			state->data[1] = msg[0].addr;
+			state->data[2] = msg[0].len;
+
+			memcpy(&state->data[3], msg[0].buf, msg[0].len);
+
+			if (dvb_usb_generic_rw(d, state->data, msg[0].len + 3,
+						state->data, 1, 0) < 0)
+				err("i2c transfer failed.");
+
+		}
+		break;
+	case 2:
+		/* always i2c read */
+		if (4 + msg[0].len > sizeof(state->data)) {
+			warn("i2c rd: len=%d is too big!\n",
+			     msg[0].len);
+			num = -EOPNOTSUPP;
+			break;
+		}
+		if (1 + msg[1].len > sizeof(state->data)) {
+			warn("i2c rd: len=%d is too big!\n",
+			     msg[1].len);
+			num = -EOPNOTSUPP;
+			break;
+		}
+
+		state->data[0] = 0x09;
+		state->data[1] = msg[0].len;
+		state->data[2] = msg[1].len;
+		state->data[3] = msg[0].addr;
+		memcpy(&state->data[4], msg[0].buf, msg[0].len);
+
+		if (dvb_usb_generic_rw(d, state->data, msg[0].len + 4,
+					state->data, msg[1].len + 1, 0) < 0)
+			err("i2c transfer failed.");
+
+		memcpy(msg[1].buf, &state->data[1], msg[1].len);
+		break;
+	default:
+		warn("more than 2 i2c messages at a time is not handled yet.");
+		break;
+	}
+	mutex_unlock(&d->data_mutex);
+	mutex_unlock(&d->i2c_mutex);
+	return num;
+}
+
+static u32 dw210x_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm dw2102_i2c_algo = {
+	.master_xfer = dw2102_i2c_transfer,
+	.functionality = dw210x_i2c_func,
+};
+
+static struct i2c_algorithm dw2102_serit_i2c_algo = {
+	.master_xfer = dw2102_serit_i2c_transfer,
+	.functionality = dw210x_i2c_func,
+};
+
+static struct i2c_algorithm dw2102_earda_i2c_algo = {
+	.master_xfer = dw2102_earda_i2c_transfer,
+	.functionality = dw210x_i2c_func,
+};
+
+static struct i2c_algorithm dw2104_i2c_algo = {
+	.master_xfer = dw2104_i2c_transfer,
+	.functionality = dw210x_i2c_func,
+};
+
+static struct i2c_algorithm dw3101_i2c_algo = {
+	.master_xfer = dw3101_i2c_transfer,
+	.functionality = dw210x_i2c_func,
+};
+
+static struct i2c_algorithm s6x0_i2c_algo = {
+	.master_xfer = s6x0_i2c_transfer,
+	.functionality = dw210x_i2c_func,
+};
+
+static struct i2c_algorithm su3000_i2c_algo = {
+	.master_xfer = su3000_i2c_transfer,
+	.functionality = dw210x_i2c_func,
+};
+
+static int dw210x_read_mac_address(struct dvb_usb_device *d, u8 mac[6])
+{
+	int i;
+	u8 ibuf[] = {0, 0};
+	u8 eeprom[256], eepromline[16];
+
+	for (i = 0; i < 256; i++) {
+		if (dw210x_op_rw(d->udev, 0xb6, 0xa0 , i, ibuf, 2, DW210X_READ_MSG) < 0) {
+			err("read eeprom failed.");
+			return -1;
+		} else {
+			eepromline[i%16] = ibuf[0];
+			eeprom[i] = ibuf[0];
+		}
+		if ((i % 16) == 15) {
+			deb_xfer("%02x: ", i - 15);
+			debug_dump(eepromline, 16, deb_xfer);
+		}
+	}
+
+	memcpy(mac, eeprom + 8, 6);
+	return 0;
+};
+
+static int s6x0_read_mac_address(struct dvb_usb_device *d, u8 mac[6])
+{
+	int i, ret;
+	u8 ibuf[] = { 0 }, obuf[] = { 0 };
+	u8 eeprom[256], eepromline[16];
+	struct i2c_msg msg[] = {
+		{
+			.addr = 0xa0 >> 1,
+			.flags = 0,
+			.buf = obuf,
+			.len = 1,
+		}, {
+			.addr = 0xa0 >> 1,
+			.flags = I2C_M_RD,
+			.buf = ibuf,
+			.len = 1,
+		}
+	};
+
+	for (i = 0; i < 256; i++) {
+		obuf[0] = i;
+		ret = s6x0_i2c_transfer(&d->i2c_adap, msg, 2);
+		if (ret != 2) {
+			err("read eeprom failed.");
+			return -1;
+		} else {
+			eepromline[i % 16] = ibuf[0];
+			eeprom[i] = ibuf[0];
+		}
+
+		if ((i % 16) == 15) {
+			deb_xfer("%02x: ", i - 15);
+			debug_dump(eepromline, 16, deb_xfer);
+		}
+	}
+
+	memcpy(mac, eeprom + 16, 6);
+	return 0;
+};
+
+static int su3000_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff)
+{
+	static u8 command_start[] = {0x00};
+	static u8 command_stop[] = {0x01};
+	struct i2c_msg msg = {
+		.addr = SU3000_STREAM_CTRL,
+		.flags = 0,
+		.buf = onoff ? command_start : command_stop,
+		.len = 1
+	};
+
+	i2c_transfer(&adap->dev->i2c_adap, &msg, 1);
+
+	return 0;
+}
+
+static int su3000_power_ctrl(struct dvb_usb_device *d, int i)
+{
+	struct dw2102_state *state = (struct dw2102_state *)d->priv;
+	int ret = 0;
+
+	info("%s: %d, initialized %d", __func__, i, state->initialized);
+
+	if (i && !state->initialized) {
+		mutex_lock(&d->data_mutex);
+
+		state->data[0] = 0xde;
+		state->data[1] = 0;
+
+		state->initialized = 1;
+		/* reset board */
+		ret = dvb_usb_generic_rw(d, state->data, 2, NULL, 0, 0);
+		mutex_unlock(&d->data_mutex);
+	}
+
+	return ret;
+}
+
+static int su3000_read_mac_address(struct dvb_usb_device *d, u8 mac[6])
+{
+	int i;
+	u8 obuf[] = { 0x1f, 0xf0 };
+	u8 ibuf[] = { 0 };
+	struct i2c_msg msg[] = {
+		{
+			.addr = 0x51,
+			.flags = 0,
+			.buf = obuf,
+			.len = 2,
+		}, {
+			.addr = 0x51,
+			.flags = I2C_M_RD,
+			.buf = ibuf,
+			.len = 1,
+
+		}
+	};
+
+	for (i = 0; i < 6; i++) {
+		obuf[1] = 0xf0 + i;
+		if (i2c_transfer(&d->i2c_adap, msg, 2) != 2)
+			break;
+		else
+			mac[i] = ibuf[0];
+	}
+
+	return 0;
+}
+
+static int su3000_identify_state(struct usb_device *udev,
+				 struct dvb_usb_device_properties *props,
+				 struct dvb_usb_device_description **desc,
+				 int *cold)
+{
+	info("%s", __func__);
+
+	*cold = 0;
+	return 0;
+}
+
+static int dw210x_set_voltage(struct dvb_frontend *fe,
+			      enum fe_sec_voltage voltage)
+{
+	static u8 command_13v[] = {0x00, 0x01};
+	static u8 command_18v[] = {0x01, 0x01};
+	static u8 command_off[] = {0x00, 0x00};
+	struct i2c_msg msg = {
+		.addr = DW2102_VOLTAGE_CTRL,
+		.flags = 0,
+		.buf = command_off,
+		.len = 2,
+	};
+
+	struct dvb_usb_adapter *udev_adap =
+		(struct dvb_usb_adapter *)(fe->dvb->priv);
+	if (voltage == SEC_VOLTAGE_18)
+		msg.buf = command_18v;
+	else if (voltage == SEC_VOLTAGE_13)
+		msg.buf = command_13v;
+
+	i2c_transfer(&udev_adap->dev->i2c_adap, &msg, 1);
+
+	return 0;
+}
+
+static int s660_set_voltage(struct dvb_frontend *fe,
+			    enum fe_sec_voltage voltage)
+{
+	struct dvb_usb_adapter *d =
+		(struct dvb_usb_adapter *)(fe->dvb->priv);
+	struct dw2102_state *st = (struct dw2102_state *)d->dev->priv;
+
+	dw210x_set_voltage(fe, voltage);
+	if (st->old_set_voltage)
+		st->old_set_voltage(fe, voltage);
+
+	return 0;
+}
+
+static void dw210x_led_ctrl(struct dvb_frontend *fe, int offon)
+{
+	static u8 led_off[] = { 0 };
+	static u8 led_on[] = { 1 };
+	struct i2c_msg msg = {
+		.addr = DW2102_LED_CTRL,
+		.flags = 0,
+		.buf = led_off,
+		.len = 1
+	};
+	struct dvb_usb_adapter *udev_adap =
+		(struct dvb_usb_adapter *)(fe->dvb->priv);
+
+	if (offon)
+		msg.buf = led_on;
+	i2c_transfer(&udev_adap->dev->i2c_adap, &msg, 1);
+}
+
+static int tt_s2_4600_read_status(struct dvb_frontend *fe,
+				  enum fe_status *status)
+{
+	struct dvb_usb_adapter *d =
+		(struct dvb_usb_adapter *)(fe->dvb->priv);
+	struct dw2102_state *st = (struct dw2102_state *)d->dev->priv;
+	int ret;
+
+	ret = st->fe_read_status(fe, status);
+
+	/* resync slave fifo when signal change from unlock to lock */
+	if ((*status & FE_HAS_LOCK) && (!st->last_lock))
+		su3000_streaming_ctrl(d, 1);
+
+	st->last_lock = (*status & FE_HAS_LOCK) ? 1 : 0;
+	return ret;
+}
+
+static struct stv0299_config sharp_z0194a_config = {
+	.demod_address = 0x68,
+	.inittab = sharp_z0194a_inittab,
+	.mclk = 88000000UL,
+	.invert = 1,
+	.skip_reinit = 0,
+	.lock_output = STV0299_LOCKOUTPUT_1,
+	.volt13_op0_op1 = STV0299_VOLT13_OP1,
+	.min_delay_ms = 100,
+	.set_symbol_rate = sharp_z0194a_set_symbol_rate,
+};
+
+static struct cx24116_config dw2104_config = {
+	.demod_address = 0x55,
+	.mpg_clk_pos_pol = 0x01,
+};
+
+static struct si21xx_config serit_sp1511lhb_config = {
+	.demod_address = 0x68,
+	.min_delay_ms = 100,
+
+};
+
+static struct tda10023_config dw3101_tda10023_config = {
+	.demod_address = 0x0c,
+	.invert = 1,
+};
+
+static struct mt312_config zl313_config = {
+	.demod_address = 0x0e,
+};
+
+static struct ds3000_config dw2104_ds3000_config = {
+	.demod_address = 0x68,
+};
+
+static struct ts2020_config dw2104_ts2020_config = {
+	.tuner_address = 0x60,
+	.clk_out_div = 1,
+	.frequency_div = 1060000,
+};
+
+static struct ds3000_config s660_ds3000_config = {
+	.demod_address = 0x68,
+	.ci_mode = 1,
+	.set_lock_led = dw210x_led_ctrl,
+};
+
+static struct ts2020_config s660_ts2020_config = {
+	.tuner_address = 0x60,
+	.clk_out_div = 1,
+	.frequency_div = 1146000,
+};
+
+static struct stv0900_config dw2104a_stv0900_config = {
+	.demod_address = 0x6a,
+	.demod_mode = 0,
+	.xtal = 27000000,
+	.clkmode = 3,/* 0-CLKI, 2-XTALI, else AUTO */
+	.diseqc_mode = 2,/* 2/3 PWM */
+	.tun1_maddress = 0,/* 0x60 */
+	.tun1_adc = 0,/* 2 Vpp */
+	.path1_mode = 3,
+};
+
+static struct stb6100_config dw2104a_stb6100_config = {
+	.tuner_address = 0x60,
+	.refclock = 27000000,
+};
+
+static struct stv0900_config dw2104_stv0900_config = {
+	.demod_address = 0x68,
+	.demod_mode = 0,
+	.xtal = 8000000,
+	.clkmode = 3,
+	.diseqc_mode = 2,
+	.tun1_maddress = 0,
+	.tun1_adc = 1,/* 1 Vpp */
+	.path1_mode = 3,
+};
+
+static struct stv6110_config dw2104_stv6110_config = {
+	.i2c_address = 0x60,
+	.mclk = 16000000,
+	.clk_div = 1,
+};
+
+static struct stv0900_config prof_7500_stv0900_config = {
+	.demod_address = 0x6a,
+	.demod_mode = 0,
+	.xtal = 27000000,
+	.clkmode = 3,/* 0-CLKI, 2-XTALI, else AUTO */
+	.diseqc_mode = 2,/* 2/3 PWM */
+	.tun1_maddress = 0,/* 0x60 */
+	.tun1_adc = 0,/* 2 Vpp */
+	.path1_mode = 3,
+	.tun1_type = 3,
+	.set_lock_led = dw210x_led_ctrl,
+};
+
+static struct ds3000_config su3000_ds3000_config = {
+	.demod_address = 0x68,
+	.ci_mode = 1,
+	.set_lock_led = dw210x_led_ctrl,
+};
+
+static struct cxd2820r_config cxd2820r_config = {
+	.i2c_address = 0x6c, /* (0xd8 >> 1) */
+	.ts_mode = 0x38,
+	.ts_clock_inv = 1,
+};
+
+static struct tda18271_config tda18271_config = {
+	.output_opt = TDA18271_OUTPUT_LT_OFF,
+	.gate = TDA18271_GATE_DIGITAL,
+};
+
+static u8 m88rs2000_inittab[] = {
+	DEMOD_WRITE, 0x9a, 0x30,
+	DEMOD_WRITE, 0x00, 0x01,
+	WRITE_DELAY, 0x19, 0x00,
+	DEMOD_WRITE, 0x00, 0x00,
+	DEMOD_WRITE, 0x9a, 0xb0,
+	DEMOD_WRITE, 0x81, 0xc1,
+	DEMOD_WRITE, 0x81, 0x81,
+	DEMOD_WRITE, 0x86, 0xc6,
+	DEMOD_WRITE, 0x9a, 0x30,
+	DEMOD_WRITE, 0xf0, 0x80,
+	DEMOD_WRITE, 0xf1, 0xbf,
+	DEMOD_WRITE, 0xb0, 0x45,
+	DEMOD_WRITE, 0xb2, 0x01,
+	DEMOD_WRITE, 0x9a, 0xb0,
+	0xff, 0xaa, 0xff
+};
+
+static struct m88rs2000_config s421_m88rs2000_config = {
+	.demod_addr = 0x68,
+	.inittab = m88rs2000_inittab,
+};
+
+static int dw2104_frontend_attach(struct dvb_usb_adapter *d)
+{
+	struct dvb_tuner_ops *tuner_ops = NULL;
+
+	if (demod_probe & 4) {
+		d->fe_adap[0].fe = dvb_attach(stv0900_attach, &dw2104a_stv0900_config,
+				&d->dev->i2c_adap, 0);
+		if (d->fe_adap[0].fe != NULL) {
+			if (dvb_attach(stb6100_attach, d->fe_adap[0].fe,
+					&dw2104a_stb6100_config,
+					&d->dev->i2c_adap)) {
+				tuner_ops = &d->fe_adap[0].fe->ops.tuner_ops;
+				tuner_ops->set_frequency = stb6100_set_freq;
+				tuner_ops->get_frequency = stb6100_get_freq;
+				tuner_ops->set_bandwidth = stb6100_set_bandw;
+				tuner_ops->get_bandwidth = stb6100_get_bandw;
+				d->fe_adap[0].fe->ops.set_voltage = dw210x_set_voltage;
+				info("Attached STV0900+STB6100!");
+				return 0;
+			}
+		}
+	}
+
+	if (demod_probe & 2) {
+		d->fe_adap[0].fe = dvb_attach(stv0900_attach, &dw2104_stv0900_config,
+				&d->dev->i2c_adap, 0);
+		if (d->fe_adap[0].fe != NULL) {
+			if (dvb_attach(stv6110_attach, d->fe_adap[0].fe,
+					&dw2104_stv6110_config,
+					&d->dev->i2c_adap)) {
+				d->fe_adap[0].fe->ops.set_voltage = dw210x_set_voltage;
+				info("Attached STV0900+STV6110A!");
+				return 0;
+			}
+		}
+	}
+
+	if (demod_probe & 1) {
+		d->fe_adap[0].fe = dvb_attach(cx24116_attach, &dw2104_config,
+				&d->dev->i2c_adap);
+		if (d->fe_adap[0].fe != NULL) {
+			d->fe_adap[0].fe->ops.set_voltage = dw210x_set_voltage;
+			info("Attached cx24116!");
+			return 0;
+		}
+	}
+
+	d->fe_adap[0].fe = dvb_attach(ds3000_attach, &dw2104_ds3000_config,
+			&d->dev->i2c_adap);
+	if (d->fe_adap[0].fe != NULL) {
+		dvb_attach(ts2020_attach, d->fe_adap[0].fe,
+			&dw2104_ts2020_config, &d->dev->i2c_adap);
+		d->fe_adap[0].fe->ops.set_voltage = dw210x_set_voltage;
+		info("Attached DS3000!");
+		return 0;
+	}
+
+	return -EIO;
+}
+
+static struct dvb_usb_device_properties dw2102_properties;
+static struct dvb_usb_device_properties dw2104_properties;
+static struct dvb_usb_device_properties s6x0_properties;
+
+static int dw2102_frontend_attach(struct dvb_usb_adapter *d)
+{
+	if (dw2102_properties.i2c_algo == &dw2102_serit_i2c_algo) {
+		/*dw2102_properties.adapter->tuner_attach = NULL;*/
+		d->fe_adap[0].fe = dvb_attach(si21xx_attach, &serit_sp1511lhb_config,
+					&d->dev->i2c_adap);
+		if (d->fe_adap[0].fe != NULL) {
+			d->fe_adap[0].fe->ops.set_voltage = dw210x_set_voltage;
+			info("Attached si21xx!");
+			return 0;
+		}
+	}
+
+	if (dw2102_properties.i2c_algo == &dw2102_earda_i2c_algo) {
+		d->fe_adap[0].fe = dvb_attach(stv0288_attach, &earda_config,
+					&d->dev->i2c_adap);
+		if (d->fe_adap[0].fe != NULL) {
+			if (dvb_attach(stb6000_attach, d->fe_adap[0].fe, 0x61,
+					&d->dev->i2c_adap)) {
+				d->fe_adap[0].fe->ops.set_voltage = dw210x_set_voltage;
+				info("Attached stv0288!");
+				return 0;
+			}
+		}
+	}
+
+	if (dw2102_properties.i2c_algo == &dw2102_i2c_algo) {
+		/*dw2102_properties.adapter->tuner_attach = dw2102_tuner_attach;*/
+		d->fe_adap[0].fe = dvb_attach(stv0299_attach, &sharp_z0194a_config,
+					&d->dev->i2c_adap);
+		if (d->fe_adap[0].fe != NULL) {
+			d->fe_adap[0].fe->ops.set_voltage = dw210x_set_voltage;
+			info("Attached stv0299!");
+			return 0;
+		}
+	}
+	return -EIO;
+}
+
+static int dw3101_frontend_attach(struct dvb_usb_adapter *d)
+{
+	d->fe_adap[0].fe = dvb_attach(tda10023_attach, &dw3101_tda10023_config,
+				&d->dev->i2c_adap, 0x48);
+	if (d->fe_adap[0].fe != NULL) {
+		info("Attached tda10023!");
+		return 0;
+	}
+	return -EIO;
+}
+
+static int zl100313_frontend_attach(struct dvb_usb_adapter *d)
+{
+	d->fe_adap[0].fe = dvb_attach(mt312_attach, &zl313_config,
+			&d->dev->i2c_adap);
+	if (d->fe_adap[0].fe != NULL) {
+		if (dvb_attach(zl10039_attach, d->fe_adap[0].fe, 0x60,
+				&d->dev->i2c_adap)) {
+			d->fe_adap[0].fe->ops.set_voltage = dw210x_set_voltage;
+			info("Attached zl100313+zl10039!");
+			return 0;
+		}
+	}
+
+	return -EIO;
+}
+
+static int stv0288_frontend_attach(struct dvb_usb_adapter *d)
+{
+	u8 obuf[] = {7, 1};
+
+	d->fe_adap[0].fe = dvb_attach(stv0288_attach, &earda_config,
+			&d->dev->i2c_adap);
+
+	if (d->fe_adap[0].fe == NULL)
+		return -EIO;
+
+	if (NULL == dvb_attach(stb6000_attach, d->fe_adap[0].fe, 0x61, &d->dev->i2c_adap))
+		return -EIO;
+
+	d->fe_adap[0].fe->ops.set_voltage = dw210x_set_voltage;
+
+	dw210x_op_rw(d->dev->udev, 0x8a, 0, 0, obuf, 2, DW210X_WRITE_MSG);
+
+	info("Attached stv0288+stb6000!");
+
+	return 0;
+
+}
+
+static int ds3000_frontend_attach(struct dvb_usb_adapter *d)
+{
+	struct dw2102_state *st = d->dev->priv;
+	u8 obuf[] = {7, 1};
+
+	d->fe_adap[0].fe = dvb_attach(ds3000_attach, &s660_ds3000_config,
+			&d->dev->i2c_adap);
+
+	if (d->fe_adap[0].fe == NULL)
+		return -EIO;
+
+	dvb_attach(ts2020_attach, d->fe_adap[0].fe, &s660_ts2020_config,
+		&d->dev->i2c_adap);
+
+	st->old_set_voltage = d->fe_adap[0].fe->ops.set_voltage;
+	d->fe_adap[0].fe->ops.set_voltage = s660_set_voltage;
+
+	dw210x_op_rw(d->dev->udev, 0x8a, 0, 0, obuf, 2, DW210X_WRITE_MSG);
+
+	info("Attached ds3000+ts2020!");
+
+	return 0;
+}
+
+static int prof_7500_frontend_attach(struct dvb_usb_adapter *d)
+{
+	u8 obuf[] = {7, 1};
+
+	d->fe_adap[0].fe = dvb_attach(stv0900_attach, &prof_7500_stv0900_config,
+					&d->dev->i2c_adap, 0);
+	if (d->fe_adap[0].fe == NULL)
+		return -EIO;
+
+	d->fe_adap[0].fe->ops.set_voltage = dw210x_set_voltage;
+
+	dw210x_op_rw(d->dev->udev, 0x8a, 0, 0, obuf, 2, DW210X_WRITE_MSG);
+
+	info("Attached STV0900+STB6100A!");
+
+	return 0;
+}
+
+static int su3000_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *d = adap->dev;
+	struct dw2102_state *state = d->priv;
+
+	mutex_lock(&d->data_mutex);
+
+	state->data[0] = 0xe;
+	state->data[1] = 0x80;
+	state->data[2] = 0;
+
+	if (dvb_usb_generic_rw(d, state->data, 3, state->data, 1, 0) < 0)
+		err("command 0x0e transfer failed.");
+
+	state->data[0] = 0xe;
+	state->data[1] = 0x02;
+	state->data[2] = 1;
+
+	if (dvb_usb_generic_rw(d, state->data, 3, state->data, 1, 0) < 0)
+		err("command 0x0e transfer failed.");
+	msleep(300);
+
+	state->data[0] = 0xe;
+	state->data[1] = 0x83;
+	state->data[2] = 0;
+
+	if (dvb_usb_generic_rw(d, state->data, 3, state->data, 1, 0) < 0)
+		err("command 0x0e transfer failed.");
+
+	state->data[0] = 0xe;
+	state->data[1] = 0x83;
+	state->data[2] = 1;
+
+	if (dvb_usb_generic_rw(d, state->data, 3, state->data, 1, 0) < 0)
+		err("command 0x0e transfer failed.");
+
+	state->data[0] = 0x51;
+
+	if (dvb_usb_generic_rw(d, state->data, 1, state->data, 1, 0) < 0)
+		err("command 0x51 transfer failed.");
+
+	mutex_unlock(&d->data_mutex);
+
+	adap->fe_adap[0].fe = dvb_attach(ds3000_attach, &su3000_ds3000_config,
+					&d->i2c_adap);
+	if (adap->fe_adap[0].fe == NULL)
+		return -EIO;
+
+	if (dvb_attach(ts2020_attach, adap->fe_adap[0].fe,
+				&dw2104_ts2020_config,
+				&d->i2c_adap)) {
+		info("Attached DS3000/TS2020!");
+		return 0;
+	}
+
+	info("Failed to attach DS3000/TS2020!");
+	return -EIO;
+}
+
+static int t220_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *d = adap->dev;
+	struct dw2102_state *state = d->priv;
+
+	mutex_lock(&d->data_mutex);
+
+	state->data[0] = 0xe;
+	state->data[1] = 0x87;
+	state->data[2] = 0x0;
+
+	if (dvb_usb_generic_rw(d, state->data, 3, state->data, 1, 0) < 0)
+		err("command 0x0e transfer failed.");
+
+	state->data[0] = 0xe;
+	state->data[1] = 0x86;
+	state->data[2] = 1;
+
+	if (dvb_usb_generic_rw(d, state->data, 3, state->data, 1, 0) < 0)
+		err("command 0x0e transfer failed.");
+
+	state->data[0] = 0xe;
+	state->data[1] = 0x80;
+	state->data[2] = 0;
+
+	if (dvb_usb_generic_rw(d, state->data, 3, state->data, 1, 0) < 0)
+		err("command 0x0e transfer failed.");
+
+	msleep(50);
+
+	state->data[0] = 0xe;
+	state->data[1] = 0x80;
+	state->data[2] = 1;
+
+	if (dvb_usb_generic_rw(d, state->data, 3, state->data, 1, 0) < 0)
+		err("command 0x0e transfer failed.");
+
+	state->data[0] = 0x51;
+
+	if (dvb_usb_generic_rw(d, state->data, 1, state->data, 1, 0) < 0)
+		err("command 0x51 transfer failed.");
+
+	mutex_unlock(&d->data_mutex);
+
+	adap->fe_adap[0].fe = dvb_attach(cxd2820r_attach, &cxd2820r_config,
+					&d->i2c_adap, NULL);
+	if (adap->fe_adap[0].fe != NULL) {
+		if (dvb_attach(tda18271_attach, adap->fe_adap[0].fe, 0x60,
+					&d->i2c_adap, &tda18271_config)) {
+			info("Attached TDA18271HD/CXD2820R!");
+			return 0;
+		}
+	}
+
+	info("Failed to attach TDA18271HD/CXD2820R!");
+	return -EIO;
+}
+
+static int m88rs2000_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *d = adap->dev;
+	struct dw2102_state *state = d->priv;
+
+	mutex_lock(&d->data_mutex);
+
+	state->data[0] = 0x51;
+
+	if (dvb_usb_generic_rw(d, state->data, 1, state->data, 1, 0) < 0)
+		err("command 0x51 transfer failed.");
+
+	mutex_unlock(&d->data_mutex);
+
+	adap->fe_adap[0].fe = dvb_attach(m88rs2000_attach,
+					&s421_m88rs2000_config,
+					&d->i2c_adap);
+
+	if (adap->fe_adap[0].fe == NULL)
+		return -EIO;
+
+	if (dvb_attach(ts2020_attach, adap->fe_adap[0].fe,
+				&dw2104_ts2020_config,
+				&d->i2c_adap)) {
+		info("Attached RS2000/TS2020!");
+		return 0;
+	}
+
+	info("Failed to attach RS2000/TS2020!");
+	return -EIO;
+}
+
+static int tt_s2_4600_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *d = adap->dev;
+	struct dw2102_state *state = d->priv;
+	struct i2c_adapter *i2c_adapter;
+	struct i2c_client *client;
+	struct i2c_board_info board_info;
+	struct m88ds3103_platform_data m88ds3103_pdata = {};
+	struct ts2020_config ts2020_config = {};
+
+	mutex_lock(&d->data_mutex);
+
+	state->data[0] = 0xe;
+	state->data[1] = 0x80;
+	state->data[2] = 0x0;
+
+	if (dvb_usb_generic_rw(d, state->data, 3, state->data, 1, 0) < 0)
+		err("command 0x0e transfer failed.");
+
+	state->data[0] = 0xe;
+	state->data[1] = 0x02;
+	state->data[2] = 1;
+
+	if (dvb_usb_generic_rw(d, state->data, 3, state->data, 1, 0) < 0)
+		err("command 0x0e transfer failed.");
+	msleep(300);
+
+	state->data[0] = 0xe;
+	state->data[1] = 0x83;
+	state->data[2] = 0;
+
+	if (dvb_usb_generic_rw(d, state->data, 3, state->data, 1, 0) < 0)
+		err("command 0x0e transfer failed.");
+
+	state->data[0] = 0xe;
+	state->data[1] = 0x83;
+	state->data[2] = 1;
+
+	if (dvb_usb_generic_rw(d, state->data, 3, state->data, 1, 0) < 0)
+		err("command 0x0e transfer failed.");
+
+	state->data[0] = 0x51;
+
+	if (dvb_usb_generic_rw(d, state->data, 1, state->data, 1, 0) < 0)
+		err("command 0x51 transfer failed.");
+
+	mutex_unlock(&d->data_mutex);
+
+	/* attach demod */
+	m88ds3103_pdata.clk = 27000000;
+	m88ds3103_pdata.i2c_wr_max = 33;
+	m88ds3103_pdata.ts_mode = M88DS3103_TS_CI;
+	m88ds3103_pdata.ts_clk = 16000;
+	m88ds3103_pdata.ts_clk_pol = 0;
+	m88ds3103_pdata.spec_inv = 0;
+	m88ds3103_pdata.agc = 0x99;
+	m88ds3103_pdata.agc_inv = 0;
+	m88ds3103_pdata.clk_out = M88DS3103_CLOCK_OUT_ENABLED;
+	m88ds3103_pdata.envelope_mode = 0;
+	m88ds3103_pdata.lnb_hv_pol = 1;
+	m88ds3103_pdata.lnb_en_pol = 0;
+	memset(&board_info, 0, sizeof(board_info));
+	strlcpy(board_info.type, "m88ds3103", I2C_NAME_SIZE);
+	board_info.addr = 0x68;
+	board_info.platform_data = &m88ds3103_pdata;
+	request_module("m88ds3103");
+	client = i2c_new_device(&d->i2c_adap, &board_info);
+	if (client == NULL || client->dev.driver == NULL)
+		return -ENODEV;
+	if (!try_module_get(client->dev.driver->owner)) {
+		i2c_unregister_device(client);
+		return -ENODEV;
+	}
+	adap->fe_adap[0].fe = m88ds3103_pdata.get_dvb_frontend(client);
+	i2c_adapter = m88ds3103_pdata.get_i2c_adapter(client);
+
+	state->i2c_client_demod = client;
+
+	/* attach tuner */
+	ts2020_config.fe = adap->fe_adap[0].fe;
+	memset(&board_info, 0, sizeof(board_info));
+	strlcpy(board_info.type, "ts2022", I2C_NAME_SIZE);
+	board_info.addr = 0x60;
+	board_info.platform_data = &ts2020_config;
+	request_module("ts2020");
+	client = i2c_new_device(i2c_adapter, &board_info);
+
+	if (client == NULL || client->dev.driver == NULL) {
+		dvb_frontend_detach(adap->fe_adap[0].fe);
+		return -ENODEV;
+	}
+
+	if (!try_module_get(client->dev.driver->owner)) {
+		i2c_unregister_device(client);
+		dvb_frontend_detach(adap->fe_adap[0].fe);
+		return -ENODEV;
+	}
+
+	/* delegate signal strength measurement to tuner */
+	adap->fe_adap[0].fe->ops.read_signal_strength =
+			adap->fe_adap[0].fe->ops.tuner_ops.get_rf_strength;
+
+	state->i2c_client_tuner = client;
+
+	/* hook fe: need to resync the slave fifo when signal locks */
+	state->fe_read_status = adap->fe_adap[0].fe->ops.read_status;
+	adap->fe_adap[0].fe->ops.read_status = tt_s2_4600_read_status;
+
+	state->last_lock = 0;
+
+	return 0;
+}
+
+static int dw2102_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	dvb_attach(dvb_pll_attach, adap->fe_adap[0].fe, 0x60,
+		&adap->dev->i2c_adap, DVB_PLL_OPERA1);
+	return 0;
+}
+
+static int dw3101_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	dvb_attach(dvb_pll_attach, adap->fe_adap[0].fe, 0x60,
+		&adap->dev->i2c_adap, DVB_PLL_TUA6034);
+
+	return 0;
+}
+
+static int dw2102_rc_query(struct dvb_usb_device *d)
+{
+	u8 key[2];
+	struct i2c_msg msg = {
+		.addr = DW2102_RC_QUERY,
+		.flags = I2C_M_RD,
+		.buf = key,
+		.len = 2
+	};
+
+	if (d->props.i2c_algo->master_xfer(&d->i2c_adap, &msg, 1) == 1) {
+		if (msg.buf[0] != 0xff) {
+			deb_rc("%s: rc code: %x, %x\n",
+					__func__, key[0], key[1]);
+			rc_keydown(d->rc_dev, RC_PROTO_UNKNOWN, key[0], 0);
+		}
+	}
+
+	return 0;
+}
+
+static int prof_rc_query(struct dvb_usb_device *d)
+{
+	u8 key[2];
+	struct i2c_msg msg = {
+		.addr = DW2102_RC_QUERY,
+		.flags = I2C_M_RD,
+		.buf = key,
+		.len = 2
+	};
+
+	if (d->props.i2c_algo->master_xfer(&d->i2c_adap, &msg, 1) == 1) {
+		if (msg.buf[0] != 0xff) {
+			deb_rc("%s: rc code: %x, %x\n",
+					__func__, key[0], key[1]);
+			rc_keydown(d->rc_dev, RC_PROTO_UNKNOWN, key[0] ^ 0xff,
+				   0);
+		}
+	}
+
+	return 0;
+}
+
+static int su3000_rc_query(struct dvb_usb_device *d)
+{
+	u8 key[2];
+	struct i2c_msg msg = {
+		.addr = DW2102_RC_QUERY,
+		.flags = I2C_M_RD,
+		.buf = key,
+		.len = 2
+	};
+
+	if (d->props.i2c_algo->master_xfer(&d->i2c_adap, &msg, 1) == 1) {
+		if (msg.buf[0] != 0xff) {
+			deb_rc("%s: rc code: %x, %x\n",
+					__func__, key[0], key[1]);
+			rc_keydown(d->rc_dev, RC_PROTO_RC5,
+				   RC_SCANCODE_RC5(key[1], key[0]), 0);
+		}
+	}
+
+	return 0;
+}
+
+enum dw2102_table_entry {
+	CYPRESS_DW2102,
+	CYPRESS_DW2101,
+	CYPRESS_DW2104,
+	TEVII_S650,
+	TERRATEC_CINERGY_S,
+	CYPRESS_DW3101,
+	TEVII_S630,
+	PROF_1100,
+	TEVII_S660,
+	PROF_7500,
+	GENIATECH_SU3000,
+	TERRATEC_CINERGY_S2,
+	TEVII_S480_1,
+	TEVII_S480_2,
+	X3M_SPC1400HD,
+	TEVII_S421,
+	TEVII_S632,
+	TERRATEC_CINERGY_S2_R2,
+	TERRATEC_CINERGY_S2_R3,
+	TERRATEC_CINERGY_S2_R4,
+	GOTVIEW_SAT_HD,
+	GENIATECH_T220,
+	TECHNOTREND_S2_4600,
+	TEVII_S482_1,
+	TEVII_S482_2,
+	TERRATEC_CINERGY_S2_BOX,
+	TEVII_S662
+};
+
+static struct usb_device_id dw2102_table[] = {
+	[CYPRESS_DW2102] = {USB_DEVICE(USB_VID_CYPRESS, USB_PID_DW2102)},
+	[CYPRESS_DW2101] = {USB_DEVICE(USB_VID_CYPRESS, 0x2101)},
+	[CYPRESS_DW2104] = {USB_DEVICE(USB_VID_CYPRESS, USB_PID_DW2104)},
+	[TEVII_S650] = {USB_DEVICE(0x9022, USB_PID_TEVII_S650)},
+	[TERRATEC_CINERGY_S] = {USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_CINERGY_S)},
+	[CYPRESS_DW3101] = {USB_DEVICE(USB_VID_CYPRESS, USB_PID_DW3101)},
+	[TEVII_S630] = {USB_DEVICE(0x9022, USB_PID_TEVII_S630)},
+	[PROF_1100] = {USB_DEVICE(0x3011, USB_PID_PROF_1100)},
+	[TEVII_S660] = {USB_DEVICE(0x9022, USB_PID_TEVII_S660)},
+	[PROF_7500] = {USB_DEVICE(0x3034, 0x7500)},
+	[GENIATECH_SU3000] = {USB_DEVICE(0x1f4d, 0x3000)},
+	[TERRATEC_CINERGY_S2] = {USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_CINERGY_S2_R1)},
+	[TEVII_S480_1] = {USB_DEVICE(0x9022, USB_PID_TEVII_S480_1)},
+	[TEVII_S480_2] = {USB_DEVICE(0x9022, USB_PID_TEVII_S480_2)},
+	[X3M_SPC1400HD] = {USB_DEVICE(0x1f4d, 0x3100)},
+	[TEVII_S421] = {USB_DEVICE(0x9022, USB_PID_TEVII_S421)},
+	[TEVII_S632] = {USB_DEVICE(0x9022, USB_PID_TEVII_S632)},
+	[TERRATEC_CINERGY_S2_R2] = {USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_CINERGY_S2_R2)},
+	[TERRATEC_CINERGY_S2_R3] = {USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_CINERGY_S2_R3)},
+	[TERRATEC_CINERGY_S2_R4] = {USB_DEVICE(USB_VID_TERRATEC, USB_PID_TERRATEC_CINERGY_S2_R4)},
+	[GOTVIEW_SAT_HD] = {USB_DEVICE(0x1FE1, USB_PID_GOTVIEW_SAT_HD)},
+	[GENIATECH_T220] = {USB_DEVICE(0x1f4d, 0xD220)},
+	[TECHNOTREND_S2_4600] = {USB_DEVICE(USB_VID_TECHNOTREND,
+		USB_PID_TECHNOTREND_CONNECT_S2_4600)},
+	[TEVII_S482_1] = {USB_DEVICE(0x9022, 0xd483)},
+	[TEVII_S482_2] = {USB_DEVICE(0x9022, 0xd484)},
+	[TERRATEC_CINERGY_S2_BOX] = {USB_DEVICE(USB_VID_TERRATEC, 0x0105)},
+	[TEVII_S662] = {USB_DEVICE(0x9022, USB_PID_TEVII_S662)},
+	{ }
+};
+
+MODULE_DEVICE_TABLE(usb, dw2102_table);
+
+static int dw2102_load_firmware(struct usb_device *dev,
+			const struct firmware *frmwr)
+{
+	u8 *b, *p;
+	int ret = 0, i;
+	u8 reset;
+	u8 reset16[] = {0, 0, 0, 0, 0, 0, 0};
+	const struct firmware *fw;
+
+	switch (le16_to_cpu(dev->descriptor.idProduct)) {
+	case 0x2101:
+		ret = request_firmware(&fw, DW2101_FIRMWARE, &dev->dev);
+		if (ret != 0) {
+			err(err_str, DW2101_FIRMWARE);
+			return ret;
+		}
+		break;
+	default:
+		fw = frmwr;
+		break;
+	}
+	info("start downloading DW210X firmware");
+	p = kmalloc(fw->size, GFP_KERNEL);
+	reset = 1;
+	/*stop the CPU*/
+	dw210x_op_rw(dev, 0xa0, 0x7f92, 0, &reset, 1, DW210X_WRITE_MSG);
+	dw210x_op_rw(dev, 0xa0, 0xe600, 0, &reset, 1, DW210X_WRITE_MSG);
+
+	if (p != NULL) {
+		memcpy(p, fw->data, fw->size);
+		for (i = 0; i < fw->size; i += 0x40) {
+			b = (u8 *) p + i;
+			if (dw210x_op_rw(dev, 0xa0, i, 0, b , 0x40,
+					DW210X_WRITE_MSG) != 0x40) {
+				err("error while transferring firmware");
+				ret = -EINVAL;
+				break;
+			}
+		}
+		/* restart the CPU */
+		reset = 0;
+		if (ret || dw210x_op_rw(dev, 0xa0, 0x7f92, 0, &reset, 1,
+					DW210X_WRITE_MSG) != 1) {
+			err("could not restart the USB controller CPU.");
+			ret = -EINVAL;
+		}
+		if (ret || dw210x_op_rw(dev, 0xa0, 0xe600, 0, &reset, 1,
+					DW210X_WRITE_MSG) != 1) {
+			err("could not restart the USB controller CPU.");
+			ret = -EINVAL;
+		}
+		/* init registers */
+		switch (le16_to_cpu(dev->descriptor.idProduct)) {
+		case USB_PID_TEVII_S650:
+			dw2104_properties.rc.core.rc_codes = RC_MAP_TEVII_NEC;
+			/* fall through */
+		case USB_PID_DW2104:
+			reset = 1;
+			dw210x_op_rw(dev, 0xc4, 0x0000, 0, &reset, 1,
+					DW210X_WRITE_MSG);
+			/* fall through */
+		case USB_PID_DW3101:
+			reset = 0;
+			dw210x_op_rw(dev, 0xbf, 0x0040, 0, &reset, 0,
+					DW210X_WRITE_MSG);
+			break;
+		case USB_PID_TERRATEC_CINERGY_S:
+		case USB_PID_DW2102:
+			dw210x_op_rw(dev, 0xbf, 0x0040, 0, &reset, 0,
+					DW210X_WRITE_MSG);
+			dw210x_op_rw(dev, 0xb9, 0x0000, 0, &reset16[0], 2,
+					DW210X_READ_MSG);
+			/* check STV0299 frontend  */
+			dw210x_op_rw(dev, 0xb5, 0, 0, &reset16[0], 2,
+					DW210X_READ_MSG);
+			if ((reset16[0] == 0xa1) || (reset16[0] == 0x80)) {
+				dw2102_properties.i2c_algo = &dw2102_i2c_algo;
+				dw2102_properties.adapter->fe[0].tuner_attach = &dw2102_tuner_attach;
+				break;
+			} else {
+				/* check STV0288 frontend  */
+				reset16[0] = 0xd0;
+				reset16[1] = 1;
+				reset16[2] = 0;
+				dw210x_op_rw(dev, 0xc2, 0, 0, &reset16[0], 3,
+						DW210X_WRITE_MSG);
+				dw210x_op_rw(dev, 0xc3, 0xd1, 0, &reset16[0], 3,
+						DW210X_READ_MSG);
+				if (reset16[2] == 0x11) {
+					dw2102_properties.i2c_algo = &dw2102_earda_i2c_algo;
+					break;
+				}
+			}
+			/* fall through */
+		case 0x2101:
+			dw210x_op_rw(dev, 0xbc, 0x0030, 0, &reset16[0], 2,
+					DW210X_READ_MSG);
+			dw210x_op_rw(dev, 0xba, 0x0000, 0, &reset16[0], 7,
+					DW210X_READ_MSG);
+			dw210x_op_rw(dev, 0xba, 0x0000, 0, &reset16[0], 7,
+					DW210X_READ_MSG);
+			dw210x_op_rw(dev, 0xb9, 0x0000, 0, &reset16[0], 2,
+					DW210X_READ_MSG);
+			break;
+		}
+
+		msleep(100);
+		kfree(p);
+	}
+
+	if (le16_to_cpu(dev->descriptor.idProduct) == 0x2101)
+		release_firmware(fw);
+	return ret;
+}
+
+static struct dvb_usb_device_properties dw2102_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+	.usb_ctrl = DEVICE_SPECIFIC,
+	.firmware = DW2102_FIRMWARE,
+	.no_reconnect = 1,
+
+	.i2c_algo = &dw2102_serit_i2c_algo,
+
+	.rc.core = {
+		.rc_interval = 150,
+		.rc_codes = RC_MAP_DM1105_NEC,
+		.module_name = "dw2102",
+		.allowed_protos   = RC_PROTO_BIT_NEC,
+		.rc_query = dw2102_rc_query,
+	},
+
+	.generic_bulk_ctrl_endpoint = 0x81,
+	/* parameter for the MPEG2-data transfer */
+	.num_adapters = 1,
+	.download_firmware = dw2102_load_firmware,
+	.read_mac_address = dw210x_read_mac_address,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.frontend_attach = dw2102_frontend_attach,
+			.stream = {
+				.type = USB_BULK,
+				.count = 8,
+				.endpoint = 0x82,
+				.u = {
+					.bulk = {
+						.buffersize = 4096,
+					}
+				}
+			},
+		}},
+		}
+	},
+	.num_device_descs = 3,
+	.devices = {
+		{"DVBWorld DVB-S 2102 USB2.0",
+			{&dw2102_table[CYPRESS_DW2102], NULL},
+			{NULL},
+		},
+		{"DVBWorld DVB-S 2101 USB2.0",
+			{&dw2102_table[CYPRESS_DW2101], NULL},
+			{NULL},
+		},
+		{"TerraTec Cinergy S USB",
+			{&dw2102_table[TERRATEC_CINERGY_S], NULL},
+			{NULL},
+		},
+	}
+};
+
+static struct dvb_usb_device_properties dw2104_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+	.usb_ctrl = DEVICE_SPECIFIC,
+	.firmware = DW2104_FIRMWARE,
+	.no_reconnect = 1,
+
+	.i2c_algo = &dw2104_i2c_algo,
+	.rc.core = {
+		.rc_interval = 150,
+		.rc_codes = RC_MAP_DM1105_NEC,
+		.module_name = "dw2102",
+		.allowed_protos   = RC_PROTO_BIT_NEC,
+		.rc_query = dw2102_rc_query,
+	},
+
+	.generic_bulk_ctrl_endpoint = 0x81,
+	/* parameter for the MPEG2-data transfer */
+	.num_adapters = 1,
+	.download_firmware = dw2102_load_firmware,
+	.read_mac_address = dw210x_read_mac_address,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.frontend_attach = dw2104_frontend_attach,
+			.stream = {
+				.type = USB_BULK,
+				.count = 8,
+				.endpoint = 0x82,
+				.u = {
+					.bulk = {
+						.buffersize = 4096,
+					}
+				}
+			},
+		}},
+		}
+	},
+	.num_device_descs = 2,
+	.devices = {
+		{ "DVBWorld DW2104 USB2.0",
+			{&dw2102_table[CYPRESS_DW2104], NULL},
+			{NULL},
+		},
+		{ "TeVii S650 USB2.0",
+			{&dw2102_table[TEVII_S650], NULL},
+			{NULL},
+		},
+	}
+};
+
+static struct dvb_usb_device_properties dw3101_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+	.usb_ctrl = DEVICE_SPECIFIC,
+	.firmware = DW3101_FIRMWARE,
+	.no_reconnect = 1,
+
+	.i2c_algo = &dw3101_i2c_algo,
+	.rc.core = {
+		.rc_interval = 150,
+		.rc_codes = RC_MAP_DM1105_NEC,
+		.module_name = "dw2102",
+		.allowed_protos   = RC_PROTO_BIT_NEC,
+		.rc_query = dw2102_rc_query,
+	},
+
+	.generic_bulk_ctrl_endpoint = 0x81,
+	/* parameter for the MPEG2-data transfer */
+	.num_adapters = 1,
+	.download_firmware = dw2102_load_firmware,
+	.read_mac_address = dw210x_read_mac_address,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.frontend_attach = dw3101_frontend_attach,
+			.tuner_attach = dw3101_tuner_attach,
+			.stream = {
+				.type = USB_BULK,
+				.count = 8,
+				.endpoint = 0x82,
+				.u = {
+					.bulk = {
+						.buffersize = 4096,
+					}
+				}
+			},
+		}},
+		}
+	},
+	.num_device_descs = 1,
+	.devices = {
+		{ "DVBWorld DVB-C 3101 USB2.0",
+			{&dw2102_table[CYPRESS_DW3101], NULL},
+			{NULL},
+		},
+	}
+};
+
+static struct dvb_usb_device_properties s6x0_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+	.usb_ctrl = DEVICE_SPECIFIC,
+	.size_of_priv = sizeof(struct dw2102_state),
+	.firmware = S630_FIRMWARE,
+	.no_reconnect = 1,
+
+	.i2c_algo = &s6x0_i2c_algo,
+	.rc.core = {
+		.rc_interval = 150,
+		.rc_codes = RC_MAP_TEVII_NEC,
+		.module_name = "dw2102",
+		.allowed_protos   = RC_PROTO_BIT_NEC,
+		.rc_query = dw2102_rc_query,
+	},
+
+	.generic_bulk_ctrl_endpoint = 0x81,
+	.num_adapters = 1,
+	.download_firmware = dw2102_load_firmware,
+	.read_mac_address = s6x0_read_mac_address,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.frontend_attach = zl100313_frontend_attach,
+			.stream = {
+				.type = USB_BULK,
+				.count = 8,
+				.endpoint = 0x82,
+				.u = {
+					.bulk = {
+						.buffersize = 4096,
+					}
+				}
+			},
+		}},
+		}
+	},
+	.num_device_descs = 1,
+	.devices = {
+		{"TeVii S630 USB",
+			{&dw2102_table[TEVII_S630], NULL},
+			{NULL},
+		},
+	}
+};
+
+static const struct dvb_usb_device_description d1100 = {
+	"Prof 1100 USB ",
+	{&dw2102_table[PROF_1100], NULL},
+	{NULL},
+};
+
+static const struct dvb_usb_device_description d660 = {
+	"TeVii S660 USB",
+	{&dw2102_table[TEVII_S660], NULL},
+	{NULL},
+};
+
+static const struct dvb_usb_device_description d480_1 = {
+	"TeVii S480.1 USB",
+	{&dw2102_table[TEVII_S480_1], NULL},
+	{NULL},
+};
+
+static const struct dvb_usb_device_description d480_2 = {
+	"TeVii S480.2 USB",
+	{&dw2102_table[TEVII_S480_2], NULL},
+	{NULL},
+};
+
+static const struct dvb_usb_device_description d7500 = {
+	"Prof 7500 USB DVB-S2",
+	{&dw2102_table[PROF_7500], NULL},
+	{NULL},
+};
+
+static const struct dvb_usb_device_description d421 = {
+	"TeVii S421 PCI",
+	{&dw2102_table[TEVII_S421], NULL},
+	{NULL},
+};
+
+static const struct dvb_usb_device_description d632 = {
+	"TeVii S632 USB",
+	{&dw2102_table[TEVII_S632], NULL},
+	{NULL},
+};
+
+static struct dvb_usb_device_properties su3000_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+	.usb_ctrl = DEVICE_SPECIFIC,
+	.size_of_priv = sizeof(struct dw2102_state),
+	.power_ctrl = su3000_power_ctrl,
+	.num_adapters = 1,
+	.identify_state	= su3000_identify_state,
+	.i2c_algo = &su3000_i2c_algo,
+
+	.rc.core = {
+		.rc_interval = 150,
+		.rc_codes = RC_MAP_SU3000,
+		.module_name = "dw2102",
+		.allowed_protos   = RC_PROTO_BIT_RC5,
+		.rc_query = su3000_rc_query,
+	},
+
+	.read_mac_address = su3000_read_mac_address,
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.streaming_ctrl   = su3000_streaming_ctrl,
+			.frontend_attach  = su3000_frontend_attach,
+			.stream = {
+				.type = USB_BULK,
+				.count = 8,
+				.endpoint = 0x82,
+				.u = {
+					.bulk = {
+						.buffersize = 4096,
+					}
+				}
+			}
+		}},
+		}
+	},
+	.num_device_descs = 6,
+	.devices = {
+		{ "SU3000HD DVB-S USB2.0",
+			{ &dw2102_table[GENIATECH_SU3000], NULL },
+			{ NULL },
+		},
+		{ "Terratec Cinergy S2 USB HD",
+			{ &dw2102_table[TERRATEC_CINERGY_S2], NULL },
+			{ NULL },
+		},
+		{ "X3M TV SPC1400HD PCI",
+			{ &dw2102_table[X3M_SPC1400HD], NULL },
+			{ NULL },
+		},
+		{ "Terratec Cinergy S2 USB HD Rev.2",
+			{ &dw2102_table[TERRATEC_CINERGY_S2_R2], NULL },
+			{ NULL },
+		},
+		{ "Terratec Cinergy S2 USB HD Rev.3",
+			{ &dw2102_table[TERRATEC_CINERGY_S2_R3], NULL },
+			{ NULL },
+		},
+		{ "GOTVIEW Satellite HD",
+			{ &dw2102_table[GOTVIEW_SAT_HD], NULL },
+			{ NULL },
+		},
+	}
+};
+
+static struct dvb_usb_device_properties t220_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+	.usb_ctrl = DEVICE_SPECIFIC,
+	.size_of_priv = sizeof(struct dw2102_state),
+	.power_ctrl = su3000_power_ctrl,
+	.num_adapters = 1,
+	.identify_state	= su3000_identify_state,
+	.i2c_algo = &su3000_i2c_algo,
+
+	.rc.core = {
+		.rc_interval = 150,
+		.rc_codes = RC_MAP_SU3000,
+		.module_name = "dw2102",
+		.allowed_protos   = RC_PROTO_BIT_RC5,
+		.rc_query = su3000_rc_query,
+	},
+
+	.read_mac_address = su3000_read_mac_address,
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = { {
+			.streaming_ctrl   = su3000_streaming_ctrl,
+			.frontend_attach  = t220_frontend_attach,
+			.stream = {
+				.type = USB_BULK,
+				.count = 8,
+				.endpoint = 0x82,
+				.u = {
+					.bulk = {
+						.buffersize = 4096,
+					}
+				}
+			}
+		} },
+		}
+	},
+	.num_device_descs = 1,
+	.devices = {
+		{ "Geniatech T220 DVB-T/T2 USB2.0",
+			{ &dw2102_table[GENIATECH_T220], NULL },
+			{ NULL },
+		},
+	}
+};
+
+static struct dvb_usb_device_properties tt_s2_4600_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+	.usb_ctrl = DEVICE_SPECIFIC,
+	.size_of_priv = sizeof(struct dw2102_state),
+	.power_ctrl = su3000_power_ctrl,
+	.num_adapters = 1,
+	.identify_state	= su3000_identify_state,
+	.i2c_algo = &su3000_i2c_algo,
+
+	.rc.core = {
+		.rc_interval = 250,
+		.rc_codes = RC_MAP_TT_1500,
+		.module_name = "dw2102",
+		.allowed_protos   = RC_PROTO_BIT_RC5,
+		.rc_query = su3000_rc_query,
+	},
+
+	.read_mac_address = su3000_read_mac_address,
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.streaming_ctrl   = su3000_streaming_ctrl,
+			.frontend_attach  = tt_s2_4600_frontend_attach,
+			.stream = {
+				.type = USB_BULK,
+				.count = 8,
+				.endpoint = 0x82,
+				.u = {
+					.bulk = {
+						.buffersize = 4096,
+					}
+				}
+			}
+		} },
+		}
+	},
+	.num_device_descs = 5,
+	.devices = {
+		{ "TechnoTrend TT-connect S2-4600",
+			{ &dw2102_table[TECHNOTREND_S2_4600], NULL },
+			{ NULL },
+		},
+		{ "TeVii S482 (tuner 1)",
+			{ &dw2102_table[TEVII_S482_1], NULL },
+			{ NULL },
+		},
+		{ "TeVii S482 (tuner 2)",
+			{ &dw2102_table[TEVII_S482_2], NULL },
+			{ NULL },
+		},
+		{ "Terratec Cinergy S2 USB BOX",
+			{ &dw2102_table[TERRATEC_CINERGY_S2_BOX], NULL },
+			{ NULL },
+		},
+		{ "TeVii S662",
+			{ &dw2102_table[TEVII_S662], NULL },
+			{ NULL },
+		},
+	}
+};
+
+static int dw2102_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	int retval = -ENOMEM;
+	struct dvb_usb_device_properties *p1100;
+	struct dvb_usb_device_properties *s660;
+	struct dvb_usb_device_properties *p7500;
+	struct dvb_usb_device_properties *s421;
+
+	p1100 = kmemdup(&s6x0_properties,
+			sizeof(struct dvb_usb_device_properties), GFP_KERNEL);
+	if (!p1100)
+		goto err0;
+
+	/* copy default structure */
+	/* fill only different fields */
+	p1100->firmware = P1100_FIRMWARE;
+	p1100->devices[0] = d1100;
+	p1100->rc.core.rc_query = prof_rc_query;
+	p1100->rc.core.rc_codes = RC_MAP_TBS_NEC;
+	p1100->adapter->fe[0].frontend_attach = stv0288_frontend_attach;
+
+	s660 = kmemdup(&s6x0_properties,
+		       sizeof(struct dvb_usb_device_properties), GFP_KERNEL);
+	if (!s660)
+		goto err1;
+
+	s660->firmware = S660_FIRMWARE;
+	s660->num_device_descs = 3;
+	s660->devices[0] = d660;
+	s660->devices[1] = d480_1;
+	s660->devices[2] = d480_2;
+	s660->adapter->fe[0].frontend_attach = ds3000_frontend_attach;
+
+	p7500 = kmemdup(&s6x0_properties,
+			sizeof(struct dvb_usb_device_properties), GFP_KERNEL);
+	if (!p7500)
+		goto err2;
+
+	p7500->firmware = P7500_FIRMWARE;
+	p7500->devices[0] = d7500;
+	p7500->rc.core.rc_query = prof_rc_query;
+	p7500->rc.core.rc_codes = RC_MAP_TBS_NEC;
+	p7500->adapter->fe[0].frontend_attach = prof_7500_frontend_attach;
+
+
+	s421 = kmemdup(&su3000_properties,
+		       sizeof(struct dvb_usb_device_properties), GFP_KERNEL);
+	if (!s421)
+		goto err3;
+
+	s421->num_device_descs = 2;
+	s421->devices[0] = d421;
+	s421->devices[1] = d632;
+	s421->adapter->fe[0].frontend_attach = m88rs2000_frontend_attach;
+
+	if (0 == dvb_usb_device_init(intf, &dw2102_properties,
+			THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, &dw2104_properties,
+			THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, &dw3101_properties,
+			THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, &s6x0_properties,
+			THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, p1100,
+			THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, s660,
+			THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, p7500,
+			THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, s421,
+			THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, &su3000_properties,
+			 THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, &t220_properties,
+			 THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, &tt_s2_4600_properties,
+			 THIS_MODULE, NULL, adapter_nr)) {
+
+		/* clean up copied properties */
+		kfree(s421);
+		kfree(p7500);
+		kfree(s660);
+		kfree(p1100);
+
+		return 0;
+	}
+
+	retval = -ENODEV;
+	kfree(s421);
+err3:
+	kfree(p7500);
+err2:
+	kfree(s660);
+err1:
+	kfree(p1100);
+err0:
+	return retval;
+}
+
+static void dw2102_disconnect(struct usb_interface *intf)
+{
+	struct dvb_usb_device *d = usb_get_intfdata(intf);
+	struct dw2102_state *st = (struct dw2102_state *)d->priv;
+	struct i2c_client *client;
+
+	/* remove I2C client for tuner */
+	client = st->i2c_client_tuner;
+	if (client) {
+		module_put(client->dev.driver->owner);
+		i2c_unregister_device(client);
+	}
+
+	/* remove I2C client for demodulator */
+	client = st->i2c_client_demod;
+	if (client) {
+		module_put(client->dev.driver->owner);
+		i2c_unregister_device(client);
+	}
+
+	dvb_usb_device_exit(intf);
+}
+
+static struct usb_driver dw2102_driver = {
+	.name = "dw2102",
+	.probe = dw2102_probe,
+	.disconnect = dw2102_disconnect,
+	.id_table = dw2102_table,
+};
+
+module_usb_driver(dw2102_driver);
+
+MODULE_AUTHOR("Igor M. Liplianin (c) liplianin@me.by");
+MODULE_DESCRIPTION("Driver for DVBWorld DVB-S 2101, 2102, DVB-S2 2104, DVB-C 3101 USB2.0, TeVii S421, S480, S482, S600, S630, S632, S650, TeVii S660, S662, Prof 1100, 7500 USB2.0, Geniatech SU3000, T220, TechnoTrend S2-4600, Terratec Cinergy S2 devices");
+MODULE_VERSION("0.1");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(DW2101_FIRMWARE);
+MODULE_FIRMWARE(DW2102_FIRMWARE);
+MODULE_FIRMWARE(DW2104_FIRMWARE);
+MODULE_FIRMWARE(DW3101_FIRMWARE);
+MODULE_FIRMWARE(S630_FIRMWARE);
+MODULE_FIRMWARE(S660_FIRMWARE);
+MODULE_FIRMWARE(P1100_FIRMWARE);
+MODULE_FIRMWARE(P7500_FIRMWARE);
diff --git a/drivers/media/usb/dvb-usb/dw2102.h b/drivers/media/usb/dvb-usb/dw2102.h
new file mode 100644
index 0000000..f64cf79
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/dw2102.h
@@ -0,0 +1,10 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _DW2102_H_
+#define _DW2102_H_
+
+#define DVB_USB_LOG_PREFIX "dw2102"
+#include "dvb-usb.h"
+
+#define deb_xfer(args...) dprintk(dvb_usb_dw2102_debug, 0x02, args)
+#define deb_rc(args...)   dprintk(dvb_usb_dw2102_debug, 0x04, args)
+#endif
diff --git a/drivers/media/usb/dvb-usb/friio-fe.c b/drivers/media/usb/dvb-usb/friio-fe.c
new file mode 100644
index 0000000..e6bd0ed
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/friio-fe.c
@@ -0,0 +1,440 @@
+/* DVB USB compliant Linux driver for the Friio USB2.0 ISDB-T receiver.
+ *
+ * Copyright (C) 2009 Akihiro Tsukada <tskd2@yahoo.co.jp>
+ *
+ * This module is based off the the gl861 and vp702x modules.
+ *
+ * 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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#include <linux/init.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/kernel.h>
+
+#include "friio.h"
+
+struct jdvbt90502_state {
+	struct i2c_adapter *i2c;
+	struct dvb_frontend frontend;
+	struct jdvbt90502_config config;
+};
+
+/* NOTE: TC90502 has 16bit register-address? */
+/* register 0x0100 is used for reading PLL status, so reg is u16 here */
+static int jdvbt90502_reg_read(struct jdvbt90502_state *state,
+			       const u16 reg, u8 *buf, const size_t count)
+{
+	int ret;
+	u8 wbuf[3];
+	struct i2c_msg msg[2];
+
+	wbuf[0] = reg & 0xFF;
+	wbuf[1] = 0;
+	wbuf[2] = reg >> 8;
+
+	msg[0].addr = state->config.demod_address;
+	msg[0].flags = 0;
+	msg[0].buf = wbuf;
+	msg[0].len = sizeof(wbuf);
+
+	msg[1].addr = msg[0].addr;
+	msg[1].flags = I2C_M_RD;
+	msg[1].buf = buf;
+	msg[1].len = count;
+
+	ret = i2c_transfer(state->i2c, msg, 2);
+	if (ret != 2) {
+		deb_fe(" reg read failed.\n");
+		return -EREMOTEIO;
+	}
+	return 0;
+}
+
+/* currently 16bit register-address is not used, so reg is u8 here */
+static int jdvbt90502_single_reg_write(struct jdvbt90502_state *state,
+				       const u8 reg, const u8 val)
+{
+	struct i2c_msg msg;
+	u8 wbuf[2];
+
+	wbuf[0] = reg;
+	wbuf[1] = val;
+
+	msg.addr = state->config.demod_address;
+	msg.flags = 0;
+	msg.buf = wbuf;
+	msg.len = sizeof(wbuf);
+
+	if (i2c_transfer(state->i2c, &msg, 1) != 1) {
+		deb_fe(" reg write failed.");
+		return -EREMOTEIO;
+	}
+	return 0;
+}
+
+static int _jdvbt90502_write(struct dvb_frontend *fe, const u8 buf[], int len)
+{
+	struct jdvbt90502_state *state = fe->demodulator_priv;
+	int err, i;
+	for (i = 0; i < len - 1; i++) {
+		err = jdvbt90502_single_reg_write(state,
+						  buf[0] + i, buf[i + 1]);
+		if (err)
+			return err;
+	}
+
+	return 0;
+}
+
+/* read pll status byte via the demodulator's I2C register */
+/* note: Win box reads it by 8B block at the I2C addr 0x30 from reg:0x80 */
+static int jdvbt90502_pll_read(struct jdvbt90502_state *state, u8 *result)
+{
+	int ret;
+
+	/* +1 for reading */
+	u8 pll_addr_byte = (state->config.pll_address << 1) + 1;
+
+	*result = 0;
+
+	ret = jdvbt90502_single_reg_write(state, JDVBT90502_2ND_I2C_REG,
+					  pll_addr_byte);
+	if (ret)
+		goto error;
+
+	ret = jdvbt90502_reg_read(state, 0x0100, result, 1);
+	if (ret)
+		goto error;
+
+	deb_fe("PLL read val:%02x\n", *result);
+	return 0;
+
+error:
+	deb_fe("%s:ret == %d\n", __func__, ret);
+	return -EREMOTEIO;
+}
+
+
+/* set pll frequency via the demodulator's I2C register */
+static int jdvbt90502_pll_set_freq(struct jdvbt90502_state *state, u32 freq)
+{
+	int ret;
+	int retry;
+	u8 res1;
+	u8 res2[9];
+
+	u8 pll_freq_cmd[PLL_CMD_LEN];
+	u8 pll_agc_cmd[PLL_CMD_LEN];
+	struct i2c_msg msg[2];
+	u32 f;
+
+	deb_fe("%s: freq=%d, step=%d\n", __func__, freq,
+	       state->frontend.ops.info.frequency_stepsize_hz);
+	/* freq -> oscilator frequency conversion. */
+	/* freq: 473,000,000 + n*6,000,000 [+ 142857 (center freq. shift)] */
+	f = freq / state->frontend.ops.info.frequency_stepsize_hz;
+	/* add 399[1/7 MHZ] = 57MHz for the IF  */
+	f += 399;
+	/* add center frequency shift if necessary */
+	if (f % 7 == 0)
+		f++;
+	pll_freq_cmd[DEMOD_REDIRECT_REG] = JDVBT90502_2ND_I2C_REG; /* 0xFE */
+	pll_freq_cmd[ADDRESS_BYTE] = state->config.pll_address << 1;
+	pll_freq_cmd[DIVIDER_BYTE1] = (f >> 8) & 0x7F;
+	pll_freq_cmd[DIVIDER_BYTE2] = f & 0xFF;
+	pll_freq_cmd[CONTROL_BYTE] = 0xB2; /* ref.divider:28, 4MHz/28=1/7MHz */
+	pll_freq_cmd[BANDSWITCH_BYTE] = 0x08;	/* UHF band */
+
+	msg[0].addr = state->config.demod_address;
+	msg[0].flags = 0;
+	msg[0].buf = pll_freq_cmd;
+	msg[0].len = sizeof(pll_freq_cmd);
+
+	ret = i2c_transfer(state->i2c, &msg[0], 1);
+	if (ret != 1)
+		goto error;
+
+	udelay(50);
+
+	pll_agc_cmd[DEMOD_REDIRECT_REG] = pll_freq_cmd[DEMOD_REDIRECT_REG];
+	pll_agc_cmd[ADDRESS_BYTE] = pll_freq_cmd[ADDRESS_BYTE];
+	pll_agc_cmd[DIVIDER_BYTE1] = pll_freq_cmd[DIVIDER_BYTE1];
+	pll_agc_cmd[DIVIDER_BYTE2] = pll_freq_cmd[DIVIDER_BYTE2];
+	pll_agc_cmd[CONTROL_BYTE] = 0x9A; /*  AGC_CTRL instead of BANDSWITCH */
+	pll_agc_cmd[AGC_CTRL_BYTE] = 0x50;
+	/* AGC Time Constant 2s, AGC take-over point:103dBuV(lowest) */
+
+	msg[1].addr = msg[0].addr;
+	msg[1].flags = 0;
+	msg[1].buf = pll_agc_cmd;
+	msg[1].len = sizeof(pll_agc_cmd);
+
+	ret = i2c_transfer(state->i2c, &msg[1], 1);
+	if (ret != 1)
+		goto error;
+
+	/* I don't know what these cmds are for,  */
+	/* but the USB log on a windows box contains them */
+	ret = jdvbt90502_single_reg_write(state, 0x01, 0x40);
+	ret |= jdvbt90502_single_reg_write(state, 0x01, 0x00);
+	if (ret)
+		goto error;
+	udelay(100);
+
+	/* wait for the demod to be ready? */
+#define RETRY_COUNT 5
+	for (retry = 0; retry < RETRY_COUNT; retry++) {
+		ret = jdvbt90502_reg_read(state, 0x0096, &res1, 1);
+		if (ret)
+			goto error;
+		/* if (res1 != 0x00) goto error; */
+		ret = jdvbt90502_reg_read(state, 0x00B0, res2, sizeof(res2));
+		if (ret)
+			goto error;
+		if (res2[0] >= 0xA7)
+			break;
+		msleep(100);
+	}
+	if (retry >= RETRY_COUNT) {
+		deb_fe("%s: FE does not get ready after freq setting.\n",
+		       __func__);
+		return -EREMOTEIO;
+	}
+
+	return 0;
+error:
+	deb_fe("%s:ret == %d\n", __func__, ret);
+	return -EREMOTEIO;
+}
+
+static int jdvbt90502_read_status(struct dvb_frontend *fe,
+				  enum fe_status *state)
+{
+	u8 result;
+	int ret;
+
+	*state = FE_HAS_SIGNAL;
+
+	ret = jdvbt90502_pll_read(fe->demodulator_priv, &result);
+	if (ret) {
+		deb_fe("%s:ret == %d\n", __func__, ret);
+		return -EREMOTEIO;
+	}
+
+	*state = FE_HAS_SIGNAL
+		| FE_HAS_CARRIER
+		| FE_HAS_VITERBI
+		| FE_HAS_SYNC;
+
+	if (result & PLL_STATUS_LOCKED)
+		*state |= FE_HAS_LOCK;
+
+	return 0;
+}
+
+static int jdvbt90502_read_signal_strength(struct dvb_frontend *fe,
+					   u16 *strength)
+{
+	int ret;
+	u8 rbuf[37];
+
+	*strength = 0;
+
+	/* status register (incl. signal strength) : 0x89  */
+	/* TODO: read just the necessary registers [0x8B..0x8D]? */
+	ret = jdvbt90502_reg_read(fe->demodulator_priv, 0x0089,
+				  rbuf, sizeof(rbuf));
+
+	if (ret) {
+		deb_fe("%s:ret == %d\n", __func__, ret);
+		return -EREMOTEIO;
+	}
+
+	/* signal_strength: rbuf[2-4] (24bit BE), use lower 16bit for now. */
+	*strength = (rbuf[3] << 8) + rbuf[4];
+	if (rbuf[2])
+		*strength = 0xffff;
+
+	return 0;
+}
+
+static int jdvbt90502_set_frontend(struct dvb_frontend *fe)
+{
+	struct dtv_frontend_properties *p = &fe->dtv_property_cache;
+
+	/**
+	 * NOTE: ignore all the parameters except frequency.
+	 *       others should be fixed to the proper value for ISDB-T,
+	 *       but don't check here.
+	 */
+
+	struct jdvbt90502_state *state = fe->demodulator_priv;
+	int ret;
+
+	deb_fe("%s: Freq:%d\n", __func__, p->frequency);
+
+	/* This driver only works on auto mode */
+	p->inversion = INVERSION_AUTO;
+	p->bandwidth_hz = 6000000;
+	p->code_rate_HP = FEC_AUTO;
+	p->code_rate_LP = FEC_AUTO;
+	p->modulation = QAM_64;
+	p->transmission_mode = TRANSMISSION_MODE_AUTO;
+	p->guard_interval = GUARD_INTERVAL_AUTO;
+	p->hierarchy = HIERARCHY_AUTO;
+	p->delivery_system = SYS_ISDBT;
+
+	ret = jdvbt90502_pll_set_freq(state, p->frequency);
+	if (ret) {
+		deb_fe("%s:ret == %d\n", __func__, ret);
+		return -EREMOTEIO;
+	}
+
+	return 0;
+}
+
+
+/*
+ * (reg, val) commad list to initialize this module.
+ *  captured on a Windows box.
+ */
+static u8 init_code[][2] = {
+	{0x01, 0x40},
+	{0x04, 0x38},
+	{0x05, 0x40},
+	{0x07, 0x40},
+	{0x0F, 0x4F},
+	{0x11, 0x21},
+	{0x12, 0x0B},
+	{0x13, 0x2F},
+	{0x14, 0x31},
+	{0x16, 0x02},
+	{0x21, 0xC4},
+	{0x22, 0x20},
+	{0x2C, 0x79},
+	{0x2D, 0x34},
+	{0x2F, 0x00},
+	{0x30, 0x28},
+	{0x31, 0x31},
+	{0x32, 0xDF},
+	{0x38, 0x01},
+	{0x39, 0x78},
+	{0x3B, 0x33},
+	{0x3C, 0x33},
+	{0x48, 0x90},
+	{0x51, 0x68},
+	{0x5E, 0x38},
+	{0x71, 0x00},
+	{0x72, 0x08},
+	{0x77, 0x00},
+	{0xC0, 0x21},
+	{0xC1, 0x10},
+	{0xE4, 0x1A},
+	{0xEA, 0x1F},
+	{0x77, 0x00},
+	{0x71, 0x00},
+	{0x71, 0x00},
+	{0x76, 0x0C},
+};
+
+static int jdvbt90502_init(struct dvb_frontend *fe)
+{
+	int i = -1;
+	int ret;
+	struct i2c_msg msg;
+
+	struct jdvbt90502_state *state = fe->demodulator_priv;
+
+	deb_fe("%s called.\n", __func__);
+
+	msg.addr = state->config.demod_address;
+	msg.flags = 0;
+	msg.len = 2;
+	for (i = 0; i < ARRAY_SIZE(init_code); i++) {
+		msg.buf = init_code[i];
+		ret = i2c_transfer(state->i2c, &msg, 1);
+		if (ret != 1)
+			goto error;
+	}
+	fe->dtv_property_cache.delivery_system = SYS_ISDBT;
+	msleep(100);
+
+	return 0;
+
+error:
+	deb_fe("%s: init_code[%d] failed. ret==%d\n", __func__, i, ret);
+	return -EREMOTEIO;
+}
+
+
+static void jdvbt90502_release(struct dvb_frontend *fe)
+{
+	struct jdvbt90502_state *state = fe->demodulator_priv;
+	kfree(state);
+}
+
+
+static const struct dvb_frontend_ops jdvbt90502_ops;
+
+struct dvb_frontend *jdvbt90502_attach(struct dvb_usb_device *d)
+{
+	struct jdvbt90502_state *state = NULL;
+
+	deb_info("%s called.\n", __func__);
+
+	/* allocate memory for the internal state */
+	state = kzalloc(sizeof(struct jdvbt90502_state), GFP_KERNEL);
+	if (state == NULL)
+		goto error;
+
+	/* setup the state */
+	state->i2c = &d->i2c_adap;
+	state->config = friio_fe_config;
+
+	/* create dvb_frontend */
+	state->frontend.ops = jdvbt90502_ops;
+	state->frontend.demodulator_priv = state;
+
+	if (jdvbt90502_init(&state->frontend) < 0)
+		goto error;
+
+	return &state->frontend;
+
+error:
+	kfree(state);
+	return NULL;
+}
+
+static const struct dvb_frontend_ops jdvbt90502_ops = {
+	.delsys = { SYS_ISDBT },
+	.info = {
+		.name			= "Comtech JDVBT90502 ISDB-T",
+		.frequency_min_hz	= 473000000, /* UHF 13ch, center */
+		.frequency_max_hz	= 767142857, /* UHF 62ch, center */
+		.frequency_stepsize_hz	= JDVBT90502_PLL_CLK / JDVBT90502_PLL_DIVIDER,
+
+		/* NOTE: this driver ignores all parameters but frequency. */
+		.caps = FE_CAN_INVERSION_AUTO |
+			FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 |
+			FE_CAN_FEC_4_5 | FE_CAN_FEC_5_6 | FE_CAN_FEC_6_7 |
+			FE_CAN_FEC_7_8 | FE_CAN_FEC_8_9 | FE_CAN_FEC_AUTO |
+			FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO |
+			FE_CAN_TRANSMISSION_MODE_AUTO |
+			FE_CAN_GUARD_INTERVAL_AUTO |
+			FE_CAN_HIERARCHY_AUTO,
+	},
+
+	.release = jdvbt90502_release,
+
+	.init = jdvbt90502_init,
+	.write = _jdvbt90502_write,
+
+	.set_frontend = jdvbt90502_set_frontend,
+
+	.read_status = jdvbt90502_read_status,
+	.read_signal_strength = jdvbt90502_read_signal_strength,
+};
diff --git a/drivers/media/usb/dvb-usb/friio.c b/drivers/media/usb/dvb-usb/friio.c
new file mode 100644
index 0000000..fe799a7
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/friio.c
@@ -0,0 +1,522 @@
+/* DVB USB compliant Linux driver for the Friio USB2.0 ISDB-T receiver.
+ *
+ * Copyright (C) 2009 Akihiro Tsukada <tskd2@yahoo.co.jp>
+ *
+ * This module is based off the the gl861 and vp702x modules.
+ *
+ * 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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#include "friio.h"
+
+/* debug */
+int dvb_usb_friio_debug;
+module_param_named(debug, dvb_usb_friio_debug, int, 0644);
+MODULE_PARM_DESC(debug,
+		 "set debugging level (1=info,2=xfer,4=rc,8=fe (or-able))."
+		 DVB_USB_DEBUG_STATUS);
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+/*
+ * Indirect I2C access to the PLL via FE.
+ * whole I2C protocol data to the PLL is sent via the FE's I2C register.
+ * This is done by a control msg to the FE with the I2C data accompanied, and
+ * a specific USB request number is assigned for that purpose.
+ *
+ * this func sends wbuf[1..] to the I2C register wbuf[0] at addr (= at FE).
+ * TODO: refoctored, smarter i2c functions.
+ */
+static int gl861_i2c_ctrlmsg_data(struct dvb_usb_device *d, u8 addr,
+				  u8 *wbuf, u16 wlen, u8 *rbuf, u16 rlen)
+{
+	u16 index = wbuf[0];	/* must be JDVBT90502_2ND_I2C_REG(=0xFE) */
+	u16 value = addr << (8 + 1);
+	int wo = (rbuf == NULL || rlen == 0);	/* write only */
+	u8 req, type;
+
+	deb_xfer("write to PLL:0x%02x via FE reg:0x%02x, len:%d\n",
+		 wbuf[1], wbuf[0], wlen - 1);
+
+	if (wo && wlen >= 2) {
+		req = GL861_REQ_I2C_DATA_CTRL_WRITE;
+		type = GL861_WRITE;
+		udelay(20);
+		return usb_control_msg(d->udev, usb_sndctrlpipe(d->udev, 0),
+				       req, type, value, index,
+				       &wbuf[1], wlen - 1, 2000);
+	}
+
+	deb_xfer("not supported ctrl-msg, aborting.");
+	return -EINVAL;
+}
+
+/* normal I2C access (without extra data arguments).
+ * write to the register wbuf[0] at I2C address addr with the value wbuf[1],
+ *  or read from the register wbuf[0].
+ * register address can be 16bit (wbuf[2]<<8 | wbuf[0]) if wlen==3
+ */
+static int gl861_i2c_msg(struct dvb_usb_device *d, u8 addr,
+			 u8 *wbuf, u16 wlen, u8 *rbuf, u16 rlen)
+{
+	u16 index;
+	u16 value = addr << (8 + 1);
+	int wo = (rbuf == NULL || rlen == 0);	/* write-only */
+	u8 req, type;
+	unsigned int pipe;
+
+	/* special case for the indirect I2C access to the PLL via FE, */
+	if (addr == friio_fe_config.demod_address &&
+	    wbuf[0] == JDVBT90502_2ND_I2C_REG)
+		return gl861_i2c_ctrlmsg_data(d, addr, wbuf, wlen, rbuf, rlen);
+
+	if (wo) {
+		req = GL861_REQ_I2C_WRITE;
+		type = GL861_WRITE;
+		pipe = usb_sndctrlpipe(d->udev, 0);
+	} else {		/* rw */
+		req = GL861_REQ_I2C_READ;
+		type = GL861_READ;
+		pipe = usb_rcvctrlpipe(d->udev, 0);
+	}
+
+	switch (wlen) {
+	case 1:
+		index = wbuf[0];
+		break;
+	case 2:
+		index = wbuf[0];
+		value = value + wbuf[1];
+		break;
+	case 3:
+		/* special case for 16bit register-address */
+		index = (wbuf[2] << 8) | wbuf[0];
+		value = value + wbuf[1];
+		break;
+	default:
+		deb_xfer("wlen = %x, aborting.", wlen);
+		return -EINVAL;
+	}
+	msleep(1);
+	return usb_control_msg(d->udev, pipe, req, type,
+			       value, index, rbuf, rlen, 2000);
+}
+
+/* I2C */
+static int gl861_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[],
+			  int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	int i;
+
+
+	if (num > 2)
+		return -EINVAL;
+
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	for (i = 0; i < num; i++) {
+		/* write/read request */
+		if (i + 1 < num && (msg[i + 1].flags & I2C_M_RD)) {
+			if (gl861_i2c_msg(d, msg[i].addr,
+					  msg[i].buf, msg[i].len,
+					  msg[i + 1].buf, msg[i + 1].len) < 0)
+				break;
+			i++;
+		} else
+			if (gl861_i2c_msg(d, msg[i].addr, msg[i].buf,
+					  msg[i].len, NULL, 0) < 0)
+				break;
+	}
+
+	mutex_unlock(&d->i2c_mutex);
+	return i;
+}
+
+static u32 gl861_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static int friio_ext_ctl(struct dvb_usb_adapter *adap,
+			 u32 sat_color, int lnb_on)
+{
+	int i;
+	int ret;
+	struct i2c_msg msg;
+	u8 *buf;
+	u32 mask;
+	u8 lnb = (lnb_on) ? FRIIO_CTL_LNB : 0;
+
+	buf = kmalloc(2, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	msg.addr = 0x00;
+	msg.flags = 0;
+	msg.len = 2;
+	msg.buf = buf;
+
+	buf[0] = 0x00;
+
+	/* send 2bit header (&B10) */
+	buf[1] = lnb | FRIIO_CTL_LED | FRIIO_CTL_STROBE;
+	ret = gl861_i2c_xfer(&adap->dev->i2c_adap, &msg, 1);
+	buf[1] |= FRIIO_CTL_CLK;
+	ret += gl861_i2c_xfer(&adap->dev->i2c_adap, &msg, 1);
+
+	buf[1] = lnb | FRIIO_CTL_STROBE;
+	ret += gl861_i2c_xfer(&adap->dev->i2c_adap, &msg, 1);
+	buf[1] |= FRIIO_CTL_CLK;
+	ret += gl861_i2c_xfer(&adap->dev->i2c_adap, &msg, 1);
+
+	/* send 32bit(satur, R, G, B) data in serial */
+	mask = 1 << 31;
+	for (i = 0; i < 32; i++) {
+		buf[1] = lnb | FRIIO_CTL_STROBE;
+		if (sat_color & mask)
+			buf[1] |= FRIIO_CTL_LED;
+		ret += gl861_i2c_xfer(&adap->dev->i2c_adap, &msg, 1);
+		buf[1] |= FRIIO_CTL_CLK;
+		ret += gl861_i2c_xfer(&adap->dev->i2c_adap, &msg, 1);
+		mask >>= 1;
+	}
+
+	/* set the strobe off */
+	buf[1] = lnb;
+	ret += gl861_i2c_xfer(&adap->dev->i2c_adap, &msg, 1);
+	buf[1] |= FRIIO_CTL_CLK;
+	ret += gl861_i2c_xfer(&adap->dev->i2c_adap, &msg, 1);
+
+	kfree(buf);
+	return (ret == 70);
+}
+
+
+static int friio_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff);
+
+/* TODO: move these init cmds to the FE's init routine? */
+static u8 streaming_init_cmds[][2] = {
+	{0x33, 0x08},
+	{0x37, 0x40},
+	{0x3A, 0x1F},
+	{0x3B, 0xFF},
+	{0x3C, 0x1F},
+	{0x3D, 0xFF},
+	{0x38, 0x00},
+	{0x35, 0x00},
+	{0x39, 0x00},
+	{0x36, 0x00},
+};
+static int cmdlen = sizeof(streaming_init_cmds) / 2;
+
+/*
+ * Command sequence in this init function is a replay
+ *  of the captured USB commands from the Windows proprietary driver.
+ */
+static int friio_initialize(struct dvb_usb_device *d)
+{
+	int ret;
+	int i;
+	int retry = 0;
+	u8 *rbuf, *wbuf;
+
+	deb_info("%s called.\n", __func__);
+
+	wbuf = kmalloc(3, GFP_KERNEL);
+	if (!wbuf)
+		return -ENOMEM;
+
+	rbuf = kmalloc(2, GFP_KERNEL);
+	if (!rbuf) {
+		kfree(wbuf);
+		return -ENOMEM;
+	}
+
+	/* use gl861_i2c_msg instead of gl861_i2c_xfer(), */
+	/* because the i2c device is not set up yet. */
+	wbuf[0] = 0x11;
+	wbuf[1] = 0x02;
+	ret = gl861_i2c_msg(d, 0x00, wbuf, 2, NULL, 0);
+	if (ret < 0)
+		goto error;
+	msleep(2);
+
+	wbuf[0] = 0x11;
+	wbuf[1] = 0x00;
+	ret = gl861_i2c_msg(d, 0x00, wbuf, 2, NULL, 0);
+	if (ret < 0)
+		goto error;
+	msleep(1);
+
+	/* following msgs should be in the FE's init code? */
+	/* cmd sequence to identify the device type? (friio black/white) */
+	wbuf[0] = 0x03;
+	wbuf[1] = 0x80;
+	/* can't use gl861_i2c_cmd, as the register-addr is 16bit(0x0100) */
+	ret = usb_control_msg(d->udev, usb_sndctrlpipe(d->udev, 0),
+			      GL861_REQ_I2C_DATA_CTRL_WRITE, GL861_WRITE,
+			      0x1200, 0x0100, wbuf, 2, 2000);
+	if (ret < 0)
+		goto error;
+
+	msleep(2);
+	wbuf[0] = 0x00;
+	wbuf[2] = 0x01;		/* reg.0x0100 */
+	wbuf[1] = 0x00;
+	ret = gl861_i2c_msg(d, 0x12 >> 1, wbuf, 3, rbuf, 2);
+	/* my Friio White returns 0xffff. */
+	if (ret < 0 || rbuf[0] != 0xff || rbuf[1] != 0xff)
+		goto error;
+
+	msleep(2);
+	wbuf[0] = 0x03;
+	wbuf[1] = 0x80;
+	ret = usb_control_msg(d->udev, usb_sndctrlpipe(d->udev, 0),
+			      GL861_REQ_I2C_DATA_CTRL_WRITE, GL861_WRITE,
+			      0x9000, 0x0100, wbuf, 2, 2000);
+	if (ret < 0)
+		goto error;
+
+	msleep(2);
+	wbuf[0] = 0x00;
+	wbuf[2] = 0x01;		/* reg.0x0100 */
+	wbuf[1] = 0x00;
+	ret = gl861_i2c_msg(d, 0x90 >> 1, wbuf, 3, rbuf, 2);
+	/* my Friio White returns 0xffff again. */
+	if (ret < 0 || rbuf[0] != 0xff || rbuf[1] != 0xff)
+		goto error;
+
+	msleep(1);
+
+restart:
+	/* ============ start DEMOD init cmds ================== */
+	/* read PLL status to clear the POR bit */
+	wbuf[0] = JDVBT90502_2ND_I2C_REG;
+	wbuf[1] = (FRIIO_PLL_ADDR << 1) + 1;	/* +1 for reading */
+	ret = gl861_i2c_msg(d, FRIIO_DEMOD_ADDR, wbuf, 2, NULL, 0);
+	if (ret < 0)
+		goto error;
+
+	msleep(5);
+	/* note: DEMODULATOR has 16bit register-address. */
+	wbuf[0] = 0x00;
+	wbuf[2] = 0x01;		/* reg addr: 0x0100 */
+	wbuf[1] = 0x00;		/* val: not used */
+	ret = gl861_i2c_msg(d, FRIIO_DEMOD_ADDR, wbuf, 3, rbuf, 1);
+	if (ret < 0)
+		goto error;
+/*
+	msleep(1);
+	wbuf[0] = 0x80;
+	wbuf[1] = 0x00;
+	ret = gl861_i2c_msg(d, FRIIO_DEMOD_ADDR, wbuf, 2, rbuf, 1);
+	if (ret < 0)
+		goto error;
+ */
+	if (rbuf[0] & 0x80) {	/* still in PowerOnReset state? */
+		if (++retry > 3) {
+			deb_info("failed to get the correct FE demod status:0x%02x\n",
+				 rbuf[0]);
+			goto error;
+		}
+		msleep(100);
+		goto restart;
+	}
+
+	/* TODO: check return value in rbuf */
+	/* =========== end DEMOD init cmds ===================== */
+	msleep(1);
+
+	wbuf[0] = 0x30;
+	wbuf[1] = 0x04;
+	ret = gl861_i2c_msg(d, 0x00, wbuf, 2, NULL, 0);
+	if (ret < 0)
+		goto error;
+
+	msleep(2);
+	/* following 2 cmds unnecessary? */
+	wbuf[0] = 0x00;
+	wbuf[1] = 0x01;
+	ret = gl861_i2c_msg(d, 0x00, wbuf, 2, NULL, 0);
+	if (ret < 0)
+		goto error;
+
+	wbuf[0] = 0x06;
+	wbuf[1] = 0x0F;
+	ret = gl861_i2c_msg(d, 0x00, wbuf, 2, NULL, 0);
+	if (ret < 0)
+		goto error;
+
+	/* some streaming ctl cmds (maybe) */
+	msleep(10);
+	for (i = 0; i < cmdlen; i++) {
+		ret = gl861_i2c_msg(d, 0x00, streaming_init_cmds[i], 2,
+				    NULL, 0);
+		if (ret < 0)
+			goto error;
+		msleep(1);
+	}
+	msleep(20);
+
+	/* change the LED color etc. */
+	ret = friio_streaming_ctrl(&d->adapter[0], 0);
+	if (ret < 0)
+		goto error;
+
+	return 0;
+
+error:
+	kfree(wbuf);
+	kfree(rbuf);
+	deb_info("%s:ret == %d\n", __func__, ret);
+	return -EIO;
+}
+
+/* Callbacks for DVB USB */
+
+static int friio_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff)
+{
+	int ret;
+
+	deb_info("%s called.(%d)\n", __func__, onoff);
+
+	/* set the LED color and saturation (and LNB on) */
+	if (onoff)
+		ret = friio_ext_ctl(adap, 0x6400ff64, 1);
+	else
+		ret = friio_ext_ctl(adap, 0x96ff00ff, 1);
+
+	if (ret != 1) {
+		deb_info("%s failed to send cmdx. ret==%d\n", __func__, ret);
+		return -EREMOTEIO;
+	}
+	return 0;
+}
+
+static int friio_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	if (friio_initialize(adap->dev) < 0)
+		return -EIO;
+
+	adap->fe_adap[0].fe = jdvbt90502_attach(adap->dev);
+	if (adap->fe_adap[0].fe == NULL)
+		return -EIO;
+
+	return 0;
+}
+
+/* DVB USB Driver stuff */
+static struct dvb_usb_device_properties friio_properties;
+
+static int friio_probe(struct usb_interface *intf,
+		       const struct usb_device_id *id)
+{
+	struct dvb_usb_device *d;
+	struct usb_host_interface *alt;
+	int ret;
+
+	if (intf->num_altsetting < GL861_ALTSETTING_COUNT)
+		return -ENODEV;
+
+	alt = usb_altnum_to_altsetting(intf, FRIIO_BULK_ALTSETTING);
+	if (alt == NULL) {
+		deb_rc("not alt found!\n");
+		return -ENODEV;
+	}
+	ret = usb_set_interface(interface_to_usbdev(intf),
+				alt->desc.bInterfaceNumber,
+				alt->desc.bAlternateSetting);
+	if (ret != 0) {
+		deb_rc("failed to set alt-setting!\n");
+		return ret;
+	}
+
+	ret = dvb_usb_device_init(intf, &friio_properties,
+				  THIS_MODULE, &d, adapter_nr);
+	if (ret == 0)
+		friio_streaming_ctrl(&d->adapter[0], 1);
+
+	return ret;
+}
+
+
+struct jdvbt90502_config friio_fe_config = {
+	.demod_address = FRIIO_DEMOD_ADDR,
+	.pll_address = FRIIO_PLL_ADDR,
+};
+
+static struct i2c_algorithm gl861_i2c_algo = {
+	.master_xfer   = gl861_i2c_xfer,
+	.functionality = gl861_i2c_func,
+};
+
+static struct usb_device_id friio_table[] = {
+	{ USB_DEVICE(USB_VID_774, USB_PID_FRIIO_WHITE) },
+	{ }		/* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, friio_table);
+
+
+static struct dvb_usb_device_properties friio_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+	.usb_ctrl = DEVICE_SPECIFIC,
+
+	.size_of_priv = 0,
+
+	.num_adapters = 1,
+	.adapter = {
+		/* caps:0 =>  no pid filter, 188B TS packet */
+		/* GL861 has a HW pid filter, but no info available. */
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.caps  = 0,
+
+			.frontend_attach  = friio_frontend_attach,
+			.streaming_ctrl = friio_streaming_ctrl,
+
+			.stream = {
+				.type = USB_BULK,
+				/* count <= MAX_NO_URBS_FOR_DATA_STREAM(10) */
+				.count = 8,
+				.endpoint = 0x01,
+				.u = {
+					/* GL861 has 6KB buf inside */
+					.bulk = {
+						.buffersize = 16384,
+					}
+				}
+			},
+		}},
+		}
+	},
+	.i2c_algo = &gl861_i2c_algo,
+
+	.num_device_descs = 1,
+	.devices = {
+		{
+			.name = "774 Friio ISDB-T USB2.0",
+			.cold_ids = { NULL },
+			.warm_ids = { &friio_table[0], NULL },
+		},
+	}
+};
+
+static struct usb_driver friio_driver = {
+	.name		= "dvb_usb_friio",
+	.probe		= friio_probe,
+	.disconnect	= dvb_usb_device_exit,
+	.id_table	= friio_table,
+};
+
+module_usb_driver(friio_driver);
+
+MODULE_AUTHOR("Akihiro Tsukada <tskd2@yahoo.co.jp>");
+MODULE_DESCRIPTION("Driver for Friio ISDB-T USB2.0 Receiver");
+MODULE_VERSION("0.2");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/friio.h b/drivers/media/usb/dvb-usb/friio.h
new file mode 100644
index 0000000..a53af56
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/friio.h
@@ -0,0 +1,99 @@
+/* DVB USB compliant Linux driver for the Friio USB2.0 ISDB-T receiver.
+ *
+ * Copyright (C) 2009 Akihiro Tsukada <tskd2@yahoo.co.jp>
+ *
+ * This module is based off the the gl861 and vp702x modules.
+ *
+ * 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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#ifndef _DVB_USB_FRIIO_H_
+#define _DVB_USB_FRIIO_H_
+
+/**
+ *      Friio Components
+ *       USB hub:                                AU4254
+ *         USB controller(+ TS dmx & streaming): GL861
+ *         Frontend:                             comtech JDVBT-90502
+ *             (tuner PLL:                       tua6034, I2C addr:(0xC0 >> 1))
+ *             (OFDM demodulator:                TC90502, I2C addr:(0x30 >> 1))
+ *         LED x3 (+LNB) control:                PIC 16F676
+ *         EEPROM:                               24C08
+ *
+ *        (USB smart card reader:                AU9522)
+ *
+ */
+
+#define DVB_USB_LOG_PREFIX "friio"
+#include "dvb-usb.h"
+
+extern int dvb_usb_friio_debug;
+#define deb_info(args...) dprintk(dvb_usb_friio_debug, 0x01, args)
+#define deb_xfer(args...) dprintk(dvb_usb_friio_debug, 0x02, args)
+#define deb_rc(args...)   dprintk(dvb_usb_friio_debug, 0x04, args)
+#define deb_fe(args...)   dprintk(dvb_usb_friio_debug, 0x08, args)
+
+/* Vendor requests */
+#define GL861_WRITE		0x40
+#define GL861_READ		0xc0
+
+/* command bytes */
+#define GL861_REQ_I2C_WRITE	0x01
+#define GL861_REQ_I2C_READ	0x02
+/* For control msg with data argument */
+/* Used for accessing the PLL on the secondary I2C bus of FE via GL861 */
+#define GL861_REQ_I2C_DATA_CTRL_WRITE	0x03
+
+#define GL861_ALTSETTING_COUNT	2
+#define FRIIO_BULK_ALTSETTING	0
+#define FRIIO_ISOC_ALTSETTING	1
+
+/* LED & LNB control via PIC. */
+/* basically, it's serial control with clock and strobe. */
+/* write the below 4bit control data to the reg 0x00 at the I2C addr 0x00 */
+/* when controlling the LEDs, 32bit(saturation, R, G, B) is sent on the bit3*/
+#define FRIIO_CTL_LNB (1 << 0)
+#define FRIIO_CTL_STROBE (1 << 1)
+#define FRIIO_CTL_CLK (1 << 2)
+#define FRIIO_CTL_LED (1 << 3)
+
+/* Front End related */
+
+#define FRIIO_DEMOD_ADDR  (0x30 >> 1)
+#define FRIIO_PLL_ADDR  (0xC0 >> 1)
+
+#define JDVBT90502_PLL_CLK	4000000
+#define JDVBT90502_PLL_DIVIDER	28
+
+#define JDVBT90502_2ND_I2C_REG 0xFE
+
+/* byte index for pll i2c command data structure*/
+/* see datasheet for tua6034 */
+#define DEMOD_REDIRECT_REG 0
+#define ADDRESS_BYTE       1
+#define DIVIDER_BYTE1      2
+#define DIVIDER_BYTE2      3
+#define CONTROL_BYTE       4
+#define BANDSWITCH_BYTE    5
+#define AGC_CTRL_BYTE      5
+#define PLL_CMD_LEN        6
+
+/* bit masks for PLL STATUS response */
+#define PLL_STATUS_POR_MODE   0x80 /* 1: Power on Reset (test) Mode */
+#define PLL_STATUS_LOCKED     0x40 /* 1: locked */
+#define PLL_STATUS_AGC_ACTIVE 0x08 /* 1:active */
+#define PLL_STATUS_TESTMODE   0x07 /* digital output level (5 level) */
+  /* 0.15Vcc step   0x00: < 0.15Vcc, ..., 0x04: >= 0.6Vcc (<= 1Vcc) */
+
+
+struct jdvbt90502_config {
+	u8 demod_address; /* i2c addr for demodulator IC */
+	u8 pll_address;   /* PLL addr on the secondary i2c*/
+};
+extern struct jdvbt90502_config friio_fe_config;
+
+extern struct dvb_frontend *jdvbt90502_attach(struct dvb_usb_device *d);
+#endif
diff --git a/drivers/media/usb/dvb-usb/gp8psk.c b/drivers/media/usb/dvb-usb/gp8psk.c
new file mode 100644
index 0000000..13e96b0
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/gp8psk.c
@@ -0,0 +1,393 @@
+/* DVB USB compliant Linux driver for the
+ *  - GENPIX 8pks/qpsk/DCII USB2.0 DVB-S module
+ *
+ * Copyright (C) 2006,2007 Alan Nisota (alannisota@gmail.com)
+ * Copyright (C) 2006,2007 Genpix Electronics (genpix@genpix-electronics.com)
+ *
+ * Thanks to GENPIX for the sample code used to implement this module.
+ *
+ * This module is based off the vp7045 and vp702x modules
+ *
+ *	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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#include "gp8psk.h"
+#include "gp8psk-fe.h"
+
+/* debug */
+static char bcm4500_firmware[] = "dvb-usb-gp8psk-02.fw";
+int dvb_usb_gp8psk_debug;
+module_param_named(debug,dvb_usb_gp8psk_debug, int, 0644);
+MODULE_PARM_DESC(debug, "set debugging level (1=info,xfer=2,rc=4 (or-able))." DVB_USB_DEBUG_STATUS);
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+struct gp8psk_state {
+	unsigned char data[80];
+};
+
+static int gp8psk_usb_in_op(struct dvb_usb_device *d, u8 req, u16 value,
+			    u16 index, u8 *b, int blen)
+{
+	struct gp8psk_state *st = d->priv;
+	int ret = 0,try = 0;
+
+	if (blen > sizeof(st->data))
+		return -EIO;
+
+	if ((ret = mutex_lock_interruptible(&d->usb_mutex)))
+		return ret;
+
+	while (ret >= 0 && ret != blen && try < 3) {
+		ret = usb_control_msg(d->udev,
+			usb_rcvctrlpipe(d->udev,0),
+			req,
+			USB_TYPE_VENDOR | USB_DIR_IN,
+			value, index, st->data, blen,
+			2000);
+		deb_info("reading number %d (ret: %d)\n",try,ret);
+		try++;
+	}
+
+	if (ret < 0 || ret != blen) {
+		warn("usb in %d operation failed.", req);
+		ret = -EIO;
+	} else {
+		ret = 0;
+		memcpy(b, st->data, blen);
+	}
+
+	deb_xfer("in: req. %x, val: %x, ind: %x, buffer: ",req,value,index);
+	debug_dump(b,blen,deb_xfer);
+
+	mutex_unlock(&d->usb_mutex);
+
+	return ret;
+}
+
+static int gp8psk_usb_out_op(struct dvb_usb_device *d, u8 req, u16 value,
+			     u16 index, u8 *b, int blen)
+{
+	struct gp8psk_state *st = d->priv;
+	int ret;
+
+	deb_xfer("out: req. %x, val: %x, ind: %x, buffer: ",req,value,index);
+	debug_dump(b,blen,deb_xfer);
+
+	if (blen > sizeof(st->data))
+		return -EIO;
+
+	if ((ret = mutex_lock_interruptible(&d->usb_mutex)))
+		return ret;
+
+	memcpy(st->data, b, blen);
+	if (usb_control_msg(d->udev,
+			usb_sndctrlpipe(d->udev,0),
+			req,
+			USB_TYPE_VENDOR | USB_DIR_OUT,
+			value, index, st->data, blen,
+			2000) != blen) {
+		warn("usb out operation failed.");
+		ret = -EIO;
+	} else
+		ret = 0;
+	mutex_unlock(&d->usb_mutex);
+
+	return ret;
+}
+
+
+static int gp8psk_get_fw_version(struct dvb_usb_device *d, u8 *fw_vers)
+{
+	return gp8psk_usb_in_op(d, GET_FW_VERS, 0, 0, fw_vers, 6);
+}
+
+static int gp8psk_get_fpga_version(struct dvb_usb_device *d, u8 *fpga_vers)
+{
+	return gp8psk_usb_in_op(d, GET_FPGA_VERS, 0, 0, fpga_vers, 1);
+}
+
+static void gp8psk_info(struct dvb_usb_device *d)
+{
+	u8 fpga_vers, fw_vers[6];
+
+	if (!gp8psk_get_fw_version(d, fw_vers))
+		info("FW Version = %i.%02i.%i (0x%x)  Build %4i/%02i/%02i",
+		fw_vers[2], fw_vers[1], fw_vers[0], GP8PSK_FW_VERS(fw_vers),
+		2000 + fw_vers[5], fw_vers[4], fw_vers[3]);
+	else
+		info("failed to get FW version");
+
+	if (!gp8psk_get_fpga_version(d, &fpga_vers))
+		info("FPGA Version = %i", fpga_vers);
+	else
+		info("failed to get FPGA version");
+}
+
+static int gp8psk_load_bcm4500fw(struct dvb_usb_device *d)
+{
+	int ret;
+	const struct firmware *fw = NULL;
+	const u8 *ptr;
+	u8 *buf;
+	if ((ret = request_firmware(&fw, bcm4500_firmware,
+					&d->udev->dev)) != 0) {
+		err("did not find the bcm4500 firmware file '%s' (status %d). You can use <kernel_dir>/scripts/get_dvb_firmware to get the firmware",
+			bcm4500_firmware,ret);
+		return ret;
+	}
+
+	ret = -EINVAL;
+
+	if (gp8psk_usb_out_op(d, LOAD_BCM4500,1,0,NULL, 0))
+		goto out_rel_fw;
+
+	info("downloading bcm4500 firmware from file '%s'",bcm4500_firmware);
+
+	ptr = fw->data;
+	buf = kmalloc(64, GFP_KERNEL);
+	if (!buf) {
+		ret = -ENOMEM;
+		goto out_rel_fw;
+	}
+
+	while (ptr[0] != 0xff) {
+		u16 buflen = ptr[0] + 4;
+		if (ptr + buflen >= fw->data + fw->size) {
+			err("failed to load bcm4500 firmware.");
+			goto out_free;
+		}
+		if (buflen > 64) {
+			err("firmware chunk size bigger than 64 bytes.");
+			goto out_free;
+		}
+
+		memcpy(buf, ptr, buflen);
+		if (dvb_usb_generic_write(d, buf, buflen)) {
+			err("failed to load bcm4500 firmware.");
+			goto out_free;
+		}
+		ptr += buflen;
+	}
+
+	ret = 0;
+
+out_free:
+	kfree(buf);
+out_rel_fw:
+	release_firmware(fw);
+
+	return ret;
+}
+
+static int gp8psk_power_ctrl(struct dvb_usb_device *d, int onoff)
+{
+	u8 status, buf;
+	int gp_product_id = le16_to_cpu(d->udev->descriptor.idProduct);
+
+	if (onoff) {
+		gp8psk_usb_in_op(d, GET_8PSK_CONFIG,0,0,&status,1);
+		if (! (status & bm8pskStarted)) {  /* started */
+			if(gp_product_id == USB_PID_GENPIX_SKYWALKER_CW3K)
+				gp8psk_usb_out_op(d, CW3K_INIT, 1, 0, NULL, 0);
+			if (gp8psk_usb_in_op(d, BOOT_8PSK, 1, 0, &buf, 1))
+				return -EINVAL;
+			gp8psk_info(d);
+		}
+
+		if (gp_product_id == USB_PID_GENPIX_8PSK_REV_1_WARM)
+			if (! (status & bm8pskFW_Loaded)) /* BCM4500 firmware loaded */
+				if(gp8psk_load_bcm4500fw(d))
+					return -EINVAL;
+
+		if (! (status & bmIntersilOn)) /* LNB Power */
+			if (gp8psk_usb_in_op(d, START_INTERSIL, 1, 0,
+					&buf, 1))
+				return -EINVAL;
+
+		/* Set DVB mode to 1 */
+		if (gp_product_id == USB_PID_GENPIX_8PSK_REV_1_WARM)
+			if (gp8psk_usb_out_op(d, SET_DVB_MODE, 1, 0, NULL, 0))
+				return -EINVAL;
+		/* Abort possible TS (if previous tune crashed) */
+		if (gp8psk_usb_out_op(d, ARM_TRANSFER, 0, 0, NULL, 0))
+			return -EINVAL;
+	} else {
+		/* Turn off LNB power */
+		if (gp8psk_usb_in_op(d, START_INTERSIL, 0, 0, &buf, 1))
+			return -EINVAL;
+		/* Turn off 8psk power */
+		if (gp8psk_usb_in_op(d, BOOT_8PSK, 0, 0, &buf, 1))
+			return -EINVAL;
+		if(gp_product_id == USB_PID_GENPIX_SKYWALKER_CW3K)
+			gp8psk_usb_out_op(d, CW3K_INIT, 0, 0, NULL, 0);
+	}
+	return 0;
+}
+
+static int gp8psk_bcm4500_reload(struct dvb_usb_device *d)
+{
+	u8 buf;
+	int gp_product_id = le16_to_cpu(d->udev->descriptor.idProduct);
+
+	deb_xfer("reloading firmware\n");
+
+	/* Turn off 8psk power */
+	if (gp8psk_usb_in_op(d, BOOT_8PSK, 0, 0, &buf, 1))
+		return -EINVAL;
+	/* Turn On 8psk power */
+	if (gp8psk_usb_in_op(d, BOOT_8PSK, 1, 0, &buf, 1))
+		return -EINVAL;
+	/* load BCM4500 firmware */
+	if (gp_product_id == USB_PID_GENPIX_8PSK_REV_1_WARM)
+		if (gp8psk_load_bcm4500fw(d))
+			return -EINVAL;
+	return 0;
+}
+
+static int gp8psk_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff)
+{
+	return gp8psk_usb_out_op(adap->dev, ARM_TRANSFER, onoff, 0 , NULL, 0);
+}
+
+/* Callbacks for gp8psk-fe.c */
+
+static int gp8psk_fe_in(void *priv, u8 req, u16 value,
+			    u16 index, u8 *b, int blen)
+{
+	struct dvb_usb_device *d = priv;
+
+	return gp8psk_usb_in_op(d, req, value, index, b, blen);
+}
+
+static int gp8psk_fe_out(void *priv, u8 req, u16 value,
+			    u16 index, u8 *b, int blen)
+{
+	struct dvb_usb_device *d = priv;
+
+	return gp8psk_usb_out_op(d, req, value, index, b, blen);
+}
+
+static int gp8psk_fe_reload(void *priv)
+{
+	struct dvb_usb_device *d = priv;
+
+	return gp8psk_bcm4500_reload(d);
+}
+
+static const struct gp8psk_fe_ops gp8psk_fe_ops = {
+	.in = gp8psk_fe_in,
+	.out = gp8psk_fe_out,
+	.reload = gp8psk_fe_reload,
+};
+
+static int gp8psk_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_usb_device *d = adap->dev;
+	int id = le16_to_cpu(d->udev->descriptor.idProduct);
+	int is_rev1;
+
+	is_rev1 = (id == USB_PID_GENPIX_8PSK_REV_1_WARM) ? true : false;
+
+	adap->fe_adap[0].fe = dvb_attach(gp8psk_fe_attach,
+					 &gp8psk_fe_ops, d, is_rev1);
+	return 0;
+}
+
+static struct dvb_usb_device_properties gp8psk_properties;
+
+static int gp8psk_usb_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	int ret;
+	struct usb_device *udev = interface_to_usbdev(intf);
+	ret = dvb_usb_device_init(intf, &gp8psk_properties,
+				  THIS_MODULE, NULL, adapter_nr);
+	if (ret == 0) {
+		info("found Genpix USB device pID = %x (hex)",
+			le16_to_cpu(udev->descriptor.idProduct));
+	}
+	return ret;
+}
+
+static struct usb_device_id gp8psk_usb_table [] = {
+	    { USB_DEVICE(USB_VID_GENPIX, USB_PID_GENPIX_8PSK_REV_1_COLD) },
+	    { USB_DEVICE(USB_VID_GENPIX, USB_PID_GENPIX_8PSK_REV_1_WARM) },
+	    { USB_DEVICE(USB_VID_GENPIX, USB_PID_GENPIX_8PSK_REV_2) },
+	    { USB_DEVICE(USB_VID_GENPIX, USB_PID_GENPIX_SKYWALKER_1) },
+	    { USB_DEVICE(USB_VID_GENPIX, USB_PID_GENPIX_SKYWALKER_2) },
+/*	    { USB_DEVICE(USB_VID_GENPIX, USB_PID_GENPIX_SKYWALKER_CW3K) }, */
+	    { 0 },
+};
+MODULE_DEVICE_TABLE(usb, gp8psk_usb_table);
+
+static struct dvb_usb_device_properties gp8psk_properties = {
+	.usb_ctrl = CYPRESS_FX2,
+	.firmware = "dvb-usb-gp8psk-01.fw",
+
+	.size_of_priv = sizeof(struct gp8psk_state),
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.streaming_ctrl   = gp8psk_streaming_ctrl,
+			.frontend_attach  = gp8psk_frontend_attach,
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+				.count = 7,
+				.endpoint = 0x82,
+				.u = {
+					.bulk = {
+						.buffersize = 8192,
+					}
+				}
+			},
+		}},
+		}
+	},
+	.power_ctrl       = gp8psk_power_ctrl,
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.num_device_descs = 4,
+	.devices = {
+		{ .name = "Genpix 8PSK-to-USB2 Rev.1 DVB-S receiver",
+		  .cold_ids = { &gp8psk_usb_table[0], NULL },
+		  .warm_ids = { &gp8psk_usb_table[1], NULL },
+		},
+		{ .name = "Genpix 8PSK-to-USB2 Rev.2 DVB-S receiver",
+		  .cold_ids = { NULL },
+		  .warm_ids = { &gp8psk_usb_table[2], NULL },
+		},
+		{ .name = "Genpix SkyWalker-1 DVB-S receiver",
+		  .cold_ids = { NULL },
+		  .warm_ids = { &gp8psk_usb_table[3], NULL },
+		},
+		{ .name = "Genpix SkyWalker-2 DVB-S receiver",
+		  .cold_ids = { NULL },
+		  .warm_ids = { &gp8psk_usb_table[4], NULL },
+		},
+		{ NULL },
+	}
+};
+
+/* usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver gp8psk_usb_driver = {
+	.name		= "dvb_usb_gp8psk",
+	.probe		= gp8psk_usb_probe,
+	.disconnect = dvb_usb_device_exit,
+	.id_table	= gp8psk_usb_table,
+};
+
+module_usb_driver(gp8psk_usb_driver);
+
+MODULE_AUTHOR("Alan Nisota <alannisota@gamil.com>");
+MODULE_DESCRIPTION("Driver for Genpix DVB-S");
+MODULE_VERSION("1.1");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/gp8psk.h b/drivers/media/usb/dvb-usb/gp8psk.h
new file mode 100644
index 0000000..fd063e3
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/gp8psk.h
@@ -0,0 +1,37 @@
+/* DVB USB compliant Linux driver for the
+ *  - GENPIX 8pks/qpsk/DCII USB2.0 DVB-S module
+ *
+ * Copyright (C) 2006 Alan Nisota (alannisota@gmail.com)
+ * Copyright (C) 2006,2007 Alan Nisota (alannisota@gmail.com)
+ *
+ * Thanks to GENPIX for the sample code used to implement this module.
+ *
+ * This module is based off the vp7045 and vp702x modules
+ *
+ *	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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#ifndef _DVB_USB_GP8PSK_H_
+#define _DVB_USB_GP8PSK_H_
+
+#define DVB_USB_LOG_PREFIX "gp8psk"
+#include "dvb-usb.h"
+
+extern int dvb_usb_gp8psk_debug;
+#define deb_info(args...) dprintk(dvb_usb_gp8psk_debug,0x01,args)
+#define deb_xfer(args...) dprintk(dvb_usb_gp8psk_debug,0x02,args)
+#define deb_rc(args...)   dprintk(dvb_usb_gp8psk_debug,0x04,args)
+
+#define GET_USB_SPEED                     0x07
+
+#define RESET_FX2                         0x13
+
+#define FW_VERSION_READ                   0x0B
+#define VENDOR_STRING_READ                0x0C
+#define PRODUCT_STRING_READ               0x0D
+#define FW_BCD_VERSION_READ               0x14
+
+#endif
diff --git a/drivers/media/usb/dvb-usb/m920x.c b/drivers/media/usb/dvb-usb/m920x.c
new file mode 100644
index 0000000..22554d9
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/m920x.c
@@ -0,0 +1,1264 @@
+/* DVB USB compliant linux driver for MSI Mega Sky 580 DVB-T USB2.0 receiver
+ *
+ * Copyright (C) 2006 Aapo Tahkola (aet@rasterburn.org)
+ *
+ *	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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+
+#include "m920x.h"
+
+#include "mt352.h"
+#include "mt352_priv.h"
+#include "qt1010.h"
+#include "tda1004x.h"
+#include "tda827x.h"
+#include "mt2060.h"
+
+#include <media/tuner.h>
+#include "tuner-simple.h"
+#include <asm/unaligned.h>
+
+/* debug */
+static int dvb_usb_m920x_debug;
+module_param_named(debug,dvb_usb_m920x_debug, int, 0644);
+MODULE_PARM_DESC(debug, "set debugging level (1=rc (or-able))." DVB_USB_DEBUG_STATUS);
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static int m920x_set_filter(struct dvb_usb_device *d, int type, int idx, int pid);
+
+static inline int m920x_read(struct usb_device *udev, u8 request, u16 value,
+			     u16 index, void *data, int size)
+{
+	int ret;
+
+	ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+			      request, USB_TYPE_VENDOR | USB_DIR_IN,
+			      value, index, data, size, 2000);
+	if (ret < 0) {
+		printk(KERN_INFO "m920x_read = error: %d\n", ret);
+		return ret;
+	}
+
+	if (ret != size) {
+		deb("m920x_read = no data\n");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static inline int m920x_write(struct usb_device *udev, u8 request,
+			      u16 value, u16 index)
+{
+	return usb_control_msg(udev, usb_sndctrlpipe(udev, 0), request,
+			       USB_TYPE_VENDOR | USB_DIR_OUT, value, index,
+			       NULL, 0, 2000);
+}
+
+static inline int m920x_write_seq(struct usb_device *udev, u8 request,
+				  struct m920x_inits *seq)
+{
+	int ret;
+	do {
+		ret = m920x_write(udev, request, seq->data, seq->address);
+		if (ret != 0)
+			return ret;
+
+		seq++;
+	} while (seq->address);
+
+	return 0;
+}
+
+static int m920x_init(struct dvb_usb_device *d, struct m920x_inits *rc_seq)
+{
+	int ret, i, epi, flags = 0;
+	int adap_enabled[M9206_MAX_ADAPTERS] = { 0 };
+
+	/* Remote controller init. */
+	if (d->props.rc.legacy.rc_query || d->props.rc.core.rc_query) {
+		deb("Initialising remote control\n");
+		ret = m920x_write_seq(d->udev, M9206_CORE, rc_seq);
+		if (ret != 0) {
+			deb("Initialising remote control failed\n");
+			return ret;
+		}
+
+		deb("Initialising remote control success\n");
+	}
+
+	for (i = 0; i < d->props.num_adapters; i++)
+		flags |= d->adapter[i].props.fe[0].caps;
+
+	/* Some devices(Dposh) might crash if we attempt touch at all. */
+	if (flags & DVB_USB_ADAP_HAS_PID_FILTER) {
+		for (i = 0; i < d->props.num_adapters; i++) {
+			epi = d->adapter[i].props.fe[0].stream.endpoint - 0x81;
+
+			if (epi < 0 || epi >= M9206_MAX_ADAPTERS) {
+				printk(KERN_INFO "m920x: Unexpected adapter endpoint!\n");
+				return -EINVAL;
+			}
+
+			adap_enabled[epi] = 1;
+		}
+
+		for (i = 0; i < M9206_MAX_ADAPTERS; i++) {
+			if (adap_enabled[i])
+				continue;
+
+			if ((ret = m920x_set_filter(d, 0x81 + i, 0, 0x0)) != 0)
+				return ret;
+
+			if ((ret = m920x_set_filter(d, 0x81 + i, 0, 0x02f5)) != 0)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+static int m920x_init_ep(struct usb_interface *intf)
+{
+	struct usb_device *udev = interface_to_usbdev(intf);
+	struct usb_host_interface *alt;
+
+	if ((alt = usb_altnum_to_altsetting(intf, 1)) == NULL) {
+		deb("No alt found!\n");
+		return -ENODEV;
+	}
+
+	return usb_set_interface(udev, alt->desc.bInterfaceNumber,
+				 alt->desc.bAlternateSetting);
+}
+
+static inline void m920x_parse_rc_state(struct dvb_usb_device *d, u8 rc_state,
+					int *state)
+{
+	struct m920x_state *m = d->priv;
+
+	switch (rc_state) {
+	case 0x80:
+		*state = REMOTE_NO_KEY_PRESSED;
+		break;
+
+	case 0x88: /* framing error or "invalid code" */
+	case 0x99:
+	case 0xc0:
+	case 0xd8:
+		*state = REMOTE_NO_KEY_PRESSED;
+		m->rep_count = 0;
+		break;
+
+	case 0x93:
+	case 0x92:
+	case 0x83: /* pinnacle PCTV310e */
+	case 0x82:
+		m->rep_count = 0;
+		*state = REMOTE_KEY_PRESSED;
+		break;
+
+	case 0x91:
+	case 0x81: /* pinnacle PCTV310e */
+		/* prevent immediate auto-repeat */
+		if (++m->rep_count > 2)
+			*state = REMOTE_KEY_REPEAT;
+		else
+			*state = REMOTE_NO_KEY_PRESSED;
+		break;
+
+	default:
+		deb("Unexpected rc state %02x\n", rc_state);
+		*state = REMOTE_NO_KEY_PRESSED;
+		break;
+	}
+}
+
+static int m920x_rc_query(struct dvb_usb_device *d, u32 *event, int *state)
+{
+	int i, ret = 0;
+	u8 *rc_state;
+
+	rc_state = kmalloc(2, GFP_KERNEL);
+	if (!rc_state)
+		return -ENOMEM;
+
+	ret = m920x_read(d->udev, M9206_CORE, 0x0, M9206_RC_STATE,
+			 rc_state, 1);
+	if (ret != 0)
+		goto out;
+
+	ret = m920x_read(d->udev, M9206_CORE, 0x0, M9206_RC_KEY,
+			 rc_state + 1, 1);
+	if (ret != 0)
+		goto out;
+
+	m920x_parse_rc_state(d, rc_state[0], state);
+
+	for (i = 0; i < d->props.rc.legacy.rc_map_size; i++)
+		if (rc5_data(&d->props.rc.legacy.rc_map_table[i]) == rc_state[1]) {
+			*event = d->props.rc.legacy.rc_map_table[i].keycode;
+			goto out;
+		}
+
+	if (rc_state[1] != 0)
+		deb("Unknown rc key %02x\n", rc_state[1]);
+
+	*state = REMOTE_NO_KEY_PRESSED;
+
+ out:
+	kfree(rc_state);
+	return ret;
+}
+
+static int m920x_rc_core_query(struct dvb_usb_device *d)
+{
+	int ret = 0;
+	u8 *rc_state;
+	int state;
+
+	rc_state = kmalloc(2, GFP_KERNEL);
+	if (!rc_state)
+		return -ENOMEM;
+
+	if ((ret = m920x_read(d->udev, M9206_CORE, 0x0, M9206_RC_STATE, &rc_state[0], 1)) != 0)
+		goto out;
+
+	if ((ret = m920x_read(d->udev, M9206_CORE, 0x0, M9206_RC_KEY, &rc_state[1], 1)) != 0)
+		goto out;
+
+	deb("state=0x%02x keycode=0x%02x\n", rc_state[0], rc_state[1]);
+
+	m920x_parse_rc_state(d, rc_state[0], &state);
+
+	if (state == REMOTE_NO_KEY_PRESSED)
+		rc_keyup(d->rc_dev);
+	else if (state == REMOTE_KEY_REPEAT)
+		rc_repeat(d->rc_dev);
+	else
+		rc_keydown(d->rc_dev, RC_PROTO_UNKNOWN, rc_state[1], 0);
+
+out:
+	kfree(rc_state);
+	return ret;
+}
+
+/* I2C */
+static int m920x_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[], int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	int i, j;
+	int ret = 0;
+
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	for (i = 0; i < num; i++) {
+		if (msg[i].flags & (I2C_M_NO_RD_ACK | I2C_M_IGNORE_NAK | I2C_M_TEN) || msg[i].len == 0) {
+			/* For a 0 byte message, I think sending the address
+			 * to index 0x80|0x40 would be the correct thing to
+			 * do.  However, zero byte messages are only used for
+			 * probing, and since we don't know how to get the
+			 * slave's ack, we can't probe. */
+			ret = -ENOTSUPP;
+			goto unlock;
+		}
+		/* Send START & address/RW bit */
+		if (!(msg[i].flags & I2C_M_NOSTART)) {
+			if ((ret = m920x_write(d->udev, M9206_I2C,
+					(msg[i].addr << 1) |
+					(msg[i].flags & I2C_M_RD ? 0x01 : 0), 0x80)) != 0)
+				goto unlock;
+			/* Should check for ack here, if we knew how. */
+		}
+		if (msg[i].flags & I2C_M_RD) {
+			for (j = 0; j < msg[i].len; j++) {
+				/* Last byte of transaction?
+				 * Send STOP, otherwise send ACK. */
+				int stop = (i+1 == num && j+1 == msg[i].len) ? 0x40 : 0x01;
+
+				if ((ret = m920x_read(d->udev, M9206_I2C, 0x0,
+						      0x20 | stop,
+						      &msg[i].buf[j], 1)) != 0)
+					goto unlock;
+			}
+		} else {
+			for (j = 0; j < msg[i].len; j++) {
+				/* Last byte of transaction? Then send STOP. */
+				int stop = (i+1 == num && j+1 == msg[i].len) ? 0x40 : 0x00;
+
+				if ((ret = m920x_write(d->udev, M9206_I2C, msg[i].buf[j], stop)) != 0)
+					goto unlock;
+				/* Should check for ack here too. */
+			}
+		}
+	}
+	ret = num;
+
+ unlock:
+	mutex_unlock(&d->i2c_mutex);
+
+	return ret;
+}
+
+static u32 m920x_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm m920x_i2c_algo = {
+	.master_xfer   = m920x_i2c_xfer,
+	.functionality = m920x_i2c_func,
+};
+
+/* pid filter */
+static int m920x_set_filter(struct dvb_usb_device *d, int type, int idx, int pid)
+{
+	int ret = 0;
+
+	if (pid >= 0x8000)
+		return -EINVAL;
+
+	pid |= 0x8000;
+
+	if ((ret = m920x_write(d->udev, M9206_FILTER, pid, (type << 8) | (idx * 4) )) != 0)
+		return ret;
+
+	if ((ret = m920x_write(d->udev, M9206_FILTER, 0, (type << 8) | (idx * 4) )) != 0)
+		return ret;
+
+	return ret;
+}
+
+static int m920x_update_filters(struct dvb_usb_adapter *adap)
+{
+	struct m920x_state *m = adap->dev->priv;
+	int enabled = m->filtering_enabled[adap->id];
+	int i, ret = 0, filter = 0;
+	int ep = adap->props.fe[0].stream.endpoint;
+
+	for (i = 0; i < M9206_MAX_FILTERS; i++)
+		if (m->filters[adap->id][i] == 8192)
+			enabled = 0;
+
+	/* Disable all filters */
+	if ((ret = m920x_set_filter(adap->dev, ep, 1, enabled)) != 0)
+		return ret;
+
+	for (i = 0; i < M9206_MAX_FILTERS; i++)
+		if ((ret = m920x_set_filter(adap->dev, ep, i + 2, 0)) != 0)
+			return ret;
+
+	/* Set */
+	if (enabled) {
+		for (i = 0; i < M9206_MAX_FILTERS; i++) {
+			if (m->filters[adap->id][i] == 0)
+				continue;
+
+			if ((ret = m920x_set_filter(adap->dev, ep, filter + 2, m->filters[adap->id][i])) != 0)
+				return ret;
+
+			filter++;
+		}
+	}
+
+	return ret;
+}
+
+static int m920x_pid_filter_ctrl(struct dvb_usb_adapter *adap, int onoff)
+{
+	struct m920x_state *m = adap->dev->priv;
+
+	m->filtering_enabled[adap->id] = onoff ? 1 : 0;
+
+	return m920x_update_filters(adap);
+}
+
+static int m920x_pid_filter(struct dvb_usb_adapter *adap, int index, u16 pid, int onoff)
+{
+	struct m920x_state *m = adap->dev->priv;
+
+	m->filters[adap->id][index] = onoff ? pid : 0;
+
+	return m920x_update_filters(adap);
+}
+
+static int m920x_firmware_download(struct usb_device *udev, const struct firmware *fw)
+{
+	u16 value, index, size;
+	u8 *read, *buff;
+	int i, pass, ret = 0;
+
+	buff = kmalloc(65536, GFP_KERNEL);
+	if (buff == NULL)
+		return -ENOMEM;
+
+	read = kmalloc(4, GFP_KERNEL);
+	if (!read) {
+		kfree(buff);
+		return -ENOMEM;
+	}
+
+	if ((ret = m920x_read(udev, M9206_FILTER, 0x0, 0x8000, read, 4)) != 0)
+		goto done;
+	deb("%*ph\n", 4, read);
+
+	if ((ret = m920x_read(udev, M9206_FW, 0x0, 0x0, read, 1)) != 0)
+		goto done;
+	deb("%x\n", read[0]);
+
+	for (pass = 0; pass < 2; pass++) {
+		for (i = 0; i + (sizeof(u16) * 3) < fw->size;) {
+			value = get_unaligned_le16(fw->data + i);
+			i += sizeof(u16);
+
+			index = get_unaligned_le16(fw->data + i);
+			i += sizeof(u16);
+
+			size = get_unaligned_le16(fw->data + i);
+			i += sizeof(u16);
+
+			if (pass == 1) {
+				/* Will stall if using fw->data ... */
+				memcpy(buff, fw->data + i, size);
+
+				ret = usb_control_msg(udev, usb_sndctrlpipe(udev,0),
+						      M9206_FW,
+						      USB_TYPE_VENDOR | USB_DIR_OUT,
+						      value, index, buff, size, 20);
+				if (ret != size) {
+					deb("error while uploading fw!\n");
+					ret = -EIO;
+					goto done;
+				}
+				msleep(3);
+			}
+			i += size;
+		}
+		if (i != fw->size) {
+			deb("bad firmware file!\n");
+			ret = -EINVAL;
+			goto done;
+		}
+	}
+
+	msleep(36);
+
+	/* m920x will disconnect itself from the bus after this. */
+	(void) m920x_write(udev, M9206_CORE, 0x01, M9206_FW_GO);
+	deb("firmware uploaded!\n");
+
+ done:
+	kfree(read);
+	kfree(buff);
+
+	return ret;
+}
+
+/* Callbacks for DVB USB */
+static int m920x_identify_state(struct usb_device *udev,
+				struct dvb_usb_device_properties *props,
+				struct dvb_usb_device_description **desc,
+				int *cold)
+{
+	struct usb_host_interface *alt;
+
+	alt = usb_altnum_to_altsetting(usb_ifnum_to_if(udev, 0), 1);
+	*cold = (alt == NULL) ? 1 : 0;
+
+	return 0;
+}
+
+/* demod configurations */
+static int m920x_mt352_demod_init(struct dvb_frontend *fe)
+{
+	int ret;
+	u8 config[] = { CONFIG, 0x3d };
+	u8 clock[] = { CLOCK_CTL, 0x30 };
+	u8 reset[] = { RESET, 0x80 };
+	u8 adc_ctl[] = { ADC_CTL_1, 0x40 };
+	u8 agc[] = { AGC_TARGET, 0x1c, 0x20 };
+	u8 sec_agc[] = { 0x69, 0x00, 0xff, 0xff, 0x40, 0xff, 0x00, 0x40, 0x40 };
+	u8 unk1[] = { 0x93, 0x1a };
+	u8 unk2[] = { 0xb5, 0x7a };
+
+	deb("Demod init!\n");
+
+	if ((ret = mt352_write(fe, config, ARRAY_SIZE(config))) != 0)
+		return ret;
+	if ((ret = mt352_write(fe, clock, ARRAY_SIZE(clock))) != 0)
+		return ret;
+	if ((ret = mt352_write(fe, reset, ARRAY_SIZE(reset))) != 0)
+		return ret;
+	if ((ret = mt352_write(fe, adc_ctl, ARRAY_SIZE(adc_ctl))) != 0)
+		return ret;
+	if ((ret = mt352_write(fe, agc, ARRAY_SIZE(agc))) != 0)
+		return ret;
+	if ((ret = mt352_write(fe, sec_agc, ARRAY_SIZE(sec_agc))) != 0)
+		return ret;
+	if ((ret = mt352_write(fe, unk1, ARRAY_SIZE(unk1))) != 0)
+		return ret;
+	if ((ret = mt352_write(fe, unk2, ARRAY_SIZE(unk2))) != 0)
+		return ret;
+
+	return 0;
+}
+
+static struct mt352_config m920x_mt352_config = {
+	.demod_address = 0x0f,
+	.no_tuner = 1,
+	.demod_init = m920x_mt352_demod_init,
+};
+
+static struct tda1004x_config m920x_tda10046_08_config = {
+	.demod_address = 0x08,
+	.invert = 0,
+	.invert_oclk = 0,
+	.ts_mode = TDA10046_TS_SERIAL,
+	.xtal_freq = TDA10046_XTAL_16M,
+	.if_freq = TDA10046_FREQ_045,
+	.agc_config = TDA10046_AGC_TDA827X,
+	.gpio_config = TDA10046_GPTRI,
+	.request_firmware = NULL,
+};
+
+static struct tda1004x_config m920x_tda10046_0b_config = {
+	.demod_address = 0x0b,
+	.invert = 0,
+	.invert_oclk = 0,
+	.ts_mode = TDA10046_TS_SERIAL,
+	.xtal_freq = TDA10046_XTAL_16M,
+	.if_freq = TDA10046_FREQ_045,
+	.agc_config = TDA10046_AGC_TDA827X,
+	.gpio_config = TDA10046_GPTRI,
+	.request_firmware = NULL, /* uses firmware EEPROM */
+};
+
+/* tuner configurations */
+static struct qt1010_config m920x_qt1010_config = {
+	.i2c_address = 0x62
+};
+
+static struct mt2060_config m920x_mt2060_config = {
+	.i2c_address = 0x60, /* 0xc0 */
+	.clock_out = 0,
+};
+
+
+/* Callbacks for DVB USB */
+static int m920x_mt352_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	deb("%s\n",__func__);
+
+	adap->fe_adap[0].fe = dvb_attach(mt352_attach,
+					 &m920x_mt352_config,
+					 &adap->dev->i2c_adap);
+	if ((adap->fe_adap[0].fe) == NULL)
+		return -EIO;
+
+	return 0;
+}
+
+static int m920x_mt352_frontend_attach_vp7049(struct dvb_usb_adapter *adap)
+{
+	struct m920x_inits vp7049_fe_init_seq[] = {
+		/* XXX without these commands the frontend cannot be detected,
+		 * they must be sent BEFORE the frontend is attached */
+		{ 0xff28,         0x00 },
+		{ 0xff23,         0x00 },
+		{ 0xff28,         0x00 },
+		{ 0xff23,         0x00 },
+		{ 0xff21,         0x20 },
+		{ 0xff21,         0x60 },
+		{ 0xff28,         0x00 },
+		{ 0xff22,         0x00 },
+		{ 0xff20,         0x30 },
+		{ 0xff20,         0x20 },
+		{ 0xff20,         0x30 },
+		{ } /* terminating entry */
+	};
+	int ret;
+
+	deb("%s\n", __func__);
+
+	ret = m920x_write_seq(adap->dev->udev, M9206_CORE, vp7049_fe_init_seq);
+	if (ret != 0) {
+		deb("Initialization of vp7049 frontend failed.");
+		return ret;
+	}
+
+	return m920x_mt352_frontend_attach(adap);
+}
+
+static int m920x_tda10046_08_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	deb("%s\n",__func__);
+
+	adap->fe_adap[0].fe = dvb_attach(tda10046_attach,
+					 &m920x_tda10046_08_config,
+					 &adap->dev->i2c_adap);
+	if ((adap->fe_adap[0].fe) == NULL)
+		return -EIO;
+
+	return 0;
+}
+
+static int m920x_tda10046_0b_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	deb("%s\n",__func__);
+
+	adap->fe_adap[0].fe = dvb_attach(tda10046_attach,
+					 &m920x_tda10046_0b_config,
+					 &adap->dev->i2c_adap);
+	if ((adap->fe_adap[0].fe) == NULL)
+		return -EIO;
+
+	return 0;
+}
+
+static int m920x_qt1010_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	deb("%s\n",__func__);
+
+	if (dvb_attach(qt1010_attach, adap->fe_adap[0].fe, &adap->dev->i2c_adap, &m920x_qt1010_config) == NULL)
+		return -ENODEV;
+
+	return 0;
+}
+
+static int m920x_tda8275_60_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	deb("%s\n",__func__);
+
+	if (dvb_attach(tda827x_attach, adap->fe_adap[0].fe, 0x60, &adap->dev->i2c_adap, NULL) == NULL)
+		return -ENODEV;
+
+	return 0;
+}
+
+static int m920x_tda8275_61_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	deb("%s\n",__func__);
+
+	if (dvb_attach(tda827x_attach, adap->fe_adap[0].fe, 0x61, &adap->dev->i2c_adap, NULL) == NULL)
+		return -ENODEV;
+
+	return 0;
+}
+
+static int m920x_fmd1216me_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	dvb_attach(simple_tuner_attach, adap->fe_adap[0].fe,
+		   &adap->dev->i2c_adap, 0x61,
+		   TUNER_PHILIPS_FMD1216ME_MK3);
+	return 0;
+}
+
+static int m920x_mt2060_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	deb("%s\n", __func__);
+
+	if (dvb_attach(mt2060_attach, adap->fe_adap[0].fe, &adap->dev->i2c_adap,
+		       &m920x_mt2060_config, 1220) == NULL)
+		return -ENODEV;
+
+	return 0;
+}
+
+
+/* device-specific initialization */
+static struct m920x_inits megasky_rc_init [] = {
+	{ M9206_RC_INIT2, 0xa8 },
+	{ M9206_RC_INIT1, 0x51 },
+	{ } /* terminating entry */
+};
+
+static struct m920x_inits tvwalkertwin_rc_init [] = {
+	{ M9206_RC_INIT2, 0x00 },
+	{ M9206_RC_INIT1, 0xef },
+	{ 0xff28,         0x00 },
+	{ 0xff23,         0x00 },
+	{ 0xff21,         0x30 },
+	{ } /* terminating entry */
+};
+
+static struct m920x_inits pinnacle310e_init[] = {
+	/* without these the tuner doesn't work */
+	{ 0xff20,         0x9b },
+	{ 0xff22,         0x70 },
+
+	/* rc settings */
+	{ 0xff50,         0x80 },
+	{ M9206_RC_INIT1, 0x00 },
+	{ M9206_RC_INIT2, 0xff },
+	{ } /* terminating entry */
+};
+
+static struct m920x_inits vp7049_rc_init[] = {
+	{ 0xff28,         0x00 },
+	{ 0xff23,         0x00 },
+	{ 0xff21,         0x70 },
+	{ M9206_RC_INIT2, 0x00 },
+	{ M9206_RC_INIT1, 0xff },
+	{ } /* terminating entry */
+};
+
+/* ir keymaps */
+static struct rc_map_table rc_map_megasky_table[] = {
+	{ 0x0012, KEY_POWER },
+	{ 0x001e, KEY_CYCLEWINDOWS }, /* min/max */
+	{ 0x0002, KEY_CHANNELUP },
+	{ 0x0005, KEY_CHANNELDOWN },
+	{ 0x0003, KEY_VOLUMEUP },
+	{ 0x0006, KEY_VOLUMEDOWN },
+	{ 0x0004, KEY_MUTE },
+	{ 0x0007, KEY_OK }, /* TS */
+	{ 0x0008, KEY_STOP },
+	{ 0x0009, KEY_MENU }, /* swap */
+	{ 0x000a, KEY_REWIND },
+	{ 0x001b, KEY_PAUSE },
+	{ 0x001f, KEY_FASTFORWARD },
+	{ 0x000c, KEY_RECORD },
+	{ 0x000d, KEY_CAMERA }, /* screenshot */
+	{ 0x000e, KEY_COFFEE }, /* "MTS" */
+};
+
+static struct rc_map_table rc_map_tvwalkertwin_table[] = {
+	{ 0x0001, KEY_ZOOM }, /* Full Screen */
+	{ 0x0002, KEY_CAMERA }, /* snapshot */
+	{ 0x0003, KEY_MUTE },
+	{ 0x0004, KEY_REWIND },
+	{ 0x0005, KEY_PLAYPAUSE }, /* Play/Pause */
+	{ 0x0006, KEY_FASTFORWARD },
+	{ 0x0007, KEY_RECORD },
+	{ 0x0008, KEY_STOP },
+	{ 0x0009, KEY_TIME }, /* Timeshift */
+	{ 0x000c, KEY_COFFEE }, /* Recall */
+	{ 0x000e, KEY_CHANNELUP },
+	{ 0x0012, KEY_POWER },
+	{ 0x0015, KEY_MENU }, /* source */
+	{ 0x0018, KEY_CYCLEWINDOWS }, /* TWIN PIP */
+	{ 0x001a, KEY_CHANNELDOWN },
+	{ 0x001b, KEY_VOLUMEDOWN },
+	{ 0x001e, KEY_VOLUMEUP },
+};
+
+static struct rc_map_table rc_map_pinnacle310e_table[] = {
+	{ 0x16, KEY_POWER },
+	{ 0x17, KEY_FAVORITES },
+	{ 0x0f, KEY_TEXT },
+	{ 0x48, KEY_PROGRAM },		/* preview */
+	{ 0x1c, KEY_EPG },
+	{ 0x04, KEY_LIST },		/* record list */
+	{ 0x03, KEY_1 },
+	{ 0x01, KEY_2 },
+	{ 0x06, KEY_3 },
+	{ 0x09, KEY_4 },
+	{ 0x1d, KEY_5 },
+	{ 0x1f, KEY_6 },
+	{ 0x0d, KEY_7 },
+	{ 0x19, KEY_8 },
+	{ 0x1b, KEY_9 },
+	{ 0x15, KEY_0 },
+	{ 0x0c, KEY_CANCEL },
+	{ 0x4a, KEY_CLEAR },
+	{ 0x13, KEY_BACK },
+	{ 0x00, KEY_TAB },
+	{ 0x4b, KEY_UP },
+	{ 0x4e, KEY_LEFT },
+	{ 0x52, KEY_RIGHT },
+	{ 0x51, KEY_DOWN },
+	{ 0x4f, KEY_ENTER },		/* could also be KEY_OK */
+	{ 0x1e, KEY_VOLUMEUP },
+	{ 0x0a, KEY_VOLUMEDOWN },
+	{ 0x05, KEY_CHANNELUP },
+	{ 0x02, KEY_CHANNELDOWN },
+	{ 0x11, KEY_RECORD },
+	{ 0x14, KEY_PLAY },
+	{ 0x4c, KEY_PAUSE },
+	{ 0x1a, KEY_STOP },
+	{ 0x40, KEY_REWIND },
+	{ 0x12, KEY_FASTFORWARD },
+	{ 0x41, KEY_PREVIOUSSONG },	/* Replay */
+	{ 0x42, KEY_NEXTSONG },		/* Skip */
+	{ 0x54, KEY_CAMERA },		/* Capture */
+/*	{ 0x50, KEY_SAP },	*/		/* Sap */
+	{ 0x47, KEY_CYCLEWINDOWS },	/* Pip */
+	{ 0x4d, KEY_SCREEN },		/* FullScreen */
+	{ 0x08, KEY_SUBTITLE },
+	{ 0x0e, KEY_MUTE },
+/*	{ 0x49, KEY_LR },	*/		/* L/R */
+	{ 0x07, KEY_SLEEP },		/* Hibernate */
+	{ 0x08, KEY_VIDEO },		/* A/V */
+	{ 0x0e, KEY_MENU },		/* Recall */
+	{ 0x45, KEY_ZOOMIN },
+	{ 0x46, KEY_ZOOMOUT },
+	{ 0x18, KEY_RED },		/* Red */
+	{ 0x53, KEY_GREEN },		/* Green */
+	{ 0x5e, KEY_YELLOW },		/* Yellow */
+	{ 0x5f, KEY_BLUE },		/* Blue */
+};
+
+/* DVB USB Driver stuff */
+static struct dvb_usb_device_properties megasky_properties;
+static struct dvb_usb_device_properties digivox_mini_ii_properties;
+static struct dvb_usb_device_properties tvwalkertwin_properties;
+static struct dvb_usb_device_properties dposh_properties;
+static struct dvb_usb_device_properties pinnacle_pctv310e_properties;
+static struct dvb_usb_device_properties vp7049_properties;
+
+static int m920x_probe(struct usb_interface *intf,
+		       const struct usb_device_id *id)
+{
+	struct dvb_usb_device *d = NULL;
+	int ret;
+	struct m920x_inits *rc_init_seq = NULL;
+	int bInterfaceNumber = intf->cur_altsetting->desc.bInterfaceNumber;
+
+	deb("Probing for m920x device at interface %d\n", bInterfaceNumber);
+
+	if (bInterfaceNumber == 0) {
+		/* Single-tuner device, or first interface on
+		 * multi-tuner device
+		 */
+
+		ret = dvb_usb_device_init(intf, &megasky_properties,
+					  THIS_MODULE, &d, adapter_nr);
+		if (ret == 0) {
+			rc_init_seq = megasky_rc_init;
+			goto found;
+		}
+
+		ret = dvb_usb_device_init(intf, &digivox_mini_ii_properties,
+					  THIS_MODULE, &d, adapter_nr);
+		if (ret == 0) {
+			/* No remote control, so no rc_init_seq */
+			goto found;
+		}
+
+		/* This configures both tuners on the TV Walker Twin */
+		ret = dvb_usb_device_init(intf, &tvwalkertwin_properties,
+					  THIS_MODULE, &d, adapter_nr);
+		if (ret == 0) {
+			rc_init_seq = tvwalkertwin_rc_init;
+			goto found;
+		}
+
+		ret = dvb_usb_device_init(intf, &dposh_properties,
+					  THIS_MODULE, &d, adapter_nr);
+		if (ret == 0) {
+			/* Remote controller not supported yet. */
+			goto found;
+		}
+
+		ret = dvb_usb_device_init(intf, &pinnacle_pctv310e_properties,
+					  THIS_MODULE, &d, adapter_nr);
+		if (ret == 0) {
+			rc_init_seq = pinnacle310e_init;
+			goto found;
+		}
+
+		ret = dvb_usb_device_init(intf, &vp7049_properties,
+					  THIS_MODULE, &d, adapter_nr);
+		if (ret == 0) {
+			rc_init_seq = vp7049_rc_init;
+			goto found;
+		}
+
+		return ret;
+	} else {
+		/* Another interface on a multi-tuner device */
+
+		/* The LifeView TV Walker Twin gets here, but struct
+		 * tvwalkertwin_properties already configured both
+		 * tuners, so there is nothing for us to do here
+		 */
+	}
+
+ found:
+	if ((ret = m920x_init_ep(intf)) < 0)
+		return ret;
+
+	if (d && (ret = m920x_init(d, rc_init_seq)) != 0)
+		return ret;
+
+	return ret;
+}
+
+static struct usb_device_id m920x_table [] = {
+		{ USB_DEVICE(USB_VID_MSI, USB_PID_MSI_MEGASKY580) },
+		{ USB_DEVICE(USB_VID_ANUBIS_ELECTRONIC,
+			     USB_PID_MSI_DIGI_VOX_MINI_II) },
+		{ USB_DEVICE(USB_VID_ANUBIS_ELECTRONIC,
+			     USB_PID_LIFEVIEW_TV_WALKER_TWIN_COLD) },
+		{ USB_DEVICE(USB_VID_ANUBIS_ELECTRONIC,
+			     USB_PID_LIFEVIEW_TV_WALKER_TWIN_WARM) },
+		{ USB_DEVICE(USB_VID_DPOSH, USB_PID_DPOSH_M9206_COLD) },
+		{ USB_DEVICE(USB_VID_DPOSH, USB_PID_DPOSH_M9206_WARM) },
+		{ USB_DEVICE(USB_VID_VISIONPLUS, USB_PID_PINNACLE_PCTV310E) },
+		{ USB_DEVICE(USB_VID_AZUREWAVE, USB_PID_TWINHAN_VP7049) },
+		{ }		/* Terminating entry */
+};
+MODULE_DEVICE_TABLE (usb, m920x_table);
+
+static struct dvb_usb_device_properties megasky_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl = DEVICE_SPECIFIC,
+	.firmware = "dvb-usb-megasky-02.fw",
+	.download_firmware = m920x_firmware_download,
+
+	.rc.legacy = {
+		.rc_interval      = 100,
+		.rc_map_table     = rc_map_megasky_table,
+		.rc_map_size      = ARRAY_SIZE(rc_map_megasky_table),
+		.rc_query         = m920x_rc_query,
+	},
+
+	.size_of_priv     = sizeof(struct m920x_state),
+
+	.identify_state   = m920x_identify_state,
+	.num_adapters = 1,
+	.adapter = {{
+		.num_frontends = 1,
+		.fe = {{
+
+		.caps = DVB_USB_ADAP_HAS_PID_FILTER |
+			DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+
+		.pid_filter_count = 8,
+		.pid_filter       = m920x_pid_filter,
+		.pid_filter_ctrl  = m920x_pid_filter_ctrl,
+
+		.frontend_attach  = m920x_mt352_frontend_attach,
+		.tuner_attach     = m920x_qt1010_tuner_attach,
+
+		.stream = {
+			.type = USB_BULK,
+			.count = 8,
+			.endpoint = 0x81,
+			.u = {
+				.bulk = {
+					.buffersize = 512,
+				}
+			}
+		},
+		}},
+	}},
+	.i2c_algo         = &m920x_i2c_algo,
+
+	.num_device_descs = 1,
+	.devices = {
+		{   "MSI Mega Sky 580 DVB-T USB2.0",
+			{ &m920x_table[0], NULL },
+			{ NULL },
+		}
+	}
+};
+
+static struct dvb_usb_device_properties digivox_mini_ii_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl = DEVICE_SPECIFIC,
+	.firmware = "dvb-usb-digivox-02.fw",
+	.download_firmware = m920x_firmware_download,
+
+	.size_of_priv     = sizeof(struct m920x_state),
+
+	.identify_state   = m920x_identify_state,
+	.num_adapters = 1,
+	.adapter = {{
+		.num_frontends = 1,
+		.fe = {{
+
+		.caps = DVB_USB_ADAP_HAS_PID_FILTER |
+			DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+
+		.pid_filter_count = 8,
+		.pid_filter       = m920x_pid_filter,
+		.pid_filter_ctrl  = m920x_pid_filter_ctrl,
+
+		.frontend_attach  = m920x_tda10046_08_frontend_attach,
+		.tuner_attach     = m920x_tda8275_60_tuner_attach,
+
+		.stream = {
+			.type = USB_BULK,
+			.count = 8,
+			.endpoint = 0x81,
+			.u = {
+				.bulk = {
+					.buffersize = 0x4000,
+				}
+			}
+		},
+		}},
+	}},
+	.i2c_algo         = &m920x_i2c_algo,
+
+	.num_device_descs = 1,
+	.devices = {
+		{   "MSI DIGI VOX mini II DVB-T USB2.0",
+			{ &m920x_table[1], NULL },
+			{ NULL },
+		},
+	}
+};
+
+/* LifeView TV Walker Twin support by Nick Andrew <nick@nick-andrew.net>
+ *
+ * LifeView TV Walker Twin has 1 x M9206, 2 x TDA10046, 2 x TDA8275A
+ * TDA10046 #0 is located at i2c address 0x08
+ * TDA10046 #1 is located at i2c address 0x0b
+ * TDA8275A #0 is located at i2c address 0x60
+ * TDA8275A #1 is located at i2c address 0x61
+ */
+static struct dvb_usb_device_properties tvwalkertwin_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl = DEVICE_SPECIFIC,
+	.firmware = "dvb-usb-tvwalkert.fw",
+	.download_firmware = m920x_firmware_download,
+
+	.rc.legacy = {
+		.rc_interval      = 100,
+		.rc_map_table     = rc_map_tvwalkertwin_table,
+		.rc_map_size      = ARRAY_SIZE(rc_map_tvwalkertwin_table),
+		.rc_query         = m920x_rc_query,
+	},
+
+	.size_of_priv     = sizeof(struct m920x_state),
+
+	.identify_state   = m920x_identify_state,
+	.num_adapters = 2,
+	.adapter = {{
+		.num_frontends = 1,
+		.fe = {{
+
+		.caps = DVB_USB_ADAP_HAS_PID_FILTER |
+			DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+
+		.pid_filter_count = 8,
+		.pid_filter       = m920x_pid_filter,
+		.pid_filter_ctrl  = m920x_pid_filter_ctrl,
+
+		.frontend_attach  = m920x_tda10046_08_frontend_attach,
+		.tuner_attach     = m920x_tda8275_60_tuner_attach,
+
+		.stream = {
+			.type = USB_BULK,
+			.count = 8,
+			.endpoint = 0x81,
+			.u = {
+				 .bulk = {
+					 .buffersize = 512,
+				 }
+			}
+		}},
+		}},{
+		.num_frontends = 1,
+		.fe = {{
+
+		.caps = DVB_USB_ADAP_HAS_PID_FILTER |
+			DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+
+		.pid_filter_count = 8,
+		.pid_filter       = m920x_pid_filter,
+		.pid_filter_ctrl  = m920x_pid_filter_ctrl,
+
+		.frontend_attach  = m920x_tda10046_0b_frontend_attach,
+		.tuner_attach     = m920x_tda8275_61_tuner_attach,
+
+		.stream = {
+			.type = USB_BULK,
+			.count = 8,
+			.endpoint = 0x82,
+			.u = {
+				 .bulk = {
+					 .buffersize = 512,
+				 }
+			}
+		}},
+		},
+	}},
+	.i2c_algo         = &m920x_i2c_algo,
+
+	.num_device_descs = 1,
+	.devices = {
+		{   .name = "LifeView TV Walker Twin DVB-T USB2.0",
+		    .cold_ids = { &m920x_table[2], NULL },
+		    .warm_ids = { &m920x_table[3], NULL },
+		},
+	}
+};
+
+static struct dvb_usb_device_properties dposh_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl = DEVICE_SPECIFIC,
+	.firmware = "dvb-usb-dposh-01.fw",
+	.download_firmware = m920x_firmware_download,
+
+	.size_of_priv     = sizeof(struct m920x_state),
+
+	.identify_state   = m920x_identify_state,
+	.num_adapters = 1,
+	.adapter = {{
+		.num_frontends = 1,
+		.fe = {{
+		/* Hardware pid filters don't work with this device/firmware */
+
+		.frontend_attach  = m920x_mt352_frontend_attach,
+		.tuner_attach     = m920x_qt1010_tuner_attach,
+
+		.stream = {
+			.type = USB_BULK,
+			.count = 8,
+			.endpoint = 0x81,
+			.u = {
+				 .bulk = {
+					 .buffersize = 512,
+				 }
+			}
+		},
+		}},
+	}},
+	.i2c_algo         = &m920x_i2c_algo,
+
+	.num_device_descs = 1,
+	.devices = {
+		 {   .name = "Dposh DVB-T USB2.0",
+		     .cold_ids = { &m920x_table[4], NULL },
+		     .warm_ids = { &m920x_table[5], NULL },
+		 },
+	 }
+};
+
+static struct dvb_usb_device_properties pinnacle_pctv310e_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl = DEVICE_SPECIFIC,
+	.download_firmware = NULL,
+
+	.rc.legacy = {
+		.rc_interval      = 100,
+		.rc_map_table     = rc_map_pinnacle310e_table,
+		.rc_map_size      = ARRAY_SIZE(rc_map_pinnacle310e_table),
+		.rc_query         = m920x_rc_query,
+	},
+
+	.size_of_priv     = sizeof(struct m920x_state),
+
+	.identify_state   = m920x_identify_state,
+	.num_adapters = 1,
+	.adapter = {{
+		.num_frontends = 1,
+		.fe = {{
+
+		.caps = DVB_USB_ADAP_HAS_PID_FILTER |
+			DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+
+		.pid_filter_count = 8,
+		.pid_filter       = m920x_pid_filter,
+		.pid_filter_ctrl  = m920x_pid_filter_ctrl,
+
+		.frontend_attach  = m920x_mt352_frontend_attach,
+		.tuner_attach     = m920x_fmd1216me_tuner_attach,
+
+		.stream = {
+			.type = USB_ISOC,
+			.count = 5,
+			.endpoint = 0x84,
+			.u = {
+				.isoc = {
+					.framesperurb = 128,
+					.framesize = 564,
+					.interval = 1,
+				}
+			}
+		},
+		}},
+	} },
+	.i2c_algo         = &m920x_i2c_algo,
+
+	.num_device_descs = 1,
+	.devices = {
+		{   "Pinnacle PCTV 310e",
+			{ &m920x_table[6], NULL },
+			{ NULL },
+		}
+	}
+};
+
+static struct dvb_usb_device_properties vp7049_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl = DEVICE_SPECIFIC,
+	.firmware = "dvb-usb-vp7049-0.95.fw",
+	.download_firmware = m920x_firmware_download,
+
+	.rc.core = {
+		.rc_interval    = 150,
+		.rc_codes       = RC_MAP_TWINHAN_VP1027_DVBS,
+		.rc_query       = m920x_rc_core_query,
+		.allowed_protos = RC_PROTO_BIT_UNKNOWN,
+	},
+
+	.size_of_priv     = sizeof(struct m920x_state),
+
+	.identify_state   = m920x_identify_state,
+	.num_adapters = 1,
+	.adapter = {{
+		.num_frontends = 1,
+		.fe = {{
+
+		.caps = DVB_USB_ADAP_HAS_PID_FILTER |
+			DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+
+		.pid_filter_count = 8,
+		.pid_filter       = m920x_pid_filter,
+		.pid_filter_ctrl  = m920x_pid_filter_ctrl,
+
+		.frontend_attach  = m920x_mt352_frontend_attach_vp7049,
+		.tuner_attach     = m920x_mt2060_tuner_attach,
+
+		.stream = {
+			.type = USB_BULK,
+			.count = 8,
+			.endpoint = 0x81,
+			.u = {
+				 .bulk = {
+					 .buffersize = 512,
+				 }
+			}
+		},
+		} },
+	} },
+	.i2c_algo         = &m920x_i2c_algo,
+
+	.num_device_descs = 1,
+	.devices = {
+		{   "DTV-DVB UDTT7049",
+			{ &m920x_table[7], NULL },
+			{ NULL },
+		}
+	 }
+};
+
+static struct usb_driver m920x_driver = {
+	.name		= "dvb_usb_m920x",
+	.probe		= m920x_probe,
+	.disconnect	= dvb_usb_device_exit,
+	.id_table	= m920x_table,
+};
+
+module_usb_driver(m920x_driver);
+
+MODULE_AUTHOR("Aapo Tahkola <aet@rasterburn.org>");
+MODULE_DESCRIPTION("DVB Driver for ULI M920x");
+MODULE_VERSION("0.1");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/m920x.h b/drivers/media/usb/dvb-usb/m920x.h
new file mode 100644
index 0000000..bab3c6a
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/m920x.h
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _DVB_USB_M920X_H_
+#define _DVB_USB_M920X_H_
+
+#define DVB_USB_LOG_PREFIX "m920x"
+#include "dvb-usb.h"
+
+#define deb(args...)   dprintk(dvb_usb_m920x_debug,0x01,args)
+
+#define M9206_CORE	0x22
+#define M9206_RC_STATE	0xff51
+#define M9206_RC_KEY	0xff52
+#define M9206_RC_INIT1	0xff54
+#define M9206_RC_INIT2	0xff55
+#define M9206_FW_GO	0xff69
+
+#define M9206_I2C	0x23
+#define M9206_FILTER	0x25
+#define M9206_FW	0x30
+
+#define M9206_MAX_FILTERS 8
+#define M9206_MAX_ADAPTERS 4
+
+/*
+sequences found in logs:
+[index value]
+0x80 write addr
+(0x00 out byte)*
+0x40 out byte
+
+0x80 write addr
+(0x00 out byte)*
+0x80 read addr
+(0x21 in byte)*
+0x60 in byte
+
+this sequence works:
+0x80 read addr
+(0x21 in byte)*
+0x60 in byte
+
+Guess at API of the I2C function:
+I2C operation is done one byte at a time with USB control messages.  The
+index the messages is sent to is made up of a set of flags that control
+the I2C bus state:
+0x80:  Send START condition.  After a START condition, one would normally
+       always send the 7-bit slave I2C address as the 7 MSB, followed by
+       the read/write bit as the LSB.
+0x40:  Send STOP condition.  This should be set on the last byte of an
+       I2C transaction.
+0x20:  Read a byte from the slave.  As opposed to writing a byte to the
+       slave.  The slave will normally not produce any data unless you
+       set the R/W bit to 1 when sending the slave's address after the
+       START condition.
+0x01:  Respond with ACK, as opposed to a NACK.  For a multi-byte read,
+       the master should send an ACK, that is pull SDA low during the 9th
+       clock cycle, after every byte but the last.  This flags only makes
+       sense when bit 0x20 is set, indicating a read.
+
+What any other bits might mean, or how to get the slave's ACK/NACK
+response to a write, is unknown.
+*/
+
+struct m920x_state {
+	u16 filters[M9206_MAX_ADAPTERS][M9206_MAX_FILTERS];
+	int filtering_enabled[M9206_MAX_ADAPTERS];
+	int rep_count;
+};
+
+/* Initialisation data for the m920x
+ */
+
+struct m920x_inits {
+	u16 address;
+	u8  data;
+};
+
+#endif
diff --git a/drivers/media/usb/dvb-usb/nova-t-usb2.c b/drivers/media/usb/dvb-usb/nova-t-usb2.c
new file mode 100644
index 0000000..43e0e0f
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/nova-t-usb2.c
@@ -0,0 +1,244 @@
+/* DVB USB framework compliant Linux driver for the Hauppauge WinTV-NOVA-T usb2
+ * DVB-T receiver.
+ *
+ * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@posteo.de)
+ *
+ *	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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#include "dibusb.h"
+
+static int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "set debugging level (1=rc,2=eeprom (|-able))." DVB_USB_DEBUG_STATUS);
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+#define deb_rc(args...) dprintk(debug,0x01,args)
+#define deb_ee(args...) dprintk(debug,0x02,args)
+
+/* Hauppauge NOVA-T USB2 keys */
+static struct rc_map_table rc_map_haupp_table[] = {
+	{ 0x1e00, KEY_0 },
+	{ 0x1e01, KEY_1 },
+	{ 0x1e02, KEY_2 },
+	{ 0x1e03, KEY_3 },
+	{ 0x1e04, KEY_4 },
+	{ 0x1e05, KEY_5 },
+	{ 0x1e06, KEY_6 },
+	{ 0x1e07, KEY_7 },
+	{ 0x1e08, KEY_8 },
+	{ 0x1e09, KEY_9 },
+	{ 0x1e0a, KEY_KPASTERISK },
+	{ 0x1e0b, KEY_RED },
+	{ 0x1e0c, KEY_RADIO },
+	{ 0x1e0d, KEY_MENU },
+	{ 0x1e0e, KEY_GRAVE }, /* # */
+	{ 0x1e0f, KEY_MUTE },
+	{ 0x1e10, KEY_VOLUMEUP },
+	{ 0x1e11, KEY_VOLUMEDOWN },
+	{ 0x1e12, KEY_CHANNEL },
+	{ 0x1e14, KEY_UP },
+	{ 0x1e15, KEY_DOWN },
+	{ 0x1e16, KEY_LEFT },
+	{ 0x1e17, KEY_RIGHT },
+	{ 0x1e18, KEY_VIDEO },
+	{ 0x1e19, KEY_AUDIO },
+	{ 0x1e1a, KEY_IMAGES },
+	{ 0x1e1b, KEY_EPG },
+	{ 0x1e1c, KEY_TV },
+	{ 0x1e1e, KEY_NEXT },
+	{ 0x1e1f, KEY_BACK },
+	{ 0x1e20, KEY_CHANNELUP },
+	{ 0x1e21, KEY_CHANNELDOWN },
+	{ 0x1e24, KEY_LAST }, /* Skip backwards */
+	{ 0x1e25, KEY_OK },
+	{ 0x1e29, KEY_BLUE},
+	{ 0x1e2e, KEY_GREEN },
+	{ 0x1e30, KEY_PAUSE },
+	{ 0x1e32, KEY_REWIND },
+	{ 0x1e34, KEY_FASTFORWARD },
+	{ 0x1e35, KEY_PLAY },
+	{ 0x1e36, KEY_STOP },
+	{ 0x1e37, KEY_RECORD },
+	{ 0x1e38, KEY_YELLOW },
+	{ 0x1e3b, KEY_GOTO },
+	{ 0x1e3d, KEY_POWER },
+};
+
+/* Firmware bug? sometimes, when a new key is pressed, the previous pressed key
+ * is delivered. No workaround yet, maybe a new firmware.
+ */
+static int nova_t_rc_query(struct dvb_usb_device *d, u32 *event, int *state)
+{
+	u8 *buf, data, toggle, custom;
+	u16 raw;
+	int i, ret;
+	struct dibusb_device_state *st = d->priv;
+
+	buf = kmalloc(5, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	buf[0] = DIBUSB_REQ_POLL_REMOTE;
+	buf[1] = 0x35;
+	ret = dvb_usb_generic_rw(d, buf, 2, buf, 5, 0);
+	if (ret < 0)
+		goto ret;
+
+	*state = REMOTE_NO_KEY_PRESSED;
+	switch (buf[0]) {
+		case DIBUSB_RC_HAUPPAUGE_KEY_PRESSED:
+			raw = ((buf[1] << 8) | buf[2]) >> 3;
+			toggle = !!(raw & 0x800);
+			data = raw & 0x3f;
+			custom = (raw >> 6) & 0x1f;
+
+			deb_rc("raw key code 0x%02x, 0x%02x, 0x%02x to c: %02x d: %02x toggle: %d\n",
+			       buf[1], buf[2], buf[3], custom, data, toggle);
+
+			for (i = 0; i < ARRAY_SIZE(rc_map_haupp_table); i++) {
+				if (rc5_data(&rc_map_haupp_table[i]) == data &&
+					rc5_custom(&rc_map_haupp_table[i]) == custom) {
+
+					deb_rc("c: %x, d: %x\n", rc5_data(&rc_map_haupp_table[i]),
+								 rc5_custom(&rc_map_haupp_table[i]));
+
+					*event = rc_map_haupp_table[i].keycode;
+					*state = REMOTE_KEY_PRESSED;
+					if (st->old_toggle == toggle) {
+						if (st->last_repeat_count++ < 2)
+							*state = REMOTE_NO_KEY_PRESSED;
+					} else {
+						st->last_repeat_count = 0;
+						st->old_toggle = toggle;
+					}
+					break;
+				}
+			}
+
+			break;
+		case DIBUSB_RC_HAUPPAUGE_KEY_EMPTY:
+		default:
+			break;
+	}
+
+ret:
+	kfree(buf);
+	return ret;
+}
+
+static int nova_t_read_mac_address (struct dvb_usb_device *d, u8 mac[6])
+{
+	int i;
+	u8 b;
+
+	mac[0] = 0x00;
+	mac[1] = 0x0d;
+	mac[2] = 0xfe;
+
+	/* this is a complete guess, but works for my box */
+	for (i = 136; i < 139; i++) {
+		dibusb_read_eeprom_byte(d,i, &b);
+
+		mac[5 - (i - 136)] = b;
+	}
+
+	return 0;
+}
+
+/* USB Driver stuff */
+static struct dvb_usb_device_properties nova_t_properties;
+
+static int nova_t_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	return dvb_usb_device_init(intf, &nova_t_properties,
+				   THIS_MODULE, NULL, adapter_nr);
+}
+
+/* do not change the order of the ID table */
+static struct usb_device_id nova_t_table [] = {
+/* 00 */	{ USB_DEVICE(USB_VID_HAUPPAUGE,     USB_PID_WINTV_NOVA_T_USB2_COLD) },
+/* 01 */	{ USB_DEVICE(USB_VID_HAUPPAUGE,     USB_PID_WINTV_NOVA_T_USB2_WARM) },
+			{ }		/* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, nova_t_table);
+
+static struct dvb_usb_device_properties nova_t_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl = CYPRESS_FX2,
+	.firmware = "dvb-usb-nova-t-usb2-02.fw",
+
+	.num_adapters     = 1,
+	.adapter          = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.caps = DVB_USB_ADAP_HAS_PID_FILTER | DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+			.pid_filter_count = 32,
+
+			.streaming_ctrl   = dibusb2_0_streaming_ctrl,
+			.pid_filter       = dibusb_pid_filter,
+			.pid_filter_ctrl  = dibusb_pid_filter_ctrl,
+			.frontend_attach  = dibusb_dib3000mc_frontend_attach,
+			.tuner_attach     = dibusb_dib3000mc_tuner_attach,
+
+			/* parameter for the MPEG2-data transfer */
+					.stream = {
+						.type = USB_BULK,
+				.count = 7,
+				.endpoint = 0x06,
+				.u = {
+					.bulk = {
+						.buffersize = 4096,
+					}
+				}
+			},
+		}},
+			.size_of_priv     = sizeof(struct dibusb_state),
+		}
+	},
+	.size_of_priv     = sizeof(struct dibusb_device_state),
+
+	.power_ctrl       = dibusb2_0_power_ctrl,
+	.read_mac_address = nova_t_read_mac_address,
+
+	.rc.legacy = {
+		.rc_interval      = 100,
+		.rc_map_table     = rc_map_haupp_table,
+		.rc_map_size      = ARRAY_SIZE(rc_map_haupp_table),
+		.rc_query         = nova_t_rc_query,
+	},
+
+	.i2c_algo         = &dibusb_i2c_algo,
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.num_device_descs = 1,
+	.devices = {
+		{   "Hauppauge WinTV-NOVA-T usb2",
+			{ &nova_t_table[0], NULL },
+			{ &nova_t_table[1], NULL },
+		},
+		{ NULL },
+	}
+};
+
+static struct usb_driver nova_t_driver = {
+	.name		= "dvb_usb_nova_t_usb2",
+	.probe		= nova_t_probe,
+	.disconnect = dvb_usb_device_exit,
+	.id_table	= nova_t_table,
+};
+
+module_usb_driver(nova_t_driver);
+
+MODULE_AUTHOR("Patrick Boettcher <patrick.boettcher@posteo.de>");
+MODULE_DESCRIPTION("Hauppauge WinTV-NOVA-T usb2");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/opera1.c b/drivers/media/usb/dvb-usb/opera1.c
new file mode 100644
index 0000000..61a377e
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/opera1.c
@@ -0,0 +1,583 @@
+/* DVB USB framework compliant Linux driver for the Opera1 DVB-S Card
+*
+* Copyright (C) 2006 Mario Hlawitschka (dh1pa@amsat.org)
+* Copyright (C) 2006 Marco Gittler (g.marco@freenet.de)
+*
+*	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, version 2.
+*
+* see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+*/
+
+#define DVB_USB_LOG_PREFIX "opera"
+
+#include "dvb-usb.h"
+#include "stv0299.h"
+
+#define OPERA_READ_MSG 0
+#define OPERA_WRITE_MSG 1
+#define OPERA_I2C_TUNER 0xd1
+
+#define READ_FX2_REG_REQ  0xba
+#define READ_MAC_ADDR 0x08
+#define OPERA_WRITE_FX2 0xbb
+#define OPERA_TUNER_REQ 0xb1
+#define REG_1F_SYMBOLRATE_BYTE0 0x1f
+#define REG_20_SYMBOLRATE_BYTE1 0x20
+#define REG_21_SYMBOLRATE_BYTE2 0x21
+
+#define ADDR_B600_VOLTAGE_13V (0x02)
+#define ADDR_B601_VOLTAGE_18V (0x03)
+#define ADDR_B1A6_STREAM_CTRL (0x04)
+#define ADDR_B880_READ_REMOTE (0x05)
+
+struct opera1_state {
+	u32 last_key_pressed;
+};
+struct rc_map_opera_table {
+	u32 keycode;
+	u32 event;
+};
+
+static int dvb_usb_opera1_debug;
+module_param_named(debug, dvb_usb_opera1_debug, int, 0644);
+MODULE_PARM_DESC(debug,
+		 "set debugging level (1=info,xfer=2,pll=4,ts=8,err=16,rc=32,fw=64 (or-able))."
+		 DVB_USB_DEBUG_STATUS);
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+
+static int opera1_xilinx_rw(struct usb_device *dev, u8 request, u16 value,
+			    u8 * data, u16 len, int flags)
+{
+	int ret;
+	u8 tmp;
+	u8 *buf;
+	unsigned int pipe = (flags == OPERA_READ_MSG) ?
+		usb_rcvctrlpipe(dev,0) : usb_sndctrlpipe(dev, 0);
+	u8 request_type = (flags == OPERA_READ_MSG) ? USB_DIR_IN : USB_DIR_OUT;
+
+	buf = kmalloc(len, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	if (flags == OPERA_WRITE_MSG)
+		memcpy(buf, data, len);
+	ret = usb_control_msg(dev, pipe, request,
+			request_type | USB_TYPE_VENDOR, value, 0x0,
+			buf, len, 2000);
+
+	if (request == OPERA_TUNER_REQ) {
+		tmp = buf[0];
+		if (usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+			    OPERA_TUNER_REQ, USB_DIR_IN | USB_TYPE_VENDOR,
+			    0x01, 0x0, buf, 1, 2000) < 1 || buf[0] != 0x08) {
+			ret = 0;
+			goto out;
+		}
+		buf[0] = tmp;
+	}
+	if (flags == OPERA_READ_MSG)
+		memcpy(data, buf, len);
+out:
+	kfree(buf);
+	return ret;
+}
+
+/* I2C */
+
+static int opera1_usb_i2c_msgxfer(struct dvb_usb_device *dev, u16 addr,
+				  u8 * buf, u16 len)
+{
+	int ret = 0;
+	u8 request;
+	u16 value;
+
+	if (!dev) {
+		info("no usb_device");
+		return -EINVAL;
+	}
+	if (mutex_lock_interruptible(&dev->usb_mutex) < 0)
+		return -EAGAIN;
+
+	switch (addr>>1){
+		case ADDR_B600_VOLTAGE_13V:
+			request=0xb6;
+			value=0x00;
+			break;
+		case ADDR_B601_VOLTAGE_18V:
+			request=0xb6;
+			value=0x01;
+			break;
+		case ADDR_B1A6_STREAM_CTRL:
+			request=0xb1;
+			value=0xa6;
+			break;
+		case ADDR_B880_READ_REMOTE:
+			request=0xb8;
+			value=0x80;
+			break;
+		default:
+			request=0xb1;
+			value=addr;
+	}
+	ret = opera1_xilinx_rw(dev->udev, request,
+		value, buf, len,
+		addr&0x01?OPERA_READ_MSG:OPERA_WRITE_MSG);
+
+	mutex_unlock(&dev->usb_mutex);
+	return ret;
+}
+
+static int opera1_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msg[],
+			   int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	int i = 0, tmp = 0;
+
+	if (!d)
+		return -ENODEV;
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	for (i = 0; i < num; i++) {
+		if ((tmp = opera1_usb_i2c_msgxfer(d,
+					(msg[i].addr<<1)|(msg[i].flags&I2C_M_RD?0x01:0),
+					msg[i].buf,
+					msg[i].len
+					)) != msg[i].len) {
+			break;
+		}
+		if (dvb_usb_opera1_debug & 0x10)
+			info("sending i2c message %d %d", tmp, msg[i].len);
+	}
+	mutex_unlock(&d->i2c_mutex);
+	return num;
+}
+
+static u32 opera1_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm opera1_i2c_algo = {
+	.master_xfer = opera1_i2c_xfer,
+	.functionality = opera1_i2c_func,
+};
+
+static int opera1_set_voltage(struct dvb_frontend *fe,
+			      enum fe_sec_voltage voltage)
+{
+	static u8 command_13v[1]={0x00};
+	static u8 command_18v[1]={0x01};
+	struct i2c_msg msg[] = {
+		{.addr = ADDR_B600_VOLTAGE_13V,.flags = 0,.buf = command_13v,.len = 1},
+	};
+	struct dvb_usb_adapter *udev_adap =
+	    (struct dvb_usb_adapter *)(fe->dvb->priv);
+	if (voltage == SEC_VOLTAGE_18) {
+		msg[0].addr = ADDR_B601_VOLTAGE_18V;
+		msg[0].buf = command_18v;
+	}
+	i2c_transfer(&udev_adap->dev->i2c_adap, msg, 1);
+	return 0;
+}
+
+static int opera1_stv0299_set_symbol_rate(struct dvb_frontend *fe, u32 srate,
+					  u32 ratio)
+{
+	stv0299_writereg(fe, 0x13, 0x98);
+	stv0299_writereg(fe, 0x14, 0x95);
+	stv0299_writereg(fe, REG_1F_SYMBOLRATE_BYTE0, (ratio >> 16) & 0xff);
+	stv0299_writereg(fe, REG_20_SYMBOLRATE_BYTE1, (ratio >> 8) & 0xff);
+	stv0299_writereg(fe, REG_21_SYMBOLRATE_BYTE2, (ratio) & 0xf0);
+	return 0;
+
+}
+static u8 opera1_inittab[] = {
+	0x00, 0xa1,
+	0x01, 0x15,
+	0x02, 0x30,
+	0x03, 0x00,
+	0x04, 0x7d,
+	0x05, 0x05,
+	0x06, 0x02,
+	0x07, 0x00,
+	0x0b, 0x00,
+	0x0c, 0x01,
+	0x0d, 0x81,
+	0x0e, 0x44,
+	0x0f, 0x19,
+	0x10, 0x3f,
+	0x11, 0x84,
+	0x12, 0xda,
+	0x13, 0x98,
+	0x14, 0x95,
+	0x15, 0xc9,
+	0x16, 0xeb,
+	0x17, 0x00,
+	0x18, 0x19,
+	0x19, 0x8b,
+	0x1a, 0x00,
+	0x1b, 0x82,
+	0x1c, 0x7f,
+	0x1d, 0x00,
+	0x1e, 0x00,
+	REG_1F_SYMBOLRATE_BYTE0, 0x06,
+	REG_20_SYMBOLRATE_BYTE1, 0x50,
+	REG_21_SYMBOLRATE_BYTE2, 0x10,
+	0x22, 0x00,
+	0x23, 0x00,
+	0x24, 0x37,
+	0x25, 0xbc,
+	0x26, 0x00,
+	0x27, 0x00,
+	0x28, 0x00,
+	0x29, 0x1e,
+	0x2a, 0x14,
+	0x2b, 0x1f,
+	0x2c, 0x09,
+	0x2d, 0x0a,
+	0x2e, 0x00,
+	0x2f, 0x00,
+	0x30, 0x00,
+	0x31, 0x1f,
+	0x32, 0x19,
+	0x33, 0xfc,
+	0x34, 0x13,
+	0xff, 0xff,
+};
+
+static struct stv0299_config opera1_stv0299_config = {
+	.demod_address = 0xd0>>1,
+	.min_delay_ms = 100,
+	.mclk = 88000000UL,
+	.invert = 1,
+	.skip_reinit = 0,
+	.lock_output = STV0299_LOCKOUTPUT_0,
+	.volt13_op0_op1 = STV0299_VOLT13_OP0,
+	.inittab = opera1_inittab,
+	.set_symbol_rate = opera1_stv0299_set_symbol_rate,
+};
+
+static int opera1_frontend_attach(struct dvb_usb_adapter *d)
+{
+	d->fe_adap[0].fe = dvb_attach(stv0299_attach, &opera1_stv0299_config,
+				      &d->dev->i2c_adap);
+	if ((d->fe_adap[0].fe) != NULL) {
+		d->fe_adap[0].fe->ops.set_voltage = opera1_set_voltage;
+		return 0;
+	}
+	info("not attached stv0299");
+	return -EIO;
+}
+
+static int opera1_tuner_attach(struct dvb_usb_adapter *adap)
+{
+	dvb_attach(
+		dvb_pll_attach, adap->fe_adap[0].fe, 0xc0>>1,
+		&adap->dev->i2c_adap, DVB_PLL_OPERA1
+	);
+	return 0;
+}
+
+static int opera1_power_ctrl(struct dvb_usb_device *d, int onoff)
+{
+	u8 val = onoff ? 0x01 : 0x00;
+
+	if (dvb_usb_opera1_debug)
+		info("power %s", onoff ? "on" : "off");
+	return opera1_xilinx_rw(d->udev, 0xb7, val,
+				&val, 1, OPERA_WRITE_MSG);
+}
+
+static int opera1_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff)
+{
+	static u8 buf_start[2] = { 0xff, 0x03 };
+	static u8 buf_stop[2] = { 0xff, 0x00 };
+	struct i2c_msg start_tuner[] = {
+		{.addr = ADDR_B1A6_STREAM_CTRL,.buf = onoff ? buf_start : buf_stop,.len = 2},
+	};
+	if (dvb_usb_opera1_debug)
+		info("streaming %s", onoff ? "on" : "off");
+	i2c_transfer(&adap->dev->i2c_adap, start_tuner, 1);
+	return 0;
+}
+
+static int opera1_pid_filter(struct dvb_usb_adapter *adap, int index, u16 pid,
+			     int onoff)
+{
+	u8 b_pid[3];
+	struct i2c_msg msg[] = {
+		{.addr = ADDR_B1A6_STREAM_CTRL,.buf = b_pid,.len = 3},
+	};
+	if (dvb_usb_opera1_debug)
+		info("pidfilter index: %d pid: %d %s", index, pid,
+			onoff ? "on" : "off");
+	b_pid[0] = (2 * index) + 4;
+	b_pid[1] = onoff ? (pid & 0xff) : (0x00);
+	b_pid[2] = onoff ? ((pid >> 8) & 0xff) : (0x00);
+	i2c_transfer(&adap->dev->i2c_adap, msg, 1);
+	return 0;
+}
+
+static int opera1_pid_filter_control(struct dvb_usb_adapter *adap, int onoff)
+{
+	int u = 0x04;
+	u8 b_pid[3];
+	struct i2c_msg msg[] = {
+		{.addr = ADDR_B1A6_STREAM_CTRL,.buf = b_pid,.len = 3},
+	};
+	if (dvb_usb_opera1_debug)
+		info("%s hw-pidfilter", onoff ? "enable" : "disable");
+	for (; u < 0x7e; u += 2) {
+		b_pid[0] = u;
+		b_pid[1] = 0;
+		b_pid[2] = 0x80;
+		i2c_transfer(&adap->dev->i2c_adap, msg, 1);
+	}
+	return 0;
+}
+
+static struct rc_map_table rc_map_opera1_table[] = {
+	{0x5fa0, KEY_1},
+	{0x51af, KEY_2},
+	{0x5da2, KEY_3},
+	{0x41be, KEY_4},
+	{0x0bf5, KEY_5},
+	{0x43bd, KEY_6},
+	{0x47b8, KEY_7},
+	{0x49b6, KEY_8},
+	{0x05fa, KEY_9},
+	{0x45ba, KEY_0},
+	{0x09f6, KEY_CHANNELUP},	/*chanup */
+	{0x1be5, KEY_CHANNELDOWN},	/*chandown */
+	{0x5da3, KEY_VOLUMEDOWN},	/*voldown */
+	{0x5fa1, KEY_VOLUMEUP},		/*volup */
+	{0x07f8, KEY_SPACE},		/*tab */
+	{0x1fe1, KEY_OK},		/*play ok */
+	{0x1be4, KEY_ZOOM},		/*zoom */
+	{0x59a6, KEY_MUTE},		/*mute */
+	{0x5ba5, KEY_RADIO},		/*tv/f */
+	{0x19e7, KEY_RECORD},		/*rec */
+	{0x01fe, KEY_STOP},		/*Stop */
+	{0x03fd, KEY_PAUSE},		/*pause */
+	{0x03fc, KEY_SCREEN},		/*<- -> */
+	{0x07f9, KEY_CAMERA},		/*capture */
+	{0x47b9, KEY_ESC},		/*exit */
+	{0x43bc, KEY_POWER2},		/*power */
+};
+
+static int opera1_rc_query(struct dvb_usb_device *dev, u32 * event, int *state)
+{
+	struct opera1_state *opst = dev->priv;
+	u8 rcbuffer[32];
+	const u16 startmarker1 = 0x10ed;
+	const u16 startmarker2 = 0x11ec;
+	struct i2c_msg read_remote[] = {
+		{.addr = ADDR_B880_READ_REMOTE,.buf = rcbuffer,.flags = I2C_M_RD,.len = 32},
+	};
+	int i = 0;
+	u32 send_key = 0;
+
+	if (i2c_transfer(&dev->i2c_adap, read_remote, 1) == 1) {
+		for (i = 0; i < 32; i++) {
+			if (rcbuffer[i])
+				send_key |= 1;
+			if (i < 31)
+				send_key = send_key << 1;
+		}
+		if (send_key & 0x8000)
+			send_key = (send_key << 1) | (send_key >> 15 & 0x01);
+
+		if (send_key == 0xffff && opst->last_key_pressed != 0) {
+			*state = REMOTE_KEY_REPEAT;
+			*event = opst->last_key_pressed;
+			return 0;
+		}
+		for (; send_key != 0;) {
+			if (send_key >> 16 == startmarker2) {
+				break;
+			} else if (send_key >> 16 == startmarker1) {
+				send_key =
+					(send_key & 0xfffeffff) | (startmarker1 << 16);
+				break;
+			} else
+				send_key >>= 1;
+		}
+
+		if (send_key == 0)
+			return 0;
+
+		send_key = (send_key & 0xffff) | 0x0100;
+
+		for (i = 0; i < ARRAY_SIZE(rc_map_opera1_table); i++) {
+			if (rc5_scan(&rc_map_opera1_table[i]) == (send_key & 0xffff)) {
+				*state = REMOTE_KEY_PRESSED;
+				*event = rc_map_opera1_table[i].keycode;
+				opst->last_key_pressed =
+					rc_map_opera1_table[i].keycode;
+				break;
+			}
+			opst->last_key_pressed = 0;
+		}
+	} else
+		*state = REMOTE_NO_KEY_PRESSED;
+	return 0;
+}
+
+static struct usb_device_id opera1_table[] = {
+	{USB_DEVICE(USB_VID_CYPRESS, USB_PID_OPERA1_COLD)},
+	{USB_DEVICE(USB_VID_OPERA1, USB_PID_OPERA1_WARM)},
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, opera1_table);
+
+static int opera1_read_mac_address(struct dvb_usb_device *d, u8 mac[6])
+{
+	u8 command[] = { READ_MAC_ADDR };
+	opera1_xilinx_rw(d->udev, 0xb1, 0xa0, command, 1, OPERA_WRITE_MSG);
+	opera1_xilinx_rw(d->udev, 0xb1, 0xa1, mac, 6, OPERA_READ_MSG);
+	return 0;
+}
+static int opera1_xilinx_load_firmware(struct usb_device *dev,
+				       const char *filename)
+{
+	const struct firmware *fw = NULL;
+	u8 *b, *p;
+	int ret = 0, i,fpgasize=40;
+	u8 testval;
+	info("start downloading fpga firmware %s",filename);
+
+	if ((ret = request_firmware(&fw, filename, &dev->dev)) != 0) {
+		err("did not find the firmware file '%s'. You can use <kernel_dir>/scripts/get_dvb_firmware to get the firmware",
+			filename);
+		return ret;
+	} else {
+		p = kmalloc(fw->size, GFP_KERNEL);
+		opera1_xilinx_rw(dev, 0xbc, 0x00, &testval, 1, OPERA_READ_MSG);
+		if (p != NULL && testval != 0x67) {
+
+			u8 reset = 0, fpga_command = 0;
+			memcpy(p, fw->data, fw->size);
+			/* clear fpga ? */
+			opera1_xilinx_rw(dev, 0xbc, 0xaa, &fpga_command, 1,
+					 OPERA_WRITE_MSG);
+			for (i = 0; i < fw->size;) {
+				if ( (fw->size - i) <fpgasize){
+				    fpgasize=fw->size-i;
+				}
+				b = (u8 *) p + i;
+				if (opera1_xilinx_rw
+					(dev, OPERA_WRITE_FX2, 0x0, b , fpgasize,
+						OPERA_WRITE_MSG) != fpgasize
+					) {
+					err("error while transferring firmware");
+					ret = -EINVAL;
+					break;
+				}
+				i = i + fpgasize;
+			}
+			/* restart the CPU */
+			if (ret || opera1_xilinx_rw
+					(dev, 0xa0, 0xe600, &reset, 1,
+					OPERA_WRITE_MSG) != 1) {
+				err("could not restart the USB controller CPU.");
+				ret = -EINVAL;
+			}
+		}
+	}
+	kfree(p);
+	release_firmware(fw);
+	return ret;
+}
+
+static struct dvb_usb_device_properties opera1_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+	.usb_ctrl = CYPRESS_FX2,
+	.firmware = "dvb-usb-opera-01.fw",
+	.size_of_priv = sizeof(struct opera1_state),
+
+	.power_ctrl = opera1_power_ctrl,
+	.i2c_algo = &opera1_i2c_algo,
+
+	.rc.legacy = {
+		.rc_map_table = rc_map_opera1_table,
+		.rc_map_size = ARRAY_SIZE(rc_map_opera1_table),
+		.rc_interval = 200,
+		.rc_query = opera1_rc_query,
+	},
+	.read_mac_address = opera1_read_mac_address,
+	.generic_bulk_ctrl_endpoint = 0x00,
+	/* parameter for the MPEG2-data transfer */
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.frontend_attach = opera1_frontend_attach,
+			.streaming_ctrl = opera1_streaming_ctrl,
+			.tuner_attach = opera1_tuner_attach,
+			.caps =
+				DVB_USB_ADAP_HAS_PID_FILTER |
+				DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF,
+			.pid_filter = opera1_pid_filter,
+			.pid_filter_ctrl = opera1_pid_filter_control,
+			.pid_filter_count = 252,
+			.stream = {
+				.type = USB_BULK,
+				.count = 10,
+				.endpoint = 0x82,
+				.u = {
+					.bulk = {
+						.buffersize = 4096,
+					}
+				}
+			},
+		}},
+		}
+	},
+	.num_device_descs = 1,
+	.devices = {
+		{"Opera1 DVB-S USB2.0",
+			{&opera1_table[0], NULL},
+			{&opera1_table[1], NULL},
+		},
+	}
+};
+
+static int opera1_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	struct usb_device *udev = interface_to_usbdev(intf);
+
+	if (le16_to_cpu(udev->descriptor.idProduct) == USB_PID_OPERA1_WARM &&
+	    le16_to_cpu(udev->descriptor.idVendor) == USB_VID_OPERA1 &&
+		opera1_xilinx_load_firmware(udev, "dvb-usb-opera1-fpga-01.fw") != 0
+	    ) {
+		return -EINVAL;
+	}
+
+	if (0 != dvb_usb_device_init(intf, &opera1_properties,
+				     THIS_MODULE, NULL, adapter_nr))
+		return -EINVAL;
+	return 0;
+}
+
+static struct usb_driver opera1_driver = {
+	.name = "opera1",
+	.probe = opera1_probe,
+	.disconnect = dvb_usb_device_exit,
+	.id_table = opera1_table,
+};
+
+module_usb_driver(opera1_driver);
+
+MODULE_AUTHOR("Mario Hlawitschka (c) dh1pa@amsat.org");
+MODULE_AUTHOR("Marco Gittler (c) g.marco@freenet.de");
+MODULE_DESCRIPTION("Driver for Opera1 DVB-S device");
+MODULE_VERSION("0.1");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/pctv452e.c b/drivers/media/usb/dvb-usb/pctv452e.c
new file mode 100644
index 0000000..0af7438
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/pctv452e.c
@@ -0,0 +1,1102 @@
+/*
+ * PCTV 452e DVB driver
+ *
+ * Copyright (c) 2006-2008 Dominik Kuhlen <dkuhlen@gmx.net>
+ *
+ * TT connect S2-3650-CI Common Interface support, MAC readout
+ * Copyright (C) 2008 Michael H. Schimek <mschimek@gmx.at>
+ *
+ * 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.
+ */
+
+/* dvb usb framework */
+#define DVB_USB_LOG_PREFIX "pctv452e"
+#include "dvb-usb.h"
+
+/* Demodulator */
+#include "stb0899_drv.h"
+#include "stb0899_reg.h"
+#include "stb0899_cfg.h"
+/* Tuner */
+#include "stb6100.h"
+#include "stb6100_cfg.h"
+/* FE Power */
+#include "lnbp22.h"
+
+#include <media/dvb_ca_en50221.h>
+#include "ttpci-eeprom.h"
+
+static int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off).");
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+#define ISOC_INTERFACE_ALTERNATIVE 3
+
+#define SYNC_BYTE_OUT 0xaa
+#define SYNC_BYTE_IN  0x55
+
+/* guessed: (copied from ttusb-budget) */
+#define PCTV_CMD_RESET 0x15
+/* command to poll IR receiver */
+#define PCTV_CMD_IR    0x1b
+/* command to send I2C  */
+#define PCTV_CMD_I2C   0x31
+
+#define I2C_ADDR_STB0899 (0xd0 >> 1)
+#define I2C_ADDR_STB6100 (0xc0 >> 1)
+#define I2C_ADDR_LNBP22  (0x10 >> 1)
+#define I2C_ADDR_24C16   (0xa0 >> 1)
+#define I2C_ADDR_24C64   (0xa2 >> 1)
+
+
+/* pctv452e sends us this amount of data for each issued usb-command */
+#define PCTV_ANSWER_LEN 64
+/* Wait up to 1000ms for device  */
+#define PCTV_TIMEOUT 1000
+
+
+#define PCTV_LED_GPIO   STB0899_GPIO01
+#define PCTV_LED_GREEN  0x82
+#define PCTV_LED_ORANGE 0x02
+
+#define ci_dbg(format, arg...)				\
+do {							\
+	if (0)						\
+		printk(KERN_DEBUG DVB_USB_LOG_PREFIX	\
+			": " format "\n" , ## arg);	\
+} while (0)
+
+enum {
+	TT3650_CMD_CI_TEST = 0x40,
+	TT3650_CMD_CI_RD_CTRL,
+	TT3650_CMD_CI_WR_CTRL,
+	TT3650_CMD_CI_RD_ATTR,
+	TT3650_CMD_CI_WR_ATTR,
+	TT3650_CMD_CI_RESET,
+	TT3650_CMD_CI_SET_VIDEO_PORT
+};
+
+
+static struct stb0899_postproc pctv45e_postproc[] = {
+	{ PCTV_LED_GPIO, STB0899_GPIOPULLUP },
+	{ 0, 0 }
+};
+
+/*
+ * stores all private variables for communication with the PCTV452e DVB-S2
+ */
+struct pctv452e_state {
+	struct dvb_ca_en50221 ca;
+	struct mutex ca_mutex;
+
+	u8 c;	   /* transaction counter, wraps around...  */
+	u8 initialized; /* set to 1 if 0x15 has been sent */
+	u16 last_rc_key;
+};
+
+static int tt3650_ci_msg(struct dvb_usb_device *d, u8 cmd, u8 *data,
+			 unsigned int write_len, unsigned int read_len)
+{
+	struct pctv452e_state *state = (struct pctv452e_state *)d->priv;
+	u8 *buf;
+	u8 id;
+	unsigned int rlen;
+	int ret;
+
+	if (!data || (write_len > 64 - 4) || (read_len > 64 - 4)) {
+		err("%s: transfer data invalid", __func__);
+		return -EIO;
+	}
+
+	buf = kmalloc(64, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	id = state->c++;
+
+	buf[0] = SYNC_BYTE_OUT;
+	buf[1] = id;
+	buf[2] = cmd;
+	buf[3] = write_len;
+
+	memcpy(buf + 4, data, write_len);
+
+	rlen = (read_len > 0) ? 64 : 0;
+	ret = dvb_usb_generic_rw(d, buf, 4 + write_len,
+				  buf, rlen, /* delay_ms */ 0);
+	if (0 != ret)
+		goto failed;
+
+	ret = -EIO;
+	if (SYNC_BYTE_IN != buf[0] || id != buf[1])
+		goto failed;
+
+	memcpy(data, buf + 4, read_len);
+
+	kfree(buf);
+	return 0;
+
+failed:
+	err("CI error %d; %02X %02X %02X -> %*ph.",
+	     ret, SYNC_BYTE_OUT, id, cmd, 3, buf);
+
+	kfree(buf);
+	return ret;
+}
+
+static int tt3650_ci_msg_locked(struct dvb_ca_en50221 *ca,
+				u8 cmd, u8 *data, unsigned int write_len,
+				unsigned int read_len)
+{
+	struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data;
+	struct pctv452e_state *state = (struct pctv452e_state *)d->priv;
+	int ret;
+
+	mutex_lock(&state->ca_mutex);
+	ret = tt3650_ci_msg(d, cmd, data, write_len, read_len);
+	mutex_unlock(&state->ca_mutex);
+
+	return ret;
+}
+
+static int tt3650_ci_read_attribute_mem(struct dvb_ca_en50221 *ca,
+				 int slot, int address)
+{
+	u8 buf[3];
+	int ret;
+
+	if (0 != slot)
+		return -EINVAL;
+
+	buf[0] = (address >> 8) & 0x0F;
+	buf[1] = address;
+
+	ret = tt3650_ci_msg_locked(ca, TT3650_CMD_CI_RD_ATTR, buf, 2, 3);
+
+	ci_dbg("%s %04x -> %d 0x%02x",
+		__func__, address, ret, buf[2]);
+
+	if (ret < 0)
+		return ret;
+
+	return buf[2];
+}
+
+static int tt3650_ci_write_attribute_mem(struct dvb_ca_en50221 *ca,
+				 int slot, int address, u8 value)
+{
+	u8 buf[3];
+
+	ci_dbg("%s %d 0x%04x 0x%02x",
+		__func__, slot, address, value);
+
+	if (0 != slot)
+		return -EINVAL;
+
+	buf[0] = (address >> 8) & 0x0F;
+	buf[1] = address;
+	buf[2] = value;
+
+	return tt3650_ci_msg_locked(ca, TT3650_CMD_CI_WR_ATTR, buf, 3, 3);
+}
+
+static int tt3650_ci_read_cam_control(struct dvb_ca_en50221 *ca,
+				 int			slot,
+				 u8			address)
+{
+	u8 buf[2];
+	int ret;
+
+	if (0 != slot)
+		return -EINVAL;
+
+	buf[0] = address & 3;
+
+	ret = tt3650_ci_msg_locked(ca, TT3650_CMD_CI_RD_CTRL, buf, 1, 2);
+
+	ci_dbg("%s 0x%02x -> %d 0x%02x",
+		__func__, address, ret, buf[1]);
+
+	if (ret < 0)
+		return ret;
+
+	return buf[1];
+}
+
+static int tt3650_ci_write_cam_control(struct dvb_ca_en50221 *ca,
+				 int			slot,
+				 u8			address,
+				 u8			value)
+{
+	u8 buf[2];
+
+	ci_dbg("%s %d 0x%02x 0x%02x",
+		__func__, slot, address, value);
+
+	if (0 != slot)
+		return -EINVAL;
+
+	buf[0] = address;
+	buf[1] = value;
+
+	return tt3650_ci_msg_locked(ca, TT3650_CMD_CI_WR_CTRL, buf, 2, 2);
+}
+
+static int tt3650_ci_set_video_port(struct dvb_ca_en50221 *ca,
+				 int			slot,
+				 int			enable)
+{
+	u8 buf[1];
+	int ret;
+
+	ci_dbg("%s %d %d", __func__, slot, enable);
+
+	if (0 != slot)
+		return -EINVAL;
+
+	enable = !!enable;
+	buf[0] = enable;
+
+	ret = tt3650_ci_msg_locked(ca, TT3650_CMD_CI_SET_VIDEO_PORT, buf, 1, 1);
+	if (ret < 0)
+		return ret;
+
+	if (enable != buf[0]) {
+		err("CI not %sabled.", enable ? "en" : "dis");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int tt3650_ci_slot_shutdown(struct dvb_ca_en50221 *ca, int slot)
+{
+	return tt3650_ci_set_video_port(ca, slot, /* enable */ 0);
+}
+
+static int tt3650_ci_slot_ts_enable(struct dvb_ca_en50221 *ca, int slot)
+{
+	return tt3650_ci_set_video_port(ca, slot, /* enable */ 1);
+}
+
+static int tt3650_ci_slot_reset(struct dvb_ca_en50221 *ca, int slot)
+{
+	struct dvb_usb_device *d = (struct dvb_usb_device *)ca->data;
+	struct pctv452e_state *state = (struct pctv452e_state *)d->priv;
+	u8 buf[1];
+	int ret;
+
+	ci_dbg("%s %d", __func__, slot);
+
+	if (0 != slot)
+		return -EINVAL;
+
+	buf[0] = 0;
+
+	mutex_lock(&state->ca_mutex);
+
+	ret = tt3650_ci_msg(d, TT3650_CMD_CI_RESET, buf, 1, 1);
+	if (0 != ret)
+		goto failed;
+
+	msleep(500);
+
+	buf[0] = 1;
+
+	ret = tt3650_ci_msg(d, TT3650_CMD_CI_RESET, buf, 1, 1);
+	if (0 != ret)
+		goto failed;
+
+	msleep(500);
+
+	buf[0] = 0; /* FTA */
+
+	ret = tt3650_ci_msg(d, TT3650_CMD_CI_SET_VIDEO_PORT, buf, 1, 1);
+
+ failed:
+	mutex_unlock(&state->ca_mutex);
+
+	return ret;
+}
+
+static int tt3650_ci_poll_slot_status(struct dvb_ca_en50221 *ca,
+				 int			slot,
+				 int			open)
+{
+	u8 buf[1];
+	int ret;
+
+	if (0 != slot)
+		return -EINVAL;
+
+	ret = tt3650_ci_msg_locked(ca, TT3650_CMD_CI_TEST, buf, 0, 1);
+	if (0 != ret)
+		return ret;
+
+	if (1 == buf[0])
+		return DVB_CA_EN50221_POLL_CAM_PRESENT |
+			DVB_CA_EN50221_POLL_CAM_READY;
+
+	return 0;
+
+}
+
+static void tt3650_ci_uninit(struct dvb_usb_device *d)
+{
+	struct pctv452e_state *state;
+
+	ci_dbg("%s", __func__);
+
+	if (NULL == d)
+		return;
+
+	state = (struct pctv452e_state *)d->priv;
+	if (NULL == state)
+		return;
+
+	if (NULL == state->ca.data)
+		return;
+
+	/* Error ignored. */
+	tt3650_ci_set_video_port(&state->ca, /* slot */ 0, /* enable */ 0);
+
+	dvb_ca_en50221_release(&state->ca);
+
+	memset(&state->ca, 0, sizeof(state->ca));
+}
+
+static int tt3650_ci_init(struct dvb_usb_adapter *a)
+{
+	struct dvb_usb_device *d = a->dev;
+	struct pctv452e_state *state = (struct pctv452e_state *)d->priv;
+	int ret;
+
+	ci_dbg("%s", __func__);
+
+	mutex_init(&state->ca_mutex);
+
+	state->ca.owner = THIS_MODULE;
+	state->ca.read_attribute_mem = tt3650_ci_read_attribute_mem;
+	state->ca.write_attribute_mem = tt3650_ci_write_attribute_mem;
+	state->ca.read_cam_control = tt3650_ci_read_cam_control;
+	state->ca.write_cam_control = tt3650_ci_write_cam_control;
+	state->ca.slot_reset = tt3650_ci_slot_reset;
+	state->ca.slot_shutdown = tt3650_ci_slot_shutdown;
+	state->ca.slot_ts_enable = tt3650_ci_slot_ts_enable;
+	state->ca.poll_slot_status = tt3650_ci_poll_slot_status;
+	state->ca.data = d;
+
+	ret = dvb_ca_en50221_init(&a->dvb_adap,
+				   &state->ca,
+				   /* flags */ 0,
+				   /* n_slots */ 1);
+	if (0 != ret) {
+		err("Cannot initialize CI: Error %d.", ret);
+		memset(&state->ca, 0, sizeof(state->ca));
+		return ret;
+	}
+
+	info("CI initialized.");
+
+	return 0;
+}
+
+#define CMD_BUFFER_SIZE 0x28
+static int pctv452e_i2c_msg(struct dvb_usb_device *d, u8 addr,
+				const u8 *snd_buf, u8 snd_len,
+				u8 *rcv_buf, u8 rcv_len)
+{
+	struct pctv452e_state *state = (struct pctv452e_state *)d->priv;
+	u8 *buf;
+	u8 id;
+	int ret;
+
+	buf = kmalloc(64, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	id = state->c++;
+
+	ret = -EINVAL;
+	if (snd_len > 64 - 7 || rcv_len > 64 - 7)
+		goto failed;
+
+	buf[0] = SYNC_BYTE_OUT;
+	buf[1] = id;
+	buf[2] = PCTV_CMD_I2C;
+	buf[3] = snd_len + 3;
+	buf[4] = addr << 1;
+	buf[5] = snd_len;
+	buf[6] = rcv_len;
+
+	memcpy(buf + 7, snd_buf, snd_len);
+
+	ret = dvb_usb_generic_rw(d, buf, 7 + snd_len,
+				  buf, /* rcv_len */ 64,
+				  /* delay_ms */ 0);
+	if (ret < 0)
+		goto failed;
+
+	/* TT USB protocol error. */
+	ret = -EIO;
+	if (SYNC_BYTE_IN != buf[0] || id != buf[1])
+		goto failed;
+
+	/* I2C device didn't respond as expected. */
+	ret = -EREMOTEIO;
+	if (buf[5] < snd_len || buf[6] < rcv_len)
+		goto failed;
+
+	memcpy(rcv_buf, buf + 7, rcv_len);
+
+	kfree(buf);
+	return rcv_len;
+
+failed:
+	err("I2C error %d; %02X %02X  %02X %02X %02X -> %*ph",
+	     ret, SYNC_BYTE_OUT, id, addr << 1, snd_len, rcv_len,
+	     7, buf);
+
+	kfree(buf);
+	return ret;
+}
+
+static int pctv452e_i2c_xfer(struct i2c_adapter *adapter, struct i2c_msg *msg,
+				int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adapter);
+	int i;
+
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	for (i = 0; i < num; i++) {
+		u8 addr, snd_len, rcv_len, *snd_buf, *rcv_buf;
+		int ret;
+
+		if (msg[i].flags & I2C_M_RD) {
+			addr = msg[i].addr;
+			snd_buf = NULL;
+			snd_len = 0;
+			rcv_buf = msg[i].buf;
+			rcv_len = msg[i].len;
+		} else {
+			addr = msg[i].addr;
+			snd_buf = msg[i].buf;
+			snd_len = msg[i].len;
+			rcv_buf = NULL;
+			rcv_len = 0;
+		}
+
+		ret = pctv452e_i2c_msg(d, addr, snd_buf, snd_len, rcv_buf,
+					rcv_len);
+		if (ret < rcv_len)
+			break;
+	}
+
+	mutex_unlock(&d->i2c_mutex);
+	return i;
+}
+
+static u32 pctv452e_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static int pctv452e_power_ctrl(struct dvb_usb_device *d, int i)
+{
+	struct pctv452e_state *state = (struct pctv452e_state *)d->priv;
+	u8 *b0, *rx;
+	int ret;
+
+	info("%s: %d\n", __func__, i);
+
+	if (!i)
+		return 0;
+
+	if (state->initialized)
+		return 0;
+
+	b0 = kmalloc(5 + PCTV_ANSWER_LEN, GFP_KERNEL);
+	if (!b0)
+		return -ENOMEM;
+
+	rx = b0 + 5;
+
+	/* hmm where shoud this should go? */
+	ret = usb_set_interface(d->udev, 0, ISOC_INTERFACE_ALTERNATIVE);
+	if (ret != 0)
+		info("%s: Warning set interface returned: %d\n",
+			__func__, ret);
+
+	/* this is a one-time initialization, dont know where to put */
+	b0[0] = 0xaa;
+	b0[1] = state->c++;
+	b0[2] = PCTV_CMD_RESET;
+	b0[3] = 1;
+	b0[4] = 0;
+	/* reset board */
+	ret = dvb_usb_generic_rw(d, b0, 5, rx, PCTV_ANSWER_LEN, 0);
+	if (ret)
+		goto ret;
+
+	b0[1] = state->c++;
+	b0[4] = 1;
+	/* reset board (again?) */
+	ret = dvb_usb_generic_rw(d, b0, 5, rx, PCTV_ANSWER_LEN, 0);
+	if (ret)
+		goto ret;
+
+	state->initialized = 1;
+
+ret:
+	kfree(b0);
+	return ret;
+}
+
+static int pctv452e_rc_query(struct dvb_usb_device *d)
+{
+	struct pctv452e_state *state = (struct pctv452e_state *)d->priv;
+	u8 *b, *rx;
+	int ret, i;
+	u8 id;
+
+	b = kmalloc(CMD_BUFFER_SIZE + PCTV_ANSWER_LEN, GFP_KERNEL);
+	if (!b)
+		return -ENOMEM;
+
+	rx = b + CMD_BUFFER_SIZE;
+
+	id = state->c++;
+
+	/* prepare command header  */
+	b[0] = SYNC_BYTE_OUT;
+	b[1] = id;
+	b[2] = PCTV_CMD_IR;
+	b[3] = 0;
+
+	/* send ir request */
+	ret = dvb_usb_generic_rw(d, b, 4, rx, PCTV_ANSWER_LEN, 0);
+	if (ret != 0)
+		goto ret;
+
+	if (debug > 3) {
+		info("%s: read: %2d: %*ph: ", __func__, ret, 3, rx);
+		for (i = 0; (i < rx[3]) && ((i+3) < PCTV_ANSWER_LEN); i++)
+			info(" %02x", rx[i+3]);
+
+		info("\n");
+	}
+
+	if ((rx[3] == 9) &&  (rx[12] & 0x01)) {
+		/* got a "press" event */
+		state->last_rc_key = RC_SCANCODE_RC5(rx[7], rx[6]);
+		if (debug > 2)
+			info("%s: cmd=0x%02x sys=0x%02x\n",
+				__func__, rx[6], rx[7]);
+
+		rc_keydown(d->rc_dev, RC_PROTO_RC5, state->last_rc_key, 0);
+	} else if (state->last_rc_key) {
+		rc_keyup(d->rc_dev);
+		state->last_rc_key = 0;
+	}
+ret:
+	kfree(b);
+	return ret;
+}
+
+static int pctv452e_read_mac_address(struct dvb_usb_device *d, u8 mac[6])
+{
+	const u8 mem_addr[] = { 0x1f, 0xcc };
+	u8 encoded_mac[20];
+	int ret;
+
+	ret = -EAGAIN;
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		goto failed;
+
+	ret = pctv452e_i2c_msg(d, I2C_ADDR_24C16,
+				mem_addr + 1, /* snd_len */ 1,
+				encoded_mac, /* rcv_len */ 20);
+	if (-EREMOTEIO == ret)
+		/* Caution! A 24C16 interprets 0xA2 0x1F 0xCC as a
+		   byte write if /WC is low. */
+		ret = pctv452e_i2c_msg(d, I2C_ADDR_24C64,
+					mem_addr, 2,
+					encoded_mac, 20);
+
+	mutex_unlock(&d->i2c_mutex);
+
+	if (20 != ret)
+		goto failed;
+
+	ret = ttpci_eeprom_decode_mac(mac, encoded_mac);
+	if (0 != ret)
+		goto failed;
+
+	return 0;
+
+failed:
+	eth_zero_addr(mac);
+
+	return ret;
+}
+
+static const struct stb0899_s1_reg pctv452e_init_dev[] = {
+	{ STB0899_DISCNTRL1,	0x26 },
+	{ STB0899_DISCNTRL2,	0x80 },
+	{ STB0899_DISRX_ST0,	0x04 },
+	{ STB0899_DISRX_ST1,	0x20 },
+	{ STB0899_DISPARITY,	0x00 },
+	{ STB0899_DISFIFO,	0x00 },
+	{ STB0899_DISF22,	0x99 },
+	{ STB0899_DISF22RX,	0x85 }, /* 0xa8 */
+	{ STB0899_ACRPRESC,	0x11 },
+	{ STB0899_ACRDIV1,	0x0a },
+	{ STB0899_ACRDIV2,	0x05 },
+	{ STB0899_DACR1	,	0x00 },
+	{ STB0899_DACR2	,	0x00 },
+	{ STB0899_OUTCFG,	0x00 },
+	{ STB0899_MODECFG,	0x00 }, /* Inversion */
+	{ STB0899_IRQMSK_3,	0xf3 },
+	{ STB0899_IRQMSK_2,	0xfc },
+	{ STB0899_IRQMSK_1,	0xff },
+	{ STB0899_IRQMSK_0,	0xff },
+	{ STB0899_I2CCFG,	0x88 },
+	{ STB0899_I2CRPT,	0x58 },
+	{ STB0899_GPIO00CFG,	0x82 },
+	{ STB0899_GPIO01CFG,	0x82 }, /* LED: 0x02 green, 0x82 orange */
+	{ STB0899_GPIO02CFG,	0x82 },
+	{ STB0899_GPIO03CFG,	0x82 },
+	{ STB0899_GPIO04CFG,	0x82 },
+	{ STB0899_GPIO05CFG,	0x82 },
+	{ STB0899_GPIO06CFG,	0x82 },
+	{ STB0899_GPIO07CFG,	0x82 },
+	{ STB0899_GPIO08CFG,	0x82 },
+	{ STB0899_GPIO09CFG,	0x82 },
+	{ STB0899_GPIO10CFG,	0x82 },
+	{ STB0899_GPIO11CFG,	0x82 },
+	{ STB0899_GPIO12CFG,	0x82 },
+	{ STB0899_GPIO13CFG,	0x82 },
+	{ STB0899_GPIO14CFG,	0x82 },
+	{ STB0899_GPIO15CFG,	0x82 },
+	{ STB0899_GPIO16CFG,	0x82 },
+	{ STB0899_GPIO17CFG,	0x82 },
+	{ STB0899_GPIO18CFG,	0x82 },
+	{ STB0899_GPIO19CFG,	0x82 },
+	{ STB0899_GPIO20CFG,	0x82 },
+	{ STB0899_SDATCFG,	0xb8 },
+	{ STB0899_SCLTCFG,	0xba },
+	{ STB0899_AGCRFCFG,	0x1c }, /* 0x11 DVB-S; 0x1c DVB-S2 (1c, rjkm) */
+	{ STB0899_GPIO22,	0x82 },
+	{ STB0899_GPIO21,	0x91 },
+	{ STB0899_DIRCLKCFG,	0x82 },
+	{ STB0899_CLKOUT27CFG,	0x7e },
+	{ STB0899_STDBYCFG,	0x82 },
+	{ STB0899_CS0CFG,	0x82 },
+	{ STB0899_CS1CFG,	0x82 },
+	{ STB0899_DISEQCOCFG,	0x20 },
+	{ STB0899_NCOARSE,	0x15 }, /* 0x15 27Mhz, F/3 198MHz, F/6 108MHz */
+	{ STB0899_SYNTCTRL,	0x00 }, /* 0x00 CLKI, 0x02 XTALI */
+	{ STB0899_FILTCTRL,	0x00 },
+	{ STB0899_SYSCTRL,	0x00 },
+	{ STB0899_STOPCLK1,	0x20 }, /* orig: 0x00 budget-ci: 0x20 */
+	{ STB0899_STOPCLK2,	0x00 },
+	{ STB0899_INTBUFCTRL,	0x0a },
+	{ STB0899_AGC2I1,	0x00 },
+	{ STB0899_AGC2I2,	0x00 },
+	{ STB0899_AGCIQIN,	0x00 },
+	{ STB0899_TSTRES,	0x40 }, /* rjkm */
+	{ 0xffff,		0xff },
+};
+
+static const struct stb0899_s1_reg pctv452e_init_s1_demod[] = {
+	{ STB0899_DEMOD,	0x00 },
+	{ STB0899_RCOMPC,	0xc9 },
+	{ STB0899_AGC1CN,	0x01 },
+	{ STB0899_AGC1REF,	0x10 },
+	{ STB0899_RTC,		0x23 },
+	{ STB0899_TMGCFG,	0x4e },
+	{ STB0899_AGC2REF,	0x34 },
+	{ STB0899_TLSR,		0x84 },
+	{ STB0899_CFD,		0xf7 },
+	{ STB0899_ACLC,		0x87 },
+	{ STB0899_BCLC,		0x94 },
+	{ STB0899_EQON,		0x41 },
+	{ STB0899_LDT,		0xf1 },
+	{ STB0899_LDT2,		0xe3 },
+	{ STB0899_EQUALREF,	0xb4 },
+	{ STB0899_TMGRAMP,	0x10 },
+	{ STB0899_TMGTHD,	0x30 },
+	{ STB0899_IDCCOMP,	0xfd },
+	{ STB0899_QDCCOMP,	0xff },
+	{ STB0899_POWERI,	0x0c },
+	{ STB0899_POWERQ,	0x0f },
+	{ STB0899_RCOMP,	0x6c },
+	{ STB0899_AGCIQIN,	0x80 },
+	{ STB0899_AGC2I1,	0x06 },
+	{ STB0899_AGC2I2,	0x00 },
+	{ STB0899_TLIR,		0x30 },
+	{ STB0899_RTF,		0x7f },
+	{ STB0899_DSTATUS,	0x00 },
+	{ STB0899_LDI,		0xbc },
+	{ STB0899_CFRM,		0xea },
+	{ STB0899_CFRL,		0x31 },
+	{ STB0899_NIRM,		0x2b },
+	{ STB0899_NIRL,		0x80 },
+	{ STB0899_ISYMB,	0x1d },
+	{ STB0899_QSYMB,	0xa6 },
+	{ STB0899_SFRH,		0x2f },
+	{ STB0899_SFRM,		0x68 },
+	{ STB0899_SFRL,		0x40 },
+	{ STB0899_SFRUPH,	0x2f },
+	{ STB0899_SFRUPM,	0x68 },
+	{ STB0899_SFRUPL,	0x40 },
+	{ STB0899_EQUAI1,	0x02 },
+	{ STB0899_EQUAQ1,	0xff },
+	{ STB0899_EQUAI2,	0x04 },
+	{ STB0899_EQUAQ2,	0x05 },
+	{ STB0899_EQUAI3,	0x02 },
+	{ STB0899_EQUAQ3,	0xfd },
+	{ STB0899_EQUAI4,	0x03 },
+	{ STB0899_EQUAQ4,	0x07 },
+	{ STB0899_EQUAI5,	0x08 },
+	{ STB0899_EQUAQ5,	0xf5 },
+	{ STB0899_DSTATUS2,	0x00 },
+	{ STB0899_VSTATUS,	0x00 },
+	{ STB0899_VERROR,	0x86 },
+	{ STB0899_IQSWAP,	0x2a },
+	{ STB0899_ECNT1M,	0x00 },
+	{ STB0899_ECNT1L,	0x00 },
+	{ STB0899_ECNT2M,	0x00 },
+	{ STB0899_ECNT2L,	0x00 },
+	{ STB0899_ECNT3M,	0x0a },
+	{ STB0899_ECNT3L,	0xad },
+	{ STB0899_FECAUTO1,	0x06 },
+	{ STB0899_FECM,		0x01 },
+	{ STB0899_VTH12,	0xb0 },
+	{ STB0899_VTH23,	0x7a },
+	{ STB0899_VTH34,	0x58 },
+	{ STB0899_VTH56,	0x38 },
+	{ STB0899_VTH67,	0x34 },
+	{ STB0899_VTH78,	0x24 },
+	{ STB0899_PRVIT,	0xff },
+	{ STB0899_VITSYNC,	0x19 },
+	{ STB0899_RSULC,	0xb1 }, /* DVB = 0xb1, DSS = 0xa1 */
+	{ STB0899_TSULC,	0x42 },
+	{ STB0899_RSLLC,	0x41 },
+	{ STB0899_TSLPL,	0x12 },
+	{ STB0899_TSCFGH,	0x0c },
+	{ STB0899_TSCFGM,	0x00 },
+	{ STB0899_TSCFGL,	0x00 },
+	{ STB0899_TSOUT,	0x69 }, /* 0x0d for CAM */
+	{ STB0899_RSSYNCDEL,	0x00 },
+	{ STB0899_TSINHDELH,	0x02 },
+	{ STB0899_TSINHDELM,	0x00 },
+	{ STB0899_TSINHDELL,	0x00 },
+	{ STB0899_TSLLSTKM,	0x1b },
+	{ STB0899_TSLLSTKL,	0xb3 },
+	{ STB0899_TSULSTKM,	0x00 },
+	{ STB0899_TSULSTKL,	0x00 },
+	{ STB0899_PCKLENUL,	0xbc },
+	{ STB0899_PCKLENLL,	0xcc },
+	{ STB0899_RSPCKLEN,	0xbd },
+	{ STB0899_TSSTATUS,	0x90 },
+	{ STB0899_ERRCTRL1,	0xb6 },
+	{ STB0899_ERRCTRL2,	0x95 },
+	{ STB0899_ERRCTRL3,	0x8d },
+	{ STB0899_DMONMSK1,	0x27 },
+	{ STB0899_DMONMSK0,	0x03 },
+	{ STB0899_DEMAPVIT,	0x5c },
+	{ STB0899_PLPARM,	0x19 },
+	{ STB0899_PDELCTRL,	0x48 },
+	{ STB0899_PDELCTRL2,	0x00 },
+	{ STB0899_BBHCTRL1,	0x00 },
+	{ STB0899_BBHCTRL2,	0x00 },
+	{ STB0899_HYSTTHRESH,	0x77 },
+	{ STB0899_MATCSTM,	0x00 },
+	{ STB0899_MATCSTL,	0x00 },
+	{ STB0899_UPLCSTM,	0x00 },
+	{ STB0899_UPLCSTL,	0x00 },
+	{ STB0899_DFLCSTM,	0x00 },
+	{ STB0899_DFLCSTL,	0x00 },
+	{ STB0899_SYNCCST,	0x00 },
+	{ STB0899_SYNCDCSTM,	0x00 },
+	{ STB0899_SYNCDCSTL,	0x00 },
+	{ STB0899_ISI_ENTRY,	0x00 },
+	{ STB0899_ISI_BIT_EN,	0x00 },
+	{ STB0899_MATSTRM,	0xf0 },
+	{ STB0899_MATSTRL,	0x02 },
+	{ STB0899_UPLSTRM,	0x45 },
+	{ STB0899_UPLSTRL,	0x60 },
+	{ STB0899_DFLSTRM,	0xe3 },
+	{ STB0899_DFLSTRL,	0x00 },
+	{ STB0899_SYNCSTR,	0x47 },
+	{ STB0899_SYNCDSTRM,	0x05 },
+	{ STB0899_SYNCDSTRL,	0x18 },
+	{ STB0899_CFGPDELSTATUS1, 0x19 },
+	{ STB0899_CFGPDELSTATUS2, 0x2b },
+	{ STB0899_BBFERRORM,	0x00 },
+	{ STB0899_BBFERRORL,	0x01 },
+	{ STB0899_UPKTERRORM,	0x00 },
+	{ STB0899_UPKTERRORL,	0x00 },
+	{ 0xffff,		0xff },
+};
+
+static struct stb0899_config stb0899_config = {
+	.init_dev	= pctv452e_init_dev,
+	.init_s2_demod	= stb0899_s2_init_2,
+	.init_s1_demod	= pctv452e_init_s1_demod,
+	.init_s2_fec	= stb0899_s2_init_4,
+	.init_tst	= stb0899_s1_init_5,
+
+	.demod_address   = I2C_ADDR_STB0899, /* I2C Address */
+	.block_sync_mode = STB0899_SYNC_FORCED, /* ? */
+
+	.xtal_freq       = 27000000,	 /* Assume Hz ? */
+	.inversion       = IQ_SWAP_ON,
+
+	.lo_clk	  = 76500000,
+	.hi_clk	  = 99000000,
+
+	.ts_output_mode  = 0,	/* Use parallel mode */
+	.clock_polarity  = 0,
+	.data_clk_parity = 0,
+	.fec_mode	= 0,
+
+	.esno_ave	    = STB0899_DVBS2_ESNO_AVE,
+	.esno_quant	  = STB0899_DVBS2_ESNO_QUANT,
+	.avframes_coarse     = STB0899_DVBS2_AVFRAMES_COARSE,
+	.avframes_fine       = STB0899_DVBS2_AVFRAMES_FINE,
+	.miss_threshold      = STB0899_DVBS2_MISS_THRESHOLD,
+	.uwp_threshold_acq   = STB0899_DVBS2_UWP_THRESHOLD_ACQ,
+	.uwp_threshold_track = STB0899_DVBS2_UWP_THRESHOLD_TRACK,
+	.uwp_threshold_sof   = STB0899_DVBS2_UWP_THRESHOLD_SOF,
+	.sof_search_timeout  = STB0899_DVBS2_SOF_SEARCH_TIMEOUT,
+
+	.btr_nco_bits	  = STB0899_DVBS2_BTR_NCO_BITS,
+	.btr_gain_shift_offset = STB0899_DVBS2_BTR_GAIN_SHIFT_OFFSET,
+	.crl_nco_bits	  = STB0899_DVBS2_CRL_NCO_BITS,
+	.ldpc_max_iter	 = STB0899_DVBS2_LDPC_MAX_ITER,
+
+	.tuner_get_frequency	= stb6100_get_frequency,
+	.tuner_set_frequency	= stb6100_set_frequency,
+	.tuner_set_bandwidth	= stb6100_set_bandwidth,
+	.tuner_get_bandwidth	= stb6100_get_bandwidth,
+	.tuner_set_rfsiggain	= NULL,
+
+	/* helper for switching LED green/orange */
+	.postproc = pctv45e_postproc
+};
+
+static struct stb6100_config stb6100_config = {
+	.tuner_address = I2C_ADDR_STB6100,
+	.refclock      = 27000000
+};
+
+
+static struct i2c_algorithm pctv452e_i2c_algo = {
+	.master_xfer   = pctv452e_i2c_xfer,
+	.functionality = pctv452e_i2c_func
+};
+
+static int pctv452e_frontend_attach(struct dvb_usb_adapter *a)
+{
+	struct usb_device_id *id;
+
+	a->fe_adap[0].fe = dvb_attach(stb0899_attach, &stb0899_config,
+						&a->dev->i2c_adap);
+	if (!a->fe_adap[0].fe)
+		return -ENODEV;
+
+	/*
+	 * dvb_frontend will call dvb_detach for both stb0899_detach
+	 * and stb0899_release but we only do dvb_attach(stb0899_attach).
+	 * Increment the module refcount instead.
+	 */
+	symbol_get(stb0899_attach);
+
+	if ((dvb_attach(lnbp22_attach, a->fe_adap[0].fe,
+					&a->dev->i2c_adap)) == NULL)
+		err("Cannot attach lnbp22\n");
+
+	id = a->dev->desc->warm_ids[0];
+	if (USB_VID_TECHNOTREND == id->idVendor
+	    && USB_PID_TECHNOTREND_CONNECT_S2_3650_CI == id->idProduct)
+		/* Error ignored. */
+		tt3650_ci_init(a);
+
+	return 0;
+}
+
+static int pctv452e_tuner_attach(struct dvb_usb_adapter *a)
+{
+	if (!a->fe_adap[0].fe)
+		return -ENODEV;
+	if (dvb_attach(stb6100_attach, a->fe_adap[0].fe, &stb6100_config,
+					&a->dev->i2c_adap) == NULL) {
+		err("%s failed\n", __func__);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static struct usb_device_id pctv452e_usb_table[] = {
+	{USB_DEVICE(USB_VID_PINNACLE, USB_PID_PCTV_452E)},
+	{USB_DEVICE(USB_VID_TECHNOTREND, USB_PID_TECHNOTREND_CONNECT_S2_3600)},
+	{USB_DEVICE(USB_VID_TECHNOTREND,
+				USB_PID_TECHNOTREND_CONNECT_S2_3650_CI)},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, pctv452e_usb_table);
+
+static struct dvb_usb_device_properties pctv452e_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER, /* more ? */
+	.usb_ctrl = DEVICE_SPECIFIC,
+
+	.size_of_priv     = sizeof(struct pctv452e_state),
+
+	.power_ctrl       = pctv452e_power_ctrl,
+
+	.rc.core = {
+		.rc_codes	= RC_MAP_DIB0700_RC5_TABLE,
+		.allowed_protos	= RC_PROTO_BIT_RC5,
+		.rc_query	= pctv452e_rc_query,
+		.rc_interval	= 100,
+	},
+
+	.num_adapters     = 1,
+	.adapter = {{
+		.num_frontends = 1,
+		.fe = {{
+			.frontend_attach  = pctv452e_frontend_attach,
+			.tuner_attach     = pctv452e_tuner_attach,
+
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type     = USB_ISOC,
+				.count    = 4,
+				.endpoint = 0x02,
+				.u = {
+					.isoc = {
+						.framesperurb = 4,
+						.framesize    = 940,
+						.interval     = 1
+					}
+				}
+			},
+		} },
+	} },
+
+	.i2c_algo = &pctv452e_i2c_algo,
+
+	.generic_bulk_ctrl_endpoint = 1, /* allow generice rw function */
+
+	.num_device_descs = 1,
+	.devices = {
+		{ .name = "PCTV HDTV USB",
+		  .cold_ids = { NULL, NULL }, /* this is a warm only device */
+		  .warm_ids = { &pctv452e_usb_table[0], NULL }
+		},
+		{ NULL },
+	}
+};
+
+static struct dvb_usb_device_properties tt_connect_s2_3600_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER, /* more ? */
+	.usb_ctrl = DEVICE_SPECIFIC,
+
+	.size_of_priv		= sizeof(struct pctv452e_state),
+
+	.power_ctrl		= pctv452e_power_ctrl,
+	.read_mac_address	= pctv452e_read_mac_address,
+
+	.rc.core = {
+		.rc_codes	= RC_MAP_TT_1500,
+		.allowed_protos	= RC_PROTO_BIT_RC5,
+		.rc_query	= pctv452e_rc_query,
+		.rc_interval	= 100,
+	},
+
+	.num_adapters		= 1,
+	.adapter = {{
+		.num_frontends = 1,
+		.fe = {{
+			.frontend_attach = pctv452e_frontend_attach,
+			.tuner_attach = pctv452e_tuner_attach,
+
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_ISOC,
+				.count = 4,
+				.endpoint = 0x02,
+				.u = {
+					.isoc = {
+						.framesperurb = 64,
+						.framesize = 940,
+						.interval = 1
+					}
+				}
+			},
+
+		} },
+	} },
+
+	.i2c_algo = &pctv452e_i2c_algo,
+
+	.generic_bulk_ctrl_endpoint = 1, /* allow generic rw function*/
+
+	.num_device_descs = 2,
+	.devices = {
+		{ .name = "Technotrend TT Connect S2-3600",
+		  .cold_ids = { NULL, NULL }, /* this is a warm only device */
+		  .warm_ids = { &pctv452e_usb_table[1], NULL }
+		},
+		{ .name = "Technotrend TT Connect S2-3650-CI",
+		  .cold_ids = { NULL, NULL },
+		  .warm_ids = { &pctv452e_usb_table[2], NULL }
+		},
+		{ NULL },
+	}
+};
+
+static void pctv452e_usb_disconnect(struct usb_interface *intf)
+{
+	struct dvb_usb_device *d = usb_get_intfdata(intf);
+
+	tt3650_ci_uninit(d);
+	dvb_usb_device_exit(intf);
+}
+
+static int pctv452e_usb_probe(struct usb_interface *intf,
+				const struct usb_device_id *id)
+{
+	if (0 == dvb_usb_device_init(intf, &pctv452e_properties,
+					THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, &tt_connect_s2_3600_properties,
+					THIS_MODULE, NULL, adapter_nr))
+		return 0;
+
+	return -ENODEV;
+}
+
+static struct usb_driver pctv452e_usb_driver = {
+	.name       = "pctv452e",
+	.probe      = pctv452e_usb_probe,
+	.disconnect = pctv452e_usb_disconnect,
+	.id_table   = pctv452e_usb_table,
+};
+
+module_usb_driver(pctv452e_usb_driver);
+
+MODULE_AUTHOR("Dominik Kuhlen <dkuhlen@gmx.net>");
+MODULE_AUTHOR("Andre Weidemann <Andre.Weidemann@web.de>");
+MODULE_AUTHOR("Michael H. Schimek <mschimek@gmx.at>");
+MODULE_DESCRIPTION("Pinnacle PCTV HDTV USB DVB / TT connect S2-3600 Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/technisat-usb2.c b/drivers/media/usb/dvb-usb/technisat-usb2.c
new file mode 100644
index 0000000..18d0f8f
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/technisat-usb2.c
@@ -0,0 +1,807 @@
+/*
+ * Linux driver for Technisat DVB-S/S2 USB 2.0 device
+ *
+ * Copyright (C) 2010 Patrick Boettcher,
+ *                    Kernel Labs Inc. PO Box 745, St James, NY 11780
+ *
+ * Development was sponsored by Technisat Digital UK Limited, whose
+ * registered office is Witan Gate House 500 - 600 Witan Gate West,
+ * Milton Keynes, MK9 1SH
+ *
+ * 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 PROVIDED "AS IS" AND BOTH THE COPYRIGHT HOLDER AND
+ * TECHNISAT DIGITAL UK LTD DISCLAIM ALL WARRANTIES WITH REGARD TO
+ * THIS PROGRAM INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY OR
+ * FITNESS FOR A PARTICULAR PURPOSE.  NEITHER THE COPYRIGHT HOLDER
+ * NOR TECHNISAT DIGITAL UK LIMITED SHALL BE LIABLE FOR ANY SPECIAL,
+ * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS PROGRAM. See the
+ * GNU General Public License for more details.
+ */
+
+#define DVB_USB_LOG_PREFIX "technisat-usb2"
+#include "dvb-usb.h"
+
+#include "stv6110x.h"
+#include "stv090x.h"
+
+/* module parameters */
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug,
+		"set debugging level (bit-mask: 1=info,2=eeprom,4=i2c,8=rc)." \
+		DVB_USB_DEBUG_STATUS);
+
+/* disables all LED control command and
+ * also does not start the signal polling thread */
+static int disable_led_control;
+module_param(disable_led_control, int, 0444);
+MODULE_PARM_DESC(disable_led_control,
+		"disable LED control of the device (default: 0 - LED control is active).");
+
+/* device private data */
+struct technisat_usb2_state {
+	struct dvb_usb_device *dev;
+	struct delayed_work green_led_work;
+	u8 power_state;
+
+	u16 last_scan_code;
+
+	u8 buf[64];
+};
+
+/* debug print helpers */
+#define deb_info(args...)    dprintk(debug, 0x01, args)
+#define deb_eeprom(args...)  dprintk(debug, 0x02, args)
+#define deb_i2c(args...)     dprintk(debug, 0x04, args)
+#define deb_rc(args...)      dprintk(debug, 0x08, args)
+
+/* vendor requests */
+#define SET_IFCLK_TO_EXTERNAL_TSCLK_VENDOR_REQUEST 0xB3
+#define SET_FRONT_END_RESET_VENDOR_REQUEST         0xB4
+#define GET_VERSION_INFO_VENDOR_REQUEST            0xB5
+#define SET_GREEN_LED_VENDOR_REQUEST               0xB6
+#define SET_RED_LED_VENDOR_REQUEST                 0xB7
+#define GET_IR_DATA_VENDOR_REQUEST                 0xB8
+#define SET_LED_TIMER_DIVIDER_VENDOR_REQUEST       0xB9
+#define SET_USB_REENUMERATION                      0xBA
+
+/* i2c-access methods */
+#define I2C_SPEED_100KHZ_BIT 0x40
+
+#define I2C_STATUS_NAK 7
+#define I2C_STATUS_OK 8
+
+static int technisat_usb2_i2c_access(struct usb_device *udev,
+		u8 device_addr, u8 *tx, u8 txlen, u8 *rx, u8 rxlen)
+{
+	u8 *b;
+	int ret, actual_length;
+
+	b = kmalloc(64, GFP_KERNEL);
+	if (!b)
+		return -ENOMEM;
+
+	deb_i2c("i2c-access: %02x, tx: ", device_addr);
+	debug_dump(tx, txlen, deb_i2c);
+	deb_i2c(" ");
+
+	if (txlen > 62) {
+		err("i2c TX buffer can't exceed 62 bytes (dev 0x%02x)",
+				device_addr);
+		txlen = 62;
+	}
+	if (rxlen > 62) {
+		err("i2c RX buffer can't exceed 62 bytes (dev 0x%02x)",
+				device_addr);
+		rxlen = 62;
+	}
+
+	b[0] = I2C_SPEED_100KHZ_BIT;
+	b[1] = device_addr << 1;
+
+	if (rx != NULL) {
+		b[0] |= rxlen;
+		b[1] |= 1;
+	}
+
+	memcpy(&b[2], tx, txlen);
+	ret = usb_bulk_msg(udev,
+			usb_sndbulkpipe(udev, 0x01),
+			b, 2 + txlen,
+			NULL, 1000);
+
+	if (ret < 0) {
+		err("i2c-error: out failed %02x = %d", device_addr, ret);
+		goto err;
+	}
+
+	ret = usb_bulk_msg(udev,
+			usb_rcvbulkpipe(udev, 0x01),
+			b, 64, &actual_length, 1000);
+	if (ret < 0) {
+		err("i2c-error: in failed %02x = %d", device_addr, ret);
+		goto err;
+	}
+
+	if (b[0] != I2C_STATUS_OK) {
+		err("i2c-error: %02x = %d", device_addr, b[0]);
+		/* handle tuner-i2c-nak */
+		if (!(b[0] == I2C_STATUS_NAK &&
+				device_addr == 0x60
+				/* && device_is_technisat_usb2 */))
+			goto err;
+	}
+
+	deb_i2c("status: %d, ", b[0]);
+
+	if (rx != NULL) {
+		memcpy(rx, &b[2], rxlen);
+
+		deb_i2c("rx (%d): ", rxlen);
+		debug_dump(rx, rxlen, deb_i2c);
+	}
+
+	deb_i2c("\n");
+
+err:
+	kfree(b);
+	return ret;
+}
+
+static int technisat_usb2_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msg,
+				int num)
+{
+	int ret = 0, i;
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+
+	/* Ensure nobody else hits the i2c bus while we're sending our
+	   sequence of messages, (such as the remote control thread) */
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	for (i = 0; i < num; i++) {
+		if (i+1 < num && msg[i+1].flags & I2C_M_RD) {
+			ret = technisat_usb2_i2c_access(d->udev, msg[i+1].addr,
+						msg[i].buf, msg[i].len,
+						msg[i+1].buf, msg[i+1].len);
+			if (ret != 0)
+				break;
+			i++;
+		} else {
+			ret = technisat_usb2_i2c_access(d->udev, msg[i].addr,
+						msg[i].buf, msg[i].len,
+						NULL, 0);
+			if (ret != 0)
+				break;
+		}
+	}
+
+	if (ret == 0)
+		ret = i;
+
+	mutex_unlock(&d->i2c_mutex);
+
+	return ret;
+}
+
+static u32 technisat_usb2_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm technisat_usb2_i2c_algo = {
+	.master_xfer   = technisat_usb2_i2c_xfer,
+	.functionality = technisat_usb2_i2c_func,
+};
+
+#if 0
+static void technisat_usb2_frontend_reset(struct usb_device *udev)
+{
+	usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+			SET_FRONT_END_RESET_VENDOR_REQUEST,
+			USB_TYPE_VENDOR | USB_DIR_OUT,
+			10, 0,
+			NULL, 0, 500);
+}
+#endif
+
+/* LED control */
+enum technisat_usb2_led_state {
+	TECH_LED_OFF,
+	TECH_LED_BLINK,
+	TECH_LED_ON,
+	TECH_LED_UNDEFINED
+};
+
+static int technisat_usb2_set_led(struct dvb_usb_device *d, int red,
+				  enum technisat_usb2_led_state st)
+{
+	struct technisat_usb2_state *state = d->priv;
+	u8 *led = state->buf;
+	int ret;
+
+	led[0] = red ? SET_RED_LED_VENDOR_REQUEST : SET_GREEN_LED_VENDOR_REQUEST;
+
+	if (disable_led_control && st != TECH_LED_OFF)
+		return 0;
+
+	switch (st) {
+	case TECH_LED_ON:
+		led[1] = 0x82;
+		break;
+	case TECH_LED_BLINK:
+		led[1] = 0x82;
+		if (red) {
+			led[2] = 0x02;
+			led[3] = 10;
+			led[4] = 10;
+		} else {
+			led[2] = 0xff;
+			led[3] = 50;
+			led[4] = 50;
+		}
+		led[5] = 1;
+		break;
+
+	default:
+	case TECH_LED_OFF:
+		led[1] = 0x80;
+		break;
+	}
+
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	ret = usb_control_msg(d->udev, usb_sndctrlpipe(d->udev, 0),
+		red ? SET_RED_LED_VENDOR_REQUEST : SET_GREEN_LED_VENDOR_REQUEST,
+		USB_TYPE_VENDOR | USB_DIR_OUT,
+		0, 0,
+		led, 8, 500);
+
+	mutex_unlock(&d->i2c_mutex);
+	return ret;
+}
+
+static int technisat_usb2_set_led_timer(struct dvb_usb_device *d, u8 red, u8 green)
+{
+	struct technisat_usb2_state *state = d->priv;
+	u8 *b = state->buf;
+	int ret;
+
+	b[0] = 0;
+
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	ret = usb_control_msg(d->udev, usb_sndctrlpipe(d->udev, 0),
+		SET_LED_TIMER_DIVIDER_VENDOR_REQUEST,
+		USB_TYPE_VENDOR | USB_DIR_OUT,
+		(red << 8) | green, 0,
+		b, 1, 500);
+
+	mutex_unlock(&d->i2c_mutex);
+
+	return ret;
+}
+
+static void technisat_usb2_green_led_control(struct work_struct *work)
+{
+	struct technisat_usb2_state *state =
+		container_of(work, struct technisat_usb2_state, green_led_work.work);
+	struct dvb_frontend *fe = state->dev->adapter[0].fe_adap[0].fe;
+
+	if (state->power_state == 0)
+		goto schedule;
+
+	if (fe != NULL) {
+		enum fe_status status;
+
+		if (fe->ops.read_status(fe, &status) != 0)
+			goto schedule;
+
+		if (status & FE_HAS_LOCK) {
+			u32 ber;
+
+			if (fe->ops.read_ber(fe, &ber) != 0)
+				goto schedule;
+
+			if (ber > 1000)
+				technisat_usb2_set_led(state->dev, 0, TECH_LED_BLINK);
+			else
+				technisat_usb2_set_led(state->dev, 0, TECH_LED_ON);
+		} else
+			technisat_usb2_set_led(state->dev, 0, TECH_LED_OFF);
+	}
+
+schedule:
+	schedule_delayed_work(&state->green_led_work,
+			msecs_to_jiffies(500));
+}
+
+/* method to find out whether the firmware has to be downloaded or not */
+static int technisat_usb2_identify_state(struct usb_device *udev,
+		struct dvb_usb_device_properties *props,
+		struct dvb_usb_device_description **desc, int *cold)
+{
+	int ret;
+	u8 *version;
+
+	version = kmalloc(3, GFP_KERNEL);
+	if (!version)
+		return -ENOMEM;
+
+	/* first select the interface */
+	if (usb_set_interface(udev, 0, 1) != 0)
+		err("could not set alternate setting to 0");
+	else
+		info("set alternate setting");
+
+	*cold = 0; /* by default do not download a firmware - just in case something is wrong */
+
+	ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+		GET_VERSION_INFO_VENDOR_REQUEST,
+		USB_TYPE_VENDOR | USB_DIR_IN,
+		0, 0,
+		version, 3, 500);
+
+	if (ret < 0)
+		*cold = 1;
+	else {
+		info("firmware version: %d.%d", version[1], version[2]);
+		*cold = 0;
+	}
+
+	kfree(version);
+
+	return 0;
+}
+
+/* power control */
+static int technisat_usb2_power_ctrl(struct dvb_usb_device *d, int level)
+{
+	struct technisat_usb2_state *state = d->priv;
+
+	state->power_state = level;
+
+	if (disable_led_control)
+		return 0;
+
+	/* green led is turned off in any case - will be turned on when tuning */
+	technisat_usb2_set_led(d, 0, TECH_LED_OFF);
+	/* red led is turned on all the time */
+	technisat_usb2_set_led(d, 1, TECH_LED_ON);
+	return 0;
+}
+
+/* mac address reading - from the eeprom */
+#if 0
+static void technisat_usb2_eeprom_dump(struct dvb_usb_device *d)
+{
+	u8 reg;
+	u8 b[16];
+	int i, j;
+
+	/* full EEPROM dump */
+	for (j = 0; j < 256 * 4; j += 16) {
+		reg = j;
+		if (technisat_usb2_i2c_access(d->udev, 0x50 + j / 256, &reg, 1, b, 16) != 0)
+			break;
+
+		deb_eeprom("EEPROM: %01x%02x: ", j / 256, reg);
+		for (i = 0; i < 16; i++)
+			deb_eeprom("%02x ", b[i]);
+		deb_eeprom("\n");
+	}
+}
+#endif
+
+static u8 technisat_usb2_calc_lrc(const u8 *b, u16 length)
+{
+	u8 lrc = 0;
+	while (--length)
+		lrc ^= *b++;
+	return lrc;
+}
+
+static int technisat_usb2_eeprom_lrc_read(struct dvb_usb_device *d,
+	u16 offset, u8 *b, u16 length, u8 tries)
+{
+	u8 bo = offset & 0xff;
+	struct i2c_msg msg[] = {
+		{
+			.addr = 0x50 | ((offset >> 8) & 0x3),
+			.buf = &bo,
+			.len = 1
+		}, {
+			.addr = 0x50 | ((offset >> 8) & 0x3),
+			.flags	= I2C_M_RD,
+			.buf = b,
+			.len = length
+		}
+	};
+
+	while (tries--) {
+		int status;
+
+		if (i2c_transfer(&d->i2c_adap, msg, 2) != 2)
+			break;
+
+		status =
+			technisat_usb2_calc_lrc(b, length - 1) == b[length - 1];
+
+		if (status)
+			return 0;
+	}
+
+	return -EREMOTEIO;
+}
+
+#define EEPROM_MAC_START 0x3f8
+#define EEPROM_MAC_TOTAL 8
+static int technisat_usb2_read_mac_address(struct dvb_usb_device *d,
+		u8 mac[])
+{
+	u8 buf[EEPROM_MAC_TOTAL];
+
+	if (technisat_usb2_eeprom_lrc_read(d, EEPROM_MAC_START,
+				buf, EEPROM_MAC_TOTAL, 4) != 0)
+		return -ENODEV;
+
+	memcpy(mac, buf, 6);
+	return 0;
+}
+
+static struct stv090x_config technisat_usb2_stv090x_config;
+
+/* frontend attach */
+static int technisat_usb2_set_voltage(struct dvb_frontend *fe,
+				      enum fe_sec_voltage voltage)
+{
+	int i;
+	u8 gpio[3] = { 0 }; /* 0 = 2, 1 = 3, 2 = 4 */
+
+	gpio[2] = 1; /* high - voltage ? */
+
+	switch (voltage) {
+	case SEC_VOLTAGE_13:
+		gpio[0] = 1;
+		break;
+	case SEC_VOLTAGE_18:
+		gpio[0] = 1;
+		gpio[1] = 1;
+		break;
+	default:
+	case SEC_VOLTAGE_OFF:
+		break;
+	}
+
+	for (i = 0; i < 3; i++)
+		if (technisat_usb2_stv090x_config.set_gpio(fe, i+2, 0,
+							   gpio[i], 0) != 0)
+			return -EREMOTEIO;
+	return 0;
+}
+
+static struct stv090x_config technisat_usb2_stv090x_config = {
+	.device         = STV0903,
+	.demod_mode     = STV090x_SINGLE,
+	.clk_mode       = STV090x_CLK_EXT,
+
+	.xtal           = 8000000,
+	.address        = 0x68,
+
+	.ts1_mode       = STV090x_TSMODE_DVBCI,
+	.ts1_clk        = 13400000,
+	.ts1_tei        = 1,
+
+	.repeater_level = STV090x_RPTLEVEL_64,
+
+	.tuner_bbgain   = 6,
+};
+
+static struct stv6110x_config technisat_usb2_stv6110x_config = {
+	.addr           = 0x60,
+	.refclk         = 16000000,
+	.clk_div        = 2,
+};
+
+static int technisat_usb2_frontend_attach(struct dvb_usb_adapter *a)
+{
+	struct usb_device *udev = a->dev->udev;
+	int ret;
+
+	a->fe_adap[0].fe = dvb_attach(stv090x_attach, &technisat_usb2_stv090x_config,
+			&a->dev->i2c_adap, STV090x_DEMODULATOR_0);
+
+	if (a->fe_adap[0].fe) {
+		const struct stv6110x_devctl *ctl;
+
+		ctl = dvb_attach(stv6110x_attach,
+				a->fe_adap[0].fe,
+				&technisat_usb2_stv6110x_config,
+				&a->dev->i2c_adap);
+
+		if (ctl) {
+			technisat_usb2_stv090x_config.tuner_init          = ctl->tuner_init;
+			technisat_usb2_stv090x_config.tuner_sleep         = ctl->tuner_sleep;
+			technisat_usb2_stv090x_config.tuner_set_mode      = ctl->tuner_set_mode;
+			technisat_usb2_stv090x_config.tuner_set_frequency = ctl->tuner_set_frequency;
+			technisat_usb2_stv090x_config.tuner_get_frequency = ctl->tuner_get_frequency;
+			technisat_usb2_stv090x_config.tuner_set_bandwidth = ctl->tuner_set_bandwidth;
+			technisat_usb2_stv090x_config.tuner_get_bandwidth = ctl->tuner_get_bandwidth;
+			technisat_usb2_stv090x_config.tuner_set_bbgain    = ctl->tuner_set_bbgain;
+			technisat_usb2_stv090x_config.tuner_get_bbgain    = ctl->tuner_get_bbgain;
+			technisat_usb2_stv090x_config.tuner_set_refclk    = ctl->tuner_set_refclk;
+			technisat_usb2_stv090x_config.tuner_get_status    = ctl->tuner_get_status;
+
+			/* call the init function once to initialize
+			   tuner's clock output divider and demod's
+			   master clock */
+			if (a->fe_adap[0].fe->ops.init)
+				a->fe_adap[0].fe->ops.init(a->fe_adap[0].fe);
+
+			if (mutex_lock_interruptible(&a->dev->i2c_mutex) < 0)
+				return -EAGAIN;
+
+			ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+					SET_IFCLK_TO_EXTERNAL_TSCLK_VENDOR_REQUEST,
+					USB_TYPE_VENDOR | USB_DIR_OUT,
+					0, 0,
+					NULL, 0, 500);
+			mutex_unlock(&a->dev->i2c_mutex);
+
+			if (ret != 0)
+				err("could not set IF_CLK to external");
+
+			a->fe_adap[0].fe->ops.set_voltage = technisat_usb2_set_voltage;
+
+			/* if everything was successful assign a nice name to the frontend */
+			strlcpy(a->fe_adap[0].fe->ops.info.name, a->dev->desc->name,
+					sizeof(a->fe_adap[0].fe->ops.info.name));
+		} else {
+			dvb_frontend_detach(a->fe_adap[0].fe);
+			a->fe_adap[0].fe = NULL;
+		}
+	}
+
+	technisat_usb2_set_led_timer(a->dev, 1, 1);
+
+	return a->fe_adap[0].fe == NULL ? -ENODEV : 0;
+}
+
+/* Remote control */
+
+/* the device is giving providing raw IR-signals to the host mapping
+ * it only to one remote control is just the default implementation
+ */
+#define NOMINAL_IR_BIT_TRANSITION_TIME_US 889
+#define NOMINAL_IR_BIT_TIME_US (2 * NOMINAL_IR_BIT_TRANSITION_TIME_US)
+
+#define FIRMWARE_CLOCK_TICK 83333
+#define FIRMWARE_CLOCK_DIVISOR 256
+
+#define IR_PERCENT_TOLERANCE 15
+
+#define NOMINAL_IR_BIT_TRANSITION_TICKS ((NOMINAL_IR_BIT_TRANSITION_TIME_US * 1000 * 1000) / FIRMWARE_CLOCK_TICK)
+#define NOMINAL_IR_BIT_TRANSITION_TICK_COUNT (NOMINAL_IR_BIT_TRANSITION_TICKS / FIRMWARE_CLOCK_DIVISOR)
+
+#define NOMINAL_IR_BIT_TIME_TICKS ((NOMINAL_IR_BIT_TIME_US * 1000 * 1000) / FIRMWARE_CLOCK_TICK)
+#define NOMINAL_IR_BIT_TIME_TICK_COUNT (NOMINAL_IR_BIT_TIME_TICKS / FIRMWARE_CLOCK_DIVISOR)
+
+#define MINIMUM_IR_BIT_TRANSITION_TICK_COUNT (NOMINAL_IR_BIT_TRANSITION_TICK_COUNT - ((NOMINAL_IR_BIT_TRANSITION_TICK_COUNT * IR_PERCENT_TOLERANCE) / 100))
+#define MAXIMUM_IR_BIT_TRANSITION_TICK_COUNT (NOMINAL_IR_BIT_TRANSITION_TICK_COUNT + ((NOMINAL_IR_BIT_TRANSITION_TICK_COUNT * IR_PERCENT_TOLERANCE) / 100))
+
+#define MINIMUM_IR_BIT_TIME_TICK_COUNT (NOMINAL_IR_BIT_TIME_TICK_COUNT - ((NOMINAL_IR_BIT_TIME_TICK_COUNT * IR_PERCENT_TOLERANCE) / 100))
+#define MAXIMUM_IR_BIT_TIME_TICK_COUNT (NOMINAL_IR_BIT_TIME_TICK_COUNT + ((NOMINAL_IR_BIT_TIME_TICK_COUNT * IR_PERCENT_TOLERANCE) / 100))
+
+static int technisat_usb2_get_ir(struct dvb_usb_device *d)
+{
+	struct technisat_usb2_state *state = d->priv;
+	u8 *buf = state->buf;
+	u8 *b;
+	int ret;
+	struct ir_raw_event ev;
+
+	buf[0] = GET_IR_DATA_VENDOR_REQUEST;
+	buf[1] = 0x08;
+	buf[2] = 0x8f;
+	buf[3] = MINIMUM_IR_BIT_TRANSITION_TICK_COUNT;
+	buf[4] = MAXIMUM_IR_BIT_TIME_TICK_COUNT;
+
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+	ret = usb_control_msg(d->udev, usb_sndctrlpipe(d->udev, 0),
+			GET_IR_DATA_VENDOR_REQUEST,
+			USB_TYPE_VENDOR | USB_DIR_OUT,
+			0, 0,
+			buf, 5, 500);
+	if (ret < 0)
+		goto unlock;
+
+	buf[1] = 0;
+	buf[2] = 0;
+	ret = usb_control_msg(d->udev, usb_rcvctrlpipe(d->udev, 0),
+			GET_IR_DATA_VENDOR_REQUEST,
+			USB_TYPE_VENDOR | USB_DIR_IN,
+			0x8080, 0,
+			buf, 62, 500);
+
+unlock:
+	mutex_unlock(&d->i2c_mutex);
+
+	if (ret < 0)
+		return ret;
+
+	if (ret == 1)
+		return 0; /* no key pressed */
+
+	/* decoding */
+	b = buf+1;
+
+#if 0
+	deb_rc("RC: %d ", ret);
+	debug_dump(b, ret, deb_rc);
+#endif
+
+	ev.pulse = 0;
+	while (1) {
+		ev.pulse = !ev.pulse;
+		ev.duration = (*b * FIRMWARE_CLOCK_DIVISOR * FIRMWARE_CLOCK_TICK) / 1000;
+		ir_raw_event_store(d->rc_dev, &ev);
+
+		b++;
+		if (*b == 0xff) {
+			ev.pulse = 0;
+			ev.duration = 888888*2;
+			ir_raw_event_store(d->rc_dev, &ev);
+			break;
+		}
+	}
+
+	ir_raw_event_handle(d->rc_dev);
+
+	return 1;
+}
+
+static int technisat_usb2_rc_query(struct dvb_usb_device *d)
+{
+	int ret = technisat_usb2_get_ir(d);
+
+	if (ret < 0)
+		return ret;
+
+	if (ret == 0)
+		return 0;
+
+	if (!disable_led_control)
+		technisat_usb2_set_led(d, 1, TECH_LED_BLINK);
+
+	return 0;
+}
+
+/* DVB-USB and USB stuff follows */
+static struct usb_device_id technisat_usb2_id_table[] = {
+	{ USB_DEVICE(USB_VID_TECHNISAT, USB_PID_TECHNISAT_USB2_DVB_S2) },
+	{ 0 }		/* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, technisat_usb2_id_table);
+
+/* device description */
+static struct dvb_usb_device_properties technisat_usb2_devices = {
+	.caps              = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl          = CYPRESS_FX2,
+
+	.identify_state    = technisat_usb2_identify_state,
+	.firmware          = "dvb-usb-SkyStar_USB_HD_FW_v17_63.HEX.fw",
+
+	.size_of_priv      = sizeof(struct technisat_usb2_state),
+
+	.i2c_algo          = &technisat_usb2_i2c_algo,
+
+	.power_ctrl        = technisat_usb2_power_ctrl,
+	.read_mac_address  = technisat_usb2_read_mac_address,
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.frontend_attach  = technisat_usb2_frontend_attach,
+
+			.stream = {
+				.type = USB_ISOC,
+				.count = 4,
+				.endpoint = 0x2,
+				.u = {
+					.isoc = {
+						.framesperurb = 32,
+						.framesize = 2048,
+						.interval = 1,
+					}
+				}
+			},
+		}},
+			.size_of_priv = 0,
+		},
+	},
+
+	.num_device_descs = 1,
+	.devices = {
+		{   "Technisat SkyStar USB HD (DVB-S/S2)",
+			{ &technisat_usb2_id_table[0], NULL },
+			{ NULL },
+		},
+	},
+
+	.rc.core = {
+		.rc_interval = 100,
+		.rc_codes    = RC_MAP_TECHNISAT_USB2,
+		.module_name = "technisat-usb2",
+		.rc_query    = technisat_usb2_rc_query,
+		.allowed_protos = RC_PROTO_BIT_ALL_IR_DECODER,
+		.driver_type    = RC_DRIVER_IR_RAW,
+	}
+};
+
+static int technisat_usb2_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	struct dvb_usb_device *dev;
+
+	if (dvb_usb_device_init(intf, &technisat_usb2_devices, THIS_MODULE,
+				&dev, adapter_nr) != 0)
+		return -ENODEV;
+
+	if (dev) {
+		struct technisat_usb2_state *state = dev->priv;
+		state->dev = dev;
+
+		if (!disable_led_control) {
+			INIT_DELAYED_WORK(&state->green_led_work,
+					technisat_usb2_green_led_control);
+			schedule_delayed_work(&state->green_led_work,
+					msecs_to_jiffies(500));
+		}
+	}
+
+	return 0;
+}
+
+static void technisat_usb2_disconnect(struct usb_interface *intf)
+{
+	struct dvb_usb_device *dev = usb_get_intfdata(intf);
+
+	/* work and stuff was only created when the device is is hot-state */
+	if (dev != NULL) {
+		struct technisat_usb2_state *state = dev->priv;
+		if (state != NULL)
+			cancel_delayed_work_sync(&state->green_led_work);
+	}
+
+	dvb_usb_device_exit(intf);
+}
+
+static struct usb_driver technisat_usb2_driver = {
+	.name       = "dvb_usb_technisat_usb2",
+	.probe      = technisat_usb2_probe,
+	.disconnect = technisat_usb2_disconnect,
+	.id_table   = technisat_usb2_id_table,
+};
+
+module_usb_driver(technisat_usb2_driver);
+
+MODULE_AUTHOR("Patrick Boettcher <pboettcher@kernellabs.com>");
+MODULE_DESCRIPTION("Driver for Technisat DVB-S/S2 USB 2.0 device");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/ttusb2.c b/drivers/media/usb/dvb-usb/ttusb2.c
new file mode 100644
index 0000000..b4d6811
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/ttusb2.c
@@ -0,0 +1,845 @@
+/* DVB USB compliant linux driver for Technotrend DVB USB boxes and clones
+ * (e.g. Pinnacle 400e DVB-S USB2.0).
+ *
+ * The Pinnacle 400e uses the same protocol as the Technotrend USB1.1 boxes.
+ *
+ * TDA8263 + TDA10086
+ *
+ * I2C addresses:
+ * 0x08 - LNBP21PD   - LNB power supply
+ * 0x0e - TDA10086   - Demodulator
+ * 0x50 - FX2 eeprom
+ * 0x60 - TDA8263    - Tuner
+ * 0x78 ???
+ *
+ * Copyright (c) 2002 Holger Waechtler <holger@convergence.de>
+ * Copyright (c) 2003 Felix Domke <tmbinc@elitedvb.net>
+ * Copyright (C) 2005-6 Patrick Boettcher <pb@linuxtv.org>
+ *
+ *	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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#define DVB_USB_LOG_PREFIX "ttusb2"
+#include "dvb-usb.h"
+
+#include "ttusb2.h"
+
+#include "tda826x.h"
+#include "tda10086.h"
+#include "tda1002x.h"
+#include "tda10048.h"
+#include "tda827x.h"
+#include "lnbp21.h"
+/* CA */
+#include <media/dvb_ca_en50221.h>
+
+/* debug */
+static int dvb_usb_ttusb2_debug;
+#define deb_info(args...)   dprintk(dvb_usb_ttusb2_debug,0x01,args)
+module_param_named(debug,dvb_usb_ttusb2_debug, int, 0644);
+MODULE_PARM_DESC(debug, "set debugging level (1=info (or-able))." DVB_USB_DEBUG_STATUS);
+static int dvb_usb_ttusb2_debug_ci;
+module_param_named(debug_ci,dvb_usb_ttusb2_debug_ci, int, 0644);
+MODULE_PARM_DESC(debug_ci, "set debugging ci." DVB_USB_DEBUG_STATUS);
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+#define ci_dbg(format, arg...)                \
+do {                                          \
+	if (dvb_usb_ttusb2_debug_ci)                                    \
+		printk(KERN_DEBUG DVB_USB_LOG_PREFIX \
+			": %s " format "\n" , __func__, ## arg);       \
+} while (0)
+
+enum {
+	TT3650_CMD_CI_TEST = 0x40,
+	TT3650_CMD_CI_RD_CTRL,
+	TT3650_CMD_CI_WR_CTRL,
+	TT3650_CMD_CI_RD_ATTR,
+	TT3650_CMD_CI_WR_ATTR,
+	TT3650_CMD_CI_RESET,
+	TT3650_CMD_CI_SET_VIDEO_PORT
+};
+
+struct ttusb2_state {
+	struct dvb_ca_en50221 ca;
+	struct mutex ca_mutex;
+	u8 id;
+	u16 last_rc_key;
+};
+
+static int ttusb2_msg(struct dvb_usb_device *d, u8 cmd,
+		u8 *wbuf, int wlen, u8 *rbuf, int rlen)
+{
+	struct ttusb2_state *st = d->priv;
+	u8 *s, *r = NULL;
+	int ret = 0;
+
+	if (4 + rlen > 64)
+		return -EIO;
+
+	s = kzalloc(wlen+4, GFP_KERNEL);
+	if (!s)
+		return -ENOMEM;
+
+	r = kzalloc(64, GFP_KERNEL);
+	if (!r) {
+		kfree(s);
+		return -ENOMEM;
+	}
+
+	s[0] = 0xaa;
+	s[1] = ++st->id;
+	s[2] = cmd;
+	s[3] = wlen;
+	memcpy(&s[4],wbuf,wlen);
+
+	ret = dvb_usb_generic_rw(d, s, wlen+4, r, 64, 0);
+
+	if (ret  != 0 ||
+		r[0] != 0x55 ||
+		r[1] != s[1] ||
+		r[2] != cmd ||
+		(rlen > 0 && r[3] != rlen)) {
+		warn("there might have been an error during control message transfer. (rlen = %d, was %d)",rlen,r[3]);
+		kfree(s);
+		kfree(r);
+		return -EIO;
+	}
+
+	if (rlen > 0)
+		memcpy(rbuf, &r[4], rlen);
+
+	kfree(s);
+	kfree(r);
+
+	return 0;
+}
+
+/* ci */
+static int tt3650_ci_msg(struct dvb_usb_device *d, u8 cmd, u8 *data, unsigned int write_len, unsigned int read_len)
+{
+	int ret;
+	u8 rx[60];/* (64 -4) */
+	ret = ttusb2_msg(d, cmd, data, write_len, rx, read_len);
+	if (!ret)
+		memcpy(data, rx, read_len);
+	return ret;
+}
+
+static int tt3650_ci_msg_locked(struct dvb_ca_en50221 *ca, u8 cmd, u8 *data, unsigned int write_len, unsigned int read_len)
+{
+	struct dvb_usb_device *d = ca->data;
+	struct ttusb2_state *state = d->priv;
+	int ret;
+
+	mutex_lock(&state->ca_mutex);
+	ret = tt3650_ci_msg(d, cmd, data, write_len, read_len);
+	mutex_unlock(&state->ca_mutex);
+
+	return ret;
+}
+
+static int tt3650_ci_read_attribute_mem(struct dvb_ca_en50221 *ca, int slot, int address)
+{
+	u8 buf[3];
+	int ret = 0;
+
+	if (slot)
+		return -EINVAL;
+
+	buf[0] = (address >> 8) & 0x0F;
+	buf[1] = address;
+
+
+	ret = tt3650_ci_msg_locked(ca, TT3650_CMD_CI_RD_ATTR, buf, 2, 3);
+
+	ci_dbg("%04x -> %d 0x%02x", address, ret, buf[2]);
+
+	if (ret < 0)
+		return ret;
+
+	return buf[2];
+}
+
+static int tt3650_ci_write_attribute_mem(struct dvb_ca_en50221 *ca, int slot, int address, u8 value)
+{
+	u8 buf[3];
+
+	ci_dbg("%d 0x%04x 0x%02x", slot, address, value);
+
+	if (slot)
+		return -EINVAL;
+
+	buf[0] = (address >> 8) & 0x0F;
+	buf[1] = address;
+	buf[2] = value;
+
+	return tt3650_ci_msg_locked(ca, TT3650_CMD_CI_WR_ATTR, buf, 3, 3);
+}
+
+static int tt3650_ci_read_cam_control(struct dvb_ca_en50221 *ca, int slot, u8 address)
+{
+	u8 buf[2];
+	int ret;
+
+	if (slot)
+		return -EINVAL;
+
+	buf[0] = address & 3;
+
+	ret = tt3650_ci_msg_locked(ca, TT3650_CMD_CI_RD_CTRL, buf, 1, 2);
+
+	ci_dbg("0x%02x -> %d 0x%02x", address, ret, buf[1]);
+
+	if (ret < 0)
+		return ret;
+
+	return buf[1];
+}
+
+static int tt3650_ci_write_cam_control(struct dvb_ca_en50221 *ca, int slot, u8 address, u8 value)
+{
+	u8 buf[2];
+
+	ci_dbg("%d 0x%02x 0x%02x", slot, address, value);
+
+	if (slot)
+		return -EINVAL;
+
+	buf[0] = address;
+	buf[1] = value;
+
+	return tt3650_ci_msg_locked(ca, TT3650_CMD_CI_WR_CTRL, buf, 2, 2);
+}
+
+static int tt3650_ci_set_video_port(struct dvb_ca_en50221 *ca, int slot, int enable)
+{
+	u8 buf[1];
+	int ret;
+
+	ci_dbg("%d %d", slot, enable);
+
+	if (slot)
+		return -EINVAL;
+
+	buf[0] = enable;
+
+	ret = tt3650_ci_msg_locked(ca, TT3650_CMD_CI_SET_VIDEO_PORT, buf, 1, 1);
+	if (ret < 0)
+		return ret;
+
+	if (enable != buf[0]) {
+		err("CI not %sabled.", enable ? "en" : "dis");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int tt3650_ci_slot_shutdown(struct dvb_ca_en50221 *ca, int slot)
+{
+	return tt3650_ci_set_video_port(ca, slot, 0);
+}
+
+static int tt3650_ci_slot_ts_enable(struct dvb_ca_en50221 *ca, int slot)
+{
+	return tt3650_ci_set_video_port(ca, slot, 1);
+}
+
+static int tt3650_ci_slot_reset(struct dvb_ca_en50221 *ca, int slot)
+{
+	struct dvb_usb_device *d = ca->data;
+	struct ttusb2_state *state = d->priv;
+	u8 buf[1];
+	int ret;
+
+	ci_dbg("%d", slot);
+
+	if (slot)
+		return -EINVAL;
+
+	buf[0] = 0;
+
+	mutex_lock(&state->ca_mutex);
+
+	ret = tt3650_ci_msg(d, TT3650_CMD_CI_RESET, buf, 1, 1);
+	if (ret)
+		goto failed;
+
+	msleep(500);
+
+	buf[0] = 1;
+
+	ret = tt3650_ci_msg(d, TT3650_CMD_CI_RESET, buf, 1, 1);
+	if (ret)
+		goto failed;
+
+	msleep(500);
+
+	buf[0] = 0; /* FTA */
+
+	ret = tt3650_ci_msg(d, TT3650_CMD_CI_SET_VIDEO_PORT, buf, 1, 1);
+
+	msleep(1100);
+
+ failed:
+	mutex_unlock(&state->ca_mutex);
+
+	return ret;
+}
+
+static int tt3650_ci_poll_slot_status(struct dvb_ca_en50221 *ca, int slot, int open)
+{
+	u8 buf[1];
+	int ret;
+
+	if (slot)
+		return -EINVAL;
+
+	ret = tt3650_ci_msg_locked(ca, TT3650_CMD_CI_TEST, buf, 0, 1);
+	if (ret)
+		return ret;
+
+	if (1 == buf[0]) {
+		return DVB_CA_EN50221_POLL_CAM_PRESENT |
+			DVB_CA_EN50221_POLL_CAM_READY;
+	}
+	return 0;
+}
+
+static void tt3650_ci_uninit(struct dvb_usb_device *d)
+{
+	struct ttusb2_state *state;
+
+	ci_dbg("");
+
+	if (NULL == d)
+		return;
+
+	state = d->priv;
+	if (NULL == state)
+		return;
+
+	if (NULL == state->ca.data)
+		return;
+
+	dvb_ca_en50221_release(&state->ca);
+
+	memset(&state->ca, 0, sizeof(state->ca));
+}
+
+static int tt3650_ci_init(struct dvb_usb_adapter *a)
+{
+	struct dvb_usb_device *d = a->dev;
+	struct ttusb2_state *state = d->priv;
+	int ret;
+
+	ci_dbg("");
+
+	mutex_init(&state->ca_mutex);
+
+	state->ca.owner = THIS_MODULE;
+	state->ca.read_attribute_mem = tt3650_ci_read_attribute_mem;
+	state->ca.write_attribute_mem = tt3650_ci_write_attribute_mem;
+	state->ca.read_cam_control = tt3650_ci_read_cam_control;
+	state->ca.write_cam_control = tt3650_ci_write_cam_control;
+	state->ca.slot_reset = tt3650_ci_slot_reset;
+	state->ca.slot_shutdown = tt3650_ci_slot_shutdown;
+	state->ca.slot_ts_enable = tt3650_ci_slot_ts_enable;
+	state->ca.poll_slot_status = tt3650_ci_poll_slot_status;
+	state->ca.data = d;
+
+	ret = dvb_ca_en50221_init(&a->dvb_adap,
+				  &state->ca,
+				  /* flags */ 0,
+				  /* n_slots */ 1);
+	if (ret) {
+		err("Cannot initialize CI: Error %d.", ret);
+		memset(&state->ca, 0, sizeof(state->ca));
+		return ret;
+	}
+
+	info("CI initialized.");
+
+	return 0;
+}
+
+static int ttusb2_i2c_xfer(struct i2c_adapter *adap,struct i2c_msg msg[],int num)
+{
+	struct dvb_usb_device *d = i2c_get_adapdata(adap);
+	static u8 obuf[60], ibuf[60];
+	int i, write_read, read;
+
+	if (mutex_lock_interruptible(&d->i2c_mutex) < 0)
+		return -EAGAIN;
+
+	if (num > 2)
+		warn("more than 2 i2c messages at a time is not handled yet. TODO.");
+
+	for (i = 0; i < num; i++) {
+		write_read = i+1 < num && (msg[i+1].flags & I2C_M_RD);
+		read = msg[i].flags & I2C_M_RD;
+
+		if (3 + msg[i].len > sizeof(obuf)) {
+			err("i2c wr len=%d too high", msg[i].len);
+			break;
+		}
+		if (write_read) {
+			if (3 + msg[i+1].len > sizeof(ibuf)) {
+				err("i2c rd len=%d too high", msg[i+1].len);
+				break;
+			}
+		} else if (read) {
+			if (3 + msg[i].len > sizeof(ibuf)) {
+				err("i2c rd len=%d too high", msg[i].len);
+				break;
+			}
+		}
+
+		obuf[0] = (msg[i].addr << 1) | (write_read | read);
+		if (read)
+			obuf[1] = 0;
+		else
+			obuf[1] = msg[i].len;
+
+		/* read request */
+		if (write_read)
+			obuf[2] = msg[i+1].len;
+		else if (read)
+			obuf[2] = msg[i].len;
+		else
+			obuf[2] = 0;
+
+		memcpy(&obuf[3], msg[i].buf, msg[i].len);
+
+		if (ttusb2_msg(d, CMD_I2C_XFER, obuf, obuf[1]+3, ibuf, obuf[2] + 3) < 0) {
+			err("i2c transfer failed.");
+			break;
+		}
+
+		if (write_read) {
+			memcpy(msg[i+1].buf, &ibuf[3], msg[i+1].len);
+			i++;
+		} else if (read)
+			memcpy(msg[i].buf, &ibuf[3], msg[i].len);
+	}
+
+	mutex_unlock(&d->i2c_mutex);
+	return i;
+}
+
+static u32 ttusb2_i2c_func(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+static struct i2c_algorithm ttusb2_i2c_algo = {
+	.master_xfer   = ttusb2_i2c_xfer,
+	.functionality = ttusb2_i2c_func,
+};
+
+/* command to poll IR receiver (copied from pctv452e.c) */
+#define CMD_GET_IR_CODE     0x1b
+
+/* IR */
+static int tt3650_rc_query(struct dvb_usb_device *d)
+{
+	int ret;
+	u8 rx[9]; /* A CMD_GET_IR_CODE reply is 9 bytes long */
+	struct ttusb2_state *st = d->priv;
+	ret = ttusb2_msg(d, CMD_GET_IR_CODE, NULL, 0, rx, sizeof(rx));
+	if (ret != 0)
+		return ret;
+
+	if (rx[8] & 0x01) {
+		/* got a "press" event */
+		st->last_rc_key = RC_SCANCODE_RC5(rx[3], rx[2]);
+		deb_info("%s: cmd=0x%02x sys=0x%02x\n", __func__, rx[2], rx[3]);
+		rc_keydown(d->rc_dev, RC_PROTO_RC5, st->last_rc_key, rx[1]);
+	} else if (st->last_rc_key) {
+		rc_keyup(d->rc_dev);
+		st->last_rc_key = 0;
+	}
+
+	return 0;
+}
+
+
+/* Callbacks for DVB USB */
+static int ttusb2_identify_state (struct usb_device *udev, struct
+		dvb_usb_device_properties *props, struct dvb_usb_device_description **desc,
+		int *cold)
+{
+	*cold = udev->descriptor.iManufacturer == 0 && udev->descriptor.iProduct == 0;
+	return 0;
+}
+
+static int ttusb2_power_ctrl(struct dvb_usb_device *d, int onoff)
+{
+	u8 b = onoff;
+	ttusb2_msg(d, CMD_POWER, &b, 0, NULL, 0);
+	return ttusb2_msg(d, CMD_POWER, &b, 1, NULL, 0);
+}
+
+
+static struct tda10086_config tda10086_config = {
+	.demod_address = 0x0e,
+	.invert = 0,
+	.diseqc_tone = 1,
+	.xtal_freq = TDA10086_XTAL_16M,
+};
+
+static struct tda10023_config tda10023_config = {
+	.demod_address = 0x0c,
+	.invert = 0,
+	.xtal = 16000000,
+	.pll_m = 11,
+	.pll_p = 3,
+	.pll_n = 1,
+	.deltaf = 0xa511,
+};
+
+static struct tda10048_config tda10048_config = {
+	.demod_address    = 0x10 >> 1,
+	.output_mode      = TDA10048_PARALLEL_OUTPUT,
+	.inversion        = TDA10048_INVERSION_ON,
+	.dtv6_if_freq_khz = TDA10048_IF_4000,
+	.dtv7_if_freq_khz = TDA10048_IF_4500,
+	.dtv8_if_freq_khz = TDA10048_IF_5000,
+	.clk_freq_khz     = TDA10048_CLK_16000,
+	.no_firmware      = 1,
+	.set_pll          = true ,
+	.pll_m            = 5,
+	.pll_n            = 3,
+	.pll_p            = 0,
+};
+
+static struct tda827x_config tda827x_config = {
+	.config = 0,
+};
+
+static int ttusb2_frontend_tda10086_attach(struct dvb_usb_adapter *adap)
+{
+	if (usb_set_interface(adap->dev->udev,0,3) < 0)
+		err("set interface to alts=3 failed");
+
+	if ((adap->fe_adap[0].fe = dvb_attach(tda10086_attach, &tda10086_config, &adap->dev->i2c_adap)) == NULL) {
+		deb_info("TDA10086 attach failed\n");
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int ttusb2_ct3650_i2c_gate_ctrl(struct dvb_frontend *fe, int enable)
+{
+	struct dvb_usb_adapter *adap = fe->dvb->priv;
+
+	return adap->fe_adap[0].fe->ops.i2c_gate_ctrl(adap->fe_adap[0].fe, enable);
+}
+
+static int ttusb2_frontend_tda10023_attach(struct dvb_usb_adapter *adap)
+{
+	if (usb_set_interface(adap->dev->udev, 0, 3) < 0)
+		err("set interface to alts=3 failed");
+
+	if (adap->fe_adap[0].fe == NULL) {
+		/* FE 0 DVB-C */
+		adap->fe_adap[0].fe = dvb_attach(tda10023_attach,
+			&tda10023_config, &adap->dev->i2c_adap, 0x48);
+
+		if (adap->fe_adap[0].fe == NULL) {
+			deb_info("TDA10023 attach failed\n");
+			return -ENODEV;
+		}
+		tt3650_ci_init(adap);
+	} else {
+		adap->fe_adap[1].fe = dvb_attach(tda10048_attach,
+			&tda10048_config, &adap->dev->i2c_adap);
+
+		if (adap->fe_adap[1].fe == NULL) {
+			deb_info("TDA10048 attach failed\n");
+			return -ENODEV;
+		}
+
+		/* tuner is behind TDA10023 I2C-gate */
+		adap->fe_adap[1].fe->ops.i2c_gate_ctrl = ttusb2_ct3650_i2c_gate_ctrl;
+
+	}
+
+	return 0;
+}
+
+static int ttusb2_tuner_tda827x_attach(struct dvb_usb_adapter *adap)
+{
+	struct dvb_frontend *fe;
+
+	/* MFE: select correct FE to attach tuner since that's called twice */
+	if (adap->fe_adap[1].fe == NULL)
+		fe = adap->fe_adap[0].fe;
+	else
+		fe = adap->fe_adap[1].fe;
+
+	/* attach tuner */
+	if (dvb_attach(tda827x_attach, fe, 0x61, &adap->dev->i2c_adap, &tda827x_config) == NULL) {
+		printk(KERN_ERR "%s: No tda827x found!\n", __func__);
+		return -ENODEV;
+	}
+	return 0;
+}
+
+static int ttusb2_tuner_tda826x_attach(struct dvb_usb_adapter *adap)
+{
+	if (dvb_attach(tda826x_attach, adap->fe_adap[0].fe, 0x60, &adap->dev->i2c_adap, 0) == NULL) {
+		deb_info("TDA8263 attach failed\n");
+		return -ENODEV;
+	}
+
+	if (dvb_attach(lnbp21_attach, adap->fe_adap[0].fe, &adap->dev->i2c_adap, 0, 0) == NULL) {
+		deb_info("LNBP21 attach failed\n");
+		return -ENODEV;
+	}
+	return 0;
+}
+
+/* DVB USB Driver stuff */
+static struct dvb_usb_device_properties ttusb2_properties;
+static struct dvb_usb_device_properties ttusb2_properties_s2400;
+static struct dvb_usb_device_properties ttusb2_properties_ct3650;
+
+static void ttusb2_usb_disconnect(struct usb_interface *intf)
+{
+	struct dvb_usb_device *d = usb_get_intfdata(intf);
+
+	tt3650_ci_uninit(d);
+	dvb_usb_device_exit(intf);
+}
+
+static int ttusb2_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	if (0 == dvb_usb_device_init(intf, &ttusb2_properties,
+				     THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, &ttusb2_properties_s2400,
+				     THIS_MODULE, NULL, adapter_nr) ||
+	    0 == dvb_usb_device_init(intf, &ttusb2_properties_ct3650,
+				     THIS_MODULE, NULL, adapter_nr))
+		return 0;
+	return -ENODEV;
+}
+
+static struct usb_device_id ttusb2_table [] = {
+	{ USB_DEVICE(USB_VID_PINNACLE, USB_PID_PCTV_400E) },
+	{ USB_DEVICE(USB_VID_PINNACLE, USB_PID_PCTV_450E) },
+	{ USB_DEVICE(USB_VID_TECHNOTREND,
+		USB_PID_TECHNOTREND_CONNECT_S2400) },
+	{ USB_DEVICE(USB_VID_TECHNOTREND,
+		USB_PID_TECHNOTREND_CONNECT_CT3650) },
+	{ USB_DEVICE(USB_VID_TECHNOTREND,
+		USB_PID_TECHNOTREND_CONNECT_S2400_8KEEPROM) },
+	{}		/* Terminating entry */
+};
+MODULE_DEVICE_TABLE (usb, ttusb2_table);
+
+static struct dvb_usb_device_properties ttusb2_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl = CYPRESS_FX2,
+	.firmware = "dvb-usb-pctv-400e-01.fw",
+
+	.size_of_priv = sizeof(struct ttusb2_state),
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.streaming_ctrl   = NULL, // ttusb2_streaming_ctrl,
+
+			.frontend_attach  = ttusb2_frontend_tda10086_attach,
+			.tuner_attach     = ttusb2_tuner_tda826x_attach,
+
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_ISOC,
+				.count = 5,
+				.endpoint = 0x02,
+				.u = {
+					.isoc = {
+						.framesperurb = 4,
+						.framesize = 940,
+						.interval = 1,
+					}
+				}
+			}
+		}},
+		}
+	},
+
+	.power_ctrl       = ttusb2_power_ctrl,
+	.identify_state   = ttusb2_identify_state,
+
+	.i2c_algo         = &ttusb2_i2c_algo,
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.num_device_descs = 2,
+	.devices = {
+		{   "Pinnacle 400e DVB-S USB2.0",
+			{ &ttusb2_table[0], NULL },
+			{ NULL },
+		},
+		{   "Pinnacle 450e DVB-S USB2.0",
+			{ &ttusb2_table[1], NULL },
+			{ NULL },
+		},
+	}
+};
+
+static struct dvb_usb_device_properties ttusb2_properties_s2400 = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl = CYPRESS_FX2,
+	.firmware = "dvb-usb-tt-s2400-01.fw",
+
+	.size_of_priv = sizeof(struct ttusb2_state),
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.streaming_ctrl   = NULL,
+
+			.frontend_attach  = ttusb2_frontend_tda10086_attach,
+			.tuner_attach     = ttusb2_tuner_tda826x_attach,
+
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_ISOC,
+				.count = 5,
+				.endpoint = 0x02,
+				.u = {
+					.isoc = {
+						.framesperurb = 4,
+						.framesize = 940,
+						.interval = 1,
+					}
+				}
+			}
+		}},
+		}
+	},
+
+	.power_ctrl       = ttusb2_power_ctrl,
+	.identify_state   = ttusb2_identify_state,
+
+	.i2c_algo         = &ttusb2_i2c_algo,
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.num_device_descs = 2,
+	.devices = {
+		{   "Technotrend TT-connect S-2400",
+			{ &ttusb2_table[2], NULL },
+			{ NULL },
+		},
+		{   "Technotrend TT-connect S-2400 (8kB EEPROM)",
+			{ &ttusb2_table[4], NULL },
+			{ NULL },
+		},
+	}
+};
+
+static struct dvb_usb_device_properties ttusb2_properties_ct3650 = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl = CYPRESS_FX2,
+
+	.size_of_priv = sizeof(struct ttusb2_state),
+
+	.rc.core = {
+		.rc_interval      = 150, /* Less than IR_KEYPRESS_TIMEOUT */
+		.rc_codes         = RC_MAP_TT_1500,
+		.rc_query         = tt3650_rc_query,
+		.allowed_protos   = RC_PROTO_BIT_RC5,
+	},
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 2,
+		.fe = {{
+			.streaming_ctrl   = NULL,
+
+			.frontend_attach  = ttusb2_frontend_tda10023_attach,
+			.tuner_attach = ttusb2_tuner_tda827x_attach,
+
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_ISOC,
+				.count = 5,
+				.endpoint = 0x02,
+				.u = {
+					.isoc = {
+						.framesperurb = 4,
+						.framesize = 940,
+						.interval = 1,
+					}
+				}
+			}
+		}, {
+			.streaming_ctrl   = NULL,
+
+			.frontend_attach  = ttusb2_frontend_tda10023_attach,
+			.tuner_attach = ttusb2_tuner_tda827x_attach,
+
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_ISOC,
+				.count = 5,
+				.endpoint = 0x02,
+				.u = {
+					.isoc = {
+						.framesperurb = 4,
+						.framesize = 940,
+						.interval = 1,
+					}
+				}
+			}
+		}},
+		},
+	},
+
+	.power_ctrl       = ttusb2_power_ctrl,
+	.identify_state   = ttusb2_identify_state,
+
+	.i2c_algo         = &ttusb2_i2c_algo,
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.num_device_descs = 1,
+	.devices = {
+		{   "Technotrend TT-connect CT-3650",
+			.warm_ids = { &ttusb2_table[3], NULL },
+		},
+	}
+};
+
+static struct usb_driver ttusb2_driver = {
+	.name		= "dvb_usb_ttusb2",
+	.probe		= ttusb2_probe,
+	.disconnect	= ttusb2_usb_disconnect,
+	.id_table	= ttusb2_table,
+};
+
+module_usb_driver(ttusb2_driver);
+
+MODULE_AUTHOR("Patrick Boettcher <patrick.boettcher@posteo.de>");
+MODULE_DESCRIPTION("Driver for Pinnacle PCTV 400e DVB-S USB2.0");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/ttusb2.h b/drivers/media/usb/dvb-usb/ttusb2.h
new file mode 100644
index 0000000..8b6525e
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/ttusb2.h
@@ -0,0 +1,70 @@
+/* DVB USB compliant linux driver for Technotrend DVB USB boxes and clones
+ * (e.g. Pinnacle 400e DVB-S USB2.0).
+ *
+ * Copyright (c) 2002 Holger Waechtler <holger@convergence.de>
+ * Copyright (c) 2003 Felix Domke <tmbinc@elitedvb.net>
+ * Copyright (C) 2005-6 Patrick Boettcher <pb@linuxtv.de>
+ *
+ *	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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#ifndef _DVB_USB_TTUSB2_H_
+#define _DVB_USB_TTUSB2_H_
+
+/* TTUSB protocol
+ *
+ * always to messages (out/in)
+ * out message:
+ * 0xaa <id> <cmdbyte> <datalen> <data...>
+ *
+ * in message (complete block is always 0x40 bytes long)
+ * 0x55 <id> <cmdbyte> <datalen> <data...>
+ *
+ * id is incremented for each transaction
+ */
+
+#define CMD_DSP_DOWNLOAD    0x13
+/* out data: <byte>[28]
+ * last block must be empty */
+
+#define CMD_DSP_BOOT        0x14
+/* out data: nothing */
+
+#define CMD_POWER           0x15
+/* out data: <on=1/off=0> */
+
+#define CMD_LNB             0x16
+/* out data: <power=1> <18V=0,13V=1> <tone> <??=1> <??=1> */
+
+#define CMD_GET_VERSION     0x17
+/* in  data: <version_byte>[5] */
+
+#define CMD_DISEQC          0x18
+/* out data: <master=0xff/burst=??> <cmdlen> <cmdbytes>[cmdlen] */
+
+#define CMD_PID_ENABLE      0x22
+/* out data: <index> <type: ts=1/sec=2> <pid msb> <pid lsb> */
+
+#define CMD_PID_DISABLE     0x23
+/* out data: <index> */
+
+#define CMD_FILTER_ENABLE   0x24
+/* out data: <index> <pid_idx> <filter>[12] <mask>[12] */
+
+#define CMD_FILTER_DISABLE  0x25
+/* out data: <index> */
+
+#define CMD_GET_DSP_VERSION 0x26
+/* in  data: <version_byte>[28] */
+
+#define CMD_I2C_XFER        0x31
+/* out data: <addr << 1> <sndlen> <rcvlen> <data>[sndlen]
+ * in  data: <addr << 1> <sndlen> <rcvlen> <data>[rcvlen] */
+
+#define CMD_I2C_BITRATE     0x32
+/* out data: <default=0> */
+
+#endif
diff --git a/drivers/media/usb/dvb-usb/umt-010.c b/drivers/media/usb/dvb-usb/umt-010.c
new file mode 100644
index 0000000..920bc67
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/umt-010.c
@@ -0,0 +1,151 @@
+/* DVB USB framework compliant Linux driver for the HanfTek UMT-010 USB2.0
+ * DVB-T receiver.
+ *
+ * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@posteo.de)
+ *
+ *	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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#include "dibusb.h"
+
+#include "mt352.h"
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static int umt_mt352_demod_init(struct dvb_frontend *fe)
+{
+	static u8 mt352_clock_config[] = { 0x89, 0xb8, 0x2d };
+	static u8 mt352_reset[] = { 0x50, 0x80 };
+	static u8 mt352_mclk_ratio[] = { 0x8b, 0x00 };
+	static u8 mt352_adc_ctl_1_cfg[] = { 0x8E, 0x40 };
+	static u8 mt352_agc_cfg[] = { 0x67, 0x10, 0xa0 };
+
+	static u8 mt352_sec_agc_cfg1[] = { 0x6a, 0xff };
+	static u8 mt352_sec_agc_cfg2[] = { 0x6d, 0xff };
+	static u8 mt352_sec_agc_cfg3[] = { 0x70, 0x40 };
+	static u8 mt352_sec_agc_cfg4[] = { 0x7b, 0x03 };
+	static u8 mt352_sec_agc_cfg5[] = { 0x7d, 0x0f };
+
+	static u8 mt352_acq_ctl[] = { 0x53, 0x50 };
+	static u8 mt352_input_freq_1[] = { 0x56, 0x31, 0x06 };
+
+	mt352_write(fe, mt352_clock_config, sizeof(mt352_clock_config));
+	udelay(2000);
+	mt352_write(fe, mt352_reset, sizeof(mt352_reset));
+	mt352_write(fe, mt352_mclk_ratio, sizeof(mt352_mclk_ratio));
+
+	mt352_write(fe, mt352_adc_ctl_1_cfg, sizeof(mt352_adc_ctl_1_cfg));
+	mt352_write(fe, mt352_agc_cfg, sizeof(mt352_agc_cfg));
+
+	mt352_write(fe, mt352_sec_agc_cfg1, sizeof(mt352_sec_agc_cfg1));
+	mt352_write(fe, mt352_sec_agc_cfg2, sizeof(mt352_sec_agc_cfg2));
+	mt352_write(fe, mt352_sec_agc_cfg3, sizeof(mt352_sec_agc_cfg3));
+	mt352_write(fe, mt352_sec_agc_cfg4, sizeof(mt352_sec_agc_cfg4));
+	mt352_write(fe, mt352_sec_agc_cfg5, sizeof(mt352_sec_agc_cfg5));
+
+	mt352_write(fe, mt352_acq_ctl, sizeof(mt352_acq_ctl));
+	mt352_write(fe, mt352_input_freq_1, sizeof(mt352_input_freq_1));
+
+	return 0;
+}
+
+static int umt_mt352_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	struct mt352_config umt_config;
+
+	memset(&umt_config,0,sizeof(struct mt352_config));
+	umt_config.demod_init = umt_mt352_demod_init;
+	umt_config.demod_address = 0xf;
+
+	adap->fe_adap[0].fe = dvb_attach(mt352_attach, &umt_config, &adap->dev->i2c_adap);
+
+	return 0;
+}
+
+static int umt_tuner_attach (struct dvb_usb_adapter *adap)
+{
+	dvb_attach(dvb_pll_attach, adap->fe_adap[0].fe, 0x61, NULL, DVB_PLL_TUA6034);
+	return 0;
+}
+
+/* USB Driver stuff */
+static struct dvb_usb_device_properties umt_properties;
+
+static int umt_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	if (0 == dvb_usb_device_init(intf, &umt_properties,
+				     THIS_MODULE, NULL, adapter_nr))
+		return 0;
+	return -EINVAL;
+}
+
+/* do not change the order of the ID table */
+static struct usb_device_id umt_table [] = {
+/* 00 */	{ USB_DEVICE(USB_VID_HANFTEK, USB_PID_HANFTEK_UMT_010_COLD) },
+/* 01 */	{ USB_DEVICE(USB_VID_HANFTEK, USB_PID_HANFTEK_UMT_010_WARM) },
+			{ }		/* Terminating entry */
+};
+MODULE_DEVICE_TABLE (usb, umt_table);
+
+static struct dvb_usb_device_properties umt_properties = {
+	.caps = DVB_USB_IS_AN_I2C_ADAPTER,
+
+	.usb_ctrl = CYPRESS_FX2,
+	.firmware = "dvb-usb-umt-010-02.fw",
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.streaming_ctrl   = dibusb2_0_streaming_ctrl,
+			.frontend_attach  = umt_mt352_frontend_attach,
+			.tuner_attach     = umt_tuner_attach,
+
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+				.count = MAX_NO_URBS_FOR_DATA_STREAM,
+				.endpoint = 0x06,
+				.u = {
+					.bulk = {
+						.buffersize = 512,
+					}
+				}
+			},
+		}},
+			.size_of_priv     = sizeof(struct dibusb_state),
+		}
+	},
+	.power_ctrl       = dibusb_power_ctrl,
+
+	.i2c_algo         = &dibusb_i2c_algo,
+
+	.generic_bulk_ctrl_endpoint = 0x01,
+
+	.num_device_descs = 1,
+	.devices = {
+		{	"Hanftek UMT-010 DVB-T USB2.0",
+			{ &umt_table[0], NULL },
+			{ &umt_table[1], NULL },
+		},
+	}
+};
+
+static struct usb_driver umt_driver = {
+	.name		= "dvb_usb_umt_010",
+	.probe		= umt_probe,
+	.disconnect = dvb_usb_device_exit,
+	.id_table	= umt_table,
+};
+
+module_usb_driver(umt_driver);
+
+MODULE_AUTHOR("Patrick Boettcher <patrick.boettcher@posteo.de>");
+MODULE_DESCRIPTION("Driver for HanfTek UMT 010 USB2.0 DVB-T device");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/usb-urb.c b/drivers/media/usb/dvb-usb/usb-urb.c
new file mode 100644
index 0000000..9771f09
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/usb-urb.c
@@ -0,0 +1,255 @@
+// SPDX-License-Identifier: GPL-2.0
+/* usb-urb.c is part of the DVB USB library.
+ *
+ * Copyright (C) 2004-6 Patrick Boettcher (patrick.boettcher@posteo.de)
+ * see dvb-usb-init.c for copyright information.
+ *
+ * This file keeps functions for initializing and handling the
+ * BULK and ISOC USB data transfers in a generic way.
+ * Can be used for DVB-only and also, that's the plan, for
+ * Hybrid USB devices (analog and DVB).
+ */
+#include "dvb-usb-common.h"
+
+/* URB stuff for streaming */
+static void usb_urb_complete(struct urb *urb)
+{
+	struct usb_data_stream *stream = urb->context;
+	int ptype = usb_pipetype(urb->pipe);
+	int i;
+	u8 *b;
+
+	deb_uxfer("'%s' urb completed. status: %d, length: %d/%d, pack_num: %d, errors: %d\n",
+		ptype == PIPE_ISOCHRONOUS ? "isoc" : "bulk",
+		urb->status,urb->actual_length,urb->transfer_buffer_length,
+		urb->number_of_packets,urb->error_count);
+
+	switch (urb->status) {
+		case 0:         /* success */
+		case -ETIMEDOUT:    /* NAK */
+			break;
+		case -ECONNRESET:   /* kill */
+		case -ENOENT:
+		case -ESHUTDOWN:
+			return;
+		default:        /* error */
+			deb_ts("urb completion error %d.\n", urb->status);
+			break;
+	}
+
+	b = (u8 *) urb->transfer_buffer;
+	switch (ptype) {
+		case PIPE_ISOCHRONOUS:
+			for (i = 0; i < urb->number_of_packets; i++) {
+
+				if (urb->iso_frame_desc[i].status != 0)
+					deb_ts("iso frame descriptor has an error: %d\n",urb->iso_frame_desc[i].status);
+				else if (urb->iso_frame_desc[i].actual_length > 0)
+					stream->complete(stream, b + urb->iso_frame_desc[i].offset, urb->iso_frame_desc[i].actual_length);
+
+				urb->iso_frame_desc[i].status = 0;
+				urb->iso_frame_desc[i].actual_length = 0;
+			}
+			debug_dump(b,20,deb_uxfer);
+			break;
+		case PIPE_BULK:
+			if (urb->actual_length > 0)
+				stream->complete(stream, b, urb->actual_length);
+			break;
+		default:
+			err("unknown endpoint type in completion handler.");
+			return;
+	}
+	usb_submit_urb(urb,GFP_ATOMIC);
+}
+
+int usb_urb_kill(struct usb_data_stream *stream)
+{
+	int i;
+	for (i = 0; i < stream->urbs_submitted; i++) {
+		deb_ts("killing URB no. %d.\n",i);
+
+		/* stop the URB */
+		usb_kill_urb(stream->urb_list[i]);
+	}
+	stream->urbs_submitted = 0;
+	return 0;
+}
+
+int usb_urb_submit(struct usb_data_stream *stream)
+{
+	int i,ret;
+	for (i = 0; i < stream->urbs_initialized; i++) {
+		deb_ts("submitting URB no. %d\n",i);
+		if ((ret = usb_submit_urb(stream->urb_list[i],GFP_ATOMIC))) {
+			err("could not submit URB no. %d - get them all back",i);
+			usb_urb_kill(stream);
+			return ret;
+		}
+		stream->urbs_submitted++;
+	}
+	return 0;
+}
+
+static int usb_free_stream_buffers(struct usb_data_stream *stream)
+{
+	if (stream->state & USB_STATE_URB_BUF) {
+		while (stream->buf_num) {
+			stream->buf_num--;
+			deb_mem("freeing buffer %d\n",stream->buf_num);
+			usb_free_coherent(stream->udev, stream->buf_size,
+					  stream->buf_list[stream->buf_num],
+					  stream->dma_addr[stream->buf_num]);
+		}
+	}
+
+	stream->state &= ~USB_STATE_URB_BUF;
+
+	return 0;
+}
+
+static int usb_allocate_stream_buffers(struct usb_data_stream *stream, int num, unsigned long size)
+{
+	stream->buf_num = 0;
+	stream->buf_size = size;
+
+	deb_mem("all in all I will use %lu bytes for streaming\n",num*size);
+
+	for (stream->buf_num = 0; stream->buf_num < num; stream->buf_num++) {
+		deb_mem("allocating buffer %d\n",stream->buf_num);
+		if (( stream->buf_list[stream->buf_num] =
+					usb_alloc_coherent(stream->udev, size, GFP_KERNEL,
+					&stream->dma_addr[stream->buf_num]) ) == NULL) {
+			deb_mem("not enough memory for urb-buffer allocation.\n");
+			usb_free_stream_buffers(stream);
+			return -ENOMEM;
+		}
+		deb_mem("buffer %d: %p (dma: %Lu)\n",
+			stream->buf_num,
+stream->buf_list[stream->buf_num], (long long)stream->dma_addr[stream->buf_num]);
+		memset(stream->buf_list[stream->buf_num],0,size);
+		stream->state |= USB_STATE_URB_BUF;
+	}
+	deb_mem("allocation successful\n");
+
+	return 0;
+}
+
+static int usb_bulk_urb_init(struct usb_data_stream *stream)
+{
+	int i, j;
+
+	if ((i = usb_allocate_stream_buffers(stream,stream->props.count,
+					stream->props.u.bulk.buffersize)) < 0)
+		return i;
+
+	/* allocate the URBs */
+	for (i = 0; i < stream->props.count; i++) {
+		stream->urb_list[i] = usb_alloc_urb(0, GFP_KERNEL);
+		if (!stream->urb_list[i]) {
+			deb_mem("not enough memory for urb_alloc_urb!.\n");
+			for (j = 0; j < i; j++)
+				usb_free_urb(stream->urb_list[j]);
+			return -ENOMEM;
+		}
+		usb_fill_bulk_urb( stream->urb_list[i], stream->udev,
+				usb_rcvbulkpipe(stream->udev,stream->props.endpoint),
+				stream->buf_list[i],
+				stream->props.u.bulk.buffersize,
+				usb_urb_complete, stream);
+
+		stream->urb_list[i]->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+		stream->urb_list[i]->transfer_dma = stream->dma_addr[i];
+		stream->urbs_initialized++;
+	}
+	return 0;
+}
+
+static int usb_isoc_urb_init(struct usb_data_stream *stream)
+{
+	int i,j;
+
+	if ((i = usb_allocate_stream_buffers(stream,stream->props.count,
+					stream->props.u.isoc.framesize*stream->props.u.isoc.framesperurb)) < 0)
+		return i;
+
+	/* allocate the URBs */
+	for (i = 0; i < stream->props.count; i++) {
+		struct urb *urb;
+		int frame_offset = 0;
+
+		stream->urb_list[i] = usb_alloc_urb(stream->props.u.isoc.framesperurb, GFP_KERNEL);
+		if (!stream->urb_list[i]) {
+			deb_mem("not enough memory for urb_alloc_urb!\n");
+			for (j = 0; j < i; j++)
+				usb_free_urb(stream->urb_list[j]);
+			return -ENOMEM;
+		}
+
+		urb = stream->urb_list[i];
+
+		urb->dev = stream->udev;
+		urb->context = stream;
+		urb->complete = usb_urb_complete;
+		urb->pipe = usb_rcvisocpipe(stream->udev,stream->props.endpoint);
+		urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
+		urb->interval = stream->props.u.isoc.interval;
+		urb->number_of_packets = stream->props.u.isoc.framesperurb;
+		urb->transfer_buffer_length = stream->buf_size;
+		urb->transfer_buffer = stream->buf_list[i];
+		urb->transfer_dma = stream->dma_addr[i];
+
+		for (j = 0; j < stream->props.u.isoc.framesperurb; j++) {
+			urb->iso_frame_desc[j].offset = frame_offset;
+			urb->iso_frame_desc[j].length = stream->props.u.isoc.framesize;
+			frame_offset += stream->props.u.isoc.framesize;
+		}
+
+		stream->urbs_initialized++;
+	}
+	return 0;
+}
+
+int usb_urb_init(struct usb_data_stream *stream, struct usb_data_stream_properties *props)
+{
+	if (stream == NULL || props == NULL)
+		return -EINVAL;
+
+	memcpy(&stream->props, props, sizeof(*props));
+
+	usb_clear_halt(stream->udev,usb_rcvbulkpipe(stream->udev,stream->props.endpoint));
+
+	if (stream->complete == NULL) {
+		err("there is no data callback - this doesn't make sense.");
+		return -EINVAL;
+	}
+
+	switch (stream->props.type) {
+		case USB_BULK:
+			return usb_bulk_urb_init(stream);
+		case USB_ISOC:
+			return usb_isoc_urb_init(stream);
+		default:
+			err("unknown URB-type for data transfer.");
+			return -EINVAL;
+	}
+}
+
+int usb_urb_exit(struct usb_data_stream *stream)
+{
+	int i;
+
+	usb_urb_kill(stream);
+
+	for (i = 0; i < stream->urbs_initialized; i++) {
+		if (stream->urb_list[i] != NULL) {
+			deb_mem("freeing URB no. %d.\n",i);
+			/* free the URBs */
+			usb_free_urb(stream->urb_list[i]);
+		}
+	}
+	stream->urbs_initialized = 0;
+
+	usb_free_stream_buffers(stream);
+	return 0;
+}
diff --git a/drivers/media/usb/dvb-usb/vp702x-fe.c b/drivers/media/usb/dvb-usb/vp702x-fe.c
new file mode 100644
index 0000000..9eb8114
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/vp702x-fe.c
@@ -0,0 +1,381 @@
+/* DVB frontend part of the Linux driver for the TwinhanDTV StarBox USB2.0
+ * DVB-S receiver.
+ *
+ * Copyright (C) 2005 Ralph Metzler <rjkm@metzlerbros.de>
+ *                    Metzler Brothers Systementwicklung GbR
+ *
+ * Copyright (C) 2005 Patrick Boettcher <patrick.boettcher@posteo.de>
+ *
+ * Thanks to Twinhan who kindly provided hardware and information.
+ *
+ * This file can be removed soon, after the DST-driver is rewritten to provice
+ * the frontend-controlling separately.
+ *
+ *	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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ *
+ */
+#include "vp702x.h"
+
+struct vp702x_fe_state {
+	struct dvb_frontend fe;
+	struct dvb_usb_device *d;
+
+	struct dvb_frontend_ops ops;
+
+	enum fe_sec_voltage voltage;
+	enum fe_sec_tone_mode tone_mode;
+
+	u8 lnb_buf[8];
+
+	u8 lock;
+	u8 sig;
+	u8 snr;
+
+	unsigned long next_status_check;
+	unsigned long status_check_interval;
+};
+
+static int vp702x_fe_refresh_state(struct vp702x_fe_state *st)
+{
+	struct vp702x_device_state *dst = st->d->priv;
+	u8 *buf;
+
+	if (time_after(jiffies, st->next_status_check)) {
+		mutex_lock(&dst->buf_mutex);
+		buf = dst->buf;
+
+		vp702x_usb_in_op(st->d, READ_STATUS, 0, 0, buf, 10);
+		st->lock = buf[4];
+
+		vp702x_usb_in_op(st->d, READ_TUNER_REG_REQ, 0x11, 0, buf, 1);
+		st->snr = buf[0];
+
+		vp702x_usb_in_op(st->d, READ_TUNER_REG_REQ, 0x15, 0, buf, 1);
+		st->sig = buf[0];
+
+		mutex_unlock(&dst->buf_mutex);
+		st->next_status_check = jiffies + (st->status_check_interval*HZ)/1000;
+	}
+	return 0;
+}
+
+static u8 vp702x_chksum(u8 *buf,int f, int count)
+{
+	u8 s = 0;
+	int i;
+	for (i = f; i < f+count; i++)
+		s += buf[i];
+	return ~s+1;
+}
+
+static int vp702x_fe_read_status(struct dvb_frontend *fe,
+				 enum fe_status *status)
+{
+	struct vp702x_fe_state *st = fe->demodulator_priv;
+	vp702x_fe_refresh_state(st);
+	deb_fe("%s\n",__func__);
+
+	if (st->lock == 0)
+		*status = FE_HAS_LOCK | FE_HAS_SYNC | FE_HAS_VITERBI | FE_HAS_SIGNAL | FE_HAS_CARRIER;
+	else
+		*status = 0;
+
+	if (*status & FE_HAS_LOCK)
+		st->status_check_interval = 1000;
+	else
+		st->status_check_interval = 250;
+	return 0;
+}
+
+/* not supported by this Frontend */
+static int vp702x_fe_read_ber(struct dvb_frontend* fe, u32 *ber)
+{
+	struct vp702x_fe_state *st = fe->demodulator_priv;
+	vp702x_fe_refresh_state(st);
+	*ber = 0;
+	return 0;
+}
+
+/* not supported by this Frontend */
+static int vp702x_fe_read_unc_blocks(struct dvb_frontend* fe, u32 *unc)
+{
+	struct vp702x_fe_state *st = fe->demodulator_priv;
+	vp702x_fe_refresh_state(st);
+	*unc = 0;
+	return 0;
+}
+
+static int vp702x_fe_read_signal_strength(struct dvb_frontend* fe, u16 *strength)
+{
+	struct vp702x_fe_state *st = fe->demodulator_priv;
+	vp702x_fe_refresh_state(st);
+
+	*strength = (st->sig << 8) | st->sig;
+	return 0;
+}
+
+static int vp702x_fe_read_snr(struct dvb_frontend* fe, u16 *snr)
+{
+	u8 _snr;
+	struct vp702x_fe_state *st = fe->demodulator_priv;
+	vp702x_fe_refresh_state(st);
+
+	_snr = (st->snr & 0x1f) * 0xff / 0x1f;
+	*snr = (_snr << 8) | _snr;
+	return 0;
+}
+
+static int vp702x_fe_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings *tune)
+{
+	deb_fe("%s\n",__func__);
+	tune->min_delay_ms = 2000;
+	return 0;
+}
+
+static int vp702x_fe_set_frontend(struct dvb_frontend *fe)
+{
+	struct dtv_frontend_properties *fep = &fe->dtv_property_cache;
+	struct vp702x_fe_state *st = fe->demodulator_priv;
+	struct vp702x_device_state *dst = st->d->priv;
+	u32 freq = fep->frequency/1000;
+	/*CalFrequency*/
+/*	u16 frequencyRef[16] = { 2, 4, 8, 16, 32, 64, 128, 256, 24, 5, 10, 20, 40, 80, 160, 320 }; */
+	u64 sr;
+	u8 *cmd;
+
+	mutex_lock(&dst->buf_mutex);
+
+	cmd = dst->buf;
+	memset(cmd, 0, 10);
+
+	cmd[0] = (freq >> 8) & 0x7f;
+	cmd[1] =  freq       & 0xff;
+	cmd[2] = 1; /* divrate == 4 -> frequencyRef[1] -> 1 here */
+
+	sr = (u64) (fep->symbol_rate/1000) << 20;
+	do_div(sr,88000);
+	cmd[3] = (sr >> 12) & 0xff;
+	cmd[4] = (sr >> 4)  & 0xff;
+	cmd[5] = (sr << 4)  & 0xf0;
+
+	deb_fe("setting frontend to: %u -> %u (%x) LNB-based GHz, symbolrate: %d -> %lu (%lx)\n",
+			fep->frequency, freq, freq, fep->symbol_rate,
+			(unsigned long) sr, (unsigned long) sr);
+
+/*	if (fep->inversion == INVERSION_ON)
+		cmd[6] |= 0x80; */
+
+	if (st->voltage == SEC_VOLTAGE_18)
+		cmd[6] |= 0x40;
+
+/*	if (fep->symbol_rate > 8000000)
+		cmd[6] |= 0x20;
+
+	if (fep->frequency < 1531000)
+		cmd[6] |= 0x04;
+
+	if (st->tone_mode == SEC_TONE_ON)
+		cmd[6] |= 0x01;*/
+
+	cmd[7] = vp702x_chksum(cmd,0,7);
+
+	st->status_check_interval = 250;
+	st->next_status_check = jiffies;
+
+	vp702x_usb_inout_op(st->d, cmd, 8, cmd, 10, 100);
+
+	if (cmd[2] == 0 && cmd[3] == 0)
+		deb_fe("tuning failed.\n");
+	else
+		deb_fe("tuning succeeded.\n");
+
+	mutex_unlock(&dst->buf_mutex);
+
+	return 0;
+}
+
+static int vp702x_fe_init(struct dvb_frontend *fe)
+{
+	struct vp702x_fe_state *st = fe->demodulator_priv;
+	deb_fe("%s\n",__func__);
+	vp702x_usb_in_op(st->d, RESET_TUNER, 0, 0, NULL, 0);
+	return 0;
+}
+
+static int vp702x_fe_sleep(struct dvb_frontend *fe)
+{
+	deb_fe("%s\n",__func__);
+	return 0;
+}
+
+static int vp702x_fe_send_diseqc_msg (struct dvb_frontend* fe,
+				    struct dvb_diseqc_master_cmd *m)
+{
+	u8 *cmd;
+	struct vp702x_fe_state *st = fe->demodulator_priv;
+	struct vp702x_device_state *dst = st->d->priv;
+
+	deb_fe("%s\n",__func__);
+
+	if (m->msg_len > 4)
+		return -EINVAL;
+
+	mutex_lock(&dst->buf_mutex);
+
+	cmd = dst->buf;
+	cmd[1] = SET_DISEQC_CMD;
+	cmd[2] = m->msg_len;
+	memcpy(&cmd[3], m->msg, m->msg_len);
+	cmd[7] = vp702x_chksum(cmd, 0, 7);
+
+	vp702x_usb_inout_op(st->d, cmd, 8, cmd, 10, 100);
+
+	if (cmd[2] == 0 && cmd[3] == 0)
+		deb_fe("diseqc cmd failed.\n");
+	else
+		deb_fe("diseqc cmd succeeded.\n");
+
+	mutex_unlock(&dst->buf_mutex);
+
+	return 0;
+}
+
+static int vp702x_fe_send_diseqc_burst(struct dvb_frontend *fe,
+				       enum fe_sec_mini_cmd burst)
+{
+	deb_fe("%s\n",__func__);
+	return 0;
+}
+
+static int vp702x_fe_set_tone(struct dvb_frontend *fe,
+			      enum fe_sec_tone_mode tone)
+{
+	struct vp702x_fe_state *st = fe->demodulator_priv;
+	struct vp702x_device_state *dst = st->d->priv;
+	u8 *buf;
+
+	deb_fe("%s\n",__func__);
+
+	st->tone_mode = tone;
+
+	if (tone == SEC_TONE_ON)
+		st->lnb_buf[2] = 0x02;
+	else
+		st->lnb_buf[2] = 0x00;
+
+	st->lnb_buf[7] = vp702x_chksum(st->lnb_buf, 0, 7);
+
+	mutex_lock(&dst->buf_mutex);
+
+	buf = dst->buf;
+	memcpy(buf, st->lnb_buf, 8);
+
+	vp702x_usb_inout_op(st->d, buf, 8, buf, 10, 100);
+	if (buf[2] == 0 && buf[3] == 0)
+		deb_fe("set_tone cmd failed.\n");
+	else
+		deb_fe("set_tone cmd succeeded.\n");
+
+	mutex_unlock(&dst->buf_mutex);
+
+	return 0;
+}
+
+static int vp702x_fe_set_voltage(struct dvb_frontend *fe,
+				 enum fe_sec_voltage voltage)
+{
+	struct vp702x_fe_state *st = fe->demodulator_priv;
+	struct vp702x_device_state *dst = st->d->priv;
+	u8 *buf;
+	deb_fe("%s\n",__func__);
+
+	st->voltage = voltage;
+
+	if (voltage != SEC_VOLTAGE_OFF)
+		st->lnb_buf[4] = 0x01;
+	else
+		st->lnb_buf[4] = 0x00;
+
+	st->lnb_buf[7] = vp702x_chksum(st->lnb_buf, 0, 7);
+
+	mutex_lock(&dst->buf_mutex);
+
+	buf = dst->buf;
+	memcpy(buf, st->lnb_buf, 8);
+
+	vp702x_usb_inout_op(st->d, buf, 8, buf, 10, 100);
+	if (buf[2] == 0 && buf[3] == 0)
+		deb_fe("set_voltage cmd failed.\n");
+	else
+		deb_fe("set_voltage cmd succeeded.\n");
+
+	mutex_unlock(&dst->buf_mutex);
+	return 0;
+}
+
+static void vp702x_fe_release(struct dvb_frontend* fe)
+{
+	struct vp702x_fe_state *st = fe->demodulator_priv;
+	kfree(st);
+}
+
+static const struct dvb_frontend_ops vp702x_fe_ops;
+
+struct dvb_frontend * vp702x_fe_attach(struct dvb_usb_device *d)
+{
+	struct vp702x_fe_state *s = kzalloc(sizeof(struct vp702x_fe_state), GFP_KERNEL);
+	if (s == NULL)
+		goto error;
+
+	s->d = d;
+
+	memcpy(&s->fe.ops,&vp702x_fe_ops,sizeof(struct dvb_frontend_ops));
+	s->fe.demodulator_priv = s;
+
+	s->lnb_buf[1] = SET_LNB_POWER;
+	s->lnb_buf[3] = 0xff; /* 0=tone burst, 2=data burst, ff=off */
+
+	return &s->fe;
+error:
+	return NULL;
+}
+
+
+static const struct dvb_frontend_ops vp702x_fe_ops = {
+	.delsys = { SYS_DVBS },
+	.info = {
+		.name           = "Twinhan DST-like frontend (VP7021/VP7020) DVB-S",
+		.frequency_min_hz       =  950 * MHz,
+		.frequency_max_hz       = 2150 * MHz,
+		.frequency_stepsize_hz  =    1 * MHz,
+		.symbol_rate_min     = 1000000,
+		.symbol_rate_max     = 45000000,
+		.symbol_rate_tolerance = 500,  /* ppm */
+		.caps = FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 |
+		FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 |
+		FE_CAN_QPSK |
+		FE_CAN_FEC_AUTO
+	},
+	.release = vp702x_fe_release,
+
+	.init  = vp702x_fe_init,
+	.sleep = vp702x_fe_sleep,
+
+	.set_frontend = vp702x_fe_set_frontend,
+	.get_tune_settings = vp702x_fe_get_tune_settings,
+
+	.read_status = vp702x_fe_read_status,
+	.read_ber = vp702x_fe_read_ber,
+	.read_signal_strength = vp702x_fe_read_signal_strength,
+	.read_snr = vp702x_fe_read_snr,
+	.read_ucblocks = vp702x_fe_read_unc_blocks,
+
+	.diseqc_send_master_cmd = vp702x_fe_send_diseqc_msg,
+	.diseqc_send_burst = vp702x_fe_send_diseqc_burst,
+	.set_tone = vp702x_fe_set_tone,
+	.set_voltage = vp702x_fe_set_voltage,
+};
diff --git a/drivers/media/usb/dvb-usb/vp702x.c b/drivers/media/usb/dvb-usb/vp702x.c
new file mode 100644
index 0000000..c3529ea
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/vp702x.c
@@ -0,0 +1,445 @@
+/* DVB USB compliant Linux driver for the TwinhanDTV StarBox USB2.0 DVB-S
+ * receiver.
+ *
+ * Copyright (C) 2005 Ralph Metzler <rjkm@metzlerbros.de>
+ *                    Metzler Brothers Systementwicklung GbR
+ *
+ * Copyright (C) 2005 Patrick Boettcher <patrick.boettcher@posteo.de>
+ *
+ * Thanks to Twinhan who kindly provided hardware and information.
+ *
+ *	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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#include "vp702x.h"
+#include <linux/mutex.h>
+
+/* debug */
+int dvb_usb_vp702x_debug;
+module_param_named(debug,dvb_usb_vp702x_debug, int, 0644);
+MODULE_PARM_DESC(debug, "set debugging level (1=info,xfer=2,rc=4 (or-able))." DVB_USB_DEBUG_STATUS);
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+struct vp702x_adapter_state {
+	int pid_filter_count;
+	int pid_filter_can_bypass;
+	u8  pid_filter_state;
+};
+
+static int vp702x_usb_in_op_unlocked(struct dvb_usb_device *d, u8 req,
+				     u16 value, u16 index, u8 *b, int blen)
+{
+	int ret;
+
+	ret = usb_control_msg(d->udev,
+		usb_rcvctrlpipe(d->udev, 0),
+		req,
+		USB_TYPE_VENDOR | USB_DIR_IN,
+		value, index, b, blen,
+		2000);
+
+	if (ret < 0) {
+		warn("usb in operation failed. (%d)", ret);
+		ret = -EIO;
+	} else
+		ret = 0;
+
+
+	deb_xfer("in: req. %02x, val: %04x, ind: %04x, buffer: ",req,value,index);
+	debug_dump(b,blen,deb_xfer);
+
+	return ret;
+}
+
+int vp702x_usb_in_op(struct dvb_usb_device *d, u8 req, u16 value,
+		     u16 index, u8 *b, int blen)
+{
+	int ret;
+
+	mutex_lock(&d->usb_mutex);
+	ret = vp702x_usb_in_op_unlocked(d, req, value, index, b, blen);
+	mutex_unlock(&d->usb_mutex);
+
+	return ret;
+}
+
+static int vp702x_usb_out_op_unlocked(struct dvb_usb_device *d, u8 req,
+				      u16 value, u16 index, u8 *b, int blen)
+{
+	int ret;
+	deb_xfer("out: req. %02x, val: %04x, ind: %04x, buffer: ",req,value,index);
+	debug_dump(b,blen,deb_xfer);
+
+	if ((ret = usb_control_msg(d->udev,
+			usb_sndctrlpipe(d->udev,0),
+			req,
+			USB_TYPE_VENDOR | USB_DIR_OUT,
+			value,index,b,blen,
+			2000)) != blen) {
+		warn("usb out operation failed. (%d)",ret);
+		return -EIO;
+	} else
+		return 0;
+}
+
+static int vp702x_usb_out_op(struct dvb_usb_device *d, u8 req, u16 value,
+			     u16 index, u8 *b, int blen)
+{
+	int ret;
+
+	mutex_lock(&d->usb_mutex);
+	ret = vp702x_usb_out_op_unlocked(d, req, value, index, b, blen);
+	mutex_unlock(&d->usb_mutex);
+
+	return ret;
+}
+
+int vp702x_usb_inout_op(struct dvb_usb_device *d, u8 *o, int olen, u8 *i, int ilen, int msec)
+{
+	int ret;
+
+	if ((ret = mutex_lock_interruptible(&d->usb_mutex)))
+		return ret;
+
+	ret = vp702x_usb_out_op_unlocked(d, REQUEST_OUT, 0, 0, o, olen);
+	msleep(msec);
+	ret = vp702x_usb_in_op_unlocked(d, REQUEST_IN, 0, 0, i, ilen);
+
+	mutex_unlock(&d->usb_mutex);
+	return ret;
+}
+
+static int vp702x_usb_inout_cmd(struct dvb_usb_device *d, u8 cmd, u8 *o,
+				int olen, u8 *i, int ilen, int msec)
+{
+	struct vp702x_device_state *st = d->priv;
+	int ret = 0;
+	u8 *buf;
+	int buflen = max(olen + 2, ilen + 1);
+
+	ret = mutex_lock_interruptible(&st->buf_mutex);
+	if (ret < 0)
+		return ret;
+
+	if (buflen > st->buf_len) {
+		buf = kmalloc(buflen, GFP_KERNEL);
+		if (!buf) {
+			mutex_unlock(&st->buf_mutex);
+			return -ENOMEM;
+		}
+		info("successfully reallocated a bigger buffer");
+		kfree(st->buf);
+		st->buf = buf;
+		st->buf_len = buflen;
+	} else {
+		buf = st->buf;
+	}
+
+	buf[0] = 0x00;
+	buf[1] = cmd;
+	memcpy(&buf[2], o, olen);
+
+	ret = vp702x_usb_inout_op(d, buf, olen+2, buf, ilen+1, msec);
+
+	if (ret == 0)
+		memcpy(i, &buf[1], ilen);
+	mutex_unlock(&st->buf_mutex);
+
+	return ret;
+}
+
+static int vp702x_set_pld_mode(struct dvb_usb_adapter *adap, u8 bypass)
+{
+	int ret;
+	struct vp702x_device_state *st = adap->dev->priv;
+	u8 *buf;
+
+	mutex_lock(&st->buf_mutex);
+
+	buf = st->buf;
+	memset(buf, 0, 16);
+
+	ret = vp702x_usb_in_op(adap->dev, 0xe0, (bypass << 8) | 0x0e,
+			0, buf, 16);
+	mutex_unlock(&st->buf_mutex);
+	return ret;
+}
+
+static int vp702x_set_pld_state(struct dvb_usb_adapter *adap, u8 state)
+{
+	int ret;
+	struct vp702x_device_state *st = adap->dev->priv;
+	u8 *buf;
+
+	mutex_lock(&st->buf_mutex);
+
+	buf = st->buf;
+	memset(buf, 0, 16);
+	ret = vp702x_usb_in_op(adap->dev, 0xe0, (state << 8) | 0x0f,
+			0, buf, 16);
+
+	mutex_unlock(&st->buf_mutex);
+
+	return ret;
+}
+
+static int vp702x_set_pid(struct dvb_usb_adapter *adap, u16 pid, u8 id, int onoff)
+{
+	struct vp702x_adapter_state *st = adap->priv;
+	struct vp702x_device_state *dst = adap->dev->priv;
+	u8 *buf;
+
+	if (onoff)
+		st->pid_filter_state |=  (1 << id);
+	else {
+		st->pid_filter_state &= ~(1 << id);
+		pid = 0xffff;
+	}
+
+	id = 0x10 + id*2;
+
+	vp702x_set_pld_state(adap, st->pid_filter_state);
+
+	mutex_lock(&dst->buf_mutex);
+
+	buf = dst->buf;
+	memset(buf, 0, 16);
+	vp702x_usb_in_op(adap->dev, 0xe0, (((pid >> 8) & 0xff) << 8) | (id), 0, buf, 16);
+	vp702x_usb_in_op(adap->dev, 0xe0, (((pid     ) & 0xff) << 8) | (id+1), 0, buf, 16);
+
+	mutex_unlock(&dst->buf_mutex);
+
+	return 0;
+}
+
+
+static int vp702x_init_pid_filter(struct dvb_usb_adapter *adap)
+{
+	struct vp702x_adapter_state *st = adap->priv;
+	struct vp702x_device_state *dst = adap->dev->priv;
+	int i;
+	u8 *b;
+
+	st->pid_filter_count = 8;
+	st->pid_filter_can_bypass = 1;
+	st->pid_filter_state = 0x00;
+
+	vp702x_set_pld_mode(adap, 1); /* bypass */
+
+	for (i = 0; i < st->pid_filter_count; i++)
+		vp702x_set_pid(adap, 0xffff, i, 1);
+
+	mutex_lock(&dst->buf_mutex);
+	b = dst->buf;
+	memset(b, 0, 10);
+	vp702x_usb_in_op(adap->dev, 0xb5, 3, 0, b, 10);
+	vp702x_usb_in_op(adap->dev, 0xb5, 0, 0, b, 10);
+	vp702x_usb_in_op(adap->dev, 0xb5, 1, 0, b, 10);
+	mutex_unlock(&dst->buf_mutex);
+	/*vp702x_set_pld_mode(d, 0); // filter */
+
+	return 0;
+}
+
+static int vp702x_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff)
+{
+	return 0;
+}
+
+/* keys for the enclosed remote control */
+static struct rc_map_table rc_map_vp702x_table[] = {
+	{ 0x0001, KEY_1 },
+	{ 0x0002, KEY_2 },
+};
+
+/* remote control stuff (does not work with my box) */
+static int vp702x_rc_query(struct dvb_usb_device *d, u32 *event, int *state)
+{
+/* remove the following return to enabled remote querying */
+#if 0
+	u8 *key;
+	int i;
+
+	key = kmalloc(10, GFP_KERNEL);
+	if (!key)
+		return -ENOMEM;
+
+	vp702x_usb_in_op(d,READ_REMOTE_REQ,0,0,key,10);
+
+	deb_rc("remote query key: %x %d\n",key[1],key[1]);
+
+	if (key[1] == 0x44) {
+		*state = REMOTE_NO_KEY_PRESSED;
+		kfree(key);
+		return 0;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(rc_map_vp702x_table); i++)
+		if (rc5_custom(&rc_map_vp702x_table[i]) == key[1]) {
+			*state = REMOTE_KEY_PRESSED;
+			*event = rc_map_vp702x_table[i].keycode;
+			break;
+		}
+	kfree(key);
+#endif
+
+	return 0;
+}
+
+
+static int vp702x_read_mac_addr(struct dvb_usb_device *d,u8 mac[6])
+{
+	u8 i, *buf;
+	struct vp702x_device_state *st = d->priv;
+
+	mutex_lock(&st->buf_mutex);
+	buf = st->buf;
+	for (i = 6; i < 12; i++)
+		vp702x_usb_in_op(d, READ_EEPROM_REQ, i, 1, &buf[i - 6], 1);
+
+	memcpy(mac, buf, 6);
+	mutex_unlock(&st->buf_mutex);
+	return 0;
+}
+
+static int vp702x_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	u8 buf[10] = { 0 };
+
+	vp702x_usb_out_op(adap->dev, SET_TUNER_POWER_REQ, 0, 7, NULL, 0);
+
+	if (vp702x_usb_inout_cmd(adap->dev, GET_SYSTEM_STRING, NULL, 0,
+				   buf, 10, 10))
+		return -EIO;
+
+	buf[9] = '\0';
+	info("system string: %s",&buf[1]);
+
+	vp702x_init_pid_filter(adap);
+
+	adap->fe_adap[0].fe = vp702x_fe_attach(adap->dev);
+	vp702x_usb_out_op(adap->dev, SET_TUNER_POWER_REQ, 1, 7, NULL, 0);
+
+	return 0;
+}
+
+static struct dvb_usb_device_properties vp702x_properties;
+
+static int vp702x_usb_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	struct dvb_usb_device *d;
+	struct vp702x_device_state *st;
+	int ret;
+
+	ret = dvb_usb_device_init(intf, &vp702x_properties,
+				   THIS_MODULE, &d, adapter_nr);
+	if (ret)
+		goto out;
+
+	st = d->priv;
+	st->buf_len = 16;
+	st->buf = kmalloc(st->buf_len, GFP_KERNEL);
+	if (!st->buf) {
+		ret = -ENOMEM;
+		dvb_usb_device_exit(intf);
+		goto out;
+	}
+	mutex_init(&st->buf_mutex);
+
+out:
+	return ret;
+
+}
+
+static void vp702x_usb_disconnect(struct usb_interface *intf)
+{
+	struct dvb_usb_device *d = usb_get_intfdata(intf);
+	struct vp702x_device_state *st = d->priv;
+	mutex_lock(&st->buf_mutex);
+	kfree(st->buf);
+	mutex_unlock(&st->buf_mutex);
+	dvb_usb_device_exit(intf);
+}
+
+static struct usb_device_id vp702x_usb_table [] = {
+	    { USB_DEVICE(USB_VID_VISIONPLUS, USB_PID_TWINHAN_VP7021_COLD) },
+//	    { USB_DEVICE(USB_VID_VISIONPLUS, USB_PID_TWINHAN_VP7020_COLD) },
+//	    { USB_DEVICE(USB_VID_VISIONPLUS, USB_PID_TWINHAN_VP7020_WARM) },
+	    { 0 },
+};
+MODULE_DEVICE_TABLE(usb, vp702x_usb_table);
+
+static struct dvb_usb_device_properties vp702x_properties = {
+	.usb_ctrl = CYPRESS_FX2,
+	.firmware            = "dvb-usb-vp702x-02.fw",
+	.no_reconnect        = 1,
+
+	.size_of_priv     = sizeof(struct vp702x_device_state),
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.caps             = DVB_USB_ADAP_RECEIVES_204_BYTE_TS,
+
+			.streaming_ctrl   = vp702x_streaming_ctrl,
+			.frontend_attach  = vp702x_frontend_attach,
+
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+				.count = 10,
+				.endpoint = 0x02,
+				.u = {
+					.bulk = {
+						.buffersize = 4096,
+					}
+				}
+			},
+		}},
+			.size_of_priv     = sizeof(struct vp702x_adapter_state),
+		}
+	},
+	.read_mac_address = vp702x_read_mac_addr,
+
+	.rc.legacy = {
+		.rc_map_table       = rc_map_vp702x_table,
+		.rc_map_size  = ARRAY_SIZE(rc_map_vp702x_table),
+		.rc_interval      = 400,
+		.rc_query         = vp702x_rc_query,
+	},
+
+	.num_device_descs = 1,
+	.devices = {
+		{ .name = "TwinhanDTV StarBox DVB-S USB2.0 (VP7021)",
+		  .cold_ids = { &vp702x_usb_table[0], NULL },
+		  .warm_ids = { NULL },
+		},
+/*		{ .name = "TwinhanDTV StarBox DVB-S USB2.0 (VP7020)",
+		  .cold_ids = { &vp702x_usb_table[2], NULL },
+		  .warm_ids = { &vp702x_usb_table[3], NULL },
+		},
+*/		{ NULL },
+	}
+};
+
+/* usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver vp702x_usb_driver = {
+	.name		= "dvb_usb_vp702x",
+	.probe		= vp702x_usb_probe,
+	.disconnect	= vp702x_usb_disconnect,
+	.id_table	= vp702x_usb_table,
+};
+
+module_usb_driver(vp702x_usb_driver);
+
+MODULE_AUTHOR("Patrick Boettcher <patrick.boettcher@posteo.de>");
+MODULE_DESCRIPTION("Driver for Twinhan StarBox DVB-S USB2.0 and clones");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/vp702x.h b/drivers/media/usb/dvb-usb/vp702x.h
new file mode 100644
index 0000000..18ad7ce
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/vp702x.h
@@ -0,0 +1,114 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _DVB_USB_VP7021_H_
+#define _DVB_USB_VP7021_H_
+
+#define DVB_USB_LOG_PREFIX "vp702x"
+#include "dvb-usb.h"
+
+extern int dvb_usb_vp702x_debug;
+#define deb_info(args...) dprintk(dvb_usb_vp702x_debug,0x01,args)
+#define deb_xfer(args...) dprintk(dvb_usb_vp702x_debug,0x02,args)
+#define deb_rc(args...)   dprintk(dvb_usb_vp702x_debug,0x04,args)
+#define deb_fe(args...)   dprintk(dvb_usb_vp702x_debug,0x08,args)
+
+/* commands are read and written with USB control messages */
+
+/* consecutive read/write operation */
+#define REQUEST_OUT		0xB2
+#define REQUEST_IN		0xB3
+
+/* the out-buffer of these consecutive operations contain sub-commands when b[0] = 0
+ * request: 0xB2; i: 0; v: 0; b[0] = 0, b[1] = subcmd, additional buffer
+ * the returning buffer looks as follows
+ * request: 0xB3; i: 0; v: 0; b[0] = 0xB3, additional buffer */
+
+#define GET_TUNER_STATUS	0x05
+/* additional in buffer:
+ * 0   1   2    3              4   5   6               7       8
+ * N/A N/A 0x05 signal-quality N/A N/A signal-strength lock==0 N/A */
+
+#define GET_SYSTEM_STRING	0x06
+/* additional in buffer:
+ * 0   1   2   3   4   5   6   7   8
+ * N/A 'U' 'S' 'B' '7' '0' '2' 'X' N/A */
+
+#define SET_DISEQC_CMD		0x08
+/* additional out buffer:
+ * 0    1  2  3  4
+ * len  X1 X2 X3 X4
+ * additional in buffer:
+ * 0   1 2
+ * N/A 0 0   b[1] == b[2] == 0 -> success, failure otherwise */
+
+#define SET_LNB_POWER		0x09
+/* additional out buffer:
+ * 0    1    2
+ * 0x00 0xff 1 = on, 0 = off
+ * additional in buffer:
+ * 0   1 2
+ * N/A 0 0   b[1] == b[2] == 0 -> success failure otherwise */
+
+#define GET_MAC_ADDRESS		0x0A
+/* #define GET_MAC_ADDRESS   0x0B */
+/* additional in buffer:
+ * 0   1   2            3    4    5    6    7    8
+ * N/A N/A 0x0A or 0x0B MAC0 MAC1 MAC2 MAC3 MAC4 MAC5 */
+
+#define SET_PID_FILTER		0x11
+/* additional in buffer:
+ * 0        1        ... 14       15       16
+ * PID0_MSB PID0_LSB ... PID7_MSB PID7_LSB PID_active (bits) */
+
+/* request: 0xB2; i: 0; v: 0;
+ * b[0] != 0 -> tune and lock a channel
+ * 0     1     2       3      4      5      6    7
+ * freq0 freq1 divstep srate0 srate1 srate2 flag chksum
+ */
+
+/* one direction requests */
+#define READ_REMOTE_REQ		0xB4
+/* IN  i: 0; v: 0; b[0] == request, b[1] == key */
+
+#define READ_PID_NUMBER_REQ	0xB5
+/* IN  i: 0; v: 0; b[0] == request, b[1] == 0, b[2] = pid number */
+
+#define WRITE_EEPROM_REQ	0xB6
+/* OUT i: offset; v: value to write; no extra buffer */
+
+#define READ_EEPROM_REQ		0xB7
+/* IN  i: bufferlen; v: offset; buffer with bufferlen bytes */
+
+#define READ_STATUS		0xB8
+/* IN  i: 0; v: 0; bufferlen 10 */
+
+#define READ_TUNER_REG_REQ	0xB9
+/* IN  i: 0; v: register; b[0] = value */
+
+#define READ_FX2_REG_REQ	0xBA
+/* IN  i: offset; v: 0; b[0] = value */
+
+#define WRITE_FX2_REG_REQ	0xBB
+/* OUT i: offset; v: value to write; 1 byte extra buffer */
+
+#define SET_TUNER_POWER_REQ	0xBC
+/* IN  i: 0 = power off, 1 = power on */
+
+#define WRITE_TUNER_REG_REQ	0xBD
+/* IN  i: register, v: value to write, no extra buffer */
+
+#define RESET_TUNER		0xBE
+/* IN  i: 0, v: 0, no extra buffer */
+
+struct vp702x_device_state {
+	struct mutex buf_mutex;
+	int buf_len;
+	u8 *buf;
+};
+
+
+extern struct dvb_frontend * vp702x_fe_attach(struct dvb_usb_device *d);
+
+extern int vp702x_usb_inout_op(struct dvb_usb_device *d, u8 *o, int olen, u8 *i, int ilen, int msec);
+extern int vp702x_usb_in_op(struct dvb_usb_device *d, u8 req, u16 value, u16 index, u8 *b, int blen);
+
+#endif
diff --git a/drivers/media/usb/dvb-usb/vp7045-fe.c b/drivers/media/usb/dvb-usb/vp7045-fe.c
new file mode 100644
index 0000000..1173ae2
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/vp7045-fe.c
@@ -0,0 +1,191 @@
+/* DVB frontend part of the Linux driver for TwinhanDTV Alpha/MagicBoxII USB2.0
+ * DVB-T receiver.
+ *
+ * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@posteo.de)
+ *
+ * Thanks to Twinhan who kindly provided hardware and information.
+ *
+ *	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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ *
+ */
+#include "vp7045.h"
+
+/* It is a Zarlink MT352 within a Samsung Tuner (DNOS404ZH102A) - 040929 - AAT
+ *
+ * Programming is hidden inside the firmware, so set_frontend is very easy.
+ * Even though there is a Firmware command that one can use to access the demod
+ * via its registers. This is used for status information.
+ */
+
+struct vp7045_fe_state {
+	struct dvb_frontend fe;
+	struct dvb_usb_device *d;
+};
+
+static int vp7045_fe_read_status(struct dvb_frontend *fe,
+				 enum fe_status *status)
+{
+	struct vp7045_fe_state *state = fe->demodulator_priv;
+	u8 s0 = vp7045_read_reg(state->d,0x00),
+	   s1 = vp7045_read_reg(state->d,0x01),
+	   s3 = vp7045_read_reg(state->d,0x03);
+
+	*status = 0;
+	if (s0 & (1 << 4))
+		*status |= FE_HAS_CARRIER;
+	if (s0 & (1 << 1))
+		*status |= FE_HAS_VITERBI;
+	if (s0 & (1 << 5))
+		*status |= FE_HAS_LOCK;
+	if (s1 & (1 << 1))
+		*status |= FE_HAS_SYNC;
+	if (s3 & (1 << 6))
+		*status |= FE_HAS_SIGNAL;
+
+	if ((*status & (FE_HAS_CARRIER | FE_HAS_VITERBI | FE_HAS_SYNC)) !=
+			(FE_HAS_CARRIER | FE_HAS_VITERBI | FE_HAS_SYNC))
+		*status &= ~FE_HAS_LOCK;
+
+	return 0;
+}
+
+static int vp7045_fe_read_ber(struct dvb_frontend* fe, u32 *ber)
+{
+	struct vp7045_fe_state *state = fe->demodulator_priv;
+	*ber = (vp7045_read_reg(state->d, 0x0D) << 16) |
+	       (vp7045_read_reg(state->d, 0x0E) << 8) |
+		vp7045_read_reg(state->d, 0x0F);
+	return 0;
+}
+
+static int vp7045_fe_read_unc_blocks(struct dvb_frontend* fe, u32 *unc)
+{
+	struct vp7045_fe_state *state = fe->demodulator_priv;
+	*unc = (vp7045_read_reg(state->d, 0x10) << 8) |
+		    vp7045_read_reg(state->d, 0x11);
+	return 0;
+}
+
+static int vp7045_fe_read_signal_strength(struct dvb_frontend* fe, u16 *strength)
+{
+	struct vp7045_fe_state *state = fe->demodulator_priv;
+	u16 signal = (vp7045_read_reg(state->d, 0x14) << 8) |
+		vp7045_read_reg(state->d, 0x15);
+
+	*strength = ~signal;
+	return 0;
+}
+
+static int vp7045_fe_read_snr(struct dvb_frontend* fe, u16 *snr)
+{
+	struct vp7045_fe_state *state = fe->demodulator_priv;
+	u8 _snr = vp7045_read_reg(state->d, 0x09);
+	*snr = (_snr << 8) | _snr;
+	return 0;
+}
+
+static int vp7045_fe_init(struct dvb_frontend* fe)
+{
+	return 0;
+}
+
+static int vp7045_fe_sleep(struct dvb_frontend* fe)
+{
+	return 0;
+}
+
+static int vp7045_fe_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings *tune)
+{
+	tune->min_delay_ms = 800;
+	return 0;
+}
+
+static int vp7045_fe_set_frontend(struct dvb_frontend *fe)
+{
+	struct dtv_frontend_properties *fep = &fe->dtv_property_cache;
+	struct vp7045_fe_state *state = fe->demodulator_priv;
+	u8 buf[5];
+	u32 freq = fep->frequency / 1000;
+
+	buf[0] = (freq >> 16) & 0xff;
+	buf[1] = (freq >>  8) & 0xff;
+	buf[2] =  freq        & 0xff;
+	buf[3] = 0;
+
+	switch (fep->bandwidth_hz) {
+	case 8000000:
+		buf[4] = 8;
+		break;
+	case 7000000:
+		buf[4] = 7;
+		break;
+	case 6000000:
+		buf[4] = 6;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	vp7045_usb_op(state->d,LOCK_TUNER_COMMAND,buf,5,NULL,0,200);
+	return 0;
+}
+
+static void vp7045_fe_release(struct dvb_frontend* fe)
+{
+	struct vp7045_fe_state *state = fe->demodulator_priv;
+	kfree(state);
+}
+
+static const struct dvb_frontend_ops vp7045_fe_ops;
+
+struct dvb_frontend * vp7045_fe_attach(struct dvb_usb_device *d)
+{
+	struct vp7045_fe_state *s = kzalloc(sizeof(struct vp7045_fe_state), GFP_KERNEL);
+	if (s == NULL)
+		goto error;
+
+	s->d = d;
+	memcpy(&s->fe.ops, &vp7045_fe_ops, sizeof(struct dvb_frontend_ops));
+	s->fe.demodulator_priv = s;
+
+	return &s->fe;
+error:
+	return NULL;
+}
+
+
+static const struct dvb_frontend_ops vp7045_fe_ops = {
+	.delsys = { SYS_DVBT },
+	.info = {
+		.name			= "Twinhan VP7045/46 USB DVB-T",
+		.frequency_min_hz	=  44250 * kHz,
+		.frequency_max_hz	= 867250 * kHz,
+		.frequency_stepsize_hz	=      1 * kHz,
+		.caps = FE_CAN_INVERSION_AUTO |
+				FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 |
+				FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO |
+				FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO |
+				FE_CAN_TRANSMISSION_MODE_AUTO |
+				FE_CAN_GUARD_INTERVAL_AUTO |
+				FE_CAN_RECOVER |
+				FE_CAN_HIERARCHY_AUTO,
+	},
+
+	.release = vp7045_fe_release,
+
+	.init = vp7045_fe_init,
+	.sleep = vp7045_fe_sleep,
+
+	.set_frontend = vp7045_fe_set_frontend,
+	.get_tune_settings = vp7045_fe_get_tune_settings,
+
+	.read_status = vp7045_fe_read_status,
+	.read_ber = vp7045_fe_read_ber,
+	.read_signal_strength = vp7045_fe_read_signal_strength,
+	.read_snr = vp7045_fe_read_snr,
+	.read_ucblocks = vp7045_fe_read_unc_blocks,
+};
diff --git a/drivers/media/usb/dvb-usb/vp7045.c b/drivers/media/usb/dvb-usb/vp7045.c
new file mode 100644
index 0000000..e2c8a85
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/vp7045.c
@@ -0,0 +1,244 @@
+/* DVB USB compliant Linux driver for the
+ *  - TwinhanDTV Alpha/MagicBoxII USB2.0 DVB-T receiver
+ *  - DigitalNow TinyUSB2 DVB-t receiver
+ *
+ * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@posteo.de)
+ *
+ * Thanks to Twinhan who kindly provided hardware and information.
+ *
+ *	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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#include "vp7045.h"
+
+/* debug */
+static int dvb_usb_vp7045_debug;
+module_param_named(debug,dvb_usb_vp7045_debug, int, 0644);
+MODULE_PARM_DESC(debug, "set debugging level (1=info,xfer=2,rc=4 (or-able))." DVB_USB_DEBUG_STATUS);
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+#define deb_info(args...) dprintk(dvb_usb_vp7045_debug,0x01,args)
+#define deb_xfer(args...) dprintk(dvb_usb_vp7045_debug,0x02,args)
+#define deb_rc(args...)   dprintk(dvb_usb_vp7045_debug,0x04,args)
+
+int vp7045_usb_op(struct dvb_usb_device *d, u8 cmd, u8 *out, int outlen, u8 *in, int inlen, int msec)
+{
+	int ret = 0;
+	u8 *buf = d->priv;
+
+	buf[0] = cmd;
+
+	if (outlen > 19)
+		outlen = 19;
+
+	if (inlen > 11)
+		inlen = 11;
+
+	ret = mutex_lock_interruptible(&d->usb_mutex);
+	if (ret)
+		return ret;
+
+	if (out != NULL && outlen > 0)
+		memcpy(&buf[1], out, outlen);
+
+	deb_xfer("out buffer: ");
+	debug_dump(buf, outlen+1, deb_xfer);
+
+
+	if (usb_control_msg(d->udev,
+			usb_sndctrlpipe(d->udev,0),
+			TH_COMMAND_OUT, USB_TYPE_VENDOR | USB_DIR_OUT, 0, 0,
+			buf, 20, 2000) != 20) {
+		err("USB control message 'out' went wrong.");
+		ret = -EIO;
+		goto unlock;
+	}
+
+	msleep(msec);
+
+	if (usb_control_msg(d->udev,
+			usb_rcvctrlpipe(d->udev,0),
+			TH_COMMAND_IN, USB_TYPE_VENDOR | USB_DIR_IN, 0, 0,
+			buf, 12, 2000) != 12) {
+		err("USB control message 'in' went wrong.");
+		ret = -EIO;
+		goto unlock;
+	}
+
+	deb_xfer("in buffer: ");
+	debug_dump(buf, 12, deb_xfer);
+
+	if (in != NULL && inlen > 0)
+		memcpy(in, &buf[1], inlen);
+
+unlock:
+	mutex_unlock(&d->usb_mutex);
+
+	return ret;
+}
+
+u8 vp7045_read_reg(struct dvb_usb_device *d, u8 reg)
+{
+	u8 obuf[2] = { 0 },v;
+	obuf[1] = reg;
+
+	vp7045_usb_op(d,TUNER_REG_READ,obuf,2,&v,1,30);
+
+	return v;
+}
+
+static int vp7045_power_ctrl(struct dvb_usb_device *d, int onoff)
+{
+	u8 v = onoff;
+	return vp7045_usb_op(d,SET_TUNER_POWER,&v,1,NULL,0,150);
+}
+
+static int vp7045_rc_query(struct dvb_usb_device *d)
+{
+	u8 key;
+	vp7045_usb_op(d,RC_VAL_READ,NULL,0,&key,1,20);
+
+	deb_rc("remote query key: %x %d\n",key,key);
+
+	if (key != 0x44) {
+		/*
+		 * The 8 bit address isn't available, but since the remote uses
+		 * address 0 we'll use that. nec repeats are ignored too, even
+		 * though the remote sends them.
+		 */
+		rc_keydown(d->rc_dev, RC_PROTO_NEC, RC_SCANCODE_NEC(0, key), 0);
+	}
+
+	return 0;
+}
+
+static int vp7045_read_eeprom(struct dvb_usb_device *d,u8 *buf, int len, int offset)
+{
+	int i = 0;
+	u8 v,br[2];
+	for (i=0; i < len; i++) {
+		v = offset + i;
+		vp7045_usb_op(d,GET_EE_VALUE,&v,1,br,2,5);
+		buf[i] = br[1];
+	}
+	deb_info("VP7045 EEPROM read (offs: %d, len: %d) : ",offset, i);
+	debug_dump(buf,i,deb_info);
+	return 0;
+}
+
+static int vp7045_read_mac_addr(struct dvb_usb_device *d,u8 mac[6])
+{
+	return vp7045_read_eeprom(d,mac, 6, MAC_0_ADDR);
+}
+
+static int vp7045_frontend_attach(struct dvb_usb_adapter *adap)
+{
+	u8 buf[255] = { 0 };
+
+	vp7045_usb_op(adap->dev,VENDOR_STRING_READ,NULL,0,buf,20,0);
+	buf[10] = '\0';
+	deb_info("firmware says: %s ",buf);
+
+	vp7045_usb_op(adap->dev,PRODUCT_STRING_READ,NULL,0,buf,20,0);
+	buf[10] = '\0';
+	deb_info("%s ",buf);
+
+	vp7045_usb_op(adap->dev,FW_VERSION_READ,NULL,0,buf,20,0);
+	buf[10] = '\0';
+	deb_info("v%s\n",buf);
+
+/*	Dump the EEPROM */
+/*	vp7045_read_eeprom(d,buf, 255, FX2_ID_ADDR); */
+
+	adap->fe_adap[0].fe = vp7045_fe_attach(adap->dev);
+
+	return 0;
+}
+
+static struct dvb_usb_device_properties vp7045_properties;
+
+static int vp7045_usb_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	return dvb_usb_device_init(intf, &vp7045_properties,
+				   THIS_MODULE, NULL, adapter_nr);
+}
+
+static struct usb_device_id vp7045_usb_table [] = {
+	    { USB_DEVICE(USB_VID_VISIONPLUS, USB_PID_TWINHAN_VP7045_COLD) },
+	    { USB_DEVICE(USB_VID_VISIONPLUS, USB_PID_TWINHAN_VP7045_WARM) },
+	    { USB_DEVICE(USB_VID_VISIONPLUS, USB_PID_DNTV_TINYUSB2_COLD) },
+	    { USB_DEVICE(USB_VID_VISIONPLUS, USB_PID_DNTV_TINYUSB2_WARM) },
+	    { 0 },
+};
+MODULE_DEVICE_TABLE(usb, vp7045_usb_table);
+
+static struct dvb_usb_device_properties vp7045_properties = {
+	.usb_ctrl = CYPRESS_FX2,
+	.firmware = "dvb-usb-vp7045-01.fw",
+	.size_of_priv = 20,
+
+	.num_adapters = 1,
+	.adapter = {
+		{
+		.num_frontends = 1,
+		.fe = {{
+			.frontend_attach  = vp7045_frontend_attach,
+			/* parameter for the MPEG2-data transfer */
+			.stream = {
+				.type = USB_BULK,
+				.count = 7,
+				.endpoint = 0x02,
+				.u = {
+					.bulk = {
+						.buffersize = 4096,
+					}
+				}
+			},
+		}},
+		}
+	},
+	.power_ctrl       = vp7045_power_ctrl,
+	.read_mac_address = vp7045_read_mac_addr,
+
+	.rc.core = {
+		.rc_interval	= 400,
+		.rc_codes	= RC_MAP_TWINHAN_VP1027_DVBS,
+		.module_name    = KBUILD_MODNAME,
+		.rc_query	= vp7045_rc_query,
+		.allowed_protos = RC_PROTO_BIT_NEC,
+		.scancode_mask	= 0xff,
+	},
+
+	.num_device_descs = 2,
+	.devices = {
+		{ .name = "Twinhan USB2.0 DVB-T receiver (TwinhanDTV Alpha/MagicBox II)",
+		  .cold_ids = { &vp7045_usb_table[0], NULL },
+		  .warm_ids = { &vp7045_usb_table[1], NULL },
+		},
+		{ .name = "DigitalNow TinyUSB 2 DVB-t Receiver",
+		  .cold_ids = { &vp7045_usb_table[2], NULL },
+		  .warm_ids = { &vp7045_usb_table[3], NULL },
+		},
+		{ NULL },
+	}
+};
+
+/* usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver vp7045_usb_driver = {
+	.name		= "dvb_usb_vp7045",
+	.probe		= vp7045_usb_probe,
+	.disconnect	= dvb_usb_device_exit,
+	.id_table	= vp7045_usb_table,
+};
+
+module_usb_driver(vp7045_usb_driver);
+
+MODULE_AUTHOR("Patrick Boettcher <patrick.boettcher@posteo.de>");
+MODULE_DESCRIPTION("Driver for Twinhan MagicBox/Alpha and DNTV tinyUSB2 DVB-T USB2.0");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/dvb-usb/vp7045.h b/drivers/media/usb/dvb-usb/vp7045.h
new file mode 100644
index 0000000..2fdafd8
--- /dev/null
+++ b/drivers/media/usb/dvb-usb/vp7045.h
@@ -0,0 +1,70 @@
+/* Common header-file of the Linux driver for the TwinhanDTV Alpha/MagicBoxII
+ * USB2.0 DVB-T receiver.
+ *
+ * Copyright (C) 2004-5 Patrick Boettcher (patrick.boettcher@posteo.de)
+ *
+ * Thanks to Twinhan who kindly provided hardware and information.
+ *
+ *	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, version 2.
+ *
+ * see Documentation/media/dvb-drivers/dvb-usb.rst for more information
+ */
+#ifndef _DVB_USB_VP7045_H_
+#define _DVB_USB_VP7045_H_
+
+#define DVB_USB_LOG_PREFIX "vp7045"
+#include "dvb-usb.h"
+
+/* vp7045 commands */
+
+/* Twinhan Vendor requests */
+#define TH_COMMAND_IN                     0xC0
+#define TH_COMMAND_OUT                    0xC1
+
+/* command bytes */
+#define TUNER_REG_READ                    0x03
+#define TUNER_REG_WRITE                   0x04
+
+#define RC_VAL_READ                       0x05
+ #define RC_NO_KEY                        0x44
+
+#define SET_TUNER_POWER                   0x06
+#define CHECK_TUNER_POWER                 0x12
+ #define Tuner_Power_ON                   1
+ #define Tuner_Power_OFF                  0
+
+#define GET_USB_SPEED                     0x07
+
+#define LOCK_TUNER_COMMAND                0x09
+
+#define TUNER_SIGNAL_READ                 0x0A
+
+/* FX2 eeprom */
+#define SET_EE_VALUE                      0x10
+#define GET_EE_VALUE                      0x11
+ #define FX2_ID_ADDR                      0x00
+ #define VID_MSB_ADDR                     0x02
+ #define VID_LSB_ADDR                     0x01
+ #define PID_MSB_ADDR                     0x04
+ #define PID_LSB_ADDR                     0x03
+ #define MAC_0_ADDR                       0x07
+ #define MAC_1_ADDR                       0x08
+ #define MAC_2_ADDR                       0x09
+ #define MAC_3_ADDR                       0x0a
+ #define MAC_4_ADDR                       0x0b
+ #define MAC_5_ADDR                       0x0c
+
+#define RESET_FX2                         0x13
+
+#define FW_VERSION_READ                   0x0B
+#define VENDOR_STRING_READ                0x0C
+#define PRODUCT_STRING_READ               0x0D
+#define FW_BCD_VERSION_READ               0x14
+
+extern struct dvb_frontend * vp7045_fe_attach(struct dvb_usb_device *d);
+extern int vp7045_usb_op(struct dvb_usb_device *d, u8 cmd, u8 *out, int outlen, u8 *in, int inlen,int msec);
+extern u8 vp7045_read_reg(struct dvb_usb_device *d, u8 reg);
+
+#endif
diff --git a/drivers/media/usb/em28xx/Kconfig b/drivers/media/usb/em28xx/Kconfig
new file mode 100644
index 0000000..451e076
--- /dev/null
+++ b/drivers/media/usb/em28xx/Kconfig
@@ -0,0 +1,80 @@
+config VIDEO_EM28XX
+	tristate "Empia EM28xx USB devices support"
+	depends on VIDEO_DEV && I2C
+	select VIDEO_TUNER
+	select VIDEO_TVEEPROM
+
+config VIDEO_EM28XX_V4L2
+	tristate "Empia EM28xx analog TV, video capture and/or webcam support"
+	depends on VIDEO_EM28XX
+	select VIDEOBUF2_VMALLOC
+	select VIDEO_SAA711X if MEDIA_SUBDRV_AUTOSELECT
+	select VIDEO_TVP5150 if MEDIA_SUBDRV_AUTOSELECT
+	select VIDEO_MSP3400 if MEDIA_SUBDRV_AUTOSELECT
+	select VIDEO_MT9V011 if MEDIA_SUBDRV_AUTOSELECT && MEDIA_CAMERA_SUPPORT
+	select VIDEO_OV2640 if MEDIA_SUBDRV_AUTOSELECT && MEDIA_CAMERA_SUPPORT
+	---help---
+	  This is a video4linux driver for Empia 28xx based TV cards.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called em28xx
+
+config VIDEO_EM28XX_ALSA
+	depends on VIDEO_EM28XX && SND
+	select SND_PCM
+	tristate "Empia EM28xx ALSA audio module"
+	---help---
+	  This is an ALSA driver for some Empia 28xx based TV cards.
+
+	  This is not required for em2800/em2820/em2821 boards. However,
+	  newer em28xx devices uses Vendor Class for audio, instead of
+	  implementing the USB Audio Class. For those chips, this module
+	  will enable digital audio.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called em28xx-alsa
+
+config VIDEO_EM28XX_DVB
+	tristate "DVB/ATSC Support for em28xx based TV cards"
+	depends on VIDEO_EM28XX && DVB_CORE
+	select DVB_LGDT330X if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_LGDT3305 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_LGDT3306A if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_ZL10353 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_TDA10023 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_S921 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_DRXD if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_CXD2820R if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_DRXK if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_TDA18271C2DD if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_TDA10071 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_A8293 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_MT352 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_S5H1409 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_MB86A20S if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_QT1010 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_TDA18271 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_TDA18212 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_M88DS3103 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_TS2020 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_DRX39XYJ if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_SI2168 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_SI2157 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_TC90522 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_QM1D1C0042 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_SIMPLE if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_XC2028 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_XC5000 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_MT2060 if MEDIA_SUBDRV_AUTOSELECT
+	---help---
+	  This adds support for DVB cards based on the
+	  Empiatech em28xx chips.
+
+config VIDEO_EM28XX_RC
+	tristate "EM28XX Remote Controller support"
+	depends on RC_CORE
+	depends on VIDEO_EM28XX
+	depends on !(RC_CORE=m && VIDEO_EM28XX=y)
+	default VIDEO_EM28XX
+	---help---
+	  Enables Remote Controller support on em28xx driver.
diff --git a/drivers/media/usb/em28xx/Makefile b/drivers/media/usb/em28xx/Makefile
new file mode 100644
index 0000000..8a22400
--- /dev/null
+++ b/drivers/media/usb/em28xx/Makefile
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0
+em28xx-y +=	em28xx-core.o em28xx-i2c.o em28xx-cards.o em28xx-camera.o
+
+em28xx-v4l-objs := em28xx-video.o em28xx-vbi.o
+em28xx-alsa-objs := em28xx-audio.o
+em28xx-rc-objs := em28xx-input.o
+
+obj-$(CONFIG_VIDEO_EM28XX) += em28xx.o
+obj-$(CONFIG_VIDEO_EM28XX_V4L2) += em28xx-v4l.o
+obj-$(CONFIG_VIDEO_EM28XX_ALSA) += em28xx-alsa.o
+obj-$(CONFIG_VIDEO_EM28XX_DVB) += em28xx-dvb.o
+obj-$(CONFIG_VIDEO_EM28XX_RC) += em28xx-rc.o
+
+ccflags-y += -Idrivers/media/tuners
+ccflags-y += -Idrivers/media/dvb-frontends
diff --git a/drivers/media/usb/em28xx/em28xx-audio.c b/drivers/media/usb/em28xx/em28xx-audio.c
new file mode 100644
index 0000000..8e799ae
--- /dev/null
+++ b/drivers/media/usb/em28xx/em28xx-audio.c
@@ -0,0 +1,1070 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Empiatech em28x1 audio extension
+//
+// Copyright (C) 2006 Markus Rechberger <mrechberger@gmail.com>
+//
+// Copyright (C) 2007-2016 Mauro Carvalho Chehab
+//	- Port to work with the in-kernel driver
+//	- Cleanups, fixes, alsa-controls, etc.
+//
+// This driver is based on my previous au600 usb pstn audio driver
+// and inherits all the copyrights
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include "em28xx.h"
+
+#include <linux/kernel.h>
+#include <linux/usb.h>
+#include <linux/init.h>
+#include <linux/sound.h>
+#include <linux/spinlock.h>
+#include <linux/soundcard.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+#include <linux/proc_fs.h>
+#include <linux/module.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/info.h>
+#include <sound/initval.h>
+#include <sound/control.h>
+#include <sound/tlv.h>
+#include <sound/ac97_codec.h>
+#include <media/v4l2-common.h>
+
+static int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "activates debug info");
+
+#define EM28XX_MAX_AUDIO_BUFS		5
+#define EM28XX_MIN_AUDIO_PACKETS	64
+
+#define dprintk(fmt, arg...) do {					\
+	if (debug)						\
+		dev_printk(KERN_DEBUG, &dev->intf->dev,			\
+			   "video: %s: " fmt, __func__, ## arg);	\
+} while (0)
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+
+static int em28xx_deinit_isoc_audio(struct em28xx *dev)
+{
+	int i;
+
+	dprintk("Stopping isoc\n");
+	for (i = 0; i < dev->adev.num_urb; i++) {
+		struct urb *urb = dev->adev.urb[i];
+
+		if (!irqs_disabled())
+			usb_kill_urb(urb);
+		else
+			usb_unlink_urb(urb);
+	}
+
+	return 0;
+}
+
+static void em28xx_audio_isocirq(struct urb *urb)
+{
+	struct em28xx            *dev = urb->context;
+	int                      i;
+	unsigned int             oldptr;
+	int                      period_elapsed = 0;
+	int                      status;
+	unsigned char            *cp;
+	unsigned int             stride;
+	struct snd_pcm_substream *substream;
+	struct snd_pcm_runtime   *runtime;
+
+	if (dev->disconnected) {
+		dprintk("device disconnected while streaming. URB status=%d.\n",
+			urb->status);
+		atomic_set(&dev->adev.stream_started, 0);
+		return;
+	}
+
+	switch (urb->status) {
+	case 0:             /* success */
+	case -ETIMEDOUT:    /* NAK */
+		break;
+	case -ECONNRESET:   /* kill */
+	case -ENOENT:
+	case -ESHUTDOWN:
+		return;
+	default:            /* error */
+		dprintk("urb completion error %d.\n", urb->status);
+		break;
+	}
+
+	if (atomic_read(&dev->adev.stream_started) == 0)
+		return;
+
+	if (dev->adev.capture_pcm_substream) {
+		substream = dev->adev.capture_pcm_substream;
+		runtime = substream->runtime;
+		stride = runtime->frame_bits >> 3;
+
+		for (i = 0; i < urb->number_of_packets; i++) {
+			int length =
+			    urb->iso_frame_desc[i].actual_length / stride;
+			cp = (unsigned char *)urb->transfer_buffer +
+			    urb->iso_frame_desc[i].offset;
+
+			if (!length)
+				continue;
+
+			oldptr = dev->adev.hwptr_done_capture;
+			if (oldptr + length >= runtime->buffer_size) {
+				unsigned int cnt =
+				    runtime->buffer_size - oldptr;
+				memcpy(runtime->dma_area + oldptr * stride, cp,
+				       cnt * stride);
+				memcpy(runtime->dma_area, cp + cnt * stride,
+				       length * stride - cnt * stride);
+			} else {
+				memcpy(runtime->dma_area + oldptr * stride, cp,
+				       length * stride);
+			}
+
+			snd_pcm_stream_lock(substream);
+
+			dev->adev.hwptr_done_capture += length;
+			if (dev->adev.hwptr_done_capture >=
+			    runtime->buffer_size)
+				dev->adev.hwptr_done_capture -=
+				    runtime->buffer_size;
+
+			dev->adev.capture_transfer_done += length;
+			if (dev->adev.capture_transfer_done >=
+			    runtime->period_size) {
+				dev->adev.capture_transfer_done -=
+				    runtime->period_size;
+				period_elapsed = 1;
+			}
+
+			snd_pcm_stream_unlock(substream);
+		}
+		if (period_elapsed)
+			snd_pcm_period_elapsed(substream);
+	}
+	urb->status = 0;
+
+	status = usb_submit_urb(urb, GFP_ATOMIC);
+	if (status < 0)
+		dev_err(&dev->intf->dev,
+			"resubmit of audio urb failed (error=%i)\n",
+			status);
+}
+
+static int em28xx_init_audio_isoc(struct em28xx *dev)
+{
+	int       i, err;
+
+	dprintk("Starting isoc transfers\n");
+
+	/* Start streaming */
+	for (i = 0; i < dev->adev.num_urb; i++) {
+		memset(dev->adev.transfer_buffer[i], 0x80,
+		       dev->adev.urb[i]->transfer_buffer_length);
+
+		err = usb_submit_urb(dev->adev.urb[i], GFP_ATOMIC);
+		if (err) {
+			dev_err(&dev->intf->dev,
+				"submit of audio urb failed (error=%i)\n",
+				err);
+			em28xx_deinit_isoc_audio(dev);
+			atomic_set(&dev->adev.stream_started, 0);
+			return err;
+		}
+	}
+
+	return 0;
+}
+
+static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs,
+					size_t size)
+{
+	struct em28xx *dev = snd_pcm_substream_chip(subs);
+	struct snd_pcm_runtime *runtime = subs->runtime;
+
+	dprintk("Allocating vbuffer\n");
+	if (runtime->dma_area) {
+		if (runtime->dma_bytes > size)
+			return 0;
+
+		vfree(runtime->dma_area);
+	}
+	runtime->dma_area = vmalloc(size);
+	if (!runtime->dma_area)
+		return -ENOMEM;
+
+	runtime->dma_bytes = size;
+
+	return 0;
+}
+
+static const struct snd_pcm_hardware snd_em28xx_hw_capture = {
+	.info = SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_MMAP           |
+		SNDRV_PCM_INFO_INTERLEAVED    |
+		SNDRV_PCM_INFO_BATCH	      |
+		SNDRV_PCM_INFO_MMAP_VALID,
+
+	.formats = SNDRV_PCM_FMTBIT_S16_LE,
+
+	.rates = SNDRV_PCM_RATE_48000,
+
+	.rate_min = 48000,
+	.rate_max = 48000,
+	.channels_min = 2,
+	.channels_max = 2,
+	.buffer_bytes_max = 62720 * 8,	/* just about the value in usbaudio.c */
+
+	/*
+	 * The period is 12.288 bytes. Allow a 10% of variation along its
+	 * value, in order to avoid overruns/underruns due to some clock
+	 * drift.
+	 *
+	 * FIXME: This period assumes 64 packets, and a 48000 PCM rate.
+	 * Calculate it dynamically.
+	 */
+	.period_bytes_min = 11059,
+	.period_bytes_max = 13516,
+
+	.periods_min = 2,
+	.periods_max = 98,		/* 12544, */
+};
+
+static int snd_em28xx_capture_open(struct snd_pcm_substream *substream)
+{
+	struct em28xx *dev = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int nonblock, ret = 0;
+
+	if (!dev) {
+		pr_err("em28xx-audio: BUG: em28xx can't find device struct. Can't proceed with open\n");
+		return -ENODEV;
+	}
+
+	if (dev->disconnected)
+		return -ENODEV;
+
+	dprintk("opening device and trying to acquire exclusive lock\n");
+
+	nonblock = !!(substream->f_flags & O_NONBLOCK);
+	if (nonblock) {
+		if (!mutex_trylock(&dev->lock))
+			return -EAGAIN;
+	} else {
+		mutex_lock(&dev->lock);
+	}
+
+	runtime->hw = snd_em28xx_hw_capture;
+
+	if (dev->adev.users == 0) {
+		if (!dev->alt || dev->is_audio_only) {
+			struct usb_device *udev;
+
+			udev = interface_to_usbdev(dev->intf);
+
+			if (dev->is_audio_only)
+				/* audio is on a separate interface */
+				dev->alt = 1;
+			else
+				/* audio is on the same interface as video */
+				dev->alt = 7;
+				/*
+				 * FIXME: The intention seems to be to select
+				 * the alt setting with the largest
+				 * wMaxPacketSize for the video endpoint.
+				 * At least dev->alt should be used instead, but
+				 * we should probably not touch it at all if it
+				 * is already >0, because wMaxPacketSize of the
+				 * audio endpoints seems to be the same for all.
+				 */
+			dprintk("changing alternate number on interface %d to %d\n",
+				dev->ifnum, dev->alt);
+			usb_set_interface(udev, dev->ifnum, dev->alt);
+		}
+
+		/* Sets volume, mute, etc */
+		dev->mute = 0;
+		ret = em28xx_audio_analog_set(dev);
+		if (ret < 0)
+			goto err;
+	}
+
+	kref_get(&dev->ref);
+	dev->adev.users++;
+	mutex_unlock(&dev->lock);
+
+	/* Dynamically adjust the period size */
+	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+	snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_BYTES,
+				     dev->adev.period * 95 / 100,
+				     dev->adev.period * 105 / 100);
+
+	dev->adev.capture_pcm_substream = substream;
+
+	return 0;
+err:
+	mutex_unlock(&dev->lock);
+
+	dev_err(&dev->intf->dev,
+		"Error while configuring em28xx mixer\n");
+	return ret;
+}
+
+static int snd_em28xx_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct em28xx *dev = snd_pcm_substream_chip(substream);
+
+	dprintk("closing device\n");
+
+	dev->mute = 1;
+	mutex_lock(&dev->lock);
+	dev->adev.users--;
+	if (atomic_read(&dev->adev.stream_started) > 0) {
+		atomic_set(&dev->adev.stream_started, 0);
+		schedule_work(&dev->adev.wq_trigger);
+	}
+
+	em28xx_audio_analog_set(dev);
+	if (substream->runtime->dma_area) {
+		dprintk("freeing\n");
+		vfree(substream->runtime->dma_area);
+		substream->runtime->dma_area = NULL;
+	}
+	mutex_unlock(&dev->lock);
+	kref_put(&dev->ref, em28xx_free_device);
+
+	return 0;
+}
+
+static int snd_em28xx_hw_capture_params(struct snd_pcm_substream *substream,
+					struct snd_pcm_hw_params *hw_params)
+{
+	int ret;
+	struct em28xx *dev = snd_pcm_substream_chip(substream);
+
+	if (dev->disconnected)
+		return -ENODEV;
+
+	dprintk("Setting capture parameters\n");
+
+	ret = snd_pcm_alloc_vmalloc_buffer(substream,
+					   params_buffer_bytes(hw_params));
+	if (ret < 0)
+		return ret;
+#if 0
+	/*
+	 * TODO: set up em28xx audio chip to deliver the correct audio format,
+	 * current default is 48000hz multiplexed => 96000hz mono
+	 * which shouldn't matter since analogue TV only supports mono
+	 */
+	unsigned int channels, rate, format;
+
+	format = params_format(hw_params);
+	rate = params_rate(hw_params);
+	channels = params_channels(hw_params);
+#endif
+
+	return 0;
+}
+
+static int snd_em28xx_hw_capture_free(struct snd_pcm_substream *substream)
+{
+	struct em28xx *dev = snd_pcm_substream_chip(substream);
+	struct em28xx_audio *adev = &dev->adev;
+
+	dprintk("Stop capture, if needed\n");
+
+	if (atomic_read(&adev->stream_started) > 0) {
+		atomic_set(&adev->stream_started, 0);
+		schedule_work(&adev->wq_trigger);
+	}
+
+	return 0;
+}
+
+static int snd_em28xx_prepare(struct snd_pcm_substream *substream)
+{
+	struct em28xx *dev = snd_pcm_substream_chip(substream);
+
+	if (dev->disconnected)
+		return -ENODEV;
+
+	dev->adev.hwptr_done_capture = 0;
+	dev->adev.capture_transfer_done = 0;
+
+	return 0;
+}
+
+static void audio_trigger(struct work_struct *work)
+{
+	struct em28xx_audio *adev =
+			    container_of(work, struct em28xx_audio, wq_trigger);
+	struct em28xx *dev = container_of(adev, struct em28xx, adev);
+
+	if (atomic_read(&adev->stream_started)) {
+		dprintk("starting capture");
+		em28xx_init_audio_isoc(dev);
+	} else {
+		dprintk("stopping capture");
+		em28xx_deinit_isoc_audio(dev);
+	}
+}
+
+static int snd_em28xx_capture_trigger(struct snd_pcm_substream *substream,
+				      int cmd)
+{
+	struct em28xx *dev = snd_pcm_substream_chip(substream);
+	int retval = 0;
+
+	if (dev->disconnected)
+		return -ENODEV;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: /* fall through */
+	case SNDRV_PCM_TRIGGER_RESUME: /* fall through */
+	case SNDRV_PCM_TRIGGER_START:
+		atomic_set(&dev->adev.stream_started, 1);
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH: /* fall through */
+	case SNDRV_PCM_TRIGGER_SUSPEND: /* fall through */
+	case SNDRV_PCM_TRIGGER_STOP:
+		atomic_set(&dev->adev.stream_started, 0);
+		break;
+	default:
+		retval = -EINVAL;
+	}
+	schedule_work(&dev->adev.wq_trigger);
+	return retval;
+}
+
+static snd_pcm_uframes_t snd_em28xx_capture_pointer(struct snd_pcm_substream
+						    *substream)
+{
+	unsigned long flags;
+	struct em28xx *dev;
+	snd_pcm_uframes_t hwptr_done;
+
+	dev = snd_pcm_substream_chip(substream);
+	if (dev->disconnected)
+		return SNDRV_PCM_POS_XRUN;
+
+	spin_lock_irqsave(&dev->adev.slock, flags);
+	hwptr_done = dev->adev.hwptr_done_capture;
+	spin_unlock_irqrestore(&dev->adev.slock, flags);
+
+	return hwptr_done;
+}
+
+static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs,
+					     unsigned long offset)
+{
+	void *pageptr = subs->runtime->dma_area + offset;
+
+	return vmalloc_to_page(pageptr);
+}
+
+/*
+ * AC97 volume control support
+ */
+static int em28xx_vol_info(struct snd_kcontrol *kcontrol,
+			   struct snd_ctl_elem_info *info)
+{
+	struct em28xx *dev = snd_kcontrol_chip(kcontrol);
+
+	if (dev->disconnected)
+		return -ENODEV;
+
+	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	info->count = 2;
+	info->value.integer.min = 0;
+	info->value.integer.max = 0x1f;
+
+	return 0;
+}
+
+static int em28xx_vol_put(struct snd_kcontrol *kcontrol,
+			  struct snd_ctl_elem_value *value)
+{
+	struct em28xx *dev = snd_kcontrol_chip(kcontrol);
+	struct snd_pcm_substream *substream = dev->adev.capture_pcm_substream;
+	u16 val = (0x1f - (value->value.integer.value[0] & 0x1f)) |
+		  (0x1f - (value->value.integer.value[1] & 0x1f)) << 8;
+	int nonblock = 0;
+	int rc;
+
+	if (dev->disconnected)
+		return -ENODEV;
+
+	if (substream)
+		nonblock = !!(substream->f_flags & O_NONBLOCK);
+	if (nonblock) {
+		if (!mutex_trylock(&dev->lock))
+			return -EAGAIN;
+	} else {
+		mutex_lock(&dev->lock);
+	}
+	rc = em28xx_read_ac97(dev, kcontrol->private_value);
+	if (rc < 0)
+		goto err;
+
+	val |= rc & 0x8000;	/* Preserve the mute flag */
+
+	rc = em28xx_write_ac97(dev, kcontrol->private_value, val);
+	if (rc < 0)
+		goto err;
+
+	dprintk("%sleft vol %d, right vol %d (0x%04x) to ac97 volume control 0x%04x\n",
+		(val & 0x8000) ? "muted " : "",
+		0x1f - ((val >> 8) & 0x1f), 0x1f - (val & 0x1f),
+		val, (int)kcontrol->private_value);
+
+err:
+	mutex_unlock(&dev->lock);
+	return rc;
+}
+
+static int em28xx_vol_get(struct snd_kcontrol *kcontrol,
+			  struct snd_ctl_elem_value *value)
+{
+	struct em28xx *dev = snd_kcontrol_chip(kcontrol);
+	struct snd_pcm_substream *substream = dev->adev.capture_pcm_substream;
+	int nonblock = 0;
+	int val;
+
+	if (dev->disconnected)
+		return -ENODEV;
+
+	if (substream)
+		nonblock = !!(substream->f_flags & O_NONBLOCK);
+	if (nonblock) {
+		if (!mutex_trylock(&dev->lock))
+			return -EAGAIN;
+	} else {
+		mutex_lock(&dev->lock);
+	}
+	val = em28xx_read_ac97(dev, kcontrol->private_value);
+	mutex_unlock(&dev->lock);
+	if (val < 0)
+		return val;
+
+	dprintk("%sleft vol %d, right vol %d (0x%04x) from ac97 volume control 0x%04x\n",
+		(val & 0x8000) ? "muted " : "",
+		0x1f - ((val >> 8) & 0x1f), 0x1f - (val & 0x1f),
+		val, (int)kcontrol->private_value);
+
+	value->value.integer.value[0] = 0x1f - (val & 0x1f);
+	value->value.integer.value[1] = 0x1f - ((val >> 8) & 0x1f);
+
+	return 0;
+}
+
+static int em28xx_vol_put_mute(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *value)
+{
+	struct em28xx *dev = snd_kcontrol_chip(kcontrol);
+	u16 val = value->value.integer.value[0];
+	struct snd_pcm_substream *substream = dev->adev.capture_pcm_substream;
+	int nonblock = 0;
+	int rc;
+
+	if (dev->disconnected)
+		return -ENODEV;
+
+	if (substream)
+		nonblock = !!(substream->f_flags & O_NONBLOCK);
+	if (nonblock) {
+		if (!mutex_trylock(&dev->lock))
+			return -EAGAIN;
+	} else {
+		mutex_lock(&dev->lock);
+	}
+	rc = em28xx_read_ac97(dev, kcontrol->private_value);
+	if (rc < 0)
+		goto err;
+
+	if (val)
+		rc &= 0x1f1f;
+	else
+		rc |= 0x8000;
+
+	rc = em28xx_write_ac97(dev, kcontrol->private_value, rc);
+	if (rc < 0)
+		goto err;
+
+	dprintk("%sleft vol %d, right vol %d (0x%04x) to ac97 volume control 0x%04x\n",
+		(val & 0x8000) ? "muted " : "",
+		0x1f - ((val >> 8) & 0x1f), 0x1f - (val & 0x1f),
+		val, (int)kcontrol->private_value);
+
+err:
+	mutex_unlock(&dev->lock);
+	return rc;
+}
+
+static int em28xx_vol_get_mute(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_value *value)
+{
+	struct em28xx *dev = snd_kcontrol_chip(kcontrol);
+	struct snd_pcm_substream *substream = dev->adev.capture_pcm_substream;
+	int nonblock = 0;
+	int val;
+
+	if (dev->disconnected)
+		return -ENODEV;
+
+	if (substream)
+		nonblock = !!(substream->f_flags & O_NONBLOCK);
+	if (nonblock) {
+		if (!mutex_trylock(&dev->lock))
+			return -EAGAIN;
+	} else {
+		mutex_lock(&dev->lock);
+	}
+	val = em28xx_read_ac97(dev, kcontrol->private_value);
+	mutex_unlock(&dev->lock);
+	if (val < 0)
+		return val;
+
+	if (val & 0x8000)
+		value->value.integer.value[0] = 0;
+	else
+		value->value.integer.value[0] = 1;
+
+	dprintk("%sleft vol %d, right vol %d (0x%04x) from ac97 volume control 0x%04x\n",
+		(val & 0x8000) ? "muted " : "",
+		0x1f - ((val >> 8) & 0x1f), 0x1f - (val & 0x1f),
+		val, (int)kcontrol->private_value);
+
+	return 0;
+}
+
+static const DECLARE_TLV_DB_SCALE(em28xx_db_scale, -3450, 150, 0);
+
+static int em28xx_cvol_new(struct snd_card *card, struct em28xx *dev,
+			   char *name, int id)
+{
+	int err;
+	char ctl_name[44];
+	struct snd_kcontrol *kctl;
+	struct snd_kcontrol_new tmp;
+
+	memset(&tmp, 0, sizeof(tmp));
+	tmp.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	tmp.private_value = id,
+	tmp.name  = ctl_name,
+
+	/* Add Mute Control */
+	sprintf(ctl_name, "%s Switch", name);
+	tmp.get  = em28xx_vol_get_mute;
+	tmp.put  = em28xx_vol_put_mute;
+	tmp.info = snd_ctl_boolean_mono_info;
+	kctl = snd_ctl_new1(&tmp, dev);
+	err = snd_ctl_add(card, kctl);
+	if (err < 0)
+		return err;
+	dprintk("Added control %s for ac97 volume control 0x%04x\n",
+		ctl_name, id);
+
+	memset(&tmp, 0, sizeof(tmp));
+	tmp.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	tmp.private_value = id,
+	tmp.name  = ctl_name,
+
+	/* Add Volume Control */
+	sprintf(ctl_name, "%s Volume", name);
+	tmp.get   = em28xx_vol_get;
+	tmp.put   = em28xx_vol_put;
+	tmp.info  = em28xx_vol_info;
+	tmp.tlv.p = em28xx_db_scale,
+	kctl = snd_ctl_new1(&tmp, dev);
+	err = snd_ctl_add(card, kctl);
+	if (err < 0)
+		return err;
+	dprintk("Added control %s for ac97 volume control 0x%04x\n",
+		ctl_name, id);
+
+	return 0;
+}
+
+/*
+ * register/unregister code and data
+ */
+static const struct snd_pcm_ops snd_em28xx_pcm_capture = {
+	.open      = snd_em28xx_capture_open,
+	.close     = snd_em28xx_pcm_close,
+	.ioctl     = snd_pcm_lib_ioctl,
+	.hw_params = snd_em28xx_hw_capture_params,
+	.hw_free   = snd_em28xx_hw_capture_free,
+	.prepare   = snd_em28xx_prepare,
+	.trigger   = snd_em28xx_capture_trigger,
+	.pointer   = snd_em28xx_capture_pointer,
+	.page      = snd_pcm_get_vmalloc_page,
+};
+
+static void em28xx_audio_free_urb(struct em28xx *dev)
+{
+	struct usb_device *udev = interface_to_usbdev(dev->intf);
+	int i;
+
+	for (i = 0; i < dev->adev.num_urb; i++) {
+		struct urb *urb = dev->adev.urb[i];
+
+		if (!urb)
+			continue;
+
+		usb_free_coherent(udev, urb->transfer_buffer_length,
+				  dev->adev.transfer_buffer[i],
+				  urb->transfer_dma);
+
+		usb_free_urb(urb);
+	}
+	kfree(dev->adev.urb);
+	kfree(dev->adev.transfer_buffer);
+	dev->adev.num_urb = 0;
+}
+
+/* high bandwidth multiplier, as encoded in highspeed endpoint descriptors */
+static int em28xx_audio_ep_packet_size(struct usb_device *udev,
+				       struct usb_endpoint_descriptor *e)
+{
+	int size = le16_to_cpu(e->wMaxPacketSize);
+
+	if (udev->speed == USB_SPEED_HIGH)
+		return (size & 0x7ff) *  (1 + (((size) >> 11) & 0x03));
+
+	return size & 0x7ff;
+}
+
+static int em28xx_audio_urb_init(struct em28xx *dev)
+{
+	struct usb_interface *intf;
+	struct usb_endpoint_descriptor *e, *ep = NULL;
+	struct usb_device *udev = interface_to_usbdev(dev->intf);
+	int                 i, ep_size, interval, num_urb, npackets;
+	int		    urb_size, bytes_per_transfer;
+	u8 alt;
+
+	if (dev->ifnum)
+		alt = 1;
+	else
+		alt = 7;
+
+	intf = usb_ifnum_to_if(udev, dev->ifnum);
+
+	if (intf->num_altsetting <= alt) {
+		dev_err(&dev->intf->dev, "alt %d doesn't exist on interface %d\n",
+			dev->ifnum, alt);
+		return -ENODEV;
+	}
+
+	for (i = 0; i < intf->altsetting[alt].desc.bNumEndpoints; i++) {
+		e = &intf->altsetting[alt].endpoint[i].desc;
+		if (!usb_endpoint_dir_in(e))
+			continue;
+		if (e->bEndpointAddress == EM28XX_EP_AUDIO) {
+			ep = e;
+			break;
+		}
+	}
+
+	if (!ep) {
+		dev_err(&dev->intf->dev, "Couldn't find an audio endpoint");
+		return -ENODEV;
+	}
+
+	ep_size = em28xx_audio_ep_packet_size(udev, ep);
+	interval = 1 << (ep->bInterval - 1);
+
+	dev_info(&dev->intf->dev,
+		 "Endpoint 0x%02x %s on intf %d alt %d interval = %d, size %d\n",
+		 EM28XX_EP_AUDIO, usb_speed_string(udev->speed),
+		 dev->ifnum, alt, interval, ep_size);
+
+	/* Calculate the number and size of URBs to better fit the audio samples */
+
+	/*
+	 * Estimate the number of bytes per DMA transfer.
+	 *
+	 * This is given by the bit rate (for now, only 48000 Hz) multiplied
+	 * by 2 channels and 2 bytes/sample divided by the number of microframe
+	 * intervals and by the microframe rate (125 us)
+	 */
+	bytes_per_transfer = DIV_ROUND_UP(48000 * 2 * 2, 125 * interval);
+
+	/*
+	 * Estimate the number of transfer URBs. Don't let it go past the
+	 * maximum number of URBs that is known to be supported by the device.
+	 */
+	num_urb = DIV_ROUND_UP(bytes_per_transfer, ep_size);
+	if (num_urb > EM28XX_MAX_AUDIO_BUFS)
+		num_urb = EM28XX_MAX_AUDIO_BUFS;
+
+	/*
+	 * Now that we know the number of bytes per transfer and the number of
+	 * URBs, estimate the typical size of an URB, in order to adjust the
+	 * minimal number of packets.
+	 */
+	urb_size = bytes_per_transfer / num_urb;
+
+	/*
+	 * Now, calculate the amount of audio packets to be filled on each
+	 * URB. In order to preserve the old behaviour, use a minimal
+	 * threshold for this value.
+	 */
+	npackets = EM28XX_MIN_AUDIO_PACKETS;
+	if (urb_size > ep_size * npackets)
+		npackets = DIV_ROUND_UP(urb_size, ep_size);
+
+	dev_info(&dev->intf->dev,
+		 "Number of URBs: %d, with %d packets and %d size\n",
+		 num_urb, npackets, urb_size);
+
+	/* Estimate the bytes per period */
+	dev->adev.period = urb_size * npackets;
+
+	/* Allocate space to store the number of URBs to be used */
+
+	dev->adev.transfer_buffer = kcalloc(num_urb,
+					    sizeof(*dev->adev.transfer_buffer),
+					    GFP_ATOMIC);
+	if (!dev->adev.transfer_buffer)
+		return -ENOMEM;
+
+	dev->adev.urb = kcalloc(num_urb, sizeof(*dev->adev.urb), GFP_ATOMIC);
+	if (!dev->adev.urb) {
+		kfree(dev->adev.transfer_buffer);
+		return -ENOMEM;
+	}
+
+	/* Alloc memory for each URB and for each transfer buffer */
+	dev->adev.num_urb = num_urb;
+	for (i = 0; i < num_urb; i++) {
+		struct urb *urb;
+		int j, k;
+		void *buf;
+
+		urb = usb_alloc_urb(npackets, GFP_ATOMIC);
+		if (!urb) {
+			em28xx_audio_free_urb(dev);
+			return -ENOMEM;
+		}
+		dev->adev.urb[i] = urb;
+
+		buf = usb_alloc_coherent(udev, npackets * ep_size, GFP_ATOMIC,
+					 &urb->transfer_dma);
+		if (!buf) {
+			dev_err(&dev->intf->dev,
+				"usb_alloc_coherent failed!\n");
+			em28xx_audio_free_urb(dev);
+			return -ENOMEM;
+		}
+		dev->adev.transfer_buffer[i] = buf;
+
+		urb->dev = udev;
+		urb->context = dev;
+		urb->pipe = usb_rcvisocpipe(udev, EM28XX_EP_AUDIO);
+		urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
+		urb->transfer_buffer = buf;
+		urb->interval = interval;
+		urb->complete = em28xx_audio_isocirq;
+		urb->number_of_packets = npackets;
+		urb->transfer_buffer_length = ep_size * npackets;
+
+		for (j = k = 0; j < npackets; j++, k += ep_size) {
+			urb->iso_frame_desc[j].offset = k;
+			urb->iso_frame_desc[j].length = ep_size;
+		}
+	}
+
+	return 0;
+}
+
+static int em28xx_audio_init(struct em28xx *dev)
+{
+	struct em28xx_audio *adev = &dev->adev;
+	struct usb_device *udev = interface_to_usbdev(dev->intf);
+	struct snd_pcm      *pcm;
+	struct snd_card     *card;
+	static int          devnr;
+	int		    err;
+
+	if (dev->usb_audio_type != EM28XX_USB_AUDIO_VENDOR) {
+		/*
+		 * This device does not support the extension (in this case
+		 * the device is expecting the snd-usb-audio module or
+		 * doesn't have analog audio support at all)
+		 */
+		return 0;
+	}
+
+	dev_info(&dev->intf->dev, "Binding audio extension\n");
+
+	kref_get(&dev->ref);
+
+	dev_info(&dev->intf->dev,
+		 "em28xx-audio.c: Copyright (C) 2006 Markus Rechberger\n");
+	dev_info(&dev->intf->dev,
+		 "em28xx-audio.c: Copyright (C) 2007-2016 Mauro Carvalho Chehab\n");
+
+	err = snd_card_new(&dev->intf->dev, index[devnr], "Em28xx Audio",
+			   THIS_MODULE, 0, &card);
+	if (err < 0)
+		return err;
+
+	spin_lock_init(&adev->slock);
+	adev->sndcard = card;
+	adev->udev = udev;
+
+	err = snd_pcm_new(card, "Em28xx Audio", 0, 0, 1, &pcm);
+	if (err < 0)
+		goto card_free;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_em28xx_pcm_capture);
+	pcm->info_flags = 0;
+	pcm->private_data = dev;
+	strcpy(pcm->name, "Empia 28xx Capture");
+
+	strcpy(card->driver, "Em28xx-Audio");
+	strcpy(card->shortname, "Em28xx Audio");
+	strcpy(card->longname, "Empia Em28xx Audio");
+
+	INIT_WORK(&adev->wq_trigger, audio_trigger);
+
+	if (dev->audio_mode.ac97 != EM28XX_NO_AC97) {
+		em28xx_cvol_new(card, dev, "Video", AC97_VIDEO);
+		em28xx_cvol_new(card, dev, "Line In", AC97_LINE);
+		em28xx_cvol_new(card, dev, "Phone", AC97_PHONE);
+		em28xx_cvol_new(card, dev, "Microphone", AC97_MIC);
+		em28xx_cvol_new(card, dev, "CD", AC97_CD);
+		em28xx_cvol_new(card, dev, "AUX", AC97_AUX);
+		em28xx_cvol_new(card, dev, "PCM", AC97_PCM);
+
+		em28xx_cvol_new(card, dev, "Master", AC97_MASTER);
+		em28xx_cvol_new(card, dev, "Line", AC97_HEADPHONE);
+		em28xx_cvol_new(card, dev, "Mono", AC97_MASTER_MONO);
+		em28xx_cvol_new(card, dev, "LFE", AC97_CENTER_LFE_MASTER);
+		em28xx_cvol_new(card, dev, "Surround", AC97_SURROUND_MASTER);
+	}
+
+	err = em28xx_audio_urb_init(dev);
+	if (err)
+		goto card_free;
+
+	err = snd_card_register(card);
+	if (err < 0)
+		goto urb_free;
+
+	dev_info(&dev->intf->dev, "Audio extension successfully initialized\n");
+	return 0;
+
+urb_free:
+	em28xx_audio_free_urb(dev);
+
+card_free:
+	snd_card_free(card);
+	adev->sndcard = NULL;
+
+	return err;
+}
+
+static int em28xx_audio_fini(struct em28xx *dev)
+{
+	if (!dev)
+		return 0;
+
+	if (dev->usb_audio_type != EM28XX_USB_AUDIO_VENDOR) {
+		/*
+		 * This device does not support the extension (in this case
+		 * the device is expecting the snd-usb-audio module or
+		 * doesn't have analog audio support at all)
+		 */
+		return 0;
+	}
+
+	dev_info(&dev->intf->dev, "Closing audio extension\n");
+
+	if (dev->adev.sndcard) {
+		snd_card_disconnect(dev->adev.sndcard);
+		flush_work(&dev->adev.wq_trigger);
+
+		em28xx_audio_free_urb(dev);
+
+		snd_card_free(dev->adev.sndcard);
+		dev->adev.sndcard = NULL;
+	}
+
+	kref_put(&dev->ref, em28xx_free_device);
+	return 0;
+}
+
+static int em28xx_audio_suspend(struct em28xx *dev)
+{
+	if (!dev)
+		return 0;
+
+	if (dev->usb_audio_type != EM28XX_USB_AUDIO_VENDOR)
+		return 0;
+
+	dev_info(&dev->intf->dev, "Suspending audio extension\n");
+	em28xx_deinit_isoc_audio(dev);
+	atomic_set(&dev->adev.stream_started, 0);
+	return 0;
+}
+
+static int em28xx_audio_resume(struct em28xx *dev)
+{
+	if (!dev)
+		return 0;
+
+	if (dev->usb_audio_type != EM28XX_USB_AUDIO_VENDOR)
+		return 0;
+
+	dev_info(&dev->intf->dev, "Resuming audio extension\n");
+	/* Nothing to do other than schedule_work() ?? */
+	schedule_work(&dev->adev.wq_trigger);
+	return 0;
+}
+
+static struct em28xx_ops audio_ops = {
+	.id   = EM28XX_AUDIO,
+	.name = "Em28xx Audio Extension",
+	.init = em28xx_audio_init,
+	.fini = em28xx_audio_fini,
+	.suspend = em28xx_audio_suspend,
+	.resume = em28xx_audio_resume,
+};
+
+static int __init em28xx_alsa_register(void)
+{
+	return em28xx_register_extension(&audio_ops);
+}
+
+static void __exit em28xx_alsa_unregister(void)
+{
+	em28xx_unregister_extension(&audio_ops);
+}
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Markus Rechberger <mrechberger@gmail.com>");
+MODULE_AUTHOR("Mauro Carvalho Chehab");
+MODULE_DESCRIPTION(DRIVER_DESC " - audio interface");
+MODULE_VERSION(EM28XX_VERSION);
+
+module_init(em28xx_alsa_register);
+module_exit(em28xx_alsa_unregister);
diff --git a/drivers/media/usb/em28xx/em28xx-camera.c b/drivers/media/usb/em28xx/em28xx-camera.c
new file mode 100644
index 0000000..d1e66b5
--- /dev/null
+++ b/drivers/media/usb/em28xx/em28xx-camera.c
@@ -0,0 +1,422 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// em28xx-camera.c - driver for Empia EM25xx/27xx/28xx USB video capture devices
+//
+// Copyright (C) 2009 Mauro Carvalho Chehab <mchehab@kernel.org>
+// Copyright (C) 2013 Frank Schäfer <fschaefer.oss@googlemail.com>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include "em28xx.h"
+
+#include <linux/i2c.h>
+#include <linux/usb.h>
+#include <media/i2c/mt9v011.h>
+#include <media/v4l2-common.h>
+
+/* Possible i2c addresses of Micron sensors */
+static unsigned short micron_sensor_addrs[] = {
+	0xb8 >> 1,   /* MT9V111, MT9V403 */
+	0xba >> 1,   /* MT9M001/011/111/112, MT9V011/012/112, MT9D011 */
+	0x90 >> 1,   /* MT9V012/112, MT9D011 (alternative address) */
+	I2C_CLIENT_END
+};
+
+/* Possible i2c addresses of Omnivision sensors */
+static unsigned short omnivision_sensor_addrs[] = {
+	0x42 >> 1,   /* OV7725, OV7670/60/48 */
+	0x60 >> 1,   /* OV2640, OV9650/53/55 */
+	I2C_CLIENT_END
+};
+
+/* FIXME: Should be replaced by a proper mt9m111 driver */
+static int em28xx_initialize_mt9m111(struct em28xx *dev)
+{
+	int i;
+	unsigned char regs[][3] = {
+		{ 0x0d, 0x00, 0x01, },  /* reset and use defaults */
+		{ 0x0d, 0x00, 0x00, },
+		{ 0x0a, 0x00, 0x21, },
+		{ 0x21, 0x04, 0x00, },  /* full readout spd, no row/col skip */
+	};
+
+	for (i = 0; i < ARRAY_SIZE(regs); i++)
+		i2c_master_send(&dev->i2c_client[dev->def_i2c_bus],
+				&regs[i][0], 3);
+
+	/* FIXME: This won't be creating a sensor at the media graph */
+
+	return 0;
+}
+
+/* FIXME: Should be replaced by a proper mt9m001 driver */
+static int em28xx_initialize_mt9m001(struct em28xx *dev)
+{
+	int i;
+	unsigned char regs[][3] = {
+		{ 0x0d, 0x00, 0x01, },
+		{ 0x0d, 0x00, 0x00, },
+		{ 0x04, 0x05, 0x00, },	/* hres = 1280 */
+		{ 0x03, 0x04, 0x00, },  /* vres = 1024 */
+		{ 0x20, 0x11, 0x00, },
+		{ 0x06, 0x00, 0x10, },
+		{ 0x2b, 0x00, 0x24, },
+		{ 0x2e, 0x00, 0x24, },
+		{ 0x35, 0x00, 0x24, },
+		{ 0x2d, 0x00, 0x20, },
+		{ 0x2c, 0x00, 0x20, },
+		{ 0x09, 0x0a, 0xd4, },
+		{ 0x35, 0x00, 0x57, },
+	};
+
+	for (i = 0; i < ARRAY_SIZE(regs); i++)
+		i2c_master_send(&dev->i2c_client[dev->def_i2c_bus],
+				&regs[i][0], 3);
+
+	/* FIXME: This won't be creating a sensor at the media graph */
+
+	return 0;
+}
+
+/*
+ * Probes Micron sensors with 8 bit address and 16 bit register width
+ */
+static int em28xx_probe_sensor_micron(struct em28xx *dev)
+{
+	int ret, i;
+	char *name;
+	u16 id;
+
+	struct i2c_client *client = &dev->i2c_client[dev->def_i2c_bus];
+
+	dev->em28xx_sensor = EM28XX_NOSENSOR;
+	for (i = 0; micron_sensor_addrs[i] != I2C_CLIENT_END; i++) {
+		client->addr = micron_sensor_addrs[i];
+		/* Read chip ID from register 0x00 */
+		ret = i2c_smbus_read_word_data(client, 0x00); /* assumes LE */
+		if (ret < 0) {
+			if (ret != -ENXIO)
+				dev_err(&dev->intf->dev,
+					"couldn't read from i2c device 0x%02x: error %i\n",
+				       client->addr << 1, ret);
+			continue;
+		}
+		id = swab16(ret); /* LE -> BE */
+		/* Read chip ID from register 0xff */
+		ret = i2c_smbus_read_word_data(client, 0xff);
+		if (ret < 0) {
+			dev_err(&dev->intf->dev,
+				"couldn't read from i2c device 0x%02x: error %i\n",
+				client->addr << 1, ret);
+			continue;
+		}
+		/* Validate chip ID to be sure we have a Micron device */
+		if (id != swab16(ret))
+			continue;
+		/* Check chip ID */
+		switch (id) {
+		case 0x1222:
+			name = "MT9V012"; /* MI370 */ /* 640x480 */
+			break;
+		case 0x1229:
+			name = "MT9V112"; /* 640x480 */
+			break;
+		case 0x1433:
+			name = "MT9M011"; /* 1280x1024 */
+			break;
+		case 0x143a:    /* found in the ECS G200 */
+			name = "MT9M111"; /* MI1310 */ /* 1280x1024 */
+			dev->em28xx_sensor = EM28XX_MT9M111;
+			break;
+		case 0x148c:
+			name = "MT9M112"; /* MI1320 */ /* 1280x1024 */
+			break;
+		case 0x1511:
+			name = "MT9D011"; /* MI2010 */ /* 1600x1200 */
+			break;
+		case 0x8232:
+		case 0x8243:	/* rev B */
+			name = "MT9V011"; /* MI360 */ /* 640x480 */
+			dev->em28xx_sensor = EM28XX_MT9V011;
+			break;
+		case 0x8431:
+			name = "MT9M001"; /* 1280x1024 */
+			dev->em28xx_sensor = EM28XX_MT9M001;
+			break;
+		default:
+			dev_info(&dev->intf->dev,
+				 "unknown Micron sensor detected: 0x%04x\n",
+				 id);
+			return 0;
+		}
+
+		if (dev->em28xx_sensor == EM28XX_NOSENSOR)
+			dev_info(&dev->intf->dev,
+				 "unsupported sensor detected: %s\n", name);
+		else
+			dev_info(&dev->intf->dev,
+				 "sensor %s detected\n", name);
+
+		return 0;
+	}
+
+	return -ENODEV;
+}
+
+/*
+ * Probes Omnivision sensors with 8 bit address and register width
+ */
+static int em28xx_probe_sensor_omnivision(struct em28xx *dev)
+{
+	int ret, i;
+	char *name;
+	u8 reg;
+	u16 id;
+	struct i2c_client *client = &dev->i2c_client[dev->def_i2c_bus];
+
+	dev->em28xx_sensor = EM28XX_NOSENSOR;
+	/*
+	 * NOTE: these devices have the register auto incrementation disabled
+	 * by default, so we have to use single byte reads !
+	 */
+	for (i = 0; omnivision_sensor_addrs[i] != I2C_CLIENT_END; i++) {
+		client->addr = omnivision_sensor_addrs[i];
+		/* Read manufacturer ID from registers 0x1c-0x1d (BE) */
+		reg = 0x1c;
+		ret = i2c_smbus_read_byte_data(client, reg);
+		if (ret < 0) {
+			if (ret != -ENXIO)
+				dev_err(&dev->intf->dev,
+					"couldn't read from i2c device 0x%02x: error %i\n",
+					client->addr << 1, ret);
+			continue;
+		}
+		id = ret << 8;
+		reg = 0x1d;
+		ret = i2c_smbus_read_byte_data(client, reg);
+		if (ret < 0) {
+			dev_err(&dev->intf->dev,
+				"couldn't read from i2c device 0x%02x: error %i\n",
+				client->addr << 1, ret);
+			continue;
+		}
+		id += ret;
+		/* Check manufacturer ID */
+		if (id != 0x7fa2)
+			continue;
+		/* Read product ID from registers 0x0a-0x0b (BE) */
+		reg = 0x0a;
+		ret = i2c_smbus_read_byte_data(client, reg);
+		if (ret < 0) {
+			dev_err(&dev->intf->dev,
+				"couldn't read from i2c device 0x%02x: error %i\n",
+				client->addr << 1, ret);
+			continue;
+		}
+		id = ret << 8;
+		reg = 0x0b;
+		ret = i2c_smbus_read_byte_data(client, reg);
+		if (ret < 0) {
+			dev_err(&dev->intf->dev,
+				"couldn't read from i2c device 0x%02x: error %i\n",
+				client->addr << 1, ret);
+			continue;
+		}
+		id += ret;
+		/* Check product ID */
+		switch (id) {
+		case 0x2642:
+			name = "OV2640";
+			dev->em28xx_sensor = EM28XX_OV2640;
+			break;
+		case 0x7648:
+			name = "OV7648";
+			break;
+		case 0x7660:
+			name = "OV7660";
+			break;
+		case 0x7673:
+			name = "OV7670";
+			break;
+		case 0x7720:
+			name = "OV7720";
+			break;
+		case 0x7721:
+			name = "OV7725";
+			break;
+		case 0x9648: /* Rev 2 */
+		case 0x9649: /* Rev 3 */
+			name = "OV9640";
+			break;
+		case 0x9650:
+		case 0x9652: /* OV9653 */
+			name = "OV9650";
+			break;
+		case 0x9656: /* Rev 4 */
+		case 0x9657: /* Rev 5 */
+			name = "OV9655";
+			break;
+		default:
+			dev_info(&dev->intf->dev,
+				 "unknown OmniVision sensor detected: 0x%04x\n",
+				id);
+			return 0;
+		}
+
+		if (dev->em28xx_sensor == EM28XX_NOSENSOR)
+			dev_info(&dev->intf->dev,
+				 "unsupported sensor detected: %s\n", name);
+		else
+			dev_info(&dev->intf->dev,
+				 "sensor %s detected\n", name);
+
+		return 0;
+	}
+
+	return -ENODEV;
+}
+
+int em28xx_detect_sensor(struct em28xx *dev)
+{
+	int ret;
+
+	ret = em28xx_probe_sensor_micron(dev);
+
+	if (dev->em28xx_sensor == EM28XX_NOSENSOR && ret < 0)
+		ret = em28xx_probe_sensor_omnivision(dev);
+
+	/*
+	 * NOTE: the Windows driver also probes i2c addresses
+	 *       0x22 (Samsung ?) and 0x66 (Kodak ?)
+	 */
+
+	if (dev->em28xx_sensor == EM28XX_NOSENSOR && ret < 0) {
+		dev_info(&dev->intf->dev,
+			 "No sensor detected\n");
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+int em28xx_init_camera(struct em28xx *dev)
+{
+	struct i2c_client *client = &dev->i2c_client[dev->def_i2c_bus];
+	struct i2c_adapter *adap = &dev->i2c_adap[dev->def_i2c_bus];
+	struct em28xx_v4l2 *v4l2 = dev->v4l2;
+
+	switch (dev->em28xx_sensor) {
+	case EM28XX_MT9V011:
+	{
+		struct mt9v011_platform_data pdata;
+		struct i2c_board_info mt9v011_info = {
+			.type = "mt9v011",
+			.addr = client->addr,
+			.platform_data = &pdata,
+		};
+
+		v4l2->sensor_xres = 640;
+		v4l2->sensor_yres = 480;
+
+		/*
+		 * FIXME: mt9v011 uses I2S speed as xtal clk - at least with
+		 * the Silvercrest cam I have here for testing - for higher
+		 * resolutions, a high clock cause horizontal artifacts, so we
+		 * need to use a lower xclk frequency.
+		 * Yet, it would be possible to adjust xclk depending on the
+		 * desired resolution, since this affects directly the
+		 * frame rate.
+		 */
+		dev->board.xclk = EM28XX_XCLK_FREQUENCY_4_3MHZ;
+		em28xx_write_reg(dev, EM28XX_R0F_XCLK, dev->board.xclk);
+		v4l2->sensor_xtal = 4300000;
+		pdata.xtal = v4l2->sensor_xtal;
+		if (NULL ==
+		    v4l2_i2c_new_subdev_board(&v4l2->v4l2_dev, adap,
+					      &mt9v011_info, NULL))
+			return -ENODEV;
+		v4l2->vinmode = EM28XX_VINMODE_RGB8_GRBG;
+		v4l2->vinctl = 0x00;
+
+		break;
+	}
+	case EM28XX_MT9M001:
+		v4l2->sensor_xres = 1280;
+		v4l2->sensor_yres = 1024;
+
+		em28xx_initialize_mt9m001(dev);
+
+		v4l2->vinmode = EM28XX_VINMODE_RGB8_BGGR;
+		v4l2->vinctl = 0x00;
+
+		break;
+	case EM28XX_MT9M111:
+		v4l2->sensor_xres = 640;
+		v4l2->sensor_yres = 512;
+
+		dev->board.xclk = EM28XX_XCLK_FREQUENCY_48MHZ;
+		em28xx_write_reg(dev, EM28XX_R0F_XCLK, dev->board.xclk);
+		em28xx_initialize_mt9m111(dev);
+
+		v4l2->vinmode = EM28XX_VINMODE_YUV422_UYVY;
+		v4l2->vinctl = 0x00;
+
+		break;
+	case EM28XX_OV2640:
+	{
+		struct v4l2_subdev *subdev;
+		struct i2c_board_info ov2640_info = {
+			.type = "ov2640",
+			.flags = I2C_CLIENT_SCCB,
+			.addr = client->addr,
+		};
+		struct v4l2_subdev_format format = {
+			.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+		};
+
+		/*
+		 * FIXME: sensor supports resolutions up to 1600x1200, but
+		 * resolution setting/switching needs to be modified to
+		 * - switch sensor output resolution (including further
+		 *   configuration changes)
+		 * - adjust bridge xclk
+		 * - disable 16 bit (12 bit) output formats on high resolutions
+		 */
+		v4l2->sensor_xres = 640;
+		v4l2->sensor_yres = 480;
+
+		subdev =
+		     v4l2_i2c_new_subdev_board(&v4l2->v4l2_dev, adap,
+					       &ov2640_info, NULL);
+		if (!subdev)
+			return -ENODEV;
+
+		format.format.code = MEDIA_BUS_FMT_YUYV8_2X8;
+		format.format.width = 640;
+		format.format.height = 480;
+		v4l2_subdev_call(subdev, pad, set_fmt, NULL, &format);
+
+		/* NOTE: for UXGA=1600x1200 switch to 12MHz */
+		dev->board.xclk = EM28XX_XCLK_FREQUENCY_24MHZ;
+		em28xx_write_reg(dev, EM28XX_R0F_XCLK, dev->board.xclk);
+		v4l2->vinmode = EM28XX_VINMODE_YUV422_YUYV;
+		v4l2->vinctl = 0x00;
+
+		break;
+	}
+	case EM28XX_NOSENSOR:
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(em28xx_init_camera);
diff --git a/drivers/media/usb/em28xx/em28xx-cards.c b/drivers/media/usb/em28xx/em28xx-cards.c
new file mode 100644
index 0000000..87b887b
--- /dev/null
+++ b/drivers/media/usb/em28xx/em28xx-cards.c
@@ -0,0 +1,4078 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// em28xx-cards.c - driver for Empia EM2800/EM2820/2840 USB
+//		    video capture devices
+//
+// Copyright (C) 2005 Ludovico Cavedon <cavedon@sssup.it>
+//		      Markus Rechberger <mrechberger@gmail.com>
+//		      Mauro Carvalho Chehab <mchehab@kernel.org>
+//		      Sascha Sommer <saschasommer@freenet.de>
+// Copyright (C) 2012 Frank Schäfer <fschaefer.oss@googlemail.com>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include "em28xx.h"
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/usb.h>
+#include <media/tuner.h>
+#include <media/drv-intf/msp3400.h>
+#include <media/i2c/saa7115.h>
+#include <dt-bindings/media/tvp5150.h>
+#include <media/i2c/tvaudio.h>
+#include <media/tveeprom.h>
+#include <media/v4l2-common.h>
+#include <sound/ac97_codec.h>
+
+#define DRIVER_NAME         "em28xx"
+
+static int tuner = -1;
+module_param(tuner, int, 0444);
+MODULE_PARM_DESC(tuner, "tuner type");
+
+static unsigned int disable_ir;
+module_param(disable_ir, int, 0444);
+MODULE_PARM_DESC(disable_ir, "disable infrared remote support");
+
+static unsigned int disable_usb_speed_check;
+module_param(disable_usb_speed_check, int, 0444);
+MODULE_PARM_DESC(disable_usb_speed_check,
+		 "override min bandwidth requirement of 480M bps");
+
+static unsigned int card[]     = {[0 ... (EM28XX_MAXBOARDS - 1)] = -1U };
+module_param_array(card,  int, NULL, 0444);
+MODULE_PARM_DESC(card,     "card type");
+
+static int usb_xfer_mode = -1;
+module_param(usb_xfer_mode, int, 0444);
+MODULE_PARM_DESC(usb_xfer_mode,
+		 "USB transfer mode for frame data (-1 = auto, 0 = prefer isoc, 1 = prefer bulk)");
+
+/* Bitmask marking allocated devices from 0 to EM28XX_MAXBOARDS - 1 */
+static DECLARE_BITMAP(em28xx_devused, EM28XX_MAXBOARDS);
+
+struct em28xx_hash_table {
+	unsigned long hash;
+	unsigned int  model;
+	unsigned int  tuner;
+};
+
+static void em28xx_pre_card_setup(struct em28xx *dev);
+
+/*
+ *  Reset sequences for analog/digital modes
+ */
+
+/* Reset for the most [analog] boards */
+static const struct em28xx_reg_seq default_analog[] = {
+	{EM2820_R08_GPIO_CTRL,	0x6d,   ~EM_GPIO_4,	10},
+	{	-1,		-1,	-1,		-1},
+};
+
+/* Reset for the most [digital] boards */
+static const struct em28xx_reg_seq default_digital[] = {
+	{EM2820_R08_GPIO_CTRL,	0x6e,	~EM_GPIO_4,	10},
+	{	-1,		-1,	-1,		-1},
+};
+
+/* Board :Zolid Hybrid Tv Stick */
+static struct em28xx_reg_seq zolid_tuner[] = {
+	{EM2820_R08_GPIO_CTRL,		0xfd,		0xff,	100},
+	{EM2820_R08_GPIO_CTRL,		0xfe,		0xff,	100},
+	{		-1,					-1,			-1,		 -1},
+};
+
+static struct em28xx_reg_seq zolid_digital[] = {
+	{EM2820_R08_GPIO_CTRL,		0x6a,		0xff,	100},
+	{EM2820_R08_GPIO_CTRL,		0x7a,		0xff,	100},
+	{EM2880_R04_GPO,			0x04,		0xff,	100},
+	{EM2880_R04_GPO,			0x0c,		0xff,	100},
+	{	-1,						-1,			-1,		 -1},
+};
+
+/* Board Hauppauge WinTV HVR 900 analog */
+static const struct em28xx_reg_seq hauppauge_wintv_hvr_900_analog[] = {
+	{EM2820_R08_GPIO_CTRL,	0x2d,	~EM_GPIO_4,	10},
+	{	0x05,		0xff,	0x10,		10},
+	{	-1,		-1,	-1,		-1},
+};
+
+/* Board Hauppauge WinTV HVR 900 digital */
+static const struct em28xx_reg_seq hauppauge_wintv_hvr_900_digital[] = {
+	{EM2820_R08_GPIO_CTRL,	0x2e,	~EM_GPIO_4,	10},
+	{EM2880_R04_GPO,	0x04,	0x0f,		10},
+	{EM2880_R04_GPO,	0x0c,	0x0f,		10},
+	{	-1,		-1,	-1,		-1},
+};
+
+/* Board Hauppauge WinTV HVR 900 (R2) digital */
+static const struct em28xx_reg_seq hauppauge_wintv_hvr_900R2_digital[] = {
+	{EM2820_R08_GPIO_CTRL,	0x2e,	~EM_GPIO_4,	10},
+	{EM2880_R04_GPO,	0x0c,	0x0f,		10},
+	{	-1,		-1,	-1,		-1},
+};
+
+/* Boards - EM2880 MSI DIGIVOX AD and EM2880_BOARD_MSI_DIGIVOX_AD_II */
+static const struct em28xx_reg_seq em2880_msi_digivox_ad_analog[] = {
+	{EM2820_R08_GPIO_CTRL,	0x69,   ~EM_GPIO_4,	10},
+	{	-1,		-1,	-1,		-1},
+};
+
+/* Board - EM2882 Kworld 315U digital */
+static const struct em28xx_reg_seq em2882_kworld_315u_digital[] = {
+	{EM2820_R08_GPIO_CTRL,	0xff,	0xff,		10},
+	{EM2820_R08_GPIO_CTRL,	0xfe,	0xff,		10},
+	{EM2880_R04_GPO,	0x04,	0xff,		10},
+	{EM2880_R04_GPO,	0x0c,	0xff,		10},
+	{EM2820_R08_GPIO_CTRL,	0x7e,	0xff,		10},
+	{	-1,		-1,	-1,		-1},
+};
+
+static const struct em28xx_reg_seq em2882_kworld_315u_tuner_gpio[] = {
+	{EM2880_R04_GPO,	0x08,	0xff,		10},
+	{EM2880_R04_GPO,	0x0c,	0xff,		10},
+	{EM2880_R04_GPO,	0x08,	0xff,		10},
+	{EM2880_R04_GPO,	0x0c,	0xff,		10},
+	{	-1,		-1,	-1,		-1},
+};
+
+static const struct em28xx_reg_seq kworld_330u_analog[] = {
+	{EM2820_R08_GPIO_CTRL,	0x6d,	~EM_GPIO_4,	10},
+	{EM2880_R04_GPO,	0x00,	0xff,		10},
+	{	-1,		-1,	-1,		-1},
+};
+
+static const struct em28xx_reg_seq kworld_330u_digital[] = {
+	{EM2820_R08_GPIO_CTRL,	0x6e,	~EM_GPIO_4,	10},
+	{EM2880_R04_GPO,	0x08,	0xff,		10},
+	{	-1,		-1,	-1,		-1},
+};
+
+/*
+ * Evga inDtube
+ * GPIO0 - Enable digital power (s5h1409) - low to enable
+ * GPIO1 - Enable analog power (tvp5150/emp202) - low to enable
+ * GPIO4 - xc3028 reset
+ * GOP3  - s5h1409 reset
+ */
+static const struct em28xx_reg_seq evga_indtube_analog[] = {
+	{EM2820_R08_GPIO_CTRL,	0x79,   0xff,		60},
+	{	-1,		-1,	-1,		-1},
+};
+
+static const struct em28xx_reg_seq evga_indtube_digital[] = {
+	{EM2820_R08_GPIO_CTRL,	0x7a,	0xff,		 1},
+	{EM2880_R04_GPO,	0x04,	0xff,		10},
+	{EM2880_R04_GPO,	0x0c,	0xff,		 1},
+	{	-1,		-1,	-1,		-1},
+};
+
+/*
+ * KWorld PlusTV 340U, UB435-Q and UB435-Q V2 (ATSC) GPIOs map:
+ * EM_GPIO_0 - currently unknown
+ * EM_GPIO_1 - LED disable/enable (1 = off, 0 = on)
+ * EM_GPIO_2 - currently unknown
+ * EM_GPIO_3 - currently unknown
+ * EM_GPIO_4 - TDA18271HD/C1 tuner (1 = active, 0 = in reset)
+ * EM_GPIO_5 - LGDT3304 ATSC/QAM demod (1 = active, 0 = in reset)
+ * EM_GPIO_6 - currently unknown
+ * EM_GPIO_7 - currently unknown
+ */
+static const struct em28xx_reg_seq kworld_a340_digital[] = {
+	{EM2820_R08_GPIO_CTRL,	0x6d,	~EM_GPIO_4,	10},
+	{	-1,		-1,	-1,		-1},
+};
+
+static const struct em28xx_reg_seq kworld_ub435q_v3_digital[] = {
+	{EM2874_R80_GPIO_P0_CTRL,	0xff,	0xff,	100},
+	{EM2874_R80_GPIO_P0_CTRL,	0xfe,	0xff,	100},
+	{EM2874_R80_GPIO_P0_CTRL,	0xbe,	0xff,	100},
+	{EM2874_R80_GPIO_P0_CTRL,	0xfe,	0xff,	100},
+	{	-1,			-1,	-1,	-1},
+};
+
+/* Pinnacle Hybrid Pro eb1a:2881 */
+static const struct em28xx_reg_seq pinnacle_hybrid_pro_analog[] = {
+	{EM2820_R08_GPIO_CTRL,	0xfd,   ~EM_GPIO_4,	10},
+	{	-1,		-1,	-1,		-1},
+};
+
+static const struct em28xx_reg_seq pinnacle_hybrid_pro_digital[] = {
+	{EM2820_R08_GPIO_CTRL,	0x6e,	~EM_GPIO_4,	10},
+	{EM2880_R04_GPO,	0x04,	0xff,	       100},/* zl10353 reset */
+	{EM2880_R04_GPO,	0x0c,	0xff,		 1},
+	{	-1,		-1,	-1,		-1},
+};
+
+static const struct em28xx_reg_seq terratec_cinergy_USB_XS_FR_analog[] = {
+	{EM2820_R08_GPIO_CTRL,	0x6d,	~EM_GPIO_4,	10},
+	{EM2880_R04_GPO,	0x00,	0xff,		10},
+	{	-1,		-1,	-1,		-1},
+};
+
+static const struct em28xx_reg_seq terratec_cinergy_USB_XS_FR_digital[] = {
+	{EM2820_R08_GPIO_CTRL,	0x6e,	~EM_GPIO_4,	10},
+	{EM2880_R04_GPO,	0x08,	0xff,		10},
+	{	-1,		-1,	-1,		-1},
+};
+
+/*
+ * PCTV HD Mini (80e) GPIOs
+ * 0-5: not used
+ * 6:   demod reset, active low
+ * 7:   LED on, active high
+ */
+static const struct em28xx_reg_seq em2874_pctv_80e_digital[] = {
+	{EM28XX_R06_I2C_CLK,    0x45,   0xff,		  10}, /*400 KHz*/
+	{EM2874_R80_GPIO_P0_CTRL, 0x00,   0xff,		  100},/*Demod reset*/
+	{EM2874_R80_GPIO_P0_CTRL, 0x40,   0xff,		  10},
+	{  -1,			-1,	-1,		  -1},
+};
+
+/*
+ * eb1a:2868 Reddo DVB-C USB TV Box
+ * GPIO4 - CU1216L NIM
+ * Other GPIOs seems to be don't care.
+ */
+static const struct em28xx_reg_seq reddo_dvb_c_usb_box[] = {
+	{EM2820_R08_GPIO_CTRL,	0xfe,	0xff,		10},
+	{EM2820_R08_GPIO_CTRL,	0xde,	0xff,		10},
+	{EM2820_R08_GPIO_CTRL,	0xfe,	0xff,		10},
+	{EM2820_R08_GPIO_CTRL,	0xff,	0xff,		10},
+	{EM2820_R08_GPIO_CTRL,	0x7f,	0xff,		10},
+	{EM2820_R08_GPIO_CTRL,	0x6f,	0xff,		10},
+	{EM2820_R08_GPIO_CTRL,	0xff,	0xff,		10},
+	{	-1,		-1,	-1,		-1},
+};
+
+/* Callback for the most boards */
+static const struct em28xx_reg_seq default_tuner_gpio[] = {
+	{EM2820_R08_GPIO_CTRL,	EM_GPIO_4,	EM_GPIO_4,	10},
+	{EM2820_R08_GPIO_CTRL,	0,		EM_GPIO_4,	10},
+	{EM2820_R08_GPIO_CTRL,	EM_GPIO_4,	EM_GPIO_4,	10},
+	{	-1,		-1,		-1,		-1},
+};
+
+/* Mute/unmute */
+static const struct em28xx_reg_seq compro_unmute_tv_gpio[] = {
+	{EM2820_R08_GPIO_CTRL,	5,	7,	10},
+	{	-1,		-1,	-1,	-1},
+};
+
+static const struct em28xx_reg_seq compro_unmute_svid_gpio[] = {
+	{EM2820_R08_GPIO_CTRL,	4,	7,	10},
+	{	-1,		-1,	-1,	-1},
+};
+
+static const struct em28xx_reg_seq compro_mute_gpio[] = {
+	{EM2820_R08_GPIO_CTRL,	6,	7,	10},
+	{	-1,		-1,	-1,	-1},
+};
+
+/* Terratec AV350 */
+static const struct em28xx_reg_seq terratec_av350_mute_gpio[] = {
+	{EM2820_R08_GPIO_CTRL,	0xff,	0x7f,		10},
+	{	-1,		-1,	-1,		-1},
+};
+
+static const struct em28xx_reg_seq terratec_av350_unmute_gpio[] = {
+	{EM2820_R08_GPIO_CTRL,	0xff,	0xff,		10},
+	{	-1,		-1,	-1,		-1},
+};
+
+static const struct em28xx_reg_seq silvercrest_reg_seq[] = {
+	{EM2820_R08_GPIO_CTRL,	0xff,	0xff,		10},
+	{EM2820_R08_GPIO_CTRL,	0x01,	0xf7,		10},
+	{	-1,		-1,	-1,		-1},
+};
+
+static const struct em28xx_reg_seq vc211a_enable[] = {
+	{EM2820_R08_GPIO_CTRL,	0xff,	0x07,		10},
+	{EM2820_R08_GPIO_CTRL,	0xff,	0x0f,		10},
+	{EM2820_R08_GPIO_CTRL,	0xff,	0x0b,		10},
+	{	-1,		-1,	-1,		-1},
+};
+
+static const struct em28xx_reg_seq dikom_dk300_digital[] = {
+	{EM2820_R08_GPIO_CTRL,	0x6e,	~EM_GPIO_4,	10},
+	{EM2880_R04_GPO,	0x08,	0xff,		10},
+	{	-1,		-1,	-1,		-1},
+};
+
+/* Reset for the most [digital] boards */
+static const struct em28xx_reg_seq leadership_digital[] = {
+	{EM2874_R80_GPIO_P0_CTRL,	0x70,	0xff,	10},
+	{	-1,			-1,	-1,	-1},
+};
+
+static const struct em28xx_reg_seq leadership_reset[] = {
+	{EM2874_R80_GPIO_P0_CTRL,	0xf0,	0xff,	10},
+	{EM2874_R80_GPIO_P0_CTRL,	0xb0,	0xff,	10},
+	{EM2874_R80_GPIO_P0_CTRL,	0xf0,	0xff,	10},
+	{	-1,			-1,	-1,	-1},
+};
+
+/*
+ * 2013:024f PCTV nanoStick T2 290e
+ * GPIO_6 - demod reset
+ * GPIO_7 - LED
+ */
+static const struct em28xx_reg_seq pctv_290e[] = {
+	{EM2874_R80_GPIO_P0_CTRL,	0x00,	0xff,	80},
+	{EM2874_R80_GPIO_P0_CTRL,	0x40,	0xff,	80}, /* GPIO_6 = 1 */
+	{EM2874_R80_GPIO_P0_CTRL,	0xc0,	0xff,	80}, /* GPIO_7 = 1 */
+	{	-1,			-1,	-1,	-1},
+};
+
+#if 0
+static const struct em28xx_reg_seq terratec_h5_gpio[] = {
+	{EM2820_R08_GPIO_CTRL,		0xff,	0xff,	10},
+	{EM2874_R80_GPIO_P0_CTRL,	0xf6,	0xff,	100},
+	{EM2874_R80_GPIO_P0_CTRL,	0xf2,	0xff,	50},
+	{EM2874_R80_GPIO_P0_CTRL,	0xf6,	0xff,	50},
+	{	-1,			-1,	-1,	-1},
+};
+
+static const struct em28xx_reg_seq terratec_h5_digital[] = {
+	{EM2874_R80_GPIO_P0_CTRL,	0xf6,	0xff,	10},
+	{EM2874_R80_GPIO_P0_CTRL,	0xe6,	0xff,	100},
+	{EM2874_R80_GPIO_P0_CTRL,	0xa6,	0xff,	10},
+	{	-1,			-1,	-1,	-1},
+};
+#endif
+
+/*
+ * 2013:024f PCTV DVB-S2 Stick 460e
+ * GPIO_0 - POWER_ON
+ * GPIO_1 - BOOST
+ * GPIO_2 - VUV_LNB (red LED)
+ * GPIO_3 - EXT_12V
+ * GPIO_4 - INT_DEM (DEMOD GPIO_0)
+ * GPIO_5 - INT_LNB
+ * GPIO_6 - RESET_DEM
+ * GPIO_7 - LED (green LED)
+ */
+static const struct em28xx_reg_seq pctv_460e[] = {
+	{EM2874_R80_GPIO_P0_CTRL,	0x01,	0xff,	50},
+	{	0x0d,			0xff,	0xff,	50},
+	{EM2874_R80_GPIO_P0_CTRL,	0x41,	0xff,	50}, /* GPIO_6=1 */
+	{	0x0d,			0x42,	0xff,	50},
+	{EM2874_R80_GPIO_P0_CTRL,	0x61,	0xff,	50}, /* GPIO_5=1 */
+	{	-1,			-1,	-1,	-1},
+};
+
+static const struct em28xx_reg_seq c3tech_digital_duo_digital[] = {
+	{EM2874_R80_GPIO_P0_CTRL,	0xff,	0xff,	10},
+	{EM2874_R80_GPIO_P0_CTRL,	0xfd,	0xff,	10}, /* xc5000 reset */
+	{EM2874_R80_GPIO_P0_CTRL,	0xf9,	0xff,	35},
+	{EM2874_R80_GPIO_P0_CTRL,	0xfd,	0xff,	10},
+	{EM2874_R80_GPIO_P0_CTRL,	0xff,	0xff,	10},
+	{EM2874_R80_GPIO_P0_CTRL,	0xfe,	0xff,	10},
+	{EM2874_R80_GPIO_P0_CTRL,	0xbe,	0xff,	10},
+	{EM2874_R80_GPIO_P0_CTRL,	0xfe,	0xff,	20},
+	{	-1,			-1,	-1,	-1},
+};
+
+/*
+ * 2013:0258 PCTV DVB-S2 Stick (461e)
+ * GPIO 0 = POWER_ON
+ * GPIO 1 = BOOST
+ * GPIO 2 = VUV_LNB (red LED)
+ * GPIO 3 = #EXT_12V
+ * GPIO 4 = INT_DEM
+ * GPIO 5 = INT_LNB
+ * GPIO 6 = #RESET_DEM
+ * GPIO 7 = P07_LED (green LED)
+ */
+static const struct em28xx_reg_seq pctv_461e[] = {
+	{EM2874_R80_GPIO_P0_CTRL,      0x7f, 0xff,    0},
+	{0x0d,                 0xff, 0xff,    0},
+	{EM2874_R80_GPIO_P0_CTRL,      0x3f, 0xff,  100}, /* reset demod */
+	{EM2874_R80_GPIO_P0_CTRL,      0x7f, 0xff,  200}, /* reset demod */
+	{0x0d,                 0x42, 0xff,    0},
+	{EM2874_R80_GPIO_P0_CTRL,      0xeb, 0xff,    0},
+	{EM2874_R5F_TS_ENABLE, 0x84, 0x84,    0}, /* parallel? | null discard */
+	{                  -1,   -1,   -1,   -1},
+};
+
+#if 0
+static const struct em28xx_reg_seq hauppauge_930c_gpio[] = {
+	{EM2874_R80_GPIO_P0_CTRL,	0x6f,	0xff,	10},
+	{EM2874_R80_GPIO_P0_CTRL,	0x4f,	0xff,	10}, /* xc5000 reset */
+	{EM2874_R80_GPIO_P0_CTRL,	0x6f,	0xff,	10},
+	{EM2874_R80_GPIO_P0_CTRL,	0x4f,	0xff,	10},
+	{	-1,			-1,	-1,	-1},
+};
+
+static const struct em28xx_reg_seq hauppauge_930c_digital[] = {
+	{EM2874_R80_GPIO_P0_CTRL,	0xf6,	0xff,	10},
+	{EM2874_R80_GPIO_P0_CTRL,	0xe6,	0xff,	100},
+	{EM2874_R80_GPIO_P0_CTRL,	0xa6,	0xff,	10},
+	{	-1,			-1,	-1,	-1},
+};
+#endif
+
+/*
+ * 1b80:e425 MaxMedia UB425-TC
+ * 1b80:e1cc Delock 61959
+ * GPIO_6 - demod reset, 0=active
+ * GPIO_7 - LED, 0=active
+ */
+static const struct em28xx_reg_seq maxmedia_ub425_tc[] = {
+	{EM2874_R80_GPIO_P0_CTRL,	0x83,	0xff,	100},
+	{EM2874_R80_GPIO_P0_CTRL,	0xc3,	0xff,	100}, /* GPIO_6 = 1 */
+	{EM2874_R80_GPIO_P0_CTRL,	0x43,	0xff,	000}, /* GPIO_7 = 0 */
+	{	-1,			-1,	-1,	-1},
+};
+
+/*
+ * 2304:0242 PCTV QuatroStick (510e)
+ * GPIO_2: decoder reset, 0=active
+ * GPIO_4: decoder suspend, 0=active
+ * GPIO_6: demod reset, 0=active
+ * GPIO_7: LED, 1=active
+ */
+static const struct em28xx_reg_seq pctv_510e[] = {
+	{EM2874_R80_GPIO_P0_CTRL,	0x10,	0xff,	100},
+	{EM2874_R80_GPIO_P0_CTRL,	0x14,	0xff,	100}, /* GPIO_2 = 1 */
+	{EM2874_R80_GPIO_P0_CTRL,	0x54,	0xff,	050}, /* GPIO_6 = 1 */
+	{	-1,			-1,	-1,	-1},
+};
+
+/*
+ * 2013:0251 PCTV QuatroStick nano (520e)
+ * GPIO_2: decoder reset, 0=active
+ * GPIO_4: decoder suspend, 0=active
+ * GPIO_6: demod reset, 0=active
+ * GPIO_7: LED, 1=active
+ */
+static const struct em28xx_reg_seq pctv_520e[] = {
+	{EM2874_R80_GPIO_P0_CTRL,	0x10,	0xff,	100},
+	{EM2874_R80_GPIO_P0_CTRL,	0x14,	0xff,	100}, /* GPIO_2 = 1 */
+	{EM2874_R80_GPIO_P0_CTRL,	0x54,	0xff,	050}, /* GPIO_6 = 1 */
+	{EM2874_R80_GPIO_P0_CTRL,	0xd4,	0xff,	000}, /* GPIO_7 = 1 */
+	{	-1,			-1,	-1,	-1},
+};
+
+/*
+ * 1ae7:9003/9004 SpeedLink Vicious And Devine Laplace webcam
+ * reg 0x80/0x84:
+ * GPIO_0: capturing LED, 0=on, 1=off
+ * GPIO_2: AV mute button, 0=pressed, 1=unpressed
+ * GPIO 3: illumination button, 0=pressed, 1=unpressed
+ * GPIO_6: illumination/flash LED, 0=on, 1=off
+ * reg 0x81/0x85:
+ * GPIO_7: snapshot button, 0=pressed, 1=unpressed
+ */
+static const struct em28xx_reg_seq speedlink_vad_laplace_reg_seq[] = {
+	{EM2820_R08_GPIO_CTRL,		0xf7,	0xff,	10},
+	{EM2874_R80_GPIO_P0_CTRL,	0xff,	0xb2,	10},
+	{	-1,			-1,	-1,	-1},
+};
+
+static const struct em28xx_reg_seq pctv_292e[] = {
+	{EM2874_R80_GPIO_P0_CTRL,      0xff, 0xff,      0},
+	{0x0d,                         0xff, 0xff,    950},
+	{EM2874_R80_GPIO_P0_CTRL,      0xbd, 0xff,    100},
+	{EM2874_R80_GPIO_P0_CTRL,      0xfd, 0xff,    410},
+	{EM2874_R80_GPIO_P0_CTRL,      0x7d, 0xff,    300},
+	{EM2874_R80_GPIO_P0_CTRL,      0x7c, 0xff,     60},
+	{0x0d,                         0x42, 0xff,     50},
+	{EM2874_R5F_TS_ENABLE,         0x85, 0xff,      0},
+	{-1,                             -1,   -1,     -1},
+};
+
+static const struct em28xx_reg_seq terratec_t2_stick_hd[] = {
+	{EM2874_R80_GPIO_P0_CTRL,	0xff,	0xff,	0},
+	{0x0d,				0xff,	0xff,	600},
+	{EM2874_R80_GPIO_P0_CTRL,	0xfc,	0xff,	10},
+	{EM2874_R80_GPIO_P0_CTRL,	0xbc,	0xff,	100},
+	{EM2874_R80_GPIO_P0_CTRL,	0xfc,	0xff,	100},
+	{EM2874_R80_GPIO_P0_CTRL,	0x00,	0xff,	300},
+	{EM2874_R80_GPIO_P0_CTRL,	0xf8,	0xff,	100},
+	{EM2874_R80_GPIO_P0_CTRL,	0xfc,	0xff,	300},
+	{0x0d,				0x42,	0xff,	1000},
+	{EM2874_R5F_TS_ENABLE,		0x85,	0xff,	0},
+	{-1,                             -1,   -1,     -1},
+};
+
+static const struct em28xx_reg_seq plex_px_bcud[] = {
+	{EM2874_R80_GPIO_P0_CTRL,	0xff,	0xff,	0},
+	{0x0d,				0xff,	0xff,	0},
+	{EM2874_R50_IR_CONFIG,		0x01,	0xff,	0},
+	{EM28XX_R06_I2C_CLK,		0x40,	0xff,	0},
+	{EM2874_R80_GPIO_P0_CTRL,	0xfd,	0xff,	100},
+	{EM28XX_R12_VINENABLE,		0x20,	0x20,	0},
+	{0x0d,				0x42,	0xff,	1000},
+	{EM2874_R80_GPIO_P0_CTRL,	0xfc,	0xff,	10},
+	{EM2874_R80_GPIO_P0_CTRL,	0xfd,	0xff,	10},
+	{0x73,				0xfd,	0xff,	100},
+	{-1,				-1,	-1,	-1},
+};
+
+/*
+ * 2040:0265 Hauppauge WinTV-dualHD DVB Isoc
+ * 2040:8265 Hauppauge WinTV-dualHD DVB Bulk
+ * 2040:026d Hauppauge WinTV-dualHD ATSC/QAM Isoc
+ * 2040:826d Hauppauge WinTV-dualHD ATSC/QAM Bulk
+ * reg 0x80/0x84:
+ * GPIO_0: Yellow LED tuner 1, 0=on, 1=off
+ * GPIO_1: Green LED tuner 1, 0=on, 1=off
+ * GPIO_2: Yellow LED tuner 2, 0=on, 1=off
+ * GPIO_3: Green LED tuner 2, 0=on, 1=off
+ * GPIO_5: Reset #2, 0=active
+ * GPIO_6: Reset #1, 0=active
+ */
+static const struct em28xx_reg_seq hauppauge_dualhd_dvb[] = {
+	{EM2874_R80_GPIO_P0_CTRL,      0xff, 0xff,      0},
+	{0x0d,                         0xff, 0xff,    200},
+	{0x50,                         0x04, 0xff,    300},
+	{EM2874_R80_GPIO_P0_CTRL,      0xbf, 0xff,    100}, /* demod 1 reset */
+	{EM2874_R80_GPIO_P0_CTRL,      0xff, 0xff,    100},
+	{EM2874_R80_GPIO_P0_CTRL,      0xdf, 0xff,    100}, /* demod 2 reset */
+	{EM2874_R80_GPIO_P0_CTRL,      0xff, 0xff,    100},
+	{EM2874_R5F_TS_ENABLE,         0x00, 0xff,     50}, /* disable TS filters */
+	{EM2874_R5D_TS1_PKT_SIZE,      0x05, 0xff,     50},
+	{EM2874_R5E_TS2_PKT_SIZE,      0x05, 0xff,     50},
+	{-1,                             -1,   -1,     -1},
+};
+
+/*
+ *  Button definitions
+ */
+static const struct em28xx_button std_snapshot_button[] = {
+	{
+		.role         = EM28XX_BUTTON_SNAPSHOT,
+		.reg_r        = EM28XX_R0C_USBSUSP,
+		.reg_clearing = EM28XX_R0C_USBSUSP,
+		.mask         = EM28XX_R0C_USBSUSP_SNAPSHOT,
+		.inverted     = 0,
+	},
+	{-1, 0, 0, 0, 0},
+};
+
+static const struct em28xx_button speedlink_vad_laplace_buttons[] = {
+	{
+		.role     = EM28XX_BUTTON_SNAPSHOT,
+		.reg_r    = EM2874_R85_GPIO_P1_STATE,
+		.mask     = 0x80,
+		.inverted = 1,
+	},
+	{
+		.role     = EM28XX_BUTTON_ILLUMINATION,
+		.reg_r    = EM2874_R84_GPIO_P0_STATE,
+		.mask     = 0x08,
+		.inverted = 1,
+	},
+	{-1, 0, 0, 0, 0},
+};
+
+/*
+ *  LED definitions
+ */
+static struct em28xx_led speedlink_vad_laplace_leds[] = {
+	{
+		.role      = EM28XX_LED_ANALOG_CAPTURING,
+		.gpio_reg  = EM2874_R80_GPIO_P0_CTRL,
+		.gpio_mask = 0x01,
+		.inverted  = 1,
+	},
+	{
+		.role      = EM28XX_LED_ILLUMINATION,
+		.gpio_reg  = EM2874_R80_GPIO_P0_CTRL,
+		.gpio_mask = 0x40,
+		.inverted  = 1,
+	},
+	{-1, 0, 0, 0},
+};
+
+static struct em28xx_led kworld_ub435q_v3_leds[] = {
+	{
+		.role      = EM28XX_LED_DIGITAL_CAPTURING,
+		.gpio_reg  = EM2874_R80_GPIO_P0_CTRL,
+		.gpio_mask = 0x80,
+		.inverted  = 1,
+	},
+	{-1, 0, 0, 0},
+};
+
+static struct em28xx_led pctv_80e_leds[] = {
+	{
+		.role      = EM28XX_LED_DIGITAL_CAPTURING,
+		.gpio_reg  = EM2874_R80_GPIO_P0_CTRL,
+		.gpio_mask = 0x80,
+		.inverted  = 0,
+	},
+	{-1, 0, 0, 0},
+};
+
+static struct em28xx_led terratec_grabby_leds[] = {
+	{
+		.role      = EM28XX_LED_ANALOG_CAPTURING,
+		.gpio_reg  = EM2820_R08_GPIO_CTRL,
+		.gpio_mask = EM_GPIO_3,
+		.inverted  = 1,
+	},
+	{-1, 0, 0, 0},
+};
+
+static struct em28xx_led hauppauge_dualhd_leds[] = {
+	{
+		.role      = EM28XX_LED_DIGITAL_CAPTURING,
+		.gpio_reg  = EM2874_R80_GPIO_P0_CTRL,
+		.gpio_mask = EM_GPIO_1,
+		.inverted  = 1,
+	},
+	{
+		.role      = EM28XX_LED_DIGITAL_CAPTURING_TS2,
+		.gpio_reg  = EM2874_R80_GPIO_P0_CTRL,
+		.gpio_mask = EM_GPIO_3,
+		.inverted  = 1,
+	},
+	{-1, 0, 0, 0},
+};
+
+/*
+ *  Board definitions
+ */
+const struct em28xx_board em28xx_boards[] = {
+	[EM2750_BOARD_UNKNOWN] = {
+		.name          = "EM2710/EM2750/EM2751 webcam grabber",
+		.xclk          = EM28XX_XCLK_FREQUENCY_20MHZ,
+		.tuner_type    = TUNER_ABSENT,
+		.is_webcam     = 1,
+		.input         = { {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = 0,
+			.amux     = EM28XX_AMUX_VIDEO,
+			.gpio     = silvercrest_reg_seq,
+		} },
+	},
+	[EM2800_BOARD_UNKNOWN] = {
+		.name         = "Unknown EM2800 video grabber",
+		.is_em2800    = 1,
+		.tda9887_conf = TDA9887_PRESENT,
+		.decoder      = EM28XX_SAA711X,
+		.tuner_type   = TUNER_ABSENT,
+		.input        = { {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2820_BOARD_UNKNOWN] = {
+		.name          = "Unknown EM2750/28xx video grabber",
+		.tuner_type    = TUNER_ABSENT,
+		.is_webcam     = 1,	/* To enable sensor probe */
+	},
+	[EM2882_BOARD_ZOLID_HYBRID_TV_STICK] = {
+		.name			= ":ZOLID HYBRID TV STICK",
+		.tuner_type		= TUNER_XC2028,
+		.tuner_gpio		= zolid_tuner,
+		.decoder		= EM28XX_TVP5150,
+		.xclk			= EM28XX_XCLK_FREQUENCY_12MHZ,
+		.mts_firmware	= 1,
+		.has_dvb		= 1,
+		.dvb_gpio		= zolid_digital,
+	},
+	[EM2750_BOARD_DLCW_130] = {
+		/* Beijing Huaqi Information Digital Technology Co., Ltd */
+		.name          = "Huaqi DLCW-130",
+		.valid         = EM28XX_BOARD_NOT_VALIDATED,
+		.xclk          = EM28XX_XCLK_FREQUENCY_48MHZ,
+		.tuner_type    = TUNER_ABSENT,
+		.is_webcam     = 1,
+		.input         = { {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = 0,
+			.amux     = EM28XX_AMUX_VIDEO,
+		} },
+	},
+	[EM2820_BOARD_KWORLD_PVRTV2800RF] = {
+		.name         = "Kworld PVR TV 2800 RF",
+		.tuner_type   = TUNER_TEMIC_PAL,
+		.tda9887_conf = TDA9887_PRESENT,
+		.decoder      = EM28XX_SAA711X,
+		.input        = { {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2820_BOARD_GADMEI_TVR200] = {
+		.name         = "Gadmei TVR200",
+		.tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+		.tda9887_conf = TDA9887_PRESENT,
+		.decoder      = EM28XX_SAA711X,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2820_BOARD_TERRATEC_CINERGY_250] = {
+		.name         = "Terratec Cinergy 250 USB",
+		.tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+		.has_ir_i2c   = 1,
+		.tda9887_conf = TDA9887_PRESENT,
+		.decoder      = EM28XX_SAA711X,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = EM28XX_AMUX_VIDEO,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2820_BOARD_PINNACLE_USB_2] = {
+		.name         = "Pinnacle PCTV USB 2",
+		.tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+		.has_ir_i2c   = 1,
+		.tda9887_conf = TDA9887_PRESENT,
+		.decoder      = EM28XX_SAA711X,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = EM28XX_AMUX_VIDEO,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2820_BOARD_HAUPPAUGE_WINTV_USB_2] = {
+		.name         = "Hauppauge WinTV USB 2",
+		.tuner_type   = TUNER_PHILIPS_FM1236_MK3,
+		.tda9887_conf = TDA9887_PRESENT |
+				TDA9887_PORT1_ACTIVE |
+				TDA9887_PORT2_ACTIVE,
+		.decoder      = EM28XX_TVP5150,
+		.has_msp34xx  = 1,
+		.has_ir_i2c   = 1,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = MSP_INPUT_DEFAULT,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = MSP_INPUT(MSP_IN_SCART1, MSP_IN_TUNER1,
+					MSP_DSP_IN_SCART, MSP_DSP_IN_SCART),
+		} },
+	},
+	[EM2820_BOARD_DLINK_USB_TV] = {
+		.name         = "D-Link DUB-T210 TV Tuner",
+		.valid        = EM28XX_BOARD_NOT_VALIDATED,
+		.tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+		.tda9887_conf = TDA9887_PRESENT,
+		.decoder      = EM28XX_SAA711X,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2820_BOARD_HERCULES_SMART_TV_USB2] = {
+		.name         = "Hercules Smart TV USB 2.0",
+		.valid        = EM28XX_BOARD_NOT_VALIDATED,
+		.tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+		.tda9887_conf = TDA9887_PRESENT,
+		.decoder      = EM28XX_SAA711X,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2820_BOARD_PINNACLE_USB_2_FM1216ME] = {
+		.name         = "Pinnacle PCTV USB 2 (Philips FM1216ME)",
+		.valid        = EM28XX_BOARD_NOT_VALIDATED,
+		.tuner_type   = TUNER_PHILIPS_FM1216ME_MK3,
+		.tda9887_conf = TDA9887_PRESENT,
+		.decoder      = EM28XX_SAA711X,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = EM28XX_AMUX_VIDEO,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2820_BOARD_GADMEI_UTV310] = {
+		.name         = "Gadmei UTV310",
+		.valid        = EM28XX_BOARD_NOT_VALIDATED,
+		.tuner_type   = TUNER_TNF_5335MF,
+		.tda9887_conf = TDA9887_PRESENT,
+		.decoder      = EM28XX_SAA711X,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2820_BOARD_LEADTEK_WINFAST_USBII_DELUXE] = {
+		.name         = "Leadtek Winfast USB II Deluxe",
+		.valid        = EM28XX_BOARD_NOT_VALIDATED,
+		.tuner_type   = TUNER_PHILIPS_FM1216ME_MK3,
+		.has_ir_i2c   = 1,
+		.tvaudio_addr = 0x58,
+		.tda9887_conf = TDA9887_PRESENT |
+				TDA9887_PORT2_ACTIVE |
+				TDA9887_QSS,
+		.decoder      = EM28XX_SAA711X,
+		.adecoder     = EM28XX_TVAUDIO,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE4,
+			.amux     = EM28XX_AMUX_AUX,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = SAA7115_COMPOSITE5,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+			.radio	  = {
+			.type     = EM28XX_RADIO,
+			.amux     = EM28XX_AMUX_AUX,
+			}
+	},
+	[EM2820_BOARD_VIDEOLOGY_20K14XUSB] = {
+		.name         = "Videology 20K14XUSB USB2.0",
+		.valid        = EM28XX_BOARD_NOT_VALIDATED,
+		.tuner_type   = TUNER_ABSENT,
+		.is_webcam    = 1,
+		.input        = { {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = 0,
+			.amux     = EM28XX_AMUX_VIDEO,
+		} },
+	},
+	[EM2820_BOARD_SILVERCREST_WEBCAM] = {
+		.name         = "Silvercrest Webcam 1.3mpix",
+		.tuner_type   = TUNER_ABSENT,
+		.is_webcam    = 1,
+		.input        = { {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = 0,
+			.amux     = EM28XX_AMUX_VIDEO,
+			.gpio     = silvercrest_reg_seq,
+		} },
+	},
+	[EM2821_BOARD_SUPERCOMP_USB_2] = {
+		.name         = "Supercomp USB 2.0 TV",
+		.valid        = EM28XX_BOARD_NOT_VALIDATED,
+		.tuner_type   = TUNER_PHILIPS_FM1236_MK3,
+		.tda9887_conf = TDA9887_PRESENT |
+				TDA9887_PORT1_ACTIVE |
+				TDA9887_PORT2_ACTIVE,
+		.decoder      = EM28XX_SAA711X,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2821_BOARD_USBGEAR_VD204] = {
+		.name         = "Usbgear VD204v9",
+		.valid        = EM28XX_BOARD_NOT_VALIDATED,
+		.tuner_type   = TUNER_ABSENT,	/* Capture only device */
+		.decoder      = EM28XX_SAA711X,
+		.input        = { {
+			.type  = EM28XX_VMUX_COMPOSITE,
+			.vmux  = SAA7115_COMPOSITE0,
+			.amux  = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type  = EM28XX_VMUX_SVIDEO,
+			.vmux  = SAA7115_SVIDEO3,
+			.amux  = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2860_BOARD_NETGMBH_CAM] = {
+		/* Beijing Huaqi Information Digital Technology Co., Ltd */
+		.name         = "NetGMBH Cam",
+		.valid        = EM28XX_BOARD_NOT_VALIDATED,
+		.tuner_type   = TUNER_ABSENT,
+		.is_webcam    = 1,
+		.input        = { {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = 0,
+			.amux     = EM28XX_AMUX_VIDEO,
+		} },
+	},
+	[EM2860_BOARD_TYPHOON_DVD_MAKER] = {
+		.name         = "Typhoon DVD Maker",
+		.decoder      = EM28XX_SAA711X,
+		.tuner_type   = TUNER_ABSENT,	/* Capture only device */
+		.input        = { {
+			.type  = EM28XX_VMUX_COMPOSITE,
+			.vmux  = SAA7115_COMPOSITE0,
+			.amux  = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type  = EM28XX_VMUX_SVIDEO,
+			.vmux  = SAA7115_SVIDEO3,
+			.amux  = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2860_BOARD_GADMEI_UTV330] = {
+		.name         = "Gadmei UTV330",
+		.valid        = EM28XX_BOARD_NOT_VALIDATED,
+		.tuner_type   = TUNER_TNF_5335MF,
+		.tda9887_conf = TDA9887_PRESENT,
+		.decoder      = EM28XX_SAA711X,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = EM28XX_AMUX_VIDEO,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2861_BOARD_GADMEI_UTV330PLUS] = {
+		.name         = "Gadmei UTV330+",
+		.tuner_type   = TUNER_TNF_5335MF,
+		.tda9887_conf = TDA9887_PRESENT,
+		.ir_codes     = RC_MAP_GADMEI_RM008Z,
+		.decoder      = EM28XX_SAA711X,
+		.xclk         = EM28XX_XCLK_FREQUENCY_12MHZ,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = EM28XX_AMUX_VIDEO,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2860_BOARD_TERRATEC_HYBRID_XS] = {
+		.name         = "Terratec Cinergy A Hybrid XS",
+		.valid        = EM28XX_BOARD_NOT_VALIDATED,
+		.tuner_type   = TUNER_XC2028,
+		.tuner_gpio   = default_tuner_gpio,
+		.decoder      = EM28XX_TVP5150,
+
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		} },
+	},
+	[EM2861_BOARD_KWORLD_PVRTV_300U] = {
+		.name	      = "KWorld PVRTV 300U",
+		.valid        = EM28XX_BOARD_NOT_VALIDATED,
+		.tuner_type   = TUNER_XC2028,
+		.tuner_gpio   = default_tuner_gpio,
+		.decoder      = EM28XX_TVP5150,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2861_BOARD_YAKUMO_MOVIE_MIXER] = {
+		.name          = "Yakumo MovieMixer",
+		.tuner_type    = TUNER_ABSENT,	/* Capture only device */
+		.decoder       = EM28XX_TVP5150,
+		.input         = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2860_BOARD_TVP5150_REFERENCE_DESIGN] = {
+		.name          = "EM2860/TVP5150 Reference Design",
+		.tuner_type    = TUNER_ABSENT,	/* Capture only device */
+		.decoder       = EM28XX_TVP5150,
+		.input         = { {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2861_BOARD_PLEXTOR_PX_TV100U] = {
+		.name         = "Plextor ConvertX PX-TV100U",
+		.tuner_type   = TUNER_TNF_5335MF,
+		.xclk         = EM28XX_XCLK_I2S_MSB_TIMING |
+				EM28XX_XCLK_FREQUENCY_12MHZ,
+		.tda9887_conf = TDA9887_PRESENT,
+		.decoder      = EM28XX_TVP5150,
+		.has_msp34xx  = 1,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = pinnacle_hybrid_pro_analog,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = pinnacle_hybrid_pro_analog,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = pinnacle_hybrid_pro_analog,
+		} },
+	},
+
+	/* Those boards with em2870 are DVB Only*/
+
+	[EM2870_BOARD_TERRATEC_XS] = {
+		.name         = "Terratec Cinergy T XS",
+		.valid        = EM28XX_BOARD_NOT_VALIDATED,
+		.tuner_type   = TUNER_XC2028,
+		.tuner_gpio   = default_tuner_gpio,
+	},
+	[EM2870_BOARD_TERRATEC_XS_MT2060] = {
+		.name         = "Terratec Cinergy T XS (MT2060)",
+		.xclk         = EM28XX_XCLK_IR_RC5_MODE |
+				EM28XX_XCLK_FREQUENCY_12MHZ,
+		.i2c_speed    = EM28XX_I2C_CLK_WAIT_ENABLE,
+		.tuner_type   = TUNER_ABSENT, /* MT2060 */
+		.has_dvb      = 1,
+		.tuner_gpio   = default_tuner_gpio,
+	},
+	[EM2870_BOARD_KWORLD_350U] = {
+		.name         = "Kworld 350 U DVB-T",
+		.valid        = EM28XX_BOARD_NOT_VALIDATED,
+		.tuner_type   = TUNER_XC2028,
+		.tuner_gpio   = default_tuner_gpio,
+	},
+	[EM2870_BOARD_KWORLD_355U] = {
+		.name         = "Kworld 355 U DVB-T",
+		.valid        = EM28XX_BOARD_NOT_VALIDATED,
+		.tuner_type   = TUNER_ABSENT,
+		.tuner_gpio   = default_tuner_gpio,
+		.has_dvb      = 1,
+		.dvb_gpio     = default_digital,
+	},
+	[EM2870_BOARD_PINNACLE_PCTV_DVB] = {
+		.name         = "Pinnacle PCTV DVB-T",
+		.valid        = EM28XX_BOARD_NOT_VALIDATED,
+		.tuner_type   = TUNER_ABSENT, /* MT2060 */
+		/* djh - I have serious doubts this is right... */
+		.xclk         = EM28XX_XCLK_IR_RC5_MODE |
+				EM28XX_XCLK_FREQUENCY_10MHZ,
+	},
+	[EM2870_BOARD_COMPRO_VIDEOMATE] = {
+		.name         = "Compro, VideoMate U3",
+		.valid        = EM28XX_BOARD_NOT_VALIDATED,
+		.tuner_type   = TUNER_ABSENT, /* MT2060 */
+	},
+
+	[EM2880_BOARD_TERRATEC_HYBRID_XS_FR] = {
+		.name         = "Terratec Hybrid XS Secam",
+		.has_msp34xx  = 1,
+		.tuner_type   = TUNER_XC2028,
+		.tuner_gpio   = default_tuner_gpio,
+		.decoder      = EM28XX_TVP5150,
+		.has_dvb      = 1,
+		.dvb_gpio     = terratec_cinergy_USB_XS_FR_digital,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+			.gpio     = terratec_cinergy_USB_XS_FR_analog,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = terratec_cinergy_USB_XS_FR_analog,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = terratec_cinergy_USB_XS_FR_analog,
+		} },
+	},
+	[EM2884_BOARD_TERRATEC_H5] = {
+		.name         = "Terratec Cinergy H5",
+		.has_dvb      = 1,
+#if 0
+		.tuner_type   = TUNER_PHILIPS_TDA8290,
+		.tuner_addr   = 0x41,
+		.dvb_gpio     = terratec_h5_digital, /* FIXME: probably wrong */
+		.tuner_gpio   = terratec_h5_gpio,
+#else
+		.tuner_type   = TUNER_ABSENT,
+#endif
+		.def_i2c_bus  = 1,
+		.i2c_speed    = EM28XX_I2C_CLK_WAIT_ENABLE |
+				EM28XX_I2C_FREQ_400_KHZ,
+	},
+	[EM2884_BOARD_TERRATEC_H6] = {
+		.name         = "Terratec Cinergy H6 rev. 2",
+		.has_dvb      = 1,
+		.ir_codes     = RC_MAP_NEC_TERRATEC_CINERGY_XS,
+#if 0
+		.tuner_type   = TUNER_PHILIPS_TDA8290,
+		.tuner_addr   = 0x41,
+		.dvb_gpio     = terratec_h5_digital, /* FIXME: probably wrong */
+		.tuner_gpio   = terratec_h5_gpio,
+#else
+		.tuner_type   = TUNER_ABSENT,
+#endif
+		.def_i2c_bus  = 1,
+		.i2c_speed    = EM28XX_I2C_CLK_WAIT_ENABLE |
+				EM28XX_I2C_FREQ_400_KHZ,
+	},
+	[EM2884_BOARD_HAUPPAUGE_WINTV_HVR_930C] = {
+		.name         = "Hauppauge WinTV HVR 930C",
+		.has_dvb      = 1,
+#if 0 /* FIXME: Add analog support */
+		.tuner_type   = TUNER_XC5000,
+		.tuner_addr   = 0x41,
+		.dvb_gpio     = hauppauge_930c_digital,
+		.tuner_gpio   = hauppauge_930c_gpio,
+#else
+		.tuner_type   = TUNER_ABSENT,
+#endif
+		.ir_codes     = RC_MAP_HAUPPAUGE,
+		.def_i2c_bus  = 1,
+		.i2c_speed    = EM28XX_I2C_CLK_WAIT_ENABLE |
+				EM28XX_I2C_FREQ_400_KHZ,
+	},
+	[EM2884_BOARD_C3TECH_DIGITAL_DUO] = {
+		.name         = "C3 Tech Digital Duo HDTV/SDTV USB",
+		.has_dvb      = 1,
+		/* FIXME: Add analog support - need a saa7136 driver */
+		.tuner_type = TUNER_ABSENT,	/* Digital-only TDA18271HD */
+		.ir_codes     = RC_MAP_EMPTY,
+		.def_i2c_bus  = 1,
+		.i2c_speed    = EM28XX_I2C_CLK_WAIT_ENABLE,
+		.dvb_gpio     = c3tech_digital_duo_digital,
+	},
+	[EM2884_BOARD_CINERGY_HTC_STICK] = {
+		.name         = "Terratec Cinergy HTC Stick",
+		.has_dvb      = 1,
+		.ir_codes     = RC_MAP_NEC_TERRATEC_CINERGY_XS,
+		.tuner_type   = TUNER_ABSENT,
+		.def_i2c_bus  = 1,
+		.i2c_speed    = EM28XX_I2C_CLK_WAIT_ENABLE |
+				EM28XX_I2C_FREQ_400_KHZ,
+	},
+	[EM2884_BOARD_ELGATO_EYETV_HYBRID_2008] = {
+		.name         = "Elgato EyeTV Hybrid 2008 INT",
+		.has_dvb      = 1,
+		.ir_codes     = RC_MAP_NEC_TERRATEC_CINERGY_XS,
+		.tuner_type   = TUNER_ABSENT,
+		.def_i2c_bus  = 1,
+		.i2c_speed    = EM28XX_I2C_CLK_WAIT_ENABLE |
+				EM28XX_I2C_FREQ_400_KHZ,
+	},
+	[EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900] = {
+		.name         = "Hauppauge WinTV HVR 900",
+		.tda9887_conf = TDA9887_PRESENT,
+		.tuner_type   = TUNER_XC2028,
+		.tuner_gpio   = default_tuner_gpio,
+		.mts_firmware = 1,
+		.has_dvb      = 1,
+		.dvb_gpio     = hauppauge_wintv_hvr_900_digital,
+		.ir_codes     = RC_MAP_HAUPPAUGE,
+		.decoder      = EM28XX_TVP5150,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		} },
+	},
+	[EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2] = {
+		.name         = "Hauppauge WinTV HVR 900 (R2)",
+		.tda9887_conf = TDA9887_PRESENT,
+		.tuner_type   = TUNER_XC2028,
+		.tuner_gpio   = default_tuner_gpio,
+		.mts_firmware = 1,
+		.has_dvb      = 1,
+		.dvb_gpio     = hauppauge_wintv_hvr_900R2_digital,
+		.ir_codes     = RC_MAP_HAUPPAUGE,
+		.decoder      = EM28XX_TVP5150,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		} },
+	},
+	[EM2883_BOARD_HAUPPAUGE_WINTV_HVR_850] = {
+		.name           = "Hauppauge WinTV HVR 850",
+		.tuner_type     = TUNER_XC2028,
+		.tuner_gpio     = default_tuner_gpio,
+		.mts_firmware   = 1,
+		.has_dvb        = 1,
+		.dvb_gpio       = hauppauge_wintv_hvr_900_digital,
+		.ir_codes       = RC_MAP_HAUPPAUGE,
+		.decoder        = EM28XX_TVP5150,
+		.input          = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		} },
+	},
+	[EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950] = {
+		.name           = "Hauppauge WinTV HVR 950",
+		.tuner_type     = TUNER_XC2028,
+		.tuner_gpio     = default_tuner_gpio,
+		.mts_firmware   = 1,
+		.has_dvb        = 1,
+		.dvb_gpio       = hauppauge_wintv_hvr_900_digital,
+		.ir_codes       = RC_MAP_HAUPPAUGE,
+		.decoder        = EM28XX_TVP5150,
+		.input          = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		} },
+	},
+	[EM2880_BOARD_PINNACLE_PCTV_HD_PRO] = {
+		.name           = "Pinnacle PCTV HD Pro Stick",
+		.tuner_type     = TUNER_XC2028,
+		.tuner_gpio   = default_tuner_gpio,
+		.mts_firmware   = 1,
+		.has_dvb        = 1,
+		.dvb_gpio       = hauppauge_wintv_hvr_900_digital,
+		.ir_codes       = RC_MAP_PINNACLE_PCTV_HD,
+		.decoder        = EM28XX_TVP5150,
+		.input          = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		} },
+	},
+	[EM2880_BOARD_AMD_ATI_TV_WONDER_HD_600] = {
+		.name           = "AMD ATI TV Wonder HD 600",
+		.tuner_type     = TUNER_XC2028,
+		.tuner_gpio     = default_tuner_gpio,
+		.mts_firmware   = 1,
+		.has_dvb        = 1,
+		.dvb_gpio       = hauppauge_wintv_hvr_900_digital,
+		.ir_codes       = RC_MAP_ATI_TV_WONDER_HD_600,
+		.decoder        = EM28XX_TVP5150,
+		.input          = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		} },
+	},
+	[EM2880_BOARD_TERRATEC_HYBRID_XS] = {
+		.name           = "Terratec Hybrid XS",
+		.tuner_type     = TUNER_XC2028,
+		.tuner_gpio     = default_tuner_gpio,
+		.decoder        = EM28XX_TVP5150,
+		.has_dvb        = 1,
+		.dvb_gpio       = default_digital,
+		.ir_codes       = RC_MAP_TERRATEC_CINERGY_XS,
+		.xclk           = EM28XX_XCLK_FREQUENCY_12MHZ, /* NEC IR */
+		.input          = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+			.gpio     = default_analog,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = default_analog,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = default_analog,
+		} },
+	},
+	/*
+	 * maybe there's a reason behind it why Terratec sells the Hybrid XS
+	 * as Prodigy XS with a different PID, let's keep it separated for now
+	 * maybe we'll need it later on
+	 */
+	[EM2880_BOARD_TERRATEC_PRODIGY_XS] = {
+		.name         = "Terratec Prodigy XS",
+		.tuner_type   = TUNER_XC2028,
+		.tuner_gpio   = default_tuner_gpio,
+		.decoder      = EM28XX_TVP5150,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		} },
+	},
+	[EM2820_BOARD_MSI_VOX_USB_2] = {
+		.name		   = "MSI VOX USB 2.0",
+		.tuner_type	   = TUNER_LG_PAL_NEW_TAPC,
+		.tda9887_conf	   = TDA9887_PRESENT      |
+				     TDA9887_PORT1_ACTIVE |
+				     TDA9887_PORT2_ACTIVE,
+		.max_range_640_480 = 1,
+		.decoder           = EM28XX_SAA711X,
+		.input             = { {
+			.type      = EM28XX_VMUX_TELEVISION,
+			.vmux      = SAA7115_COMPOSITE4,
+			.amux      = EM28XX_AMUX_VIDEO,
+		}, {
+			.type      = EM28XX_VMUX_COMPOSITE,
+			.vmux      = SAA7115_COMPOSITE0,
+			.amux      = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type      = EM28XX_VMUX_SVIDEO,
+			.vmux      = SAA7115_SVIDEO3,
+			.amux      = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2800_BOARD_TERRATEC_CINERGY_200] = {
+		.name         = "Terratec Cinergy 200 USB",
+		.is_em2800    = 1,
+		.has_ir_i2c   = 1,
+		.tuner_type   = TUNER_LG_TALN,
+		.tda9887_conf = TDA9887_PRESENT,
+		.decoder      = EM28XX_SAA711X,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = EM28XX_AMUX_VIDEO,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2800_BOARD_GRABBEEX_USB2800] = {
+		.name       = "eMPIA Technology, Inc. GrabBeeX+ Video Encoder",
+		.is_em2800  = 1,
+		.decoder    = EM28XX_SAA711X,
+		.tuner_type = TUNER_ABSENT, /* capture only board */
+		.input      = { {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2800_BOARD_VC211A] = {
+		.name         = "Actionmaster/LinXcel/Digitus VC211A",
+		.is_em2800    = 1,
+		.tuner_type   = TUNER_ABSENT,	/* Capture-only board */
+		.decoder      = EM28XX_SAA711X,
+		.input        = { {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = vc211a_enable,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = vc211a_enable,
+		} },
+	},
+	[EM2800_BOARD_LEADTEK_WINFAST_USBII] = {
+		.name         = "Leadtek Winfast USB II",
+		.is_em2800    = 1,
+		.tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+		.tda9887_conf = TDA9887_PRESENT,
+		.decoder      = EM28XX_SAA711X,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = EM28XX_AMUX_VIDEO,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2800_BOARD_KWORLD_USB2800] = {
+		.name         = "Kworld USB2800",
+		.is_em2800    = 1,
+		.tuner_type   = TUNER_PHILIPS_FCV1236D,
+		.tda9887_conf = TDA9887_PRESENT,
+		.decoder      = EM28XX_SAA711X,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = EM28XX_AMUX_VIDEO,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2820_BOARD_PINNACLE_DVC_90] = {
+		.name	      = "Pinnacle Dazzle DVC 90/100/101/107 / Kaiser Baas Video to DVD maker / Kworld DVD Maker 2 / Plextor ConvertX PX-AV100U",
+		.tuner_type   = TUNER_ABSENT, /* capture only board */
+		.decoder      = EM28XX_SAA711X,
+		.input        = { {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2800_BOARD_VGEAR_POCKETTV] = {
+		.name         = "V-Gear PocketTV",
+		.is_em2800    = 1,
+		.tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+		.tda9887_conf = TDA9887_PRESENT,
+		.decoder      = EM28XX_SAA711X,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = EM28XX_AMUX_VIDEO,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2820_BOARD_PROLINK_PLAYTV_BOX4_USB2] = {
+		.name         = "Pixelview PlayTV Box 4 USB 2.0",
+		.tda9887_conf = TDA9887_PRESENT,
+		.tuner_type   = TUNER_YMEC_TVF_5533MF,
+		.decoder      = EM28XX_SAA711X,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = EM28XX_AMUX_VIDEO,
+			.aout     = EM28XX_AOUT_MONO |	/* I2S */
+				    EM28XX_AOUT_MASTER,	/* Line out pin */
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2820_BOARD_PROLINK_PLAYTV_USB2] = {
+		.name         = "SIIG AVTuner-PVR / Pixelview Prolink PlayTV USB 2.0",
+		.buttons = std_snapshot_button,
+		.tda9887_conf = TDA9887_PRESENT,
+		.tuner_type   = TUNER_YMEC_TVF_5533MF,
+		.tuner_addr   = 0x60,
+		.decoder      = EM28XX_SAA711X,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = SAA7115_COMPOSITE2,
+			.amux     = EM28XX_AMUX_VIDEO,
+			.aout     = EM28XX_AOUT_MONO |	/* I2S */
+				    EM28XX_AOUT_MASTER,	/* Line out pin */
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2860_BOARD_SAA711X_REFERENCE_DESIGN] = {
+		.name                = "EM2860/SAA711X Reference Design",
+		.buttons = std_snapshot_button,
+		.tuner_type          = TUNER_ABSENT,
+		.decoder             = EM28XX_SAA711X,
+		.input               = { {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = SAA7115_COMPOSITE0,
+		} },
+	},
+
+	[EM2874_BOARD_LEADERSHIP_ISDBT] = {
+		.def_i2c_bus	= 1,
+		.i2c_speed      = EM28XX_I2C_CLK_WAIT_ENABLE |
+				  EM28XX_I2C_FREQ_100_KHZ,
+		.xclk		= EM28XX_XCLK_FREQUENCY_10MHZ,
+		.name		= "EM2874 Leadership ISDBT",
+		.tuner_type	= TUNER_ABSENT,
+		.tuner_gpio     = leadership_reset,
+		.dvb_gpio       = leadership_digital,
+		.has_dvb	= 1,
+	},
+
+	[EM2880_BOARD_MSI_DIGIVOX_AD] = {
+		.name         = "MSI DigiVox A/D",
+		.valid        = EM28XX_BOARD_NOT_VALIDATED,
+		.tuner_type   = TUNER_XC2028,
+		.tuner_gpio   = default_tuner_gpio,
+		.decoder      = EM28XX_TVP5150,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+			.gpio     = em2880_msi_digivox_ad_analog,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = em2880_msi_digivox_ad_analog,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = em2880_msi_digivox_ad_analog,
+		} },
+	},
+	[EM2880_BOARD_MSI_DIGIVOX_AD_II] = {
+		.name         = "MSI DigiVox A/D II",
+		.valid        = EM28XX_BOARD_NOT_VALIDATED,
+		.tuner_type   = TUNER_XC2028,
+		.tuner_gpio   = default_tuner_gpio,
+		.decoder      = EM28XX_TVP5150,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+			.gpio     = em2880_msi_digivox_ad_analog,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = em2880_msi_digivox_ad_analog,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = em2880_msi_digivox_ad_analog,
+		} },
+	},
+	[EM2880_BOARD_KWORLD_DVB_305U] = {
+		.name	      = "KWorld DVB-T 305U",
+		.tuner_type   = TUNER_XC2028,
+		.tuner_gpio   = default_tuner_gpio,
+		.decoder      = EM28XX_TVP5150,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2880_BOARD_KWORLD_DVB_310U] = {
+		.name	      = "KWorld DVB-T 310U",
+		.tuner_type   = TUNER_XC2028,
+		.tuner_gpio   = default_tuner_gpio,
+		.has_dvb      = 1,
+		.dvb_gpio     = default_digital,
+		.mts_firmware = 1,
+		.decoder      = EM28XX_TVP5150,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+			.gpio     = default_analog,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = default_analog,
+		}, {	/* S-video has not been tested yet */
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = default_analog,
+		} },
+	},
+	[EM2882_BOARD_KWORLD_ATSC_315U] = {
+		.name		= "KWorld ATSC 315U HDTV TV Box",
+		.valid		= EM28XX_BOARD_NOT_VALIDATED,
+		.tuner_type	= TUNER_THOMSON_DTT761X,
+		.tuner_gpio	= em2882_kworld_315u_tuner_gpio,
+		.tda9887_conf	= TDA9887_PRESENT,
+		.decoder	= EM28XX_SAA711X,
+		.has_dvb	= 1,
+		.dvb_gpio	= em2882_kworld_315u_digital,
+		.ir_codes	= RC_MAP_KWORLD_315U,
+		.xclk		= EM28XX_XCLK_FREQUENCY_12MHZ,
+		.i2c_speed	= EM28XX_I2C_CLK_WAIT_ENABLE,
+#if 0
+		/* FIXME: Analog mode - still not ready */
+		.input        = { {
+			.type = EM28XX_VMUX_TELEVISION,
+			.vmux = SAA7115_COMPOSITE2,
+			.amux = EM28XX_AMUX_VIDEO,
+			.gpio = em2882_kworld_315u_analog,
+			.aout = EM28XX_AOUT_PCM_IN | EM28XX_AOUT_PCM_STEREO,
+		}, {
+			.type = EM28XX_VMUX_COMPOSITE,
+			.vmux = SAA7115_COMPOSITE0,
+			.amux = EM28XX_AMUX_LINE_IN,
+			.gpio = em2882_kworld_315u_analog1,
+			.aout = EM28XX_AOUT_PCM_IN | EM28XX_AOUT_PCM_STEREO,
+		}, {
+			.type = EM28XX_VMUX_SVIDEO,
+			.vmux = SAA7115_SVIDEO3,
+			.amux = EM28XX_AMUX_LINE_IN,
+			.gpio = em2882_kworld_315u_analog1,
+			.aout = EM28XX_AOUT_PCM_IN | EM28XX_AOUT_PCM_STEREO,
+		} },
+#endif
+	},
+	[EM2880_BOARD_EMPIRE_DUAL_TV] = {
+		.name = "Empire dual TV",
+		.tuner_type = TUNER_XC2028,
+		.tuner_gpio = default_tuner_gpio,
+		.has_dvb = 1,
+		.dvb_gpio = default_digital,
+		.mts_firmware = 1,
+		.decoder = EM28XX_TVP5150,
+		.input = { {
+			.type = EM28XX_VMUX_TELEVISION,
+			.vmux = TVP5150_COMPOSITE0,
+			.amux = EM28XX_AMUX_VIDEO,
+			.gpio = default_analog,
+		}, {
+			.type = EM28XX_VMUX_COMPOSITE,
+			.vmux = TVP5150_COMPOSITE1,
+			.amux = EM28XX_AMUX_LINE_IN,
+			.gpio = default_analog,
+		}, {
+			.type = EM28XX_VMUX_SVIDEO,
+			.vmux = TVP5150_SVIDEO,
+			.amux = EM28XX_AMUX_LINE_IN,
+			.gpio = default_analog,
+		} },
+	},
+	[EM2881_BOARD_DNT_DA2_HYBRID] = {
+		.name         = "DNT DA2 Hybrid",
+		.valid        = EM28XX_BOARD_NOT_VALIDATED,
+		.tuner_type   = TUNER_XC2028,
+		.tuner_gpio   = default_tuner_gpio,
+		.decoder      = EM28XX_TVP5150,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+			.gpio     = default_analog,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = default_analog,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = default_analog,
+		} },
+	},
+	[EM2881_BOARD_PINNACLE_HYBRID_PRO] = {
+		.name         = "Pinnacle Hybrid Pro",
+		.tuner_type   = TUNER_XC2028,
+		.tuner_gpio   = default_tuner_gpio,
+		.decoder      = EM28XX_TVP5150,
+		.has_dvb      = 1,
+		.dvb_gpio     = pinnacle_hybrid_pro_digital,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+			.gpio     = pinnacle_hybrid_pro_analog,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = pinnacle_hybrid_pro_analog,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = pinnacle_hybrid_pro_analog,
+		} },
+	},
+	[EM2882_BOARD_PINNACLE_HYBRID_PRO_330E] = {
+		.name         = "Pinnacle Hybrid Pro (330e)",
+		.tuner_type   = TUNER_XC2028,
+		.tuner_gpio   = default_tuner_gpio,
+		.mts_firmware = 1,
+		.has_dvb      = 1,
+		.dvb_gpio     = hauppauge_wintv_hvr_900R2_digital,
+		.ir_codes     = RC_MAP_PINNACLE_PCTV_HD,
+		.decoder      = EM28XX_TVP5150,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		} },
+	},
+	[EM2882_BOARD_KWORLD_VS_DVBT] = {
+		.name         = "Kworld VS-DVB-T 323UR",
+		.tuner_type   = TUNER_XC2028,
+		.tuner_gpio   = default_tuner_gpio,
+		.decoder      = EM28XX_TVP5150,
+		.mts_firmware = 1,
+		.has_dvb      = 1,
+		.dvb_gpio     = kworld_330u_digital,
+		.xclk         = EM28XX_XCLK_FREQUENCY_12MHZ, /* NEC IR */
+		.ir_codes     = RC_MAP_KWORLD_315U,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2882_BOARD_TERRATEC_HYBRID_XS] = {
+		.name         = "Terratec Cinnergy Hybrid T USB XS (em2882)",
+		.tuner_type   = TUNER_XC2028,
+		.tuner_gpio   = default_tuner_gpio,
+		.mts_firmware = 1,
+		.decoder      = EM28XX_TVP5150,
+		.has_dvb      = 1,
+		.dvb_gpio     = hauppauge_wintv_hvr_900_digital,
+		.ir_codes     = RC_MAP_TERRATEC_CINERGY_XS,
+		.xclk         = EM28XX_XCLK_FREQUENCY_12MHZ,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = hauppauge_wintv_hvr_900_analog,
+		} },
+	},
+	[EM2882_BOARD_DIKOM_DK300] = {
+		.name         = "Dikom DK300",
+		.tuner_type   = TUNER_XC2028,
+		.tuner_gpio   = default_tuner_gpio,
+		.decoder      = EM28XX_TVP5150,
+		.mts_firmware = 1,
+		.has_dvb      = 1,
+		.dvb_gpio     = dikom_dk300_digital,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+			.gpio     = default_analog,
+		} },
+	},
+	[EM2883_BOARD_KWORLD_HYBRID_330U] = {
+		.name         = "Kworld PlusTV HD Hybrid 330",
+		.tuner_type   = TUNER_XC2028,
+		.tuner_gpio   = default_tuner_gpio,
+		.decoder      = EM28XX_TVP5150,
+		.mts_firmware = 1,
+		.has_dvb      = 1,
+		.dvb_gpio     = kworld_330u_digital,
+		.xclk             = EM28XX_XCLK_FREQUENCY_12MHZ,
+		.i2c_speed        = EM28XX_I2C_CLK_WAIT_ENABLE |
+				    EM28XX_I2C_EEPROM_ON_BOARD |
+				    EM28XX_I2C_EEPROM_KEY_VALID,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+			.gpio     = kworld_330u_analog,
+			.aout     = EM28XX_AOUT_PCM_IN | EM28XX_AOUT_PCM_STEREO,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = kworld_330u_analog,
+			.aout     = EM28XX_AOUT_PCM_IN | EM28XX_AOUT_PCM_STEREO,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = kworld_330u_analog,
+		} },
+	},
+	[EM2820_BOARD_COMPRO_VIDEOMATE_FORYOU] = {
+		.name         = "Compro VideoMate ForYou/Stereo",
+		.tuner_type   = TUNER_LG_PAL_NEW_TAPC,
+		.tvaudio_addr = 0xb0,
+		.tda9887_conf = TDA9887_PRESENT,
+		.decoder      = EM28XX_TVP5150,
+		.adecoder     = EM28XX_TVAUDIO,
+		.mute_gpio    = compro_mute_gpio,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+			.gpio     = compro_unmute_tv_gpio,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = compro_unmute_svid_gpio,
+		} },
+	},
+	[EM2860_BOARD_KAIOMY_TVNPC_U2] = {
+		.name	      = "Kaiomy TVnPC U2",
+		.vchannels    = 3,
+		.tuner_type   = TUNER_XC2028,
+		.tuner_addr   = 0x61,
+		.mts_firmware = 1,
+		.decoder      = EM28XX_TVP5150,
+		.tuner_gpio   = default_tuner_gpio,
+		.ir_codes     = RC_MAP_KAIOMY,
+		.input          = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+		.radio		= {
+			.type     = EM28XX_RADIO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}
+	},
+	[EM2860_BOARD_EASYCAP] = {
+		.name         = "Easy Cap Capture DC-60",
+		.vchannels    = 2,
+		.tuner_type   = TUNER_ABSENT,
+		.decoder      = EM28XX_SAA711X,
+		.input           = { {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	[EM2820_BOARD_IODATA_GVMVP_SZ] = {
+		.name       = "IO-DATA GV-MVP/SZ",
+		.tuner_type   = TUNER_PHILIPS_FM1236_MK3,
+		.tuner_gpio   = default_tuner_gpio,
+		.tda9887_conf = TDA9887_PRESENT,
+		.decoder      = EM28XX_TVP5150,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+		}, { /* Composite has not been tested yet */
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_VIDEO,
+		}, { /* S-video has not been tested yet */
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_VIDEO,
+		} },
+	},
+	[EM2860_BOARD_TERRATEC_GRABBY] = {
+		.name            = "Terratec Grabby",
+		.vchannels       = 2,
+		.tuner_type      = TUNER_ABSENT,
+		.decoder         = EM28XX_SAA711X,
+		.xclk            = EM28XX_XCLK_FREQUENCY_12MHZ,
+		.input           = { {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+		.buttons         = std_snapshot_button,
+		.leds            = terratec_grabby_leds,
+	},
+	[EM2860_BOARD_TERRATEC_AV350] = {
+		.name            = "Terratec AV350",
+		.vchannels       = 2,
+		.tuner_type      = TUNER_ABSENT,
+		.decoder         = EM28XX_TVP5150,
+		.xclk            = EM28XX_XCLK_FREQUENCY_12MHZ,
+		.mute_gpio       = terratec_av350_mute_gpio,
+		.input           = { {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = terratec_av350_unmute_gpio,
+
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = terratec_av350_unmute_gpio,
+		} },
+	},
+
+	[EM2860_BOARD_ELGATO_VIDEO_CAPTURE] = {
+		.name         = "Elgato Video Capture",
+		.decoder      = EM28XX_SAA711X,
+		.tuner_type   = TUNER_ABSENT,   /* Capture only device */
+		.input        = { {
+			.type  = EM28XX_VMUX_COMPOSITE,
+			.vmux  = SAA7115_COMPOSITE0,
+			.amux  = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type  = EM28XX_VMUX_SVIDEO,
+			.vmux  = SAA7115_SVIDEO3,
+			.amux  = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+
+	[EM2882_BOARD_EVGA_INDTUBE] = {
+		.name         = "Evga inDtube",
+		.tuner_type   = TUNER_XC2028,
+		.tuner_gpio   = default_tuner_gpio,
+		.decoder      = EM28XX_TVP5150,
+		.xclk         = EM28XX_XCLK_FREQUENCY_12MHZ, /* NEC IR */
+		.mts_firmware = 1,
+		.has_dvb      = 1,
+		.dvb_gpio     = evga_indtube_digital,
+		.ir_codes     = RC_MAP_EVGA_INDTUBE,
+		.input        = { {
+			.type     = EM28XX_VMUX_TELEVISION,
+			.vmux     = TVP5150_COMPOSITE0,
+			.amux     = EM28XX_AMUX_VIDEO,
+			.gpio     = evga_indtube_analog,
+		}, {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = evga_indtube_analog,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+			.gpio     = evga_indtube_analog,
+		} },
+	},
+	/*
+	 * eb1a:2868 Empia EM2870 + Philips CU1216L NIM
+	 * (Philips TDA10023 + Infineon TUA6034)
+	 */
+	[EM2870_BOARD_REDDO_DVB_C_USB_BOX] = {
+		.name          = "Reddo DVB-C USB TV Box",
+		.tuner_type    = TUNER_ABSENT,
+		.tuner_gpio    = reddo_dvb_c_usb_box,
+		.has_dvb       = 1,
+	},
+	/*
+	 * 1b80:a340 - Empia EM2870, NXP TDA18271HD and LG DT3304, sold
+	 * initially as the KWorld PlusTV 340U, then as the UB435-Q.
+	 * Early variants have a TDA18271HD/C1, later ones a TDA18271HD/C2
+	 */
+	[EM2870_BOARD_KWORLD_A340] = {
+		.name       = "KWorld PlusTV 340U or UB435-Q (ATSC)",
+		.tuner_type = TUNER_ABSENT,	/* Digital-only TDA18271HD */
+		.has_dvb    = 1,
+		.dvb_gpio   = kworld_a340_digital,
+		.tuner_gpio = default_tuner_gpio,
+	},
+	/*
+	 * 2013:024f PCTV nanoStick T2 290e.
+	 * Empia EM28174, Sony CXD2820R and NXP TDA18271HD/C2
+	 */
+	[EM28174_BOARD_PCTV_290E] = {
+		.name          = "PCTV nanoStick T2 290e",
+		.def_i2c_bus   = 1,
+		.i2c_speed     = EM28XX_I2C_CLK_WAIT_ENABLE |
+				 EM28XX_I2C_FREQ_100_KHZ,
+		.tuner_type    = TUNER_ABSENT,
+		.tuner_gpio    = pctv_290e,
+		.has_dvb       = 1,
+		.ir_codes      = RC_MAP_PINNACLE_PCTV_HD,
+	},
+	/*
+	 * 2013:024f PCTV DVB-S2 Stick 460e
+	 * Empia EM28174, NXP TDA10071, Conexant CX24118A and Allegro A8293
+	 */
+	[EM28174_BOARD_PCTV_460E] = {
+		.def_i2c_bus   = 1,
+		.i2c_speed     = EM28XX_I2C_CLK_WAIT_ENABLE |
+				 EM28XX_I2C_FREQ_400_KHZ,
+		.name          = "PCTV DVB-S2 Stick (460e)",
+		.tuner_type    = TUNER_ABSENT,
+		.tuner_gpio    = pctv_460e,
+		.has_dvb       = 1,
+		.ir_codes      = RC_MAP_PINNACLE_PCTV_HD,
+	},
+	/*
+	 * eb1a:5006 Honestech VIDBOX NW03
+	 * Empia EM2860, Philips SAA7113, Empia EMP202, No Tuner
+	 */
+	[EM2860_BOARD_HT_VIDBOX_NW03] = {
+		.name                = "Honestech Vidbox NW03",
+		.tuner_type          = TUNER_ABSENT,
+		.decoder             = EM28XX_SAA711X,
+		.input               = { {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = SAA7115_COMPOSITE0,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = SAA7115_SVIDEO3,  /* S-VIDEO needs check */
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	/*
+	 * 1b80:e425 MaxMedia UB425-TC
+	 * Empia EM2874B + Micronas DRX 3913KA2 + NXP TDA18271HDC2
+	 */
+	[EM2874_BOARD_MAXMEDIA_UB425_TC] = {
+		.name          = "MaxMedia UB425-TC",
+		.tuner_type    = TUNER_ABSENT,
+		.tuner_gpio    = maxmedia_ub425_tc,
+		.has_dvb       = 1,
+		.ir_codes      = RC_MAP_REDDO,
+		.def_i2c_bus   = 1,
+		.i2c_speed     = EM28XX_I2C_CLK_WAIT_ENABLE |
+				EM28XX_I2C_FREQ_400_KHZ,
+	},
+	/*
+	 * 2304:0242 PCTV QuatroStick (510e)
+	 * Empia EM2884 + Micronas DRX 3926K + NXP TDA18271HDC2
+	 */
+	[EM2884_BOARD_PCTV_510E] = {
+		.name          = "PCTV QuatroStick (510e)",
+		.tuner_type    = TUNER_ABSENT,
+		.tuner_gpio    = pctv_510e,
+		.has_dvb       = 1,
+		.ir_codes      = RC_MAP_PINNACLE_PCTV_HD,
+		.def_i2c_bus   = 1,
+		.i2c_speed     = EM28XX_I2C_CLK_WAIT_ENABLE |
+				EM28XX_I2C_FREQ_400_KHZ,
+	},
+	/*
+	 * 2013:0251 PCTV QuatroStick nano (520e)
+	 * Empia EM2884 + Micronas DRX 3926K + NXP TDA18271HDC2
+	 */
+	[EM2884_BOARD_PCTV_520E] = {
+		.name          = "PCTV QuatroStick nano (520e)",
+		.tuner_type    = TUNER_ABSENT,
+		.tuner_gpio    = pctv_520e,
+		.has_dvb       = 1,
+		.ir_codes      = RC_MAP_PINNACLE_PCTV_HD,
+		.def_i2c_bus   = 1,
+		.i2c_speed     = EM28XX_I2C_CLK_WAIT_ENABLE |
+				EM28XX_I2C_FREQ_400_KHZ,
+	},
+	[EM2884_BOARD_TERRATEC_HTC_USB_XS] = {
+		.name         = "Terratec Cinergy HTC USB XS",
+		.has_dvb      = 1,
+		.ir_codes     = RC_MAP_NEC_TERRATEC_CINERGY_XS,
+		.tuner_type   = TUNER_ABSENT,
+		.def_i2c_bus  = 1,
+		.i2c_speed    = EM28XX_I2C_CLK_WAIT_ENABLE |
+				EM28XX_I2C_FREQ_400_KHZ,
+	},
+	/*
+	 * 1b80:e1cc Delock 61959
+	 * Empia EM2874B + Micronas DRX 3913KA2 + NXP TDA18271HDC2
+	 * mostly the same as MaxMedia UB-425-TC but different remote
+	 */
+	[EM2874_BOARD_DELOCK_61959] = {
+		.name          = "Delock 61959",
+		.tuner_type    = TUNER_ABSENT,
+		.tuner_gpio    = maxmedia_ub425_tc,
+		.has_dvb       = 1,
+		.ir_codes      = RC_MAP_DELOCK_61959,
+		.def_i2c_bus   = 1,
+		.i2c_speed     = EM28XX_I2C_CLK_WAIT_ENABLE |
+				EM28XX_I2C_FREQ_400_KHZ,
+	},
+	/*
+	 * 1b80:e346 KWorld USB ATSC TV Stick UB435-Q V2
+	 * Empia EM2874B + LG DT3305 + NXP TDA18271HDC2
+	 */
+	[EM2874_BOARD_KWORLD_UB435Q_V2] = {
+		.name		= "KWorld USB ATSC TV Stick UB435-Q V2",
+		.tuner_type	= TUNER_ABSENT,
+		.has_dvb	= 1,
+		.dvb_gpio	= kworld_a340_digital,
+		.tuner_gpio	= default_tuner_gpio,
+		.def_i2c_bus	= 1,
+	},
+	/*
+	 * 1b80:e34c KWorld USB ATSC TV Stick UB435-Q V3
+	 * Empia EM2874B + LG DT3305 + NXP TDA18271HDC2
+	 */
+	[EM2874_BOARD_KWORLD_UB435Q_V3] = {
+		.name		= "KWorld USB ATSC TV Stick UB435-Q V3",
+		.tuner_type	= TUNER_ABSENT,
+		.has_dvb	= 1,
+		.tuner_gpio	= kworld_ub435q_v3_digital,
+		.def_i2c_bus	= 1,
+		.i2c_speed      = EM28XX_I2C_CLK_WAIT_ENABLE |
+				  EM28XX_I2C_FREQ_100_KHZ,
+		.leds = kworld_ub435q_v3_leds,
+	},
+	[EM2874_BOARD_PCTV_HD_MINI_80E] = {
+		.name         = "Pinnacle PCTV HD Mini",
+		.tuner_type   = TUNER_ABSENT,
+		.has_dvb      = 1,
+		.dvb_gpio     = em2874_pctv_80e_digital,
+		.decoder      = EM28XX_NODECODER,
+		.ir_codes     = RC_MAP_PINNACLE_PCTV_HD,
+		.leds         = pctv_80e_leds,
+	},
+	/*
+	 * 1ae7:9003/9004 SpeedLink Vicious And Devine Laplace webcam
+	 * Empia EM2765 + OmniVision OV2640
+	 */
+	[EM2765_BOARD_SPEEDLINK_VAD_LAPLACE] = {
+		.name         = "SpeedLink Vicious And Devine Laplace webcam",
+		.xclk         = EM28XX_XCLK_FREQUENCY_24MHZ,
+		.i2c_speed    = EM28XX_I2C_CLK_WAIT_ENABLE |
+				EM28XX_I2C_FREQ_100_KHZ,
+		.def_i2c_bus  = 1,
+		.tuner_type   = TUNER_ABSENT,
+		.is_webcam    = 1,
+		.input        = { {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.amux     = EM28XX_AMUX_VIDEO,
+			.gpio     = speedlink_vad_laplace_reg_seq,
+		} },
+		.buttons = speedlink_vad_laplace_buttons,
+		.leds = speedlink_vad_laplace_leds,
+	},
+	/*
+	 * 2013:0258 PCTV DVB-S2 Stick (461e)
+	 * Empia EM28178, Montage M88DS3103, Montage M88TS2022, Allegro A8293
+	 */
+	[EM28178_BOARD_PCTV_461E] = {
+		.def_i2c_bus   = 1,
+		.i2c_speed     = EM28XX_I2C_CLK_WAIT_ENABLE |
+				 EM28XX_I2C_FREQ_400_KHZ,
+		.name          = "PCTV DVB-S2 Stick (461e)",
+		.tuner_type    = TUNER_ABSENT,
+		.tuner_gpio    = pctv_461e,
+		.has_dvb       = 1,
+		.ir_codes      = RC_MAP_PINNACLE_PCTV_HD,
+	},
+	/*
+	 * 2013:025f PCTV tripleStick (292e).
+	 * Empia EM28178, Silicon Labs Si2168, Silicon Labs Si2157
+	 */
+	[EM28178_BOARD_PCTV_292E] = {
+		.name          = "PCTV tripleStick (292e)",
+		.def_i2c_bus   = 1,
+		.i2c_speed     = EM28XX_I2C_CLK_WAIT_ENABLE |
+				 EM28XX_I2C_FREQ_400_KHZ,
+		.tuner_type    = TUNER_ABSENT,
+		.tuner_gpio    = pctv_292e,
+		.has_dvb       = 1,
+		.ir_codes      = RC_MAP_PINNACLE_PCTV_HD,
+	},
+	[EM2861_BOARD_LEADTEK_VC100] = {
+		.name          = "Leadtek VC100",
+		.tuner_type    = TUNER_ABSENT,	/* Capture only device */
+		.decoder       = EM28XX_TVP5150,
+		.input         = { {
+			.type     = EM28XX_VMUX_COMPOSITE,
+			.vmux     = TVP5150_COMPOSITE1,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		}, {
+			.type     = EM28XX_VMUX_SVIDEO,
+			.vmux     = TVP5150_SVIDEO,
+			.amux     = EM28XX_AMUX_LINE_IN,
+		} },
+	},
+	/*
+	 * eb1a:8179 Terratec Cinergy T2 Stick HD.
+	 * Empia EM28178, Silicon Labs Si2168, Silicon Labs Si2146
+	 */
+	[EM28178_BOARD_TERRATEC_T2_STICK_HD] = {
+		.name          = "Terratec Cinergy T2 Stick HD",
+		.def_i2c_bus   = 1,
+		.i2c_speed     = EM28XX_I2C_CLK_WAIT_ENABLE |
+				 EM28XX_I2C_FREQ_400_KHZ,
+		.tuner_type    = TUNER_ABSENT,
+		.tuner_gpio    = terratec_t2_stick_hd,
+		.has_dvb       = 1,
+		.ir_codes      = RC_MAP_TERRATEC_SLIM_2,
+	},
+
+	/*
+	 * 3275:0085 PLEX PX-BCUD.
+	 * Empia EM28178, TOSHIBA TC90532XBG, Sharp QM1D1C0042
+	 */
+	[EM28178_BOARD_PLEX_PX_BCUD] = {
+		.name          = "PLEX PX-BCUD",
+		.xclk          = EM28XX_XCLK_FREQUENCY_4_3MHZ,
+		.def_i2c_bus   = 1,
+		.i2c_speed     = EM28XX_I2C_CLK_WAIT_ENABLE,
+		.tuner_type    = TUNER_ABSENT,
+		.tuner_gpio    = plex_px_bcud,
+		.has_dvb       = 1,
+	},
+	/*
+	 * 2040:0265 Hauppauge WinTV-dualHD (DVB version) Isoc.
+	 * 2040:8265 Hauppauge WinTV-dualHD (DVB version) Bulk.
+	 * Empia EM28274, 2x Silicon Labs Si2168, 2x Silicon Labs Si2157
+	 */
+	[EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_DVB] = {
+		.name          = "Hauppauge WinTV-dualHD DVB",
+		.def_i2c_bus   = 1,
+		.i2c_speed     = EM28XX_I2C_CLK_WAIT_ENABLE |
+				 EM28XX_I2C_FREQ_400_KHZ,
+		.tuner_type    = TUNER_ABSENT,
+		.tuner_gpio    = hauppauge_dualhd_dvb,
+		.has_dvb       = 1,
+		.has_dual_ts   = 1,
+		.ir_codes      = RC_MAP_HAUPPAUGE,
+		.leds          = hauppauge_dualhd_leds,
+	},
+	/*
+	 * 2040:026d Hauppauge WinTV-dualHD (model 01595 - ATSC/QAM) Isoc.
+	 * 2040:826d Hauppauge WinTV-dualHD (model 01595 - ATSC/QAM) Bulk.
+	 * Empia EM28274, 2x LG LGDT3306A, 2x Silicon Labs Si2157
+	 */
+	[EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_01595] = {
+		.name          = "Hauppauge WinTV-dualHD 01595 ATSC/QAM",
+		.def_i2c_bus   = 1,
+		.i2c_speed     = EM28XX_I2C_CLK_WAIT_ENABLE |
+				 EM28XX_I2C_FREQ_400_KHZ,
+		.tuner_type    = TUNER_ABSENT,
+		.tuner_gpio    = hauppauge_dualhd_dvb,
+		.has_dvb       = 1,
+		.has_dual_ts   = 1,
+		.ir_codes      = RC_MAP_HAUPPAUGE,
+		.leds          = hauppauge_dualhd_leds,
+	},
+};
+EXPORT_SYMBOL_GPL(em28xx_boards);
+
+static const unsigned int em28xx_bcount = ARRAY_SIZE(em28xx_boards);
+
+/* table of devices that work with this driver */
+struct usb_device_id em28xx_id_table[] = {
+	{ USB_DEVICE(0xeb1a, 0x2750),
+			.driver_info = EM2750_BOARD_UNKNOWN },
+	{ USB_DEVICE(0xeb1a, 0x2751),
+			.driver_info = EM2750_BOARD_UNKNOWN },
+	{ USB_DEVICE(0xeb1a, 0x2800),
+			.driver_info = EM2800_BOARD_UNKNOWN },
+	{ USB_DEVICE(0xeb1a, 0x2710),
+			.driver_info = EM2820_BOARD_UNKNOWN },
+	{ USB_DEVICE(0xeb1a, 0x2820),
+			.driver_info = EM2820_BOARD_UNKNOWN },
+	{ USB_DEVICE(0xeb1a, 0x2821),
+			.driver_info = EM2820_BOARD_UNKNOWN },
+	{ USB_DEVICE(0xeb1a, 0x2860),
+			.driver_info = EM2820_BOARD_UNKNOWN },
+	{ USB_DEVICE(0xeb1a, 0x2861),
+			.driver_info = EM2820_BOARD_UNKNOWN },
+	{ USB_DEVICE(0xeb1a, 0x2862),
+			.driver_info = EM2820_BOARD_UNKNOWN },
+	{ USB_DEVICE(0xeb1a, 0x2863),
+			.driver_info = EM2820_BOARD_UNKNOWN },
+	{ USB_DEVICE(0xeb1a, 0x2870),
+			.driver_info = EM2820_BOARD_UNKNOWN },
+	{ USB_DEVICE(0xeb1a, 0x2881),
+			.driver_info = EM2820_BOARD_UNKNOWN },
+	{ USB_DEVICE(0xeb1a, 0x2883), /* used by :Zolid Hybrid Tv Stick */
+			.driver_info = EM2820_BOARD_UNKNOWN },
+	{ USB_DEVICE(0xeb1a, 0x2868),
+			.driver_info = EM2820_BOARD_UNKNOWN },
+	{ USB_DEVICE(0xeb1a, 0x2875),
+			.driver_info = EM2820_BOARD_UNKNOWN },
+	{ USB_DEVICE(0xeb1a, 0x2885), /* MSI Digivox Trio */
+			.driver_info = EM2884_BOARD_TERRATEC_H5 },
+	{ USB_DEVICE(0xeb1a, 0xe300),
+			.driver_info = EM2861_BOARD_KWORLD_PVRTV_300U },
+	{ USB_DEVICE(0xeb1a, 0xe303),
+			.driver_info = EM2860_BOARD_KAIOMY_TVNPC_U2 },
+	{ USB_DEVICE(0xeb1a, 0xe305),
+			.driver_info = EM2880_BOARD_KWORLD_DVB_305U },
+	{ USB_DEVICE(0xeb1a, 0xe310),
+			.driver_info = EM2880_BOARD_MSI_DIGIVOX_AD },
+	{ USB_DEVICE(0xeb1a, 0xa313),
+		.driver_info = EM2882_BOARD_KWORLD_ATSC_315U },
+	{ USB_DEVICE(0xeb1a, 0xa316),
+			.driver_info = EM2883_BOARD_KWORLD_HYBRID_330U },
+	{ USB_DEVICE(0xeb1a, 0xe320),
+			.driver_info = EM2880_BOARD_MSI_DIGIVOX_AD_II },
+	{ USB_DEVICE(0xeb1a, 0xe323),
+			.driver_info = EM2882_BOARD_KWORLD_VS_DVBT },
+	{ USB_DEVICE(0xeb1a, 0xe350),
+			.driver_info = EM2870_BOARD_KWORLD_350U },
+	{ USB_DEVICE(0xeb1a, 0xe355),
+			.driver_info = EM2870_BOARD_KWORLD_355U },
+	{ USB_DEVICE(0xeb1a, 0x2801),
+			.driver_info = EM2800_BOARD_GRABBEEX_USB2800 },
+	{ USB_DEVICE(0xeb1a, 0xe357),
+			.driver_info = EM2870_BOARD_KWORLD_355U },
+	{ USB_DEVICE(0xeb1a, 0xe359),
+			.driver_info = EM2870_BOARD_KWORLD_355U },
+	{ USB_DEVICE(0x1b80, 0xe302), /* Kaiser Baas Video to DVD maker */
+			.driver_info = EM2820_BOARD_PINNACLE_DVC_90 },
+	{ USB_DEVICE(0x1b80, 0xe304), /* Kworld DVD Maker 2 */
+			.driver_info = EM2820_BOARD_PINNACLE_DVC_90 },
+	{ USB_DEVICE(0x0ccd, 0x0036),
+			.driver_info = EM2820_BOARD_TERRATEC_CINERGY_250 },
+	{ USB_DEVICE(0x0ccd, 0x004c),
+			.driver_info = EM2880_BOARD_TERRATEC_HYBRID_XS_FR },
+	{ USB_DEVICE(0x0ccd, 0x004f),
+			.driver_info = EM2860_BOARD_TERRATEC_HYBRID_XS },
+	{ USB_DEVICE(0x0ccd, 0x005e),
+			.driver_info = EM2882_BOARD_TERRATEC_HYBRID_XS },
+	{ USB_DEVICE(0x0ccd, 0x0042),
+			.driver_info = EM2882_BOARD_TERRATEC_HYBRID_XS },
+	{ USB_DEVICE(0x0ccd, 0x0043),
+			.driver_info = EM2870_BOARD_TERRATEC_XS_MT2060 },
+	{ USB_DEVICE(0x0ccd, 0x008e),	/* Cinergy HTC USB XS Rev. 1 */
+			.driver_info = EM2884_BOARD_TERRATEC_HTC_USB_XS },
+	{ USB_DEVICE(0x0ccd, 0x00ac),	/* Cinergy HTC USB XS Rev. 2 */
+			.driver_info = EM2884_BOARD_TERRATEC_HTC_USB_XS },
+	{ USB_DEVICE(0x0ccd, 0x10a2),	/* H5 Rev. 1 */
+			.driver_info = EM2884_BOARD_TERRATEC_H5 },
+	{ USB_DEVICE(0x0ccd, 0x10ad),	/* H5 Rev. 2 */
+			.driver_info = EM2884_BOARD_TERRATEC_H5 },
+	{ USB_DEVICE(0x0ccd, 0x10b6),	/* H5 Rev. 3 */
+			.driver_info = EM2884_BOARD_TERRATEC_H5 },
+	{ USB_DEVICE(0x0ccd, 0x10b2),	/* H6 */
+			.driver_info = EM2884_BOARD_TERRATEC_H6 },
+	{ USB_DEVICE(0x0ccd, 0x0084),
+			.driver_info = EM2860_BOARD_TERRATEC_AV350 },
+	{ USB_DEVICE(0x0ccd, 0x0096),
+			.driver_info = EM2860_BOARD_TERRATEC_GRABBY },
+	{ USB_DEVICE(0x0ccd, 0x10AF),
+			.driver_info = EM2860_BOARD_TERRATEC_GRABBY },
+	{ USB_DEVICE(0x0ccd, 0x00b2),
+			.driver_info = EM2884_BOARD_CINERGY_HTC_STICK },
+	{ USB_DEVICE(0x0fd9, 0x0018),
+			.driver_info = EM2884_BOARD_ELGATO_EYETV_HYBRID_2008 },
+	{ USB_DEVICE(0x0fd9, 0x0033),
+			.driver_info = EM2860_BOARD_ELGATO_VIDEO_CAPTURE },
+	{ USB_DEVICE(0x185b, 0x2870),
+			.driver_info = EM2870_BOARD_COMPRO_VIDEOMATE },
+	{ USB_DEVICE(0x185b, 0x2041),
+			.driver_info = EM2820_BOARD_COMPRO_VIDEOMATE_FORYOU },
+	{ USB_DEVICE(0x2040, 0x4200),
+			.driver_info = EM2820_BOARD_HAUPPAUGE_WINTV_USB_2 },
+	{ USB_DEVICE(0x2040, 0x4201),
+			.driver_info = EM2820_BOARD_HAUPPAUGE_WINTV_USB_2 },
+	{ USB_DEVICE(0x2040, 0x6500),
+			.driver_info = EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900 },
+	{ USB_DEVICE(0x2040, 0x6502),
+			.driver_info = EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2 },
+	{ USB_DEVICE(0x2040, 0x6513), /* HCW HVR-980 */
+			.driver_info = EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950 },
+	{ USB_DEVICE(0x2040, 0x6517), /* HP  HVR-950 */
+			.driver_info = EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950 },
+	{ USB_DEVICE(0x2040, 0x651b), /* RP  HVR-950 */
+			.driver_info = EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950 },
+	{ USB_DEVICE(0x2040, 0x651f),
+			.driver_info = EM2883_BOARD_HAUPPAUGE_WINTV_HVR_850 },
+	{ USB_DEVICE(0x2040, 0x0265),
+			.driver_info = EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_DVB },
+	{ USB_DEVICE(0x2040, 0x8265),
+			.driver_info = EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_DVB },
+	{ USB_DEVICE(0x2040, 0x026d),
+			.driver_info = EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_01595 },
+	{ USB_DEVICE(0x2040, 0x826d),
+			.driver_info = EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_01595 },
+	{ USB_DEVICE(0x0438, 0xb002),
+			.driver_info = EM2880_BOARD_AMD_ATI_TV_WONDER_HD_600 },
+	{ USB_DEVICE(0x2001, 0xf112),
+			.driver_info = EM2820_BOARD_DLINK_USB_TV },
+	{ USB_DEVICE(0x2304, 0x0207),
+			.driver_info = EM2820_BOARD_PINNACLE_DVC_90 },
+	{ USB_DEVICE(0x2304, 0x0208),
+			.driver_info = EM2820_BOARD_PINNACLE_USB_2 },
+	{ USB_DEVICE(0x2304, 0x021a),
+			.driver_info = EM2820_BOARD_PINNACLE_DVC_90 },
+	{ USB_DEVICE(0x2304, 0x0226),
+			.driver_info = EM2882_BOARD_PINNACLE_HYBRID_PRO_330E },
+	{ USB_DEVICE(0x2304, 0x0227),
+			.driver_info = EM2880_BOARD_PINNACLE_PCTV_HD_PRO },
+	{ USB_DEVICE(0x2304, 0x023f),
+			.driver_info = EM2874_BOARD_PCTV_HD_MINI_80E },
+	{ USB_DEVICE(0x0413, 0x6023),
+			.driver_info = EM2800_BOARD_LEADTEK_WINFAST_USBII },
+	{ USB_DEVICE(0x093b, 0xa003),
+		       .driver_info = EM2820_BOARD_PINNACLE_DVC_90 },
+	{ USB_DEVICE(0x093b, 0xa005),
+			.driver_info = EM2861_BOARD_PLEXTOR_PX_TV100U },
+	{ USB_DEVICE(0x04bb, 0x0515),
+			.driver_info = EM2820_BOARD_IODATA_GVMVP_SZ },
+	{ USB_DEVICE(0xeb1a, 0x50a6),
+			.driver_info = EM2860_BOARD_GADMEI_UTV330 },
+	{ USB_DEVICE(0x1b80, 0xa340),
+			.driver_info = EM2870_BOARD_KWORLD_A340 },
+	{ USB_DEVICE(0x1b80, 0xe346),
+			.driver_info = EM2874_BOARD_KWORLD_UB435Q_V2 },
+	{ USB_DEVICE(0x1b80, 0xe34c),
+			.driver_info = EM2874_BOARD_KWORLD_UB435Q_V3 },
+	{ USB_DEVICE(0x2013, 0x024f),
+			.driver_info = EM28174_BOARD_PCTV_290E },
+	{ USB_DEVICE(0x2013, 0x024c),
+			.driver_info = EM28174_BOARD_PCTV_460E },
+	{ USB_DEVICE(0x2040, 0x1605),
+			.driver_info = EM2884_BOARD_HAUPPAUGE_WINTV_HVR_930C },
+	{ USB_DEVICE(0x1b80, 0xe755),
+			.driver_info = EM2884_BOARD_C3TECH_DIGITAL_DUO },
+	{ USB_DEVICE(0xeb1a, 0x5006),
+			.driver_info = EM2860_BOARD_HT_VIDBOX_NW03 },
+	{ USB_DEVICE(0x1b80, 0xe309), /* Sveon STV40 */
+			.driver_info = EM2860_BOARD_EASYCAP },
+	{ USB_DEVICE(0x1b80, 0xe425),
+			.driver_info = EM2874_BOARD_MAXMEDIA_UB425_TC },
+	{ USB_DEVICE(0x2304, 0x0242),
+			.driver_info = EM2884_BOARD_PCTV_510E },
+	{ USB_DEVICE(0x2013, 0x0251),
+			.driver_info = EM2884_BOARD_PCTV_520E },
+	{ USB_DEVICE(0x1b80, 0xe1cc),
+			.driver_info = EM2874_BOARD_DELOCK_61959 },
+	{ USB_DEVICE(0x1ae7, 0x9003),
+			.driver_info = EM2765_BOARD_SPEEDLINK_VAD_LAPLACE },
+	{ USB_DEVICE(0x1ae7, 0x9004),
+			.driver_info = EM2765_BOARD_SPEEDLINK_VAD_LAPLACE },
+	{ USB_DEVICE(0x2013, 0x0258),
+			.driver_info = EM28178_BOARD_PCTV_461E },
+	{ USB_DEVICE(0x2013, 0x025f),
+			.driver_info = EM28178_BOARD_PCTV_292E },
+	{ USB_DEVICE(0x2013, 0x0264), /* Hauppauge WinTV-soloHD 292e SE */
+			.driver_info = EM28178_BOARD_PCTV_292E },
+	{ USB_DEVICE(0x2040, 0x0264), /* Hauppauge WinTV-soloHD Isoc */
+			.driver_info = EM28178_BOARD_PCTV_292E },
+	{ USB_DEVICE(0x2040, 0x8264), /* Hauppauge OEM Generic WinTV-soloHD Bulk */
+			.driver_info = EM28178_BOARD_PCTV_292E },
+	{ USB_DEVICE(0x2040, 0x8268), /* Hauppauge Retail WinTV-soloHD Bulk */
+			.driver_info = EM28178_BOARD_PCTV_292E },
+	{ USB_DEVICE(0x0413, 0x6f07),
+			.driver_info = EM2861_BOARD_LEADTEK_VC100 },
+	{ USB_DEVICE(0xeb1a, 0x8179),
+			.driver_info = EM28178_BOARD_TERRATEC_T2_STICK_HD },
+	{ USB_DEVICE(0x3275, 0x0085),
+			.driver_info = EM28178_BOARD_PLEX_PX_BCUD },
+	{ USB_DEVICE(0xeb1a, 0x5051), /* Ion Video 2 PC MKII / Startech svid2usb23 / Raygo R12-41373 */
+			.driver_info = EM2860_BOARD_TVP5150_REFERENCE_DESIGN },
+	{ },
+};
+MODULE_DEVICE_TABLE(usb, em28xx_id_table);
+
+/*
+ * EEPROM hash table for devices with generic USB IDs
+ */
+static const struct em28xx_hash_table em28xx_eeprom_hash[] = {
+	/* P/N: SA 60002070465 Tuner: TVF7533-MF */
+	{0x6ce05a8f, EM2820_BOARD_PROLINK_PLAYTV_USB2, TUNER_YMEC_TVF_5533MF},
+	{0x72cc5a8b, EM2820_BOARD_PROLINK_PLAYTV_BOX4_USB2, TUNER_YMEC_TVF_5533MF},
+	{0x966a0441, EM2880_BOARD_KWORLD_DVB_310U, TUNER_XC2028},
+	{0x166a0441, EM2880_BOARD_EMPIRE_DUAL_TV, TUNER_XC2028},
+	{0xcee44a99, EM2882_BOARD_EVGA_INDTUBE, TUNER_XC2028},
+	{0xb8846b20, EM2881_BOARD_PINNACLE_HYBRID_PRO, TUNER_XC2028},
+	{0x63f653bd, EM2870_BOARD_REDDO_DVB_C_USB_BOX, TUNER_ABSENT},
+	{0x4e913442, EM2882_BOARD_DIKOM_DK300, TUNER_XC2028},
+	{0x85dd871e, EM2882_BOARD_ZOLID_HYBRID_TV_STICK, TUNER_XC2028},
+};
+
+/* I2C devicelist hash table for devices with generic USB IDs */
+static const struct em28xx_hash_table em28xx_i2c_hash[] = {
+	{0xb06a32c3, EM2800_BOARD_TERRATEC_CINERGY_200, TUNER_LG_PAL_NEW_TAPC},
+	{0xf51200e3, EM2800_BOARD_VGEAR_POCKETTV, TUNER_LG_PAL_NEW_TAPC},
+	{0x1ba50080, EM2860_BOARD_SAA711X_REFERENCE_DESIGN, TUNER_ABSENT},
+	{0x77800080, EM2860_BOARD_TVP5150_REFERENCE_DESIGN, TUNER_ABSENT},
+	{0xc51200e3, EM2820_BOARD_GADMEI_TVR200, TUNER_LG_PAL_NEW_TAPC},
+	{0x4ba50080, EM2861_BOARD_GADMEI_UTV330PLUS, TUNER_TNF_5335MF},
+	{0x6b800080, EM2874_BOARD_LEADERSHIP_ISDBT, TUNER_ABSENT},
+	{0x27e10080, EM2882_BOARD_ZOLID_HYBRID_TV_STICK, TUNER_XC2028},
+};
+
+/* NOTE: introduce a separate hash table for devices with 16 bit eeproms */
+
+int em28xx_tuner_callback(void *ptr, int component, int command, int arg)
+{
+	struct em28xx_i2c_bus *i2c_bus = ptr;
+	struct em28xx *dev = i2c_bus->dev;
+	int rc = 0;
+
+	if (dev->tuner_type != TUNER_XC2028 && dev->tuner_type != TUNER_XC5000)
+		return 0;
+
+	if (command != XC2028_TUNER_RESET && command != XC5000_TUNER_RESET)
+		return 0;
+
+	rc = em28xx_gpio_set(dev, dev->board.tuner_gpio);
+
+	return rc;
+}
+EXPORT_SYMBOL_GPL(em28xx_tuner_callback);
+
+static inline void em28xx_set_xclk_i2c_speed(struct em28xx *dev)
+{
+	const struct em28xx_board *board = &em28xx_boards[dev->model];
+	u8 xclk = board->xclk, i2c_speed = board->i2c_speed;
+
+	/*
+	 * Those are the default values for the majority of boards
+	 * Use those values if not specified otherwise at boards entry
+	 */
+	if (!xclk)
+		xclk = EM28XX_XCLK_IR_RC5_MODE |
+		       EM28XX_XCLK_FREQUENCY_12MHZ;
+
+	em28xx_write_reg(dev, EM28XX_R0F_XCLK, xclk);
+
+	if (!i2c_speed)
+		i2c_speed = EM28XX_I2C_CLK_WAIT_ENABLE |
+			    EM28XX_I2C_FREQ_100_KHZ;
+
+	dev->i2c_speed = i2c_speed & 0x03;
+
+	if (!dev->board.is_em2800)
+		em28xx_write_reg(dev, EM28XX_R06_I2C_CLK, i2c_speed);
+	msleep(50);
+}
+
+static inline void em28xx_set_model(struct em28xx *dev)
+{
+	dev->board = em28xx_boards[dev->model];
+	dev->has_msp34xx = dev->board.has_msp34xx;
+	dev->is_webcam = dev->board.is_webcam;
+
+	em28xx_set_xclk_i2c_speed(dev);
+
+	/* Should be initialized early, for I2C to work */
+	dev->def_i2c_bus = dev->board.def_i2c_bus;
+}
+
+/*
+ * Wait until AC97_RESET reports the expected value reliably before proceeding.
+ * We also check that two unrelated registers accesses don't return the same
+ * value to avoid premature return.
+ * This procedure helps ensuring AC97 register accesses are reliable.
+ */
+static int em28xx_wait_until_ac97_features_equals(struct em28xx *dev,
+						  int expected_feat)
+{
+	unsigned long timeout = jiffies + msecs_to_jiffies(2000);
+	int feat, powerdown;
+
+	while (time_is_after_jiffies(timeout)) {
+		feat = em28xx_read_ac97(dev, AC97_RESET);
+		if (feat < 0)
+			return feat;
+
+		powerdown = em28xx_read_ac97(dev, AC97_POWERDOWN);
+		if (powerdown < 0)
+			return powerdown;
+
+		if (feat == expected_feat && feat != powerdown)
+			return 0;
+
+		msleep(50);
+	}
+
+	dev_warn(&dev->intf->dev, "AC97 registers access is not reliable !\n");
+	return -ETIMEDOUT;
+}
+
+/*
+ * Since em28xx_pre_card_setup() requires a proper dev->model,
+ * this won't work for boards with generic PCI IDs
+ */
+static void em28xx_pre_card_setup(struct em28xx *dev)
+{
+	/*
+	 * Set the initial XCLK and I2C clock values based on the board
+	 * definition
+	 */
+	em28xx_set_xclk_i2c_speed(dev);
+
+	/* request some modules */
+	switch (dev->model) {
+	case EM2861_BOARD_PLEXTOR_PX_TV100U:
+		/* Sets the msp34xx I2S speed */
+		dev->i2s_speed = 2048000;
+		break;
+	case EM2861_BOARD_KWORLD_PVRTV_300U:
+	case EM2880_BOARD_KWORLD_DVB_305U:
+		em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0x6d);
+		usleep_range(10000, 11000);
+		em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0x7d);
+		usleep_range(10000, 11000);
+		break;
+	case EM2870_BOARD_COMPRO_VIDEOMATE:
+		/*
+		 * TODO: someone can do some cleanup here...
+		 *	 not everything's needed
+		 */
+		em28xx_write_reg(dev, EM2880_R04_GPO, 0x00);
+		usleep_range(10000, 11000);
+		em28xx_write_reg(dev, EM2880_R04_GPO, 0x01);
+		usleep_range(10000, 11000);
+		em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0xfd);
+		msleep(70);
+		em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0xfc);
+		msleep(70);
+		em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0xdc);
+		msleep(70);
+		em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0xfc);
+		msleep(70);
+		break;
+	case EM2870_BOARD_TERRATEC_XS_MT2060:
+		/*
+		 * this device needs some gpio writes to get the DVB-T
+		 * demod work
+		 */
+		em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0xfe);
+		msleep(70);
+		em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0xde);
+		msleep(70);
+		em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0xfe);
+		msleep(70);
+		break;
+	case EM2870_BOARD_PINNACLE_PCTV_DVB:
+		/*
+		 * this device needs some gpio writes to get the
+		 * DVB-T demod work
+		 */
+		em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0xfe);
+		msleep(70);
+		em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0xde);
+		msleep(70);
+		em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0xfe);
+		msleep(70);
+		break;
+	case EM2820_BOARD_GADMEI_UTV310:
+	case EM2820_BOARD_MSI_VOX_USB_2:
+		/* enables audio for that devices */
+		em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0xfd);
+		break;
+
+	case EM2882_BOARD_KWORLD_ATSC_315U:
+		em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0xff);
+		usleep_range(10000, 11000);
+		em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0xfe);
+		usleep_range(10000, 11000);
+		em28xx_write_reg(dev, EM2880_R04_GPO, 0x00);
+		usleep_range(10000, 11000);
+		em28xx_write_reg(dev, EM2880_R04_GPO, 0x08);
+		usleep_range(10000, 11000);
+		break;
+
+	case EM2860_BOARD_KAIOMY_TVNPC_U2:
+		em28xx_write_regs(dev, EM28XX_R0F_XCLK, "\x07", 1);
+		em28xx_write_regs(dev, EM28XX_R06_I2C_CLK, "\x40", 1);
+		em28xx_write_regs(dev, 0x0d, "\x42", 1);
+		em28xx_write_regs(dev, 0x08, "\xfd", 1);
+		usleep_range(10000, 11000);
+		em28xx_write_regs(dev, 0x08, "\xff", 1);
+		usleep_range(10000, 11000);
+		em28xx_write_regs(dev, 0x08, "\x7f", 1);
+		usleep_range(10000, 11000);
+		em28xx_write_regs(dev, 0x08, "\x6b", 1);
+
+		break;
+	case EM2860_BOARD_EASYCAP:
+		em28xx_write_regs(dev, 0x08, "\xf8", 1);
+		break;
+
+	case EM2820_BOARD_IODATA_GVMVP_SZ:
+		em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0xff);
+		msleep(70);
+		em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0xf7);
+		usleep_range(10000, 11000);
+		em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0xfe);
+		msleep(70);
+		em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0xfd);
+		msleep(70);
+		break;
+
+	case EM2860_BOARD_TERRATEC_GRABBY:
+		/*
+		 * HACK?: Ensure AC97 register reading is reliable before
+		 * proceeding. In practice, this will wait about 1.6 seconds.
+		 */
+		em28xx_wait_until_ac97_features_equals(dev, 0x6a90);
+		break;
+	}
+
+	em28xx_gpio_set(dev, dev->board.tuner_gpio);
+	em28xx_set_mode(dev, EM28XX_ANALOG_MODE);
+
+	/* Unlock device */
+	em28xx_set_mode(dev, EM28XX_SUSPEND);
+}
+
+static int em28xx_hint_board(struct em28xx *dev)
+{
+	int i;
+
+	if (dev->is_webcam) {
+		if (dev->em28xx_sensor == EM28XX_MT9V011) {
+			dev->model = EM2820_BOARD_SILVERCREST_WEBCAM;
+		} else if (dev->em28xx_sensor == EM28XX_MT9M001 ||
+			   dev->em28xx_sensor == EM28XX_MT9M111) {
+			dev->model = EM2750_BOARD_UNKNOWN;
+		}
+		/* FIXME: IMPROVE ! */
+
+		return 0;
+	}
+
+	/*
+	 * HINT method: EEPROM
+	 *
+	 * This method works only for boards with eeprom.
+	 * Uses a hash of all eeprom bytes. The hash should be
+	 * unique for a vendor/tuner pair.
+	 * There are a high chance that tuners for different
+	 * video standards produce different hashes.
+	 */
+	for (i = 0; i < ARRAY_SIZE(em28xx_eeprom_hash); i++) {
+		if (dev->hash == em28xx_eeprom_hash[i].hash) {
+			dev->model = em28xx_eeprom_hash[i].model;
+			dev->tuner_type = em28xx_eeprom_hash[i].tuner;
+
+			dev_err(&dev->intf->dev,
+				"Your board has no unique USB ID.\n"
+				"A hint were successfully done, based on eeprom hash.\n"
+				"This method is not 100%% failproof.\n"
+				"If the board were misdetected, please email this log to:\n"
+				"\tV4L Mailing List  <linux-media@vger.kernel.org>\n"
+				"Board detected as %s\n",
+			       em28xx_boards[dev->model].name);
+
+			return 0;
+		}
+	}
+
+	/*
+	 * HINT method: I2C attached devices
+	 *
+	 * This method works for all boards.
+	 * Uses a hash of i2c scanned devices.
+	 * Devices with the same i2c attached chips will
+	 * be considered equal.
+	 * This method is less precise than the eeprom one.
+	 */
+
+	/* user did not request i2c scanning => do it now */
+	if (!dev->i2c_hash)
+		em28xx_do_i2c_scan(dev, dev->def_i2c_bus);
+
+	for (i = 0; i < ARRAY_SIZE(em28xx_i2c_hash); i++) {
+		if (dev->i2c_hash == em28xx_i2c_hash[i].hash) {
+			dev->model = em28xx_i2c_hash[i].model;
+			dev->tuner_type = em28xx_i2c_hash[i].tuner;
+			dev_err(&dev->intf->dev,
+				"Your board has no unique USB ID.\n"
+				"A hint were successfully done, based on i2c devicelist hash.\n"
+				"This method is not 100%% failproof.\n"
+				"If the board were misdetected, please email this log to:\n"
+				"\tV4L Mailing List  <linux-media@vger.kernel.org>\n"
+				"Board detected as %s\n",
+				em28xx_boards[dev->model].name);
+
+			return 0;
+		}
+	}
+
+	dev_err(&dev->intf->dev,
+		"Your board has no unique USB ID and thus need a hint to be detected.\n"
+		"You may try to use card=<n> insmod option to workaround that.\n"
+		"Please send an email with this log to:\n"
+		"\tV4L Mailing List <linux-media@vger.kernel.org>\n"
+		"Board eeprom hash is 0x%08lx\n"
+		"Board i2c devicelist hash is 0x%08lx\n",
+		dev->hash, dev->i2c_hash);
+
+	dev_err(&dev->intf->dev,
+		"Here is a list of valid choices for the card=<n> insmod option:\n");
+	for (i = 0; i < em28xx_bcount; i++) {
+		dev_err(&dev->intf->dev,
+			"    card=%d -> %s\n", i, em28xx_boards[i].name);
+	}
+	return -1;
+}
+
+static void em28xx_card_setup(struct em28xx *dev)
+{
+	int i, j, idx;
+	bool duplicate_entry;
+
+	/*
+	 * If the device can be a webcam, seek for a sensor.
+	 * If sensor is not found, then it isn't a webcam.
+	 */
+	if (dev->is_webcam) {
+		em28xx_detect_sensor(dev);
+		if (dev->em28xx_sensor == EM28XX_NOSENSOR)
+			/* NOTE: error/unknown sensor/no sensor */
+			dev->is_webcam = 0;
+	}
+
+	switch (dev->model) {
+	case EM2750_BOARD_UNKNOWN:
+	case EM2820_BOARD_UNKNOWN:
+	case EM2800_BOARD_UNKNOWN:
+		/*
+		 * The K-WORLD DVB-T 310U is detected as an MSI Digivox AD.
+		 *
+		 * This occurs because they share identical USB vendor and
+		 * product IDs.
+		 *
+		 * What we do here is look up the EEPROM hash of the K-WORLD
+		 * and if it is found then we decide that we do not have
+		 * a DIGIVOX and reset the device to the K-WORLD instead.
+		 *
+		 * This solution is only valid if they do not share eeprom
+		 * hash identities which has not been determined as yet.
+		 */
+		if (em28xx_hint_board(dev) < 0) {
+			dev_err(&dev->intf->dev, "Board not discovered\n");
+		} else {
+			em28xx_set_model(dev);
+			em28xx_pre_card_setup(dev);
+		}
+		break;
+	default:
+		em28xx_set_model(dev);
+	}
+
+	dev_info(&dev->intf->dev, "Identified as %s (card=%d)\n",
+		 dev->board.name, dev->model);
+
+	dev->tuner_type = em28xx_boards[dev->model].tuner_type;
+
+	/* request some modules */
+	switch (dev->model) {
+	case EM2820_BOARD_HAUPPAUGE_WINTV_USB_2:
+	case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900:
+	case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2:
+	case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_850:
+	case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950:
+	case EM2884_BOARD_HAUPPAUGE_WINTV_HVR_930C:
+	case EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_DVB:
+	case EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_01595:
+	{
+		struct tveeprom tv;
+
+		if (!dev->eedata)
+			break;
+#if defined(CONFIG_MODULES) && defined(MODULE)
+		request_module("tveeprom");
+#endif
+		/* Call first TVeeprom */
+
+		tveeprom_hauppauge_analog(&tv, dev->eedata);
+
+		dev->tuner_type = tv.tuner_type;
+
+		if (tv.audio_processor == TVEEPROM_AUDPROC_MSP) {
+			dev->i2s_speed = 2048000;
+			dev->has_msp34xx = 1;
+		}
+		break;
+	}
+	case EM2882_BOARD_KWORLD_ATSC_315U:
+		em28xx_write_reg(dev, 0x0d, 0x42);
+		usleep_range(10000, 11000);
+		em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0xfd);
+		usleep_range(10000, 11000);
+		break;
+	case EM2820_BOARD_KWORLD_PVRTV2800RF:
+		/* GPIO enables sound on KWORLD PVR TV 2800RF */
+		em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0xf9);
+		break;
+	case EM2820_BOARD_UNKNOWN:
+	case EM2800_BOARD_UNKNOWN:
+		/*
+		 * The K-WORLD DVB-T 310U is detected as an MSI Digivox AD.
+		 *
+		 * This occurs because they share identical USB vendor and
+		 * product IDs.
+		 *
+		 * What we do here is look up the EEPROM hash of the K-WORLD
+		 * and if it is found then we decide that we do not have
+		 * a DIGIVOX and reset the device to the K-WORLD instead.
+		 *
+		 * This solution is only valid if they do not share eeprom
+		 * hash identities which has not been determined as yet.
+		 */
+	case EM2880_BOARD_MSI_DIGIVOX_AD:
+		if (!em28xx_hint_board(dev))
+			em28xx_set_model(dev);
+
+		/*
+		 * In cases where we had to use a board hint, the call to
+		 * em28xx_set_mode() in em28xx_pre_card_setup() was a no-op,
+		 * so make the call now so the analog GPIOs are set properly
+		 * before probing the i2c bus.
+		 */
+		em28xx_gpio_set(dev, dev->board.tuner_gpio);
+		em28xx_set_mode(dev, EM28XX_ANALOG_MODE);
+		break;
+
+		/*
+		 * The Dikom DK300 is detected as an Kworld VS-DVB-T 323UR.
+		 *
+		 * This occurs because they share identical USB vendor and
+		 * product IDs.
+		 *
+		 * What we do here is look up the EEPROM hash of the Dikom
+		 * and if it is found then we decide that we do not have
+		 * a Kworld and reset the device to the Dikom instead.
+		 *
+		 * This solution is only valid if they do not share eeprom
+		 * hash identities which has not been determined as yet.
+		 */
+	case EM2882_BOARD_KWORLD_VS_DVBT:
+		if (!em28xx_hint_board(dev))
+			em28xx_set_model(dev);
+
+		/*
+		 * In cases where we had to use a board hint, the call to
+		 * em28xx_set_mode() in em28xx_pre_card_setup() was a no-op,
+		 * so make the call now so the analog GPIOs are set properly
+		 * before probing the i2c bus.
+		 */
+		em28xx_gpio_set(dev, dev->board.tuner_gpio);
+		em28xx_set_mode(dev, EM28XX_ANALOG_MODE);
+		break;
+	}
+
+	if (dev->board.valid == EM28XX_BOARD_NOT_VALIDATED) {
+		dev_err(&dev->intf->dev,
+			"\n\n"
+			"The support for this board weren't valid yet.\n"
+			"Please send a report of having this working\n"
+			"not to V4L mailing list (and/or to other addresses)\n\n");
+	}
+
+	/* Free eeprom data memory */
+	kfree(dev->eedata);
+	dev->eedata = NULL;
+
+	/* Allow override tuner type by a module parameter */
+	if (tuner >= 0)
+		dev->tuner_type = tuner;
+
+	/*
+	 * Dynamically generate a list of valid audio inputs for this
+	 * specific board, mapping them via enum em28xx_amux.
+	 */
+
+	idx = 0;
+	for (i = 0; i < MAX_EM28XX_INPUT; i++) {
+		if (!INPUT(i)->type)
+			continue;
+
+		/* Skip already mapped audio inputs */
+		duplicate_entry = false;
+		for (j = 0; j < idx; j++) {
+			if (INPUT(i)->amux == dev->amux_map[j]) {
+				duplicate_entry = true;
+				break;
+			}
+		}
+		if (duplicate_entry)
+			continue;
+
+		dev->amux_map[idx++] = INPUT(i)->amux;
+	}
+	for (; idx < MAX_EM28XX_INPUT; idx++)
+		dev->amux_map[idx] = EM28XX_AMUX_UNUSED;
+}
+
+void em28xx_setup_xc3028(struct em28xx *dev, struct xc2028_ctrl *ctl)
+{
+	memset(ctl, 0, sizeof(*ctl));
+
+	ctl->fname   = XC2028_DEFAULT_FIRMWARE;
+	ctl->max_len = 64;
+	ctl->mts = em28xx_boards[dev->model].mts_firmware;
+
+	switch (dev->model) {
+	case EM2880_BOARD_EMPIRE_DUAL_TV:
+	case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900:
+	case EM2882_BOARD_TERRATEC_HYBRID_XS:
+	case EM2880_BOARD_TERRATEC_HYBRID_XS:
+	case EM2880_BOARD_TERRATEC_HYBRID_XS_FR:
+	case EM2881_BOARD_PINNACLE_HYBRID_PRO:
+	case EM2882_BOARD_ZOLID_HYBRID_TV_STICK:
+		ctl->demod = XC3028_FE_ZARLINK456;
+		break;
+	case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2:
+	case EM2882_BOARD_PINNACLE_HYBRID_PRO_330E:
+		ctl->demod = XC3028_FE_DEFAULT;
+		break;
+	case EM2880_BOARD_AMD_ATI_TV_WONDER_HD_600:
+		ctl->demod = XC3028_FE_DEFAULT;
+		ctl->fname = XC3028L_DEFAULT_FIRMWARE;
+		break;
+	case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_850:
+	case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950:
+	case EM2880_BOARD_PINNACLE_PCTV_HD_PRO:
+		/* FIXME: Better to specify the needed IF */
+		ctl->demod = XC3028_FE_DEFAULT;
+		break;
+	case EM2883_BOARD_KWORLD_HYBRID_330U:
+	case EM2882_BOARD_DIKOM_DK300:
+	case EM2882_BOARD_KWORLD_VS_DVBT:
+		ctl->demod = XC3028_FE_CHINA;
+		ctl->fname = XC2028_DEFAULT_FIRMWARE;
+		break;
+	case EM2882_BOARD_EVGA_INDTUBE:
+		ctl->demod = XC3028_FE_CHINA;
+		ctl->fname = XC3028L_DEFAULT_FIRMWARE;
+		break;
+	default:
+		ctl->demod = XC3028_FE_OREN538;
+	}
+}
+EXPORT_SYMBOL_GPL(em28xx_setup_xc3028);
+
+static void request_module_async(struct work_struct *work)
+{
+	struct em28xx *dev = container_of(work,
+			     struct em28xx, request_module_wk);
+
+	/*
+	 * The em28xx extensions can be modules or builtin. If the
+	 * modules are already loaded or are built in, those extensions
+	 * can be initialised right now. Otherwise, the module init
+	 * code will do it.
+	 */
+
+	/*
+	 * Devices with an audio-only intf also have a V4L/DVB/RC
+	 * intf. Don't register extensions twice on those devices.
+	 */
+	if (dev->is_audio_only) {
+#if defined(CONFIG_MODULES) && defined(MODULE)
+		request_module("em28xx-alsa");
+#endif
+		return;
+	}
+
+	em28xx_init_extension(dev);
+
+#if defined(CONFIG_MODULES) && defined(MODULE)
+	if (dev->has_video)
+		request_module("em28xx-v4l");
+	if (dev->usb_audio_type == EM28XX_USB_AUDIO_CLASS)
+		request_module("snd-usb-audio");
+	else if (dev->usb_audio_type == EM28XX_USB_AUDIO_VENDOR)
+		request_module("em28xx-alsa");
+	if (dev->board.has_dvb)
+		request_module("em28xx-dvb");
+	if (dev->board.buttons ||
+	    ((dev->board.ir_codes || dev->board.has_ir_i2c) && !disable_ir))
+		request_module("em28xx-rc");
+#endif /* CONFIG_MODULES */
+}
+
+static void request_modules(struct em28xx *dev)
+{
+	INIT_WORK(&dev->request_module_wk, request_module_async);
+	schedule_work(&dev->request_module_wk);
+}
+
+static void flush_request_modules(struct em28xx *dev)
+{
+	flush_work(&dev->request_module_wk);
+}
+
+static int em28xx_media_device_init(struct em28xx *dev,
+				    struct usb_device *udev)
+{
+#ifdef CONFIG_MEDIA_CONTROLLER
+	struct media_device *mdev;
+
+	mdev = kzalloc(sizeof(*mdev), GFP_KERNEL);
+	if (!mdev)
+		return -ENOMEM;
+
+	if (udev->product)
+		media_device_usb_init(mdev, udev, udev->product);
+	else if (udev->manufacturer)
+		media_device_usb_init(mdev, udev, udev->manufacturer);
+	else
+		media_device_usb_init(mdev, udev, dev_name(&dev->intf->dev));
+
+	dev->media_dev = mdev;
+#endif
+	return 0;
+}
+
+static void em28xx_unregister_media_device(struct em28xx *dev)
+{
+#ifdef CONFIG_MEDIA_CONTROLLER
+	if (dev->media_dev) {
+		media_device_unregister(dev->media_dev);
+		media_device_cleanup(dev->media_dev);
+		kfree(dev->media_dev);
+		dev->media_dev = NULL;
+	}
+#endif
+}
+
+/*
+ * em28xx_release_resources()
+ * unregisters the v4l2,i2c and usb devices
+ * called when the device gets disconnected or at module unload
+ */
+static void em28xx_release_resources(struct em28xx *dev)
+{
+	struct usb_device *udev = interface_to_usbdev(dev->intf);
+
+	/*FIXME: I2C IR should be disconnected */
+
+	mutex_lock(&dev->lock);
+
+	em28xx_unregister_media_device(dev);
+
+	if (dev->def_i2c_bus)
+		em28xx_i2c_unregister(dev, 1);
+	em28xx_i2c_unregister(dev, 0);
+
+	if (dev->ts == PRIMARY_TS)
+		usb_put_dev(udev);
+
+	/* Mark device as unused */
+	clear_bit(dev->devno, em28xx_devused);
+
+	mutex_unlock(&dev->lock);
+};
+
+/**
+ * em28xx_free_device() - Free em28xx device
+ *
+ * @ref: struct kref for em28xx device
+ *
+ * This is called when all extensions and em28xx core unregisters a device
+ */
+void em28xx_free_device(struct kref *ref)
+{
+	struct em28xx *dev = kref_to_dev(ref);
+
+	dev_info(&dev->intf->dev, "Freeing device\n");
+
+	if (!dev->disconnected)
+		em28xx_release_resources(dev);
+
+	if (dev->ts == PRIMARY_TS)
+		kfree(dev->alt_max_pkt_size_isoc);
+
+	kfree(dev);
+}
+EXPORT_SYMBOL_GPL(em28xx_free_device);
+
+/*
+ * em28xx_init_dev()
+ * allocates and inits the device structs, registers i2c bus and v4l device
+ */
+static int em28xx_init_dev(struct em28xx *dev, struct usb_device *udev,
+			   struct usb_interface *intf,
+			   int minor)
+{
+	int retval;
+	const char *chip_name = NULL;
+
+	dev->intf = intf;
+	mutex_init(&dev->ctrl_urb_lock);
+	spin_lock_init(&dev->slock);
+
+	dev->em28xx_write_regs = em28xx_write_regs;
+	dev->em28xx_read_reg = em28xx_read_reg;
+	dev->em28xx_read_reg_req_len = em28xx_read_reg_req_len;
+	dev->em28xx_write_regs_req = em28xx_write_regs_req;
+	dev->em28xx_read_reg_req = em28xx_read_reg_req;
+	dev->board.is_em2800 = em28xx_boards[dev->model].is_em2800;
+
+	em28xx_set_model(dev);
+
+	dev->wait_after_write = 5;
+
+	/* Based on the Chip ID, set the device configuration */
+	retval = em28xx_read_reg(dev, EM28XX_R0A_CHIPID);
+	if (retval > 0) {
+		dev->chip_id = retval;
+
+		switch (dev->chip_id) {
+		case CHIP_ID_EM2800:
+			chip_name = "em2800";
+			break;
+		case CHIP_ID_EM2710:
+			chip_name = "em2710";
+			break;
+		case CHIP_ID_EM2750:
+			chip_name = "em2750";
+			break;
+		case CHIP_ID_EM2765:
+			chip_name = "em2765";
+			dev->wait_after_write = 0;
+			dev->is_em25xx = 1;
+			dev->eeprom_addrwidth_16bit = 1;
+			break;
+		case CHIP_ID_EM2820:
+			chip_name = "em2710/2820";
+			if (le16_to_cpu(udev->descriptor.idVendor) == 0xeb1a) {
+				__le16 idProd = udev->descriptor.idProduct;
+
+				if (le16_to_cpu(idProd) == 0x2710)
+					chip_name = "em2710";
+				else if (le16_to_cpu(idProd) == 0x2820)
+					chip_name = "em2820";
+			}
+			/* NOTE: the em2820 is used in webcams, too ! */
+			break;
+		case CHIP_ID_EM2840:
+			chip_name = "em2840";
+			break;
+		case CHIP_ID_EM2860:
+			chip_name = "em2860";
+			break;
+		case CHIP_ID_EM2870:
+			chip_name = "em2870";
+			dev->wait_after_write = 0;
+			break;
+		case CHIP_ID_EM2874:
+			chip_name = "em2874";
+			dev->wait_after_write = 0;
+			dev->eeprom_addrwidth_16bit = 1;
+			break;
+		case CHIP_ID_EM28174:
+			chip_name = "em28174";
+			dev->wait_after_write = 0;
+			dev->eeprom_addrwidth_16bit = 1;
+			break;
+		case CHIP_ID_EM28178:
+			chip_name = "em28178";
+			dev->wait_after_write = 0;
+			dev->eeprom_addrwidth_16bit = 1;
+			break;
+		case CHIP_ID_EM2883:
+			chip_name = "em2882/3";
+			dev->wait_after_write = 0;
+			break;
+		case CHIP_ID_EM2884:
+			chip_name = "em2884";
+			dev->wait_after_write = 0;
+			dev->eeprom_addrwidth_16bit = 1;
+			break;
+		}
+	}
+	if (!chip_name)
+		dev_info(&dev->intf->dev,
+			 "unknown em28xx chip ID (%d)\n", dev->chip_id);
+	else
+		dev_info(&dev->intf->dev, "chip ID is %s\n", chip_name);
+
+	em28xx_media_device_init(dev, udev);
+
+	if (dev->is_audio_only) {
+		retval = em28xx_audio_setup(dev);
+		if (retval)
+			return -ENODEV;
+		em28xx_init_extension(dev);
+
+		return 0;
+	}
+
+	em28xx_pre_card_setup(dev);
+
+	rt_mutex_init(&dev->i2c_bus_lock);
+
+	/* register i2c bus 0 */
+	if (dev->board.is_em2800)
+		retval = em28xx_i2c_register(dev, 0, EM28XX_I2C_ALGO_EM2800);
+	else
+		retval = em28xx_i2c_register(dev, 0, EM28XX_I2C_ALGO_EM28XX);
+	if (retval < 0) {
+		dev_err(&dev->intf->dev,
+			"%s: em28xx_i2c_register bus 0 - error [%d]!\n",
+		       __func__, retval);
+		return retval;
+	}
+
+	/* register i2c bus 1 */
+	if (dev->def_i2c_bus) {
+		if (dev->is_em25xx)
+			retval = em28xx_i2c_register(dev, 1,
+						     EM28XX_I2C_ALGO_EM25XX_BUS_B);
+		else
+			retval = em28xx_i2c_register(dev, 1,
+						     EM28XX_I2C_ALGO_EM28XX);
+		if (retval < 0) {
+			dev_err(&dev->intf->dev,
+				"%s: em28xx_i2c_register bus 1 - error [%d]!\n",
+				__func__, retval);
+
+			em28xx_i2c_unregister(dev, 0);
+
+			return retval;
+		}
+	}
+
+	/* Do board specific init and eeprom reading */
+	em28xx_card_setup(dev);
+
+	return 0;
+}
+
+static int em28xx_duplicate_dev(struct em28xx *dev)
+{
+	int nr;
+	struct em28xx *sec_dev = kzalloc(sizeof(*sec_dev), GFP_KERNEL);
+
+	if (!sec_dev) {
+		dev->dev_next = NULL;
+		return -ENOMEM;
+	}
+	memcpy(sec_dev, dev, sizeof(*sec_dev));
+	/* Check to see next free device and mark as used */
+	do {
+		nr = find_first_zero_bit(em28xx_devused, EM28XX_MAXBOARDS);
+		if (nr >= EM28XX_MAXBOARDS) {
+			/* No free device slots */
+			dev_warn(&dev->intf->dev, ": Supports only %i em28xx boards.\n",
+				 EM28XX_MAXBOARDS);
+			kfree(sec_dev);
+			dev->dev_next = NULL;
+			return -ENOMEM;
+		}
+	} while (test_and_set_bit(nr, em28xx_devused));
+	sec_dev->devno = nr;
+	snprintf(sec_dev->name, 28, "em28xx #%d", nr);
+	sec_dev->dev_next = NULL;
+	dev->dev_next = sec_dev;
+	return 0;
+}
+
+/* high bandwidth multiplier, as encoded in highspeed endpoint descriptors */
+#define hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03))
+
+static void em28xx_check_usb_descriptor(struct em28xx *dev,
+					struct usb_device *udev,
+					struct usb_interface *intf,
+					int alt, int ep,
+					bool *has_vendor_audio,
+					bool *has_video,
+					bool *has_dvb)
+{
+	const struct usb_endpoint_descriptor *e;
+	int sizedescr, size;
+
+	/*
+	 * NOTE:
+	 *
+	 * Old logic with support for isoc transfers only was:
+	 *  0x82	isoc		=> analog
+	 *  0x83	isoc		=> audio
+	 *  0x84	isoc		=> digital
+	 *
+	 * New logic with support for bulk transfers
+	 *  0x82	isoc		=> analog
+	 *  0x82	bulk		=> analog
+	 *  0x83	isoc*		=> audio
+	 *  0x84	isoc		=> digital
+	 *  0x84	bulk		=> analog or digital**
+	 *  0x85	isoc		=> digital TS2
+	 *  0x85	bulk		=> digital TS2
+	 * (*: audio should always be isoc)
+	 * (**: analog, if ep 0x82 is isoc, otherwise digital)
+	 *
+	 * The new logic preserves backwards compatibility and
+	 * reflects the endpoint configurations we have seen
+	 * so far. But there might be devices for which this
+	 * logic is not sufficient...
+	 */
+
+	e = &intf->altsetting[alt].endpoint[ep].desc;
+
+	if (!usb_endpoint_dir_in(e))
+		return;
+
+	sizedescr = le16_to_cpu(e->wMaxPacketSize);
+	size = sizedescr & 0x7ff;
+
+	if (udev->speed == USB_SPEED_HIGH)
+		size = size * hb_mult(sizedescr);
+
+	/* Only inspect input endpoints */
+
+	switch (e->bEndpointAddress) {
+	case 0x82:
+		*has_video = true;
+		if (usb_endpoint_xfer_isoc(e)) {
+			dev->analog_ep_isoc = e->bEndpointAddress;
+			dev->alt_max_pkt_size_isoc[alt] = size;
+		} else if (usb_endpoint_xfer_bulk(e)) {
+			dev->analog_ep_bulk = e->bEndpointAddress;
+		}
+		return;
+	case 0x83:
+		if (usb_endpoint_xfer_isoc(e))
+			*has_vendor_audio = true;
+		else
+			dev_err(&intf->dev,
+				"error: skipping audio endpoint 0x83, because it uses bulk transfers !\n");
+		return;
+	case 0x84:
+		if (*has_video && (usb_endpoint_xfer_bulk(e))) {
+			dev->analog_ep_bulk = e->bEndpointAddress;
+		} else {
+			if (usb_endpoint_xfer_isoc(e)) {
+				if (size > dev->dvb_max_pkt_size_isoc) {
+					/*
+					 * 2) some manufacturers (e.g. Terratec)
+					 * disable endpoints by setting
+					 * wMaxPacketSize to 0 bytes for all
+					 * alt settings. So far, we've seen
+					 * this for DVB isoc endpoints only.
+					 */
+					*has_dvb = true;
+					dev->dvb_ep_isoc = e->bEndpointAddress;
+					dev->dvb_max_pkt_size_isoc = size;
+					dev->dvb_alt_isoc = alt;
+				}
+			} else {
+				*has_dvb = true;
+				dev->dvb_ep_bulk = e->bEndpointAddress;
+			}
+		}
+		return;
+	case 0x85:
+		if (usb_endpoint_xfer_isoc(e)) {
+			if (size > dev->dvb_max_pkt_size_isoc_ts2) {
+				dev->dvb_ep_isoc_ts2 = e->bEndpointAddress;
+				dev->dvb_max_pkt_size_isoc_ts2 = size;
+				dev->dvb_alt_isoc = alt;
+			}
+		} else {
+			dev->dvb_ep_bulk_ts2 = e->bEndpointAddress;
+		}
+		return;
+	}
+}
+
+/*
+ * em28xx_usb_probe()
+ * checks for supported devices
+ */
+static int em28xx_usb_probe(struct usb_interface *intf,
+			    const struct usb_device_id *id)
+{
+	struct usb_device *udev;
+	struct em28xx *dev = NULL;
+	int retval;
+	bool has_vendor_audio = false, has_video = false, has_dvb = false;
+	int i, nr, try_bulk;
+	const int ifnum = intf->altsetting[0].desc.bInterfaceNumber;
+	char *speed;
+
+	udev = usb_get_dev(interface_to_usbdev(intf));
+
+	/* Check to see next free device and mark as used */
+	do {
+		nr = find_first_zero_bit(em28xx_devused, EM28XX_MAXBOARDS);
+		if (nr >= EM28XX_MAXBOARDS) {
+			/* No free device slots */
+			dev_err(&intf->dev,
+				"Driver supports up to %i em28xx boards.\n",
+			       EM28XX_MAXBOARDS);
+			retval = -ENOMEM;
+			goto err_no_slot;
+		}
+	} while (test_and_set_bit(nr, em28xx_devused));
+
+	/* Don't register audio interfaces */
+	if (intf->altsetting[0].desc.bInterfaceClass == USB_CLASS_AUDIO) {
+		dev_info(&intf->dev,
+			"audio device (%04x:%04x): interface %i, class %i\n",
+			le16_to_cpu(udev->descriptor.idVendor),
+			le16_to_cpu(udev->descriptor.idProduct),
+			ifnum,
+			intf->altsetting[0].desc.bInterfaceClass);
+
+		retval = -ENODEV;
+		goto err;
+	}
+
+	/* allocate memory for our device state and initialize it */
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev) {
+		retval = -ENOMEM;
+		goto err;
+	}
+
+	/* compute alternate max packet sizes */
+	dev->alt_max_pkt_size_isoc = kcalloc(intf->num_altsetting,
+					     sizeof(dev->alt_max_pkt_size_isoc[0]),
+					     GFP_KERNEL);
+	if (!dev->alt_max_pkt_size_isoc) {
+		kfree(dev);
+		retval = -ENOMEM;
+		goto err;
+	}
+
+	/* Get endpoints */
+	for (i = 0; i < intf->num_altsetting; i++) {
+		int ep;
+
+		for (ep = 0;
+		     ep < intf->altsetting[i].desc.bNumEndpoints;
+		     ep++)
+			em28xx_check_usb_descriptor(dev, udev, intf,
+						    i, ep,
+						    &has_vendor_audio,
+						    &has_video,
+						    &has_dvb);
+	}
+
+	if (!(has_vendor_audio || has_video || has_dvb)) {
+		retval = -ENODEV;
+		goto err_free;
+	}
+
+	switch (udev->speed) {
+	case USB_SPEED_LOW:
+		speed = "1.5";
+		break;
+	case USB_SPEED_UNKNOWN:
+	case USB_SPEED_FULL:
+		speed = "12";
+		break;
+	case USB_SPEED_HIGH:
+		speed = "480";
+		break;
+	default:
+		speed = "unknown";
+	}
+
+	dev_info(&intf->dev,
+		"New device %s %s @ %s Mbps (%04x:%04x, interface %d, class %d)\n",
+		udev->manufacturer ? udev->manufacturer : "",
+		udev->product ? udev->product : "",
+		speed,
+		le16_to_cpu(udev->descriptor.idVendor),
+		le16_to_cpu(udev->descriptor.idProduct),
+		ifnum,
+		intf->altsetting->desc.bInterfaceNumber);
+
+	/*
+	 * Make sure we have 480 Mbps of bandwidth, otherwise things like
+	 * video stream wouldn't likely work, since 12 Mbps is generally
+	 * not enough even for most Digital TV streams.
+	 */
+	if (udev->speed != USB_SPEED_HIGH && disable_usb_speed_check == 0) {
+		dev_err(&intf->dev, "Device initialization failed.\n");
+		dev_err(&intf->dev,
+			"Device must be connected to a high-speed USB 2.0 port.\n");
+		retval = -ENODEV;
+		goto err_free;
+	}
+
+	dev->devno = nr;
+	dev->model = id->driver_info;
+	dev->alt   = -1;
+	dev->is_audio_only = has_vendor_audio && !(has_video || has_dvb);
+	dev->has_video = has_video;
+	dev->ifnum = ifnum;
+
+	dev->ts = PRIMARY_TS;
+	snprintf(dev->name, 28, "em28xx");
+	dev->dev_next = NULL;
+
+	if (has_vendor_audio) {
+		dev_info(&intf->dev,
+			"Audio interface %i found (Vendor Class)\n", ifnum);
+		dev->usb_audio_type = EM28XX_USB_AUDIO_VENDOR;
+	}
+	/* Checks if audio is provided by a USB Audio Class intf */
+	for (i = 0; i < udev->config->desc.bNumInterfaces; i++) {
+		struct usb_interface *uif = udev->config->interface[i];
+
+		if (uif->altsetting[0].desc.bInterfaceClass == USB_CLASS_AUDIO) {
+			if (has_vendor_audio)
+				dev_err(&intf->dev,
+					"em28xx: device seems to have vendor AND usb audio class interfaces !\n"
+					"\t\tThe vendor interface will be ignored. Please contact the developers <linux-media@vger.kernel.org>\n");
+			dev->usb_audio_type = EM28XX_USB_AUDIO_CLASS;
+			break;
+		}
+	}
+
+	if (has_video)
+		dev_info(&intf->dev, "Video interface %i found:%s%s\n",
+			ifnum,
+			dev->analog_ep_bulk ? " bulk" : "",
+			dev->analog_ep_isoc ? " isoc" : "");
+	if (has_dvb)
+		dev_info(&intf->dev, "DVB interface %i found:%s%s\n",
+			ifnum,
+			dev->dvb_ep_bulk ? " bulk" : "",
+			dev->dvb_ep_isoc ? " isoc" : "");
+
+	dev->num_alt = intf->num_altsetting;
+
+	if ((unsigned int)card[nr] < em28xx_bcount)
+		dev->model = card[nr];
+
+	/* save our data pointer in this intf device */
+	usb_set_intfdata(intf, dev);
+
+	/* allocate device struct and check if the device is a webcam */
+	mutex_init(&dev->lock);
+	retval = em28xx_init_dev(dev, udev, intf, nr);
+	if (retval)
+		goto err_free;
+
+	if (usb_xfer_mode < 0) {
+		if (dev->is_webcam)
+			try_bulk = 1;
+		else
+			try_bulk = 0;
+	} else {
+		try_bulk = usb_xfer_mode > 0;
+	}
+
+	/* Disable V4L2 if the device doesn't have a decoder or image sensor */
+	if (has_video &&
+	    dev->board.decoder == EM28XX_NODECODER &&
+	    dev->em28xx_sensor == EM28XX_NOSENSOR) {
+		dev_err(&intf->dev,
+			"Currently, V4L2 is not supported on this model\n");
+		has_video = false;
+		dev->has_video = false;
+	}
+
+	if (dev->board.has_dual_ts &&
+	    (dev->tuner_type != TUNER_ABSENT || INPUT(0)->type)) {
+		/*
+		 * The logic with sets alternate is not ready for dual-tuners
+		 * which analog modes.
+		 */
+		dev_err(&intf->dev,
+			"We currently don't support analog TV or stream capture on dual tuners.\n");
+		has_video = false;
+	}
+
+	/* Select USB transfer types to use */
+	if (has_video) {
+		if (!dev->analog_ep_isoc || (try_bulk && dev->analog_ep_bulk))
+			dev->analog_xfer_bulk = 1;
+		dev_info(&intf->dev, "analog set to %s mode.\n",
+			dev->analog_xfer_bulk ? "bulk" : "isoc");
+	}
+	if (has_dvb) {
+		if (!dev->dvb_ep_isoc || (try_bulk && dev->dvb_ep_bulk))
+			dev->dvb_xfer_bulk = 1;
+		dev_info(&intf->dev, "dvb set to %s mode.\n",
+			dev->dvb_xfer_bulk ? "bulk" : "isoc");
+	}
+
+	if (dev->board.has_dual_ts && em28xx_duplicate_dev(dev) == 0) {
+		dev->dev_next->ts = SECONDARY_TS;
+		dev->dev_next->alt   = -1;
+		dev->dev_next->is_audio_only = has_vendor_audio &&
+						!(has_video || has_dvb);
+		dev->dev_next->has_video = false;
+		dev->dev_next->ifnum = ifnum;
+		dev->dev_next->model = id->driver_info;
+
+		mutex_init(&dev->dev_next->lock);
+		retval = em28xx_init_dev(dev->dev_next, udev, intf,
+					 dev->dev_next->devno);
+		if (retval)
+			goto err_free;
+
+		dev->dev_next->board.ir_codes = NULL; /* No IR for 2nd tuner */
+		dev->dev_next->board.has_ir_i2c = 0; /* No IR for 2nd tuner */
+
+		if (usb_xfer_mode < 0) {
+			if (dev->dev_next->is_webcam)
+				try_bulk = 1;
+			else
+				try_bulk = 0;
+		} else {
+			try_bulk = usb_xfer_mode > 0;
+		}
+
+		/* Select USB transfer types to use */
+		if (has_dvb) {
+			if (!dev->dvb_ep_isoc_ts2 ||
+			    (try_bulk && dev->dvb_ep_bulk_ts2))
+				dev->dev_next->dvb_xfer_bulk = 1;
+			dev_info(&dev->intf->dev, "dvb ts2 set to %s mode.\n",
+				 dev->dev_next->dvb_xfer_bulk ? "bulk" : "isoc");
+		}
+
+		dev->dev_next->dvb_ep_isoc = dev->dvb_ep_isoc_ts2;
+		dev->dev_next->dvb_ep_bulk = dev->dvb_ep_bulk_ts2;
+		dev->dev_next->dvb_max_pkt_size_isoc = dev->dvb_max_pkt_size_isoc_ts2;
+		dev->dev_next->dvb_alt_isoc = dev->dvb_alt_isoc;
+
+		/* Configuare hardware to support TS2*/
+		if (dev->dvb_xfer_bulk) {
+			/* The ep4 and ep5 are configuared for BULK */
+			em28xx_write_reg(dev, 0x0b, 0x96);
+			mdelay(100);
+			em28xx_write_reg(dev, 0x0b, 0x80);
+			mdelay(100);
+		} else {
+			/* The ep4 and ep5 are configuared for ISO */
+			em28xx_write_reg(dev, 0x0b, 0x96);
+			mdelay(100);
+			em28xx_write_reg(dev, 0x0b, 0x82);
+			mdelay(100);
+		}
+
+		kref_init(&dev->dev_next->ref);
+	}
+
+	kref_init(&dev->ref);
+
+	request_modules(dev);
+
+	/*
+	 * Do it at the end, to reduce dynamic configuration changes during
+	 * the device init. Yet, as request_modules() can be async, the
+	 * topology will likely change after the load of the em28xx subdrivers.
+	 */
+#ifdef CONFIG_MEDIA_CONTROLLER
+	retval = media_device_register(dev->media_dev);
+#endif
+
+	return 0;
+
+err_free:
+	kfree(dev->alt_max_pkt_size_isoc);
+	kfree(dev);
+
+err:
+	clear_bit(nr, em28xx_devused);
+
+err_no_slot:
+	usb_put_dev(udev);
+	return retval;
+}
+
+/*
+ * em28xx_usb_disconnect()
+ * called when the device gets disconnected
+ * video device will be unregistered on v4l2_close in case it is still open
+ */
+static void em28xx_usb_disconnect(struct usb_interface *intf)
+{
+	struct em28xx *dev;
+
+	dev = usb_get_intfdata(intf);
+	usb_set_intfdata(intf, NULL);
+
+	if (!dev)
+		return;
+
+	if (dev->dev_next) {
+		dev->dev_next->disconnected = 1;
+		dev_info(&dev->intf->dev, "Disconnecting %s\n",
+			 dev->dev_next->name);
+		flush_request_modules(dev->dev_next);
+	}
+
+	dev->disconnected = 1;
+
+	dev_info(&dev->intf->dev, "Disconnecting %s\n", dev->name);
+
+	flush_request_modules(dev);
+
+	em28xx_close_extension(dev);
+
+	if (dev->dev_next)
+		em28xx_release_resources(dev->dev_next);
+	em28xx_release_resources(dev);
+
+	if (dev->dev_next) {
+		kref_put(&dev->dev_next->ref, em28xx_free_device);
+		dev->dev_next = NULL;
+	}
+	kref_put(&dev->ref, em28xx_free_device);
+}
+
+static int em28xx_usb_suspend(struct usb_interface *intf,
+			      pm_message_t message)
+{
+	struct em28xx *dev;
+
+	dev = usb_get_intfdata(intf);
+	if (!dev)
+		return 0;
+	em28xx_suspend_extension(dev);
+	return 0;
+}
+
+static int em28xx_usb_resume(struct usb_interface *intf)
+{
+	struct em28xx *dev;
+
+	dev = usb_get_intfdata(intf);
+	if (!dev)
+		return 0;
+	em28xx_resume_extension(dev);
+	return 0;
+}
+
+static struct usb_driver em28xx_usb_driver = {
+	.name = "em28xx",
+	.probe = em28xx_usb_probe,
+	.disconnect = em28xx_usb_disconnect,
+	.suspend = em28xx_usb_suspend,
+	.resume = em28xx_usb_resume,
+	.reset_resume = em28xx_usb_resume,
+	.id_table = em28xx_id_table,
+};
+
+module_usb_driver(em28xx_usb_driver);
diff --git a/drivers/media/usb/em28xx/em28xx-core.c b/drivers/media/usb/em28xx/em28xx-core.c
new file mode 100644
index 0000000..5657f87
--- /dev/null
+++ b/drivers/media/usb/em28xx/em28xx-core.c
@@ -0,0 +1,1182 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// em28xx-core.c - driver for Empia EM2800/EM2820/2840 USB video capture devices
+//
+// Copyright (C) 2005 Ludovico Cavedon <cavedon@sssup.it>
+//		      Markus Rechberger <mrechberger@gmail.com>
+//		      Mauro Carvalho Chehab <mchehab@kernel.org>
+//		      Sascha Sommer <saschasommer@freenet.de>
+// Copyright (C) 2012 Frank Schäfer <fschaefer.oss@googlemail.com>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include "em28xx.h"
+
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/vmalloc.h>
+#include <sound/ac97_codec.h>
+#include <media/v4l2-common.h>
+
+#define DRIVER_AUTHOR "Ludovico Cavedon <cavedon@sssup.it>, " \
+		      "Markus Rechberger <mrechberger@gmail.com>, " \
+		      "Mauro Carvalho Chehab <mchehab@kernel.org>, " \
+		      "Sascha Sommer <saschasommer@freenet.de>"
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION(EM28XX_VERSION);
+
+/* #define ENABLE_DEBUG_ISOC_FRAMES */
+
+static unsigned int core_debug;
+module_param(core_debug, int, 0644);
+MODULE_PARM_DESC(core_debug, "enable debug messages [core and isoc]");
+
+#define em28xx_coredbg(fmt, arg...) do {				\
+	if (core_debug)							\
+		dev_printk(KERN_DEBUG, &dev->intf->dev,			\
+			   "core: %s: " fmt, __func__, ## arg);		\
+} while (0)
+
+static unsigned int reg_debug;
+module_param(reg_debug, int, 0644);
+MODULE_PARM_DESC(reg_debug, "enable debug messages [URB reg]");
+
+#define em28xx_regdbg(fmt, arg...) do {				\
+	if (reg_debug)							\
+		dev_printk(KERN_DEBUG, &dev->intf->dev,			\
+			   "reg: %s: " fmt, __func__, ## arg);		\
+} while (0)
+
+/* FIXME: don't abuse core_debug */
+#define em28xx_isocdbg(fmt, arg...) do {				\
+	if (core_debug)							\
+		dev_printk(KERN_DEBUG, &dev->intf->dev,			\
+			   "core: %s: " fmt, __func__, ## arg);		\
+} while (0)
+
+/*
+ * em28xx_read_reg_req()
+ * reads data from the usb device specifying bRequest
+ */
+int em28xx_read_reg_req_len(struct em28xx *dev, u8 req, u16 reg,
+			    char *buf, int len)
+{
+	int ret;
+	struct usb_device *udev = interface_to_usbdev(dev->intf);
+	int pipe = usb_rcvctrlpipe(udev, 0);
+
+	if (dev->disconnected)
+		return -ENODEV;
+
+	if (len > URB_MAX_CTRL_SIZE)
+		return -EINVAL;
+
+	mutex_lock(&dev->ctrl_urb_lock);
+	ret = usb_control_msg(udev, pipe, req,
+			      USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      0x0000, reg, dev->urb_buf, len, HZ);
+	if (ret < 0) {
+		em28xx_regdbg("(pipe 0x%08x): IN:  %02x %02x %02x %02x %02x %02x %02x %02x  failed with error %i\n",
+			      pipe,
+			      USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      req, 0, 0,
+			      reg & 0xff, reg >> 8,
+			      len & 0xff, len >> 8, ret);
+		mutex_unlock(&dev->ctrl_urb_lock);
+		return usb_translate_errors(ret);
+	}
+
+	if (len)
+		memcpy(buf, dev->urb_buf, len);
+
+	mutex_unlock(&dev->ctrl_urb_lock);
+
+	em28xx_regdbg("(pipe 0x%08x): IN:  %02x %02x %02x %02x %02x %02x %02x %02x <<< %*ph\n",
+		      pipe, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+		      req, 0, 0,
+		      reg & 0xff, reg >> 8,
+		      len & 0xff, len >> 8, len, buf);
+
+	return ret;
+}
+
+/*
+ * em28xx_read_reg_req()
+ * reads data from the usb device specifying bRequest
+ */
+int em28xx_read_reg_req(struct em28xx *dev, u8 req, u16 reg)
+{
+	int ret;
+	u8 val;
+
+	ret = em28xx_read_reg_req_len(dev, req, reg, &val, 1);
+	if (ret < 0)
+		return ret;
+
+	return val;
+}
+
+int em28xx_read_reg(struct em28xx *dev, u16 reg)
+{
+	return em28xx_read_reg_req(dev, USB_REQ_GET_STATUS, reg);
+}
+EXPORT_SYMBOL_GPL(em28xx_read_reg);
+
+/*
+ * em28xx_write_regs_req()
+ * sends data to the usb device, specifying bRequest
+ */
+int em28xx_write_regs_req(struct em28xx *dev, u8 req, u16 reg, char *buf,
+			  int len)
+{
+	int ret;
+	struct usb_device *udev = interface_to_usbdev(dev->intf);
+	int pipe = usb_sndctrlpipe(udev, 0);
+
+	if (dev->disconnected)
+		return -ENODEV;
+
+	if (len < 1 || len > URB_MAX_CTRL_SIZE)
+		return -EINVAL;
+
+	mutex_lock(&dev->ctrl_urb_lock);
+	memcpy(dev->urb_buf, buf, len);
+	ret = usb_control_msg(udev, pipe, req,
+			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      0x0000, reg, dev->urb_buf, len, HZ);
+	mutex_unlock(&dev->ctrl_urb_lock);
+
+	if (ret < 0) {
+		em28xx_regdbg("(pipe 0x%08x): OUT:  %02x %02x %02x %02x %02x %02x %02x %02x >>> %*ph  failed with error %i\n",
+			      pipe,
+			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      req, 0, 0,
+			      reg & 0xff, reg >> 8,
+			      len & 0xff, len >> 8, len, buf, ret);
+		return usb_translate_errors(ret);
+	}
+
+	em28xx_regdbg("(pipe 0x%08x): OUT:  %02x %02x %02x %02x %02x %02x %02x %02x >>> %*ph\n",
+		      pipe,
+		      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+		      req, 0, 0,
+		      reg & 0xff, reg >> 8,
+		      len & 0xff, len >> 8, len, buf);
+
+	if (dev->wait_after_write)
+		msleep(dev->wait_after_write);
+
+	return ret;
+}
+
+int em28xx_write_regs(struct em28xx *dev, u16 reg, char *buf, int len)
+{
+	return em28xx_write_regs_req(dev, USB_REQ_GET_STATUS, reg, buf, len);
+}
+EXPORT_SYMBOL_GPL(em28xx_write_regs);
+
+/* Write a single register */
+int em28xx_write_reg(struct em28xx *dev, u16 reg, u8 val)
+{
+	return em28xx_write_regs(dev, reg, &val, 1);
+}
+EXPORT_SYMBOL_GPL(em28xx_write_reg);
+
+/*
+ * em28xx_write_reg_bits()
+ * sets only some bits (specified by bitmask) of a register, by first reading
+ * the actual value
+ */
+int em28xx_write_reg_bits(struct em28xx *dev, u16 reg, u8 val,
+			  u8 bitmask)
+{
+	int oldval;
+	u8 newval;
+
+	oldval = em28xx_read_reg(dev, reg);
+	if (oldval < 0)
+		return oldval;
+
+	newval = (((u8)oldval) & ~bitmask) | (val & bitmask);
+
+	return em28xx_write_regs(dev, reg, &newval, 1);
+}
+EXPORT_SYMBOL_GPL(em28xx_write_reg_bits);
+
+/*
+ * em28xx_toggle_reg_bits()
+ * toggles/inverts the bits (specified by bitmask) of a register
+ */
+int em28xx_toggle_reg_bits(struct em28xx *dev, u16 reg, u8 bitmask)
+{
+	int oldval;
+	u8 newval;
+
+	oldval = em28xx_read_reg(dev, reg);
+	if (oldval < 0)
+		return oldval;
+
+	newval = (~oldval & bitmask) | (oldval & ~bitmask);
+
+	return em28xx_write_reg(dev, reg, newval);
+}
+EXPORT_SYMBOL_GPL(em28xx_toggle_reg_bits);
+
+/*
+ * em28xx_is_ac97_ready()
+ * Checks if ac97 is ready
+ */
+static int em28xx_is_ac97_ready(struct em28xx *dev)
+{
+	unsigned long timeout = jiffies + msecs_to_jiffies(EM28XX_AC97_XFER_TIMEOUT);
+	int ret;
+
+	/* Wait up to 50 ms for AC97 command to complete */
+	while (time_is_after_jiffies(timeout)) {
+		ret = em28xx_read_reg(dev, EM28XX_R43_AC97BUSY);
+		if (ret < 0)
+			return ret;
+
+		if (!(ret & 0x01))
+			return 0;
+		msleep(5);
+	}
+
+	dev_warn(&dev->intf->dev,
+		 "AC97 command still being executed: not handled properly!\n");
+	return -EBUSY;
+}
+
+/*
+ * em28xx_read_ac97()
+ * write a 16 bit value to the specified AC97 address (LSB first!)
+ */
+int em28xx_read_ac97(struct em28xx *dev, u8 reg)
+{
+	int ret;
+	u8 addr = (reg & 0x7f) | 0x80;
+	__le16 val;
+
+	ret = em28xx_is_ac97_ready(dev);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, EM28XX_R42_AC97ADDR, &addr, 1);
+	if (ret < 0)
+		return ret;
+
+	ret = dev->em28xx_read_reg_req_len(dev, 0, EM28XX_R40_AC97LSB,
+					   (u8 *)&val, sizeof(val));
+
+	if (ret < 0)
+		return ret;
+	return le16_to_cpu(val);
+}
+EXPORT_SYMBOL_GPL(em28xx_read_ac97);
+
+/*
+ * em28xx_write_ac97()
+ * write a 16 bit value to the specified AC97 address (LSB first!)
+ */
+int em28xx_write_ac97(struct em28xx *dev, u8 reg, u16 val)
+{
+	int ret;
+	u8 addr = reg & 0x7f;
+	__le16 value;
+
+	value = cpu_to_le16(val);
+
+	ret = em28xx_is_ac97_ready(dev);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, EM28XX_R40_AC97LSB, (u8 *)&value, 2);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_regs(dev, EM28XX_R42_AC97ADDR, &addr, 1);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(em28xx_write_ac97);
+
+struct em28xx_vol_itable {
+	enum em28xx_amux mux;
+	u8		 reg;
+};
+
+static struct em28xx_vol_itable inputs[] = {
+	{ EM28XX_AMUX_VIDEO,	AC97_VIDEO	},
+	{ EM28XX_AMUX_LINE_IN,	AC97_LINE	},
+	{ EM28XX_AMUX_PHONE,	AC97_PHONE	},
+	{ EM28XX_AMUX_MIC,	AC97_MIC	},
+	{ EM28XX_AMUX_CD,	AC97_CD		},
+	{ EM28XX_AMUX_AUX,	AC97_AUX	},
+	{ EM28XX_AMUX_PCM_OUT,	AC97_PCM	},
+};
+
+static int set_ac97_input(struct em28xx *dev)
+{
+	int ret, i;
+	enum em28xx_amux amux = dev->ctl_ainput;
+
+	/*
+	 * EM28XX_AMUX_VIDEO2 is a special case used to indicate that
+	 * em28xx should point to LINE IN, while AC97 should use VIDEO
+	 */
+	if (amux == EM28XX_AMUX_VIDEO2)
+		amux = EM28XX_AMUX_VIDEO;
+
+	/* Mute all entres but the one that were selected */
+	for (i = 0; i < ARRAY_SIZE(inputs); i++) {
+		if (amux == inputs[i].mux)
+			ret = em28xx_write_ac97(dev, inputs[i].reg, 0x0808);
+		else
+			ret = em28xx_write_ac97(dev, inputs[i].reg, 0x8000);
+
+		if (ret < 0)
+			dev_warn(&dev->intf->dev,
+				 "couldn't setup AC97 register %d\n",
+				 inputs[i].reg);
+	}
+	return 0;
+}
+
+static int em28xx_set_audio_source(struct em28xx *dev)
+{
+	int ret;
+	u8 input;
+
+	if (dev->board.is_em2800) {
+		if (dev->ctl_ainput == EM28XX_AMUX_VIDEO)
+			input = EM2800_AUDIO_SRC_TUNER;
+		else
+			input = EM2800_AUDIO_SRC_LINE;
+
+		ret = em28xx_write_regs(dev, EM2800_R08_AUDIOSRC, &input, 1);
+		if (ret < 0)
+			return ret;
+	}
+
+	if (dev->has_msp34xx) {
+		input = EM28XX_AUDIO_SRC_TUNER;
+	} else {
+		switch (dev->ctl_ainput) {
+		case EM28XX_AMUX_VIDEO:
+			input = EM28XX_AUDIO_SRC_TUNER;
+			break;
+		default:
+			input = EM28XX_AUDIO_SRC_LINE;
+			break;
+		}
+	}
+
+	if (dev->board.mute_gpio && dev->mute)
+		em28xx_gpio_set(dev, dev->board.mute_gpio);
+	else
+		em28xx_gpio_set(dev, INPUT(dev->ctl_input)->gpio);
+
+	ret = em28xx_write_reg_bits(dev, EM28XX_R0E_AUDIOSRC, input, 0xc0);
+	if (ret < 0)
+		return ret;
+	usleep_range(10000, 11000);
+
+	switch (dev->audio_mode.ac97) {
+	case EM28XX_NO_AC97:
+		break;
+	default:
+		ret = set_ac97_input(dev);
+	}
+
+	return ret;
+}
+
+struct em28xx_vol_otable {
+	enum em28xx_aout mux;
+	u8		 reg;
+};
+
+static const struct em28xx_vol_otable outputs[] = {
+	{ EM28XX_AOUT_MASTER, AC97_MASTER		},
+	{ EM28XX_AOUT_LINE,   AC97_HEADPHONE		},
+	{ EM28XX_AOUT_MONO,   AC97_MASTER_MONO		},
+	{ EM28XX_AOUT_LFE,    AC97_CENTER_LFE_MASTER	},
+	{ EM28XX_AOUT_SURR,   AC97_SURROUND_MASTER	},
+};
+
+int em28xx_audio_analog_set(struct em28xx *dev)
+{
+	int ret, i;
+	u8 xclk;
+
+	if (dev->int_audio_type == EM28XX_INT_AUDIO_NONE)
+		return 0;
+
+	/*
+	 * It is assumed that all devices use master volume for output.
+	 * It would be possible to use also line output.
+	 */
+	if (dev->audio_mode.ac97 != EM28XX_NO_AC97) {
+		/* Mute all outputs */
+		for (i = 0; i < ARRAY_SIZE(outputs); i++) {
+			ret = em28xx_write_ac97(dev, outputs[i].reg, 0x8000);
+			if (ret < 0)
+				dev_warn(&dev->intf->dev,
+					 "couldn't setup AC97 register %d\n",
+					 outputs[i].reg);
+		}
+	}
+
+	xclk = dev->board.xclk & 0x7f;
+	if (!dev->mute)
+		xclk |= EM28XX_XCLK_AUDIO_UNMUTE;
+
+	ret = em28xx_write_reg(dev, EM28XX_R0F_XCLK, xclk);
+	if (ret < 0)
+		return ret;
+	usleep_range(10000, 11000);
+
+	/* Selects the proper audio input */
+	ret = em28xx_set_audio_source(dev);
+
+	/* Sets volume */
+	if (dev->audio_mode.ac97 != EM28XX_NO_AC97) {
+		int vol;
+
+		em28xx_write_ac97(dev, AC97_POWERDOWN, 0x4200);
+		em28xx_write_ac97(dev, AC97_EXTENDED_STATUS, 0x0031);
+		em28xx_write_ac97(dev, AC97_PCM_LR_ADC_RATE, 0xbb80);
+
+		/* LSB: left channel - both channels with the same level */
+		vol = (0x1f - dev->volume) | ((0x1f - dev->volume) << 8);
+
+		/* Mute device, if needed */
+		if (dev->mute)
+			vol |= 0x8000;
+
+		/* Sets volume */
+		for (i = 0; i < ARRAY_SIZE(outputs); i++) {
+			if (dev->ctl_aoutput & outputs[i].mux)
+				ret = em28xx_write_ac97(dev, outputs[i].reg,
+							vol);
+			if (ret < 0)
+				dev_warn(&dev->intf->dev,
+					 "couldn't setup AC97 register %d\n",
+					 outputs[i].reg);
+		}
+
+		if (dev->ctl_aoutput & EM28XX_AOUT_PCM_IN) {
+			int sel = ac97_return_record_select(dev->ctl_aoutput);
+
+			/*
+			 * Use the same input for both left and right
+			 * channels
+			 */
+			sel |= (sel << 8);
+
+			em28xx_write_ac97(dev, AC97_REC_SEL, sel);
+		}
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(em28xx_audio_analog_set);
+
+int em28xx_audio_setup(struct em28xx *dev)
+{
+	int vid1, vid2, feat, cfg;
+	u32 vid = 0;
+	u8 i2s_samplerates;
+
+	if (dev->chip_id == CHIP_ID_EM2870 ||
+	    dev->chip_id == CHIP_ID_EM2874 ||
+	    dev->chip_id == CHIP_ID_EM28174 ||
+	    dev->chip_id == CHIP_ID_EM28178) {
+		/* Digital only device - don't load any alsa module */
+		dev->int_audio_type = EM28XX_INT_AUDIO_NONE;
+		dev->usb_audio_type = EM28XX_USB_AUDIO_NONE;
+		return 0;
+	}
+
+	/* See how this device is configured */
+	cfg = em28xx_read_reg(dev, EM28XX_R00_CHIPCFG);
+	dev_info(&dev->intf->dev, "Config register raw data: 0x%02x\n", cfg);
+	if (cfg < 0) { /* Register read error */
+		/* Be conservative */
+		dev->int_audio_type = EM28XX_INT_AUDIO_AC97;
+	} else if ((cfg & EM28XX_CHIPCFG_AUDIOMASK) == 0x00) {
+		/* The device doesn't have vendor audio at all */
+		dev->int_audio_type = EM28XX_INT_AUDIO_NONE;
+		dev->usb_audio_type = EM28XX_USB_AUDIO_NONE;
+		return 0;
+	} else if ((cfg & EM28XX_CHIPCFG_AUDIOMASK) != EM28XX_CHIPCFG_AC97) {
+		dev->int_audio_type = EM28XX_INT_AUDIO_I2S;
+		if (dev->chip_id < CHIP_ID_EM2860 &&
+		    (cfg & EM28XX_CHIPCFG_AUDIOMASK) ==
+		    EM2820_CHIPCFG_I2S_1_SAMPRATE)
+			i2s_samplerates = 1;
+		else if (dev->chip_id >= CHIP_ID_EM2860 &&
+			 (cfg & EM28XX_CHIPCFG_AUDIOMASK) ==
+			 EM2860_CHIPCFG_I2S_5_SAMPRATES)
+			i2s_samplerates = 5;
+		else
+			i2s_samplerates = 3;
+		dev_info(&dev->intf->dev, "I2S Audio (%d sample rate(s))\n",
+			 i2s_samplerates);
+		/* Skip the code that does AC97 vendor detection */
+		dev->audio_mode.ac97 = EM28XX_NO_AC97;
+		goto init_audio;
+	} else {
+		dev->int_audio_type = EM28XX_INT_AUDIO_AC97;
+	}
+
+	dev->audio_mode.ac97 = EM28XX_AC97_OTHER;
+
+	vid1 = em28xx_read_ac97(dev, AC97_VENDOR_ID1);
+	if (vid1 < 0) {
+		/*
+		 * Device likely doesn't support AC97
+		 * Note: (some) em2800 devices without eeprom reports 0x91 on
+		 *	 CHIPCFG register, even not having an AC97 chip
+		 */
+		dev_warn(&dev->intf->dev,
+			 "AC97 chip type couldn't be determined\n");
+		dev->audio_mode.ac97 = EM28XX_NO_AC97;
+		if (dev->usb_audio_type == EM28XX_USB_AUDIO_VENDOR)
+			dev->usb_audio_type = EM28XX_USB_AUDIO_NONE;
+		dev->int_audio_type = EM28XX_INT_AUDIO_NONE;
+		goto init_audio;
+	}
+
+	vid2 = em28xx_read_ac97(dev, AC97_VENDOR_ID2);
+	if (vid2 < 0)
+		goto init_audio;
+
+	vid = vid1 << 16 | vid2;
+	dev_warn(&dev->intf->dev, "AC97 vendor ID = 0x%08x\n", vid);
+
+	feat = em28xx_read_ac97(dev, AC97_RESET);
+	if (feat < 0)
+		goto init_audio;
+
+	dev_warn(&dev->intf->dev, "AC97 features = 0x%04x\n", feat);
+
+	/* Try to identify what audio processor we have */
+	if ((vid == 0xffffffff || vid == 0x83847650) && feat == 0x6a90)
+		dev->audio_mode.ac97 = EM28XX_AC97_EM202;
+	else if ((vid >> 8) == 0x838476)
+		dev->audio_mode.ac97 = EM28XX_AC97_SIGMATEL;
+
+init_audio:
+	/* Reports detected AC97 processor */
+	switch (dev->audio_mode.ac97) {
+	case EM28XX_NO_AC97:
+		dev_info(&dev->intf->dev, "No AC97 audio processor\n");
+		break;
+	case EM28XX_AC97_EM202:
+		dev_info(&dev->intf->dev,
+			 "Empia 202 AC97 audio processor detected\n");
+		break;
+	case EM28XX_AC97_SIGMATEL:
+		dev_info(&dev->intf->dev,
+			 "Sigmatel audio processor detected (stac 97%02x)\n",
+			 vid & 0xff);
+		break;
+	case EM28XX_AC97_OTHER:
+		dev_warn(&dev->intf->dev,
+			 "Unknown AC97 audio processor detected!\n");
+		break;
+	default:
+		break;
+	}
+
+	return em28xx_audio_analog_set(dev);
+}
+EXPORT_SYMBOL_GPL(em28xx_audio_setup);
+
+const struct em28xx_led *em28xx_find_led(struct em28xx *dev,
+					 enum em28xx_led_role role)
+{
+	if (dev->board.leds) {
+		u8 k = 0;
+
+		while (dev->board.leds[k].role >= 0 &&
+		       dev->board.leds[k].role < EM28XX_NUM_LED_ROLES) {
+			if (dev->board.leds[k].role == role)
+				return &dev->board.leds[k];
+			k++;
+		}
+	}
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(em28xx_find_led);
+
+int em28xx_capture_start(struct em28xx *dev, int start)
+{
+	int rc;
+	const struct em28xx_led *led = NULL;
+
+	if (dev->chip_id == CHIP_ID_EM2874 ||
+	    dev->chip_id == CHIP_ID_EM2884 ||
+	    dev->chip_id == CHIP_ID_EM28174 ||
+	    dev->chip_id == CHIP_ID_EM28178) {
+		/* The Transport Stream Enable Register moved in em2874 */
+		if (dev->dvb_xfer_bulk) {
+			/* Max Tx Size = 188 * 256 = 48128 - LCM(188,512) * 2 */
+			em28xx_write_reg(dev, (dev->ts == PRIMARY_TS) ?
+					 EM2874_R5D_TS1_PKT_SIZE :
+					 EM2874_R5E_TS2_PKT_SIZE,
+					 0xff);
+		} else {
+			/* ISOC Maximum Transfer Size = 188 * 5 */
+			em28xx_write_reg(dev, (dev->ts == PRIMARY_TS) ?
+					 EM2874_R5D_TS1_PKT_SIZE :
+					 EM2874_R5E_TS2_PKT_SIZE,
+					 dev->dvb_max_pkt_size_isoc / 188);
+		}
+		if (dev->ts == PRIMARY_TS)
+			rc = em28xx_write_reg_bits(dev,
+						   EM2874_R5F_TS_ENABLE,
+						   start ? EM2874_TS1_CAPTURE_ENABLE : 0x00,
+						   EM2874_TS1_CAPTURE_ENABLE | EM2874_TS1_FILTER_ENABLE | EM2874_TS1_NULL_DISCARD);
+		else
+			rc = em28xx_write_reg_bits(dev,
+						   EM2874_R5F_TS_ENABLE,
+						   start ? EM2874_TS2_CAPTURE_ENABLE : 0x00,
+						   EM2874_TS2_CAPTURE_ENABLE | EM2874_TS2_FILTER_ENABLE | EM2874_TS2_NULL_DISCARD);
+	} else {
+		/* FIXME: which is the best order? */
+		/* video registers are sampled by VREF */
+		rc = em28xx_write_reg_bits(dev, EM28XX_R0C_USBSUSP,
+					   start ? 0x10 : 0x00, 0x10);
+		if (rc < 0)
+			return rc;
+
+		if (start) {
+			if (dev->is_webcam)
+				rc = em28xx_write_reg(dev, 0x13, 0x0c);
+
+			/* Enable video capture */
+			rc = em28xx_write_reg(dev, 0x48, 0x00);
+			if (rc < 0)
+				return rc;
+
+			if (dev->mode == EM28XX_ANALOG_MODE)
+				rc = em28xx_write_reg(dev,
+						      EM28XX_R12_VINENABLE,
+						      0x67);
+			else
+				rc = em28xx_write_reg(dev,
+						      EM28XX_R12_VINENABLE,
+						      0x37);
+			if (rc < 0)
+				return rc;
+
+			usleep_range(10000, 11000);
+		} else {
+			/* disable video capture */
+			rc = em28xx_write_reg(dev, EM28XX_R12_VINENABLE, 0x27);
+		}
+	}
+
+	if (dev->mode == EM28XX_ANALOG_MODE)
+		led = em28xx_find_led(dev, EM28XX_LED_ANALOG_CAPTURING);
+	else
+		led = em28xx_find_led(dev, EM28XX_LED_DIGITAL_CAPTURING);
+
+	if (led)
+		em28xx_write_reg_bits(dev, led->gpio_reg,
+				      (!start ^ led->inverted) ?
+				      ~led->gpio_mask : led->gpio_mask,
+				      led->gpio_mask);
+
+	return rc;
+}
+
+int em28xx_gpio_set(struct em28xx *dev, const struct em28xx_reg_seq *gpio)
+{
+	int rc = 0;
+
+	if (!gpio)
+		return rc;
+
+	if (dev->mode != EM28XX_SUSPEND) {
+		em28xx_write_reg(dev, 0x48, 0x00);
+		if (dev->mode == EM28XX_ANALOG_MODE)
+			em28xx_write_reg(dev, EM28XX_R12_VINENABLE, 0x67);
+		else
+			em28xx_write_reg(dev, EM28XX_R12_VINENABLE, 0x37);
+		usleep_range(10000, 11000);
+	}
+
+	/* Send GPIO reset sequences specified at board entry */
+	while (gpio->sleep >= 0) {
+		if (gpio->reg >= 0) {
+			rc = em28xx_write_reg_bits(dev,
+						   gpio->reg,
+						   gpio->val,
+						   gpio->mask);
+			if (rc < 0)
+				return rc;
+		}
+		if (gpio->sleep > 0)
+			msleep(gpio->sleep);
+
+		gpio++;
+	}
+	return rc;
+}
+EXPORT_SYMBOL_GPL(em28xx_gpio_set);
+
+int em28xx_set_mode(struct em28xx *dev, enum em28xx_mode set_mode)
+{
+	if (dev->mode == set_mode)
+		return 0;
+
+	if (set_mode == EM28XX_SUSPEND) {
+		dev->mode = set_mode;
+
+		/* FIXME: add suspend support for ac97 */
+
+		return em28xx_gpio_set(dev, dev->board.suspend_gpio);
+	}
+
+	dev->mode = set_mode;
+
+	if (dev->mode == EM28XX_DIGITAL_MODE)
+		return em28xx_gpio_set(dev, dev->board.dvb_gpio);
+	else
+		return em28xx_gpio_set(dev, INPUT(dev->ctl_input)->gpio);
+}
+EXPORT_SYMBOL_GPL(em28xx_set_mode);
+
+/*
+ *URB control
+ */
+
+/*
+ * URB completion handler for isoc/bulk transfers
+ */
+static void em28xx_irq_callback(struct urb *urb)
+{
+	struct em28xx *dev = urb->context;
+	int i;
+
+	switch (urb->status) {
+	case 0:             /* success */
+	case -ETIMEDOUT:    /* NAK */
+		break;
+	case -ECONNRESET:   /* kill */
+	case -ENOENT:
+	case -ESHUTDOWN:
+		return;
+	default:            /* error */
+		em28xx_isocdbg("urb completion error %d.\n", urb->status);
+		break;
+	}
+
+	/* Copy data from URB */
+	spin_lock(&dev->slock);
+	dev->usb_ctl.urb_data_copy(dev, urb);
+	spin_unlock(&dev->slock);
+
+	/* Reset urb buffers */
+	for (i = 0; i < urb->number_of_packets; i++) {
+		/* isoc only (bulk: number_of_packets = 0) */
+		urb->iso_frame_desc[i].status = 0;
+		urb->iso_frame_desc[i].actual_length = 0;
+	}
+	urb->status = 0;
+
+	urb->status = usb_submit_urb(urb, GFP_ATOMIC);
+	if (urb->status) {
+		em28xx_isocdbg("urb resubmit failed (error=%i)\n",
+			       urb->status);
+	}
+}
+
+/*
+ * Stop and Deallocate URBs
+ */
+void em28xx_uninit_usb_xfer(struct em28xx *dev, enum em28xx_mode mode)
+{
+	struct urb *urb;
+	struct em28xx_usb_bufs *usb_bufs;
+	int i;
+
+	em28xx_isocdbg("called %s in mode %d\n", __func__, mode);
+
+	if (mode == EM28XX_DIGITAL_MODE)
+		usb_bufs = &dev->usb_ctl.digital_bufs;
+	else
+		usb_bufs = &dev->usb_ctl.analog_bufs;
+
+	for (i = 0; i < usb_bufs->num_bufs; i++) {
+		urb = usb_bufs->urb[i];
+		if (urb) {
+			if (!irqs_disabled())
+				usb_kill_urb(urb);
+			else
+				usb_unlink_urb(urb);
+
+			usb_free_urb(urb);
+			usb_bufs->urb[i] = NULL;
+		}
+	}
+
+	kfree(usb_bufs->urb);
+	kfree(usb_bufs->buf);
+
+	usb_bufs->urb = NULL;
+	usb_bufs->buf = NULL;
+	usb_bufs->num_bufs = 0;
+
+	em28xx_capture_start(dev, 0);
+}
+EXPORT_SYMBOL_GPL(em28xx_uninit_usb_xfer);
+
+/*
+ * Stop URBs
+ */
+void em28xx_stop_urbs(struct em28xx *dev)
+{
+	int i;
+	struct urb *urb;
+	struct em28xx_usb_bufs *isoc_bufs = &dev->usb_ctl.digital_bufs;
+
+	em28xx_isocdbg("called %s\n", __func__);
+
+	for (i = 0; i < isoc_bufs->num_bufs; i++) {
+		urb = isoc_bufs->urb[i];
+		if (urb) {
+			if (!irqs_disabled())
+				usb_kill_urb(urb);
+			else
+				usb_unlink_urb(urb);
+		}
+	}
+
+	em28xx_capture_start(dev, 0);
+}
+EXPORT_SYMBOL_GPL(em28xx_stop_urbs);
+
+/*
+ * Allocate URBs
+ */
+int em28xx_alloc_urbs(struct em28xx *dev, enum em28xx_mode mode, int xfer_bulk,
+		      int num_bufs, int max_pkt_size, int packet_multiplier)
+{
+	struct em28xx_usb_bufs *usb_bufs;
+	struct urb *urb;
+	struct usb_device *udev = interface_to_usbdev(dev->intf);
+	int i;
+	int sb_size, pipe;
+	int j, k;
+
+	em28xx_isocdbg("em28xx: called %s in mode %d\n", __func__, mode);
+
+	/*
+	 * Check mode and if we have an endpoint for the selected
+	 * transfer type, select buffer
+	 */
+	if (mode == EM28XX_DIGITAL_MODE) {
+		if ((xfer_bulk && !dev->dvb_ep_bulk) ||
+		    (!xfer_bulk && !dev->dvb_ep_isoc)) {
+			dev_err(&dev->intf->dev,
+				"no endpoint for DVB mode and transfer type %d\n",
+				xfer_bulk > 0);
+			return -EINVAL;
+		}
+		usb_bufs = &dev->usb_ctl.digital_bufs;
+	} else if (mode == EM28XX_ANALOG_MODE) {
+		if ((xfer_bulk && !dev->analog_ep_bulk) ||
+		    (!xfer_bulk && !dev->analog_ep_isoc)) {
+			dev_err(&dev->intf->dev,
+				"no endpoint for analog mode and transfer type %d\n",
+				xfer_bulk > 0);
+			return -EINVAL;
+		}
+		usb_bufs = &dev->usb_ctl.analog_bufs;
+	} else {
+		dev_err(&dev->intf->dev, "invalid mode selected\n");
+		return -EINVAL;
+	}
+
+	/* De-allocates all pending stuff */
+	em28xx_uninit_usb_xfer(dev, mode);
+
+	usb_bufs->num_bufs = num_bufs;
+
+	usb_bufs->urb = kcalloc(num_bufs, sizeof(void *), GFP_KERNEL);
+	if (!usb_bufs->urb)
+		return -ENOMEM;
+
+	usb_bufs->buf = kcalloc(num_bufs, sizeof(void *), GFP_KERNEL);
+	if (!usb_bufs->buf) {
+		kfree(usb_bufs->buf);
+		return -ENOMEM;
+	}
+
+	usb_bufs->max_pkt_size = max_pkt_size;
+	if (xfer_bulk)
+		usb_bufs->num_packets = 0;
+	else
+		usb_bufs->num_packets = packet_multiplier;
+	dev->usb_ctl.vid_buf = NULL;
+	dev->usb_ctl.vbi_buf = NULL;
+
+	sb_size = packet_multiplier * usb_bufs->max_pkt_size;
+
+	/* allocate urbs and transfer buffers */
+	for (i = 0; i < usb_bufs->num_bufs; i++) {
+		urb = usb_alloc_urb(usb_bufs->num_packets, GFP_KERNEL);
+		if (!urb) {
+			em28xx_uninit_usb_xfer(dev, mode);
+			return -ENOMEM;
+		}
+		usb_bufs->urb[i] = urb;
+
+		usb_bufs->buf[i] = kzalloc(sb_size, GFP_KERNEL);
+		if (!usb_bufs->buf[i]) {
+			em28xx_uninit_usb_xfer(dev, mode);
+
+			for (i--; i >= 0; i--)
+				kfree(usb_bufs->buf[i]);
+
+			kfree(usb_bufs->buf);
+			usb_bufs->buf = NULL;
+
+			return -ENOMEM;
+		}
+
+		urb->transfer_flags = URB_FREE_BUFFER;
+
+		if (xfer_bulk) { /* bulk */
+			pipe = usb_rcvbulkpipe(udev,
+					       mode == EM28XX_ANALOG_MODE ?
+					       dev->analog_ep_bulk :
+					       dev->dvb_ep_bulk);
+			usb_fill_bulk_urb(urb, udev, pipe, usb_bufs->buf[i],
+					  sb_size, em28xx_irq_callback, dev);
+		} else { /* isoc */
+			pipe = usb_rcvisocpipe(udev,
+					       mode == EM28XX_ANALOG_MODE ?
+					       dev->analog_ep_isoc :
+					       dev->dvb_ep_isoc);
+			usb_fill_int_urb(urb, udev, pipe, usb_bufs->buf[i],
+					 sb_size, em28xx_irq_callback, dev, 1);
+			urb->transfer_flags |= URB_ISO_ASAP;
+			k = 0;
+			for (j = 0; j < usb_bufs->num_packets; j++) {
+				urb->iso_frame_desc[j].offset = k;
+				urb->iso_frame_desc[j].length =
+							usb_bufs->max_pkt_size;
+				k += usb_bufs->max_pkt_size;
+			}
+		}
+
+		urb->number_of_packets = usb_bufs->num_packets;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(em28xx_alloc_urbs);
+
+/*
+ * Allocate URBs and start IRQ
+ */
+int em28xx_init_usb_xfer(struct em28xx *dev, enum em28xx_mode mode,
+			 int xfer_bulk, int num_bufs, int max_pkt_size,
+		    int packet_multiplier,
+		    int (*urb_data_copy)(struct em28xx *dev, struct urb *urb))
+{
+	struct em28xx_dmaqueue *dma_q = &dev->vidq;
+	struct em28xx_dmaqueue *vbi_dma_q = &dev->vbiq;
+	struct em28xx_usb_bufs *usb_bufs;
+	struct usb_device *udev = interface_to_usbdev(dev->intf);
+	int i;
+	int rc;
+	int alloc;
+
+	em28xx_isocdbg("em28xx: called %s in mode %d\n", __func__, mode);
+
+	dev->usb_ctl.urb_data_copy = urb_data_copy;
+
+	if (mode == EM28XX_DIGITAL_MODE) {
+		usb_bufs = &dev->usb_ctl.digital_bufs;
+		/* no need to free/alloc usb buffers in digital mode */
+		alloc = 0;
+	} else {
+		usb_bufs = &dev->usb_ctl.analog_bufs;
+		alloc = 1;
+	}
+
+	if (alloc) {
+		rc = em28xx_alloc_urbs(dev, mode, xfer_bulk, num_bufs,
+				       max_pkt_size, packet_multiplier);
+		if (rc)
+			return rc;
+	}
+
+	if (xfer_bulk) {
+		rc = usb_clear_halt(udev, usb_bufs->urb[0]->pipe);
+		if (rc < 0) {
+			dev_err(&dev->intf->dev,
+				"failed to clear USB bulk endpoint stall/halt condition (error=%i)\n",
+			       rc);
+			em28xx_uninit_usb_xfer(dev, mode);
+			return rc;
+		}
+	}
+
+	init_waitqueue_head(&dma_q->wq);
+	init_waitqueue_head(&vbi_dma_q->wq);
+
+	em28xx_capture_start(dev, 1);
+
+	/* submit urbs and enables IRQ */
+	for (i = 0; i < usb_bufs->num_bufs; i++) {
+		rc = usb_submit_urb(usb_bufs->urb[i], GFP_KERNEL);
+		if (rc) {
+			dev_err(&dev->intf->dev,
+				"submit of urb %i failed (error=%i)\n", i, rc);
+			em28xx_uninit_usb_xfer(dev, mode);
+			return rc;
+		}
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(em28xx_init_usb_xfer);
+
+/*
+ * Device control list
+ */
+
+static LIST_HEAD(em28xx_devlist);
+static DEFINE_MUTEX(em28xx_devlist_mutex);
+
+/*
+ * Extension interface
+ */
+
+static LIST_HEAD(em28xx_extension_devlist);
+
+int em28xx_register_extension(struct em28xx_ops *ops)
+{
+	struct em28xx *dev = NULL;
+
+	mutex_lock(&em28xx_devlist_mutex);
+	list_add_tail(&ops->next, &em28xx_extension_devlist);
+	list_for_each_entry(dev, &em28xx_devlist, devlist) {
+		if (ops->init) {
+			ops->init(dev);
+			if (dev->dev_next)
+				ops->init(dev->dev_next);
+		}
+	}
+	mutex_unlock(&em28xx_devlist_mutex);
+	pr_info("em28xx: Registered (%s) extension\n", ops->name);
+	return 0;
+}
+EXPORT_SYMBOL(em28xx_register_extension);
+
+void em28xx_unregister_extension(struct em28xx_ops *ops)
+{
+	struct em28xx *dev = NULL;
+
+	mutex_lock(&em28xx_devlist_mutex);
+	list_for_each_entry(dev, &em28xx_devlist, devlist) {
+		if (ops->fini) {
+			if (dev->dev_next)
+				ops->fini(dev->dev_next);
+			ops->fini(dev);
+		}
+	}
+	list_del(&ops->next);
+	mutex_unlock(&em28xx_devlist_mutex);
+	pr_info("em28xx: Removed (%s) extension\n", ops->name);
+}
+EXPORT_SYMBOL(em28xx_unregister_extension);
+
+void em28xx_init_extension(struct em28xx *dev)
+{
+	const struct em28xx_ops *ops = NULL;
+
+	mutex_lock(&em28xx_devlist_mutex);
+	list_add_tail(&dev->devlist, &em28xx_devlist);
+	list_for_each_entry(ops, &em28xx_extension_devlist, next) {
+		if (ops->init) {
+			ops->init(dev);
+			if (dev->dev_next)
+				ops->init(dev->dev_next);
+		}
+	}
+	mutex_unlock(&em28xx_devlist_mutex);
+}
+
+void em28xx_close_extension(struct em28xx *dev)
+{
+	const struct em28xx_ops *ops = NULL;
+
+	mutex_lock(&em28xx_devlist_mutex);
+	list_for_each_entry(ops, &em28xx_extension_devlist, next) {
+		if (ops->fini) {
+			if (dev->dev_next)
+				ops->fini(dev->dev_next);
+			ops->fini(dev);
+		}
+	}
+	list_del(&dev->devlist);
+	mutex_unlock(&em28xx_devlist_mutex);
+}
+
+int em28xx_suspend_extension(struct em28xx *dev)
+{
+	const struct em28xx_ops *ops = NULL;
+
+	dev_info(&dev->intf->dev, "Suspending extensions\n");
+	mutex_lock(&em28xx_devlist_mutex);
+	list_for_each_entry(ops, &em28xx_extension_devlist, next) {
+		if (ops->suspend)
+			ops->suspend(dev);
+		if (dev->dev_next)
+			ops->suspend(dev->dev_next);
+	}
+	mutex_unlock(&em28xx_devlist_mutex);
+	return 0;
+}
+
+int em28xx_resume_extension(struct em28xx *dev)
+{
+	const struct em28xx_ops *ops = NULL;
+
+	dev_info(&dev->intf->dev, "Resuming extensions\n");
+	mutex_lock(&em28xx_devlist_mutex);
+	list_for_each_entry(ops, &em28xx_extension_devlist, next) {
+		if (!ops->resume)
+			continue;
+		ops->resume(dev);
+		if (dev->dev_next)
+			ops->resume(dev->dev_next);
+	}
+	mutex_unlock(&em28xx_devlist_mutex);
+	return 0;
+}
diff --git a/drivers/media/usb/em28xx/em28xx-dvb.c b/drivers/media/usb/em28xx/em28xx-dvb.c
new file mode 100644
index 0000000..a73faf1
--- /dev/null
+++ b/drivers/media/usb/em28xx/em28xx-dvb.c
@@ -0,0 +1,2066 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// DVB device driver for em28xx
+//
+// (c) 2008-2011 Mauro Carvalho Chehab <mchehab@kernel.org>
+//
+// (c) 2008 Devin Heitmueller <devin.heitmueller@gmail.com>
+//	- Fixes for the driver to properly work with HVR-950
+//	- Fixes for the driver to properly work with Pinnacle PCTV HD Pro Stick
+//	- Fixes for the driver to properly work with AMD ATI TV Wonder HD 600
+//
+// (c) 2008 Aidan Thornton <makosoft@googlemail.com>
+//
+// (c) 2012 Frank Schäfer <fschaefer.oss@googlemail.com>
+//
+// Based on cx88-dvb, saa7134-dvb and videobuf-dvb originally written by:
+//	(c) 2004, 2005 Chris Pascoe <c.pascoe@itee.uq.edu.au>
+//	(c) 2004 Gerd Knorr <kraxel@bytesex.org> [SuSE Labs]
+//
+// 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 version 2 of the License.
+
+#include "em28xx.h"
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+
+#include <media/v4l2-common.h>
+#include <media/dvb_demux.h>
+#include <media/dvb_net.h>
+#include <media/dmxdev.h>
+#include <media/tuner.h>
+#include "tuner-simple.h"
+#include <linux/gpio.h>
+
+#include "lgdt330x.h"
+#include "lgdt3305.h"
+#include "lgdt3306a.h"
+#include "zl10353.h"
+#include "s5h1409.h"
+#include "mt2060.h"
+#include "mt352.h"
+#include "mt352_priv.h" /* FIXME */
+#include "tda1002x.h"
+#include "drx39xyj/drx39xxj.h"
+#include "tda18271.h"
+#include "s921.h"
+#include "drxd.h"
+#include "cxd2820r.h"
+#include "tda18271c2dd.h"
+#include "drxk.h"
+#include "tda10071.h"
+#include "tda18212.h"
+#include "a8293.h"
+#include "qt1010.h"
+#include "mb86a20s.h"
+#include "m88ds3103.h"
+#include "ts2020.h"
+#include "si2168.h"
+#include "si2157.h"
+#include "tc90522.h"
+#include "qm1d1c0042.h"
+
+MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@kernel.org>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION(DRIVER_DESC " - digital TV interface");
+MODULE_VERSION(EM28XX_VERSION);
+
+static unsigned int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "enable debug messages [dvb]");
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+#define dprintk(level, fmt, arg...) do {				\
+	if (debug >= level)						\
+		dev_printk(KERN_DEBUG, &dev->intf->dev,			\
+			   "dvb: " fmt, ## arg);			\
+} while (0)
+
+struct em28xx_dvb {
+	struct dvb_frontend        *fe[2];
+
+	/* feed count management */
+	struct mutex               lock;
+	int                        nfeeds;
+
+	/* general boilerplate stuff */
+	struct dvb_adapter         adapter;
+	struct dvb_demux           demux;
+	struct dmxdev              dmxdev;
+	struct dmx_frontend        fe_hw;
+	struct dmx_frontend        fe_mem;
+	struct dvb_net             net;
+
+	/* Due to DRX-K - probably need changes */
+	int (*gate_ctrl)(struct dvb_frontend *fe, int gate);
+	struct semaphore      pll_mutex;
+	bool			dont_attach_fe1;
+	int			lna_gpio;
+	struct i2c_client	*i2c_client_demod;
+	struct i2c_client	*i2c_client_tuner;
+	struct i2c_client	*i2c_client_sec;
+};
+
+static inline void print_err_status(struct em28xx *dev,
+				    int packet, int status)
+{
+	char *errmsg = "Unknown";
+
+	switch (status) {
+	case -ENOENT:
+		errmsg = "unlinked synchronously";
+		break;
+	case -ECONNRESET:
+		errmsg = "unlinked asynchronously";
+		break;
+	case -ENOSR:
+		errmsg = "Buffer error (overrun)";
+		break;
+	case -EPIPE:
+		errmsg = "Stalled (device not responding)";
+		break;
+	case -EOVERFLOW:
+		errmsg = "Babble (bad cable?)";
+		break;
+	case -EPROTO:
+		errmsg = "Bit-stuff error (bad cable?)";
+		break;
+	case -EILSEQ:
+		errmsg = "CRC/Timeout (could be anything)";
+		break;
+	case -ETIME:
+		errmsg = "Device does not respond";
+		break;
+	}
+	if (packet < 0) {
+		dprintk(1, "URB status %d [%s].\n", status, errmsg);
+	} else {
+		dprintk(1, "URB packet %d, status %d [%s].\n",
+			packet, status, errmsg);
+	}
+}
+
+static inline int em28xx_dvb_urb_data_copy(struct em28xx *dev, struct urb *urb)
+{
+	int xfer_bulk, num_packets, i;
+
+	if (!dev)
+		return 0;
+
+	if (dev->disconnected)
+		return 0;
+
+	if (urb->status < 0)
+		print_err_status(dev, -1, urb->status);
+
+	xfer_bulk = usb_pipebulk(urb->pipe);
+
+	if (xfer_bulk) /* bulk */
+		num_packets = 1;
+	else /* isoc */
+		num_packets = urb->number_of_packets;
+
+	for (i = 0; i < num_packets; i++) {
+		if (xfer_bulk) {
+			if (urb->status < 0) {
+				print_err_status(dev, i, urb->status);
+				if (urb->status != -EPROTO)
+					continue;
+			}
+			if (!urb->actual_length)
+				continue;
+			dvb_dmx_swfilter(&dev->dvb->demux, urb->transfer_buffer,
+					 urb->actual_length);
+		} else {
+			if (urb->iso_frame_desc[i].status < 0) {
+				print_err_status(dev, i,
+						 urb->iso_frame_desc[i].status);
+				if (urb->iso_frame_desc[i].status != -EPROTO)
+					continue;
+			}
+			if (!urb->iso_frame_desc[i].actual_length)
+				continue;
+			dvb_dmx_swfilter(&dev->dvb->demux,
+					 urb->transfer_buffer +
+					 urb->iso_frame_desc[i].offset,
+					 urb->iso_frame_desc[i].actual_length);
+		}
+	}
+
+	return 0;
+}
+
+static int em28xx_start_streaming(struct em28xx_dvb *dvb)
+{
+	int rc;
+	struct em28xx_i2c_bus *i2c_bus = dvb->adapter.priv;
+	struct em28xx *dev = i2c_bus->dev;
+	struct usb_device *udev = interface_to_usbdev(dev->intf);
+	int dvb_max_packet_size, packet_multiplier, dvb_alt;
+
+	if (dev->dvb_xfer_bulk) {
+		if (!dev->dvb_ep_bulk)
+			return -ENODEV;
+		dvb_max_packet_size = 512; /* USB 2.0 spec */
+		packet_multiplier = EM28XX_DVB_BULK_PACKET_MULTIPLIER;
+		dvb_alt = 0;
+	} else { /* isoc */
+		if (!dev->dvb_ep_isoc)
+			return -ENODEV;
+		dvb_max_packet_size = dev->dvb_max_pkt_size_isoc;
+		if (dvb_max_packet_size < 0)
+			return dvb_max_packet_size;
+		packet_multiplier = EM28XX_DVB_NUM_ISOC_PACKETS;
+		dvb_alt = dev->dvb_alt_isoc;
+	}
+
+	if (!dev->board.has_dual_ts)
+		usb_set_interface(udev, dev->ifnum, dvb_alt);
+
+	rc = em28xx_set_mode(dev, EM28XX_DIGITAL_MODE);
+	if (rc < 0)
+		return rc;
+
+	dprintk(1, "Using %d buffers each with %d x %d bytes, alternate %d\n",
+		EM28XX_DVB_NUM_BUFS,
+		packet_multiplier,
+		dvb_max_packet_size, dvb_alt);
+
+	return em28xx_init_usb_xfer(dev, EM28XX_DIGITAL_MODE,
+				    dev->dvb_xfer_bulk,
+				    EM28XX_DVB_NUM_BUFS,
+				    dvb_max_packet_size,
+				    packet_multiplier,
+				    em28xx_dvb_urb_data_copy);
+}
+
+static int em28xx_stop_streaming(struct em28xx_dvb *dvb)
+{
+	struct em28xx_i2c_bus *i2c_bus = dvb->adapter.priv;
+	struct em28xx *dev = i2c_bus->dev;
+
+	em28xx_stop_urbs(dev);
+
+	return 0;
+}
+
+static int em28xx_start_feed(struct dvb_demux_feed *feed)
+{
+	struct dvb_demux *demux  = feed->demux;
+	struct em28xx_dvb *dvb = demux->priv;
+	int rc, ret;
+
+	if (!demux->dmx.frontend)
+		return -EINVAL;
+
+	mutex_lock(&dvb->lock);
+	dvb->nfeeds++;
+	rc = dvb->nfeeds;
+
+	if (dvb->nfeeds == 1) {
+		ret = em28xx_start_streaming(dvb);
+		if (ret < 0)
+			rc = ret;
+	}
+
+	mutex_unlock(&dvb->lock);
+	return rc;
+}
+
+static int em28xx_stop_feed(struct dvb_demux_feed *feed)
+{
+	struct dvb_demux *demux  = feed->demux;
+	struct em28xx_dvb *dvb = demux->priv;
+	int err = 0;
+
+	mutex_lock(&dvb->lock);
+	dvb->nfeeds--;
+
+	if (!dvb->nfeeds)
+		err = em28xx_stop_streaming(dvb);
+
+	mutex_unlock(&dvb->lock);
+	return err;
+}
+
+/* ------------------------------------------------------------------ */
+static int em28xx_dvb_bus_ctrl(struct dvb_frontend *fe, int acquire)
+{
+	struct em28xx_i2c_bus *i2c_bus = fe->dvb->priv;
+	struct em28xx *dev = i2c_bus->dev;
+
+	if (acquire)
+		return em28xx_set_mode(dev, EM28XX_DIGITAL_MODE);
+	else
+		return em28xx_set_mode(dev, EM28XX_SUSPEND);
+}
+
+/* ------------------------------------------------------------------ */
+
+static struct lgdt330x_config em2880_lgdt3303_dev = {
+	.demod_chip = LGDT3303,
+};
+
+static struct lgdt3305_config em2870_lgdt3304_dev = {
+	.i2c_addr           = 0x0e,
+	.demod_chip         = LGDT3304,
+	.spectral_inversion = 1,
+	.deny_i2c_rptr      = 1,
+	.mpeg_mode          = LGDT3305_MPEG_PARALLEL,
+	.tpclk_edge         = LGDT3305_TPCLK_FALLING_EDGE,
+	.tpvalid_polarity   = LGDT3305_TP_VALID_HIGH,
+	.vsb_if_khz         = 3250,
+	.qam_if_khz         = 4000,
+};
+
+static struct lgdt3305_config em2874_lgdt3305_dev = {
+	.i2c_addr           = 0x0e,
+	.demod_chip         = LGDT3305,
+	.spectral_inversion = 1,
+	.deny_i2c_rptr      = 0,
+	.mpeg_mode          = LGDT3305_MPEG_SERIAL,
+	.tpclk_edge         = LGDT3305_TPCLK_FALLING_EDGE,
+	.tpvalid_polarity   = LGDT3305_TP_VALID_HIGH,
+	.vsb_if_khz         = 3250,
+	.qam_if_khz         = 4000,
+};
+
+static struct lgdt3305_config em2874_lgdt3305_nogate_dev = {
+	.i2c_addr           = 0x0e,
+	.demod_chip         = LGDT3305,
+	.spectral_inversion = 1,
+	.deny_i2c_rptr      = 1,
+	.mpeg_mode          = LGDT3305_MPEG_SERIAL,
+	.tpclk_edge         = LGDT3305_TPCLK_FALLING_EDGE,
+	.tpvalid_polarity   = LGDT3305_TP_VALID_HIGH,
+	.vsb_if_khz         = 3600,
+	.qam_if_khz         = 3600,
+};
+
+static struct s921_config sharp_isdbt = {
+	.demod_address = 0x30 >> 1
+};
+
+static struct zl10353_config em28xx_zl10353_with_xc3028 = {
+	.demod_address = (0x1e >> 1),
+	.no_tuner = 1,
+	.parallel_ts = 1,
+	.if2 = 45600,
+};
+
+static struct s5h1409_config em28xx_s5h1409_with_xc3028 = {
+	.demod_address = 0x32 >> 1,
+	.output_mode   = S5H1409_PARALLEL_OUTPUT,
+	.gpio          = S5H1409_GPIO_OFF,
+	.inversion     = S5H1409_INVERSION_OFF,
+	.status_mode   = S5H1409_DEMODLOCKING,
+	.mpeg_timing   = S5H1409_MPEGTIMING_CONTINUOUS_NONINVERTING_CLOCK
+};
+
+static struct tda18271_std_map kworld_a340_std_map = {
+	.atsc_6   = { .if_freq = 3250, .agc_mode = 3, .std = 0,
+		      .if_lvl = 1, .rfagc_top = 0x37, },
+	.qam_6    = { .if_freq = 4000, .agc_mode = 3, .std = 1,
+		      .if_lvl = 1, .rfagc_top = 0x37, },
+};
+
+static struct tda18271_config kworld_a340_config = {
+	.std_map           = &kworld_a340_std_map,
+};
+
+static struct tda18271_config kworld_ub435q_v2_config = {
+	.std_map	= &kworld_a340_std_map,
+	.gate		= TDA18271_GATE_DIGITAL,
+};
+
+static struct tda18212_config kworld_ub435q_v3_config = {
+	.if_atsc_vsb	= 3600,
+	.if_atsc_qam	= 3600,
+};
+
+static struct zl10353_config em28xx_zl10353_xc3028_no_i2c_gate = {
+	.demod_address = (0x1e >> 1),
+	.no_tuner = 1,
+	.disable_i2c_gate_ctrl = 1,
+	.parallel_ts = 1,
+	.if2 = 45600,
+};
+
+static struct drxd_config em28xx_drxd = {
+	.demod_address = 0x70,
+	.demod_revision = 0xa2,
+	.pll_type = DRXD_PLL_NONE,
+	.clock = 12000,
+	.insert_rs_byte = 1,
+	.IF = 42800000,
+	.disable_i2c_gate_ctrl = 1,
+};
+
+static struct drxk_config terratec_h5_drxk = {
+	.adr = 0x29,
+	.single_master = 1,
+	.no_i2c_bridge = 1,
+	.microcode_name = "dvb-usb-terratec-h5-drxk.fw",
+	.qam_demod_parameter_count = 2,
+};
+
+static struct drxk_config hauppauge_930c_drxk = {
+	.adr = 0x29,
+	.single_master = 1,
+	.no_i2c_bridge = 1,
+	.microcode_name = "dvb-usb-hauppauge-hvr930c-drxk.fw",
+	.chunk_size = 56,
+	.qam_demod_parameter_count = 2,
+};
+
+static struct drxk_config terratec_htc_stick_drxk = {
+	.adr = 0x29,
+	.single_master = 1,
+	.no_i2c_bridge = 1,
+	.microcode_name = "dvb-usb-terratec-htc-stick-drxk.fw",
+	.chunk_size = 54,
+	.qam_demod_parameter_count = 2,
+	/* Required for the antenna_gpio to disable LNA. */
+	.antenna_dvbt = true,
+	/* The windows driver uses the same. This will disable LNA. */
+	.antenna_gpio = 0x6,
+};
+
+static struct drxk_config maxmedia_ub425_tc_drxk = {
+	.adr = 0x29,
+	.single_master = 1,
+	.no_i2c_bridge = 1,
+	.microcode_name = "dvb-demod-drxk-01.fw",
+	.chunk_size = 62,
+	.qam_demod_parameter_count = 2,
+};
+
+static struct drxk_config pctv_520e_drxk = {
+	.adr = 0x29,
+	.single_master = 1,
+	.microcode_name = "dvb-demod-drxk-pctv.fw",
+	.qam_demod_parameter_count = 2,
+	.chunk_size = 58,
+	.antenna_dvbt = true, /* disable LNA */
+	.antenna_gpio = (1 << 2), /* disable LNA */
+};
+
+static int drxk_gate_ctrl(struct dvb_frontend *fe, int enable)
+{
+	struct em28xx_dvb *dvb = fe->sec_priv;
+	int status;
+
+	if (!dvb)
+		return -EINVAL;
+
+	if (enable) {
+		down(&dvb->pll_mutex);
+		status = dvb->gate_ctrl(fe, 1);
+	} else {
+		status = dvb->gate_ctrl(fe, 0);
+		up(&dvb->pll_mutex);
+	}
+	return status;
+}
+
+static void hauppauge_hvr930c_init(struct em28xx *dev)
+{
+	int i;
+
+	struct em28xx_reg_seq hauppauge_hvr930c_init[] = {
+		{EM2874_R80_GPIO_P0_CTRL,	0xff,	0xff,	0x65},
+		{EM2874_R80_GPIO_P0_CTRL,	0xfb,	0xff,	0x32},
+		{EM2874_R80_GPIO_P0_CTRL,	0xff,	0xff,	0xb8},
+		{	-1,			-1,	-1,	-1},
+	};
+	struct em28xx_reg_seq hauppauge_hvr930c_end[] = {
+		{EM2874_R80_GPIO_P0_CTRL,	0xef,	0xff,	0x01},
+		{EM2874_R80_GPIO_P0_CTRL,	0xaf,	0xff,	0x65},
+		{EM2874_R80_GPIO_P0_CTRL,	0xef,	0xff,	0x76},
+		{EM2874_R80_GPIO_P0_CTRL,	0xef,	0xff,	0x01},
+		{EM2874_R80_GPIO_P0_CTRL,	0xcf,	0xff,	0x0b},
+		{EM2874_R80_GPIO_P0_CTRL,	0xef,	0xff,	0x40},
+
+		{EM2874_R80_GPIO_P0_CTRL,	0xcf,	0xff,	0x65},
+		{EM2874_R80_GPIO_P0_CTRL,	0xef,	0xff,	0x65},
+		{EM2874_R80_GPIO_P0_CTRL,	0xcf,	0xff,	0x0b},
+		{EM2874_R80_GPIO_P0_CTRL,	0xef,	0xff,	0x65},
+
+		{	-1,			-1,	-1,	-1},
+	};
+
+	struct {
+		unsigned char r[4];
+		int len;
+	} regs[] = {
+		{{ 0x06, 0x02, 0x00, 0x31 }, 4},
+		{{ 0x01, 0x02 }, 2},
+		{{ 0x01, 0x02, 0x00, 0xc6 }, 4},
+		{{ 0x01, 0x00 }, 2},
+		{{ 0x01, 0x00, 0xff, 0xaf }, 4},
+		{{ 0x01, 0x00, 0x03, 0xa0 }, 4},
+		{{ 0x01, 0x00 }, 2},
+		{{ 0x01, 0x00, 0x73, 0xaf }, 4},
+		{{ 0x04, 0x00 }, 2},
+		{{ 0x00, 0x04 }, 2},
+		{{ 0x00, 0x04, 0x00, 0x0a }, 4},
+		{{ 0x04, 0x14 }, 2},
+		{{ 0x04, 0x14, 0x00, 0x00 }, 4},
+	};
+
+	em28xx_gpio_set(dev, hauppauge_hvr930c_init);
+	em28xx_write_reg(dev, EM28XX_R06_I2C_CLK, 0x40);
+	usleep_range(10000, 11000);
+	em28xx_write_reg(dev, EM28XX_R06_I2C_CLK, 0x44);
+	usleep_range(10000, 11000);
+
+	dev->i2c_client[dev->def_i2c_bus].addr = 0x82 >> 1;
+
+	for (i = 0; i < ARRAY_SIZE(regs); i++)
+		i2c_master_send(&dev->i2c_client[dev->def_i2c_bus],
+				regs[i].r, regs[i].len);
+	em28xx_gpio_set(dev, hauppauge_hvr930c_end);
+
+	msleep(100);
+
+	em28xx_write_reg(dev, EM28XX_R06_I2C_CLK, 0x44);
+	msleep(30);
+
+	em28xx_write_reg(dev, EM28XX_R06_I2C_CLK, 0x45);
+	usleep_range(10000, 11000);
+}
+
+static void terratec_h5_init(struct em28xx *dev)
+{
+	int i;
+	struct em28xx_reg_seq terratec_h5_init[] = {
+		{EM2820_R08_GPIO_CTRL,		0xff,	0xff,	10},
+		{EM2874_R80_GPIO_P0_CTRL,	0xf6,	0xff,	100},
+		{EM2874_R80_GPIO_P0_CTRL,	0xf2,	0xff,	50},
+		{EM2874_R80_GPIO_P0_CTRL,	0xf6,	0xff,	100},
+		{	-1,			-1,	-1,	-1},
+	};
+	struct em28xx_reg_seq terratec_h5_end[] = {
+		{EM2874_R80_GPIO_P0_CTRL,	0xe6,	0xff,	100},
+		{EM2874_R80_GPIO_P0_CTRL,	0xa6,	0xff,	50},
+		{EM2874_R80_GPIO_P0_CTRL,	0xe6,	0xff,	100},
+		{	-1,			-1,	-1,	-1},
+	};
+	struct {
+		unsigned char r[4];
+		int len;
+	} regs[] = {
+		{{ 0x06, 0x02, 0x00, 0x31 }, 4},
+		{{ 0x01, 0x02 }, 2},
+		{{ 0x01, 0x02, 0x00, 0xc6 }, 4},
+		{{ 0x01, 0x00 }, 2},
+		{{ 0x01, 0x00, 0xff, 0xaf }, 4},
+		{{ 0x01, 0x00, 0x03, 0xa0 }, 4},
+		{{ 0x01, 0x00 }, 2},
+		{{ 0x01, 0x00, 0x73, 0xaf }, 4},
+		{{ 0x04, 0x00 }, 2},
+		{{ 0x00, 0x04 }, 2},
+		{{ 0x00, 0x04, 0x00, 0x0a }, 4},
+		{{ 0x04, 0x14 }, 2},
+		{{ 0x04, 0x14, 0x00, 0x00 }, 4},
+	};
+
+	em28xx_gpio_set(dev, terratec_h5_init);
+	em28xx_write_reg(dev, EM28XX_R06_I2C_CLK, 0x40);
+	usleep_range(10000, 11000);
+	em28xx_write_reg(dev, EM28XX_R06_I2C_CLK, 0x45);
+	usleep_range(10000, 11000);
+
+	dev->i2c_client[dev->def_i2c_bus].addr = 0x82 >> 1;
+
+	for (i = 0; i < ARRAY_SIZE(regs); i++)
+		i2c_master_send(&dev->i2c_client[dev->def_i2c_bus],
+				regs[i].r, regs[i].len);
+	em28xx_gpio_set(dev, terratec_h5_end);
+};
+
+static void terratec_htc_stick_init(struct em28xx *dev)
+{
+	int i;
+
+	/*
+	 * GPIO configuration:
+	 * 0xff: unknown (does not affect DVB-T).
+	 * 0xf6: DRX-K (demodulator).
+	 * 0xe6: unknown (does not affect DVB-T).
+	 * 0xb6: unknown (does not affect DVB-T).
+	 */
+	struct em28xx_reg_seq terratec_htc_stick_init[] = {
+		{EM2820_R08_GPIO_CTRL,		0xff,	0xff,	10},
+		{EM2874_R80_GPIO_P0_CTRL,	0xf6,	0xff,	100},
+		{EM2874_R80_GPIO_P0_CTRL,	0xe6,	0xff,	50},
+		{EM2874_R80_GPIO_P0_CTRL,	0xf6,	0xff,	100},
+		{	-1,			-1,	-1,	-1},
+	};
+	struct em28xx_reg_seq terratec_htc_stick_end[] = {
+		{EM2874_R80_GPIO_P0_CTRL,	0xb6,	0xff,	100},
+		{EM2874_R80_GPIO_P0_CTRL,	0xf6,	0xff,	50},
+		{	-1,			-1,	-1,	-1},
+	};
+
+	/*
+	 * Init the analog decoder (not yet supported), but
+	 * it's probably still a good idea.
+	 */
+	struct {
+		unsigned char r[4];
+		int len;
+	} regs[] = {
+		{{ 0x06, 0x02, 0x00, 0x31 }, 4},
+		{{ 0x01, 0x02 }, 2},
+		{{ 0x01, 0x02, 0x00, 0xc6 }, 4},
+		{{ 0x01, 0x00 }, 2},
+		{{ 0x01, 0x00, 0xff, 0xaf }, 4},
+	};
+
+	em28xx_gpio_set(dev, terratec_htc_stick_init);
+
+	em28xx_write_reg(dev, EM28XX_R06_I2C_CLK, 0x40);
+	usleep_range(10000, 11000);
+	em28xx_write_reg(dev, EM28XX_R06_I2C_CLK, 0x44);
+	usleep_range(10000, 11000);
+
+	dev->i2c_client[dev->def_i2c_bus].addr = 0x82 >> 1;
+
+	for (i = 0; i < ARRAY_SIZE(regs); i++)
+		i2c_master_send(&dev->i2c_client[dev->def_i2c_bus],
+				regs[i].r, regs[i].len);
+
+	em28xx_gpio_set(dev, terratec_htc_stick_end);
+};
+
+static void terratec_htc_usb_xs_init(struct em28xx *dev)
+{
+	int i;
+
+	struct em28xx_reg_seq terratec_htc_usb_xs_init[] = {
+		{EM2820_R08_GPIO_CTRL,		0xff,	0xff,	10},
+		{EM2874_R80_GPIO_P0_CTRL,	0xb2,	0xff,	100},
+		{EM2874_R80_GPIO_P0_CTRL,	0xb2,	0xff,	50},
+		{EM2874_R80_GPIO_P0_CTRL,	0xb6,	0xff,	100},
+		{	-1,			-1,	-1,	-1},
+	};
+	struct em28xx_reg_seq terratec_htc_usb_xs_end[] = {
+		{EM2874_R80_GPIO_P0_CTRL,	0xa6,	0xff,	100},
+		{EM2874_R80_GPIO_P0_CTRL,	0xa6,	0xff,	50},
+		{EM2874_R80_GPIO_P0_CTRL,	0xe6,	0xff,	100},
+		{	-1,			-1,	-1,	-1},
+	};
+
+	/*
+	 * Init the analog decoder (not yet supported), but
+	 * it's probably still a good idea.
+	 */
+	struct {
+		unsigned char r[4];
+		int len;
+	} regs[] = {
+		{{ 0x06, 0x02, 0x00, 0x31 }, 4},
+		{{ 0x01, 0x02 }, 2},
+		{{ 0x01, 0x02, 0x00, 0xc6 }, 4},
+		{{ 0x01, 0x00 }, 2},
+		{{ 0x01, 0x00, 0xff, 0xaf }, 4},
+		{{ 0x01, 0x00, 0x03, 0xa0 }, 4},
+		{{ 0x01, 0x00 }, 2},
+		{{ 0x01, 0x00, 0x73, 0xaf }, 4},
+		{{ 0x04, 0x00 }, 2},
+		{{ 0x00, 0x04 }, 2},
+		{{ 0x00, 0x04, 0x00, 0x0a }, 4},
+		{{ 0x04, 0x14 }, 2},
+		{{ 0x04, 0x14, 0x00, 0x00 }, 4},
+	};
+
+	em28xx_write_reg(dev, EM28XX_R06_I2C_CLK, 0x40);
+
+	em28xx_gpio_set(dev, terratec_htc_usb_xs_init);
+
+	em28xx_write_reg(dev, EM28XX_R06_I2C_CLK, 0x40);
+	usleep_range(10000, 11000);
+	em28xx_write_reg(dev, EM28XX_R06_I2C_CLK, 0x44);
+	usleep_range(10000, 11000);
+
+	dev->i2c_client[dev->def_i2c_bus].addr = 0x82 >> 1;
+
+	for (i = 0; i < ARRAY_SIZE(regs); i++)
+		i2c_master_send(&dev->i2c_client[dev->def_i2c_bus],
+				regs[i].r, regs[i].len);
+
+	em28xx_gpio_set(dev, terratec_htc_usb_xs_end);
+};
+
+static void pctv_520e_init(struct em28xx *dev)
+{
+	/*
+	 * Init AVF4910B analog decoder. Looks like I2C traffic to
+	 * digital demodulator and tuner are routed via AVF4910B.
+	 */
+	int i;
+	struct {
+		unsigned char r[4];
+		int len;
+	} regs[] = {
+		{{ 0x06, 0x02, 0x00, 0x31 }, 4},
+		{{ 0x01, 0x02 }, 2},
+		{{ 0x01, 0x02, 0x00, 0xc6 }, 4},
+		{{ 0x01, 0x00 }, 2},
+		{{ 0x01, 0x00, 0xff, 0xaf }, 4},
+		{{ 0x01, 0x00, 0x03, 0xa0 }, 4},
+		{{ 0x01, 0x00 }, 2},
+		{{ 0x01, 0x00, 0x73, 0xaf }, 4},
+	};
+
+	dev->i2c_client[dev->def_i2c_bus].addr = 0x82 >> 1; /* 0x41 */
+
+	for (i = 0; i < ARRAY_SIZE(regs); i++)
+		i2c_master_send(&dev->i2c_client[dev->def_i2c_bus],
+				regs[i].r, regs[i].len);
+};
+
+static int em28xx_pctv_290e_set_lna(struct dvb_frontend *fe)
+{
+	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+	struct em28xx_i2c_bus *i2c_bus = fe->dvb->priv;
+	struct em28xx *dev = i2c_bus->dev;
+#ifdef CONFIG_GPIOLIB
+	struct em28xx_dvb *dvb = dev->dvb;
+	int ret;
+	unsigned long flags;
+
+	if (c->lna == 1)
+		flags = GPIOF_OUT_INIT_HIGH; /* enable LNA */
+	else
+		flags = GPIOF_OUT_INIT_LOW; /* disable LNA */
+
+	ret = gpio_request_one(dvb->lna_gpio, flags, NULL);
+	if (ret)
+		dev_err(&dev->intf->dev, "gpio request failed %d\n", ret);
+	else
+		gpio_free(dvb->lna_gpio);
+
+	return ret;
+#else
+	dev_warn(&dev->intf->dev, "%s: LNA control is disabled (lna=%u)\n",
+		 KBUILD_MODNAME, c->lna);
+	return 0;
+#endif
+}
+
+static int em28xx_pctv_292e_set_lna(struct dvb_frontend *fe)
+{
+	struct dtv_frontend_properties *c = &fe->dtv_property_cache;
+	struct em28xx_i2c_bus *i2c_bus = fe->dvb->priv;
+	struct em28xx *dev = i2c_bus->dev;
+	u8 lna;
+
+	if (c->lna == 1)
+		lna = 0x01;
+	else
+		lna = 0x00;
+
+	return em28xx_write_reg_bits(dev, EM2874_R80_GPIO_P0_CTRL, lna, 0x01);
+}
+
+static int em28xx_mt352_terratec_xs_init(struct dvb_frontend *fe)
+{
+	/* Values extracted from a USB trace of the Terratec Windows driver */
+	static u8 clock_config[]   = { CLOCK_CTL,  0x38, 0x2c };
+	static u8 reset[]          = { RESET,      0x80 };
+	static u8 adc_ctl_1_cfg[]  = { ADC_CTL_1,  0x40 };
+	static u8 agc_cfg[]        = { AGC_TARGET, 0x28, 0xa0 };
+	static u8 input_freq_cfg[] = { INPUT_FREQ_1, 0x31, 0xb8 };
+	static u8 rs_err_cfg[]     = { RS_ERR_PER_1, 0x00, 0x4d };
+	static u8 capt_range_cfg[] = { CAPT_RANGE, 0x32 };
+	static u8 trl_nom_cfg[]    = { TRL_NOMINAL_RATE_1, 0x64, 0x00 };
+	static u8 tps_given_cfg[]  = { TPS_GIVEN_1, 0x40, 0x80, 0x50 };
+	static u8 tuner_go[]       = { TUNER_GO, 0x01};
+
+	mt352_write(fe, clock_config,   sizeof(clock_config));
+	usleep_range(200, 250);
+	mt352_write(fe, reset,          sizeof(reset));
+	mt352_write(fe, adc_ctl_1_cfg,  sizeof(adc_ctl_1_cfg));
+	mt352_write(fe, agc_cfg,        sizeof(agc_cfg));
+	mt352_write(fe, input_freq_cfg, sizeof(input_freq_cfg));
+	mt352_write(fe, rs_err_cfg,     sizeof(rs_err_cfg));
+	mt352_write(fe, capt_range_cfg, sizeof(capt_range_cfg));
+	mt352_write(fe, trl_nom_cfg,    sizeof(trl_nom_cfg));
+	mt352_write(fe, tps_given_cfg,  sizeof(tps_given_cfg));
+	mt352_write(fe, tuner_go,       sizeof(tuner_go));
+	return 0;
+}
+
+static void px_bcud_init(struct em28xx *dev)
+{
+	int i;
+	struct {
+		unsigned char r[4];
+		int len;
+	} regs1[] = {
+		{{ 0x0e, 0x77 }, 2},
+		{{ 0x0f, 0x77 }, 2},
+		{{ 0x03, 0x90 }, 2},
+	}, regs2[] = {
+		{{ 0x07, 0x01 }, 2},
+		{{ 0x08, 0x10 }, 2},
+		{{ 0x13, 0x00 }, 2},
+		{{ 0x17, 0x00 }, 2},
+		{{ 0x03, 0x01 }, 2},
+		{{ 0x10, 0xb1 }, 2},
+		{{ 0x11, 0x40 }, 2},
+		{{ 0x85, 0x7a }, 2},
+		{{ 0x87, 0x04 }, 2},
+	};
+	static struct em28xx_reg_seq gpio[] = {
+		{EM28XX_R06_I2C_CLK,		0x40,	0xff,	300},
+		{EM2874_R80_GPIO_P0_CTRL,	0xfd,	0xff,	60},
+		{EM28XX_R15_RGAIN,		0x20,	0xff,	0},
+		{EM28XX_R16_GGAIN,		0x20,	0xff,	0},
+		{EM28XX_R17_BGAIN,		0x20,	0xff,	0},
+		{EM28XX_R18_ROFFSET,		0x00,	0xff,	0},
+		{EM28XX_R19_GOFFSET,		0x00,	0xff,	0},
+		{EM28XX_R1A_BOFFSET,		0x00,	0xff,	0},
+		{EM28XX_R23_UOFFSET,		0x00,	0xff,	0},
+		{EM28XX_R24_VOFFSET,		0x00,	0xff,	0},
+		{EM28XX_R26_COMPR,		0x00,	0xff,	0},
+		{0x13,				0x08,	0xff,	0},
+		{EM28XX_R12_VINENABLE,		0x27,	0xff,	0},
+		{EM28XX_R0C_USBSUSP,		0x10,	0xff,	0},
+		{EM28XX_R27_OUTFMT,		0x00,	0xff,	0},
+		{EM28XX_R10_VINMODE,		0x00,	0xff,	0},
+		{EM28XX_R11_VINCTRL,		0x11,	0xff,	0},
+		{EM2874_R50_IR_CONFIG,		0x01,	0xff,	0},
+		{EM2874_R5F_TS_ENABLE,		0x80,	0xff,	0},
+		{EM28XX_R06_I2C_CLK,		0x46,	0xff,	0},
+	};
+	em28xx_write_reg(dev, EM28XX_R06_I2C_CLK, 0x46);
+	/* sleeping ISDB-T */
+	dev->dvb->i2c_client_demod->addr = 0x14;
+	for (i = 0; i < ARRAY_SIZE(regs1); i++)
+		i2c_master_send(dev->dvb->i2c_client_demod,
+				regs1[i].r, regs1[i].len);
+	/* sleeping ISDB-S */
+	dev->dvb->i2c_client_demod->addr = 0x15;
+	for (i = 0; i < ARRAY_SIZE(regs2); i++)
+		i2c_master_send(dev->dvb->i2c_client_demod, regs2[i].r,
+				regs2[i].len);
+	for (i = 0; i < ARRAY_SIZE(gpio); i++) {
+		em28xx_write_reg_bits(dev, gpio[i].reg, gpio[i].val,
+				      gpio[i].mask);
+		if (gpio[i].sleep > 0)
+			msleep(gpio[i].sleep);
+	}
+};
+
+static struct mt352_config terratec_xs_mt352_cfg = {
+	.demod_address = (0x1e >> 1),
+	.no_tuner = 1,
+	.if2 = 45600,
+	.demod_init = em28xx_mt352_terratec_xs_init,
+};
+
+static struct tda10023_config em28xx_tda10023_config = {
+	.demod_address = 0x0c,
+	.invert = 1,
+};
+
+static struct cxd2820r_config em28xx_cxd2820r_config = {
+	.i2c_address = (0xd8 >> 1),
+	.ts_mode = CXD2820R_TS_SERIAL,
+};
+
+static struct tda18271_config em28xx_cxd2820r_tda18271_config = {
+	.output_opt = TDA18271_OUTPUT_LT_OFF,
+	.gate = TDA18271_GATE_DIGITAL,
+};
+
+static struct zl10353_config em28xx_zl10353_no_i2c_gate_dev = {
+	.demod_address = (0x1e >> 1),
+	.disable_i2c_gate_ctrl = 1,
+	.no_tuner = 1,
+	.parallel_ts = 1,
+};
+
+static struct mt2060_config em28xx_mt2060_config = {
+	.i2c_address = 0x60,
+};
+
+static struct qt1010_config em28xx_qt1010_config = {
+	.i2c_address = 0x62
+};
+
+static const struct mb86a20s_config c3tech_duo_mb86a20s_config = {
+	.demod_address = 0x10,
+	.is_serial = true,
+};
+
+static struct tda18271_std_map mb86a20s_tda18271_config = {
+	.dvbt_6   = { .if_freq = 4000, .agc_mode = 3, .std = 4,
+		      .if_lvl = 1, .rfagc_top = 0x37, },
+};
+
+static struct tda18271_config c3tech_duo_tda18271_config = {
+	.std_map = &mb86a20s_tda18271_config,
+	.gate    = TDA18271_GATE_DIGITAL,
+	.small_i2c = TDA18271_03_BYTE_CHUNK_INIT,
+};
+
+static struct tda18271_std_map drx_j_std_map = {
+	.atsc_6   = { .if_freq = 5000, .agc_mode = 3, .std = 0, .if_lvl = 1,
+		      .rfagc_top = 0x37, },
+	.qam_6    = { .if_freq = 5380, .agc_mode = 3, .std = 3, .if_lvl = 1,
+		      .rfagc_top = 0x37, },
+};
+
+static struct tda18271_config pinnacle_80e_dvb_config = {
+	.std_map = &drx_j_std_map,
+	.gate    = TDA18271_GATE_DIGITAL,
+	.role    = TDA18271_MASTER,
+};
+
+static struct lgdt3306a_config hauppauge_01595_lgdt3306a_config = {
+	.qam_if_khz         = 4000,
+	.vsb_if_khz         = 3250,
+	.spectral_inversion = 0,
+	.deny_i2c_rptr      = 0,
+	.mpeg_mode          = LGDT3306A_MPEG_SERIAL,
+	.tpclk_edge         = LGDT3306A_TPCLK_RISING_EDGE,
+	.tpvalid_polarity   = LGDT3306A_TP_VALID_HIGH,
+	.xtalMHz            = 25,
+};
+
+/* ------------------------------------------------------------------ */
+
+static noinline_for_stack int em28xx_attach_xc3028(u8 addr, struct em28xx *dev)
+{
+	struct dvb_frontend *fe;
+	struct xc2028_config cfg;
+	struct xc2028_ctrl ctl;
+
+	memset(&cfg, 0, sizeof(cfg));
+	cfg.i2c_adap  = &dev->i2c_adap[dev->def_i2c_bus];
+	cfg.i2c_addr  = addr;
+
+	memset(&ctl, 0, sizeof(ctl));
+	em28xx_setup_xc3028(dev, &ctl);
+	cfg.ctrl  = &ctl;
+
+	if (!dev->dvb->fe[0]) {
+		dev_err(&dev->intf->dev,
+			"dvb frontend not attached. Can't attach xc3028\n");
+		return -EINVAL;
+	}
+
+	fe = dvb_attach(xc2028_attach, dev->dvb->fe[0], &cfg);
+	if (!fe) {
+		dev_err(&dev->intf->dev, "xc3028 attach failed\n");
+		dvb_frontend_detach(dev->dvb->fe[0]);
+		dev->dvb->fe[0] = NULL;
+		return -EINVAL;
+	}
+
+	dev_info(&dev->intf->dev, "xc3028 attached\n");
+
+	return 0;
+}
+
+/* ------------------------------------------------------------------ */
+
+static int em28xx_register_dvb(struct em28xx_dvb *dvb, struct module *module,
+			       struct em28xx *dev, struct device *device)
+{
+	int result;
+	bool create_rf_connector = false;
+
+	mutex_init(&dvb->lock);
+
+	/* register adapter */
+	result = dvb_register_adapter(&dvb->adapter,
+				      dev_name(&dev->intf->dev), module,
+				      device, adapter_nr);
+	if (result < 0) {
+		dev_warn(&dev->intf->dev,
+			 "dvb_register_adapter failed (errno = %d)\n",
+			 result);
+		goto fail_adapter;
+	}
+#ifdef CONFIG_MEDIA_CONTROLLER_DVB
+	dvb->adapter.mdev = dev->media_dev;
+#endif
+
+	/* Ensure all frontends negotiate bus access */
+	dvb->fe[0]->ops.ts_bus_ctrl = em28xx_dvb_bus_ctrl;
+	if (dvb->fe[1])
+		dvb->fe[1]->ops.ts_bus_ctrl = em28xx_dvb_bus_ctrl;
+
+	dvb->adapter.priv = &dev->i2c_bus[dev->def_i2c_bus];
+
+	/* register frontend */
+	result = dvb_register_frontend(&dvb->adapter, dvb->fe[0]);
+	if (result < 0) {
+		dev_warn(&dev->intf->dev,
+			 "dvb_register_frontend failed (errno = %d)\n",
+			 result);
+		goto fail_frontend0;
+	}
+
+	/* register 2nd frontend */
+	if (dvb->fe[1]) {
+		result = dvb_register_frontend(&dvb->adapter, dvb->fe[1]);
+		if (result < 0) {
+			dev_warn(&dev->intf->dev,
+				 "2nd dvb_register_frontend failed (errno = %d)\n",
+				 result);
+			goto fail_frontend1;
+		}
+	}
+
+	/* register demux stuff */
+	dvb->demux.dmx.capabilities =
+		DMX_TS_FILTERING | DMX_SECTION_FILTERING |
+		DMX_MEMORY_BASED_FILTERING;
+	dvb->demux.priv       = dvb;
+	dvb->demux.filternum  = 256;
+	dvb->demux.feednum    = 256;
+	dvb->demux.start_feed = em28xx_start_feed;
+	dvb->demux.stop_feed  = em28xx_stop_feed;
+
+	result = dvb_dmx_init(&dvb->demux);
+	if (result < 0) {
+		dev_warn(&dev->intf->dev,
+			 "dvb_dmx_init failed (errno = %d)\n",
+			 result);
+		goto fail_dmx;
+	}
+
+	dvb->dmxdev.filternum    = 256;
+	dvb->dmxdev.demux        = &dvb->demux.dmx;
+	dvb->dmxdev.capabilities = 0;
+	result = dvb_dmxdev_init(&dvb->dmxdev, &dvb->adapter);
+	if (result < 0) {
+		dev_warn(&dev->intf->dev,
+			 "dvb_dmxdev_init failed (errno = %d)\n",
+			 result);
+		goto fail_dmxdev;
+	}
+
+	dvb->fe_hw.source = DMX_FRONTEND_0;
+	result = dvb->demux.dmx.add_frontend(&dvb->demux.dmx, &dvb->fe_hw);
+	if (result < 0) {
+		dev_warn(&dev->intf->dev,
+			 "add_frontend failed (DMX_FRONTEND_0, errno = %d)\n",
+			 result);
+		goto fail_fe_hw;
+	}
+
+	dvb->fe_mem.source = DMX_MEMORY_FE;
+	result = dvb->demux.dmx.add_frontend(&dvb->demux.dmx, &dvb->fe_mem);
+	if (result < 0) {
+		dev_warn(&dev->intf->dev,
+			 "add_frontend failed (DMX_MEMORY_FE, errno = %d)\n",
+			 result);
+		goto fail_fe_mem;
+	}
+
+	result = dvb->demux.dmx.connect_frontend(&dvb->demux.dmx, &dvb->fe_hw);
+	if (result < 0) {
+		dev_warn(&dev->intf->dev,
+			 "connect_frontend failed (errno = %d)\n",
+			 result);
+		goto fail_fe_conn;
+	}
+
+	/* register network adapter */
+	dvb_net_init(&dvb->adapter, &dvb->net, &dvb->demux.dmx);
+
+	/* If the analog part won't create RF connectors, DVB will do it */
+	if (!dev->has_video || dev->tuner_type == TUNER_ABSENT)
+		create_rf_connector = true;
+
+	result = dvb_create_media_graph(&dvb->adapter, create_rf_connector);
+	if (result < 0)
+		goto fail_create_graph;
+
+	return 0;
+
+fail_create_graph:
+	dvb_net_release(&dvb->net);
+fail_fe_conn:
+	dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_mem);
+fail_fe_mem:
+	dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_hw);
+fail_fe_hw:
+	dvb_dmxdev_release(&dvb->dmxdev);
+fail_dmxdev:
+	dvb_dmx_release(&dvb->demux);
+fail_dmx:
+	if (dvb->fe[1])
+		dvb_unregister_frontend(dvb->fe[1]);
+	dvb_unregister_frontend(dvb->fe[0]);
+fail_frontend1:
+	if (dvb->fe[1])
+		dvb_frontend_detach(dvb->fe[1]);
+fail_frontend0:
+	dvb_frontend_detach(dvb->fe[0]);
+	dvb_unregister_adapter(&dvb->adapter);
+fail_adapter:
+	return result;
+}
+
+static void em28xx_unregister_dvb(struct em28xx_dvb *dvb)
+{
+	dvb_net_release(&dvb->net);
+	dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_mem);
+	dvb->demux.dmx.remove_frontend(&dvb->demux.dmx, &dvb->fe_hw);
+	dvb_dmxdev_release(&dvb->dmxdev);
+	dvb_dmx_release(&dvb->demux);
+	if (dvb->fe[1])
+		dvb_unregister_frontend(dvb->fe[1]);
+	dvb_unregister_frontend(dvb->fe[0]);
+	if (dvb->fe[1] && !dvb->dont_attach_fe1)
+		dvb_frontend_detach(dvb->fe[1]);
+	dvb_frontend_detach(dvb->fe[0]);
+	dvb_unregister_adapter(&dvb->adapter);
+}
+
+static int em28174_dvb_init_pctv_460e(struct em28xx *dev)
+{
+	struct em28xx_dvb *dvb = dev->dvb;
+	struct tda10071_platform_data tda10071_pdata = {};
+	struct a8293_platform_data a8293_pdata = {};
+
+	/* attach demod + tuner combo */
+	tda10071_pdata.clk = 40444000; /* 40.444 MHz */
+	tda10071_pdata.i2c_wr_max = 64;
+	tda10071_pdata.ts_mode = TDA10071_TS_SERIAL;
+	tda10071_pdata.pll_multiplier = 20;
+	tda10071_pdata.tuner_i2c_addr = 0x14;
+
+	dvb->i2c_client_demod = dvb_module_probe("tda10071", "tda10071_cx24118",
+						 &dev->i2c_adap[dev->def_i2c_bus],
+						 0x55, &tda10071_pdata);
+	if (!dvb->i2c_client_demod)
+		return -ENODEV;
+
+	dvb->fe[0] = tda10071_pdata.get_dvb_frontend(dvb->i2c_client_demod);
+
+	/* attach SEC */
+	a8293_pdata.dvb_frontend = dvb->fe[0];
+
+	dvb->i2c_client_sec = dvb_module_probe("a8293", NULL,
+					       &dev->i2c_adap[dev->def_i2c_bus],
+					       0x08, &a8293_pdata);
+	if (!dvb->i2c_client_sec) {
+		dvb_module_release(dvb->i2c_client_demod);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int em28178_dvb_init_pctv_461e(struct em28xx *dev)
+{
+	struct em28xx_dvb *dvb = dev->dvb;
+	struct i2c_adapter *i2c_adapter;
+	struct m88ds3103_platform_data m88ds3103_pdata = {};
+	struct ts2020_config ts2020_config = {};
+	struct a8293_platform_data a8293_pdata = {};
+
+	/* attach demod */
+	m88ds3103_pdata.clk = 27000000;
+	m88ds3103_pdata.i2c_wr_max = 33;
+	m88ds3103_pdata.ts_mode = M88DS3103_TS_PARALLEL;
+	m88ds3103_pdata.ts_clk = 16000;
+	m88ds3103_pdata.ts_clk_pol = 1;
+	m88ds3103_pdata.agc = 0x99;
+
+	dvb->i2c_client_demod = dvb_module_probe("m88ds3103", NULL,
+						 &dev->i2c_adap[dev->def_i2c_bus],
+						 0x68, &m88ds3103_pdata);
+	if (!dvb->i2c_client_demod)
+		return -ENODEV;
+
+	dvb->fe[0] = m88ds3103_pdata.get_dvb_frontend(dvb->i2c_client_demod);
+	i2c_adapter = m88ds3103_pdata.get_i2c_adapter(dvb->i2c_client_demod);
+
+	/* attach tuner */
+	ts2020_config.fe = dvb->fe[0];
+
+	dvb->i2c_client_tuner = dvb_module_probe("ts2020", "ts2022",
+						 i2c_adapter,
+						 0x60, &ts2020_config);
+	if (!dvb->i2c_client_tuner) {
+		dvb_module_release(dvb->i2c_client_demod);
+		return -ENODEV;
+	}
+
+	/* delegate signal strength measurement to tuner */
+	dvb->fe[0]->ops.read_signal_strength =
+			dvb->fe[0]->ops.tuner_ops.get_rf_strength;
+
+	/* attach SEC */
+	a8293_pdata.dvb_frontend = dvb->fe[0];
+	dvb->i2c_client_sec = dvb_module_probe("a8293", NULL,
+					       &dev->i2c_adap[dev->def_i2c_bus],
+					       0x08, &a8293_pdata);
+	if (!dvb->i2c_client_sec) {
+		dvb_module_release(dvb->i2c_client_tuner);
+		dvb_module_release(dvb->i2c_client_demod);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int em28178_dvb_init_pctv_292e(struct em28xx *dev)
+{
+	struct em28xx_dvb *dvb = dev->dvb;
+	struct i2c_adapter *adapter;
+	struct si2168_config si2168_config = {};
+	struct si2157_config si2157_config = {};
+
+	/* attach demod */
+	si2168_config.i2c_adapter = &adapter;
+	si2168_config.fe = &dvb->fe[0];
+	si2168_config.ts_mode = SI2168_TS_PARALLEL;
+	si2168_config.spectral_inversion = true;
+
+	dvb->i2c_client_demod = dvb_module_probe("si2168", NULL,
+						 &dev->i2c_adap[dev->def_i2c_bus],
+						 0x64, &si2168_config);
+	if (!dvb->i2c_client_demod)
+		return -ENODEV;
+
+	/* attach tuner */
+	si2157_config.fe = dvb->fe[0];
+	si2157_config.if_port = 1;
+#ifdef CONFIG_MEDIA_CONTROLLER_DVB
+	si2157_config.mdev = dev->media_dev;
+#endif
+	dvb->i2c_client_tuner = dvb_module_probe("si2157", NULL,
+						 adapter,
+						 0x60, &si2157_config);
+	if (!dvb->i2c_client_tuner) {
+		dvb_module_release(dvb->i2c_client_demod);
+		return -ENODEV;
+	}
+	dvb->fe[0]->ops.set_lna = em28xx_pctv_292e_set_lna;
+
+	return 0;
+}
+
+static int em28178_dvb_init_terratec_t2_stick_hd(struct em28xx *dev)
+{
+	struct em28xx_dvb *dvb = dev->dvb;
+	struct i2c_adapter *adapter;
+	struct si2168_config si2168_config = {};
+	struct si2157_config si2157_config = {};
+
+	/* attach demod */
+	si2168_config.i2c_adapter = &adapter;
+	si2168_config.fe = &dvb->fe[0];
+	si2168_config.ts_mode = SI2168_TS_PARALLEL;
+
+	dvb->i2c_client_demod = dvb_module_probe("si2168", NULL,
+						 &dev->i2c_adap[dev->def_i2c_bus],
+						 0x64, &si2168_config);
+	if (!dvb->i2c_client_demod)
+		return -ENODEV;
+
+	/* attach tuner */
+	memset(&si2157_config, 0, sizeof(si2157_config));
+	si2157_config.fe = dvb->fe[0];
+	si2157_config.if_port = 0;
+#ifdef CONFIG_MEDIA_CONTROLLER_DVB
+	si2157_config.mdev = dev->media_dev;
+#endif
+	dvb->i2c_client_tuner = dvb_module_probe("si2157", "si2146",
+						 adapter,
+						 0x60, &si2157_config);
+	if (!dvb->i2c_client_tuner) {
+		dvb_module_release(dvb->i2c_client_demod);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int em28178_dvb_init_plex_px_bcud(struct em28xx *dev)
+{
+	struct em28xx_dvb *dvb = dev->dvb;
+	struct tc90522_config tc90522_config = {};
+	struct qm1d1c0042_config qm1d1c0042_config = {};
+
+	/* attach demod */
+	dvb->i2c_client_demod = dvb_module_probe("tc90522", "tc90522sat",
+						 &dev->i2c_adap[dev->def_i2c_bus],
+						 0x15, &tc90522_config);
+	if (!dvb->i2c_client_demod)
+		return -ENODEV;
+
+	/* attach tuner */
+	qm1d1c0042_config.fe = tc90522_config.fe;
+	qm1d1c0042_config.lpf = 1;
+
+	dvb->i2c_client_tuner = dvb_module_probe("qm1d1c0042", NULL,
+						 tc90522_config.tuner_i2c,
+						 0x61, &qm1d1c0042_config);
+	if (!dvb->i2c_client_tuner) {
+		dvb_module_release(dvb->i2c_client_demod);
+		return -ENODEV;
+	}
+
+	dvb->fe[0] = tc90522_config.fe;
+	px_bcud_init(dev);
+
+	return 0;
+}
+
+static int em28174_dvb_init_hauppauge_wintv_dualhd_dvb(struct em28xx *dev)
+{
+	struct em28xx_dvb *dvb = dev->dvb;
+	struct i2c_adapter *adapter;
+	struct si2168_config si2168_config = {};
+	struct si2157_config si2157_config = {};
+	unsigned char addr;
+
+	/* attach demod */
+	si2168_config.i2c_adapter = &adapter;
+	si2168_config.fe = &dvb->fe[0];
+	si2168_config.ts_mode = SI2168_TS_SERIAL;
+	si2168_config.spectral_inversion = true;
+	addr = (dev->ts == PRIMARY_TS) ? 0x64 : 0x67;
+
+	dvb->i2c_client_demod = dvb_module_probe("si2168", NULL,
+						 &dev->i2c_adap[dev->def_i2c_bus],
+						 addr, &si2168_config);
+	if (!dvb->i2c_client_demod)
+		return -ENODEV;
+
+	/* attach tuner */
+	memset(&si2157_config, 0, sizeof(si2157_config));
+	si2157_config.fe = dvb->fe[0];
+	si2157_config.if_port = 1;
+#ifdef CONFIG_MEDIA_CONTROLLER_DVB
+	si2157_config.mdev = dev->media_dev;
+#endif
+	addr = (dev->ts == PRIMARY_TS) ? 0x60 : 0x63;
+
+	dvb->i2c_client_tuner = dvb_module_probe("si2157", NULL,
+						 adapter,
+						 addr, &si2157_config);
+	if (!dvb->i2c_client_tuner) {
+		dvb_module_release(dvb->i2c_client_demod);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int em28174_dvb_init_hauppauge_wintv_dualhd_01595(struct em28xx *dev)
+{
+	struct em28xx_dvb *dvb = dev->dvb;
+	struct i2c_adapter *adapter;
+	struct lgdt3306a_config lgdt3306a_config =  {};
+	struct si2157_config si2157_config = {};
+	unsigned char addr;
+
+	/* attach demod */
+	lgdt3306a_config = hauppauge_01595_lgdt3306a_config;
+	lgdt3306a_config.fe = &dvb->fe[0];
+	lgdt3306a_config.i2c_adapter = &adapter;
+	addr = (dev->ts == PRIMARY_TS) ? 0x59 : 0x0e;
+
+	dvb->i2c_client_demod = dvb_module_probe("lgdt3306a", NULL,
+						 &dev->i2c_adap[dev->def_i2c_bus],
+						 addr, &lgdt3306a_config);
+	if (!dvb->i2c_client_demod)
+		return -ENODEV;
+
+	/* attach tuner */
+	si2157_config.fe = dvb->fe[0];
+	si2157_config.if_port = 1;
+	si2157_config.inversion = 1;
+#ifdef CONFIG_MEDIA_CONTROLLER_DVB
+	si2157_config.mdev = dev->media_dev;
+#endif
+	addr = (dev->ts == PRIMARY_TS) ? 0x60 : 0x62;
+
+	dvb->i2c_client_tuner = dvb_module_probe("si2157", NULL,
+						 adapter,
+						 addr, &si2157_config);
+	if (!dvb->i2c_client_tuner) {
+		dvb_module_release(dvb->i2c_client_demod);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int em28xx_dvb_init(struct em28xx *dev)
+{
+	int result = 0, dvb_alt = 0;
+	struct em28xx_dvb *dvb;
+	struct usb_device *udev;
+
+	if (dev->is_audio_only) {
+		/* Shouldn't initialize IR for this interface */
+		return 0;
+	}
+
+	if (!dev->board.has_dvb) {
+		/* This device does not support the extension */
+		return 0;
+	}
+
+	dev_info(&dev->intf->dev, "Binding DVB extension\n");
+
+	dvb = kzalloc(sizeof(*dvb), GFP_KERNEL);
+	if (!dvb)
+		return -ENOMEM;
+
+	dev->dvb = dvb;
+	dvb->fe[0] = NULL;
+	dvb->fe[1] = NULL;
+
+	/* pre-allocate DVB usb transfer buffers */
+	if (dev->dvb_xfer_bulk) {
+		result = em28xx_alloc_urbs(dev, EM28XX_DIGITAL_MODE,
+					   dev->dvb_xfer_bulk,
+					   EM28XX_DVB_NUM_BUFS,
+					   512,
+					   EM28XX_DVB_BULK_PACKET_MULTIPLIER);
+	} else {
+		result = em28xx_alloc_urbs(dev, EM28XX_DIGITAL_MODE,
+					   dev->dvb_xfer_bulk,
+					   EM28XX_DVB_NUM_BUFS,
+					   dev->dvb_max_pkt_size_isoc,
+					   EM28XX_DVB_NUM_ISOC_PACKETS);
+	}
+	if (result) {
+		dev_err(&dev->intf->dev,
+			"failed to pre-allocate USB transfer buffers for DVB.\n");
+		kfree(dvb);
+		dev->dvb = NULL;
+		return result;
+	}
+
+	mutex_lock(&dev->lock);
+	em28xx_set_mode(dev, EM28XX_DIGITAL_MODE);
+	/* init frontend */
+	switch (dev->model) {
+	case EM2874_BOARD_LEADERSHIP_ISDBT:
+		dvb->fe[0] = dvb_attach(s921_attach,
+					&sharp_isdbt,
+					&dev->i2c_adap[dev->def_i2c_bus]);
+
+		if (!dvb->fe[0]) {
+			result = -EINVAL;
+			goto out_free;
+		}
+
+		break;
+	case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_850:
+	case EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950:
+	case EM2880_BOARD_PINNACLE_PCTV_HD_PRO:
+	case EM2880_BOARD_AMD_ATI_TV_WONDER_HD_600:
+		dvb->fe[0] = dvb_attach(lgdt330x_attach,
+					&em2880_lgdt3303_dev,
+					0x0e,
+					&dev->i2c_adap[dev->def_i2c_bus]);
+		if (em28xx_attach_xc3028(0x61, dev) < 0) {
+			result = -EINVAL;
+			goto out_free;
+		}
+		break;
+	case EM2880_BOARD_KWORLD_DVB_310U:
+		dvb->fe[0] = dvb_attach(zl10353_attach,
+					&em28xx_zl10353_with_xc3028,
+					&dev->i2c_adap[dev->def_i2c_bus]);
+		if (em28xx_attach_xc3028(0x61, dev) < 0) {
+			result = -EINVAL;
+			goto out_free;
+		}
+		break;
+	case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900:
+	case EM2882_BOARD_TERRATEC_HYBRID_XS:
+	case EM2880_BOARD_EMPIRE_DUAL_TV:
+	case EM2882_BOARD_ZOLID_HYBRID_TV_STICK:
+		dvb->fe[0] = dvb_attach(zl10353_attach,
+					&em28xx_zl10353_xc3028_no_i2c_gate,
+					&dev->i2c_adap[dev->def_i2c_bus]);
+		if (em28xx_attach_xc3028(0x61, dev) < 0) {
+			result = -EINVAL;
+			goto out_free;
+		}
+		break;
+	case EM2880_BOARD_TERRATEC_HYBRID_XS:
+	case EM2880_BOARD_TERRATEC_HYBRID_XS_FR:
+	case EM2881_BOARD_PINNACLE_HYBRID_PRO:
+	case EM2882_BOARD_DIKOM_DK300:
+	case EM2882_BOARD_KWORLD_VS_DVBT:
+		/*
+		 * Those boards could have either a zl10353 or a mt352.
+		 * If the chip id isn't for zl10353, try mt352.
+		 */
+		dvb->fe[0] = dvb_attach(zl10353_attach,
+					&em28xx_zl10353_xc3028_no_i2c_gate,
+					&dev->i2c_adap[dev->def_i2c_bus]);
+		if (!dvb->fe[0])
+			dvb->fe[0] = dvb_attach(mt352_attach,
+						&terratec_xs_mt352_cfg,
+						&dev->i2c_adap[dev->def_i2c_bus]);
+
+		if (em28xx_attach_xc3028(0x61, dev) < 0) {
+			result = -EINVAL;
+			goto out_free;
+		}
+		break;
+	case EM2870_BOARD_TERRATEC_XS_MT2060:
+		dvb->fe[0] = dvb_attach(zl10353_attach,
+					&em28xx_zl10353_no_i2c_gate_dev,
+					&dev->i2c_adap[dev->def_i2c_bus]);
+		if (dvb->fe[0]) {
+			dvb_attach(mt2060_attach, dvb->fe[0],
+				   &dev->i2c_adap[dev->def_i2c_bus],
+				   &em28xx_mt2060_config, 1220);
+		}
+		break;
+	case EM2870_BOARD_KWORLD_355U:
+		dvb->fe[0] = dvb_attach(zl10353_attach,
+					&em28xx_zl10353_no_i2c_gate_dev,
+					&dev->i2c_adap[dev->def_i2c_bus]);
+		if (dvb->fe[0])
+			dvb_attach(qt1010_attach, dvb->fe[0],
+				   &dev->i2c_adap[dev->def_i2c_bus],
+				   &em28xx_qt1010_config);
+		break;
+	case EM2883_BOARD_KWORLD_HYBRID_330U:
+	case EM2882_BOARD_EVGA_INDTUBE:
+		dvb->fe[0] = dvb_attach(s5h1409_attach,
+					&em28xx_s5h1409_with_xc3028,
+					&dev->i2c_adap[dev->def_i2c_bus]);
+		if (em28xx_attach_xc3028(0x61, dev) < 0) {
+			result = -EINVAL;
+			goto out_free;
+		}
+		break;
+	case EM2882_BOARD_KWORLD_ATSC_315U:
+		dvb->fe[0] = dvb_attach(lgdt330x_attach,
+					&em2880_lgdt3303_dev,
+					0x0e,
+					&dev->i2c_adap[dev->def_i2c_bus]);
+		if (dvb->fe[0]) {
+			if (!dvb_attach(simple_tuner_attach, dvb->fe[0],
+					&dev->i2c_adap[dev->def_i2c_bus],
+					0x61, TUNER_THOMSON_DTT761X)) {
+				result = -EINVAL;
+				goto out_free;
+			}
+		}
+		break;
+	case EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2:
+	case EM2882_BOARD_PINNACLE_HYBRID_PRO_330E:
+		dvb->fe[0] = dvb_attach(drxd_attach, &em28xx_drxd, NULL,
+					&dev->i2c_adap[dev->def_i2c_bus],
+					&dev->intf->dev);
+		if (em28xx_attach_xc3028(0x61, dev) < 0) {
+			result = -EINVAL;
+			goto out_free;
+		}
+		break;
+	case EM2870_BOARD_REDDO_DVB_C_USB_BOX:
+		/* Philips CU1216L NIM (Philips TDA10023 + Infineon TUA6034) */
+		dvb->fe[0] = dvb_attach(tda10023_attach,
+					&em28xx_tda10023_config,
+					&dev->i2c_adap[dev->def_i2c_bus],
+					0x48);
+		if (dvb->fe[0]) {
+			if (!dvb_attach(simple_tuner_attach, dvb->fe[0],
+					&dev->i2c_adap[dev->def_i2c_bus],
+					0x60, TUNER_PHILIPS_CU1216L)) {
+				result = -EINVAL;
+				goto out_free;
+			}
+		}
+		break;
+	case EM2870_BOARD_KWORLD_A340:
+		dvb->fe[0] = dvb_attach(lgdt3305_attach,
+					&em2870_lgdt3304_dev,
+					&dev->i2c_adap[dev->def_i2c_bus]);
+		if (!dvb->fe[0]) {
+			result = -EINVAL;
+			goto out_free;
+		}
+		if (!dvb_attach(tda18271_attach, dvb->fe[0], 0x60,
+				&dev->i2c_adap[dev->def_i2c_bus],
+				&kworld_a340_config)) {
+			dvb_frontend_detach(dvb->fe[0]);
+			result = -EINVAL;
+			goto out_free;
+		}
+		break;
+	case EM28174_BOARD_PCTV_290E:
+		/* set default GPIO0 for LNA, used if GPIOLIB is undefined */
+		dvb->lna_gpio = CXD2820R_GPIO_E | CXD2820R_GPIO_O |
+				CXD2820R_GPIO_L;
+		dvb->fe[0] = dvb_attach(cxd2820r_attach,
+					&em28xx_cxd2820r_config,
+					&dev->i2c_adap[dev->def_i2c_bus],
+					&dvb->lna_gpio);
+		if (dvb->fe[0]) {
+			/* FE 0 attach tuner */
+			if (!dvb_attach(tda18271_attach,
+					dvb->fe[0],
+					0x60,
+					&dev->i2c_adap[dev->def_i2c_bus],
+					&em28xx_cxd2820r_tda18271_config)) {
+				dvb_frontend_detach(dvb->fe[0]);
+				result = -EINVAL;
+				goto out_free;
+			}
+
+#ifdef CONFIG_GPIOLIB
+			/* enable LNA for DVB-T, DVB-T2 and DVB-C */
+			result = gpio_request_one(dvb->lna_gpio,
+						  GPIOF_OUT_INIT_LOW, NULL);
+			if (result)
+				dev_err(&dev->intf->dev,
+					"gpio request failed %d\n",
+					result);
+			else
+				gpio_free(dvb->lna_gpio);
+
+			result = 0; /* continue even set LNA fails */
+#endif
+			dvb->fe[0]->ops.set_lna = em28xx_pctv_290e_set_lna;
+		}
+
+		break;
+	case EM2884_BOARD_HAUPPAUGE_WINTV_HVR_930C:
+	{
+		struct xc5000_config cfg = {};
+
+		hauppauge_hvr930c_init(dev);
+
+		dvb->fe[0] = dvb_attach(drxk_attach,
+					&hauppauge_930c_drxk,
+					&dev->i2c_adap[dev->def_i2c_bus]);
+		if (!dvb->fe[0]) {
+			result = -EINVAL;
+			goto out_free;
+		}
+		/* FIXME: do we need a pll semaphore? */
+		dvb->fe[0]->sec_priv = dvb;
+		sema_init(&dvb->pll_mutex, 1);
+		dvb->gate_ctrl = dvb->fe[0]->ops.i2c_gate_ctrl;
+		dvb->fe[0]->ops.i2c_gate_ctrl = drxk_gate_ctrl;
+
+		/* Attach xc5000 */
+		cfg.i2c_address  = 0x61;
+		cfg.if_khz = 4000;
+
+		if (dvb->fe[0]->ops.i2c_gate_ctrl)
+			dvb->fe[0]->ops.i2c_gate_ctrl(dvb->fe[0], 1);
+		if (!dvb_attach(xc5000_attach, dvb->fe[0],
+				&dev->i2c_adap[dev->def_i2c_bus], &cfg)) {
+			result = -EINVAL;
+			goto out_free;
+		}
+		if (dvb->fe[0]->ops.i2c_gate_ctrl)
+			dvb->fe[0]->ops.i2c_gate_ctrl(dvb->fe[0], 0);
+
+		break;
+	}
+	case EM2884_BOARD_TERRATEC_H5:
+		terratec_h5_init(dev);
+
+		dvb->fe[0] = dvb_attach(drxk_attach, &terratec_h5_drxk,
+					&dev->i2c_adap[dev->def_i2c_bus]);
+		if (!dvb->fe[0]) {
+			result = -EINVAL;
+			goto out_free;
+		}
+		/* FIXME: do we need a pll semaphore? */
+		dvb->fe[0]->sec_priv = dvb;
+		sema_init(&dvb->pll_mutex, 1);
+		dvb->gate_ctrl = dvb->fe[0]->ops.i2c_gate_ctrl;
+		dvb->fe[0]->ops.i2c_gate_ctrl = drxk_gate_ctrl;
+
+		/* Attach tda18271 to DVB-C frontend */
+		if (dvb->fe[0]->ops.i2c_gate_ctrl)
+			dvb->fe[0]->ops.i2c_gate_ctrl(dvb->fe[0], 1);
+		if (!dvb_attach(tda18271c2dd_attach, dvb->fe[0],
+				&dev->i2c_adap[dev->def_i2c_bus], 0x60)) {
+			result = -EINVAL;
+			goto out_free;
+		}
+		if (dvb->fe[0]->ops.i2c_gate_ctrl)
+			dvb->fe[0]->ops.i2c_gate_ctrl(dvb->fe[0], 0);
+
+		break;
+	case EM2884_BOARD_C3TECH_DIGITAL_DUO:
+		dvb->fe[0] = dvb_attach(mb86a20s_attach,
+					&c3tech_duo_mb86a20s_config,
+					&dev->i2c_adap[dev->def_i2c_bus]);
+		if (dvb->fe[0])
+			dvb_attach(tda18271_attach, dvb->fe[0], 0x60,
+				   &dev->i2c_adap[dev->def_i2c_bus],
+				   &c3tech_duo_tda18271_config);
+		break;
+	case EM28174_BOARD_PCTV_460E:
+		result = em28174_dvb_init_pctv_460e(dev);
+		if (result)
+			goto out_free;
+		break;
+	case EM2874_BOARD_DELOCK_61959:
+	case EM2874_BOARD_MAXMEDIA_UB425_TC:
+		/* attach demodulator */
+		dvb->fe[0] = dvb_attach(drxk_attach, &maxmedia_ub425_tc_drxk,
+					&dev->i2c_adap[dev->def_i2c_bus]);
+
+		if (dvb->fe[0]) {
+			/* disable I2C-gate */
+			dvb->fe[0]->ops.i2c_gate_ctrl = NULL;
+
+			/* attach tuner */
+			if (!dvb_attach(tda18271_attach, dvb->fe[0], 0x60,
+					&dev->i2c_adap[dev->def_i2c_bus],
+					&em28xx_cxd2820r_tda18271_config)) {
+				dvb_frontend_detach(dvb->fe[0]);
+				result = -EINVAL;
+				goto out_free;
+			}
+		}
+		break;
+	case EM2884_BOARD_PCTV_510E:
+	case EM2884_BOARD_PCTV_520E:
+		pctv_520e_init(dev);
+
+		/* attach demodulator */
+		dvb->fe[0] = dvb_attach(drxk_attach, &pctv_520e_drxk,
+					&dev->i2c_adap[dev->def_i2c_bus]);
+
+		if (dvb->fe[0]) {
+			/* attach tuner */
+			if (!dvb_attach(tda18271_attach, dvb->fe[0], 0x60,
+					&dev->i2c_adap[dev->def_i2c_bus],
+					&em28xx_cxd2820r_tda18271_config)) {
+				dvb_frontend_detach(dvb->fe[0]);
+				result = -EINVAL;
+				goto out_free;
+			}
+		}
+		break;
+	case EM2884_BOARD_ELGATO_EYETV_HYBRID_2008:
+	case EM2884_BOARD_CINERGY_HTC_STICK:
+	case EM2884_BOARD_TERRATEC_H6:
+		terratec_htc_stick_init(dev);
+
+		/* attach demodulator */
+		dvb->fe[0] = dvb_attach(drxk_attach, &terratec_htc_stick_drxk,
+					&dev->i2c_adap[dev->def_i2c_bus]);
+		if (!dvb->fe[0]) {
+			result = -EINVAL;
+			goto out_free;
+		}
+
+		/* Attach the demodulator. */
+		if (!dvb_attach(tda18271_attach, dvb->fe[0], 0x60,
+				&dev->i2c_adap[dev->def_i2c_bus],
+				&em28xx_cxd2820r_tda18271_config)) {
+			result = -EINVAL;
+			goto out_free;
+		}
+		break;
+	case EM2884_BOARD_TERRATEC_HTC_USB_XS:
+		terratec_htc_usb_xs_init(dev);
+
+		/* attach demodulator */
+		dvb->fe[0] = dvb_attach(drxk_attach, &terratec_htc_stick_drxk,
+					&dev->i2c_adap[dev->def_i2c_bus]);
+		if (!dvb->fe[0]) {
+			result = -EINVAL;
+			goto out_free;
+		}
+
+		/* Attach the demodulator. */
+		if (!dvb_attach(tda18271_attach, dvb->fe[0], 0x60,
+				&dev->i2c_adap[dev->def_i2c_bus],
+				&em28xx_cxd2820r_tda18271_config)) {
+			result = -EINVAL;
+			goto out_free;
+		}
+		break;
+	case EM2874_BOARD_KWORLD_UB435Q_V2:
+		dvb->fe[0] = dvb_attach(lgdt3305_attach,
+					&em2874_lgdt3305_dev,
+					&dev->i2c_adap[dev->def_i2c_bus]);
+		if (!dvb->fe[0]) {
+			result = -EINVAL;
+			goto out_free;
+		}
+
+		/* Attach the demodulator. */
+		if (!dvb_attach(tda18271_attach, dvb->fe[0], 0x60,
+				&dev->i2c_adap[dev->def_i2c_bus],
+				&kworld_ub435q_v2_config)) {
+			result = -EINVAL;
+			goto out_free;
+		}
+		break;
+	case EM2874_BOARD_KWORLD_UB435Q_V3:
+	{
+		struct i2c_adapter *adapter = &dev->i2c_adap[dev->def_i2c_bus];
+
+		dvb->fe[0] = dvb_attach(lgdt3305_attach,
+					&em2874_lgdt3305_nogate_dev,
+					&dev->i2c_adap[dev->def_i2c_bus]);
+		if (!dvb->fe[0]) {
+			result = -EINVAL;
+			goto out_free;
+		}
+
+		/* attach tuner */
+		kworld_ub435q_v3_config.fe = dvb->fe[0];
+
+		dvb->i2c_client_tuner = dvb_module_probe("tda18212", NULL,
+							 adapter, 0x60,
+							 &kworld_ub435q_v3_config);
+		if (!dvb->i2c_client_tuner) {
+			dvb_frontend_detach(dvb->fe[0]);
+			result = -ENODEV;
+			goto out_free;
+		}
+		break;
+	}
+	case EM2874_BOARD_PCTV_HD_MINI_80E:
+		dvb->fe[0] = dvb_attach(drx39xxj_attach,
+					&dev->i2c_adap[dev->def_i2c_bus]);
+		if (dvb->fe[0]) {
+			dvb->fe[0] = dvb_attach(tda18271_attach, dvb->fe[0],
+						0x60,
+						&dev->i2c_adap[dev->def_i2c_bus],
+						&pinnacle_80e_dvb_config);
+			if (!dvb->fe[0]) {
+				result = -EINVAL;
+				goto out_free;
+			}
+		}
+		break;
+	case EM28178_BOARD_PCTV_461E:
+		result = em28178_dvb_init_pctv_461e(dev);
+		if (result)
+			goto out_free;
+		break;
+	case EM28178_BOARD_PCTV_292E:
+		result = em28178_dvb_init_pctv_292e(dev);
+		if (result)
+			goto out_free;
+		break;
+	case EM28178_BOARD_TERRATEC_T2_STICK_HD:
+		result = em28178_dvb_init_terratec_t2_stick_hd(dev);
+		if (result)
+			goto out_free;
+		break;
+	case EM28178_BOARD_PLEX_PX_BCUD:
+		result = em28178_dvb_init_plex_px_bcud(dev);
+		if (result)
+			goto out_free;
+		break;
+	case EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_DVB:
+		result = em28174_dvb_init_hauppauge_wintv_dualhd_dvb(dev);
+		if (result)
+			goto out_free;
+		break;
+	case EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_01595:
+		result = em28174_dvb_init_hauppauge_wintv_dualhd_01595(dev);
+		if (result)
+			goto out_free;
+		break;
+	default:
+		dev_err(&dev->intf->dev,
+			"The frontend of your DVB/ATSC card isn't supported yet\n");
+		break;
+	}
+	if (!dvb->fe[0]) {
+		dev_err(&dev->intf->dev, "frontend initialization failed\n");
+		result = -EINVAL;
+		goto out_free;
+	}
+	/* define general-purpose callback pointer */
+	dvb->fe[0]->callback = em28xx_tuner_callback;
+	if (dvb->fe[1])
+		dvb->fe[1]->callback = em28xx_tuner_callback;
+
+	/* register everything */
+	result = em28xx_register_dvb(dvb, THIS_MODULE, dev, &dev->intf->dev);
+
+	if (result < 0)
+		goto out_free;
+
+	if (dev->dvb_xfer_bulk) {
+		dvb_alt = 0;
+	} else { /* isoc */
+		dvb_alt = dev->dvb_alt_isoc;
+	}
+
+	udev = interface_to_usbdev(dev->intf);
+	usb_set_interface(udev, dev->ifnum, dvb_alt);
+	dev_info(&dev->intf->dev, "DVB extension successfully initialized\n");
+
+	kref_get(&dev->ref);
+
+ret:
+	em28xx_set_mode(dev, EM28XX_SUSPEND);
+	mutex_unlock(&dev->lock);
+	return result;
+
+out_free:
+	kfree(dvb);
+	dev->dvb = NULL;
+	goto ret;
+}
+
+static inline void prevent_sleep(struct dvb_frontend_ops *ops)
+{
+	ops->set_voltage = NULL;
+	ops->sleep = NULL;
+	ops->tuner_ops.sleep = NULL;
+}
+
+static int em28xx_dvb_fini(struct em28xx *dev)
+{
+	struct em28xx_dvb *dvb;
+
+	if (dev->is_audio_only) {
+		/* Shouldn't initialize IR for this interface */
+		return 0;
+	}
+
+	if (!dev->board.has_dvb) {
+		/* This device does not support the extension */
+		return 0;
+	}
+
+	if (!dev->dvb)
+		return 0;
+
+	dev_info(&dev->intf->dev, "Closing DVB extension\n");
+
+	dvb = dev->dvb;
+
+	em28xx_uninit_usb_xfer(dev, EM28XX_DIGITAL_MODE);
+
+	if (dev->disconnected) {
+		/*
+		 * We cannot tell the device to sleep
+		 * once it has been unplugged.
+		 */
+		if (dvb->fe[0]) {
+			prevent_sleep(&dvb->fe[0]->ops);
+			dvb->fe[0]->exit = DVB_FE_DEVICE_REMOVED;
+		}
+		if (dvb->fe[1]) {
+			prevent_sleep(&dvb->fe[1]->ops);
+			dvb->fe[1]->exit = DVB_FE_DEVICE_REMOVED;
+		}
+	}
+
+	em28xx_unregister_dvb(dvb);
+
+	/* release I2C module bindings */
+	dvb_module_release(dvb->i2c_client_sec);
+	dvb_module_release(dvb->i2c_client_tuner);
+	dvb_module_release(dvb->i2c_client_demod);
+
+	kfree(dvb);
+	dev->dvb = NULL;
+	kref_put(&dev->ref, em28xx_free_device);
+
+	return 0;
+}
+
+static int em28xx_dvb_suspend(struct em28xx *dev)
+{
+	int ret = 0;
+
+	if (dev->is_audio_only)
+		return 0;
+
+	if (!dev->board.has_dvb)
+		return 0;
+
+	dev_info(&dev->intf->dev, "Suspending DVB extension\n");
+	if (dev->dvb) {
+		struct em28xx_dvb *dvb = dev->dvb;
+
+		if (dvb->fe[0]) {
+			ret = dvb_frontend_suspend(dvb->fe[0]);
+			dev_info(&dev->intf->dev, "fe0 suspend %d\n", ret);
+		}
+		if (dvb->fe[1]) {
+			dvb_frontend_suspend(dvb->fe[1]);
+			dev_info(&dev->intf->dev, "fe1 suspend %d\n", ret);
+		}
+	}
+
+	return 0;
+}
+
+static int em28xx_dvb_resume(struct em28xx *dev)
+{
+	int ret = 0;
+
+	if (dev->is_audio_only)
+		return 0;
+
+	if (!dev->board.has_dvb)
+		return 0;
+
+	dev_info(&dev->intf->dev, "Resuming DVB extension\n");
+	if (dev->dvb) {
+		struct em28xx_dvb *dvb = dev->dvb;
+
+		if (dvb->fe[0]) {
+			ret = dvb_frontend_resume(dvb->fe[0]);
+			dev_info(&dev->intf->dev, "fe0 resume %d\n", ret);
+		}
+
+		if (dvb->fe[1]) {
+			ret = dvb_frontend_resume(dvb->fe[1]);
+			dev_info(&dev->intf->dev, "fe1 resume %d\n", ret);
+		}
+	}
+
+	return 0;
+}
+
+static struct em28xx_ops dvb_ops = {
+	.id   = EM28XX_DVB,
+	.name = "Em28xx dvb Extension",
+	.init = em28xx_dvb_init,
+	.fini = em28xx_dvb_fini,
+	.suspend = em28xx_dvb_suspend,
+	.resume = em28xx_dvb_resume,
+};
+
+static int __init em28xx_dvb_register(void)
+{
+	return em28xx_register_extension(&dvb_ops);
+}
+
+static void __exit em28xx_dvb_unregister(void)
+{
+	em28xx_unregister_extension(&dvb_ops);
+}
+
+module_init(em28xx_dvb_register);
+module_exit(em28xx_dvb_unregister);
diff --git a/drivers/media/usb/em28xx/em28xx-i2c.c b/drivers/media/usb/em28xx/em28xx-i2c.c
new file mode 100644
index 0000000..e19d634
--- /dev/null
+++ b/drivers/media/usb/em28xx/em28xx-i2c.c
@@ -0,0 +1,1034 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// em28xx-i2c.c - driver for Empia EM2800/EM2820/2840 USB video capture devices
+//
+// Copyright (C) 2005 Ludovico Cavedon <cavedon@sssup.it>
+//		      Markus Rechberger <mrechberger@gmail.com>
+//		      Mauro Carvalho Chehab <mchehab@kernel.org>
+//		      Sascha Sommer <saschasommer@freenet.de>
+// Copyright (C) 2013 Frank Schäfer <fschaefer.oss@googlemail.com>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include "em28xx.h"
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/usb.h>
+#include <linux/i2c.h>
+#include <linux/jiffies.h>
+
+#include "tuner-xc2028.h"
+#include <media/v4l2-common.h>
+#include <media/tuner.h>
+
+/* ----------------------------------------------------------- */
+
+static unsigned int i2c_scan;
+module_param(i2c_scan, int, 0444);
+MODULE_PARM_DESC(i2c_scan, "scan i2c bus at insmod time");
+
+static unsigned int i2c_debug;
+module_param(i2c_debug, int, 0644);
+MODULE_PARM_DESC(i2c_debug, "i2c debug message level (1: normal debug, 2: show I2C transfers)");
+
+#define dprintk(level, fmt, arg...) do {				\
+	if (i2c_debug > level)						\
+		dev_printk(KERN_DEBUG, &dev->intf->dev,			\
+			   "i2c: %s: " fmt, __func__, ## arg);		\
+} while (0)
+
+/*
+ * Time in msecs to wait for i2c xfers to finish.
+ * 35ms is the maximum time a SMBUS device could wait when
+ * clock stretching is used. As the transfer itself will take
+ * some time to happen, set it to 35 ms.
+ *
+ * Ok, I2C doesn't specify any limit. So, eventually, we may need
+ * to increase this timeout.
+ */
+#define EM28XX_I2C_XFER_TIMEOUT         35 /* ms */
+
+static int em28xx_i2c_timeout(struct em28xx *dev)
+{
+	int time = EM28XX_I2C_XFER_TIMEOUT;
+
+	switch (dev->i2c_speed & 0x03) {
+	case EM28XX_I2C_FREQ_25_KHZ:
+		time += 4;		/* Assume 4 ms for transfers */
+		break;
+	case EM28XX_I2C_FREQ_100_KHZ:
+	case EM28XX_I2C_FREQ_400_KHZ:
+		time += 1;		/* Assume 1 ms for transfers */
+		break;
+	default: /* EM28XX_I2C_FREQ_1_5_MHZ */
+		break;
+	}
+
+	return msecs_to_jiffies(time);
+}
+
+/*
+ * em2800_i2c_send_bytes()
+ * send up to 4 bytes to the em2800 i2c device
+ */
+static int em2800_i2c_send_bytes(struct em28xx *dev, u8 addr, u8 *buf, u16 len)
+{
+	unsigned long timeout = jiffies + em28xx_i2c_timeout(dev);
+	int ret;
+	u8 b2[6];
+
+	if (len < 1 || len > 4)
+		return -EOPNOTSUPP;
+
+	b2[5] = 0x80 + len - 1;
+	b2[4] = addr;
+	b2[3] = buf[0];
+	if (len > 1)
+		b2[2] = buf[1];
+	if (len > 2)
+		b2[1] = buf[2];
+	if (len > 3)
+		b2[0] = buf[3];
+
+	/* trigger write */
+	ret = dev->em28xx_write_regs(dev, 4 - len, &b2[4 - len], 2 + len);
+	if (ret != 2 + len) {
+		dev_warn(&dev->intf->dev,
+			 "failed to trigger write to i2c address 0x%x (error=%i)\n",
+			    addr, ret);
+		return (ret < 0) ? ret : -EIO;
+	}
+	/* wait for completion */
+	while (time_is_after_jiffies(timeout)) {
+		ret = dev->em28xx_read_reg(dev, 0x05);
+		if (ret == 0x80 + len - 1)
+			return len;
+		if (ret == 0x94 + len - 1) {
+			dprintk(1, "R05 returned 0x%02x: I2C ACK error\n", ret);
+			return -ENXIO;
+		}
+		if (ret < 0) {
+			dev_warn(&dev->intf->dev,
+				 "failed to get i2c transfer status from bridge register (error=%i)\n",
+				ret);
+			return ret;
+		}
+		usleep_range(5000, 6000);
+	}
+	dprintk(0, "write to i2c device at 0x%x timed out\n", addr);
+	return -ETIMEDOUT;
+}
+
+/*
+ * em2800_i2c_recv_bytes()
+ * read up to 4 bytes from the em2800 i2c device
+ */
+static int em2800_i2c_recv_bytes(struct em28xx *dev, u8 addr, u8 *buf, u16 len)
+{
+	unsigned long timeout = jiffies + em28xx_i2c_timeout(dev);
+	u8 buf2[4];
+	int ret;
+	int i;
+
+	if (len < 1 || len > 4)
+		return -EOPNOTSUPP;
+
+	/* trigger read */
+	buf2[1] = 0x84 + len - 1;
+	buf2[0] = addr;
+	ret = dev->em28xx_write_regs(dev, 0x04, buf2, 2);
+	if (ret != 2) {
+		dev_warn(&dev->intf->dev,
+			 "failed to trigger read from i2c address 0x%x (error=%i)\n",
+			 addr, ret);
+		return (ret < 0) ? ret : -EIO;
+	}
+
+	/* wait for completion */
+	while (time_is_after_jiffies(timeout)) {
+		ret = dev->em28xx_read_reg(dev, 0x05);
+		if (ret == 0x84 + len - 1)
+			break;
+		if (ret == 0x94 + len - 1) {
+			dprintk(1, "R05 returned 0x%02x: I2C ACK error\n",
+				ret);
+			return -ENXIO;
+		}
+		if (ret < 0) {
+			dev_warn(&dev->intf->dev,
+				 "failed to get i2c transfer status from bridge register (error=%i)\n",
+				 ret);
+			return ret;
+		}
+		usleep_range(5000, 6000);
+	}
+	if (ret != 0x84 + len - 1)
+		dprintk(0, "read from i2c device at 0x%x timed out\n", addr);
+
+	/* get the received message */
+	ret = dev->em28xx_read_reg_req_len(dev, 0x00, 4 - len, buf2, len);
+	if (ret != len) {
+		dev_warn(&dev->intf->dev,
+			 "reading from i2c device at 0x%x failed: couldn't get the received message from the bridge (error=%i)\n",
+			 addr, ret);
+		return (ret < 0) ? ret : -EIO;
+	}
+	for (i = 0; i < len; i++)
+		buf[i] = buf2[len - 1 - i];
+
+	return ret;
+}
+
+/*
+ * em2800_i2c_check_for_device()
+ * check if there is an i2c device at the supplied address
+ */
+static int em2800_i2c_check_for_device(struct em28xx *dev, u8 addr)
+{
+	u8 buf;
+	int ret;
+
+	ret = em2800_i2c_recv_bytes(dev, addr, &buf, 1);
+	if (ret == 1)
+		return 0;
+	return (ret < 0) ? ret : -EIO;
+}
+
+/*
+ * em28xx_i2c_send_bytes()
+ */
+static int em28xx_i2c_send_bytes(struct em28xx *dev, u16 addr, u8 *buf,
+				 u16 len, int stop)
+{
+	unsigned long timeout = jiffies + em28xx_i2c_timeout(dev);
+	int ret;
+
+	if (len < 1 || len > 64)
+		return -EOPNOTSUPP;
+	/*
+	 * NOTE: limited by the USB ctrl message constraints
+	 * Zero length reads always succeed, even if no device is connected
+	 */
+
+	/* Write to i2c device */
+	ret = dev->em28xx_write_regs_req(dev, stop ? 2 : 3, addr, buf, len);
+	if (ret != len) {
+		if (ret < 0) {
+			dev_warn(&dev->intf->dev,
+				 "writing to i2c device at 0x%x failed (error=%i)\n",
+				 addr, ret);
+			return ret;
+		}
+		dev_warn(&dev->intf->dev,
+			 "%i bytes write to i2c device at 0x%x requested, but %i bytes written\n",
+				len, addr, ret);
+		return -EIO;
+	}
+
+	/* wait for completion */
+	while (time_is_after_jiffies(timeout)) {
+		ret = dev->em28xx_read_reg(dev, 0x05);
+		if (ret == 0) /* success */
+			return len;
+		if (ret == 0x10) {
+			dprintk(1, "I2C ACK error on writing to addr 0x%02x\n",
+				addr);
+			return -ENXIO;
+		}
+		if (ret < 0) {
+			dev_warn(&dev->intf->dev,
+				 "failed to get i2c transfer status from bridge register (error=%i)\n",
+				 ret);
+			return ret;
+		}
+		usleep_range(5000, 6000);
+		/*
+		 * NOTE: do we really have to wait for success ?
+		 * Never seen anything else than 0x00 or 0x10
+		 * (even with high payload) ...
+		 */
+	}
+
+	if (ret == 0x02 || ret == 0x04) {
+		/* NOTE: these errors seem to be related to clock stretching */
+		dprintk(0,
+			"write to i2c device at 0x%x timed out (status=%i)\n",
+			addr, ret);
+		return -ETIMEDOUT;
+	}
+
+	dev_warn(&dev->intf->dev,
+		 "write to i2c device at 0x%x failed with unknown error (status=%i)\n",
+		 addr, ret);
+	return -EIO;
+}
+
+/*
+ * em28xx_i2c_recv_bytes()
+ * read a byte from the i2c device
+ */
+static int em28xx_i2c_recv_bytes(struct em28xx *dev, u16 addr, u8 *buf, u16 len)
+{
+	int ret;
+
+	if (len < 1 || len > 64)
+		return -EOPNOTSUPP;
+	/*
+	 * NOTE: limited by the USB ctrl message constraints
+	 * Zero length reads always succeed, even if no device is connected
+	 */
+
+	/* Read data from i2c device */
+	ret = dev->em28xx_read_reg_req_len(dev, 2, addr, buf, len);
+	if (ret < 0) {
+		dev_warn(&dev->intf->dev,
+			 "reading from i2c device at 0x%x failed (error=%i)\n",
+			 addr, ret);
+		return ret;
+	}
+	/*
+	 * NOTE: some devices with two i2c busses have the bad habit to return 0
+	 * bytes if we are on bus B AND there was no write attempt to the
+	 * specified slave address before AND no device is present at the
+	 * requested slave address.
+	 * Anyway, the next check will fail with -ENXIO in this case, so avoid
+	 * spamming the system log on device probing and do nothing here.
+	 */
+
+	/* Check success of the i2c operation */
+	ret = dev->em28xx_read_reg(dev, 0x05);
+	if (ret == 0) /* success */
+		return len;
+	if (ret < 0) {
+		dev_warn(&dev->intf->dev,
+			 "failed to get i2c transfer status from bridge register (error=%i)\n",
+			 ret);
+		return ret;
+	}
+	if (ret == 0x10) {
+		dprintk(1, "I2C ACK error on writing to addr 0x%02x\n",
+			addr);
+		return -ENXIO;
+	}
+
+	if (ret == 0x02 || ret == 0x04) {
+		/* NOTE: these errors seem to be related to clock stretching */
+		dprintk(0,
+			"write to i2c device at 0x%x timed out (status=%i)\n",
+			addr, ret);
+		return -ETIMEDOUT;
+	}
+
+	dev_warn(&dev->intf->dev,
+		 "write to i2c device at 0x%x failed with unknown error (status=%i)\n",
+		 addr, ret);
+	return -EIO;
+}
+
+/*
+ * em28xx_i2c_check_for_device()
+ * check if there is a i2c_device at the supplied address
+ */
+static int em28xx_i2c_check_for_device(struct em28xx *dev, u16 addr)
+{
+	int ret;
+	u8 buf;
+
+	ret = em28xx_i2c_recv_bytes(dev, addr, &buf, 1);
+	if (ret == 1)
+		return 0;
+	return (ret < 0) ? ret : -EIO;
+}
+
+/*
+ * em25xx_bus_B_send_bytes
+ * write bytes to the i2c device
+ */
+static int em25xx_bus_B_send_bytes(struct em28xx *dev, u16 addr, u8 *buf,
+				   u16 len)
+{
+	int ret;
+
+	if (len < 1 || len > 64)
+		return -EOPNOTSUPP;
+	/*
+	 * NOTE: limited by the USB ctrl message constraints
+	 * Zero length reads always succeed, even if no device is connected
+	 */
+
+	/* Set register and write value */
+	ret = dev->em28xx_write_regs_req(dev, 0x06, addr, buf, len);
+	if (ret != len) {
+		if (ret < 0) {
+			dev_warn(&dev->intf->dev,
+				 "writing to i2c device at 0x%x failed (error=%i)\n",
+				 addr, ret);
+			return ret;
+		}
+
+		dev_warn(&dev->intf->dev,
+			 "%i bytes write to i2c device at 0x%x requested, but %i bytes written\n",
+			 len, addr, ret);
+		return -EIO;
+	}
+	/* Check success */
+	ret = dev->em28xx_read_reg_req(dev, 0x08, 0x0000);
+	/*
+	 * NOTE: the only error we've seen so far is
+	 * 0x01 when the slave device is not present
+	 */
+	if (!ret)
+		return len;
+
+	if (ret > 0) {
+		dprintk(1, "Bus B R08 returned 0x%02x: I2C ACK error\n", ret);
+		return -ENXIO;
+	}
+
+	return ret;
+	/*
+	 * NOTE: With chip types (other chip IDs) which actually don't support
+	 * this operation, it seems to succeed ALWAYS ! (even if there is no
+	 * slave device or even no second i2c bus provided)
+	 */
+}
+
+/*
+ * em25xx_bus_B_recv_bytes
+ * read bytes from the i2c device
+ */
+static int em25xx_bus_B_recv_bytes(struct em28xx *dev, u16 addr, u8 *buf,
+				   u16 len)
+{
+	int ret;
+
+	if (len < 1 || len > 64)
+		return -EOPNOTSUPP;
+	/*
+	 * NOTE: limited by the USB ctrl message constraints
+	 * Zero length reads always succeed, even if no device is connected
+	 */
+
+	/* Read value */
+	ret = dev->em28xx_read_reg_req_len(dev, 0x06, addr, buf, len);
+	if (ret < 0) {
+		dev_warn(&dev->intf->dev,
+			 "reading from i2c device at 0x%x failed (error=%i)\n",
+			 addr, ret);
+		return ret;
+	}
+	/*
+	 * NOTE: some devices with two i2c busses have the bad habit to return 0
+	 * bytes if we are on bus B AND there was no write attempt to the
+	 * specified slave address before AND no device is present at the
+	 * requested slave address.
+	 * Anyway, the next check will fail with -ENXIO in this case, so avoid
+	 * spamming the system log on device probing and do nothing here.
+	 */
+
+	/* Check success */
+	ret = dev->em28xx_read_reg_req(dev, 0x08, 0x0000);
+	/*
+	 * NOTE: the only error we've seen so far is
+	 * 0x01 when the slave device is not present
+	 */
+	if (!ret)
+		return len;
+
+	if (ret > 0) {
+		dprintk(1, "Bus B R08 returned 0x%02x: I2C ACK error\n", ret);
+		return -ENXIO;
+	}
+
+	return ret;
+	/*
+	 * NOTE: With chip types (other chip IDs) which actually don't support
+	 * this operation, it seems to succeed ALWAYS ! (even if there is no
+	 * slave device or even no second i2c bus provided)
+	 */
+}
+
+/*
+ * em25xx_bus_B_check_for_device()
+ * check if there is a i2c device at the supplied address
+ */
+static int em25xx_bus_B_check_for_device(struct em28xx *dev, u16 addr)
+{
+	u8 buf;
+	int ret;
+
+	ret = em25xx_bus_B_recv_bytes(dev, addr, &buf, 1);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+	/*
+	 * NOTE: With chips which do not support this operation,
+	 * it seems to succeed ALWAYS ! (even if no device connected)
+	 */
+}
+
+static inline int i2c_check_for_device(struct em28xx_i2c_bus *i2c_bus, u16 addr)
+{
+	struct em28xx *dev = i2c_bus->dev;
+	int rc = -EOPNOTSUPP;
+
+	if (i2c_bus->algo_type == EM28XX_I2C_ALGO_EM28XX)
+		rc = em28xx_i2c_check_for_device(dev, addr);
+	else if (i2c_bus->algo_type == EM28XX_I2C_ALGO_EM2800)
+		rc = em2800_i2c_check_for_device(dev, addr);
+	else if (i2c_bus->algo_type == EM28XX_I2C_ALGO_EM25XX_BUS_B)
+		rc = em25xx_bus_B_check_for_device(dev, addr);
+	return rc;
+}
+
+static inline int i2c_recv_bytes(struct em28xx_i2c_bus *i2c_bus,
+				 struct i2c_msg msg)
+{
+	struct em28xx *dev = i2c_bus->dev;
+	u16 addr = msg.addr << 1;
+	int rc = -EOPNOTSUPP;
+
+	if (i2c_bus->algo_type == EM28XX_I2C_ALGO_EM28XX)
+		rc = em28xx_i2c_recv_bytes(dev, addr, msg.buf, msg.len);
+	else if (i2c_bus->algo_type == EM28XX_I2C_ALGO_EM2800)
+		rc = em2800_i2c_recv_bytes(dev, addr, msg.buf, msg.len);
+	else if (i2c_bus->algo_type == EM28XX_I2C_ALGO_EM25XX_BUS_B)
+		rc = em25xx_bus_B_recv_bytes(dev, addr, msg.buf, msg.len);
+	return rc;
+}
+
+static inline int i2c_send_bytes(struct em28xx_i2c_bus *i2c_bus,
+				 struct i2c_msg msg, int stop)
+{
+	struct em28xx *dev = i2c_bus->dev;
+	u16 addr = msg.addr << 1;
+	int rc = -EOPNOTSUPP;
+
+	if (i2c_bus->algo_type == EM28XX_I2C_ALGO_EM28XX)
+		rc = em28xx_i2c_send_bytes(dev, addr, msg.buf, msg.len, stop);
+	else if (i2c_bus->algo_type == EM28XX_I2C_ALGO_EM2800)
+		rc = em2800_i2c_send_bytes(dev, addr, msg.buf, msg.len);
+	else if (i2c_bus->algo_type == EM28XX_I2C_ALGO_EM25XX_BUS_B)
+		rc = em25xx_bus_B_send_bytes(dev, addr, msg.buf, msg.len);
+	return rc;
+}
+
+/*
+ * em28xx_i2c_xfer()
+ * the main i2c transfer function
+ */
+static int em28xx_i2c_xfer(struct i2c_adapter *i2c_adap,
+			   struct i2c_msg msgs[], int num)
+{
+	struct em28xx_i2c_bus *i2c_bus = i2c_adap->algo_data;
+	struct em28xx *dev = i2c_bus->dev;
+	unsigned int bus = i2c_bus->bus;
+	int addr, rc, i;
+	u8 reg;
+
+	/*
+	 * prevent i2c xfer attempts after device is disconnected
+	 * some fe's try to do i2c writes/reads from their release
+	 * interfaces when called in disconnect path
+	 */
+	if (dev->disconnected)
+		return -ENODEV;
+
+	if (!rt_mutex_trylock(&dev->i2c_bus_lock))
+		return -EAGAIN;
+
+	/* Switch I2C bus if needed */
+	if (bus != dev->cur_i2c_bus &&
+	    i2c_bus->algo_type == EM28XX_I2C_ALGO_EM28XX) {
+		if (bus == 1)
+			reg = EM2874_I2C_SECONDARY_BUS_SELECT;
+		else
+			reg = 0;
+		em28xx_write_reg_bits(dev, EM28XX_R06_I2C_CLK, reg,
+				      EM2874_I2C_SECONDARY_BUS_SELECT);
+		dev->cur_i2c_bus = bus;
+	}
+
+	for (i = 0; i < num; i++) {
+		addr = msgs[i].addr << 1;
+		if (!msgs[i].len) {
+			/*
+			 * no len: check only for device presence
+			 * This code is only called during device probe.
+			 */
+			rc = i2c_check_for_device(i2c_bus, addr);
+
+			if (rc == -ENXIO)
+				rc = -ENODEV;
+		} else if (msgs[i].flags & I2C_M_RD) {
+			/* read bytes */
+			rc = i2c_recv_bytes(i2c_bus, msgs[i]);
+		} else {
+			/* write bytes */
+			rc = i2c_send_bytes(i2c_bus, msgs[i], i == num - 1);
+		}
+
+		if (rc < 0)
+			goto error;
+
+		dprintk(2, "%s %s addr=%02x len=%d: %*ph\n",
+			(msgs[i].flags & I2C_M_RD) ? "read" : "write",
+			i == num - 1 ? "stop" : "nonstop",
+			addr, msgs[i].len,
+			msgs[i].len, msgs[i].buf);
+	}
+
+	rt_mutex_unlock(&dev->i2c_bus_lock);
+	return num;
+
+error:
+	dprintk(2, "%s %s addr=%02x len=%d: %sERROR: %i\n",
+		(msgs[i].flags & I2C_M_RD) ? "read" : "write",
+		i == num - 1 ? "stop" : "nonstop",
+		addr, msgs[i].len,
+		(rc == -ENODEV) ? "no device " : "",
+		rc);
+
+	rt_mutex_unlock(&dev->i2c_bus_lock);
+	return rc;
+}
+
+/*
+ * based on linux/sunrpc/svcauth.h and linux/hash.h
+ * The original hash function returns a different value, if arch is x86_64
+ * or i386.
+ */
+static inline unsigned long em28xx_hash_mem(char *buf, int length, int bits)
+{
+	unsigned long hash = 0;
+	unsigned long l = 0;
+	int len = 0;
+	unsigned char c;
+
+	do {
+		if (len == length) {
+			c = (char)len;
+			len = -1;
+		} else {
+			c = *buf++;
+		}
+		l = (l << 8) | c;
+		len++;
+		if ((len & (32 / 8 - 1)) == 0)
+			hash = ((hash ^ l) * 0x9e370001UL);
+	} while (len);
+
+	return (hash >> (32 - bits)) & 0xffffffffUL;
+}
+
+/*
+ * Helper function to read data blocks from i2c clients with 8 or 16 bit
+ * address width, 8 bit register width and auto incrementation been activated
+ */
+static int em28xx_i2c_read_block(struct em28xx *dev, unsigned int bus, u16 addr,
+				 bool addr_w16, u16 len, u8 *data)
+{
+	int remain = len, rsize, rsize_max, ret;
+	u8 buf[2];
+
+	/* Sanity check */
+	if (addr + remain > (addr_w16 * 0xff00 + 0xff + 1))
+		return -EINVAL;
+	/* Select address */
+	buf[0] = addr >> 8;
+	buf[1] = addr & 0xff;
+	ret = i2c_master_send(&dev->i2c_client[bus],
+			      buf + !addr_w16, 1 + addr_w16);
+	if (ret < 0)
+		return ret;
+	/* Read data */
+	if (dev->board.is_em2800)
+		rsize_max = 4;
+	else
+		rsize_max = 64;
+	while (remain > 0) {
+		if (remain > rsize_max)
+			rsize = rsize_max;
+		else
+			rsize = remain;
+
+		ret = i2c_master_recv(&dev->i2c_client[bus], data, rsize);
+		if (ret < 0)
+			return ret;
+
+		remain -= rsize;
+		data += rsize;
+	}
+
+	return len;
+}
+
+static int em28xx_i2c_eeprom(struct em28xx *dev, unsigned int bus,
+			     u8 **eedata, u16 *eedata_len)
+{
+	const u16 len = 256;
+	/*
+	 * FIXME common length/size for bytes to read, to display, hash
+	 * calculation and returned device dataset. Simplifies the code a lot,
+	 * but we might have to deal with multiple sizes in the future !
+	 */
+	int err;
+	struct em28xx_eeprom *dev_config;
+	u8 buf, *data;
+
+	*eedata = NULL;
+	*eedata_len = 0;
+
+	/* EEPROM is always on i2c bus 0 on all known devices. */
+
+	dev->i2c_client[bus].addr = 0xa0 >> 1;
+
+	/* Check if board has eeprom */
+	err = i2c_master_recv(&dev->i2c_client[bus], &buf, 0);
+	if (err < 0) {
+		dev_info(&dev->intf->dev, "board has no eeprom\n");
+		return -ENODEV;
+	}
+
+	data = kzalloc(len, GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	/* Read EEPROM content */
+	err = em28xx_i2c_read_block(dev, bus, 0x0000,
+				    dev->eeprom_addrwidth_16bit,
+				    len, data);
+	if (err != len) {
+		dev_err(&dev->intf->dev,
+			"failed to read eeprom (err=%d)\n", err);
+		goto error;
+	}
+
+	if (i2c_debug) {
+		/* Display eeprom content */
+		print_hex_dump(KERN_DEBUG, "em28xx eeprom ", DUMP_PREFIX_OFFSET,
+			       16, 1, data, len, true);
+
+		if (dev->eeprom_addrwidth_16bit)
+			dev_info(&dev->intf->dev,
+				 "eeprom %06x: ... (skipped)\n", 256);
+	}
+
+	if (dev->eeprom_addrwidth_16bit &&
+	    data[0] == 0x26 && data[3] == 0x00) {
+		/* new eeprom format; size 4-64kb */
+		u16 mc_start;
+		u16 hwconf_offset;
+
+		dev->hash = em28xx_hash_mem(data, len, 32);
+		mc_start = (data[1] << 8) + 4;	/* usually 0x0004 */
+
+		dev_info(&dev->intf->dev,
+			 "EEPROM ID = %4ph, EEPROM hash = 0x%08lx\n",
+			 data, dev->hash);
+		dev_info(&dev->intf->dev,
+			 "EEPROM info:\n");
+		dev_info(&dev->intf->dev,
+			 "\tmicrocode start address = 0x%04x, boot configuration = 0x%02x\n",
+			 mc_start, data[2]);
+		/*
+		 * boot configuration (address 0x0002):
+		 * [0]   microcode download speed: 1 = 400 kHz; 0 = 100 kHz
+		 * [1]   always selects 12 kb RAM
+		 * [2]   USB device speed: 1 = force Full Speed; 0 = auto detect
+		 * [4]   1 = force fast mode and no suspend for device testing
+		 * [5:7] USB PHY tuning registers; determined by device
+		 *       characterization
+		 */
+
+		/*
+		 * Read hardware config dataset offset from address
+		 * (microcode start + 46)
+		 */
+		err = em28xx_i2c_read_block(dev, bus, mc_start + 46, 1, 2,
+					    data);
+		if (err != 2) {
+			dev_err(&dev->intf->dev,
+				"failed to read hardware configuration data from eeprom (err=%d)\n",
+				err);
+			goto error;
+		}
+
+		/* Calculate hardware config dataset start address */
+		hwconf_offset = mc_start + data[0] + (data[1] << 8);
+
+		/* Read hardware config dataset */
+		/*
+		 * NOTE: the microcode copy can be multiple pages long, but
+		 * we assume the hardware config dataset is the same as in
+		 * the old eeprom and not longer than 256 bytes.
+		 * tveeprom is currently also limited to 256 bytes.
+		 */
+		err = em28xx_i2c_read_block(dev, bus, hwconf_offset, 1, len,
+					    data);
+		if (err != len) {
+			dev_err(&dev->intf->dev,
+				"failed to read hardware configuration data from eeprom (err=%d)\n",
+				err);
+			goto error;
+		}
+
+		/* Verify hardware config dataset */
+		/* NOTE: not all devices provide this type of dataset */
+		if (data[0] != 0x1a || data[1] != 0xeb ||
+		    data[2] != 0x67 || data[3] != 0x95) {
+			dev_info(&dev->intf->dev,
+				 "\tno hardware configuration dataset found in eeprom\n");
+			kfree(data);
+			return 0;
+		}
+
+		/*
+		 * TODO: decrypt eeprom data for camera bridges
+		 * (em25xx, em276x+)
+		 */
+
+	} else if (!dev->eeprom_addrwidth_16bit &&
+		   data[0] == 0x1a && data[1] == 0xeb &&
+		   data[2] == 0x67 && data[3] == 0x95) {
+		dev->hash = em28xx_hash_mem(data, len, 32);
+		dev_info(&dev->intf->dev,
+			 "EEPROM ID = %4ph, EEPROM hash = 0x%08lx\n",
+			 data, dev->hash);
+		dev_info(&dev->intf->dev,
+			 "EEPROM info:\n");
+	} else {
+		dev_info(&dev->intf->dev,
+			 "unknown eeprom format or eeprom corrupted !\n");
+		err = -ENODEV;
+		goto error;
+	}
+
+	*eedata = data;
+	*eedata_len = len;
+	dev_config = (void *)*eedata;
+
+	switch (le16_to_cpu(dev_config->chip_conf) >> 4 & 0x3) {
+	case 0:
+		dev_info(&dev->intf->dev, "\tNo audio on board.\n");
+		break;
+	case 1:
+		dev_info(&dev->intf->dev, "\tAC97 audio (5 sample rates)\n");
+		break;
+	case 2:
+		if (dev->chip_id < CHIP_ID_EM2860)
+			dev_info(&dev->intf->dev,
+				 "\tI2S audio, sample rate=32k\n");
+		else
+			dev_info(&dev->intf->dev,
+				 "\tI2S audio, 3 sample rates\n");
+		break;
+	case 3:
+		if (dev->chip_id < CHIP_ID_EM2860)
+			dev_info(&dev->intf->dev,
+				 "\tI2S audio, 3 sample rates\n");
+		else
+			dev_info(&dev->intf->dev,
+				 "\tI2S audio, 5 sample rates\n");
+		break;
+	}
+
+	if (le16_to_cpu(dev_config->chip_conf) & 1 << 3)
+		dev_info(&dev->intf->dev, "\tUSB Remote wakeup capable\n");
+
+	if (le16_to_cpu(dev_config->chip_conf) & 1 << 2)
+		dev_info(&dev->intf->dev, "\tUSB Self power capable\n");
+
+	switch (le16_to_cpu(dev_config->chip_conf) & 0x3) {
+	case 0:
+		dev_info(&dev->intf->dev, "\t500mA max power\n");
+		break;
+	case 1:
+		dev_info(&dev->intf->dev, "\t400mA max power\n");
+		break;
+	case 2:
+		dev_info(&dev->intf->dev, "\t300mA max power\n");
+		break;
+	case 3:
+		dev_info(&dev->intf->dev, "\t200mA max power\n");
+		break;
+	}
+	dev_info(&dev->intf->dev,
+		 "\tTable at offset 0x%02x, strings=0x%04x, 0x%04x, 0x%04x\n",
+		 dev_config->string_idx_table,
+		 le16_to_cpu(dev_config->string1),
+		 le16_to_cpu(dev_config->string2),
+		 le16_to_cpu(dev_config->string3));
+
+	return 0;
+
+error:
+	kfree(data);
+	return err;
+}
+
+/* ----------------------------------------------------------- */
+
+/*
+ * functionality()
+ */
+static u32 functionality(struct i2c_adapter *i2c_adap)
+{
+	struct em28xx_i2c_bus *i2c_bus = i2c_adap->algo_data;
+
+	if (i2c_bus->algo_type == EM28XX_I2C_ALGO_EM28XX ||
+	    i2c_bus->algo_type == EM28XX_I2C_ALGO_EM25XX_BUS_B) {
+		return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+	} else if (i2c_bus->algo_type == EM28XX_I2C_ALGO_EM2800)  {
+		return (I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL) &
+			~I2C_FUNC_SMBUS_WRITE_BLOCK_DATA;
+	}
+
+	WARN(1, "Unknown i2c bus algorithm.\n");
+	return 0;
+}
+
+static const struct i2c_algorithm em28xx_algo = {
+	.master_xfer   = em28xx_i2c_xfer,
+	.functionality = functionality,
+};
+
+static const struct i2c_adapter em28xx_adap_template = {
+	.owner = THIS_MODULE,
+	.name = "em28xx",
+	.algo = &em28xx_algo,
+};
+
+static const struct i2c_client em28xx_client_template = {
+	.name = "em28xx internal",
+};
+
+/* ----------------------------------------------------------- */
+
+/*
+ * i2c_devs
+ * incomplete list of known devices
+ */
+static char *i2c_devs[128] = {
+	[0x1c >> 1] = "lgdt330x",
+	[0x3e >> 1] = "remote IR sensor",
+	[0x4a >> 1] = "saa7113h",
+	[0x52 >> 1] = "drxk",
+	[0x60 >> 1] = "remote IR sensor",
+	[0x8e >> 1] = "remote IR sensor",
+	[0x86 >> 1] = "tda9887",
+	[0x80 >> 1] = "msp34xx",
+	[0x88 >> 1] = "msp34xx",
+	[0xa0 >> 1] = "eeprom",
+	[0xb0 >> 1] = "tda9874",
+	[0xb8 >> 1] = "tvp5150a",
+	[0xba >> 1] = "webcam sensor or tvp5150a",
+	[0xc0 >> 1] = "tuner (analog)",
+	[0xc2 >> 1] = "tuner (analog)",
+	[0xc4 >> 1] = "tuner (analog)",
+	[0xc6 >> 1] = "tuner (analog)",
+};
+
+/*
+ * do_i2c_scan()
+ * check i2c address range for devices
+ */
+void em28xx_do_i2c_scan(struct em28xx *dev, unsigned int bus)
+{
+	u8 i2c_devicelist[128];
+	unsigned char buf;
+	int i, rc;
+
+	memset(i2c_devicelist, 0, ARRAY_SIZE(i2c_devicelist));
+
+	for (i = 0; i < ARRAY_SIZE(i2c_devs); i++) {
+		dev->i2c_client[bus].addr = i;
+		rc = i2c_master_recv(&dev->i2c_client[bus], &buf, 0);
+		if (rc < 0)
+			continue;
+		i2c_devicelist[i] = i;
+		dev_info(&dev->intf->dev,
+			 "found i2c device @ 0x%x on bus %d [%s]\n",
+			 i << 1, bus, i2c_devs[i] ? i2c_devs[i] : "???");
+	}
+
+	if (bus == dev->def_i2c_bus)
+		dev->i2c_hash = em28xx_hash_mem(i2c_devicelist,
+						ARRAY_SIZE(i2c_devicelist), 32);
+}
+
+/*
+ * em28xx_i2c_register()
+ * register i2c bus
+ */
+int em28xx_i2c_register(struct em28xx *dev, unsigned int bus,
+			enum em28xx_i2c_algo_type algo_type)
+{
+	int retval;
+
+	if (WARN_ON(!dev->em28xx_write_regs || !dev->em28xx_read_reg ||
+		    !dev->em28xx_write_regs_req || !dev->em28xx_read_reg_req))
+		return -ENODEV;
+
+	if (bus >= NUM_I2C_BUSES)
+		return -ENODEV;
+
+	dev->i2c_adap[bus] = em28xx_adap_template;
+	dev->i2c_adap[bus].dev.parent = &dev->intf->dev;
+	strcpy(dev->i2c_adap[bus].name, dev_name(&dev->intf->dev));
+
+	dev->i2c_bus[bus].bus = bus;
+	dev->i2c_bus[bus].algo_type = algo_type;
+	dev->i2c_bus[bus].dev = dev;
+	dev->i2c_adap[bus].algo_data = &dev->i2c_bus[bus];
+
+	retval = i2c_add_adapter(&dev->i2c_adap[bus]);
+	if (retval < 0) {
+		dev_err(&dev->intf->dev,
+			"%s: i2c_add_adapter failed! retval [%d]\n",
+			__func__, retval);
+		return retval;
+	}
+
+	dev->i2c_client[bus] = em28xx_client_template;
+	dev->i2c_client[bus].adapter = &dev->i2c_adap[bus];
+
+	/* Up to now, all eeproms are at bus 0 */
+	if (!bus) {
+		retval = em28xx_i2c_eeprom(dev, bus,
+					   &dev->eedata, &dev->eedata_len);
+		if (retval < 0 && retval != -ENODEV) {
+			dev_err(&dev->intf->dev,
+				"%s: em28xx_i2_eeprom failed! retval [%d]\n",
+				__func__, retval);
+		}
+	}
+
+	if (i2c_scan)
+		em28xx_do_i2c_scan(dev, bus);
+
+	return 0;
+}
+
+/*
+ * em28xx_i2c_unregister()
+ * unregister i2c_bus
+ */
+int em28xx_i2c_unregister(struct em28xx *dev, unsigned int bus)
+{
+	if (bus >= NUM_I2C_BUSES)
+		return -ENODEV;
+
+	i2c_del_adapter(&dev->i2c_adap[bus]);
+	return 0;
+}
diff --git a/drivers/media/usb/em28xx/em28xx-input.c b/drivers/media/usb/em28xx/em28xx-input.c
new file mode 100644
index 0000000..f84a120
--- /dev/null
+++ b/drivers/media/usb/em28xx/em28xx-input.c
@@ -0,0 +1,963 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// handle em28xx IR remotes via linux kernel input layer.
+//
+// Copyright (C) 2005 Ludovico Cavedon <cavedon@sssup.it>
+//		      Markus Rechberger <mrechberger@gmail.com>
+//		      Mauro Carvalho Chehab <mchehab@kernel.org>
+//		      Sascha Sommer <saschasommer@freenet.de>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include "em28xx.h"
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/usb.h>
+#include <linux/slab.h>
+#include <linux/bitrev.h>
+
+#define EM28XX_SNAPSHOT_KEY				KEY_CAMERA
+#define EM28XX_BUTTONS_DEBOUNCED_QUERY_INTERVAL		500 /* [ms] */
+#define EM28XX_BUTTONS_VOLATILE_QUERY_INTERVAL		100 /* [ms] */
+
+static unsigned int ir_debug;
+module_param(ir_debug, int, 0644);
+MODULE_PARM_DESC(ir_debug, "enable debug messages [IR]");
+
+#define MODULE_NAME "em28xx"
+
+#define dprintk(fmt, arg...) do {					\
+	if (ir_debug)							\
+		dev_printk(KERN_DEBUG, &ir->dev->intf->dev,		\
+			   "input: %s: " fmt, __func__, ## arg);	\
+} while (0)
+
+/*
+ * Polling structure used by em28xx IR's
+ */
+
+struct em28xx_ir_poll_result {
+	unsigned int toggle_bit:1;
+	unsigned int read_count:7;
+
+	enum rc_proto protocol;
+	u32 scancode;
+};
+
+struct em28xx_IR {
+	struct em28xx *dev;
+	struct rc_dev *rc;
+	char name[32];
+	char phys[32];
+
+	/* poll decoder */
+	int polling;
+	struct delayed_work work;
+	unsigned int full_code:1;
+	unsigned int last_readcount;
+	u64 rc_proto;
+
+	struct i2c_client *i2c_client;
+
+	int  (*get_key_i2c)(struct i2c_client *ir, enum rc_proto *protocol,
+			    u32 *scancode);
+	int  (*get_key)(struct em28xx_IR *ir, struct em28xx_ir_poll_result *r);
+};
+
+/*
+ * I2C IR based get keycodes - should be used with ir-kbd-i2c
+ */
+
+static int em28xx_get_key_terratec(struct i2c_client *i2c_dev,
+				   enum rc_proto *protocol, u32 *scancode)
+{
+	int rc;
+	unsigned char b;
+
+	/* poll IR chip */
+	rc = i2c_master_recv(i2c_dev, &b, 1);
+	if (rc != 1) {
+		if (rc < 0)
+			return rc;
+		return -EIO;
+	}
+
+	/*
+	 * it seems that 0xFE indicates that a button is still hold
+	 * down, while 0xff indicates that no button is hold down.
+	 */
+
+	if (b == 0xff)
+		return 0;
+
+	if (b == 0xfe)
+		/* keep old data */
+		return 1;
+
+	*protocol = RC_PROTO_UNKNOWN;
+	*scancode = b;
+	return 1;
+}
+
+static int em28xx_get_key_em_haup(struct i2c_client *i2c_dev,
+				  enum rc_proto *protocol, u32 *scancode)
+{
+	unsigned char buf[2];
+	int size;
+
+	/* poll IR chip */
+	size = i2c_master_recv(i2c_dev, buf, sizeof(buf));
+
+	if (size != 2)
+		return -EIO;
+
+	/* Does eliminate repeated parity code */
+	if (buf[1] == 0xff)
+		return 0;
+
+	/*
+	 * Rearranges bits to the right order.
+	 * The bit order were determined experimentally by using
+	 * The original Hauppauge Grey IR and another RC5 that uses addr=0x08
+	 * The RC5 code has 14 bits, but we've experimentally determined
+	 * the meaning for only 11 bits.
+	 * So, the code translation is not complete. Yet, it is enough to
+	 * work with the provided RC5 IR.
+	 */
+	*protocol = RC_PROTO_RC5;
+	*scancode = (bitrev8(buf[1]) & 0x1f) << 8 | bitrev8(buf[0]) >> 2;
+	return 1;
+}
+
+static int em28xx_get_key_pinnacle_usb_grey(struct i2c_client *i2c_dev,
+					    enum rc_proto *protocol,
+					    u32 *scancode)
+{
+	unsigned char buf[3];
+
+	/* poll IR chip */
+
+	if (i2c_master_recv(i2c_dev, buf, 3) != 3)
+		return -EIO;
+
+	if (buf[0] != 0x00)
+		return 0;
+
+	*protocol = RC_PROTO_UNKNOWN;
+	*scancode = buf[2] & 0x3f;
+	return 1;
+}
+
+static int em28xx_get_key_winfast_usbii_deluxe(struct i2c_client *i2c_dev,
+					       enum rc_proto *protocol,
+					       u32 *scancode)
+{
+	unsigned char subaddr, keydetect, key;
+
+	struct i2c_msg msg[] = {
+		{
+			.addr = i2c_dev->addr,
+			.flags = 0,
+			.buf = &subaddr, .len = 1
+		}, {
+			.addr = i2c_dev->addr,
+			.flags = I2C_M_RD,
+			.buf = &keydetect,
+			.len = 1
+		}
+	};
+
+	subaddr = 0x10;
+	if (i2c_transfer(i2c_dev->adapter, msg, 2) != 2)
+		return -EIO;
+	if (keydetect == 0x00)
+		return 0;
+
+	subaddr = 0x00;
+	msg[1].buf = &key;
+	if (i2c_transfer(i2c_dev->adapter, msg, 2) != 2)
+		return -EIO;
+	if (key == 0x00)
+		return 0;
+
+	*protocol = RC_PROTO_UNKNOWN;
+	*scancode = key;
+	return 1;
+}
+
+/*
+ * Poll based get keycode functions
+ */
+
+/* This is for the em2860/em2880 */
+static int default_polling_getkey(struct em28xx_IR *ir,
+				  struct em28xx_ir_poll_result *poll_result)
+{
+	struct em28xx *dev = ir->dev;
+	int rc;
+	u8 msg[3] = { 0, 0, 0 };
+
+	/*
+	 * Read key toggle, brand, and key code
+	 * on registers 0x45, 0x46 and 0x47
+	 */
+	rc = dev->em28xx_read_reg_req_len(dev, 0, EM28XX_R45_IR,
+					  msg, sizeof(msg));
+	if (rc < 0)
+		return rc;
+
+	/* Infrared toggle (Reg 0x45[7]) */
+	poll_result->toggle_bit = (msg[0] >> 7);
+
+	/* Infrared read count (Reg 0x45[6:0] */
+	poll_result->read_count = (msg[0] & 0x7f);
+
+	/* Remote Control Address/Data (Regs 0x46/0x47) */
+	switch (ir->rc_proto) {
+	case RC_PROTO_BIT_RC5:
+		poll_result->protocol = RC_PROTO_RC5;
+		poll_result->scancode = RC_SCANCODE_RC5(msg[1], msg[2]);
+		break;
+
+	case RC_PROTO_BIT_NEC:
+		poll_result->protocol = RC_PROTO_NEC;
+		poll_result->scancode = RC_SCANCODE_NEC(msg[1], msg[2]);
+		break;
+
+	default:
+		poll_result->protocol = RC_PROTO_UNKNOWN;
+		poll_result->scancode = msg[1] << 8 | msg[2];
+		break;
+	}
+
+	return 0;
+}
+
+static int em2874_polling_getkey(struct em28xx_IR *ir,
+				 struct em28xx_ir_poll_result *poll_result)
+{
+	struct em28xx *dev = ir->dev;
+	int rc;
+	u8 msg[5] = { 0, 0, 0, 0, 0 };
+
+	/*
+	 * Read key toggle, brand, and key code
+	 * on registers 0x51-55
+	 */
+	rc = dev->em28xx_read_reg_req_len(dev, 0, EM2874_R51_IR,
+					  msg, sizeof(msg));
+	if (rc < 0)
+		return rc;
+
+	/* Infrared toggle (Reg 0x51[7]) */
+	poll_result->toggle_bit = (msg[0] >> 7);
+
+	/* Infrared read count (Reg 0x51[6:0] */
+	poll_result->read_count = (msg[0] & 0x7f);
+
+	/*
+	 * Remote Control Address (Reg 0x52)
+	 * Remote Control Data (Reg 0x53-0x55)
+	 */
+	switch (ir->rc_proto) {
+	case RC_PROTO_BIT_RC5:
+		poll_result->protocol = RC_PROTO_RC5;
+		poll_result->scancode = RC_SCANCODE_RC5(msg[1], msg[2]);
+		break;
+
+	case RC_PROTO_BIT_NEC:
+		poll_result->scancode = msg[1] << 8 | msg[2];
+		if ((msg[3] ^ msg[4]) != 0xff) {	/* 32 bits NEC */
+			poll_result->protocol = RC_PROTO_NEC32;
+			poll_result->scancode = RC_SCANCODE_NEC32((msg[1] << 24) |
+								  (msg[2] << 16) |
+								  (msg[3] << 8)  |
+								  (msg[4]));
+		} else if ((msg[1] ^ msg[2]) != 0xff) {	/* 24 bits NEC */
+			poll_result->protocol = RC_PROTO_NECX;
+			poll_result->scancode = RC_SCANCODE_NECX(msg[1] << 8 |
+								 msg[2], msg[3]);
+		} else {				/* Normal NEC */
+			poll_result->protocol = RC_PROTO_NEC;
+			poll_result->scancode = RC_SCANCODE_NEC(msg[1], msg[3]);
+		}
+		break;
+
+	case RC_PROTO_BIT_RC6_0:
+		poll_result->protocol = RC_PROTO_RC6_0;
+		poll_result->scancode = RC_SCANCODE_RC6_0(msg[1], msg[2]);
+		break;
+
+	default:
+		poll_result->protocol = RC_PROTO_UNKNOWN;
+		poll_result->scancode = (msg[1] << 24) | (msg[2] << 16) |
+					(msg[3] << 8)  | msg[4];
+		break;
+	}
+
+	return 0;
+}
+
+/*
+ * Polling code for em28xx
+ */
+
+static int em28xx_i2c_ir_handle_key(struct em28xx_IR *ir)
+{
+	static u32 scancode;
+	enum rc_proto protocol;
+	int rc;
+
+	rc = ir->get_key_i2c(ir->i2c_client, &protocol, &scancode);
+	if (rc < 0) {
+		dprintk("ir->get_key_i2c() failed: %d\n", rc);
+		return rc;
+	}
+
+	if (rc) {
+		dprintk("%s: proto = 0x%04x, scancode = 0x%04x\n",
+			__func__, protocol, scancode);
+		rc_keydown(ir->rc, protocol, scancode, 0);
+	}
+	return 0;
+}
+
+static void em28xx_ir_handle_key(struct em28xx_IR *ir)
+{
+	int result;
+	struct em28xx_ir_poll_result poll_result;
+
+	/* read the registers containing the IR status */
+	result = ir->get_key(ir, &poll_result);
+	if (unlikely(result < 0)) {
+		dprintk("ir->get_key() failed: %d\n", result);
+		return;
+	}
+
+	if (unlikely(poll_result.read_count != ir->last_readcount)) {
+		dprintk("%s: toggle: %d, count: %d, key 0x%04x\n", __func__,
+			poll_result.toggle_bit, poll_result.read_count,
+			poll_result.scancode);
+		if (ir->full_code)
+			rc_keydown(ir->rc,
+				   poll_result.protocol,
+				   poll_result.scancode,
+				   poll_result.toggle_bit);
+		else
+			rc_keydown(ir->rc,
+				   RC_PROTO_UNKNOWN,
+				   poll_result.scancode & 0xff,
+				   poll_result.toggle_bit);
+
+		if (ir->dev->chip_id == CHIP_ID_EM2874 ||
+		    ir->dev->chip_id == CHIP_ID_EM2884)
+			/*
+			 * The em2874 clears the readcount field every time the
+			 * register is read.  The em2860/2880 datasheet says
+			 * that it is supposed to clear the readcount, but it
+			 * doesn't. So with the em2874, we are looking for a
+			 * non-zero read count as opposed to a readcount
+			 * that is incrementing
+			 */
+			ir->last_readcount = 0;
+		else
+			ir->last_readcount = poll_result.read_count;
+	}
+}
+
+static void em28xx_ir_work(struct work_struct *work)
+{
+	struct em28xx_IR *ir = container_of(work, struct em28xx_IR, work.work);
+
+	if (ir->i2c_client) /* external i2c device */
+		em28xx_i2c_ir_handle_key(ir);
+	else /* internal device */
+		em28xx_ir_handle_key(ir);
+	schedule_delayed_work(&ir->work, msecs_to_jiffies(ir->polling));
+}
+
+static int em28xx_ir_start(struct rc_dev *rc)
+{
+	struct em28xx_IR *ir = rc->priv;
+
+	INIT_DELAYED_WORK(&ir->work, em28xx_ir_work);
+	schedule_delayed_work(&ir->work, 0);
+
+	return 0;
+}
+
+static void em28xx_ir_stop(struct rc_dev *rc)
+{
+	struct em28xx_IR *ir = rc->priv;
+
+	cancel_delayed_work_sync(&ir->work);
+}
+
+static int em2860_ir_change_protocol(struct rc_dev *rc_dev, u64 *rc_proto)
+{
+	struct em28xx_IR *ir = rc_dev->priv;
+	struct em28xx *dev = ir->dev;
+
+	/* Adjust xclk based on IR table for RC5/NEC tables */
+	if (*rc_proto & RC_PROTO_BIT_RC5) {
+		dev->board.xclk |= EM28XX_XCLK_IR_RC5_MODE;
+		ir->full_code = 1;
+		*rc_proto = RC_PROTO_BIT_RC5;
+	} else if (*rc_proto & RC_PROTO_BIT_NEC) {
+		dev->board.xclk &= ~EM28XX_XCLK_IR_RC5_MODE;
+		ir->full_code = 1;
+		*rc_proto = RC_PROTO_BIT_NEC;
+	} else if (*rc_proto & RC_PROTO_BIT_UNKNOWN) {
+		*rc_proto = RC_PROTO_BIT_UNKNOWN;
+	} else {
+		*rc_proto = ir->rc_proto;
+		return -EINVAL;
+	}
+	em28xx_write_reg_bits(dev, EM28XX_R0F_XCLK, dev->board.xclk,
+			      EM28XX_XCLK_IR_RC5_MODE);
+
+	ir->rc_proto = *rc_proto;
+
+	return 0;
+}
+
+static int em2874_ir_change_protocol(struct rc_dev *rc_dev, u64 *rc_proto)
+{
+	struct em28xx_IR *ir = rc_dev->priv;
+	struct em28xx *dev = ir->dev;
+	u8 ir_config = EM2874_IR_RC5;
+
+	/* Adjust xclk and set type based on IR table for RC5/NEC/RC6 tables */
+	if (*rc_proto & RC_PROTO_BIT_RC5) {
+		dev->board.xclk |= EM28XX_XCLK_IR_RC5_MODE;
+		ir->full_code = 1;
+		*rc_proto = RC_PROTO_BIT_RC5;
+	} else if (*rc_proto & RC_PROTO_BIT_NEC) {
+		dev->board.xclk &= ~EM28XX_XCLK_IR_RC5_MODE;
+		ir_config = EM2874_IR_NEC | EM2874_IR_NEC_NO_PARITY;
+		ir->full_code = 1;
+		*rc_proto = RC_PROTO_BIT_NEC;
+	} else if (*rc_proto & RC_PROTO_BIT_RC6_0) {
+		dev->board.xclk |= EM28XX_XCLK_IR_RC5_MODE;
+		ir_config = EM2874_IR_RC6_MODE_0;
+		ir->full_code = 1;
+		*rc_proto = RC_PROTO_BIT_RC6_0;
+	} else if (*rc_proto & RC_PROTO_BIT_UNKNOWN) {
+		*rc_proto = RC_PROTO_BIT_UNKNOWN;
+	} else {
+		*rc_proto = ir->rc_proto;
+		return -EINVAL;
+	}
+	em28xx_write_regs(dev, EM2874_R50_IR_CONFIG, &ir_config, 1);
+	em28xx_write_reg_bits(dev, EM28XX_R0F_XCLK, dev->board.xclk,
+			      EM28XX_XCLK_IR_RC5_MODE);
+
+	ir->rc_proto = *rc_proto;
+
+	return 0;
+}
+
+static int em28xx_ir_change_protocol(struct rc_dev *rc_dev, u64 *rc_proto)
+{
+	struct em28xx_IR *ir = rc_dev->priv;
+	struct em28xx *dev = ir->dev;
+
+	/* Setup the proper handler based on the chip */
+	switch (dev->chip_id) {
+	case CHIP_ID_EM2860:
+	case CHIP_ID_EM2883:
+		return em2860_ir_change_protocol(rc_dev, rc_proto);
+	case CHIP_ID_EM2884:
+	case CHIP_ID_EM2874:
+	case CHIP_ID_EM28174:
+	case CHIP_ID_EM28178:
+		return em2874_ir_change_protocol(rc_dev, rc_proto);
+	default:
+		dev_err(&ir->dev->intf->dev,
+			"Unrecognized em28xx chip id 0x%02x: IR not supported\n",
+			dev->chip_id);
+		return -EINVAL;
+	}
+}
+
+static int em28xx_probe_i2c_ir(struct em28xx *dev)
+{
+	int i = 0;
+	/*
+	 * Leadtek winfast tv USBII deluxe can find a non working IR-device
+	 * at address 0x18, so if that address is needed for another board in
+	 * the future, please put it after 0x1f.
+	 */
+	const unsigned short addr_list[] = {
+		 0x1f, 0x30, 0x47, I2C_CLIENT_END
+	};
+
+	while (addr_list[i] != I2C_CLIENT_END) {
+		if (i2c_probe_func_quick_read(&dev->i2c_adap[dev->def_i2c_bus],
+					      addr_list[i]) == 1)
+			return addr_list[i];
+		i++;
+	}
+
+	return -ENODEV;
+}
+
+/*
+ * Handle buttons
+ */
+
+static void em28xx_query_buttons(struct work_struct *work)
+{
+	struct em28xx *dev =
+		container_of(work, struct em28xx, buttons_query_work.work);
+	u8 i, j;
+	int regval;
+	bool is_pressed, was_pressed;
+	const struct em28xx_led *led;
+
+	/* Poll and evaluate all addresses */
+	for (i = 0; i < dev->num_button_polling_addresses; i++) {
+		/* Read value from register */
+		regval = em28xx_read_reg(dev, dev->button_polling_addresses[i]);
+		if (regval < 0)
+			continue;
+		/* Check states of the buttons and act */
+		j = 0;
+		while (dev->board.buttons[j].role >= 0 &&
+		       dev->board.buttons[j].role < EM28XX_NUM_BUTTON_ROLES) {
+			const struct em28xx_button *button;
+
+			button = &dev->board.buttons[j];
+
+			/* Check if button uses the current address */
+			if (button->reg_r != dev->button_polling_addresses[i]) {
+				j++;
+				continue;
+			}
+			/* Determine if button is and was pressed last time */
+			is_pressed = regval & button->mask;
+			was_pressed = dev->button_polling_last_values[i]
+				       & button->mask;
+			if (button->inverted) {
+				is_pressed = !is_pressed;
+				was_pressed = !was_pressed;
+			}
+			/* Clear button state (if needed) */
+			if (is_pressed && button->reg_clearing)
+				em28xx_write_reg(dev, button->reg_clearing,
+						 (~regval & button->mask)
+						    | (regval & ~button->mask));
+			/* Handle button state */
+			if (!is_pressed || was_pressed) {
+				j++;
+				continue;
+			}
+			switch (button->role) {
+			case EM28XX_BUTTON_SNAPSHOT:
+				/* Emulate the keypress */
+				input_report_key(dev->sbutton_input_dev,
+						 EM28XX_SNAPSHOT_KEY, 1);
+				/* Unpress the key */
+				input_report_key(dev->sbutton_input_dev,
+						 EM28XX_SNAPSHOT_KEY, 0);
+				break;
+			case EM28XX_BUTTON_ILLUMINATION:
+				led = em28xx_find_led(dev,
+						      EM28XX_LED_ILLUMINATION);
+				/* Switch illumination LED on/off */
+				if (led)
+					em28xx_toggle_reg_bits(dev,
+							       led->gpio_reg,
+							       led->gpio_mask);
+				break;
+			default:
+				WARN_ONCE(1, "BUG: unhandled button role.");
+			}
+			/* Next button */
+			j++;
+		}
+		/* Save current value for comparison during the next polling */
+		dev->button_polling_last_values[i] = regval;
+	}
+	/* Schedule next poll */
+	schedule_delayed_work(&dev->buttons_query_work,
+			      msecs_to_jiffies(dev->button_polling_interval));
+}
+
+static int em28xx_register_snapshot_button(struct em28xx *dev)
+{
+	struct usb_device *udev = interface_to_usbdev(dev->intf);
+	struct input_dev *input_dev;
+	int err;
+
+	dev_info(&dev->intf->dev, "Registering snapshot button...\n");
+	input_dev = input_allocate_device();
+	if (!input_dev)
+		return -ENOMEM;
+
+	usb_make_path(udev, dev->snapshot_button_path,
+		      sizeof(dev->snapshot_button_path));
+	strlcat(dev->snapshot_button_path, "/sbutton",
+		sizeof(dev->snapshot_button_path));
+
+	input_dev->name = "em28xx snapshot button";
+	input_dev->phys = dev->snapshot_button_path;
+	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP);
+	set_bit(EM28XX_SNAPSHOT_KEY, input_dev->keybit);
+	input_dev->keycodesize = 0;
+	input_dev->keycodemax = 0;
+	input_dev->id.bustype = BUS_USB;
+	input_dev->id.vendor = le16_to_cpu(udev->descriptor.idVendor);
+	input_dev->id.product = le16_to_cpu(udev->descriptor.idProduct);
+	input_dev->id.version = 1;
+	input_dev->dev.parent = &dev->intf->dev;
+
+	err = input_register_device(input_dev);
+	if (err) {
+		dev_err(&dev->intf->dev, "input_register_device failed\n");
+		input_free_device(input_dev);
+		return err;
+	}
+
+	dev->sbutton_input_dev = input_dev;
+	return 0;
+}
+
+static void em28xx_init_buttons(struct em28xx *dev)
+{
+	u8  i = 0, j = 0;
+	bool addr_new = false;
+
+	dev->button_polling_interval = EM28XX_BUTTONS_DEBOUNCED_QUERY_INTERVAL;
+	while (dev->board.buttons[i].role >= 0 &&
+	       dev->board.buttons[i].role < EM28XX_NUM_BUTTON_ROLES) {
+		const struct em28xx_button *button = &dev->board.buttons[i];
+
+		/* Check if polling address is already on the list */
+		addr_new = true;
+		for (j = 0; j < dev->num_button_polling_addresses; j++) {
+			if (button->reg_r == dev->button_polling_addresses[j]) {
+				addr_new = false;
+				break;
+			}
+		}
+		/* Check if max. number of polling addresses is exceeded */
+		if (addr_new && dev->num_button_polling_addresses
+					   >= EM28XX_NUM_BUTTON_ADDRESSES_MAX) {
+			WARN_ONCE(1, "BUG: maximum number of button polling addresses exceeded.");
+			goto next_button;
+		}
+		/* Button role specific checks and actions */
+		if (button->role == EM28XX_BUTTON_SNAPSHOT) {
+			/* Register input device */
+			if (em28xx_register_snapshot_button(dev) < 0)
+				goto next_button;
+		} else if (button->role == EM28XX_BUTTON_ILLUMINATION) {
+			/* Check sanity */
+			if (!em28xx_find_led(dev, EM28XX_LED_ILLUMINATION)) {
+				dev_err(&dev->intf->dev,
+					"BUG: illumination button defined, but no illumination LED.\n");
+				goto next_button;
+			}
+		}
+		/* Add read address to list of polling addresses */
+		if (addr_new) {
+			unsigned int index = dev->num_button_polling_addresses;
+
+			dev->button_polling_addresses[index] = button->reg_r;
+			dev->num_button_polling_addresses++;
+		}
+		/* Reduce polling interval if necessary */
+		if (!button->reg_clearing)
+			dev->button_polling_interval =
+					 EM28XX_BUTTONS_VOLATILE_QUERY_INTERVAL;
+next_button:
+		/* Next button */
+		i++;
+	}
+
+	/* Start polling */
+	if (dev->num_button_polling_addresses) {
+		memset(dev->button_polling_last_values, 0,
+		       EM28XX_NUM_BUTTON_ADDRESSES_MAX);
+		schedule_delayed_work(&dev->buttons_query_work,
+				      msecs_to_jiffies(dev->button_polling_interval));
+	}
+}
+
+static void em28xx_shutdown_buttons(struct em28xx *dev)
+{
+	/* Cancel polling */
+	cancel_delayed_work_sync(&dev->buttons_query_work);
+	/* Clear polling addresses list */
+	dev->num_button_polling_addresses = 0;
+	/* Deregister input devices */
+	if (dev->sbutton_input_dev) {
+		dev_info(&dev->intf->dev, "Deregistering snapshot button\n");
+		input_unregister_device(dev->sbutton_input_dev);
+		dev->sbutton_input_dev = NULL;
+	}
+}
+
+static int em28xx_ir_init(struct em28xx *dev)
+{
+	struct usb_device *udev = interface_to_usbdev(dev->intf);
+	struct em28xx_IR *ir;
+	struct rc_dev *rc;
+	int err = -ENOMEM;
+	u64 rc_proto;
+	u16 i2c_rc_dev_addr = 0;
+
+	if (dev->is_audio_only) {
+		/* Shouldn't initialize IR for this interface */
+		return 0;
+	}
+
+	kref_get(&dev->ref);
+	INIT_DELAYED_WORK(&dev->buttons_query_work, em28xx_query_buttons);
+
+	if (dev->board.buttons)
+		em28xx_init_buttons(dev);
+
+	if (dev->board.has_ir_i2c) {
+		i2c_rc_dev_addr = em28xx_probe_i2c_ir(dev);
+		if (!i2c_rc_dev_addr) {
+			dev->board.has_ir_i2c = 0;
+			dev_warn(&dev->intf->dev,
+				 "No i2c IR remote control device found.\n");
+			return -ENODEV;
+		}
+	}
+
+	if (!dev->board.ir_codes && !dev->board.has_ir_i2c) {
+		/* No remote control support */
+		dev_warn(&dev->intf->dev,
+			 "Remote control support is not available for this card.\n");
+		return 0;
+	}
+
+	dev_info(&dev->intf->dev, "Registering input extension\n");
+
+	ir = kzalloc(sizeof(*ir), GFP_KERNEL);
+	if (!ir)
+		return -ENOMEM;
+	rc = rc_allocate_device(RC_DRIVER_SCANCODE);
+	if (!rc)
+		goto error;
+
+	/* record handles to ourself */
+	ir->dev = dev;
+	dev->ir = ir;
+	ir->rc = rc;
+
+	rc->priv = ir;
+	rc->open = em28xx_ir_start;
+	rc->close = em28xx_ir_stop;
+
+	if (dev->board.has_ir_i2c) {	/* external i2c device */
+		switch (dev->model) {
+		case EM2800_BOARD_TERRATEC_CINERGY_200:
+		case EM2820_BOARD_TERRATEC_CINERGY_250:
+			rc->map_name = RC_MAP_EM_TERRATEC;
+			ir->get_key_i2c = em28xx_get_key_terratec;
+			break;
+		case EM2820_BOARD_PINNACLE_USB_2:
+			rc->map_name = RC_MAP_PINNACLE_GREY;
+			ir->get_key_i2c = em28xx_get_key_pinnacle_usb_grey;
+			break;
+		case EM2820_BOARD_HAUPPAUGE_WINTV_USB_2:
+			rc->map_name = RC_MAP_HAUPPAUGE;
+			ir->get_key_i2c = em28xx_get_key_em_haup;
+			rc->allowed_protocols = RC_PROTO_BIT_RC5;
+			break;
+		case EM2820_BOARD_LEADTEK_WINFAST_USBII_DELUXE:
+			rc->map_name = RC_MAP_WINFAST_USBII_DELUXE;
+			ir->get_key_i2c = em28xx_get_key_winfast_usbii_deluxe;
+			break;
+		default:
+			err = -ENODEV;
+			goto error;
+		}
+
+		ir->i2c_client = kzalloc(sizeof(*ir->i2c_client), GFP_KERNEL);
+		if (!ir->i2c_client)
+			goto error;
+		ir->i2c_client->adapter = &ir->dev->i2c_adap[dev->def_i2c_bus];
+		ir->i2c_client->addr = i2c_rc_dev_addr;
+		ir->i2c_client->flags = 0;
+		/* NOTE: all other fields of i2c_client are unused */
+	} else {	/* internal device */
+		switch (dev->chip_id) {
+		case CHIP_ID_EM2860:
+		case CHIP_ID_EM2883:
+			rc->allowed_protocols = RC_PROTO_BIT_RC5 |
+						RC_PROTO_BIT_NEC;
+			ir->get_key = default_polling_getkey;
+			break;
+		case CHIP_ID_EM2884:
+		case CHIP_ID_EM2874:
+		case CHIP_ID_EM28174:
+		case CHIP_ID_EM28178:
+			ir->get_key = em2874_polling_getkey;
+			rc->allowed_protocols = RC_PROTO_BIT_RC5 |
+				RC_PROTO_BIT_NEC | RC_PROTO_BIT_NECX |
+				RC_PROTO_BIT_NEC32 | RC_PROTO_BIT_RC6_0;
+			break;
+		default:
+			err = -ENODEV;
+			goto error;
+		}
+
+		rc->change_protocol = em28xx_ir_change_protocol;
+		rc->map_name = dev->board.ir_codes;
+
+		/* By default, keep protocol field untouched */
+		rc_proto = RC_PROTO_BIT_UNKNOWN;
+		err = em28xx_ir_change_protocol(rc, &rc_proto);
+		if (err)
+			goto error;
+	}
+
+	/* This is how often we ask the chip for IR information */
+	ir->polling = 100; /* ms */
+
+	/* init input device */
+	snprintf(ir->name, sizeof(ir->name), "%s IR",
+		 dev_name(&dev->intf->dev));
+
+	usb_make_path(udev, ir->phys, sizeof(ir->phys));
+	strlcat(ir->phys, "/input0", sizeof(ir->phys));
+
+	rc->device_name = ir->name;
+	rc->input_phys = ir->phys;
+	rc->input_id.bustype = BUS_USB;
+	rc->input_id.version = 1;
+	rc->input_id.vendor = le16_to_cpu(udev->descriptor.idVendor);
+	rc->input_id.product = le16_to_cpu(udev->descriptor.idProduct);
+	rc->dev.parent = &dev->intf->dev;
+	rc->driver_name = MODULE_NAME;
+
+	/* all done */
+	err = rc_register_device(rc);
+	if (err)
+		goto error;
+
+	dev_info(&dev->intf->dev, "Input extension successfully initialized\n");
+
+	return 0;
+
+error:
+	kfree(ir->i2c_client);
+	dev->ir = NULL;
+	rc_free_device(rc);
+	kfree(ir);
+	return err;
+}
+
+static int em28xx_ir_fini(struct em28xx *dev)
+{
+	struct em28xx_IR *ir = dev->ir;
+
+	if (dev->is_audio_only) {
+		/* Shouldn't initialize IR for this interface */
+		return 0;
+	}
+
+	dev_info(&dev->intf->dev, "Closing input extension\n");
+
+	em28xx_shutdown_buttons(dev);
+
+	/* skip detach on non attached boards */
+	if (!ir)
+		goto ref_put;
+
+	rc_unregister_device(ir->rc);
+
+	kfree(ir->i2c_client);
+
+	/* done */
+	kfree(ir);
+	dev->ir = NULL;
+
+ref_put:
+	kref_put(&dev->ref, em28xx_free_device);
+
+	return 0;
+}
+
+static int em28xx_ir_suspend(struct em28xx *dev)
+{
+	struct em28xx_IR *ir = dev->ir;
+
+	if (dev->is_audio_only)
+		return 0;
+
+	dev_info(&dev->intf->dev, "Suspending input extension\n");
+	if (ir)
+		cancel_delayed_work_sync(&ir->work);
+	cancel_delayed_work_sync(&dev->buttons_query_work);
+	/*
+	 * is canceling delayed work sufficient or does the rc event
+	 * kthread needs stopping? kthread is stopped in
+	 * ir_raw_event_unregister()
+	 */
+	return 0;
+}
+
+static int em28xx_ir_resume(struct em28xx *dev)
+{
+	struct em28xx_IR *ir = dev->ir;
+
+	if (dev->is_audio_only)
+		return 0;
+
+	dev_info(&dev->intf->dev, "Resuming input extension\n");
+	/*
+	 * if suspend calls ir_raw_event_unregister(), the should call
+	 * ir_raw_event_register()
+	 */
+	if (ir)
+		schedule_delayed_work(&ir->work, msecs_to_jiffies(ir->polling));
+	if (dev->num_button_polling_addresses)
+		schedule_delayed_work(&dev->buttons_query_work,
+				      msecs_to_jiffies(dev->button_polling_interval));
+	return 0;
+}
+
+static struct em28xx_ops rc_ops = {
+	.id   = EM28XX_RC,
+	.name = "Em28xx Input Extension",
+	.init = em28xx_ir_init,
+	.fini = em28xx_ir_fini,
+	.suspend = em28xx_ir_suspend,
+	.resume = em28xx_ir_resume,
+};
+
+static int __init em28xx_rc_register(void)
+{
+	return em28xx_register_extension(&rc_ops);
+}
+
+static void __exit em28xx_rc_unregister(void)
+{
+	em28xx_unregister_extension(&rc_ops);
+}
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Mauro Carvalho Chehab");
+MODULE_DESCRIPTION(DRIVER_DESC " - input interface");
+MODULE_VERSION(EM28XX_VERSION);
+
+module_init(em28xx_rc_register);
+module_exit(em28xx_rc_unregister);
diff --git a/drivers/media/usb/em28xx/em28xx-reg.h b/drivers/media/usb/em28xx/em28xx-reg.h
new file mode 100644
index 0000000..f53afe1
--- /dev/null
+++ b/drivers/media/usb/em28xx/em28xx-reg.h
@@ -0,0 +1,301 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+/*
+ * em28xx-reg.h - Register definitions for em28xx driver
+ */
+
+#define EM_GPIO_0  ((unsigned char)BIT(0))
+#define EM_GPIO_1  ((unsigned char)BIT(1))
+#define EM_GPIO_2  ((unsigned char)BIT(2))
+#define EM_GPIO_3  ((unsigned char)BIT(3))
+#define EM_GPIO_4  ((unsigned char)BIT(4))
+#define EM_GPIO_5  ((unsigned char)BIT(5))
+#define EM_GPIO_6  ((unsigned char)BIT(6))
+#define EM_GPIO_7  ((unsigned char)BIT(7))
+
+#define EM_GPO_0   ((unsigned char)BIT(0))
+#define EM_GPO_1   ((unsigned char)BIT(1))
+#define EM_GPO_2   ((unsigned char)BIT(2))
+#define EM_GPO_3   ((unsigned char)BIT(3))
+
+/* em28xx endpoints */
+/* 0x82:   (always ?) analog */
+#define EM28XX_EP_AUDIO		0x83
+/* 0x84:   digital or analog */
+
+/* em2800 registers */
+#define EM2800_R08_AUDIOSRC 0x08
+
+/* em28xx registers */
+
+#define EM28XX_R00_CHIPCFG	0x00
+
+/* em28xx Chip Configuration 0x00 */
+#define EM2860_CHIPCFG_VENDOR_AUDIO		0x80
+#define EM2860_CHIPCFG_I2S_VOLUME_CAPABLE	0x40
+#define EM2820_CHIPCFG_I2S_3_SAMPRATES		0x30
+#define EM2860_CHIPCFG_I2S_5_SAMPRATES		0x30
+#define EM2820_CHIPCFG_I2S_1_SAMPRATE		0x20
+#define EM2860_CHIPCFG_I2S_3_SAMPRATES		0x20
+#define EM28XX_CHIPCFG_AC97			0x10
+#define EM28XX_CHIPCFG_AUDIOMASK		0x30
+
+#define EM28XX_R01_CHIPCFG2	0x01
+
+/* em28xx Chip Configuration 2 0x01 */
+#define EM28XX_CHIPCFG2_TS_PRESENT		0x10
+#define EM28XX_CHIPCFG2_TS_REQ_INTERVAL_MASK	0x0c /* bits 3-2 */
+#define EM28XX_CHIPCFG2_TS_REQ_INTERVAL_1MF	0x00
+#define EM28XX_CHIPCFG2_TS_REQ_INTERVAL_2MF	0x04
+#define EM28XX_CHIPCFG2_TS_REQ_INTERVAL_4MF	0x08
+#define EM28XX_CHIPCFG2_TS_REQ_INTERVAL_8MF	0x0c
+#define EM28XX_CHIPCFG2_TS_PACKETSIZE_MASK	0x03 /* bits 0-1 */
+#define EM28XX_CHIPCFG2_TS_PACKETSIZE_188	0x00
+#define EM28XX_CHIPCFG2_TS_PACKETSIZE_376	0x01
+#define EM28XX_CHIPCFG2_TS_PACKETSIZE_564	0x02
+#define EM28XX_CHIPCFG2_TS_PACKETSIZE_752	0x03
+
+/* GPIO/GPO registers */
+#define EM2880_R04_GPO		0x04    /* em2880-em2883 only */
+#define EM2820_R08_GPIO_CTRL	0x08	/* em2820-em2873/83 only */
+#define EM2820_R09_GPIO_STATE	0x09	/* em2820-em2873/83 only */
+
+#define EM28XX_R06_I2C_CLK	0x06
+
+/* em28xx I2C Clock Register (0x06) */
+#define EM28XX_I2C_CLK_ACK_LAST_READ	0x80
+#define EM28XX_I2C_CLK_WAIT_ENABLE	0x40
+#define EM28XX_I2C_EEPROM_ON_BOARD	0x08
+#define EM28XX_I2C_EEPROM_KEY_VALID	0x04
+#define EM2874_I2C_SECONDARY_BUS_SELECT	0x04 /* em2874 has two i2c busses */
+#define EM28XX_I2C_FREQ_1_5_MHZ		0x03 /* bus frequency (bits [1-0]) */
+#define EM28XX_I2C_FREQ_25_KHZ		0x02
+#define EM28XX_I2C_FREQ_400_KHZ		0x01
+#define EM28XX_I2C_FREQ_100_KHZ		0x00
+
+#define EM28XX_R0A_CHIPID	0x0a
+#define EM28XX_R0C_USBSUSP	0x0c
+#define   EM28XX_R0C_USBSUSP_SNAPSHOT	0x20 /* 1=button pressed, needs reset */
+
+#define EM28XX_R0E_AUDIOSRC	0x0e
+#define EM28XX_R0F_XCLK	0x0f
+
+/* em28xx XCLK Register (0x0f) */
+#define EM28XX_XCLK_AUDIO_UNMUTE	0x80 /* otherwise audio muted */
+#define EM28XX_XCLK_I2S_MSB_TIMING	0x40 /* otherwise standard timing */
+#define EM28XX_XCLK_IR_RC5_MODE		0x20 /* otherwise NEC mode */
+#define EM28XX_XCLK_IR_NEC_CHK_PARITY	0x10
+#define EM28XX_XCLK_FREQUENCY_30MHZ	0x00 /* Freq. select (bits [3-0]) */
+#define EM28XX_XCLK_FREQUENCY_15MHZ	0x01
+#define EM28XX_XCLK_FREQUENCY_10MHZ	0x02
+#define EM28XX_XCLK_FREQUENCY_7_5MHZ	0x03
+#define EM28XX_XCLK_FREQUENCY_6MHZ	0x04
+#define EM28XX_XCLK_FREQUENCY_5MHZ	0x05
+#define EM28XX_XCLK_FREQUENCY_4_3MHZ	0x06
+#define EM28XX_XCLK_FREQUENCY_12MHZ	0x07
+#define EM28XX_XCLK_FREQUENCY_20MHZ	0x08
+#define EM28XX_XCLK_FREQUENCY_20MHZ_2	0x09
+#define EM28XX_XCLK_FREQUENCY_48MHZ	0x0a
+#define EM28XX_XCLK_FREQUENCY_24MHZ	0x0b
+
+#define EM28XX_R10_VINMODE	0x10
+	  /* used by all non-camera devices: */
+#define   EM28XX_VINMODE_YUV422_CbYCrY  0x10
+	  /* used by camera devices: */
+#define   EM28XX_VINMODE_YUV422_YUYV    0x08
+#define   EM28XX_VINMODE_YUV422_YVYU    0x09
+#define   EM28XX_VINMODE_YUV422_UYVY    0x0a
+#define   EM28XX_VINMODE_YUV422_VYUY    0x0b
+#define   EM28XX_VINMODE_RGB8_BGGR      0x0c
+#define   EM28XX_VINMODE_RGB8_GRBG      0x0d
+#define   EM28XX_VINMODE_RGB8_GBRG      0x0e
+#define   EM28XX_VINMODE_RGB8_RGGB      0x0f
+	  /*
+	   * apparently:
+	   *   bit 0: swap component 1+2 with 3+4
+	   *                 => e.g.: YUYV => YVYU, BGGR => GRBG
+	   *   bit 1: swap component 1 with 2 and 3 with 4
+	   *                 => e.g.: YUYV => UYVY, BGGR => GBRG
+	   */
+
+#define EM28XX_R11_VINCTRL	0x11
+
+/* em28xx Video Input Control Register 0x11 */
+#define EM28XX_VINCTRL_VBI_SLICED	0x80
+#define EM28XX_VINCTRL_VBI_RAW		0x40
+#define EM28XX_VINCTRL_VOUT_MODE_IN	0x20 /* HREF,VREF,VACT in output */
+#define EM28XX_VINCTRL_CCIR656_ENABLE	0x10
+#define EM28XX_VINCTRL_VBI_16BIT_RAW	0x08 /* otherwise 8-bit raw */
+#define EM28XX_VINCTRL_FID_ON_HREF	0x04
+#define EM28XX_VINCTRL_DUAL_EDGE_STROBE	0x02
+#define EM28XX_VINCTRL_INTERLACED	0x01
+
+#define EM28XX_R12_VINENABLE	0x12	/* */
+
+#define EM28XX_R14_GAMMA	0x14
+#define EM28XX_R15_RGAIN	0x15
+#define EM28XX_R16_GGAIN	0x16
+#define EM28XX_R17_BGAIN	0x17
+#define EM28XX_R18_ROFFSET	0x18
+#define EM28XX_R19_GOFFSET	0x19
+#define EM28XX_R1A_BOFFSET	0x1a
+
+#define EM28XX_R1B_OFLOW	0x1b
+#define EM28XX_R1C_HSTART	0x1c
+#define EM28XX_R1D_VSTART	0x1d
+#define EM28XX_R1E_CWIDTH	0x1e
+#define EM28XX_R1F_CHEIGHT	0x1f
+
+#define EM28XX_R20_YGAIN	0x20 /* contrast [0:4]   */
+#define   CONTRAST_DEFAULT	0x10
+
+#define EM28XX_R21_YOFFSET	0x21 /* brightness       */	/* signed */
+#define   BRIGHTNESS_DEFAULT	0x00
+
+#define EM28XX_R22_UVGAIN	0x22 /* saturation [0:4] */
+#define   SATURATION_DEFAULT	0x10
+
+#define EM28XX_R23_UOFFSET	0x23 /* blue balance     */	/* signed */
+#define   BLUE_BALANCE_DEFAULT	0x00
+
+#define EM28XX_R24_VOFFSET	0x24 /* red balance      */	/* signed */
+#define   RED_BALANCE_DEFAULT	0x00
+
+#define EM28XX_R25_SHARPNESS	0x25 /* sharpness [0:4]  */
+#define   SHARPNESS_DEFAULT	0x00
+
+#define EM28XX_R26_COMPR	0x26
+#define EM28XX_R27_OUTFMT	0x27
+
+/* em28xx Output Format Register (0x27) */
+#define EM28XX_OUTFMT_RGB_8_RGRG	0x00
+#define EM28XX_OUTFMT_RGB_8_GRGR	0x01
+#define EM28XX_OUTFMT_RGB_8_GBGB	0x02
+#define EM28XX_OUTFMT_RGB_8_BGBG	0x03
+#define EM28XX_OUTFMT_RGB_16_656	0x04
+#define EM28XX_OUTFMT_RGB_8_BAYER	0x08 /* Pattern in Reg 0x10[1-0] */
+#define EM28XX_OUTFMT_YUV211		0x10
+#define EM28XX_OUTFMT_YUV422_Y0UY1V	0x14
+#define EM28XX_OUTFMT_YUV422_Y1UY0V	0x15
+#define EM28XX_OUTFMT_YUV411		0x18
+
+#define EM28XX_R28_XMIN	0x28
+#define EM28XX_R29_XMAX	0x29
+#define EM28XX_R2A_YMIN	0x2a
+#define EM28XX_R2B_YMAX	0x2b
+
+#define EM28XX_R30_HSCALELOW	0x30
+#define EM28XX_R31_HSCALEHIGH	0x31
+#define EM28XX_R32_VSCALELOW	0x32
+#define EM28XX_R33_VSCALEHIGH	0x33
+#define   EM28XX_HVSCALE_MAX	0x3fff /* => 20% */
+
+#define EM28XX_R34_VBI_START_H	0x34
+#define EM28XX_R35_VBI_START_V	0x35
+/*
+ * NOTE: the EM276x (and EM25xx, EM277x/8x ?) (camera bridges) use these
+ * registers for a different unknown purpose.
+ *   => register 0x34 is set to capture width / 16
+ *   => register 0x35 is set to capture height / 16
+ */
+
+#define EM28XX_R36_VBI_WIDTH	0x36
+#define EM28XX_R37_VBI_HEIGHT	0x37
+
+#define EM28XX_R40_AC97LSB	0x40
+#define EM28XX_R41_AC97MSB	0x41
+#define EM28XX_R42_AC97ADDR	0x42
+#define EM28XX_R43_AC97BUSY	0x43
+
+#define EM28XX_R45_IR		0x45
+	/*
+	 * 0x45  bit 7    - parity bit
+	 *	 bits 6-0 - count
+	 * 0x46  IR brand
+	 *  0x47  IR data
+	 */
+
+/* em2874 registers */
+#define EM2874_R50_IR_CONFIG    0x50
+#define EM2874_R51_IR           0x51
+#define EM2874_R5D_TS1_PKT_SIZE 0x5d
+#define EM2874_R5E_TS2_PKT_SIZE 0x5e
+	/*
+	 * For both TS1 and TS2, In isochronous mode:
+	 *  0x01  188 bytes
+	 *  0x02  376 bytes
+	 *  0x03  564 bytes
+	 *  0x04  752 bytes
+	 *  0x05  940 bytes
+	 * In bulk mode:
+	 *  0x01..0xff  total packet count in 188-byte
+	 */
+
+#define EM2874_R5F_TS_ENABLE    0x5f
+
+/* em2874/174/84, em25xx, em276x/7x/8x GPIO registers */
+/*
+ * NOTE: not all ports are bonded out;
+ * Some ports are multiplexed with special function I/O
+ */
+#define EM2874_R80_GPIO_P0_CTRL    0x80
+#define EM2874_R81_GPIO_P1_CTRL    0x81
+#define EM2874_R82_GPIO_P2_CTRL    0x82
+#define EM2874_R83_GPIO_P3_CTRL    0x83
+#define EM2874_R84_GPIO_P0_STATE   0x84
+#define EM2874_R85_GPIO_P1_STATE   0x85
+#define EM2874_R86_GPIO_P2_STATE   0x86
+#define EM2874_R87_GPIO_P3_STATE   0x87
+
+/* em2874 IR config register (0x50) */
+#define EM2874_IR_NEC           0x00
+#define EM2874_IR_NEC_NO_PARITY 0x01
+#define EM2874_IR_RC5           0x04
+#define EM2874_IR_RC6_MODE_0    0x08
+#define EM2874_IR_RC6_MODE_6A   0x0b
+
+/* em2874 Transport Stream Enable Register (0x5f) */
+#define EM2874_TS1_CAPTURE_ENABLE ((unsigned char)BIT(0))
+#define EM2874_TS1_FILTER_ENABLE  ((unsigned char)BIT(1))
+#define EM2874_TS1_NULL_DISCARD   ((unsigned char)BIT(2))
+#define EM2874_TS2_CAPTURE_ENABLE ((unsigned char)BIT(4))
+#define EM2874_TS2_FILTER_ENABLE  ((unsigned char)BIT(5))
+#define EM2874_TS2_NULL_DISCARD   ((unsigned char)BIT(6))
+
+/* register settings */
+#define EM2800_AUDIO_SRC_TUNER  0x0d
+#define EM2800_AUDIO_SRC_LINE   0x0c
+#define EM28XX_AUDIO_SRC_TUNER	0xc0
+#define EM28XX_AUDIO_SRC_LINE	0x80
+
+/* FIXME: Need to be populated with the other chip ID's */
+enum em28xx_chip_id {
+	CHIP_ID_EM2800 = 7,
+	CHIP_ID_EM2710 = 17,
+	CHIP_ID_EM2820 = 18,	/* Also used by some em2710 */
+	CHIP_ID_EM2840 = 20,
+	CHIP_ID_EM2750 = 33,
+	CHIP_ID_EM2860 = 34,
+	CHIP_ID_EM2870 = 35,
+	CHIP_ID_EM2883 = 36,
+	CHIP_ID_EM2765 = 54,
+	CHIP_ID_EM2874 = 65,
+	CHIP_ID_EM2884 = 68,
+	CHIP_ID_EM28174 = 113,
+	CHIP_ID_EM28178 = 114,
+};
+
+/*
+ * Registers used by em202
+ */
+
+/* EMP202 vendor registers */
+#define EM202_EXT_MODEM_CTRL     0x3e
+#define EM202_GPIO_CONF          0x4c
+#define EM202_GPIO_POLARITY      0x4e
+#define EM202_GPIO_STICKY        0x50
+#define EM202_GPIO_MASK          0x52
+#define EM202_GPIO_STATUS        0x54
+#define EM202_SPDIF_OUT_SEL      0x6a
+#define EM202_ANTIPOP            0x72
+#define EM202_EAPD_GPIO_ACCESS   0x74
diff --git a/drivers/media/usb/em28xx/em28xx-v4l.h b/drivers/media/usb/em28xx/em28xx-v4l.h
new file mode 100644
index 0000000..6216cdd
--- /dev/null
+++ b/drivers/media/usb/em28xx/em28xx-v4l.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * em28xx-video.c - driver for Empia EM2800/EM2820/2840 USB
+ *		    video capture devices
+ *
+ * Copyright (C) 2013-2014 Mauro Carvalho Chehab <mchehab+samsung@kernel.org>
+ *
+ * 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 version 2 of the License.
+ *
+ * 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.
+ */
+
+int em28xx_start_analog_streaming(struct vb2_queue *vq, unsigned int count);
+void em28xx_stop_vbi_streaming(struct vb2_queue *vq);
+extern const struct vb2_ops em28xx_vbi_qops;
diff --git a/drivers/media/usb/em28xx/em28xx-vbi.c b/drivers/media/usb/em28xx/em28xx-vbi.c
new file mode 100644
index 0000000..63c4836
--- /dev/null
+++ b/drivers/media/usb/em28xx/em28xx-vbi.c
@@ -0,0 +1,99 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// em28xx-vbi.c - VBI driver for em28xx
+//
+// Copyright (C) 2009 Devin Heitmueller <dheitmueller@kernellabs.com>
+//
+// This work was sponsored by EyeMagnet Limited.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include "em28xx.h"
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/hardirq.h>
+#include <linux/init.h>
+#include <linux/usb.h>
+
+#include "em28xx-v4l.h"
+
+/* ------------------------------------------------------------------ */
+
+static int vbi_queue_setup(struct vb2_queue *vq,
+			   unsigned int *nbuffers, unsigned int *nplanes,
+			   unsigned int sizes[], struct device *alloc_devs[])
+{
+	struct em28xx *dev = vb2_get_drv_priv(vq);
+	struct em28xx_v4l2 *v4l2 = dev->v4l2;
+	unsigned long size = v4l2->vbi_width * v4l2->vbi_height * 2;
+
+	if (*nbuffers < 2)
+		*nbuffers = 2;
+
+	if (*nplanes) {
+		if (sizes[0] < size)
+			return -EINVAL;
+		size = sizes[0];
+	}
+
+	*nplanes = 1;
+	sizes[0] = size;
+
+	return 0;
+}
+
+static int vbi_buffer_prepare(struct vb2_buffer *vb)
+{
+	struct em28xx        *dev  = vb2_get_drv_priv(vb->vb2_queue);
+	struct em28xx_v4l2   *v4l2 = dev->v4l2;
+	unsigned long        size;
+
+	size = v4l2->vbi_width * v4l2->vbi_height * 2;
+
+	if (vb2_plane_size(vb, 0) < size) {
+		dev_info(&dev->intf->dev,
+			 "%s data will not fit into plane (%lu < %lu)\n",
+			 __func__, vb2_plane_size(vb, 0), size);
+		return -EINVAL;
+	}
+	vb2_set_plane_payload(vb, 0, size);
+
+	return 0;
+}
+
+static void
+vbi_buffer_queue(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct em28xx *dev = vb2_get_drv_priv(vb->vb2_queue);
+	struct em28xx_buffer *buf =
+		container_of(vbuf, struct em28xx_buffer, vb);
+	struct em28xx_dmaqueue *vbiq = &dev->vbiq;
+	unsigned long flags = 0;
+
+	buf->mem = vb2_plane_vaddr(vb, 0);
+	buf->length = vb2_plane_size(vb, 0);
+
+	spin_lock_irqsave(&dev->slock, flags);
+	list_add_tail(&buf->list, &vbiq->active);
+	spin_unlock_irqrestore(&dev->slock, flags);
+}
+
+const struct vb2_ops em28xx_vbi_qops = {
+	.queue_setup    = vbi_queue_setup,
+	.buf_prepare    = vbi_buffer_prepare,
+	.buf_queue      = vbi_buffer_queue,
+	.start_streaming = em28xx_start_analog_streaming,
+	.stop_streaming = em28xx_stop_vbi_streaming,
+	.wait_prepare   = vb2_ops_wait_prepare,
+	.wait_finish    = vb2_ops_wait_finish,
+};
diff --git a/drivers/media/usb/em28xx/em28xx-video.c b/drivers/media/usb/em28xx/em28xx-video.c
new file mode 100644
index 0000000..3bf98ac
--- /dev/null
+++ b/drivers/media/usb/em28xx/em28xx-video.c
@@ -0,0 +1,2945 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// em28xx-video.c - driver for Empia EM2800/EM2820/2840 USB
+//		    video capture devices
+//
+// Copyright (C) 2005 Ludovico Cavedon <cavedon@sssup.it>
+//		      Markus Rechberger <mrechberger@gmail.com>
+//		      Mauro Carvalho Chehab <mchehab@kernel.org>
+//		      Sascha Sommer <saschasommer@freenet.de>
+// Copyright (C) 2012 Frank Schäfer <fschaefer.oss@googlemail.com>
+//
+//	Some parts based on SN9C10x PC Camera Controllers GPL driver made
+//		by Luca Risolia <luca.risolia@studio.unibo.it>
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+#include "em28xx.h"
+
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/bitmap.h>
+#include <linux/usb.h>
+#include <linux/i2c.h>
+#include <linux/mm.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+
+#include "em28xx-v4l.h"
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-event.h>
+#include <media/drv-intf/msp3400.h>
+#include <media/tuner.h>
+
+#define DRIVER_AUTHOR "Ludovico Cavedon <cavedon@sssup.it>, " \
+		      "Markus Rechberger <mrechberger@gmail.com>, " \
+		      "Mauro Carvalho Chehab <mchehab@kernel.org>, " \
+		      "Sascha Sommer <saschasommer@freenet.de>"
+
+static unsigned int isoc_debug;
+module_param(isoc_debug, int, 0644);
+MODULE_PARM_DESC(isoc_debug, "enable debug messages [isoc transfers]");
+
+static unsigned int disable_vbi;
+module_param(disable_vbi, int, 0644);
+MODULE_PARM_DESC(disable_vbi, "disable vbi support");
+
+static int alt;
+module_param(alt, int, 0644);
+MODULE_PARM_DESC(alt, "alternate setting to use for video endpoint");
+
+#define em28xx_videodbg(fmt, arg...) do {				\
+	if (video_debug)						\
+		dev_printk(KERN_DEBUG, &dev->intf->dev,			\
+			   "video: %s: " fmt, __func__, ## arg);	\
+} while (0)
+
+#define em28xx_isocdbg(fmt, arg...) do {\
+	if (isoc_debug) \
+		dev_printk(KERN_DEBUG, &dev->intf->dev,			\
+			   "isoc: %s: " fmt, __func__, ## arg);		\
+} while (0)
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC " - v4l2 interface");
+MODULE_LICENSE("GPL v2");
+MODULE_VERSION(EM28XX_VERSION);
+
+#define EM25XX_FRMDATAHDR_BYTE1			0x02
+#define EM25XX_FRMDATAHDR_BYTE2_STILL_IMAGE	0x20
+#define EM25XX_FRMDATAHDR_BYTE2_FRAME_END	0x02
+#define EM25XX_FRMDATAHDR_BYTE2_FRAME_ID	0x01
+#define EM25XX_FRMDATAHDR_BYTE2_MASK	(EM25XX_FRMDATAHDR_BYTE2_STILL_IMAGE | \
+					 EM25XX_FRMDATAHDR_BYTE2_FRAME_END |   \
+					 EM25XX_FRMDATAHDR_BYTE2_FRAME_ID)
+
+static unsigned int video_nr[] = {[0 ... (EM28XX_MAXBOARDS - 1)] = -1U };
+static unsigned int vbi_nr[]   = {[0 ... (EM28XX_MAXBOARDS - 1)] = -1U };
+static unsigned int radio_nr[] = {[0 ... (EM28XX_MAXBOARDS - 1)] = -1U };
+
+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_PARM_DESC(video_nr, "video device numbers");
+MODULE_PARM_DESC(vbi_nr,   "vbi device numbers");
+MODULE_PARM_DESC(radio_nr, "radio device numbers");
+
+static unsigned int video_debug;
+module_param(video_debug, int, 0644);
+MODULE_PARM_DESC(video_debug, "enable debug messages [video]");
+
+/* supported video standards */
+static struct em28xx_fmt format[] = {
+	{
+		.name     = "16 bpp YUY2, 4:2:2, packed",
+		.fourcc   = V4L2_PIX_FMT_YUYV,
+		.depth    = 16,
+		.reg	  = EM28XX_OUTFMT_YUV422_Y0UY1V,
+	}, {
+		.name     = "16 bpp RGB 565, LE",
+		.fourcc   = V4L2_PIX_FMT_RGB565,
+		.depth    = 16,
+		.reg      = EM28XX_OUTFMT_RGB_16_656,
+	}, {
+		.name     = "8 bpp Bayer RGRG..GBGB",
+		.fourcc   = V4L2_PIX_FMT_SRGGB8,
+		.depth    = 8,
+		.reg      = EM28XX_OUTFMT_RGB_8_RGRG,
+	}, {
+		.name     = "8 bpp Bayer BGBG..GRGR",
+		.fourcc   = V4L2_PIX_FMT_SBGGR8,
+		.depth    = 8,
+		.reg      = EM28XX_OUTFMT_RGB_8_BGBG,
+	}, {
+		.name     = "8 bpp Bayer GRGR..BGBG",
+		.fourcc   = V4L2_PIX_FMT_SGRBG8,
+		.depth    = 8,
+		.reg      = EM28XX_OUTFMT_RGB_8_GRGR,
+	}, {
+		.name     = "8 bpp Bayer GBGB..RGRG",
+		.fourcc   = V4L2_PIX_FMT_SGBRG8,
+		.depth    = 8,
+		.reg      = EM28XX_OUTFMT_RGB_8_GBGB,
+	}, {
+		.name     = "12 bpp YUV411",
+		.fourcc   = V4L2_PIX_FMT_YUV411P,
+		.depth    = 12,
+		.reg      = EM28XX_OUTFMT_YUV411,
+	},
+};
+
+/*FIXME: maxw should be dependent of alt mode */
+static inline unsigned int norm_maxw(struct em28xx *dev)
+{
+	struct em28xx_v4l2 *v4l2 = dev->v4l2;
+
+	if (dev->is_webcam)
+		return v4l2->sensor_xres;
+
+	if (dev->board.max_range_640_480)
+		return 640;
+
+	return 720;
+}
+
+static inline unsigned int norm_maxh(struct em28xx *dev)
+{
+	struct em28xx_v4l2 *v4l2 = dev->v4l2;
+
+	if (dev->is_webcam)
+		return v4l2->sensor_yres;
+
+	if (dev->board.max_range_640_480)
+		return 480;
+
+	return (v4l2->norm & V4L2_STD_625_50) ? 576 : 480;
+}
+
+static int em28xx_vbi_supported(struct em28xx *dev)
+{
+	/* Modprobe option to manually disable */
+	if (disable_vbi == 1)
+		return 0;
+
+	if (dev->is_webcam)
+		return 0;
+
+	/* FIXME: check subdevices for VBI support */
+
+	if (dev->chip_id == CHIP_ID_EM2860 ||
+	    dev->chip_id == CHIP_ID_EM2883)
+		return 1;
+
+	/* Version of em28xx that does not support VBI */
+	return 0;
+}
+
+/*
+ * em28xx_wake_i2c()
+ * configure i2c attached devices
+ */
+static void em28xx_wake_i2c(struct em28xx *dev)
+{
+	struct v4l2_device *v4l2_dev = &dev->v4l2->v4l2_dev;
+
+	v4l2_device_call_all(v4l2_dev, 0, core,  reset, 0);
+	v4l2_device_call_all(v4l2_dev, 0, video, s_routing,
+			     INPUT(dev->ctl_input)->vmux, 0, 0);
+}
+
+static int em28xx_colorlevels_set_default(struct em28xx *dev)
+{
+	em28xx_write_reg(dev, EM28XX_R20_YGAIN, CONTRAST_DEFAULT);
+	em28xx_write_reg(dev, EM28XX_R21_YOFFSET, BRIGHTNESS_DEFAULT);
+	em28xx_write_reg(dev, EM28XX_R22_UVGAIN, SATURATION_DEFAULT);
+	em28xx_write_reg(dev, EM28XX_R23_UOFFSET, BLUE_BALANCE_DEFAULT);
+	em28xx_write_reg(dev, EM28XX_R24_VOFFSET, RED_BALANCE_DEFAULT);
+	em28xx_write_reg(dev, EM28XX_R25_SHARPNESS, SHARPNESS_DEFAULT);
+
+	em28xx_write_reg(dev, EM28XX_R14_GAMMA, 0x20);
+	em28xx_write_reg(dev, EM28XX_R15_RGAIN, 0x20);
+	em28xx_write_reg(dev, EM28XX_R16_GGAIN, 0x20);
+	em28xx_write_reg(dev, EM28XX_R17_BGAIN, 0x20);
+	em28xx_write_reg(dev, EM28XX_R18_ROFFSET, 0x00);
+	em28xx_write_reg(dev, EM28XX_R19_GOFFSET, 0x00);
+	return em28xx_write_reg(dev, EM28XX_R1A_BOFFSET, 0x00);
+}
+
+static int em28xx_set_outfmt(struct em28xx *dev)
+{
+	int ret;
+	u8 fmt, vinctrl;
+	struct em28xx_v4l2 *v4l2 = dev->v4l2;
+
+	fmt = v4l2->format->reg;
+	if (!dev->is_em25xx)
+		fmt |= 0x20;
+	/*
+	 * NOTE: it's not clear if this is really needed !
+	 * The datasheets say bit 5 is a reserved bit and devices seem to work
+	 * fine without it. But the Windows driver sets it for em2710/50+em28xx
+	 * devices and we've always been setting it, too.
+	 *
+	 * em2765 (em25xx, em276x/7x/8x) devices do NOT work with this bit set,
+	 * it's likely used for an additional (compressed ?) format there.
+	 */
+	ret = em28xx_write_reg(dev, EM28XX_R27_OUTFMT, fmt);
+	if (ret < 0)
+		return ret;
+
+	ret = em28xx_write_reg(dev, EM28XX_R10_VINMODE, v4l2->vinmode);
+	if (ret < 0)
+		return ret;
+
+	vinctrl = v4l2->vinctl;
+	if (em28xx_vbi_supported(dev) == 1) {
+		vinctrl |= EM28XX_VINCTRL_VBI_RAW;
+		em28xx_write_reg(dev, EM28XX_R34_VBI_START_H, 0x00);
+		em28xx_write_reg(dev, EM28XX_R36_VBI_WIDTH,
+				 v4l2->vbi_width / 4);
+		em28xx_write_reg(dev, EM28XX_R37_VBI_HEIGHT, v4l2->vbi_height);
+		if (v4l2->norm & V4L2_STD_525_60) {
+			/* NTSC */
+			em28xx_write_reg(dev, EM28XX_R35_VBI_START_V, 0x09);
+		} else if (v4l2->norm & V4L2_STD_625_50) {
+			/* PAL */
+			em28xx_write_reg(dev, EM28XX_R35_VBI_START_V, 0x07);
+		}
+	}
+
+	return em28xx_write_reg(dev, EM28XX_R11_VINCTRL, vinctrl);
+}
+
+static int em28xx_accumulator_set(struct em28xx *dev, u8 xmin, u8 xmax,
+				  u8 ymin, u8 ymax)
+{
+	em28xx_videodbg("em28xx Scale: (%d,%d)-(%d,%d)\n",
+			xmin, ymin, xmax, ymax);
+
+	em28xx_write_regs(dev, EM28XX_R28_XMIN, &xmin, 1);
+	em28xx_write_regs(dev, EM28XX_R29_XMAX, &xmax, 1);
+	em28xx_write_regs(dev, EM28XX_R2A_YMIN, &ymin, 1);
+	return em28xx_write_regs(dev, EM28XX_R2B_YMAX, &ymax, 1);
+}
+
+static void em28xx_capture_area_set(struct em28xx *dev, u8 hstart, u8 vstart,
+				    u16 width, u16 height)
+{
+	u8 cwidth = width >> 2;
+	u8 cheight = height >> 2;
+	u8 overflow = (height >> 9 & 0x02) | (width >> 10 & 0x01);
+	/* NOTE: size limit: 2047x1023 = 2MPix */
+
+	em28xx_videodbg("capture area set to (%d,%d): %dx%d\n",
+			hstart, vstart,
+		       ((overflow & 2) << 9 | cwidth << 2),
+		       ((overflow & 1) << 10 | cheight << 2));
+
+	em28xx_write_regs(dev, EM28XX_R1C_HSTART, &hstart, 1);
+	em28xx_write_regs(dev, EM28XX_R1D_VSTART, &vstart, 1);
+	em28xx_write_regs(dev, EM28XX_R1E_CWIDTH, &cwidth, 1);
+	em28xx_write_regs(dev, EM28XX_R1F_CHEIGHT, &cheight, 1);
+	em28xx_write_regs(dev, EM28XX_R1B_OFLOW, &overflow, 1);
+
+	/* FIXME: function/meaning of these registers ? */
+	/* FIXME: align width+height to multiples of 4 ?! */
+	if (dev->is_em25xx) {
+		em28xx_write_reg(dev, 0x34, width >> 4);
+		em28xx_write_reg(dev, 0x35, height >> 4);
+	}
+}
+
+static int em28xx_scaler_set(struct em28xx *dev, u16 h, u16 v)
+{
+	u8 mode = 0x00;
+	/* the em2800 scaler only supports scaling down to 50% */
+
+	if (dev->board.is_em2800) {
+		mode = (v ? 0x20 : 0x00) | (h ? 0x10 : 0x00);
+	} else {
+		u8 buf[2];
+
+		buf[0] = h;
+		buf[1] = h >> 8;
+		em28xx_write_regs(dev, EM28XX_R30_HSCALELOW, (char *)buf, 2);
+
+		buf[0] = v;
+		buf[1] = v >> 8;
+		em28xx_write_regs(dev, EM28XX_R32_VSCALELOW, (char *)buf, 2);
+		/*
+		 * it seems that both H and V scalers must be active
+		 * to work correctly
+		 */
+		mode = (h || v) ? 0x30 : 0x00;
+	}
+	return em28xx_write_reg(dev, EM28XX_R26_COMPR, mode);
+}
+
+/* FIXME: this only function read values from dev */
+static int em28xx_resolution_set(struct em28xx *dev)
+{
+	struct em28xx_v4l2 *v4l2 = dev->v4l2;
+	int width = norm_maxw(dev);
+	int height = norm_maxh(dev);
+
+	/* Properly setup VBI */
+	v4l2->vbi_width = 720;
+	if (v4l2->norm & V4L2_STD_525_60)
+		v4l2->vbi_height = 12;
+	else
+		v4l2->vbi_height = 18;
+
+	em28xx_set_outfmt(dev);
+
+	em28xx_accumulator_set(dev, 1, (width - 4) >> 2, 1, (height - 4) >> 2);
+
+	/*
+	 * If we don't set the start position to 2 in VBI mode, we end up
+	 * with line 20/21 being YUYV encoded instead of being in 8-bit
+	 * greyscale.  The core of the issue is that line 21 (and line 23 for
+	 * PAL WSS) are inside of active video region, and as a result they
+	 * get the pixelformatting associated with that area.  So by cropping
+	 * it out, we end up with the same format as the rest of the VBI
+	 * region
+	 */
+	if (em28xx_vbi_supported(dev) == 1)
+		em28xx_capture_area_set(dev, 0, 2, width, height);
+	else
+		em28xx_capture_area_set(dev, 0, 0, width, height);
+
+	return em28xx_scaler_set(dev, v4l2->hscale, v4l2->vscale);
+}
+
+/* Set USB alternate setting for analog video */
+static int em28xx_set_alternate(struct em28xx *dev)
+{
+	struct em28xx_v4l2 *v4l2 = dev->v4l2;
+	struct usb_device *udev = interface_to_usbdev(dev->intf);
+	int err;
+	int i;
+	unsigned int min_pkt_size = v4l2->width * 2 + 4;
+
+	/*
+	 * NOTE: for isoc transfers, only alt settings > 0 are allowed
+	 * bulk transfers seem to work only with alt=0 !
+	 */
+	dev->alt = 0;
+	if (alt > 0 && alt < dev->num_alt) {
+		em28xx_videodbg("alternate forced to %d\n", dev->alt);
+		dev->alt = alt;
+		goto set_alt;
+	}
+	if (dev->analog_xfer_bulk)
+		goto set_alt;
+
+	/*
+	 * When image size is bigger than a certain value,
+	 * the frame size should be increased, otherwise, only
+	 * green screen will be received.
+	 */
+	if (v4l2->width * 2 * v4l2->height > 720 * 240 * 2)
+		min_pkt_size *= 2;
+
+	for (i = 0; i < dev->num_alt; i++) {
+		/* stop when the selected alt setting offers enough bandwidth */
+		if (dev->alt_max_pkt_size_isoc[i] >= min_pkt_size) {
+			dev->alt = i;
+			break;
+		/*
+		 * otherwise make sure that we end up with the maximum
+		 * bandwidth because the min_pkt_size equation might be wrong.
+		 *
+		 */
+		} else if (dev->alt_max_pkt_size_isoc[i] >
+			   dev->alt_max_pkt_size_isoc[dev->alt])
+			dev->alt = i;
+	}
+
+set_alt:
+	/*
+	 * NOTE: for bulk transfers, we need to call usb_set_interface()
+	 * even if the previous settings were the same. Otherwise streaming
+	 * fails with all urbs having status = -EOVERFLOW !
+	 */
+	if (dev->analog_xfer_bulk) {
+		dev->max_pkt_size = 512; /* USB 2.0 spec */
+		dev->packet_multiplier = EM28XX_BULK_PACKET_MULTIPLIER;
+	} else { /* isoc */
+		em28xx_videodbg("minimum isoc packet size: %u (alt=%d)\n",
+				min_pkt_size, dev->alt);
+		dev->max_pkt_size =
+				  dev->alt_max_pkt_size_isoc[dev->alt];
+		dev->packet_multiplier = EM28XX_NUM_ISOC_PACKETS;
+	}
+	em28xx_videodbg("setting alternate %d with wMaxPacketSize=%u\n",
+			dev->alt, dev->max_pkt_size);
+	err = usb_set_interface(udev, dev->ifnum, dev->alt);
+	if (err < 0) {
+		dev_err(&dev->intf->dev,
+			"cannot change alternate number to %d (error=%i)\n",
+			dev->alt, err);
+		return err;
+	}
+	return 0;
+}
+
+/*
+ * DMA and thread functions
+ */
+
+/*
+ * Finish the current buffer
+ */
+static inline void finish_buffer(struct em28xx *dev,
+				 struct em28xx_buffer *buf)
+{
+	em28xx_isocdbg("[%p/%d] wakeup\n", buf, buf->top_field);
+
+	buf->vb.sequence = dev->v4l2->field_count++;
+	if (dev->v4l2->progressive)
+		buf->vb.field = V4L2_FIELD_NONE;
+	else
+		buf->vb.field = V4L2_FIELD_INTERLACED;
+	buf->vb.vb2_buf.timestamp = ktime_get_ns();
+
+	vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
+}
+
+/*
+ * Copy picture data from USB buffer to videobuf buffer
+ */
+static void em28xx_copy_video(struct em28xx *dev,
+			      struct em28xx_buffer *buf,
+			      unsigned char *usb_buf,
+			      unsigned long len)
+{
+	struct em28xx_v4l2 *v4l2 = dev->v4l2;
+	void *fieldstart, *startwrite, *startread;
+	int  linesdone, currlinedone, offset, lencopy, remain;
+	int bytesperline = v4l2->width << 1;
+
+	if (buf->pos + len > buf->length)
+		len = buf->length - buf->pos;
+
+	startread = usb_buf;
+	remain = len;
+
+	if (v4l2->progressive || buf->top_field)
+		fieldstart = buf->vb_buf;
+	else /* interlaced mode, even nr. of lines */
+		fieldstart = buf->vb_buf + bytesperline;
+
+	linesdone = buf->pos / bytesperline;
+	currlinedone = buf->pos % bytesperline;
+
+	if (v4l2->progressive)
+		offset = linesdone * bytesperline + currlinedone;
+	else
+		offset = linesdone * bytesperline * 2 + currlinedone;
+
+	startwrite = fieldstart + offset;
+	lencopy = bytesperline - currlinedone;
+	lencopy = lencopy > remain ? remain : lencopy;
+
+	if ((char *)startwrite + lencopy > (char *)buf->vb_buf + buf->length) {
+		em28xx_isocdbg("Overflow of %zu bytes past buffer end (1)\n",
+			       ((char *)startwrite + lencopy) -
+			      ((char *)buf->vb_buf + buf->length));
+		remain = (char *)buf->vb_buf + buf->length -
+			 (char *)startwrite;
+		lencopy = remain;
+	}
+	if (lencopy <= 0)
+		return;
+	memcpy(startwrite, startread, lencopy);
+
+	remain -= lencopy;
+
+	while (remain > 0) {
+		if (v4l2->progressive)
+			startwrite += lencopy;
+		else
+			startwrite += lencopy + bytesperline;
+		startread += lencopy;
+		if (bytesperline > remain)
+			lencopy = remain;
+		else
+			lencopy = bytesperline;
+
+		if ((char *)startwrite + lencopy > (char *)buf->vb_buf +
+		    buf->length) {
+			em28xx_isocdbg("Overflow of %zu bytes past buffer end(2)\n",
+				       ((char *)startwrite + lencopy) -
+				       ((char *)buf->vb_buf + buf->length));
+			remain = (char *)buf->vb_buf + buf->length -
+				 (char *)startwrite;
+			lencopy = remain;
+		}
+		if (lencopy <= 0)
+			break;
+
+		memcpy(startwrite, startread, lencopy);
+
+		remain -= lencopy;
+	}
+
+	buf->pos += len;
+}
+
+/*
+ * Copy VBI data from USB buffer to videobuf buffer
+ */
+static void em28xx_copy_vbi(struct em28xx *dev,
+			    struct em28xx_buffer *buf,
+			    unsigned char *usb_buf,
+			    unsigned long len)
+{
+	unsigned int offset;
+
+	if (buf->pos + len > buf->length)
+		len = buf->length - buf->pos;
+
+	offset = buf->pos;
+	/* Make sure the bottom field populates the second half of the frame */
+	if (buf->top_field == 0)
+		offset += dev->v4l2->vbi_width * dev->v4l2->vbi_height;
+
+	memcpy(buf->vb_buf + offset, usb_buf, len);
+	buf->pos += len;
+}
+
+static inline void print_err_status(struct em28xx *dev,
+				    int packet, int status)
+{
+	char *errmsg = "Unknown";
+
+	switch (status) {
+	case -ENOENT:
+		errmsg = "unlinked synchronously";
+		break;
+	case -ECONNRESET:
+		errmsg = "unlinked asynchronously";
+		break;
+	case -ENOSR:
+		errmsg = "Buffer error (overrun)";
+		break;
+	case -EPIPE:
+		errmsg = "Stalled (device not responding)";
+		break;
+	case -EOVERFLOW:
+		errmsg = "Babble (bad cable?)";
+		break;
+	case -EPROTO:
+		errmsg = "Bit-stuff error (bad cable?)";
+		break;
+	case -EILSEQ:
+		errmsg = "CRC/Timeout (could be anything)";
+		break;
+	case -ETIME:
+		errmsg = "Device does not respond";
+		break;
+	}
+	if (packet < 0) {
+		em28xx_isocdbg("URB status %d [%s].\n",	status, errmsg);
+	} else {
+		em28xx_isocdbg("URB packet %d, status %d [%s].\n",
+			       packet, status, errmsg);
+	}
+}
+
+/*
+ * get the next available buffer from dma queue
+ */
+static inline struct em28xx_buffer *get_next_buf(struct em28xx *dev,
+						 struct em28xx_dmaqueue *dma_q)
+{
+	struct em28xx_buffer *buf;
+
+	if (list_empty(&dma_q->active)) {
+		em28xx_isocdbg("No active queue to serve\n");
+		return NULL;
+	}
+
+	/* Get the next buffer */
+	buf = list_entry(dma_q->active.next, struct em28xx_buffer, list);
+	/* Cleans up buffer - Useful for testing for frame/URB loss */
+	list_del(&buf->list);
+	buf->pos = 0;
+	buf->vb_buf = buf->mem;
+
+	return buf;
+}
+
+/*
+ * Finish the current buffer if completed and prepare for the next field
+ */
+static struct em28xx_buffer *
+finish_field_prepare_next(struct em28xx *dev,
+			  struct em28xx_buffer *buf,
+			  struct em28xx_dmaqueue *dma_q)
+{
+	struct em28xx_v4l2 *v4l2 = dev->v4l2;
+
+	if (v4l2->progressive || v4l2->top_field) { /* Brand new frame */
+		if (buf)
+			finish_buffer(dev, buf);
+		buf = get_next_buf(dev, dma_q);
+	}
+	if (buf) {
+		buf->top_field = v4l2->top_field;
+		buf->pos = 0;
+	}
+
+	return buf;
+}
+
+/*
+ * Process data packet according to the em2710/em2750/em28xx frame data format
+ */
+static inline void process_frame_data_em28xx(struct em28xx *dev,
+					     unsigned char *data_pkt,
+					     unsigned int  data_len)
+{
+	struct em28xx_v4l2      *v4l2 = dev->v4l2;
+	struct em28xx_buffer    *buf = dev->usb_ctl.vid_buf;
+	struct em28xx_buffer    *vbi_buf = dev->usb_ctl.vbi_buf;
+	struct em28xx_dmaqueue  *dma_q = &dev->vidq;
+	struct em28xx_dmaqueue  *vbi_dma_q = &dev->vbiq;
+
+	/*
+	 * capture type 0 = vbi start
+	 * capture type 1 = vbi in progress
+	 * capture type 2 = video start
+	 * capture type 3 = video in progress
+	 */
+	if (data_len >= 4) {
+		/*
+		 * NOTE: Headers are always 4 bytes and
+		 * never split across packets
+		 */
+		if (data_pkt[0] == 0x88 && data_pkt[1] == 0x88 &&
+		    data_pkt[2] == 0x88 && data_pkt[3] == 0x88) {
+			/* Continuation */
+			data_pkt += 4;
+			data_len -= 4;
+		} else if (data_pkt[0] == 0x33 && data_pkt[1] == 0x95) {
+			/* Field start (VBI mode) */
+			v4l2->capture_type = 0;
+			v4l2->vbi_read = 0;
+			em28xx_isocdbg("VBI START HEADER !!!\n");
+			v4l2->top_field = !(data_pkt[2] & 1);
+			data_pkt += 4;
+			data_len -= 4;
+		} else if (data_pkt[0] == 0x22 && data_pkt[1] == 0x5a) {
+			/* Field start (VBI disabled) */
+			v4l2->capture_type = 2;
+			em28xx_isocdbg("VIDEO START HEADER !!!\n");
+			v4l2->top_field = !(data_pkt[2] & 1);
+			data_pkt += 4;
+			data_len -= 4;
+		}
+	}
+	/*
+	 * NOTE: With bulk transfers, intermediate data packets
+	 * have no continuation header
+	 */
+
+	if (v4l2->capture_type == 0) {
+		vbi_buf = finish_field_prepare_next(dev, vbi_buf, vbi_dma_q);
+		dev->usb_ctl.vbi_buf = vbi_buf;
+		v4l2->capture_type = 1;
+	}
+
+	if (v4l2->capture_type == 1) {
+		int vbi_size = v4l2->vbi_width * v4l2->vbi_height;
+		int vbi_data_len = ((v4l2->vbi_read + data_len) > vbi_size) ?
+				   (vbi_size - v4l2->vbi_read) : data_len;
+
+		/* Copy VBI data */
+		if (vbi_buf)
+			em28xx_copy_vbi(dev, vbi_buf, data_pkt, vbi_data_len);
+		v4l2->vbi_read += vbi_data_len;
+
+		if (vbi_data_len < data_len) {
+			/* Continue with copying video data */
+			v4l2->capture_type = 2;
+			data_pkt += vbi_data_len;
+			data_len -= vbi_data_len;
+		}
+	}
+
+	if (v4l2->capture_type == 2) {
+		buf = finish_field_prepare_next(dev, buf, dma_q);
+		dev->usb_ctl.vid_buf = buf;
+		v4l2->capture_type = 3;
+	}
+
+	if (v4l2->capture_type == 3 && buf && data_len > 0)
+		em28xx_copy_video(dev, buf, data_pkt, data_len);
+}
+
+/*
+ * Process data packet according to the em25xx/em276x/7x/8x frame data format
+ */
+static inline void process_frame_data_em25xx(struct em28xx *dev,
+					     unsigned char *data_pkt,
+					     unsigned int  data_len)
+{
+	struct em28xx_buffer    *buf = dev->usb_ctl.vid_buf;
+	struct em28xx_dmaqueue  *dmaq = &dev->vidq;
+	struct em28xx_v4l2      *v4l2 = dev->v4l2;
+	bool frame_end = false;
+
+	/* Check for header */
+	/*
+	 * NOTE: at least with bulk transfers, only the first packet
+	 * has a header and has always set the FRAME_END bit
+	 */
+	if (data_len >= 2) {	/* em25xx header is only 2 bytes long */
+		if ((data_pkt[0] == EM25XX_FRMDATAHDR_BYTE1) &&
+		    ((data_pkt[1] & ~EM25XX_FRMDATAHDR_BYTE2_MASK) == 0x00)) {
+			v4l2->top_field = !(data_pkt[1] &
+					   EM25XX_FRMDATAHDR_BYTE2_FRAME_ID);
+			frame_end = data_pkt[1] &
+				    EM25XX_FRMDATAHDR_BYTE2_FRAME_END;
+			data_pkt += 2;
+			data_len -= 2;
+		}
+
+		/* Finish field and prepare next (BULK only) */
+		if (dev->analog_xfer_bulk && frame_end) {
+			buf = finish_field_prepare_next(dev, buf, dmaq);
+			dev->usb_ctl.vid_buf = buf;
+		}
+		/*
+		 * NOTE: in ISOC mode when a new frame starts and buf==NULL,
+		 * we COULD already prepare a buffer here to avoid skipping the
+		 * first frame.
+		 */
+	}
+
+	/* Copy data */
+	if (buf && data_len > 0)
+		em28xx_copy_video(dev, buf, data_pkt, data_len);
+
+	/* Finish frame (ISOC only) => avoids lag of 1 frame */
+	if (!dev->analog_xfer_bulk && frame_end) {
+		buf = finish_field_prepare_next(dev, buf, dmaq);
+		dev->usb_ctl.vid_buf = buf;
+	}
+
+	/*
+	 * NOTES:
+	 *
+	 * 1) Tested with USB bulk transfers only !
+	 * The wording in the datasheet suggests that isoc might work different.
+	 * The current code assumes that with isoc transfers each packet has a
+	 * header like with the other em28xx devices.
+	 *
+	 * 2) Support for interlaced mode is pure theory. It has not been
+	 * tested and it is unknown if these devices actually support it.
+	 */
+}
+
+/* Processes and copies the URB data content (video and VBI data) */
+static inline int em28xx_urb_data_copy(struct em28xx *dev, struct urb *urb)
+{
+	int xfer_bulk, num_packets, i;
+	unsigned char *usb_data_pkt;
+	unsigned int usb_data_len;
+
+	if (!dev)
+		return 0;
+
+	if (dev->disconnected)
+		return 0;
+
+	if (urb->status < 0)
+		print_err_status(dev, -1, urb->status);
+
+	xfer_bulk = usb_pipebulk(urb->pipe);
+
+	if (xfer_bulk) /* bulk */
+		num_packets = 1;
+	else /* isoc */
+		num_packets = urb->number_of_packets;
+
+	for (i = 0; i < num_packets; i++) {
+		if (xfer_bulk) { /* bulk */
+			usb_data_len = urb->actual_length;
+
+			usb_data_pkt = urb->transfer_buffer;
+		} else { /* isoc */
+			if (urb->iso_frame_desc[i].status < 0) {
+				print_err_status(dev, i,
+						 urb->iso_frame_desc[i].status);
+				if (urb->iso_frame_desc[i].status != -EPROTO)
+					continue;
+			}
+
+			usb_data_len = urb->iso_frame_desc[i].actual_length;
+			if (usb_data_len > dev->max_pkt_size) {
+				em28xx_isocdbg("packet bigger than packet size");
+				continue;
+			}
+
+			usb_data_pkt = urb->transfer_buffer +
+				       urb->iso_frame_desc[i].offset;
+		}
+
+		if (usb_data_len == 0) {
+			/* NOTE: happens very often with isoc transfers */
+			/* em28xx_usbdbg("packet %d is empty",i); - spammy */
+			continue;
+		}
+
+		if (dev->is_em25xx)
+			process_frame_data_em25xx(dev,
+						  usb_data_pkt, usb_data_len);
+		else
+			process_frame_data_em28xx(dev,
+						  usb_data_pkt, usb_data_len);
+	}
+	return 1;
+}
+
+static int get_resource(enum v4l2_buf_type f_type)
+{
+	switch (f_type) {
+	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+		return EM28XX_RESOURCE_VIDEO;
+	case V4L2_BUF_TYPE_VBI_CAPTURE:
+		return EM28XX_RESOURCE_VBI;
+	default:
+		WARN_ON(1);
+		return -1; /* Indicate that device is busy */
+	}
+}
+
+/* Usage lock check functions */
+static int res_get(struct em28xx *dev, enum v4l2_buf_type f_type)
+{
+	int res_type = get_resource(f_type);
+
+	/* is it free? */
+	if (dev->resources & res_type) {
+		/* no, someone else uses it */
+		return -EBUSY;
+	}
+
+	/* it's free, grab it */
+	dev->resources |= res_type;
+	em28xx_videodbg("res: get %d\n", res_type);
+	return 0;
+}
+
+static void res_free(struct em28xx *dev, enum v4l2_buf_type f_type)
+{
+	int res_type = get_resource(f_type);
+
+	dev->resources &= ~res_type;
+	em28xx_videodbg("res: put %d\n", res_type);
+}
+
+static void em28xx_v4l2_media_release(struct em28xx *dev)
+{
+#ifdef CONFIG_MEDIA_CONTROLLER
+	int i;
+
+	for (i = 0; i < MAX_EM28XX_INPUT; i++) {
+		if (!INPUT(i)->type)
+			return;
+		media_device_unregister_entity(&dev->input_ent[i]);
+	}
+#endif
+}
+
+/*
+ * Media Controller helper functions
+ */
+
+static int em28xx_enable_analog_tuner(struct em28xx *dev)
+{
+#ifdef CONFIG_MEDIA_CONTROLLER
+	struct media_device *mdev = dev->media_dev;
+	struct em28xx_v4l2 *v4l2 = dev->v4l2;
+	struct media_entity *source;
+	struct media_link *link, *found_link = NULL;
+	int ret, active_links = 0;
+
+	if (!mdev || !v4l2->decoder)
+		return 0;
+
+	/*
+	 * This will find the tuner that is connected into the decoder.
+	 * Technically, this is not 100% correct, as the device may be
+	 * using an analog input instead of the tuner. However, as we can't
+	 * do DVB streaming while the DMA engine is being used for V4L2,
+	 * this should be enough for the actual needs.
+	 */
+	list_for_each_entry(link, &v4l2->decoder->links, list) {
+		if (link->sink->entity == v4l2->decoder) {
+			found_link = link;
+			if (link->flags & MEDIA_LNK_FL_ENABLED)
+				active_links++;
+			break;
+		}
+	}
+
+	if (active_links == 1 || !found_link)
+		return 0;
+
+	source = found_link->source->entity;
+	list_for_each_entry(link, &source->links, list) {
+		struct media_entity *sink;
+		int flags = 0;
+
+		sink = link->sink->entity;
+
+		if (sink == v4l2->decoder)
+			flags = MEDIA_LNK_FL_ENABLED;
+
+		ret = media_entity_setup_link(link, flags);
+		if (ret) {
+			dev_err(&dev->intf->dev,
+				"Couldn't change link %s->%s to %s. Error %d\n",
+				source->name, sink->name,
+				flags ? "enabled" : "disabled",
+				ret);
+			return ret;
+		}
+
+		em28xx_videodbg("link %s->%s was %s\n",
+				source->name, sink->name,
+				flags ? "ENABLED" : "disabled");
+	}
+#endif
+	return 0;
+}
+
+static const char * const iname[] = {
+	[EM28XX_VMUX_COMPOSITE]  = "Composite",
+	[EM28XX_VMUX_SVIDEO]     = "S-Video",
+	[EM28XX_VMUX_TELEVISION] = "Television",
+	[EM28XX_RADIO]           = "Radio",
+};
+
+static void em28xx_v4l2_create_entities(struct em28xx *dev)
+{
+#if defined(CONFIG_MEDIA_CONTROLLER)
+	struct em28xx_v4l2 *v4l2 = dev->v4l2;
+	int ret, i;
+
+	/* Initialize Video, VBI and Radio pads */
+	v4l2->video_pad.flags = MEDIA_PAD_FL_SINK;
+	ret = media_entity_pads_init(&v4l2->vdev.entity, 1, &v4l2->video_pad);
+	if (ret < 0)
+		dev_err(&dev->intf->dev,
+			"failed to initialize video media entity!\n");
+
+	if (em28xx_vbi_supported(dev)) {
+		v4l2->vbi_pad.flags = MEDIA_PAD_FL_SINK;
+		ret = media_entity_pads_init(&v4l2->vbi_dev.entity, 1,
+					     &v4l2->vbi_pad);
+		if (ret < 0)
+			dev_err(&dev->intf->dev,
+				"failed to initialize vbi media entity!\n");
+	}
+
+	/* Webcams don't have input connectors */
+	if (dev->is_webcam)
+		return;
+
+	/* Create entities for each input connector */
+	for (i = 0; i < MAX_EM28XX_INPUT; i++) {
+		struct media_entity *ent = &dev->input_ent[i];
+
+		if (!INPUT(i)->type)
+			break;
+
+		ent->name = iname[INPUT(i)->type];
+		ent->flags = MEDIA_ENT_FL_CONNECTOR;
+		dev->input_pad[i].flags = MEDIA_PAD_FL_SOURCE;
+
+		switch (INPUT(i)->type) {
+		case EM28XX_VMUX_COMPOSITE:
+			ent->function = MEDIA_ENT_F_CONN_COMPOSITE;
+			break;
+		case EM28XX_VMUX_SVIDEO:
+			ent->function = MEDIA_ENT_F_CONN_SVIDEO;
+			break;
+		default: /* EM28XX_VMUX_TELEVISION or EM28XX_RADIO */
+			if (dev->tuner_type != TUNER_ABSENT)
+				ent->function = MEDIA_ENT_F_CONN_RF;
+			break;
+		}
+
+		ret = media_entity_pads_init(ent, 1, &dev->input_pad[i]);
+		if (ret < 0)
+			dev_err(&dev->intf->dev,
+				"failed to initialize input pad[%d]!\n", i);
+
+		ret = media_device_register_entity(dev->media_dev, ent);
+		if (ret < 0)
+			dev_err(&dev->intf->dev,
+				"failed to register input entity %d!\n", i);
+	}
+#endif
+}
+
+/*
+ * Videobuf2 operations
+ */
+
+static int queue_setup(struct vb2_queue *vq,
+		       unsigned int *nbuffers, unsigned int *nplanes,
+		       unsigned int sizes[], struct device *alloc_devs[])
+{
+	struct em28xx *dev = vb2_get_drv_priv(vq);
+	struct em28xx_v4l2 *v4l2 = dev->v4l2;
+	unsigned long size =
+		    (v4l2->width * v4l2->height * v4l2->format->depth + 7) >> 3;
+
+	if (*nplanes)
+		return sizes[0] < size ? -EINVAL : 0;
+	*nplanes = 1;
+	sizes[0] = size;
+
+	em28xx_enable_analog_tuner(dev);
+
+	return 0;
+}
+
+static int
+buffer_prepare(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct em28xx        *dev = vb2_get_drv_priv(vb->vb2_queue);
+	struct em28xx_v4l2   *v4l2 = dev->v4l2;
+	unsigned long size;
+
+	em28xx_videodbg("%s, field=%d\n", __func__, vbuf->field);
+
+	size = (v4l2->width * v4l2->height * v4l2->format->depth + 7) >> 3;
+
+	if (vb2_plane_size(vb, 0) < size) {
+		em28xx_videodbg("%s data will not fit into plane (%lu < %lu)\n",
+				__func__, vb2_plane_size(vb, 0), size);
+		return -EINVAL;
+	}
+	vb2_set_plane_payload(vb, 0, size);
+
+	return 0;
+}
+
+int em28xx_start_analog_streaming(struct vb2_queue *vq, unsigned int count)
+{
+	struct em28xx *dev = vb2_get_drv_priv(vq);
+	struct em28xx_v4l2 *v4l2 = dev->v4l2;
+	struct v4l2_frequency f;
+	struct v4l2_fh *owner;
+	int rc = 0;
+
+	em28xx_videodbg("%s\n", __func__);
+
+	dev->v4l2->field_count = 0;
+
+	/*
+	 * Make sure streaming is not already in progress for this type
+	 * of filehandle (e.g. video, vbi)
+	 */
+	rc = res_get(dev, vq->type);
+	if (rc)
+		return rc;
+
+	if (v4l2->streaming_users == 0) {
+		/* First active streaming user, so allocate all the URBs */
+
+		/* Allocate the USB bandwidth */
+		em28xx_set_alternate(dev);
+
+		/*
+		 * Needed, since GPIO might have disabled power of
+		 * some i2c device
+		 */
+		em28xx_wake_i2c(dev);
+
+		v4l2->capture_type = -1;
+		rc = em28xx_init_usb_xfer(dev, EM28XX_ANALOG_MODE,
+					  dev->analog_xfer_bulk,
+					  EM28XX_NUM_BUFS,
+					  dev->max_pkt_size,
+					  dev->packet_multiplier,
+					  em28xx_urb_data_copy);
+		if (rc < 0)
+			return rc;
+
+		/*
+		 * djh: it's not clear whether this code is still needed.  I'm
+		 * leaving it in here for now entirely out of concern for
+		 * backward compatibility (the old code did it)
+		 */
+
+		/* Ask tuner to go to analog or radio mode */
+		memset(&f, 0, sizeof(f));
+		f.frequency = v4l2->frequency;
+		owner = (struct v4l2_fh *)vq->owner;
+		if (owner && owner->vdev->vfl_type == VFL_TYPE_RADIO)
+			f.type = V4L2_TUNER_RADIO;
+		else
+			f.type = V4L2_TUNER_ANALOG_TV;
+		v4l2_device_call_all(&v4l2->v4l2_dev,
+				     0, tuner, s_frequency, &f);
+
+		/* Enable video stream at TV decoder */
+		v4l2_device_call_all(&v4l2->v4l2_dev, 0, video, s_stream, 1);
+	}
+
+	v4l2->streaming_users++;
+
+	return rc;
+}
+
+static void em28xx_stop_streaming(struct vb2_queue *vq)
+{
+	struct em28xx *dev = vb2_get_drv_priv(vq);
+	struct em28xx_v4l2 *v4l2 = dev->v4l2;
+	struct em28xx_dmaqueue *vidq = &dev->vidq;
+	unsigned long flags = 0;
+
+	em28xx_videodbg("%s\n", __func__);
+
+	res_free(dev, vq->type);
+
+	if (v4l2->streaming_users-- == 1) {
+		/* Disable video stream at TV decoder */
+		v4l2_device_call_all(&v4l2->v4l2_dev, 0, video, s_stream, 0);
+
+		/* Last active user, so shutdown all the URBS */
+		em28xx_uninit_usb_xfer(dev, EM28XX_ANALOG_MODE);
+	}
+
+	spin_lock_irqsave(&dev->slock, flags);
+	if (dev->usb_ctl.vid_buf) {
+		vb2_buffer_done(&dev->usb_ctl.vid_buf->vb.vb2_buf,
+				VB2_BUF_STATE_ERROR);
+		dev->usb_ctl.vid_buf = NULL;
+	}
+	while (!list_empty(&vidq->active)) {
+		struct em28xx_buffer *buf;
+
+		buf = list_entry(vidq->active.next, struct em28xx_buffer, list);
+		list_del(&buf->list);
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+	}
+	spin_unlock_irqrestore(&dev->slock, flags);
+}
+
+void em28xx_stop_vbi_streaming(struct vb2_queue *vq)
+{
+	struct em28xx *dev = vb2_get_drv_priv(vq);
+	struct em28xx_v4l2 *v4l2 = dev->v4l2;
+	struct em28xx_dmaqueue *vbiq = &dev->vbiq;
+	unsigned long flags = 0;
+
+	em28xx_videodbg("%s\n", __func__);
+
+	res_free(dev, vq->type);
+
+	if (v4l2->streaming_users-- == 1) {
+		/* Disable video stream at TV decoder */
+		v4l2_device_call_all(&v4l2->v4l2_dev, 0, video, s_stream, 0);
+
+		/* Last active user, so shutdown all the URBS */
+		em28xx_uninit_usb_xfer(dev, EM28XX_ANALOG_MODE);
+	}
+
+	spin_lock_irqsave(&dev->slock, flags);
+	if (dev->usb_ctl.vbi_buf) {
+		vb2_buffer_done(&dev->usb_ctl.vbi_buf->vb.vb2_buf,
+				VB2_BUF_STATE_ERROR);
+		dev->usb_ctl.vbi_buf = NULL;
+	}
+	while (!list_empty(&vbiq->active)) {
+		struct em28xx_buffer *buf;
+
+		buf = list_entry(vbiq->active.next, struct em28xx_buffer, list);
+		list_del(&buf->list);
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+	}
+	spin_unlock_irqrestore(&dev->slock, flags);
+}
+
+static void
+buffer_queue(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct em28xx *dev = vb2_get_drv_priv(vb->vb2_queue);
+	struct em28xx_buffer *buf =
+		container_of(vbuf, struct em28xx_buffer, vb);
+	struct em28xx_dmaqueue *vidq = &dev->vidq;
+	unsigned long flags = 0;
+
+	em28xx_videodbg("%s\n", __func__);
+	buf->mem = vb2_plane_vaddr(vb, 0);
+	buf->length = vb2_plane_size(vb, 0);
+
+	spin_lock_irqsave(&dev->slock, flags);
+	list_add_tail(&buf->list, &vidq->active);
+	spin_unlock_irqrestore(&dev->slock, flags);
+}
+
+static const struct vb2_ops em28xx_video_qops = {
+	.queue_setup    = queue_setup,
+	.buf_prepare    = buffer_prepare,
+	.buf_queue      = buffer_queue,
+	.start_streaming = em28xx_start_analog_streaming,
+	.stop_streaming = em28xx_stop_streaming,
+	.wait_prepare   = vb2_ops_wait_prepare,
+	.wait_finish    = vb2_ops_wait_finish,
+};
+
+static int em28xx_vb2_setup(struct em28xx *dev)
+{
+	int rc;
+	struct vb2_queue *q;
+	struct em28xx_v4l2 *v4l2 = dev->v4l2;
+
+	/* Setup Videobuf2 for Video capture */
+	q = &v4l2->vb_vidq;
+	q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	q->io_modes = VB2_READ | VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
+	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	q->drv_priv = dev;
+	q->buf_struct_size = sizeof(struct em28xx_buffer);
+	q->ops = &em28xx_video_qops;
+	q->mem_ops = &vb2_vmalloc_memops;
+
+	rc = vb2_queue_init(q);
+	if (rc < 0)
+		return rc;
+
+	/* Setup Videobuf2 for VBI capture */
+	q = &v4l2->vb_vbiq;
+	q->type = V4L2_BUF_TYPE_VBI_CAPTURE;
+	q->io_modes = VB2_READ | VB2_MMAP | VB2_USERPTR;
+	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	q->drv_priv = dev;
+	q->buf_struct_size = sizeof(struct em28xx_buffer);
+	q->ops = &em28xx_vbi_qops;
+	q->mem_ops = &vb2_vmalloc_memops;
+
+	rc = vb2_queue_init(q);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+/*
+ * v4l2 interface
+ */
+
+static void video_mux(struct em28xx *dev, int index)
+{
+	struct v4l2_device *v4l2_dev = &dev->v4l2->v4l2_dev;
+
+	dev->ctl_input = index;
+	dev->ctl_ainput = INPUT(index)->amux;
+	dev->ctl_aoutput = INPUT(index)->aout;
+
+	if (!dev->ctl_aoutput)
+		dev->ctl_aoutput = EM28XX_AOUT_MASTER;
+
+	v4l2_device_call_all(v4l2_dev, 0, video, s_routing,
+			     INPUT(index)->vmux, 0, 0);
+
+	if (dev->has_msp34xx) {
+		if (dev->i2s_speed) {
+			v4l2_device_call_all(v4l2_dev, 0, audio,
+					     s_i2s_clock_freq, dev->i2s_speed);
+		}
+		/* Note: this is msp3400 specific */
+		v4l2_device_call_all(v4l2_dev, 0, audio, s_routing,
+				     dev->ctl_ainput,
+				     MSP_OUTPUT(MSP_SC_IN_DSP_SCART1), 0);
+	}
+
+	if (dev->board.adecoder != EM28XX_NOADECODER) {
+		v4l2_device_call_all(v4l2_dev, 0, audio, s_routing,
+				     dev->ctl_ainput, dev->ctl_aoutput, 0);
+	}
+
+	em28xx_audio_analog_set(dev);
+}
+
+static void em28xx_ctrl_notify(struct v4l2_ctrl *ctrl, void *priv)
+{
+	struct em28xx *dev = priv;
+
+	/*
+	 * In the case of non-AC97 volume controls, we still need
+	 * to do some setups at em28xx, in order to mute/unmute
+	 * and to adjust audio volume. However, the value ranges
+	 * should be checked by the corresponding V4L subdriver.
+	 */
+	switch (ctrl->id) {
+	case V4L2_CID_AUDIO_MUTE:
+		dev->mute = ctrl->val;
+		em28xx_audio_analog_set(dev);
+		break;
+	case V4L2_CID_AUDIO_VOLUME:
+		dev->volume = ctrl->val;
+		em28xx_audio_analog_set(dev);
+		break;
+	}
+}
+
+static int em28xx_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct em28xx_v4l2 *v4l2 =
+		  container_of(ctrl->handler, struct em28xx_v4l2, ctrl_handler);
+	struct em28xx *dev = v4l2->dev;
+	int ret = -EINVAL;
+
+	switch (ctrl->id) {
+	case V4L2_CID_AUDIO_MUTE:
+		dev->mute = ctrl->val;
+		ret = em28xx_audio_analog_set(dev);
+		break;
+	case V4L2_CID_AUDIO_VOLUME:
+		dev->volume = ctrl->val;
+		ret = em28xx_audio_analog_set(dev);
+		break;
+	case V4L2_CID_CONTRAST:
+		ret = em28xx_write_reg(dev, EM28XX_R20_YGAIN, ctrl->val);
+		break;
+	case V4L2_CID_BRIGHTNESS:
+		ret = em28xx_write_reg(dev, EM28XX_R21_YOFFSET, ctrl->val);
+		break;
+	case V4L2_CID_SATURATION:
+		ret = em28xx_write_reg(dev, EM28XX_R22_UVGAIN, ctrl->val);
+		break;
+	case V4L2_CID_BLUE_BALANCE:
+		ret = em28xx_write_reg(dev, EM28XX_R23_UOFFSET, ctrl->val);
+		break;
+	case V4L2_CID_RED_BALANCE:
+		ret = em28xx_write_reg(dev, EM28XX_R24_VOFFSET, ctrl->val);
+		break;
+	case V4L2_CID_SHARPNESS:
+		ret = em28xx_write_reg(dev, EM28XX_R25_SHARPNESS, ctrl->val);
+		break;
+	}
+
+	return (ret < 0) ? ret : 0;
+}
+
+static const struct v4l2_ctrl_ops em28xx_ctrl_ops = {
+	.s_ctrl = em28xx_s_ctrl,
+};
+
+static void size_to_scale(struct em28xx *dev,
+			  unsigned int width, unsigned int height,
+			unsigned int *hscale, unsigned int *vscale)
+{
+	unsigned int          maxw = norm_maxw(dev);
+	unsigned int          maxh = norm_maxh(dev);
+
+	*hscale = (((unsigned long)maxw) << 12) / width - 4096L;
+	if (*hscale > EM28XX_HVSCALE_MAX)
+		*hscale = EM28XX_HVSCALE_MAX;
+
+	*vscale = (((unsigned long)maxh) << 12) / height - 4096L;
+	if (*vscale > EM28XX_HVSCALE_MAX)
+		*vscale = EM28XX_HVSCALE_MAX;
+}
+
+static void scale_to_size(struct em28xx *dev,
+			  unsigned int hscale, unsigned int vscale,
+			  unsigned int *width, unsigned int *height)
+{
+	unsigned int          maxw = norm_maxw(dev);
+	unsigned int          maxh = norm_maxh(dev);
+
+	*width = (((unsigned long)maxw) << 12) / (hscale + 4096L);
+	*height = (((unsigned long)maxh) << 12) / (vscale + 4096L);
+
+	/* Don't let width or height to be zero */
+	if (*width < 1)
+		*width = 1;
+	if (*height < 1)
+		*height = 1;
+}
+
+/*
+ * IOCTL vidioc handling
+ */
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+				struct v4l2_format *f)
+{
+	struct em28xx         *dev = video_drvdata(file);
+	struct em28xx_v4l2    *v4l2 = dev->v4l2;
+
+	f->fmt.pix.width = v4l2->width;
+	f->fmt.pix.height = v4l2->height;
+	f->fmt.pix.pixelformat = v4l2->format->fourcc;
+	f->fmt.pix.bytesperline = (v4l2->width * v4l2->format->depth + 7) >> 3;
+	f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * v4l2->height;
+	f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+
+	/* FIXME: TOP? NONE? BOTTOM? ALTENATE? */
+	if (v4l2->progressive)
+		f->fmt.pix.field = V4L2_FIELD_NONE;
+	else
+		f->fmt.pix.field = v4l2->interlaced_fieldmode ?
+			   V4L2_FIELD_INTERLACED : V4L2_FIELD_TOP;
+	return 0;
+}
+
+static struct em28xx_fmt *format_by_fourcc(unsigned int fourcc)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(format); i++)
+		if (format[i].fourcc == fourcc)
+			return &format[i];
+
+	return NULL;
+}
+
+static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
+				  struct v4l2_format *f)
+{
+	struct em28xx         *dev   = video_drvdata(file);
+	struct em28xx_v4l2    *v4l2  = dev->v4l2;
+	unsigned int          width  = f->fmt.pix.width;
+	unsigned int          height = f->fmt.pix.height;
+	unsigned int          maxw   = norm_maxw(dev);
+	unsigned int          maxh   = norm_maxh(dev);
+	unsigned int          hscale, vscale;
+	struct em28xx_fmt     *fmt;
+
+	fmt = format_by_fourcc(f->fmt.pix.pixelformat);
+	if (!fmt) {
+		fmt = &format[0];
+		em28xx_videodbg("Fourcc format (%08x) invalid. Using default (%08x).\n",
+				f->fmt.pix.pixelformat, fmt->fourcc);
+	}
+
+	if (dev->board.is_em2800) {
+		/* the em2800 can only scale down to 50% */
+		height = height > (3 * maxh / 4) ? maxh : maxh / 2;
+		width = width > (3 * maxw / 4) ? maxw : maxw / 2;
+		/*
+		 * MaxPacketSize for em2800 is too small to capture at full
+		 * resolution use half of maxw as the scaler can only scale
+		 * to 50%
+		 */
+		if (width == maxw && height == maxh)
+			width /= 2;
+	} else {
+		/*
+		 * width must even because of the YUYV format
+		 * height must be even because of interlacing
+		 */
+		v4l_bound_align_image(&width, 48, maxw, 1, &height, 32, maxh,
+				      1, 0);
+	}
+	/* Avoid division by zero at size_to_scale */
+	if (width < 1)
+		width = 1;
+	if (height < 1)
+		height = 1;
+
+	size_to_scale(dev, width, height, &hscale, &vscale);
+	scale_to_size(dev, hscale, vscale, &width, &height);
+
+	f->fmt.pix.width = width;
+	f->fmt.pix.height = height;
+	f->fmt.pix.pixelformat = fmt->fourcc;
+	f->fmt.pix.bytesperline = (width * fmt->depth + 7) >> 3;
+	f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * height;
+	f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+	if (v4l2->progressive)
+		f->fmt.pix.field = V4L2_FIELD_NONE;
+	else
+		f->fmt.pix.field = v4l2->interlaced_fieldmode ?
+			   V4L2_FIELD_INTERLACED : V4L2_FIELD_TOP;
+	f->fmt.pix.priv = 0;
+
+	return 0;
+}
+
+static int em28xx_set_video_format(struct em28xx *dev, unsigned int fourcc,
+				   unsigned int width, unsigned int height)
+{
+	struct em28xx_fmt     *fmt;
+	struct em28xx_v4l2    *v4l2 = dev->v4l2;
+
+	fmt = format_by_fourcc(fourcc);
+	if (!fmt)
+		return -EINVAL;
+
+	v4l2->format = fmt;
+	v4l2->width  = width;
+	v4l2->height = height;
+
+	/* set new image size */
+	size_to_scale(dev, v4l2->width, v4l2->height,
+		      &v4l2->hscale, &v4l2->vscale);
+
+	em28xx_resolution_set(dev);
+
+	return 0;
+}
+
+static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+				struct v4l2_format *f)
+{
+	struct em28xx *dev = video_drvdata(file);
+	struct em28xx_v4l2 *v4l2 = dev->v4l2;
+
+	if (vb2_is_busy(&v4l2->vb_vidq))
+		return -EBUSY;
+
+	vidioc_try_fmt_vid_cap(file, priv, f);
+
+	return em28xx_set_video_format(dev, f->fmt.pix.pixelformat,
+				f->fmt.pix.width, f->fmt.pix.height);
+}
+
+static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *norm)
+{
+	struct em28xx *dev = video_drvdata(file);
+
+	*norm = dev->v4l2->norm;
+
+	return 0;
+}
+
+static int vidioc_querystd(struct file *file, void *priv, v4l2_std_id *norm)
+{
+	struct em28xx *dev = video_drvdata(file);
+
+	v4l2_device_call_all(&dev->v4l2->v4l2_dev, 0, video, querystd, norm);
+
+	return 0;
+}
+
+static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id norm)
+{
+	struct em28xx      *dev  = video_drvdata(file);
+	struct em28xx_v4l2 *v4l2 = dev->v4l2;
+	struct v4l2_format f;
+
+	if (norm == v4l2->norm)
+		return 0;
+
+	if (v4l2->streaming_users > 0)
+		return -EBUSY;
+
+	v4l2->norm = norm;
+
+	/* Adjusts width/height, if needed */
+	f.fmt.pix.width = 720;
+	f.fmt.pix.height = (norm & V4L2_STD_525_60) ? 480 : 576;
+	vidioc_try_fmt_vid_cap(file, priv, &f);
+
+	/* set new image size */
+	v4l2->width = f.fmt.pix.width;
+	v4l2->height = f.fmt.pix.height;
+	size_to_scale(dev, v4l2->width, v4l2->height,
+		      &v4l2->hscale, &v4l2->vscale);
+
+	em28xx_resolution_set(dev);
+	v4l2_device_call_all(&v4l2->v4l2_dev, 0, video, s_std, v4l2->norm);
+
+	return 0;
+}
+
+static int vidioc_g_parm(struct file *file, void *priv,
+			 struct v4l2_streamparm *p)
+{
+	struct v4l2_subdev_frame_interval ival = { 0 };
+	struct em28xx      *dev  = video_drvdata(file);
+	struct em28xx_v4l2 *v4l2 = dev->v4l2;
+	int rc = 0;
+
+	if (p->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
+	    p->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+		return -EINVAL;
+
+	p->parm.capture.readbuffers = EM28XX_MIN_BUF;
+	p->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
+	if (dev->is_webcam) {
+		rc = v4l2_device_call_until_err(&v4l2->v4l2_dev, 0,
+						video, g_frame_interval, &ival);
+		if (!rc)
+			p->parm.capture.timeperframe = ival.interval;
+	} else {
+		v4l2_video_std_frame_period(v4l2->norm,
+					    &p->parm.capture.timeperframe);
+	}
+
+	return rc;
+}
+
+static int vidioc_s_parm(struct file *file, void *priv,
+			 struct v4l2_streamparm *p)
+{
+	struct em28xx *dev = video_drvdata(file);
+	struct v4l2_subdev_frame_interval ival = {
+		0,
+		p->parm.capture.timeperframe
+	};
+	int rc = 0;
+
+	if (!dev->is_webcam)
+		return -ENOTTY;
+
+	if (p->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
+	    p->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+		return -EINVAL;
+
+	memset(&p->parm, 0, sizeof(p->parm));
+	p->parm.capture.readbuffers = EM28XX_MIN_BUF;
+	p->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
+	rc = v4l2_device_call_until_err(&dev->v4l2->v4l2_dev, 0,
+					video, s_frame_interval, &ival);
+	if (!rc)
+		p->parm.capture.timeperframe = ival.interval;
+	return rc;
+}
+
+static int vidioc_enum_input(struct file *file, void *priv,
+			     struct v4l2_input *i)
+{
+	struct em28xx *dev = video_drvdata(file);
+	unsigned int       n;
+	int j;
+
+	n = i->index;
+	if (n >= MAX_EM28XX_INPUT)
+		return -EINVAL;
+	if (!INPUT(n)->type)
+		return -EINVAL;
+
+	i->type = V4L2_INPUT_TYPE_CAMERA;
+
+	strcpy(i->name, iname[INPUT(n)->type]);
+
+	if (INPUT(n)->type == EM28XX_VMUX_TELEVISION)
+		i->type = V4L2_INPUT_TYPE_TUNER;
+
+	i->std = dev->v4l2->vdev.tvnorms;
+	/* webcams do not have the STD API */
+	if (dev->is_webcam)
+		i->capabilities = 0;
+
+	/* Dynamically generates an audioset bitmask */
+	i->audioset = 0;
+	for (j = 0; j < MAX_EM28XX_INPUT; j++)
+		if (dev->amux_map[j] != EM28XX_AMUX_UNUSED)
+			i->audioset |= 1 << j;
+
+	return 0;
+}
+
+static int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
+{
+	struct em28xx *dev = video_drvdata(file);
+
+	*i = dev->ctl_input;
+
+	return 0;
+}
+
+static int vidioc_s_input(struct file *file, void *priv, unsigned int i)
+{
+	struct em28xx *dev = video_drvdata(file);
+
+	if (i >= MAX_EM28XX_INPUT)
+		return -EINVAL;
+	if (!INPUT(i)->type)
+		return -EINVAL;
+
+	video_mux(dev, i);
+	return 0;
+}
+
+static int em28xx_fill_audio_input(struct em28xx *dev,
+				   const char *s,
+				   struct v4l2_audio *a,
+				   unsigned int index)
+{
+	unsigned int idx = dev->amux_map[index];
+
+	/*
+	 * With msp3400, almost all mappings use the default (amux = 0).
+	 * The only one may use a different value is WinTV USB2, where it
+	 * can also be SCART1 input.
+	 * As it is very doubtful that we would see new boards with msp3400,
+	 * let's just reuse the existing switch.
+	 */
+	if (dev->has_msp34xx && idx != EM28XX_AMUX_UNUSED)
+		idx = EM28XX_AMUX_LINE_IN;
+
+	switch (idx) {
+	case EM28XX_AMUX_VIDEO:
+		strcpy(a->name, "Television");
+		break;
+	case EM28XX_AMUX_LINE_IN:
+		strcpy(a->name, "Line In");
+		break;
+	case EM28XX_AMUX_VIDEO2:
+		strcpy(a->name, "Television alt");
+		break;
+	case EM28XX_AMUX_PHONE:
+		strcpy(a->name, "Phone");
+		break;
+	case EM28XX_AMUX_MIC:
+		strcpy(a->name, "Mic");
+		break;
+	case EM28XX_AMUX_CD:
+		strcpy(a->name, "CD");
+		break;
+	case EM28XX_AMUX_AUX:
+		strcpy(a->name, "Aux");
+		break;
+	case EM28XX_AMUX_PCM_OUT:
+		strcpy(a->name, "PCM");
+		break;
+	case EM28XX_AMUX_UNUSED:
+	default:
+		return -EINVAL;
+	}
+	a->index = index;
+	a->capability = V4L2_AUDCAP_STEREO;
+
+	em28xx_videodbg("%s: audio input index %d is '%s'\n",
+			s, a->index, a->name);
+
+	return 0;
+}
+
+static int vidioc_enumaudio(struct file *file, void *fh, struct v4l2_audio *a)
+{
+	struct em28xx *dev = video_drvdata(file);
+
+	if (a->index >= MAX_EM28XX_INPUT)
+		return -EINVAL;
+
+	return em28xx_fill_audio_input(dev, __func__, a, a->index);
+}
+
+static int vidioc_g_audio(struct file *file, void *priv, struct v4l2_audio *a)
+{
+	struct em28xx *dev = video_drvdata(file);
+	int i;
+
+	for (i = 0; i < MAX_EM28XX_INPUT; i++)
+		if (dev->ctl_ainput == dev->amux_map[i])
+			return em28xx_fill_audio_input(dev, __func__, a, i);
+
+	/* Should never happen! */
+	return -EINVAL;
+}
+
+static int vidioc_s_audio(struct file *file, void *priv,
+			  const struct v4l2_audio *a)
+{
+	struct em28xx *dev = video_drvdata(file);
+	int idx, i;
+
+	if (a->index >= MAX_EM28XX_INPUT)
+		return -EINVAL;
+
+	idx = dev->amux_map[a->index];
+
+	if (idx == EM28XX_AMUX_UNUSED)
+		return -EINVAL;
+
+	dev->ctl_ainput = idx;
+
+	/*
+	 * FIXME: This is wrong, as different inputs at em28xx_cards
+	 * may have different audio outputs. So, the right thing
+	 * to do is to implement VIDIOC_G_AUDOUT/VIDIOC_S_AUDOUT.
+	 * With the current board definitions, this would work fine,
+	 * as, currently, all boards fit.
+	 */
+	for (i = 0; i < MAX_EM28XX_INPUT; i++)
+		if (idx == dev->amux_map[i])
+			break;
+	if (i == MAX_EM28XX_INPUT)
+		return -EINVAL;
+
+	dev->ctl_aoutput = INPUT(i)->aout;
+
+	if (!dev->ctl_aoutput)
+		dev->ctl_aoutput = EM28XX_AOUT_MASTER;
+
+	em28xx_videodbg("%s: set audio input to %d\n", __func__,
+			dev->ctl_ainput);
+
+	return 0;
+}
+
+static int vidioc_g_tuner(struct file *file, void *priv,
+			  struct v4l2_tuner *t)
+{
+	struct em28xx *dev = video_drvdata(file);
+
+	if (t->index != 0)
+		return -EINVAL;
+
+	strcpy(t->name, "Tuner");
+
+	v4l2_device_call_all(&dev->v4l2->v4l2_dev, 0, tuner, g_tuner, t);
+	return 0;
+}
+
+static int vidioc_s_tuner(struct file *file, void *priv,
+			  const struct v4l2_tuner *t)
+{
+	struct em28xx *dev = video_drvdata(file);
+
+	if (t->index != 0)
+		return -EINVAL;
+
+	v4l2_device_call_all(&dev->v4l2->v4l2_dev, 0, tuner, s_tuner, t);
+	return 0;
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+			      struct v4l2_frequency *f)
+{
+	struct em28xx         *dev = video_drvdata(file);
+	struct em28xx_v4l2    *v4l2 = dev->v4l2;
+
+	if (f->tuner != 0)
+		return -EINVAL;
+
+	f->frequency = v4l2->frequency;
+	return 0;
+}
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+			      const struct v4l2_frequency *f)
+{
+	struct v4l2_frequency  new_freq = *f;
+	struct em28xx             *dev  = video_drvdata(file);
+	struct em28xx_v4l2        *v4l2 = dev->v4l2;
+
+	if (f->tuner != 0)
+		return -EINVAL;
+
+	v4l2_device_call_all(&v4l2->v4l2_dev, 0, tuner, s_frequency, f);
+	v4l2_device_call_all(&v4l2->v4l2_dev, 0, tuner, g_frequency, &new_freq);
+	v4l2->frequency = new_freq.frequency;
+
+	return 0;
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int vidioc_g_chip_info(struct file *file, void *priv,
+			      struct v4l2_dbg_chip_info *chip)
+{
+	struct em28xx *dev = video_drvdata(file);
+
+	if (chip->match.addr > 1)
+		return -EINVAL;
+	if (chip->match.addr == 1)
+		strlcpy(chip->name, "ac97", sizeof(chip->name));
+	else
+		strlcpy(chip->name,
+			dev->v4l2->v4l2_dev.name, sizeof(chip->name));
+	return 0;
+}
+
+static int em28xx_reg_len(int reg)
+{
+	switch (reg) {
+	case EM28XX_R40_AC97LSB:
+	case EM28XX_R30_HSCALELOW:
+	case EM28XX_R32_VSCALELOW:
+		return 2;
+	default:
+		return 1;
+	}
+}
+
+static int vidioc_g_register(struct file *file, void *priv,
+			     struct v4l2_dbg_register *reg)
+{
+	struct em28xx *dev = video_drvdata(file);
+	int ret;
+
+	if (reg->match.addr > 1)
+		return -EINVAL;
+	if (reg->match.addr) {
+		ret = em28xx_read_ac97(dev, reg->reg);
+		if (ret < 0)
+			return ret;
+
+		reg->val = ret;
+		reg->size = 1;
+		return 0;
+	}
+
+	/* Match host */
+	reg->size = em28xx_reg_len(reg->reg);
+	if (reg->size == 1) {
+		ret = em28xx_read_reg(dev, reg->reg);
+
+		if (ret < 0)
+			return ret;
+
+		reg->val = ret;
+	} else {
+		__le16 val = 0;
+
+		ret = dev->em28xx_read_reg_req_len(dev, USB_REQ_GET_STATUS,
+						   reg->reg, (char *)&val, 2);
+		if (ret < 0)
+			return ret;
+
+		reg->val = le16_to_cpu(val);
+	}
+
+	return 0;
+}
+
+static int vidioc_s_register(struct file *file, void *priv,
+			     const struct v4l2_dbg_register *reg)
+{
+	struct em28xx *dev = video_drvdata(file);
+	__le16 buf;
+
+	if (reg->match.addr > 1)
+		return -EINVAL;
+	if (reg->match.addr)
+		return em28xx_write_ac97(dev, reg->reg, reg->val);
+
+	/* Match host */
+	buf = cpu_to_le16(reg->val);
+
+	return em28xx_write_regs(dev, reg->reg, (char *)&buf,
+			       em28xx_reg_len(reg->reg));
+}
+#endif
+
+static int vidioc_querycap(struct file *file, void  *priv,
+			   struct v4l2_capability *cap)
+{
+	struct video_device   *vdev = video_devdata(file);
+	struct em28xx         *dev  = video_drvdata(file);
+	struct em28xx_v4l2    *v4l2 = dev->v4l2;
+	struct usb_device *udev = interface_to_usbdev(dev->intf);
+
+	strlcpy(cap->driver, "em28xx", sizeof(cap->driver));
+	strlcpy(cap->card, em28xx_boards[dev->model].name, sizeof(cap->card));
+	usb_make_path(udev, cap->bus_info, sizeof(cap->bus_info));
+
+	if (vdev->vfl_type == VFL_TYPE_GRABBER)
+		cap->device_caps = V4L2_CAP_READWRITE |
+			V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
+	else if (vdev->vfl_type == VFL_TYPE_RADIO)
+		cap->device_caps = V4L2_CAP_RADIO;
+	else
+		cap->device_caps = V4L2_CAP_READWRITE | V4L2_CAP_VBI_CAPTURE;
+
+	if (dev->int_audio_type != EM28XX_INT_AUDIO_NONE)
+		cap->device_caps |= V4L2_CAP_AUDIO;
+
+	if (dev->tuner_type != TUNER_ABSENT)
+		cap->device_caps |= V4L2_CAP_TUNER;
+
+	cap->capabilities = cap->device_caps |
+			    V4L2_CAP_DEVICE_CAPS | V4L2_CAP_READWRITE |
+			    V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
+	if (video_is_registered(&v4l2->vbi_dev))
+		cap->capabilities |= V4L2_CAP_VBI_CAPTURE;
+	if (video_is_registered(&v4l2->radio_dev))
+		cap->capabilities |= V4L2_CAP_RADIO;
+	return 0;
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
+				   struct v4l2_fmtdesc *f)
+{
+	if (unlikely(f->index >= ARRAY_SIZE(format)))
+		return -EINVAL;
+
+	strlcpy(f->description, format[f->index].name, sizeof(f->description));
+	f->pixelformat = format[f->index].fourcc;
+
+	return 0;
+}
+
+static int vidioc_enum_framesizes(struct file *file, void *priv,
+				  struct v4l2_frmsizeenum *fsize)
+{
+	struct em28xx         *dev = video_drvdata(file);
+	struct em28xx_fmt     *fmt;
+	unsigned int	      maxw = norm_maxw(dev);
+	unsigned int	      maxh = norm_maxh(dev);
+
+	fmt = format_by_fourcc(fsize->pixel_format);
+	if (!fmt) {
+		em28xx_videodbg("Fourcc format (%08x) invalid.\n",
+				fsize->pixel_format);
+		return -EINVAL;
+	}
+
+	if (dev->board.is_em2800) {
+		if (fsize->index > 1)
+			return -EINVAL;
+		fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+		fsize->discrete.width = maxw / (1 + fsize->index);
+		fsize->discrete.height = maxh / (1 + fsize->index);
+		return 0;
+	}
+
+	if (fsize->index != 0)
+		return -EINVAL;
+
+	/* Report a continuous range */
+	fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
+	scale_to_size(dev, EM28XX_HVSCALE_MAX, EM28XX_HVSCALE_MAX,
+		      &fsize->stepwise.min_width, &fsize->stepwise.min_height);
+	if (fsize->stepwise.min_width < 48)
+		fsize->stepwise.min_width = 48;
+	if (fsize->stepwise.min_height < 38)
+		fsize->stepwise.min_height = 38;
+	fsize->stepwise.max_width = maxw;
+	fsize->stepwise.max_height = maxh;
+	fsize->stepwise.step_width = 1;
+	fsize->stepwise.step_height = 1;
+	return 0;
+}
+
+/* RAW VBI ioctls */
+
+static int vidioc_g_fmt_vbi_cap(struct file *file, void *priv,
+				struct v4l2_format *format)
+{
+	struct em28xx         *dev  = video_drvdata(file);
+	struct em28xx_v4l2    *v4l2 = dev->v4l2;
+
+	format->fmt.vbi.samples_per_line = v4l2->vbi_width;
+	format->fmt.vbi.sample_format = V4L2_PIX_FMT_GREY;
+	format->fmt.vbi.offset = 0;
+	format->fmt.vbi.flags = 0;
+	format->fmt.vbi.sampling_rate = 6750000 * 4 / 2;
+	format->fmt.vbi.count[0] = v4l2->vbi_height;
+	format->fmt.vbi.count[1] = v4l2->vbi_height;
+	memset(format->fmt.vbi.reserved, 0, sizeof(format->fmt.vbi.reserved));
+
+	/* Varies by video standard (NTSC, PAL, etc.) */
+	if (v4l2->norm & V4L2_STD_525_60) {
+		/* NTSC */
+		format->fmt.vbi.start[0] = 10;
+		format->fmt.vbi.start[1] = 273;
+	} else if (v4l2->norm & V4L2_STD_625_50) {
+		/* PAL */
+		format->fmt.vbi.start[0] = 6;
+		format->fmt.vbi.start[1] = 318;
+	}
+
+	return 0;
+}
+
+/*
+ * RADIO ESPECIFIC IOCTLS
+ */
+
+static int radio_g_tuner(struct file *file, void *priv,
+			 struct v4l2_tuner *t)
+{
+	struct em28xx *dev = video_drvdata(file);
+
+	if (unlikely(t->index > 0))
+		return -EINVAL;
+
+	strcpy(t->name, "Radio");
+
+	v4l2_device_call_all(&dev->v4l2->v4l2_dev, 0, tuner, g_tuner, t);
+
+	return 0;
+}
+
+static int radio_s_tuner(struct file *file, void *priv,
+			 const struct v4l2_tuner *t)
+{
+	struct em28xx *dev = video_drvdata(file);
+
+	if (t->index != 0)
+		return -EINVAL;
+
+	v4l2_device_call_all(&dev->v4l2->v4l2_dev, 0, tuner, s_tuner, t);
+
+	return 0;
+}
+
+/*
+ * em28xx_free_v4l2() - Free struct em28xx_v4l2
+ *
+ * @ref: struct kref for struct em28xx_v4l2
+ *
+ * Called when all users of struct em28xx_v4l2 are gone
+ */
+static void em28xx_free_v4l2(struct kref *ref)
+{
+	struct em28xx_v4l2 *v4l2 = container_of(ref, struct em28xx_v4l2, ref);
+
+	v4l2->dev->v4l2 = NULL;
+	kfree(v4l2);
+}
+
+/*
+ * em28xx_v4l2_open()
+ * inits the device and starts isoc transfer
+ */
+static int em28xx_v4l2_open(struct file *filp)
+{
+	struct video_device *vdev = video_devdata(filp);
+	struct em28xx *dev = video_drvdata(filp);
+	struct em28xx_v4l2 *v4l2 = dev->v4l2;
+	enum v4l2_buf_type fh_type = 0;
+	int ret;
+
+	switch (vdev->vfl_type) {
+	case VFL_TYPE_GRABBER:
+		fh_type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+		break;
+	case VFL_TYPE_VBI:
+		fh_type = V4L2_BUF_TYPE_VBI_CAPTURE;
+		break;
+	case VFL_TYPE_RADIO:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	em28xx_videodbg("open dev=%s type=%s users=%d\n",
+			video_device_node_name(vdev), v4l2_type_names[fh_type],
+			v4l2->users);
+
+	if (mutex_lock_interruptible(&dev->lock))
+		return -ERESTARTSYS;
+
+	ret = v4l2_fh_open(filp);
+	if (ret) {
+		dev_err(&dev->intf->dev,
+			"%s: v4l2_fh_open() returned error %d\n",
+		       __func__, ret);
+		mutex_unlock(&dev->lock);
+		return ret;
+	}
+
+	if (v4l2->users == 0) {
+		em28xx_set_mode(dev, EM28XX_ANALOG_MODE);
+
+		if (vdev->vfl_type != VFL_TYPE_RADIO)
+			em28xx_resolution_set(dev);
+
+		/*
+		 * Needed, since GPIO might have disabled power
+		 * of some i2c devices
+		 */
+		em28xx_wake_i2c(dev);
+	}
+
+	if (vdev->vfl_type == VFL_TYPE_RADIO) {
+		em28xx_videodbg("video_open: setting radio device\n");
+		v4l2_device_call_all(&v4l2->v4l2_dev, 0, tuner, s_radio);
+	}
+
+	kref_get(&dev->ref);
+	kref_get(&v4l2->ref);
+	v4l2->users++;
+
+	mutex_unlock(&dev->lock);
+
+	return 0;
+}
+
+/*
+ * em28xx_v4l2_fini()
+ * unregisters the v4l2,i2c and usb devices
+ * called when the device gets disconected or at module unload
+ */
+static int em28xx_v4l2_fini(struct em28xx *dev)
+{
+	struct em28xx_v4l2 *v4l2 = dev->v4l2;
+
+	if (dev->is_audio_only) {
+		/* Shouldn't initialize IR for this interface */
+		return 0;
+	}
+
+	if (!dev->has_video) {
+		/* This device does not support the v4l2 extension */
+		return 0;
+	}
+
+	if (!v4l2)
+		return 0;
+
+	dev_info(&dev->intf->dev, "Closing video extension\n");
+
+	mutex_lock(&dev->lock);
+
+	v4l2_device_disconnect(&v4l2->v4l2_dev);
+
+	em28xx_uninit_usb_xfer(dev, EM28XX_ANALOG_MODE);
+
+	em28xx_v4l2_media_release(dev);
+
+	if (video_is_registered(&v4l2->radio_dev)) {
+		dev_info(&dev->intf->dev, "V4L2 device %s deregistered\n",
+			 video_device_node_name(&v4l2->radio_dev));
+		video_unregister_device(&v4l2->radio_dev);
+	}
+	if (video_is_registered(&v4l2->vbi_dev)) {
+		dev_info(&dev->intf->dev, "V4L2 device %s deregistered\n",
+			 video_device_node_name(&v4l2->vbi_dev));
+		video_unregister_device(&v4l2->vbi_dev);
+	}
+	if (video_is_registered(&v4l2->vdev)) {
+		dev_info(&dev->intf->dev, "V4L2 device %s deregistered\n",
+			 video_device_node_name(&v4l2->vdev));
+		video_unregister_device(&v4l2->vdev);
+	}
+
+	v4l2_ctrl_handler_free(&v4l2->ctrl_handler);
+	v4l2_device_unregister(&v4l2->v4l2_dev);
+
+	kref_put(&v4l2->ref, em28xx_free_v4l2);
+
+	mutex_unlock(&dev->lock);
+
+	kref_put(&dev->ref, em28xx_free_device);
+
+	return 0;
+}
+
+static int em28xx_v4l2_suspend(struct em28xx *dev)
+{
+	if (dev->is_audio_only)
+		return 0;
+
+	if (!dev->has_video)
+		return 0;
+
+	dev_info(&dev->intf->dev, "Suspending video extension\n");
+	em28xx_stop_urbs(dev);
+	return 0;
+}
+
+static int em28xx_v4l2_resume(struct em28xx *dev)
+{
+	if (dev->is_audio_only)
+		return 0;
+
+	if (!dev->has_video)
+		return 0;
+
+	dev_info(&dev->intf->dev, "Resuming video extension\n");
+	/* what do we do here */
+	return 0;
+}
+
+/*
+ * em28xx_v4l2_close()
+ * stops streaming and deallocates all resources allocated by the v4l2
+ * calls and ioctls
+ */
+static int em28xx_v4l2_close(struct file *filp)
+{
+	struct em28xx         *dev  = video_drvdata(filp);
+	struct em28xx_v4l2    *v4l2 = dev->v4l2;
+	struct usb_device *udev = interface_to_usbdev(dev->intf);
+	int              err;
+
+	em28xx_videodbg("users=%d\n", v4l2->users);
+
+	vb2_fop_release(filp);
+	mutex_lock(&dev->lock);
+
+	if (v4l2->users == 1) {
+		/* No sense to try to write to the device */
+		if (dev->disconnected)
+			goto exit;
+
+		/* Save some power by putting tuner to sleep */
+		v4l2_device_call_all(&v4l2->v4l2_dev, 0, tuner, standby);
+
+		/* do this before setting alternate! */
+		em28xx_set_mode(dev, EM28XX_SUSPEND);
+
+		/* set alternate 0 */
+		dev->alt = 0;
+		em28xx_videodbg("setting alternate 0\n");
+		err = usb_set_interface(udev, 0, 0);
+		if (err < 0) {
+			dev_err(&dev->intf->dev,
+				"cannot change alternate number to 0 (error=%i)\n",
+				err);
+		}
+	}
+
+exit:
+	v4l2->users--;
+	kref_put(&v4l2->ref, em28xx_free_v4l2);
+	mutex_unlock(&dev->lock);
+	kref_put(&dev->ref, em28xx_free_device);
+
+	return 0;
+}
+
+static const struct v4l2_file_operations em28xx_v4l_fops = {
+	.owner         = THIS_MODULE,
+	.open          = em28xx_v4l2_open,
+	.release       = em28xx_v4l2_close,
+	.read          = vb2_fop_read,
+	.poll          = vb2_fop_poll,
+	.mmap          = vb2_fop_mmap,
+	.unlocked_ioctl = video_ioctl2,
+};
+
+static const struct v4l2_ioctl_ops video_ioctl_ops = {
+	.vidioc_querycap            = vidioc_querycap,
+	.vidioc_enum_fmt_vid_cap    = vidioc_enum_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap       = vidioc_g_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap     = vidioc_try_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap       = vidioc_s_fmt_vid_cap,
+	.vidioc_g_fmt_vbi_cap       = vidioc_g_fmt_vbi_cap,
+	.vidioc_try_fmt_vbi_cap     = vidioc_g_fmt_vbi_cap,
+	.vidioc_s_fmt_vbi_cap       = vidioc_g_fmt_vbi_cap,
+	.vidioc_enum_framesizes     = vidioc_enum_framesizes,
+	.vidioc_enumaudio           = vidioc_enumaudio,
+	.vidioc_g_audio             = vidioc_g_audio,
+	.vidioc_s_audio             = vidioc_s_audio,
+
+	.vidioc_reqbufs             = vb2_ioctl_reqbufs,
+	.vidioc_create_bufs         = vb2_ioctl_create_bufs,
+	.vidioc_prepare_buf         = vb2_ioctl_prepare_buf,
+	.vidioc_querybuf            = vb2_ioctl_querybuf,
+	.vidioc_qbuf                = vb2_ioctl_qbuf,
+	.vidioc_dqbuf               = vb2_ioctl_dqbuf,
+
+	.vidioc_g_std               = vidioc_g_std,
+	.vidioc_querystd            = vidioc_querystd,
+	.vidioc_s_std               = vidioc_s_std,
+	.vidioc_g_parm		    = vidioc_g_parm,
+	.vidioc_s_parm		    = vidioc_s_parm,
+	.vidioc_enum_input          = vidioc_enum_input,
+	.vidioc_g_input             = vidioc_g_input,
+	.vidioc_s_input             = vidioc_s_input,
+	.vidioc_streamon            = vb2_ioctl_streamon,
+	.vidioc_streamoff           = vb2_ioctl_streamoff,
+	.vidioc_g_tuner             = vidioc_g_tuner,
+	.vidioc_s_tuner             = vidioc_s_tuner,
+	.vidioc_g_frequency         = vidioc_g_frequency,
+	.vidioc_s_frequency         = vidioc_s_frequency,
+	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.vidioc_g_chip_info         = vidioc_g_chip_info,
+	.vidioc_g_register          = vidioc_g_register,
+	.vidioc_s_register          = vidioc_s_register,
+#endif
+};
+
+static const struct video_device em28xx_video_template = {
+	.fops		= &em28xx_v4l_fops,
+	.ioctl_ops	= &video_ioctl_ops,
+	.release	= video_device_release_empty,
+	.tvnorms	= V4L2_STD_ALL,
+};
+
+static const struct v4l2_file_operations radio_fops = {
+	.owner         = THIS_MODULE,
+	.open          = em28xx_v4l2_open,
+	.release       = em28xx_v4l2_close,
+	.unlocked_ioctl = video_ioctl2,
+};
+
+static const struct v4l2_ioctl_ops radio_ioctl_ops = {
+	.vidioc_querycap      = vidioc_querycap,
+	.vidioc_g_tuner       = radio_g_tuner,
+	.vidioc_s_tuner       = radio_s_tuner,
+	.vidioc_g_frequency   = vidioc_g_frequency,
+	.vidioc_s_frequency   = vidioc_s_frequency,
+	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.vidioc_g_chip_info   = vidioc_g_chip_info,
+	.vidioc_g_register    = vidioc_g_register,
+	.vidioc_s_register    = vidioc_s_register,
+#endif
+};
+
+static struct video_device em28xx_radio_template = {
+	.fops		= &radio_fops,
+	.ioctl_ops	= &radio_ioctl_ops,
+	.release	= video_device_release_empty,
+};
+
+/* I2C possible address to saa7115, tvp5150, msp3400, tvaudio */
+static unsigned short saa711x_addrs[] = {
+	0x4a >> 1, 0x48 >> 1,   /* SAA7111, SAA7111A and SAA7113 */
+	0x42 >> 1, 0x40 >> 1,   /* SAA7114, SAA7115 and SAA7118 */
+	I2C_CLIENT_END };
+
+static unsigned short tvp5150_addrs[] = {
+	0xb8 >> 1,
+	0xba >> 1,
+	I2C_CLIENT_END
+};
+
+static unsigned short msp3400_addrs[] = {
+	0x80 >> 1,
+	0x88 >> 1,
+	I2C_CLIENT_END
+};
+
+/******************************** usb interface ******************************/
+
+static void em28xx_vdev_init(struct em28xx *dev,
+			     struct video_device *vfd,
+			     const struct video_device *template,
+			     const char *type_name)
+{
+	*vfd		= *template;
+	vfd->v4l2_dev	= &dev->v4l2->v4l2_dev;
+	vfd->lock	= &dev->lock;
+	if (dev->is_webcam)
+		vfd->tvnorms = 0;
+
+	snprintf(vfd->name, sizeof(vfd->name), "%s %s",
+		 dev_name(&dev->intf->dev), type_name);
+
+	video_set_drvdata(vfd, dev);
+}
+
+static void em28xx_tuner_setup(struct em28xx *dev, unsigned short tuner_addr)
+{
+	struct em28xx_v4l2      *v4l2 = dev->v4l2;
+	struct v4l2_device      *v4l2_dev = &v4l2->v4l2_dev;
+	struct tuner_setup      tun_setup;
+	struct v4l2_frequency   f;
+
+	memset(&tun_setup, 0, sizeof(tun_setup));
+
+	tun_setup.mode_mask = T_ANALOG_TV | T_RADIO;
+	tun_setup.tuner_callback = em28xx_tuner_callback;
+
+	if (dev->board.radio.type) {
+		tun_setup.type = dev->board.radio.type;
+		tun_setup.addr = dev->board.radio_addr;
+
+		v4l2_device_call_all(v4l2_dev,
+				     0, tuner, s_type_addr, &tun_setup);
+	}
+
+	if (dev->tuner_type != TUNER_ABSENT && dev->tuner_type) {
+		tun_setup.type   = dev->tuner_type;
+		tun_setup.addr   = tuner_addr;
+
+		v4l2_device_call_all(v4l2_dev,
+				     0, tuner, s_type_addr, &tun_setup);
+	}
+
+	if (dev->board.tda9887_conf) {
+		struct v4l2_priv_tun_config tda9887_cfg;
+
+		tda9887_cfg.tuner = TUNER_TDA9887;
+		tda9887_cfg.priv = &dev->board.tda9887_conf;
+
+		v4l2_device_call_all(v4l2_dev,
+				     0, tuner, s_config, &tda9887_cfg);
+	}
+
+	if (dev->tuner_type == TUNER_XC2028) {
+		struct v4l2_priv_tun_config  xc2028_cfg;
+		struct xc2028_ctrl           ctl;
+
+		memset(&xc2028_cfg, 0, sizeof(xc2028_cfg));
+		memset(&ctl, 0, sizeof(ctl));
+
+		em28xx_setup_xc3028(dev, &ctl);
+
+		xc2028_cfg.tuner = TUNER_XC2028;
+		xc2028_cfg.priv  = &ctl;
+
+		v4l2_device_call_all(v4l2_dev, 0, tuner, s_config, &xc2028_cfg);
+	}
+
+	/* configure tuner */
+	f.tuner = 0;
+	f.type = V4L2_TUNER_ANALOG_TV;
+	f.frequency = 9076;     /* just a magic number */
+	v4l2->frequency = f.frequency;
+	v4l2_device_call_all(v4l2_dev, 0, tuner, s_frequency, &f);
+}
+
+static int em28xx_v4l2_init(struct em28xx *dev)
+{
+	u8 val;
+	int ret;
+	unsigned int maxw;
+	struct v4l2_ctrl_handler *hdl;
+	struct em28xx_v4l2 *v4l2;
+
+	if (dev->is_audio_only) {
+		/* Shouldn't initialize IR for this interface */
+		return 0;
+	}
+
+	if (!dev->has_video) {
+		/* This device does not support the v4l2 extension */
+		return 0;
+	}
+
+	dev_info(&dev->intf->dev, "Registering V4L2 extension\n");
+
+	mutex_lock(&dev->lock);
+
+	v4l2 = kzalloc(sizeof(*v4l2), GFP_KERNEL);
+	if (!v4l2) {
+		mutex_unlock(&dev->lock);
+		return -ENOMEM;
+	}
+	kref_init(&v4l2->ref);
+	v4l2->dev = dev;
+	dev->v4l2 = v4l2;
+
+#ifdef CONFIG_MEDIA_CONTROLLER
+	v4l2->v4l2_dev.mdev = dev->media_dev;
+#endif
+	ret = v4l2_device_register(&dev->intf->dev, &v4l2->v4l2_dev);
+	if (ret < 0) {
+		dev_err(&dev->intf->dev,
+			"Call to v4l2_device_register() failed!\n");
+		goto err;
+	}
+
+	hdl = &v4l2->ctrl_handler;
+	v4l2_ctrl_handler_init(hdl, 8);
+	v4l2->v4l2_dev.ctrl_handler = hdl;
+
+	if (dev->is_webcam)
+		v4l2->progressive = true;
+
+	/*
+	 * Default format, used for tvp5150 or saa711x output formats
+	 */
+	v4l2->vinmode = EM28XX_VINMODE_YUV422_CbYCrY;
+	v4l2->vinctl  = EM28XX_VINCTRL_INTERLACED |
+			EM28XX_VINCTRL_CCIR656_ENABLE;
+
+	/* request some modules */
+
+	if (dev->has_msp34xx)
+		v4l2_i2c_new_subdev(&v4l2->v4l2_dev,
+				    &dev->i2c_adap[dev->def_i2c_bus],
+				    "msp3400", 0, msp3400_addrs);
+
+	if (dev->board.decoder == EM28XX_SAA711X)
+		v4l2_i2c_new_subdev(&v4l2->v4l2_dev,
+				    &dev->i2c_adap[dev->def_i2c_bus],
+				    "saa7115_auto", 0, saa711x_addrs);
+
+	if (dev->board.decoder == EM28XX_TVP5150)
+		v4l2_i2c_new_subdev(&v4l2->v4l2_dev,
+				    &dev->i2c_adap[dev->def_i2c_bus],
+				    "tvp5150", 0, tvp5150_addrs);
+
+	if (dev->board.adecoder == EM28XX_TVAUDIO)
+		v4l2_i2c_new_subdev(&v4l2->v4l2_dev,
+				    &dev->i2c_adap[dev->def_i2c_bus],
+				    "tvaudio", dev->board.tvaudio_addr, NULL);
+
+	/* Initialize tuner and camera */
+
+	if (dev->board.tuner_type != TUNER_ABSENT) {
+		unsigned short tuner_addr = dev->board.tuner_addr;
+		int has_demod = (dev->board.tda9887_conf & TDA9887_PRESENT);
+
+		if (dev->board.radio.type)
+			v4l2_i2c_new_subdev(&v4l2->v4l2_dev,
+					    &dev->i2c_adap[dev->def_i2c_bus],
+					    "tuner", dev->board.radio_addr,
+					    NULL);
+
+		if (has_demod)
+			v4l2_i2c_new_subdev(&v4l2->v4l2_dev,
+					    &dev->i2c_adap[dev->def_i2c_bus],
+					    "tuner", 0,
+					    v4l2_i2c_tuner_addrs(ADDRS_DEMOD));
+		if (tuner_addr == 0) {
+			enum v4l2_i2c_tuner_type type =
+				has_demod ? ADDRS_TV_WITH_DEMOD : ADDRS_TV;
+			struct v4l2_subdev *sd;
+
+			sd = v4l2_i2c_new_subdev(&v4l2->v4l2_dev,
+						 &dev->i2c_adap[dev->def_i2c_bus],
+						 "tuner", 0,
+						 v4l2_i2c_tuner_addrs(type));
+
+			if (sd)
+				tuner_addr = v4l2_i2c_subdev_addr(sd);
+		} else {
+			v4l2_i2c_new_subdev(&v4l2->v4l2_dev,
+					    &dev->i2c_adap[dev->def_i2c_bus],
+					    "tuner", tuner_addr, NULL);
+		}
+
+		em28xx_tuner_setup(dev, tuner_addr);
+	}
+
+	if (dev->em28xx_sensor != EM28XX_NOSENSOR)
+		em28xx_init_camera(dev);
+
+	/* Configure audio */
+	ret = em28xx_audio_setup(dev);
+	if (ret < 0) {
+		dev_err(&dev->intf->dev,
+			"%s: Error while setting audio - error [%d]!\n",
+			__func__, ret);
+		goto unregister_dev;
+	}
+	if (dev->audio_mode.ac97 != EM28XX_NO_AC97) {
+		v4l2_ctrl_new_std(hdl, &em28xx_ctrl_ops,
+				  V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
+		v4l2_ctrl_new_std(hdl, &em28xx_ctrl_ops,
+				  V4L2_CID_AUDIO_VOLUME, 0, 0x1f, 1, 0x1f);
+	} else {
+		/* install the em28xx notify callback */
+		v4l2_ctrl_notify(v4l2_ctrl_find(hdl, V4L2_CID_AUDIO_MUTE),
+				 em28xx_ctrl_notify, dev);
+		v4l2_ctrl_notify(v4l2_ctrl_find(hdl, V4L2_CID_AUDIO_VOLUME),
+				 em28xx_ctrl_notify, dev);
+	}
+
+	/* wake i2c devices */
+	em28xx_wake_i2c(dev);
+
+	/* init video dma queues */
+	INIT_LIST_HEAD(&dev->vidq.active);
+	INIT_LIST_HEAD(&dev->vbiq.active);
+
+	if (dev->has_msp34xx) {
+		/* Send a reset to other chips via gpio */
+		ret = em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0xf7);
+		if (ret < 0) {
+			dev_err(&dev->intf->dev,
+				"%s: em28xx_write_reg - msp34xx(1) failed! error [%d]\n",
+				__func__, ret);
+			goto unregister_dev;
+		}
+		usleep_range(10000, 11000);
+
+		ret = em28xx_write_reg(dev, EM2820_R08_GPIO_CTRL, 0xff);
+		if (ret < 0) {
+			dev_err(&dev->intf->dev,
+				"%s: em28xx_write_reg - msp34xx(2) failed! error [%d]\n",
+				__func__, ret);
+			goto unregister_dev;
+		}
+		usleep_range(10000, 11000);
+	}
+
+	/* set default norm */
+	v4l2->norm = V4L2_STD_PAL;
+	v4l2_device_call_all(&v4l2->v4l2_dev, 0, video, s_std, v4l2->norm);
+	v4l2->interlaced_fieldmode = EM28XX_INTERLACED_DEFAULT;
+
+	/* Analog specific initialization */
+	v4l2->format = &format[0];
+
+	maxw = norm_maxw(dev);
+	/*
+	 * MaxPacketSize for em2800 is too small to capture at full resolution
+	 * use half of maxw as the scaler can only scale to 50%
+	 */
+	if (dev->board.is_em2800)
+		maxw /= 2;
+
+	em28xx_set_video_format(dev, format[0].fourcc,
+				maxw, norm_maxh(dev));
+
+	video_mux(dev, 0);
+
+	/* Audio defaults */
+	dev->mute = 1;
+	dev->volume = 0x1f;
+
+/*	em28xx_write_reg(dev, EM28XX_R0E_AUDIOSRC, 0xc0); audio register */
+	val = (u8)em28xx_read_reg(dev, EM28XX_R0F_XCLK);
+	em28xx_write_reg(dev, EM28XX_R0F_XCLK,
+			 (EM28XX_XCLK_AUDIO_UNMUTE | val));
+
+	em28xx_set_outfmt(dev);
+
+	/* Add image controls */
+
+	/*
+	 * NOTE: at this point, the subdevices are already registered, so
+	 * bridge controls are only added/enabled when no subdevice provides
+	 * them
+	 */
+	if (!v4l2_ctrl_find(hdl, V4L2_CID_CONTRAST))
+		v4l2_ctrl_new_std(hdl, &em28xx_ctrl_ops,
+				  V4L2_CID_CONTRAST,
+				  0, 0x1f, 1, CONTRAST_DEFAULT);
+	if (!v4l2_ctrl_find(hdl, V4L2_CID_BRIGHTNESS))
+		v4l2_ctrl_new_std(hdl, &em28xx_ctrl_ops,
+				  V4L2_CID_BRIGHTNESS,
+				  -0x80, 0x7f, 1, BRIGHTNESS_DEFAULT);
+	if (!v4l2_ctrl_find(hdl, V4L2_CID_SATURATION))
+		v4l2_ctrl_new_std(hdl, &em28xx_ctrl_ops,
+				  V4L2_CID_SATURATION,
+				  0, 0x1f, 1, SATURATION_DEFAULT);
+	if (!v4l2_ctrl_find(hdl, V4L2_CID_BLUE_BALANCE))
+		v4l2_ctrl_new_std(hdl, &em28xx_ctrl_ops,
+				  V4L2_CID_BLUE_BALANCE,
+				  -0x30, 0x30, 1, BLUE_BALANCE_DEFAULT);
+	if (!v4l2_ctrl_find(hdl, V4L2_CID_RED_BALANCE))
+		v4l2_ctrl_new_std(hdl, &em28xx_ctrl_ops,
+				  V4L2_CID_RED_BALANCE,
+				  -0x30, 0x30, 1, RED_BALANCE_DEFAULT);
+	if (!v4l2_ctrl_find(hdl, V4L2_CID_SHARPNESS))
+		v4l2_ctrl_new_std(hdl, &em28xx_ctrl_ops,
+				  V4L2_CID_SHARPNESS,
+				  0, 0x0f, 1, SHARPNESS_DEFAULT);
+
+	/* Reset image controls */
+	em28xx_colorlevels_set_default(dev);
+	v4l2_ctrl_handler_setup(hdl);
+	ret = hdl->error;
+	if (ret)
+		goto unregister_dev;
+
+	/* allocate and fill video video_device struct */
+	em28xx_vdev_init(dev, &v4l2->vdev, &em28xx_video_template, "video");
+	mutex_init(&v4l2->vb_queue_lock);
+	mutex_init(&v4l2->vb_vbi_queue_lock);
+	v4l2->vdev.queue = &v4l2->vb_vidq;
+	v4l2->vdev.queue->lock = &v4l2->vb_queue_lock;
+
+	/* disable inapplicable ioctls */
+	if (dev->is_webcam) {
+		v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_QUERYSTD);
+		v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_G_STD);
+		v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_S_STD);
+	} else {
+		v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_S_PARM);
+	}
+	if (dev->tuner_type == TUNER_ABSENT) {
+		v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_G_TUNER);
+		v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_S_TUNER);
+		v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_G_FREQUENCY);
+		v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_S_FREQUENCY);
+	}
+	if (dev->int_audio_type == EM28XX_INT_AUDIO_NONE) {
+		v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_G_AUDIO);
+		v4l2_disable_ioctl(&v4l2->vdev, VIDIOC_S_AUDIO);
+	}
+
+	/* register v4l2 video video_device */
+	ret = video_register_device(&v4l2->vdev, VFL_TYPE_GRABBER,
+				    video_nr[dev->devno]);
+	if (ret) {
+		dev_err(&dev->intf->dev,
+			"unable to register video device (error=%i).\n", ret);
+		goto unregister_dev;
+	}
+
+	/* Allocate and fill vbi video_device struct */
+	if (em28xx_vbi_supported(dev) == 1) {
+		em28xx_vdev_init(dev, &v4l2->vbi_dev, &em28xx_video_template,
+				 "vbi");
+
+		v4l2->vbi_dev.queue = &v4l2->vb_vbiq;
+		v4l2->vbi_dev.queue->lock = &v4l2->vb_vbi_queue_lock;
+
+		/* disable inapplicable ioctls */
+		v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_S_PARM);
+		if (dev->tuner_type == TUNER_ABSENT) {
+			v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_G_TUNER);
+			v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_S_TUNER);
+			v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_G_FREQUENCY);
+			v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_S_FREQUENCY);
+		}
+		if (dev->int_audio_type == EM28XX_INT_AUDIO_NONE) {
+			v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_G_AUDIO);
+			v4l2_disable_ioctl(&v4l2->vbi_dev, VIDIOC_S_AUDIO);
+		}
+
+		/* register v4l2 vbi video_device */
+		ret = video_register_device(&v4l2->vbi_dev, VFL_TYPE_VBI,
+					    vbi_nr[dev->devno]);
+		if (ret < 0) {
+			dev_err(&dev->intf->dev,
+				"unable to register vbi device\n");
+			goto unregister_dev;
+		}
+	}
+
+	if (em28xx_boards[dev->model].radio.type == EM28XX_RADIO) {
+		em28xx_vdev_init(dev, &v4l2->radio_dev, &em28xx_radio_template,
+				 "radio");
+		ret = video_register_device(&v4l2->radio_dev, VFL_TYPE_RADIO,
+					    radio_nr[dev->devno]);
+		if (ret < 0) {
+			dev_err(&dev->intf->dev,
+				"can't register radio device\n");
+			goto unregister_dev;
+		}
+		dev_info(&dev->intf->dev,
+			 "Registered radio device as %s\n",
+			 video_device_node_name(&v4l2->radio_dev));
+	}
+
+	/* Init entities at the Media Controller */
+	em28xx_v4l2_create_entities(dev);
+
+#ifdef CONFIG_MEDIA_CONTROLLER
+	ret = v4l2_mc_create_media_graph(dev->media_dev);
+	if (ret) {
+		dev_err(&dev->intf->dev,
+			"failed to create media graph\n");
+		em28xx_v4l2_media_release(dev);
+		goto unregister_dev;
+	}
+#endif
+
+	dev_info(&dev->intf->dev,
+		 "V4L2 video device registered as %s\n",
+		 video_device_node_name(&v4l2->vdev));
+
+	if (video_is_registered(&v4l2->vbi_dev))
+		dev_info(&dev->intf->dev,
+			 "V4L2 VBI device registered as %s\n",
+			 video_device_node_name(&v4l2->vbi_dev));
+
+	/* Save some power by putting tuner to sleep */
+	v4l2_device_call_all(&v4l2->v4l2_dev, 0, tuner, standby);
+
+	/* initialize videobuf2 stuff */
+	em28xx_vb2_setup(dev);
+
+	dev_info(&dev->intf->dev,
+		 "V4L2 extension successfully initialized\n");
+
+	kref_get(&dev->ref);
+
+	mutex_unlock(&dev->lock);
+	return 0;
+
+unregister_dev:
+	if (video_is_registered(&v4l2->radio_dev)) {
+		dev_info(&dev->intf->dev,
+			 "V4L2 device %s deregistered\n",
+			 video_device_node_name(&v4l2->radio_dev));
+		video_unregister_device(&v4l2->radio_dev);
+	}
+	if (video_is_registered(&v4l2->vbi_dev)) {
+		dev_info(&dev->intf->dev,
+			 "V4L2 device %s deregistered\n",
+			 video_device_node_name(&v4l2->vbi_dev));
+		video_unregister_device(&v4l2->vbi_dev);
+	}
+	if (video_is_registered(&v4l2->vdev)) {
+		dev_info(&dev->intf->dev,
+			 "V4L2 device %s deregistered\n",
+			 video_device_node_name(&v4l2->vdev));
+		video_unregister_device(&v4l2->vdev);
+	}
+
+	v4l2_ctrl_handler_free(&v4l2->ctrl_handler);
+	v4l2_device_unregister(&v4l2->v4l2_dev);
+err:
+	dev->v4l2 = NULL;
+	kref_put(&v4l2->ref, em28xx_free_v4l2);
+	mutex_unlock(&dev->lock);
+	return ret;
+}
+
+static struct em28xx_ops v4l2_ops = {
+	.id   = EM28XX_V4L2,
+	.name = "Em28xx v4l2 Extension",
+	.init = em28xx_v4l2_init,
+	.fini = em28xx_v4l2_fini,
+	.suspend = em28xx_v4l2_suspend,
+	.resume = em28xx_v4l2_resume,
+};
+
+static int __init em28xx_video_register(void)
+{
+	return em28xx_register_extension(&v4l2_ops);
+}
+
+static void __exit em28xx_video_unregister(void)
+{
+	em28xx_unregister_extension(&v4l2_ops);
+}
+
+module_init(em28xx_video_register);
+module_exit(em28xx_video_unregister);
diff --git a/drivers/media/usb/em28xx/em28xx.h b/drivers/media/usb/em28xx/em28xx.h
new file mode 100644
index 0000000..a551072
--- /dev/null
+++ b/drivers/media/usb/em28xx/em28xx.h
@@ -0,0 +1,855 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * em28xx.h - driver for Empia EM2800/EM2820/2840 USB video capture devices
+ *
+ * Copyright (C) 2005 Markus Rechberger <mrechberger@gmail.com>
+ *		      Ludovico Cavedon <cavedon@sssup.it>
+ *		      Mauro Carvalho Chehab <mchehab@kernel.org>
+ * Copyright (C) 2012 Frank Schäfer <fschaefer.oss@googlemail.com>
+ *
+ * Based on the em2800 driver from Sascha Sommer <saschasommer@freenet.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef _EM28XX_H
+#define _EM28XX_H
+
+#include <linux/bitfield.h>
+
+#define EM28XX_VERSION "0.2.2"
+#define DRIVER_DESC    "Empia em28xx device driver"
+
+#include <linux/workqueue.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/kref.h>
+#include <linux/videodev2.h>
+
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-vmalloc.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-fh.h>
+#include <media/i2c/ir-kbd-i2c.h>
+#include <media/rc-core.h>
+#include "tuner-xc2028.h"
+#include "xc5000.h"
+#include "em28xx-reg.h"
+
+/* Boards supported by driver */
+#define EM2800_BOARD_UNKNOWN			  0
+#define EM2820_BOARD_UNKNOWN			  1
+#define EM2820_BOARD_TERRATEC_CINERGY_250	  2
+#define EM2820_BOARD_PINNACLE_USB_2		  3
+#define EM2820_BOARD_HAUPPAUGE_WINTV_USB_2	  4
+#define EM2820_BOARD_MSI_VOX_USB_2		  5
+#define EM2800_BOARD_TERRATEC_CINERGY_200	  6
+#define EM2800_BOARD_LEADTEK_WINFAST_USBII	  7
+#define EM2800_BOARD_KWORLD_USB2800		  8
+#define EM2820_BOARD_PINNACLE_DVC_90		  9
+#define EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900	  10
+#define EM2880_BOARD_TERRATEC_HYBRID_XS		  11
+#define EM2820_BOARD_KWORLD_PVRTV2800RF		  12
+#define EM2880_BOARD_TERRATEC_PRODIGY_XS	  13
+#define EM2820_BOARD_PROLINK_PLAYTV_USB2	  14
+#define EM2800_BOARD_VGEAR_POCKETTV		  15
+#define EM2883_BOARD_HAUPPAUGE_WINTV_HVR_950	  16
+#define EM2880_BOARD_PINNACLE_PCTV_HD_PRO	  17
+#define EM2880_BOARD_HAUPPAUGE_WINTV_HVR_900_R2	  18
+#define EM2860_BOARD_SAA711X_REFERENCE_DESIGN	  19
+#define EM2880_BOARD_AMD_ATI_TV_WONDER_HD_600	  20
+#define EM2800_BOARD_GRABBEEX_USB2800		  21
+#define EM2750_BOARD_UNKNOWN			  22
+#define EM2750_BOARD_DLCW_130			  23
+#define EM2820_BOARD_DLINK_USB_TV		  24
+#define EM2820_BOARD_GADMEI_UTV310		  25
+#define EM2820_BOARD_HERCULES_SMART_TV_USB2	  26
+#define EM2820_BOARD_PINNACLE_USB_2_FM1216ME	  27
+#define EM2820_BOARD_LEADTEK_WINFAST_USBII_DELUXE 28
+#define EM2860_BOARD_TVP5150_REFERENCE_DESIGN	  29
+#define EM2820_BOARD_VIDEOLOGY_20K14XUSB	  30
+#define EM2821_BOARD_USBGEAR_VD204		  31
+#define EM2821_BOARD_SUPERCOMP_USB_2		  32
+#define EM2860_BOARD_ELGATO_VIDEO_CAPTURE	  33
+#define EM2860_BOARD_TERRATEC_HYBRID_XS		  34
+#define EM2860_BOARD_TYPHOON_DVD_MAKER		  35
+#define EM2860_BOARD_NETGMBH_CAM		  36
+#define EM2860_BOARD_GADMEI_UTV330		  37
+#define EM2861_BOARD_YAKUMO_MOVIE_MIXER		  38
+#define EM2861_BOARD_KWORLD_PVRTV_300U		  39
+#define EM2861_BOARD_PLEXTOR_PX_TV100U		  40
+#define EM2870_BOARD_KWORLD_350U		  41
+#define EM2870_BOARD_KWORLD_355U		  42
+#define EM2870_BOARD_TERRATEC_XS		  43
+#define EM2870_BOARD_TERRATEC_XS_MT2060		  44
+#define EM2870_BOARD_PINNACLE_PCTV_DVB		  45
+#define EM2870_BOARD_COMPRO_VIDEOMATE		  46
+#define EM2880_BOARD_KWORLD_DVB_305U		  47
+#define EM2880_BOARD_KWORLD_DVB_310U		  48
+#define EM2880_BOARD_MSI_DIGIVOX_AD		  49
+#define EM2880_BOARD_MSI_DIGIVOX_AD_II		  50
+#define EM2880_BOARD_TERRATEC_HYBRID_XS_FR	  51
+#define EM2881_BOARD_DNT_DA2_HYBRID		  52
+#define EM2881_BOARD_PINNACLE_HYBRID_PRO	  53
+#define EM2882_BOARD_KWORLD_VS_DVBT		  54
+#define EM2882_BOARD_TERRATEC_HYBRID_XS		  55
+#define EM2882_BOARD_PINNACLE_HYBRID_PRO_330E	  56
+#define EM2883_BOARD_KWORLD_HYBRID_330U		  57
+#define EM2820_BOARD_COMPRO_VIDEOMATE_FORYOU	  58
+#define EM2874_BOARD_PCTV_HD_MINI_80E		  59
+#define EM2883_BOARD_HAUPPAUGE_WINTV_HVR_850	  60
+#define EM2820_BOARD_PROLINK_PLAYTV_BOX4_USB2	  61
+#define EM2820_BOARD_GADMEI_TVR200		  62
+#define EM2860_BOARD_KAIOMY_TVNPC_U2		  63
+#define EM2860_BOARD_EASYCAP			  64
+#define EM2820_BOARD_IODATA_GVMVP_SZ		  65
+#define EM2880_BOARD_EMPIRE_DUAL_TV		  66
+#define EM2860_BOARD_TERRATEC_GRABBY		  67
+#define EM2860_BOARD_TERRATEC_AV350		  68
+#define EM2882_BOARD_KWORLD_ATSC_315U		  69
+#define EM2882_BOARD_EVGA_INDTUBE		  70
+#define EM2820_BOARD_SILVERCREST_WEBCAM		  71
+#define EM2861_BOARD_GADMEI_UTV330PLUS		  72
+#define EM2870_BOARD_REDDO_DVB_C_USB_BOX	  73
+#define EM2800_BOARD_VC211A			  74
+#define EM2882_BOARD_DIKOM_DK300		  75
+#define EM2870_BOARD_KWORLD_A340		  76
+#define EM2874_BOARD_LEADERSHIP_ISDBT		  77
+#define EM28174_BOARD_PCTV_290E			  78
+#define EM2884_BOARD_TERRATEC_H5		  79
+#define EM28174_BOARD_PCTV_460E			  80
+#define EM2884_BOARD_HAUPPAUGE_WINTV_HVR_930C	  81
+#define EM2884_BOARD_CINERGY_HTC_STICK		  82
+#define EM2860_BOARD_HT_VIDBOX_NW03		  83
+#define EM2874_BOARD_MAXMEDIA_UB425_TC		  84
+#define EM2884_BOARD_PCTV_510E			  85
+#define EM2884_BOARD_PCTV_520E			  86
+#define EM2884_BOARD_TERRATEC_HTC_USB_XS	  87
+#define EM2884_BOARD_C3TECH_DIGITAL_DUO		  88
+#define EM2874_BOARD_DELOCK_61959		  89
+#define EM2874_BOARD_KWORLD_UB435Q_V2		  90
+#define EM2765_BOARD_SPEEDLINK_VAD_LAPLACE	  91
+#define EM28178_BOARD_PCTV_461E                   92
+#define EM2874_BOARD_KWORLD_UB435Q_V3		  93
+#define EM28178_BOARD_PCTV_292E                   94
+#define EM2861_BOARD_LEADTEK_VC100                95
+#define EM28178_BOARD_TERRATEC_T2_STICK_HD        96
+#define EM2884_BOARD_ELGATO_EYETV_HYBRID_2008     97
+#define EM28178_BOARD_PLEX_PX_BCUD                98
+#define EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_DVB  99
+#define EM28174_BOARD_HAUPPAUGE_WINTV_DUALHD_01595 100
+#define EM2884_BOARD_TERRATEC_H6		  101
+#define EM2882_BOARD_ZOLID_HYBRID_TV_STICK		102
+
+/* Limits minimum and default number of buffers */
+#define EM28XX_MIN_BUF 4
+#define EM28XX_DEF_BUF 8
+
+/*Limits the max URB message size */
+#define URB_MAX_CTRL_SIZE 80
+
+/* Params for validated field */
+#define EM28XX_BOARD_NOT_VALIDATED 1
+#define EM28XX_BOARD_VALIDATED	   0
+
+/* Params for em28xx_cmd() audio */
+#define EM28XX_START_AUDIO      1
+#define EM28XX_STOP_AUDIO       0
+
+/* maximum number of em28xx boards */
+#define EM28XX_MAXBOARDS DVB_MAX_ADAPTERS /* All adapters could be em28xx */
+
+/* maximum number of frames that can be queued */
+#define EM28XX_NUM_FRAMES 5
+/* number of frames that get used for v4l2_read() */
+#define EM28XX_NUM_READ_FRAMES 2
+
+/* number of buffers for isoc transfers */
+#define EM28XX_NUM_BUFS 5
+#define EM28XX_DVB_NUM_BUFS 5
+
+/* max number of I2C buses on em28xx devices */
+#define NUM_I2C_BUSES	2
+
+/*
+ * isoc transfers: number of packets for each buffer
+ * windows requests only 64 packets .. so we better do the same
+ * this is what I found out for all alternate numbers there!
+ */
+#define EM28XX_NUM_ISOC_PACKETS 64
+#define EM28XX_DVB_NUM_ISOC_PACKETS 64
+
+/*
+ * bulk transfers: transfer buffer size = packet size * packet multiplier
+ * USB 2.0 spec says bulk packet size is always 512 bytes
+ */
+#define EM28XX_BULK_PACKET_MULTIPLIER 384
+#define EM28XX_DVB_BULK_PACKET_MULTIPLIER 94
+
+#define EM28XX_INTERLACED_DEFAULT 1
+
+/* time in msecs to wait for AC97 xfers to finish */
+#define EM28XX_AC97_XFER_TIMEOUT	100
+
+/* max. number of button state polling addresses */
+#define EM28XX_NUM_BUTTON_ADDRESSES_MAX		5
+
+#define PRIMARY_TS	0
+#define SECONDARY_TS	1
+
+enum em28xx_mode {
+	EM28XX_SUSPEND,
+	EM28XX_ANALOG_MODE,
+	EM28XX_DIGITAL_MODE,
+};
+
+struct em28xx;
+
+/**
+ * struct em28xx_usb_bufs - Contains URB-related buffer data
+ *
+ * @max_pkt_size:	max packet size of isoc transaction
+ * @num_packets:	number of packets in each buffer
+ * @num_bufs:		number of allocated urb
+ * @urb:		urb for isoc/bulk transfers
+ * @buf:		transfer buffers for isoc/bulk transfer
+ */
+struct em28xx_usb_bufs {
+	int				max_pkt_size;
+	int				num_packets;
+	int				num_bufs;
+	struct urb			**urb;
+	char				**buf;
+};
+
+/**
+ * struct em28xx_usb_ctl - Contains URB-related buffer data
+ *
+ * @analog_bufs:	isoc/bulk transfer buffers for analog mode
+ * @digital_bufs:	isoc/bulk transfer buffers for digital mode
+ * @vid_buf:		Stores already requested video buffers
+ * @vbi_buf:		Stores already requested VBI buffers
+ * @urb_data_copy:	copy data from URB
+ */
+struct em28xx_usb_ctl {
+	struct em28xx_usb_bufs		analog_bufs;
+	struct em28xx_usb_bufs		digital_bufs;
+	struct em28xx_buffer	*vid_buf;
+	struct em28xx_buffer	*vbi_buf;
+	int (*urb_data_copy)(struct em28xx *dev, struct urb *urb);
+};
+
+/**
+ * struct em28xx_fmt - Struct to enumberate video formats
+ *
+ * @name:	Name for the video standard
+ * @fourcc:	v4l2 format id
+ * @depth:	mean number of bits to represent a pixel
+ * @reg:	em28xx register value to set it
+ */
+struct em28xx_fmt {
+	char	*name;
+	u32	fourcc;
+	int	depth;
+	int	reg;
+};
+
+/**
+ * struct em28xx_buffer- buffer for storing one video frame
+ *
+ * @vb:		common v4l buffer stuff
+ * @list:	List to associate it with the other buffers
+ * @mem:	pointer to the buffer, as returned by vb2_plane_vaddr()
+ * @length:	length of the buffer, as returned by vb2_plane_size()
+ * @top_field:	If non-zero, indicate that the buffer is the top field
+ * @pos:	Indicate the next position of the buffer to be filled.
+ * @vb_buf:	pointer to vmalloc memory address in vb
+ *
+ * .. note::
+ *
+ *    in interlaced mode, @pos is reset to zero at the start of each new
+ *    field (not frame !)
+ */
+struct em28xx_buffer {
+	struct vb2_v4l2_buffer	vb;		/* must be first */
+
+	struct list_head	list;
+
+	void			*mem;
+	unsigned int		length;
+	int			top_field;
+
+	unsigned int		pos;
+
+	char			*vb_buf;
+};
+
+struct em28xx_dmaqueue {
+	struct list_head       active;
+
+	wait_queue_head_t          wq;
+};
+
+/* inputs */
+
+#define MAX_EM28XX_INPUT 4
+enum enum28xx_itype {
+	EM28XX_VMUX_COMPOSITE = 1,
+	EM28XX_VMUX_SVIDEO,
+	EM28XX_VMUX_TELEVISION,
+	EM28XX_RADIO,
+};
+
+enum em28xx_ac97_mode {
+	EM28XX_NO_AC97 = 0,
+	EM28XX_AC97_EM202,
+	EM28XX_AC97_SIGMATEL,
+	EM28XX_AC97_OTHER,
+};
+
+struct em28xx_audio_mode {
+	enum em28xx_ac97_mode ac97;
+};
+
+enum em28xx_int_audio_type {
+	EM28XX_INT_AUDIO_NONE = 0,
+	EM28XX_INT_AUDIO_AC97,
+	EM28XX_INT_AUDIO_I2S,
+};
+
+enum em28xx_usb_audio_type {
+	EM28XX_USB_AUDIO_NONE = 0,
+	EM28XX_USB_AUDIO_CLASS,
+	EM28XX_USB_AUDIO_VENDOR,
+};
+
+/**
+ * em28xx_amux - describes the type of audio input used by em28xx
+ *
+ * @EM28XX_AMUX_UNUSED:
+ *	Used only on em28xx dev->map field, in order to mark an entry
+ *	as unused.
+ * @EM28XX_AMUX_VIDEO:
+ *	On devices without AC97, this is the only value that it is currently
+ *	allowed.
+ *	On devices with AC97, it corresponds to the AC97 mixer "Video" control.
+ * @EM28XX_AMUX_LINE_IN:
+ *	Only for devices with AC97. Corresponds to AC97 mixer "Line In".
+ * @EM28XX_AMUX_VIDEO2:
+ *	Only for devices with AC97. It means that em28xx should use "Line In"
+ *	And AC97 should use the "Video" mixer control.
+ * @EM28XX_AMUX_PHONE:
+ *	Only for devices with AC97. Corresponds to AC97 mixer "Phone".
+ * @EM28XX_AMUX_MIC:
+ *	Only for devices with AC97. Corresponds to AC97 mixer "Mic".
+ * @EM28XX_AMUX_CD:
+ *	Only for devices with AC97. Corresponds to AC97 mixer "CD".
+ * @EM28XX_AMUX_AUX:
+ *	Only for devices with AC97. Corresponds to AC97 mixer "Aux".
+ * @EM28XX_AMUX_PCM_OUT:
+ *	Only for devices with AC97. Corresponds to AC97 mixer "PCM out".
+ *
+ * The em28xx chip itself has only two audio inputs: tuner and line in.
+ * On almost all devices, only the tuner input is used.
+ *
+ * However, on most devices, an auxiliary AC97 codec device is used,
+ * usually connected to the em28xx tuner input (except for
+ * @EM28XX_AMUX_LINE_IN).
+ *
+ * The AC97 device typically have several different inputs and outputs.
+ * The exact number and description depends on their model.
+ *
+ * It is possible to AC97 to mixer more than one different entries at the
+ * same time, via the alsa mux.
+ */
+enum em28xx_amux {
+	EM28XX_AMUX_UNUSED = -1,
+	EM28XX_AMUX_VIDEO = 0,
+	EM28XX_AMUX_LINE_IN,
+
+	/* Some less-common mixer setups */
+	EM28XX_AMUX_VIDEO2,
+	EM28XX_AMUX_PHONE,
+	EM28XX_AMUX_MIC,
+	EM28XX_AMUX_CD,
+	EM28XX_AMUX_AUX,
+	EM28XX_AMUX_PCM_OUT,
+};
+
+enum em28xx_aout {
+	/* AC97 outputs */
+	EM28XX_AOUT_MASTER = BIT(0),
+	EM28XX_AOUT_LINE   = BIT(1),
+	EM28XX_AOUT_MONO   = BIT(2),
+	EM28XX_AOUT_LFE    = BIT(3),
+	EM28XX_AOUT_SURR   = BIT(4),
+
+	/* PCM IN Mixer - used by AC97_RECORD_SELECT register */
+	EM28XX_AOUT_PCM_IN = BIT(7),
+
+	/* Bits 10-8 are used to indicate the PCM IN record select */
+	EM28XX_AOUT_PCM_MIC_PCM = 0 << 8,
+	EM28XX_AOUT_PCM_CD	= 1 << 8,
+	EM28XX_AOUT_PCM_VIDEO	= 2 << 8,
+	EM28XX_AOUT_PCM_AUX	= 3 << 8,
+	EM28XX_AOUT_PCM_LINE	= 4 << 8,
+	EM28XX_AOUT_PCM_STEREO	= 5 << 8,
+	EM28XX_AOUT_PCM_MONO	= 6 << 8,
+	EM28XX_AOUT_PCM_PHONE	= 7 << 8,
+};
+
+static inline int ac97_return_record_select(int a_out)
+{
+	return (a_out & 0x700) >> 8;
+}
+
+struct em28xx_reg_seq {
+	int reg;
+	unsigned char val, mask;
+	int sleep;
+};
+
+struct em28xx_input {
+	enum enum28xx_itype type;
+	unsigned int vmux;
+	enum em28xx_amux amux;
+	enum em28xx_aout aout;
+	const struct em28xx_reg_seq *gpio;
+};
+
+#define INPUT(nr) (&em28xx_boards[dev->model].input[nr])
+
+enum em28xx_decoder {
+	EM28XX_NODECODER = 0,
+	EM28XX_TVP5150,
+	EM28XX_SAA711X,
+};
+
+enum em28xx_sensor {
+	EM28XX_NOSENSOR = 0,
+	EM28XX_MT9V011,
+	EM28XX_MT9M001,
+	EM28XX_MT9M111,
+	EM28XX_OV2640,
+};
+
+enum em28xx_adecoder {
+	EM28XX_NOADECODER = 0,
+	EM28XX_TVAUDIO,
+};
+
+enum em28xx_led_role {
+	EM28XX_LED_ANALOG_CAPTURING = 0,
+	EM28XX_LED_DIGITAL_CAPTURING,
+	EM28XX_LED_DIGITAL_CAPTURING_TS2,
+	EM28XX_LED_ILLUMINATION,
+	EM28XX_NUM_LED_ROLES, /* must be the last */
+};
+
+struct em28xx_led {
+	enum em28xx_led_role role;
+	u8 gpio_reg;
+	u8 gpio_mask;
+	bool inverted;
+};
+
+enum em28xx_button_role {
+	EM28XX_BUTTON_SNAPSHOT = 0,
+	EM28XX_BUTTON_ILLUMINATION,
+	EM28XX_NUM_BUTTON_ROLES, /* must be the last */
+};
+
+struct em28xx_button {
+	enum em28xx_button_role role;
+	u8 reg_r;
+	u8 reg_clearing;
+	u8 mask;
+	bool inverted;
+};
+
+struct em28xx_board {
+	char *name;
+	int vchannels;
+	int tuner_type;
+	int tuner_addr;
+	unsigned int def_i2c_bus;	/* Default I2C bus */
+
+	/* i2c flags */
+	unsigned int tda9887_conf;
+
+	/* GPIO sequences */
+	const struct em28xx_reg_seq *dvb_gpio;
+	const struct em28xx_reg_seq *suspend_gpio;
+	const struct em28xx_reg_seq *tuner_gpio;
+	const struct em28xx_reg_seq *mute_gpio;
+
+	unsigned int is_em2800:1;
+	unsigned int has_msp34xx:1;
+	unsigned int mts_firmware:1;
+	unsigned int max_range_640_480:1;
+	unsigned int has_dvb:1;
+	unsigned int has_dual_ts:1;
+	unsigned int is_webcam:1;
+	unsigned int valid:1;
+	unsigned int has_ir_i2c:1;
+
+	unsigned char xclk, i2c_speed;
+	unsigned char radio_addr;
+	unsigned short tvaudio_addr;
+
+	enum em28xx_decoder decoder;
+	enum em28xx_adecoder adecoder;
+
+	struct em28xx_input       input[MAX_EM28XX_INPUT];
+	struct em28xx_input	  radio;
+	char			  *ir_codes;
+
+	/* LEDs that need to be controlled explicitly */
+	struct em28xx_led	  *leds;
+
+	/* Buttons */
+	const struct em28xx_button *buttons;
+};
+
+struct em28xx_eeprom {
+	u8 id[4];			/* 1a eb 67 95 */
+	__le16 vendor_ID;
+	__le16 product_ID;
+
+	__le16 chip_conf;
+
+	__le16 board_conf;
+
+	__le16 string1, string2, string3;
+
+	u8 string_idx_table;
+};
+
+#define EM28XX_CAPTURE_STREAM_EN 1
+
+/* em28xx extensions */
+#define EM28XX_AUDIO   0x10
+#define EM28XX_DVB     0x20
+#define EM28XX_RC      0x30
+#define EM28XX_V4L2    0x40
+
+/* em28xx resource types (used for res_get/res_lock etc */
+#define EM28XX_RESOURCE_VIDEO 0x01
+#define EM28XX_RESOURCE_VBI   0x02
+
+struct em28xx_v4l2 {
+	struct kref ref;
+	struct em28xx *dev;
+
+	struct v4l2_device v4l2_dev;
+	struct v4l2_ctrl_handler ctrl_handler;
+
+	struct video_device vdev;
+	struct video_device vbi_dev;
+	struct video_device radio_dev;
+
+	/* Videobuf2 */
+	struct vb2_queue vb_vidq;
+	struct vb2_queue vb_vbiq;
+	struct mutex vb_queue_lock;	/* Protects vb_vidq */
+	struct mutex vb_vbi_queue_lock;	/* Protects vb_vbiq */
+
+	u8 vinmode;
+	u8 vinctl;
+
+	/* Camera specific fields */
+	int sensor_xres;
+	int sensor_yres;
+	int sensor_xtal;
+
+	int users;		/* user count for exclusive use */
+	int streaming_users;    /* number of actively streaming users */
+
+	u32 frequency;		/* selected tuner frequency */
+
+	struct em28xx_fmt *format;
+	v4l2_std_id norm;	/* selected tv norm */
+
+	/* Progressive/interlaced mode */
+	bool progressive;
+	int interlaced_fieldmode; /* 1=interlaced fields, 0=just top fields */
+	/* FIXME: everything else than interlaced_fieldmode=1 doesn't work */
+
+	/* Frame properties */
+	int width;		/* current frame width */
+	int height;		/* current frame height */
+	unsigned int hscale;	/* horizontal scale factor (see datasheet) */
+	unsigned int vscale;	/* vertical scale factor (see datasheet) */
+	unsigned int vbi_width;
+	unsigned int vbi_height; /* lines per field */
+
+	/* Capture state tracking */
+	int capture_type;
+	bool top_field;
+	int vbi_read;
+	unsigned int field_count;
+
+#ifdef CONFIG_MEDIA_CONTROLLER
+	struct media_pad video_pad, vbi_pad;
+	struct media_entity *decoder;
+#endif
+};
+
+struct em28xx_audio {
+	char name[50];
+	unsigned int num_urb;
+	char **transfer_buffer;
+	struct urb **urb;
+	struct usb_device *udev;
+	unsigned int capture_transfer_done;
+	struct snd_pcm_substream   *capture_pcm_substream;
+
+	unsigned int hwptr_done_capture;
+	struct snd_card            *sndcard;
+
+	size_t period;
+
+	int users;
+	spinlock_t slock;		/* Protects struct em28xx_audio */
+
+	/* Controls streaming */
+	struct work_struct wq_trigger;	/* trigger to start/stop audio */
+	atomic_t       stream_started;	/* stream should be running if true */
+};
+
+struct em28xx;
+
+enum em28xx_i2c_algo_type {
+	EM28XX_I2C_ALGO_EM28XX = 0,
+	EM28XX_I2C_ALGO_EM2800,
+	EM28XX_I2C_ALGO_EM25XX_BUS_B,
+};
+
+struct em28xx_i2c_bus {
+	struct em28xx *dev;
+
+	unsigned int bus;
+	enum em28xx_i2c_algo_type algo_type;
+};
+
+/* main device struct */
+struct em28xx {
+	struct kref ref;
+
+	// Sub-module data
+	struct em28xx_v4l2 *v4l2;
+	struct em28xx_dvb *dvb;
+	struct em28xx_audio adev;
+	struct em28xx_IR *ir;
+
+	// generic device properties
+	int model;		// index in the device_data struct
+	int devno;		// marks the number of this device
+	enum em28xx_chip_id chip_id;
+
+	unsigned int is_em25xx:1;	// em25xx/em276x/7x/8x family bridge
+	unsigned int disconnected:1;	// device has been diconnected
+	unsigned int has_video:1;
+	unsigned int is_audio_only:1;
+	unsigned int is_webcam:1;
+	unsigned int has_msp34xx:1;
+	unsigned int i2c_speed:2;
+	enum em28xx_int_audio_type int_audio_type;
+	enum em28xx_usb_audio_type usb_audio_type;
+	unsigned char name[32];
+
+	struct em28xx_board board;
+
+	enum em28xx_sensor em28xx_sensor;	// camera specific
+
+	// Some older em28xx chips needs a waiting time after writing
+	unsigned int wait_after_write;
+
+	struct list_head	devlist;
+
+	u32 i2s_speed;		// I2S speed for audio digital stream
+
+	struct em28xx_audio_mode audio_mode;
+
+	int tuner_type;		// type of the tuner
+
+	// i2c i/o
+	struct i2c_adapter i2c_adap[NUM_I2C_BUSES];
+	struct i2c_client i2c_client[NUM_I2C_BUSES];
+	struct em28xx_i2c_bus i2c_bus[NUM_I2C_BUSES];
+
+	unsigned char eeprom_addrwidth_16bit:1;
+	unsigned int def_i2c_bus;	// Default I2C bus
+	unsigned int cur_i2c_bus;	// Current I2C bus
+	struct rt_mutex i2c_bus_lock;
+
+	// video for linux
+	unsigned int ctl_input;	// selected input
+	unsigned int ctl_ainput;// selected audio input
+	unsigned int ctl_aoutput;// selected audio output
+	enum em28xx_amux amux_map[MAX_EM28XX_INPUT];
+
+	int mute;
+	int volume;
+
+	unsigned long hash;	// eeprom hash - for boards with generic ID
+	unsigned long i2c_hash;	// i2c devicelist hash -
+				// for boards with generic ID
+
+	struct work_struct         request_module_wk;
+
+	// locks
+	struct mutex lock;		/* protects em28xx struct */
+	struct mutex ctrl_urb_lock;	/* protects urb_buf */
+
+	// resources in use
+	unsigned int resources;
+
+	// eeprom content
+	u8 *eedata;
+	u16 eedata_len;
+
+	// Isoc control struct
+	struct em28xx_dmaqueue vidq;
+	struct em28xx_dmaqueue vbiq;
+	struct em28xx_usb_ctl usb_ctl;
+
+	spinlock_t slock; /* Protects em28xx video/vbi/dvb IRQ stream data */
+
+	// usb transfer
+	struct usb_interface *intf;	// the usb interface
+	u8 ifnum;		// number of the assigned usb interface
+	u8 analog_ep_isoc;	// address of isoc endpoint for analog
+	u8 analog_ep_bulk;	// address of bulk endpoint for analog
+	u8 dvb_ep_isoc_ts2;	// address of isoc endpoint for DVB TS2
+	u8 dvb_ep_bulk_ts2;	// address of bulk endpoint for DVB TS2
+	u8 dvb_ep_isoc;		// address of isoc endpoint for DVB
+	u8 dvb_ep_bulk;		// address of bulk endpoint for DVB
+	int alt;		// alternate setting
+	int max_pkt_size;	// max packet size of the selected ep at alt
+	int packet_multiplier;	// multiplier for wMaxPacketSize, used for
+				// URB buffer size definition
+	int num_alt;		// number of alternative settings
+	unsigned int *alt_max_pkt_size_isoc; // array of isoc wMaxPacketSize
+	unsigned int analog_xfer_bulk:1;	// use bulk instead of isoc
+						// transfers for analog
+	int dvb_alt_isoc;	// alternate setting for DVB isoc transfers
+	unsigned int dvb_max_pkt_size_isoc;	// isoc max packet size of the
+						// selected DVB ep at dvb_alt
+	unsigned int dvb_max_pkt_size_isoc_ts2;	// isoc max packet size of the
+						// selected DVB ep at dvb_alt
+	unsigned int dvb_xfer_bulk:1;		// use bulk instead of isoc
+						// transfers for DVB
+	char urb_buf[URB_MAX_CTRL_SIZE];	// urb control msg buffer
+
+	// helper funcs that call usb_control_msg
+	int (*em28xx_write_regs)(struct em28xx *dev, u16 reg,
+				 char *buf, int len);
+	int (*em28xx_read_reg)(struct em28xx *dev, u16 reg);
+	int (*em28xx_read_reg_req_len)(struct em28xx *dev, u8 req, u16 reg,
+				       char *buf, int len);
+	int (*em28xx_write_regs_req)(struct em28xx *dev, u8 req, u16 reg,
+				     char *buf, int len);
+	int (*em28xx_read_reg_req)(struct em28xx *dev, u8 req, u16 reg);
+
+	enum em28xx_mode mode;
+
+	// Button state polling
+	struct delayed_work buttons_query_work;
+	u8 button_polling_addresses[EM28XX_NUM_BUTTON_ADDRESSES_MAX];
+	u8 button_polling_last_values[EM28XX_NUM_BUTTON_ADDRESSES_MAX];
+	u8 num_button_polling_addresses;
+	u16 button_polling_interval; // [ms]
+	// Snapshot button input device
+	char snapshot_button_path[30];	// path of the input dev
+	struct input_dev *sbutton_input_dev;
+
+#ifdef CONFIG_MEDIA_CONTROLLER
+	struct media_device *media_dev;
+	struct media_entity input_ent[MAX_EM28XX_INPUT];
+	struct media_pad input_pad[MAX_EM28XX_INPUT];
+#endif
+
+	struct em28xx	*dev_next;
+	int ts;
+};
+
+#define kref_to_dev(d) container_of(d, struct em28xx, ref)
+
+struct em28xx_ops {
+	struct list_head next;
+	char *name;
+	int id;
+	int (*init)(struct em28xx *dev);
+	int (*fini)(struct em28xx *dev);
+	int (*suspend)(struct em28xx *dev);
+	int (*resume)(struct em28xx *dev);
+};
+
+/* Provided by em28xx-i2c.c */
+void em28xx_do_i2c_scan(struct em28xx *dev, unsigned int bus);
+int  em28xx_i2c_register(struct em28xx *dev, unsigned int bus,
+			 enum em28xx_i2c_algo_type algo_type);
+int  em28xx_i2c_unregister(struct em28xx *dev, unsigned int bus);
+
+/* Provided by em28xx-core.c */
+int em28xx_read_reg_req_len(struct em28xx *dev, u8 req, u16 reg,
+			    char *buf, int len);
+int em28xx_read_reg_req(struct em28xx *dev, u8 req, u16 reg);
+int em28xx_read_reg(struct em28xx *dev, u16 reg);
+int em28xx_write_regs_req(struct em28xx *dev, u8 req, u16 reg, char *buf,
+			  int len);
+int em28xx_write_regs(struct em28xx *dev, u16 reg, char *buf, int len);
+int em28xx_write_reg(struct em28xx *dev, u16 reg, u8 val);
+int em28xx_write_reg_bits(struct em28xx *dev, u16 reg, u8 val,
+			  u8 bitmask);
+int em28xx_toggle_reg_bits(struct em28xx *dev, u16 reg, u8 bitmask);
+
+int em28xx_read_ac97(struct em28xx *dev, u8 reg);
+int em28xx_write_ac97(struct em28xx *dev, u8 reg, u16 val);
+
+int em28xx_audio_analog_set(struct em28xx *dev);
+int em28xx_audio_setup(struct em28xx *dev);
+
+const struct em28xx_led *em28xx_find_led(struct em28xx *dev,
+					 enum em28xx_led_role role);
+int em28xx_capture_start(struct em28xx *dev, int start);
+int em28xx_alloc_urbs(struct em28xx *dev, enum em28xx_mode mode, int xfer_bulk,
+		      int num_bufs, int max_pkt_size, int packet_multiplier);
+int em28xx_init_usb_xfer(struct em28xx *dev, enum em28xx_mode mode,
+			 int xfer_bulk,
+			 int num_bufs, int max_pkt_size, int packet_multiplier,
+			 int (*urb_data_copy)
+					(struct em28xx *dev, struct urb *urb));
+void em28xx_uninit_usb_xfer(struct em28xx *dev, enum em28xx_mode mode);
+void em28xx_stop_urbs(struct em28xx *dev);
+int em28xx_set_mode(struct em28xx *dev, enum em28xx_mode set_mode);
+int em28xx_gpio_set(struct em28xx *dev, const struct em28xx_reg_seq *gpio);
+int em28xx_register_extension(struct em28xx_ops *dev);
+void em28xx_unregister_extension(struct em28xx_ops *dev);
+void em28xx_init_extension(struct em28xx *dev);
+void em28xx_close_extension(struct em28xx *dev);
+int em28xx_suspend_extension(struct em28xx *dev);
+int em28xx_resume_extension(struct em28xx *dev);
+
+/* Provided by em28xx-cards.c */
+extern const struct em28xx_board em28xx_boards[];
+extern struct usb_device_id em28xx_id_table[];
+int em28xx_tuner_callback(void *ptr, int component, int command, int arg);
+void em28xx_setup_xc3028(struct em28xx *dev, struct xc2028_ctrl *ctl);
+void em28xx_free_device(struct kref *ref);
+
+/* Provided by em28xx-camera.c */
+int em28xx_detect_sensor(struct em28xx *dev);
+int em28xx_init_camera(struct em28xx *dev);
+
+#endif
diff --git a/drivers/media/usb/go7007/Kconfig b/drivers/media/usb/go7007/Kconfig
new file mode 100644
index 0000000..af1d024
--- /dev/null
+++ b/drivers/media/usb/go7007/Kconfig
@@ -0,0 +1,51 @@
+config VIDEO_GO7007
+	tristate "WIS GO7007 MPEG encoder support"
+	depends on VIDEO_DEV && I2C
+	depends on SND && USB
+	select VIDEOBUF2_VMALLOC
+	select VIDEO_TUNER
+	select CYPRESS_FIRMWARE
+	select SND_PCM
+	select VIDEO_SONY_BTF_MPX if MEDIA_SUBDRV_AUTOSELECT
+	select VIDEO_SAA711X if MEDIA_SUBDRV_AUTOSELECT
+	select VIDEO_TW2804 if MEDIA_SUBDRV_AUTOSELECT
+	select VIDEO_TW9903 if MEDIA_SUBDRV_AUTOSELECT
+	select VIDEO_TW9906 if MEDIA_SUBDRV_AUTOSELECT
+	select VIDEO_OV7640 if MEDIA_SUBDRV_AUTOSELECT && MEDIA_CAMERA_SUPPORT
+	select VIDEO_UDA1342 if MEDIA_SUBDRV_AUTOSELECT
+	---help---
+	  This is a video4linux driver for the WIS GO7007 MPEG
+	  encoder chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called go7007.
+
+config VIDEO_GO7007_USB
+	tristate "WIS GO7007 USB support"
+	depends on VIDEO_GO7007 && USB
+	---help---
+	  This is a video4linux driver for the WIS GO7007 MPEG
+	  encoder chip over USB.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called go7007-usb.
+
+config VIDEO_GO7007_LOADER
+	tristate "WIS GO7007 Loader support"
+	depends on VIDEO_GO7007
+	default y
+	---help---
+	  This is a go7007 firmware loader driver for the WIS GO7007
+	  MPEG encoder chip over USB.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called go7007-loader.
+
+config VIDEO_GO7007_USB_S2250_BOARD
+	tristate "Sensoray 2250/2251 support"
+	depends on VIDEO_GO7007_USB && USB
+	---help---
+	  This is a video4linux driver for the Sensoray 2250/2251 device.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called s2250.
diff --git a/drivers/media/usb/go7007/Makefile b/drivers/media/usb/go7007/Makefile
new file mode 100644
index 0000000..3d95bbc
--- /dev/null
+++ b/drivers/media/usb/go7007/Makefile
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_VIDEO_GO7007) += go7007.o
+obj-$(CONFIG_VIDEO_GO7007_USB) += go7007-usb.o
+obj-$(CONFIG_VIDEO_GO7007_LOADER) += go7007-loader.o
+obj-$(CONFIG_VIDEO_GO7007_USB_S2250_BOARD) += s2250.o
+
+go7007-y := go7007-v4l2.o go7007-driver.o go7007-i2c.o go7007-fw.o \
+		snd-go7007.o
+
+s2250-y := s2250-board.o
+
+ccflags-$(CONFIG_VIDEO_GO7007_LOADER:m=y) += -Idrivers/media/common
diff --git a/drivers/media/usb/go7007/go7007-driver.c b/drivers/media/usb/go7007/go7007-driver.c
new file mode 100644
index 0000000..62aeebc
--- /dev/null
+++ b/drivers/media/usb/go7007/go7007-driver.c
@@ -0,0 +1,771 @@
+/*
+ * Copyright (C) 2005-2006 Micronas USA Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (Version 2) as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/unistd.h>
+#include <linux/time.h>
+#include <linux/mm.h>
+#include <linux/vmalloc.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/firmware.h>
+#include <linux/mutex.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/videodev2.h>
+#include <media/tuner.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-event.h>
+
+#include "go7007-priv.h"
+
+/*
+ * Wait for an interrupt to be delivered from the GO7007SB and return
+ * the associated value and data.
+ *
+ * Must be called with the hw_lock held.
+ */
+int go7007_read_interrupt(struct go7007 *go, u16 *value, u16 *data)
+{
+	go->interrupt_available = 0;
+	go->hpi_ops->read_interrupt(go);
+	if (wait_event_timeout(go->interrupt_waitq,
+				go->interrupt_available, 5*HZ) < 0) {
+		v4l2_err(&go->v4l2_dev, "timeout waiting for read interrupt\n");
+		return -1;
+	}
+	if (!go->interrupt_available)
+		return -1;
+	go->interrupt_available = 0;
+	*value = go->interrupt_value & 0xfffe;
+	*data = go->interrupt_data;
+	return 0;
+}
+EXPORT_SYMBOL(go7007_read_interrupt);
+
+/*
+ * Read a register/address on the GO7007SB.
+ *
+ * Must be called with the hw_lock held.
+ */
+int go7007_read_addr(struct go7007 *go, u16 addr, u16 *data)
+{
+	int count = 100;
+	u16 value;
+
+	if (go7007_write_interrupt(go, 0x0010, addr) < 0)
+		return -EIO;
+	while (count-- > 0) {
+		if (go7007_read_interrupt(go, &value, data) == 0 &&
+				value == 0xa000)
+			return 0;
+	}
+	return -EIO;
+}
+EXPORT_SYMBOL(go7007_read_addr);
+
+/*
+ * Send the boot firmware to the encoder, which just wakes it up and lets
+ * us talk to the GPIO pins and on-board I2C adapter.
+ *
+ * Must be called with the hw_lock held.
+ */
+static int go7007_load_encoder(struct go7007 *go)
+{
+	const struct firmware *fw_entry;
+	char fw_name[] = "go7007/go7007fw.bin";
+	void *bounce;
+	int fw_len, rv = 0;
+	u16 intr_val, intr_data;
+
+	if (go->boot_fw == NULL) {
+		if (request_firmware(&fw_entry, fw_name, go->dev)) {
+			v4l2_err(go, "unable to load firmware from file \"%s\"\n", fw_name);
+			return -1;
+		}
+		if (fw_entry->size < 16 || memcmp(fw_entry->data, "WISGO7007FW", 11)) {
+			v4l2_err(go, "file \"%s\" does not appear to be go7007 firmware\n", fw_name);
+			release_firmware(fw_entry);
+			return -1;
+		}
+		fw_len = fw_entry->size - 16;
+		bounce = kmemdup(fw_entry->data + 16, fw_len, GFP_KERNEL);
+		if (bounce == NULL) {
+			v4l2_err(go, "unable to allocate %d bytes for firmware transfer\n", fw_len);
+			release_firmware(fw_entry);
+			return -1;
+		}
+		release_firmware(fw_entry);
+		go->boot_fw_len = fw_len;
+		go->boot_fw = bounce;
+	}
+	if (go7007_interface_reset(go) < 0 ||
+	    go7007_send_firmware(go, go->boot_fw, go->boot_fw_len) < 0 ||
+	    go7007_read_interrupt(go, &intr_val, &intr_data) < 0 ||
+			(intr_val & ~0x1) != 0x5a5a) {
+		v4l2_err(go, "error transferring firmware\n");
+		rv = -1;
+	}
+	return rv;
+}
+
+MODULE_FIRMWARE("go7007/go7007fw.bin");
+
+/*
+ * Boot the encoder and register the I2C adapter if requested.  Do the
+ * minimum initialization necessary, since the board-specific code may
+ * still need to probe the board ID.
+ *
+ * Must NOT be called with the hw_lock held.
+ */
+int go7007_boot_encoder(struct go7007 *go, int init_i2c)
+{
+	int ret;
+
+	mutex_lock(&go->hw_lock);
+	ret = go7007_load_encoder(go);
+	mutex_unlock(&go->hw_lock);
+	if (ret < 0)
+		return -1;
+	if (!init_i2c)
+		return 0;
+	if (go7007_i2c_init(go) < 0)
+		return -1;
+	go->i2c_adapter_online = 1;
+	return 0;
+}
+EXPORT_SYMBOL(go7007_boot_encoder);
+
+/*
+ * Configure any hardware-related registers in the GO7007, such as GPIO
+ * pins and bus parameters, which are board-specific.  This assumes
+ * the boot firmware has already been downloaded.
+ *
+ * Must be called with the hw_lock held.
+ */
+static int go7007_init_encoder(struct go7007 *go)
+{
+	if (go->board_info->audio_flags & GO7007_AUDIO_I2S_MASTER) {
+		go7007_write_addr(go, 0x1000, 0x0811);
+		go7007_write_addr(go, 0x1000, 0x0c11);
+	}
+	switch (go->board_id) {
+	case GO7007_BOARDID_MATRIX_REV:
+		/* Set GPIO pin 0 to be an output (audio clock control) */
+		go7007_write_addr(go, 0x3c82, 0x0001);
+		go7007_write_addr(go, 0x3c80, 0x00fe);
+		break;
+	case GO7007_BOARDID_ADLINK_MPG24:
+		/* set GPIO5 to be an output, currently low */
+		go7007_write_addr(go, 0x3c82, 0x0000);
+		go7007_write_addr(go, 0x3c80, 0x00df);
+		break;
+	case GO7007_BOARDID_ADS_USBAV_709:
+		/* GPIO pin 0: audio clock control */
+		/*      pin 2: TW9906 reset */
+		/*      pin 3: capture LED */
+		go7007_write_addr(go, 0x3c82, 0x000d);
+		go7007_write_addr(go, 0x3c80, 0x00f2);
+		break;
+	}
+	return 0;
+}
+
+/*
+ * Send the boot firmware to the GO7007 and configure the registers.  This
+ * is the only way to stop the encoder once it has started streaming video.
+ *
+ * Must be called with the hw_lock held.
+ */
+int go7007_reset_encoder(struct go7007 *go)
+{
+	if (go7007_load_encoder(go) < 0)
+		return -1;
+	return go7007_init_encoder(go);
+}
+
+/*
+ * Attempt to instantiate an I2C client by ID, probably loading a module.
+ */
+static int init_i2c_module(struct i2c_adapter *adapter, const struct go_i2c *const i2c)
+{
+	struct go7007 *go = i2c_get_adapdata(adapter);
+	struct v4l2_device *v4l2_dev = &go->v4l2_dev;
+	struct v4l2_subdev *sd;
+	struct i2c_board_info info;
+
+	memset(&info, 0, sizeof(info));
+	strlcpy(info.type, i2c->type, sizeof(info.type));
+	info.addr = i2c->addr;
+	info.flags = i2c->flags;
+
+	sd = v4l2_i2c_new_subdev_board(v4l2_dev, adapter, &info, NULL);
+	if (sd) {
+		if (i2c->is_video)
+			go->sd_video = sd;
+		if (i2c->is_audio)
+			go->sd_audio = sd;
+		return 0;
+	}
+
+	pr_info("go7007: probing for module i2c:%s failed\n", i2c->type);
+	return -EINVAL;
+}
+
+/*
+ * Detach and unregister the encoder.  The go7007 struct won't be freed
+ * until v4l2 finishes releasing its resources and all associated fds are
+ * closed by applications.
+ */
+static void go7007_remove(struct v4l2_device *v4l2_dev)
+{
+	struct go7007 *go = container_of(v4l2_dev, struct go7007, v4l2_dev);
+
+	v4l2_device_unregister(v4l2_dev);
+	if (go->hpi_ops->release)
+		go->hpi_ops->release(go);
+	if (go->i2c_adapter_online) {
+		i2c_del_adapter(&go->i2c_adapter);
+		go->i2c_adapter_online = 0;
+	}
+
+	kfree(go->boot_fw);
+	go7007_v4l2_remove(go);
+	kfree(go);
+}
+
+/*
+ * Finalize the GO7007 hardware setup, register the on-board I2C adapter
+ * (if used on this board), load the I2C client driver for the sensor
+ * (SAA7115 or whatever) and other devices, and register the ALSA and V4L2
+ * interfaces.
+ *
+ * Must NOT be called with the hw_lock held.
+ */
+int go7007_register_encoder(struct go7007 *go, unsigned num_i2c_devs)
+{
+	int i, ret;
+
+	dev_info(go->dev, "go7007: registering new %s\n", go->name);
+
+	go->v4l2_dev.release = go7007_remove;
+	ret = v4l2_device_register(go->dev, &go->v4l2_dev);
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&go->hw_lock);
+	ret = go7007_init_encoder(go);
+	mutex_unlock(&go->hw_lock);
+	if (ret < 0)
+		return ret;
+
+	ret = go7007_v4l2_ctrl_init(go);
+	if (ret < 0)
+		return ret;
+
+	if (!go->i2c_adapter_online &&
+			go->board_info->flags & GO7007_BOARD_USE_ONBOARD_I2C) {
+		ret = go7007_i2c_init(go);
+		if (ret < 0)
+			return ret;
+		go->i2c_adapter_online = 1;
+	}
+	if (go->i2c_adapter_online) {
+		if (go->board_id == GO7007_BOARDID_ADS_USBAV_709) {
+			/* Reset the TW9906 */
+			go7007_write_addr(go, 0x3c82, 0x0009);
+			msleep(50);
+			go7007_write_addr(go, 0x3c82, 0x000d);
+		}
+		for (i = 0; i < num_i2c_devs; ++i)
+			init_i2c_module(&go->i2c_adapter, &go->board_info->i2c_devs[i]);
+
+		if (go->tuner_type >= 0) {
+			struct tuner_setup setup = {
+				.addr = ADDR_UNSET,
+				.type = go->tuner_type,
+				.mode_mask = T_ANALOG_TV,
+			};
+
+			v4l2_device_call_all(&go->v4l2_dev, 0, tuner,
+				s_type_addr, &setup);
+		}
+		if (go->board_id == GO7007_BOARDID_ADLINK_MPG24)
+			v4l2_subdev_call(go->sd_video, video, s_routing,
+					0, 0, go->channel_number + 1);
+	}
+
+	ret = go7007_v4l2_init(go);
+	if (ret < 0)
+		return ret;
+
+	if (go->board_info->flags & GO7007_BOARD_HAS_AUDIO) {
+		go->audio_enabled = 1;
+		go7007_snd_init(go);
+	}
+	return 0;
+}
+EXPORT_SYMBOL(go7007_register_encoder);
+
+/*
+ * Send the encode firmware to the encoder, which will cause it
+ * to immediately start delivering the video and audio streams.
+ *
+ * Must be called with the hw_lock held.
+ */
+int go7007_start_encoder(struct go7007 *go)
+{
+	u8 *fw;
+	int fw_len, rv = 0, i, x, y;
+	u16 intr_val, intr_data;
+
+	go->modet_enable = 0;
+	for (i = 0; i < 4; i++)
+		go->modet[i].enable = 0;
+
+	switch (v4l2_ctrl_g_ctrl(go->modet_mode)) {
+	case V4L2_DETECT_MD_MODE_GLOBAL:
+		memset(go->modet_map, 0, sizeof(go->modet_map));
+		go->modet[0].enable = 1;
+		go->modet_enable = 1;
+		break;
+	case V4L2_DETECT_MD_MODE_REGION_GRID:
+		for (y = 0; y < go->height / 16; y++) {
+			for (x = 0; x < go->width / 16; x++) {
+				int idx = y * go->width / 16 + x;
+
+				go->modet[go->modet_map[idx]].enable = 1;
+			}
+		}
+		go->modet_enable = 1;
+		break;
+	}
+
+	if (go->dvd_mode)
+		go->modet_enable = 0;
+
+	if (go7007_construct_fw_image(go, &fw, &fw_len) < 0)
+		return -1;
+
+	if (go7007_send_firmware(go, fw, fw_len) < 0 ||
+			go7007_read_interrupt(go, &intr_val, &intr_data) < 0) {
+		v4l2_err(&go->v4l2_dev, "error transferring firmware\n");
+		rv = -1;
+		goto start_error;
+	}
+
+	go->state = STATE_DATA;
+	go->parse_length = 0;
+	go->seen_frame = 0;
+	if (go7007_stream_start(go) < 0) {
+		v4l2_err(&go->v4l2_dev, "error starting stream transfer\n");
+		rv = -1;
+		goto start_error;
+	}
+
+start_error:
+	kfree(fw);
+	return rv;
+}
+
+/*
+ * Store a byte in the current video buffer, if there is one.
+ */
+static inline void store_byte(struct go7007_buffer *vb, u8 byte)
+{
+	if (vb && vb->vb.vb2_buf.planes[0].bytesused < GO7007_BUF_SIZE) {
+		u8 *ptr = vb2_plane_vaddr(&vb->vb.vb2_buf, 0);
+
+		ptr[vb->vb.vb2_buf.planes[0].bytesused++] = byte;
+	}
+}
+
+static void go7007_set_motion_regions(struct go7007 *go, struct go7007_buffer *vb,
+		u32 motion_regions)
+{
+	if (motion_regions != go->modet_event_status) {
+		struct v4l2_event ev = {
+			.type = V4L2_EVENT_MOTION_DET,
+			.u.motion_det = {
+				.flags = V4L2_EVENT_MD_FL_HAVE_FRAME_SEQ,
+				.frame_sequence = vb->vb.sequence,
+				.region_mask = motion_regions,
+			},
+		};
+
+		v4l2_event_queue(&go->vdev, &ev);
+		go->modet_event_status = motion_regions;
+	}
+}
+
+/*
+ * Determine regions with motion and send a motion detection event
+ * in case of changes.
+ */
+static void go7007_motion_regions(struct go7007 *go, struct go7007_buffer *vb)
+{
+	u32 *bytesused = &vb->vb.vb2_buf.planes[0].bytesused;
+	unsigned motion[4] = { 0, 0, 0, 0 };
+	u32 motion_regions = 0;
+	unsigned stride = (go->width + 7) >> 3;
+	unsigned x, y;
+	int i;
+
+	for (i = 0; i < 216; ++i)
+		store_byte(vb, go->active_map[i]);
+	for (y = 0; y < go->height / 16; y++) {
+		for (x = 0; x < go->width / 16; x++) {
+			if (!(go->active_map[y * stride + (x >> 3)] & (1 << (x & 7))))
+				continue;
+			motion[go->modet_map[y * (go->width / 16) + x]]++;
+		}
+	}
+	motion_regions = ((motion[0] > 0) << 0) |
+			 ((motion[1] > 0) << 1) |
+			 ((motion[2] > 0) << 2) |
+			 ((motion[3] > 0) << 3);
+	*bytesused -= 216;
+	go7007_set_motion_regions(go, vb, motion_regions);
+}
+
+/*
+ * Deliver the last video buffer and get a new one to start writing to.
+ */
+static struct go7007_buffer *frame_boundary(struct go7007 *go, struct go7007_buffer *vb)
+{
+	u32 *bytesused;
+	struct go7007_buffer *vb_tmp = NULL;
+	unsigned long flags;
+
+	if (vb == NULL) {
+		spin_lock_irqsave(&go->spinlock, flags);
+		if (!list_empty(&go->vidq_active))
+			vb = go->active_buf =
+				list_first_entry(&go->vidq_active, struct go7007_buffer, list);
+		spin_unlock_irqrestore(&go->spinlock, flags);
+		go->next_seq++;
+		return vb;
+	}
+	bytesused = &vb->vb.vb2_buf.planes[0].bytesused;
+
+	vb->vb.sequence = go->next_seq++;
+	if (vb->modet_active && *bytesused + 216 < GO7007_BUF_SIZE)
+		go7007_motion_regions(go, vb);
+	else
+		go7007_set_motion_regions(go, vb, 0);
+
+	vb->vb.vb2_buf.timestamp = ktime_get_ns();
+	vb_tmp = vb;
+	spin_lock_irqsave(&go->spinlock, flags);
+	list_del(&vb->list);
+	if (list_empty(&go->vidq_active))
+		vb = NULL;
+	else
+		vb = list_first_entry(&go->vidq_active,
+				struct go7007_buffer, list);
+	go->active_buf = vb;
+	spin_unlock_irqrestore(&go->spinlock, flags);
+	vb2_buffer_done(&vb_tmp->vb.vb2_buf, VB2_BUF_STATE_DONE);
+	return vb;
+}
+
+static void write_bitmap_word(struct go7007 *go)
+{
+	int x, y, i, stride = ((go->width >> 4) + 7) >> 3;
+
+	for (i = 0; i < 16; ++i) {
+		y = (((go->parse_length - 1) << 3) + i) / (go->width >> 4);
+		x = (((go->parse_length - 1) << 3) + i) % (go->width >> 4);
+		if (stride * y + (x >> 3) < sizeof(go->active_map))
+			go->active_map[stride * y + (x >> 3)] |=
+					(go->modet_word & 1) << (x & 0x7);
+		go->modet_word >>= 1;
+	}
+}
+
+/*
+ * Parse a chunk of the video stream into frames.  The frames are not
+ * delimited by the hardware, so we have to parse the frame boundaries
+ * based on the type of video stream we're receiving.
+ */
+void go7007_parse_video_stream(struct go7007 *go, u8 *buf, int length)
+{
+	struct go7007_buffer *vb = go->active_buf;
+	int i, seq_start_code = -1, gop_start_code = -1, frame_start_code = -1;
+
+	switch (go->format) {
+	case V4L2_PIX_FMT_MPEG4:
+		seq_start_code = 0xB0;
+		gop_start_code = 0xB3;
+		frame_start_code = 0xB6;
+		break;
+	case V4L2_PIX_FMT_MPEG1:
+	case V4L2_PIX_FMT_MPEG2:
+		seq_start_code = 0xB3;
+		gop_start_code = 0xB8;
+		frame_start_code = 0x00;
+		break;
+	}
+
+	for (i = 0; i < length; ++i) {
+		if (vb && vb->vb.vb2_buf.planes[0].bytesused >=
+				GO7007_BUF_SIZE - 3) {
+			v4l2_info(&go->v4l2_dev, "dropping oversized frame\n");
+			vb->vb.vb2_buf.planes[0].bytesused = 0;
+			vb->frame_offset = 0;
+			vb->modet_active = 0;
+			vb = go->active_buf = NULL;
+		}
+
+		switch (go->state) {
+		case STATE_DATA:
+			switch (buf[i]) {
+			case 0x00:
+				go->state = STATE_00;
+				break;
+			case 0xFF:
+				go->state = STATE_FF;
+				break;
+			default:
+				store_byte(vb, buf[i]);
+				break;
+			}
+			break;
+		case STATE_00:
+			switch (buf[i]) {
+			case 0x00:
+				go->state = STATE_00_00;
+				break;
+			case 0xFF:
+				store_byte(vb, 0x00);
+				go->state = STATE_FF;
+				break;
+			default:
+				store_byte(vb, 0x00);
+				store_byte(vb, buf[i]);
+				go->state = STATE_DATA;
+				break;
+			}
+			break;
+		case STATE_00_00:
+			switch (buf[i]) {
+			case 0x00:
+				store_byte(vb, 0x00);
+				/* go->state remains STATE_00_00 */
+				break;
+			case 0x01:
+				go->state = STATE_00_00_01;
+				break;
+			case 0xFF:
+				store_byte(vb, 0x00);
+				store_byte(vb, 0x00);
+				go->state = STATE_FF;
+				break;
+			default:
+				store_byte(vb, 0x00);
+				store_byte(vb, 0x00);
+				store_byte(vb, buf[i]);
+				go->state = STATE_DATA;
+				break;
+			}
+			break;
+		case STATE_00_00_01:
+			if (buf[i] == 0xF8 && go->modet_enable == 0) {
+				/* MODET start code, but MODET not enabled */
+				store_byte(vb, 0x00);
+				store_byte(vb, 0x00);
+				store_byte(vb, 0x01);
+				store_byte(vb, 0xF8);
+				go->state = STATE_DATA;
+				break;
+			}
+			/* If this is the start of a new MPEG frame,
+			 * get a new buffer */
+			if ((go->format == V4L2_PIX_FMT_MPEG1 ||
+			     go->format == V4L2_PIX_FMT_MPEG2 ||
+			     go->format == V4L2_PIX_FMT_MPEG4) &&
+			    (buf[i] == seq_start_code ||
+			     buf[i] == gop_start_code ||
+			     buf[i] == frame_start_code)) {
+				if (vb == NULL || go->seen_frame)
+					vb = frame_boundary(go, vb);
+				go->seen_frame = buf[i] == frame_start_code;
+				if (vb && go->seen_frame)
+					vb->frame_offset =
+					vb->vb.vb2_buf.planes[0].bytesused;
+			}
+			/* Handle any special chunk types, or just write the
+			 * start code to the (potentially new) buffer */
+			switch (buf[i]) {
+			case 0xF5: /* timestamp */
+				go->parse_length = 12;
+				go->state = STATE_UNPARSED;
+				break;
+			case 0xF6: /* vbi */
+				go->state = STATE_VBI_LEN_A;
+				break;
+			case 0xF8: /* MD map */
+				go->parse_length = 0;
+				memset(go->active_map, 0,
+						sizeof(go->active_map));
+				go->state = STATE_MODET_MAP;
+				break;
+			case 0xFF: /* Potential JPEG start code */
+				store_byte(vb, 0x00);
+				store_byte(vb, 0x00);
+				store_byte(vb, 0x01);
+				go->state = STATE_FF;
+				break;
+			default:
+				store_byte(vb, 0x00);
+				store_byte(vb, 0x00);
+				store_byte(vb, 0x01);
+				store_byte(vb, buf[i]);
+				go->state = STATE_DATA;
+				break;
+			}
+			break;
+		case STATE_FF:
+			switch (buf[i]) {
+			case 0x00:
+				store_byte(vb, 0xFF);
+				go->state = STATE_00;
+				break;
+			case 0xFF:
+				store_byte(vb, 0xFF);
+				/* go->state remains STATE_FF */
+				break;
+			case 0xD8:
+				if (go->format == V4L2_PIX_FMT_MJPEG)
+					vb = frame_boundary(go, vb);
+				/* fall through */
+			default:
+				store_byte(vb, 0xFF);
+				store_byte(vb, buf[i]);
+				go->state = STATE_DATA;
+				break;
+			}
+			break;
+		case STATE_VBI_LEN_A:
+			go->parse_length = buf[i] << 8;
+			go->state = STATE_VBI_LEN_B;
+			break;
+		case STATE_VBI_LEN_B:
+			go->parse_length |= buf[i];
+			if (go->parse_length > 0)
+				go->state = STATE_UNPARSED;
+			else
+				go->state = STATE_DATA;
+			break;
+		case STATE_MODET_MAP:
+			if (go->parse_length < 204) {
+				if (go->parse_length & 1) {
+					go->modet_word |= buf[i];
+					write_bitmap_word(go);
+				} else
+					go->modet_word = buf[i] << 8;
+			} else if (go->parse_length == 207 && vb) {
+				vb->modet_active = buf[i];
+			}
+			if (++go->parse_length == 208)
+				go->state = STATE_DATA;
+			break;
+		case STATE_UNPARSED:
+			if (--go->parse_length == 0)
+				go->state = STATE_DATA;
+			break;
+		}
+	}
+}
+EXPORT_SYMBOL(go7007_parse_video_stream);
+
+/*
+ * Allocate a new go7007 struct.  Used by the hardware-specific probe.
+ */
+struct go7007 *go7007_alloc(const struct go7007_board_info *board,
+						struct device *dev)
+{
+	struct go7007 *go;
+	int i;
+
+	go = kzalloc(sizeof(struct go7007), GFP_KERNEL);
+	if (go == NULL)
+		return NULL;
+	go->dev = dev;
+	go->board_info = board;
+	go->board_id = 0;
+	go->tuner_type = -1;
+	go->channel_number = 0;
+	go->name[0] = 0;
+	mutex_init(&go->hw_lock);
+	init_waitqueue_head(&go->frame_waitq);
+	spin_lock_init(&go->spinlock);
+	go->status = STATUS_INIT;
+	memset(&go->i2c_adapter, 0, sizeof(go->i2c_adapter));
+	go->i2c_adapter_online = 0;
+	go->interrupt_available = 0;
+	init_waitqueue_head(&go->interrupt_waitq);
+	go->input = 0;
+	go7007_update_board(go);
+	go->encoder_h_halve = 0;
+	go->encoder_v_halve = 0;
+	go->encoder_subsample = 0;
+	go->format = V4L2_PIX_FMT_MJPEG;
+	go->bitrate = 1500000;
+	go->fps_scale = 1;
+	go->pali = 0;
+	go->aspect_ratio = GO7007_RATIO_1_1;
+	go->gop_size = 0;
+	go->ipb = 0;
+	go->closed_gop = 0;
+	go->repeat_seqhead = 0;
+	go->seq_header_enable = 0;
+	go->gop_header_enable = 0;
+	go->dvd_mode = 0;
+	go->interlace_coding = 0;
+	for (i = 0; i < 4; ++i)
+		go->modet[i].enable = 0;
+	for (i = 0; i < 1624; ++i)
+		go->modet_map[i] = 0;
+	go->audio_deliver = NULL;
+	go->audio_enabled = 0;
+
+	return go;
+}
+EXPORT_SYMBOL(go7007_alloc);
+
+void go7007_update_board(struct go7007 *go)
+{
+	const struct go7007_board_info *board = go->board_info;
+
+	if (board->sensor_flags & GO7007_SENSOR_TV) {
+		go->standard = GO7007_STD_NTSC;
+		go->std = V4L2_STD_NTSC_M;
+		go->width = 720;
+		go->height = 480;
+		go->sensor_framerate = 30000;
+	} else {
+		go->standard = GO7007_STD_OTHER;
+		go->width = board->sensor_width;
+		go->height = board->sensor_height;
+		go->sensor_framerate = board->sensor_framerate;
+	}
+	go->encoder_v_offset = board->sensor_v_offset;
+	go->encoder_h_offset = board->sensor_h_offset;
+}
+EXPORT_SYMBOL(go7007_update_board);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/usb/go7007/go7007-fw.c b/drivers/media/usb/go7007/go7007-fw.c
new file mode 100644
index 0000000..24f5b61
--- /dev/null
+++ b/drivers/media/usb/go7007/go7007-fw.c
@@ -0,0 +1,1631 @@
+/*
+ * Copyright (C) 2005-2006 Micronas USA Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (Version 2) as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+/*
+ * This file contains code to generate a firmware image for the GO7007SB
+ * encoder.  Much of the firmware is read verbatim from a file, but some of
+ * it concerning bitrate control and other things that can be configured at
+ * run-time are generated dynamically.  Note that the format headers
+ * generated here do not affect the functioning of the encoder; they are
+ * merely parroted back to the host at the start of each frame.
+ */
+
+#include <linux/module.h>
+#include <linux/time.h>
+#include <linux/mm.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <asm/byteorder.h>
+
+#include "go7007-priv.h"
+
+#define GO7007_FW_NAME "go7007/go7007tv.bin"
+
+/* Constants used in the source firmware image to describe code segments */
+
+#define	FLAG_MODE_MJPEG		(1)
+#define	FLAG_MODE_MPEG1		(1<<1)
+#define	FLAG_MODE_MPEG2		(1<<2)
+#define	FLAG_MODE_MPEG4		(1<<3)
+#define	FLAG_MODE_H263		(1<<4)
+#define FLAG_MODE_ALL		(FLAG_MODE_MJPEG | FLAG_MODE_MPEG1 | \
+					FLAG_MODE_MPEG2 | FLAG_MODE_MPEG4 | \
+					FLAG_MODE_H263)
+#define FLAG_SPECIAL		(1<<8)
+
+#define SPECIAL_FRM_HEAD	0
+#define SPECIAL_BRC_CTRL	1
+#define SPECIAL_CONFIG		2
+#define SPECIAL_SEQHEAD		3
+#define SPECIAL_AV_SYNC		4
+#define SPECIAL_FINAL		5
+#define SPECIAL_AUDIO		6
+#define SPECIAL_MODET		7
+
+/* Little data class for creating MPEG headers bit-by-bit */
+
+struct code_gen {
+	unsigned char *p; /* destination */
+	u32 a; /* collects bits at the top of the variable */
+	int b; /* bit position of most recently-written bit */
+	int len; /* written out so far */
+};
+
+#define CODE_GEN(name, dest) struct code_gen name = { dest, 0, 32, 0 }
+
+#define CODE_ADD(name, val, length) do { \
+	name.b -= (length); \
+	name.a |= (val) << name.b; \
+	while (name.b <= 24) { \
+		*name.p = name.a >> 24; \
+		++name.p; \
+		name.a <<= 8; \
+		name.b += 8; \
+		name.len += 8; \
+	} \
+} while (0)
+
+#define CODE_LENGTH(name) (name.len + (32 - name.b))
+
+/* Tables for creating the bitrate control data */
+
+static const s16 converge_speed_ip[101] = {
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 2,
+	2, 2, 2, 2, 2, 2, 2, 2, 2, 3,
+	3, 3, 3, 3, 3, 4, 4, 4, 4, 4,
+	5, 5, 5, 6, 6, 6, 7, 7, 8, 8,
+	9, 10, 10, 11, 12, 13, 14, 15, 16, 17,
+	19, 20, 22, 23, 25, 27, 30, 32, 35, 38,
+	41, 45, 49, 53, 58, 63, 69, 76, 83, 91,
+	100
+};
+
+static const s16 converge_speed_ipb[101] = {
+	3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+	3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
+	3, 3, 3, 3, 3, 4, 4, 4, 4, 4,
+	4, 4, 4, 4, 5, 5, 5, 5, 5, 6,
+	6, 6, 6, 7, 7, 7, 7, 8, 8, 9,
+	9, 9, 10, 10, 11, 12, 12, 13, 14, 14,
+	15, 16, 17, 18, 19, 20, 22, 23, 25, 26,
+	28, 30, 32, 34, 37, 40, 42, 46, 49, 53,
+	57, 61, 66, 71, 77, 83, 90, 97, 106, 115,
+	125, 135, 147, 161, 175, 191, 209, 228, 249, 273,
+	300
+};
+
+static const s16 LAMBDA_table[4][101] = {
+	{	16, 16, 16, 16, 17, 17, 17, 18, 18, 18,
+		19, 19, 19, 20, 20, 20, 21, 21, 22, 22,
+		22, 23, 23, 24, 24, 25, 25, 25, 26, 26,
+		27, 27, 28, 28, 29, 29, 30, 31, 31, 32,
+		32, 33, 33, 34, 35, 35, 36, 37, 37, 38,
+		39, 39, 40, 41, 42, 42, 43, 44, 45, 46,
+		46, 47, 48, 49, 50, 51, 52, 53, 54, 55,
+		56, 57, 58, 59, 60, 61, 62, 63, 64, 65,
+		67, 68, 69, 70, 72, 73, 74, 76, 77, 78,
+		80, 81, 83, 84, 86, 87, 89, 90, 92, 94,
+		96
+	},
+	{
+		20, 20, 20, 21, 21, 21, 22, 22, 23, 23,
+		23, 24, 24, 25, 25, 26, 26, 27, 27, 28,
+		28, 29, 29, 30, 30, 31, 31, 32, 33, 33,
+		34, 34, 35, 36, 36, 37, 38, 38, 39, 40,
+		40, 41, 42, 43, 43, 44, 45, 46, 47, 48,
+		48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
+		58, 59, 60, 61, 62, 64, 65, 66, 67, 68,
+		70, 71, 72, 73, 75, 76, 78, 79, 80, 82,
+		83, 85, 86, 88, 90, 91, 93, 95, 96, 98,
+		100, 102, 103, 105, 107, 109, 111, 113, 115, 117,
+		120
+	},
+	{
+		24, 24, 24, 25, 25, 26, 26, 27, 27, 28,
+		28, 29, 29, 30, 30, 31, 31, 32, 33, 33,
+		34, 34, 35, 36, 36, 37, 38, 38, 39, 40,
+		41, 41, 42, 43, 44, 44, 45, 46, 47, 48,
+		49, 50, 50, 51, 52, 53, 54, 55, 56, 57,
+		58, 59, 60, 62, 63, 64, 65, 66, 67, 69,
+		70, 71, 72, 74, 75, 76, 78, 79, 81, 82,
+		84, 85, 87, 88, 90, 92, 93, 95, 97, 98,
+		100, 102, 104, 106, 108, 110, 112, 114, 116, 118,
+		120, 122, 124, 127, 129, 131, 134, 136, 138, 141,
+		144
+	},
+	{
+		32, 32, 33, 33, 34, 34, 35, 36, 36, 37,
+		38, 38, 39, 40, 41, 41, 42, 43, 44, 44,
+		45, 46, 47, 48, 49, 50, 50, 51, 52, 53,
+		54, 55, 56, 57, 58, 59, 60, 62, 63, 64,
+		65, 66, 67, 69, 70, 71, 72, 74, 75, 76,
+		78, 79, 81, 82, 84, 85, 87, 88, 90, 92,
+		93, 95, 97, 98, 100, 102, 104, 106, 108, 110,
+		112, 114, 116, 118, 120, 122, 124, 127, 129, 131,
+		134, 136, 139, 141, 144, 146, 149, 152, 154, 157,
+		160, 163, 166, 169, 172, 175, 178, 181, 185, 188,
+		192
+	}
+};
+
+/* MPEG blank frame generation tables */
+
+enum mpeg_frame_type {
+	PFRAME,
+	BFRAME_PRE,
+	BFRAME_POST,
+	BFRAME_BIDIR,
+	BFRAME_EMPTY
+};
+
+static const u32 addrinctab[33][2] = {
+	{ 0x01, 1 },	{ 0x03, 3 },	{ 0x02, 3 },	{ 0x03, 4 },
+	{ 0x02, 4 },	{ 0x03, 5 },	{ 0x02, 5 },	{ 0x07, 7 },
+	{ 0x06, 7 },	{ 0x0b, 8 },	{ 0x0a, 8 },	{ 0x09, 8 },
+	{ 0x08, 8 },	{ 0x07, 8 },	{ 0x06, 8 },	{ 0x17, 10 },
+	{ 0x16, 10 },	{ 0x15, 10 },	{ 0x14, 10 },	{ 0x13, 10 },
+	{ 0x12, 10 },	{ 0x23, 11 },	{ 0x22, 11 },	{ 0x21, 11 },
+	{ 0x20, 11 },	{ 0x1f, 11 },	{ 0x1e, 11 },	{ 0x1d, 11 },
+	{ 0x1c, 11 },	{ 0x1b, 11 },	{ 0x1a, 11 },	{ 0x19, 11 },
+	{ 0x18, 11 }
+};
+
+/* Standard JPEG tables */
+
+static const u8 default_intra_quant_table[] = {
+	 8, 16, 19, 22, 26, 27, 29, 34,
+	16, 16, 22, 24, 27, 29, 34, 37,
+	19, 22, 26, 27, 29, 34, 34, 38,
+	22, 22, 26, 27, 29, 34, 37, 40,
+	22, 26, 27, 29, 32, 35, 40, 48,
+	26, 27, 29, 32, 35, 40, 48, 58,
+	26, 27, 29, 34, 38, 46, 56, 69,
+	27, 29, 35, 38, 46, 56, 69, 83
+};
+
+static const u8 bits_dc_luminance[] = {
+	0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0
+};
+
+static const u8 val_dc_luminance[] = {
+	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
+};
+
+static const u8 bits_dc_chrominance[] = {
+	0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0
+};
+
+static const u8 val_dc_chrominance[] = {
+	0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
+};
+
+static const u8 bits_ac_luminance[] = {
+	0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d
+};
+
+static const u8 val_ac_luminance[] = {
+	0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12,
+	0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
+	0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08,
+	0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
+	0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16,
+	0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
+	0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+	0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
+	0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
+	0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
+	0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
+	0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
+	0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
+	0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
+	0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
+	0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5,
+	0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4,
+	0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
+	0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea,
+	0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
+	0xf9, 0xfa
+};
+
+static const u8 bits_ac_chrominance[] = {
+	0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77
+};
+
+static const u8 val_ac_chrominance[] = {
+	0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21,
+	0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,
+	0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91,
+	0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0,
+	0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34,
+	0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
+	0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38,
+	0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
+	0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
+	0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
+	0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
+	0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
+	0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96,
+	0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
+	0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4,
+	0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3,
+	0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2,
+	0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
+	0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9,
+	0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
+	0xf9, 0xfa
+};
+
+/* Zig-zag mapping for quant table
+ *
+ * OK, let's do this mapping on the actual table above so it doesn't have
+ * to be done on the fly.
+ */
+static const int zz[64] = {
+	0,   1,  8, 16,  9,  2,  3, 10, 17, 24, 32, 25, 18, 11,  4,  5,
+	12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13,  6,  7, 14, 21, 28,
+	35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51,
+	58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63
+};
+
+static int copy_packages(__le16 *dest, u16 *src, int pkg_cnt, int space)
+{
+	int i, cnt = pkg_cnt * 32;
+
+	if (space < cnt)
+		return -1;
+
+	for (i = 0; i < cnt; ++i)
+		dest[i] = cpu_to_le16p(src + i);
+
+	return cnt;
+}
+
+static int mjpeg_frame_header(struct go7007 *go, unsigned char *buf, int q)
+{
+	int i, p = 0;
+
+	buf[p++] = 0xff;
+	buf[p++] = 0xd8;
+	buf[p++] = 0xff;
+	buf[p++] = 0xdb;
+	buf[p++] = 0;
+	buf[p++] = 2 + 65;
+	buf[p++] = 0;
+	buf[p++] = default_intra_quant_table[0];
+	for (i = 1; i < 64; ++i)
+		/* buf[p++] = (default_intra_quant_table[i] * q) >> 3; */
+		buf[p++] = (default_intra_quant_table[zz[i]] * q) >> 3;
+	buf[p++] = 0xff;
+	buf[p++] = 0xc0;
+	buf[p++] = 0;
+	buf[p++] = 17;
+	buf[p++] = 8;
+	buf[p++] = go->height >> 8;
+	buf[p++] = go->height & 0xff;
+	buf[p++] = go->width >> 8;
+	buf[p++] = go->width & 0xff;
+	buf[p++] = 3;
+	buf[p++] = 1;
+	buf[p++] = 0x22;
+	buf[p++] = 0;
+	buf[p++] = 2;
+	buf[p++] = 0x11;
+	buf[p++] = 0;
+	buf[p++] = 3;
+	buf[p++] = 0x11;
+	buf[p++] = 0;
+	buf[p++] = 0xff;
+	buf[p++] = 0xc4;
+	buf[p++] = 418 >> 8;
+	buf[p++] = 418 & 0xff;
+	buf[p++] = 0x00;
+	memcpy(buf + p, bits_dc_luminance + 1, 16);
+	p += 16;
+	memcpy(buf + p, val_dc_luminance, sizeof(val_dc_luminance));
+	p += sizeof(val_dc_luminance);
+	buf[p++] = 0x01;
+	memcpy(buf + p, bits_dc_chrominance + 1, 16);
+	p += 16;
+	memcpy(buf + p, val_dc_chrominance, sizeof(val_dc_chrominance));
+	p += sizeof(val_dc_chrominance);
+	buf[p++] = 0x10;
+	memcpy(buf + p, bits_ac_luminance + 1, 16);
+	p += 16;
+	memcpy(buf + p, val_ac_luminance, sizeof(val_ac_luminance));
+	p += sizeof(val_ac_luminance);
+	buf[p++] = 0x11;
+	memcpy(buf + p, bits_ac_chrominance + 1, 16);
+	p += 16;
+	memcpy(buf + p, val_ac_chrominance, sizeof(val_ac_chrominance));
+	p += sizeof(val_ac_chrominance);
+	buf[p++] = 0xff;
+	buf[p++] = 0xda;
+	buf[p++] = 0;
+	buf[p++] = 12;
+	buf[p++] = 3;
+	buf[p++] = 1;
+	buf[p++] = 0x00;
+	buf[p++] = 2;
+	buf[p++] = 0x11;
+	buf[p++] = 3;
+	buf[p++] = 0x11;
+	buf[p++] = 0;
+	buf[p++] = 63;
+	buf[p++] = 0;
+	return p;
+}
+
+static int gen_mjpeghdr_to_package(struct go7007 *go, __le16 *code, int space)
+{
+	u8 *buf;
+	u16 mem = 0x3e00;
+	unsigned int addr = 0x19;
+	int size = 0, i, off = 0, chunk;
+
+	buf = kzalloc(4096, GFP_KERNEL);
+	if (buf == NULL)
+		return -ENOMEM;
+
+	for (i = 1; i < 32; ++i) {
+		mjpeg_frame_header(go, buf + size, i);
+		size += 80;
+	}
+	chunk = mjpeg_frame_header(go, buf + size, 1);
+	memmove(buf + size, buf + size + 80, chunk - 80);
+	size += chunk - 80;
+
+	for (i = 0; i < size; i += chunk * 2) {
+		if (space - off < 32) {
+			off = -1;
+			goto done;
+		}
+
+		code[off + 1] = __cpu_to_le16(0x8000 | mem);
+
+		chunk = 28;
+		if (mem + chunk > 0x4000)
+			chunk = 0x4000 - mem;
+		if (i + 2 * chunk > size)
+			chunk = (size - i) / 2;
+
+		if (chunk < 28) {
+			code[off] = __cpu_to_le16(0x4000 | chunk);
+			code[off + 31] = __cpu_to_le16(addr++);
+			mem = 0x3e00;
+		} else {
+			code[off] = __cpu_to_le16(0x1000 | 28);
+			code[off + 31] = 0;
+			mem += 28;
+		}
+
+		memcpy(&code[off + 2], buf + i, chunk * 2);
+		off += 32;
+	}
+done:
+	kfree(buf);
+	return off;
+}
+
+static int mpeg1_frame_header(struct go7007 *go, unsigned char *buf,
+		int modulo, int pict_struct, enum mpeg_frame_type frame)
+{
+	int i, j, mb_code, mb_len;
+	int rows = go->interlace_coding ? go->height / 32 : go->height / 16;
+	CODE_GEN(c, buf + 6);
+
+	switch (frame) {
+	case PFRAME:
+		mb_code = 0x1;
+		mb_len = 3;
+		break;
+	case BFRAME_PRE:
+		mb_code = 0x2;
+		mb_len = 4;
+		break;
+	case BFRAME_POST:
+		mb_code = 0x2;
+		mb_len = 3;
+		break;
+	case BFRAME_BIDIR:
+		mb_code = 0x2;
+		mb_len = 2;
+		break;
+	default: /* keep the compiler happy */
+		mb_code = mb_len = 0;
+		break;
+	}
+
+	CODE_ADD(c, frame == PFRAME ? 0x2 : 0x3, 13);
+	CODE_ADD(c, 0xffff, 16);
+	CODE_ADD(c, go->format == V4L2_PIX_FMT_MPEG2 ? 0x7 : 0x4, 4);
+	if (frame != PFRAME)
+		CODE_ADD(c, go->format == V4L2_PIX_FMT_MPEG2 ? 0x7 : 0x4, 4);
+	else
+		CODE_ADD(c, 0, 4); /* Is this supposed to be here?? */
+	CODE_ADD(c, 0, 3); /* What is this?? */
+	/* Byte-align with zeros */
+	j = 8 - (CODE_LENGTH(c) % 8);
+	if (j != 8)
+		CODE_ADD(c, 0, j);
+
+	if (go->format == V4L2_PIX_FMT_MPEG2) {
+		CODE_ADD(c, 0x1, 24);
+		CODE_ADD(c, 0xb5, 8);
+		CODE_ADD(c, 0x844, 12);
+		CODE_ADD(c, frame == PFRAME ? 0xff : 0x44, 8);
+		if (go->interlace_coding) {
+			CODE_ADD(c, pict_struct, 4);
+			if (go->dvd_mode)
+				CODE_ADD(c, 0x000, 11);
+			else
+				CODE_ADD(c, 0x200, 11);
+		} else {
+			CODE_ADD(c, 0x3, 4);
+			CODE_ADD(c, 0x20c, 11);
+		}
+		/* Byte-align with zeros */
+		j = 8 - (CODE_LENGTH(c) % 8);
+		if (j != 8)
+			CODE_ADD(c, 0, j);
+	}
+
+	for (i = 0; i < rows; ++i) {
+		CODE_ADD(c, 1, 24);
+		CODE_ADD(c, i + 1, 8);
+		CODE_ADD(c, 0x2, 6);
+		CODE_ADD(c, 0x1, 1);
+		CODE_ADD(c, mb_code, mb_len);
+		if (go->interlace_coding) {
+			CODE_ADD(c, 0x1, 2);
+			CODE_ADD(c, pict_struct == 1 ? 0x0 : 0x1, 1);
+		}
+		if (frame == BFRAME_BIDIR) {
+			CODE_ADD(c, 0x3, 2);
+			if (go->interlace_coding)
+				CODE_ADD(c, pict_struct == 1 ? 0x0 : 0x1, 1);
+		}
+		CODE_ADD(c, 0x3, 2);
+		for (j = (go->width >> 4) - 2; j >= 33; j -= 33)
+			CODE_ADD(c, 0x8, 11);
+		CODE_ADD(c, addrinctab[j][0], addrinctab[j][1]);
+		CODE_ADD(c, mb_code, mb_len);
+		if (go->interlace_coding) {
+			CODE_ADD(c, 0x1, 2);
+			CODE_ADD(c, pict_struct == 1 ? 0x0 : 0x1, 1);
+		}
+		if (frame == BFRAME_BIDIR) {
+			CODE_ADD(c, 0x3, 2);
+			if (go->interlace_coding)
+				CODE_ADD(c, pict_struct == 1 ? 0x0 : 0x1, 1);
+		}
+		CODE_ADD(c, 0x3, 2);
+
+		/* Byte-align with zeros */
+		j = 8 - (CODE_LENGTH(c) % 8);
+		if (j != 8)
+			CODE_ADD(c, 0, j);
+	}
+
+	i = CODE_LENGTH(c) + 4 * 8;
+	buf[2] = 0x00;
+	buf[3] = 0x00;
+	buf[4] = 0x01;
+	buf[5] = 0x00;
+	return i;
+}
+
+static int mpeg1_sequence_header(struct go7007 *go, unsigned char *buf, int ext)
+{
+	int i, aspect_ratio, picture_rate;
+	CODE_GEN(c, buf + 6);
+
+	if (go->format == V4L2_PIX_FMT_MPEG1) {
+		switch (go->aspect_ratio) {
+		case GO7007_RATIO_4_3:
+			aspect_ratio = go->standard == GO7007_STD_NTSC ? 3 : 2;
+			break;
+		case GO7007_RATIO_16_9:
+			aspect_ratio = go->standard == GO7007_STD_NTSC ? 5 : 4;
+			break;
+		default:
+			aspect_ratio = 1;
+			break;
+		}
+	} else {
+		switch (go->aspect_ratio) {
+		case GO7007_RATIO_4_3:
+			aspect_ratio = 2;
+			break;
+		case GO7007_RATIO_16_9:
+			aspect_ratio = 3;
+			break;
+		default:
+			aspect_ratio = 1;
+			break;
+		}
+	}
+	switch (go->sensor_framerate) {
+	case 24000:
+		picture_rate = 1;
+		break;
+	case 24024:
+		picture_rate = 2;
+		break;
+	case 25025:
+		picture_rate = go->interlace_coding ? 6 : 3;
+		break;
+	case 30000:
+		picture_rate = go->interlace_coding ? 7 : 4;
+		break;
+	case 30030:
+		picture_rate = go->interlace_coding ? 8 : 5;
+		break;
+	default:
+		picture_rate = 5; /* 30 fps seems like a reasonable default */
+		break;
+	}
+
+	CODE_ADD(c, go->width, 12);
+	CODE_ADD(c, go->height, 12);
+	CODE_ADD(c, aspect_ratio, 4);
+	CODE_ADD(c, picture_rate, 4);
+	CODE_ADD(c, go->format == V4L2_PIX_FMT_MPEG2 ? 20000 : 0x3ffff, 18);
+	CODE_ADD(c, 1, 1);
+	CODE_ADD(c, go->format == V4L2_PIX_FMT_MPEG2 ? 112 : 20, 10);
+	CODE_ADD(c, 0, 3);
+
+	/* Byte-align with zeros */
+	i = 8 - (CODE_LENGTH(c) % 8);
+	if (i != 8)
+		CODE_ADD(c, 0, i);
+
+	if (go->format == V4L2_PIX_FMT_MPEG2) {
+		CODE_ADD(c, 0x1, 24);
+		CODE_ADD(c, 0xb5, 8);
+		CODE_ADD(c, 0x148, 12);
+		if (go->interlace_coding)
+			CODE_ADD(c, 0x20001, 20);
+		else
+			CODE_ADD(c, 0xa0001, 20);
+		CODE_ADD(c, 0, 16);
+
+		/* Byte-align with zeros */
+		i = 8 - (CODE_LENGTH(c) % 8);
+		if (i != 8)
+			CODE_ADD(c, 0, i);
+
+		if (ext) {
+			CODE_ADD(c, 0x1, 24);
+			CODE_ADD(c, 0xb52, 12);
+			CODE_ADD(c, go->standard == GO7007_STD_NTSC ? 2 : 1, 3);
+			CODE_ADD(c, 0x105, 9);
+			CODE_ADD(c, 0x505, 16);
+			CODE_ADD(c, go->width, 14);
+			CODE_ADD(c, 1, 1);
+			CODE_ADD(c, go->height, 14);
+
+			/* Byte-align with zeros */
+			i = 8 - (CODE_LENGTH(c) % 8);
+			if (i != 8)
+				CODE_ADD(c, 0, i);
+		}
+	}
+
+	i = CODE_LENGTH(c) + 4 * 8;
+	buf[0] = i & 0xff;
+	buf[1] = i >> 8;
+	buf[2] = 0x00;
+	buf[3] = 0x00;
+	buf[4] = 0x01;
+	buf[5] = 0xb3;
+	return i;
+}
+
+static int gen_mpeg1hdr_to_package(struct go7007 *go,
+					__le16 *code, int space, int *framelen)
+{
+	u8 *buf;
+	u16 mem = 0x3e00;
+	unsigned int addr = 0x19;
+	int i, off = 0, chunk;
+
+	buf = kzalloc(5120, GFP_KERNEL);
+	if (buf == NULL)
+		return -ENOMEM;
+
+	framelen[0] = mpeg1_frame_header(go, buf, 0, 1, PFRAME);
+	if (go->interlace_coding)
+		framelen[0] += mpeg1_frame_header(go, buf + framelen[0] / 8,
+							0, 2, PFRAME);
+	buf[0] = framelen[0] & 0xff;
+	buf[1] = framelen[0] >> 8;
+	i = 368;
+	framelen[1] = mpeg1_frame_header(go, buf + i, 0, 1, BFRAME_PRE);
+	if (go->interlace_coding)
+		framelen[1] += mpeg1_frame_header(go, buf + i + framelen[1] / 8,
+							0, 2, BFRAME_PRE);
+	buf[i] = framelen[1] & 0xff;
+	buf[i + 1] = framelen[1] >> 8;
+	i += 1632;
+	framelen[2] = mpeg1_frame_header(go, buf + i, 0, 1, BFRAME_POST);
+	if (go->interlace_coding)
+		framelen[2] += mpeg1_frame_header(go, buf + i + framelen[2] / 8,
+							0, 2, BFRAME_POST);
+	buf[i] = framelen[2] & 0xff;
+	buf[i + 1] = framelen[2] >> 8;
+	i += 1432;
+	framelen[3] = mpeg1_frame_header(go, buf + i, 0, 1, BFRAME_BIDIR);
+	if (go->interlace_coding)
+		framelen[3] += mpeg1_frame_header(go, buf + i + framelen[3] / 8,
+							0, 2, BFRAME_BIDIR);
+	buf[i] = framelen[3] & 0xff;
+	buf[i + 1] = framelen[3] >> 8;
+	i += 1632 + 16;
+	mpeg1_sequence_header(go, buf + i, 0);
+	i += 40;
+	for (i = 0; i < 5120; i += chunk * 2) {
+		if (space - off < 32) {
+			off = -1;
+			goto done;
+		}
+
+		code[off + 1] = __cpu_to_le16(0x8000 | mem);
+
+		chunk = 28;
+		if (mem + chunk > 0x4000)
+			chunk = 0x4000 - mem;
+		if (i + 2 * chunk > 5120)
+			chunk = (5120 - i) / 2;
+
+		if (chunk < 28) {
+			code[off] = __cpu_to_le16(0x4000 | chunk);
+			code[off + 31] = __cpu_to_le16(addr);
+			if (mem + chunk == 0x4000) {
+				mem = 0x3e00;
+				++addr;
+			}
+		} else {
+			code[off] = __cpu_to_le16(0x1000 | 28);
+			code[off + 31] = 0;
+			mem += 28;
+		}
+
+		memcpy(&code[off + 2], buf + i, chunk * 2);
+		off += 32;
+	}
+done:
+	kfree(buf);
+	return off;
+}
+
+static int vti_bitlen(struct go7007 *go)
+{
+	unsigned int i, max_time_incr = go->sensor_framerate / go->fps_scale;
+
+	for (i = 31; (max_time_incr & ((1 << i) - 1)) == max_time_incr; --i)
+		;
+	return i + 1;
+}
+
+static int mpeg4_frame_header(struct go7007 *go, unsigned char *buf,
+		int modulo, enum mpeg_frame_type frame)
+{
+	int i;
+	CODE_GEN(c, buf + 6);
+	int mb_count = (go->width >> 4) * (go->height >> 4);
+
+	CODE_ADD(c, frame == PFRAME ? 0x1 : 0x2, 2);
+	if (modulo)
+		CODE_ADD(c, 0x1, 1);
+	CODE_ADD(c, 0x1, 2);
+	CODE_ADD(c, 0, vti_bitlen(go));
+	CODE_ADD(c, 0x3, 2);
+	if (frame == PFRAME)
+		CODE_ADD(c, 0, 1);
+	CODE_ADD(c, 0xc, 11);
+	if (frame != PFRAME)
+		CODE_ADD(c, 0x4, 3);
+	if (frame != BFRAME_EMPTY) {
+		for (i = 0; i < mb_count; ++i) {
+			switch (frame) {
+			case PFRAME:
+				CODE_ADD(c, 0x1, 1);
+				break;
+			case BFRAME_PRE:
+				CODE_ADD(c, 0x47, 8);
+				break;
+			case BFRAME_POST:
+				CODE_ADD(c, 0x27, 7);
+				break;
+			case BFRAME_BIDIR:
+				CODE_ADD(c, 0x5f, 8);
+				break;
+			case BFRAME_EMPTY: /* keep compiler quiet */
+				break;
+			}
+		}
+	}
+
+	/* Byte-align with a zero followed by ones */
+	i = 8 - (CODE_LENGTH(c) % 8);
+	CODE_ADD(c, 0, 1);
+	CODE_ADD(c, (1 << (i - 1)) - 1, i - 1);
+
+	i = CODE_LENGTH(c) + 4 * 8;
+	buf[0] = i & 0xff;
+	buf[1] = i >> 8;
+	buf[2] = 0x00;
+	buf[3] = 0x00;
+	buf[4] = 0x01;
+	buf[5] = 0xb6;
+	return i;
+}
+
+static int mpeg4_sequence_header(struct go7007 *go, unsigned char *buf, int ext)
+{
+	const unsigned char head[] = { 0x00, 0x00, 0x01, 0xb0, go->pali,
+		0x00, 0x00, 0x01, 0xb5, 0x09,
+		0x00, 0x00, 0x01, 0x00,
+		0x00, 0x00, 0x01, 0x20, };
+	int i, aspect_ratio;
+	int fps = go->sensor_framerate / go->fps_scale;
+	CODE_GEN(c, buf + 2 + sizeof(head));
+
+	switch (go->aspect_ratio) {
+	case GO7007_RATIO_4_3:
+		aspect_ratio = go->standard == GO7007_STD_NTSC ? 3 : 2;
+		break;
+	case GO7007_RATIO_16_9:
+		aspect_ratio = go->standard == GO7007_STD_NTSC ? 5 : 4;
+		break;
+	default:
+		aspect_ratio = 1;
+		break;
+	}
+
+	memcpy(buf + 2, head, sizeof(head));
+	CODE_ADD(c, 0x191, 17);
+	CODE_ADD(c, aspect_ratio, 4);
+	CODE_ADD(c, 0x1, 4);
+	CODE_ADD(c, fps, 16);
+	CODE_ADD(c, 0x3, 2);
+	CODE_ADD(c, 1001, vti_bitlen(go));
+	CODE_ADD(c, 1, 1);
+	CODE_ADD(c, go->width, 13);
+	CODE_ADD(c, 1, 1);
+	CODE_ADD(c, go->height, 13);
+	CODE_ADD(c, 0x2830, 14);
+
+	/* Byte-align */
+	i = 8 - (CODE_LENGTH(c) % 8);
+	CODE_ADD(c, 0, 1);
+	CODE_ADD(c, (1 << (i - 1)) - 1, i - 1);
+
+	i = CODE_LENGTH(c) + sizeof(head) * 8;
+	buf[0] = i & 0xff;
+	buf[1] = i >> 8;
+	return i;
+}
+
+static int gen_mpeg4hdr_to_package(struct go7007 *go,
+					__le16 *code, int space, int *framelen)
+{
+	u8 *buf;
+	u16 mem = 0x3e00;
+	unsigned int addr = 0x19;
+	int i, off = 0, chunk;
+
+	buf = kzalloc(5120, GFP_KERNEL);
+	if (buf == NULL)
+		return -ENOMEM;
+
+	framelen[0] = mpeg4_frame_header(go, buf, 0, PFRAME);
+	i = 368;
+	framelen[1] = mpeg4_frame_header(go, buf + i, 0, BFRAME_PRE);
+	i += 1632;
+	framelen[2] = mpeg4_frame_header(go, buf + i, 0, BFRAME_POST);
+	i += 1432;
+	framelen[3] = mpeg4_frame_header(go, buf + i, 0, BFRAME_BIDIR);
+	i += 1632;
+	mpeg4_frame_header(go, buf + i, 0, BFRAME_EMPTY);
+	i += 16;
+	mpeg4_sequence_header(go, buf + i, 0);
+	i += 40;
+	for (i = 0; i < 5120; i += chunk * 2) {
+		if (space - off < 32) {
+			off = -1;
+			goto done;
+		}
+
+		code[off + 1] = __cpu_to_le16(0x8000 | mem);
+
+		chunk = 28;
+		if (mem + chunk > 0x4000)
+			chunk = 0x4000 - mem;
+		if (i + 2 * chunk > 5120)
+			chunk = (5120 - i) / 2;
+
+		if (chunk < 28) {
+			code[off] = __cpu_to_le16(0x4000 | chunk);
+			code[off + 31] = __cpu_to_le16(addr);
+			if (mem + chunk == 0x4000) {
+				mem = 0x3e00;
+				++addr;
+			}
+		} else {
+			code[off] = __cpu_to_le16(0x1000 | 28);
+			code[off + 31] = 0;
+			mem += 28;
+		}
+
+		memcpy(&code[off + 2], buf + i, chunk * 2);
+		off += 32;
+	}
+	mem = 0x3e00;
+	addr = go->ipb ? 0x14f9 : 0x0af9;
+	memset(buf, 0, 5120);
+	framelen[4] = mpeg4_frame_header(go, buf, 1, PFRAME);
+	i = 368;
+	framelen[5] = mpeg4_frame_header(go, buf + i, 1, BFRAME_PRE);
+	i += 1632;
+	framelen[6] = mpeg4_frame_header(go, buf + i, 1, BFRAME_POST);
+	i += 1432;
+	framelen[7] = mpeg4_frame_header(go, buf + i, 1, BFRAME_BIDIR);
+	i += 1632;
+	mpeg4_frame_header(go, buf + i, 1, BFRAME_EMPTY);
+	i += 16;
+	for (i = 0; i < 5120; i += chunk * 2) {
+		if (space - off < 32) {
+			off = -1;
+			goto done;
+		}
+
+		code[off + 1] = __cpu_to_le16(0x8000 | mem);
+
+		chunk = 28;
+		if (mem + chunk > 0x4000)
+			chunk = 0x4000 - mem;
+		if (i + 2 * chunk > 5120)
+			chunk = (5120 - i) / 2;
+
+		if (chunk < 28) {
+			code[off] = __cpu_to_le16(0x4000 | chunk);
+			code[off + 31] = __cpu_to_le16(addr);
+			if (mem + chunk == 0x4000) {
+				mem = 0x3e00;
+				++addr;
+			}
+		} else {
+			code[off] = __cpu_to_le16(0x1000 | 28);
+			code[off + 31] = 0;
+			mem += 28;
+		}
+
+		memcpy(&code[off + 2], buf + i, chunk * 2);
+		off += 32;
+	}
+done:
+	kfree(buf);
+	return off;
+}
+
+static int brctrl_to_package(struct go7007 *go,
+					__le16 *code, int space, int *framelen)
+{
+	int converge_speed = 0;
+	int lambda = (go->format == V4L2_PIX_FMT_MJPEG || go->dvd_mode) ?
+				100 : 0;
+	int peak_rate = 6 * go->bitrate / 5;
+	int vbv_buffer = go->format == V4L2_PIX_FMT_MJPEG ?
+				go->bitrate :
+				(go->dvd_mode ? 900000 : peak_rate);
+	int fps = go->sensor_framerate / go->fps_scale;
+	int q = 0;
+	/* Bizarre math below depends on rounding errors in division */
+	u32 sgop_expt_addr = go->bitrate / 32 * (go->ipb ? 3 : 1) * 1001 / fps;
+	u32 sgop_peak_addr = peak_rate / 32 * 1001 / fps;
+	u32 total_expt_addr = go->bitrate / 32 * 1000 / fps * (fps / 1000);
+	u32 vbv_alert_addr = vbv_buffer * 3 / (4 * 32);
+	u32 cplx[] = {
+		q > 0 ? sgop_expt_addr * q :
+			2 * go->width * go->height * (go->ipb ? 6 : 4) / 32,
+		q > 0 ? sgop_expt_addr * q :
+			2 * go->width * go->height * (go->ipb ? 6 : 4) / 32,
+		q > 0 ? sgop_expt_addr * q :
+			2 * go->width * go->height * (go->ipb ? 6 : 4) / 32,
+		q > 0 ? sgop_expt_addr * q :
+			2 * go->width * go->height * (go->ipb ? 6 : 4) / 32,
+	};
+	u32 calc_q = q > 0 ? q : cplx[0] / sgop_expt_addr;
+	u16 pack[] = {
+		0x200e,		0x0000,
+		0xBF20,		go->ipb ? converge_speed_ipb[converge_speed]
+					: converge_speed_ip[converge_speed],
+		0xBF21,		go->ipb ? 2 : 0,
+		0xBF22,		go->ipb ? LAMBDA_table[0][lambda / 2 + 50]
+					: 32767,
+		0xBF23,		go->ipb ? LAMBDA_table[1][lambda] : 32767,
+		0xBF24,		32767,
+		0xBF25,		lambda > 99 ? 32767 : LAMBDA_table[3][lambda],
+		0xBF26,		sgop_expt_addr & 0x0000FFFF,
+		0xBF27,		sgop_expt_addr >> 16,
+		0xBF28,		sgop_peak_addr & 0x0000FFFF,
+		0xBF29,		sgop_peak_addr >> 16,
+		0xBF2A,		vbv_alert_addr & 0x0000FFFF,
+		0xBF2B,		vbv_alert_addr >> 16,
+		0xBF2C,		0,
+		0xBF2D,		0,
+		0,		0,
+
+		0x200e,		0x0000,
+		0xBF2E,		vbv_alert_addr & 0x0000FFFF,
+		0xBF2F,		vbv_alert_addr >> 16,
+		0xBF30,		cplx[0] & 0x0000FFFF,
+		0xBF31,		cplx[0] >> 16,
+		0xBF32,		cplx[1] & 0x0000FFFF,
+		0xBF33,		cplx[1] >> 16,
+		0xBF34,		cplx[2] & 0x0000FFFF,
+		0xBF35,		cplx[2] >> 16,
+		0xBF36,		cplx[3] & 0x0000FFFF,
+		0xBF37,		cplx[3] >> 16,
+		0xBF38,		0,
+		0xBF39,		0,
+		0xBF3A,		total_expt_addr & 0x0000FFFF,
+		0xBF3B,		total_expt_addr >> 16,
+		0,		0,
+
+		0x200e,		0x0000,
+		0xBF3C,		total_expt_addr & 0x0000FFFF,
+		0xBF3D,		total_expt_addr >> 16,
+		0xBF3E,		0,
+		0xBF3F,		0,
+		0xBF48,		0,
+		0xBF49,		0,
+		0xBF4A,		calc_q < 4 ? 4 : (calc_q > 124 ? 124 : calc_q),
+		0xBF4B,		4,
+		0xBF4C,		0,
+		0xBF4D,		0,
+		0xBF4E,		0,
+		0xBF4F,		0,
+		0xBF50,		0,
+		0xBF51,		0,
+		0,		0,
+
+		0x200e,		0x0000,
+		0xBF40,		sgop_expt_addr & 0x0000FFFF,
+		0xBF41,		sgop_expt_addr >> 16,
+		0xBF42,		0,
+		0xBF43,		0,
+		0xBF44,		0,
+		0xBF45,		0,
+		0xBF46,		(go->width >> 4) * (go->height >> 4),
+		0xBF47,		0,
+		0xBF64,		0,
+		0xBF65,		0,
+		0xBF18,		framelen[4],
+		0xBF19,		framelen[5],
+		0xBF1A,		framelen[6],
+		0xBF1B,		framelen[7],
+		0,		0,
+
+#if 0
+		/* Remove once we don't care about matching */
+		0x200e,		0x0000,
+		0xBF56,		4,
+		0xBF57,		0,
+		0xBF58,		5,
+		0xBF59,		0,
+		0xBF5A,		6,
+		0xBF5B,		0,
+		0xBF5C,		8,
+		0xBF5D,		0,
+		0xBF5E,		1,
+		0xBF5F,		0,
+		0xBF60,		1,
+		0xBF61,		0,
+		0xBF62,		0,
+		0xBF63,		0,
+		0,		0,
+#else
+		0x2008,		0x0000,
+		0xBF56,		4,
+		0xBF57,		0,
+		0xBF58,		5,
+		0xBF59,		0,
+		0xBF5A,		6,
+		0xBF5B,		0,
+		0xBF5C,		8,
+		0xBF5D,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+#endif
+
+		0x200e,		0x0000,
+		0xBF10,		0,
+		0xBF11,		0,
+		0xBF12,		0,
+		0xBF13,		0,
+		0xBF14,		0,
+		0xBF15,		0,
+		0xBF16,		0,
+		0xBF17,		0,
+		0xBF7E,		0,
+		0xBF7F,		1,
+		0xBF52,		framelen[0],
+		0xBF53,		framelen[1],
+		0xBF54,		framelen[2],
+		0xBF55,		framelen[3],
+		0,		0,
+	};
+
+	return copy_packages(code, pack, 6, space);
+}
+
+static int config_package(struct go7007 *go, __le16 *code, int space)
+{
+	int fps = go->sensor_framerate / go->fps_scale / 1000;
+	int rows = go->interlace_coding ? go->height / 32 : go->height / 16;
+	int brc_window_size = fps;
+	int q_min = 2, q_max = 31;
+	int THACCoeffSet0 = 0;
+	u16 pack[] = {
+		0x200e,		0x0000,
+		0xc002,		0x14b4,
+		0xc003,		0x28b4,
+		0xc004,		0x3c5a,
+		0xdc05,		0x2a77,
+		0xc6c3,		go->format == V4L2_PIX_FMT_MPEG4 ? 0 :
+				(go->format == V4L2_PIX_FMT_H263 ? 0 : 1),
+		0xc680,		go->format == V4L2_PIX_FMT_MPEG4 ? 0xf1 :
+				(go->format == V4L2_PIX_FMT_H263 ? 0x61 :
+									0xd3),
+		0xc780,		0x0140,
+		0xe009,		0x0001,
+		0xc60f,		0x0008,
+		0xd4ff,		0x0002,
+		0xe403,		2340,
+		0xe406,		75,
+		0xd411,		0x0001,
+		0xd410,		0xa1d6,
+		0x0001,		0x2801,
+
+		0x200d,		0x0000,
+		0xe402,		0x018b,
+		0xe401,		0x8b01,
+		0xd472,		(go->board_info->sensor_flags &
+							GO7007_SENSOR_TV) &&
+						(!go->interlace_coding) ?
+					0x01b0 : 0x0170,
+		0xd475,		(go->board_info->sensor_flags &
+							GO7007_SENSOR_TV) &&
+						(!go->interlace_coding) ?
+					0x0008 : 0x0009,
+		0xc404,		go->interlace_coding ? 0x44 :
+				(go->format == V4L2_PIX_FMT_MPEG4 ? 0x11 :
+				(go->format == V4L2_PIX_FMT_MPEG1 ? 0x02 :
+				(go->format == V4L2_PIX_FMT_MPEG2 ? 0x04 :
+				(go->format == V4L2_PIX_FMT_H263  ? 0x08 :
+								     0x20)))),
+		0xbf0a,		(go->format == V4L2_PIX_FMT_MPEG4 ? 8 :
+				(go->format == V4L2_PIX_FMT_MPEG1 ? 1 :
+				(go->format == V4L2_PIX_FMT_MPEG2 ? 2 :
+				(go->format == V4L2_PIX_FMT_H263 ? 4 : 16)))) |
+				((go->repeat_seqhead ? 1 : 0) << 6) |
+				((go->dvd_mode ? 1 : 0) << 9) |
+				((go->gop_header_enable ? 1 : 0) << 10),
+		0xbf0b,		0,
+		0xdd5a,		go->ipb ? 0x14 : 0x0a,
+		0xbf0c,		0,
+		0xbf0d,		0,
+		0xc683,		THACCoeffSet0,
+		0xc40a,		(go->width << 4) | rows,
+		0xe01a,		go->board_info->hpi_buffer_cap,
+		0,		0,
+		0,		0,
+
+		0x2008,		0,
+		0xe402,		0x88,
+		0xe401,		0x8f01,
+		0xbf6a,		0,
+		0xbf6b,		0,
+		0xbf6c,		0,
+		0xbf6d,		0,
+		0xbf6e,		0,
+		0xbf6f,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+
+		0x200e,		0,
+		0xbf66,		brc_window_size,
+		0xbf67,		0,
+		0xbf68,		q_min,
+		0xbf69,		q_max,
+		0xbfe0,		0,
+		0xbfe1,		0,
+		0xbfe2,		0,
+		0xbfe3,		go->ipb ? 3 : 1,
+		0xc031,		go->board_info->sensor_flags &
+					GO7007_SENSOR_VBI ? 1 : 0,
+		0xc01c,		0x1f,
+		0xdd8c,		0x15,
+		0xdd94,		0x15,
+		0xdd88,		go->ipb ? 0x1401 : 0x0a01,
+		0xdd90,		go->ipb ? 0x1401 : 0x0a01,
+		0,		0,
+
+		0x200e,		0,
+		0xbfe4,		0,
+		0xbfe5,		0,
+		0xbfe6,		0,
+		0xbfe7,		fps << 8,
+		0xbfe8,		0x3a00,
+		0xbfe9,		0,
+		0xbfea,		0,
+		0xbfeb,		0,
+		0xbfec,		(go->interlace_coding ? 1 << 15 : 0) |
+					(go->modet_enable ? 0xa : 0) |
+					(go->board_info->sensor_flags &
+						GO7007_SENSOR_VBI ? 1 : 0),
+		0xbfed,		0,
+		0xbfee,		0,
+		0xbfef,		0,
+		0xbff0,		go->board_info->sensor_flags &
+					GO7007_SENSOR_TV ? 0xf060 : 0xb060,
+		0xbff1,		0,
+		0,		0,
+	};
+
+	return copy_packages(code, pack, 5, space);
+}
+
+static int seqhead_to_package(struct go7007 *go, __le16 *code, int space,
+	int (*sequence_header_func)(struct go7007 *go,
+		unsigned char *buf, int ext))
+{
+	int vop_time_increment_bitlength = vti_bitlen(go);
+	int fps = go->sensor_framerate / go->fps_scale *
+					(go->interlace_coding ? 2 : 1);
+	unsigned char buf[40] = { };
+	int len = sequence_header_func(go, buf, 1);
+	u16 pack[] = {
+		0x2006,		0,
+		0xbf08,		fps,
+		0xbf09,		0,
+		0xbff2,		vop_time_increment_bitlength,
+		0xbff3,		(1 << vop_time_increment_bitlength) - 1,
+		0xbfe6,		0,
+		0xbfe7,		(fps / 1000) << 8,
+		0,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+
+		0x2007,		0,
+		0xc800,		buf[2] << 8 | buf[3],
+		0xc801,		buf[4] << 8 | buf[5],
+		0xc802,		buf[6] << 8 | buf[7],
+		0xc803,		buf[8] << 8 | buf[9],
+		0xc406,		64,
+		0xc407,		len - 64,
+		0xc61b,		1,
+		0,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+
+		0x200e,		0,
+		0xc808,		buf[10] << 8 | buf[11],
+		0xc809,		buf[12] << 8 | buf[13],
+		0xc80a,		buf[14] << 8 | buf[15],
+		0xc80b,		buf[16] << 8 | buf[17],
+		0xc80c,		buf[18] << 8 | buf[19],
+		0xc80d,		buf[20] << 8 | buf[21],
+		0xc80e,		buf[22] << 8 | buf[23],
+		0xc80f,		buf[24] << 8 | buf[25],
+		0xc810,		buf[26] << 8 | buf[27],
+		0xc811,		buf[28] << 8 | buf[29],
+		0xc812,		buf[30] << 8 | buf[31],
+		0xc813,		buf[32] << 8 | buf[33],
+		0xc814,		buf[34] << 8 | buf[35],
+		0xc815,		buf[36] << 8 | buf[37],
+		0,		0,
+		0,		0,
+		0,		0,
+	};
+
+	return copy_packages(code, pack, 3, space);
+}
+
+static int relative_prime(int big, int little)
+{
+	int remainder;
+
+	while (little != 0) {
+		remainder = big % little;
+		big = little;
+		little = remainder;
+	}
+	return big;
+}
+
+static int avsync_to_package(struct go7007 *go, __le16 *code, int space)
+{
+	int arate = go->board_info->audio_rate * 1001 * go->fps_scale;
+	int ratio = arate / go->sensor_framerate;
+	int adjratio = ratio * 215 / 100;
+	int rprime = relative_prime(go->sensor_framerate,
+					arate % go->sensor_framerate);
+	int f1 = (arate % go->sensor_framerate) / rprime;
+	int f2 = (go->sensor_framerate - arate % go->sensor_framerate) / rprime;
+	u16 pack[] = {
+		0x200e,		0,
+		0xbf98,		(u16)((-adjratio) & 0xffff),
+		0xbf99,		(u16)((-adjratio) >> 16),
+		0xbf92,		0,
+		0xbf93,		0,
+		0xbff4,		f1 > f2 ? f1 : f2,
+		0xbff5,		f1 < f2 ? f1 : f2,
+		0xbff6,		f1 < f2 ? ratio : ratio + 1,
+		0xbff7,		f1 > f2 ? ratio : ratio + 1,
+		0xbff8,		0,
+		0xbff9,		0,
+		0xbffa,		adjratio & 0xffff,
+		0xbffb,		adjratio >> 16,
+		0xbf94,		0,
+		0xbf95,		0,
+		0,		0,
+	};
+
+	return copy_packages(code, pack, 1, space);
+}
+
+static int final_package(struct go7007 *go, __le16 *code, int space)
+{
+	int rows = go->interlace_coding ? go->height / 32 : go->height / 16;
+	u16 pack[] = {
+		0x8000,
+		0,
+		0,
+		0,
+		0,
+		0,
+		0,
+		2,
+		((go->board_info->sensor_flags & GO7007_SENSOR_TV) &&
+						(!go->interlace_coding) ?
+					(1 << 14) | (1 << 9) : 0) |
+			((go->encoder_subsample ? 1 : 0) << 8) |
+			(go->board_info->sensor_flags &
+				GO7007_SENSOR_CONFIG_MASK),
+		((go->encoder_v_halve ? 1 : 0) << 14) |
+			(go->encoder_v_halve ? rows << 9 : rows << 8) |
+			(go->encoder_h_halve ? 1 << 6 : 0) |
+			(go->encoder_h_halve ? go->width >> 3 : go->width >> 4),
+		(1 << 15) | (go->encoder_v_offset << 6) |
+			(1 << 7) | (go->encoder_h_offset >> 2),
+		(1 << 6),
+		0,
+		0,
+		((go->fps_scale - 1) << 8) |
+			(go->board_info->sensor_flags & GO7007_SENSOR_TV ?
+						(1 << 7) : 0) |
+			0x41,
+		go->ipb ? 0xd4c : 0x36b,
+		(rows << 8) | (go->width >> 4),
+		go->format == V4L2_PIX_FMT_MPEG4 ? 0x0404 : 0,
+		(1 << 15) | ((go->interlace_coding ? 1 : 0) << 13) |
+			((go->closed_gop ? 1 : 0) << 12) |
+			((go->format == V4L2_PIX_FMT_MPEG4 ? 1 : 0) << 11) |
+		/*	(1 << 9) |   */
+			((go->ipb ? 3 : 0) << 7) |
+			((go->modet_enable ? 1 : 0) << 2) |
+			((go->dvd_mode ? 1 : 0) << 1) | 1,
+		(go->format == V4L2_PIX_FMT_MPEG1 ? 0x89a0 :
+			(go->format == V4L2_PIX_FMT_MPEG2 ? 0x89a0 :
+			(go->format == V4L2_PIX_FMT_MJPEG ? 0x89a0 :
+			(go->format == V4L2_PIX_FMT_MPEG4 ? 0x8920 :
+			(go->format == V4L2_PIX_FMT_H263 ? 0x8920 : 0))))),
+		go->ipb ? 0x1f15 : 0x1f0b,
+		go->ipb ? 0x0015 : 0x000b,
+		go->ipb ? 0xa800 : 0x5800,
+		0xffff,
+		0x0020 + 0x034b * 0,
+		0x0020 + 0x034b * 1,
+		0x0020 + 0x034b * 2,
+		0x0020 + 0x034b * 3,
+		0x0020 + 0x034b * 4,
+		0x0020 + 0x034b * 5,
+		go->ipb ? (go->gop_size / 3) : go->gop_size,
+		(go->height >> 4) * (go->width >> 4) * 110 / 100,
+	};
+
+	return copy_packages(code, pack, 1, space);
+}
+
+static int audio_to_package(struct go7007 *go, __le16 *code, int space)
+{
+	int clock_config = ((go->board_info->audio_flags &
+				GO7007_AUDIO_I2S_MASTER ? 1 : 0) << 11) |
+			((go->board_info->audio_flags &
+				GO7007_AUDIO_OKI_MODE ? 1 : 0) << 8) |
+			(((go->board_info->audio_bclk_div / 4) - 1) << 4) |
+			(go->board_info->audio_main_div - 1);
+	u16 pack[] = {
+		0x200d,		0,
+		0x9002,		0,
+		0x9002,		0,
+		0x9031,		0,
+		0x9032,		0,
+		0x9033,		0,
+		0x9034,		0,
+		0x9035,		0,
+		0x9036,		0,
+		0x9037,		0,
+		0x9040,		0,
+		0x9000,		clock_config,
+		0x9001,		(go->board_info->audio_flags & 0xffff) |
+					(1 << 9),
+		0x9000,		((go->board_info->audio_flags &
+						GO7007_AUDIO_I2S_MASTER ?
+						1 : 0) << 10) |
+					clock_config,
+		0,		0,
+		0,		0,
+		0x2005,		0,
+		0x9041,		0,
+		0x9042,		256,
+		0x9043,		0,
+		0x9044,		16,
+		0x9045,		16,
+		0,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+		0,		0,
+	};
+
+	return copy_packages(code, pack, 2, space);
+}
+
+static int modet_to_package(struct go7007 *go, __le16 *code, int space)
+{
+	bool has_modet0 = go->modet[0].enable;
+	bool has_modet1 = go->modet[1].enable;
+	bool has_modet2 = go->modet[2].enable;
+	bool has_modet3 = go->modet[3].enable;
+	int ret, mb, i, addr, cnt = 0;
+	u16 pack[32];
+	u16 thresholds[] = {
+		0x200e,		0,
+		0xbf82,		has_modet0 ? go->modet[0].pixel_threshold : 32767,
+		0xbf83,		has_modet1 ? go->modet[1].pixel_threshold : 32767,
+		0xbf84,		has_modet2 ? go->modet[2].pixel_threshold : 32767,
+		0xbf85,		has_modet3 ? go->modet[3].pixel_threshold : 32767,
+		0xbf86,		has_modet0 ? go->modet[0].motion_threshold : 32767,
+		0xbf87,		has_modet1 ? go->modet[1].motion_threshold : 32767,
+		0xbf88,		has_modet2 ? go->modet[2].motion_threshold : 32767,
+		0xbf89,		has_modet3 ? go->modet[3].motion_threshold : 32767,
+		0xbf8a,		has_modet0 ? go->modet[0].mb_threshold : 32767,
+		0xbf8b,		has_modet1 ? go->modet[1].mb_threshold : 32767,
+		0xbf8c,		has_modet2 ? go->modet[2].mb_threshold : 32767,
+		0xbf8d,		has_modet3 ? go->modet[3].mb_threshold : 32767,
+		0xbf8e,		0,
+		0xbf8f,		0,
+		0,		0,
+	};
+
+	ret = copy_packages(code, thresholds, 1, space);
+	if (ret < 0)
+		return -1;
+	cnt += ret;
+
+	addr = 0xbac0;
+	memset(pack, 0, 64);
+	i = 0;
+	for (mb = 0; mb < 1624; ++mb) {
+		pack[i * 2 + 3] <<= 2;
+		pack[i * 2 + 3] |= go->modet_map[mb];
+		if (mb % 8 != 7)
+			continue;
+		pack[i * 2 + 2] = addr++;
+		++i;
+		if (i == 10 || mb == 1623) {
+			pack[0] = 0x2000 | i;
+			ret = copy_packages(code + cnt, pack, 1, space - cnt);
+			if (ret < 0)
+				return -1;
+			cnt += ret;
+			i = 0;
+			memset(pack, 0, 64);
+		}
+		pack[i * 2 + 3] = 0;
+	}
+
+	memset(pack, 0, 64);
+	i = 0;
+	for (addr = 0xbb90; addr < 0xbbfa; ++addr) {
+		pack[i * 2 + 2] = addr;
+		pack[i * 2 + 3] = 0;
+		++i;
+		if (i == 10 || addr == 0xbbf9) {
+			pack[0] = 0x2000 | i;
+			ret = copy_packages(code + cnt, pack, 1, space - cnt);
+			if (ret < 0)
+				return -1;
+			cnt += ret;
+			i = 0;
+			memset(pack, 0, 64);
+		}
+	}
+	return cnt;
+}
+
+static int do_special(struct go7007 *go, u16 type, __le16 *code, int space,
+			int *framelen)
+{
+	switch (type) {
+	case SPECIAL_FRM_HEAD:
+		switch (go->format) {
+		case V4L2_PIX_FMT_MJPEG:
+			return gen_mjpeghdr_to_package(go, code, space);
+		case V4L2_PIX_FMT_MPEG1:
+		case V4L2_PIX_FMT_MPEG2:
+			return gen_mpeg1hdr_to_package(go, code, space,
+								framelen);
+		case V4L2_PIX_FMT_MPEG4:
+			return gen_mpeg4hdr_to_package(go, code, space,
+								framelen);
+		default:
+			break;
+		}
+		break;
+	case SPECIAL_BRC_CTRL:
+		return brctrl_to_package(go, code, space, framelen);
+	case SPECIAL_CONFIG:
+		return config_package(go, code, space);
+	case SPECIAL_SEQHEAD:
+		switch (go->format) {
+		case V4L2_PIX_FMT_MPEG1:
+		case V4L2_PIX_FMT_MPEG2:
+			return seqhead_to_package(go, code, space,
+					mpeg1_sequence_header);
+		case V4L2_PIX_FMT_MPEG4:
+			return seqhead_to_package(go, code, space,
+					mpeg4_sequence_header);
+		default:
+			return 0;
+		}
+	case SPECIAL_AV_SYNC:
+		return avsync_to_package(go, code, space);
+	case SPECIAL_FINAL:
+		return final_package(go, code, space);
+	case SPECIAL_AUDIO:
+		return audio_to_package(go, code, space);
+	case SPECIAL_MODET:
+		return modet_to_package(go, code, space);
+	}
+	dev_err(go->dev,
+		"firmware file contains unsupported feature %04x\n", type);
+	return -1;
+}
+
+int go7007_construct_fw_image(struct go7007 *go, u8 **fw, int *fwlen)
+{
+	const struct firmware *fw_entry;
+	__le16 *code, *src;
+	int framelen[8] = { }; /* holds the lengths of empty frame templates */
+	int codespace = 64 * 1024, i = 0, srclen, chunk_len, chunk_flags;
+	int mode_flag;
+	int ret;
+
+	switch (go->format) {
+	case V4L2_PIX_FMT_MJPEG:
+		mode_flag = FLAG_MODE_MJPEG;
+		break;
+	case V4L2_PIX_FMT_MPEG1:
+		mode_flag = FLAG_MODE_MPEG1;
+		break;
+	case V4L2_PIX_FMT_MPEG2:
+		mode_flag = FLAG_MODE_MPEG2;
+		break;
+	case V4L2_PIX_FMT_MPEG4:
+		mode_flag = FLAG_MODE_MPEG4;
+		break;
+	default:
+		return -1;
+	}
+	if (request_firmware(&fw_entry, GO7007_FW_NAME, go->dev)) {
+		dev_err(go->dev,
+			"unable to load firmware from file \"%s\"\n",
+			GO7007_FW_NAME);
+		return -1;
+	}
+	code = kcalloc(codespace, 2, GFP_KERNEL);
+	if (code == NULL)
+		goto fw_failed;
+
+	src = (__le16 *)fw_entry->data;
+	srclen = fw_entry->size / 2;
+	while (srclen >= 2) {
+		chunk_flags = __le16_to_cpu(src[0]);
+		chunk_len = __le16_to_cpu(src[1]);
+		if (chunk_len + 2 > srclen) {
+			dev_err(go->dev,
+				"firmware file \"%s\" appears to be corrupted\n",
+				GO7007_FW_NAME);
+			goto fw_failed;
+		}
+		if (chunk_flags & mode_flag) {
+			if (chunk_flags & FLAG_SPECIAL) {
+				ret = do_special(go, __le16_to_cpu(src[2]),
+					&code[i], codespace - i, framelen);
+				if (ret < 0) {
+					dev_err(go->dev,
+						"insufficient memory for firmware construction\n");
+					goto fw_failed;
+				}
+				i += ret;
+			} else {
+				if (codespace - i < chunk_len) {
+					dev_err(go->dev,
+						"insufficient memory for firmware construction\n");
+					goto fw_failed;
+				}
+				memcpy(&code[i], &src[2], chunk_len * 2);
+				i += chunk_len;
+			}
+		}
+		srclen -= chunk_len + 2;
+		src += chunk_len + 2;
+	}
+	release_firmware(fw_entry);
+	*fw = (u8 *)code;
+	*fwlen = i * 2;
+	return 0;
+
+fw_failed:
+	kfree(code);
+	release_firmware(fw_entry);
+	return -1;
+}
+
+MODULE_FIRMWARE(GO7007_FW_NAME);
diff --git a/drivers/media/usb/go7007/go7007-i2c.c b/drivers/media/usb/go7007/go7007-i2c.c
new file mode 100644
index 0000000..c084bf7
--- /dev/null
+++ b/drivers/media/usb/go7007/go7007-i2c.c
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2005-2006 Micronas USA Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (Version 2) as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/list.h>
+#include <linux/unistd.h>
+#include <linux/time.h>
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/uaccess.h>
+
+#include "go7007-priv.h"
+
+/********************* Driver for on-board I2C adapter *********************/
+
+/* #define GO7007_I2C_DEBUG */
+
+#define SPI_I2C_ADDR_BASE		0x1400
+#define STATUS_REG_ADDR			(SPI_I2C_ADDR_BASE + 0x2)
+#define I2C_CTRL_REG_ADDR		(SPI_I2C_ADDR_BASE + 0x6)
+#define I2C_DEV_UP_ADDR_REG_ADDR	(SPI_I2C_ADDR_BASE + 0x7)
+#define I2C_LO_ADDR_REG_ADDR		(SPI_I2C_ADDR_BASE + 0x8)
+#define I2C_DATA_REG_ADDR		(SPI_I2C_ADDR_BASE + 0x9)
+#define I2C_CLKFREQ_REG_ADDR		(SPI_I2C_ADDR_BASE + 0xa)
+
+#define I2C_STATE_MASK			0x0007
+#define I2C_READ_READY_MASK		0x0008
+
+/* There is only one I2C port on the TW2804 that feeds all four GO7007 VIPs
+ * on the Adlink PCI-MPG24, so access is shared between all of them. */
+static DEFINE_MUTEX(adlink_mpg24_i2c_lock);
+
+static int go7007_i2c_xfer(struct go7007 *go, u16 addr, int read,
+		u16 command, int flags, u8 *data)
+{
+	int i, ret = -EIO;
+	u16 val;
+
+	if (go->status == STATUS_SHUTDOWN)
+		return -ENODEV;
+
+#ifdef GO7007_I2C_DEBUG
+	if (read)
+		dev_dbg(go->dev, "go7007-i2c: reading 0x%02x on 0x%02x\n",
+			command, addr);
+	else
+		dev_dbg(go->dev,
+			"go7007-i2c: writing 0x%02x to 0x%02x on 0x%02x\n",
+			*data, command, addr);
+#endif
+
+	mutex_lock(&go->hw_lock);
+
+	if (go->board_id == GO7007_BOARDID_ADLINK_MPG24) {
+		/* Bridge the I2C port on this GO7007 to the shared bus */
+		mutex_lock(&adlink_mpg24_i2c_lock);
+		go7007_write_addr(go, 0x3c82, 0x0020);
+	}
+
+	/* Wait for I2C adapter to be ready */
+	for (i = 0; i < 10; ++i) {
+		if (go7007_read_addr(go, STATUS_REG_ADDR, &val) < 0)
+			goto i2c_done;
+		if (!(val & I2C_STATE_MASK))
+			break;
+		msleep(100);
+	}
+	if (i == 10) {
+		dev_err(go->dev, "go7007-i2c: I2C adapter is hung\n");
+		goto i2c_done;
+	}
+
+	/* Set target register (command) */
+	go7007_write_addr(go, I2C_CTRL_REG_ADDR, flags);
+	go7007_write_addr(go, I2C_LO_ADDR_REG_ADDR, command);
+
+	/* If we're writing, send the data and target address and we're done */
+	if (!read) {
+		go7007_write_addr(go, I2C_DATA_REG_ADDR, *data);
+		go7007_write_addr(go, I2C_DEV_UP_ADDR_REG_ADDR,
+					(addr << 9) | (command >> 8));
+		ret = 0;
+		goto i2c_done;
+	}
+
+	/* Otherwise, we're reading.  First clear i2c_rx_data_rdy. */
+	if (go7007_read_addr(go, I2C_DATA_REG_ADDR, &val) < 0)
+		goto i2c_done;
+
+	/* Send the target address plus read flag */
+	go7007_write_addr(go, I2C_DEV_UP_ADDR_REG_ADDR,
+			(addr << 9) | 0x0100 | (command >> 8));
+
+	/* Wait for i2c_rx_data_rdy */
+	for (i = 0; i < 10; ++i) {
+		if (go7007_read_addr(go, STATUS_REG_ADDR, &val) < 0)
+			goto i2c_done;
+		if (val & I2C_READ_READY_MASK)
+			break;
+		msleep(100);
+	}
+	if (i == 10) {
+		dev_err(go->dev, "go7007-i2c: I2C adapter is hung\n");
+		goto i2c_done;
+	}
+
+	/* Retrieve the read byte */
+	if (go7007_read_addr(go, I2C_DATA_REG_ADDR, &val) < 0)
+		goto i2c_done;
+	*data = val;
+	ret = 0;
+
+i2c_done:
+	if (go->board_id == GO7007_BOARDID_ADLINK_MPG24) {
+		/* Isolate the I2C port on this GO7007 from the shared bus */
+		go7007_write_addr(go, 0x3c82, 0x0000);
+		mutex_unlock(&adlink_mpg24_i2c_lock);
+	}
+	mutex_unlock(&go->hw_lock);
+	return ret;
+}
+
+static int go7007_smbus_xfer(struct i2c_adapter *adapter, u16 addr,
+		unsigned short flags, char read_write,
+		u8 command, int size, union i2c_smbus_data *data)
+{
+	struct go7007 *go = i2c_get_adapdata(adapter);
+
+	if (size != I2C_SMBUS_BYTE_DATA)
+		return -EIO;
+	return go7007_i2c_xfer(go, addr, read_write == I2C_SMBUS_READ, command,
+			flags & I2C_CLIENT_SCCB ? 0x10 : 0x00, &data->byte);
+}
+
+/* VERY LIMITED I2C master xfer function -- only needed because the
+ * SMBus functions only support 8-bit commands and the SAA7135 uses
+ * 16-bit commands.  The I2C interface on the GO7007, as limited as
+ * it is, does support this mode. */
+
+static int go7007_i2c_master_xfer(struct i2c_adapter *adapter,
+					struct i2c_msg msgs[], int num)
+{
+	struct go7007 *go = i2c_get_adapdata(adapter);
+	int i;
+
+	for (i = 0; i < num; ++i) {
+		/* We can only do two things here -- write three bytes, or
+		 * write two bytes and read one byte. */
+		if (msgs[i].len == 2) {
+			if (i + 1 == num || msgs[i].addr != msgs[i + 1].addr ||
+					(msgs[i].flags & I2C_M_RD) ||
+					!(msgs[i + 1].flags & I2C_M_RD) ||
+					msgs[i + 1].len != 1)
+				return -EIO;
+			if (go7007_i2c_xfer(go, msgs[i].addr, 1,
+					(msgs[i].buf[0] << 8) | msgs[i].buf[1],
+					0x01, &msgs[i + 1].buf[0]) < 0)
+				return -EIO;
+			++i;
+		} else if (msgs[i].len == 3) {
+			if (msgs[i].flags & I2C_M_RD)
+				return -EIO;
+			if (msgs[i].len != 3)
+				return -EIO;
+			if (go7007_i2c_xfer(go, msgs[i].addr, 0,
+					(msgs[i].buf[0] << 8) | msgs[i].buf[1],
+					0x01, &msgs[i].buf[2]) < 0)
+				return -EIO;
+		} else
+			return -EIO;
+	}
+
+	return num;
+}
+
+static u32 go7007_functionality(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_SMBUS_BYTE_DATA;
+}
+
+static const struct i2c_algorithm go7007_algo = {
+	.smbus_xfer	= go7007_smbus_xfer,
+	.master_xfer	= go7007_i2c_master_xfer,
+	.functionality	= go7007_functionality,
+};
+
+static struct i2c_adapter go7007_adap_templ = {
+	.owner			= THIS_MODULE,
+	.name			= "WIS GO7007SB",
+	.algo			= &go7007_algo,
+};
+
+int go7007_i2c_init(struct go7007 *go)
+{
+	memcpy(&go->i2c_adapter, &go7007_adap_templ,
+			sizeof(go7007_adap_templ));
+	go->i2c_adapter.dev.parent = go->dev;
+	i2c_set_adapdata(&go->i2c_adapter, go);
+	if (i2c_add_adapter(&go->i2c_adapter) < 0) {
+		dev_err(go->dev,
+			"go7007-i2c: error: i2c_add_adapter failed\n");
+		return -1;
+	}
+	return 0;
+}
diff --git a/drivers/media/usb/go7007/go7007-loader.c b/drivers/media/usb/go7007/go7007-loader.c
new file mode 100644
index 0000000..042f78a
--- /dev/null
+++ b/drivers/media/usb/go7007/go7007-loader.c
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2008 Sensoray Company Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (Version 2) as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/firmware.h>
+#include <cypress_firmware.h>
+
+struct fw_config {
+	u16 vendor;
+	u16 product;
+	const char * const fw_name1;
+	const char * const fw_name2;
+};
+
+static struct fw_config fw_configs[] = {
+	{ 0x1943, 0xa250, "go7007/s2250-1.fw", "go7007/s2250-2.fw" },
+	{ 0x093b, 0xa002, "go7007/px-m402u.fw", NULL },
+	{ 0x093b, 0xa004, "go7007/px-tv402u.fw", NULL },
+	{ 0x0eb1, 0x6666, "go7007/lr192.fw", NULL },
+	{ 0x0eb1, 0x6668, "go7007/wis-startrek.fw", NULL },
+	{ 0, 0, NULL, NULL }
+};
+MODULE_FIRMWARE("go7007/s2250-1.fw");
+MODULE_FIRMWARE("go7007/s2250-2.fw");
+MODULE_FIRMWARE("go7007/px-m402u.fw");
+MODULE_FIRMWARE("go7007/px-tv402u.fw");
+MODULE_FIRMWARE("go7007/lr192.fw");
+MODULE_FIRMWARE("go7007/wis-startrek.fw");
+
+static int go7007_loader_probe(struct usb_interface *interface,
+				const struct usb_device_id *id)
+{
+	struct usb_device *usbdev;
+	const struct firmware *fw;
+	u16 vendor, product;
+	const char *fw1, *fw2;
+	int ret;
+	int i;
+
+	usbdev = usb_get_dev(interface_to_usbdev(interface));
+	if (!usbdev)
+		goto failed2;
+
+	if (usbdev->descriptor.bNumConfigurations != 1) {
+		dev_err(&interface->dev, "can't handle multiple config\n");
+		goto failed2;
+	}
+
+	vendor = le16_to_cpu(usbdev->descriptor.idVendor);
+	product = le16_to_cpu(usbdev->descriptor.idProduct);
+
+	for (i = 0; fw_configs[i].fw_name1; i++)
+		if (fw_configs[i].vendor == vendor &&
+		    fw_configs[i].product == product)
+			break;
+
+	/* Should never happen */
+	if (fw_configs[i].fw_name1 == NULL)
+		goto failed2;
+
+	fw1 = fw_configs[i].fw_name1;
+	fw2 = fw_configs[i].fw_name2;
+
+	dev_info(&interface->dev, "loading firmware %s\n", fw1);
+
+	if (request_firmware(&fw, fw1, &usbdev->dev)) {
+		dev_err(&interface->dev,
+			"unable to load firmware from file \"%s\"\n", fw1);
+		goto failed2;
+	}
+	ret = cypress_load_firmware(usbdev, fw, CYPRESS_FX2);
+	release_firmware(fw);
+	if (0 != ret) {
+		dev_err(&interface->dev, "loader download failed\n");
+		goto failed2;
+	}
+
+	if (fw2 == NULL)
+		return 0;
+
+	if (request_firmware(&fw, fw2, &usbdev->dev)) {
+		dev_err(&interface->dev,
+			"unable to load firmware from file \"%s\"\n", fw2);
+		goto failed2;
+	}
+	ret = cypress_load_firmware(usbdev, fw, CYPRESS_FX2);
+	release_firmware(fw);
+	if (0 != ret) {
+		dev_err(&interface->dev, "firmware download failed\n");
+		goto failed2;
+	}
+	return 0;
+
+failed2:
+	usb_put_dev(usbdev);
+	dev_err(&interface->dev, "probe failed\n");
+	return -ENODEV;
+}
+
+static void go7007_loader_disconnect(struct usb_interface *interface)
+{
+	dev_info(&interface->dev, "disconnect\n");
+	usb_put_dev(interface_to_usbdev(interface));
+	usb_set_intfdata(interface, NULL);
+}
+
+static const struct usb_device_id go7007_loader_ids[] = {
+	{ USB_DEVICE(0x1943, 0xa250) },
+	{ USB_DEVICE(0x093b, 0xa002) },
+	{ USB_DEVICE(0x093b, 0xa004) },
+	{ USB_DEVICE(0x0eb1, 0x6666) },
+	{ USB_DEVICE(0x0eb1, 0x6668) },
+	{}                          /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, go7007_loader_ids);
+
+static struct usb_driver go7007_loader_driver = {
+	.name		= "go7007-loader",
+	.probe		= go7007_loader_probe,
+	.disconnect	= go7007_loader_disconnect,
+	.id_table	= go7007_loader_ids,
+};
+
+module_usb_driver(go7007_loader_driver);
+
+MODULE_AUTHOR("");
+MODULE_DESCRIPTION("firmware loader for go7007-usb");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/usb/go7007/go7007-priv.h b/drivers/media/usb/go7007/go7007-priv.h
new file mode 100644
index 0000000..bebee8c
--- /dev/null
+++ b/drivers/media/usb/go7007/go7007-priv.h
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2005-2006 Micronas USA Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (Version 2) as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+/*
+ * This is the private include file for the go7007 driver.  It should not
+ * be included by anybody but the driver itself, and especially not by
+ * user-space applications.
+ */
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-fh.h>
+#include <media/videobuf2-v4l2.h>
+
+struct go7007;
+
+/* IDs to activate board-specific support code */
+#define GO7007_BOARDID_MATRIX_II	0
+#define GO7007_BOARDID_MATRIX_RELOAD	1
+#define GO7007_BOARDID_STAR_TREK	2
+#define GO7007_BOARDID_PCI_VOYAGER	3
+#define GO7007_BOARDID_XMEN		4
+#define GO7007_BOARDID_XMEN_II		5
+#define GO7007_BOARDID_XMEN_III		6
+#define GO7007_BOARDID_MATRIX_REV	7
+#define GO7007_BOARDID_PX_M402U		8
+#define GO7007_BOARDID_PX_TV402U	9
+#define GO7007_BOARDID_LIFEVIEW_LR192	10 /* TV Walker Ultra */
+#define GO7007_BOARDID_ENDURA		11
+#define GO7007_BOARDID_ADLINK_MPG24	12
+#define GO7007_BOARDID_SENSORAY_2250	13 /* Sensoray 2250/2251 */
+#define GO7007_BOARDID_ADS_USBAV_709    14
+
+/* Various characteristics of each board */
+#define GO7007_BOARD_HAS_AUDIO		(1<<0)
+#define GO7007_BOARD_USE_ONBOARD_I2C	(1<<1)
+#define GO7007_BOARD_HAS_TUNER		(1<<2)
+
+/* Characteristics of sensor devices */
+#define GO7007_SENSOR_VALID_POLAR	(1<<0)
+#define GO7007_SENSOR_HREF_POLAR	(1<<1)
+#define GO7007_SENSOR_VREF_POLAR	(1<<2)
+#define GO7007_SENSOR_FIELD_ID_POLAR	(1<<3)
+#define GO7007_SENSOR_BIT_WIDTH		(1<<4)
+#define GO7007_SENSOR_VALID_ENABLE	(1<<5)
+#define GO7007_SENSOR_656		(1<<6)
+#define GO7007_SENSOR_CONFIG_MASK	0x7f
+#define GO7007_SENSOR_TV		(1<<7)
+#define GO7007_SENSOR_VBI		(1<<8)
+#define GO7007_SENSOR_SCALING		(1<<9)
+#define GO7007_SENSOR_SAA7115		(1<<10)
+
+/* Characteristics of audio sensor devices */
+#define GO7007_AUDIO_I2S_MODE_1		(1)
+#define GO7007_AUDIO_I2S_MODE_2		(2)
+#define GO7007_AUDIO_I2S_MODE_3		(3)
+#define GO7007_AUDIO_BCLK_POLAR		(1<<2)
+#define GO7007_AUDIO_WORD_14		(14<<4)
+#define GO7007_AUDIO_WORD_16		(16<<4)
+#define GO7007_AUDIO_ONE_CHANNEL	(1<<11)
+#define GO7007_AUDIO_I2S_MASTER		(1<<16)
+#define GO7007_AUDIO_OKI_MODE		(1<<17)
+
+#define GO7007_CID_CUSTOM_BASE		(V4L2_CID_DETECT_CLASS_BASE + 0x1000)
+#define V4L2_CID_PIXEL_THRESHOLD0	(GO7007_CID_CUSTOM_BASE+1)
+#define V4L2_CID_MOTION_THRESHOLD0	(GO7007_CID_CUSTOM_BASE+2)
+#define V4L2_CID_MB_THRESHOLD0		(GO7007_CID_CUSTOM_BASE+3)
+#define V4L2_CID_PIXEL_THRESHOLD1	(GO7007_CID_CUSTOM_BASE+4)
+#define V4L2_CID_MOTION_THRESHOLD1	(GO7007_CID_CUSTOM_BASE+5)
+#define V4L2_CID_MB_THRESHOLD1		(GO7007_CID_CUSTOM_BASE+6)
+#define V4L2_CID_PIXEL_THRESHOLD2	(GO7007_CID_CUSTOM_BASE+7)
+#define V4L2_CID_MOTION_THRESHOLD2	(GO7007_CID_CUSTOM_BASE+8)
+#define V4L2_CID_MB_THRESHOLD2		(GO7007_CID_CUSTOM_BASE+9)
+#define V4L2_CID_PIXEL_THRESHOLD3	(GO7007_CID_CUSTOM_BASE+10)
+#define V4L2_CID_MOTION_THRESHOLD3	(GO7007_CID_CUSTOM_BASE+11)
+#define V4L2_CID_MB_THRESHOLD3		(GO7007_CID_CUSTOM_BASE+12)
+
+struct go7007_board_info {
+	unsigned int flags;
+	int hpi_buffer_cap;
+	unsigned int sensor_flags;
+	int sensor_width;
+	int sensor_height;
+	int sensor_framerate;
+	int sensor_h_offset;
+	int sensor_v_offset;
+	unsigned int audio_flags;
+	int audio_rate;
+	int audio_bclk_div;
+	int audio_main_div;
+	int num_i2c_devs;
+	struct go_i2c {
+		const char *type;
+		unsigned int is_video:1;
+		unsigned int is_audio:1;
+		int addr;
+		u32 flags;
+	} i2c_devs[5];
+	int num_inputs;
+	struct {
+		int video_input;
+		int audio_index;
+		char *name;
+	} inputs[4];
+	int video_config;
+	int num_aud_inputs;
+	struct {
+		int audio_input;
+		char *name;
+	} aud_inputs[3];
+};
+
+struct go7007_hpi_ops {
+	int (*interface_reset)(struct go7007 *go);
+	int (*write_interrupt)(struct go7007 *go, int addr, int data);
+	int (*read_interrupt)(struct go7007 *go);
+	int (*stream_start)(struct go7007 *go);
+	int (*stream_stop)(struct go7007 *go);
+	int (*send_firmware)(struct go7007 *go, u8 *data, int len);
+	int (*send_command)(struct go7007 *go, unsigned int cmd, void *arg);
+	void (*release)(struct go7007 *go);
+};
+
+/* The video buffer size must be a multiple of PAGE_SIZE */
+#define	GO7007_BUF_PAGES	(128 * 1024 / PAGE_SIZE)
+#define	GO7007_BUF_SIZE		(GO7007_BUF_PAGES << PAGE_SHIFT)
+
+struct go7007_buffer {
+	struct vb2_v4l2_buffer vb;
+	struct list_head list;
+	unsigned int frame_offset;
+	u32 modet_active;
+};
+
+#define GO7007_RATIO_1_1	0
+#define GO7007_RATIO_4_3	1
+#define GO7007_RATIO_16_9	2
+
+enum go7007_parser_state {
+	STATE_DATA,
+	STATE_00,
+	STATE_00_00,
+	STATE_00_00_01,
+	STATE_FF,
+	STATE_VBI_LEN_A,
+	STATE_VBI_LEN_B,
+	STATE_MODET_MAP,
+	STATE_UNPARSED,
+};
+
+struct go7007 {
+	struct device *dev;
+	u8 bus_info[32];
+	const struct go7007_board_info *board_info;
+	unsigned int board_id;
+	int tuner_type;
+	int channel_number; /* for multi-channel boards like Adlink PCI-MPG24 */
+	char name[64];
+	struct video_device vdev;
+	void *boot_fw;
+	unsigned boot_fw_len;
+	struct v4l2_device v4l2_dev;
+	struct v4l2_ctrl_handler hdl;
+	struct v4l2_ctrl *mpeg_video_encoding;
+	struct v4l2_ctrl *mpeg_video_gop_size;
+	struct v4l2_ctrl *mpeg_video_gop_closure;
+	struct v4l2_ctrl *mpeg_video_bitrate;
+	struct v4l2_ctrl *mpeg_video_aspect_ratio;
+	struct v4l2_ctrl *mpeg_video_b_frames;
+	struct v4l2_ctrl *mpeg_video_rep_seqheader;
+	struct v4l2_ctrl *modet_mode;
+	enum { STATUS_INIT, STATUS_ONLINE, STATUS_SHUTDOWN } status;
+	spinlock_t spinlock;
+	struct mutex hw_lock;
+	struct mutex serialize_lock;
+	int audio_enabled;
+	struct v4l2_subdev *sd_video;
+	struct v4l2_subdev *sd_audio;
+	u8 usb_buf[16];
+
+	/* Video input */
+	int input;
+	int aud_input;
+	enum { GO7007_STD_NTSC, GO7007_STD_PAL, GO7007_STD_OTHER } standard;
+	v4l2_std_id std;
+	int sensor_framerate;
+	int width;
+	int height;
+	int encoder_h_offset;
+	int encoder_v_offset;
+	unsigned int encoder_h_halve:1;
+	unsigned int encoder_v_halve:1;
+	unsigned int encoder_subsample:1;
+
+	/* Encoder config */
+	u32 format;
+	int bitrate;
+	int fps_scale;
+	int pali;
+	int aspect_ratio;
+	int gop_size;
+	unsigned int ipb:1;
+	unsigned int closed_gop:1;
+	unsigned int repeat_seqhead:1;
+	unsigned int seq_header_enable:1;
+	unsigned int gop_header_enable:1;
+	unsigned int dvd_mode:1;
+	unsigned int interlace_coding:1;
+
+	/* Motion detection */
+	unsigned int modet_enable:1;
+	struct {
+		unsigned int enable:1;
+		int pixel_threshold;
+		int motion_threshold;
+		int mb_threshold;
+	} modet[4];
+	unsigned char modet_map[1624];
+	unsigned char active_map[216];
+	u32 modet_event_status;
+
+	/* Video streaming */
+	struct mutex queue_lock;
+	struct vb2_queue vidq;
+	enum go7007_parser_state state;
+	int parse_length;
+	u16 modet_word;
+	int seen_frame;
+	u32 next_seq;
+	struct list_head vidq_active;
+	wait_queue_head_t frame_waitq;
+	struct go7007_buffer *active_buf;
+
+	/* Audio streaming */
+	void (*audio_deliver)(struct go7007 *go, u8 *buf, int length);
+	void *snd_context;
+
+	/* I2C */
+	int i2c_adapter_online;
+	struct i2c_adapter i2c_adapter;
+
+	/* HPI driver */
+	const struct go7007_hpi_ops *hpi_ops;
+	void *hpi_context;
+	int interrupt_available;
+	wait_queue_head_t interrupt_waitq;
+	unsigned short interrupt_value;
+	unsigned short interrupt_data;
+};
+
+static inline struct go7007 *to_go7007(struct v4l2_device *v4l2_dev)
+{
+	return container_of(v4l2_dev, struct go7007, v4l2_dev);
+}
+
+/* All of these must be called with the hpi_lock mutex held! */
+#define go7007_interface_reset(go) \
+			((go)->hpi_ops->interface_reset(go))
+#define	go7007_write_interrupt(go, x, y) \
+			((go)->hpi_ops->write_interrupt)((go), (x), (y))
+#define go7007_stream_start(go) \
+			((go)->hpi_ops->stream_start(go))
+#define go7007_stream_stop(go) \
+			((go)->hpi_ops->stream_stop(go))
+#define	go7007_send_firmware(go, x, y) \
+			((go)->hpi_ops->send_firmware)((go), (x), (y))
+#define go7007_write_addr(go, x, y) \
+			((go)->hpi_ops->write_interrupt)((go), (x)|0x8000, (y))
+
+/* go7007-driver.c */
+int go7007_read_addr(struct go7007 *go, u16 addr, u16 *data);
+int go7007_read_interrupt(struct go7007 *go, u16 *value, u16 *data);
+int go7007_boot_encoder(struct go7007 *go, int init_i2c);
+int go7007_reset_encoder(struct go7007 *go);
+int go7007_register_encoder(struct go7007 *go, unsigned num_i2c_devs);
+int go7007_start_encoder(struct go7007 *go);
+void go7007_parse_video_stream(struct go7007 *go, u8 *buf, int length);
+struct go7007 *go7007_alloc(const struct go7007_board_info *board,
+					struct device *dev);
+void go7007_update_board(struct go7007 *go);
+
+/* go7007-fw.c */
+int go7007_construct_fw_image(struct go7007 *go, u8 **fw, int *fwlen);
+
+/* go7007-i2c.c */
+int go7007_i2c_init(struct go7007 *go);
+int go7007_i2c_remove(struct go7007 *go);
+
+/* go7007-v4l2.c */
+int go7007_v4l2_init(struct go7007 *go);
+int go7007_v4l2_ctrl_init(struct go7007 *go);
+void go7007_v4l2_remove(struct go7007 *go);
+
+/* snd-go7007.c */
+int go7007_snd_init(struct go7007 *go);
+int go7007_snd_remove(struct go7007 *go);
diff --git a/drivers/media/usb/go7007/go7007-usb.c b/drivers/media/usb/go7007/go7007-usb.c
new file mode 100644
index 0000000..19c6a03
--- /dev/null
+++ b/drivers/media/usb/go7007/go7007-usb.c
@@ -0,0 +1,1350 @@
+/*
+ * Copyright (C) 2005-2006 Micronas USA Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (Version 2) as
+ * published by the Free Software Foundation.
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/wait.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/mm.h>
+#include <linux/usb.h>
+#include <linux/i2c.h>
+#include <asm/byteorder.h>
+#include <media/i2c/saa7115.h>
+#include <media/tuner.h>
+#include <media/i2c/uda1342.h>
+
+#include "go7007-priv.h"
+
+static unsigned int assume_endura;
+module_param(assume_endura, int, 0644);
+MODULE_PARM_DESC(assume_endura,
+			"when probing fails, hardware is a Pelco Endura");
+
+/* #define GO7007_I2C_DEBUG */ /* for debugging the EZ-USB I2C adapter */
+
+#define	HPI_STATUS_ADDR	0xFFF4
+#define	INT_PARAM_ADDR	0xFFF6
+#define	INT_INDEX_ADDR	0xFFF8
+
+/*
+ * Pipes on EZ-USB interface:
+ *	0 snd - Control
+ *	0 rcv - Control
+ *	2 snd - Download firmware (control)
+ *	4 rcv - Read Interrupt (interrupt)
+ *	6 rcv - Read Video (bulk)
+ *	8 rcv - Read Audio (bulk)
+ */
+
+#define GO7007_USB_EZUSB		(1<<0)
+#define GO7007_USB_EZUSB_I2C		(1<<1)
+
+struct go7007_usb_board {
+	unsigned int flags;
+	struct go7007_board_info main_info;
+};
+
+struct go7007_usb {
+	const struct go7007_usb_board *board;
+	struct mutex i2c_lock;
+	struct usb_device *usbdev;
+	struct urb *video_urbs[8];
+	struct urb *audio_urbs[8];
+	struct urb *intr_urb;
+};
+
+/*********************** Product specification data ***********************/
+
+static const struct go7007_usb_board board_matrix_ii = {
+	.flags		= GO7007_USB_EZUSB,
+	.main_info	= {
+		.flags		 = GO7007_BOARD_HAS_AUDIO |
+					GO7007_BOARD_USE_ONBOARD_I2C,
+		.audio_flags	 = GO7007_AUDIO_I2S_MODE_1 |
+					GO7007_AUDIO_WORD_16,
+		.audio_rate	 = 48000,
+		.audio_bclk_div	 = 8,
+		.audio_main_div	 = 2,
+		.hpi_buffer_cap  = 7,
+		.sensor_flags	 = GO7007_SENSOR_656 |
+					GO7007_SENSOR_VALID_ENABLE |
+					GO7007_SENSOR_TV |
+					GO7007_SENSOR_SAA7115 |
+					GO7007_SENSOR_VBI |
+					GO7007_SENSOR_SCALING,
+		.num_i2c_devs	 = 1,
+		.i2c_devs	 = {
+			{
+				.type	= "saa7115",
+				.addr	= 0x20,
+				.is_video = 1,
+			},
+		},
+		.num_inputs	 = 2,
+		.inputs		 = {
+			{
+				.video_input	= 0,
+				.name		= "Composite",
+			},
+			{
+				.video_input	= 9,
+				.name		= "S-Video",
+			},
+		},
+		.video_config	= SAA7115_IDQ_IS_DEFAULT,
+	},
+};
+
+static const struct go7007_usb_board board_matrix_reload = {
+	.flags		= GO7007_USB_EZUSB,
+	.main_info	= {
+		.flags		 = GO7007_BOARD_HAS_AUDIO |
+					GO7007_BOARD_USE_ONBOARD_I2C,
+		.audio_flags	 = GO7007_AUDIO_I2S_MODE_1 |
+					GO7007_AUDIO_I2S_MASTER |
+					GO7007_AUDIO_WORD_16,
+		.audio_rate	 = 48000,
+		.audio_bclk_div	 = 8,
+		.audio_main_div	 = 2,
+		.hpi_buffer_cap  = 7,
+		.sensor_flags	 = GO7007_SENSOR_656 |
+					GO7007_SENSOR_TV,
+		.num_i2c_devs	 = 1,
+		.i2c_devs	 = {
+			{
+				.type	= "saa7113",
+				.addr	= 0x25,
+				.is_video = 1,
+			},
+		},
+		.num_inputs	 = 2,
+		.inputs		 = {
+			{
+				.video_input	= 0,
+				.name		= "Composite",
+			},
+			{
+				.video_input	= 9,
+				.name		= "S-Video",
+			},
+		},
+		.video_config	= SAA7115_IDQ_IS_DEFAULT,
+	},
+};
+
+static const struct go7007_usb_board board_star_trek = {
+	.flags		= GO7007_USB_EZUSB | GO7007_USB_EZUSB_I2C,
+	.main_info	= {
+		.flags		 = GO7007_BOARD_HAS_AUDIO, /* |
+					GO7007_BOARD_HAS_TUNER, */
+		.sensor_flags	 = GO7007_SENSOR_656 |
+					GO7007_SENSOR_VALID_ENABLE |
+					GO7007_SENSOR_TV |
+					GO7007_SENSOR_SAA7115 |
+					GO7007_SENSOR_VBI |
+					GO7007_SENSOR_SCALING,
+		.audio_flags	 = GO7007_AUDIO_I2S_MODE_1 |
+					GO7007_AUDIO_WORD_16,
+		.audio_bclk_div	 = 8,
+		.audio_main_div	 = 2,
+		.hpi_buffer_cap  = 7,
+		.num_i2c_devs	 = 1,
+		.i2c_devs	 = {
+			{
+				.type	= "saa7115",
+				.addr	= 0x20,
+				.is_video = 1,
+			},
+		},
+		.num_inputs	 = 2,
+		.inputs		 = {
+		/*	{
+		 *		.video_input	= 3,
+		 *		.audio_index	= AUDIO_TUNER,
+		 *		.name		= "Tuner",
+		 *	},
+		 */
+			{
+				.video_input	= 1,
+			/*	.audio_index	= AUDIO_EXTERN, */
+				.name		= "Composite",
+			},
+			{
+				.video_input	= 8,
+			/*	.audio_index	= AUDIO_EXTERN, */
+				.name		= "S-Video",
+			},
+		},
+		.video_config	= SAA7115_IDQ_IS_DEFAULT,
+	},
+};
+
+static const struct go7007_usb_board board_px_tv402u = {
+	.flags		= GO7007_USB_EZUSB | GO7007_USB_EZUSB_I2C,
+	.main_info	= {
+		.flags		 = GO7007_BOARD_HAS_AUDIO |
+					GO7007_BOARD_HAS_TUNER,
+		.sensor_flags	 = GO7007_SENSOR_656 |
+					GO7007_SENSOR_VALID_ENABLE |
+					GO7007_SENSOR_TV |
+					GO7007_SENSOR_SAA7115 |
+					GO7007_SENSOR_VBI |
+					GO7007_SENSOR_SCALING,
+		.audio_flags	 = GO7007_AUDIO_I2S_MODE_1 |
+					GO7007_AUDIO_WORD_16,
+		.audio_bclk_div	 = 8,
+		.audio_main_div	 = 2,
+		.hpi_buffer_cap  = 7,
+		.num_i2c_devs	 = 5,
+		.i2c_devs	 = {
+			{
+				.type	= "saa7115",
+				.addr	= 0x20,
+				.is_video = 1,
+			},
+			{
+				.type	= "uda1342",
+				.addr	= 0x1a,
+				.is_audio = 1,
+			},
+			{
+				.type	= "tuner",
+				.addr	= 0x60,
+			},
+			{
+				.type	= "tuner",
+				.addr	= 0x43,
+			},
+			{
+				.type	= "sony-btf-mpx",
+				.addr	= 0x44,
+			},
+		},
+		.num_inputs	 = 3,
+		.inputs		 = {
+			{
+				.video_input	= 3,
+				.audio_index	= 0,
+				.name		= "Tuner",
+			},
+			{
+				.video_input	= 1,
+				.audio_index	= 1,
+				.name		= "Composite",
+			},
+			{
+				.video_input	= 8,
+				.audio_index	= 1,
+				.name		= "S-Video",
+			},
+		},
+		.video_config	= SAA7115_IDQ_IS_DEFAULT,
+		.num_aud_inputs	 = 2,
+		.aud_inputs	 = {
+			{
+				.audio_input	= UDA1342_IN2,
+				.name		= "Tuner",
+			},
+			{
+				.audio_input	= UDA1342_IN1,
+				.name		= "Line In",
+			},
+		},
+	},
+};
+
+static const struct go7007_usb_board board_xmen = {
+	.flags		= 0,
+	.main_info	= {
+		.flags		  = GO7007_BOARD_USE_ONBOARD_I2C,
+		.hpi_buffer_cap   = 0,
+		.sensor_flags	  = GO7007_SENSOR_VREF_POLAR,
+		.sensor_width	  = 320,
+		.sensor_height	  = 240,
+		.sensor_framerate = 30030,
+		.audio_flags	  = GO7007_AUDIO_ONE_CHANNEL |
+					GO7007_AUDIO_I2S_MODE_3 |
+					GO7007_AUDIO_WORD_14 |
+					GO7007_AUDIO_I2S_MASTER |
+					GO7007_AUDIO_BCLK_POLAR |
+					GO7007_AUDIO_OKI_MODE,
+		.audio_rate	  = 8000,
+		.audio_bclk_div	  = 48,
+		.audio_main_div	  = 1,
+		.num_i2c_devs	  = 1,
+		.i2c_devs	  = {
+			{
+				.type	= "ov7640",
+				.addr	= 0x21,
+			},
+		},
+		.num_inputs	  = 1,
+		.inputs		  = {
+			{
+				.name		= "Camera",
+			},
+		},
+	},
+};
+
+static const struct go7007_usb_board board_matrix_revolution = {
+	.flags		= GO7007_USB_EZUSB,
+	.main_info	= {
+		.flags		 = GO7007_BOARD_HAS_AUDIO |
+					GO7007_BOARD_USE_ONBOARD_I2C,
+		.audio_flags	 = GO7007_AUDIO_I2S_MODE_1 |
+					GO7007_AUDIO_I2S_MASTER |
+					GO7007_AUDIO_WORD_16,
+		.audio_rate	 = 48000,
+		.audio_bclk_div	 = 8,
+		.audio_main_div	 = 2,
+		.hpi_buffer_cap  = 7,
+		.sensor_flags	 = GO7007_SENSOR_656 |
+					GO7007_SENSOR_TV |
+					GO7007_SENSOR_VBI,
+		.num_i2c_devs	 = 1,
+		.i2c_devs	 = {
+			{
+				.type	= "tw9903",
+				.is_video = 1,
+				.addr	= 0x44,
+			},
+		},
+		.num_inputs	 = 2,
+		.inputs		 = {
+			{
+				.video_input	= 2,
+				.name		= "Composite",
+			},
+			{
+				.video_input	= 8,
+				.name		= "S-Video",
+			},
+		},
+	},
+};
+
+#if 0
+static const struct go7007_usb_board board_lifeview_lr192 = {
+	.flags		= GO7007_USB_EZUSB,
+	.main_info	= {
+		.flags		 = GO7007_BOARD_HAS_AUDIO |
+					GO7007_BOARD_USE_ONBOARD_I2C,
+		.audio_flags	 = GO7007_AUDIO_I2S_MODE_1 |
+					GO7007_AUDIO_WORD_16,
+		.audio_rate	 = 48000,
+		.audio_bclk_div	 = 8,
+		.audio_main_div	 = 2,
+		.hpi_buffer_cap  = 7,
+		.sensor_flags	 = GO7007_SENSOR_656 |
+					GO7007_SENSOR_VALID_ENABLE |
+					GO7007_SENSOR_TV |
+					GO7007_SENSOR_VBI |
+					GO7007_SENSOR_SCALING,
+		.num_i2c_devs	 = 0,
+		.num_inputs	 = 1,
+		.inputs		 = {
+			{
+				.video_input	= 0,
+				.name		= "Composite",
+			},
+		},
+	},
+};
+#endif
+
+static const struct go7007_usb_board board_endura = {
+	.flags		= 0,
+	.main_info	= {
+		.flags		 = 0,
+		.audio_flags	 = GO7007_AUDIO_I2S_MODE_1 |
+					GO7007_AUDIO_I2S_MASTER |
+					GO7007_AUDIO_WORD_16,
+		.audio_rate	 = 8000,
+		.audio_bclk_div	 = 48,
+		.audio_main_div	 = 8,
+		.hpi_buffer_cap  = 0,
+		.sensor_flags	 = GO7007_SENSOR_656 |
+					GO7007_SENSOR_TV,
+		.sensor_h_offset = 8,
+		.num_i2c_devs	 = 0,
+		.num_inputs	 = 1,
+		.inputs		 = {
+			{
+				.name		= "Camera",
+			},
+		},
+	},
+};
+
+static const struct go7007_usb_board board_adlink_mpg24 = {
+	.flags		= 0,
+	.main_info	= {
+		.flags		 = GO7007_BOARD_USE_ONBOARD_I2C,
+		.audio_flags	 = GO7007_AUDIO_I2S_MODE_1 |
+					GO7007_AUDIO_I2S_MASTER |
+					GO7007_AUDIO_WORD_16,
+		.audio_rate	 = 48000,
+		.audio_bclk_div	 = 8,
+		.audio_main_div	 = 2,
+		.hpi_buffer_cap  = 0,
+		.sensor_flags	 = GO7007_SENSOR_656 |
+					GO7007_SENSOR_TV |
+					GO7007_SENSOR_VBI,
+		.num_i2c_devs	 = 1,
+		.i2c_devs	 = {
+			{
+				.type	= "tw2804",
+				.addr	= 0x00, /* yes, really */
+				.flags  = I2C_CLIENT_TEN,
+				.is_video = 1,
+			},
+		},
+		.num_inputs	 = 1,
+		.inputs		 = {
+			{
+				.name		= "Composite",
+			},
+		},
+	},
+};
+
+static const struct go7007_usb_board board_sensoray_2250 = {
+	.flags		= GO7007_USB_EZUSB | GO7007_USB_EZUSB_I2C,
+	.main_info	= {
+		.audio_flags	 = GO7007_AUDIO_I2S_MODE_1 |
+					GO7007_AUDIO_I2S_MASTER |
+					GO7007_AUDIO_WORD_16,
+		.flags		 = GO7007_BOARD_HAS_AUDIO,
+		.audio_rate	 = 48000,
+		.audio_bclk_div	 = 8,
+		.audio_main_div	 = 2,
+		.hpi_buffer_cap  = 7,
+		.sensor_flags	 = GO7007_SENSOR_656 |
+					GO7007_SENSOR_TV,
+		.num_i2c_devs	 = 1,
+		.i2c_devs	 = {
+			{
+				.type	= "s2250",
+				.addr	= 0x43,
+				.is_video = 1,
+				.is_audio = 1,
+			},
+		},
+		.num_inputs	 = 2,
+		.inputs		 = {
+			{
+				.video_input	= 0,
+				.name		= "Composite",
+			},
+			{
+				.video_input	= 1,
+				.name		= "S-Video",
+			},
+		},
+		.num_aud_inputs	 = 3,
+		.aud_inputs	 = {
+			{
+				.audio_input	= 0,
+				.name		= "Line In",
+			},
+			{
+				.audio_input	= 1,
+				.name		= "Mic",
+			},
+			{
+				.audio_input	= 2,
+				.name		= "Mic Boost",
+			},
+		},
+	},
+};
+
+static const struct go7007_usb_board board_ads_usbav_709 = {
+	.flags		= GO7007_USB_EZUSB,
+	.main_info	= {
+		.flags		 = GO7007_BOARD_HAS_AUDIO |
+					GO7007_BOARD_USE_ONBOARD_I2C,
+		.audio_flags	 = GO7007_AUDIO_I2S_MODE_1 |
+					GO7007_AUDIO_I2S_MASTER |
+					GO7007_AUDIO_WORD_16,
+		.audio_rate	 = 48000,
+		.audio_bclk_div	 = 8,
+		.audio_main_div	 = 2,
+		.hpi_buffer_cap  = 7,
+		.sensor_flags	 = GO7007_SENSOR_656 |
+					GO7007_SENSOR_TV |
+					GO7007_SENSOR_VBI,
+		.num_i2c_devs	 = 1,
+		.i2c_devs	 = {
+			{
+				.type	= "tw9906",
+				.is_video = 1,
+				.addr	= 0x44,
+			},
+		},
+		.num_inputs	 = 2,
+		.inputs		 = {
+			{
+				.video_input	= 0,
+				.name		= "Composite",
+			},
+			{
+				.video_input	= 10,
+				.name		= "S-Video",
+			},
+		},
+	},
+};
+
+static const struct usb_device_id go7007_usb_id_table[] = {
+	{
+		.match_flags	= USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION |
+					USB_DEVICE_ID_MATCH_INT_INFO,
+		.idVendor	= 0x0eb1,  /* Vendor ID of WIS Technologies */
+		.idProduct	= 0x7007,  /* Product ID of GO7007SB chip */
+		.bcdDevice_lo	= 0x200,   /* Revision number of XMen */
+		.bcdDevice_hi	= 0x200,
+		.bInterfaceClass	= 255,
+		.bInterfaceSubClass	= 0,
+		.bInterfaceProtocol	= 255,
+		.driver_info	= (kernel_ulong_t)GO7007_BOARDID_XMEN,
+	},
+	{
+		.match_flags	= USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION,
+		.idVendor	= 0x0eb1,  /* Vendor ID of WIS Technologies */
+		.idProduct	= 0x7007,  /* Product ID of GO7007SB chip */
+		.bcdDevice_lo	= 0x202,   /* Revision number of Matrix II */
+		.bcdDevice_hi	= 0x202,
+		.driver_info	= (kernel_ulong_t)GO7007_BOARDID_MATRIX_II,
+	},
+	{
+		.match_flags	= USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION,
+		.idVendor	= 0x0eb1,  /* Vendor ID of WIS Technologies */
+		.idProduct	= 0x7007,  /* Product ID of GO7007SB chip */
+		.bcdDevice_lo	= 0x204,   /* Revision number of Matrix */
+		.bcdDevice_hi	= 0x204,   /*     Reloaded */
+		.driver_info	= (kernel_ulong_t)GO7007_BOARDID_MATRIX_RELOAD,
+	},
+	{
+		.match_flags	= USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION |
+					USB_DEVICE_ID_MATCH_INT_INFO,
+		.idVendor	= 0x0eb1,  /* Vendor ID of WIS Technologies */
+		.idProduct	= 0x7007,  /* Product ID of GO7007SB chip */
+		.bcdDevice_lo	= 0x205,   /* Revision number of XMen-II */
+		.bcdDevice_hi	= 0x205,
+		.bInterfaceClass	= 255,
+		.bInterfaceSubClass	= 0,
+		.bInterfaceProtocol	= 255,
+		.driver_info	= (kernel_ulong_t)GO7007_BOARDID_XMEN_II,
+	},
+	{
+		.match_flags	= USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION,
+		.idVendor	= 0x0eb1,  /* Vendor ID of WIS Technologies */
+		.idProduct	= 0x7007,  /* Product ID of GO7007SB chip */
+		.bcdDevice_lo	= 0x208,   /* Revision number of Star Trek */
+		.bcdDevice_hi	= 0x208,
+		.driver_info	= (kernel_ulong_t)GO7007_BOARDID_STAR_TREK,
+	},
+	{
+		.match_flags	= USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION |
+					USB_DEVICE_ID_MATCH_INT_INFO,
+		.idVendor	= 0x0eb1,  /* Vendor ID of WIS Technologies */
+		.idProduct	= 0x7007,  /* Product ID of GO7007SB chip */
+		.bcdDevice_lo	= 0x209,   /* Revision number of XMen-III */
+		.bcdDevice_hi	= 0x209,
+		.bInterfaceClass	= 255,
+		.bInterfaceSubClass	= 0,
+		.bInterfaceProtocol	= 255,
+		.driver_info	= (kernel_ulong_t)GO7007_BOARDID_XMEN_III,
+	},
+	{
+		.match_flags	= USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION,
+		.idVendor	= 0x0eb1,  /* Vendor ID of WIS Technologies */
+		.idProduct	= 0x7007,  /* Product ID of GO7007SB chip */
+		.bcdDevice_lo	= 0x210,   /* Revision number of Matrix */
+		.bcdDevice_hi	= 0x210,   /*     Revolution */
+		.driver_info	= (kernel_ulong_t)GO7007_BOARDID_MATRIX_REV,
+	},
+	{
+		.match_flags	= USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION,
+		.idVendor	= 0x093b,  /* Vendor ID of Plextor */
+		.idProduct	= 0xa102,  /* Product ID of M402U */
+		.bcdDevice_lo	= 0x1,	   /* revision number of Blueberry */
+		.bcdDevice_hi	= 0x1,
+		.driver_info	= (kernel_ulong_t)GO7007_BOARDID_PX_M402U,
+	},
+	{
+		.match_flags	= USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION,
+		.idVendor	= 0x093b,  /* Vendor ID of Plextor */
+		.idProduct	= 0xa104,  /* Product ID of TV402U */
+		.bcdDevice_lo	= 0x1,
+		.bcdDevice_hi	= 0x1,
+		.driver_info	= (kernel_ulong_t)GO7007_BOARDID_PX_TV402U,
+	},
+	{
+		.match_flags	= USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION,
+		.idVendor	= 0x10fd,  /* Vendor ID of Anubis Electronics */
+		.idProduct	= 0xde00,  /* Product ID of Lifeview LR192 */
+		.bcdDevice_lo	= 0x1,
+		.bcdDevice_hi	= 0x1,
+		.driver_info	= (kernel_ulong_t)GO7007_BOARDID_LIFEVIEW_LR192,
+	},
+	{
+		.match_flags	= USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION,
+		.idVendor	= 0x1943,  /* Vendor ID Sensoray */
+		.idProduct	= 0x2250,  /* Product ID of 2250/2251 */
+		.bcdDevice_lo	= 0x1,
+		.bcdDevice_hi	= 0x1,
+		.driver_info	= (kernel_ulong_t)GO7007_BOARDID_SENSORAY_2250,
+	},
+	{
+		.match_flags	= USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION,
+		.idVendor	= 0x06e1,  /* Vendor ID of ADS Technologies */
+		.idProduct	= 0x0709,  /* Product ID of DVD Xpress DX2 */
+		.bcdDevice_lo	= 0x204,
+		.bcdDevice_hi	= 0x204,
+		.driver_info	= (kernel_ulong_t)GO7007_BOARDID_ADS_USBAV_709,
+	},
+	{ }					/* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, go7007_usb_id_table);
+
+/********************* Driver for EZ-USB HPI interface *********************/
+
+static int go7007_usb_vendor_request(struct go7007 *go, int request,
+		int value, int index, void *transfer_buffer, int length, int in)
+{
+	struct go7007_usb *usb = go->hpi_context;
+	int timeout = 5000;
+
+	if (in) {
+		return usb_control_msg(usb->usbdev,
+				usb_rcvctrlpipe(usb->usbdev, 0), request,
+				USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+				value, index, transfer_buffer, length, timeout);
+	} else {
+		return usb_control_msg(usb->usbdev,
+				usb_sndctrlpipe(usb->usbdev, 0), request,
+				USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+				value, index, transfer_buffer, length, timeout);
+	}
+}
+
+static int go7007_usb_interface_reset(struct go7007 *go)
+{
+	struct go7007_usb *usb = go->hpi_context;
+	u16 intr_val, intr_data;
+
+	if (go->status == STATUS_SHUTDOWN)
+		return -1;
+	/* Reset encoder */
+	if (go7007_write_interrupt(go, 0x0001, 0x0001) < 0)
+		return -1;
+	msleep(100);
+
+	if (usb->board->flags & GO7007_USB_EZUSB) {
+		/* Reset buffer in EZ-USB */
+		pr_debug("resetting EZ-USB buffers\n");
+		if (go7007_usb_vendor_request(go, 0x10, 0, 0, NULL, 0, 0) < 0 ||
+		    go7007_usb_vendor_request(go, 0x10, 0, 0, NULL, 0, 0) < 0)
+			return -1;
+
+		/* Reset encoder again */
+		if (go7007_write_interrupt(go, 0x0001, 0x0001) < 0)
+			return -1;
+		msleep(100);
+	}
+
+	/* Wait for an interrupt to indicate successful hardware reset */
+	if (go7007_read_interrupt(go, &intr_val, &intr_data) < 0 ||
+			(intr_val & ~0x1) != 0x55aa) {
+		dev_err(go->dev, "unable to reset the USB interface\n");
+		return -1;
+	}
+	return 0;
+}
+
+static int go7007_usb_ezusb_write_interrupt(struct go7007 *go,
+						int addr, int data)
+{
+	struct go7007_usb *usb = go->hpi_context;
+	int i, r;
+	u16 status_reg = 0;
+	int timeout = 500;
+
+	pr_debug("WriteInterrupt: %04x %04x\n", addr, data);
+
+	for (i = 0; i < 100; ++i) {
+		r = usb_control_msg(usb->usbdev,
+				usb_rcvctrlpipe(usb->usbdev, 0), 0x14,
+				USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+				0, HPI_STATUS_ADDR, go->usb_buf,
+				sizeof(status_reg), timeout);
+		if (r < 0)
+			break;
+		status_reg = le16_to_cpu(*((__le16 *)go->usb_buf));
+		if (!(status_reg & 0x0010))
+			break;
+		msleep(10);
+	}
+	if (r < 0)
+		goto write_int_error;
+	if (i == 100) {
+		dev_err(go->dev, "device is hung, status reg = 0x%04x\n", status_reg);
+		return -1;
+	}
+	r = usb_control_msg(usb->usbdev, usb_sndctrlpipe(usb->usbdev, 0), 0x12,
+			USB_TYPE_VENDOR | USB_RECIP_DEVICE, data,
+			INT_PARAM_ADDR, NULL, 0, timeout);
+	if (r < 0)
+		goto write_int_error;
+	r = usb_control_msg(usb->usbdev, usb_sndctrlpipe(usb->usbdev, 0),
+			0x12, USB_TYPE_VENDOR | USB_RECIP_DEVICE, addr,
+			INT_INDEX_ADDR, NULL, 0, timeout);
+	if (r < 0)
+		goto write_int_error;
+	return 0;
+
+write_int_error:
+	dev_err(go->dev, "error in WriteInterrupt: %d\n", r);
+	return r;
+}
+
+static int go7007_usb_onboard_write_interrupt(struct go7007 *go,
+						int addr, int data)
+{
+	struct go7007_usb *usb = go->hpi_context;
+	int r;
+	int timeout = 500;
+
+	pr_debug("WriteInterrupt: %04x %04x\n", addr, data);
+
+	go->usb_buf[0] = data & 0xff;
+	go->usb_buf[1] = data >> 8;
+	go->usb_buf[2] = addr & 0xff;
+	go->usb_buf[3] = addr >> 8;
+	go->usb_buf[4] = go->usb_buf[5] = go->usb_buf[6] = go->usb_buf[7] = 0;
+	r = usb_control_msg(usb->usbdev, usb_sndctrlpipe(usb->usbdev, 2), 0x00,
+			USB_TYPE_VENDOR | USB_RECIP_ENDPOINT, 0x55aa,
+			0xf0f0, go->usb_buf, 8, timeout);
+	if (r < 0) {
+		dev_err(go->dev, "error in WriteInterrupt: %d\n", r);
+		return r;
+	}
+	return 0;
+}
+
+static void go7007_usb_readinterrupt_complete(struct urb *urb)
+{
+	struct go7007 *go = (struct go7007 *)urb->context;
+	__le16 *regs = (__le16 *)urb->transfer_buffer;
+	int status = urb->status;
+
+	if (status) {
+		if (status != -ESHUTDOWN &&
+				go->status != STATUS_SHUTDOWN) {
+			dev_err(go->dev, "error in read interrupt: %d\n", urb->status);
+		} else {
+			wake_up(&go->interrupt_waitq);
+			return;
+		}
+	} else if (urb->actual_length != urb->transfer_buffer_length) {
+		dev_err(go->dev, "short read in interrupt pipe!\n");
+	} else {
+		go->interrupt_available = 1;
+		go->interrupt_data = __le16_to_cpu(regs[0]);
+		go->interrupt_value = __le16_to_cpu(regs[1]);
+		pr_debug("ReadInterrupt: %04x %04x\n",
+				go->interrupt_value, go->interrupt_data);
+	}
+
+	wake_up(&go->interrupt_waitq);
+}
+
+static int go7007_usb_read_interrupt(struct go7007 *go)
+{
+	struct go7007_usb *usb = go->hpi_context;
+	int r;
+
+	r = usb_submit_urb(usb->intr_urb, GFP_KERNEL);
+	if (r < 0) {
+		dev_err(go->dev, "unable to submit interrupt urb: %d\n", r);
+		return r;
+	}
+	return 0;
+}
+
+static void go7007_usb_read_video_pipe_complete(struct urb *urb)
+{
+	struct go7007 *go = (struct go7007 *)urb->context;
+	int r, status = urb->status;
+
+	if (!vb2_is_streaming(&go->vidq)) {
+		wake_up_interruptible(&go->frame_waitq);
+		return;
+	}
+	if (status) {
+		dev_err(go->dev, "error in video pipe: %d\n", status);
+		return;
+	}
+	if (urb->actual_length != urb->transfer_buffer_length) {
+		dev_err(go->dev, "short read in video pipe!\n");
+		return;
+	}
+	go7007_parse_video_stream(go, urb->transfer_buffer, urb->actual_length);
+	r = usb_submit_urb(urb, GFP_ATOMIC);
+	if (r < 0)
+		dev_err(go->dev, "error in video pipe: %d\n", r);
+}
+
+static void go7007_usb_read_audio_pipe_complete(struct urb *urb)
+{
+	struct go7007 *go = (struct go7007 *)urb->context;
+	int r, status = urb->status;
+
+	if (!vb2_is_streaming(&go->vidq))
+		return;
+	if (status) {
+		dev_err(go->dev, "error in audio pipe: %d\n",
+			status);
+		return;
+	}
+	if (urb->actual_length != urb->transfer_buffer_length) {
+		dev_err(go->dev, "short read in audio pipe!\n");
+		return;
+	}
+	if (go->audio_deliver != NULL)
+		go->audio_deliver(go, urb->transfer_buffer, urb->actual_length);
+	r = usb_submit_urb(urb, GFP_ATOMIC);
+	if (r < 0)
+		dev_err(go->dev, "error in audio pipe: %d\n", r);
+}
+
+static int go7007_usb_stream_start(struct go7007 *go)
+{
+	struct go7007_usb *usb = go->hpi_context;
+	int i, r;
+
+	for (i = 0; i < 8; ++i) {
+		r = usb_submit_urb(usb->video_urbs[i], GFP_KERNEL);
+		if (r < 0) {
+			dev_err(go->dev, "error submitting video urb %d: %d\n", i, r);
+			goto video_submit_failed;
+		}
+	}
+	if (!go->audio_enabled)
+		return 0;
+
+	for (i = 0; i < 8; ++i) {
+		r = usb_submit_urb(usb->audio_urbs[i], GFP_KERNEL);
+		if (r < 0) {
+			dev_err(go->dev, "error submitting audio urb %d: %d\n", i, r);
+			goto audio_submit_failed;
+		}
+	}
+	return 0;
+
+audio_submit_failed:
+	for (i = 0; i < 7; ++i)
+		usb_kill_urb(usb->audio_urbs[i]);
+video_submit_failed:
+	for (i = 0; i < 8; ++i)
+		usb_kill_urb(usb->video_urbs[i]);
+	return -1;
+}
+
+static int go7007_usb_stream_stop(struct go7007 *go)
+{
+	struct go7007_usb *usb = go->hpi_context;
+	int i;
+
+	if (go->status == STATUS_SHUTDOWN)
+		return 0;
+	for (i = 0; i < 8; ++i)
+		usb_kill_urb(usb->video_urbs[i]);
+	if (go->audio_enabled)
+		for (i = 0; i < 8; ++i)
+			usb_kill_urb(usb->audio_urbs[i]);
+	return 0;
+}
+
+static int go7007_usb_send_firmware(struct go7007 *go, u8 *data, int len)
+{
+	struct go7007_usb *usb = go->hpi_context;
+	int transferred, pipe;
+	int timeout = 500;
+
+	pr_debug("DownloadBuffer sending %d bytes\n", len);
+
+	if (usb->board->flags & GO7007_USB_EZUSB)
+		pipe = usb_sndbulkpipe(usb->usbdev, 2);
+	else
+		pipe = usb_sndbulkpipe(usb->usbdev, 3);
+
+	return usb_bulk_msg(usb->usbdev, pipe, data, len,
+					&transferred, timeout);
+}
+
+static void go7007_usb_release(struct go7007 *go)
+{
+	struct go7007_usb *usb = go->hpi_context;
+	struct urb *vurb, *aurb;
+	int i;
+
+	if (usb->intr_urb) {
+		usb_kill_urb(usb->intr_urb);
+		kfree(usb->intr_urb->transfer_buffer);
+		usb_free_urb(usb->intr_urb);
+	}
+
+	/* Free USB-related structs */
+	for (i = 0; i < 8; ++i) {
+		vurb = usb->video_urbs[i];
+		if (vurb) {
+			usb_kill_urb(vurb);
+			kfree(vurb->transfer_buffer);
+			usb_free_urb(vurb);
+		}
+		aurb = usb->audio_urbs[i];
+		if (aurb) {
+			usb_kill_urb(aurb);
+			kfree(aurb->transfer_buffer);
+			usb_free_urb(aurb);
+		}
+	}
+
+	kfree(go->hpi_context);
+}
+
+static const struct go7007_hpi_ops go7007_usb_ezusb_hpi_ops = {
+	.interface_reset	= go7007_usb_interface_reset,
+	.write_interrupt	= go7007_usb_ezusb_write_interrupt,
+	.read_interrupt		= go7007_usb_read_interrupt,
+	.stream_start		= go7007_usb_stream_start,
+	.stream_stop		= go7007_usb_stream_stop,
+	.send_firmware		= go7007_usb_send_firmware,
+	.release		= go7007_usb_release,
+};
+
+static const struct go7007_hpi_ops go7007_usb_onboard_hpi_ops = {
+	.interface_reset	= go7007_usb_interface_reset,
+	.write_interrupt	= go7007_usb_onboard_write_interrupt,
+	.read_interrupt		= go7007_usb_read_interrupt,
+	.stream_start		= go7007_usb_stream_start,
+	.stream_stop		= go7007_usb_stream_stop,
+	.send_firmware		= go7007_usb_send_firmware,
+	.release		= go7007_usb_release,
+};
+
+/********************* Driver for EZ-USB I2C adapter *********************/
+
+static int go7007_usb_i2c_master_xfer(struct i2c_adapter *adapter,
+					struct i2c_msg msgs[], int num)
+{
+	struct go7007 *go = i2c_get_adapdata(adapter);
+	struct go7007_usb *usb = go->hpi_context;
+	u8 *buf = go->usb_buf;
+	int buf_len, i;
+	int ret = -EIO;
+
+	if (go->status == STATUS_SHUTDOWN)
+		return -ENODEV;
+
+	mutex_lock(&usb->i2c_lock);
+
+	for (i = 0; i < num; ++i) {
+		/* The hardware command is "write some bytes then read some
+		 * bytes", so we try to coalesce a write followed by a read
+		 * into a single USB transaction */
+		if (i + 1 < num && msgs[i].addr == msgs[i + 1].addr &&
+				!(msgs[i].flags & I2C_M_RD) &&
+				(msgs[i + 1].flags & I2C_M_RD)) {
+#ifdef GO7007_I2C_DEBUG
+			pr_debug("i2c write/read %d/%d bytes on %02x\n",
+				msgs[i].len, msgs[i + 1].len, msgs[i].addr);
+#endif
+			buf[0] = 0x01;
+			buf[1] = msgs[i].len + 1;
+			buf[2] = msgs[i].addr << 1;
+			memcpy(&buf[3], msgs[i].buf, msgs[i].len);
+			buf_len = msgs[i].len + 3;
+			buf[buf_len++] = msgs[++i].len;
+		} else if (msgs[i].flags & I2C_M_RD) {
+#ifdef GO7007_I2C_DEBUG
+			pr_debug("i2c read %d bytes on %02x\n",
+					msgs[i].len, msgs[i].addr);
+#endif
+			buf[0] = 0x01;
+			buf[1] = 1;
+			buf[2] = msgs[i].addr << 1;
+			buf[3] = msgs[i].len;
+			buf_len = 4;
+		} else {
+#ifdef GO7007_I2C_DEBUG
+			pr_debug("i2c write %d bytes on %02x\n",
+					msgs[i].len, msgs[i].addr);
+#endif
+			buf[0] = 0x00;
+			buf[1] = msgs[i].len + 1;
+			buf[2] = msgs[i].addr << 1;
+			memcpy(&buf[3], msgs[i].buf, msgs[i].len);
+			buf_len = msgs[i].len + 3;
+			buf[buf_len++] = 0;
+		}
+		if (go7007_usb_vendor_request(go, 0x24, 0, 0,
+						buf, buf_len, 0) < 0)
+			goto i2c_done;
+		if (msgs[i].flags & I2C_M_RD) {
+			memset(buf, 0, msgs[i].len + 1);
+			if (go7007_usb_vendor_request(go, 0x25, 0, 0, buf,
+						msgs[i].len + 1, 1) < 0)
+				goto i2c_done;
+			memcpy(msgs[i].buf, buf + 1, msgs[i].len);
+		}
+	}
+	ret = num;
+
+i2c_done:
+	mutex_unlock(&usb->i2c_lock);
+	return ret;
+}
+
+static u32 go7007_usb_functionality(struct i2c_adapter *adapter)
+{
+	/* No errors are reported by the hardware, so we don't bother
+	 * supporting quick writes to avoid confusing probing */
+	return (I2C_FUNC_SMBUS_EMUL) & ~I2C_FUNC_SMBUS_QUICK;
+}
+
+static const struct i2c_algorithm go7007_usb_algo = {
+	.master_xfer	= go7007_usb_i2c_master_xfer,
+	.functionality	= go7007_usb_functionality,
+};
+
+static struct i2c_adapter go7007_usb_adap_templ = {
+	.owner			= THIS_MODULE,
+	.name			= "WIS GO7007SB EZ-USB",
+	.algo			= &go7007_usb_algo,
+};
+
+/********************* USB add/remove functions *********************/
+
+static int go7007_usb_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	struct go7007 *go;
+	struct go7007_usb *usb;
+	const struct go7007_usb_board *board;
+	struct usb_device *usbdev = interface_to_usbdev(intf);
+	unsigned num_i2c_devs;
+	char *name;
+	int video_pipe, i, v_urb_len;
+
+	pr_debug("probing new GO7007 USB board\n");
+
+	switch (id->driver_info) {
+	case GO7007_BOARDID_MATRIX_II:
+		name = "WIS Matrix II or compatible";
+		board = &board_matrix_ii;
+		break;
+	case GO7007_BOARDID_MATRIX_RELOAD:
+		name = "WIS Matrix Reloaded or compatible";
+		board = &board_matrix_reload;
+		break;
+	case GO7007_BOARDID_MATRIX_REV:
+		name = "WIS Matrix Revolution or compatible";
+		board = &board_matrix_revolution;
+		break;
+	case GO7007_BOARDID_STAR_TREK:
+		name = "WIS Star Trek or compatible";
+		board = &board_star_trek;
+		break;
+	case GO7007_BOARDID_XMEN:
+		name = "WIS XMen or compatible";
+		board = &board_xmen;
+		break;
+	case GO7007_BOARDID_XMEN_II:
+		name = "WIS XMen II or compatible";
+		board = &board_xmen;
+		break;
+	case GO7007_BOARDID_XMEN_III:
+		name = "WIS XMen III or compatible";
+		board = &board_xmen;
+		break;
+	case GO7007_BOARDID_PX_M402U:
+		name = "Plextor PX-M402U";
+		board = &board_matrix_ii;
+		break;
+	case GO7007_BOARDID_PX_TV402U:
+		name = "Plextor PX-TV402U (unknown tuner)";
+		board = &board_px_tv402u;
+		break;
+	case GO7007_BOARDID_LIFEVIEW_LR192:
+		dev_err(&intf->dev, "The Lifeview TV Walker Ultra is not supported. Sorry!\n");
+		return -ENODEV;
+#if 0
+		name = "Lifeview TV Walker Ultra";
+		board = &board_lifeview_lr192;
+#endif
+		break;
+	case GO7007_BOARDID_SENSORAY_2250:
+		dev_info(&intf->dev, "Sensoray 2250 found\n");
+		name = "Sensoray 2250/2251";
+		board = &board_sensoray_2250;
+		break;
+	case GO7007_BOARDID_ADS_USBAV_709:
+		name = "ADS Tech DVD Xpress DX2";
+		board = &board_ads_usbav_709;
+		break;
+	default:
+		dev_err(&intf->dev, "unknown board ID %d!\n",
+				(unsigned int)id->driver_info);
+		return -ENODEV;
+	}
+
+	go = go7007_alloc(&board->main_info, &intf->dev);
+	if (go == NULL)
+		return -ENOMEM;
+
+	usb = kzalloc(sizeof(struct go7007_usb), GFP_KERNEL);
+	if (usb == NULL) {
+		kfree(go);
+		return -ENOMEM;
+	}
+
+	usb->board = board;
+	usb->usbdev = usbdev;
+	usb_make_path(usbdev, go->bus_info, sizeof(go->bus_info));
+	go->board_id = id->driver_info;
+	strncpy(go->name, name, sizeof(go->name));
+	if (board->flags & GO7007_USB_EZUSB)
+		go->hpi_ops = &go7007_usb_ezusb_hpi_ops;
+	else
+		go->hpi_ops = &go7007_usb_onboard_hpi_ops;
+	go->hpi_context = usb;
+
+	/* Allocate the URB and buffer for receiving incoming interrupts */
+	usb->intr_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (usb->intr_urb == NULL)
+		goto allocfail;
+	usb->intr_urb->transfer_buffer = kmalloc_array(2, sizeof(u16),
+						       GFP_KERNEL);
+	if (usb->intr_urb->transfer_buffer == NULL)
+		goto allocfail;
+
+	if (go->board_id == GO7007_BOARDID_SENSORAY_2250)
+		usb_fill_bulk_urb(usb->intr_urb, usb->usbdev,
+			usb_rcvbulkpipe(usb->usbdev, 4),
+			usb->intr_urb->transfer_buffer, 2*sizeof(u16),
+			go7007_usb_readinterrupt_complete, go);
+	else
+		usb_fill_int_urb(usb->intr_urb, usb->usbdev,
+			usb_rcvintpipe(usb->usbdev, 4),
+			usb->intr_urb->transfer_buffer, 2*sizeof(u16),
+			go7007_usb_readinterrupt_complete, go, 8);
+	usb_set_intfdata(intf, &go->v4l2_dev);
+
+	/* Boot the GO7007 */
+	if (go7007_boot_encoder(go, go->board_info->flags &
+					GO7007_BOARD_USE_ONBOARD_I2C) < 0)
+		goto allocfail;
+
+	/* Register the EZ-USB I2C adapter, if we're using it */
+	if (board->flags & GO7007_USB_EZUSB_I2C) {
+		memcpy(&go->i2c_adapter, &go7007_usb_adap_templ,
+				sizeof(go7007_usb_adap_templ));
+		mutex_init(&usb->i2c_lock);
+		go->i2c_adapter.dev.parent = go->dev;
+		i2c_set_adapdata(&go->i2c_adapter, go);
+		if (i2c_add_adapter(&go->i2c_adapter) < 0) {
+			dev_err(go->dev, "error: i2c_add_adapter failed\n");
+			goto allocfail;
+		}
+		go->i2c_adapter_online = 1;
+	}
+
+	/* Pelco and Adlink reused the XMen and XMen-III vendor and product
+	 * IDs for their own incompatible designs.  We can detect XMen boards
+	 * by probing the sensor, but there is no way to probe the sensors on
+	 * the Pelco and Adlink designs so we default to the Adlink.  If it
+	 * is actually a Pelco, the user must set the assume_endura module
+	 * parameter. */
+	if ((go->board_id == GO7007_BOARDID_XMEN ||
+				go->board_id == GO7007_BOARDID_XMEN_III) &&
+			go->i2c_adapter_online) {
+		union i2c_smbus_data data;
+
+		/* Check to see if register 0x0A is 0x76 */
+		i2c_smbus_xfer(&go->i2c_adapter, 0x21, I2C_CLIENT_SCCB,
+			I2C_SMBUS_READ, 0x0A, I2C_SMBUS_BYTE_DATA, &data);
+		if (data.byte != 0x76) {
+			if (assume_endura) {
+				go->board_id = GO7007_BOARDID_ENDURA;
+				usb->board = board = &board_endura;
+				go->board_info = &board->main_info;
+				strncpy(go->name, "Pelco Endura",
+					sizeof(go->name));
+			} else {
+				u16 channel;
+
+				/* read channel number from GPIO[1:0] */
+				go7007_read_addr(go, 0x3c81, &channel);
+				channel &= 0x3;
+				go->board_id = GO7007_BOARDID_ADLINK_MPG24;
+				usb->board = board = &board_adlink_mpg24;
+				go->board_info = &board->main_info;
+				go->channel_number = channel;
+				snprintf(go->name, sizeof(go->name),
+					"Adlink PCI-MPG24, channel #%d",
+					channel);
+			}
+			go7007_update_board(go);
+		}
+	}
+
+	num_i2c_devs = go->board_info->num_i2c_devs;
+
+	/* Probe the tuner model on the TV402U */
+	if (go->board_id == GO7007_BOARDID_PX_TV402U) {
+		/* Board strapping indicates tuner model */
+		if (go7007_usb_vendor_request(go, 0x41, 0, 0, go->usb_buf, 3,
+					1) < 0) {
+			dev_err(go->dev, "GPIO read failed!\n");
+			goto allocfail;
+		}
+		switch (go->usb_buf[0] >> 6) {
+		case 1:
+			go->tuner_type = TUNER_SONY_BTF_PG472Z;
+			go->std = V4L2_STD_PAL;
+			strncpy(go->name, "Plextor PX-TV402U-EU",
+					sizeof(go->name));
+			break;
+		case 2:
+			go->tuner_type = TUNER_SONY_BTF_PK467Z;
+			go->std = V4L2_STD_NTSC_M_JP;
+			num_i2c_devs -= 2;
+			strncpy(go->name, "Plextor PX-TV402U-JP",
+					sizeof(go->name));
+			break;
+		case 3:
+			go->tuner_type = TUNER_SONY_BTF_PB463Z;
+			num_i2c_devs -= 2;
+			strncpy(go->name, "Plextor PX-TV402U-NA",
+					sizeof(go->name));
+			break;
+		default:
+			pr_debug("unable to detect tuner type!\n");
+			break;
+		}
+		/* Configure tuner mode selection inputs connected
+		 * to the EZ-USB GPIO output pins */
+		if (go7007_usb_vendor_request(go, 0x40, 0x7f02, 0,
+					NULL, 0, 0) < 0) {
+			dev_err(go->dev, "GPIO write failed!\n");
+			goto allocfail;
+		}
+	}
+
+	/* Print a nasty message if the user attempts to use a USB2.0 device in
+	 * a USB1.1 port.  There will be silent corruption of the stream. */
+	if ((board->flags & GO7007_USB_EZUSB) &&
+			usbdev->speed != USB_SPEED_HIGH)
+		dev_err(go->dev, "*** WARNING ***  This device must be connected to a USB 2.0 port! Attempting to capture video through a USB 1.1 port will result in stream corruption, even at low bitrates!\n");
+
+	/* Allocate the URBs and buffers for receiving the video stream */
+	if (board->flags & GO7007_USB_EZUSB) {
+		v_urb_len = 1024;
+		video_pipe = usb_rcvbulkpipe(usb->usbdev, 6);
+	} else {
+		v_urb_len = 512;
+		video_pipe = usb_rcvbulkpipe(usb->usbdev, 1);
+	}
+	for (i = 0; i < 8; ++i) {
+		usb->video_urbs[i] = usb_alloc_urb(0, GFP_KERNEL);
+		if (usb->video_urbs[i] == NULL)
+			goto allocfail;
+		usb->video_urbs[i]->transfer_buffer =
+						kmalloc(v_urb_len, GFP_KERNEL);
+		if (usb->video_urbs[i]->transfer_buffer == NULL)
+			goto allocfail;
+		usb_fill_bulk_urb(usb->video_urbs[i], usb->usbdev, video_pipe,
+				usb->video_urbs[i]->transfer_buffer, v_urb_len,
+				go7007_usb_read_video_pipe_complete, go);
+	}
+
+	/* Allocate the URBs and buffers for receiving the audio stream */
+	if ((board->flags & GO7007_USB_EZUSB) &&
+	    (board->main_info.flags & GO7007_BOARD_HAS_AUDIO)) {
+		for (i = 0; i < 8; ++i) {
+			usb->audio_urbs[i] = usb_alloc_urb(0, GFP_KERNEL);
+			if (usb->audio_urbs[i] == NULL)
+				goto allocfail;
+			usb->audio_urbs[i]->transfer_buffer = kmalloc(4096,
+								GFP_KERNEL);
+			if (usb->audio_urbs[i]->transfer_buffer == NULL)
+				goto allocfail;
+			usb_fill_bulk_urb(usb->audio_urbs[i], usb->usbdev,
+				usb_rcvbulkpipe(usb->usbdev, 8),
+				usb->audio_urbs[i]->transfer_buffer, 4096,
+				go7007_usb_read_audio_pipe_complete, go);
+		}
+	}
+
+	/* Do any final GO7007 initialization, then register the
+	 * V4L2 and ALSA interfaces */
+	if (go7007_register_encoder(go, num_i2c_devs) < 0)
+		goto allocfail;
+
+	go->status = STATUS_ONLINE;
+	return 0;
+
+allocfail:
+	go7007_usb_release(go);
+	kfree(go);
+	return -ENOMEM;
+}
+
+static void go7007_usb_disconnect(struct usb_interface *intf)
+{
+	struct go7007 *go = to_go7007(usb_get_intfdata(intf));
+
+	mutex_lock(&go->queue_lock);
+	mutex_lock(&go->serialize_lock);
+
+	if (go->audio_enabled)
+		go7007_snd_remove(go);
+
+	go->status = STATUS_SHUTDOWN;
+	v4l2_device_disconnect(&go->v4l2_dev);
+	video_unregister_device(&go->vdev);
+	mutex_unlock(&go->serialize_lock);
+	mutex_unlock(&go->queue_lock);
+
+	v4l2_device_put(&go->v4l2_dev);
+}
+
+static struct usb_driver go7007_usb_driver = {
+	.name		= "go7007",
+	.probe		= go7007_usb_probe,
+	.disconnect	= go7007_usb_disconnect,
+	.id_table	= go7007_usb_id_table,
+};
+
+module_usb_driver(go7007_usb_driver);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/usb/go7007/go7007-v4l2.c b/drivers/media/usb/go7007/go7007-v4l2.c
new file mode 100644
index 0000000..c55c82f
--- /dev/null
+++ b/drivers/media/usb/go7007/go7007-v4l2.c
@@ -0,0 +1,1175 @@
+/*
+ * Copyright (C) 2005-2006 Micronas USA Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (Version 2) as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/spinlock.h>
+#include <linux/slab.h>
+#include <linux/fs.h>
+#include <linux/unistd.h>
+#include <linux/time.h>
+#include <linux/vmalloc.h>
+#include <linux/pagemap.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/uaccess.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-subdev.h>
+#include <media/v4l2-event.h>
+#include <media/videobuf2-vmalloc.h>
+#include <media/i2c/saa7115.h>
+
+#include "go7007-priv.h"
+
+#define call_all(dev, o, f, args...) \
+	v4l2_device_call_until_err(dev, 0, o, f, ##args)
+
+static bool valid_pixelformat(u32 pixelformat)
+{
+	switch (pixelformat) {
+	case V4L2_PIX_FMT_MJPEG:
+	case V4L2_PIX_FMT_MPEG1:
+	case V4L2_PIX_FMT_MPEG2:
+	case V4L2_PIX_FMT_MPEG4:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static u32 get_frame_type_flag(struct go7007_buffer *vb, int format)
+{
+	u8 *ptr = vb2_plane_vaddr(&vb->vb.vb2_buf, 0);
+
+	switch (format) {
+	case V4L2_PIX_FMT_MJPEG:
+		return V4L2_BUF_FLAG_KEYFRAME;
+	case V4L2_PIX_FMT_MPEG4:
+		switch ((ptr[vb->frame_offset + 4] >> 6) & 0x3) {
+		case 0:
+			return V4L2_BUF_FLAG_KEYFRAME;
+		case 1:
+			return V4L2_BUF_FLAG_PFRAME;
+		case 2:
+			return V4L2_BUF_FLAG_BFRAME;
+		default:
+			return 0;
+		}
+	case V4L2_PIX_FMT_MPEG1:
+	case V4L2_PIX_FMT_MPEG2:
+		switch ((ptr[vb->frame_offset + 5] >> 3) & 0x7) {
+		case 1:
+			return V4L2_BUF_FLAG_KEYFRAME;
+		case 2:
+			return V4L2_BUF_FLAG_PFRAME;
+		case 3:
+			return V4L2_BUF_FLAG_BFRAME;
+		default:
+			return 0;
+		}
+	}
+
+	return 0;
+}
+
+static void get_resolution(struct go7007 *go, int *width, int *height)
+{
+	switch (go->standard) {
+	case GO7007_STD_NTSC:
+		*width = 720;
+		*height = 480;
+		break;
+	case GO7007_STD_PAL:
+		*width = 720;
+		*height = 576;
+		break;
+	case GO7007_STD_OTHER:
+	default:
+		*width = go->board_info->sensor_width;
+		*height = go->board_info->sensor_height;
+		break;
+	}
+}
+
+static void set_formatting(struct go7007 *go)
+{
+	if (go->format == V4L2_PIX_FMT_MJPEG) {
+		go->pali = 0;
+		go->aspect_ratio = GO7007_RATIO_1_1;
+		go->gop_size = 0;
+		go->ipb = 0;
+		go->closed_gop = 0;
+		go->repeat_seqhead = 0;
+		go->seq_header_enable = 0;
+		go->gop_header_enable = 0;
+		go->dvd_mode = 0;
+		return;
+	}
+
+	switch (go->format) {
+	case V4L2_PIX_FMT_MPEG1:
+		go->pali = 0;
+		break;
+	default:
+	case V4L2_PIX_FMT_MPEG2:
+		go->pali = 0x48;
+		break;
+	case V4L2_PIX_FMT_MPEG4:
+		/* For future reference: this is the list of MPEG4
+		 * profiles that are available, although they are
+		 * untested:
+		 *
+		 * Profile		pali
+		 * --------------	----
+		 * PROFILE_S_L0		0x08
+		 * PROFILE_S_L1		0x01
+		 * PROFILE_S_L2		0x02
+		 * PROFILE_S_L3		0x03
+		 * PROFILE_ARTS_L1	0x91
+		 * PROFILE_ARTS_L2	0x92
+		 * PROFILE_ARTS_L3	0x93
+		 * PROFILE_ARTS_L4	0x94
+		 * PROFILE_AS_L0	0xf0
+		 * PROFILE_AS_L1	0xf1
+		 * PROFILE_AS_L2	0xf2
+		 * PROFILE_AS_L3	0xf3
+		 * PROFILE_AS_L4	0xf4
+		 * PROFILE_AS_L5	0xf5
+		 */
+		go->pali = 0xf5;
+		break;
+	}
+	go->gop_size = v4l2_ctrl_g_ctrl(go->mpeg_video_gop_size);
+	go->closed_gop = v4l2_ctrl_g_ctrl(go->mpeg_video_gop_closure);
+	go->ipb = v4l2_ctrl_g_ctrl(go->mpeg_video_b_frames) != 0;
+	go->bitrate = v4l2_ctrl_g_ctrl(go->mpeg_video_bitrate);
+	go->repeat_seqhead = v4l2_ctrl_g_ctrl(go->mpeg_video_rep_seqheader);
+	go->gop_header_enable = 1;
+	go->dvd_mode = 0;
+	if (go->format == V4L2_PIX_FMT_MPEG2)
+		go->dvd_mode =
+			go->bitrate == 9800000 &&
+			go->gop_size == 15 &&
+			go->ipb == 0 &&
+			go->repeat_seqhead == 1 &&
+			go->closed_gop;
+
+	switch (v4l2_ctrl_g_ctrl(go->mpeg_video_aspect_ratio)) {
+	default:
+	case V4L2_MPEG_VIDEO_ASPECT_1x1:
+		go->aspect_ratio = GO7007_RATIO_1_1;
+		break;
+	case V4L2_MPEG_VIDEO_ASPECT_4x3:
+		go->aspect_ratio = GO7007_RATIO_4_3;
+		break;
+	case V4L2_MPEG_VIDEO_ASPECT_16x9:
+		go->aspect_ratio = GO7007_RATIO_16_9;
+		break;
+	}
+}
+
+static int set_capture_size(struct go7007 *go, struct v4l2_format *fmt, int try)
+{
+	int sensor_height = 0, sensor_width = 0;
+	int width, height;
+
+	if (fmt != NULL && !valid_pixelformat(fmt->fmt.pix.pixelformat))
+		return -EINVAL;
+
+	get_resolution(go, &sensor_width, &sensor_height);
+
+	if (fmt == NULL) {
+		width = sensor_width;
+		height = sensor_height;
+	} else if (go->board_info->sensor_flags & GO7007_SENSOR_SCALING) {
+		if (fmt->fmt.pix.width > sensor_width)
+			width = sensor_width;
+		else if (fmt->fmt.pix.width < 144)
+			width = 144;
+		else
+			width = fmt->fmt.pix.width & ~0x0f;
+
+		if (fmt->fmt.pix.height > sensor_height)
+			height = sensor_height;
+		else if (fmt->fmt.pix.height < 96)
+			height = 96;
+		else
+			height = fmt->fmt.pix.height & ~0x0f;
+	} else {
+		width = fmt->fmt.pix.width;
+
+		if (width <= sensor_width / 4) {
+			width = sensor_width / 4;
+			height = sensor_height / 4;
+		} else if (width <= sensor_width / 2) {
+			width = sensor_width / 2;
+			height = sensor_height / 2;
+		} else {
+			width = sensor_width;
+			height = sensor_height;
+		}
+		width &= ~0xf;
+		height &= ~0xf;
+	}
+
+	if (fmt != NULL) {
+		u32 pixelformat = fmt->fmt.pix.pixelformat;
+
+		memset(fmt, 0, sizeof(*fmt));
+		fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+		fmt->fmt.pix.width = width;
+		fmt->fmt.pix.height = height;
+		fmt->fmt.pix.pixelformat = pixelformat;
+		fmt->fmt.pix.field = V4L2_FIELD_NONE;
+		fmt->fmt.pix.bytesperline = 0;
+		fmt->fmt.pix.sizeimage = GO7007_BUF_SIZE;
+		fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+	}
+
+	if (try)
+		return 0;
+
+	if (fmt)
+		go->format = fmt->fmt.pix.pixelformat;
+	go->width = width;
+	go->height = height;
+	go->encoder_h_offset = go->board_info->sensor_h_offset;
+	go->encoder_v_offset = go->board_info->sensor_v_offset;
+
+	if (go->board_info->sensor_flags & GO7007_SENSOR_SCALING) {
+		struct v4l2_subdev_format format = {
+			.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+		};
+
+		format.format.code = MEDIA_BUS_FMT_FIXED;
+		format.format.width = fmt ? fmt->fmt.pix.width : width;
+		format.format.height = height;
+		go->encoder_h_halve = 0;
+		go->encoder_v_halve = 0;
+		go->encoder_subsample = 0;
+		call_all(&go->v4l2_dev, pad, set_fmt, NULL, &format);
+	} else {
+		if (width <= sensor_width / 4) {
+			go->encoder_h_halve = 1;
+			go->encoder_v_halve = 1;
+			go->encoder_subsample = 1;
+		} else if (width <= sensor_width / 2) {
+			go->encoder_h_halve = 1;
+			go->encoder_v_halve = 1;
+			go->encoder_subsample = 0;
+		} else {
+			go->encoder_h_halve = 0;
+			go->encoder_v_halve = 0;
+			go->encoder_subsample = 0;
+		}
+	}
+	return 0;
+}
+
+static int vidioc_querycap(struct file *file, void  *priv,
+					struct v4l2_capability *cap)
+{
+	struct go7007 *go = video_drvdata(file);
+
+	strlcpy(cap->driver, "go7007", sizeof(cap->driver));
+	strlcpy(cap->card, go->name, sizeof(cap->card));
+	strlcpy(cap->bus_info, go->bus_info, sizeof(cap->bus_info));
+
+	cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE |
+				V4L2_CAP_STREAMING;
+
+	if (go->board_info->num_aud_inputs)
+		cap->device_caps |= V4L2_CAP_AUDIO;
+	if (go->board_info->flags & GO7007_BOARD_HAS_TUNER)
+		cap->device_caps |= V4L2_CAP_TUNER;
+	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+	return 0;
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
+					struct v4l2_fmtdesc *fmt)
+{
+	char *desc = NULL;
+
+	switch (fmt->index) {
+	case 0:
+		fmt->pixelformat = V4L2_PIX_FMT_MJPEG;
+		desc = "Motion JPEG";
+		break;
+	case 1:
+		fmt->pixelformat = V4L2_PIX_FMT_MPEG1;
+		desc = "MPEG-1 ES";
+		break;
+	case 2:
+		fmt->pixelformat = V4L2_PIX_FMT_MPEG2;
+		desc = "MPEG-2 ES";
+		break;
+	case 3:
+		fmt->pixelformat = V4L2_PIX_FMT_MPEG4;
+		desc = "MPEG-4 ES";
+		break;
+	default:
+		return -EINVAL;
+	}
+	fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	fmt->flags = V4L2_FMT_FLAG_COMPRESSED;
+
+	strncpy(fmt->description, desc, sizeof(fmt->description));
+
+	return 0;
+}
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+					struct v4l2_format *fmt)
+{
+	struct go7007 *go = video_drvdata(file);
+
+	fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	fmt->fmt.pix.width = go->width;
+	fmt->fmt.pix.height = go->height;
+	fmt->fmt.pix.pixelformat = go->format;
+	fmt->fmt.pix.field = V4L2_FIELD_NONE;
+	fmt->fmt.pix.bytesperline = 0;
+	fmt->fmt.pix.sizeimage = GO7007_BUF_SIZE;
+	fmt->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+
+	return 0;
+}
+
+static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
+			struct v4l2_format *fmt)
+{
+	struct go7007 *go = video_drvdata(file);
+
+	return set_capture_size(go, fmt, 1);
+}
+
+static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+			struct v4l2_format *fmt)
+{
+	struct go7007 *go = video_drvdata(file);
+
+	if (vb2_is_busy(&go->vidq))
+		return -EBUSY;
+
+	return set_capture_size(go, fmt, 0);
+}
+
+static int go7007_queue_setup(struct vb2_queue *q,
+		unsigned int *num_buffers, unsigned int *num_planes,
+		unsigned int sizes[], struct device *alloc_devs[])
+{
+	sizes[0] = GO7007_BUF_SIZE;
+	*num_planes = 1;
+
+	if (*num_buffers < 2)
+		*num_buffers = 2;
+
+	return 0;
+}
+
+static void go7007_buf_queue(struct vb2_buffer *vb)
+{
+	struct vb2_queue *vq = vb->vb2_queue;
+	struct go7007 *go = vb2_get_drv_priv(vq);
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct go7007_buffer *go7007_vb =
+		container_of(vbuf, struct go7007_buffer, vb);
+	unsigned long flags;
+
+	spin_lock_irqsave(&go->spinlock, flags);
+	list_add_tail(&go7007_vb->list, &go->vidq_active);
+	spin_unlock_irqrestore(&go->spinlock, flags);
+}
+
+static int go7007_buf_prepare(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct go7007_buffer *go7007_vb =
+		container_of(vbuf, struct go7007_buffer, vb);
+
+	go7007_vb->modet_active = 0;
+	go7007_vb->frame_offset = 0;
+	vb->planes[0].bytesused = 0;
+	return 0;
+}
+
+static void go7007_buf_finish(struct vb2_buffer *vb)
+{
+	struct vb2_queue *vq = vb->vb2_queue;
+	struct go7007 *go = vb2_get_drv_priv(vq);
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct go7007_buffer *go7007_vb =
+		container_of(vbuf, struct go7007_buffer, vb);
+	u32 frame_type_flag = get_frame_type_flag(go7007_vb, go->format);
+
+	vbuf->flags &= ~(V4L2_BUF_FLAG_KEYFRAME | V4L2_BUF_FLAG_BFRAME |
+			V4L2_BUF_FLAG_PFRAME);
+	vbuf->flags |= frame_type_flag;
+	vbuf->field = V4L2_FIELD_NONE;
+}
+
+static int go7007_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+	struct go7007 *go = vb2_get_drv_priv(q);
+	int ret;
+
+	set_formatting(go);
+	mutex_lock(&go->hw_lock);
+	go->next_seq = 0;
+	go->active_buf = NULL;
+	go->modet_event_status = 0;
+	q->streaming = 1;
+	if (go7007_start_encoder(go) < 0)
+		ret = -EIO;
+	else
+		ret = 0;
+	mutex_unlock(&go->hw_lock);
+	if (ret) {
+		q->streaming = 0;
+		return ret;
+	}
+	call_all(&go->v4l2_dev, video, s_stream, 1);
+	v4l2_ctrl_grab(go->mpeg_video_gop_size, true);
+	v4l2_ctrl_grab(go->mpeg_video_gop_closure, true);
+	v4l2_ctrl_grab(go->mpeg_video_bitrate, true);
+	v4l2_ctrl_grab(go->mpeg_video_aspect_ratio, true);
+	/* Turn on Capture LED */
+	if (go->board_id == GO7007_BOARDID_ADS_USBAV_709)
+		go7007_write_addr(go, 0x3c82, 0x0005);
+	return ret;
+}
+
+static void go7007_stop_streaming(struct vb2_queue *q)
+{
+	struct go7007 *go = vb2_get_drv_priv(q);
+	unsigned long flags;
+
+	q->streaming = 0;
+	go7007_stream_stop(go);
+	mutex_lock(&go->hw_lock);
+	go7007_reset_encoder(go);
+	mutex_unlock(&go->hw_lock);
+	call_all(&go->v4l2_dev, video, s_stream, 0);
+
+	spin_lock_irqsave(&go->spinlock, flags);
+	INIT_LIST_HEAD(&go->vidq_active);
+	spin_unlock_irqrestore(&go->spinlock, flags);
+	v4l2_ctrl_grab(go->mpeg_video_gop_size, false);
+	v4l2_ctrl_grab(go->mpeg_video_gop_closure, false);
+	v4l2_ctrl_grab(go->mpeg_video_bitrate, false);
+	v4l2_ctrl_grab(go->mpeg_video_aspect_ratio, false);
+	/* Turn on Capture LED */
+	if (go->board_id == GO7007_BOARDID_ADS_USBAV_709)
+		go7007_write_addr(go, 0x3c82, 0x000d);
+}
+
+static const struct vb2_ops go7007_video_qops = {
+	.queue_setup    = go7007_queue_setup,
+	.buf_queue      = go7007_buf_queue,
+	.buf_prepare    = go7007_buf_prepare,
+	.buf_finish     = go7007_buf_finish,
+	.start_streaming = go7007_start_streaming,
+	.stop_streaming = go7007_stop_streaming,
+	.wait_prepare   = vb2_ops_wait_prepare,
+	.wait_finish    = vb2_ops_wait_finish,
+};
+
+static int vidioc_g_parm(struct file *filp, void *priv,
+		struct v4l2_streamparm *parm)
+{
+	struct go7007 *go = video_drvdata(filp);
+	struct v4l2_fract timeperframe = {
+		.numerator = 1001 *  go->fps_scale,
+		.denominator = go->sensor_framerate,
+	};
+
+	if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	parm->parm.capture.readbuffers = 2;
+	parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
+	parm->parm.capture.timeperframe = timeperframe;
+
+	return 0;
+}
+
+static int vidioc_s_parm(struct file *filp, void *priv,
+		struct v4l2_streamparm *parm)
+{
+	struct go7007 *go = video_drvdata(filp);
+	unsigned int n, d;
+
+	if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	n = go->sensor_framerate *
+		parm->parm.capture.timeperframe.numerator;
+	d = 1001 * parm->parm.capture.timeperframe.denominator;
+	if (n != 0 && d != 0 && n > d)
+		go->fps_scale = (n + d/2) / d;
+	else
+		go->fps_scale = 1;
+
+	return vidioc_g_parm(filp, priv, parm);
+}
+
+/* VIDIOC_ENUMSTD on go7007 were used for enumerating the supported fps and
+   its resolution, when the device is not connected to TV.
+   This is were an API abuse, probably used by the lack of specific IOCTL's to
+   enumerate it, by the time the driver was written.
+
+   However, since kernel 2.6.19, two new ioctls (VIDIOC_ENUM_FRAMEINTERVALS
+   and VIDIOC_ENUM_FRAMESIZES) were added for this purpose.
+
+   The two functions below implement the newer ioctls
+*/
+static int vidioc_enum_framesizes(struct file *filp, void *priv,
+				  struct v4l2_frmsizeenum *fsize)
+{
+	struct go7007 *go = video_drvdata(filp);
+	int width, height;
+
+	if (fsize->index > 2)
+		return -EINVAL;
+
+	if (!valid_pixelformat(fsize->pixel_format))
+		return -EINVAL;
+
+	get_resolution(go, &width, &height);
+	fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+	fsize->discrete.width = (width >> fsize->index) & ~0xf;
+	fsize->discrete.height = (height >> fsize->index) & ~0xf;
+	return 0;
+}
+
+static int vidioc_enum_frameintervals(struct file *filp, void *priv,
+				      struct v4l2_frmivalenum *fival)
+{
+	struct go7007 *go = video_drvdata(filp);
+	int width, height;
+	int i;
+
+	if (fival->index > 4)
+		return -EINVAL;
+
+	if (!valid_pixelformat(fival->pixel_format))
+		return -EINVAL;
+
+	if (!(go->board_info->sensor_flags & GO7007_SENSOR_SCALING)) {
+		get_resolution(go, &width, &height);
+		for (i = 0; i <= 2; i++)
+			if (fival->width == ((width >> i) & ~0xf) &&
+			    fival->height == ((height >> i) & ~0xf))
+				break;
+		if (i > 2)
+			return -EINVAL;
+	}
+	fival->type = V4L2_FRMIVAL_TYPE_DISCRETE;
+	fival->discrete.numerator = 1001 * (fival->index + 1);
+	fival->discrete.denominator = go->sensor_framerate;
+	return 0;
+}
+
+static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *std)
+{
+	struct go7007 *go = video_drvdata(file);
+
+	*std = go->std;
+	return 0;
+}
+
+static int go7007_s_std(struct go7007 *go)
+{
+	if (go->std & V4L2_STD_625_50) {
+		go->standard = GO7007_STD_PAL;
+		go->sensor_framerate = 25025;
+	} else {
+		go->standard = GO7007_STD_NTSC;
+		go->sensor_framerate = 30000;
+	}
+
+	call_all(&go->v4l2_dev, video, s_std, go->std);
+	set_capture_size(go, NULL, 0);
+	return 0;
+}
+
+static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id std)
+{
+	struct go7007 *go = video_drvdata(file);
+
+	if (vb2_is_busy(&go->vidq))
+		return -EBUSY;
+
+	go->std = std;
+
+	return go7007_s_std(go);
+}
+
+static int vidioc_querystd(struct file *file, void *priv, v4l2_std_id *std)
+{
+	struct go7007 *go = video_drvdata(file);
+
+	return call_all(&go->v4l2_dev, video, querystd, std);
+}
+
+static int vidioc_enum_input(struct file *file, void *priv,
+				struct v4l2_input *inp)
+{
+	struct go7007 *go = video_drvdata(file);
+
+	if (inp->index >= go->board_info->num_inputs)
+		return -EINVAL;
+
+	strlcpy(inp->name, go->board_info->inputs[inp->index].name,
+			sizeof(inp->name));
+
+	/* If this board has a tuner, it will be the first input */
+	if ((go->board_info->flags & GO7007_BOARD_HAS_TUNER) &&
+			inp->index == 0)
+		inp->type = V4L2_INPUT_TYPE_TUNER;
+	else
+		inp->type = V4L2_INPUT_TYPE_CAMERA;
+
+	if (go->board_info->num_aud_inputs)
+		inp->audioset = (1 << go->board_info->num_aud_inputs) - 1;
+	else
+		inp->audioset = 0;
+	inp->tuner = 0;
+	if (go->board_info->sensor_flags & GO7007_SENSOR_TV)
+		inp->std = video_devdata(file)->tvnorms;
+	else
+		inp->std = 0;
+
+	return 0;
+}
+
+
+static int vidioc_g_input(struct file *file, void *priv, unsigned int *input)
+{
+	struct go7007 *go = video_drvdata(file);
+
+	*input = go->input;
+
+	return 0;
+}
+
+static int vidioc_enumaudio(struct file *file, void *fh, struct v4l2_audio *a)
+{
+	struct go7007 *go = video_drvdata(file);
+
+	if (a->index >= go->board_info->num_aud_inputs)
+		return -EINVAL;
+	strlcpy(a->name, go->board_info->aud_inputs[a->index].name,
+		sizeof(a->name));
+	a->capability = V4L2_AUDCAP_STEREO;
+	return 0;
+}
+
+static int vidioc_g_audio(struct file *file, void *fh, struct v4l2_audio *a)
+{
+	struct go7007 *go = video_drvdata(file);
+
+	a->index = go->aud_input;
+	strlcpy(a->name, go->board_info->aud_inputs[go->aud_input].name,
+		sizeof(a->name));
+	a->capability = V4L2_AUDCAP_STEREO;
+	return 0;
+}
+
+static int vidioc_s_audio(struct file *file, void *fh,
+	const struct v4l2_audio *a)
+{
+	struct go7007 *go = video_drvdata(file);
+
+	if (a->index >= go->board_info->num_aud_inputs)
+		return -EINVAL;
+	go->aud_input = a->index;
+	v4l2_subdev_call(go->sd_audio, audio, s_routing,
+		go->board_info->aud_inputs[go->aud_input].audio_input, 0, 0);
+	return 0;
+}
+
+static void go7007_s_input(struct go7007 *go)
+{
+	unsigned int input = go->input;
+
+	v4l2_subdev_call(go->sd_video, video, s_routing,
+			go->board_info->inputs[input].video_input, 0,
+			go->board_info->video_config);
+	if (go->board_info->num_aud_inputs) {
+		int aud_input = go->board_info->inputs[input].audio_index;
+
+		v4l2_subdev_call(go->sd_audio, audio, s_routing,
+			go->board_info->aud_inputs[aud_input].audio_input, 0, 0);
+		go->aud_input = aud_input;
+	}
+}
+
+static int vidioc_s_input(struct file *file, void *priv, unsigned int input)
+{
+	struct go7007 *go = video_drvdata(file);
+
+	if (input >= go->board_info->num_inputs)
+		return -EINVAL;
+	if (vb2_is_busy(&go->vidq))
+		return -EBUSY;
+
+	go->input = input;
+	go7007_s_input(go);
+
+	return 0;
+}
+
+static int vidioc_g_tuner(struct file *file, void *priv,
+				struct v4l2_tuner *t)
+{
+	struct go7007 *go = video_drvdata(file);
+
+	if (t->index != 0)
+		return -EINVAL;
+
+	strlcpy(t->name, "Tuner", sizeof(t->name));
+	return call_all(&go->v4l2_dev, tuner, g_tuner, t);
+}
+
+static int vidioc_s_tuner(struct file *file, void *priv,
+				const struct v4l2_tuner *t)
+{
+	struct go7007 *go = video_drvdata(file);
+
+	if (t->index != 0)
+		return -EINVAL;
+
+	return call_all(&go->v4l2_dev, tuner, s_tuner, t);
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+				struct v4l2_frequency *f)
+{
+	struct go7007 *go = video_drvdata(file);
+
+	if (f->tuner)
+		return -EINVAL;
+
+	return call_all(&go->v4l2_dev, tuner, g_frequency, f);
+}
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+				const struct v4l2_frequency *f)
+{
+	struct go7007 *go = video_drvdata(file);
+
+	if (f->tuner)
+		return -EINVAL;
+
+	return call_all(&go->v4l2_dev, tuner, s_frequency, f);
+}
+
+static int vidioc_log_status(struct file *file, void *priv)
+{
+	struct go7007 *go = video_drvdata(file);
+
+	v4l2_ctrl_log_status(file, priv);
+	return call_all(&go->v4l2_dev, core, log_status);
+}
+
+static int vidioc_subscribe_event(struct v4l2_fh *fh,
+				const struct v4l2_event_subscription *sub)
+{
+
+	switch (sub->type) {
+	case V4L2_EVENT_MOTION_DET:
+		/* Allow for up to 30 events (1 second for NTSC) to be
+		 * stored. */
+		return v4l2_event_subscribe(fh, sub, 30, NULL);
+	default:
+		return v4l2_ctrl_subscribe_event(fh, sub);
+	}
+}
+
+
+static int go7007_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct go7007 *go =
+		container_of(ctrl->handler, struct go7007, hdl);
+	unsigned y;
+	u8 *mt;
+
+	switch (ctrl->id) {
+	case V4L2_CID_PIXEL_THRESHOLD0:
+		go->modet[0].pixel_threshold = ctrl->val;
+		break;
+	case V4L2_CID_MOTION_THRESHOLD0:
+		go->modet[0].motion_threshold = ctrl->val;
+		break;
+	case V4L2_CID_MB_THRESHOLD0:
+		go->modet[0].mb_threshold = ctrl->val;
+		break;
+	case V4L2_CID_PIXEL_THRESHOLD1:
+		go->modet[1].pixel_threshold = ctrl->val;
+		break;
+	case V4L2_CID_MOTION_THRESHOLD1:
+		go->modet[1].motion_threshold = ctrl->val;
+		break;
+	case V4L2_CID_MB_THRESHOLD1:
+		go->modet[1].mb_threshold = ctrl->val;
+		break;
+	case V4L2_CID_PIXEL_THRESHOLD2:
+		go->modet[2].pixel_threshold = ctrl->val;
+		break;
+	case V4L2_CID_MOTION_THRESHOLD2:
+		go->modet[2].motion_threshold = ctrl->val;
+		break;
+	case V4L2_CID_MB_THRESHOLD2:
+		go->modet[2].mb_threshold = ctrl->val;
+		break;
+	case V4L2_CID_PIXEL_THRESHOLD3:
+		go->modet[3].pixel_threshold = ctrl->val;
+		break;
+	case V4L2_CID_MOTION_THRESHOLD3:
+		go->modet[3].motion_threshold = ctrl->val;
+		break;
+	case V4L2_CID_MB_THRESHOLD3:
+		go->modet[3].mb_threshold = ctrl->val;
+		break;
+	case V4L2_CID_DETECT_MD_REGION_GRID:
+		mt = go->modet_map;
+		for (y = 0; y < go->height / 16; y++, mt += go->width / 16)
+			memcpy(mt, ctrl->p_new.p_u8 + y * (720 / 16), go->width / 16);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static const struct v4l2_file_operations go7007_fops = {
+	.owner		= THIS_MODULE,
+	.open		= v4l2_fh_open,
+	.release	= vb2_fop_release,
+	.unlocked_ioctl	= video_ioctl2,
+	.read		= vb2_fop_read,
+	.mmap		= vb2_fop_mmap,
+	.poll		= vb2_fop_poll,
+};
+
+static const struct v4l2_ioctl_ops video_ioctl_ops = {
+	.vidioc_querycap          = vidioc_querycap,
+	.vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap     = vidioc_g_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap   = vidioc_try_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap     = vidioc_s_fmt_vid_cap,
+	.vidioc_reqbufs           = vb2_ioctl_reqbufs,
+	.vidioc_querybuf          = vb2_ioctl_querybuf,
+	.vidioc_qbuf              = vb2_ioctl_qbuf,
+	.vidioc_dqbuf             = vb2_ioctl_dqbuf,
+	.vidioc_g_std             = vidioc_g_std,
+	.vidioc_s_std             = vidioc_s_std,
+	.vidioc_querystd          = vidioc_querystd,
+	.vidioc_enum_input        = vidioc_enum_input,
+	.vidioc_g_input           = vidioc_g_input,
+	.vidioc_s_input           = vidioc_s_input,
+	.vidioc_enumaudio         = vidioc_enumaudio,
+	.vidioc_g_audio           = vidioc_g_audio,
+	.vidioc_s_audio           = vidioc_s_audio,
+	.vidioc_streamon          = vb2_ioctl_streamon,
+	.vidioc_streamoff         = vb2_ioctl_streamoff,
+	.vidioc_g_tuner           = vidioc_g_tuner,
+	.vidioc_s_tuner           = vidioc_s_tuner,
+	.vidioc_g_frequency       = vidioc_g_frequency,
+	.vidioc_s_frequency       = vidioc_s_frequency,
+	.vidioc_g_parm            = vidioc_g_parm,
+	.vidioc_s_parm            = vidioc_s_parm,
+	.vidioc_enum_framesizes   = vidioc_enum_framesizes,
+	.vidioc_enum_frameintervals = vidioc_enum_frameintervals,
+	.vidioc_log_status        = vidioc_log_status,
+	.vidioc_subscribe_event   = vidioc_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static const struct video_device go7007_template = {
+	.name		= "go7007",
+	.fops		= &go7007_fops,
+	.release	= video_device_release_empty,
+	.ioctl_ops	= &video_ioctl_ops,
+	.tvnorms	= V4L2_STD_ALL,
+};
+
+static const struct v4l2_ctrl_ops go7007_ctrl_ops = {
+	.s_ctrl = go7007_s_ctrl,
+};
+
+static const struct v4l2_ctrl_config go7007_pixel_threshold0_ctrl = {
+	.ops = &go7007_ctrl_ops,
+	.id = V4L2_CID_PIXEL_THRESHOLD0,
+	.name = "Pixel Threshold Region 0",
+	.type = V4L2_CTRL_TYPE_INTEGER,
+	.def = 20,
+	.max = 32767,
+	.step = 1,
+};
+
+static const struct v4l2_ctrl_config go7007_motion_threshold0_ctrl = {
+	.ops = &go7007_ctrl_ops,
+	.id = V4L2_CID_MOTION_THRESHOLD0,
+	.name = "Motion Threshold Region 0",
+	.type = V4L2_CTRL_TYPE_INTEGER,
+	.def = 80,
+	.max = 32767,
+	.step = 1,
+};
+
+static const struct v4l2_ctrl_config go7007_mb_threshold0_ctrl = {
+	.ops = &go7007_ctrl_ops,
+	.id = V4L2_CID_MB_THRESHOLD0,
+	.name = "MB Threshold Region 0",
+	.type = V4L2_CTRL_TYPE_INTEGER,
+	.def = 200,
+	.max = 32767,
+	.step = 1,
+};
+
+static const struct v4l2_ctrl_config go7007_pixel_threshold1_ctrl = {
+	.ops = &go7007_ctrl_ops,
+	.id = V4L2_CID_PIXEL_THRESHOLD1,
+	.name = "Pixel Threshold Region 1",
+	.type = V4L2_CTRL_TYPE_INTEGER,
+	.def = 20,
+	.max = 32767,
+	.step = 1,
+};
+
+static const struct v4l2_ctrl_config go7007_motion_threshold1_ctrl = {
+	.ops = &go7007_ctrl_ops,
+	.id = V4L2_CID_MOTION_THRESHOLD1,
+	.name = "Motion Threshold Region 1",
+	.type = V4L2_CTRL_TYPE_INTEGER,
+	.def = 80,
+	.max = 32767,
+	.step = 1,
+};
+
+static const struct v4l2_ctrl_config go7007_mb_threshold1_ctrl = {
+	.ops = &go7007_ctrl_ops,
+	.id = V4L2_CID_MB_THRESHOLD1,
+	.name = "MB Threshold Region 1",
+	.type = V4L2_CTRL_TYPE_INTEGER,
+	.def = 200,
+	.max = 32767,
+	.step = 1,
+};
+
+static const struct v4l2_ctrl_config go7007_pixel_threshold2_ctrl = {
+	.ops = &go7007_ctrl_ops,
+	.id = V4L2_CID_PIXEL_THRESHOLD2,
+	.name = "Pixel Threshold Region 2",
+	.type = V4L2_CTRL_TYPE_INTEGER,
+	.def = 20,
+	.max = 32767,
+	.step = 1,
+};
+
+static const struct v4l2_ctrl_config go7007_motion_threshold2_ctrl = {
+	.ops = &go7007_ctrl_ops,
+	.id = V4L2_CID_MOTION_THRESHOLD2,
+	.name = "Motion Threshold Region 2",
+	.type = V4L2_CTRL_TYPE_INTEGER,
+	.def = 80,
+	.max = 32767,
+	.step = 1,
+};
+
+static const struct v4l2_ctrl_config go7007_mb_threshold2_ctrl = {
+	.ops = &go7007_ctrl_ops,
+	.id = V4L2_CID_MB_THRESHOLD2,
+	.name = "MB Threshold Region 2",
+	.type = V4L2_CTRL_TYPE_INTEGER,
+	.def = 200,
+	.max = 32767,
+	.step = 1,
+};
+
+static const struct v4l2_ctrl_config go7007_pixel_threshold3_ctrl = {
+	.ops = &go7007_ctrl_ops,
+	.id = V4L2_CID_PIXEL_THRESHOLD3,
+	.name = "Pixel Threshold Region 3",
+	.type = V4L2_CTRL_TYPE_INTEGER,
+	.def = 20,
+	.max = 32767,
+	.step = 1,
+};
+
+static const struct v4l2_ctrl_config go7007_motion_threshold3_ctrl = {
+	.ops = &go7007_ctrl_ops,
+	.id = V4L2_CID_MOTION_THRESHOLD3,
+	.name = "Motion Threshold Region 3",
+	.type = V4L2_CTRL_TYPE_INTEGER,
+	.def = 80,
+	.max = 32767,
+	.step = 1,
+};
+
+static const struct v4l2_ctrl_config go7007_mb_threshold3_ctrl = {
+	.ops = &go7007_ctrl_ops,
+	.id = V4L2_CID_MB_THRESHOLD3,
+	.name = "MB Threshold Region 3",
+	.type = V4L2_CTRL_TYPE_INTEGER,
+	.def = 200,
+	.max = 32767,
+	.step = 1,
+};
+
+static const struct v4l2_ctrl_config go7007_mb_regions_ctrl = {
+	.ops = &go7007_ctrl_ops,
+	.id = V4L2_CID_DETECT_MD_REGION_GRID,
+	.dims = { 576 / 16, 720 / 16 },
+	.max = 3,
+	.step = 1,
+};
+
+int go7007_v4l2_ctrl_init(struct go7007 *go)
+{
+	struct v4l2_ctrl_handler *hdl = &go->hdl;
+	struct v4l2_ctrl *ctrl;
+
+	v4l2_ctrl_handler_init(hdl, 22);
+	go->mpeg_video_gop_size = v4l2_ctrl_new_std(hdl, NULL,
+			V4L2_CID_MPEG_VIDEO_GOP_SIZE, 0, 34, 1, 15);
+	go->mpeg_video_gop_closure = v4l2_ctrl_new_std(hdl, NULL,
+			V4L2_CID_MPEG_VIDEO_GOP_CLOSURE, 0, 1, 1, 1);
+	go->mpeg_video_bitrate = v4l2_ctrl_new_std(hdl, NULL,
+			V4L2_CID_MPEG_VIDEO_BITRATE,
+			64000, 10000000, 1, 9800000);
+	go->mpeg_video_b_frames = v4l2_ctrl_new_std(hdl, NULL,
+			V4L2_CID_MPEG_VIDEO_B_FRAMES, 0, 2, 2, 0);
+	go->mpeg_video_rep_seqheader = v4l2_ctrl_new_std(hdl, NULL,
+			V4L2_CID_MPEG_VIDEO_REPEAT_SEQ_HEADER, 0, 1, 1, 1);
+
+	go->mpeg_video_aspect_ratio = v4l2_ctrl_new_std_menu(hdl, NULL,
+			V4L2_CID_MPEG_VIDEO_ASPECT,
+			V4L2_MPEG_VIDEO_ASPECT_16x9, 0,
+			V4L2_MPEG_VIDEO_ASPECT_1x1);
+	ctrl = v4l2_ctrl_new_std(hdl, NULL,
+			V4L2_CID_JPEG_ACTIVE_MARKER, 0,
+			V4L2_JPEG_ACTIVE_MARKER_DQT |
+			V4L2_JPEG_ACTIVE_MARKER_DHT, 0,
+			V4L2_JPEG_ACTIVE_MARKER_DQT |
+			V4L2_JPEG_ACTIVE_MARKER_DHT);
+	if (ctrl)
+		ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+	v4l2_ctrl_new_custom(hdl, &go7007_pixel_threshold0_ctrl, NULL);
+	v4l2_ctrl_new_custom(hdl, &go7007_motion_threshold0_ctrl, NULL);
+	v4l2_ctrl_new_custom(hdl, &go7007_mb_threshold0_ctrl, NULL);
+	v4l2_ctrl_new_custom(hdl, &go7007_pixel_threshold1_ctrl, NULL);
+	v4l2_ctrl_new_custom(hdl, &go7007_motion_threshold1_ctrl, NULL);
+	v4l2_ctrl_new_custom(hdl, &go7007_mb_threshold1_ctrl, NULL);
+	v4l2_ctrl_new_custom(hdl, &go7007_pixel_threshold2_ctrl, NULL);
+	v4l2_ctrl_new_custom(hdl, &go7007_motion_threshold2_ctrl, NULL);
+	v4l2_ctrl_new_custom(hdl, &go7007_mb_threshold2_ctrl, NULL);
+	v4l2_ctrl_new_custom(hdl, &go7007_pixel_threshold3_ctrl, NULL);
+	v4l2_ctrl_new_custom(hdl, &go7007_motion_threshold3_ctrl, NULL);
+	v4l2_ctrl_new_custom(hdl, &go7007_mb_threshold3_ctrl, NULL);
+	v4l2_ctrl_new_custom(hdl, &go7007_mb_regions_ctrl, NULL);
+	go->modet_mode = v4l2_ctrl_new_std_menu(hdl, NULL,
+			V4L2_CID_DETECT_MD_MODE,
+			V4L2_DETECT_MD_MODE_REGION_GRID,
+			1 << V4L2_DETECT_MD_MODE_THRESHOLD_GRID,
+			V4L2_DETECT_MD_MODE_DISABLED);
+	if (hdl->error) {
+		int rv = hdl->error;
+
+		v4l2_err(&go->v4l2_dev, "Could not register controls\n");
+		return rv;
+	}
+	go->v4l2_dev.ctrl_handler = hdl;
+	return 0;
+}
+
+int go7007_v4l2_init(struct go7007 *go)
+{
+	struct video_device *vdev = &go->vdev;
+	int rv;
+
+	mutex_init(&go->serialize_lock);
+	mutex_init(&go->queue_lock);
+
+	INIT_LIST_HEAD(&go->vidq_active);
+	go->vidq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	go->vidq.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
+	go->vidq.ops = &go7007_video_qops;
+	go->vidq.mem_ops = &vb2_vmalloc_memops;
+	go->vidq.drv_priv = go;
+	go->vidq.buf_struct_size = sizeof(struct go7007_buffer);
+	go->vidq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	go->vidq.lock = &go->queue_lock;
+	rv = vb2_queue_init(&go->vidq);
+	if (rv)
+		return rv;
+	*vdev = go7007_template;
+	vdev->lock = &go->serialize_lock;
+	vdev->queue = &go->vidq;
+	video_set_drvdata(vdev, go);
+	vdev->v4l2_dev = &go->v4l2_dev;
+	if (!v4l2_device_has_op(&go->v4l2_dev, 0, video, querystd))
+		v4l2_disable_ioctl(vdev, VIDIOC_QUERYSTD);
+	if (!(go->board_info->flags & GO7007_BOARD_HAS_TUNER)) {
+		v4l2_disable_ioctl(vdev, VIDIOC_S_FREQUENCY);
+		v4l2_disable_ioctl(vdev, VIDIOC_G_FREQUENCY);
+		v4l2_disable_ioctl(vdev, VIDIOC_S_TUNER);
+		v4l2_disable_ioctl(vdev, VIDIOC_G_TUNER);
+	} else {
+		struct v4l2_frequency f = {
+			.type = V4L2_TUNER_ANALOG_TV,
+			.frequency = 980,
+		};
+
+		call_all(&go->v4l2_dev, tuner, s_frequency, &f);
+	}
+	if (!(go->board_info->sensor_flags & GO7007_SENSOR_TV)) {
+		v4l2_disable_ioctl(vdev, VIDIOC_G_STD);
+		v4l2_disable_ioctl(vdev, VIDIOC_S_STD);
+		vdev->tvnorms = 0;
+	}
+	if (go->board_info->sensor_flags & GO7007_SENSOR_SCALING)
+		v4l2_disable_ioctl(vdev, VIDIOC_ENUM_FRAMESIZES);
+	if (go->board_info->num_aud_inputs == 0) {
+		v4l2_disable_ioctl(vdev, VIDIOC_G_AUDIO);
+		v4l2_disable_ioctl(vdev, VIDIOC_S_AUDIO);
+		v4l2_disable_ioctl(vdev, VIDIOC_ENUMAUDIO);
+	}
+	/* Setup correct crystal frequency on this board */
+	if (go->board_info->sensor_flags & GO7007_SENSOR_SAA7115)
+		v4l2_subdev_call(go->sd_video, video, s_crystal_freq,
+				SAA7115_FREQ_24_576_MHZ,
+				SAA7115_FREQ_FL_APLL | SAA7115_FREQ_FL_UCGC |
+				SAA7115_FREQ_FL_DOUBLE_ASCLK);
+	go7007_s_input(go);
+	if (go->board_info->sensor_flags & GO7007_SENSOR_TV)
+		go7007_s_std(go);
+	rv = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
+	if (rv < 0)
+		return rv;
+	dev_info(go->dev, "registered device %s [v4l2]\n",
+		 video_device_node_name(vdev));
+
+	return 0;
+}
+
+void go7007_v4l2_remove(struct go7007 *go)
+{
+	v4l2_ctrl_handler_free(&go->hdl);
+}
diff --git a/drivers/media/usb/go7007/s2250-board.c b/drivers/media/usb/go7007/s2250-board.c
new file mode 100644
index 0000000..1466db1
--- /dev/null
+++ b/drivers/media/usb/go7007/s2250-board.c
@@ -0,0 +1,639 @@
+/*
+ * Copyright (C) 2008 Sensoray Company Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (Version 2) as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/i2c.h>
+#include <linux/videodev2.h>
+#include <linux/slab.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-subdev.h>
+#include "go7007-priv.h"
+
+MODULE_DESCRIPTION("Sensoray 2250/2251 i2c v4l2 subdev driver");
+MODULE_LICENSE("GPL v2");
+
+/*
+ * Note: this board has two i2c devices: a vpx3226f and a tlv320aic23b.
+ * Due to the unusual way these are accessed on this device we do not
+ * reuse the i2c drivers, but instead they are implemented in this
+ * driver. It would be nice to improve on this, though.
+ */
+
+#define TLV320_ADDRESS      0x34
+#define VPX322_ADDR_ANALOGCONTROL1	0x02
+#define VPX322_ADDR_BRIGHTNESS0		0x0127
+#define VPX322_ADDR_BRIGHTNESS1		0x0131
+#define VPX322_ADDR_CONTRAST0		0x0128
+#define VPX322_ADDR_CONTRAST1		0x0132
+#define VPX322_ADDR_HUE			0x00dc
+#define VPX322_ADDR_SAT			0x0030
+
+struct go7007_usb_board {
+	unsigned int flags;
+	struct go7007_board_info main_info;
+};
+
+struct go7007_usb {
+	struct go7007_usb_board *board;
+	struct mutex i2c_lock;
+	struct usb_device *usbdev;
+	struct urb *video_urbs[8];
+	struct urb *audio_urbs[8];
+	struct urb *intr_urb;
+};
+
+static unsigned char aud_regs[] = {
+	0x1e, 0x00,
+	0x00, 0x17,
+	0x02, 0x17,
+	0x04, 0xf9,
+	0x06, 0xf9,
+	0x08, 0x02,
+	0x0a, 0x00,
+	0x0c, 0x00,
+	0x0a, 0x00,
+	0x0c, 0x00,
+	0x0e, 0x02,
+	0x10, 0x00,
+	0x12, 0x01,
+	0x00, 0x00,
+};
+
+
+static unsigned char vid_regs[] = {
+	0xF2, 0x0f,
+	0xAA, 0x00,
+	0xF8, 0xff,
+	0x00, 0x00,
+};
+
+static u16 vid_regs_fp[] = {
+	0x028, 0x067,
+	0x120, 0x016,
+	0x121, 0xcF2,
+	0x122, 0x0F2,
+	0x123, 0x00c,
+	0x124, 0x2d0,
+	0x125, 0x2e0,
+	0x126, 0x004,
+	0x128, 0x1E0,
+	0x12A, 0x016,
+	0x12B, 0x0F2,
+	0x12C, 0x0F2,
+	0x12D, 0x00c,
+	0x12E, 0x2d0,
+	0x12F, 0x2e0,
+	0x130, 0x004,
+	0x132, 0x1E0,
+	0x140, 0x060,
+	0x153, 0x00C,
+	0x154, 0x200,
+	0x150, 0x801,
+	0x000, 0x000
+};
+
+/* PAL specific values */
+static u16 vid_regs_fp_pal[] = {
+	0x120, 0x017,
+	0x121, 0xd22,
+	0x122, 0x122,
+	0x12A, 0x017,
+	0x12B, 0x122,
+	0x12C, 0x122,
+	0x140, 0x060,
+	0x000, 0x000,
+};
+
+struct s2250 {
+	struct v4l2_subdev sd;
+	struct v4l2_ctrl_handler hdl;
+	v4l2_std_id std;
+	int input;
+	int brightness;
+	int contrast;
+	int saturation;
+	int hue;
+	int reg12b_val;
+	int audio_input;
+	struct i2c_client *audio;
+};
+
+static inline struct s2250 *to_state(struct v4l2_subdev *sd)
+{
+	return container_of(sd, struct s2250, sd);
+}
+
+/* from go7007-usb.c which is Copyright (C) 2005-2006 Micronas USA Inc.*/
+static int go7007_usb_vendor_request(struct go7007 *go, u16 request,
+	u16 value, u16 index, void *transfer_buffer, int length, int in)
+{
+	struct go7007_usb *usb = go->hpi_context;
+	int timeout = 5000;
+
+	if (in) {
+		return usb_control_msg(usb->usbdev,
+				usb_rcvctrlpipe(usb->usbdev, 0), request,
+				USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
+				value, index, transfer_buffer, length, timeout);
+	} else {
+		return usb_control_msg(usb->usbdev,
+				usb_sndctrlpipe(usb->usbdev, 0), request,
+				USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+				value, index, transfer_buffer, length, timeout);
+	}
+}
+/* end from go7007-usb.c which is Copyright (C) 2005-2006 Micronas USA Inc.*/
+
+static int write_reg(struct i2c_client *client, u8 reg, u8 value)
+{
+	struct go7007 *go = i2c_get_adapdata(client->adapter);
+	struct go7007_usb *usb;
+	int rc;
+	int dev_addr = client->addr << 1;  /* firmware wants 8-bit address */
+	u8 *buf;
+
+	if (go == NULL)
+		return -ENODEV;
+
+	if (go->status == STATUS_SHUTDOWN)
+		return -EBUSY;
+
+	buf = kzalloc(16, GFP_KERNEL);
+	if (buf == NULL)
+		return -ENOMEM;
+
+	usb = go->hpi_context;
+	if (mutex_lock_interruptible(&usb->i2c_lock) != 0) {
+		dev_info(&client->dev, "i2c lock failed\n");
+		kfree(buf);
+		return -EINTR;
+	}
+	rc = go7007_usb_vendor_request(go, 0x55, dev_addr,
+				       (reg<<8 | value),
+				       buf,
+				       16, 1);
+
+	mutex_unlock(&usb->i2c_lock);
+	kfree(buf);
+	return rc;
+}
+
+static int write_reg_fp(struct i2c_client *client, u16 addr, u16 val)
+{
+	struct go7007 *go = i2c_get_adapdata(client->adapter);
+	struct go7007_usb *usb;
+	int rc;
+	u8 *buf;
+	struct s2250 *dec = i2c_get_clientdata(client);
+
+	if (go == NULL)
+		return -ENODEV;
+
+	if (go->status == STATUS_SHUTDOWN)
+		return -EBUSY;
+
+	buf = kzalloc(16, GFP_KERNEL);
+
+	if (buf == NULL)
+		return -ENOMEM;
+
+
+
+	memset(buf, 0xcd, 6);
+
+	usb = go->hpi_context;
+	if (mutex_lock_interruptible(&usb->i2c_lock) != 0) {
+		dev_info(&client->dev, "i2c lock failed\n");
+		kfree(buf);
+		return -EINTR;
+	}
+	rc = go7007_usb_vendor_request(go, 0x57, addr, val, buf, 16, 1);
+	mutex_unlock(&usb->i2c_lock);
+	if (rc < 0) {
+		kfree(buf);
+		return rc;
+	}
+
+	if (buf[0] == 0) {
+		unsigned int subaddr, val_read;
+
+		subaddr = (buf[4] << 8) + buf[5];
+		val_read = (buf[2] << 8) + buf[3];
+		kfree(buf);
+		if (val_read != val) {
+			dev_info(&client->dev, "invalid fp write %x %x\n",
+				 val_read, val);
+			return -EFAULT;
+		}
+		if (subaddr != addr) {
+			dev_info(&client->dev, "invalid fp write addr %x %x\n",
+				 subaddr, addr);
+			return -EFAULT;
+		}
+	} else {
+		kfree(buf);
+		return -EFAULT;
+	}
+
+	/* save last 12b value */
+	if (addr == 0x12b)
+		dec->reg12b_val = val;
+
+	return 0;
+}
+
+static int read_reg_fp(struct i2c_client *client, u16 addr, u16 *val)
+{
+	struct go7007 *go = i2c_get_adapdata(client->adapter);
+	struct go7007_usb *usb;
+	int rc;
+	u8 *buf;
+
+	if (go == NULL)
+		return -ENODEV;
+
+	if (go->status == STATUS_SHUTDOWN)
+		return -EBUSY;
+
+	buf = kzalloc(16, GFP_KERNEL);
+
+	if (buf == NULL)
+		return -ENOMEM;
+
+
+
+	memset(buf, 0xcd, 6);
+	usb = go->hpi_context;
+	if (mutex_lock_interruptible(&usb->i2c_lock) != 0) {
+		dev_info(&client->dev, "i2c lock failed\n");
+		kfree(buf);
+		return -EINTR;
+	}
+	rc = go7007_usb_vendor_request(go, 0x58, addr, 0, buf, 16, 1);
+	mutex_unlock(&usb->i2c_lock);
+	if (rc < 0) {
+		kfree(buf);
+		return rc;
+	}
+
+	*val = (buf[0] << 8) | buf[1];
+	kfree(buf);
+
+	return 0;
+}
+
+
+static int write_regs(struct i2c_client *client, u8 *regs)
+{
+	int i;
+
+	for (i = 0; !((regs[i] == 0x00) && (regs[i+1] == 0x00)); i += 2) {
+		if (write_reg(client, regs[i], regs[i+1]) < 0) {
+			dev_info(&client->dev, "failed\n");
+			return -1;
+		}
+	}
+	return 0;
+}
+
+static int write_regs_fp(struct i2c_client *client, u16 *regs)
+{
+	int i;
+
+	for (i = 0; !((regs[i] == 0x00) && (regs[i+1] == 0x00)); i += 2) {
+		if (write_reg_fp(client, regs[i], regs[i+1]) < 0) {
+			dev_info(&client->dev, "failed fp\n");
+			return -1;
+		}
+	}
+	return 0;
+}
+
+
+/* ------------------------------------------------------------------------- */
+
+static int s2250_s_video_routing(struct v4l2_subdev *sd, u32 input, u32 output,
+				 u32 config)
+{
+	struct s2250 *state = to_state(sd);
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	int vidsys;
+
+	vidsys = (state->std == V4L2_STD_NTSC) ? 0x01 : 0x00;
+	if (input == 0) {
+		/* composite */
+		write_reg_fp(client, 0x20, 0x020 | vidsys);
+		write_reg_fp(client, 0x21, 0x662);
+		write_reg_fp(client, 0x140, 0x060);
+	} else if (input == 1) {
+		/* S-Video */
+		write_reg_fp(client, 0x20, 0x040 | vidsys);
+		write_reg_fp(client, 0x21, 0x666);
+		write_reg_fp(client, 0x140, 0x060);
+	} else {
+		return -EINVAL;
+	}
+	state->input = input;
+	return 0;
+}
+
+static int s2250_s_std(struct v4l2_subdev *sd, v4l2_std_id norm)
+{
+	struct s2250 *state = to_state(sd);
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+	u16 vidsource;
+
+	vidsource = (state->input == 1) ? 0x040 : 0x020;
+	if (norm & V4L2_STD_625_50) {
+		write_regs_fp(client, vid_regs_fp);
+		write_regs_fp(client, vid_regs_fp_pal);
+		write_reg_fp(client, 0x20, vidsource);
+	} else {
+		write_regs_fp(client, vid_regs_fp);
+		write_reg_fp(client, 0x20, vidsource | 1);
+	}
+	state->std = norm;
+	return 0;
+}
+
+static int s2250_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct s2250 *state = container_of(ctrl->handler, struct s2250, hdl);
+	struct i2c_client *client = v4l2_get_subdevdata(&state->sd);
+	u16 oldvalue;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		read_reg_fp(client, VPX322_ADDR_BRIGHTNESS0, &oldvalue);
+		write_reg_fp(client, VPX322_ADDR_BRIGHTNESS0,
+			     ctrl->val | (oldvalue & ~0xff));
+		read_reg_fp(client, VPX322_ADDR_BRIGHTNESS1, &oldvalue);
+		write_reg_fp(client, VPX322_ADDR_BRIGHTNESS1,
+			     ctrl->val | (oldvalue & ~0xff));
+		write_reg_fp(client, 0x140, 0x60);
+		break;
+	case V4L2_CID_CONTRAST:
+		read_reg_fp(client, VPX322_ADDR_CONTRAST0, &oldvalue);
+		write_reg_fp(client, VPX322_ADDR_CONTRAST0,
+			     ctrl->val | (oldvalue & ~0x3f));
+		read_reg_fp(client, VPX322_ADDR_CONTRAST1, &oldvalue);
+		write_reg_fp(client, VPX322_ADDR_CONTRAST1,
+			     ctrl->val | (oldvalue & ~0x3f));
+		write_reg_fp(client, 0x140, 0x60);
+		break;
+	case V4L2_CID_SATURATION:
+		write_reg_fp(client, VPX322_ADDR_SAT, ctrl->val);
+		break;
+	case V4L2_CID_HUE:
+		write_reg_fp(client, VPX322_ADDR_HUE, ctrl->val);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int s2250_set_fmt(struct v4l2_subdev *sd,
+		struct v4l2_subdev_pad_config *cfg,
+		struct v4l2_subdev_format *format)
+{
+	struct v4l2_mbus_framefmt *fmt = &format->format;
+	struct s2250 *state = to_state(sd);
+	struct i2c_client *client = v4l2_get_subdevdata(sd);
+
+	if (format->pad)
+		return -EINVAL;
+
+	if (format->which == V4L2_SUBDEV_FORMAT_TRY)
+		return 0;
+
+	if (fmt->height < 640) {
+		write_reg_fp(client, 0x12b, state->reg12b_val | 0x400);
+		write_reg_fp(client, 0x140, 0x060);
+	} else {
+		write_reg_fp(client, 0x12b, state->reg12b_val & ~0x400);
+		write_reg_fp(client, 0x140, 0x060);
+	}
+	return 0;
+}
+
+static int s2250_s_audio_routing(struct v4l2_subdev *sd, u32 input, u32 output,
+				 u32 config)
+{
+	struct s2250 *state = to_state(sd);
+
+	switch (input) {
+	case 0:
+		write_reg(state->audio, 0x08, 0x02); /* Line In */
+		break;
+	case 1:
+		write_reg(state->audio, 0x08, 0x04); /* Mic */
+		break;
+	case 2:
+		write_reg(state->audio, 0x08, 0x05); /* Mic Boost */
+		break;
+	default:
+		return -EINVAL;
+	}
+	state->audio_input = input;
+	return 0;
+}
+
+
+static int s2250_log_status(struct v4l2_subdev *sd)
+{
+	struct s2250 *state = to_state(sd);
+
+	v4l2_info(sd, "Standard: %s\n", state->std == V4L2_STD_NTSC ? "NTSC" :
+					state->std == V4L2_STD_PAL ? "PAL" :
+					state->std == V4L2_STD_SECAM ? "SECAM" :
+					"unknown");
+	v4l2_info(sd, "Input: %s\n", state->input == 0 ? "Composite" :
+					state->input == 1 ? "S-video" :
+					"error");
+	v4l2_info(sd, "Audio input: %s\n", state->audio_input == 0 ? "Line In" :
+					state->audio_input == 1 ? "Mic" :
+					state->audio_input == 2 ? "Mic Boost" :
+					"error");
+	return v4l2_ctrl_subdev_log_status(sd);
+}
+
+/* --------------------------------------------------------------------------*/
+
+static const struct v4l2_ctrl_ops s2250_ctrl_ops = {
+	.s_ctrl = s2250_s_ctrl,
+};
+
+static const struct v4l2_subdev_core_ops s2250_core_ops = {
+	.log_status = s2250_log_status,
+};
+
+static const struct v4l2_subdev_audio_ops s2250_audio_ops = {
+	.s_routing = s2250_s_audio_routing,
+};
+
+static const struct v4l2_subdev_video_ops s2250_video_ops = {
+	.s_std = s2250_s_std,
+	.s_routing = s2250_s_video_routing,
+};
+
+static const struct v4l2_subdev_pad_ops s2250_pad_ops = {
+	.set_fmt = s2250_set_fmt,
+};
+
+static const struct v4l2_subdev_ops s2250_ops = {
+	.core = &s2250_core_ops,
+	.audio = &s2250_audio_ops,
+	.video = &s2250_video_ops,
+	.pad = &s2250_pad_ops,
+};
+
+/* --------------------------------------------------------------------------*/
+
+static int s2250_probe(struct i2c_client *client,
+		       const struct i2c_device_id *id)
+{
+	struct i2c_client *audio;
+	struct i2c_adapter *adapter = client->adapter;
+	struct s2250 *state;
+	struct v4l2_subdev *sd;
+	u8 *data;
+	struct go7007 *go = i2c_get_adapdata(adapter);
+	struct go7007_usb *usb = go->hpi_context;
+
+	audio = i2c_new_dummy(adapter, TLV320_ADDRESS >> 1);
+	if (audio == NULL)
+		return -ENOMEM;
+
+	state = kzalloc(sizeof(struct s2250), GFP_KERNEL);
+	if (state == NULL) {
+		i2c_unregister_device(audio);
+		return -ENOMEM;
+	}
+
+	sd = &state->sd;
+	v4l2_i2c_subdev_init(sd, client, &s2250_ops);
+
+	v4l2_info(sd, "initializing %s at address 0x%x on %s\n",
+	       "Sensoray 2250/2251", client->addr, client->adapter->name);
+
+	v4l2_ctrl_handler_init(&state->hdl, 4);
+	v4l2_ctrl_new_std(&state->hdl, &s2250_ctrl_ops,
+		V4L2_CID_BRIGHTNESS, -128, 127, 1, 0);
+	v4l2_ctrl_new_std(&state->hdl, &s2250_ctrl_ops,
+		V4L2_CID_CONTRAST, 0, 0x3f, 1, 0x32);
+	v4l2_ctrl_new_std(&state->hdl, &s2250_ctrl_ops,
+		V4L2_CID_SATURATION, 0, 4094, 1, 2070);
+	v4l2_ctrl_new_std(&state->hdl, &s2250_ctrl_ops,
+		V4L2_CID_HUE, -512, 511, 1, 0);
+	sd->ctrl_handler = &state->hdl;
+	if (state->hdl.error) {
+		int err = state->hdl.error;
+
+		v4l2_ctrl_handler_free(&state->hdl);
+		kfree(state);
+		return err;
+	}
+
+	state->std = V4L2_STD_NTSC;
+	state->brightness = 50;
+	state->contrast = 50;
+	state->saturation = 50;
+	state->hue = 0;
+	state->audio = audio;
+
+	/* initialize the audio */
+	if (write_regs(audio, aud_regs) < 0) {
+		dev_err(&client->dev, "error initializing audio\n");
+		goto fail;
+	}
+
+	if (write_regs(client, vid_regs) < 0) {
+		dev_err(&client->dev, "error initializing decoder\n");
+		goto fail;
+	}
+	if (write_regs_fp(client, vid_regs_fp) < 0) {
+		dev_err(&client->dev, "error initializing decoder\n");
+		goto fail;
+	}
+	/* set default channel */
+	/* composite */
+	write_reg_fp(client, 0x20, 0x020 | 1);
+	write_reg_fp(client, 0x21, 0x662);
+	write_reg_fp(client, 0x140, 0x060);
+
+	/* set default audio input */
+	state->audio_input = 0;
+	write_reg(client, 0x08, 0x02); /* Line In */
+
+	if (mutex_lock_interruptible(&usb->i2c_lock) == 0) {
+		data = kzalloc(16, GFP_KERNEL);
+		if (data != NULL) {
+			int rc = go7007_usb_vendor_request(go, 0x41, 0, 0,
+						       data, 16, 1);
+
+			if (rc > 0) {
+				u8 mask;
+
+				data[0] = 0;
+				mask = 1<<5;
+				data[0] &= ~mask;
+				data[1] |= mask;
+				go7007_usb_vendor_request(go, 0x40, 0,
+							  (data[1]<<8)
+							  + data[1],
+							  data, 16, 0);
+			}
+			kfree(data);
+		}
+		mutex_unlock(&usb->i2c_lock);
+	}
+
+	v4l2_info(sd, "initialized successfully\n");
+	return 0;
+
+fail:
+	i2c_unregister_device(audio);
+	v4l2_ctrl_handler_free(&state->hdl);
+	kfree(state);
+	return -EIO;
+}
+
+static int s2250_remove(struct i2c_client *client)
+{
+	struct s2250 *state = to_state(i2c_get_clientdata(client));
+
+	v4l2_device_unregister_subdev(&state->sd);
+	v4l2_ctrl_handler_free(&state->hdl);
+	kfree(state);
+	return 0;
+}
+
+static const struct i2c_device_id s2250_id[] = {
+	{ "s2250", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, s2250_id);
+
+static struct i2c_driver s2250_driver = {
+	.driver = {
+		.name	= "s2250",
+	},
+	.probe		= s2250_probe,
+	.remove		= s2250_remove,
+	.id_table	= s2250_id,
+};
+
+module_i2c_driver(s2250_driver);
diff --git a/drivers/media/usb/go7007/snd-go7007.c b/drivers/media/usb/go7007/snd-go7007.c
new file mode 100644
index 0000000..137fc25
--- /dev/null
+++ b/drivers/media/usb/go7007/snd-go7007.c
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2005-2006 Micronas USA Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License (Version 2) as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <linux/sched.h>
+#include <linux/vmalloc.h>
+#include <linux/time.h>
+#include <linux/mm.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+
+#include "go7007-priv.h"
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;
+static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+module_param_array(index, int, NULL, 0444);
+module_param_array(id, charp, NULL, 0444);
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for the go7007 audio driver");
+MODULE_PARM_DESC(id, "ID string for the go7007 audio driver");
+MODULE_PARM_DESC(enable, "Enable for the go7007 audio driver");
+
+struct go7007_snd {
+	struct snd_card *card;
+	struct snd_pcm *pcm;
+	struct snd_pcm_substream *substream;
+	spinlock_t lock;
+	int w_idx;
+	int hw_ptr;
+	int avail;
+	int capturing;
+};
+
+static const struct snd_pcm_hardware go7007_snd_capture_hw = {
+	.info			= (SNDRV_PCM_INFO_MMAP |
+					SNDRV_PCM_INFO_INTERLEAVED |
+					SNDRV_PCM_INFO_BLOCK_TRANSFER |
+					SNDRV_PCM_INFO_MMAP_VALID),
+	.formats		= SNDRV_PCM_FMTBIT_S16_LE,
+	.rates			= SNDRV_PCM_RATE_48000,
+	.rate_min		= 48000,
+	.rate_max		= 48000,
+	.channels_min		= 2,
+	.channels_max		= 2,
+	.buffer_bytes_max	= (128*1024),
+	.period_bytes_min	= 4096,
+	.period_bytes_max	= (128*1024),
+	.periods_min		= 1,
+	.periods_max		= 32,
+};
+
+static void parse_audio_stream_data(struct go7007 *go, u8 *buf, int length)
+{
+	struct go7007_snd *gosnd = go->snd_context;
+	struct snd_pcm_runtime *runtime = gosnd->substream->runtime;
+	int frames = bytes_to_frames(runtime, length);
+	unsigned long flags;
+
+	spin_lock_irqsave(&gosnd->lock, flags);
+	gosnd->hw_ptr += frames;
+	if (gosnd->hw_ptr >= runtime->buffer_size)
+		gosnd->hw_ptr -= runtime->buffer_size;
+	gosnd->avail += frames;
+	spin_unlock_irqrestore(&gosnd->lock, flags);
+	if (gosnd->w_idx + length > runtime->dma_bytes) {
+		int cpy = runtime->dma_bytes - gosnd->w_idx;
+
+		memcpy(runtime->dma_area + gosnd->w_idx, buf, cpy);
+		length -= cpy;
+		buf += cpy;
+		gosnd->w_idx = 0;
+	}
+	memcpy(runtime->dma_area + gosnd->w_idx, buf, length);
+	gosnd->w_idx += length;
+	spin_lock_irqsave(&gosnd->lock, flags);
+	if (gosnd->avail < runtime->period_size) {
+		spin_unlock_irqrestore(&gosnd->lock, flags);
+		return;
+	}
+	gosnd->avail -= runtime->period_size;
+	spin_unlock_irqrestore(&gosnd->lock, flags);
+	if (gosnd->capturing)
+		snd_pcm_period_elapsed(gosnd->substream);
+}
+
+static int go7007_snd_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *hw_params)
+{
+	struct go7007 *go = snd_pcm_substream_chip(substream);
+	unsigned int bytes;
+
+	bytes = params_buffer_bytes(hw_params);
+	if (substream->runtime->dma_bytes > 0)
+		vfree(substream->runtime->dma_area);
+	substream->runtime->dma_bytes = 0;
+	substream->runtime->dma_area = vmalloc(bytes);
+	if (substream->runtime->dma_area == NULL)
+		return -ENOMEM;
+	substream->runtime->dma_bytes = bytes;
+	go->audio_deliver = parse_audio_stream_data;
+	return 0;
+}
+
+static int go7007_snd_hw_free(struct snd_pcm_substream *substream)
+{
+	struct go7007 *go = snd_pcm_substream_chip(substream);
+
+	go->audio_deliver = NULL;
+	if (substream->runtime->dma_bytes > 0)
+		vfree(substream->runtime->dma_area);
+	substream->runtime->dma_bytes = 0;
+	return 0;
+}
+
+static int go7007_snd_capture_open(struct snd_pcm_substream *substream)
+{
+	struct go7007 *go = snd_pcm_substream_chip(substream);
+	struct go7007_snd *gosnd = go->snd_context;
+	unsigned long flags;
+	int r;
+
+	spin_lock_irqsave(&gosnd->lock, flags);
+	if (gosnd->substream == NULL) {
+		gosnd->substream = substream;
+		substream->runtime->hw = go7007_snd_capture_hw;
+		r = 0;
+	} else
+		r = -EBUSY;
+	spin_unlock_irqrestore(&gosnd->lock, flags);
+	return r;
+}
+
+static int go7007_snd_capture_close(struct snd_pcm_substream *substream)
+{
+	struct go7007 *go = snd_pcm_substream_chip(substream);
+	struct go7007_snd *gosnd = go->snd_context;
+
+	gosnd->substream = NULL;
+	return 0;
+}
+
+static int go7007_snd_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	return 0;
+}
+
+static int go7007_snd_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct go7007 *go = snd_pcm_substream_chip(substream);
+	struct go7007_snd *gosnd = go->snd_context;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+		/* Just set a flag to indicate we should signal ALSA when
+		 * sound comes in */
+		gosnd->capturing = 1;
+		return 0;
+	case SNDRV_PCM_TRIGGER_STOP:
+		gosnd->hw_ptr = gosnd->w_idx = gosnd->avail = 0;
+		gosnd->capturing = 0;
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+static snd_pcm_uframes_t go7007_snd_pcm_pointer(struct snd_pcm_substream *substream)
+{
+	struct go7007 *go = snd_pcm_substream_chip(substream);
+	struct go7007_snd *gosnd = go->snd_context;
+
+	return gosnd->hw_ptr;
+}
+
+static struct page *go7007_snd_pcm_page(struct snd_pcm_substream *substream,
+					unsigned long offset)
+{
+	return vmalloc_to_page(substream->runtime->dma_area + offset);
+}
+
+static const struct snd_pcm_ops go7007_snd_capture_ops = {
+	.open		= go7007_snd_capture_open,
+	.close		= go7007_snd_capture_close,
+	.ioctl		= snd_pcm_lib_ioctl,
+	.hw_params	= go7007_snd_hw_params,
+	.hw_free	= go7007_snd_hw_free,
+	.prepare	= go7007_snd_pcm_prepare,
+	.trigger	= go7007_snd_pcm_trigger,
+	.pointer	= go7007_snd_pcm_pointer,
+	.page		= go7007_snd_pcm_page,
+};
+
+static int go7007_snd_free(struct snd_device *device)
+{
+	struct go7007 *go = device->device_data;
+
+	kfree(go->snd_context);
+	go->snd_context = NULL;
+	return 0;
+}
+
+static struct snd_device_ops go7007_snd_device_ops = {
+	.dev_free	= go7007_snd_free,
+};
+
+int go7007_snd_init(struct go7007 *go)
+{
+	static int dev;
+	struct go7007_snd *gosnd;
+	int ret;
+
+	if (dev >= SNDRV_CARDS)
+		return -ENODEV;
+	if (!enable[dev]) {
+		dev++;
+		return -ENOENT;
+	}
+	gosnd = kmalloc(sizeof(struct go7007_snd), GFP_KERNEL);
+	if (gosnd == NULL)
+		return -ENOMEM;
+	spin_lock_init(&gosnd->lock);
+	gosnd->hw_ptr = gosnd->w_idx = gosnd->avail = 0;
+	gosnd->capturing = 0;
+	ret = snd_card_new(go->dev, index[dev], id[dev], THIS_MODULE, 0,
+			   &gosnd->card);
+	if (ret < 0) {
+		kfree(gosnd);
+		return ret;
+	}
+	ret = snd_device_new(gosnd->card, SNDRV_DEV_LOWLEVEL, go,
+			&go7007_snd_device_ops);
+	if (ret < 0) {
+		kfree(gosnd);
+		return ret;
+	}
+	ret = snd_pcm_new(gosnd->card, "go7007", 0, 0, 1, &gosnd->pcm);
+	if (ret < 0) {
+		snd_card_free(gosnd->card);
+		kfree(gosnd);
+		return ret;
+	}
+	strlcpy(gosnd->card->driver, "go7007", sizeof(gosnd->card->driver));
+	strlcpy(gosnd->card->shortname, go->name, sizeof(gosnd->card->driver));
+	strlcpy(gosnd->card->longname, gosnd->card->shortname,
+			sizeof(gosnd->card->longname));
+
+	gosnd->pcm->private_data = go;
+	snd_pcm_set_ops(gosnd->pcm, SNDRV_PCM_STREAM_CAPTURE,
+			&go7007_snd_capture_ops);
+
+	ret = snd_card_register(gosnd->card);
+	if (ret < 0) {
+		snd_card_free(gosnd->card);
+		kfree(gosnd);
+		return ret;
+	}
+
+	gosnd->substream = NULL;
+	go->snd_context = gosnd;
+	v4l2_device_get(&go->v4l2_dev);
+	++dev;
+
+	return 0;
+}
+EXPORT_SYMBOL(go7007_snd_init);
+
+int go7007_snd_remove(struct go7007 *go)
+{
+	struct go7007_snd *gosnd = go->snd_context;
+
+	snd_card_disconnect(gosnd->card);
+	snd_card_free_when_closed(gosnd->card);
+	v4l2_device_put(&go->v4l2_dev);
+	return 0;
+}
+EXPORT_SYMBOL(go7007_snd_remove);
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/usb/gspca/Kconfig b/drivers/media/usb/gspca/Kconfig
new file mode 100644
index 0000000..d3b6665
--- /dev/null
+++ b/drivers/media/usb/gspca/Kconfig
@@ -0,0 +1,456 @@
+menuconfig USB_GSPCA
+	tristate "GSPCA based webcams"
+	depends on VIDEO_V4L2
+	depends on INPUT || INPUT=n
+	select VIDEOBUF2_VMALLOC
+	default m
+	---help---
+	  Say Y here if you want to enable selecting webcams based
+	  on the GSPCA framework.
+
+	  See <file:Documentation/media/v4l-drivers/gspca-cardlist.rst> for more info.
+
+	  This driver uses the Video For Linux API. You must say Y or M to
+	  "Video For Linux" to use this driver.
+
+	  To compile this driver as modules, choose M here: the
+	  module will be called gspca_main.
+
+
+if USB_GSPCA && VIDEO_V4L2
+
+source "drivers/media/usb/gspca/m5602/Kconfig"
+source "drivers/media/usb/gspca/stv06xx/Kconfig"
+source "drivers/media/usb/gspca/gl860/Kconfig"
+
+config USB_GSPCA_BENQ
+	tristate "Benq USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for the Benq DC E300 camera.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_benq.
+
+config USB_GSPCA_CONEX
+	tristate "Conexant Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the Conexant chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_conex.
+
+config USB_GSPCA_CPIA1
+	tristate "cpia CPiA (version 1) Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for USB cameras based on the cpia
+	  CPiA chip. Note that you need atleast version 0.6.4 of libv4l for
+	  applications to understand the videoformat generated by this driver.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_cpia1.
+
+config USB_GSPCA_DTCS033
+	tristate "DTCS033 (Scopium) USB Astro-Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for the Scopium camera
+	  for planetary astrophotography.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_dtcs033.
+
+config USB_GSPCA_ETOMS
+	tristate "Etoms USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the Etoms chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_etoms.
+
+config USB_GSPCA_FINEPIX
+	tristate "Fujifilm FinePix USB V4L2 driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the FinePix chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_finepix.
+
+config USB_GSPCA_JEILINJ
+	tristate "Jeilin JPEG USB V4L2 driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on this Jeilin chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_jeilinj.
+
+config USB_GSPCA_JL2005BCD
+	tristate "JL2005B/C/D USB V4L2 driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based the
+	  JL2005B, JL2005C, or JL2005D chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_jl2005bcd.
+
+config USB_GSPCA_KINECT
+	tristate "Kinect sensor device USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for the Microsoft Kinect sensor device.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_kinect.
+
+config USB_GSPCA_KONICA
+	tristate "Konica USB Camera V4L2 driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the Konica chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_konica.
+
+config USB_GSPCA_MARS
+	tristate "Mars USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the Mars chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_mars.
+
+config USB_GSPCA_MR97310A
+	tristate "Mars-Semi MR97310A USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the MR97310A chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_mr97310a.
+
+config USB_GSPCA_NW80X
+	tristate "Divio based (NW80x) USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the NW80x chips.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_nw80x.
+
+config USB_GSPCA_OV519
+	tristate "OV51x / OVFX2 / W996xCF USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on one of these:
+	  OV511(+), OV518(+), OV519, OVFX2, W9967CF, W9968CF
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_ov519.
+
+config USB_GSPCA_OV534
+	tristate "OV534 OV772x USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the OV534 chip
+	  and sensor OV772x (e.g. Sony Playstation EYE)
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_ov534.
+
+config USB_GSPCA_OV534_9
+	tristate "OV534 OV965x USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the OV534 chip
+	  and sensor OV965x (e.g. Hercules Dualpix)
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_ov534_9.
+
+config USB_GSPCA_PAC207
+	tristate "Pixart PAC207 USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the PAC207 chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_pac207.
+
+config USB_GSPCA_PAC7302
+	tristate "Pixart PAC7302 USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the PAC7302 chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_pac7302.
+
+config USB_GSPCA_PAC7311
+	tristate "Pixart PAC7311 USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the PAC7311 chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_pac7311.
+
+config USB_GSPCA_SE401
+	tristate "SE401 USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the
+	  Endpoints (formerly known as AOX) se401 chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_se401.
+
+config USB_GSPCA_SN9C2028
+	tristate "SONIX Dual-Mode USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want streaming support for Sonix SN9C2028 cameras.
+	  These are supported as stillcams in libgphoto2/camlibs/sonix.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_sn9c2028.
+
+config USB_GSPCA_SN9C20X
+	tristate "SN9C20X USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the
+	  sn9c20x chips (SN9C201 and SN9C202).
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_sn9c20x.
+
+config USB_GSPCA_SONIXB
+	tristate "SONIX Bayer USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the Sonix
+	  chips with Bayer format (SN9C101, SN9C102 and SN9C103).
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_sonixb.
+
+config USB_GSPCA_SONIXJ
+	tristate "SONIX JPEG USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the Sonix
+	  chips with JPEG format (SN9C102P, SN9C105 and >= SN9C110).
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_sonixj
+
+config USB_GSPCA_SPCA500
+	tristate "SPCA500 USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the SPCA500 chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_spca500.
+
+config USB_GSPCA_SPCA501
+	tristate "SPCA501 USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the SPCA501 chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_spca501.
+
+config USB_GSPCA_SPCA505
+	tristate "SPCA505 USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the SPCA505 chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_spca505.
+
+config USB_GSPCA_SPCA506
+	tristate "SPCA506 USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the SPCA506 chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_spca506.
+
+config USB_GSPCA_SPCA508
+	tristate "SPCA508 USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the SPCA508 chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_spca508.
+
+config USB_GSPCA_SPCA561
+	tristate "SPCA561 USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the SPCA561 chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_spca561.
+
+config USB_GSPCA_SPCA1528
+	tristate "SPCA1528 USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the SPCA1528 chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_spca1528.
+
+config USB_GSPCA_SQ905
+	tristate "SQ Technologies SQ905 based USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the SQ905 chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_sq905.
+
+config USB_GSPCA_SQ905C
+	tristate "SQ Technologies SQ905C based USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the SQ905C chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_sq905c.
+
+config USB_GSPCA_SQ930X
+	tristate "SQ Technologies SQ930X based USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the SQ930X chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_sq930x.
+
+config USB_GSPCA_STK014
+	tristate "Syntek DV4000 (STK014) USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the STK014 chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_stk014.
+
+config USB_GSPCA_STK1135
+	tristate "Syntek STK1135 USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the STK1135 chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_stk1135.
+
+config USB_GSPCA_STV0680
+	tristate "STV0680 USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the STV0680 chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_stv0680.
+
+config USB_GSPCA_SUNPLUS
+	tristate "SUNPLUS USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the Sunplus
+	  SPCA504(abc) SPCA533 SPCA536 chips.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_sunplus.
+
+config USB_GSPCA_T613
+	tristate "T613 (JPEG Compliance) USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the T613 chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_t613.
+
+config USB_GSPCA_TOPRO
+	tristate "TOPRO USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the
+	  TP6800 and TP6810 Topro chips.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_topro.
+
+config USB_GSPCA_TOUPTEK
+	tristate "Touptek USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the ToupTek UCMOS
+	  / AmScope MU series camera.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_touptek.
+
+config USB_GSPCA_TV8532
+	tristate "TV8532 USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the TV8531 chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_tv8532.
+
+config USB_GSPCA_VC032X
+	tristate "VC032X USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the VC032X chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_vc032x.
+
+config USB_GSPCA_VICAM
+	tristate "ViCam USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for the 3com homeconnect camera
+	  (vicam).
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_vicam.
+
+config USB_GSPCA_XIRLINK_CIT
+	tristate "Xirlink C-It USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for Xirlink C-It bases cameras.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_xirlink_cit.
+
+config USB_GSPCA_ZC3XX
+	tristate "ZC3XX USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the ZC3XX chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_zc3xx.
+
+endif
diff --git a/drivers/media/usb/gspca/Makefile b/drivers/media/usb/gspca/Makefile
new file mode 100644
index 0000000..3e3ecbf
--- /dev/null
+++ b/drivers/media/usb/gspca/Makefile
@@ -0,0 +1,100 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_USB_GSPCA)          += gspca_main.o
+obj-$(CONFIG_USB_GSPCA_BENQ)     += gspca_benq.o
+obj-$(CONFIG_USB_GSPCA_CONEX)    += gspca_conex.o
+obj-$(CONFIG_USB_GSPCA_CPIA1)    += gspca_cpia1.o
+obj-$(CONFIG_USB_GSPCA_DTCS033)  += gspca_dtcs033.o
+obj-$(CONFIG_USB_GSPCA_ETOMS)    += gspca_etoms.o
+obj-$(CONFIG_USB_GSPCA_FINEPIX)  += gspca_finepix.o
+obj-$(CONFIG_USB_GSPCA_JEILINJ)  += gspca_jeilinj.o
+obj-$(CONFIG_USB_GSPCA_JL2005BCD) += gspca_jl2005bcd.o
+obj-$(CONFIG_USB_GSPCA_KINECT)   += gspca_kinect.o
+obj-$(CONFIG_USB_GSPCA_KONICA)   += gspca_konica.o
+obj-$(CONFIG_USB_GSPCA_MARS)     += gspca_mars.o
+obj-$(CONFIG_USB_GSPCA_MR97310A) += gspca_mr97310a.o
+obj-$(CONFIG_USB_GSPCA_NW80X)    += gspca_nw80x.o
+obj-$(CONFIG_USB_GSPCA_OV519)    += gspca_ov519.o
+obj-$(CONFIG_USB_GSPCA_OV534)    += gspca_ov534.o
+obj-$(CONFIG_USB_GSPCA_OV534_9)  += gspca_ov534_9.o
+obj-$(CONFIG_USB_GSPCA_PAC207)   += gspca_pac207.o
+obj-$(CONFIG_USB_GSPCA_PAC7302)  += gspca_pac7302.o
+obj-$(CONFIG_USB_GSPCA_PAC7311)  += gspca_pac7311.o
+obj-$(CONFIG_USB_GSPCA_SE401)    += gspca_se401.o
+obj-$(CONFIG_USB_GSPCA_SN9C2028) += gspca_sn9c2028.o
+obj-$(CONFIG_USB_GSPCA_SN9C20X)  += gspca_sn9c20x.o
+obj-$(CONFIG_USB_GSPCA_SONIXB)   += gspca_sonixb.o
+obj-$(CONFIG_USB_GSPCA_SONIXJ)   += gspca_sonixj.o
+obj-$(CONFIG_USB_GSPCA_SPCA500)  += gspca_spca500.o
+obj-$(CONFIG_USB_GSPCA_SPCA501)  += gspca_spca501.o
+obj-$(CONFIG_USB_GSPCA_SPCA505)  += gspca_spca505.o
+obj-$(CONFIG_USB_GSPCA_SPCA506)  += gspca_spca506.o
+obj-$(CONFIG_USB_GSPCA_SPCA508)  += gspca_spca508.o
+obj-$(CONFIG_USB_GSPCA_SPCA561)  += gspca_spca561.o
+obj-$(CONFIG_USB_GSPCA_SPCA1528) += gspca_spca1528.o
+obj-$(CONFIG_USB_GSPCA_SQ905)    += gspca_sq905.o
+obj-$(CONFIG_USB_GSPCA_SQ905C)   += gspca_sq905c.o
+obj-$(CONFIG_USB_GSPCA_SQ930X)   += gspca_sq930x.o
+obj-$(CONFIG_USB_GSPCA_SUNPLUS)  += gspca_sunplus.o
+obj-$(CONFIG_USB_GSPCA_STK014)   += gspca_stk014.o
+obj-$(CONFIG_USB_GSPCA_STK1135)  += gspca_stk1135.o
+obj-$(CONFIG_USB_GSPCA_STV0680)  += gspca_stv0680.o
+obj-$(CONFIG_USB_GSPCA_T613)     += gspca_t613.o
+obj-$(CONFIG_USB_GSPCA_TOPRO)    += gspca_topro.o
+obj-$(CONFIG_USB_GSPCA_TOUPTEK)  += gspca_touptek.o
+obj-$(CONFIG_USB_GSPCA_TV8532)   += gspca_tv8532.o
+obj-$(CONFIG_USB_GSPCA_VC032X)   += gspca_vc032x.o
+obj-$(CONFIG_USB_GSPCA_VICAM)    += gspca_vicam.o
+obj-$(CONFIG_USB_GSPCA_XIRLINK_CIT) += gspca_xirlink_cit.o
+obj-$(CONFIG_USB_GSPCA_ZC3XX)    += gspca_zc3xx.o
+
+gspca_main-objs     := gspca.o autogain_functions.o
+gspca_benq-objs     := benq.o
+gspca_conex-objs    := conex.o
+gspca_cpia1-objs    := cpia1.o
+gspca_dtcs033-objs  := dtcs033.o
+gspca_etoms-objs    := etoms.o
+gspca_finepix-objs  := finepix.o
+gspca_jeilinj-objs  := jeilinj.o
+gspca_jl2005bcd-objs  := jl2005bcd.o
+gspca_kinect-objs   := kinect.o
+gspca_konica-objs   := konica.o
+gspca_mars-objs     := mars.o
+gspca_mr97310a-objs := mr97310a.o
+gspca_nw80x-objs    := nw80x.o
+gspca_ov519-objs    := ov519.o
+gspca_ov534-objs    := ov534.o
+gspca_ov534_9-objs  := ov534_9.o
+gspca_pac207-objs   := pac207.o
+gspca_pac7302-objs  := pac7302.o
+gspca_pac7311-objs  := pac7311.o
+gspca_se401-objs    := se401.o
+gspca_sn9c2028-objs := sn9c2028.o
+gspca_sn9c20x-objs  := sn9c20x.o
+gspca_sonixb-objs   := sonixb.o
+gspca_sonixj-objs   := sonixj.o
+gspca_spca500-objs  := spca500.o
+gspca_spca501-objs  := spca501.o
+gspca_spca505-objs  := spca505.o
+gspca_spca506-objs  := spca506.o
+gspca_spca508-objs  := spca508.o
+gspca_spca561-objs  := spca561.o
+gspca_spca1528-objs := spca1528.o
+gspca_sq905-objs    := sq905.o
+gspca_sq905c-objs   := sq905c.o
+gspca_sq930x-objs   := sq930x.o
+gspca_stk014-objs   := stk014.o
+gspca_stk1135-objs  := stk1135.o
+gspca_stv0680-objs  := stv0680.o
+gspca_sunplus-objs  := sunplus.o
+gspca_t613-objs     := t613.o
+gspca_topro-objs    := topro.o
+gspca_touptek-objs  := touptek.o
+gspca_tv8532-objs   := tv8532.o
+gspca_vc032x-objs   := vc032x.o
+gspca_vicam-objs    := vicam.o
+gspca_xirlink_cit-objs := xirlink_cit.o
+gspca_zc3xx-objs    := zc3xx.o
+
+obj-$(CONFIG_USB_M5602)   += m5602/
+obj-$(CONFIG_USB_STV06XX) += stv06xx/
+obj-$(CONFIG_USB_GL860)   += gl860/
diff --git a/drivers/media/usb/gspca/autogain_functions.c b/drivers/media/usb/gspca/autogain_functions.c
new file mode 100644
index 0000000..6dfab2b
--- /dev/null
+++ b/drivers/media/usb/gspca/autogain_functions.c
@@ -0,0 +1,174 @@
+/*
+ * Functions for auto gain.
+ *
+ * Copyright (C) 2010-2012 Hans de Goede <hdegoede@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#include "gspca.h"
+
+/* auto gain and exposure algorithm based on the knee algorithm described here:
+   http://ytse.tricolour.net/docs/LowLightOptimization.html
+
+   Returns 0 if no changes were made, 1 if the gain and or exposure settings
+   where changed. */
+int gspca_expo_autogain(
+			struct gspca_dev *gspca_dev,
+			int avg_lum,
+			int desired_avg_lum,
+			int deadzone,
+			int gain_knee,
+			int exposure_knee)
+{
+	s32 gain, orig_gain, exposure, orig_exposure;
+	int i, steps, retval = 0;
+
+	if (v4l2_ctrl_g_ctrl(gspca_dev->autogain) == 0)
+		return 0;
+
+	orig_gain = gain = v4l2_ctrl_g_ctrl(gspca_dev->gain);
+	orig_exposure = exposure = v4l2_ctrl_g_ctrl(gspca_dev->exposure);
+
+	/* If we are of a multiple of deadzone, do multiple steps to reach the
+	   desired lumination fast (with the risc of a slight overshoot) */
+	steps = abs(desired_avg_lum - avg_lum) / deadzone;
+
+	gspca_dbg(gspca_dev, D_FRAM, "autogain: lum: %d, desired: %d, steps: %d\n",
+		  avg_lum, desired_avg_lum, steps);
+
+	for (i = 0; i < steps; i++) {
+		if (avg_lum > desired_avg_lum) {
+			if (gain > gain_knee)
+				gain--;
+			else if (exposure > exposure_knee)
+				exposure--;
+			else if (gain > gspca_dev->gain->default_value)
+				gain--;
+			else if (exposure > gspca_dev->exposure->minimum)
+				exposure--;
+			else if (gain > gspca_dev->gain->minimum)
+				gain--;
+			else
+				break;
+		} else {
+			if (gain < gspca_dev->gain->default_value)
+				gain++;
+			else if (exposure < exposure_knee)
+				exposure++;
+			else if (gain < gain_knee)
+				gain++;
+			else if (exposure < gspca_dev->exposure->maximum)
+				exposure++;
+			else if (gain < gspca_dev->gain->maximum)
+				gain++;
+			else
+				break;
+		}
+	}
+
+	if (gain != orig_gain) {
+		v4l2_ctrl_s_ctrl(gspca_dev->gain, gain);
+		retval = 1;
+	}
+	if (exposure != orig_exposure) {
+		v4l2_ctrl_s_ctrl(gspca_dev->exposure, exposure);
+		retval = 1;
+	}
+
+	if (retval)
+		gspca_dbg(gspca_dev, D_FRAM, "autogain: changed gain: %d, expo: %d\n",
+			  gain, exposure);
+	return retval;
+}
+EXPORT_SYMBOL(gspca_expo_autogain);
+
+/* Autogain + exposure algorithm for cameras with a coarse exposure control
+   (usually this means we can only control the clockdiv to change exposure)
+   As changing the clockdiv so that the fps drops from 30 to 15 fps for
+   example, will lead to a huge exposure change (it effectively doubles),
+   this algorithm normally tries to only adjust the gain (between 40 and
+   80 %) and if that does not help, only then changes exposure. This leads
+   to a much more stable image then using the knee algorithm which at
+   certain points of the knee graph will only try to adjust exposure,
+   which leads to oscilating as one exposure step is huge.
+
+   Returns 0 if no changes were made, 1 if the gain and or exposure settings
+   where changed. */
+int gspca_coarse_grained_expo_autogain(
+			struct gspca_dev *gspca_dev,
+			int avg_lum,
+			int desired_avg_lum,
+			int deadzone)
+{
+	s32 gain_low, gain_high, gain, orig_gain, exposure, orig_exposure;
+	int steps, retval = 0;
+
+	if (v4l2_ctrl_g_ctrl(gspca_dev->autogain) == 0)
+		return 0;
+
+	orig_gain = gain = v4l2_ctrl_g_ctrl(gspca_dev->gain);
+	orig_exposure = exposure = v4l2_ctrl_g_ctrl(gspca_dev->exposure);
+
+	gain_low  = (s32)(gspca_dev->gain->maximum - gspca_dev->gain->minimum) /
+		    5 * 2 + gspca_dev->gain->minimum;
+	gain_high = (s32)(gspca_dev->gain->maximum - gspca_dev->gain->minimum) /
+		    5 * 4 + gspca_dev->gain->minimum;
+
+	/* If we are of a multiple of deadzone, do multiple steps to reach the
+	   desired lumination fast (with the risc of a slight overshoot) */
+	steps = (desired_avg_lum - avg_lum) / deadzone;
+
+	gspca_dbg(gspca_dev, D_FRAM, "autogain: lum: %d, desired: %d, steps: %d\n",
+		  avg_lum, desired_avg_lum, steps);
+
+	if ((gain + steps) > gain_high &&
+	    exposure < gspca_dev->exposure->maximum) {
+		gain = gain_high;
+		gspca_dev->exp_too_low_cnt++;
+		gspca_dev->exp_too_high_cnt = 0;
+	} else if ((gain + steps) < gain_low &&
+		   exposure > gspca_dev->exposure->minimum) {
+		gain = gain_low;
+		gspca_dev->exp_too_high_cnt++;
+		gspca_dev->exp_too_low_cnt = 0;
+	} else {
+		gain += steps;
+		if (gain > gspca_dev->gain->maximum)
+			gain = gspca_dev->gain->maximum;
+		else if (gain < gspca_dev->gain->minimum)
+			gain = gspca_dev->gain->minimum;
+		gspca_dev->exp_too_high_cnt = 0;
+		gspca_dev->exp_too_low_cnt = 0;
+	}
+
+	if (gspca_dev->exp_too_high_cnt > 3) {
+		exposure--;
+		gspca_dev->exp_too_high_cnt = 0;
+	} else if (gspca_dev->exp_too_low_cnt > 3) {
+		exposure++;
+		gspca_dev->exp_too_low_cnt = 0;
+	}
+
+	if (gain != orig_gain) {
+		v4l2_ctrl_s_ctrl(gspca_dev->gain, gain);
+		retval = 1;
+	}
+	if (exposure != orig_exposure) {
+		v4l2_ctrl_s_ctrl(gspca_dev->exposure, exposure);
+		retval = 1;
+	}
+
+	if (retval)
+		gspca_dbg(gspca_dev, D_FRAM, "autogain: changed gain: %d, expo: %d\n",
+			  gain, exposure);
+	return retval;
+}
+EXPORT_SYMBOL(gspca_coarse_grained_expo_autogain);
diff --git a/drivers/media/usb/gspca/benq.c b/drivers/media/usb/gspca/benq.c
new file mode 100644
index 0000000..8a8db5e
--- /dev/null
+++ b/drivers/media/usb/gspca/benq.c
@@ -0,0 +1,283 @@
+/*
+ * Benq DC E300 subdriver
+ *
+ * Copyright (C) 2009 Jean-Francois Moine (http://moinejf.free.fr)
+ *
+ * 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
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "benq"
+
+#include "gspca.h"
+
+MODULE_AUTHOR("Jean-Francois Moine <http://moinejf.free.fr>");
+MODULE_DESCRIPTION("Benq DC E300 USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+};
+
+static const struct v4l2_pix_format vga_mode[] = {
+	{320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG},
+};
+
+static void sd_isoc_irq(struct urb *urb);
+
+/* -- write a register -- */
+static void reg_w(struct gspca_dev *gspca_dev,
+			u16 value, u16 index)
+{
+	struct usb_device *dev = gspca_dev->dev;
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+			0x02,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			value,
+			index,
+			NULL,
+			0,
+			500);
+	if (ret < 0) {
+		pr_err("reg_w err %d\n", ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	gspca_dev->cam.cam_mode = vga_mode;
+	gspca_dev->cam.nmodes = ARRAY_SIZE(vga_mode);
+	gspca_dev->cam.no_urb_create = 1;
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	return 0;
+}
+
+/* -- start the camera -- */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct urb *urb;
+	int i, n;
+
+	/* create 4 URBs - 2 on endpoint 0x83 and 2 on 0x082 */
+#if MAX_NURBS < 4
+#error "Not enough URBs in the gspca table"
+#endif
+#define SD_PKT_SZ 64
+#define SD_NPKT 32
+	for (n = 0; n < 4; n++) {
+		urb = usb_alloc_urb(SD_NPKT, GFP_KERNEL);
+		if (!urb)
+			return -ENOMEM;
+		gspca_dev->urb[n] = urb;
+		urb->transfer_buffer = usb_alloc_coherent(gspca_dev->dev,
+						SD_PKT_SZ * SD_NPKT,
+						GFP_KERNEL,
+						&urb->transfer_dma);
+
+		if (urb->transfer_buffer == NULL) {
+			pr_err("usb_alloc_coherent failed\n");
+			return -ENOMEM;
+		}
+		urb->dev = gspca_dev->dev;
+		urb->context = gspca_dev;
+		urb->transfer_buffer_length = SD_PKT_SZ * SD_NPKT;
+		urb->pipe = usb_rcvisocpipe(gspca_dev->dev,
+					n & 1 ? 0x82 : 0x83);
+		urb->transfer_flags = URB_ISO_ASAP
+					| URB_NO_TRANSFER_DMA_MAP;
+		urb->interval = 1;
+		urb->complete = sd_isoc_irq;
+		urb->number_of_packets = SD_NPKT;
+		for (i = 0; i < SD_NPKT; i++) {
+			urb->iso_frame_desc[i].length = SD_PKT_SZ;
+			urb->iso_frame_desc[i].offset = SD_PKT_SZ * i;
+		}
+	}
+
+	return gspca_dev->usb_err;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	struct usb_interface *intf;
+
+	reg_w(gspca_dev, 0x003c, 0x0003);
+	reg_w(gspca_dev, 0x003c, 0x0004);
+	reg_w(gspca_dev, 0x003c, 0x0005);
+	reg_w(gspca_dev, 0x003c, 0x0006);
+	reg_w(gspca_dev, 0x003c, 0x0007);
+
+	intf = usb_ifnum_to_if(gspca_dev->dev, gspca_dev->iface);
+	usb_set_interface(gspca_dev->dev, gspca_dev->iface,
+					intf->num_altsetting - 1);
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,		/* isoc packet */
+			int len)		/* iso packet length */
+{
+	/* unused */
+}
+
+/* reception of an URB */
+static void sd_isoc_irq(struct urb *urb)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *) urb->context;
+	struct urb *urb0;
+	u8 *data;
+	int i, st;
+
+	gspca_dbg(gspca_dev, D_PACK, "sd isoc irq\n");
+	if (!gspca_dev->streaming)
+		return;
+	if (urb->status != 0) {
+		if (urb->status == -ESHUTDOWN)
+			return;		/* disconnection */
+#ifdef CONFIG_PM
+		if (gspca_dev->frozen)
+			return;
+#endif
+		pr_err("urb status: %d\n", urb->status);
+		return;
+	}
+
+	/* if this is a control URN (ep 0x83), wait */
+	if (urb == gspca_dev->urb[0] || urb == gspca_dev->urb[2])
+		return;
+
+	/* scan both received URBs */
+	if (urb == gspca_dev->urb[1])
+		urb0 = gspca_dev->urb[0];
+	else
+		urb0 = gspca_dev->urb[2];
+	for (i = 0; i < urb->number_of_packets; i++) {
+
+		/* check the packet status and length */
+		if (urb0->iso_frame_desc[i].actual_length != SD_PKT_SZ
+		    || urb->iso_frame_desc[i].actual_length != SD_PKT_SZ) {
+			gspca_err(gspca_dev, "ISOC bad lengths %d / %d\n",
+				  urb0->iso_frame_desc[i].actual_length,
+				  urb->iso_frame_desc[i].actual_length);
+			gspca_dev->last_packet_type = DISCARD_PACKET;
+			continue;
+		}
+		st = urb0->iso_frame_desc[i].status;
+		if (st == 0)
+			st = urb->iso_frame_desc[i].status;
+		if (st) {
+			pr_err("ISOC data error: [%d] status=%d\n",
+				i, st);
+			gspca_dev->last_packet_type = DISCARD_PACKET;
+			continue;
+		}
+
+		/*
+		 * The images are received in URBs of different endpoints
+		 * (0x83 and 0x82).
+		 * Image pieces in URBs of ep 0x83 are continuated in URBs of
+		 * ep 0x82 of the same index.
+		 * The packets in the URBs of endpoint 0x83 start with:
+		 *	- 80 ba/bb 00 00 = start of image followed by 'ff d8'
+		 *	- 04 ba/bb oo oo = image piece
+		 *		where 'oo oo' is the image offset
+						(not cheked)
+		 *	- (other -> bad frame)
+		 * The images are JPEG encoded with full header and
+		 * normal ff escape.
+		 * The end of image ('ff d9') may occur in any URB.
+		 * (not cheked)
+		 */
+		data = (u8 *) urb0->transfer_buffer
+					+ urb0->iso_frame_desc[i].offset;
+		if (data[0] == 0x80 && (data[1] & 0xfe) == 0xba) {
+
+			/* new image */
+			gspca_frame_add(gspca_dev, LAST_PACKET,
+					NULL, 0);
+			gspca_frame_add(gspca_dev, FIRST_PACKET,
+					data + 4, SD_PKT_SZ - 4);
+		} else if (data[0] == 0x04 && (data[1] & 0xfe) == 0xba) {
+			gspca_frame_add(gspca_dev, INTER_PACKET,
+					data + 4, SD_PKT_SZ - 4);
+		} else {
+			gspca_dev->last_packet_type = DISCARD_PACKET;
+			continue;
+		}
+		data = (u8 *) urb->transfer_buffer
+					+ urb->iso_frame_desc[i].offset;
+		gspca_frame_add(gspca_dev, INTER_PACKET,
+				data, SD_PKT_SZ);
+	}
+
+	/* resubmit the URBs */
+	st = usb_submit_urb(urb0, GFP_ATOMIC);
+	if (st < 0)
+		pr_err("usb_submit_urb(0) ret %d\n", st);
+	st = usb_submit_urb(urb, GFP_ATOMIC);
+	if (st < 0)
+		pr_err("usb_submit_urb() ret %d\n", st);
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.pkt_scan = sd_pkt_scan,
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x04a5, 0x3035)},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/conex.c b/drivers/media/usb/gspca/conex.c
new file mode 100644
index 0000000..6df4e20
--- /dev/null
+++ b/drivers/media/usb/gspca/conex.c
@@ -0,0 +1,962 @@
+/*
+ *		Connexant Cx11646 library
+ *		Copyright (C) 2004 Michel Xhaard mxhaard@magic.fr
+ *
+ * V4L2 by Jean-Francois Moine <http://moinejf.free.fr>
+ *
+ * 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
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "conex"
+
+#include "gspca.h"
+#define CONEX_CAM 1		/* special JPEG header */
+#include "jpeg.h"
+
+MODULE_AUTHOR("Michel Xhaard <mxhaard@users.sourceforge.net>");
+MODULE_DESCRIPTION("GSPCA USB Conexant Camera Driver");
+MODULE_LICENSE("GPL");
+
+#define QUALITY 50
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+	struct v4l2_ctrl *brightness;
+	struct v4l2_ctrl *contrast;
+	struct v4l2_ctrl *sat;
+
+	u8 jpeg_hdr[JPEG_HDR_SZ];
+};
+
+static const struct v4l2_pix_format vga_mode[] = {
+	{176, 144, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 176,
+		.sizeimage = 176 * 144 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 3},
+	{320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 2},
+	{352, 288, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 352,
+		.sizeimage = 352 * 288 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 1},
+	{640, 480, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 0},
+};
+
+/* the read bytes are found in gspca_dev->usb_buf */
+static void reg_r(struct gspca_dev *gspca_dev,
+		  __u16 index,
+		  __u16 len)
+{
+	struct usb_device *dev = gspca_dev->dev;
+
+	if (len > USB_BUF_SZ) {
+		gspca_err(gspca_dev, "reg_r: buffer overflow\n");
+		return;
+	}
+
+	usb_control_msg(dev,
+			usb_rcvctrlpipe(dev, 0),
+			0,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0,
+			index, gspca_dev->usb_buf, len,
+			500);
+	gspca_dbg(gspca_dev, D_USBI, "reg read [%02x] -> %02x ..\n",
+		  index, gspca_dev->usb_buf[0]);
+}
+
+/* the bytes to write are in gspca_dev->usb_buf */
+static void reg_w_val(struct gspca_dev *gspca_dev,
+			__u16 index,
+			__u8 val)
+{
+	struct usb_device *dev = gspca_dev->dev;
+
+	gspca_dev->usb_buf[0] = val;
+	usb_control_msg(dev,
+			usb_sndctrlpipe(dev, 0),
+			0,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0,
+			index, gspca_dev->usb_buf, 1, 500);
+}
+
+static void reg_w(struct gspca_dev *gspca_dev,
+		  __u16 index,
+		  const __u8 *buffer,
+		  __u16 len)
+{
+	struct usb_device *dev = gspca_dev->dev;
+
+	if (len > USB_BUF_SZ) {
+		gspca_err(gspca_dev, "reg_w: buffer overflow\n");
+		return;
+	}
+	gspca_dbg(gspca_dev, D_USBO, "reg write [%02x] = %02x..\n",
+		  index, *buffer);
+
+	memcpy(gspca_dev->usb_buf, buffer, len);
+	usb_control_msg(dev,
+			usb_sndctrlpipe(dev, 0),
+			0,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0,
+			index, gspca_dev->usb_buf, len, 500);
+}
+
+static const __u8 cx_sensor_init[][4] = {
+	{0x88, 0x11, 0x01, 0x01},
+	{0x88, 0x12, 0x70, 0x01},
+	{0x88, 0x0f, 0x00, 0x01},
+	{0x88, 0x05, 0x01, 0x01},
+	{}
+};
+
+static const __u8 cx11646_fw1[][3] = {
+	{0x00, 0x02, 0x00},
+	{0x01, 0x43, 0x00},
+	{0x02, 0xA7, 0x00},
+	{0x03, 0x8B, 0x01},
+	{0x04, 0xE9, 0x02},
+	{0x05, 0x08, 0x04},
+	{0x06, 0x08, 0x05},
+	{0x07, 0x07, 0x06},
+	{0x08, 0xE7, 0x06},
+	{0x09, 0xC6, 0x07},
+	{0x0A, 0x86, 0x08},
+	{0x0B, 0x46, 0x09},
+	{0x0C, 0x05, 0x0A},
+	{0x0D, 0xA5, 0x0A},
+	{0x0E, 0x45, 0x0B},
+	{0x0F, 0xE5, 0x0B},
+	{0x10, 0x85, 0x0C},
+	{0x11, 0x25, 0x0D},
+	{0x12, 0xC4, 0x0D},
+	{0x13, 0x45, 0x0E},
+	{0x14, 0xE4, 0x0E},
+	{0x15, 0x64, 0x0F},
+	{0x16, 0xE4, 0x0F},
+	{0x17, 0x64, 0x10},
+	{0x18, 0xE4, 0x10},
+	{0x19, 0x64, 0x11},
+	{0x1A, 0xE4, 0x11},
+	{0x1B, 0x64, 0x12},
+	{0x1C, 0xE3, 0x12},
+	{0x1D, 0x44, 0x13},
+	{0x1E, 0xC3, 0x13},
+	{0x1F, 0x24, 0x14},
+	{0x20, 0xA3, 0x14},
+	{0x21, 0x04, 0x15},
+	{0x22, 0x83, 0x15},
+	{0x23, 0xE3, 0x15},
+	{0x24, 0x43, 0x16},
+	{0x25, 0xA4, 0x16},
+	{0x26, 0x23, 0x17},
+	{0x27, 0x83, 0x17},
+	{0x28, 0xE3, 0x17},
+	{0x29, 0x43, 0x18},
+	{0x2A, 0xA3, 0x18},
+	{0x2B, 0x03, 0x19},
+	{0x2C, 0x63, 0x19},
+	{0x2D, 0xC3, 0x19},
+	{0x2E, 0x22, 0x1A},
+	{0x2F, 0x63, 0x1A},
+	{0x30, 0xC3, 0x1A},
+	{0x31, 0x23, 0x1B},
+	{0x32, 0x83, 0x1B},
+	{0x33, 0xE2, 0x1B},
+	{0x34, 0x23, 0x1C},
+	{0x35, 0x83, 0x1C},
+	{0x36, 0xE2, 0x1C},
+	{0x37, 0x23, 0x1D},
+	{0x38, 0x83, 0x1D},
+	{0x39, 0xE2, 0x1D},
+	{0x3A, 0x23, 0x1E},
+	{0x3B, 0x82, 0x1E},
+	{0x3C, 0xC3, 0x1E},
+	{0x3D, 0x22, 0x1F},
+	{0x3E, 0x63, 0x1F},
+	{0x3F, 0xC1, 0x1F},
+	{}
+};
+static void cx11646_fw(struct gspca_dev*gspca_dev)
+{
+	int i = 0;
+
+	reg_w_val(gspca_dev, 0x006a, 0x02);
+	while (cx11646_fw1[i][1]) {
+		reg_w(gspca_dev, 0x006b, cx11646_fw1[i], 3);
+		i++;
+	}
+	reg_w_val(gspca_dev, 0x006a, 0x00);
+}
+
+static const __u8 cxsensor[] = {
+	0x88, 0x12, 0x70, 0x01,
+	0x88, 0x0d, 0x02, 0x01,
+	0x88, 0x0f, 0x00, 0x01,
+	0x88, 0x03, 0x71, 0x01, 0x88, 0x04, 0x00, 0x01,	/* 3 */
+	0x88, 0x02, 0x10, 0x01,
+	0x88, 0x00, 0xD4, 0x01, 0x88, 0x01, 0x01, 0x01,	/* 5 */
+	0x88, 0x0B, 0x00, 0x01,
+	0x88, 0x0A, 0x0A, 0x01,
+	0x88, 0x00, 0x08, 0x01, 0x88, 0x01, 0x00, 0x01,	/* 8 */
+	0x88, 0x05, 0x01, 0x01,
+	0xA1, 0x18, 0x00, 0x01,
+	0x00
+};
+
+static const __u8 reg20[] = { 0x10, 0x42, 0x81, 0x19, 0xd3, 0xff, 0xa7, 0xff };
+static const __u8 reg28[] = { 0x87, 0x00, 0x87, 0x00, 0x8f, 0xff, 0xea, 0xff };
+static const __u8 reg10[] = { 0xb1, 0xb1 };
+static const __u8 reg71a[] = { 0x08, 0x18, 0x0a, 0x1e };	/* 640 */
+static const __u8 reg71b[] = { 0x04, 0x0c, 0x05, 0x0f };
+	/* 352{0x04,0x0a,0x06,0x12}; //352{0x05,0x0e,0x06,0x11}; //352 */
+static const __u8 reg71c[] = { 0x02, 0x07, 0x03, 0x09 };
+					/* 320{0x04,0x0c,0x05,0x0f}; //320 */
+static const __u8 reg71d[] = { 0x02, 0x07, 0x03, 0x09 };	/* 176 */
+static const __u8 reg7b[] = { 0x00, 0xff, 0x00, 0xff, 0x00, 0xff };
+
+static void cx_sensor(struct gspca_dev*gspca_dev)
+{
+	int i = 0;
+	int length;
+	const __u8 *ptsensor = cxsensor;
+
+	reg_w(gspca_dev, 0x0020, reg20, 8);
+	reg_w(gspca_dev, 0x0028, reg28, 8);
+	reg_w(gspca_dev, 0x0010, reg10, 2);
+	reg_w_val(gspca_dev, 0x0092, 0x03);
+
+	switch (gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv) {
+	case 0:
+		reg_w(gspca_dev, 0x0071, reg71a, 4);
+		break;
+	case 1:
+		reg_w(gspca_dev, 0x0071, reg71b, 4);
+		break;
+	default:
+/*	case 2: */
+		reg_w(gspca_dev, 0x0071, reg71c, 4);
+		break;
+	case 3:
+		reg_w(gspca_dev, 0x0071, reg71d, 4);
+		break;
+	}
+	reg_w(gspca_dev, 0x007b, reg7b, 6);
+	reg_w_val(gspca_dev, 0x00f8, 0x00);
+	reg_w(gspca_dev, 0x0010, reg10, 2);
+	reg_w_val(gspca_dev, 0x0098, 0x41);
+	for (i = 0; i < 11; i++) {
+		if (i == 3 || i == 5 || i == 8)
+			length = 8;
+		else
+			length = 4;
+		reg_w(gspca_dev, 0x00e5, ptsensor, length);
+		if (length == 4)
+			reg_r(gspca_dev, 0x00e8, 1);
+		else
+			reg_r(gspca_dev, 0x00e8, length);
+		ptsensor += length;
+	}
+	reg_r(gspca_dev, 0x00e7, 8);
+}
+
+static const __u8 cx_inits_176[] = {
+	0x33, 0x81, 0xB0, 0x00, 0x90, 0x00, 0x0A, 0x03,	/* 176x144 */
+	0x00, 0x03, 0x03, 0x03, 0x1B, 0x05, 0x30, 0x03,
+	0x65, 0x15, 0x18, 0x25, 0x03, 0x25, 0x08, 0x30,
+	0x3B, 0x25, 0x10, 0x00, 0x04, 0x00, 0x00, 0x00,
+	0xDC, 0xFF, 0xEE, 0xFF, 0xC5, 0xFF, 0xBF, 0xFF,
+	0xF7, 0xFF, 0x88, 0xFF, 0x66, 0x02, 0x28, 0x02,
+	0x1E, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+static const __u8 cx_inits_320[] = {
+	0x7f, 0x7f, 0x40, 0x01, 0xf0, 0x00, 0x02, 0x01,
+	0x00, 0x01, 0x01, 0x01, 0x10, 0x00, 0x02, 0x01,
+	0x65, 0x45, 0xfa, 0x4c, 0x2c, 0xdf, 0xb9, 0x81,
+	0x30, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+	0xe2, 0xff, 0xf1, 0xff, 0xc2, 0xff, 0xbc, 0xff,
+	0xf5, 0xff, 0x6d, 0xff, 0xf6, 0x01, 0x43, 0x02,
+	0xd3, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+static const __u8 cx_inits_352[] = {
+	0x2e, 0x7c, 0x60, 0x01, 0x20, 0x01, 0x05, 0x03,
+	0x00, 0x06, 0x03, 0x06, 0x1b, 0x10, 0x05, 0x3b,
+	0x30, 0x25, 0x18, 0x25, 0x08, 0x30, 0x03, 0x25,
+	0x3b, 0x30, 0x25, 0x1b, 0x10, 0x05, 0x00, 0x00,
+	0xe3, 0xff, 0xf1, 0xff, 0xc2, 0xff, 0xbc, 0xff,
+	0xf5, 0xff, 0x6b, 0xff, 0xee, 0x01, 0x43, 0x02,
+	0xe4, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+static const __u8 cx_inits_640[] = {
+	0x7e, 0x7e, 0x80, 0x02, 0xe0, 0x01, 0x01, 0x01,
+	0x00, 0x02, 0x01, 0x02, 0x10, 0x30, 0x01, 0x01,
+	0x65, 0x45, 0xf7, 0x52, 0x2c, 0xdf, 0xb9, 0x81,
+	0x30, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+	0xe2, 0xff, 0xf1, 0xff, 0xc2, 0xff, 0xbc, 0xff,
+	0xf6, 0xff, 0x7b, 0xff, 0x01, 0x02, 0x43, 0x02,
+	0x77, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+static void cx11646_initsize(struct gspca_dev *gspca_dev)
+{
+	const __u8 *cxinit;
+	static const __u8 reg12[] = { 0x08, 0x05, 0x07, 0x04, 0x24 };
+	static const __u8 reg17[] =
+			{ 0x0a, 0x00, 0xf2, 0x01, 0x0f, 0x00, 0x97, 0x02 };
+
+	switch (gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv) {
+	case 0:
+		cxinit = cx_inits_640;
+		break;
+	case 1:
+		cxinit = cx_inits_352;
+		break;
+	default:
+/*	case 2: */
+		cxinit = cx_inits_320;
+		break;
+	case 3:
+		cxinit = cx_inits_176;
+		break;
+	}
+	reg_w_val(gspca_dev, 0x009a, 0x01);
+	reg_w_val(gspca_dev, 0x0010, 0x10);
+	reg_w(gspca_dev, 0x0012, reg12, 5);
+	reg_w(gspca_dev, 0x0017, reg17, 8);
+	reg_w_val(gspca_dev, 0x00c0, 0x00);
+	reg_w_val(gspca_dev, 0x00c1, 0x04);
+	reg_w_val(gspca_dev, 0x00c2, 0x04);
+
+	reg_w(gspca_dev, 0x0061, cxinit, 8);
+	cxinit += 8;
+	reg_w(gspca_dev, 0x00ca, cxinit, 8);
+	cxinit += 8;
+	reg_w(gspca_dev, 0x00d2, cxinit, 8);
+	cxinit += 8;
+	reg_w(gspca_dev, 0x00da, cxinit, 6);
+	cxinit += 8;
+	reg_w(gspca_dev, 0x0041, cxinit, 8);
+	cxinit += 8;
+	reg_w(gspca_dev, 0x0049, cxinit, 8);
+	cxinit += 8;
+	reg_w(gspca_dev, 0x0051, cxinit, 2);
+
+	reg_r(gspca_dev, 0x0010, 1);
+}
+
+static const __u8 cx_jpeg_init[][8] = {
+	{0xff, 0xd8, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x15},	/* 1 */
+	{0x0f, 0x10, 0x12, 0x10, 0x0d, 0x15, 0x12, 0x11},
+	{0x12, 0x18, 0x16, 0x15, 0x19, 0x20, 0x35, 0x22},
+	{0x20, 0x1d, 0x1d, 0x20, 0x41, 0x2e, 0x31, 0x26},
+	{0x35, 0x4d, 0x43, 0x51, 0x4f, 0x4b, 0x43, 0x4a},
+	{0x49, 0x55, 0x5F, 0x79, 0x67, 0x55, 0x5A, 0x73},
+	{0x5B, 0x49, 0x4A, 0x6A, 0x90, 0x6B, 0x73, 0x7D},
+	{0x81, 0x88, 0x89, 0x88, 0x52, 0x66, 0x95, 0xA0},
+	{0x94, 0x84, 0x9E, 0x79, 0x85, 0x88, 0x83, 0x01},
+	{0x15, 0x0F, 0x10, 0x12, 0x10, 0x0D, 0x15, 0x12},
+	{0x11, 0x12, 0x18, 0x16, 0x15, 0x19, 0x20, 0x35},
+	{0x22, 0x20, 0x1D, 0x1D, 0x20, 0x41, 0x2E, 0x31},
+	{0x26, 0x35, 0x4D, 0x43, 0x51, 0x4F, 0x4B, 0x43},
+	{0x4A, 0x49, 0x55, 0x5F, 0x79, 0x67, 0x55, 0x5A},
+	{0x73, 0x5B, 0x49, 0x4A, 0x6A, 0x90, 0x6B, 0x73},
+	{0x7D, 0x81, 0x88, 0x89, 0x88, 0x52, 0x66, 0x95},
+	{0xA0, 0x94, 0x84, 0x9E, 0x79, 0x85, 0x88, 0x83},
+	{0xFF, 0xC4, 0x01, 0xA2, 0x00, 0x00, 0x01, 0x05},
+	{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00},
+	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02},
+	{0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A},
+	{0x0B, 0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01},
+	{0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00},
+	{0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05},
+	{0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x10, 0x00},
+	{0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, 0x05},
+	{0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7D, 0x01},
+	{0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21},
+	{0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 0x22},
+	{0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23},
+	{0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0, 0x24},
+	{0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17},
+	{0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28, 0x29},
+	{0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A},
+	{0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A},
+	{0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A},
+	{0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A},
+	{0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A},
+	{0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A},
+	{0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99},
+	{0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8},
+	{0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7},
+	{0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6},
+	{0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5},
+	{0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2, 0xE3},
+	{0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1},
+	{0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9},
+	{0xFA, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04},
+	{0x03, 0x04, 0x07, 0x05, 0x04, 0x04, 0x00, 0x01},
+	{0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11, 0x04},
+	{0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07},
+	{0x61, 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14},
+	{0x42, 0x91, 0xA1, 0xB1, 0xC1, 0x09, 0x23, 0x33},
+	{0x52, 0xF0, 0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16},
+	{0x24, 0x34, 0xE1, 0x25, 0xF1, 0x17, 0x18, 0x19},
+	{0x1A, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x35, 0x36},
+	{0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46},
+	{0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56},
+	{0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66},
+	{0x67, 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76},
+	{0x77, 0x78, 0x79, 0x7A, 0x82, 0x83, 0x84, 0x85},
+	{0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94},
+	{0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3},
+	{0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2},
+	{0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA},
+	{0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9},
+	{0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8},
+	{0xD9, 0xDA, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7},
+	{0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6},
+	{0xF7, 0xF8, 0xF9, 0xFA, 0xFF, 0x20, 0x00, 0x1F},
+	{0x02, 0x0C, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00},
+	{0x00, 0x00, 0x11, 0x00, 0x11, 0x22, 0x00, 0x22},
+	{0x22, 0x11, 0x22, 0x22, 0x11, 0x33, 0x33, 0x11},
+	{0x44, 0x66, 0x22, 0x55, 0x66, 0xFF, 0xDD, 0x00},
+	{0x04, 0x00, 0x14, 0xFF, 0xC0, 0x00, 0x11, 0x08},
+	{0x00, 0xF0, 0x01, 0x40, 0x03, 0x00, 0x21, 0x00},
+	{0x01, 0x11, 0x01, 0x02, 0x11, 0x01, 0xFF, 0xDA},
+	{0x00, 0x0C, 0x03, 0x00, 0x00, 0x01, 0x11, 0x02},
+	{0x11, 0x00, 0x3F, 0x00, 0xFF, 0xD9, 0x00, 0x00}	/* 79 */
+};
+
+
+static const __u8 cxjpeg_640[][8] = {
+	{0xff, 0xd8, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x10},	/* 1 */
+	{0x0b, 0x0c, 0x0e, 0x0c, 0x0a, 0x10, 0x0e, 0x0d},
+	{0x0e, 0x12, 0x11, 0x10, 0x13, 0x18, 0x28, 0x1a},
+	{0x18, 0x16, 0x16, 0x18, 0x31, 0x23, 0x25, 0x1d},
+	{0x28, 0x3a, 0x33, 0x3D, 0x3C, 0x39, 0x33, 0x38},
+	{0x37, 0x40, 0x48, 0x5C, 0x4E, 0x40, 0x44, 0x57},
+	{0x45, 0x37, 0x38, 0x50, 0x6D, 0x51, 0x57, 0x5F},
+	{0x62, 0x67, 0x68, 0x67, 0x3E, 0x4D, 0x71, 0x79},
+	{0x70, 0x64, 0x78, 0x5C, 0x65, 0x67, 0x63, 0x01},
+	{0x10, 0x0B, 0x0C, 0x0E, 0x0C, 0x0A, 0x10, 0x0E},
+	{0x0D, 0x0E, 0x12, 0x11, 0x10, 0x13, 0x18, 0x28},
+	{0x1A, 0x18, 0x16, 0x16, 0x18, 0x31, 0x23, 0x25},
+	{0x1D, 0x28, 0x3A, 0x33, 0x3D, 0x3C, 0x39, 0x33},
+	{0x38, 0x37, 0x40, 0x48, 0x5C, 0x4E, 0x40, 0x44},
+	{0x57, 0x45, 0x37, 0x38, 0x50, 0x6D, 0x51, 0x57},
+	{0x5F, 0x62, 0x67, 0x68, 0x67, 0x3E, 0x4D, 0x71},
+	{0x79, 0x70, 0x64, 0x78, 0x5C, 0x65, 0x67, 0x63},
+	{0xFF, 0x20, 0x00, 0x1F, 0x00, 0x83, 0x00, 0x00},
+	{0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00},
+	{0x11, 0x22, 0x00, 0x22, 0x22, 0x11, 0x22, 0x22},
+	{0x11, 0x33, 0x33, 0x11, 0x44, 0x66, 0x22, 0x55},
+	{0x66, 0xFF, 0xDD, 0x00, 0x04, 0x00, 0x28, 0xFF},
+	{0xC0, 0x00, 0x11, 0x08, 0x01, 0xE0, 0x02, 0x80},
+	{0x03, 0x00, 0x21, 0x00, 0x01, 0x11, 0x01, 0x02},
+	{0x11, 0x01, 0xFF, 0xDA, 0x00, 0x0C, 0x03, 0x00},
+	{0x00, 0x01, 0x11, 0x02, 0x11, 0x00, 0x3F, 0x00},
+	{0xFF, 0xD9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}	/* 27 */
+};
+static const __u8 cxjpeg_352[][8] = {
+	{0xff, 0xd8, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x0d},
+	{0x09, 0x09, 0x0b, 0x09, 0x08, 0x0D, 0x0b, 0x0a},
+	{0x0b, 0x0e, 0x0d, 0x0d, 0x0f, 0x13, 0x1f, 0x14},
+	{0x13, 0x11, 0x11, 0x13, 0x26, 0x1b, 0x1d, 0x17},
+	{0x1F, 0x2D, 0x28, 0x30, 0x2F, 0x2D, 0x28, 0x2C},
+	{0x2B, 0x32, 0x38, 0x48, 0x3D, 0x32, 0x35, 0x44},
+	{0x36, 0x2B, 0x2C, 0x3F, 0x55, 0x3F, 0x44, 0x4A},
+	{0x4D, 0x50, 0x51, 0x50, 0x30, 0x3C, 0x58, 0x5F},
+	{0x58, 0x4E, 0x5E, 0x48, 0x4F, 0x50, 0x4D, 0x01},
+	{0x0D, 0x09, 0x09, 0x0B, 0x09, 0x08, 0x0D, 0x0B},
+	{0x0A, 0x0B, 0x0E, 0x0D, 0x0D, 0x0F, 0x13, 0x1F},
+	{0x14, 0x13, 0x11, 0x11, 0x13, 0x26, 0x1B, 0x1D},
+	{0x17, 0x1F, 0x2D, 0x28, 0x30, 0x2F, 0x2D, 0x28},
+	{0x2C, 0x2B, 0x32, 0x38, 0x48, 0x3D, 0x32, 0x35},
+	{0x44, 0x36, 0x2B, 0x2C, 0x3F, 0x55, 0x3F, 0x44},
+	{0x4A, 0x4D, 0x50, 0x51, 0x50, 0x30, 0x3C, 0x58},
+	{0x5F, 0x58, 0x4E, 0x5E, 0x48, 0x4F, 0x50, 0x4D},
+	{0xFF, 0x20, 0x00, 0x1F, 0x01, 0x83, 0x00, 0x00},
+	{0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00},
+	{0x11, 0x22, 0x00, 0x22, 0x22, 0x11, 0x22, 0x22},
+	{0x11, 0x33, 0x33, 0x11, 0x44, 0x66, 0x22, 0x55},
+	{0x66, 0xFF, 0xDD, 0x00, 0x04, 0x00, 0x16, 0xFF},
+	{0xC0, 0x00, 0x11, 0x08, 0x01, 0x20, 0x01, 0x60},
+	{0x03, 0x00, 0x21, 0x00, 0x01, 0x11, 0x01, 0x02},
+	{0x11, 0x01, 0xFF, 0xDA, 0x00, 0x0C, 0x03, 0x00},
+	{0x00, 0x01, 0x11, 0x02, 0x11, 0x00, 0x3F, 0x00},
+	{0xFF, 0xD9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
+};
+static const __u8 cxjpeg_320[][8] = {
+	{0xff, 0xd8, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x05},
+	{0x03, 0x04, 0x04, 0x04, 0x03, 0x05, 0x04, 0x04},
+	{0x04, 0x05, 0x05, 0x05, 0x06, 0x07, 0x0c, 0x08},
+	{0x07, 0x07, 0x07, 0x07, 0x0f, 0x0b, 0x0b, 0x09},
+	{0x0C, 0x11, 0x0F, 0x12, 0x12, 0x11, 0x0f, 0x11},
+	{0x11, 0x13, 0x16, 0x1C, 0x17, 0x13, 0x14, 0x1A},
+	{0x15, 0x11, 0x11, 0x18, 0x21, 0x18, 0x1A, 0x1D},
+	{0x1D, 0x1F, 0x1F, 0x1F, 0x13, 0x17, 0x22, 0x24},
+	{0x22, 0x1E, 0x24, 0x1C, 0x1E, 0x1F, 0x1E, 0x01},
+	{0x05, 0x03, 0x04, 0x04, 0x04, 0x03, 0x05, 0x04},
+	{0x04, 0x04, 0x05, 0x05, 0x05, 0x06, 0x07, 0x0C},
+	{0x08, 0x07, 0x07, 0x07, 0x07, 0x0F, 0x0B, 0x0B},
+	{0x09, 0x0C, 0x11, 0x0F, 0x12, 0x12, 0x11, 0x0F},
+	{0x11, 0x11, 0x13, 0x16, 0x1C, 0x17, 0x13, 0x14},
+	{0x1A, 0x15, 0x11, 0x11, 0x18, 0x21, 0x18, 0x1A},
+	{0x1D, 0x1D, 0x1F, 0x1F, 0x1F, 0x13, 0x17, 0x22},
+	{0x24, 0x22, 0x1E, 0x24, 0x1C, 0x1E, 0x1F, 0x1E},
+	{0xFF, 0x20, 0x00, 0x1F, 0x02, 0x0C, 0x00, 0x00},
+	{0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00},
+	{0x11, 0x22, 0x00, 0x22, 0x22, 0x11, 0x22, 0x22},
+	{0x11, 0x33, 0x33, 0x11, 0x44, 0x66, 0x22, 0x55},
+	{0x66, 0xFF, 0xDD, 0x00, 0x04, 0x00, 0x14, 0xFF},
+	{0xC0, 0x00, 0x11, 0x08, 0x00, 0xF0, 0x01, 0x40},
+	{0x03, 0x00, 0x21, 0x00, 0x01, 0x11, 0x01, 0x02},
+	{0x11, 0x01, 0xFF, 0xDA, 0x00, 0x0C, 0x03, 0x00},
+	{0x00, 0x01, 0x11, 0x02, 0x11, 0x00, 0x3F, 0x00},
+	{0xFF, 0xD9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}	/* 27 */
+};
+static const __u8 cxjpeg_176[][8] = {
+	{0xff, 0xd8, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x0d},
+	{0x09, 0x09, 0x0B, 0x09, 0x08, 0x0D, 0x0B, 0x0A},
+	{0x0B, 0x0E, 0x0D, 0x0D, 0x0F, 0x13, 0x1F, 0x14},
+	{0x13, 0x11, 0x11, 0x13, 0x26, 0x1B, 0x1D, 0x17},
+	{0x1F, 0x2D, 0x28, 0x30, 0x2F, 0x2D, 0x28, 0x2C},
+	{0x2B, 0x32, 0x38, 0x48, 0x3D, 0x32, 0x35, 0x44},
+	{0x36, 0x2B, 0x2C, 0x3F, 0x55, 0x3F, 0x44, 0x4A},
+	{0x4D, 0x50, 0x51, 0x50, 0x30, 0x3C, 0x58, 0x5F},
+	{0x58, 0x4E, 0x5E, 0x48, 0x4F, 0x50, 0x4D, 0x01},
+	{0x0D, 0x09, 0x09, 0x0B, 0x09, 0x08, 0x0D, 0x0B},
+	{0x0A, 0x0B, 0x0E, 0x0D, 0x0D, 0x0F, 0x13, 0x1F},
+	{0x14, 0x13, 0x11, 0x11, 0x13, 0x26, 0x1B, 0x1D},
+	{0x17, 0x1F, 0x2D, 0x28, 0x30, 0x2F, 0x2D, 0x28},
+	{0x2C, 0x2B, 0x32, 0x38, 0x48, 0x3D, 0x32, 0x35},
+	{0x44, 0x36, 0x2B, 0x2C, 0x3F, 0x55, 0x3F, 0x44},
+	{0x4A, 0x4D, 0x50, 0x51, 0x50, 0x30, 0x3C, 0x58},
+	{0x5F, 0x58, 0x4E, 0x5E, 0x48, 0x4F, 0x50, 0x4D},
+	{0xFF, 0x20, 0x00, 0x1F, 0x03, 0xA1, 0x00, 0x00},
+	{0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11, 0x00},
+	{0x11, 0x22, 0x00, 0x22, 0x22, 0x11, 0x22, 0x22},
+	{0x11, 0x33, 0x33, 0x11, 0x44, 0x66, 0x22, 0x55},
+	{0x66, 0xFF, 0xDD, 0x00, 0x04, 0x00, 0x0B, 0xFF},
+	{0xC0, 0x00, 0x11, 0x08, 0x00, 0x90, 0x00, 0xB0},
+	{0x03, 0x00, 0x21, 0x00, 0x01, 0x11, 0x01, 0x02},
+	{0x11, 0x01, 0xFF, 0xDA, 0x00, 0x0C, 0x03, 0x00},
+	{0x00, 0x01, 0x11, 0x02, 0x11, 0x00, 0x3F, 0x00},
+	{0xFF, 0xD9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
+};
+/* 640 take with the zcx30x part */
+static const __u8 cxjpeg_qtable[][8] = {
+	{0xff, 0xd8, 0xff, 0xdb, 0x00, 0x84, 0x00, 0x08},
+	{0x06, 0x06, 0x07, 0x06, 0x05, 0x08, 0x07, 0x07},
+	{0x07, 0x09, 0x09, 0x08, 0x0a, 0x0c, 0x14, 0x0a},
+	{0x0c, 0x0b, 0x0b, 0x0c, 0x19, 0x12, 0x13, 0x0f},
+	{0x14, 0x1d, 0x1a, 0x1f, 0x1e, 0x1d, 0x1a, 0x1c},
+	{0x1c, 0x20, 0x24, 0x2e, 0x27, 0x20, 0x22, 0x2c},
+	{0x23, 0x1c, 0x1c, 0x28, 0x37, 0x29, 0x2c, 0x30},
+	{0x31, 0x34, 0x34, 0x34, 0x1f, 0x27, 0x39, 0x3d},
+	{0x38, 0x32, 0x3c, 0x2e, 0x33, 0x34, 0x32, 0x01},
+	{0x09, 0x09, 0x09, 0x0c, 0x0b, 0x0c, 0x18, 0x0a},
+	{0x0a, 0x18, 0x32, 0x21, 0x1c, 0x21, 0x32, 0x32},
+	{0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32},
+	{0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32},
+	{0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32},
+	{0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32},
+	{0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32},
+	{0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32, 0x32},
+	{0xFF, 0xD9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}	/* 18 */
+};
+
+
+static void cx11646_jpegInit(struct gspca_dev*gspca_dev)
+{
+	int i;
+	int length;
+
+	reg_w_val(gspca_dev, 0x00c0, 0x01);
+	reg_w_val(gspca_dev, 0x00c3, 0x00);
+	reg_w_val(gspca_dev, 0x00c0, 0x00);
+	reg_r(gspca_dev, 0x0001, 1);
+	length = 8;
+	for (i = 0; i < 79; i++) {
+		if (i == 78)
+			length = 6;
+		reg_w(gspca_dev, 0x0008, cx_jpeg_init[i], length);
+	}
+	reg_r(gspca_dev, 0x0002, 1);
+	reg_w_val(gspca_dev, 0x0055, 0x14);
+}
+
+static const __u8 reg12[] = { 0x0a, 0x05, 0x07, 0x04, 0x19 };
+static const __u8 regE5_8[] =
+		{ 0x88, 0x00, 0xd4, 0x01, 0x88, 0x01, 0x01, 0x01 };
+static const __u8 regE5a[] = { 0x88, 0x0a, 0x0c, 0x01 };
+static const __u8 regE5b[] = { 0x88, 0x0b, 0x12, 0x01 };
+static const __u8 regE5c[] = { 0x88, 0x05, 0x01, 0x01 };
+static const __u8 reg51[] = { 0x77, 0x03 };
+#define reg70 0x03
+
+static void cx11646_jpeg(struct gspca_dev*gspca_dev)
+{
+	int i;
+	int length;
+	__u8 Reg55;
+	int retry;
+
+	reg_w_val(gspca_dev, 0x00c0, 0x01);
+	reg_w_val(gspca_dev, 0x00c3, 0x00);
+	reg_w_val(gspca_dev, 0x00c0, 0x00);
+	reg_r(gspca_dev, 0x0001, 1);
+	length = 8;
+	switch (gspca_dev->cam.cam_mode[gspca_dev->curr_mode].priv) {
+	case 0:
+		for (i = 0; i < 27; i++) {
+			if (i == 26)
+				length = 2;
+			reg_w(gspca_dev, 0x0008, cxjpeg_640[i], length);
+		}
+		Reg55 = 0x28;
+		break;
+	case 1:
+		for (i = 0; i < 27; i++) {
+			if (i == 26)
+				length = 2;
+			reg_w(gspca_dev, 0x0008, cxjpeg_352[i], length);
+		}
+		Reg55 = 0x16;
+		break;
+	default:
+/*	case 2: */
+		for (i = 0; i < 27; i++) {
+			if (i == 26)
+				length = 2;
+			reg_w(gspca_dev, 0x0008, cxjpeg_320[i], length);
+		}
+		Reg55 = 0x14;
+		break;
+	case 3:
+		for (i = 0; i < 27; i++) {
+			if (i == 26)
+				length = 2;
+			reg_w(gspca_dev, 0x0008, cxjpeg_176[i], length);
+		}
+		Reg55 = 0x0B;
+		break;
+	}
+
+	reg_r(gspca_dev, 0x0002, 1);
+	reg_w_val(gspca_dev, 0x0055, Reg55);
+	reg_r(gspca_dev, 0x0002, 1);
+	reg_w(gspca_dev, 0x0010, reg10, 2);
+	reg_w_val(gspca_dev, 0x0054, 0x02);
+	reg_w_val(gspca_dev, 0x0054, 0x01);
+	reg_w_val(gspca_dev, 0x0000, 0x94);
+	reg_w_val(gspca_dev, 0x0053, 0xc0);
+	reg_w_val(gspca_dev, 0x00fc, 0xe1);
+	reg_w_val(gspca_dev, 0x0000, 0x00);
+	/* wait for completion */
+	retry = 50;
+	do {
+		reg_r(gspca_dev, 0x0002, 1);
+							/* 0x07 until 0x00 */
+		if (gspca_dev->usb_buf[0] == 0x00)
+			break;
+		reg_w_val(gspca_dev, 0x0053, 0x00);
+	} while (--retry);
+	if (retry == 0)
+		gspca_err(gspca_dev, "Damned Errors sending jpeg Table\n");
+	/* send the qtable now */
+	reg_r(gspca_dev, 0x0001, 1);		/* -> 0x18 */
+	length = 8;
+	for (i = 0; i < 18; i++) {
+		if (i == 17)
+			length = 2;
+		reg_w(gspca_dev, 0x0008, cxjpeg_qtable[i], length);
+
+	}
+	reg_r(gspca_dev, 0x0002, 1);	/* 0x00 */
+	reg_r(gspca_dev, 0x0053, 1);	/* 0x00 */
+	reg_w_val(gspca_dev, 0x0054, 0x02);
+	reg_w_val(gspca_dev, 0x0054, 0x01);
+	reg_w_val(gspca_dev, 0x0000, 0x94);
+	reg_w_val(gspca_dev, 0x0053, 0xc0);
+
+	reg_r(gspca_dev, 0x0038, 1);		/* 0x40 */
+	reg_r(gspca_dev, 0x0038, 1);		/* 0x40 */
+	reg_r(gspca_dev, 0x001f, 1);		/* 0x38 */
+	reg_w(gspca_dev, 0x0012, reg12, 5);
+	reg_w(gspca_dev, 0x00e5, regE5_8, 8);
+	reg_r(gspca_dev, 0x00e8, 8);
+	reg_w(gspca_dev, 0x00e5, regE5a, 4);
+	reg_r(gspca_dev, 0x00e8, 1);		/* 0x00 */
+	reg_w_val(gspca_dev, 0x009a, 0x01);
+	reg_w(gspca_dev, 0x00e5, regE5b, 4);
+	reg_r(gspca_dev, 0x00e8, 1);		/* 0x00 */
+	reg_w(gspca_dev, 0x00e5, regE5c, 4);
+	reg_r(gspca_dev, 0x00e8, 1);		/* 0x00 */
+
+	reg_w(gspca_dev, 0x0051, reg51, 2);
+	reg_w(gspca_dev, 0x0010, reg10, 2);
+	reg_w_val(gspca_dev, 0x0070, reg70);
+}
+
+static void cx11646_init1(struct gspca_dev *gspca_dev)
+{
+	int i = 0;
+
+	reg_w_val(gspca_dev, 0x0010, 0x00);
+	reg_w_val(gspca_dev, 0x0053, 0x00);
+	reg_w_val(gspca_dev, 0x0052, 0x00);
+	reg_w_val(gspca_dev, 0x009b, 0x2f);
+	reg_w_val(gspca_dev, 0x009c, 0x10);
+	reg_r(gspca_dev, 0x0098, 1);
+	reg_w_val(gspca_dev, 0x0098, 0x40);
+	reg_r(gspca_dev, 0x0099, 1);
+	reg_w_val(gspca_dev, 0x0099, 0x07);
+	reg_w_val(gspca_dev, 0x0039, 0x40);
+	reg_w_val(gspca_dev, 0x003c, 0xff);
+	reg_w_val(gspca_dev, 0x003f, 0x1f);
+	reg_w_val(gspca_dev, 0x003d, 0x40);
+/*	reg_w_val(gspca_dev, 0x003d, 0x60); */
+	reg_r(gspca_dev, 0x0099, 1);			/* ->0x07 */
+
+	while (cx_sensor_init[i][0]) {
+		reg_w_val(gspca_dev, 0x00e5, cx_sensor_init[i][0]);
+		reg_r(gspca_dev, 0x00e8, 1);		/* -> 0x00 */
+		if (i == 1) {
+			reg_w_val(gspca_dev, 0x00ed, 0x01);
+			reg_r(gspca_dev, 0x00ed, 1);	/* -> 0x01 */
+		}
+		i++;
+	}
+	reg_w_val(gspca_dev, 0x00c3, 0x00);
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	struct cam *cam;
+
+	cam = &gspca_dev->cam;
+	cam->cam_mode = vga_mode;
+	cam->nmodes = ARRAY_SIZE(vga_mode);
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	cx11646_init1(gspca_dev);
+	cx11646_initsize(gspca_dev);
+	cx11646_fw(gspca_dev);
+	cx_sensor(gspca_dev);
+	cx11646_jpegInit(gspca_dev);
+	return 0;
+}
+
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	/* create the JPEG header */
+	jpeg_define(sd->jpeg_hdr, gspca_dev->pixfmt.height,
+			gspca_dev->pixfmt.width,
+			0x22);		/* JPEG 411 */
+	jpeg_set_qual(sd->jpeg_hdr, QUALITY);
+
+	cx11646_initsize(gspca_dev);
+	cx11646_fw(gspca_dev);
+	cx_sensor(gspca_dev);
+	cx11646_jpeg(gspca_dev);
+	return 0;
+}
+
+/* called on streamoff with alt 0 and on disconnect */
+static void sd_stop0(struct gspca_dev *gspca_dev)
+{
+	int retry = 50;
+
+	if (!gspca_dev->present)
+		return;
+	reg_w_val(gspca_dev, 0x0000, 0x00);
+	reg_r(gspca_dev, 0x0002, 1);
+	reg_w_val(gspca_dev, 0x0053, 0x00);
+
+	while (retry--) {
+/*		reg_r(gspca_dev, 0x0002, 1);*/
+		reg_r(gspca_dev, 0x0053, 1);
+		if (gspca_dev->usb_buf[0] == 0)
+			break;
+	}
+	reg_w_val(gspca_dev, 0x0000, 0x00);
+	reg_r(gspca_dev, 0x0002, 1);
+
+	reg_w_val(gspca_dev, 0x0010, 0x00);
+	reg_r(gspca_dev, 0x0033, 1);
+	reg_w_val(gspca_dev, 0x00fc, 0xe0);
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (data[0] == 0xff && data[1] == 0xd8) {
+
+		/* start of frame */
+		gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
+
+		/* put the JPEG header in the new frame */
+		gspca_frame_add(gspca_dev, FIRST_PACKET,
+				sd->jpeg_hdr, JPEG_HDR_SZ);
+		data += 2;
+		len -= 2;
+	}
+	gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+}
+
+static void setbrightness(struct gspca_dev *gspca_dev, s32 val, s32 sat)
+{
+	__u8 regE5cbx[] = { 0x88, 0x00, 0xd4, 0x01, 0x88, 0x01, 0x01, 0x01 };
+	__u8 reg51c[2];
+
+	regE5cbx[2] = val;
+	reg_w(gspca_dev, 0x00e5, regE5cbx, 8);
+	reg_r(gspca_dev, 0x00e8, 8);
+	reg_w(gspca_dev, 0x00e5, regE5c, 4);
+	reg_r(gspca_dev, 0x00e8, 1);		/* 0x00 */
+
+	reg51c[0] = 0x77;
+	reg51c[1] = sat;
+	reg_w(gspca_dev, 0x0051, reg51c, 2);
+	reg_w(gspca_dev, 0x0010, reg10, 2);
+	reg_w_val(gspca_dev, 0x0070, reg70);
+}
+
+static void setcontrast(struct gspca_dev *gspca_dev, s32 val, s32 sat)
+{
+	__u8 regE5acx[] = { 0x88, 0x0a, 0x0c, 0x01 };	/* seem MSB */
+/*	__u8 regE5bcx[] = { 0x88, 0x0b, 0x12, 0x01};	 * LSB */
+	__u8 reg51c[2];
+
+	regE5acx[2] = val;
+	reg_w(gspca_dev, 0x00e5, regE5acx, 4);
+	reg_r(gspca_dev, 0x00e8, 1);		/* 0x00 */
+	reg51c[0] = 0x77;
+	reg51c[1] = sat;
+	reg_w(gspca_dev, 0x0051, reg51c, 2);
+	reg_w(gspca_dev, 0x0010, reg10, 2);
+	reg_w_val(gspca_dev, 0x0070, reg70);
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *)gspca_dev;
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		setbrightness(gspca_dev, ctrl->val, sd->sat->cur.val);
+		break;
+	case V4L2_CID_CONTRAST:
+		setcontrast(gspca_dev, ctrl->val, sd->sat->cur.val);
+		break;
+	case V4L2_CID_SATURATION:
+		setbrightness(gspca_dev, sd->brightness->cur.val, ctrl->val);
+		setcontrast(gspca_dev, sd->contrast->cur.val, ctrl->val);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *)gspca_dev;
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 3);
+	sd->brightness = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0, 255, 1, 0xd4);
+	sd->contrast = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_CONTRAST, 0x0a, 0x1f, 1, 0x0c);
+	sd->sat = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_SATURATION, 0, 7, 1, 3);
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	return 0;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.start = sd_start,
+	.stop0 = sd_stop0,
+	.pkt_scan = sd_pkt_scan,
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x0572, 0x0041)},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/cpia1.c b/drivers/media/usb/gspca/cpia1.c
new file mode 100644
index 0000000..2b09af8
--- /dev/null
+++ b/drivers/media/usb/gspca/cpia1.c
@@ -0,0 +1,1902 @@
+/*
+ * cpia CPiA (1) gspca driver
+ *
+ * Copyright (C) 2010-2011 Hans de Goede <hdegoede@redhat.com>
+ *
+ * This module is adapted from the in kernel v4l1 cpia driver which is :
+ *
+ * (C) Copyright 1999-2000 Peter Pregler
+ * (C) Copyright 1999-2000 Scott J. Bertin
+ * (C) Copyright 1999-2000 Johannes Erdfelt <johannes@erdfelt.com>
+ * (C) Copyright 2000 STMicroelectronics
+ *
+ * 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.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "cpia1"
+
+#include <linux/input.h>
+#include <linux/sched/signal.h>
+
+#include "gspca.h"
+
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_DESCRIPTION("Vision CPiA");
+MODULE_LICENSE("GPL");
+
+/* constant value's */
+#define MAGIC_0		0x19
+#define MAGIC_1		0x68
+#define DATA_IN		0xc0
+#define DATA_OUT	0x40
+#define VIDEOSIZE_QCIF	0	/* 176x144 */
+#define VIDEOSIZE_CIF	1	/* 352x288 */
+#define SUBSAMPLE_420	0
+#define SUBSAMPLE_422	1
+#define YUVORDER_YUYV	0
+#define YUVORDER_UYVY	1
+#define NOT_COMPRESSED	0
+#define COMPRESSED	1
+#define NO_DECIMATION	0
+#define DECIMATION_ENAB	1
+#define EOI		0xff	/* End Of Image */
+#define EOL		0xfd	/* End Of Line */
+#define FRAME_HEADER_SIZE	64
+
+/* Image grab modes */
+#define CPIA_GRAB_SINGLE	0
+#define CPIA_GRAB_CONTINEOUS	1
+
+/* Compression parameters */
+#define CPIA_COMPRESSION_NONE	0
+#define CPIA_COMPRESSION_AUTO	1
+#define CPIA_COMPRESSION_MANUAL	2
+#define CPIA_COMPRESSION_TARGET_QUALITY         0
+#define CPIA_COMPRESSION_TARGET_FRAMERATE       1
+
+/* Return offsets for GetCameraState */
+#define SYSTEMSTATE	0
+#define GRABSTATE	1
+#define STREAMSTATE	2
+#define FATALERROR	3
+#define CMDERROR	4
+#define DEBUGFLAGS	5
+#define VPSTATUS	6
+#define ERRORCODE	7
+
+/* SystemState */
+#define UNINITIALISED_STATE	0
+#define PASS_THROUGH_STATE	1
+#define LO_POWER_STATE		2
+#define HI_POWER_STATE		3
+#define WARM_BOOT_STATE		4
+
+/* GrabState */
+#define GRAB_IDLE		0
+#define GRAB_ACTIVE		1
+#define GRAB_DONE		2
+
+/* StreamState */
+#define STREAM_NOT_READY	0
+#define STREAM_READY		1
+#define STREAM_OPEN		2
+#define STREAM_PAUSED		3
+#define STREAM_FINISHED		4
+
+/* Fatal Error, CmdError, and DebugFlags */
+#define CPIA_FLAG	  1
+#define SYSTEM_FLAG	  2
+#define INT_CTRL_FLAG	  4
+#define PROCESS_FLAG	  8
+#define COM_FLAG	 16
+#define VP_CTRL_FLAG	 32
+#define CAPTURE_FLAG	 64
+#define DEBUG_FLAG	128
+
+/* VPStatus */
+#define VP_STATE_OK			0x00
+
+#define VP_STATE_FAILED_VIDEOINIT	0x01
+#define VP_STATE_FAILED_AECACBINIT	0x02
+#define VP_STATE_AEC_MAX		0x04
+#define VP_STATE_ACB_BMAX		0x08
+
+#define VP_STATE_ACB_RMIN		0x10
+#define VP_STATE_ACB_GMIN		0x20
+#define VP_STATE_ACB_RMAX		0x40
+#define VP_STATE_ACB_GMAX		0x80
+
+/* default (minimum) compensation values */
+#define COMP_RED        220
+#define COMP_GREEN1     214
+#define COMP_GREEN2     COMP_GREEN1
+#define COMP_BLUE       230
+
+/* exposure status */
+#define EXPOSURE_VERY_LIGHT 0
+#define EXPOSURE_LIGHT      1
+#define EXPOSURE_NORMAL     2
+#define EXPOSURE_DARK       3
+#define EXPOSURE_VERY_DARK  4
+
+#define CPIA_MODULE_CPIA			(0 << 5)
+#define CPIA_MODULE_SYSTEM			(1 << 5)
+#define CPIA_MODULE_VP_CTRL			(5 << 5)
+#define CPIA_MODULE_CAPTURE			(6 << 5)
+#define CPIA_MODULE_DEBUG			(7 << 5)
+
+#define INPUT (DATA_IN << 8)
+#define OUTPUT (DATA_OUT << 8)
+
+#define CPIA_COMMAND_GetCPIAVersion	(INPUT | CPIA_MODULE_CPIA | 1)
+#define CPIA_COMMAND_GetPnPID		(INPUT | CPIA_MODULE_CPIA | 2)
+#define CPIA_COMMAND_GetCameraStatus	(INPUT | CPIA_MODULE_CPIA | 3)
+#define CPIA_COMMAND_GotoHiPower	(OUTPUT | CPIA_MODULE_CPIA | 4)
+#define CPIA_COMMAND_GotoLoPower	(OUTPUT | CPIA_MODULE_CPIA | 5)
+#define CPIA_COMMAND_GotoSuspend	(OUTPUT | CPIA_MODULE_CPIA | 7)
+#define CPIA_COMMAND_GotoPassThrough	(OUTPUT | CPIA_MODULE_CPIA | 8)
+#define CPIA_COMMAND_ModifyCameraStatus	(OUTPUT | CPIA_MODULE_CPIA | 10)
+
+#define CPIA_COMMAND_ReadVCRegs		(INPUT | CPIA_MODULE_SYSTEM | 1)
+#define CPIA_COMMAND_WriteVCReg		(OUTPUT | CPIA_MODULE_SYSTEM | 2)
+#define CPIA_COMMAND_ReadMCPorts	(INPUT | CPIA_MODULE_SYSTEM | 3)
+#define CPIA_COMMAND_WriteMCPort	(OUTPUT | CPIA_MODULE_SYSTEM | 4)
+#define CPIA_COMMAND_SetBaudRate	(OUTPUT | CPIA_MODULE_SYSTEM | 5)
+#define CPIA_COMMAND_SetECPTiming	(OUTPUT | CPIA_MODULE_SYSTEM | 6)
+#define CPIA_COMMAND_ReadIDATA		(INPUT | CPIA_MODULE_SYSTEM | 7)
+#define CPIA_COMMAND_WriteIDATA		(OUTPUT | CPIA_MODULE_SYSTEM | 8)
+#define CPIA_COMMAND_GenericCall	(OUTPUT | CPIA_MODULE_SYSTEM | 9)
+#define CPIA_COMMAND_I2CStart		(OUTPUT | CPIA_MODULE_SYSTEM | 10)
+#define CPIA_COMMAND_I2CStop		(OUTPUT | CPIA_MODULE_SYSTEM | 11)
+#define CPIA_COMMAND_I2CWrite		(OUTPUT | CPIA_MODULE_SYSTEM | 12)
+#define CPIA_COMMAND_I2CRead		(INPUT | CPIA_MODULE_SYSTEM | 13)
+
+#define CPIA_COMMAND_GetVPVersion	(INPUT | CPIA_MODULE_VP_CTRL | 1)
+#define CPIA_COMMAND_ResetFrameCounter	(INPUT | CPIA_MODULE_VP_CTRL | 2)
+#define CPIA_COMMAND_SetColourParams	(OUTPUT | CPIA_MODULE_VP_CTRL | 3)
+#define CPIA_COMMAND_SetExposure	(OUTPUT | CPIA_MODULE_VP_CTRL | 4)
+#define CPIA_COMMAND_SetColourBalance	(OUTPUT | CPIA_MODULE_VP_CTRL | 6)
+#define CPIA_COMMAND_SetSensorFPS	(OUTPUT | CPIA_MODULE_VP_CTRL | 7)
+#define CPIA_COMMAND_SetVPDefaults	(OUTPUT | CPIA_MODULE_VP_CTRL | 8)
+#define CPIA_COMMAND_SetApcor		(OUTPUT | CPIA_MODULE_VP_CTRL | 9)
+#define CPIA_COMMAND_SetFlickerCtrl	(OUTPUT | CPIA_MODULE_VP_CTRL | 10)
+#define CPIA_COMMAND_SetVLOffset	(OUTPUT | CPIA_MODULE_VP_CTRL | 11)
+#define CPIA_COMMAND_GetColourParams	(INPUT | CPIA_MODULE_VP_CTRL | 16)
+#define CPIA_COMMAND_GetColourBalance	(INPUT | CPIA_MODULE_VP_CTRL | 17)
+#define CPIA_COMMAND_GetExposure	(INPUT | CPIA_MODULE_VP_CTRL | 18)
+#define CPIA_COMMAND_SetSensorMatrix	(OUTPUT | CPIA_MODULE_VP_CTRL | 19)
+#define CPIA_COMMAND_ColourBars		(OUTPUT | CPIA_MODULE_VP_CTRL | 25)
+#define CPIA_COMMAND_ReadVPRegs		(INPUT | CPIA_MODULE_VP_CTRL | 30)
+#define CPIA_COMMAND_WriteVPReg		(OUTPUT | CPIA_MODULE_VP_CTRL | 31)
+
+#define CPIA_COMMAND_GrabFrame		(OUTPUT | CPIA_MODULE_CAPTURE | 1)
+#define CPIA_COMMAND_UploadFrame	(OUTPUT | CPIA_MODULE_CAPTURE | 2)
+#define CPIA_COMMAND_SetGrabMode	(OUTPUT | CPIA_MODULE_CAPTURE | 3)
+#define CPIA_COMMAND_InitStreamCap	(OUTPUT | CPIA_MODULE_CAPTURE | 4)
+#define CPIA_COMMAND_FiniStreamCap	(OUTPUT | CPIA_MODULE_CAPTURE | 5)
+#define CPIA_COMMAND_StartStreamCap	(OUTPUT | CPIA_MODULE_CAPTURE | 6)
+#define CPIA_COMMAND_EndStreamCap	(OUTPUT | CPIA_MODULE_CAPTURE | 7)
+#define CPIA_COMMAND_SetFormat		(OUTPUT | CPIA_MODULE_CAPTURE | 8)
+#define CPIA_COMMAND_SetROI		(OUTPUT | CPIA_MODULE_CAPTURE | 9)
+#define CPIA_COMMAND_SetCompression	(OUTPUT | CPIA_MODULE_CAPTURE | 10)
+#define CPIA_COMMAND_SetCompressionTarget (OUTPUT | CPIA_MODULE_CAPTURE | 11)
+#define CPIA_COMMAND_SetYUVThresh	(OUTPUT | CPIA_MODULE_CAPTURE | 12)
+#define CPIA_COMMAND_SetCompressionParams (OUTPUT | CPIA_MODULE_CAPTURE | 13)
+#define CPIA_COMMAND_DiscardFrame	(OUTPUT | CPIA_MODULE_CAPTURE | 14)
+#define CPIA_COMMAND_GrabReset		(OUTPUT | CPIA_MODULE_CAPTURE | 15)
+
+#define CPIA_COMMAND_OutputRS232	(OUTPUT | CPIA_MODULE_DEBUG | 1)
+#define CPIA_COMMAND_AbortProcess	(OUTPUT | CPIA_MODULE_DEBUG | 4)
+#define CPIA_COMMAND_SetDramPage	(OUTPUT | CPIA_MODULE_DEBUG | 5)
+#define CPIA_COMMAND_StartDramUpload	(OUTPUT | CPIA_MODULE_DEBUG | 6)
+#define CPIA_COMMAND_StartDummyDtream	(OUTPUT | CPIA_MODULE_DEBUG | 8)
+#define CPIA_COMMAND_AbortStream	(OUTPUT | CPIA_MODULE_DEBUG | 9)
+#define CPIA_COMMAND_DownloadDRAM	(OUTPUT | CPIA_MODULE_DEBUG | 10)
+#define CPIA_COMMAND_Null		(OUTPUT | CPIA_MODULE_DEBUG | 11)
+
+#define ROUND_UP_EXP_FOR_FLICKER 15
+
+/* Constants for automatic frame rate adjustment */
+#define MAX_EXP       302
+#define MAX_EXP_102   255
+#define LOW_EXP       140
+#define VERY_LOW_EXP   70
+#define TC             94
+#define	EXP_ACC_DARK   50
+#define	EXP_ACC_LIGHT  90
+#define HIGH_COMP_102 160
+#define MAX_COMP      239
+#define DARK_TIME       3
+#define LIGHT_TIME      3
+
+#define FIRMWARE_VERSION(x, y) (sd->params.version.firmwareVersion == (x) && \
+				sd->params.version.firmwareRevision == (y))
+
+#define CPIA1_CID_COMP_TARGET (V4L2_CTRL_CLASS_USER + 0x1000)
+#define BRIGHTNESS_DEF 50
+#define CONTRAST_DEF 48
+#define SATURATION_DEF 50
+#define FREQ_DEF V4L2_CID_POWER_LINE_FREQUENCY_50HZ
+#define ILLUMINATORS_1_DEF 0
+#define ILLUMINATORS_2_DEF 0
+#define COMP_TARGET_DEF CPIA_COMPRESSION_TARGET_QUALITY
+
+/* Developer's Guide Table 5 p 3-34
+ * indexed by [mains][sensorFps.baserate][sensorFps.divisor]*/
+static u8 flicker_jumps[2][2][4] =
+{ { { 76, 38, 19, 9 }, { 92, 46, 23, 11 } },
+  { { 64, 32, 16, 8 }, { 76, 38, 19, 9} }
+};
+
+struct cam_params {
+	struct {
+		u8 firmwareVersion;
+		u8 firmwareRevision;
+		u8 vcVersion;
+		u8 vcRevision;
+	} version;
+	struct {
+		u16 vendor;
+		u16 product;
+		u16 deviceRevision;
+	} pnpID;
+	struct {
+		u8 vpVersion;
+		u8 vpRevision;
+		u16 cameraHeadID;
+	} vpVersion;
+	struct {
+		u8 systemState;
+		u8 grabState;
+		u8 streamState;
+		u8 fatalError;
+		u8 cmdError;
+		u8 debugFlags;
+		u8 vpStatus;
+		u8 errorCode;
+	} status;
+	struct {
+		u8 brightness;
+		u8 contrast;
+		u8 saturation;
+	} colourParams;
+	struct {
+		u8 gainMode;
+		u8 expMode;
+		u8 compMode;
+		u8 centreWeight;
+		u8 gain;
+		u8 fineExp;
+		u8 coarseExpLo;
+		u8 coarseExpHi;
+		u8 redComp;
+		u8 green1Comp;
+		u8 green2Comp;
+		u8 blueComp;
+	} exposure;
+	struct {
+		u8 balanceMode;
+		u8 redGain;
+		u8 greenGain;
+		u8 blueGain;
+	} colourBalance;
+	struct {
+		u8 divisor;
+		u8 baserate;
+	} sensorFps;
+	struct {
+		u8 gain1;
+		u8 gain2;
+		u8 gain4;
+		u8 gain8;
+	} apcor;
+	struct {
+		u8 disabled;
+		u8 flickerMode;
+		u8 coarseJump;
+		u8 allowableOverExposure;
+	} flickerControl;
+	struct {
+		u8 gain1;
+		u8 gain2;
+		u8 gain4;
+		u8 gain8;
+	} vlOffset;
+	struct {
+		u8 mode;
+		u8 decimation;
+	} compression;
+	struct {
+		u8 frTargeting;
+		u8 targetFR;
+		u8 targetQ;
+	} compressionTarget;
+	struct {
+		u8 yThreshold;
+		u8 uvThreshold;
+	} yuvThreshold;
+	struct {
+		u8 hysteresis;
+		u8 threshMax;
+		u8 smallStep;
+		u8 largeStep;
+		u8 decimationHysteresis;
+		u8 frDiffStepThresh;
+		u8 qDiffStepThresh;
+		u8 decimationThreshMod;
+	} compressionParams;
+	struct {
+		u8 videoSize;		/* CIF/QCIF */
+		u8 subSample;
+		u8 yuvOrder;
+	} format;
+	struct {                        /* Intel QX3 specific data */
+		u8 qx3_detected;        /* a QX3 is present */
+		u8 toplight;            /* top light lit , R/W */
+		u8 bottomlight;         /* bottom light lit, R/W */
+		u8 button;              /* snapshot button pressed (R/O) */
+		u8 cradled;             /* microscope is in cradle (R/O) */
+	} qx3;
+	struct {
+		u8 colStart;		/* skip first 8*colStart pixels */
+		u8 colEnd;		/* finish at 8*colEnd pixels */
+		u8 rowStart;		/* skip first 4*rowStart lines */
+		u8 rowEnd;		/* finish at 4*rowEnd lines */
+	} roi;
+	u8 ecpTiming;
+	u8 streamStartLine;
+};
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;		/* !! must be the first item */
+	struct cam_params params;		/* camera settings */
+
+	atomic_t cam_exposure;
+	atomic_t fps;
+	int exposure_count;
+	u8 exposure_status;
+	struct v4l2_ctrl *freq;
+	u8 mainsFreq;				/* 0 = 50hz, 1 = 60hz */
+	u8 first_frame;
+};
+
+static const struct v4l2_pix_format mode[] = {
+	{160, 120, V4L2_PIX_FMT_CPIA1, V4L2_FIELD_NONE,
+		/* The sizeimage is trial and error, as with low framerates
+		   the camera will pad out usb frames, making the image
+		   data larger then strictly necessary */
+		.bytesperline = 160,
+		.sizeimage = 65536,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 3},
+	{176, 144, V4L2_PIX_FMT_CPIA1, V4L2_FIELD_NONE,
+		.bytesperline = 172,
+		.sizeimage = 65536,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 2},
+	{320, 240, V4L2_PIX_FMT_CPIA1, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 262144,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1},
+	{352, 288, V4L2_PIX_FMT_CPIA1, V4L2_FIELD_NONE,
+		.bytesperline = 352,
+		.sizeimage = 262144,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+};
+
+/**********************************************************************
+ *
+ * General functions
+ *
+ **********************************************************************/
+
+static int cpia_usb_transferCmd(struct gspca_dev *gspca_dev, u8 *command)
+{
+	u8 requesttype;
+	unsigned int pipe;
+	int ret, databytes = command[6] | (command[7] << 8);
+	/* Sometimes we see spurious EPIPE errors */
+	int retries = 3;
+
+	if (command[0] == DATA_IN) {
+		pipe = usb_rcvctrlpipe(gspca_dev->dev, 0);
+		requesttype = USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE;
+	} else if (command[0] == DATA_OUT) {
+		pipe = usb_sndctrlpipe(gspca_dev->dev, 0);
+		requesttype = USB_TYPE_VENDOR | USB_RECIP_DEVICE;
+	} else {
+		gspca_err(gspca_dev, "Unexpected first byte of command: %x\n",
+			  command[0]);
+		return -EINVAL;
+	}
+
+retry:
+	ret = usb_control_msg(gspca_dev->dev, pipe,
+			      command[1],
+			      requesttype,
+			      command[2] | (command[3] << 8),
+			      command[4] | (command[5] << 8),
+			      gspca_dev->usb_buf, databytes, 1000);
+
+	if (ret < 0)
+		pr_err("usb_control_msg %02x, error %d\n", command[1], ret);
+
+	if (ret == -EPIPE && retries > 0) {
+		retries--;
+		goto retry;
+	}
+
+	return (ret < 0) ? ret : 0;
+}
+
+/* send an arbitrary command to the camera */
+static int do_command(struct gspca_dev *gspca_dev, u16 command,
+		      u8 a, u8 b, u8 c, u8 d)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int ret, datasize;
+	u8 cmd[8];
+
+	switch (command) {
+	case CPIA_COMMAND_GetCPIAVersion:
+	case CPIA_COMMAND_GetPnPID:
+	case CPIA_COMMAND_GetCameraStatus:
+	case CPIA_COMMAND_GetVPVersion:
+	case CPIA_COMMAND_GetColourParams:
+	case CPIA_COMMAND_GetColourBalance:
+	case CPIA_COMMAND_GetExposure:
+		datasize = 8;
+		break;
+	case CPIA_COMMAND_ReadMCPorts:
+	case CPIA_COMMAND_ReadVCRegs:
+		datasize = 4;
+		break;
+	default:
+		datasize = 0;
+		break;
+	}
+
+	cmd[0] = command >> 8;
+	cmd[1] = command & 0xff;
+	cmd[2] = a;
+	cmd[3] = b;
+	cmd[4] = c;
+	cmd[5] = d;
+	cmd[6] = datasize;
+	cmd[7] = 0;
+
+	ret = cpia_usb_transferCmd(gspca_dev, cmd);
+	if (ret)
+		return ret;
+
+	switch (command) {
+	case CPIA_COMMAND_GetCPIAVersion:
+		sd->params.version.firmwareVersion = gspca_dev->usb_buf[0];
+		sd->params.version.firmwareRevision = gspca_dev->usb_buf[1];
+		sd->params.version.vcVersion = gspca_dev->usb_buf[2];
+		sd->params.version.vcRevision = gspca_dev->usb_buf[3];
+		break;
+	case CPIA_COMMAND_GetPnPID:
+		sd->params.pnpID.vendor =
+			gspca_dev->usb_buf[0] | (gspca_dev->usb_buf[1] << 8);
+		sd->params.pnpID.product =
+			gspca_dev->usb_buf[2] | (gspca_dev->usb_buf[3] << 8);
+		sd->params.pnpID.deviceRevision =
+			gspca_dev->usb_buf[4] | (gspca_dev->usb_buf[5] << 8);
+		break;
+	case CPIA_COMMAND_GetCameraStatus:
+		sd->params.status.systemState = gspca_dev->usb_buf[0];
+		sd->params.status.grabState = gspca_dev->usb_buf[1];
+		sd->params.status.streamState = gspca_dev->usb_buf[2];
+		sd->params.status.fatalError = gspca_dev->usb_buf[3];
+		sd->params.status.cmdError = gspca_dev->usb_buf[4];
+		sd->params.status.debugFlags = gspca_dev->usb_buf[5];
+		sd->params.status.vpStatus = gspca_dev->usb_buf[6];
+		sd->params.status.errorCode = gspca_dev->usb_buf[7];
+		break;
+	case CPIA_COMMAND_GetVPVersion:
+		sd->params.vpVersion.vpVersion = gspca_dev->usb_buf[0];
+		sd->params.vpVersion.vpRevision = gspca_dev->usb_buf[1];
+		sd->params.vpVersion.cameraHeadID =
+			gspca_dev->usb_buf[2] | (gspca_dev->usb_buf[3] << 8);
+		break;
+	case CPIA_COMMAND_GetColourParams:
+		sd->params.colourParams.brightness = gspca_dev->usb_buf[0];
+		sd->params.colourParams.contrast = gspca_dev->usb_buf[1];
+		sd->params.colourParams.saturation = gspca_dev->usb_buf[2];
+		break;
+	case CPIA_COMMAND_GetColourBalance:
+		sd->params.colourBalance.redGain = gspca_dev->usb_buf[0];
+		sd->params.colourBalance.greenGain = gspca_dev->usb_buf[1];
+		sd->params.colourBalance.blueGain = gspca_dev->usb_buf[2];
+		break;
+	case CPIA_COMMAND_GetExposure:
+		sd->params.exposure.gain = gspca_dev->usb_buf[0];
+		sd->params.exposure.fineExp = gspca_dev->usb_buf[1];
+		sd->params.exposure.coarseExpLo = gspca_dev->usb_buf[2];
+		sd->params.exposure.coarseExpHi = gspca_dev->usb_buf[3];
+		sd->params.exposure.redComp = gspca_dev->usb_buf[4];
+		sd->params.exposure.green1Comp = gspca_dev->usb_buf[5];
+		sd->params.exposure.green2Comp = gspca_dev->usb_buf[6];
+		sd->params.exposure.blueComp = gspca_dev->usb_buf[7];
+		break;
+
+	case CPIA_COMMAND_ReadMCPorts:
+		/* test button press */
+		a = ((gspca_dev->usb_buf[1] & 0x02) == 0);
+		if (a != sd->params.qx3.button) {
+#if IS_ENABLED(CONFIG_INPUT)
+			input_report_key(gspca_dev->input_dev, KEY_CAMERA, a);
+			input_sync(gspca_dev->input_dev);
+#endif
+			sd->params.qx3.button = a;
+		}
+		if (sd->params.qx3.button) {
+			/* button pressed - unlock the latch */
+			do_command(gspca_dev, CPIA_COMMAND_WriteMCPort,
+				   3, 0xdf, 0xdf, 0);
+			do_command(gspca_dev, CPIA_COMMAND_WriteMCPort,
+				   3, 0xff, 0xff, 0);
+		}
+
+		/* test whether microscope is cradled */
+		sd->params.qx3.cradled = ((gspca_dev->usb_buf[2] & 0x40) == 0);
+		break;
+	}
+
+	return 0;
+}
+
+/* send a command to the camera with an additional data transaction */
+static int do_command_extended(struct gspca_dev *gspca_dev, u16 command,
+			       u8 a, u8 b, u8 c, u8 d,
+			       u8 e, u8 f, u8 g, u8 h,
+			       u8 i, u8 j, u8 k, u8 l)
+{
+	u8 cmd[8];
+
+	cmd[0] = command >> 8;
+	cmd[1] = command & 0xff;
+	cmd[2] = a;
+	cmd[3] = b;
+	cmd[4] = c;
+	cmd[5] = d;
+	cmd[6] = 8;
+	cmd[7] = 0;
+	gspca_dev->usb_buf[0] = e;
+	gspca_dev->usb_buf[1] = f;
+	gspca_dev->usb_buf[2] = g;
+	gspca_dev->usb_buf[3] = h;
+	gspca_dev->usb_buf[4] = i;
+	gspca_dev->usb_buf[5] = j;
+	gspca_dev->usb_buf[6] = k;
+	gspca_dev->usb_buf[7] = l;
+
+	return cpia_usb_transferCmd(gspca_dev, cmd);
+}
+
+/*  find_over_exposure
+ *  Finds a suitable value of OverExposure for use with SetFlickerCtrl
+ *  Some calculation is required because this value changes with the brightness
+ *  set with SetColourParameters
+ *
+ *  Parameters: Brightness - last brightness value set with SetColourParameters
+ *
+ *  Returns: OverExposure value to use with SetFlickerCtrl
+ */
+#define FLICKER_MAX_EXPOSURE                    250
+#define FLICKER_ALLOWABLE_OVER_EXPOSURE         146
+#define FLICKER_BRIGHTNESS_CONSTANT             59
+static int find_over_exposure(int brightness)
+{
+	int MaxAllowableOverExposure, OverExposure;
+
+	MaxAllowableOverExposure = FLICKER_MAX_EXPOSURE - brightness -
+				   FLICKER_BRIGHTNESS_CONSTANT;
+
+	if (MaxAllowableOverExposure < FLICKER_ALLOWABLE_OVER_EXPOSURE)
+		OverExposure = MaxAllowableOverExposure;
+	else
+		OverExposure = FLICKER_ALLOWABLE_OVER_EXPOSURE;
+
+	return OverExposure;
+}
+#undef FLICKER_MAX_EXPOSURE
+#undef FLICKER_ALLOWABLE_OVER_EXPOSURE
+#undef FLICKER_BRIGHTNESS_CONSTANT
+
+/* initialise cam_data structure  */
+static void reset_camera_params(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam_params *params = &sd->params;
+
+	/* The following parameter values are the defaults from
+	 * "Software Developer's Guide for CPiA Cameras".  Any changes
+	 * to the defaults are noted in comments. */
+	params->colourParams.brightness = BRIGHTNESS_DEF;
+	params->colourParams.contrast = CONTRAST_DEF;
+	params->colourParams.saturation = SATURATION_DEF;
+	params->exposure.gainMode = 4;
+	params->exposure.expMode = 2;		/* AEC */
+	params->exposure.compMode = 1;
+	params->exposure.centreWeight = 1;
+	params->exposure.gain = 0;
+	params->exposure.fineExp = 0;
+	params->exposure.coarseExpLo = 185;
+	params->exposure.coarseExpHi = 0;
+	params->exposure.redComp = COMP_RED;
+	params->exposure.green1Comp = COMP_GREEN1;
+	params->exposure.green2Comp = COMP_GREEN2;
+	params->exposure.blueComp = COMP_BLUE;
+	params->colourBalance.balanceMode = 2;	/* ACB */
+	params->colourBalance.redGain = 32;
+	params->colourBalance.greenGain = 6;
+	params->colourBalance.blueGain = 92;
+	params->apcor.gain1 = 0x18;
+	params->apcor.gain2 = 0x16;
+	params->apcor.gain4 = 0x24;
+	params->apcor.gain8 = 0x34;
+	params->vlOffset.gain1 = 20;
+	params->vlOffset.gain2 = 24;
+	params->vlOffset.gain4 = 26;
+	params->vlOffset.gain8 = 26;
+	params->compressionParams.hysteresis = 3;
+	params->compressionParams.threshMax = 11;
+	params->compressionParams.smallStep = 1;
+	params->compressionParams.largeStep = 3;
+	params->compressionParams.decimationHysteresis = 2;
+	params->compressionParams.frDiffStepThresh = 5;
+	params->compressionParams.qDiffStepThresh = 3;
+	params->compressionParams.decimationThreshMod = 2;
+	/* End of default values from Software Developer's Guide */
+
+	/* Set Sensor FPS to 15fps. This seems better than 30fps
+	 * for indoor lighting. */
+	params->sensorFps.divisor = 1;
+	params->sensorFps.baserate = 1;
+
+	params->flickerControl.flickerMode = 0;
+	params->flickerControl.disabled = 1;
+	params->flickerControl.coarseJump =
+		flicker_jumps[sd->mainsFreq]
+			     [params->sensorFps.baserate]
+			     [params->sensorFps.divisor];
+	params->flickerControl.allowableOverExposure =
+		find_over_exposure(params->colourParams.brightness);
+
+	params->yuvThreshold.yThreshold = 6; /* From windows driver */
+	params->yuvThreshold.uvThreshold = 6; /* From windows driver */
+
+	params->format.subSample = SUBSAMPLE_420;
+	params->format.yuvOrder = YUVORDER_YUYV;
+
+	params->compression.mode = CPIA_COMPRESSION_AUTO;
+	params->compression.decimation = NO_DECIMATION;
+
+	params->compressionTarget.frTargeting = COMP_TARGET_DEF;
+	params->compressionTarget.targetFR = 15; /* From windows driver */
+	params->compressionTarget.targetQ = 5; /* From windows driver */
+
+	params->qx3.qx3_detected = 0;
+	params->qx3.toplight = 0;
+	params->qx3.bottomlight = 0;
+	params->qx3.button = 0;
+	params->qx3.cradled = 0;
+}
+
+static void printstatus(struct gspca_dev *gspca_dev, struct cam_params *params)
+{
+	gspca_dbg(gspca_dev, D_PROBE, "status: %02x %02x %02x %02x %02x %02x %02x %02x\n",
+		  params->status.systemState, params->status.grabState,
+		  params->status.streamState, params->status.fatalError,
+		  params->status.cmdError, params->status.debugFlags,
+		  params->status.vpStatus, params->status.errorCode);
+}
+
+static int goto_low_power(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int ret;
+
+	ret = do_command(gspca_dev, CPIA_COMMAND_GotoLoPower, 0, 0, 0, 0);
+	if (ret)
+		return ret;
+
+	ret = do_command(gspca_dev, CPIA_COMMAND_GetCameraStatus, 0, 0, 0, 0);
+	if (ret)
+		return ret;
+
+	if (sd->params.status.systemState != LO_POWER_STATE) {
+		if (sd->params.status.systemState != WARM_BOOT_STATE) {
+			gspca_err(gspca_dev, "unexpected state after lo power cmd: %02x\n",
+				  sd->params.status.systemState);
+			printstatus(gspca_dev, &sd->params);
+		}
+		return -EIO;
+	}
+
+	gspca_dbg(gspca_dev, D_CONF, "camera now in LOW power state\n");
+	return 0;
+}
+
+static int goto_high_power(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int ret;
+
+	ret = do_command(gspca_dev, CPIA_COMMAND_GotoHiPower, 0, 0, 0, 0);
+	if (ret)
+		return ret;
+
+	msleep_interruptible(40);	/* windows driver does it too */
+
+	if (signal_pending(current))
+		return -EINTR;
+
+	ret = do_command(gspca_dev, CPIA_COMMAND_GetCameraStatus, 0, 0, 0, 0);
+	if (ret)
+		return ret;
+
+	if (sd->params.status.systemState != HI_POWER_STATE) {
+		gspca_err(gspca_dev, "unexpected state after hi power cmd: %02x\n",
+			  sd->params.status.systemState);
+		printstatus(gspca_dev, &sd->params);
+		return -EIO;
+	}
+
+	gspca_dbg(gspca_dev, D_CONF, "camera now in HIGH power state\n");
+	return 0;
+}
+
+static int get_version_information(struct gspca_dev *gspca_dev)
+{
+	int ret;
+
+	/* GetCPIAVersion */
+	ret = do_command(gspca_dev, CPIA_COMMAND_GetCPIAVersion, 0, 0, 0, 0);
+	if (ret)
+		return ret;
+
+	/* GetPnPID */
+	return do_command(gspca_dev, CPIA_COMMAND_GetPnPID, 0, 0, 0, 0);
+}
+
+static int save_camera_state(struct gspca_dev *gspca_dev)
+{
+	int ret;
+
+	ret = do_command(gspca_dev, CPIA_COMMAND_GetColourBalance, 0, 0, 0, 0);
+	if (ret)
+		return ret;
+
+	return do_command(gspca_dev, CPIA_COMMAND_GetExposure, 0, 0, 0, 0);
+}
+
+static int command_setformat(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int ret;
+
+	ret = do_command(gspca_dev, CPIA_COMMAND_SetFormat,
+			 sd->params.format.videoSize,
+			 sd->params.format.subSample,
+			 sd->params.format.yuvOrder, 0);
+	if (ret)
+		return ret;
+
+	return do_command(gspca_dev, CPIA_COMMAND_SetROI,
+			  sd->params.roi.colStart, sd->params.roi.colEnd,
+			  sd->params.roi.rowStart, sd->params.roi.rowEnd);
+}
+
+static int command_setcolourparams(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	return do_command(gspca_dev, CPIA_COMMAND_SetColourParams,
+			  sd->params.colourParams.brightness,
+			  sd->params.colourParams.contrast,
+			  sd->params.colourParams.saturation, 0);
+}
+
+static int command_setapcor(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	return do_command(gspca_dev, CPIA_COMMAND_SetApcor,
+			  sd->params.apcor.gain1,
+			  sd->params.apcor.gain2,
+			  sd->params.apcor.gain4,
+			  sd->params.apcor.gain8);
+}
+
+static int command_setvloffset(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	return do_command(gspca_dev, CPIA_COMMAND_SetVLOffset,
+			  sd->params.vlOffset.gain1,
+			  sd->params.vlOffset.gain2,
+			  sd->params.vlOffset.gain4,
+			  sd->params.vlOffset.gain8);
+}
+
+static int command_setexposure(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int ret;
+
+	ret = do_command_extended(gspca_dev, CPIA_COMMAND_SetExposure,
+				  sd->params.exposure.gainMode,
+				  1,
+				  sd->params.exposure.compMode,
+				  sd->params.exposure.centreWeight,
+				  sd->params.exposure.gain,
+				  sd->params.exposure.fineExp,
+				  sd->params.exposure.coarseExpLo,
+				  sd->params.exposure.coarseExpHi,
+				  sd->params.exposure.redComp,
+				  sd->params.exposure.green1Comp,
+				  sd->params.exposure.green2Comp,
+				  sd->params.exposure.blueComp);
+	if (ret)
+		return ret;
+
+	if (sd->params.exposure.expMode != 1) {
+		ret = do_command_extended(gspca_dev, CPIA_COMMAND_SetExposure,
+					  0,
+					  sd->params.exposure.expMode,
+					  0, 0,
+					  sd->params.exposure.gain,
+					  sd->params.exposure.fineExp,
+					  sd->params.exposure.coarseExpLo,
+					  sd->params.exposure.coarseExpHi,
+					  0, 0, 0, 0);
+	}
+
+	return ret;
+}
+
+static int command_setcolourbalance(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->params.colourBalance.balanceMode == 1) {
+		int ret;
+
+		ret = do_command(gspca_dev, CPIA_COMMAND_SetColourBalance,
+				 1,
+				 sd->params.colourBalance.redGain,
+				 sd->params.colourBalance.greenGain,
+				 sd->params.colourBalance.blueGain);
+		if (ret)
+			return ret;
+
+		return do_command(gspca_dev, CPIA_COMMAND_SetColourBalance,
+				  3, 0, 0, 0);
+	}
+	if (sd->params.colourBalance.balanceMode == 2) {
+		return do_command(gspca_dev, CPIA_COMMAND_SetColourBalance,
+				  2, 0, 0, 0);
+	}
+	if (sd->params.colourBalance.balanceMode == 3) {
+		return do_command(gspca_dev, CPIA_COMMAND_SetColourBalance,
+				  3, 0, 0, 0);
+	}
+
+	return -EINVAL;
+}
+
+static int command_setcompressiontarget(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	return do_command(gspca_dev, CPIA_COMMAND_SetCompressionTarget,
+			  sd->params.compressionTarget.frTargeting,
+			  sd->params.compressionTarget.targetFR,
+			  sd->params.compressionTarget.targetQ, 0);
+}
+
+static int command_setyuvtresh(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	return do_command(gspca_dev, CPIA_COMMAND_SetYUVThresh,
+			  sd->params.yuvThreshold.yThreshold,
+			  sd->params.yuvThreshold.uvThreshold, 0, 0);
+}
+
+static int command_setcompressionparams(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	return do_command_extended(gspca_dev,
+			    CPIA_COMMAND_SetCompressionParams,
+			    0, 0, 0, 0,
+			    sd->params.compressionParams.hysteresis,
+			    sd->params.compressionParams.threshMax,
+			    sd->params.compressionParams.smallStep,
+			    sd->params.compressionParams.largeStep,
+			    sd->params.compressionParams.decimationHysteresis,
+			    sd->params.compressionParams.frDiffStepThresh,
+			    sd->params.compressionParams.qDiffStepThresh,
+			    sd->params.compressionParams.decimationThreshMod);
+}
+
+static int command_setcompression(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	return do_command(gspca_dev, CPIA_COMMAND_SetCompression,
+			  sd->params.compression.mode,
+			  sd->params.compression.decimation, 0, 0);
+}
+
+static int command_setsensorfps(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	return do_command(gspca_dev, CPIA_COMMAND_SetSensorFPS,
+			  sd->params.sensorFps.divisor,
+			  sd->params.sensorFps.baserate, 0, 0);
+}
+
+static int command_setflickerctrl(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	return do_command(gspca_dev, CPIA_COMMAND_SetFlickerCtrl,
+			  sd->params.flickerControl.flickerMode,
+			  sd->params.flickerControl.coarseJump,
+			  sd->params.flickerControl.allowableOverExposure,
+			  0);
+}
+
+static int command_setecptiming(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	return do_command(gspca_dev, CPIA_COMMAND_SetECPTiming,
+			  sd->params.ecpTiming, 0, 0, 0);
+}
+
+static int command_pause(struct gspca_dev *gspca_dev)
+{
+	return do_command(gspca_dev, CPIA_COMMAND_EndStreamCap, 0, 0, 0, 0);
+}
+
+static int command_resume(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	return do_command(gspca_dev, CPIA_COMMAND_InitStreamCap,
+			  0, sd->params.streamStartLine, 0, 0);
+}
+
+static int command_setlights(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int ret, p1, p2;
+
+	p1 = (sd->params.qx3.bottomlight == 0) << 1;
+	p2 = (sd->params.qx3.toplight == 0) << 3;
+
+	ret = do_command(gspca_dev, CPIA_COMMAND_WriteVCReg,
+			 0x90, 0x8f, 0x50, 0);
+	if (ret)
+		return ret;
+
+	return do_command(gspca_dev, CPIA_COMMAND_WriteMCPort, 2, 0,
+			  p1 | p2 | 0xe0, 0);
+}
+
+static int set_flicker(struct gspca_dev *gspca_dev, int on, int apply)
+{
+	/* Everything in here is from the Windows driver */
+/* define for compgain calculation */
+#if 0
+#define COMPGAIN(base, curexp, newexp) \
+    (u8) ((((float) base - 128.0) * ((float) curexp / (float) newexp)) + 128.5)
+#define EXP_FROM_COMP(basecomp, curcomp, curexp) \
+    (u16)((float)curexp * (float)(u8)(curcomp + 128) / \
+    (float)(u8)(basecomp - 128))
+#else
+  /* equivalent functions without floating point math */
+#define COMPGAIN(base, curexp, newexp) \
+    (u8)(128 + (((u32)(2*(base-128)*curexp + newexp)) / (2 * newexp)))
+#define EXP_FROM_COMP(basecomp, curcomp, curexp) \
+    (u16)(((u32)(curexp * (u8)(curcomp + 128)) / (u8)(basecomp - 128)))
+#endif
+
+	struct sd *sd = (struct sd *) gspca_dev;
+	int currentexp = sd->params.exposure.coarseExpLo +
+			 sd->params.exposure.coarseExpHi * 256;
+	int ret, startexp;
+
+	if (on) {
+		int cj = sd->params.flickerControl.coarseJump;
+		sd->params.flickerControl.flickerMode = 1;
+		sd->params.flickerControl.disabled = 0;
+		if (sd->params.exposure.expMode != 2) {
+			sd->params.exposure.expMode = 2;
+			sd->exposure_status = EXPOSURE_NORMAL;
+		}
+		currentexp = currentexp << sd->params.exposure.gain;
+		sd->params.exposure.gain = 0;
+		/* round down current exposure to nearest value */
+		startexp = (currentexp + ROUND_UP_EXP_FOR_FLICKER) / cj;
+		if (startexp < 1)
+			startexp = 1;
+		startexp = (startexp * cj) - 1;
+		if (FIRMWARE_VERSION(1, 2))
+			while (startexp > MAX_EXP_102)
+				startexp -= cj;
+		else
+			while (startexp > MAX_EXP)
+				startexp -= cj;
+		sd->params.exposure.coarseExpLo = startexp & 0xff;
+		sd->params.exposure.coarseExpHi = startexp >> 8;
+		if (currentexp > startexp) {
+			if (currentexp > (2 * startexp))
+				currentexp = 2 * startexp;
+			sd->params.exposure.redComp =
+				COMPGAIN(COMP_RED, currentexp, startexp);
+			sd->params.exposure.green1Comp =
+				COMPGAIN(COMP_GREEN1, currentexp, startexp);
+			sd->params.exposure.green2Comp =
+				COMPGAIN(COMP_GREEN2, currentexp, startexp);
+			sd->params.exposure.blueComp =
+				COMPGAIN(COMP_BLUE, currentexp, startexp);
+		} else {
+			sd->params.exposure.redComp = COMP_RED;
+			sd->params.exposure.green1Comp = COMP_GREEN1;
+			sd->params.exposure.green2Comp = COMP_GREEN2;
+			sd->params.exposure.blueComp = COMP_BLUE;
+		}
+		if (FIRMWARE_VERSION(1, 2))
+			sd->params.exposure.compMode = 0;
+		else
+			sd->params.exposure.compMode = 1;
+
+		sd->params.apcor.gain1 = 0x18;
+		sd->params.apcor.gain2 = 0x18;
+		sd->params.apcor.gain4 = 0x16;
+		sd->params.apcor.gain8 = 0x14;
+	} else {
+		sd->params.flickerControl.flickerMode = 0;
+		sd->params.flickerControl.disabled = 1;
+		/* Average equivalent coarse for each comp channel */
+		startexp = EXP_FROM_COMP(COMP_RED,
+				sd->params.exposure.redComp, currentexp);
+		startexp += EXP_FROM_COMP(COMP_GREEN1,
+				sd->params.exposure.green1Comp, currentexp);
+		startexp += EXP_FROM_COMP(COMP_GREEN2,
+				sd->params.exposure.green2Comp, currentexp);
+		startexp += EXP_FROM_COMP(COMP_BLUE,
+				sd->params.exposure.blueComp, currentexp);
+		startexp = startexp >> 2;
+		while (startexp > MAX_EXP && sd->params.exposure.gain <
+		       sd->params.exposure.gainMode - 1) {
+			startexp = startexp >> 1;
+			++sd->params.exposure.gain;
+		}
+		if (FIRMWARE_VERSION(1, 2) && startexp > MAX_EXP_102)
+			startexp = MAX_EXP_102;
+		if (startexp > MAX_EXP)
+			startexp = MAX_EXP;
+		sd->params.exposure.coarseExpLo = startexp & 0xff;
+		sd->params.exposure.coarseExpHi = startexp >> 8;
+		sd->params.exposure.redComp = COMP_RED;
+		sd->params.exposure.green1Comp = COMP_GREEN1;
+		sd->params.exposure.green2Comp = COMP_GREEN2;
+		sd->params.exposure.blueComp = COMP_BLUE;
+		sd->params.exposure.compMode = 1;
+		sd->params.apcor.gain1 = 0x18;
+		sd->params.apcor.gain2 = 0x16;
+		sd->params.apcor.gain4 = 0x24;
+		sd->params.apcor.gain8 = 0x34;
+	}
+	sd->params.vlOffset.gain1 = 20;
+	sd->params.vlOffset.gain2 = 24;
+	sd->params.vlOffset.gain4 = 26;
+	sd->params.vlOffset.gain8 = 26;
+
+	if (apply) {
+		ret = command_setexposure(gspca_dev);
+		if (ret)
+			return ret;
+
+		ret = command_setapcor(gspca_dev);
+		if (ret)
+			return ret;
+
+		ret = command_setvloffset(gspca_dev);
+		if (ret)
+			return ret;
+
+		ret = command_setflickerctrl(gspca_dev);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+#undef EXP_FROM_COMP
+#undef COMPGAIN
+}
+
+/* monitor the exposure and adjust the sensor frame rate if needed */
+static void monitor_exposure(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 exp_acc, bcomp, cmd[8];
+	int ret, light_exp, dark_exp, very_dark_exp;
+	int old_exposure, new_exposure, framerate;
+	int setfps = 0, setexp = 0, setflicker = 0;
+
+	/* get necessary stats and register settings from camera */
+	/* do_command can't handle this, so do it ourselves */
+	cmd[0] = CPIA_COMMAND_ReadVPRegs >> 8;
+	cmd[1] = CPIA_COMMAND_ReadVPRegs & 0xff;
+	cmd[2] = 30;
+	cmd[3] = 4;
+	cmd[4] = 9;
+	cmd[5] = 8;
+	cmd[6] = 8;
+	cmd[7] = 0;
+	ret = cpia_usb_transferCmd(gspca_dev, cmd);
+	if (ret) {
+		pr_err("ReadVPRegs(30,4,9,8) - failed: %d\n", ret);
+		return;
+	}
+	exp_acc = gspca_dev->usb_buf[0];
+	bcomp = gspca_dev->usb_buf[1];
+
+	light_exp = sd->params.colourParams.brightness +
+		    TC - 50 + EXP_ACC_LIGHT;
+	if (light_exp > 255)
+		light_exp = 255;
+	dark_exp = sd->params.colourParams.brightness +
+		   TC - 50 - EXP_ACC_DARK;
+	if (dark_exp < 0)
+		dark_exp = 0;
+	very_dark_exp = dark_exp / 2;
+
+	old_exposure = sd->params.exposure.coarseExpHi * 256 +
+		       sd->params.exposure.coarseExpLo;
+
+	if (!sd->params.flickerControl.disabled) {
+		/* Flicker control on */
+		int max_comp = FIRMWARE_VERSION(1, 2) ? MAX_COMP :
+							HIGH_COMP_102;
+		bcomp += 128;	/* decode */
+		if (bcomp >= max_comp && exp_acc < dark_exp) {
+			/* dark */
+			if (exp_acc < very_dark_exp) {
+				/* very dark */
+				if (sd->exposure_status == EXPOSURE_VERY_DARK)
+					++sd->exposure_count;
+				else {
+					sd->exposure_status =
+						EXPOSURE_VERY_DARK;
+					sd->exposure_count = 1;
+				}
+			} else {
+				/* just dark */
+				if (sd->exposure_status == EXPOSURE_DARK)
+					++sd->exposure_count;
+				else {
+					sd->exposure_status = EXPOSURE_DARK;
+					sd->exposure_count = 1;
+				}
+			}
+		} else if (old_exposure <= LOW_EXP || exp_acc > light_exp) {
+			/* light */
+			if (old_exposure <= VERY_LOW_EXP) {
+				/* very light */
+				if (sd->exposure_status == EXPOSURE_VERY_LIGHT)
+					++sd->exposure_count;
+				else {
+					sd->exposure_status =
+						EXPOSURE_VERY_LIGHT;
+					sd->exposure_count = 1;
+				}
+			} else {
+				/* just light */
+				if (sd->exposure_status == EXPOSURE_LIGHT)
+					++sd->exposure_count;
+				else {
+					sd->exposure_status = EXPOSURE_LIGHT;
+					sd->exposure_count = 1;
+				}
+			}
+		} else {
+			/* not dark or light */
+			sd->exposure_status = EXPOSURE_NORMAL;
+		}
+	} else {
+		/* Flicker control off */
+		if (old_exposure >= MAX_EXP && exp_acc < dark_exp) {
+			/* dark */
+			if (exp_acc < very_dark_exp) {
+				/* very dark */
+				if (sd->exposure_status == EXPOSURE_VERY_DARK)
+					++sd->exposure_count;
+				else {
+					sd->exposure_status =
+						EXPOSURE_VERY_DARK;
+					sd->exposure_count = 1;
+				}
+			} else {
+				/* just dark */
+				if (sd->exposure_status == EXPOSURE_DARK)
+					++sd->exposure_count;
+				else {
+					sd->exposure_status = EXPOSURE_DARK;
+					sd->exposure_count = 1;
+				}
+			}
+		} else if (old_exposure <= LOW_EXP || exp_acc > light_exp) {
+			/* light */
+			if (old_exposure <= VERY_LOW_EXP) {
+				/* very light */
+				if (sd->exposure_status == EXPOSURE_VERY_LIGHT)
+					++sd->exposure_count;
+				else {
+					sd->exposure_status =
+						EXPOSURE_VERY_LIGHT;
+					sd->exposure_count = 1;
+				}
+			} else {
+				/* just light */
+				if (sd->exposure_status == EXPOSURE_LIGHT)
+					++sd->exposure_count;
+				else {
+					sd->exposure_status = EXPOSURE_LIGHT;
+					sd->exposure_count = 1;
+				}
+			}
+		} else {
+			/* not dark or light */
+			sd->exposure_status = EXPOSURE_NORMAL;
+		}
+	}
+
+	framerate = atomic_read(&sd->fps);
+	if (framerate > 30 || framerate < 1)
+		framerate = 1;
+
+	if (!sd->params.flickerControl.disabled) {
+		/* Flicker control on */
+		if ((sd->exposure_status == EXPOSURE_VERY_DARK ||
+		     sd->exposure_status == EXPOSURE_DARK) &&
+		    sd->exposure_count >= DARK_TIME * framerate &&
+		    sd->params.sensorFps.divisor < 2) {
+
+			/* dark for too long */
+			++sd->params.sensorFps.divisor;
+			setfps = 1;
+
+			sd->params.flickerControl.coarseJump =
+				flicker_jumps[sd->mainsFreq]
+					     [sd->params.sensorFps.baserate]
+					     [sd->params.sensorFps.divisor];
+			setflicker = 1;
+
+			new_exposure = sd->params.flickerControl.coarseJump-1;
+			while (new_exposure < old_exposure / 2)
+				new_exposure +=
+					sd->params.flickerControl.coarseJump;
+			sd->params.exposure.coarseExpLo = new_exposure & 0xff;
+			sd->params.exposure.coarseExpHi = new_exposure >> 8;
+			setexp = 1;
+			sd->exposure_status = EXPOSURE_NORMAL;
+			gspca_dbg(gspca_dev, D_CONF, "Automatically decreasing sensor_fps\n");
+
+		} else if ((sd->exposure_status == EXPOSURE_VERY_LIGHT ||
+			    sd->exposure_status == EXPOSURE_LIGHT) &&
+			   sd->exposure_count >= LIGHT_TIME * framerate &&
+			   sd->params.sensorFps.divisor > 0) {
+
+			/* light for too long */
+			int max_exp = FIRMWARE_VERSION(1, 2) ? MAX_EXP_102 :
+							       MAX_EXP;
+			--sd->params.sensorFps.divisor;
+			setfps = 1;
+
+			sd->params.flickerControl.coarseJump =
+				flicker_jumps[sd->mainsFreq]
+					     [sd->params.sensorFps.baserate]
+					     [sd->params.sensorFps.divisor];
+			setflicker = 1;
+
+			new_exposure = sd->params.flickerControl.coarseJump-1;
+			while (new_exposure < 2 * old_exposure &&
+			       new_exposure +
+			       sd->params.flickerControl.coarseJump < max_exp)
+				new_exposure +=
+					sd->params.flickerControl.coarseJump;
+			sd->params.exposure.coarseExpLo = new_exposure & 0xff;
+			sd->params.exposure.coarseExpHi = new_exposure >> 8;
+			setexp = 1;
+			sd->exposure_status = EXPOSURE_NORMAL;
+			gspca_dbg(gspca_dev, D_CONF, "Automatically increasing sensor_fps\n");
+		}
+	} else {
+		/* Flicker control off */
+		if ((sd->exposure_status == EXPOSURE_VERY_DARK ||
+		     sd->exposure_status == EXPOSURE_DARK) &&
+		    sd->exposure_count >= DARK_TIME * framerate &&
+		    sd->params.sensorFps.divisor < 2) {
+
+			/* dark for too long */
+			++sd->params.sensorFps.divisor;
+			setfps = 1;
+
+			if (sd->params.exposure.gain > 0) {
+				--sd->params.exposure.gain;
+				setexp = 1;
+			}
+			sd->exposure_status = EXPOSURE_NORMAL;
+			gspca_dbg(gspca_dev, D_CONF, "Automatically decreasing sensor_fps\n");
+
+		} else if ((sd->exposure_status == EXPOSURE_VERY_LIGHT ||
+			    sd->exposure_status == EXPOSURE_LIGHT) &&
+			   sd->exposure_count >= LIGHT_TIME * framerate &&
+			   sd->params.sensorFps.divisor > 0) {
+
+			/* light for too long */
+			--sd->params.sensorFps.divisor;
+			setfps = 1;
+
+			if (sd->params.exposure.gain <
+			    sd->params.exposure.gainMode - 1) {
+				++sd->params.exposure.gain;
+				setexp = 1;
+			}
+			sd->exposure_status = EXPOSURE_NORMAL;
+			gspca_dbg(gspca_dev, D_CONF, "Automatically increasing sensor_fps\n");
+		}
+	}
+
+	if (setexp)
+		command_setexposure(gspca_dev);
+
+	if (setfps)
+		command_setsensorfps(gspca_dev);
+
+	if (setflicker)
+		command_setflickerctrl(gspca_dev);
+}
+
+/*-----------------------------------------------------------------*/
+/* if flicker is switched off, this function switches it back on.It checks,
+   however, that conditions are suitable before restarting it.
+   This should only be called for firmware version 1.2.
+
+   It also adjust the colour balance when an exposure step is detected - as
+   long as flicker is running
+*/
+static void restart_flicker(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int cam_exposure, old_exp;
+
+	if (!FIRMWARE_VERSION(1, 2))
+		return;
+
+	cam_exposure = atomic_read(&sd->cam_exposure);
+
+	if (sd->params.flickerControl.flickerMode == 0 ||
+	    cam_exposure == 0)
+		return;
+
+	old_exp = sd->params.exposure.coarseExpLo +
+		  sd->params.exposure.coarseExpHi*256;
+	/*
+	  see how far away camera exposure is from a valid
+	  flicker exposure value
+	*/
+	cam_exposure %= sd->params.flickerControl.coarseJump;
+	if (!sd->params.flickerControl.disabled &&
+	    cam_exposure <= sd->params.flickerControl.coarseJump - 3) {
+		/* Flicker control auto-disabled */
+		sd->params.flickerControl.disabled = 1;
+	}
+
+	if (sd->params.flickerControl.disabled &&
+	    old_exp > sd->params.flickerControl.coarseJump +
+		      ROUND_UP_EXP_FOR_FLICKER) {
+		/* exposure is now high enough to switch
+		   flicker control back on */
+		set_flicker(gspca_dev, 1, 1);
+	}
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam;
+
+	sd->mainsFreq = FREQ_DEF == V4L2_CID_POWER_LINE_FREQUENCY_60HZ;
+	reset_camera_params(gspca_dev);
+
+	gspca_dbg(gspca_dev, D_PROBE, "cpia CPiA camera detected (vid/pid 0x%04X:0x%04X)\n",
+		  id->idVendor, id->idProduct);
+
+	cam = &gspca_dev->cam;
+	cam->cam_mode = mode;
+	cam->nmodes = ARRAY_SIZE(mode);
+
+	goto_low_power(gspca_dev);
+	/* Check the firmware version. */
+	sd->params.version.firmwareVersion = 0;
+	get_version_information(gspca_dev);
+	if (sd->params.version.firmwareVersion != 1) {
+		gspca_err(gspca_dev, "only firmware version 1 is supported (got: %d)\n",
+			  sd->params.version.firmwareVersion);
+		return -ENODEV;
+	}
+
+	/* A bug in firmware 1-02 limits gainMode to 2 */
+	if (sd->params.version.firmwareRevision <= 2 &&
+	    sd->params.exposure.gainMode > 2) {
+		sd->params.exposure.gainMode = 2;
+	}
+
+	/* set QX3 detected flag */
+	sd->params.qx3.qx3_detected = (sd->params.pnpID.vendor == 0x0813 &&
+				       sd->params.pnpID.product == 0x0001);
+	return 0;
+}
+
+/* -- start the camera -- */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int priv, ret;
+
+	/* Start the camera in low power mode */
+	if (goto_low_power(gspca_dev)) {
+		if (sd->params.status.systemState != WARM_BOOT_STATE) {
+			gspca_err(gspca_dev, "unexpected systemstate: %02x\n",
+				  sd->params.status.systemState);
+			printstatus(gspca_dev, &sd->params);
+			return -ENODEV;
+		}
+
+		/* FIXME: this is just dirty trial and error */
+		ret = goto_high_power(gspca_dev);
+		if (ret)
+			return ret;
+
+		ret = do_command(gspca_dev, CPIA_COMMAND_DiscardFrame,
+				 0, 0, 0, 0);
+		if (ret)
+			return ret;
+
+		ret = goto_low_power(gspca_dev);
+		if (ret)
+			return ret;
+	}
+
+	/* procedure described in developer's guide p3-28 */
+
+	/* Check the firmware version. */
+	sd->params.version.firmwareVersion = 0;
+	get_version_information(gspca_dev);
+
+	/* The fatal error checking should be done after
+	 * the camera powers up (developer's guide p 3-38) */
+
+	/* Set streamState before transition to high power to avoid bug
+	 * in firmware 1-02 */
+	ret = do_command(gspca_dev, CPIA_COMMAND_ModifyCameraStatus,
+			 STREAMSTATE, 0, STREAM_NOT_READY, 0);
+	if (ret)
+		return ret;
+
+	/* GotoHiPower */
+	ret = goto_high_power(gspca_dev);
+	if (ret)
+		return ret;
+
+	/* Check the camera status */
+	ret = do_command(gspca_dev, CPIA_COMMAND_GetCameraStatus, 0, 0, 0, 0);
+	if (ret)
+		return ret;
+
+	if (sd->params.status.fatalError) {
+		gspca_err(gspca_dev, "fatal_error: %04x, vp_status: %04x\n",
+			  sd->params.status.fatalError,
+			  sd->params.status.vpStatus);
+		return -EIO;
+	}
+
+	/* VPVersion can't be retrieved before the camera is in HiPower,
+	 * so get it here instead of in get_version_information. */
+	ret = do_command(gspca_dev, CPIA_COMMAND_GetVPVersion, 0, 0, 0, 0);
+	if (ret)
+		return ret;
+
+	/* Determine video mode settings */
+	sd->params.streamStartLine = 120;
+
+	priv = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].priv;
+	if (priv & 0x01) { /* crop */
+		sd->params.roi.colStart = 2;
+		sd->params.roi.rowStart = 6;
+	} else {
+		sd->params.roi.colStart = 0;
+		sd->params.roi.rowStart = 0;
+	}
+
+	if (priv & 0x02) { /* quarter */
+		sd->params.format.videoSize = VIDEOSIZE_QCIF;
+		sd->params.roi.colStart /= 2;
+		sd->params.roi.rowStart /= 2;
+		sd->params.streamStartLine /= 2;
+	} else
+		sd->params.format.videoSize = VIDEOSIZE_CIF;
+
+	sd->params.roi.colEnd = sd->params.roi.colStart +
+				(gspca_dev->pixfmt.width >> 3);
+	sd->params.roi.rowEnd = sd->params.roi.rowStart +
+				(gspca_dev->pixfmt.height >> 2);
+
+	/* And now set the camera to a known state */
+	ret = do_command(gspca_dev, CPIA_COMMAND_SetGrabMode,
+			 CPIA_GRAB_CONTINEOUS, 0, 0, 0);
+	if (ret)
+		return ret;
+	/* We start with compression disabled, as we need one uncompressed
+	   frame to handle later compressed frames */
+	ret = do_command(gspca_dev, CPIA_COMMAND_SetCompression,
+			 CPIA_COMPRESSION_NONE,
+			 NO_DECIMATION, 0, 0);
+	if (ret)
+		return ret;
+	ret = command_setcompressiontarget(gspca_dev);
+	if (ret)
+		return ret;
+	ret = command_setcolourparams(gspca_dev);
+	if (ret)
+		return ret;
+	ret = command_setformat(gspca_dev);
+	if (ret)
+		return ret;
+	ret = command_setyuvtresh(gspca_dev);
+	if (ret)
+		return ret;
+	ret = command_setecptiming(gspca_dev);
+	if (ret)
+		return ret;
+	ret = command_setcompressionparams(gspca_dev);
+	if (ret)
+		return ret;
+	ret = command_setexposure(gspca_dev);
+	if (ret)
+		return ret;
+	ret = command_setcolourbalance(gspca_dev);
+	if (ret)
+		return ret;
+	ret = command_setsensorfps(gspca_dev);
+	if (ret)
+		return ret;
+	ret = command_setapcor(gspca_dev);
+	if (ret)
+		return ret;
+	ret = command_setflickerctrl(gspca_dev);
+	if (ret)
+		return ret;
+	ret = command_setvloffset(gspca_dev);
+	if (ret)
+		return ret;
+
+	/* Start stream */
+	ret = command_resume(gspca_dev);
+	if (ret)
+		return ret;
+
+	/* Wait 6 frames before turning compression on for the sensor to get
+	   all settings and AEC/ACB to settle */
+	sd->first_frame = 6;
+	sd->exposure_status = EXPOSURE_NORMAL;
+	sd->exposure_count = 0;
+	atomic_set(&sd->cam_exposure, 0);
+	atomic_set(&sd->fps, 0);
+
+	return 0;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd __maybe_unused = (struct sd *) gspca_dev;
+
+	command_pause(gspca_dev);
+
+	/* save camera state for later open (developers guide ch 3.5.3) */
+	save_camera_state(gspca_dev);
+
+	/* GotoLoPower */
+	goto_low_power(gspca_dev);
+
+	/* Update the camera status */
+	do_command(gspca_dev, CPIA_COMMAND_GetCameraStatus, 0, 0, 0, 0);
+
+#if IS_ENABLED(CONFIG_INPUT)
+	/* If the last button state is pressed, release it now! */
+	if (sd->params.qx3.button) {
+		/* The camera latch will hold the pressed state until we reset
+		   the latch, so we do not reset sd->params.qx3.button now, to
+		   avoid a false keypress being reported the next sd_start */
+		input_report_key(gspca_dev->input_dev, KEY_CAMERA, 0);
+		input_sync(gspca_dev->input_dev);
+	}
+#endif
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int ret;
+
+	/* Start / Stop the camera to make sure we are talking to
+	   a supported camera, and to get some information from it
+	   to print. */
+	ret = sd_start(gspca_dev);
+	if (ret)
+		return ret;
+
+	/* Ensure the QX3 illuminators' states are restored upon resume,
+	   or disable the illuminator controls, if this isn't a QX3 */
+	if (sd->params.qx3.qx3_detected)
+		command_setlights(gspca_dev);
+
+	sd_stopN(gspca_dev);
+
+	gspca_dbg(gspca_dev, D_PROBE, "CPIA Version:             %d.%02d (%d.%d)\n",
+		  sd->params.version.firmwareVersion,
+		  sd->params.version.firmwareRevision,
+		  sd->params.version.vcVersion,
+		  sd->params.version.vcRevision);
+	gspca_dbg(gspca_dev, D_PROBE, "CPIA PnP-ID:              %04x:%04x:%04x",
+		  sd->params.pnpID.vendor, sd->params.pnpID.product,
+		  sd->params.pnpID.deviceRevision);
+	gspca_dbg(gspca_dev, D_PROBE, "VP-Version:               %d.%d %04x",
+		  sd->params.vpVersion.vpVersion,
+		  sd->params.vpVersion.vpRevision,
+		  sd->params.vpVersion.cameraHeadID);
+
+	return 0;
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,
+			int len)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	/* Check for SOF */
+	if (len >= 64 &&
+	    data[0] == MAGIC_0 && data[1] == MAGIC_1 &&
+	    data[16] == sd->params.format.videoSize &&
+	    data[17] == sd->params.format.subSample &&
+	    data[18] == sd->params.format.yuvOrder &&
+	    data[24] == sd->params.roi.colStart &&
+	    data[25] == sd->params.roi.colEnd &&
+	    data[26] == sd->params.roi.rowStart &&
+	    data[27] == sd->params.roi.rowEnd) {
+		u8 *image;
+
+		atomic_set(&sd->cam_exposure, data[39] * 2);
+		atomic_set(&sd->fps, data[41]);
+
+		/* Check for proper EOF for last frame */
+		image = gspca_dev->image;
+		if (image != NULL &&
+		    gspca_dev->image_len > 4 &&
+		    image[gspca_dev->image_len - 4] == 0xff &&
+		    image[gspca_dev->image_len - 3] == 0xff &&
+		    image[gspca_dev->image_len - 2] == 0xff &&
+		    image[gspca_dev->image_len - 1] == 0xff)
+			gspca_frame_add(gspca_dev, LAST_PACKET,
+						NULL, 0);
+
+		gspca_frame_add(gspca_dev, FIRST_PACKET, data, len);
+		return;
+	}
+
+	gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+}
+
+static void sd_dq_callback(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	/* Set the normal compression settings once we have captured a
+	   few uncompressed frames (and AEC has hopefully settled) */
+	if (sd->first_frame) {
+		sd->first_frame--;
+		if (sd->first_frame == 0)
+			command_setcompression(gspca_dev);
+	}
+
+	/* Switch flicker control back on if it got turned off */
+	restart_flicker(gspca_dev);
+
+	/* If AEC is enabled, monitor the exposure and
+	   adjust the sensor frame rate if needed */
+	if (sd->params.exposure.expMode == 2)
+		monitor_exposure(gspca_dev);
+
+	/* Update our knowledge of the camera state */
+	do_command(gspca_dev, CPIA_COMMAND_GetExposure, 0, 0, 0, 0);
+	do_command(gspca_dev, CPIA_COMMAND_ReadMCPorts, 0, 0, 0, 0);
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *)gspca_dev;
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming && ctrl->id != V4L2_CID_POWER_LINE_FREQUENCY)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		sd->params.colourParams.brightness = ctrl->val;
+		sd->params.flickerControl.allowableOverExposure =
+			find_over_exposure(sd->params.colourParams.brightness);
+		gspca_dev->usb_err = command_setcolourparams(gspca_dev);
+		if (!gspca_dev->usb_err)
+			gspca_dev->usb_err = command_setflickerctrl(gspca_dev);
+		break;
+	case V4L2_CID_CONTRAST:
+		sd->params.colourParams.contrast = ctrl->val;
+		gspca_dev->usb_err = command_setcolourparams(gspca_dev);
+		break;
+	case V4L2_CID_SATURATION:
+		sd->params.colourParams.saturation = ctrl->val;
+		gspca_dev->usb_err = command_setcolourparams(gspca_dev);
+		break;
+	case V4L2_CID_POWER_LINE_FREQUENCY:
+		sd->mainsFreq = ctrl->val == V4L2_CID_POWER_LINE_FREQUENCY_60HZ;
+		sd->params.flickerControl.coarseJump =
+			flicker_jumps[sd->mainsFreq]
+			[sd->params.sensorFps.baserate]
+			[sd->params.sensorFps.divisor];
+
+		gspca_dev->usb_err = set_flicker(gspca_dev,
+			ctrl->val != V4L2_CID_POWER_LINE_FREQUENCY_DISABLED,
+			gspca_dev->streaming);
+		break;
+	case V4L2_CID_ILLUMINATORS_1:
+		sd->params.qx3.bottomlight = ctrl->val;
+		gspca_dev->usb_err = command_setlights(gspca_dev);
+		break;
+	case V4L2_CID_ILLUMINATORS_2:
+		sd->params.qx3.toplight = ctrl->val;
+		gspca_dev->usb_err = command_setlights(gspca_dev);
+		break;
+	case CPIA1_CID_COMP_TARGET:
+		sd->params.compressionTarget.frTargeting = ctrl->val;
+		gspca_dev->usb_err = command_setcompressiontarget(gspca_dev);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *)gspca_dev;
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+	static const char * const comp_target_menu[] = {
+		"Quality",
+		"Framerate",
+		NULL
+	};
+	static const struct v4l2_ctrl_config comp_target = {
+		.ops = &sd_ctrl_ops,
+		.id = CPIA1_CID_COMP_TARGET,
+		.type = V4L2_CTRL_TYPE_MENU,
+		.name = "Compression Target",
+		.qmenu = comp_target_menu,
+		.max = 1,
+		.def = COMP_TARGET_DEF,
+	};
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 7);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0, 100, 1, BRIGHTNESS_DEF);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_CONTRAST, 0, 96, 8, CONTRAST_DEF);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_SATURATION, 0, 100, 1, SATURATION_DEF);
+	sd->freq = v4l2_ctrl_new_std_menu(hdl, &sd_ctrl_ops,
+			V4L2_CID_POWER_LINE_FREQUENCY,
+			V4L2_CID_POWER_LINE_FREQUENCY_60HZ, 0,
+			FREQ_DEF);
+	if (sd->params.qx3.qx3_detected) {
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+				V4L2_CID_ILLUMINATORS_1, 0, 1, 1,
+				ILLUMINATORS_1_DEF);
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+				V4L2_CID_ILLUMINATORS_2, 0, 1, 1,
+				ILLUMINATORS_2_DEF);
+	}
+	v4l2_ctrl_new_custom(hdl, &comp_target, NULL);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	return 0;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.dq_callback = sd_dq_callback,
+	.pkt_scan = sd_pkt_scan,
+#if IS_ENABLED(CONFIG_INPUT)
+	.other_input = 1,
+#endif
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x0553, 0x0002)},
+	{USB_DEVICE(0x0813, 0x0001)},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/dtcs033.c b/drivers/media/usb/gspca/dtcs033.c
new file mode 100644
index 0000000..7654c8c
--- /dev/null
+++ b/drivers/media/usb/gspca/dtcs033.c
@@ -0,0 +1,439 @@
+/*
+ * Subdriver for Scopium astro-camera (DTCS033, 0547:7303)
+ *
+ * Copyright (C) 2014 Robert Butora (robert.butora.fi@gmail.com)
+ *
+ * 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
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#define MODULE_NAME "dtcs033"
+#include "gspca.h"
+
+MODULE_AUTHOR("Robert Butora <robert.butora.fi@gmail.com>");
+MODULE_DESCRIPTION("Scopium DTCS033 astro-cam USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+struct dtcs033_usb_requests {
+	u8 bRequestType;
+	u8 bRequest;
+	u16 wValue;
+	u16 wIndex;
+	u16 wLength;
+};
+
+/* send a usb request */
+static void reg_rw(struct gspca_dev *gspca_dev,
+		u8 bRequestType, u8 bRequest,
+		u16 wValue, u16 wIndex, u16 wLength)
+{
+	struct usb_device *udev = gspca_dev->dev;
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+
+	ret = usb_control_msg(udev,
+		usb_rcvctrlpipe(udev, 0),
+		bRequest,
+		bRequestType,
+		wValue, wIndex,
+		gspca_dev->usb_buf, wLength, 500);
+
+	if (ret < 0) {
+		gspca_dev->usb_err = ret;
+		pr_err("usb_control_msg error %d\n", ret);
+	}
+
+	return;
+}
+/* send several usb in/out requests */
+static int reg_reqs(struct gspca_dev *gspca_dev,
+		    const struct dtcs033_usb_requests *preqs, int n_reqs)
+{
+	int i = 0;
+	const struct dtcs033_usb_requests *preq;
+
+	while ((i < n_reqs) && (gspca_dev->usb_err >= 0)) {
+
+		preq = &preqs[i];
+
+		reg_rw(gspca_dev, preq->bRequestType, preq->bRequest,
+			preq->wValue, preq->wIndex, preq->wLength);
+
+		if (gspca_dev->usb_err < 0) {
+
+			gspca_err(gspca_dev, "usb error request no: %d / %d\n",
+				  i, n_reqs);
+		} else if (preq->bRequestType & USB_DIR_IN) {
+
+			gspca_dbg(gspca_dev, D_STREAM,
+				  "USB IN (%d) returned[%d] %3ph %s",
+				  i,
+				  preq->wLength,
+				  gspca_dev->usb_buf,
+				  preq->wLength > 3 ? "...\n" : "\n");
+		}
+
+		i++;
+	}
+	return gspca_dev->usb_err;
+}
+
+/* -- subdriver interface implementation -- */
+
+#define DT_COLS (640)
+static const struct v4l2_pix_format dtcs033_mode[] = {
+	/* raw Bayer patterned output */
+	{DT_COLS, 480, V4L2_PIX_FMT_GREY, V4L2_FIELD_NONE,
+		.bytesperline = DT_COLS,
+		.sizeimage = DT_COLS*480,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+	},
+	/* this mode will demosaic the Bayer pattern */
+	{DT_COLS, 480, V4L2_PIX_FMT_SRGGB8, V4L2_FIELD_NONE,
+		.bytesperline = DT_COLS,
+		.sizeimage = DT_COLS*480,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+	}
+};
+
+/* config called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+		const struct usb_device_id *id)
+{
+	gspca_dev->cam.cam_mode = dtcs033_mode;
+	gspca_dev->cam.nmodes = ARRAY_SIZE(dtcs033_mode);
+
+	gspca_dev->cam.bulk = 1;
+	gspca_dev->cam.bulk_nurbs = 1;
+	gspca_dev->cam.bulk_size = DT_COLS*512;
+
+	return 0;
+}
+
+/* init called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	return 0;
+}
+
+/* start stop the camera */
+static int  dtcs033_start(struct gspca_dev *gspca_dev);
+static void dtcs033_stopN(struct gspca_dev *gspca_dev);
+
+/* intercept camera image data */
+static void dtcs033_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,  /* packet data */
+			int len)   /* packet data length */
+{
+	/* drop incomplete frames */
+	if (len != DT_COLS*512) {
+		gspca_dev->last_packet_type = DISCARD_PACKET;
+		/* gspca.c: discard invalidates the whole frame. */
+		return;
+	}
+
+	/* forward complete frames */
+	gspca_frame_add(gspca_dev, FIRST_PACKET, NULL, 0);
+	gspca_frame_add(gspca_dev, INTER_PACKET,
+		data + 16*DT_COLS,
+		len  - 32*DT_COLS); /* skip first & last 16 lines */
+	gspca_frame_add(gspca_dev, LAST_PACKET,  NULL, 0);
+
+	return;
+}
+
+/* -- controls: exposure and gain -- */
+
+static void dtcs033_setexposure(struct gspca_dev *gspca_dev,
+			s32 expo, s32 gain)
+{
+	/* gain [dB] encoding */
+	u16 sGain   = (u16)gain;
+	u16 gainVal = 224+(sGain-14)*(768-224)/(33-14);
+	u16 wIndex =  0x0100|(0x00FF&gainVal);
+	u16 wValue = (0xFF00&gainVal)>>8;
+
+	/* exposure time [msec] encoding */
+	u16 sXTime   = (u16)expo;
+	u16 xtimeVal = (524*(150-(sXTime-1)))/150;
+
+	const u8 bRequestType =
+		USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE;
+	const u8 bRequest = 0x18;
+
+	reg_rw(gspca_dev,
+		bRequestType, bRequest, wValue, wIndex, 0);
+	if (gspca_dev->usb_err < 0)
+		gspca_err(gspca_dev, "usb error in setexposure(gain) sequence\n");
+
+	reg_rw(gspca_dev,
+		bRequestType, bRequest, (xtimeVal<<4), 0x6300, 0);
+	if (gspca_dev->usb_err < 0)
+		gspca_err(gspca_dev, "usb error in setexposure(time) sequence\n");
+}
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;/* !! must be the first item */
+
+	/* exposure & gain controls */
+	struct {
+		struct v4l2_ctrl *exposure;
+		struct v4l2_ctrl *gain;
+	};
+};
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+	container_of(ctrl->handler,
+		struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_EXPOSURE:
+		dtcs033_setexposure(gspca_dev,
+				ctrl->val, sd->gain->val);
+		break;
+	case V4L2_CID_GAIN:
+		dtcs033_setexposure(gspca_dev,
+				sd->exposure->val, ctrl->val);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int dtcs033_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 2);
+	/*                               min max step default */
+	sd->exposure = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+				V4L2_CID_EXPOSURE,
+				1,  150,  1,  75);/* [msec] */
+	sd->gain     = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+				V4L2_CID_GAIN,
+				14,  33,  1,  24);/* [dB] */
+	if (hdl->error) {
+		gspca_err(gspca_dev, "Could not initialize controls: %d\n",
+			  hdl->error);
+		return hdl->error;
+	}
+
+	v4l2_ctrl_cluster(2, &sd->exposure);
+	return 0;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name     = MODULE_NAME,
+	.config   = sd_config,
+	.init     = sd_init,
+	.start    = dtcs033_start,
+	.stopN    = dtcs033_stopN,
+	.pkt_scan = dtcs033_pkt_scan,
+	.init_controls = dtcs033_init_controls,
+};
+
+/* -- module initialisation -- */
+
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x0547, 0x7303)},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* device connect */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id,
+			&sd_desc, sizeof(struct sd),
+			THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name       = MODULE_NAME,
+	.id_table   = device_table,
+	.probe      = sd_probe,
+	.disconnect   = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend      = gspca_suspend,
+	.resume       = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+module_usb_driver(sd_driver);
+
+
+/* ---------------------------------------------------------
+ USB requests to start/stop the camera [USB 2.0 spec Ch.9].
+
+ bRequestType :
+ 0x40 =  USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ 0xC0 =  USB_DIR_IN  | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+*/
+static const struct dtcs033_usb_requests dtcs033_start_reqs[] = {
+/* -- bRequest,wValue,wIndex,wLength */
+{ 0x40, 0x01, 0x0001, 0x000F, 0x0000 },
+{ 0x40, 0x01, 0x0000, 0x000F, 0x0000 },
+{ 0x40, 0x01, 0x0001, 0x000F, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x7F00, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x1001, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x0004, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x7F01, 0x0000 },
+{ 0x40, 0x18, 0x30E0, 0x0009, 0x0000 },
+{ 0x40, 0x18, 0x0500, 0x012C, 0x0000 },
+{ 0x40, 0x18, 0x0380, 0x0200, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x035C, 0x0000 },
+{ 0x40, 0x18, 0x05C0, 0x0438, 0x0000 },
+{ 0x40, 0x18, 0x0440, 0x0500, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x0668, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x0700, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x0800, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x0900, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x0A00, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x0B00, 0x0000 },
+{ 0x40, 0x18, 0x30E0, 0x6009, 0x0000 },
+{ 0x40, 0x18, 0x0500, 0x612C, 0x0000 },
+{ 0x40, 0x18, 0x2090, 0x6274, 0x0000 },
+{ 0x40, 0x18, 0x05C0, 0x6338, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x6400, 0x0000 },
+{ 0x40, 0x18, 0x05C0, 0x6538, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x6600, 0x0000 },
+{ 0x40, 0x18, 0x0680, 0x6744, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x6800, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x6900, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x6A00, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x6B00, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x6C00, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x6D00, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x6E00, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x808C, 0x0000 },
+{ 0x40, 0x18, 0x0010, 0x8101, 0x0000 },
+{ 0x40, 0x18, 0x30E0, 0x8200, 0x0000 },
+{ 0x40, 0x18, 0x0810, 0x832C, 0x0000 },
+{ 0x40, 0x18, 0x0680, 0x842B, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x8500, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x8600, 0x0000 },
+{ 0x40, 0x18, 0x0280, 0x8715, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x880C, 0x0000 },
+{ 0x40, 0x18, 0x0010, 0x8901, 0x0000 },
+{ 0x40, 0x18, 0x30E0, 0x8A00, 0x0000 },
+{ 0x40, 0x18, 0x0810, 0x8B2C, 0x0000 },
+{ 0x40, 0x18, 0x0680, 0x8C2B, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x8D00, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x8E00, 0x0000 },
+{ 0x40, 0x18, 0x0280, 0x8F15, 0x0000 },
+{ 0x40, 0x18, 0x0010, 0xD040, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0xD100, 0x0000 },
+{ 0x40, 0x18, 0x00B0, 0xD20A, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0xD300, 0x0000 },
+{ 0x40, 0x18, 0x30E2, 0xD40D, 0x0000 },
+{ 0x40, 0x18, 0x0001, 0xD5C0, 0x0000 },
+{ 0x40, 0x18, 0x00A0, 0xD60A, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0xD700, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x7F00, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x1501, 0x0000 },
+{ 0x40, 0x18, 0x0001, 0x01FF, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x0200, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x0304, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x1101, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x1201, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x1300, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x1400, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x1601, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x1800, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x1900, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x1A00, 0x0000 },
+{ 0x40, 0x18, 0x2000, 0x1B00, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x1C00, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x2100, 0x0000 },
+{ 0x40, 0x18, 0x00C0, 0x228E, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x3001, 0x0000 },
+{ 0x40, 0x18, 0x0010, 0x3101, 0x0000 },
+{ 0x40, 0x18, 0x0008, 0x3301, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x3400, 0x0000 },
+{ 0x40, 0x18, 0x0012, 0x3549, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x3620, 0x0000 },
+{ 0x40, 0x18, 0x0001, 0x3700, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x4000, 0x0000 },
+{ 0x40, 0x18, 0xFFFF, 0x41FF, 0x0000 },
+{ 0x40, 0x18, 0xFFFF, 0x42FF, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x500F, 0x0000 },
+{ 0x40, 0x18, 0x2272, 0x5108, 0x0000 },
+{ 0x40, 0x18, 0x2272, 0x5208, 0x0000 },
+{ 0x40, 0x18, 0xFFFF, 0x53FF, 0x0000 },
+{ 0x40, 0x18, 0xFFFF, 0x54FF, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x6000, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x6102, 0x0000 },
+{ 0x40, 0x18, 0x0010, 0x6214, 0x0000 },
+{ 0x40, 0x18, 0x0C80, 0x6300, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x6401, 0x0000 },
+{ 0x40, 0x18, 0x0680, 0x6551, 0x0000 },
+{ 0x40, 0x18, 0xFFFF, 0x66FF, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x6702, 0x0000 },
+{ 0x40, 0x18, 0x0010, 0x6800, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x6900, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x6A00, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x6B00, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x6C00, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x6D01, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x6E00, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x6F00, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x7000, 0x0000 },
+{ 0x40, 0x18, 0x0001, 0x7118, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x2001, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x1101, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x1301, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x1300, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x1501, 0x0000 },
+{ 0xC0, 0x11, 0x0000, 0x24C0, 0x0003 },
+{ 0x40, 0x18, 0x0000, 0x3000, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x3620, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x1501, 0x0000 },
+{ 0x40, 0x18, 0x0010, 0x6300, 0x0000 },
+{ 0x40, 0x18, 0x0002, 0x01F0, 0x0000 },
+{ 0x40, 0x01, 0x0003, 0x000F, 0x0000 }
+};
+
+static const struct dtcs033_usb_requests dtcs033_stop_reqs[] = {
+/* -- bRequest,wValue,wIndex,wLength */
+{ 0x40, 0x01, 0x0001, 0x000F, 0x0000 },
+{ 0x40, 0x01, 0x0000, 0x000F, 0x0000 },
+{ 0x40, 0x18, 0x0000, 0x0003, 0x0000 }
+};
+static int dtcs033_start(struct gspca_dev *gspca_dev)
+{
+	return reg_reqs(gspca_dev, dtcs033_start_reqs,
+		ARRAY_SIZE(dtcs033_start_reqs));
+}
+
+static void dtcs033_stopN(struct gspca_dev *gspca_dev)
+{
+	reg_reqs(gspca_dev, dtcs033_stop_reqs,
+		ARRAY_SIZE(dtcs033_stop_reqs));
+	return;
+}
diff --git a/drivers/media/usb/gspca/etoms.c b/drivers/media/usb/gspca/etoms.c
new file mode 100644
index 0000000..48b2889
--- /dev/null
+++ b/drivers/media/usb/gspca/etoms.c
@@ -0,0 +1,791 @@
+/*
+ * Etoms Et61x151 GPL Linux driver by Michel Xhaard (09/09/2004)
+ *
+ * V4L2 by Jean-Francois Moine <http://moinejf.free.fr>
+ *
+ * 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
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "etoms"
+
+#include "gspca.h"
+
+MODULE_AUTHOR("Michel Xhaard <mxhaard@users.sourceforge.net>");
+MODULE_DESCRIPTION("Etoms USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+
+	unsigned char autogain;
+
+	char sensor;
+#define SENSOR_PAS106 0
+#define SENSOR_TAS5130CXX 1
+	signed char ag_cnt;
+#define AG_CNT_START 13
+};
+
+static const struct v4l2_pix_format vga_mode[] = {
+	{320, 240, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1},
+/*	{640, 480, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0}, */
+};
+
+static const struct v4l2_pix_format sif_mode[] = {
+	{176, 144, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 176,
+		.sizeimage = 176 * 144,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1},
+	{352, 288, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 352,
+		.sizeimage = 352 * 288,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+};
+
+#define ETOMS_ALT_SIZE_1000   12
+
+#define ET_GPIO_DIR_CTRL 0x04	/* Control IO bit[0..5] (0 in  1 out) */
+#define ET_GPIO_OUT 0x05	/* Only IO data */
+#define ET_GPIO_IN 0x06		/* Read Only IO data */
+#define ET_RESET_ALL 0x03
+#define ET_ClCK 0x01
+#define ET_CTRL 0x02		/* enable i2c OutClck Powerdown mode */
+
+#define ET_COMP 0x12		/* Compression register */
+#define ET_MAXQt 0x13
+#define ET_MINQt 0x14
+#define ET_COMP_VAL0 0x02
+#define ET_COMP_VAL1 0x03
+
+#define ET_REG1d 0x1d
+#define ET_REG1e 0x1e
+#define ET_REG1f 0x1f
+#define ET_REG20 0x20
+#define ET_REG21 0x21
+#define ET_REG22 0x22
+#define ET_REG23 0x23
+#define ET_REG24 0x24
+#define ET_REG25 0x25
+/* base registers for luma calculation */
+#define ET_LUMA_CENTER 0x39
+
+#define ET_G_RED 0x4d
+#define ET_G_GREEN1 0x4e
+#define ET_G_BLUE 0x4f
+#define ET_G_GREEN2 0x50
+#define ET_G_GR_H 0x51
+#define ET_G_GB_H 0x52
+
+#define ET_O_RED 0x34
+#define ET_O_GREEN1 0x35
+#define ET_O_BLUE 0x36
+#define ET_O_GREEN2 0x37
+
+#define ET_SYNCHRO 0x68
+#define ET_STARTX 0x69
+#define ET_STARTY 0x6a
+#define ET_WIDTH_LOW 0x6b
+#define ET_HEIGTH_LOW 0x6c
+#define ET_W_H_HEIGTH 0x6d
+
+#define ET_REG6e 0x6e		/* OBW */
+#define ET_REG6f 0x6f		/* OBW */
+#define ET_REG70 0x70		/* OBW_AWB */
+#define ET_REG71 0x71		/* OBW_AWB */
+#define ET_REG72 0x72		/* OBW_AWB */
+#define ET_REG73 0x73		/* Clkdelay ns */
+#define ET_REG74 0x74		/* test pattern */
+#define ET_REG75 0x75		/* test pattern */
+
+#define ET_I2C_CLK 0x8c
+#define ET_PXL_CLK 0x60
+
+#define ET_I2C_BASE 0x89
+#define ET_I2C_COUNT 0x8a
+#define ET_I2C_PREFETCH 0x8b
+#define ET_I2C_REG 0x88
+#define ET_I2C_DATA7 0x87
+#define ET_I2C_DATA6 0x86
+#define ET_I2C_DATA5 0x85
+#define ET_I2C_DATA4 0x84
+#define ET_I2C_DATA3 0x83
+#define ET_I2C_DATA2 0x82
+#define ET_I2C_DATA1 0x81
+#define ET_I2C_DATA0 0x80
+
+#define PAS106_REG2 0x02	/* pxlClk = systemClk/(reg2) */
+#define PAS106_REG3 0x03	/* line/frame H [11..4] */
+#define PAS106_REG4 0x04	/* line/frame L [3..0] */
+#define PAS106_REG5 0x05	/* exposure time line offset(default 5) */
+#define PAS106_REG6 0x06	/* exposure time pixel offset(default 6) */
+#define PAS106_REG7 0x07	/* signbit Dac (default 0) */
+#define PAS106_REG9 0x09
+#define PAS106_REG0e 0x0e	/* global gain [4..0](default 0x0e) */
+#define PAS106_REG13 0x13	/* end i2c write */
+
+static const __u8 GainRGBG[] = { 0x80, 0x80, 0x80, 0x80, 0x00, 0x00 };
+
+static const __u8 I2c2[] = { 0x08, 0x08, 0x08, 0x08, 0x0d };
+
+static const __u8 I2c3[] = { 0x12, 0x05 };
+
+static const __u8 I2c4[] = { 0x41, 0x08 };
+
+/* read 'len' bytes to gspca_dev->usb_buf */
+static void reg_r(struct gspca_dev *gspca_dev,
+		  __u16 index,
+		  __u16 len)
+{
+	struct usb_device *dev = gspca_dev->dev;
+
+	if (len > USB_BUF_SZ) {
+		gspca_err(gspca_dev, "reg_r: buffer overflow\n");
+		return;
+	}
+
+	usb_control_msg(dev,
+			usb_rcvctrlpipe(dev, 0),
+			0,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+			0,
+			index, gspca_dev->usb_buf, len, 500);
+	gspca_dbg(gspca_dev, D_USBI, "reg read [%02x] -> %02x ..\n",
+		  index, gspca_dev->usb_buf[0]);
+}
+
+static void reg_w_val(struct gspca_dev *gspca_dev,
+			__u16 index,
+			__u8 val)
+{
+	struct usb_device *dev = gspca_dev->dev;
+
+	gspca_dev->usb_buf[0] = val;
+	usb_control_msg(dev,
+			usb_sndctrlpipe(dev, 0),
+			0,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+			0,
+			index, gspca_dev->usb_buf, 1, 500);
+}
+
+static void reg_w(struct gspca_dev *gspca_dev,
+		  __u16 index,
+		  const __u8 *buffer,
+		  __u16 len)
+{
+	struct usb_device *dev = gspca_dev->dev;
+
+	if (len > USB_BUF_SZ) {
+		pr_err("reg_w: buffer overflow\n");
+		return;
+	}
+	gspca_dbg(gspca_dev, D_USBO, "reg write [%02x] = %02x..\n",
+		  index, *buffer);
+
+	memcpy(gspca_dev->usb_buf, buffer, len);
+	usb_control_msg(dev,
+			usb_sndctrlpipe(dev, 0),
+			0,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+			0, index, gspca_dev->usb_buf, len, 500);
+}
+
+static int i2c_w(struct gspca_dev *gspca_dev,
+		 __u8 reg,
+		 const __u8 *buffer,
+		 int len, __u8 mode)
+{
+	/* buffer should be [D0..D7] */
+	__u8 ptchcount;
+
+	/* set the base address */
+	reg_w_val(gspca_dev, ET_I2C_BASE, 0x40);
+					 /* sensor base for the pas106 */
+	/* set count and prefetch */
+	ptchcount = ((len & 0x07) << 4) | (mode & 0x03);
+	reg_w_val(gspca_dev, ET_I2C_COUNT, ptchcount);
+	/* set the register base */
+	reg_w_val(gspca_dev, ET_I2C_REG, reg);
+	while (--len >= 0)
+		reg_w_val(gspca_dev, ET_I2C_DATA0 + len, buffer[len]);
+	return 0;
+}
+
+static int i2c_r(struct gspca_dev *gspca_dev,
+			__u8 reg)
+{
+	/* set the base address */
+	reg_w_val(gspca_dev, ET_I2C_BASE, 0x40);
+					/* sensor base for the pas106 */
+	/* set count and prefetch (cnd: 4 bits - mode: 4 bits) */
+	reg_w_val(gspca_dev, ET_I2C_COUNT, 0x11);
+	reg_w_val(gspca_dev, ET_I2C_REG, reg);	/* set the register base */
+	reg_w_val(gspca_dev, ET_I2C_PREFETCH, 0x02);	/* prefetch */
+	reg_w_val(gspca_dev, ET_I2C_PREFETCH, 0x00);
+	reg_r(gspca_dev, ET_I2C_DATA0, 1);	/* read one byte */
+	return 0;
+}
+
+static int Et_WaitStatus(struct gspca_dev *gspca_dev)
+{
+	int retry = 10;
+
+	while (retry--) {
+		reg_r(gspca_dev, ET_ClCK, 1);
+		if (gspca_dev->usb_buf[0] != 0)
+			return 1;
+	}
+	return 0;
+}
+
+static int et_video(struct gspca_dev *gspca_dev,
+		    int on)
+{
+	int ret;
+
+	reg_w_val(gspca_dev, ET_GPIO_OUT,
+		  on ? 0x10		/* startvideo - set Bit5 */
+		     : 0);		/* stopvideo */
+	ret = Et_WaitStatus(gspca_dev);
+	if (ret != 0)
+		gspca_err(gspca_dev, "timeout video on/off\n");
+	return ret;
+}
+
+static void Et_init2(struct gspca_dev *gspca_dev)
+{
+	__u8 value;
+	static const __u8 FormLine[] = { 0x84, 0x03, 0x14, 0xf4, 0x01, 0x05 };
+
+	gspca_dbg(gspca_dev, D_STREAM, "Open Init2 ET\n");
+	reg_w_val(gspca_dev, ET_GPIO_DIR_CTRL, 0x2f);
+	reg_w_val(gspca_dev, ET_GPIO_OUT, 0x10);
+	reg_r(gspca_dev, ET_GPIO_IN, 1);
+	reg_w_val(gspca_dev, ET_ClCK, 0x14); /* 0x14 // 0x16 enabled pattern */
+	reg_w_val(gspca_dev, ET_CTRL, 0x1b);
+
+	/*  compression et subsampling */
+	if (gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv)
+		value = ET_COMP_VAL1;	/* 320 */
+	else
+		value = ET_COMP_VAL0;	/* 640 */
+	reg_w_val(gspca_dev, ET_COMP, value);
+	reg_w_val(gspca_dev, ET_MAXQt, 0x1f);
+	reg_w_val(gspca_dev, ET_MINQt, 0x04);
+	/* undocumented registers */
+	reg_w_val(gspca_dev, ET_REG1d, 0xff);
+	reg_w_val(gspca_dev, ET_REG1e, 0xff);
+	reg_w_val(gspca_dev, ET_REG1f, 0xff);
+	reg_w_val(gspca_dev, ET_REG20, 0x35);
+	reg_w_val(gspca_dev, ET_REG21, 0x01);
+	reg_w_val(gspca_dev, ET_REG22, 0x00);
+	reg_w_val(gspca_dev, ET_REG23, 0xff);
+	reg_w_val(gspca_dev, ET_REG24, 0xff);
+	reg_w_val(gspca_dev, ET_REG25, 0x0f);
+	/* colors setting */
+	reg_w_val(gspca_dev, 0x30, 0x11);		/* 0x30 */
+	reg_w_val(gspca_dev, 0x31, 0x40);
+	reg_w_val(gspca_dev, 0x32, 0x00);
+	reg_w_val(gspca_dev, ET_O_RED, 0x00);		/* 0x34 */
+	reg_w_val(gspca_dev, ET_O_GREEN1, 0x00);
+	reg_w_val(gspca_dev, ET_O_BLUE, 0x00);
+	reg_w_val(gspca_dev, ET_O_GREEN2, 0x00);
+	/*************/
+	reg_w_val(gspca_dev, ET_G_RED, 0x80);		/* 0x4d */
+	reg_w_val(gspca_dev, ET_G_GREEN1, 0x80);
+	reg_w_val(gspca_dev, ET_G_BLUE, 0x80);
+	reg_w_val(gspca_dev, ET_G_GREEN2, 0x80);
+	reg_w_val(gspca_dev, ET_G_GR_H, 0x00);
+	reg_w_val(gspca_dev, ET_G_GB_H, 0x00);		/* 0x52 */
+	/* Window control registers */
+	reg_w_val(gspca_dev, 0x61, 0x80);		/* use cmc_out */
+	reg_w_val(gspca_dev, 0x62, 0x02);
+	reg_w_val(gspca_dev, 0x63, 0x03);
+	reg_w_val(gspca_dev, 0x64, 0x14);
+	reg_w_val(gspca_dev, 0x65, 0x0e);
+	reg_w_val(gspca_dev, 0x66, 0x02);
+	reg_w_val(gspca_dev, 0x67, 0x02);
+
+	/**************************************/
+	reg_w_val(gspca_dev, ET_SYNCHRO, 0x8f);		/* 0x68 */
+	reg_w_val(gspca_dev, ET_STARTX, 0x69);		/* 0x6a //0x69 */
+	reg_w_val(gspca_dev, ET_STARTY, 0x0d);		/* 0x0d //0x0c */
+	reg_w_val(gspca_dev, ET_WIDTH_LOW, 0x80);
+	reg_w_val(gspca_dev, ET_HEIGTH_LOW, 0xe0);
+	reg_w_val(gspca_dev, ET_W_H_HEIGTH, 0x60);	/* 6d */
+	reg_w_val(gspca_dev, ET_REG6e, 0x86);
+	reg_w_val(gspca_dev, ET_REG6f, 0x01);
+	reg_w_val(gspca_dev, ET_REG70, 0x26);
+	reg_w_val(gspca_dev, ET_REG71, 0x7a);
+	reg_w_val(gspca_dev, ET_REG72, 0x01);
+	/* Clock Pattern registers ***************** */
+	reg_w_val(gspca_dev, ET_REG73, 0x00);
+	reg_w_val(gspca_dev, ET_REG74, 0x18);		/* 0x28 */
+	reg_w_val(gspca_dev, ET_REG75, 0x0f);		/* 0x01 */
+	/**********************************************/
+	reg_w_val(gspca_dev, 0x8a, 0x20);
+	reg_w_val(gspca_dev, 0x8d, 0x0f);
+	reg_w_val(gspca_dev, 0x8e, 0x08);
+	/**************************************/
+	reg_w_val(gspca_dev, 0x03, 0x08);
+	reg_w_val(gspca_dev, ET_PXL_CLK, 0x03);
+	reg_w_val(gspca_dev, 0x81, 0xff);
+	reg_w_val(gspca_dev, 0x80, 0x00);
+	reg_w_val(gspca_dev, 0x81, 0xff);
+	reg_w_val(gspca_dev, 0x80, 0x20);
+	reg_w_val(gspca_dev, 0x03, 0x01);
+	reg_w_val(gspca_dev, 0x03, 0x00);
+	reg_w_val(gspca_dev, 0x03, 0x08);
+	/********************************************/
+
+/*	reg_r(gspca_dev, ET_I2C_BASE, 1);
+					 always 0x40 as the pas106 ??? */
+	/* set the sensor */
+	if (gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv)
+		value = 0x04;		/* 320 */
+	else				/* 640 */
+		value = 0x1e;	/* 0x17	 * setting PixelClock
+					 * 0x03 mean 24/(3+1) = 6 Mhz
+					 * 0x05 -> 24/(5+1) = 4 Mhz
+					 * 0x0b -> 24/(11+1) = 2 Mhz
+					 * 0x17 -> 24/(23+1) = 1 Mhz
+					 */
+	reg_w_val(gspca_dev, ET_PXL_CLK, value);
+	/* now set by fifo the FormatLine setting */
+	reg_w(gspca_dev, 0x62, FormLine, 6);
+
+	/* set exposure times [ 0..0x78] 0->longvalue 0x78->shortvalue */
+	reg_w_val(gspca_dev, 0x81, 0x47);	/* 0x47; */
+	reg_w_val(gspca_dev, 0x80, 0x40);	/* 0x40; */
+	/* Pedro change */
+	/* Brightness change Brith+ decrease value */
+	/* Brigth- increase value */
+	/* original value = 0x70; */
+	reg_w_val(gspca_dev, 0x81, 0x30);	/* 0x20; - set brightness */
+	reg_w_val(gspca_dev, 0x80, 0x20);	/* 0x20; */
+}
+
+static void setbrightness(struct gspca_dev *gspca_dev, s32 val)
+{
+	int i;
+
+	for (i = 0; i < 4; i++)
+		reg_w_val(gspca_dev, ET_O_RED + i, val);
+}
+
+static void setcontrast(struct gspca_dev *gspca_dev, s32 val)
+{
+	__u8 RGBG[] = { 0x80, 0x80, 0x80, 0x80, 0x00, 0x00 };
+
+	memset(RGBG, val, sizeof(RGBG) - 2);
+	reg_w(gspca_dev, ET_G_RED, RGBG, 6);
+}
+
+static void setcolors(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	__u8 I2cc[] = { 0x05, 0x02, 0x02, 0x05, 0x0d };
+	__u8 i2cflags = 0x01;
+	/* __u8 green = 0; */
+
+	I2cc[3] = val;	/* red */
+	I2cc[0] = 15 - val;	/* blue */
+	/* green = 15 - ((((7*I2cc[0]) >> 2 ) + I2cc[3]) >> 1); */
+	/* I2cc[1] = I2cc[2] = green; */
+	if (sd->sensor == SENSOR_PAS106) {
+		i2c_w(gspca_dev, PAS106_REG13, &i2cflags, 1, 3);
+		i2c_w(gspca_dev, PAS106_REG9, I2cc, sizeof I2cc, 1);
+	}
+}
+
+static s32 getcolors(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->sensor == SENSOR_PAS106) {
+/*		i2c_r(gspca_dev, PAS106_REG9);		 * blue */
+		i2c_r(gspca_dev, PAS106_REG9 + 3);	/* red */
+		return gspca_dev->usb_buf[0] & 0x0f;
+	}
+	return 0;
+}
+
+static void setautogain(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->autogain)
+		sd->ag_cnt = AG_CNT_START;
+	else
+		sd->ag_cnt = -1;
+}
+
+static void Et_init1(struct gspca_dev *gspca_dev)
+{
+	__u8 value;
+/*	__u8 I2c0 [] = {0x0a, 0x12, 0x05, 0x22, 0xac, 0x00, 0x01, 0x00}; */
+	__u8 I2c0[] = { 0x0a, 0x12, 0x05, 0x6d, 0xcd, 0x00, 0x01, 0x00 };
+						/* try 1/120 0x6d 0xcd 0x40 */
+/*	__u8 I2c0 [] = {0x0a, 0x12, 0x05, 0xfe, 0xfe, 0xc0, 0x01, 0x00};
+						 * 1/60000 hmm ?? */
+
+	gspca_dbg(gspca_dev, D_STREAM, "Open Init1 ET\n\n");
+	reg_w_val(gspca_dev, ET_GPIO_DIR_CTRL, 7);
+	reg_r(gspca_dev, ET_GPIO_IN, 1);
+	reg_w_val(gspca_dev, ET_RESET_ALL, 1);
+	reg_w_val(gspca_dev, ET_RESET_ALL, 0);
+	reg_w_val(gspca_dev, ET_ClCK, 0x10);
+	reg_w_val(gspca_dev, ET_CTRL, 0x19);
+	/*   compression et subsampling */
+	if (gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv)
+		value = ET_COMP_VAL1;
+	else
+		value = ET_COMP_VAL0;
+	gspca_dbg(gspca_dev, D_STREAM, "Open mode %d Compression %d\n",
+		  gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv,
+		  value);
+	reg_w_val(gspca_dev, ET_COMP, value);
+	reg_w_val(gspca_dev, ET_MAXQt, 0x1d);
+	reg_w_val(gspca_dev, ET_MINQt, 0x02);
+	/* undocumented registers */
+	reg_w_val(gspca_dev, ET_REG1d, 0xff);
+	reg_w_val(gspca_dev, ET_REG1e, 0xff);
+	reg_w_val(gspca_dev, ET_REG1f, 0xff);
+	reg_w_val(gspca_dev, ET_REG20, 0x35);
+	reg_w_val(gspca_dev, ET_REG21, 0x01);
+	reg_w_val(gspca_dev, ET_REG22, 0x00);
+	reg_w_val(gspca_dev, ET_REG23, 0xf7);
+	reg_w_val(gspca_dev, ET_REG24, 0xff);
+	reg_w_val(gspca_dev, ET_REG25, 0x07);
+	/* colors setting */
+	reg_w_val(gspca_dev, ET_G_RED, 0x80);
+	reg_w_val(gspca_dev, ET_G_GREEN1, 0x80);
+	reg_w_val(gspca_dev, ET_G_BLUE, 0x80);
+	reg_w_val(gspca_dev, ET_G_GREEN2, 0x80);
+	reg_w_val(gspca_dev, ET_G_GR_H, 0x00);
+	reg_w_val(gspca_dev, ET_G_GB_H, 0x00);
+	/* Window control registers */
+	reg_w_val(gspca_dev, ET_SYNCHRO, 0xf0);
+	reg_w_val(gspca_dev, ET_STARTX, 0x56);		/* 0x56 */
+	reg_w_val(gspca_dev, ET_STARTY, 0x05);		/* 0x04 */
+	reg_w_val(gspca_dev, ET_WIDTH_LOW, 0x60);
+	reg_w_val(gspca_dev, ET_HEIGTH_LOW, 0x20);
+	reg_w_val(gspca_dev, ET_W_H_HEIGTH, 0x50);
+	reg_w_val(gspca_dev, ET_REG6e, 0x86);
+	reg_w_val(gspca_dev, ET_REG6f, 0x01);
+	reg_w_val(gspca_dev, ET_REG70, 0x86);
+	reg_w_val(gspca_dev, ET_REG71, 0x14);
+	reg_w_val(gspca_dev, ET_REG72, 0x00);
+	/* Clock Pattern registers */
+	reg_w_val(gspca_dev, ET_REG73, 0x00);
+	reg_w_val(gspca_dev, ET_REG74, 0x00);
+	reg_w_val(gspca_dev, ET_REG75, 0x0a);
+	reg_w_val(gspca_dev, ET_I2C_CLK, 0x04);
+	reg_w_val(gspca_dev, ET_PXL_CLK, 0x01);
+	/* set the sensor */
+	if (gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv) {
+		I2c0[0] = 0x06;
+		i2c_w(gspca_dev, PAS106_REG2, I2c0, sizeof I2c0, 1);
+		i2c_w(gspca_dev, PAS106_REG9, I2c2, sizeof I2c2, 1);
+		value = 0x06;
+		i2c_w(gspca_dev, PAS106_REG2, &value, 1, 1);
+		i2c_w(gspca_dev, PAS106_REG3, I2c3, sizeof I2c3, 1);
+		/* value = 0x1f; */
+		value = 0x04;
+		i2c_w(gspca_dev, PAS106_REG0e, &value, 1, 1);
+	} else {
+		I2c0[0] = 0x0a;
+
+		i2c_w(gspca_dev, PAS106_REG2, I2c0, sizeof I2c0, 1);
+		i2c_w(gspca_dev, PAS106_REG9, I2c2, sizeof I2c2, 1);
+		value = 0x0a;
+		i2c_w(gspca_dev, PAS106_REG2, &value, 1, 1);
+		i2c_w(gspca_dev, PAS106_REG3, I2c3, sizeof I2c3, 1);
+		value = 0x04;
+		/* value = 0x10; */
+		i2c_w(gspca_dev, PAS106_REG0e, &value, 1, 1);
+		/* bit 2 enable bit 1:2 select 0 1 2 3
+		   value = 0x07;                                * curve 0 *
+		   i2c_w(gspca_dev, PAS106_REG0f, &value, 1, 1);
+		 */
+	}
+
+/*	value = 0x01; */
+/*	value = 0x22; */
+/*	i2c_w(gspca_dev, PAS106_REG5, &value, 1, 1); */
+	/* magnetude and sign bit for DAC */
+	i2c_w(gspca_dev, PAS106_REG7, I2c4, sizeof I2c4, 1);
+	/* now set by fifo the whole colors setting */
+	reg_w(gspca_dev, ET_G_RED, GainRGBG, 6);
+	setcolors(gspca_dev, getcolors(gspca_dev));
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+		     const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam;
+
+	cam = &gspca_dev->cam;
+	sd->sensor = id->driver_info;
+	if (sd->sensor == SENSOR_PAS106) {
+		cam->cam_mode = sif_mode;
+		cam->nmodes = ARRAY_SIZE(sif_mode);
+	} else {
+		cam->cam_mode = vga_mode;
+		cam->nmodes = ARRAY_SIZE(vga_mode);
+	}
+	sd->ag_cnt = -1;
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->sensor == SENSOR_PAS106)
+		Et_init1(gspca_dev);
+	else
+		Et_init2(gspca_dev);
+	reg_w_val(gspca_dev, ET_RESET_ALL, 0x08);
+	et_video(gspca_dev, 0);		/* video off */
+	return 0;
+}
+
+/* -- start the camera -- */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->sensor == SENSOR_PAS106)
+		Et_init1(gspca_dev);
+	else
+		Et_init2(gspca_dev);
+
+	setautogain(gspca_dev);
+
+	reg_w_val(gspca_dev, ET_RESET_ALL, 0x08);
+	et_video(gspca_dev, 1);		/* video on */
+	return 0;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	et_video(gspca_dev, 0);		/* video off */
+}
+
+static __u8 Et_getgainG(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->sensor == SENSOR_PAS106) {
+		i2c_r(gspca_dev, PAS106_REG0e);
+		gspca_dbg(gspca_dev, D_CONF, "Etoms gain G %d\n",
+			  gspca_dev->usb_buf[0]);
+		return gspca_dev->usb_buf[0];
+	}
+	return 0x1f;
+}
+
+static void Et_setgainG(struct gspca_dev *gspca_dev, __u8 gain)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->sensor == SENSOR_PAS106) {
+		__u8 i2cflags = 0x01;
+
+		i2c_w(gspca_dev, PAS106_REG13, &i2cflags, 1, 3);
+		i2c_w(gspca_dev, PAS106_REG0e, &gain, 1, 1);
+	}
+}
+
+#define BLIMIT(bright) \
+	(u8)((bright > 0x1f) ? 0x1f : ((bright < 4) ? 3 : bright))
+#define LIMIT(color) \
+	(u8)((color > 0xff) ? 0xff : ((color < 0) ? 0 : color))
+
+static void do_autogain(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	__u8 luma;
+	__u8 luma_mean = 128;
+	__u8 luma_delta = 20;
+	__u8 spring = 4;
+	int Gbright;
+	__u8 r, g, b;
+
+	if (sd->ag_cnt < 0)
+		return;
+	if (--sd->ag_cnt >= 0)
+		return;
+	sd->ag_cnt = AG_CNT_START;
+
+	Gbright = Et_getgainG(gspca_dev);
+	reg_r(gspca_dev, ET_LUMA_CENTER, 4);
+	g = (gspca_dev->usb_buf[0] + gspca_dev->usb_buf[3]) >> 1;
+	r = gspca_dev->usb_buf[1];
+	b = gspca_dev->usb_buf[2];
+	r = ((r << 8) - (r << 4) - (r << 3)) >> 10;
+	b = ((b << 7) >> 10);
+	g = ((g << 9) + (g << 7) + (g << 5)) >> 10;
+	luma = LIMIT(r + g + b);
+	gspca_dbg(gspca_dev, D_FRAM, "Etoms luma G %d\n", luma);
+	if (luma < luma_mean - luma_delta || luma > luma_mean + luma_delta) {
+		Gbright += (luma_mean - luma) >> spring;
+		Gbright = BLIMIT(Gbright);
+		gspca_dbg(gspca_dev, D_FRAM, "Etoms Gbright %d\n", Gbright);
+		Et_setgainG(gspca_dev, (__u8) Gbright);
+	}
+}
+
+#undef BLIMIT
+#undef LIMIT
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	int seqframe;
+
+	seqframe = data[0] & 0x3f;
+	len = (int) (((data[0] & 0xc0) << 2) | data[1]);
+	if (seqframe == 0x3f) {
+		gspca_dbg(gspca_dev, D_FRAM,
+			  "header packet found datalength %d !!\n", len);
+		gspca_dbg(gspca_dev, D_FRAM, "G %d R %d G %d B %d",
+			  data[2], data[3], data[4], data[5]);
+		data += 30;
+		/* don't change datalength as the chips provided it */
+		gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
+		gspca_frame_add(gspca_dev, FIRST_PACKET, data, len);
+		return;
+	}
+	if (len) {
+		data += 8;
+		gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+	} else {			/* Drop Packet */
+		gspca_dev->last_packet_type = DISCARD_PACKET;
+	}
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *)gspca_dev;
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		setbrightness(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_CONTRAST:
+		setcontrast(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_SATURATION:
+		setcolors(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_AUTOGAIN:
+		sd->autogain = ctrl->val;
+		setautogain(gspca_dev);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *)gspca_dev;
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 4);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 1, 127, 1, 63);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_CONTRAST, 0, 255, 1, 127);
+	if (sd->sensor == SENSOR_PAS106)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_SATURATION, 0, 15, 1, 7);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	return 0;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.pkt_scan = sd_pkt_scan,
+	.dq_callback = do_autogain,
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x102c, 0x6151), .driver_info = SENSOR_PAS106},
+	{USB_DEVICE(0x102c, 0x6251), .driver_info = SENSOR_TAS5130CXX},
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+		    const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+			       THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/finepix.c b/drivers/media/usb/gspca/finepix.c
new file mode 100644
index 0000000..1ef1239
--- /dev/null
+++ b/drivers/media/usb/gspca/finepix.c
@@ -0,0 +1,302 @@
+/*
+ * Fujifilm Finepix subdriver
+ *
+ * Copyright (C) 2008 Frank Zago
+ *
+ * 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
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "finepix"
+
+#include "gspca.h"
+
+MODULE_AUTHOR("Frank Zago <frank@zago.net>");
+MODULE_DESCRIPTION("Fujifilm FinePix USB V4L2 driver");
+MODULE_LICENSE("GPL");
+
+/* Default timeout, in ms */
+#define FPIX_TIMEOUT 250
+
+/* Maximum transfer size to use. The windows driver reads by chunks of
+ * 0x2000 bytes, so do the same. Note: reading more seems to work
+ * too. */
+#define FPIX_MAX_TRANSFER 0x2000
+
+/* Structure to hold all of our device specific stuff */
+struct usb_fpix {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+
+	struct work_struct work_struct;
+};
+
+/* Delay after which claim the next frame. If the delay is too small,
+ * the camera will return old frames. On the 4800Z, 20ms is bad, 25ms
+ * will fail every 4 or 5 frames, but 30ms is perfect. On the A210,
+ * 30ms is bad while 35ms is perfect. */
+#define NEXT_FRAME_DELAY 35
+
+/* These cameras only support 320x200. */
+static const struct v4l2_pix_format fpix_mode[1] = {
+	{ 320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0}
+};
+
+/* send a command to the webcam */
+static int command(struct gspca_dev *gspca_dev,
+		int order)	/* 0: reset, 1: frame request */
+{
+	static u8 order_values[2][12] = {
+		{0xc6, 0, 0, 0, 0, 0, 0,    0, 0x20, 0, 0, 0},	/* reset */
+		{0xd3, 0, 0, 0, 0, 0, 0, 0x01,    0, 0, 0, 0},	/* fr req */
+	};
+
+	memcpy(gspca_dev->usb_buf, order_values[order], 12);
+	return usb_control_msg(gspca_dev->dev,
+			usb_sndctrlpipe(gspca_dev->dev, 0),
+			USB_REQ_GET_STATUS,
+			USB_DIR_OUT | USB_TYPE_CLASS |
+			USB_RECIP_INTERFACE, 0, 0, gspca_dev->usb_buf,
+			12, FPIX_TIMEOUT);
+}
+
+/*
+ * This function is called as a workqueue function and runs whenever the camera
+ * is streaming data. Because it is a workqueue function it is allowed to sleep
+ * so we can use synchronous USB calls. To avoid possible collisions with other
+ * threads attempting to use gspca_dev->usb_buf we take the usb_lock when
+ * performing USB operations using it. In practice we don't really need this
+ * as the camera doesn't provide any controls.
+ */
+static void dostream(struct work_struct *work)
+{
+	struct usb_fpix *dev = container_of(work, struct usb_fpix, work_struct);
+	struct gspca_dev *gspca_dev = &dev->gspca_dev;
+	struct urb *urb = gspca_dev->urb[0];
+	u8 *data = urb->transfer_buffer;
+	int ret = 0;
+	int len;
+
+	gspca_dbg(gspca_dev, D_STREAM, "dostream started\n");
+
+	/* loop reading a frame */
+again:
+	while (gspca_dev->present && gspca_dev->streaming) {
+#ifdef CONFIG_PM
+		if (gspca_dev->frozen)
+			break;
+#endif
+
+		/* request a frame */
+		mutex_lock(&gspca_dev->usb_lock);
+		ret = command(gspca_dev, 1);
+		mutex_unlock(&gspca_dev->usb_lock);
+		if (ret < 0)
+			break;
+#ifdef CONFIG_PM
+		if (gspca_dev->frozen)
+			break;
+#endif
+		if (!gspca_dev->present || !gspca_dev->streaming)
+			break;
+
+		/* the frame comes in parts */
+		for (;;) {
+			ret = usb_bulk_msg(gspca_dev->dev,
+					urb->pipe,
+					data,
+					FPIX_MAX_TRANSFER,
+					&len, FPIX_TIMEOUT);
+			if (ret < 0) {
+				/* Most of the time we get a timeout
+				 * error. Just restart. */
+				goto again;
+			}
+#ifdef CONFIG_PM
+			if (gspca_dev->frozen)
+				goto out;
+#endif
+			if (!gspca_dev->present || !gspca_dev->streaming)
+				goto out;
+			if (len < FPIX_MAX_TRANSFER ||
+				(data[len - 2] == 0xff &&
+					data[len - 1] == 0xd9)) {
+
+				/* If the result is less than what was asked
+				 * for, then it's the end of the
+				 * frame. Sometimes the jpeg is not complete,
+				 * but there's nothing we can do. We also end
+				 * here if the the jpeg ends right at the end
+				 * of the frame. */
+				gspca_frame_add(gspca_dev, LAST_PACKET,
+						data, len);
+				break;
+			}
+
+			/* got a partial image */
+			gspca_frame_add(gspca_dev,
+					gspca_dev->last_packet_type
+						== LAST_PACKET
+					? FIRST_PACKET : INTER_PACKET,
+					data, len);
+		}
+
+		/* We must wait before trying reading the next
+		 * frame. If we don't, or if the delay is too short,
+		 * the camera will disconnect. */
+		msleep(NEXT_FRAME_DELAY);
+	}
+
+out:
+	gspca_dbg(gspca_dev, D_STREAM, "dostream stopped\n");
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+		const struct usb_device_id *id)
+{
+	struct usb_fpix *dev = (struct usb_fpix *) gspca_dev;
+	struct cam *cam = &gspca_dev->cam;
+
+	cam->cam_mode = fpix_mode;
+	cam->nmodes = 1;
+	cam->bulk = 1;
+	cam->bulk_size = FPIX_MAX_TRANSFER;
+
+	INIT_WORK(&dev->work_struct, dostream);
+
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	return 0;
+}
+
+/* start the camera */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct usb_fpix *dev = (struct usb_fpix *) gspca_dev;
+	int ret, len;
+
+	/* Init the device */
+	ret = command(gspca_dev, 0);
+	if (ret < 0) {
+		pr_err("init failed %d\n", ret);
+		return ret;
+	}
+
+	/* Read the result of the command. Ignore the result, for it
+	 * varies with the device. */
+	ret = usb_bulk_msg(gspca_dev->dev,
+			gspca_dev->urb[0]->pipe,
+			gspca_dev->urb[0]->transfer_buffer,
+			FPIX_MAX_TRANSFER, &len,
+			FPIX_TIMEOUT);
+	if (ret < 0) {
+		pr_err("usb_bulk_msg failed %d\n", ret);
+		return ret;
+	}
+
+	/* Request a frame, but don't read it */
+	ret = command(gspca_dev, 1);
+	if (ret < 0) {
+		pr_err("frame request failed %d\n", ret);
+		return ret;
+	}
+
+	/* Again, reset bulk in endpoint */
+	usb_clear_halt(gspca_dev->dev, gspca_dev->urb[0]->pipe);
+
+	schedule_work(&dev->work_struct);
+
+	return 0;
+}
+
+/* called on streamoff with alt==0 and on disconnect */
+/* the usb_lock is held at entry - restore on exit */
+static void sd_stop0(struct gspca_dev *gspca_dev)
+{
+	struct usb_fpix *dev = (struct usb_fpix *) gspca_dev;
+
+	/* wait for the work queue to terminate */
+	mutex_unlock(&gspca_dev->usb_lock);
+	flush_work(&dev->work_struct);
+	mutex_lock(&gspca_dev->usb_lock);
+}
+
+/* Table of supported USB devices */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x04cb, 0x0104)},
+	{USB_DEVICE(0x04cb, 0x0109)},
+	{USB_DEVICE(0x04cb, 0x010b)},
+	{USB_DEVICE(0x04cb, 0x010f)},
+	{USB_DEVICE(0x04cb, 0x0111)},
+	{USB_DEVICE(0x04cb, 0x0113)},
+	{USB_DEVICE(0x04cb, 0x0115)},
+	{USB_DEVICE(0x04cb, 0x0117)},
+	{USB_DEVICE(0x04cb, 0x0119)},
+	{USB_DEVICE(0x04cb, 0x011b)},
+	{USB_DEVICE(0x04cb, 0x011d)},
+	{USB_DEVICE(0x04cb, 0x0121)},
+	{USB_DEVICE(0x04cb, 0x0123)},
+	{USB_DEVICE(0x04cb, 0x0125)},
+	{USB_DEVICE(0x04cb, 0x0127)},
+	{USB_DEVICE(0x04cb, 0x0129)},
+	{USB_DEVICE(0x04cb, 0x012b)},
+	{USB_DEVICE(0x04cb, 0x012d)},
+	{USB_DEVICE(0x04cb, 0x012f)},
+	{USB_DEVICE(0x04cb, 0x0131)},
+	{USB_DEVICE(0x04cb, 0x013b)},
+	{USB_DEVICE(0x04cb, 0x013d)},
+	{USB_DEVICE(0x04cb, 0x013f)},
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name   = MODULE_NAME,
+	.config = sd_config,
+	.init   = sd_init,
+	.start  = sd_start,
+	.stop0  = sd_stop0,
+};
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id,
+			&sd_desc,
+			sizeof(struct usb_fpix),
+			THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name       = MODULE_NAME,
+	.id_table   = device_table,
+	.probe      = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume  = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/gl860/Kconfig b/drivers/media/usb/gspca/gl860/Kconfig
new file mode 100644
index 0000000..22772f5
--- /dev/null
+++ b/drivers/media/usb/gspca/gl860/Kconfig
@@ -0,0 +1,8 @@
+config USB_GL860
+	tristate "GL860 USB Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the GL860 chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_gl860.
diff --git a/drivers/media/usb/gspca/gl860/Makefile b/drivers/media/usb/gspca/gl860/Makefile
new file mode 100644
index 0000000..7bcfa36
--- /dev/null
+++ b/drivers/media/usb/gspca/gl860/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_USB_GL860) += gspca_gl860.o
+
+gspca_gl860-objs := gl860.o \
+		    gl860-mi1320.o \
+		    gl860-ov2640.o \
+		    gl860-ov9655.o \
+		    gl860-mi2020.o
+
+ccflags-y += -I$(srctree)/drivers/media/usb/gspca
+
diff --git a/drivers/media/usb/gspca/gl860/gl860-mi1320.c b/drivers/media/usb/gspca/gl860/gl860-mi1320.c
new file mode 100644
index 0000000..b57160e
--- /dev/null
+++ b/drivers/media/usb/gspca/gl860/gl860-mi1320.c
@@ -0,0 +1,536 @@
+/* Subdriver for the GL860 chip with the MI1320 sensor
+ * Author Olivier LORIN from own logs
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Sensor : MI1320 */
+
+#include "gl860.h"
+
+static struct validx tbl_common[] = {
+	{0xba00, 0x00f0}, {0xba00, 0x00f1}, {0xba51, 0x0066}, {0xba02, 0x00f1},
+	{0xba05, 0x0067}, {0xba05, 0x00f1}, {0xbaa0, 0x0065}, {0xba00, 0x00f1},
+	{0xffff, 0xffff},
+	{0xba00, 0x00f0}, {0xba02, 0x00f1}, {0xbafa, 0x0028}, {0xba02, 0x00f1},
+	{0xba00, 0x00f0}, {0xba01, 0x00f1}, {0xbaf0, 0x0006}, {0xba0e, 0x00f1},
+	{0xba70, 0x0006}, {0xba0e, 0x00f1},
+	{0xffff, 0xffff},
+	{0xba74, 0x0006}, {0xba0e, 0x00f1},
+	{0xffff, 0xffff},
+	{0x0061, 0x0000}, {0x0068, 0x000d},
+};
+
+static struct validx tbl_init_at_startup[] = {
+	{0x0000, 0x0000}, {0x0010, 0x0010},
+	{35, 0xffff},
+	{0x0008, 0x00c0}, {0x0001, 0x00c1}, {0x0001, 0x00c2}, {0x0020, 0x0006},
+	{0x006a, 0x000d},
+};
+
+static struct validx tbl_sensor_settings_common[] = {
+	{0x0010, 0x0010}, {0x0003, 0x00c1}, {0x0042, 0x00c2}, {0x0040, 0x0000},
+	{0x006a, 0x0007}, {0x006a, 0x000d}, {0x0063, 0x0006},
+};
+static struct validx tbl_sensor_settings_1280[] = {
+	{0xba00, 0x00f0}, {0xba00, 0x00f1}, {0xba5a, 0x0066}, {0xba02, 0x00f1},
+	{0xba05, 0x0067}, {0xba05, 0x00f1}, {0xba20, 0x0065}, {0xba00, 0x00f1},
+};
+static struct validx tbl_sensor_settings_800[] = {
+	{0xba00, 0x00f0}, {0xba00, 0x00f1}, {0xba5a, 0x0066}, {0xba02, 0x00f1},
+	{0xba05, 0x0067}, {0xba05, 0x00f1}, {0xba20, 0x0065}, {0xba00, 0x00f1},
+};
+static struct validx tbl_sensor_settings_640[] = {
+	{0xba00, 0x00f0}, {0xba00, 0x00f1}, {0xbaa0, 0x0065}, {0xba00, 0x00f1},
+	{0xba51, 0x0066}, {0xba02, 0x00f1}, {0xba05, 0x0067}, {0xba05, 0x00f1},
+	{0xba20, 0x0065}, {0xba00, 0x00f1},
+};
+static struct validx tbl_post_unset_alt[] = {
+	{0xba00, 0x00f0}, {0xba00, 0x00f1}, {0xbaa0, 0x0065}, {0xba00, 0x00f1},
+	{0x0061, 0x0000}, {0x0068, 0x000d},
+};
+
+static u8 *tbl_1280[] = {
+	"\x0d\x80\xf1\x08\x03\x04\xf1\x00" "\x04\x05\xf1\x02\x05\x00\xf1\xf1"
+	"\x06\x00\xf1\x0d\x20\x01\xf1\x00" "\x21\x84\xf1\x00\x0d\x00\xf1\x08"
+	"\xf0\x00\xf1\x01\x34\x00\xf1\x00" "\x9b\x43\xf1\x00\xa6\x05\xf1\x00"
+	"\xa9\x04\xf1\x00\xa1\x05\xf1\x00" "\xa4\x04\xf1\x00\xae\x0a\xf1\x08"
+	,
+	"\xf0\x00\xf1\x02\x3a\x05\xf1\xf1" "\x3c\x05\xf1\xf1\x59\x01\xf1\x47"
+	"\x5a\x01\xf1\x88\x5c\x0a\xf1\x06" "\x5d\x0e\xf1\x0a\x64\x5e\xf1\x1c"
+	"\xd2\x00\xf1\xcf\xcb\x00\xf1\x01"
+	,
+	"\xd3\x02\xd4\x28\xd5\x01\xd0\x02" "\xd1\x18\xd2\xc1"
+};
+
+static u8 *tbl_800[] = {
+	"\x0d\x80\xf1\x08\x03\x03\xf1\xc0" "\x04\x05\xf1\x02\x05\x00\xf1\xf1"
+	"\x06\x00\xf1\x0d\x20\x01\xf1\x00" "\x21\x84\xf1\x00\x0d\x00\xf1\x08"
+	"\xf0\x00\xf1\x01\x34\x00\xf1\x00" "\x9b\x43\xf1\x00\xa6\x05\xf1\x00"
+	"\xa9\x03\xf1\xc0\xa1\x03\xf1\x20" "\xa4\x02\xf1\x5a\xae\x0a\xf1\x08"
+	,
+	"\xf0\x00\xf1\x02\x3a\x05\xf1\xf1" "\x3c\x05\xf1\xf1\x59\x01\xf1\x47"
+	"\x5a\x01\xf1\x88\x5c\x0a\xf1\x06" "\x5d\x0e\xf1\x0a\x64\x5e\xf1\x1c"
+	"\xd2\x00\xf1\xcf\xcb\x00\xf1\x01"
+	,
+	"\xd3\x02\xd4\x18\xd5\x21\xd0\x02" "\xd1\x10\xd2\x59"
+};
+
+static u8 *tbl_640[] = {
+	"\x0d\x80\xf1\x08\x03\x04\xf1\x04" "\x04\x05\xf1\x02\x07\x01\xf1\x7c"
+	"\x08\x00\xf1\x0e\x21\x80\xf1\x00" "\x0d\x00\xf1\x08\xf0\x00\xf1\x01"
+	"\x34\x10\xf1\x10\x3a\x43\xf1\x00" "\xa6\x05\xf1\x02\xa9\x04\xf1\x04"
+	"\xa7\x02\xf1\x81\xaa\x01\xf1\xe2" "\xae\x0c\xf1\x09"
+	,
+	"\xf0\x00\xf1\x02\x39\x03\xf1\xfc" "\x3b\x04\xf1\x04\x57\x01\xf1\xb6"
+	"\x58\x02\xf1\x0d\x5c\x1f\xf1\x19" "\x5d\x24\xf1\x1e\x64\x5e\xf1\x1c"
+	"\xd2\x00\xf1\x00\xcb\x00\xf1\x01"
+	,
+	"\xd3\x02\xd4\x10\xd5\x81\xd0\x02" "\xd1\x08\xd2\xe1"
+};
+
+static s32 tbl_sat[] = {0x25, 0x1d, 0x15, 0x0d, 0x05, 0x4d, 0x55, 0x5d, 0x2d};
+static s32 tbl_bright[] = {0, 8, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70};
+static s32 tbl_backlight[] = {0x0e, 0x06, 0x02};
+
+static s32 tbl_cntr1[] = {
+	0x90, 0x98, 0xa0, 0xa8, 0xb0, 0xb8, 0xc0, 0xc8, 0xd0, 0xe0, 0xf0};
+static s32 tbl_cntr2[] = {
+	0x70, 0x68, 0x60, 0x58, 0x50, 0x48, 0x40, 0x38, 0x30, 0x20, 0x10};
+
+static u8 dat_wbalNL[] =
+	"\xf0\x00\xf1\x01\x05\x00\xf1\x06" "\x3b\x04\xf1\x2a\x47\x10\xf1\x10"
+	"\x9d\x3c\xf1\xae\xaf\x10\xf1\x00" "\xf0\x00\xf1\x02\x2f\x91\xf1\x20"
+	"\x9c\x91\xf1\x20\x37\x03\xf1\x00" "\x9d\xc5\xf1\x0f\xf0\x00\xf1\x00";
+
+static u8 dat_wbalLL[] =
+	"\xf0\x00\xf1\x01\x05\x00\xf1\x0c" "\x3b\x04\xf1\x2a\x47\x40\xf1\x40"
+	"\x9d\x20\xf1\xae\xaf\x10\xf1\x00" "\xf0\x00\xf1\x02\x2f\xd1\xf1\x00"
+	"\x9c\xd1\xf1\x00\x37\x03\xf1\x00" "\x9d\xc5\xf1\x3f\xf0\x00\xf1\x00";
+
+static u8 dat_wbalBL[] =
+	"\xf0\x00\xf1\x01\x05\x00\xf1\x06" "\x47\x10\xf1\x30\x9d\x3c\xf1\xae"
+	"\xaf\x10\xf1\x00\xf0\x00\xf1\x02" "\x2f\x91\xf1\x20\x9c\x91\xf1\x20"
+	"\x37\x03\xf1\x00\x9d\xc5\xf1\x2f" "\xf0\x00\xf1\x00";
+
+static u8 dat_hvflip1[] = {0xf0, 0x00, 0xf1, 0x00};
+
+static u8 dat_common00[] =
+	"\x00\x01\x07\x6a\x06\x63\x0d\x6a" "\xc0\x00\x10\x10\xc1\x03\xc2\x42"
+	"\xd8\x04\x58\x00\x04\x02";
+static u8 dat_common01[] =
+	"\x0d\x00\xf1\x0b\x0d\x00\xf1\x08" "\x35\x00\xf1\x22\x68\x00\xf1\x5d"
+	"\xf0\x00\xf1\x01\x06\x70\xf1\x0e" "\xf0\x00\xf1\x02\xdd\x18\xf1\xe0";
+static u8 dat_common02[] =
+	"\x05\x01\xf1\x84\x06\x00\xf1\x44" "\x07\x00\xf1\xbe\x08\x00\xf1\x1e"
+	"\x20\x01\xf1\x03\x21\x84\xf1\x00" "\x22\x0d\xf1\x0f\x24\x80\xf1\x00"
+	"\x34\x18\xf1\x2d\x35\x00\xf1\x22" "\x43\x83\xf1\x83\x59\x00\xf1\xff";
+static u8 dat_common03[] =
+	"\xf0\x00\xf1\x02\x39\x06\xf1\x8c" "\x3a\x06\xf1\x8c\x3b\x03\xf1\xda"
+	"\x3c\x05\xf1\x30\x57\x01\xf1\x0c" "\x58\x01\xf1\x42\x59\x01\xf1\x0c"
+	"\x5a\x01\xf1\x42\x5c\x13\xf1\x0e" "\x5d\x17\xf1\x12\x64\x1e\xf1\x1c";
+static u8 dat_common04[] =
+	"\xf0\x00\xf1\x02\x24\x5f\xf1\x20" "\x28\xea\xf1\x02\x5f\x41\xf1\x43";
+static u8 dat_common05[] =
+	"\x02\x00\xf1\xee\x03\x29\xf1\x1a" "\x04\x02\xf1\xa4\x09\x00\xf1\x68"
+	"\x0a\x00\xf1\x2a\x0b\x00\xf1\x04" "\x0c\x00\xf1\x93\x0d\x00\xf1\x82"
+	"\x0e\x00\xf1\x40\x0f\x00\xf1\x5f" "\x10\x00\xf1\x4e\x11\x00\xf1\x5b";
+static u8 dat_common06[] =
+	"\x15\x00\xf1\xc9\x16\x00\xf1\x5e" "\x17\x00\xf1\x9d\x18\x00\xf1\x06"
+	"\x19\x00\xf1\x89\x1a\x00\xf1\x12" "\x1b\x00\xf1\xa1\x1c\x00\xf1\xe4"
+	"\x1d\x00\xf1\x7a\x1e\x00\xf1\x64" "\xf6\x00\xf1\x5f";
+static u8 dat_common07[] =
+	"\xf0\x00\xf1\x01\x53\x09\xf1\x03" "\x54\x3d\xf1\x1c\x55\x99\xf1\x72"
+	"\x56\xc1\xf1\xb1\x57\xd8\xf1\xce" "\x58\xe0\xf1\x00\xdc\x0a\xf1\x03"
+	"\xdd\x45\xf1\x20\xde\xae\xf1\x82" "\xdf\xdc\xf1\xc9\xe0\xf6\xf1\xea"
+	"\xe1\xff\xf1\x00";
+static u8 dat_common08[] =
+	"\xf0\x00\xf1\x01\x80\x00\xf1\x06" "\x81\xf6\xf1\x08\x82\xfb\xf1\xf7"
+	"\x83\x00\xf1\xfe\xb6\x07\xf1\x03" "\xb7\x18\xf1\x0c\x84\xfb\xf1\x06"
+	"\x85\xfb\xf1\xf9\x86\x00\xf1\xff" "\xb8\x07\xf1\x04\xb9\x16\xf1\x0a";
+static u8 dat_common09[] =
+	"\x87\xfa\xf1\x05\x88\xfc\xf1\xf9" "\x89\x00\xf1\xff\xba\x06\xf1\x03"
+	"\xbb\x17\xf1\x09\x8a\xe8\xf1\x14" "\x8b\xf7\xf1\xf0\x8c\xfd\xf1\xfa"
+	"\x8d\x00\xf1\x00\xbc\x05\xf1\x01" "\xbd\x0c\xf1\x08\xbe\x00\xf1\x14";
+static u8 dat_common10[] =
+	"\x8e\xea\xf1\x13\x8f\xf7\xf1\xf2" "\x90\xfd\xf1\xfa\x91\x00\xf1\x00"
+	"\xbf\x05\xf1\x01\xc0\x0a\xf1\x08" "\xc1\x00\xf1\x0c\x92\xed\xf1\x0f"
+	"\x93\xf9\xf1\xf4\x94\xfe\xf1\xfb" "\x95\x00\xf1\x00\xc2\x04\xf1\x01"
+	"\xc3\x0a\xf1\x07\xc4\x00\xf1\x10";
+static u8 dat_common11[] =
+	"\xf0\x00\xf1\x01\x05\x00\xf1\x06" "\x25\x00\xf1\x55\x34\x10\xf1\x10"
+	"\x35\xf0\xf1\x10\x3a\x02\xf1\x03" "\x3b\x04\xf1\x2a\x9b\x43\xf1\x00"
+	"\xa4\x03\xf1\xc0\xa7\x02\xf1\x81";
+
+static int  mi1320_init_at_startup(struct gspca_dev *gspca_dev);
+static int  mi1320_configure_alt(struct gspca_dev *gspca_dev);
+static int  mi1320_init_pre_alt(struct gspca_dev *gspca_dev);
+static int  mi1320_init_post_alt(struct gspca_dev *gspca_dev);
+static void mi1320_post_unset_alt(struct gspca_dev *gspca_dev);
+static int  mi1320_sensor_settings(struct gspca_dev *gspca_dev);
+static int  mi1320_camera_settings(struct gspca_dev *gspca_dev);
+/*==========================================================================*/
+
+void mi1320_init_settings(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	sd->vcur.backlight  =  0;
+	sd->vcur.brightness =  0;
+	sd->vcur.sharpness  =  6;
+	sd->vcur.contrast   = 10;
+	sd->vcur.gamma      = 20;
+	sd->vcur.hue        =  0;
+	sd->vcur.saturation =  6;
+	sd->vcur.whitebal   =  0;
+	sd->vcur.mirror     = 0;
+	sd->vcur.flip       = 0;
+	sd->vcur.AC50Hz     = 1;
+
+	sd->vmax.backlight  =  2;
+	sd->vmax.brightness =  8;
+	sd->vmax.sharpness  =  7;
+	sd->vmax.contrast   =  0; /* 10 but not working with this driver */
+	sd->vmax.gamma      = 40;
+	sd->vmax.hue        =  5 + 1;
+	sd->vmax.saturation =  8;
+	sd->vmax.whitebal   =  2;
+	sd->vmax.mirror     = 1;
+	sd->vmax.flip       = 1;
+	sd->vmax.AC50Hz     = 1;
+
+	sd->dev_camera_settings = mi1320_camera_settings;
+	sd->dev_init_at_startup = mi1320_init_at_startup;
+	sd->dev_configure_alt   = mi1320_configure_alt;
+	sd->dev_init_pre_alt    = mi1320_init_pre_alt;
+	sd->dev_post_unset_alt  = mi1320_post_unset_alt;
+}
+
+/*==========================================================================*/
+
+static void common(struct gspca_dev *gspca_dev)
+{
+	s32 n; /* reserved for FETCH functions */
+
+	ctrl_out(gspca_dev, 0x40, 3, 0x0000, 0x0200, 22, dat_common00);
+	ctrl_out(gspca_dev, 0x40, 1, 0x0041, 0x0000, 0, NULL);
+	ctrl_out(gspca_dev, 0x40, 3, 0xba00, 0x0200, 32, dat_common01);
+	n = fetch_validx(gspca_dev, tbl_common, ARRAY_SIZE(tbl_common));
+	ctrl_out(gspca_dev, 0x40, 3, 0xba00, 0x0200, 48, dat_common02);
+	ctrl_out(gspca_dev, 0x40, 3, 0xba00, 0x0200, 48, dat_common03);
+	ctrl_out(gspca_dev, 0x40, 3, 0xba00, 0x0200, 16, dat_common04);
+	ctrl_out(gspca_dev, 0x40, 3, 0xba00, 0x0200, 48, dat_common05);
+	ctrl_out(gspca_dev, 0x40, 3, 0xba00, 0x0200, 44, dat_common06);
+	keep_on_fetching_validx(gspca_dev, tbl_common,
+					ARRAY_SIZE(tbl_common), n);
+	ctrl_out(gspca_dev, 0x40, 3, 0xba00, 0x0200, 52, dat_common07);
+	ctrl_out(gspca_dev, 0x40, 3, 0xba00, 0x0200, 48, dat_common08);
+	ctrl_out(gspca_dev, 0x40, 3, 0xba00, 0x0200, 48, dat_common09);
+	ctrl_out(gspca_dev, 0x40, 3, 0xba00, 0x0200, 56, dat_common10);
+	keep_on_fetching_validx(gspca_dev, tbl_common,
+					ARRAY_SIZE(tbl_common), n);
+	ctrl_out(gspca_dev, 0x40, 3, 0xba00, 0x0200, 40, dat_common11);
+	keep_on_fetching_validx(gspca_dev, tbl_common,
+					ARRAY_SIZE(tbl_common), n);
+}
+
+static int mi1320_init_at_startup(struct gspca_dev *gspca_dev)
+{
+	fetch_validx(gspca_dev, tbl_init_at_startup,
+				ARRAY_SIZE(tbl_init_at_startup));
+
+	common(gspca_dev);
+
+/*	ctrl_out(gspca_dev, 0x40, 11, 0x0000, 0x0000, 0, NULL); */
+
+	return 0;
+}
+
+static int mi1320_init_pre_alt(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	sd->mirrorMask = 0;
+
+	sd->vold.backlight  = -1;
+	sd->vold.brightness = -1;
+	sd->vold.sharpness  = -1;
+	sd->vold.contrast   = -1;
+	sd->vold.saturation = -1;
+	sd->vold.gamma    = -1;
+	sd->vold.hue      = -1;
+	sd->vold.whitebal = -1;
+	sd->vold.mirror   = -1;
+	sd->vold.flip     = -1;
+	sd->vold.AC50Hz   = -1;
+
+	common(gspca_dev);
+
+	mi1320_sensor_settings(gspca_dev);
+
+	mi1320_init_post_alt(gspca_dev);
+
+	return 0;
+}
+
+static int mi1320_init_post_alt(struct gspca_dev *gspca_dev)
+{
+	mi1320_camera_settings(gspca_dev);
+
+	return 0;
+}
+
+static int mi1320_sensor_settings(struct gspca_dev *gspca_dev)
+{
+	s32 reso = gspca_dev->cam.cam_mode[(s32) gspca_dev->curr_mode].priv;
+
+	ctrl_out(gspca_dev, 0x40, 5, 0x0001, 0x0000, 0, NULL);
+
+	fetch_validx(gspca_dev, tbl_sensor_settings_common,
+				ARRAY_SIZE(tbl_sensor_settings_common));
+
+	switch (reso) {
+	case IMAGE_1280:
+		fetch_validx(gspca_dev, tbl_sensor_settings_1280,
+					ARRAY_SIZE(tbl_sensor_settings_1280));
+		ctrl_out(gspca_dev, 0x40, 3, 0xba00, 0x0200, 64, tbl_1280[0]);
+		ctrl_out(gspca_dev, 0x40, 3, 0xba00, 0x0200, 40, tbl_1280[1]);
+		ctrl_out(gspca_dev, 0x40, 3, 0x0000, 0x0200, 12, tbl_1280[2]);
+		break;
+
+	case IMAGE_800:
+		fetch_validx(gspca_dev, tbl_sensor_settings_800,
+					ARRAY_SIZE(tbl_sensor_settings_800));
+		ctrl_out(gspca_dev, 0x40, 3, 0xba00, 0x0200, 64, tbl_800[0]);
+		ctrl_out(gspca_dev, 0x40, 3, 0xba00, 0x0200, 40, tbl_800[1]);
+		ctrl_out(gspca_dev, 0x40, 3, 0x0000, 0x0200, 12, tbl_800[2]);
+		break;
+
+	default:
+		fetch_validx(gspca_dev, tbl_sensor_settings_640,
+					ARRAY_SIZE(tbl_sensor_settings_640));
+		ctrl_out(gspca_dev, 0x40, 3, 0xba00, 0x0200, 60, tbl_640[0]);
+		ctrl_out(gspca_dev, 0x40, 3, 0xba00, 0x0200, 40, tbl_640[1]);
+		ctrl_out(gspca_dev, 0x40, 3, 0x0000, 0x0200, 12, tbl_640[2]);
+		break;
+	}
+	return 0;
+}
+
+static int mi1320_configure_alt(struct gspca_dev *gspca_dev)
+{
+	s32 reso = gspca_dev->cam.cam_mode[(s32) gspca_dev->curr_mode].priv;
+
+	switch (reso) {
+	case IMAGE_640:
+		gspca_dev->alt = 3 + 1;
+		break;
+
+	case IMAGE_800:
+	case IMAGE_1280:
+		gspca_dev->alt = 1 + 1;
+		break;
+	}
+	return 0;
+}
+
+static int mi1320_camera_settings(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	s32 backlight = sd->vcur.backlight;
+	s32 bright = sd->vcur.brightness;
+	s32 sharp  = sd->vcur.sharpness;
+	s32 cntr   = sd->vcur.contrast;
+	s32 gam	   = sd->vcur.gamma;
+	s32 hue    = sd->vcur.hue;
+	s32 sat	   = sd->vcur.saturation;
+	s32 wbal   = sd->vcur.whitebal;
+	s32 mirror = (((sd->vcur.mirror > 0) ^ sd->mirrorMask) > 0);
+	s32 flip   = (((sd->vcur.flip   > 0) ^ sd->mirrorMask) > 0);
+	s32 freq   = (sd->vcur.AC50Hz > 0);
+	s32 i;
+
+	if (freq != sd->vold.AC50Hz) {
+		sd->vold.AC50Hz = freq;
+
+		freq = 2 * (freq == 0);
+		ctrl_out(gspca_dev, 0x40, 1, 0xba00, 0x00f0, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0xba02, 0x00f1, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0xba00       , 0x005b, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0xba01 + freq, 0x00f1, 0, NULL);
+	}
+
+	if (wbal != sd->vold.whitebal) {
+		sd->vold.whitebal = wbal;
+		if (wbal < 0 || wbal > sd->vmax.whitebal)
+			wbal = 0;
+
+		for (i = 0; i < 2; i++) {
+			if (wbal == 0) { /* Normal light */
+				ctrl_out(gspca_dev, 0x40, 1,
+						0x0010, 0x0010, 0, NULL);
+				ctrl_out(gspca_dev, 0x40, 1,
+						0x0003, 0x00c1, 0, NULL);
+				ctrl_out(gspca_dev, 0x40, 1,
+						0x0042, 0x00c2, 0, NULL);
+				ctrl_out(gspca_dev, 0x40, 3,
+						0xba00, 0x0200, 48, dat_wbalNL);
+			}
+
+			if (wbal == 1) { /* Low light */
+				ctrl_out(gspca_dev, 0x40, 1,
+						0x0010, 0x0010, 0, NULL);
+				ctrl_out(gspca_dev, 0x40, 1,
+						0x0004, 0x00c1, 0, NULL);
+				ctrl_out(gspca_dev, 0x40, 1,
+						0x0043, 0x00c2, 0, NULL);
+				ctrl_out(gspca_dev, 0x40, 3,
+						0xba00, 0x0200, 48, dat_wbalLL);
+			}
+
+			if (wbal == 2) { /* Back light */
+				ctrl_out(gspca_dev, 0x40, 1,
+						0x0010, 0x0010, 0, NULL);
+				ctrl_out(gspca_dev, 0x40, 1,
+						0x0003, 0x00c1, 0, NULL);
+				ctrl_out(gspca_dev, 0x40, 1,
+						0x0042, 0x00c2, 0, NULL);
+				ctrl_out(gspca_dev, 0x40, 3,
+						0xba00, 0x0200, 44, dat_wbalBL);
+			}
+		}
+	}
+
+	if (bright != sd->vold.brightness) {
+		sd->vold.brightness = bright;
+		if (bright < 0 || bright > sd->vmax.brightness)
+			bright = 0;
+
+		bright = tbl_bright[bright];
+		ctrl_out(gspca_dev, 0x40, 1, 0xba00, 0x00f0, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0xba01, 0x00f1, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0xba00 + bright, 0x0034, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0xba00 + bright, 0x00f1, 0, NULL);
+	}
+
+	if (sat != sd->vold.saturation) {
+		sd->vold.saturation = sat;
+		if (sat < 0 || sat > sd->vmax.saturation)
+			sat = 0;
+
+		sat = tbl_sat[sat];
+		ctrl_out(gspca_dev, 0x40, 1, 0xba00, 0x00f0, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0xba01, 0x00f1, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0xba00      , 0x0025, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0xba00 + sat, 0x00f1, 0, NULL);
+	}
+
+	if (sharp != sd->vold.sharpness) {
+		sd->vold.sharpness = sharp;
+		if (sharp < 0 || sharp > sd->vmax.sharpness)
+			sharp = 0;
+
+		ctrl_out(gspca_dev, 0x40, 1, 0xba00, 0x00f0, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0xba01, 0x00f1, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0xba00        , 0x0005, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0xba00 + sharp, 0x00f1, 0, NULL);
+	}
+
+	if (hue != sd->vold.hue) {
+		/* 0=normal  1=NB  2="sepia"  3=negative  4=other  5=other2 */
+		if (hue < 0 || hue > sd->vmax.hue)
+			hue = 0;
+		if (hue == sd->vmax.hue)
+			sd->swapRB = 1;
+		else
+			sd->swapRB = 0;
+
+		ctrl_out(gspca_dev, 0x40, 1, 0xba00, 0x00f0, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0xba01, 0x00f1, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0xba70, 0x00e2, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0xba00 + hue * (hue < 6), 0x00f1,
+							0, NULL);
+	}
+
+	if (backlight != sd->vold.backlight) {
+		sd->vold.backlight = backlight;
+		if (backlight < 0 || backlight > sd->vmax.backlight)
+			backlight = 0;
+
+		backlight = tbl_backlight[backlight];
+		for (i = 0; i < 2; i++) {
+			ctrl_out(gspca_dev, 0x40, 1, 0xba00, 0x00f0, 0, NULL);
+			ctrl_out(gspca_dev, 0x40, 1, 0xba01, 0x00f1, 0, NULL);
+			ctrl_out(gspca_dev, 0x40, 1, 0xba74, 0x0006, 0, NULL);
+			ctrl_out(gspca_dev, 0x40, 1, 0xba80 + backlight, 0x00f1,
+								0, NULL);
+		}
+	}
+
+	if (hue != sd->vold.hue) {
+		sd->vold.hue = hue;
+
+		ctrl_out(gspca_dev, 0x40, 1, 0xba00, 0x00f0, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0xba01, 0x00f1, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0xba70, 0x00e2, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0xba00 + hue * (hue < 6), 0x00f1,
+							0, NULL);
+	}
+
+	if (mirror != sd->vold.mirror || flip != sd->vold.flip) {
+		u8 dat_hvflip2[4] = {0x20, 0x01, 0xf1, 0x00};
+		sd->vold.mirror = mirror;
+		sd->vold.flip = flip;
+
+		dat_hvflip2[3] = flip + 2 * mirror;
+		ctrl_out(gspca_dev, 0x40, 3, 0xba00, 0x0200, 4, dat_hvflip1);
+		ctrl_out(gspca_dev, 0x40, 3, 0xba00, 0x0200, 4, dat_hvflip2);
+	}
+
+	if (gam != sd->vold.gamma) {
+		sd->vold.gamma = gam;
+		if (gam < 0 || gam > sd->vmax.gamma)
+			gam = 0;
+
+		gam = 2 * gam;
+		ctrl_out(gspca_dev, 0x40, 1, 0xba00, 0x00f0, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0xba01, 0x00f1, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0xba04      , 0x003b, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0xba02 + gam, 0x00f1, 0, NULL);
+	}
+
+	if (cntr != sd->vold.contrast) {
+		sd->vold.contrast = cntr;
+		if (cntr < 0 || cntr > sd->vmax.contrast)
+			cntr = 0;
+
+		ctrl_out(gspca_dev, 0x40, 1, 0xba00, 0x00f0, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0xba01, 0x00f1, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0xba00 + tbl_cntr1[cntr], 0x0035,
+							0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0xba00 + tbl_cntr2[cntr], 0x00f1,
+							0, NULL);
+	}
+
+	return 0;
+}
+
+static void mi1320_post_unset_alt(struct gspca_dev *gspca_dev)
+{
+	ctrl_out(gspca_dev, 0x40, 5, 0x0000, 0x0000, 0, NULL);
+
+	fetch_validx(gspca_dev, tbl_post_unset_alt,
+				ARRAY_SIZE(tbl_post_unset_alt));
+}
diff --git a/drivers/media/usb/gspca/gl860/gl860-mi2020.c b/drivers/media/usb/gspca/gl860/gl860-mi2020.c
new file mode 100644
index 0000000..a785828
--- /dev/null
+++ b/drivers/media/usb/gspca/gl860/gl860-mi2020.c
@@ -0,0 +1,805 @@
+/* Subdriver for the GL860 chip with the MI2020 sensor
+ * Author Olivier LORIN, from logs by Iceman/Soro2005 + Fret_saw/Hulkie/Tricid
+ * with the help of Kytrix/BUGabundo/Blazercist.
+ * Driver achieved thanks to a webcam gift by Kytrix.
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Sensor : MI2020 */
+
+#include "gl860.h"
+
+static u8 dat_wbal1[] = {0x8c, 0xa2, 0x0c};
+
+static u8 dat_bright1[] = {0x8c, 0xa2, 0x06};
+static u8 dat_bright3[] = {0x8c, 0xa1, 0x02};
+static u8 dat_bright4[] = {0x90, 0x00, 0x0f};
+static u8 dat_bright5[] = {0x8c, 0xa1, 0x03};
+static u8 dat_bright6[] = {0x90, 0x00, 0x05};
+
+static u8 dat_hvflip1[] = {0x8c, 0x27, 0x19};
+static u8 dat_hvflip3[] = {0x8c, 0x27, 0x3b};
+static u8 dat_hvflip5[] = {0x8c, 0xa1, 0x03};
+static u8 dat_hvflip6[] = {0x90, 0x00, 0x06};
+
+static struct idxdata tbl_middle_hvflip_low[] = {
+	{0x33, {0x90, 0x00, 0x06}},
+	{6, {0xff, 0xff, 0xff}},
+	{0x33, {0x90, 0x00, 0x06}},
+	{6, {0xff, 0xff, 0xff}},
+	{0x33, {0x90, 0x00, 0x06}},
+	{6, {0xff, 0xff, 0xff}},
+	{0x33, {0x90, 0x00, 0x06}},
+	{6, {0xff, 0xff, 0xff}},
+};
+
+static struct idxdata tbl_middle_hvflip_big[] = {
+	{0x33, {0x8c, 0xa1, 0x03}}, {0x33, {0x90, 0x00, 0x01}},
+	{0x33, {0x8c, 0xa1, 0x20}}, {0x33, {0x90, 0x00, 0x00}},
+	{0x33, {0x8c, 0xa7, 0x02}}, {0x33, {0x90, 0x00, 0x00}},
+	{102, {0xff, 0xff, 0xff}},
+	{0x33, {0x8c, 0xa1, 0x03}}, {0x33, {0x90, 0x00, 0x02}},
+	{0x33, {0x8c, 0xa1, 0x20}}, {0x33, {0x90, 0x00, 0x72}},
+	{0x33, {0x8c, 0xa7, 0x02}}, {0x33, {0x90, 0x00, 0x01}},
+};
+
+static struct idxdata tbl_end_hvflip[] = {
+	{0x33, {0x8c, 0xa1, 0x02}}, {0x33, {0x90, 0x00, 0x1f}},
+	{6, {0xff, 0xff, 0xff}},
+	{0x33, {0x8c, 0xa1, 0x02}}, {0x33, {0x90, 0x00, 0x1f}},
+	{6, {0xff, 0xff, 0xff}},
+	{0x33, {0x8c, 0xa1, 0x02}}, {0x33, {0x90, 0x00, 0x1f}},
+	{6, {0xff, 0xff, 0xff}},
+	{0x33, {0x8c, 0xa1, 0x02}}, {0x33, {0x90, 0x00, 0x1f}},
+};
+
+static u8 dat_freq1[] = { 0x8c, 0xa4, 0x04 };
+
+static u8 dat_multi5[] = { 0x8c, 0xa1, 0x03 };
+static u8 dat_multi6[] = { 0x90, 0x00, 0x05 };
+
+static struct validx tbl_init_at_startup[] = {
+	{0x0000, 0x0000}, {0x0010, 0x0010}, {0x0008, 0x00c0}, {0x0001, 0x00c1},
+	{0x0001, 0x00c2}, {0x0020, 0x0006}, {0x006a, 0x000d},
+	{53, 0xffff},
+	{0x0040, 0x0000}, {0x0063, 0x0006},
+};
+
+static struct validx tbl_common_0B[] = {
+	{0x0002, 0x0004}, {0x006a, 0x0007}, {0x00ef, 0x0006}, {0x006a, 0x000d},
+	{0x0000, 0x00c0}, {0x0010, 0x0010}, {0x0003, 0x00c1}, {0x0042, 0x00c2},
+	{0x0004, 0x00d8}, {0x0000, 0x0058}, {0x0041, 0x0000},
+};
+
+static struct idxdata tbl_common_3B[] = {
+	{0x33, {0x86, 0x25, 0x01}}, {0x33, {0x86, 0x25, 0x00}},
+	{2, {0xff, 0xff, 0xff}},
+	{0x30, {0x1a, 0x0a, 0xcc}}, {0x32, {0x02, 0x00, 0x08}},
+	{0x33, {0xf4, 0x03, 0x1d}},
+	{6, {0xff, 0xff, 0xff}}, /* 12 */
+	{0x34, {0x1e, 0x8f, 0x09}}, {0x34, {0x1c, 0x01, 0x28}},
+	{0x34, {0x1e, 0x8f, 0x09}},
+	{2, {0xff, 0xff, 0xff}}, /* - */
+	{0x34, {0x1e, 0x8f, 0x09}}, {0x32, {0x14, 0x06, 0xe6}},
+	{0x33, {0x8c, 0x22, 0x23}}, {0x33, {0x90, 0x00, 0x00}},
+	{0x33, {0x8c, 0xa2, 0x0f}}, {0x33, {0x90, 0x00, 0x0d}},
+	{0x33, {0x8c, 0xa2, 0x10}}, {0x33, {0x90, 0x00, 0x0b}},
+	{0x33, {0x8c, 0xa2, 0x11}}, {0x33, {0x90, 0x00, 0x07}},
+	{0x33, {0xf4, 0x03, 0x1d}}, {0x35, {0xa2, 0x00, 0xe2}},
+	{0x33, {0x8c, 0xab, 0x05}}, {0x33, {0x90, 0x00, 0x01}},
+	{0x32, {0x6e, 0x00, 0x86}}, {0x32, {0x70, 0x0f, 0xaa}},
+	{0x32, {0x72, 0x0f, 0xe4}}, {0x33, {0x8c, 0xa3, 0x4a}},
+	{0x33, {0x90, 0x00, 0x5a}}, {0x33, {0x8c, 0xa3, 0x4b}},
+	{0x33, {0x90, 0x00, 0xa6}}, {0x33, {0x8c, 0xa3, 0x61}},
+	{0x33, {0x90, 0x00, 0xc8}}, {0x33, {0x8c, 0xa3, 0x62}},
+	{0x33, {0x90, 0x00, 0xe1}}, {0x34, {0xce, 0x01, 0xa8}},
+	{0x34, {0xd0, 0x66, 0x33}}, {0x34, {0xd2, 0x31, 0x9a}},
+	{0x34, {0xd4, 0x94, 0x63}}, {0x34, {0xd6, 0x4b, 0x25}},
+	{0x34, {0xd8, 0x26, 0x70}}, {0x34, {0xda, 0x72, 0x4c}},
+	{0x34, {0xdc, 0xff, 0x04}}, {0x34, {0xde, 0x01, 0x5b}},
+	{0x34, {0xe6, 0x01, 0x13}}, {0x34, {0xee, 0x0b, 0xf0}},
+	{0x34, {0xf6, 0x0b, 0xa4}}, {0x35, {0x00, 0xf6, 0xe7}},
+	{0x35, {0x08, 0x0d, 0xfd}}, {0x35, {0x10, 0x25, 0x63}},
+	{0x35, {0x18, 0x35, 0x6c}}, {0x35, {0x20, 0x42, 0x7e}},
+	{0x35, {0x28, 0x19, 0x44}}, {0x35, {0x30, 0x39, 0xd4}},
+	{0x35, {0x38, 0xf5, 0xa8}}, {0x35, {0x4c, 0x07, 0x90}},
+	{0x35, {0x44, 0x07, 0xb8}}, {0x35, {0x5c, 0x06, 0x88}},
+	{0x35, {0x54, 0x07, 0xff}}, {0x34, {0xe0, 0x01, 0x52}},
+	{0x34, {0xe8, 0x00, 0xcc}}, {0x34, {0xf0, 0x0d, 0x83}},
+	{0x34, {0xf8, 0x0c, 0xb3}}, {0x35, {0x02, 0xfe, 0xba}},
+	{0x35, {0x0a, 0x04, 0xe0}}, {0x35, {0x12, 0x1c, 0x63}},
+	{0x35, {0x1a, 0x2b, 0x5a}}, {0x35, {0x22, 0x32, 0x5e}},
+	{0x35, {0x2a, 0x0d, 0x28}}, {0x35, {0x32, 0x2c, 0x02}},
+	{0x35, {0x3a, 0xf4, 0xfa}}, {0x35, {0x4e, 0x07, 0xef}},
+	{0x35, {0x46, 0x07, 0x88}}, {0x35, {0x5e, 0x07, 0xc1}},
+	{0x35, {0x56, 0x04, 0x64}}, {0x34, {0xe4, 0x01, 0x15}},
+	{0x34, {0xec, 0x00, 0x82}}, {0x34, {0xf4, 0x0c, 0xce}},
+	{0x34, {0xfc, 0x0c, 0xba}}, {0x35, {0x06, 0x1f, 0x02}},
+	{0x35, {0x0e, 0x02, 0xe3}}, {0x35, {0x16, 0x1a, 0x50}},
+	{0x35, {0x1e, 0x24, 0x39}}, {0x35, {0x26, 0x23, 0x4c}},
+	{0x35, {0x2e, 0xf9, 0x1b}}, {0x35, {0x36, 0x23, 0x19}},
+	{0x35, {0x3e, 0x12, 0x08}}, {0x35, {0x52, 0x07, 0x22}},
+	{0x35, {0x4a, 0x03, 0xd3}}, {0x35, {0x62, 0x06, 0x54}},
+	{0x35, {0x5a, 0x04, 0x5d}}, {0x34, {0xe2, 0x01, 0x04}},
+	{0x34, {0xea, 0x00, 0xa0}}, {0x34, {0xf2, 0x0c, 0xbc}},
+	{0x34, {0xfa, 0x0c, 0x5b}}, {0x35, {0x04, 0x17, 0xf2}},
+	{0x35, {0x0c, 0x02, 0x08}}, {0x35, {0x14, 0x28, 0x43}},
+	{0x35, {0x1c, 0x28, 0x62}}, {0x35, {0x24, 0x2b, 0x60}},
+	{0x35, {0x2c, 0x07, 0x33}}, {0x35, {0x34, 0x1f, 0xb0}},
+	{0x35, {0x3c, 0xed, 0xcd}}, {0x35, {0x50, 0x00, 0x06}},
+	{0x35, {0x48, 0x07, 0xff}}, {0x35, {0x60, 0x05, 0x89}},
+	{0x35, {0x58, 0x07, 0xff}}, {0x35, {0x40, 0x00, 0xa0}},
+	{0x35, {0x42, 0x00, 0x00}}, {0x32, {0x10, 0x01, 0xfc}},
+	{0x33, {0x8c, 0xa1, 0x18}}, {0x33, {0x90, 0x00, 0x3c}},
+	{0x33, {0x78, 0x00, 0x00}},
+	{2, {0xff, 0xff, 0xff}},
+	{0x35, {0xb8, 0x1f, 0x20}}, {0x33, {0x8c, 0xa2, 0x06}},
+	{0x33, {0x90, 0x00, 0x10}}, {0x33, {0x8c, 0xa2, 0x07}},
+	{0x33, {0x90, 0x00, 0x08}}, {0x33, {0x8c, 0xa2, 0x42}},
+	{0x33, {0x90, 0x00, 0x0b}}, {0x33, {0x8c, 0xa2, 0x4a}},
+	{0x33, {0x90, 0x00, 0x8c}}, {0x35, {0xba, 0xfa, 0x08}},
+	{0x33, {0x8c, 0xa2, 0x02}}, {0x33, {0x90, 0x00, 0x22}},
+	{0x33, {0x8c, 0xa2, 0x03}}, {0x33, {0x90, 0x00, 0xbb}},
+	{0x33, {0x8c, 0xa4, 0x04}}, {0x33, {0x90, 0x00, 0x80}},
+	{0x33, {0x8c, 0xa7, 0x9d}}, {0x33, {0x90, 0x00, 0x00}},
+	{0x33, {0x8c, 0xa7, 0x9e}}, {0x33, {0x90, 0x00, 0x00}},
+	{0x33, {0x8c, 0xa2, 0x0c}}, {0x33, {0x90, 0x00, 0x17}},
+	{0x33, {0x8c, 0xa2, 0x15}}, {0x33, {0x90, 0x00, 0x04}},
+	{0x33, {0x8c, 0xa2, 0x14}}, {0x33, {0x90, 0x00, 0x20}},
+	{0x33, {0x8c, 0xa1, 0x03}}, {0x33, {0x90, 0x00, 0x00}},
+	{0x33, {0x8c, 0x27, 0x17}}, {0x33, {0x90, 0x21, 0x11}},
+	{0x33, {0x8c, 0x27, 0x1b}}, {0x33, {0x90, 0x02, 0x4f}},
+	{0x33, {0x8c, 0x27, 0x25}}, {0x33, {0x90, 0x06, 0x0f}},
+	{0x33, {0x8c, 0x27, 0x39}}, {0x33, {0x90, 0x21, 0x11}},
+	{0x33, {0x8c, 0x27, 0x3d}}, {0x33, {0x90, 0x01, 0x20}},
+	{0x33, {0x8c, 0x27, 0x47}}, {0x33, {0x90, 0x09, 0x4c}},
+	{0x33, {0x8c, 0x27, 0x03}}, {0x33, {0x90, 0x02, 0x84}},
+	{0x33, {0x8c, 0x27, 0x05}}, {0x33, {0x90, 0x01, 0xe2}},
+	{0x33, {0x8c, 0x27, 0x07}}, {0x33, {0x90, 0x06, 0x40}},
+	{0x33, {0x8c, 0x27, 0x09}}, {0x33, {0x90, 0x04, 0xb0}},
+	{0x33, {0x8c, 0x27, 0x0d}}, {0x33, {0x90, 0x00, 0x00}},
+	{0x33, {0x8c, 0x27, 0x0f}}, {0x33, {0x90, 0x00, 0x00}},
+	{0x33, {0x8c, 0x27, 0x11}}, {0x33, {0x90, 0x04, 0xbd}},
+	{0x33, {0x8c, 0x27, 0x13}}, {0x33, {0x90, 0x06, 0x4d}},
+	{0x33, {0x8c, 0x27, 0x15}}, {0x33, {0x90, 0x00, 0x00}},
+	{0x33, {0x8c, 0x27, 0x17}}, {0x33, {0x90, 0x21, 0x11}},
+	{0x33, {0x8c, 0x27, 0x19}}, {0x33, {0x90, 0x04, 0x6c}},
+	{0x33, {0x8c, 0x27, 0x1b}}, {0x33, {0x90, 0x02, 0x4f}},
+	{0x33, {0x8c, 0x27, 0x1d}}, {0x33, {0x90, 0x01, 0x02}},
+	{0x33, {0x8c, 0x27, 0x1f}}, {0x33, {0x90, 0x02, 0x79}},
+	{0x33, {0x8c, 0x27, 0x21}}, {0x33, {0x90, 0x01, 0x55}},
+	{0x33, {0x8c, 0x27, 0x23}}, {0x33, {0x90, 0x02, 0x85}},
+	{0x33, {0x8c, 0x27, 0x25}}, {0x33, {0x90, 0x06, 0x0f}},
+	{0x33, {0x8c, 0x27, 0x27}}, {0x33, {0x90, 0x20, 0x20}},
+	{0x33, {0x8c, 0x27, 0x29}}, {0x33, {0x90, 0x20, 0x20}},
+	{0x33, {0x8c, 0x27, 0x2b}}, {0x33, {0x90, 0x10, 0x20}},
+	{0x33, {0x8c, 0x27, 0x2d}}, {0x33, {0x90, 0x20, 0x07}},
+	{0x33, {0x8c, 0x27, 0x2f}}, {0x33, {0x90, 0x00, 0x04}},
+	{0x33, {0x8c, 0x27, 0x31}}, {0x33, {0x90, 0x00, 0x04}},
+	{0x33, {0x8c, 0x27, 0x33}}, {0x33, {0x90, 0x04, 0xbb}},
+	{0x33, {0x8c, 0x27, 0x35}}, {0x33, {0x90, 0x06, 0x4b}},
+	{0x33, {0x8c, 0x27, 0x37}}, {0x33, {0x90, 0x00, 0x00}},
+	{0x33, {0x8c, 0x27, 0x39}}, {0x33, {0x90, 0x21, 0x11}},
+	{0x33, {0x8c, 0x27, 0x3b}}, {0x33, {0x90, 0x00, 0x24}},
+	{0x33, {0x8c, 0x27, 0x3d}}, {0x33, {0x90, 0x01, 0x20}},
+	{0x33, {0x8c, 0x27, 0x41}}, {0x33, {0x90, 0x01, 0x69}},
+	{0x33, {0x8c, 0x27, 0x45}}, {0x33, {0x90, 0x04, 0xed}},
+	{0x33, {0x8c, 0x27, 0x47}}, {0x33, {0x90, 0x09, 0x4c}},
+	{0x33, {0x8c, 0x27, 0x51}}, {0x33, {0x90, 0x00, 0x00}},
+	{0x33, {0x8c, 0x27, 0x53}}, {0x33, {0x90, 0x03, 0x20}},
+	{0x33, {0x8c, 0x27, 0x55}}, {0x33, {0x90, 0x00, 0x00}},
+	{0x33, {0x8c, 0x27, 0x57}}, {0x33, {0x90, 0x02, 0x58}},
+	{0x33, {0x8c, 0x27, 0x5f}}, {0x33, {0x90, 0x00, 0x00}},
+	{0x33, {0x8c, 0x27, 0x61}}, {0x33, {0x90, 0x06, 0x40}},
+	{0x33, {0x8c, 0x27, 0x63}}, {0x33, {0x90, 0x00, 0x00}},
+	{0x33, {0x8c, 0x27, 0x65}}, {0x33, {0x90, 0x04, 0xb0}},
+	{0x33, {0x8c, 0x22, 0x2e}}, {0x33, {0x90, 0x00, 0xa1}},
+	{0x33, {0x8c, 0xa4, 0x08}}, {0x33, {0x90, 0x00, 0x1f}},
+	{0x33, {0x8c, 0xa4, 0x09}}, {0x33, {0x90, 0x00, 0x21}},
+	{0x33, {0x8c, 0xa4, 0x0a}}, {0x33, {0x90, 0x00, 0x25}},
+	{0x33, {0x8c, 0xa4, 0x0b}}, {0x33, {0x90, 0x00, 0x27}},
+	{0x33, {0x8c, 0x24, 0x11}}, {0x33, {0x90, 0x00, 0xa1}},
+	{0x33, {0x8c, 0x24, 0x13}}, {0x33, {0x90, 0x00, 0xc1}},
+	{0x33, {0x8c, 0x24, 0x15}}, {0x33, {0x90, 0x00, 0x6a}},
+	{0x33, {0x8c, 0x24, 0x17}}, {0x33, {0x90, 0x00, 0x80}},
+	{0x33, {0x8c, 0xa1, 0x03}}, {0x33, {0x90, 0x00, 0x05}},
+	{2, {0xff, 0xff, 0xff}},
+	{0x33, {0x8c, 0xa1, 0x03}}, {0x33, {0x90, 0x00, 0x06}},
+	{3, {0xff, 0xff, 0xff}},
+};
+
+static struct idxdata tbl_init_post_alt_low1[] = {
+	{0x33, {0x8c, 0x27, 0x15}}, {0x33, {0x90, 0x00, 0x25}},
+	{0x33, {0x8c, 0x22, 0x2e}}, {0x33, {0x90, 0x00, 0x81}},
+	{0x33, {0x8c, 0xa4, 0x08}}, {0x33, {0x90, 0x00, 0x17}},
+	{0x33, {0x8c, 0xa4, 0x09}}, {0x33, {0x90, 0x00, 0x1a}},
+	{0x33, {0x8c, 0xa4, 0x0a}}, {0x33, {0x90, 0x00, 0x1d}},
+	{0x33, {0x8c, 0xa4, 0x0b}}, {0x33, {0x90, 0x00, 0x20}},
+	{0x33, {0x8c, 0x24, 0x11}}, {0x33, {0x90, 0x00, 0x81}},
+	{0x33, {0x8c, 0x24, 0x13}}, {0x33, {0x90, 0x00, 0x9b}},
+};
+
+static struct idxdata tbl_init_post_alt_low2[] = {
+	{0x33, {0x8c, 0x27, 0x03}}, {0x33, {0x90, 0x03, 0x24}},
+	{0x33, {0x8c, 0x27, 0x05}}, {0x33, {0x90, 0x02, 0x58}},
+	{0x33, {0x8c, 0xa1, 0x03}}, {0x33, {0x90, 0x00, 0x05}},
+	{2, {0xff, 0xff, 0xff}},
+	{0x33, {0x8c, 0xa1, 0x03}}, {0x33, {0x90, 0x00, 0x06}},
+	{2, {0xff, 0xff, 0xff}},
+};
+
+static struct idxdata tbl_init_post_alt_low3[] = {
+	{0x34, {0x1e, 0x8f, 0x09}}, {0x34, {0x1c, 0x01, 0x28}},
+	{0x34, {0x1e, 0x8f, 0x09}},
+	{2, {0xff, 0xff, 0xff}},
+	{0x34, {0x1e, 0x8f, 0x09}}, {0x32, {0x14, 0x06, 0xe6}},
+	{0x33, {0x8c, 0xa1, 0x20}}, {0x33, {0x90, 0x00, 0x00}},
+	{0x33, {0x8c, 0xa1, 0x03}}, {0x33, {0x90, 0x00, 0x01}},
+	{0x33, {0x2e, 0x01, 0x00}}, {0x34, {0x04, 0x00, 0x2a}},
+	{0x33, {0x8c, 0xa7, 0x02}}, {0x33, {0x90, 0x00, 0x00}},
+	{0x33, {0x8c, 0x27, 0x95}}, {0x33, {0x90, 0x01, 0x00}},
+	{2, {0xff, 0xff, 0xff}},
+	{0x33, {0x8c, 0xa1, 0x20}}, {0x33, {0x90, 0x00, 0x72}},
+	{0x33, {0x8c, 0xa1, 0x03}}, {0x33, {0x90, 0x00, 0x02}},
+	{0x33, {0x8c, 0xa7, 0x02}}, {0x33, {0x90, 0x00, 0x01}},
+	{2, {0xff, 0xff, 0xff}},
+	{0x33, {0x8c, 0xa1, 0x20}}, {0x33, {0x90, 0x00, 0x00}},
+	{0x33, {0x8c, 0xa1, 0x03}}, {0x33, {0x90, 0x00, 0x01}},
+	{0x33, {0x8c, 0xa7, 0x02}}, {0x33, {0x90, 0x00, 0x00}},
+	{2, {0xff, 0xff, 0xff}},
+	{0x33, {0x8c, 0xa1, 0x03}}, {0x33, {0x90, 0x00, 0x05}},
+	{2, {0xff, 0xff, 0xff}},
+	{0x33, {0x8c, 0xa1, 0x03}}, {0x33, {0x90, 0x00, 0x06}},
+	{2, {0xff, 0xff, 0xff}},
+	{0x33, {0x8c, 0xa1, 0x03}}, {0x33, {0x90, 0x00, 0x05}},
+	{2, {0xff, 0xff, 0xff}},
+	{0x33, {0x8c, 0xa1, 0x03}}, {0x33, {0x90, 0x00, 0x06}},
+};
+
+static struct idxdata tbl_init_post_alt_big[] = {
+	{0x33, {0x8c, 0xa1, 0x03}}, {0x33, {0x90, 0x00, 0x05}},
+	{2, {0xff, 0xff, 0xff}},
+	{0x33, {0x8c, 0xa1, 0x03}}, {0x33, {0x90, 0x00, 0x06}},
+	{2, {0xff, 0xff, 0xff}},
+	{0x34, {0x1e, 0x8f, 0x09}}, {0x34, {0x1c, 0x01, 0x28}},
+	{0x34, {0x1e, 0x8f, 0x09}},
+	{2, {0xff, 0xff, 0xff}},
+	{0x34, {0x1e, 0x8f, 0x09}}, {0x32, {0x14, 0x06, 0xe6}},
+	{0x33, {0x8c, 0xa1, 0x03}},
+	{0x33, {0x90, 0x00, 0x05}},
+	{2, {0xff, 0xff, 0xff}},
+	{0x33, {0x8c, 0xa1, 0x03}}, {0x33, {0x90, 0x00, 0x06}},
+	{2, {0xff, 0xff, 0xff}},
+	{0x33, {0x8c, 0xa1, 0x03}}, {0x33, {0x90, 0x00, 0x05}},
+	{2, {0xff, 0xff, 0xff}},
+	{0x33, {0x8c, 0xa1, 0x03}}, {0x33, {0x90, 0x00, 0x06}},
+	{0x33, {0x8c, 0xa1, 0x20}}, {0x33, {0x90, 0x00, 0x72}},
+	{0x33, {0x8c, 0xa1, 0x30}}, {0x33, {0x90, 0x00, 0x03}},
+	{0x33, {0x8c, 0xa1, 0x31}}, {0x33, {0x90, 0x00, 0x02}},
+	{0x33, {0x8c, 0xa1, 0x32}}, {0x33, {0x90, 0x00, 0x03}},
+	{0x33, {0x8c, 0xa1, 0x34}}, {0x33, {0x90, 0x00, 0x03}},
+	{0x33, {0x8c, 0xa1, 0x03}}, {0x33, {0x90, 0x00, 0x02}},
+	{0x33, {0x2e, 0x01, 0x00}}, {0x34, {0x04, 0x00, 0x2a}},
+	{0x33, {0x8c, 0xa7, 0x02}}, {0x33, {0x90, 0x00, 0x01}},
+	{0x33, {0x8c, 0x27, 0x97}}, {0x33, {0x90, 0x01, 0x00}},
+	{51, {0xff, 0xff, 0xff}},
+	{0x33, {0x8c, 0xa1, 0x20}}, {0x33, {0x90, 0x00, 0x00}},
+	{0x33, {0x8c, 0xa1, 0x03}}, {0x33, {0x90, 0x00, 0x01}},
+	{0x33, {0x8c, 0xa7, 0x02}}, {0x33, {0x90, 0x00, 0x00}},
+	{51, {0xff, 0xff, 0xff}},
+	{0x33, {0x8c, 0xa1, 0x20}}, {0x33, {0x90, 0x00, 0x72}},
+	{0x33, {0x8c, 0xa1, 0x03}}, {0x33, {0x90, 0x00, 0x02}},
+	{0x33, {0x8c, 0xa7, 0x02}}, {0x33, {0x90, 0x00, 0x01}},
+	{51, {0xff, 0xff, 0xff}},
+};
+
+static struct idxdata tbl_init_post_alt_3B[] = {
+	{0x32, {0x10, 0x01, 0xf8}}, {0x34, {0xce, 0x01, 0xa8}},
+	{0x34, {0xd0, 0x66, 0x33}}, {0x34, {0xd2, 0x31, 0x9a}},
+	{0x34, {0xd4, 0x94, 0x63}}, {0x34, {0xd6, 0x4b, 0x25}},
+	{0x34, {0xd8, 0x26, 0x70}}, {0x34, {0xda, 0x72, 0x4c}},
+	{0x34, {0xdc, 0xff, 0x04}}, {0x34, {0xde, 0x01, 0x5b}},
+	{0x34, {0xe6, 0x01, 0x13}}, {0x34, {0xee, 0x0b, 0xf0}},
+	{0x34, {0xf6, 0x0b, 0xa4}}, {0x35, {0x00, 0xf6, 0xe7}},
+	{0x35, {0x08, 0x0d, 0xfd}}, {0x35, {0x10, 0x25, 0x63}},
+	{0x35, {0x18, 0x35, 0x6c}}, {0x35, {0x20, 0x42, 0x7e}},
+	{0x35, {0x28, 0x19, 0x44}}, {0x35, {0x30, 0x39, 0xd4}},
+	{0x35, {0x38, 0xf5, 0xa8}}, {0x35, {0x4c, 0x07, 0x90}},
+	{0x35, {0x44, 0x07, 0xb8}}, {0x35, {0x5c, 0x06, 0x88}},
+	{0x35, {0x54, 0x07, 0xff}}, {0x34, {0xe0, 0x01, 0x52}},
+	{0x34, {0xe8, 0x00, 0xcc}}, {0x34, {0xf0, 0x0d, 0x83}},
+	{0x34, {0xf8, 0x0c, 0xb3}}, {0x35, {0x02, 0xfe, 0xba}},
+	{0x35, {0x0a, 0x04, 0xe0}}, {0x35, {0x12, 0x1c, 0x63}},
+	{0x35, {0x1a, 0x2b, 0x5a}}, {0x35, {0x22, 0x32, 0x5e}},
+	{0x35, {0x2a, 0x0d, 0x28}}, {0x35, {0x32, 0x2c, 0x02}},
+	{0x35, {0x3a, 0xf4, 0xfa}}, {0x35, {0x4e, 0x07, 0xef}},
+	{0x35, {0x46, 0x07, 0x88}}, {0x35, {0x5e, 0x07, 0xc1}},
+	{0x35, {0x56, 0x04, 0x64}}, {0x34, {0xe4, 0x01, 0x15}},
+	{0x34, {0xec, 0x00, 0x82}}, {0x34, {0xf4, 0x0c, 0xce}},
+	{0x34, {0xfc, 0x0c, 0xba}}, {0x35, {0x06, 0x1f, 0x02}},
+	{0x35, {0x0e, 0x02, 0xe3}}, {0x35, {0x16, 0x1a, 0x50}},
+	{0x35, {0x1e, 0x24, 0x39}}, {0x35, {0x26, 0x23, 0x4c}},
+	{0x35, {0x2e, 0xf9, 0x1b}}, {0x35, {0x36, 0x23, 0x19}},
+	{0x35, {0x3e, 0x12, 0x08}}, {0x35, {0x52, 0x07, 0x22}},
+	{0x35, {0x4a, 0x03, 0xd3}}, {0x35, {0x62, 0x06, 0x54}},
+	{0x35, {0x5a, 0x04, 0x5d}}, {0x34, {0xe2, 0x01, 0x04}},
+	{0x34, {0xea, 0x00, 0xa0}}, {0x34, {0xf2, 0x0c, 0xbc}},
+	{0x34, {0xfa, 0x0c, 0x5b}}, {0x35, {0x04, 0x17, 0xf2}},
+	{0x35, {0x0c, 0x02, 0x08}}, {0x35, {0x14, 0x28, 0x43}},
+	{0x35, {0x1c, 0x28, 0x62}}, {0x35, {0x24, 0x2b, 0x60}},
+	{0x35, {0x2c, 0x07, 0x33}}, {0x35, {0x34, 0x1f, 0xb0}},
+	{0x35, {0x3c, 0xed, 0xcd}}, {0x35, {0x50, 0x00, 0x06}},
+	{0x35, {0x48, 0x07, 0xff}}, {0x35, {0x60, 0x05, 0x89}},
+	{0x35, {0x58, 0x07, 0xff}}, {0x35, {0x40, 0x00, 0xa0}},
+	{0x35, {0x42, 0x00, 0x00}}, {0x32, {0x10, 0x01, 0xfc}},
+	{0x33, {0x8c, 0xa1, 0x18}}, {0x33, {0x90, 0x00, 0x3c}},
+};
+
+static u8 *dat_640  = "\xd0\x02\xd1\x08\xd2\xe1\xd3\x02\xd4\x10\xd5\x81";
+static u8 *dat_800  = "\xd0\x02\xd1\x10\xd2\x57\xd3\x02\xd4\x18\xd5\x21";
+static u8 *dat_1280 = "\xd0\x02\xd1\x20\xd2\x01\xd3\x02\xd4\x28\xd5\x01";
+static u8 *dat_1600 = "\xd0\x02\xd1\x20\xd2\xaf\xd3\x02\xd4\x30\xd5\x41";
+
+static int  mi2020_init_at_startup(struct gspca_dev *gspca_dev);
+static int  mi2020_configure_alt(struct gspca_dev *gspca_dev);
+static int  mi2020_init_pre_alt(struct gspca_dev *gspca_dev);
+static int  mi2020_init_post_alt(struct gspca_dev *gspca_dev);
+static void mi2020_post_unset_alt(struct gspca_dev *gspca_dev);
+static int  mi2020_camera_settings(struct gspca_dev *gspca_dev);
+/*==========================================================================*/
+
+void mi2020_init_settings(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	sd->vcur.backlight  =  0;
+	sd->vcur.brightness = 70;
+	sd->vcur.sharpness  = 20;
+	sd->vcur.contrast   =  0;
+	sd->vcur.gamma      =  0;
+	sd->vcur.hue        =  0;
+	sd->vcur.saturation = 60;
+	sd->vcur.whitebal   =  0; /* 50, not done by hardware */
+	sd->vcur.mirror = 0;
+	sd->vcur.flip   = 0;
+	sd->vcur.AC50Hz = 1;
+
+	sd->vmax.backlight  =  64;
+	sd->vmax.brightness = 128;
+	sd->vmax.sharpness  =  40;
+	sd->vmax.contrast   =   3;
+	sd->vmax.gamma      =   2;
+	sd->vmax.hue        =   0 + 1; /* 200, not done by hardware */
+	sd->vmax.saturation =   0;     /* 100, not done by hardware */
+	sd->vmax.whitebal   =   2;     /* 100, not done by hardware */
+	sd->vmax.mirror = 1;
+	sd->vmax.flip   = 1;
+	sd->vmax.AC50Hz = 1;
+
+	sd->dev_camera_settings = mi2020_camera_settings;
+	sd->dev_init_at_startup = mi2020_init_at_startup;
+	sd->dev_configure_alt   = mi2020_configure_alt;
+	sd->dev_init_pre_alt    = mi2020_init_pre_alt;
+	sd->dev_post_unset_alt  = mi2020_post_unset_alt;
+}
+
+/*==========================================================================*/
+
+static void common(struct gspca_dev *gspca_dev)
+{
+	fetch_validx(gspca_dev, tbl_common_0B, ARRAY_SIZE(tbl_common_0B));
+	fetch_idxdata(gspca_dev, tbl_common_3B, ARRAY_SIZE(tbl_common_3B));
+	ctrl_out(gspca_dev, 0x40, 1, 0x0041, 0x0000, 0, NULL);
+}
+
+static int mi2020_init_at_startup(struct gspca_dev *gspca_dev)
+{
+	u8 c;
+
+	ctrl_in(gspca_dev, 0xc0, 2, 0x0000, 0x0004, 1, &c);
+	ctrl_in(gspca_dev, 0xc0, 2, 0x0000, 0x0004, 1, &c);
+
+	fetch_validx(gspca_dev, tbl_init_at_startup,
+			ARRAY_SIZE(tbl_init_at_startup));
+
+	ctrl_out(gspca_dev, 0x40,  1, 0x7a00, 0x8030,  0, NULL);
+	ctrl_in(gspca_dev, 0xc0,  2, 0x7a00, 0x8030,  1, &c);
+
+	common(gspca_dev);
+
+	msleep(61);
+/*	ctrl_out(gspca_dev, 0x40, 11, 0x0000, 0x0000,  0, NULL); */
+/*	msleep(36); */
+	ctrl_out(gspca_dev, 0x40,  1, 0x0001, 0x0000,  0, NULL);
+
+	return 0;
+}
+
+static int mi2020_init_pre_alt(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	sd->mirrorMask =  0;
+	sd->vold.hue   = -1;
+
+	/* These controls need to be reset */
+	sd->vold.brightness = -1;
+	sd->vold.sharpness  = -1;
+
+	/* If not different from default, they do not need to be set */
+	sd->vold.contrast  = 0;
+	sd->vold.gamma     = 0;
+	sd->vold.backlight = 0;
+
+	mi2020_init_post_alt(gspca_dev);
+
+	return 0;
+}
+
+static int mi2020_init_post_alt(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	s32 reso = gspca_dev->cam.cam_mode[(s32) gspca_dev->curr_mode].priv;
+
+	s32 mirror = (((sd->vcur.mirror > 0) ^ sd->mirrorMask) > 0);
+	s32 flip   = (((sd->vcur.flip   > 0) ^ sd->mirrorMask) > 0);
+	s32 freq   = (sd->vcur.AC50Hz  > 0);
+	s32 wbal   = sd->vcur.whitebal;
+
+	u8 dat_freq2[] = {0x90, 0x00, 0x80};
+	u8 dat_multi1[] = {0x8c, 0xa7, 0x00};
+	u8 dat_multi2[] = {0x90, 0x00, 0x00};
+	u8 dat_multi3[] = {0x8c, 0xa7, 0x00};
+	u8 dat_multi4[] = {0x90, 0x00, 0x00};
+	u8 dat_hvflip2[] = {0x90, 0x04, 0x6c};
+	u8 dat_hvflip4[] = {0x90, 0x00, 0x24};
+	u8 dat_wbal2[] = {0x90, 0x00, 0x00};
+	u8 c;
+
+	sd->nbIm = -1;
+
+	dat_freq2[2] = freq ? 0xc0 : 0x80;
+	dat_multi1[2] = 0x9d;
+	dat_multi3[2] = dat_multi1[2] + 1;
+	if (wbal == 0) {
+		dat_multi4[2] = dat_multi2[2] = 0;
+		dat_wbal2[2] = 0x17;
+	} else if (wbal == 1) {
+		dat_multi4[2] = dat_multi2[2] = 0;
+		dat_wbal2[2] = 0x35;
+	} else if (wbal == 2) {
+		dat_multi4[2] = dat_multi2[2] = 0x20;
+		dat_wbal2[2] = 0x17;
+	}
+	dat_hvflip2[2] = 0x6c + 2 * (1 - flip) + (1 - mirror);
+	dat_hvflip4[2] = 0x24 + 2 * (1 - flip) + (1 - mirror);
+
+	msleep(200);
+	ctrl_out(gspca_dev, 0x40, 5, 0x0001, 0x0000, 0, NULL);
+	msleep(2);
+
+	common(gspca_dev);
+
+	msleep(142);
+	ctrl_out(gspca_dev, 0x40,  1, 0x0010, 0x0010,  0, NULL);
+	ctrl_out(gspca_dev, 0x40,  1, 0x0003, 0x00c1,  0, NULL);
+	ctrl_out(gspca_dev, 0x40,  1, 0x0042, 0x00c2,  0, NULL);
+	ctrl_out(gspca_dev, 0x40,  1, 0x006a, 0x000d,  0, NULL);
+
+	switch (reso) {
+	case IMAGE_640:
+	case IMAGE_800:
+		if (reso != IMAGE_800)
+			ctrl_out(gspca_dev, 0x40,  3, 0x0000, 0x0200,
+				12, dat_640);
+		else
+			ctrl_out(gspca_dev, 0x40,  3, 0x0000, 0x0200,
+				12, dat_800);
+
+		fetch_idxdata(gspca_dev, tbl_init_post_alt_low1,
+					ARRAY_SIZE(tbl_init_post_alt_low1));
+
+		if (reso == IMAGE_800)
+			fetch_idxdata(gspca_dev, tbl_init_post_alt_low2,
+					ARRAY_SIZE(tbl_init_post_alt_low2));
+
+		fetch_idxdata(gspca_dev, tbl_init_post_alt_low3,
+				ARRAY_SIZE(tbl_init_post_alt_low3));
+
+		ctrl_out(gspca_dev, 0x40, 1, 0x0010, 0x0010, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0x0000, 0x00c1, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0x0041, 0x00c2, 0, NULL);
+		msleep(120);
+		break;
+
+	case IMAGE_1280:
+	case IMAGE_1600:
+		if (reso == IMAGE_1280) {
+			ctrl_out(gspca_dev, 0x40, 3, 0x0000, 0x0200,
+					12, dat_1280);
+			ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033,
+					3, "\x8c\x27\x07");
+			ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033,
+					3, "\x90\x05\x04");
+			ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033,
+					3, "\x8c\x27\x09");
+			ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033,
+					3, "\x90\x04\x02");
+		} else {
+			ctrl_out(gspca_dev, 0x40, 3, 0x0000, 0x0200,
+					12, dat_1600);
+			ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033,
+					3, "\x8c\x27\x07");
+			ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033,
+					3, "\x90\x06\x40");
+			ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033,
+					3, "\x8c\x27\x09");
+			ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033,
+					3, "\x90\x04\xb0");
+		}
+
+		fetch_idxdata(gspca_dev, tbl_init_post_alt_big,
+				ARRAY_SIZE(tbl_init_post_alt_big));
+
+		ctrl_out(gspca_dev, 0x40, 1, 0x0001, 0x0010, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0x0000, 0x00c1, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0x0041, 0x00c2, 0, NULL);
+		msleep(1850);
+	}
+
+	ctrl_out(gspca_dev, 0x40, 1, 0x0040, 0x0000, 0, NULL);
+	msleep(40);
+
+	/* AC power frequency */
+	ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_freq1);
+	ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_freq2);
+	msleep(33);
+	/* light source */
+	ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_multi1);
+	ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_multi2);
+	ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_multi3);
+	ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_multi4);
+	ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_wbal1);
+	ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_wbal2);
+	ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_multi5);
+	ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_multi6);
+	msleep(7);
+	ctrl_in(gspca_dev, 0xc0, 2, 0x0000, 0x0000, 1, &c);
+
+	fetch_idxdata(gspca_dev, tbl_init_post_alt_3B,
+			ARRAY_SIZE(tbl_init_post_alt_3B));
+
+	/* hvflip */
+	ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_hvflip1);
+	ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_hvflip2);
+	ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_hvflip3);
+	ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_hvflip4);
+	ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_hvflip5);
+	ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_hvflip6);
+	msleep(250);
+
+	if (reso == IMAGE_640 || reso == IMAGE_800)
+		fetch_idxdata(gspca_dev, tbl_middle_hvflip_low,
+				ARRAY_SIZE(tbl_middle_hvflip_low));
+	else
+		fetch_idxdata(gspca_dev, tbl_middle_hvflip_big,
+				ARRAY_SIZE(tbl_middle_hvflip_big));
+
+	fetch_idxdata(gspca_dev, tbl_end_hvflip,
+			ARRAY_SIZE(tbl_end_hvflip));
+
+	sd->nbIm = 0;
+
+	sd->vold.mirror    = mirror;
+	sd->vold.flip      = flip;
+	sd->vold.AC50Hz    = freq;
+	sd->vold.whitebal  = wbal;
+
+	mi2020_camera_settings(gspca_dev);
+
+	return 0;
+}
+
+static int mi2020_configure_alt(struct gspca_dev *gspca_dev)
+{
+	s32 reso = gspca_dev->cam.cam_mode[(s32) gspca_dev->curr_mode].priv;
+
+	switch (reso) {
+	case IMAGE_640:
+		gspca_dev->alt = 3 + 1;
+		break;
+
+	case IMAGE_800:
+	case IMAGE_1280:
+	case IMAGE_1600:
+		gspca_dev->alt = 1 + 1;
+		break;
+	}
+	return 0;
+}
+
+static int mi2020_camera_settings(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	s32 reso = gspca_dev->cam.cam_mode[(s32) gspca_dev->curr_mode].priv;
+
+	s32 backlight = sd->vcur.backlight;
+	s32 bright =  sd->vcur.brightness;
+	s32 sharp  =  sd->vcur.sharpness;
+	s32 cntr   =  sd->vcur.contrast;
+	s32 gam	   =  sd->vcur.gamma;
+	s32 hue    = (sd->vcur.hue > 0);
+	s32 mirror = (((sd->vcur.mirror > 0) ^ sd->mirrorMask) > 0);
+	s32 flip   = (((sd->vcur.flip   > 0) ^ sd->mirrorMask) > 0);
+	s32 freq   = (sd->vcur.AC50Hz > 0);
+	s32 wbal   = sd->vcur.whitebal;
+
+	u8 dat_sharp[] = {0x6c, 0x00, 0x08};
+	u8 dat_bright2[] = {0x90, 0x00, 0x00};
+	u8 dat_freq2[] = {0x90, 0x00, 0x80};
+	u8 dat_multi1[] = {0x8c, 0xa7, 0x00};
+	u8 dat_multi2[] = {0x90, 0x00, 0x00};
+	u8 dat_multi3[] = {0x8c, 0xa7, 0x00};
+	u8 dat_multi4[] = {0x90, 0x00, 0x00};
+	u8 dat_hvflip2[] = {0x90, 0x04, 0x6c};
+	u8 dat_hvflip4[] = {0x90, 0x00, 0x24};
+	u8 dat_wbal2[] = {0x90, 0x00, 0x00};
+
+	/* Less than 4 images received -> too early to set the settings */
+	if (sd->nbIm < 4) {
+		sd->waitSet = 1;
+		return 0;
+	}
+	sd->waitSet = 0;
+
+	if (freq != sd->vold.AC50Hz) {
+		sd->vold.AC50Hz = freq;
+
+		dat_freq2[2] = freq ? 0xc0 : 0x80;
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_freq1);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_freq2);
+		msleep(20);
+	}
+
+	if (wbal != sd->vold.whitebal) {
+		sd->vold.whitebal = wbal;
+		if (wbal < 0 || wbal > sd->vmax.whitebal)
+			wbal = 0;
+
+		dat_multi1[2] = 0x9d;
+		dat_multi3[2] = dat_multi1[2] + 1;
+		if (wbal == 0) {
+			dat_multi4[2] = dat_multi2[2] = 0;
+			dat_wbal2[2] = 0x17;
+		} else if (wbal == 1) {
+			dat_multi4[2] = dat_multi2[2] = 0;
+			dat_wbal2[2] = 0x35;
+		} else if (wbal == 2) {
+			dat_multi4[2] = dat_multi2[2] = 0x20;
+			dat_wbal2[2] = 0x17;
+		}
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_multi1);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_multi2);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_multi3);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_multi4);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_wbal1);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_wbal2);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_multi5);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_multi6);
+	}
+
+	if (mirror != sd->vold.mirror || flip != sd->vold.flip) {
+		sd->vold.mirror = mirror;
+		sd->vold.flip   = flip;
+
+		dat_hvflip2[2] = 0x6c + 2 * (1 - flip) + (1 - mirror);
+		dat_hvflip4[2] = 0x24 + 2 * (1 - flip) + (1 - mirror);
+
+		fetch_idxdata(gspca_dev, tbl_init_post_alt_3B,
+				ARRAY_SIZE(tbl_init_post_alt_3B));
+
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_hvflip1);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_hvflip2);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_hvflip3);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_hvflip4);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_hvflip5);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_hvflip6);
+		msleep(40);
+
+		if (reso == IMAGE_640 || reso == IMAGE_800)
+			fetch_idxdata(gspca_dev, tbl_middle_hvflip_low,
+					ARRAY_SIZE(tbl_middle_hvflip_low));
+		else
+			fetch_idxdata(gspca_dev, tbl_middle_hvflip_big,
+					ARRAY_SIZE(tbl_middle_hvflip_big));
+
+		fetch_idxdata(gspca_dev, tbl_end_hvflip,
+				ARRAY_SIZE(tbl_end_hvflip));
+	}
+
+	if (bright != sd->vold.brightness) {
+		sd->vold.brightness = bright;
+		if (bright < 0 || bright > sd->vmax.brightness)
+			bright = 0;
+
+		dat_bright2[2] = bright;
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_bright1);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_bright2);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_bright3);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_bright4);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_bright5);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_bright6);
+	}
+
+	if (cntr != sd->vold.contrast || gam != sd->vold.gamma) {
+		sd->vold.contrast = cntr;
+		if (cntr < 0 || cntr > sd->vmax.contrast)
+			cntr = 0;
+		sd->vold.gamma = gam;
+		if (gam < 0 || gam > sd->vmax.gamma)
+			gam = 0;
+
+		dat_multi1[2] = 0x6d;
+		dat_multi3[2] = dat_multi1[2] + 1;
+		if (cntr == 0)
+			cntr = 4;
+		dat_multi4[2] = dat_multi2[2] = cntr * 0x10 + 2 - gam;
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_multi1);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_multi2);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_multi3);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_multi4);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_multi5);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_multi6);
+	}
+
+	if (backlight != sd->vold.backlight) {
+		sd->vold.backlight = backlight;
+		if (backlight < 0 || backlight > sd->vmax.backlight)
+			backlight = 0;
+
+		dat_multi1[2] = 0x9d;
+		dat_multi3[2] = dat_multi1[2] + 1;
+		dat_multi4[2] = dat_multi2[2] = backlight;
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_multi1);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_multi2);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_multi3);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_multi4);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_multi5);
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0033, 3, dat_multi6);
+	}
+
+	if (sharp != sd->vold.sharpness) {
+		sd->vold.sharpness = sharp;
+		if (sharp < 0 || sharp > sd->vmax.sharpness)
+			sharp = 0;
+
+		dat_sharp[1] = sharp;
+		ctrl_out(gspca_dev, 0x40, 3, 0x7a00, 0x0032, 3, dat_sharp);
+	}
+
+	if (hue != sd->vold.hue) {
+		sd->swapRB = hue;
+		sd->vold.hue = hue;
+	}
+
+	return 0;
+}
+
+static void mi2020_post_unset_alt(struct gspca_dev *gspca_dev)
+{
+	ctrl_out(gspca_dev, 0x40, 5, 0x0000, 0x0000, 0, NULL);
+	msleep(40);
+	ctrl_out(gspca_dev, 0x40, 1, 0x0001, 0x0000, 0, NULL);
+}
diff --git a/drivers/media/usb/gspca/gl860/gl860-ov2640.c b/drivers/media/usb/gspca/gl860/gl860-ov2640.c
new file mode 100644
index 0000000..768cac5
--- /dev/null
+++ b/drivers/media/usb/gspca/gl860/gl860-ov2640.c
@@ -0,0 +1,489 @@
+/* Subdriver for the GL860 chip with the OV2640 sensor
+ * Author Olivier LORIN, from Malmostoso's logs
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Sensor : OV2640 */
+
+#include "gl860.h"
+
+static u8 dat_init1[] = "\x00\x41\x07\x6a\x06\x61\x0d\x6a" "\x10\x10\xc1\x01";
+
+static u8 c61[] = {0x61}; /* expected */
+static u8 c51[] = {0x51}; /* expected */
+static u8 c50[] = {0x50}; /* expected */
+static u8 c28[] = {0x28}; /* expected */
+static u8 ca8[] = {0xa8}; /* expected */
+
+static u8 dat_post[] =
+	"\x00\x41\x07\x6a\x06\xef\x0d\x6a" "\x10\x10\xc1\x01";
+
+static u8 dat_640[]  = "\xd0\x01\xd1\x08\xd2\xe0\xd3\x02\xd4\x10\xd5\x81";
+static u8 dat_800[]  = "\xd0\x01\xd1\x10\xd2\x58\xd3\x02\xd4\x18\xd5\x21";
+static u8 dat_1280[] = "\xd0\x01\xd1\x18\xd2\xc0\xd3\x02\xd4\x28\xd5\x01";
+static u8 dat_1600[] = "\xd0\x01\xd1\x20\xd2\xb0\xd3\x02\xd4\x30\xd5\x41";
+
+static struct validx tbl_init_at_startup[] = {
+	{0x0000, 0x0000}, {0x0010, 0x0010}, {0x0008, 0x00c0}, {0x0001, 0x00c1},
+	{0x0001, 0x00c2}, {0x0020, 0x0006}, {0x006a, 0x000d},
+	{0x0050, 0x0000}, {0x0041, 0x0000}, {0x006a, 0x0007}, {0x0061, 0x0006},
+	{0x006a, 0x000d}, {0x0000, 0x00c0}, {0x0010, 0x0010}, {0x0001, 0x00c1},
+	{0x0041, 0x00c2}, {0x0004, 0x00d8}, {0x0012, 0x0004}, {0x0000, 0x0058},
+	{0x0041, 0x0000}, {0x0061, 0x0000},
+};
+
+static struct validx tbl_common[] = {
+	{0x6000, 0x00ff}, {0x60ff, 0x002c}, {0x60df, 0x002e}, {0x6001, 0x00ff},
+	{0x6080, 0x0012}, {0x6000, 0x0000}, {0x6000, 0x0045}, {0x6000, 0x0010},
+	{0x6035, 0x003c}, {0x6000, 0x0011}, {0x6028, 0x0004}, {0x60e5, 0x0013},
+	{0x6088, 0x0014}, {0x600c, 0x002c}, {0x6078, 0x0033}, {0x60f7, 0x003b},
+	{0x6000, 0x003e}, {0x6011, 0x0043}, {0x6010, 0x0016}, {0x6082, 0x0039},
+	{0x6088, 0x0035}, {0x600a, 0x0022}, {0x6040, 0x0037}, {0x6000, 0x0023},
+	{0x60a0, 0x0034}, {0x601a, 0x0036}, {0x6002, 0x0006}, {0x60c0, 0x0007},
+	{0x60b7, 0x000d}, {0x6001, 0x000e}, {0x6000, 0x004c}, {0x6081, 0x004a},
+	{0x6099, 0x0021}, {0x6002, 0x0009}, {0x603e, 0x0024}, {0x6034, 0x0025},
+	{0x6081, 0x0026}, {0x6000, 0x0000}, {0x6000, 0x0045}, {0x6000, 0x0010},
+	{0x6000, 0x005c}, {0x6000, 0x0063}, {0x6000, 0x007c}, {0x6070, 0x0061},
+	{0x6080, 0x0062}, {0x6080, 0x0020}, {0x6030, 0x0028}, {0x6000, 0x006c},
+	{0x6000, 0x006e}, {0x6002, 0x0070}, {0x6094, 0x0071}, {0x60c1, 0x0073},
+	{0x6034, 0x003d}, {0x6057, 0x005a}, {0x60bb, 0x004f}, {0x609c, 0x0050},
+	{0x6080, 0x006d}, {0x6002, 0x0039}, {0x6033, 0x003a}, {0x60f1, 0x003b},
+	{0x6031, 0x003c}, {0x6000, 0x00ff}, {0x6014, 0x00e0}, {0x60ff, 0x0076},
+	{0x60a0, 0x0033}, {0x6020, 0x0042}, {0x6018, 0x0043}, {0x6000, 0x004c},
+	{0x60d0, 0x0087}, {0x600f, 0x0088}, {0x6003, 0x00d7}, {0x6010, 0x00d9},
+	{0x6005, 0x00da}, {0x6082, 0x00d3}, {0x60c0, 0x00f9}, {0x6006, 0x0044},
+	{0x6007, 0x00d1}, {0x6002, 0x00d2}, {0x6000, 0x00d2}, {0x6011, 0x00d8},
+	{0x6008, 0x00c8}, {0x6080, 0x00c9}, {0x6008, 0x007c}, {0x6020, 0x007d},
+	{0x6020, 0x007d}, {0x6000, 0x0090}, {0x600e, 0x0091}, {0x601a, 0x0091},
+	{0x6031, 0x0091}, {0x605a, 0x0091}, {0x6069, 0x0091}, {0x6075, 0x0091},
+	{0x607e, 0x0091}, {0x6088, 0x0091}, {0x608f, 0x0091}, {0x6096, 0x0091},
+	{0x60a3, 0x0091}, {0x60af, 0x0091}, {0x60c4, 0x0091}, {0x60d7, 0x0091},
+	{0x60e8, 0x0091}, {0x6020, 0x0091}, {0x6000, 0x0092}, {0x6006, 0x0093},
+	{0x60e3, 0x0093}, {0x6005, 0x0093}, {0x6005, 0x0093}, {0x6000, 0x0093},
+	{0x6004, 0x0093}, {0x6000, 0x0093}, {0x6000, 0x0093}, {0x6000, 0x0093},
+	{0x6000, 0x0093}, {0x6000, 0x0093}, {0x6000, 0x0093}, {0x6000, 0x0093},
+	{0x6000, 0x0096}, {0x6008, 0x0097}, {0x6019, 0x0097}, {0x6002, 0x0097},
+	{0x600c, 0x0097}, {0x6024, 0x0097}, {0x6030, 0x0097}, {0x6028, 0x0097},
+	{0x6026, 0x0097}, {0x6002, 0x0097}, {0x6098, 0x0097}, {0x6080, 0x0097},
+	{0x6000, 0x0097}, {0x6000, 0x0097}, {0x60ed, 0x00c3}, {0x609a, 0x00c4},
+	{0x6000, 0x00a4}, {0x6011, 0x00c5}, {0x6051, 0x00c6}, {0x6010, 0x00c7},
+	{0x6066, 0x00b6}, {0x60a5, 0x00b8}, {0x6064, 0x00b7}, {0x607c, 0x00b9},
+	{0x60af, 0x00b3}, {0x6097, 0x00b4}, {0x60ff, 0x00b5}, {0x60c5, 0x00b0},
+	{0x6094, 0x00b1}, {0x600f, 0x00b2}, {0x605c, 0x00c4}, {0x6000, 0x00a8},
+	{0x60c8, 0x00c0}, {0x6096, 0x00c1}, {0x601d, 0x0086}, {0x6000, 0x0050},
+	{0x6090, 0x0051}, {0x6018, 0x0052}, {0x6000, 0x0053}, {0x6000, 0x0054},
+	{0x6088, 0x0055}, {0x6000, 0x0057}, {0x6090, 0x005a}, {0x6018, 0x005b},
+	{0x6005, 0x005c}, {0x60ed, 0x00c3}, {0x6000, 0x007f}, {0x6005, 0x00da},
+	{0x601f, 0x00e5}, {0x6067, 0x00e1}, {0x6000, 0x00e0}, {0x60ff, 0x00dd},
+	{0x6000, 0x0005}, {0x6001, 0x00ff}, {0x6000, 0x0000}, {0x6000, 0x0045},
+	{0x6000, 0x0010},
+};
+
+static struct validx tbl_sensor_settings_common1[] = {
+	{0x0041, 0x0000}, {0x006a, 0x0007}, {0x00ef, 0x0006}, {0x006a, 0x000d},
+	{0x0000, 0x00c0}, {0x0010, 0x0010}, {0x0001, 0x00c1}, {0x0041, 0x00c2},
+	{0x0004, 0x00d8}, {0x0012, 0x0004}, {0x0000, 0x0058}, {0x0041, 0x0000},
+	{50, 0xffff},
+	{0x0061, 0x0000},
+	{0xffff, 0xffff},
+	{0x6000, 0x00ff}, {0x6000, 0x007c}, {0x6007, 0x007d},
+	{30, 0xffff},
+	{0x0040, 0x0000},
+};
+
+static struct validx tbl_sensor_settings_common2[] = {
+	{0x6001, 0x00ff}, {0x6038, 0x000c},
+	{10, 0xffff},
+	{0x6000, 0x0011},
+};
+
+static struct validx tbl_640[] = {
+	{0x6000, 0x00ff}, {0x60f1, 0x00dd}, {0x6004, 0x00e0}, {0x6067, 0x00e1},
+	{0x6004, 0x00da}, {0x6000, 0x00ff}, {0x60f1, 0x00dd}, {0x6004, 0x00e0},
+	{0x6001, 0x00ff}, {0x6000, 0x0012}, {0x6000, 0x0011}, {0x6011, 0x0017},
+	{0x6075, 0x0018}, {0x6001, 0x0019}, {0x6097, 0x001a}, {0x6036, 0x0032},
+	{0x60bb, 0x004f}, {0x6057, 0x005a}, {0x609c, 0x0050}, {0x6080, 0x006d},
+	{0x6092, 0x0026}, {0x60ff, 0x0020}, {0x6000, 0x0027}, {0x6000, 0x00ff},
+	{0x60c8, 0x00c0}, {0x6096, 0x00c1}, {0x6000, 0x008c}, {0x603d, 0x0086},
+	{0x6089, 0x0050}, {0x6090, 0x0051}, {0x602c, 0x0052}, {0x6000, 0x0053},
+	{0x6000, 0x0054}, {0x6088, 0x0055}, {0x6000, 0x0057}, {0x60a0, 0x005a},
+	{0x6078, 0x005b}, {0x6000, 0x005c}, {0x6004, 0x00d3}, {0x6000, 0x00e0},
+	{0x60ff, 0x00dd}, {0x60a1, 0x005a},
+};
+
+static struct validx tbl_800[] = {
+	{0x6000, 0x00ff}, {0x60f1, 0x00dd}, {0x6004, 0x00e0}, {0x6067, 0x00e1},
+	{0x6004, 0x00da}, {0x6000, 0x00ff}, {0x60f1, 0x00dd}, {0x6004, 0x00e0},
+	{0x6001, 0x00ff}, {0x6040, 0x0012}, {0x6000, 0x0011}, {0x6011, 0x0017},
+	{0x6043, 0x0018}, {0x6000, 0x0019}, {0x604b, 0x001a}, {0x6009, 0x0032},
+	{0x60ca, 0x004f}, {0x60a8, 0x0050}, {0x6000, 0x006d}, {0x6038, 0x003d},
+	{0x60c8, 0x0035}, {0x6000, 0x0022}, {0x6092, 0x0026}, {0x60ff, 0x0020},
+	{0x6000, 0x0027}, {0x6000, 0x00ff}, {0x6064, 0x00c0}, {0x604b, 0x00c1},
+	{0x6000, 0x008c}, {0x601d, 0x0086}, {0x6082, 0x00d3}, {0x6000, 0x00e0},
+	{0x60ff, 0x00dd}, {0x6020, 0x008c}, {0x6001, 0x00ff}, {0x6044, 0x0018},
+};
+
+static struct validx tbl_big1[] = {
+	{0x0002, 0x00c1}, {0x6000, 0x00ff}, {0x60f1, 0x00dd}, {0x6004, 0x00e0},
+	{0x6001, 0x00ff}, {0x6000, 0x0012}, {0x6000, 0x0000}, {0x6000, 0x0045},
+	{0x6000, 0x0010}, {0x6000, 0x0011}, {0x6011, 0x0017}, {0x6075, 0x0018},
+	{0x6001, 0x0019}, {0x6097, 0x001a}, {0x6036, 0x0032}, {0x60bb, 0x004f},
+	{0x609c, 0x0050}, {0x6057, 0x005a}, {0x6080, 0x006d}, {0x6043, 0x000f},
+	{0x608f, 0x0003}, {0x6005, 0x007c}, {0x6081, 0x0026}, {0x6000, 0x00ff},
+	{0x60c8, 0x00c0}, {0x6096, 0x00c1}, {0x6000, 0x008c},
+};
+
+static struct validx tbl_big2[] = {
+	{0x603d, 0x0086}, {0x6000, 0x0050}, {0x6090, 0x0051}, {0x602c, 0x0052},
+	{0x6000, 0x0053}, {0x6000, 0x0054}, {0x6088, 0x0055}, {0x6000, 0x0057},
+	{0x6040, 0x005a}, {0x60f0, 0x005b}, {0x6001, 0x005c}, {0x6082, 0x00d3},
+	{0x6000, 0x008e},
+};
+
+static struct validx tbl_big3[] = {
+	{0x6004, 0x00da}, {0x6000, 0x00e0}, {0x6067, 0x00e1}, {0x60ff, 0x00dd},
+	{0x6001, 0x00ff}, {0x6000, 0x00ff}, {0x60f1, 0x00dd}, {0x6004, 0x00e0},
+	{0x6001, 0x00ff}, {0x6000, 0x0011}, {0x6000, 0x00ff}, {0x6010, 0x00c7},
+	{0x6000, 0x0092}, {0x6006, 0x0093}, {0x60e3, 0x0093}, {0x6005, 0x0093},
+	{0x6005, 0x0093}, {0x60ed, 0x00c3}, {0x6000, 0x00a4}, {0x60d0, 0x0087},
+	{0x6003, 0x0096}, {0x600c, 0x0097}, {0x6024, 0x0097}, {0x6030, 0x0097},
+	{0x6028, 0x0097}, {0x6026, 0x0097}, {0x6002, 0x0097}, {0x6001, 0x00ff},
+	{0x6043, 0x000f}, {0x608f, 0x0003}, {0x6000, 0x002d}, {0x6000, 0x002e},
+	{0x600a, 0x0022}, {0x6002, 0x0070}, {0x6008, 0x0014}, {0x6048, 0x0014},
+	{0x6000, 0x00ff}, {0x6000, 0x00e0}, {0x60ff, 0x00dd},
+};
+
+static struct validx tbl_post_unset_alt[] = {
+	{0x006a, 0x000d}, {0x6001, 0x00ff}, {0x6081, 0x0026}, {0x6000, 0x0000},
+	{0x6000, 0x0045}, {0x6000, 0x0010}, {0x6068, 0x000d},
+	{50, 0xffff},
+	{0x0021, 0x0000},
+};
+
+static int  ov2640_init_at_startup(struct gspca_dev *gspca_dev);
+static int  ov2640_configure_alt(struct gspca_dev *gspca_dev);
+static int  ov2640_init_pre_alt(struct gspca_dev *gspca_dev);
+static int  ov2640_init_post_alt(struct gspca_dev *gspca_dev);
+static void ov2640_post_unset_alt(struct gspca_dev *gspca_dev);
+static int  ov2640_camera_settings(struct gspca_dev *gspca_dev);
+/*==========================================================================*/
+
+void ov2640_init_settings(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	sd->vcur.backlight  =  32;
+	sd->vcur.brightness =   0;
+	sd->vcur.sharpness  =   6;
+	sd->vcur.contrast   =   0;
+	sd->vcur.gamma      =  32;
+	sd->vcur.hue        =   0;
+	sd->vcur.saturation = 128;
+	sd->vcur.whitebal   =  64;
+	sd->vcur.mirror     =   0;
+	sd->vcur.flip       =   0;
+
+	sd->vmax.backlight  =  64;
+	sd->vmax.brightness = 255;
+	sd->vmax.sharpness  =  31;
+	sd->vmax.contrast   = 255;
+	sd->vmax.gamma      =  64;
+	sd->vmax.hue        = 254 + 2;
+	sd->vmax.saturation = 255;
+	sd->vmax.whitebal   = 128;
+	sd->vmax.mirror     = 1;
+	sd->vmax.flip       = 1;
+	sd->vmax.AC50Hz     = 0;
+
+	sd->dev_camera_settings = ov2640_camera_settings;
+	sd->dev_init_at_startup = ov2640_init_at_startup;
+	sd->dev_configure_alt   = ov2640_configure_alt;
+	sd->dev_init_pre_alt    = ov2640_init_pre_alt;
+	sd->dev_post_unset_alt  = ov2640_post_unset_alt;
+}
+
+/*==========================================================================*/
+
+static void common(struct gspca_dev *gspca_dev)
+{
+	fetch_validx(gspca_dev, tbl_common, ARRAY_SIZE(tbl_common));
+}
+
+static int ov2640_init_at_startup(struct gspca_dev *gspca_dev)
+{
+	fetch_validx(gspca_dev, tbl_init_at_startup,
+			ARRAY_SIZE(tbl_init_at_startup));
+
+	ctrl_out(gspca_dev, 0x40, 3, 0x0000, 0x0200, 12, dat_init1);
+
+	common(gspca_dev);
+
+	ctrl_in(gspca_dev, 0xc0, 2, 0x0000, 0x0006, 1, c61);
+
+	ctrl_out(gspca_dev, 0x40, 1, 0x00ef, 0x0006, 0, NULL);
+
+	ctrl_in(gspca_dev, 0xc0, 2, 0x0000, 0x0000, 1, c51);
+
+	ctrl_out(gspca_dev, 0x40, 1, 0x0051, 0x0000, 0, NULL);
+/*	ctrl_out(gspca_dev, 0x40, 11, 0x0000, 0x0000, 0, NULL); */
+
+	return 0;
+}
+
+static int ov2640_init_pre_alt(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	sd->mirrorMask = 0;
+
+	sd->vold.backlight  = -1;
+	sd->vold.brightness = -1;
+	sd->vold.sharpness  = -1;
+	sd->vold.contrast   = -1;
+	sd->vold.saturation = -1;
+	sd->vold.gamma    = -1;
+	sd->vold.hue      = -1;
+	sd->vold.whitebal = -1;
+	sd->vold.mirror = -1;
+	sd->vold.flip   = -1;
+
+	ov2640_init_post_alt(gspca_dev);
+
+	return 0;
+}
+
+static int ov2640_init_post_alt(struct gspca_dev *gspca_dev)
+{
+	s32 reso = gspca_dev->cam.cam_mode[(s32) gspca_dev->curr_mode].priv;
+	s32 n; /* reserved for FETCH functions */
+
+	ctrl_out(gspca_dev, 0x40, 5, 0x0001, 0x0000, 0, NULL);
+
+	n = fetch_validx(gspca_dev, tbl_sensor_settings_common1,
+			ARRAY_SIZE(tbl_sensor_settings_common1));
+	ctrl_out(gspca_dev, 0x40, 3, 0x0000, 0x0200, 12, dat_post);
+	common(gspca_dev);
+	keep_on_fetching_validx(gspca_dev, tbl_sensor_settings_common1,
+				ARRAY_SIZE(tbl_sensor_settings_common1), n);
+
+	switch (reso) {
+	case IMAGE_640:
+		n = fetch_validx(gspca_dev, tbl_640, ARRAY_SIZE(tbl_640));
+		ctrl_out(gspca_dev, 0x40, 3, 0x0000, 0x0200, 12, dat_640);
+		break;
+
+	case IMAGE_800:
+		n = fetch_validx(gspca_dev, tbl_800, ARRAY_SIZE(tbl_800));
+		ctrl_out(gspca_dev, 0x40, 3, 0x0000, 0x0200, 12, dat_800);
+		break;
+
+	case IMAGE_1600:
+	case IMAGE_1280:
+		n = fetch_validx(gspca_dev, tbl_big1, ARRAY_SIZE(tbl_big1));
+
+		if (reso == IMAGE_1280) {
+			n = fetch_validx(gspca_dev, tbl_big2,
+					ARRAY_SIZE(tbl_big2));
+		} else {
+			ctrl_out(gspca_dev, 0x40, 1, 0x601d, 0x0086, 0, NULL);
+			ctrl_out(gspca_dev, 0x40, 1, 0x6001, 0x00d7, 0, NULL);
+			ctrl_out(gspca_dev, 0x40, 1, 0x6082, 0x00d3, 0, NULL);
+		}
+
+		n = fetch_validx(gspca_dev, tbl_big3, ARRAY_SIZE(tbl_big3));
+
+		if (reso == IMAGE_1280) {
+			ctrl_out(gspca_dev, 0x40, 1, 0x6001, 0x00ff, 0, NULL);
+			ctrl_out(gspca_dev, 0x40, 3, 0x0000, 0x0200,
+					12, dat_1280);
+		} else {
+			ctrl_out(gspca_dev, 0x40, 1, 0x6020, 0x008c, 0, NULL);
+			ctrl_out(gspca_dev, 0x40, 1, 0x6001, 0x00ff, 0, NULL);
+			ctrl_out(gspca_dev, 0x40, 1, 0x6076, 0x0018, 0, NULL);
+			ctrl_out(gspca_dev, 0x40, 3, 0x0000, 0x0200,
+					12, dat_1600);
+		}
+		break;
+	}
+
+	n = fetch_validx(gspca_dev, tbl_sensor_settings_common2,
+			ARRAY_SIZE(tbl_sensor_settings_common2));
+
+	ov2640_camera_settings(gspca_dev);
+
+	return 0;
+}
+
+static int ov2640_configure_alt(struct gspca_dev *gspca_dev)
+{
+	s32 reso = gspca_dev->cam.cam_mode[(s32) gspca_dev->curr_mode].priv;
+
+	switch (reso) {
+	case IMAGE_640:
+		gspca_dev->alt = 3 + 1;
+		break;
+
+	case IMAGE_800:
+	case IMAGE_1280:
+	case IMAGE_1600:
+		gspca_dev->alt = 1 + 1;
+		break;
+	}
+	return 0;
+}
+
+static int ov2640_camera_settings(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	s32 backlight = sd->vcur.backlight;
+	s32 bright = sd->vcur.brightness;
+	s32 sharp  = sd->vcur.sharpness;
+	s32 gam    = sd->vcur.gamma;
+	s32 cntr   = sd->vcur.contrast;
+	s32 sat    = sd->vcur.saturation;
+	s32 hue    = sd->vcur.hue;
+	s32 wbal   = sd->vcur.whitebal;
+	s32 mirror = (((sd->vcur.mirror > 0) ^ sd->mirrorMask) == 0);
+	s32 flip   = (((sd->vcur.flip   > 0) ^ sd->mirrorMask) == 0);
+
+	if (backlight != sd->vold.backlight) {
+		/* No sd->vold.backlight=backlight; (to be done again later) */
+		if (backlight < 0 || backlight > sd->vmax.backlight)
+			backlight = 0;
+
+		ctrl_out(gspca_dev, 0x40, 1, 0x6001                 , 0x00ff,
+				0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0x601e + backlight     , 0x0024,
+				0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0x601e + backlight - 10, 0x0025,
+				0, NULL);
+	}
+
+	if (bright != sd->vold.brightness) {
+		sd->vold.brightness = bright;
+		if (bright < 0 || bright > sd->vmax.brightness)
+			bright = 0;
+
+		ctrl_out(gspca_dev, 0x40, 1, 0x6000         , 0x00ff, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0x6009         , 0x007c, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0x6000 + bright, 0x007d, 0, NULL);
+	}
+
+	if (wbal != sd->vold.whitebal) {
+		sd->vold.whitebal = wbal;
+		if (wbal < 0 || wbal > sd->vmax.whitebal)
+			wbal = 0;
+
+		ctrl_out(gspca_dev, 0x40, 1, 0x6000       , 0x00ff, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0x6003       , 0x007c, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0x6000 + wbal, 0x007d, 0, NULL);
+	}
+
+	if (cntr != sd->vold.contrast) {
+		sd->vold.contrast = cntr;
+		if (cntr < 0 || cntr > sd->vmax.contrast)
+			cntr = 0;
+
+		ctrl_out(gspca_dev, 0x40, 1, 0x6000       , 0x00ff, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0x6007       , 0x007c, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0x6000 + cntr, 0x007d, 0, NULL);
+	}
+
+	if (sat != sd->vold.saturation) {
+		sd->vold.saturation = sat;
+		if (sat < 0 || sat > sd->vmax.saturation)
+			sat = 0;
+
+		ctrl_out(gspca_dev, 0x40, 1, 0x6000      , 0x00ff, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0x6001      , 0x007c, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0x6000 + sat, 0x007d, 0, NULL);
+	}
+
+	if (sharp != sd->vold.sharpness) {
+		sd->vold.sharpness = sharp;
+		if (sharp < 0 || sharp > sd->vmax.sharpness)
+			sharp = 0;
+
+		ctrl_out(gspca_dev, 0x40, 1, 0x6000        , 0x00ff, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0x6001        , 0x0092, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0x60c0 + sharp, 0x0093, 0, NULL);
+	}
+
+	if (hue != sd->vold.hue) {
+		sd->vold.hue = hue;
+		if (hue < 0 || hue > sd->vmax.hue)
+			hue = 0;
+
+		ctrl_out(gspca_dev, 0x40, 1, 0x6000     , 0x00ff, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0x6002     , 0x007c, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0x6000 + hue * (hue < 255), 0x007d,
+				0, NULL);
+		if (hue >= 255)
+			sd->swapRB = 1;
+		else
+			sd->swapRB = 0;
+	}
+
+	if (gam != sd->vold.gamma) {
+		sd->vold.gamma = gam;
+		if (gam < 0 || gam > sd->vmax.gamma)
+			gam = 0;
+
+		ctrl_out(gspca_dev, 0x40, 1, 0x6000      , 0x00ff, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0x6008      , 0x007c, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0x6000 + gam, 0x007d, 0, NULL);
+	}
+
+	if (mirror != sd->vold.mirror || flip != sd->vold.flip) {
+		sd->vold.mirror = mirror;
+		sd->vold.flip   = flip;
+
+		mirror = 0x80 * mirror;
+		ctrl_out(gspca_dev, 0x40, 1, 0x6001, 0x00ff, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0x6000, 0x8004, 0, NULL);
+		ctrl_in(gspca_dev, 0xc0, 2, 0x6000, 0x8004, 1, c28);
+		ctrl_out(gspca_dev, 0x40, 1, 0x6028 + mirror, 0x0004, 0, NULL);
+
+		flip = 0x50 * flip + mirror;
+		ctrl_out(gspca_dev, 0x40, 1, 0x6001, 0x00ff, 0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0x6000, 0x8004, 0, NULL);
+		ctrl_in(gspca_dev, 0xc0, 2, 0x6000, 0x8004, 1, ca8);
+		ctrl_out(gspca_dev, 0x40, 1, 0x6028 + flip, 0x0004, 0, NULL);
+
+		ctrl_in(gspca_dev, 0xc0, 2, 0x0000, 0x0000, 1, c50);
+	}
+
+	if (backlight != sd->vold.backlight) {
+		sd->vold.backlight = backlight;
+
+		ctrl_out(gspca_dev, 0x40, 1, 0x6001                 , 0x00ff,
+				0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0x601e + backlight     , 0x0024,
+				0, NULL);
+		ctrl_out(gspca_dev, 0x40, 1, 0x601e + backlight - 10, 0x0025,
+				0, NULL);
+	}
+
+	return 0;
+}
+
+static void ov2640_post_unset_alt(struct gspca_dev *gspca_dev)
+{
+	ctrl_out(gspca_dev, 0x40, 5, 0x0000, 0x0000, 0, NULL);
+	msleep(20);
+	fetch_validx(gspca_dev, tbl_post_unset_alt,
+			ARRAY_SIZE(tbl_post_unset_alt));
+}
diff --git a/drivers/media/usb/gspca/gl860/gl860-ov9655.c b/drivers/media/usb/gspca/gl860/gl860-ov9655.c
new file mode 100644
index 0000000..5ae9619
--- /dev/null
+++ b/drivers/media/usb/gspca/gl860/gl860-ov9655.c
@@ -0,0 +1,336 @@
+/* Subdriver for the GL860 chip with the OV9655 sensor
+ * Author Olivier LORIN, from logs done by Simon (Sur3) and Almighurt
+ * on dsd's weblog
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/* Sensor : OV9655 */
+
+#include "gl860.h"
+
+static struct validx tbl_init_at_startup[] = {
+	{0x0000, 0x0000}, {0x0010, 0x0010}, {0x0008, 0x00c0}, {0x0001, 0x00c1},
+	{0x0001, 0x00c2}, {0x0020, 0x0006}, {0x006a, 0x000d},
+
+	{0x0040, 0x0000},
+};
+
+static struct validx tbl_commmon[] = {
+	{0x0041, 0x0000}, {0x006a, 0x0007}, {0x0063, 0x0006}, {0x006a, 0x000d},
+	{0x0000, 0x00c0}, {0x0010, 0x0010}, {0x0001, 0x00c1}, {0x0041, 0x00c2},
+	{0x0004, 0x00d8}, {0x0012, 0x0004}, {0x0000, 0x0058}, {0x0040, 0x0000},
+	{0x00f3, 0x0006}, {0x0058, 0x0000}, {0x0048, 0x0000}, {0x0061, 0x0000},
+};
+
+static s32 tbl_length[] = {12, 56, 52, 54, 56, 42, 32, 12};
+
+static u8 *tbl_640[] = {
+	"\x00\x40\x07\x6a\x06\xf3\x0d\x6a" "\x10\x10\xc1\x01"
+	,
+	"\x12\x80\x00\x00\x01\x98\x02\x80" "\x03\x12\x04\x03\x0b\x57\x0e\x61"
+	"\x0f\x42\x11\x01\x12\x60\x13\x00" "\x14\x3a\x16\x24\x17\x14\x18\x00"
+	"\x19\x01\x1a\x3d\x1e\x04\x24\x3c" "\x25\x36\x26\x72\x27\x08\x28\x08"
+	"\x29\x15\x2a\x00\x2b\x00\x2c\x08"
+	,
+	"\x32\xff\x33\x00\x34\x3d\x35\x00" "\x36\xfa\x38\x72\x39\x57\x3a\x00"
+	"\x3b\x0c\x3d\x99\x3e\x0c\x3f\xc1" "\x40\xc0\x41\x00\x42\xc0\x43\x0a"
+	"\x44\xf0\x45\x46\x46\x62\x47\x2a" "\x48\x3c\x4a\xee\x4b\xe7\x4c\xe7"
+	"\x4d\xe7\x4e\xe7"
+	,
+	"\x4f\x98\x50\x98\x51\x00\x52\x28" "\x53\x70\x54\x98\x58\x1a\x59\x85"
+	"\x5a\xa9\x5b\x64\x5c\x84\x5d\x53" "\x5e\x0e\x5f\xf0\x60\xf0\x61\xf0"
+	"\x62\x00\x63\x00\x64\x02\x65\x20" "\x66\x00\x69\x0a\x6b\x5a\x6c\x04"
+	"\x6d\x55\x6e\x00\x6f\x9d"
+	,
+	"\x70\x15\x71\x78\x72\x00\x73\x00" "\x74\x3a\x75\x35\x76\x01\x77\x02"
+	"\x7a\x24\x7b\x04\x7c\x07\x7d\x10" "\x7e\x28\x7f\x36\x80\x44\x81\x52"
+	"\x82\x60\x83\x6c\x84\x78\x85\x8c" "\x86\x9e\x87\xbb\x88\xd2\x89\xe5"
+	"\x8a\x23\x8c\x8d\x90\x7c\x91\x7b"
+	,
+	"\x9d\x02\x9e\x02\x9f\x74\xa0\x73" "\xa1\x40\xa4\x50\xa5\x68\xa6\x70"
+	"\xa8\xc1\xa9\xef\xaa\x92\xab\x04" "\xac\x80\xad\x80\xae\x80\xaf\x80"
+	"\xb2\xf2\xb3\x20\xb4\x20\xb5\x00" "\xb6\xaf"
+	,
+	"\xbb\xae\xbc\x4f\xbd\x4e\xbe\x6a" "\xbf\x68\xc0\xaa\xc1\xc0\xc2\x01"
+	"\xc3\x4e\xc6\x85\xc7\x81\xc9\xe0" "\xca\xe8\xcb\xf0\xcc\xd8\xcd\x93"
+	,
+	"\xd0\x01\xd1\x08\xd2\xe0\xd3\x01" "\xd4\x10\xd5\x80"
+};
+
+static u8 *tbl_1280[] = {
+	"\x00\x40\x07\x6a\x06\xf3\x0d\x6a" "\x10\x10\xc1\x01"
+	,
+	"\x12\x80\x00\x00\x01\x98\x02\x80" "\x03\x12\x04\x01\x0b\x57\x0e\x61"
+	"\x0f\x42\x11\x00\x12\x00\x13\x00" "\x14\x3a\x16\x24\x17\x1b\x18\xbb"
+	"\x19\x01\x1a\x81\x1e\x04\x24\x3c" "\x25\x36\x26\x72\x27\x08\x28\x08"
+	"\x29\x15\x2a\x00\x2b\x00\x2c\x08"
+	,
+	"\x32\xa4\x33\x00\x34\x3d\x35\x00" "\x36\xf8\x38\x72\x39\x57\x3a\x00"
+	"\x3b\x0c\x3d\x99\x3e\x0c\x3f\xc2" "\x40\xc0\x41\x00\x42\xc0\x43\x0a"
+	"\x44\xf0\x45\x46\x46\x62\x47\x2a" "\x48\x3c\x4a\xec\x4b\xe8\x4c\xe8"
+	"\x4d\xe8\x4e\xe8"
+	,
+	"\x4f\x98\x50\x98\x51\x00\x52\x28" "\x53\x70\x54\x98\x58\x1a\x59\x85"
+	"\x5a\xa9\x5b\x64\x5c\x84\x5d\x53" "\x5e\x0e\x5f\xf0\x60\xf0\x61\xf0"
+	"\x62\x00\x63\x00\x64\x02\x65\x20" "\x66\x00\x69\x02\x6b\x5a\x6c\x04"
+	"\x6d\x55\x6e\x00\x6f\x9d"
+	,
+	"\x70\x08\x71\x78\x72\x00\x73\x01" "\x74\x3a\x75\x35\x76\x01\x77\x02"
+	"\x7a\x24\x7b\x04\x7c\x07\x7d\x10" "\x7e\x28\x7f\x36\x80\x44\x81\x52"
+	"\x82\x60\x83\x6c\x84\x78\x85\x8c" "\x86\x9e\x87\xbb\x88\xd2\x89\xe5"
+	"\x8a\x23\x8c\x0d\x90\x90\x91\x90"
+	,
+	"\x9d\x02\x9e\x02\x9f\x94\xa0\x94" "\xa1\x01\xa4\x50\xa5\x68\xa6\x70"
+	"\xa8\xc1\xa9\xef\xaa\x92\xab\x04" "\xac\x80\xad\x80\xae\x80\xaf\x80"
+	"\xb2\xf2\xb3\x20\xb4\x20\xb5\x00" "\xb6\xaf"
+	,
+	"\xbb\xae\xbc\x38\xbd\x39\xbe\x01" "\xbf\x01\xc0\xe2\xc1\xc0\xc2\x01"
+	"\xc3\x4e\xc6\x85\xc7\x81\xc9\xe0" "\xca\xe8\xcb\xf0\xcc\xd8\xcd\x93"
+	,
+	"\xd0\x21\xd1\x18\xd2\xe0\xd3\x01" "\xd4\x28\xd5\x00"
+};
+
+static u8 c04[] = {0x04};
+static u8 dat_post1[] = "\x04\x00\x10\x20\xa1\x00\x00\x02";
+static u8 dat_post2[] = "\x10\x10\xc1\x02";
+static u8 dat_post3[] = "\x04\x00\x10\x7c\xa1\x00\x00\x04";
+static u8 dat_post4[] = "\x10\x02\xc1\x06";
+static u8 dat_post5[] = "\x04\x00\x10\x7b\xa1\x00\x00\x08";
+static u8 dat_post6[] = "\x10\x10\xc1\x05";
+static u8 dat_post7[] = "\x04\x00\x10\x7c\xa1\x00\x00\x08";
+static u8 dat_post8[] = "\x04\x00\x10\x7c\xa1\x00\x00\x09";
+
+static struct validx tbl_init_post_alt[] = {
+	{0x6032, 0x00ff}, {0x6032, 0x00ff}, {0x6032, 0x00ff}, {0x603c, 0x00ff},
+	{0x6003, 0x00ff}, {0x6032, 0x00ff}, {0x6032, 0x00ff}, {0x6001, 0x00ff},
+	{0x6000, 0x801e},
+	{0xffff, 0xffff},
+	{0x6004, 0x001e}, {0x6000, 0x801e},
+	{0xffff, 0xffff},
+	{0x6004, 0x001e}, {0x6012, 0x0003}, {0x6000, 0x801e},
+	{0xffff, 0xffff},
+	{0x6004, 0x001e}, {0x6000, 0x801e},
+	{0xffff, 0xffff},
+	{0x6004, 0x001e}, {0x6012, 0x0003},
+	{0xffff, 0xffff},
+	{0x6000, 0x801e},
+	{0xffff, 0xffff},
+	{0x6004, 0x001e}, {0x6000, 0x801e},
+	{0xffff, 0xffff},
+	{0x6004, 0x001e}, {0x6012, 0x0003}, {0x6000, 0x801e},
+	{0xffff, 0xffff},
+	{0x6004, 0x001e}, {0x6000, 0x801e},
+	{0xffff, 0xffff},
+	{0x6004, 0x001e}, {0x6012, 0x0003},
+	{0xffff, 0xffff},
+	{0x6000, 0x801e},
+	{0xffff, 0xffff},
+	{0x6004, 0x001e}, {0x6000, 0x801e},
+	{0xffff, 0xffff},
+	{0x6004, 0x001e}, {0x6012, 0x0003},
+};
+
+static int  ov9655_init_at_startup(struct gspca_dev *gspca_dev);
+static int  ov9655_configure_alt(struct gspca_dev *gspca_dev);
+static int  ov9655_init_pre_alt(struct gspca_dev *gspca_dev);
+static int  ov9655_init_post_alt(struct gspca_dev *gspca_dev);
+static void ov9655_post_unset_alt(struct gspca_dev *gspca_dev);
+static int  ov9655_camera_settings(struct gspca_dev *gspca_dev);
+/*==========================================================================*/
+
+void ov9655_init_settings(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	sd->vcur.backlight  =   0;
+	sd->vcur.brightness = 128;
+	sd->vcur.sharpness  =   0;
+	sd->vcur.contrast   =   0;
+	sd->vcur.gamma      =   0;
+	sd->vcur.hue        =   0;
+	sd->vcur.saturation =   0;
+	sd->vcur.whitebal   =   0;
+
+	sd->vmax.backlight  =   0;
+	sd->vmax.brightness = 255;
+	sd->vmax.sharpness  =   0;
+	sd->vmax.contrast   =   0;
+	sd->vmax.gamma      =   0;
+	sd->vmax.hue        =   0 + 1;
+	sd->vmax.saturation =   0;
+	sd->vmax.whitebal   =   0;
+	sd->vmax.mirror     = 0;
+	sd->vmax.flip       = 0;
+	sd->vmax.AC50Hz     = 0;
+
+	sd->dev_camera_settings = ov9655_camera_settings;
+	sd->dev_init_at_startup = ov9655_init_at_startup;
+	sd->dev_configure_alt   = ov9655_configure_alt;
+	sd->dev_init_pre_alt    = ov9655_init_pre_alt;
+	sd->dev_post_unset_alt  = ov9655_post_unset_alt;
+}
+
+/*==========================================================================*/
+
+static int ov9655_init_at_startup(struct gspca_dev *gspca_dev)
+{
+	fetch_validx(gspca_dev, tbl_init_at_startup,
+			ARRAY_SIZE(tbl_init_at_startup));
+	fetch_validx(gspca_dev, tbl_commmon, ARRAY_SIZE(tbl_commmon));
+/*	ctrl_out(gspca_dev, 0x40, 11, 0x0000, 0x0000, 0, NULL);*/
+
+	return 0;
+}
+
+static int ov9655_init_pre_alt(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	sd->vold.brightness = -1;
+	sd->vold.hue = -1;
+
+	fetch_validx(gspca_dev, tbl_commmon, ARRAY_SIZE(tbl_commmon));
+
+	ov9655_init_post_alt(gspca_dev);
+
+	return 0;
+}
+
+static int ov9655_init_post_alt(struct gspca_dev *gspca_dev)
+{
+	s32 reso = gspca_dev->cam.cam_mode[(s32) gspca_dev->curr_mode].priv;
+	s32 n; /* reserved for FETCH functions */
+	s32 i;
+	u8 **tbl;
+
+	ctrl_out(gspca_dev, 0x40, 5, 0x0001, 0x0000, 0, NULL);
+
+	tbl = (reso == IMAGE_640) ? tbl_640 : tbl_1280;
+
+	ctrl_out(gspca_dev, 0x40, 3, 0x0000, 0x0200,
+			tbl_length[0], tbl[0]);
+	for (i = 1; i < 7; i++)
+		ctrl_out(gspca_dev, 0x40, 3, 0x6000, 0x0200,
+				tbl_length[i], tbl[i]);
+	ctrl_out(gspca_dev, 0x40, 3, 0x0000, 0x0200,
+			tbl_length[7], tbl[7]);
+
+	n = fetch_validx(gspca_dev, tbl_init_post_alt,
+			ARRAY_SIZE(tbl_init_post_alt));
+
+	ctrl_in(gspca_dev, 0xc0, 2, 0x6000, 0x801e, 1, c04);
+	keep_on_fetching_validx(gspca_dev, tbl_init_post_alt,
+					ARRAY_SIZE(tbl_init_post_alt), n);
+	ctrl_in(gspca_dev, 0xc0, 2, 0x6000, 0x801e, 1, c04);
+	keep_on_fetching_validx(gspca_dev, tbl_init_post_alt,
+					ARRAY_SIZE(tbl_init_post_alt), n);
+	ctrl_in(gspca_dev, 0xc0, 2, 0x6000, 0x801e, 1, c04);
+	keep_on_fetching_validx(gspca_dev, tbl_init_post_alt,
+					ARRAY_SIZE(tbl_init_post_alt), n);
+	ctrl_in(gspca_dev, 0xc0, 2, 0x6000, 0x801e, 1, c04);
+	keep_on_fetching_validx(gspca_dev, tbl_init_post_alt,
+					ARRAY_SIZE(tbl_init_post_alt), n);
+	ctrl_out(gspca_dev, 0x40, 3, 0x6000, 0x0200, 8, dat_post1);
+	keep_on_fetching_validx(gspca_dev, tbl_init_post_alt,
+					ARRAY_SIZE(tbl_init_post_alt), n);
+
+	ctrl_in(gspca_dev, 0xc0, 2, 0x6000, 0x801e, 1, c04);
+	keep_on_fetching_validx(gspca_dev, tbl_init_post_alt,
+					ARRAY_SIZE(tbl_init_post_alt), n);
+	ctrl_in(gspca_dev, 0xc0, 2, 0x6000, 0x801e, 1, c04);
+	keep_on_fetching_validx(gspca_dev, tbl_init_post_alt,
+					ARRAY_SIZE(tbl_init_post_alt), n);
+	ctrl_in(gspca_dev, 0xc0, 2, 0x6000, 0x801e, 1, c04);
+	keep_on_fetching_validx(gspca_dev, tbl_init_post_alt,
+					ARRAY_SIZE(tbl_init_post_alt), n);
+	ctrl_in(gspca_dev, 0xc0, 2, 0x6000, 0x801e, 1, c04);
+	keep_on_fetching_validx(gspca_dev, tbl_init_post_alt,
+					ARRAY_SIZE(tbl_init_post_alt), n);
+	ctrl_out(gspca_dev, 0x40, 3, 0x6000, 0x0200, 8, dat_post1);
+	keep_on_fetching_validx(gspca_dev, tbl_init_post_alt,
+					ARRAY_SIZE(tbl_init_post_alt), n);
+
+	ctrl_in(gspca_dev, 0xc0, 2, 0x6000, 0x801e, 1, c04);
+	keep_on_fetching_validx(gspca_dev, tbl_init_post_alt,
+					ARRAY_SIZE(tbl_init_post_alt), n);
+	ctrl_in(gspca_dev, 0xc0, 2, 0x6000, 0x801e, 1, c04);
+	keep_on_fetching_validx(gspca_dev, tbl_init_post_alt,
+					ARRAY_SIZE(tbl_init_post_alt), n);
+
+	ctrl_out(gspca_dev, 0x40, 3, 0x6000, 0x0200, 8, dat_post1);
+
+	ctrl_out(gspca_dev, 0x40, 3, 0x0000, 0x0200, 4, dat_post2);
+	ctrl_out(gspca_dev, 0x40, 3, 0x6000, 0x0200, 8, dat_post3);
+
+	ctrl_out(gspca_dev, 0x40, 3, 0x0000, 0x0200, 4, dat_post4);
+	ctrl_out(gspca_dev, 0x40, 3, 0x6000, 0x0200, 8, dat_post5);
+
+	ctrl_out(gspca_dev, 0x40, 3, 0x0000, 0x0200, 4, dat_post6);
+	ctrl_out(gspca_dev, 0x40, 3, 0x6000, 0x0200, 8, dat_post7);
+
+	ctrl_out(gspca_dev, 0x40, 3, 0x6000, 0x0200, 8, dat_post8);
+
+	ov9655_camera_settings(gspca_dev);
+
+	return 0;
+}
+
+static int ov9655_configure_alt(struct gspca_dev *gspca_dev)
+{
+	s32 reso = gspca_dev->cam.cam_mode[(s32) gspca_dev->curr_mode].priv;
+
+	switch (reso) {
+	case IMAGE_640:
+		gspca_dev->alt = 1 + 1;
+		break;
+
+	default:
+		gspca_dev->alt = 1 + 1;
+		break;
+	}
+	return 0;
+}
+
+static int ov9655_camera_settings(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	u8 dat_bright[] = "\x04\x00\x10\x7c\xa1\x00\x00\x70";
+
+	s32 bright = sd->vcur.brightness;
+	s32 hue    = sd->vcur.hue;
+
+	if (bright != sd->vold.brightness) {
+		sd->vold.brightness = bright;
+		if (bright < 0 || bright > sd->vmax.brightness)
+			bright = 0;
+
+		dat_bright[3] = bright;
+		ctrl_out(gspca_dev, 0x40, 3, 0x6000, 0x0200, 8, dat_bright);
+	}
+
+	if (hue != sd->vold.hue) {
+		sd->vold.hue = hue;
+		sd->swapRB = (hue != 0);
+	}
+
+	return 0;
+}
+
+static void ov9655_post_unset_alt(struct gspca_dev *gspca_dev)
+{
+	ctrl_out(gspca_dev, 0x40, 5, 0x0000, 0x0000, 0, NULL);
+	ctrl_out(gspca_dev, 0x40, 1, 0x0061, 0x0000, 0, NULL);
+}
diff --git a/drivers/media/usb/gspca/gl860/gl860.c b/drivers/media/usb/gspca/gl860/gl860.c
new file mode 100644
index 0000000..262200a
--- /dev/null
+++ b/drivers/media/usb/gspca/gl860/gl860.c
@@ -0,0 +1,742 @@
+/* GSPCA subdrivers for Genesys Logic webcams with the GL860 chip
+ * Subdriver core
+ *
+ * 2009/09/24 Olivier Lorin <o.lorin@laposte.net>
+ * GSPCA by Jean-Francois Moine <http://moinejf.free.fr>
+ * Thanks BUGabundo and Malmostoso for your amazing help!
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include "gspca.h"
+#include "gl860.h"
+
+MODULE_AUTHOR("Olivier Lorin <o.lorin@laposte.net>");
+MODULE_DESCRIPTION("Genesys Logic USB PC Camera Driver");
+MODULE_LICENSE("GPL");
+
+/*======================== static function declarations ====================*/
+
+static void (*dev_init_settings)(struct gspca_dev *gspca_dev);
+
+static int  sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id);
+static int  sd_init(struct gspca_dev *gspca_dev);
+static int  sd_isoc_init(struct gspca_dev *gspca_dev);
+static int  sd_start(struct gspca_dev *gspca_dev);
+static void sd_stop0(struct gspca_dev *gspca_dev);
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data, int len);
+static void sd_callback(struct gspca_dev *gspca_dev);
+
+static int gl860_guess_sensor(struct gspca_dev *gspca_dev,
+				u16 vendor_id, u16 product_id);
+
+/*============================ driver options ==============================*/
+
+static s32 AC50Hz = 0xff;
+module_param(AC50Hz, int, 0644);
+MODULE_PARM_DESC(AC50Hz, " Does AC power frequency is 50Hz? (0/1)");
+
+static char sensor[7];
+module_param_string(sensor, sensor, sizeof(sensor), 0644);
+MODULE_PARM_DESC(sensor,
+		" Driver sensor ('MI1320'/'MI2020'/'OV9655'/'OV2640')");
+
+/*============================ webcam controls =============================*/
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		sd->vcur.brightness = ctrl->val;
+		break;
+	case V4L2_CID_CONTRAST:
+		sd->vcur.contrast = ctrl->val;
+		break;
+	case V4L2_CID_SATURATION:
+		sd->vcur.saturation = ctrl->val;
+		break;
+	case V4L2_CID_HUE:
+		sd->vcur.hue = ctrl->val;
+		break;
+	case V4L2_CID_GAMMA:
+		sd->vcur.gamma = ctrl->val;
+		break;
+	case V4L2_CID_HFLIP:
+		sd->vcur.mirror = ctrl->val;
+		break;
+	case V4L2_CID_VFLIP:
+		sd->vcur.flip = ctrl->val;
+		break;
+	case V4L2_CID_POWER_LINE_FREQUENCY:
+		sd->vcur.AC50Hz = ctrl->val;
+		break;
+	case V4L2_CID_WHITE_BALANCE_TEMPERATURE:
+		sd->vcur.whitebal = ctrl->val;
+		break;
+	case V4L2_CID_SHARPNESS:
+		sd->vcur.sharpness = ctrl->val;
+		break;
+	case V4L2_CID_BACKLIGHT_COMPENSATION:
+		sd->vcur.backlight = ctrl->val;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (gspca_dev->streaming)
+		sd->waitSet = 1;
+
+	return 0;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 11);
+
+	if (sd->vmax.brightness)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops, V4L2_CID_BRIGHTNESS,
+				  0, sd->vmax.brightness, 1,
+				  sd->vcur.brightness);
+
+	if (sd->vmax.contrast)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops, V4L2_CID_CONTRAST,
+				  0, sd->vmax.contrast, 1,
+				  sd->vcur.contrast);
+
+	if (sd->vmax.saturation)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops, V4L2_CID_SATURATION,
+				  0, sd->vmax.saturation, 1,
+				  sd->vcur.saturation);
+
+	if (sd->vmax.hue)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops, V4L2_CID_HUE,
+				  0, sd->vmax.hue, 1, sd->vcur.hue);
+
+	if (sd->vmax.gamma)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops, V4L2_CID_GAMMA,
+				  0, sd->vmax.gamma, 1, sd->vcur.gamma);
+
+	if (sd->vmax.mirror)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops, V4L2_CID_HFLIP,
+				  0, sd->vmax.mirror, 1, sd->vcur.mirror);
+
+	if (sd->vmax.flip)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops, V4L2_CID_VFLIP,
+				  0, sd->vmax.flip, 1, sd->vcur.flip);
+
+	if (sd->vmax.AC50Hz)
+		v4l2_ctrl_new_std_menu(hdl, &sd_ctrl_ops,
+				  V4L2_CID_POWER_LINE_FREQUENCY,
+				  sd->vmax.AC50Hz, 0, sd->vcur.AC50Hz);
+
+	if (sd->vmax.whitebal)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+				  V4L2_CID_WHITE_BALANCE_TEMPERATURE,
+				  0, sd->vmax.whitebal, 1, sd->vcur.whitebal);
+
+	if (sd->vmax.sharpness)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops, V4L2_CID_SHARPNESS,
+				  0, sd->vmax.sharpness, 1,
+				  sd->vcur.sharpness);
+
+	if (sd->vmax.backlight)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+				  V4L2_CID_BACKLIGHT_COMPENSATION,
+				  0, sd->vmax.backlight, 1,
+				  sd->vcur.backlight);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+
+	return 0;
+}
+
+/*==================== sud-driver structure initialisation =================*/
+
+static const struct sd_desc sd_desc_mi1320 = {
+	.name        = MODULE_NAME,
+	.config      = sd_config,
+	.init        = sd_init,
+	.init_controls = sd_init_controls,
+	.isoc_init   = sd_isoc_init,
+	.start       = sd_start,
+	.stop0       = sd_stop0,
+	.pkt_scan    = sd_pkt_scan,
+	.dq_callback = sd_callback,
+};
+
+static const struct sd_desc sd_desc_mi2020 = {
+	.name        = MODULE_NAME,
+	.config      = sd_config,
+	.init        = sd_init,
+	.init_controls = sd_init_controls,
+	.isoc_init   = sd_isoc_init,
+	.start       = sd_start,
+	.stop0       = sd_stop0,
+	.pkt_scan    = sd_pkt_scan,
+	.dq_callback = sd_callback,
+};
+
+static const struct sd_desc sd_desc_ov2640 = {
+	.name        = MODULE_NAME,
+	.config      = sd_config,
+	.init        = sd_init,
+	.init_controls = sd_init_controls,
+	.isoc_init   = sd_isoc_init,
+	.start       = sd_start,
+	.stop0       = sd_stop0,
+	.pkt_scan    = sd_pkt_scan,
+	.dq_callback = sd_callback,
+};
+
+static const struct sd_desc sd_desc_ov9655 = {
+	.name        = MODULE_NAME,
+	.config      = sd_config,
+	.init        = sd_init,
+	.init_controls = sd_init_controls,
+	.isoc_init   = sd_isoc_init,
+	.start       = sd_start,
+	.stop0       = sd_stop0,
+	.pkt_scan    = sd_pkt_scan,
+	.dq_callback = sd_callback,
+};
+
+/*=========================== sub-driver image sizes =======================*/
+
+static struct v4l2_pix_format mi2020_mode[] = {
+	{ 640,  480, V4L2_PIX_FMT_SGBRG8, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0
+	},
+	{ 800,  598, V4L2_PIX_FMT_SGBRG8, V4L2_FIELD_NONE,
+		.bytesperline = 800,
+		.sizeimage = 800 * 598,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1
+	},
+	{1280, 1024, V4L2_PIX_FMT_SGBRG8, V4L2_FIELD_NONE,
+		.bytesperline = 1280,
+		.sizeimage = 1280 * 1024,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 2
+	},
+	{1600, 1198, V4L2_PIX_FMT_SGBRG8, V4L2_FIELD_NONE,
+		.bytesperline = 1600,
+		.sizeimage = 1600 * 1198,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 3
+	},
+};
+
+static struct v4l2_pix_format ov2640_mode[] = {
+	{ 640,  480, V4L2_PIX_FMT_SGBRG8, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0
+	},
+	{ 800,  600, V4L2_PIX_FMT_SGBRG8, V4L2_FIELD_NONE,
+		.bytesperline = 800,
+		.sizeimage = 800 * 600,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1
+	},
+	{1280,  960, V4L2_PIX_FMT_SGBRG8, V4L2_FIELD_NONE,
+		.bytesperline = 1280,
+		.sizeimage = 1280 * 960,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 2
+	},
+	{1600, 1200, V4L2_PIX_FMT_SGBRG8, V4L2_FIELD_NONE,
+		.bytesperline = 1600,
+		.sizeimage = 1600 * 1200,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 3
+	},
+};
+
+static struct v4l2_pix_format mi1320_mode[] = {
+	{ 640,  480, V4L2_PIX_FMT_SGBRG8, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0
+	},
+	{ 800,  600, V4L2_PIX_FMT_SGBRG8, V4L2_FIELD_NONE,
+		.bytesperline = 800,
+		.sizeimage = 800 * 600,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1
+	},
+	{1280,  960, V4L2_PIX_FMT_SGBRG8, V4L2_FIELD_NONE,
+		.bytesperline = 1280,
+		.sizeimage = 1280 * 960,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 2
+	},
+};
+
+static struct v4l2_pix_format ov9655_mode[] = {
+	{ 640,  480, V4L2_PIX_FMT_SGBRG8, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0
+	},
+	{1280,  960, V4L2_PIX_FMT_SGBRG8, V4L2_FIELD_NONE,
+		.bytesperline = 1280,
+		.sizeimage = 1280 * 960,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1
+	},
+};
+
+/*========================= sud-driver functions ===========================*/
+
+/* This function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam;
+	u16 vendor_id, product_id;
+
+	/* Get USB VendorID and ProductID */
+	vendor_id  = id->idVendor;
+	product_id = id->idProduct;
+
+	sd->nbRightUp = 1;
+	sd->nbIm = -1;
+
+	sd->sensor = 0xff;
+	if (strcmp(sensor, "MI1320") == 0)
+		sd->sensor = ID_MI1320;
+	else if (strcmp(sensor, "OV2640") == 0)
+		sd->sensor = ID_OV2640;
+	else if (strcmp(sensor, "OV9655") == 0)
+		sd->sensor = ID_OV9655;
+	else if (strcmp(sensor, "MI2020") == 0)
+		sd->sensor = ID_MI2020;
+
+	/* Get sensor and set the suitable init/start/../stop functions */
+	if (gl860_guess_sensor(gspca_dev, vendor_id, product_id) == -1)
+		return -1;
+
+	cam = &gspca_dev->cam;
+
+	switch (sd->sensor) {
+	case ID_MI1320:
+		gspca_dev->sd_desc = &sd_desc_mi1320;
+		cam->cam_mode = mi1320_mode;
+		cam->nmodes = ARRAY_SIZE(mi1320_mode);
+		dev_init_settings   = mi1320_init_settings;
+		break;
+
+	case ID_MI2020:
+		gspca_dev->sd_desc = &sd_desc_mi2020;
+		cam->cam_mode = mi2020_mode;
+		cam->nmodes = ARRAY_SIZE(mi2020_mode);
+		dev_init_settings   = mi2020_init_settings;
+		break;
+
+	case ID_OV2640:
+		gspca_dev->sd_desc = &sd_desc_ov2640;
+		cam->cam_mode = ov2640_mode;
+		cam->nmodes = ARRAY_SIZE(ov2640_mode);
+		dev_init_settings   = ov2640_init_settings;
+		break;
+
+	case ID_OV9655:
+		gspca_dev->sd_desc = &sd_desc_ov9655;
+		cam->cam_mode = ov9655_mode;
+		cam->nmodes = ARRAY_SIZE(ov9655_mode);
+		dev_init_settings   = ov9655_init_settings;
+		break;
+	}
+
+	dev_init_settings(gspca_dev);
+	if (AC50Hz != 0xff)
+		((struct sd *) gspca_dev)->vcur.AC50Hz = AC50Hz;
+
+	return 0;
+}
+
+/* This function is called at probe time after sd_config */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	return sd->dev_init_at_startup(gspca_dev);
+}
+
+/* This function is called before to choose the alt setting */
+static int sd_isoc_init(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	return sd->dev_configure_alt(gspca_dev);
+}
+
+/* This function is called to start the webcam */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	return sd->dev_init_pre_alt(gspca_dev);
+}
+
+/* This function is called to stop the webcam */
+static void sd_stop0(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (!sd->gspca_dev.present)
+		return;
+
+	return sd->dev_post_unset_alt(gspca_dev);
+}
+
+/* This function is called when an image is being received */
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data, int len)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	static s32 nSkipped;
+
+	s32 mode = (s32) gspca_dev->curr_mode;
+	s32 nToSkip =
+		sd->swapRB * (gspca_dev->cam.cam_mode[mode].bytesperline + 1);
+
+	/* Test only against 0202h, so endianness does not matter */
+	switch (*(s16 *) data) {
+	case 0x0202:		/* End of frame, start a new one */
+		gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
+		nSkipped = 0;
+		if (sd->nbIm >= 0 && sd->nbIm < 10)
+			sd->nbIm++;
+		gspca_frame_add(gspca_dev, FIRST_PACKET, NULL, 0);
+		break;
+
+	default:
+		data += 2;
+		len  -= 2;
+		if (nSkipped + len <= nToSkip)
+			nSkipped += len;
+		else {
+			if (nSkipped < nToSkip && nSkipped + len > nToSkip) {
+				data += nToSkip - nSkipped;
+				len  -= nToSkip - nSkipped;
+				nSkipped = nToSkip + 1;
+			}
+			gspca_frame_add(gspca_dev,
+				INTER_PACKET, data, len);
+		}
+		break;
+	}
+}
+
+/* This function is called when an image has been read */
+/* This function is used to monitor webcam orientation */
+static void sd_callback(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (!_OV9655_) {
+		u8 state;
+		u8 upsideDown;
+
+		/* Probe sensor orientation */
+		ctrl_in(gspca_dev, 0xc0, 2, 0x0000, 0x0000, 1, (void *)&state);
+
+		/* C8/40 means upside-down (looking backwards) */
+		/* D8/50 means right-up (looking onwards) */
+		upsideDown = (state == 0xc8 || state == 0x40);
+
+		if (upsideDown && sd->nbRightUp > -4) {
+			if (sd->nbRightUp > 0)
+				sd->nbRightUp = 0;
+			if (sd->nbRightUp == -3) {
+				sd->mirrorMask = 1;
+				sd->waitSet = 1;
+			}
+			sd->nbRightUp--;
+		}
+		if (!upsideDown && sd->nbRightUp < 4) {
+			if (sd->nbRightUp  < 0)
+				sd->nbRightUp = 0;
+			if (sd->nbRightUp == 3) {
+				sd->mirrorMask = 0;
+				sd->waitSet = 1;
+			}
+			sd->nbRightUp++;
+		}
+	}
+
+	if (sd->waitSet)
+		sd->dev_camera_settings(gspca_dev);
+}
+
+/*=================== USB driver structure initialisation ==================*/
+
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x05e3, 0x0503)},
+	{USB_DEVICE(0x05e3, 0xf191)},
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+static int sd_probe(struct usb_interface *intf,
+				const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id,
+			&sd_desc_mi1320, sizeof(struct sd), THIS_MODULE);
+}
+
+static void sd_disconnect(struct usb_interface *intf)
+{
+	gspca_disconnect(intf);
+}
+
+static struct usb_driver sd_driver = {
+	.name       = MODULE_NAME,
+	.id_table   = device_table,
+	.probe      = sd_probe,
+	.disconnect = sd_disconnect,
+#ifdef CONFIG_PM
+	.suspend    = gspca_suspend,
+	.resume     = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+/*====================== Init and Exit module functions ====================*/
+
+module_usb_driver(sd_driver);
+
+/*==========================================================================*/
+
+int gl860_RTx(struct gspca_dev *gspca_dev,
+		unsigned char pref, u32 req, u16 val, u16 index,
+		s32 len, void *pdata)
+{
+	struct usb_device *udev = gspca_dev->dev;
+	s32 r = 0;
+
+	if (pref == 0x40) { /* Send */
+		if (len > 0) {
+			memcpy(gspca_dev->usb_buf, pdata, len);
+			r = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+					req, pref, val, index,
+					gspca_dev->usb_buf,
+					len, 400 + 200 * (len > 1));
+		} else {
+			r = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+					req, pref, val, index, NULL, len, 400);
+		}
+	} else { /* Receive */
+		if (len > 0) {
+			r = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+					req, pref, val, index,
+					gspca_dev->usb_buf,
+					len, 400 + 200 * (len > 1));
+			memcpy(pdata, gspca_dev->usb_buf, len);
+		} else {
+			r = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+					req, pref, val, index, NULL, len, 400);
+		}
+	}
+
+	if (r < 0)
+		pr_err("ctrl transfer failed %4d [p%02x r%d v%04x i%04x len%d]\n",
+		       r, pref, req, val, index, len);
+	else if (len > 1 && r < len)
+		gspca_err(gspca_dev, "short ctrl transfer %d/%d\n", r, len);
+
+	msleep(1);
+
+	return r;
+}
+
+int fetch_validx(struct gspca_dev *gspca_dev, struct validx *tbl, int len)
+{
+	int n;
+
+	for (n = 0; n < len; n++) {
+		if (tbl[n].idx != 0xffff)
+			ctrl_out(gspca_dev, 0x40, 1, tbl[n].val,
+					tbl[n].idx, 0, NULL);
+		else if (tbl[n].val == 0xffff)
+			break;
+		else
+			msleep(tbl[n].val);
+	}
+	return n;
+}
+
+int keep_on_fetching_validx(struct gspca_dev *gspca_dev, struct validx *tbl,
+				int len, int n)
+{
+	while (++n < len) {
+		if (tbl[n].idx != 0xffff)
+			ctrl_out(gspca_dev, 0x40, 1, tbl[n].val, tbl[n].idx,
+					0, NULL);
+		else if (tbl[n].val == 0xffff)
+			break;
+		else
+			msleep(tbl[n].val);
+	}
+	return n;
+}
+
+void fetch_idxdata(struct gspca_dev *gspca_dev, struct idxdata *tbl, int len)
+{
+	int n;
+
+	for (n = 0; n < len; n++) {
+		if (memcmp(tbl[n].data, "\xff\xff\xff", 3) != 0)
+			ctrl_out(gspca_dev, 0x40, 3, 0x7a00, tbl[n].idx,
+					3, tbl[n].data);
+		else
+			msleep(tbl[n].idx);
+	}
+}
+
+static int gl860_guess_sensor(struct gspca_dev *gspca_dev,
+				u16 vendor_id, u16 product_id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 probe, nb26, nb96, nOV, ntry;
+
+	if (product_id == 0xf191)
+		sd->sensor = ID_MI1320;
+
+	if (sd->sensor == 0xff) {
+		ctrl_in(gspca_dev, 0xc0, 2, 0x0000, 0x0004, 1, &probe);
+		ctrl_in(gspca_dev, 0xc0, 2, 0x0000, 0x0004, 1, &probe);
+
+		ctrl_out(gspca_dev, 0x40, 1, 0x0000, 0x0000, 0, NULL);
+		msleep(3);
+		ctrl_out(gspca_dev, 0x40, 1, 0x0010, 0x0010, 0, NULL);
+		msleep(3);
+		ctrl_out(gspca_dev, 0x40, 1, 0x0008, 0x00c0, 0, NULL);
+		msleep(3);
+		ctrl_out(gspca_dev, 0x40, 1, 0x0001, 0x00c1, 0, NULL);
+		msleep(3);
+		ctrl_out(gspca_dev, 0x40, 1, 0x0001, 0x00c2, 0, NULL);
+		msleep(3);
+		ctrl_out(gspca_dev, 0x40, 1, 0x0020, 0x0006, 0, NULL);
+		msleep(3);
+		ctrl_out(gspca_dev, 0x40, 1, 0x006a, 0x000d, 0, NULL);
+		msleep(56);
+
+		gspca_dbg(gspca_dev, D_PROBE, "probing for sensor MI2020 or OVXXXX\n");
+		nOV = 0;
+		for (ntry = 0; ntry < 4; ntry++) {
+			ctrl_out(gspca_dev, 0x40, 1, 0x0040, 0x0000, 0, NULL);
+			msleep(3);
+			ctrl_out(gspca_dev, 0x40, 1, 0x0063, 0x0006, 0, NULL);
+			msleep(3);
+			ctrl_out(gspca_dev, 0x40, 1, 0x7a00, 0x8030, 0, NULL);
+			msleep(10);
+			ctrl_in(gspca_dev, 0xc0, 2, 0x7a00, 0x8030, 1, &probe);
+			gspca_dbg(gspca_dev, D_PROBE, "probe=0x%02x\n", probe);
+			if (probe == 0xff)
+				nOV++;
+		}
+
+		if (nOV) {
+			gspca_dbg(gspca_dev, D_PROBE, "0xff -> OVXXXX\n");
+			gspca_dbg(gspca_dev, D_PROBE, "probing for sensor OV2640 or OV9655");
+
+			nb26 = nb96 = 0;
+			for (ntry = 0; ntry < 4; ntry++) {
+				ctrl_out(gspca_dev, 0x40, 1, 0x0040, 0x0000,
+						0, NULL);
+				msleep(3);
+				ctrl_out(gspca_dev, 0x40, 1, 0x6000, 0x800a,
+						0, NULL);
+				msleep(10);
+
+				/* Wait for 26(OV2640) or 96(OV9655) */
+				ctrl_in(gspca_dev, 0xc0, 2, 0x6000, 0x800a,
+						1, &probe);
+
+				if (probe == 0x26 || probe == 0x40) {
+					gspca_dbg(gspca_dev, D_PROBE,
+						  "probe=0x%02x -> OV2640\n",
+						  probe);
+					sd->sensor = ID_OV2640;
+					nb26 += 4;
+					break;
+				}
+				if (probe == 0x96 || probe == 0x55) {
+					gspca_dbg(gspca_dev, D_PROBE,
+						  "probe=0x%02x -> OV9655\n",
+						  probe);
+					sd->sensor = ID_OV9655;
+					nb96 += 4;
+					break;
+				}
+				gspca_dbg(gspca_dev, D_PROBE, "probe=0x%02x\n",
+					  probe);
+				if (probe == 0x00)
+					nb26++;
+				if (probe == 0xff)
+					nb96++;
+				msleep(3);
+			}
+			if (nb26 < 4 && nb96 < 4)
+				return -1;
+		} else {
+			gspca_dbg(gspca_dev, D_PROBE, "Not any 0xff -> MI2020\n");
+			sd->sensor = ID_MI2020;
+		}
+	}
+
+	if (_MI1320_) {
+		gspca_dbg(gspca_dev, D_PROBE, "05e3:f191 sensor MI1320 (1.3M)\n");
+	} else if (_MI2020_) {
+		gspca_dbg(gspca_dev, D_PROBE, "05e3:0503 sensor MI2020 (2.0M)\n");
+	} else if (_OV9655_) {
+		gspca_dbg(gspca_dev, D_PROBE, "05e3:0503 sensor OV9655 (1.3M)\n");
+	} else if (_OV2640_) {
+		gspca_dbg(gspca_dev, D_PROBE, "05e3:0503 sensor OV2640 (2.0M)\n");
+	} else {
+		gspca_dbg(gspca_dev, D_PROBE, "***** Unknown sensor *****\n");
+		return -1;
+	}
+
+	return 0;
+}
diff --git a/drivers/media/usb/gspca/gl860/gl860.h b/drivers/media/usb/gspca/gl860/gl860.h
new file mode 100644
index 0000000..0330a02
--- /dev/null
+++ b/drivers/media/usb/gspca/gl860/gl860.h
@@ -0,0 +1,105 @@
+/* GSPCA subdrivers for Genesys Logic webcams with the GL860 chip
+ * Subdriver declarations
+ *
+ * 2009/10/14 Olivier LORIN <o.lorin@laposte.net>
+ *
+ * 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
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef GL860_DEV_H
+#define GL860_DEV_H
+
+#include "gspca.h"
+
+#define MODULE_NAME "gspca_gl860"
+#define DRIVER_VERSION "0.9d10"
+
+#define ctrl_in  gl860_RTx
+#define ctrl_out gl860_RTx
+
+#define ID_MI1320   1
+#define ID_OV2640   2
+#define ID_OV9655   4
+#define ID_MI2020   8
+
+#define _MI1320_  (((struct sd *) gspca_dev)->sensor == ID_MI1320)
+#define _MI2020_  (((struct sd *) gspca_dev)->sensor == ID_MI2020)
+#define _OV2640_  (((struct sd *) gspca_dev)->sensor == ID_OV2640)
+#define _OV9655_  (((struct sd *) gspca_dev)->sensor == ID_OV9655)
+
+#define IMAGE_640   0
+#define IMAGE_800   1
+#define IMAGE_1280  2
+#define IMAGE_1600  3
+
+struct sd_gl860 {
+	u16 backlight;
+	u16 brightness;
+	u16 sharpness;
+	u16 contrast;
+	u16 gamma;
+	u16 hue;
+	u16 saturation;
+	u16 whitebal;
+	u8  mirror;
+	u8  flip;
+	u8  AC50Hz;
+};
+
+/* Specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+
+	struct sd_gl860 vcur;
+	struct sd_gl860 vold;
+	struct sd_gl860 vmax;
+
+	int  (*dev_configure_alt)  (struct gspca_dev *);
+	int  (*dev_init_at_startup)(struct gspca_dev *);
+	int  (*dev_init_pre_alt)   (struct gspca_dev *);
+	void (*dev_post_unset_alt) (struct gspca_dev *);
+	int  (*dev_camera_settings)(struct gspca_dev *);
+
+	u8   swapRB;
+	u8   mirrorMask;
+	u8   sensor;
+	s32  nbIm;
+	s32  nbRightUp;
+	u8   waitSet;
+};
+
+struct validx {
+	u16 val;
+	u16 idx;
+};
+
+struct idxdata {
+	u8 idx;
+	u8 data[3];
+};
+
+int fetch_validx(struct gspca_dev *gspca_dev, struct validx *tbl, int len);
+int keep_on_fetching_validx(struct gspca_dev *gspca_dev, struct validx *tbl,
+				int len, int n);
+void fetch_idxdata(struct gspca_dev *gspca_dev, struct idxdata *tbl, int len);
+
+int gl860_RTx(struct gspca_dev *gspca_dev,
+			unsigned char pref, u32 req, u16 val, u16 index,
+			s32 len, void *pdata);
+
+void mi1320_init_settings(struct gspca_dev *);
+void ov2640_init_settings(struct gspca_dev *);
+void ov9655_init_settings(struct gspca_dev *);
+void mi2020_init_settings(struct gspca_dev *);
+
+#endif
diff --git a/drivers/media/usb/gspca/gspca.c b/drivers/media/usb/gspca/gspca.c
new file mode 100644
index 0000000..405a6a7
--- /dev/null
+++ b/drivers/media/usb/gspca/gspca.c
@@ -0,0 +1,1720 @@
+/*
+ * Main USB camera driver
+ *
+ * Copyright (C) 2008-2011 Jean-François Moine <http://moinejf.free.fr>
+ *
+ * Camera button input handling by Márton Németh
+ * Copyright (C) 2009-2010 Márton Németh <nm127@freemail.hu>
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define GSPCA_VERSION	"2.14.0"
+
+#include <linux/init.h>
+#include <linux/fs.h>
+#include <linux/vmalloc.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/string.h>
+#include <linux/pagemap.h>
+#include <linux/io.h>
+#include <asm/page.h>
+#include <linux/uaccess.h>
+#include <linux/ktime.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-fh.h>
+#include <media/v4l2-event.h>
+
+#include "gspca.h"
+
+#if IS_ENABLED(CONFIG_INPUT)
+#include <linux/input.h>
+#include <linux/usb/input.h>
+#endif
+
+/* global values */
+#define DEF_NURBS 3		/* default number of URBs */
+#if DEF_NURBS > MAX_NURBS
+#error "DEF_NURBS too big"
+#endif
+
+MODULE_AUTHOR("Jean-François Moine <http://moinejf.free.fr>");
+MODULE_DESCRIPTION("GSPCA USB Camera Driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(GSPCA_VERSION);
+
+int gspca_debug;
+EXPORT_SYMBOL(gspca_debug);
+
+static void PDEBUG_MODE(struct gspca_dev *gspca_dev, int debug, char *txt,
+			__u32 pixfmt, int w, int h)
+{
+	if ((pixfmt >> 24) >= '0' && (pixfmt >> 24) <= 'z') {
+		gspca_dbg(gspca_dev, debug, "%s %c%c%c%c %dx%d\n",
+			  txt,
+			  pixfmt & 0xff,
+			  (pixfmt >> 8) & 0xff,
+			  (pixfmt >> 16) & 0xff,
+			  pixfmt >> 24,
+			  w, h);
+	} else {
+		gspca_dbg(gspca_dev, debug, "%s 0x%08x %dx%d\n",
+			  txt,
+			  pixfmt,
+			  w, h);
+	}
+}
+
+/* specific memory types - !! should be different from V4L2_MEMORY_xxx */
+#define GSPCA_MEMORY_NO 0	/* V4L2_MEMORY_xxx starts from 1 */
+#define GSPCA_MEMORY_READ 7
+
+/*
+ * Input and interrupt endpoint handling functions
+ */
+#if IS_ENABLED(CONFIG_INPUT)
+static void int_irq(struct urb *urb)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *) urb->context;
+	int ret;
+
+	ret = urb->status;
+	switch (ret) {
+	case 0:
+		if (gspca_dev->sd_desc->int_pkt_scan(gspca_dev,
+		    urb->transfer_buffer, urb->actual_length) < 0) {
+			gspca_err(gspca_dev, "Unknown packet received\n");
+		}
+		break;
+
+	case -ENOENT:
+	case -ECONNRESET:
+	case -ENODEV:
+	case -ESHUTDOWN:
+		/* Stop is requested either by software or hardware is gone,
+		 * keep the ret value non-zero and don't resubmit later.
+		 */
+		break;
+
+	default:
+		gspca_err(gspca_dev, "URB error %i, resubmitting\n",
+			  urb->status);
+		urb->status = 0;
+		ret = 0;
+	}
+
+	if (ret == 0) {
+		ret = usb_submit_urb(urb, GFP_ATOMIC);
+		if (ret < 0)
+			pr_err("Resubmit URB failed with error %i\n", ret);
+	}
+}
+
+static int gspca_input_connect(struct gspca_dev *dev)
+{
+	struct input_dev *input_dev;
+	int err = 0;
+
+	dev->input_dev = NULL;
+	if (dev->sd_desc->int_pkt_scan || dev->sd_desc->other_input)  {
+		input_dev = input_allocate_device();
+		if (!input_dev)
+			return -ENOMEM;
+
+		usb_make_path(dev->dev, dev->phys, sizeof(dev->phys));
+		strlcat(dev->phys, "/input0", sizeof(dev->phys));
+
+		input_dev->name = dev->sd_desc->name;
+		input_dev->phys = dev->phys;
+
+		usb_to_input_id(dev->dev, &input_dev->id);
+
+		input_dev->evbit[0] = BIT_MASK(EV_KEY);
+		input_dev->keybit[BIT_WORD(KEY_CAMERA)] = BIT_MASK(KEY_CAMERA);
+		input_dev->dev.parent = &dev->dev->dev;
+
+		err = input_register_device(input_dev);
+		if (err) {
+			pr_err("Input device registration failed with error %i\n",
+			       err);
+			input_dev->dev.parent = NULL;
+			input_free_device(input_dev);
+		} else {
+			dev->input_dev = input_dev;
+		}
+	}
+
+	return err;
+}
+
+static int alloc_and_submit_int_urb(struct gspca_dev *gspca_dev,
+			  struct usb_endpoint_descriptor *ep)
+{
+	unsigned int buffer_len;
+	int interval;
+	struct urb *urb;
+	struct usb_device *dev;
+	void *buffer = NULL;
+	int ret = -EINVAL;
+
+	buffer_len = le16_to_cpu(ep->wMaxPacketSize);
+	interval = ep->bInterval;
+	gspca_dbg(gspca_dev, D_CONF, "found int in endpoint: 0x%x, buffer_len=%u, interval=%u\n",
+		  ep->bEndpointAddress, buffer_len, interval);
+
+	dev = gspca_dev->dev;
+
+	urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!urb) {
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	buffer = usb_alloc_coherent(dev, buffer_len,
+				GFP_KERNEL, &urb->transfer_dma);
+	if (!buffer) {
+		ret = -ENOMEM;
+		goto error_buffer;
+	}
+	usb_fill_int_urb(urb, dev,
+		usb_rcvintpipe(dev, ep->bEndpointAddress),
+		buffer, buffer_len,
+		int_irq, (void *)gspca_dev, interval);
+	urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+	ret = usb_submit_urb(urb, GFP_KERNEL);
+	if (ret < 0) {
+		gspca_err(gspca_dev, "submit int URB failed with error %i\n",
+			  ret);
+		goto error_submit;
+	}
+	gspca_dev->int_urb = urb;
+	return ret;
+
+error_submit:
+	usb_free_coherent(dev,
+			  urb->transfer_buffer_length,
+			  urb->transfer_buffer,
+			  urb->transfer_dma);
+error_buffer:
+	usb_free_urb(urb);
+error:
+	return ret;
+}
+
+static void gspca_input_create_urb(struct gspca_dev *gspca_dev)
+{
+	struct usb_interface *intf;
+	struct usb_host_interface *intf_desc;
+	struct usb_endpoint_descriptor *ep;
+	int i;
+
+	if (gspca_dev->sd_desc->int_pkt_scan)  {
+		intf = usb_ifnum_to_if(gspca_dev->dev, gspca_dev->iface);
+		intf_desc = intf->cur_altsetting;
+		for (i = 0; i < intf_desc->desc.bNumEndpoints; i++) {
+			ep = &intf_desc->endpoint[i].desc;
+			if (usb_endpoint_dir_in(ep) &&
+			    usb_endpoint_xfer_int(ep)) {
+
+				alloc_and_submit_int_urb(gspca_dev, ep);
+				break;
+			}
+		}
+	}
+}
+
+static void gspca_input_destroy_urb(struct gspca_dev *gspca_dev)
+{
+	struct urb *urb;
+
+	urb = gspca_dev->int_urb;
+	if (urb) {
+		gspca_dev->int_urb = NULL;
+		usb_kill_urb(urb);
+		usb_free_coherent(gspca_dev->dev,
+				  urb->transfer_buffer_length,
+				  urb->transfer_buffer,
+				  urb->transfer_dma);
+		usb_free_urb(urb);
+	}
+}
+#else
+static inline void gspca_input_destroy_urb(struct gspca_dev *gspca_dev)
+{
+}
+
+static inline void gspca_input_create_urb(struct gspca_dev *gspca_dev)
+{
+}
+
+static inline int gspca_input_connect(struct gspca_dev *dev)
+{
+	return 0;
+}
+#endif
+
+/*
+ * fill a video frame from an URB and resubmit
+ */
+static void fill_frame(struct gspca_dev *gspca_dev,
+			struct urb *urb)
+{
+	u8 *data;		/* address of data in the iso message */
+	int i, len, st;
+	cam_pkt_op pkt_scan;
+
+	if (urb->status != 0) {
+		if (urb->status == -ESHUTDOWN)
+			return;		/* disconnection */
+#ifdef CONFIG_PM
+		if (gspca_dev->frozen)
+			return;
+#endif
+		gspca_err(gspca_dev, "urb status: %d\n", urb->status);
+		urb->status = 0;
+		goto resubmit;
+	}
+	pkt_scan = gspca_dev->sd_desc->pkt_scan;
+	for (i = 0; i < urb->number_of_packets; i++) {
+		len = urb->iso_frame_desc[i].actual_length;
+
+		/* check the packet status and length */
+		st = urb->iso_frame_desc[i].status;
+		if (st) {
+			pr_err("ISOC data error: [%d] len=%d, status=%d\n",
+			       i, len, st);
+			gspca_dev->last_packet_type = DISCARD_PACKET;
+			continue;
+		}
+		if (len == 0) {
+			if (gspca_dev->empty_packet == 0)
+				gspca_dev->empty_packet = 1;
+			continue;
+		}
+
+		/* let the packet be analyzed by the subdriver */
+		gspca_dbg(gspca_dev, D_PACK, "packet [%d] o:%d l:%d\n",
+			  i, urb->iso_frame_desc[i].offset, len);
+		data = (u8 *) urb->transfer_buffer
+					+ urb->iso_frame_desc[i].offset;
+		pkt_scan(gspca_dev, data, len);
+	}
+
+resubmit:
+	/* resubmit the URB */
+	st = usb_submit_urb(urb, GFP_ATOMIC);
+	if (st < 0)
+		pr_err("usb_submit_urb() ret %d\n", st);
+}
+
+/*
+ * ISOC message interrupt from the USB device
+ *
+ * Analyse each packet and call the subdriver for copy to the frame buffer.
+ */
+static void isoc_irq(struct urb *urb)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *) urb->context;
+
+	gspca_dbg(gspca_dev, D_PACK, "isoc irq\n");
+	if (!vb2_start_streaming_called(&gspca_dev->queue))
+		return;
+	fill_frame(gspca_dev, urb);
+}
+
+/*
+ * bulk message interrupt from the USB device
+ */
+static void bulk_irq(struct urb *urb)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *) urb->context;
+	int st;
+
+	gspca_dbg(gspca_dev, D_PACK, "bulk irq\n");
+	if (!vb2_start_streaming_called(&gspca_dev->queue))
+		return;
+	switch (urb->status) {
+	case 0:
+		break;
+	case -ESHUTDOWN:
+		return;		/* disconnection */
+	default:
+#ifdef CONFIG_PM
+		if (gspca_dev->frozen)
+			return;
+#endif
+		gspca_err(gspca_dev, "urb status: %d\n", urb->status);
+		urb->status = 0;
+		goto resubmit;
+	}
+
+	gspca_dbg(gspca_dev, D_PACK, "packet l:%d\n", urb->actual_length);
+	gspca_dev->sd_desc->pkt_scan(gspca_dev,
+				urb->transfer_buffer,
+				urb->actual_length);
+
+resubmit:
+	/* resubmit the URB */
+	if (gspca_dev->cam.bulk_nurbs != 0) {
+		st = usb_submit_urb(urb, GFP_ATOMIC);
+		if (st < 0)
+			pr_err("usb_submit_urb() ret %d\n", st);
+	}
+}
+
+/*
+ * add data to the current frame
+ *
+ * This function is called by the subdrivers at interrupt level.
+ *
+ * To build a frame, these ones must add
+ *	- one FIRST_PACKET
+ *	- 0 or many INTER_PACKETs
+ *	- one LAST_PACKET
+ * DISCARD_PACKET invalidates the whole frame.
+ */
+void gspca_frame_add(struct gspca_dev *gspca_dev,
+			enum gspca_packet_type packet_type,
+			const u8 *data,
+			int len)
+{
+	struct gspca_buffer *buf;
+	unsigned long flags;
+
+	gspca_dbg(gspca_dev, D_PACK, "add t:%d l:%d\n",	packet_type, len);
+
+	spin_lock_irqsave(&gspca_dev->qlock, flags);
+	buf = list_first_entry_or_null(&gspca_dev->buf_list,
+				       typeof(*buf), list);
+	spin_unlock_irqrestore(&gspca_dev->qlock, flags);
+
+	if (packet_type == FIRST_PACKET) {
+		/* if there is no queued buffer, discard the whole frame */
+		if (!buf) {
+			gspca_dev->last_packet_type = DISCARD_PACKET;
+			gspca_dev->sequence++;
+			return;
+		}
+		gspca_dev->image = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
+		gspca_dev->image_len = 0;
+	} else {
+		switch (gspca_dev->last_packet_type) {
+		case DISCARD_PACKET:
+			if (packet_type == LAST_PACKET) {
+				gspca_dev->last_packet_type = packet_type;
+				gspca_dev->image = NULL;
+				gspca_dev->image_len = 0;
+			}
+			return;
+		case LAST_PACKET:
+			return;
+		}
+	}
+
+	/* append the packet to the frame buffer */
+	if (len > 0) {
+		if (gspca_dev->image_len + len > PAGE_ALIGN(gspca_dev->pixfmt.sizeimage)) {
+			gspca_err(gspca_dev, "frame overflow %d > %d\n",
+				  gspca_dev->image_len + len,
+				  PAGE_ALIGN(gspca_dev->pixfmt.sizeimage));
+			packet_type = DISCARD_PACKET;
+		} else {
+/* !! image is NULL only when last pkt is LAST or DISCARD
+			if (gspca_dev->image == NULL) {
+				pr_err("gspca_frame_add() image == NULL\n");
+				return;
+			}
+ */
+			memcpy(gspca_dev->image + gspca_dev->image_len,
+				data, len);
+			gspca_dev->image_len += len;
+		}
+	}
+	gspca_dev->last_packet_type = packet_type;
+
+	/* if last packet, invalidate packet concatenation until
+	 * next first packet, wake up the application and advance
+	 * in the queue */
+	if (packet_type == LAST_PACKET) {
+		spin_lock_irqsave(&gspca_dev->qlock, flags);
+		list_del(&buf->list);
+		spin_unlock_irqrestore(&gspca_dev->qlock, flags);
+		buf->vb.vb2_buf.timestamp = ktime_get_ns();
+		vb2_set_plane_payload(&buf->vb.vb2_buf, 0,
+				      gspca_dev->image_len);
+		buf->vb.sequence = gspca_dev->sequence++;
+		buf->vb.field = V4L2_FIELD_NONE;
+		gspca_dbg(gspca_dev, D_FRAM, "frame complete len:%d\n",
+			  gspca_dev->image_len);
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
+		gspca_dev->image = NULL;
+		gspca_dev->image_len = 0;
+	}
+}
+EXPORT_SYMBOL(gspca_frame_add);
+
+static void destroy_urbs(struct gspca_dev *gspca_dev)
+{
+	struct urb *urb;
+	unsigned int i;
+
+	gspca_dbg(gspca_dev, D_STREAM, "kill transfer\n");
+
+	/* Killing all URBs guarantee that no URB completion
+	 * handler is running. Therefore, there shouldn't
+	 * be anyone trying to access gspca_dev->urb[i]
+	 */
+	for (i = 0; i < MAX_NURBS; i++)
+		usb_kill_urb(gspca_dev->urb[i]);
+
+	gspca_dbg(gspca_dev, D_STREAM, "releasing urbs\n");
+	for (i = 0; i < MAX_NURBS; i++) {
+		urb = gspca_dev->urb[i];
+		if (!urb)
+			continue;
+		gspca_dev->urb[i] = NULL;
+		usb_free_coherent(gspca_dev->dev,
+				  urb->transfer_buffer_length,
+				  urb->transfer_buffer,
+				  urb->transfer_dma);
+		usb_free_urb(urb);
+	}
+}
+
+static int gspca_set_alt0(struct gspca_dev *gspca_dev)
+{
+	int ret;
+
+	if (gspca_dev->alt == 0)
+		return 0;
+	ret = usb_set_interface(gspca_dev->dev, gspca_dev->iface, 0);
+	if (ret < 0)
+		pr_err("set alt 0 err %d\n", ret);
+	return ret;
+}
+
+/*
+ * look for an input transfer endpoint in an alternate setting.
+ *
+ * If xfer_ep is invalid, return the first valid ep found, otherwise
+ * look for exactly the ep with address equal to xfer_ep.
+ */
+static struct usb_host_endpoint *alt_xfer(struct usb_host_interface *alt,
+					  int xfer, int xfer_ep)
+{
+	struct usb_host_endpoint *ep;
+	int i, attr;
+
+	for (i = 0; i < alt->desc.bNumEndpoints; i++) {
+		ep = &alt->endpoint[i];
+		attr = ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK;
+		if (attr == xfer
+		    && ep->desc.wMaxPacketSize != 0
+		    && usb_endpoint_dir_in(&ep->desc)
+		    && (xfer_ep < 0 || ep->desc.bEndpointAddress == xfer_ep))
+			return ep;
+	}
+	return NULL;
+}
+
+/* compute the minimum bandwidth for the current transfer */
+static u32 which_bandwidth(struct gspca_dev *gspca_dev)
+{
+	u32 bandwidth;
+
+	/* get the (max) image size */
+	bandwidth = gspca_dev->pixfmt.sizeimage;
+
+	/* if the image is compressed, estimate its mean size */
+	if (!gspca_dev->cam.needs_full_bandwidth &&
+	    bandwidth < gspca_dev->pixfmt.width *
+				gspca_dev->pixfmt.height)
+		bandwidth = bandwidth * 3 / 8;	/* 0.375 */
+
+	/* estimate the frame rate */
+	if (gspca_dev->sd_desc->get_streamparm) {
+		struct v4l2_streamparm parm;
+
+		gspca_dev->sd_desc->get_streamparm(gspca_dev, &parm);
+		bandwidth *= parm.parm.capture.timeperframe.denominator;
+		bandwidth /= parm.parm.capture.timeperframe.numerator;
+	} else {
+
+		/* don't hope more than 15 fps with USB 1.1 and
+		 * image resolution >= 640x480 */
+		if (gspca_dev->pixfmt.width >= 640
+		 && gspca_dev->dev->speed == USB_SPEED_FULL)
+			bandwidth *= 15;		/* 15 fps */
+		else
+			bandwidth *= 30;		/* 30 fps */
+	}
+
+	gspca_dbg(gspca_dev, D_STREAM, "min bandwidth: %d\n", bandwidth);
+	return bandwidth;
+}
+
+/* endpoint table */
+#define MAX_ALT 16
+struct ep_tb_s {
+	u32 alt;
+	u32 bandwidth;
+};
+
+/*
+ * build the table of the endpoints
+ * and compute the minimum bandwidth for the image transfer
+ */
+static int build_isoc_ep_tb(struct gspca_dev *gspca_dev,
+			struct usb_interface *intf,
+			struct ep_tb_s *ep_tb)
+{
+	struct usb_host_endpoint *ep;
+	int i, j, nbalt, psize, found;
+	u32 bandwidth, last_bw;
+
+	nbalt = intf->num_altsetting;
+	if (nbalt > MAX_ALT)
+		nbalt = MAX_ALT;	/* fixme: should warn */
+
+	/* build the endpoint table */
+	i = 0;
+	last_bw = 0;
+	for (;;) {
+		ep_tb->bandwidth = 2000 * 2000 * 120;
+		found = 0;
+		for (j = 0; j < nbalt; j++) {
+			ep = alt_xfer(&intf->altsetting[j],
+				      USB_ENDPOINT_XFER_ISOC,
+				      gspca_dev->xfer_ep);
+			if (ep == NULL)
+				continue;
+			if (ep->desc.bInterval == 0) {
+				pr_err("alt %d iso endp with 0 interval\n", j);
+				continue;
+			}
+			psize = le16_to_cpu(ep->desc.wMaxPacketSize);
+			psize = (psize & 0x07ff) * (1 + ((psize >> 11) & 3));
+			bandwidth = psize * 1000;
+			if (gspca_dev->dev->speed == USB_SPEED_HIGH
+			 || gspca_dev->dev->speed >= USB_SPEED_SUPER)
+				bandwidth *= 8;
+			bandwidth /= 1 << (ep->desc.bInterval - 1);
+			if (bandwidth <= last_bw)
+				continue;
+			if (bandwidth < ep_tb->bandwidth) {
+				ep_tb->bandwidth = bandwidth;
+				ep_tb->alt = j;
+				found = 1;
+			}
+		}
+		if (!found)
+			break;
+		gspca_dbg(gspca_dev, D_STREAM, "alt %d bandwidth %d\n",
+			  ep_tb->alt, ep_tb->bandwidth);
+		last_bw = ep_tb->bandwidth;
+		i++;
+		ep_tb++;
+	}
+
+	/*
+	 * If the camera:
+	 * has a usb audio class interface (a built in usb mic); and
+	 * is a usb 1 full speed device; and
+	 * uses the max full speed iso bandwidth; and
+	 * and has more than 1 alt setting
+	 * then skip the highest alt setting to spare bandwidth for the mic
+	 */
+	if (gspca_dev->audio &&
+			gspca_dev->dev->speed == USB_SPEED_FULL &&
+			last_bw >= 1000000 &&
+			i > 1) {
+		gspca_dbg(gspca_dev, D_STREAM, "dev has usb audio, skipping highest alt\n");
+		i--;
+		ep_tb--;
+	}
+
+	/* get the requested bandwidth and start at the highest atlsetting */
+	bandwidth = which_bandwidth(gspca_dev);
+	ep_tb--;
+	while (i > 1) {
+		ep_tb--;
+		if (ep_tb->bandwidth < bandwidth)
+			break;
+		i--;
+	}
+	return i;
+}
+
+/*
+ * create the URBs for image transfer
+ */
+static int create_urbs(struct gspca_dev *gspca_dev,
+			struct usb_host_endpoint *ep)
+{
+	struct urb *urb;
+	int n, nurbs, i, psize, npkt, bsize;
+
+	/* calculate the packet size and the number of packets */
+	psize = le16_to_cpu(ep->desc.wMaxPacketSize);
+
+	if (!gspca_dev->cam.bulk) {		/* isoc */
+
+		/* See paragraph 5.9 / table 5-11 of the usb 2.0 spec. */
+		if (gspca_dev->pkt_size == 0)
+			psize = (psize & 0x07ff) * (1 + ((psize >> 11) & 3));
+		else
+			psize = gspca_dev->pkt_size;
+		npkt = gspca_dev->cam.npkt;
+		if (npkt == 0)
+			npkt = 32;		/* default value */
+		bsize = psize * npkt;
+		gspca_dbg(gspca_dev, D_STREAM,
+			  "isoc %d pkts size %d = bsize:%d\n",
+			  npkt, psize, bsize);
+		nurbs = DEF_NURBS;
+	} else {				/* bulk */
+		npkt = 0;
+		bsize = gspca_dev->cam.bulk_size;
+		if (bsize == 0)
+			bsize = psize;
+		gspca_dbg(gspca_dev, D_STREAM, "bulk bsize:%d\n", bsize);
+		if (gspca_dev->cam.bulk_nurbs != 0)
+			nurbs = gspca_dev->cam.bulk_nurbs;
+		else
+			nurbs = 1;
+	}
+
+	for (n = 0; n < nurbs; n++) {
+		urb = usb_alloc_urb(npkt, GFP_KERNEL);
+		if (!urb)
+			return -ENOMEM;
+		gspca_dev->urb[n] = urb;
+		urb->transfer_buffer = usb_alloc_coherent(gspca_dev->dev,
+						bsize,
+						GFP_KERNEL,
+						&urb->transfer_dma);
+
+		if (urb->transfer_buffer == NULL) {
+			pr_err("usb_alloc_coherent failed\n");
+			return -ENOMEM;
+		}
+		urb->dev = gspca_dev->dev;
+		urb->context = gspca_dev;
+		urb->transfer_buffer_length = bsize;
+		if (npkt != 0) {		/* ISOC */
+			urb->pipe = usb_rcvisocpipe(gspca_dev->dev,
+						    ep->desc.bEndpointAddress);
+			urb->transfer_flags = URB_ISO_ASAP
+					| URB_NO_TRANSFER_DMA_MAP;
+			urb->interval = 1 << (ep->desc.bInterval - 1);
+			urb->complete = isoc_irq;
+			urb->number_of_packets = npkt;
+			for (i = 0; i < npkt; i++) {
+				urb->iso_frame_desc[i].length = psize;
+				urb->iso_frame_desc[i].offset = psize * i;
+			}
+		} else {		/* bulk */
+			urb->pipe = usb_rcvbulkpipe(gspca_dev->dev,
+						ep->desc.bEndpointAddress);
+			urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+			urb->complete = bulk_irq;
+		}
+	}
+	return 0;
+}
+
+/* Note: both the queue and the usb locks should be held when calling this */
+static void gspca_stream_off(struct gspca_dev *gspca_dev)
+{
+	gspca_dev->streaming = false;
+	gspca_dev->usb_err = 0;
+	if (gspca_dev->sd_desc->stopN)
+		gspca_dev->sd_desc->stopN(gspca_dev);
+	destroy_urbs(gspca_dev);
+	gspca_input_destroy_urb(gspca_dev);
+	gspca_set_alt0(gspca_dev);
+	if (gspca_dev->present)
+		gspca_input_create_urb(gspca_dev);
+	if (gspca_dev->sd_desc->stop0)
+		gspca_dev->sd_desc->stop0(gspca_dev);
+	gspca_dbg(gspca_dev, D_STREAM, "stream off OK\n");
+}
+
+/*
+ * start the USB transfer
+ */
+static int gspca_init_transfer(struct gspca_dev *gspca_dev)
+{
+	struct usb_interface *intf;
+	struct usb_host_endpoint *ep;
+	struct urb *urb;
+	struct ep_tb_s ep_tb[MAX_ALT];
+	int n, ret, xfer, alt, alt_idx;
+
+	/* reset the streaming variables */
+	gspca_dev->image = NULL;
+	gspca_dev->image_len = 0;
+	gspca_dev->last_packet_type = DISCARD_PACKET;
+
+	gspca_dev->usb_err = 0;
+
+	/* do the specific subdriver stuff before endpoint selection */
+	intf = usb_ifnum_to_if(gspca_dev->dev, gspca_dev->iface);
+	gspca_dev->alt = gspca_dev->cam.bulk ? intf->num_altsetting : 0;
+	if (gspca_dev->sd_desc->isoc_init) {
+		ret = gspca_dev->sd_desc->isoc_init(gspca_dev);
+		if (ret < 0)
+			return ret;
+	}
+	xfer = gspca_dev->cam.bulk ? USB_ENDPOINT_XFER_BULK
+				   : USB_ENDPOINT_XFER_ISOC;
+
+	/* if bulk or the subdriver forced an altsetting, get the endpoint */
+	if (gspca_dev->alt != 0) {
+		gspca_dev->alt--;	/* (previous version compatibility) */
+		ep = alt_xfer(&intf->altsetting[gspca_dev->alt], xfer,
+			      gspca_dev->xfer_ep);
+		if (ep == NULL) {
+			pr_err("bad altsetting %d\n", gspca_dev->alt);
+			return -EIO;
+		}
+		ep_tb[0].alt = gspca_dev->alt;
+		alt_idx = 1;
+	} else {
+		/* else, compute the minimum bandwidth
+		 * and build the endpoint table */
+		alt_idx = build_isoc_ep_tb(gspca_dev, intf, ep_tb);
+		if (alt_idx <= 0) {
+			pr_err("no transfer endpoint found\n");
+			return -EIO;
+		}
+	}
+
+	/* set the highest alternate setting and
+	 * loop until urb submit succeeds */
+	gspca_input_destroy_urb(gspca_dev);
+
+	gspca_dev->alt = ep_tb[--alt_idx].alt;
+	alt = -1;
+	for (;;) {
+		if (alt != gspca_dev->alt) {
+			alt = gspca_dev->alt;
+			if (intf->num_altsetting > 1) {
+				ret = usb_set_interface(gspca_dev->dev,
+							gspca_dev->iface,
+							alt);
+				if (ret < 0) {
+					if (ret == -ENOSPC)
+						goto retry; /*fixme: ugly*/
+					pr_err("set alt %d err %d\n", alt, ret);
+					goto out;
+				}
+			}
+		}
+		if (!gspca_dev->cam.no_urb_create) {
+			gspca_dbg(gspca_dev, D_STREAM, "init transfer alt %d\n",
+				  alt);
+			ret = create_urbs(gspca_dev,
+				alt_xfer(&intf->altsetting[alt], xfer,
+					 gspca_dev->xfer_ep));
+			if (ret < 0) {
+				destroy_urbs(gspca_dev);
+				goto out;
+			}
+		}
+
+		/* clear the bulk endpoint */
+		if (gspca_dev->cam.bulk)
+			usb_clear_halt(gspca_dev->dev,
+					gspca_dev->urb[0]->pipe);
+
+		/* start the cam */
+		ret = gspca_dev->sd_desc->start(gspca_dev);
+		if (ret < 0) {
+			destroy_urbs(gspca_dev);
+			goto out;
+		}
+		v4l2_ctrl_handler_setup(gspca_dev->vdev.ctrl_handler);
+		gspca_dev->streaming = true;
+
+		/* some bulk transfers are started by the subdriver */
+		if (gspca_dev->cam.bulk && gspca_dev->cam.bulk_nurbs == 0)
+			break;
+
+		/* submit the URBs */
+		for (n = 0; n < MAX_NURBS; n++) {
+			urb = gspca_dev->urb[n];
+			if (urb == NULL)
+				break;
+			ret = usb_submit_urb(urb, GFP_KERNEL);
+			if (ret < 0)
+				break;
+		}
+		if (ret >= 0)
+			break;			/* transfer is started */
+
+		/* something when wrong
+		 * stop the webcam and free the transfer resources */
+		gspca_stream_off(gspca_dev);
+		if (ret != -ENOSPC) {
+			pr_err("usb_submit_urb alt %d err %d\n",
+			       gspca_dev->alt, ret);
+			goto out;
+		}
+
+		/* the bandwidth is not wide enough
+		 * negotiate or try a lower alternate setting */
+retry:
+		gspca_err(gspca_dev, "alt %d - bandwidth not wide enough, trying again\n",
+			  alt);
+		msleep(20);	/* wait for kill complete */
+		if (gspca_dev->sd_desc->isoc_nego) {
+			ret = gspca_dev->sd_desc->isoc_nego(gspca_dev);
+			if (ret < 0)
+				goto out;
+		} else {
+			if (alt_idx <= 0) {
+				pr_err("no transfer endpoint found\n");
+				ret = -EIO;
+				goto out;
+			}
+			gspca_dev->alt = ep_tb[--alt_idx].alt;
+		}
+	}
+out:
+	gspca_input_create_urb(gspca_dev);
+	return ret;
+}
+
+static void gspca_set_default_mode(struct gspca_dev *gspca_dev)
+{
+	int i;
+
+	i = gspca_dev->cam.nmodes - 1;	/* take the highest mode */
+	gspca_dev->curr_mode = i;
+	gspca_dev->pixfmt = gspca_dev->cam.cam_mode[i];
+
+	/* does nothing if ctrl_handler == NULL */
+	v4l2_ctrl_handler_setup(gspca_dev->vdev.ctrl_handler);
+}
+
+static int wxh_to_mode(struct gspca_dev *gspca_dev,
+			int width, int height)
+{
+	int i;
+
+	for (i = 0; i < gspca_dev->cam.nmodes; i++) {
+		if (width == gspca_dev->cam.cam_mode[i].width
+		    && height == gspca_dev->cam.cam_mode[i].height)
+			return i;
+	}
+	return -EINVAL;
+}
+
+static int wxh_to_nearest_mode(struct gspca_dev *gspca_dev,
+			int width, int height)
+{
+	int i;
+
+	for (i = gspca_dev->cam.nmodes; --i > 0; ) {
+		if (width >= gspca_dev->cam.cam_mode[i].width
+		    && height >= gspca_dev->cam.cam_mode[i].height)
+			break;
+	}
+	return i;
+}
+
+/*
+ * search a mode with the right pixel format
+ */
+static int gspca_get_mode(struct gspca_dev *gspca_dev,
+			int mode,
+			int pixfmt)
+{
+	int modeU, modeD;
+
+	modeU = modeD = mode;
+	while ((modeU < gspca_dev->cam.nmodes) || modeD >= 0) {
+		if (--modeD >= 0) {
+			if (gspca_dev->cam.cam_mode[modeD].pixelformat
+								== pixfmt)
+				return modeD;
+		}
+		if (++modeU < gspca_dev->cam.nmodes) {
+			if (gspca_dev->cam.cam_mode[modeU].pixelformat
+								== pixfmt)
+				return modeU;
+		}
+	}
+	return -EINVAL;
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int vidioc_g_chip_info(struct file *file, void *priv,
+				struct v4l2_dbg_chip_info *chip)
+{
+	struct gspca_dev *gspca_dev = video_drvdata(file);
+
+	gspca_dev->usb_err = 0;
+	if (gspca_dev->sd_desc->get_chip_info)
+		return gspca_dev->sd_desc->get_chip_info(gspca_dev, chip);
+	return chip->match.addr ? -EINVAL : 0;
+}
+
+static int vidioc_g_register(struct file *file, void *priv,
+		struct v4l2_dbg_register *reg)
+{
+	struct gspca_dev *gspca_dev = video_drvdata(file);
+
+	gspca_dev->usb_err = 0;
+	return gspca_dev->sd_desc->get_register(gspca_dev, reg);
+}
+
+static int vidioc_s_register(struct file *file, void *priv,
+		const struct v4l2_dbg_register *reg)
+{
+	struct gspca_dev *gspca_dev = video_drvdata(file);
+
+	gspca_dev->usb_err = 0;
+	return gspca_dev->sd_desc->set_register(gspca_dev, reg);
+}
+#endif
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
+				struct v4l2_fmtdesc *fmtdesc)
+{
+	struct gspca_dev *gspca_dev = video_drvdata(file);
+	int i, j, index;
+	__u32 fmt_tb[8];
+
+	/* give an index to each format */
+	index = 0;
+	for (i = gspca_dev->cam.nmodes; --i >= 0; ) {
+		fmt_tb[index] = gspca_dev->cam.cam_mode[i].pixelformat;
+		j = 0;
+		for (;;) {
+			if (fmt_tb[j] == fmt_tb[index])
+				break;
+			j++;
+		}
+		if (j == index) {
+			if (fmtdesc->index == index)
+				break;		/* new format */
+			index++;
+			if (index >= ARRAY_SIZE(fmt_tb))
+				return -EINVAL;
+		}
+	}
+	if (i < 0)
+		return -EINVAL;		/* no more format */
+
+	fmtdesc->pixelformat = fmt_tb[index];
+	if (gspca_dev->cam.cam_mode[i].sizeimage <
+			gspca_dev->cam.cam_mode[i].width *
+				gspca_dev->cam.cam_mode[i].height)
+		fmtdesc->flags = V4L2_FMT_FLAG_COMPRESSED;
+	fmtdesc->description[0] = fmtdesc->pixelformat & 0xff;
+	fmtdesc->description[1] = (fmtdesc->pixelformat >> 8) & 0xff;
+	fmtdesc->description[2] = (fmtdesc->pixelformat >> 16) & 0xff;
+	fmtdesc->description[3] = fmtdesc->pixelformat >> 24;
+	fmtdesc->description[4] = '\0';
+	return 0;
+}
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+			    struct v4l2_format *fmt)
+{
+	struct gspca_dev *gspca_dev = video_drvdata(file);
+
+	fmt->fmt.pix = gspca_dev->pixfmt;
+	/* some drivers use priv internally, zero it before giving it back to
+	   the core */
+	fmt->fmt.pix.priv = 0;
+	return 0;
+}
+
+static int try_fmt_vid_cap(struct gspca_dev *gspca_dev,
+			struct v4l2_format *fmt)
+{
+	int w, h, mode, mode2;
+
+	w = fmt->fmt.pix.width;
+	h = fmt->fmt.pix.height;
+
+	PDEBUG_MODE(gspca_dev, D_CONF, "try fmt cap",
+		    fmt->fmt.pix.pixelformat, w, h);
+
+	/* search the nearest mode for width and height */
+	mode = wxh_to_nearest_mode(gspca_dev, w, h);
+
+	/* OK if right palette */
+	if (gspca_dev->cam.cam_mode[mode].pixelformat
+						!= fmt->fmt.pix.pixelformat) {
+
+		/* else, search the closest mode with the same pixel format */
+		mode2 = gspca_get_mode(gspca_dev, mode,
+					fmt->fmt.pix.pixelformat);
+		if (mode2 >= 0)
+			mode = mode2;
+	}
+	fmt->fmt.pix = gspca_dev->cam.cam_mode[mode];
+	if (gspca_dev->sd_desc->try_fmt) {
+		/* pass original resolution to subdriver try_fmt */
+		fmt->fmt.pix.width = w;
+		fmt->fmt.pix.height = h;
+		gspca_dev->sd_desc->try_fmt(gspca_dev, fmt);
+	}
+	/* some drivers use priv internally, zero it before giving it back to
+	   the core */
+	fmt->fmt.pix.priv = 0;
+	return mode;			/* used when s_fmt */
+}
+
+static int vidioc_try_fmt_vid_cap(struct file *file,
+			      void *priv,
+			      struct v4l2_format *fmt)
+{
+	struct gspca_dev *gspca_dev = video_drvdata(file);
+
+	if (try_fmt_vid_cap(gspca_dev, fmt) < 0)
+		return -EINVAL;
+	return 0;
+}
+
+static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+			    struct v4l2_format *fmt)
+{
+	struct gspca_dev *gspca_dev = video_drvdata(file);
+	int mode;
+
+	if (vb2_is_busy(&gspca_dev->queue))
+		return -EBUSY;
+
+	mode = try_fmt_vid_cap(gspca_dev, fmt);
+	if (mode < 0)
+		return -EINVAL;
+
+	gspca_dev->curr_mode = mode;
+	if (gspca_dev->sd_desc->try_fmt)
+		/* subdriver try_fmt can modify format parameters */
+		gspca_dev->pixfmt = fmt->fmt.pix;
+	else
+		gspca_dev->pixfmt = gspca_dev->cam.cam_mode[mode];
+	return 0;
+}
+
+static int vidioc_enum_framesizes(struct file *file, void *priv,
+				  struct v4l2_frmsizeenum *fsize)
+{
+	struct gspca_dev *gspca_dev = video_drvdata(file);
+	int i;
+	__u32 index = 0;
+
+	if (gspca_dev->sd_desc->enum_framesizes)
+		return gspca_dev->sd_desc->enum_framesizes(gspca_dev, fsize);
+
+	for (i = 0; i < gspca_dev->cam.nmodes; i++) {
+		if (fsize->pixel_format !=
+				gspca_dev->cam.cam_mode[i].pixelformat)
+			continue;
+
+		if (fsize->index == index) {
+			fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+			fsize->discrete.width =
+				gspca_dev->cam.cam_mode[i].width;
+			fsize->discrete.height =
+				gspca_dev->cam.cam_mode[i].height;
+			return 0;
+		}
+		index++;
+	}
+
+	return -EINVAL;
+}
+
+static int vidioc_enum_frameintervals(struct file *filp, void *priv,
+				      struct v4l2_frmivalenum *fival)
+{
+	struct gspca_dev *gspca_dev = video_drvdata(filp);
+	int mode;
+	__u32 i;
+
+	mode = wxh_to_mode(gspca_dev, fival->width, fival->height);
+	if (mode < 0)
+		return -EINVAL;
+
+	if (gspca_dev->cam.mode_framerates == NULL ||
+			gspca_dev->cam.mode_framerates[mode].nrates == 0)
+		return -EINVAL;
+
+	if (fival->pixel_format !=
+			gspca_dev->cam.cam_mode[mode].pixelformat)
+		return -EINVAL;
+
+	for (i = 0; i < gspca_dev->cam.mode_framerates[mode].nrates; i++) {
+		if (fival->index == i) {
+			fival->type = V4L2_FRMIVAL_TYPE_DISCRETE;
+			fival->discrete.numerator = 1;
+			fival->discrete.denominator =
+				gspca_dev->cam.mode_framerates[mode].rates[i];
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static void gspca_release(struct v4l2_device *v4l2_device)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(v4l2_device, struct gspca_dev, v4l2_dev);
+
+	v4l2_ctrl_handler_free(gspca_dev->vdev.ctrl_handler);
+	v4l2_device_unregister(&gspca_dev->v4l2_dev);
+	kfree(gspca_dev->usb_buf);
+	kfree(gspca_dev);
+}
+
+static int vidioc_querycap(struct file *file, void  *priv,
+			   struct v4l2_capability *cap)
+{
+	struct gspca_dev *gspca_dev = video_drvdata(file);
+
+	strlcpy((char *) cap->driver, gspca_dev->sd_desc->name,
+			sizeof cap->driver);
+	if (gspca_dev->dev->product != NULL) {
+		strlcpy((char *) cap->card, gspca_dev->dev->product,
+			sizeof cap->card);
+	} else {
+		snprintf((char *) cap->card, sizeof cap->card,
+			"USB Camera (%04x:%04x)",
+			le16_to_cpu(gspca_dev->dev->descriptor.idVendor),
+			le16_to_cpu(gspca_dev->dev->descriptor.idProduct));
+	}
+	usb_make_path(gspca_dev->dev, (char *) cap->bus_info,
+			sizeof(cap->bus_info));
+	cap->device_caps = V4L2_CAP_VIDEO_CAPTURE
+			  | V4L2_CAP_STREAMING
+			  | V4L2_CAP_READWRITE;
+	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+	return 0;
+}
+
+static int vidioc_enum_input(struct file *file, void *priv,
+				struct v4l2_input *input)
+{
+	struct gspca_dev *gspca_dev = video_drvdata(file);
+
+	if (input->index != 0)
+		return -EINVAL;
+	input->type = V4L2_INPUT_TYPE_CAMERA;
+	input->status = gspca_dev->cam.input_flags;
+	strlcpy(input->name, gspca_dev->sd_desc->name,
+		sizeof input->name);
+	return 0;
+}
+
+static int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
+{
+	*i = 0;
+	return 0;
+}
+
+static int vidioc_s_input(struct file *file, void *priv, unsigned int i)
+{
+	if (i > 0)
+		return -EINVAL;
+	return 0;
+}
+
+static int vidioc_g_jpegcomp(struct file *file, void *priv,
+			struct v4l2_jpegcompression *jpegcomp)
+{
+	struct gspca_dev *gspca_dev = video_drvdata(file);
+
+	gspca_dev->usb_err = 0;
+	return gspca_dev->sd_desc->get_jcomp(gspca_dev, jpegcomp);
+}
+
+static int vidioc_s_jpegcomp(struct file *file, void *priv,
+			const struct v4l2_jpegcompression *jpegcomp)
+{
+	struct gspca_dev *gspca_dev = video_drvdata(file);
+
+	gspca_dev->usb_err = 0;
+	return gspca_dev->sd_desc->set_jcomp(gspca_dev, jpegcomp);
+}
+
+static int vidioc_g_parm(struct file *filp, void *priv,
+			struct v4l2_streamparm *parm)
+{
+	struct gspca_dev *gspca_dev = video_drvdata(filp);
+
+	parm->parm.capture.readbuffers = gspca_dev->queue.min_buffers_needed;
+
+	if (!gspca_dev->sd_desc->get_streamparm)
+		return 0;
+
+	parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
+	gspca_dev->usb_err = 0;
+	gspca_dev->sd_desc->get_streamparm(gspca_dev, parm);
+	return gspca_dev->usb_err;
+}
+
+static int vidioc_s_parm(struct file *filp, void *priv,
+			struct v4l2_streamparm *parm)
+{
+	struct gspca_dev *gspca_dev = video_drvdata(filp);
+
+	parm->parm.capture.readbuffers = gspca_dev->queue.min_buffers_needed;
+
+	if (!gspca_dev->sd_desc->set_streamparm) {
+		parm->parm.capture.capability = 0;
+		return 0;
+	}
+
+	parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
+	gspca_dev->usb_err = 0;
+	gspca_dev->sd_desc->set_streamparm(gspca_dev, parm);
+	return gspca_dev->usb_err;
+}
+
+static int gspca_queue_setup(struct vb2_queue *vq,
+			     unsigned int *nbuffers, unsigned int *nplanes,
+			     unsigned int sizes[], struct device *alloc_devs[])
+{
+	struct gspca_dev *gspca_dev = vb2_get_drv_priv(vq);
+	unsigned int size = PAGE_ALIGN(gspca_dev->pixfmt.sizeimage);
+
+	if (*nplanes)
+		return sizes[0] < size ? -EINVAL : 0;
+	*nplanes = 1;
+	sizes[0] = size;
+	return 0;
+}
+
+static int gspca_buffer_prepare(struct vb2_buffer *vb)
+{
+	struct gspca_dev *gspca_dev = vb2_get_drv_priv(vb->vb2_queue);
+	unsigned long size = PAGE_ALIGN(gspca_dev->pixfmt.sizeimage);
+
+	if (vb2_plane_size(vb, 0) < size) {
+		gspca_err(gspca_dev, "buffer too small (%lu < %lu)\n",
+			 vb2_plane_size(vb, 0), size);
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static void gspca_buffer_finish(struct vb2_buffer *vb)
+{
+	struct gspca_dev *gspca_dev = vb2_get_drv_priv(vb->vb2_queue);
+
+	if (!gspca_dev->sd_desc->dq_callback)
+		return;
+
+	gspca_dev->usb_err = 0;
+	if (gspca_dev->present)
+		gspca_dev->sd_desc->dq_callback(gspca_dev);
+}
+
+static void gspca_buffer_queue(struct vb2_buffer *vb)
+{
+	struct gspca_dev *gspca_dev = vb2_get_drv_priv(vb->vb2_queue);
+	struct gspca_buffer *buf = to_gspca_buffer(vb);
+	unsigned long flags;
+
+	spin_lock_irqsave(&gspca_dev->qlock, flags);
+	list_add_tail(&buf->list, &gspca_dev->buf_list);
+	spin_unlock_irqrestore(&gspca_dev->qlock, flags);
+}
+
+static void gspca_return_all_buffers(struct gspca_dev *gspca_dev,
+				     enum vb2_buffer_state state)
+{
+	struct gspca_buffer *buf, *node;
+	unsigned long flags;
+
+	spin_lock_irqsave(&gspca_dev->qlock, flags);
+	list_for_each_entry_safe(buf, node, &gspca_dev->buf_list, list) {
+		vb2_buffer_done(&buf->vb.vb2_buf, state);
+		list_del(&buf->list);
+	}
+	spin_unlock_irqrestore(&gspca_dev->qlock, flags);
+}
+
+static int gspca_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+	struct gspca_dev *gspca_dev = vb2_get_drv_priv(vq);
+	int ret;
+
+	gspca_dev->sequence = 0;
+
+	ret = gspca_init_transfer(gspca_dev);
+	if (ret)
+		gspca_return_all_buffers(gspca_dev, VB2_BUF_STATE_QUEUED);
+	return ret;
+}
+
+static void gspca_stop_streaming(struct vb2_queue *vq)
+{
+	struct gspca_dev *gspca_dev = vb2_get_drv_priv(vq);
+
+	gspca_stream_off(gspca_dev);
+
+	/* Release all active buffers */
+	gspca_return_all_buffers(gspca_dev, VB2_BUF_STATE_ERROR);
+}
+
+static const struct vb2_ops gspca_qops = {
+	.queue_setup		= gspca_queue_setup,
+	.buf_prepare		= gspca_buffer_prepare,
+	.buf_finish		= gspca_buffer_finish,
+	.buf_queue		= gspca_buffer_queue,
+	.start_streaming	= gspca_start_streaming,
+	.stop_streaming		= gspca_stop_streaming,
+	.wait_prepare		= vb2_ops_wait_prepare,
+	.wait_finish		= vb2_ops_wait_finish,
+};
+
+static const struct v4l2_file_operations dev_fops = {
+	.owner = THIS_MODULE,
+	.open = v4l2_fh_open,
+	.release = vb2_fop_release,
+	.unlocked_ioctl = video_ioctl2,
+	.read = vb2_fop_read,
+	.mmap = vb2_fop_mmap,
+	.poll = vb2_fop_poll,
+};
+
+static const struct v4l2_ioctl_ops dev_ioctl_ops = {
+	.vidioc_querycap	= vidioc_querycap,
+	.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap	= vidioc_try_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap	= vidioc_g_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap	= vidioc_s_fmt_vid_cap,
+	.vidioc_enum_input	= vidioc_enum_input,
+	.vidioc_g_input		= vidioc_g_input,
+	.vidioc_s_input		= vidioc_s_input,
+	.vidioc_g_jpegcomp	= vidioc_g_jpegcomp,
+	.vidioc_s_jpegcomp	= vidioc_s_jpegcomp,
+	.vidioc_g_parm		= vidioc_g_parm,
+	.vidioc_s_parm		= vidioc_s_parm,
+	.vidioc_enum_framesizes = vidioc_enum_framesizes,
+	.vidioc_enum_frameintervals = vidioc_enum_frameintervals,
+
+	.vidioc_reqbufs		= vb2_ioctl_reqbufs,
+	.vidioc_create_bufs	= vb2_ioctl_create_bufs,
+	.vidioc_querybuf	= vb2_ioctl_querybuf,
+	.vidioc_qbuf		= vb2_ioctl_qbuf,
+	.vidioc_dqbuf		= vb2_ioctl_dqbuf,
+	.vidioc_expbuf		= vb2_ioctl_expbuf,
+	.vidioc_streamon	= vb2_ioctl_streamon,
+	.vidioc_streamoff	= vb2_ioctl_streamoff,
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.vidioc_g_chip_info	= vidioc_g_chip_info,
+	.vidioc_g_register	= vidioc_g_register,
+	.vidioc_s_register	= vidioc_s_register,
+#endif
+	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static const struct video_device gspca_template = {
+	.name = "gspca main driver",
+	.fops = &dev_fops,
+	.ioctl_ops = &dev_ioctl_ops,
+	.release = video_device_release_empty, /* We use v4l2_dev.release */
+};
+
+/*
+ * probe and create a new gspca device
+ *
+ * This function must be called by the sub-driver when it is
+ * called for probing a new device.
+ */
+int gspca_dev_probe2(struct usb_interface *intf,
+		const struct usb_device_id *id,
+		const struct sd_desc *sd_desc,
+		int dev_size,
+		struct module *module)
+{
+	struct gspca_dev *gspca_dev;
+	struct usb_device *dev = interface_to_usbdev(intf);
+	struct vb2_queue *q;
+	int ret;
+
+	pr_info("%s-" GSPCA_VERSION " probing %04x:%04x\n",
+		sd_desc->name, id->idVendor, id->idProduct);
+
+	/* create the device */
+	if (dev_size < sizeof *gspca_dev)
+		dev_size = sizeof *gspca_dev;
+	gspca_dev = kzalloc(dev_size, GFP_KERNEL);
+	if (!gspca_dev) {
+		pr_err("couldn't kzalloc gspca struct\n");
+		return -ENOMEM;
+	}
+	gspca_dev->usb_buf = kmalloc(USB_BUF_SZ, GFP_KERNEL);
+	if (!gspca_dev->usb_buf) {
+		pr_err("out of memory\n");
+		ret = -ENOMEM;
+		goto out;
+	}
+	gspca_dev->dev = dev;
+	gspca_dev->iface = intf->cur_altsetting->desc.bInterfaceNumber;
+	gspca_dev->xfer_ep = -1;
+
+	/* check if any audio device */
+	if (dev->actconfig->desc.bNumInterfaces != 1) {
+		int i;
+		struct usb_interface *intf2;
+
+		for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) {
+			intf2 = dev->actconfig->interface[i];
+			if (intf2 != NULL
+			 && intf2->altsetting != NULL
+			 && intf2->altsetting->desc.bInterfaceClass ==
+					 USB_CLASS_AUDIO) {
+				gspca_dev->audio = 1;
+				break;
+			}
+		}
+	}
+
+	gspca_dev->v4l2_dev.release = gspca_release;
+	ret = v4l2_device_register(&intf->dev, &gspca_dev->v4l2_dev);
+	if (ret)
+		goto out;
+	gspca_dev->present = true;
+	gspca_dev->sd_desc = sd_desc;
+	gspca_dev->empty_packet = -1;	/* don't check the empty packets */
+	gspca_dev->vdev = gspca_template;
+	gspca_dev->vdev.v4l2_dev = &gspca_dev->v4l2_dev;
+	video_set_drvdata(&gspca_dev->vdev, gspca_dev);
+	gspca_dev->module = module;
+
+	mutex_init(&gspca_dev->usb_lock);
+	gspca_dev->vdev.lock = &gspca_dev->usb_lock;
+	init_waitqueue_head(&gspca_dev->wq);
+
+	/* Initialize the vb2 queue */
+	q = &gspca_dev->queue;
+	q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ;
+	q->drv_priv = gspca_dev;
+	q->buf_struct_size = sizeof(struct gspca_buffer);
+	q->ops = &gspca_qops;
+	q->mem_ops = &vb2_vmalloc_memops;
+	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	q->min_buffers_needed = 2;
+	q->lock = &gspca_dev->usb_lock;
+	ret = vb2_queue_init(q);
+	if (ret)
+		goto out;
+	gspca_dev->vdev.queue = q;
+
+	INIT_LIST_HEAD(&gspca_dev->buf_list);
+	spin_lock_init(&gspca_dev->qlock);
+
+	/* configure the subdriver and initialize the USB device */
+	ret = sd_desc->config(gspca_dev, id);
+	if (ret < 0)
+		goto out;
+	ret = sd_desc->init(gspca_dev);
+	if (ret < 0)
+		goto out;
+	if (sd_desc->init_controls)
+		ret = sd_desc->init_controls(gspca_dev);
+	if (ret < 0)
+		goto out;
+	gspca_set_default_mode(gspca_dev);
+
+	ret = gspca_input_connect(gspca_dev);
+	if (ret)
+		goto out;
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	if (!gspca_dev->sd_desc->get_register)
+		v4l2_disable_ioctl(&gspca_dev->vdev, VIDIOC_DBG_G_REGISTER);
+	if (!gspca_dev->sd_desc->set_register)
+		v4l2_disable_ioctl(&gspca_dev->vdev, VIDIOC_DBG_S_REGISTER);
+#endif
+	if (!gspca_dev->sd_desc->get_jcomp)
+		v4l2_disable_ioctl(&gspca_dev->vdev, VIDIOC_G_JPEGCOMP);
+	if (!gspca_dev->sd_desc->set_jcomp)
+		v4l2_disable_ioctl(&gspca_dev->vdev, VIDIOC_S_JPEGCOMP);
+
+	/* init video stuff */
+	ret = video_register_device(&gspca_dev->vdev,
+				  VFL_TYPE_GRABBER,
+				  -1);
+	if (ret < 0) {
+		pr_err("video_register_device err %d\n", ret);
+		goto out;
+	}
+
+	usb_set_intfdata(intf, gspca_dev);
+	gspca_dbg(gspca_dev, D_PROBE, "%s created\n",
+		  video_device_node_name(&gspca_dev->vdev));
+
+	gspca_input_create_urb(gspca_dev);
+
+	return 0;
+out:
+#if IS_ENABLED(CONFIG_INPUT)
+	if (gspca_dev->input_dev)
+		input_unregister_device(gspca_dev->input_dev);
+#endif
+	v4l2_ctrl_handler_free(gspca_dev->vdev.ctrl_handler);
+	kfree(gspca_dev->usb_buf);
+	kfree(gspca_dev);
+	return ret;
+}
+EXPORT_SYMBOL(gspca_dev_probe2);
+
+/* same function as the previous one, but check the interface */
+int gspca_dev_probe(struct usb_interface *intf,
+		const struct usb_device_id *id,
+		const struct sd_desc *sd_desc,
+		int dev_size,
+		struct module *module)
+{
+	struct usb_device *dev = interface_to_usbdev(intf);
+
+	/* we don't handle multi-config cameras */
+	if (dev->descriptor.bNumConfigurations != 1) {
+		pr_err("%04x:%04x too many config\n",
+		       id->idVendor, id->idProduct);
+		return -ENODEV;
+	}
+
+	/* the USB video interface must be the first one */
+	if (dev->actconfig->desc.bNumInterfaces != 1
+	 && intf->cur_altsetting->desc.bInterfaceNumber != 0)
+		return -ENODEV;
+
+	return gspca_dev_probe2(intf, id, sd_desc, dev_size, module);
+}
+EXPORT_SYMBOL(gspca_dev_probe);
+
+/*
+ * USB disconnection
+ *
+ * This function must be called by the sub-driver
+ * when the device disconnects, after the specific resources are freed.
+ */
+void gspca_disconnect(struct usb_interface *intf)
+{
+	struct gspca_dev *gspca_dev = usb_get_intfdata(intf);
+#if IS_ENABLED(CONFIG_INPUT)
+	struct input_dev *input_dev;
+#endif
+
+	gspca_dbg(gspca_dev, D_PROBE, "%s disconnect\n",
+		  video_device_node_name(&gspca_dev->vdev));
+
+	mutex_lock(&gspca_dev->usb_lock);
+	gspca_dev->present = false;
+
+	vb2_queue_error(&gspca_dev->queue);
+
+#if IS_ENABLED(CONFIG_INPUT)
+	input_dev = gspca_dev->input_dev;
+	if (input_dev) {
+		gspca_dev->input_dev = NULL;
+		input_unregister_device(input_dev);
+	}
+#endif
+
+	v4l2_device_disconnect(&gspca_dev->v4l2_dev);
+	video_unregister_device(&gspca_dev->vdev);
+
+	mutex_unlock(&gspca_dev->usb_lock);
+
+	/* (this will call gspca_release() immediately or on last close) */
+	v4l2_device_put(&gspca_dev->v4l2_dev);
+}
+EXPORT_SYMBOL(gspca_disconnect);
+
+#ifdef CONFIG_PM
+int gspca_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	struct gspca_dev *gspca_dev = usb_get_intfdata(intf);
+
+	gspca_input_destroy_urb(gspca_dev);
+
+	if (!vb2_start_streaming_called(&gspca_dev->queue))
+		return 0;
+
+	mutex_lock(&gspca_dev->usb_lock);
+	gspca_dev->frozen = 1;		/* avoid urb error messages */
+	gspca_dev->usb_err = 0;
+	if (gspca_dev->sd_desc->stopN)
+		gspca_dev->sd_desc->stopN(gspca_dev);
+	destroy_urbs(gspca_dev);
+	gspca_set_alt0(gspca_dev);
+	if (gspca_dev->sd_desc->stop0)
+		gspca_dev->sd_desc->stop0(gspca_dev);
+	mutex_unlock(&gspca_dev->usb_lock);
+
+	return 0;
+}
+EXPORT_SYMBOL(gspca_suspend);
+
+int gspca_resume(struct usb_interface *intf)
+{
+	struct gspca_dev *gspca_dev = usb_get_intfdata(intf);
+	int streaming, ret = 0;
+
+	mutex_lock(&gspca_dev->usb_lock);
+	gspca_dev->frozen = 0;
+	gspca_dev->usb_err = 0;
+	gspca_dev->sd_desc->init(gspca_dev);
+	/*
+	 * Most subdrivers send all ctrl values on sd_start and thus
+	 * only write to the device registers on s_ctrl when streaming ->
+	 * Clear streaming to avoid setting all ctrls twice.
+	 */
+	streaming = vb2_start_streaming_called(&gspca_dev->queue);
+	if (streaming)
+		ret = gspca_init_transfer(gspca_dev);
+	else
+		gspca_input_create_urb(gspca_dev);
+	mutex_unlock(&gspca_dev->usb_lock);
+
+	return ret;
+}
+EXPORT_SYMBOL(gspca_resume);
+#endif
+
+/* -- module insert / remove -- */
+static int __init gspca_init(void)
+{
+	pr_info("v" GSPCA_VERSION " registered\n");
+	return 0;
+}
+static void __exit gspca_exit(void)
+{
+}
+
+module_init(gspca_init);
+module_exit(gspca_exit);
+
+module_param_named(debug, gspca_debug, int, 0644);
+MODULE_PARM_DESC(debug,
+		"1:probe 2:config 3:stream 4:frame 5:packet 6:usbi 7:usbo");
diff --git a/drivers/media/usb/gspca/gspca.h b/drivers/media/usb/gspca/gspca.h
new file mode 100644
index 0000000..b0ced2e
--- /dev/null
+++ b/drivers/media/usb/gspca/gspca.h
@@ -0,0 +1,242 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef GSPCAV2_H
+#define GSPCAV2_H
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/usb.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-vmalloc.h>
+#include <linux/mutex.h>
+
+
+
+/* GSPCA debug codes */
+
+#define D_PROBE  1
+#define D_CONF   2
+#define D_STREAM 3
+#define D_FRAM   4
+#define D_PACK   5
+#define D_USBI   6
+#define D_USBO   7
+
+extern int gspca_debug;
+
+
+#define gspca_dbg(gspca_dev, level, fmt, ...)			\
+	v4l2_dbg(level, gspca_debug, &(gspca_dev)->v4l2_dev,	\
+		 fmt, ##__VA_ARGS__)
+
+#define gspca_err(gspca_dev, fmt, ...)				\
+	v4l2_err(&(gspca_dev)->v4l2_dev, fmt, ##__VA_ARGS__)
+
+#define GSPCA_MAX_FRAMES 16	/* maximum number of video frame buffers */
+/* image transfers */
+#define MAX_NURBS 4		/* max number of URBs */
+
+
+/* used to list framerates supported by a camera mode (resolution) */
+struct framerates {
+	const u8 *rates;
+	int nrates;
+};
+
+/* device information - set at probe time */
+struct cam {
+	const struct v4l2_pix_format *cam_mode;	/* size nmodes */
+	const struct framerates *mode_framerates; /* must have size nmodes,
+						   * just like cam_mode */
+	u32 bulk_size;		/* buffer size when image transfer by bulk */
+	u32 input_flags;	/* value for ENUM_INPUT status flags */
+	u8 nmodes;		/* size of cam_mode */
+	u8 no_urb_create;	/* don't create transfer URBs */
+	u8 bulk_nurbs;		/* number of URBs in bulk mode
+				 * - cannot be > MAX_NURBS
+				 * - when 0 and bulk_size != 0 means
+				 *   1 URB and submit done by subdriver */
+	u8 bulk;		/* image transfer by 0:isoc / 1:bulk */
+	u8 npkt;		/* number of packets in an ISOC message
+				 * 0 is the default value: 32 packets */
+	u8 needs_full_bandwidth;/* Set this flag to notify the bandwidth calc.
+				 * code that the cam fills all image buffers to
+				 * the max, even when using compression. */
+};
+
+struct gspca_dev;
+struct gspca_frame;
+
+/* subdriver operations */
+typedef int (*cam_op) (struct gspca_dev *);
+typedef void (*cam_v_op) (struct gspca_dev *);
+typedef int (*cam_cf_op) (struct gspca_dev *, const struct usb_device_id *);
+typedef int (*cam_get_jpg_op) (struct gspca_dev *,
+				struct v4l2_jpegcompression *);
+typedef int (*cam_set_jpg_op) (struct gspca_dev *,
+				const struct v4l2_jpegcompression *);
+typedef int (*cam_get_reg_op) (struct gspca_dev *,
+				struct v4l2_dbg_register *);
+typedef int (*cam_set_reg_op) (struct gspca_dev *,
+				const struct v4l2_dbg_register *);
+typedef int (*cam_chip_info_op) (struct gspca_dev *,
+				struct v4l2_dbg_chip_info *);
+typedef void (*cam_streamparm_op) (struct gspca_dev *,
+				  struct v4l2_streamparm *);
+typedef void (*cam_pkt_op) (struct gspca_dev *gspca_dev,
+				u8 *data,
+				int len);
+typedef int (*cam_int_pkt_op) (struct gspca_dev *gspca_dev,
+				u8 *data,
+				int len);
+typedef void (*cam_format_op) (struct gspca_dev *gspca_dev,
+				struct v4l2_format *fmt);
+typedef int (*cam_frmsize_op) (struct gspca_dev *gspca_dev,
+				struct v4l2_frmsizeenum *fsize);
+
+/* subdriver description */
+struct sd_desc {
+/* information */
+	const char *name;	/* sub-driver name */
+/* mandatory operations */
+	cam_cf_op config;	/* called on probe */
+	cam_op init;		/* called on probe and resume */
+	cam_op init_controls;	/* called on probe */
+	cam_op start;		/* called on stream on after URBs creation */
+	cam_pkt_op pkt_scan;
+/* optional operations */
+	cam_op isoc_init;	/* called on stream on before getting the EP */
+	cam_op isoc_nego;	/* called when URB submit failed with NOSPC */
+	cam_v_op stopN;		/* called on stream off - main alt */
+	cam_v_op stop0;		/* called on stream off & disconnect - alt 0 */
+	cam_v_op dq_callback;	/* called when a frame has been dequeued */
+	cam_get_jpg_op get_jcomp;
+	cam_set_jpg_op set_jcomp;
+	cam_streamparm_op get_streamparm;
+	cam_streamparm_op set_streamparm;
+	cam_format_op try_fmt;
+	cam_frmsize_op enum_framesizes;
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	cam_set_reg_op set_register;
+	cam_get_reg_op get_register;
+	cam_chip_info_op get_chip_info;
+#endif
+#if IS_ENABLED(CONFIG_INPUT)
+	cam_int_pkt_op int_pkt_scan;
+	/* other_input makes the gspca core create gspca_dev->input even when
+	   int_pkt_scan is NULL, for cams with non interrupt driven buttons */
+	u8 other_input;
+#endif
+};
+
+/* packet types when moving from iso buf to frame buf */
+enum gspca_packet_type {
+	DISCARD_PACKET,
+	FIRST_PACKET,
+	INTER_PACKET,
+	LAST_PACKET
+};
+
+struct gspca_buffer {
+	struct vb2_v4l2_buffer vb;
+	struct list_head list;
+};
+
+static inline struct gspca_buffer *to_gspca_buffer(struct vb2_buffer *vb2)
+{
+	return container_of(vb2, struct gspca_buffer, vb.vb2_buf);
+}
+
+struct gspca_dev {
+	struct video_device vdev;	/* !! must be the first item */
+	struct module *module;		/* subdriver handling the device */
+	struct v4l2_device v4l2_dev;
+	struct usb_device *dev;
+
+#if IS_ENABLED(CONFIG_INPUT)
+	struct input_dev *input_dev;
+	char phys[64];			/* physical device path */
+#endif
+
+	struct cam cam;				/* device information */
+	const struct sd_desc *sd_desc;		/* subdriver description */
+	struct v4l2_ctrl_handler ctrl_handler;
+
+	/* autogain and exposure or gain control cluster, these are global as
+	   the autogain/exposure functions in autogain_functions.c use them */
+	struct {
+		struct v4l2_ctrl *autogain;
+		struct v4l2_ctrl *exposure;
+		struct v4l2_ctrl *gain;
+		int exp_too_low_cnt, exp_too_high_cnt;
+	};
+
+#define USB_BUF_SZ 64
+	__u8 *usb_buf;				/* buffer for USB exchanges */
+	struct urb *urb[MAX_NURBS];
+#if IS_ENABLED(CONFIG_INPUT)
+	struct urb *int_urb;
+#endif
+
+	u8 *image;				/* image being filled */
+	u32 image_len;				/* current length of image */
+	__u8 last_packet_type;
+	__s8 empty_packet;		/* if (-1) don't check empty packets */
+	bool streaming;
+
+	__u8 curr_mode;			/* current camera mode */
+	struct v4l2_pix_format pixfmt;	/* current mode parameters */
+	__u32 sequence;			/* frame sequence number */
+
+	struct vb2_queue queue;
+
+	spinlock_t qlock;
+	struct list_head buf_list;
+
+	wait_queue_head_t wq;		/* wait queue */
+	struct mutex usb_lock;		/* usb exchange protection */
+	int usb_err;			/* USB error - protected by usb_lock */
+	u16 pkt_size;			/* ISOC packet size */
+#ifdef CONFIG_PM
+	char frozen;			/* suspend - resume */
+#endif
+	bool present;
+	char memory;			/* memory type (V4L2_MEMORY_xxx) */
+	__u8 iface;			/* USB interface number */
+	__u8 alt;			/* USB alternate setting */
+	int xfer_ep;			/* USB transfer endpoint address */
+	u8 audio;			/* presence of audio device */
+
+	/* (*) These variables are proteced by both usb_lock and queue_lock,
+	   that is any code setting them is holding *both*, which means that
+	   any code getting them needs to hold at least one of them */
+};
+
+int gspca_dev_probe(struct usb_interface *intf,
+		const struct usb_device_id *id,
+		const struct sd_desc *sd_desc,
+		int dev_size,
+		struct module *module);
+int gspca_dev_probe2(struct usb_interface *intf,
+		const struct usb_device_id *id,
+		const struct sd_desc *sd_desc,
+		int dev_size,
+		struct module *module);
+void gspca_disconnect(struct usb_interface *intf);
+void gspca_frame_add(struct gspca_dev *gspca_dev,
+			enum gspca_packet_type packet_type,
+			const u8 *data,
+			int len);
+#ifdef CONFIG_PM
+int gspca_suspend(struct usb_interface *intf, pm_message_t message);
+int gspca_resume(struct usb_interface *intf);
+#endif
+int gspca_expo_autogain(struct gspca_dev *gspca_dev, int avg_lum,
+	int desired_avg_lum, int deadzone, int gain_knee, int exposure_knee);
+int gspca_coarse_grained_expo_autogain(struct gspca_dev *gspca_dev,
+	int avg_lum, int desired_avg_lum, int deadzone);
+
+#endif /* GSPCAV2_H */
diff --git a/drivers/media/usb/gspca/jeilinj.c b/drivers/media/usb/gspca/jeilinj.c
new file mode 100644
index 0000000..86d0a0a
--- /dev/null
+++ b/drivers/media/usb/gspca/jeilinj.c
@@ -0,0 +1,546 @@
+/*
+ * Jeilinj subdriver
+ *
+ * Supports some Jeilin dual-mode cameras which use bulk transport and
+ * download raw JPEG data.
+ *
+ * Copyright (C) 2009 Theodore Kilgore
+ *
+ * Sportscam DV15 support and control settings are
+ * Copyright (C) 2011 Patrice Chotard
+ *
+ * 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
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "jeilinj"
+
+#include <linux/slab.h>
+#include "gspca.h"
+#include "jpeg.h"
+
+MODULE_AUTHOR("Theodore Kilgore <kilgota@auburn.edu>");
+MODULE_DESCRIPTION("GSPCA/JEILINJ USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+/* Default timeouts, in ms */
+#define JEILINJ_CMD_TIMEOUT 500
+#define JEILINJ_CMD_DELAY 160
+#define JEILINJ_DATA_TIMEOUT 1000
+
+/* Maximum transfer size to use. */
+#define JEILINJ_MAX_TRANSFER 0x200
+#define FRAME_HEADER_LEN 0x10
+#define FRAME_START 0xFFFFFFFF
+
+enum {
+	SAKAR_57379,
+	SPORTSCAM_DV15,
+};
+
+#define CAMQUALITY_MIN 0	/* highest cam quality */
+#define CAMQUALITY_MAX 97	/* lowest cam quality  */
+
+/* Structure to hold all of our device specific stuff */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+	int blocks_left;
+	const struct v4l2_pix_format *cap_mode;
+	struct v4l2_ctrl *freq;
+	struct v4l2_ctrl *jpegqual;
+	/* Driver stuff */
+	u8 type;
+	u8 quality;				 /* image quality */
+#define QUALITY_MIN 35
+#define QUALITY_MAX 85
+#define QUALITY_DEF 85
+	u8 jpeg_hdr[JPEG_HDR_SZ];
+};
+
+struct jlj_command {
+	unsigned char instruction[2];
+	unsigned char ack_wanted;
+	unsigned char delay;
+};
+
+/* AFAICT these cameras will only do 320x240. */
+static struct v4l2_pix_format jlj_mode[] = {
+	{ 320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 0},
+	{ 640, 480, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 0}
+};
+
+/*
+ * cam uses endpoint 0x03 to send commands, 0x84 for read commands,
+ * and 0x82 for bulk transfer.
+ */
+
+/* All commands are two bytes only */
+static void jlj_write2(struct gspca_dev *gspca_dev, unsigned char *command)
+{
+	int retval;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	memcpy(gspca_dev->usb_buf, command, 2);
+	retval = usb_bulk_msg(gspca_dev->dev,
+			usb_sndbulkpipe(gspca_dev->dev, 3),
+			gspca_dev->usb_buf, 2, NULL, 500);
+	if (retval < 0) {
+		pr_err("command write [%02x] error %d\n",
+		       gspca_dev->usb_buf[0], retval);
+		gspca_dev->usb_err = retval;
+	}
+}
+
+/* Responses are one byte only */
+static void jlj_read1(struct gspca_dev *gspca_dev, unsigned char *response)
+{
+	int retval;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	retval = usb_bulk_msg(gspca_dev->dev,
+	usb_rcvbulkpipe(gspca_dev->dev, 0x84),
+				gspca_dev->usb_buf, 1, NULL, 500);
+	*response = gspca_dev->usb_buf[0];
+	if (retval < 0) {
+		pr_err("read command [%02x] error %d\n",
+		       gspca_dev->usb_buf[0], retval);
+		gspca_dev->usb_err = retval;
+	}
+}
+
+static void setfreq(struct gspca_dev *gspca_dev, s32 val)
+{
+	u8 freq_commands[][2] = {
+		{0x71, 0x80},
+		{0x70, 0x07}
+	};
+
+	freq_commands[0][1] |= val >> 1;
+
+	jlj_write2(gspca_dev, freq_commands[0]);
+	jlj_write2(gspca_dev, freq_commands[1]);
+}
+
+static void setcamquality(struct gspca_dev *gspca_dev, s32 val)
+{
+	u8 quality_commands[][2] = {
+		{0x71, 0x1E},
+		{0x70, 0x06}
+	};
+	u8 camquality;
+
+	/* adapt camera quality from jpeg quality */
+	camquality = ((QUALITY_MAX - val) * CAMQUALITY_MAX)
+		/ (QUALITY_MAX - QUALITY_MIN);
+	quality_commands[0][1] += camquality;
+
+	jlj_write2(gspca_dev, quality_commands[0]);
+	jlj_write2(gspca_dev, quality_commands[1]);
+}
+
+static void setautogain(struct gspca_dev *gspca_dev, s32 val)
+{
+	u8 autogain_commands[][2] = {
+		{0x94, 0x02},
+		{0xcf, 0x00}
+	};
+
+	autogain_commands[1][1] = val << 4;
+
+	jlj_write2(gspca_dev, autogain_commands[0]);
+	jlj_write2(gspca_dev, autogain_commands[1]);
+}
+
+static void setred(struct gspca_dev *gspca_dev, s32 val)
+{
+	u8 setred_commands[][2] = {
+		{0x94, 0x02},
+		{0xe6, 0x00}
+	};
+
+	setred_commands[1][1] = val;
+
+	jlj_write2(gspca_dev, setred_commands[0]);
+	jlj_write2(gspca_dev, setred_commands[1]);
+}
+
+static void setgreen(struct gspca_dev *gspca_dev, s32 val)
+{
+	u8 setgreen_commands[][2] = {
+		{0x94, 0x02},
+		{0xe7, 0x00}
+	};
+
+	setgreen_commands[1][1] = val;
+
+	jlj_write2(gspca_dev, setgreen_commands[0]);
+	jlj_write2(gspca_dev, setgreen_commands[1]);
+}
+
+static void setblue(struct gspca_dev *gspca_dev, s32 val)
+{
+	u8 setblue_commands[][2] = {
+		{0x94, 0x02},
+		{0xe9, 0x00}
+	};
+
+	setblue_commands[1][1] = val;
+
+	jlj_write2(gspca_dev, setblue_commands[0]);
+	jlj_write2(gspca_dev, setblue_commands[1]);
+}
+
+static int jlj_start(struct gspca_dev *gspca_dev)
+{
+	int i;
+	int start_commands_size;
+	u8 response = 0xff;
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct jlj_command start_commands[] = {
+		{{0x71, 0x81}, 0, 0},
+		{{0x70, 0x05}, 0, JEILINJ_CMD_DELAY},
+		{{0x95, 0x70}, 1, 0},
+		{{0x71, 0x81 - gspca_dev->curr_mode}, 0, 0},
+		{{0x70, 0x04}, 0, JEILINJ_CMD_DELAY},
+		{{0x95, 0x70}, 1, 0},
+		{{0x71, 0x00}, 0, 0},   /* start streaming ??*/
+		{{0x70, 0x08}, 0, JEILINJ_CMD_DELAY},
+		{{0x95, 0x70}, 1, 0},
+#define SPORTSCAM_DV15_CMD_SIZE 9
+		{{0x94, 0x02}, 0, 0},
+		{{0xde, 0x24}, 0, 0},
+		{{0x94, 0x02}, 0, 0},
+		{{0xdd, 0xf0}, 0, 0},
+		{{0x94, 0x02}, 0, 0},
+		{{0xe3, 0x2c}, 0, 0},
+		{{0x94, 0x02}, 0, 0},
+		{{0xe4, 0x00}, 0, 0},
+		{{0x94, 0x02}, 0, 0},
+		{{0xe5, 0x00}, 0, 0},
+		{{0x94, 0x02}, 0, 0},
+		{{0xe6, 0x2c}, 0, 0},
+		{{0x94, 0x03}, 0, 0},
+		{{0xaa, 0x00}, 0, 0}
+	};
+
+	sd->blocks_left = 0;
+	/* Under Windows, USB spy shows that only the 9 first start
+	 * commands are used for SPORTSCAM_DV15 webcam
+	 */
+	if (sd->type == SPORTSCAM_DV15)
+		start_commands_size = SPORTSCAM_DV15_CMD_SIZE;
+	else
+		start_commands_size = ARRAY_SIZE(start_commands);
+
+	for (i = 0; i < start_commands_size; i++) {
+		jlj_write2(gspca_dev, start_commands[i].instruction);
+		if (start_commands[i].delay)
+			msleep(start_commands[i].delay);
+		if (start_commands[i].ack_wanted)
+			jlj_read1(gspca_dev, &response);
+	}
+	setcamquality(gspca_dev, v4l2_ctrl_g_ctrl(sd->jpegqual));
+	msleep(2);
+	setfreq(gspca_dev, v4l2_ctrl_g_ctrl(sd->freq));
+	if (gspca_dev->usb_err < 0)
+		gspca_err(gspca_dev, "Start streaming command failed\n");
+	return gspca_dev->usb_err;
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data, int len)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int packet_type;
+	u32 header_marker;
+
+	gspca_dbg(gspca_dev, D_STREAM, "Got %d bytes out of %d for Block 0\n",
+		  len, JEILINJ_MAX_TRANSFER);
+	if (len != JEILINJ_MAX_TRANSFER) {
+		gspca_dbg(gspca_dev, D_PACK, "bad length\n");
+		goto discard;
+	}
+	/* check if it's start of frame */
+	header_marker = ((u32 *)data)[0];
+	if (header_marker == FRAME_START) {
+		sd->blocks_left = data[0x0a] - 1;
+		gspca_dbg(gspca_dev, D_STREAM, "blocks_left = 0x%x\n",
+			  sd->blocks_left);
+		/* Start a new frame, and add the JPEG header, first thing */
+		gspca_frame_add(gspca_dev, FIRST_PACKET,
+				sd->jpeg_hdr, JPEG_HDR_SZ);
+		/* Toss line 0 of data block 0, keep the rest. */
+		gspca_frame_add(gspca_dev, INTER_PACKET,
+				data + FRAME_HEADER_LEN,
+				JEILINJ_MAX_TRANSFER - FRAME_HEADER_LEN);
+	} else if (sd->blocks_left > 0) {
+		gspca_dbg(gspca_dev, D_STREAM, "%d blocks remaining for frame\n",
+			  sd->blocks_left);
+		sd->blocks_left -= 1;
+		if (sd->blocks_left == 0)
+			packet_type = LAST_PACKET;
+		else
+			packet_type = INTER_PACKET;
+		gspca_frame_add(gspca_dev, packet_type,
+				data, JEILINJ_MAX_TRANSFER);
+	} else
+		goto discard;
+	return;
+discard:
+	/* Discard data until a new frame starts. */
+	gspca_dev->last_packet_type = DISCARD_PACKET;
+}
+
+/* This function is called at probe time just before sd_init */
+static int sd_config(struct gspca_dev *gspca_dev,
+		const struct usb_device_id *id)
+{
+	struct cam *cam = &gspca_dev->cam;
+	struct sd *dev  = (struct sd *) gspca_dev;
+
+	dev->type = id->driver_info;
+	dev->quality = QUALITY_DEF;
+
+	cam->cam_mode = jlj_mode;
+	cam->nmodes = ARRAY_SIZE(jlj_mode);
+	cam->bulk = 1;
+	cam->bulk_nurbs = 1;
+	cam->bulk_size = JEILINJ_MAX_TRANSFER;
+	return 0;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	int i;
+	u8 *buf;
+	static u8 stop_commands[][2] = {
+		{0x71, 0x00},
+		{0x70, 0x09},
+		{0x71, 0x80},
+		{0x70, 0x05}
+	};
+
+	for (;;) {
+		/* get the image remaining blocks */
+		usb_bulk_msg(gspca_dev->dev,
+				gspca_dev->urb[0]->pipe,
+				gspca_dev->urb[0]->transfer_buffer,
+				JEILINJ_MAX_TRANSFER, NULL,
+				JEILINJ_DATA_TIMEOUT);
+
+		/* search for 0xff 0xd9  (EOF for JPEG) */
+		i = 0;
+		buf = gspca_dev->urb[0]->transfer_buffer;
+		while ((i < (JEILINJ_MAX_TRANSFER - 1)) &&
+			((buf[i] != 0xff) || (buf[i+1] != 0xd9)))
+			i++;
+
+		if (i != (JEILINJ_MAX_TRANSFER - 1))
+			/* last remaining block found */
+			break;
+		}
+
+	for (i = 0; i < ARRAY_SIZE(stop_commands); i++)
+		jlj_write2(gspca_dev, stop_commands[i]);
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	return gspca_dev->usb_err;
+}
+
+/* Set up for getting frames. */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *dev = (struct sd *) gspca_dev;
+
+	/* create the JPEG header */
+	jpeg_define(dev->jpeg_hdr, gspca_dev->pixfmt.height,
+			gspca_dev->pixfmt.width,
+			0x21);          /* JPEG 422 */
+	jpeg_set_qual(dev->jpeg_hdr, dev->quality);
+	gspca_dbg(gspca_dev, D_STREAM, "Start streaming at %dx%d\n",
+		  gspca_dev->pixfmt.height, gspca_dev->pixfmt.width);
+	jlj_start(gspca_dev);
+	return gspca_dev->usb_err;
+}
+
+/* Table of supported USB devices */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x0979, 0x0280), .driver_info = SAKAR_57379},
+	{USB_DEVICE(0x0979, 0x0270), .driver_info = SPORTSCAM_DV15},
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *)gspca_dev;
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_POWER_LINE_FREQUENCY:
+		setfreq(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_RED_BALANCE:
+		setred(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_GAIN:
+		setgreen(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_BLUE_BALANCE:
+		setblue(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_AUTOGAIN:
+		setautogain(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_JPEG_COMPRESSION_QUALITY:
+		jpeg_set_qual(sd->jpeg_hdr, ctrl->val);
+		setcamquality(gspca_dev, ctrl->val);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *)gspca_dev;
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+	static const struct v4l2_ctrl_config custom_autogain = {
+		.ops = &sd_ctrl_ops,
+		.id = V4L2_CID_AUTOGAIN,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Automatic Gain (and Exposure)",
+		.max = 3,
+		.step = 1,
+		.def = 0,
+	};
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 6);
+	sd->freq = v4l2_ctrl_new_std_menu(hdl, &sd_ctrl_ops,
+			V4L2_CID_POWER_LINE_FREQUENCY,
+			V4L2_CID_POWER_LINE_FREQUENCY_60HZ, 1,
+			V4L2_CID_POWER_LINE_FREQUENCY_60HZ);
+	v4l2_ctrl_new_custom(hdl, &custom_autogain, NULL);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_RED_BALANCE, 0, 3, 1, 2);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_GAIN, 0, 3, 1, 2);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BLUE_BALANCE, 0, 3, 1, 2);
+	sd->jpegqual = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_JPEG_COMPRESSION_QUALITY,
+			QUALITY_MIN, QUALITY_MAX, 1, QUALITY_DEF);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	return 0;
+}
+
+static int sd_set_jcomp(struct gspca_dev *gspca_dev,
+			const struct v4l2_jpegcompression *jcomp)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	v4l2_ctrl_s_ctrl(sd->jpegqual, jcomp->quality);
+	return 0;
+}
+
+static int sd_get_jcomp(struct gspca_dev *gspca_dev,
+			struct v4l2_jpegcompression *jcomp)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	memset(jcomp, 0, sizeof *jcomp);
+	jcomp->quality = v4l2_ctrl_g_ctrl(sd->jpegqual);
+	jcomp->jpeg_markers = V4L2_JPEG_MARKER_DHT
+			| V4L2_JPEG_MARKER_DQT;
+	return 0;
+}
+
+
+/* sub-driver description */
+static const struct sd_desc sd_desc_sakar_57379 = {
+	.name   = MODULE_NAME,
+	.config = sd_config,
+	.init   = sd_init,
+	.start  = sd_start,
+	.stopN  = sd_stopN,
+	.pkt_scan = sd_pkt_scan,
+};
+
+/* sub-driver description */
+static const struct sd_desc sd_desc_sportscam_dv15 = {
+	.name   = MODULE_NAME,
+	.config = sd_config,
+	.init   = sd_init,
+	.init_controls = sd_init_controls,
+	.start  = sd_start,
+	.stopN  = sd_stopN,
+	.pkt_scan = sd_pkt_scan,
+	.get_jcomp = sd_get_jcomp,
+	.set_jcomp = sd_set_jcomp,
+};
+
+static const struct sd_desc *sd_desc[2] = {
+	&sd_desc_sakar_57379,
+	&sd_desc_sportscam_dv15
+};
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id,
+			sd_desc[id->driver_info],
+			sizeof(struct sd),
+			THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name       = MODULE_NAME,
+	.id_table   = device_table,
+	.probe      = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume  = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/jl2005bcd.c b/drivers/media/usb/gspca/jl2005bcd.c
new file mode 100644
index 0000000..c402459
--- /dev/null
+++ b/drivers/media/usb/gspca/jl2005bcd.c
@@ -0,0 +1,531 @@
+/*
+ * Jeilin JL2005B/C/D library
+ *
+ * Copyright (C) 2011 Theodore Kilgore <kilgota@auburn.edu>
+ *
+ * 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
+ * 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.
+ */
+
+#define MODULE_NAME "jl2005bcd"
+
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+#include "gspca.h"
+
+
+MODULE_AUTHOR("Theodore Kilgore <kilgota@auburn.edu>");
+MODULE_DESCRIPTION("JL2005B/C/D USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+/* Default timeouts, in ms */
+#define JL2005C_CMD_TIMEOUT 500
+#define JL2005C_DATA_TIMEOUT 1000
+
+/* Maximum transfer size to use. */
+#define JL2005C_MAX_TRANSFER 0x200
+#define FRAME_HEADER_LEN 16
+
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;  /* !! must be the first item */
+	unsigned char firmware_id[6];
+	const struct v4l2_pix_format *cap_mode;
+	/* Driver stuff */
+	struct work_struct work_struct;
+	u8 frame_brightness;
+	int block_size;	/* block size of camera */
+	int vga;	/* 1 if vga cam, 0 if cif cam */
+};
+
+
+/* Camera has two resolution settings. What they are depends on model. */
+static const struct v4l2_pix_format cif_mode[] = {
+	{176, 144, V4L2_PIX_FMT_JL2005BCD, V4L2_FIELD_NONE,
+		.bytesperline = 176,
+		.sizeimage = 176 * 144,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+	{352, 288, V4L2_PIX_FMT_JL2005BCD, V4L2_FIELD_NONE,
+		.bytesperline = 352,
+		.sizeimage = 352 * 288,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+};
+
+static const struct v4l2_pix_format vga_mode[] = {
+	{320, 240, V4L2_PIX_FMT_JL2005BCD, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+	{640, 480, V4L2_PIX_FMT_JL2005BCD, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+};
+
+/*
+ * cam uses endpoint 0x03 to send commands, 0x84 for read commands,
+ * and 0x82 for bulk data transfer.
+ */
+
+/* All commands are two bytes only */
+static int jl2005c_write2(struct gspca_dev *gspca_dev, unsigned char *command)
+{
+	int retval;
+
+	memcpy(gspca_dev->usb_buf, command, 2);
+	retval = usb_bulk_msg(gspca_dev->dev,
+			usb_sndbulkpipe(gspca_dev->dev, 3),
+			gspca_dev->usb_buf, 2, NULL, 500);
+	if (retval < 0)
+		pr_err("command write [%02x] error %d\n",
+		       gspca_dev->usb_buf[0], retval);
+	return retval;
+}
+
+/* Response to a command is one byte in usb_buf[0], only if requested. */
+static int jl2005c_read1(struct gspca_dev *gspca_dev)
+{
+	int retval;
+
+	retval = usb_bulk_msg(gspca_dev->dev,
+				usb_rcvbulkpipe(gspca_dev->dev, 0x84),
+				gspca_dev->usb_buf, 1, NULL, 500);
+	if (retval < 0)
+		pr_err("read command [0x%02x] error %d\n",
+		       gspca_dev->usb_buf[0], retval);
+	return retval;
+}
+
+/* Response appears in gspca_dev->usb_buf[0] */
+static int jl2005c_read_reg(struct gspca_dev *gspca_dev, unsigned char reg)
+{
+	int retval;
+
+	static u8 instruction[2] = {0x95, 0x00};
+	/* put register to read in byte 1 */
+	instruction[1] = reg;
+	/* Send the read request */
+	retval = jl2005c_write2(gspca_dev, instruction);
+	if (retval < 0)
+		return retval;
+	retval = jl2005c_read1(gspca_dev);
+
+	return retval;
+}
+
+static int jl2005c_start_new_frame(struct gspca_dev *gspca_dev)
+{
+	int i;
+	int retval;
+	int frame_brightness = 0;
+
+	static u8 instruction[2] = {0x7f, 0x01};
+
+	retval = jl2005c_write2(gspca_dev, instruction);
+	if (retval < 0)
+		return retval;
+
+	i = 0;
+	while (i < 20 && !frame_brightness) {
+		/* If we tried 20 times, give up. */
+		retval = jl2005c_read_reg(gspca_dev, 0x7e);
+		if (retval < 0)
+			return retval;
+		frame_brightness = gspca_dev->usb_buf[0];
+		retval = jl2005c_read_reg(gspca_dev, 0x7d);
+		if (retval < 0)
+			return retval;
+		i++;
+	}
+	gspca_dbg(gspca_dev, D_FRAM, "frame_brightness is 0x%02x\n",
+		  gspca_dev->usb_buf[0]);
+	return retval;
+}
+
+static int jl2005c_write_reg(struct gspca_dev *gspca_dev, unsigned char reg,
+						    unsigned char value)
+{
+	int retval;
+	u8 instruction[2];
+
+	instruction[0] = reg;
+	instruction[1] = value;
+
+	retval = jl2005c_write2(gspca_dev, instruction);
+	if (retval < 0)
+			return retval;
+
+	return retval;
+}
+
+static int jl2005c_get_firmware_id(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *)gspca_dev;
+	int i = 0;
+	int retval = -1;
+	unsigned char regs_to_read[] = {0x57, 0x02, 0x03, 0x5d, 0x5e, 0x5f};
+
+	gspca_dbg(gspca_dev, D_PROBE, "Running jl2005c_get_firmware_id\n");
+	/* Read the first ID byte once for warmup */
+	retval = jl2005c_read_reg(gspca_dev, regs_to_read[0]);
+	gspca_dbg(gspca_dev, D_PROBE, "response is %02x\n",
+		  gspca_dev->usb_buf[0]);
+	if (retval < 0)
+		return retval;
+	/* Now actually get the ID string */
+	for (i = 0; i < 6; i++) {
+		retval = jl2005c_read_reg(gspca_dev, regs_to_read[i]);
+		if (retval < 0)
+			return retval;
+		sd->firmware_id[i] = gspca_dev->usb_buf[0];
+	}
+	gspca_dbg(gspca_dev, D_PROBE, "firmware ID is %02x%02x%02x%02x%02x%02x\n",
+		  sd->firmware_id[0],
+		  sd->firmware_id[1],
+		  sd->firmware_id[2],
+		  sd->firmware_id[3],
+		  sd->firmware_id[4],
+		  sd->firmware_id[5]);
+	return 0;
+}
+
+static int jl2005c_stream_start_vga_lg
+		    (struct gspca_dev *gspca_dev)
+{
+	int i;
+	int retval = -1;
+	static u8 instruction[][2] = {
+		{0x05, 0x00},
+		{0x7c, 0x00},
+		{0x7d, 0x18},
+		{0x02, 0x00},
+		{0x01, 0x00},
+		{0x04, 0x52},
+	};
+
+	for (i = 0; i < ARRAY_SIZE(instruction); i++) {
+		msleep(60);
+		retval = jl2005c_write2(gspca_dev, instruction[i]);
+		if (retval < 0)
+			return retval;
+	}
+	msleep(60);
+	return retval;
+}
+
+static int jl2005c_stream_start_vga_small(struct gspca_dev *gspca_dev)
+{
+	int i;
+	int retval = -1;
+	static u8 instruction[][2] = {
+		{0x06, 0x00},
+		{0x7c, 0x00},
+		{0x7d, 0x1a},
+		{0x02, 0x00},
+		{0x01, 0x00},
+		{0x04, 0x52},
+	};
+
+	for (i = 0; i < ARRAY_SIZE(instruction); i++) {
+		msleep(60);
+		retval = jl2005c_write2(gspca_dev, instruction[i]);
+		if (retval < 0)
+			return retval;
+	}
+	msleep(60);
+	return retval;
+}
+
+static int jl2005c_stream_start_cif_lg(struct gspca_dev *gspca_dev)
+{
+	int i;
+	int retval = -1;
+	static u8 instruction[][2] = {
+		{0x05, 0x00},
+		{0x7c, 0x00},
+		{0x7d, 0x30},
+		{0x02, 0x00},
+		{0x01, 0x00},
+		{0x04, 0x42},
+	};
+
+	for (i = 0; i < ARRAY_SIZE(instruction); i++) {
+		msleep(60);
+		retval = jl2005c_write2(gspca_dev, instruction[i]);
+		if (retval < 0)
+			return retval;
+	}
+	msleep(60);
+	return retval;
+}
+
+static int jl2005c_stream_start_cif_small(struct gspca_dev *gspca_dev)
+{
+	int i;
+	int retval = -1;
+	static u8 instruction[][2] = {
+		{0x06, 0x00},
+		{0x7c, 0x00},
+		{0x7d, 0x32},
+		{0x02, 0x00},
+		{0x01, 0x00},
+		{0x04, 0x42},
+	};
+
+	for (i = 0; i < ARRAY_SIZE(instruction); i++) {
+		msleep(60);
+		retval = jl2005c_write2(gspca_dev, instruction[i]);
+		if (retval < 0)
+			return retval;
+	}
+	msleep(60);
+	return retval;
+}
+
+
+static int jl2005c_stop(struct gspca_dev *gspca_dev)
+{
+	return jl2005c_write_reg(gspca_dev, 0x07, 0x00);
+}
+
+/*
+ * This function is called as a workqueue function and runs whenever the camera
+ * is streaming data. Because it is a workqueue function it is allowed to sleep
+ * so we can use synchronous USB calls. To avoid possible collisions with other
+ * threads attempting to use gspca_dev->usb_buf we take the usb_lock when
+ * performing USB operations using it. In practice we don't really need this
+ * as the camera doesn't provide any controls.
+ */
+static void jl2005c_dostream(struct work_struct *work)
+{
+	struct sd *dev = container_of(work, struct sd, work_struct);
+	struct gspca_dev *gspca_dev = &dev->gspca_dev;
+	int bytes_left = 0; /* bytes remaining in current frame. */
+	int data_len;   /* size to use for the next read. */
+	int header_read = 0;
+	unsigned char header_sig[2] = {0x4a, 0x4c};
+	int act_len;
+	int packet_type;
+	int ret;
+	u8 *buffer;
+
+	buffer = kmalloc(JL2005C_MAX_TRANSFER, GFP_KERNEL);
+	if (!buffer) {
+		pr_err("Couldn't allocate USB buffer\n");
+		goto quit_stream;
+	}
+
+	while (gspca_dev->present && gspca_dev->streaming) {
+#ifdef CONFIG_PM
+		if (gspca_dev->frozen)
+			break;
+#endif
+		/* Check if this is a new frame. If so, start the frame first */
+		if (!header_read) {
+			mutex_lock(&gspca_dev->usb_lock);
+			ret = jl2005c_start_new_frame(gspca_dev);
+			mutex_unlock(&gspca_dev->usb_lock);
+			if (ret < 0)
+				goto quit_stream;
+			ret = usb_bulk_msg(gspca_dev->dev,
+				usb_rcvbulkpipe(gspca_dev->dev, 0x82),
+				buffer, JL2005C_MAX_TRANSFER, &act_len,
+				JL2005C_DATA_TIMEOUT);
+			gspca_dbg(gspca_dev, D_PACK,
+				  "Got %d bytes out of %d for header\n",
+				  act_len, JL2005C_MAX_TRANSFER);
+			if (ret < 0 || act_len < JL2005C_MAX_TRANSFER)
+				goto quit_stream;
+			/* Check whether we actually got the first blodk */
+			if (memcmp(header_sig, buffer, 2) != 0) {
+				pr_err("First block is not the first block\n");
+				goto quit_stream;
+			}
+			/* total size to fetch is byte 7, times blocksize
+			 * of which we already got act_len */
+			bytes_left = buffer[0x07] * dev->block_size - act_len;
+			gspca_dbg(gspca_dev, D_PACK, "bytes_left = 0x%x\n",
+				  bytes_left);
+			/* We keep the header. It has other information, too.*/
+			packet_type = FIRST_PACKET;
+			gspca_frame_add(gspca_dev, packet_type,
+					buffer, act_len);
+			header_read = 1;
+		}
+		while (bytes_left > 0 && gspca_dev->present) {
+			data_len = bytes_left > JL2005C_MAX_TRANSFER ?
+				JL2005C_MAX_TRANSFER : bytes_left;
+			ret = usb_bulk_msg(gspca_dev->dev,
+				usb_rcvbulkpipe(gspca_dev->dev, 0x82),
+				buffer, data_len, &act_len,
+				JL2005C_DATA_TIMEOUT);
+			if (ret < 0 || act_len < data_len)
+				goto quit_stream;
+			gspca_dbg(gspca_dev, D_PACK,
+				  "Got %d bytes out of %d for frame\n",
+				  data_len, bytes_left);
+			bytes_left -= data_len;
+			if (bytes_left == 0) {
+				packet_type = LAST_PACKET;
+				header_read = 0;
+			} else
+				packet_type = INTER_PACKET;
+			gspca_frame_add(gspca_dev, packet_type,
+					buffer, data_len);
+		}
+	}
+quit_stream:
+	if (gspca_dev->present) {
+		mutex_lock(&gspca_dev->usb_lock);
+		jl2005c_stop(gspca_dev);
+		mutex_unlock(&gspca_dev->usb_lock);
+	}
+	kfree(buffer);
+}
+
+
+
+
+/* This function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	struct cam *cam;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	cam = &gspca_dev->cam;
+	/* We don't use the buffer gspca allocates so make it small. */
+	cam->bulk_size = 64;
+	cam->bulk = 1;
+	/* For the rest, the camera needs to be detected */
+	jl2005c_get_firmware_id(gspca_dev);
+	/* Here are some known firmware IDs
+	 * First some JL2005B cameras
+	 * {0x41, 0x07, 0x04, 0x2c, 0xe8, 0xf2}	Sakar KidzCam
+	 * {0x45, 0x02, 0x08, 0xb9, 0x00, 0xd2}	No-name JL2005B
+	 * JL2005C cameras
+	 * {0x01, 0x0c, 0x16, 0x10, 0xf8, 0xc8}	Argus DC-1512
+	 * {0x12, 0x04, 0x03, 0xc0, 0x00, 0xd8}	ICarly
+	 * {0x86, 0x08, 0x05, 0x02, 0x00, 0xd4}	Jazz
+	 *
+	 * Based upon this scanty evidence, we can detect a CIF camera by
+	 * testing byte 0 for 0x4x.
+	 */
+	if ((sd->firmware_id[0] & 0xf0) == 0x40) {
+		cam->cam_mode	= cif_mode;
+		cam->nmodes	= ARRAY_SIZE(cif_mode);
+		sd->block_size	= 0x80;
+	} else {
+		cam->cam_mode	= vga_mode;
+		cam->nmodes	= ARRAY_SIZE(vga_mode);
+		sd->block_size	= 0x200;
+	}
+
+	INIT_WORK(&sd->work_struct, jl2005c_dostream);
+
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	return 0;
+}
+
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+
+	struct sd *sd = (struct sd *) gspca_dev;
+	sd->cap_mode = gspca_dev->cam.cam_mode;
+
+	switch (gspca_dev->pixfmt.width) {
+	case 640:
+		gspca_dbg(gspca_dev, D_STREAM, "Start streaming at vga resolution\n");
+		jl2005c_stream_start_vga_lg(gspca_dev);
+		break;
+	case 320:
+		gspca_dbg(gspca_dev, D_STREAM, "Start streaming at qvga resolution\n");
+		jl2005c_stream_start_vga_small(gspca_dev);
+		break;
+	case 352:
+		gspca_dbg(gspca_dev, D_STREAM, "Start streaming at cif resolution\n");
+		jl2005c_stream_start_cif_lg(gspca_dev);
+		break;
+	case 176:
+		gspca_dbg(gspca_dev, D_STREAM, "Start streaming at qcif resolution\n");
+		jl2005c_stream_start_cif_small(gspca_dev);
+		break;
+	default:
+		pr_err("Unknown resolution specified\n");
+		return -1;
+	}
+
+	schedule_work(&sd->work_struct);
+
+	return 0;
+}
+
+/* called on streamoff with alt==0 and on disconnect */
+/* the usb_lock is held at entry - restore on exit */
+static void sd_stop0(struct gspca_dev *gspca_dev)
+{
+	struct sd *dev = (struct sd *) gspca_dev;
+
+	/* wait for the work queue to terminate */
+	mutex_unlock(&gspca_dev->usb_lock);
+	/* This waits for sq905c_dostream to finish */
+	flush_work(&dev->work_struct);
+	mutex_lock(&gspca_dev->usb_lock);
+}
+
+
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.start = sd_start,
+	.stop0 = sd_stop0,
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x0979, 0x0227)},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+				const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/jpeg.h b/drivers/media/usb/gspca/jpeg.h
new file mode 100644
index 0000000..d5ad7c9
--- /dev/null
+++ b/drivers/media/usb/gspca/jpeg.h
@@ -0,0 +1,166 @@
+#ifndef JPEG_H
+#define JPEG_H 1
+/*
+ * Insert a JPEG header at start of frame
+ *
+ * This module is used by the gspca subdrivers.
+ * A special case is done for Conexant webcams.
+ *
+ * Copyright (C) Jean-Francois Moine (http://moinejf.free.fr)
+ *
+ * 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
+ * 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.
+ *
+ */
+
+/*
+ * generation options
+ *	CONEX_CAM	Conexant if present
+ */
+
+/* JPEG header */
+static const u8 jpeg_head[] = {
+	0xff, 0xd8,			/* jpeg */
+
+/* quantization table quality 50% */
+	0xff, 0xdb, 0x00, 0x84,		/* DQT */
+0,
+#define JPEG_QT0_OFFSET 7
+	0x10, 0x0b, 0x0c, 0x0e, 0x0c, 0x0a, 0x10, 0x0e,
+	0x0d, 0x0e, 0x12, 0x11, 0x10, 0x13, 0x18, 0x28,
+	0x1a, 0x18, 0x16, 0x16, 0x18, 0x31, 0x23, 0x25,
+	0x1d, 0x28, 0x3a, 0x33, 0x3d, 0x3c, 0x39, 0x33,
+	0x38, 0x37, 0x40, 0x48, 0x5c, 0x4e, 0x40, 0x44,
+	0x57, 0x45, 0x37, 0x38, 0x50, 0x6d, 0x51, 0x57,
+	0x5f, 0x62, 0x67, 0x68, 0x67, 0x3e, 0x4d, 0x71,
+	0x79, 0x70, 0x64, 0x78, 0x5c, 0x65, 0x67, 0x63,
+1,
+#define JPEG_QT1_OFFSET 72
+	0x11, 0x12, 0x12, 0x18, 0x15, 0x18, 0x2f, 0x1a,
+	0x1a, 0x2f, 0x63, 0x42, 0x38, 0x42, 0x63, 0x63,
+	0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+	0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+	0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+	0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+	0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+	0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+
+/* huffman table */
+	0xff, 0xc4, 0x01, 0xa2,
+	0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01,
+	0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+	0x07, 0x08, 0x09, 0x0a, 0x0b, 0x01, 0x00, 0x03,
+	0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+	0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+	0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+	0x0a, 0x0b, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03,
+	0x02, 0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00,
+	0x00, 0x01, 0x7d, 0x01, 0x02, 0x03, 0x00, 0x04,
+	0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13,
+	0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81,
+	0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15,
+	0x52, 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, 0x82,
+	0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25,
+	0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36,
+	0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46,
+	0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56,
+	0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66,
+	0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76,
+	0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86,
+	0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95,
+	0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4,
+	0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3,
+	0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2,
+	0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca,
+	0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9,
+	0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
+	0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5,
+	0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0x11, 0x00, 0x02,
+	0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05,
+	0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01,
+	0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06,
+	0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, 0x22,
+	0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1,
+	0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 0x15, 0x62,
+	0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25,
+	0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, 0x27, 0x28,
+	0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a,
+	0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a,
+	0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a,
+	0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a,
+	0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
+	0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
+	0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98,
+	0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
+	0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
+	0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5,
+	0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4,
+	0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3,
+	0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2,
+	0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa,
+#ifdef CONEX_CAM
+/* the Conexant frames start with SOF0 */
+#define JPEG_HDR_SZ 556
+#else
+	0xff, 0xc0, 0x00, 0x11,		/* SOF0 (start of frame 0 */
+	0x08,				/* data precision */
+#define JPEG_HEIGHT_OFFSET 561
+	0x01, 0xe0,			/* height */
+	0x02, 0x80,			/* width */
+	0x03,				/* component number */
+		0x01,
+			0x21,		/* samples Y */
+			0x00,		/* quant Y */
+		0x02, 0x11, 0x01,	/* samples CbCr - quant CbCr */
+		0x03, 0x11, 0x01,
+
+	0xff, 0xda, 0x00, 0x0c,		/* SOS (start of scan) */
+	0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00
+#define JPEG_HDR_SZ 589
+#endif
+};
+
+/* define the JPEG header */
+static void jpeg_define(u8 *jpeg_hdr,
+			int height,
+			int width,
+			int samplesY)
+{
+	memcpy(jpeg_hdr, jpeg_head, sizeof jpeg_head);
+#ifndef CONEX_CAM
+	jpeg_hdr[JPEG_HEIGHT_OFFSET + 0] = height >> 8;
+	jpeg_hdr[JPEG_HEIGHT_OFFSET + 1] = height;
+	jpeg_hdr[JPEG_HEIGHT_OFFSET + 2] = width >> 8;
+	jpeg_hdr[JPEG_HEIGHT_OFFSET + 3] = width;
+	jpeg_hdr[JPEG_HEIGHT_OFFSET + 6] = samplesY;
+#endif
+}
+
+/* set the JPEG quality */
+static void jpeg_set_qual(u8 *jpeg_hdr,
+			  int quality)
+{
+	int i, sc;
+
+	if (quality <= 0)
+		sc = 5000;
+	else if (quality < 50)
+		sc = 5000 / quality;
+	else
+		sc = 200 - quality * 2;
+	for (i = 0; i < 64; i++) {
+		jpeg_hdr[JPEG_QT0_OFFSET + i] =
+			(jpeg_head[JPEG_QT0_OFFSET + i] * sc + 50) / 100;
+		jpeg_hdr[JPEG_QT1_OFFSET + i] =
+			(jpeg_head[JPEG_QT1_OFFSET + i] * sc + 50) / 100;
+	}
+}
+#endif
diff --git a/drivers/media/usb/gspca/kinect.c b/drivers/media/usb/gspca/kinect.c
new file mode 100644
index 0000000..f993f62
--- /dev/null
+++ b/drivers/media/usb/gspca/kinect.c
@@ -0,0 +1,483 @@
+/*
+ * kinect sensor device camera, gspca driver
+ *
+ * Copyright (C) 2011  Antonio Ospite <ospite@studenti.unina.it>
+ *
+ * Based on the OpenKinect project and libfreenect
+ * http://openkinect.org/wiki/Init_Analysis
+ *
+ * Special thanks to Steven Toth and kernellabs.com for sponsoring a Kinect
+ * sensor device which I tested the driver on.
+ *
+ * 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
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "kinect"
+
+#include "gspca.h"
+
+#define CTRL_TIMEOUT 500
+
+MODULE_AUTHOR("Antonio Ospite <ospite@studenti.unina.it>");
+MODULE_DESCRIPTION("GSPCA/Kinect Sensor Device USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+static bool depth_mode;
+
+struct pkt_hdr {
+	uint8_t magic[2];
+	uint8_t pad;
+	uint8_t flag;
+	uint8_t unk1;
+	uint8_t seq;
+	uint8_t unk2;
+	uint8_t unk3;
+	uint32_t timestamp;
+};
+
+struct cam_hdr {
+	uint8_t magic[2];
+	__le16 len;
+	__le16 cmd;
+	__le16 tag;
+};
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev; /* !! must be the first item */
+	uint16_t cam_tag;           /* a sequence number for packets */
+	uint8_t stream_flag;        /* to identify different stream types */
+	uint8_t obuf[0x400];        /* output buffer for control commands */
+	uint8_t ibuf[0x200];        /* input buffer for control commands */
+};
+
+#define MODE_640x480   0x0001
+#define MODE_640x488   0x0002
+#define MODE_1280x1024 0x0004
+
+#define FORMAT_BAYER   0x0010
+#define FORMAT_UYVY    0x0020
+#define FORMAT_Y10B    0x0040
+
+#define FPS_HIGH       0x0100
+
+static const struct v4l2_pix_format depth_camera_mode[] = {
+	{640, 480, V4L2_PIX_FMT_Y10BPACK, V4L2_FIELD_NONE,
+	 .bytesperline = 640 * 10 / 8,
+	 .sizeimage =  640 * 480 * 10 / 8,
+	 .colorspace = V4L2_COLORSPACE_SRGB,
+	 .priv = MODE_640x488 | FORMAT_Y10B},
+};
+
+static const struct v4l2_pix_format video_camera_mode[] = {
+	{640, 480, V4L2_PIX_FMT_SGRBG8, V4L2_FIELD_NONE,
+	 .bytesperline = 640,
+	 .sizeimage = 640 * 480,
+	 .colorspace = V4L2_COLORSPACE_SRGB,
+	 .priv = MODE_640x480 | FORMAT_BAYER | FPS_HIGH},
+	{640, 480, V4L2_PIX_FMT_UYVY, V4L2_FIELD_NONE,
+	 .bytesperline = 640 * 2,
+	 .sizeimage = 640 * 480 * 2,
+	 .colorspace = V4L2_COLORSPACE_SRGB,
+	 .priv = MODE_640x480 | FORMAT_UYVY},
+	{1280, 1024, V4L2_PIX_FMT_SGRBG8, V4L2_FIELD_NONE,
+	 .bytesperline = 1280,
+	 .sizeimage = 1280 * 1024,
+	 .colorspace = V4L2_COLORSPACE_SRGB,
+	 .priv = MODE_1280x1024 | FORMAT_BAYER},
+	{640, 488, V4L2_PIX_FMT_Y10BPACK, V4L2_FIELD_NONE,
+	 .bytesperline = 640 * 10 / 8,
+	 .sizeimage =  640 * 488 * 10 / 8,
+	 .colorspace = V4L2_COLORSPACE_SRGB,
+	 .priv = MODE_640x488 | FORMAT_Y10B | FPS_HIGH},
+	{1280, 1024, V4L2_PIX_FMT_Y10BPACK, V4L2_FIELD_NONE,
+	 .bytesperline = 1280 * 10 / 8,
+	 .sizeimage =  1280 * 1024 * 10 / 8,
+	 .colorspace = V4L2_COLORSPACE_SRGB,
+	 .priv = MODE_1280x1024 | FORMAT_Y10B},
+};
+
+static int kinect_write(struct usb_device *udev, uint8_t *data,
+			uint16_t wLength)
+{
+	return usb_control_msg(udev,
+			      usb_sndctrlpipe(udev, 0),
+			      0x00,
+			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      0, 0, data, wLength, CTRL_TIMEOUT);
+}
+
+static int kinect_read(struct usb_device *udev, uint8_t *data, uint16_t wLength)
+{
+	return usb_control_msg(udev,
+			      usb_rcvctrlpipe(udev, 0),
+			      0x00,
+			      USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      0, 0, data, wLength, CTRL_TIMEOUT);
+}
+
+static int send_cmd(struct gspca_dev *gspca_dev, uint16_t cmd, void *cmdbuf,
+		unsigned int cmd_len, void *replybuf, unsigned int reply_len)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct usb_device *udev = gspca_dev->dev;
+	int res, actual_len;
+	uint8_t *obuf = sd->obuf;
+	uint8_t *ibuf = sd->ibuf;
+	struct cam_hdr *chdr = (void *)obuf;
+	struct cam_hdr *rhdr = (void *)ibuf;
+
+	if (cmd_len & 1 || cmd_len > (0x400 - sizeof(*chdr))) {
+		pr_err("send_cmd: Invalid command length (0x%x)\n", cmd_len);
+		return -1;
+	}
+
+	chdr->magic[0] = 0x47;
+	chdr->magic[1] = 0x4d;
+	chdr->cmd = cpu_to_le16(cmd);
+	chdr->tag = cpu_to_le16(sd->cam_tag);
+	chdr->len = cpu_to_le16(cmd_len / 2);
+
+	memcpy(obuf+sizeof(*chdr), cmdbuf, cmd_len);
+
+	res = kinect_write(udev, obuf, cmd_len + sizeof(*chdr));
+	gspca_dbg(gspca_dev, D_USBO, "Control cmd=%04x tag=%04x len=%04x: %d\n",
+		  cmd,
+		  sd->cam_tag, cmd_len, res);
+	if (res < 0) {
+		pr_err("send_cmd: Output control transfer failed (%d)\n", res);
+		return res;
+	}
+
+	do {
+		actual_len = kinect_read(udev, ibuf, 0x200);
+	} while (actual_len == 0);
+	gspca_dbg(gspca_dev, D_USBO, "Control reply: %d\n", actual_len);
+	if (actual_len < (int)sizeof(*rhdr)) {
+		pr_err("send_cmd: Input control transfer failed (%d)\n",
+		       actual_len);
+		return actual_len < 0 ? actual_len : -EREMOTEIO;
+	}
+	actual_len -= sizeof(*rhdr);
+
+	if (rhdr->magic[0] != 0x52 || rhdr->magic[1] != 0x42) {
+		pr_err("send_cmd: Bad magic %02x %02x\n",
+		       rhdr->magic[0], rhdr->magic[1]);
+		return -1;
+	}
+	if (rhdr->cmd != chdr->cmd) {
+		pr_err("send_cmd: Bad cmd %02x != %02x\n",
+		       rhdr->cmd, chdr->cmd);
+		return -1;
+	}
+	if (rhdr->tag != chdr->tag) {
+		pr_err("send_cmd: Bad tag %04x != %04x\n",
+		       rhdr->tag, chdr->tag);
+		return -1;
+	}
+	if (le16_to_cpu(rhdr->len) != (actual_len/2)) {
+		pr_err("send_cmd: Bad len %04x != %04x\n",
+		       le16_to_cpu(rhdr->len), (int)(actual_len/2));
+		return -1;
+	}
+
+	if (actual_len > reply_len) {
+		pr_warn("send_cmd: Data buffer is %d bytes long, but got %d bytes\n",
+			reply_len, actual_len);
+		memcpy(replybuf, ibuf+sizeof(*rhdr), reply_len);
+	} else {
+		memcpy(replybuf, ibuf+sizeof(*rhdr), actual_len);
+	}
+
+	sd->cam_tag++;
+
+	return actual_len;
+}
+
+static int write_register(struct gspca_dev *gspca_dev, uint16_t reg,
+			uint16_t data)
+{
+	uint16_t reply[2];
+	__le16 cmd[2];
+	int res;
+
+	cmd[0] = cpu_to_le16(reg);
+	cmd[1] = cpu_to_le16(data);
+
+	gspca_dbg(gspca_dev, D_USBO, "Write Reg 0x%04x <= 0x%02x\n", reg, data);
+	res = send_cmd(gspca_dev, 0x03, cmd, 4, reply, 4);
+	if (res < 0)
+		return res;
+	if (res != 2) {
+		pr_warn("send_cmd returned %d [%04x %04x], 0000 expected\n",
+			res, reply[0], reply[1]);
+	}
+	return 0;
+}
+
+/* this function is called at probe time */
+static int sd_config_video(struct gspca_dev *gspca_dev,
+		     const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam;
+
+	sd->cam_tag = 0;
+
+	sd->stream_flag = 0x80;
+
+	cam = &gspca_dev->cam;
+
+	cam->cam_mode = video_camera_mode;
+	cam->nmodes = ARRAY_SIZE(video_camera_mode);
+
+	gspca_dev->xfer_ep = 0x81;
+
+#if 0
+	/* Setting those values is not needed for video stream */
+	cam->npkt = 15;
+	gspca_dev->pkt_size = 960 * 2;
+#endif
+
+	return 0;
+}
+
+static int sd_config_depth(struct gspca_dev *gspca_dev,
+		     const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam;
+
+	sd->cam_tag = 0;
+
+	sd->stream_flag = 0x70;
+
+	cam = &gspca_dev->cam;
+
+	cam->cam_mode = depth_camera_mode;
+	cam->nmodes = ARRAY_SIZE(depth_camera_mode);
+
+	gspca_dev->xfer_ep = 0x82;
+
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	gspca_dbg(gspca_dev, D_PROBE, "Kinect Camera device.\n");
+
+	return 0;
+}
+
+static int sd_start_video(struct gspca_dev *gspca_dev)
+{
+	int mode;
+	uint8_t fmt_reg, fmt_val;
+	uint8_t res_reg, res_val;
+	uint8_t fps_reg, fps_val;
+	uint8_t mode_val;
+
+	mode = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].priv;
+
+	if (mode & FORMAT_Y10B) {
+		fmt_reg = 0x19;
+		res_reg = 0x1a;
+		fps_reg = 0x1b;
+		mode_val = 0x03;
+	} else {
+		fmt_reg = 0x0c;
+		res_reg = 0x0d;
+		fps_reg = 0x0e;
+		mode_val = 0x01;
+	}
+
+	/* format */
+	if (mode & FORMAT_UYVY)
+		fmt_val = 0x05;
+	else
+		fmt_val = 0x00;
+
+	if (mode & MODE_1280x1024)
+		res_val = 0x02;
+	else
+		res_val = 0x01;
+
+	if (mode & FPS_HIGH)
+		fps_val = 0x1e;
+	else
+		fps_val = 0x0f;
+
+
+	/* turn off IR-reset function */
+	write_register(gspca_dev, 0x105, 0x00);
+
+	/* Reset video stream */
+	write_register(gspca_dev, 0x05, 0x00);
+
+	/* Due to some ridiculous condition in the firmware, we have to start
+	 * and stop the depth stream before the camera will hand us 1280x1024
+	 * IR.  This is a stupid workaround, but we've yet to find a better
+	 * solution.
+	 *
+	 * Thanks to Drew Fisher for figuring this out.
+	 */
+	if (mode & (FORMAT_Y10B | MODE_1280x1024)) {
+		write_register(gspca_dev, 0x13, 0x01);
+		write_register(gspca_dev, 0x14, 0x1e);
+		write_register(gspca_dev, 0x06, 0x02);
+		write_register(gspca_dev, 0x06, 0x00);
+	}
+
+	write_register(gspca_dev, fmt_reg, fmt_val);
+	write_register(gspca_dev, res_reg, res_val);
+	write_register(gspca_dev, fps_reg, fps_val);
+
+	/* Start video stream */
+	write_register(gspca_dev, 0x05, mode_val);
+
+	/* disable Hflip */
+	write_register(gspca_dev, 0x47, 0x00);
+
+	return 0;
+}
+
+static int sd_start_depth(struct gspca_dev *gspca_dev)
+{
+	/* turn off IR-reset function */
+	write_register(gspca_dev, 0x105, 0x00);
+
+	/* reset depth stream */
+	write_register(gspca_dev, 0x06, 0x00);
+	/* Depth Stream Format 0x03: 11 bit stream | 0x02: 10 bit */
+	write_register(gspca_dev, 0x12, 0x02);
+	/* Depth Stream Resolution 1: standard (640x480) */
+	write_register(gspca_dev, 0x13, 0x01);
+	/* Depth Framerate / 0x1e (30): 30 fps */
+	write_register(gspca_dev, 0x14, 0x1e);
+	/* Depth Stream Control  / 2: Open Depth Stream */
+	write_register(gspca_dev, 0x06, 0x02);
+	/* disable depth hflip / LSB = 0: Smoothing Disabled */
+	write_register(gspca_dev, 0x17, 0x00);
+
+	return 0;
+}
+
+static void sd_stopN_video(struct gspca_dev *gspca_dev)
+{
+	/* reset video stream */
+	write_register(gspca_dev, 0x05, 0x00);
+}
+
+static void sd_stopN_depth(struct gspca_dev *gspca_dev)
+{
+	/* reset depth stream */
+	write_register(gspca_dev, 0x06, 0x00);
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev, u8 *__data, int len)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	struct pkt_hdr *hdr = (void *)__data;
+	uint8_t *data = __data + sizeof(*hdr);
+	int datalen = len - sizeof(*hdr);
+
+	uint8_t sof = sd->stream_flag | 1;
+	uint8_t mof = sd->stream_flag | 2;
+	uint8_t eof = sd->stream_flag | 5;
+
+	if (len < 12)
+		return;
+
+	if (hdr->magic[0] != 'R' || hdr->magic[1] != 'B') {
+		pr_warn("[Stream %02x] Invalid magic %02x%02x\n",
+			sd->stream_flag, hdr->magic[0], hdr->magic[1]);
+		return;
+	}
+
+	if (hdr->flag == sof)
+		gspca_frame_add(gspca_dev, FIRST_PACKET, data, datalen);
+
+	else if (hdr->flag == mof)
+		gspca_frame_add(gspca_dev, INTER_PACKET, data, datalen);
+
+	else if (hdr->flag == eof)
+		gspca_frame_add(gspca_dev, LAST_PACKET, data, datalen);
+
+	else
+		pr_warn("Packet type not recognized...\n");
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc_video = {
+	.name      = MODULE_NAME,
+	.config    = sd_config_video,
+	.init      = sd_init,
+	.start     = sd_start_video,
+	.stopN     = sd_stopN_video,
+	.pkt_scan  = sd_pkt_scan,
+	/*
+	.get_streamparm = sd_get_streamparm,
+	.set_streamparm = sd_set_streamparm,
+	*/
+};
+static const struct sd_desc sd_desc_depth = {
+	.name      = MODULE_NAME,
+	.config    = sd_config_depth,
+	.init      = sd_init,
+	.start     = sd_start_depth,
+	.stopN     = sd_stopN_depth,
+	.pkt_scan  = sd_pkt_scan,
+	/*
+	.get_streamparm = sd_get_streamparm,
+	.set_streamparm = sd_set_streamparm,
+	*/
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x045e, 0x02ae)},
+	{USB_DEVICE(0x045e, 0x02bf)},
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+	if (depth_mode)
+		return gspca_dev_probe(intf, id, &sd_desc_depth,
+				       sizeof(struct sd), THIS_MODULE);
+	else
+		return gspca_dev_probe(intf, id, &sd_desc_video,
+				       sizeof(struct sd), THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name       = MODULE_NAME,
+	.id_table   = device_table,
+	.probe      = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend    = gspca_suspend,
+	.resume     = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
+
+module_param(depth_mode, bool, 0644);
+MODULE_PARM_DESC(depth_mode, "0=video 1=depth");
diff --git a/drivers/media/usb/gspca/konica.c b/drivers/media/usb/gspca/konica.c
new file mode 100644
index 0000000..989ae99
--- /dev/null
+++ b/drivers/media/usb/gspca/konica.c
@@ -0,0 +1,482 @@
+/*
+ * Driver for USB webcams based on Konica chipset. This
+ * chipset is used in Intel YC76 camera.
+ *
+ * Copyright (C) 2010 Hans de Goede <hdegoede@redhat.com>
+ *
+ * Based on the usbvideo v4l1 konicawc driver which is:
+ *
+ * Copyright (C) 2002 Simon Evans <spse@secret.org.uk>
+ *
+ * The code for making gspca work with a webcam with 2 isoc endpoints was
+ * taken from the benq gspca subdriver which is:
+ *
+ * Copyright (C) 2009 Jean-Francois Moine (http://moinejf.free.fr)
+ *
+ * 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
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "konica"
+
+#include <linux/input.h>
+#include "gspca.h"
+
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_DESCRIPTION("Konica chipset USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+#define WHITEBAL_REG   0x01
+#define BRIGHTNESS_REG 0x02
+#define SHARPNESS_REG  0x03
+#define CONTRAST_REG   0x04
+#define SATURATION_REG 0x05
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+	struct urb *last_data_urb;
+	u8 snapshot_pressed;
+};
+
+
+/* .priv is what goes to register 8 for this mode, known working values:
+   0x00 -> 176x144, cropped
+   0x01 -> 176x144, cropped
+   0x02 -> 176x144, cropped
+   0x03 -> 176x144, cropped
+   0x04 -> 176x144, binned
+   0x05 -> 320x240
+   0x06 -> 320x240
+   0x07 -> 160x120, cropped
+   0x08 -> 160x120, cropped
+   0x09 -> 160x120, binned (note has 136 lines)
+   0x0a -> 160x120, binned (note has 136 lines)
+   0x0b -> 160x120, cropped
+*/
+static const struct v4l2_pix_format vga_mode[] = {
+	{160, 120, V4L2_PIX_FMT_KONICA420, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 160 * 136 * 3 / 2 + 960,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0x0a},
+	{176, 144, V4L2_PIX_FMT_KONICA420, V4L2_FIELD_NONE,
+		.bytesperline = 176,
+		.sizeimage = 176 * 144 * 3 / 2 + 960,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0x04},
+	{320, 240, V4L2_PIX_FMT_KONICA420, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3 / 2 + 960,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0x05},
+};
+
+static void sd_isoc_irq(struct urb *urb);
+
+static void reg_w(struct gspca_dev *gspca_dev, u16 value, u16 index)
+{
+	struct usb_device *dev = gspca_dev->dev;
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+			0x02,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			value,
+			index,
+			NULL,
+			0,
+			1000);
+	if (ret < 0) {
+		pr_err("reg_w err writing %02x to %02x: %d\n",
+		       value, index, ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+static void reg_r(struct gspca_dev *gspca_dev, u16 value, u16 index)
+{
+	struct usb_device *dev = gspca_dev->dev;
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+			0x03,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			value,
+			index,
+			gspca_dev->usb_buf,
+			2,
+			1000);
+	if (ret < 0) {
+		pr_err("reg_r err %d\n", ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+static void konica_stream_on(struct gspca_dev *gspca_dev)
+{
+	reg_w(gspca_dev, 1, 0x0b);
+}
+
+static void konica_stream_off(struct gspca_dev *gspca_dev)
+{
+	reg_w(gspca_dev, 0, 0x0b);
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	gspca_dev->cam.cam_mode = vga_mode;
+	gspca_dev->cam.nmodes = ARRAY_SIZE(vga_mode);
+	gspca_dev->cam.no_urb_create = 1;
+
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	int i;
+
+	/*
+	 * The konica needs a freaking large time to "boot" (approx 6.5 sec.),
+	 * and does not want to be bothered while doing so :|
+	 * Register 0x10 counts from 1 - 3, with 3 being "ready"
+	 */
+	msleep(6000);
+	for (i = 0; i < 20; i++) {
+		reg_r(gspca_dev, 0, 0x10);
+		if (gspca_dev->usb_buf[0] == 3)
+			break;
+		msleep(100);
+	}
+	reg_w(gspca_dev, 0, 0x0d);
+
+	return gspca_dev->usb_err;
+}
+
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct urb *urb;
+	int i, n, packet_size;
+	struct usb_host_interface *alt;
+	struct usb_interface *intf;
+
+	intf = usb_ifnum_to_if(sd->gspca_dev.dev, sd->gspca_dev.iface);
+	alt = usb_altnum_to_altsetting(intf, sd->gspca_dev.alt);
+	if (!alt) {
+		pr_err("Couldn't get altsetting\n");
+		return -EIO;
+	}
+
+	if (alt->desc.bNumEndpoints < 2)
+		return -ENODEV;
+
+	packet_size = le16_to_cpu(alt->endpoint[0].desc.wMaxPacketSize);
+
+	n = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].priv;
+	reg_w(gspca_dev, n, 0x08);
+
+	konica_stream_on(gspca_dev);
+
+	if (gspca_dev->usb_err)
+		return gspca_dev->usb_err;
+
+	/* create 4 URBs - 2 on endpoint 0x83 and 2 on 0x082 */
+#if MAX_NURBS < 4
+#error "Not enough URBs in the gspca table"
+#endif
+#define SD_NPKT 32
+	for (n = 0; n < 4; n++) {
+		i = n & 1 ? 0 : 1;
+		packet_size =
+			le16_to_cpu(alt->endpoint[i].desc.wMaxPacketSize);
+		urb = usb_alloc_urb(SD_NPKT, GFP_KERNEL);
+		if (!urb)
+			return -ENOMEM;
+		gspca_dev->urb[n] = urb;
+		urb->transfer_buffer = usb_alloc_coherent(gspca_dev->dev,
+						packet_size * SD_NPKT,
+						GFP_KERNEL,
+						&urb->transfer_dma);
+		if (urb->transfer_buffer == NULL) {
+			pr_err("usb_buffer_alloc failed\n");
+			return -ENOMEM;
+		}
+
+		urb->dev = gspca_dev->dev;
+		urb->context = gspca_dev;
+		urb->transfer_buffer_length = packet_size * SD_NPKT;
+		urb->pipe = usb_rcvisocpipe(gspca_dev->dev,
+					n & 1 ? 0x81 : 0x82);
+		urb->transfer_flags = URB_ISO_ASAP
+					| URB_NO_TRANSFER_DMA_MAP;
+		urb->interval = 1;
+		urb->complete = sd_isoc_irq;
+		urb->number_of_packets = SD_NPKT;
+		for (i = 0; i < SD_NPKT; i++) {
+			urb->iso_frame_desc[i].length = packet_size;
+			urb->iso_frame_desc[i].offset = packet_size * i;
+		}
+	}
+
+	return 0;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd __maybe_unused = (struct sd *) gspca_dev;
+
+	konica_stream_off(gspca_dev);
+#if IS_ENABLED(CONFIG_INPUT)
+	/* Don't keep the button in the pressed state "forever" if it was
+	   pressed when streaming is stopped */
+	if (sd->snapshot_pressed) {
+		input_report_key(gspca_dev->input_dev, KEY_CAMERA, 0);
+		input_sync(gspca_dev->input_dev);
+		sd->snapshot_pressed = 0;
+	}
+#endif
+}
+
+/* reception of an URB */
+static void sd_isoc_irq(struct urb *urb)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *) urb->context;
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct urb *data_urb, *status_urb;
+	u8 *data;
+	int i, st;
+
+	gspca_dbg(gspca_dev, D_PACK, "sd isoc irq\n");
+	if (!gspca_dev->streaming)
+		return;
+
+	if (urb->status != 0) {
+		if (urb->status == -ESHUTDOWN)
+			return;		/* disconnection */
+#ifdef CONFIG_PM
+		if (gspca_dev->frozen)
+			return;
+#endif
+		gspca_err(gspca_dev, "urb status: %d\n", urb->status);
+		st = usb_submit_urb(urb, GFP_ATOMIC);
+		if (st < 0)
+			pr_err("resubmit urb error %d\n", st);
+		return;
+	}
+
+	/* if this is a data URB (ep 0x82), wait */
+	if (urb->transfer_buffer_length > 32) {
+		sd->last_data_urb = urb;
+		return;
+	}
+
+	status_urb = urb;
+	data_urb   = sd->last_data_urb;
+	sd->last_data_urb = NULL;
+
+	if (!data_urb || data_urb->start_frame != status_urb->start_frame) {
+		gspca_err(gspca_dev, "lost sync on frames\n");
+		goto resubmit;
+	}
+
+	if (data_urb->number_of_packets != status_urb->number_of_packets) {
+		gspca_err(gspca_dev, "no packets does not match, data: %d, status: %d\n",
+			  data_urb->number_of_packets,
+			  status_urb->number_of_packets);
+		goto resubmit;
+	}
+
+	for (i = 0; i < status_urb->number_of_packets; i++) {
+		if (data_urb->iso_frame_desc[i].status ||
+		    status_urb->iso_frame_desc[i].status) {
+			gspca_err(gspca_dev, "pkt %d data-status %d, status-status %d\n",
+				  i,
+				  data_urb->iso_frame_desc[i].status,
+				  status_urb->iso_frame_desc[i].status);
+			gspca_dev->last_packet_type = DISCARD_PACKET;
+			continue;
+		}
+
+		if (status_urb->iso_frame_desc[i].actual_length != 1) {
+			gspca_err(gspca_dev, "bad status packet length %d\n",
+				  status_urb->iso_frame_desc[i].actual_length);
+			gspca_dev->last_packet_type = DISCARD_PACKET;
+			continue;
+		}
+
+		st = *((u8 *)status_urb->transfer_buffer
+				+ status_urb->iso_frame_desc[i].offset);
+
+		data = (u8 *)data_urb->transfer_buffer
+				+ data_urb->iso_frame_desc[i].offset;
+
+		/* st: 0x80-0xff: frame start with frame number (ie 0-7f)
+		 * otherwise:
+		 * bit 0 0: keep packet
+		 *	 1: drop packet (padding data)
+		 *
+		 * bit 4 0 button not clicked
+		 *       1 button clicked
+		 * button is used to `take a picture' (in software)
+		 */
+		if (st & 0x80) {
+			gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
+			gspca_frame_add(gspca_dev, FIRST_PACKET, NULL, 0);
+		} else {
+#if IS_ENABLED(CONFIG_INPUT)
+			u8 button_state = st & 0x40 ? 1 : 0;
+			if (sd->snapshot_pressed != button_state) {
+				input_report_key(gspca_dev->input_dev,
+						 KEY_CAMERA,
+						 button_state);
+				input_sync(gspca_dev->input_dev);
+				sd->snapshot_pressed = button_state;
+			}
+#endif
+			if (st & 0x01)
+				continue;
+		}
+		gspca_frame_add(gspca_dev, INTER_PACKET, data,
+				data_urb->iso_frame_desc[i].actual_length);
+	}
+
+resubmit:
+	if (data_urb) {
+		st = usb_submit_urb(data_urb, GFP_ATOMIC);
+		if (st < 0)
+			gspca_err(gspca_dev, "usb_submit_urb(data_urb) ret %d\n",
+				  st);
+	}
+	st = usb_submit_urb(status_urb, GFP_ATOMIC);
+	if (st < 0)
+		gspca_err(gspca_dev, "usb_submit_urb(status_urb) ret %d\n", st);
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		konica_stream_off(gspca_dev);
+		reg_w(gspca_dev, ctrl->val, BRIGHTNESS_REG);
+		konica_stream_on(gspca_dev);
+		break;
+	case V4L2_CID_CONTRAST:
+		konica_stream_off(gspca_dev);
+		reg_w(gspca_dev, ctrl->val, CONTRAST_REG);
+		konica_stream_on(gspca_dev);
+		break;
+	case V4L2_CID_SATURATION:
+		konica_stream_off(gspca_dev);
+		reg_w(gspca_dev, ctrl->val, SATURATION_REG);
+		konica_stream_on(gspca_dev);
+		break;
+	case V4L2_CID_WHITE_BALANCE_TEMPERATURE:
+		konica_stream_off(gspca_dev);
+		reg_w(gspca_dev, ctrl->val, WHITEBAL_REG);
+		konica_stream_on(gspca_dev);
+		break;
+	case V4L2_CID_SHARPNESS:
+		konica_stream_off(gspca_dev);
+		reg_w(gspca_dev, ctrl->val, SHARPNESS_REG);
+		konica_stream_on(gspca_dev);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 5);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0, 9, 1, 4);
+	/* Needs to be verified */
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_CONTRAST, 0, 9, 1, 4);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_SATURATION, 0, 9, 1, 4);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_WHITE_BALANCE_TEMPERATURE,
+			0, 33, 1, 25);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_SHARPNESS, 0, 9, 1, 4);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	return 0;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.start = sd_start,
+	.stopN = sd_stopN,
+#if IS_ENABLED(CONFIG_INPUT)
+	.other_input = 1,
+#endif
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x04c8, 0x0720)}, /* Intel YC 76 */
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/m5602/Kconfig b/drivers/media/usb/gspca/m5602/Kconfig
new file mode 100644
index 0000000..13a0039
--- /dev/null
+++ b/drivers/media/usb/gspca/m5602/Kconfig
@@ -0,0 +1,9 @@
+config USB_M5602
+	tristate "ALi USB m5602 Camera Driver"
+	depends on VIDEO_V4L2 && USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on the
+	  ALi m5602 connected to various image sensors.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_m5602.
diff --git a/drivers/media/usb/gspca/m5602/Makefile b/drivers/media/usb/gspca/m5602/Makefile
new file mode 100644
index 0000000..95c9db6
--- /dev/null
+++ b/drivers/media/usb/gspca/m5602/Makefile
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_USB_M5602) += gspca_m5602.o
+
+gspca_m5602-objs := m5602_core.o \
+		    m5602_ov9650.o \
+		    m5602_ov7660.o \
+		    m5602_mt9m111.o \
+		    m5602_po1030.o \
+		    m5602_s5k83a.o \
+		    m5602_s5k4aa.o
+
+ccflags-y += -I$(srctree)/drivers/media/usb/gspca
diff --git a/drivers/media/usb/gspca/m5602/m5602_bridge.h b/drivers/media/usb/gspca/m5602/m5602_bridge.h
new file mode 100644
index 0000000..43ebc03
--- /dev/null
+++ b/drivers/media/usb/gspca/m5602/m5602_bridge.h
@@ -0,0 +1,165 @@
+/*
+ * USB Driver for ALi m5602 based webcams
+ *
+ * Copyright (C) 2008 Erik Andrén
+ * Copyright (C) 2007 Ilyes Gouta. Based on the m5603x Linux Driver Project.
+ * Copyright (C) 2005 m5603x Linux Driver Project <m5602@x3ng.com.br>
+ *
+ * Portions of code to USB interface and ALi driver software,
+ * Copyright (c) 2006 Willem Duinker
+ * v4l2 interface modeled after the V4L2 driver
+ * for SN9C10x PC Camera Controllers
+ *
+ * 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, version 2.
+ *
+ */
+
+#ifndef M5602_BRIDGE_H_
+#define M5602_BRIDGE_H_
+
+#include <linux/slab.h>
+#include "gspca.h"
+
+#define MODULE_NAME "ALi m5602"
+
+/*****************************************************************************/
+
+#define M5602_XB_SENSOR_TYPE		0x00
+#define M5602_XB_SENSOR_CTRL		0x01
+#define M5602_XB_LINE_OF_FRAME_H	0x02
+#define M5602_XB_LINE_OF_FRAME_L	0x03
+#define M5602_XB_PIX_OF_LINE_H		0x04
+#define M5602_XB_PIX_OF_LINE_L		0x05
+#define M5602_XB_VSYNC_PARA		0x06
+#define M5602_XB_HSYNC_PARA		0x07
+#define M5602_XB_TEST_MODE_1		0x08
+#define M5602_XB_TEST_MODE_2		0x09
+#define M5602_XB_SIG_INI		0x0a
+#define M5602_XB_DS_PARA		0x0e
+#define M5602_XB_TRIG_PARA		0x0f
+#define M5602_XB_CLK_PD			0x10
+#define M5602_XB_MCU_CLK_CTRL		0x12
+#define M5602_XB_MCU_CLK_DIV		0x13
+#define M5602_XB_SEN_CLK_CTRL		0x14
+#define M5602_XB_SEN_CLK_DIV		0x15
+#define M5602_XB_AUD_CLK_CTRL		0x16
+#define M5602_XB_AUD_CLK_DIV		0x17
+#define M5602_OB_AC_LINK_STATE		0x22
+#define M5602_OB_PCM_SLOT_INDEX		0x24
+#define M5602_OB_GPIO_SLOT_INDEX	0x25
+#define M5602_OB_ACRX_STATUS_ADDRESS_H	0x28
+#define M5602_OB_ACRX_STATUS_DATA_L	0x29
+#define M5602_OB_ACRX_STATUS_DATA_H	0x2a
+#define M5602_OB_ACTX_COMMAND_ADDRESS	0x31
+#define M5602_OB_ACRX_COMMAND_DATA_L	0x32
+#define M5602_OB_ACTX_COMMAND_DATA_H	0X33
+#define M5602_XB_DEVCTR1		0x41
+#define M5602_XB_EPSETR0		0x42
+#define M5602_XB_EPAFCTR		0x47
+#define M5602_XB_EPBFCTR		0x49
+#define M5602_XB_EPEFCTR		0x4f
+#define M5602_XB_TEST_REG		0x53
+#define M5602_XB_ALT2SIZE		0x54
+#define M5602_XB_ALT3SIZE		0x55
+#define M5602_XB_OBSFRAME		0x56
+#define M5602_XB_PWR_CTL		0x59
+#define M5602_XB_ADC_CTRL		0x60
+#define M5602_XB_ADC_DATA		0x61
+#define M5602_XB_MISC_CTRL		0x62
+#define M5602_XB_SNAPSHOT		0x63
+#define M5602_XB_SCRATCH_1		0x64
+#define M5602_XB_SCRATCH_2		0x65
+#define M5602_XB_SCRATCH_3		0x66
+#define M5602_XB_SCRATCH_4		0x67
+#define M5602_XB_I2C_CTRL		0x68
+#define M5602_XB_I2C_CLK_DIV		0x69
+#define M5602_XB_I2C_DEV_ADDR		0x6a
+#define M5602_XB_I2C_REG_ADDR		0x6b
+#define M5602_XB_I2C_DATA		0x6c
+#define M5602_XB_I2C_STATUS		0x6d
+#define M5602_XB_GPIO_DAT_H		0x70
+#define M5602_XB_GPIO_DAT_L		0x71
+#define M5602_XB_GPIO_DIR_H		0x72
+#define M5602_XB_GPIO_DIR_L		0x73
+#define M5602_XB_GPIO_EN_H		0x74
+#define M5602_XB_GPIO_EN_L		0x75
+#define M5602_XB_GPIO_DAT		0x76
+#define M5602_XB_GPIO_DIR		0x77
+#define M5602_XB_SEN_CLK_CONTROL	0x80
+#define M5602_XB_SEN_CLK_DIVISION	0x81
+#define M5602_XB_CPR_CLK_CONTROL	0x82
+#define M5602_XB_CPR_CLK_DIVISION	0x83
+#define M5602_XB_MCU_CLK_CONTROL	0x84
+#define M5602_XB_MCU_CLK_DIVISION	0x85
+#define M5602_XB_DCT_CLK_CONTROL	0x86
+#define M5602_XB_DCT_CLK_DIVISION	0x87
+#define M5602_XB_EC_CLK_CONTROL		0x88
+#define M5602_XB_EC_CLK_DIVISION	0x89
+#define M5602_XB_LBUF_CLK_CONTROL	0x8a
+#define M5602_XB_LBUF_CLK_DIVISION	0x8b
+
+#define I2C_BUSY 0x80
+
+/*****************************************************************************/
+
+/* Driver info */
+#define DRIVER_AUTHOR "ALi m5602 Linux Driver Project"
+#define DRIVER_DESC "ALi m5602 webcam driver"
+
+#define M5602_ISOC_ENDPOINT_ADDR 0x81
+#define M5602_INTR_ENDPOINT_ADDR 0x82
+
+#define M5602_URB_MSG_TIMEOUT   5000
+
+/*****************************************************************************/
+
+struct sd {
+	struct gspca_dev gspca_dev;
+
+	/* A pointer to the currently connected sensor */
+	const struct m5602_sensor *sensor;
+
+	/* The current frame's id, used to detect frame boundaries */
+	u8 frame_id;
+
+	/* The current frame count */
+	u32 frame_count;
+
+	/* Camera rotation polling thread for "flipable" cams */
+	struct task_struct *rotation_thread;
+
+	struct { /* auto-white-bal + green/red/blue balance control cluster */
+		struct v4l2_ctrl *auto_white_bal;
+		struct v4l2_ctrl *red_bal;
+		struct v4l2_ctrl *blue_bal;
+		struct v4l2_ctrl *green_bal;
+	};
+	struct { /* autoexpo / expo cluster */
+		struct v4l2_ctrl *autoexpo;
+		struct v4l2_ctrl *expo;
+	};
+	struct { /* autogain / gain cluster */
+		struct v4l2_ctrl *autogain;
+		struct v4l2_ctrl *gain;
+	};
+	struct { /* hflip/vflip cluster */
+		struct v4l2_ctrl *hflip;
+		struct v4l2_ctrl *vflip;
+	};
+};
+
+int m5602_read_bridge(
+	struct sd *sd, const u8 address, u8 *i2c_data);
+
+int m5602_write_bridge(
+	struct sd *sd, const u8 address, const u8 i2c_data);
+
+int m5602_write_sensor(struct sd *sd, const u8 address,
+		       u8 *i2c_data, const u8 len);
+
+int m5602_read_sensor(struct sd *sd, const u8 address,
+		      u8 *i2c_data, const u8 len);
+
+#endif
diff --git a/drivers/media/usb/gspca/m5602/m5602_core.c b/drivers/media/usb/gspca/m5602/m5602_core.c
new file mode 100644
index 0000000..30b7cf1
--- /dev/null
+++ b/drivers/media/usb/gspca/m5602/m5602_core.c
@@ -0,0 +1,450 @@
+ /*
+ * USB Driver for ALi m5602 based webcams
+ *
+ * Copyright (C) 2008 Erik Andrén
+ * Copyright (C) 2007 Ilyes Gouta. Based on the m5603x Linux Driver Project.
+ * Copyright (C) 2005 m5603x Linux Driver Project <m5602@x3ng.com.br>
+ *
+ * Portions of code to USB interface and ALi driver software,
+ * Copyright (c) 2006 Willem Duinker
+ * v4l2 interface modeled after the V4L2 driver
+ * for SN9C10x PC Camera Controllers
+ *
+ * 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, version 2.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include "m5602_ov9650.h"
+#include "m5602_ov7660.h"
+#include "m5602_mt9m111.h"
+#include "m5602_po1030.h"
+#include "m5602_s5k83a.h"
+#include "m5602_s5k4aa.h"
+
+/* Kernel module parameters */
+int force_sensor;
+static bool dump_bridge;
+bool dump_sensor;
+
+static const struct usb_device_id m5602_table[] = {
+	{USB_DEVICE(0x0402, 0x5602)},
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, m5602_table);
+
+/* A skeleton used for sending messages to the sensor */
+static const unsigned char sensor_urb_skeleton[] = {
+	0x23, M5602_XB_GPIO_EN_H, 0x81, 0x06,
+	0x23, M5602_XB_MISC_CTRL, 0x81, 0x80,
+	0x13, M5602_XB_I2C_DEV_ADDR, 0x81, 0x00,
+	0x13, M5602_XB_I2C_REG_ADDR, 0x81, 0x00,
+	0x13, M5602_XB_I2C_DATA, 0x81, 0x00,
+	0x13, M5602_XB_I2C_CTRL, 0x81, 0x11
+};
+
+/* A skeleton used for sending messages to the m5602 bridge */
+static const unsigned char bridge_urb_skeleton[] = {
+	0x13, 0x00, 0x81, 0x00
+};
+
+/* Reads a byte from the m5602 */
+int m5602_read_bridge(struct sd *sd, const u8 address, u8 *i2c_data)
+{
+	int err;
+	struct gspca_dev *gspca_dev = (struct gspca_dev *) sd;
+	struct usb_device *udev = sd->gspca_dev.dev;
+	__u8 *buf = sd->gspca_dev.usb_buf;
+
+	err = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+			      0x04, 0xc0, 0x14,
+			      0x8100 + address, buf,
+			      1, M5602_URB_MSG_TIMEOUT);
+	*i2c_data = buf[0];
+
+	gspca_dbg(gspca_dev, D_CONF, "Reading bridge register 0x%x containing 0x%x\n",
+		  address, *i2c_data);
+
+	/* usb_control_msg(...) returns the number of bytes sent upon success,
+	mask that and return zero instead*/
+	return (err < 0) ? err : 0;
+}
+
+/* Writes a byte to the m5602 */
+int m5602_write_bridge(struct sd *sd, const u8 address, const u8 i2c_data)
+{
+	int err;
+	struct gspca_dev *gspca_dev = (struct gspca_dev *) sd;
+	struct usb_device *udev = sd->gspca_dev.dev;
+	__u8 *buf = sd->gspca_dev.usb_buf;
+
+	gspca_dbg(gspca_dev, D_CONF, "Writing bridge register 0x%x with 0x%x\n",
+		  address, i2c_data);
+
+	memcpy(buf, bridge_urb_skeleton,
+	       sizeof(bridge_urb_skeleton));
+	buf[1] = address;
+	buf[3] = i2c_data;
+
+	err = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+				0x04, 0x40, 0x19,
+				0x0000, buf,
+				4, M5602_URB_MSG_TIMEOUT);
+
+	/* usb_control_msg(...) returns the number of bytes sent upon success,
+	   mask that and return zero instead */
+	return (err < 0) ? err : 0;
+}
+
+static int m5602_wait_for_i2c(struct sd *sd)
+{
+	int err;
+	u8 data;
+
+	do {
+		err = m5602_read_bridge(sd, M5602_XB_I2C_STATUS, &data);
+	} while ((data & I2C_BUSY) && !err);
+	return err;
+}
+
+int m5602_read_sensor(struct sd *sd, const u8 address,
+		       u8 *i2c_data, const u8 len)
+{
+	int err, i;
+	struct gspca_dev *gspca_dev = (struct gspca_dev *) sd;
+
+	if (!len || len > sd->sensor->i2c_regW)
+		return -EINVAL;
+
+	err = m5602_wait_for_i2c(sd);
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_I2C_DEV_ADDR,
+				 sd->sensor->i2c_slave_id);
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_I2C_REG_ADDR, address);
+	if (err < 0)
+		return err;
+
+	/* Sensors with registers that are of only
+	   one byte width are differently read */
+
+	/* FIXME: This works with the ov9650, but has issues with the po1030 */
+	if (sd->sensor->i2c_regW == 1) {
+		err = m5602_write_bridge(sd, M5602_XB_I2C_CTRL, 1);
+		if (err < 0)
+			return err;
+
+		err = m5602_write_bridge(sd, M5602_XB_I2C_CTRL, 0x08);
+	} else {
+		err = m5602_write_bridge(sd, M5602_XB_I2C_CTRL, 0x18 + len);
+	}
+
+	for (i = 0; (i < len) && !err; i++) {
+		err = m5602_wait_for_i2c(sd);
+		if (err < 0)
+			return err;
+
+		err = m5602_read_bridge(sd, M5602_XB_I2C_DATA, &(i2c_data[i]));
+
+		gspca_dbg(gspca_dev, D_CONF, "Reading sensor register 0x%x containing 0x%x\n",
+			  address, *i2c_data);
+	}
+	return err;
+}
+
+int m5602_write_sensor(struct sd *sd, const u8 address,
+			u8 *i2c_data, const u8 len)
+{
+	int err, i;
+	u8 *p;
+	struct gspca_dev *gspca_dev = (struct gspca_dev *) sd;
+	struct usb_device *udev = sd->gspca_dev.dev;
+	__u8 *buf = sd->gspca_dev.usb_buf;
+
+	/* No sensor with a data width larger than 16 bits has yet been seen */
+	if (len > sd->sensor->i2c_regW || !len)
+		return -EINVAL;
+
+	memcpy(buf, sensor_urb_skeleton,
+	       sizeof(sensor_urb_skeleton));
+
+	buf[11] = sd->sensor->i2c_slave_id;
+	buf[15] = address;
+
+	/* Special case larger sensor writes */
+	p = buf + 16;
+
+	/* Copy a four byte write sequence for each byte to be written to */
+	for (i = 0; i < len; i++) {
+		memcpy(p, sensor_urb_skeleton + 16, 4);
+		p[3] = i2c_data[i];
+		p += 4;
+		gspca_dbg(gspca_dev, D_CONF, "Writing sensor register 0x%x with 0x%x\n",
+			  address, i2c_data[i]);
+	}
+
+	/* Copy the tailer */
+	memcpy(p, sensor_urb_skeleton + 20, 4);
+
+	/* Set the total length */
+	p[3] = 0x10 + len;
+
+	err = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+			      0x04, 0x40, 0x19,
+			      0x0000, buf,
+			      20 + len * 4, M5602_URB_MSG_TIMEOUT);
+
+	return (err < 0) ? err : 0;
+}
+
+/* Dump all the registers of the m5602 bridge,
+   unfortunately this breaks the camera until it's power cycled */
+static void m5602_dump_bridge(struct sd *sd)
+{
+	int i;
+	for (i = 0; i < 0x80; i++) {
+		unsigned char val = 0;
+		m5602_read_bridge(sd, i, &val);
+		pr_info("ALi m5602 address 0x%x contains 0x%x\n", i, val);
+	}
+	pr_info("Warning: The ALi m5602 webcam probably won't work until it's power cycled\n");
+}
+
+static int m5602_probe_sensor(struct sd *sd)
+{
+	/* Try the po1030 */
+	sd->sensor = &po1030;
+	if (!sd->sensor->probe(sd))
+		return 0;
+
+	/* Try the mt9m111 sensor */
+	sd->sensor = &mt9m111;
+	if (!sd->sensor->probe(sd))
+		return 0;
+
+	/* Try the s5k4aa */
+	sd->sensor = &s5k4aa;
+	if (!sd->sensor->probe(sd))
+		return 0;
+
+	/* Try the ov9650 */
+	sd->sensor = &ov9650;
+	if (!sd->sensor->probe(sd))
+		return 0;
+
+	/* Try the ov7660 */
+	sd->sensor = &ov7660;
+	if (!sd->sensor->probe(sd))
+		return 0;
+
+	/* Try the s5k83a */
+	sd->sensor = &s5k83a;
+	if (!sd->sensor->probe(sd))
+		return 0;
+
+	/* More sensor probe function goes here */
+	pr_info("Failed to find a sensor\n");
+	sd->sensor = NULL;
+	return -ENODEV;
+}
+
+static int m5602_configure(struct gspca_dev *gspca_dev,
+			   const struct usb_device_id *id);
+
+static int m5602_init(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int err;
+
+	gspca_dbg(gspca_dev, D_CONF, "Initializing ALi m5602 webcam\n");
+	/* Run the init sequence */
+	err = sd->sensor->init(sd);
+
+	return err;
+}
+
+static int m5602_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (!sd->sensor->init_controls)
+		return 0;
+
+	return sd->sensor->init_controls(sd);
+}
+
+static int m5602_start_transfer(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	__u8 *buf = sd->gspca_dev.usb_buf;
+	int err;
+
+	/* Send start command to the camera */
+	const u8 buffer[4] = {0x13, 0xf9, 0x0f, 0x01};
+
+	if (sd->sensor->start)
+		sd->sensor->start(sd);
+
+	memcpy(buf, buffer, sizeof(buffer));
+	err = usb_control_msg(gspca_dev->dev,
+			      usb_sndctrlpipe(gspca_dev->dev, 0),
+			      0x04, 0x40, 0x19, 0x0000, buf,
+			      sizeof(buffer), M5602_URB_MSG_TIMEOUT);
+
+	gspca_dbg(gspca_dev, D_STREAM, "Transfer started\n");
+	return (err < 0) ? err : 0;
+}
+
+static void m5602_urb_complete(struct gspca_dev *gspca_dev,
+				u8 *data, int len)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (len < 6) {
+		gspca_dbg(gspca_dev, D_PACK, "Packet is less than 6 bytes\n");
+		return;
+	}
+
+	/* Frame delimiter: ff xx xx xx ff ff */
+	if (data[0] == 0xff && data[4] == 0xff && data[5] == 0xff &&
+	    data[2] != sd->frame_id) {
+		gspca_dbg(gspca_dev, D_FRAM, "Frame delimiter detected\n");
+		sd->frame_id = data[2];
+
+		/* Remove the extra fluff appended on each header */
+		data += 6;
+		len -= 6;
+
+		/* Complete the last frame (if any) */
+		gspca_frame_add(gspca_dev, LAST_PACKET,
+				NULL, 0);
+		sd->frame_count++;
+
+		/* Create a new frame */
+		gspca_frame_add(gspca_dev, FIRST_PACKET, data, len);
+
+		gspca_dbg(gspca_dev, D_FRAM, "Starting new frame %d\n",
+			  sd->frame_count);
+
+	} else {
+		int cur_frame_len;
+
+		cur_frame_len = gspca_dev->image_len;
+		/* Remove urb header */
+		data += 4;
+		len -= 4;
+
+		if (cur_frame_len + len <= gspca_dev->pixfmt.sizeimage) {
+			gspca_dbg(gspca_dev, D_FRAM, "Continuing frame %d copying %d bytes\n",
+				  sd->frame_count, len);
+
+			gspca_frame_add(gspca_dev, INTER_PACKET,
+					data, len);
+		} else {
+			/* Add the remaining data up to frame size */
+			gspca_frame_add(gspca_dev, INTER_PACKET, data,
+				gspca_dev->pixfmt.sizeimage - cur_frame_len);
+		}
+	}
+}
+
+static void m5602_stop_transfer(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	/* Run the sensor specific end transfer sequence */
+	if (sd->sensor->stop)
+		sd->sensor->stop(sd);
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name		= MODULE_NAME,
+	.config		= m5602_configure,
+	.init		= m5602_init,
+	.init_controls	= m5602_init_controls,
+	.start		= m5602_start_transfer,
+	.stopN		= m5602_stop_transfer,
+	.pkt_scan	= m5602_urb_complete
+};
+
+/* this function is called at probe time */
+static int m5602_configure(struct gspca_dev *gspca_dev,
+			   const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam;
+	int err;
+
+	cam = &gspca_dev->cam;
+
+	if (dump_bridge)
+		m5602_dump_bridge(sd);
+
+	/* Probe sensor */
+	err = m5602_probe_sensor(sd);
+	if (err)
+		goto fail;
+
+	return 0;
+
+fail:
+	gspca_err(gspca_dev, "ALi m5602 webcam failed\n");
+	cam->cam_mode = NULL;
+	cam->nmodes = 0;
+
+	return err;
+}
+
+static int m5602_probe(struct usb_interface *intf,
+		       const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+			       THIS_MODULE);
+}
+
+static void m5602_disconnect(struct usb_interface *intf)
+{
+	struct gspca_dev *gspca_dev = usb_get_intfdata(intf);
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->sensor->disconnect)
+		sd->sensor->disconnect(sd);
+
+	gspca_disconnect(intf);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = m5602_table,
+	.probe = m5602_probe,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+	.disconnect = m5602_disconnect
+};
+
+module_usb_driver(sd_driver);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+module_param(force_sensor, int, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(force_sensor,
+		"forces detection of a sensor, 1 = OV9650, 2 = S5K83A, 3 = S5K4AA, 4 = MT9M111, 5 = PO1030, 6 = OV7660");
+
+module_param(dump_bridge, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(dump_bridge, "Dumps all usb bridge registers at startup");
+
+module_param(dump_sensor, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(dump_sensor, "Dumps all usb sensor registers at startup providing a sensor is found");
diff --git a/drivers/media/usb/gspca/m5602/m5602_mt9m111.c b/drivers/media/usb/gspca/m5602/m5602_mt9m111.c
new file mode 100644
index 0000000..c9947c4
--- /dev/null
+++ b/drivers/media/usb/gspca/m5602/m5602_mt9m111.c
@@ -0,0 +1,602 @@
+/*
+ * Driver for the mt9m111 sensor
+ *
+ * Copyright (C) 2008 Erik Andrén
+ * Copyright (C) 2007 Ilyes Gouta. Based on the m5603x Linux Driver Project.
+ * Copyright (C) 2005 m5603x Linux Driver Project <m5602@x3ng.com.br>
+ *
+ * Portions of code to USB interface and ALi driver software,
+ * Copyright (c) 2006 Willem Duinker
+ * v4l2 interface modeled after the V4L2 driver
+ * for SN9C10x PC Camera Controllers
+ *
+ * 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, version 2.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include "m5602_mt9m111.h"
+
+static int mt9m111_s_ctrl(struct v4l2_ctrl *ctrl);
+static void mt9m111_dump_registers(struct sd *sd);
+
+static const unsigned char preinit_mt9m111[][4] = {
+	{BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00},
+	{BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x0d, 0x00},
+	{BRIDGE, M5602_XB_SENSOR_CTRL, 0x00, 0x00},
+	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00},
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x09, 0x00},
+
+	{SENSOR, MT9M111_PAGE_MAP, 0x00, 0x00},
+	{SENSOR, MT9M111_SC_RESET,
+		MT9M111_RESET |
+		MT9M111_RESTART |
+		MT9M111_ANALOG_STANDBY |
+		MT9M111_CHIP_DISABLE,
+		MT9M111_SHOW_BAD_FRAMES |
+		MT9M111_RESTART_BAD_FRAMES |
+		MT9M111_SYNCHRONIZE_CHANGES},
+
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x05, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x04, 0x00},
+	{BRIDGE, M5602_XB_GPIO_EN_H, 0x3e, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR_H, 0x3e, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT_H, 0x02, 0x00},
+	{BRIDGE, M5602_XB_GPIO_EN_L, 0xff, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR_L, 0xff, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT_L, 0x00, 0x00},
+
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x07, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x0b, 0x00},
+	{BRIDGE, M5602_XB_GPIO_EN_H, 0x06, 0x00},
+	{BRIDGE, M5602_XB_GPIO_EN_L, 0x00, 0x00},
+
+	{BRIDGE, M5602_XB_I2C_CLK_DIV, 0x0a, 0x00}
+};
+
+static const unsigned char init_mt9m111[][4] = {
+	{BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00},
+	{BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00},
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x09, 0x00},
+
+	{BRIDGE, M5602_XB_GPIO_EN_H, 0x06, 0x00},
+	{BRIDGE, M5602_XB_GPIO_EN_L, 0x00, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x04, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR_H, 0x3e, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR_L, 0xff, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT_H, 0x02, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT_L, 0x00, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x07, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x0b, 0x00},
+	{BRIDGE, M5602_XB_I2C_CLK_DIV, 0x0a, 0x00},
+
+	{SENSOR, MT9M111_SC_RESET, 0x00, 0x29},
+	{SENSOR, MT9M111_PAGE_MAP, 0x00, 0x00},
+	{SENSOR, MT9M111_SC_RESET, 0x00, 0x08},
+	{SENSOR, MT9M111_PAGE_MAP, 0x00, 0x01},
+	{SENSOR, MT9M111_CP_OPERATING_MODE_CTL, 0x00,
+			MT9M111_CP_OPERATING_MODE_CTL},
+	{SENSOR, MT9M111_CP_LENS_CORRECTION_1, 0x04, 0x2a},
+	{SENSOR, MT9M111_CP_DEFECT_CORR_CONTEXT_A, 0x00,
+				MT9M111_2D_DEFECT_CORRECTION_ENABLE},
+	{SENSOR, MT9M111_CP_DEFECT_CORR_CONTEXT_B, 0x00,
+				MT9M111_2D_DEFECT_CORRECTION_ENABLE},
+	{SENSOR, MT9M111_CP_LUMA_OFFSET, 0x00, 0x00},
+	{SENSOR, MT9M111_CP_LUMA_CLIP, 0xff, 0x00},
+	{SENSOR, MT9M111_CP_OUTPUT_FORMAT_CTL2_CONTEXT_A, 0x14, 0x00},
+	{SENSOR, MT9M111_CP_OUTPUT_FORMAT_CTL2_CONTEXT_B, 0x14, 0x00},
+	{SENSOR, 0xcd, 0x00, 0x0e},
+	{SENSOR, 0xd0, 0x00, 0x40},
+
+	{SENSOR, MT9M111_PAGE_MAP, 0x00, 0x02},
+	{SENSOR, MT9M111_CC_AUTO_EXPOSURE_PARAMETER_18, 0x00, 0x00},
+	{SENSOR, MT9M111_CC_AWB_PARAMETER_7, 0xef, 0x03},
+
+	{SENSOR, MT9M111_PAGE_MAP, 0x00, 0x00},
+	{SENSOR, 0x33, 0x03, 0x49},
+	{SENSOR, 0x34, 0xc0, 0x19},
+	{SENSOR, 0x3f, 0x20, 0x20},
+	{SENSOR, 0x40, 0x20, 0x20},
+	{SENSOR, 0x5a, 0xc0, 0x0a},
+	{SENSOR, 0x70, 0x7b, 0x0a},
+	{SENSOR, 0x71, 0xff, 0x00},
+	{SENSOR, 0x72, 0x19, 0x0e},
+	{SENSOR, 0x73, 0x18, 0x0f},
+	{SENSOR, 0x74, 0x57, 0x32},
+	{SENSOR, 0x75, 0x56, 0x34},
+	{SENSOR, 0x76, 0x73, 0x35},
+	{SENSOR, 0x77, 0x30, 0x12},
+	{SENSOR, 0x78, 0x79, 0x02},
+	{SENSOR, 0x79, 0x75, 0x06},
+	{SENSOR, 0x7a, 0x77, 0x0a},
+	{SENSOR, 0x7b, 0x78, 0x09},
+	{SENSOR, 0x7c, 0x7d, 0x06},
+	{SENSOR, 0x7d, 0x31, 0x10},
+	{SENSOR, 0x7e, 0x00, 0x7e},
+	{SENSOR, 0x80, 0x59, 0x04},
+	{SENSOR, 0x81, 0x59, 0x04},
+	{SENSOR, 0x82, 0x57, 0x0a},
+	{SENSOR, 0x83, 0x58, 0x0b},
+	{SENSOR, 0x84, 0x47, 0x0c},
+	{SENSOR, 0x85, 0x48, 0x0e},
+	{SENSOR, 0x86, 0x5b, 0x02},
+	{SENSOR, 0x87, 0x00, 0x5c},
+	{SENSOR, MT9M111_CONTEXT_CONTROL, 0x00, MT9M111_SEL_CONTEXT_B},
+	{SENSOR, 0x60, 0x00, 0x80},
+	{SENSOR, 0x61, 0x00, 0x00},
+	{SENSOR, 0x62, 0x00, 0x00},
+	{SENSOR, 0x63, 0x00, 0x00},
+	{SENSOR, 0x64, 0x00, 0x00},
+
+	{SENSOR, MT9M111_SC_ROWSTART, 0x00, 0x0d}, /* 13 */
+	{SENSOR, MT9M111_SC_COLSTART, 0x00, 0x12}, /* 18 */
+	{SENSOR, MT9M111_SC_WINDOW_HEIGHT, 0x04, 0x00}, /* 1024 */
+	{SENSOR, MT9M111_SC_WINDOW_WIDTH, 0x05, 0x10}, /* 1296 */
+	{SENSOR, MT9M111_SC_HBLANK_CONTEXT_B, 0x01, 0x60}, /* 352 */
+	{SENSOR, MT9M111_SC_VBLANK_CONTEXT_B, 0x00, 0x11}, /* 17 */
+	{SENSOR, MT9M111_SC_HBLANK_CONTEXT_A, 0x01, 0x60}, /* 352 */
+	{SENSOR, MT9M111_SC_VBLANK_CONTEXT_A, 0x00, 0x11}, /* 17 */
+	{SENSOR, MT9M111_SC_R_MODE_CONTEXT_A, 0x01, 0x0f}, /* 271 */
+	{SENSOR, 0x30, 0x04, 0x00},
+	/* Set number of blank rows chosen to 400 */
+	{SENSOR, MT9M111_SC_SHUTTER_WIDTH, 0x01, 0x90},
+};
+
+static const unsigned char start_mt9m111[][4] = {
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x06, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00},
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x09, 0x00},
+	{BRIDGE, M5602_XB_LINE_OF_FRAME_H, 0x81, 0x00},
+	{BRIDGE, M5602_XB_PIX_OF_LINE_H, 0x82, 0x00},
+	{BRIDGE, M5602_XB_SIG_INI, 0x01, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},
+};
+
+static struct v4l2_pix_format mt9m111_modes[] = {
+	{
+		640,
+		480,
+		V4L2_PIX_FMT_SBGGR8,
+		V4L2_FIELD_NONE,
+		.sizeimage = 640 * 480,
+		.bytesperline = 640,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0
+	}
+};
+
+static const struct v4l2_ctrl_ops mt9m111_ctrl_ops = {
+	.s_ctrl = mt9m111_s_ctrl,
+};
+
+static const struct v4l2_ctrl_config mt9m111_greenbal_cfg = {
+	.ops	= &mt9m111_ctrl_ops,
+	.id	= M5602_V4L2_CID_GREEN_BALANCE,
+	.name	= "Green Balance",
+	.type	= V4L2_CTRL_TYPE_INTEGER,
+	.min	= 0,
+	.max	= 0x7ff,
+	.step	= 1,
+	.def	= MT9M111_GREEN_GAIN_DEFAULT,
+	.flags	= V4L2_CTRL_FLAG_SLIDER,
+};
+
+int mt9m111_probe(struct sd *sd)
+{
+	u8 data[2] = {0x00, 0x00};
+	int i;
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+
+	if (force_sensor) {
+		if (force_sensor == MT9M111_SENSOR) {
+			pr_info("Forcing a %s sensor\n", mt9m111.name);
+			goto sensor_found;
+		}
+		/* If we want to force another sensor, don't try to probe this
+		 * one */
+		return -ENODEV;
+	}
+
+	gspca_dbg(gspca_dev, D_PROBE, "Probing for a mt9m111 sensor\n");
+
+	/* Do the preinit */
+	for (i = 0; i < ARRAY_SIZE(preinit_mt9m111); i++) {
+		if (preinit_mt9m111[i][0] == BRIDGE) {
+			m5602_write_bridge(sd,
+				preinit_mt9m111[i][1],
+				preinit_mt9m111[i][2]);
+		} else {
+			data[0] = preinit_mt9m111[i][2];
+			data[1] = preinit_mt9m111[i][3];
+			m5602_write_sensor(sd,
+				preinit_mt9m111[i][1], data, 2);
+		}
+	}
+
+	if (m5602_read_sensor(sd, MT9M111_SC_CHIPVER, data, 2))
+		return -ENODEV;
+
+	if ((data[0] == 0x14) && (data[1] == 0x3a)) {
+		pr_info("Detected a mt9m111 sensor\n");
+		goto sensor_found;
+	}
+
+	return -ENODEV;
+
+sensor_found:
+	sd->gspca_dev.cam.cam_mode = mt9m111_modes;
+	sd->gspca_dev.cam.nmodes = ARRAY_SIZE(mt9m111_modes);
+
+	return 0;
+}
+
+int mt9m111_init(struct sd *sd)
+{
+	int i, err = 0;
+
+	/* Init the sensor */
+	for (i = 0; i < ARRAY_SIZE(init_mt9m111) && !err; i++) {
+		u8 data[2];
+
+		if (init_mt9m111[i][0] == BRIDGE) {
+			err = m5602_write_bridge(sd,
+				init_mt9m111[i][1],
+				init_mt9m111[i][2]);
+		} else {
+			data[0] = init_mt9m111[i][2];
+			data[1] = init_mt9m111[i][3];
+			err = m5602_write_sensor(sd,
+				init_mt9m111[i][1], data, 2);
+		}
+	}
+
+	if (dump_sensor)
+		mt9m111_dump_registers(sd);
+
+	return 0;
+}
+
+int mt9m111_init_controls(struct sd *sd)
+{
+	struct v4l2_ctrl_handler *hdl = &sd->gspca_dev.ctrl_handler;
+
+	sd->gspca_dev.vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 7);
+
+	sd->auto_white_bal = v4l2_ctrl_new_std(hdl, &mt9m111_ctrl_ops,
+					       V4L2_CID_AUTO_WHITE_BALANCE,
+					       0, 1, 1, 0);
+	sd->green_bal = v4l2_ctrl_new_custom(hdl, &mt9m111_greenbal_cfg, NULL);
+	sd->red_bal = v4l2_ctrl_new_std(hdl, &mt9m111_ctrl_ops,
+					V4L2_CID_RED_BALANCE, 0, 0x7ff, 1,
+					MT9M111_RED_GAIN_DEFAULT);
+	sd->blue_bal = v4l2_ctrl_new_std(hdl, &mt9m111_ctrl_ops,
+					V4L2_CID_BLUE_BALANCE, 0, 0x7ff, 1,
+					MT9M111_BLUE_GAIN_DEFAULT);
+
+	v4l2_ctrl_new_std(hdl, &mt9m111_ctrl_ops, V4L2_CID_GAIN, 0,
+			  (INITIAL_MAX_GAIN - 1) * 2 * 2 * 2, 1,
+			  MT9M111_DEFAULT_GAIN);
+
+	sd->hflip = v4l2_ctrl_new_std(hdl, &mt9m111_ctrl_ops, V4L2_CID_HFLIP,
+				      0, 1, 1, 0);
+	sd->vflip = v4l2_ctrl_new_std(hdl, &mt9m111_ctrl_ops, V4L2_CID_VFLIP,
+				      0, 1, 1, 0);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+
+	v4l2_ctrl_auto_cluster(4, &sd->auto_white_bal, 0, false);
+	v4l2_ctrl_cluster(2, &sd->hflip);
+
+	return 0;
+}
+
+int mt9m111_start(struct sd *sd)
+{
+	int i, err = 0;
+	u8 data[2];
+	struct cam *cam = &sd->gspca_dev.cam;
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+
+	int width = cam->cam_mode[sd->gspca_dev.curr_mode].width - 1;
+	int height = cam->cam_mode[sd->gspca_dev.curr_mode].height;
+
+	for (i = 0; i < ARRAY_SIZE(start_mt9m111) && !err; i++) {
+		if (start_mt9m111[i][0] == BRIDGE) {
+			err = m5602_write_bridge(sd,
+				start_mt9m111[i][1],
+				start_mt9m111[i][2]);
+		} else {
+			data[0] = start_mt9m111[i][2];
+			data[1] = start_mt9m111[i][3];
+			err = m5602_write_sensor(sd,
+				start_mt9m111[i][1], data, 2);
+		}
+	}
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_VSYNC_PARA, (height >> 8) & 0xff);
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_VSYNC_PARA, (height & 0xff));
+	if (err < 0)
+		return err;
+
+	for (i = 0; i < 2 && !err; i++)
+		err = m5602_write_bridge(sd, M5602_XB_VSYNC_PARA, 0);
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_SIG_INI, 0);
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_SIG_INI, 2);
+	if (err < 0)
+		return err;
+
+	for (i = 0; i < 2 && !err; i++)
+		err = m5602_write_bridge(sd, M5602_XB_HSYNC_PARA, 0);
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_HSYNC_PARA,
+				 (width >> 8) & 0xff);
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_HSYNC_PARA, width & 0xff);
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_SIG_INI, 0);
+	if (err < 0)
+		return err;
+
+	switch (width) {
+	case 640:
+		gspca_dbg(gspca_dev, D_CONF, "Configuring camera for VGA mode\n");
+		break;
+
+	case 320:
+		gspca_dbg(gspca_dev, D_CONF, "Configuring camera for QVGA mode\n");
+		break;
+	}
+	return err;
+}
+
+void mt9m111_disconnect(struct sd *sd)
+{
+	sd->sensor = NULL;
+}
+
+static int mt9m111_set_hvflip(struct gspca_dev *gspca_dev)
+{
+	int err;
+	u8 data[2] = {0x00, 0x00};
+	struct sd *sd = (struct sd *) gspca_dev;
+	int hflip;
+	int vflip;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set hvflip to %d %d\n",
+		  sd->hflip->val, sd->vflip->val);
+
+	/* The mt9m111 is flipped by default */
+	hflip = !sd->hflip->val;
+	vflip = !sd->vflip->val;
+
+	/* Set the correct page map */
+	err = m5602_write_sensor(sd, MT9M111_PAGE_MAP, data, 2);
+	if (err < 0)
+		return err;
+
+	data[0] = MT9M111_RMB_OVER_SIZED;
+	if (gspca_dev->pixfmt.width == 640) {
+		data[1] = MT9M111_RMB_ROW_SKIP_2X |
+			  MT9M111_RMB_COLUMN_SKIP_2X |
+			  (hflip << 1) | vflip;
+	} else {
+		data[1] = MT9M111_RMB_ROW_SKIP_4X |
+			  MT9M111_RMB_COLUMN_SKIP_4X |
+			  (hflip << 1) | vflip;
+	}
+	err = m5602_write_sensor(sd, MT9M111_SC_R_MODE_CONTEXT_B,
+					data, 2);
+	return err;
+}
+
+static int mt9m111_set_auto_white_balance(struct gspca_dev *gspca_dev,
+					  __s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int err;
+	u8 data[2];
+
+	err = m5602_read_sensor(sd, MT9M111_CP_OPERATING_MODE_CTL, data, 2);
+	if (err < 0)
+		return err;
+
+	data[1] = ((data[1] & 0xfd) | ((val & 0x01) << 1));
+
+	err = m5602_write_sensor(sd, MT9M111_CP_OPERATING_MODE_CTL, data, 2);
+
+	gspca_dbg(gspca_dev, D_CONF, "Set auto white balance %d\n", val);
+	return err;
+}
+
+static int mt9m111_set_gain(struct gspca_dev *gspca_dev, __s32 val)
+{
+	int err, tmp;
+	u8 data[2] = {0x00, 0x00};
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	/* Set the correct page map */
+	err = m5602_write_sensor(sd, MT9M111_PAGE_MAP, data, 2);
+	if (err < 0)
+		return err;
+
+	if (val >= INITIAL_MAX_GAIN * 2 * 2 * 2)
+		return -EINVAL;
+
+	if ((val >= INITIAL_MAX_GAIN * 2 * 2) &&
+	    (val < (INITIAL_MAX_GAIN - 1) * 2 * 2 * 2))
+		tmp = (1 << 10) | (val << 9) |
+				(val << 8) | (val / 8);
+	else if ((val >= INITIAL_MAX_GAIN * 2) &&
+		 (val <  INITIAL_MAX_GAIN * 2 * 2))
+		tmp = (1 << 9) | (1 << 8) | (val / 4);
+	else if ((val >= INITIAL_MAX_GAIN) &&
+		 (val < INITIAL_MAX_GAIN * 2))
+		tmp = (1 << 8) | (val / 2);
+	else
+		tmp = val;
+
+	data[1] = (tmp & 0xff);
+	data[0] = (tmp & 0xff00) >> 8;
+	gspca_dbg(gspca_dev, D_CONF, "tmp=%d, data[1]=%d, data[0]=%d\n", tmp,
+		  data[1], data[0]);
+
+	err = m5602_write_sensor(sd, MT9M111_SC_GLOBAL_GAIN,
+				   data, 2);
+
+	return err;
+}
+
+static int mt9m111_set_green_balance(struct gspca_dev *gspca_dev, __s32 val)
+{
+	int err;
+	u8 data[2];
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	data[1] = (val & 0xff);
+	data[0] = (val & 0xff00) >> 8;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set green balance %d\n", val);
+	err = m5602_write_sensor(sd, MT9M111_SC_GREEN_1_GAIN,
+				 data, 2);
+	if (err < 0)
+		return err;
+
+	return m5602_write_sensor(sd, MT9M111_SC_GREEN_2_GAIN,
+				  data, 2);
+}
+
+static int mt9m111_set_blue_balance(struct gspca_dev *gspca_dev, __s32 val)
+{
+	u8 data[2];
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	data[1] = (val & 0xff);
+	data[0] = (val & 0xff00) >> 8;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set blue balance %d\n", val);
+
+	return m5602_write_sensor(sd, MT9M111_SC_BLUE_GAIN,
+				  data, 2);
+}
+
+static int mt9m111_set_red_balance(struct gspca_dev *gspca_dev, __s32 val)
+{
+	u8 data[2];
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	data[1] = (val & 0xff);
+	data[0] = (val & 0xff00) >> 8;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set red balance %d\n", val);
+
+	return m5602_write_sensor(sd, MT9M111_SC_RED_GAIN,
+				  data, 2);
+}
+
+static int mt9m111_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *) gspca_dev;
+	int err;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_AUTO_WHITE_BALANCE:
+		err = mt9m111_set_auto_white_balance(gspca_dev, ctrl->val);
+		if (err || ctrl->val)
+			return err;
+		err = mt9m111_set_green_balance(gspca_dev, sd->green_bal->val);
+		if (err)
+			return err;
+		err = mt9m111_set_red_balance(gspca_dev, sd->red_bal->val);
+		if (err)
+			return err;
+		err = mt9m111_set_blue_balance(gspca_dev, sd->blue_bal->val);
+		break;
+	case V4L2_CID_GAIN:
+		err = mt9m111_set_gain(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_HFLIP:
+		err = mt9m111_set_hvflip(gspca_dev);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return err;
+}
+
+static void mt9m111_dump_registers(struct sd *sd)
+{
+	u8 address, value[2] = {0x00, 0x00};
+
+	pr_info("Dumping the mt9m111 register state\n");
+
+	pr_info("Dumping the mt9m111 sensor core registers\n");
+	value[1] = MT9M111_SENSOR_CORE;
+	m5602_write_sensor(sd, MT9M111_PAGE_MAP, value, 2);
+	for (address = 0; address < 0xff; address++) {
+		m5602_read_sensor(sd, address, value, 2);
+		pr_info("register 0x%x contains 0x%x%x\n",
+			address, value[0], value[1]);
+	}
+
+	pr_info("Dumping the mt9m111 color pipeline registers\n");
+	value[1] = MT9M111_COLORPIPE;
+	m5602_write_sensor(sd, MT9M111_PAGE_MAP, value, 2);
+	for (address = 0; address < 0xff; address++) {
+		m5602_read_sensor(sd, address, value, 2);
+		pr_info("register 0x%x contains 0x%x%x\n",
+			address, value[0], value[1]);
+	}
+
+	pr_info("Dumping the mt9m111 camera control registers\n");
+	value[1] = MT9M111_CAMERA_CONTROL;
+	m5602_write_sensor(sd, MT9M111_PAGE_MAP, value, 2);
+	for (address = 0; address < 0xff; address++) {
+		m5602_read_sensor(sd, address, value, 2);
+		pr_info("register 0x%x contains 0x%x%x\n",
+			address, value[0], value[1]);
+	}
+
+	pr_info("mt9m111 register state dump complete\n");
+}
diff --git a/drivers/media/usb/gspca/m5602/m5602_mt9m111.h b/drivers/media/usb/gspca/m5602/m5602_mt9m111.h
new file mode 100644
index 0000000..781a163
--- /dev/null
+++ b/drivers/media/usb/gspca/m5602/m5602_mt9m111.h
@@ -0,0 +1,129 @@
+/*
+ * Driver for the mt9m111 sensor
+ *
+ * Copyright (C) 2008 Erik Andrén
+ * Copyright (C) 2007 Ilyes Gouta. Based on the m5603x Linux Driver Project.
+ * Copyright (C) 2005 m5603x Linux Driver Project <m5602@x3ng.com.br>
+ *
+ * Portions of code to USB interface and ALi driver software,
+ * Copyright (c) 2006 Willem Duinker
+ * v4l2 interface modeled after the V4L2 driver
+ * for SN9C10x PC Camera Controllers
+ *
+ * Some defines taken from the mt9m111 sensor driver
+ * Copyright (C) 2008, Robert Jarzmik <robert.jarzmik@free.fr>
+ *
+ * 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, version 2.
+ *
+ */
+
+#ifndef M5602_MT9M111_H_
+#define M5602_MT9M111_H_
+
+#include "m5602_sensor.h"
+
+/*****************************************************************************/
+
+#define MT9M111_SC_CHIPVER			0x00
+#define MT9M111_SC_ROWSTART			0x01
+#define MT9M111_SC_COLSTART			0x02
+#define MT9M111_SC_WINDOW_HEIGHT		0x03
+#define MT9M111_SC_WINDOW_WIDTH			0x04
+#define MT9M111_SC_HBLANK_CONTEXT_B		0x05
+#define MT9M111_SC_VBLANK_CONTEXT_B		0x06
+#define MT9M111_SC_HBLANK_CONTEXT_A		0x07
+#define MT9M111_SC_VBLANK_CONTEXT_A		0x08
+#define MT9M111_SC_SHUTTER_WIDTH		0x09
+#define MT9M111_SC_ROW_SPEED			0x0a
+#define MT9M111_SC_EXTRA_DELAY			0x0b
+#define MT9M111_SC_SHUTTER_DELAY		0x0c
+#define MT9M111_SC_RESET			0x0d
+#define MT9M111_SC_R_MODE_CONTEXT_B		0x20
+#define MT9M111_SC_R_MODE_CONTEXT_A		0x21
+#define MT9M111_SC_FLASH_CONTROL		0x23
+#define MT9M111_SC_GREEN_1_GAIN			0x2b
+#define MT9M111_SC_BLUE_GAIN			0x2c
+#define MT9M111_SC_RED_GAIN			0x2d
+#define MT9M111_SC_GREEN_2_GAIN			0x2e
+#define MT9M111_SC_GLOBAL_GAIN			0x2f
+
+#define MT9M111_CONTEXT_CONTROL			0xc8
+#define MT9M111_PAGE_MAP			0xf0
+#define MT9M111_BYTEWISE_ADDRESS		0xf1
+
+#define MT9M111_CP_OPERATING_MODE_CTL		0x06
+#define MT9M111_CP_LUMA_OFFSET			0x34
+#define MT9M111_CP_LUMA_CLIP			0x35
+#define MT9M111_CP_OUTPUT_FORMAT_CTL2_CONTEXT_A 0x3a
+#define MT9M111_CP_LENS_CORRECTION_1		0x3b
+#define MT9M111_CP_DEFECT_CORR_CONTEXT_A	0x4c
+#define MT9M111_CP_DEFECT_CORR_CONTEXT_B	0x4d
+#define MT9M111_CP_OUTPUT_FORMAT_CTL2_CONTEXT_B 0x9b
+#define MT9M111_CP_GLOBAL_CLK_CONTROL		0xb3
+
+#define MT9M111_CC_AUTO_EXPOSURE_PARAMETER_18   0x65
+#define MT9M111_CC_AWB_PARAMETER_7		0x28
+
+#define MT9M111_SENSOR_CORE			0x00
+#define MT9M111_COLORPIPE			0x01
+#define MT9M111_CAMERA_CONTROL			0x02
+
+#define MT9M111_RESET				(1 << 0)
+#define MT9M111_RESTART				(1 << 1)
+#define MT9M111_ANALOG_STANDBY			(1 << 2)
+#define MT9M111_CHIP_ENABLE			(1 << 3)
+#define MT9M111_CHIP_DISABLE			(0 << 3)
+#define MT9M111_OUTPUT_DISABLE			(1 << 4)
+#define MT9M111_SHOW_BAD_FRAMES			(1 << 0)
+#define MT9M111_RESTART_BAD_FRAMES		(1 << 1)
+#define MT9M111_SYNCHRONIZE_CHANGES		(1 << 7)
+
+#define MT9M111_RMB_OVER_SIZED			(1 << 0)
+#define MT9M111_RMB_MIRROR_ROWS			(1 << 0)
+#define MT9M111_RMB_MIRROR_COLS			(1 << 1)
+#define MT9M111_RMB_ROW_SKIP_2X			(1 << 2)
+#define MT9M111_RMB_COLUMN_SKIP_2X		(1 << 3)
+#define MT9M111_RMB_ROW_SKIP_4X			(1 << 4)
+#define MT9M111_RMB_COLUMN_SKIP_4X		(1 << 5)
+
+#define MT9M111_COLOR_MATRIX_BYPASS		(1 << 4)
+#define MT9M111_SEL_CONTEXT_B			(1 << 3)
+
+#define MT9M111_TRISTATE_PIN_IN_STANDBY		(1 << 1)
+#define MT9M111_SOC_SOFT_STANDBY		(1 << 0)
+
+#define MT9M111_2D_DEFECT_CORRECTION_ENABLE	(1 << 0)
+
+#define INITIAL_MAX_GAIN			64
+#define MT9M111_DEFAULT_GAIN			283
+#define MT9M111_GREEN_GAIN_DEFAULT		0x20
+#define MT9M111_BLUE_GAIN_DEFAULT		0x20
+#define MT9M111_RED_GAIN_DEFAULT		0x20
+
+/*****************************************************************************/
+
+/* Kernel module parameters */
+extern int force_sensor;
+extern bool dump_sensor;
+
+int mt9m111_probe(struct sd *sd);
+int mt9m111_init(struct sd *sd);
+int mt9m111_init_controls(struct sd *sd);
+int mt9m111_start(struct sd *sd);
+void mt9m111_disconnect(struct sd *sd);
+
+static const struct m5602_sensor mt9m111 = {
+	.name = "MT9M111",
+
+	.i2c_slave_id = 0xba,
+	.i2c_regW = 2,
+
+	.probe = mt9m111_probe,
+	.init = mt9m111_init,
+	.init_controls = mt9m111_init_controls,
+	.disconnect = mt9m111_disconnect,
+	.start = mt9m111_start,
+};
+#endif
diff --git a/drivers/media/usb/gspca/m5602/m5602_ov7660.c b/drivers/media/usb/gspca/m5602/m5602_ov7660.c
new file mode 100644
index 0000000..aa1f569
--- /dev/null
+++ b/drivers/media/usb/gspca/m5602/m5602_ov7660.c
@@ -0,0 +1,472 @@
+/*
+ * Driver for the ov7660 sensor
+ *
+ * Copyright (C) 2009 Erik Andrén
+ * Copyright (C) 2007 Ilyes Gouta. Based on the m5603x Linux Driver Project.
+ * Copyright (C) 2005 m5603x Linux Driver Project <m5602@x3ng.com.br>
+ *
+ * Portions of code to USB interface and ALi driver software,
+ * Copyright (c) 2006 Willem Duinker
+ * v4l2 interface modeled after the V4L2 driver
+ * for SN9C10x PC Camera Controllers
+ *
+ * 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, version 2.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include "m5602_ov7660.h"
+
+static int ov7660_s_ctrl(struct v4l2_ctrl *ctrl);
+static void ov7660_dump_registers(struct sd *sd);
+
+static const unsigned char preinit_ov7660[][4] = {
+	{BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02},
+	{BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0},
+	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0},
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x0d},
+	{BRIDGE, M5602_XB_SENSOR_CTRL, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x03},
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x03},
+	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0},
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c},
+
+	{SENSOR, OV7660_OFON, 0x0c},
+	{SENSOR, OV7660_COM2, 0x11},
+	{SENSOR, OV7660_COM7, 0x05},
+
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x01},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x04},
+	{BRIDGE, M5602_XB_GPIO_EN_H, 0x06},
+	{BRIDGE, M5602_XB_GPIO_DIR_H, 0x06},
+	{BRIDGE, M5602_XB_GPIO_DAT_H, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x08},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0},
+	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0},
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c},
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x05},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x00},
+	{BRIDGE, M5602_XB_GPIO_EN_H, 0x06},
+	{BRIDGE, M5602_XB_GPIO_EN_L, 0x00}
+};
+
+static const unsigned char init_ov7660[][4] = {
+	{BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02},
+	{BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0},
+	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0},
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x0d},
+	{BRIDGE, M5602_XB_SENSOR_CTRL, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x01},
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x01},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0},
+	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0},
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c},
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x05},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x00},
+	{BRIDGE, M5602_XB_GPIO_EN_H, 0x06},
+	{BRIDGE, M5602_XB_GPIO_EN_L, 0x00},
+	{SENSOR, OV7660_COM7, 0x80},
+	{SENSOR, OV7660_CLKRC, 0x80},
+	{SENSOR, OV7660_COM9, 0x4c},
+	{SENSOR, OV7660_OFON, 0x43},
+	{SENSOR, OV7660_COM12, 0x28},
+	{SENSOR, OV7660_COM8, 0x00},
+	{SENSOR, OV7660_COM10, 0x40},
+	{SENSOR, OV7660_HSTART, 0x0c},
+	{SENSOR, OV7660_HSTOP, 0x61},
+	{SENSOR, OV7660_HREF, 0xa4},
+	{SENSOR, OV7660_PSHFT, 0x0b},
+	{SENSOR, OV7660_VSTART, 0x01},
+	{SENSOR, OV7660_VSTOP, 0x7a},
+	{SENSOR, OV7660_VSTOP, 0x00},
+	{SENSOR, OV7660_COM7, 0x05},
+	{SENSOR, OV7660_COM6, 0x42},
+	{SENSOR, OV7660_BBIAS, 0x94},
+	{SENSOR, OV7660_GbBIAS, 0x94},
+	{SENSOR, OV7660_RSVD29, 0x94},
+	{SENSOR, OV7660_RBIAS, 0x94},
+	{SENSOR, OV7660_COM1, 0x00},
+	{SENSOR, OV7660_AECH, 0x00},
+	{SENSOR, OV7660_AECHH, 0x00},
+	{SENSOR, OV7660_ADC, 0x05},
+	{SENSOR, OV7660_COM13, 0x00},
+	{SENSOR, OV7660_RSVDA1, 0x23},
+	{SENSOR, OV7660_TSLB, 0x0d},
+	{SENSOR, OV7660_HV, 0x80},
+	{SENSOR, OV7660_LCC1, 0x00},
+	{SENSOR, OV7660_LCC2, 0x00},
+	{SENSOR, OV7660_LCC3, 0x10},
+	{SENSOR, OV7660_LCC4, 0x40},
+	{SENSOR, OV7660_LCC5, 0x01},
+
+	{SENSOR, OV7660_AECH, 0x20},
+	{SENSOR, OV7660_COM1, 0x00},
+	{SENSOR, OV7660_OFON, 0x0c},
+	{SENSOR, OV7660_COM2, 0x11},
+	{SENSOR, OV7660_COM7, 0x05},
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x01},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x04},
+	{BRIDGE, M5602_XB_GPIO_EN_H, 0x06},
+	{BRIDGE, M5602_XB_GPIO_DIR_H, 0x06},
+	{BRIDGE, M5602_XB_GPIO_DAT_H, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x08},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0},
+	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0},
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c},
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x05},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x00},
+	{BRIDGE, M5602_XB_GPIO_EN_H, 0x06},
+	{BRIDGE, M5602_XB_GPIO_EN_L, 0x00},
+	{SENSOR, OV7660_AECH, 0x5f},
+	{SENSOR, OV7660_COM1, 0x03},
+	{SENSOR, OV7660_OFON, 0x0c},
+	{SENSOR, OV7660_COM2, 0x11},
+	{SENSOR, OV7660_COM7, 0x05},
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x01},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x04},
+	{BRIDGE, M5602_XB_GPIO_EN_H, 0x06},
+	{BRIDGE, M5602_XB_GPIO_DIR_H, 0x06},
+	{BRIDGE, M5602_XB_GPIO_DAT_H, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x08},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0},
+	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0},
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c},
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x05},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x00},
+	{BRIDGE, M5602_XB_GPIO_EN_H, 0x06},
+	{BRIDGE, M5602_XB_GPIO_EN_L, 0x00},
+
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x06},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0},
+	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0},
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c},
+	{BRIDGE, M5602_XB_LINE_OF_FRAME_H, 0x81},
+	{BRIDGE, M5602_XB_PIX_OF_LINE_H, 0x82},
+	{BRIDGE, M5602_XB_SIG_INI, 0x01},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x08},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x01},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0xec},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00},
+	{BRIDGE, M5602_XB_SIG_INI, 0x00},
+	{BRIDGE, M5602_XB_SIG_INI, 0x02},
+	{BRIDGE, M5602_XB_HSYNC_PARA, 0x00},
+	{BRIDGE, M5602_XB_HSYNC_PARA, 0x27},
+	{BRIDGE, M5602_XB_HSYNC_PARA, 0x02},
+	{BRIDGE, M5602_XB_HSYNC_PARA, 0xa7},
+	{BRIDGE, M5602_XB_SIG_INI, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0},
+};
+
+static struct v4l2_pix_format ov7660_modes[] = {
+	{
+		640,
+		480,
+		V4L2_PIX_FMT_SBGGR8,
+		V4L2_FIELD_NONE,
+		.sizeimage =
+			640 * 480,
+		.bytesperline = 640,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0
+	}
+};
+
+static const struct v4l2_ctrl_ops ov7660_ctrl_ops = {
+	.s_ctrl = ov7660_s_ctrl,
+};
+
+int ov7660_probe(struct sd *sd)
+{
+	int err = 0, i;
+	u8 prod_id = 0, ver_id = 0;
+
+	if (force_sensor) {
+		if (force_sensor == OV7660_SENSOR) {
+			pr_info("Forcing an %s sensor\n", ov7660.name);
+			goto sensor_found;
+		}
+		/* If we want to force another sensor,
+		don't try to probe this one */
+		return -ENODEV;
+	}
+
+	/* Do the preinit */
+	for (i = 0; i < ARRAY_SIZE(preinit_ov7660) && !err; i++) {
+		u8 data[2];
+
+		if (preinit_ov7660[i][0] == BRIDGE) {
+			err = m5602_write_bridge(sd,
+				preinit_ov7660[i][1],
+				preinit_ov7660[i][2]);
+		} else {
+			data[0] = preinit_ov7660[i][2];
+			err = m5602_write_sensor(sd,
+				preinit_ov7660[i][1], data, 1);
+		}
+	}
+	if (err < 0)
+		return err;
+
+	if (m5602_read_sensor(sd, OV7660_PID, &prod_id, 1))
+		return -ENODEV;
+
+	if (m5602_read_sensor(sd, OV7660_VER, &ver_id, 1))
+		return -ENODEV;
+
+	pr_info("Sensor reported 0x%x%x\n", prod_id, ver_id);
+
+	if ((prod_id == 0x76) && (ver_id == 0x60)) {
+		pr_info("Detected a ov7660 sensor\n");
+		goto sensor_found;
+	}
+	return -ENODEV;
+
+sensor_found:
+	sd->gspca_dev.cam.cam_mode = ov7660_modes;
+	sd->gspca_dev.cam.nmodes = ARRAY_SIZE(ov7660_modes);
+
+	return 0;
+}
+
+int ov7660_init(struct sd *sd)
+{
+	int i, err;
+
+	/* Init the sensor */
+	for (i = 0; i < ARRAY_SIZE(init_ov7660); i++) {
+		u8 data[2];
+
+		if (init_ov7660[i][0] == BRIDGE) {
+			err = m5602_write_bridge(sd,
+				init_ov7660[i][1],
+				init_ov7660[i][2]);
+		} else {
+			data[0] = init_ov7660[i][2];
+			err = m5602_write_sensor(sd,
+				init_ov7660[i][1], data, 1);
+		}
+		if (err < 0)
+			return err;
+	}
+
+	if (dump_sensor)
+		ov7660_dump_registers(sd);
+
+	return 0;
+}
+
+int ov7660_init_controls(struct sd *sd)
+{
+	struct v4l2_ctrl_handler *hdl = &sd->gspca_dev.ctrl_handler;
+
+	sd->gspca_dev.vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 6);
+
+	v4l2_ctrl_new_std(hdl, &ov7660_ctrl_ops, V4L2_CID_AUTO_WHITE_BALANCE,
+			  0, 1, 1, 1);
+	v4l2_ctrl_new_std_menu(hdl, &ov7660_ctrl_ops,
+			  V4L2_CID_EXPOSURE_AUTO, 1, 0, V4L2_EXPOSURE_AUTO);
+
+	sd->autogain = v4l2_ctrl_new_std(hdl, &ov7660_ctrl_ops,
+					 V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
+	sd->gain = v4l2_ctrl_new_std(hdl, &ov7660_ctrl_ops, V4L2_CID_GAIN, 0,
+				     255, 1, OV7660_DEFAULT_GAIN);
+
+	sd->hflip = v4l2_ctrl_new_std(hdl, &ov7660_ctrl_ops, V4L2_CID_HFLIP,
+				      0, 1, 1, 0);
+	sd->vflip = v4l2_ctrl_new_std(hdl, &ov7660_ctrl_ops, V4L2_CID_VFLIP,
+				      0, 1, 1, 0);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+
+	v4l2_ctrl_auto_cluster(2, &sd->autogain, 0, false);
+	v4l2_ctrl_cluster(2, &sd->hflip);
+
+	return 0;
+}
+
+int ov7660_start(struct sd *sd)
+{
+	return 0;
+}
+
+int ov7660_stop(struct sd *sd)
+{
+	return 0;
+}
+
+void ov7660_disconnect(struct sd *sd)
+{
+	ov7660_stop(sd);
+
+	sd->sensor = NULL;
+}
+
+static int ov7660_set_gain(struct gspca_dev *gspca_dev, __s32 val)
+{
+	int err;
+	u8 i2c_data = val;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	gspca_dbg(gspca_dev, D_CONF, "Setting gain to %d\n", val);
+
+	err = m5602_write_sensor(sd, OV7660_GAIN, &i2c_data, 1);
+	return err;
+}
+
+static int ov7660_set_auto_white_balance(struct gspca_dev *gspca_dev,
+					 __s32 val)
+{
+	int err;
+	u8 i2c_data;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set auto white balance to %d\n", val);
+
+	err = m5602_read_sensor(sd, OV7660_COM8, &i2c_data, 1);
+	if (err < 0)
+		return err;
+
+	i2c_data = ((i2c_data & 0xfd) | ((val & 0x01) << 1));
+	err = m5602_write_sensor(sd, OV7660_COM8, &i2c_data, 1);
+
+	return err;
+}
+
+static int ov7660_set_auto_gain(struct gspca_dev *gspca_dev, __s32 val)
+{
+	int err;
+	u8 i2c_data;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set auto gain control to %d\n", val);
+
+	err = m5602_read_sensor(sd, OV7660_COM8, &i2c_data, 1);
+	if (err < 0)
+		return err;
+
+	i2c_data = ((i2c_data & 0xfb) | ((val & 0x01) << 2));
+
+	return m5602_write_sensor(sd, OV7660_COM8, &i2c_data, 1);
+}
+
+static int ov7660_set_auto_exposure(struct gspca_dev *gspca_dev,
+				    __s32 val)
+{
+	int err;
+	u8 i2c_data;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set auto exposure control to %d\n", val);
+
+	err = m5602_read_sensor(sd, OV7660_COM8, &i2c_data, 1);
+	if (err < 0)
+		return err;
+
+	val = (val == V4L2_EXPOSURE_AUTO);
+	i2c_data = ((i2c_data & 0xfe) | ((val & 0x01) << 0));
+
+	return m5602_write_sensor(sd, OV7660_COM8, &i2c_data, 1);
+}
+
+static int ov7660_set_hvflip(struct gspca_dev *gspca_dev)
+{
+	int err;
+	u8 i2c_data;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set hvflip to %d, %d\n",
+		  sd->hflip->val, sd->vflip->val);
+
+	i2c_data = (sd->hflip->val << 5) | (sd->vflip->val << 4);
+
+	err = m5602_write_sensor(sd, OV7660_MVFP, &i2c_data, 1);
+
+	return err;
+}
+
+static int ov7660_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *) gspca_dev;
+	int err;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_AUTO_WHITE_BALANCE:
+		err = ov7660_set_auto_white_balance(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_EXPOSURE_AUTO:
+		err = ov7660_set_auto_exposure(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_AUTOGAIN:
+		err = ov7660_set_auto_gain(gspca_dev, ctrl->val);
+		if (err || ctrl->val)
+			return err;
+		err = ov7660_set_gain(gspca_dev, sd->gain->val);
+		break;
+	case V4L2_CID_HFLIP:
+		err = ov7660_set_hvflip(gspca_dev);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return err;
+}
+
+static void ov7660_dump_registers(struct sd *sd)
+{
+	int address;
+	pr_info("Dumping the ov7660 register state\n");
+	for (address = 0; address < 0xa9; address++) {
+		u8 value;
+		m5602_read_sensor(sd, address, &value, 1);
+		pr_info("register 0x%x contains 0x%x\n", address, value);
+	}
+
+	pr_info("ov7660 register state dump complete\n");
+
+	pr_info("Probing for which registers that are read/write\n");
+	for (address = 0; address < 0xff; address++) {
+		u8 old_value, ctrl_value;
+		u8 test_value[2] = {0xff, 0xff};
+
+		m5602_read_sensor(sd, address, &old_value, 1);
+		m5602_write_sensor(sd, address, test_value, 1);
+		m5602_read_sensor(sd, address, &ctrl_value, 1);
+
+		if (ctrl_value == test_value[0])
+			pr_info("register 0x%x is writeable\n", address);
+		else
+			pr_info("register 0x%x is read only\n", address);
+
+		/* Restore original value */
+		m5602_write_sensor(sd, address, &old_value, 1);
+	}
+}
diff --git a/drivers/media/usb/gspca/m5602/m5602_ov7660.h b/drivers/media/usb/gspca/m5602/m5602_ov7660.h
new file mode 100644
index 0000000..72445d5
--- /dev/null
+++ b/drivers/media/usb/gspca/m5602/m5602_ov7660.h
@@ -0,0 +1,110 @@
+/*
+ * Driver for the ov7660 sensor
+ *
+ * Copyright (C) 2009 Erik Andrén
+ * Copyright (C) 2007 Ilyes Gouta. Based on the m5603x Linux Driver Project.
+ * Copyright (C) 2005 m5603x Linux Driver Project <m5602@x3ng.com.br>
+ *
+ * Portions of code to USB interface and ALi driver software,
+ * Copyright (c) 2006 Willem Duinker
+ * v4l2 interface modeled after the V4L2 driver
+ * for SN9C10x PC Camera Controllers
+ *
+ * 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, version 2.
+ *
+ */
+
+#ifndef M5602_OV7660_H_
+#define M5602_OV7660_H_
+
+#include "m5602_sensor.h"
+
+#define OV7660_GAIN		0x00
+#define OV7660_BLUE_GAIN	0x01
+#define OV7660_RED_GAIN		0x02
+#define OV7660_VREF		0x03
+#define OV7660_COM1		0x04
+#define OV7660_BAVE		0x05
+#define OV7660_GEAVE		0x06
+#define OV7660_AECHH		0x07
+#define OV7660_RAVE		0x08
+#define OV7660_COM2		0x09
+#define OV7660_PID		0x0a
+#define OV7660_VER		0x0b
+#define OV7660_COM3		0x0c
+#define OV7660_COM4		0x0d
+#define OV7660_COM5		0x0e
+#define OV7660_COM6		0x0f
+#define OV7660_AECH		0x10
+#define OV7660_CLKRC		0x11
+#define OV7660_COM7		0x12
+#define OV7660_COM8		0x13
+#define OV7660_COM9		0x14
+#define OV7660_COM10		0x15
+#define OV7660_RSVD16		0x16
+#define OV7660_HSTART		0x17
+#define OV7660_HSTOP		0x18
+#define OV7660_VSTART		0x19
+#define OV7660_VSTOP		0x1a
+#define OV7660_PSHFT		0x1b
+#define OV7660_MIDH		0x1c
+#define OV7660_MIDL		0x1d
+#define OV7660_MVFP		0x1e
+#define OV7660_LAEC		0x1f
+#define OV7660_BOS		0x20
+#define OV7660_GBOS		0x21
+#define OV7660_GROS		0x22
+#define OV7660_ROS		0x23
+#define OV7660_AEW		0x24
+#define OV7660_AEB		0x25
+#define OV7660_VPT		0x26
+#define OV7660_BBIAS		0x27
+#define OV7660_GbBIAS		0x28
+#define OV7660_RSVD29		0x29
+#define OV7660_RBIAS		0x2c
+#define OV7660_HREF		0x32
+#define OV7660_ADC		0x37
+#define OV7660_OFON		0x39
+#define OV7660_TSLB		0x3a
+#define OV7660_COM12		0x3c
+#define OV7660_COM13		0x3d
+#define OV7660_LCC1		0x62
+#define OV7660_LCC2		0x63
+#define OV7660_LCC3		0x64
+#define OV7660_LCC4		0x65
+#define OV7660_LCC5		0x66
+#define OV7660_HV		0x69
+#define OV7660_RSVDA1		0xa1
+
+#define OV7660_DEFAULT_GAIN		0x0e
+#define OV7660_DEFAULT_RED_GAIN		0x80
+#define OV7660_DEFAULT_BLUE_GAIN	0x80
+#define OV7660_DEFAULT_SATURATION	0x00
+#define OV7660_DEFAULT_EXPOSURE		0x20
+
+/* Kernel module parameters */
+extern int force_sensor;
+extern bool dump_sensor;
+
+int ov7660_probe(struct sd *sd);
+int ov7660_init(struct sd *sd);
+int ov7660_init(struct sd *sd);
+int ov7660_init_controls(struct sd *sd);
+int ov7660_start(struct sd *sd);
+int ov7660_stop(struct sd *sd);
+void ov7660_disconnect(struct sd *sd);
+
+static const struct m5602_sensor ov7660 = {
+	.name = "ov7660",
+	.i2c_slave_id = 0x42,
+	.i2c_regW = 1,
+	.probe = ov7660_probe,
+	.init = ov7660_init,
+	.init_controls = ov7660_init_controls,
+	.start = ov7660_start,
+	.stop = ov7660_stop,
+	.disconnect = ov7660_disconnect,
+};
+#endif
diff --git a/drivers/media/usb/gspca/m5602/m5602_ov9650.c b/drivers/media/usb/gspca/m5602/m5602_ov9650.c
new file mode 100644
index 0000000..2ffbb54
--- /dev/null
+++ b/drivers/media/usb/gspca/m5602/m5602_ov9650.c
@@ -0,0 +1,788 @@
+
+/*
+ * Driver for the ov9650 sensor
+ *
+ * Copyright (C) 2008 Erik Andrén
+ * Copyright (C) 2007 Ilyes Gouta. Based on the m5603x Linux Driver Project.
+ * Copyright (C) 2005 m5603x Linux Driver Project <m5602@x3ng.com.br>
+ *
+ * Portions of code to USB interface and ALi driver software,
+ * Copyright (c) 2006 Willem Duinker
+ * v4l2 interface modeled after the V4L2 driver
+ * for SN9C10x PC Camera Controllers
+ *
+ * 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, version 2.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include "m5602_ov9650.h"
+
+static int ov9650_s_ctrl(struct v4l2_ctrl *ctrl);
+static void ov9650_dump_registers(struct sd *sd);
+
+static const unsigned char preinit_ov9650[][3] = {
+	/* [INITCAM] */
+	{BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02},
+	{BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0},
+	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0},
+	{BRIDGE, M5602_XB_SENSOR_CTRL, 0x00},
+
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x08},
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x05},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x04},
+	{BRIDGE, M5602_XB_GPIO_EN_H, 0x06},
+	{BRIDGE, M5602_XB_GPIO_DIR_H, 0x06},
+	{BRIDGE, M5602_XB_GPIO_DAT_H, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x00},
+	{BRIDGE, M5602_XB_I2C_CLK_DIV, 0x0a},
+	/* Reset chip */
+	{SENSOR, OV9650_COM7, OV9650_REGISTER_RESET},
+	/* Enable double clock */
+	{SENSOR, OV9650_CLKRC, 0x80},
+	/* Do something out of spec with the power */
+	{SENSOR, OV9650_OFON, 0x40}
+};
+
+static const unsigned char init_ov9650[][3] = {
+	/* [INITCAM] */
+	{BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02},
+	{BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0},
+	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0},
+	{BRIDGE, M5602_XB_SENSOR_CTRL, 0x00},
+
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x08},
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x05},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x04},
+	{BRIDGE, M5602_XB_GPIO_EN_H, 0x06},
+	{BRIDGE, M5602_XB_GPIO_DIR_H, 0x06},
+	{BRIDGE, M5602_XB_GPIO_DAT_H, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x00},
+	{BRIDGE, M5602_XB_I2C_CLK_DIV, 0x0a},
+
+	/* Reset chip */
+	{SENSOR, OV9650_COM7, OV9650_REGISTER_RESET},
+	/* One extra reset is needed in order to make the sensor behave
+	   properly when resuming from ram, could be a timing issue */
+	{SENSOR, OV9650_COM7, OV9650_REGISTER_RESET},
+
+	/* Enable double clock */
+	{SENSOR, OV9650_CLKRC, 0x80},
+	/* Do something out of spec with the power */
+	{SENSOR, OV9650_OFON, 0x40},
+
+	/* Set fast AGC/AEC algorithm with unlimited step size */
+	{SENSOR, OV9650_COM8, OV9650_FAST_AGC_AEC |
+			      OV9650_AEC_UNLIM_STEP_SIZE},
+
+	{SENSOR, OV9650_CHLF, 0x10},
+	{SENSOR, OV9650_ARBLM, 0xbf},
+	{SENSOR, OV9650_ACOM38, 0x81},
+	/* Turn off color matrix coefficient double option */
+	{SENSOR, OV9650_COM16, 0x00},
+	/* Enable color matrix for RGB/YUV, Delay Y channel,
+	set output Y/UV delay to 1 */
+	{SENSOR, OV9650_COM13, 0x19},
+	/* Enable digital BLC, Set output mode to U Y V Y */
+	{SENSOR, OV9650_TSLB, 0x0c},
+	/* Limit the AGC/AEC stable upper region */
+	{SENSOR, OV9650_COM24, 0x00},
+	/* Enable HREF and some out of spec things */
+	{SENSOR, OV9650_COM12, 0x73},
+	/* Set all DBLC offset signs to positive and
+	do some out of spec stuff */
+	{SENSOR, OV9650_DBLC1, 0xdf},
+	{SENSOR, OV9650_COM21, 0x06},
+	{SENSOR, OV9650_RSVD35, 0x91},
+	/* Necessary, no camera stream without it */
+	{SENSOR, OV9650_RSVD16, 0x06},
+	{SENSOR, OV9650_RSVD94, 0x99},
+	{SENSOR, OV9650_RSVD95, 0x99},
+	{SENSOR, OV9650_RSVD96, 0x04},
+	/* Enable full range output */
+	{SENSOR, OV9650_COM15, 0x0},
+	/* Enable HREF at optical black, enable ADBLC bias,
+	enable ADBLC, reset timings at format change */
+	{SENSOR, OV9650_COM6, 0x4b},
+	/* Subtract 32 from the B channel bias */
+	{SENSOR, OV9650_BBIAS, 0xa0},
+	/* Subtract 32 from the Gb channel bias */
+	{SENSOR, OV9650_GbBIAS, 0xa0},
+	/* Do not bypass the analog BLC and to some out of spec stuff */
+	{SENSOR, OV9650_Gr_COM, 0x00},
+	/* Subtract 32 from the R channel bias */
+	{SENSOR, OV9650_RBIAS, 0xa0},
+	/* Subtract 32 from the R channel bias */
+	{SENSOR, OV9650_RBIAS, 0x0},
+	{SENSOR, OV9650_COM26, 0x80},
+	{SENSOR, OV9650_ACOMA9, 0x98},
+	/* Set the AGC/AEC stable region upper limit */
+	{SENSOR, OV9650_AEW, 0x68},
+	/* Set the AGC/AEC stable region lower limit */
+	{SENSOR, OV9650_AEB, 0x5c},
+	/* Set the high and low limit nibbles to 3 */
+	{SENSOR, OV9650_VPT, 0xc3},
+	/* Set the Automatic Gain Ceiling (AGC) to 128x,
+	drop VSYNC at frame drop,
+	limit exposure timing,
+	drop frame when the AEC step is larger than the exposure gap */
+	{SENSOR, OV9650_COM9, 0x6e},
+	/* Set VSYNC negative, Set RESET to SLHS (slave mode horizontal sync)
+	and set PWDN to SLVS (slave mode vertical sync) */
+	{SENSOR, OV9650_COM10, 0x42},
+	/* Set horizontal column start high to default value */
+	{SENSOR, OV9650_HSTART, 0x1a}, /* 210 */
+	/* Set horizontal column end */
+	{SENSOR, OV9650_HSTOP, 0xbf}, /* 1534 */
+	/* Complementing register to the two writes above */
+	{SENSOR, OV9650_HREF, 0xb2},
+	/* Set vertical row start high bits */
+	{SENSOR, OV9650_VSTRT, 0x02},
+	/* Set vertical row end low bits */
+	{SENSOR, OV9650_VSTOP, 0x7e},
+	/* Set complementing vertical frame control */
+	{SENSOR, OV9650_VREF, 0x10},
+	{SENSOR, OV9650_ADC, 0x04},
+	{SENSOR, OV9650_HV, 0x40},
+
+	/* Enable denoise, and white-pixel erase */
+	{SENSOR, OV9650_COM22, OV9650_DENOISE_ENABLE |
+		 OV9650_WHITE_PIXEL_ENABLE |
+		 OV9650_WHITE_PIXEL_OPTION},
+
+	/* Enable VARIOPIXEL */
+	{SENSOR, OV9650_COM3, OV9650_VARIOPIXEL},
+	{SENSOR, OV9650_COM4, OV9650_QVGA_VARIOPIXEL},
+
+	/* Put the sensor in soft sleep mode */
+	{SENSOR, OV9650_COM2, OV9650_SOFT_SLEEP | OV9650_OUTPUT_DRIVE_2X},
+};
+
+static const unsigned char res_init_ov9650[][3] = {
+	{SENSOR, OV9650_COM2, OV9650_OUTPUT_DRIVE_2X},
+
+	{BRIDGE, M5602_XB_LINE_OF_FRAME_H, 0x82},
+	{BRIDGE, M5602_XB_LINE_OF_FRAME_L, 0x00},
+	{BRIDGE, M5602_XB_PIX_OF_LINE_H, 0x82},
+	{BRIDGE, M5602_XB_PIX_OF_LINE_L, 0x00},
+	{BRIDGE, M5602_XB_SIG_INI, 0x01}
+};
+
+/* Vertically and horizontally flips the image if matched, needed for machines
+   where the sensor is mounted upside down */
+static
+    const
+	struct dmi_system_id ov9650_flip_dmi_table[] = {
+	{
+		.ident = "ASUS A6Ja",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "A6J")
+		}
+	},
+	{
+		.ident = "ASUS A6JC",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "A6JC")
+		}
+	},
+	{
+		.ident = "ASUS A6K",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "A6K")
+		}
+	},
+	{
+		.ident = "ASUS A6Kt",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "A6Kt")
+		}
+	},
+	{
+		.ident = "ASUS A6VA",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "A6VA")
+		}
+	},
+	{
+
+		.ident = "ASUS A6VC",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "A6VC")
+		}
+	},
+	{
+		.ident = "ASUS A6VM",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "A6VM")
+		}
+	},
+	{
+		.ident = "ASUS A7V",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "A7V")
+		}
+	},
+	{
+		.ident = "Alienware Aurora m9700",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "alienware"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Aurora m9700")
+		}
+	},
+	{}
+};
+
+static struct v4l2_pix_format ov9650_modes[] = {
+	{
+		176,
+		144,
+		V4L2_PIX_FMT_SBGGR8,
+		V4L2_FIELD_NONE,
+		.sizeimage =
+			176 * 144,
+		.bytesperline = 176,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 9
+	}, {
+		320,
+		240,
+		V4L2_PIX_FMT_SBGGR8,
+		V4L2_FIELD_NONE,
+		.sizeimage =
+			320 * 240,
+		.bytesperline = 320,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 8
+	}, {
+		352,
+		288,
+		V4L2_PIX_FMT_SBGGR8,
+		V4L2_FIELD_NONE,
+		.sizeimage =
+			352 * 288,
+		.bytesperline = 352,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 9
+	}, {
+		640,
+		480,
+		V4L2_PIX_FMT_SBGGR8,
+		V4L2_FIELD_NONE,
+		.sizeimage =
+			640 * 480,
+		.bytesperline = 640,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 9
+	}
+};
+
+static const struct v4l2_ctrl_ops ov9650_ctrl_ops = {
+	.s_ctrl = ov9650_s_ctrl,
+};
+
+int ov9650_probe(struct sd *sd)
+{
+	int err = 0;
+	u8 prod_id = 0, ver_id = 0, i;
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+
+	if (force_sensor) {
+		if (force_sensor == OV9650_SENSOR) {
+			pr_info("Forcing an %s sensor\n", ov9650.name);
+			goto sensor_found;
+		}
+		/* If we want to force another sensor,
+		   don't try to probe this one */
+		return -ENODEV;
+	}
+
+	gspca_dbg(gspca_dev, D_PROBE, "Probing for an ov9650 sensor\n");
+
+	/* Run the pre-init before probing the sensor */
+	for (i = 0; i < ARRAY_SIZE(preinit_ov9650) && !err; i++) {
+		u8 data = preinit_ov9650[i][2];
+		if (preinit_ov9650[i][0] == SENSOR)
+			err = m5602_write_sensor(sd,
+				preinit_ov9650[i][1], &data, 1);
+		else
+			err = m5602_write_bridge(sd,
+				preinit_ov9650[i][1], data);
+	}
+
+	if (err < 0)
+		return err;
+
+	if (m5602_read_sensor(sd, OV9650_PID, &prod_id, 1))
+		return -ENODEV;
+
+	if (m5602_read_sensor(sd, OV9650_VER, &ver_id, 1))
+		return -ENODEV;
+
+	if ((prod_id == 0x96) && (ver_id == 0x52)) {
+		pr_info("Detected an ov9650 sensor\n");
+		goto sensor_found;
+	}
+	return -ENODEV;
+
+sensor_found:
+	sd->gspca_dev.cam.cam_mode = ov9650_modes;
+	sd->gspca_dev.cam.nmodes = ARRAY_SIZE(ov9650_modes);
+
+	return 0;
+}
+
+int ov9650_init(struct sd *sd)
+{
+	int i, err = 0;
+	u8 data;
+
+	if (dump_sensor)
+		ov9650_dump_registers(sd);
+
+	for (i = 0; i < ARRAY_SIZE(init_ov9650) && !err; i++) {
+		data = init_ov9650[i][2];
+		if (init_ov9650[i][0] == SENSOR)
+			err = m5602_write_sensor(sd, init_ov9650[i][1],
+						  &data, 1);
+		else
+			err = m5602_write_bridge(sd, init_ov9650[i][1], data);
+	}
+
+	return 0;
+}
+
+int ov9650_init_controls(struct sd *sd)
+{
+	struct v4l2_ctrl_handler *hdl = &sd->gspca_dev.ctrl_handler;
+
+	sd->gspca_dev.vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 9);
+
+	sd->auto_white_bal = v4l2_ctrl_new_std(hdl, &ov9650_ctrl_ops,
+					       V4L2_CID_AUTO_WHITE_BALANCE,
+					       0, 1, 1, 1);
+	sd->red_bal = v4l2_ctrl_new_std(hdl, &ov9650_ctrl_ops,
+					V4L2_CID_RED_BALANCE, 0, 255, 1,
+					RED_GAIN_DEFAULT);
+	sd->blue_bal = v4l2_ctrl_new_std(hdl, &ov9650_ctrl_ops,
+					V4L2_CID_BLUE_BALANCE, 0, 255, 1,
+					BLUE_GAIN_DEFAULT);
+
+	sd->autoexpo = v4l2_ctrl_new_std_menu(hdl, &ov9650_ctrl_ops,
+			  V4L2_CID_EXPOSURE_AUTO, 1, 0, V4L2_EXPOSURE_AUTO);
+	sd->expo = v4l2_ctrl_new_std(hdl, &ov9650_ctrl_ops, V4L2_CID_EXPOSURE,
+			  0, 0x1ff, 4, EXPOSURE_DEFAULT);
+
+	sd->autogain = v4l2_ctrl_new_std(hdl, &ov9650_ctrl_ops,
+					 V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
+	sd->gain = v4l2_ctrl_new_std(hdl, &ov9650_ctrl_ops, V4L2_CID_GAIN, 0,
+				     0x3ff, 1, GAIN_DEFAULT);
+
+	sd->hflip = v4l2_ctrl_new_std(hdl, &ov9650_ctrl_ops, V4L2_CID_HFLIP,
+				      0, 1, 1, 0);
+	sd->vflip = v4l2_ctrl_new_std(hdl, &ov9650_ctrl_ops, V4L2_CID_VFLIP,
+				      0, 1, 1, 0);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+
+	v4l2_ctrl_auto_cluster(3, &sd->auto_white_bal, 0, false);
+	v4l2_ctrl_auto_cluster(2, &sd->autoexpo, 0, false);
+	v4l2_ctrl_auto_cluster(2, &sd->autogain, 0, false);
+	v4l2_ctrl_cluster(2, &sd->hflip);
+
+	return 0;
+}
+
+int ov9650_start(struct sd *sd)
+{
+	u8 data;
+	int i, err = 0;
+	struct cam *cam = &sd->gspca_dev.cam;
+
+	int width = cam->cam_mode[sd->gspca_dev.curr_mode].width;
+	int height = cam->cam_mode[sd->gspca_dev.curr_mode].height;
+	int ver_offs = cam->cam_mode[sd->gspca_dev.curr_mode].priv;
+	int hor_offs = OV9650_LEFT_OFFSET;
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+
+	if ((!dmi_check_system(ov9650_flip_dmi_table) &&
+		sd->vflip->val) ||
+		(dmi_check_system(ov9650_flip_dmi_table) &&
+		!sd->vflip->val))
+		ver_offs--;
+
+	if (width <= 320)
+		hor_offs /= 2;
+
+	/* Synthesize the vsync/hsync setup */
+	for (i = 0; i < ARRAY_SIZE(res_init_ov9650) && !err; i++) {
+		if (res_init_ov9650[i][0] == BRIDGE)
+			err = m5602_write_bridge(sd, res_init_ov9650[i][1],
+				res_init_ov9650[i][2]);
+		else if (res_init_ov9650[i][0] == SENSOR) {
+			data = res_init_ov9650[i][2];
+			err = m5602_write_sensor(sd,
+				res_init_ov9650[i][1], &data, 1);
+		}
+	}
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_VSYNC_PARA,
+				 ((ver_offs >> 8) & 0xff));
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_VSYNC_PARA, (ver_offs & 0xff));
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_VSYNC_PARA, 0);
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_VSYNC_PARA, (height >> 8) & 0xff);
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_VSYNC_PARA, (height & 0xff));
+	if (err < 0)
+		return err;
+
+	for (i = 0; i < 2 && !err; i++)
+		err = m5602_write_bridge(sd, M5602_XB_VSYNC_PARA, 0);
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_SIG_INI, 0);
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_SIG_INI, 2);
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_HSYNC_PARA,
+				 (hor_offs >> 8) & 0xff);
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_HSYNC_PARA, hor_offs & 0xff);
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_HSYNC_PARA,
+				 ((width + hor_offs) >> 8) & 0xff);
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_HSYNC_PARA,
+				 ((width + hor_offs) & 0xff));
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_SIG_INI, 0);
+	if (err < 0)
+		return err;
+
+	switch (width) {
+	case 640:
+		gspca_dbg(gspca_dev, D_CONF, "Configuring camera for VGA mode\n");
+
+		data = OV9650_VGA_SELECT | OV9650_RGB_SELECT |
+		       OV9650_RAW_RGB_SELECT;
+		err = m5602_write_sensor(sd, OV9650_COM7, &data, 1);
+		break;
+
+	case 352:
+		gspca_dbg(gspca_dev, D_CONF, "Configuring camera for CIF mode\n");
+
+		data = OV9650_CIF_SELECT | OV9650_RGB_SELECT |
+				OV9650_RAW_RGB_SELECT;
+		err = m5602_write_sensor(sd, OV9650_COM7, &data, 1);
+		break;
+
+	case 320:
+		gspca_dbg(gspca_dev, D_CONF, "Configuring camera for QVGA mode\n");
+
+		data = OV9650_QVGA_SELECT | OV9650_RGB_SELECT |
+				OV9650_RAW_RGB_SELECT;
+		err = m5602_write_sensor(sd, OV9650_COM7, &data, 1);
+		break;
+
+	case 176:
+		gspca_dbg(gspca_dev, D_CONF, "Configuring camera for QCIF mode\n");
+
+		data = OV9650_QCIF_SELECT | OV9650_RGB_SELECT |
+			OV9650_RAW_RGB_SELECT;
+		err = m5602_write_sensor(sd, OV9650_COM7, &data, 1);
+		break;
+	}
+	return err;
+}
+
+int ov9650_stop(struct sd *sd)
+{
+	u8 data = OV9650_SOFT_SLEEP | OV9650_OUTPUT_DRIVE_2X;
+	return m5602_write_sensor(sd, OV9650_COM2, &data, 1);
+}
+
+void ov9650_disconnect(struct sd *sd)
+{
+	ov9650_stop(sd);
+
+	sd->sensor = NULL;
+}
+
+static int ov9650_set_exposure(struct gspca_dev *gspca_dev, __s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 i2c_data;
+	int err;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set exposure to %d\n", val);
+
+	/* The 6 MSBs */
+	i2c_data = (val >> 10) & 0x3f;
+	err = m5602_write_sensor(sd, OV9650_AECHM,
+				  &i2c_data, 1);
+	if (err < 0)
+		return err;
+
+	/* The 8 middle bits */
+	i2c_data = (val >> 2) & 0xff;
+	err = m5602_write_sensor(sd, OV9650_AECH,
+				  &i2c_data, 1);
+	if (err < 0)
+		return err;
+
+	/* The 2 LSBs */
+	i2c_data = val & 0x03;
+	err = m5602_write_sensor(sd, OV9650_COM1, &i2c_data, 1);
+	return err;
+}
+
+static int ov9650_set_gain(struct gspca_dev *gspca_dev, __s32 val)
+{
+	int err;
+	u8 i2c_data;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	gspca_dbg(gspca_dev, D_CONF, "Setting gain to %d\n", val);
+
+	/* The 2 MSB */
+	/* Read the OV9650_VREF register first to avoid
+	   corrupting the VREF high and low bits */
+	err = m5602_read_sensor(sd, OV9650_VREF, &i2c_data, 1);
+	if (err < 0)
+		return err;
+
+	/* Mask away all uninteresting bits */
+	i2c_data = ((val & 0x0300) >> 2) |
+			(i2c_data & 0x3f);
+	err = m5602_write_sensor(sd, OV9650_VREF, &i2c_data, 1);
+	if (err < 0)
+		return err;
+
+	/* The 8 LSBs */
+	i2c_data = val & 0xff;
+	err = m5602_write_sensor(sd, OV9650_GAIN, &i2c_data, 1);
+	return err;
+}
+
+static int ov9650_set_red_balance(struct gspca_dev *gspca_dev, __s32 val)
+{
+	int err;
+	u8 i2c_data;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set red gain to %d\n", val);
+
+	i2c_data = val & 0xff;
+	err = m5602_write_sensor(sd, OV9650_RED, &i2c_data, 1);
+	return err;
+}
+
+static int ov9650_set_blue_balance(struct gspca_dev *gspca_dev, __s32 val)
+{
+	int err;
+	u8 i2c_data;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set blue gain to %d\n", val);
+
+	i2c_data = val & 0xff;
+	err = m5602_write_sensor(sd, OV9650_BLUE, &i2c_data, 1);
+	return err;
+}
+
+static int ov9650_set_hvflip(struct gspca_dev *gspca_dev)
+{
+	int err;
+	u8 i2c_data;
+	struct sd *sd = (struct sd *) gspca_dev;
+	int hflip = sd->hflip->val;
+	int vflip = sd->vflip->val;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set hvflip to %d %d\n", hflip, vflip);
+
+	if (dmi_check_system(ov9650_flip_dmi_table))
+		vflip = !vflip;
+
+	i2c_data = (hflip << 5) | (vflip << 4);
+	err = m5602_write_sensor(sd, OV9650_MVFP, &i2c_data, 1);
+	if (err < 0)
+		return err;
+
+	/* When vflip is toggled we need to readjust the bridge hsync/vsync */
+	if (gspca_dev->streaming)
+		err = ov9650_start(sd);
+
+	return err;
+}
+
+static int ov9650_set_auto_exposure(struct gspca_dev *gspca_dev,
+				    __s32 val)
+{
+	int err;
+	u8 i2c_data;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set auto exposure control to %d\n", val);
+
+	err = m5602_read_sensor(sd, OV9650_COM8, &i2c_data, 1);
+	if (err < 0)
+		return err;
+
+	val = (val == V4L2_EXPOSURE_AUTO);
+	i2c_data = ((i2c_data & 0xfe) | ((val & 0x01) << 0));
+
+	return m5602_write_sensor(sd, OV9650_COM8, &i2c_data, 1);
+}
+
+static int ov9650_set_auto_white_balance(struct gspca_dev *gspca_dev,
+					 __s32 val)
+{
+	int err;
+	u8 i2c_data;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set auto white balance to %d\n", val);
+
+	err = m5602_read_sensor(sd, OV9650_COM8, &i2c_data, 1);
+	if (err < 0)
+		return err;
+
+	i2c_data = ((i2c_data & 0xfd) | ((val & 0x01) << 1));
+	err = m5602_write_sensor(sd, OV9650_COM8, &i2c_data, 1);
+
+	return err;
+}
+
+static int ov9650_set_auto_gain(struct gspca_dev *gspca_dev, __s32 val)
+{
+	int err;
+	u8 i2c_data;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set auto gain control to %d\n", val);
+
+	err = m5602_read_sensor(sd, OV9650_COM8, &i2c_data, 1);
+	if (err < 0)
+		return err;
+
+	i2c_data = ((i2c_data & 0xfb) | ((val & 0x01) << 2));
+
+	return m5602_write_sensor(sd, OV9650_COM8, &i2c_data, 1);
+}
+
+static int ov9650_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *) gspca_dev;
+	int err;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_AUTO_WHITE_BALANCE:
+		err = ov9650_set_auto_white_balance(gspca_dev, ctrl->val);
+		if (err || ctrl->val)
+			return err;
+		err = ov9650_set_red_balance(gspca_dev, sd->red_bal->val);
+		if (err)
+			return err;
+		err = ov9650_set_blue_balance(gspca_dev, sd->blue_bal->val);
+		break;
+	case V4L2_CID_EXPOSURE_AUTO:
+		err = ov9650_set_auto_exposure(gspca_dev, ctrl->val);
+		if (err || ctrl->val == V4L2_EXPOSURE_AUTO)
+			return err;
+		err = ov9650_set_exposure(gspca_dev, sd->expo->val);
+		break;
+	case V4L2_CID_AUTOGAIN:
+		err = ov9650_set_auto_gain(gspca_dev, ctrl->val);
+		if (err || ctrl->val)
+			return err;
+		err = ov9650_set_gain(gspca_dev, sd->gain->val);
+		break;
+	case V4L2_CID_HFLIP:
+		err = ov9650_set_hvflip(gspca_dev);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return err;
+}
+
+static void ov9650_dump_registers(struct sd *sd)
+{
+	int address;
+	pr_info("Dumping the ov9650 register state\n");
+	for (address = 0; address < 0xa9; address++) {
+		u8 value;
+		m5602_read_sensor(sd, address, &value, 1);
+		pr_info("register 0x%x contains 0x%x\n", address, value);
+	}
+
+	pr_info("ov9650 register state dump complete\n");
+
+	pr_info("Probing for which registers that are read/write\n");
+	for (address = 0; address < 0xff; address++) {
+		u8 old_value, ctrl_value;
+		u8 test_value[2] = {0xff, 0xff};
+
+		m5602_read_sensor(sd, address, &old_value, 1);
+		m5602_write_sensor(sd, address, test_value, 1);
+		m5602_read_sensor(sd, address, &ctrl_value, 1);
+
+		if (ctrl_value == test_value[0])
+			pr_info("register 0x%x is writeable\n", address);
+		else
+			pr_info("register 0x%x is read only\n", address);
+
+		/* Restore original value */
+		m5602_write_sensor(sd, address, &old_value, 1);
+	}
+}
diff --git a/drivers/media/usb/gspca/m5602/m5602_ov9650.h b/drivers/media/usb/gspca/m5602/m5602_ov9650.h
new file mode 100644
index 0000000..ce3db06
--- /dev/null
+++ b/drivers/media/usb/gspca/m5602/m5602_ov9650.h
@@ -0,0 +1,159 @@
+/*
+ * Driver for the ov9650 sensor
+ *
+ * Copyright (C) 2008 Erik Andrén
+ * Copyright (C) 2007 Ilyes Gouta. Based on the m5603x Linux Driver Project.
+ * Copyright (C) 2005 m5603x Linux Driver Project <m5602@x3ng.com.br>
+ *
+ * Portions of code to USB interface and ALi driver software,
+ * Copyright (c) 2006 Willem Duinker
+ * v4l2 interface modeled after the V4L2 driver
+ * for SN9C10x PC Camera Controllers
+ *
+ * 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, version 2.
+ *
+ */
+
+#ifndef M5602_OV9650_H_
+#define M5602_OV9650_H_
+
+#include <linux/dmi.h>
+#include "m5602_sensor.h"
+
+/*****************************************************************************/
+
+#define OV9650_GAIN			0x00
+#define OV9650_BLUE			0x01
+#define OV9650_RED			0x02
+#define OV9650_VREF			0x03
+#define OV9650_COM1			0x04
+#define OV9650_BAVE			0x05
+#define OV9650_GEAVE			0x06
+#define OV9650_RSVD7			0x07
+#define OV9650_COM2			0x09
+#define OV9650_PID			0x0a
+#define OV9650_VER			0x0b
+#define OV9650_COM3			0x0c
+#define OV9650_COM4			0x0d
+#define OV9650_COM5			0x0e
+#define OV9650_COM6			0x0f
+#define OV9650_AECH			0x10
+#define OV9650_CLKRC			0x11
+#define OV9650_COM7			0x12
+#define OV9650_COM8			0x13
+#define OV9650_COM9			0x14
+#define OV9650_COM10			0x15
+#define OV9650_RSVD16			0x16
+#define OV9650_HSTART			0x17
+#define OV9650_HSTOP			0x18
+#define OV9650_VSTRT			0x19
+#define OV9650_VSTOP			0x1a
+#define OV9650_PSHFT			0x1b
+#define OV9650_MVFP			0x1e
+#define OV9650_AEW			0x24
+#define OV9650_AEB			0x25
+#define OV9650_VPT			0x26
+#define OV9650_BBIAS			0x27
+#define OV9650_GbBIAS			0x28
+#define OV9650_Gr_COM			0x29
+#define OV9650_RBIAS			0x2c
+#define OV9650_HREF			0x32
+#define OV9650_CHLF			0x33
+#define OV9650_ARBLM			0x34
+#define OV9650_RSVD35			0x35
+#define OV9650_RSVD36			0x36
+#define OV9650_ADC			0x37
+#define OV9650_ACOM38			0x38
+#define OV9650_OFON			0x39
+#define OV9650_TSLB			0x3a
+#define OV9650_COM12			0x3c
+#define OV9650_COM13			0x3d
+#define OV9650_COM15			0x40
+#define OV9650_COM16			0x41
+#define OV9650_LCC1			0x62
+#define OV9650_LCC2			0x63
+#define OV9650_LCC3			0x64
+#define OV9650_LCC4			0x65
+#define OV9650_LCC5			0x66
+#define OV9650_HV			0x69
+#define OV9650_DBLV			0x6b
+#define OV9650_COM21			0x8b
+#define OV9650_COM22			0x8c
+#define OV9650_COM24			0x8e
+#define OV9650_DBLC1			0x8f
+#define OV9650_RSVD94			0x94
+#define OV9650_RSVD95			0x95
+#define OV9650_RSVD96			0x96
+#define OV9650_LCCFB			0x9d
+#define OV9650_LCCFR			0x9e
+#define OV9650_AECHM			0xa1
+#define OV9650_COM26			0xa5
+#define OV9650_ACOMA8			0xa8
+#define OV9650_ACOMA9			0xa9
+
+#define OV9650_REGISTER_RESET		(1 << 7)
+#define OV9650_VGA_SELECT		(1 << 6)
+#define OV9650_CIF_SELECT		(1 << 5)
+#define OV9650_QVGA_SELECT		(1 << 4)
+#define OV9650_QCIF_SELECT		(1 << 3)
+#define OV9650_RGB_SELECT		(1 << 2)
+#define OV9650_RAW_RGB_SELECT		(1 << 0)
+
+#define OV9650_FAST_AGC_AEC		(1 << 7)
+#define OV9650_AEC_UNLIM_STEP_SIZE	(1 << 6)
+#define OV9650_BANDING			(1 << 5)
+#define OV9650_AGC_EN			(1 << 2)
+#define OV9650_AWB_EN			(1 << 1)
+#define OV9650_AEC_EN			(1 << 0)
+
+#define OV9650_VARIOPIXEL		(1 << 2)
+#define OV9650_SYSTEM_CLK_SEL		(1 << 7)
+#define OV9650_SLAM_MODE		(1 << 4)
+
+#define OV9650_QVGA_VARIOPIXEL		(1 << 7)
+
+#define OV9650_VFLIP			(1 << 4)
+#define OV9650_HFLIP			(1 << 5)
+
+#define OV9650_SOFT_SLEEP		(1 << 4)
+#define OV9650_OUTPUT_DRIVE_2X		(1 << 0)
+
+#define OV9650_DENOISE_ENABLE		(1 << 5)
+#define OV9650_WHITE_PIXEL_ENABLE	(1 << 1)
+#define OV9650_WHITE_PIXEL_OPTION	(1 << 0)
+
+#define OV9650_LEFT_OFFSET		0x62
+
+#define GAIN_DEFAULT			0x14
+#define RED_GAIN_DEFAULT		0x70
+#define BLUE_GAIN_DEFAULT		0x20
+#define EXPOSURE_DEFAULT		0x1ff
+
+/*****************************************************************************/
+
+/* Kernel module parameters */
+extern int force_sensor;
+extern bool dump_sensor;
+
+int ov9650_probe(struct sd *sd);
+int ov9650_init(struct sd *sd);
+int ov9650_init_controls(struct sd *sd);
+int ov9650_start(struct sd *sd);
+int ov9650_stop(struct sd *sd);
+void ov9650_disconnect(struct sd *sd);
+
+static const struct m5602_sensor ov9650 = {
+	.name = "OV9650",
+	.i2c_slave_id = 0x60,
+	.i2c_regW = 1,
+	.probe = ov9650_probe,
+	.init = ov9650_init,
+	.init_controls = ov9650_init_controls,
+	.start = ov9650_start,
+	.stop = ov9650_stop,
+	.disconnect = ov9650_disconnect,
+};
+
+#endif
diff --git a/drivers/media/usb/gspca/m5602/m5602_po1030.c b/drivers/media/usb/gspca/m5602/m5602_po1030.c
new file mode 100644
index 0000000..37d2891
--- /dev/null
+++ b/drivers/media/usb/gspca/m5602/m5602_po1030.c
@@ -0,0 +1,623 @@
+/*
+ * Driver for the po1030 sensor
+ *
+ * Copyright (c) 2008 Erik Andrén
+ * Copyright (c) 2007 Ilyes Gouta. Based on the m5603x Linux Driver Project.
+ * Copyright (c) 2005 m5603x Linux Driver Project <m5602@x3ng.com.br>
+ *
+ * Portions of code to USB interface and ALi driver software,
+ * Copyright (c) 2006 Willem Duinker
+ * v4l2 interface modeled after the V4L2 driver
+ * for SN9C10x PC Camera Controllers
+ *
+ * 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, version 2.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include "m5602_po1030.h"
+
+static int po1030_s_ctrl(struct v4l2_ctrl *ctrl);
+static void po1030_dump_registers(struct sd *sd);
+
+static const unsigned char preinit_po1030[][3] = {
+	{BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02},
+	{BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0},
+	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0},
+	{BRIDGE, M5602_XB_SENSOR_CTRL, 0x00},
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c},
+	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0},
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x05},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x04},
+	{BRIDGE, M5602_XB_GPIO_EN_H, 0x06},
+	{BRIDGE, M5602_XB_GPIO_DIR_H, 0x06},
+	{BRIDGE, M5602_XB_GPIO_DAT_H, 0x02},
+
+	{SENSOR, PO1030_AUTOCTRL2, PO1030_SENSOR_RESET | (1 << 2)},
+
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x04},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0},
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c},
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x05},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x00}
+};
+
+static const unsigned char init_po1030[][3] = {
+	{BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02},
+	{BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0},
+	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0},
+	{BRIDGE, M5602_XB_SENSOR_CTRL, 0x00},
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x0c},
+
+	{SENSOR, PO1030_AUTOCTRL2, PO1030_SENSOR_RESET | (1 << 2)},
+
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x05},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x04},
+	{BRIDGE, M5602_XB_GPIO_EN_H, 0x06},
+	{BRIDGE, M5602_XB_GPIO_EN_L, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR_H, 0x06},
+	{BRIDGE, M5602_XB_GPIO_DAT_H, 0x02},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x04},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0},
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x05},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x00},
+
+	{SENSOR, PO1030_AUTOCTRL2, 0x04},
+
+	{SENSOR, PO1030_OUTFORMCTRL2, PO1030_RAW_RGB_BAYER},
+	{SENSOR, PO1030_AUTOCTRL1, PO1030_WEIGHT_WIN_2X},
+
+	{SENSOR, PO1030_CONTROL2, 0x03},
+	{SENSOR, 0x21, 0x90},
+	{SENSOR, PO1030_YTARGET, 0x60},
+	{SENSOR, 0x59, 0x13},
+	{SENSOR, PO1030_OUTFORMCTRL1, PO1030_HREF_ENABLE},
+	{SENSOR, PO1030_EDGE_ENH_OFF, 0x00},
+	{SENSOR, PO1030_EGA, 0x80},
+	{SENSOR, 0x78, 0x14},
+	{SENSOR, 0x6f, 0x01},
+	{SENSOR, PO1030_GLOBALGAINMAX, 0x14},
+	{SENSOR, PO1030_Cb_U_GAIN, 0x38},
+	{SENSOR, PO1030_Cr_V_GAIN, 0x38},
+	{SENSOR, PO1030_CONTROL1, PO1030_SHUTTER_MODE |
+				  PO1030_AUTO_SUBSAMPLING |
+				  PO1030_FRAME_EQUAL},
+	{SENSOR, PO1030_GC0, 0x10},
+	{SENSOR, PO1030_GC1, 0x20},
+	{SENSOR, PO1030_GC2, 0x40},
+	{SENSOR, PO1030_GC3, 0x60},
+	{SENSOR, PO1030_GC4, 0x80},
+	{SENSOR, PO1030_GC5, 0xa0},
+	{SENSOR, PO1030_GC6, 0xc0},
+	{SENSOR, PO1030_GC7, 0xff},
+
+	/* Set the width to 751 */
+	{SENSOR, PO1030_FRAMEWIDTH_H, 0x02},
+	{SENSOR, PO1030_FRAMEWIDTH_L, 0xef},
+
+	/* Set the height to 540 */
+	{SENSOR, PO1030_FRAMEHEIGHT_H, 0x02},
+	{SENSOR, PO1030_FRAMEHEIGHT_L, 0x1c},
+
+	/* Set the x window to 1 */
+	{SENSOR, PO1030_WINDOWX_H, 0x00},
+	{SENSOR, PO1030_WINDOWX_L, 0x01},
+
+	/* Set the y window to 1 */
+	{SENSOR, PO1030_WINDOWY_H, 0x00},
+	{SENSOR, PO1030_WINDOWY_L, 0x01},
+
+	/* with a very low lighted environment increase the exposure but
+	 * decrease the FPS (Frame Per Second) */
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0},
+
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x05},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x00},
+	{BRIDGE, M5602_XB_GPIO_EN_H, 0x06},
+	{BRIDGE, M5602_XB_GPIO_EN_L, 0x00},
+};
+
+static struct v4l2_pix_format po1030_modes[] = {
+	{
+		640,
+		480,
+		V4L2_PIX_FMT_SBGGR8,
+		V4L2_FIELD_NONE,
+		.sizeimage = 640 * 480,
+		.bytesperline = 640,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 2
+	}
+};
+
+static const struct v4l2_ctrl_ops po1030_ctrl_ops = {
+	.s_ctrl = po1030_s_ctrl,
+};
+
+static const struct v4l2_ctrl_config po1030_greenbal_cfg = {
+	.ops	= &po1030_ctrl_ops,
+	.id	= M5602_V4L2_CID_GREEN_BALANCE,
+	.name	= "Green Balance",
+	.type	= V4L2_CTRL_TYPE_INTEGER,
+	.min	= 0,
+	.max	= 255,
+	.step	= 1,
+	.def	= PO1030_GREEN_GAIN_DEFAULT,
+	.flags	= V4L2_CTRL_FLAG_SLIDER,
+};
+
+int po1030_probe(struct sd *sd)
+{
+	u8 dev_id_h = 0, i;
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+
+	if (force_sensor) {
+		if (force_sensor == PO1030_SENSOR) {
+			pr_info("Forcing a %s sensor\n", po1030.name);
+			goto sensor_found;
+		}
+		/* If we want to force another sensor, don't try to probe this
+		 * one */
+		return -ENODEV;
+	}
+
+	gspca_dbg(gspca_dev, D_PROBE, "Probing for a po1030 sensor\n");
+
+	/* Run the pre-init to actually probe the unit */
+	for (i = 0; i < ARRAY_SIZE(preinit_po1030); i++) {
+		u8 data = preinit_po1030[i][2];
+		if (preinit_po1030[i][0] == SENSOR)
+			m5602_write_sensor(sd,
+				preinit_po1030[i][1], &data, 1);
+		else
+			m5602_write_bridge(sd, preinit_po1030[i][1], data);
+	}
+
+	if (m5602_read_sensor(sd, PO1030_DEVID_H, &dev_id_h, 1))
+		return -ENODEV;
+
+	if (dev_id_h == 0x30) {
+		pr_info("Detected a po1030 sensor\n");
+		goto sensor_found;
+	}
+	return -ENODEV;
+
+sensor_found:
+	sd->gspca_dev.cam.cam_mode = po1030_modes;
+	sd->gspca_dev.cam.nmodes = ARRAY_SIZE(po1030_modes);
+
+	return 0;
+}
+
+int po1030_init(struct sd *sd)
+{
+	int i, err = 0;
+
+	/* Init the sensor */
+	for (i = 0; i < ARRAY_SIZE(init_po1030) && !err; i++) {
+		u8 data[2] = {0x00, 0x00};
+
+		switch (init_po1030[i][0]) {
+		case BRIDGE:
+			err = m5602_write_bridge(sd,
+				init_po1030[i][1],
+				init_po1030[i][2]);
+			break;
+
+		case SENSOR:
+			data[0] = init_po1030[i][2];
+			err = m5602_write_sensor(sd,
+				init_po1030[i][1], data, 1);
+			break;
+
+		default:
+			pr_info("Invalid stream command, exiting init\n");
+			return -EINVAL;
+		}
+	}
+	if (err < 0)
+		return err;
+
+	if (dump_sensor)
+		po1030_dump_registers(sd);
+
+	return 0;
+}
+
+int po1030_init_controls(struct sd *sd)
+{
+	struct v4l2_ctrl_handler *hdl = &sd->gspca_dev.ctrl_handler;
+
+	sd->gspca_dev.vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 9);
+
+	sd->auto_white_bal = v4l2_ctrl_new_std(hdl, &po1030_ctrl_ops,
+					       V4L2_CID_AUTO_WHITE_BALANCE,
+					       0, 1, 1, 0);
+	sd->green_bal = v4l2_ctrl_new_custom(hdl, &po1030_greenbal_cfg, NULL);
+	sd->red_bal = v4l2_ctrl_new_std(hdl, &po1030_ctrl_ops,
+					V4L2_CID_RED_BALANCE, 0, 255, 1,
+					PO1030_RED_GAIN_DEFAULT);
+	sd->blue_bal = v4l2_ctrl_new_std(hdl, &po1030_ctrl_ops,
+					V4L2_CID_BLUE_BALANCE, 0, 255, 1,
+					PO1030_BLUE_GAIN_DEFAULT);
+
+	sd->autoexpo = v4l2_ctrl_new_std_menu(hdl, &po1030_ctrl_ops,
+			  V4L2_CID_EXPOSURE_AUTO, 1, 0, V4L2_EXPOSURE_MANUAL);
+	sd->expo = v4l2_ctrl_new_std(hdl, &po1030_ctrl_ops, V4L2_CID_EXPOSURE,
+			  0, 0x2ff, 1, PO1030_EXPOSURE_DEFAULT);
+
+	sd->gain = v4l2_ctrl_new_std(hdl, &po1030_ctrl_ops, V4L2_CID_GAIN, 0,
+				     0x4f, 1, PO1030_GLOBAL_GAIN_DEFAULT);
+
+	sd->hflip = v4l2_ctrl_new_std(hdl, &po1030_ctrl_ops, V4L2_CID_HFLIP,
+				      0, 1, 1, 0);
+	sd->vflip = v4l2_ctrl_new_std(hdl, &po1030_ctrl_ops, V4L2_CID_VFLIP,
+				      0, 1, 1, 0);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+
+	v4l2_ctrl_auto_cluster(4, &sd->auto_white_bal, 0, false);
+	v4l2_ctrl_auto_cluster(2, &sd->autoexpo, 0, false);
+	v4l2_ctrl_cluster(2, &sd->hflip);
+
+	return 0;
+}
+
+int po1030_start(struct sd *sd)
+{
+	struct cam *cam = &sd->gspca_dev.cam;
+	int i, err = 0;
+	int width = cam->cam_mode[sd->gspca_dev.curr_mode].width;
+	int height = cam->cam_mode[sd->gspca_dev.curr_mode].height;
+	int ver_offs = cam->cam_mode[sd->gspca_dev.curr_mode].priv;
+	u8 data;
+
+	switch (width) {
+	case 320:
+		data = PO1030_SUBSAMPLING;
+		err = m5602_write_sensor(sd, PO1030_CONTROL3, &data, 1);
+		if (err < 0)
+			return err;
+
+		data = ((width + 3) >> 8) & 0xff;
+		err = m5602_write_sensor(sd, PO1030_WINDOWWIDTH_H, &data, 1);
+		if (err < 0)
+			return err;
+
+		data = (width + 3) & 0xff;
+		err = m5602_write_sensor(sd, PO1030_WINDOWWIDTH_L, &data, 1);
+		if (err < 0)
+			return err;
+
+		data = ((height + 1) >> 8) & 0xff;
+		err = m5602_write_sensor(sd, PO1030_WINDOWHEIGHT_H, &data, 1);
+		if (err < 0)
+			return err;
+
+		data = (height + 1) & 0xff;
+		err = m5602_write_sensor(sd, PO1030_WINDOWHEIGHT_L, &data, 1);
+
+		height += 6;
+		width -= 1;
+		break;
+
+	case 640:
+		data = 0;
+		err = m5602_write_sensor(sd, PO1030_CONTROL3, &data, 1);
+		if (err < 0)
+			return err;
+
+		data = ((width + 7) >> 8) & 0xff;
+		err = m5602_write_sensor(sd, PO1030_WINDOWWIDTH_H, &data, 1);
+		if (err < 0)
+			return err;
+
+		data = (width + 7) & 0xff;
+		err = m5602_write_sensor(sd, PO1030_WINDOWWIDTH_L, &data, 1);
+		if (err < 0)
+			return err;
+
+		data = ((height + 3) >> 8) & 0xff;
+		err = m5602_write_sensor(sd, PO1030_WINDOWHEIGHT_H, &data, 1);
+		if (err < 0)
+			return err;
+
+		data = (height + 3) & 0xff;
+		err = m5602_write_sensor(sd, PO1030_WINDOWHEIGHT_L, &data, 1);
+
+		height += 12;
+		width -= 2;
+		break;
+	}
+	err = m5602_write_bridge(sd, M5602_XB_SENSOR_TYPE, 0x0c);
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_LINE_OF_FRAME_H, 0x81);
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_PIX_OF_LINE_H, 0x82);
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_SIG_INI, 0x01);
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_VSYNC_PARA,
+				 ((ver_offs >> 8) & 0xff));
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_VSYNC_PARA, (ver_offs & 0xff));
+	if (err < 0)
+		return err;
+
+	for (i = 0; i < 2 && !err; i++)
+		err = m5602_write_bridge(sd, M5602_XB_VSYNC_PARA, 0);
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_VSYNC_PARA, (height >> 8) & 0xff);
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_VSYNC_PARA, (height & 0xff));
+	if (err < 0)
+		return err;
+
+	for (i = 0; i < 2 && !err; i++)
+		err = m5602_write_bridge(sd, M5602_XB_VSYNC_PARA, 0);
+
+	for (i = 0; i < 2 && !err; i++)
+		err = m5602_write_bridge(sd, M5602_XB_SIG_INI, 0);
+
+	for (i = 0; i < 2 && !err; i++)
+		err = m5602_write_bridge(sd, M5602_XB_HSYNC_PARA, 0);
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_HSYNC_PARA, (width >> 8) & 0xff);
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_HSYNC_PARA, (width & 0xff));
+	if (err < 0)
+		return err;
+
+	err = m5602_write_bridge(sd, M5602_XB_SIG_INI, 0);
+	return err;
+}
+
+static int po1030_set_exposure(struct gspca_dev *gspca_dev, __s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 i2c_data;
+	int err;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set exposure to %d\n", val & 0xffff);
+
+	i2c_data = ((val & 0xff00) >> 8);
+	gspca_dbg(gspca_dev, D_CONF, "Set exposure to high byte to 0x%x\n",
+		  i2c_data);
+
+	err = m5602_write_sensor(sd, PO1030_INTEGLINES_H,
+				  &i2c_data, 1);
+	if (err < 0)
+		return err;
+
+	i2c_data = (val & 0xff);
+	gspca_dbg(gspca_dev, D_CONF, "Set exposure to low byte to 0x%x\n",
+		  i2c_data);
+	err = m5602_write_sensor(sd, PO1030_INTEGLINES_M,
+				  &i2c_data, 1);
+
+	return err;
+}
+
+static int po1030_set_gain(struct gspca_dev *gspca_dev, __s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 i2c_data;
+	int err;
+
+	i2c_data = val & 0xff;
+	gspca_dbg(gspca_dev, D_CONF, "Set global gain to %d\n", i2c_data);
+	err = m5602_write_sensor(sd, PO1030_GLOBALGAIN,
+				 &i2c_data, 1);
+	return err;
+}
+
+static int po1030_set_hvflip(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 i2c_data;
+	int err;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set hvflip %d %d\n",
+		  sd->hflip->val, sd->vflip->val);
+	err = m5602_read_sensor(sd, PO1030_CONTROL2, &i2c_data, 1);
+	if (err < 0)
+		return err;
+
+	i2c_data = (0x3f & i2c_data) | (sd->hflip->val << 7) |
+		   (sd->vflip->val << 6);
+
+	err = m5602_write_sensor(sd, PO1030_CONTROL2,
+				 &i2c_data, 1);
+
+	return err;
+}
+
+static int po1030_set_red_balance(struct gspca_dev *gspca_dev, __s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 i2c_data;
+	int err;
+
+	i2c_data = val & 0xff;
+	gspca_dbg(gspca_dev, D_CONF, "Set red gain to %d\n", i2c_data);
+	err = m5602_write_sensor(sd, PO1030_RED_GAIN,
+				  &i2c_data, 1);
+	return err;
+}
+
+static int po1030_set_blue_balance(struct gspca_dev *gspca_dev, __s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 i2c_data;
+	int err;
+
+	i2c_data = val & 0xff;
+	gspca_dbg(gspca_dev, D_CONF, "Set blue gain to %d\n", i2c_data);
+	err = m5602_write_sensor(sd, PO1030_BLUE_GAIN,
+				  &i2c_data, 1);
+
+	return err;
+}
+
+static int po1030_set_green_balance(struct gspca_dev *gspca_dev, __s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 i2c_data;
+	int err;
+
+	i2c_data = val & 0xff;
+	gspca_dbg(gspca_dev, D_CONF, "Set green gain to %d\n", i2c_data);
+
+	err = m5602_write_sensor(sd, PO1030_GREEN_1_GAIN,
+			   &i2c_data, 1);
+	if (err < 0)
+		return err;
+
+	return m5602_write_sensor(sd, PO1030_GREEN_2_GAIN,
+				 &i2c_data, 1);
+}
+
+static int po1030_set_auto_white_balance(struct gspca_dev *gspca_dev,
+					 __s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 i2c_data;
+	int err;
+
+	err = m5602_read_sensor(sd, PO1030_AUTOCTRL1, &i2c_data, 1);
+	if (err < 0)
+		return err;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set auto white balance to %d\n", val);
+	i2c_data = (i2c_data & 0xfe) | (val & 0x01);
+	err = m5602_write_sensor(sd, PO1030_AUTOCTRL1, &i2c_data, 1);
+	return err;
+}
+
+static int po1030_set_auto_exposure(struct gspca_dev *gspca_dev,
+				    __s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 i2c_data;
+	int err;
+
+	err = m5602_read_sensor(sd, PO1030_AUTOCTRL1, &i2c_data, 1);
+	if (err < 0)
+		return err;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set auto exposure to %d\n", val);
+	val = (val == V4L2_EXPOSURE_AUTO);
+	i2c_data = (i2c_data & 0xfd) | ((val & 0x01) << 1);
+	return m5602_write_sensor(sd, PO1030_AUTOCTRL1, &i2c_data, 1);
+}
+
+void po1030_disconnect(struct sd *sd)
+{
+	sd->sensor = NULL;
+}
+
+static int po1030_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *) gspca_dev;
+	int err;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_AUTO_WHITE_BALANCE:
+		err = po1030_set_auto_white_balance(gspca_dev, ctrl->val);
+		if (err || ctrl->val)
+			return err;
+		err = po1030_set_green_balance(gspca_dev, sd->green_bal->val);
+		if (err)
+			return err;
+		err = po1030_set_red_balance(gspca_dev, sd->red_bal->val);
+		if (err)
+			return err;
+		err = po1030_set_blue_balance(gspca_dev, sd->blue_bal->val);
+		break;
+	case V4L2_CID_EXPOSURE_AUTO:
+		err = po1030_set_auto_exposure(gspca_dev, ctrl->val);
+		if (err || ctrl->val == V4L2_EXPOSURE_AUTO)
+			return err;
+		err = po1030_set_exposure(gspca_dev, sd->expo->val);
+		break;
+	case V4L2_CID_GAIN:
+		err = po1030_set_gain(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_HFLIP:
+		err = po1030_set_hvflip(gspca_dev);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return err;
+}
+
+static void po1030_dump_registers(struct sd *sd)
+{
+	int address;
+	u8 value = 0;
+
+	pr_info("Dumping the po1030 sensor core registers\n");
+	for (address = 0; address < 0x7f; address++) {
+		m5602_read_sensor(sd, address, &value, 1);
+		pr_info("register 0x%x contains 0x%x\n", address, value);
+	}
+
+	pr_info("po1030 register state dump complete\n");
+
+	pr_info("Probing for which registers that are read/write\n");
+	for (address = 0; address < 0xff; address++) {
+		u8 old_value, ctrl_value;
+		u8 test_value[2] = {0xff, 0xff};
+
+		m5602_read_sensor(sd, address, &old_value, 1);
+		m5602_write_sensor(sd, address, test_value, 1);
+		m5602_read_sensor(sd, address, &ctrl_value, 1);
+
+		if (ctrl_value == test_value[0])
+			pr_info("register 0x%x is writeable\n", address);
+		else
+			pr_info("register 0x%x is read only\n", address);
+
+		/* Restore original value */
+		m5602_write_sensor(sd, address, &old_value, 1);
+	}
+}
diff --git a/drivers/media/usb/gspca/m5602/m5602_po1030.h b/drivers/media/usb/gspca/m5602/m5602_po1030.h
new file mode 100644
index 0000000..981a91a
--- /dev/null
+++ b/drivers/media/usb/gspca/m5602/m5602_po1030.h
@@ -0,0 +1,170 @@
+/*
+ * Driver for the po1030 sensor.
+ *
+ * Copyright (c) 2008 Erik Andrén
+ * Copyright (c) 2007 Ilyes Gouta. Based on the m5603x Linux Driver Project.
+ * Copyright (c) 2005 m5603x Linux Driver Project <m5602@x3ng.com.br>
+ *
+ * Portions of code to USB interface and ALi driver software,
+ * Copyright (c) 2006 Willem Duinker
+ * v4l2 interface modeled after the V4L2 driver
+ * for SN9C10x PC Camera Controllers
+ *
+ * Register defines taken from Pascal Stangs Procyon Armlib
+ *
+ * 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, version 2.
+ *
+ */
+
+#ifndef M5602_PO1030_H_
+#define M5602_PO1030_H_
+
+#include "m5602_sensor.h"
+
+/*****************************************************************************/
+
+#define PO1030_DEVID_H		0x00
+#define PO1030_DEVID_L		0x01
+#define PO1030_FRAMEWIDTH_H	0x04
+#define PO1030_FRAMEWIDTH_L	0x05
+#define PO1030_FRAMEHEIGHT_H	0x06
+#define PO1030_FRAMEHEIGHT_L	0x07
+#define PO1030_WINDOWX_H	0x08
+#define PO1030_WINDOWX_L	0x09
+#define PO1030_WINDOWY_H	0x0a
+#define PO1030_WINDOWY_L	0x0b
+#define PO1030_WINDOWWIDTH_H	0x0c
+#define PO1030_WINDOWWIDTH_L	0x0d
+#define PO1030_WINDOWHEIGHT_H	0x0e
+#define PO1030_WINDOWHEIGHT_L	0x0f
+
+#define PO1030_GLOBALIBIAS	0x12
+#define PO1030_PIXELIBIAS	0x13
+
+#define PO1030_GLOBALGAIN	0x15
+#define PO1030_RED_GAIN		0x16
+#define PO1030_GREEN_1_GAIN	0x17
+#define PO1030_BLUE_GAIN	0x18
+#define PO1030_GREEN_2_GAIN	0x19
+
+#define PO1030_INTEGLINES_H	0x1a
+#define PO1030_INTEGLINES_M	0x1b
+#define PO1030_INTEGLINES_L	0x1c
+
+#define PO1030_CONTROL1		0x1d
+#define PO1030_CONTROL2		0x1e
+#define PO1030_CONTROL3		0x1f
+#define PO1030_CONTROL4		0x20
+
+#define PO1030_PERIOD50_H	0x23
+#define PO1030_PERIOD50_L	0x24
+#define PO1030_PERIOD60_H	0x25
+#define PO1030_PERIOD60_L	0x26
+#define PO1030_REGCLK167	0x27
+#define PO1030_FLICKER_DELTA50	0x28
+#define PO1030_FLICKERDELTA60	0x29
+
+#define PO1030_ADCOFFSET	0x2c
+
+/* Gamma Correction Coeffs */
+#define PO1030_GC0		0x2d
+#define PO1030_GC1		0x2e
+#define PO1030_GC2		0x2f
+#define PO1030_GC3		0x30
+#define PO1030_GC4		0x31
+#define PO1030_GC5		0x32
+#define PO1030_GC6		0x33
+#define PO1030_GC7		0x34
+
+/* Color Transform Matrix */
+#define PO1030_CT0		0x35
+#define PO1030_CT1		0x36
+#define PO1030_CT2		0x37
+#define PO1030_CT3		0x38
+#define PO1030_CT4		0x39
+#define PO1030_CT5		0x3a
+#define PO1030_CT6		0x3b
+#define PO1030_CT7		0x3c
+#define PO1030_CT8		0x3d
+
+#define PO1030_AUTOCTRL1	0x3e
+#define PO1030_AUTOCTRL2	0x3f
+
+#define PO1030_YTARGET		0x40
+#define PO1030_GLOBALGAINMIN	0x41
+#define PO1030_GLOBALGAINMAX	0x42
+
+#define PO1030_AWB_RED_TUNING	0x47
+#define PO1030_AWB_BLUE_TUNING	0x48
+
+/* Output format control */
+#define PO1030_OUTFORMCTRL1	0x5a
+#define PO1030_OUTFORMCTRL2	0x5b
+#define PO1030_OUTFORMCTRL3	0x5c
+#define PO1030_OUTFORMCTRL4	0x5d
+#define PO1030_OUTFORMCTRL5	0x5e
+
+#define PO1030_EDGE_ENH_OFF	0x5f
+#define PO1030_EGA		0x60
+
+#define PO1030_Cb_U_GAIN	0x63
+#define PO1030_Cr_V_GAIN	0x64
+
+#define PO1030_YCONTRAST	0x74
+#define PO1030_YSATURATION	0x75
+
+#define PO1030_HFLIP		(1 << 7)
+#define PO1030_VFLIP		(1 << 6)
+
+#define PO1030_HREF_ENABLE	(1 << 6)
+
+#define PO1030_RAW_RGB_BAYER	0x4
+
+#define PO1030_FRAME_EQUAL	(1 << 3)
+#define PO1030_AUTO_SUBSAMPLING (1 << 4)
+
+#define PO1030_WEIGHT_WIN_2X	(1 << 3)
+
+#define PO1030_SHUTTER_MODE	(1 << 6)
+#define PO1030_AUTO_SUBSAMPLING	(1 << 4)
+#define PO1030_FRAME_EQUAL	(1 << 3)
+
+#define PO1030_SENSOR_RESET	(1 << 5)
+
+#define PO1030_SUBSAMPLING	(1 << 6)
+
+/*****************************************************************************/
+
+#define PO1030_GLOBAL_GAIN_DEFAULT	0x12
+#define PO1030_EXPOSURE_DEFAULT		0x0085
+#define PO1030_BLUE_GAIN_DEFAULT	0x36
+#define PO1030_RED_GAIN_DEFAULT		0x36
+#define PO1030_GREEN_GAIN_DEFAULT	0x40
+
+/*****************************************************************************/
+
+/* Kernel module parameters */
+extern int force_sensor;
+extern bool dump_sensor;
+
+int po1030_probe(struct sd *sd);
+int po1030_init(struct sd *sd);
+int po1030_init_controls(struct sd *sd);
+int po1030_start(struct sd *sd);
+void po1030_disconnect(struct sd *sd);
+
+static const struct m5602_sensor po1030 = {
+	.name = "PO1030",
+
+	.i2c_slave_id = 0xdc,
+	.i2c_regW = 1,
+
+	.probe = po1030_probe,
+	.init = po1030_init,
+	.init_controls = po1030_init_controls,
+	.start = po1030_start,
+	.disconnect = po1030_disconnect,
+};
+#endif
diff --git a/drivers/media/usb/gspca/m5602/m5602_s5k4aa.c b/drivers/media/usb/gspca/m5602/m5602_s5k4aa.c
new file mode 100644
index 0000000..cec4a58
--- /dev/null
+++ b/drivers/media/usb/gspca/m5602/m5602_s5k4aa.c
@@ -0,0 +1,763 @@
+/*
+ * Driver for the s5k4aa sensor
+ *
+ * Copyright (C) 2008 Erik Andrén
+ * Copyright (C) 2007 Ilyes Gouta. Based on the m5603x Linux Driver Project.
+ * Copyright (C) 2005 m5603x Linux Driver Project <m5602@x3ng.com.br>
+ *
+ * Portions of code to USB interface and ALi driver software,
+ * Copyright (c) 2006 Willem Duinker
+ * v4l2 interface modeled after the V4L2 driver
+ * for SN9C10x PC Camera Controllers
+ *
+ * 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, version 2.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include "m5602_s5k4aa.h"
+
+static const unsigned char preinit_s5k4aa[][4] = {
+	{BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00},
+	{BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00},
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x0d, 0x00},
+	{BRIDGE, M5602_XB_SENSOR_CTRL, 0x00, 0x00},
+
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x08, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0x80, 0x00},
+	{BRIDGE, M5602_XB_GPIO_EN_H, 0x3f, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR_H, 0x3f, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x00, 0x00},
+	{BRIDGE, M5602_XB_GPIO_EN_L, 0xff, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR_L, 0xff, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT_L, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00},
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x08, 0x00},
+
+	{BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00},
+	{BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x14, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xf0, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x1c, 0x00},
+	{BRIDGE, M5602_XB_GPIO_EN_H, 0x06, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR_H, 0x06, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00},
+	{BRIDGE, M5602_XB_GPIO_EN_L, 0x00, 0x00},
+	{BRIDGE, M5602_XB_I2C_CLK_DIV, 0x20, 0x00},
+
+	{SENSOR, S5K4AA_PAGE_MAP, 0x00, 0x00}
+};
+
+static const unsigned char init_s5k4aa[][4] = {
+	{BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00},
+	{BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00},
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x0d, 0x00},
+	{BRIDGE, M5602_XB_SENSOR_CTRL, 0x00, 0x00},
+
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x08, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0x80, 0x00},
+	{BRIDGE, M5602_XB_GPIO_EN_H, 0x3f, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR_H, 0x3f, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x00, 0x00},
+	{BRIDGE, M5602_XB_GPIO_EN_L, 0xff, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR_L, 0xff, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT_L, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00},
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x08, 0x00},
+
+	{BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00},
+	{BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x14, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xf0, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x1c, 0x00},
+	{BRIDGE, M5602_XB_GPIO_EN_H, 0x06, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR_H, 0x06, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00},
+	{BRIDGE, M5602_XB_GPIO_EN_L, 0x00, 0x00},
+	{BRIDGE, M5602_XB_I2C_CLK_DIV, 0x20, 0x00},
+
+	{SENSOR, S5K4AA_PAGE_MAP, 0x07, 0x00},
+	{SENSOR, 0x36, 0x01, 0x00},
+	{SENSOR, S5K4AA_PAGE_MAP, 0x00, 0x00},
+	{SENSOR, 0x7b, 0xff, 0x00},
+	{SENSOR, S5K4AA_PAGE_MAP, 0x02, 0x00},
+	{SENSOR, 0x0c, 0x05, 0x00},
+	{SENSOR, 0x02, 0x0e, 0x00},
+	{SENSOR, S5K4AA_READ_MODE, 0xa0, 0x00},
+	{SENSOR, 0x37, 0x00, 0x00},
+};
+
+static const unsigned char VGA_s5k4aa[][4] = {
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x06, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00},
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x08, 0x00},
+	{BRIDGE, M5602_XB_LINE_OF_FRAME_H, 0x81, 0x00},
+	{BRIDGE, M5602_XB_PIX_OF_LINE_H, 0x82, 0x00},
+	{BRIDGE, M5602_XB_SIG_INI, 0x01, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},
+	/* VSYNC_PARA, VSYNC_PARA : img height 480 = 0x01e0 */
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x01, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0xe0, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SIG_INI, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SIG_INI, 0x02, 0x00},
+	{BRIDGE, M5602_XB_HSYNC_PARA, 0x00, 0x00},
+	{BRIDGE, M5602_XB_HSYNC_PARA, 0x00, 0x00},
+	/* HSYNC_PARA, HSYNC_PARA : img width 640 = 0x0280 */
+	{BRIDGE, M5602_XB_HSYNC_PARA, 0x02, 0x00},
+	{BRIDGE, M5602_XB_HSYNC_PARA, 0x80, 0x00},
+	{BRIDGE, M5602_XB_SIG_INI, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xa0, 0x00}, /* 48 MHz */
+
+	{SENSOR, S5K4AA_PAGE_MAP, 0x02, 0x00},
+	{SENSOR, S5K4AA_READ_MODE, S5K4AA_RM_H_FLIP | S5K4AA_RM_ROW_SKIP_2X
+		| S5K4AA_RM_COL_SKIP_2X, 0x00},
+	/* 0x37 : Fix image stability when light is too bright and improves
+	 * image quality in 640x480, but worsens it in 1280x1024 */
+	{SENSOR, 0x37, 0x01, 0x00},
+	/* ROWSTART_HI, ROWSTART_LO : 10 + (1024-960)/2 = 42 = 0x002a */
+	{SENSOR, S5K4AA_ROWSTART_HI, 0x00, 0x00},
+	{SENSOR, S5K4AA_ROWSTART_LO, 0x29, 0x00},
+	{SENSOR, S5K4AA_COLSTART_HI, 0x00, 0x00},
+	{SENSOR, S5K4AA_COLSTART_LO, 0x0c, 0x00},
+	/* window_height_hi, window_height_lo : 960 = 0x03c0 */
+	{SENSOR, S5K4AA_WINDOW_HEIGHT_HI, 0x03, 0x00},
+	{SENSOR, S5K4AA_WINDOW_HEIGHT_LO, 0xc0, 0x00},
+	/* window_width_hi, window_width_lo : 1280 = 0x0500 */
+	{SENSOR, S5K4AA_WINDOW_WIDTH_HI, 0x05, 0x00},
+	{SENSOR, S5K4AA_WINDOW_WIDTH_LO, 0x00, 0x00},
+	{SENSOR, S5K4AA_H_BLANK_HI__, 0x00, 0x00},
+	{SENSOR, S5K4AA_H_BLANK_LO__, 0xa8, 0x00}, /* helps to sync... */
+	{SENSOR, S5K4AA_EXPOSURE_HI, 0x01, 0x00},
+	{SENSOR, S5K4AA_EXPOSURE_LO, 0x00, 0x00},
+	{SENSOR, 0x11, 0x04, 0x00},
+	{SENSOR, 0x12, 0xc3, 0x00},
+	{SENSOR, S5K4AA_PAGE_MAP, 0x02, 0x00},
+	{SENSOR, 0x02, 0x0e, 0x00},
+};
+
+static const unsigned char SXGA_s5k4aa[][4] = {
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x06, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00},
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x08, 0x00},
+	{BRIDGE, M5602_XB_LINE_OF_FRAME_H, 0x81, 0x00},
+	{BRIDGE, M5602_XB_PIX_OF_LINE_H, 0x82, 0x00},
+	{BRIDGE, M5602_XB_SIG_INI, 0x01, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},
+	/* VSYNC_PARA, VSYNC_PARA : img height 1024 = 0x0400 */
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x04, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SIG_INI, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SIG_INI, 0x02, 0x00},
+	{BRIDGE, M5602_XB_HSYNC_PARA, 0x00, 0x00},
+	{BRIDGE, M5602_XB_HSYNC_PARA, 0x00, 0x00},
+	/* HSYNC_PARA, HSYNC_PARA : img width 1280 = 0x0500 */
+	{BRIDGE, M5602_XB_HSYNC_PARA, 0x05, 0x00},
+	{BRIDGE, M5602_XB_HSYNC_PARA, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SIG_INI, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xa0, 0x00}, /* 48 MHz */
+
+	{SENSOR, S5K4AA_PAGE_MAP, 0x02, 0x00},
+	{SENSOR, S5K4AA_READ_MODE, S5K4AA_RM_H_FLIP, 0x00},
+	{SENSOR, 0x37, 0x01, 0x00},
+	{SENSOR, S5K4AA_ROWSTART_HI, 0x00, 0x00},
+	{SENSOR, S5K4AA_ROWSTART_LO, 0x09, 0x00},
+	{SENSOR, S5K4AA_COLSTART_HI, 0x00, 0x00},
+	{SENSOR, S5K4AA_COLSTART_LO, 0x0a, 0x00},
+	{SENSOR, S5K4AA_WINDOW_HEIGHT_HI, 0x04, 0x00},
+	{SENSOR, S5K4AA_WINDOW_HEIGHT_LO, 0x00, 0x00},
+	{SENSOR, S5K4AA_WINDOW_WIDTH_HI, 0x05, 0x00},
+	{SENSOR, S5K4AA_WINDOW_WIDTH_LO, 0x00, 0x00},
+	{SENSOR, S5K4AA_H_BLANK_HI__, 0x01, 0x00},
+	{SENSOR, S5K4AA_H_BLANK_LO__, 0xa8, 0x00},
+	{SENSOR, S5K4AA_EXPOSURE_HI, 0x01, 0x00},
+	{SENSOR, S5K4AA_EXPOSURE_LO, 0x00, 0x00},
+	{SENSOR, 0x11, 0x04, 0x00},
+	{SENSOR, 0x12, 0xc3, 0x00},
+	{SENSOR, S5K4AA_PAGE_MAP, 0x02, 0x00},
+	{SENSOR, 0x02, 0x0e, 0x00},
+};
+
+
+static int s5k4aa_s_ctrl(struct v4l2_ctrl *ctrl);
+static void s5k4aa_dump_registers(struct sd *sd);
+
+static const struct v4l2_ctrl_ops s5k4aa_ctrl_ops = {
+	.s_ctrl = s5k4aa_s_ctrl,
+};
+
+static
+    const
+	struct dmi_system_id s5k4aa_vflip_dmi_table[] = {
+	{
+		.ident = "BRUNEINIT",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "BRUNENIT"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "BRUNENIT"),
+			DMI_MATCH(DMI_BOARD_VERSION, "00030D0000000001")
+		}
+	}, {
+		.ident = "Fujitsu-Siemens Amilo Xa 2528",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "AMILO Xa 2528")
+		}
+	}, {
+		.ident = "Fujitsu-Siemens Amilo Xi 2428",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "AMILO Xi 2428")
+		}
+	}, {
+		.ident = "Fujitsu-Siemens Amilo Xi 2528",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "AMILO Xi 2528")
+		}
+	}, {
+		.ident = "Fujitsu-Siemens Amilo Xi 2550",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "AMILO Xi 2550")
+		}
+	}, {
+		.ident = "Fujitsu-Siemens Amilo Pa 2548",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "AMILO Pa 2548")
+		}
+	}, {
+		.ident = "Fujitsu-Siemens Amilo Pi 2530",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "AMILO Pi 2530")
+		}
+	}, {
+		.ident = "MSI GX700",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "GX700"),
+			DMI_MATCH(DMI_BIOS_DATE, "12/02/2008")
+		}
+	}, {
+		.ident = "MSI GX700",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "GX700"),
+			DMI_MATCH(DMI_BIOS_DATE, "07/26/2007")
+		}
+	}, {
+		.ident = "MSI GX700",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "GX700"),
+			DMI_MATCH(DMI_BIOS_DATE, "07/19/2007")
+		}
+	}, {
+		.ident = "MSI GX700/GX705/EX700",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "GX700/GX705/EX700")
+		}
+	}, {
+		.ident = "MSI L735",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Micro-Star International"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "MS-1717X")
+		}
+	}, {
+		.ident = "Lenovo Y300",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "L3000 Y300"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Y300")
+		}
+	},
+	{ }
+};
+
+static struct v4l2_pix_format s5k4aa_modes[] = {
+	{
+		640,
+		480,
+		V4L2_PIX_FMT_SBGGR8,
+		V4L2_FIELD_NONE,
+		.sizeimage =
+			640 * 480,
+		.bytesperline = 640,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0
+	},
+	{
+		1280,
+		1024,
+		V4L2_PIX_FMT_SBGGR8,
+		V4L2_FIELD_NONE,
+		.sizeimage =
+			1280 * 1024,
+		.bytesperline = 1280,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0
+	}
+};
+
+int s5k4aa_probe(struct sd *sd)
+{
+	u8 prod_id[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+	const u8 expected_prod_id[6] = {0x00, 0x10, 0x00, 0x4b, 0x33, 0x75};
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	int i, err = 0;
+
+	if (force_sensor) {
+		if (force_sensor == S5K4AA_SENSOR) {
+			pr_info("Forcing a %s sensor\n", s5k4aa.name);
+			goto sensor_found;
+		}
+		/* If we want to force another sensor, don't try to probe this
+		 * one */
+		return -ENODEV;
+	}
+
+	gspca_dbg(gspca_dev, D_PROBE, "Probing for a s5k4aa sensor\n");
+
+	/* Preinit the sensor */
+	for (i = 0; i < ARRAY_SIZE(preinit_s5k4aa) && !err; i++) {
+		u8 data[2] = {0x00, 0x00};
+
+		switch (preinit_s5k4aa[i][0]) {
+		case BRIDGE:
+			err = m5602_write_bridge(sd,
+						 preinit_s5k4aa[i][1],
+						 preinit_s5k4aa[i][2]);
+			break;
+
+		case SENSOR:
+			data[0] = preinit_s5k4aa[i][2];
+			err = m5602_write_sensor(sd,
+						  preinit_s5k4aa[i][1],
+						  data, 1);
+			break;
+
+		case SENSOR_LONG:
+			data[0] = preinit_s5k4aa[i][2];
+			data[1] = preinit_s5k4aa[i][3];
+			err = m5602_write_sensor(sd,
+						  preinit_s5k4aa[i][1],
+						  data, 2);
+			break;
+		default:
+			pr_info("Invalid stream command, exiting init\n");
+			return -EINVAL;
+		}
+	}
+
+	/* Test some registers, but we don't know their exact meaning yet */
+	if (m5602_read_sensor(sd, 0x00, prod_id, 2))
+		return -ENODEV;
+	if (m5602_read_sensor(sd, 0x02, prod_id+2, 2))
+		return -ENODEV;
+	if (m5602_read_sensor(sd, 0x04, prod_id+4, 2))
+		return -ENODEV;
+
+	if (memcmp(prod_id, expected_prod_id, sizeof(prod_id)))
+		return -ENODEV;
+	else
+		pr_info("Detected a s5k4aa sensor\n");
+
+sensor_found:
+	sd->gspca_dev.cam.cam_mode = s5k4aa_modes;
+	sd->gspca_dev.cam.nmodes = ARRAY_SIZE(s5k4aa_modes);
+
+	return 0;
+}
+
+int s5k4aa_start(struct sd *sd)
+{
+	int i, err = 0;
+	u8 data[2];
+	struct cam *cam = &sd->gspca_dev.cam;
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+
+	switch (cam->cam_mode[sd->gspca_dev.curr_mode].width) {
+	case 1280:
+		gspca_dbg(gspca_dev, D_CONF, "Configuring camera for SXGA mode\n");
+
+		for (i = 0; i < ARRAY_SIZE(SXGA_s5k4aa); i++) {
+			switch (SXGA_s5k4aa[i][0]) {
+			case BRIDGE:
+				err = m5602_write_bridge(sd,
+						 SXGA_s5k4aa[i][1],
+						 SXGA_s5k4aa[i][2]);
+			break;
+
+			case SENSOR:
+				data[0] = SXGA_s5k4aa[i][2];
+				err = m5602_write_sensor(sd,
+						 SXGA_s5k4aa[i][1],
+						 data, 1);
+			break;
+
+			case SENSOR_LONG:
+				data[0] = SXGA_s5k4aa[i][2];
+				data[1] = SXGA_s5k4aa[i][3];
+				err = m5602_write_sensor(sd,
+						  SXGA_s5k4aa[i][1],
+						  data, 2);
+			break;
+
+			default:
+				pr_err("Invalid stream command, exiting init\n");
+				return -EINVAL;
+			}
+		}
+		break;
+
+	case 640:
+		gspca_dbg(gspca_dev, D_CONF, "Configuring camera for VGA mode\n");
+
+		for (i = 0; i < ARRAY_SIZE(VGA_s5k4aa); i++) {
+			switch (VGA_s5k4aa[i][0]) {
+			case BRIDGE:
+				err = m5602_write_bridge(sd,
+						 VGA_s5k4aa[i][1],
+						 VGA_s5k4aa[i][2]);
+			break;
+
+			case SENSOR:
+				data[0] = VGA_s5k4aa[i][2];
+				err = m5602_write_sensor(sd,
+						 VGA_s5k4aa[i][1],
+						 data, 1);
+			break;
+
+			case SENSOR_LONG:
+				data[0] = VGA_s5k4aa[i][2];
+				data[1] = VGA_s5k4aa[i][3];
+				err = m5602_write_sensor(sd,
+						  VGA_s5k4aa[i][1],
+						  data, 2);
+			break;
+
+			default:
+				pr_err("Invalid stream command, exiting init\n");
+				return -EINVAL;
+			}
+		}
+		break;
+	}
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+int s5k4aa_init(struct sd *sd)
+{
+	int i, err = 0;
+
+	for (i = 0; i < ARRAY_SIZE(init_s5k4aa) && !err; i++) {
+		u8 data[2] = {0x00, 0x00};
+
+		switch (init_s5k4aa[i][0]) {
+		case BRIDGE:
+			err = m5602_write_bridge(sd,
+				init_s5k4aa[i][1],
+				init_s5k4aa[i][2]);
+			break;
+
+		case SENSOR:
+			data[0] = init_s5k4aa[i][2];
+			err = m5602_write_sensor(sd,
+				init_s5k4aa[i][1], data, 1);
+			break;
+
+		case SENSOR_LONG:
+			data[0] = init_s5k4aa[i][2];
+			data[1] = init_s5k4aa[i][3];
+			err = m5602_write_sensor(sd,
+				init_s5k4aa[i][1], data, 2);
+			break;
+		default:
+			pr_info("Invalid stream command, exiting init\n");
+			return -EINVAL;
+		}
+	}
+
+	if (dump_sensor)
+		s5k4aa_dump_registers(sd);
+
+	return err;
+}
+
+int s5k4aa_init_controls(struct sd *sd)
+{
+	struct v4l2_ctrl_handler *hdl = &sd->gspca_dev.ctrl_handler;
+
+	sd->gspca_dev.vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 6);
+
+	v4l2_ctrl_new_std(hdl, &s5k4aa_ctrl_ops, V4L2_CID_BRIGHTNESS,
+			  0, 0x1f, 1, S5K4AA_DEFAULT_BRIGHTNESS);
+
+	v4l2_ctrl_new_std(hdl, &s5k4aa_ctrl_ops, V4L2_CID_EXPOSURE,
+			  13, 0xfff, 1, 0x100);
+
+	v4l2_ctrl_new_std(hdl, &s5k4aa_ctrl_ops, V4L2_CID_GAIN,
+			  0, 127, 1, S5K4AA_DEFAULT_GAIN);
+
+	v4l2_ctrl_new_std(hdl, &s5k4aa_ctrl_ops, V4L2_CID_SHARPNESS,
+			  0, 1, 1, 1);
+
+	sd->hflip = v4l2_ctrl_new_std(hdl, &s5k4aa_ctrl_ops, V4L2_CID_HFLIP,
+				      0, 1, 1, 0);
+	sd->vflip = v4l2_ctrl_new_std(hdl, &s5k4aa_ctrl_ops, V4L2_CID_VFLIP,
+				      0, 1, 1, 0);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+
+	v4l2_ctrl_cluster(2, &sd->hflip);
+
+	return 0;
+}
+
+static int s5k4aa_set_exposure(struct gspca_dev *gspca_dev, __s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 data = S5K4AA_PAGE_MAP_2;
+	int err;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set exposure to %d\n", val);
+	err = m5602_write_sensor(sd, S5K4AA_PAGE_MAP, &data, 1);
+	if (err < 0)
+		return err;
+	data = (val >> 8) & 0xff;
+	err = m5602_write_sensor(sd, S5K4AA_EXPOSURE_HI, &data, 1);
+	if (err < 0)
+		return err;
+	data = val & 0xff;
+	err = m5602_write_sensor(sd, S5K4AA_EXPOSURE_LO, &data, 1);
+
+	return err;
+}
+
+static int s5k4aa_set_hvflip(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 data = S5K4AA_PAGE_MAP_2;
+	int err;
+	int hflip = sd->hflip->val;
+	int vflip = sd->vflip->val;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set hvflip %d %d\n", hflip, vflip);
+	err = m5602_write_sensor(sd, S5K4AA_PAGE_MAP, &data, 1);
+	if (err < 0)
+		return err;
+
+	err = m5602_read_sensor(sd, S5K4AA_READ_MODE, &data, 1);
+	if (err < 0)
+		return err;
+
+	if (dmi_check_system(s5k4aa_vflip_dmi_table)) {
+		hflip = !hflip;
+		vflip = !vflip;
+	}
+
+	data = (data & 0x7f) | (vflip << 7) | (hflip << 6);
+	err = m5602_write_sensor(sd, S5K4AA_READ_MODE, &data, 1);
+	if (err < 0)
+		return err;
+
+	err = m5602_read_sensor(sd, S5K4AA_COLSTART_LO, &data, 1);
+	if (err < 0)
+		return err;
+	if (hflip)
+		data &= 0xfe;
+	else
+		data |= 0x01;
+	err = m5602_write_sensor(sd, S5K4AA_COLSTART_LO, &data, 1);
+	if (err < 0)
+		return err;
+
+	err = m5602_read_sensor(sd, S5K4AA_ROWSTART_LO, &data, 1);
+	if (err < 0)
+		return err;
+	if (vflip)
+		data &= 0xfe;
+	else
+		data |= 0x01;
+	err = m5602_write_sensor(sd, S5K4AA_ROWSTART_LO, &data, 1);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+static int s5k4aa_set_gain(struct gspca_dev *gspca_dev, __s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 data = S5K4AA_PAGE_MAP_2;
+	int err;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set gain to %d\n", val);
+	err = m5602_write_sensor(sd, S5K4AA_PAGE_MAP, &data, 1);
+	if (err < 0)
+		return err;
+
+	data = val & 0xff;
+	err = m5602_write_sensor(sd, S5K4AA_GAIN, &data, 1);
+
+	return err;
+}
+
+static int s5k4aa_set_brightness(struct gspca_dev *gspca_dev, __s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 data = S5K4AA_PAGE_MAP_2;
+	int err;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set brightness to %d\n", val);
+	err = m5602_write_sensor(sd, S5K4AA_PAGE_MAP, &data, 1);
+	if (err < 0)
+		return err;
+
+	data = val & 0xff;
+	return m5602_write_sensor(sd, S5K4AA_BRIGHTNESS, &data, 1);
+}
+
+static int s5k4aa_set_noise(struct gspca_dev *gspca_dev, __s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 data = S5K4AA_PAGE_MAP_2;
+	int err;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set noise to %d\n", val);
+	err = m5602_write_sensor(sd, S5K4AA_PAGE_MAP, &data, 1);
+	if (err < 0)
+		return err;
+
+	data = val & 0x01;
+	return m5602_write_sensor(sd, S5K4AA_NOISE_SUPP, &data, 1);
+}
+
+static int s5k4aa_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	int err;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		err = s5k4aa_set_brightness(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_EXPOSURE:
+		err = s5k4aa_set_exposure(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_GAIN:
+		err = s5k4aa_set_gain(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_SHARPNESS:
+		err = s5k4aa_set_noise(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_HFLIP:
+		err = s5k4aa_set_hvflip(gspca_dev);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return err;
+}
+
+void s5k4aa_disconnect(struct sd *sd)
+{
+	sd->sensor = NULL;
+}
+
+static void s5k4aa_dump_registers(struct sd *sd)
+{
+	int address;
+	u8 page, old_page;
+	m5602_read_sensor(sd, S5K4AA_PAGE_MAP, &old_page, 1);
+	for (page = 0; page < 16; page++) {
+		m5602_write_sensor(sd, S5K4AA_PAGE_MAP, &page, 1);
+		pr_info("Dumping the s5k4aa register state for page 0x%x\n",
+			page);
+		for (address = 0; address <= 0xff; address++) {
+			u8 value = 0;
+			m5602_read_sensor(sd, address, &value, 1);
+			pr_info("register 0x%x contains 0x%x\n",
+				address, value);
+		}
+	}
+	pr_info("s5k4aa register state dump complete\n");
+
+	for (page = 0; page < 16; page++) {
+		m5602_write_sensor(sd, S5K4AA_PAGE_MAP, &page, 1);
+		pr_info("Probing for which registers that are read/write for page 0x%x\n",
+			page);
+		for (address = 0; address <= 0xff; address++) {
+			u8 old_value, ctrl_value, test_value = 0xff;
+
+			m5602_read_sensor(sd, address, &old_value, 1);
+			m5602_write_sensor(sd, address, &test_value, 1);
+			m5602_read_sensor(sd, address, &ctrl_value, 1);
+
+			if (ctrl_value == test_value)
+				pr_info("register 0x%x is writeable\n",
+					address);
+			else
+				pr_info("register 0x%x is read only\n",
+					address);
+
+			/* Restore original value */
+			m5602_write_sensor(sd, address, &old_value, 1);
+		}
+	}
+	pr_info("Read/write register probing complete\n");
+	m5602_write_sensor(sd, S5K4AA_PAGE_MAP, &old_page, 1);
+}
diff --git a/drivers/media/usb/gspca/m5602/m5602_s5k4aa.h b/drivers/media/usb/gspca/m5602/m5602_s5k4aa.h
new file mode 100644
index 0000000..8407682
--- /dev/null
+++ b/drivers/media/usb/gspca/m5602/m5602_s5k4aa.h
@@ -0,0 +1,88 @@
+/*
+ * Driver for the s5k4aa sensor
+ *
+ * Copyright (C) 2008 Erik Andrén
+ * Copyright (C) 2007 Ilyes Gouta. Based on the m5603x Linux Driver Project.
+ * Copyright (C) 2005 m5603x Linux Driver Project <m5602@x3ng.com.br>
+ *
+ * Portions of code to USB interface and ALi driver software,
+ * Copyright (c) 2006 Willem Duinker
+ * v4l2 interface modeled after the V4L2 driver
+ * for SN9C10x PC Camera Controllers
+ *
+ * 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, version 2.
+ *
+ */
+
+#ifndef M5602_S5K4AA_H_
+#define M5602_S5K4AA_H_
+
+#include <linux/dmi.h>
+
+#include "m5602_sensor.h"
+
+/*****************************************************************************/
+
+#define S5K4AA_PAGE_MAP			0xec
+
+#define S5K4AA_PAGE_MAP_0		0x00
+#define S5K4AA_PAGE_MAP_1		0x01
+#define S5K4AA_PAGE_MAP_2		0x02
+
+/* Sensor register definitions for page 0x02 */
+#define S5K4AA_READ_MODE		0x03
+#define S5K4AA_ROWSTART_HI		0x04
+#define S5K4AA_ROWSTART_LO		0x05
+#define S5K4AA_COLSTART_HI		0x06
+#define S5K4AA_COLSTART_LO		0x07
+#define S5K4AA_WINDOW_HEIGHT_HI		0x08
+#define S5K4AA_WINDOW_HEIGHT_LO		0x09
+#define S5K4AA_WINDOW_WIDTH_HI		0x0a
+#define S5K4AA_WINDOW_WIDTH_LO		0x0b
+#define S5K4AA_GLOBAL_GAIN__		0x0f
+/* sync lost, if too low, reduces frame rate if too high */
+#define S5K4AA_H_BLANK_HI__		0x1d
+#define S5K4AA_H_BLANK_LO__		0x1e
+#define S5K4AA_EXPOSURE_HI		0x17
+#define S5K4AA_EXPOSURE_LO		0x18
+#define S5K4AA_BRIGHTNESS		0x1f /* (digital?) gain : 5 bits */
+#define S5K4AA_GAIN			0x20 /* (analogue?) gain : 7 bits */
+#define S5K4AA_NOISE_SUPP		0x37
+
+#define S5K4AA_RM_ROW_SKIP_4X		0x08
+#define S5K4AA_RM_ROW_SKIP_2X		0x04
+#define S5K4AA_RM_COL_SKIP_4X		0x02
+#define S5K4AA_RM_COL_SKIP_2X		0x01
+#define S5K4AA_RM_H_FLIP		0x40
+#define S5K4AA_RM_V_FLIP		0x80
+
+#define S5K4AA_DEFAULT_GAIN		0x5f
+#define S5K4AA_DEFAULT_BRIGHTNESS	0x10
+
+/*****************************************************************************/
+
+/* Kernel module parameters */
+extern int force_sensor;
+extern bool dump_sensor;
+
+int s5k4aa_probe(struct sd *sd);
+int s5k4aa_init(struct sd *sd);
+int s5k4aa_init_controls(struct sd *sd);
+int s5k4aa_start(struct sd *sd);
+void s5k4aa_disconnect(struct sd *sd);
+
+static const struct m5602_sensor s5k4aa = {
+	.name = "S5K4AA",
+	.i2c_slave_id = 0x5a,
+	.i2c_regW = 2,
+
+	.probe = s5k4aa_probe,
+	.init = s5k4aa_init,
+	.init_controls = s5k4aa_init_controls,
+	.start = s5k4aa_start,
+	.disconnect = s5k4aa_disconnect,
+};
+
+#endif
diff --git a/drivers/media/usb/gspca/m5602/m5602_s5k83a.c b/drivers/media/usb/gspca/m5602/m5602_s5k83a.c
new file mode 100644
index 0000000..3d8ab18
--- /dev/null
+++ b/drivers/media/usb/gspca/m5602/m5602_s5k83a.c
@@ -0,0 +1,585 @@
+/*
+ * Driver for the s5k83a sensor
+ *
+ * Copyright (C) 2008 Erik Andrén
+ * Copyright (C) 2007 Ilyes Gouta. Based on the m5603x Linux Driver Project.
+ * Copyright (C) 2005 m5603x Linux Driver Project <m5602@x3ng.com.br>
+ *
+ * Portions of code to USB interface and ALi driver software,
+ * Copyright (c) 2006 Willem Duinker
+ * v4l2 interface modeled after the V4L2 driver
+ * for SN9C10x PC Camera Controllers
+ *
+ * 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, version 2.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kthread.h>
+#include "m5602_s5k83a.h"
+
+static int s5k83a_s_ctrl(struct v4l2_ctrl *ctrl);
+
+static const struct v4l2_ctrl_ops s5k83a_ctrl_ops = {
+	.s_ctrl = s5k83a_s_ctrl,
+};
+
+static struct v4l2_pix_format s5k83a_modes[] = {
+	{
+		640,
+		480,
+		V4L2_PIX_FMT_SBGGR8,
+		V4L2_FIELD_NONE,
+		.sizeimage =
+			640 * 480,
+		.bytesperline = 640,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0
+	}
+};
+
+static const unsigned char preinit_s5k83a[][4] = {
+	{BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00},
+	{BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00},
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x0d, 0x00},
+	{BRIDGE, M5602_XB_SENSOR_CTRL, 0x00, 0x00},
+
+	{BRIDGE, M5602_XB_SIG_INI, 0x00, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x08, 0x00},
+	{BRIDGE, M5602_XB_GPIO_EN_H, 0x3f, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR_H, 0x3f, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00},
+	{BRIDGE, M5602_XB_GPIO_EN_L, 0xff, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR_L, 0xff, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT_L, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0x80, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00},
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x09, 0x00},
+	{BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00},
+	{BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xf0, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x1c, 0x00},
+	{BRIDGE, M5602_XB_GPIO_EN_H, 0x06, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR_H, 0x06, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00},
+	{BRIDGE, M5602_XB_GPIO_EN_L, 0x00, 0x00},
+	{BRIDGE, M5602_XB_I2C_CLK_DIV, 0x20, 0x00},
+};
+
+/* This could probably be considerably shortened.
+   I don't have the hardware to experiment with it, patches welcome
+*/
+static const unsigned char init_s5k83a[][4] = {
+	/* The following sequence is useless after a clean boot
+	   but is necessary after resume from suspend */
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x08, 0x00},
+	{BRIDGE, M5602_XB_GPIO_EN_H, 0x3f, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR_H, 0x3f, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00},
+	{BRIDGE, M5602_XB_GPIO_EN_L, 0xff, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR_L, 0xff, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT_L, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0x80, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00},
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x09, 0x00},
+	{BRIDGE, M5602_XB_MCU_CLK_DIV, 0x02, 0x00},
+	{BRIDGE, M5602_XB_MCU_CLK_CTRL, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xf0, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR, 0x1d, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT, 0x08, 0x00},
+	{BRIDGE, M5602_XB_GPIO_EN_H, 0x06, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DIR_H, 0x06, 0x00},
+	{BRIDGE, M5602_XB_GPIO_DAT_H, 0x00, 0x00},
+	{BRIDGE, M5602_XB_GPIO_EN_L, 0x00, 0x00},
+	{BRIDGE, M5602_XB_I2C_CLK_DIV, 0x20, 0x00},
+
+	{SENSOR, S5K83A_PAGE_MAP, 0x04, 0x00},
+	{SENSOR, 0xaf, 0x01, 0x00},
+	{SENSOR, S5K83A_PAGE_MAP, 0x00, 0x00},
+	{SENSOR, 0x7b, 0xff, 0x00},
+	{SENSOR, S5K83A_PAGE_MAP, 0x05, 0x00},
+	{SENSOR, 0x01, 0x50, 0x00},
+	{SENSOR, 0x12, 0x20, 0x00},
+	{SENSOR, 0x17, 0x40, 0x00},
+	{SENSOR, 0x1c, 0x00, 0x00},
+	{SENSOR, 0x02, 0x70, 0x00},
+	{SENSOR, 0x03, 0x0b, 0x00},
+	{SENSOR, 0x04, 0xf0, 0x00},
+	{SENSOR, 0x05, 0x0b, 0x00},
+	{SENSOR, 0x06, 0x71, 0x00},
+	{SENSOR, 0x07, 0xe8, 0x00}, /* 488 */
+	{SENSOR, 0x08, 0x02, 0x00},
+	{SENSOR, 0x09, 0x88, 0x00}, /* 648 */
+	{SENSOR, 0x14, 0x00, 0x00},
+	{SENSOR, 0x15, 0x20, 0x00}, /* 32 */
+	{SENSOR, 0x19, 0x00, 0x00},
+	{SENSOR, 0x1a, 0x98, 0x00}, /* 152 */
+	{SENSOR, 0x0f, 0x02, 0x00},
+	{SENSOR, 0x10, 0xe5, 0x00}, /* 741 */
+	/* normal colors
+	(this is value after boot, but after tries can be different) */
+	{SENSOR, 0x00, 0x06, 0x00},
+};
+
+static const unsigned char start_s5k83a[][4] = {
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x06, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00},
+	{BRIDGE, M5602_XB_ADC_CTRL, 0xc0, 0x00},
+	{BRIDGE, M5602_XB_SENSOR_TYPE, 0x09, 0x00},
+	{BRIDGE, M5602_XB_LINE_OF_FRAME_H, 0x81, 0x00},
+	{BRIDGE, M5602_XB_PIX_OF_LINE_H, 0x82, 0x00},
+	{BRIDGE, M5602_XB_SIG_INI, 0x01, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x01, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0xe4, 0x00}, /* 484 */
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},
+	{BRIDGE, M5602_XB_VSYNC_PARA, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SIG_INI, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SIG_INI, 0x02, 0x00},
+	{BRIDGE, M5602_XB_HSYNC_PARA, 0x00, 0x00},
+	{BRIDGE, M5602_XB_HSYNC_PARA, 0x00, 0x00},
+	{BRIDGE, M5602_XB_HSYNC_PARA, 0x02, 0x00},
+	{BRIDGE, M5602_XB_HSYNC_PARA, 0x7f, 0x00}, /* 639 */
+	{BRIDGE, M5602_XB_SIG_INI, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_DIV, 0x00, 0x00},
+	{BRIDGE, M5602_XB_SEN_CLK_CTRL, 0xb0, 0x00},
+};
+
+static void s5k83a_dump_registers(struct sd *sd);
+static int s5k83a_get_rotation(struct sd *sd, u8 *reg_data);
+static int s5k83a_set_led_indication(struct sd *sd, u8 val);
+static int s5k83a_set_flip_real(struct gspca_dev *gspca_dev,
+				__s32 vflip, __s32 hflip);
+
+int s5k83a_probe(struct sd *sd)
+{
+	u8 prod_id = 0, ver_id = 0;
+	int i, err = 0;
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+
+	if (force_sensor) {
+		if (force_sensor == S5K83A_SENSOR) {
+			pr_info("Forcing a %s sensor\n", s5k83a.name);
+			goto sensor_found;
+		}
+		/* If we want to force another sensor, don't try to probe this
+		 * one */
+		return -ENODEV;
+	}
+
+	gspca_dbg(gspca_dev, D_PROBE, "Probing for a s5k83a sensor\n");
+
+	/* Preinit the sensor */
+	for (i = 0; i < ARRAY_SIZE(preinit_s5k83a) && !err; i++) {
+		u8 data[2] = {preinit_s5k83a[i][2], preinit_s5k83a[i][3]};
+		if (preinit_s5k83a[i][0] == SENSOR)
+			err = m5602_write_sensor(sd, preinit_s5k83a[i][1],
+				data, 2);
+		else
+			err = m5602_write_bridge(sd, preinit_s5k83a[i][1],
+				data[0]);
+	}
+
+	/* We don't know what register (if any) that contain the product id
+	 * Just pick the first addresses that seem to produce the same results
+	 * on multiple machines */
+	if (m5602_read_sensor(sd, 0x00, &prod_id, 1))
+		return -ENODEV;
+
+	if (m5602_read_sensor(sd, 0x01, &ver_id, 1))
+		return -ENODEV;
+
+	if ((prod_id == 0xff) || (ver_id == 0xff))
+		return -ENODEV;
+	else
+		pr_info("Detected a s5k83a sensor\n");
+
+sensor_found:
+	sd->gspca_dev.cam.cam_mode = s5k83a_modes;
+	sd->gspca_dev.cam.nmodes = ARRAY_SIZE(s5k83a_modes);
+
+	/* null the pointer! thread is't running now */
+	sd->rotation_thread = NULL;
+
+	return 0;
+}
+
+int s5k83a_init(struct sd *sd)
+{
+	int i, err = 0;
+
+	for (i = 0; i < ARRAY_SIZE(init_s5k83a) && !err; i++) {
+		u8 data[2] = {0x00, 0x00};
+
+		switch (init_s5k83a[i][0]) {
+		case BRIDGE:
+			err = m5602_write_bridge(sd,
+					init_s5k83a[i][1],
+					init_s5k83a[i][2]);
+			break;
+
+		case SENSOR:
+			data[0] = init_s5k83a[i][2];
+			err = m5602_write_sensor(sd,
+				init_s5k83a[i][1], data, 1);
+			break;
+
+		case SENSOR_LONG:
+			data[0] = init_s5k83a[i][2];
+			data[1] = init_s5k83a[i][3];
+			err = m5602_write_sensor(sd,
+				init_s5k83a[i][1], data, 2);
+			break;
+		default:
+			pr_info("Invalid stream command, exiting init\n");
+			return -EINVAL;
+		}
+	}
+
+	if (dump_sensor)
+		s5k83a_dump_registers(sd);
+
+	return err;
+}
+
+int s5k83a_init_controls(struct sd *sd)
+{
+	struct v4l2_ctrl_handler *hdl = &sd->gspca_dev.ctrl_handler;
+
+	sd->gspca_dev.vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 6);
+
+	v4l2_ctrl_new_std(hdl, &s5k83a_ctrl_ops, V4L2_CID_BRIGHTNESS,
+			  0, 255, 1, S5K83A_DEFAULT_BRIGHTNESS);
+
+	v4l2_ctrl_new_std(hdl, &s5k83a_ctrl_ops, V4L2_CID_EXPOSURE,
+			  0, S5K83A_MAXIMUM_EXPOSURE, 1,
+			  S5K83A_DEFAULT_EXPOSURE);
+
+	v4l2_ctrl_new_std(hdl, &s5k83a_ctrl_ops, V4L2_CID_GAIN,
+			  0, 255, 1, S5K83A_DEFAULT_GAIN);
+
+	sd->hflip = v4l2_ctrl_new_std(hdl, &s5k83a_ctrl_ops, V4L2_CID_HFLIP,
+				      0, 1, 1, 0);
+	sd->vflip = v4l2_ctrl_new_std(hdl, &s5k83a_ctrl_ops, V4L2_CID_VFLIP,
+				      0, 1, 1, 0);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+
+	v4l2_ctrl_cluster(2, &sd->hflip);
+
+	return 0;
+}
+
+static int rotation_thread_function(void *data)
+{
+	struct sd *sd = (struct sd *) data;
+	u8 reg, previous_rotation = 0;
+	__s32 vflip, hflip;
+
+	set_current_state(TASK_INTERRUPTIBLE);
+	while (!schedule_timeout(msecs_to_jiffies(100))) {
+		if (mutex_lock_interruptible(&sd->gspca_dev.usb_lock))
+			break;
+
+		s5k83a_get_rotation(sd, &reg);
+		if (previous_rotation != reg) {
+			previous_rotation = reg;
+			pr_info("Camera was flipped\n");
+
+			hflip = sd->hflip->val;
+			vflip = sd->vflip->val;
+
+			if (reg) {
+				vflip = !vflip;
+				hflip = !hflip;
+			}
+			s5k83a_set_flip_real((struct gspca_dev *) sd,
+					      vflip, hflip);
+		}
+
+		mutex_unlock(&sd->gspca_dev.usb_lock);
+		set_current_state(TASK_INTERRUPTIBLE);
+	}
+
+	/* return to "front" flip */
+	if (previous_rotation) {
+		hflip = sd->hflip->val;
+		vflip = sd->vflip->val;
+		s5k83a_set_flip_real((struct gspca_dev *) sd, vflip, hflip);
+	}
+
+	sd->rotation_thread = NULL;
+	return 0;
+}
+
+int s5k83a_start(struct sd *sd)
+{
+	int i, err = 0;
+
+	/* Create another thread, polling the GPIO ports of the camera to check
+	   if it got rotated. This is how the windows driver does it so we have
+	   to assume that there is no better way of accomplishing this */
+	sd->rotation_thread = kthread_create(rotation_thread_function,
+					     sd, "rotation thread");
+	if (IS_ERR(sd->rotation_thread)) {
+		err = PTR_ERR(sd->rotation_thread);
+		sd->rotation_thread = NULL;
+		return err;
+	}
+	wake_up_process(sd->rotation_thread);
+
+	/* Preinit the sensor */
+	for (i = 0; i < ARRAY_SIZE(start_s5k83a) && !err; i++) {
+		u8 data[2] = {start_s5k83a[i][2], start_s5k83a[i][3]};
+		if (start_s5k83a[i][0] == SENSOR)
+			err = m5602_write_sensor(sd, start_s5k83a[i][1],
+				data, 2);
+		else
+			err = m5602_write_bridge(sd, start_s5k83a[i][1],
+				data[0]);
+	}
+	if (err < 0)
+		return err;
+
+	return s5k83a_set_led_indication(sd, 1);
+}
+
+int s5k83a_stop(struct sd *sd)
+{
+	if (sd->rotation_thread)
+		kthread_stop(sd->rotation_thread);
+
+	return s5k83a_set_led_indication(sd, 0);
+}
+
+void s5k83a_disconnect(struct sd *sd)
+{
+	s5k83a_stop(sd);
+
+	sd->sensor = NULL;
+}
+
+static int s5k83a_set_gain(struct gspca_dev *gspca_dev, __s32 val)
+{
+	int err;
+	u8 data[2];
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	data[0] = 0x00;
+	data[1] = 0x20;
+	err = m5602_write_sensor(sd, 0x14, data, 2);
+	if (err < 0)
+		return err;
+
+	data[0] = 0x01;
+	data[1] = 0x00;
+	err = m5602_write_sensor(sd, 0x0d, data, 2);
+	if (err < 0)
+		return err;
+
+	/* FIXME: This is not sane, we need to figure out the composition
+		  of these registers */
+	data[0] = val >> 3; /* gain, high 5 bits */
+	data[1] = val >> 1; /* gain, high 7 bits */
+	err = m5602_write_sensor(sd, S5K83A_GAIN, data, 2);
+
+	return err;
+}
+
+static int s5k83a_set_brightness(struct gspca_dev *gspca_dev, __s32 val)
+{
+	int err;
+	u8 data[1];
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	data[0] = val;
+	err = m5602_write_sensor(sd, S5K83A_BRIGHTNESS, data, 1);
+	return err;
+}
+
+static int s5k83a_set_exposure(struct gspca_dev *gspca_dev, __s32 val)
+{
+	int err;
+	u8 data[2];
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	data[0] = 0;
+	data[1] = val;
+	err = m5602_write_sensor(sd, S5K83A_EXPOSURE, data, 2);
+	return err;
+}
+
+static int s5k83a_set_flip_real(struct gspca_dev *gspca_dev,
+				__s32 vflip, __s32 hflip)
+{
+	int err;
+	u8 data[1];
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	data[0] = 0x05;
+	err = m5602_write_sensor(sd, S5K83A_PAGE_MAP, data, 1);
+	if (err < 0)
+		return err;
+
+	/* six bit is vflip, seven is hflip */
+	data[0] = S5K83A_FLIP_MASK;
+	data[0] = (vflip) ? data[0] | 0x40 : data[0];
+	data[0] = (hflip) ? data[0] | 0x80 : data[0];
+
+	err = m5602_write_sensor(sd, S5K83A_FLIP, data, 1);
+	if (err < 0)
+		return err;
+
+	data[0] = (vflip) ? 0x0b : 0x0a;
+	err = m5602_write_sensor(sd, S5K83A_VFLIP_TUNE, data, 1);
+	if (err < 0)
+		return err;
+
+	data[0] = (hflip) ? 0x0a : 0x0b;
+	err = m5602_write_sensor(sd, S5K83A_HFLIP_TUNE, data, 1);
+	return err;
+}
+
+static int s5k83a_set_hvflip(struct gspca_dev *gspca_dev)
+{
+	int err;
+	u8 reg;
+	struct sd *sd = (struct sd *) gspca_dev;
+	int hflip = sd->hflip->val;
+	int vflip = sd->vflip->val;
+
+	err = s5k83a_get_rotation(sd, &reg);
+	if (err < 0)
+		return err;
+	if (reg) {
+		hflip = !hflip;
+		vflip = !vflip;
+	}
+
+	err = s5k83a_set_flip_real(gspca_dev, vflip, hflip);
+	return err;
+}
+
+static int s5k83a_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	int err;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		err = s5k83a_set_brightness(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_EXPOSURE:
+		err = s5k83a_set_exposure(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_GAIN:
+		err = s5k83a_set_gain(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_HFLIP:
+		err = s5k83a_set_hvflip(gspca_dev);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return err;
+}
+
+static int s5k83a_set_led_indication(struct sd *sd, u8 val)
+{
+	int err = 0;
+	u8 data[1];
+
+	err = m5602_read_bridge(sd, M5602_XB_GPIO_DAT, data);
+	if (err < 0)
+		return err;
+
+	if (val)
+		data[0] = data[0] | S5K83A_GPIO_LED_MASK;
+	else
+		data[0] = data[0] & ~S5K83A_GPIO_LED_MASK;
+
+	err = m5602_write_bridge(sd, M5602_XB_GPIO_DAT, data[0]);
+
+	return err;
+}
+
+/* Get camera rotation on Acer notebooks */
+static int s5k83a_get_rotation(struct sd *sd, u8 *reg_data)
+{
+	int err = m5602_read_bridge(sd, M5602_XB_GPIO_DAT, reg_data);
+	*reg_data = (*reg_data & S5K83A_GPIO_ROTATION_MASK) ? 0 : 1;
+	return err;
+}
+
+static void s5k83a_dump_registers(struct sd *sd)
+{
+	int address;
+	u8 page, old_page;
+	m5602_read_sensor(sd, S5K83A_PAGE_MAP, &old_page, 1);
+
+	for (page = 0; page < 16; page++) {
+		m5602_write_sensor(sd, S5K83A_PAGE_MAP, &page, 1);
+		pr_info("Dumping the s5k83a register state for page 0x%x\n",
+			page);
+		for (address = 0; address <= 0xff; address++) {
+			u8 val = 0;
+			m5602_read_sensor(sd, address, &val, 1);
+			pr_info("register 0x%x contains 0x%x\n", address, val);
+		}
+	}
+	pr_info("s5k83a register state dump complete\n");
+
+	for (page = 0; page < 16; page++) {
+		m5602_write_sensor(sd, S5K83A_PAGE_MAP, &page, 1);
+		pr_info("Probing for which registers that are read/write for page 0x%x\n",
+			page);
+		for (address = 0; address <= 0xff; address++) {
+			u8 old_val, ctrl_val, test_val = 0xff;
+
+			m5602_read_sensor(sd, address, &old_val, 1);
+			m5602_write_sensor(sd, address, &test_val, 1);
+			m5602_read_sensor(sd, address, &ctrl_val, 1);
+
+			if (ctrl_val == test_val)
+				pr_info("register 0x%x is writeable\n",
+					address);
+			else
+				pr_info("register 0x%x is read only\n",
+					address);
+
+			/* Restore original val */
+			m5602_write_sensor(sd, address, &old_val, 1);
+		}
+	}
+	pr_info("Read/write register probing complete\n");
+	m5602_write_sensor(sd, S5K83A_PAGE_MAP, &old_page, 1);
+}
diff --git a/drivers/media/usb/gspca/m5602/m5602_s5k83a.h b/drivers/media/usb/gspca/m5602/m5602_s5k83a.h
new file mode 100644
index 0000000..3212bfe
--- /dev/null
+++ b/drivers/media/usb/gspca/m5602/m5602_s5k83a.h
@@ -0,0 +1,64 @@
+/*
+ * Driver for the s5k83a sensor
+ *
+ * Copyright (C) 2008 Erik Andrén
+ * Copyright (C) 2007 Ilyes Gouta. Based on the m5603x Linux Driver Project.
+ * Copyright (C) 2005 m5603x Linux Driver Project <m5602@x3ng.com.br>
+ *
+ * Portions of code to USB interface and ALi driver software,
+ * Copyright (c) 2006 Willem Duinker
+ * v4l2 interface modeled after the V4L2 driver
+ * for SN9C10x PC Camera Controllers
+ *
+ * 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, version 2.
+ *
+ */
+
+#ifndef M5602_S5K83A_H_
+#define M5602_S5K83A_H_
+
+#include "m5602_sensor.h"
+
+#define S5K83A_FLIP			0x01
+#define S5K83A_HFLIP_TUNE		0x03
+#define S5K83A_VFLIP_TUNE		0x05
+#define S5K83A_BRIGHTNESS		0x0a
+#define S5K83A_EXPOSURE			0x18
+#define S5K83A_GAIN			0x1b
+#define S5K83A_PAGE_MAP			0xec
+
+#define S5K83A_DEFAULT_GAIN		0x71
+#define S5K83A_DEFAULT_BRIGHTNESS	0x7e
+#define S5K83A_DEFAULT_EXPOSURE		0x00
+#define S5K83A_MAXIMUM_EXPOSURE		0x3c
+#define S5K83A_FLIP_MASK		0x10
+#define S5K83A_GPIO_LED_MASK		0x10
+#define S5K83A_GPIO_ROTATION_MASK	0x40
+
+/*****************************************************************************/
+
+/* Kernel module parameters */
+extern int force_sensor;
+extern bool dump_sensor;
+
+int s5k83a_probe(struct sd *sd);
+int s5k83a_init(struct sd *sd);
+int s5k83a_init_controls(struct sd *sd);
+int s5k83a_start(struct sd *sd);
+int s5k83a_stop(struct sd *sd);
+void s5k83a_disconnect(struct sd *sd);
+
+static const struct m5602_sensor s5k83a = {
+	.name = "S5K83A",
+	.probe = s5k83a_probe,
+	.init = s5k83a_init,
+	.init_controls = s5k83a_init_controls,
+	.start = s5k83a_start,
+	.stop = s5k83a_stop,
+	.disconnect = s5k83a_disconnect,
+	.i2c_slave_id = 0x5a,
+	.i2c_regW = 2,
+};
+#endif
diff --git a/drivers/media/usb/gspca/m5602/m5602_sensor.h b/drivers/media/usb/gspca/m5602/m5602_sensor.h
new file mode 100644
index 0000000..48341b4
--- /dev/null
+++ b/drivers/media/usb/gspca/m5602/m5602_sensor.h
@@ -0,0 +1,73 @@
+/*
+ * USB Driver for ALi m5602 based webcams
+ *
+ * Copyright (C) 2008 Erik Andrén
+ * Copyright (C) 2007 Ilyes Gouta. Based on the m5603x Linux Driver Project.
+ * Copyright (C) 2005 m5603x Linux Driver Project <m5602@x3ng.com.br>
+ *
+ * Portions of code to USB interface and ALi driver software,
+ * Copyright (c) 2006 Willem Duinker
+ * v4l2 interface modeled after the V4L2 driver
+ * for SN9C10x PC Camera Controllers
+ *
+ * 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, version 2.
+ *
+ */
+
+#ifndef M5602_SENSOR_H_
+#define M5602_SENSOR_H_
+
+#include "m5602_bridge.h"
+
+#define M5602_V4L2_CID_GREEN_BALANCE	(V4L2_CID_PRIVATE_BASE + 0)
+#define M5602_V4L2_CID_NOISE_SUPPRESION	(V4L2_CID_PRIVATE_BASE + 1)
+
+/* Enumerates all supported sensors */
+enum sensors {
+	OV9650_SENSOR	= 1,
+	S5K83A_SENSOR	= 2,
+	S5K4AA_SENSOR	= 3,
+	MT9M111_SENSOR	= 4,
+	PO1030_SENSOR	= 5,
+	OV7660_SENSOR   = 6,
+};
+
+/* Enumerates all possible instruction types */
+enum instruction {
+	BRIDGE,
+	SENSOR,
+	SENSOR_LONG
+};
+
+struct m5602_sensor {
+	/* Defines the name of a sensor */
+	char name[32];
+
+	/* What i2c address the sensor is connected to */
+	u8 i2c_slave_id;
+
+	/* Width of each i2c register (in bytes) */
+	u8 i2c_regW;
+
+	/* Probes if the sensor is connected */
+	int (*probe)(struct sd *sd);
+
+	/* Performs a initialization sequence */
+	int (*init)(struct sd *sd);
+
+	/* Controls initialization, maybe NULL */
+	int (*init_controls)(struct sd *sd);
+
+	/* Executed when the camera starts to send data */
+	int (*start)(struct sd *sd);
+
+	/* Executed when the camera ends to send data */
+	int (*stop)(struct sd *sd);
+
+	/* Executed when the device is disconnected */
+	void (*disconnect)(struct sd *sd);
+};
+
+#endif
diff --git a/drivers/media/usb/gspca/mars.c b/drivers/media/usb/gspca/mars.c
new file mode 100644
index 0000000..a537cb1
--- /dev/null
+++ b/drivers/media/usb/gspca/mars.c
@@ -0,0 +1,436 @@
+/*
+ *		Mars-Semi MR97311A library
+ *		Copyright (C) 2005 <bradlch@hotmail.com>
+ *
+ * V4L2 by Jean-Francois Moine <http://moinejf.free.fr>
+ *
+ * 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
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "mars"
+
+#include "gspca.h"
+#include "jpeg.h"
+
+MODULE_AUTHOR("Michel Xhaard <mxhaard@users.sourceforge.net>");
+MODULE_DESCRIPTION("GSPCA/Mars USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+#define QUALITY 50
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+
+	struct v4l2_ctrl *brightness;
+	struct v4l2_ctrl *saturation;
+	struct v4l2_ctrl *sharpness;
+	struct v4l2_ctrl *gamma;
+	struct { /* illuminator control cluster */
+		struct v4l2_ctrl *illum_top;
+		struct v4l2_ctrl *illum_bottom;
+	};
+	u8 jpeg_hdr[JPEG_HDR_SZ];
+};
+
+/* V4L2 controls supported by the driver */
+static void setbrightness(struct gspca_dev *gspca_dev, s32 val);
+static void setcolors(struct gspca_dev *gspca_dev, s32 val);
+static void setgamma(struct gspca_dev *gspca_dev, s32 val);
+static void setsharpness(struct gspca_dev *gspca_dev, s32 val);
+
+static const struct v4l2_pix_format vga_mode[] = {
+	{320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 2},
+	{640, 480, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 1},
+};
+
+static const __u8 mi_data[0x20] = {
+/*	 01    02   03     04    05    06    07    08 */
+	0x48, 0x22, 0x01, 0x47, 0x10, 0x00, 0x00, 0x00,
+/*	 09    0a   0b     0c    0d    0e    0f    10 */
+	0x00, 0x01, 0x30, 0x01, 0x30, 0x01, 0x30, 0x01,
+/*	 11    12   13     14    15    16    17    18 */
+	0x30, 0x00, 0x04, 0x00, 0x06, 0x01, 0xe2, 0x02,
+/*	 19    1a   1b     1c    1d    1e    1f    20 */
+	0x82, 0x00, 0x20, 0x17, 0x80, 0x08, 0x0c, 0x00
+};
+
+/* write <len> bytes from gspca_dev->usb_buf */
+static void reg_w(struct gspca_dev *gspca_dev,
+		 int len)
+{
+	int alen, ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+
+	ret = usb_bulk_msg(gspca_dev->dev,
+			usb_sndbulkpipe(gspca_dev->dev, 4),
+			gspca_dev->usb_buf,
+			len,
+			&alen,
+			500);	/* timeout in milliseconds */
+	if (ret < 0) {
+		pr_err("reg write [%02x] error %d\n",
+		       gspca_dev->usb_buf[0], ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+static void mi_w(struct gspca_dev *gspca_dev,
+		 u8 addr,
+		 u8 value)
+{
+	gspca_dev->usb_buf[0] = 0x1f;
+	gspca_dev->usb_buf[1] = 0;			/* control byte */
+	gspca_dev->usb_buf[2] = addr;
+	gspca_dev->usb_buf[3] = value;
+
+	reg_w(gspca_dev, 4);
+}
+
+static void setbrightness(struct gspca_dev *gspca_dev, s32 val)
+{
+	gspca_dev->usb_buf[0] = 0x61;
+	gspca_dev->usb_buf[1] = val;
+	reg_w(gspca_dev, 2);
+}
+
+static void setcolors(struct gspca_dev *gspca_dev, s32 val)
+{
+	gspca_dev->usb_buf[0] = 0x5f;
+	gspca_dev->usb_buf[1] = val << 3;
+	gspca_dev->usb_buf[2] = ((val >> 2) & 0xf8) | 0x04;
+	reg_w(gspca_dev, 3);
+}
+
+static void setgamma(struct gspca_dev *gspca_dev, s32 val)
+{
+	gspca_dev->usb_buf[0] = 0x06;
+	gspca_dev->usb_buf[1] = val * 0x40;
+	reg_w(gspca_dev, 2);
+}
+
+static void setsharpness(struct gspca_dev *gspca_dev, s32 val)
+{
+	gspca_dev->usb_buf[0] = 0x67;
+	gspca_dev->usb_buf[1] = val * 4 + 3;
+	reg_w(gspca_dev, 2);
+}
+
+static void setilluminators(struct gspca_dev *gspca_dev, bool top, bool bottom)
+{
+	/* both are off if not streaming */
+	gspca_dev->usb_buf[0] = 0x22;
+	if (top)
+		gspca_dev->usb_buf[1] = 0x76;
+	else if (bottom)
+		gspca_dev->usb_buf[1] = 0x7a;
+	else
+		gspca_dev->usb_buf[1] = 0x7e;
+	reg_w(gspca_dev, 2);
+}
+
+static int mars_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *)gspca_dev;
+
+	gspca_dev->usb_err = 0;
+
+	if (ctrl->id == V4L2_CID_ILLUMINATORS_1) {
+		/* only one can be on at a time */
+		if (ctrl->is_new && ctrl->val)
+			sd->illum_bottom->val = 0;
+		if (sd->illum_bottom->is_new && sd->illum_bottom->val)
+			sd->illum_top->val = 0;
+	}
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		setbrightness(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_SATURATION:
+		setcolors(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_GAMMA:
+		setgamma(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_ILLUMINATORS_1:
+		setilluminators(gspca_dev, sd->illum_top->val,
+					   sd->illum_bottom->val);
+		break;
+	case V4L2_CID_SHARPNESS:
+		setsharpness(gspca_dev, ctrl->val);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops mars_ctrl_ops = {
+	.s_ctrl = mars_s_ctrl,
+};
+
+/* this function is called at probe time */
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 6);
+	sd->brightness = v4l2_ctrl_new_std(hdl, &mars_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0, 30, 1, 15);
+	sd->saturation = v4l2_ctrl_new_std(hdl, &mars_ctrl_ops,
+			V4L2_CID_SATURATION, 0, 255, 1, 200);
+	sd->gamma = v4l2_ctrl_new_std(hdl, &mars_ctrl_ops,
+			V4L2_CID_GAMMA, 0, 3, 1, 1);
+	sd->sharpness = v4l2_ctrl_new_std(hdl, &mars_ctrl_ops,
+			V4L2_CID_SHARPNESS, 0, 2, 1, 1);
+	sd->illum_top = v4l2_ctrl_new_std(hdl, &mars_ctrl_ops,
+			V4L2_CID_ILLUMINATORS_1, 0, 1, 1, 0);
+	sd->illum_top->flags |= V4L2_CTRL_FLAG_UPDATE;
+	sd->illum_bottom = v4l2_ctrl_new_std(hdl, &mars_ctrl_ops,
+			V4L2_CID_ILLUMINATORS_2, 0, 1, 1, 0);
+	sd->illum_bottom->flags |= V4L2_CTRL_FLAG_UPDATE;
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	v4l2_ctrl_cluster(2, &sd->illum_top);
+	return 0;
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	struct cam *cam;
+
+	cam = &gspca_dev->cam;
+	cam->cam_mode = vga_mode;
+	cam->nmodes = ARRAY_SIZE(vga_mode);
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	return 0;
+}
+
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 *data;
+	int i;
+
+	/* create the JPEG header */
+	jpeg_define(sd->jpeg_hdr, gspca_dev->pixfmt.height,
+			gspca_dev->pixfmt.width,
+			0x21);		/* JPEG 422 */
+	jpeg_set_qual(sd->jpeg_hdr, QUALITY);
+
+	data = gspca_dev->usb_buf;
+
+	data[0] = 0x01;		/* address */
+	data[1] = 0x01;
+	reg_w(gspca_dev, 2);
+
+	/*
+	   Initialize the MR97113 chip register
+	 */
+	data[0] = 0x00;		/* address */
+	data[1] = 0x0c | 0x01;	/* reg 0 */
+	data[2] = 0x01;		/* reg 1 */
+	data[3] = gspca_dev->pixfmt.width / 8;	/* h_size , reg 2 */
+	data[4] = gspca_dev->pixfmt.height / 8;	/* v_size , reg 3 */
+	data[5] = 0x30;		/* reg 4, MI, PAS5101 :
+				 *	0x30 for 24mhz , 0x28 for 12mhz */
+	data[6] = 0x02;		/* reg 5, H start - was 0x04 */
+	data[7] = v4l2_ctrl_g_ctrl(sd->gamma) * 0x40;	/* reg 0x06: gamma */
+	data[8] = 0x01;		/* reg 7, V start - was 0x03 */
+/*	if (h_size == 320 ) */
+/*		data[9]= 0x56;	 * reg 8, 24MHz, 2:1 scale down */
+/*	else */
+	data[9] = 0x52;		/* reg 8, 24MHz, no scale down */
+/*jfm: from win trace*/
+	data[10] = 0x18;
+
+	reg_w(gspca_dev, 11);
+
+	data[0] = 0x23;		/* address */
+	data[1] = 0x09;		/* reg 35, append frame header */
+
+	reg_w(gspca_dev, 2);
+
+	data[0] = 0x3c;		/* address */
+/*	if (gspca_dev->width == 1280) */
+/*		data[1] = 200;	 * reg 60, pc-cam frame size
+				 *	(unit: 4KB) 800KB */
+/*	else */
+	data[1] = 50;		/* 50 reg 60, pc-cam frame size
+				 *	(unit: 4KB) 200KB */
+	reg_w(gspca_dev, 2);
+
+	/* auto dark-gain */
+	data[0] = 0x5e;		/* address */
+	data[1] = 0;		/* reg 94, Y Gain (auto) */
+/*jfm: from win trace*/
+				/* reg 0x5f/0x60 (LE) = saturation */
+				/* h (60): xxxx x100
+				 * l (5f): xxxx x000 */
+	data[2] = v4l2_ctrl_g_ctrl(sd->saturation) << 3;
+	data[3] = ((v4l2_ctrl_g_ctrl(sd->saturation) >> 2) & 0xf8) | 0x04;
+	data[4] = v4l2_ctrl_g_ctrl(sd->brightness); /* reg 0x61 = brightness */
+	data[5] = 0x00;
+
+	reg_w(gspca_dev, 6);
+
+	data[0] = 0x67;
+/*jfm: from win trace*/
+	data[1] = v4l2_ctrl_g_ctrl(sd->sharpness) * 4 + 3;
+	data[2] = 0x14;
+	reg_w(gspca_dev, 3);
+
+	data[0] = 0x69;
+	data[1] = 0x2f;
+	data[2] = 0x28;
+	data[3] = 0x42;
+	reg_w(gspca_dev, 4);
+
+	data[0] = 0x63;
+	data[1] = 0x07;
+	reg_w(gspca_dev, 2);
+/*jfm: win trace - many writes here to reg 0x64*/
+
+	/* initialize the MI sensor */
+	for (i = 0; i < sizeof mi_data; i++)
+		mi_w(gspca_dev, i + 1, mi_data[i]);
+
+	data[0] = 0x00;
+	data[1] = 0x4d;		/* ISOC transferring enable... */
+	reg_w(gspca_dev, 2);
+
+	setilluminators(gspca_dev, v4l2_ctrl_g_ctrl(sd->illum_top),
+				   v4l2_ctrl_g_ctrl(sd->illum_bottom));
+
+	return gspca_dev->usb_err;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (v4l2_ctrl_g_ctrl(sd->illum_top) ||
+	    v4l2_ctrl_g_ctrl(sd->illum_bottom)) {
+		setilluminators(gspca_dev, false, false);
+		msleep(20);
+	}
+
+	gspca_dev->usb_buf[0] = 1;
+	gspca_dev->usb_buf[1] = 0;
+	reg_w(gspca_dev, 2);
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int p;
+
+	if (len < 6) {
+/*		gspca_dev->last_packet_type = DISCARD_PACKET; */
+		return;
+	}
+	for (p = 0; p < len - 6; p++) {
+		if (data[0 + p] == 0xff
+		    && data[1 + p] == 0xff
+		    && data[2 + p] == 0x00
+		    && data[3 + p] == 0xff
+		    && data[4 + p] == 0x96) {
+			if (data[5 + p] == 0x64
+			    || data[5 + p] == 0x65
+			    || data[5 + p] == 0x66
+			    || data[5 + p] == 0x67) {
+				gspca_dbg(gspca_dev, D_PACK, "sof offset: %d len: %d\n",
+					  p, len);
+				gspca_frame_add(gspca_dev, LAST_PACKET,
+						data, p);
+
+				/* put the JPEG header */
+				gspca_frame_add(gspca_dev, FIRST_PACKET,
+					sd->jpeg_hdr, JPEG_HDR_SZ);
+				data += p + 16;
+				len -= p + 16;
+				break;
+			}
+		}
+	}
+	gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.pkt_scan = sd_pkt_scan,
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x093a, 0x050f)},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/mr97310a.c b/drivers/media/usb/gspca/mr97310a.c
new file mode 100644
index 0000000..bea1963
--- /dev/null
+++ b/drivers/media/usb/gspca/mr97310a.c
@@ -0,0 +1,1087 @@
+/*
+ * Mars MR97310A library
+ *
+ * The original mr97310a driver, which supported the Aiptek Pencam VGA+, is
+ * Copyright (C) 2009 Kyle Guinn <elyk03@gmail.com>
+ *
+ * Support for the MR97310A cameras in addition to the Aiptek Pencam VGA+
+ * and for the routines for detecting and classifying these various cameras,
+ * is Copyright (C) 2009 Theodore Kilgore <kilgota@auburn.edu>
+ *
+ * Support for the control settings for the CIF cameras is
+ * Copyright (C) 2009 Hans de Goede <hdegoede@redhat.com> and
+ * Thomas Kaiser <thomas@kaiser-linux.li>
+ *
+ * Support for the control settings for the VGA cameras is
+ * Copyright (C) 2009 Theodore Kilgore <kilgota@auburn.edu>
+ *
+ * Several previously unsupported cameras are owned and have been tested by
+ * Hans de Goede <hdegoede@redhat.com> and
+ * Thomas Kaiser <thomas@kaiser-linux.li> and
+ * Theodore Kilgore <kilgota@auburn.edu> and
+ * Edmond Rodriguez <erodrig_97@yahoo.com> and
+ * Aurelien Jacobs <aurel@gnuage.org>
+ *
+ * The MR97311A support in gspca/mars.c has been helpful in understanding some
+ * of the registers in these cameras.
+ *
+ * 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
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "mr97310a"
+
+#include "gspca.h"
+
+#define CAM_TYPE_CIF			0
+#define CAM_TYPE_VGA			1
+
+#define MR97310A_BRIGHTNESS_DEFAULT	0
+
+#define MR97310A_EXPOSURE_MIN		0
+#define MR97310A_EXPOSURE_MAX		4095
+#define MR97310A_EXPOSURE_DEFAULT	1000
+
+#define MR97310A_GAIN_MIN		0
+#define MR97310A_GAIN_MAX		31
+#define MR97310A_GAIN_DEFAULT		25
+
+#define MR97310A_CONTRAST_MIN		0
+#define MR97310A_CONTRAST_MAX		31
+#define MR97310A_CONTRAST_DEFAULT	23
+
+#define MR97310A_CS_GAIN_MIN		0
+#define MR97310A_CS_GAIN_MAX		0x7ff
+#define MR97310A_CS_GAIN_DEFAULT	0x110
+
+#define MR97310A_CID_CLOCKDIV (V4L2_CTRL_CLASS_USER + 0x1000)
+#define MR97310A_MIN_CLOCKDIV_MIN	3
+#define MR97310A_MIN_CLOCKDIV_MAX	8
+#define MR97310A_MIN_CLOCKDIV_DEFAULT	3
+
+MODULE_AUTHOR("Kyle Guinn <elyk03@gmail.com>,Theodore Kilgore <kilgota@auburn.edu>");
+MODULE_DESCRIPTION("GSPCA/Mars-Semi MR97310A USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+/* global parameters */
+static int force_sensor_type = -1;
+module_param(force_sensor_type, int, 0644);
+MODULE_PARM_DESC(force_sensor_type, "Force sensor type (-1 (auto), 0 or 1)");
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;  /* !! must be the first item */
+	struct { /* exposure/min_clockdiv control cluster */
+		struct v4l2_ctrl *exposure;
+		struct v4l2_ctrl *min_clockdiv;
+	};
+	u8 sof_read;
+	u8 cam_type;	/* 0 is CIF and 1 is VGA */
+	u8 sensor_type;	/* We use 0 and 1 here, too. */
+	u8 do_lcd_stop;
+	u8 adj_colors;
+};
+
+struct sensor_w_data {
+	u8 reg;
+	u8 flags;
+	u8 data[16];
+	int len;
+};
+
+static void sd_stopN(struct gspca_dev *gspca_dev);
+
+static const struct v4l2_pix_format vga_mode[] = {
+	{160, 120, V4L2_PIX_FMT_MR97310A, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 160 * 120,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 4},
+	{176, 144, V4L2_PIX_FMT_MR97310A, V4L2_FIELD_NONE,
+		.bytesperline = 176,
+		.sizeimage = 176 * 144,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 3},
+	{320, 240, V4L2_PIX_FMT_MR97310A, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 2},
+	{352, 288, V4L2_PIX_FMT_MR97310A, V4L2_FIELD_NONE,
+		.bytesperline = 352,
+		.sizeimage = 352 * 288,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1},
+	{640, 480, V4L2_PIX_FMT_MR97310A, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+};
+
+/* the bytes to write are in gspca_dev->usb_buf */
+static int mr_write(struct gspca_dev *gspca_dev, int len)
+{
+	int rc;
+
+	rc = usb_bulk_msg(gspca_dev->dev,
+			  usb_sndbulkpipe(gspca_dev->dev, 4),
+			  gspca_dev->usb_buf, len, NULL, 500);
+	if (rc < 0)
+		pr_err("reg write [%02x] error %d\n",
+		       gspca_dev->usb_buf[0], rc);
+	return rc;
+}
+
+/* the bytes are read into gspca_dev->usb_buf */
+static int mr_read(struct gspca_dev *gspca_dev, int len)
+{
+	int rc;
+
+	rc = usb_bulk_msg(gspca_dev->dev,
+			  usb_rcvbulkpipe(gspca_dev->dev, 3),
+			  gspca_dev->usb_buf, len, NULL, 500);
+	if (rc < 0)
+		pr_err("reg read [%02x] error %d\n",
+		       gspca_dev->usb_buf[0], rc);
+	return rc;
+}
+
+static int sensor_write_reg(struct gspca_dev *gspca_dev, u8 reg, u8 flags,
+	const u8 *data, int len)
+{
+	gspca_dev->usb_buf[0] = 0x1f;
+	gspca_dev->usb_buf[1] = flags;
+	gspca_dev->usb_buf[2] = reg;
+	memcpy(gspca_dev->usb_buf + 3, data, len);
+
+	return mr_write(gspca_dev, len + 3);
+}
+
+static int sensor_write_regs(struct gspca_dev *gspca_dev,
+	const struct sensor_w_data *data, int len)
+{
+	int i, rc;
+
+	for (i = 0; i < len; i++) {
+		rc = sensor_write_reg(gspca_dev, data[i].reg, data[i].flags,
+					  data[i].data, data[i].len);
+		if (rc < 0)
+			return rc;
+	}
+
+	return 0;
+}
+
+static int sensor_write1(struct gspca_dev *gspca_dev, u8 reg, u8 data)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 buf, confirm_reg;
+	int rc;
+
+	buf = data;
+	if (sd->cam_type == CAM_TYPE_CIF) {
+		rc = sensor_write_reg(gspca_dev, reg, 0x01, &buf, 1);
+		confirm_reg = sd->sensor_type ? 0x13 : 0x11;
+	} else {
+		rc = sensor_write_reg(gspca_dev, reg, 0x00, &buf, 1);
+		confirm_reg = 0x11;
+	}
+	if (rc < 0)
+		return rc;
+
+	buf = 0x01;
+	rc = sensor_write_reg(gspca_dev, confirm_reg, 0x00, &buf, 1);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+static int cam_get_response16(struct gspca_dev *gspca_dev, u8 reg, int verbose)
+{
+	int err_code;
+
+	gspca_dev->usb_buf[0] = reg;
+	err_code = mr_write(gspca_dev, 1);
+	if (err_code < 0)
+		return err_code;
+
+	err_code = mr_read(gspca_dev, 16);
+	if (err_code < 0)
+		return err_code;
+
+	if (verbose)
+		gspca_dbg(gspca_dev, D_PROBE, "Register: %02x reads %02x%02x%02x\n",
+			  reg,
+			  gspca_dev->usb_buf[0],
+			  gspca_dev->usb_buf[1],
+			  gspca_dev->usb_buf[2]);
+
+	return 0;
+}
+
+static int zero_the_pointer(struct gspca_dev *gspca_dev)
+{
+	__u8 *data = gspca_dev->usb_buf;
+	int err_code;
+	u8 status = 0;
+	int tries = 0;
+
+	err_code = cam_get_response16(gspca_dev, 0x21, 0);
+	if (err_code < 0)
+		return err_code;
+
+	data[0] = 0x19;
+	data[1] = 0x51;
+	err_code = mr_write(gspca_dev, 2);
+	if (err_code < 0)
+		return err_code;
+
+	err_code = cam_get_response16(gspca_dev, 0x21, 0);
+	if (err_code < 0)
+		return err_code;
+
+	data[0] = 0x19;
+	data[1] = 0xba;
+	err_code = mr_write(gspca_dev, 2);
+	if (err_code < 0)
+		return err_code;
+
+	err_code = cam_get_response16(gspca_dev, 0x21, 0);
+	if (err_code < 0)
+		return err_code;
+
+	data[0] = 0x19;
+	data[1] = 0x00;
+	err_code = mr_write(gspca_dev, 2);
+	if (err_code < 0)
+		return err_code;
+
+	err_code = cam_get_response16(gspca_dev, 0x21, 0);
+	if (err_code < 0)
+		return err_code;
+
+	data[0] = 0x19;
+	data[1] = 0x00;
+	err_code = mr_write(gspca_dev, 2);
+	if (err_code < 0)
+		return err_code;
+
+	while (status != 0x0a && tries < 256) {
+		err_code = cam_get_response16(gspca_dev, 0x21, 0);
+		status = data[0];
+		tries++;
+		if (err_code < 0)
+			return err_code;
+	}
+	if (status != 0x0a)
+		gspca_err(gspca_dev, "status is %02x\n", status);
+
+	tries = 0;
+	while (tries < 4) {
+		data[0] = 0x19;
+		data[1] = 0x00;
+		err_code = mr_write(gspca_dev, 2);
+		if (err_code < 0)
+			return err_code;
+
+		err_code = cam_get_response16(gspca_dev, 0x21, 0);
+		status = data[0];
+		tries++;
+		if (err_code < 0)
+			return err_code;
+	}
+
+	data[0] = 0x19;
+	err_code = mr_write(gspca_dev, 1);
+	if (err_code < 0)
+		return err_code;
+
+	err_code = mr_read(gspca_dev, 16);
+	if (err_code < 0)
+		return err_code;
+
+	return 0;
+}
+
+static int stream_start(struct gspca_dev *gspca_dev)
+{
+	gspca_dev->usb_buf[0] = 0x01;
+	gspca_dev->usb_buf[1] = 0x01;
+	return mr_write(gspca_dev, 2);
+}
+
+static void stream_stop(struct gspca_dev *gspca_dev)
+{
+	gspca_dev->usb_buf[0] = 0x01;
+	gspca_dev->usb_buf[1] = 0x00;
+	if (mr_write(gspca_dev, 2) < 0)
+		gspca_err(gspca_dev, "Stream Stop failed\n");
+}
+
+static void lcd_stop(struct gspca_dev *gspca_dev)
+{
+	gspca_dev->usb_buf[0] = 0x19;
+	gspca_dev->usb_buf[1] = 0x54;
+	if (mr_write(gspca_dev, 2) < 0)
+		gspca_err(gspca_dev, "LCD Stop failed\n");
+}
+
+static int isoc_enable(struct gspca_dev *gspca_dev)
+{
+	gspca_dev->usb_buf[0] = 0x00;
+	gspca_dev->usb_buf[1] = 0x4d;  /* ISOC transferring enable... */
+	return mr_write(gspca_dev, 2);
+}
+
+/* This function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+		     const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam;
+	int err_code;
+
+	cam = &gspca_dev->cam;
+	cam->cam_mode = vga_mode;
+	cam->nmodes = ARRAY_SIZE(vga_mode);
+	sd->do_lcd_stop = 0;
+
+	/* Several of the supported CIF cameras share the same USB ID but
+	 * require different initializations and different control settings.
+	 * The same is true of the VGA cameras. Therefore, we are forced
+	 * to start the initialization process in order to determine which
+	 * camera is present. Some of the supported cameras require the
+	 * memory pointer to be set to 0 as the very first item of business
+	 * or else they will not stream. So we do that immediately.
+	 */
+	err_code = zero_the_pointer(gspca_dev);
+	if (err_code < 0)
+		return err_code;
+
+	err_code = stream_start(gspca_dev);
+	if (err_code < 0)
+		return err_code;
+
+	/* Now, the query for sensor type. */
+	err_code = cam_get_response16(gspca_dev, 0x07, 1);
+	if (err_code < 0)
+		return err_code;
+
+	if (id->idProduct == 0x0110 || id->idProduct == 0x010e) {
+		sd->cam_type = CAM_TYPE_CIF;
+		cam->nmodes--;
+		/*
+		 * All but one of the known CIF cameras share the same USB ID,
+		 * but two different init routines are in use, and the control
+		 * settings are different, too. We need to detect which camera
+		 * of the two known varieties is connected!
+		 *
+		 * A list of known CIF cameras follows. They all report either
+		 * 0200 for type 0 or 0300 for type 1.
+		 * If you have another to report, please do
+		 *
+		 * Name		sd->sensor_type		reported by
+		 *
+		 * Sakar 56379 Spy-shot	0		T. Kilgore
+		 * Innovage		0		T. Kilgore
+		 * Vivitar Mini		0		H. De Goede
+		 * Vivitar Mini		0		E. Rodriguez
+		 * Vivitar Mini		1		T. Kilgore
+		 * Elta-Media 8212dc	1		T. Kaiser
+		 * Philips dig. keych.	1		T. Kilgore
+		 * Trust Spyc@m 100	1		A. Jacobs
+		 */
+		switch (gspca_dev->usb_buf[0]) {
+		case 2:
+			sd->sensor_type = 0;
+			break;
+		case 3:
+			sd->sensor_type = 1;
+			break;
+		default:
+			pr_err("Unknown CIF Sensor id : %02x\n",
+			       gspca_dev->usb_buf[1]);
+			return -ENODEV;
+		}
+		gspca_dbg(gspca_dev, D_PROBE, "MR97310A CIF camera detected, sensor: %d\n",
+			  sd->sensor_type);
+	} else {
+		sd->cam_type = CAM_TYPE_VGA;
+
+		/*
+		 * Here is a table of the responses to the query for sensor
+		 * type, from the known MR97310A VGA cameras. Six different
+		 * cameras of which five share the same USB ID.
+		 *
+		 * Name			gspca_dev->usb_buf[]	sd->sensor_type
+		 *				sd->do_lcd_stop
+		 * Aiptek Pencam VGA+	0300		0		1
+		 * ION digital		0300		0		1
+		 * Argus DC-1620	0450		1		0
+		 * Argus QuickClix	0420		1		1
+		 * Sakar 77379 Digital	0350		0		1
+		 * Sakar 1638x CyberPix	0120		0		2
+		 *
+		 * Based upon these results, we assume default settings
+		 * and then correct as necessary, as follows.
+		 *
+		 */
+
+		sd->sensor_type = 1;
+		sd->do_lcd_stop = 0;
+		sd->adj_colors = 0;
+		if (gspca_dev->usb_buf[0] == 0x01) {
+			sd->sensor_type = 2;
+		} else if ((gspca_dev->usb_buf[0] != 0x03) &&
+					(gspca_dev->usb_buf[0] != 0x04)) {
+			pr_err("Unknown VGA Sensor id Byte 0: %02x\n",
+			       gspca_dev->usb_buf[0]);
+			pr_err("Defaults assumed, may not work\n");
+			pr_err("Please report this\n");
+		}
+		/* Sakar Digital color needs to be adjusted. */
+		if ((gspca_dev->usb_buf[0] == 0x03) &&
+					(gspca_dev->usb_buf[1] == 0x50))
+			sd->adj_colors = 1;
+		if (gspca_dev->usb_buf[0] == 0x04) {
+			sd->do_lcd_stop = 1;
+			switch (gspca_dev->usb_buf[1]) {
+			case 0x50:
+				sd->sensor_type = 0;
+				gspca_dbg(gspca_dev, D_PROBE, "sensor_type corrected to 0\n");
+				break;
+			case 0x20:
+				/* Nothing to do here. */
+				break;
+			default:
+				pr_err("Unknown VGA Sensor id Byte 1: %02x\n",
+				       gspca_dev->usb_buf[1]);
+				pr_err("Defaults assumed, may not work\n");
+				pr_err("Please report this\n");
+			}
+		}
+		gspca_dbg(gspca_dev, D_PROBE, "MR97310A VGA camera detected, sensor: %d\n",
+			  sd->sensor_type);
+	}
+	/* Stop streaming as we've started it only to probe the sensor type. */
+	sd_stopN(gspca_dev);
+
+	if (force_sensor_type != -1) {
+		sd->sensor_type = !!force_sensor_type;
+		gspca_dbg(gspca_dev, D_PROBE, "Forcing sensor type to: %d\n",
+			  sd->sensor_type);
+	}
+
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	return 0;
+}
+
+static int start_cif_cam(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	__u8 *data = gspca_dev->usb_buf;
+	int err_code;
+	static const __u8 startup_string[] = {
+		0x00,
+		0x0d,
+		0x01,
+		0x00, /* Hsize/8 for 352 or 320 */
+		0x00, /* Vsize/4 for 288 or 240 */
+		0x13, /* or 0xbb, depends on sensor */
+		0x00, /* Hstart, depends on res. */
+		0x00, /* reserved ? */
+		0x00, /* Vstart, depends on res. and sensor */
+		0x50, /* 0x54 to get 176 or 160 */
+		0xc0
+	};
+
+	/* Note: Some of the above descriptions guessed from MR97113A driver */
+
+	memcpy(data, startup_string, 11);
+	if (sd->sensor_type)
+		data[5] = 0xbb;
+
+	switch (gspca_dev->pixfmt.width) {
+	case 160:
+		data[9] |= 0x04;  /* reg 8, 2:1 scale down from 320 */
+		/* fall thru */
+	case 320:
+	default:
+		data[3] = 0x28;			   /* reg 2, H size/8 */
+		data[4] = 0x3c;			   /* reg 3, V size/4 */
+		data[6] = 0x14;			   /* reg 5, H start  */
+		data[8] = 0x1a + sd->sensor_type;  /* reg 7, V start  */
+		break;
+	case 176:
+		data[9] |= 0x04;  /* reg 8, 2:1 scale down from 352 */
+		/* fall thru */
+	case 352:
+		data[3] = 0x2c;			   /* reg 2, H size/8 */
+		data[4] = 0x48;			   /* reg 3, V size/4 */
+		data[6] = 0x06;			   /* reg 5, H start  */
+		data[8] = 0x06 - sd->sensor_type;  /* reg 7, V start  */
+		break;
+	}
+	err_code = mr_write(gspca_dev, 11);
+	if (err_code < 0)
+		return err_code;
+
+	if (!sd->sensor_type) {
+		static const struct sensor_w_data cif_sensor0_init_data[] = {
+			{0x02, 0x00, {0x03, 0x5a, 0xb5, 0x01,
+				      0x0f, 0x14, 0x0f, 0x10}, 8},
+			{0x0c, 0x00, {0x04, 0x01, 0x01, 0x00, 0x1f}, 5},
+			{0x12, 0x00, {0x07}, 1},
+			{0x1f, 0x00, {0x06}, 1},
+			{0x27, 0x00, {0x04}, 1},
+			{0x29, 0x00, {0x0c}, 1},
+			{0x40, 0x00, {0x40, 0x00, 0x04}, 3},
+			{0x50, 0x00, {0x60}, 1},
+			{0x60, 0x00, {0x06}, 1},
+			{0x6b, 0x00, {0x85, 0x85, 0xc8, 0xc8, 0xc8, 0xc8}, 6},
+			{0x72, 0x00, {0x1e, 0x56}, 2},
+			{0x75, 0x00, {0x58, 0x40, 0xa2, 0x02, 0x31, 0x02,
+				      0x31, 0x80, 0x00}, 9},
+			{0x11, 0x00, {0x01}, 1},
+			{0, 0, {0}, 0}
+		};
+		err_code = sensor_write_regs(gspca_dev, cif_sensor0_init_data,
+					 ARRAY_SIZE(cif_sensor0_init_data));
+	} else {	/* sd->sensor_type = 1 */
+		static const struct sensor_w_data cif_sensor1_init_data[] = {
+			/* Reg 3,4, 7,8 get set by the controls */
+			{0x02, 0x00, {0x10}, 1},
+			{0x05, 0x01, {0x22}, 1}, /* 5/6 also seen as 65h/32h */
+			{0x06, 0x01, {0x00}, 1},
+			{0x09, 0x02, {0x0e}, 1},
+			{0x0a, 0x02, {0x05}, 1},
+			{0x0b, 0x02, {0x05}, 1},
+			{0x0c, 0x02, {0x0f}, 1},
+			{0x0d, 0x02, {0x07}, 1},
+			{0x0e, 0x02, {0x0c}, 1},
+			{0x0f, 0x00, {0x00}, 1},
+			{0x10, 0x00, {0x06}, 1},
+			{0x11, 0x00, {0x07}, 1},
+			{0x12, 0x00, {0x00}, 1},
+			{0x13, 0x00, {0x01}, 1},
+			{0, 0, {0}, 0}
+		};
+		/* Without this command the cam won't work with USB-UHCI */
+		gspca_dev->usb_buf[0] = 0x0a;
+		gspca_dev->usb_buf[1] = 0x00;
+		err_code = mr_write(gspca_dev, 2);
+		if (err_code < 0)
+			return err_code;
+		err_code = sensor_write_regs(gspca_dev, cif_sensor1_init_data,
+					 ARRAY_SIZE(cif_sensor1_init_data));
+	}
+	return err_code;
+}
+
+static int start_vga_cam(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	__u8 *data = gspca_dev->usb_buf;
+	int err_code;
+	static const __u8 startup_string[] =
+		{0x00, 0x0d, 0x01, 0x00, 0x00, 0x2b, 0x00, 0x00,
+		 0x00, 0x50, 0xc0};
+	/* What some of these mean is explained in start_cif_cam(), above */
+
+	memcpy(data, startup_string, 11);
+	if (!sd->sensor_type) {
+		data[5]  = 0x00;
+		data[10] = 0x91;
+	}
+	if (sd->sensor_type == 2) {
+		data[5]  = 0x00;
+		data[10] = 0x18;
+	}
+
+	switch (gspca_dev->pixfmt.width) {
+	case 160:
+		data[9] |= 0x0c;  /* reg 8, 4:1 scale down */
+		/* fall thru */
+	case 320:
+		data[9] |= 0x04;  /* reg 8, 2:1 scale down */
+		/* fall thru */
+	case 640:
+	default:
+		data[3] = 0x50;  /* reg 2, H size/8 */
+		data[4] = 0x78;  /* reg 3, V size/4 */
+		data[6] = 0x04;  /* reg 5, H start */
+		data[8] = 0x03;  /* reg 7, V start */
+		if (sd->sensor_type == 2) {
+			data[6] = 2;
+			data[8] = 1;
+		}
+		if (sd->do_lcd_stop)
+			data[8] = 0x04;  /* Bayer tile shifted */
+		break;
+
+	case 176:
+		data[9] |= 0x04;  /* reg 8, 2:1 scale down */
+		/* fall thru */
+	case 352:
+		data[3] = 0x2c;  /* reg 2, H size */
+		data[4] = 0x48;  /* reg 3, V size */
+		data[6] = 0x94;  /* reg 5, H start */
+		data[8] = 0x63;  /* reg 7, V start */
+		if (sd->do_lcd_stop)
+			data[8] = 0x64;  /* Bayer tile shifted */
+		break;
+	}
+
+	err_code = mr_write(gspca_dev, 11);
+	if (err_code < 0)
+		return err_code;
+
+	if (!sd->sensor_type) {
+		static const struct sensor_w_data vga_sensor0_init_data[] = {
+			{0x01, 0x00, {0x0c, 0x00, 0x04}, 3},
+			{0x14, 0x00, {0x01, 0xe4, 0x02, 0x84}, 4},
+			{0x20, 0x00, {0x00, 0x80, 0x00, 0x08}, 4},
+			{0x25, 0x00, {0x03, 0xa9, 0x80}, 3},
+			{0x30, 0x00, {0x30, 0x18, 0x10, 0x18}, 4},
+			{0, 0, {0}, 0}
+		};
+		err_code = sensor_write_regs(gspca_dev, vga_sensor0_init_data,
+					 ARRAY_SIZE(vga_sensor0_init_data));
+	} else if (sd->sensor_type == 1) {
+		static const struct sensor_w_data color_adj[] = {
+			{0x02, 0x00, {0x06, 0x59, 0x0c, 0x16, 0x00,
+				/* adjusted blue, green, red gain correct
+				   too much blue from the Sakar Digital */
+				0x05, 0x01, 0x04}, 8}
+		};
+
+		static const struct sensor_w_data color_no_adj[] = {
+			{0x02, 0x00, {0x06, 0x59, 0x0c, 0x16, 0x00,
+				/* default blue, green, red gain settings */
+				0x07, 0x00, 0x01}, 8}
+		};
+
+		static const struct sensor_w_data vga_sensor1_init_data[] = {
+			{0x11, 0x04, {0x01}, 1},
+			{0x0a, 0x00, {0x00, 0x01, 0x00, 0x00, 0x01,
+			/* These settings may be better for some cameras */
+			/* {0x0a, 0x00, {0x01, 0x06, 0x00, 0x00, 0x01, */
+				0x00, 0x0a}, 7},
+			{0x11, 0x04, {0x01}, 1},
+			{0x12, 0x00, {0x00, 0x63, 0x00, 0x70, 0x00, 0x00}, 6},
+			{0x11, 0x04, {0x01}, 1},
+			{0, 0, {0}, 0}
+		};
+
+		if (sd->adj_colors)
+			err_code = sensor_write_regs(gspca_dev, color_adj,
+					 ARRAY_SIZE(color_adj));
+		else
+			err_code = sensor_write_regs(gspca_dev, color_no_adj,
+					 ARRAY_SIZE(color_no_adj));
+
+		if (err_code < 0)
+			return err_code;
+
+		err_code = sensor_write_regs(gspca_dev, vga_sensor1_init_data,
+					 ARRAY_SIZE(vga_sensor1_init_data));
+	} else {	/* sensor type == 2 */
+		static const struct sensor_w_data vga_sensor2_init_data[] = {
+
+			{0x01, 0x00, {0x48}, 1},
+			{0x02, 0x00, {0x22}, 1},
+			/* Reg 3 msb and 4 is lsb of the exposure setting*/
+			{0x05, 0x00, {0x10}, 1},
+			{0x06, 0x00, {0x00}, 1},
+			{0x07, 0x00, {0x00}, 1},
+			{0x08, 0x00, {0x00}, 1},
+			{0x09, 0x00, {0x00}, 1},
+			/* The following are used in the gain control
+			 * which is BTW completely borked in the OEM driver
+			 * The values for each color go from 0 to 0x7ff
+			 *{0x0a, 0x00, {0x01}, 1},  green1 gain msb
+			 *{0x0b, 0x00, {0x10}, 1},  green1 gain lsb
+			 *{0x0c, 0x00, {0x01}, 1},  red gain msb
+			 *{0x0d, 0x00, {0x10}, 1},  red gain lsb
+			 *{0x0e, 0x00, {0x01}, 1},  blue gain msb
+			 *{0x0f, 0x00, {0x10}, 1},  blue gain lsb
+			 *{0x10, 0x00, {0x01}, 1}, green2 gain msb
+			 *{0x11, 0x00, {0x10}, 1}, green2 gain lsb
+			 */
+			{0x12, 0x00, {0x00}, 1},
+			{0x13, 0x00, {0x04}, 1}, /* weird effect on colors */
+			{0x14, 0x00, {0x00}, 1},
+			{0x15, 0x00, {0x06}, 1},
+			{0x16, 0x00, {0x01}, 1},
+			{0x17, 0x00, {0xe2}, 1}, /* vertical alignment */
+			{0x18, 0x00, {0x02}, 1},
+			{0x19, 0x00, {0x82}, 1}, /* don't mess with */
+			{0x1a, 0x00, {0x00}, 1},
+			{0x1b, 0x00, {0x20}, 1},
+			/* {0x1c, 0x00, {0x17}, 1}, contrast control */
+			{0x1d, 0x00, {0x80}, 1}, /* moving causes a mess */
+			{0x1e, 0x00, {0x08}, 1}, /* moving jams the camera */
+			{0x1f, 0x00, {0x0c}, 1},
+			{0x20, 0x00, {0x00}, 1},
+			{0, 0, {0}, 0}
+		};
+		err_code = sensor_write_regs(gspca_dev, vga_sensor2_init_data,
+					 ARRAY_SIZE(vga_sensor2_init_data));
+	}
+	return err_code;
+}
+
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int err_code;
+
+	sd->sof_read = 0;
+
+	/* Some of the VGA cameras require the memory pointer
+	 * to be set to 0 again. We have been forced to start the
+	 * stream in sd_config() to detect the hardware, and closed it.
+	 * Thus, we need here to do a completely fresh and clean start. */
+	err_code = zero_the_pointer(gspca_dev);
+	if (err_code < 0)
+		return err_code;
+
+	err_code = stream_start(gspca_dev);
+	if (err_code < 0)
+		return err_code;
+
+	if (sd->cam_type == CAM_TYPE_CIF) {
+		err_code = start_cif_cam(gspca_dev);
+	} else {
+		err_code = start_vga_cam(gspca_dev);
+	}
+	if (err_code < 0)
+		return err_code;
+
+	return isoc_enable(gspca_dev);
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	stream_stop(gspca_dev);
+	/* Not all the cams need this, but even if not, probably a good idea */
+	zero_the_pointer(gspca_dev);
+	if (sd->do_lcd_stop)
+		lcd_stop(gspca_dev);
+}
+
+static void setbrightness(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 sign_reg = 7;  /* This reg and the next one used on CIF cams. */
+	u8 value_reg = 8; /* VGA cams seem to use regs 0x0b and 0x0c */
+	static const u8 quick_clix_table[] =
+	/*	  0  1  2   3  4  5  6  7  8  9  10  11  12  13  14  15 */
+		{ 0, 4, 8, 12, 1, 2, 3, 5, 6, 9,  7, 10, 13, 11, 14, 15};
+	if (sd->cam_type == CAM_TYPE_VGA) {
+		sign_reg += 4;
+		value_reg += 4;
+	}
+
+	/* Note register 7 is also seen as 0x8x or 0xCx in some dumps */
+	if (val > 0) {
+		sensor_write1(gspca_dev, sign_reg, 0x00);
+	} else {
+		sensor_write1(gspca_dev, sign_reg, 0x01);
+		val = 257 - val;
+	}
+	/* Use lookup table for funky Argus QuickClix brightness */
+	if (sd->do_lcd_stop)
+		val = quick_clix_table[val];
+
+	sensor_write1(gspca_dev, value_reg, val);
+}
+
+static void setexposure(struct gspca_dev *gspca_dev, s32 expo, s32 min_clockdiv)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int exposure = MR97310A_EXPOSURE_DEFAULT;
+	u8 buf[2];
+
+	if (sd->cam_type == CAM_TYPE_CIF && sd->sensor_type == 1) {
+		/* This cam does not like exposure settings < 300,
+		   so scale 0 - 4095 to 300 - 4095 */
+		exposure = (expo * 9267) / 10000 + 300;
+		sensor_write1(gspca_dev, 3, exposure >> 4);
+		sensor_write1(gspca_dev, 4, exposure & 0x0f);
+	} else if (sd->sensor_type == 2) {
+		exposure = expo;
+		exposure >>= 3;
+		sensor_write1(gspca_dev, 3, exposure >> 8);
+		sensor_write1(gspca_dev, 4, exposure & 0xff);
+	} else {
+		/* We have both a clock divider and an exposure register.
+		   We first calculate the clock divider, as that determines
+		   the maximum exposure and then we calculate the exposure
+		   register setting (which goes from 0 - 511).
+
+		   Note our 0 - 4095 exposure is mapped to 0 - 511
+		   milliseconds exposure time */
+		u8 clockdiv = (60 * expo + 7999) / 8000;
+
+		/* Limit framerate to not exceed usb bandwidth */
+		if (clockdiv < min_clockdiv && gspca_dev->pixfmt.width >= 320)
+			clockdiv = min_clockdiv;
+		else if (clockdiv < 2)
+			clockdiv = 2;
+
+		if (sd->cam_type == CAM_TYPE_VGA && clockdiv < 4)
+			clockdiv = 4;
+
+		/* Frame exposure time in ms = 1000 * clockdiv / 60 ->
+		exposure = (sd->exposure / 8) * 511 / (1000 * clockdiv / 60) */
+		exposure = (60 * 511 * expo) / (8000 * clockdiv);
+		if (exposure > 511)
+			exposure = 511;
+
+		/* exposure register value is reversed! */
+		exposure = 511 - exposure;
+
+		buf[0] = exposure & 0xff;
+		buf[1] = exposure >> 8;
+		sensor_write_reg(gspca_dev, 0x0e, 0, buf, 2);
+		sensor_write1(gspca_dev, 0x02, clockdiv);
+	}
+}
+
+static void setgain(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 gainreg;
+
+	if (sd->cam_type == CAM_TYPE_CIF && sd->sensor_type == 1)
+		sensor_write1(gspca_dev, 0x0e, val);
+	else if (sd->cam_type == CAM_TYPE_VGA && sd->sensor_type == 2)
+		for (gainreg = 0x0a; gainreg < 0x11; gainreg += 2) {
+			sensor_write1(gspca_dev, gainreg, val >> 8);
+			sensor_write1(gspca_dev, gainreg + 1, val & 0xff);
+		}
+	else
+		sensor_write1(gspca_dev, 0x10, val);
+}
+
+static void setcontrast(struct gspca_dev *gspca_dev, s32 val)
+{
+	sensor_write1(gspca_dev, 0x1c, val);
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *)gspca_dev;
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		setbrightness(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_CONTRAST:
+		setcontrast(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_EXPOSURE:
+		setexposure(gspca_dev, sd->exposure->val,
+			    sd->min_clockdiv ? sd->min_clockdiv->val : 0);
+		break;
+	case V4L2_CID_GAIN:
+		setgain(gspca_dev, ctrl->val);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *)gspca_dev;
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+	static const struct v4l2_ctrl_config clockdiv = {
+		.ops = &sd_ctrl_ops,
+		.id = MR97310A_CID_CLOCKDIV,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Minimum Clock Divider",
+		.min = MR97310A_MIN_CLOCKDIV_MIN,
+		.max = MR97310A_MIN_CLOCKDIV_MAX,
+		.step = 1,
+		.def = MR97310A_MIN_CLOCKDIV_DEFAULT,
+	};
+	bool has_brightness = false;
+	bool has_argus_brightness = false;
+	bool has_contrast = false;
+	bool has_gain = false;
+	bool has_cs_gain = false;
+	bool has_exposure = false;
+	bool has_clockdiv = false;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 4);
+
+	/* Setup controls depending on camera type */
+	if (sd->cam_type == CAM_TYPE_CIF) {
+		/* No brightness for sensor_type 0 */
+		if (sd->sensor_type == 0)
+			has_exposure = has_gain = has_clockdiv = true;
+		else
+			has_exposure = has_gain = has_brightness = true;
+	} else {
+		/* All controls need to be disabled if VGA sensor_type is 0 */
+		if (sd->sensor_type == 0)
+			; /* no controls! */
+		else if (sd->sensor_type == 2)
+			has_exposure = has_cs_gain = has_contrast = true;
+		else if (sd->do_lcd_stop)
+			has_exposure = has_gain = has_argus_brightness =
+				has_clockdiv = true;
+		else
+			has_exposure = has_gain = has_brightness =
+				has_clockdiv = true;
+	}
+
+	/* Separate brightness control description for Argus QuickClix as it has
+	 * different limits from the other mr97310a cameras, and separate gain
+	 * control for Sakar CyberPix camera. */
+	/*
+	 * This control is disabled for CIF type 1 and VGA type 0 cameras.
+	 * It does not quite act linearly for the Argus QuickClix camera,
+	 * but it does control brightness. The values are 0 - 15 only, and
+	 * the table above makes them act consecutively.
+	 */
+	if (has_brightness)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, -254, 255, 1,
+			MR97310A_BRIGHTNESS_DEFAULT);
+	else if (has_argus_brightness)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0, 15, 1,
+			MR97310A_BRIGHTNESS_DEFAULT);
+	if (has_contrast)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_CONTRAST, MR97310A_CONTRAST_MIN,
+			MR97310A_CONTRAST_MAX, 1, MR97310A_CONTRAST_DEFAULT);
+	if (has_gain)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_GAIN, MR97310A_GAIN_MIN, MR97310A_GAIN_MAX,
+			1, MR97310A_GAIN_DEFAULT);
+	else if (has_cs_gain)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops, V4L2_CID_GAIN,
+			MR97310A_CS_GAIN_MIN, MR97310A_CS_GAIN_MAX,
+			1, MR97310A_CS_GAIN_DEFAULT);
+	if (has_exposure)
+		sd->exposure = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_EXPOSURE, MR97310A_EXPOSURE_MIN,
+			MR97310A_EXPOSURE_MAX, 1, MR97310A_EXPOSURE_DEFAULT);
+	if (has_clockdiv)
+		sd->min_clockdiv = v4l2_ctrl_new_custom(hdl, &clockdiv, NULL);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	if (has_exposure && has_clockdiv)
+		v4l2_ctrl_cluster(2, &sd->exposure);
+	return 0;
+}
+
+/* Include pac common sof detection functions */
+#include "pac_common.h"
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,		/* isoc packet */
+			int len)		/* iso packet length */
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	unsigned char *sof;
+
+	sof = pac_find_sof(gspca_dev, &sd->sof_read, data, len);
+	if (sof) {
+		int n;
+
+		/* finish decoding current frame */
+		n = sof - data;
+		if (n > sizeof pac_sof_marker)
+			n -= sizeof pac_sof_marker;
+		else
+			n = 0;
+		gspca_frame_add(gspca_dev, LAST_PACKET,
+					data, n);
+		/* Start next frame. */
+		gspca_frame_add(gspca_dev, FIRST_PACKET,
+			pac_sof_marker, sizeof pac_sof_marker);
+		len -= sof - data;
+		data = sof;
+	}
+	gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.pkt_scan = sd_pkt_scan,
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x08ca, 0x0110)},	/* Trust Spyc@m 100 */
+	{USB_DEVICE(0x08ca, 0x0111)},	/* Aiptek Pencam VGA+ */
+	{USB_DEVICE(0x093a, 0x010f)},	/* All other known MR97310A VGA cams */
+	{USB_DEVICE(0x093a, 0x010e)},	/* All known MR97310A CIF cams */
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+		    const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+			       THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/nw80x.c b/drivers/media/usb/gspca/nw80x.c
new file mode 100644
index 0000000..bedc04a
--- /dev/null
+++ b/drivers/media/usb/gspca/nw80x.c
@@ -0,0 +1,2109 @@
+/*
+ * DivIO nw80x subdriver
+ *
+ * Copyright (C) 2011 Jean-François Moine (http://moinejf.free.fr)
+ * Copyright (C) 2003 Sylvain Munaut <tnt@246tNt.com>
+ *			Kjell Claesson <keyson@users.sourceforge.net>
+ *
+ * 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
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "nw80x"
+
+#include "gspca.h"
+
+MODULE_AUTHOR("Jean-Francois Moine <http://moinejf.free.fr>");
+MODULE_DESCRIPTION("NW80x USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+static int webcam;
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+
+	u32 ae_res;
+	s8 ag_cnt;
+#define AG_CNT_START 13
+	u8 exp_too_low_cnt;
+	u8 exp_too_high_cnt;
+
+	u8 bridge;
+	u8 webcam;
+};
+
+enum bridges {
+	BRIDGE_NW800,	/* and et31x110 */
+	BRIDGE_NW801,
+	BRIDGE_NW802,
+};
+enum webcams {
+	Generic800,
+	SpaceCam,	/* Trust 120 SpaceCam */
+	SpaceCam2,	/* other Trust 120 SpaceCam */
+	Cvideopro,	/* Conceptronic Video Pro */
+	Dlink350c,
+	DS3303u,
+	Kr651us,
+	Kritter,
+	Mustek300,
+	Proscope,
+	Twinkle,
+	DvcV6,
+	P35u,
+	Generic802,
+	NWEBCAMS	/* number of webcams */
+};
+
+static const u8 webcam_chip[NWEBCAMS] = {
+	[Generic800]	= BRIDGE_NW800,	/* 06a5:0000
+					 * Typhoon Webcam 100 USB */
+
+	[SpaceCam]	= BRIDGE_NW800,	/* 06a5:d800
+				* Trust SpaceCam120 or SpaceCam100 PORTABLE */
+
+	[SpaceCam2]	= BRIDGE_NW800,	/* 06a5:d800 - pas106
+			* other Trust SpaceCam120 or SpaceCam100 PORTABLE */
+
+	[Cvideopro]	= BRIDGE_NW802,	/* 06a5:d001
+			* Conceptronic Video Pro 'CVIDEOPRO USB Webcam CCD' */
+
+	[Dlink350c]	= BRIDGE_NW802,	/* 06a5:d001
+					 * D-Link NetQam Pro 250plus */
+
+	[DS3303u]	= BRIDGE_NW801,	/* 06a5:d001
+				* Plustek Opticam 500U or ProLink DS3303u */
+
+	[Kr651us]	= BRIDGE_NW802,	/* 06a5:d001
+					 * Panasonic GP-KR651US */
+
+	[Kritter]	= BRIDGE_NW802,	/* 06a5:d001
+					 * iRez Kritter cam */
+
+	[Mustek300]	= BRIDGE_NW802,	/* 055f:d001
+					 * Mustek Wcam 300 mini */
+
+	[Proscope]	= BRIDGE_NW802,	/* 06a5:d001
+					 * Scalar USB Microscope (ProScope) */
+
+	[Twinkle]	= BRIDGE_NW800,	/* 06a5:d800 - hv7121b? (seems pas106)
+					 * Divio Chicony TwinkleCam
+					 * DSB-C110 */
+
+	[DvcV6]		= BRIDGE_NW802,	/* 0502:d001
+					 * DVC V6 */
+
+	[P35u]		= BRIDGE_NW801,	/* 052b:d001, 06a5:d001 and 06be:d001
+					 * EZCam Pro p35u */
+
+	[Generic802]	= BRIDGE_NW802,
+};
+/*
+ * other webcams:
+ *	- nw801 046d:d001
+ *		Logitech QuickCam Pro (dark focus ring)
+ *	- nw801 0728:d001
+ *		AVerMedia Camguard
+ *	- nw??? 06a5:d001
+ *		D-Link NetQam Pro 250plus
+ *	- nw800 065a:d800
+ *		Showcam NGS webcam
+ *	- nw??? ????:????
+ *		Sceptre svc300
+ */
+
+/*
+ * registers
+ *    nw800/et31x110	  nw801		  nw802
+ *	0000..009e	0000..00a1	0000..009e
+ *	0200..0211	   id		   id
+ *	0300..0302	   id		   id
+ *	0400..0406	  (inex)	0400..0406
+ *	0500..0505	0500..0506	  (inex)
+ *	0600..061a	0600..0601	0600..0601
+ *	0800..0814	   id		   id
+ *	1000..109c	1000..10a1	1000..109a
+ */
+
+/* resolutions
+ *	nw800: 320x240, 352x288
+ *	nw801/802: 320x240, 640x480
+ */
+static const struct v4l2_pix_format cif_mode[] = {
+	{320, 240, V4L2_PIX_FMT_JPGL, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 4 / 8,
+		.colorspace = V4L2_COLORSPACE_JPEG},
+	{352, 288, V4L2_PIX_FMT_JPGL, V4L2_FIELD_NONE,
+		.bytesperline = 352,
+		.sizeimage = 352 * 288 * 4 / 8,
+		.colorspace = V4L2_COLORSPACE_JPEG}
+};
+static const struct v4l2_pix_format vga_mode[] = {
+	{320, 240, V4L2_PIX_FMT_JPGL, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 4 / 8,
+		.colorspace = V4L2_COLORSPACE_JPEG},
+	{640, 480, V4L2_PIX_FMT_JPGL, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 3 / 8,
+		.colorspace = V4L2_COLORSPACE_JPEG},
+};
+
+/*
+ * The sequences below contain:
+ *	- 1st and 2nd bytes: either
+ *		- register number (BE)
+ *		- I2C0 + i2c address
+ *	- 3rd byte: data length (=0 for end of sequence)
+ *	- n bytes: data
+ */
+#define I2C0 0xff
+
+static const u8 nw800_init[] = {
+	0x04, 0x05, 0x01, 0x61,
+	0x04, 0x04, 0x01, 0x01,
+	0x04, 0x06, 0x01, 0x04,
+	0x04, 0x04, 0x03, 0x00, 0x00, 0x00,
+	0x05, 0x05, 0x01, 0x00,
+	0, 0, 0
+};
+static const u8 nw800_start[] = {
+	0x04, 0x06, 0x01, 0xc0,
+	0x00, 0x00, 0x40, 0x10, 0x43, 0x00, 0xb4, 0x01, 0x10, 0x00, 0x4f,
+			  0xef, 0x0e, 0x00, 0x74, 0x01, 0x01, 0x00, 0x19,
+			  0x00, 0x01, 0x00, 0x19, 0x00, 0x01, 0x00, 0x19,
+			  0x00, 0x01, 0x00, 0x19, 0x00, 0x3e, 0x00, 0x24,
+			  0x03, 0x3e, 0x00, 0x86, 0x00, 0x3e, 0x00, 0x86,
+			  0x00, 0x3e, 0x00, 0x86, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x56, 0x00, 0x9e, 0x00, 0x56, 0x00, 0x9e,
+			  0x00, 0x56, 0x00, 0x9e, 0x00, 0x01, 0x00, 0x01,
+	0x00, 0x40, 0x40, 0x00, 0x6e, 0x00, 0xb6, 0x00, 0x6e, 0x00, 0x78,
+			  0x04, 0x6e, 0x00, 0xb6, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x6e, 0x00, 0xb6, 0x00, 0x6e, 0x00, 0x78,
+			  0x04, 0x6e, 0x00, 0xb6, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0xca, 0x03, 0x46, 0x04, 0xca, 0x03, 0x46,
+			  0x04, 0x10, 0x00, 0x36, 0x00, 0xd2, 0x00, 0xee,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0xf0,
+			  0x00, 0x3e, 0x00, 0xaa, 0x00, 0x88, 0x00, 0x2e,
+	0x00, 0x80, 0x1f, 0xa0, 0x48, 0xc3, 0x02, 0x88, 0x0c, 0x68, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0xa8, 0x06, 0x00, 0x08,
+			  0x00, 0x32, 0x01, 0x01, 0x00, 0x16, 0x00, 0x04,
+			  0x00, 0x4b, 0x00, 0x76, 0x00, 0x86, 0x00,
+	0x02, 0x00, 0x12, 0x78, 0xa0, 0x9e, 0x78, 0xa0, 0x00, 0x00, 0x00,
+			  0x00, 0xf0, 0x18, 0x0b, 0x06, 0x62, 0x82, 0xa0,
+			  0x40, 0x20,
+	0x03, 0x00, 0x03, 0x03, 0x00, 0x00,
+	0x04, 0x00, 0x07, 0x01, 0x10, 0x00, 0x00, 0x00, 0x61, 0xc0,
+	0x05, 0x00, 0x06, 0xe8, 0x00, 0x00, 0x00, 0x20, 0x20,
+	0x06, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00,
+	0x08, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00,
+	0x10, 0x00, 0x40, 0x83, 0x02, 0x20, 0x00, 0x13, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x08, 0x0a,
+			  0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x49, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+			  0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x10, 0x08,
+			  0x03, 0x00, 0x00, 0x00, 0x00, 0x20, 0x10, 0x06,
+			  0xf7, 0xee, 0x1c, 0x1c, 0xe9, 0xfc, 0x10, 0x80,
+	0x10, 0x40, 0x40, 0x80, 0x00, 0x05, 0x35, 0x5e, 0x78, 0x8b, 0x99,
+			  0xa4, 0xae, 0xb5, 0xbc, 0xc1, 0xc6, 0xc9, 0xcc,
+			  0xcf, 0xd0, 0x00, 0x11, 0x22, 0x32, 0x43, 0x54,
+			  0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3, 0xd2,
+			  0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32, 0x43,
+			  0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3,
+			  0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32,
+			  0x43, 0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3,
+	0x10, 0x80, 0x1d, 0xc3, 0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x62,
+			  0x01, 0x24, 0x01, 0x62, 0x01, 0x24, 0x01, 0x20,
+			  0x01, 0x60, 0x01, 0x00, 0x00,
+
+	0x04, 0x04, 0x01, 0xff,
+	0x04, 0x06, 0x01, 0xc4,
+
+	0x04, 0x06, 0x01, 0xc0,
+	0x00, 0x00, 0x40, 0x10, 0x43, 0x00, 0xb4, 0x01, 0x10, 0x00, 0x4f,
+			  0xef, 0x0e, 0x00, 0x74, 0x01, 0x01, 0x00, 0x19,
+			  0x00, 0x01, 0x00, 0x19, 0x00, 0x01, 0x00, 0x19,
+			  0x00, 0x01, 0x00, 0x19, 0x00, 0x3e, 0x00, 0x24,
+			  0x03, 0x3e, 0x00, 0x86, 0x00, 0x3e, 0x00, 0x86,
+			  0x00, 0x3e, 0x00, 0x86, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x56, 0x00, 0x9e, 0x00, 0x56, 0x00, 0x9e,
+			  0x00, 0x56, 0x00, 0x9e, 0x00, 0x01, 0x00, 0x01,
+	0x00, 0x40, 0x40, 0x00, 0x6e, 0x00, 0xb6, 0x00, 0x6e, 0x00, 0x78,
+			  0x04, 0x6e, 0x00, 0xb6, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x6e, 0x00, 0xb6, 0x00, 0x6e, 0x00, 0x78,
+			  0x04, 0x6e, 0x00, 0xb6, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0xca, 0x03, 0x46, 0x04, 0xca, 0x03, 0x46,
+			  0x04, 0x10, 0x00, 0x36, 0x00, 0xd2, 0x00, 0xee,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0xf0,
+			  0x00, 0x3e, 0x00, 0xaa, 0x00, 0x88, 0x00, 0x2e,
+	0x00, 0x80, 0x1f, 0xa0, 0x48, 0xc3, 0x02, 0x88, 0x0c, 0x68, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0xa8, 0x06, 0x00, 0x08,
+			  0x00, 0x32, 0x01, 0x01, 0x00, 0x16, 0x00, 0x04,
+			  0x00, 0x4b, 0x00, 0x76, 0x00, 0x86, 0x00,
+	0x02, 0x00, 0x12, 0x78, 0xa0, 0x9e, 0x78, 0xa0, 0x00, 0x00, 0x00,
+			  0x00, 0xf0, 0x18, 0x0b, 0x06, 0x62, 0x82, 0xa0,
+			  0x40, 0x20,
+	0x03, 0x00, 0x03, 0x03, 0x00, 0x00,
+	0x04, 0x00, 0x07, 0x01, 0x10, 0x00, 0x00, 0x00, 0x61, 0xc0,
+	0x05, 0x00, 0x06, 0xe8, 0x00, 0x00, 0x00, 0x20, 0x20,
+	0x06, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00,
+	0x08, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00,
+	0x10, 0x00, 0x40, 0x83, 0x02, 0x20, 0x00, 0x13, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x08, 0x0a,
+			  0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x49, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+			  0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x10, 0x08,
+			  0x03, 0x00, 0x00, 0x00, 0x00, 0x20, 0x10, 0x06,
+			  0xf7, 0xee, 0x1c, 0x1c, 0xe9, 0xfc, 0x10, 0x80,
+	0x10, 0x40, 0x40, 0x80, 0x00, 0x05, 0x35, 0x5e, 0x78, 0x8b, 0x99,
+			  0xa4, 0xae, 0xb5, 0xbc, 0xc1, 0xc6, 0xc9, 0xcc,
+			  0xcf, 0xd0, 0x00, 0x11, 0x22, 0x32, 0x43, 0x54,
+			  0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3, 0xd2,
+			  0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32, 0x43,
+			  0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3,
+			  0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32,
+			  0x43, 0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3,
+	0x10, 0x80, 0x1d, 0xc3, 0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x62,
+			  0x01, 0x24, 0x01, 0x62, 0x01, 0x24, 0x01, 0x20,
+			  0x01, 0x60, 0x01, 0x00, 0x00,
+
+	0x02, 0x00, 0x11, 0x48, 0x58, 0x9e, 0x48, 0x58, 0x00, 0x00, 0x00,
+			  0x00, 0x84, 0x36, 0x05, 0x01, 0xf2, 0x86, 0x65,
+			  0x40,
+	0x00, 0x80, 0x01, 0xa0,
+	0x10, 0x1a, 0x01, 0x00,
+	0x00, 0x91, 0x02, 0x6c, 0x01,
+	0x00, 0x03, 0x02, 0xc8, 0x01,
+	0x10, 0x1a, 0x01, 0x00,
+	0x10, 0x00, 0x01, 0x83,
+	0x10, 0x8f, 0x0c, 0x62, 0x01, 0x24, 0x01, 0x62, 0x01, 0x24, 0x01,
+			  0x20, 0x01, 0x60, 0x01,
+	0x10, 0x85, 0x08, 0x00, 0x00, 0x5f, 0x01, 0x00, 0x00, 0x1f, 0x01,
+	0x10, 0x1b, 0x02, 0x69, 0x00,
+	0x10, 0x11, 0x08, 0x00, 0x00, 0x5f, 0x01, 0x00, 0x00, 0x1f, 0x01,
+	0x05, 0x02, 0x01, 0x02,
+	0x06, 0x00, 0x02, 0x04, 0xd9,
+	0x05, 0x05, 0x01, 0x20,
+	0x05, 0x05, 0x01, 0x21,
+	0x10, 0x0e, 0x01, 0x08,
+	0x10, 0x41, 0x11, 0x00, 0x08, 0x21, 0x3d, 0x52, 0x63, 0x75, 0x83,
+			  0x91, 0x9e, 0xaa, 0xb6, 0xc1, 0xcc, 0xd6, 0xe0,
+			  0xea,
+	0x10, 0x03, 0x01, 0x00,
+	0x10, 0x0f, 0x02, 0x13, 0x13,
+	0x10, 0x03, 0x01, 0x14,
+	0x10, 0x41, 0x11, 0x00, 0x08, 0x21, 0x3d, 0x52, 0x63, 0x75, 0x83,
+			  0x91, 0x9e, 0xaa, 0xb6, 0xc1, 0xcc, 0xd6, 0xe0,
+			  0xea,
+	0x10, 0x0b, 0x01, 0x14,
+	0x10, 0x0d, 0x01, 0x20,
+	0x10, 0x0c, 0x01, 0x34,
+	0x04, 0x06, 0x01, 0xc3,
+	0x04, 0x04, 0x01, 0x00,
+	0x05, 0x02, 0x01, 0x02,
+	0x06, 0x00, 0x02, 0x00, 0x48,
+	0x05, 0x05, 0x01, 0x20,
+	0x05, 0x05, 0x01, 0x21,
+	0, 0, 0
+};
+
+/* 06a5:d001 - nw801 - Panasonic
+ *		P35u */
+static const u8 nw801_start_1[] = {
+	0x05, 0x06, 0x01, 0x04,
+	0x00, 0x00, 0x40, 0x0e, 0x00, 0x00, 0xf9, 0x02, 0x11, 0x00, 0x0e,
+			  0x01, 0x1f, 0x00, 0x0d, 0x02, 0x01, 0x00, 0x19,
+			  0x00, 0x01, 0x00, 0x19, 0x00, 0x01, 0x00, 0x19,
+			  0x00, 0x01, 0x00, 0x19, 0x00, 0xce, 0x00, 0xf4,
+			  0x05, 0x3e, 0x00, 0x86, 0x00, 0x3e, 0x00, 0x86,
+			  0x00, 0x3e, 0x00, 0x86, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x56, 0x00, 0x9e, 0x00, 0x56, 0x00, 0x9e,
+			  0x00, 0x56, 0x00, 0x9e, 0x00, 0x01, 0x00, 0x01,
+	0x00, 0x40, 0x40, 0x00, 0x6e, 0x00, 0xb6, 0x00, 0x6e, 0x00, 0x78,
+			  0x04, 0x6e, 0x00, 0xb6, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x6e, 0x00, 0xb6, 0x00, 0x6e, 0x00, 0x78,
+			  0x04, 0x6e, 0x00, 0xb6, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0xca, 0x03, 0x46, 0x04, 0xca, 0x03, 0x46,
+			  0x04, 0x10, 0x00, 0x36, 0x00, 0xd2, 0x00, 0xee,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0xf0,
+			  0x00, 0x3e, 0x00, 0xaa, 0x00, 0x88, 0x00, 0x2e,
+	0x00, 0x80, 0x22, 0xb4, 0x6f, 0x3f, 0x0f, 0x88, 0x20, 0x08, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x69, 0xa8, 0x1f, 0x00,
+			  0x0d, 0x02, 0x07, 0x00, 0x01, 0x00, 0x19, 0x00,
+			  0xf2, 0x00, 0x18, 0x06, 0x10, 0x06, 0x10, 0x00,
+			  0x36, 0x00,
+	0x02, 0x00, 0x12, 0x78, 0xa0, 0x9e, 0x78, 0xa0, 0x00, 0x00, 0x00,
+			  0x00, 0xf0, 0x18, 0x0b, 0x06, 0x62, 0x82, 0xa0,
+			  0x40, 0x20,
+	0x03, 0x00, 0x03, 0x00, 0x00, 0x00,
+	0x05, 0x00, 0x07, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x06, 0x00, 0x02, 0x09, 0x99,
+	0x08, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00,
+	0x10, 0x00, 0x40, 0x22, 0x02, 0x80, 0x00, 0x1e, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x0a, 0x15, 0x08, 0x08, 0x0a,
+			  0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x01, 0x35, 0xfd, 0x07, 0x3d, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x14, 0x02,
+			  0x00, 0x01, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
+			  0x40, 0x00, 0x00, 0x00, 0x40, 0x20, 0x10, 0x06,
+			  0x00, 0x00, 0x00, 0x00, 0x20, 0x10, 0x06, 0xf7,
+	0x10, 0x40, 0x40, 0xee, 0x1c, 0x1c, 0xe9, 0xfc, 0x10, 0x80, 0x80,
+			  0x00, 0x05, 0x35, 0x5e, 0x78, 0x8b, 0x99, 0xa4,
+			  0xae, 0xb5, 0xbc, 0xc1, 0xc6, 0xc9, 0xcc, 0xcf,
+			  0xd0, 0x00, 0x11, 0x22, 0x32, 0x43, 0x54, 0x64,
+			  0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3, 0xd2, 0xe2,
+			  0xf1, 0xff, 0x00, 0x11, 0x22, 0x32, 0x43, 0x54,
+			  0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3, 0xd2,
+			  0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32, 0x43,
+	0x10, 0x80, 0x22, 0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3,
+			  0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x82, 0x02,
+			  0xe4, 0x01, 0x40, 0x01, 0xf0, 0x00, 0x40, 0x01,
+			  0xf0, 0x00,
+	0, 0, 0,
+};
+static const u8 nw801_start_qvga[] = {
+	0x02, 0x00, 0x10, 0x3c, 0x50, 0x9e, 0x3c, 0x50, 0x00, 0x00, 0x00,
+			  0x00, 0x78, 0x18, 0x0b, 0x06, 0xa2, 0x86, 0x78,
+	0x02, 0x0f, 0x01, 0x6b,
+	0x10, 0x1a, 0x01, 0x15,
+	0x00, 0x00, 0x01, 0x1e,
+	0x10, 0x00, 0x01, 0x2f,
+	0x10, 0x8c, 0x08, 0x00, 0x00, 0x3f, 0x01, 0x00, 0x00, 0xef, 0x00,
+	0x10, 0x11, 0x08, 0x29, 0x00, 0x18, 0x01, 0x1f, 0x00, 0xd2, 0x00,
+							/* AE window */
+	0, 0, 0,
+};
+static const u8 nw801_start_vga[] = {
+	0x02, 0x00, 0x10, 0x78, 0xa0, 0x97, 0x78, 0xa0, 0x00, 0x00, 0x00,
+			  0x00, 0xf0, 0x18, 0x0b, 0x06, 0x62, 0x82, 0xf0,
+	0x02, 0x0f, 0x01, 0xd5,
+	0x10, 0x1a, 0x01, 0x15,
+	0x00, 0x00, 0x01, 0x0e,
+	0x10, 0x00, 0x01, 0x22,
+	0x10, 0x8c, 0x08, 0x00, 0x00, 0x7f, 0x02, 0x00, 0x00, 0xdf, 0x01,
+	0x10, 0x11, 0x08, 0x51, 0x00, 0x30, 0x02, 0x3d, 0x00, 0xa4, 0x01,
+	0, 0, 0,
+};
+static const u8 nw801_start_2[] = {
+	0x10, 0x04, 0x01, 0x1a,
+	0x10, 0x19, 0x01, 0x09,				/* clock */
+	0x10, 0x24, 0x06, 0xc0, 0x00, 0x3f, 0x02, 0x00, 0x01,
+							 /* .. gain .. */
+	0x00, 0x03, 0x02, 0x92, 0x03,
+	0x00, 0x1d, 0x04, 0xf2, 0x00, 0x24, 0x07,
+	0x00, 0x7b, 0x01, 0xcf,
+	0x10, 0x94, 0x01, 0x07,
+	0x05, 0x05, 0x01, 0x01,
+	0x05, 0x04, 0x01, 0x01,
+	0x10, 0x0e, 0x01, 0x08,
+	0x10, 0x48, 0x11, 0x00, 0x37, 0x55, 0x6b, 0x7d, 0x8d, 0x9b, 0xa8,
+			  0xb4, 0xbf, 0xca, 0xd4, 0xdd, 0xe6, 0xef, 0xf0,
+			  0xf0,
+	0x10, 0x03, 0x01, 0x00,
+	0x10, 0x0f, 0x02, 0x0c, 0x0c,
+	0x10, 0x03, 0x01, 0x08,
+	0x10, 0x48, 0x11, 0x00, 0x37, 0x55, 0x6b, 0x7d, 0x8d, 0x9b, 0xa8,
+			  0xb4, 0xbf, 0xca, 0xd4, 0xdd, 0xe6, 0xef, 0xf0,
+			  0xf0,
+	0x10, 0x0b, 0x01, 0x0b,
+	0x10, 0x0d, 0x01, 0x0b,
+	0x10, 0x0c, 0x01, 0x1f,
+	0x05, 0x06, 0x01, 0x03,
+	0, 0, 0
+};
+
+/* nw802 (sharp IR3Y38M?) */
+static const u8 nw802_start[] = {
+	0x04, 0x06, 0x01, 0x04,
+	0x00, 0x00, 0x40, 0x10, 0x00, 0x00, 0xf9, 0x02, 0x10, 0x00, 0x4d,
+			  0x0f, 0x1f, 0x00, 0x0d, 0x02, 0x01, 0x00, 0x19,
+			  0x00, 0x01, 0x00, 0x19, 0x00, 0x01, 0x00, 0x19,
+			  0x00, 0x01, 0x00, 0x19, 0x00, 0xce, 0x00, 0xf4,
+			  0x05, 0x3e, 0x00, 0x86, 0x00, 0x3e, 0x00, 0x86,
+			  0x00, 0x3e, 0x00, 0x86, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x56, 0x00, 0x9e, 0x00, 0x56, 0x00, 0x9e,
+			  0x00, 0x56, 0x00, 0x9e, 0x00, 0x01, 0x00, 0x01,
+	0x00, 0x40, 0x40, 0x00, 0x6e, 0x00, 0xb6, 0x00, 0x6e, 0x00, 0x78,
+			  0x04, 0x6e, 0x00, 0xb6, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x6e, 0x00, 0xb6, 0x00, 0x6e, 0x00, 0x78,
+			  0x04, 0x6e, 0x00, 0xb6, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0xca, 0x03, 0x46, 0x04, 0xca, 0x03, 0x46,
+			  0x04, 0x10, 0x00, 0x36, 0x00, 0xd2, 0x00, 0xee,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0xf0,
+			  0x00, 0x3e, 0x00, 0xaa, 0x00, 0x88, 0x00, 0x2e,
+	0x00, 0x80, 0x1f, 0xb4, 0x6f, 0x3f, 0x0f, 0x88, 0x20, 0x68, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0xa8, 0x08, 0x00, 0x11,
+			  0x00, 0x0c, 0x02, 0x01, 0x00, 0x16, 0x00, 0x94,
+			  0x00, 0x10, 0x06, 0x08, 0x00, 0x18, 0x00,
+	0x02, 0x00, 0x12, 0x78, 0xa0, 0x9e, 0x78, 0xa0, 0x00, 0x00, 0x00,
+			  0x00, 0xf0, 0x18, 0x0b, 0x06, 0x62, 0x82, 0xa0,
+			  0x40, 0x20,
+	0x03, 0x00, 0x03, 0x03, 0x00, 0x00,
+	0x04, 0x00, 0x07, 0x01, 0x10, 0x00, 0x00, 0x00, 0x21, 0x00,
+	0x06, 0x00, 0x02, 0x09, 0x99,
+	0x08, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00,
+	0x10, 0x00, 0x40, 0xa1, 0x02, 0x80, 0x00, 0x1d, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x08, 0x0a,
+			  0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x49, 0x13, 0xff, 0x01, 0xc0, 0x00, 0x14,
+			  0x02, 0x00, 0x01, 0x00, 0x00, 0x20, 0x00, 0x00,
+			  0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x10, 0x08,
+			  0x03, 0x00, 0x00, 0x00, 0x00, 0x20, 0x10, 0x06,
+			  0xf7, 0xee, 0x1c, 0x1c, 0xe9, 0xfc, 0x10, 0x80,
+	0x10, 0x40, 0x40, 0x80, 0x00, 0x05, 0x35, 0x5e, 0x78, 0x8b, 0x99,
+			  0xa4, 0xae, 0xb5, 0xbc, 0xc1, 0xc6, 0xc9, 0xcc,
+			  0xcf, 0xd0, 0x00, 0x11, 0x22, 0x32, 0x43, 0x54,
+			  0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3, 0xd2,
+			  0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32, 0x43,
+			  0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3,
+			  0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32,
+			  0x43, 0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3,
+	0x10, 0x80, 0x1b, 0xc3, 0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x05, 0x82,
+			  0x02, 0xe4, 0x01, 0x40, 0x01, 0xf0, 0x00, 0x40,
+			  0x01, 0xf0, 0x00,
+	0x02, 0x00, 0x11, 0x3c, 0x50, 0x9e, 0x3c, 0x50, 0x00, 0x00, 0x00,
+			  0x00, 0x78, 0x3f, 0x10, 0x02, 0xf2, 0x8f, 0x78,
+			  0x40,
+	0x10, 0x1a, 0x01, 0x00,
+	0x10, 0x00, 0x01, 0xad,
+	0x00, 0x00, 0x01, 0x08,
+	0x10, 0x85, 0x08, 0x00, 0x00, 0x3f, 0x01, 0x00, 0x00, 0xef, 0x00,
+	0x10, 0x1b, 0x02, 0x00, 0x00,
+	0x10, 0x11, 0x08, 0x51, 0x00, 0xf0, 0x00, 0x3d, 0x00, 0xb4, 0x00,
+	0x10, 0x1d, 0x08, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0, 0x00, 0xa0,
+	0x10, 0x0e, 0x01, 0x27,
+	0x10, 0x41, 0x11, 0x00, 0x0e, 0x35, 0x4f, 0x62, 0x71, 0x7f, 0x8b,
+			  0x96, 0xa0, 0xa9, 0xb2, 0xbb, 0xc3, 0xca, 0xd2,
+			  0xd8,
+	0x10, 0x03, 0x01, 0x00,
+	0x10, 0x0f, 0x02, 0x14, 0x14,
+	0x10, 0x03, 0x01, 0x0c,
+	0x10, 0x41, 0x11, 0x00, 0x11, 0x22, 0x32, 0x43, 0x54, 0x64, 0x74,
+			  0x84, 0x94, 0xa4, 0xb3, 0xc3, 0xd2, 0xe2, 0xf1,
+			  0xff,
+/*			  0x00, 0x0e, 0x35, 0x4f, 0x62, 0x71, 0x7f, 0x8b,
+ *			  0x96, 0xa0, 0xa9, 0xb2, 0xbb, 0xc3, 0xca, 0xd2,
+ *			  0xd8,	*/
+	0x10, 0x0b, 0x01, 0x10,
+	0x10, 0x0d, 0x01, 0x11,
+	0x10, 0x0c, 0x01, 0x1c,
+	0x04, 0x06, 0x01, 0x03,
+	0x04, 0x04, 0x01, 0x00,
+	0, 0, 0
+};
+/* et31x110 - Trust 120 SpaceCam */
+static const u8 spacecam_init[] = {
+	0x04, 0x05, 0x01, 0x01,
+	0x04, 0x04, 0x01, 0x01,
+	0x04, 0x06, 0x01, 0x04,
+	0x04, 0x04, 0x03, 0x00, 0x00, 0x00,
+	0x05, 0x05, 0x01, 0x00,
+	0, 0, 0
+};
+static const u8 spacecam_start[] = {
+	0x04, 0x06, 0x01, 0x44,
+	0x00, 0x00, 0x40, 0x10, 0x43, 0x00, 0xb4, 0x01, 0x10, 0x00, 0x4f,
+			  0xef, 0x0e, 0x00, 0x74, 0x01, 0x01, 0x00, 0x19,
+			  0x00, 0x01, 0x00, 0x19, 0x00, 0x01, 0x00, 0x19,
+			  0x00, 0x01, 0x00, 0x19, 0x00, 0x3e, 0x00, 0x24,
+			  0x03, 0x3e, 0x00, 0x86, 0x00, 0x3e, 0x00, 0x86,
+			  0x00, 0x3e, 0x00, 0x86, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x56, 0x00, 0x9e, 0x00, 0x56, 0x00, 0x9e,
+			  0x00, 0x56, 0x00, 0x9e, 0x00, 0x01, 0x00, 0x01,
+	0x00, 0x40, 0x40, 0x00, 0x6e, 0x00, 0xb6, 0x00, 0x6e, 0x00, 0x78,
+			  0x04, 0x6e, 0x00, 0xb6, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x6e, 0x00, 0xb6, 0x00, 0x6e, 0x00, 0x78,
+			  0x04, 0x6e, 0x00, 0xb6, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0xca, 0x03, 0x46, 0x04, 0xca, 0x03, 0x46,
+			  0x04, 0x10, 0x00, 0x36, 0x00, 0xd2, 0x00, 0xee,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0xf0,
+			  0x00, 0x3e, 0x00, 0xaa, 0x00, 0x88, 0x00, 0x2e,
+	0x00, 0x80, 0x1f, 0xa0, 0x48, 0xc3, 0x02, 0x88, 0x0c, 0x68, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0xa8, 0x06, 0x00, 0x08,
+			  0x00, 0x32, 0x01, 0x01, 0x00, 0x16, 0x00, 0x04,
+			  0x00, 0x4b, 0x00, 0x7c, 0x00, 0x80, 0x00,
+	0x02, 0x00, 0x12, 0x78, 0xa0, 0x9e, 0x78, 0xa0, 0x00, 0x00, 0x00,
+			  0x00, 0xf0, 0x18, 0x0b, 0x06, 0x62, 0x82, 0xa0,
+			  0x40, 0x20,
+	0x03, 0x00, 0x03, 0x03, 0x00, 0x00,
+	0x04, 0x00, 0x07, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x05, 0x00, 0x06, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x06, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00,
+	0x08, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00,
+	0x10, 0x00, 0x40, 0x83, 0x02, 0x20, 0x00, 0x11, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x08, 0x0a,
+			  0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x49, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+			  0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x10, 0x08,
+			  0x03, 0x00, 0x00, 0x00, 0x00, 0x20, 0x10, 0x06,
+			  0xf7, 0xee, 0x1c, 0x1c, 0xe9, 0xfc, 0x10, 0x80,
+	0x10, 0x40, 0x40, 0x80, 0x00, 0x05, 0x35, 0x5e, 0x78, 0x8b, 0x99,
+			  0xa4, 0xae, 0xb5, 0xbc, 0xc1, 0xc6, 0xc9, 0xcc,
+			  0xcf, 0xd0, 0x00, 0x11, 0x22, 0x32, 0x43, 0x54,
+			  0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3, 0xd2,
+			  0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32, 0x43,
+			  0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3,
+			  0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32,
+			  0x43, 0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3,
+	0x10, 0x80, 0x1d, 0xc3, 0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x00, 0x62,
+			  0x01, 0x24, 0x01, 0x62, 0x01, 0x24, 0x01, 0x20,
+			  0x01, 0x60, 0x01, 0x00, 0x00,
+	0x04, 0x06, 0x01, 0xc0,
+	0x10, 0x85, 0x08, 0x00, 0x00, 0x5f, 0x01, 0x00, 0x00, 0x1f, 0x01,
+	0x02, 0x00, 0x11, 0x48, 0x58, 0x9e, 0x48, 0x58, 0x00, 0x00, 0x00,
+			  0x00, 0x84, 0x36, 0x05, 0x01, 0xf2, 0x86, 0x65,
+			  0x40,
+	0x00, 0x80, 0x01, 0xa0,
+	0x10, 0x1a, 0x01, 0x00,
+	0x00, 0x91, 0x02, 0x32, 0x01,
+	0x00, 0x03, 0x02, 0x08, 0x02,
+	0x10, 0x00, 0x01, 0x83,
+	0x10, 0x8f, 0x0c, 0x62, 0x01, 0x24, 0x01, 0x62, 0x01, 0x24, 0x01,
+			  0x20, 0x01, 0x60, 0x01,
+	0x10, 0x11, 0x08, 0x00, 0x00, 0x5f, 0x01, 0x00, 0x00, 0x1f, 0x01,
+	0x10, 0x0e, 0x01, 0x08,
+	0x10, 0x41, 0x11, 0x00, 0x64, 0x99, 0xc0, 0xe2, 0xf9, 0xf9, 0xf9,
+			  0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9,
+			  0xf9,
+	0x10, 0x03, 0x01, 0x00,
+	0x10, 0x0f, 0x02, 0x13, 0x13,
+	0x10, 0x03, 0x01, 0x06,
+	0x10, 0x41, 0x11, 0x00, 0x64, 0x99, 0xc0, 0xe2, 0xf9, 0xf9, 0xf9,
+			  0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9,
+			  0xf9,
+	0x10, 0x0b, 0x01, 0x08,
+	0x10, 0x0d, 0x01, 0x10,
+	0x10, 0x0c, 0x01, 0x1f,
+	0x04, 0x06, 0x01, 0xc3,
+	0x04, 0x05, 0x01, 0x40,
+	0x04, 0x04, 0x01, 0x40,
+	0, 0, 0
+};
+/* et31x110 - pas106 - other Trust SpaceCam120 */
+static const u8 spacecam2_start[] = {
+	0x04, 0x06, 0x01, 0x44,
+	0x04, 0x06, 0x01, 0x00,
+	0x00, 0x00, 0x40, 0x14, 0x83, 0x00, 0xba, 0x01, 0x10, 0x00, 0x4f,
+			  0xef, 0x00, 0x00, 0x60, 0x00, 0x01, 0x00, 0x19,
+			  0x00, 0x01, 0x00, 0x19, 0x00, 0x01, 0x00, 0x19,
+			  0x00, 0x01, 0x00, 0x19, 0x00, 0x06, 0x00, 0xfc,
+			  0x01, 0x3e, 0x00, 0x86, 0x00, 0x3e, 0x00, 0x86,
+			  0x00, 0x3e, 0x00, 0x86, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x56, 0x00, 0x9e, 0x00, 0x56, 0x00, 0x9e,
+			  0x00, 0x56, 0x00, 0x9e, 0x00, 0x01, 0x00, 0x01,
+	0x00, 0x40, 0x40, 0x00, 0x6e, 0x00, 0xb6, 0x00, 0x6e, 0x00, 0x78,
+			  0x04, 0x6e, 0x00, 0xb6, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x6e, 0x00, 0xb6, 0x00, 0x6e, 0x00, 0x78,
+			  0x04, 0x6e, 0x00, 0xb6, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0xca, 0x03, 0x46, 0x04, 0xca, 0x03, 0x46,
+			  0x04, 0x10, 0x00, 0x36, 0x00, 0xd2, 0x00, 0xee,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0xf0,
+			  0x00, 0x3e, 0x00, 0xaa, 0x00, 0x88, 0x00, 0x2e,
+	0x00, 0x80, 0x1f, 0xb8, 0x48, 0x0f, 0x04, 0x88, 0x14, 0x68, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0xa8, 0x01, 0x00, 0x03,
+			  0x00, 0x24, 0x01, 0x01, 0x00, 0x16, 0x00, 0x04,
+			  0x00, 0x4b, 0x00, 0x76, 0x00, 0x86, 0x00,
+	0x02, 0x00, 0x12, 0x78, 0xa0, 0x9e, 0x78, 0xa0, 0x00, 0x00, 0x00,
+			  0x00, 0xf0, 0x18, 0x0b, 0x06, 0x62, 0x82, 0xa0,
+			  0x40, 0x20,
+	0x03, 0x00, 0x03, 0x03, 0x00, 0x00,
+	0x04, 0x00, 0x07, 0x01, 0x10, 0x00, 0x00, 0x00, 0x61, 0x00,
+	0x05, 0x00, 0x06, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x06, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00,
+	0x08, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00,
+	0x10, 0x00, 0x40, 0x80, 0x02, 0x20, 0x00, 0x13, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x08, 0x0a,
+			  0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x49, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+			  0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x10, 0x08,
+			  0x03, 0x00, 0x00, 0x00, 0x00, 0x20, 0x10, 0x06,
+			  0xf7, 0xee, 0x1c, 0x1c, 0xe9, 0xfc, 0x10, 0x80,
+	0x10, 0x40, 0x40, 0x80, 0x00, 0x05, 0x35, 0x5e, 0x78, 0x8b, 0x99,
+			  0xa4, 0xae, 0xb5, 0xbc, 0xc1, 0xc6, 0xc9, 0xcc,
+			  0xcf, 0xd0, 0x00, 0x11, 0x22, 0x32, 0x43, 0x54,
+			  0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3, 0xd2,
+			  0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32, 0x43,
+			  0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3,
+			  0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32,
+			  0x43, 0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3,
+	0x10, 0x80, 0x1d, 0xc3, 0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x62,
+			  0x01, 0x24, 0x01, 0x62, 0x01, 0x24, 0x01, 0x20,
+			  0x01, 0x60, 0x01, 0x00, 0x00,
+	0x10, 0x85, 0x08, 0x00, 0x00, 0x5f, 0x01, 0x00, 0x00, 0x1f, 0x01,
+	0x04, 0x04, 0x01, 0x40,
+	0x04, 0x04, 0x01, 0x00,
+	I2C0, 0x40, 0x0c, 0x02, 0x0c, 0x12, 0x07, 0x00, 0x00, 0x00, 0x05,
+			  0x00, 0x00, 0x05, 0x05,
+	I2C0, 0x40, 0x02, 0x11, 0x06,
+	I2C0, 0x40, 0x02, 0x14, 0x00,
+	I2C0, 0x40, 0x02, 0x13, 0x01,		/* i2c end */
+	0x02, 0x00, 0x11, 0x48, 0x58, 0x9e, 0x48, 0x58, 0x00, 0x00, 0x00,
+			  0x00, 0x84, 0x36, 0x05, 0x01, 0xf2, 0x86, 0x65,
+			  0x40,
+	I2C0, 0x40, 0x02, 0x02, 0x0c,		/* pixel clock */
+	I2C0, 0x40, 0x02, 0x0f, 0x00,
+	I2C0, 0x40, 0x02, 0x13, 0x01,		/* i2c end */
+	0x10, 0x00, 0x01, 0x01,
+	0x10, 0x8f, 0x0c, 0x62, 0x01, 0x24, 0x01, 0x62, 0x01, 0x24, 0x01,
+			  0x20, 0x01, 0x60, 0x01,
+	I2C0, 0x40, 0x02, 0x05, 0x0f,		/* exposure */
+	I2C0, 0x40, 0x02, 0x13, 0x01,		/* i2c end */
+	I2C0, 0x40, 0x07, 0x09, 0x0b, 0x0f, 0x05, 0x05, 0x0f, 0x00,
+						/* gains */
+	I2C0, 0x40, 0x03, 0x12, 0x04, 0x01,
+	0x10, 0x11, 0x08, 0x00, 0x00, 0x5f, 0x01, 0x00, 0x00, 0x1f, 0x01,
+	0x10, 0x0e, 0x01, 0x08,
+	0x10, 0x41, 0x11, 0x00, 0x17, 0x3f, 0x69, 0x7b, 0x8c, 0x9a, 0xa7,
+			  0xb3, 0xbf, 0xc9, 0xd3, 0xdd, 0xe6, 0xef, 0xf7,
+			  0xf9,
+	0x10, 0x03, 0x01, 0x00,
+	0x10, 0x0f, 0x02, 0x13, 0x13,
+	0x10, 0x03, 0x01, 0x06,
+	0x10, 0x41, 0x11, 0x00, 0x17, 0x3f, 0x69, 0x7b, 0x8c, 0x9a, 0xa7,
+			  0xb3, 0xbf, 0xc9, 0xd3, 0xdd, 0xe6, 0xef, 0xf7,
+			  0xf9,
+	0x10, 0x0b, 0x01, 0x11,
+	0x10, 0x0d, 0x01, 0x10,
+	0x10, 0x0c, 0x01, 0x14,
+	0x04, 0x06, 0x01, 0x03,
+	0x04, 0x05, 0x01, 0x61,
+	0x04, 0x04, 0x01, 0x00,
+	0, 0, 0
+};
+
+/* nw802 - Conceptronic Video Pro */
+static const u8 cvideopro_start[] = {
+	0x04, 0x06, 0x01, 0x04,
+	0x00, 0x00, 0x40, 0x54, 0x96, 0x98, 0xf9, 0x02, 0x18, 0x00, 0x4c,
+			  0x0f, 0x1f, 0x00, 0x0d, 0x02, 0x01, 0x00, 0x19,
+			  0x00, 0x01, 0x00, 0x19, 0x00, 0x01, 0x00, 0x19,
+			  0x00, 0x0b, 0x00, 0x1b, 0x00, 0xc8, 0x00, 0xf4,
+			  0x05, 0xb4, 0x00, 0xcc, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0xa2, 0x00, 0xc6, 0x00, 0x60, 0x00, 0xc6,
+			  0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+	0x00, 0x40, 0x40, 0x00, 0xae, 0x00, 0xd2, 0x00, 0xae, 0x00, 0xd2,
+			  0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0xa8, 0x00, 0xc0, 0x00, 0x66, 0x00, 0xc0,
+			  0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x0a, 0x00, 0x54, 0x00, 0x0a, 0x00, 0x54,
+			  0x00, 0x10, 0x00, 0x36, 0x00, 0xd2, 0x00, 0xee,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6,
+			  0x00, 0x5d, 0x00, 0xc7, 0x00, 0x7e, 0x00, 0x30,
+	0x00, 0x80, 0x1f, 0x98, 0x43, 0x3f, 0x0d, 0x88, 0x20, 0x80, 0x3f,
+			  0x47, 0xaf, 0x00, 0x00, 0xa8, 0x08, 0x00, 0x11,
+			  0x00, 0x0c, 0x02, 0x0c, 0x00, 0x1c, 0x00, 0x94,
+			  0x00, 0x10, 0x06, 0x24, 0x00, 0x4a, 0x00,
+	0x02, 0x00, 0x12, 0x78, 0xa0, 0x9e, 0x78, 0xa0, 0x00, 0x00, 0x00,
+			  0x00, 0xf0, 0x18, 0x0b, 0x06, 0x62, 0x82, 0xa0,
+			  0x40, 0x20,
+	0x03, 0x00, 0x03, 0x03, 0x00, 0x00,
+	0x04, 0x00, 0x07, 0x01, 0x10, 0x00, 0x00, 0x00, 0xff, 0x00,
+	0x06, 0x00, 0x02, 0x09, 0x99,
+	0x08, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00,
+	0x10, 0x00, 0x40, 0xa0, 0x02, 0x80, 0x00, 0x12, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x08, 0x0a,
+			  0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x49, 0x13, 0x00, 0x00, 0xe0, 0x00, 0x0c,
+			  0x00, 0x52, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+			  0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x10, 0x08,
+			  0x03, 0x00, 0x00, 0x00, 0x00, 0x20, 0x10, 0x06,
+			  0xf7, 0xee, 0x1c, 0x1c, 0xe9, 0xfc, 0x10, 0x80,
+	0x10, 0x40, 0x40, 0x80, 0x00, 0x05, 0x35, 0x5e, 0x78, 0x8b, 0x99,
+			  0xa4, 0xae, 0xb5, 0xbc, 0xc1, 0xc6, 0xc9, 0xcc,
+			  0xcf, 0xd0, 0x00, 0x11, 0x22, 0x32, 0x43, 0x54,
+			  0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3, 0xd2,
+			  0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32, 0x43,
+			  0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3,
+			  0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32,
+			  0x43, 0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3,
+	0x10, 0x80, 0x1b, 0xc3, 0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x82,
+			  0x02, 0xe4, 0x01, 0x40, 0x01, 0xf0, 0x00, 0x40,
+			  0x01, 0xf0, 0x00,
+	0x02, 0x00, 0x11, 0x3c, 0x50, 0x8c, 0x3c, 0x50, 0x00, 0x00, 0x00,
+			  0x00, 0x78, 0x3f, 0x3f, 0x06, 0xf2, 0x8f, 0xf0,
+			  0x40,
+	0x10, 0x1a, 0x01, 0x03,
+	0x10, 0x00, 0x01, 0xac,
+	0x10, 0x85, 0x08, 0x00, 0x00, 0x3f, 0x01, 0x00, 0x00, 0xef, 0x00,
+	0x10, 0x1b, 0x02, 0x3b, 0x01,
+	0x10, 0x11, 0x08, 0x61, 0x00, 0xe0, 0x00, 0x49, 0x00, 0xa8, 0x00,
+	0x10, 0x1f, 0x06, 0x01, 0x20, 0x02, 0xe8, 0x03, 0x00,
+	0x10, 0x1d, 0x02, 0x40, 0x06,
+	0x10, 0x0e, 0x01, 0x08,
+	0x10, 0x41, 0x11, 0x00, 0x0f, 0x46, 0x62, 0x76, 0x86, 0x94, 0xa0,
+			  0xab, 0xb6, 0xbf, 0xc8, 0xcf, 0xd7, 0xdc, 0xdc,
+			  0xdc,
+	0x10, 0x03, 0x01, 0x00,
+	0x10, 0x0f, 0x02, 0x12, 0x12,
+	0x10, 0x03, 0x01, 0x0c,
+	0x10, 0x41, 0x11, 0x00, 0x0f, 0x46, 0x62, 0x76, 0x86, 0x94, 0xa0,
+			  0xab, 0xb6, 0xbf, 0xc8, 0xcf, 0xd7, 0xdc, 0xdc,
+			  0xdc,
+	0x10, 0x0b, 0x01, 0x09,
+	0x10, 0x0d, 0x01, 0x10,
+	0x10, 0x0c, 0x01, 0x2f,
+	0x04, 0x06, 0x01, 0x03,
+	0x04, 0x04, 0x01, 0x00,
+	0, 0, 0
+};
+
+/* nw802 - D-link dru-350c cam */
+static const u8 dlink_start[] = {
+	0x04, 0x06, 0x01, 0x04,
+	0x00, 0x00, 0x40, 0x10, 0x00, 0x00, 0x92, 0x03, 0x10, 0x00, 0x4d,
+			  0x0f, 0x1f, 0x00, 0x0d, 0x02, 0x01, 0x00, 0x19,
+			  0x00, 0x01, 0x00, 0x19, 0x00, 0x01, 0x00, 0x19,
+			  0x00, 0x01, 0x00, 0x19, 0x00, 0xce, 0x00, 0xf4,
+			  0x05, 0x3e, 0x00, 0x86, 0x00, 0x3e, 0x00, 0x86,
+			  0x00, 0x3e, 0x00, 0x86, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x56, 0x00, 0x9e, 0x00, 0x56, 0x00, 0x9e,
+			  0x00, 0x56, 0x00, 0x9e, 0x00, 0x01, 0x00, 0x01,
+	0x00, 0x40, 0x40, 0x00, 0x6e, 0x00, 0xb6, 0x00, 0x6e, 0x00, 0x78,
+			  0x04, 0x6e, 0x00, 0xb6, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x6e, 0x00, 0xb6, 0x00, 0x6e, 0x00, 0x78,
+			  0x04, 0x6e, 0x00, 0xb6, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0xca, 0x03, 0x46, 0x04, 0xca, 0x03, 0x46,
+			  0x04, 0x10, 0x00, 0x36, 0x00, 0xd2, 0x00, 0xee,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0xf0,
+			  0x00, 0x3e, 0x00, 0xaa, 0x00, 0x88, 0x00, 0x2e,
+	0x00, 0x80, 0x1f, 0xb4, 0x6f, 0x3f, 0x0f, 0x88, 0x20, 0x68, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0xa8, 0x08, 0x00, 0x11,
+			  0x00, 0x0c, 0x02, 0x01, 0x00, 0x16, 0x00, 0x94,
+			  0x00, 0x10, 0x06, 0x10, 0x00, 0x36, 0x00,
+	0x02, 0x00, 0x12, 0x78, 0xa0, 0x9e, 0x78, 0xa0, 0x00, 0x00, 0x00,
+			  0x00, 0xf0, 0x18, 0x0b, 0x06, 0x62, 0x82, 0xa0,
+			  0x40, 0x20,
+	0x03, 0x00, 0x03, 0x03, 0x00, 0x00,
+	0x04, 0x00, 0x07, 0x01, 0x10, 0x00, 0x00, 0x00, 0x21, 0x00,
+	0x06, 0x00, 0x02, 0x09, 0x99,
+	0x08, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00,
+	0x10, 0x00, 0x40, 0xa1, 0x02, 0x80, 0x00, 0x12, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x08, 0x0a,
+			  0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x49, 0x13, 0x00, 0x00, 0xc0, 0x00, 0x14,
+			  0x02, 0x00, 0x01, 0x00, 0x00, 0x20, 0x00, 0x00,
+			  0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x10, 0x08,
+			  0x03, 0x00, 0x00, 0x00, 0x00, 0x20, 0x10, 0x06,
+			  0xf7, 0xee, 0x1c, 0x1c, 0xe9, 0xfc, 0x10, 0x80,
+	0x10, 0x40, 0x40, 0x80, 0x00, 0x05, 0x35, 0x5e, 0x78, 0x8b, 0x99,
+			  0xa4, 0xae, 0xb5, 0xbc, 0xc1, 0xc6, 0xc9, 0xcc,
+			  0xcf, 0xd0, 0x00, 0x11, 0x22, 0x32, 0x43, 0x54,
+			  0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3, 0xd2,
+			  0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32, 0x43,
+			  0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3,
+			  0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32,
+			  0x43, 0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3,
+	0x10, 0x80, 0x1b, 0xc3, 0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x01, 0x82,
+			  0x02, 0xe4, 0x01, 0x40, 0x01, 0xf0, 0x00, 0x40,
+			  0x01, 0xf0, 0x00,
+	0x02, 0x00, 0x11, 0x3c, 0x50, 0x9e, 0x3c, 0x50, 0x00, 0x00, 0x00,
+			  0x00, 0x78, 0x3f, 0x10, 0x02, 0xf2, 0x8f, 0x78,
+			  0x40,
+	0x10, 0x1a, 0x01, 0x00,
+	0x10, 0x00, 0x01, 0xad,
+	0x00, 0x00, 0x01, 0x08,
+	0x10, 0x85, 0x08, 0x00, 0x00, 0x3f, 0x01, 0x00, 0x00, 0xef, 0x00,
+	0x10, 0x1b, 0x02, 0x00, 0x00,
+	0x10, 0x11, 0x08, 0x51, 0x00, 0xf0, 0x00, 0x3d, 0x00, 0xb4, 0x00,
+	0x10, 0x1d, 0x08, 0x40, 0x06, 0x01, 0x20, 0x02, 0xe8, 0x03, 0x00,
+	0x10, 0x0e, 0x01, 0x20,
+	0x10, 0x41, 0x11, 0x00, 0x07, 0x1e, 0x38, 0x4d, 0x60, 0x70, 0x7f,
+			  0x8e, 0x9b, 0xa8, 0xb4, 0xbf, 0xca, 0xd5, 0xdf,
+			  0xea,
+	0x10, 0x03, 0x01, 0x00,
+	0x10, 0x0f, 0x02, 0x11, 0x11,
+	0x10, 0x03, 0x01, 0x10,
+	0x10, 0x41, 0x11, 0x00, 0x07, 0x1e, 0x38, 0x4d, 0x60, 0x70, 0x7f,
+			  0x8e, 0x9b, 0xa8, 0xb4, 0xbf, 0xca, 0xd5, 0xdf,
+			  0xea,
+	0x10, 0x0b, 0x01, 0x19,
+	0x10, 0x0d, 0x01, 0x10,
+	0x10, 0x0c, 0x01, 0x1e,
+	0x04, 0x06, 0x01, 0x03,
+	0x04, 0x04, 0x01, 0x00,
+	0, 0, 0
+};
+
+/* 06a5:d001 - nw801 - Sony
+ *		Plustek Opticam 500U or ProLink DS3303u (Hitachi HD49322BF) */
+/*fixme: 320x240 only*/
+static const u8 ds3303_start[] = {
+	0x05, 0x06, 0x01, 0x04,
+	0x00, 0x00, 0x40, 0x16, 0x00, 0x00, 0xf9, 0x02, 0x11, 0x00, 0x0e,
+			  0x01, 0x1f, 0x00, 0x0d, 0x02, 0x01, 0x00, 0x19,
+			  0x00, 0x01, 0x00, 0x19, 0x00, 0x01, 0x00, 0x19,
+			  0x00, 0x01, 0x00, 0x19, 0x00, 0xce, 0x00, 0xf4,
+			  0x05, 0x3e, 0x00, 0x86, 0x00, 0x3e, 0x00, 0x86,
+			  0x00, 0x3e, 0x00, 0x86, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x56, 0x00, 0x9e, 0x00, 0x56, 0x00, 0x9e,
+			  0x00, 0x56, 0x00, 0x9e, 0x00, 0x01, 0x00, 0x01,
+	0x00, 0x40, 0x40, 0x00, 0x6e, 0x00, 0xb6, 0x00, 0x6e, 0x00, 0x78,
+			  0x04, 0x6e, 0x00, 0xb6, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x6e, 0x00, 0xb6, 0x00, 0x6e, 0x00, 0x78,
+			  0x04, 0x6e, 0x00, 0xb6, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0xca, 0x03, 0x46, 0x04, 0xca, 0x03, 0x46,
+			  0x04, 0x10, 0x00, 0x36, 0x00, 0xd2, 0x00, 0xee,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0xf0,
+			  0x00, 0x3e, 0x00, 0xaa, 0x00, 0x88, 0x00, 0x2e,
+	0x00, 0x80, 0x22, 0xb4, 0x6f, 0x3f, 0x0f, 0x88, 0x20, 0x08, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0xa9, 0xa8, 0x1f, 0x00,
+			  0x0d, 0x02, 0x07, 0x00, 0x01, 0x00, 0x19, 0x00,
+			  0xf2, 0x00, 0x18, 0x06, 0x10, 0x06, 0x10, 0x00,
+			  0x36, 0x00,
+	0x02, 0x00, 0x12, 0x03, 0xa0, 0x9e, 0x78, 0xa0, 0x00, 0x00, 0x00,
+			  0x00, 0xf0, 0x18, 0x0b, 0x06, 0x62, 0x82, 0x50,
+			  0x40, 0x20,
+	0x03, 0x00, 0x03, 0x03, 0x00, 0x00,
+	0x05, 0x00, 0x07, 0x01, 0x10, 0x00, 0x00, 0x00, 0xff, 0x00,
+	0x06, 0x00, 0x02, 0x09, 0x99,
+	0x08, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00,
+	0x10, 0x00, 0x40, 0x2f, 0x02, 0x80, 0x00, 0x12, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x10, 0x1f, 0x10, 0x08, 0x0a,
+			  0x0a, 0x51, 0x00, 0xf1, 0x00, 0x3c, 0x00, 0xb4,
+			  0x00, 0x01, 0x15, 0xfd, 0x07, 0x3d, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x8c, 0x04, 0x01, 0x20,
+			  0x02, 0x00, 0x03, 0x00, 0x20, 0x00, 0x00, 0x00,
+			  0x20, 0x00, 0x00, 0x00, 0x20, 0x10, 0x08, 0x03,
+			  0x00, 0x00, 0x00, 0x00, 0x20, 0x10, 0x06, 0xf7,
+	0x10, 0x40, 0x40, 0xee, 0x1c, 0x1c, 0xe9, 0xfc, 0x10, 0x80, 0x80,
+			  0x00, 0x2d, 0x46, 0x58, 0x67, 0x74, 0x7f, 0x88,
+			  0x94, 0x9d, 0xa6, 0xae, 0xb5, 0xbd, 0xc4, 0xcb,
+			  0xd1, 0x00, 0x11, 0x22, 0x32, 0x43, 0x54, 0x64,
+			  0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3, 0xd2, 0xe2,
+			  0xf1, 0xff, 0x00, 0x11, 0x22, 0x32, 0x43, 0x54,
+			  0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3, 0xd2,
+			  0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32, 0x43,
+	0x10, 0x80, 0x22, 0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3,
+			  0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x00, 0x3f, 0x01,
+			  0x00, 0x00, 0xef, 0x00, 0x02, 0x0a, 0x82, 0x02,
+			  0xe4, 0x01, 0x40, 0x01, 0xf0, 0x00, 0x40, 0x01,
+			  0xf0, 0x00,
+
+	0x02, 0x00, 0x11, 0x3c, 0x50, 0x9e, 0x3c, 0x50, 0x00, 0x00, 0x00,
+			  0x00, 0x78, 0x3f, 0x3f, 0x00, 0xf2, 0x8f, 0x81,
+			  0x40,
+	0x10, 0x1a, 0x01, 0x15,
+	0x10, 0x00, 0x01, 0x2f,
+	0x10, 0x8c, 0x08, 0x00, 0x00, 0x3f, 0x01, 0x00, 0x00, 0xef, 0x00,
+	0x10, 0x1b, 0x02, 0x00, 0x00,
+	0x10, 0x11, 0x08, 0x61, 0x00, 0xe0, 0x00, 0x49, 0x00, 0xa8, 0x00,
+	0x10, 0x26, 0x06, 0x01, 0x20, 0x02, 0xe8, 0x03, 0x00,
+	0x10, 0x24, 0x02, 0x40, 0x06,
+	0x10, 0x0e, 0x01, 0x08,
+	0x10, 0x48, 0x11, 0x00, 0x15, 0x40, 0x67, 0x84, 0x9d, 0xb2, 0xc6,
+			  0xd6, 0xe7, 0xf6, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9,
+			  0xf9,
+	0x10, 0x03, 0x01, 0x00,
+	0x10, 0x0f, 0x02, 0x16, 0x16,
+	0x10, 0x03, 0x01, 0x0c,
+	0x10, 0x48, 0x11, 0x00, 0x15, 0x40, 0x67, 0x84, 0x9d, 0xb2, 0xc6,
+			  0xd6, 0xe7, 0xf6, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9,
+			  0xf9,
+	0x10, 0x0b, 0x01, 0x26,
+	0x10, 0x0d, 0x01, 0x10,
+	0x10, 0x0c, 0x01, 0x1c,
+	0x05, 0x06, 0x01, 0x03,
+	0x05, 0x04, 0x01, 0x00,
+	0, 0, 0
+};
+
+/* 06a5:d001 - nw802 - Panasonic
+ *		GP-KR651US (Philips TDA8786) */
+static const u8 kr651_start_1[] = {
+	0x04, 0x06, 0x01, 0x04,
+	0x00, 0x00, 0x40, 0x44, 0x96, 0x98, 0xf9, 0x02, 0x18, 0x00, 0x48,
+			  0x0f, 0x1f, 0x00, 0x0d, 0x02, 0x01, 0x00, 0x19,
+			  0x00, 0x01, 0x00, 0x19, 0x00, 0x01, 0x00, 0x19,
+			  0x00, 0x0b, 0x00, 0x1b, 0x00, 0xc8, 0x00, 0xf4,
+			  0x05, 0xb4, 0x00, 0xcc, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0xa2, 0x00, 0xc6, 0x00, 0x60, 0x00, 0xc6,
+			  0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+	0x00, 0x40, 0x40, 0x00, 0xae, 0x00, 0xd2, 0x00, 0xae, 0x00, 0xd2,
+			  0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0xa8, 0x00, 0xc0, 0x00, 0x66, 0x00, 0xc0,
+			  0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x0a, 0x00, 0x54, 0x00, 0x0a, 0x00, 0x54,
+			  0x00, 0x10, 0x00, 0x36, 0x00, 0xd2, 0x00, 0xee,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6,
+			  0x00, 0x5d, 0x00, 0xc7, 0x00, 0x7e, 0x00, 0x30,
+	0x00, 0x80, 0x1f, 0x18, 0x43, 0x3f, 0x0d, 0x88, 0x20, 0x80, 0x3f,
+			  0x47, 0xaf, 0x00, 0x00, 0xa8, 0x08, 0x00, 0x11,
+			  0x00, 0x0c, 0x02, 0x0c, 0x00, 0x1c, 0x00, 0x94,
+			  0x00, 0x10, 0x06, 0x24, 0x00, 0x4a, 0x00,
+	0x02, 0x00, 0x12, 0x78, 0xa0, 0x9e, 0x78, 0xa0, 0x00, 0x00, 0x00,
+			  0x00, 0xf0, 0x18, 0x0b, 0x06, 0x62, 0x82, 0xa0,
+			  0x40, 0x20,
+	0x03, 0x00, 0x03, 0x02, 0x00, 0x00,
+	0x04, 0x00, 0x07, 0x01, 0x10, 0x00, 0x00, 0x00, 0x21, 0x00,
+	0x06, 0x00, 0x02, 0x09, 0x99,
+	0x08, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00,
+	0x10, 0x00, 0x40, 0xa0, 0x02, 0x80, 0x00, 0x12, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x08, 0x0a,
+			  0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x49, 0x13, 0x00, 0x00, 0xe0, 0x00, 0x0c,
+			  0x00, 0x52, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+			  0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x10, 0x08,
+			  0x03, 0x00, 0x00, 0x00, 0x00, 0x20, 0x10, 0x06,
+			  0xf7, 0xee, 0x1c, 0x1c, 0xe9, 0xfc, 0x10, 0x80,
+	0x10, 0x40, 0x40, 0x80, 0x00, 0x05, 0x35, 0x5e, 0x78, 0x8b, 0x99,
+			  0xa4, 0xae, 0xb5, 0xbc, 0xc1, 0xc6, 0xc9, 0xcc,
+			  0xcf, 0xd0, 0x00, 0x11, 0x22, 0x32, 0x43, 0x54,
+			  0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3, 0xd2,
+			  0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32, 0x43,
+			  0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3,
+			  0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32,
+			  0x43, 0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3,
+	0x10, 0x80, 0x1b, 0xc3, 0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x82,
+			  0x02, 0xe4, 0x01, 0x40, 0x01, 0xf0, 0x00, 0x40,
+			  0x01, 0xf0, 0x00,
+	0, 0, 0
+};
+static const u8 kr651_start_qvga[] = {
+	0x02, 0x00, 0x11, 0x3c, 0x50, 0x9e, 0x3c, 0x50, 0x00, 0x00, 0x00,
+			  0x00, 0x78, 0x3f, 0x10, 0x02, 0xf2, 0x8f, 0x78,
+			  0x40,
+	0x10, 0x1a, 0x01, 0x03,
+	0x10, 0x00, 0x01, 0xac,
+	0x10, 0x85, 0x08, 0x00, 0x00, 0x3f, 0x01, 0x00, 0x00, 0xef, 0x00,
+	0x10, 0x1b, 0x02, 0x00, 0x00,
+	0x10, 0x11, 0x08, 0x29, 0x00, 0x18, 0x01, 0x1f, 0x00, 0xd2, 0x00,
+	0x10, 0x1d, 0x06, 0xe0, 0x00, 0x0c, 0x00, 0x52, 0x00,
+	0x10, 0x1d, 0x02, 0x28, 0x01,
+	0, 0, 0
+};
+static const u8 kr651_start_vga[] = {
+	0x02, 0x00, 0x11, 0x78, 0xa0, 0x8c, 0x78, 0xa0, 0x00, 0x00, 0x00,
+			  0x00, 0xf0, 0x30, 0x03, 0x01, 0x82, 0x82, 0x98,
+			  0x80,
+	0x10, 0x1a, 0x01, 0x03,
+	0x10, 0x00, 0x01, 0xa0,
+	0x10, 0x85, 0x08, 0x00, 0x00, 0x7f, 0x02, 0x00, 0x00, 0xdf, 0x01,
+	0x10, 0x1b, 0x02, 0x00, 0x00,
+	0x10, 0x11, 0x08, 0x51, 0x00, 0x30, 0x02, 0x3d, 0x00, 0xa4, 0x01,
+	0x10, 0x1d, 0x06, 0xe0, 0x00, 0x0c, 0x00, 0x52, 0x00,
+	0x10, 0x1d, 0x02, 0x68, 0x00,
+};
+static const u8 kr651_start_2[] = {
+	0x10, 0x0e, 0x01, 0x08,
+	0x10, 0x41, 0x11, 0x00, 0x11, 0x3c, 0x5c, 0x74, 0x88, 0x99, 0xa8,
+			  0xb7, 0xc4, 0xd0, 0xdc, 0xdc, 0xdc, 0xdc, 0xdc,
+			  0xdc,
+	0x10, 0x03, 0x01, 0x00,
+	0x10, 0x0f, 0x02, 0x0c, 0x0c,
+	0x10, 0x03, 0x01, 0x0c,
+	0x10, 0x41, 0x11, 0x00, 0x11, 0x3c, 0x5c, 0x74, 0x88, 0x99, 0xa8,
+			  0xb7, 0xc4, 0xd0, 0xdc, 0xdc, 0xdc, 0xdc, 0xdc,
+			  0xdc,
+	0x10, 0x0b, 0x01, 0x10,
+	0x10, 0x0d, 0x01, 0x10,
+	0x10, 0x0c, 0x01, 0x2d,
+	0x04, 0x06, 0x01, 0x03,
+	0x04, 0x04, 0x01, 0x00,
+	0, 0, 0
+};
+
+/* nw802 - iRez Kritter cam */
+static const u8 kritter_start[] = {
+	0x04, 0x06, 0x01, 0x06,
+	0x00, 0x00, 0x40, 0x44, 0x96, 0x98, 0x94, 0x03, 0x18, 0x00, 0x48,
+			  0x0f, 0x1e, 0x00, 0x0c, 0x02, 0x01, 0x00, 0x19,
+			  0x00, 0x01, 0x00, 0x19, 0x00, 0x01, 0x00, 0x19,
+			  0x00, 0x0b, 0x00, 0x1b, 0x00, 0x0a, 0x01, 0x28,
+			  0x07, 0xb4, 0x00, 0xcc, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0xa2, 0x00, 0xc6, 0x00, 0x60, 0x00, 0xc6,
+			  0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+	0x00, 0x40, 0x40, 0x00, 0xae, 0x00, 0xd2, 0x00, 0xae, 0x00, 0xd2,
+			  0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0xa8, 0x00, 0xc0, 0x00, 0x66, 0x00, 0xc0,
+			  0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x0a, 0x00, 0x54, 0x00, 0x0a, 0x00, 0x54,
+			  0x00, 0x10, 0x00, 0x36, 0x00, 0xd2, 0x00, 0xee,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6,
+			  0x00, 0x5d, 0x00, 0x0e, 0x00, 0x7e, 0x00, 0x30,
+	0x00, 0x80, 0x1f, 0x18, 0x43, 0x3f, 0x0d, 0x88, 0x20, 0x80, 0x3f,
+			  0x47, 0xaf, 0x00, 0x00, 0xa8, 0x08, 0x00, 0x11,
+			  0x00, 0x0b, 0x02, 0x0c, 0x00, 0x1c, 0x00, 0x94,
+			  0x00, 0x10, 0x06, 0x24, 0x00, 0x4a, 0x00,
+	0x02, 0x00, 0x12, 0x78, 0xa0, 0x9e, 0x78, 0xa0, 0x00, 0x00, 0x00,
+			  0x00, 0xf0, 0x18, 0x0b, 0x06, 0x62, 0x82, 0xa0,
+			  0x40, 0x20,
+	0x03, 0x00, 0x03, 0x02, 0x00, 0x00,
+	0x04, 0x00, 0x07, 0x01, 0x10, 0x00, 0x00, 0x00, 0xff, 0x00,
+	0x06, 0x00, 0x02, 0x09, 0x99,
+	0x08, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00,
+	0x10, 0x00, 0x40, 0xa0, 0x02, 0x80, 0x00, 0x12, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x08, 0x0a,
+			  0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x49, 0x13, 0x00, 0x00, 0xe0, 0x00, 0x0c,
+			  0x00, 0x52, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+			  0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x10, 0x08,
+			  0x03, 0x00, 0x00, 0x00, 0x00, 0x20, 0x10, 0x06,
+			  0xf7, 0xee, 0x1c, 0x1c, 0xe9, 0xfc, 0x10, 0x80,
+	0x10, 0x40, 0x40, 0x80, 0x00, 0x05, 0x35, 0x5e, 0x78, 0x8b, 0x99,
+			  0xa4, 0xae, 0xb5, 0xbc, 0xc1, 0xc6, 0xc9, 0xcc,
+			  0xcf, 0xd0, 0x00, 0x11, 0x22, 0x32, 0x43, 0x54,
+			  0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3, 0xd2,
+			  0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32, 0x43,
+			  0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3,
+			  0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32,
+			  0x43, 0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3,
+	0x10, 0x80, 0x1b, 0xc3, 0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x82,
+			  0x02, 0xe4, 0x01, 0x40, 0x01, 0xf0, 0x00, 0x40,
+			  0x01, 0xf0, 0x00,
+	0x02, 0x00, 0x11, 0x3c, 0x50, 0x8c, 0x3c, 0x50, 0x00, 0x00, 0x00,
+			  0x00, 0x78, 0x3f, 0x3f, 0x06, 0xf2, 0x8f, 0xf0,
+			  0x40,
+	0x10, 0x1a, 0x01, 0x03,
+	0x10, 0x00, 0x01, 0xaf,
+	0x10, 0x85, 0x08, 0x00, 0x00, 0x3f, 0x01, 0x00, 0x00, 0xef, 0x00,
+	0x10, 0x1b, 0x02, 0x3b, 0x01,
+	0x10, 0x11, 0x08, 0x61, 0x00, 0xe0, 0x00, 0x49, 0x00, 0xa8, 0x00,
+	0x10, 0x1d, 0x06, 0xe0, 0x00, 0x0c, 0x00, 0x52, 0x00,
+	0x10, 0x1d, 0x02, 0x00, 0x00,
+	0x10, 0x0e, 0x01, 0x08,
+	0x10, 0x41, 0x11, 0x00, 0x0d, 0x36, 0x4e, 0x60, 0x6f, 0x7b, 0x86,
+			  0x90, 0x98, 0xa1, 0xa9, 0xb1, 0xb7, 0xbe, 0xc4,
+			  0xcb,
+	0x10, 0x03, 0x01, 0x00,
+	0x10, 0x0f, 0x02, 0x0d, 0x0d,
+	0x10, 0x03, 0x01, 0x02,
+	0x10, 0x41, 0x11, 0x00, 0x0d, 0x36, 0x4e, 0x60, 0x6f, 0x7b, 0x86,
+			  0x90, 0x98, 0xa1, 0xa9, 0xb1, 0xb7, 0xbe, 0xc4,
+			  0xcb,
+	0x10, 0x0b, 0x01, 0x17,
+	0x10, 0x0d, 0x01, 0x10,
+	0x10, 0x0c, 0x01, 0x1e,
+	0x04, 0x06, 0x01, 0x03,
+	0x04, 0x04, 0x01, 0x00,
+	0, 0, 0
+};
+
+/* nw802 - Mustek Wcam 300 mini */
+static const u8 mustek_start[] = {
+	0x04, 0x06, 0x01, 0x04,
+	0x00, 0x00, 0x40, 0x10, 0x00, 0x00, 0x92, 0x03, 0x10, 0x00, 0x4d,
+			  0x0f, 0x1f, 0x00, 0x0d, 0x02, 0x01, 0x00, 0x19,
+			  0x00, 0x01, 0x00, 0x19, 0x00, 0x01, 0x00, 0x19,
+			  0x00, 0x01, 0x00, 0x19, 0x00, 0xce, 0x00, 0xf4,
+			  0x05, 0x3e, 0x00, 0x86, 0x00, 0x3e, 0x00, 0x86,
+			  0x00, 0x3e, 0x00, 0x86, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x56, 0x00, 0x9e, 0x00, 0x56, 0x00, 0x9e,
+			  0x00, 0x56, 0x00, 0x9e, 0x00, 0x01, 0x00, 0x01,
+	0x00, 0x40, 0x40, 0x00, 0x6e, 0x00, 0xb6, 0x00, 0x6e, 0x00, 0x78,
+			  0x04, 0x6e, 0x00, 0xb6, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x6e, 0x00, 0xb6, 0x00, 0x6e, 0x00, 0x78,
+			  0x04, 0x6e, 0x00, 0xb6, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0xca, 0x03, 0x46, 0x04, 0xca, 0x03, 0x46,
+			  0x04, 0x10, 0x00, 0x36, 0x00, 0xd2, 0x00, 0xee,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0xf0,
+			  0x00, 0x3e, 0x00, 0xaa, 0x00, 0x88, 0x00, 0x2e,
+	0x00, 0x80, 0x1f, 0xb4, 0x6f, 0x3f, 0x0f, 0x88, 0x20, 0x68, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0xa8, 0x08, 0x00, 0x11,
+			  0x00, 0x0c, 0x02, 0x01, 0x00, 0x16, 0x00, 0x94,
+			  0x00, 0x10, 0x06, 0xfc, 0x05, 0x0c, 0x06,
+	0x02, 0x00, 0x12, 0x78, 0xa0, 0x9e, 0x78, 0xa0, 0x00, 0x00, 0x00,
+			  0x00, 0xf0, 0x18, 0x0b, 0x06, 0x62, 0x82, 0xa0,
+			  0x40, 0x20,
+	0x03, 0x00, 0x03, 0x03, 0x00, 0x00,
+	0x04, 0x00, 0x07, 0x01, 0x10, 0x00, 0x00, 0x00, 0x21, 0x00,
+	0x06, 0x00, 0x02, 0x09, 0x99,
+	0x08, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00,
+	0x10, 0x00, 0x40, 0xa1, 0x02, 0x80, 0x00, 0x13, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x08, 0x0a,
+			  0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x49, 0x13, 0x00, 0x00, 0xc0, 0x00, 0x14,
+			  0x02, 0x00, 0x01, 0x00, 0x00, 0x20, 0x00, 0x00,
+			  0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x10, 0x08,
+			  0x03, 0x00, 0x00, 0x00, 0x00, 0x20, 0x10, 0x06,
+			  0xf7, 0xee, 0x1c, 0x1c, 0xe9, 0xfc, 0x10, 0x80,
+	0x10, 0x40, 0x40, 0x80, 0x00, 0x05, 0x35, 0x5e, 0x78, 0x8b, 0x99,
+			  0xa4, 0xae, 0xb5, 0xbc, 0xc1, 0xc6, 0xc9, 0xcc,
+			  0xcf, 0xd0, 0x00, 0x11, 0x22, 0x32, 0x43, 0x54,
+			  0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3, 0xd2,
+			  0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32, 0x43,
+			  0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3,
+			  0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32,
+			  0x43, 0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3,
+	0x10, 0x80, 0x1b, 0xc3, 0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x01, 0x82,
+			  0x02, 0xe4, 0x01, 0x40, 0x01, 0xf0, 0x00, 0x40,
+			  0x01, 0xf0, 0x00,
+	0x02, 0x00, 0x11, 0x3c, 0x50, 0x9e, 0x3c, 0x50, 0x00, 0x00, 0x00,
+			  0x00, 0x78, 0x3f, 0x10, 0x02, 0xf2, 0x8f, 0x78,
+			  0x40,
+	0x10, 0x1a, 0x01, 0x00,
+	0x10, 0x00, 0x01, 0xad,
+	0x00, 0x00, 0x01, 0x08,
+	0x10, 0x85, 0x08, 0x00, 0x00, 0x3f, 0x01, 0x00, 0x00, 0xef, 0x00,
+	0x10, 0x1b, 0x02, 0x00, 0x00,
+	0x10, 0x11, 0x08, 0x00, 0x00, 0x3f, 0x01, 0x00, 0x00, 0xef, 0x00,
+	0x10, 0x1d, 0x08, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20, 0x00, 0x20,
+	0x10, 0x0e, 0x01, 0x0f,
+	0x10, 0x41, 0x11, 0x00, 0x0f, 0x29, 0x4a, 0x64, 0x7a, 0x8c, 0x9e,
+			  0xad, 0xba, 0xc7, 0xd3, 0xde, 0xe8, 0xf1, 0xf9,
+			  0xff,
+	0x10, 0x0f, 0x02, 0x11, 0x11,
+	0x10, 0x03, 0x01, 0x0c,
+	0x10, 0x41, 0x11, 0x00, 0x0f, 0x29, 0x4a, 0x64, 0x7a, 0x8c, 0x9e,
+			  0xad, 0xba, 0xc7, 0xd3, 0xde, 0xe8, 0xf1, 0xf9,
+			  0xff,
+	0x10, 0x0b, 0x01, 0x1c,
+	0x10, 0x0d, 0x01, 0x1a,
+	0x10, 0x0c, 0x01, 0x34,
+	0x04, 0x05, 0x01, 0x61,
+	0x04, 0x04, 0x01, 0x40,
+	0x04, 0x06, 0x01, 0x03,
+	0, 0, 0
+};
+
+/* nw802 - Scope USB Microscope M2 (ProScope) (Hitachi HD49322BF) */
+static const u8 proscope_init[] = {
+	0x04, 0x05, 0x01, 0x21,
+	0x04, 0x04, 0x01, 0x01,
+	0, 0, 0
+};
+static const u8 proscope_start_1[] = {
+	0x04, 0x06, 0x01, 0x04,
+	0x00, 0x00, 0x40, 0x10, 0x01, 0x00, 0xf9, 0x02, 0x10, 0x00, 0x04,
+			  0x0f, 0x1f, 0x00, 0x0d, 0x02, 0x01, 0x00, 0x19,
+			  0x00, 0x01, 0x00, 0x19, 0x00, 0x01, 0x00, 0x19,
+			  0x00, 0x08, 0x00, 0x17, 0x00, 0xce, 0x00, 0xf4,
+			  0x05, 0x3e, 0x00, 0x86, 0x00, 0x3e, 0x00, 0x86,
+			  0x00, 0xce, 0x00, 0xf8, 0x03, 0x3e, 0x00, 0x86,
+			  0x00, 0x56, 0x00, 0x9e, 0x00, 0x56, 0x00, 0x9e,
+			  0x00, 0x56, 0x00, 0x9e, 0x00, 0x01, 0x00, 0x01,
+	0x00, 0x40, 0x40, 0x00, 0x6e, 0x00, 0xb6, 0x00, 0x6e, 0x00, 0xb6,
+			  0x00, 0x6e, 0x00, 0xb6, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x6e, 0x00, 0xb6, 0x00, 0x6e, 0x00, 0x78,
+			  0x04, 0x6e, 0x00, 0xb6, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0xf6, 0x03, 0x34, 0x04, 0xf6, 0x03, 0x34,
+			  0x04, 0x10, 0x00, 0x36, 0x00, 0xd2, 0x00, 0xee,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0xe8,
+			  0x00, 0x3e, 0x00, 0xaa, 0x00, 0x88, 0x00, 0x2e,
+	0x00, 0x80, 0x1f, 0xb4, 0x6f, 0x1f, 0x0f, 0x08, 0x20, 0xa8, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0xa8, 0x08, 0x00, 0x11,
+			  0x00, 0x0c, 0x02, 0x01, 0x00, 0x19, 0x00, 0x94,
+			  0x00, 0x10, 0x06, 0x10, 0x00, 0x36, 0x00,
+	0x02, 0x00, 0x12, 0x78, 0xa0, 0x9e, 0x78, 0xa0, 0x00, 0x00, 0x00,
+			  0x00, 0xf0, 0x18, 0x0b, 0x06, 0x62, 0x82, 0xa0,
+			  0x40, 0x20,
+	0x03, 0x00, 0x03, 0x03, 0x00, 0x00,
+	0x04, 0x00, 0x07, 0x01, 0x10, 0x00, 0x00, 0x00, 0x21, 0x00,
+	0x06, 0x00, 0x02, 0x09, 0x99,
+	0x08, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00,
+	0x10, 0x00, 0x40, 0xad, 0x02, 0x80, 0x00, 0x12, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x10, 0x1f, 0x10, 0x08, 0x0a,
+			  0x0a, 0x51, 0x00, 0xf1, 0x00, 0x3c, 0x00, 0xb4,
+			  0x00, 0x49, 0x13, 0x00, 0x00, 0x8c, 0x04, 0x01,
+			  0x20, 0x02, 0x00, 0x03, 0x00, 0x20, 0x00, 0x00,
+			  0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x10, 0x08,
+			  0x03, 0x00, 0x00, 0x00, 0x00, 0x20, 0x10, 0x06,
+			  0xf7, 0xee, 0x1c, 0x1c, 0xe9, 0xfc, 0x10, 0x80,
+	0x10, 0x40, 0x40, 0x80, 0x00, 0x2d, 0x46, 0x58, 0x67, 0x74, 0x7f,
+			  0x88, 0x94, 0x9d, 0xa6, 0xae, 0xb5, 0xbd, 0xc4,
+			  0xcb, 0xd1, 0x00, 0x11, 0x22, 0x32, 0x43, 0x54,
+			  0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3, 0xd2,
+			  0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32, 0x43,
+			  0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3,
+			  0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32,
+			  0x43, 0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3,
+	0x10, 0x80, 0x1b, 0xc3, 0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x00, 0x3f,
+			  0x01, 0x00, 0x00, 0xef, 0x00, 0x09, 0x05, 0x82,
+			  0x02, 0xe4, 0x01, 0x40, 0x01, 0xf0, 0x00, 0x40,
+			  0x01, 0xf0, 0x00,
+	0, 0, 0
+};
+static const u8 proscope_start_qvga[] = {
+	0x02, 0x00, 0x11, 0x3c, 0x50, 0x9e, 0x3c, 0x50, 0x00, 0x00, 0x00,
+			  0x00, 0x78, 0x3f, 0x10, 0x02, 0xf2, 0x8f, 0x78,
+			  0x40,
+	0x10, 0x1a, 0x01, 0x06,
+	0x00, 0x03, 0x02, 0xf9, 0x02,
+	0x10, 0x85, 0x08, 0x00, 0x00, 0x3f, 0x01, 0x00, 0x00, 0xef, 0x00,
+	0x10, 0x1b, 0x02, 0x00, 0x00,
+	0x10, 0x11, 0x08, 0x00, 0x00, 0x3f, 0x01, 0x00, 0x00, 0xef, 0x00,
+	0x10, 0x1d, 0x08, 0xc0, 0x0d, 0x01, 0x20, 0x02, 0xe8, 0x03, 0x00,
+	0x10, 0x0e, 0x01, 0x10,
+	0, 0, 0
+};
+static const u8 proscope_start_vga[] = {
+	0x00, 0x03, 0x02, 0xf9, 0x02,
+	0x10, 0x85, 0x08, 0x00, 0x00, 0x7f, 0x02, 0x00, 0x00, 0xdf, 0x01,
+	0x02, 0x00, 0x11, 0x78, 0xa0, 0x8c, 0x78, 0xa0, 0x00, 0x00, 0x00,
+			  0x00, 0xf0, 0x16, 0x00, 0x00, 0x82, 0x84, 0x00,
+			  0x80,
+	0x10, 0x1a, 0x01, 0x06,
+	0x10, 0x00, 0x01, 0xa1,
+	0x10, 0x1b, 0x02, 0x00, 0x00,
+	0x10, 0x1d, 0x08, 0xc0, 0x0d, 0x01, 0x20, 0x02, 0xe8, 0x03, 0x00,
+	0x10, 0x11, 0x08, 0x00, 0x00, 0x7f, 0x02, 0x00, 0x00, 0xdf, 0x01,
+	0x10, 0x0e, 0x01, 0x10,
+	0x10, 0x41, 0x11, 0x00, 0x10, 0x51, 0x6e, 0x83, 0x93, 0xa1, 0xae,
+			  0xb9, 0xc3, 0xcc, 0xd4, 0xdd, 0xe4, 0xeb, 0xf2,
+			  0xf9,
+	0x10, 0x03, 0x01, 0x00,
+	0, 0, 0
+};
+static const u8 proscope_start_2[] = {
+	0x10, 0x0f, 0x02, 0x0c, 0x0c,
+	0x10, 0x03, 0x01, 0x0c,
+	0x10, 0x41, 0x11, 0x00, 0x10, 0x51, 0x6e, 0x83, 0x93, 0xa1, 0xae,
+			  0xb9, 0xc3, 0xcc, 0xd4, 0xdd, 0xe4, 0xeb, 0xf2,
+			  0xf9,
+	0x10, 0x0b, 0x01, 0x0b,
+	0x10, 0x0d, 0x01, 0x10,
+	0x10, 0x0c, 0x01, 0x1b,
+	0x04, 0x06, 0x01, 0x03,
+	0x04, 0x05, 0x01, 0x21,
+	0x04, 0x04, 0x01, 0x00,
+	0, 0, 0
+};
+
+/* nw800 - hv7121b? (seems pas106) - Divio Chicony TwinkleCam */
+static const u8 twinkle_start[] = {
+	0x04, 0x06, 0x01, 0x44,
+	0x04, 0x06, 0x01, 0x00,
+	0x00, 0x00, 0x40, 0x14, 0x83, 0x00, 0xba, 0x01, 0x10, 0x00, 0x4f,
+			  0xef, 0x00, 0x00, 0x60, 0x00, 0x01, 0x00, 0x19,
+			  0x00, 0x01, 0x00, 0x19, 0x00, 0x01, 0x00, 0x19,
+			  0x00, 0x01, 0x00, 0x19, 0x00, 0x06, 0x00, 0xfc,
+			  0x01, 0x3e, 0x00, 0x86, 0x00, 0x3e, 0x00, 0x86,
+			  0x00, 0x3e, 0x00, 0x86, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x56, 0x00, 0x9e, 0x00, 0x56, 0x00, 0x9e,
+			  0x00, 0x56, 0x00, 0x9e, 0x00, 0x01, 0x00, 0x01,
+	0x00, 0x40, 0x40, 0x00, 0x6e, 0x00, 0xb6, 0x00, 0x6e, 0x00, 0x78,
+			  0x04, 0x6e, 0x00, 0xb6, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x6e, 0x00, 0xb6, 0x00, 0x6e, 0x00, 0x78,
+			  0x04, 0x6e, 0x00, 0xb6, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0xca, 0x03, 0x46, 0x04, 0xca, 0x03, 0x46,
+			  0x04, 0x10, 0x00, 0x36, 0x00, 0xd2, 0x00, 0xee,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x00, 0xf0,
+			  0x00, 0x3e, 0x00, 0xaa, 0x00, 0x88, 0x00, 0x2e,
+	0x00, 0x80, 0x1f, 0xb8, 0x48, 0x0f, 0x04, 0x88, 0x14, 0x68, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0xa8, 0x01, 0x00, 0x03,
+			  0x00, 0x24, 0x01, 0x01, 0x00, 0x16, 0x00, 0x04,
+			  0x00, 0x4b, 0x00, 0x76, 0x00, 0x86, 0x00,
+	0x02, 0x00, 0x12, 0x78, 0xa0, 0x9e, 0x78, 0xa0, 0x00, 0x00, 0x00,
+			  0x00, 0xf0, 0x18, 0x0b, 0x06, 0x62, 0x82, 0xa0,
+			  0x40, 0x20,
+	0x03, 0x00, 0x03, 0x03, 0x00, 0x00,
+	0x04, 0x00, 0x07, 0x01, 0x10, 0x00, 0x00, 0x00, 0x61, 0x00,
+	0x05, 0x00, 0x06, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x06, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00,
+	0x08, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00,
+	0x10, 0x00, 0x40, 0x80, 0x02, 0x20, 0x00, 0x11, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x08, 0x08,
+			  0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x49, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+			  0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x10, 0x08,
+			  0x03, 0x00, 0x00, 0x10, 0x00, 0x20, 0x10, 0x06,
+			  0xf7, 0xee, 0x1c, 0x1c, 0xe9, 0xfc, 0x00, 0x80,
+	0x10, 0x40, 0x40, 0x80, 0x00, 0x05, 0x35, 0x5e, 0x78, 0x8b, 0x99,
+			  0xa4, 0xae, 0xb5, 0xbc, 0xc1, 0xc6, 0xc9, 0xcc,
+			  0xcf, 0xd0, 0x00, 0x11, 0x22, 0x32, 0x43, 0x54,
+			  0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3, 0xd2,
+			  0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32, 0x43,
+			  0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3,
+			  0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32,
+			  0x43, 0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3,
+	0x10, 0x80, 0x1d, 0xc3, 0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x62,
+			  0x01, 0x24, 0x01, 0x62, 0x01, 0x24, 0x01, 0x20,
+			  0x01, 0x60, 0x01, 0x00, 0x00,
+
+	0x10, 0x85, 0x08, 0x00, 0x00, 0x5f, 0x01, 0x00, 0x00, 0x1f, 0x01,
+	0x04, 0x04, 0x01, 0x10,
+	0x04, 0x04, 0x01, 0x00,
+	0x04, 0x05, 0x01, 0x61,
+	0x04, 0x04, 0x01, 0x01,
+	I2C0, 0x40, 0x0c, 0x02, 0x0c, 0x12, 0x07, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x0a,
+	I2C0, 0x40, 0x02, 0x11, 0x06,
+	I2C0, 0x40, 0x02, 0x14, 0x00,
+	I2C0, 0x40, 0x02, 0x13, 0x01,		/* i2c end */
+	I2C0, 0x40, 0x02, 0x07, 0x01,
+	0x02, 0x00, 0x11, 0x48, 0x58, 0x9e, 0x48, 0x58, 0x00, 0x00, 0x00,
+			  0x00, 0x84, 0x36, 0x05, 0x01, 0xf2, 0x86, 0x65,
+			  0x40,
+	I2C0, 0x40, 0x02, 0x02, 0x0c,
+	I2C0, 0x40, 0x02, 0x13, 0x01,
+	0x10, 0x00, 0x01, 0x01,
+	0x10, 0x8f, 0x0c, 0x62, 0x01, 0x24, 0x01, 0x62, 0x01, 0x24, 0x01,
+			  0x20, 0x01, 0x60, 0x01,
+	I2C0, 0x40, 0x02, 0x05, 0x0f,
+	I2C0, 0x40, 0x02, 0x13, 0x01,
+	I2C0, 0x40, 0x08, 0x08, 0x04, 0x0b, 0x01, 0x01, 0x02, 0x00, 0x17,
+	I2C0, 0x40, 0x03, 0x12, 0x00, 0x01,
+	0x10, 0x11, 0x08, 0x00, 0x00, 0x5f, 0x01, 0x00, 0x00, 0x1f, 0x01,
+	I2C0, 0x40, 0x02, 0x12, 0x00,
+	I2C0, 0x40, 0x02, 0x0e, 0x00,
+	I2C0, 0x40, 0x02, 0x11, 0x06,
+	0x10, 0x41, 0x11, 0x00, 0x17, 0x3f, 0x69, 0x7b, 0x8c, 0x9a, 0xa7,
+			  0xb3, 0xbf, 0xc9, 0xd3, 0xdd, 0xe6, 0xef, 0xf7,
+			  0xf9,
+	0x10, 0x03, 0x01, 0x00,
+	0x10, 0x0f, 0x02, 0x0c, 0x0c,
+	0x10, 0x03, 0x01, 0x06,
+	0x10, 0x41, 0x11, 0x00, 0x17, 0x3f, 0x69, 0x7b, 0x8c, 0x9a, 0xa7,
+			  0xb3, 0xbf, 0xc9, 0xd3, 0xdd, 0xe6, 0xef, 0xf7,
+			  0xf9,
+	0x10, 0x0b, 0x01, 0x19,
+	0x10, 0x0d, 0x01, 0x10,
+	0x10, 0x0c, 0x01, 0x0d,
+	0x04, 0x06, 0x01, 0x03,
+	0x04, 0x05, 0x01, 0x61,
+	0x04, 0x04, 0x01, 0x41,
+	0, 0, 0
+};
+
+/* nw802 dvc-v6 */
+static const u8 dvcv6_start[] = {
+	0x04, 0x06, 0x01, 0x06,
+	0x00, 0x00, 0x40, 0x54, 0x96, 0x98, 0xf9, 0x02, 0x18, 0x00, 0x4c,
+			  0x0f, 0x1f, 0x00, 0x0d, 0x02, 0x01, 0x00, 0x19,
+			  0x00, 0x01, 0x00, 0x19, 0x00, 0x01, 0x00, 0x19,
+			  0x00, 0x0b, 0x00, 0x1b, 0x00, 0xc8, 0x00, 0xf4,
+			  0x05, 0xb4, 0x00, 0xcc, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0xa2, 0x00, 0xc6, 0x00, 0x60, 0x00, 0xc6,
+			  0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+	0x00, 0x40, 0x40, 0x00, 0xae, 0x00, 0xd2, 0x00, 0xae, 0x00, 0xd2,
+			  0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0xa8, 0x00, 0xc0, 0x00, 0x66, 0x00, 0xc0,
+			  0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01,
+			  0x00, 0x0a, 0x00, 0x54, 0x00, 0x0a, 0x00, 0x54,
+			  0x00, 0x10, 0x00, 0x36, 0x00, 0xd2, 0x00, 0xee,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf6,
+			  0x00, 0x5d, 0x00, 0xc7, 0x00, 0x7e, 0x00, 0x30,
+	0x00, 0x80, 0x1f, 0x98, 0x43, 0x3f, 0x0d, 0x88, 0x20, 0x80, 0x3f,
+			  0x47, 0xaf, 0x00, 0x00, 0xa8, 0x08, 0x00, 0x11,
+			  0x00, 0x0c, 0x02, 0x0c, 0x00, 0x1c, 0x00, 0x94,
+			  0x00, 0x10, 0x06, 0x24, 0x00, 0x4a, 0x00,
+	0x02, 0x00, 0x12, 0x78, 0xa0, 0x9e, 0x78, 0xa0, 0x00, 0x00, 0x00,
+			  0x00, 0xf0, 0x18, 0x0b, 0x06, 0x62, 0x82, 0xa0,
+			  0x40, 0x20,
+	0x03, 0x00, 0x03, 0x03, 0x00, 0x00,
+	0x04, 0x00, 0x07, 0x01, 0x10, 0x00, 0x00, 0x00, 0xff, 0x00,
+	0x06, 0x00, 0x02, 0x09, 0x99,
+	0x08, 0x00, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00,
+	0x10, 0x00, 0x40, 0xa0, 0x02, 0x80, 0x00, 0x12, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x08, 0x0a,
+			  0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			  0x00, 0x49, 0x13, 0x00, 0x00, 0xe0, 0x00, 0x0c,
+			  0x00, 0x52, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+			  0x00, 0x20, 0x00, 0x00, 0x00, 0x20, 0x10, 0x08,
+			  0x03, 0x00, 0x00, 0x00, 0x00, 0x20, 0x10, 0x06,
+			  0xf7, 0xee, 0x1c, 0x1c, 0xe9, 0xfc, 0x10, 0x80,
+	0x10, 0x40, 0x40, 0x80, 0x00, 0x05, 0x35, 0x5e, 0x78, 0x8b, 0x99,
+			  0xa4, 0xae, 0xb5, 0xbc, 0xc1, 0xc6, 0xc9, 0xcc,
+			  0xcf, 0xd0, 0x00, 0x11, 0x22, 0x32, 0x43, 0x54,
+			  0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3, 0xd2,
+			  0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32, 0x43,
+			  0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3, 0xc3,
+			  0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x11, 0x22, 0x32,
+			  0x43, 0x54, 0x64, 0x74, 0x84, 0x94, 0xa4, 0xb3,
+	0x10, 0x80, 0x1b, 0xc3, 0xd2, 0xe2, 0xf1, 0xff, 0x00, 0x00, 0x00,
+			  0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x05, 0x82,
+			  0x02, 0xe4, 0x01, 0x40, 0x01, 0xf0, 0x00, 0x40,
+			  0x01, 0xf0, 0x00,
+	0x00, 0x03, 0x02, 0x94, 0x03,
+	0x00, 0x1d, 0x04, 0x0a, 0x01, 0x28, 0x07,
+	0x00, 0x7b, 0x02, 0xe0, 0x00,
+	0x10, 0x8d, 0x01, 0x00,
+	0x00, 0x09, 0x04, 0x1e, 0x00, 0x0c, 0x02,
+	0x00, 0x91, 0x02, 0x0b, 0x02,
+	0x10, 0x00, 0x01, 0xaf,
+	0x02, 0x00, 0x11, 0x3c, 0x50, 0x8f, 0x3c, 0x50, 0x00, 0x00, 0x00,
+			  0x00, 0x78, 0x3f, 0x3f, 0x06, 0xf2, 0x8f, 0xf0,
+			  0x40,
+	0x10, 0x1a, 0x01, 0x02,
+	0x10, 0x00, 0x01, 0xaf,
+	0x10, 0x85, 0x08, 0x00, 0x00, 0x3f, 0x01, 0x00, 0x00, 0xef, 0x00,
+	0x10, 0x1b, 0x02, 0x07, 0x01,
+	0x10, 0x11, 0x08, 0x61, 0x00, 0xe0, 0x00, 0x49, 0x00, 0xa8, 0x00,
+	0x10, 0x1f, 0x06, 0x01, 0x20, 0x02, 0xe8, 0x03, 0x00,
+	0x10, 0x1d, 0x02, 0x40, 0x06,
+	0x10, 0x0e, 0x01, 0x08,
+	0x10, 0x41, 0x11, 0x00, 0x0f, 0x54, 0x6f, 0x82, 0x91, 0x9f, 0xaa,
+			  0xb4, 0xbd, 0xc5, 0xcd, 0xd5, 0xdb, 0xdc, 0xdc,
+			  0xdc,
+	0x10, 0x03, 0x01, 0x00,
+	0x10, 0x0f, 0x02, 0x12, 0x12,
+	0x10, 0x03, 0x01, 0x11,
+	0x10, 0x41, 0x11, 0x00, 0x0f, 0x54, 0x6f, 0x82, 0x91, 0x9f, 0xaa,
+			  0xb4, 0xbd, 0xc5, 0xcd, 0xd5, 0xdb, 0xdc, 0xdc,
+			  0xdc,
+	0x10, 0x0b, 0x01, 0x16,
+	0x10, 0x0d, 0x01, 0x10,
+	0x10, 0x0c, 0x01, 0x1a,
+	0x04, 0x06, 0x01, 0x03,
+	0x04, 0x04, 0x01, 0x00,
+};
+
+static const u8 *webcam_start[] = {
+	[Generic800] = nw800_start,
+	[SpaceCam] = spacecam_start,
+	[SpaceCam2] = spacecam2_start,
+	[Cvideopro] = cvideopro_start,
+	[Dlink350c] = dlink_start,
+	[DS3303u] = ds3303_start,
+	[Kr651us] = kr651_start_1,
+	[Kritter] = kritter_start,
+	[Mustek300] = mustek_start,
+	[Proscope] = proscope_start_1,
+	[Twinkle] = twinkle_start,
+	[DvcV6] = dvcv6_start,
+	[P35u] = nw801_start_1,
+	[Generic802] = nw802_start,
+};
+
+/* -- write a register -- */
+static void reg_w(struct gspca_dev *gspca_dev,
+			u16 index,
+			const u8 *data,
+			int len)
+{
+	struct usb_device *dev = gspca_dev->dev;
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	if (len == 1)
+		gspca_dbg(gspca_dev, D_USBO, "SET 00 0000 %04x %02x\n",
+			  index, *data);
+	else
+		gspca_dbg(gspca_dev, D_USBO, "SET 00 0000 %04x %02x %02x ...\n",
+			  index, *data, data[1]);
+	memcpy(gspca_dev->usb_buf, data, len);
+	ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+			0x00,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0x00,		/* value */
+			index,
+			gspca_dev->usb_buf,
+			len,
+			500);
+	if (ret < 0) {
+		pr_err("reg_w err %d\n", ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+/* -- read registers in usb_buf -- */
+static void reg_r(struct gspca_dev *gspca_dev,
+			u16 index,
+			int len)
+{
+	struct usb_device *dev = gspca_dev->dev;
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+			0x00,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0x00, index,
+			gspca_dev->usb_buf, len, 500);
+	if (ret < 0) {
+		pr_err("reg_r err %d\n", ret);
+		gspca_dev->usb_err = ret;
+		return;
+	}
+	if (len == 1)
+		gspca_dbg(gspca_dev, D_USBI, "GET 00 0000 %04x %02x\n",
+			  index, gspca_dev->usb_buf[0]);
+	else
+		gspca_dbg(gspca_dev, D_USBI, "GET 00 0000 %04x %02x %02x ..\n",
+			  index, gspca_dev->usb_buf[0],
+			  gspca_dev->usb_buf[1]);
+}
+
+static void i2c_w(struct gspca_dev *gspca_dev,
+			u8 i2c_addr,
+			const u8 *data,
+			int len)
+{
+	u8 val[2];
+	int i;
+
+	reg_w(gspca_dev, 0x0600, data + 1, len - 1);
+	reg_w(gspca_dev, 0x0600, data, len);
+	val[0] = len;
+	val[1] = i2c_addr;
+	reg_w(gspca_dev, 0x0502, val, 2);
+	val[0] = 0x01;
+	reg_w(gspca_dev, 0x0501, val, 1);
+	for (i = 5; --i >= 0; ) {
+		msleep(4);
+		reg_r(gspca_dev, 0x0505, 1);
+		if (gspca_dev->usb_err < 0)
+			return;
+		if (gspca_dev->usb_buf[0] == 0)
+			return;
+	}
+	gspca_dev->usb_err = -ETIME;
+}
+
+static void reg_w_buf(struct gspca_dev *gspca_dev,
+			const u8 *cmd)
+{
+	u16 reg;
+	int len;
+
+	for (;;) {
+		reg = *cmd++ << 8;
+		reg += *cmd++;
+		len = *cmd++;
+		if (len == 0)
+			break;
+		if (cmd[-3] != I2C0)
+			reg_w(gspca_dev, reg, cmd, len);
+		else
+			i2c_w(gspca_dev, reg, cmd, len);
+		cmd += len;
+	}
+}
+
+static int swap_bits(int v)
+{
+	int r, i;
+
+	r = 0;
+	for (i = 0; i < 8; i++) {
+		r <<= 1;
+		if (v & 1)
+			r++;
+		v >>= 1;
+	}
+	return r;
+}
+
+static void setgain(struct gspca_dev *gspca_dev, u8 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 v[2];
+
+	switch (sd->webcam) {
+	case P35u:
+		reg_w(gspca_dev, 0x1026, &val, 1);
+		break;
+	case Kr651us:
+		/* 0 - 253 */
+		val = swap_bits(val);
+		v[0] = val << 3;
+		v[1] = val >> 5;
+		reg_w(gspca_dev, 0x101d, v, 2);	/* SIF reg0/1 (AGC) */
+		break;
+	}
+}
+
+static void setexposure(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 v[2];
+
+	switch (sd->webcam) {
+	case P35u:
+		v[0] = ((9 - val) << 3) | 0x01;
+		reg_w(gspca_dev, 0x1019, v, 1);
+		break;
+	case Cvideopro:
+	case DvcV6:
+	case Kritter:
+	case Kr651us:
+		v[0] = val;
+		v[1] = val >> 8;
+		reg_w(gspca_dev, 0x101b, v, 2);
+		break;
+	}
+}
+
+static void setautogain(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int w, h;
+
+	if (!val) {
+		sd->ag_cnt = -1;
+		return;
+	}
+	sd->ag_cnt = AG_CNT_START;
+
+	reg_r(gspca_dev, 0x1004, 1);
+	if (gspca_dev->usb_buf[0] & 0x04) {	/* if AE_FULL_FRM */
+		sd->ae_res = gspca_dev->pixfmt.width * gspca_dev->pixfmt.height;
+	} else {				/* get the AE window size */
+		reg_r(gspca_dev, 0x1011, 8);
+		w = (gspca_dev->usb_buf[1] << 8) + gspca_dev->usb_buf[0]
+		  - (gspca_dev->usb_buf[3] << 8) - gspca_dev->usb_buf[2];
+		h = (gspca_dev->usb_buf[5] << 8) + gspca_dev->usb_buf[4]
+		  - (gspca_dev->usb_buf[7] << 8) - gspca_dev->usb_buf[6];
+		sd->ae_res = h * w;
+		if (sd->ae_res == 0)
+			sd->ae_res = gspca_dev->pixfmt.width *
+					gspca_dev->pixfmt.height;
+	}
+}
+
+static int nw802_test_reg(struct gspca_dev *gspca_dev,
+			u16 index,
+			u8 value)
+{
+	/* write the value */
+	reg_w(gspca_dev, index, &value, 1);
+
+	/* read it */
+	reg_r(gspca_dev, index, 1);
+
+	return gspca_dev->usb_buf[0] == value;
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if ((unsigned) webcam >= NWEBCAMS)
+		webcam = 0;
+	sd->webcam = webcam;
+	gspca_dev->cam.needs_full_bandwidth = 1;
+	sd->ag_cnt = -1;
+
+	/*
+	 * Autodetect sequence inspired from some log.
+	 * We try to detect what registers exist or not.
+	 * If 0x0500 does not exist => NW802
+	 * If it does, test 0x109b. If it doesn't exist,
+	 * then it's a NW801. Else, a NW800
+	 * If a et31x110 (nw800 and 06a5:d800)
+	 *	get the sensor ID
+	 */
+	if (!nw802_test_reg(gspca_dev, 0x0500, 0x55)) {
+		sd->bridge = BRIDGE_NW802;
+		if (sd->webcam == Generic800)
+			sd->webcam = Generic802;
+	} else if (!nw802_test_reg(gspca_dev, 0x109b, 0xaa)) {
+		sd->bridge = BRIDGE_NW801;
+		if (sd->webcam == Generic800)
+			sd->webcam = P35u;
+	} else if (id->idVendor == 0x06a5 && id->idProduct == 0xd800) {
+		reg_r(gspca_dev, 0x0403, 1);		/* GPIO */
+		gspca_dbg(gspca_dev, D_PROBE, "et31x110 sensor type %02x\n",
+			  gspca_dev->usb_buf[0]);
+		switch (gspca_dev->usb_buf[0] >> 1) {
+		case 0x00:				/* ?? */
+			if (sd->webcam == Generic800)
+				sd->webcam = SpaceCam;
+			break;
+		case 0x01:				/* Hynix? */
+			if (sd->webcam == Generic800)
+				sd->webcam = Twinkle;
+			break;
+		case 0x0a:				/* Pixart */
+			if (sd->webcam == Generic800)
+				sd->webcam = SpaceCam2;
+			break;
+		}
+	}
+	if (webcam_chip[sd->webcam] != sd->bridge) {
+		pr_err("Bad webcam type %d for NW80%d\n",
+		       sd->webcam, sd->bridge);
+		gspca_dev->usb_err = -ENODEV;
+		return gspca_dev->usb_err;
+	}
+	gspca_dbg(gspca_dev, D_PROBE, "Bridge nw80%d - type: %d\n",
+		  sd->bridge, sd->webcam);
+
+	if (sd->bridge == BRIDGE_NW800) {
+		switch (sd->webcam) {
+		case DS3303u:
+			gspca_dev->cam.cam_mode = cif_mode;	/* qvga */
+			break;
+		default:
+			gspca_dev->cam.cam_mode = &cif_mode[1];	/* cif */
+			break;
+		}
+		gspca_dev->cam.nmodes = 1;
+	} else {
+		gspca_dev->cam.cam_mode = vga_mode;
+		switch (sd->webcam) {
+		case Kr651us:
+		case Proscope:
+		case P35u:
+			gspca_dev->cam.nmodes = ARRAY_SIZE(vga_mode);
+			break;
+		default:
+			gspca_dev->cam.nmodes = 1;	/* qvga only */
+			break;
+		}
+	}
+
+	return gspca_dev->usb_err;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	switch (sd->bridge) {
+	case BRIDGE_NW800:
+		switch (sd->webcam) {
+		case SpaceCam:
+			reg_w_buf(gspca_dev, spacecam_init);
+			break;
+		default:
+			reg_w_buf(gspca_dev, nw800_init);
+			break;
+		}
+		break;
+	default:
+		switch (sd->webcam) {
+		case Mustek300:
+		case P35u:
+		case Proscope:
+			reg_w_buf(gspca_dev, proscope_init);
+			break;
+		}
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+/* -- start the camera -- */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	const u8 *cmd;
+
+	cmd = webcam_start[sd->webcam];
+	reg_w_buf(gspca_dev, cmd);
+	switch (sd->webcam) {
+	case P35u:
+		if (gspca_dev->pixfmt.width == 320)
+			reg_w_buf(gspca_dev, nw801_start_qvga);
+		else
+			reg_w_buf(gspca_dev, nw801_start_vga);
+		reg_w_buf(gspca_dev, nw801_start_2);
+		break;
+	case Kr651us:
+		if (gspca_dev->pixfmt.width == 320)
+			reg_w_buf(gspca_dev, kr651_start_qvga);
+		else
+			reg_w_buf(gspca_dev, kr651_start_vga);
+		reg_w_buf(gspca_dev, kr651_start_2);
+		break;
+	case Proscope:
+		if (gspca_dev->pixfmt.width == 320)
+			reg_w_buf(gspca_dev, proscope_start_qvga);
+		else
+			reg_w_buf(gspca_dev, proscope_start_vga);
+		reg_w_buf(gspca_dev, proscope_start_2);
+		break;
+	}
+
+	sd->exp_too_high_cnt = 0;
+	sd->exp_too_low_cnt = 0;
+	return gspca_dev->usb_err;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 value;
+
+	/* 'go' off */
+	if (sd->bridge != BRIDGE_NW801) {
+		value = 0x02;
+		reg_w(gspca_dev, 0x0406, &value, 1);
+	}
+
+	/* LED off */
+	switch (sd->webcam) {
+	case Cvideopro:
+	case Kr651us:
+	case DvcV6:
+	case Kritter:
+		value = 0xff;
+		break;
+	case Dlink350c:
+		value = 0x21;
+		break;
+	case SpaceCam:
+	case SpaceCam2:
+	case Proscope:
+	case Twinkle:
+		value = 0x01;
+		break;
+	default:
+		return;
+	}
+	reg_w(gspca_dev, 0x0404, &value, 1);
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	/*
+	 * frame header = '00 00 hh ww ss xx ff ff'
+	 * with:
+	 *	- 'hh': height / 4
+	 *	- 'ww': width / 4
+	 *	- 'ss': frame sequence number c0..dd
+	 */
+	if (data[0] == 0x00 && data[1] == 0x00
+	 && data[6] == 0xff && data[7] == 0xff) {
+		gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
+		gspca_frame_add(gspca_dev, FIRST_PACKET, data + 8, len - 8);
+	} else {
+		gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+	}
+}
+
+static void do_autogain(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int luma;
+
+	if (sd->ag_cnt < 0)
+		return;
+	if (--sd->ag_cnt >= 0)
+		return;
+	sd->ag_cnt = AG_CNT_START;
+
+	/* get the average luma */
+	reg_r(gspca_dev, sd->bridge == BRIDGE_NW801 ? 0x080d : 0x080c, 4);
+	luma = (gspca_dev->usb_buf[3] << 24) + (gspca_dev->usb_buf[2] << 16)
+		+ (gspca_dev->usb_buf[1] << 8) + gspca_dev->usb_buf[0];
+	luma /= sd->ae_res;
+
+	switch (sd->webcam) {
+	case P35u:
+		gspca_coarse_grained_expo_autogain(gspca_dev, luma, 100, 5);
+		break;
+	default:
+		gspca_expo_autogain(gspca_dev, luma, 100, 5, 230, 0);
+		break;
+	}
+}
+
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	/* autogain/gain/exposure control cluster */
+	case V4L2_CID_AUTOGAIN:
+		if (ctrl->is_new)
+			setautogain(gspca_dev, ctrl->val);
+		if (!ctrl->val) {
+			if (gspca_dev->gain->is_new)
+				setgain(gspca_dev, gspca_dev->gain->val);
+			if (gspca_dev->exposure->is_new)
+				setexposure(gspca_dev,
+					    gspca_dev->exposure->val);
+		}
+		break;
+	/* Some webcams only have exposure, so handle that separately from the
+	   autogain/gain/exposure cluster in the previous case. */
+	case V4L2_CID_EXPOSURE:
+		setexposure(gspca_dev, gspca_dev->exposure->val);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *)gspca_dev;
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 3);
+	switch (sd->webcam) {
+	case P35u:
+		gspca_dev->autogain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
+		/* For P35u choose coarse expo auto gain function gain minimum,
+		 * to avoid a large settings jump the first auto adjustment */
+		gspca_dev->gain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_GAIN, 0, 127, 1, 127 / 5 * 2);
+		gspca_dev->exposure = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_EXPOSURE, 0, 9, 1, 9);
+		break;
+	case Kr651us:
+		gspca_dev->autogain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
+		gspca_dev->gain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_GAIN, 0, 253, 1, 128);
+		/* fall through */
+	case Cvideopro:
+	case DvcV6:
+	case Kritter:
+		gspca_dev->exposure = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_EXPOSURE, 0, 315, 1, 150);
+		break;
+	default:
+		break;
+	}
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	if (gspca_dev->autogain)
+		v4l2_ctrl_auto_cluster(3, &gspca_dev->autogain, 0, false);
+	return 0;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.pkt_scan = sd_pkt_scan,
+	.dq_callback = do_autogain,
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x046d, 0xd001)},
+	{USB_DEVICE(0x0502, 0xd001)},
+	{USB_DEVICE(0x052b, 0xd001)},
+	{USB_DEVICE(0x055f, 0xd001)},
+	{USB_DEVICE(0x06a5, 0x0000)},
+	{USB_DEVICE(0x06a5, 0xd001)},
+	{USB_DEVICE(0x06a5, 0xd800)},
+	{USB_DEVICE(0x06be, 0xd001)},
+	{USB_DEVICE(0x0728, 0xd001)},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
+
+module_param(webcam, int, 0644);
+MODULE_PARM_DESC(webcam,
+	"Webcam type\n"
+	"0: generic\n"
+	"1: Trust 120 SpaceCam\n"
+	"2: other Trust 120 SpaceCam\n"
+	"3: Conceptronic Video Pro\n"
+	"4: D-link dru-350c\n"
+	"5: Plustek Opticam 500U\n"
+	"6: Panasonic GP-KR651US\n"
+	"7: iRez Kritter\n"
+	"8: Mustek Wcam 300 mini\n"
+	"9: Scalar USB Microscope M2 (Proscope)\n"
+	"10: Divio Chicony TwinkleCam\n"
+	"11: DVC-V6\n");
diff --git a/drivers/media/usb/gspca/ov519.c b/drivers/media/usb/gspca/ov519.c
new file mode 100644
index 0000000..10fcbe9
--- /dev/null
+++ b/drivers/media/usb/gspca/ov519.c
@@ -0,0 +1,5020 @@
+/*
+ * OV519 driver
+ *
+ * Copyright (C) 2008-2011 Jean-François Moine <moinejf@free.fr>
+ * Copyright (C) 2009 Hans de Goede <hdegoede@redhat.com>
+ *
+ * This module is adapted from the ov51x-jpeg package, which itself
+ * was adapted from the ov511 driver.
+ *
+ * Original copyright for the ov511 driver is:
+ *
+ * Copyright (c) 1999-2006 Mark W. McClelland
+ * Support for OV519, OV8610 Copyright (c) 2003 Joerg Heckenbach
+ * Many improvements by Bret Wallach <bwallac1@san.rr.com>
+ * Color fixes by by Orion Sky Lawlor <olawlor@acm.org> (2/26/2000)
+ * OV7620 fixes by Charl P. Botha <cpbotha@ieee.org>
+ * Changes by Claudio Matsuoka <claudio@conectiva.com>
+ *
+ * ov51x-jpeg original copyright is:
+ *
+ * Copyright (c) 2004-2007 Romain Beauxis <toots@rastageeks.org>
+ * Support for OV7670 sensors was contributed by Sam Skipsey <aoanla@yahoo.com>
+ *
+ * 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
+ * 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.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "ov519"
+
+#include <linux/input.h>
+#include "gspca.h"
+
+/* The jpeg_hdr is used by w996Xcf only */
+/* The CONEX_CAM define for jpeg.h needs renaming, now its used here too */
+#define CONEX_CAM
+#include "jpeg.h"
+
+MODULE_AUTHOR("Jean-Francois Moine <http://moinejf.free.fr>");
+MODULE_DESCRIPTION("OV519 USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+/* global parameters */
+static int frame_rate;
+
+/* Number of times to retry a failed I2C transaction. Increase this if you
+ * are getting "Failed to read sensor ID..." */
+static int i2c_detect_tries = 10;
+
+/* ov519 device descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;		/* !! must be the first item */
+
+	struct v4l2_ctrl *jpegqual;
+	struct v4l2_ctrl *freq;
+	struct { /* h/vflip control cluster */
+		struct v4l2_ctrl *hflip;
+		struct v4l2_ctrl *vflip;
+	};
+	struct { /* autobrightness/brightness control cluster */
+		struct v4l2_ctrl *autobright;
+		struct v4l2_ctrl *brightness;
+	};
+
+	u8 revision;
+
+	u8 packet_nr;
+
+	char bridge;
+#define BRIDGE_OV511		0
+#define BRIDGE_OV511PLUS	1
+#define BRIDGE_OV518		2
+#define BRIDGE_OV518PLUS	3
+#define BRIDGE_OV519		4		/* = ov530 */
+#define BRIDGE_OVFX2		5
+#define BRIDGE_W9968CF		6
+#define BRIDGE_MASK		7
+
+	char invert_led;
+#define BRIDGE_INVERT_LED	8
+
+	char snapshot_pressed;
+	char snapshot_needs_reset;
+
+	/* Determined by sensor type */
+	u8 sif;
+
+#define QUALITY_MIN 50
+#define QUALITY_MAX 70
+#define QUALITY_DEF 50
+
+	u8 stopped;		/* Streaming is temporarily paused */
+	u8 first_frame;
+
+	u8 frame_rate;		/* current Framerate */
+	u8 clockdiv;		/* clockdiv override */
+
+	s8 sensor;		/* Type of image sensor chip (SEN_*) */
+
+	u8 sensor_addr;
+	u16 sensor_width;
+	u16 sensor_height;
+	s16 sensor_reg_cache[256];
+
+	u8 jpeg_hdr[JPEG_HDR_SZ];
+};
+enum sensors {
+	SEN_OV2610,
+	SEN_OV2610AE,
+	SEN_OV3610,
+	SEN_OV6620,
+	SEN_OV6630,
+	SEN_OV66308AF,
+	SEN_OV7610,
+	SEN_OV7620,
+	SEN_OV7620AE,
+	SEN_OV7640,
+	SEN_OV7648,
+	SEN_OV7660,
+	SEN_OV7670,
+	SEN_OV76BE,
+	SEN_OV8610,
+	SEN_OV9600,
+};
+
+/* Note this is a bit of a hack, but the w9968cf driver needs the code for all
+   the ov sensors which is already present here. When we have the time we
+   really should move the sensor drivers to v4l2 sub drivers. */
+#include "w996Xcf.c"
+
+/* table of the disabled controls */
+struct ctrl_valid {
+	unsigned int has_brightness:1;
+	unsigned int has_contrast:1;
+	unsigned int has_exposure:1;
+	unsigned int has_autogain:1;
+	unsigned int has_sat:1;
+	unsigned int has_hvflip:1;
+	unsigned int has_autobright:1;
+	unsigned int has_freq:1;
+};
+
+static const struct ctrl_valid valid_controls[] = {
+	[SEN_OV2610] = {
+		.has_exposure = 1,
+		.has_autogain = 1,
+	},
+	[SEN_OV2610AE] = {
+		.has_exposure = 1,
+		.has_autogain = 1,
+	},
+	[SEN_OV3610] = {
+		/* No controls */
+	},
+	[SEN_OV6620] = {
+		.has_brightness = 1,
+		.has_contrast = 1,
+		.has_sat = 1,
+		.has_autobright = 1,
+		.has_freq = 1,
+	},
+	[SEN_OV6630] = {
+		.has_brightness = 1,
+		.has_contrast = 1,
+		.has_sat = 1,
+		.has_autobright = 1,
+		.has_freq = 1,
+	},
+	[SEN_OV66308AF] = {
+		.has_brightness = 1,
+		.has_contrast = 1,
+		.has_sat = 1,
+		.has_autobright = 1,
+		.has_freq = 1,
+	},
+	[SEN_OV7610] = {
+		.has_brightness = 1,
+		.has_contrast = 1,
+		.has_sat = 1,
+		.has_autobright = 1,
+		.has_freq = 1,
+	},
+	[SEN_OV7620] = {
+		.has_brightness = 1,
+		.has_contrast = 1,
+		.has_sat = 1,
+		.has_autobright = 1,
+		.has_freq = 1,
+	},
+	[SEN_OV7620AE] = {
+		.has_brightness = 1,
+		.has_contrast = 1,
+		.has_sat = 1,
+		.has_autobright = 1,
+		.has_freq = 1,
+	},
+	[SEN_OV7640] = {
+		.has_brightness = 1,
+		.has_sat = 1,
+		.has_freq = 1,
+	},
+	[SEN_OV7648] = {
+		.has_brightness = 1,
+		.has_sat = 1,
+		.has_freq = 1,
+	},
+	[SEN_OV7660] = {
+		.has_brightness = 1,
+		.has_contrast = 1,
+		.has_sat = 1,
+		.has_hvflip = 1,
+		.has_freq = 1,
+	},
+	[SEN_OV7670] = {
+		.has_brightness = 1,
+		.has_contrast = 1,
+		.has_hvflip = 1,
+		.has_freq = 1,
+	},
+	[SEN_OV76BE] = {
+		.has_brightness = 1,
+		.has_contrast = 1,
+		.has_sat = 1,
+		.has_autobright = 1,
+		.has_freq = 1,
+	},
+	[SEN_OV8610] = {
+		.has_brightness = 1,
+		.has_contrast = 1,
+		.has_sat = 1,
+		.has_autobright = 1,
+	},
+	[SEN_OV9600] = {
+		.has_exposure = 1,
+		.has_autogain = 1,
+	},
+};
+
+static const struct v4l2_pix_format ov519_vga_mode[] = {
+	{320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 1},
+	{640, 480, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 0},
+};
+static const struct v4l2_pix_format ov519_sif_mode[] = {
+	{160, 120, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 160 * 120 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 3},
+	{176, 144, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 176,
+		.sizeimage = 176 * 144 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 1},
+	{320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 2},
+	{352, 288, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 352,
+		.sizeimage = 352 * 288 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 0},
+};
+
+/* Note some of the sizeimage values for the ov511 / ov518 may seem
+   larger then necessary, however they need to be this big as the ov511 /
+   ov518 always fills the entire isoc frame, using 0 padding bytes when
+   it doesn't have any data. So with low framerates the amount of data
+   transferred can become quite large (libv4l will remove all the 0 padding
+   in userspace). */
+static const struct v4l2_pix_format ov518_vga_mode[] = {
+	{320, 240, V4L2_PIX_FMT_OV518, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 1},
+	{640, 480, V4L2_PIX_FMT_OV518, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 2,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 0},
+};
+static const struct v4l2_pix_format ov518_sif_mode[] = {
+	{160, 120, V4L2_PIX_FMT_OV518, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 70000,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 3},
+	{176, 144, V4L2_PIX_FMT_OV518, V4L2_FIELD_NONE,
+		.bytesperline = 176,
+		.sizeimage = 70000,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 1},
+	{320, 240, V4L2_PIX_FMT_OV518, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 2},
+	{352, 288, V4L2_PIX_FMT_OV518, V4L2_FIELD_NONE,
+		.bytesperline = 352,
+		.sizeimage = 352 * 288 * 3,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 0},
+};
+
+static const struct v4l2_pix_format ov511_vga_mode[] = {
+	{320, 240, V4L2_PIX_FMT_OV511, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 1},
+	{640, 480, V4L2_PIX_FMT_OV511, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 2,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 0},
+};
+static const struct v4l2_pix_format ov511_sif_mode[] = {
+	{160, 120, V4L2_PIX_FMT_OV511, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 70000,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 3},
+	{176, 144, V4L2_PIX_FMT_OV511, V4L2_FIELD_NONE,
+		.bytesperline = 176,
+		.sizeimage = 70000,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 1},
+	{320, 240, V4L2_PIX_FMT_OV511, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 2},
+	{352, 288, V4L2_PIX_FMT_OV511, V4L2_FIELD_NONE,
+		.bytesperline = 352,
+		.sizeimage = 352 * 288 * 3,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 0},
+};
+
+static const struct v4l2_pix_format ovfx2_ov2610_mode[] = {
+	{800, 600, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 800,
+		.sizeimage = 800 * 600,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1},
+	{1600, 1200, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 1600,
+		.sizeimage = 1600 * 1200,
+		.colorspace = V4L2_COLORSPACE_SRGB},
+};
+static const struct v4l2_pix_format ovfx2_ov3610_mode[] = {
+	{640, 480, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1},
+	{800, 600, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 800,
+		.sizeimage = 800 * 600,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1},
+	{1024, 768, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 1024,
+		.sizeimage = 1024 * 768,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1},
+	{1600, 1200, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 1600,
+		.sizeimage = 1600 * 1200,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+	{2048, 1536, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 2048,
+		.sizeimage = 2048 * 1536,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+};
+static const struct v4l2_pix_format ovfx2_ov9600_mode[] = {
+	{640, 480, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1},
+	{1280, 1024, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 1280,
+		.sizeimage = 1280 * 1024,
+		.colorspace = V4L2_COLORSPACE_SRGB},
+};
+
+/* Registers common to OV511 / OV518 */
+#define R51x_FIFO_PSIZE			0x30	/* 2 bytes wide w/ OV518(+) */
+#define R51x_SYS_RESET			0x50
+	/* Reset type flags */
+	#define	OV511_RESET_OMNICE	0x08
+#define R51x_SYS_INIT			0x53
+#define R51x_SYS_SNAP			0x52
+#define R51x_SYS_CUST_ID		0x5f
+#define R51x_COMP_LUT_BEGIN		0x80
+
+/* OV511 Camera interface register numbers */
+#define R511_CAM_DELAY			0x10
+#define R511_CAM_EDGE			0x11
+#define R511_CAM_PXCNT			0x12
+#define R511_CAM_LNCNT			0x13
+#define R511_CAM_PXDIV			0x14
+#define R511_CAM_LNDIV			0x15
+#define R511_CAM_UV_EN			0x16
+#define R511_CAM_LINE_MODE		0x17
+#define R511_CAM_OPTS			0x18
+
+#define R511_SNAP_FRAME			0x19
+#define R511_SNAP_PXCNT			0x1a
+#define R511_SNAP_LNCNT			0x1b
+#define R511_SNAP_PXDIV			0x1c
+#define R511_SNAP_LNDIV			0x1d
+#define R511_SNAP_UV_EN			0x1e
+#define R511_SNAP_OPTS			0x1f
+
+#define R511_DRAM_FLOW_CTL		0x20
+#define R511_FIFO_OPTS			0x31
+#define R511_I2C_CTL			0x40
+#define R511_SYS_LED_CTL		0x55	/* OV511+ only */
+#define R511_COMP_EN			0x78
+#define R511_COMP_LUT_EN		0x79
+
+/* OV518 Camera interface register numbers */
+#define R518_GPIO_OUT			0x56	/* OV518(+) only */
+#define R518_GPIO_CTL			0x57	/* OV518(+) only */
+
+/* OV519 Camera interface register numbers */
+#define OV519_R10_H_SIZE		0x10
+#define OV519_R11_V_SIZE		0x11
+#define OV519_R12_X_OFFSETL		0x12
+#define OV519_R13_X_OFFSETH		0x13
+#define OV519_R14_Y_OFFSETL		0x14
+#define OV519_R15_Y_OFFSETH		0x15
+#define OV519_R16_DIVIDER		0x16
+#define OV519_R20_DFR			0x20
+#define OV519_R25_FORMAT		0x25
+
+/* OV519 System Controller register numbers */
+#define OV519_R51_RESET1		0x51
+#define OV519_R54_EN_CLK1		0x54
+#define OV519_R57_SNAPSHOT		0x57
+
+#define OV519_GPIO_DATA_OUT0		0x71
+#define OV519_GPIO_IO_CTRL0		0x72
+
+/*#define OV511_ENDPOINT_ADDRESS 1	 * Isoc endpoint number */
+
+/*
+ * The FX2 chip does not give us a zero length read at end of frame.
+ * It does, however, give a short read at the end of a frame, if
+ * necessary, rather than run two frames together.
+ *
+ * By choosing the right bulk transfer size, we are guaranteed to always
+ * get a short read for the last read of each frame.  Frame sizes are
+ * always a composite number (width * height, or a multiple) so if we
+ * choose a prime number, we are guaranteed that the last read of a
+ * frame will be short.
+ *
+ * But it isn't that easy: the 2.6 kernel requires a multiple of 4KB,
+ * otherwise EOVERFLOW "babbling" errors occur.  I have not been able
+ * to figure out why.  [PMiller]
+ *
+ * The constant (13 * 4096) is the largest "prime enough" number less than 64KB.
+ *
+ * It isn't enough to know the number of bytes per frame, in case we
+ * have data dropouts or buffer overruns (even though the FX2 double
+ * buffers, there are some pretty strict real time constraints for
+ * isochronous transfer for larger frame sizes).
+ */
+/*jfm: this value does not work for 800x600 - see isoc_init */
+#define OVFX2_BULK_SIZE (13 * 4096)
+
+/* I2C registers */
+#define R51x_I2C_W_SID		0x41
+#define R51x_I2C_SADDR_3	0x42
+#define R51x_I2C_SADDR_2	0x43
+#define R51x_I2C_R_SID		0x44
+#define R51x_I2C_DATA		0x45
+#define R518_I2C_CTL		0x47	/* OV518(+) only */
+#define OVFX2_I2C_ADDR		0x00
+
+/* I2C ADDRESSES */
+#define OV7xx0_SID   0x42
+#define OV_HIRES_SID 0x60		/* OV9xxx / OV2xxx / OV3xxx */
+#define OV8xx0_SID   0xa0
+#define OV6xx0_SID   0xc0
+
+/* OV7610 registers */
+#define OV7610_REG_GAIN		0x00	/* gain setting (5:0) */
+#define OV7610_REG_BLUE		0x01	/* blue channel balance */
+#define OV7610_REG_RED		0x02	/* red channel balance */
+#define OV7610_REG_SAT		0x03	/* saturation */
+#define OV8610_REG_HUE		0x04	/* 04 reserved */
+#define OV7610_REG_CNT		0x05	/* Y contrast */
+#define OV7610_REG_BRT		0x06	/* Y brightness */
+#define OV7610_REG_COM_C	0x14	/* misc common regs */
+#define OV7610_REG_ID_HIGH	0x1c	/* manufacturer ID MSB */
+#define OV7610_REG_ID_LOW	0x1d	/* manufacturer ID LSB */
+#define OV7610_REG_COM_I	0x29	/* misc settings */
+
+/* OV7660 and OV7670 registers */
+#define OV7670_R00_GAIN		0x00	/* Gain lower 8 bits (rest in vref) */
+#define OV7670_R01_BLUE		0x01	/* blue gain */
+#define OV7670_R02_RED		0x02	/* red gain */
+#define OV7670_R03_VREF		0x03	/* Pieces of GAIN, VSTART, VSTOP */
+#define OV7670_R04_COM1		0x04	/* Control 1 */
+/*#define OV7670_R07_AECHH	0x07	 * AEC MS 5 bits */
+#define OV7670_R0C_COM3		0x0c	/* Control 3 */
+#define OV7670_R0D_COM4		0x0d	/* Control 4 */
+#define OV7670_R0E_COM5		0x0e	/* All "reserved" */
+#define OV7670_R0F_COM6		0x0f	/* Control 6 */
+#define OV7670_R10_AECH		0x10	/* More bits of AEC value */
+#define OV7670_R11_CLKRC	0x11	/* Clock control */
+#define OV7670_R12_COM7		0x12	/* Control 7 */
+#define   OV7670_COM7_FMT_VGA	 0x00
+/*#define   OV7670_COM7_YUV	 0x00	 * YUV */
+#define   OV7670_COM7_FMT_QVGA	 0x10	/* QVGA format */
+#define   OV7670_COM7_FMT_MASK	 0x38
+#define   OV7670_COM7_RESET	 0x80	/* Register reset */
+#define OV7670_R13_COM8		0x13	/* Control 8 */
+#define   OV7670_COM8_AEC	 0x01	/* Auto exposure enable */
+#define   OV7670_COM8_AWB	 0x02	/* White balance enable */
+#define   OV7670_COM8_AGC	 0x04	/* Auto gain enable */
+#define   OV7670_COM8_BFILT	 0x20	/* Band filter enable */
+#define   OV7670_COM8_AECSTEP	 0x40	/* Unlimited AEC step size */
+#define   OV7670_COM8_FASTAEC	 0x80	/* Enable fast AGC/AEC */
+#define OV7670_R14_COM9		0x14	/* Control 9 - gain ceiling */
+#define OV7670_R15_COM10	0x15	/* Control 10 */
+#define OV7670_R17_HSTART	0x17	/* Horiz start high bits */
+#define OV7670_R18_HSTOP	0x18	/* Horiz stop high bits */
+#define OV7670_R19_VSTART	0x19	/* Vert start high bits */
+#define OV7670_R1A_VSTOP	0x1a	/* Vert stop high bits */
+#define OV7670_R1E_MVFP		0x1e	/* Mirror / vflip */
+#define   OV7670_MVFP_VFLIP	 0x10	/* vertical flip */
+#define   OV7670_MVFP_MIRROR	 0x20	/* Mirror image */
+#define OV7670_R24_AEW		0x24	/* AGC upper limit */
+#define OV7670_R25_AEB		0x25	/* AGC lower limit */
+#define OV7670_R26_VPT		0x26	/* AGC/AEC fast mode op region */
+#define OV7670_R32_HREF		0x32	/* HREF pieces */
+#define OV7670_R3A_TSLB		0x3a	/* lots of stuff */
+#define OV7670_R3B_COM11	0x3b	/* Control 11 */
+#define   OV7670_COM11_EXP	 0x02
+#define   OV7670_COM11_HZAUTO	 0x10	/* Auto detect 50/60 Hz */
+#define OV7670_R3C_COM12	0x3c	/* Control 12 */
+#define OV7670_R3D_COM13	0x3d	/* Control 13 */
+#define   OV7670_COM13_GAMMA	 0x80	/* Gamma enable */
+#define   OV7670_COM13_UVSAT	 0x40	/* UV saturation auto adjustment */
+#define OV7670_R3E_COM14	0x3e	/* Control 14 */
+#define OV7670_R3F_EDGE		0x3f	/* Edge enhancement factor */
+#define OV7670_R40_COM15	0x40	/* Control 15 */
+/*#define   OV7670_COM15_R00FF	 0xc0	 *	00 to FF */
+#define OV7670_R41_COM16	0x41	/* Control 16 */
+#define   OV7670_COM16_AWBGAIN	 0x08	/* AWB gain enable */
+/* end of ov7660 common registers */
+#define OV7670_R55_BRIGHT	0x55	/* Brightness */
+#define OV7670_R56_CONTRAS	0x56	/* Contrast control */
+#define OV7670_R69_GFIX		0x69	/* Fix gain control */
+/*#define OV7670_R8C_RGB444	0x8c	 * RGB 444 control */
+#define OV7670_R9F_HAECC1	0x9f	/* Hist AEC/AGC control 1 */
+#define OV7670_RA0_HAECC2	0xa0	/* Hist AEC/AGC control 2 */
+#define OV7670_RA5_BD50MAX	0xa5	/* 50hz banding step limit */
+#define OV7670_RA6_HAECC3	0xa6	/* Hist AEC/AGC control 3 */
+#define OV7670_RA7_HAECC4	0xa7	/* Hist AEC/AGC control 4 */
+#define OV7670_RA8_HAECC5	0xa8	/* Hist AEC/AGC control 5 */
+#define OV7670_RA9_HAECC6	0xa9	/* Hist AEC/AGC control 6 */
+#define OV7670_RAA_HAECC7	0xaa	/* Hist AEC/AGC control 7 */
+#define OV7670_RAB_BD60MAX	0xab	/* 60hz banding step limit */
+
+struct ov_regvals {
+	u8 reg;
+	u8 val;
+};
+struct ov_i2c_regvals {
+	u8 reg;
+	u8 val;
+};
+
+/* Settings for OV2610 camera chip */
+static const struct ov_i2c_regvals norm_2610[] = {
+	{ 0x12, 0x80 },	/* reset */
+};
+
+static const struct ov_i2c_regvals norm_2610ae[] = {
+	{0x12, 0x80},	/* reset */
+	{0x13, 0xcd},
+	{0x09, 0x01},
+	{0x0d, 0x00},
+	{0x11, 0x80},
+	{0x12, 0x20},	/* 1600x1200 */
+	{0x33, 0x0c},
+	{0x35, 0x90},
+	{0x36, 0x37},
+/* ms-win traces */
+	{0x11, 0x83},	/* clock / 3 ? */
+	{0x2d, 0x00},	/* 60 Hz filter */
+	{0x24, 0xb0},	/* normal colors */
+	{0x25, 0x90},
+	{0x10, 0x43},
+};
+
+static const struct ov_i2c_regvals norm_3620b[] = {
+	/*
+	 * From the datasheet: "Note that after writing to register COMH
+	 * (0x12) to change the sensor mode, registers related to the
+	 * sensor’s cropping window will be reset back to their default
+	 * values."
+	 *
+	 * "wait 4096 external clock ... to make sure the sensor is
+	 * stable and ready to access registers" i.e. 160us at 24MHz
+	 */
+	{ 0x12, 0x80 }, /* COMH reset */
+	{ 0x12, 0x00 }, /* QXGA, master */
+
+	/*
+	 * 11 CLKRC "Clock Rate Control"
+	 * [7] internal frequency doublers: on
+	 * [6] video port mode: master
+	 * [5:0] clock divider: 1
+	 */
+	{ 0x11, 0x80 },
+
+	/*
+	 * 13 COMI "Common Control I"
+	 *                  = 192 (0xC0) 11000000
+	 *    COMI[7] "AEC speed selection"
+	 *                  =   1 (0x01) 1....... "Faster AEC correction"
+	 *    COMI[6] "AEC speed step selection"
+	 *                  =   1 (0x01) .1...... "Big steps, fast"
+	 *    COMI[5] "Banding filter on off"
+	 *                  =   0 (0x00) ..0..... "Off"
+	 *    COMI[4] "Banding filter option"
+	 *                  =   0 (0x00) ...0.... "Main clock is 48 MHz and
+	 *                                         the PLL is ON"
+	 *    COMI[3] "Reserved"
+	 *                  =   0 (0x00) ....0...
+	 *    COMI[2] "AGC auto manual control selection"
+	 *                  =   0 (0x00) .....0.. "Manual"
+	 *    COMI[1] "AWB auto manual control selection"
+	 *                  =   0 (0x00) ......0. "Manual"
+	 *    COMI[0] "Exposure control"
+	 *                  =   0 (0x00) .......0 "Manual"
+	 */
+	{ 0x13, 0xc0 },
+
+	/*
+	 * 09 COMC "Common Control C"
+	 *                  =   8 (0x08) 00001000
+	 *    COMC[7:5] "Reserved"
+	 *                  =   0 (0x00) 000.....
+	 *    COMC[4] "Sleep Mode Enable"
+	 *                  =   0 (0x00) ...0.... "Normal mode"
+	 *    COMC[3:2] "Sensor sampling reset timing selection"
+	 *                  =   2 (0x02) ....10.. "Longer reset time"
+	 *    COMC[1:0] "Output drive current select"
+	 *                  =   0 (0x00) ......00 "Weakest"
+	 */
+	{ 0x09, 0x08 },
+
+	/*
+	 * 0C COMD "Common Control D"
+	 *                  =   8 (0x08) 00001000
+	 *    COMD[7] "Reserved"
+	 *                  =   0 (0x00) 0.......
+	 *    COMD[6] "Swap MSB and LSB at the output port"
+	 *                  =   0 (0x00) .0...... "False"
+	 *    COMD[5:3] "Reserved"
+	 *                  =   1 (0x01) ..001...
+	 *    COMD[2] "Output Average On Off"
+	 *                  =   0 (0x00) .....0.. "Output Normal"
+	 *    COMD[1] "Sensor precharge voltage selection"
+	 *                  =   0 (0x00) ......0. "Selects internal
+	 *                                         reference precharge
+	 *                                         voltage"
+	 *    COMD[0] "Snapshot option"
+	 *                  =   0 (0x00) .......0 "Enable live video output
+	 *                                         after snapshot sequence"
+	 */
+	{ 0x0c, 0x08 },
+
+	/*
+	 * 0D COME "Common Control E"
+	 *                  = 161 (0xA1) 10100001
+	 *    COME[7] "Output average option"
+	 *                  =   1 (0x01) 1....... "Output average of 4 pixels"
+	 *    COME[6] "Anti-blooming control"
+	 *                  =   0 (0x00) .0...... "Off"
+	 *    COME[5:3] "Reserved"
+	 *                  =   4 (0x04) ..100...
+	 *    COME[2] "Clock output power down pin status"
+	 *                  =   0 (0x00) .....0.. "Tri-state data output pin
+	 *                                         on power down"
+	 *    COME[1] "Data output pin status selection at power down"
+	 *                  =   0 (0x00) ......0. "Tri-state VSYNC, PCLK,
+	 *                                         HREF, and CHSYNC pins on
+	 *                                         power down"
+	 *    COME[0] "Auto zero circuit select"
+	 *                  =   1 (0x01) .......1 "On"
+	 */
+	{ 0x0d, 0xa1 },
+
+	/*
+	 * 0E COMF "Common Control F"
+	 *                  = 112 (0x70) 01110000
+	 *    COMF[7] "System clock selection"
+	 *                  =   0 (0x00) 0....... "Use 24 MHz system clock"
+	 *    COMF[6:4] "Reserved"
+	 *                  =   7 (0x07) .111....
+	 *    COMF[3] "Manual auto negative offset canceling selection"
+	 *                  =   0 (0x00) ....0... "Auto detect negative
+	 *                                         offset and cancel it"
+	 *    COMF[2:0] "Reserved"
+	 *                  =   0 (0x00) .....000
+	 */
+	{ 0x0e, 0x70 },
+
+	/*
+	 * 0F COMG "Common Control G"
+	 *                  =  66 (0x42) 01000010
+	 *    COMG[7] "Optical black output selection"
+	 *                  =   0 (0x00) 0....... "Disable"
+	 *    COMG[6] "Black level calibrate selection"
+	 *                  =   1 (0x01) .1...... "Use optical black pixels
+	 *                                         to calibrate"
+	 *    COMG[5:4] "Reserved"
+	 *                  =   0 (0x00) ..00....
+	 *    COMG[3] "Channel offset adjustment"
+	 *                  =   0 (0x00) ....0... "Disable offset adjustment"
+	 *    COMG[2] "ADC black level calibration option"
+	 *                  =   0 (0x00) .....0.. "Use B/G line and G/R
+	 *                                         line to calibrate each
+	 *                                         channel's black level"
+	 *    COMG[1] "Reserved"
+	 *                  =   1 (0x01) ......1.
+	 *    COMG[0] "ADC black level calibration enable"
+	 *                  =   0 (0x00) .......0 "Disable"
+	 */
+	{ 0x0f, 0x42 },
+
+	/*
+	 * 14 COMJ "Common Control J"
+	 *                  = 198 (0xC6) 11000110
+	 *    COMJ[7:6] "AGC gain ceiling"
+	 *                  =   3 (0x03) 11...... "8x"
+	 *    COMJ[5:4] "Reserved"
+	 *                  =   0 (0x00) ..00....
+	 *    COMJ[3] "Auto banding filter"
+	 *                  =   0 (0x00) ....0... "Banding filter is always
+	 *                                         on off depending on
+	 *                                         COMI[5] setting"
+	 *    COMJ[2] "VSYNC drop option"
+	 *                  =   1 (0x01) .....1.. "SYNC is dropped if frame
+	 *                                         data is dropped"
+	 *    COMJ[1] "Frame data drop"
+	 *                  =   1 (0x01) ......1. "Drop frame data if
+	 *                                         exposure is not within
+	 *                                         tolerance.  In AEC mode,
+	 *                                         data is normally dropped
+	 *                                         when data is out of
+	 *                                         range."
+	 *    COMJ[0] "Reserved"
+	 *                  =   0 (0x00) .......0
+	 */
+	{ 0x14, 0xc6 },
+
+	/*
+	 * 15 COMK "Common Control K"
+	 *                  =   2 (0x02) 00000010
+	 *    COMK[7] "CHSYNC pin output swap"
+	 *                  =   0 (0x00) 0....... "CHSYNC"
+	 *    COMK[6] "HREF pin output swap"
+	 *                  =   0 (0x00) .0...... "HREF"
+	 *    COMK[5] "PCLK output selection"
+	 *                  =   0 (0x00) ..0..... "PCLK always output"
+	 *    COMK[4] "PCLK edge selection"
+	 *                  =   0 (0x00) ...0.... "Data valid on falling edge"
+	 *    COMK[3] "HREF output polarity"
+	 *                  =   0 (0x00) ....0... "positive"
+	 *    COMK[2] "Reserved"
+	 *                  =   0 (0x00) .....0..
+	 *    COMK[1] "VSYNC polarity"
+	 *                  =   1 (0x01) ......1. "negative"
+	 *    COMK[0] "HSYNC polarity"
+	 *                  =   0 (0x00) .......0 "positive"
+	 */
+	{ 0x15, 0x02 },
+
+	/*
+	 * 33 CHLF "Current Control"
+	 *                  =   9 (0x09) 00001001
+	 *    CHLF[7:6] "Sensor current control"
+	 *                  =   0 (0x00) 00......
+	 *    CHLF[5] "Sensor current range control"
+	 *                  =   0 (0x00) ..0..... "normal range"
+	 *    CHLF[4] "Sensor current"
+	 *                  =   0 (0x00) ...0.... "normal current"
+	 *    CHLF[3] "Sensor buffer current control"
+	 *                  =   1 (0x01) ....1... "half current"
+	 *    CHLF[2] "Column buffer current control"
+	 *                  =   0 (0x00) .....0.. "normal current"
+	 *    CHLF[1] "Analog DSP current control"
+	 *                  =   0 (0x00) ......0. "normal current"
+	 *    CHLF[1] "ADC current control"
+	 *                  =   0 (0x00) ......0. "normal current"
+	 */
+	{ 0x33, 0x09 },
+
+	/*
+	 * 34 VBLM "Blooming Control"
+	 *                  =  80 (0x50) 01010000
+	 *    VBLM[7] "Hard soft reset switch"
+	 *                  =   0 (0x00) 0....... "Hard reset"
+	 *    VBLM[6:4] "Blooming voltage selection"
+	 *                  =   5 (0x05) .101....
+	 *    VBLM[3:0] "Sensor current control"
+	 *                  =   0 (0x00) ....0000
+	 */
+	{ 0x34, 0x50 },
+
+	/*
+	 * 36 VCHG "Sensor Precharge Voltage Control"
+	 *                  =   0 (0x00) 00000000
+	 *    VCHG[7] "Reserved"
+	 *                  =   0 (0x00) 0.......
+	 *    VCHG[6:4] "Sensor precharge voltage control"
+	 *                  =   0 (0x00) .000....
+	 *    VCHG[3:0] "Sensor array common reference"
+	 *                  =   0 (0x00) ....0000
+	 */
+	{ 0x36, 0x00 },
+
+	/*
+	 * 37 ADC "ADC Reference Control"
+	 *                  =   4 (0x04) 00000100
+	 *    ADC[7:4] "Reserved"
+	 *                  =   0 (0x00) 0000....
+	 *    ADC[3] "ADC input signal range"
+	 *                  =   0 (0x00) ....0... "Input signal 1.0x"
+	 *    ADC[2:0] "ADC range control"
+	 *                  =   4 (0x04) .....100
+	 */
+	{ 0x37, 0x04 },
+
+	/*
+	 * 38 ACOM "Analog Common Ground"
+	 *                  =  82 (0x52) 01010010
+	 *    ACOM[7] "Analog gain control"
+	 *                  =   0 (0x00) 0....... "Gain 1x"
+	 *    ACOM[6] "Analog black level calibration"
+	 *                  =   1 (0x01) .1...... "On"
+	 *    ACOM[5:0] "Reserved"
+	 *                  =  18 (0x12) ..010010
+	 */
+	{ 0x38, 0x52 },
+
+	/*
+	 * 3A FREFA "Internal Reference Adjustment"
+	 *                  =   0 (0x00) 00000000
+	 *    FREFA[7:0] "Range"
+	 *                  =   0 (0x00) 00000000
+	 */
+	{ 0x3a, 0x00 },
+
+	/*
+	 * 3C FVOPT "Internal Reference Adjustment"
+	 *                  =  31 (0x1F) 00011111
+	 *    FVOPT[7:0] "Range"
+	 *                  =  31 (0x1F) 00011111
+	 */
+	{ 0x3c, 0x1f },
+
+	/*
+	 * 44 Undocumented  =   0 (0x00) 00000000
+	 *    44[7:0] "It's a secret"
+	 *                  =   0 (0x00) 00000000
+	 */
+	{ 0x44, 0x00 },
+
+	/*
+	 * 40 Undocumented  =   0 (0x00) 00000000
+	 *    40[7:0] "It's a secret"
+	 *                  =   0 (0x00) 00000000
+	 */
+	{ 0x40, 0x00 },
+
+	/*
+	 * 41 Undocumented  =   0 (0x00) 00000000
+	 *    41[7:0] "It's a secret"
+	 *                  =   0 (0x00) 00000000
+	 */
+	{ 0x41, 0x00 },
+
+	/*
+	 * 42 Undocumented  =   0 (0x00) 00000000
+	 *    42[7:0] "It's a secret"
+	 *                  =   0 (0x00) 00000000
+	 */
+	{ 0x42, 0x00 },
+
+	/*
+	 * 43 Undocumented  =   0 (0x00) 00000000
+	 *    43[7:0] "It's a secret"
+	 *                  =   0 (0x00) 00000000
+	 */
+	{ 0x43, 0x00 },
+
+	/*
+	 * 45 Undocumented  = 128 (0x80) 10000000
+	 *    45[7:0] "It's a secret"
+	 *                  = 128 (0x80) 10000000
+	 */
+	{ 0x45, 0x80 },
+
+	/*
+	 * 48 Undocumented  = 192 (0xC0) 11000000
+	 *    48[7:0] "It's a secret"
+	 *                  = 192 (0xC0) 11000000
+	 */
+	{ 0x48, 0xc0 },
+
+	/*
+	 * 49 Undocumented  =  25 (0x19) 00011001
+	 *    49[7:0] "It's a secret"
+	 *                  =  25 (0x19) 00011001
+	 */
+	{ 0x49, 0x19 },
+
+	/*
+	 * 4B Undocumented  = 128 (0x80) 10000000
+	 *    4B[7:0] "It's a secret"
+	 *                  = 128 (0x80) 10000000
+	 */
+	{ 0x4b, 0x80 },
+
+	/*
+	 * 4D Undocumented  = 196 (0xC4) 11000100
+	 *    4D[7:0] "It's a secret"
+	 *                  = 196 (0xC4) 11000100
+	 */
+	{ 0x4d, 0xc4 },
+
+	/*
+	 * 35 VREF "Reference Voltage Control"
+	 *                  =  76 (0x4c) 01001100
+	 *    VREF[7:5] "Column high reference control"
+	 *                  =   2 (0x02) 010..... "higher voltage"
+	 *    VREF[4:2] "Column low reference control"
+	 *                  =   3 (0x03) ...011.. "Highest voltage"
+	 *    VREF[1:0] "Reserved"
+	 *                  =   0 (0x00) ......00
+	 */
+	{ 0x35, 0x4c },
+
+	/*
+	 * 3D Undocumented  =   0 (0x00) 00000000
+	 *    3D[7:0] "It's a secret"
+	 *                  =   0 (0x00) 00000000
+	 */
+	{ 0x3d, 0x00 },
+
+	/*
+	 * 3E Undocumented  =   0 (0x00) 00000000
+	 *    3E[7:0] "It's a secret"
+	 *                  =   0 (0x00) 00000000
+	 */
+	{ 0x3e, 0x00 },
+
+	/*
+	 * 3B FREFB "Internal Reference Adjustment"
+	 *                  =  24 (0x18) 00011000
+	 *    FREFB[7:0] "Range"
+	 *                  =  24 (0x18) 00011000
+	 */
+	{ 0x3b, 0x18 },
+
+	/*
+	 * 33 CHLF "Current Control"
+	 *                  =  25 (0x19) 00011001
+	 *    CHLF[7:6] "Sensor current control"
+	 *                  =   0 (0x00) 00......
+	 *    CHLF[5] "Sensor current range control"
+	 *                  =   0 (0x00) ..0..... "normal range"
+	 *    CHLF[4] "Sensor current"
+	 *                  =   1 (0x01) ...1.... "double current"
+	 *    CHLF[3] "Sensor buffer current control"
+	 *                  =   1 (0x01) ....1... "half current"
+	 *    CHLF[2] "Column buffer current control"
+	 *                  =   0 (0x00) .....0.. "normal current"
+	 *    CHLF[1] "Analog DSP current control"
+	 *                  =   0 (0x00) ......0. "normal current"
+	 *    CHLF[1] "ADC current control"
+	 *                  =   0 (0x00) ......0. "normal current"
+	 */
+	{ 0x33, 0x19 },
+
+	/*
+	 * 34 VBLM "Blooming Control"
+	 *                  =  90 (0x5A) 01011010
+	 *    VBLM[7] "Hard soft reset switch"
+	 *                  =   0 (0x00) 0....... "Hard reset"
+	 *    VBLM[6:4] "Blooming voltage selection"
+	 *                  =   5 (0x05) .101....
+	 *    VBLM[3:0] "Sensor current control"
+	 *                  =  10 (0x0A) ....1010
+	 */
+	{ 0x34, 0x5a },
+
+	/*
+	 * 3B FREFB "Internal Reference Adjustment"
+	 *                  =   0 (0x00) 00000000
+	 *    FREFB[7:0] "Range"
+	 *                  =   0 (0x00) 00000000
+	 */
+	{ 0x3b, 0x00 },
+
+	/*
+	 * 33 CHLF "Current Control"
+	 *                  =   9 (0x09) 00001001
+	 *    CHLF[7:6] "Sensor current control"
+	 *                  =   0 (0x00) 00......
+	 *    CHLF[5] "Sensor current range control"
+	 *                  =   0 (0x00) ..0..... "normal range"
+	 *    CHLF[4] "Sensor current"
+	 *                  =   0 (0x00) ...0.... "normal current"
+	 *    CHLF[3] "Sensor buffer current control"
+	 *                  =   1 (0x01) ....1... "half current"
+	 *    CHLF[2] "Column buffer current control"
+	 *                  =   0 (0x00) .....0.. "normal current"
+	 *    CHLF[1] "Analog DSP current control"
+	 *                  =   0 (0x00) ......0. "normal current"
+	 *    CHLF[1] "ADC current control"
+	 *                  =   0 (0x00) ......0. "normal current"
+	 */
+	{ 0x33, 0x09 },
+
+	/*
+	 * 34 VBLM "Blooming Control"
+	 *                  =  80 (0x50) 01010000
+	 *    VBLM[7] "Hard soft reset switch"
+	 *                  =   0 (0x00) 0....... "Hard reset"
+	 *    VBLM[6:4] "Blooming voltage selection"
+	 *                  =   5 (0x05) .101....
+	 *    VBLM[3:0] "Sensor current control"
+	 *                  =   0 (0x00) ....0000
+	 */
+	{ 0x34, 0x50 },
+
+	/*
+	 * 12 COMH "Common Control H"
+	 *                  =  64 (0x40) 01000000
+	 *    COMH[7] "SRST"
+	 *                  =   0 (0x00) 0....... "No-op"
+	 *    COMH[6:4] "Resolution selection"
+	 *                  =   4 (0x04) .100.... "XGA"
+	 *    COMH[3] "Master slave selection"
+	 *                  =   0 (0x00) ....0... "Master mode"
+	 *    COMH[2] "Internal B/R channel option"
+	 *                  =   0 (0x00) .....0.. "B/R use same channel"
+	 *    COMH[1] "Color bar test pattern"
+	 *                  =   0 (0x00) ......0. "Off"
+	 *    COMH[0] "Reserved"
+	 *                  =   0 (0x00) .......0
+	 */
+	{ 0x12, 0x40 },
+
+	/*
+	 * 17 HREFST "Horizontal window start"
+	 *                  =  31 (0x1F) 00011111
+	 *    HREFST[7:0] "Horizontal window start, 8 MSBs"
+	 *                  =  31 (0x1F) 00011111
+	 */
+	{ 0x17, 0x1f },
+
+	/*
+	 * 18 HREFEND "Horizontal window end"
+	 *                  =  95 (0x5F) 01011111
+	 *    HREFEND[7:0] "Horizontal Window End, 8 MSBs"
+	 *                  =  95 (0x5F) 01011111
+	 */
+	{ 0x18, 0x5f },
+
+	/*
+	 * 19 VSTRT "Vertical window start"
+	 *                  =   0 (0x00) 00000000
+	 *    VSTRT[7:0] "Vertical Window Start, 8 MSBs"
+	 *                  =   0 (0x00) 00000000
+	 */
+	{ 0x19, 0x00 },
+
+	/*
+	 * 1A VEND "Vertical window end"
+	 *                  =  96 (0x60) 01100000
+	 *    VEND[7:0] "Vertical Window End, 8 MSBs"
+	 *                  =  96 (0x60) 01100000
+	 */
+	{ 0x1a, 0x60 },
+
+	/*
+	 * 32 COMM "Common Control M"
+	 *                  =  18 (0x12) 00010010
+	 *    COMM[7:6] "Pixel clock divide option"
+	 *                  =   0 (0x00) 00...... "/1"
+	 *    COMM[5:3] "Horizontal window end position, 3 LSBs"
+	 *                  =   2 (0x02) ..010...
+	 *    COMM[2:0] "Horizontal window start position, 3 LSBs"
+	 *                  =   2 (0x02) .....010
+	 */
+	{ 0x32, 0x12 },
+
+	/*
+	 * 03 COMA "Common Control A"
+	 *                  =  74 (0x4A) 01001010
+	 *    COMA[7:4] "AWB Update Threshold"
+	 *                  =   4 (0x04) 0100....
+	 *    COMA[3:2] "Vertical window end line control 2 LSBs"
+	 *                  =   2 (0x02) ....10..
+	 *    COMA[1:0] "Vertical window start line control 2 LSBs"
+	 *                  =   2 (0x02) ......10
+	 */
+	{ 0x03, 0x4a },
+
+	/*
+	 * 11 CLKRC "Clock Rate Control"
+	 *                  = 128 (0x80) 10000000
+	 *    CLKRC[7] "Internal frequency doublers on off seclection"
+	 *                  =   1 (0x01) 1....... "On"
+	 *    CLKRC[6] "Digital video master slave selection"
+	 *                  =   0 (0x00) .0...... "Master mode, sensor
+	 *                                         provides PCLK"
+	 *    CLKRC[5:0] "Clock divider { CLK = PCLK/(1+CLKRC[5:0]) }"
+	 *                  =   0 (0x00) ..000000
+	 */
+	{ 0x11, 0x80 },
+
+	/*
+	 * 12 COMH "Common Control H"
+	 *                  =   0 (0x00) 00000000
+	 *    COMH[7] "SRST"
+	 *                  =   0 (0x00) 0....... "No-op"
+	 *    COMH[6:4] "Resolution selection"
+	 *                  =   0 (0x00) .000.... "QXGA"
+	 *    COMH[3] "Master slave selection"
+	 *                  =   0 (0x00) ....0... "Master mode"
+	 *    COMH[2] "Internal B/R channel option"
+	 *                  =   0 (0x00) .....0.. "B/R use same channel"
+	 *    COMH[1] "Color bar test pattern"
+	 *                  =   0 (0x00) ......0. "Off"
+	 *    COMH[0] "Reserved"
+	 *                  =   0 (0x00) .......0
+	 */
+	{ 0x12, 0x00 },
+
+	/*
+	 * 12 COMH "Common Control H"
+	 *                  =  64 (0x40) 01000000
+	 *    COMH[7] "SRST"
+	 *                  =   0 (0x00) 0....... "No-op"
+	 *    COMH[6:4] "Resolution selection"
+	 *                  =   4 (0x04) .100.... "XGA"
+	 *    COMH[3] "Master slave selection"
+	 *                  =   0 (0x00) ....0... "Master mode"
+	 *    COMH[2] "Internal B/R channel option"
+	 *                  =   0 (0x00) .....0.. "B/R use same channel"
+	 *    COMH[1] "Color bar test pattern"
+	 *                  =   0 (0x00) ......0. "Off"
+	 *    COMH[0] "Reserved"
+	 *                  =   0 (0x00) .......0
+	 */
+	{ 0x12, 0x40 },
+
+	/*
+	 * 17 HREFST "Horizontal window start"
+	 *                  =  31 (0x1F) 00011111
+	 *    HREFST[7:0] "Horizontal window start, 8 MSBs"
+	 *                  =  31 (0x1F) 00011111
+	 */
+	{ 0x17, 0x1f },
+
+	/*
+	 * 18 HREFEND "Horizontal window end"
+	 *                  =  95 (0x5F) 01011111
+	 *    HREFEND[7:0] "Horizontal Window End, 8 MSBs"
+	 *                  =  95 (0x5F) 01011111
+	 */
+	{ 0x18, 0x5f },
+
+	/*
+	 * 19 VSTRT "Vertical window start"
+	 *                  =   0 (0x00) 00000000
+	 *    VSTRT[7:0] "Vertical Window Start, 8 MSBs"
+	 *                  =   0 (0x00) 00000000
+	 */
+	{ 0x19, 0x00 },
+
+	/*
+	 * 1A VEND "Vertical window end"
+	 *                  =  96 (0x60) 01100000
+	 *    VEND[7:0] "Vertical Window End, 8 MSBs"
+	 *                  =  96 (0x60) 01100000
+	 */
+	{ 0x1a, 0x60 },
+
+	/*
+	 * 32 COMM "Common Control M"
+	 *                  =  18 (0x12) 00010010
+	 *    COMM[7:6] "Pixel clock divide option"
+	 *                  =   0 (0x00) 00...... "/1"
+	 *    COMM[5:3] "Horizontal window end position, 3 LSBs"
+	 *                  =   2 (0x02) ..010...
+	 *    COMM[2:0] "Horizontal window start position, 3 LSBs"
+	 *                  =   2 (0x02) .....010
+	 */
+	{ 0x32, 0x12 },
+
+	/*
+	 * 03 COMA "Common Control A"
+	 *                  =  74 (0x4A) 01001010
+	 *    COMA[7:4] "AWB Update Threshold"
+	 *                  =   4 (0x04) 0100....
+	 *    COMA[3:2] "Vertical window end line control 2 LSBs"
+	 *                  =   2 (0x02) ....10..
+	 *    COMA[1:0] "Vertical window start line control 2 LSBs"
+	 *                  =   2 (0x02) ......10
+	 */
+	{ 0x03, 0x4a },
+
+	/*
+	 * 02 RED "Red Gain Control"
+	 *                  = 175 (0xAF) 10101111
+	 *    RED[7] "Action"
+	 *                  =   1 (0x01) 1....... "gain = 1/(1+bitrev([6:0]))"
+	 *    RED[6:0] "Value"
+	 *                  =  47 (0x2F) .0101111
+	 */
+	{ 0x02, 0xaf },
+
+	/*
+	 * 2D ADDVSL "VSYNC Pulse Width"
+	 *                  = 210 (0xD2) 11010010
+	 *    ADDVSL[7:0] "VSYNC pulse width, LSB"
+	 *                  = 210 (0xD2) 11010010
+	 */
+	{ 0x2d, 0xd2 },
+
+	/*
+	 * 00 GAIN          =  24 (0x18) 00011000
+	 *    GAIN[7:6] "Reserved"
+	 *                  =   0 (0x00) 00......
+	 *    GAIN[5] "Double"
+	 *                  =   0 (0x00) ..0..... "False"
+	 *    GAIN[4] "Double"
+	 *                  =   1 (0x01) ...1.... "True"
+	 *    GAIN[3:0] "Range"
+	 *                  =   8 (0x08) ....1000
+	 */
+	{ 0x00, 0x18 },
+
+	/*
+	 * 01 BLUE "Blue Gain Control"
+	 *                  = 240 (0xF0) 11110000
+	 *    BLUE[7] "Action"
+	 *                  =   1 (0x01) 1....... "gain = 1/(1+bitrev([6:0]))"
+	 *    BLUE[6:0] "Value"
+	 *                  = 112 (0x70) .1110000
+	 */
+	{ 0x01, 0xf0 },
+
+	/*
+	 * 10 AEC "Automatic Exposure Control"
+	 *                  =  10 (0x0A) 00001010
+	 *    AEC[7:0] "Automatic Exposure Control, 8 MSBs"
+	 *                  =  10 (0x0A) 00001010
+	 */
+	{ 0x10, 0x0a },
+
+	{ 0xe1, 0x67 },
+	{ 0xe3, 0x03 },
+	{ 0xe4, 0x26 },
+	{ 0xe5, 0x3e },
+	{ 0xf8, 0x01 },
+	{ 0xff, 0x01 },
+};
+
+static const struct ov_i2c_regvals norm_6x20[] = {
+	{ 0x12, 0x80 }, /* reset */
+	{ 0x11, 0x01 },
+	{ 0x03, 0x60 },
+	{ 0x05, 0x7f }, /* For when autoadjust is off */
+	{ 0x07, 0xa8 },
+	/* The ratio of 0x0c and 0x0d controls the white point */
+	{ 0x0c, 0x24 },
+	{ 0x0d, 0x24 },
+	{ 0x0f, 0x15 }, /* COMS */
+	{ 0x10, 0x75 }, /* AEC Exposure time */
+	{ 0x12, 0x24 }, /* Enable AGC */
+	{ 0x14, 0x04 },
+	/* 0x16: 0x06 helps frame stability with moving objects */
+	{ 0x16, 0x06 },
+/*	{ 0x20, 0x30 },  * Aperture correction enable */
+	{ 0x26, 0xb2 }, /* BLC enable */
+	/* 0x28: 0x05 Selects RGB format if RGB on */
+	{ 0x28, 0x05 },
+	{ 0x2a, 0x04 }, /* Disable framerate adjust */
+/*	{ 0x2b, 0xac },  * Framerate; Set 2a[7] first */
+	{ 0x2d, 0x85 },
+	{ 0x33, 0xa0 }, /* Color Processing Parameter */
+	{ 0x34, 0xd2 }, /* Max A/D range */
+	{ 0x38, 0x8b },
+	{ 0x39, 0x40 },
+
+	{ 0x3c, 0x39 }, /* Enable AEC mode changing */
+	{ 0x3c, 0x3c }, /* Change AEC mode */
+	{ 0x3c, 0x24 }, /* Disable AEC mode changing */
+
+	{ 0x3d, 0x80 },
+	/* These next two registers (0x4a, 0x4b) are undocumented.
+	 * They control the color balance */
+	{ 0x4a, 0x80 },
+	{ 0x4b, 0x80 },
+	{ 0x4d, 0xd2 }, /* This reduces noise a bit */
+	{ 0x4e, 0xc1 },
+	{ 0x4f, 0x04 },
+/* Do 50-53 have any effect? */
+/* Toggle 0x12[2] off and on here? */
+};
+
+static const struct ov_i2c_regvals norm_6x30[] = {
+	{ 0x12, 0x80 }, /* Reset */
+	{ 0x00, 0x1f }, /* Gain */
+	{ 0x01, 0x99 }, /* Blue gain */
+	{ 0x02, 0x7c }, /* Red gain */
+	{ 0x03, 0xc0 }, /* Saturation */
+	{ 0x05, 0x0a }, /* Contrast */
+	{ 0x06, 0x95 }, /* Brightness */
+	{ 0x07, 0x2d }, /* Sharpness */
+	{ 0x0c, 0x20 },
+	{ 0x0d, 0x20 },
+	{ 0x0e, 0xa0 }, /* Was 0x20, bit7 enables a 2x gain which we need */
+	{ 0x0f, 0x05 },
+	{ 0x10, 0x9a },
+	{ 0x11, 0x00 }, /* Pixel clock = fastest */
+	{ 0x12, 0x24 }, /* Enable AGC and AWB */
+	{ 0x13, 0x21 },
+	{ 0x14, 0x80 },
+	{ 0x15, 0x01 },
+	{ 0x16, 0x03 },
+	{ 0x17, 0x38 },
+	{ 0x18, 0xea },
+	{ 0x19, 0x04 },
+	{ 0x1a, 0x93 },
+	{ 0x1b, 0x00 },
+	{ 0x1e, 0xc4 },
+	{ 0x1f, 0x04 },
+	{ 0x20, 0x20 },
+	{ 0x21, 0x10 },
+	{ 0x22, 0x88 },
+	{ 0x23, 0xc0 }, /* Crystal circuit power level */
+	{ 0x25, 0x9a }, /* Increase AEC black ratio */
+	{ 0x26, 0xb2 }, /* BLC enable */
+	{ 0x27, 0xa2 },
+	{ 0x28, 0x00 },
+	{ 0x29, 0x00 },
+	{ 0x2a, 0x84 }, /* 60 Hz power */
+	{ 0x2b, 0xa8 }, /* 60 Hz power */
+	{ 0x2c, 0xa0 },
+	{ 0x2d, 0x95 }, /* Enable auto-brightness */
+	{ 0x2e, 0x88 },
+	{ 0x33, 0x26 },
+	{ 0x34, 0x03 },
+	{ 0x36, 0x8f },
+	{ 0x37, 0x80 },
+	{ 0x38, 0x83 },
+	{ 0x39, 0x80 },
+	{ 0x3a, 0x0f },
+	{ 0x3b, 0x3c },
+	{ 0x3c, 0x1a },
+	{ 0x3d, 0x80 },
+	{ 0x3e, 0x80 },
+	{ 0x3f, 0x0e },
+	{ 0x40, 0x00 }, /* White bal */
+	{ 0x41, 0x00 }, /* White bal */
+	{ 0x42, 0x80 },
+	{ 0x43, 0x3f }, /* White bal */
+	{ 0x44, 0x80 },
+	{ 0x45, 0x20 },
+	{ 0x46, 0x20 },
+	{ 0x47, 0x80 },
+	{ 0x48, 0x7f },
+	{ 0x49, 0x00 },
+	{ 0x4a, 0x00 },
+	{ 0x4b, 0x80 },
+	{ 0x4c, 0xd0 },
+	{ 0x4d, 0x10 }, /* U = 0.563u, V = 0.714v */
+	{ 0x4e, 0x40 },
+	{ 0x4f, 0x07 }, /* UV avg., col. killer: max */
+	{ 0x50, 0xff },
+	{ 0x54, 0x23 }, /* Max AGC gain: 18dB */
+	{ 0x55, 0xff },
+	{ 0x56, 0x12 },
+	{ 0x57, 0x81 },
+	{ 0x58, 0x75 },
+	{ 0x59, 0x01 }, /* AGC dark current comp.: +1 */
+	{ 0x5a, 0x2c },
+	{ 0x5b, 0x0f }, /* AWB chrominance levels */
+	{ 0x5c, 0x10 },
+	{ 0x3d, 0x80 },
+	{ 0x27, 0xa6 },
+	{ 0x12, 0x20 }, /* Toggle AWB */
+	{ 0x12, 0x24 },
+};
+
+/* Lawrence Glaister <lg@jfm.bc.ca> reports:
+ *
+ * Register 0x0f in the 7610 has the following effects:
+ *
+ * 0x85 (AEC method 1): Best overall, good contrast range
+ * 0x45 (AEC method 2): Very overexposed
+ * 0xa5 (spec sheet default): Ok, but the black level is
+ *	shifted resulting in loss of contrast
+ * 0x05 (old driver setting): very overexposed, too much
+ *	contrast
+ */
+static const struct ov_i2c_regvals norm_7610[] = {
+	{ 0x10, 0xff },
+	{ 0x16, 0x06 },
+	{ 0x28, 0x24 },
+	{ 0x2b, 0xac },
+	{ 0x12, 0x00 },
+	{ 0x38, 0x81 },
+	{ 0x28, 0x24 },	/* 0c */
+	{ 0x0f, 0x85 },	/* lg's setting */
+	{ 0x15, 0x01 },
+	{ 0x20, 0x1c },
+	{ 0x23, 0x2a },
+	{ 0x24, 0x10 },
+	{ 0x25, 0x8a },
+	{ 0x26, 0xa2 },
+	{ 0x27, 0xc2 },
+	{ 0x2a, 0x04 },
+	{ 0x2c, 0xfe },
+	{ 0x2d, 0x93 },
+	{ 0x30, 0x71 },
+	{ 0x31, 0x60 },
+	{ 0x32, 0x26 },
+	{ 0x33, 0x20 },
+	{ 0x34, 0x48 },
+	{ 0x12, 0x24 },
+	{ 0x11, 0x01 },
+	{ 0x0c, 0x24 },
+	{ 0x0d, 0x24 },
+};
+
+static const struct ov_i2c_regvals norm_7620[] = {
+	{ 0x12, 0x80 },		/* reset */
+	{ 0x00, 0x00 },		/* gain */
+	{ 0x01, 0x80 },		/* blue gain */
+	{ 0x02, 0x80 },		/* red gain */
+	{ 0x03, 0xc0 },		/* OV7670_R03_VREF */
+	{ 0x06, 0x60 },
+	{ 0x07, 0x00 },
+	{ 0x0c, 0x24 },
+	{ 0x0c, 0x24 },
+	{ 0x0d, 0x24 },
+	{ 0x11, 0x01 },
+	{ 0x12, 0x24 },
+	{ 0x13, 0x01 },
+	{ 0x14, 0x84 },
+	{ 0x15, 0x01 },
+	{ 0x16, 0x03 },
+	{ 0x17, 0x2f },
+	{ 0x18, 0xcf },
+	{ 0x19, 0x06 },
+	{ 0x1a, 0xf5 },
+	{ 0x1b, 0x00 },
+	{ 0x20, 0x18 },
+	{ 0x21, 0x80 },
+	{ 0x22, 0x80 },
+	{ 0x23, 0x00 },
+	{ 0x26, 0xa2 },
+	{ 0x27, 0xea },
+	{ 0x28, 0x22 }, /* Was 0x20, bit1 enables a 2x gain which we need */
+	{ 0x29, 0x00 },
+	{ 0x2a, 0x10 },
+	{ 0x2b, 0x00 },
+	{ 0x2c, 0x88 },
+	{ 0x2d, 0x91 },
+	{ 0x2e, 0x80 },
+	{ 0x2f, 0x44 },
+	{ 0x60, 0x27 },
+	{ 0x61, 0x02 },
+	{ 0x62, 0x5f },
+	{ 0x63, 0xd5 },
+	{ 0x64, 0x57 },
+	{ 0x65, 0x83 },
+	{ 0x66, 0x55 },
+	{ 0x67, 0x92 },
+	{ 0x68, 0xcf },
+	{ 0x69, 0x76 },
+	{ 0x6a, 0x22 },
+	{ 0x6b, 0x00 },
+	{ 0x6c, 0x02 },
+	{ 0x6d, 0x44 },
+	{ 0x6e, 0x80 },
+	{ 0x6f, 0x1d },
+	{ 0x70, 0x8b },
+	{ 0x71, 0x00 },
+	{ 0x72, 0x14 },
+	{ 0x73, 0x54 },
+	{ 0x74, 0x00 },
+	{ 0x75, 0x8e },
+	{ 0x76, 0x00 },
+	{ 0x77, 0xff },
+	{ 0x78, 0x80 },
+	{ 0x79, 0x80 },
+	{ 0x7a, 0x80 },
+	{ 0x7b, 0xe2 },
+	{ 0x7c, 0x00 },
+};
+
+/* 7640 and 7648. The defaults should be OK for most registers. */
+static const struct ov_i2c_regvals norm_7640[] = {
+	{ 0x12, 0x80 },
+	{ 0x12, 0x14 },
+};
+
+static const struct ov_regvals init_519_ov7660[] = {
+	{ 0x5d,	0x03 }, /* Turn off suspend mode */
+	{ 0x53,	0x9b }, /* 0x9f enables the (unused) microcontroller */
+	{ 0x54,	0x0f }, /* bit2 (jpeg enable) */
+	{ 0xa2,	0x20 }, /* a2-a5 are undocumented */
+	{ 0xa3,	0x18 },
+	{ 0xa4,	0x04 },
+	{ 0xa5,	0x28 },
+	{ 0x37,	0x00 },	/* SetUsbInit */
+	{ 0x55,	0x02 }, /* 4.096 Mhz audio clock */
+	/* Enable both fields, YUV Input, disable defect comp (why?) */
+	{ 0x20,	0x0c },	/* 0x0d does U <-> V swap */
+	{ 0x21,	0x38 },
+	{ 0x22,	0x1d },
+	{ 0x17,	0x50 }, /* undocumented */
+	{ 0x37,	0x00 }, /* undocumented */
+	{ 0x40,	0xff }, /* I2C timeout counter */
+	{ 0x46,	0x00 }, /* I2C clock prescaler */
+};
+static const struct ov_i2c_regvals norm_7660[] = {
+	{OV7670_R12_COM7, OV7670_COM7_RESET},
+	{OV7670_R11_CLKRC, 0x81},
+	{0x92, 0x00},			/* DM_LNL */
+	{0x93, 0x00},			/* DM_LNH */
+	{0x9d, 0x4c},			/* BD50ST */
+	{0x9e, 0x3f},			/* BD60ST */
+	{OV7670_R3B_COM11, 0x02},
+	{OV7670_R13_COM8, 0xf5},
+	{OV7670_R10_AECH, 0x00},
+	{OV7670_R00_GAIN, 0x00},
+	{OV7670_R01_BLUE, 0x7c},
+	{OV7670_R02_RED, 0x9d},
+	{OV7670_R12_COM7, 0x00},
+	{OV7670_R04_COM1, 00},
+	{OV7670_R18_HSTOP, 0x01},
+	{OV7670_R17_HSTART, 0x13},
+	{OV7670_R32_HREF, 0x92},
+	{OV7670_R19_VSTART, 0x02},
+	{OV7670_R1A_VSTOP, 0x7a},
+	{OV7670_R03_VREF, 0x00},
+	{OV7670_R0E_COM5, 0x04},
+	{OV7670_R0F_COM6, 0x62},
+	{OV7670_R15_COM10, 0x00},
+	{0x16, 0x02},			/* RSVD */
+	{0x1b, 0x00},			/* PSHFT */
+	{OV7670_R1E_MVFP, 0x01},
+	{0x29, 0x3c},			/* RSVD */
+	{0x33, 0x00},			/* CHLF */
+	{0x34, 0x07},			/* ARBLM */
+	{0x35, 0x84},			/* RSVD */
+	{0x36, 0x00},			/* RSVD */
+	{0x37, 0x04},			/* ADC */
+	{0x39, 0x43},			/* OFON */
+	{OV7670_R3A_TSLB, 0x00},
+	{OV7670_R3C_COM12, 0x6c},
+	{OV7670_R3D_COM13, 0x98},
+	{OV7670_R3F_EDGE, 0x23},
+	{OV7670_R40_COM15, 0xc1},
+	{OV7670_R41_COM16, 0x22},
+	{0x6b, 0x0a},			/* DBLV */
+	{0xa1, 0x08},			/* RSVD */
+	{0x69, 0x80},			/* HV */
+	{0x43, 0xf0},			/* RSVD.. */
+	{0x44, 0x10},
+	{0x45, 0x78},
+	{0x46, 0xa8},
+	{0x47, 0x60},
+	{0x48, 0x80},
+	{0x59, 0xba},
+	{0x5a, 0x9a},
+	{0x5b, 0x22},
+	{0x5c, 0xb9},
+	{0x5d, 0x9b},
+	{0x5e, 0x10},
+	{0x5f, 0xe0},
+	{0x60, 0x85},
+	{0x61, 0x60},
+	{0x9f, 0x9d},			/* RSVD */
+	{0xa0, 0xa0},			/* DSPC2 */
+	{0x4f, 0x60},			/* matrix */
+	{0x50, 0x64},
+	{0x51, 0x04},
+	{0x52, 0x18},
+	{0x53, 0x3c},
+	{0x54, 0x54},
+	{0x55, 0x40},
+	{0x56, 0x40},
+	{0x57, 0x40},
+	{0x58, 0x0d},			/* matrix sign */
+	{0x8b, 0xcc},			/* RSVD */
+	{0x8c, 0xcc},
+	{0x8d, 0xcf},
+	{0x6c, 0x40},			/* gamma curve */
+	{0x6d, 0xe0},
+	{0x6e, 0xa0},
+	{0x6f, 0x80},
+	{0x70, 0x70},
+	{0x71, 0x80},
+	{0x72, 0x60},
+	{0x73, 0x60},
+	{0x74, 0x50},
+	{0x75, 0x40},
+	{0x76, 0x38},
+	{0x77, 0x3c},
+	{0x78, 0x32},
+	{0x79, 0x1a},
+	{0x7a, 0x28},
+	{0x7b, 0x24},
+	{0x7c, 0x04},			/* gamma curve */
+	{0x7d, 0x12},
+	{0x7e, 0x26},
+	{0x7f, 0x46},
+	{0x80, 0x54},
+	{0x81, 0x64},
+	{0x82, 0x70},
+	{0x83, 0x7c},
+	{0x84, 0x86},
+	{0x85, 0x8e},
+	{0x86, 0x9c},
+	{0x87, 0xab},
+	{0x88, 0xc4},
+	{0x89, 0xd1},
+	{0x8a, 0xe5},
+	{OV7670_R14_COM9, 0x1e},
+	{OV7670_R24_AEW, 0x80},
+	{OV7670_R25_AEB, 0x72},
+	{OV7670_R26_VPT, 0xb3},
+	{0x62, 0x80},			/* LCC1 */
+	{0x63, 0x80},			/* LCC2 */
+	{0x64, 0x06},			/* LCC3 */
+	{0x65, 0x00},			/* LCC4 */
+	{0x66, 0x01},			/* LCC5 */
+	{0x94, 0x0e},			/* RSVD.. */
+	{0x95, 0x14},
+	{OV7670_R13_COM8, OV7670_COM8_FASTAEC
+			| OV7670_COM8_AECSTEP
+			| OV7670_COM8_BFILT
+			| 0x10
+			| OV7670_COM8_AGC
+			| OV7670_COM8_AWB
+			| OV7670_COM8_AEC},
+	{0xa1, 0xc8}
+};
+static const struct ov_i2c_regvals norm_9600[] = {
+	{0x12, 0x80},
+	{0x0c, 0x28},
+	{0x11, 0x80},
+	{0x13, 0xb5},
+	{0x14, 0x3e},
+	{0x1b, 0x04},
+	{0x24, 0xb0},
+	{0x25, 0x90},
+	{0x26, 0x94},
+	{0x35, 0x90},
+	{0x37, 0x07},
+	{0x38, 0x08},
+	{0x01, 0x8e},
+	{0x02, 0x85}
+};
+
+/* 7670. Defaults taken from OmniVision provided data,
+*  as provided by Jonathan Corbet of OLPC		*/
+static const struct ov_i2c_regvals norm_7670[] = {
+	{ OV7670_R12_COM7, OV7670_COM7_RESET },
+	{ OV7670_R3A_TSLB, 0x04 },		/* OV */
+	{ OV7670_R12_COM7, OV7670_COM7_FMT_VGA }, /* VGA */
+	{ OV7670_R11_CLKRC, 0x01 },
+/*
+ * Set the hardware window.  These values from OV don't entirely
+ * make sense - hstop is less than hstart.  But they work...
+ */
+	{ OV7670_R17_HSTART, 0x13 },
+	{ OV7670_R18_HSTOP, 0x01 },
+	{ OV7670_R32_HREF, 0xb6 },
+	{ OV7670_R19_VSTART, 0x02 },
+	{ OV7670_R1A_VSTOP, 0x7a },
+	{ OV7670_R03_VREF, 0x0a },
+
+	{ OV7670_R0C_COM3, 0x00 },
+	{ OV7670_R3E_COM14, 0x00 },
+/* Mystery scaling numbers */
+	{ 0x70, 0x3a },
+	{ 0x71, 0x35 },
+	{ 0x72, 0x11 },
+	{ 0x73, 0xf0 },
+	{ 0xa2, 0x02 },
+/*	{ OV7670_R15_COM10, 0x0 }, */
+
+/* Gamma curve values */
+	{ 0x7a, 0x20 },
+	{ 0x7b, 0x10 },
+	{ 0x7c, 0x1e },
+	{ 0x7d, 0x35 },
+	{ 0x7e, 0x5a },
+	{ 0x7f, 0x69 },
+	{ 0x80, 0x76 },
+	{ 0x81, 0x80 },
+	{ 0x82, 0x88 },
+	{ 0x83, 0x8f },
+	{ 0x84, 0x96 },
+	{ 0x85, 0xa3 },
+	{ 0x86, 0xaf },
+	{ 0x87, 0xc4 },
+	{ 0x88, 0xd7 },
+	{ 0x89, 0xe8 },
+
+/* AGC and AEC parameters.  Note we start by disabling those features,
+   then turn them only after tweaking the values. */
+	{ OV7670_R13_COM8, OV7670_COM8_FASTAEC
+			 | OV7670_COM8_AECSTEP
+			 | OV7670_COM8_BFILT },
+	{ OV7670_R00_GAIN, 0x00 },
+	{ OV7670_R10_AECH, 0x00 },
+	{ OV7670_R0D_COM4, 0x40 }, /* magic reserved bit */
+	{ OV7670_R14_COM9, 0x18 }, /* 4x gain + magic rsvd bit */
+	{ OV7670_RA5_BD50MAX, 0x05 },
+	{ OV7670_RAB_BD60MAX, 0x07 },
+	{ OV7670_R24_AEW, 0x95 },
+	{ OV7670_R25_AEB, 0x33 },
+	{ OV7670_R26_VPT, 0xe3 },
+	{ OV7670_R9F_HAECC1, 0x78 },
+	{ OV7670_RA0_HAECC2, 0x68 },
+	{ 0xa1, 0x03 }, /* magic */
+	{ OV7670_RA6_HAECC3, 0xd8 },
+	{ OV7670_RA7_HAECC4, 0xd8 },
+	{ OV7670_RA8_HAECC5, 0xf0 },
+	{ OV7670_RA9_HAECC6, 0x90 },
+	{ OV7670_RAA_HAECC7, 0x94 },
+	{ OV7670_R13_COM8, OV7670_COM8_FASTAEC
+			| OV7670_COM8_AECSTEP
+			| OV7670_COM8_BFILT
+			| OV7670_COM8_AGC
+			| OV7670_COM8_AEC },
+
+/* Almost all of these are magic "reserved" values.  */
+	{ OV7670_R0E_COM5, 0x61 },
+	{ OV7670_R0F_COM6, 0x4b },
+	{ 0x16, 0x02 },
+	{ OV7670_R1E_MVFP, 0x07 },
+	{ 0x21, 0x02 },
+	{ 0x22, 0x91 },
+	{ 0x29, 0x07 },
+	{ 0x33, 0x0b },
+	{ 0x35, 0x0b },
+	{ 0x37, 0x1d },
+	{ 0x38, 0x71 },
+	{ 0x39, 0x2a },
+	{ OV7670_R3C_COM12, 0x78 },
+	{ 0x4d, 0x40 },
+	{ 0x4e, 0x20 },
+	{ OV7670_R69_GFIX, 0x00 },
+	{ 0x6b, 0x4a },
+	{ 0x74, 0x10 },
+	{ 0x8d, 0x4f },
+	{ 0x8e, 0x00 },
+	{ 0x8f, 0x00 },
+	{ 0x90, 0x00 },
+	{ 0x91, 0x00 },
+	{ 0x96, 0x00 },
+	{ 0x9a, 0x00 },
+	{ 0xb0, 0x84 },
+	{ 0xb1, 0x0c },
+	{ 0xb2, 0x0e },
+	{ 0xb3, 0x82 },
+	{ 0xb8, 0x0a },
+
+/* More reserved magic, some of which tweaks white balance */
+	{ 0x43, 0x0a },
+	{ 0x44, 0xf0 },
+	{ 0x45, 0x34 },
+	{ 0x46, 0x58 },
+	{ 0x47, 0x28 },
+	{ 0x48, 0x3a },
+	{ 0x59, 0x88 },
+	{ 0x5a, 0x88 },
+	{ 0x5b, 0x44 },
+	{ 0x5c, 0x67 },
+	{ 0x5d, 0x49 },
+	{ 0x5e, 0x0e },
+	{ 0x6c, 0x0a },
+	{ 0x6d, 0x55 },
+	{ 0x6e, 0x11 },
+	{ 0x6f, 0x9f },			/* "9e for advance AWB" */
+	{ 0x6a, 0x40 },
+	{ OV7670_R01_BLUE, 0x40 },
+	{ OV7670_R02_RED, 0x60 },
+	{ OV7670_R13_COM8, OV7670_COM8_FASTAEC
+			| OV7670_COM8_AECSTEP
+			| OV7670_COM8_BFILT
+			| OV7670_COM8_AGC
+			| OV7670_COM8_AEC
+			| OV7670_COM8_AWB },
+
+/* Matrix coefficients */
+	{ 0x4f, 0x80 },
+	{ 0x50, 0x80 },
+	{ 0x51, 0x00 },
+	{ 0x52, 0x22 },
+	{ 0x53, 0x5e },
+	{ 0x54, 0x80 },
+	{ 0x58, 0x9e },
+
+	{ OV7670_R41_COM16, OV7670_COM16_AWBGAIN },
+	{ OV7670_R3F_EDGE, 0x00 },
+	{ 0x75, 0x05 },
+	{ 0x76, 0xe1 },
+	{ 0x4c, 0x00 },
+	{ 0x77, 0x01 },
+	{ OV7670_R3D_COM13, OV7670_COM13_GAMMA
+			  | OV7670_COM13_UVSAT
+			  | 2},		/* was 3 */
+	{ 0x4b, 0x09 },
+	{ 0xc9, 0x60 },
+	{ OV7670_R41_COM16, 0x38 },
+	{ 0x56, 0x40 },
+
+	{ 0x34, 0x11 },
+	{ OV7670_R3B_COM11, OV7670_COM11_EXP|OV7670_COM11_HZAUTO },
+	{ 0xa4, 0x88 },
+	{ 0x96, 0x00 },
+	{ 0x97, 0x30 },
+	{ 0x98, 0x20 },
+	{ 0x99, 0x30 },
+	{ 0x9a, 0x84 },
+	{ 0x9b, 0x29 },
+	{ 0x9c, 0x03 },
+	{ 0x9d, 0x4c },
+	{ 0x9e, 0x3f },
+	{ 0x78, 0x04 },
+
+/* Extra-weird stuff.  Some sort of multiplexor register */
+	{ 0x79, 0x01 },
+	{ 0xc8, 0xf0 },
+	{ 0x79, 0x0f },
+	{ 0xc8, 0x00 },
+	{ 0x79, 0x10 },
+	{ 0xc8, 0x7e },
+	{ 0x79, 0x0a },
+	{ 0xc8, 0x80 },
+	{ 0x79, 0x0b },
+	{ 0xc8, 0x01 },
+	{ 0x79, 0x0c },
+	{ 0xc8, 0x0f },
+	{ 0x79, 0x0d },
+	{ 0xc8, 0x20 },
+	{ 0x79, 0x09 },
+	{ 0xc8, 0x80 },
+	{ 0x79, 0x02 },
+	{ 0xc8, 0xc0 },
+	{ 0x79, 0x03 },
+	{ 0xc8, 0x40 },
+	{ 0x79, 0x05 },
+	{ 0xc8, 0x30 },
+	{ 0x79, 0x26 },
+};
+
+static const struct ov_i2c_regvals norm_8610[] = {
+	{ 0x12, 0x80 },
+	{ 0x00, 0x00 },
+	{ 0x01, 0x80 },
+	{ 0x02, 0x80 },
+	{ 0x03, 0xc0 },
+	{ 0x04, 0x30 },
+	{ 0x05, 0x30 }, /* was 0x10, new from windrv 090403 */
+	{ 0x06, 0x70 }, /* was 0x80, new from windrv 090403 */
+	{ 0x0a, 0x86 },
+	{ 0x0b, 0xb0 },
+	{ 0x0c, 0x20 },
+	{ 0x0d, 0x20 },
+	{ 0x11, 0x01 },
+	{ 0x12, 0x25 },
+	{ 0x13, 0x01 },
+	{ 0x14, 0x04 },
+	{ 0x15, 0x01 }, /* Lin and Win think different about UV order */
+	{ 0x16, 0x03 },
+	{ 0x17, 0x38 }, /* was 0x2f, new from windrv 090403 */
+	{ 0x18, 0xea }, /* was 0xcf, new from windrv 090403 */
+	{ 0x19, 0x02 }, /* was 0x06, new from windrv 090403 */
+	{ 0x1a, 0xf5 },
+	{ 0x1b, 0x00 },
+	{ 0x20, 0xd0 }, /* was 0x90, new from windrv 090403 */
+	{ 0x23, 0xc0 }, /* was 0x00, new from windrv 090403 */
+	{ 0x24, 0x30 }, /* was 0x1d, new from windrv 090403 */
+	{ 0x25, 0x50 }, /* was 0x57, new from windrv 090403 */
+	{ 0x26, 0xa2 },
+	{ 0x27, 0xea },
+	{ 0x28, 0x00 },
+	{ 0x29, 0x00 },
+	{ 0x2a, 0x80 },
+	{ 0x2b, 0xc8 }, /* was 0xcc, new from windrv 090403 */
+	{ 0x2c, 0xac },
+	{ 0x2d, 0x45 }, /* was 0xd5, new from windrv 090403 */
+	{ 0x2e, 0x80 },
+	{ 0x2f, 0x14 }, /* was 0x01, new from windrv 090403 */
+	{ 0x4c, 0x00 },
+	{ 0x4d, 0x30 }, /* was 0x10, new from windrv 090403 */
+	{ 0x60, 0x02 }, /* was 0x01, new from windrv 090403 */
+	{ 0x61, 0x00 }, /* was 0x09, new from windrv 090403 */
+	{ 0x62, 0x5f }, /* was 0xd7, new from windrv 090403 */
+	{ 0x63, 0xff },
+	{ 0x64, 0x53 }, /* new windrv 090403 says 0x57,
+			 * maybe thats wrong */
+	{ 0x65, 0x00 },
+	{ 0x66, 0x55 },
+	{ 0x67, 0xb0 },
+	{ 0x68, 0xc0 }, /* was 0xaf, new from windrv 090403 */
+	{ 0x69, 0x02 },
+	{ 0x6a, 0x22 },
+	{ 0x6b, 0x00 },
+	{ 0x6c, 0x99 }, /* was 0x80, old windrv says 0x00, but
+			 * deleting bit7 colors the first images red */
+	{ 0x6d, 0x11 }, /* was 0x00, new from windrv 090403 */
+	{ 0x6e, 0x11 }, /* was 0x00, new from windrv 090403 */
+	{ 0x6f, 0x01 },
+	{ 0x70, 0x8b },
+	{ 0x71, 0x00 },
+	{ 0x72, 0x14 },
+	{ 0x73, 0x54 },
+	{ 0x74, 0x00 },/* 0x60? - was 0x00, new from windrv 090403 */
+	{ 0x75, 0x0e },
+	{ 0x76, 0x02 }, /* was 0x02, new from windrv 090403 */
+	{ 0x77, 0xff },
+	{ 0x78, 0x80 },
+	{ 0x79, 0x80 },
+	{ 0x7a, 0x80 },
+	{ 0x7b, 0x10 }, /* was 0x13, new from windrv 090403 */
+	{ 0x7c, 0x00 },
+	{ 0x7d, 0x08 }, /* was 0x09, new from windrv 090403 */
+	{ 0x7e, 0x08 }, /* was 0xc0, new from windrv 090403 */
+	{ 0x7f, 0xfb },
+	{ 0x80, 0x28 },
+	{ 0x81, 0x00 },
+	{ 0x82, 0x23 },
+	{ 0x83, 0x0b },
+	{ 0x84, 0x00 },
+	{ 0x85, 0x62 }, /* was 0x61, new from windrv 090403 */
+	{ 0x86, 0xc9 },
+	{ 0x87, 0x00 },
+	{ 0x88, 0x00 },
+	{ 0x89, 0x01 },
+	{ 0x12, 0x20 },
+	{ 0x12, 0x25 }, /* was 0x24, new from windrv 090403 */
+};
+
+static unsigned char ov7670_abs_to_sm(unsigned char v)
+{
+	if (v > 127)
+		return v & 0x7f;
+	return (128 - v) | 0x80;
+}
+
+/* Write a OV519 register */
+static void reg_w(struct sd *sd, u16 index, u16 value)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	int ret, req = 0;
+
+	if (sd->gspca_dev.usb_err < 0)
+		return;
+
+	/* Avoid things going to fast for the bridge with a xhci host */
+	udelay(150);
+
+	switch (sd->bridge) {
+	case BRIDGE_OV511:
+	case BRIDGE_OV511PLUS:
+		req = 2;
+		break;
+	case BRIDGE_OVFX2:
+		req = 0x0a;
+		/* fall through */
+	case BRIDGE_W9968CF:
+		gspca_dbg(gspca_dev, D_USBO, "SET %02x %04x %04x\n",
+			  req, value, index);
+		ret = usb_control_msg(sd->gspca_dev.dev,
+			usb_sndctrlpipe(sd->gspca_dev.dev, 0),
+			req,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			value, index, NULL, 0, 500);
+		goto leave;
+	default:
+		req = 1;
+	}
+
+	gspca_dbg(gspca_dev, D_USBO, "SET %02x 0000 %04x %02x\n",
+		  req, index, value);
+	sd->gspca_dev.usb_buf[0] = value;
+	ret = usb_control_msg(sd->gspca_dev.dev,
+			usb_sndctrlpipe(sd->gspca_dev.dev, 0),
+			req,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0, index,
+			sd->gspca_dev.usb_buf, 1, 500);
+leave:
+	if (ret < 0) {
+		gspca_err(gspca_dev, "reg_w %02x failed %d\n", index, ret);
+		sd->gspca_dev.usb_err = ret;
+		return;
+	}
+}
+
+/* Read from a OV519 register, note not valid for the w9968cf!! */
+/* returns: negative is error, pos or zero is data */
+static int reg_r(struct sd *sd, u16 index)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	int ret;
+	int req;
+
+	if (sd->gspca_dev.usb_err < 0)
+		return -1;
+
+	switch (sd->bridge) {
+	case BRIDGE_OV511:
+	case BRIDGE_OV511PLUS:
+		req = 3;
+		break;
+	case BRIDGE_OVFX2:
+		req = 0x0b;
+		break;
+	default:
+		req = 1;
+	}
+
+	/* Avoid things going to fast for the bridge with a xhci host */
+	udelay(150);
+	ret = usb_control_msg(sd->gspca_dev.dev,
+			usb_rcvctrlpipe(sd->gspca_dev.dev, 0),
+			req,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0, index, sd->gspca_dev.usb_buf, 1, 500);
+
+	if (ret >= 0) {
+		ret = sd->gspca_dev.usb_buf[0];
+		gspca_dbg(gspca_dev, D_USBI, "GET %02x 0000 %04x %02x\n",
+			  req, index, ret);
+	} else {
+		gspca_err(gspca_dev, "reg_r %02x failed %d\n", index, ret);
+		sd->gspca_dev.usb_err = ret;
+	}
+
+	return ret;
+}
+
+/* Read 8 values from a OV519 register */
+static int reg_r8(struct sd *sd,
+		  u16 index)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	int ret;
+
+	if (sd->gspca_dev.usb_err < 0)
+		return -1;
+
+	/* Avoid things going to fast for the bridge with a xhci host */
+	udelay(150);
+	ret = usb_control_msg(sd->gspca_dev.dev,
+			usb_rcvctrlpipe(sd->gspca_dev.dev, 0),
+			1,			/* REQ_IO */
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0, index, sd->gspca_dev.usb_buf, 8, 500);
+
+	if (ret >= 0) {
+		ret = sd->gspca_dev.usb_buf[0];
+	} else {
+		gspca_err(gspca_dev, "reg_r8 %02x failed %d\n", index, ret);
+		sd->gspca_dev.usb_err = ret;
+	}
+
+	return ret;
+}
+
+/*
+ * Writes bits at positions specified by mask to an OV51x reg. Bits that are in
+ * the same position as 1's in "mask" are cleared and set to "value". Bits
+ * that are in the same position as 0's in "mask" are preserved, regardless
+ * of their respective state in "value".
+ */
+static void reg_w_mask(struct sd *sd,
+			u16 index,
+			u8 value,
+			u8 mask)
+{
+	int ret;
+	u8 oldval;
+
+	if (mask != 0xff) {
+		value &= mask;			/* Enforce mask on value */
+		ret = reg_r(sd, index);
+		if (ret < 0)
+			return;
+
+		oldval = ret & ~mask;		/* Clear the masked bits */
+		value |= oldval;		/* Set the desired bits */
+	}
+	reg_w(sd, index, value);
+}
+
+/*
+ * Writes multiple (n) byte value to a single register. Only valid with certain
+ * registers (0x30 and 0xc4 - 0xce).
+ */
+static void ov518_reg_w32(struct sd *sd, u16 index, u32 value, int n)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	int ret;
+
+	if (sd->gspca_dev.usb_err < 0)
+		return;
+
+	*((__le32 *) sd->gspca_dev.usb_buf) = __cpu_to_le32(value);
+
+	/* Avoid things going to fast for the bridge with a xhci host */
+	udelay(150);
+	ret = usb_control_msg(sd->gspca_dev.dev,
+			usb_sndctrlpipe(sd->gspca_dev.dev, 0),
+			1 /* REG_IO */,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0, index,
+			sd->gspca_dev.usb_buf, n, 500);
+	if (ret < 0) {
+		gspca_err(gspca_dev, "reg_w32 %02x failed %d\n", index, ret);
+		sd->gspca_dev.usb_err = ret;
+	}
+}
+
+static void ov511_i2c_w(struct sd *sd, u8 reg, u8 value)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	int rc, retries;
+
+	gspca_dbg(gspca_dev, D_USBO, "ov511_i2c_w %02x %02x\n", reg, value);
+
+	/* Three byte write cycle */
+	for (retries = 6; ; ) {
+		/* Select camera register */
+		reg_w(sd, R51x_I2C_SADDR_3, reg);
+
+		/* Write "value" to I2C data port of OV511 */
+		reg_w(sd, R51x_I2C_DATA, value);
+
+		/* Initiate 3-byte write cycle */
+		reg_w(sd, R511_I2C_CTL, 0x01);
+
+		do {
+			rc = reg_r(sd, R511_I2C_CTL);
+		} while (rc > 0 && ((rc & 1) == 0)); /* Retry until idle */
+
+		if (rc < 0)
+			return;
+
+		if ((rc & 2) == 0) /* Ack? */
+			break;
+		if (--retries < 0) {
+			gspca_dbg(gspca_dev, D_USBO, "i2c write retries exhausted\n");
+			return;
+		}
+	}
+}
+
+static int ov511_i2c_r(struct sd *sd, u8 reg)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	int rc, value, retries;
+
+	/* Two byte write cycle */
+	for (retries = 6; ; ) {
+		/* Select camera register */
+		reg_w(sd, R51x_I2C_SADDR_2, reg);
+
+		/* Initiate 2-byte write cycle */
+		reg_w(sd, R511_I2C_CTL, 0x03);
+
+		do {
+			rc = reg_r(sd, R511_I2C_CTL);
+		} while (rc > 0 && ((rc & 1) == 0)); /* Retry until idle */
+
+		if (rc < 0)
+			return rc;
+
+		if ((rc & 2) == 0) /* Ack? */
+			break;
+
+		/* I2C abort */
+		reg_w(sd, R511_I2C_CTL, 0x10);
+
+		if (--retries < 0) {
+			gspca_dbg(gspca_dev, D_USBI, "i2c write retries exhausted\n");
+			return -1;
+		}
+	}
+
+	/* Two byte read cycle */
+	for (retries = 6; ; ) {
+		/* Initiate 2-byte read cycle */
+		reg_w(sd, R511_I2C_CTL, 0x05);
+
+		do {
+			rc = reg_r(sd, R511_I2C_CTL);
+		} while (rc > 0 && ((rc & 1) == 0)); /* Retry until idle */
+
+		if (rc < 0)
+			return rc;
+
+		if ((rc & 2) == 0) /* Ack? */
+			break;
+
+		/* I2C abort */
+		reg_w(sd, R511_I2C_CTL, 0x10);
+
+		if (--retries < 0) {
+			gspca_dbg(gspca_dev, D_USBI, "i2c read retries exhausted\n");
+			return -1;
+		}
+	}
+
+	value = reg_r(sd, R51x_I2C_DATA);
+
+	gspca_dbg(gspca_dev, D_USBI, "ov511_i2c_r %02x %02x\n", reg, value);
+
+	/* This is needed to make i2c_w() work */
+	reg_w(sd, R511_I2C_CTL, 0x05);
+
+	return value;
+}
+
+/*
+ * The OV518 I2C I/O procedure is different, hence, this function.
+ * This is normally only called from i2c_w(). Note that this function
+ * always succeeds regardless of whether the sensor is present and working.
+ */
+static void ov518_i2c_w(struct sd *sd,
+		u8 reg,
+		u8 value)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+
+	gspca_dbg(gspca_dev, D_USBO, "ov518_i2c_w %02x %02x\n", reg, value);
+
+	/* Select camera register */
+	reg_w(sd, R51x_I2C_SADDR_3, reg);
+
+	/* Write "value" to I2C data port of OV511 */
+	reg_w(sd, R51x_I2C_DATA, value);
+
+	/* Initiate 3-byte write cycle */
+	reg_w(sd, R518_I2C_CTL, 0x01);
+
+	/* wait for write complete */
+	msleep(4);
+	reg_r8(sd, R518_I2C_CTL);
+}
+
+/*
+ * returns: negative is error, pos or zero is data
+ *
+ * The OV518 I2C I/O procedure is different, hence, this function.
+ * This is normally only called from i2c_r(). Note that this function
+ * always succeeds regardless of whether the sensor is present and working.
+ */
+static int ov518_i2c_r(struct sd *sd, u8 reg)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	int value;
+
+	/* Select camera register */
+	reg_w(sd, R51x_I2C_SADDR_2, reg);
+
+	/* Initiate 2-byte write cycle */
+	reg_w(sd, R518_I2C_CTL, 0x03);
+	reg_r8(sd, R518_I2C_CTL);
+
+	/* Initiate 2-byte read cycle */
+	reg_w(sd, R518_I2C_CTL, 0x05);
+	reg_r8(sd, R518_I2C_CTL);
+
+	value = reg_r(sd, R51x_I2C_DATA);
+	gspca_dbg(gspca_dev, D_USBI, "ov518_i2c_r %02x %02x\n", reg, value);
+	return value;
+}
+
+static void ovfx2_i2c_w(struct sd *sd, u8 reg, u8 value)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	int ret;
+
+	if (sd->gspca_dev.usb_err < 0)
+		return;
+
+	ret = usb_control_msg(sd->gspca_dev.dev,
+			usb_sndctrlpipe(sd->gspca_dev.dev, 0),
+			0x02,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			(u16) value, (u16) reg, NULL, 0, 500);
+
+	if (ret < 0) {
+		gspca_err(gspca_dev, "ovfx2_i2c_w %02x failed %d\n", reg, ret);
+		sd->gspca_dev.usb_err = ret;
+	}
+
+	gspca_dbg(gspca_dev, D_USBO, "ovfx2_i2c_w %02x %02x\n", reg, value);
+}
+
+static int ovfx2_i2c_r(struct sd *sd, u8 reg)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	int ret;
+
+	if (sd->gspca_dev.usb_err < 0)
+		return -1;
+
+	ret = usb_control_msg(sd->gspca_dev.dev,
+			usb_rcvctrlpipe(sd->gspca_dev.dev, 0),
+			0x03,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0, (u16) reg, sd->gspca_dev.usb_buf, 1, 500);
+
+	if (ret >= 0) {
+		ret = sd->gspca_dev.usb_buf[0];
+		gspca_dbg(gspca_dev, D_USBI, "ovfx2_i2c_r %02x %02x\n",
+			  reg, ret);
+	} else {
+		gspca_err(gspca_dev, "ovfx2_i2c_r %02x failed %d\n", reg, ret);
+		sd->gspca_dev.usb_err = ret;
+	}
+
+	return ret;
+}
+
+static void i2c_w(struct sd *sd, u8 reg, u8 value)
+{
+	if (sd->sensor_reg_cache[reg] == value)
+		return;
+
+	switch (sd->bridge) {
+	case BRIDGE_OV511:
+	case BRIDGE_OV511PLUS:
+		ov511_i2c_w(sd, reg, value);
+		break;
+	case BRIDGE_OV518:
+	case BRIDGE_OV518PLUS:
+	case BRIDGE_OV519:
+		ov518_i2c_w(sd, reg, value);
+		break;
+	case BRIDGE_OVFX2:
+		ovfx2_i2c_w(sd, reg, value);
+		break;
+	case BRIDGE_W9968CF:
+		w9968cf_i2c_w(sd, reg, value);
+		break;
+	}
+
+	if (sd->gspca_dev.usb_err >= 0) {
+		/* Up on sensor reset empty the register cache */
+		if (reg == 0x12 && (value & 0x80))
+			memset(sd->sensor_reg_cache, -1,
+				sizeof(sd->sensor_reg_cache));
+		else
+			sd->sensor_reg_cache[reg] = value;
+	}
+}
+
+static int i2c_r(struct sd *sd, u8 reg)
+{
+	int ret = -1;
+
+	if (sd->sensor_reg_cache[reg] != -1)
+		return sd->sensor_reg_cache[reg];
+
+	switch (sd->bridge) {
+	case BRIDGE_OV511:
+	case BRIDGE_OV511PLUS:
+		ret = ov511_i2c_r(sd, reg);
+		break;
+	case BRIDGE_OV518:
+	case BRIDGE_OV518PLUS:
+	case BRIDGE_OV519:
+		ret = ov518_i2c_r(sd, reg);
+		break;
+	case BRIDGE_OVFX2:
+		ret = ovfx2_i2c_r(sd, reg);
+		break;
+	case BRIDGE_W9968CF:
+		ret = w9968cf_i2c_r(sd, reg);
+		break;
+	}
+
+	if (ret >= 0)
+		sd->sensor_reg_cache[reg] = ret;
+
+	return ret;
+}
+
+/* Writes bits at positions specified by mask to an I2C reg. Bits that are in
+ * the same position as 1's in "mask" are cleared and set to "value". Bits
+ * that are in the same position as 0's in "mask" are preserved, regardless
+ * of their respective state in "value".
+ */
+static void i2c_w_mask(struct sd *sd,
+			u8 reg,
+			u8 value,
+			u8 mask)
+{
+	int rc;
+	u8 oldval;
+
+	value &= mask;			/* Enforce mask on value */
+	rc = i2c_r(sd, reg);
+	if (rc < 0)
+		return;
+	oldval = rc & ~mask;		/* Clear the masked bits */
+	value |= oldval;		/* Set the desired bits */
+	i2c_w(sd, reg, value);
+}
+
+/* Temporarily stops OV511 from functioning. Must do this before changing
+ * registers while the camera is streaming */
+static inline void ov51x_stop(struct sd *sd)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+
+	gspca_dbg(gspca_dev, D_STREAM, "stopping\n");
+	sd->stopped = 1;
+	switch (sd->bridge) {
+	case BRIDGE_OV511:
+	case BRIDGE_OV511PLUS:
+		reg_w(sd, R51x_SYS_RESET, 0x3d);
+		break;
+	case BRIDGE_OV518:
+	case BRIDGE_OV518PLUS:
+		reg_w_mask(sd, R51x_SYS_RESET, 0x3a, 0x3a);
+		break;
+	case BRIDGE_OV519:
+		reg_w(sd, OV519_R51_RESET1, 0x0f);
+		reg_w(sd, OV519_R51_RESET1, 0x00);
+		reg_w(sd, 0x22, 0x00);		/* FRAR */
+		break;
+	case BRIDGE_OVFX2:
+		reg_w_mask(sd, 0x0f, 0x00, 0x02);
+		break;
+	case BRIDGE_W9968CF:
+		reg_w(sd, 0x3c, 0x0a05); /* stop USB transfer */
+		break;
+	}
+}
+
+/* Restarts OV511 after ov511_stop() is called. Has no effect if it is not
+ * actually stopped (for performance). */
+static inline void ov51x_restart(struct sd *sd)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+
+	gspca_dbg(gspca_dev, D_STREAM, "restarting\n");
+	if (!sd->stopped)
+		return;
+	sd->stopped = 0;
+
+	/* Reinitialize the stream */
+	switch (sd->bridge) {
+	case BRIDGE_OV511:
+	case BRIDGE_OV511PLUS:
+		reg_w(sd, R51x_SYS_RESET, 0x00);
+		break;
+	case BRIDGE_OV518:
+	case BRIDGE_OV518PLUS:
+		reg_w(sd, 0x2f, 0x80);
+		reg_w(sd, R51x_SYS_RESET, 0x00);
+		break;
+	case BRIDGE_OV519:
+		reg_w(sd, OV519_R51_RESET1, 0x0f);
+		reg_w(sd, OV519_R51_RESET1, 0x00);
+		reg_w(sd, 0x22, 0x1d);		/* FRAR */
+		break;
+	case BRIDGE_OVFX2:
+		reg_w_mask(sd, 0x0f, 0x02, 0x02);
+		break;
+	case BRIDGE_W9968CF:
+		reg_w(sd, 0x3c, 0x8a05); /* USB FIFO enable */
+		break;
+	}
+}
+
+static void ov51x_set_slave_ids(struct sd *sd, u8 slave);
+
+/* This does an initial reset of an OmniVision sensor and ensures that I2C
+ * is synchronized. Returns <0 on failure.
+ */
+static int init_ov_sensor(struct sd *sd, u8 slave)
+{
+	int i;
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+
+	ov51x_set_slave_ids(sd, slave);
+
+	/* Reset the sensor */
+	i2c_w(sd, 0x12, 0x80);
+
+	/* Wait for it to initialize */
+	msleep(150);
+
+	for (i = 0; i < i2c_detect_tries; i++) {
+		if (i2c_r(sd, OV7610_REG_ID_HIGH) == 0x7f &&
+		    i2c_r(sd, OV7610_REG_ID_LOW) == 0xa2) {
+			gspca_dbg(gspca_dev, D_PROBE, "I2C synced in %d attempt(s)\n",
+				  i);
+			return 0;
+		}
+
+		/* Reset the sensor */
+		i2c_w(sd, 0x12, 0x80);
+
+		/* Wait for it to initialize */
+		msleep(150);
+
+		/* Dummy read to sync I2C */
+		if (i2c_r(sd, 0x00) < 0)
+			return -1;
+	}
+	return -1;
+}
+
+/* Set the read and write slave IDs. The "slave" argument is the write slave,
+ * and the read slave will be set to (slave + 1).
+ * This should not be called from outside the i2c I/O functions.
+ * Sets I2C read and write slave IDs. Returns <0 for error
+ */
+static void ov51x_set_slave_ids(struct sd *sd,
+				u8 slave)
+{
+	switch (sd->bridge) {
+	case BRIDGE_OVFX2:
+		reg_w(sd, OVFX2_I2C_ADDR, slave);
+		return;
+	case BRIDGE_W9968CF:
+		sd->sensor_addr = slave;
+		return;
+	}
+
+	reg_w(sd, R51x_I2C_W_SID, slave);
+	reg_w(sd, R51x_I2C_R_SID, slave + 1);
+}
+
+static void write_regvals(struct sd *sd,
+			 const struct ov_regvals *regvals,
+			 int n)
+{
+	while (--n >= 0) {
+		reg_w(sd, regvals->reg, regvals->val);
+		regvals++;
+	}
+}
+
+static void write_i2c_regvals(struct sd *sd,
+			const struct ov_i2c_regvals *regvals,
+			int n)
+{
+	while (--n >= 0) {
+		i2c_w(sd, regvals->reg, regvals->val);
+		regvals++;
+	}
+}
+
+/****************************************************************************
+ *
+ * OV511 and sensor configuration
+ *
+ ***************************************************************************/
+
+/* This initializes the OV2x10 / OV3610 / OV3620 / OV9600 */
+static void ov_hires_configure(struct sd *sd)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	int high, low;
+
+	if (sd->bridge != BRIDGE_OVFX2) {
+		gspca_err(gspca_dev, "error hires sensors only supported with ovfx2\n");
+		return;
+	}
+
+	gspca_dbg(gspca_dev, D_PROBE, "starting ov hires configuration\n");
+
+	/* Detect sensor (sub)type */
+	high = i2c_r(sd, 0x0a);
+	low = i2c_r(sd, 0x0b);
+	/* info("%x, %x", high, low); */
+	switch (high) {
+	case 0x96:
+		switch (low) {
+		case 0x40:
+			gspca_dbg(gspca_dev, D_PROBE, "Sensor is a OV2610\n");
+			sd->sensor = SEN_OV2610;
+			return;
+		case 0x41:
+			gspca_dbg(gspca_dev, D_PROBE, "Sensor is a OV2610AE\n");
+			sd->sensor = SEN_OV2610AE;
+			return;
+		case 0xb1:
+			gspca_dbg(gspca_dev, D_PROBE, "Sensor is a OV9600\n");
+			sd->sensor = SEN_OV9600;
+			return;
+		}
+		break;
+	case 0x36:
+		if ((low & 0x0f) == 0x00) {
+			gspca_dbg(gspca_dev, D_PROBE, "Sensor is a OV3610\n");
+			sd->sensor = SEN_OV3610;
+			return;
+		}
+		break;
+	}
+	gspca_err(gspca_dev, "Error unknown sensor type: %02x%02x\n",
+		  high, low);
+}
+
+/* This initializes the OV8110, OV8610 sensor. The OV8110 uses
+ * the same register settings as the OV8610, since they are very similar.
+ */
+static void ov8xx0_configure(struct sd *sd)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	int rc;
+
+	gspca_dbg(gspca_dev, D_PROBE, "starting ov8xx0 configuration\n");
+
+	/* Detect sensor (sub)type */
+	rc = i2c_r(sd, OV7610_REG_COM_I);
+	if (rc < 0) {
+		gspca_err(gspca_dev, "Error detecting sensor type\n");
+		return;
+	}
+	if ((rc & 3) == 1)
+		sd->sensor = SEN_OV8610;
+	else
+		gspca_err(gspca_dev, "Unknown image sensor version: %d\n",
+			  rc & 3);
+}
+
+/* This initializes the OV7610, OV7620, or OV76BE sensor. The OV76BE uses
+ * the same register settings as the OV7610, since they are very similar.
+ */
+static void ov7xx0_configure(struct sd *sd)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	int rc, high, low;
+
+	gspca_dbg(gspca_dev, D_PROBE, "starting OV7xx0 configuration\n");
+
+	/* Detect sensor (sub)type */
+	rc = i2c_r(sd, OV7610_REG_COM_I);
+
+	/* add OV7670 here
+	 * it appears to be wrongly detected as a 7610 by default */
+	if (rc < 0) {
+		gspca_err(gspca_dev, "Error detecting sensor type\n");
+		return;
+	}
+	if ((rc & 3) == 3) {
+		/* quick hack to make OV7670s work */
+		high = i2c_r(sd, 0x0a);
+		low = i2c_r(sd, 0x0b);
+		/* info("%x, %x", high, low); */
+		if (high == 0x76 && (low & 0xf0) == 0x70) {
+			gspca_dbg(gspca_dev, D_PROBE, "Sensor is an OV76%02x\n",
+				  low);
+			sd->sensor = SEN_OV7670;
+		} else {
+			gspca_dbg(gspca_dev, D_PROBE, "Sensor is an OV7610\n");
+			sd->sensor = SEN_OV7610;
+		}
+	} else if ((rc & 3) == 1) {
+		/* I don't know what's different about the 76BE yet. */
+		if (i2c_r(sd, 0x15) & 1) {
+			gspca_dbg(gspca_dev, D_PROBE, "Sensor is an OV7620AE\n");
+			sd->sensor = SEN_OV7620AE;
+		} else {
+			gspca_dbg(gspca_dev, D_PROBE, "Sensor is an OV76BE\n");
+			sd->sensor = SEN_OV76BE;
+		}
+	} else if ((rc & 3) == 0) {
+		/* try to read product id registers */
+		high = i2c_r(sd, 0x0a);
+		if (high < 0) {
+			gspca_err(gspca_dev, "Error detecting camera chip PID\n");
+			return;
+		}
+		low = i2c_r(sd, 0x0b);
+		if (low < 0) {
+			gspca_err(gspca_dev, "Error detecting camera chip VER\n");
+			return;
+		}
+		if (high == 0x76) {
+			switch (low) {
+			case 0x30:
+				gspca_err(gspca_dev, "Sensor is an OV7630/OV7635\n");
+				gspca_err(gspca_dev, "7630 is not supported by this driver\n");
+				return;
+			case 0x40:
+				gspca_dbg(gspca_dev, D_PROBE, "Sensor is an OV7645\n");
+				sd->sensor = SEN_OV7640; /* FIXME */
+				break;
+			case 0x45:
+				gspca_dbg(gspca_dev, D_PROBE, "Sensor is an OV7645B\n");
+				sd->sensor = SEN_OV7640; /* FIXME */
+				break;
+			case 0x48:
+				gspca_dbg(gspca_dev, D_PROBE, "Sensor is an OV7648\n");
+				sd->sensor = SEN_OV7648;
+				break;
+			case 0x60:
+				gspca_dbg(gspca_dev, D_PROBE, "Sensor is a OV7660\n");
+				sd->sensor = SEN_OV7660;
+				break;
+			default:
+				gspca_err(gspca_dev, "Unknown sensor: 0x76%02x\n",
+					  low);
+				return;
+			}
+		} else {
+			gspca_dbg(gspca_dev, D_PROBE, "Sensor is an OV7620\n");
+			sd->sensor = SEN_OV7620;
+		}
+	} else {
+		gspca_err(gspca_dev, "Unknown image sensor version: %d\n",
+			  rc & 3);
+	}
+}
+
+/* This initializes the OV6620, OV6630, OV6630AE, or OV6630AF sensor. */
+static void ov6xx0_configure(struct sd *sd)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	int rc;
+
+	gspca_dbg(gspca_dev, D_PROBE, "starting OV6xx0 configuration\n");
+
+	/* Detect sensor (sub)type */
+	rc = i2c_r(sd, OV7610_REG_COM_I);
+	if (rc < 0) {
+		gspca_err(gspca_dev, "Error detecting sensor type\n");
+		return;
+	}
+
+	/* Ugh. The first two bits are the version bits, but
+	 * the entire register value must be used. I guess OVT
+	 * underestimated how many variants they would make. */
+	switch (rc) {
+	case 0x00:
+		sd->sensor = SEN_OV6630;
+		pr_warn("WARNING: Sensor is an OV66308. Your camera may have been misdetected in previous driver versions.\n");
+		break;
+	case 0x01:
+		sd->sensor = SEN_OV6620;
+		gspca_dbg(gspca_dev, D_PROBE, "Sensor is an OV6620\n");
+		break;
+	case 0x02:
+		sd->sensor = SEN_OV6630;
+		gspca_dbg(gspca_dev, D_PROBE, "Sensor is an OV66308AE\n");
+		break;
+	case 0x03:
+		sd->sensor = SEN_OV66308AF;
+		gspca_dbg(gspca_dev, D_PROBE, "Sensor is an OV66308AF\n");
+		break;
+	case 0x90:
+		sd->sensor = SEN_OV6630;
+		pr_warn("WARNING: Sensor is an OV66307. Your camera may have been misdetected in previous driver versions.\n");
+		break;
+	default:
+		gspca_err(gspca_dev, "FATAL: Unknown sensor version: 0x%02x\n",
+			  rc);
+		return;
+	}
+
+	/* Set sensor-specific vars */
+	sd->sif = 1;
+}
+
+/* Turns on or off the LED. Only has an effect with OV511+/OV518(+)/OV519 */
+static void ov51x_led_control(struct sd *sd, int on)
+{
+	if (sd->invert_led)
+		on = !on;
+
+	switch (sd->bridge) {
+	/* OV511 has no LED control */
+	case BRIDGE_OV511PLUS:
+		reg_w(sd, R511_SYS_LED_CTL, on);
+		break;
+	case BRIDGE_OV518:
+	case BRIDGE_OV518PLUS:
+		reg_w_mask(sd, R518_GPIO_OUT, 0x02 * on, 0x02);
+		break;
+	case BRIDGE_OV519:
+		reg_w_mask(sd, OV519_GPIO_DATA_OUT0, on, 1);
+		break;
+	}
+}
+
+static void sd_reset_snapshot(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (!sd->snapshot_needs_reset)
+		return;
+
+	/* Note it is important that we clear sd->snapshot_needs_reset,
+	   before actually clearing the snapshot state in the bridge
+	   otherwise we might race with the pkt_scan interrupt handler */
+	sd->snapshot_needs_reset = 0;
+
+	switch (sd->bridge) {
+	case BRIDGE_OV511:
+	case BRIDGE_OV511PLUS:
+		reg_w(sd, R51x_SYS_SNAP, 0x02);
+		reg_w(sd, R51x_SYS_SNAP, 0x00);
+		break;
+	case BRIDGE_OV518:
+	case BRIDGE_OV518PLUS:
+		reg_w(sd, R51x_SYS_SNAP, 0x02); /* Reset */
+		reg_w(sd, R51x_SYS_SNAP, 0x01); /* Enable */
+		break;
+	case BRIDGE_OV519:
+		reg_w(sd, R51x_SYS_RESET, 0x40);
+		reg_w(sd, R51x_SYS_RESET, 0x00);
+		break;
+	}
+}
+
+static void ov51x_upload_quan_tables(struct sd *sd)
+{
+	static const unsigned char yQuanTable511[] = {
+		0, 1, 1, 2, 2, 3, 3, 4,
+		1, 1, 1, 2, 2, 3, 4, 4,
+		1, 1, 2, 2, 3, 4, 4, 4,
+		2, 2, 2, 3, 4, 4, 4, 4,
+		2, 2, 3, 4, 4, 5, 5, 5,
+		3, 3, 4, 4, 5, 5, 5, 5,
+		3, 4, 4, 4, 5, 5, 5, 5,
+		4, 4, 4, 4, 5, 5, 5, 5
+	};
+
+	static const unsigned char uvQuanTable511[] = {
+		0, 2, 2, 3, 4, 4, 4, 4,
+		2, 2, 2, 4, 4, 4, 4, 4,
+		2, 2, 3, 4, 4, 4, 4, 4,
+		3, 4, 4, 4, 4, 4, 4, 4,
+		4, 4, 4, 4, 4, 4, 4, 4,
+		4, 4, 4, 4, 4, 4, 4, 4,
+		4, 4, 4, 4, 4, 4, 4, 4,
+		4, 4, 4, 4, 4, 4, 4, 4
+	};
+
+	/* OV518 quantization tables are 8x4 (instead of 8x8) */
+	static const unsigned char yQuanTable518[] = {
+		5, 4, 5, 6, 6, 7, 7, 7,
+		5, 5, 5, 5, 6, 7, 7, 7,
+		6, 6, 6, 6, 7, 7, 7, 8,
+		7, 7, 6, 7, 7, 7, 8, 8
+	};
+	static const unsigned char uvQuanTable518[] = {
+		6, 6, 6, 7, 7, 7, 7, 7,
+		6, 6, 6, 7, 7, 7, 7, 7,
+		6, 6, 6, 7, 7, 7, 7, 8,
+		7, 7, 7, 7, 7, 7, 8, 8
+	};
+
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	const unsigned char *pYTable, *pUVTable;
+	unsigned char val0, val1;
+	int i, size, reg = R51x_COMP_LUT_BEGIN;
+
+	gspca_dbg(gspca_dev, D_PROBE, "Uploading quantization tables\n");
+
+	if (sd->bridge == BRIDGE_OV511 || sd->bridge == BRIDGE_OV511PLUS) {
+		pYTable = yQuanTable511;
+		pUVTable = uvQuanTable511;
+		size = 32;
+	} else {
+		pYTable = yQuanTable518;
+		pUVTable = uvQuanTable518;
+		size = 16;
+	}
+
+	for (i = 0; i < size; i++) {
+		val0 = *pYTable++;
+		val1 = *pYTable++;
+		val0 &= 0x0f;
+		val1 &= 0x0f;
+		val0 |= val1 << 4;
+		reg_w(sd, reg, val0);
+
+		val0 = *pUVTable++;
+		val1 = *pUVTable++;
+		val0 &= 0x0f;
+		val1 &= 0x0f;
+		val0 |= val1 << 4;
+		reg_w(sd, reg + size, val0);
+
+		reg++;
+	}
+}
+
+/* This initializes the OV511/OV511+ and the sensor */
+static void ov511_configure(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	/* For 511 and 511+ */
+	static const struct ov_regvals init_511[] = {
+		{ R51x_SYS_RESET,	0x7f },
+		{ R51x_SYS_INIT,	0x01 },
+		{ R51x_SYS_RESET,	0x7f },
+		{ R51x_SYS_INIT,	0x01 },
+		{ R51x_SYS_RESET,	0x3f },
+		{ R51x_SYS_INIT,	0x01 },
+		{ R51x_SYS_RESET,	0x3d },
+	};
+
+	static const struct ov_regvals norm_511[] = {
+		{ R511_DRAM_FLOW_CTL,	0x01 },
+		{ R51x_SYS_SNAP,	0x00 },
+		{ R51x_SYS_SNAP,	0x02 },
+		{ R51x_SYS_SNAP,	0x00 },
+		{ R511_FIFO_OPTS,	0x1f },
+		{ R511_COMP_EN,		0x00 },
+		{ R511_COMP_LUT_EN,	0x03 },
+	};
+
+	static const struct ov_regvals norm_511_p[] = {
+		{ R511_DRAM_FLOW_CTL,	0xff },
+		{ R51x_SYS_SNAP,	0x00 },
+		{ R51x_SYS_SNAP,	0x02 },
+		{ R51x_SYS_SNAP,	0x00 },
+		{ R511_FIFO_OPTS,	0xff },
+		{ R511_COMP_EN,		0x00 },
+		{ R511_COMP_LUT_EN,	0x03 },
+	};
+
+	static const struct ov_regvals compress_511[] = {
+		{ 0x70, 0x1f },
+		{ 0x71, 0x05 },
+		{ 0x72, 0x06 },
+		{ 0x73, 0x06 },
+		{ 0x74, 0x14 },
+		{ 0x75, 0x03 },
+		{ 0x76, 0x04 },
+		{ 0x77, 0x04 },
+	};
+
+	gspca_dbg(gspca_dev, D_PROBE, "Device custom id %x\n",
+		  reg_r(sd, R51x_SYS_CUST_ID));
+
+	write_regvals(sd, init_511, ARRAY_SIZE(init_511));
+
+	switch (sd->bridge) {
+	case BRIDGE_OV511:
+		write_regvals(sd, norm_511, ARRAY_SIZE(norm_511));
+		break;
+	case BRIDGE_OV511PLUS:
+		write_regvals(sd, norm_511_p, ARRAY_SIZE(norm_511_p));
+		break;
+	}
+
+	/* Init compression */
+	write_regvals(sd, compress_511, ARRAY_SIZE(compress_511));
+
+	ov51x_upload_quan_tables(sd);
+}
+
+/* This initializes the OV518/OV518+ and the sensor */
+static void ov518_configure(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	/* For 518 and 518+ */
+	static const struct ov_regvals init_518[] = {
+		{ R51x_SYS_RESET,	0x40 },
+		{ R51x_SYS_INIT,	0xe1 },
+		{ R51x_SYS_RESET,	0x3e },
+		{ R51x_SYS_INIT,	0xe1 },
+		{ R51x_SYS_RESET,	0x00 },
+		{ R51x_SYS_INIT,	0xe1 },
+		{ 0x46,			0x00 },
+		{ 0x5d,			0x03 },
+	};
+
+	static const struct ov_regvals norm_518[] = {
+		{ R51x_SYS_SNAP,	0x02 }, /* Reset */
+		{ R51x_SYS_SNAP,	0x01 }, /* Enable */
+		{ 0x31,			0x0f },
+		{ 0x5d,			0x03 },
+		{ 0x24,			0x9f },
+		{ 0x25,			0x90 },
+		{ 0x20,			0x00 },
+		{ 0x51,			0x04 },
+		{ 0x71,			0x19 },
+		{ 0x2f,			0x80 },
+	};
+
+	static const struct ov_regvals norm_518_p[] = {
+		{ R51x_SYS_SNAP,	0x02 }, /* Reset */
+		{ R51x_SYS_SNAP,	0x01 }, /* Enable */
+		{ 0x31,			0x0f },
+		{ 0x5d,			0x03 },
+		{ 0x24,			0x9f },
+		{ 0x25,			0x90 },
+		{ 0x20,			0x60 },
+		{ 0x51,			0x02 },
+		{ 0x71,			0x19 },
+		{ 0x40,			0xff },
+		{ 0x41,			0x42 },
+		{ 0x46,			0x00 },
+		{ 0x33,			0x04 },
+		{ 0x21,			0x19 },
+		{ 0x3f,			0x10 },
+		{ 0x2f,			0x80 },
+	};
+
+	/* First 5 bits of custom ID reg are a revision ID on OV518 */
+	sd->revision = reg_r(sd, R51x_SYS_CUST_ID) & 0x1f;
+	gspca_dbg(gspca_dev, D_PROBE, "Device revision %d\n", sd->revision);
+
+	write_regvals(sd, init_518, ARRAY_SIZE(init_518));
+
+	/* Set LED GPIO pin to output mode */
+	reg_w_mask(sd, R518_GPIO_CTL, 0x00, 0x02);
+
+	switch (sd->bridge) {
+	case BRIDGE_OV518:
+		write_regvals(sd, norm_518, ARRAY_SIZE(norm_518));
+		break;
+	case BRIDGE_OV518PLUS:
+		write_regvals(sd, norm_518_p, ARRAY_SIZE(norm_518_p));
+		break;
+	}
+
+	ov51x_upload_quan_tables(sd);
+
+	reg_w(sd, 0x2f, 0x80);
+}
+
+static void ov519_configure(struct sd *sd)
+{
+	static const struct ov_regvals init_519[] = {
+		{ 0x5a, 0x6d }, /* EnableSystem */
+		{ 0x53, 0x9b }, /* don't enable the microcontroller */
+		{ OV519_R54_EN_CLK1, 0xff }, /* set bit2 to enable jpeg */
+		{ 0x5d, 0x03 },
+		{ 0x49, 0x01 },
+		{ 0x48, 0x00 },
+		/* Set LED pin to output mode. Bit 4 must be cleared or sensor
+		 * detection will fail. This deserves further investigation. */
+		{ OV519_GPIO_IO_CTRL0,   0xee },
+		{ OV519_R51_RESET1, 0x0f },
+		{ OV519_R51_RESET1, 0x00 },
+		{ 0x22, 0x00 },
+		/* windows reads 0x55 at this point*/
+	};
+
+	write_regvals(sd, init_519, ARRAY_SIZE(init_519));
+}
+
+static void ovfx2_configure(struct sd *sd)
+{
+	static const struct ov_regvals init_fx2[] = {
+		{ 0x00, 0x60 },
+		{ 0x02, 0x01 },
+		{ 0x0f, 0x1d },
+		{ 0xe9, 0x82 },
+		{ 0xea, 0xc7 },
+		{ 0xeb, 0x10 },
+		{ 0xec, 0xf6 },
+	};
+
+	sd->stopped = 1;
+
+	write_regvals(sd, init_fx2, ARRAY_SIZE(init_fx2));
+}
+
+/* set the mode */
+/* This function works for ov7660 only */
+static void ov519_set_mode(struct sd *sd)
+{
+	static const struct ov_regvals bridge_ov7660[2][10] = {
+		{{0x10, 0x14}, {0x11, 0x1e}, {0x12, 0x00}, {0x13, 0x00},
+		 {0x14, 0x00}, {0x15, 0x00}, {0x16, 0x00}, {0x20, 0x0c},
+		 {0x25, 0x01}, {0x26, 0x00}},
+		{{0x10, 0x28}, {0x11, 0x3c}, {0x12, 0x00}, {0x13, 0x00},
+		 {0x14, 0x00}, {0x15, 0x00}, {0x16, 0x00}, {0x20, 0x0c},
+		 {0x25, 0x03}, {0x26, 0x00}}
+	};
+	static const struct ov_i2c_regvals sensor_ov7660[2][3] = {
+		{{0x12, 0x00}, {0x24, 0x00}, {0x0c, 0x0c}},
+		{{0x12, 0x00}, {0x04, 0x00}, {0x0c, 0x00}}
+	};
+	static const struct ov_i2c_regvals sensor_ov7660_2[] = {
+		{OV7670_R17_HSTART, 0x13},
+		{OV7670_R18_HSTOP, 0x01},
+		{OV7670_R32_HREF, 0x92},
+		{OV7670_R19_VSTART, 0x02},
+		{OV7670_R1A_VSTOP, 0x7a},
+		{OV7670_R03_VREF, 0x00},
+/*		{0x33, 0x00}, */
+/*		{0x34, 0x07}, */
+/*		{0x36, 0x00}, */
+/*		{0x6b, 0x0a}, */
+	};
+
+	write_regvals(sd, bridge_ov7660[sd->gspca_dev.curr_mode],
+			ARRAY_SIZE(bridge_ov7660[0]));
+	write_i2c_regvals(sd, sensor_ov7660[sd->gspca_dev.curr_mode],
+			ARRAY_SIZE(sensor_ov7660[0]));
+	write_i2c_regvals(sd, sensor_ov7660_2,
+			ARRAY_SIZE(sensor_ov7660_2));
+}
+
+/* set the frame rate */
+/* This function works for sensors ov7640, ov7648 ov7660 and ov7670 only */
+static void ov519_set_fr(struct sd *sd)
+{
+	int fr;
+	u8 clock;
+	/* frame rate table with indices:
+	 *	- mode = 0: 320x240, 1: 640x480
+	 *	- fr rate = 0: 30, 1: 25, 2: 20, 3: 15, 4: 10, 5: 5
+	 *	- reg = 0: bridge a4, 1: bridge 23, 2: sensor 11 (clock)
+	 */
+	static const u8 fr_tb[2][6][3] = {
+		{{0x04, 0xff, 0x00},
+		 {0x04, 0x1f, 0x00},
+		 {0x04, 0x1b, 0x00},
+		 {0x04, 0x15, 0x00},
+		 {0x04, 0x09, 0x00},
+		 {0x04, 0x01, 0x00}},
+		{{0x0c, 0xff, 0x00},
+		 {0x0c, 0x1f, 0x00},
+		 {0x0c, 0x1b, 0x00},
+		 {0x04, 0xff, 0x01},
+		 {0x04, 0x1f, 0x01},
+		 {0x04, 0x1b, 0x01}},
+	};
+
+	if (frame_rate > 0)
+		sd->frame_rate = frame_rate;
+	if (sd->frame_rate >= 30)
+		fr = 0;
+	else if (sd->frame_rate >= 25)
+		fr = 1;
+	else if (sd->frame_rate >= 20)
+		fr = 2;
+	else if (sd->frame_rate >= 15)
+		fr = 3;
+	else if (sd->frame_rate >= 10)
+		fr = 4;
+	else
+		fr = 5;
+	reg_w(sd, 0xa4, fr_tb[sd->gspca_dev.curr_mode][fr][0]);
+	reg_w(sd, 0x23, fr_tb[sd->gspca_dev.curr_mode][fr][1]);
+	clock = fr_tb[sd->gspca_dev.curr_mode][fr][2];
+	if (sd->sensor == SEN_OV7660)
+		clock |= 0x80;		/* enable double clock */
+	ov518_i2c_w(sd, OV7670_R11_CLKRC, clock);
+}
+
+static void setautogain(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	i2c_w_mask(sd, 0x13, val ? 0x05 : 0x00, 0x05);
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam = &gspca_dev->cam;
+
+	sd->bridge = id->driver_info & BRIDGE_MASK;
+	sd->invert_led = (id->driver_info & BRIDGE_INVERT_LED) != 0;
+
+	switch (sd->bridge) {
+	case BRIDGE_OV511:
+	case BRIDGE_OV511PLUS:
+		cam->cam_mode = ov511_vga_mode;
+		cam->nmodes = ARRAY_SIZE(ov511_vga_mode);
+		break;
+	case BRIDGE_OV518:
+	case BRIDGE_OV518PLUS:
+		cam->cam_mode = ov518_vga_mode;
+		cam->nmodes = ARRAY_SIZE(ov518_vga_mode);
+		break;
+	case BRIDGE_OV519:
+		cam->cam_mode = ov519_vga_mode;
+		cam->nmodes = ARRAY_SIZE(ov519_vga_mode);
+		break;
+	case BRIDGE_OVFX2:
+		cam->cam_mode = ov519_vga_mode;
+		cam->nmodes = ARRAY_SIZE(ov519_vga_mode);
+		cam->bulk_size = OVFX2_BULK_SIZE;
+		cam->bulk_nurbs = MAX_NURBS;
+		cam->bulk = 1;
+		break;
+	case BRIDGE_W9968CF:
+		cam->cam_mode = w9968cf_vga_mode;
+		cam->nmodes = ARRAY_SIZE(w9968cf_vga_mode);
+		break;
+	}
+
+	sd->frame_rate = 15;
+
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam = &gspca_dev->cam;
+
+	switch (sd->bridge) {
+	case BRIDGE_OV511:
+	case BRIDGE_OV511PLUS:
+		ov511_configure(gspca_dev);
+		break;
+	case BRIDGE_OV518:
+	case BRIDGE_OV518PLUS:
+		ov518_configure(gspca_dev);
+		break;
+	case BRIDGE_OV519:
+		ov519_configure(sd);
+		break;
+	case BRIDGE_OVFX2:
+		ovfx2_configure(sd);
+		break;
+	case BRIDGE_W9968CF:
+		w9968cf_configure(sd);
+		break;
+	}
+
+	/* The OV519 must be more aggressive about sensor detection since
+	 * I2C write will never fail if the sensor is not present. We have
+	 * to try to initialize the sensor to detect its presence */
+	sd->sensor = -1;
+
+	/* Test for 76xx */
+	if (init_ov_sensor(sd, OV7xx0_SID) >= 0) {
+		ov7xx0_configure(sd);
+
+	/* Test for 6xx0 */
+	} else if (init_ov_sensor(sd, OV6xx0_SID) >= 0) {
+		ov6xx0_configure(sd);
+
+	/* Test for 8xx0 */
+	} else if (init_ov_sensor(sd, OV8xx0_SID) >= 0) {
+		ov8xx0_configure(sd);
+
+	/* Test for 3xxx / 2xxx */
+	} else if (init_ov_sensor(sd, OV_HIRES_SID) >= 0) {
+		ov_hires_configure(sd);
+	} else {
+		gspca_err(gspca_dev, "Can't determine sensor slave IDs\n");
+		goto error;
+	}
+
+	if (sd->sensor < 0)
+		goto error;
+
+	ov51x_led_control(sd, 0);	/* turn LED off */
+
+	switch (sd->bridge) {
+	case BRIDGE_OV511:
+	case BRIDGE_OV511PLUS:
+		if (sd->sif) {
+			cam->cam_mode = ov511_sif_mode;
+			cam->nmodes = ARRAY_SIZE(ov511_sif_mode);
+		}
+		break;
+	case BRIDGE_OV518:
+	case BRIDGE_OV518PLUS:
+		if (sd->sif) {
+			cam->cam_mode = ov518_sif_mode;
+			cam->nmodes = ARRAY_SIZE(ov518_sif_mode);
+		}
+		break;
+	case BRIDGE_OV519:
+		if (sd->sif) {
+			cam->cam_mode = ov519_sif_mode;
+			cam->nmodes = ARRAY_SIZE(ov519_sif_mode);
+		}
+		break;
+	case BRIDGE_OVFX2:
+		switch (sd->sensor) {
+		case SEN_OV2610:
+		case SEN_OV2610AE:
+			cam->cam_mode = ovfx2_ov2610_mode;
+			cam->nmodes = ARRAY_SIZE(ovfx2_ov2610_mode);
+			break;
+		case SEN_OV3610:
+			cam->cam_mode = ovfx2_ov3610_mode;
+			cam->nmodes = ARRAY_SIZE(ovfx2_ov3610_mode);
+			break;
+		case SEN_OV9600:
+			cam->cam_mode = ovfx2_ov9600_mode;
+			cam->nmodes = ARRAY_SIZE(ovfx2_ov9600_mode);
+			break;
+		default:
+			if (sd->sif) {
+				cam->cam_mode = ov519_sif_mode;
+				cam->nmodes = ARRAY_SIZE(ov519_sif_mode);
+			}
+			break;
+		}
+		break;
+	case BRIDGE_W9968CF:
+		if (sd->sif)
+			cam->nmodes = ARRAY_SIZE(w9968cf_vga_mode) - 1;
+
+		/* w9968cf needs initialisation once the sensor is known */
+		w9968cf_init(sd);
+		break;
+	}
+
+	/* initialize the sensor */
+	switch (sd->sensor) {
+	case SEN_OV2610:
+		write_i2c_regvals(sd, norm_2610, ARRAY_SIZE(norm_2610));
+
+		/* Enable autogain, autoexpo, awb, bandfilter */
+		i2c_w_mask(sd, 0x13, 0x27, 0x27);
+		break;
+	case SEN_OV2610AE:
+		write_i2c_regvals(sd, norm_2610ae, ARRAY_SIZE(norm_2610ae));
+
+		/* enable autoexpo */
+		i2c_w_mask(sd, 0x13, 0x05, 0x05);
+		break;
+	case SEN_OV3610:
+		write_i2c_regvals(sd, norm_3620b, ARRAY_SIZE(norm_3620b));
+
+		/* Enable autogain, autoexpo, awb, bandfilter */
+		i2c_w_mask(sd, 0x13, 0x27, 0x27);
+		break;
+	case SEN_OV6620:
+		write_i2c_regvals(sd, norm_6x20, ARRAY_SIZE(norm_6x20));
+		break;
+	case SEN_OV6630:
+	case SEN_OV66308AF:
+		write_i2c_regvals(sd, norm_6x30, ARRAY_SIZE(norm_6x30));
+		break;
+	default:
+/*	case SEN_OV7610: */
+/*	case SEN_OV76BE: */
+		write_i2c_regvals(sd, norm_7610, ARRAY_SIZE(norm_7610));
+		i2c_w_mask(sd, 0x0e, 0x00, 0x40);
+		break;
+	case SEN_OV7620:
+	case SEN_OV7620AE:
+		write_i2c_regvals(sd, norm_7620, ARRAY_SIZE(norm_7620));
+		break;
+	case SEN_OV7640:
+	case SEN_OV7648:
+		write_i2c_regvals(sd, norm_7640, ARRAY_SIZE(norm_7640));
+		break;
+	case SEN_OV7660:
+		i2c_w(sd, OV7670_R12_COM7, OV7670_COM7_RESET);
+		msleep(14);
+		reg_w(sd, OV519_R57_SNAPSHOT, 0x23);
+		write_regvals(sd, init_519_ov7660,
+				ARRAY_SIZE(init_519_ov7660));
+		write_i2c_regvals(sd, norm_7660, ARRAY_SIZE(norm_7660));
+		sd->gspca_dev.curr_mode = 1;	/* 640x480 */
+		ov519_set_mode(sd);
+		ov519_set_fr(sd);
+		sd_reset_snapshot(gspca_dev);
+		ov51x_restart(sd);
+		ov51x_stop(sd);			/* not in win traces */
+		ov51x_led_control(sd, 0);
+		break;
+	case SEN_OV7670:
+		write_i2c_regvals(sd, norm_7670, ARRAY_SIZE(norm_7670));
+		break;
+	case SEN_OV8610:
+		write_i2c_regvals(sd, norm_8610, ARRAY_SIZE(norm_8610));
+		break;
+	case SEN_OV9600:
+		write_i2c_regvals(sd, norm_9600, ARRAY_SIZE(norm_9600));
+
+		/* enable autoexpo */
+/*		i2c_w_mask(sd, 0x13, 0x05, 0x05); */
+		break;
+	}
+	return gspca_dev->usb_err;
+error:
+	gspca_err(gspca_dev, "OV519 Config failed\n");
+	return -EINVAL;
+}
+
+/* function called at start time before URB creation */
+static int sd_isoc_init(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	switch (sd->bridge) {
+	case BRIDGE_OVFX2:
+		if (gspca_dev->pixfmt.width != 800)
+			gspca_dev->cam.bulk_size = OVFX2_BULK_SIZE;
+		else
+			gspca_dev->cam.bulk_size = 7 * 4096;
+		break;
+	}
+	return 0;
+}
+
+/* Set up the OV511/OV511+ with the given image parameters.
+ *
+ * Do not put any sensor-specific code in here (including I2C I/O functions)
+ */
+static void ov511_mode_init_regs(struct sd *sd)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	int hsegs, vsegs, packet_size, fps, needed;
+	int interlaced = 0;
+	struct usb_host_interface *alt;
+	struct usb_interface *intf;
+
+	intf = usb_ifnum_to_if(sd->gspca_dev.dev, sd->gspca_dev.iface);
+	alt = usb_altnum_to_altsetting(intf, sd->gspca_dev.alt);
+	if (!alt) {
+		gspca_err(gspca_dev, "Couldn't get altsetting\n");
+		sd->gspca_dev.usb_err = -EIO;
+		return;
+	}
+
+	packet_size = le16_to_cpu(alt->endpoint[0].desc.wMaxPacketSize);
+	reg_w(sd, R51x_FIFO_PSIZE, packet_size >> 5);
+
+	reg_w(sd, R511_CAM_UV_EN, 0x01);
+	reg_w(sd, R511_SNAP_UV_EN, 0x01);
+	reg_w(sd, R511_SNAP_OPTS, 0x03);
+
+	/* Here I'm assuming that snapshot size == image size.
+	 * I hope that's always true. --claudio
+	 */
+	hsegs = (sd->gspca_dev.pixfmt.width >> 3) - 1;
+	vsegs = (sd->gspca_dev.pixfmt.height >> 3) - 1;
+
+	reg_w(sd, R511_CAM_PXCNT, hsegs);
+	reg_w(sd, R511_CAM_LNCNT, vsegs);
+	reg_w(sd, R511_CAM_PXDIV, 0x00);
+	reg_w(sd, R511_CAM_LNDIV, 0x00);
+
+	/* YUV420, low pass filter on */
+	reg_w(sd, R511_CAM_OPTS, 0x03);
+
+	/* Snapshot additions */
+	reg_w(sd, R511_SNAP_PXCNT, hsegs);
+	reg_w(sd, R511_SNAP_LNCNT, vsegs);
+	reg_w(sd, R511_SNAP_PXDIV, 0x00);
+	reg_w(sd, R511_SNAP_LNDIV, 0x00);
+
+	/******** Set the framerate ********/
+	if (frame_rate > 0)
+		sd->frame_rate = frame_rate;
+
+	switch (sd->sensor) {
+	case SEN_OV6620:
+		/* No framerate control, doesn't like higher rates yet */
+		sd->clockdiv = 3;
+		break;
+
+	/* Note once the FIXME's in mode_init_ov_sensor_regs() are fixed
+	   for more sensors we need to do this for them too */
+	case SEN_OV7620:
+	case SEN_OV7620AE:
+	case SEN_OV7640:
+	case SEN_OV7648:
+	case SEN_OV76BE:
+		if (sd->gspca_dev.pixfmt.width == 320)
+			interlaced = 1;
+		/* Fall through */
+	case SEN_OV6630:
+	case SEN_OV7610:
+	case SEN_OV7670:
+		switch (sd->frame_rate) {
+		case 30:
+		case 25:
+			/* Not enough bandwidth to do 640x480 @ 30 fps */
+			if (sd->gspca_dev.pixfmt.width != 640) {
+				sd->clockdiv = 0;
+				break;
+			}
+			/* For 640x480 case */
+			/* fall through */
+		default:
+/*		case 20: */
+/*		case 15: */
+			sd->clockdiv = 1;
+			break;
+		case 10:
+			sd->clockdiv = 2;
+			break;
+		case 5:
+			sd->clockdiv = 5;
+			break;
+		}
+		if (interlaced) {
+			sd->clockdiv = (sd->clockdiv + 1) * 2 - 1;
+			/* Higher then 10 does not work */
+			if (sd->clockdiv > 10)
+				sd->clockdiv = 10;
+		}
+		break;
+
+	case SEN_OV8610:
+		/* No framerate control ?? */
+		sd->clockdiv = 0;
+		break;
+	}
+
+	/* Check if we have enough bandwidth to disable compression */
+	fps = (interlaced ? 60 : 30) / (sd->clockdiv + 1) + 1;
+	needed = fps * sd->gspca_dev.pixfmt.width *
+			sd->gspca_dev.pixfmt.height * 3 / 2;
+	/* 1000 isoc packets/sec */
+	if (needed > 1000 * packet_size) {
+		/* Enable Y and UV quantization and compression */
+		reg_w(sd, R511_COMP_EN, 0x07);
+		reg_w(sd, R511_COMP_LUT_EN, 0x03);
+	} else {
+		reg_w(sd, R511_COMP_EN, 0x06);
+		reg_w(sd, R511_COMP_LUT_EN, 0x00);
+	}
+
+	reg_w(sd, R51x_SYS_RESET, OV511_RESET_OMNICE);
+	reg_w(sd, R51x_SYS_RESET, 0);
+}
+
+/* Sets up the OV518/OV518+ with the given image parameters
+ *
+ * OV518 needs a completely different approach, until we can figure out what
+ * the individual registers do. Also, only 15 FPS is supported now.
+ *
+ * Do not put any sensor-specific code in here (including I2C I/O functions)
+ */
+static void ov518_mode_init_regs(struct sd *sd)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	int hsegs, vsegs, packet_size;
+	struct usb_host_interface *alt;
+	struct usb_interface *intf;
+
+	intf = usb_ifnum_to_if(sd->gspca_dev.dev, sd->gspca_dev.iface);
+	alt = usb_altnum_to_altsetting(intf, sd->gspca_dev.alt);
+	if (!alt) {
+		gspca_err(gspca_dev, "Couldn't get altsetting\n");
+		sd->gspca_dev.usb_err = -EIO;
+		return;
+	}
+
+	packet_size = le16_to_cpu(alt->endpoint[0].desc.wMaxPacketSize);
+	ov518_reg_w32(sd, R51x_FIFO_PSIZE, packet_size & ~7, 2);
+
+	/******** Set the mode ********/
+	reg_w(sd, 0x2b, 0);
+	reg_w(sd, 0x2c, 0);
+	reg_w(sd, 0x2d, 0);
+	reg_w(sd, 0x2e, 0);
+	reg_w(sd, 0x3b, 0);
+	reg_w(sd, 0x3c, 0);
+	reg_w(sd, 0x3d, 0);
+	reg_w(sd, 0x3e, 0);
+
+	if (sd->bridge == BRIDGE_OV518) {
+		/* Set 8-bit (YVYU) input format */
+		reg_w_mask(sd, 0x20, 0x08, 0x08);
+
+		/* Set 12-bit (4:2:0) output format */
+		reg_w_mask(sd, 0x28, 0x80, 0xf0);
+		reg_w_mask(sd, 0x38, 0x80, 0xf0);
+	} else {
+		reg_w(sd, 0x28, 0x80);
+		reg_w(sd, 0x38, 0x80);
+	}
+
+	hsegs = sd->gspca_dev.pixfmt.width / 16;
+	vsegs = sd->gspca_dev.pixfmt.height / 4;
+
+	reg_w(sd, 0x29, hsegs);
+	reg_w(sd, 0x2a, vsegs);
+
+	reg_w(sd, 0x39, hsegs);
+	reg_w(sd, 0x3a, vsegs);
+
+	/* Windows driver does this here; who knows why */
+	reg_w(sd, 0x2f, 0x80);
+
+	/******** Set the framerate ********/
+	if (sd->bridge == BRIDGE_OV518PLUS && sd->revision == 0 &&
+					      sd->sensor == SEN_OV7620AE)
+		sd->clockdiv = 0;
+	else
+		sd->clockdiv = 1;
+
+	/* Mode independent, but framerate dependent, regs */
+	/* 0x51: Clock divider; Only works on some cams which use 2 crystals */
+	reg_w(sd, 0x51, 0x04);
+	reg_w(sd, 0x22, 0x18);
+	reg_w(sd, 0x23, 0xff);
+
+	if (sd->bridge == BRIDGE_OV518PLUS) {
+		switch (sd->sensor) {
+		case SEN_OV7620AE:
+			/*
+			 * HdG: 640x480 needs special handling on device
+			 * revision 2, we check for device revison > 0 to
+			 * avoid regressions, as we don't know the correct
+			 * thing todo for revision 1.
+			 *
+			 * Also this likely means we don't need to
+			 * differentiate between the OV7620 and OV7620AE,
+			 * earlier testing hitting this same problem likely
+			 * happened to be with revision < 2 cams using an
+			 * OV7620 and revision 2 cams using an OV7620AE.
+			 */
+			if (sd->revision > 0 &&
+					sd->gspca_dev.pixfmt.width == 640) {
+				reg_w(sd, 0x20, 0x60);
+				reg_w(sd, 0x21, 0x1f);
+			} else {
+				reg_w(sd, 0x20, 0x00);
+				reg_w(sd, 0x21, 0x19);
+			}
+			break;
+		case SEN_OV7620:
+			reg_w(sd, 0x20, 0x00);
+			reg_w(sd, 0x21, 0x19);
+			break;
+		default:
+			reg_w(sd, 0x21, 0x19);
+		}
+	} else
+		reg_w(sd, 0x71, 0x17);	/* Compression-related? */
+
+	/* FIXME: Sensor-specific */
+	/* Bit 5 is what matters here. Of course, it is "reserved" */
+	i2c_w(sd, 0x54, 0x23);
+
+	reg_w(sd, 0x2f, 0x80);
+
+	if (sd->bridge == BRIDGE_OV518PLUS) {
+		reg_w(sd, 0x24, 0x94);
+		reg_w(sd, 0x25, 0x90);
+		ov518_reg_w32(sd, 0xc4,    400, 2);	/* 190h   */
+		ov518_reg_w32(sd, 0xc6,    540, 2);	/* 21ch   */
+		ov518_reg_w32(sd, 0xc7,    540, 2);	/* 21ch   */
+		ov518_reg_w32(sd, 0xc8,    108, 2);	/* 6ch    */
+		ov518_reg_w32(sd, 0xca, 131098, 3);	/* 2001ah */
+		ov518_reg_w32(sd, 0xcb,    532, 2);	/* 214h   */
+		ov518_reg_w32(sd, 0xcc,   2400, 2);	/* 960h   */
+		ov518_reg_w32(sd, 0xcd,     32, 2);	/* 20h    */
+		ov518_reg_w32(sd, 0xce,    608, 2);	/* 260h   */
+	} else {
+		reg_w(sd, 0x24, 0x9f);
+		reg_w(sd, 0x25, 0x90);
+		ov518_reg_w32(sd, 0xc4,    400, 2);	/* 190h   */
+		ov518_reg_w32(sd, 0xc6,    381, 2);	/* 17dh   */
+		ov518_reg_w32(sd, 0xc7,    381, 2);	/* 17dh   */
+		ov518_reg_w32(sd, 0xc8,    128, 2);	/* 80h    */
+		ov518_reg_w32(sd, 0xca, 183331, 3);	/* 2cc23h */
+		ov518_reg_w32(sd, 0xcb,    746, 2);	/* 2eah   */
+		ov518_reg_w32(sd, 0xcc,   1750, 2);	/* 6d6h   */
+		ov518_reg_w32(sd, 0xcd,     45, 2);	/* 2dh    */
+		ov518_reg_w32(sd, 0xce,    851, 2);	/* 353h   */
+	}
+
+	reg_w(sd, 0x2f, 0x80);
+}
+
+/* Sets up the OV519 with the given image parameters
+ *
+ * OV519 needs a completely different approach, until we can figure out what
+ * the individual registers do.
+ *
+ * Do not put any sensor-specific code in here (including I2C I/O functions)
+ */
+static void ov519_mode_init_regs(struct sd *sd)
+{
+	static const struct ov_regvals mode_init_519_ov7670[] = {
+		{ 0x5d,	0x03 }, /* Turn off suspend mode */
+		{ 0x53,	0x9f }, /* was 9b in 1.65-1.08 */
+		{ OV519_R54_EN_CLK1, 0x0f }, /* bit2 (jpeg enable) */
+		{ 0xa2,	0x20 }, /* a2-a5 are undocumented */
+		{ 0xa3,	0x18 },
+		{ 0xa4,	0x04 },
+		{ 0xa5,	0x28 },
+		{ 0x37,	0x00 },	/* SetUsbInit */
+		{ 0x55,	0x02 }, /* 4.096 Mhz audio clock */
+		/* Enable both fields, YUV Input, disable defect comp (why?) */
+		{ 0x20,	0x0c },
+		{ 0x21,	0x38 },
+		{ 0x22,	0x1d },
+		{ 0x17,	0x50 }, /* undocumented */
+		{ 0x37,	0x00 }, /* undocumented */
+		{ 0x40,	0xff }, /* I2C timeout counter */
+		{ 0x46,	0x00 }, /* I2C clock prescaler */
+		{ 0x59,	0x04 },	/* new from windrv 090403 */
+		{ 0xff,	0x00 }, /* undocumented */
+		/* windows reads 0x55 at this point, why? */
+	};
+
+	static const struct ov_regvals mode_init_519[] = {
+		{ 0x5d,	0x03 }, /* Turn off suspend mode */
+		{ 0x53,	0x9f }, /* was 9b in 1.65-1.08 */
+		{ OV519_R54_EN_CLK1, 0x0f }, /* bit2 (jpeg enable) */
+		{ 0xa2,	0x20 }, /* a2-a5 are undocumented */
+		{ 0xa3,	0x18 },
+		{ 0xa4,	0x04 },
+		{ 0xa5,	0x28 },
+		{ 0x37,	0x00 },	/* SetUsbInit */
+		{ 0x55,	0x02 }, /* 4.096 Mhz audio clock */
+		/* Enable both fields, YUV Input, disable defect comp (why?) */
+		{ 0x22,	0x1d },
+		{ 0x17,	0x50 }, /* undocumented */
+		{ 0x37,	0x00 }, /* undocumented */
+		{ 0x40,	0xff }, /* I2C timeout counter */
+		{ 0x46,	0x00 }, /* I2C clock prescaler */
+		{ 0x59,	0x04 },	/* new from windrv 090403 */
+		{ 0xff,	0x00 }, /* undocumented */
+		/* windows reads 0x55 at this point, why? */
+	};
+
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+
+	/******** Set the mode ********/
+	switch (sd->sensor) {
+	default:
+		write_regvals(sd, mode_init_519, ARRAY_SIZE(mode_init_519));
+		if (sd->sensor == SEN_OV7640 ||
+		    sd->sensor == SEN_OV7648) {
+			/* Select 8-bit input mode */
+			reg_w_mask(sd, OV519_R20_DFR, 0x10, 0x10);
+		}
+		break;
+	case SEN_OV7660:
+		return;		/* done by ov519_set_mode/fr() */
+	case SEN_OV7670:
+		write_regvals(sd, mode_init_519_ov7670,
+				ARRAY_SIZE(mode_init_519_ov7670));
+		break;
+	}
+
+	reg_w(sd, OV519_R10_H_SIZE,	sd->gspca_dev.pixfmt.width >> 4);
+	reg_w(sd, OV519_R11_V_SIZE,	sd->gspca_dev.pixfmt.height >> 3);
+	if (sd->sensor == SEN_OV7670 &&
+	    sd->gspca_dev.cam.cam_mode[sd->gspca_dev.curr_mode].priv)
+		reg_w(sd, OV519_R12_X_OFFSETL, 0x04);
+	else if (sd->sensor == SEN_OV7648 &&
+	    sd->gspca_dev.cam.cam_mode[sd->gspca_dev.curr_mode].priv)
+		reg_w(sd, OV519_R12_X_OFFSETL, 0x01);
+	else
+		reg_w(sd, OV519_R12_X_OFFSETL, 0x00);
+	reg_w(sd, OV519_R13_X_OFFSETH,	0x00);
+	reg_w(sd, OV519_R14_Y_OFFSETL,	0x00);
+	reg_w(sd, OV519_R15_Y_OFFSETH,	0x00);
+	reg_w(sd, OV519_R16_DIVIDER,	0x00);
+	reg_w(sd, OV519_R25_FORMAT,	0x03); /* YUV422 */
+	reg_w(sd, 0x26,			0x00); /* Undocumented */
+
+	/******** Set the framerate ********/
+	if (frame_rate > 0)
+		sd->frame_rate = frame_rate;
+
+/* FIXME: These are only valid at the max resolution. */
+	sd->clockdiv = 0;
+	switch (sd->sensor) {
+	case SEN_OV7640:
+	case SEN_OV7648:
+		switch (sd->frame_rate) {
+		default:
+/*		case 30: */
+			reg_w(sd, 0xa4, 0x0c);
+			reg_w(sd, 0x23, 0xff);
+			break;
+		case 25:
+			reg_w(sd, 0xa4, 0x0c);
+			reg_w(sd, 0x23, 0x1f);
+			break;
+		case 20:
+			reg_w(sd, 0xa4, 0x0c);
+			reg_w(sd, 0x23, 0x1b);
+			break;
+		case 15:
+			reg_w(sd, 0xa4, 0x04);
+			reg_w(sd, 0x23, 0xff);
+			sd->clockdiv = 1;
+			break;
+		case 10:
+			reg_w(sd, 0xa4, 0x04);
+			reg_w(sd, 0x23, 0x1f);
+			sd->clockdiv = 1;
+			break;
+		case 5:
+			reg_w(sd, 0xa4, 0x04);
+			reg_w(sd, 0x23, 0x1b);
+			sd->clockdiv = 1;
+			break;
+		}
+		break;
+	case SEN_OV8610:
+		switch (sd->frame_rate) {
+		default:	/* 15 fps */
+/*		case 15: */
+			reg_w(sd, 0xa4, 0x06);
+			reg_w(sd, 0x23, 0xff);
+			break;
+		case 10:
+			reg_w(sd, 0xa4, 0x06);
+			reg_w(sd, 0x23, 0x1f);
+			break;
+		case 5:
+			reg_w(sd, 0xa4, 0x06);
+			reg_w(sd, 0x23, 0x1b);
+			break;
+		}
+		break;
+	case SEN_OV7670:		/* guesses, based on 7640 */
+		gspca_dbg(gspca_dev, D_STREAM, "Setting framerate to %d fps\n",
+			  (sd->frame_rate == 0) ? 15 : sd->frame_rate);
+		reg_w(sd, 0xa4, 0x10);
+		switch (sd->frame_rate) {
+		case 30:
+			reg_w(sd, 0x23, 0xff);
+			break;
+		case 20:
+			reg_w(sd, 0x23, 0x1b);
+			break;
+		default:
+/*		case 15: */
+			reg_w(sd, 0x23, 0xff);
+			sd->clockdiv = 1;
+			break;
+		}
+		break;
+	}
+}
+
+static void mode_init_ov_sensor_regs(struct sd *sd)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	int qvga, xstart, xend, ystart, yend;
+	u8 v;
+
+	qvga = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].priv & 1;
+
+	/******** Mode (VGA/QVGA) and sensor specific regs ********/
+	switch (sd->sensor) {
+	case SEN_OV2610:
+		i2c_w_mask(sd, 0x14, qvga ? 0x20 : 0x00, 0x20);
+		i2c_w_mask(sd, 0x28, qvga ? 0x00 : 0x20, 0x20);
+		i2c_w(sd, 0x24, qvga ? 0x20 : 0x3a);
+		i2c_w(sd, 0x25, qvga ? 0x30 : 0x60);
+		i2c_w_mask(sd, 0x2d, qvga ? 0x40 : 0x00, 0x40);
+		i2c_w_mask(sd, 0x67, qvga ? 0xf0 : 0x90, 0xf0);
+		i2c_w_mask(sd, 0x74, qvga ? 0x20 : 0x00, 0x20);
+		return;
+	case SEN_OV2610AE: {
+		u8 v;
+
+		/* frame rates:
+		 *	10fps / 5 fps for 1600x1200
+		 *	40fps / 20fps for 800x600
+		 */
+		v = 80;
+		if (qvga) {
+			if (sd->frame_rate < 25)
+				v = 0x81;
+		} else {
+			if (sd->frame_rate < 10)
+				v = 0x81;
+		}
+		i2c_w(sd, 0x11, v);
+		i2c_w(sd, 0x12, qvga ? 0x60 : 0x20);
+		return;
+	    }
+	case SEN_OV3610:
+		if (qvga) {
+			xstart = (1040 - gspca_dev->pixfmt.width) / 2 +
+				(0x1f << 4);
+			ystart = (776 - gspca_dev->pixfmt.height) / 2;
+		} else {
+			xstart = (2076 - gspca_dev->pixfmt.width) / 2 +
+				(0x10 << 4);
+			ystart = (1544 - gspca_dev->pixfmt.height) / 2;
+		}
+		xend = xstart + gspca_dev->pixfmt.width;
+		yend = ystart + gspca_dev->pixfmt.height;
+		/* Writing to the COMH register resets the other windowing regs
+		   to their default values, so we must do this first. */
+		i2c_w_mask(sd, 0x12, qvga ? 0x40 : 0x00, 0xf0);
+		i2c_w_mask(sd, 0x32,
+			   (((xend >> 1) & 7) << 3) | ((xstart >> 1) & 7),
+			   0x3f);
+		i2c_w_mask(sd, 0x03,
+			   (((yend >> 1) & 3) << 2) | ((ystart >> 1) & 3),
+			   0x0f);
+		i2c_w(sd, 0x17, xstart >> 4);
+		i2c_w(sd, 0x18, xend >> 4);
+		i2c_w(sd, 0x19, ystart >> 3);
+		i2c_w(sd, 0x1a, yend >> 3);
+		return;
+	case SEN_OV8610:
+		/* For OV8610 qvga means qsvga */
+		i2c_w_mask(sd, OV7610_REG_COM_C, qvga ? (1 << 5) : 0, 1 << 5);
+		i2c_w_mask(sd, 0x13, 0x00, 0x20); /* Select 16 bit data bus */
+		i2c_w_mask(sd, 0x12, 0x04, 0x06); /* AWB: 1 Test pattern: 0 */
+		i2c_w_mask(sd, 0x2d, 0x00, 0x40); /* from windrv 090403 */
+		i2c_w_mask(sd, 0x28, 0x20, 0x20); /* progressive mode on */
+		break;
+	case SEN_OV7610:
+		i2c_w_mask(sd, 0x14, qvga ? 0x20 : 0x00, 0x20);
+		i2c_w(sd, 0x35, qvga ? 0x1e : 0x9e);
+		i2c_w_mask(sd, 0x13, 0x00, 0x20); /* Select 16 bit data bus */
+		i2c_w_mask(sd, 0x12, 0x04, 0x06); /* AWB: 1 Test pattern: 0 */
+		break;
+	case SEN_OV7620:
+	case SEN_OV7620AE:
+	case SEN_OV76BE:
+		i2c_w_mask(sd, 0x14, qvga ? 0x20 : 0x00, 0x20);
+		i2c_w_mask(sd, 0x28, qvga ? 0x00 : 0x20, 0x20);
+		i2c_w(sd, 0x24, qvga ? 0x20 : 0x3a);
+		i2c_w(sd, 0x25, qvga ? 0x30 : 0x60);
+		i2c_w_mask(sd, 0x2d, qvga ? 0x40 : 0x00, 0x40);
+		i2c_w_mask(sd, 0x67, qvga ? 0xb0 : 0x90, 0xf0);
+		i2c_w_mask(sd, 0x74, qvga ? 0x20 : 0x00, 0x20);
+		i2c_w_mask(sd, 0x13, 0x00, 0x20); /* Select 16 bit data bus */
+		i2c_w_mask(sd, 0x12, 0x04, 0x06); /* AWB: 1 Test pattern: 0 */
+		if (sd->sensor == SEN_OV76BE)
+			i2c_w(sd, 0x35, qvga ? 0x1e : 0x9e);
+		break;
+	case SEN_OV7640:
+	case SEN_OV7648:
+		i2c_w_mask(sd, 0x14, qvga ? 0x20 : 0x00, 0x20);
+		i2c_w_mask(sd, 0x28, qvga ? 0x00 : 0x20, 0x20);
+		/* Setting this undocumented bit in qvga mode removes a very
+		   annoying vertical shaking of the image */
+		i2c_w_mask(sd, 0x2d, qvga ? 0x40 : 0x00, 0x40);
+		/* Unknown */
+		i2c_w_mask(sd, 0x67, qvga ? 0xf0 : 0x90, 0xf0);
+		/* Allow higher automatic gain (to allow higher framerates) */
+		i2c_w_mask(sd, 0x74, qvga ? 0x20 : 0x00, 0x20);
+		i2c_w_mask(sd, 0x12, 0x04, 0x04); /* AWB: 1 */
+		break;
+	case SEN_OV7670:
+		/* set COM7_FMT_VGA or COM7_FMT_QVGA
+		 * do we need to set anything else?
+		 *	HSTART etc are set in set_ov_sensor_window itself */
+		i2c_w_mask(sd, OV7670_R12_COM7,
+			 qvga ? OV7670_COM7_FMT_QVGA : OV7670_COM7_FMT_VGA,
+			 OV7670_COM7_FMT_MASK);
+		i2c_w_mask(sd, 0x13, 0x00, 0x20); /* Select 16 bit data bus */
+		i2c_w_mask(sd, OV7670_R13_COM8, OV7670_COM8_AWB,
+				OV7670_COM8_AWB);
+		if (qvga) {		/* QVGA from ov7670.c by
+					 * Jonathan Corbet */
+			xstart = 164;
+			xend = 28;
+			ystart = 14;
+			yend = 494;
+		} else {		/* VGA */
+			xstart = 158;
+			xend = 14;
+			ystart = 10;
+			yend = 490;
+		}
+		/* OV7670 hardware window registers are split across
+		 * multiple locations */
+		i2c_w(sd, OV7670_R17_HSTART, xstart >> 3);
+		i2c_w(sd, OV7670_R18_HSTOP, xend >> 3);
+		v = i2c_r(sd, OV7670_R32_HREF);
+		v = (v & 0xc0) | ((xend & 0x7) << 3) | (xstart & 0x07);
+		msleep(10);	/* need to sleep between read and write to
+				 * same reg! */
+		i2c_w(sd, OV7670_R32_HREF, v);
+
+		i2c_w(sd, OV7670_R19_VSTART, ystart >> 2);
+		i2c_w(sd, OV7670_R1A_VSTOP, yend >> 2);
+		v = i2c_r(sd, OV7670_R03_VREF);
+		v = (v & 0xc0) | ((yend & 0x3) << 2) | (ystart & 0x03);
+		msleep(10);	/* need to sleep between read and write to
+				 * same reg! */
+		i2c_w(sd, OV7670_R03_VREF, v);
+		break;
+	case SEN_OV6620:
+		i2c_w_mask(sd, 0x14, qvga ? 0x20 : 0x00, 0x20);
+		i2c_w_mask(sd, 0x13, 0x00, 0x20); /* Select 16 bit data bus */
+		i2c_w_mask(sd, 0x12, 0x04, 0x06); /* AWB: 1 Test pattern: 0 */
+		break;
+	case SEN_OV6630:
+	case SEN_OV66308AF:
+		i2c_w_mask(sd, 0x14, qvga ? 0x20 : 0x00, 0x20);
+		i2c_w_mask(sd, 0x12, 0x04, 0x06); /* AWB: 1 Test pattern: 0 */
+		break;
+	case SEN_OV9600: {
+		const struct ov_i2c_regvals *vals;
+		static const struct ov_i2c_regvals sxga_15[] = {
+			{0x11, 0x80}, {0x14, 0x3e}, {0x24, 0x85}, {0x25, 0x75}
+		};
+		static const struct ov_i2c_regvals sxga_7_5[] = {
+			{0x11, 0x81}, {0x14, 0x3e}, {0x24, 0x85}, {0x25, 0x75}
+		};
+		static const struct ov_i2c_regvals vga_30[] = {
+			{0x11, 0x81}, {0x14, 0x7e}, {0x24, 0x70}, {0x25, 0x60}
+		};
+		static const struct ov_i2c_regvals vga_15[] = {
+			{0x11, 0x83}, {0x14, 0x3e}, {0x24, 0x80}, {0x25, 0x70}
+		};
+
+		/* frame rates:
+		 *	15fps / 7.5 fps for 1280x1024
+		 *	30fps / 15fps for 640x480
+		 */
+		i2c_w_mask(sd, 0x12, qvga ? 0x40 : 0x00, 0x40);
+		if (qvga)
+			vals = sd->frame_rate < 30 ? vga_15 : vga_30;
+		else
+			vals = sd->frame_rate < 15 ? sxga_7_5 : sxga_15;
+		write_i2c_regvals(sd, vals, ARRAY_SIZE(sxga_15));
+		return;
+	    }
+	default:
+		return;
+	}
+
+	/******** Clock programming ********/
+	i2c_w(sd, 0x11, sd->clockdiv);
+}
+
+/* this function works for bridge ov519 and sensors ov7660 and ov7670 only */
+static void sethvflip(struct gspca_dev *gspca_dev, s32 hflip, s32 vflip)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->gspca_dev.streaming)
+		reg_w(sd, OV519_R51_RESET1, 0x0f);	/* block stream */
+	i2c_w_mask(sd, OV7670_R1E_MVFP,
+		OV7670_MVFP_MIRROR * hflip | OV7670_MVFP_VFLIP * vflip,
+		OV7670_MVFP_MIRROR | OV7670_MVFP_VFLIP);
+	if (sd->gspca_dev.streaming)
+		reg_w(sd, OV519_R51_RESET1, 0x00);	/* restart stream */
+}
+
+static void set_ov_sensor_window(struct sd *sd)
+{
+	struct gspca_dev *gspca_dev;
+	int qvga, crop;
+	int hwsbase, hwebase, vwsbase, vwebase, hwscale, vwscale;
+
+	/* mode setup is fully handled in mode_init_ov_sensor_regs for these */
+	switch (sd->sensor) {
+	case SEN_OV2610:
+	case SEN_OV2610AE:
+	case SEN_OV3610:
+	case SEN_OV7670:
+	case SEN_OV9600:
+		mode_init_ov_sensor_regs(sd);
+		return;
+	case SEN_OV7660:
+		ov519_set_mode(sd);
+		ov519_set_fr(sd);
+		return;
+	}
+
+	gspca_dev = &sd->gspca_dev;
+	qvga = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].priv & 1;
+	crop = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].priv & 2;
+
+	/* The different sensor ICs handle setting up of window differently.
+	 * IF YOU SET IT WRONG, YOU WILL GET ALL ZERO ISOC DATA FROM OV51x!! */
+	switch (sd->sensor) {
+	case SEN_OV8610:
+		hwsbase = 0x1e;
+		hwebase = 0x1e;
+		vwsbase = 0x02;
+		vwebase = 0x02;
+		break;
+	case SEN_OV7610:
+	case SEN_OV76BE:
+		hwsbase = 0x38;
+		hwebase = 0x3a;
+		vwsbase = vwebase = 0x05;
+		break;
+	case SEN_OV6620:
+	case SEN_OV6630:
+	case SEN_OV66308AF:
+		hwsbase = 0x38;
+		hwebase = 0x3a;
+		vwsbase = 0x05;
+		vwebase = 0x06;
+		if (sd->sensor == SEN_OV66308AF && qvga)
+			/* HDG: this fixes U and V getting swapped */
+			hwsbase++;
+		if (crop) {
+			hwsbase += 8;
+			hwebase += 8;
+			vwsbase += 11;
+			vwebase += 11;
+		}
+		break;
+	case SEN_OV7620:
+	case SEN_OV7620AE:
+		hwsbase = 0x2f;		/* From 7620.SET (spec is wrong) */
+		hwebase = 0x2f;
+		vwsbase = vwebase = 0x05;
+		break;
+	case SEN_OV7640:
+	case SEN_OV7648:
+		hwsbase = 0x1a;
+		hwebase = 0x1a;
+		vwsbase = vwebase = 0x03;
+		break;
+	default:
+		return;
+	}
+
+	switch (sd->sensor) {
+	case SEN_OV6620:
+	case SEN_OV6630:
+	case SEN_OV66308AF:
+		if (qvga) {		/* QCIF */
+			hwscale = 0;
+			vwscale = 0;
+		} else {		/* CIF */
+			hwscale = 1;
+			vwscale = 1;	/* The datasheet says 0;
+					 * it's wrong */
+		}
+		break;
+	case SEN_OV8610:
+		if (qvga) {		/* QSVGA */
+			hwscale = 1;
+			vwscale = 1;
+		} else {		/* SVGA */
+			hwscale = 2;
+			vwscale = 2;
+		}
+		break;
+	default:			/* SEN_OV7xx0 */
+		if (qvga) {		/* QVGA */
+			hwscale = 1;
+			vwscale = 0;
+		} else {		/* VGA */
+			hwscale = 2;
+			vwscale = 1;
+		}
+	}
+
+	mode_init_ov_sensor_regs(sd);
+
+	i2c_w(sd, 0x17, hwsbase);
+	i2c_w(sd, 0x18, hwebase + (sd->sensor_width >> hwscale));
+	i2c_w(sd, 0x19, vwsbase);
+	i2c_w(sd, 0x1a, vwebase + (sd->sensor_height >> vwscale));
+}
+
+/* -- start the camera -- */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	/* Default for most bridges, allow bridge_mode_init_regs to override */
+	sd->sensor_width = sd->gspca_dev.pixfmt.width;
+	sd->sensor_height = sd->gspca_dev.pixfmt.height;
+
+	switch (sd->bridge) {
+	case BRIDGE_OV511:
+	case BRIDGE_OV511PLUS:
+		ov511_mode_init_regs(sd);
+		break;
+	case BRIDGE_OV518:
+	case BRIDGE_OV518PLUS:
+		ov518_mode_init_regs(sd);
+		break;
+	case BRIDGE_OV519:
+		ov519_mode_init_regs(sd);
+		break;
+	/* case BRIDGE_OVFX2: nothing to do */
+	case BRIDGE_W9968CF:
+		w9968cf_mode_init_regs(sd);
+		break;
+	}
+
+	set_ov_sensor_window(sd);
+
+	/* Force clear snapshot state in case the snapshot button was
+	   pressed while we weren't streaming */
+	sd->snapshot_needs_reset = 1;
+	sd_reset_snapshot(gspca_dev);
+
+	sd->first_frame = 3;
+
+	ov51x_restart(sd);
+	ov51x_led_control(sd, 1);
+	return gspca_dev->usb_err;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	ov51x_stop(sd);
+	ov51x_led_control(sd, 0);
+}
+
+static void sd_stop0(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (!sd->gspca_dev.present)
+		return;
+	if (sd->bridge == BRIDGE_W9968CF)
+		w9968cf_stop0(sd);
+
+#if IS_ENABLED(CONFIG_INPUT)
+	/* If the last button state is pressed, release it now! */
+	if (sd->snapshot_pressed) {
+		input_report_key(gspca_dev->input_dev, KEY_CAMERA, 0);
+		input_sync(gspca_dev->input_dev);
+		sd->snapshot_pressed = 0;
+	}
+#endif
+	if (sd->bridge == BRIDGE_OV519)
+		reg_w(sd, OV519_R57_SNAPSHOT, 0x23);
+}
+
+static void ov51x_handle_button(struct gspca_dev *gspca_dev, u8 state)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->snapshot_pressed != state) {
+#if IS_ENABLED(CONFIG_INPUT)
+		input_report_key(gspca_dev->input_dev, KEY_CAMERA, state);
+		input_sync(gspca_dev->input_dev);
+#endif
+		if (state)
+			sd->snapshot_needs_reset = 1;
+
+		sd->snapshot_pressed = state;
+	} else {
+		/* On the ov511 / ov519 we need to reset the button state
+		   multiple times, as resetting does not work as long as the
+		   button stays pressed */
+		switch (sd->bridge) {
+		case BRIDGE_OV511:
+		case BRIDGE_OV511PLUS:
+		case BRIDGE_OV519:
+			if (state)
+				sd->snapshot_needs_reset = 1;
+			break;
+		}
+	}
+}
+
+static void ov511_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *in,			/* isoc packet */
+			int len)		/* iso packet length */
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	/* SOF/EOF packets have 1st to 8th bytes zeroed and the 9th
+	 * byte non-zero. The EOF packet has image width/height in the
+	 * 10th and 11th bytes. The 9th byte is given as follows:
+	 *
+	 * bit 7: EOF
+	 *     6: compression enabled
+	 *     5: 422/420/400 modes
+	 *     4: 422/420/400 modes
+	 *     3: 1
+	 *     2: snapshot button on
+	 *     1: snapshot frame
+	 *     0: even/odd field
+	 */
+	if (!(in[0] | in[1] | in[2] | in[3] | in[4] | in[5] | in[6] | in[7]) &&
+	    (in[8] & 0x08)) {
+		ov51x_handle_button(gspca_dev, (in[8] >> 2) & 1);
+		if (in[8] & 0x80) {
+			/* Frame end */
+			if ((in[9] + 1) * 8 != gspca_dev->pixfmt.width ||
+			    (in[10] + 1) * 8 != gspca_dev->pixfmt.height) {
+				gspca_err(gspca_dev, "Invalid frame size, got: %dx%d, requested: %dx%d\n",
+					  (in[9] + 1) * 8, (in[10] + 1) * 8,
+					  gspca_dev->pixfmt.width,
+					  gspca_dev->pixfmt.height);
+				gspca_dev->last_packet_type = DISCARD_PACKET;
+				return;
+			}
+			/* Add 11 byte footer to frame, might be useful */
+			gspca_frame_add(gspca_dev, LAST_PACKET, in, 11);
+			return;
+		} else {
+			/* Frame start */
+			gspca_frame_add(gspca_dev, FIRST_PACKET, in, 0);
+			sd->packet_nr = 0;
+		}
+	}
+
+	/* Ignore the packet number */
+	len--;
+
+	/* intermediate packet */
+	gspca_frame_add(gspca_dev, INTER_PACKET, in, len);
+}
+
+static void ov518_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	/* A false positive here is likely, until OVT gives me
+	 * the definitive SOF/EOF format */
+	if ((!(data[0] | data[1] | data[2] | data[3] | data[5])) && data[6]) {
+		ov51x_handle_button(gspca_dev, (data[6] >> 1) & 1);
+		gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
+		gspca_frame_add(gspca_dev, FIRST_PACKET, NULL, 0);
+		sd->packet_nr = 0;
+	}
+
+	if (gspca_dev->last_packet_type == DISCARD_PACKET)
+		return;
+
+	/* Does this device use packet numbers ? */
+	if (len & 7) {
+		len--;
+		if (sd->packet_nr == data[len])
+			sd->packet_nr++;
+		/* The last few packets of the frame (which are all 0's
+		   except that they may contain part of the footer), are
+		   numbered 0 */
+		else if (sd->packet_nr == 0 || data[len]) {
+			gspca_err(gspca_dev, "Invalid packet nr: %d (expect: %d)\n",
+				  (int)data[len], (int)sd->packet_nr);
+			gspca_dev->last_packet_type = DISCARD_PACKET;
+			return;
+		}
+	}
+
+	/* intermediate packet */
+	gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+}
+
+static void ov519_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	/* Header of ov519 is 16 bytes:
+	 *     Byte     Value      Description
+	 *	0	0xff	magic
+	 *	1	0xff	magic
+	 *	2	0xff	magic
+	 *	3	0xXX	0x50 = SOF, 0x51 = EOF
+	 *	9	0xXX	0x01 initial frame without data,
+	 *			0x00 standard frame with image
+	 *	14	Lo	in EOF: length of image data / 8
+	 *	15	Hi
+	 */
+
+	if (data[0] == 0xff && data[1] == 0xff && data[2] == 0xff) {
+		switch (data[3]) {
+		case 0x50:		/* start of frame */
+			/* Don't check the button state here, as the state
+			   usually (always ?) changes at EOF and checking it
+			   here leads to unnecessary snapshot state resets. */
+#define HDRSZ 16
+			data += HDRSZ;
+			len -= HDRSZ;
+#undef HDRSZ
+			if (data[0] == 0xff || data[1] == 0xd8)
+				gspca_frame_add(gspca_dev, FIRST_PACKET,
+						data, len);
+			else
+				gspca_dev->last_packet_type = DISCARD_PACKET;
+			return;
+		case 0x51:		/* end of frame */
+			ov51x_handle_button(gspca_dev, data[11] & 1);
+			if (data[9] != 0)
+				gspca_dev->last_packet_type = DISCARD_PACKET;
+			gspca_frame_add(gspca_dev, LAST_PACKET,
+					NULL, 0);
+			return;
+		}
+	}
+
+	/* intermediate packet */
+	gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+}
+
+static void ovfx2_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+
+	/* A short read signals EOF */
+	if (len < gspca_dev->cam.bulk_size) {
+		/* If the frame is short, and it is one of the first ones
+		   the sensor and bridge are still syncing, so drop it. */
+		if (sd->first_frame) {
+			sd->first_frame--;
+			if (gspca_dev->image_len <
+				  sd->gspca_dev.pixfmt.width *
+					sd->gspca_dev.pixfmt.height)
+				gspca_dev->last_packet_type = DISCARD_PACKET;
+		}
+		gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
+		gspca_frame_add(gspca_dev, FIRST_PACKET, NULL, 0);
+	}
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	switch (sd->bridge) {
+	case BRIDGE_OV511:
+	case BRIDGE_OV511PLUS:
+		ov511_pkt_scan(gspca_dev, data, len);
+		break;
+	case BRIDGE_OV518:
+	case BRIDGE_OV518PLUS:
+		ov518_pkt_scan(gspca_dev, data, len);
+		break;
+	case BRIDGE_OV519:
+		ov519_pkt_scan(gspca_dev, data, len);
+		break;
+	case BRIDGE_OVFX2:
+		ovfx2_pkt_scan(gspca_dev, data, len);
+		break;
+	case BRIDGE_W9968CF:
+		w9968cf_pkt_scan(gspca_dev, data, len);
+		break;
+	}
+}
+
+/* -- management routines -- */
+
+static void setbrightness(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	static const struct ov_i2c_regvals brit_7660[][7] = {
+		{{0x0f, 0x6a}, {0x24, 0x40}, {0x25, 0x2b}, {0x26, 0x90},
+			{0x27, 0xe0}, {0x28, 0xe0}, {0x2c, 0xe0}},
+		{{0x0f, 0x6a}, {0x24, 0x50}, {0x25, 0x40}, {0x26, 0xa1},
+			{0x27, 0xc0}, {0x28, 0xc0}, {0x2c, 0xc0}},
+		{{0x0f, 0x6a}, {0x24, 0x68}, {0x25, 0x58}, {0x26, 0xc2},
+			{0x27, 0xa0}, {0x28, 0xa0}, {0x2c, 0xa0}},
+		{{0x0f, 0x6a}, {0x24, 0x70}, {0x25, 0x68}, {0x26, 0xd3},
+			{0x27, 0x80}, {0x28, 0x80}, {0x2c, 0x80}},
+		{{0x0f, 0x6a}, {0x24, 0x80}, {0x25, 0x70}, {0x26, 0xd3},
+			{0x27, 0x20}, {0x28, 0x20}, {0x2c, 0x20}},
+		{{0x0f, 0x6a}, {0x24, 0x88}, {0x25, 0x78}, {0x26, 0xd3},
+			{0x27, 0x40}, {0x28, 0x40}, {0x2c, 0x40}},
+		{{0x0f, 0x6a}, {0x24, 0x90}, {0x25, 0x80}, {0x26, 0xd4},
+			{0x27, 0x60}, {0x28, 0x60}, {0x2c, 0x60}}
+	};
+
+	switch (sd->sensor) {
+	case SEN_OV8610:
+	case SEN_OV7610:
+	case SEN_OV76BE:
+	case SEN_OV6620:
+	case SEN_OV6630:
+	case SEN_OV66308AF:
+	case SEN_OV7640:
+	case SEN_OV7648:
+		i2c_w(sd, OV7610_REG_BRT, val);
+		break;
+	case SEN_OV7620:
+	case SEN_OV7620AE:
+		i2c_w(sd, OV7610_REG_BRT, val);
+		break;
+	case SEN_OV7660:
+		write_i2c_regvals(sd, brit_7660[val],
+				ARRAY_SIZE(brit_7660[0]));
+		break;
+	case SEN_OV7670:
+/*win trace
+ *		i2c_w_mask(sd, OV7670_R13_COM8, 0, OV7670_COM8_AEC); */
+		i2c_w(sd, OV7670_R55_BRIGHT, ov7670_abs_to_sm(val));
+		break;
+	}
+}
+
+static void setcontrast(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	static const struct ov_i2c_regvals contrast_7660[][31] = {
+		{{0x6c, 0xf0}, {0x6d, 0xf0}, {0x6e, 0xf8}, {0x6f, 0xa0},
+		 {0x70, 0x58}, {0x71, 0x38}, {0x72, 0x30}, {0x73, 0x30},
+		 {0x74, 0x28}, {0x75, 0x28}, {0x76, 0x24}, {0x77, 0x24},
+		 {0x78, 0x22}, {0x79, 0x28}, {0x7a, 0x2a}, {0x7b, 0x34},
+		 {0x7c, 0x0f}, {0x7d, 0x1e}, {0x7e, 0x3d}, {0x7f, 0x65},
+		 {0x80, 0x70}, {0x81, 0x77}, {0x82, 0x7d}, {0x83, 0x83},
+		 {0x84, 0x88}, {0x85, 0x8d}, {0x86, 0x96}, {0x87, 0x9f},
+		 {0x88, 0xb0}, {0x89, 0xc4}, {0x8a, 0xd9}},
+		{{0x6c, 0xf0}, {0x6d, 0xf0}, {0x6e, 0xf8}, {0x6f, 0x94},
+		 {0x70, 0x58}, {0x71, 0x40}, {0x72, 0x30}, {0x73, 0x30},
+		 {0x74, 0x30}, {0x75, 0x30}, {0x76, 0x2c}, {0x77, 0x24},
+		 {0x78, 0x22}, {0x79, 0x28}, {0x7a, 0x2a}, {0x7b, 0x31},
+		 {0x7c, 0x0f}, {0x7d, 0x1e}, {0x7e, 0x3d}, {0x7f, 0x62},
+		 {0x80, 0x6d}, {0x81, 0x75}, {0x82, 0x7b}, {0x83, 0x81},
+		 {0x84, 0x87}, {0x85, 0x8d}, {0x86, 0x98}, {0x87, 0xa1},
+		 {0x88, 0xb2}, {0x89, 0xc6}, {0x8a, 0xdb}},
+		{{0x6c, 0xf0}, {0x6d, 0xf0}, {0x6e, 0xf0}, {0x6f, 0x84},
+		 {0x70, 0x58}, {0x71, 0x48}, {0x72, 0x40}, {0x73, 0x40},
+		 {0x74, 0x28}, {0x75, 0x28}, {0x76, 0x28}, {0x77, 0x24},
+		 {0x78, 0x26}, {0x79, 0x28}, {0x7a, 0x28}, {0x7b, 0x34},
+		 {0x7c, 0x0f}, {0x7d, 0x1e}, {0x7e, 0x3c}, {0x7f, 0x5d},
+		 {0x80, 0x68}, {0x81, 0x71}, {0x82, 0x79}, {0x83, 0x81},
+		 {0x84, 0x86}, {0x85, 0x8b}, {0x86, 0x95}, {0x87, 0x9e},
+		 {0x88, 0xb1}, {0x89, 0xc5}, {0x8a, 0xd9}},
+		{{0x6c, 0xf0}, {0x6d, 0xf0}, {0x6e, 0xf0}, {0x6f, 0x70},
+		 {0x70, 0x58}, {0x71, 0x58}, {0x72, 0x48}, {0x73, 0x48},
+		 {0x74, 0x38}, {0x75, 0x40}, {0x76, 0x34}, {0x77, 0x34},
+		 {0x78, 0x2e}, {0x79, 0x28}, {0x7a, 0x24}, {0x7b, 0x22},
+		 {0x7c, 0x0f}, {0x7d, 0x1e}, {0x7e, 0x3c}, {0x7f, 0x58},
+		 {0x80, 0x63}, {0x81, 0x6e}, {0x82, 0x77}, {0x83, 0x80},
+		 {0x84, 0x87}, {0x85, 0x8f}, {0x86, 0x9c}, {0x87, 0xa9},
+		 {0x88, 0xc0}, {0x89, 0xd4}, {0x8a, 0xe6}},
+		{{0x6c, 0xa0}, {0x6d, 0xf0}, {0x6e, 0x90}, {0x6f, 0x80},
+		 {0x70, 0x70}, {0x71, 0x80}, {0x72, 0x60}, {0x73, 0x60},
+		 {0x74, 0x58}, {0x75, 0x60}, {0x76, 0x4c}, {0x77, 0x38},
+		 {0x78, 0x38}, {0x79, 0x2a}, {0x7a, 0x20}, {0x7b, 0x0e},
+		 {0x7c, 0x0a}, {0x7d, 0x14}, {0x7e, 0x26}, {0x7f, 0x46},
+		 {0x80, 0x54}, {0x81, 0x64}, {0x82, 0x70}, {0x83, 0x7c},
+		 {0x84, 0x87}, {0x85, 0x93}, {0x86, 0xa6}, {0x87, 0xb4},
+		 {0x88, 0xd0}, {0x89, 0xe5}, {0x8a, 0xf5}},
+		{{0x6c, 0x60}, {0x6d, 0x80}, {0x6e, 0x60}, {0x6f, 0x80},
+		 {0x70, 0x80}, {0x71, 0x80}, {0x72, 0x88}, {0x73, 0x30},
+		 {0x74, 0x70}, {0x75, 0x68}, {0x76, 0x64}, {0x77, 0x50},
+		 {0x78, 0x3c}, {0x79, 0x22}, {0x7a, 0x10}, {0x7b, 0x08},
+		 {0x7c, 0x06}, {0x7d, 0x0e}, {0x7e, 0x1a}, {0x7f, 0x3a},
+		 {0x80, 0x4a}, {0x81, 0x5a}, {0x82, 0x6b}, {0x83, 0x7b},
+		 {0x84, 0x89}, {0x85, 0x96}, {0x86, 0xaf}, {0x87, 0xc3},
+		 {0x88, 0xe1}, {0x89, 0xf2}, {0x8a, 0xfa}},
+		{{0x6c, 0x20}, {0x6d, 0x40}, {0x6e, 0x20}, {0x6f, 0x60},
+		 {0x70, 0x88}, {0x71, 0xc8}, {0x72, 0xc0}, {0x73, 0xb8},
+		 {0x74, 0xa8}, {0x75, 0xb8}, {0x76, 0x80}, {0x77, 0x5c},
+		 {0x78, 0x26}, {0x79, 0x10}, {0x7a, 0x08}, {0x7b, 0x04},
+		 {0x7c, 0x02}, {0x7d, 0x06}, {0x7e, 0x0a}, {0x7f, 0x22},
+		 {0x80, 0x33}, {0x81, 0x4c}, {0x82, 0x64}, {0x83, 0x7b},
+		 {0x84, 0x90}, {0x85, 0xa7}, {0x86, 0xc7}, {0x87, 0xde},
+		 {0x88, 0xf1}, {0x89, 0xf9}, {0x8a, 0xfd}},
+	};
+
+	switch (sd->sensor) {
+	case SEN_OV7610:
+	case SEN_OV6620:
+		i2c_w(sd, OV7610_REG_CNT, val);
+		break;
+	case SEN_OV6630:
+	case SEN_OV66308AF:
+		i2c_w_mask(sd, OV7610_REG_CNT, val >> 4, 0x0f);
+		break;
+	case SEN_OV8610: {
+		static const u8 ctab[] = {
+			0x03, 0x09, 0x0b, 0x0f, 0x53, 0x6f, 0x35, 0x7f
+		};
+
+		/* Use Y gamma control instead. Bit 0 enables it. */
+		i2c_w(sd, 0x64, ctab[val >> 5]);
+		break;
+	    }
+	case SEN_OV7620:
+	case SEN_OV7620AE: {
+		static const u8 ctab[] = {
+			0x01, 0x05, 0x09, 0x11, 0x15, 0x35, 0x37, 0x57,
+			0x5b, 0xa5, 0xa7, 0xc7, 0xc9, 0xcf, 0xef, 0xff
+		};
+
+		/* Use Y gamma control instead. Bit 0 enables it. */
+		i2c_w(sd, 0x64, ctab[val >> 4]);
+		break;
+	    }
+	case SEN_OV7660:
+		write_i2c_regvals(sd, contrast_7660[val],
+					ARRAY_SIZE(contrast_7660[0]));
+		break;
+	case SEN_OV7670:
+		/* check that this isn't just the same as ov7610 */
+		i2c_w(sd, OV7670_R56_CONTRAS, val >> 1);
+		break;
+	}
+}
+
+static void setexposure(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	i2c_w(sd, 0x10, val);
+}
+
+static void setcolors(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	static const struct ov_i2c_regvals colors_7660[][6] = {
+		{{0x4f, 0x28}, {0x50, 0x2a}, {0x51, 0x02}, {0x52, 0x0a},
+		 {0x53, 0x19}, {0x54, 0x23}},
+		{{0x4f, 0x47}, {0x50, 0x4a}, {0x51, 0x03}, {0x52, 0x11},
+		 {0x53, 0x2c}, {0x54, 0x3e}},
+		{{0x4f, 0x66}, {0x50, 0x6b}, {0x51, 0x05}, {0x52, 0x19},
+		 {0x53, 0x40}, {0x54, 0x59}},
+		{{0x4f, 0x84}, {0x50, 0x8b}, {0x51, 0x06}, {0x52, 0x20},
+		 {0x53, 0x53}, {0x54, 0x73}},
+		{{0x4f, 0xa3}, {0x50, 0xab}, {0x51, 0x08}, {0x52, 0x28},
+		 {0x53, 0x66}, {0x54, 0x8e}},
+	};
+
+	switch (sd->sensor) {
+	case SEN_OV8610:
+	case SEN_OV7610:
+	case SEN_OV76BE:
+	case SEN_OV6620:
+	case SEN_OV6630:
+	case SEN_OV66308AF:
+		i2c_w(sd, OV7610_REG_SAT, val);
+		break;
+	case SEN_OV7620:
+	case SEN_OV7620AE:
+		/* Use UV gamma control instead. Bits 0 & 7 are reserved. */
+/*		rc = ov_i2c_write(sd->dev, 0x62, (val >> 9) & 0x7e);
+		if (rc < 0)
+			goto out; */
+		i2c_w(sd, OV7610_REG_SAT, val);
+		break;
+	case SEN_OV7640:
+	case SEN_OV7648:
+		i2c_w(sd, OV7610_REG_SAT, val & 0xf0);
+		break;
+	case SEN_OV7660:
+		write_i2c_regvals(sd, colors_7660[val],
+					ARRAY_SIZE(colors_7660[0]));
+		break;
+	case SEN_OV7670:
+		/* supported later once I work out how to do it
+		 * transparently fail now! */
+		/* set REG_COM13 values for UV sat auto mode */
+		break;
+	}
+}
+
+static void setautobright(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	i2c_w_mask(sd, 0x2d, val ? 0x10 : 0x00, 0x10);
+}
+
+static void setfreq_i(struct sd *sd, s32 val)
+{
+	if (sd->sensor == SEN_OV7660
+	 || sd->sensor == SEN_OV7670) {
+		switch (val) {
+		case 0: /* Banding filter disabled */
+			i2c_w_mask(sd, OV7670_R13_COM8, 0, OV7670_COM8_BFILT);
+			break;
+		case 1: /* 50 hz */
+			i2c_w_mask(sd, OV7670_R13_COM8, OV7670_COM8_BFILT,
+				   OV7670_COM8_BFILT);
+			i2c_w_mask(sd, OV7670_R3B_COM11, 0x08, 0x18);
+			break;
+		case 2: /* 60 hz */
+			i2c_w_mask(sd, OV7670_R13_COM8, OV7670_COM8_BFILT,
+				   OV7670_COM8_BFILT);
+			i2c_w_mask(sd, OV7670_R3B_COM11, 0x00, 0x18);
+			break;
+		case 3: /* Auto hz - ov7670 only */
+			i2c_w_mask(sd, OV7670_R13_COM8, OV7670_COM8_BFILT,
+				   OV7670_COM8_BFILT);
+			i2c_w_mask(sd, OV7670_R3B_COM11, OV7670_COM11_HZAUTO,
+				   0x18);
+			break;
+		}
+	} else {
+		switch (val) {
+		case 0: /* Banding filter disabled */
+			i2c_w_mask(sd, 0x2d, 0x00, 0x04);
+			i2c_w_mask(sd, 0x2a, 0x00, 0x80);
+			break;
+		case 1: /* 50 hz (filter on and framerate adj) */
+			i2c_w_mask(sd, 0x2d, 0x04, 0x04);
+			i2c_w_mask(sd, 0x2a, 0x80, 0x80);
+			/* 20 fps -> 16.667 fps */
+			if (sd->sensor == SEN_OV6620 ||
+			    sd->sensor == SEN_OV6630 ||
+			    sd->sensor == SEN_OV66308AF)
+				i2c_w(sd, 0x2b, 0x5e);
+			else
+				i2c_w(sd, 0x2b, 0xac);
+			break;
+		case 2: /* 60 hz (filter on, ...) */
+			i2c_w_mask(sd, 0x2d, 0x04, 0x04);
+			if (sd->sensor == SEN_OV6620 ||
+			    sd->sensor == SEN_OV6630 ||
+			    sd->sensor == SEN_OV66308AF) {
+				/* 20 fps -> 15 fps */
+				i2c_w_mask(sd, 0x2a, 0x80, 0x80);
+				i2c_w(sd, 0x2b, 0xa8);
+			} else {
+				/* no framerate adj. */
+				i2c_w_mask(sd, 0x2a, 0x00, 0x80);
+			}
+			break;
+		}
+	}
+}
+
+static void setfreq(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	setfreq_i(sd, val);
+
+	/* Ugly but necessary */
+	if (sd->bridge == BRIDGE_W9968CF)
+		w9968cf_set_crop_window(sd);
+}
+
+static int sd_get_jcomp(struct gspca_dev *gspca_dev,
+			struct v4l2_jpegcompression *jcomp)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->bridge != BRIDGE_W9968CF)
+		return -ENOTTY;
+
+	memset(jcomp, 0, sizeof *jcomp);
+	jcomp->quality = v4l2_ctrl_g_ctrl(sd->jpegqual);
+	jcomp->jpeg_markers = V4L2_JPEG_MARKER_DHT | V4L2_JPEG_MARKER_DQT |
+			      V4L2_JPEG_MARKER_DRI;
+	return 0;
+}
+
+static int sd_set_jcomp(struct gspca_dev *gspca_dev,
+			const struct v4l2_jpegcompression *jcomp)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->bridge != BRIDGE_W9968CF)
+		return -ENOTTY;
+
+	v4l2_ctrl_s_ctrl(sd->jpegqual, jcomp->quality);
+	return 0;
+}
+
+static int sd_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *)gspca_dev;
+
+	gspca_dev->usb_err = 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_AUTOGAIN:
+		gspca_dev->exposure->val = i2c_r(sd, 0x10);
+		break;
+	}
+	return 0;
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *)gspca_dev;
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		setbrightness(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_CONTRAST:
+		setcontrast(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_POWER_LINE_FREQUENCY:
+		setfreq(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_AUTOBRIGHTNESS:
+		if (ctrl->is_new)
+			setautobright(gspca_dev, ctrl->val);
+		if (!ctrl->val && sd->brightness->is_new)
+			setbrightness(gspca_dev, sd->brightness->val);
+		break;
+	case V4L2_CID_SATURATION:
+		setcolors(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_HFLIP:
+		sethvflip(gspca_dev, ctrl->val, sd->vflip->val);
+		break;
+	case V4L2_CID_AUTOGAIN:
+		if (ctrl->is_new)
+			setautogain(gspca_dev, ctrl->val);
+		if (!ctrl->val && gspca_dev->exposure->is_new)
+			setexposure(gspca_dev, gspca_dev->exposure->val);
+		break;
+	case V4L2_CID_JPEG_COMPRESSION_QUALITY:
+		return -EBUSY; /* Should never happen, as we grab the ctrl */
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.g_volatile_ctrl = sd_g_volatile_ctrl,
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *)gspca_dev;
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 10);
+	if (valid_controls[sd->sensor].has_brightness)
+		sd->brightness = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0,
+			sd->sensor == SEN_OV7660 ? 6 : 255, 1,
+			sd->sensor == SEN_OV7660 ? 3 : 127);
+	if (valid_controls[sd->sensor].has_contrast) {
+		if (sd->sensor == SEN_OV7660)
+			v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+				V4L2_CID_CONTRAST, 0, 6, 1, 3);
+		else
+			v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+				V4L2_CID_CONTRAST, 0, 255, 1,
+				(sd->sensor == SEN_OV6630 ||
+				 sd->sensor == SEN_OV66308AF) ? 200 : 127);
+	}
+	if (valid_controls[sd->sensor].has_sat)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_SATURATION, 0,
+			sd->sensor == SEN_OV7660 ? 4 : 255, 1,
+			sd->sensor == SEN_OV7660 ? 2 : 127);
+	if (valid_controls[sd->sensor].has_exposure)
+		gspca_dev->exposure = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_EXPOSURE, 0, 255, 1, 127);
+	if (valid_controls[sd->sensor].has_hvflip) {
+		sd->hflip = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_HFLIP, 0, 1, 1, 0);
+		sd->vflip = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_VFLIP, 0, 1, 1, 0);
+	}
+	if (valid_controls[sd->sensor].has_autobright)
+		sd->autobright = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_AUTOBRIGHTNESS, 0, 1, 1, 1);
+	if (valid_controls[sd->sensor].has_autogain)
+		gspca_dev->autogain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
+	if (valid_controls[sd->sensor].has_freq) {
+		if (sd->sensor == SEN_OV7670)
+			sd->freq = v4l2_ctrl_new_std_menu(hdl, &sd_ctrl_ops,
+				V4L2_CID_POWER_LINE_FREQUENCY,
+				V4L2_CID_POWER_LINE_FREQUENCY_AUTO, 0,
+				V4L2_CID_POWER_LINE_FREQUENCY_AUTO);
+		else
+			sd->freq = v4l2_ctrl_new_std_menu(hdl, &sd_ctrl_ops,
+				V4L2_CID_POWER_LINE_FREQUENCY,
+				V4L2_CID_POWER_LINE_FREQUENCY_60HZ, 0, 0);
+	}
+	if (sd->bridge == BRIDGE_W9968CF)
+		sd->jpegqual = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_JPEG_COMPRESSION_QUALITY,
+			QUALITY_MIN, QUALITY_MAX, 1, QUALITY_DEF);
+
+	if (hdl->error) {
+		gspca_err(gspca_dev, "Could not initialize controls\n");
+		return hdl->error;
+	}
+	if (gspca_dev->autogain)
+		v4l2_ctrl_auto_cluster(3, &gspca_dev->autogain, 0, true);
+	if (sd->autobright)
+		v4l2_ctrl_auto_cluster(2, &sd->autobright, 0, false);
+	if (sd->hflip)
+		v4l2_ctrl_cluster(2, &sd->hflip);
+	return 0;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.isoc_init = sd_isoc_init,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.stop0 = sd_stop0,
+	.pkt_scan = sd_pkt_scan,
+	.dq_callback = sd_reset_snapshot,
+	.get_jcomp = sd_get_jcomp,
+	.set_jcomp = sd_set_jcomp,
+#if IS_ENABLED(CONFIG_INPUT)
+	.other_input = 1,
+#endif
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x041e, 0x4003), .driver_info = BRIDGE_W9968CF },
+	{USB_DEVICE(0x041e, 0x4052),
+		.driver_info = BRIDGE_OV519 | BRIDGE_INVERT_LED },
+	{USB_DEVICE(0x041e, 0x405f), .driver_info = BRIDGE_OV519 },
+	{USB_DEVICE(0x041e, 0x4060), .driver_info = BRIDGE_OV519 },
+	{USB_DEVICE(0x041e, 0x4061), .driver_info = BRIDGE_OV519 },
+	{USB_DEVICE(0x041e, 0x4064), .driver_info = BRIDGE_OV519 },
+	{USB_DEVICE(0x041e, 0x4067), .driver_info = BRIDGE_OV519 },
+	{USB_DEVICE(0x041e, 0x4068), .driver_info = BRIDGE_OV519 },
+	{USB_DEVICE(0x045e, 0x028c),
+		.driver_info = BRIDGE_OV519 | BRIDGE_INVERT_LED },
+	{USB_DEVICE(0x054c, 0x0154), .driver_info = BRIDGE_OV519 },
+	{USB_DEVICE(0x054c, 0x0155), .driver_info = BRIDGE_OV519 },
+	{USB_DEVICE(0x05a9, 0x0511), .driver_info = BRIDGE_OV511 },
+	{USB_DEVICE(0x05a9, 0x0518), .driver_info = BRIDGE_OV518 },
+	{USB_DEVICE(0x05a9, 0x0519),
+		.driver_info = BRIDGE_OV519 | BRIDGE_INVERT_LED },
+	{USB_DEVICE(0x05a9, 0x0530),
+		.driver_info = BRIDGE_OV519 | BRIDGE_INVERT_LED },
+	{USB_DEVICE(0x05a9, 0x2800), .driver_info = BRIDGE_OVFX2 },
+	{USB_DEVICE(0x05a9, 0x4519), .driver_info = BRIDGE_OV519 },
+	{USB_DEVICE(0x05a9, 0x8519), .driver_info = BRIDGE_OV519 },
+	{USB_DEVICE(0x05a9, 0xa511), .driver_info = BRIDGE_OV511PLUS },
+	{USB_DEVICE(0x05a9, 0xa518), .driver_info = BRIDGE_OV518PLUS },
+	{USB_DEVICE(0x0813, 0x0002), .driver_info = BRIDGE_OV511PLUS },
+	{USB_DEVICE(0x0b62, 0x0059), .driver_info = BRIDGE_OVFX2 },
+	{USB_DEVICE(0x0e96, 0xc001), .driver_info = BRIDGE_OVFX2 },
+	{USB_DEVICE(0x1046, 0x9967), .driver_info = BRIDGE_W9968CF },
+	{USB_DEVICE(0x8020, 0xef04), .driver_info = BRIDGE_OVFX2 },
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
+
+module_param(frame_rate, int, 0644);
+MODULE_PARM_DESC(frame_rate, "Frame rate (5, 10, 15, 20 or 30 fps)");
diff --git a/drivers/media/usb/gspca/ov534.c b/drivers/media/usb/gspca/ov534.c
new file mode 100644
index 0000000..d06dc07
--- /dev/null
+++ b/drivers/media/usb/gspca/ov534.c
@@ -0,0 +1,1545 @@
+/*
+ * ov534-ov7xxx gspca driver
+ *
+ * Copyright (C) 2008 Antonio Ospite <ospite@studenti.unina.it>
+ * Copyright (C) 2008 Jim Paris <jim@jtan.com>
+ * Copyright (C) 2009 Jean-Francois Moine http://moinejf.free.fr
+ *
+ * Based on a prototype written by Mark Ferrell <majortrips@gmail.com>
+ * USB protocol reverse engineered by Jim Paris <jim@jtan.com>
+ * https://jim.sh/svn/jim/devl/playstation/ps3/eye/test/
+ *
+ * PS3 Eye camera enhanced by Richard Kaswy http://kaswy.free.fr
+ * PS3 Eye camera - brightness, contrast, awb, agc, aec controls
+ *                  added by Max Thrun <bear24rw@gmail.com>
+ * PS3 Eye camera - FPS range extended by Joseph Howse
+ *                  <josephhowse@nummist.com> http://nummist.com
+ *
+ * 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
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "ov534"
+
+#include "gspca.h"
+
+#include <linux/fixp-arith.h>
+#include <media/v4l2-ctrls.h>
+
+#define OV534_REG_ADDRESS	0xf1	/* sensor address */
+#define OV534_REG_SUBADDR	0xf2
+#define OV534_REG_WRITE		0xf3
+#define OV534_REG_READ		0xf4
+#define OV534_REG_OPERATION	0xf5
+#define OV534_REG_STATUS	0xf6
+
+#define OV534_OP_WRITE_3	0x37
+#define OV534_OP_WRITE_2	0x33
+#define OV534_OP_READ_2		0xf9
+
+#define CTRL_TIMEOUT 500
+#define DEFAULT_FRAME_RATE 30
+
+MODULE_AUTHOR("Antonio Ospite <ospite@studenti.unina.it>");
+MODULE_DESCRIPTION("GSPCA/OV534 USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+
+	struct v4l2_ctrl_handler ctrl_handler;
+	struct v4l2_ctrl *hue;
+	struct v4l2_ctrl *saturation;
+	struct v4l2_ctrl *brightness;
+	struct v4l2_ctrl *contrast;
+	struct { /* gain control cluster */
+		struct v4l2_ctrl *autogain;
+		struct v4l2_ctrl *gain;
+	};
+	struct v4l2_ctrl *autowhitebalance;
+	struct { /* exposure control cluster */
+		struct v4l2_ctrl *autoexposure;
+		struct v4l2_ctrl *exposure;
+	};
+	struct v4l2_ctrl *sharpness;
+	struct v4l2_ctrl *hflip;
+	struct v4l2_ctrl *vflip;
+	struct v4l2_ctrl *plfreq;
+
+	__u32 last_pts;
+	u16 last_fid;
+	u8 frame_rate;
+
+	u8 sensor;
+};
+enum sensors {
+	SENSOR_OV767x,
+	SENSOR_OV772x,
+	NSENSORS
+};
+
+static int sd_start(struct gspca_dev *gspca_dev);
+static void sd_stopN(struct gspca_dev *gspca_dev);
+
+
+static const struct v4l2_pix_format ov772x_mode[] = {
+	{320, 240, V4L2_PIX_FMT_YUYV, V4L2_FIELD_NONE,
+	 .bytesperline = 320 * 2,
+	 .sizeimage = 320 * 240 * 2,
+	 .colorspace = V4L2_COLORSPACE_SRGB,
+	 .priv = 1},
+	{640, 480, V4L2_PIX_FMT_YUYV, V4L2_FIELD_NONE,
+	 .bytesperline = 640 * 2,
+	 .sizeimage = 640 * 480 * 2,
+	 .colorspace = V4L2_COLORSPACE_SRGB,
+	 .priv = 0},
+};
+static const struct v4l2_pix_format ov767x_mode[] = {
+	{320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG},
+	{640, 480, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG},
+};
+
+static const u8 qvga_rates[] = {187, 150, 137, 125, 100, 75, 60, 50, 37, 30};
+static const u8 vga_rates[] = {60, 50, 40, 30, 15};
+
+static const struct framerates ov772x_framerates[] = {
+	{ /* 320x240 */
+		.rates = qvga_rates,
+		.nrates = ARRAY_SIZE(qvga_rates),
+	},
+	{ /* 640x480 */
+		.rates = vga_rates,
+		.nrates = ARRAY_SIZE(vga_rates),
+	},
+};
+
+struct reg_array {
+	const u8 (*val)[2];
+	int len;
+};
+
+static const u8 bridge_init_767x[][2] = {
+/* comments from the ms-win file apollo7670.set */
+/* str1 */
+	{0xf1, 0x42},
+	{0x88, 0xf8},
+	{0x89, 0xff},
+	{0x76, 0x03},
+	{0x92, 0x03},
+	{0x95, 0x10},
+	{0xe2, 0x00},
+	{0xe7, 0x3e},
+	{0x8d, 0x1c},
+	{0x8e, 0x00},
+	{0x8f, 0x00},
+	{0x1f, 0x00},
+	{0xc3, 0xf9},
+	{0x89, 0xff},
+	{0x88, 0xf8},
+	{0x76, 0x03},
+	{0x92, 0x01},
+	{0x93, 0x18},
+	{0x1c, 0x00},
+	{0x1d, 0x48},
+	{0x1d, 0x00},
+	{0x1d, 0xff},
+	{0x1d, 0x02},
+	{0x1d, 0x58},
+	{0x1d, 0x00},
+	{0x1c, 0x0a},
+	{0x1d, 0x0a},
+	{0x1d, 0x0e},
+	{0xc0, 0x50},	/* HSize 640 */
+	{0xc1, 0x3c},	/* VSize 480 */
+	{0x34, 0x05},	/* enable Audio Suspend mode */
+	{0xc2, 0x0c},	/* Input YUV */
+	{0xc3, 0xf9},	/* enable PRE */
+	{0x34, 0x05},	/* enable Audio Suspend mode */
+	{0xe7, 0x2e},	/* this solves failure of "SuspendResumeTest" */
+	{0x31, 0xf9},	/* enable 1.8V Suspend */
+	{0x35, 0x02},	/* turn on JPEG */
+	{0xd9, 0x10},
+	{0x25, 0x42},	/* GPIO[8]:Input */
+	{0x94, 0x11},	/* If the default setting is loaded when
+			 * system boots up, this flag is closed here */
+};
+static const u8 sensor_init_767x[][2] = {
+	{0x12, 0x80},
+	{0x11, 0x03},
+	{0x3a, 0x04},
+	{0x12, 0x00},
+	{0x17, 0x13},
+	{0x18, 0x01},
+	{0x32, 0xb6},
+	{0x19, 0x02},
+	{0x1a, 0x7a},
+	{0x03, 0x0a},
+	{0x0c, 0x00},
+	{0x3e, 0x00},
+	{0x70, 0x3a},
+	{0x71, 0x35},
+	{0x72, 0x11},
+	{0x73, 0xf0},
+	{0xa2, 0x02},
+	{0x7a, 0x2a},	/* set Gamma=1.6 below */
+	{0x7b, 0x12},
+	{0x7c, 0x1d},
+	{0x7d, 0x2d},
+	{0x7e, 0x45},
+	{0x7f, 0x50},
+	{0x80, 0x59},
+	{0x81, 0x62},
+	{0x82, 0x6b},
+	{0x83, 0x73},
+	{0x84, 0x7b},
+	{0x85, 0x8a},
+	{0x86, 0x98},
+	{0x87, 0xb2},
+	{0x88, 0xca},
+	{0x89, 0xe0},
+	{0x13, 0xe0},
+	{0x00, 0x00},
+	{0x10, 0x00},
+	{0x0d, 0x40},
+	{0x14, 0x38},	/* gain max 16x */
+	{0xa5, 0x05},
+	{0xab, 0x07},
+	{0x24, 0x95},
+	{0x25, 0x33},
+	{0x26, 0xe3},
+	{0x9f, 0x78},
+	{0xa0, 0x68},
+	{0xa1, 0x03},
+	{0xa6, 0xd8},
+	{0xa7, 0xd8},
+	{0xa8, 0xf0},
+	{0xa9, 0x90},
+	{0xaa, 0x94},
+	{0x13, 0xe5},
+	{0x0e, 0x61},
+	{0x0f, 0x4b},
+	{0x16, 0x02},
+	{0x21, 0x02},
+	{0x22, 0x91},
+	{0x29, 0x07},
+	{0x33, 0x0b},
+	{0x35, 0x0b},
+	{0x37, 0x1d},
+	{0x38, 0x71},
+	{0x39, 0x2a},
+	{0x3c, 0x78},
+	{0x4d, 0x40},
+	{0x4e, 0x20},
+	{0x69, 0x00},
+	{0x6b, 0x4a},
+	{0x74, 0x10},
+	{0x8d, 0x4f},
+	{0x8e, 0x00},
+	{0x8f, 0x00},
+	{0x90, 0x00},
+	{0x91, 0x00},
+	{0x96, 0x00},
+	{0x9a, 0x80},
+	{0xb0, 0x84},
+	{0xb1, 0x0c},
+	{0xb2, 0x0e},
+	{0xb3, 0x82},
+	{0xb8, 0x0a},
+	{0x43, 0x0a},
+	{0x44, 0xf0},
+	{0x45, 0x34},
+	{0x46, 0x58},
+	{0x47, 0x28},
+	{0x48, 0x3a},
+	{0x59, 0x88},
+	{0x5a, 0x88},
+	{0x5b, 0x44},
+	{0x5c, 0x67},
+	{0x5d, 0x49},
+	{0x5e, 0x0e},
+	{0x6c, 0x0a},
+	{0x6d, 0x55},
+	{0x6e, 0x11},
+	{0x6f, 0x9f},
+	{0x6a, 0x40},
+	{0x01, 0x40},
+	{0x02, 0x40},
+	{0x13, 0xe7},
+	{0x4f, 0x80},
+	{0x50, 0x80},
+	{0x51, 0x00},
+	{0x52, 0x22},
+	{0x53, 0x5e},
+	{0x54, 0x80},
+	{0x58, 0x9e},
+	{0x41, 0x08},
+	{0x3f, 0x00},
+	{0x75, 0x04},
+	{0x76, 0xe1},
+	{0x4c, 0x00},
+	{0x77, 0x01},
+	{0x3d, 0xc2},
+	{0x4b, 0x09},
+	{0xc9, 0x60},
+	{0x41, 0x38},	/* jfm: auto sharpness + auto de-noise  */
+	{0x56, 0x40},
+	{0x34, 0x11},
+	{0x3b, 0xc2},
+	{0xa4, 0x8a},	/* Night mode trigger point */
+	{0x96, 0x00},
+	{0x97, 0x30},
+	{0x98, 0x20},
+	{0x99, 0x20},
+	{0x9a, 0x84},
+	{0x9b, 0x29},
+	{0x9c, 0x03},
+	{0x9d, 0x4c},
+	{0x9e, 0x3f},
+	{0x78, 0x04},
+	{0x79, 0x01},
+	{0xc8, 0xf0},
+	{0x79, 0x0f},
+	{0xc8, 0x00},
+	{0x79, 0x10},
+	{0xc8, 0x7e},
+	{0x79, 0x0a},
+	{0xc8, 0x80},
+	{0x79, 0x0b},
+	{0xc8, 0x01},
+	{0x79, 0x0c},
+	{0xc8, 0x0f},
+	{0x79, 0x0d},
+	{0xc8, 0x20},
+	{0x79, 0x09},
+	{0xc8, 0x80},
+	{0x79, 0x02},
+	{0xc8, 0xc0},
+	{0x79, 0x03},
+	{0xc8, 0x20},
+	{0x79, 0x26},
+};
+static const u8 bridge_start_vga_767x[][2] = {
+/* str59 JPG */
+	{0x94, 0xaa},
+	{0xf1, 0x42},
+	{0xe5, 0x04},
+	{0xc0, 0x50},
+	{0xc1, 0x3c},
+	{0xc2, 0x0c},
+	{0x35, 0x02},	/* turn on JPEG */
+	{0xd9, 0x10},
+	{0xda, 0x00},	/* for higher clock rate(30fps) */
+	{0x34, 0x05},	/* enable Audio Suspend mode */
+	{0xc3, 0xf9},	/* enable PRE */
+	{0x8c, 0x00},	/* CIF VSize LSB[2:0] */
+	{0x8d, 0x1c},	/* output YUV */
+/*	{0x34, 0x05},	 * enable Audio Suspend mode (?) */
+	{0x50, 0x00},	/* H/V divider=0 */
+	{0x51, 0xa0},	/* input H=640/4 */
+	{0x52, 0x3c},	/* input V=480/4 */
+	{0x53, 0x00},	/* offset X=0 */
+	{0x54, 0x00},	/* offset Y=0 */
+	{0x55, 0x00},	/* H/V size[8]=0 */
+	{0x57, 0x00},	/* H-size[9]=0 */
+	{0x5c, 0x00},	/* output size[9:8]=0 */
+	{0x5a, 0xa0},	/* output H=640/4 */
+	{0x5b, 0x78},	/* output V=480/4 */
+	{0x1c, 0x0a},
+	{0x1d, 0x0a},
+	{0x94, 0x11},
+};
+static const u8 sensor_start_vga_767x[][2] = {
+	{0x11, 0x01},
+	{0x1e, 0x04},
+	{0x19, 0x02},
+	{0x1a, 0x7a},
+};
+static const u8 bridge_start_qvga_767x[][2] = {
+/* str86 JPG */
+	{0x94, 0xaa},
+	{0xf1, 0x42},
+	{0xe5, 0x04},
+	{0xc0, 0x80},
+	{0xc1, 0x60},
+	{0xc2, 0x0c},
+	{0x35, 0x02},	/* turn on JPEG */
+	{0xd9, 0x10},
+	{0xc0, 0x50},	/* CIF HSize 640 */
+	{0xc1, 0x3c},	/* CIF VSize 480 */
+	{0x8c, 0x00},	/* CIF VSize LSB[2:0] */
+	{0x8d, 0x1c},	/* output YUV */
+	{0x34, 0x05},	/* enable Audio Suspend mode */
+	{0xc2, 0x4c},	/* output YUV and Enable DCW */
+	{0xc3, 0xf9},	/* enable PRE */
+	{0x1c, 0x00},	/* indirect addressing */
+	{0x1d, 0x48},	/* output YUV422 */
+	{0x50, 0x89},	/* H/V divider=/2; plus DCW AVG */
+	{0x51, 0xa0},	/* DCW input H=640/4 */
+	{0x52, 0x78},	/* DCW input V=480/4 */
+	{0x53, 0x00},	/* offset X=0 */
+	{0x54, 0x00},	/* offset Y=0 */
+	{0x55, 0x00},	/* H/V size[8]=0 */
+	{0x57, 0x00},	/* H-size[9]=0 */
+	{0x5c, 0x00},	/* DCW output size[9:8]=0 */
+	{0x5a, 0x50},	/* DCW output H=320/4 */
+	{0x5b, 0x3c},	/* DCW output V=240/4 */
+	{0x1c, 0x0a},
+	{0x1d, 0x0a},
+	{0x94, 0x11},
+};
+static const u8 sensor_start_qvga_767x[][2] = {
+	{0x11, 0x01},
+	{0x1e, 0x04},
+	{0x19, 0x02},
+	{0x1a, 0x7a},
+};
+
+static const u8 bridge_init_772x[][2] = {
+	{ 0xc2, 0x0c },
+	{ 0x88, 0xf8 },
+	{ 0xc3, 0x69 },
+	{ 0x89, 0xff },
+	{ 0x76, 0x03 },
+	{ 0x92, 0x01 },
+	{ 0x93, 0x18 },
+	{ 0x94, 0x10 },
+	{ 0x95, 0x10 },
+	{ 0xe2, 0x00 },
+	{ 0xe7, 0x3e },
+
+	{ 0x96, 0x00 },
+
+	{ 0x97, 0x20 },
+	{ 0x97, 0x20 },
+	{ 0x97, 0x20 },
+	{ 0x97, 0x0a },
+	{ 0x97, 0x3f },
+	{ 0x97, 0x4a },
+	{ 0x97, 0x20 },
+	{ 0x97, 0x15 },
+	{ 0x97, 0x0b },
+
+	{ 0x8e, 0x40 },
+	{ 0x1f, 0x81 },
+	{ 0x34, 0x05 },
+	{ 0xe3, 0x04 },
+	{ 0x88, 0x00 },
+	{ 0x89, 0x00 },
+	{ 0x76, 0x00 },
+	{ 0xe7, 0x2e },
+	{ 0x31, 0xf9 },
+	{ 0x25, 0x42 },
+	{ 0x21, 0xf0 },
+
+	{ 0x1c, 0x00 },
+	{ 0x1d, 0x40 },
+	{ 0x1d, 0x02 }, /* payload size 0x0200 * 4 = 2048 bytes */
+	{ 0x1d, 0x00 }, /* payload size */
+
+	{ 0x1d, 0x02 }, /* frame size 0x025800 * 4 = 614400 */
+	{ 0x1d, 0x58 }, /* frame size */
+	{ 0x1d, 0x00 }, /* frame size */
+
+	{ 0x1c, 0x0a },
+	{ 0x1d, 0x08 }, /* turn on UVC header */
+	{ 0x1d, 0x0e }, /* .. */
+
+	{ 0x8d, 0x1c },
+	{ 0x8e, 0x80 },
+	{ 0xe5, 0x04 },
+
+	{ 0xc0, 0x50 },
+	{ 0xc1, 0x3c },
+	{ 0xc2, 0x0c },
+};
+static const u8 sensor_init_772x[][2] = {
+	{ 0x12, 0x80 },
+	{ 0x11, 0x01 },
+/*fixme: better have a delay?*/
+	{ 0x11, 0x01 },
+	{ 0x11, 0x01 },
+	{ 0x11, 0x01 },
+	{ 0x11, 0x01 },
+	{ 0x11, 0x01 },
+	{ 0x11, 0x01 },
+	{ 0x11, 0x01 },
+	{ 0x11, 0x01 },
+	{ 0x11, 0x01 },
+	{ 0x11, 0x01 },
+
+	{ 0x3d, 0x03 },
+	{ 0x17, 0x26 },
+	{ 0x18, 0xa0 },
+	{ 0x19, 0x07 },
+	{ 0x1a, 0xf0 },
+	{ 0x32, 0x00 },
+	{ 0x29, 0xa0 },
+	{ 0x2c, 0xf0 },
+	{ 0x65, 0x20 },
+	{ 0x11, 0x01 },
+	{ 0x42, 0x7f },
+	{ 0x63, 0xaa },		/* AWB - was e0 */
+	{ 0x64, 0xff },
+	{ 0x66, 0x00 },
+	{ 0x13, 0xf0 },		/* com8 */
+	{ 0x0d, 0x41 },
+	{ 0x0f, 0xc5 },
+	{ 0x14, 0x11 },
+
+	{ 0x22, 0x7f },
+	{ 0x23, 0x03 },
+	{ 0x24, 0x40 },
+	{ 0x25, 0x30 },
+	{ 0x26, 0xa1 },
+	{ 0x2a, 0x00 },
+	{ 0x2b, 0x00 },
+	{ 0x6b, 0xaa },
+	{ 0x13, 0xff },		/* AWB */
+
+	{ 0x90, 0x05 },
+	{ 0x91, 0x01 },
+	{ 0x92, 0x03 },
+	{ 0x93, 0x00 },
+	{ 0x94, 0x60 },
+	{ 0x95, 0x3c },
+	{ 0x96, 0x24 },
+	{ 0x97, 0x1e },
+	{ 0x98, 0x62 },
+	{ 0x99, 0x80 },
+	{ 0x9a, 0x1e },
+	{ 0x9b, 0x08 },
+	{ 0x9c, 0x20 },
+	{ 0x9e, 0x81 },
+
+	{ 0xa6, 0x07 },
+	{ 0x7e, 0x0c },
+	{ 0x7f, 0x16 },
+	{ 0x80, 0x2a },
+	{ 0x81, 0x4e },
+	{ 0x82, 0x61 },
+	{ 0x83, 0x6f },
+	{ 0x84, 0x7b },
+	{ 0x85, 0x86 },
+	{ 0x86, 0x8e },
+	{ 0x87, 0x97 },
+	{ 0x88, 0xa4 },
+	{ 0x89, 0xaf },
+	{ 0x8a, 0xc5 },
+	{ 0x8b, 0xd7 },
+	{ 0x8c, 0xe8 },
+	{ 0x8d, 0x20 },
+
+	{ 0x0c, 0x90 },
+
+	{ 0x2b, 0x00 },
+	{ 0x22, 0x7f },
+	{ 0x23, 0x03 },
+	{ 0x11, 0x01 },
+	{ 0x0c, 0xd0 },
+	{ 0x64, 0xff },
+	{ 0x0d, 0x41 },
+
+	{ 0x14, 0x41 },
+	{ 0x0e, 0xcd },
+	{ 0xac, 0xbf },
+	{ 0x8e, 0x00 },		/* De-noise threshold */
+	{ 0x0c, 0xd0 }
+};
+static const u8 bridge_start_vga_772x[][2] = {
+	{0x1c, 0x00},
+	{0x1d, 0x40},
+	{0x1d, 0x02},
+	{0x1d, 0x00},
+	{0x1d, 0x02},
+	{0x1d, 0x58},
+	{0x1d, 0x00},
+	{0xc0, 0x50},
+	{0xc1, 0x3c},
+};
+static const u8 sensor_start_vga_772x[][2] = {
+	{0x12, 0x00},
+	{0x17, 0x26},
+	{0x18, 0xa0},
+	{0x19, 0x07},
+	{0x1a, 0xf0},
+	{0x29, 0xa0},
+	{0x2c, 0xf0},
+	{0x65, 0x20},
+};
+static const u8 bridge_start_qvga_772x[][2] = {
+	{0x1c, 0x00},
+	{0x1d, 0x40},
+	{0x1d, 0x02},
+	{0x1d, 0x00},
+	{0x1d, 0x01},
+	{0x1d, 0x4b},
+	{0x1d, 0x00},
+	{0xc0, 0x28},
+	{0xc1, 0x1e},
+};
+static const u8 sensor_start_qvga_772x[][2] = {
+	{0x12, 0x40},
+	{0x17, 0x3f},
+	{0x18, 0x50},
+	{0x19, 0x03},
+	{0x1a, 0x78},
+	{0x29, 0x50},
+	{0x2c, 0x78},
+	{0x65, 0x2f},
+};
+
+static void ov534_reg_write(struct gspca_dev *gspca_dev, u16 reg, u8 val)
+{
+	struct usb_device *udev = gspca_dev->dev;
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+
+	gspca_dbg(gspca_dev, D_USBO, "SET 01 0000 %04x %02x\n", reg, val);
+	gspca_dev->usb_buf[0] = val;
+	ret = usb_control_msg(udev,
+			      usb_sndctrlpipe(udev, 0),
+			      0x01,
+			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      0x00, reg, gspca_dev->usb_buf, 1, CTRL_TIMEOUT);
+	if (ret < 0) {
+		pr_err("write failed %d\n", ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+static u8 ov534_reg_read(struct gspca_dev *gspca_dev, u16 reg)
+{
+	struct usb_device *udev = gspca_dev->dev;
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return 0;
+	ret = usb_control_msg(udev,
+			      usb_rcvctrlpipe(udev, 0),
+			      0x01,
+			      USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      0x00, reg, gspca_dev->usb_buf, 1, CTRL_TIMEOUT);
+	gspca_dbg(gspca_dev, D_USBI, "GET 01 0000 %04x %02x\n",
+		  reg, gspca_dev->usb_buf[0]);
+	if (ret < 0) {
+		pr_err("read failed %d\n", ret);
+		gspca_dev->usb_err = ret;
+	}
+	return gspca_dev->usb_buf[0];
+}
+
+/* Two bits control LED: 0x21 bit 7 and 0x23 bit 7.
+ * (direction and output)? */
+static void ov534_set_led(struct gspca_dev *gspca_dev, int status)
+{
+	u8 data;
+
+	gspca_dbg(gspca_dev, D_CONF, "led status: %d\n", status);
+
+	data = ov534_reg_read(gspca_dev, 0x21);
+	data |= 0x80;
+	ov534_reg_write(gspca_dev, 0x21, data);
+
+	data = ov534_reg_read(gspca_dev, 0x23);
+	if (status)
+		data |= 0x80;
+	else
+		data &= ~0x80;
+
+	ov534_reg_write(gspca_dev, 0x23, data);
+
+	if (!status) {
+		data = ov534_reg_read(gspca_dev, 0x21);
+		data &= ~0x80;
+		ov534_reg_write(gspca_dev, 0x21, data);
+	}
+}
+
+static int sccb_check_status(struct gspca_dev *gspca_dev)
+{
+	u8 data;
+	int i;
+
+	for (i = 0; i < 5; i++) {
+		msleep(10);
+		data = ov534_reg_read(gspca_dev, OV534_REG_STATUS);
+
+		switch (data) {
+		case 0x00:
+			return 1;
+		case 0x04:
+			return 0;
+		case 0x03:
+			break;
+		default:
+			gspca_err(gspca_dev, "sccb status 0x%02x, attempt %d/5\n",
+				  data, i + 1);
+		}
+	}
+	return 0;
+}
+
+static void sccb_reg_write(struct gspca_dev *gspca_dev, u8 reg, u8 val)
+{
+	gspca_dbg(gspca_dev, D_USBO, "sccb write: %02x %02x\n", reg, val);
+	ov534_reg_write(gspca_dev, OV534_REG_SUBADDR, reg);
+	ov534_reg_write(gspca_dev, OV534_REG_WRITE, val);
+	ov534_reg_write(gspca_dev, OV534_REG_OPERATION, OV534_OP_WRITE_3);
+
+	if (!sccb_check_status(gspca_dev)) {
+		pr_err("sccb_reg_write failed\n");
+		gspca_dev->usb_err = -EIO;
+	}
+}
+
+static u8 sccb_reg_read(struct gspca_dev *gspca_dev, u16 reg)
+{
+	ov534_reg_write(gspca_dev, OV534_REG_SUBADDR, reg);
+	ov534_reg_write(gspca_dev, OV534_REG_OPERATION, OV534_OP_WRITE_2);
+	if (!sccb_check_status(gspca_dev))
+		pr_err("sccb_reg_read failed 1\n");
+
+	ov534_reg_write(gspca_dev, OV534_REG_OPERATION, OV534_OP_READ_2);
+	if (!sccb_check_status(gspca_dev))
+		pr_err("sccb_reg_read failed 2\n");
+
+	return ov534_reg_read(gspca_dev, OV534_REG_READ);
+}
+
+/* output a bridge sequence (reg - val) */
+static void reg_w_array(struct gspca_dev *gspca_dev,
+			const u8 (*data)[2], int len)
+{
+	while (--len >= 0) {
+		ov534_reg_write(gspca_dev, (*data)[0], (*data)[1]);
+		data++;
+	}
+}
+
+/* output a sensor sequence (reg - val) */
+static void sccb_w_array(struct gspca_dev *gspca_dev,
+			const u8 (*data)[2], int len)
+{
+	while (--len >= 0) {
+		if ((*data)[0] != 0xff) {
+			sccb_reg_write(gspca_dev, (*data)[0], (*data)[1]);
+		} else {
+			sccb_reg_read(gspca_dev, (*data)[1]);
+			sccb_reg_write(gspca_dev, 0xff, 0x00);
+		}
+		data++;
+	}
+}
+
+/* ov772x specific controls */
+static void set_frame_rate(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int i;
+	struct rate_s {
+		u8 fps;
+		u8 r11;
+		u8 r0d;
+		u8 re5;
+	};
+	const struct rate_s *r;
+	static const struct rate_s rate_0[] = {	/* 640x480 */
+		{60, 0x01, 0xc1, 0x04},
+		{50, 0x01, 0x41, 0x02},
+		{40, 0x02, 0xc1, 0x04},
+		{30, 0x04, 0x81, 0x02},
+		{15, 0x03, 0x41, 0x04},
+	};
+	static const struct rate_s rate_1[] = {	/* 320x240 */
+/*		{205, 0x01, 0xc1, 0x02},  * 205 FPS: video is partly corrupt */
+		{187, 0x01, 0x81, 0x02}, /* 187 FPS or below: video is valid */
+		{150, 0x01, 0xc1, 0x04},
+		{137, 0x02, 0xc1, 0x02},
+		{125, 0x02, 0x81, 0x02},
+		{100, 0x02, 0xc1, 0x04},
+		{75, 0x03, 0xc1, 0x04},
+		{60, 0x04, 0xc1, 0x04},
+		{50, 0x02, 0x41, 0x04},
+		{37, 0x03, 0x41, 0x04},
+		{30, 0x04, 0x41, 0x04},
+	};
+
+	if (sd->sensor != SENSOR_OV772x)
+		return;
+	if (gspca_dev->cam.cam_mode[gspca_dev->curr_mode].priv == 0) {
+		r = rate_0;
+		i = ARRAY_SIZE(rate_0);
+	} else {
+		r = rate_1;
+		i = ARRAY_SIZE(rate_1);
+	}
+	while (--i > 0) {
+		if (sd->frame_rate >= r->fps)
+			break;
+		r++;
+	}
+
+	sccb_reg_write(gspca_dev, 0x11, r->r11);
+	sccb_reg_write(gspca_dev, 0x0d, r->r0d);
+	ov534_reg_write(gspca_dev, 0xe5, r->re5);
+
+	gspca_dbg(gspca_dev, D_PROBE, "frame_rate: %d\n", r->fps);
+}
+
+static void sethue(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->sensor == SENSOR_OV767x) {
+		/* TBD */
+	} else {
+		s16 huesin;
+		s16 huecos;
+
+		/* According to the datasheet the registers expect HUESIN and
+		 * HUECOS to be the result of the trigonometric functions,
+		 * scaled by 0x80.
+		 *
+		 * The 0x7fff here represents the maximum absolute value
+		 * returned byt fixp_sin and fixp_cos, so the scaling will
+		 * consider the result like in the interval [-1.0, 1.0].
+		 */
+		huesin = fixp_sin16(val) * 0x80 / 0x7fff;
+		huecos = fixp_cos16(val) * 0x80 / 0x7fff;
+
+		if (huesin < 0) {
+			sccb_reg_write(gspca_dev, 0xab,
+				sccb_reg_read(gspca_dev, 0xab) | 0x2);
+			huesin = -huesin;
+		} else {
+			sccb_reg_write(gspca_dev, 0xab,
+				sccb_reg_read(gspca_dev, 0xab) & ~0x2);
+
+		}
+		sccb_reg_write(gspca_dev, 0xa9, (u8)huecos);
+		sccb_reg_write(gspca_dev, 0xaa, (u8)huesin);
+	}
+}
+
+static void setsaturation(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->sensor == SENSOR_OV767x) {
+		int i;
+		static u8 color_tb[][6] = {
+			{0x42, 0x42, 0x00, 0x11, 0x30, 0x41},
+			{0x52, 0x52, 0x00, 0x16, 0x3c, 0x52},
+			{0x66, 0x66, 0x00, 0x1b, 0x4b, 0x66},
+			{0x80, 0x80, 0x00, 0x22, 0x5e, 0x80},
+			{0x9a, 0x9a, 0x00, 0x29, 0x71, 0x9a},
+			{0xb8, 0xb8, 0x00, 0x31, 0x87, 0xb8},
+			{0xdd, 0xdd, 0x00, 0x3b, 0xa2, 0xdd},
+		};
+
+		for (i = 0; i < ARRAY_SIZE(color_tb[0]); i++)
+			sccb_reg_write(gspca_dev, 0x4f + i, color_tb[val][i]);
+	} else {
+		sccb_reg_write(gspca_dev, 0xa7, val); /* U saturation */
+		sccb_reg_write(gspca_dev, 0xa8, val); /* V saturation */
+	}
+}
+
+static void setbrightness(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->sensor == SENSOR_OV767x) {
+		if (val < 0)
+			val = 0x80 - val;
+		sccb_reg_write(gspca_dev, 0x55, val);	/* bright */
+	} else {
+		sccb_reg_write(gspca_dev, 0x9b, val);
+	}
+}
+
+static void setcontrast(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->sensor == SENSOR_OV767x)
+		sccb_reg_write(gspca_dev, 0x56, val);	/* contras */
+	else
+		sccb_reg_write(gspca_dev, 0x9c, val);
+}
+
+static void setgain(struct gspca_dev *gspca_dev, s32 val)
+{
+	switch (val & 0x30) {
+	case 0x00:
+		val &= 0x0f;
+		break;
+	case 0x10:
+		val &= 0x0f;
+		val |= 0x30;
+		break;
+	case 0x20:
+		val &= 0x0f;
+		val |= 0x70;
+		break;
+	default:
+/*	case 0x30: */
+		val &= 0x0f;
+		val |= 0xf0;
+		break;
+	}
+	sccb_reg_write(gspca_dev, 0x00, val);
+}
+
+static s32 getgain(struct gspca_dev *gspca_dev)
+{
+	return sccb_reg_read(gspca_dev, 0x00);
+}
+
+static void setexposure(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->sensor == SENSOR_OV767x) {
+
+		/* set only aec[9:2] */
+		sccb_reg_write(gspca_dev, 0x10, val);	/* aech */
+	} else {
+
+		/* 'val' is one byte and represents half of the exposure value
+		 * we are going to set into registers, a two bytes value:
+		 *
+		 *    MSB: ((u16) val << 1) >> 8   == val >> 7
+		 *    LSB: ((u16) val << 1) & 0xff == val << 1
+		 */
+		sccb_reg_write(gspca_dev, 0x08, val >> 7);
+		sccb_reg_write(gspca_dev, 0x10, val << 1);
+	}
+}
+
+static s32 getexposure(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->sensor == SENSOR_OV767x) {
+		/* get only aec[9:2] */
+		return sccb_reg_read(gspca_dev, 0x10);	/* aech */
+	} else {
+		u8 hi = sccb_reg_read(gspca_dev, 0x08);
+		u8 lo = sccb_reg_read(gspca_dev, 0x10);
+		return (hi << 8 | lo) >> 1;
+	}
+}
+
+static void setagc(struct gspca_dev *gspca_dev, s32 val)
+{
+	if (val) {
+		sccb_reg_write(gspca_dev, 0x13,
+				sccb_reg_read(gspca_dev, 0x13) | 0x04);
+		sccb_reg_write(gspca_dev, 0x64,
+				sccb_reg_read(gspca_dev, 0x64) | 0x03);
+	} else {
+		sccb_reg_write(gspca_dev, 0x13,
+				sccb_reg_read(gspca_dev, 0x13) & ~0x04);
+		sccb_reg_write(gspca_dev, 0x64,
+				sccb_reg_read(gspca_dev, 0x64) & ~0x03);
+	}
+}
+
+static void setawb(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (val) {
+		sccb_reg_write(gspca_dev, 0x13,
+				sccb_reg_read(gspca_dev, 0x13) | 0x02);
+		if (sd->sensor == SENSOR_OV772x)
+			sccb_reg_write(gspca_dev, 0x63,
+				sccb_reg_read(gspca_dev, 0x63) | 0xc0);
+	} else {
+		sccb_reg_write(gspca_dev, 0x13,
+				sccb_reg_read(gspca_dev, 0x13) & ~0x02);
+		if (sd->sensor == SENSOR_OV772x)
+			sccb_reg_write(gspca_dev, 0x63,
+				sccb_reg_read(gspca_dev, 0x63) & ~0xc0);
+	}
+}
+
+static void setaec(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 data;
+
+	data = sd->sensor == SENSOR_OV767x ?
+			0x05 :		/* agc + aec */
+			0x01;		/* agc */
+	switch (val) {
+	case V4L2_EXPOSURE_AUTO:
+		sccb_reg_write(gspca_dev, 0x13,
+				sccb_reg_read(gspca_dev, 0x13) | data);
+		break;
+	case V4L2_EXPOSURE_MANUAL:
+		sccb_reg_write(gspca_dev, 0x13,
+				sccb_reg_read(gspca_dev, 0x13) & ~data);
+		break;
+	}
+}
+
+static void setsharpness(struct gspca_dev *gspca_dev, s32 val)
+{
+	sccb_reg_write(gspca_dev, 0x91, val);	/* Auto de-noise threshold */
+	sccb_reg_write(gspca_dev, 0x8e, val);	/* De-noise threshold */
+}
+
+static void sethvflip(struct gspca_dev *gspca_dev, s32 hflip, s32 vflip)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 val;
+
+	if (sd->sensor == SENSOR_OV767x) {
+		val = sccb_reg_read(gspca_dev, 0x1e);	/* mvfp */
+		val &= ~0x30;
+		if (hflip)
+			val |= 0x20;
+		if (vflip)
+			val |= 0x10;
+		sccb_reg_write(gspca_dev, 0x1e, val);
+	} else {
+		val = sccb_reg_read(gspca_dev, 0x0c);
+		val &= ~0xc0;
+		if (hflip == 0)
+			val |= 0x40;
+		if (vflip == 0)
+			val |= 0x80;
+		sccb_reg_write(gspca_dev, 0x0c, val);
+	}
+}
+
+static void setlightfreq(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	val = val ? 0x9e : 0x00;
+	if (sd->sensor == SENSOR_OV767x) {
+		sccb_reg_write(gspca_dev, 0x2a, 0x00);
+		if (val)
+			val = 0x9d;	/* insert dummy to 25fps for 50Hz */
+	}
+	sccb_reg_write(gspca_dev, 0x2b, val);
+}
+
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+		     const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam;
+
+	cam = &gspca_dev->cam;
+
+	cam->cam_mode = ov772x_mode;
+	cam->nmodes = ARRAY_SIZE(ov772x_mode);
+
+	sd->frame_rate = DEFAULT_FRAME_RATE;
+
+	return 0;
+}
+
+static int ov534_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct sd *sd = container_of(ctrl->handler, struct sd, ctrl_handler);
+	struct gspca_dev *gspca_dev = &sd->gspca_dev;
+
+	switch (ctrl->id) {
+	case V4L2_CID_AUTOGAIN:
+		gspca_dev->usb_err = 0;
+		if (ctrl->val && sd->gain && gspca_dev->streaming)
+			sd->gain->val = getgain(gspca_dev);
+		return gspca_dev->usb_err;
+
+	case V4L2_CID_EXPOSURE_AUTO:
+		gspca_dev->usb_err = 0;
+		if (ctrl->val == V4L2_EXPOSURE_AUTO && sd->exposure &&
+		    gspca_dev->streaming)
+			sd->exposure->val = getexposure(gspca_dev);
+		return gspca_dev->usb_err;
+	}
+	return -EINVAL;
+}
+
+static int ov534_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct sd *sd = container_of(ctrl->handler, struct sd, ctrl_handler);
+	struct gspca_dev *gspca_dev = &sd->gspca_dev;
+
+	gspca_dev->usb_err = 0;
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_HUE:
+		sethue(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_SATURATION:
+		setsaturation(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_BRIGHTNESS:
+		setbrightness(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_CONTRAST:
+		setcontrast(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_AUTOGAIN:
+	/* case V4L2_CID_GAIN: */
+		setagc(gspca_dev, ctrl->val);
+		if (!gspca_dev->usb_err && !ctrl->val && sd->gain)
+			setgain(gspca_dev, sd->gain->val);
+		break;
+	case V4L2_CID_AUTO_WHITE_BALANCE:
+		setawb(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_EXPOSURE_AUTO:
+	/* case V4L2_CID_EXPOSURE: */
+		setaec(gspca_dev, ctrl->val);
+		if (!gspca_dev->usb_err && ctrl->val == V4L2_EXPOSURE_MANUAL &&
+		    sd->exposure)
+			setexposure(gspca_dev, sd->exposure->val);
+		break;
+	case V4L2_CID_SHARPNESS:
+		setsharpness(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_HFLIP:
+		sethvflip(gspca_dev, ctrl->val, sd->vflip->val);
+		break;
+	case V4L2_CID_VFLIP:
+		sethvflip(gspca_dev, sd->hflip->val, ctrl->val);
+		break;
+	case V4L2_CID_POWER_LINE_FREQUENCY:
+		setlightfreq(gspca_dev, ctrl->val);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops ov534_ctrl_ops = {
+	.g_volatile_ctrl = ov534_g_volatile_ctrl,
+	.s_ctrl = ov534_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct v4l2_ctrl_handler *hdl = &sd->ctrl_handler;
+	/* parameters with different values between the supported sensors */
+	int saturation_min;
+	int saturation_max;
+	int saturation_def;
+	int brightness_min;
+	int brightness_max;
+	int brightness_def;
+	int contrast_max;
+	int contrast_def;
+	int exposure_min;
+	int exposure_max;
+	int exposure_def;
+	int hflip_def;
+
+	if (sd->sensor == SENSOR_OV767x) {
+		saturation_min = 0,
+		saturation_max = 6,
+		saturation_def = 3,
+		brightness_min = -127;
+		brightness_max = 127;
+		brightness_def = 0;
+		contrast_max = 0x80;
+		contrast_def = 0x40;
+		exposure_min = 0x08;
+		exposure_max = 0x60;
+		exposure_def = 0x13;
+		hflip_def = 1;
+	} else {
+		saturation_min = 0,
+		saturation_max = 255,
+		saturation_def = 64,
+		brightness_min = 0;
+		brightness_max = 255;
+		brightness_def = 0;
+		contrast_max = 255;
+		contrast_def = 32;
+		exposure_min = 0;
+		exposure_max = 255;
+		exposure_def = 120;
+		hflip_def = 0;
+	}
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+
+	v4l2_ctrl_handler_init(hdl, 13);
+
+	if (sd->sensor == SENSOR_OV772x)
+		sd->hue = v4l2_ctrl_new_std(hdl, &ov534_ctrl_ops,
+				V4L2_CID_HUE, -90, 90, 1, 0);
+
+	sd->saturation = v4l2_ctrl_new_std(hdl, &ov534_ctrl_ops,
+			V4L2_CID_SATURATION, saturation_min, saturation_max, 1,
+			saturation_def);
+	sd->brightness = v4l2_ctrl_new_std(hdl, &ov534_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, brightness_min, brightness_max, 1,
+			brightness_def);
+	sd->contrast = v4l2_ctrl_new_std(hdl, &ov534_ctrl_ops,
+			V4L2_CID_CONTRAST, 0, contrast_max, 1, contrast_def);
+
+	if (sd->sensor == SENSOR_OV772x) {
+		sd->autogain = v4l2_ctrl_new_std(hdl, &ov534_ctrl_ops,
+				V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
+		sd->gain = v4l2_ctrl_new_std(hdl, &ov534_ctrl_ops,
+				V4L2_CID_GAIN, 0, 63, 1, 20);
+	}
+
+	sd->autoexposure = v4l2_ctrl_new_std_menu(hdl, &ov534_ctrl_ops,
+			V4L2_CID_EXPOSURE_AUTO,
+			V4L2_EXPOSURE_MANUAL, 0,
+			V4L2_EXPOSURE_AUTO);
+	sd->exposure = v4l2_ctrl_new_std(hdl, &ov534_ctrl_ops,
+			V4L2_CID_EXPOSURE, exposure_min, exposure_max, 1,
+			exposure_def);
+
+	sd->autowhitebalance = v4l2_ctrl_new_std(hdl, &ov534_ctrl_ops,
+			V4L2_CID_AUTO_WHITE_BALANCE, 0, 1, 1, 1);
+
+	if (sd->sensor == SENSOR_OV772x)
+		sd->sharpness = v4l2_ctrl_new_std(hdl, &ov534_ctrl_ops,
+				V4L2_CID_SHARPNESS, 0, 63, 1, 0);
+
+	sd->hflip = v4l2_ctrl_new_std(hdl, &ov534_ctrl_ops,
+			V4L2_CID_HFLIP, 0, 1, 1, hflip_def);
+	sd->vflip = v4l2_ctrl_new_std(hdl, &ov534_ctrl_ops,
+			V4L2_CID_VFLIP, 0, 1, 1, 0);
+	sd->plfreq = v4l2_ctrl_new_std_menu(hdl, &ov534_ctrl_ops,
+			V4L2_CID_POWER_LINE_FREQUENCY,
+			V4L2_CID_POWER_LINE_FREQUENCY_50HZ, 0,
+			V4L2_CID_POWER_LINE_FREQUENCY_DISABLED);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+
+	if (sd->sensor == SENSOR_OV772x)
+		v4l2_ctrl_auto_cluster(2, &sd->autogain, 0, true);
+
+	v4l2_ctrl_auto_cluster(2, &sd->autoexposure, V4L2_EXPOSURE_MANUAL,
+			       true);
+
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u16 sensor_id;
+	static const struct reg_array bridge_init[NSENSORS] = {
+	[SENSOR_OV767x] = {bridge_init_767x, ARRAY_SIZE(bridge_init_767x)},
+	[SENSOR_OV772x] = {bridge_init_772x, ARRAY_SIZE(bridge_init_772x)},
+	};
+	static const struct reg_array sensor_init[NSENSORS] = {
+	[SENSOR_OV767x] = {sensor_init_767x, ARRAY_SIZE(sensor_init_767x)},
+	[SENSOR_OV772x] = {sensor_init_772x, ARRAY_SIZE(sensor_init_772x)},
+	};
+
+	/* reset bridge */
+	ov534_reg_write(gspca_dev, 0xe7, 0x3a);
+	ov534_reg_write(gspca_dev, 0xe0, 0x08);
+	msleep(100);
+
+	/* initialize the sensor address */
+	ov534_reg_write(gspca_dev, OV534_REG_ADDRESS, 0x42);
+
+	/* reset sensor */
+	sccb_reg_write(gspca_dev, 0x12, 0x80);
+	msleep(10);
+
+	/* probe the sensor */
+	sccb_reg_read(gspca_dev, 0x0a);
+	sensor_id = sccb_reg_read(gspca_dev, 0x0a) << 8;
+	sccb_reg_read(gspca_dev, 0x0b);
+	sensor_id |= sccb_reg_read(gspca_dev, 0x0b);
+	gspca_dbg(gspca_dev, D_PROBE, "Sensor ID: %04x\n", sensor_id);
+
+	if ((sensor_id & 0xfff0) == 0x7670) {
+		sd->sensor = SENSOR_OV767x;
+		gspca_dev->cam.cam_mode = ov767x_mode;
+		gspca_dev->cam.nmodes = ARRAY_SIZE(ov767x_mode);
+	} else {
+		sd->sensor = SENSOR_OV772x;
+		gspca_dev->cam.bulk = 1;
+		gspca_dev->cam.bulk_size = 16384;
+		gspca_dev->cam.bulk_nurbs = 2;
+		gspca_dev->cam.mode_framerates = ov772x_framerates;
+	}
+
+	/* initialize */
+	reg_w_array(gspca_dev, bridge_init[sd->sensor].val,
+			bridge_init[sd->sensor].len);
+	ov534_set_led(gspca_dev, 1);
+	sccb_w_array(gspca_dev, sensor_init[sd->sensor].val,
+			sensor_init[sd->sensor].len);
+
+	sd_stopN(gspca_dev);
+/*	set_frame_rate(gspca_dev);	*/
+
+	return gspca_dev->usb_err;
+}
+
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int mode;
+	static const struct reg_array bridge_start[NSENSORS][2] = {
+	[SENSOR_OV767x] = {{bridge_start_qvga_767x,
+					ARRAY_SIZE(bridge_start_qvga_767x)},
+			{bridge_start_vga_767x,
+					ARRAY_SIZE(bridge_start_vga_767x)}},
+	[SENSOR_OV772x] = {{bridge_start_qvga_772x,
+					ARRAY_SIZE(bridge_start_qvga_772x)},
+			{bridge_start_vga_772x,
+					ARRAY_SIZE(bridge_start_vga_772x)}},
+	};
+	static const struct reg_array sensor_start[NSENSORS][2] = {
+	[SENSOR_OV767x] = {{sensor_start_qvga_767x,
+					ARRAY_SIZE(sensor_start_qvga_767x)},
+			{sensor_start_vga_767x,
+					ARRAY_SIZE(sensor_start_vga_767x)}},
+	[SENSOR_OV772x] = {{sensor_start_qvga_772x,
+					ARRAY_SIZE(sensor_start_qvga_772x)},
+			{sensor_start_vga_772x,
+					ARRAY_SIZE(sensor_start_vga_772x)}},
+	};
+
+	/* (from ms-win trace) */
+	if (sd->sensor == SENSOR_OV767x)
+		sccb_reg_write(gspca_dev, 0x1e, 0x04);
+					/* black sun enable ? */
+
+	mode = gspca_dev->curr_mode;	/* 0: 320x240, 1: 640x480 */
+	reg_w_array(gspca_dev, bridge_start[sd->sensor][mode].val,
+				bridge_start[sd->sensor][mode].len);
+	sccb_w_array(gspca_dev, sensor_start[sd->sensor][mode].val,
+				sensor_start[sd->sensor][mode].len);
+
+	set_frame_rate(gspca_dev);
+
+	if (sd->hue)
+		sethue(gspca_dev, v4l2_ctrl_g_ctrl(sd->hue));
+	setsaturation(gspca_dev, v4l2_ctrl_g_ctrl(sd->saturation));
+	if (sd->autogain)
+		setagc(gspca_dev, v4l2_ctrl_g_ctrl(sd->autogain));
+	setawb(gspca_dev, v4l2_ctrl_g_ctrl(sd->autowhitebalance));
+	setaec(gspca_dev, v4l2_ctrl_g_ctrl(sd->autoexposure));
+	if (sd->gain)
+		setgain(gspca_dev, v4l2_ctrl_g_ctrl(sd->gain));
+	setexposure(gspca_dev, v4l2_ctrl_g_ctrl(sd->exposure));
+	setbrightness(gspca_dev, v4l2_ctrl_g_ctrl(sd->brightness));
+	setcontrast(gspca_dev, v4l2_ctrl_g_ctrl(sd->contrast));
+	if (sd->sharpness)
+		setsharpness(gspca_dev, v4l2_ctrl_g_ctrl(sd->sharpness));
+	sethvflip(gspca_dev, v4l2_ctrl_g_ctrl(sd->hflip),
+		  v4l2_ctrl_g_ctrl(sd->vflip));
+	setlightfreq(gspca_dev, v4l2_ctrl_g_ctrl(sd->plfreq));
+
+	ov534_set_led(gspca_dev, 1);
+	ov534_reg_write(gspca_dev, 0xe0, 0x00);
+	return gspca_dev->usb_err;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	ov534_reg_write(gspca_dev, 0xe0, 0x09);
+	ov534_set_led(gspca_dev, 0);
+}
+
+/* Values for bmHeaderInfo (Video and Still Image Payload Headers, 2.4.3.3) */
+#define UVC_STREAM_EOH	(1 << 7)
+#define UVC_STREAM_ERR	(1 << 6)
+#define UVC_STREAM_STI	(1 << 5)
+#define UVC_STREAM_RES	(1 << 4)
+#define UVC_STREAM_SCR	(1 << 3)
+#define UVC_STREAM_PTS	(1 << 2)
+#define UVC_STREAM_EOF	(1 << 1)
+#define UVC_STREAM_FID	(1 << 0)
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data, int len)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	__u32 this_pts;
+	u16 this_fid;
+	int remaining_len = len;
+	int payload_len;
+
+	payload_len = gspca_dev->cam.bulk ? 2048 : 2040;
+	do {
+		len = min(remaining_len, payload_len);
+
+		/* Payloads are prefixed with a UVC-style header.  We
+		   consider a frame to start when the FID toggles, or the PTS
+		   changes.  A frame ends when EOF is set, and we've received
+		   the correct number of bytes. */
+
+		/* Verify UVC header.  Header length is always 12 */
+		if (data[0] != 12 || len < 12) {
+			gspca_dbg(gspca_dev, D_PACK, "bad header\n");
+			goto discard;
+		}
+
+		/* Check errors */
+		if (data[1] & UVC_STREAM_ERR) {
+			gspca_dbg(gspca_dev, D_PACK, "payload error\n");
+			goto discard;
+		}
+
+		/* Extract PTS and FID */
+		if (!(data[1] & UVC_STREAM_PTS)) {
+			gspca_dbg(gspca_dev, D_PACK, "PTS not present\n");
+			goto discard;
+		}
+		this_pts = (data[5] << 24) | (data[4] << 16)
+						| (data[3] << 8) | data[2];
+		this_fid = (data[1] & UVC_STREAM_FID) ? 1 : 0;
+
+		/* If PTS or FID has changed, start a new frame. */
+		if (this_pts != sd->last_pts || this_fid != sd->last_fid) {
+			if (gspca_dev->last_packet_type == INTER_PACKET)
+				gspca_frame_add(gspca_dev, LAST_PACKET,
+						NULL, 0);
+			sd->last_pts = this_pts;
+			sd->last_fid = this_fid;
+			gspca_frame_add(gspca_dev, FIRST_PACKET,
+					data + 12, len - 12);
+		/* If this packet is marked as EOF, end the frame */
+		} else if (data[1] & UVC_STREAM_EOF) {
+			sd->last_pts = 0;
+			if (gspca_dev->pixfmt.pixelformat == V4L2_PIX_FMT_YUYV
+			 && gspca_dev->image_len + len - 12 !=
+				   gspca_dev->pixfmt.width *
+					gspca_dev->pixfmt.height * 2) {
+				gspca_dbg(gspca_dev, D_PACK, "wrong sized frame\n");
+				goto discard;
+			}
+			gspca_frame_add(gspca_dev, LAST_PACKET,
+					data + 12, len - 12);
+		} else {
+
+			/* Add the data from this payload */
+			gspca_frame_add(gspca_dev, INTER_PACKET,
+					data + 12, len - 12);
+		}
+
+		/* Done this payload */
+		goto scan_next;
+
+discard:
+		/* Discard data until a new frame starts. */
+		gspca_dev->last_packet_type = DISCARD_PACKET;
+
+scan_next:
+		remaining_len -= len;
+		data += len;
+	} while (remaining_len > 0);
+}
+
+/* get stream parameters (framerate) */
+static void sd_get_streamparm(struct gspca_dev *gspca_dev,
+			     struct v4l2_streamparm *parm)
+{
+	struct v4l2_captureparm *cp = &parm->parm.capture;
+	struct v4l2_fract *tpf = &cp->timeperframe;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	tpf->numerator = 1;
+	tpf->denominator = sd->frame_rate;
+}
+
+/* set stream parameters (framerate) */
+static void sd_set_streamparm(struct gspca_dev *gspca_dev,
+			     struct v4l2_streamparm *parm)
+{
+	struct v4l2_captureparm *cp = &parm->parm.capture;
+	struct v4l2_fract *tpf = &cp->timeperframe;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (tpf->numerator == 0 || tpf->denominator == 0)
+		sd->frame_rate = DEFAULT_FRAME_RATE;
+	else
+		sd->frame_rate = tpf->denominator / tpf->numerator;
+
+	if (gspca_dev->streaming)
+		set_frame_rate(gspca_dev);
+
+	/* Return the actual framerate */
+	tpf->numerator = 1;
+	tpf->denominator = sd->frame_rate;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name     = MODULE_NAME,
+	.config   = sd_config,
+	.init     = sd_init,
+	.init_controls = sd_init_controls,
+	.start    = sd_start,
+	.stopN    = sd_stopN,
+	.pkt_scan = sd_pkt_scan,
+	.get_streamparm = sd_get_streamparm,
+	.set_streamparm = sd_set_streamparm,
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x1415, 0x2000)},
+	{USB_DEVICE(0x06f8, 0x3002)},
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name       = MODULE_NAME,
+	.id_table   = device_table,
+	.probe      = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend    = gspca_suspend,
+	.resume     = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/ov534_9.c b/drivers/media/usb/gspca/ov534_9.c
new file mode 100644
index 0000000..3d1364d
--- /dev/null
+++ b/drivers/media/usb/gspca/ov534_9.c
@@ -0,0 +1,1829 @@
+/*
+ * ov534-ov9xxx gspca driver
+ *
+ * Copyright (C) 2009-2011 Jean-Francois Moine http://moinejf.free.fr
+ * Copyright (C) 2008 Antonio Ospite <ospite@studenti.unina.it>
+ * Copyright (C) 2008 Jim Paris <jim@jtan.com>
+ *
+ * Based on a prototype written by Mark Ferrell <majortrips@gmail.com>
+ * USB protocol reverse engineered by Jim Paris <jim@jtan.com>
+ * https://jim.sh/svn/jim/devl/playstation/ps3/eye/test/
+ *
+ * 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
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "ov534_9"
+
+#include "gspca.h"
+
+#define OV534_REG_ADDRESS	0xf1	/* sensor address */
+#define OV534_REG_SUBADDR	0xf2
+#define OV534_REG_WRITE		0xf3
+#define OV534_REG_READ		0xf4
+#define OV534_REG_OPERATION	0xf5
+#define OV534_REG_STATUS	0xf6
+
+#define OV534_OP_WRITE_3	0x37
+#define OV534_OP_WRITE_2	0x33
+#define OV534_OP_READ_2		0xf9
+
+#define CTRL_TIMEOUT 500
+
+MODULE_AUTHOR("Jean-Francois Moine <moinejf@free.fr>");
+MODULE_DESCRIPTION("GSPCA/OV534_9 USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+	__u32 last_pts;
+	u8 last_fid;
+
+	u8 sensor;
+};
+enum sensors {
+	SENSOR_OV965x,		/* ov9657 */
+	SENSOR_OV971x,		/* ov9712 */
+	SENSOR_OV562x,		/* ov5621 */
+	SENSOR_OV361x,		/* ov3610 */
+	NSENSORS
+};
+
+static const struct v4l2_pix_format ov965x_mode[] = {
+#define QVGA_MODE 0
+	{320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG},
+#define VGA_MODE 1
+	{640, 480, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG},
+#define SVGA_MODE 2
+	{800, 600, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 800,
+		.sizeimage = 800 * 600 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG},
+#define XGA_MODE 3
+	{1024, 768, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 1024,
+		.sizeimage = 1024 * 768 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG},
+#define SXGA_MODE 4
+	{1280, 1024, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 1280,
+		.sizeimage = 1280 * 1024 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG},
+};
+
+static const struct v4l2_pix_format ov971x_mode[] = {
+	{640, 480, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480,
+		.colorspace = V4L2_COLORSPACE_SRGB
+	}
+};
+
+static const struct v4l2_pix_format ov562x_mode[] = {
+	{2592, 1680, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 2592,
+		.sizeimage = 2592 * 1680,
+		.colorspace = V4L2_COLORSPACE_SRGB
+	}
+};
+
+enum ov361x {
+	ov361x_2048 = 0,
+	ov361x_1600,
+	ov361x_1024,
+	ov361x_640,
+	ov361x_320,
+	ov361x_160,
+	ov361x_last
+};
+
+static const struct v4l2_pix_format ov361x_mode[] = {
+	{0x800, 0x600, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 0x800,
+		.sizeimage = 0x800 * 0x600,
+		.colorspace = V4L2_COLORSPACE_SRGB},
+	{1600, 1200, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 1600,
+		.sizeimage = 1600 * 1200,
+		.colorspace = V4L2_COLORSPACE_SRGB},
+	{1024, 768, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 768,
+		.sizeimage = 1024 * 768,
+		.colorspace = V4L2_COLORSPACE_SRGB},
+	{640, 480, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480,
+		.colorspace = V4L2_COLORSPACE_SRGB},
+	{320, 240, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240,
+		.colorspace = V4L2_COLORSPACE_SRGB},
+	{160, 120, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 160 * 120,
+		.colorspace = V4L2_COLORSPACE_SRGB}
+};
+
+static const u8 ov361x_start_2048[][2] = {
+	{0x12, 0x80},
+	{0x13, 0xcf},
+	{0x14, 0x40},
+	{0x15, 0x00},
+	{0x01, 0x80},
+	{0x02, 0x80},
+	{0x04, 0x70},
+	{0x0d, 0x40},
+	{0x0f, 0x47},
+	{0x11, 0x81},
+	{0x32, 0x36},
+	{0x33, 0x0c},
+	{0x34, 0x00},
+	{0x35, 0x90},
+	{0x12, 0x00},
+	{0x17, 0x10},
+	{0x18, 0x90},
+	{0x19, 0x00},
+	{0x1a, 0xc0},
+};
+static const u8 ov361x_bridge_start_2048[][2] = {
+	{0xf1, 0x60},
+	{0x88, 0x00},
+	{0x89, 0x08},
+	{0x8a, 0x00},
+	{0x8b, 0x06},
+	{0x8c, 0x01},
+	{0x8d, 0x10},
+	{0x1c, 0x00},
+	{0x1d, 0x48},
+	{0x1d, 0x00},
+	{0x1d, 0xff},
+	{0x1c, 0x0a},
+	{0x1d, 0x2e},
+	{0x1d, 0x1e},
+};
+
+static const u8 ov361x_start_1600[][2] = {
+	{0x12, 0x80},
+	{0x13, 0xcf},
+	{0x14, 0x40},
+	{0x15, 0x00},
+	{0x01, 0x80},
+	{0x02, 0x80},
+	{0x04, 0x70},
+	{0x0d, 0x40},
+	{0x0f, 0x47},
+	{0x11, 0x81},
+	{0x32, 0x36},
+	{0x33, 0x0C},
+	{0x34, 0x00},
+	{0x35, 0x90},
+	{0x12, 0x00},
+	{0x17, 0x10},
+	{0x18, 0x90},
+	{0x19, 0x00},
+	{0x1a, 0xc0},
+};
+static const u8 ov361x_bridge_start_1600[][2] = {
+	{0xf1, 0x60},  /* Hsize[7:0] */
+	{0x88, 0x00},  /* Hsize[15:8] Write Only, can't read */
+	{0x89, 0x08},  /* Vsize[7:0] */
+	{0x8a, 0x00},  /* Vsize[15:8] Write Only, can't read */
+	{0x8b, 0x06},  /* for Iso */
+	{0x8c, 0x01},  /* RAW input */
+	{0x8d, 0x10},
+	{0x1c, 0x00},  /* RAW output, Iso transfer */
+	{0x1d, 0x48},
+	{0x1d, 0x00},
+	{0x1d, 0xff},
+	{0x1c, 0x0a},  /* turn off JPEG, Iso mode */
+	{0x1d, 0x2e},  /* for Iso */
+	{0x1d, 0x1e},
+};
+
+static const u8 ov361x_start_1024[][2] = {
+	{0x12, 0x80},
+	{0x13, 0xcf},
+	{0x14, 0x40},
+	{0x15, 0x00},
+	{0x01, 0x80},
+	{0x02, 0x80},
+	{0x04, 0x70},
+	{0x0d, 0x40},
+	{0x0f, 0x47},
+	{0x11, 0x81},
+	{0x32, 0x36},
+	{0x33, 0x0C},
+	{0x34, 0x00},
+	{0x35, 0x90},
+	{0x12, 0x40},
+	{0x17, 0x1f},
+	{0x18, 0x5f},
+	{0x19, 0x00},
+	{0x1a, 0x68},
+};
+static const u8 ov361x_bridge_start_1024[][2] = {
+	{0xf1, 0x60},  /* Hsize[7:0] */
+	{0x88, 0x00},  /* Hsize[15:8] Write Only, can't read */
+	{0x89, 0x04},  /* Vsize[7:0] */
+	{0x8a, 0x00},  /* Vsize[15:8] Write Only, can't read */
+	{0x8b, 0x03},  /* for Iso */
+	{0x8c, 0x01},  /* RAW input  */
+	{0x8d, 0x10},
+	{0x1c, 0x00},  /* RAW output, Iso transfer */
+	{0x1d, 0x48},
+	{0x1d, 0x00},
+	{0x1d, 0xff},
+	{0x1c, 0x0a},  /* turn off JPEG, Iso mode */
+	{0x1d, 0x2e},  /* for Iso */
+	{0x1d, 0x1e},
+};
+
+static const u8 ov361x_start_640[][2] = {
+	{0x12, 0x80},
+	{0x13, 0xcf},
+	{0x14, 0x40},
+	{0x15, 0x00},
+	{0x01, 0x80},
+	{0x02, 0x80},
+	{0x04, 0x70},
+	{0x0d, 0x40},
+	{0x0f, 0x47},
+	{0x11, 0x81},
+	{0x32, 0x36},
+	{0x33, 0x0C},
+	{0x34, 0x00},
+	{0x35, 0x90},
+	{0x12, 0x40},
+	{0x17, 0x1f},
+	{0x18, 0x5f},
+	{0x19, 0x00},
+	{0x1a, 0x68},
+};
+
+static const u8 ov361x_bridge_start_640[][2] = {
+	{0xf1, 0x60},  /* Hsize[7:0]*/
+	{0x88, 0x00},  /* Hsize[15:8] Write Only, can't read */
+	{0x89, 0x04},  /* Vsize[7:0] */
+	{0x8a, 0x00},  /* Vsize[15:8] Write Only, can't read */
+	{0x8b, 0x03},  /* for Iso */
+	{0x8c, 0x01},  /* RAW input */
+	{0x8d, 0x10},
+	{0x1c, 0x00},  /* RAW output, Iso transfer */
+	{0x1d, 0x48},
+	{0x1d, 0x00},
+	{0x1d, 0xff},
+	{0x1c, 0x0a},  /* turn off JPEG, Iso mode */
+	{0x1d, 0x2e},  /* for Iso */
+	{0x1d, 0x1e},
+};
+
+static const u8 ov361x_start_320[][2] = {
+	{0x12, 0x80},
+	{0x13, 0xcf},
+	{0x14, 0x40},
+	{0x15, 0x00},
+	{0x01, 0x80},
+	{0x02, 0x80},
+	{0x04, 0x70},
+	{0x0d, 0x40},
+	{0x0f, 0x47},
+	{0x11, 0x81},
+	{0x32, 0x36},
+	{0x33, 0x0C},
+	{0x34, 0x00},
+	{0x35, 0x90},
+	{0x12, 0x40},
+	{0x17, 0x1f},
+	{0x18, 0x5f},
+	{0x19, 0x00},
+	{0x1a, 0x68},
+};
+
+static const u8 ov361x_bridge_start_320[][2] = {
+	{0xf1, 0x60},  /* Hsize[7:0] */
+	{0x88, 0x00},  /* Hsize[15:8] Write Only, can't read */
+	{0x89, 0x04},  /* Vsize[7:0] */
+	{0x8a, 0x00},  /* Vsize[15:8] Write Only, can't read */
+	{0x8b, 0x03},  /* for Iso */
+	{0x8c, 0x01},  /* RAW input */
+	{0x8d, 0x10},
+	{0x1c, 0x00},  /* RAW output, Iso transfer; */
+	{0x1d, 0x48},
+	{0x1d, 0x00},
+	{0x1d, 0xff},
+	{0x1c, 0x0a},  /* turn off JPEG, Iso mode */
+	{0x1d, 0x2e},  /* for Iso */
+	{0x1d, 0x1e},
+};
+
+static const u8 ov361x_start_160[][2] = {
+	{0x12, 0x80},
+	{0x13, 0xcf},
+	{0x14, 0x40},
+	{0x15, 0x00},
+	{0x01, 0x80},
+	{0x02, 0x80},
+	{0x04, 0x70},
+	{0x0d, 0x40},
+	{0x0f, 0x47},
+	{0x11, 0x81},
+	{0x32, 0x36},
+	{0x33, 0x0C},
+	{0x34, 0x00},
+	{0x35, 0x90},
+	{0x12, 0x40},
+	{0x17, 0x1f},
+	{0x18, 0x5f},
+	{0x19, 0x00},
+	{0x1a, 0x68},
+};
+
+static const u8 ov361x_bridge_start_160[][2] = {
+	{0xf1, 0x60},  /* Hsize[7:0] */
+	{0x88, 0x00},  /* Hsize[15:8] Write Only, can't read */
+	{0x89, 0x04},  /* Vsize[7:0] */
+	{0x8a, 0x00},  /* Vsize[15:8] Write Only, can't read */
+	{0x8b, 0x03},  /* for Iso */
+	{0x8c, 0x01},  /* RAW input */
+	{0x8d, 0x10},
+	{0x1c, 0x00},  /* RAW output, Iso transfer */
+	{0x1d, 0x48},
+	{0x1d, 0x00},
+	{0x1d, 0xff},
+	{0x1c, 0x0a},  /* turn off JPEG, Iso mode */
+	{0x1d, 0x2e},  /* for Iso */
+	{0x1d, 0x1e},
+};
+
+static const u8 bridge_init[][2] = {
+	{0x88, 0xf8},
+	{0x89, 0xff},
+	{0x76, 0x03},
+	{0x92, 0x03},
+	{0x95, 0x10},
+	{0xe2, 0x00},
+	{0xe7, 0x3e},
+	{0x8d, 0x1c},
+	{0x8e, 0x00},
+	{0x8f, 0x00},
+	{0x1f, 0x00},
+	{0xc3, 0xf9},
+	{0x89, 0xff},
+	{0x88, 0xf8},
+	{0x76, 0x03},
+	{0x92, 0x01},
+	{0x93, 0x18},
+	{0x1c, 0x0a},
+	{0x1d, 0x48},
+	{0xc0, 0x50},
+	{0xc1, 0x3c},
+	{0x34, 0x05},
+	{0xc2, 0x0c},
+	{0xc3, 0xf9},
+	{0x34, 0x05},
+	{0xe7, 0x2e},
+	{0x31, 0xf9},
+	{0x35, 0x02},
+	{0xd9, 0x10},
+	{0x25, 0x42},
+	{0x94, 0x11},
+};
+
+static const u8 ov965x_init[][2] = {
+	{0x12, 0x80},	/* com7 - SSCB reset */
+	{0x00, 0x00},	/* gain */
+	{0x01, 0x80},	/* blue */
+	{0x02, 0x80},	/* red */
+	{0x03, 0x1b},	/* vref */
+	{0x04, 0x03},	/* com1 - exposure low bits */
+	{0x0b, 0x57},	/* ver */
+	{0x0e, 0x61},	/* com5 */
+	{0x0f, 0x42},	/* com6 */
+	{0x11, 0x00},	/* clkrc */
+	{0x12, 0x02},	/* com7 - 15fps VGA YUYV */
+	{0x13, 0xe7},	/* com8 - everything (AGC, AWB and AEC) */
+	{0x14, 0x28},	/* com9 */
+	{0x16, 0x24},	/* reg16 */
+	{0x17, 0x1d},	/* hstart*/
+	{0x18, 0xbd},	/* hstop */
+	{0x19, 0x01},	/* vstrt */
+	{0x1a, 0x81},	/* vstop*/
+	{0x1e, 0x04},	/* mvfp */
+	{0x24, 0x3c},	/* aew */
+	{0x25, 0x36},	/* aeb */
+	{0x26, 0x71},	/* vpt */
+	{0x27, 0x08},	/* bbias */
+	{0x28, 0x08},	/* gbbias */
+	{0x29, 0x15},	/* gr com */
+	{0x2a, 0x00},	/* exhch */
+	{0x2b, 0x00},	/* exhcl */
+	{0x2c, 0x08},	/* rbias */
+	{0x32, 0xff},	/* href */
+	{0x33, 0x00},	/* chlf */
+	{0x34, 0x3f},	/* aref1 */
+	{0x35, 0x00},	/* aref2 */
+	{0x36, 0xf8},	/* aref3 */
+	{0x38, 0x72},	/* adc2 */
+	{0x39, 0x57},	/* aref4 */
+	{0x3a, 0x80},	/* tslb - yuyv */
+	{0x3b, 0xc4},	/* com11 - night mode 1/4 frame rate */
+	{0x3d, 0x99},	/* com13 */
+	{0x3f, 0xc1},	/* edge */
+	{0x40, 0xc0},	/* com15 */
+	{0x41, 0x40},	/* com16 */
+	{0x42, 0xc0},	/* com17 */
+	{0x43, 0x0a},	/* rsvd */
+	{0x44, 0xf0},
+	{0x45, 0x46},
+	{0x46, 0x62},
+	{0x47, 0x2a},
+	{0x48, 0x3c},
+	{0x4a, 0xfc},
+	{0x4b, 0xfc},
+	{0x4c, 0x7f},
+	{0x4d, 0x7f},
+	{0x4e, 0x7f},
+	{0x4f, 0x98},	/* matrix */
+	{0x50, 0x98},
+	{0x51, 0x00},
+	{0x52, 0x28},
+	{0x53, 0x70},
+	{0x54, 0x98},
+	{0x58, 0x1a},	/* matrix coef sign */
+	{0x59, 0x85},	/* AWB control */
+	{0x5a, 0xa9},
+	{0x5b, 0x64},
+	{0x5c, 0x84},
+	{0x5d, 0x53},
+	{0x5e, 0x0e},
+	{0x5f, 0xf0},	/* AWB blue limit */
+	{0x60, 0xf0},	/* AWB red limit */
+	{0x61, 0xf0},	/* AWB green limit */
+	{0x62, 0x00},	/* lcc1 */
+	{0x63, 0x00},	/* lcc2 */
+	{0x64, 0x02},	/* lcc3 */
+	{0x65, 0x16},	/* lcc4 */
+	{0x66, 0x01},	/* lcc5 */
+	{0x69, 0x02},	/* hv */
+	{0x6b, 0x5a},	/* dbvl */
+	{0x6c, 0x04},
+	{0x6d, 0x55},
+	{0x6e, 0x00},
+	{0x6f, 0x9d},
+	{0x70, 0x21},	/* dnsth */
+	{0x71, 0x78},
+	{0x72, 0x00},	/* poidx */
+	{0x73, 0x01},	/* pckdv */
+	{0x74, 0x3a},	/* xindx */
+	{0x75, 0x35},	/* yindx */
+	{0x76, 0x01},
+	{0x77, 0x02},
+	{0x7a, 0x12},	/* gamma curve */
+	{0x7b, 0x08},
+	{0x7c, 0x16},
+	{0x7d, 0x30},
+	{0x7e, 0x5e},
+	{0x7f, 0x72},
+	{0x80, 0x82},
+	{0x81, 0x8e},
+	{0x82, 0x9a},
+	{0x83, 0xa4},
+	{0x84, 0xac},
+	{0x85, 0xb8},
+	{0x86, 0xc3},
+	{0x87, 0xd6},
+	{0x88, 0xe6},
+	{0x89, 0xf2},
+	{0x8a, 0x03},
+	{0x8c, 0x89},	/* com19 */
+	{0x14, 0x28},	/* com9 */
+	{0x90, 0x7d},
+	{0x91, 0x7b},
+	{0x9d, 0x03},	/* lcc6 */
+	{0x9e, 0x04},	/* lcc7 */
+	{0x9f, 0x7a},
+	{0xa0, 0x79},
+	{0xa1, 0x40},	/* aechm */
+	{0xa4, 0x50},	/* com21 */
+	{0xa5, 0x68},	/* com26 */
+	{0xa6, 0x4a},	/* AWB green */
+	{0xa8, 0xc1},	/* refa8 */
+	{0xa9, 0xef},	/* refa9 */
+	{0xaa, 0x92},
+	{0xab, 0x04},
+	{0xac, 0x80},	/* black level control */
+	{0xad, 0x80},
+	{0xae, 0x80},
+	{0xaf, 0x80},
+	{0xb2, 0xf2},
+	{0xb3, 0x20},
+	{0xb4, 0x20},	/* ctrlb4 */
+	{0xb5, 0x00},
+	{0xb6, 0xaf},
+	{0xbb, 0xae},
+	{0xbc, 0x7f},	/* ADC channel offsets */
+	{0xdb, 0x7f},
+	{0xbe, 0x7f},
+	{0xbf, 0x7f},
+	{0xc0, 0xe2},
+	{0xc1, 0xc0},
+	{0xc2, 0x01},
+	{0xc3, 0x4e},
+	{0xc6, 0x85},
+	{0xc7, 0x80},	/* com24 */
+	{0xc9, 0xe0},
+	{0xca, 0xe8},
+	{0xcb, 0xf0},
+	{0xcc, 0xd8},
+	{0xcd, 0xf1},
+	{0x4f, 0x98},	/* matrix */
+	{0x50, 0x98},
+	{0x51, 0x00},
+	{0x52, 0x28},
+	{0x53, 0x70},
+	{0x54, 0x98},
+	{0x58, 0x1a},
+	{0xff, 0x41},	/* read 41, write ff 00 */
+	{0x41, 0x40},	/* com16 */
+
+	{0xc5, 0x03},	/* 60 Hz banding filter */
+	{0x6a, 0x02},	/* 50 Hz banding filter */
+
+	{0x12, 0x62},	/* com7 - 30fps VGA YUV */
+	{0x36, 0xfa},	/* aref3 */
+	{0x69, 0x0a},	/* hv */
+	{0x8c, 0x89},	/* com22 */
+	{0x14, 0x28},	/* com9 */
+	{0x3e, 0x0c},
+	{0x41, 0x40},	/* com16 */
+	{0x72, 0x00},
+	{0x73, 0x00},
+	{0x74, 0x3a},
+	{0x75, 0x35},
+	{0x76, 0x01},
+	{0xc7, 0x80},
+	{0x03, 0x12},	/* vref */
+	{0x17, 0x16},	/* hstart */
+	{0x18, 0x02},	/* hstop */
+	{0x19, 0x01},	/* vstrt */
+	{0x1a, 0x3d},	/* vstop */
+	{0x32, 0xff},	/* href */
+	{0xc0, 0xaa},
+};
+
+static const u8 bridge_init_2[][2] = {
+	{0x94, 0xaa},
+	{0xf1, 0x60},
+	{0xe5, 0x04},
+	{0xc0, 0x50},
+	{0xc1, 0x3c},
+	{0x8c, 0x00},
+	{0x8d, 0x1c},
+	{0x34, 0x05},
+
+	{0xc2, 0x0c},
+	{0xc3, 0xf9},
+	{0xda, 0x01},
+	{0x50, 0x00},
+	{0x51, 0xa0},
+	{0x52, 0x3c},
+	{0x53, 0x00},
+	{0x54, 0x00},
+	{0x55, 0x00},
+	{0x57, 0x00},
+	{0x5c, 0x00},
+	{0x5a, 0xa0},
+	{0x5b, 0x78},
+	{0x35, 0x02},
+	{0xd9, 0x10},
+	{0x94, 0x11},
+};
+
+static const u8 ov965x_init_2[][2] = {
+	{0x3b, 0xc4},
+	{0x1e, 0x04},	/* mvfp */
+	{0x13, 0xe0},	/* com8 */
+	{0x00, 0x00},	/* gain */
+	{0x13, 0xe7},	/* com8 - everything (AGC, AWB and AEC) */
+	{0x11, 0x03},	/* clkrc */
+	{0x6b, 0x5a},	/* dblv */
+	{0x6a, 0x05},
+	{0xc5, 0x07},
+	{0xa2, 0x4b},
+	{0xa3, 0x3e},
+	{0x2d, 0x00},
+	{0xff, 0x42},	/* read 42, write ff 00 */
+	{0x42, 0xc0},	/* com17 */
+	{0x2d, 0x00},
+	{0xff, 0x42},	/* read 42, write ff 00 */
+	{0x42, 0xc1},	/* com17 */
+/* sharpness */
+	{0x3f, 0x01},
+	{0xff, 0x42},	/* read 42, write ff 00 */
+	{0x42, 0xc1},	/* com17 */
+/* saturation */
+	{0x4f, 0x98},	/* matrix */
+	{0x50, 0x98},
+	{0x51, 0x00},
+	{0x52, 0x28},
+	{0x53, 0x70},
+	{0x54, 0x98},
+	{0x58, 0x1a},
+	{0xff, 0x41},	/* read 41, write ff 00 */
+	{0x41, 0x40},	/* com16 */
+/* contrast */
+	{0x56, 0x40},
+/* brightness */
+	{0x55, 0x8f},
+/* expo */
+	{0x10, 0x25},	/* aech - exposure high bits */
+	{0xff, 0x13},	/* read 13, write ff 00 */
+	{0x13, 0xe7},	/* com8 - everything (AGC, AWB and AEC) */
+};
+
+static const u8 ov971x_init[][2] = {
+	{0x12, 0x80},
+	{0x09, 0x10},
+	{0x1e, 0x07},
+	{0x5f, 0x18},
+	{0x69, 0x04},
+	{0x65, 0x2a},
+	{0x68, 0x0a},
+	{0x39, 0x28},
+	{0x4d, 0x90},
+	{0xc1, 0x80},
+	{0x0c, 0x30},
+	{0x6d, 0x02},
+	{0x96, 0xf1},
+	{0xbc, 0x68},
+	{0x12, 0x00},
+	{0x3b, 0x00},
+	{0x97, 0x80},
+	{0x17, 0x25},
+	{0x18, 0xa2},
+	{0x19, 0x01},
+	{0x1a, 0xca},
+	{0x03, 0x0a},
+	{0x32, 0x07},
+	{0x98, 0x40},	/*{0x98, 0x00},*/
+	{0x99, 0xA0},	/*{0x99, 0x00},*/
+	{0x9a, 0x01},	/*{0x9a, 0x00},*/
+	{0x57, 0x00},
+	{0x58, 0x78},	/*{0x58, 0xc8},*/
+	{0x59, 0x50},	/*{0x59, 0xa0},*/
+	{0x4c, 0x13},
+	{0x4b, 0x36},
+	{0x3d, 0x3c},
+	{0x3e, 0x03},
+	{0xbd, 0x50},	/*{0xbd, 0xa0},*/
+	{0xbe, 0x78},	/*{0xbe, 0xc8},*/
+	{0x4e, 0x55},
+	{0x4f, 0x55},
+	{0x50, 0x55},
+	{0x51, 0x55},
+	{0x24, 0x55},
+	{0x25, 0x40},
+	{0x26, 0xa1},
+	{0x5c, 0x59},
+	{0x5d, 0x00},
+	{0x11, 0x00},
+	{0x2a, 0x98},
+	{0x2b, 0x06},
+	{0x2d, 0x00},
+	{0x2e, 0x00},
+	{0x13, 0xa5},
+	{0x14, 0x40},
+	{0x4a, 0x00},
+	{0x49, 0xce},
+	{0x22, 0x03},
+	{0x09, 0x00}
+};
+
+static const u8 ov965x_start_1_vga[][2] = {	/* same for qvga */
+	{0x12, 0x62},	/* com7 - 30fps VGA YUV */
+	{0x36, 0xfa},	/* aref3 */
+	{0x69, 0x0a},	/* hv */
+	{0x8c, 0x89},	/* com22 */
+	{0x14, 0x28},	/* com9 */
+	{0x3e, 0x0c},	/* com14 */
+	{0x41, 0x40},	/* com16 */
+	{0x72, 0x00},
+	{0x73, 0x00},
+	{0x74, 0x3a},
+	{0x75, 0x35},
+	{0x76, 0x01},
+	{0xc7, 0x80},	/* com24 */
+	{0x03, 0x12},	/* vref */
+	{0x17, 0x16},	/* hstart */
+	{0x18, 0x02},	/* hstop */
+	{0x19, 0x01},	/* vstrt */
+	{0x1a, 0x3d},	/* vstop */
+	{0x32, 0xff},	/* href */
+	{0xc0, 0xaa},
+};
+
+static const u8 ov965x_start_1_svga[][2] = {
+	{0x12, 0x02},	/* com7 - YUYV - VGA 15 full resolution */
+	{0x36, 0xf8},	/* aref3 */
+	{0x69, 0x02},	/* hv */
+	{0x8c, 0x0d},	/* com22 */
+	{0x3e, 0x0c},	/* com14 */
+	{0x41, 0x40},	/* com16 */
+	{0x72, 0x00},
+	{0x73, 0x01},
+	{0x74, 0x3a},
+	{0x75, 0x35},
+	{0x76, 0x01},
+	{0xc7, 0x80},	/* com24 */
+	{0x03, 0x1b},	/* vref */
+	{0x17, 0x1d},	/* hstart */
+	{0x18, 0xbd},	/* hstop */
+	{0x19, 0x01},	/* vstrt */
+	{0x1a, 0x81},	/* vstop */
+	{0x32, 0xff},	/* href */
+	{0xc0, 0xe2},
+};
+
+static const u8 ov965x_start_1_xga[][2] = {
+	{0x12, 0x02},	/* com7 */
+	{0x36, 0xf8},	/* aref3 */
+	{0x69, 0x02},	/* hv */
+	{0x8c, 0x89},	/* com22 */
+	{0x14, 0x28},	/* com9 */
+	{0x3e, 0x0c},	/* com14 */
+	{0x41, 0x40},	/* com16 */
+	{0x72, 0x00},
+	{0x73, 0x01},
+	{0x74, 0x3a},
+	{0x75, 0x35},
+	{0x76, 0x01},
+	{0xc7, 0x80},	/* com24 */
+	{0x03, 0x1b},	/* vref */
+	{0x17, 0x1d},	/* hstart */
+	{0x18, 0xbd},	/* hstop */
+	{0x19, 0x01},	/* vstrt */
+	{0x1a, 0x81},	/* vstop */
+	{0x32, 0xff},	/* href */
+	{0xc0, 0xe2},
+};
+
+static const u8 ov965x_start_1_sxga[][2] = {
+	{0x12, 0x02},	/* com7 */
+	{0x36, 0xf8},	/* aref3 */
+	{0x69, 0x02},	/* hv */
+	{0x8c, 0x89},	/* com22 */
+	{0x14, 0x28},	/* com9 */
+	{0x3e, 0x0c},	/* com14 */
+	{0x41, 0x40},	/* com16 */
+	{0x72, 0x00},
+	{0x73, 0x01},
+	{0x74, 0x3a},
+	{0x75, 0x35},
+	{0x76, 0x01},
+	{0xc7, 0x80},	/* com24 */
+	{0x03, 0x1b},	/* vref */
+	{0x17, 0x1d},	/* hstart */
+	{0x18, 0x02},	/* hstop */
+	{0x19, 0x01},	/* vstrt */
+	{0x1a, 0x81},	/* vstop */
+	{0x32, 0xff},	/* href */
+	{0xc0, 0xe2},
+};
+
+static const u8 bridge_start_qvga[][2] = {
+	{0x94, 0xaa},
+	{0xf1, 0x60},
+	{0xe5, 0x04},
+	{0xc0, 0x50},
+	{0xc1, 0x3c},
+	{0x8c, 0x00},
+	{0x8d, 0x1c},
+	{0x34, 0x05},
+
+	{0xc2, 0x4c},
+	{0xc3, 0xf9},
+	{0xda, 0x00},
+	{0x50, 0x00},
+	{0x51, 0xa0},
+	{0x52, 0x78},
+	{0x53, 0x00},
+	{0x54, 0x00},
+	{0x55, 0x00},
+	{0x57, 0x00},
+	{0x5c, 0x00},
+	{0x5a, 0x50},
+	{0x5b, 0x3c},
+	{0x35, 0x02},
+	{0xd9, 0x10},
+	{0x94, 0x11},
+};
+
+static const u8 bridge_start_vga[][2] = {
+	{0x94, 0xaa},
+	{0xf1, 0x60},
+	{0xe5, 0x04},
+	{0xc0, 0x50},
+	{0xc1, 0x3c},
+	{0x8c, 0x00},
+	{0x8d, 0x1c},
+	{0x34, 0x05},
+	{0xc2, 0x0c},
+	{0xc3, 0xf9},
+	{0xda, 0x01},
+	{0x50, 0x00},
+	{0x51, 0xa0},
+	{0x52, 0x3c},
+	{0x53, 0x00},
+	{0x54, 0x00},
+	{0x55, 0x00},
+	{0x57, 0x00},
+	{0x5c, 0x00},
+	{0x5a, 0xa0},
+	{0x5b, 0x78},
+	{0x35, 0x02},
+	{0xd9, 0x10},
+	{0x94, 0x11},
+};
+
+static const u8 bridge_start_svga[][2] = {
+	{0x94, 0xaa},
+	{0xf1, 0x60},
+	{0xe5, 0x04},
+	{0xc0, 0xa0},
+	{0xc1, 0x80},
+	{0x8c, 0x00},
+	{0x8d, 0x1c},
+	{0x34, 0x05},
+	{0xc2, 0x4c},
+	{0xc3, 0xf9},
+	{0x50, 0x00},
+	{0x51, 0x40},
+	{0x52, 0x00},
+	{0x53, 0x00},
+	{0x54, 0x00},
+	{0x55, 0x88},
+	{0x57, 0x00},
+	{0x5c, 0x00},
+	{0x5a, 0xc8},
+	{0x5b, 0x96},
+	{0x35, 0x02},
+	{0xd9, 0x10},
+	{0xda, 0x00},
+	{0x94, 0x11},
+};
+
+static const u8 bridge_start_xga[][2] = {
+	{0x94, 0xaa},
+	{0xf1, 0x60},
+	{0xe5, 0x04},
+	{0xc0, 0xa0},
+	{0xc1, 0x80},
+	{0x8c, 0x00},
+	{0x8d, 0x1c},
+	{0x34, 0x05},
+	{0xc2, 0x4c},
+	{0xc3, 0xf9},
+	{0x50, 0x00},
+	{0x51, 0x40},
+	{0x52, 0x00},
+	{0x53, 0x00},
+	{0x54, 0x00},
+	{0x55, 0x88},
+	{0x57, 0x00},
+	{0x5c, 0x01},
+	{0x5a, 0x00},
+	{0x5b, 0xc0},
+	{0x35, 0x02},
+	{0xd9, 0x10},
+	{0xda, 0x01},
+	{0x94, 0x11},
+};
+
+static const u8 bridge_start_sxga[][2] = {
+	{0x94, 0xaa},
+	{0xf1, 0x60},
+	{0xe5, 0x04},
+	{0xc0, 0xa0},
+	{0xc1, 0x80},
+	{0x8c, 0x00},
+	{0x8d, 0x1c},
+	{0x34, 0x05},
+	{0xc2, 0x0c},
+	{0xc3, 0xf9},
+	{0xda, 0x00},
+	{0x35, 0x02},
+	{0xd9, 0x10},
+	{0x94, 0x11},
+};
+
+static const u8 ov965x_start_2_qvga[][2] = {
+	{0x3b, 0xe4},	/* com11 - night mode 1/4 frame rate */
+	{0x1e, 0x04},	/* mvfp */
+	{0x13, 0xe0},	/* com8 */
+	{0x00, 0x00},
+	{0x13, 0xe7},	/* com8 - everything (AGC, AWB and AEC) */
+	{0x11, 0x01},	/* clkrc */
+	{0x6b, 0x5a},	/* dblv */
+	{0x6a, 0x02},	/* 50 Hz banding filter */
+	{0xc5, 0x03},	/* 60 Hz banding filter */
+	{0xa2, 0x96},	/* bd50 */
+	{0xa3, 0x7d},	/* bd60 */
+
+	{0xff, 0x13},	/* read 13, write ff 00 */
+	{0x13, 0xe7},
+	{0x3a, 0x80},	/* tslb - yuyv */
+};
+
+static const u8 ov965x_start_2_vga[][2] = {
+	{0x3b, 0xc4},	/* com11 - night mode 1/4 frame rate */
+	{0x1e, 0x04},	/* mvfp */
+	{0x13, 0xe0},	/* com8 */
+	{0x00, 0x00},
+	{0x13, 0xe7},	/* com8 - everything (AGC, AWB and AEC) */
+	{0x11, 0x03},	/* clkrc */
+	{0x6b, 0x5a},	/* dblv */
+	{0x6a, 0x05},	/* 50 Hz banding filter */
+	{0xc5, 0x07},	/* 60 Hz banding filter */
+	{0xa2, 0x4b},	/* bd50 */
+	{0xa3, 0x3e},	/* bd60 */
+
+	{0x2d, 0x00},	/* advfl */
+};
+
+static const u8 ov965x_start_2_svga[][2] = {	/* same for xga */
+	{0x3b, 0xc4},	/* com11 - night mode 1/4 frame rate */
+	{0x1e, 0x04},	/* mvfp */
+	{0x13, 0xe0},	/* com8 */
+	{0x00, 0x00},
+	{0x13, 0xe7},	/* com8 - everything (AGC, AWB and AEC) */
+	{0x11, 0x01},	/* clkrc */
+	{0x6b, 0x5a},	/* dblv */
+	{0x6a, 0x0c},	/* 50 Hz banding filter */
+	{0xc5, 0x0f},	/* 60 Hz banding filter */
+	{0xa2, 0x4e},	/* bd50 */
+	{0xa3, 0x41},	/* bd60 */
+};
+
+static const u8 ov965x_start_2_sxga[][2] = {
+	{0x13, 0xe0},	/* com8 */
+	{0x00, 0x00},
+	{0x13, 0xe7},	/* com8 - everything (AGC, AWB and AEC) */
+	{0x3b, 0xc4},	/* com11 - night mode 1/4 frame rate */
+	{0x1e, 0x04},	/* mvfp */
+	{0x11, 0x01},	/* clkrc */
+	{0x6b, 0x5a},	/* dblv */
+	{0x6a, 0x0c},	/* 50 Hz banding filter */
+	{0xc5, 0x0f},	/* 60 Hz banding filter */
+	{0xa2, 0x4e},	/* bd50 */
+	{0xa3, 0x41},	/* bd60 */
+};
+
+static const u8 ov562x_init[][2] = {
+	{0x88, 0x20},
+	{0x89, 0x0a},
+	{0x8a, 0x90},
+	{0x8b, 0x06},
+	{0x8c, 0x01},
+	{0x8d, 0x10},
+	{0x1c, 0x00},
+	{0x1d, 0x48},
+	{0x1d, 0x00},
+	{0x1d, 0xff},
+	{0x1c, 0x0a},
+	{0x1d, 0x2e},
+	{0x1d, 0x1e},
+};
+
+static const u8 ov562x_init_2[][2] = {
+	{0x12, 0x80},
+	{0x11, 0x41},
+	{0x13, 0x00},
+	{0x10, 0x1e},
+	{0x3b, 0x07},
+	{0x5b, 0x40},
+	{0x39, 0x07},
+	{0x53, 0x02},
+	{0x54, 0x60},
+	{0x04, 0x20},
+	{0x27, 0x04},
+	{0x3d, 0x40},
+	{0x36, 0x00},
+	{0xc5, 0x04},
+	{0x4e, 0x00},
+	{0x4f, 0x93},
+	{0x50, 0x7b},
+	{0xca, 0x0c},
+	{0xcb, 0x0f},
+	{0x39, 0x07},
+	{0x4a, 0x10},
+	{0x3e, 0x0a},
+	{0x3d, 0x00},
+	{0x0c, 0x38},
+	{0x38, 0x90},
+	{0x46, 0x30},
+	{0x4f, 0x93},
+	{0x50, 0x7b},
+	{0xab, 0x00},
+	{0xca, 0x0c},
+	{0xcb, 0x0f},
+	{0x37, 0x02},
+	{0x44, 0x48},
+	{0x8d, 0x44},
+	{0x2a, 0x00},
+	{0x2b, 0x00},
+	{0x32, 0x00},
+	{0x38, 0x90},
+	{0x53, 0x02},
+	{0x54, 0x60},
+	{0x12, 0x00},
+	{0x17, 0x12},
+	{0x18, 0xb4},
+	{0x19, 0x0c},
+	{0x1a, 0xf4},
+	{0x03, 0x4a},
+	{0x89, 0x20},
+	{0x83, 0x80},
+	{0xb7, 0x9d},
+	{0xb6, 0x11},
+	{0xb5, 0x55},
+	{0xb4, 0x00},
+	{0xa9, 0xf0},
+	{0xa8, 0x0a},
+	{0xb8, 0xf0},
+	{0xb9, 0xf0},
+	{0xba, 0xf0},
+	{0x81, 0x07},
+	{0x63, 0x44},
+	{0x13, 0xc7},
+	{0x14, 0x60},
+	{0x33, 0x75},
+	{0x2c, 0x00},
+	{0x09, 0x00},
+	{0x35, 0x30},
+	{0x27, 0x04},
+	{0x3c, 0x07},
+	{0x3a, 0x0a},
+	{0x3b, 0x07},
+	{0x01, 0x40},
+	{0x02, 0x40},
+	{0x16, 0x40},
+	{0x52, 0xb0},
+	{0x51, 0x83},
+	{0x21, 0xbb},
+	{0x22, 0x10},
+	{0x23, 0x03},
+	{0x35, 0x38},
+	{0x20, 0x90},
+	{0x28, 0x30},
+	{0x73, 0xe1},
+	{0x6c, 0x00},
+	{0x6d, 0x80},
+	{0x6e, 0x00},
+	{0x70, 0x04},
+	{0x71, 0x00},
+	{0x8d, 0x04},
+	{0x64, 0x00},
+	{0x65, 0x00},
+	{0x66, 0x00},
+	{0x67, 0x00},
+	{0x68, 0x00},
+	{0x69, 0x00},
+	{0x6a, 0x00},
+	{0x6b, 0x00},
+	{0x71, 0x94},
+	{0x74, 0x20},
+	{0x80, 0x09},
+	{0x85, 0xc0},
+};
+
+static void reg_w_i(struct gspca_dev *gspca_dev, u16 reg, u8 val)
+{
+	struct usb_device *udev = gspca_dev->dev;
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	gspca_dev->usb_buf[0] = val;
+	ret = usb_control_msg(udev,
+			      usb_sndctrlpipe(udev, 0),
+			      0x01,
+			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      0x00, reg, gspca_dev->usb_buf, 1, CTRL_TIMEOUT);
+	if (ret < 0) {
+		pr_err("reg_w failed %d\n", ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+static void reg_w(struct gspca_dev *gspca_dev, u16 reg, u8 val)
+{
+	gspca_dbg(gspca_dev, D_USBO, "reg_w [%04x] = %02x\n", reg, val);
+	reg_w_i(gspca_dev, reg, val);
+}
+
+static u8 reg_r(struct gspca_dev *gspca_dev, u16 reg)
+{
+	struct usb_device *udev = gspca_dev->dev;
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return 0;
+	ret = usb_control_msg(udev,
+			      usb_rcvctrlpipe(udev, 0),
+			      0x01,
+			      USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      0x00, reg, gspca_dev->usb_buf, 1, CTRL_TIMEOUT);
+	gspca_dbg(gspca_dev, D_USBI, "reg_r [%04x] -> %02x\n",
+		  reg, gspca_dev->usb_buf[0]);
+	if (ret < 0) {
+		pr_err("reg_r err %d\n", ret);
+		gspca_dev->usb_err = ret;
+	}
+	return gspca_dev->usb_buf[0];
+}
+
+static int sccb_check_status(struct gspca_dev *gspca_dev)
+{
+	u8 data;
+	int i;
+
+	for (i = 0; i < 5; i++) {
+		msleep(20);
+		data = reg_r(gspca_dev, OV534_REG_STATUS);
+
+		switch (data) {
+		case 0x00:
+			return 1;
+		case 0x04:
+			return 0;
+		case 0x03:
+			break;
+		default:
+			gspca_dbg(gspca_dev, D_USBI|D_USBO,
+				  "sccb status 0x%02x, attempt %d/5\n",
+				  data, i + 1);
+		}
+	}
+	return 0;
+}
+
+static void sccb_write(struct gspca_dev *gspca_dev, u8 reg, u8 val)
+{
+	gspca_dbg(gspca_dev, D_USBO, "sccb_write [%02x] = %02x\n", reg, val);
+	reg_w_i(gspca_dev, OV534_REG_SUBADDR, reg);
+	reg_w_i(gspca_dev, OV534_REG_WRITE, val);
+	reg_w_i(gspca_dev, OV534_REG_OPERATION, OV534_OP_WRITE_3);
+
+	if (!sccb_check_status(gspca_dev))
+		pr_err("sccb_write failed\n");
+}
+
+static u8 sccb_read(struct gspca_dev *gspca_dev, u16 reg)
+{
+	reg_w(gspca_dev, OV534_REG_SUBADDR, reg);
+	reg_w(gspca_dev, OV534_REG_OPERATION, OV534_OP_WRITE_2);
+	if (!sccb_check_status(gspca_dev))
+		pr_err("sccb_read failed 1\n");
+
+	reg_w(gspca_dev, OV534_REG_OPERATION, OV534_OP_READ_2);
+	if (!sccb_check_status(gspca_dev))
+		pr_err("sccb_read failed 2\n");
+
+	return reg_r(gspca_dev, OV534_REG_READ);
+}
+
+/* output a bridge sequence (reg - val) */
+static void reg_w_array(struct gspca_dev *gspca_dev,
+			const u8 (*data)[2], int len)
+{
+	while (--len >= 0) {
+		reg_w(gspca_dev, (*data)[0], (*data)[1]);
+		data++;
+	}
+}
+
+/* output a sensor sequence (reg - val) */
+static void sccb_w_array(struct gspca_dev *gspca_dev,
+			const u8 (*data)[2], int len)
+{
+	while (--len >= 0) {
+		if ((*data)[0] != 0xff) {
+			sccb_write(gspca_dev, (*data)[0], (*data)[1]);
+		} else {
+			sccb_read(gspca_dev, (*data)[1]);
+			sccb_write(gspca_dev, 0xff, 0x00);
+		}
+		data++;
+	}
+}
+
+/* Two bits control LED: 0x21 bit 7 and 0x23 bit 7.
+ * (direction and output)? */
+static void set_led(struct gspca_dev *gspca_dev, int status)
+{
+	u8 data;
+
+	gspca_dbg(gspca_dev, D_CONF, "led status: %d\n", status);
+
+	data = reg_r(gspca_dev, 0x21);
+	data |= 0x80;
+	reg_w(gspca_dev, 0x21, data);
+
+	data = reg_r(gspca_dev, 0x23);
+	if (status)
+		data |= 0x80;
+	else
+		data &= ~0x80;
+
+	reg_w(gspca_dev, 0x23, data);
+
+	if (!status) {
+		data = reg_r(gspca_dev, 0x21);
+		data &= ~0x80;
+		reg_w(gspca_dev, 0x21, data);
+	}
+}
+
+static void setbrightness(struct gspca_dev *gspca_dev, s32 brightness)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 val;
+	s8 sval;
+
+	if (sd->sensor == SENSOR_OV562x) {
+		sval = brightness;
+		val = 0x76;
+		val += sval;
+		sccb_write(gspca_dev, 0x24, val);
+		val = 0x6a;
+		val += sval;
+		sccb_write(gspca_dev, 0x25, val);
+		if (sval < -40)
+			val = 0x71;
+		else if (sval < 20)
+			val = 0x94;
+		else
+			val = 0xe6;
+		sccb_write(gspca_dev, 0x26, val);
+	} else {
+		val = brightness;
+		if (val < 8)
+			val = 15 - val;		/* f .. 8 */
+		else
+			val = val - 8;		/* 0 .. 7 */
+		sccb_write(gspca_dev, 0x55,	/* brtn - brightness adjustment */
+				0x0f | (val << 4));
+	}
+}
+
+static void setcontrast(struct gspca_dev *gspca_dev, s32 val)
+{
+	sccb_write(gspca_dev, 0x56,	/* cnst1 - contrast 1 ctrl coeff */
+			val << 4);
+}
+
+static void setautogain(struct gspca_dev *gspca_dev, s32 autogain)
+{
+	u8 val;
+
+/*fixme: should adjust agc/awb/aec by different controls */
+	val = sccb_read(gspca_dev, 0x13);		/* com8 */
+	sccb_write(gspca_dev, 0xff, 0x00);
+	if (autogain)
+		val |= 0x05;		/* agc & aec */
+	else
+		val &= 0xfa;
+	sccb_write(gspca_dev, 0x13, val);
+}
+
+static void setexposure(struct gspca_dev *gspca_dev, s32 exposure)
+{
+	static const u8 expo[4] = {0x00, 0x25, 0x38, 0x5e};
+	u8 val;
+
+	sccb_write(gspca_dev, 0x10, expo[exposure]);	/* aec[9:2] */
+
+	val = sccb_read(gspca_dev, 0x13);		/* com8 */
+	sccb_write(gspca_dev, 0xff, 0x00);
+	sccb_write(gspca_dev, 0x13, val);
+
+	val = sccb_read(gspca_dev, 0xa1);		/* aech */
+	sccb_write(gspca_dev, 0xff, 0x00);
+	sccb_write(gspca_dev, 0xa1, val & 0xe0);	/* aec[15:10] = 0 */
+}
+
+static void setsharpness(struct gspca_dev *gspca_dev, s32 val)
+{
+	if (val < 0) {				/* auto */
+		val = sccb_read(gspca_dev, 0x42);	/* com17 */
+		sccb_write(gspca_dev, 0xff, 0x00);
+		sccb_write(gspca_dev, 0x42, val | 0x40);
+				/* Edge enhancement strength auto adjust */
+		return;
+	}
+	if (val != 0)
+		val = 1 << (val - 1);
+	sccb_write(gspca_dev, 0x3f,	/* edge - edge enhance. factor */
+			val);
+	val = sccb_read(gspca_dev, 0x42);		/* com17 */
+	sccb_write(gspca_dev, 0xff, 0x00);
+	sccb_write(gspca_dev, 0x42, val & 0xbf);
+}
+
+static void setsatur(struct gspca_dev *gspca_dev, s32 val)
+{
+	u8 val1, val2, val3;
+	static const u8 matrix[5][2] = {
+		{0x14, 0x38},
+		{0x1e, 0x54},
+		{0x28, 0x70},
+		{0x32, 0x8c},
+		{0x48, 0x90}
+	};
+
+	val1 = matrix[val][0];
+	val2 = matrix[val][1];
+	val3 = val1 + val2;
+	sccb_write(gspca_dev, 0x4f, val3);	/* matrix coeff */
+	sccb_write(gspca_dev, 0x50, val3);
+	sccb_write(gspca_dev, 0x51, 0x00);
+	sccb_write(gspca_dev, 0x52, val1);
+	sccb_write(gspca_dev, 0x53, val2);
+	sccb_write(gspca_dev, 0x54, val3);
+	sccb_write(gspca_dev, 0x58, 0x1a);	/* mtxs - coeff signs */
+
+	val1 = sccb_read(gspca_dev, 0x41);	/* com16 */
+	sccb_write(gspca_dev, 0xff, 0x00);
+	sccb_write(gspca_dev, 0x41, val1);
+}
+
+static void setlightfreq(struct gspca_dev *gspca_dev, s32 freq)
+{
+	u8 val;
+
+	val = sccb_read(gspca_dev, 0x13);		/* com8 */
+	sccb_write(gspca_dev, 0xff, 0x00);
+	if (freq == 0) {
+		sccb_write(gspca_dev, 0x13, val & 0xdf);
+		return;
+	}
+	sccb_write(gspca_dev, 0x13, val | 0x20);
+
+	val = sccb_read(gspca_dev, 0x42);		/* com17 */
+	sccb_write(gspca_dev, 0xff, 0x00);
+	if (freq == 1)
+		val |= 0x01;
+	else
+		val &= 0xfe;
+	sccb_write(gspca_dev, 0x42, val);
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+		     const struct usb_device_id *id)
+{
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u16 sensor_id;
+
+	/* reset bridge */
+	reg_w(gspca_dev, 0xe7, 0x3a);
+	reg_w(gspca_dev, 0xe0, 0x08);
+	msleep(100);
+
+	/* initialize the sensor address */
+	reg_w(gspca_dev, OV534_REG_ADDRESS, 0x60);
+
+	/* reset sensor */
+	sccb_write(gspca_dev, 0x12, 0x80);
+	msleep(10);
+
+	/* probe the sensor */
+	sccb_read(gspca_dev, 0x0a);
+	sensor_id = sccb_read(gspca_dev, 0x0a) << 8;
+	sccb_read(gspca_dev, 0x0b);
+	sensor_id |= sccb_read(gspca_dev, 0x0b);
+	gspca_dbg(gspca_dev, D_PROBE, "Sensor ID: %04x\n", sensor_id);
+
+	/* initialize */
+	if ((sensor_id & 0xfff0) == 0x9650) {
+		sd->sensor = SENSOR_OV965x;
+
+		gspca_dev->cam.cam_mode = ov965x_mode;
+		gspca_dev->cam.nmodes = ARRAY_SIZE(ov965x_mode);
+
+		reg_w_array(gspca_dev, bridge_init,
+				ARRAY_SIZE(bridge_init));
+		sccb_w_array(gspca_dev, ov965x_init,
+				ARRAY_SIZE(ov965x_init));
+		reg_w_array(gspca_dev, bridge_init_2,
+				ARRAY_SIZE(bridge_init_2));
+		sccb_w_array(gspca_dev, ov965x_init_2,
+				ARRAY_SIZE(ov965x_init_2));
+		reg_w(gspca_dev, 0xe0, 0x00);
+		reg_w(gspca_dev, 0xe0, 0x01);
+		set_led(gspca_dev, 0);
+		reg_w(gspca_dev, 0xe0, 0x00);
+	} else if ((sensor_id & 0xfff0) == 0x9710) {
+		const char *p;
+		int l;
+
+		sd->sensor = SENSOR_OV971x;
+
+		gspca_dev->cam.cam_mode = ov971x_mode;
+		gspca_dev->cam.nmodes = ARRAY_SIZE(ov971x_mode);
+
+		gspca_dev->cam.bulk = 1;
+		gspca_dev->cam.bulk_size = 16384;
+		gspca_dev->cam.bulk_nurbs = 2;
+
+		sccb_w_array(gspca_dev, ov971x_init,
+				ARRAY_SIZE(ov971x_init));
+
+		/* set video format on bridge processor */
+		/* access bridge processor's video format registers at: 0x00 */
+		reg_w(gspca_dev, 0x1c, 0x00);
+		/*set register: 0x00 is 'RAW8', 0x40 is 'YUV422' (YUYV?)*/
+		reg_w(gspca_dev, 0x1d, 0x00);
+
+		/* Will W. specific stuff
+		 * set VSYNC to
+		 *	output (0x1f) if first webcam
+		 *	input (0x17) if 2nd or 3rd webcam */
+		p = video_device_node_name(&gspca_dev->vdev);
+		l = strlen(p) - 1;
+		if (p[l] == '0')
+			reg_w(gspca_dev, 0x56, 0x1f);
+		else
+			reg_w(gspca_dev, 0x56, 0x17);
+	} else if ((sensor_id & 0xfff0) == 0x5620) {
+		sd->sensor = SENSOR_OV562x;
+		gspca_dev->cam.cam_mode = ov562x_mode;
+		gspca_dev->cam.nmodes = ARRAY_SIZE(ov562x_mode);
+
+		reg_w_array(gspca_dev, ov562x_init,
+				ARRAY_SIZE(ov562x_init));
+		sccb_w_array(gspca_dev, ov562x_init_2,
+				ARRAY_SIZE(ov562x_init_2));
+		reg_w(gspca_dev, 0xe0, 0x00);
+	} else if ((sensor_id & 0xfff0) == 0x3610) {
+		sd->sensor = SENSOR_OV361x;
+		gspca_dev->cam.cam_mode = ov361x_mode;
+		gspca_dev->cam.nmodes = ARRAY_SIZE(ov361x_mode);
+		reg_w(gspca_dev, 0xe7, 0x3a);
+		reg_w(gspca_dev, 0xf1, 0x60);
+		sccb_write(gspca_dev, 0x12, 0x80);
+	} else {
+		pr_err("Unknown sensor %04x", sensor_id);
+		return -EINVAL;
+	}
+
+	return gspca_dev->usb_err;
+}
+
+static int sd_start_ov361x(struct gspca_dev *gspca_dev)
+{
+	sccb_write(gspca_dev, 0x12, 0x80);
+	msleep(20);
+	switch (gspca_dev->curr_mode % (ov361x_last)) {
+	case ov361x_2048:
+		reg_w_array(gspca_dev, ov361x_bridge_start_2048,
+			    ARRAY_SIZE(ov361x_bridge_start_2048));
+		sccb_w_array(gspca_dev, ov361x_start_2048,
+			     ARRAY_SIZE(ov361x_start_2048));
+		break;
+	case ov361x_1600:
+		reg_w_array(gspca_dev, ov361x_bridge_start_1600,
+			    ARRAY_SIZE(ov361x_bridge_start_1600));
+		sccb_w_array(gspca_dev, ov361x_start_1600,
+			     ARRAY_SIZE(ov361x_start_1600));
+		break;
+	case ov361x_1024:
+		reg_w_array(gspca_dev, ov361x_bridge_start_1024,
+			    ARRAY_SIZE(ov361x_bridge_start_1024));
+		sccb_w_array(gspca_dev, ov361x_start_1024,
+			     ARRAY_SIZE(ov361x_start_1024));
+		break;
+	case ov361x_640:
+		reg_w_array(gspca_dev, ov361x_bridge_start_640,
+			    ARRAY_SIZE(ov361x_bridge_start_640));
+		sccb_w_array(gspca_dev, ov361x_start_640,
+			     ARRAY_SIZE(ov361x_start_640));
+		break;
+	case ov361x_320:
+		reg_w_array(gspca_dev, ov361x_bridge_start_320,
+			    ARRAY_SIZE(ov361x_bridge_start_320));
+		sccb_w_array(gspca_dev, ov361x_start_320,
+			     ARRAY_SIZE(ov361x_start_320));
+		break;
+	case ov361x_160:
+		reg_w_array(gspca_dev, ov361x_bridge_start_160,
+			    ARRAY_SIZE(ov361x_bridge_start_160));
+		sccb_w_array(gspca_dev, ov361x_start_160,
+			     ARRAY_SIZE(ov361x_start_160));
+		break;
+	}
+	reg_w(gspca_dev, 0xe0, 0x00); /* start transfer */
+
+	return gspca_dev->usb_err;
+}
+
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->sensor == SENSOR_OV971x)
+		return gspca_dev->usb_err;
+	if (sd->sensor == SENSOR_OV562x)
+		return gspca_dev->usb_err;
+	if (sd->sensor == SENSOR_OV361x)
+		return sd_start_ov361x(gspca_dev);
+
+	switch (gspca_dev->curr_mode) {
+	case QVGA_MODE:			/* 320x240 */
+		sccb_w_array(gspca_dev, ov965x_start_1_vga,
+				ARRAY_SIZE(ov965x_start_1_vga));
+		reg_w_array(gspca_dev, bridge_start_qvga,
+				ARRAY_SIZE(bridge_start_qvga));
+		sccb_w_array(gspca_dev, ov965x_start_2_qvga,
+				ARRAY_SIZE(ov965x_start_2_qvga));
+		break;
+	case VGA_MODE:			/* 640x480 */
+		sccb_w_array(gspca_dev, ov965x_start_1_vga,
+				ARRAY_SIZE(ov965x_start_1_vga));
+		reg_w_array(gspca_dev, bridge_start_vga,
+				ARRAY_SIZE(bridge_start_vga));
+		sccb_w_array(gspca_dev, ov965x_start_2_vga,
+				ARRAY_SIZE(ov965x_start_2_vga));
+		break;
+	case SVGA_MODE:			/* 800x600 */
+		sccb_w_array(gspca_dev, ov965x_start_1_svga,
+				ARRAY_SIZE(ov965x_start_1_svga));
+		reg_w_array(gspca_dev, bridge_start_svga,
+				ARRAY_SIZE(bridge_start_svga));
+		sccb_w_array(gspca_dev, ov965x_start_2_svga,
+				ARRAY_SIZE(ov965x_start_2_svga));
+		break;
+	case XGA_MODE:			/* 1024x768 */
+		sccb_w_array(gspca_dev, ov965x_start_1_xga,
+				ARRAY_SIZE(ov965x_start_1_xga));
+		reg_w_array(gspca_dev, bridge_start_xga,
+				ARRAY_SIZE(bridge_start_xga));
+		sccb_w_array(gspca_dev, ov965x_start_2_svga,
+				ARRAY_SIZE(ov965x_start_2_svga));
+		break;
+	default:
+/*	case SXGA_MODE:			 * 1280x1024 */
+		sccb_w_array(gspca_dev, ov965x_start_1_sxga,
+				ARRAY_SIZE(ov965x_start_1_sxga));
+		reg_w_array(gspca_dev, bridge_start_sxga,
+				ARRAY_SIZE(bridge_start_sxga));
+		sccb_w_array(gspca_dev, ov965x_start_2_sxga,
+				ARRAY_SIZE(ov965x_start_2_sxga));
+		break;
+	}
+
+	reg_w(gspca_dev, 0xe0, 0x00);
+	reg_w(gspca_dev, 0xe0, 0x00);
+	set_led(gspca_dev, 1);
+	return gspca_dev->usb_err;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	if (((struct sd *)gspca_dev)->sensor == SENSOR_OV361x) {
+		reg_w(gspca_dev, 0xe0, 0x01); /* stop transfer */
+		/* reg_w(gspca_dev, 0x31, 0x09); */
+		return;
+	}
+	reg_w(gspca_dev, 0xe0, 0x01);
+	set_led(gspca_dev, 0);
+	reg_w(gspca_dev, 0xe0, 0x00);
+}
+
+/* Values for bmHeaderInfo (Video and Still Image Payload Headers, 2.4.3.3) */
+#define UVC_STREAM_EOH	(1 << 7)
+#define UVC_STREAM_ERR	(1 << 6)
+#define UVC_STREAM_STI	(1 << 5)
+#define UVC_STREAM_RES	(1 << 4)
+#define UVC_STREAM_SCR	(1 << 3)
+#define UVC_STREAM_PTS	(1 << 2)
+#define UVC_STREAM_EOF	(1 << 1)
+#define UVC_STREAM_FID	(1 << 0)
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data, int len)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	__u32 this_pts;
+	u8 this_fid;
+	int remaining_len = len;
+	int payload_len;
+
+	payload_len = gspca_dev->cam.bulk ? 2048 : 2040;
+	do {
+		len = min(remaining_len, payload_len);
+
+		/* Payloads are prefixed with a UVC-style header.  We
+		   consider a frame to start when the FID toggles, or the PTS
+		   changes.  A frame ends when EOF is set, and we've received
+		   the correct number of bytes. */
+
+		/* Verify UVC header.  Header length is always 12 */
+		if (data[0] != 12 || len < 12) {
+			gspca_dbg(gspca_dev, D_PACK, "bad header\n");
+			goto discard;
+		}
+
+		/* Check errors */
+		if (data[1] & UVC_STREAM_ERR) {
+			gspca_dbg(gspca_dev, D_PACK, "payload error\n");
+			goto discard;
+		}
+
+		/* Extract PTS and FID */
+		if (!(data[1] & UVC_STREAM_PTS)) {
+			gspca_dbg(gspca_dev, D_PACK, "PTS not present\n");
+			goto discard;
+		}
+		this_pts = (data[5] << 24) | (data[4] << 16)
+						| (data[3] << 8) | data[2];
+		this_fid = data[1] & UVC_STREAM_FID;
+
+		/* If PTS or FID has changed, start a new frame. */
+		if (this_pts != sd->last_pts || this_fid != sd->last_fid) {
+			if (gspca_dev->last_packet_type == INTER_PACKET)
+				gspca_frame_add(gspca_dev, LAST_PACKET,
+						NULL, 0);
+			sd->last_pts = this_pts;
+			sd->last_fid = this_fid;
+			gspca_frame_add(gspca_dev, FIRST_PACKET,
+					data + 12, len - 12);
+		/* If this packet is marked as EOF, end the frame */
+		} else if (data[1] & UVC_STREAM_EOF) {
+			sd->last_pts = 0;
+			gspca_frame_add(gspca_dev, LAST_PACKET,
+					data + 12, len - 12);
+		} else {
+
+			/* Add the data from this payload */
+			gspca_frame_add(gspca_dev, INTER_PACKET,
+					data + 12, len - 12);
+		}
+
+		/* Done this payload */
+		goto scan_next;
+
+discard:
+		/* Discard data until a new frame starts. */
+		gspca_dev->last_packet_type = DISCARD_PACKET;
+
+scan_next:
+		remaining_len -= len;
+		data += len;
+	} while (remaining_len > 0);
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		setbrightness(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_CONTRAST:
+		setcontrast(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_SATURATION:
+		setsatur(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_POWER_LINE_FREQUENCY:
+		setlightfreq(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_SHARPNESS:
+		setsharpness(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_AUTOGAIN:
+		if (ctrl->is_new)
+			setautogain(gspca_dev, ctrl->val);
+		if (!ctrl->val && gspca_dev->exposure->is_new)
+			setexposure(gspca_dev, gspca_dev->exposure->val);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *)gspca_dev;
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	if (sd->sensor == SENSOR_OV971x)
+		return 0;
+	if (sd->sensor == SENSOR_OV361x)
+		return 0;
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 7);
+	if (sd->sensor == SENSOR_OV562x) {
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, -90, 90, 1, 0);
+	} else {
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0, 15, 1, 7);
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_CONTRAST, 0, 15, 1, 3);
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_SATURATION, 0, 4, 1, 2);
+		/* -1 = auto */
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_SHARPNESS, -1, 4, 1, -1);
+		gspca_dev->autogain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
+		gspca_dev->exposure = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_EXPOSURE, 0, 3, 1, 0);
+		v4l2_ctrl_new_std_menu(hdl, &sd_ctrl_ops,
+			V4L2_CID_POWER_LINE_FREQUENCY,
+			V4L2_CID_POWER_LINE_FREQUENCY_60HZ, 0, 0);
+		v4l2_ctrl_auto_cluster(3, &gspca_dev->autogain, 0, false);
+	}
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	return 0;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name     = MODULE_NAME,
+	.config   = sd_config,
+	.init     = sd_init,
+	.init_controls = sd_init_controls,
+	.start    = sd_start,
+	.stopN    = sd_stopN,
+	.pkt_scan = sd_pkt_scan,
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x05a9, 0x8065)},
+	{USB_DEVICE(0x06f8, 0x3003)},
+	{USB_DEVICE(0x05a9, 0x1550)},
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name       = MODULE_NAME,
+	.id_table   = device_table,
+	.probe      = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend    = gspca_suspend,
+	.resume     = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/pac207.c b/drivers/media/usb/gspca/pac207.c
new file mode 100644
index 0000000..a1df7af
--- /dev/null
+++ b/drivers/media/usb/gspca/pac207.c
@@ -0,0 +1,485 @@
+/*
+ * Pixart PAC207BCA library
+ *
+ * Copyright (C) 2008 Hans de Goede <hdegoede@redhat.com>
+ * Copyright (C) 2005 Thomas Kaiser thomas@kaiser-linux.li
+ * Copyleft (C) 2005 Michel Xhaard mxhaard@magic.fr
+ *
+ * V4L2 by Jean-Francois Moine <http://moinejf.free.fr>
+ *
+ * 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.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "pac207"
+
+#include <linux/input.h>
+#include "gspca.h"
+/* Include pac common sof detection functions */
+#include "pac_common.h"
+
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_DESCRIPTION("Pixart PAC207");
+MODULE_LICENSE("GPL");
+
+#define PAC207_CTRL_TIMEOUT		100  /* ms */
+
+#define PAC207_BRIGHTNESS_MIN		0
+#define PAC207_BRIGHTNESS_MAX		255
+#define PAC207_BRIGHTNESS_DEFAULT	46
+#define PAC207_BRIGHTNESS_REG		0x08
+
+#define PAC207_EXPOSURE_MIN		3
+#define PAC207_EXPOSURE_MAX		90 /* 1 sec expo time / 1 fps */
+#define PAC207_EXPOSURE_DEFAULT		5 /* power on default: 3 */
+#define PAC207_EXPOSURE_REG		0x02
+
+#define PAC207_GAIN_MIN			0
+#define PAC207_GAIN_MAX			31
+#define PAC207_GAIN_DEFAULT		7 /* power on default: 9 */
+#define PAC207_GAIN_REG			0x0e
+
+#define PAC207_AUTOGAIN_DEADZONE	30
+
+/* global parameters */
+static int led_invert;
+module_param(led_invert, int, 0644);
+MODULE_PARM_DESC(led_invert, "Invert led");
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;		/* !! must be the first item */
+
+	struct v4l2_ctrl *brightness;
+
+	u8 mode;
+	u8 sof_read;
+	u8 header_read;
+	u8 autogain_ignore_frames;
+
+	atomic_t avg_lum;
+};
+
+static const struct v4l2_pix_format sif_mode[] = {
+	{176, 144, V4L2_PIX_FMT_PAC207, V4L2_FIELD_NONE,
+		.bytesperline = 176,
+		.sizeimage = (176 + 2) * 144,
+			/* uncompressed, add 2 bytes / line for line header */
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1},
+	{352, 288, V4L2_PIX_FMT_PAC207, V4L2_FIELD_NONE,
+		.bytesperline = 352,
+			/* compressed, but only when needed (not compressed
+			   when the framerate is low) */
+		.sizeimage = (352 + 2) * 288,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+};
+
+static const __u8 pac207_sensor_init[][8] = {
+	{0x10, 0x12, 0x0d, 0x12, 0x0c, 0x01, 0x29, 0x84},
+	{0x49, 0x64, 0x64, 0x64, 0x04, 0x10, 0xf0, 0x30},
+	{0x00, 0x00, 0x00, 0x70, 0xa0, 0xf8, 0x00, 0x00},
+	{0x32, 0x00, 0x96, 0x00, 0xa2, 0x02, 0xaf, 0x00},
+};
+
+static void pac207_write_regs(struct gspca_dev *gspca_dev, u16 index,
+	const u8 *buffer, u16 length)
+{
+	struct usb_device *udev = gspca_dev->dev;
+	int err;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+
+	memcpy(gspca_dev->usb_buf, buffer, length);
+
+	err = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x01,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+			0x00, index,
+			gspca_dev->usb_buf, length, PAC207_CTRL_TIMEOUT);
+	if (err < 0) {
+		pr_err("Failed to write registers to index 0x%04X, error %d\n",
+		       index, err);
+		gspca_dev->usb_err = err;
+	}
+}
+
+static void pac207_write_reg(struct gspca_dev *gspca_dev, u16 index, u16 value)
+{
+	struct usb_device *udev = gspca_dev->dev;
+	int err;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+
+	err = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x00,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+			value, index, NULL, 0, PAC207_CTRL_TIMEOUT);
+	if (err) {
+		pr_err("Failed to write a register (index 0x%04X, value 0x%02X, error %d)\n",
+		       index, value, err);
+		gspca_dev->usb_err = err;
+	}
+}
+
+static int pac207_read_reg(struct gspca_dev *gspca_dev, u16 index)
+{
+	struct usb_device *udev = gspca_dev->dev;
+	int res;
+
+	if (gspca_dev->usb_err < 0)
+		return 0;
+
+	res = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x00,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+			0x00, index,
+			gspca_dev->usb_buf, 1, PAC207_CTRL_TIMEOUT);
+	if (res < 0) {
+		pr_err("Failed to read a register (index 0x%04X, error %d)\n",
+		       index, res);
+		gspca_dev->usb_err = res;
+		return 0;
+	}
+
+	return gspca_dev->usb_buf[0];
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	struct cam *cam;
+	u8 idreg[2];
+
+	idreg[0] = pac207_read_reg(gspca_dev, 0x0000);
+	idreg[1] = pac207_read_reg(gspca_dev, 0x0001);
+	idreg[0] = ((idreg[0] & 0x0f) << 4) | ((idreg[1] & 0xf0) >> 4);
+	idreg[1] = idreg[1] & 0x0f;
+	gspca_dbg(gspca_dev, D_PROBE, "Pixart Sensor ID 0x%02X Chips ID 0x%02X\n",
+		  idreg[0], idreg[1]);
+
+	if (idreg[0] != 0x27) {
+		gspca_dbg(gspca_dev, D_PROBE, "Error invalid sensor ID!\n");
+		return -ENODEV;
+	}
+
+	gspca_dbg(gspca_dev, D_PROBE,
+		  "Pixart PAC207BCA Image Processor and Control Chip detected (vid/pid 0x%04X:0x%04X)\n",
+		  id->idVendor, id->idProduct);
+
+	cam = &gspca_dev->cam;
+	cam->cam_mode = sif_mode;
+	cam->nmodes = ARRAY_SIZE(sif_mode);
+
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	u8 mode;
+
+	/* mode: Image Format (Bit 0), LED (1), Compr. test mode (2) */
+	if (led_invert)
+		mode = 0x02;
+	else
+		mode = 0x00;
+	pac207_write_reg(gspca_dev, 0x41, mode);
+	pac207_write_reg(gspca_dev, 0x0f, 0x00); /* Power Control */
+
+	return gspca_dev->usb_err;
+}
+
+static void setcontrol(struct gspca_dev *gspca_dev, u16 reg, u16 val)
+{
+	pac207_write_reg(gspca_dev, reg, val);
+	pac207_write_reg(gspca_dev, 0x13, 0x01);	/* Bit 0, auto clear */
+	pac207_write_reg(gspca_dev, 0x1c, 0x01);	/* not documented */
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *)gspca_dev;
+
+	gspca_dev->usb_err = 0;
+
+	if (ctrl->id == V4L2_CID_AUTOGAIN && ctrl->is_new && ctrl->val) {
+		/* when switching to autogain set defaults to make sure
+		   we are on a valid point of the autogain gain /
+		   exposure knee graph, and give this change time to
+		   take effect before doing autogain. */
+		gspca_dev->exposure->val    = PAC207_EXPOSURE_DEFAULT;
+		gspca_dev->gain->val        = PAC207_GAIN_DEFAULT;
+		sd->autogain_ignore_frames  = PAC_AUTOGAIN_IGNORE_FRAMES;
+	}
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		setcontrol(gspca_dev, PAC207_BRIGHTNESS_REG, ctrl->val);
+		break;
+	case V4L2_CID_AUTOGAIN:
+		if (gspca_dev->exposure->is_new || (ctrl->is_new && ctrl->val))
+			setcontrol(gspca_dev, PAC207_EXPOSURE_REG,
+				   gspca_dev->exposure->val);
+		if (gspca_dev->gain->is_new || (ctrl->is_new && ctrl->val))
+			setcontrol(gspca_dev, PAC207_GAIN_REG,
+				   gspca_dev->gain->val);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+/* this function is called at probe time */
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 4);
+
+	sd->brightness = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+				V4L2_CID_BRIGHTNESS,
+				PAC207_BRIGHTNESS_MIN, PAC207_BRIGHTNESS_MAX,
+				1, PAC207_BRIGHTNESS_DEFAULT);
+	gspca_dev->autogain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+				V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
+	gspca_dev->exposure = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+				V4L2_CID_EXPOSURE,
+				PAC207_EXPOSURE_MIN, PAC207_EXPOSURE_MAX,
+				1, PAC207_EXPOSURE_DEFAULT);
+	gspca_dev->gain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+				V4L2_CID_GAIN,
+				PAC207_GAIN_MIN, PAC207_GAIN_MAX,
+				1, PAC207_GAIN_DEFAULT);
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	v4l2_ctrl_auto_cluster(3, &gspca_dev->autogain, 0, false);
+	return 0;
+}
+
+/* -- start the camera -- */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	__u8 mode;
+
+	pac207_write_reg(gspca_dev, 0x0f, 0x10); /* Power control (Bit 6-0) */
+	pac207_write_regs(gspca_dev, 0x0002, pac207_sensor_init[0], 8);
+	pac207_write_regs(gspca_dev, 0x000a, pac207_sensor_init[1], 8);
+	pac207_write_regs(gspca_dev, 0x0012, pac207_sensor_init[2], 8);
+	pac207_write_regs(gspca_dev, 0x0042, pac207_sensor_init[3], 8);
+
+	/* Compression Balance */
+	if (gspca_dev->pixfmt.width == 176)
+		pac207_write_reg(gspca_dev, 0x4a, 0xff);
+	else
+		pac207_write_reg(gspca_dev, 0x4a, 0x30);
+	pac207_write_reg(gspca_dev, 0x4b, 0x00); /* Sram test value */
+	pac207_write_reg(gspca_dev, 0x08, v4l2_ctrl_g_ctrl(sd->brightness));
+
+	/* PGA global gain (Bit 4-0) */
+	pac207_write_reg(gspca_dev, 0x0e,
+		v4l2_ctrl_g_ctrl(gspca_dev->gain));
+	pac207_write_reg(gspca_dev, 0x02,
+		v4l2_ctrl_g_ctrl(gspca_dev->exposure)); /* PXCK = 12MHz /n */
+
+	/* mode: Image Format (Bit 0), LED (1), Compr. test mode (2) */
+	if (led_invert)
+		mode = 0x00;
+	else
+		mode = 0x02;
+	if (gspca_dev->pixfmt.width == 176) {	/* 176x144 */
+		mode |= 0x01;
+		gspca_dbg(gspca_dev, D_STREAM, "pac207_start mode 176x144\n");
+	} else {				/* 352x288 */
+		gspca_dbg(gspca_dev, D_STREAM, "pac207_start mode 352x288\n");
+	}
+	pac207_write_reg(gspca_dev, 0x41, mode);
+
+	pac207_write_reg(gspca_dev, 0x13, 0x01); /* Bit 0, auto clear */
+	pac207_write_reg(gspca_dev, 0x1c, 0x01); /* not documented */
+	msleep(10);
+	pac207_write_reg(gspca_dev, 0x40, 0x01); /* Start ISO pipe */
+
+	sd->sof_read = 0;
+	sd->autogain_ignore_frames = 0;
+	atomic_set(&sd->avg_lum, -1);
+	return gspca_dev->usb_err;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	u8 mode;
+
+	/* mode: Image Format (Bit 0), LED (1), Compr. test mode (2) */
+	if (led_invert)
+		mode = 0x02;
+	else
+		mode = 0x00;
+	pac207_write_reg(gspca_dev, 0x40, 0x00); /* Stop ISO pipe */
+	pac207_write_reg(gspca_dev, 0x41, mode); /* Turn off LED */
+	pac207_write_reg(gspca_dev, 0x0f, 0x00); /* Power Control */
+}
+
+
+static void pac207_do_auto_gain(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int avg_lum = atomic_read(&sd->avg_lum);
+
+	if (avg_lum == -1)
+		return;
+
+	if (sd->autogain_ignore_frames > 0)
+		sd->autogain_ignore_frames--;
+	else if (gspca_coarse_grained_expo_autogain(gspca_dev, avg_lum,
+			90, PAC207_AUTOGAIN_DEADZONE))
+		sd->autogain_ignore_frames = PAC_AUTOGAIN_IGNORE_FRAMES;
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,
+			int len)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	unsigned char *sof;
+
+	sof = pac_find_sof(gspca_dev, &sd->sof_read, data, len);
+	if (sof) {
+		int n;
+
+		/* finish decoding current frame */
+		n = sof - data;
+		if (n > sizeof pac_sof_marker)
+			n -= sizeof pac_sof_marker;
+		else
+			n = 0;
+		gspca_frame_add(gspca_dev, LAST_PACKET,
+				data, n);
+		sd->header_read = 0;
+		gspca_frame_add(gspca_dev, FIRST_PACKET, NULL, 0);
+		len -= sof - data;
+		data = sof;
+	}
+	if (sd->header_read < 11) {
+		int needed;
+
+		/* get average lumination from frame header (byte 5) */
+		if (sd->header_read < 5) {
+			needed = 5 - sd->header_read;
+			if (len >= needed)
+				atomic_set(&sd->avg_lum, data[needed - 1]);
+		}
+		/* skip the rest of the header */
+		needed = 11 - sd->header_read;
+		if (len <= needed) {
+			sd->header_read += len;
+			return;
+		}
+		data += needed;
+		len -= needed;
+		sd->header_read = 11;
+	}
+
+	gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+}
+
+#if IS_ENABLED(CONFIG_INPUT)
+static int sd_int_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,		/* interrupt packet data */
+			int len)		/* interrupt packet length */
+{
+	int ret = -EINVAL;
+
+	if (len == 2 && data[0] == 0x5a && data[1] == 0x5a) {
+		input_report_key(gspca_dev->input_dev, KEY_CAMERA, 1);
+		input_sync(gspca_dev->input_dev);
+		input_report_key(gspca_dev->input_dev, KEY_CAMERA, 0);
+		input_sync(gspca_dev->input_dev);
+		ret = 0;
+	}
+
+	return ret;
+}
+#endif
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.dq_callback = pac207_do_auto_gain,
+	.pkt_scan = sd_pkt_scan,
+#if IS_ENABLED(CONFIG_INPUT)
+	.int_pkt_scan = sd_int_pkt_scan,
+#endif
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x041e, 0x4028)},
+	{USB_DEVICE(0x093a, 0x2460)},
+	{USB_DEVICE(0x093a, 0x2461)},
+	{USB_DEVICE(0x093a, 0x2463)},
+	{USB_DEVICE(0x093a, 0x2464)},
+	{USB_DEVICE(0x093a, 0x2468)},
+	{USB_DEVICE(0x093a, 0x2470)},
+	{USB_DEVICE(0x093a, 0x2471)},
+	{USB_DEVICE(0x093a, 0x2472)},
+	{USB_DEVICE(0x093a, 0x2474)},
+	{USB_DEVICE(0x093a, 0x2476)},
+	{USB_DEVICE(0x145f, 0x013a)},
+	{USB_DEVICE(0x2001, 0xf115)},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/pac7302.c b/drivers/media/usb/gspca/pac7302.c
new file mode 100644
index 0000000..b8ff201
--- /dev/null
+++ b/drivers/media/usb/gspca/pac7302.c
@@ -0,0 +1,961 @@
+/*
+ * Pixart PAC7302 driver
+ *
+ * Copyright (C) 2008-2012 Jean-Francois Moine <http://moinejf.free.fr>
+ * Copyright (C) 2005 Thomas Kaiser thomas@kaiser-linux.li
+ *
+ * Separated from Pixart PAC7311 library by Márton Németh
+ * Camera button input handling by Márton Németh <nm127@freemail.hu>
+ * Copyright (C) 2009-2010 Márton Németh <nm127@freemail.hu>
+ *
+ * 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
+ * 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.
+ */
+
+/*
+ * Some documentation about various registers as determined by trial and error.
+ *
+ * Register page 0:
+ *
+ * Address	Description
+ * 0x01		Red balance control
+ * 0x02		Green balance control
+ * 0x03		Blue balance control
+ *		     The Windows driver uses a quadratic approach to map
+ *		     the settable values (0-200) on register values:
+ *		     min=0x20, default=0x40, max=0x80
+ * 0x0f-0x20	Color and saturation control
+ * 0xa2-0xab	Brightness, contrast and gamma control
+ * 0xb6		Sharpness control (bits 0-4)
+ *
+ * Register page 1:
+ *
+ * Address	Description
+ * 0x78		Global control, bit 6 controls the LED (inverted)
+ * 0x80		Compression balance, 2 interesting settings:
+ *		0x0f Default
+ *		0x50 Values >= this switch the camera to a lower compression,
+ *		     using the same table for both luminance and chrominance.
+ *		     This gives a sharper picture. Only usable when running
+ *		     at < 15 fps! Note currently the driver does not use this
+ *		     as the quality gain is small and the generated JPG-s are
+ *		     only understood by v4l-utils >= 0.8.9
+ *
+ * Register page 3:
+ *
+ * Address	Description
+ * 0x02		Clock divider 3-63, fps = 90 / val. Must be a multiple of 3 on
+ *		the 7302, so one of 3, 6, 9, ..., except when between 6 and 12?
+ * 0x03		Variable framerate ctrl reg2==3: 0 -> ~30 fps, 255 -> ~22fps
+ * 0x04		Another var framerate ctrl reg2==3, reg3==0: 0 -> ~30 fps,
+ *		63 -> ~27 fps, the 2 msb's must always be 1 !!
+ * 0x05		Another var framerate ctrl reg2==3, reg3==0, reg4==0xc0:
+ *		1 -> ~30 fps, 2 -> ~20 fps
+ * 0x0e		Exposure bits 0-7, 0-448, 0 = use full frame time
+ * 0x0f		Exposure bit 8, 0-448, 448 = no exposure at all
+ * 0x10		Gain 0-31
+ * 0x12		Another gain 0-31, unlike 0x10 this one seems to start with an
+ *		amplification value of 1 rather then 0 at its lowest setting
+ * 0x21		Bitfield: 0-1 unused, 2-3 vflip/hflip, 4-5 unknown, 6-7 unused
+ * 0x80		Another framerate control, best left at 1, moving it from 1 to
+ *		2 causes the framerate to become 3/4th of what it was, and
+ *		also seems to cause pixel averaging, resulting in an effective
+ *		resolution of 320x240 and thus a much blockier image
+ *
+ * The registers are accessed in the following functions:
+ *
+ * Page | Register   | Function
+ * -----+------------+---------------------------------------------------
+ *  0   | 0x01       | setredbalance()
+ *  0   | 0x03       | setbluebalance()
+ *  0   | 0x0f..0x20 | setcolors()
+ *  0   | 0xa2..0xab | setbrightcont()
+ *  0   | 0xb6       | setsharpness()
+ *  0   | 0xc6       | setwhitebalance()
+ *  0   | 0xdc       | setbrightcont(), setcolors()
+ *  3   | 0x02       | setexposure()
+ *  3   | 0x10, 0x12 | setgain()
+ *  3   | 0x11       | setcolors(), setgain(), setexposure(), sethvflip()
+ *  3   | 0x21       | sethvflip()
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/input.h>
+#include "gspca.h"
+/* Include pac common sof detection functions */
+#include "pac_common.h"
+
+#define PAC7302_RGB_BALANCE_MIN		  0
+#define PAC7302_RGB_BALANCE_MAX		200
+#define PAC7302_RGB_BALANCE_DEFAULT	100
+#define PAC7302_GAIN_DEFAULT		 15
+#define PAC7302_GAIN_KNEE		 42
+#define PAC7302_EXPOSURE_DEFAULT	 66 /* 33 ms / 30 fps */
+#define PAC7302_EXPOSURE_KNEE		133 /* 66 ms / 15 fps */
+
+MODULE_AUTHOR("Jean-Francois Moine <http://moinejf.free.fr>, Thomas Kaiser thomas@kaiser-linux.li");
+MODULE_DESCRIPTION("Pixart PAC7302");
+MODULE_LICENSE("GPL");
+
+struct sd {
+	struct gspca_dev gspca_dev;		/* !! must be the first item */
+
+	struct { /* brightness / contrast cluster */
+		struct v4l2_ctrl *brightness;
+		struct v4l2_ctrl *contrast;
+	};
+	struct v4l2_ctrl *saturation;
+	struct v4l2_ctrl *white_balance;
+	struct v4l2_ctrl *red_balance;
+	struct v4l2_ctrl *blue_balance;
+	struct { /* flip cluster */
+		struct v4l2_ctrl *hflip;
+		struct v4l2_ctrl *vflip;
+	};
+	struct v4l2_ctrl *sharpness;
+	u8 flags;
+#define FL_HFLIP 0x01		/* mirrored by default */
+#define FL_VFLIP 0x02		/* vertical flipped by default */
+
+	u8 sof_read;
+	s8 autogain_ignore_frames;
+
+	atomic_t avg_lum;
+};
+
+static const struct v4l2_pix_format vga_mode[] = {
+	{640, 480, V4L2_PIX_FMT_PJPG, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+	},
+};
+
+#define LOAD_PAGE3		255
+#define END_OF_SEQUENCE		0
+
+static const u8 init_7302[] = {
+/*	index,value */
+	0xff, 0x01,		/* page 1 */
+	0x78, 0x00,		/* deactivate */
+	0xff, 0x01,
+	0x78, 0x40,		/* led off */
+};
+static const u8 start_7302[] = {
+/*	index, len, [value]* */
+	0xff, 1,	0x00,		/* page 0 */
+	0x00, 12,	0x01, 0x40, 0x40, 0x40, 0x01, 0xe0, 0x02, 0x80,
+			0x00, 0x00, 0x00, 0x00,
+	0x0d, 24,	0x03, 0x01, 0x00, 0xb5, 0x07, 0xcb, 0x00, 0x00,
+			0x07, 0xc8, 0x00, 0xea, 0x07, 0xcf, 0x07, 0xf7,
+			0x07, 0x7e, 0x01, 0x0b, 0x00, 0x00, 0x00, 0x11,
+	0x26, 2,	0xaa, 0xaa,
+	0x2e, 1,	0x31,
+	0x38, 1,	0x01,
+	0x3a, 3,	0x14, 0xff, 0x5a,
+	0x43, 11,	0x00, 0x0a, 0x18, 0x11, 0x01, 0x2c, 0x88, 0x11,
+			0x00, 0x54, 0x11,
+	0x55, 1,	0x00,
+	0x62, 4,	0x10, 0x1e, 0x1e, 0x18,
+	0x6b, 1,	0x00,
+	0x6e, 3,	0x08, 0x06, 0x00,
+	0x72, 3,	0x00, 0xff, 0x00,
+	0x7d, 23,	0x01, 0x01, 0x58, 0x46, 0x50, 0x3c, 0x50, 0x3c,
+			0x54, 0x46, 0x54, 0x56, 0x52, 0x50, 0x52, 0x50,
+			0x56, 0x64, 0xa4, 0x00, 0xda, 0x00, 0x00,
+	0xa2, 10,	0x22, 0x2c, 0x3c, 0x54, 0x69, 0x7c, 0x9c, 0xb9,
+			0xd2, 0xeb,
+	0xaf, 1,	0x02,
+	0xb5, 2,	0x08, 0x08,
+	0xb8, 2,	0x08, 0x88,
+	0xc4, 4,	0xae, 0x01, 0x04, 0x01,
+	0xcc, 1,	0x00,
+	0xd1, 11,	0x01, 0x30, 0x49, 0x5e, 0x6f, 0x7f, 0x8e, 0xa9,
+			0xc1, 0xd7, 0xec,
+	0xdc, 1,	0x01,
+	0xff, 1,	0x01,		/* page 1 */
+	0x12, 3,	0x02, 0x00, 0x01,
+	0x3e, 2,	0x00, 0x00,
+	0x76, 5,	0x01, 0x20, 0x40, 0x00, 0xf2,
+	0x7c, 1,	0x00,
+	0x7f, 10,	0x4b, 0x0f, 0x01, 0x2c, 0x02, 0x58, 0x03, 0x20,
+			0x02, 0x00,
+	0x96, 5,	0x01, 0x10, 0x04, 0x01, 0x04,
+	0xc8, 14,	0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00,
+			0x07, 0x00, 0x01, 0x07, 0x04, 0x01,
+	0xd8, 1,	0x01,
+	0xdb, 2,	0x00, 0x01,
+	0xde, 7,	0x00, 0x01, 0x04, 0x04, 0x00, 0x00, 0x00,
+	0xe6, 4,	0x00, 0x00, 0x00, 0x01,
+	0xeb, 1,	0x00,
+	0xff, 1,	0x02,		/* page 2 */
+	0x22, 1,	0x00,
+	0xff, 1,	0x03,		/* page 3 */
+	0, LOAD_PAGE3,			/* load the page 3 */
+	0x11, 1,	0x01,
+	0xff, 1,	0x02,		/* page 2 */
+	0x13, 1,	0x00,
+	0x22, 4,	0x1f, 0xa4, 0xf0, 0x96,
+	0x27, 2,	0x14, 0x0c,
+	0x2a, 5,	0xc8, 0x00, 0x18, 0x12, 0x22,
+	0x64, 8,	0x00, 0x00, 0xf0, 0x01, 0x14, 0x44, 0x44, 0x44,
+	0x6e, 1,	0x08,
+	0xff, 1,	0x01,		/* page 1 */
+	0x78, 1,	0x00,
+	0, END_OF_SEQUENCE		/* end of sequence */
+};
+
+#define SKIP		0xaa
+/* page 3 - the value SKIP says skip the index - see reg_w_page() */
+static const u8 page3_7302[] = {
+	0x90, 0x40, 0x03, 0x00, 0xc0, 0x01, 0x14, 0x16,
+	0x14, 0x12, 0x00, 0x00, 0x00, 0x02, 0x33, 0x00,
+	0x0f, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x47, 0x01, 0xb3, 0x01, 0x00,
+	0x00, 0x08, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x21,
+	0x00, 0x00, 0x00, 0x54, 0xf4, 0x02, 0x52, 0x54,
+	0xa4, 0xb8, 0xe0, 0x2a, 0xf6, 0x00, 0x00, 0x00,
+	0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0xfc, 0x00, 0xf2, 0x1f, 0x04, 0x00, 0x00,
+	SKIP, 0x00, 0x00, 0xc0, 0xc0, 0x10, 0x00, 0x00,
+	0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x40, 0xff, 0x03, 0x19, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0xc8, 0xc8, 0xc8,
+	0xc8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50,
+	0x08, 0x10, 0x24, 0x40, 0x00, 0x00, 0x00, 0x00,
+	0x01, 0x00, 0x02, 0x47, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x02, 0xfa, 0x00, 0x64, 0x5a, 0x28, 0x00,
+	0x00
+};
+
+static void reg_w_buf(struct gspca_dev *gspca_dev,
+		u8 index,
+		  const u8 *buffer, int len)
+{
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	memcpy(gspca_dev->usb_buf, buffer, len);
+	ret = usb_control_msg(gspca_dev->dev,
+			usb_sndctrlpipe(gspca_dev->dev, 0),
+			0,		/* request */
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0,		/* value */
+			index, gspca_dev->usb_buf, len,
+			500);
+	if (ret < 0) {
+		pr_err("reg_w_buf failed i: %02x error %d\n",
+		       index, ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+
+static void reg_w(struct gspca_dev *gspca_dev,
+		u8 index,
+		u8 value)
+{
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	gspca_dev->usb_buf[0] = value;
+	ret = usb_control_msg(gspca_dev->dev,
+			usb_sndctrlpipe(gspca_dev->dev, 0),
+			0,			/* request */
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0, index, gspca_dev->usb_buf, 1,
+			500);
+	if (ret < 0) {
+		pr_err("reg_w() failed i: %02x v: %02x error %d\n",
+		       index, value, ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+static void reg_w_seq(struct gspca_dev *gspca_dev,
+		const u8 *seq, int len)
+{
+	while (--len >= 0) {
+		reg_w(gspca_dev, seq[0], seq[1]);
+		seq += 2;
+	}
+}
+
+/* load the beginning of a page */
+static void reg_w_page(struct gspca_dev *gspca_dev,
+			const u8 *page, int len)
+{
+	int index;
+	int ret = 0;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	for (index = 0; index < len; index++) {
+		if (page[index] == SKIP)		/* skip this index */
+			continue;
+		gspca_dev->usb_buf[0] = page[index];
+		ret = usb_control_msg(gspca_dev->dev,
+				usb_sndctrlpipe(gspca_dev->dev, 0),
+				0,			/* request */
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+				0, index, gspca_dev->usb_buf, 1,
+				500);
+		if (ret < 0) {
+			pr_err("reg_w_page() failed i: %02x v: %02x error %d\n",
+			       index, page[index], ret);
+			gspca_dev->usb_err = ret;
+			break;
+		}
+	}
+}
+
+/* output a variable sequence */
+static void reg_w_var(struct gspca_dev *gspca_dev,
+			const u8 *seq,
+			const u8 *page3, unsigned int page3_len)
+{
+	int index, len;
+
+	for (;;) {
+		index = *seq++;
+		len = *seq++;
+		switch (len) {
+		case END_OF_SEQUENCE:
+			return;
+		case LOAD_PAGE3:
+			reg_w_page(gspca_dev, page3, page3_len);
+			break;
+		default:
+			if (len > USB_BUF_SZ) {
+				gspca_err(gspca_dev, "Incorrect variable sequence\n");
+				return;
+			}
+			while (len > 0) {
+				if (len < 8) {
+					reg_w_buf(gspca_dev,
+						index, seq, len);
+					seq += len;
+					break;
+				}
+				reg_w_buf(gspca_dev, index, seq, 8);
+				seq += 8;
+				index += 8;
+				len -= 8;
+			}
+		}
+	}
+	/* not reached */
+}
+
+/* this function is called at probe time for pac7302 */
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam;
+
+	cam = &gspca_dev->cam;
+
+	cam->cam_mode = vga_mode;	/* only 640x480 */
+	cam->nmodes = ARRAY_SIZE(vga_mode);
+
+	sd->flags = id->driver_info;
+	return 0;
+}
+
+static void setbrightcont(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int i, v;
+	static const u8 max[10] =
+		{0x29, 0x33, 0x42, 0x5a, 0x6e, 0x80, 0x9f, 0xbb,
+		 0xd4, 0xec};
+	static const u8 delta[10] =
+		{0x35, 0x33, 0x33, 0x2f, 0x2a, 0x25, 0x1e, 0x17,
+		 0x11, 0x0b};
+
+	reg_w(gspca_dev, 0xff, 0x00);		/* page 0 */
+	for (i = 0; i < 10; i++) {
+		v = max[i];
+		v += (sd->brightness->val - (s32)sd->brightness->maximum)
+			* 150 / (s32)sd->brightness->maximum; /* 200 ? */
+		v -= delta[i] * sd->contrast->val / (s32)sd->contrast->maximum;
+		if (v < 0)
+			v = 0;
+		else if (v > 0xff)
+			v = 0xff;
+		reg_w(gspca_dev, 0xa2 + i, v);
+	}
+	reg_w(gspca_dev, 0xdc, 0x01);
+}
+
+static void setcolors(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int i, v;
+	static const int a[9] =
+		{217, -212, 0, -101, 170, -67, -38, -315, 355};
+	static const int b[9] =
+		{19, 106, 0, 19, 106, 1, 19, 106, 1};
+
+	reg_w(gspca_dev, 0xff, 0x03);			/* page 3 */
+	reg_w(gspca_dev, 0x11, 0x01);
+	reg_w(gspca_dev, 0xff, 0x00);			/* page 0 */
+	for (i = 0; i < 9; i++) {
+		v = a[i] * sd->saturation->val / (s32)sd->saturation->maximum;
+		v += b[i];
+		reg_w(gspca_dev, 0x0f + 2 * i, (v >> 8) & 0x07);
+		reg_w(gspca_dev, 0x0f + 2 * i + 1, v);
+	}
+	reg_w(gspca_dev, 0xdc, 0x01);
+}
+
+static void setwhitebalance(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	reg_w(gspca_dev, 0xff, 0x00);		/* page 0 */
+	reg_w(gspca_dev, 0xc6, sd->white_balance->val);
+
+	reg_w(gspca_dev, 0xdc, 0x01);
+}
+
+static u8 rgbbalance_ctrl_to_reg_value(s32 rgb_ctrl_val)
+{
+	const unsigned int k = 1000;	/* precision factor */
+	unsigned int norm;
+
+	/* Normed value [0...k] */
+	norm = k * (rgb_ctrl_val - PAC7302_RGB_BALANCE_MIN)
+		    / (PAC7302_RGB_BALANCE_MAX - PAC7302_RGB_BALANCE_MIN);
+	/* Qudratic apporach improves control at small (register) values: */
+	return 64 * norm * norm / (k*k)  +  32 * norm / k  +  32;
+	/* Y = 64*X*X + 32*X + 32
+	 * => register values 0x20-0x80; Windows driver uses these limits */
+
+	/* NOTE: for full value range (0x00-0xff) use
+	 *         Y = 254*X*X + X
+	 *         => 254 * norm * norm / (k*k)  +  1 * norm / k	*/
+}
+
+static void setredbalance(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	reg_w(gspca_dev, 0xff, 0x00);			/* page 0 */
+	reg_w(gspca_dev, 0x01,
+	      rgbbalance_ctrl_to_reg_value(sd->red_balance->val));
+
+	reg_w(gspca_dev, 0xdc, 0x01);
+}
+
+static void setbluebalance(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	reg_w(gspca_dev, 0xff, 0x00);			/* page 0 */
+	reg_w(gspca_dev, 0x03,
+	      rgbbalance_ctrl_to_reg_value(sd->blue_balance->val));
+
+	reg_w(gspca_dev, 0xdc, 0x01);
+}
+
+static void setgain(struct gspca_dev *gspca_dev)
+{
+	u8 reg10, reg12;
+
+	if (gspca_dev->gain->val < 32) {
+		reg10 = gspca_dev->gain->val;
+		reg12 = 0;
+	} else {
+		reg10 = 31;
+		reg12 = gspca_dev->gain->val - 31;
+	}
+
+	reg_w(gspca_dev, 0xff, 0x03);			/* page 3 */
+	reg_w(gspca_dev, 0x10, reg10);
+	reg_w(gspca_dev, 0x12, reg12);
+
+	/* load registers to sensor (Bit 0, auto clear) */
+	reg_w(gspca_dev, 0x11, 0x01);
+}
+
+static void setexposure(struct gspca_dev *gspca_dev)
+{
+	u8 clockdiv;
+	u16 exposure;
+
+	/*
+	 * Register 2 of frame 3 contains the clock divider configuring the
+	 * no fps according to the formula: 90 / reg. sd->exposure is the
+	 * desired exposure time in 0.5 ms.
+	 */
+	clockdiv = (90 * gspca_dev->exposure->val + 1999) / 2000;
+
+	/*
+	 * Note clockdiv = 3 also works, but when running at 30 fps, depending
+	 * on the scene being recorded, the camera switches to another
+	 * quantization table for certain JPEG blocks, and we don't know how
+	 * to decompress these blocks. So we cap the framerate at 15 fps.
+	 */
+	if (clockdiv < 6)
+		clockdiv = 6;
+	else if (clockdiv > 63)
+		clockdiv = 63;
+
+	/*
+	 * Register 2 MUST be a multiple of 3, except when between 6 and 12?
+	 * Always round up, otherwise we cannot get the desired frametime
+	 * using the partial frame time exposure control.
+	 */
+	if (clockdiv < 6 || clockdiv > 12)
+		clockdiv = ((clockdiv + 2) / 3) * 3;
+
+	/*
+	 * frame exposure time in ms = 1000 * clockdiv / 90    ->
+	 * exposure = (sd->exposure / 2) * 448 / (1000 * clockdiv / 90)
+	 */
+	exposure = (gspca_dev->exposure->val * 45 * 448) / (1000 * clockdiv);
+	/* 0 = use full frametime, 448 = no exposure, reverse it */
+	exposure = 448 - exposure;
+
+	reg_w(gspca_dev, 0xff, 0x03);			/* page 3 */
+	reg_w(gspca_dev, 0x02, clockdiv);
+	reg_w(gspca_dev, 0x0e, exposure & 0xff);
+	reg_w(gspca_dev, 0x0f, exposure >> 8);
+
+	/* load registers to sensor (Bit 0, auto clear) */
+	reg_w(gspca_dev, 0x11, 0x01);
+}
+
+static void sethvflip(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 data, hflip, vflip;
+
+	hflip = sd->hflip->val;
+	if (sd->flags & FL_HFLIP)
+		hflip = !hflip;
+	vflip = sd->vflip->val;
+	if (sd->flags & FL_VFLIP)
+		vflip = !vflip;
+
+	reg_w(gspca_dev, 0xff, 0x03);			/* page 3 */
+	data = (hflip ? 0x08 : 0x00) | (vflip ? 0x04 : 0x00);
+	reg_w(gspca_dev, 0x21, data);
+
+	/* load registers to sensor (Bit 0, auto clear) */
+	reg_w(gspca_dev, 0x11, 0x01);
+}
+
+static void setsharpness(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	reg_w(gspca_dev, 0xff, 0x00);		/* page 0 */
+	reg_w(gspca_dev, 0xb6, sd->sharpness->val);
+
+	reg_w(gspca_dev, 0xdc, 0x01);
+}
+
+/* this function is called at probe and resume time for pac7302 */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	reg_w_seq(gspca_dev, init_7302, sizeof(init_7302)/2);
+	return gspca_dev->usb_err;
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *)gspca_dev;
+
+	gspca_dev->usb_err = 0;
+
+	if (ctrl->id == V4L2_CID_AUTOGAIN && ctrl->is_new && ctrl->val) {
+		/* when switching to autogain set defaults to make sure
+		   we are on a valid point of the autogain gain /
+		   exposure knee graph, and give this change time to
+		   take effect before doing autogain. */
+		gspca_dev->exposure->val    = PAC7302_EXPOSURE_DEFAULT;
+		gspca_dev->gain->val        = PAC7302_GAIN_DEFAULT;
+		sd->autogain_ignore_frames  = PAC_AUTOGAIN_IGNORE_FRAMES;
+	}
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		setbrightcont(gspca_dev);
+		break;
+	case V4L2_CID_SATURATION:
+		setcolors(gspca_dev);
+		break;
+	case V4L2_CID_WHITE_BALANCE_TEMPERATURE:
+		setwhitebalance(gspca_dev);
+		break;
+	case V4L2_CID_RED_BALANCE:
+		setredbalance(gspca_dev);
+		break;
+	case V4L2_CID_BLUE_BALANCE:
+		setbluebalance(gspca_dev);
+		break;
+	case V4L2_CID_AUTOGAIN:
+		if (gspca_dev->exposure->is_new || (ctrl->is_new && ctrl->val))
+			setexposure(gspca_dev);
+		if (gspca_dev->gain->is_new || (ctrl->is_new && ctrl->val))
+			setgain(gspca_dev);
+		break;
+	case V4L2_CID_HFLIP:
+		sethvflip(gspca_dev);
+		break;
+	case V4L2_CID_SHARPNESS:
+		setsharpness(gspca_dev);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+/* this function is called at probe time */
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 12);
+
+	sd->brightness = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+					V4L2_CID_BRIGHTNESS, 0, 32, 1, 16);
+	sd->contrast = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+					V4L2_CID_CONTRAST, 0, 255, 1, 127);
+
+	sd->saturation = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+					V4L2_CID_SATURATION, 0, 255, 1, 127);
+	sd->white_balance = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+					V4L2_CID_WHITE_BALANCE_TEMPERATURE,
+					0, 255, 1, 55);
+	sd->red_balance = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+					V4L2_CID_RED_BALANCE,
+					PAC7302_RGB_BALANCE_MIN,
+					PAC7302_RGB_BALANCE_MAX,
+					1, PAC7302_RGB_BALANCE_DEFAULT);
+	sd->blue_balance = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+					V4L2_CID_BLUE_BALANCE,
+					PAC7302_RGB_BALANCE_MIN,
+					PAC7302_RGB_BALANCE_MAX,
+					1, PAC7302_RGB_BALANCE_DEFAULT);
+
+	gspca_dev->autogain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+					V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
+	gspca_dev->exposure = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+					V4L2_CID_EXPOSURE, 0, 1023, 1,
+					PAC7302_EXPOSURE_DEFAULT);
+	gspca_dev->gain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+					V4L2_CID_GAIN, 0, 62, 1,
+					PAC7302_GAIN_DEFAULT);
+
+	sd->hflip = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+		V4L2_CID_HFLIP, 0, 1, 1, 0);
+	sd->vflip = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+		V4L2_CID_VFLIP, 0, 1, 1, 0);
+
+	sd->sharpness = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+					V4L2_CID_SHARPNESS, 0, 15, 1, 8);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+
+	v4l2_ctrl_cluster(2, &sd->brightness);
+	v4l2_ctrl_auto_cluster(3, &gspca_dev->autogain, 0, false);
+	v4l2_ctrl_cluster(2, &sd->hflip);
+	return 0;
+}
+
+/* -- start the camera -- */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	reg_w_var(gspca_dev, start_7302,
+		page3_7302, sizeof(page3_7302));
+
+	sd->sof_read = 0;
+	sd->autogain_ignore_frames = 0;
+	atomic_set(&sd->avg_lum, 270 + sd->brightness->val);
+
+	/* start stream */
+	reg_w(gspca_dev, 0xff, 0x01);
+	reg_w(gspca_dev, 0x78, 0x01);
+
+	return gspca_dev->usb_err;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+
+	/* stop stream */
+	reg_w(gspca_dev, 0xff, 0x01);
+	reg_w(gspca_dev, 0x78, 0x00);
+}
+
+/* called on streamoff with alt 0 and on disconnect for pac7302 */
+static void sd_stop0(struct gspca_dev *gspca_dev)
+{
+	if (!gspca_dev->present)
+		return;
+	reg_w(gspca_dev, 0xff, 0x01);
+	reg_w(gspca_dev, 0x78, 0x40);
+}
+
+static void do_autogain(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int avg_lum = atomic_read(&sd->avg_lum);
+	int desired_lum;
+	const int deadzone = 30;
+
+	if (sd->autogain_ignore_frames < 0)
+		return;
+
+	if (sd->autogain_ignore_frames > 0) {
+		sd->autogain_ignore_frames--;
+	} else {
+		desired_lum = 270 + sd->brightness->val;
+
+		if (gspca_expo_autogain(gspca_dev, avg_lum, desired_lum,
+					deadzone, PAC7302_GAIN_KNEE,
+					PAC7302_EXPOSURE_KNEE))
+			sd->autogain_ignore_frames =
+						PAC_AUTOGAIN_IGNORE_FRAMES;
+	}
+}
+
+/* JPEG header */
+static const u8 jpeg_header[] = {
+	0xff, 0xd8,	/* SOI: Start of Image */
+
+	0xff, 0xc0,	/* SOF0: Start of Frame (Baseline DCT) */
+	0x00, 0x11,	/* length = 17 bytes (including this length field) */
+	0x08,		/* Precision: 8 */
+	0x02, 0x80,	/* height = 640 (image rotated) */
+	0x01, 0xe0,	/* width = 480 */
+	0x03,		/* Number of image components: 3 */
+	0x01, 0x21, 0x00, /* ID=1, Subsampling 1x1, Quantization table: 0 */
+	0x02, 0x11, 0x01, /* ID=2, Subsampling 2x1, Quantization table: 1 */
+	0x03, 0x11, 0x01, /* ID=3, Subsampling 2x1, Quantization table: 1 */
+
+	0xff, 0xda,	/* SOS: Start Of Scan */
+	0x00, 0x0c,	/* length = 12 bytes (including this length field) */
+	0x03,		/* number of components: 3 */
+	0x01, 0x00,	/* selector 1, table 0x00 */
+	0x02, 0x11,	/* selector 2, table 0x11 */
+	0x03, 0x11,	/* selector 3, table 0x11 */
+	0x00, 0x3f,	/* Spectral selection: 0 .. 63 */
+	0x00		/* Successive approximation: 0 */
+};
+
+/* this function is run at interrupt level */
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 *image;
+	u8 *sof;
+
+	sof = pac_find_sof(gspca_dev, &sd->sof_read, data, len);
+	if (sof) {
+		int n, lum_offset, footer_length;
+
+		/*
+		 * 6 bytes after the FF D9 EOF marker a number of lumination
+		 * bytes are send corresponding to different parts of the
+		 * image, the 14th and 15th byte after the EOF seem to
+		 * correspond to the center of the image.
+		 */
+		lum_offset = 61 + sizeof pac_sof_marker;
+		footer_length = 74;
+
+		/* Finish decoding current frame */
+		n = (sof - data) - (footer_length + sizeof pac_sof_marker);
+		if (n < 0) {
+			gspca_dev->image_len += n;
+			n = 0;
+		} else {
+			gspca_frame_add(gspca_dev, INTER_PACKET, data, n);
+		}
+
+		image = gspca_dev->image;
+		if (image != NULL
+		 && image[gspca_dev->image_len - 2] == 0xff
+		 && image[gspca_dev->image_len - 1] == 0xd9)
+			gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
+
+		n = sof - data;
+		len -= n;
+		data = sof;
+
+		/* Get average lumination */
+		if (gspca_dev->last_packet_type == LAST_PACKET &&
+				n >= lum_offset)
+			atomic_set(&sd->avg_lum, data[-lum_offset] +
+						data[-lum_offset + 1]);
+
+		/* Start the new frame with the jpeg header */
+		/* The PAC7302 has the image rotated 90 degrees */
+		gspca_frame_add(gspca_dev, FIRST_PACKET,
+				jpeg_header, sizeof jpeg_header);
+	}
+	gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int sd_dbg_s_register(struct gspca_dev *gspca_dev,
+			const struct v4l2_dbg_register *reg)
+{
+	u8 index;
+	u8 value;
+
+	/*
+	 * reg->reg: bit0..15: reserved for register index (wIndex is 16bit
+	 *		       long on the USB bus)
+	 */
+	if (reg->match.addr == 0 &&
+	    (reg->reg < 0x000000ff) &&
+	    (reg->val <= 0x000000ff)
+	) {
+		/* Currently writing to page 0 is only supported. */
+		/* reg_w() only supports 8bit index */
+		index = reg->reg;
+		value = reg->val;
+
+		/*
+		 * Note that there shall be no access to other page
+		 * by any other function between the page switch and
+		 * the actual register write.
+		 */
+		reg_w(gspca_dev, 0xff, 0x00);		/* page 0 */
+		reg_w(gspca_dev, index, value);
+
+		reg_w(gspca_dev, 0xdc, 0x01);
+	}
+	return gspca_dev->usb_err;
+}
+#endif
+
+#if IS_ENABLED(CONFIG_INPUT)
+static int sd_int_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,		/* interrupt packet data */
+			int len)		/* interrupt packet length */
+{
+	int ret = -EINVAL;
+	u8 data0, data1;
+
+	if (len == 2) {
+		data0 = data[0];
+		data1 = data[1];
+		if ((data0 == 0x00 && data1 == 0x11) ||
+		    (data0 == 0x22 && data1 == 0x33) ||
+		    (data0 == 0x44 && data1 == 0x55) ||
+		    (data0 == 0x66 && data1 == 0x77) ||
+		    (data0 == 0x88 && data1 == 0x99) ||
+		    (data0 == 0xaa && data1 == 0xbb) ||
+		    (data0 == 0xcc && data1 == 0xdd) ||
+		    (data0 == 0xee && data1 == 0xff)) {
+			input_report_key(gspca_dev->input_dev, KEY_CAMERA, 1);
+			input_sync(gspca_dev->input_dev);
+			input_report_key(gspca_dev->input_dev, KEY_CAMERA, 0);
+			input_sync(gspca_dev->input_dev);
+			ret = 0;
+		}
+	}
+
+	return ret;
+}
+#endif
+
+/* sub-driver description for pac7302 */
+static const struct sd_desc sd_desc = {
+	.name = KBUILD_MODNAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.stop0 = sd_stop0,
+	.pkt_scan = sd_pkt_scan,
+	.dq_callback = do_autogain,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.set_register = sd_dbg_s_register,
+#endif
+#if IS_ENABLED(CONFIG_INPUT)
+	.int_pkt_scan = sd_int_pkt_scan,
+#endif
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x06f8, 0x3009)},
+	{USB_DEVICE(0x06f8, 0x301b)},
+	{USB_DEVICE(0x093a, 0x2620)},
+	{USB_DEVICE(0x093a, 0x2621)},
+	{USB_DEVICE(0x093a, 0x2622), .driver_info = FL_VFLIP},
+	{USB_DEVICE(0x093a, 0x2623), .driver_info = FL_VFLIP},
+	{USB_DEVICE(0x093a, 0x2624), .driver_info = FL_VFLIP},
+	{USB_DEVICE(0x093a, 0x2625)},
+	{USB_DEVICE(0x093a, 0x2626)},
+	{USB_DEVICE(0x093a, 0x2627), .driver_info = FL_VFLIP},
+	{USB_DEVICE(0x093a, 0x2628)},
+	{USB_DEVICE(0x093a, 0x2629), .driver_info = FL_VFLIP},
+	{USB_DEVICE(0x093a, 0x262a)},
+	{USB_DEVICE(0x093a, 0x262c)},
+	{USB_DEVICE(0x145f, 0x013c)},
+	{USB_DEVICE(0x1ae7, 0x2001)}, /* SpeedLink Snappy Mic SL-6825-SBK */
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = KBUILD_MODNAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/pac7311.c b/drivers/media/usb/gspca/pac7311.c
new file mode 100644
index 0000000..44db4f4
--- /dev/null
+++ b/drivers/media/usb/gspca/pac7311.c
@@ -0,0 +1,696 @@
+/*
+ *		Pixart PAC7311 library
+ *		Copyright (C) 2005 Thomas Kaiser thomas@kaiser-linux.li
+ *
+ * V4L2 by Jean-Francois Moine <http://moinejf.free.fr>
+ *
+ * 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
+ * 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.
+ */
+
+/* Some documentation about various registers as determined by trial and error.
+ *
+ * Register page 1:
+ *
+ * Address	Description
+ * 0x08		Unknown compressor related, must always be 8 except when not
+ *		in 640x480 resolution and page 4 reg 2 <= 3 then set it to 9 !
+ * 0x1b		Auto white balance related, bit 0 is AWB enable (inverted)
+ *		bits 345 seem to toggle per color gains on/off (inverted)
+ * 0x78		Global control, bit 6 controls the LED (inverted)
+ * 0x80		Compression balance, interesting settings:
+ *		0x01 Use this to allow the camera to switch to higher compr.
+ *		     on the fly. Needed to stay within bandwidth @ 640x480@30
+ *		0x1c From usb captures under Windows for 640x480
+ *		0x2a Values >= this switch the camera to a lower compression,
+ *		     using the same table for both luminance and chrominance.
+ *		     This gives a sharper picture. Usable only at 640x480@ <
+ *		     15 fps or 320x240 / 160x120. Note currently the driver
+ *		     does not use this as the quality gain is small and the
+ *		     generated JPG-s are only understood by v4l-utils >= 0.8.9
+ *		0x3f From usb captures under Windows for 320x240
+ *		0x69 From usb captures under Windows for 160x120
+ *
+ * Register page 4:
+ *
+ * Address	Description
+ * 0x02		Clock divider 2-63, fps =~ 60 / val. Must be a multiple of 3 on
+ *		the 7302, so one of 3, 6, 9, ..., except when between 6 and 12?
+ * 0x0f		Master gain 1-245, low value = high gain
+ * 0x10		Another gain 0-15, limited influence (1-2x gain I guess)
+ * 0x21		Bitfield: 0-1 unused, 2-3 vflip/hflip, 4-5 unknown, 6-7 unused
+ *		Note setting vflip disabled leads to a much lower image quality,
+ *		so we always vflip, and tell userspace to flip it back
+ * 0x27		Seems to toggle various gains on / off, Setting bit 7 seems to
+ *		completely disable the analog amplification block. Set to 0x68
+ *		for max gain, 0x14 for minimal gain.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "pac7311"
+
+#include <linux/input.h>
+#include "gspca.h"
+/* Include pac common sof detection functions */
+#include "pac_common.h"
+
+#define PAC7311_GAIN_DEFAULT     122
+#define PAC7311_EXPOSURE_DEFAULT   3 /* 20 fps, avoid using high compr. */
+
+MODULE_AUTHOR("Thomas Kaiser thomas@kaiser-linux.li");
+MODULE_DESCRIPTION("Pixart PAC7311");
+MODULE_LICENSE("GPL");
+
+struct sd {
+	struct gspca_dev gspca_dev;		/* !! must be the first item */
+
+	struct v4l2_ctrl *contrast;
+	struct v4l2_ctrl *hflip;
+
+	u8 sof_read;
+	u8 autogain_ignore_frames;
+
+	atomic_t avg_lum;
+};
+
+static const struct v4l2_pix_format vga_mode[] = {
+	{160, 120, V4L2_PIX_FMT_PJPG, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 160 * 120 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 2},
+	{320, 240, V4L2_PIX_FMT_PJPG, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 1},
+	{640, 480, V4L2_PIX_FMT_PJPG, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 0},
+};
+
+#define LOAD_PAGE4		254
+#define END_OF_SEQUENCE		0
+
+static const __u8 init_7311[] = {
+	0xff, 0x01,
+	0x78, 0x40,	/* Bit_0=start stream, Bit_6=LED */
+	0x78, 0x40,	/* Bit_0=start stream, Bit_6=LED */
+	0x78, 0x44,	/* Bit_0=start stream, Bit_6=LED */
+	0xff, 0x04,
+	0x27, 0x80,
+	0x28, 0xca,
+	0x29, 0x53,
+	0x2a, 0x0e,
+	0xff, 0x01,
+	0x3e, 0x20,
+};
+
+static const __u8 start_7311[] = {
+/*	index, len, [value]* */
+	0xff, 1,	0x01,		/* page 1 */
+	0x02, 43,	0x48, 0x0a, 0x40, 0x08, 0x00, 0x00, 0x08, 0x00,
+			0x06, 0xff, 0x11, 0xff, 0x5a, 0x30, 0x90, 0x4c,
+			0x00, 0x07, 0x00, 0x0a, 0x10, 0x00, 0xa0, 0x10,
+			0x02, 0x00, 0x00, 0x00, 0x00, 0x0b, 0x01, 0x00,
+			0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+			0x00, 0x00, 0x00,
+	0x3e, 42,	0x00, 0x00, 0x78, 0x52, 0x4a, 0x52, 0x78, 0x6e,
+			0x48, 0x46, 0x48, 0x6e, 0x5f, 0x49, 0x42, 0x49,
+			0x5f, 0x5f, 0x49, 0x42, 0x49, 0x5f, 0x6e, 0x48,
+			0x46, 0x48, 0x6e, 0x78, 0x52, 0x4a, 0x52, 0x78,
+			0x00, 0x00, 0x09, 0x1b, 0x34, 0x49, 0x5c, 0x9b,
+			0xd0, 0xff,
+	0x78, 6,	0x44, 0x00, 0xf2, 0x01, 0x01, 0x80,
+	0x7f, 18,	0x2a, 0x1c, 0x00, 0xc8, 0x02, 0x58, 0x03, 0x84,
+			0x12, 0x00, 0x1a, 0x04, 0x08, 0x0c, 0x10, 0x14,
+			0x18, 0x20,
+	0x96, 3,	0x01, 0x08, 0x04,
+	0xa0, 4,	0x44, 0x44, 0x44, 0x04,
+	0xf0, 13,	0x01, 0x00, 0x00, 0x00, 0x22, 0x00, 0x20, 0x00,
+			0x3f, 0x00, 0x0a, 0x01, 0x00,
+	0xff, 1,	0x04,		/* page 4 */
+	0, LOAD_PAGE4,			/* load the page 4 */
+	0x11, 1,	0x01,
+	0, END_OF_SEQUENCE		/* end of sequence */
+};
+
+#define SKIP		0xaa
+/* page 4 - the value SKIP says skip the index - see reg_w_page() */
+static const __u8 page4_7311[] = {
+	SKIP, SKIP, 0x04, 0x54, 0x07, 0x2b, 0x09, 0x0f,
+	0x09, 0x00, SKIP, SKIP, 0x07, 0x00, 0x00, 0x62,
+	0x08, SKIP, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x03, 0xa0, 0x01, 0xf4, SKIP,
+	SKIP, 0x00, 0x08, SKIP, 0x03, SKIP, 0x00, 0x68,
+	0xca, 0x10, 0x06, 0x78, 0x00, 0x00, 0x00, 0x00,
+	0x23, 0x28, 0x04, 0x11, 0x00, 0x00
+};
+
+static void reg_w_buf(struct gspca_dev *gspca_dev,
+		  __u8 index,
+		  const u8 *buffer, int len)
+{
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	memcpy(gspca_dev->usb_buf, buffer, len);
+	ret = usb_control_msg(gspca_dev->dev,
+			usb_sndctrlpipe(gspca_dev->dev, 0),
+			0,		/* request */
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0,		/* value */
+			index, gspca_dev->usb_buf, len,
+			500);
+	if (ret < 0) {
+		pr_err("reg_w_buf() failed index 0x%02x, error %d\n",
+		       index, ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+
+static void reg_w(struct gspca_dev *gspca_dev,
+		  __u8 index,
+		  __u8 value)
+{
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	gspca_dev->usb_buf[0] = value;
+	ret = usb_control_msg(gspca_dev->dev,
+			usb_sndctrlpipe(gspca_dev->dev, 0),
+			0,			/* request */
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0, index, gspca_dev->usb_buf, 1,
+			500);
+	if (ret < 0) {
+		pr_err("reg_w() failed index 0x%02x, value 0x%02x, error %d\n",
+		       index, value, ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+static void reg_w_seq(struct gspca_dev *gspca_dev,
+		const __u8 *seq, int len)
+{
+	while (--len >= 0) {
+		reg_w(gspca_dev, seq[0], seq[1]);
+		seq += 2;
+	}
+}
+
+/* load the beginning of a page */
+static void reg_w_page(struct gspca_dev *gspca_dev,
+			const __u8 *page, int len)
+{
+	int index;
+	int ret = 0;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	for (index = 0; index < len; index++) {
+		if (page[index] == SKIP)		/* skip this index */
+			continue;
+		gspca_dev->usb_buf[0] = page[index];
+		ret = usb_control_msg(gspca_dev->dev,
+				usb_sndctrlpipe(gspca_dev->dev, 0),
+				0,			/* request */
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+				0, index, gspca_dev->usb_buf, 1,
+				500);
+		if (ret < 0) {
+			pr_err("reg_w_page() failed index 0x%02x, value 0x%02x, error %d\n",
+			       index, page[index], ret);
+			gspca_dev->usb_err = ret;
+			break;
+		}
+	}
+}
+
+/* output a variable sequence */
+static void reg_w_var(struct gspca_dev *gspca_dev,
+			const __u8 *seq,
+			const __u8 *page4, unsigned int page4_len)
+{
+	int index, len;
+
+	for (;;) {
+		index = *seq++;
+		len = *seq++;
+		switch (len) {
+		case END_OF_SEQUENCE:
+			return;
+		case LOAD_PAGE4:
+			reg_w_page(gspca_dev, page4, page4_len);
+			break;
+		default:
+			if (len > USB_BUF_SZ) {
+				gspca_err(gspca_dev, "Incorrect variable sequence\n");
+				return;
+			}
+			while (len > 0) {
+				if (len < 8) {
+					reg_w_buf(gspca_dev,
+						index, seq, len);
+					seq += len;
+					break;
+				}
+				reg_w_buf(gspca_dev, index, seq, 8);
+				seq += 8;
+				index += 8;
+				len -= 8;
+			}
+		}
+	}
+	/* not reached */
+}
+
+/* this function is called at probe time for pac7311 */
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	struct cam *cam = &gspca_dev->cam;
+
+	cam->cam_mode = vga_mode;
+	cam->nmodes = ARRAY_SIZE(vga_mode);
+	cam->input_flags = V4L2_IN_ST_VFLIP;
+
+	return 0;
+}
+
+static void setcontrast(struct gspca_dev *gspca_dev, s32 val)
+{
+	reg_w(gspca_dev, 0xff, 0x04);
+	reg_w(gspca_dev, 0x10, val);
+	/* load registers to sensor (Bit 0, auto clear) */
+	reg_w(gspca_dev, 0x11, 0x01);
+}
+
+static void setgain(struct gspca_dev *gspca_dev, s32 val)
+{
+	reg_w(gspca_dev, 0xff, 0x04);			/* page 4 */
+	reg_w(gspca_dev, 0x0e, 0x00);
+	reg_w(gspca_dev, 0x0f, gspca_dev->gain->maximum - val + 1);
+
+	/* load registers to sensor (Bit 0, auto clear) */
+	reg_w(gspca_dev, 0x11, 0x01);
+}
+
+static void setexposure(struct gspca_dev *gspca_dev, s32 val)
+{
+	reg_w(gspca_dev, 0xff, 0x04);			/* page 4 */
+	reg_w(gspca_dev, 0x02, val);
+
+	/* load registers to sensor (Bit 0, auto clear) */
+	reg_w(gspca_dev, 0x11, 0x01);
+
+	/*
+	 * Page 1 register 8 must always be 0x08 except when not in
+	 *  640x480 mode and page 4 reg 2 <= 3 then it must be 9
+	 */
+	reg_w(gspca_dev, 0xff, 0x01);
+	if (gspca_dev->pixfmt.width != 640 && val <= 3)
+		reg_w(gspca_dev, 0x08, 0x09);
+	else
+		reg_w(gspca_dev, 0x08, 0x08);
+
+	/*
+	 * Page1 register 80 sets the compression balance, normally we
+	 * want / use 0x1c, but for 640x480@30fps we must allow the
+	 * camera to use higher compression or we may run out of
+	 * bandwidth.
+	 */
+	if (gspca_dev->pixfmt.width == 640 && val == 2)
+		reg_w(gspca_dev, 0x80, 0x01);
+	else
+		reg_w(gspca_dev, 0x80, 0x1c);
+
+	/* load registers to sensor (Bit 0, auto clear) */
+	reg_w(gspca_dev, 0x11, 0x01);
+}
+
+static void sethvflip(struct gspca_dev *gspca_dev, s32 hflip, s32 vflip)
+{
+	__u8 data;
+
+	reg_w(gspca_dev, 0xff, 0x04);			/* page 4 */
+	data = (hflip ? 0x04 : 0x00) |
+	       (vflip ? 0x08 : 0x00);
+	reg_w(gspca_dev, 0x21, data);
+
+	/* load registers to sensor (Bit 0, auto clear) */
+	reg_w(gspca_dev, 0x11, 0x01);
+}
+
+/* this function is called at probe and resume time for pac7311 */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	reg_w_seq(gspca_dev, init_7311, sizeof(init_7311)/2);
+	return gspca_dev->usb_err;
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *)gspca_dev;
+
+	gspca_dev->usb_err = 0;
+
+	if (ctrl->id == V4L2_CID_AUTOGAIN && ctrl->is_new && ctrl->val) {
+		/* when switching to autogain set defaults to make sure
+		   we are on a valid point of the autogain gain /
+		   exposure knee graph, and give this change time to
+		   take effect before doing autogain. */
+		gspca_dev->exposure->val    = PAC7311_EXPOSURE_DEFAULT;
+		gspca_dev->gain->val        = PAC7311_GAIN_DEFAULT;
+		sd->autogain_ignore_frames  = PAC_AUTOGAIN_IGNORE_FRAMES;
+	}
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_CONTRAST:
+		setcontrast(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_AUTOGAIN:
+		if (gspca_dev->exposure->is_new || (ctrl->is_new && ctrl->val))
+			setexposure(gspca_dev, gspca_dev->exposure->val);
+		if (gspca_dev->gain->is_new || (ctrl->is_new && ctrl->val))
+			setgain(gspca_dev, gspca_dev->gain->val);
+		break;
+	case V4L2_CID_HFLIP:
+		sethvflip(gspca_dev, sd->hflip->val, 1);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+/* this function is called at probe time */
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 5);
+
+	sd->contrast = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+					V4L2_CID_CONTRAST, 0, 15, 1, 7);
+	gspca_dev->autogain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+					V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
+	gspca_dev->exposure = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+					V4L2_CID_EXPOSURE, 2, 63, 1,
+					PAC7311_EXPOSURE_DEFAULT);
+	gspca_dev->gain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+					V4L2_CID_GAIN, 0, 244, 1,
+					PAC7311_GAIN_DEFAULT);
+	sd->hflip = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+		V4L2_CID_HFLIP, 0, 1, 1, 0);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+
+	v4l2_ctrl_auto_cluster(3, &gspca_dev->autogain, 0, false);
+	return 0;
+}
+
+/* -- start the camera -- */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	sd->sof_read = 0;
+
+	reg_w_var(gspca_dev, start_7311,
+		page4_7311, sizeof(page4_7311));
+	setcontrast(gspca_dev, v4l2_ctrl_g_ctrl(sd->contrast));
+	setgain(gspca_dev, v4l2_ctrl_g_ctrl(gspca_dev->gain));
+	setexposure(gspca_dev, v4l2_ctrl_g_ctrl(gspca_dev->exposure));
+	sethvflip(gspca_dev, v4l2_ctrl_g_ctrl(sd->hflip), 1);
+
+	/* set correct resolution */
+	switch (gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv) {
+	case 2:					/* 160x120 */
+		reg_w(gspca_dev, 0xff, 0x01);
+		reg_w(gspca_dev, 0x17, 0x20);
+		reg_w(gspca_dev, 0x87, 0x10);
+		break;
+	case 1:					/* 320x240 */
+		reg_w(gspca_dev, 0xff, 0x01);
+		reg_w(gspca_dev, 0x17, 0x30);
+		reg_w(gspca_dev, 0x87, 0x11);
+		break;
+	case 0:					/* 640x480 */
+		reg_w(gspca_dev, 0xff, 0x01);
+		reg_w(gspca_dev, 0x17, 0x00);
+		reg_w(gspca_dev, 0x87, 0x12);
+		break;
+	}
+
+	sd->sof_read = 0;
+	sd->autogain_ignore_frames = 0;
+	atomic_set(&sd->avg_lum, -1);
+
+	/* start stream */
+	reg_w(gspca_dev, 0xff, 0x01);
+	reg_w(gspca_dev, 0x78, 0x05);
+
+	return gspca_dev->usb_err;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	reg_w(gspca_dev, 0xff, 0x04);
+	reg_w(gspca_dev, 0x27, 0x80);
+	reg_w(gspca_dev, 0x28, 0xca);
+	reg_w(gspca_dev, 0x29, 0x53);
+	reg_w(gspca_dev, 0x2a, 0x0e);
+	reg_w(gspca_dev, 0xff, 0x01);
+	reg_w(gspca_dev, 0x3e, 0x20);
+	reg_w(gspca_dev, 0x78, 0x44); /* Bit_0=start stream, Bit_6=LED */
+	reg_w(gspca_dev, 0x78, 0x44); /* Bit_0=start stream, Bit_6=LED */
+	reg_w(gspca_dev, 0x78, 0x44); /* Bit_0=start stream, Bit_6=LED */
+}
+
+static void do_autogain(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int avg_lum = atomic_read(&sd->avg_lum);
+	int desired_lum, deadzone;
+
+	if (avg_lum == -1)
+		return;
+
+	desired_lum = 170;
+	deadzone = 20;
+
+	if (sd->autogain_ignore_frames > 0)
+		sd->autogain_ignore_frames--;
+	else if (gspca_coarse_grained_expo_autogain(gspca_dev, avg_lum,
+						    desired_lum, deadzone))
+		sd->autogain_ignore_frames = PAC_AUTOGAIN_IGNORE_FRAMES;
+}
+
+/* JPEG header, part 1 */
+static const unsigned char pac_jpeg_header1[] = {
+  0xff, 0xd8,		/* SOI: Start of Image */
+
+  0xff, 0xc0,		/* SOF0: Start of Frame (Baseline DCT) */
+  0x00, 0x11,		/* length = 17 bytes (including this length field) */
+  0x08			/* Precision: 8 */
+  /* 2 bytes is placed here: number of image lines */
+  /* 2 bytes is placed here: samples per line */
+};
+
+/* JPEG header, continued */
+static const unsigned char pac_jpeg_header2[] = {
+  0x03,			/* Number of image components: 3 */
+  0x01, 0x21, 0x00,	/* ID=1, Subsampling 1x1, Quantization table: 0 */
+  0x02, 0x11, 0x01,	/* ID=2, Subsampling 2x1, Quantization table: 1 */
+  0x03, 0x11, 0x01,	/* ID=3, Subsampling 2x1, Quantization table: 1 */
+
+  0xff, 0xda,		/* SOS: Start Of Scan */
+  0x00, 0x0c,		/* length = 12 bytes (including this length field) */
+  0x03,			/* number of components: 3 */
+  0x01, 0x00,		/* selector 1, table 0x00 */
+  0x02, 0x11,		/* selector 2, table 0x11 */
+  0x03, 0x11,		/* selector 3, table 0x11 */
+  0x00, 0x3f,		/* Spectral selection: 0 .. 63 */
+  0x00			/* Successive approximation: 0 */
+};
+
+static void pac_start_frame(struct gspca_dev *gspca_dev,
+		__u16 lines, __u16 samples_per_line)
+{
+	unsigned char tmpbuf[4];
+
+	gspca_frame_add(gspca_dev, FIRST_PACKET,
+		pac_jpeg_header1, sizeof(pac_jpeg_header1));
+
+	tmpbuf[0] = lines >> 8;
+	tmpbuf[1] = lines & 0xff;
+	tmpbuf[2] = samples_per_line >> 8;
+	tmpbuf[3] = samples_per_line & 0xff;
+
+	gspca_frame_add(gspca_dev, INTER_PACKET,
+		tmpbuf, sizeof(tmpbuf));
+	gspca_frame_add(gspca_dev, INTER_PACKET,
+		pac_jpeg_header2, sizeof(pac_jpeg_header2));
+}
+
+/* this function is run at interrupt level */
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 *image;
+	unsigned char *sof;
+
+	sof = pac_find_sof(gspca_dev, &sd->sof_read, data, len);
+	if (sof) {
+		int n, lum_offset, footer_length;
+
+		/*
+		 * 6 bytes after the FF D9 EOF marker a number of lumination
+		 * bytes are send corresponding to different parts of the
+		 * image, the 14th and 15th byte after the EOF seem to
+		 * correspond to the center of the image.
+		 */
+		lum_offset = 24 + sizeof pac_sof_marker;
+		footer_length = 26;
+
+		/* Finish decoding current frame */
+		n = (sof - data) - (footer_length + sizeof pac_sof_marker);
+		if (n < 0) {
+			gspca_dev->image_len += n;
+			n = 0;
+		} else {
+			gspca_frame_add(gspca_dev, INTER_PACKET, data, n);
+		}
+		image = gspca_dev->image;
+		if (image != NULL
+		 && image[gspca_dev->image_len - 2] == 0xff
+		 && image[gspca_dev->image_len - 1] == 0xd9)
+			gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
+
+		n = sof - data;
+		len -= n;
+		data = sof;
+
+		/* Get average lumination */
+		if (gspca_dev->last_packet_type == LAST_PACKET &&
+				n >= lum_offset)
+			atomic_set(&sd->avg_lum, data[-lum_offset] +
+						data[-lum_offset + 1]);
+		else
+			atomic_set(&sd->avg_lum, -1);
+
+		/* Start the new frame with the jpeg header */
+		pac_start_frame(gspca_dev,
+			gspca_dev->pixfmt.height, gspca_dev->pixfmt.width);
+	}
+	gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+}
+
+#if IS_ENABLED(CONFIG_INPUT)
+static int sd_int_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,		/* interrupt packet data */
+			int len)		/* interrupt packet length */
+{
+	int ret = -EINVAL;
+	u8 data0, data1;
+
+	if (len == 2) {
+		data0 = data[0];
+		data1 = data[1];
+		if ((data0 == 0x00 && data1 == 0x11) ||
+		    (data0 == 0x22 && data1 == 0x33) ||
+		    (data0 == 0x44 && data1 == 0x55) ||
+		    (data0 == 0x66 && data1 == 0x77) ||
+		    (data0 == 0x88 && data1 == 0x99) ||
+		    (data0 == 0xaa && data1 == 0xbb) ||
+		    (data0 == 0xcc && data1 == 0xdd) ||
+		    (data0 == 0xee && data1 == 0xff)) {
+			input_report_key(gspca_dev->input_dev, KEY_CAMERA, 1);
+			input_sync(gspca_dev->input_dev);
+			input_report_key(gspca_dev->input_dev, KEY_CAMERA, 0);
+			input_sync(gspca_dev->input_dev);
+			ret = 0;
+		}
+	}
+
+	return ret;
+}
+#endif
+
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.pkt_scan = sd_pkt_scan,
+	.dq_callback = do_autogain,
+#if IS_ENABLED(CONFIG_INPUT)
+	.int_pkt_scan = sd_int_pkt_scan,
+#endif
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x093a, 0x2600)},
+	{USB_DEVICE(0x093a, 0x2601)},
+	{USB_DEVICE(0x093a, 0x2603)},
+	{USB_DEVICE(0x093a, 0x2608)},
+	{USB_DEVICE(0x093a, 0x260e)},
+	{USB_DEVICE(0x093a, 0x260f)},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/pac_common.h b/drivers/media/usb/gspca/pac_common.h
new file mode 100644
index 0000000..31f2a42
--- /dev/null
+++ b/drivers/media/usb/gspca/pac_common.h
@@ -0,0 +1,129 @@
+/*
+ * Pixart PAC207BCA / PAC73xx common functions
+ *
+ * Copyright (C) 2008 Hans de Goede <j.w.r.degoede@hhs.nl>
+ * Copyright (C) 2005 Thomas Kaiser thomas@kaiser-linux.li
+ * Copyleft (C) 2005 Michel Xhaard mxhaard@magic.fr
+ *
+ * V4L2 by Jean-Francois Moine <http://moinejf.free.fr>
+ *
+ * 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.
+ *
+ */
+
+/* We calculate the autogain at the end of the transfer of a frame, at this
+   moment a frame with the old settings is being captured and transmitted. So
+   if we adjust the gain or exposure we must ignore atleast the next frame for
+   the new settings to come into effect before doing any other adjustments. */
+#define PAC_AUTOGAIN_IGNORE_FRAMES	2
+
+static const unsigned char pac_sof_marker[5] =
+		{ 0xff, 0xff, 0x00, 0xff, 0x96 };
+
+/*
+   The following state machine finds the SOF marker sequence
+   0xff, 0xff, 0x00, 0xff, 0x96 in a byte stream.
+
+	   +----------+
+	   | 0: START |<---------------\
+	   +----------+<-\             |
+	     |       \---/otherwise    |
+	     v 0xff                    |
+	   +----------+ otherwise      |
+	   |     1    |--------------->*
+	   |          |                ^
+	   +----------+                |
+	     |                         |
+	     v 0xff                    |
+	   +----------+<-\0xff         |
+	/->|          |--/             |
+	|  |     2    |--------------->*
+	|  |          | otherwise      ^
+	|  +----------+                |
+	|    |                         |
+	|    v 0x00                    |
+	|  +----------+                |
+	|  |     3    |                |
+	|  |          |--------------->*
+	|  +----------+ otherwise      ^
+	|    |                         |
+   0xff |    v 0xff                    |
+	|  +----------+                |
+	\--|     4    |                |
+	   |          |----------------/
+	   +----------+ otherwise
+	     |
+	     v 0x96
+	   +----------+
+	   |  FOUND   |
+	   +----------+
+*/
+
+static unsigned char *pac_find_sof(struct gspca_dev *gspca_dev, u8 *sof_read,
+					unsigned char *m, int len)
+{
+	int i;
+
+	/* Search for the SOF marker (fixed part) in the header */
+	for (i = 0; i < len; i++) {
+		switch (*sof_read) {
+		case 0:
+			if (m[i] == 0xff)
+				*sof_read = 1;
+			break;
+		case 1:
+			if (m[i] == 0xff)
+				*sof_read = 2;
+			else
+				*sof_read = 0;
+			break;
+		case 2:
+			switch (m[i]) {
+			case 0x00:
+				*sof_read = 3;
+				break;
+			case 0xff:
+				/* stay in this state */
+				break;
+			default:
+				*sof_read = 0;
+			}
+			break;
+		case 3:
+			if (m[i] == 0xff)
+				*sof_read = 4;
+			else
+				*sof_read = 0;
+			break;
+		case 4:
+			switch (m[i]) {
+			case 0x96:
+				/* Pattern found */
+				gspca_dbg(gspca_dev, D_FRAM,
+					  "SOF found, bytes to analyze: %u - Frame starts at byte #%u\n",
+					  len, i + 1);
+				*sof_read = 0;
+				return m + i + 1;
+				break;
+			case 0xff:
+				*sof_read = 2;
+				break;
+			default:
+				*sof_read = 0;
+			}
+			break;
+		default:
+			*sof_read = 0;
+		}
+	}
+
+	return NULL;
+}
diff --git a/drivers/media/usb/gspca/se401.c b/drivers/media/usb/gspca/se401.c
new file mode 100644
index 0000000..477da06
--- /dev/null
+++ b/drivers/media/usb/gspca/se401.c
@@ -0,0 +1,735 @@
+/*
+ * GSPCA Endpoints (formerly known as AOX) se401 USB Camera sub Driver
+ *
+ * Copyright (C) 2011 Hans de Goede <hdegoede@redhat.com>
+ *
+ * Based on the v4l1 se401 driver which is:
+ *
+ * Copyright (c) 2000 Jeroen B. Vreeken (pe1rxq@amsat.org)
+ *
+ * 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.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "se401"
+
+#define BULK_SIZE 4096
+#define PACKET_SIZE 1024
+#define READ_REQ_SIZE 64
+#define MAX_MODES ((READ_REQ_SIZE - 6) / 4)
+/* The se401 compression algorithm uses a fixed quant factor, which
+   can be configured by setting the high nibble of the SE401_OPERATINGMODE
+   feature. This needs to exactly match what is in libv4l! */
+#define SE401_QUANT_FACT 8
+
+#include <linux/input.h>
+#include <linux/slab.h>
+#include "gspca.h"
+#include "se401.h"
+
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_DESCRIPTION("Endpoints se401");
+MODULE_LICENSE("GPL");
+
+/* exposure change state machine states */
+enum {
+	EXPO_CHANGED,
+	EXPO_DROP_FRAME,
+	EXPO_NO_CHANGE,
+};
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+	struct { /* exposure/freq control cluster */
+		struct v4l2_ctrl *exposure;
+		struct v4l2_ctrl *freq;
+	};
+	bool has_brightness;
+	struct v4l2_pix_format fmts[MAX_MODES];
+	int pixels_read;
+	int packet_read;
+	u8 packet[PACKET_SIZE];
+	u8 restart_stream;
+	u8 button_state;
+	u8 resetlevel;
+	u8 resetlevel_frame_count;
+	int resetlevel_adjust_dir;
+	int expo_change_state;
+};
+
+
+static void se401_write_req(struct gspca_dev *gspca_dev, u16 req, u16 value,
+			    int silent)
+{
+	int err;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+
+	err = usb_control_msg(gspca_dev->dev,
+			      usb_sndctrlpipe(gspca_dev->dev, 0), req,
+			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      value, 0, NULL, 0, 1000);
+	if (err < 0) {
+		if (!silent)
+			pr_err("write req failed req %#04x val %#04x error %d\n",
+			       req, value, err);
+		gspca_dev->usb_err = err;
+	}
+}
+
+static void se401_read_req(struct gspca_dev *gspca_dev, u16 req, int silent)
+{
+	int err;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+
+	if (USB_BUF_SZ < READ_REQ_SIZE) {
+		pr_err("USB_BUF_SZ too small!!\n");
+		gspca_dev->usb_err = -ENOBUFS;
+		return;
+	}
+
+	err = usb_control_msg(gspca_dev->dev,
+			      usb_rcvctrlpipe(gspca_dev->dev, 0), req,
+			      USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      0, 0, gspca_dev->usb_buf, READ_REQ_SIZE, 1000);
+	if (err < 0) {
+		if (!silent)
+			pr_err("read req failed req %#04x error %d\n",
+			       req, err);
+		gspca_dev->usb_err = err;
+	}
+}
+
+static void se401_set_feature(struct gspca_dev *gspca_dev,
+			      u16 selector, u16 param)
+{
+	int err;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+
+	err = usb_control_msg(gspca_dev->dev,
+			      usb_sndctrlpipe(gspca_dev->dev, 0),
+			      SE401_REQ_SET_EXT_FEATURE,
+			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      param, selector, NULL, 0, 1000);
+	if (err < 0) {
+		pr_err("set feature failed sel %#04x param %#04x error %d\n",
+		       selector, param, err);
+		gspca_dev->usb_err = err;
+	}
+}
+
+static int se401_get_feature(struct gspca_dev *gspca_dev, u16 selector)
+{
+	int err;
+
+	if (gspca_dev->usb_err < 0)
+		return gspca_dev->usb_err;
+
+	if (USB_BUF_SZ < 2) {
+		pr_err("USB_BUF_SZ too small!!\n");
+		gspca_dev->usb_err = -ENOBUFS;
+		return gspca_dev->usb_err;
+	}
+
+	err = usb_control_msg(gspca_dev->dev,
+			      usb_rcvctrlpipe(gspca_dev->dev, 0),
+			      SE401_REQ_GET_EXT_FEATURE,
+			      USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      0, selector, gspca_dev->usb_buf, 2, 1000);
+	if (err < 0) {
+		pr_err("get feature failed sel %#04x error %d\n",
+		       selector, err);
+		gspca_dev->usb_err = err;
+		return err;
+	}
+	return gspca_dev->usb_buf[0] | (gspca_dev->usb_buf[1] << 8);
+}
+
+static void setbrightness(struct gspca_dev *gspca_dev, s32 val)
+{
+	/* HDG: this does not seem to do anything on my cam */
+	se401_write_req(gspca_dev, SE401_REQ_SET_BRT, val, 0);
+}
+
+static void setgain(struct gspca_dev *gspca_dev, s32 val)
+{
+	u16 gain = 63 - val;
+
+	/* red color gain */
+	se401_set_feature(gspca_dev, HV7131_REG_ARCG, gain);
+	/* green color gain */
+	se401_set_feature(gspca_dev, HV7131_REG_AGCG, gain);
+	/* blue color gain */
+	se401_set_feature(gspca_dev, HV7131_REG_ABCG, gain);
+}
+
+static void setexposure(struct gspca_dev *gspca_dev, s32 val, s32 freq)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int integration = val << 6;
+	u8 expose_h, expose_m, expose_l;
+
+	/* Do this before the set_feature calls, for proper timing wrt
+	   the interrupt driven pkt_scan. Note we may still race but that
+	   is not a big issue, the expo change state machine is merely for
+	   avoiding underexposed frames getting send out, if one sneaks
+	   through so be it */
+	sd->expo_change_state = EXPO_CHANGED;
+
+	if (freq == V4L2_CID_POWER_LINE_FREQUENCY_50HZ)
+		integration = integration - integration % 106667;
+	if (freq == V4L2_CID_POWER_LINE_FREQUENCY_60HZ)
+		integration = integration - integration % 88889;
+
+	expose_h = (integration >> 16);
+	expose_m = (integration >> 8);
+	expose_l = integration;
+
+	/* integration time low */
+	se401_set_feature(gspca_dev, HV7131_REG_TITL, expose_l);
+	/* integration time mid */
+	se401_set_feature(gspca_dev, HV7131_REG_TITM, expose_m);
+	/* integration time high */
+	se401_set_feature(gspca_dev, HV7131_REG_TITU, expose_h);
+}
+
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *)gspca_dev;
+	struct cam *cam = &gspca_dev->cam;
+	u8 *cd = gspca_dev->usb_buf;
+	int i, j, n;
+	int widths[MAX_MODES], heights[MAX_MODES];
+
+	/* Read the camera descriptor */
+	se401_read_req(gspca_dev, SE401_REQ_GET_CAMERA_DESCRIPTOR, 1);
+	if (gspca_dev->usb_err) {
+		/* Sometimes after being idle for a while the se401 won't
+		   respond and needs a good kicking  */
+		usb_reset_device(gspca_dev->dev);
+		gspca_dev->usb_err = 0;
+		se401_read_req(gspca_dev, SE401_REQ_GET_CAMERA_DESCRIPTOR, 0);
+	}
+
+	/* Some cameras start with their LED on */
+	se401_write_req(gspca_dev, SE401_REQ_LED_CONTROL, 0, 0);
+	if (gspca_dev->usb_err)
+		return gspca_dev->usb_err;
+
+	if (cd[1] != 0x41) {
+		pr_err("Wrong descriptor type\n");
+		return -ENODEV;
+	}
+
+	if (!(cd[2] & SE401_FORMAT_BAYER)) {
+		pr_err("Bayer format not supported!\n");
+		return -ENODEV;
+	}
+
+	if (cd[3])
+		pr_info("ExtraFeatures: %d\n", cd[3]);
+
+	n = cd[4] | (cd[5] << 8);
+	if (n > MAX_MODES) {
+		pr_err("Too many frame sizes\n");
+		return -ENODEV;
+	}
+
+	for (i = 0; i < n ; i++) {
+		widths[i] = cd[6 + i * 4 + 0] | (cd[6 + i * 4 + 1] << 8);
+		heights[i] = cd[6 + i * 4 + 2] | (cd[6 + i * 4 + 3] << 8);
+	}
+
+	for (i = 0; i < n ; i++) {
+		sd->fmts[i].width = widths[i];
+		sd->fmts[i].height = heights[i];
+		sd->fmts[i].field = V4L2_FIELD_NONE;
+		sd->fmts[i].colorspace = V4L2_COLORSPACE_SRGB;
+		sd->fmts[i].priv = 1;
+
+		/* janggu compression only works for 1/4th or 1/16th res */
+		for (j = 0; j < n; j++) {
+			if (widths[j] / 2 == widths[i] &&
+			    heights[j] / 2 == heights[i]) {
+				sd->fmts[i].priv = 2;
+				break;
+			}
+		}
+		/* 1/16th if available too is better then 1/4th, because
+		   we then use a larger area of the sensor */
+		for (j = 0; j < n; j++) {
+			if (widths[j] / 4 == widths[i] &&
+			    heights[j] / 4 == heights[i]) {
+				sd->fmts[i].priv = 4;
+				break;
+			}
+		}
+
+		if (sd->fmts[i].priv == 1) {
+			/* Not a 1/4th or 1/16th res, use bayer */
+			sd->fmts[i].pixelformat = V4L2_PIX_FMT_SBGGR8;
+			sd->fmts[i].bytesperline = widths[i];
+			sd->fmts[i].sizeimage = widths[i] * heights[i];
+			pr_info("Frame size: %dx%d bayer\n",
+				widths[i], heights[i]);
+		} else {
+			/* Found a match use janggu compression */
+			sd->fmts[i].pixelformat = V4L2_PIX_FMT_SE401;
+			sd->fmts[i].bytesperline = 0;
+			sd->fmts[i].sizeimage = widths[i] * heights[i] * 3;
+			pr_info("Frame size: %dx%d 1/%dth janggu\n",
+				widths[i], heights[i],
+				sd->fmts[i].priv * sd->fmts[i].priv);
+		}
+	}
+
+	cam->cam_mode = sd->fmts;
+	cam->nmodes = n;
+	cam->bulk = 1;
+	cam->bulk_size = BULK_SIZE;
+	cam->bulk_nurbs = 4;
+	sd->resetlevel = 0x2d; /* Set initial resetlevel */
+
+	/* See if the camera supports brightness */
+	se401_read_req(gspca_dev, SE401_REQ_GET_BRT, 1);
+	sd->has_brightness = !!gspca_dev->usb_err;
+	gspca_dev->usb_err = 0;
+
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	return 0;
+}
+
+/* function called at start time before URB creation */
+static int sd_isoc_init(struct gspca_dev *gspca_dev)
+{
+	gspca_dev->alt = 1;	/* Ignore the bogus isoc alt settings */
+
+	return gspca_dev->usb_err;
+}
+
+/* -- start the camera -- */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *)gspca_dev;
+	int mult = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].priv;
+	int mode = 0;
+
+	se401_write_req(gspca_dev, SE401_REQ_CAMERA_POWER, 1, 1);
+	if (gspca_dev->usb_err) {
+		/* Sometimes after being idle for a while the se401 won't
+		   respond and needs a good kicking  */
+		usb_reset_device(gspca_dev->dev);
+		gspca_dev->usb_err = 0;
+		se401_write_req(gspca_dev, SE401_REQ_CAMERA_POWER, 1, 0);
+	}
+	se401_write_req(gspca_dev, SE401_REQ_LED_CONTROL, 1, 0);
+
+	se401_set_feature(gspca_dev, HV7131_REG_MODE_B, 0x05);
+
+	/* set size + mode */
+	se401_write_req(gspca_dev, SE401_REQ_SET_WIDTH,
+			gspca_dev->pixfmt.width * mult, 0);
+	se401_write_req(gspca_dev, SE401_REQ_SET_HEIGHT,
+			gspca_dev->pixfmt.height * mult, 0);
+	/*
+	 * HDG: disabled this as it does not seem to do anything
+	 * se401_write_req(gspca_dev, SE401_REQ_SET_OUTPUT_MODE,
+	 *		   SE401_FORMAT_BAYER, 0);
+	 */
+
+	switch (mult) {
+	case 1: /* Raw bayer */
+		mode = 0x03; break;
+	case 2: /* 1/4th janggu */
+		mode = SE401_QUANT_FACT << 4; break;
+	case 4: /* 1/16th janggu */
+		mode = (SE401_QUANT_FACT << 4) | 0x02; break;
+	}
+	se401_set_feature(gspca_dev, SE401_OPERATINGMODE, mode);
+
+	se401_set_feature(gspca_dev, HV7131_REG_ARLV, sd->resetlevel);
+
+	sd->packet_read = 0;
+	sd->pixels_read = 0;
+	sd->restart_stream = 0;
+	sd->resetlevel_frame_count = 0;
+	sd->resetlevel_adjust_dir = 0;
+	sd->expo_change_state = EXPO_NO_CHANGE;
+
+	se401_write_req(gspca_dev, SE401_REQ_START_CONTINUOUS_CAPTURE, 0, 0);
+
+	return gspca_dev->usb_err;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	se401_write_req(gspca_dev, SE401_REQ_STOP_CONTINUOUS_CAPTURE, 0, 0);
+	se401_write_req(gspca_dev, SE401_REQ_LED_CONTROL, 0, 0);
+	se401_write_req(gspca_dev, SE401_REQ_CAMERA_POWER, 0, 0);
+}
+
+static void sd_dq_callback(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *)gspca_dev;
+	unsigned int ahrc, alrc;
+	int oldreset, adjust_dir;
+
+	/* Restart the stream if requested do so by pkt_scan */
+	if (sd->restart_stream) {
+		sd_stopN(gspca_dev);
+		sd_start(gspca_dev);
+		sd->restart_stream = 0;
+	}
+
+	/* Automatically adjust sensor reset level
+	   Hyundai have some really nice docs about this and other sensor
+	   related stuff on their homepage: www.hei.co.kr */
+	sd->resetlevel_frame_count++;
+	if (sd->resetlevel_frame_count < 20)
+		return;
+
+	/* For some reason this normally read-only register doesn't get reset
+	   to zero after reading them just once... */
+	se401_get_feature(gspca_dev, HV7131_REG_HIREFNOH);
+	se401_get_feature(gspca_dev, HV7131_REG_HIREFNOL);
+	se401_get_feature(gspca_dev, HV7131_REG_LOREFNOH);
+	se401_get_feature(gspca_dev, HV7131_REG_LOREFNOL);
+	ahrc = 256*se401_get_feature(gspca_dev, HV7131_REG_HIREFNOH) +
+	    se401_get_feature(gspca_dev, HV7131_REG_HIREFNOL);
+	alrc = 256*se401_get_feature(gspca_dev, HV7131_REG_LOREFNOH) +
+	    se401_get_feature(gspca_dev, HV7131_REG_LOREFNOL);
+
+	/* Not an exact science, but it seems to work pretty well... */
+	oldreset = sd->resetlevel;
+	if (alrc > 10) {
+		while (alrc >= 10 && sd->resetlevel < 63) {
+			sd->resetlevel++;
+			alrc /= 2;
+		}
+	} else if (ahrc > 20) {
+		while (ahrc >= 20 && sd->resetlevel > 0) {
+			sd->resetlevel--;
+			ahrc /= 2;
+		}
+	}
+	/* Detect ping-pong-ing and halve adjustment to avoid overshoot */
+	if (sd->resetlevel > oldreset)
+		adjust_dir = 1;
+	else
+		adjust_dir = -1;
+	if (sd->resetlevel_adjust_dir &&
+	    sd->resetlevel_adjust_dir != adjust_dir)
+		sd->resetlevel = oldreset + (sd->resetlevel - oldreset) / 2;
+
+	if (sd->resetlevel != oldreset) {
+		sd->resetlevel_adjust_dir = adjust_dir;
+		se401_set_feature(gspca_dev, HV7131_REG_ARLV, sd->resetlevel);
+	}
+
+	sd->resetlevel_frame_count = 0;
+}
+
+static void sd_complete_frame(struct gspca_dev *gspca_dev, u8 *data, int len)
+{
+	struct sd *sd = (struct sd *)gspca_dev;
+
+	switch (sd->expo_change_state) {
+	case EXPO_CHANGED:
+		/* The exposure was changed while this frame
+		   was being send, so this frame is ok */
+		sd->expo_change_state = EXPO_DROP_FRAME;
+		break;
+	case EXPO_DROP_FRAME:
+		/* The exposure was changed while this frame
+		   was being captured, drop it! */
+		gspca_dev->last_packet_type = DISCARD_PACKET;
+		sd->expo_change_state = EXPO_NO_CHANGE;
+		break;
+	case EXPO_NO_CHANGE:
+		break;
+	}
+	gspca_frame_add(gspca_dev, LAST_PACKET, data, len);
+}
+
+static void sd_pkt_scan_janggu(struct gspca_dev *gspca_dev, u8 *data, int len)
+{
+	struct sd *sd = (struct sd *)gspca_dev;
+	int imagesize = gspca_dev->pixfmt.width * gspca_dev->pixfmt.height;
+	int i, plen, bits, pixels, info, count;
+
+	if (sd->restart_stream)
+		return;
+
+	/* Sometimes a 1024 bytes garbage bulk packet is send between frames */
+	if (gspca_dev->last_packet_type == LAST_PACKET && len == 1024) {
+		gspca_dev->last_packet_type = DISCARD_PACKET;
+		return;
+	}
+
+	i = 0;
+	while (i < len) {
+		/* Read header if not already be present from prev bulk pkt */
+		if (sd->packet_read < 4) {
+			count = 4 - sd->packet_read;
+			if (count > len - i)
+				count = len - i;
+			memcpy(&sd->packet[sd->packet_read], &data[i], count);
+			sd->packet_read += count;
+			i += count;
+			if (sd->packet_read < 4)
+				break;
+		}
+		bits   = sd->packet[3] + (sd->packet[2] << 8);
+		pixels = sd->packet[1] + ((sd->packet[0] & 0x3f) << 8);
+		info   = (sd->packet[0] & 0xc0) >> 6;
+		plen   = ((bits + 47) >> 4) << 1;
+		/* Sanity checks */
+		if (plen > 1024) {
+			pr_err("invalid packet len %d restarting stream\n",
+			       plen);
+			goto error;
+		}
+		if (info == 3) {
+			pr_err("unknown frame info value restarting stream\n");
+			goto error;
+		}
+
+		/* Read (remainder of) packet contents */
+		count = plen - sd->packet_read;
+		if (count > len - i)
+			count = len - i;
+		memcpy(&sd->packet[sd->packet_read], &data[i], count);
+		sd->packet_read += count;
+		i += count;
+		if (sd->packet_read < plen)
+			break;
+
+		sd->pixels_read += pixels;
+		sd->packet_read = 0;
+
+		switch (info) {
+		case 0: /* Frame data */
+			gspca_frame_add(gspca_dev, INTER_PACKET, sd->packet,
+					plen);
+			break;
+		case 1: /* EOF */
+			if (sd->pixels_read != imagesize) {
+				pr_err("frame size %d expected %d\n",
+				       sd->pixels_read, imagesize);
+				goto error;
+			}
+			sd_complete_frame(gspca_dev, sd->packet, plen);
+			return; /* Discard the rest of the bulk packet !! */
+		case 2: /* SOF */
+			gspca_frame_add(gspca_dev, FIRST_PACKET, sd->packet,
+					plen);
+			sd->pixels_read = pixels;
+			break;
+		}
+	}
+	return;
+
+error:
+	sd->restart_stream = 1;
+	/* Give userspace a 0 bytes frame, so our dq callback gets
+	   called and it can restart the stream */
+	gspca_frame_add(gspca_dev, FIRST_PACKET, NULL, 0);
+	gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
+}
+
+static void sd_pkt_scan_bayer(struct gspca_dev *gspca_dev, u8 *data, int len)
+{
+	struct cam *cam = &gspca_dev->cam;
+	int imagesize = cam->cam_mode[gspca_dev->curr_mode].sizeimage;
+
+	if (gspca_dev->image_len == 0) {
+		gspca_frame_add(gspca_dev, FIRST_PACKET, data, len);
+		return;
+	}
+
+	if (gspca_dev->image_len + len >= imagesize) {
+		sd_complete_frame(gspca_dev, data, len);
+		return;
+	}
+
+	gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev, u8 *data, int len)
+{
+	int mult = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].priv;
+
+	if (len == 0)
+		return;
+
+	if (mult == 1) /* mult == 1 means raw bayer */
+		sd_pkt_scan_bayer(gspca_dev, data, len);
+	else
+		sd_pkt_scan_janggu(gspca_dev, data, len);
+}
+
+#if IS_ENABLED(CONFIG_INPUT)
+static int sd_int_pkt_scan(struct gspca_dev *gspca_dev, u8 *data, int len)
+{
+	struct sd *sd = (struct sd *)gspca_dev;
+	u8 state;
+
+	if (len != 2)
+		return -EINVAL;
+
+	switch (data[0]) {
+	case 0:
+	case 1:
+		state = data[0];
+		break;
+	default:
+		return -EINVAL;
+	}
+	if (sd->button_state != state) {
+		input_report_key(gspca_dev->input_dev, KEY_CAMERA, state);
+		input_sync(gspca_dev->input_dev);
+		sd->button_state = state;
+	}
+
+	return 0;
+}
+#endif
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *)gspca_dev;
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		setbrightness(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_GAIN:
+		setgain(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_EXPOSURE:
+		setexposure(gspca_dev, ctrl->val, sd->freq->val);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *)gspca_dev;
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 4);
+	if (sd->has_brightness)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0, 255, 1, 15);
+	/* max is really 63 but > 50 is not pretty */
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_GAIN, 0, 50, 1, 25);
+	sd->exposure = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_EXPOSURE, 0, 32767, 1, 15000);
+	sd->freq = v4l2_ctrl_new_std_menu(hdl, &sd_ctrl_ops,
+			V4L2_CID_POWER_LINE_FREQUENCY,
+			V4L2_CID_POWER_LINE_FREQUENCY_60HZ, 0, 0);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	v4l2_ctrl_cluster(2, &sd->exposure);
+	return 0;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.isoc_init = sd_isoc_init,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.dq_callback = sd_dq_callback,
+	.pkt_scan = sd_pkt_scan,
+#if IS_ENABLED(CONFIG_INPUT)
+	.int_pkt_scan = sd_int_pkt_scan,
+#endif
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x03e8, 0x0004)}, /* Endpoints/Aox SE401 */
+	{USB_DEVICE(0x0471, 0x030b)}, /* Philips PCVC665K */
+	{USB_DEVICE(0x047d, 0x5001)}, /* Kensington 67014 */
+	{USB_DEVICE(0x047d, 0x5002)}, /* Kensington 6701(5/7) */
+	{USB_DEVICE(0x047d, 0x5003)}, /* Kensington 67016 */
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static int sd_pre_reset(struct usb_interface *intf)
+{
+	return 0;
+}
+
+static int sd_post_reset(struct usb_interface *intf)
+{
+	return 0;
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+	.pre_reset = sd_pre_reset,
+	.post_reset = sd_post_reset,
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/se401.h b/drivers/media/usb/gspca/se401.h
new file mode 100644
index 0000000..7cc0728
--- /dev/null
+++ b/drivers/media/usb/gspca/se401.h
@@ -0,0 +1,86 @@
+/*
+ * GSPCA Endpoints (formerly known as AOX) se401 USB Camera sub Driver
+ *
+ * Copyright (C) 2011 Hans de Goede <hdegoede@redhat.com>
+ *
+ * Based on the v4l1 se401 driver which is:
+ *
+ * Copyright (c) 2000 Jeroen B. Vreeken (pe1rxq@amsat.org)
+ *
+ * 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.
+ *
+ */
+
+#define SE401_REQ_GET_CAMERA_DESCRIPTOR		0x06
+#define SE401_REQ_START_CONTINUOUS_CAPTURE	0x41
+#define SE401_REQ_STOP_CONTINUOUS_CAPTURE	0x42
+#define SE401_REQ_CAPTURE_FRAME			0x43
+#define SE401_REQ_GET_BRT			0x44
+#define SE401_REQ_SET_BRT			0x45
+#define SE401_REQ_GET_WIDTH			0x4c
+#define SE401_REQ_SET_WIDTH			0x4d
+#define SE401_REQ_GET_HEIGHT			0x4e
+#define SE401_REQ_SET_HEIGHT			0x4f
+#define SE401_REQ_GET_OUTPUT_MODE		0x50
+#define SE401_REQ_SET_OUTPUT_MODE		0x51
+#define SE401_REQ_GET_EXT_FEATURE		0x52
+#define SE401_REQ_SET_EXT_FEATURE		0x53
+#define SE401_REQ_CAMERA_POWER			0x56
+#define SE401_REQ_LED_CONTROL			0x57
+#define SE401_REQ_BIOS				0xff
+
+#define SE401_BIOS_READ				0x07
+
+#define SE401_FORMAT_BAYER	0x40
+
+/* Hyundai hv7131b registers
+   7121 and 7141 should be the same (haven't really checked...) */
+/* Mode registers: */
+#define HV7131_REG_MODE_A		0x00
+#define HV7131_REG_MODE_B		0x01
+#define HV7131_REG_MODE_C		0x02
+/* Frame registers: */
+#define HV7131_REG_FRSU		0x10
+#define HV7131_REG_FRSL		0x11
+#define HV7131_REG_FCSU		0x12
+#define HV7131_REG_FCSL		0x13
+#define HV7131_REG_FWHU		0x14
+#define HV7131_REG_FWHL		0x15
+#define HV7131_REG_FWWU		0x16
+#define HV7131_REG_FWWL		0x17
+/* Timing registers: */
+#define HV7131_REG_THBU		0x20
+#define HV7131_REG_THBL		0x21
+#define HV7131_REG_TVBU		0x22
+#define HV7131_REG_TVBL		0x23
+#define HV7131_REG_TITU		0x25
+#define HV7131_REG_TITM		0x26
+#define HV7131_REG_TITL		0x27
+#define HV7131_REG_TMCD		0x28
+/* Adjust Registers: */
+#define HV7131_REG_ARLV		0x30
+#define HV7131_REG_ARCG		0x31
+#define HV7131_REG_AGCG		0x32
+#define HV7131_REG_ABCG		0x33
+#define HV7131_REG_APBV		0x34
+#define HV7131_REG_ASLP		0x54
+/* Offset Registers: */
+#define HV7131_REG_OFSR		0x50
+#define HV7131_REG_OFSG		0x51
+#define HV7131_REG_OFSB		0x52
+/* REset level statistics registers: */
+#define HV7131_REG_LOREFNOH	0x57
+#define HV7131_REG_LOREFNOL	0x58
+#define HV7131_REG_HIREFNOH	0x59
+#define HV7131_REG_HIREFNOL	0x5a
+
+/* se401 registers */
+#define SE401_OPERATINGMODE	0x2000
diff --git a/drivers/media/usb/gspca/sn9c2028.c b/drivers/media/usb/gspca/sn9c2028.c
new file mode 100644
index 0000000..a1f7189
--- /dev/null
+++ b/drivers/media/usb/gspca/sn9c2028.c
@@ -0,0 +1,972 @@
+/*
+ * SN9C2028 library
+ *
+ * Copyright (C) 2009 Theodore Kilgore <kilgota@auburn.edu>
+ *
+ * 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
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "sn9c2028"
+
+#include "gspca.h"
+
+MODULE_AUTHOR("Theodore Kilgore");
+MODULE_DESCRIPTION("Sonix SN9C2028 USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;  /* !! must be the first item */
+	u8 sof_read;
+	u16 model;
+
+#define MIN_AVG_LUM 8500
+#define MAX_AVG_LUM 10000
+	int avg_lum;
+	u8 avg_lum_l;
+
+	struct { /* autogain and gain control cluster */
+		struct v4l2_ctrl *autogain;
+		struct v4l2_ctrl *gain;
+	};
+};
+
+struct init_command {
+	unsigned char instruction[6];
+	unsigned char to_read; /* length to read. 0 means no reply requested */
+};
+
+/* How to change the resolution of any of the VGA cams is unknown */
+static const struct v4l2_pix_format vga_mode[] = {
+	{640, 480, V4L2_PIX_FMT_SN9C2028, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 3 / 4,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+};
+
+/* No way to change the resolution of the CIF cams is known */
+static const struct v4l2_pix_format cif_mode[] = {
+	{352, 288, V4L2_PIX_FMT_SN9C2028, V4L2_FIELD_NONE,
+		.bytesperline = 352,
+		.sizeimage = 352 * 288 * 3 / 4,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+};
+
+/* the bytes to write are in gspca_dev->usb_buf */
+static int sn9c2028_command(struct gspca_dev *gspca_dev, u8 *command)
+{
+	int rc;
+
+	gspca_dbg(gspca_dev, D_USBO, "sending command %02x%02x%02x%02x%02x%02x\n",
+		  command[0], command[1], command[2],
+		  command[3], command[4], command[5]);
+
+	memcpy(gspca_dev->usb_buf, command, 6);
+	rc = usb_control_msg(gspca_dev->dev,
+			usb_sndctrlpipe(gspca_dev->dev, 0),
+			USB_REQ_GET_CONFIGURATION,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+			2, 0, gspca_dev->usb_buf, 6, 500);
+	if (rc < 0) {
+		pr_err("command write [%02x] error %d\n",
+		       gspca_dev->usb_buf[0], rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int sn9c2028_read1(struct gspca_dev *gspca_dev)
+{
+	int rc;
+
+	rc = usb_control_msg(gspca_dev->dev,
+			usb_rcvctrlpipe(gspca_dev->dev, 0),
+			USB_REQ_GET_STATUS,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+			1, 0, gspca_dev->usb_buf, 1, 500);
+	if (rc != 1) {
+		pr_err("read1 error %d\n", rc);
+		return (rc < 0) ? rc : -EIO;
+	}
+	gspca_dbg(gspca_dev, D_USBI, "read1 response %02x\n",
+		  gspca_dev->usb_buf[0]);
+	return gspca_dev->usb_buf[0];
+}
+
+static int sn9c2028_read4(struct gspca_dev *gspca_dev, u8 *reading)
+{
+	int rc;
+	rc = usb_control_msg(gspca_dev->dev,
+			usb_rcvctrlpipe(gspca_dev->dev, 0),
+			USB_REQ_GET_STATUS,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+			4, 0, gspca_dev->usb_buf, 4, 500);
+	if (rc != 4) {
+		pr_err("read4 error %d\n", rc);
+		return (rc < 0) ? rc : -EIO;
+	}
+	memcpy(reading, gspca_dev->usb_buf, 4);
+	gspca_dbg(gspca_dev, D_USBI, "read4 response %02x%02x%02x%02x\n",
+		  reading[0], reading[1], reading[2], reading[3]);
+	return rc;
+}
+
+static int sn9c2028_long_command(struct gspca_dev *gspca_dev, u8 *command)
+{
+	int i, status;
+	__u8 reading[4];
+
+	status = sn9c2028_command(gspca_dev, command);
+	if (status < 0)
+		return status;
+
+	status = -1;
+	for (i = 0; i < 256 && status < 2; i++)
+		status = sn9c2028_read1(gspca_dev);
+	if (status < 0) {
+		pr_err("long command status read error %d\n", status);
+		return status;
+	}
+
+	memset(reading, 0, 4);
+	status = sn9c2028_read4(gspca_dev, reading);
+	if (status < 0)
+		return status;
+
+	/* in general, the first byte of the response is the first byte of
+	 * the command, or'ed with 8 */
+	status = sn9c2028_read1(gspca_dev);
+	if (status < 0)
+		return status;
+
+	return 0;
+}
+
+static int sn9c2028_short_command(struct gspca_dev *gspca_dev, u8 *command)
+{
+	int err_code;
+
+	err_code = sn9c2028_command(gspca_dev, command);
+	if (err_code < 0)
+		return err_code;
+
+	err_code = sn9c2028_read1(gspca_dev);
+	if (err_code < 0)
+		return err_code;
+
+	return 0;
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+		     const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam = &gspca_dev->cam;
+
+	gspca_dbg(gspca_dev, D_PROBE, "SN9C2028 camera detected (vid/pid 0x%04X:0x%04X)\n",
+		  id->idVendor, id->idProduct);
+
+	sd->model = id->idProduct;
+
+	switch (sd->model) {
+	case 0x7005:
+		gspca_dbg(gspca_dev, D_PROBE, "Genius Smart 300 camera\n");
+		break;
+	case 0x7003:
+		gspca_dbg(gspca_dev, D_PROBE, "Genius Videocam Live v2\n");
+		break;
+	case 0x8000:
+		gspca_dbg(gspca_dev, D_PROBE, "DC31VC\n");
+		break;
+	case 0x8001:
+		gspca_dbg(gspca_dev, D_PROBE, "Spy camera\n");
+		break;
+	case 0x8003:
+		gspca_dbg(gspca_dev, D_PROBE, "CIF camera\n");
+		break;
+	case 0x8008:
+		gspca_dbg(gspca_dev, D_PROBE, "Mini-Shotz ms-350 camera\n");
+		break;
+	case 0x800a:
+		gspca_dbg(gspca_dev, D_PROBE, "Vivitar 3350b type camera\n");
+		cam->input_flags = V4L2_IN_ST_VFLIP | V4L2_IN_ST_HFLIP;
+		break;
+	}
+
+	switch (sd->model) {
+	case 0x8000:
+	case 0x8001:
+	case 0x8003:
+		cam->cam_mode = cif_mode;
+		cam->nmodes = ARRAY_SIZE(cif_mode);
+		break;
+	default:
+		cam->cam_mode = vga_mode;
+		cam->nmodes = ARRAY_SIZE(vga_mode);
+	}
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	int status = -1;
+
+	sn9c2028_read1(gspca_dev);
+	sn9c2028_read1(gspca_dev);
+	status = sn9c2028_read1(gspca_dev);
+
+	return (status < 0) ? status : 0;
+}
+
+static int run_start_commands(struct gspca_dev *gspca_dev,
+			      struct init_command *cam_commands, int n)
+{
+	int i, err_code = -1;
+
+	for (i = 0; i < n; i++) {
+		switch (cam_commands[i].to_read) {
+		case 4:
+			err_code = sn9c2028_long_command(gspca_dev,
+					cam_commands[i].instruction);
+			break;
+		case 1:
+			err_code = sn9c2028_short_command(gspca_dev,
+					cam_commands[i].instruction);
+			break;
+		case 0:
+			err_code = sn9c2028_command(gspca_dev,
+					cam_commands[i].instruction);
+			break;
+		}
+		if (err_code < 0)
+			return err_code;
+	}
+	return 0;
+}
+
+static void set_gain(struct gspca_dev *gspca_dev, s32 g)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	struct init_command genius_vcam_live_gain_cmds[] = {
+		{{0x1d, 0x25, 0x10 /* This byte is gain */,
+		  0x20, 0xab, 0x00}, 0},
+	};
+	if (!gspca_dev->streaming)
+		return;
+
+	switch (sd->model) {
+	case 0x7003:
+		genius_vcam_live_gain_cmds[0].instruction[2] = g;
+		run_start_commands(gspca_dev, genius_vcam_live_gain_cmds,
+				   ARRAY_SIZE(genius_vcam_live_gain_cmds));
+		break;
+	default:
+		break;
+	}
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *)gspca_dev;
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	/* standalone gain control */
+	case V4L2_CID_GAIN:
+		set_gain(gspca_dev, ctrl->val);
+		break;
+	/* autogain */
+	case V4L2_CID_AUTOGAIN:
+		set_gain(gspca_dev, sd->gain->val);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+	struct sd *sd = (struct sd *)gspca_dev;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 2);
+
+	switch (sd->model) {
+	case 0x7003:
+		sd->gain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_GAIN, 0, 20, 1, 0);
+		sd->autogain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+static int start_spy_cam(struct gspca_dev *gspca_dev)
+{
+	struct init_command spy_start_commands[] = {
+		{{0x0c, 0x01, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x20, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x21, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x22, 0x01, 0x04, 0x00, 0x00}, 4},
+		{{0x13, 0x23, 0x01, 0x03, 0x00, 0x00}, 4},
+		{{0x13, 0x24, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x25, 0x01, 0x16, 0x00, 0x00}, 4}, /* width  352 */
+		{{0x13, 0x26, 0x01, 0x12, 0x00, 0x00}, 4}, /* height 288 */
+		/* {{0x13, 0x27, 0x01, 0x28, 0x00, 0x00}, 4}, */
+		{{0x13, 0x27, 0x01, 0x68, 0x00, 0x00}, 4},
+		{{0x13, 0x28, 0x01, 0x09, 0x00, 0x00}, 4}, /* red gain ?*/
+		/* {{0x13, 0x28, 0x01, 0x00, 0x00, 0x00}, 4}, */
+		{{0x13, 0x29, 0x01, 0x00, 0x00, 0x00}, 4},
+		/* {{0x13, 0x29, 0x01, 0x0c, 0x00, 0x00}, 4}, */
+		{{0x13, 0x2a, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x2b, 0x01, 0x00, 0x00, 0x00}, 4},
+		/* {{0x13, 0x2c, 0x01, 0x02, 0x00, 0x00}, 4}, */
+		{{0x13, 0x2c, 0x01, 0x02, 0x00, 0x00}, 4},
+		{{0x13, 0x2d, 0x01, 0x02, 0x00, 0x00}, 4},
+		/* {{0x13, 0x2e, 0x01, 0x09, 0x00, 0x00}, 4}, */
+		{{0x13, 0x2e, 0x01, 0x09, 0x00, 0x00}, 4},
+		{{0x13, 0x2f, 0x01, 0x07, 0x00, 0x00}, 4},
+		{{0x12, 0x34, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x34, 0x01, 0xa1, 0x00, 0x00}, 4},
+		{{0x13, 0x35, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x02, 0x06, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x03, 0x13, 0x00, 0x00, 0x00}, 4}, /*don't mess with*/
+		/*{{0x11, 0x04, 0x06, 0x00, 0x00, 0x00}, 4}, observed */
+		{{0x11, 0x04, 0x00, 0x00, 0x00, 0x00}, 4}, /* brighter */
+		/*{{0x11, 0x05, 0x65, 0x00, 0x00, 0x00}, 4}, observed */
+		{{0x11, 0x05, 0x00, 0x00, 0x00, 0x00}, 4}, /* brighter */
+		{{0x11, 0x06, 0xb1, 0x00, 0x00, 0x00}, 4}, /* observed */
+		{{0x11, 0x07, 0x00, 0x00, 0x00, 0x00}, 4},
+		/*{{0x11, 0x08, 0x06, 0x00, 0x00, 0x00}, 4}, observed */
+		{{0x11, 0x08, 0x0b, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x09, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x0a, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x0b, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x0c, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x0d, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x0e, 0x04, 0x00, 0x00, 0x00}, 4},
+		/* {{0x11, 0x0f, 0x00, 0x00, 0x00, 0x00}, 4}, */
+		/* brightness or gain. 0 is default. 4 is good
+		 * indoors at night with incandescent lighting */
+		{{0x11, 0x0f, 0x04, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x10, 0x06, 0x00, 0x00, 0x00}, 4}, /*hstart or hoffs*/
+		{{0x11, 0x11, 0x06, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x12, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x14, 0x02, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x13, 0x01, 0x00, 0x00, 0x00}, 4},
+		/* {{0x1b, 0x02, 0x06, 0x00, 0x00, 0x00}, 1}, observed */
+		{{0x1b, 0x02, 0x11, 0x00, 0x00, 0x00}, 1}, /* brighter */
+		/* {{0x1b, 0x13, 0x01, 0x00, 0x00, 0x00}, 1}, observed */
+		{{0x1b, 0x13, 0x11, 0x00, 0x00, 0x00}, 1},
+		{{0x20, 0x34, 0xa1, 0x00, 0x00, 0x00}, 1}, /* compresses */
+		/* Camera should start to capture now. */
+	};
+
+	return run_start_commands(gspca_dev, spy_start_commands,
+				  ARRAY_SIZE(spy_start_commands));
+}
+
+static int start_cif_cam(struct gspca_dev *gspca_dev)
+{
+	struct init_command cif_start_commands[] = {
+		{{0x0c, 0x01, 0x00, 0x00, 0x00, 0x00}, 4},
+		/* The entire sequence below seems redundant */
+		/* {{0x13, 0x20, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x21, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x22, 0x01, 0x06, 0x00, 0x00}, 4},
+		{{0x13, 0x23, 0x01, 0x02, 0x00, 0x00}, 4},
+		{{0x13, 0x24, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x25, 0x01, 0x16, 0x00, 0x00}, 4}, width?
+		{{0x13, 0x26, 0x01, 0x12, 0x00, 0x00}, 4}, height?
+		{{0x13, 0x27, 0x01, 0x68, 0x00, 0x00}, 4}, subsample?
+		{{0x13, 0x28, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x29, 0x01, 0x20, 0x00, 0x00}, 4},
+		{{0x13, 0x2a, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x2b, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x2c, 0x01, 0x02, 0x00, 0x00}, 4},
+		{{0x13, 0x2d, 0x01, 0x03, 0x00, 0x00}, 4},
+		{{0x13, 0x2e, 0x01, 0x0f, 0x00, 0x00}, 4},
+		{{0x13, 0x2f, 0x01, 0x0c, 0x00, 0x00}, 4},
+		{{0x12, 0x34, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x34, 0x01, 0xa1, 0x00, 0x00}, 4},
+		{{0x13, 0x35, 0x01, 0x00, 0x00, 0x00}, 4},*/
+		{{0x1b, 0x21, 0x00, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x17, 0x00, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x19, 0x00, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x02, 0x06, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x03, 0x5a, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x04, 0x27, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x05, 0x01, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x12, 0x14, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x13, 0x00, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x14, 0x00, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x15, 0x00, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x16, 0x00, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x77, 0xa2, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x06, 0x0f, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x07, 0x14, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x08, 0x0f, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x09, 0x10, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x0e, 0x00, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x0f, 0x00, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x12, 0x07, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x10, 0x1f, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x11, 0x01, 0x00, 0x00, 0x00}, 1},
+		{{0x13, 0x25, 0x01, 0x16, 0x00, 0x00}, 1}, /* width/8 */
+		{{0x13, 0x26, 0x01, 0x12, 0x00, 0x00}, 1}, /* height/8 */
+		/* {{0x13, 0x27, 0x01, 0x68, 0x00, 0x00}, 4}, subsample?
+		 * {{0x13, 0x28, 0x01, 0x1e, 0x00, 0x00}, 4}, does nothing
+		 * {{0x13, 0x27, 0x01, 0x20, 0x00, 0x00}, 4}, */
+		/* {{0x13, 0x29, 0x01, 0x22, 0x00, 0x00}, 4},
+		 * causes subsampling
+		 * but not a change in the resolution setting! */
+		{{0x13, 0x2c, 0x01, 0x02, 0x00, 0x00}, 4},
+		{{0x13, 0x2d, 0x01, 0x01, 0x00, 0x00}, 4},
+		{{0x13, 0x2e, 0x01, 0x08, 0x00, 0x00}, 4},
+		{{0x13, 0x2f, 0x01, 0x06, 0x00, 0x00}, 4},
+		{{0x13, 0x28, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x1b, 0x04, 0x6d, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x05, 0x03, 0x00, 0x00, 0x00}, 1},
+		{{0x20, 0x36, 0x06, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x0e, 0x01, 0x00, 0x00, 0x00}, 1},
+		{{0x12, 0x27, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x1b, 0x0f, 0x00, 0x00, 0x00, 0x00}, 1},
+		{{0x20, 0x36, 0x05, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x10, 0x0f, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x02, 0x06, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x11, 0x01, 0x00, 0x00, 0x00}, 1},
+		{{0x20, 0x34, 0xa1, 0x00, 0x00, 0x00}, 1},/* use compression */
+		/* Camera should start to capture now. */
+	};
+
+	return run_start_commands(gspca_dev, cif_start_commands,
+				  ARRAY_SIZE(cif_start_commands));
+}
+
+static int start_ms350_cam(struct gspca_dev *gspca_dev)
+{
+	struct init_command ms350_start_commands[] = {
+		{{0x0c, 0x01, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x16, 0x01, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x20, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x21, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x22, 0x01, 0x04, 0x00, 0x00}, 4},
+		{{0x13, 0x23, 0x01, 0x03, 0x00, 0x00}, 4},
+		{{0x13, 0x24, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x25, 0x01, 0x16, 0x00, 0x00}, 4},
+		{{0x13, 0x26, 0x01, 0x12, 0x00, 0x00}, 4},
+		{{0x13, 0x27, 0x01, 0x28, 0x00, 0x00}, 4},
+		{{0x13, 0x28, 0x01, 0x09, 0x00, 0x00}, 4},
+		{{0x13, 0x29, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x2a, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x2b, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x2c, 0x01, 0x02, 0x00, 0x00}, 4},
+		{{0x13, 0x2d, 0x01, 0x03, 0x00, 0x00}, 4},
+		{{0x13, 0x2e, 0x01, 0x0f, 0x00, 0x00}, 4},
+		{{0x13, 0x2f, 0x01, 0x0c, 0x00, 0x00}, 4},
+		{{0x12, 0x34, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x34, 0x01, 0xa1, 0x00, 0x00}, 4},
+		{{0x13, 0x35, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x00, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x01, 0x70, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x02, 0x05, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x03, 0x5d, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x04, 0x07, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x05, 0x25, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x06, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x07, 0x09, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x08, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x09, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x0a, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x0b, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x0c, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x0d, 0x0c, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x0e, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x0f, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x10, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x11, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x12, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x13, 0x63, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x15, 0x70, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x18, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x11, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x25, 0x01, 0x28, 0x00, 0x00}, 4}, /* width  */
+		{{0x13, 0x26, 0x01, 0x1e, 0x00, 0x00}, 4}, /* height */
+		{{0x13, 0x28, 0x01, 0x09, 0x00, 0x00}, 4}, /* vstart? */
+		{{0x13, 0x27, 0x01, 0x28, 0x00, 0x00}, 4},
+		{{0x13, 0x29, 0x01, 0x40, 0x00, 0x00}, 4}, /* hstart? */
+		{{0x13, 0x2c, 0x01, 0x02, 0x00, 0x00}, 4},
+		{{0x13, 0x2d, 0x01, 0x03, 0x00, 0x00}, 4},
+		{{0x13, 0x2e, 0x01, 0x0f, 0x00, 0x00}, 4},
+		{{0x13, 0x2f, 0x01, 0x0c, 0x00, 0x00}, 4},
+		{{0x1b, 0x02, 0x05, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x11, 0x01, 0x00, 0x00, 0x00}, 1},
+		{{0x20, 0x18, 0x00, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x02, 0x0a, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x11, 0x01, 0x00, 0x00, 0x00}, 0},
+		/* Camera should start to capture now. */
+	};
+
+	return run_start_commands(gspca_dev, ms350_start_commands,
+				  ARRAY_SIZE(ms350_start_commands));
+}
+
+static int start_genius_cam(struct gspca_dev *gspca_dev)
+{
+	struct init_command genius_start_commands[] = {
+		{{0x0c, 0x01, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x16, 0x01, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x10, 0x00, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x25, 0x01, 0x16, 0x00, 0x00}, 4},
+		{{0x13, 0x26, 0x01, 0x12, 0x00, 0x00}, 4},
+		/* "preliminary" width and height settings */
+		{{0x13, 0x28, 0x01, 0x0e, 0x00, 0x00}, 4},
+		{{0x13, 0x27, 0x01, 0x20, 0x00, 0x00}, 4},
+		{{0x13, 0x29, 0x01, 0x22, 0x00, 0x00}, 4},
+		{{0x13, 0x2c, 0x01, 0x02, 0x00, 0x00}, 4},
+		{{0x13, 0x2d, 0x01, 0x02, 0x00, 0x00}, 4},
+		{{0x13, 0x2e, 0x01, 0x09, 0x00, 0x00}, 4},
+		{{0x13, 0x2f, 0x01, 0x07, 0x00, 0x00}, 4},
+		{{0x11, 0x20, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x21, 0x2d, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x22, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x23, 0x03, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x10, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x11, 0x64, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x12, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x13, 0x91, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x14, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x15, 0x20, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x16, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x17, 0x60, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x20, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x21, 0x2d, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x22, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x23, 0x03, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x25, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x26, 0x02, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x27, 0x88, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x30, 0x38, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x31, 0x2a, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x32, 0x2a, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x33, 0x2a, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x34, 0x02, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x5b, 0x0a, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x25, 0x01, 0x28, 0x00, 0x00}, 4}, /* real width */
+		{{0x13, 0x26, 0x01, 0x1e, 0x00, 0x00}, 4}, /* real height */
+		{{0x13, 0x28, 0x01, 0x0e, 0x00, 0x00}, 4},
+		{{0x13, 0x27, 0x01, 0x20, 0x00, 0x00}, 4},
+		{{0x13, 0x29, 0x01, 0x62, 0x00, 0x00}, 4},
+		{{0x13, 0x2c, 0x01, 0x02, 0x00, 0x00}, 4},
+		{{0x13, 0x2d, 0x01, 0x03, 0x00, 0x00}, 4},
+		{{0x13, 0x2e, 0x01, 0x0f, 0x00, 0x00}, 4},
+		{{0x13, 0x2f, 0x01, 0x0c, 0x00, 0x00}, 4},
+		{{0x11, 0x20, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x21, 0x2a, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x22, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x23, 0x28, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x10, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x11, 0x04, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x12, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x13, 0x03, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x14, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x15, 0xe0, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x16, 0x02, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x17, 0x80, 0x00, 0x00, 0x00}, 4},
+		{{0x1c, 0x20, 0x00, 0x2a, 0x00, 0x00}, 1},
+		{{0x1c, 0x20, 0x00, 0x2a, 0x00, 0x00}, 1},
+		{{0x20, 0x34, 0xa1, 0x00, 0x00, 0x00}, 0}
+		/* Camera should start to capture now. */
+	};
+
+	return run_start_commands(gspca_dev, genius_start_commands,
+				  ARRAY_SIZE(genius_start_commands));
+}
+
+static int start_genius_videocam_live(struct gspca_dev *gspca_dev)
+{
+	int r;
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct init_command genius_vcam_live_start_commands[] = {
+		{{0x0c, 0x01, 0x00, 0x00, 0x00, 0x00}, 0},
+		{{0x16, 0x01, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x10, 0x00, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x25, 0x01, 0x16, 0x00, 0x00}, 4},
+		{{0x13, 0x26, 0x01, 0x12, 0x00, 0x00}, 4},
+
+		{{0x13, 0x28, 0x01, 0x0e, 0x00, 0x00}, 4},
+		{{0x13, 0x27, 0x01, 0x20, 0x00, 0x00}, 4},
+		{{0x13, 0x29, 0x01, 0x22, 0x00, 0x00}, 4},
+		{{0x13, 0x2c, 0x01, 0x02, 0x00, 0x00}, 4},
+		{{0x13, 0x2d, 0x01, 0x02, 0x00, 0x00}, 4},
+		{{0x13, 0x2e, 0x01, 0x09, 0x00, 0x00}, 4},
+		{{0x13, 0x2f, 0x01, 0x07, 0x00, 0x00}, 4},
+		{{0x11, 0x20, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x21, 0x2d, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x22, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x23, 0x03, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x10, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x11, 0x64, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x12, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x13, 0x91, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x14, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x15, 0x20, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x16, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x17, 0x60, 0x00, 0x00, 0x00}, 4},
+		{{0x1c, 0x20, 0x00, 0x2d, 0x00, 0x00}, 4},
+		{{0x13, 0x20, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x21, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x22, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x23, 0x01, 0x01, 0x00, 0x00}, 4},
+		{{0x13, 0x24, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x25, 0x01, 0x16, 0x00, 0x00}, 4},
+		{{0x13, 0x26, 0x01, 0x12, 0x00, 0x00}, 4},
+		{{0x13, 0x27, 0x01, 0x20, 0x00, 0x00}, 4},
+		{{0x13, 0x28, 0x01, 0x0e, 0x00, 0x00}, 4},
+		{{0x13, 0x29, 0x01, 0x22, 0x00, 0x00}, 4},
+		{{0x13, 0x2a, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x2b, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x2c, 0x01, 0x02, 0x00, 0x00}, 4},
+		{{0x13, 0x2d, 0x01, 0x02, 0x00, 0x00}, 4},
+		{{0x13, 0x2e, 0x01, 0x09, 0x00, 0x00}, 4},
+		{{0x13, 0x2f, 0x01, 0x07, 0x00, 0x00}, 4},
+		{{0x12, 0x34, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x34, 0x01, 0xa1, 0x00, 0x00}, 4},
+		{{0x13, 0x35, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x01, 0x04, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x02, 0x92, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x10, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x11, 0x64, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x12, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x13, 0x91, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x14, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x15, 0x20, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x16, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x17, 0x60, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x20, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x21, 0x2d, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x22, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x23, 0x03, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x25, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x26, 0x02, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x27, 0x88, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x30, 0x38, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x31, 0x2a, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x32, 0x2a, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x33, 0x2a, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x34, 0x02, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x5b, 0x0a, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x25, 0x01, 0x28, 0x00, 0x00}, 4},
+		{{0x13, 0x26, 0x01, 0x1e, 0x00, 0x00}, 4},
+		{{0x13, 0x28, 0x01, 0x0e, 0x00, 0x00}, 4},
+		{{0x13, 0x27, 0x01, 0x20, 0x00, 0x00}, 4},
+		{{0x13, 0x29, 0x01, 0x62, 0x00, 0x00}, 4},
+		{{0x13, 0x2c, 0x01, 0x02, 0x00, 0x00}, 4},
+		{{0x13, 0x2d, 0x01, 0x03, 0x00, 0x00}, 4},
+		{{0x13, 0x2e, 0x01, 0x0f, 0x00, 0x00}, 4},
+		{{0x13, 0x2f, 0x01, 0x0c, 0x00, 0x00}, 4},
+		{{0x11, 0x20, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x21, 0x2a, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x22, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x23, 0x28, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x10, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x11, 0x04, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x12, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x13, 0x03, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x14, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x15, 0xe0, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x16, 0x02, 0x00, 0x00, 0x00}, 4},
+		{{0x11, 0x17, 0x80, 0x00, 0x00, 0x00}, 4},
+		{{0x1c, 0x20, 0x00, 0x2a, 0x00, 0x00}, 1},
+		{{0x20, 0x34, 0xa1, 0x00, 0x00, 0x00}, 0},
+		/* Camera should start to capture now. */
+		{{0x12, 0x27, 0x01, 0x00, 0x00, 0x00}, 0},
+		{{0x1b, 0x32, 0x26, 0x00, 0x00, 0x00}, 0},
+		{{0x1d, 0x25, 0x10, 0x20, 0xab, 0x00}, 0},
+	};
+
+	r = run_start_commands(gspca_dev, genius_vcam_live_start_commands,
+				  ARRAY_SIZE(genius_vcam_live_start_commands));
+	if (r < 0)
+		return r;
+
+	if (sd->gain)
+		set_gain(gspca_dev, v4l2_ctrl_g_ctrl(sd->gain));
+
+	return r;
+}
+
+static int start_vivitar_cam(struct gspca_dev *gspca_dev)
+{
+	struct init_command vivitar_start_commands[] = {
+		{{0x0c, 0x01, 0x00, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x20, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x21, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x22, 0x01, 0x01, 0x00, 0x00}, 4},
+		{{0x13, 0x23, 0x01, 0x01, 0x00, 0x00}, 4},
+		{{0x13, 0x24, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x25, 0x01, 0x28, 0x00, 0x00}, 4},
+		{{0x13, 0x26, 0x01, 0x1e, 0x00, 0x00}, 4},
+		{{0x13, 0x27, 0x01, 0x20, 0x00, 0x00}, 4},
+		{{0x13, 0x28, 0x01, 0x0a, 0x00, 0x00}, 4},
+		/*
+		 * Above is changed from OEM 0x0b. Fixes Bayer tiling.
+		 * Presumably gives a vertical shift of one row.
+		 */
+		{{0x13, 0x29, 0x01, 0x20, 0x00, 0x00}, 4},
+		/* Above seems to do horizontal shift. */
+		{{0x13, 0x2a, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x2b, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x2c, 0x01, 0x02, 0x00, 0x00}, 4},
+		{{0x13, 0x2d, 0x01, 0x03, 0x00, 0x00}, 4},
+		{{0x13, 0x2e, 0x01, 0x0f, 0x00, 0x00}, 4},
+		{{0x13, 0x2f, 0x01, 0x0c, 0x00, 0x00}, 4},
+		/* Above three commands seem to relate to brightness. */
+		{{0x12, 0x34, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x13, 0x34, 0x01, 0xa1, 0x00, 0x00}, 4},
+		{{0x13, 0x35, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x1b, 0x12, 0x80, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x01, 0x77, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x02, 0x3a, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x12, 0x78, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x13, 0x00, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x14, 0x80, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x15, 0x34, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x1b, 0x04, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x20, 0x44, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x23, 0xee, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x26, 0xa0, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x27, 0x9a, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x28, 0xa0, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x29, 0x30, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x2a, 0x80, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x2b, 0x00, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x2f, 0x3d, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x30, 0x24, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x32, 0x86, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x60, 0xa9, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x61, 0x42, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x65, 0x00, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x69, 0x38, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x6f, 0x88, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x70, 0x0b, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x71, 0x00, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x74, 0x21, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x75, 0x86, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x76, 0x00, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x7d, 0xf3, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x17, 0x1c, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x18, 0xc0, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x19, 0x05, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x1a, 0xf6, 0x00, 0x00, 0x00}, 1},
+		/* {{0x13, 0x25, 0x01, 0x28, 0x00, 0x00}, 4},
+		{{0x13, 0x26, 0x01, 0x1e, 0x00, 0x00}, 4},
+		{{0x13, 0x28, 0x01, 0x0b, 0x00, 0x00}, 4}, */
+		{{0x20, 0x36, 0x06, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x10, 0x26, 0x00, 0x00, 0x00}, 1},
+		{{0x12, 0x27, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x1b, 0x76, 0x03, 0x00, 0x00, 0x00}, 1},
+		{{0x20, 0x36, 0x05, 0x00, 0x00, 0x00}, 1},
+		{{0x1b, 0x00, 0x3f, 0x00, 0x00, 0x00}, 1},
+		/* Above is brightness; OEM driver setting is 0x10 */
+		{{0x12, 0x27, 0x01, 0x00, 0x00, 0x00}, 4},
+		{{0x20, 0x29, 0x30, 0x00, 0x00, 0x00}, 1},
+		{{0x20, 0x34, 0xa1, 0x00, 0x00, 0x00}, 1}
+	};
+
+	return run_start_commands(gspca_dev, vivitar_start_commands,
+				  ARRAY_SIZE(vivitar_start_commands));
+}
+
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int err_code;
+
+	sd->sof_read = 0;
+
+	switch (sd->model) {
+	case 0x7005:
+		err_code = start_genius_cam(gspca_dev);
+		break;
+	case 0x7003:
+		err_code = start_genius_videocam_live(gspca_dev);
+		break;
+	case 0x8001:
+		err_code = start_spy_cam(gspca_dev);
+		break;
+	case 0x8003:
+		err_code = start_cif_cam(gspca_dev);
+		break;
+	case 0x8008:
+		err_code = start_ms350_cam(gspca_dev);
+		break;
+	case 0x800a:
+		err_code = start_vivitar_cam(gspca_dev);
+		break;
+	default:
+		pr_err("Starting unknown camera, please report this\n");
+		return -ENXIO;
+	}
+
+	sd->avg_lum = -1;
+
+	return err_code;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	int result;
+	__u8 data[6];
+
+	result = sn9c2028_read1(gspca_dev);
+	if (result < 0)
+		gspca_err(gspca_dev, "Camera Stop read failed\n");
+
+	memset(data, 0, 6);
+	data[0] = 0x14;
+	result = sn9c2028_command(gspca_dev, data);
+	if (result < 0)
+		gspca_err(gspca_dev, "Camera Stop command failed\n");
+}
+
+static void do_autogain(struct gspca_dev *gspca_dev, int avg_lum)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	s32 cur_gain = v4l2_ctrl_g_ctrl(sd->gain);
+
+	if (avg_lum == -1)
+		return;
+
+	if (avg_lum < MIN_AVG_LUM) {
+		if (cur_gain == sd->gain->maximum)
+			return;
+		cur_gain++;
+		v4l2_ctrl_s_ctrl(sd->gain, cur_gain);
+	}
+	if (avg_lum > MAX_AVG_LUM) {
+		if (cur_gain == sd->gain->minimum)
+			return;
+		cur_gain--;
+		v4l2_ctrl_s_ctrl(sd->gain, cur_gain);
+	}
+
+}
+
+static void sd_dqcallback(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->autogain == NULL || !v4l2_ctrl_g_ctrl(sd->autogain))
+		return;
+
+	do_autogain(gspca_dev, sd->avg_lum);
+}
+
+/* Include sn9c2028 sof detection functions */
+#include "sn9c2028.h"
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			__u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	unsigned char *sof;
+
+	sof = sn9c2028_find_sof(gspca_dev, data, len);
+	if (sof) {
+		int n;
+
+		/* finish decoding current frame */
+		n = sof - data;
+		if (n > sizeof sn9c2028_sof_marker)
+			n -= sizeof sn9c2028_sof_marker;
+		else
+			n = 0;
+		gspca_frame_add(gspca_dev, LAST_PACKET, data, n);
+		/* Start next frame. */
+		gspca_frame_add(gspca_dev, FIRST_PACKET,
+			sn9c2028_sof_marker, sizeof sn9c2028_sof_marker);
+		len -= sof - data;
+		data = sof;
+	}
+	gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.dq_callback = sd_dqcallback,
+	.pkt_scan = sd_pkt_scan,
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x0458, 0x7005)}, /* Genius Smart 300, version 2 */
+	{USB_DEVICE(0x0458, 0x7003)}, /* Genius Videocam Live v2  */
+	/* The Genius Smart is untested. I can't find an owner ! */
+	/* {USB_DEVICE(0x0c45, 0x8000)}, DC31VC, Don't know this camera */
+	{USB_DEVICE(0x0c45, 0x8001)}, /* Wild Planet digital spy cam */
+	{USB_DEVICE(0x0c45, 0x8003)}, /* Several small CIF cameras */
+	/* {USB_DEVICE(0x0c45, 0x8006)}, Unknown VGA camera */
+	{USB_DEVICE(0x0c45, 0x8008)}, /* Mini-Shotz ms-350 */
+	{USB_DEVICE(0x0c45, 0x800a)}, /* Vivicam 3350B */
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+			       THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/sn9c2028.h b/drivers/media/usb/gspca/sn9c2028.h
new file mode 100644
index 0000000..29f1571
--- /dev/null
+++ b/drivers/media/usb/gspca/sn9c2028.h
@@ -0,0 +1,58 @@
+/*
+ * SN9C2028 common functions
+ *
+ * Copyright (C) 2009 Theodore Kilgore <kilgota@auburn,edu>
+ *
+ * Based closely upon the file gspca/pac_common.h
+ *
+ * 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.
+ *
+ */
+
+static const unsigned char sn9c2028_sof_marker[] = {
+	0xff, 0xff, 0x00, 0xc4, 0xc4, 0x96,
+	0x00,
+	0x00, /* seq */
+	0x00,
+	0x00,
+	0x00, /* avg luminance lower 8 bit */
+	0x00, /* avg luminance higher 8 bit */
+};
+
+static unsigned char *sn9c2028_find_sof(struct gspca_dev *gspca_dev,
+					unsigned char *m, int len)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int i;
+
+	/* Search for the SOF marker (fixed part) in the header */
+	for (i = 0; i < len; i++) {
+		if ((m[i] == sn9c2028_sof_marker[sd->sof_read]) ||
+		    (sd->sof_read > 5)) {
+			sd->sof_read++;
+			if (sd->sof_read == 11)
+				sd->avg_lum_l = m[i];
+			if (sd->sof_read == 12)
+				sd->avg_lum = (m[i] << 8) + sd->avg_lum_l;
+			if (sd->sof_read == sizeof(sn9c2028_sof_marker)) {
+				gspca_dbg(gspca_dev, D_FRAM,
+					  "SOF found, bytes to analyze: %u - Frame starts at byte #%u\n",
+					  len, i + 1);
+				sd->sof_read = 0;
+				return m + i + 1;
+			}
+		} else {
+			sd->sof_read = 0;
+		}
+	}
+
+	return NULL;
+}
diff --git a/drivers/media/usb/gspca/sn9c20x.c b/drivers/media/usb/gspca/sn9c20x.c
new file mode 100644
index 0000000..cfa2a04
--- /dev/null
+++ b/drivers/media/usb/gspca/sn9c20x.c
@@ -0,0 +1,2386 @@
+/*
+ *	Sonix sn9c201 sn9c202 library
+ *
+ * Copyright (C) 2012 Jean-Francois Moine <http://moinejf.free.fr>
+ *	Copyright (C) 2008-2009 microdia project <microdia@googlegroups.com>
+ *	Copyright (C) 2009 Brian Johnson <brijohn@gmail.com>
+ *
+ * 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
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/input.h>
+
+#include "gspca.h"
+#include "jpeg.h"
+
+#include <linux/dmi.h>
+
+MODULE_AUTHOR("Brian Johnson <brijohn@gmail.com>, microdia project <microdia@googlegroups.com>");
+MODULE_DESCRIPTION("GSPCA/SN9C20X USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+/*
+ * Pixel format private data
+ */
+#define SCALE_MASK	0x0f
+#define SCALE_160x120	0
+#define SCALE_320x240	1
+#define SCALE_640x480	2
+#define SCALE_1280x1024	3
+#define MODE_RAW	0x10
+#define MODE_JPEG	0x20
+#define MODE_SXGA	0x80
+
+#define SENSOR_OV9650	0
+#define SENSOR_OV9655	1
+#define SENSOR_SOI968	2
+#define SENSOR_OV7660	3
+#define SENSOR_OV7670	4
+#define SENSOR_MT9V011	5
+#define SENSOR_MT9V111	6
+#define SENSOR_MT9V112	7
+#define SENSOR_MT9M001	8
+#define SENSOR_MT9M111	9
+#define SENSOR_MT9M112  10
+#define SENSOR_HV7131R	11
+#define SENSOR_MT9VPRB	12
+
+/* camera flags */
+#define HAS_NO_BUTTON	0x1
+#define LED_REVERSE	0x2 /* some cameras unset gpio to turn on leds */
+#define FLIP_DETECT	0x4
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;
+
+	struct { /* color control cluster */
+		struct v4l2_ctrl *brightness;
+		struct v4l2_ctrl *contrast;
+		struct v4l2_ctrl *saturation;
+		struct v4l2_ctrl *hue;
+	};
+	struct { /* blue/red balance control cluster */
+		struct v4l2_ctrl *blue;
+		struct v4l2_ctrl *red;
+	};
+	struct { /* h/vflip control cluster */
+		struct v4l2_ctrl *hflip;
+		struct v4l2_ctrl *vflip;
+	};
+	struct v4l2_ctrl *gamma;
+	struct { /* autogain and exposure or gain control cluster */
+		struct v4l2_ctrl *autogain;
+		struct v4l2_ctrl *exposure;
+		struct v4l2_ctrl *gain;
+	};
+	struct v4l2_ctrl *jpegqual;
+
+	struct work_struct work;
+
+	u32 pktsz;			/* (used by pkt_scan) */
+	u16 npkt;
+	s8 nchg;
+	u8 fmt;				/* (used for JPEG QTAB update */
+
+#define MIN_AVG_LUM 80
+#define MAX_AVG_LUM 130
+	atomic_t avg_lum;
+	u8 old_step;
+	u8 older_step;
+	u8 exposure_step;
+
+	u8 i2c_addr;
+	u8 i2c_intf;
+	u8 sensor;
+	u8 hstart;
+	u8 vstart;
+
+	u8 jpeg_hdr[JPEG_HDR_SZ];
+
+	u8 flags;
+};
+
+static void qual_upd(struct work_struct *work);
+
+struct i2c_reg_u8 {
+	u8 reg;
+	u8 val;
+};
+
+struct i2c_reg_u16 {
+	u8 reg;
+	u16 val;
+};
+
+static const struct dmi_system_id flip_dmi_table[] = {
+	{
+		.ident = "MSI MS-1034",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "MICRO-STAR INT'L CO.,LTD."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "MS-1034"),
+			DMI_MATCH(DMI_PRODUCT_VERSION, "0341")
+		}
+	},
+	{
+		.ident = "MSI MS-1632",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "MSI"),
+			DMI_MATCH(DMI_BOARD_NAME, "MS-1632")
+		}
+	},
+	{
+		.ident = "MSI MS-1633X",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "MSI"),
+			DMI_MATCH(DMI_BOARD_NAME, "MS-1633X")
+		}
+	},
+	{
+		.ident = "MSI MS-1635X",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "MSI"),
+			DMI_MATCH(DMI_BOARD_NAME, "MS-1635X")
+		}
+	},
+	{
+		.ident = "ASUSTeK W7J",
+		.matches = {
+			DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK Computer Inc."),
+			DMI_MATCH(DMI_BOARD_NAME, "W7J       ")
+		}
+	},
+	{}
+};
+
+static const struct v4l2_pix_format vga_mode[] = {
+	{160, 120, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 160 * 120 * 4 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = SCALE_160x120 | MODE_JPEG},
+	{160, 120, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 160 * 120,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = SCALE_160x120 | MODE_RAW},
+	{160, 120, V4L2_PIX_FMT_SN9C20X_I420, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 240 * 120,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = SCALE_160x120},
+	{320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 4 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = SCALE_320x240 | MODE_JPEG},
+	{320, 240, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 ,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = SCALE_320x240 | MODE_RAW},
+	{320, 240, V4L2_PIX_FMT_SN9C20X_I420, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 480 * 240 ,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = SCALE_320x240},
+	{640, 480, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 4 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = SCALE_640x480 | MODE_JPEG},
+	{640, 480, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = SCALE_640x480 | MODE_RAW},
+	{640, 480, V4L2_PIX_FMT_SN9C20X_I420, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 960 * 480,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = SCALE_640x480},
+};
+
+static const struct v4l2_pix_format sxga_mode[] = {
+	{160, 120, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 160 * 120 * 4 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = SCALE_160x120 | MODE_JPEG},
+	{160, 120, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 160 * 120,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = SCALE_160x120 | MODE_RAW},
+	{160, 120, V4L2_PIX_FMT_SN9C20X_I420, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 240 * 120,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = SCALE_160x120},
+	{320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 4 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = SCALE_320x240 | MODE_JPEG},
+	{320, 240, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 ,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = SCALE_320x240 | MODE_RAW},
+	{320, 240, V4L2_PIX_FMT_SN9C20X_I420, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 480 * 240 ,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = SCALE_320x240},
+	{640, 480, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 4 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = SCALE_640x480 | MODE_JPEG},
+	{640, 480, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = SCALE_640x480 | MODE_RAW},
+	{640, 480, V4L2_PIX_FMT_SN9C20X_I420, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 960 * 480,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = SCALE_640x480},
+	{1280, 1024, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 1280,
+		.sizeimage = 1280 * 1024,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = SCALE_1280x1024 | MODE_RAW | MODE_SXGA},
+};
+
+static const struct v4l2_pix_format mono_mode[] = {
+	{160, 120, V4L2_PIX_FMT_GREY, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 160 * 120,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = SCALE_160x120 | MODE_RAW},
+	{320, 240, V4L2_PIX_FMT_GREY, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 ,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = SCALE_320x240 | MODE_RAW},
+	{640, 480, V4L2_PIX_FMT_GREY, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = SCALE_640x480 | MODE_RAW},
+	{1280, 1024, V4L2_PIX_FMT_GREY, V4L2_FIELD_NONE,
+		.bytesperline = 1280,
+		.sizeimage = 1280 * 1024,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = SCALE_1280x1024 | MODE_RAW | MODE_SXGA},
+};
+
+static const s16 hsv_red_x[] = {
+	41,  44,  46,  48,  50,  52,  54,  56,
+	58,  60,  62,  64,  66,  68,  70,  72,
+	74,  76,  78,  80,  81,  83,  85,  87,
+	88,  90,  92,  93,  95,  97,  98, 100,
+	101, 102, 104, 105, 107, 108, 109, 110,
+	112, 113, 114, 115, 116, 117, 118, 119,
+	120, 121, 122, 123, 123, 124, 125, 125,
+	126, 127, 127, 128, 128, 129, 129, 129,
+	130, 130, 130, 130, 131, 131, 131, 131,
+	131, 131, 131, 131, 130, 130, 130, 130,
+	129, 129, 129, 128, 128, 127, 127, 126,
+	125, 125, 124, 123, 122, 122, 121, 120,
+	119, 118, 117, 116, 115, 114, 112, 111,
+	110, 109, 107, 106, 105, 103, 102, 101,
+	99,  98,  96,  94,  93,  91,  90,  88,
+	86,  84,  83,  81,  79,  77,  75,  74,
+	72,  70,  68,  66,  64,  62,  60,  58,
+	56,  54,  52,  49,  47,  45,  43,  41,
+	39,  36,  34,  32,  30,  28,  25,  23,
+	21,  19,  16,  14,  12,   9,   7,   5,
+	3,   0,  -1,  -3,  -6,  -8, -10, -12,
+	-15, -17, -19, -22, -24, -26, -28, -30,
+	-33, -35, -37, -39, -41, -44, -46, -48,
+	-50, -52, -54, -56, -58, -60, -62, -64,
+	-66, -68, -70, -72, -74, -76, -78, -80,
+	-81, -83, -85, -87, -88, -90, -92, -93,
+	-95, -97, -98, -100, -101, -102, -104, -105,
+	-107, -108, -109, -110, -112, -113, -114, -115,
+	-116, -117, -118, -119, -120, -121, -122, -123,
+	-123, -124, -125, -125, -126, -127, -127, -128,
+	-128, -128, -128, -128, -128, -128, -128, -128,
+	-128, -128, -128, -128, -128, -128, -128, -128,
+	-128, -128, -128, -128, -128, -128, -128, -128,
+	-128, -127, -127, -126, -125, -125, -124, -123,
+	-122, -122, -121, -120, -119, -118, -117, -116,
+	-115, -114, -112, -111, -110, -109, -107, -106,
+	-105, -103, -102, -101, -99, -98, -96, -94,
+	-93, -91, -90, -88, -86, -84, -83, -81,
+	-79, -77, -75, -74, -72, -70, -68, -66,
+	-64, -62, -60, -58, -56, -54, -52, -49,
+	-47, -45, -43, -41, -39, -36, -34, -32,
+	-30, -28, -25, -23, -21, -19, -16, -14,
+	-12,  -9,  -7,  -5,  -3,   0,   1,   3,
+	6,   8,  10,  12,  15,  17,  19,  22,
+	24,  26,  28,  30,  33,  35,  37,  39, 41
+};
+
+static const s16 hsv_red_y[] = {
+	82,  80,  78,  76,  74,  73,  71,  69,
+	67,  65,  63,  61,  58,  56,  54,  52,
+	50,  48,  46,  44,  41,  39,  37,  35,
+	32,  30,  28,  26,  23,  21,  19,  16,
+	14,  12,  10,   7,   5,   3,   0,  -1,
+	-3,  -6,  -8, -10, -13, -15, -17, -19,
+	-22, -24, -26, -29, -31, -33, -35, -38,
+	-40, -42, -44, -46, -48, -51, -53, -55,
+	-57, -59, -61, -63, -65, -67, -69, -71,
+	-73, -75, -77, -79, -81, -82, -84, -86,
+	-88, -89, -91, -93, -94, -96, -98, -99,
+	-101, -102, -104, -105, -106, -108, -109, -110,
+	-112, -113, -114, -115, -116, -117, -119, -120,
+	-120, -121, -122, -123, -124, -125, -126, -126,
+	-127, -128, -128, -128, -128, -128, -128, -128,
+	-128, -128, -128, -128, -128, -128, -128, -128,
+	-128, -128, -128, -128, -128, -128, -128, -128,
+	-128, -128, -128, -128, -128, -128, -128, -128,
+	-127, -127, -126, -125, -125, -124, -123, -122,
+	-121, -120, -119, -118, -117, -116, -115, -114,
+	-113, -111, -110, -109, -107, -106, -105, -103,
+	-102, -100, -99, -97, -96, -94, -92, -91,
+	-89, -87, -85, -84, -82, -80, -78, -76,
+	-74, -73, -71, -69, -67, -65, -63, -61,
+	-58, -56, -54, -52, -50, -48, -46, -44,
+	-41, -39, -37, -35, -32, -30, -28, -26,
+	-23, -21, -19, -16, -14, -12, -10,  -7,
+	-5,  -3,   0,   1,   3,   6,   8,  10,
+	13,  15,  17,  19,  22,  24,  26,  29,
+	31,  33,  35,  38,  40,  42,  44,  46,
+	48,  51,  53,  55,  57,  59,  61,  63,
+	65,  67,  69,  71,  73,  75,  77,  79,
+	81,  82,  84,  86,  88,  89,  91,  93,
+	94,  96,  98,  99, 101, 102, 104, 105,
+	106, 108, 109, 110, 112, 113, 114, 115,
+	116, 117, 119, 120, 120, 121, 122, 123,
+	124, 125, 126, 126, 127, 128, 128, 129,
+	129, 130, 130, 131, 131, 131, 131, 132,
+	132, 132, 132, 132, 132, 132, 132, 132,
+	132, 132, 132, 131, 131, 131, 130, 130,
+	130, 129, 129, 128, 127, 127, 126, 125,
+	125, 124, 123, 122, 121, 120, 119, 118,
+	117, 116, 115, 114, 113, 111, 110, 109,
+	107, 106, 105, 103, 102, 100,  99,  97,
+	96, 94, 92, 91, 89, 87, 85, 84, 82
+};
+
+static const s16 hsv_green_x[] = {
+	-124, -124, -125, -125, -125, -125, -125, -125,
+	-125, -126, -126, -125, -125, -125, -125, -125,
+	-125, -124, -124, -124, -123, -123, -122, -122,
+	-121, -121, -120, -120, -119, -118, -117, -117,
+	-116, -115, -114, -113, -112, -111, -110, -109,
+	-108, -107, -105, -104, -103, -102, -100, -99,
+	-98, -96, -95, -93, -92, -91, -89, -87,
+	-86, -84, -83, -81, -79, -77, -76, -74,
+	-72, -70, -69, -67, -65, -63, -61, -59,
+	-57, -55, -53, -51, -49, -47, -45, -43,
+	-41, -39, -37, -35, -33, -30, -28, -26,
+	-24, -22, -20, -18, -15, -13, -11,  -9,
+	-7,  -4,  -2,   0,   1,   3,   6,   8,
+	10,  12,  14,  17,  19,  21,  23,  25,
+	27,  29,  32,  34,  36,  38,  40,  42,
+	44,  46,  48,  50,  52,  54,  56,  58,
+	60,  62,  64,  66,  68,  70,  71,  73,
+	75,  77,  78,  80,  82,  83,  85,  87,
+	88,  90,  91,  93,  94,  96,  97,  98,
+	100, 101, 102, 104, 105, 106, 107, 108,
+	109, 111, 112, 113, 113, 114, 115, 116,
+	117, 118, 118, 119, 120, 120, 121, 122,
+	122, 123, 123, 124, 124, 124, 125, 125,
+	125, 125, 125, 125, 125, 126, 126, 125,
+	125, 125, 125, 125, 125, 124, 124, 124,
+	123, 123, 122, 122, 121, 121, 120, 120,
+	119, 118, 117, 117, 116, 115, 114, 113,
+	112, 111, 110, 109, 108, 107, 105, 104,
+	103, 102, 100,  99,  98,  96,  95,  93,
+	92,  91,  89,  87,  86,  84,  83,  81,
+	79,  77,  76,  74,  72,  70,  69,  67,
+	65,  63,  61,  59,  57,  55,  53,  51,
+	49,  47,  45,  43,  41,  39,  37,  35,
+	33,  30,  28,  26,  24,  22,  20,  18,
+	15,  13,  11,   9,   7,   4,   2,   0,
+	-1,  -3,  -6,  -8, -10, -12, -14, -17,
+	-19, -21, -23, -25, -27, -29, -32, -34,
+	-36, -38, -40, -42, -44, -46, -48, -50,
+	-52, -54, -56, -58, -60, -62, -64, -66,
+	-68, -70, -71, -73, -75, -77, -78, -80,
+	-82, -83, -85, -87, -88, -90, -91, -93,
+	-94, -96, -97, -98, -100, -101, -102, -104,
+	-105, -106, -107, -108, -109, -111, -112, -113,
+	-113, -114, -115, -116, -117, -118, -118, -119,
+	-120, -120, -121, -122, -122, -123, -123, -124, -124
+};
+
+static const s16 hsv_green_y[] = {
+	-100, -99, -98, -97, -95, -94, -93, -91,
+	-90, -89, -87, -86, -84, -83, -81, -80,
+	-78, -76, -75, -73, -71, -70, -68, -66,
+	-64, -63, -61, -59, -57, -55, -53, -51,
+	-49, -48, -46, -44, -42, -40, -38, -36,
+	-34, -32, -30, -27, -25, -23, -21, -19,
+	-17, -15, -13, -11,  -9,  -7,  -4,  -2,
+	0,   1,   3,   5,   7,   9,  11,  14,
+	16,  18,  20,  22,  24,  26,  28,  30,
+	32,  34,  36,  38,  40,  42,  44,  46,
+	48,  50,  52,  54,  56,  58,  59,  61,
+	63,  65,  67,  68,  70,  72,  74,  75,
+	77,  78,  80,  82,  83,  85,  86,  88,
+	89,  90,  92,  93,  95,  96,  97,  98,
+	100, 101, 102, 103, 104, 105, 106, 107,
+	108, 109, 110, 111, 112, 112, 113, 114,
+	115, 115, 116, 116, 117, 117, 118, 118,
+	119, 119, 119, 120, 120, 120, 120, 120,
+	121, 121, 121, 121, 121, 121, 120, 120,
+	120, 120, 120, 119, 119, 119, 118, 118,
+	117, 117, 116, 116, 115, 114, 114, 113,
+	112, 111, 111, 110, 109, 108, 107, 106,
+	105, 104, 103, 102, 100,  99,  98,  97,
+	95,  94,  93,  91,  90,  89,  87,  86,
+	84,  83,  81,  80,  78,  76,  75,  73,
+	71,  70,  68,  66,  64,  63,  61,  59,
+	57,  55,  53,  51,  49,  48,  46,  44,
+	42,  40,  38,  36,  34,  32,  30,  27,
+	25,  23,  21,  19,  17,  15,  13,  11,
+	9,   7,   4,   2,   0,  -1,  -3,  -5,
+	-7,  -9, -11, -14, -16, -18, -20, -22,
+	-24, -26, -28, -30, -32, -34, -36, -38,
+	-40, -42, -44, -46, -48, -50, -52, -54,
+	-56, -58, -59, -61, -63, -65, -67, -68,
+	-70, -72, -74, -75, -77, -78, -80, -82,
+	-83, -85, -86, -88, -89, -90, -92, -93,
+	-95, -96, -97, -98, -100, -101, -102, -103,
+	-104, -105, -106, -107, -108, -109, -110, -111,
+	-112, -112, -113, -114, -115, -115, -116, -116,
+	-117, -117, -118, -118, -119, -119, -119, -120,
+	-120, -120, -120, -120, -121, -121, -121, -121,
+	-121, -121, -120, -120, -120, -120, -120, -119,
+	-119, -119, -118, -118, -117, -117, -116, -116,
+	-115, -114, -114, -113, -112, -111, -111, -110,
+	-109, -108, -107, -106, -105, -104, -103, -102, -100
+};
+
+static const s16 hsv_blue_x[] = {
+	112, 113, 114, 114, 115, 116, 117, 117,
+	118, 118, 119, 119, 120, 120, 120, 121,
+	121, 121, 122, 122, 122, 122, 122, 122,
+	122, 122, 122, 122, 122, 122, 121, 121,
+	121, 120, 120, 120, 119, 119, 118, 118,
+	117, 116, 116, 115, 114, 113, 113, 112,
+	111, 110, 109, 108, 107, 106, 105, 104,
+	103, 102, 100,  99,  98,  97,  95,  94,
+	93,  91,  90,  88,  87,  85,  84,  82,
+	80,  79,  77,  76,  74,  72,  70,  69,
+	67,  65,  63,  61,  60,  58,  56,  54,
+	52,  50,  48,  46,  44,  42,  40,  38,
+	36,  34,  32,  30,  28,  26,  24,  22,
+	19,  17,  15,  13,  11,   9,   7,   5,
+	2,   0,  -1,  -3,  -5,  -7,  -9, -12,
+	-14, -16, -18, -20, -22, -24, -26, -28,
+	-31, -33, -35, -37, -39, -41, -43, -45,
+	-47, -49, -51, -53, -54, -56, -58, -60,
+	-62, -64, -66, -67, -69, -71, -73, -74,
+	-76, -78, -79, -81, -83, -84, -86, -87,
+	-89, -90, -92, -93, -94, -96, -97, -98,
+	-99, -101, -102, -103, -104, -105, -106, -107,
+	-108, -109, -110, -111, -112, -113, -114, -114,
+	-115, -116, -117, -117, -118, -118, -119, -119,
+	-120, -120, -120, -121, -121, -121, -122, -122,
+	-122, -122, -122, -122, -122, -122, -122, -122,
+	-122, -122, -121, -121, -121, -120, -120, -120,
+	-119, -119, -118, -118, -117, -116, -116, -115,
+	-114, -113, -113, -112, -111, -110, -109, -108,
+	-107, -106, -105, -104, -103, -102, -100, -99,
+	-98, -97, -95, -94, -93, -91, -90, -88,
+	-87, -85, -84, -82, -80, -79, -77, -76,
+	-74, -72, -70, -69, -67, -65, -63, -61,
+	-60, -58, -56, -54, -52, -50, -48, -46,
+	-44, -42, -40, -38, -36, -34, -32, -30,
+	-28, -26, -24, -22, -19, -17, -15, -13,
+	-11,  -9,  -7,  -5,  -2,   0,   1,   3,
+	5,   7,   9,  12,  14,  16,  18,  20,
+	22,  24,  26,  28,  31,  33,  35,  37,
+	39,  41,  43,  45,  47,  49,  51,  53,
+	54,  56,  58,  60,  62,  64,  66,  67,
+	69,  71,  73,  74,  76,  78,  79,  81,
+	83,  84,  86,  87,  89,  90,  92,  93,
+	94,  96,  97,  98,  99, 101, 102, 103,
+	104, 105, 106, 107, 108, 109, 110, 111, 112
+};
+
+static const s16 hsv_blue_y[] = {
+	-11, -13, -15, -17, -19, -21, -23, -25,
+	-27, -29, -31, -33, -35, -37, -39, -41,
+	-43, -45, -46, -48, -50, -52, -54, -55,
+	-57, -59, -61, -62, -64, -66, -67, -69,
+	-71, -72, -74, -75, -77, -78, -80, -81,
+	-83, -84, -86, -87, -88, -90, -91, -92,
+	-93, -95, -96, -97, -98, -99, -100, -101,
+	-102, -103, -104, -105, -106, -106, -107, -108,
+	-109, -109, -110, -111, -111, -112, -112, -113,
+	-113, -114, -114, -114, -115, -115, -115, -115,
+	-116, -116, -116, -116, -116, -116, -116, -116,
+	-116, -115, -115, -115, -115, -114, -114, -114,
+	-113, -113, -112, -112, -111, -111, -110, -110,
+	-109, -108, -108, -107, -106, -105, -104, -103,
+	-102, -101, -100, -99, -98, -97, -96, -95,
+	-94, -93, -91, -90, -89, -88, -86, -85,
+	-84, -82, -81, -79, -78, -76, -75, -73,
+	-71, -70, -68, -67, -65, -63, -62, -60,
+	-58, -56, -55, -53, -51, -49, -47, -45,
+	-44, -42, -40, -38, -36, -34, -32, -30,
+	-28, -26, -24, -22, -20, -18, -16, -14,
+	-12, -10,  -8,  -6,  -4,  -2,   0,   1,
+	3,   5,   7,   9,  11,  13,  15,  17,
+	19,  21,  23,  25,  27,  29,  31,  33,
+	35,  37,  39,  41,  43,  45,  46,  48,
+	50,  52,  54,  55,  57,  59,  61,  62,
+	64,  66,  67,  69,  71,  72,  74,  75,
+	77,  78,  80,  81,  83,  84,  86,  87,
+	88,  90,  91,  92,  93,  95,  96,  97,
+	98,  99, 100, 101, 102, 103, 104, 105,
+	106, 106, 107, 108, 109, 109, 110, 111,
+	111, 112, 112, 113, 113, 114, 114, 114,
+	115, 115, 115, 115, 116, 116, 116, 116,
+	116, 116, 116, 116, 116, 115, 115, 115,
+	115, 114, 114, 114, 113, 113, 112, 112,
+	111, 111, 110, 110, 109, 108, 108, 107,
+	106, 105, 104, 103, 102, 101, 100,  99,
+	98,  97,  96,  95,  94,  93,  91,  90,
+	89,  88,  86,  85,  84,  82,  81,  79,
+	78,  76,  75,  73,  71,  70,  68,  67,
+	65,  63,  62,  60,  58,  56,  55,  53,
+	51,  49,  47,  45,  44,  42,  40,  38,
+	36,  34,  32,  30,  28,  26,  24,  22,
+	20,  18,  16,  14,  12,  10,   8,   6,
+	4,   2,   0,  -1,  -3,  -5,  -7,  -9, -11
+};
+
+static const u16 bridge_init[][2] = {
+	{0x1000, 0x78}, {0x1001, 0x40}, {0x1002, 0x1c},
+	{0x1020, 0x80}, {0x1061, 0x01}, {0x1067, 0x40},
+	{0x1068, 0x30}, {0x1069, 0x20},	{0x106a, 0x10},
+	{0x106b, 0x08},	{0x1188, 0x87},	{0x11a1, 0x00},
+	{0x11a2, 0x00},	{0x11a3, 0x6a},	{0x11a4, 0x50},
+	{0x11ab, 0x00},	{0x11ac, 0x00},	{0x11ad, 0x50},
+	{0x11ae, 0x3c},	{0x118a, 0x04},	{0x0395, 0x04},
+	{0x11b8, 0x3a},	{0x118b, 0x0e},	{0x10f7, 0x05},
+	{0x10f8, 0x14},	{0x10fa, 0xff},	{0x10f9, 0x00},
+	{0x11ba, 0x0a},	{0x11a5, 0x2d},	{0x11a6, 0x2d},
+	{0x11a7, 0x3a},	{0x11a8, 0x05},	{0x11a9, 0x04},
+	{0x11aa, 0x3f},	{0x11af, 0x28},	{0x11b0, 0xd8},
+	{0x11b1, 0x14},	{0x11b2, 0xec},	{0x11b3, 0x32},
+	{0x11b4, 0xdd},	{0x11b5, 0x32},	{0x11b6, 0xdd},
+	{0x10e0, 0x2c},	{0x11bc, 0x40},	{0x11bd, 0x01},
+	{0x11be, 0xf0},	{0x11bf, 0x00},	{0x118c, 0x1f},
+	{0x118d, 0x1f},	{0x118e, 0x1f},	{0x118f, 0x1f},
+	{0x1180, 0x01},	{0x1181, 0x00},	{0x1182, 0x01},
+	{0x1183, 0x00},	{0x1184, 0x50},	{0x1185, 0x80},
+	{0x1007, 0x00}
+};
+
+/* Gain = (bit[3:0] / 16 + 1) * (bit[4] + 1) * (bit[5] + 1) * (bit[6] + 1) */
+static const u8 ov_gain[] = {
+	0x00 /* 1x */, 0x04 /* 1.25x */, 0x08 /* 1.5x */, 0x0c /* 1.75x */,
+	0x10 /* 2x */, 0x12 /* 2.25x */, 0x14 /* 2.5x */, 0x16 /* 2.75x */,
+	0x18 /* 3x */, 0x1a /* 3.25x */, 0x1c /* 3.5x */, 0x1e /* 3.75x */,
+	0x30 /* 4x */, 0x31 /* 4.25x */, 0x32 /* 4.5x */, 0x33 /* 4.75x */,
+	0x34 /* 5x */, 0x35 /* 5.25x */, 0x36 /* 5.5x */, 0x37 /* 5.75x */,
+	0x38 /* 6x */, 0x39 /* 6.25x */, 0x3a /* 6.5x */, 0x3b /* 6.75x */,
+	0x3c /* 7x */, 0x3d /* 7.25x */, 0x3e /* 7.5x */, 0x3f /* 7.75x */,
+	0x70 /* 8x */
+};
+
+/* Gain = (bit[8] + 1) * (bit[7] + 1) * (bit[6:0] * 0.03125) */
+static const u16 micron1_gain[] = {
+	/* 1x   1.25x   1.5x    1.75x */
+	0x0020, 0x0028, 0x0030, 0x0038,
+	/* 2x   2.25x   2.5x    2.75x */
+	0x00a0, 0x00a4, 0x00a8, 0x00ac,
+	/* 3x   3.25x   3.5x    3.75x */
+	0x00b0, 0x00b4, 0x00b8, 0x00bc,
+	/* 4x   4.25x   4.5x    4.75x */
+	0x00c0, 0x00c4, 0x00c8, 0x00cc,
+	/* 5x   5.25x   5.5x    5.75x */
+	0x00d0, 0x00d4, 0x00d8, 0x00dc,
+	/* 6x   6.25x   6.5x    6.75x */
+	0x00e0, 0x00e4, 0x00e8, 0x00ec,
+	/* 7x   7.25x   7.5x    7.75x */
+	0x00f0, 0x00f4, 0x00f8, 0x00fc,
+	/* 8x */
+	0x01c0
+};
+
+/* mt9m001 sensor uses a different gain formula then other micron sensors */
+/* Gain = (bit[6] + 1) * (bit[5-0] * 0.125) */
+static const u16 micron2_gain[] = {
+	/* 1x   1.25x   1.5x    1.75x */
+	0x0008, 0x000a, 0x000c, 0x000e,
+	/* 2x   2.25x   2.5x    2.75x */
+	0x0010, 0x0012, 0x0014, 0x0016,
+	/* 3x   3.25x   3.5x    3.75x */
+	0x0018, 0x001a, 0x001c, 0x001e,
+	/* 4x   4.25x   4.5x    4.75x */
+	0x0020, 0x0051, 0x0052, 0x0053,
+	/* 5x   5.25x   5.5x    5.75x */
+	0x0054, 0x0055, 0x0056, 0x0057,
+	/* 6x   6.25x   6.5x    6.75x */
+	0x0058, 0x0059, 0x005a, 0x005b,
+	/* 7x   7.25x   7.5x    7.75x */
+	0x005c, 0x005d, 0x005e, 0x005f,
+	/* 8x */
+	0x0060
+};
+
+/* Gain = .5 + bit[7:0] / 16 */
+static const u8 hv7131r_gain[] = {
+	0x08 /* 1x */, 0x0c /* 1.25x */, 0x10 /* 1.5x */, 0x14 /* 1.75x */,
+	0x18 /* 2x */, 0x1c /* 2.25x */, 0x20 /* 2.5x */, 0x24 /* 2.75x */,
+	0x28 /* 3x */, 0x2c /* 3.25x */, 0x30 /* 3.5x */, 0x34 /* 3.75x */,
+	0x38 /* 4x */, 0x3c /* 4.25x */, 0x40 /* 4.5x */, 0x44 /* 4.75x */,
+	0x48 /* 5x */, 0x4c /* 5.25x */, 0x50 /* 5.5x */, 0x54 /* 5.75x */,
+	0x58 /* 6x */, 0x5c /* 6.25x */, 0x60 /* 6.5x */, 0x64 /* 6.75x */,
+	0x68 /* 7x */, 0x6c /* 7.25x */, 0x70 /* 7.5x */, 0x74 /* 7.75x */,
+	0x78 /* 8x */
+};
+
+static const struct i2c_reg_u8 soi968_init[] = {
+	{0x0c, 0x00}, {0x0f, 0x1f},
+	{0x11, 0x80}, {0x38, 0x52}, {0x1e, 0x00},
+	{0x33, 0x08}, {0x35, 0x8c}, {0x36, 0x0c},
+	{0x37, 0x04}, {0x45, 0x04}, {0x47, 0xff},
+	{0x3e, 0x00}, {0x3f, 0x00}, {0x3b, 0x20},
+	{0x3a, 0x96}, {0x3d, 0x0a}, {0x14, 0x8e},
+	{0x13, 0x8b}, {0x12, 0x40}, {0x17, 0x13},
+	{0x18, 0x63}, {0x19, 0x01}, {0x1a, 0x79},
+	{0x32, 0x24}, {0x03, 0x00}, {0x11, 0x40},
+	{0x2a, 0x10}, {0x2b, 0xe0}, {0x10, 0x32},
+	{0x00, 0x00}, {0x01, 0x80}, {0x02, 0x80},
+};
+
+static const struct i2c_reg_u8 ov7660_init[] = {
+	{0x0e, 0x80}, {0x0d, 0x08}, {0x0f, 0xc3},
+	{0x04, 0xc3}, {0x10, 0x40}, {0x11, 0x40},
+	{0x12, 0x05}, {0x13, 0xba}, {0x14, 0x2a},
+	/* HDG Set hstart and hstop, datasheet default 0x11, 0x61, using
+	   0x10, 0x61 and sd->hstart, vstart = 3, fixes ugly colored borders */
+	{0x17, 0x10}, {0x18, 0x61},
+	{0x37, 0x0f}, {0x38, 0x02}, {0x39, 0x43},
+	{0x3a, 0x00}, {0x69, 0x90}, {0x2d, 0x00},
+	{0x2e, 0x00}, {0x01, 0x78}, {0x02, 0x50},
+};
+
+static const struct i2c_reg_u8 ov7670_init[] = {
+	{0x11, 0x80}, {0x3a, 0x04}, {0x12, 0x01},
+	{0x32, 0xb6}, {0x03, 0x0a}, {0x0c, 0x00}, {0x3e, 0x00},
+	{0x70, 0x3a}, {0x71, 0x35}, {0x72, 0x11}, {0x73, 0xf0},
+	{0xa2, 0x02}, {0x13, 0xe0}, {0x00, 0x00}, {0x10, 0x00},
+	{0x0d, 0x40}, {0x14, 0x28}, {0xa5, 0x05}, {0xab, 0x07},
+	{0x24, 0x95}, {0x25, 0x33}, {0x26, 0xe3}, {0x9f, 0x75},
+	{0xa0, 0x65}, {0xa1, 0x0b}, {0xa6, 0xd8}, {0xa7, 0xd8},
+	{0xa8, 0xf0}, {0xa9, 0x90}, {0xaa, 0x94}, {0x13, 0xe5},
+	{0x0e, 0x61}, {0x0f, 0x4b}, {0x16, 0x02}, {0x1e, 0x27},
+	{0x21, 0x02}, {0x22, 0x91}, {0x29, 0x07}, {0x33, 0x0b},
+	{0x35, 0x0b}, {0x37, 0x1d}, {0x38, 0x71}, {0x39, 0x2a},
+	{0x3c, 0x78}, {0x4d, 0x40}, {0x4e, 0x20}, {0x69, 0x00},
+	{0x74, 0x19}, {0x8d, 0x4f}, {0x8e, 0x00}, {0x8f, 0x00},
+	{0x90, 0x00}, {0x91, 0x00}, {0x96, 0x00}, {0x9a, 0x80},
+	{0xb0, 0x84}, {0xb1, 0x0c}, {0xb2, 0x0e}, {0xb3, 0x82},
+	{0xb8, 0x0a}, {0x43, 0x0a}, {0x44, 0xf0}, {0x45, 0x20},
+	{0x46, 0x7d}, {0x47, 0x29}, {0x48, 0x4a}, {0x59, 0x8c},
+	{0x5a, 0xa5}, {0x5b, 0xde}, {0x5c, 0x96}, {0x5d, 0x66},
+	{0x5e, 0x10}, {0x6c, 0x0a}, {0x6d, 0x55}, {0x6e, 0x11},
+	{0x6f, 0x9e}, {0x6a, 0x40}, {0x01, 0x40}, {0x02, 0x40},
+	{0x13, 0xe7}, {0x4f, 0x6e}, {0x50, 0x70}, {0x51, 0x02},
+	{0x52, 0x1d}, {0x53, 0x56}, {0x54, 0x73}, {0x55, 0x0a},
+	{0x56, 0x55}, {0x57, 0x80}, {0x58, 0x9e}, {0x41, 0x08},
+	{0x3f, 0x02}, {0x75, 0x03}, {0x76, 0x63}, {0x4c, 0x04},
+	{0x77, 0x06}, {0x3d, 0x02}, {0x4b, 0x09}, {0xc9, 0x30},
+	{0x41, 0x08}, {0x56, 0x48}, {0x34, 0x11}, {0xa4, 0x88},
+	{0x96, 0x00}, {0x97, 0x30}, {0x98, 0x20}, {0x99, 0x30},
+	{0x9a, 0x84}, {0x9b, 0x29}, {0x9c, 0x03}, {0x9d, 0x99},
+	{0x9e, 0x7f}, {0x78, 0x04}, {0x79, 0x01}, {0xc8, 0xf0},
+	{0x79, 0x0f}, {0xc8, 0x00}, {0x79, 0x10}, {0xc8, 0x7e},
+	{0x79, 0x0a}, {0xc8, 0x80}, {0x79, 0x0b}, {0xc8, 0x01},
+	{0x79, 0x0c}, {0xc8, 0x0f}, {0x79, 0x0d}, {0xc8, 0x20},
+	{0x79, 0x09}, {0xc8, 0x80}, {0x79, 0x02}, {0xc8, 0xc0},
+	{0x79, 0x03}, {0xc8, 0x40}, {0x79, 0x05}, {0xc8, 0x30},
+	{0x79, 0x26}, {0x62, 0x20}, {0x63, 0x00}, {0x64, 0x06},
+	{0x65, 0x00}, {0x66, 0x05}, {0x94, 0x05}, {0x95, 0x0a},
+	{0x17, 0x13}, {0x18, 0x01}, {0x19, 0x02}, {0x1a, 0x7a},
+	{0x46, 0x59}, {0x47, 0x30}, {0x58, 0x9a}, {0x59, 0x84},
+	{0x5a, 0x91}, {0x5b, 0x57}, {0x5c, 0x75}, {0x5d, 0x6d},
+	{0x5e, 0x13}, {0x64, 0x07}, {0x94, 0x07}, {0x95, 0x0d},
+	{0xa6, 0xdf}, {0xa7, 0xdf}, {0x48, 0x4d}, {0x51, 0x00},
+	{0x6b, 0x0a}, {0x11, 0x80}, {0x2a, 0x00}, {0x2b, 0x00},
+	{0x92, 0x00}, {0x93, 0x00}, {0x55, 0x0a}, {0x56, 0x60},
+	{0x4f, 0x6e}, {0x50, 0x70}, {0x51, 0x00}, {0x52, 0x1d},
+	{0x53, 0x56}, {0x54, 0x73}, {0x58, 0x9a}, {0x4f, 0x6e},
+	{0x50, 0x70}, {0x51, 0x00}, {0x52, 0x1d}, {0x53, 0x56},
+	{0x54, 0x73}, {0x58, 0x9a}, {0x3f, 0x01}, {0x7b, 0x03},
+	{0x7c, 0x09}, {0x7d, 0x16}, {0x7e, 0x38}, {0x7f, 0x47},
+	{0x80, 0x53}, {0x81, 0x5e}, {0x82, 0x6a}, {0x83, 0x74},
+	{0x84, 0x80}, {0x85, 0x8c}, {0x86, 0x9b}, {0x87, 0xb2},
+	{0x88, 0xcc}, {0x89, 0xe5}, {0x7a, 0x24}, {0x3b, 0x00},
+	{0x9f, 0x76}, {0xa0, 0x65}, {0x13, 0xe2}, {0x6b, 0x0a},
+	{0x11, 0x80}, {0x2a, 0x00}, {0x2b, 0x00}, {0x92, 0x00},
+	{0x93, 0x00},
+};
+
+static const struct i2c_reg_u8 ov9650_init[] = {
+	{0x00, 0x00}, {0x01, 0x78},
+	{0x02, 0x78}, {0x03, 0x36}, {0x04, 0x03},
+	{0x05, 0x00}, {0x06, 0x00}, {0x08, 0x00},
+	{0x09, 0x01}, {0x0c, 0x00}, {0x0d, 0x00},
+	{0x0e, 0xa0}, {0x0f, 0x52}, {0x10, 0x7c},
+	{0x11, 0x80}, {0x12, 0x45}, {0x13, 0xc2},
+	{0x14, 0x2e}, {0x15, 0x00}, {0x16, 0x07},
+	{0x17, 0x24}, {0x18, 0xc5}, {0x19, 0x00},
+	{0x1a, 0x3c}, {0x1b, 0x00}, {0x1e, 0x04},
+	{0x1f, 0x00}, {0x24, 0x78}, {0x25, 0x68},
+	{0x26, 0xd4}, {0x27, 0x80}, {0x28, 0x80},
+	{0x29, 0x30}, {0x2a, 0x00}, {0x2b, 0x00},
+	{0x2c, 0x80}, {0x2d, 0x00}, {0x2e, 0x00},
+	{0x2f, 0x00}, {0x30, 0x08}, {0x31, 0x30},
+	{0x32, 0x84}, {0x33, 0xe2}, {0x34, 0xbf},
+	{0x35, 0x81}, {0x36, 0xf9}, {0x37, 0x00},
+	{0x38, 0x93}, {0x39, 0x50}, {0x3a, 0x01},
+	{0x3b, 0x01}, {0x3c, 0x73}, {0x3d, 0x19},
+	{0x3e, 0x0b}, {0x3f, 0x80}, {0x40, 0xc1},
+	{0x41, 0x00}, {0x42, 0x08}, {0x67, 0x80},
+	{0x68, 0x80}, {0x69, 0x40}, {0x6a, 0x00},
+	{0x6b, 0x0a}, {0x8b, 0x06}, {0x8c, 0x20},
+	{0x8d, 0x00}, {0x8e, 0x00}, {0x8f, 0xdf},
+	{0x92, 0x00}, {0x93, 0x00}, {0x94, 0x88},
+	{0x95, 0x88}, {0x96, 0x04}, {0xa1, 0x00},
+	{0xa5, 0x80}, {0xa8, 0x80}, {0xa9, 0xb8},
+	{0xaa, 0x92}, {0xab, 0x0a},
+};
+
+static const struct i2c_reg_u8 ov9655_init[] = {
+	{0x0e, 0x61}, {0x11, 0x80}, {0x13, 0xba},
+	{0x14, 0x2e}, {0x16, 0x24}, {0x1e, 0x04}, {0x27, 0x08},
+	{0x28, 0x08}, {0x29, 0x15}, {0x2c, 0x08}, {0x34, 0x3d},
+	{0x35, 0x00}, {0x38, 0x12}, {0x0f, 0x42}, {0x39, 0x57},
+	{0x3a, 0x00}, {0x3b, 0xcc}, {0x3c, 0x0c}, {0x3d, 0x19},
+	{0x3e, 0x0c}, {0x3f, 0x01}, {0x41, 0x40}, {0x42, 0x80},
+	{0x45, 0x46}, {0x46, 0x62}, {0x47, 0x2a}, {0x48, 0x3c},
+	{0x4a, 0xf0}, {0x4b, 0xdc}, {0x4c, 0xdc}, {0x4d, 0xdc},
+	{0x4e, 0xdc}, {0x6c, 0x04}, {0x6f, 0x9e}, {0x70, 0x05},
+	{0x71, 0x78}, {0x77, 0x02}, {0x8a, 0x23}, {0x90, 0x7e},
+	{0x91, 0x7c}, {0x9f, 0x6e}, {0xa0, 0x6e}, {0xa5, 0x68},
+	{0xa6, 0x60}, {0xa8, 0xc1}, {0xa9, 0xfa}, {0xaa, 0x92},
+	{0xab, 0x04}, {0xac, 0x80}, {0xad, 0x80}, {0xae, 0x80},
+	{0xaf, 0x80}, {0xb2, 0xf2}, {0xb3, 0x20}, {0xb5, 0x00},
+	{0xb6, 0xaf}, {0xbb, 0xae}, {0xbc, 0x44}, {0xbd, 0x44},
+	{0xbe, 0x3b}, {0xbf, 0x3a}, {0xc1, 0xc8}, {0xc2, 0x01},
+	{0xc4, 0x00}, {0xc6, 0x85}, {0xc7, 0x81}, {0xc9, 0xe0},
+	{0xca, 0xe8}, {0xcc, 0xd8}, {0xcd, 0x93}, {0x2d, 0x00},
+	{0x2e, 0x00}, {0x01, 0x80}, {0x02, 0x80}, {0x12, 0x61},
+	{0x36, 0xfa}, {0x8c, 0x8d}, {0xc0, 0xaa}, {0x69, 0x0a},
+	{0x03, 0x09}, {0x17, 0x16}, {0x18, 0x6e}, {0x19, 0x01},
+	{0x1a, 0x3e}, {0x32, 0x09}, {0x2a, 0x10}, {0x2b, 0x0a},
+	{0x92, 0x00}, {0x93, 0x00}, {0xa1, 0x00}, {0x10, 0x7c},
+	{0x04, 0x03}, {0x00, 0x13},
+};
+
+static const struct i2c_reg_u16 mt9v112_init[] = {
+	{0xf0, 0x0000}, {0x0d, 0x0021}, {0x0d, 0x0020},
+	{0x34, 0xc019}, {0x0a, 0x0011}, {0x0b, 0x000b},
+	{0x20, 0x0703}, {0x35, 0x2022}, {0xf0, 0x0001},
+	{0x05, 0x0000}, {0x06, 0x340c}, {0x3b, 0x042a},
+	{0x3c, 0x0400}, {0xf0, 0x0002}, {0x2e, 0x0c58},
+	{0x5b, 0x0001}, {0xc8, 0x9f0b}, {0xf0, 0x0001},
+	{0x9b, 0x5300}, {0xf0, 0x0000}, {0x2b, 0x0020},
+	{0x2c, 0x002a}, {0x2d, 0x0032}, {0x2e, 0x0020},
+	{0x09, 0x01dc}, {0x01, 0x000c}, {0x02, 0x0020},
+	{0x03, 0x01e0}, {0x04, 0x0280}, {0x06, 0x000c},
+	{0x05, 0x0098}, {0x20, 0x0703}, {0x09, 0x01f2},
+	{0x2b, 0x00a0}, {0x2c, 0x00a0}, {0x2d, 0x00a0},
+	{0x2e, 0x00a0}, {0x01, 0x000c}, {0x02, 0x0020},
+	{0x03, 0x01e0}, {0x04, 0x0280}, {0x06, 0x000c},
+	{0x05, 0x0098}, {0x09, 0x01c1}, {0x2b, 0x00ae},
+	{0x2c, 0x00ae}, {0x2d, 0x00ae}, {0x2e, 0x00ae},
+};
+
+static const struct i2c_reg_u16 mt9v111_init[] = {
+	{0x01, 0x0004}, {0x0d, 0x0001}, {0x0d, 0x0000},
+	{0x01, 0x0001}, {0x05, 0x0004}, {0x2d, 0xe0a0},
+	{0x2e, 0x0c64},	{0x2f, 0x0064}, {0x06, 0x600e},
+	{0x08, 0x0480}, {0x01, 0x0004}, {0x02, 0x0016},
+	{0x03, 0x01e7}, {0x04, 0x0287}, {0x05, 0x0004},
+	{0x06, 0x002d},	{0x07, 0x3002}, {0x08, 0x0008},
+	{0x0e, 0x0008}, {0x20, 0x0000}
+};
+
+static const struct i2c_reg_u16 mt9v011_init[] = {
+	{0x07, 0x0002},	{0x0d, 0x0001},	{0x0d, 0x0000},
+	{0x01, 0x0008},	{0x02, 0x0016},	{0x03, 0x01e1},
+	{0x04, 0x0281},	{0x05, 0x0083},	{0x06, 0x0006},
+	{0x0d, 0x0002}, {0x0a, 0x0000},	{0x0b, 0x0000},
+	{0x0c, 0x0000},	{0x0d, 0x0000},	{0x0e, 0x0000},
+	{0x0f, 0x0000},	{0x10, 0x0000},	{0x11, 0x0000},
+	{0x12, 0x0000},	{0x13, 0x0000},	{0x14, 0x0000},
+	{0x15, 0x0000},	{0x16, 0x0000},	{0x17, 0x0000},
+	{0x18, 0x0000},	{0x19, 0x0000},	{0x1a, 0x0000},
+	{0x1b, 0x0000},	{0x1c, 0x0000},	{0x1d, 0x0000},
+	{0x32, 0x0000},	{0x20, 0x1101},	{0x21, 0x0000},
+	{0x22, 0x0000},	{0x23, 0x0000},	{0x24, 0x0000},
+	{0x25, 0x0000},	{0x26, 0x0000},	{0x27, 0x0024},
+	{0x2f, 0xf7b0},	{0x30, 0x0005},	{0x31, 0x0000},
+	{0x32, 0x0000},	{0x33, 0x0000},	{0x34, 0x0100},
+	{0x3d, 0x068f},	{0x40, 0x01e0},	{0x41, 0x00d1},
+	{0x44, 0x0082},	{0x5a, 0x0000},	{0x5b, 0x0000},
+	{0x5c, 0x0000},	{0x5d, 0x0000},	{0x5e, 0x0000},
+	{0x5f, 0xa31d},	{0x62, 0x0611},	{0x0a, 0x0000},
+	{0x06, 0x0029},	{0x05, 0x0009},	{0x20, 0x1101},
+	{0x20, 0x1101},	{0x09, 0x0064},	{0x07, 0x0003},
+	{0x2b, 0x0033},	{0x2c, 0x00a0},	{0x2d, 0x00a0},
+	{0x2e, 0x0033},	{0x07, 0x0002},	{0x06, 0x0000},
+	{0x06, 0x0029},	{0x05, 0x0009},
+};
+
+static const struct i2c_reg_u16 mt9m001_init[] = {
+	{0x0d, 0x0001},
+	{0x0d, 0x0000},
+	{0x04, 0x0500},		/* hres = 1280 */
+	{0x03, 0x0400},		/* vres = 1024 */
+	{0x20, 0x1100},
+	{0x06, 0x0010},
+	{0x2b, 0x0024},
+	{0x2e, 0x0024},
+	{0x35, 0x0024},
+	{0x2d, 0x0020},
+	{0x2c, 0x0020},
+	{0x09, 0x0ad4},
+	{0x35, 0x0057},
+};
+
+static const struct i2c_reg_u16 mt9m111_init[] = {
+	{0xf0, 0x0000}, {0x0d, 0x0021}, {0x0d, 0x0008},
+	{0xf0, 0x0001}, {0x3a, 0x4300}, {0x9b, 0x4300},
+	{0x06, 0x708e}, {0xf0, 0x0002}, {0x2e, 0x0a1e},
+	{0xf0, 0x0000},
+};
+
+static const struct i2c_reg_u16 mt9m112_init[] = {
+	{0xf0, 0x0000}, {0x0d, 0x0021}, {0x0d, 0x0008},
+	{0xf0, 0x0001}, {0x3a, 0x4300}, {0x9b, 0x4300},
+	{0x06, 0x708e}, {0xf0, 0x0002}, {0x2e, 0x0a1e},
+	{0xf0, 0x0000},
+};
+
+static const struct i2c_reg_u8 hv7131r_init[] = {
+	{0x02, 0x08}, {0x02, 0x00}, {0x01, 0x08},
+	{0x02, 0x00}, {0x20, 0x00}, {0x21, 0xd0},
+	{0x22, 0x00}, {0x23, 0x09}, {0x01, 0x08},
+	{0x01, 0x08}, {0x01, 0x08}, {0x25, 0x07},
+	{0x26, 0xc3}, {0x27, 0x50}, {0x30, 0x62},
+	{0x31, 0x10}, {0x32, 0x06}, {0x33, 0x10},
+	{0x20, 0x00}, {0x21, 0xd0}, {0x22, 0x00},
+	{0x23, 0x09}, {0x01, 0x08},
+};
+
+static void reg_r(struct gspca_dev *gspca_dev, u16 reg, u16 length)
+{
+	struct usb_device *dev = gspca_dev->dev;
+	int result;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+			0x00,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+			reg,
+			0x00,
+			gspca_dev->usb_buf,
+			length,
+			500);
+	if (unlikely(result < 0 || result != length)) {
+		pr_err("Read register %02x failed %d\n", reg, result);
+		gspca_dev->usb_err = result;
+	}
+}
+
+static void reg_w(struct gspca_dev *gspca_dev, u16 reg,
+		 const u8 *buffer, int length)
+{
+	struct usb_device *dev = gspca_dev->dev;
+	int result;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	memcpy(gspca_dev->usb_buf, buffer, length);
+	result = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+			0x08,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+			reg,
+			0x00,
+			gspca_dev->usb_buf,
+			length,
+			500);
+	if (unlikely(result < 0 || result != length)) {
+		pr_err("Write register %02x failed %d\n", reg, result);
+		gspca_dev->usb_err = result;
+	}
+}
+
+static void reg_w1(struct gspca_dev *gspca_dev, u16 reg, const u8 value)
+{
+	reg_w(gspca_dev, reg, &value, 1);
+}
+
+static void i2c_w(struct gspca_dev *gspca_dev, const u8 *buffer)
+{
+	int i;
+
+	reg_w(gspca_dev, 0x10c0, buffer, 8);
+	for (i = 0; i < 5; i++) {
+		reg_r(gspca_dev, 0x10c0, 1);
+		if (gspca_dev->usb_err < 0)
+			return;
+		if (gspca_dev->usb_buf[0] & 0x04) {
+			if (gspca_dev->usb_buf[0] & 0x08) {
+				pr_err("i2c_w error\n");
+				gspca_dev->usb_err = -EIO;
+			}
+			return;
+		}
+		msleep(10);
+	}
+	pr_err("i2c_w reg %02x no response\n", buffer[2]);
+/*	gspca_dev->usb_err = -EIO;	fixme: may occur */
+}
+
+static void i2c_w1(struct gspca_dev *gspca_dev, u8 reg, u8 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 row[8];
+
+	/*
+	 * from the point of view of the bridge, the length
+	 * includes the address
+	 */
+	row[0] = sd->i2c_intf | (2 << 4);
+	row[1] = sd->i2c_addr;
+	row[2] = reg;
+	row[3] = val;
+	row[4] = 0x00;
+	row[5] = 0x00;
+	row[6] = 0x00;
+	row[7] = 0x10;
+
+	i2c_w(gspca_dev, row);
+}
+
+static void i2c_w1_buf(struct gspca_dev *gspca_dev,
+			const struct i2c_reg_u8 *buf, int sz)
+{
+	while (--sz >= 0) {
+		i2c_w1(gspca_dev, buf->reg, buf->val);
+		buf++;
+	}
+}
+
+static void i2c_w2(struct gspca_dev *gspca_dev, u8 reg, u16 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 row[8];
+
+	/*
+	 * from the point of view of the bridge, the length
+	 * includes the address
+	 */
+	row[0] = sd->i2c_intf | (3 << 4);
+	row[1] = sd->i2c_addr;
+	row[2] = reg;
+	row[3] = val >> 8;
+	row[4] = val;
+	row[5] = 0x00;
+	row[6] = 0x00;
+	row[7] = 0x10;
+
+	i2c_w(gspca_dev, row);
+}
+
+static void i2c_w2_buf(struct gspca_dev *gspca_dev,
+			const struct i2c_reg_u16 *buf, int sz)
+{
+	while (--sz >= 0) {
+		i2c_w2(gspca_dev, buf->reg, buf->val);
+		buf++;
+	}
+}
+
+static void i2c_r1(struct gspca_dev *gspca_dev, u8 reg, u8 *val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 row[8];
+
+	row[0] = sd->i2c_intf | (1 << 4);
+	row[1] = sd->i2c_addr;
+	row[2] = reg;
+	row[3] = 0;
+	row[4] = 0;
+	row[5] = 0;
+	row[6] = 0;
+	row[7] = 0x10;
+	i2c_w(gspca_dev, row);
+	row[0] = sd->i2c_intf | (1 << 4) | 0x02;
+	row[2] = 0;
+	i2c_w(gspca_dev, row);
+	reg_r(gspca_dev, 0x10c2, 5);
+	*val = gspca_dev->usb_buf[4];
+}
+
+static void i2c_r2(struct gspca_dev *gspca_dev, u8 reg, u16 *val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 row[8];
+
+	row[0] = sd->i2c_intf | (1 << 4);
+	row[1] = sd->i2c_addr;
+	row[2] = reg;
+	row[3] = 0;
+	row[4] = 0;
+	row[5] = 0;
+	row[6] = 0;
+	row[7] = 0x10;
+	i2c_w(gspca_dev, row);
+	row[0] = sd->i2c_intf | (2 << 4) | 0x02;
+	row[2] = 0;
+	i2c_w(gspca_dev, row);
+	reg_r(gspca_dev, 0x10c2, 5);
+	*val = (gspca_dev->usb_buf[3] << 8) | gspca_dev->usb_buf[4];
+}
+
+static void ov9650_init_sensor(struct gspca_dev *gspca_dev)
+{
+	u16 id;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	i2c_r2(gspca_dev, 0x1c, &id);
+	if (gspca_dev->usb_err < 0)
+		return;
+
+	if (id != 0x7fa2) {
+		pr_err("sensor id for ov9650 doesn't match (0x%04x)\n", id);
+		gspca_dev->usb_err = -ENODEV;
+		return;
+	}
+
+	i2c_w1(gspca_dev, 0x12, 0x80);		/* sensor reset */
+	msleep(200);
+	i2c_w1_buf(gspca_dev, ov9650_init, ARRAY_SIZE(ov9650_init));
+	if (gspca_dev->usb_err < 0)
+		pr_err("OV9650 sensor initialization failed\n");
+	sd->hstart = 1;
+	sd->vstart = 7;
+}
+
+static void ov9655_init_sensor(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	i2c_w1(gspca_dev, 0x12, 0x80);		/* sensor reset */
+	msleep(200);
+	i2c_w1_buf(gspca_dev, ov9655_init, ARRAY_SIZE(ov9655_init));
+	if (gspca_dev->usb_err < 0)
+		pr_err("OV9655 sensor initialization failed\n");
+
+	sd->hstart = 1;
+	sd->vstart = 2;
+}
+
+static void soi968_init_sensor(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	i2c_w1(gspca_dev, 0x12, 0x80);		/* sensor reset */
+	msleep(200);
+	i2c_w1_buf(gspca_dev, soi968_init, ARRAY_SIZE(soi968_init));
+	if (gspca_dev->usb_err < 0)
+		pr_err("SOI968 sensor initialization failed\n");
+
+	sd->hstart = 60;
+	sd->vstart = 11;
+}
+
+static void ov7660_init_sensor(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	i2c_w1(gspca_dev, 0x12, 0x80);		/* sensor reset */
+	msleep(200);
+	i2c_w1_buf(gspca_dev, ov7660_init, ARRAY_SIZE(ov7660_init));
+	if (gspca_dev->usb_err < 0)
+		pr_err("OV7660 sensor initialization failed\n");
+	sd->hstart = 3;
+	sd->vstart = 3;
+}
+
+static void ov7670_init_sensor(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	i2c_w1(gspca_dev, 0x12, 0x80);		/* sensor reset */
+	msleep(200);
+	i2c_w1_buf(gspca_dev, ov7670_init, ARRAY_SIZE(ov7670_init));
+	if (gspca_dev->usb_err < 0)
+		pr_err("OV7670 sensor initialization failed\n");
+
+	sd->hstart = 0;
+	sd->vstart = 1;
+}
+
+static void mt9v_init_sensor(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u16 value;
+
+	sd->i2c_addr = 0x5d;
+	i2c_r2(gspca_dev, 0xff, &value);
+	if (gspca_dev->usb_err >= 0
+	 && value == 0x8243) {
+		i2c_w2_buf(gspca_dev, mt9v011_init, ARRAY_SIZE(mt9v011_init));
+		if (gspca_dev->usb_err < 0) {
+			pr_err("MT9V011 sensor initialization failed\n");
+			return;
+		}
+		sd->hstart = 2;
+		sd->vstart = 2;
+		sd->sensor = SENSOR_MT9V011;
+		pr_info("MT9V011 sensor detected\n");
+		return;
+	}
+
+	gspca_dev->usb_err = 0;
+	sd->i2c_addr = 0x5c;
+	i2c_w2(gspca_dev, 0x01, 0x0004);
+	i2c_r2(gspca_dev, 0xff, &value);
+	if (gspca_dev->usb_err >= 0
+	 && value == 0x823a) {
+		i2c_w2_buf(gspca_dev, mt9v111_init, ARRAY_SIZE(mt9v111_init));
+		if (gspca_dev->usb_err < 0) {
+			pr_err("MT9V111 sensor initialization failed\n");
+			return;
+		}
+		sd->hstart = 2;
+		sd->vstart = 2;
+		sd->sensor = SENSOR_MT9V111;
+		pr_info("MT9V111 sensor detected\n");
+		return;
+	}
+
+	gspca_dev->usb_err = 0;
+	sd->i2c_addr = 0x5d;
+	i2c_w2(gspca_dev, 0xf0, 0x0000);
+	if (gspca_dev->usb_err < 0) {
+		gspca_dev->usb_err = 0;
+		sd->i2c_addr = 0x48;
+		i2c_w2(gspca_dev, 0xf0, 0x0000);
+	}
+	i2c_r2(gspca_dev, 0x00, &value);
+	if (gspca_dev->usb_err >= 0
+	 && value == 0x1229) {
+		i2c_w2_buf(gspca_dev, mt9v112_init, ARRAY_SIZE(mt9v112_init));
+		if (gspca_dev->usb_err < 0) {
+			pr_err("MT9V112 sensor initialization failed\n");
+			return;
+		}
+		sd->hstart = 6;
+		sd->vstart = 2;
+		sd->sensor = SENSOR_MT9V112;
+		pr_info("MT9V112 sensor detected\n");
+		return;
+	}
+
+	gspca_dev->usb_err = -ENODEV;
+}
+
+static void mt9m112_init_sensor(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	i2c_w2_buf(gspca_dev, mt9m112_init, ARRAY_SIZE(mt9m112_init));
+	if (gspca_dev->usb_err < 0)
+		pr_err("MT9M112 sensor initialization failed\n");
+
+	sd->hstart = 0;
+	sd->vstart = 2;
+}
+
+static void mt9m111_init_sensor(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	i2c_w2_buf(gspca_dev, mt9m111_init, ARRAY_SIZE(mt9m111_init));
+	if (gspca_dev->usb_err < 0)
+		pr_err("MT9M111 sensor initialization failed\n");
+
+	sd->hstart = 0;
+	sd->vstart = 2;
+}
+
+static void mt9m001_init_sensor(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u16 id;
+
+	i2c_r2(gspca_dev, 0x00, &id);
+	if (gspca_dev->usb_err < 0)
+		return;
+
+	/* must be 0x8411 or 0x8421 for colour sensor and 8431 for bw */
+	switch (id) {
+	case 0x8411:
+	case 0x8421:
+		pr_info("MT9M001 color sensor detected\n");
+		break;
+	case 0x8431:
+		pr_info("MT9M001 mono sensor detected\n");
+		break;
+	default:
+		pr_err("No MT9M001 chip detected, ID = %x\n\n", id);
+		gspca_dev->usb_err = -ENODEV;
+		return;
+	}
+
+	i2c_w2_buf(gspca_dev, mt9m001_init, ARRAY_SIZE(mt9m001_init));
+	if (gspca_dev->usb_err < 0)
+		pr_err("MT9M001 sensor initialization failed\n");
+
+	sd->hstart = 1;
+	sd->vstart = 1;
+}
+
+static void hv7131r_init_sensor(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	i2c_w1_buf(gspca_dev, hv7131r_init, ARRAY_SIZE(hv7131r_init));
+	if (gspca_dev->usb_err < 0)
+		pr_err("HV7131R Sensor initialization failed\n");
+
+	sd->hstart = 0;
+	sd->vstart = 1;
+}
+
+static void set_cmatrix(struct gspca_dev *gspca_dev,
+		s32 brightness, s32 contrast, s32 satur, s32 hue)
+{
+	s32 hue_coord, hue_index = 180 + hue;
+	u8 cmatrix[21];
+
+	memset(cmatrix, 0, sizeof(cmatrix));
+	cmatrix[2] = (contrast * 0x25 / 0x100) + 0x26;
+	cmatrix[0] = 0x13 + (cmatrix[2] - 0x26) * 0x13 / 0x25;
+	cmatrix[4] = 0x07 + (cmatrix[2] - 0x26) * 0x07 / 0x25;
+	cmatrix[18] = brightness - 0x80;
+
+	hue_coord = (hsv_red_x[hue_index] * satur) >> 8;
+	cmatrix[6] = hue_coord;
+	cmatrix[7] = (hue_coord >> 8) & 0x0f;
+
+	hue_coord = (hsv_red_y[hue_index] * satur) >> 8;
+	cmatrix[8] = hue_coord;
+	cmatrix[9] = (hue_coord >> 8) & 0x0f;
+
+	hue_coord = (hsv_green_x[hue_index] * satur) >> 8;
+	cmatrix[10] = hue_coord;
+	cmatrix[11] = (hue_coord >> 8) & 0x0f;
+
+	hue_coord = (hsv_green_y[hue_index] * satur) >> 8;
+	cmatrix[12] = hue_coord;
+	cmatrix[13] = (hue_coord >> 8) & 0x0f;
+
+	hue_coord = (hsv_blue_x[hue_index] * satur) >> 8;
+	cmatrix[14] = hue_coord;
+	cmatrix[15] = (hue_coord >> 8) & 0x0f;
+
+	hue_coord = (hsv_blue_y[hue_index] * satur) >> 8;
+	cmatrix[16] = hue_coord;
+	cmatrix[17] = (hue_coord >> 8) & 0x0f;
+
+	reg_w(gspca_dev, 0x10e1, cmatrix, 21);
+}
+
+static void set_gamma(struct gspca_dev *gspca_dev, s32 val)
+{
+	u8 gamma[17];
+	u8 gval = val * 0xb8 / 0x100;
+
+	gamma[0] = 0x0a;
+	gamma[1] = 0x13 + (gval * (0xcb - 0x13) / 0xb8);
+	gamma[2] = 0x25 + (gval * (0xee - 0x25) / 0xb8);
+	gamma[3] = 0x37 + (gval * (0xfa - 0x37) / 0xb8);
+	gamma[4] = 0x45 + (gval * (0xfc - 0x45) / 0xb8);
+	gamma[5] = 0x55 + (gval * (0xfb - 0x55) / 0xb8);
+	gamma[6] = 0x65 + (gval * (0xfc - 0x65) / 0xb8);
+	gamma[7] = 0x74 + (gval * (0xfd - 0x74) / 0xb8);
+	gamma[8] = 0x83 + (gval * (0xfe - 0x83) / 0xb8);
+	gamma[9] = 0x92 + (gval * (0xfc - 0x92) / 0xb8);
+	gamma[10] = 0xa1 + (gval * (0xfc - 0xa1) / 0xb8);
+	gamma[11] = 0xb0 + (gval * (0xfc - 0xb0) / 0xb8);
+	gamma[12] = 0xbf + (gval * (0xfb - 0xbf) / 0xb8);
+	gamma[13] = 0xce + (gval * (0xfb - 0xce) / 0xb8);
+	gamma[14] = 0xdf + (gval * (0xfd - 0xdf) / 0xb8);
+	gamma[15] = 0xea + (gval * (0xf9 - 0xea) / 0xb8);
+	gamma[16] = 0xf5;
+
+	reg_w(gspca_dev, 0x1190, gamma, 17);
+}
+
+static void set_redblue(struct gspca_dev *gspca_dev, s32 blue, s32 red)
+{
+	reg_w1(gspca_dev, 0x118c, red);
+	reg_w1(gspca_dev, 0x118f, blue);
+}
+
+static void set_hvflip(struct gspca_dev *gspca_dev, s32 hflip, s32 vflip)
+{
+	u8 value, tslb;
+	u16 value2;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if ((sd->flags & FLIP_DETECT) && dmi_check_system(flip_dmi_table)) {
+		hflip = !hflip;
+		vflip = !vflip;
+	}
+
+	switch (sd->sensor) {
+	case SENSOR_OV7660:
+		value = 0x01;
+		if (hflip)
+			value |= 0x20;
+		if (vflip) {
+			value |= 0x10;
+			sd->vstart = 2;
+		} else {
+			sd->vstart = 3;
+		}
+		reg_w1(gspca_dev, 0x1182, sd->vstart);
+		i2c_w1(gspca_dev, 0x1e, value);
+		break;
+	case SENSOR_OV9650:
+		i2c_r1(gspca_dev, 0x1e, &value);
+		value &= ~0x30;
+		tslb = 0x01;
+		if (hflip)
+			value |= 0x20;
+		if (vflip) {
+			value |= 0x10;
+			tslb = 0x49;
+		}
+		i2c_w1(gspca_dev, 0x1e, value);
+		i2c_w1(gspca_dev, 0x3a, tslb);
+		break;
+	case SENSOR_MT9V111:
+	case SENSOR_MT9V011:
+		i2c_r2(gspca_dev, 0x20, &value2);
+		value2 &= ~0xc0a0;
+		if (hflip)
+			value2 |= 0x8080;
+		if (vflip)
+			value2 |= 0x4020;
+		i2c_w2(gspca_dev, 0x20, value2);
+		break;
+	case SENSOR_MT9M112:
+	case SENSOR_MT9M111:
+	case SENSOR_MT9V112:
+		i2c_r2(gspca_dev, 0x20, &value2);
+		value2 &= ~0x0003;
+		if (hflip)
+			value2 |= 0x0002;
+		if (vflip)
+			value2 |= 0x0001;
+		i2c_w2(gspca_dev, 0x20, value2);
+		break;
+	case SENSOR_HV7131R:
+		i2c_r1(gspca_dev, 0x01, &value);
+		value &= ~0x03;
+		if (vflip)
+			value |= 0x01;
+		if (hflip)
+			value |= 0x02;
+		i2c_w1(gspca_dev, 0x01, value);
+		break;
+	}
+}
+
+static void set_exposure(struct gspca_dev *gspca_dev, s32 expo)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 exp[8] = {sd->i2c_intf, sd->i2c_addr,
+				0x00, 0x00, 0x00, 0x00, 0x00, 0x10};
+	int expo2;
+
+	if (gspca_dev->streaming)
+		exp[7] = 0x1e;
+
+	switch (sd->sensor) {
+	case SENSOR_OV7660:
+	case SENSOR_OV7670:
+	case SENSOR_OV9655:
+	case SENSOR_OV9650:
+		if (expo > 547)
+			expo2 = 547;
+		else
+			expo2 = expo;
+		exp[0] |= (2 << 4);
+		exp[2] = 0x10;			/* AECH */
+		exp[3] = expo2 >> 2;
+		exp[7] = 0x10;
+		i2c_w(gspca_dev, exp);
+		exp[2] = 0x04;			/* COM1 */
+		exp[3] = expo2 & 0x0003;
+		exp[7] = 0x10;
+		i2c_w(gspca_dev, exp);
+		expo -= expo2;
+		exp[7] = 0x1e;
+		exp[0] |= (3 << 4);
+		exp[2] = 0x2d;			/* ADVFL & ADVFH */
+		exp[3] = expo;
+		exp[4] = expo >> 8;
+		break;
+	case SENSOR_MT9M001:
+	case SENSOR_MT9V112:
+	case SENSOR_MT9V011:
+		exp[0] |= (3 << 4);
+		exp[2] = 0x09;
+		exp[3] = expo >> 8;
+		exp[4] = expo;
+		break;
+	case SENSOR_HV7131R:
+		exp[0] |= (4 << 4);
+		exp[2] = 0x25;
+		exp[3] = expo >> 5;
+		exp[4] = expo << 3;
+		exp[5] = 0;
+		break;
+	default:
+		return;
+	}
+	i2c_w(gspca_dev, exp);
+}
+
+static void set_gain(struct gspca_dev *gspca_dev, s32 g)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 gain[8] = {sd->i2c_intf, sd->i2c_addr,
+				0x00, 0x00, 0x00, 0x00, 0x00, 0x10};
+
+	if (gspca_dev->streaming)
+		gain[7] = 0x15;		/* or 1d ? */
+
+	switch (sd->sensor) {
+	case SENSOR_OV7660:
+	case SENSOR_OV7670:
+	case SENSOR_SOI968:
+	case SENSOR_OV9655:
+	case SENSOR_OV9650:
+		gain[0] |= (2 << 4);
+		gain[3] = ov_gain[g];
+		break;
+	case SENSOR_MT9V011:
+		gain[0] |= (3 << 4);
+		gain[2] = 0x35;
+		gain[3] = micron1_gain[g] >> 8;
+		gain[4] = micron1_gain[g];
+		break;
+	case SENSOR_MT9V112:
+		gain[0] |= (3 << 4);
+		gain[2] = 0x2f;
+		gain[3] = micron1_gain[g] >> 8;
+		gain[4] = micron1_gain[g];
+		break;
+	case SENSOR_MT9M001:
+		gain[0] |= (3 << 4);
+		gain[2] = 0x2f;
+		gain[3] = micron2_gain[g] >> 8;
+		gain[4] = micron2_gain[g];
+		break;
+	case SENSOR_HV7131R:
+		gain[0] |= (2 << 4);
+		gain[2] = 0x30;
+		gain[3] = hv7131r_gain[g];
+		break;
+	default:
+		return;
+	}
+	i2c_w(gspca_dev, gain);
+}
+
+static void set_quality(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	jpeg_set_qual(sd->jpeg_hdr, val);
+	reg_w1(gspca_dev, 0x1061, 0x01);	/* stop transfer */
+	reg_w1(gspca_dev, 0x10e0, sd->fmt | 0x20); /* write QTAB */
+	reg_w(gspca_dev, 0x1100, &sd->jpeg_hdr[JPEG_QT0_OFFSET], 64);
+	reg_w(gspca_dev, 0x1140, &sd->jpeg_hdr[JPEG_QT1_OFFSET], 64);
+	reg_w1(gspca_dev, 0x1061, 0x03);	/* restart transfer */
+	reg_w1(gspca_dev, 0x10e0, sd->fmt);
+	sd->fmt ^= 0x0c;			/* invert QTAB use + write */
+	reg_w1(gspca_dev, 0x10e0, sd->fmt);
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int sd_dbg_g_register(struct gspca_dev *gspca_dev,
+			struct v4l2_dbg_register *reg)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	reg->size = 1;
+	switch (reg->match.addr) {
+	case 0:
+		if (reg->reg < 0x1000 || reg->reg > 0x11ff)
+			return -EINVAL;
+		reg_r(gspca_dev, reg->reg, 1);
+		reg->val = gspca_dev->usb_buf[0];
+		return gspca_dev->usb_err;
+	case 1:
+		if (sd->sensor >= SENSOR_MT9V011 &&
+		    sd->sensor <= SENSOR_MT9M112) {
+			i2c_r2(gspca_dev, reg->reg, (u16 *) &reg->val);
+			reg->size = 2;
+		} else {
+			i2c_r1(gspca_dev, reg->reg, (u8 *) &reg->val);
+		}
+		return gspca_dev->usb_err;
+	}
+	return -EINVAL;
+}
+
+static int sd_dbg_s_register(struct gspca_dev *gspca_dev,
+			const struct v4l2_dbg_register *reg)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	switch (reg->match.addr) {
+	case 0:
+		if (reg->reg < 0x1000 || reg->reg > 0x11ff)
+			return -EINVAL;
+		reg_w1(gspca_dev, reg->reg, reg->val);
+		return gspca_dev->usb_err;
+	case 1:
+		if (sd->sensor >= SENSOR_MT9V011 &&
+		    sd->sensor <= SENSOR_MT9M112) {
+			i2c_w2(gspca_dev, reg->reg, reg->val);
+		} else {
+			i2c_w1(gspca_dev, reg->reg, reg->val);
+		}
+		return gspca_dev->usb_err;
+	}
+	return -EINVAL;
+}
+
+static int sd_chip_info(struct gspca_dev *gspca_dev,
+			struct v4l2_dbg_chip_info *chip)
+{
+	if (chip->match.addr > 1)
+		return -EINVAL;
+	if (chip->match.addr == 1)
+		strlcpy(chip->name, "sensor", sizeof(chip->name));
+	return 0;
+}
+#endif
+
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam;
+
+	cam = &gspca_dev->cam;
+	cam->needs_full_bandwidth = 1;
+
+	sd->sensor = id->driver_info >> 8;
+	sd->i2c_addr = id->driver_info;
+	sd->flags = id->driver_info >> 16;
+	sd->i2c_intf = 0x80;			/* i2c 100 Kb/s */
+
+	switch (sd->sensor) {
+	case SENSOR_MT9M112:
+	case SENSOR_MT9M111:
+	case SENSOR_OV9650:
+	case SENSOR_SOI968:
+		cam->cam_mode = sxga_mode;
+		cam->nmodes = ARRAY_SIZE(sxga_mode);
+		break;
+	case SENSOR_MT9M001:
+		cam->cam_mode = mono_mode;
+		cam->nmodes = ARRAY_SIZE(mono_mode);
+		break;
+	case SENSOR_HV7131R:
+		sd->i2c_intf = 0x81;			/* i2c 400 Kb/s */
+		/* fall thru */
+	default:
+		cam->cam_mode = vga_mode;
+		cam->nmodes = ARRAY_SIZE(vga_mode);
+		break;
+	}
+
+	sd->old_step = 0;
+	sd->older_step = 0;
+	sd->exposure_step = 16;
+
+	INIT_WORK(&sd->work, qual_upd);
+
+	return 0;
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *)gspca_dev;
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	/* color control cluster */
+	case V4L2_CID_BRIGHTNESS:
+		set_cmatrix(gspca_dev, sd->brightness->val,
+			sd->contrast->val, sd->saturation->val, sd->hue->val);
+		break;
+	case V4L2_CID_GAMMA:
+		set_gamma(gspca_dev, ctrl->val);
+		break;
+	/* blue/red balance cluster */
+	case V4L2_CID_BLUE_BALANCE:
+		set_redblue(gspca_dev, sd->blue->val, sd->red->val);
+		break;
+	/* h/vflip cluster */
+	case V4L2_CID_HFLIP:
+		set_hvflip(gspca_dev, sd->hflip->val, sd->vflip->val);
+		break;
+	/* standalone exposure control */
+	case V4L2_CID_EXPOSURE:
+		set_exposure(gspca_dev, ctrl->val);
+		break;
+	/* standalone gain control */
+	case V4L2_CID_GAIN:
+		set_gain(gspca_dev, ctrl->val);
+		break;
+	/* autogain + exposure or gain control cluster */
+	case V4L2_CID_AUTOGAIN:
+		if (sd->sensor == SENSOR_SOI968)
+			set_gain(gspca_dev, sd->gain->val);
+		else
+			set_exposure(gspca_dev, sd->exposure->val);
+		break;
+	case V4L2_CID_JPEG_COMPRESSION_QUALITY:
+		set_quality(gspca_dev, ctrl->val);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 13);
+
+	sd->brightness = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
+	sd->contrast = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_CONTRAST, 0, 255, 1, 127);
+	sd->saturation = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_SATURATION, 0, 255, 1, 127);
+	sd->hue = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_HUE, -180, 180, 1, 0);
+
+	sd->gamma = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_GAMMA, 0, 255, 1, 0x10);
+
+	sd->blue = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BLUE_BALANCE, 0, 127, 1, 0x28);
+	sd->red = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_RED_BALANCE, 0, 127, 1, 0x28);
+
+	if (sd->sensor != SENSOR_OV9655 && sd->sensor != SENSOR_SOI968 &&
+	    sd->sensor != SENSOR_OV7670 && sd->sensor != SENSOR_MT9M001 &&
+	    sd->sensor != SENSOR_MT9VPRB) {
+		sd->hflip = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_HFLIP, 0, 1, 1, 0);
+		sd->vflip = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_VFLIP, 0, 1, 1, 0);
+	}
+
+	if (sd->sensor != SENSOR_SOI968 && sd->sensor != SENSOR_MT9VPRB &&
+	    sd->sensor != SENSOR_MT9M112 && sd->sensor != SENSOR_MT9M111 &&
+	    sd->sensor != SENSOR_MT9V111)
+		sd->exposure = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_EXPOSURE, 0, 0x1780, 1, 0x33);
+
+	if (sd->sensor != SENSOR_MT9VPRB && sd->sensor != SENSOR_MT9M112 &&
+	    sd->sensor != SENSOR_MT9M111 && sd->sensor != SENSOR_MT9V111) {
+		sd->gain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_GAIN, 0, 28, 1, 0);
+		sd->autogain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
+	}
+
+	sd->jpegqual = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_JPEG_COMPRESSION_QUALITY, 50, 90, 1, 80);
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+
+	v4l2_ctrl_cluster(4, &sd->brightness);
+	v4l2_ctrl_cluster(2, &sd->blue);
+	if (sd->hflip)
+		v4l2_ctrl_cluster(2, &sd->hflip);
+	if (sd->autogain) {
+		if (sd->sensor == SENSOR_SOI968)
+			/* this sensor doesn't have the exposure control and
+			   autogain is clustered with gain instead. This works
+			   because sd->exposure == NULL. */
+			v4l2_ctrl_auto_cluster(3, &sd->autogain, 0, false);
+		else
+			/* Otherwise autogain is clustered with exposure. */
+			v4l2_ctrl_auto_cluster(2, &sd->autogain, 0, false);
+	}
+	return 0;
+}
+
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int i;
+	u8 value;
+	u8 i2c_init[9] = {
+		0x80, sd->i2c_addr, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03
+	};
+
+	for (i = 0; i < ARRAY_SIZE(bridge_init); i++) {
+		value = bridge_init[i][1];
+		reg_w(gspca_dev, bridge_init[i][0], &value, 1);
+		if (gspca_dev->usb_err < 0) {
+			pr_err("Device initialization failed\n");
+			return gspca_dev->usb_err;
+		}
+	}
+
+	if (sd->flags & LED_REVERSE)
+		reg_w1(gspca_dev, 0x1006, 0x00);
+	else
+		reg_w1(gspca_dev, 0x1006, 0x20);
+
+	reg_w(gspca_dev, 0x10c0, i2c_init, 9);
+	if (gspca_dev->usb_err < 0) {
+		pr_err("Device initialization failed\n");
+		return gspca_dev->usb_err;
+	}
+
+	switch (sd->sensor) {
+	case SENSOR_OV9650:
+		ov9650_init_sensor(gspca_dev);
+		if (gspca_dev->usb_err < 0)
+			break;
+		pr_info("OV9650 sensor detected\n");
+		break;
+	case SENSOR_OV9655:
+		ov9655_init_sensor(gspca_dev);
+		if (gspca_dev->usb_err < 0)
+			break;
+		pr_info("OV9655 sensor detected\n");
+		break;
+	case SENSOR_SOI968:
+		soi968_init_sensor(gspca_dev);
+		if (gspca_dev->usb_err < 0)
+			break;
+		pr_info("SOI968 sensor detected\n");
+		break;
+	case SENSOR_OV7660:
+		ov7660_init_sensor(gspca_dev);
+		if (gspca_dev->usb_err < 0)
+			break;
+		pr_info("OV7660 sensor detected\n");
+		break;
+	case SENSOR_OV7670:
+		ov7670_init_sensor(gspca_dev);
+		if (gspca_dev->usb_err < 0)
+			break;
+		pr_info("OV7670 sensor detected\n");
+		break;
+	case SENSOR_MT9VPRB:
+		mt9v_init_sensor(gspca_dev);
+		if (gspca_dev->usb_err < 0)
+			break;
+		pr_info("MT9VPRB sensor detected\n");
+		break;
+	case SENSOR_MT9M111:
+		mt9m111_init_sensor(gspca_dev);
+		if (gspca_dev->usb_err < 0)
+			break;
+		pr_info("MT9M111 sensor detected\n");
+		break;
+	case SENSOR_MT9M112:
+		mt9m112_init_sensor(gspca_dev);
+		if (gspca_dev->usb_err < 0)
+			break;
+		pr_info("MT9M112 sensor detected\n");
+		break;
+	case SENSOR_MT9M001:
+		mt9m001_init_sensor(gspca_dev);
+		if (gspca_dev->usb_err < 0)
+			break;
+		break;
+	case SENSOR_HV7131R:
+		hv7131r_init_sensor(gspca_dev);
+		if (gspca_dev->usb_err < 0)
+			break;
+		pr_info("HV7131R sensor detected\n");
+		break;
+	default:
+		pr_err("Unsupported sensor\n");
+		gspca_dev->usb_err = -ENODEV;
+	}
+	return gspca_dev->usb_err;
+}
+
+static void configure_sensor_output(struct gspca_dev *gspca_dev, int mode)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 value;
+
+	switch (sd->sensor) {
+	case SENSOR_SOI968:
+		if (mode & MODE_SXGA) {
+			i2c_w1(gspca_dev, 0x17, 0x1d);
+			i2c_w1(gspca_dev, 0x18, 0xbd);
+			i2c_w1(gspca_dev, 0x19, 0x01);
+			i2c_w1(gspca_dev, 0x1a, 0x81);
+			i2c_w1(gspca_dev, 0x12, 0x00);
+			sd->hstart = 140;
+			sd->vstart = 19;
+		} else {
+			i2c_w1(gspca_dev, 0x17, 0x13);
+			i2c_w1(gspca_dev, 0x18, 0x63);
+			i2c_w1(gspca_dev, 0x19, 0x01);
+			i2c_w1(gspca_dev, 0x1a, 0x79);
+			i2c_w1(gspca_dev, 0x12, 0x40);
+			sd->hstart = 60;
+			sd->vstart = 11;
+		}
+		break;
+	case SENSOR_OV9650:
+		if (mode & MODE_SXGA) {
+			i2c_w1(gspca_dev, 0x17, 0x1b);
+			i2c_w1(gspca_dev, 0x18, 0xbc);
+			i2c_w1(gspca_dev, 0x19, 0x01);
+			i2c_w1(gspca_dev, 0x1a, 0x82);
+			i2c_r1(gspca_dev, 0x12, &value);
+			i2c_w1(gspca_dev, 0x12, value & 0x07);
+		} else {
+			i2c_w1(gspca_dev, 0x17, 0x24);
+			i2c_w1(gspca_dev, 0x18, 0xc5);
+			i2c_w1(gspca_dev, 0x19, 0x00);
+			i2c_w1(gspca_dev, 0x1a, 0x3c);
+			i2c_r1(gspca_dev, 0x12, &value);
+			i2c_w1(gspca_dev, 0x12, (value & 0x7) | 0x40);
+		}
+		break;
+	case SENSOR_MT9M112:
+	case SENSOR_MT9M111:
+		if (mode & MODE_SXGA) {
+			i2c_w2(gspca_dev, 0xf0, 0x0002);
+			i2c_w2(gspca_dev, 0xc8, 0x970b);
+			i2c_w2(gspca_dev, 0xf0, 0x0000);
+		} else {
+			i2c_w2(gspca_dev, 0xf0, 0x0002);
+			i2c_w2(gspca_dev, 0xc8, 0x8000);
+			i2c_w2(gspca_dev, 0xf0, 0x0000);
+		}
+		break;
+	}
+}
+
+static int sd_isoc_init(struct gspca_dev *gspca_dev)
+{
+	struct usb_interface *intf;
+	u32 flags = gspca_dev->cam.cam_mode[(int)gspca_dev->curr_mode].priv;
+
+	/*
+	 * When using the SN9C20X_I420 fmt the sn9c20x needs more bandwidth
+	 * than our regular bandwidth calculations reserve, so we force the
+	 * use of a specific altsetting when using the SN9C20X_I420 fmt.
+	 */
+	if (!(flags & (MODE_RAW | MODE_JPEG))) {
+		intf = usb_ifnum_to_if(gspca_dev->dev, gspca_dev->iface);
+
+		if (intf->num_altsetting != 9) {
+			pr_warn("sn9c20x camera with unknown number of alt settings (%d), please report!\n",
+				intf->num_altsetting);
+			gspca_dev->alt = intf->num_altsetting;
+			return 0;
+		}
+
+		switch (gspca_dev->pixfmt.width) {
+		case 160: /* 160x120 */
+			gspca_dev->alt = 2;
+			break;
+		case 320: /* 320x240 */
+			gspca_dev->alt = 6;
+			break;
+		default:  /* >= 640x480 */
+			gspca_dev->alt = 9;
+			break;
+		}
+	}
+
+	return 0;
+}
+
+#define HW_WIN(mode, hstart, vstart) \
+((const u8 []){hstart, 0, vstart, 0, \
+(mode & MODE_SXGA ? 1280 >> 4 : 640 >> 4), \
+(mode & MODE_SXGA ? 1024 >> 3 : 480 >> 3)})
+
+#define CLR_WIN(width, height) \
+((const u8 [])\
+{0, width >> 2, 0, height >> 1,\
+((width >> 10) & 0x01) | ((height >> 8) & 0x6)})
+
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int mode = gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv;
+	int width = gspca_dev->pixfmt.width;
+	int height = gspca_dev->pixfmt.height;
+	u8 fmt, scale = 0;
+
+	jpeg_define(sd->jpeg_hdr, height, width,
+			0x21);
+	jpeg_set_qual(sd->jpeg_hdr, v4l2_ctrl_g_ctrl(sd->jpegqual));
+
+	if (mode & MODE_RAW)
+		fmt = 0x2d;
+	else if (mode & MODE_JPEG)
+		fmt = 0x24;
+	else
+		fmt = 0x2f;	/* YUV 420 */
+	sd->fmt = fmt;
+
+	switch (mode & SCALE_MASK) {
+	case SCALE_1280x1024:
+		scale = 0xc0;
+		pr_info("Set 1280x1024\n");
+		break;
+	case SCALE_640x480:
+		scale = 0x80;
+		pr_info("Set 640x480\n");
+		break;
+	case SCALE_320x240:
+		scale = 0x90;
+		pr_info("Set 320x240\n");
+		break;
+	case SCALE_160x120:
+		scale = 0xa0;
+		pr_info("Set 160x120\n");
+		break;
+	}
+
+	configure_sensor_output(gspca_dev, mode);
+	reg_w(gspca_dev, 0x1100, &sd->jpeg_hdr[JPEG_QT0_OFFSET], 64);
+	reg_w(gspca_dev, 0x1140, &sd->jpeg_hdr[JPEG_QT1_OFFSET], 64);
+	reg_w(gspca_dev, 0x10fb, CLR_WIN(width, height), 5);
+	reg_w(gspca_dev, 0x1180, HW_WIN(mode, sd->hstart, sd->vstart), 6);
+	reg_w1(gspca_dev, 0x1189, scale);
+	reg_w1(gspca_dev, 0x10e0, fmt);
+
+	set_cmatrix(gspca_dev, v4l2_ctrl_g_ctrl(sd->brightness),
+			v4l2_ctrl_g_ctrl(sd->contrast),
+			v4l2_ctrl_g_ctrl(sd->saturation),
+			v4l2_ctrl_g_ctrl(sd->hue));
+	set_gamma(gspca_dev, v4l2_ctrl_g_ctrl(sd->gamma));
+	set_redblue(gspca_dev, v4l2_ctrl_g_ctrl(sd->blue),
+			v4l2_ctrl_g_ctrl(sd->red));
+	if (sd->gain)
+		set_gain(gspca_dev, v4l2_ctrl_g_ctrl(sd->gain));
+	if (sd->exposure)
+		set_exposure(gspca_dev, v4l2_ctrl_g_ctrl(sd->exposure));
+	if (sd->hflip)
+		set_hvflip(gspca_dev, v4l2_ctrl_g_ctrl(sd->hflip),
+				v4l2_ctrl_g_ctrl(sd->vflip));
+
+	reg_w1(gspca_dev, 0x1007, 0x20);
+	reg_w1(gspca_dev, 0x1061, 0x03);
+
+	/* if JPEG, prepare the compression quality update */
+	if (mode & MODE_JPEG) {
+		sd->pktsz = sd->npkt = 0;
+		sd->nchg = 0;
+	}
+
+	return gspca_dev->usb_err;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	reg_w1(gspca_dev, 0x1007, 0x00);
+	reg_w1(gspca_dev, 0x1061, 0x01);
+}
+
+/* called on streamoff with alt==0 and on disconnect */
+/* the usb_lock is held at entry - restore on exit */
+static void sd_stop0(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	mutex_unlock(&gspca_dev->usb_lock);
+	flush_work(&sd->work);
+	mutex_lock(&gspca_dev->usb_lock);
+}
+
+static void do_autoexposure(struct gspca_dev *gspca_dev, u16 avg_lum)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	s32 cur_exp = v4l2_ctrl_g_ctrl(sd->exposure);
+	s32 max = sd->exposure->maximum - sd->exposure_step;
+	s32 min = sd->exposure->minimum + sd->exposure_step;
+	s16 new_exp;
+
+	/*
+	 * some hardcoded values are present
+	 * like those for maximal/minimal exposure
+	 * and exposure steps
+	 */
+	if (avg_lum < MIN_AVG_LUM) {
+		if (cur_exp > max)
+			return;
+
+		new_exp = cur_exp + sd->exposure_step;
+		if (new_exp > max)
+			new_exp = max;
+		if (new_exp < min)
+			new_exp = min;
+		v4l2_ctrl_s_ctrl(sd->exposure, new_exp);
+
+		sd->older_step = sd->old_step;
+		sd->old_step = 1;
+
+		if (sd->old_step ^ sd->older_step)
+			sd->exposure_step /= 2;
+		else
+			sd->exposure_step += 2;
+	}
+	if (avg_lum > MAX_AVG_LUM) {
+		if (cur_exp < min)
+			return;
+		new_exp = cur_exp - sd->exposure_step;
+		if (new_exp > max)
+			new_exp = max;
+		if (new_exp < min)
+			new_exp = min;
+		v4l2_ctrl_s_ctrl(sd->exposure, new_exp);
+		sd->older_step = sd->old_step;
+		sd->old_step = 0;
+
+		if (sd->old_step ^ sd->older_step)
+			sd->exposure_step /= 2;
+		else
+			sd->exposure_step += 2;
+	}
+}
+
+static void do_autogain(struct gspca_dev *gspca_dev, u16 avg_lum)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	s32 cur_gain = v4l2_ctrl_g_ctrl(sd->gain);
+
+	if (avg_lum < MIN_AVG_LUM && cur_gain < sd->gain->maximum)
+		v4l2_ctrl_s_ctrl(sd->gain, cur_gain + 1);
+	if (avg_lum > MAX_AVG_LUM && cur_gain > sd->gain->minimum)
+		v4l2_ctrl_s_ctrl(sd->gain, cur_gain - 1);
+}
+
+static void sd_dqcallback(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int avg_lum;
+
+	if (sd->autogain == NULL || !v4l2_ctrl_g_ctrl(sd->autogain))
+		return;
+
+	avg_lum = atomic_read(&sd->avg_lum);
+	if (sd->sensor == SENSOR_SOI968)
+		do_autogain(gspca_dev, avg_lum);
+	else
+		do_autoexposure(gspca_dev, avg_lum);
+}
+
+/* JPEG quality update */
+/* This function is executed from a work queue. */
+static void qual_upd(struct work_struct *work)
+{
+	struct sd *sd = container_of(work, struct sd, work);
+	struct gspca_dev *gspca_dev = &sd->gspca_dev;
+	s32 qual = v4l2_ctrl_g_ctrl(sd->jpegqual);
+
+	/* To protect gspca_dev->usb_buf and gspca_dev->usb_err */
+	mutex_lock(&gspca_dev->usb_lock);
+	gspca_dbg(gspca_dev, D_STREAM, "qual_upd %d%%\n", qual);
+	gspca_dev->usb_err = 0;
+	set_quality(gspca_dev, qual);
+	mutex_unlock(&gspca_dev->usb_lock);
+}
+
+#if IS_ENABLED(CONFIG_INPUT)
+static int sd_int_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,		/* interrupt packet */
+			int len)		/* interrupt packet length */
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (!(sd->flags & HAS_NO_BUTTON) && len == 1) {
+		input_report_key(gspca_dev->input_dev, KEY_CAMERA, 1);
+		input_sync(gspca_dev->input_dev);
+		input_report_key(gspca_dev->input_dev, KEY_CAMERA, 0);
+		input_sync(gspca_dev->input_dev);
+		return 0;
+	}
+	return -EINVAL;
+}
+#endif
+
+/* check the JPEG compression */
+static void transfer_check(struct gspca_dev *gspca_dev,
+			u8 *data)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int new_qual, r;
+
+	new_qual = 0;
+
+	/* if USB error, discard the frame and decrease the quality */
+	if (data[6] & 0x08) {				/* USB FIFO full */
+		gspca_dev->last_packet_type = DISCARD_PACKET;
+		new_qual = -5;
+	} else {
+
+		/* else, compute the filling rate and a new JPEG quality */
+		r = (sd->pktsz * 100) /
+			(sd->npkt *
+				gspca_dev->urb[0]->iso_frame_desc[0].length);
+		if (r >= 85)
+			new_qual = -3;
+		else if (r < 75)
+			new_qual = 2;
+	}
+	if (new_qual != 0) {
+		sd->nchg += new_qual;
+		if (sd->nchg < -6 || sd->nchg >= 12) {
+			/* Note: we are in interrupt context, so we can't
+			   use v4l2_ctrl_g/s_ctrl here. Access the value
+			   directly instead. */
+			s32 curqual = sd->jpegqual->cur.val;
+			sd->nchg = 0;
+			new_qual += curqual;
+			if (new_qual < sd->jpegqual->minimum)
+				new_qual = sd->jpegqual->minimum;
+			else if (new_qual > sd->jpegqual->maximum)
+				new_qual = sd->jpegqual->maximum;
+			if (new_qual != curqual) {
+				sd->jpegqual->cur.val = new_qual;
+				schedule_work(&sd->work);
+			}
+		}
+	} else {
+		sd->nchg = 0;
+	}
+	sd->pktsz = sd->npkt = 0;
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int avg_lum, is_jpeg;
+	static const u8 frame_header[] = {
+		0xff, 0xff, 0x00, 0xc4, 0xc4, 0x96
+	};
+
+	is_jpeg = (sd->fmt & 0x03) == 0;
+	if (len >= 64 && memcmp(data, frame_header, 6) == 0) {
+		avg_lum = ((data[35] >> 2) & 3) |
+			   (data[20] << 2) |
+			   (data[19] << 10);
+		avg_lum += ((data[35] >> 4) & 3) |
+			    (data[22] << 2) |
+			    (data[21] << 10);
+		avg_lum += ((data[35] >> 6) & 3) |
+			    (data[24] << 2) |
+			    (data[23] << 10);
+		avg_lum += (data[36] & 3) |
+			   (data[26] << 2) |
+			   (data[25] << 10);
+		avg_lum += ((data[36] >> 2) & 3) |
+			    (data[28] << 2) |
+			    (data[27] << 10);
+		avg_lum += ((data[36] >> 4) & 3) |
+			    (data[30] << 2) |
+			    (data[29] << 10);
+		avg_lum += ((data[36] >> 6) & 3) |
+			    (data[32] << 2) |
+			    (data[31] << 10);
+		avg_lum += ((data[44] >> 4) & 3) |
+			    (data[34] << 2) |
+			    (data[33] << 10);
+		avg_lum >>= 9;
+		atomic_set(&sd->avg_lum, avg_lum);
+
+		if (is_jpeg)
+			transfer_check(gspca_dev, data);
+
+		gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
+		len -= 64;
+		if (len == 0)
+			return;
+		data += 64;
+	}
+	if (gspca_dev->last_packet_type == LAST_PACKET) {
+		if (is_jpeg) {
+			gspca_frame_add(gspca_dev, FIRST_PACKET,
+				sd->jpeg_hdr, JPEG_HDR_SZ);
+			gspca_frame_add(gspca_dev, INTER_PACKET,
+				data, len);
+		} else {
+			gspca_frame_add(gspca_dev, FIRST_PACKET,
+				data, len);
+		}
+	} else {
+		/* if JPEG, count the packets and their size */
+		if (is_jpeg) {
+			sd->npkt++;
+			sd->pktsz += len;
+		}
+		gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+	}
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = KBUILD_MODNAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.isoc_init = sd_isoc_init,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.stop0 = sd_stop0,
+	.pkt_scan = sd_pkt_scan,
+#if IS_ENABLED(CONFIG_INPUT)
+	.int_pkt_scan = sd_int_pkt_scan,
+#endif
+	.dq_callback = sd_dqcallback,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.set_register = sd_dbg_s_register,
+	.get_register = sd_dbg_g_register,
+	.get_chip_info = sd_chip_info,
+#endif
+};
+
+#define SN9C20X(sensor, i2c_addr, flags) \
+	.driver_info =  ((flags & 0xff) << 16) \
+			| (SENSOR_ ## sensor << 8) \
+			| (i2c_addr)
+
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x0c45, 0x6240), SN9C20X(MT9M001, 0x5d, 0)},
+	{USB_DEVICE(0x0c45, 0x6242), SN9C20X(MT9M111, 0x5d, 0)},
+	{USB_DEVICE(0x0c45, 0x6248), SN9C20X(OV9655, 0x30, 0)},
+	{USB_DEVICE(0x0c45, 0x624c), SN9C20X(MT9M112, 0x5d, 0)},
+	{USB_DEVICE(0x0c45, 0x624e), SN9C20X(SOI968, 0x30, LED_REVERSE)},
+	{USB_DEVICE(0x0c45, 0x624f), SN9C20X(OV9650, 0x30,
+					     (FLIP_DETECT | HAS_NO_BUTTON))},
+	{USB_DEVICE(0x0c45, 0x6251), SN9C20X(OV9650, 0x30, 0)},
+	{USB_DEVICE(0x0c45, 0x6253), SN9C20X(OV9650, 0x30, 0)},
+	{USB_DEVICE(0x0c45, 0x6260), SN9C20X(OV7670, 0x21, 0)},
+	{USB_DEVICE(0x0c45, 0x6270), SN9C20X(MT9VPRB, 0x00, 0)},
+	{USB_DEVICE(0x0c45, 0x627b), SN9C20X(OV7660, 0x21, FLIP_DETECT)},
+	{USB_DEVICE(0x0c45, 0x627c), SN9C20X(HV7131R, 0x11, 0)},
+	{USB_DEVICE(0x0c45, 0x627f), SN9C20X(OV9650, 0x30, 0)},
+	{USB_DEVICE(0x0c45, 0x6280), SN9C20X(MT9M001, 0x5d, 0)},
+	{USB_DEVICE(0x0c45, 0x6282), SN9C20X(MT9M111, 0x5d, 0)},
+	{USB_DEVICE(0x0c45, 0x6288), SN9C20X(OV9655, 0x30, 0)},
+	{USB_DEVICE(0x0c45, 0x628c), SN9C20X(MT9M112, 0x5d, 0)},
+	{USB_DEVICE(0x0c45, 0x628e), SN9C20X(SOI968, 0x30, 0)},
+	{USB_DEVICE(0x0c45, 0x628f), SN9C20X(OV9650, 0x30, 0)},
+	{USB_DEVICE(0x0c45, 0x62a0), SN9C20X(OV7670, 0x21, 0)},
+	{USB_DEVICE(0x0c45, 0x62b0), SN9C20X(MT9VPRB, 0x00, 0)},
+	{USB_DEVICE(0x0c45, 0x62b3), SN9C20X(OV9655, 0x30, LED_REVERSE)},
+	{USB_DEVICE(0x0c45, 0x62bb), SN9C20X(OV7660, 0x21, LED_REVERSE)},
+	{USB_DEVICE(0x0c45, 0x62bc), SN9C20X(HV7131R, 0x11, 0)},
+	{USB_DEVICE(0x045e, 0x00f4), SN9C20X(OV9650, 0x30, 0)},
+	{USB_DEVICE(0x145f, 0x013d), SN9C20X(OV7660, 0x21, 0)},
+	{USB_DEVICE(0x0458, 0x7029), SN9C20X(HV7131R, 0x11, 0)},
+	{USB_DEVICE(0x0458, 0x7045), SN9C20X(MT9M112, 0x5d, LED_REVERSE)},
+	{USB_DEVICE(0x0458, 0x704a), SN9C20X(MT9M112, 0x5d, 0)},
+	{USB_DEVICE(0x0458, 0x704c), SN9C20X(MT9M112, 0x5d, 0)},
+	{USB_DEVICE(0xa168, 0x0610), SN9C20X(HV7131R, 0x11, 0)},
+	{USB_DEVICE(0xa168, 0x0611), SN9C20X(HV7131R, 0x11, 0)},
+	{USB_DEVICE(0xa168, 0x0613), SN9C20X(HV7131R, 0x11, 0)},
+	{USB_DEVICE(0xa168, 0x0618), SN9C20X(HV7131R, 0x11, 0)},
+	{USB_DEVICE(0xa168, 0x0614), SN9C20X(MT9M111, 0x5d, 0)},
+	{USB_DEVICE(0xa168, 0x0615), SN9C20X(MT9M111, 0x5d, 0)},
+	{USB_DEVICE(0xa168, 0x0617), SN9C20X(MT9M111, 0x5d, 0)},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+		    const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = KBUILD_MODNAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/sonixb.c b/drivers/media/usb/gspca/sonixb.c
new file mode 100644
index 0000000..5f3f297
--- /dev/null
+++ b/drivers/media/usb/gspca/sonixb.c
@@ -0,0 +1,1473 @@
+/*
+ *		sonix sn9c102 (bayer) library
+ *
+ * Copyright (C) 2009-2011 Jean-François Moine <http://moinejf.free.fr>
+ * Copyright (C) 2003 2004 Michel Xhaard mxhaard@magic.fr
+ * Add Pas106 Stefano Mozzi (C) 2004
+ *
+ * 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
+ * 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.
+ */
+
+/* Some documentation on known sonixb registers:
+
+Reg	Use
+sn9c101 / sn9c102:
+0x10	high nibble red gain low nibble blue gain
+0x11	low nibble green gain
+sn9c103:
+0x05	red gain 0-127
+0x06	blue gain 0-127
+0x07	green gain 0-127
+all:
+0x08-0x0f i2c / 3wire registers
+0x12	hstart
+0x13	vstart
+0x15	hsize (hsize = register-value * 16)
+0x16	vsize (vsize = register-value * 16)
+0x17	bit 0 toggle compression quality (according to sn9c102 driver)
+0x18	bit 7 enables compression, bit 4-5 set image down scaling:
+	00 scale 1, 01 scale 1/2, 10, scale 1/4
+0x19	high-nibble is sensor clock divider, changes exposure on sensors which
+	use a clock generated by the bridge. Some sensors have their own clock.
+0x1c	auto_exposure area (for avg_lum) startx (startx = register-value * 32)
+0x1d	auto_exposure area (for avg_lum) starty (starty = register-value * 32)
+0x1e	auto_exposure area (for avg_lum) stopx (hsize = (0x1e - 0x1c) * 32)
+0x1f	auto_exposure area (for avg_lum) stopy (vsize = (0x1f - 0x1d) * 32)
+*/
+
+#define MODULE_NAME "sonixb"
+
+#include <linux/input.h>
+#include "gspca.h"
+
+MODULE_AUTHOR("Jean-François Moine <http://moinejf.free.fr>");
+MODULE_DESCRIPTION("GSPCA/SN9C102 USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+
+	struct v4l2_ctrl *brightness;
+	struct v4l2_ctrl *plfreq;
+
+	atomic_t avg_lum;
+	int prev_avg_lum;
+	int exposure_knee;
+	int header_read;
+	u8 header[12]; /* Header without sof marker */
+
+	unsigned char autogain_ignore_frames;
+	unsigned char frames_to_drop;
+
+	__u8 bridge;			/* Type of bridge */
+#define BRIDGE_101 0
+#define BRIDGE_102 0 /* We make no difference between 101 and 102 */
+#define BRIDGE_103 1
+
+	__u8 sensor;			/* Type of image sensor chip */
+#define SENSOR_HV7131D 0
+#define SENSOR_HV7131R 1
+#define SENSOR_OV6650 2
+#define SENSOR_OV7630 3
+#define SENSOR_PAS106 4
+#define SENSOR_PAS202 5
+#define SENSOR_TAS5110C 6
+#define SENSOR_TAS5110D 7
+#define SENSOR_TAS5130CXX 8
+	__u8 reg11;
+};
+
+typedef const __u8 sensor_init_t[8];
+
+struct sensor_data {
+	const __u8 *bridge_init;
+	sensor_init_t *sensor_init;
+	int sensor_init_size;
+	int flags;
+	__u8 sensor_addr;
+};
+
+/* sensor_data flags */
+#define F_SIF		0x01	/* sif or vga */
+
+/* priv field of struct v4l2_pix_format flags (do not use low nibble!) */
+#define MODE_RAW 0x10		/* raw bayer mode */
+#define MODE_REDUCED_SIF 0x20	/* vga mode (320x240 / 160x120) on sif cam */
+
+#define COMP 0xc7		/* 0x87 //0x07 */
+#define COMP1 0xc9		/* 0x89 //0x09 */
+
+#define MCK_INIT 0x63
+#define MCK_INIT1 0x20		/*fixme: Bayer - 0x50 for JPEG ??*/
+
+#define SYS_CLK 0x04
+
+#define SENS(bridge, sensor, _flags, _sensor_addr) \
+{ \
+	.bridge_init = bridge, \
+	.sensor_init = sensor, \
+	.sensor_init_size = sizeof(sensor), \
+	.flags = _flags, .sensor_addr = _sensor_addr \
+}
+
+/* We calculate the autogain at the end of the transfer of a frame, at this
+   moment a frame with the old settings is being captured and transmitted. So
+   if we adjust the gain or exposure we must ignore atleast the next frame for
+   the new settings to come into effect before doing any other adjustments. */
+#define AUTOGAIN_IGNORE_FRAMES 1
+
+static const struct v4l2_pix_format vga_mode[] = {
+	{160, 120, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 160 * 120,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 2 | MODE_RAW},
+	{160, 120, V4L2_PIX_FMT_SN9C10X, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 160 * 120 * 5 / 4,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 2},
+	{320, 240, V4L2_PIX_FMT_SN9C10X, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 5 / 4,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1},
+	{640, 480, V4L2_PIX_FMT_SN9C10X, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 5 / 4,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+};
+static const struct v4l2_pix_format sif_mode[] = {
+	{160, 120, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 160 * 120,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1 | MODE_RAW | MODE_REDUCED_SIF},
+	{160, 120, V4L2_PIX_FMT_SN9C10X, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 160 * 120 * 5 / 4,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1 | MODE_REDUCED_SIF},
+	{176, 144, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 176,
+		.sizeimage = 176 * 144,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1 | MODE_RAW},
+	{176, 144, V4L2_PIX_FMT_SN9C10X, V4L2_FIELD_NONE,
+		.bytesperline = 176,
+		.sizeimage = 176 * 144 * 5 / 4,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1},
+	{320, 240, V4L2_PIX_FMT_SN9C10X, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 5 / 4,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0 | MODE_REDUCED_SIF},
+	{352, 288, V4L2_PIX_FMT_SN9C10X, V4L2_FIELD_NONE,
+		.bytesperline = 352,
+		.sizeimage = 352 * 288 * 5 / 4,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+};
+
+static const __u8 initHv7131d[] = {
+	0x04, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00, 0x80, 0x11, 0x00, 0x00, 0x00,
+	0x00, 0x00,
+	0x00, 0x00, 0x00, 0x02, 0x02, 0x00,
+	0x28, 0x1e, 0x60, 0x8e, 0x42,
+};
+static const __u8 hv7131d_sensor_init[][8] = {
+	{0xa0, 0x11, 0x01, 0x04, 0x00, 0x00, 0x00, 0x17},
+	{0xa0, 0x11, 0x02, 0x00, 0x00, 0x00, 0x00, 0x17},
+	{0xa0, 0x11, 0x28, 0x00, 0x00, 0x00, 0x00, 0x17},
+	{0xa0, 0x11, 0x30, 0x30, 0x00, 0x00, 0x00, 0x17}, /* reset level */
+	{0xa0, 0x11, 0x34, 0x02, 0x00, 0x00, 0x00, 0x17}, /* pixel bias volt */
+};
+
+static const __u8 initHv7131r[] = {
+	0x46, 0x77, 0x00, 0x04, 0x00, 0x00, 0x00, 0x80, 0x11, 0x00, 0x00, 0x00,
+	0x00, 0x00,
+	0x00, 0x00, 0x00, 0x02, 0x01, 0x00,
+	0x28, 0x1e, 0x60, 0x8a, 0x20,
+};
+static const __u8 hv7131r_sensor_init[][8] = {
+	{0xc0, 0x11, 0x31, 0x38, 0x2a, 0x2e, 0x00, 0x10},
+	{0xa0, 0x11, 0x01, 0x08, 0x2a, 0x2e, 0x00, 0x10},
+	{0xb0, 0x11, 0x20, 0x00, 0xd0, 0x2e, 0x00, 0x10},
+	{0xc0, 0x11, 0x25, 0x03, 0x0e, 0x28, 0x00, 0x16},
+	{0xa0, 0x11, 0x30, 0x10, 0x0e, 0x28, 0x00, 0x15},
+};
+static const __u8 initOv6650[] = {
+	0x44, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
+	0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x01, 0x01, 0x0a, 0x16, 0x12, 0x68, 0x8b,
+	0x10,
+};
+static const __u8 ov6650_sensor_init[][8] = {
+	/* Bright, contrast, etc are set through SCBB interface.
+	 * AVCAP on win2 do not send any data on this controls. */
+	/* Anyway, some registers appears to alter bright and constrat */
+
+	/* Reset sensor */
+	{0xa0, 0x60, 0x12, 0x80, 0x00, 0x00, 0x00, 0x10},
+	/* Set clock register 0x11 low nibble is clock divider */
+	{0xd0, 0x60, 0x11, 0xc0, 0x1b, 0x18, 0xc1, 0x10},
+	/* Next some unknown stuff */
+	{0xb0, 0x60, 0x15, 0x00, 0x02, 0x18, 0xc1, 0x10},
+/*	{0xa0, 0x60, 0x1b, 0x01, 0x02, 0x18, 0xc1, 0x10},
+		 * THIS SET GREEN SCREEN
+		 * (pixels could be innverted in decode kind of "brg",
+		 * but blue wont be there. Avoid this data ... */
+	{0xd0, 0x60, 0x26, 0x01, 0x14, 0xd8, 0xa4, 0x10}, /* format out? */
+	{0xd0, 0x60, 0x26, 0x01, 0x14, 0xd8, 0xa4, 0x10},
+	{0xa0, 0x60, 0x30, 0x3d, 0x0a, 0xd8, 0xa4, 0x10},
+	/* Enable rgb brightness control */
+	{0xa0, 0x60, 0x61, 0x08, 0x00, 0x00, 0x00, 0x10},
+	/* HDG: Note windows uses the line below, which sets both register 0x60
+	   and 0x61 I believe these registers of the ov6650 are identical as
+	   those of the ov7630, because if this is true the windows settings
+	   add a bit additional red gain and a lot additional blue gain, which
+	   matches my findings that the windows settings make blue much too
+	   blue and red a little too red.
+	{0xb0, 0x60, 0x60, 0x66, 0x68, 0xd8, 0xa4, 0x10}, */
+	/* Some more unknown stuff */
+	{0xa0, 0x60, 0x68, 0x04, 0x68, 0xd8, 0xa4, 0x10},
+	{0xd0, 0x60, 0x17, 0x24, 0xd6, 0x04, 0x94, 0x10}, /* Clipreg */
+};
+
+static const __u8 initOv7630[] = {
+	0x04, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,	/* r01 .. r08 */
+	0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,	/* r09 .. r10 */
+	0x00, 0x01, 0x01, 0x0a,				/* r11 .. r14 */
+	0x28, 0x1e,			/* H & V sizes     r15 .. r16 */
+	0x68, 0x8f, MCK_INIT1,				/* r17 .. r19 */
+};
+static const __u8 ov7630_sensor_init[][8] = {
+	{0xa0, 0x21, 0x12, 0x80, 0x00, 0x00, 0x00, 0x10},
+	{0xb0, 0x21, 0x01, 0x77, 0x3a, 0x00, 0x00, 0x10},
+/*	{0xd0, 0x21, 0x12, 0x7c, 0x01, 0x80, 0x34, 0x10},	   jfm */
+	{0xd0, 0x21, 0x12, 0x5c, 0x00, 0x80, 0x34, 0x10},	/* jfm */
+	{0xa0, 0x21, 0x1b, 0x04, 0x00, 0x80, 0x34, 0x10},
+	{0xa0, 0x21, 0x20, 0x44, 0x00, 0x80, 0x34, 0x10},
+	{0xa0, 0x21, 0x23, 0xee, 0x00, 0x80, 0x34, 0x10},
+	{0xd0, 0x21, 0x26, 0xa0, 0x9a, 0xa0, 0x30, 0x10},
+	{0xb0, 0x21, 0x2a, 0x80, 0x00, 0xa0, 0x30, 0x10},
+	{0xb0, 0x21, 0x2f, 0x3d, 0x24, 0xa0, 0x30, 0x10},
+	{0xa0, 0x21, 0x32, 0x86, 0x24, 0xa0, 0x30, 0x10},
+	{0xb0, 0x21, 0x60, 0xa9, 0x4a, 0xa0, 0x30, 0x10},
+/*	{0xb0, 0x21, 0x60, 0xa9, 0x42, 0xa0, 0x30, 0x10},	 * jfm */
+	{0xa0, 0x21, 0x65, 0x00, 0x42, 0xa0, 0x30, 0x10},
+	{0xa0, 0x21, 0x69, 0x38, 0x42, 0xa0, 0x30, 0x10},
+	{0xc0, 0x21, 0x6f, 0x88, 0x0b, 0x00, 0x30, 0x10},
+	{0xc0, 0x21, 0x74, 0x21, 0x8e, 0x00, 0x30, 0x10},
+	{0xa0, 0x21, 0x7d, 0xf7, 0x8e, 0x00, 0x30, 0x10},
+	{0xd0, 0x21, 0x17, 0x1c, 0xbd, 0x06, 0xf6, 0x10},
+};
+
+static const __u8 initPas106[] = {
+	0x04, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0x40, 0x00, 0x00, 0x00,
+	0x00, 0x00,
+	0x00, 0x00, 0x00, 0x04, 0x01, 0x00,
+	0x16, 0x12, 0x24, COMP1, MCK_INIT1,
+};
+/* compression 0x86 mckinit1 0x2b */
+
+/* "Known" PAS106B registers:
+  0x02 clock divider
+  0x03 Variable framerate bits 4-11
+  0x04 Var framerate bits 0-3, one must leave the 4 msb's at 0 !!
+       The variable framerate control must never be set lower then 300,
+       which sets the framerate at 90 / reg02, otherwise vsync is lost.
+  0x05 Shutter Time Line Offset, this can be used as an exposure control:
+       0 = use full frame time, 255 = no exposure at all
+       Note this may never be larger then "var-framerate control" / 2 - 2.
+       When var-framerate control is < 514, no exposure is reached at the max
+       allowed value for the framerate control value, rather then at 255.
+  0x06 Shutter Time Pixel Offset, like reg05 this influences exposure, but
+       only a very little bit, leave at 0xcd
+  0x07 offset sign bit (bit0 1 > negative offset)
+  0x08 offset
+  0x09 Blue Gain
+  0x0a Green1 Gain
+  0x0b Green2 Gain
+  0x0c Red Gain
+  0x0e Global gain
+  0x13 Write 1 to commit settings to sensor
+*/
+
+static const __u8 pas106_sensor_init[][8] = {
+	/* Pixel Clock Divider 6 */
+	{ 0xa1, 0x40, 0x02, 0x04, 0x00, 0x00, 0x00, 0x14 },
+	/* Frame Time MSB (also seen as 0x12) */
+	{ 0xa1, 0x40, 0x03, 0x13, 0x00, 0x00, 0x00, 0x14 },
+	/* Frame Time LSB (also seen as 0x05) */
+	{ 0xa1, 0x40, 0x04, 0x06, 0x00, 0x00, 0x00, 0x14 },
+	/* Shutter Time Line Offset (also seen as 0x6d) */
+	{ 0xa1, 0x40, 0x05, 0x65, 0x00, 0x00, 0x00, 0x14 },
+	/* Shutter Time Pixel Offset (also seen as 0xb1) */
+	{ 0xa1, 0x40, 0x06, 0xcd, 0x00, 0x00, 0x00, 0x14 },
+	/* Black Level Subtract Sign (also seen 0x00) */
+	{ 0xa1, 0x40, 0x07, 0xc1, 0x00, 0x00, 0x00, 0x14 },
+	/* Black Level Subtract Level (also seen 0x01) */
+	{ 0xa1, 0x40, 0x08, 0x06, 0x00, 0x00, 0x00, 0x14 },
+	{ 0xa1, 0x40, 0x08, 0x06, 0x00, 0x00, 0x00, 0x14 },
+	/* Color Gain B Pixel 5 a */
+	{ 0xa1, 0x40, 0x09, 0x05, 0x00, 0x00, 0x00, 0x14 },
+	/* Color Gain G1 Pixel 1 5 */
+	{ 0xa1, 0x40, 0x0a, 0x04, 0x00, 0x00, 0x00, 0x14 },
+	/* Color Gain G2 Pixel 1 0 5 */
+	{ 0xa1, 0x40, 0x0b, 0x04, 0x00, 0x00, 0x00, 0x14 },
+	/* Color Gain R Pixel 3 1 */
+	{ 0xa1, 0x40, 0x0c, 0x05, 0x00, 0x00, 0x00, 0x14 },
+	/* Color GainH  Pixel */
+	{ 0xa1, 0x40, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x14 },
+	/* Global Gain */
+	{ 0xa1, 0x40, 0x0e, 0x0e, 0x00, 0x00, 0x00, 0x14 },
+	/* Contrast */
+	{ 0xa1, 0x40, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x14 },
+	/* H&V synchro polarity */
+	{ 0xa1, 0x40, 0x10, 0x06, 0x00, 0x00, 0x00, 0x14 },
+	/* ?default */
+	{ 0xa1, 0x40, 0x11, 0x06, 0x00, 0x00, 0x00, 0x14 },
+	/* DAC scale */
+	{ 0xa1, 0x40, 0x12, 0x06, 0x00, 0x00, 0x00, 0x14 },
+	/* ?default */
+	{ 0xa1, 0x40, 0x14, 0x02, 0x00, 0x00, 0x00, 0x14 },
+	/* Validate Settings */
+	{ 0xa1, 0x40, 0x13, 0x01, 0x00, 0x00, 0x00, 0x14 },
+};
+
+static const __u8 initPas202[] = {
+	0x44, 0x44, 0x21, 0x30, 0x00, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0x00,
+	0x00, 0x00,
+	0x00, 0x00, 0x00, 0x06, 0x03, 0x0a,
+	0x28, 0x1e, 0x20, 0x89, 0x20,
+};
+
+/* "Known" PAS202BCB registers:
+  0x02 clock divider
+  0x04 Variable framerate bits 6-11 (*)
+  0x05 Var framerate  bits 0-5, one must leave the 2 msb's at 0 !!
+  0x07 Blue Gain
+  0x08 Green Gain
+  0x09 Red Gain
+  0x0b offset sign bit (bit0 1 > negative offset)
+  0x0c offset
+  0x0e Unknown image is slightly brighter when bit 0 is 0, if reg0f is 0 too,
+       leave at 1 otherwise we get a jump in our exposure control
+  0x0f Exposure 0-255, 0 = use full frame time, 255 = no exposure at all
+  0x10 Master gain 0 - 31
+  0x11 write 1 to apply changes
+  (*) The variable framerate control must never be set lower then 500
+      which sets the framerate at 30 / reg02, otherwise vsync is lost.
+*/
+static const __u8 pas202_sensor_init[][8] = {
+	/* Set the clock divider to 4 -> 30 / 4 = 7.5 fps, we would like
+	   to set it lower, but for some reason the bridge starts missing
+	   vsync's then */
+	{0xa0, 0x40, 0x02, 0x04, 0x00, 0x00, 0x00, 0x10},
+	{0xd0, 0x40, 0x04, 0x07, 0x34, 0x00, 0x09, 0x10},
+	{0xd0, 0x40, 0x08, 0x01, 0x00, 0x00, 0x01, 0x10},
+	{0xd0, 0x40, 0x0c, 0x00, 0x0c, 0x01, 0x32, 0x10},
+	{0xd0, 0x40, 0x10, 0x00, 0x01, 0x00, 0x63, 0x10},
+	{0xa0, 0x40, 0x15, 0x70, 0x01, 0x00, 0x63, 0x10},
+	{0xa0, 0x40, 0x18, 0x00, 0x01, 0x00, 0x63, 0x10},
+	{0xa0, 0x40, 0x11, 0x01, 0x01, 0x00, 0x63, 0x10},
+	{0xa0, 0x40, 0x03, 0x56, 0x01, 0x00, 0x63, 0x10},
+	{0xa0, 0x40, 0x11, 0x01, 0x01, 0x00, 0x63, 0x10},
+};
+
+static const __u8 initTas5110c[] = {
+	0x44, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x11, 0x00, 0x00, 0x00,
+	0x00, 0x00,
+	0x00, 0x00, 0x00, 0x45, 0x09, 0x0a,
+	0x16, 0x12, 0x60, 0x86, 0x2b,
+};
+/* Same as above, except a different hstart */
+static const __u8 initTas5110d[] = {
+	0x44, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x11, 0x00, 0x00, 0x00,
+	0x00, 0x00,
+	0x00, 0x00, 0x00, 0x41, 0x09, 0x0a,
+	0x16, 0x12, 0x60, 0x86, 0x2b,
+};
+/* tas5110c is 3 wire, tas5110d is 2 wire (regular i2c) */
+static const __u8 tas5110c_sensor_init[][8] = {
+	{0x30, 0x11, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x10},
+	{0x30, 0x11, 0x02, 0x20, 0xa9, 0x00, 0x00, 0x10},
+};
+/* Known TAS5110D registers
+ * reg02: gain, bit order reversed!! 0 == max gain, 255 == min gain
+ * reg03: bit3: vflip, bit4: ~hflip, bit7: ~gainboost (~ == inverted)
+ *        Note: writing reg03 seems to only work when written together with 02
+ */
+static const __u8 tas5110d_sensor_init[][8] = {
+	{0xa0, 0x61, 0x9a, 0xca, 0x00, 0x00, 0x00, 0x17}, /* reset */
+};
+
+static const __u8 initTas5130[] = {
+	0x04, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x11, 0x00, 0x00, 0x00,
+	0x00, 0x00,
+	0x00, 0x00, 0x00, 0x68, 0x0c, 0x0a,
+	0x28, 0x1e, 0x60, COMP, MCK_INIT,
+};
+static const __u8 tas5130_sensor_init[][8] = {
+/*	{0x30, 0x11, 0x00, 0x40, 0x47, 0x00, 0x00, 0x10},
+					* shutter 0x47 short exposure? */
+	{0x30, 0x11, 0x00, 0x40, 0x01, 0x00, 0x00, 0x10},
+					/* shutter 0x01 long exposure */
+	{0x30, 0x11, 0x02, 0x20, 0x70, 0x00, 0x00, 0x10},
+};
+
+static const struct sensor_data sensor_data[] = {
+	SENS(initHv7131d, hv7131d_sensor_init, 0, 0),
+	SENS(initHv7131r, hv7131r_sensor_init, 0, 0),
+	SENS(initOv6650, ov6650_sensor_init, F_SIF, 0x60),
+	SENS(initOv7630, ov7630_sensor_init, 0, 0x21),
+	SENS(initPas106, pas106_sensor_init, F_SIF, 0),
+	SENS(initPas202, pas202_sensor_init, 0, 0),
+	SENS(initTas5110c, tas5110c_sensor_init, F_SIF, 0),
+	SENS(initTas5110d, tas5110d_sensor_init, F_SIF, 0),
+	SENS(initTas5130, tas5130_sensor_init, 0, 0),
+};
+
+/* get one byte in gspca_dev->usb_buf */
+static void reg_r(struct gspca_dev *gspca_dev,
+		  __u16 value)
+{
+	int res;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+
+	res = usb_control_msg(gspca_dev->dev,
+			usb_rcvctrlpipe(gspca_dev->dev, 0),
+			0,			/* request */
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+			value,
+			0,			/* index */
+			gspca_dev->usb_buf, 1,
+			500);
+
+	if (res < 0) {
+		dev_err(gspca_dev->v4l2_dev.dev,
+			"Error reading register %02x: %d\n", value, res);
+		gspca_dev->usb_err = res;
+	}
+}
+
+static void reg_w(struct gspca_dev *gspca_dev,
+		  __u16 value,
+		  const __u8 *buffer,
+		  int len)
+{
+	int res;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+
+	memcpy(gspca_dev->usb_buf, buffer, len);
+	res = usb_control_msg(gspca_dev->dev,
+			usb_sndctrlpipe(gspca_dev->dev, 0),
+			0x08,			/* request */
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+			value,
+			0,			/* index */
+			gspca_dev->usb_buf, len,
+			500);
+
+	if (res < 0) {
+		dev_err(gspca_dev->v4l2_dev.dev,
+			"Error writing register %02x: %d\n", value, res);
+		gspca_dev->usb_err = res;
+	}
+}
+
+static void i2c_w(struct gspca_dev *gspca_dev, const u8 *buf)
+{
+	int retry = 60;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+
+	/* is i2c ready */
+	reg_w(gspca_dev, 0x08, buf, 8);
+	while (retry--) {
+		if (gspca_dev->usb_err < 0)
+			return;
+		msleep(1);
+		reg_r(gspca_dev, 0x08);
+		if (gspca_dev->usb_buf[0] & 0x04) {
+			if (gspca_dev->usb_buf[0] & 0x08) {
+				dev_err(gspca_dev->v4l2_dev.dev,
+					"i2c error writing %8ph\n", buf);
+				gspca_dev->usb_err = -EIO;
+			}
+			return;
+		}
+	}
+
+	dev_err(gspca_dev->v4l2_dev.dev, "i2c write timeout\n");
+	gspca_dev->usb_err = -EIO;
+}
+
+static void i2c_w_vector(struct gspca_dev *gspca_dev,
+			const __u8 buffer[][8], int len)
+{
+	for (;;) {
+		if (gspca_dev->usb_err < 0)
+			return;
+		i2c_w(gspca_dev, *buffer);
+		len -= 8;
+		if (len <= 0)
+			break;
+		buffer++;
+	}
+}
+
+static void setbrightness(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	switch (sd->sensor) {
+	case  SENSOR_OV6650:
+	case  SENSOR_OV7630: {
+		__u8 i2cOV[] =
+			{0xa0, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x10};
+
+		/* change reg 0x06 */
+		i2cOV[1] = sensor_data[sd->sensor].sensor_addr;
+		i2cOV[3] = sd->brightness->val;
+		i2c_w(gspca_dev, i2cOV);
+		break;
+	}
+	case SENSOR_PAS106:
+	case SENSOR_PAS202: {
+		__u8 i2cpbright[] =
+			{0xb0, 0x40, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x16};
+		__u8 i2cpdoit[] =
+			{0xa0, 0x40, 0x11, 0x01, 0x00, 0x00, 0x00, 0x16};
+
+		/* PAS106 uses reg 7 and 8 instead of b and c */
+		if (sd->sensor == SENSOR_PAS106) {
+			i2cpbright[2] = 7;
+			i2cpdoit[2] = 0x13;
+		}
+
+		if (sd->brightness->val < 127) {
+			/* change reg 0x0b, signreg */
+			i2cpbright[3] = 0x01;
+			/* set reg 0x0c, offset */
+			i2cpbright[4] = 127 - sd->brightness->val;
+		} else
+			i2cpbright[4] = sd->brightness->val - 127;
+
+		i2c_w(gspca_dev, i2cpbright);
+		i2c_w(gspca_dev, i2cpdoit);
+		break;
+	}
+	default:
+		break;
+	}
+}
+
+static void setgain(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 gain = gspca_dev->gain->val;
+
+	switch (sd->sensor) {
+	case SENSOR_HV7131D: {
+		__u8 i2c[] =
+			{0xc0, 0x11, 0x31, 0x00, 0x00, 0x00, 0x00, 0x17};
+
+		i2c[3] = 0x3f - gain;
+		i2c[4] = 0x3f - gain;
+		i2c[5] = 0x3f - gain;
+
+		i2c_w(gspca_dev, i2c);
+		break;
+	}
+	case SENSOR_TAS5110C:
+	case SENSOR_TAS5130CXX: {
+		__u8 i2c[] =
+			{0x30, 0x11, 0x02, 0x20, 0x70, 0x00, 0x00, 0x10};
+
+		i2c[4] = 255 - gain;
+		i2c_w(gspca_dev, i2c);
+		break;
+	}
+	case SENSOR_TAS5110D: {
+		__u8 i2c[] = {
+			0xb0, 0x61, 0x02, 0x00, 0x10, 0x00, 0x00, 0x17 };
+		gain = 255 - gain;
+		/* The bits in the register are the wrong way around!! */
+		i2c[3] |= (gain & 0x80) >> 7;
+		i2c[3] |= (gain & 0x40) >> 5;
+		i2c[3] |= (gain & 0x20) >> 3;
+		i2c[3] |= (gain & 0x10) >> 1;
+		i2c[3] |= (gain & 0x08) << 1;
+		i2c[3] |= (gain & 0x04) << 3;
+		i2c[3] |= (gain & 0x02) << 5;
+		i2c[3] |= (gain & 0x01) << 7;
+		i2c_w(gspca_dev, i2c);
+		break;
+	}
+	case SENSOR_OV6650:
+	case SENSOR_OV7630: {
+		__u8 i2c[] = {0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10};
+
+		/*
+		 * The ov7630's gain is weird, at 32 the gain drops to the
+		 * same level as at 16, so skip 32-47 (of the 0-63 scale).
+		 */
+		if (sd->sensor == SENSOR_OV7630 && gain >= 32)
+			gain += 16;
+
+		i2c[1] = sensor_data[sd->sensor].sensor_addr;
+		i2c[3] = gain;
+		i2c_w(gspca_dev, i2c);
+		break;
+	}
+	case SENSOR_PAS106:
+	case SENSOR_PAS202: {
+		__u8 i2cpgain[] =
+			{0xa0, 0x40, 0x10, 0x00, 0x00, 0x00, 0x00, 0x15};
+		__u8 i2cpcolorgain[] =
+			{0xc0, 0x40, 0x07, 0x00, 0x00, 0x00, 0x00, 0x15};
+		__u8 i2cpdoit[] =
+			{0xa0, 0x40, 0x11, 0x01, 0x00, 0x00, 0x00, 0x16};
+
+		/* PAS106 uses different regs (and has split green gains) */
+		if (sd->sensor == SENSOR_PAS106) {
+			i2cpgain[2] = 0x0e;
+			i2cpcolorgain[0] = 0xd0;
+			i2cpcolorgain[2] = 0x09;
+			i2cpdoit[2] = 0x13;
+		}
+
+		i2cpgain[3] = gain;
+		i2cpcolorgain[3] = gain >> 1;
+		i2cpcolorgain[4] = gain >> 1;
+		i2cpcolorgain[5] = gain >> 1;
+		i2cpcolorgain[6] = gain >> 1;
+
+		i2c_w(gspca_dev, i2cpgain);
+		i2c_w(gspca_dev, i2cpcolorgain);
+		i2c_w(gspca_dev, i2cpdoit);
+		break;
+	}
+	default:
+		if (sd->bridge == BRIDGE_103) {
+			u8 buf[3] = { gain, gain, gain }; /* R, G, B */
+			reg_w(gspca_dev, 0x05, buf, 3);
+		} else {
+			u8 buf[2];
+			buf[0] = gain << 4 | gain; /* Red and blue */
+			buf[1] = gain; /* Green */
+			reg_w(gspca_dev, 0x10, buf, 2);
+		}
+	}
+}
+
+static void setexposure(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	switch (sd->sensor) {
+	case SENSOR_HV7131D: {
+		/* Note the datasheet wrongly says line mode exposure uses reg
+		   0x26 and 0x27, testing has shown 0x25 + 0x26 */
+		__u8 i2c[] = {0xc0, 0x11, 0x25, 0x00, 0x00, 0x00, 0x00, 0x17};
+		u16 reg = gspca_dev->exposure->val;
+
+		i2c[3] = reg >> 8;
+		i2c[4] = reg & 0xff;
+		i2c_w(gspca_dev, i2c);
+		break;
+	}
+	case SENSOR_TAS5110C:
+	case SENSOR_TAS5110D: {
+		/* register 19's high nibble contains the sn9c10x clock divider
+		   The high nibble configures the no fps according to the
+		   formula: 60 / high_nibble. With a maximum of 30 fps */
+		u8 reg = gspca_dev->exposure->val;
+
+		reg = (reg << 4) | 0x0b;
+		reg_w(gspca_dev, 0x19, &reg, 1);
+		break;
+	}
+	case SENSOR_OV6650:
+	case SENSOR_OV7630: {
+		/* The ov6650 / ov7630 have 2 registers which both influence
+		   exposure, register 11, whose low nibble sets the nr off fps
+		   according to: fps = 30 / (low_nibble + 1)
+
+		   The fps configures the maximum exposure setting, but it is
+		   possible to use less exposure then what the fps maximum
+		   allows by setting register 10. register 10 configures the
+		   actual exposure as quotient of the full exposure, with 0
+		   being no exposure at all (not very useful) and reg10_max
+		   being max exposure possible at that framerate.
+
+		   The code maps our 0 - 510 ms exposure ctrl to these 2
+		   registers, trying to keep fps as high as possible.
+		*/
+		__u8 i2c[] = {0xb0, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10};
+		int reg10, reg11, reg10_max;
+
+		/* ov6645 datasheet says reg10_max is 9a, but that uses
+		   tline * 2 * reg10 as formula for calculating texpo, the
+		   ov6650 probably uses the same formula as the 7730 which uses
+		   tline * 4 * reg10, which explains why the reg10max we've
+		   found experimentally for the ov6650 is exactly half that of
+		   the ov6645. The ov7630 datasheet says the max is 0x41. */
+		if (sd->sensor == SENSOR_OV6650) {
+			reg10_max = 0x4d;
+			i2c[4] = 0xc0; /* OV6650 needs non default vsync pol */
+		} else
+			reg10_max = 0x41;
+
+		reg11 = (15 * gspca_dev->exposure->val + 999) / 1000;
+		if (reg11 < 1)
+			reg11 = 1;
+		else if (reg11 > 16)
+			reg11 = 16;
+
+		/* In 640x480, if the reg11 has less than 4, the image is
+		   unstable (the bridge goes into a higher compression mode
+		   which we have not reverse engineered yet). */
+		if (gspca_dev->pixfmt.width == 640 && reg11 < 4)
+			reg11 = 4;
+
+		/* frame exposure time in ms = 1000 * reg11 / 30    ->
+		reg10 = (gspca_dev->exposure->val / 2) * reg10_max
+				/ (1000 * reg11 / 30) */
+		reg10 = (gspca_dev->exposure->val * 15 * reg10_max)
+				/ (1000 * reg11);
+
+		/* Don't allow this to get below 10 when using autogain, the
+		   steps become very large (relatively) when below 10 causing
+		   the image to oscilate from much too dark, to much too bright
+		   and back again. */
+		if (gspca_dev->autogain->val && reg10 < 10)
+			reg10 = 10;
+		else if (reg10 > reg10_max)
+			reg10 = reg10_max;
+
+		/* Write reg 10 and reg11 low nibble */
+		i2c[1] = sensor_data[sd->sensor].sensor_addr;
+		i2c[3] = reg10;
+		i2c[4] |= reg11 - 1;
+
+		/* If register 11 didn't change, don't change it */
+		if (sd->reg11 == reg11)
+			i2c[0] = 0xa0;
+
+		i2c_w(gspca_dev, i2c);
+		if (gspca_dev->usb_err == 0)
+			sd->reg11 = reg11;
+		break;
+	}
+	case SENSOR_PAS202: {
+		__u8 i2cpframerate[] =
+			{0xb0, 0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x16};
+		__u8 i2cpexpo[] =
+			{0xa0, 0x40, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x16};
+		const __u8 i2cpdoit[] =
+			{0xa0, 0x40, 0x11, 0x01, 0x00, 0x00, 0x00, 0x16};
+		int framerate_ctrl;
+
+		/* The exposure knee for the autogain algorithm is 200
+		   (100 ms / 10 fps on other sensors), for values below this
+		   use the control for setting the partial frame expose time,
+		   above that use variable framerate. This way we run at max
+		   framerate (640x480@7.5 fps, 320x240@10fps) until the knee
+		   is reached. Using the variable framerate control above 200
+		   is better then playing around with both clockdiv + partial
+		   frame exposure times (like we are doing with the ov chips),
+		   as that sometimes leads to jumps in the exposure control,
+		   which are bad for auto exposure. */
+		if (gspca_dev->exposure->val < 200) {
+			i2cpexpo[3] = 255 - (gspca_dev->exposure->val * 255)
+						/ 200;
+			framerate_ctrl = 500;
+		} else {
+			/* The PAS202's exposure control goes from 0 - 4095,
+			   but anything below 500 causes vsync issues, so scale
+			   our 200-1023 to 500-4095 */
+			framerate_ctrl = (gspca_dev->exposure->val - 200)
+							* 1000 / 229 +  500;
+		}
+
+		i2cpframerate[3] = framerate_ctrl >> 6;
+		i2cpframerate[4] = framerate_ctrl & 0x3f;
+		i2c_w(gspca_dev, i2cpframerate);
+		i2c_w(gspca_dev, i2cpexpo);
+		i2c_w(gspca_dev, i2cpdoit);
+		break;
+	}
+	case SENSOR_PAS106: {
+		__u8 i2cpframerate[] =
+			{0xb1, 0x40, 0x03, 0x00, 0x00, 0x00, 0x00, 0x14};
+		__u8 i2cpexpo[] =
+			{0xa1, 0x40, 0x05, 0x00, 0x00, 0x00, 0x00, 0x14};
+		const __u8 i2cpdoit[] =
+			{0xa1, 0x40, 0x13, 0x01, 0x00, 0x00, 0x00, 0x14};
+		int framerate_ctrl;
+
+		/* For values below 150 use partial frame exposure, above
+		   that use framerate ctrl */
+		if (gspca_dev->exposure->val < 150) {
+			i2cpexpo[3] = 150 - gspca_dev->exposure->val;
+			framerate_ctrl = 300;
+		} else {
+			/* The PAS106's exposure control goes from 0 - 4095,
+			   but anything below 300 causes vsync issues, so scale
+			   our 150-1023 to 300-4095 */
+			framerate_ctrl = (gspca_dev->exposure->val - 150)
+						* 1000 / 230 + 300;
+		}
+
+		i2cpframerate[3] = framerate_ctrl >> 4;
+		i2cpframerate[4] = framerate_ctrl & 0x0f;
+		i2c_w(gspca_dev, i2cpframerate);
+		i2c_w(gspca_dev, i2cpexpo);
+		i2c_w(gspca_dev, i2cpdoit);
+		break;
+	}
+	default:
+		break;
+	}
+}
+
+static void setfreq(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->sensor == SENSOR_OV6650 || sd->sensor == SENSOR_OV7630) {
+		/* Framerate adjust register for artificial light 50 hz flicker
+		   compensation, for the ov6650 this is identical to ov6630
+		   0x2b register, see ov6630 datasheet.
+		   0x4f / 0x8a -> (30 fps -> 25 fps), 0x00 -> no adjustment */
+		__u8 i2c[] = {0xa0, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x10};
+		switch (sd->plfreq->val) {
+		default:
+/*		case 0:			 * no filter*/
+/*		case 2:			 * 60 hz */
+			i2c[3] = 0;
+			break;
+		case 1:			/* 50 hz */
+			i2c[3] = (sd->sensor == SENSOR_OV6650)
+					? 0x4f : 0x8a;
+			break;
+		}
+		i2c[1] = sensor_data[sd->sensor].sensor_addr;
+		i2c_w(gspca_dev, i2c);
+	}
+}
+
+static void do_autogain(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int deadzone, desired_avg_lum, avg_lum;
+
+	avg_lum = atomic_read(&sd->avg_lum);
+	if (avg_lum == -1)
+		return;
+
+	if (sd->autogain_ignore_frames > 0) {
+		sd->autogain_ignore_frames--;
+		return;
+	}
+
+	/* SIF / VGA sensors have a different autoexposure area and thus
+	   different avg_lum values for the same picture brightness */
+	if (sensor_data[sd->sensor].flags & F_SIF) {
+		deadzone = 500;
+		/* SIF sensors tend to overexpose, so keep this small */
+		desired_avg_lum = 5000;
+	} else {
+		deadzone = 1500;
+		desired_avg_lum = 13000;
+	}
+
+	if (sd->brightness)
+		desired_avg_lum = sd->brightness->val * desired_avg_lum / 127;
+
+	if (gspca_dev->exposure->maximum < 500) {
+		if (gspca_coarse_grained_expo_autogain(gspca_dev, avg_lum,
+				desired_avg_lum, deadzone))
+			sd->autogain_ignore_frames = AUTOGAIN_IGNORE_FRAMES;
+	} else {
+		int gain_knee = (s32)gspca_dev->gain->maximum * 9 / 10;
+		if (gspca_expo_autogain(gspca_dev, avg_lum, desired_avg_lum,
+				deadzone, gain_knee, sd->exposure_knee))
+			sd->autogain_ignore_frames = AUTOGAIN_IGNORE_FRAMES;
+	}
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam;
+
+	reg_r(gspca_dev, 0x00);
+	if (gspca_dev->usb_buf[0] != 0x10)
+		return -ENODEV;
+
+	/* copy the webcam info from the device id */
+	sd->sensor = id->driver_info >> 8;
+	sd->bridge = id->driver_info & 0xff;
+
+	cam = &gspca_dev->cam;
+	if (!(sensor_data[sd->sensor].flags & F_SIF)) {
+		cam->cam_mode = vga_mode;
+		cam->nmodes = ARRAY_SIZE(vga_mode);
+	} else {
+		cam->cam_mode = sif_mode;
+		cam->nmodes = ARRAY_SIZE(sif_mode);
+	}
+	cam->npkt = 36;			/* 36 packets per ISOC message */
+
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	const __u8 stop = 0x09; /* Disable stream turn of LED */
+
+	reg_w(gspca_dev, 0x01, &stop, 1);
+
+	return gspca_dev->usb_err;
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *)gspca_dev;
+
+	gspca_dev->usb_err = 0;
+
+	if (ctrl->id == V4L2_CID_AUTOGAIN && ctrl->is_new && ctrl->val) {
+		/* when switching to autogain set defaults to make sure
+		   we are on a valid point of the autogain gain /
+		   exposure knee graph, and give this change time to
+		   take effect before doing autogain. */
+		gspca_dev->gain->val = gspca_dev->gain->default_value;
+		gspca_dev->exposure->val = gspca_dev->exposure->default_value;
+		sd->autogain_ignore_frames = AUTOGAIN_IGNORE_FRAMES;
+	}
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		setbrightness(gspca_dev);
+		break;
+	case V4L2_CID_AUTOGAIN:
+		if (gspca_dev->exposure->is_new || (ctrl->is_new && ctrl->val))
+			setexposure(gspca_dev);
+		if (gspca_dev->gain->is_new || (ctrl->is_new && ctrl->val))
+			setgain(gspca_dev);
+		break;
+	case V4L2_CID_POWER_LINE_FREQUENCY:
+		setfreq(gspca_dev);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+/* this function is called at probe time */
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 5);
+
+	if (sd->sensor == SENSOR_OV6650 || sd->sensor == SENSOR_OV7630 ||
+	    sd->sensor == SENSOR_PAS106 || sd->sensor == SENSOR_PAS202)
+		sd->brightness = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+					V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
+
+	/* Gain range is sensor dependent */
+	switch (sd->sensor) {
+	case SENSOR_OV6650:
+	case SENSOR_PAS106:
+	case SENSOR_PAS202:
+		gspca_dev->gain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+					V4L2_CID_GAIN, 0, 31, 1, 15);
+		break;
+	case SENSOR_OV7630:
+		gspca_dev->gain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+					V4L2_CID_GAIN, 0, 47, 1, 31);
+		break;
+	case SENSOR_HV7131D:
+		gspca_dev->gain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+					V4L2_CID_GAIN, 0, 63, 1, 31);
+		break;
+	case SENSOR_TAS5110C:
+	case SENSOR_TAS5110D:
+	case SENSOR_TAS5130CXX:
+		gspca_dev->gain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+					V4L2_CID_GAIN, 0, 255, 1, 127);
+		break;
+	default:
+		if (sd->bridge == BRIDGE_103) {
+			gspca_dev->gain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+						V4L2_CID_GAIN, 0, 127, 1, 63);
+		} else {
+			gspca_dev->gain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+						V4L2_CID_GAIN, 0, 15, 1, 7);
+		}
+	}
+
+	/* Exposure range is sensor dependent, and not all have exposure */
+	switch (sd->sensor) {
+	case SENSOR_HV7131D:
+		gspca_dev->exposure = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+					V4L2_CID_EXPOSURE, 0, 8191, 1, 482);
+		sd->exposure_knee = 964;
+		break;
+	case SENSOR_OV6650:
+	case SENSOR_OV7630:
+	case SENSOR_PAS106:
+	case SENSOR_PAS202:
+		gspca_dev->exposure = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+					V4L2_CID_EXPOSURE, 0, 1023, 1, 66);
+		sd->exposure_knee = 200;
+		break;
+	case SENSOR_TAS5110C:
+	case SENSOR_TAS5110D:
+		gspca_dev->exposure = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+					V4L2_CID_EXPOSURE, 2, 15, 1, 2);
+		break;
+	}
+
+	if (gspca_dev->exposure) {
+		gspca_dev->autogain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+						V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
+	}
+
+	if (sd->sensor == SENSOR_OV6650 || sd->sensor == SENSOR_OV7630)
+		sd->plfreq = v4l2_ctrl_new_std_menu(hdl, &sd_ctrl_ops,
+			V4L2_CID_POWER_LINE_FREQUENCY,
+			V4L2_CID_POWER_LINE_FREQUENCY_60HZ, 0,
+			V4L2_CID_POWER_LINE_FREQUENCY_DISABLED);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+
+	if (gspca_dev->autogain)
+		v4l2_ctrl_auto_cluster(3, &gspca_dev->autogain, 0, false);
+
+	return 0;
+}
+
+/* -- start the camera -- */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam = &gspca_dev->cam;
+	int i, mode;
+	__u8 regs[0x31];
+
+	mode = cam->cam_mode[gspca_dev->curr_mode].priv & 0x07;
+	/* Copy registers 0x01 - 0x19 from the template */
+	memcpy(&regs[0x01], sensor_data[sd->sensor].bridge_init, 0x19);
+	/* Set the mode */
+	regs[0x18] |= mode << 4;
+
+	/* Set bridge gain to 1.0 */
+	if (sd->bridge == BRIDGE_103) {
+		regs[0x05] = 0x20; /* Red */
+		regs[0x06] = 0x20; /* Green */
+		regs[0x07] = 0x20; /* Blue */
+	} else {
+		regs[0x10] = 0x00; /* Red and blue */
+		regs[0x11] = 0x00; /* Green */
+	}
+
+	/* Setup pixel numbers and auto exposure window */
+	if (sensor_data[sd->sensor].flags & F_SIF) {
+		regs[0x1a] = 0x14; /* HO_SIZE 640, makes no sense */
+		regs[0x1b] = 0x0a; /* VO_SIZE 320, makes no sense */
+		regs[0x1c] = 0x02; /* AE H-start 64 */
+		regs[0x1d] = 0x02; /* AE V-start 64 */
+		regs[0x1e] = 0x09; /* AE H-end 288 */
+		regs[0x1f] = 0x07; /* AE V-end 224 */
+	} else {
+		regs[0x1a] = 0x1d; /* HO_SIZE 960, makes no sense */
+		regs[0x1b] = 0x10; /* VO_SIZE 512, makes no sense */
+		regs[0x1c] = 0x05; /* AE H-start 160 */
+		regs[0x1d] = 0x03; /* AE V-start 96 */
+		regs[0x1e] = 0x0f; /* AE H-end 480 */
+		regs[0x1f] = 0x0c; /* AE V-end 384 */
+	}
+
+	/* Setup the gamma table (only used with the sn9c103 bridge) */
+	for (i = 0; i < 16; i++)
+		regs[0x20 + i] = i * 16;
+	regs[0x20 + i] = 255;
+
+	/* Special cases where some regs depend on mode or bridge */
+	switch (sd->sensor) {
+	case SENSOR_TAS5130CXX:
+		/* FIXME / TESTME
+		   probably not mode specific at all most likely the upper
+		   nibble of 0x19 is exposure (clock divider) just as with
+		   the tas5110, we need someone to test this. */
+		regs[0x19] = mode ? 0x23 : 0x43;
+		break;
+	case SENSOR_OV7630:
+		/* FIXME / TESTME for some reason with the 101/102 bridge the
+		   clock is set to 12 Mhz (reg1 == 0x04), rather then 24.
+		   Also the hstart needs to go from 1 to 2 when using a 103,
+		   which is likely related. This does not seem right. */
+		if (sd->bridge == BRIDGE_103) {
+			regs[0x01] = 0x44; /* Select 24 Mhz clock */
+			regs[0x12] = 0x02; /* Set hstart to 2 */
+		}
+		break;
+	case SENSOR_PAS202:
+		/* For some unknown reason we need to increase hstart by 1 on
+		   the sn9c103, otherwise we get wrong colors (bayer shift). */
+		if (sd->bridge == BRIDGE_103)
+			regs[0x12] += 1;
+		break;
+	}
+	/* Disable compression when the raw bayer format has been selected */
+	if (cam->cam_mode[gspca_dev->curr_mode].priv & MODE_RAW)
+		regs[0x18] &= ~0x80;
+
+	/* Vga mode emulation on SIF sensor? */
+	if (cam->cam_mode[gspca_dev->curr_mode].priv & MODE_REDUCED_SIF) {
+		regs[0x12] += 16;	/* hstart adjust */
+		regs[0x13] += 24;	/* vstart adjust */
+		regs[0x15]  = 320 / 16; /* hsize */
+		regs[0x16]  = 240 / 16; /* vsize */
+	}
+
+	/* reg 0x01 bit 2 video transfert on */
+	reg_w(gspca_dev, 0x01, &regs[0x01], 1);
+	/* reg 0x17 SensorClk enable inv Clk 0x60 */
+	reg_w(gspca_dev, 0x17, &regs[0x17], 1);
+	/* Set the registers from the template */
+	reg_w(gspca_dev, 0x01, &regs[0x01],
+	      (sd->bridge == BRIDGE_103) ? 0x30 : 0x1f);
+
+	/* Init the sensor */
+	i2c_w_vector(gspca_dev, sensor_data[sd->sensor].sensor_init,
+			sensor_data[sd->sensor].sensor_init_size);
+
+	/* Mode / bridge specific sensor setup */
+	switch (sd->sensor) {
+	case SENSOR_PAS202: {
+		const __u8 i2cpclockdiv[] =
+			{0xa0, 0x40, 0x02, 0x03, 0x00, 0x00, 0x00, 0x10};
+		/* clockdiv from 4 to 3 (7.5 -> 10 fps) when in low res mode */
+		if (mode)
+			i2c_w(gspca_dev, i2cpclockdiv);
+		break;
+	    }
+	case SENSOR_OV7630:
+		/* FIXME / TESTME We should be able to handle this identical
+		   for the 101/102 and the 103 case */
+		if (sd->bridge == BRIDGE_103) {
+			const __u8 i2c[] = { 0xa0, 0x21, 0x13,
+					     0x80, 0x00, 0x00, 0x00, 0x10 };
+			i2c_w(gspca_dev, i2c);
+		}
+		break;
+	}
+	/* H_size V_size 0x28, 0x1e -> 640x480. 0x16, 0x12 -> 352x288 */
+	reg_w(gspca_dev, 0x15, &regs[0x15], 2);
+	/* compression register */
+	reg_w(gspca_dev, 0x18, &regs[0x18], 1);
+	/* H_start */
+	reg_w(gspca_dev, 0x12, &regs[0x12], 1);
+	/* V_START */
+	reg_w(gspca_dev, 0x13, &regs[0x13], 1);
+	/* reset 0x17 SensorClk enable inv Clk 0x60 */
+				/*fixme: ov7630 [17]=68 8f (+20 if 102)*/
+	reg_w(gspca_dev, 0x17, &regs[0x17], 1);
+	/*MCKSIZE ->3 */	/*fixme: not ov7630*/
+	reg_w(gspca_dev, 0x19, &regs[0x19], 1);
+	/* AE_STRX AE_STRY AE_ENDX AE_ENDY */
+	reg_w(gspca_dev, 0x1c, &regs[0x1c], 4);
+	/* Enable video transfert */
+	reg_w(gspca_dev, 0x01, &regs[0x01], 1);
+	/* Compression */
+	reg_w(gspca_dev, 0x18, &regs[0x18], 2);
+	msleep(20);
+
+	sd->reg11 = -1;
+
+	setgain(gspca_dev);
+	setbrightness(gspca_dev);
+	setexposure(gspca_dev);
+	setfreq(gspca_dev);
+
+	sd->frames_to_drop = 0;
+	sd->autogain_ignore_frames = 0;
+	gspca_dev->exp_too_high_cnt = 0;
+	gspca_dev->exp_too_low_cnt = 0;
+	atomic_set(&sd->avg_lum, -1);
+	return gspca_dev->usb_err;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	sd_init(gspca_dev);
+}
+
+static u8* find_sof(struct gspca_dev *gspca_dev, u8 *data, int len)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int i, header_size = (sd->bridge == BRIDGE_103) ? 18 : 12;
+
+	/* frames start with:
+	 *	ff ff 00 c4 c4 96	synchro
+	 *	00		(unknown)
+	 *	xx		(frame sequence / size / compression)
+	 *	(xx)		(idem - extra byte for sn9c103)
+	 *	ll mm		brightness sum inside auto exposure
+	 *	ll mm		brightness sum outside auto exposure
+	 *	(xx xx xx xx xx)	audio values for snc103
+	 */
+	for (i = 0; i < len; i++) {
+		switch (sd->header_read) {
+		case 0:
+			if (data[i] == 0xff)
+				sd->header_read++;
+			break;
+		case 1:
+			if (data[i] == 0xff)
+				sd->header_read++;
+			else
+				sd->header_read = 0;
+			break;
+		case 2:
+			if (data[i] == 0x00)
+				sd->header_read++;
+			else if (data[i] != 0xff)
+				sd->header_read = 0;
+			break;
+		case 3:
+			if (data[i] == 0xc4)
+				sd->header_read++;
+			else if (data[i] == 0xff)
+				sd->header_read = 1;
+			else
+				sd->header_read = 0;
+			break;
+		case 4:
+			if (data[i] == 0xc4)
+				sd->header_read++;
+			else if (data[i] == 0xff)
+				sd->header_read = 1;
+			else
+				sd->header_read = 0;
+			break;
+		case 5:
+			if (data[i] == 0x96)
+				sd->header_read++;
+			else if (data[i] == 0xff)
+				sd->header_read = 1;
+			else
+				sd->header_read = 0;
+			break;
+		default:
+			sd->header[sd->header_read - 6] = data[i];
+			sd->header_read++;
+			if (sd->header_read == header_size) {
+				sd->header_read = 0;
+				return data + i + 1;
+			}
+		}
+	}
+	return NULL;
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	int fr_h_sz = 0, lum_offset = 0, len_after_sof = 0;
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam = &gspca_dev->cam;
+	u8 *sof;
+
+	sof = find_sof(gspca_dev, data, len);
+	if (sof) {
+		if (sd->bridge == BRIDGE_103) {
+			fr_h_sz = 18;
+			lum_offset = 3;
+		} else {
+			fr_h_sz = 12;
+			lum_offset = 2;
+		}
+
+		len_after_sof = len - (sof - data);
+		len = (sof - data) - fr_h_sz;
+		if (len < 0)
+			len = 0;
+	}
+
+	if (cam->cam_mode[gspca_dev->curr_mode].priv & MODE_RAW) {
+		/* In raw mode we sometimes get some garbage after the frame
+		   ignore this */
+		int used;
+		int size = cam->cam_mode[gspca_dev->curr_mode].sizeimage;
+
+		used = gspca_dev->image_len;
+		if (used + len > size)
+			len = size - used;
+	}
+
+	gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+
+	if (sof) {
+		int  lum = sd->header[lum_offset] +
+			  (sd->header[lum_offset + 1] << 8);
+
+		/* When exposure changes midway a frame we
+		   get a lum of 0 in this case drop 2 frames
+		   as the frames directly after an exposure
+		   change have an unstable image. Sometimes lum
+		   *really* is 0 (cam used in low light with
+		   low exposure setting), so do not drop frames
+		   if the previous lum was 0 too. */
+		if (lum == 0 && sd->prev_avg_lum != 0) {
+			lum = -1;
+			sd->frames_to_drop = 2;
+			sd->prev_avg_lum = 0;
+		} else
+			sd->prev_avg_lum = lum;
+		atomic_set(&sd->avg_lum, lum);
+
+		if (sd->frames_to_drop)
+			sd->frames_to_drop--;
+		else
+			gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
+
+		gspca_frame_add(gspca_dev, FIRST_PACKET, sof, len_after_sof);
+	}
+}
+
+#if IS_ENABLED(CONFIG_INPUT)
+static int sd_int_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,		/* interrupt packet data */
+			int len)		/* interrupt packet length */
+{
+	int ret = -EINVAL;
+
+	if (len == 1 && data[0] == 1) {
+		input_report_key(gspca_dev->input_dev, KEY_CAMERA, 1);
+		input_sync(gspca_dev->input_dev);
+		input_report_key(gspca_dev->input_dev, KEY_CAMERA, 0);
+		input_sync(gspca_dev->input_dev);
+		ret = 0;
+	}
+
+	return ret;
+}
+#endif
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.pkt_scan = sd_pkt_scan,
+	.dq_callback = do_autogain,
+#if IS_ENABLED(CONFIG_INPUT)
+	.int_pkt_scan = sd_int_pkt_scan,
+#endif
+};
+
+/* -- module initialisation -- */
+#define SB(sensor, bridge) \
+	.driver_info = (SENSOR_ ## sensor << 8) | BRIDGE_ ## bridge
+
+
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x0c45, 0x6001), SB(TAS5110C, 102)}, /* TAS5110C1B */
+	{USB_DEVICE(0x0c45, 0x6005), SB(TAS5110C, 101)}, /* TAS5110C1B */
+	{USB_DEVICE(0x0c45, 0x6007), SB(TAS5110D, 101)}, /* TAS5110D */
+	{USB_DEVICE(0x0c45, 0x6009), SB(PAS106, 101)},
+	{USB_DEVICE(0x0c45, 0x600d), SB(PAS106, 101)},
+	{USB_DEVICE(0x0c45, 0x6011), SB(OV6650, 101)},
+	{USB_DEVICE(0x0c45, 0x6019), SB(OV7630, 101)},
+	{USB_DEVICE(0x0c45, 0x6024), SB(TAS5130CXX, 102)},
+	{USB_DEVICE(0x0c45, 0x6025), SB(TAS5130CXX, 102)},
+	{USB_DEVICE(0x0c45, 0x6027), SB(OV7630, 101)}, /* Genius Eye 310 */
+	{USB_DEVICE(0x0c45, 0x6028), SB(PAS202, 102)},
+	{USB_DEVICE(0x0c45, 0x6029), SB(PAS106, 102)},
+	{USB_DEVICE(0x0c45, 0x602a), SB(HV7131D, 102)},
+	/* {USB_DEVICE(0x0c45, 0x602b), SB(MI0343, 102)}, */
+	{USB_DEVICE(0x0c45, 0x602c), SB(OV7630, 102)},
+	{USB_DEVICE(0x0c45, 0x602d), SB(HV7131R, 102)},
+	{USB_DEVICE(0x0c45, 0x602e), SB(OV7630, 102)},
+	/* {USB_DEVICE(0x0c45, 0x6030), SB(MI03XX, 102)}, */ /* MI0343 MI0360 MI0330 */
+	/* {USB_DEVICE(0x0c45, 0x6082), SB(MI03XX, 103)}, */ /* MI0343 MI0360 */
+	{USB_DEVICE(0x0c45, 0x6083), SB(HV7131D, 103)},
+	{USB_DEVICE(0x0c45, 0x608c), SB(HV7131R, 103)},
+	/* {USB_DEVICE(0x0c45, 0x608e), SB(CISVF10, 103)}, */
+	{USB_DEVICE(0x0c45, 0x608f), SB(OV7630, 103)},
+	{USB_DEVICE(0x0c45, 0x60a8), SB(PAS106, 103)},
+	{USB_DEVICE(0x0c45, 0x60aa), SB(TAS5130CXX, 103)},
+	{USB_DEVICE(0x0c45, 0x60af), SB(PAS202, 103)},
+	{USB_DEVICE(0x0c45, 0x60b0), SB(OV7630, 103)},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/sonixj.c b/drivers/media/usb/gspca/sonixj.c
new file mode 100644
index 0000000..df8d848
--- /dev/null
+++ b/drivers/media/usb/gspca/sonixj.c
@@ -0,0 +1,2985 @@
+/*
+ * Sonix sn9c102p sn9c105 sn9c120 (jpeg) subdriver
+ *
+ * Copyright (C) 2009-2011 Jean-François Moine <http://moinejf.free.fr>
+ * Copyright (C) 2005 Michel Xhaard mxhaard@magic.fr
+ *
+ * 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
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "sonixj"
+
+#include <linux/input.h>
+#include "gspca.h"
+#include "jpeg.h"
+
+MODULE_AUTHOR("Jean-François Moine <http://moinejf.free.fr>");
+MODULE_DESCRIPTION("GSPCA/SONIX JPEG USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+
+	atomic_t avg_lum;
+	struct v4l2_ctrl *brightness;
+	struct v4l2_ctrl *contrast;
+	struct v4l2_ctrl *saturation;
+	struct { /* red/blue balance control cluster */
+		struct v4l2_ctrl *red_bal;
+		struct v4l2_ctrl *blue_bal;
+	};
+	struct { /* hflip/vflip control cluster */
+		struct v4l2_ctrl *vflip;
+		struct v4l2_ctrl *hflip;
+	};
+	struct v4l2_ctrl *gamma;
+	struct v4l2_ctrl *illum;
+	struct v4l2_ctrl *sharpness;
+	struct v4l2_ctrl *freq;
+	u32 exposure;
+
+	struct work_struct work;
+
+	u32 pktsz;			/* (used by pkt_scan) */
+	u16 npkt;
+	s8 nchg;
+	s8 short_mark;
+
+	u8 quality;			/* image quality */
+#define QUALITY_MIN 25
+#define QUALITY_MAX 90
+#define QUALITY_DEF 70
+
+	u8 reg01;
+	u8 reg17;
+	u8 reg18;
+	u8 flags;
+
+	s8 ag_cnt;
+#define AG_CNT_START 13
+
+	u8 bridge;
+#define BRIDGE_SN9C102P 0
+#define BRIDGE_SN9C105 1
+#define BRIDGE_SN9C110 2
+#define BRIDGE_SN9C120 3
+	u8 sensor;			/* Type of image sensor chip */
+	u8 i2c_addr;
+
+	u8 jpeg_hdr[JPEG_HDR_SZ];
+};
+enum sensors {
+	SENSOR_ADCM1700,
+	SENSOR_GC0307,
+	SENSOR_HV7131R,
+	SENSOR_MI0360,
+	SENSOR_MI0360B,
+	SENSOR_MO4000,
+	SENSOR_MT9V111,
+	SENSOR_OM6802,
+	SENSOR_OV7630,
+	SENSOR_OV7648,
+	SENSOR_OV7660,
+	SENSOR_PO1030,
+	SENSOR_PO2030N,
+	SENSOR_SOI768,
+	SENSOR_SP80708,
+};
+
+static void qual_upd(struct work_struct *work);
+
+/* device flags */
+#define F_PDN_INV	0x01	/* inverse pin S_PWR_DN / sn_xxx tables */
+#define F_ILLUM		0x02	/* presence of illuminator */
+
+/* sn9c1xx definitions */
+/* register 0x01 */
+#define S_PWR_DN	0x01	/* sensor power down */
+#define S_PDN_INV	0x02	/* inverse pin S_PWR_DN */
+#define V_TX_EN		0x04	/* video transfer enable */
+#define LED		0x08	/* output to pin LED */
+#define SCL_SEL_OD	0x20	/* open-drain mode */
+#define SYS_SEL_48M	0x40	/* system clock 0: 24MHz, 1: 48MHz */
+/* register 0x17 */
+#define MCK_SIZE_MASK	0x1f	/* sensor master clock */
+#define SEN_CLK_EN	0x20	/* enable sensor clock */
+#define DEF_EN		0x80	/* defect pixel by 0: soft, 1: hard */
+
+static const struct v4l2_pix_format cif_mode[] = {
+	{352, 288, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 352,
+		.sizeimage = 352 * 288 * 4 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 0},
+};
+static const struct v4l2_pix_format vga_mode[] = {
+	{160, 120, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 160 * 120 * 4 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 2},
+	{320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 1},
+	{640, 480, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		/* Note 3 / 8 is not large enough, not even 5 / 8 is ?! */
+		.sizeimage = 640 * 480 * 3 / 4 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 0},
+};
+
+static const u8 sn_adcm1700[0x1c] = {
+/*	reg0	reg1	reg2	reg3	reg4	reg5	reg6	reg7 */
+	0x00,	0x43,	0x60,	0x00,	0x1a,	0x00,	0x00,	0x00,
+/*	reg8	reg9	rega	regb	regc	regd	rege	regf */
+	0x80,	0x51,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,
+/*	reg10	reg11	reg12	reg13	reg14	reg15	reg16	reg17 */
+	0x03,	0x00,	0x05,	0x01,	0x05,	0x16,	0x12,	0x42,
+/*	reg18	reg19	reg1a	reg1b */
+	0x06,	0x00,	0x00,	0x00
+};
+
+static const u8 sn_gc0307[0x1c] = {
+/*	reg0	reg1	reg2	reg3	reg4	reg5	reg6	reg7 */
+	0x00,	0x61,	0x62,	0x00,	0x1a,	0x00,	0x00,	0x00,
+/*	reg8	reg9	rega	regb	regc	regd	rege	regf */
+	0x80,	0x21,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,
+/*	reg10	reg11	reg12	reg13	reg14	reg15	reg16	reg17 */
+	0x03,	0x00,	0x03,	0x01,	0x08,	0x28,	0x1e,	0x02,
+/*	reg18	reg19	reg1a	reg1b */
+	0x06,	0x00,	0x00,	0x00
+};
+
+static const u8 sn_hv7131[0x1c] = {
+/*	reg0	reg1	reg2	reg3	reg4	reg5	reg6	reg7 */
+	0x00,	0x03,	0x60,	0x00,	0x1a,	0x20,	0x20,	0x20,
+/*	reg8	reg9	rega	regb	regc	regd	rege	regf */
+	0x81,	0x11,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,
+/*	reg10	reg11	reg12	reg13	reg14	reg15	reg16	reg17 */
+	0x03,	0x00,	0x00,	0x01,	0x03,	0x28,	0x1e,	0x41,
+/*	reg18	reg19	reg1a	reg1b */
+	0x0a,	0x00,	0x00,	0x00
+};
+
+static const u8 sn_mi0360[0x1c] = {
+/*	reg0	reg1	reg2	reg3	reg4	reg5	reg6	reg7 */
+	0x00,	0x63,	0x40,	0x00,	0x1a,	0x20,	0x20,	0x20,
+/*	reg8	reg9	rega	regb	regc	regd	rege	regf */
+	0x81,	0x5d,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,
+/*	reg10	reg11	reg12	reg13	reg14	reg15	reg16	reg17 */
+	0x03,	0x00,	0x00,	0x02,	0x0a,	0x28,	0x1e,	0x61,
+/*	reg18	reg19	reg1a	reg1b */
+	0x06,	0x00,	0x00,	0x00
+};
+
+static const u8 sn_mi0360b[0x1c] = {
+/*	reg0	reg1	reg2	reg3	reg4	reg5	reg6	reg7 */
+	0x00,	0x61,	0x40,	0x00,	0x1a,	0x00,	0x00,	0x00,
+/*	reg8	reg9	rega	regb	regc	regd	rege	regf */
+	0x81,	0x5d,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,
+/*	reg10	reg11	reg12	reg13	reg14	reg15	reg16	reg17 */
+	0x03,	0x00,	0x00,	0x02,	0x0a,	0x28,	0x1e,	0x40,
+/*	reg18	reg19	reg1a	reg1b */
+	0x06,	0x00,	0x00,	0x00
+};
+
+static const u8 sn_mo4000[0x1c] = {
+/*	reg0	reg1	reg2	reg3	reg4	reg5	reg6	reg7 */
+	0x00,	0x23,	0x60,	0x00,	0x1a,	0x00,	0x20,	0x18,
+/*	reg8	reg9	rega	regb	regc	regd	rege	regf */
+	0x81,	0x21,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,
+/*	reg10	reg11	reg12	reg13	reg14	reg15	reg16	reg17 */
+	0x03,	 0x00,	0x0b,	0x0f,	0x14,	0x28,	0x1e,	0x40,
+/*	reg18	reg19	reg1a	reg1b */
+	0x08,	0x00,	0x00,	0x00
+};
+
+static const u8 sn_mt9v111[0x1c] = {
+/*	reg0	reg1	reg2	reg3	reg4	reg5	reg6	reg7 */
+	0x00,	0x61,	0x40,	0x00,	0x1a,	0x20,	0x20,	0x20,
+/*	reg8	reg9	rega	regb	regc	regd	rege	regf */
+	0x81,	0x5c,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,
+/*	reg10	reg11	reg12	reg13	reg14	reg15	reg16	reg17 */
+	0x03,	0x00,	0x00,	0x02,	0x1c,	0x28,	0x1e,	0x40,
+/*	reg18	reg19	reg1a	reg1b */
+	0x06,	0x00,	0x00,	0x00
+};
+
+static const u8 sn_om6802[0x1c] = {
+/*	reg0	reg1	reg2	reg3	reg4	reg5	reg6	reg7 */
+	0x00,	0x23,	0x72,	0x00,	0x1a,	0x20,	0x20,	0x19,
+/*	reg8	reg9	rega	regb	regc	regd	rege	regf */
+	0x80,	0x34,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,
+/*	reg10	reg11	reg12	reg13	reg14	reg15	reg16	reg17 */
+	0x03,	0x00,	0x51,	0x01,	0x00,	0x28,	0x1e,	0x40,
+/*	reg18	reg19	reg1a	reg1b */
+	0x05,	0x00,	0x00,	0x00
+};
+
+static const u8 sn_ov7630[0x1c] = {
+/*	reg0	reg1	reg2	reg3	reg4	reg5	reg6	reg7 */
+	0x00,	0x21,	0x40,	0x00,	0x1a,	0x00,	0x00,	0x00,
+/*	reg8	reg9	rega	regb	regc	regd	rege	regf */
+	0x81,	0x21,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,
+/*	reg10	reg11	reg12	reg13	reg14	reg15	reg16	reg17 */
+	0x03,	0x00,	0x04,	0x01,	0x0a,	0x28,	0x1e,	0xc2,
+/*	reg18	reg19	reg1a	reg1b */
+	0x0b,	0x00,	0x00,	0x00
+};
+
+static const u8 sn_ov7648[0x1c] = {
+/*	reg0	reg1	reg2	reg3	reg4	reg5	reg6	reg7 */
+	0x00,	0x63,	0x40,	0x00,	0x1a,	0x20,	0x20,	0x20,
+/*	reg8	reg9	rega	regb	regc	regd	rege	regf */
+	0x81,	0x21,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,
+/*	reg10	reg11	reg12	reg13	reg14	reg15	reg16	reg17 */
+	0x03,	0x00,	0x00,	0x01,	0x00,	0x28,	0x1e,	0x00,
+/*	reg18	reg19	reg1a	reg1b */
+	0x0b,	0x00,	0x00,	0x00
+};
+
+static const u8 sn_ov7660[0x1c] = {
+/*	reg0	reg1	reg2	reg3	reg4	reg5	reg6	reg7 */
+	0x00,	0x61,	0x40,	0x00,	0x1a,	0x00,	0x00,	0x00,
+/*	reg8	reg9	rega	regb	regc	regd	rege	regf */
+	0x81,	0x21,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,
+/*	reg10	reg11	reg12	reg13	reg14	reg15	reg16	reg17 */
+	0x03,	0x00,	0x01,	0x01,	0x08,	0x28,	0x1e,	0x20,
+/*	reg18	reg19	reg1a	reg1b */
+	0x07,	0x00,	0x00,	0x00
+};
+
+static const u8 sn_po1030[0x1c] = {
+/*	reg0	reg1	reg2	reg3	reg4	reg5	reg6	reg7 */
+	0x00,	0x21,	0x62,	0x00,	0x1a,	0x20,	0x20,	0x20,
+/*	reg8	reg9	rega	regb	regc	regd	rege	regf */
+	0x81,	0x6e,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,
+/*	reg10	reg11	reg12	reg13	reg14	reg15	reg16	reg17 */
+	0x03,	0x00,	0x00,	0x06,	0x06,	0x28,	0x1e,	0x00,
+/*	reg18	reg19	reg1a	reg1b */
+	0x07,	0x00,	0x00,	0x00
+};
+
+static const u8 sn_po2030n[0x1c] = {
+/*	reg0	reg1	reg2	reg3	reg4	reg5	reg6	reg7 */
+	0x00,	0x63,	0x40,	0x00,	0x1a,	0x00,	0x00,	0x00,
+/*	reg8	reg9	rega	regb	regc	regd	rege	regf */
+	0x81,	0x6e,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,
+/*	reg10	reg11	reg12	reg13	reg14	reg15	reg16	reg17 */
+	0x03,	0x00,	0x00,	0x01,	0x14,	0x28,	0x1e,	0x00,
+/*	reg18	reg19	reg1a	reg1b */
+	0x07,	0x00,	0x00,	0x00
+};
+
+static const u8 sn_soi768[0x1c] = {
+/*	reg0	reg1	reg2	reg3	reg4	reg5	reg6	reg7 */
+	0x00,	0x21,	0x40,	0x00,	0x1a,	0x00,	0x00,	0x00,
+/*	reg8	reg9	rega	regb	regc	regd	rege	regf */
+	0x81,	0x21,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,
+/*	reg10	reg11	reg12	reg13	reg14	reg15	reg16	reg17 */
+	0x03,	0x00,	0x00,	0x01,	0x08,	0x28,	0x1e,	0x00,
+/*	reg18	reg19	reg1a	reg1b */
+	0x07,	0x00,	0x00,	0x00
+};
+
+static const u8 sn_sp80708[0x1c] = {
+/*	reg0	reg1	reg2	reg3	reg4	reg5	reg6	reg7 */
+	0x00,	0x63,	0x60,	0x00,	0x1a,	0x20,	0x20,	0x20,
+/*	reg8	reg9	rega	regb	regc	regd	rege	regf */
+	0x81,	0x18,	0x00,	0x00,	0x00,	0x00,	0x00,	0x00,
+/*	reg10	reg11	reg12	reg13	reg14	reg15	reg16	reg17 */
+	0x03,	0x00,	0x00,	0x03,	0x04,	0x28,	0x1e,	0x00,
+/*	reg18	reg19	reg1a	reg1b */
+	0x07,	0x00,	0x00,	0x00
+};
+
+/* sequence specific to the sensors - !! index = SENSOR_xxx */
+static const u8 *sn_tb[] = {
+[SENSOR_ADCM1700] =	sn_adcm1700,
+[SENSOR_GC0307] =	sn_gc0307,
+[SENSOR_HV7131R] =	sn_hv7131,
+[SENSOR_MI0360] =	sn_mi0360,
+[SENSOR_MI0360B] =	sn_mi0360b,
+[SENSOR_MO4000] =	sn_mo4000,
+[SENSOR_MT9V111] =	sn_mt9v111,
+[SENSOR_OM6802] =	sn_om6802,
+[SENSOR_OV7630] =	sn_ov7630,
+[SENSOR_OV7648] =	sn_ov7648,
+[SENSOR_OV7660] =	sn_ov7660,
+[SENSOR_PO1030] =	sn_po1030,
+[SENSOR_PO2030N] =	sn_po2030n,
+[SENSOR_SOI768] =	sn_soi768,
+[SENSOR_SP80708] =	sn_sp80708,
+};
+
+/* default gamma table */
+static const u8 gamma_def[17] = {
+	0x00, 0x2d, 0x46, 0x5a, 0x6c, 0x7c, 0x8b, 0x99,
+	0xa6, 0xb2, 0xbf, 0xca, 0xd5, 0xe0, 0xeb, 0xf5, 0xff
+};
+/* gamma for sensor ADCM1700 */
+static const u8 gamma_spec_0[17] = {
+	0x0f, 0x39, 0x5a, 0x74, 0x86, 0x95, 0xa6, 0xb4,
+	0xbd, 0xc4, 0xcc, 0xd4, 0xd5, 0xde, 0xe4, 0xed, 0xf5
+};
+/* gamma for sensors HV7131R and MT9V111 */
+static const u8 gamma_spec_1[17] = {
+	0x08, 0x3a, 0x52, 0x65, 0x75, 0x83, 0x91, 0x9d,
+	0xa9, 0xb4, 0xbe, 0xc8, 0xd2, 0xdb, 0xe4, 0xed, 0xf5
+};
+/* gamma for sensor GC0307 */
+static const u8 gamma_spec_2[17] = {
+	0x14, 0x37, 0x50, 0x6a, 0x7c, 0x8d, 0x9d, 0xab,
+	0xb5, 0xbf, 0xc2, 0xcb, 0xd1, 0xd6, 0xdb, 0xe1, 0xeb
+};
+/* gamma for sensor SP80708 */
+static const u8 gamma_spec_3[17] = {
+	0x0a, 0x2d, 0x4e, 0x68, 0x7d, 0x8f, 0x9f, 0xab,
+	0xb7, 0xc2, 0xcc, 0xd3, 0xd8, 0xde, 0xe2, 0xe5, 0xe6
+};
+
+/* color matrix and offsets */
+static const u8 reg84[] = {
+	0x14, 0x00, 0x27, 0x00, 0x07, 0x00,	/* YR YG YB gains */
+	0xe8, 0x0f, 0xda, 0x0f, 0x40, 0x00,	/* UR UG UB */
+	0x3e, 0x00, 0xcd, 0x0f, 0xf7, 0x0f,	/* VR VG VB */
+	0x00, 0x00, 0x00			/* YUV offsets */
+};
+
+#define DELAY	0xdd
+
+static const u8 adcm1700_sensor_init[][8] = {
+	{0xa0, 0x51, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xb0, 0x51, 0x04, 0x08, 0x00, 0x00, 0x00, 0x10},	/* reset */
+	{DELAY, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+	{0xb0, 0x51, 0x04, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{DELAY, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+	{0xb0, 0x51, 0x0c, 0xe0, 0x2e, 0x00, 0x00, 0x10},
+	{0xb0, 0x51, 0x10, 0x02, 0x02, 0x00, 0x00, 0x10},
+	{0xb0, 0x51, 0x14, 0x0e, 0x0e, 0x00, 0x00, 0x10},
+	{0xb0, 0x51, 0x1c, 0x00, 0x80, 0x00, 0x00, 0x10},
+	{0xb0, 0x51, 0x20, 0x01, 0x00, 0x00, 0x00, 0x10},
+	{DELAY, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+	{0xb0, 0x51, 0x04, 0x04, 0x00, 0x00, 0x00, 0x10},
+	{DELAY, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
+	{0xb0, 0x51, 0x04, 0x01, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x51, 0xfe, 0x10, 0x00, 0x00, 0x00, 0x10},
+	{0xb0, 0x51, 0x14, 0x01, 0x00, 0x00, 0x00, 0x10},
+	{0xb0, 0x51, 0x32, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{}
+};
+static const u8 adcm1700_sensor_param1[][8] = {
+	{0xb0, 0x51, 0x26, 0xf9, 0x01, 0x00, 0x00, 0x10},	/* exposure? */
+	{0xd0, 0x51, 0x1e, 0x8e, 0x8e, 0x8e, 0x8e, 0x10},
+
+	{0xa0, 0x51, 0xfe, 0x01, 0x00, 0x00, 0x00, 0x10},
+	{0xb0, 0x51, 0x00, 0x02, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x51, 0xfe, 0x10, 0x00, 0x00, 0x00, 0x10},
+	{0xb0, 0x51, 0x32, 0x00, 0x72, 0x00, 0x00, 0x10},
+	{0xd0, 0x51, 0x1e, 0xbe, 0xd7, 0xe8, 0xbe, 0x10},	/* exposure? */
+
+	{0xa0, 0x51, 0xfe, 0x01, 0x00, 0x00, 0x00, 0x10},
+	{0xb0, 0x51, 0x00, 0x02, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x51, 0xfe, 0x10, 0x00, 0x00, 0x00, 0x10},
+	{0xb0, 0x51, 0x32, 0x00, 0xa2, 0x00, 0x00, 0x10},
+	{}
+};
+static const u8 gc0307_sensor_init[][8] = {
+	{0xa0, 0x21, 0x43, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x44, 0xa2, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x01, 0x6a, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x02, 0x70, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x11, 0x05, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x05, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x06, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x07, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x08, 0x02, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x09, 0x01, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x0a, 0xe8, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x0b, 0x02, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x0c, 0x80, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x0d, 0x22, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x0e, 0x02, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x0f, 0xb2, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x12, 0x70, 0x00, 0x00, 0x00, 0x10},
+	{DELAY, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /*delay 10ms*/
+	{0xa0, 0x21, 0x13, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x15, 0xb8, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x16, 0x13, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x17, 0x52, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x18, 0x50, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x1e, 0x0d, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x1f, 0x32, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x61, 0x90, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x63, 0x70, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x65, 0x98, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x67, 0x90, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x03, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x04, 0x96, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x45, 0x27, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x47, 0x2c, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x43, 0x47, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x44, 0xd8, 0x00, 0x00, 0x00, 0x10},
+	{}
+};
+static const u8 gc0307_sensor_param1[][8] = {
+	{0xa0, 0x21, 0x68, 0x13, 0x00, 0x00, 0x00, 0x10},
+	{0xd0, 0x21, 0x61, 0x80, 0x00, 0x80, 0x00, 0x10},
+	{0xc0, 0x21, 0x65, 0x80, 0x00, 0x80, 0x00, 0x10},
+	{0xc0, 0x21, 0x63, 0xa0, 0x00, 0xa6, 0x00, 0x10},
+/*param3*/
+	{0xa0, 0x21, 0x01, 0x6e, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x21, 0x02, 0x88, 0x00, 0x00, 0x00, 0x10},
+	{}
+};
+
+static const u8 hv7131r_sensor_init[][8] = {
+	{0xc1, 0x11, 0x01, 0x08, 0x01, 0x00, 0x00, 0x10},
+	{0xb1, 0x11, 0x34, 0x17, 0x7f, 0x00, 0x00, 0x10},
+	{0xd1, 0x11, 0x40, 0xff, 0x7f, 0x7f, 0x7f, 0x10},
+/*	{0x91, 0x11, 0x44, 0x00, 0x00, 0x00, 0x00, 0x10}, */
+	{0xd1, 0x11, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x11, 0x14, 0x01, 0xe2, 0x02, 0x82, 0x10},
+/*	{0x91, 0x11, 0x18, 0x00, 0x00, 0x00, 0x00, 0x10}, */
+
+	{0xa1, 0x11, 0x01, 0x08, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x11, 0x01, 0x08, 0x00, 0x00, 0x00, 0x10},
+	{0xc1, 0x11, 0x25, 0x00, 0x61, 0xa8, 0x00, 0x10},
+	{0xa1, 0x11, 0x30, 0x22, 0x00, 0x00, 0x00, 0x10},
+	{0xc1, 0x11, 0x31, 0x20, 0x2e, 0x20, 0x00, 0x10},
+	{0xc1, 0x11, 0x25, 0x00, 0xc3, 0x50, 0x00, 0x10},
+	{0xa1, 0x11, 0x30, 0x07, 0x00, 0x00, 0x00, 0x10}, /* gain14 */
+	{0xc1, 0x11, 0x31, 0x10, 0x10, 0x10, 0x00, 0x10}, /* r g b 101a10 */
+
+	{0xa1, 0x11, 0x01, 0x08, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x11, 0x20, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x11, 0x21, 0xd0, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x11, 0x22, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x11, 0x23, 0x09, 0x00, 0x00, 0x00, 0x10},
+
+	{0xa1, 0x11, 0x01, 0x08, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x11, 0x20, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x11, 0x21, 0xd0, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x11, 0x22, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x11, 0x23, 0x10, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x11, 0x01, 0x18, 0x00, 0x00, 0x00, 0x10},
+							/* set sensor clock */
+	{}
+};
+static const u8 mi0360_sensor_init[][8] = {
+	{0xb1, 0x5d, 0x07, 0x00, 0x02, 0x00, 0x00, 0x10},
+	{0xb1, 0x5d, 0x0d, 0x00, 0x01, 0x00, 0x00, 0x10},
+	{0xb1, 0x5d, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x01, 0x00, 0x08, 0x00, 0x16, 0x10},
+	{0xd1, 0x5d, 0x03, 0x01, 0xe2, 0x02, 0x82, 0x10},
+	{0xd1, 0x5d, 0x05, 0x00, 0x09, 0x00, 0x53, 0x10},
+	{0xb1, 0x5d, 0x0d, 0x00, 0x02, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x12, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x14, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x16, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x18, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xb1, 0x5d, 0x32, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x20, 0x91, 0x01, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x22, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x24, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x26, 0x00, 0x00, 0x00, 0x24, 0x10},
+	{0xd1, 0x5d, 0x2f, 0xf7, 0xb0, 0x00, 0x04, 0x10},
+	{0xd1, 0x5d, 0x31, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x33, 0x00, 0x00, 0x01, 0x00, 0x10},
+	{0xb1, 0x5d, 0x3d, 0x06, 0x8f, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x40, 0x01, 0xe0, 0x00, 0xd1, 0x10},
+	{0xb1, 0x5d, 0x44, 0x00, 0x82, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x58, 0x00, 0x78, 0x00, 0x43, 0x10},
+	{0xd1, 0x5d, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x5e, 0x00, 0x00, 0xa3, 0x1d, 0x10},
+	{0xb1, 0x5d, 0x62, 0x04, 0x11, 0x00, 0x00, 0x10},
+
+	{0xb1, 0x5d, 0x20, 0x91, 0x01, 0x00, 0x00, 0x10},
+	{0xb1, 0x5d, 0x20, 0x11, 0x01, 0x00, 0x00, 0x10},
+	{0xb1, 0x5d, 0x09, 0x00, 0x64, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x2b, 0x00, 0xa0, 0x00, 0xb0, 0x10},
+	{0xd1, 0x5d, 0x2d, 0x00, 0xa0, 0x00, 0xa0, 0x10},
+
+	{0xb1, 0x5d, 0x0a, 0x00, 0x02, 0x00, 0x00, 0x10}, /* sensor clck ?2 */
+	{0xb1, 0x5d, 0x06, 0x00, 0x30, 0x00, 0x00, 0x10},
+	{0xb1, 0x5d, 0x05, 0x00, 0x0a, 0x00, 0x00, 0x10},
+	{0xb1, 0x5d, 0x09, 0x02, 0x35, 0x00, 0x00, 0x10}, /* exposure 2 */
+
+	{0xd1, 0x5d, 0x2b, 0x00, 0xb9, 0x00, 0xe3, 0x10},
+	{0xd1, 0x5d, 0x2d, 0x00, 0x5f, 0x00, 0xb9, 0x10}, /* 42 */
+/*	{0xb1, 0x5d, 0x35, 0x00, 0x67, 0x00, 0x00, 0x10}, * gain orig */
+/*	{0xb1, 0x5d, 0x35, 0x00, 0x20, 0x00, 0x00, 0x10}, * gain */
+	{0xb1, 0x5d, 0x07, 0x00, 0x03, 0x00, 0x00, 0x10}, /* update */
+	{0xb1, 0x5d, 0x07, 0x00, 0x02, 0x00, 0x00, 0x10}, /* sensor on */
+	{}
+};
+static const u8 mi0360b_sensor_init[][8] = {
+	{0xb1, 0x5d, 0x07, 0x00, 0x02, 0x00, 0x00, 0x10},
+	{0xb1, 0x5d, 0x0d, 0x00, 0x01, 0x00, 0x00, 0x10},
+	{DELAY, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /*delay 20ms*/
+	{0xb1, 0x5d, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{DELAY, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /*delay 20ms*/
+	{0xd1, 0x5d, 0x01, 0x00, 0x08, 0x00, 0x16, 0x10},
+	{0xd1, 0x5d, 0x03, 0x01, 0xe2, 0x02, 0x82, 0x10},
+	{0xd1, 0x5d, 0x05, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xb1, 0x5d, 0x0d, 0x00, 0x02, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x12, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x14, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x16, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x18, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xb1, 0x5d, 0x32, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x20, 0x11, 0x01, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x22, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x24, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x26, 0x00, 0x00, 0x00, 0x24, 0x10},
+	{0xd1, 0x5d, 0x2f, 0xf7, 0xb0, 0x00, 0x04, 0x10},
+	{0xd1, 0x5d, 0x31, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x33, 0x00, 0x00, 0x01, 0x00, 0x10},
+	{0xb1, 0x5d, 0x3d, 0x06, 0x8f, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x40, 0x01, 0xe0, 0x00, 0xd1, 0x10},
+	{0xb1, 0x5d, 0x44, 0x00, 0x82, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x58, 0x00, 0x78, 0x00, 0x43, 0x10},
+	{0xd1, 0x5d, 0x5a, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x5e, 0x00, 0x00, 0xa3, 0x1d, 0x10},
+	{0xb1, 0x5d, 0x62, 0x04, 0x11, 0x00, 0x00, 0x10},
+
+	{0xb1, 0x5d, 0x20, 0x11, 0x01, 0x00, 0x00, 0x10},
+	{0xb1, 0x5d, 0x20, 0x11, 0x01, 0x00, 0x00, 0x10},
+	{0xb1, 0x5d, 0x09, 0x00, 0x64, 0x00, 0x00, 0x10},
+	{0xd1, 0x5d, 0x2b, 0x00, 0x33, 0x00, 0xa0, 0x10},
+	{0xd1, 0x5d, 0x2d, 0x00, 0xa0, 0x00, 0x33, 0x10},
+	{}
+};
+static const u8 mi0360b_sensor_param1[][8] = {
+	{0xb1, 0x5d, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xb1, 0x5d, 0x06, 0x00, 0x53, 0x00, 0x00, 0x10},
+	{0xb1, 0x5d, 0x05, 0x00, 0x09, 0x00, 0x00, 0x10},
+	{0xb1, 0x5d, 0x09, 0x02, 0x35, 0x00, 0x00, 0x10}, /* exposure 2 */
+
+	{0xd1, 0x5d, 0x2b, 0x00, 0xd1, 0x01, 0xc9, 0x10},
+	{0xd1, 0x5d, 0x2d, 0x00, 0xed, 0x00, 0xd1, 0x10},
+	{0xb1, 0x5d, 0x07, 0x00, 0x03, 0x00, 0x00, 0x10}, /* update */
+	{0xb1, 0x5d, 0x07, 0x00, 0x02, 0x00, 0x00, 0x10}, /* sensor on */
+	{}
+};
+static const u8 mo4000_sensor_init[][8] = {
+	{0xa1, 0x21, 0x01, 0x02, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x02, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x03, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x04, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x05, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x05, 0x04, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x06, 0x80, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x06, 0x81, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x11, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x11, 0x20, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x11, 0x30, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x11, 0x38, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x11, 0x38, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x12, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x0f, 0x20, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x10, 0x20, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x11, 0x38, 0x00, 0x00, 0x00, 0x10},
+	{}
+};
+static const u8 mt9v111_sensor_init[][8] = {
+	{0xb1, 0x5c, 0x0d, 0x00, 0x01, 0x00, 0x00, 0x10}, /* reset? */
+	{DELAY, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* delay 20ms */
+	{0xb1, 0x5c, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xb1, 0x5c, 0x01, 0x00, 0x01, 0x00, 0x00, 0x10}, /* IFP select */
+	{0xb1, 0x5c, 0x08, 0x04, 0x80, 0x00, 0x00, 0x10}, /* output fmt ctrl */
+	{0xb1, 0x5c, 0x06, 0x00, 0x00, 0x00, 0x00, 0x10}, /* op mode ctrl */
+	{0xb1, 0x5c, 0x01, 0x00, 0x04, 0x00, 0x00, 0x10}, /* sensor select */
+	{0xb1, 0x5c, 0x08, 0x00, 0x08, 0x00, 0x00, 0x10}, /* row start */
+	{0xb1, 0x5c, 0x02, 0x00, 0x16, 0x00, 0x00, 0x10}, /* col start */
+	{0xb1, 0x5c, 0x03, 0x01, 0xe7, 0x00, 0x00, 0x10}, /* window height */
+	{0xb1, 0x5c, 0x04, 0x02, 0x87, 0x00, 0x00, 0x10}, /* window width */
+	{0xb1, 0x5c, 0x07, 0x30, 0x02, 0x00, 0x00, 0x10}, /* output ctrl */
+	{0xb1, 0x5c, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x10}, /* shutter delay */
+	{0xb1, 0x5c, 0x12, 0x00, 0xb0, 0x00, 0x00, 0x10}, /* zoom col start */
+	{0xb1, 0x5c, 0x13, 0x00, 0x7c, 0x00, 0x00, 0x10}, /* zoom row start */
+	{0xb1, 0x5c, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x10}, /* digital zoom */
+	{0xb1, 0x5c, 0x20, 0x00, 0x00, 0x00, 0x00, 0x10}, /* read mode */
+	{0xb1, 0x5c, 0x20, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{}
+};
+static const u8 mt9v111_sensor_param1[][8] = {
+	{0xd1, 0x5c, 0x2b, 0x00, 0x33, 0x00, 0xad, 0x10}, /* G1 and B gains */
+	{0xd1, 0x5c, 0x2d, 0x00, 0xad, 0x00, 0x33, 0x10}, /* R and G2 gains */
+	{0xb1, 0x5c, 0x06, 0x00, 0x40, 0x00, 0x00, 0x10}, /* vert blanking */
+	{0xb1, 0x5c, 0x05, 0x00, 0x09, 0x00, 0x00, 0x10}, /* horiz blanking */
+	{0xb1, 0x5c, 0x35, 0x01, 0xc0, 0x00, 0x00, 0x10}, /* global gain */
+	{}
+};
+static const u8 om6802_init0[2][8] = {
+/*fixme: variable*/
+	{0xa0, 0x34, 0x29, 0x0e, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x34, 0x23, 0xb0, 0x00, 0x00, 0x00, 0x10},
+};
+static const u8 om6802_sensor_init[][8] = {
+	{0xa0, 0x34, 0xdf, 0x6d, 0x00, 0x00, 0x00, 0x10},
+						/* factory mode */
+	{0xa0, 0x34, 0xdd, 0x18, 0x00, 0x00, 0x00, 0x10},
+						/* output raw RGB */
+	{0xa0, 0x34, 0x5a, 0xc0, 0x00, 0x00, 0x00, 0x10},
+/*	{0xa0, 0x34, 0xfb, 0x11, 0x00, 0x00, 0x00, 0x10}, */
+	{0xa0, 0x34, 0xf0, 0x04, 0x00, 0x00, 0x00, 0x10},
+		/* auto-exposure speed (0) / white balance mode (auto RGB) */
+/*	{0xa0, 0x34, 0xf1, 0x02, 0x00, 0x00, 0x00, 0x10},
+							 * set color mode */
+/*	{0xa0, 0x34, 0xfe, 0x5b, 0x00, 0x00, 0x00, 0x10},
+						 * max AGC value in AE */
+/*	{0xa0, 0x34, 0xe5, 0x00, 0x00, 0x00, 0x00, 0x10},
+							 * preset AGC */
+/*	{0xa0, 0x34, 0xe6, 0x00, 0x00, 0x00, 0x00, 0x10},
+						 * preset brightness */
+/*	{0xa0, 0x34, 0xe7, 0x00, 0x00, 0x00, 0x00, 0x10},
+							 * preset contrast */
+/*	{0xa0, 0x34, 0xe8, 0x31, 0x00, 0x00, 0x00, 0x10},
+							 * preset gamma */
+	{0xa0, 0x34, 0xe9, 0x0f, 0x00, 0x00, 0x00, 0x10},
+				/* luminance mode (0x4f -> AutoExpo on) */
+	{0xa0, 0x34, 0xe4, 0xff, 0x00, 0x00, 0x00, 0x10},
+							/* preset shutter */
+/*	{0xa0, 0x34, 0xef, 0x00, 0x00, 0x00, 0x00, 0x10},
+							 * auto frame rate */
+/*	{0xa0, 0x34, 0xfb, 0xee, 0x00, 0x00, 0x00, 0x10}, */
+	{0xa0, 0x34, 0x5d, 0x80, 0x00, 0x00, 0x00, 0x10},
+	{}
+};
+static const u8 om6802_sensor_param1[][8] = {
+	{0xa0, 0x34, 0x71, 0x84, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x34, 0x72, 0x05, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x34, 0x68, 0x80, 0x00, 0x00, 0x00, 0x10},
+	{0xa0, 0x34, 0x69, 0x01, 0x00, 0x00, 0x00, 0x10},
+	{}
+};
+static const u8 ov7630_sensor_init[][8] = {
+	{0xa1, 0x21, 0x76, 0x01, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x12, 0xc8, 0x00, 0x00, 0x00, 0x10},
+	{DELAY, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* delay 20ms */
+	{0xa1, 0x21, 0x12, 0x48, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x12, 0xc8, 0x00, 0x00, 0x00, 0x10},
+	{DELAY, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* delay 20ms */
+	{0xa1, 0x21, 0x12, 0x48, 0x00, 0x00, 0x00, 0x10},
+/* win: i2c_r from 00 to 80 */
+	{0xd1, 0x21, 0x03, 0x80, 0x10, 0x20, 0x80, 0x10},
+	{0xb1, 0x21, 0x0c, 0x20, 0x20, 0x00, 0x00, 0x10},
+/* HDG: 0x11 was 0x00 change to 0x01 for better exposure (15 fps instead of 30)
+	0x13 was 0xc0 change to 0xc3 for auto gain and exposure */
+	{0xd1, 0x21, 0x11, 0x01, 0x48, 0xc3, 0x00, 0x10},
+	{0xb1, 0x21, 0x15, 0x80, 0x03, 0x00, 0x00, 0x10},
+	{0xd1, 0x21, 0x17, 0x1b, 0xbd, 0x05, 0xf6, 0x10},
+	{0xa1, 0x21, 0x1b, 0x04, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x21, 0x1f, 0x00, 0x80, 0x80, 0x80, 0x10},
+	{0xd1, 0x21, 0x23, 0xde, 0x10, 0x8a, 0xa0, 0x10},
+	{0xc1, 0x21, 0x27, 0xca, 0xa2, 0x74, 0x00, 0x10},
+	{0xd1, 0x21, 0x2a, 0x88, 0x00, 0x88, 0x01, 0x10},
+	{0xc1, 0x21, 0x2e, 0x80, 0x00, 0x18, 0x00, 0x10},
+	{0xa1, 0x21, 0x21, 0x08, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x22, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xb1, 0x21, 0x32, 0xc2, 0x08, 0x00, 0x00, 0x10},
+	{0xb1, 0x21, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x21, 0x60, 0x05, 0x40, 0x12, 0x57, 0x10},
+	{0xa1, 0x21, 0x64, 0x73, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x21, 0x65, 0x00, 0x55, 0x01, 0xac, 0x10},
+	{0xa1, 0x21, 0x69, 0x38, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x21, 0x6f, 0x1f, 0x01, 0x00, 0x10, 0x10},
+	{0xd1, 0x21, 0x73, 0x50, 0x20, 0x02, 0x01, 0x10},
+	{0xd1, 0x21, 0x77, 0xf3, 0x90, 0x98, 0x98, 0x10},
+	{0xc1, 0x21, 0x7b, 0x00, 0x4c, 0xf7, 0x00, 0x10},
+	{0xd1, 0x21, 0x17, 0x1b, 0xbd, 0x05, 0xf6, 0x10},
+	{0xa1, 0x21, 0x1b, 0x04, 0x00, 0x00, 0x00, 0x10},
+	{}
+};
+static const u8 ov7630_sensor_param1[][8] = {
+	{0xa1, 0x21, 0x12, 0x48, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x12, 0x48, 0x00, 0x00, 0x00, 0x10},
+/*fixme: + 0x12, 0x04*/
+/*	{0xa1, 0x21, 0x75, 0x82, 0x00, 0x00, 0x00, 0x10},  * COMN
+							 * set by setvflip */
+	{0xa1, 0x21, 0x10, 0x32, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xb1, 0x21, 0x01, 0x80, 0x80, 0x00, 0x00, 0x10},
+/* */
+/*	{0xa1, 0x21, 0x2a, 0x88, 0x00, 0x00, 0x00, 0x10}, * set by setfreq */
+/*	{0xa1, 0x21, 0x2b, 0x34, 0x00, 0x00, 0x00, 0x10}, * set by setfreq */
+/* */
+	{0xa1, 0x21, 0x10, 0x83, 0x00, 0x00, 0x00, 0x10},
+/*	{0xb1, 0x21, 0x01, 0x88, 0x70, 0x00, 0x00, 0x10}, */
+	{}
+};
+
+static const u8 ov7648_sensor_init[][8] = {
+	{0xa1, 0x21, 0x76, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x12, 0x80, 0x00, 0x00, 0x00, 0x10},	/* reset */
+	{DELAY, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* delay 20ms */
+	{0xa1, 0x21, 0x12, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x21, 0x03, 0xa4, 0x30, 0x88, 0x00, 0x10},
+	{0xb1, 0x21, 0x11, 0x80, 0x08, 0x00, 0x00, 0x10},
+	{0xc1, 0x21, 0x13, 0xa0, 0x04, 0x84, 0x00, 0x10},
+	{0xd1, 0x21, 0x17, 0x1a, 0x02, 0xba, 0xf4, 0x10},
+	{0xa1, 0x21, 0x1b, 0x04, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x21, 0x1f, 0x41, 0xc0, 0x80, 0x80, 0x10},
+	{0xd1, 0x21, 0x23, 0xde, 0xa0, 0x80, 0x32, 0x10},
+	{0xd1, 0x21, 0x27, 0xfe, 0xa0, 0x00, 0x91, 0x10},
+	{0xd1, 0x21, 0x2b, 0x00, 0x88, 0x85, 0x80, 0x10},
+	{0xc1, 0x21, 0x2f, 0x9c, 0x00, 0xc4, 0x00, 0x10},
+	{0xd1, 0x21, 0x60, 0xa6, 0x60, 0x88, 0x12, 0x10},
+	{0xd1, 0x21, 0x64, 0x88, 0x00, 0x00, 0x94, 0x10},
+	{0xd1, 0x21, 0x68, 0x7a, 0x0c, 0x00, 0x00, 0x10},
+	{0xd1, 0x21, 0x6c, 0x11, 0x33, 0x22, 0x00, 0x10},
+	{0xd1, 0x21, 0x70, 0x11, 0x00, 0x10, 0x50, 0x10},
+	{0xd1, 0x21, 0x74, 0x20, 0x06, 0x00, 0xb5, 0x10},
+	{0xd1, 0x21, 0x78, 0x8a, 0x00, 0x00, 0x00, 0x10},
+	{0xb1, 0x21, 0x7c, 0x00, 0x43, 0x00, 0x00, 0x10},
+
+	{0xd1, 0x21, 0x21, 0x86, 0x00, 0xde, 0xa0, 0x10},
+/*	{0xd1, 0x21, 0x25, 0x80, 0x32, 0xfe, 0xa0, 0x10}, jfm done */
+/*	{0xd1, 0x21, 0x29, 0x00, 0x91, 0x00, 0x88, 0x10}, jfm done */
+/*	{0xb1, 0x21, 0x2d, 0x85, 0x00, 0x00, 0x00, 0x10}, set by setfreq */
+	{}
+};
+static const u8 ov7648_sensor_param1[][8] = {
+/*	{0xa1, 0x21, 0x12, 0x08, 0x00, 0x00, 0x00, 0x10}, jfm done */
+/*	{0xa1, 0x21, 0x75, 0x06, 0x00, 0x00, 0x00, 0x10},   * COMN
+							 * set by setvflip */
+	{0xa1, 0x21, 0x19, 0x02, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x10, 0x32, 0x00, 0x00, 0x00, 0x10},
+/*	{0xa1, 0x21, 0x16, 0x00, 0x00, 0x00, 0x00, 0x10}, jfm done */
+/*	{0xa1, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10},  * GAIN - def */
+/*	{0xb1, 0x21, 0x01, 0x6c, 0x6c, 0x00, 0x00, 0x10},  * B R - def: 80 */
+/*...*/
+	{0xa1, 0x21, 0x11, 0x81, 0x00, 0x00, 0x00, 0x10}, /* CLKRC */
+/*	{0xa1, 0x21, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x10}, jfm done */
+/*	{0xa1, 0x21, 0x16, 0x00, 0x00, 0x00, 0x00, 0x10}, jfm done */
+/*	{0xa1, 0x21, 0x2a, 0x91, 0x00, 0x00, 0x00, 0x10}, jfm done */
+/*	{0xa1, 0x21, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x10}, jfm done */
+/*	{0xb1, 0x21, 0x01, 0x64, 0x84, 0x00, 0x00, 0x10},  * B R - def: 80 */
+
+	{}
+};
+
+static const u8 ov7660_sensor_init[][8] = {
+	{0xa1, 0x21, 0x12, 0x80, 0x00, 0x00, 0x00, 0x10}, /* reset SCCB */
+	{DELAY, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* delay 20ms */
+	{0xa1, 0x21, 0x12, 0x05, 0x00, 0x00, 0x00, 0x10},
+						/* Outformat = rawRGB */
+	{0xa1, 0x21, 0x13, 0xb8, 0x00, 0x00, 0x00, 0x10}, /* init COM8 */
+	{0xd1, 0x21, 0x00, 0x01, 0x74, 0x92, 0x00, 0x10},
+						/* GAIN BLUE RED VREF */
+	{0xd1, 0x21, 0x04, 0x00, 0x7d, 0x62, 0x00, 0x10},
+						/* COM 1 BAVE GEAVE AECHH */
+	{0xb1, 0x21, 0x08, 0x83, 0x01, 0x00, 0x00, 0x10}, /* RAVE COM2 */
+	{0xd1, 0x21, 0x0c, 0x00, 0x08, 0x04, 0x4f, 0x10}, /* COM 3 4 5 6 */
+	{0xd1, 0x21, 0x10, 0x7f, 0x40, 0x05, 0xff, 0x10},
+						/* AECH CLKRC COM7 COM8 */
+	{0xc1, 0x21, 0x14, 0x2c, 0x00, 0x02, 0x00, 0x10}, /* COM9 COM10 */
+	{0xd1, 0x21, 0x17, 0x10, 0x60, 0x02, 0x7b, 0x10},
+						/* HSTART HSTOP VSTRT VSTOP */
+	{0xa1, 0x21, 0x1b, 0x02, 0x00, 0x00, 0x00, 0x10}, /* PSHFT */
+	{0xb1, 0x21, 0x1e, 0x01, 0x0e, 0x00, 0x00, 0x10}, /* MVFP LAEC */
+	{0xd1, 0x21, 0x20, 0x07, 0x07, 0x07, 0x07, 0x10},
+					/* BOS GBOS GROS ROS (BGGR offset) */
+/*	{0xd1, 0x21, 0x24, 0x68, 0x58, 0xd4, 0x80, 0x10}, */
+	{0xd1, 0x21, 0x24, 0x78, 0x68, 0xd4, 0x80, 0x10},
+						/* AEW AEB VPT BBIAS */
+	{0xd1, 0x21, 0x28, 0x80, 0x30, 0x00, 0x00, 0x10},
+						/* GbBIAS RSVD EXHCH EXHCL */
+	{0xd1, 0x21, 0x2c, 0x80, 0x00, 0x00, 0x62, 0x10},
+						/* RBIAS ADVFL ASDVFH YAVE */
+	{0xc1, 0x21, 0x30, 0x08, 0x30, 0xb4, 0x00, 0x10},
+						/* HSYST HSYEN HREF */
+	{0xd1, 0x21, 0x33, 0x00, 0x07, 0x84, 0x00, 0x10}, /* reserved */
+	{0xd1, 0x21, 0x37, 0x0c, 0x02, 0x43, 0x00, 0x10},
+						/* ADC ACOM OFON TSLB */
+	{0xd1, 0x21, 0x3b, 0x02, 0x6c, 0x19, 0x0e, 0x10},
+						/* COM11 COM12 COM13 COM14 */
+	{0xd1, 0x21, 0x3f, 0x41, 0xc1, 0x22, 0x08, 0x10},
+						/* EDGE COM15 COM16 COM17 */
+	{0xd1, 0x21, 0x43, 0xf0, 0x10, 0x78, 0xa8, 0x10}, /* reserved */
+	{0xd1, 0x21, 0x47, 0x60, 0x80, 0x00, 0x00, 0x10}, /* reserved */
+	{0xd1, 0x21, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x10}, /* reserved */
+	{0xd1, 0x21, 0x4f, 0x46, 0x36, 0x0f, 0x17, 0x10}, /* MTX 1 2 3 4 */
+	{0xd1, 0x21, 0x53, 0x7f, 0x96, 0x40, 0x40, 0x10}, /* MTX 5 6 7 8 */
+	{0xb1, 0x21, 0x57, 0x40, 0x0f, 0x00, 0x00, 0x10}, /* MTX9 MTXS */
+	{0xd1, 0x21, 0x59, 0xba, 0x9a, 0x22, 0xb9, 0x10}, /* reserved */
+	{0xd1, 0x21, 0x5d, 0x9b, 0x10, 0xf0, 0x05, 0x10}, /* reserved */
+	{0xa1, 0x21, 0x61, 0x60, 0x00, 0x00, 0x00, 0x10}, /* reserved */
+	{0xd1, 0x21, 0x62, 0x00, 0x00, 0x50, 0x30, 0x10},
+						/* LCC1 LCC2 LCC3 LCC4 */
+	{0xa1, 0x21, 0x66, 0x00, 0x00, 0x00, 0x00, 0x10}, /* LCC5 */
+	{0xd1, 0x21, 0x67, 0x80, 0x7a, 0x90, 0x80, 0x10}, /* MANU */
+	{0xa1, 0x21, 0x6b, 0x0a, 0x00, 0x00, 0x00, 0x10},
+					/* band gap reference [0:3] DBLV */
+	{0xd1, 0x21, 0x6c, 0x30, 0x48, 0x80, 0x74, 0x10}, /* gamma curve */
+	{0xd1, 0x21, 0x70, 0x64, 0x60, 0x5c, 0x58, 0x10}, /* gamma curve */
+	{0xd1, 0x21, 0x74, 0x54, 0x4c, 0x40, 0x38, 0x10}, /* gamma curve */
+	{0xd1, 0x21, 0x78, 0x34, 0x30, 0x2f, 0x2b, 0x10}, /* gamma curve */
+	{0xd1, 0x21, 0x7c, 0x03, 0x07, 0x17, 0x34, 0x10}, /* gamma curve */
+	{0xd1, 0x21, 0x80, 0x41, 0x4d, 0x58, 0x63, 0x10}, /* gamma curve */
+	{0xd1, 0x21, 0x84, 0x6e, 0x77, 0x87, 0x95, 0x10}, /* gamma curve */
+	{0xc1, 0x21, 0x88, 0xaf, 0xc7, 0xdf, 0x00, 0x10}, /* gamma curve */
+	{0xc1, 0x21, 0x8b, 0x99, 0x99, 0xcf, 0x00, 0x10}, /* reserved */
+	{0xb1, 0x21, 0x92, 0x00, 0x00, 0x00, 0x00, 0x10}, /* DM_LNL/H */
+/* not in all ms-win traces*/
+	{0xa1, 0x21, 0xa1, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{}
+};
+static const u8 ov7660_sensor_param1[][8] = {
+	{0xa1, 0x21, 0x1e, 0x01, 0x00, 0x00, 0x00, 0x10}, /* MVFP */
+						/* bits[3..0]reserved */
+	{0xa1, 0x21, 0x1e, 0x01, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x03, 0x00, 0x00, 0x00, 0x00, 0x10},
+						/* VREF vertical frame ctrl */
+	{0xa1, 0x21, 0x03, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x10, 0x20, 0x00, 0x00, 0x00, 0x10}, /* AECH 0x20 */
+	{0xa1, 0x21, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x10}, /* ADVFL */
+	{0xa1, 0x21, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x10}, /* ADVFH */
+	{0xa1, 0x21, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x10}, /* GAIN */
+/*	{0xb1, 0x21, 0x01, 0x78, 0x78, 0x00, 0x00, 0x10}, * BLUE */
+/****** (some exchanges in the win trace) ******/
+/*fixme:param2*/
+	{0xa1, 0x21, 0x93, 0x00, 0x00, 0x00, 0x00, 0x10},/* dummy line hight */
+	{0xa1, 0x21, 0x92, 0x25, 0x00, 0x00, 0x00, 0x10}, /* dummy line low */
+	{0xa1, 0x21, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x10}, /* EXHCH */
+	{0xa1, 0x21, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x10}, /* EXHCL */
+/*	{0xa1, 0x21, 0x02, 0x90, 0x00, 0x00, 0x00, 0x10},  * RED */
+/****** (some exchanges in the win trace) ******/
+/******!! startsensor KO if changed !!****/
+/*fixme: param3*/
+	{0xa1, 0x21, 0x93, 0x01, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x92, 0xff, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x2b, 0xc3, 0x00, 0x00, 0x00, 0x10},
+	{}
+};
+
+static const u8 po1030_sensor_init[][8] = {
+/* the sensor registers are described in m5602/m5602_po1030.h */
+	{0xa1, 0x6e, 0x3f, 0x20, 0x00, 0x00, 0x00, 0x10}, /* sensor reset */
+	{DELAY, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* delay 20ms */
+	{0xa1, 0x6e, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x6e, 0x3e, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x04, 0x02, 0xb1, 0x02, 0x39, 0x10},
+	{0xd1, 0x6e, 0x08, 0x00, 0x01, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x0c, 0x02, 0x7f, 0x01, 0xe0, 0x10},
+	{0xd1, 0x6e, 0x12, 0x03, 0x02, 0x00, 0x03, 0x10},
+	{0xd1, 0x6e, 0x16, 0x85, 0x40, 0x4a, 0x40, 0x10}, /* r/g1/b/g2 gains */
+	{0xc1, 0x6e, 0x1a, 0x00, 0x80, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x1d, 0x08, 0x03, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x23, 0x00, 0xb0, 0x00, 0x94, 0x10},
+	{0xd1, 0x6e, 0x27, 0x58, 0x00, 0x00, 0x00, 0x10},
+	{0xb1, 0x6e, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x2d, 0x14, 0x35, 0x61, 0x84, 0x10}, /* gamma corr */
+	{0xd1, 0x6e, 0x31, 0xa2, 0xbd, 0xd8, 0xff, 0x10},
+	{0xd1, 0x6e, 0x35, 0x06, 0x1e, 0x12, 0x02, 0x10}, /* color matrix */
+	{0xd1, 0x6e, 0x39, 0xaa, 0x53, 0x37, 0xd5, 0x10},
+	{0xa1, 0x6e, 0x3d, 0xf2, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x3e, 0x00, 0x00, 0x80, 0x03, 0x10},
+	{0xd1, 0x6e, 0x42, 0x03, 0x00, 0x00, 0x00, 0x10},
+	{0xc1, 0x6e, 0x46, 0x00, 0x80, 0x80, 0x00, 0x10},
+	{0xd1, 0x6e, 0x4b, 0x02, 0xef, 0x08, 0xcd, 0x10},
+	{0xd1, 0x6e, 0x4f, 0x00, 0xd0, 0x00, 0xa0, 0x10},
+	{0xd1, 0x6e, 0x53, 0x01, 0xaa, 0x01, 0x40, 0x10},
+	{0xd1, 0x6e, 0x5a, 0x50, 0x04, 0x30, 0x03, 0x10}, /* raw rgb bayer */
+	{0xa1, 0x6e, 0x5e, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x5f, 0x10, 0x40, 0xff, 0x00, 0x10},
+
+	{0xd1, 0x6e, 0x63, 0x40, 0x40, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x6b, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xc1, 0x6e, 0x73, 0x10, 0x80, 0xeb, 0x00, 0x10},
+	{}
+};
+static const u8 po1030_sensor_param1[][8] = {
+/* from ms-win traces - these values change with auto gain/expo/wb.. */
+	{0xa1, 0x6e, 0x1e, 0x03, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x6e, 0x1e, 0x03, 0x00, 0x00, 0x00, 0x10},
+/* mean values */
+	{0xc1, 0x6e, 0x1a, 0x02, 0xd4, 0xa4, 0x00, 0x10}, /* integlines */
+	{0xa1, 0x6e, 0x15, 0x04, 0x00, 0x00, 0x00, 0x10}, /* global gain */
+	{0xc1, 0x6e, 0x16, 0x40, 0x40, 0x40, 0x00, 0x10}, /* r/g1/b gains */
+
+	{0xa1, 0x6e, 0x1d, 0x08, 0x00, 0x00, 0x00, 0x10}, /* control1 */
+	{0xa1, 0x6e, 0x06, 0x02, 0x00, 0x00, 0x00, 0x10}, /* frameheight */
+	{0xa1, 0x6e, 0x07, 0xd5, 0x00, 0x00, 0x00, 0x10},
+/*	{0xc1, 0x6e, 0x16, 0x49, 0x40, 0x45, 0x00, 0x10}, */
+	{}
+};
+
+static const u8 po2030n_sensor_init[][8] = {
+	{0xa1, 0x6e, 0x1e, 0x1a, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x6e, 0x1f, 0x99, 0x00, 0x00, 0x00, 0x10},
+	{DELAY, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* delay 10ms */
+	{0xa1, 0x6e, 0x1e, 0x0a, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x6e, 0x1f, 0x19, 0x00, 0x00, 0x00, 0x10},
+	{DELAY, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* delay 10ms */
+	{0xa1, 0x6e, 0x20, 0x44, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x6e, 0x04, 0x03, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x6e, 0x05, 0x70, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x6e, 0x06, 0x02, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x6e, 0x07, 0x25, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x08, 0x00, 0xd0, 0x00, 0x08, 0x10},
+	{0xd1, 0x6e, 0x0c, 0x03, 0x50, 0x01, 0xe8, 0x10},
+	{0xd1, 0x6e, 0x1d, 0x20, 0x0a, 0x19, 0x44, 0x10},
+	{0xd1, 0x6e, 0x21, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x25, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x29, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x31, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x35, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x39, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x3d, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x41, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x45, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x49, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x4d, 0x00, 0x00, 0x00, 0xed, 0x10},
+	{0xd1, 0x6e, 0x51, 0x17, 0x4a, 0x2f, 0xc0, 0x10},
+	{0xd1, 0x6e, 0x55, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x59, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x5d, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x61, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x65, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x69, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x71, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x75, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x79, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x7d, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x81, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x85, 0x00, 0x00, 0x00, 0x08, 0x10},
+	{0xd1, 0x6e, 0x89, 0x01, 0xe8, 0x00, 0x01, 0x10},
+	{0xa1, 0x6e, 0x8d, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x21, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x25, 0x00, 0x00, 0x00, 0x01, 0x10},
+	{0xd1, 0x6e, 0x29, 0xe6, 0x00, 0xbd, 0x03, 0x10},
+	{0xd1, 0x6e, 0x2d, 0x41, 0x38, 0x68, 0x40, 0x10},
+	{0xd1, 0x6e, 0x31, 0x2b, 0x00, 0x36, 0x00, 0x10},
+	{0xd1, 0x6e, 0x35, 0x30, 0x30, 0x08, 0x00, 0x10},
+	{0xd1, 0x6e, 0x39, 0x00, 0x00, 0x33, 0x06, 0x10},
+	{0xb1, 0x6e, 0x3d, 0x06, 0x02, 0x00, 0x00, 0x10},
+	{}
+};
+static const u8 po2030n_sensor_param1[][8] = {
+	{0xa1, 0x6e, 0x1a, 0x01, 0x00, 0x00, 0x00, 0x10},
+	{DELAY, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* delay 8ms */
+	{0xa1, 0x6e, 0x1b, 0xf4, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x6e, 0x15, 0x04, 0x00, 0x00, 0x00, 0x10},
+	{0xd1, 0x6e, 0x16, 0x40, 0x40, 0x40, 0x40, 0x10}, /* RGBG gains */
+/*param2*/
+	{0xa1, 0x6e, 0x1d, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x6e, 0x04, 0x03, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x6e, 0x05, 0x6f, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x6e, 0x06, 0x02, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x6e, 0x07, 0x25, 0x00, 0x00, 0x00, 0x10},
+	{}
+};
+
+static const u8 soi768_sensor_init[][8] = {
+	{0xa1, 0x21, 0x12, 0x80, 0x00, 0x00, 0x00, 0x10}, /* reset */
+	{DELAY, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, /* delay 96ms */
+	{0xa1, 0x21, 0x12, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x13, 0x80, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x0f, 0x03, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x19, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{}
+};
+static const u8 soi768_sensor_param1[][8] = {
+	{0xa1, 0x21, 0x10, 0x10, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xb1, 0x21, 0x01, 0x7f, 0x7f, 0x00, 0x00, 0x10},
+/* */
+/*	{0xa1, 0x21, 0x2e, 0x00, 0x00, 0x00, 0x00, 0x10}, */
+/*	{0xa1, 0x21, 0x2d, 0x25, 0x00, 0x00, 0x00, 0x10}, */
+	{0xa1, 0x21, 0x2b, 0x00, 0x00, 0x00, 0x00, 0x10},
+/*	{0xb1, 0x21, 0x2d, 0x00, 0x00, 0x00, 0x00, 0x10}, */
+	{0xa1, 0x21, 0x02, 0x8d, 0x00, 0x00, 0x00, 0x10},
+/* the next sequence should be used for auto gain */
+	{0xa1, 0x21, 0x00, 0x07, 0x00, 0x00, 0x00, 0x10},
+			/* global gain ? : 07 - change with 0x15 at the end */
+	{0xa1, 0x21, 0x10, 0x3f, 0x00, 0x00, 0x00, 0x10}, /* ???? : 063f */
+	{0xa1, 0x21, 0x04, 0x06, 0x00, 0x00, 0x00, 0x10},
+	{0xb1, 0x21, 0x2d, 0x63, 0x03, 0x00, 0x00, 0x10},
+			/* exposure ? : 0200 - change with 0x1e at the end */
+	{}
+};
+
+static const u8 sp80708_sensor_init[][8] = {
+	{0xa1, 0x18, 0x06, 0xf9, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x09, 0x1f, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x0d, 0xc0, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x0f, 0x0f, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x10, 0x40, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x11, 0x4e, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x12, 0x53, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x15, 0x80, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x19, 0x18, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x1a, 0x10, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x1b, 0x10, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x1c, 0x28, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x1d, 0x02, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x1e, 0x10, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x26, 0x04, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x27, 0x1e, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x28, 0x5a, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x29, 0x28, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x2a, 0x78, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x2b, 0x01, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x2c, 0xf7, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x2d, 0x2d, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x2e, 0xd5, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x39, 0x42, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x3a, 0x67, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x3b, 0x87, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x3c, 0xa3, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x3d, 0xb0, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x3e, 0xbc, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x3f, 0xc8, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x40, 0xd4, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x41, 0xdf, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x42, 0xea, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x43, 0xf5, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x45, 0x80, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x46, 0x60, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x47, 0x50, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x48, 0x30, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x49, 0x01, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x4d, 0xae, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x4e, 0x03, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x4f, 0x66, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x50, 0x1c, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x44, 0x10, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x4a, 0x30, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x51, 0x80, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x52, 0x80, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x53, 0x80, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x54, 0x80, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x55, 0x80, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x56, 0x80, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x57, 0xe0, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x58, 0xc0, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x59, 0xab, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x5a, 0xa0, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x5b, 0x99, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x5c, 0x90, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x5e, 0x24, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x5f, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x60, 0x00, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x61, 0x73, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x63, 0x42, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x64, 0x42, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x65, 0x42, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x66, 0x24, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x67, 0x24, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x68, 0x08, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x2f, 0xc9, 0x00, 0x00, 0x00, 0x10},
+	{}
+};
+static const u8 sp80708_sensor_param1[][8] = {
+	{0xa1, 0x18, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x0c, 0x04, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x03, 0x01, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x04, 0xa4, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x14, 0x3f, 0x00, 0x00, 0x00, 0x10},
+	{0xa1, 0x18, 0x5d, 0x80, 0x00, 0x00, 0x00, 0x10},
+	{0xb1, 0x18, 0x11, 0x40, 0x40, 0x00, 0x00, 0x10},
+	{}
+};
+
+static const u8 (*sensor_init[])[8] = {
+[SENSOR_ADCM1700] =	adcm1700_sensor_init,
+[SENSOR_GC0307] =	gc0307_sensor_init,
+[SENSOR_HV7131R] =	hv7131r_sensor_init,
+[SENSOR_MI0360] =	mi0360_sensor_init,
+[SENSOR_MI0360B] =	mi0360b_sensor_init,
+[SENSOR_MO4000] =	mo4000_sensor_init,
+[SENSOR_MT9V111] =	mt9v111_sensor_init,
+[SENSOR_OM6802] =	om6802_sensor_init,
+[SENSOR_OV7630] =	ov7630_sensor_init,
+[SENSOR_OV7648] =	ov7648_sensor_init,
+[SENSOR_OV7660] =	ov7660_sensor_init,
+[SENSOR_PO1030] =	po1030_sensor_init,
+[SENSOR_PO2030N] =	po2030n_sensor_init,
+[SENSOR_SOI768] =	soi768_sensor_init,
+[SENSOR_SP80708] =	sp80708_sensor_init,
+};
+
+/* read <len> bytes to gspca_dev->usb_buf */
+static void reg_r(struct gspca_dev *gspca_dev,
+		  u16 value, int len)
+{
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	if (len > USB_BUF_SZ) {
+		gspca_err(gspca_dev, "reg_r: buffer overflow\n");
+		return;
+	}
+
+	ret = usb_control_msg(gspca_dev->dev,
+			usb_rcvctrlpipe(gspca_dev->dev, 0),
+			0,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+			value, 0,
+			gspca_dev->usb_buf, len,
+			500);
+	gspca_dbg(gspca_dev, D_USBI, "reg_r [%02x] -> %02x\n",
+		  value, gspca_dev->usb_buf[0]);
+	if (ret < 0) {
+		pr_err("reg_r err %d\n", ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+static void reg_w1(struct gspca_dev *gspca_dev,
+		   u16 value,
+		   u8 data)
+{
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	gspca_dbg(gspca_dev, D_USBO, "reg_w1 [%04x] = %02x\n", value, data);
+	gspca_dev->usb_buf[0] = data;
+	ret = usb_control_msg(gspca_dev->dev,
+			usb_sndctrlpipe(gspca_dev->dev, 0),
+			0x08,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+			value,
+			0,
+			gspca_dev->usb_buf, 1,
+			500);
+	if (ret < 0) {
+		pr_err("reg_w1 err %d\n", ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+static void reg_w(struct gspca_dev *gspca_dev,
+			  u16 value,
+			  const u8 *buffer,
+			  int len)
+{
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	gspca_dbg(gspca_dev, D_USBO, "reg_w [%04x] = %02x %02x ..\n",
+		  value, buffer[0], buffer[1]);
+
+	if (len > USB_BUF_SZ) {
+		gspca_err(gspca_dev, "reg_w: buffer overflow\n");
+		return;
+	}
+
+	memcpy(gspca_dev->usb_buf, buffer, len);
+	ret = usb_control_msg(gspca_dev->dev,
+			usb_sndctrlpipe(gspca_dev->dev, 0),
+			0x08,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+			value, 0,
+			gspca_dev->usb_buf, len,
+			500);
+	if (ret < 0) {
+		pr_err("reg_w err %d\n", ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+/* I2C write 1 byte */
+static void i2c_w1(struct gspca_dev *gspca_dev, u8 reg, u8 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	gspca_dbg(gspca_dev, D_USBO, "i2c_w1 [%02x] = %02x\n", reg, val);
+	switch (sd->sensor) {
+	case SENSOR_ADCM1700:
+	case SENSOR_OM6802:
+	case SENSOR_GC0307:		/* i2c command = a0 (100 kHz) */
+		gspca_dev->usb_buf[0] = 0x80 | (2 << 4);
+		break;
+	default:			/* i2c command = a1 (400 kHz) */
+		gspca_dev->usb_buf[0] = 0x81 | (2 << 4);
+		break;
+	}
+	gspca_dev->usb_buf[1] = sd->i2c_addr;
+	gspca_dev->usb_buf[2] = reg;
+	gspca_dev->usb_buf[3] = val;
+	gspca_dev->usb_buf[4] = 0;
+	gspca_dev->usb_buf[5] = 0;
+	gspca_dev->usb_buf[6] = 0;
+	gspca_dev->usb_buf[7] = 0x10;
+	ret = usb_control_msg(gspca_dev->dev,
+			usb_sndctrlpipe(gspca_dev->dev, 0),
+			0x08,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+			0x08,			/* value = i2c */
+			0,
+			gspca_dev->usb_buf, 8,
+			500);
+	msleep(2);
+	if (ret < 0) {
+		pr_err("i2c_w1 err %d\n", ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+/* I2C write 8 bytes */
+static void i2c_w8(struct gspca_dev *gspca_dev,
+		   const u8 *buffer)
+{
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	gspca_dbg(gspca_dev, D_USBO, "i2c_w8 [%02x] = %02x ..\n",
+		  buffer[2], buffer[3]);
+	memcpy(gspca_dev->usb_buf, buffer, 8);
+	ret = usb_control_msg(gspca_dev->dev,
+			usb_sndctrlpipe(gspca_dev->dev, 0),
+			0x08,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+			0x08, 0,		/* value, index */
+			gspca_dev->usb_buf, 8,
+			500);
+	msleep(2);
+	if (ret < 0) {
+		pr_err("i2c_w8 err %d\n", ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+/* sensor read 'len' (1..5) bytes in gspca_dev->usb_buf */
+static void i2c_r(struct gspca_dev *gspca_dev, u8 reg, int len)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 mode[8];
+
+	switch (sd->sensor) {
+	case SENSOR_ADCM1700:
+	case SENSOR_OM6802:
+	case SENSOR_GC0307:		/* i2c command = a0 (100 kHz) */
+		mode[0] = 0x80 | 0x10;
+		break;
+	default:			/* i2c command = 91 (400 kHz) */
+		mode[0] = 0x81 | 0x10;
+		break;
+	}
+	mode[1] = sd->i2c_addr;
+	mode[2] = reg;
+	mode[3] = 0;
+	mode[4] = 0;
+	mode[5] = 0;
+	mode[6] = 0;
+	mode[7] = 0x10;
+	i2c_w8(gspca_dev, mode);
+	msleep(2);
+	mode[0] = (mode[0] & 0x81) | (len << 4) | 0x02;
+	mode[2] = 0;
+	i2c_w8(gspca_dev, mode);
+	msleep(2);
+	reg_r(gspca_dev, 0x0a, 5);
+}
+
+static void i2c_w_seq(struct gspca_dev *gspca_dev,
+			const u8 (*data)[8])
+{
+	while ((*data)[0] != 0) {
+		if ((*data)[0] != DELAY)
+			i2c_w8(gspca_dev, *data);
+		else
+			msleep((*data)[1]);
+		data++;
+	}
+}
+
+/* check the ID of the hv7131 sensor */
+/* this sequence is needed because it activates the sensor */
+static void hv7131r_probe(struct gspca_dev *gspca_dev)
+{
+	i2c_w1(gspca_dev, 0x02, 0);		/* sensor wakeup */
+	msleep(10);
+	reg_w1(gspca_dev, 0x02, 0x66);		/* Gpio on */
+	msleep(10);
+	i2c_r(gspca_dev, 0, 5);			/* read sensor id */
+	if (gspca_dev->usb_buf[0] == 0x02	/* chip ID (02 is R) */
+	    && gspca_dev->usb_buf[1] == 0x09
+	    && gspca_dev->usb_buf[2] == 0x01) {
+		gspca_dbg(gspca_dev, D_PROBE, "Sensor HV7131R found\n");
+		return;
+	}
+	pr_warn("Erroneous HV7131R ID 0x%02x 0x%02x 0x%02x\n",
+		gspca_dev->usb_buf[0], gspca_dev->usb_buf[1],
+		gspca_dev->usb_buf[2]);
+}
+
+static void mi0360_probe(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int i, j;
+	u16 val = 0;
+	static const u8 probe_tb[][4][8] = {
+	    {					/* mi0360 */
+		{0xb0, 0x5d, 0x07, 0x00, 0x02, 0x00, 0x00, 0x10},
+		{0x90, 0x5d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10},
+		{0xa2, 0x5d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10},
+		{0xb0, 0x5d, 0x07, 0x00, 0x00, 0x00, 0x00, 0x10}
+	    },
+	    {					/* mt9v111 */
+		{0xb0, 0x5c, 0x01, 0x00, 0x04, 0x00, 0x00, 0x10},
+		{0x90, 0x5c, 0x36, 0x00, 0x00, 0x00, 0x00, 0x10},
+		{0xa2, 0x5c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10},
+		{}
+	    },
+	};
+
+	for (i = 0; i < ARRAY_SIZE(probe_tb); i++) {
+		reg_w1(gspca_dev, 0x17, 0x62);
+		reg_w1(gspca_dev, 0x01, 0x08);
+		for (j = 0; j < 3; j++)
+			i2c_w8(gspca_dev, probe_tb[i][j]);
+		msleep(2);
+		reg_r(gspca_dev, 0x0a, 5);
+		val = (gspca_dev->usb_buf[3] << 8) | gspca_dev->usb_buf[4];
+		if (probe_tb[i][3][0] != 0)
+			i2c_w8(gspca_dev, probe_tb[i][3]);
+		reg_w1(gspca_dev, 0x01, 0x29);
+		reg_w1(gspca_dev, 0x17, 0x42);
+		if (val != 0xffff)
+			break;
+	}
+	if (gspca_dev->usb_err < 0)
+		return;
+	switch (val) {
+	case 0x8221:
+		gspca_dbg(gspca_dev, D_PROBE, "Sensor mi0360b\n");
+		sd->sensor = SENSOR_MI0360B;
+		break;
+	case 0x823a:
+		gspca_dbg(gspca_dev, D_PROBE, "Sensor mt9v111\n");
+		sd->sensor = SENSOR_MT9V111;
+		break;
+	case 0x8243:
+		gspca_dbg(gspca_dev, D_PROBE, "Sensor mi0360\n");
+		break;
+	default:
+		gspca_dbg(gspca_dev, D_PROBE, "Unknown sensor %04x - forced to mi0360\n",
+			  val);
+		break;
+	}
+}
+
+static void ov7630_probe(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u16 val;
+
+	/* check ov76xx */
+	reg_w1(gspca_dev, 0x17, 0x62);
+	reg_w1(gspca_dev, 0x01, 0x08);
+	sd->i2c_addr = 0x21;
+	i2c_r(gspca_dev, 0x0a, 2);
+	val = (gspca_dev->usb_buf[3] << 8) | gspca_dev->usb_buf[4];
+	reg_w1(gspca_dev, 0x01, 0x29);
+	reg_w1(gspca_dev, 0x17, 0x42);
+	if (gspca_dev->usb_err < 0)
+		return;
+	if (val == 0x7628) {			/* soi768 */
+		sd->sensor = SENSOR_SOI768;
+/*fixme: only valid for 0c45:613e?*/
+		gspca_dev->cam.input_flags =
+				V4L2_IN_ST_VFLIP | V4L2_IN_ST_HFLIP;
+		gspca_dbg(gspca_dev, D_PROBE, "Sensor soi768\n");
+		return;
+	}
+	gspca_dbg(gspca_dev, D_PROBE, "Sensor ov%04x\n", val);
+}
+
+static void ov7648_probe(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u16 val;
+
+	/* check ov76xx */
+	reg_w1(gspca_dev, 0x17, 0x62);
+	reg_w1(gspca_dev, 0x01, 0x08);
+	sd->i2c_addr = 0x21;
+	i2c_r(gspca_dev, 0x0a, 2);
+	val = (gspca_dev->usb_buf[3] << 8) | gspca_dev->usb_buf[4];
+	reg_w1(gspca_dev, 0x01, 0x29);
+	reg_w1(gspca_dev, 0x17, 0x42);
+	if ((val & 0xff00) == 0x7600) {		/* ov76xx */
+		gspca_dbg(gspca_dev, D_PROBE, "Sensor ov%04x\n", val);
+		return;
+	}
+
+	/* check po1030 */
+	reg_w1(gspca_dev, 0x17, 0x62);
+	reg_w1(gspca_dev, 0x01, 0x08);
+	sd->i2c_addr = 0x6e;
+	i2c_r(gspca_dev, 0x00, 2);
+	val = (gspca_dev->usb_buf[3] << 8) | gspca_dev->usb_buf[4];
+	reg_w1(gspca_dev, 0x01, 0x29);
+	reg_w1(gspca_dev, 0x17, 0x42);
+	if (gspca_dev->usb_err < 0)
+		return;
+	if (val == 0x1030) {			/* po1030 */
+		gspca_dbg(gspca_dev, D_PROBE, "Sensor po1030\n");
+		sd->sensor = SENSOR_PO1030;
+		return;
+	}
+	pr_err("Unknown sensor %04x\n", val);
+}
+
+/* 0c45:6142 sensor may be po2030n, gc0305 or gc0307 */
+static void po2030n_probe(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u16 val;
+
+	/* check gc0307 */
+	reg_w1(gspca_dev, 0x17, 0x62);
+	reg_w1(gspca_dev, 0x01, 0x08);
+	reg_w1(gspca_dev, 0x02, 0x22);
+	sd->i2c_addr = 0x21;
+	i2c_r(gspca_dev, 0x00, 1);
+	val = gspca_dev->usb_buf[4];
+	reg_w1(gspca_dev, 0x01, 0x29);		/* reset */
+	reg_w1(gspca_dev, 0x17, 0x42);
+	if (val == 0x99) {			/* gc0307 (?) */
+		gspca_dbg(gspca_dev, D_PROBE, "Sensor gc0307\n");
+		sd->sensor = SENSOR_GC0307;
+		return;
+	}
+
+	/* check po2030n */
+	reg_w1(gspca_dev, 0x17, 0x62);
+	reg_w1(gspca_dev, 0x01, 0x0a);
+	sd->i2c_addr = 0x6e;
+	i2c_r(gspca_dev, 0x00, 2);
+	val = (gspca_dev->usb_buf[3] << 8) | gspca_dev->usb_buf[4];
+	reg_w1(gspca_dev, 0x01, 0x29);
+	reg_w1(gspca_dev, 0x17, 0x42);
+	if (gspca_dev->usb_err < 0)
+		return;
+	if (val == 0x2030) {
+		gspca_dbg(gspca_dev, D_PROBE, "Sensor po2030n\n");
+/*		sd->sensor = SENSOR_PO2030N; */
+	} else {
+		pr_err("Unknown sensor ID %04x\n", val);
+	}
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam;
+
+	sd->bridge = id->driver_info >> 16;
+	sd->sensor = id->driver_info >> 8;
+	sd->flags = id->driver_info;
+
+	cam = &gspca_dev->cam;
+	if (sd->sensor == SENSOR_ADCM1700) {
+		cam->cam_mode = cif_mode;
+		cam->nmodes = ARRAY_SIZE(cif_mode);
+	} else {
+		cam->cam_mode = vga_mode;
+		cam->nmodes = ARRAY_SIZE(vga_mode);
+	}
+	cam->npkt = 24;			/* 24 packets per ISOC message */
+
+	sd->ag_cnt = -1;
+	sd->quality = QUALITY_DEF;
+
+	INIT_WORK(&sd->work, qual_upd);
+
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	const u8 *sn9c1xx;
+	u8 regGpio[] = { 0x29, 0x70 };		/* no audio */
+	u8 regF1;
+
+	/* setup a selector by bridge */
+	reg_w1(gspca_dev, 0xf1, 0x01);
+	reg_r(gspca_dev, 0x00, 1);
+	reg_w1(gspca_dev, 0xf1, 0x00);
+	reg_r(gspca_dev, 0x00, 1);		/* get sonix chip id */
+	regF1 = gspca_dev->usb_buf[0];
+	if (gspca_dev->usb_err < 0)
+		return gspca_dev->usb_err;
+	gspca_dbg(gspca_dev, D_PROBE, "Sonix chip id: %02x\n", regF1);
+	if (gspca_dev->audio)
+		regGpio[1] |= 0x04;		/* with audio */
+	switch (sd->bridge) {
+	case BRIDGE_SN9C102P:
+	case BRIDGE_SN9C105:
+		if (regF1 != 0x11)
+			return -ENODEV;
+		break;
+	default:
+/*	case BRIDGE_SN9C110: */
+/*	case BRIDGE_SN9C120: */
+		if (regF1 != 0x12)
+			return -ENODEV;
+	}
+
+	switch (sd->sensor) {
+	case SENSOR_MI0360:
+		mi0360_probe(gspca_dev);
+		break;
+	case SENSOR_OV7630:
+		ov7630_probe(gspca_dev);
+		break;
+	case SENSOR_OV7648:
+		ov7648_probe(gspca_dev);
+		break;
+	case SENSOR_PO2030N:
+		po2030n_probe(gspca_dev);
+		break;
+	}
+
+	switch (sd->bridge) {
+	case BRIDGE_SN9C102P:
+		reg_w1(gspca_dev, 0x02, regGpio[1]);
+		break;
+	default:
+		reg_w(gspca_dev, 0x01, regGpio, 2);
+		break;
+	}
+
+	/* Note we do not disable the sensor clock here (power saving mode),
+	   as that also disables the button on the cam. */
+	reg_w1(gspca_dev, 0xf1, 0x00);
+
+	/* set the i2c address */
+	sn9c1xx = sn_tb[sd->sensor];
+	sd->i2c_addr = sn9c1xx[9];
+
+	return gspca_dev->usb_err;
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl);
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+/* this function is called at probe time */
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 14);
+
+	sd->brightness = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0, 255, 1, 128);
+#define CONTRAST_MAX 127
+	sd->contrast = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_CONTRAST, 0, CONTRAST_MAX, 1, 20);
+#define COLORS_DEF 25
+	sd->saturation = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_SATURATION, 0, 40, 1, COLORS_DEF);
+	sd->red_bal = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_RED_BALANCE, 24, 40, 1, 32);
+	sd->blue_bal = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BLUE_BALANCE, 24, 40, 1, 32);
+#define GAMMA_DEF 20
+	sd->gamma = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_GAMMA, 0, 40, 1, GAMMA_DEF);
+
+	if (sd->sensor == SENSOR_OM6802)
+		sd->sharpness = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_SHARPNESS, 0, 255, 1, 16);
+	else
+		sd->sharpness = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_SHARPNESS, 0, 255, 1, 90);
+
+	if (sd->flags & F_ILLUM)
+		sd->illum = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_ILLUMINATORS_1, 0, 1, 1, 0);
+
+	if (sd->sensor == SENSOR_PO2030N) {
+		gspca_dev->exposure = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_EXPOSURE, 500, 1500, 1, 1024);
+		gspca_dev->gain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_GAIN, 4, 49, 1, 15);
+		sd->hflip = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_HFLIP, 0, 1, 1, 0);
+	}
+
+	if (sd->sensor != SENSOR_ADCM1700 && sd->sensor != SENSOR_OV7660 &&
+	    sd->sensor != SENSOR_PO1030 && sd->sensor != SENSOR_SOI768 &&
+	    sd->sensor != SENSOR_SP80708)
+		gspca_dev->autogain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
+
+	if (sd->sensor == SENSOR_HV7131R || sd->sensor == SENSOR_OV7630 ||
+	    sd->sensor == SENSOR_OV7648 || sd->sensor == SENSOR_PO2030N)
+		sd->vflip = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_VFLIP, 0, 1, 1, 0);
+
+	if (sd->sensor == SENSOR_OV7630 || sd->sensor == SENSOR_OV7648 ||
+	    sd->sensor == SENSOR_OV7660)
+		sd->freq = v4l2_ctrl_new_std_menu(hdl, &sd_ctrl_ops,
+			V4L2_CID_POWER_LINE_FREQUENCY,
+			V4L2_CID_POWER_LINE_FREQUENCY_60HZ, 0,
+			V4L2_CID_POWER_LINE_FREQUENCY_50HZ);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+
+	v4l2_ctrl_cluster(2, &sd->red_bal);
+	if (sd->sensor == SENSOR_PO2030N) {
+		v4l2_ctrl_cluster(2, &sd->vflip);
+		v4l2_ctrl_auto_cluster(3, &gspca_dev->autogain, 0, false);
+	}
+
+	return 0;
+}
+
+static u32 expo_adjust(struct gspca_dev *gspca_dev,
+			u32 expo)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	switch (sd->sensor) {
+	case SENSOR_GC0307: {
+		int a, b;
+
+		/* expo = 0..255 -> a = 19..43 */
+		a = 19 + expo * 25 / 256;
+		i2c_w1(gspca_dev, 0x68, a);
+		a -= 12;
+		b = a * a * 4;			/* heuristic */
+		i2c_w1(gspca_dev, 0x03, b >> 8);
+		i2c_w1(gspca_dev, 0x04, b);
+		break;
+	    }
+	case SENSOR_HV7131R: {
+		u8 Expodoit[] =
+			{ 0xc1, 0x11, 0x25, 0x00, 0x00, 0x00, 0x00, 0x16 };
+
+		Expodoit[3] = expo >> 16;
+		Expodoit[4] = expo >> 8;
+		Expodoit[5] = expo;
+		i2c_w8(gspca_dev, Expodoit);
+		break;
+	    }
+	case SENSOR_MI0360:
+	case SENSOR_MI0360B: {
+		u8 expoMi[] =		/* exposure 0x0635 -> 4 fp/s 0x10 */
+			{ 0xb1, 0x5d, 0x09, 0x00, 0x00, 0x00, 0x00, 0x16 };
+		static const u8 doit[] =		/* update sensor */
+			{ 0xb1, 0x5d, 0x07, 0x00, 0x03, 0x00, 0x00, 0x10 };
+		static const u8 sensorgo[] =		/* sensor on */
+			{ 0xb1, 0x5d, 0x07, 0x00, 0x02, 0x00, 0x00, 0x10 };
+
+		if (expo > 0x0635)
+			expo = 0x0635;
+		else if (expo < 0x0001)
+			expo = 0x0001;
+		expoMi[3] = expo >> 8;
+		expoMi[4] = expo;
+		i2c_w8(gspca_dev, expoMi);
+		i2c_w8(gspca_dev, doit);
+		i2c_w8(gspca_dev, sensorgo);
+		break;
+	    }
+	case SENSOR_MO4000: {
+		u8 expoMof[] =
+			{ 0xa1, 0x21, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x10 };
+		u8 expoMo10[] =
+			{ 0xa1, 0x21, 0x10, 0x00, 0x00, 0x00, 0x00, 0x10 };
+		static const u8 gainMo[] =
+			{ 0xa1, 0x21, 0x00, 0x10, 0x00, 0x00, 0x00, 0x1d };
+
+		if (expo > 0x1fff)
+			expo = 0x1fff;
+		else if (expo < 0x0001)
+			expo = 0x0001;
+		expoMof[3] = (expo & 0x03fc) >> 2;
+		i2c_w8(gspca_dev, expoMof);
+		expoMo10[3] = ((expo & 0x1c00) >> 10)
+				| ((expo & 0x0003) << 4);
+		i2c_w8(gspca_dev, expoMo10);
+		i2c_w8(gspca_dev, gainMo);
+		gspca_dbg(gspca_dev, D_FRAM, "set exposure %d\n",
+			  ((expoMo10[3] & 0x07) << 10)
+			  | (expoMof[3] << 2)
+			  | ((expoMo10[3] & 0x30) >> 4));
+		break;
+	    }
+	case SENSOR_MT9V111: {
+		u8 expo_c1[] =
+			{ 0xb1, 0x5c, 0x09, 0x00, 0x00, 0x00, 0x00, 0x10 };
+
+		if (expo > 0x0390)
+			expo = 0x0390;
+		else if (expo < 0x0060)
+			expo = 0x0060;
+		expo_c1[3] = expo >> 8;
+		expo_c1[4] = expo;
+		i2c_w8(gspca_dev, expo_c1);
+		break;
+	    }
+	case SENSOR_OM6802: {
+		u8 gainOm[] =
+			{ 0xa0, 0x34, 0xe5, 0x00, 0x00, 0x00, 0x00, 0x10 };
+				/* preset AGC - works when AutoExpo = off */
+
+		if (expo > 0x03ff)
+			expo = 0x03ff;
+		if (expo < 0x0001)
+			expo = 0x0001;
+		gainOm[3] = expo >> 2;
+		i2c_w8(gspca_dev, gainOm);
+		reg_w1(gspca_dev, 0x96, expo >> 5);
+		gspca_dbg(gspca_dev, D_FRAM, "set exposure %d\n", gainOm[3]);
+		break;
+	    }
+	}
+	return expo;
+}
+
+static void setbrightness(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	unsigned int expo;
+	int brightness = sd->brightness->val;
+	u8 k2;
+
+	k2 = (brightness - 0x80) >> 2;
+	switch (sd->sensor) {
+	case SENSOR_ADCM1700:
+		if (k2 > 0x1f)
+			k2 = 0;		/* only positive Y offset */
+		break;
+	case SENSOR_HV7131R:
+		expo = brightness << 12;
+		if (expo > 0x002dc6c0)
+			expo = 0x002dc6c0;
+		else if (expo < 0x02a0)
+			expo = 0x02a0;
+		sd->exposure = expo_adjust(gspca_dev, expo);
+		break;
+	case SENSOR_MI0360:
+	case SENSOR_MO4000:
+		expo = brightness << 4;
+		sd->exposure = expo_adjust(gspca_dev, expo);
+		break;
+	case SENSOR_MI0360B:
+		expo = brightness << 2;
+		sd->exposure = expo_adjust(gspca_dev, expo);
+		break;
+	case SENSOR_GC0307:
+		expo = brightness;
+		sd->exposure = expo_adjust(gspca_dev, expo);
+		return;			/* don't set the Y offset */
+	case SENSOR_MT9V111:
+		expo = brightness << 2;
+		sd->exposure = expo_adjust(gspca_dev, expo);
+		return;			/* don't set the Y offset */
+	case SENSOR_OM6802:
+		expo = brightness << 2;
+		sd->exposure = expo_adjust(gspca_dev, expo);
+		return;			/* Y offset already set */
+	}
+
+	reg_w1(gspca_dev, 0x96, k2);	/* color matrix Y offset */
+}
+
+static void setcontrast(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 k2;
+	u8 contrast[6];
+
+	k2 = sd->contrast->val * 37 / (CONTRAST_MAX + 1)
+				+ 37;		/* 37..73 */
+	contrast[0] = (k2 + 1) / 2;		/* red */
+	contrast[1] = 0;
+	contrast[2] = k2;			/* green */
+	contrast[3] = 0;
+	contrast[4] = k2 / 5;			/* blue */
+	contrast[5] = 0;
+	reg_w(gspca_dev, 0x84, contrast, sizeof contrast);
+}
+
+static void setcolors(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int i, v, colors;
+	const s16 *uv;
+	u8 reg8a[12];			/* U & V gains */
+	static const s16 uv_com[6] = {	/* same as reg84 in signed decimal */
+		-24, -38, 64,		/* UR UG UB */
+		 62, -51, -9		/* VR VG VB */
+	};
+	static const s16 uv_mi0360b[6] = {
+		-20, -38, 64,		/* UR UG UB */
+		 60, -51, -9		/* VR VG VB */
+	};
+
+	colors = sd->saturation->val;
+	if (sd->sensor == SENSOR_MI0360B)
+		uv = uv_mi0360b;
+	else
+		uv = uv_com;
+	for (i = 0; i < 6; i++) {
+		v = uv[i] * colors / COLORS_DEF;
+		reg8a[i * 2] = v;
+		reg8a[i * 2 + 1] = (v >> 8) & 0x0f;
+	}
+	reg_w(gspca_dev, 0x8a, reg8a, sizeof reg8a);
+}
+
+static void setredblue(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->sensor == SENSOR_PO2030N) {
+		u8 rg1b[] =		/* red  green1 blue (no g2) */
+			{0xc1, 0x6e, 0x16, 0x00, 0x40, 0x00, 0x00, 0x10};
+
+		/* 0x40 = normal value = gain x 1 */
+		rg1b[3] = sd->red_bal->val * 2;
+		rg1b[5] = sd->blue_bal->val * 2;
+		i2c_w8(gspca_dev, rg1b);
+		return;
+	}
+	reg_w1(gspca_dev, 0x05, sd->red_bal->val);
+/*	reg_w1(gspca_dev, 0x07, 32); */
+	reg_w1(gspca_dev, 0x06, sd->blue_bal->val);
+}
+
+static void setgamma(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int i, val;
+	u8 gamma[17];
+	const u8 *gamma_base;
+	static const u8 delta[17] = {
+		0x00, 0x14, 0x1c, 0x1c, 0x1c, 0x1c, 0x1b, 0x1a,
+		0x18, 0x13, 0x10, 0x0e, 0x08, 0x07, 0x04, 0x02, 0x00
+	};
+
+	switch (sd->sensor) {
+	case SENSOR_ADCM1700:
+		gamma_base = gamma_spec_0;
+		break;
+	case SENSOR_HV7131R:
+	case SENSOR_MI0360B:
+	case SENSOR_MT9V111:
+		gamma_base = gamma_spec_1;
+		break;
+	case SENSOR_GC0307:
+		gamma_base = gamma_spec_2;
+		break;
+	case SENSOR_SP80708:
+		gamma_base = gamma_spec_3;
+		break;
+	default:
+		gamma_base = gamma_def;
+		break;
+	}
+
+	val = sd->gamma->val;
+	for (i = 0; i < sizeof gamma; i++)
+		gamma[i] = gamma_base[i]
+			+ delta[i] * (val - GAMMA_DEF) / 32;
+	reg_w(gspca_dev, 0x20, gamma, sizeof gamma);
+}
+
+static void setexposure(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->sensor == SENSOR_PO2030N) {
+		u8 rexpo[] =		/* 1a: expo H, 1b: expo M */
+			{0xa1, 0x6e, 0x1a, 0x00, 0x40, 0x00, 0x00, 0x10};
+
+		rexpo[3] = gspca_dev->exposure->val >> 8;
+		i2c_w8(gspca_dev, rexpo);
+		msleep(6);
+		rexpo[2] = 0x1b;
+		rexpo[3] = gspca_dev->exposure->val;
+		i2c_w8(gspca_dev, rexpo);
+	}
+}
+
+static void setautogain(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	switch (sd->sensor) {
+	case SENSOR_OV7630:
+	case SENSOR_OV7648: {
+		u8 comb;
+
+		if (sd->sensor == SENSOR_OV7630)
+			comb = 0xc0;
+		else
+			comb = 0xa0;
+		if (gspca_dev->autogain->val)
+			comb |= 0x03;
+		i2c_w1(&sd->gspca_dev, 0x13, comb);
+		return;
+	    }
+	}
+	if (gspca_dev->autogain->val)
+		sd->ag_cnt = AG_CNT_START;
+	else
+		sd->ag_cnt = -1;
+}
+
+static void setgain(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->sensor == SENSOR_PO2030N) {
+		u8 rgain[] =		/* 15: gain */
+			{0xa1, 0x6e, 0x15, 0x00, 0x40, 0x00, 0x00, 0x15};
+
+		rgain[3] = gspca_dev->gain->val;
+		i2c_w8(gspca_dev, rgain);
+	}
+}
+
+static void sethvflip(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 comn;
+
+	switch (sd->sensor) {
+	case SENSOR_HV7131R:
+		comn = 0x18;			/* clkdiv = 1, ablcen = 1 */
+		if (sd->vflip->val)
+			comn |= 0x01;
+		i2c_w1(gspca_dev, 0x01, comn);	/* sctra */
+		break;
+	case SENSOR_OV7630:
+		comn = 0x02;
+		if (!sd->vflip->val)
+			comn |= 0x80;
+		i2c_w1(gspca_dev, 0x75, comn);
+		break;
+	case SENSOR_OV7648:
+		comn = 0x06;
+		if (sd->vflip->val)
+			comn |= 0x80;
+		i2c_w1(gspca_dev, 0x75, comn);
+		break;
+	case SENSOR_PO2030N:
+		/* Reg. 0x1E: Timing Generator Control Register 2 (Tgcontrol2)
+		 * (reset value: 0x0A)
+		 * bit7: HM: Horizontal Mirror: 0: disable, 1: enable
+		 * bit6: VM: Vertical Mirror: 0: disable, 1: enable
+		 * bit5: ST: Shutter Selection: 0: electrical, 1: mechanical
+		 * bit4: FT: Single Frame Transfer: 0: disable, 1: enable
+		 * bit3-0: X
+		 */
+		comn = 0x0a;
+		if (sd->hflip->val)
+			comn |= 0x80;
+		if (sd->vflip->val)
+			comn |= 0x40;
+		i2c_w1(&sd->gspca_dev, 0x1e, comn);
+		break;
+	}
+}
+
+static void setsharpness(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	reg_w1(gspca_dev, 0x99, sd->sharpness->val);
+}
+
+static void setillum(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	switch (sd->sensor) {
+	case SENSOR_ADCM1700:
+		reg_w1(gspca_dev, 0x02,				/* gpio */
+			sd->illum->val ? 0x64 : 0x60);
+		break;
+	case SENSOR_MT9V111:
+		reg_w1(gspca_dev, 0x02,
+			sd->illum->val ? 0x77 : 0x74);
+/* should have been: */
+/*						0x55 : 0x54);	* 370i */
+/*						0x66 : 0x64);	* Clip */
+		break;
+	}
+}
+
+static void setfreq(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->sensor == SENSOR_OV7660) {
+		u8 com8;
+
+		com8 = 0xdf;		/* auto gain/wb/expo */
+		switch (sd->freq->val) {
+		case 0: /* Banding filter disabled */
+			i2c_w1(gspca_dev, 0x13, com8 | 0x20);
+			break;
+		case 1: /* 50 hz */
+			i2c_w1(gspca_dev, 0x13, com8);
+			i2c_w1(gspca_dev, 0x3b, 0x0a);
+			break;
+		case 2: /* 60 hz */
+			i2c_w1(gspca_dev, 0x13, com8);
+			i2c_w1(gspca_dev, 0x3b, 0x02);
+			break;
+		}
+	} else {
+		u8 reg2a = 0, reg2b = 0, reg2d = 0;
+
+		/* Get reg2a / reg2d base values */
+		switch (sd->sensor) {
+		case SENSOR_OV7630:
+			reg2a = 0x08;
+			reg2d = 0x01;
+			break;
+		case SENSOR_OV7648:
+			reg2a = 0x11;
+			reg2d = 0x81;
+			break;
+		}
+
+		switch (sd->freq->val) {
+		case 0: /* Banding filter disabled */
+			break;
+		case 1: /* 50 hz (filter on and framerate adj) */
+			reg2a |= 0x80;
+			reg2b = 0xac;
+			reg2d |= 0x04;
+			break;
+		case 2: /* 60 hz (filter on, no framerate adj) */
+			reg2a |= 0x80;
+			reg2d |= 0x04;
+			break;
+		}
+		i2c_w1(gspca_dev, 0x2a, reg2a);
+		i2c_w1(gspca_dev, 0x2b, reg2b);
+		i2c_w1(gspca_dev, 0x2d, reg2d);
+	}
+}
+
+static void setjpegqual(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	jpeg_set_qual(sd->jpeg_hdr, sd->quality);
+#if USB_BUF_SZ < 64
+#error "No room enough in usb_buf for quantization table"
+#endif
+	memcpy(gspca_dev->usb_buf, &sd->jpeg_hdr[JPEG_QT0_OFFSET], 64);
+	usb_control_msg(gspca_dev->dev,
+			usb_sndctrlpipe(gspca_dev->dev, 0),
+			0x08,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+			0x0100, 0,
+			gspca_dev->usb_buf, 64,
+			500);
+	memcpy(gspca_dev->usb_buf, &sd->jpeg_hdr[JPEG_QT1_OFFSET], 64);
+	usb_control_msg(gspca_dev->dev,
+			usb_sndctrlpipe(gspca_dev->dev, 0),
+			0x08,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
+			0x0140, 0,
+			gspca_dev->usb_buf, 64,
+			500);
+
+	sd->reg18 ^= 0x40;
+	reg_w1(gspca_dev, 0x18, sd->reg18);
+}
+
+/* JPEG quality update */
+/* This function is executed from a work queue. */
+static void qual_upd(struct work_struct *work)
+{
+	struct sd *sd = container_of(work, struct sd, work);
+	struct gspca_dev *gspca_dev = &sd->gspca_dev;
+
+	/* To protect gspca_dev->usb_buf and gspca_dev->usb_err */
+	mutex_lock(&gspca_dev->usb_lock);
+	gspca_dbg(gspca_dev, D_STREAM, "qual_upd %d%%\n", sd->quality);
+	gspca_dev->usb_err = 0;
+	setjpegqual(gspca_dev);
+	mutex_unlock(&gspca_dev->usb_lock);
+}
+
+/* -- start the camera -- */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int i;
+	u8 reg01, reg17;
+	u8 reg0102[2];
+	const u8 *sn9c1xx;
+	const u8 (*init)[8];
+	const u8 *reg9a;
+	int mode;
+	static const u8 reg9a_def[] =
+		{0x00, 0x40, 0x20, 0x00, 0x00, 0x00};
+	static const u8 reg9a_spec[] =
+		{0x00, 0x40, 0x38, 0x30, 0x00, 0x20};
+	static const u8 regd4[] = {0x60, 0x00, 0x00};
+	static const u8 C0[] = { 0x2d, 0x2d, 0x3a, 0x05, 0x04, 0x3f };
+	static const u8 CA[] = { 0x28, 0xd8, 0x14, 0xec };
+	static const u8 CA_adcm1700[] =
+				{ 0x14, 0xec, 0x0a, 0xf6 };
+	static const u8 CA_po2030n[] =
+				{ 0x1e, 0xe2, 0x14, 0xec };
+	static const u8 CE[] = { 0x32, 0xdd, 0x2d, 0xdd };	/* MI0360 */
+	static const u8 CE_gc0307[] =
+				{ 0x32, 0xce, 0x2d, 0xd3 };
+	static const u8 CE_ov76xx[] =
+				{ 0x32, 0xdd, 0x32, 0xdd };
+	static const u8 CE_po2030n[] =
+				{ 0x14, 0xe7, 0x1e, 0xdd };
+
+	/* create the JPEG header */
+	jpeg_define(sd->jpeg_hdr, gspca_dev->pixfmt.height,
+			gspca_dev->pixfmt.width,
+			0x21);		/* JPEG 422 */
+
+	/* initialize the bridge */
+	sn9c1xx = sn_tb[sd->sensor];
+
+	/* sensor clock already enabled in sd_init */
+	/* reg_w1(gspca_dev, 0xf1, 0x00); */
+	reg01 = sn9c1xx[1];
+	if (sd->flags & F_PDN_INV)
+		reg01 ^= S_PDN_INV;		/* power down inverted */
+	reg_w1(gspca_dev, 0x01, reg01);
+
+	/* configure gpio */
+	reg0102[0] = reg01;
+	reg0102[1] = sn9c1xx[2];
+	if (gspca_dev->audio)
+		reg0102[1] |= 0x04;	/* keep the audio connection */
+	reg_w(gspca_dev, 0x01, reg0102, 2);
+	reg_w(gspca_dev, 0x08, &sn9c1xx[8], 2);
+	reg_w(gspca_dev, 0x17, &sn9c1xx[0x17], 5);
+	switch (sd->sensor) {
+	case SENSOR_GC0307:
+	case SENSOR_OV7660:
+	case SENSOR_PO1030:
+	case SENSOR_PO2030N:
+	case SENSOR_SOI768:
+	case SENSOR_SP80708:
+		reg9a = reg9a_spec;
+		break;
+	default:
+		reg9a = reg9a_def;
+		break;
+	}
+	reg_w(gspca_dev, 0x9a, reg9a, 6);
+
+	reg_w(gspca_dev, 0xd4, regd4, sizeof regd4);
+
+	reg_w(gspca_dev, 0x03, &sn9c1xx[3], 0x0f);
+
+	reg17 = sn9c1xx[0x17];
+	switch (sd->sensor) {
+	case SENSOR_GC0307:
+		msleep(50);		/*fixme: is it useful? */
+		break;
+	case SENSOR_OM6802:
+		msleep(10);
+		reg_w1(gspca_dev, 0x02, 0x73);
+		reg17 |= SEN_CLK_EN;
+		reg_w1(gspca_dev, 0x17, reg17);
+		reg_w1(gspca_dev, 0x01, 0x22);
+		msleep(100);
+		reg01 = SCL_SEL_OD | S_PDN_INV;
+		reg17 &= ~MCK_SIZE_MASK;
+		reg17 |= 0x04;		/* clock / 4 */
+		break;
+	}
+	reg01 |= SYS_SEL_48M;
+	reg_w1(gspca_dev, 0x01, reg01);
+	reg17 |= SEN_CLK_EN;
+	reg_w1(gspca_dev, 0x17, reg17);
+	reg01 &= ~S_PWR_DN;		/* sensor power on */
+	reg_w1(gspca_dev, 0x01, reg01);
+	reg01 &= ~SCL_SEL_OD;		/* remove open-drain mode */
+	reg_w1(gspca_dev, 0x01, reg01);
+
+	switch (sd->sensor) {
+	case SENSOR_HV7131R:
+		hv7131r_probe(gspca_dev);	/*fixme: is it useful? */
+		break;
+	case SENSOR_OM6802:
+		msleep(10);
+		reg_w1(gspca_dev, 0x01, reg01);
+		i2c_w8(gspca_dev, om6802_init0[0]);
+		i2c_w8(gspca_dev, om6802_init0[1]);
+		msleep(15);
+		reg_w1(gspca_dev, 0x02, 0x71);
+		msleep(150);
+		break;
+	case SENSOR_SP80708:
+		msleep(100);
+		reg_w1(gspca_dev, 0x02, 0x62);
+		break;
+	}
+
+	/* initialize the sensor */
+	i2c_w_seq(gspca_dev, sensor_init[sd->sensor]);
+
+	reg_w1(gspca_dev, 0x15, sn9c1xx[0x15]);
+	reg_w1(gspca_dev, 0x16, sn9c1xx[0x16]);
+	reg_w1(gspca_dev, 0x12, sn9c1xx[0x12]);
+	reg_w1(gspca_dev, 0x13, sn9c1xx[0x13]);
+	reg_w1(gspca_dev, 0x18, sn9c1xx[0x18]);
+	if (sd->sensor == SENSOR_ADCM1700) {
+		reg_w1(gspca_dev, 0xd2, 0x3a);	/* AE_H_SIZE = 116 */
+		reg_w1(gspca_dev, 0xd3, 0x30);	/* AE_V_SIZE = 96 */
+	} else {
+		reg_w1(gspca_dev, 0xd2, 0x6a);	/* AE_H_SIZE = 212 */
+		reg_w1(gspca_dev, 0xd3, 0x50);	/* AE_V_SIZE = 160 */
+	}
+	reg_w1(gspca_dev, 0xc6, 0x00);
+	reg_w1(gspca_dev, 0xc7, 0x00);
+	if (sd->sensor == SENSOR_ADCM1700) {
+		reg_w1(gspca_dev, 0xc8, 0x2c);	/* AW_H_STOP = 352 */
+		reg_w1(gspca_dev, 0xc9, 0x24);	/* AW_V_STOP = 288 */
+	} else {
+		reg_w1(gspca_dev, 0xc8, 0x50);	/* AW_H_STOP = 640 */
+		reg_w1(gspca_dev, 0xc9, 0x3c);	/* AW_V_STOP = 480 */
+	}
+	reg_w1(gspca_dev, 0x18, sn9c1xx[0x18]);
+	switch (sd->sensor) {
+	case SENSOR_OM6802:
+/*	case SENSOR_OV7648:		* fixme: sometimes */
+		break;
+	default:
+		reg17 |= DEF_EN;
+		break;
+	}
+	reg_w1(gspca_dev, 0x17, reg17);
+
+	reg_w1(gspca_dev, 0x05, 0x00);		/* red */
+	reg_w1(gspca_dev, 0x07, 0x00);		/* green */
+	reg_w1(gspca_dev, 0x06, 0x00);		/* blue */
+	reg_w1(gspca_dev, 0x14, sn9c1xx[0x14]);
+
+	setgamma(gspca_dev);
+
+/*fixme: 8 times with all zeroes and 1 or 2 times with normal values */
+	for (i = 0; i < 8; i++)
+		reg_w(gspca_dev, 0x84, reg84, sizeof reg84);
+	switch (sd->sensor) {
+	case SENSOR_ADCM1700:
+	case SENSOR_OV7660:
+	case SENSOR_SP80708:
+		reg_w1(gspca_dev, 0x9a, 0x05);
+		break;
+	case SENSOR_GC0307:
+	case SENSOR_MT9V111:
+	case SENSOR_MI0360B:
+		reg_w1(gspca_dev, 0x9a, 0x07);
+		break;
+	case SENSOR_OV7630:
+	case SENSOR_OV7648:
+		reg_w1(gspca_dev, 0x9a, 0x0a);
+		break;
+	case SENSOR_PO2030N:
+	case SENSOR_SOI768:
+		reg_w1(gspca_dev, 0x9a, 0x06);
+		break;
+	default:
+		reg_w1(gspca_dev, 0x9a, 0x08);
+		break;
+	}
+	setsharpness(gspca_dev);
+
+	reg_w(gspca_dev, 0x84, reg84, sizeof reg84);
+	reg_w1(gspca_dev, 0x05, 0x20);		/* red */
+	reg_w1(gspca_dev, 0x07, 0x20);		/* green */
+	reg_w1(gspca_dev, 0x06, 0x20);		/* blue */
+
+	init = NULL;
+	mode = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].priv;
+	reg01 |= SYS_SEL_48M | V_TX_EN;
+	reg17 &= ~MCK_SIZE_MASK;
+	reg17 |= 0x02;			/* clock / 2 */
+	switch (sd->sensor) {
+	case SENSOR_ADCM1700:
+		init = adcm1700_sensor_param1;
+		break;
+	case SENSOR_GC0307:
+		init = gc0307_sensor_param1;
+		break;
+	case SENSOR_HV7131R:
+	case SENSOR_MI0360:
+		if (!mode)
+			reg01 &= ~SYS_SEL_48M;	/* 640x480: clk 24Mhz */
+		reg17 &= ~MCK_SIZE_MASK;
+		reg17 |= 0x01;			/* clock / 1 */
+		break;
+	case SENSOR_MI0360B:
+		init = mi0360b_sensor_param1;
+		break;
+	case SENSOR_MO4000:
+		if (mode) {			/* if 320x240 */
+			reg01 &= ~SYS_SEL_48M;	/* clk 24Mz */
+			reg17 &= ~MCK_SIZE_MASK;
+			reg17 |= 0x01;		/* clock / 1 */
+		}
+		break;
+	case SENSOR_MT9V111:
+		init = mt9v111_sensor_param1;
+		break;
+	case SENSOR_OM6802:
+		init = om6802_sensor_param1;
+		if (!mode) {			/* if 640x480 */
+			reg17 &= ~MCK_SIZE_MASK;
+			reg17 |= 0x04;		/* clock / 4 */
+		} else {
+			reg01 &= ~SYS_SEL_48M;	/* clk 24Mz */
+			reg17 &= ~MCK_SIZE_MASK;
+			reg17 |= 0x02;		/* clock / 2 */
+		}
+		break;
+	case SENSOR_OV7630:
+		init = ov7630_sensor_param1;
+		break;
+	case SENSOR_OV7648:
+		init = ov7648_sensor_param1;
+		reg17 &= ~MCK_SIZE_MASK;
+		reg17 |= 0x01;			/* clock / 1 */
+		break;
+	case SENSOR_OV7660:
+		init = ov7660_sensor_param1;
+		break;
+	case SENSOR_PO1030:
+		init = po1030_sensor_param1;
+		break;
+	case SENSOR_PO2030N:
+		init = po2030n_sensor_param1;
+		break;
+	case SENSOR_SOI768:
+		init = soi768_sensor_param1;
+		break;
+	case SENSOR_SP80708:
+		init = sp80708_sensor_param1;
+		break;
+	}
+
+	/* more sensor initialization - param1 */
+	if (init != NULL) {
+		i2c_w_seq(gspca_dev, init);
+/*		init = NULL; */
+	}
+
+	reg_w(gspca_dev, 0xc0, C0, 6);
+	switch (sd->sensor) {
+	case SENSOR_ADCM1700:
+	case SENSOR_GC0307:
+	case SENSOR_SOI768:
+		reg_w(gspca_dev, 0xca, CA_adcm1700, 4);
+		break;
+	case SENSOR_PO2030N:
+		reg_w(gspca_dev, 0xca, CA_po2030n, 4);
+		break;
+	default:
+		reg_w(gspca_dev, 0xca, CA, 4);
+		break;
+	}
+	switch (sd->sensor) {
+	case SENSOR_ADCM1700:
+	case SENSOR_OV7630:
+	case SENSOR_OV7648:
+	case SENSOR_OV7660:
+	case SENSOR_SOI768:
+		reg_w(gspca_dev, 0xce, CE_ov76xx, 4);
+		break;
+	case SENSOR_GC0307:
+		reg_w(gspca_dev, 0xce, CE_gc0307, 4);
+		break;
+	case SENSOR_PO2030N:
+		reg_w(gspca_dev, 0xce, CE_po2030n, 4);
+		break;
+	default:
+		reg_w(gspca_dev, 0xce, CE, 4);
+					/* ?? {0x1e, 0xdd, 0x2d, 0xe7} */
+		break;
+	}
+
+	/* here change size mode 0 -> VGA; 1 -> CIF */
+	sd->reg18 = sn9c1xx[0x18] | (mode << 4) | 0x40;
+	reg_w1(gspca_dev, 0x18, sd->reg18);
+	setjpegqual(gspca_dev);
+
+	reg_w1(gspca_dev, 0x17, reg17);
+	reg_w1(gspca_dev, 0x01, reg01);
+	sd->reg01 = reg01;
+	sd->reg17 = reg17;
+
+	sd->pktsz = sd->npkt = 0;
+	sd->nchg = sd->short_mark = 0;
+
+	return gspca_dev->usb_err;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	static const u8 stophv7131[] =
+		{ 0xa1, 0x11, 0x02, 0x09, 0x00, 0x00, 0x00, 0x10 };
+	static const u8 stopmi0360[] =
+		{ 0xb1, 0x5d, 0x07, 0x00, 0x00, 0x00, 0x00, 0x10 };
+	static const u8 stopov7648[] =
+		{ 0xa1, 0x21, 0x76, 0x20, 0x00, 0x00, 0x00, 0x10 };
+	static const u8 stopsoi768[] =
+		{ 0xa1, 0x21, 0x12, 0x80, 0x00, 0x00, 0x00, 0x10 };
+	u8 reg01;
+	u8 reg17;
+
+	reg01 = sd->reg01;
+	reg17 = sd->reg17 & ~SEN_CLK_EN;
+	switch (sd->sensor) {
+	case SENSOR_ADCM1700:
+	case SENSOR_GC0307:
+	case SENSOR_PO2030N:
+	case SENSOR_SP80708:
+		reg01 |= LED;
+		reg_w1(gspca_dev, 0x01, reg01);
+		reg01 &= ~(LED | V_TX_EN);
+		reg_w1(gspca_dev, 0x01, reg01);
+/*		reg_w1(gspca_dev, 0x02, 0x??);	 * LED off ? */
+		break;
+	case SENSOR_HV7131R:
+		reg01 &= ~V_TX_EN;
+		reg_w1(gspca_dev, 0x01, reg01);
+		i2c_w8(gspca_dev, stophv7131);
+		break;
+	case SENSOR_MI0360:
+	case SENSOR_MI0360B:
+		reg01 &= ~V_TX_EN;
+		reg_w1(gspca_dev, 0x01, reg01);
+/*		reg_w1(gspca_dev, 0x02, 0x40);	  * LED off ? */
+		i2c_w8(gspca_dev, stopmi0360);
+		break;
+	case SENSOR_MT9V111:
+	case SENSOR_OM6802:
+	case SENSOR_PO1030:
+		reg01 &= ~V_TX_EN;
+		reg_w1(gspca_dev, 0x01, reg01);
+		break;
+	case SENSOR_OV7630:
+	case SENSOR_OV7648:
+		reg01 &= ~V_TX_EN;
+		reg_w1(gspca_dev, 0x01, reg01);
+		i2c_w8(gspca_dev, stopov7648);
+		break;
+	case SENSOR_OV7660:
+		reg01 &= ~V_TX_EN;
+		reg_w1(gspca_dev, 0x01, reg01);
+		break;
+	case SENSOR_SOI768:
+		i2c_w8(gspca_dev, stopsoi768);
+		break;
+	}
+
+	reg01 |= SCL_SEL_OD;
+	reg_w1(gspca_dev, 0x01, reg01);
+	reg01 |= S_PWR_DN;		/* sensor power down */
+	reg_w1(gspca_dev, 0x01, reg01);
+	reg_w1(gspca_dev, 0x17, reg17);
+	reg01 &= ~SYS_SEL_48M;		/* clock 24MHz */
+	reg_w1(gspca_dev, 0x01, reg01);
+	reg01 |= LED;
+	reg_w1(gspca_dev, 0x01, reg01);
+	/* Don't disable sensor clock as that disables the button on the cam */
+	/* reg_w1(gspca_dev, 0xf1, 0x01); */
+}
+
+/* called on streamoff with alt==0 and on disconnect */
+/* the usb_lock is held at entry - restore on exit */
+static void sd_stop0(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	mutex_unlock(&gspca_dev->usb_lock);
+	flush_work(&sd->work);
+	mutex_lock(&gspca_dev->usb_lock);
+}
+
+static void do_autogain(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int delta;
+	int expotimes;
+	u8 luma_mean = 130;
+	u8 luma_delta = 20;
+
+	/* Thanks S., without your advice, autobright should not work :) */
+	if (sd->ag_cnt < 0)
+		return;
+	if (--sd->ag_cnt >= 0)
+		return;
+	sd->ag_cnt = AG_CNT_START;
+
+	delta = atomic_read(&sd->avg_lum);
+	gspca_dbg(gspca_dev, D_FRAM, "mean lum %d\n", delta);
+
+	if (sd->sensor == SENSOR_PO2030N) {
+		gspca_expo_autogain(gspca_dev, delta, luma_mean, luma_delta,
+					15, 1024);
+		return;
+	}
+
+	if (delta < luma_mean - luma_delta ||
+	    delta > luma_mean + luma_delta) {
+		switch (sd->sensor) {
+		case SENSOR_GC0307:
+			expotimes = sd->exposure;
+			expotimes += (luma_mean - delta) >> 6;
+			if (expotimes < 0)
+				expotimes = 0;
+			sd->exposure = expo_adjust(gspca_dev,
+						   (unsigned int) expotimes);
+			break;
+		case SENSOR_HV7131R:
+			expotimes = sd->exposure >> 8;
+			expotimes += (luma_mean - delta) >> 4;
+			if (expotimes < 0)
+				expotimes = 0;
+			sd->exposure = expo_adjust(gspca_dev,
+					(unsigned int) (expotimes << 8));
+			break;
+		case SENSOR_OM6802:
+		case SENSOR_MT9V111:
+			expotimes = sd->exposure;
+			expotimes += (luma_mean - delta) >> 2;
+			if (expotimes < 0)
+				expotimes = 0;
+			sd->exposure = expo_adjust(gspca_dev,
+						   (unsigned int) expotimes);
+			setredblue(gspca_dev);
+			break;
+		default:
+/*		case SENSOR_MO4000: */
+/*		case SENSOR_MI0360: */
+/*		case SENSOR_MI0360B: */
+			expotimes = sd->exposure;
+			expotimes += (luma_mean - delta) >> 6;
+			if (expotimes < 0)
+				expotimes = 0;
+			sd->exposure = expo_adjust(gspca_dev,
+						   (unsigned int) expotimes);
+			setredblue(gspca_dev);
+			break;
+		}
+	}
+}
+
+/* set the average luminosity from an isoc marker */
+static void set_lum(struct sd *sd,
+		    u8 *data)
+{
+	int avg_lum;
+
+	/*	w0 w1 w2
+	 *	w3 w4 w5
+	 *	w6 w7 w8
+	 */
+	avg_lum = (data[27] << 8) + data[28]		/* w3 */
+
+		+ (data[31] << 8) + data[32]		/* w5 */
+
+		+ (data[23] << 8) + data[24]		/* w1 */
+
+		+ (data[35] << 8) + data[36]		/* w7 */
+
+		+ (data[29] << 10) + (data[30] << 2);	/* w4 * 4 */
+	avg_lum >>= 10;
+	atomic_set(&sd->avg_lum, avg_lum);
+}
+
+/* scan the URB packets */
+/* This function is run at interrupt level. */
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int i, new_qual;
+
+	/*
+	 * A frame ends on the marker
+	 *		ff ff 00 c4 c4 96 ..
+	 * which is 62 bytes long and is followed by various information
+	 * including statuses and luminosity.
+	 *
+	 * A marker may be splitted on two packets.
+	 *
+	 * The 6th byte of a marker contains the bits:
+	 *	0x08: USB full
+	 *	0xc0: frame sequence
+	 * When the bit 'USB full' is set, the frame must be discarded;
+	 * this is also the case when the 2 bytes before the marker are
+	 * not the JPEG end of frame ('ff d9').
+	 */
+
+	/* count the packets and their size */
+	sd->npkt++;
+	sd->pktsz += len;
+
+/*fixme: assumption about the following code:
+ *	- there can be only one marker in a packet
+ */
+
+	/* skip the remaining bytes of a short marker */
+	i = sd->short_mark;
+	if (i != 0) {
+		sd->short_mark = 0;
+		if (i < 0	/* if 'ff' at end of previous packet */
+		 && data[0] == 0xff
+		 && data[1] == 0x00)
+			goto marker_found;
+		if (data[0] == 0xff && data[1] == 0xff) {
+			i = 0;
+			goto marker_found;
+		}
+		len -= i;
+		if (len <= 0)
+			return;
+		data += i;
+	}
+
+	/* search backwards if there is a marker in the packet */
+	for (i = len - 1; --i >= 0; ) {
+		if (data[i] != 0xff) {
+			i--;
+			continue;
+		}
+		if (data[i + 1] == 0xff) {
+
+			/* (there may be 'ff ff' inside a marker) */
+			if (i + 2 >= len || data[i + 2] == 0x00)
+				goto marker_found;
+		}
+	}
+
+	/* no marker found */
+	/* add the JPEG header if first fragment */
+	if (data[len - 1] == 0xff)
+		sd->short_mark = -1;
+	if (gspca_dev->last_packet_type == LAST_PACKET)
+		gspca_frame_add(gspca_dev, FIRST_PACKET,
+				sd->jpeg_hdr, JPEG_HDR_SZ);
+	gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+	return;
+
+	/* marker found */
+	/* if some error, discard the frame and decrease the quality */
+marker_found:
+	new_qual = 0;
+	if (i > 2) {
+		if (data[i - 2] != 0xff || data[i - 1] != 0xd9) {
+			gspca_dev->last_packet_type = DISCARD_PACKET;
+			new_qual = -3;
+		}
+	} else if (i + 6 < len) {
+		if (data[i + 6] & 0x08) {
+			gspca_dev->last_packet_type = DISCARD_PACKET;
+			new_qual = -5;
+		}
+	}
+
+	gspca_frame_add(gspca_dev, LAST_PACKET, data, i);
+
+	/* compute the filling rate and a new JPEG quality */
+	if (new_qual == 0) {
+		int r;
+
+		r = (sd->pktsz * 100) /
+			(sd->npkt *
+				gspca_dev->urb[0]->iso_frame_desc[0].length);
+		if (r >= 85)
+			new_qual = -3;
+		else if (r < 75)
+			new_qual = 2;
+	}
+	if (new_qual != 0) {
+		sd->nchg += new_qual;
+		if (sd->nchg < -6 || sd->nchg >= 12) {
+			sd->nchg = 0;
+			new_qual += sd->quality;
+			if (new_qual < QUALITY_MIN)
+				new_qual = QUALITY_MIN;
+			else if (new_qual > QUALITY_MAX)
+				new_qual = QUALITY_MAX;
+			if (new_qual != sd->quality) {
+				sd->quality = new_qual;
+				schedule_work(&sd->work);
+			}
+		}
+	} else {
+		sd->nchg = 0;
+	}
+	sd->pktsz = sd->npkt = 0;
+
+	/* if the marker is smaller than 62 bytes,
+	 * memorize the number of bytes to skip in the next packet */
+	if (i + 62 > len) {			/* no more usable data */
+		sd->short_mark = i + 62 - len;
+		return;
+	}
+	if (sd->ag_cnt >= 0)
+		set_lum(sd, data + i);
+
+	/* if more data, start a new frame */
+	i += 62;
+	if (i < len) {
+		data += i;
+		len -= i;
+		gspca_frame_add(gspca_dev, FIRST_PACKET,
+				sd->jpeg_hdr, JPEG_HDR_SZ);
+		gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+	}
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		setbrightness(gspca_dev);
+		break;
+	case V4L2_CID_CONTRAST:
+		setcontrast(gspca_dev);
+		break;
+	case V4L2_CID_SATURATION:
+		setcolors(gspca_dev);
+		break;
+	case V4L2_CID_RED_BALANCE:
+		setredblue(gspca_dev);
+		break;
+	case V4L2_CID_GAMMA:
+		setgamma(gspca_dev);
+		break;
+	case V4L2_CID_AUTOGAIN:
+		setautogain(gspca_dev);
+		setexposure(gspca_dev);
+		setgain(gspca_dev);
+		break;
+	case V4L2_CID_VFLIP:
+		sethvflip(gspca_dev);
+		break;
+	case V4L2_CID_SHARPNESS:
+		setsharpness(gspca_dev);
+		break;
+	case V4L2_CID_ILLUMINATORS_1:
+		setillum(gspca_dev);
+		break;
+	case V4L2_CID_POWER_LINE_FREQUENCY:
+		setfreq(gspca_dev);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return gspca_dev->usb_err;
+}
+
+#if IS_ENABLED(CONFIG_INPUT)
+static int sd_int_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,		/* interrupt packet data */
+			int len)		/* interrupt packet length */
+{
+	int ret = -EINVAL;
+
+	if (len == 1 && data[0] == 1) {
+		input_report_key(gspca_dev->input_dev, KEY_CAMERA, 1);
+		input_sync(gspca_dev->input_dev);
+		input_report_key(gspca_dev->input_dev, KEY_CAMERA, 0);
+		input_sync(gspca_dev->input_dev);
+		ret = 0;
+	}
+
+	return ret;
+}
+#endif
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.stop0 = sd_stop0,
+	.pkt_scan = sd_pkt_scan,
+	.dq_callback = do_autogain,
+#if IS_ENABLED(CONFIG_INPUT)
+	.int_pkt_scan = sd_int_pkt_scan,
+#endif
+};
+
+/* -- module initialisation -- */
+#define BS(bridge, sensor) \
+	.driver_info = (BRIDGE_ ## bridge << 16) \
+			| (SENSOR_ ## sensor << 8)
+#define BSF(bridge, sensor, flags) \
+	.driver_info = (BRIDGE_ ## bridge << 16) \
+			| (SENSOR_ ## sensor << 8) \
+			| (flags)
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x0458, 0x7025), BSF(SN9C120, MI0360B, F_PDN_INV)},
+	{USB_DEVICE(0x0458, 0x702e), BS(SN9C120, OV7660)},
+	{USB_DEVICE(0x045e, 0x00f5), BSF(SN9C105, OV7660, F_PDN_INV)},
+	{USB_DEVICE(0x045e, 0x00f7), BSF(SN9C105, OV7660, F_PDN_INV)},
+	{USB_DEVICE(0x0471, 0x0327), BS(SN9C105, MI0360)},
+	{USB_DEVICE(0x0471, 0x0328), BS(SN9C105, MI0360)},
+	{USB_DEVICE(0x0471, 0x0330), BS(SN9C105, MI0360)},
+	{USB_DEVICE(0x06f8, 0x3004), BS(SN9C105, OV7660)},
+	{USB_DEVICE(0x06f8, 0x3008), BS(SN9C105, OV7660)},
+/*	{USB_DEVICE(0x0c45, 0x603a), BS(SN9C102P, OV7648)}, */
+	{USB_DEVICE(0x0c45, 0x6040), BS(SN9C102P, HV7131R)},
+/*	{USB_DEVICE(0x0c45, 0x607a), BS(SN9C102P, OV7648)}, */
+/*	{USB_DEVICE(0x0c45, 0x607b), BS(SN9C102P, OV7660)}, */
+	{USB_DEVICE(0x0c45, 0x607c), BS(SN9C102P, HV7131R)},
+/*	{USB_DEVICE(0x0c45, 0x607e), BS(SN9C102P, OV7630)}, */
+	{USB_DEVICE(0x0c45, 0x60c0), BSF(SN9C105, MI0360, F_ILLUM)},
+						/* or MT9V111 */
+/*	{USB_DEVICE(0x0c45, 0x60c2), BS(SN9C105, P1030xC)}, */
+/*	{USB_DEVICE(0x0c45, 0x60c8), BS(SN9C105, OM6802)}, */
+/*	{USB_DEVICE(0x0c45, 0x60cc), BS(SN9C105, HV7131GP)}, */
+	{USB_DEVICE(0x0c45, 0x60ce), BS(SN9C105, SP80708)},
+	{USB_DEVICE(0x0c45, 0x60ec), BS(SN9C105, MO4000)},
+/*	{USB_DEVICE(0x0c45, 0x60ef), BS(SN9C105, ICM105C)}, */
+/*	{USB_DEVICE(0x0c45, 0x60fa), BS(SN9C105, OV7648)}, */
+/*	{USB_DEVICE(0x0c45, 0x60f2), BS(SN9C105, OV7660)}, */
+	{USB_DEVICE(0x0c45, 0x60fb), BS(SN9C105, OV7660)},
+	{USB_DEVICE(0x0c45, 0x60fc), BS(SN9C105, HV7131R)},
+	{USB_DEVICE(0x0c45, 0x60fe), BS(SN9C105, OV7630)},
+	{USB_DEVICE(0x0c45, 0x6100), BS(SN9C120, MI0360)},	/*sn9c128*/
+	{USB_DEVICE(0x0c45, 0x6102), BS(SN9C120, PO2030N)},	/* /GC0305*/
+/*	{USB_DEVICE(0x0c45, 0x6108), BS(SN9C120, OM6802)}, */
+	{USB_DEVICE(0x0c45, 0x610a), BS(SN9C120, OV7648)},	/*sn9c128*/
+	{USB_DEVICE(0x0c45, 0x610b), BS(SN9C120, OV7660)},	/*sn9c128*/
+	{USB_DEVICE(0x0c45, 0x610c), BS(SN9C120, HV7131R)},	/*sn9c128*/
+	{USB_DEVICE(0x0c45, 0x610e), BS(SN9C120, OV7630)},	/*sn9c128*/
+/*	{USB_DEVICE(0x0c45, 0x610f), BS(SN9C120, S5K53BEB)}, */
+/*	{USB_DEVICE(0x0c45, 0x6122), BS(SN9C110, ICM105C)}, */
+/*	{USB_DEVICE(0x0c45, 0x6123), BS(SN9C110, SanyoCCD)}, */
+	{USB_DEVICE(0x0c45, 0x6128), BS(SN9C120, OM6802)},	/*sn9c325?*/
+/*bw600.inf:*/
+	{USB_DEVICE(0x0c45, 0x612a), BS(SN9C120, OV7648)},	/*sn9c325?*/
+	{USB_DEVICE(0x0c45, 0x612b), BS(SN9C110, ADCM1700)},
+	{USB_DEVICE(0x0c45, 0x612c), BS(SN9C110, MO4000)},
+	{USB_DEVICE(0x0c45, 0x612e), BS(SN9C110, OV7630)},
+/*	{USB_DEVICE(0x0c45, 0x612f), BS(SN9C110, ICM105C)}, */
+	{USB_DEVICE(0x0c45, 0x6130), BS(SN9C120, MI0360)},
+						/* or MT9V111 / MI0360B */
+/*	{USB_DEVICE(0x0c45, 0x6132), BS(SN9C120, OV7670)}, */
+	{USB_DEVICE(0x0c45, 0x6138), BS(SN9C120, MO4000)},
+	{USB_DEVICE(0x0c45, 0x613a), BS(SN9C120, OV7648)},
+	{USB_DEVICE(0x0c45, 0x613b), BS(SN9C120, OV7660)},
+	{USB_DEVICE(0x0c45, 0x613c), BS(SN9C120, HV7131R)},
+	{USB_DEVICE(0x0c45, 0x613e), BS(SN9C120, OV7630)},
+	{USB_DEVICE(0x0c45, 0x6142), BS(SN9C120, PO2030N)},	/*sn9c120b*/
+						/* or GC0305 / GC0307 */
+	{USB_DEVICE(0x0c45, 0x6143), BS(SN9C120, SP80708)},	/*sn9c120b*/
+	{USB_DEVICE(0x0c45, 0x6148), BS(SN9C120, OM6802)},	/*sn9c120b*/
+	{USB_DEVICE(0x0c45, 0x614a), BSF(SN9C120, ADCM1700, F_ILLUM)},
+/*	{USB_DEVICE(0x0c45, 0x614c), BS(SN9C120, GC0306)}, */	/*sn9c120b*/
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+		    const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/spca1528.c b/drivers/media/usb/gspca/spca1528.c
new file mode 100644
index 0000000..d25924e
--- /dev/null
+++ b/drivers/media/usb/gspca/spca1528.c
@@ -0,0 +1,442 @@
+/*
+ * spca1528 subdriver
+ *
+ * Copyright (C) 2010-2011 Jean-Francois Moine (http://moinejf.free.fr)
+ *
+ * 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
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "spca1528"
+
+#include "gspca.h"
+#include "jpeg.h"
+
+MODULE_AUTHOR("Jean-Francois Moine <http://moinejf.free.fr>");
+MODULE_DESCRIPTION("SPCA1528 USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+
+	u8 pkt_seq;
+
+	u8 jpeg_hdr[JPEG_HDR_SZ];
+};
+
+static const struct v4l2_pix_format vga_mode[] = {
+/*		(does not work correctly)
+	{176, 144, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 176,
+		.sizeimage = 176 * 144 * 5 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 3},
+*/
+	{320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 4 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 2},
+	{640, 480, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 1},
+};
+
+/* read <len> bytes to gspca usb_buf */
+static void reg_r(struct gspca_dev *gspca_dev,
+			u8 req,
+			u16 index,
+			int len)
+{
+#if USB_BUF_SZ < 64
+#error "USB buffer too small"
+#endif
+	struct usb_device *dev = gspca_dev->dev;
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+			req,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0x0000,			/* value */
+			index,
+			gspca_dev->usb_buf, len,
+			500);
+	gspca_dbg(gspca_dev, D_USBI, "GET %02x 0000 %04x %02x\n", req, index,
+		  gspca_dev->usb_buf[0]);
+	if (ret < 0) {
+		pr_err("reg_r err %d\n", ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+static void reg_w(struct gspca_dev *gspca_dev,
+			u8 req,
+			u16 value,
+			u16 index)
+{
+	struct usb_device *dev = gspca_dev->dev;
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	gspca_dbg(gspca_dev, D_USBO, "SET %02x %04x %04x\n", req, value, index);
+	ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+			req,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			value, index,
+			NULL, 0, 500);
+	if (ret < 0) {
+		pr_err("reg_w err %d\n", ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+static void reg_wb(struct gspca_dev *gspca_dev,
+			u8 req,
+			u16 value,
+			u16 index,
+			u8 byte)
+{
+	struct usb_device *dev = gspca_dev->dev;
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	gspca_dbg(gspca_dev, D_USBO, "SET %02x %04x %04x %02x\n",
+		  req, value, index, byte);
+	gspca_dev->usb_buf[0] = byte;
+	ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+			req,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			value, index,
+			gspca_dev->usb_buf, 1, 500);
+	if (ret < 0) {
+		pr_err("reg_w err %d\n", ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+static void wait_status_0(struct gspca_dev *gspca_dev)
+{
+	int i, w;
+
+	i = 16;
+	w = 0;
+	do {
+		reg_r(gspca_dev, 0x21, 0x0000, 1);
+		if (gspca_dev->usb_buf[0] == 0)
+			return;
+		w += 15;
+		msleep(w);
+	} while (--i > 0);
+	gspca_err(gspca_dev, "wait_status_0 timeout\n");
+	gspca_dev->usb_err = -ETIME;
+}
+
+static void wait_status_1(struct gspca_dev *gspca_dev)
+{
+	int i;
+
+	i = 10;
+	do {
+		reg_r(gspca_dev, 0x21, 0x0001, 1);
+		msleep(10);
+		if (gspca_dev->usb_buf[0] == 1) {
+			reg_wb(gspca_dev, 0x21, 0x0000, 0x0001, 0x00);
+			reg_r(gspca_dev, 0x21, 0x0001, 1);
+			return;
+		}
+	} while (--i > 0);
+	gspca_err(gspca_dev, "wait_status_1 timeout\n");
+	gspca_dev->usb_err = -ETIME;
+}
+
+static void setbrightness(struct gspca_dev *gspca_dev, s32 val)
+{
+	reg_wb(gspca_dev, 0xc0, 0x0000, 0x00c0, val);
+}
+
+static void setcontrast(struct gspca_dev *gspca_dev, s32 val)
+{
+	reg_wb(gspca_dev, 0xc1, 0x0000, 0x00c1, val);
+}
+
+static void sethue(struct gspca_dev *gspca_dev, s32 val)
+{
+	reg_wb(gspca_dev, 0xc2, 0x0000, 0x0000, val);
+}
+
+static void setcolor(struct gspca_dev *gspca_dev, s32 val)
+{
+	reg_wb(gspca_dev, 0xc3, 0x0000, 0x00c3, val);
+}
+
+static void setsharpness(struct gspca_dev *gspca_dev, s32 val)
+{
+	reg_wb(gspca_dev, 0xc4, 0x0000, 0x00c4, val);
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	gspca_dev->cam.cam_mode = vga_mode;
+	gspca_dev->cam.nmodes = ARRAY_SIZE(vga_mode);
+	gspca_dev->cam.npkt = 128; /* number of packets per ISOC message */
+			/*fixme: 256 in ms-win traces*/
+
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	reg_w(gspca_dev, 0x00, 0x0001, 0x2067);
+	reg_w(gspca_dev, 0x00, 0x00d0, 0x206b);
+	reg_w(gspca_dev, 0x00, 0x0000, 0x206c);
+	reg_w(gspca_dev, 0x00, 0x0001, 0x2069);
+	msleep(8);
+	reg_w(gspca_dev, 0x00, 0x00c0, 0x206b);
+	reg_w(gspca_dev, 0x00, 0x0000, 0x206c);
+	reg_w(gspca_dev, 0x00, 0x0001, 0x2069);
+
+	reg_r(gspca_dev, 0x20, 0x0000, 1);
+	reg_r(gspca_dev, 0x20, 0x0000, 5);
+	reg_r(gspca_dev, 0x23, 0x0000, 64);
+	gspca_dbg(gspca_dev, D_PROBE, "%s%s\n", &gspca_dev->usb_buf[0x1c],
+		  &gspca_dev->usb_buf[0x30]);
+	reg_r(gspca_dev, 0x23, 0x0001, 64);
+	return gspca_dev->usb_err;
+}
+
+/* function called at start time before URB creation */
+static int sd_isoc_init(struct gspca_dev *gspca_dev)
+{
+	u8 mode;
+
+	reg_r(gspca_dev, 0x00, 0x2520, 1);
+	wait_status_0(gspca_dev);
+	reg_w(gspca_dev, 0xc5, 0x0003, 0x0000);
+	wait_status_1(gspca_dev);
+
+	wait_status_0(gspca_dev);
+	mode = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].priv;
+	reg_wb(gspca_dev, 0x25, 0x0000, 0x0004, mode);
+	reg_r(gspca_dev, 0x25, 0x0004, 1);
+	reg_wb(gspca_dev, 0x27, 0x0000, 0x0000, 0x06);	/* 420 */
+	reg_r(gspca_dev, 0x27, 0x0000, 1);
+
+/* not useful..
+	gspca_dev->alt = 4;		* use alternate setting 3 */
+
+	return gspca_dev->usb_err;
+}
+
+/* -- start the camera -- */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	/* initialize the JPEG header */
+	jpeg_define(sd->jpeg_hdr, gspca_dev->pixfmt.height,
+			gspca_dev->pixfmt.width,
+			0x22);		/* JPEG 411 */
+
+	/* the JPEG quality shall be 85% */
+	jpeg_set_qual(sd->jpeg_hdr, 85);
+
+	reg_r(gspca_dev, 0x00, 0x2520, 1);
+	msleep(8);
+
+	/* start the capture */
+	wait_status_0(gspca_dev);
+	reg_w(gspca_dev, 0x31, 0x0000, 0x0004);	/* start request */
+	wait_status_1(gspca_dev);
+	wait_status_0(gspca_dev);
+	msleep(200);
+
+	sd->pkt_seq = 0;
+	return gspca_dev->usb_err;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	/* stop the capture */
+	wait_status_0(gspca_dev);
+	reg_w(gspca_dev, 0x31, 0x0000, 0x0000);	/* stop request */
+	wait_status_1(gspca_dev);
+	wait_status_0(gspca_dev);
+}
+
+/* move a packet adding 0x00 after 0xff */
+static void add_packet(struct gspca_dev *gspca_dev,
+			u8 *data,
+			int len)
+{
+	int i;
+
+	i = 0;
+	do {
+		if (data[i] == 0xff) {
+			gspca_frame_add(gspca_dev, INTER_PACKET,
+					data, i + 1);
+			len -= i;
+			data += i;
+			*data = 0x00;
+			i = 0;
+		}
+	} while (++i < len);
+	gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	static const u8 ffd9[] = {0xff, 0xd9};
+
+	/* image packets start with:
+	 *	02 8n
+	 * with <n> bit:
+	 *	0x01: even (0) / odd (1) image
+	 *	0x02: end of image when set
+	 */
+	if (len < 3)
+		return;				/* empty packet */
+	if (*data == 0x02) {
+		if (data[1] & 0x02) {
+			sd->pkt_seq = !(data[1] & 1);
+			add_packet(gspca_dev, data + 2, len - 2);
+			gspca_frame_add(gspca_dev, LAST_PACKET,
+					ffd9, 2);
+			return;
+		}
+		if ((data[1] & 1) != sd->pkt_seq)
+			goto err;
+		if (gspca_dev->last_packet_type == LAST_PACKET)
+			gspca_frame_add(gspca_dev, FIRST_PACKET,
+					sd->jpeg_hdr, JPEG_HDR_SZ);
+		add_packet(gspca_dev, data + 2, len - 2);
+		return;
+	}
+err:
+	gspca_dev->last_packet_type = DISCARD_PACKET;
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		setbrightness(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_CONTRAST:
+		setcontrast(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_HUE:
+		sethue(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_SATURATION:
+		setcolor(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_SHARPNESS:
+		setsharpness(gspca_dev, ctrl->val);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 5);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0, 255, 1, 128);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_CONTRAST, 0, 8, 1, 1);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_HUE, 0, 255, 1, 0);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_SATURATION, 0, 8, 1, 1);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_SHARPNESS, 0, 255, 1, 0);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	return 0;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.isoc_init = sd_isoc_init,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.pkt_scan = sd_pkt_scan,
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x04fc, 0x1528)},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	/* the video interface for isochronous transfer is 1 */
+	if (intf->cur_altsetting->desc.bInterfaceNumber != 1)
+		return -ENODEV;
+
+	return gspca_dev_probe2(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/spca500.c b/drivers/media/usb/gspca/spca500.c
new file mode 100644
index 0000000..e90d2f3
--- /dev/null
+++ b/drivers/media/usb/gspca/spca500.c
@@ -0,0 +1,989 @@
+/*
+ * SPCA500 chip based cameras initialization data
+ *
+ * V4L2 by Jean-Francois Moine <http://moinejf.free.fr>
+ *
+ * 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
+ * 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.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "spca500"
+
+#include "gspca.h"
+#include "jpeg.h"
+
+MODULE_AUTHOR("Michel Xhaard <mxhaard@users.sourceforge.net>");
+MODULE_DESCRIPTION("GSPCA/SPCA500 USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+#define QUALITY 85
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;		/* !! must be the first item */
+
+	char subtype;
+#define AgfaCl20 0
+#define AiptekPocketDV 1
+#define BenqDC1016 2
+#define CreativePCCam300 3
+#define DLinkDSC350 4
+#define Gsmartmini 5
+#define IntelPocketPCCamera 6
+#define KodakEZ200 7
+#define LogitechClickSmart310 8
+#define LogitechClickSmart510 9
+#define LogitechTraveler 10
+#define MustekGsmart300 11
+#define Optimedia 12
+#define PalmPixDC85 13
+#define ToptroIndus 14
+
+	u8 jpeg_hdr[JPEG_HDR_SZ];
+};
+
+static const struct v4l2_pix_format vga_mode[] = {
+	{320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 1},
+	{640, 480, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 0},
+};
+
+static const struct v4l2_pix_format sif_mode[] = {
+	{176, 144, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 176,
+		.sizeimage = 176 * 144 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 1},
+	{352, 288, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 352,
+		.sizeimage = 352 * 288 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 0},
+};
+
+/* Frame packet header offsets for the spca500 */
+#define SPCA500_OFFSET_PADDINGLB 2
+#define SPCA500_OFFSET_PADDINGHB 3
+#define SPCA500_OFFSET_MODE      4
+#define SPCA500_OFFSET_IMGWIDTH  5
+#define SPCA500_OFFSET_IMGHEIGHT 6
+#define SPCA500_OFFSET_IMGMODE   7
+#define SPCA500_OFFSET_QTBLINDEX 8
+#define SPCA500_OFFSET_FRAMSEQ   9
+#define SPCA500_OFFSET_CDSPINFO  10
+#define SPCA500_OFFSET_GPIO      11
+#define SPCA500_OFFSET_AUGPIO    12
+#define SPCA500_OFFSET_DATA      16
+
+
+static const __u16 spca500_visual_defaults[][3] = {
+	{0x00, 0x0003, 0x816b},	/* SSI not active sync with vsync,
+				 * hue (H byte) = 0,
+				 * saturation/hue enable,
+				 * brightness/contrast enable.
+				 */
+	{0x00, 0x0000, 0x8167},	/* brightness = 0 */
+	{0x00, 0x0020, 0x8168},	/* contrast = 0 */
+	{0x00, 0x0003, 0x816b},	/* SSI not active sync with vsync,
+				 * hue (H byte) = 0, saturation/hue enable,
+				 * brightness/contrast enable.
+				 * was 0x0003, now 0x0000.
+				 */
+	{0x00, 0x0000, 0x816a},	/* hue (L byte) = 0 */
+	{0x00, 0x0020, 0x8169},	/* saturation = 0x20 */
+	{0x00, 0x0050, 0x8157},	/* edge gain high threshold */
+	{0x00, 0x0030, 0x8158},	/* edge gain low threshold */
+	{0x00, 0x0028, 0x8159},	/* edge bandwidth high threshold */
+	{0x00, 0x000a, 0x815a},	/* edge bandwidth low threshold */
+	{0x00, 0x0001, 0x8202},	/* clock rate compensation = 1/25 sec/frame */
+	{0x0c, 0x0004, 0x0000},
+	/* set interface */
+	{}
+};
+static const __u16 Clicksmart510_defaults[][3] = {
+	{0x00, 0x00, 0x8211},
+	{0x00, 0x01, 0x82c0},
+	{0x00, 0x10, 0x82cb},
+	{0x00, 0x0f, 0x800d},
+	{0x00, 0x82, 0x8225},
+	{0x00, 0x21, 0x8228},
+	{0x00, 0x00, 0x8203},
+	{0x00, 0x00, 0x8204},
+	{0x00, 0x08, 0x8205},
+	{0x00, 0xf8, 0x8206},
+	{0x00, 0x28, 0x8207},
+	{0x00, 0xa0, 0x8208},
+	{0x00, 0x08, 0x824a},
+	{0x00, 0x08, 0x8214},
+	{0x00, 0x80, 0x82c1},
+	{0x00, 0x00, 0x82c2},
+	{0x00, 0x00, 0x82ca},
+	{0x00, 0x80, 0x82c1},
+	{0x00, 0x04, 0x82c2},
+	{0x00, 0x00, 0x82ca},
+	{0x00, 0xfc, 0x8100},
+	{0x00, 0xfc, 0x8105},
+	{0x00, 0x30, 0x8101},
+	{0x00, 0x00, 0x8102},
+	{0x00, 0x00, 0x8103},
+	{0x00, 0x66, 0x8107},
+	{0x00, 0x00, 0x816b},
+	{0x00, 0x00, 0x8155},
+	{0x00, 0x01, 0x8156},
+	{0x00, 0x60, 0x8157},
+	{0x00, 0x40, 0x8158},
+	{0x00, 0x0a, 0x8159},
+	{0x00, 0x06, 0x815a},
+	{0x00, 0x00, 0x813f},
+	{0x00, 0x00, 0x8200},
+	{0x00, 0x19, 0x8201},
+	{0x00, 0x00, 0x82c1},
+	{0x00, 0xa0, 0x82c2},
+	{0x00, 0x00, 0x82ca},
+	{0x00, 0x00, 0x8117},
+	{0x00, 0x00, 0x8118},
+	{0x00, 0x65, 0x8119},
+	{0x00, 0x00, 0x811a},
+	{0x00, 0x00, 0x811b},
+	{0x00, 0x55, 0x811c},
+	{0x00, 0x65, 0x811d},
+	{0x00, 0x55, 0x811e},
+	{0x00, 0x16, 0x811f},
+	{0x00, 0x19, 0x8120},
+	{0x00, 0x80, 0x8103},
+	{0x00, 0x83, 0x816b},
+	{0x00, 0x25, 0x8168},
+	{0x00, 0x01, 0x820f},
+	{0x00, 0xff, 0x8115},
+	{0x00, 0x48, 0x8116},
+	{0x00, 0x50, 0x8151},
+	{0x00, 0x40, 0x8152},
+	{0x00, 0x78, 0x8153},
+	{0x00, 0x40, 0x8154},
+	{0x00, 0x00, 0x8167},
+	{0x00, 0x20, 0x8168},
+	{0x00, 0x00, 0x816a},
+	{0x00, 0x03, 0x816b},
+	{0x00, 0x20, 0x8169},
+	{0x00, 0x60, 0x8157},
+	{0x00, 0x00, 0x8190},
+	{0x00, 0x00, 0x81a1},
+	{0x00, 0x00, 0x81b2},
+	{0x00, 0x27, 0x8191},
+	{0x00, 0x27, 0x81a2},
+	{0x00, 0x27, 0x81b3},
+	{0x00, 0x4b, 0x8192},
+	{0x00, 0x4b, 0x81a3},
+	{0x00, 0x4b, 0x81b4},
+	{0x00, 0x66, 0x8193},
+	{0x00, 0x66, 0x81a4},
+	{0x00, 0x66, 0x81b5},
+	{0x00, 0x79, 0x8194},
+	{0x00, 0x79, 0x81a5},
+	{0x00, 0x79, 0x81b6},
+	{0x00, 0x8a, 0x8195},
+	{0x00, 0x8a, 0x81a6},
+	{0x00, 0x8a, 0x81b7},
+	{0x00, 0x9b, 0x8196},
+	{0x00, 0x9b, 0x81a7},
+	{0x00, 0x9b, 0x81b8},
+	{0x00, 0xa6, 0x8197},
+	{0x00, 0xa6, 0x81a8},
+	{0x00, 0xa6, 0x81b9},
+	{0x00, 0xb2, 0x8198},
+	{0x00, 0xb2, 0x81a9},
+	{0x00, 0xb2, 0x81ba},
+	{0x00, 0xbe, 0x8199},
+	{0x00, 0xbe, 0x81aa},
+	{0x00, 0xbe, 0x81bb},
+	{0x00, 0xc8, 0x819a},
+	{0x00, 0xc8, 0x81ab},
+	{0x00, 0xc8, 0x81bc},
+	{0x00, 0xd2, 0x819b},
+	{0x00, 0xd2, 0x81ac},
+	{0x00, 0xd2, 0x81bd},
+	{0x00, 0xdb, 0x819c},
+	{0x00, 0xdb, 0x81ad},
+	{0x00, 0xdb, 0x81be},
+	{0x00, 0xe4, 0x819d},
+	{0x00, 0xe4, 0x81ae},
+	{0x00, 0xe4, 0x81bf},
+	{0x00, 0xed, 0x819e},
+	{0x00, 0xed, 0x81af},
+	{0x00, 0xed, 0x81c0},
+	{0x00, 0xf7, 0x819f},
+	{0x00, 0xf7, 0x81b0},
+	{0x00, 0xf7, 0x81c1},
+	{0x00, 0xff, 0x81a0},
+	{0x00, 0xff, 0x81b1},
+	{0x00, 0xff, 0x81c2},
+	{0x00, 0x03, 0x8156},
+	{0x00, 0x00, 0x8211},
+	{0x00, 0x20, 0x8168},
+	{0x00, 0x01, 0x8202},
+	{0x00, 0x30, 0x8101},
+	{0x00, 0x00, 0x8111},
+	{0x00, 0x00, 0x8112},
+	{0x00, 0x00, 0x8113},
+	{0x00, 0x00, 0x8114},
+	{}
+};
+
+static const __u8 qtable_creative_pccam[2][64] = {
+	{				/* Q-table Y-components */
+	 0x05, 0x03, 0x03, 0x05, 0x07, 0x0c, 0x0f, 0x12,
+	 0x04, 0x04, 0x04, 0x06, 0x08, 0x11, 0x12, 0x11,
+	 0x04, 0x04, 0x05, 0x07, 0x0c, 0x11, 0x15, 0x11,
+	 0x04, 0x05, 0x07, 0x09, 0x0f, 0x1a, 0x18, 0x13,
+	 0x05, 0x07, 0x0b, 0x11, 0x14, 0x21, 0x1f, 0x17,
+	 0x07, 0x0b, 0x11, 0x13, 0x18, 0x1f, 0x22, 0x1c,
+	 0x0f, 0x13, 0x17, 0x1a, 0x1f, 0x24, 0x24, 0x1e,
+	 0x16, 0x1c, 0x1d, 0x1d, 0x22, 0x1e, 0x1f, 0x1e},
+	{				/* Q-table C-components */
+	 0x05, 0x05, 0x07, 0x0e, 0x1e, 0x1e, 0x1e, 0x1e,
+	 0x05, 0x06, 0x08, 0x14, 0x1e, 0x1e, 0x1e, 0x1e,
+	 0x07, 0x08, 0x11, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+	 0x0e, 0x14, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+	 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+	 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+	 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+	 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e}
+};
+
+static const __u8 qtable_kodak_ez200[2][64] = {
+	{				/* Q-table Y-components */
+	 0x02, 0x01, 0x01, 0x02, 0x02, 0x04, 0x05, 0x06,
+	 0x01, 0x01, 0x01, 0x02, 0x03, 0x06, 0x06, 0x06,
+	 0x01, 0x01, 0x02, 0x02, 0x04, 0x06, 0x07, 0x06,
+	 0x01, 0x02, 0x02, 0x03, 0x05, 0x09, 0x08, 0x06,
+	 0x02, 0x02, 0x04, 0x06, 0x07, 0x0b, 0x0a, 0x08,
+	 0x02, 0x04, 0x06, 0x06, 0x08, 0x0a, 0x0b, 0x09,
+	 0x05, 0x06, 0x08, 0x09, 0x0a, 0x0c, 0x0c, 0x0a,
+	 0x07, 0x09, 0x0a, 0x0a, 0x0b, 0x0a, 0x0a, 0x0a},
+	{				/* Q-table C-components */
+	 0x02, 0x02, 0x02, 0x05, 0x0a, 0x0a, 0x0a, 0x0a,
+	 0x02, 0x02, 0x03, 0x07, 0x0a, 0x0a, 0x0a, 0x0a,
+	 0x02, 0x03, 0x06, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+	 0x05, 0x07, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+	 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+	 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+	 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+	 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a}
+};
+
+static const __u8 qtable_pocketdv[2][64] = {
+	{		/* Q-table Y-components start registers 0x8800 */
+	 0x06, 0x04, 0x04, 0x06, 0x0a, 0x10, 0x14, 0x18,
+	 0x05, 0x05, 0x06, 0x08, 0x0a, 0x17, 0x18, 0x16,
+	 0x06, 0x05, 0x06, 0x0a, 0x10, 0x17, 0x1c, 0x16,
+	 0x06, 0x07, 0x09, 0x0c, 0x14, 0x23, 0x20, 0x19,
+	 0x07, 0x09, 0x0f, 0x16, 0x1b, 0x2c, 0x29, 0x1f,
+	 0x0a, 0x0e, 0x16, 0x1a, 0x20, 0x2a, 0x2d, 0x25,
+	 0x14, 0x1a, 0x1f, 0x23, 0x29, 0x30, 0x30, 0x28,
+	 0x1d, 0x25, 0x26, 0x27, 0x2d, 0x28, 0x29, 0x28,
+	 },
+	{		/* Q-table C-components start registers 0x8840 */
+	 0x07, 0x07, 0x0a, 0x13, 0x28, 0x28, 0x28, 0x28,
+	 0x07, 0x08, 0x0a, 0x1a, 0x28, 0x28, 0x28, 0x28,
+	 0x0a, 0x0a, 0x16, 0x28, 0x28, 0x28, 0x28, 0x28,
+	 0x13, 0x1a, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+	 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+	 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+	 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+	 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28}
+};
+
+/* read 'len' bytes to gspca_dev->usb_buf */
+static void reg_r(struct gspca_dev *gspca_dev,
+		  __u16 index,
+		  __u16 length)
+{
+	usb_control_msg(gspca_dev->dev,
+			usb_rcvctrlpipe(gspca_dev->dev, 0),
+			0,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0,		/* value */
+			index, gspca_dev->usb_buf, length, 500);
+}
+
+static int reg_w(struct gspca_dev *gspca_dev,
+		     __u16 req, __u16 index, __u16 value)
+{
+	int ret;
+
+	gspca_dbg(gspca_dev, D_USBO, "reg write: [0x%02x] = 0x%02x\n",
+		  index, value);
+	ret = usb_control_msg(gspca_dev->dev,
+			usb_sndctrlpipe(gspca_dev->dev, 0),
+			req,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			value, index, NULL, 0, 500);
+	if (ret < 0)
+		pr_err("reg write: error %d\n", ret);
+	return ret;
+}
+
+/* returns: negative is error, pos or zero is data */
+static int reg_r_12(struct gspca_dev *gspca_dev,
+			__u16 req,	/* bRequest */
+			__u16 index,	/* wIndex */
+			__u16 length)	/* wLength (1 or 2 only) */
+{
+	int ret;
+
+	gspca_dev->usb_buf[1] = 0;
+	ret = usb_control_msg(gspca_dev->dev,
+			usb_rcvctrlpipe(gspca_dev->dev, 0),
+			req,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0,		/* value */
+			index,
+			gspca_dev->usb_buf, length,
+			500);		/* timeout */
+	if (ret < 0) {
+		pr_err("reg_r_12 err %d\n", ret);
+		return ret;
+	}
+	return (gspca_dev->usb_buf[1] << 8) + gspca_dev->usb_buf[0];
+}
+
+/*
+ * Simple function to wait for a given 8-bit value to be returned from
+ * a reg_read call.
+ * Returns: negative is error or timeout, zero is success.
+ */
+static int reg_r_wait(struct gspca_dev *gspca_dev,
+			__u16 reg, __u16 index, __u16 value)
+{
+	int ret, cnt = 20;
+
+	while (--cnt > 0) {
+		ret = reg_r_12(gspca_dev, reg, index, 1);
+		if (ret == value)
+			return 0;
+		msleep(50);
+	}
+	return -EIO;
+}
+
+static int write_vector(struct gspca_dev *gspca_dev,
+			const __u16 data[][3])
+{
+	int ret, i = 0;
+
+	while (data[i][0] != 0 || data[i][1] != 0 || data[i][2] != 0) {
+		ret = reg_w(gspca_dev, data[i][0], data[i][2], data[i][1]);
+		if (ret < 0)
+			return ret;
+		i++;
+	}
+	return 0;
+}
+
+static int spca50x_setup_qtable(struct gspca_dev *gspca_dev,
+				unsigned int request,
+				unsigned int ybase,
+				unsigned int cbase,
+				const __u8 qtable[2][64])
+{
+	int i, err;
+
+	/* loop over y components */
+	for (i = 0; i < 64; i++) {
+		err = reg_w(gspca_dev, request, ybase + i, qtable[0][i]);
+		if (err < 0)
+			return err;
+	}
+
+	/* loop over c components */
+	for (i = 0; i < 64; i++) {
+		err = reg_w(gspca_dev, request, cbase + i, qtable[1][i]);
+		if (err < 0)
+			return err;
+	}
+	return 0;
+}
+
+static void spca500_ping310(struct gspca_dev *gspca_dev)
+{
+	reg_r(gspca_dev, 0x0d04, 2);
+	gspca_dbg(gspca_dev, D_STREAM, "ClickSmart310 ping 0x0d04 0x%02x 0x%02x\n",
+		  gspca_dev->usb_buf[0], gspca_dev->usb_buf[1]);
+}
+
+static void spca500_clksmart310_init(struct gspca_dev *gspca_dev)
+{
+	reg_r(gspca_dev, 0x0d05, 2);
+	gspca_dbg(gspca_dev, D_STREAM, "ClickSmart310 init 0x0d05 0x%02x 0x%02x\n",
+		  gspca_dev->usb_buf[0], gspca_dev->usb_buf[1]);
+	reg_w(gspca_dev, 0x00, 0x8167, 0x5a);
+	spca500_ping310(gspca_dev);
+
+	reg_w(gspca_dev, 0x00, 0x8168, 0x22);
+	reg_w(gspca_dev, 0x00, 0x816a, 0xc0);
+	reg_w(gspca_dev, 0x00, 0x816b, 0x0b);
+	reg_w(gspca_dev, 0x00, 0x8169, 0x25);
+	reg_w(gspca_dev, 0x00, 0x8157, 0x5b);
+	reg_w(gspca_dev, 0x00, 0x8158, 0x5b);
+	reg_w(gspca_dev, 0x00, 0x813f, 0x03);
+	reg_w(gspca_dev, 0x00, 0x8151, 0x4a);
+	reg_w(gspca_dev, 0x00, 0x8153, 0x78);
+	reg_w(gspca_dev, 0x00, 0x0d01, 0x04);
+						/* 00 for adjust shutter */
+	reg_w(gspca_dev, 0x00, 0x0d02, 0x01);
+	reg_w(gspca_dev, 0x00, 0x8169, 0x25);
+	reg_w(gspca_dev, 0x00, 0x0d01, 0x02);
+}
+
+static void spca500_setmode(struct gspca_dev *gspca_dev,
+			__u8 xmult, __u8 ymult)
+{
+	int mode;
+
+	/* set x multiplier */
+	reg_w(gspca_dev, 0, 0x8001, xmult);
+
+	/* set y multiplier */
+	reg_w(gspca_dev, 0, 0x8002, ymult);
+
+	/* use compressed mode, VGA, with mode specific subsample */
+	mode = gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv;
+	reg_w(gspca_dev, 0, 0x8003, mode << 4);
+}
+
+static int spca500_full_reset(struct gspca_dev *gspca_dev)
+{
+	int err;
+
+	/* send the reset command */
+	err = reg_w(gspca_dev, 0xe0, 0x0001, 0x0000);
+	if (err < 0)
+		return err;
+
+	/* wait for the reset to complete */
+	err = reg_r_wait(gspca_dev, 0x06, 0x0000, 0x0000);
+	if (err < 0)
+		return err;
+	err = reg_w(gspca_dev, 0xe0, 0x0000, 0x0000);
+	if (err < 0)
+		return err;
+	err = reg_r_wait(gspca_dev, 0x06, 0, 0);
+	if (err < 0) {
+		gspca_err(gspca_dev, "reg_r_wait() failed\n");
+		return err;
+	}
+	/* all ok */
+	return 0;
+}
+
+/* Synchro the Bridge with sensor */
+/* Maybe that will work on all spca500 chip */
+/* because i only own a clicksmart310 try for that chip */
+/* using spca50x_set_packet_size() cause an Ooops here */
+/* usb_set_interface from kernel 2.6.x clear all the urb stuff */
+/* up-port the same feature as in 2.4.x kernel */
+static int spca500_synch310(struct gspca_dev *gspca_dev)
+{
+	if (usb_set_interface(gspca_dev->dev, gspca_dev->iface, 0) < 0) {
+		gspca_err(gspca_dev, "Set packet size: set interface error\n");
+		goto error;
+	}
+	spca500_ping310(gspca_dev);
+
+	reg_r(gspca_dev, 0x0d00, 1);
+
+	/* need alt setting here */
+	gspca_dbg(gspca_dev, D_PACK, "ClickSmart310 sync alt: %d\n",
+		  gspca_dev->alt);
+
+	/* Windoze use pipe with altsetting 6 why 7 here */
+	if (usb_set_interface(gspca_dev->dev,
+				gspca_dev->iface,
+				gspca_dev->alt) < 0) {
+		gspca_err(gspca_dev, "Set packet size: set interface error\n");
+		goto error;
+	}
+	return 0;
+error:
+	return -EBUSY;
+}
+
+static void spca500_reinit(struct gspca_dev *gspca_dev)
+{
+	int err;
+	__u8 Data;
+
+	/* some unknown command from Aiptek pocket dv and family300 */
+
+	reg_w(gspca_dev, 0x00, 0x0d01, 0x01);
+	reg_w(gspca_dev, 0x00, 0x0d03, 0x00);
+	reg_w(gspca_dev, 0x00, 0x0d02, 0x01);
+
+	/* enable drop packet */
+	reg_w(gspca_dev, 0x00, 0x850a, 0x0001);
+
+	err = spca50x_setup_qtable(gspca_dev, 0x00, 0x8800, 0x8840,
+				 qtable_pocketdv);
+	if (err < 0)
+		gspca_err(gspca_dev, "spca50x_setup_qtable failed on init\n");
+
+	/* set qtable index */
+	reg_w(gspca_dev, 0x00, 0x8880, 2);
+	/* family cam Quicksmart stuff */
+	reg_w(gspca_dev, 0x00, 0x800a, 0x00);
+	/* Set agc transfer: synced between frames */
+	reg_w(gspca_dev, 0x00, 0x820f, 0x01);
+	/* Init SDRAM - needed for SDRAM access */
+	reg_w(gspca_dev, 0x00, 0x870a, 0x04);
+	/*Start init sequence or stream */
+	reg_w(gspca_dev, 0, 0x8003, 0x00);
+	/* switch to video camera mode */
+	reg_w(gspca_dev, 0x00, 0x8000, 0x0004);
+	msleep(2000);
+	if (reg_r_wait(gspca_dev, 0, 0x8000, 0x44) != 0) {
+		reg_r(gspca_dev, 0x816b, 1);
+		Data = gspca_dev->usb_buf[0];
+		reg_w(gspca_dev, 0x00, 0x816b, Data);
+	}
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam;
+
+	cam = &gspca_dev->cam;
+	sd->subtype = id->driver_info;
+	if (sd->subtype != LogitechClickSmart310) {
+		cam->cam_mode = vga_mode;
+		cam->nmodes = ARRAY_SIZE(vga_mode);
+	} else {
+		cam->cam_mode = sif_mode;
+		cam->nmodes = ARRAY_SIZE(sif_mode);
+	}
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	/* initialisation of spca500 based cameras is deferred */
+	gspca_dbg(gspca_dev, D_STREAM, "SPCA500 init\n");
+	if (sd->subtype == LogitechClickSmart310)
+		spca500_clksmart310_init(gspca_dev);
+/*	else
+		spca500_initialise(gspca_dev); */
+	gspca_dbg(gspca_dev, D_STREAM, "SPCA500 init done\n");
+	return 0;
+}
+
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int err;
+	__u8 Data;
+	__u8 xmult, ymult;
+
+	/* create the JPEG header */
+	jpeg_define(sd->jpeg_hdr, gspca_dev->pixfmt.height,
+			gspca_dev->pixfmt.width,
+			0x22);		/* JPEG 411 */
+	jpeg_set_qual(sd->jpeg_hdr, QUALITY);
+
+	if (sd->subtype == LogitechClickSmart310) {
+		xmult = 0x16;
+		ymult = 0x12;
+	} else {
+		xmult = 0x28;
+		ymult = 0x1e;
+	}
+
+	/* is there a sensor here ? */
+	reg_r(gspca_dev, 0x8a04, 1);
+	gspca_dbg(gspca_dev, D_STREAM, "Spca500 Sensor Address 0x%02x\n",
+		  gspca_dev->usb_buf[0]);
+	gspca_dbg(gspca_dev, D_STREAM, "Spca500 curr_mode: %d Xmult: 0x%02x, Ymult: 0x%02x",
+		  gspca_dev->curr_mode, xmult, ymult);
+
+	/* setup qtable */
+	switch (sd->subtype) {
+	case LogitechClickSmart310:
+		 spca500_setmode(gspca_dev, xmult, ymult);
+
+		/* enable drop packet */
+		reg_w(gspca_dev, 0x00, 0x850a, 0x0001);
+		reg_w(gspca_dev, 0x00, 0x8880, 3);
+		err = spca50x_setup_qtable(gspca_dev,
+					   0x00, 0x8800, 0x8840,
+					   qtable_creative_pccam);
+		if (err < 0)
+			gspca_err(gspca_dev, "spca50x_setup_qtable failed\n");
+		/* Init SDRAM - needed for SDRAM access */
+		reg_w(gspca_dev, 0x00, 0x870a, 0x04);
+
+		/* switch to video camera mode */
+		reg_w(gspca_dev, 0x00, 0x8000, 0x0004);
+		msleep(500);
+		if (reg_r_wait(gspca_dev, 0, 0x8000, 0x44) != 0)
+			gspca_err(gspca_dev, "reg_r_wait() failed\n");
+
+		reg_r(gspca_dev, 0x816b, 1);
+		Data = gspca_dev->usb_buf[0];
+		reg_w(gspca_dev, 0x00, 0x816b, Data);
+
+		spca500_synch310(gspca_dev);
+
+		write_vector(gspca_dev, spca500_visual_defaults);
+		spca500_setmode(gspca_dev, xmult, ymult);
+		/* enable drop packet */
+		err = reg_w(gspca_dev, 0x00, 0x850a, 0x0001);
+		if (err < 0)
+			gspca_err(gspca_dev, "failed to enable drop packet\n");
+		reg_w(gspca_dev, 0x00, 0x8880, 3);
+		err = spca50x_setup_qtable(gspca_dev,
+					   0x00, 0x8800, 0x8840,
+					   qtable_creative_pccam);
+		if (err < 0)
+			gspca_err(gspca_dev, "spca50x_setup_qtable failed\n");
+
+		/* Init SDRAM - needed for SDRAM access */
+		reg_w(gspca_dev, 0x00, 0x870a, 0x04);
+
+		/* switch to video camera mode */
+		reg_w(gspca_dev, 0x00, 0x8000, 0x0004);
+
+		if (reg_r_wait(gspca_dev, 0, 0x8000, 0x44) != 0)
+			gspca_err(gspca_dev, "reg_r_wait() failed\n");
+
+		reg_r(gspca_dev, 0x816b, 1);
+		Data = gspca_dev->usb_buf[0];
+		reg_w(gspca_dev, 0x00, 0x816b, Data);
+		break;
+	case CreativePCCam300:		/* Creative PC-CAM 300 640x480 CCD */
+	case IntelPocketPCCamera:	/* FIXME: Temporary fix for
+					 *	Intel Pocket PC Camera
+					 *	- NWG (Sat 29th March 2003) */
+
+		/* do a full reset */
+		err = spca500_full_reset(gspca_dev);
+		if (err < 0)
+			gspca_err(gspca_dev, "spca500_full_reset failed\n");
+
+		/* enable drop packet */
+		err = reg_w(gspca_dev, 0x00, 0x850a, 0x0001);
+		if (err < 0)
+			gspca_err(gspca_dev, "failed to enable drop packet\n");
+		reg_w(gspca_dev, 0x00, 0x8880, 3);
+		err = spca50x_setup_qtable(gspca_dev,
+					   0x00, 0x8800, 0x8840,
+					   qtable_creative_pccam);
+		if (err < 0)
+			gspca_err(gspca_dev, "spca50x_setup_qtable failed\n");
+
+		spca500_setmode(gspca_dev, xmult, ymult);
+		reg_w(gspca_dev, 0x20, 0x0001, 0x0004);
+
+		/* switch to video camera mode */
+		reg_w(gspca_dev, 0x00, 0x8000, 0x0004);
+
+		if (reg_r_wait(gspca_dev, 0, 0x8000, 0x44) != 0)
+			gspca_err(gspca_dev, "reg_r_wait() failed\n");
+
+		reg_r(gspca_dev, 0x816b, 1);
+		Data = gspca_dev->usb_buf[0];
+		reg_w(gspca_dev, 0x00, 0x816b, Data);
+
+/*		write_vector(gspca_dev, spca500_visual_defaults); */
+		break;
+	case KodakEZ200:		/* Kodak EZ200 */
+
+		/* do a full reset */
+		err = spca500_full_reset(gspca_dev);
+		if (err < 0)
+			gspca_err(gspca_dev, "spca500_full_reset failed\n");
+		/* enable drop packet */
+		reg_w(gspca_dev, 0x00, 0x850a, 0x0001);
+		reg_w(gspca_dev, 0x00, 0x8880, 0);
+		err = spca50x_setup_qtable(gspca_dev,
+					   0x00, 0x8800, 0x8840,
+					   qtable_kodak_ez200);
+		if (err < 0)
+			gspca_err(gspca_dev, "spca50x_setup_qtable failed\n");
+		spca500_setmode(gspca_dev, xmult, ymult);
+
+		reg_w(gspca_dev, 0x20, 0x0001, 0x0004);
+
+		/* switch to video camera mode */
+		reg_w(gspca_dev, 0x00, 0x8000, 0x0004);
+
+		if (reg_r_wait(gspca_dev, 0, 0x8000, 0x44) != 0)
+			gspca_err(gspca_dev, "reg_r_wait() failed\n");
+
+		reg_r(gspca_dev, 0x816b, 1);
+		Data = gspca_dev->usb_buf[0];
+		reg_w(gspca_dev, 0x00, 0x816b, Data);
+
+/*		write_vector(gspca_dev, spca500_visual_defaults); */
+		break;
+
+	case BenqDC1016:
+	case DLinkDSC350:		/* FamilyCam 300 */
+	case AiptekPocketDV:		/* Aiptek PocketDV */
+	case Gsmartmini:		/*Mustek Gsmart Mini */
+	case MustekGsmart300:		/* Mustek Gsmart 300 */
+	case PalmPixDC85:
+	case Optimedia:
+	case ToptroIndus:
+	case AgfaCl20:
+		spca500_reinit(gspca_dev);
+		reg_w(gspca_dev, 0x00, 0x0d01, 0x01);
+		/* enable drop packet */
+		reg_w(gspca_dev, 0x00, 0x850a, 0x0001);
+
+		err = spca50x_setup_qtable(gspca_dev,
+				   0x00, 0x8800, 0x8840, qtable_pocketdv);
+		if (err < 0)
+			gspca_err(gspca_dev, "spca50x_setup_qtable failed\n");
+		reg_w(gspca_dev, 0x00, 0x8880, 2);
+
+		/* familycam Quicksmart pocketDV stuff */
+		reg_w(gspca_dev, 0x00, 0x800a, 0x00);
+		/* Set agc transfer: synced between frames */
+		reg_w(gspca_dev, 0x00, 0x820f, 0x01);
+		/* Init SDRAM - needed for SDRAM access */
+		reg_w(gspca_dev, 0x00, 0x870a, 0x04);
+
+		spca500_setmode(gspca_dev, xmult, ymult);
+		/* switch to video camera mode */
+		reg_w(gspca_dev, 0x00, 0x8000, 0x0004);
+
+		reg_r_wait(gspca_dev, 0, 0x8000, 0x44);
+
+		reg_r(gspca_dev, 0x816b, 1);
+		Data = gspca_dev->usb_buf[0];
+		reg_w(gspca_dev, 0x00, 0x816b, Data);
+		break;
+	case LogitechTraveler:
+	case LogitechClickSmart510:
+		reg_w(gspca_dev, 0x02, 0x00, 0x00);
+		/* enable drop packet */
+		reg_w(gspca_dev, 0x00, 0x850a, 0x0001);
+
+		err = spca50x_setup_qtable(gspca_dev,
+					0x00, 0x8800,
+					0x8840, qtable_creative_pccam);
+		if (err < 0)
+			gspca_err(gspca_dev, "spca50x_setup_qtable failed\n");
+		reg_w(gspca_dev, 0x00, 0x8880, 3);
+		reg_w(gspca_dev, 0x00, 0x800a, 0x00);
+		/* Init SDRAM - needed for SDRAM access */
+		reg_w(gspca_dev, 0x00, 0x870a, 0x04);
+
+		spca500_setmode(gspca_dev, xmult, ymult);
+
+		/* switch to video camera mode */
+		reg_w(gspca_dev, 0x00, 0x8000, 0x0004);
+		reg_r_wait(gspca_dev, 0, 0x8000, 0x44);
+
+		reg_r(gspca_dev, 0x816b, 1);
+		Data = gspca_dev->usb_buf[0];
+		reg_w(gspca_dev, 0x00, 0x816b, Data);
+		write_vector(gspca_dev, Clicksmart510_defaults);
+		break;
+	}
+	return 0;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	reg_w(gspca_dev, 0, 0x8003, 0x00);
+
+	/* switch to video camera mode */
+	reg_w(gspca_dev, 0x00, 0x8000, 0x0004);
+	reg_r(gspca_dev, 0x8000, 1);
+	gspca_dbg(gspca_dev, D_STREAM, "stop SPCA500 done reg8000: 0x%2x\n",
+		  gspca_dev->usb_buf[0]);
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int i;
+	static __u8 ffd9[] = {0xff, 0xd9};
+
+/* frames are jpeg 4.1.1 without 0xff escape */
+	if (data[0] == 0xff) {
+		if (data[1] != 0x01) {	/* drop packet */
+/*			gspca_dev->last_packet_type = DISCARD_PACKET; */
+			return;
+		}
+		gspca_frame_add(gspca_dev, LAST_PACKET,
+					ffd9, 2);
+
+		/* put the JPEG header in the new frame */
+		gspca_frame_add(gspca_dev, FIRST_PACKET,
+			sd->jpeg_hdr, JPEG_HDR_SZ);
+
+		data += SPCA500_OFFSET_DATA;
+		len -= SPCA500_OFFSET_DATA;
+	} else {
+		data += 1;
+		len -= 1;
+	}
+
+	/* add 0x00 after 0xff */
+	i = 0;
+	do {
+		if (data[i] == 0xff) {
+			gspca_frame_add(gspca_dev, INTER_PACKET,
+					data, i + 1);
+			len -= i;
+			data += i;
+			*data = 0x00;
+			i = 0;
+		}
+		i++;
+	} while (i < len);
+	gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+}
+
+static void setbrightness(struct gspca_dev *gspca_dev, s32 val)
+{
+	reg_w(gspca_dev, 0x00, 0x8167,
+			(__u8) (val - 128));
+}
+
+static void setcontrast(struct gspca_dev *gspca_dev, s32 val)
+{
+	reg_w(gspca_dev, 0x00, 0x8168, val);
+}
+
+static void setcolors(struct gspca_dev *gspca_dev, s32 val)
+{
+	reg_w(gspca_dev, 0x00, 0x8169, val);
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		setbrightness(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_CONTRAST:
+		setcontrast(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_SATURATION:
+		setcolors(gspca_dev, ctrl->val);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 3);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_CONTRAST, 0, 63, 1, 31);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_SATURATION, 0, 63, 1, 31);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	return 0;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.pkt_scan = sd_pkt_scan,
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x040a, 0x0300), .driver_info = KodakEZ200},
+	{USB_DEVICE(0x041e, 0x400a), .driver_info = CreativePCCam300},
+	{USB_DEVICE(0x046d, 0x0890), .driver_info = LogitechTraveler},
+	{USB_DEVICE(0x046d, 0x0900), .driver_info = LogitechClickSmart310},
+	{USB_DEVICE(0x046d, 0x0901), .driver_info = LogitechClickSmart510},
+	{USB_DEVICE(0x04a5, 0x300c), .driver_info = BenqDC1016},
+	{USB_DEVICE(0x04fc, 0x7333), .driver_info = PalmPixDC85},
+	{USB_DEVICE(0x055f, 0xc200), .driver_info = MustekGsmart300},
+	{USB_DEVICE(0x055f, 0xc220), .driver_info = Gsmartmini},
+	{USB_DEVICE(0x06bd, 0x0404), .driver_info = AgfaCl20},
+	{USB_DEVICE(0x06be, 0x0800), .driver_info = Optimedia},
+	{USB_DEVICE(0x084d, 0x0003), .driver_info = DLinkDSC350},
+	{USB_DEVICE(0x08ca, 0x0103), .driver_info = AiptekPocketDV},
+	{USB_DEVICE(0x2899, 0x012c), .driver_info = ToptroIndus},
+	{USB_DEVICE(0x8086, 0x0630), .driver_info = IntelPocketPCCamera},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/spca501.c b/drivers/media/usb/gspca/spca501.c
new file mode 100644
index 0000000..2cce74b
--- /dev/null
+++ b/drivers/media/usb/gspca/spca501.c
@@ -0,0 +1,2046 @@
+/*
+ * SPCA501 chip based cameras initialization data
+ *
+ * V4L2 by Jean-Francois Moine <http://moinejf.free.fr>
+ *
+ * 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
+ * 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.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "spca501"
+
+#include "gspca.h"
+
+MODULE_AUTHOR("Michel Xhaard <mxhaard@users.sourceforge.net>");
+MODULE_DESCRIPTION("GSPCA/SPCA501 USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+
+	unsigned short contrast;
+	__u8 brightness;
+	__u8 colors;
+	__u8 blue_balance;
+	__u8 red_balance;
+
+	char subtype;
+#define Arowana300KCMOSCamera 0
+#define IntelCreateAndShare 1
+#define KodakDVC325 2
+#define MystFromOriUnknownCamera 3
+#define SmileIntlCamera 4
+#define ThreeComHomeConnectLite 5
+#define ViewQuestM318B 6
+};
+
+static const struct v4l2_pix_format vga_mode[] = {
+	{160, 120, V4L2_PIX_FMT_SPCA501, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 160 * 120 * 3 / 2,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 2},
+	{320, 240, V4L2_PIX_FMT_SPCA501, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3 / 2,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1},
+	{640, 480, V4L2_PIX_FMT_SPCA501, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 3 / 2,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+};
+
+#define SPCA50X_REG_USB 0x2	/* spca505 501 */
+/*
+ * Data to initialize a SPCA501. From a capture file provided by Bill Roehl
+ * With SPCA501 chip description
+ */
+#define CCDSP_SET		/* set CCDSP parameters */
+#define TG_SET			/* set time generator set */
+#undef DSPWIN_SET		/* set DSP windows parameters */
+#undef ALTER_GAMA	/* Set alternate set to YUV transform coeffs. */
+#define SPCA501_SNAPBIT 0x80
+#define SPCA501_SNAPCTRL 0x10
+/* Frame packet header offsets for the spca501 */
+#define SPCA501_OFFSET_GPIO   1
+#define SPCA501_OFFSET_TYPE   2
+#define SPCA501_OFFSET_TURN3A 3
+#define SPCA501_OFFSET_FRAMSEQ 4
+#define SPCA501_OFFSET_COMPRESS 5
+#define SPCA501_OFFSET_QUANT 6
+#define SPCA501_OFFSET_QUANT2 7
+#define SPCA501_OFFSET_DATA 8
+
+#define SPCA501_PROP_COMP_ENABLE(d) ((d) & 1)
+#define SPCA501_PROP_SNAP(d) ((d) & 0x40)
+#define SPCA501_PROP_SNAP_CTRL(d) ((d) & 0x10)
+#define SPCA501_PROP_COMP_THRESH(d) (((d) & 0x0e) >> 1)
+#define SPCA501_PROP_COMP_QUANT(d) (((d) & 0x70) >> 4)
+
+/* SPCA501 CCDSP control */
+#define SPCA501_REG_CCDSP 0x01
+/* SPCA501 control/status registers */
+#define SPCA501_REG_CTLRL 0x02
+
+/* registers for color correction and YUV transformation */
+#define SPCA501_A11 0x08
+#define SPCA501_A12 0x09
+#define SPCA501_A13 0x0A
+#define SPCA501_A21 0x0B
+#define SPCA501_A22 0x0C
+#define SPCA501_A23 0x0D
+#define SPCA501_A31 0x0E
+#define SPCA501_A32 0x0F
+#define SPCA501_A33 0x10
+
+/* Data for video camera initialization before capturing */
+static const __u16 spca501_open_data[][3] = {
+	/* bmRequest,value,index */
+
+	{0x2, 0x50, 0x00},	/* C/S enable soft reset */
+	{0x2, 0x40, 0x00},	/* C/S disable soft reset */
+	{0x2, 0x02, 0x05},	/* C/S general purpose I/O data */
+	{0x2, 0x03, 0x05},	/* C/S general purpose I/O data */
+
+#ifdef CCDSP_SET
+	{0x1, 0x38, 0x01},	/* CCDSP options */
+	{0x1, 0x05, 0x02}, /* CCDSP Optical black level for user settings */
+	{0x1, 0xC0, 0x03},	/* CCDSP Optical black settings */
+
+	{0x1, 0x67, 0x07},
+	{0x1, 0x63, 0x3f},	/* CCDSP CCD gamma enable */
+	{0x1, 0x03, 0x56},	/* Add gamma correction */
+
+	{0x1, 0xFF, 0x15},	/* CCDSP High luminance for white balance */
+	{0x1, 0x01, 0x16},	/* CCDSP Low luminance for white balance */
+
+/* Color correction and RGB-to-YUV transformation coefficients changing */
+#ifdef ALTER_GAMA
+	{0x0, 0x00, 0x08},	/* A11 */
+	{0x0, 0x00, 0x09},	/* A12 */
+	{0x0, 0x90, 0x0A},	/* A13 */
+	{0x0, 0x12, 0x0B},	/* A21 */
+	{0x0, 0x00, 0x0C},	/* A22 */
+	{0x0, 0x00, 0x0D},	/* A23 */
+	{0x0, 0x00, 0x0E},	/* A31 */
+	{0x0, 0x02, 0x0F},	/* A32 */
+	{0x0, 0x00, 0x10},	/* A33 */
+#else
+	{0x1, 0x2a, 0x08},	/* A11 0x31 */
+	{0x1, 0xf8, 0x09},	/* A12 f8 */
+	{0x1, 0xf8, 0x0A},	/* A13 f8 */
+	{0x1, 0xf8, 0x0B},	/* A21 f8 */
+	{0x1, 0x14, 0x0C},	/* A22 0x14 */
+	{0x1, 0xf8, 0x0D},	/* A23 f8 */
+	{0x1, 0xf8, 0x0E},	/* A31 f8 */
+	{0x1, 0xf8, 0x0F},	/* A32 f8 */
+	{0x1, 0x20, 0x10},	/* A33 0x20 */
+#endif
+	{0x1, 0x00, 0x11},	/* R offset */
+	{0x1, 0x00, 0x12},	/* G offset */
+	{0x1, 0x00, 0x13},	/* B offset */
+	{0x1, 0x00, 0x14},	/* GB offset */
+
+#endif
+
+#ifdef TG_SET
+	/* Time generator manipulations */
+	{0x0, 0xfc, 0x0},	/* Set up high bits of shutter speed */
+	{0x0, 0x01, 0x1},	/* Set up low bits of shutter speed */
+
+	{0x0, 0xe4, 0x04},	/* DCLK*2 clock phase adjustment */
+	{0x0, 0x08, 0x05},	/* ADCK phase adjustment, inv. ext. VB */
+	{0x0, 0x03, 0x06},	/* FR phase adjustment */
+	{0x0, 0x01, 0x07},	/* FCDS phase adjustment */
+	{0x0, 0x39, 0x08},	/* FS phase adjustment */
+	{0x0, 0x88, 0x0a},	/* FH1 phase and delay adjustment */
+	{0x0, 0x03, 0x0f},	/* pixel identification */
+	{0x0, 0x00, 0x11},	/* clock source selection (default) */
+
+	/*VERY strange manipulations with
+	 * select DMCLP or OBPX to be ADCLP output (0x0C)
+	 * OPB always toggle or not (0x0D) but they allow
+	 * us to set up brightness
+	 */
+	{0x0, 0x01, 0x0c},
+	{0x0, 0xe0, 0x0d},
+	/* Done */
+#endif
+
+#ifdef DSPWIN_SET
+	{0x1, 0xa0, 0x01},	/* Setting image processing parameters */
+	{0x1, 0x1c, 0x17},	/* Changing Windows positions X1 */
+	{0x1, 0xe2, 0x19},	/* X2 */
+	{0x1, 0x1c, 0x1b},	/* X3 */
+	{0x1, 0xe2, 0x1d},	/* X4 */
+	{0x1, 0x5f, 0x1f},	/* X5 */
+	{0x1, 0x32, 0x20},	/* Y5 */
+	{0x1, 0x01, 0x10},	/* Changing A33 */
+#endif
+
+	{0x2, 0x204a, 0x07},/* Setting video compression & resolution 160x120 */
+	{0x2, 0x94, 0x06},	/* Setting video no compression */
+	{}
+};
+
+/*
+   The SPCAxxx docs from Sunplus document these values
+   in tables, one table per register number.  In the data
+   below, dmRequest is the register number, index is the Addr,
+   and value is a combination of Bit values.
+   Bit  Value (hex)
+   0    01
+   1    02
+   2    04
+   3    08
+   4    10
+   5    20
+   6    40
+   7    80
+ */
+
+/* Data for chip initialization (set default values) */
+static const __u16 spca501_init_data[][3] = {
+	/* Set all the values to powerup defaults */
+	/* bmRequest,value,index */
+	{0x0, 0xAA, 0x00},
+	{0x0, 0x02, 0x01},
+	{0x0, 0x01, 0x02},
+	{0x0, 0x02, 0x03},
+	{0x0, 0xCE, 0x04},
+	{0x0, 0x00, 0x05},
+	{0x0, 0x00, 0x06},
+	{0x0, 0x00, 0x07},
+	{0x0, 0x00, 0x08},
+	{0x0, 0x00, 0x09},
+	{0x0, 0x90, 0x0A},
+	{0x0, 0x12, 0x0B},
+	{0x0, 0x00, 0x0C},
+	{0x0, 0x00, 0x0D},
+	{0x0, 0x00, 0x0E},
+	{0x0, 0x02, 0x0F},
+	{0x0, 0x00, 0x10},
+	{0x0, 0x00, 0x11},
+	{0x0, 0x00, 0x12},
+	{0x0, 0x00, 0x13},
+	{0x0, 0x00, 0x14},
+	{0x0, 0x00, 0x15},
+	{0x0, 0x00, 0x16},
+	{0x0, 0x00, 0x17},
+	{0x0, 0x00, 0x18},
+	{0x0, 0x00, 0x19},
+	{0x0, 0x00, 0x1A},
+	{0x0, 0x00, 0x1B},
+	{0x0, 0x00, 0x1C},
+	{0x0, 0x00, 0x1D},
+	{0x0, 0x00, 0x1E},
+	{0x0, 0x00, 0x1F},
+	{0x0, 0x00, 0x20},
+	{0x0, 0x00, 0x21},
+	{0x0, 0x00, 0x22},
+	{0x0, 0x00, 0x23},
+	{0x0, 0x00, 0x24},
+	{0x0, 0x00, 0x25},
+	{0x0, 0x00, 0x26},
+	{0x0, 0x00, 0x27},
+	{0x0, 0x00, 0x28},
+	{0x0, 0x00, 0x29},
+	{0x0, 0x00, 0x2A},
+	{0x0, 0x00, 0x2B},
+	{0x0, 0x00, 0x2C},
+	{0x0, 0x00, 0x2D},
+	{0x0, 0x00, 0x2E},
+	{0x0, 0x00, 0x2F},
+	{0x0, 0x00, 0x30},
+	{0x0, 0x00, 0x31},
+	{0x0, 0x00, 0x32},
+	{0x0, 0x00, 0x33},
+	{0x0, 0x00, 0x34},
+	{0x0, 0x00, 0x35},
+	{0x0, 0x00, 0x36},
+	{0x0, 0x00, 0x37},
+	{0x0, 0x00, 0x38},
+	{0x0, 0x00, 0x39},
+	{0x0, 0x00, 0x3A},
+	{0x0, 0x00, 0x3B},
+	{0x0, 0x00, 0x3C},
+	{0x0, 0x00, 0x3D},
+	{0x0, 0x00, 0x3E},
+	{0x0, 0x00, 0x3F},
+	{0x0, 0x00, 0x40},
+	{0x0, 0x00, 0x41},
+	{0x0, 0x00, 0x42},
+	{0x0, 0x00, 0x43},
+	{0x0, 0x00, 0x44},
+	{0x0, 0x00, 0x45},
+	{0x0, 0x00, 0x46},
+	{0x0, 0x00, 0x47},
+	{0x0, 0x00, 0x48},
+	{0x0, 0x00, 0x49},
+	{0x0, 0x00, 0x4A},
+	{0x0, 0x00, 0x4B},
+	{0x0, 0x00, 0x4C},
+	{0x0, 0x00, 0x4D},
+	{0x0, 0x00, 0x4E},
+	{0x0, 0x00, 0x4F},
+	{0x0, 0x00, 0x50},
+	{0x0, 0x00, 0x51},
+	{0x0, 0x00, 0x52},
+	{0x0, 0x00, 0x53},
+	{0x0, 0x00, 0x54},
+	{0x0, 0x00, 0x55},
+	{0x0, 0x00, 0x56},
+	{0x0, 0x00, 0x57},
+	{0x0, 0x00, 0x58},
+	{0x0, 0x00, 0x59},
+	{0x0, 0x00, 0x5A},
+	{0x0, 0x00, 0x5B},
+	{0x0, 0x00, 0x5C},
+	{0x0, 0x00, 0x5D},
+	{0x0, 0x00, 0x5E},
+	{0x0, 0x00, 0x5F},
+	{0x0, 0x00, 0x60},
+	{0x0, 0x00, 0x61},
+	{0x0, 0x00, 0x62},
+	{0x0, 0x00, 0x63},
+	{0x0, 0x00, 0x64},
+	{0x0, 0x00, 0x65},
+	{0x0, 0x00, 0x66},
+	{0x0, 0x00, 0x67},
+	{0x0, 0x00, 0x68},
+	{0x0, 0x00, 0x69},
+	{0x0, 0x00, 0x6A},
+	{0x0, 0x00, 0x6B},
+	{0x0, 0x00, 0x6C},
+	{0x0, 0x00, 0x6D},
+	{0x0, 0x00, 0x6E},
+	{0x0, 0x00, 0x6F},
+	{0x0, 0x00, 0x70},
+	{0x0, 0x00, 0x71},
+	{0x0, 0x00, 0x72},
+	{0x0, 0x00, 0x73},
+	{0x0, 0x00, 0x74},
+	{0x0, 0x00, 0x75},
+	{0x0, 0x00, 0x76},
+	{0x0, 0x00, 0x77},
+	{0x0, 0x00, 0x78},
+	{0x0, 0x00, 0x79},
+	{0x0, 0x00, 0x7A},
+	{0x0, 0x00, 0x7B},
+	{0x0, 0x00, 0x7C},
+	{0x0, 0x00, 0x7D},
+	{0x0, 0x00, 0x7E},
+	{0x0, 0x00, 0x7F},
+	{0x0, 0x00, 0x80},
+	{0x0, 0x00, 0x81},
+	{0x0, 0x00, 0x82},
+	{0x0, 0x00, 0x83},
+	{0x0, 0x00, 0x84},
+	{0x0, 0x00, 0x85},
+	{0x0, 0x00, 0x86},
+	{0x0, 0x00, 0x87},
+	{0x0, 0x00, 0x88},
+	{0x0, 0x00, 0x89},
+	{0x0, 0x00, 0x8A},
+	{0x0, 0x00, 0x8B},
+	{0x0, 0x00, 0x8C},
+	{0x0, 0x00, 0x8D},
+	{0x0, 0x00, 0x8E},
+	{0x0, 0x00, 0x8F},
+	{0x0, 0x00, 0x90},
+	{0x0, 0x00, 0x91},
+	{0x0, 0x00, 0x92},
+	{0x0, 0x00, 0x93},
+	{0x0, 0x00, 0x94},
+	{0x0, 0x00, 0x95},
+	{0x0, 0x00, 0x96},
+	{0x0, 0x00, 0x97},
+	{0x0, 0x00, 0x98},
+	{0x0, 0x00, 0x99},
+	{0x0, 0x00, 0x9A},
+	{0x0, 0x00, 0x9B},
+	{0x0, 0x00, 0x9C},
+	{0x0, 0x00, 0x9D},
+	{0x0, 0x00, 0x9E},
+	{0x0, 0x00, 0x9F},
+	{0x0, 0x00, 0xA0},
+	{0x0, 0x00, 0xA1},
+	{0x0, 0x00, 0xA2},
+	{0x0, 0x00, 0xA3},
+	{0x0, 0x00, 0xA4},
+	{0x0, 0x00, 0xA5},
+	{0x0, 0x00, 0xA6},
+	{0x0, 0x00, 0xA7},
+	{0x0, 0x00, 0xA8},
+	{0x0, 0x00, 0xA9},
+	{0x0, 0x00, 0xAA},
+	{0x0, 0x00, 0xAB},
+	{0x0, 0x00, 0xAC},
+	{0x0, 0x00, 0xAD},
+	{0x0, 0x00, 0xAE},
+	{0x0, 0x00, 0xAF},
+	{0x0, 0x00, 0xB0},
+	{0x0, 0x00, 0xB1},
+	{0x0, 0x00, 0xB2},
+	{0x0, 0x00, 0xB3},
+	{0x0, 0x00, 0xB4},
+	{0x0, 0x00, 0xB5},
+	{0x0, 0x00, 0xB6},
+	{0x0, 0x00, 0xB7},
+	{0x0, 0x00, 0xB8},
+	{0x0, 0x00, 0xB9},
+	{0x0, 0x00, 0xBA},
+	{0x0, 0x00, 0xBB},
+	{0x0, 0x00, 0xBC},
+	{0x0, 0x00, 0xBD},
+	{0x0, 0x00, 0xBE},
+	{0x0, 0x00, 0xBF},
+	{0x0, 0x00, 0xC0},
+	{0x0, 0x00, 0xC1},
+	{0x0, 0x00, 0xC2},
+	{0x0, 0x00, 0xC3},
+	{0x0, 0x00, 0xC4},
+	{0x0, 0x00, 0xC5},
+	{0x0, 0x00, 0xC6},
+	{0x0, 0x00, 0xC7},
+	{0x0, 0x00, 0xC8},
+	{0x0, 0x00, 0xC9},
+	{0x0, 0x00, 0xCA},
+	{0x0, 0x00, 0xCB},
+	{0x0, 0x00, 0xCC},
+	{0x1, 0xF4, 0x00},
+	{0x1, 0x38, 0x01},
+	{0x1, 0x40, 0x02},
+	{0x1, 0x0A, 0x03},
+	{0x1, 0x40, 0x04},
+	{0x1, 0x40, 0x05},
+	{0x1, 0x40, 0x06},
+	{0x1, 0x67, 0x07},
+	{0x1, 0x31, 0x08},
+	{0x1, 0x00, 0x09},
+	{0x1, 0x00, 0x0A},
+	{0x1, 0x00, 0x0B},
+	{0x1, 0x14, 0x0C},
+	{0x1, 0x00, 0x0D},
+	{0x1, 0x00, 0x0E},
+	{0x1, 0x00, 0x0F},
+	{0x1, 0x1E, 0x10},
+	{0x1, 0x00, 0x11},
+	{0x1, 0x00, 0x12},
+	{0x1, 0x00, 0x13},
+	{0x1, 0x00, 0x14},
+	{0x1, 0xFF, 0x15},
+	{0x1, 0x01, 0x16},
+	{0x1, 0x32, 0x17},
+	{0x1, 0x23, 0x18},
+	{0x1, 0xCE, 0x19},
+	{0x1, 0x23, 0x1A},
+	{0x1, 0x32, 0x1B},
+	{0x1, 0x8D, 0x1C},
+	{0x1, 0xCE, 0x1D},
+	{0x1, 0x8D, 0x1E},
+	{0x1, 0x00, 0x1F},
+	{0x1, 0x00, 0x20},
+	{0x1, 0xFF, 0x3E},
+	{0x1, 0x02, 0x3F},
+	{0x1, 0x00, 0x40},
+	{0x1, 0x00, 0x41},
+	{0x1, 0x00, 0x42},
+	{0x1, 0x00, 0x43},
+	{0x1, 0x00, 0x44},
+	{0x1, 0x00, 0x45},
+	{0x1, 0x00, 0x46},
+	{0x1, 0x00, 0x47},
+	{0x1, 0x00, 0x48},
+	{0x1, 0x00, 0x49},
+	{0x1, 0x00, 0x4A},
+	{0x1, 0x00, 0x4B},
+	{0x1, 0x00, 0x4C},
+	{0x1, 0x00, 0x4D},
+	{0x1, 0x00, 0x4E},
+	{0x1, 0x00, 0x4F},
+	{0x1, 0x00, 0x50},
+	{0x1, 0x00, 0x51},
+	{0x1, 0x00, 0x52},
+	{0x1, 0x00, 0x53},
+	{0x1, 0x00, 0x54},
+	{0x1, 0x00, 0x55},
+	{0x1, 0x00, 0x56},
+	{0x1, 0x00, 0x57},
+	{0x1, 0x00, 0x58},
+	{0x1, 0x00, 0x59},
+	{0x1, 0x00, 0x5A},
+	{0x2, 0x03, 0x00},
+	{0x2, 0x00, 0x01},
+	{0x2, 0x00, 0x05},
+	{0x2, 0x00, 0x06},
+	{0x2, 0x00, 0x07},
+	{0x2, 0x00, 0x10},
+	{0x2, 0x00, 0x11},
+	/* Strange - looks like the 501 driver doesn't do anything
+	 * at insert time except read the EEPROM
+	 */
+	{}
+};
+
+/* Data for video camera init before capture.
+ * Capture and decoding by Colin Peart.
+ * This is is for the 3com HomeConnect Lite which is spca501a based.
+ */
+static const __u16 spca501_3com_open_data[][3] = {
+	/* bmRequest,value,index */
+	{0x2, 0x0050, 0x0000},	/* C/S Enable TG soft reset, timing mode=010 */
+	{0x2, 0x0043, 0x0000},	/* C/S Disable TG soft reset, timing mode=010 */
+	{0x2, 0x0002, 0x0005},	/* C/S GPIO */
+	{0x2, 0x0003, 0x0005},	/* C/S GPIO */
+
+#ifdef CCDSP_SET
+	{0x1, 0x0020, 0x0001},	/* CCDSP Options */
+
+	{0x1, 0x0020, 0x0002},	/* CCDSP Black Level */
+	{0x1, 0x006e, 0x0007},	/* CCDSP Gamma options */
+	{0x1, 0x0090, 0x0015},	/* CCDSP Luminance Low */
+	{0x1, 0x00ff, 0x0016},	/* CCDSP Luminance High */
+	{0x1, 0x0003, 0x003F},	/* CCDSP Gamma correction toggle */
+
+#ifdef ALTER_GAMMA
+	{0x1, 0x0010, 0x0008},	/* CCDSP YUV A11 */
+	{0x1, 0x0000, 0x0009},	/* CCDSP YUV A12 */
+	{0x1, 0x0000, 0x000a},	/* CCDSP YUV A13 */
+	{0x1, 0x0000, 0x000b},	/* CCDSP YUV A21 */
+	{0x1, 0x0010, 0x000c},	/* CCDSP YUV A22 */
+	{0x1, 0x0000, 0x000d},	/* CCDSP YUV A23 */
+	{0x1, 0x0000, 0x000e},	/* CCDSP YUV A31 */
+	{0x1, 0x0000, 0x000f},	/* CCDSP YUV A32 */
+	{0x1, 0x0010, 0x0010},	/* CCDSP YUV A33 */
+	{0x1, 0x0000, 0x0011},	/* CCDSP R Offset */
+	{0x1, 0x0000, 0x0012},	/* CCDSP G Offset */
+	{0x1, 0x0001, 0x0013},	/* CCDSP B Offset */
+	{0x1, 0x0001, 0x0014},	/* CCDSP BG Offset */
+	{0x1, 0x003f, 0x00C1},	/* CCDSP Gamma Correction Enable */
+#endif
+#endif
+
+#ifdef TG_SET
+	{0x0, 0x00fc, 0x0000},	/* TG Shutter Speed High Bits */
+	{0x0, 0x0000, 0x0001},	/* TG Shutter Speed Low Bits */
+	{0x0, 0x00e4, 0x0004},	/* TG DCLK*2 Adjust */
+	{0x0, 0x0008, 0x0005},	/* TG ADCK Adjust */
+	{0x0, 0x0003, 0x0006},	/* TG FR Phase Adjust */
+	{0x0, 0x0001, 0x0007},	/* TG FCDS Phase Adjust */
+	{0x0, 0x0039, 0x0008},	/* TG FS Phase Adjust */
+	{0x0, 0x0088, 0x000a},	/* TG MH1 */
+	{0x0, 0x0003, 0x000f},	/* TG Pixel ID */
+
+	/* Like below, unexplained toglleing */
+	{0x0, 0x0080, 0x000c},
+	{0x0, 0x0000, 0x000d},
+	{0x0, 0x0080, 0x000c},
+	{0x0, 0x0004, 0x000d},
+	{0x0, 0x0000, 0x000c},
+	{0x0, 0x0000, 0x000d},
+	{0x0, 0x0040, 0x000c},
+	{0x0, 0x0017, 0x000d},
+	{0x0, 0x00c0, 0x000c},
+	{0x0, 0x0000, 0x000d},
+	{0x0, 0x0080, 0x000c},
+	{0x0, 0x0006, 0x000d},
+	{0x0, 0x0080, 0x000c},
+	{0x0, 0x0004, 0x000d},
+	{0x0, 0x0002, 0x0003},
+#endif
+
+#ifdef DSPWIN_SET
+	{0x1, 0x001c, 0x0017},	/* CCDSP W1 Start X */
+	{0x1, 0x00e2, 0x0019},	/* CCDSP W2 Start X */
+	{0x1, 0x001c, 0x001b},	/* CCDSP W3 Start X */
+	{0x1, 0x00e2, 0x001d},	/* CCDSP W4 Start X */
+	{0x1, 0x00aa, 0x001f},	/* CCDSP W5 Start X */
+	{0x1, 0x0070, 0x0020},	/* CCDSP W5 Start Y */
+#endif
+	{0x0, 0x0001, 0x0010},	/* TG Start Clock */
+
+/*	{0x2, 0x006a, 0x0001},	 * C/S Enable ISOSYNCH Packet Engine */
+	{0x2, 0x0068, 0x0001},	/* C/S Diable ISOSYNCH Packet Engine */
+	{0x2, 0x0000, 0x0005},
+	{0x2, 0x0043, 0x0000},	/* C/S Set Timing Mode, Disable TG soft reset */
+	{0x2, 0x0043, 0x0000},	/* C/S Set Timing Mode, Disable TG soft reset */
+	{0x2, 0x0002, 0x0005},	/* C/S GPIO */
+	{0x2, 0x0003, 0x0005},	/* C/S GPIO */
+
+	{0x2, 0x006a, 0x0001},	/* C/S Enable ISOSYNCH Packet Engine */
+	{}
+};
+
+/*
+ * Data used to initialize a SPCA501C with HV7131B sensor.
+ * From a capture file taken with USBSnoop v 1.5
+ * I have a "SPCA501C pc camera chipset" manual by sunplus, but some
+ * of the value meanings are obscure or simply "reserved".
+ * to do list:
+ * 1) Understand what every value means
+ * 2) Understand why some values seem to appear more than once
+ * 3) Write a small comment for each line of the following arrays.
+ */
+static const __u16 spca501c_arowana_open_data[][3] = {
+	/* bmRequest,value,index */
+	{0x02, 0x0007, 0x0005},
+	{0x02, 0xa048, 0x0000},
+	{0x05, 0x0022, 0x0004},
+	{0x01, 0x0006, 0x0011},
+	{0x01, 0x00ff, 0x0012},
+	{0x01, 0x0014, 0x0013},
+	{0x01, 0x0000, 0x0014},
+	{0x01, 0x0042, 0x0051},
+	{0x01, 0x0040, 0x0052},
+	{0x01, 0x0051, 0x0053},
+	{0x01, 0x0040, 0x0054},
+	{0x01, 0x0000, 0x0055},
+	{0x00, 0x0025, 0x0000},
+	{0x00, 0x0026, 0x0000},
+	{0x00, 0x0001, 0x0000},
+	{0x00, 0x0027, 0x0000},
+	{0x00, 0x008a, 0x0000},
+	{}
+};
+
+static const __u16 spca501c_arowana_init_data[][3] = {
+	/* bmRequest,value,index */
+	{0x02, 0x0007, 0x0005},
+	{0x02, 0xa048, 0x0000},
+	{0x05, 0x0022, 0x0004},
+	{0x01, 0x0006, 0x0011},
+	{0x01, 0x00ff, 0x0012},
+	{0x01, 0x0014, 0x0013},
+	{0x01, 0x0000, 0x0014},
+	{0x01, 0x0042, 0x0051},
+	{0x01, 0x0040, 0x0052},
+	{0x01, 0x0051, 0x0053},
+	{0x01, 0x0040, 0x0054},
+	{0x01, 0x0000, 0x0055},
+	{0x00, 0x0025, 0x0000},
+	{0x00, 0x0026, 0x0000},
+	{0x00, 0x0001, 0x0000},
+	{0x00, 0x0027, 0x0000},
+	{0x00, 0x008a, 0x0000},
+	{0x02, 0x0000, 0x0005},
+	{0x02, 0x0007, 0x0005},
+	{0x02, 0x2000, 0x0000},
+	{0x05, 0x0022, 0x0004},
+	{0x05, 0x0015, 0x0001},
+	{0x05, 0x00ea, 0x0000},
+	{0x05, 0x0021, 0x0001},
+	{0x05, 0x00d2, 0x0000},
+	{0x05, 0x0023, 0x0001},
+	{0x05, 0x0003, 0x0000},
+	{0x05, 0x0030, 0x0001},
+	{0x05, 0x002b, 0x0000},
+	{0x05, 0x0031, 0x0001},
+	{0x05, 0x0023, 0x0000},
+	{0x05, 0x0032, 0x0001},
+	{0x05, 0x0023, 0x0000},
+	{0x05, 0x0033, 0x0001},
+	{0x05, 0x0023, 0x0000},
+	{0x05, 0x0034, 0x0001},
+	{0x05, 0x0002, 0x0000},
+	{0x05, 0x0050, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0051, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0052, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0054, 0x0001},
+	{0x05, 0x0001, 0x0000},
+	{0x00, 0x0000, 0x0001},
+	{0x00, 0x0000, 0x0002},
+	{0x00, 0x000c, 0x0003},
+	{0x00, 0x0000, 0x0004},
+	{0x00, 0x0090, 0x0005},
+	{0x00, 0x0000, 0x0006},
+	{0x00, 0x0040, 0x0007},
+	{0x00, 0x00c0, 0x0008},
+	{0x00, 0x004a, 0x0009},
+	{0x00, 0x0000, 0x000a},
+	{0x00, 0x0000, 0x000b},
+	{0x00, 0x0001, 0x000c},
+	{0x00, 0x0001, 0x000d},
+	{0x00, 0x0000, 0x000e},
+	{0x00, 0x0002, 0x000f},
+	{0x00, 0x0001, 0x0010},
+	{0x00, 0x0000, 0x0011},
+	{0x00, 0x0000, 0x0012},
+	{0x00, 0x0002, 0x0020},
+	{0x00, 0x0080, 0x0021},
+	{0x00, 0x0001, 0x0022},
+	{0x00, 0x00e0, 0x0023},
+	{0x00, 0x0000, 0x0024},
+	{0x00, 0x00d5, 0x0025},
+	{0x00, 0x0000, 0x0026},
+	{0x00, 0x000b, 0x0027},
+	{0x00, 0x0000, 0x0046},
+	{0x00, 0x0000, 0x0047},
+	{0x00, 0x0000, 0x0048},
+	{0x00, 0x0000, 0x0049},
+	{0x00, 0x0008, 0x004a},
+	{0xff, 0x0000, 0x00d0},
+	{0xff, 0x00d8, 0x00d1},
+	{0xff, 0x0000, 0x00d4},
+	{0xff, 0x0000, 0x00d5},
+	{0x01, 0x00a6, 0x0000},
+	{0x01, 0x0028, 0x0001},
+	{0x01, 0x0000, 0x0002},
+	{0x01, 0x000a, 0x0003},
+	{0x01, 0x0040, 0x0004},
+	{0x01, 0x0066, 0x0007},
+	{0x01, 0x0011, 0x0008},
+	{0x01, 0x0032, 0x0009},
+	{0x01, 0x00fd, 0x000a},
+	{0x01, 0x0038, 0x000b},
+	{0x01, 0x00d1, 0x000c},
+	{0x01, 0x00f7, 0x000d},
+	{0x01, 0x00ed, 0x000e},
+	{0x01, 0x00d8, 0x000f},
+	{0x01, 0x0038, 0x0010},
+	{0x01, 0x00ff, 0x0015},
+	{0x01, 0x0001, 0x0016},
+	{0x01, 0x0032, 0x0017},
+	{0x01, 0x0023, 0x0018},
+	{0x01, 0x00ce, 0x0019},
+	{0x01, 0x0023, 0x001a},
+	{0x01, 0x0032, 0x001b},
+	{0x01, 0x008d, 0x001c},
+	{0x01, 0x00ce, 0x001d},
+	{0x01, 0x008d, 0x001e},
+	{0x01, 0x0000, 0x001f},
+	{0x01, 0x0000, 0x0020},
+	{0x01, 0x00ff, 0x003e},
+	{0x01, 0x0003, 0x003f},
+	{0x01, 0x0000, 0x0040},
+	{0x01, 0x0035, 0x0041},
+	{0x01, 0x0053, 0x0042},
+	{0x01, 0x0069, 0x0043},
+	{0x01, 0x007c, 0x0044},
+	{0x01, 0x008c, 0x0045},
+	{0x01, 0x009a, 0x0046},
+	{0x01, 0x00a8, 0x0047},
+	{0x01, 0x00b4, 0x0048},
+	{0x01, 0x00bf, 0x0049},
+	{0x01, 0x00ca, 0x004a},
+	{0x01, 0x00d4, 0x004b},
+	{0x01, 0x00dd, 0x004c},
+	{0x01, 0x00e7, 0x004d},
+	{0x01, 0x00ef, 0x004e},
+	{0x01, 0x00f8, 0x004f},
+	{0x01, 0x00ff, 0x0050},
+	{0x01, 0x0001, 0x0056},
+	{0x01, 0x0060, 0x0057},
+	{0x01, 0x0040, 0x0058},
+	{0x01, 0x0011, 0x0059},
+	{0x01, 0x0001, 0x005a},
+	{0x02, 0x0007, 0x0005},
+	{0x02, 0xa048, 0x0000},
+	{0x02, 0x0007, 0x0005},
+	{0x02, 0x0015, 0x0006},
+	{0x02, 0x100a, 0x0007},
+	{0x02, 0xa048, 0x0000},
+	{0x02, 0xc002, 0x0001},
+	{0x02, 0x000f, 0x0005},
+	{0x02, 0xa048, 0x0000},
+	{0x05, 0x0022, 0x0004},
+	{0x05, 0x0025, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0026, 0x0001},
+	{0x05, 0x0001, 0x0000},
+	{0x05, 0x0027, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0001, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0021, 0x0001},
+	{0x05, 0x00d2, 0x0000},
+	{0x05, 0x0020, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x00, 0x0090, 0x0005},
+	{0x01, 0x00a6, 0x0000},
+	{0x02, 0x0007, 0x0005},
+	{0x02, 0x2000, 0x0000},
+	{0x05, 0x0022, 0x0004},
+	{0x05, 0x0015, 0x0001},
+	{0x05, 0x00ea, 0x0000},
+	{0x05, 0x0021, 0x0001},
+	{0x05, 0x00d2, 0x0000},
+	{0x05, 0x0023, 0x0001},
+	{0x05, 0x0003, 0x0000},
+	{0x05, 0x0030, 0x0001},
+	{0x05, 0x002b, 0x0000},
+	{0x05, 0x0031, 0x0001},
+	{0x05, 0x0023, 0x0000},
+	{0x05, 0x0032, 0x0001},
+	{0x05, 0x0023, 0x0000},
+	{0x05, 0x0033, 0x0001},
+	{0x05, 0x0023, 0x0000},
+	{0x05, 0x0034, 0x0001},
+	{0x05, 0x0002, 0x0000},
+	{0x05, 0x0050, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0051, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0052, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0054, 0x0001},
+	{0x05, 0x0001, 0x0000},
+	{0x00, 0x0000, 0x0001},
+	{0x00, 0x0000, 0x0002},
+	{0x00, 0x000c, 0x0003},
+	{0x00, 0x0000, 0x0004},
+	{0x00, 0x0090, 0x0005},
+	{0x00, 0x0000, 0x0006},
+	{0x00, 0x0040, 0x0007},
+	{0x00, 0x00c0, 0x0008},
+	{0x00, 0x004a, 0x0009},
+	{0x00, 0x0000, 0x000a},
+	{0x00, 0x0000, 0x000b},
+	{0x00, 0x0001, 0x000c},
+	{0x00, 0x0001, 0x000d},
+	{0x00, 0x0000, 0x000e},
+	{0x00, 0x0002, 0x000f},
+	{0x00, 0x0001, 0x0010},
+	{0x00, 0x0000, 0x0011},
+	{0x00, 0x0000, 0x0012},
+	{0x00, 0x0002, 0x0020},
+	{0x00, 0x0080, 0x0021},
+	{0x00, 0x0001, 0x0022},
+	{0x00, 0x00e0, 0x0023},
+	{0x00, 0x0000, 0x0024},
+	{0x00, 0x00d5, 0x0025},
+	{0x00, 0x0000, 0x0026},
+	{0x00, 0x000b, 0x0027},
+	{0x00, 0x0000, 0x0046},
+	{0x00, 0x0000, 0x0047},
+	{0x00, 0x0000, 0x0048},
+	{0x00, 0x0000, 0x0049},
+	{0x00, 0x0008, 0x004a},
+	{0xff, 0x0000, 0x00d0},
+	{0xff, 0x00d8, 0x00d1},
+	{0xff, 0x0000, 0x00d4},
+	{0xff, 0x0000, 0x00d5},
+	{0x01, 0x00a6, 0x0000},
+	{0x01, 0x0028, 0x0001},
+	{0x01, 0x0000, 0x0002},
+	{0x01, 0x000a, 0x0003},
+	{0x01, 0x0040, 0x0004},
+	{0x01, 0x0066, 0x0007},
+	{0x01, 0x0011, 0x0008},
+	{0x01, 0x0032, 0x0009},
+	{0x01, 0x00fd, 0x000a},
+	{0x01, 0x0038, 0x000b},
+	{0x01, 0x00d1, 0x000c},
+	{0x01, 0x00f7, 0x000d},
+	{0x01, 0x00ed, 0x000e},
+	{0x01, 0x00d8, 0x000f},
+	{0x01, 0x0038, 0x0010},
+	{0x01, 0x00ff, 0x0015},
+	{0x01, 0x0001, 0x0016},
+	{0x01, 0x0032, 0x0017},
+	{0x01, 0x0023, 0x0018},
+	{0x01, 0x00ce, 0x0019},
+	{0x01, 0x0023, 0x001a},
+	{0x01, 0x0032, 0x001b},
+	{0x01, 0x008d, 0x001c},
+	{0x01, 0x00ce, 0x001d},
+	{0x01, 0x008d, 0x001e},
+	{0x01, 0x0000, 0x001f},
+	{0x01, 0x0000, 0x0020},
+	{0x01, 0x00ff, 0x003e},
+	{0x01, 0x0003, 0x003f},
+	{0x01, 0x0000, 0x0040},
+	{0x01, 0x0035, 0x0041},
+	{0x01, 0x0053, 0x0042},
+	{0x01, 0x0069, 0x0043},
+	{0x01, 0x007c, 0x0044},
+	{0x01, 0x008c, 0x0045},
+	{0x01, 0x009a, 0x0046},
+	{0x01, 0x00a8, 0x0047},
+	{0x01, 0x00b4, 0x0048},
+	{0x01, 0x00bf, 0x0049},
+	{0x01, 0x00ca, 0x004a},
+	{0x01, 0x00d4, 0x004b},
+	{0x01, 0x00dd, 0x004c},
+	{0x01, 0x00e7, 0x004d},
+	{0x01, 0x00ef, 0x004e},
+	{0x01, 0x00f8, 0x004f},
+	{0x01, 0x00ff, 0x0050},
+	{0x01, 0x0001, 0x0056},
+	{0x01, 0x0060, 0x0057},
+	{0x01, 0x0040, 0x0058},
+	{0x01, 0x0011, 0x0059},
+	{0x01, 0x0001, 0x005a},
+	{0x02, 0x0007, 0x0005},
+	{0x02, 0xa048, 0x0000},
+	{0x02, 0x0007, 0x0005},
+	{0x02, 0x0015, 0x0006},
+	{0x02, 0x100a, 0x0007},
+	{0x02, 0xa048, 0x0000},
+	{0x02, 0xc002, 0x0001},
+	{0x02, 0x000f, 0x0005},
+	{0x02, 0xa048, 0x0000},
+	{0x05, 0x0022, 0x0004},
+	{0x05, 0x0025, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0026, 0x0001},
+	{0x05, 0x0001, 0x0000},
+	{0x05, 0x0027, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0001, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0021, 0x0001},
+	{0x05, 0x00d2, 0x0000},
+	{0x05, 0x0020, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x00, 0x0090, 0x0005},
+	{0x01, 0x00a6, 0x0000},
+	{0x01, 0x0003, 0x003f},
+	{0x01, 0x0001, 0x0056},
+	{0x01, 0x0011, 0x0008},
+	{0x01, 0x0032, 0x0009},
+	{0x01, 0xfffd, 0x000a},
+	{0x01, 0x0023, 0x000b},
+	{0x01, 0xffea, 0x000c},
+	{0x01, 0xfff4, 0x000d},
+	{0x01, 0xfffc, 0x000e},
+	{0x01, 0xffe3, 0x000f},
+	{0x01, 0x001f, 0x0010},
+	{0x01, 0x00a8, 0x0001},
+	{0x01, 0x0067, 0x0007},
+	{0x01, 0x0032, 0x0017},
+	{0x01, 0x0023, 0x0018},
+	{0x01, 0x00ce, 0x0019},
+	{0x01, 0x0023, 0x001a},
+	{0x01, 0x0032, 0x001b},
+	{0x01, 0x008d, 0x001c},
+	{0x01, 0x00ce, 0x001d},
+	{0x01, 0x008d, 0x001e},
+	{0x01, 0x00c8, 0x0015},
+	{0x01, 0x0032, 0x0016},
+	{0x01, 0x0000, 0x0011},
+	{0x01, 0x0000, 0x0012},
+	{0x01, 0x0000, 0x0013},
+	{0x01, 0x000a, 0x0003},
+	{0x02, 0xc002, 0x0001},
+	{0x02, 0x0007, 0x0005},
+	{0x02, 0xc000, 0x0001},
+	{0x02, 0x0000, 0x0005},
+	{0x02, 0x0007, 0x0005},
+	{0x02, 0x2000, 0x0000},
+	{0x05, 0x0022, 0x0004},
+	{0x05, 0x0015, 0x0001},
+	{0x05, 0x00ea, 0x0000},
+	{0x05, 0x0021, 0x0001},
+	{0x05, 0x00d2, 0x0000},
+	{0x05, 0x0023, 0x0001},
+	{0x05, 0x0003, 0x0000},
+	{0x05, 0x0030, 0x0001},
+	{0x05, 0x002b, 0x0000},
+	{0x05, 0x0031, 0x0001},
+	{0x05, 0x0023, 0x0000},
+	{0x05, 0x0032, 0x0001},
+	{0x05, 0x0023, 0x0000},
+	{0x05, 0x0033, 0x0001},
+	{0x05, 0x0023, 0x0000},
+	{0x05, 0x0034, 0x0001},
+	{0x05, 0x0002, 0x0000},
+	{0x05, 0x0050, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0051, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0052, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0054, 0x0001},
+	{0x05, 0x0001, 0x0000},
+	{0x00, 0x0000, 0x0001},
+	{0x00, 0x0000, 0x0002},
+	{0x00, 0x000c, 0x0003},
+	{0x00, 0x0000, 0x0004},
+	{0x00, 0x0090, 0x0005},
+	{0x00, 0x0000, 0x0006},
+	{0x00, 0x0040, 0x0007},
+	{0x00, 0x00c0, 0x0008},
+	{0x00, 0x004a, 0x0009},
+	{0x00, 0x0000, 0x000a},
+	{0x00, 0x0000, 0x000b},
+	{0x00, 0x0001, 0x000c},
+	{0x00, 0x0001, 0x000d},
+	{0x00, 0x0000, 0x000e},
+	{0x00, 0x0002, 0x000f},
+	{0x00, 0x0001, 0x0010},
+	{0x00, 0x0000, 0x0011},
+	{0x00, 0x0000, 0x0012},
+	{0x00, 0x0002, 0x0020},
+	{0x00, 0x0080, 0x0021},
+	{0x00, 0x0001, 0x0022},
+	{0x00, 0x00e0, 0x0023},
+	{0x00, 0x0000, 0x0024},
+	{0x00, 0x00d5, 0x0025},
+	{0x00, 0x0000, 0x0026},
+	{0x00, 0x000b, 0x0027},
+	{0x00, 0x0000, 0x0046},
+	{0x00, 0x0000, 0x0047},
+	{0x00, 0x0000, 0x0048},
+	{0x00, 0x0000, 0x0049},
+	{0x00, 0x0008, 0x004a},
+	{0xff, 0x0000, 0x00d0},
+	{0xff, 0x00d8, 0x00d1},
+	{0xff, 0x0000, 0x00d4},
+	{0xff, 0x0000, 0x00d5},
+	{0x01, 0x00a6, 0x0000},
+	{0x01, 0x0028, 0x0001},
+	{0x01, 0x0000, 0x0002},
+	{0x01, 0x000a, 0x0003},
+	{0x01, 0x0040, 0x0004},
+	{0x01, 0x0066, 0x0007},
+	{0x01, 0x0011, 0x0008},
+	{0x01, 0x0032, 0x0009},
+	{0x01, 0x00fd, 0x000a},
+	{0x01, 0x0038, 0x000b},
+	{0x01, 0x00d1, 0x000c},
+	{0x01, 0x00f7, 0x000d},
+	{0x01, 0x00ed, 0x000e},
+	{0x01, 0x00d8, 0x000f},
+	{0x01, 0x0038, 0x0010},
+	{0x01, 0x00ff, 0x0015},
+	{0x01, 0x0001, 0x0016},
+	{0x01, 0x0032, 0x0017},
+	{0x01, 0x0023, 0x0018},
+	{0x01, 0x00ce, 0x0019},
+	{0x01, 0x0023, 0x001a},
+	{0x01, 0x0032, 0x001b},
+	{0x01, 0x008d, 0x001c},
+	{0x01, 0x00ce, 0x001d},
+	{0x01, 0x008d, 0x001e},
+	{0x01, 0x0000, 0x001f},
+	{0x01, 0x0000, 0x0020},
+	{0x01, 0x00ff, 0x003e},
+	{0x01, 0x0003, 0x003f},
+	{0x01, 0x0000, 0x0040},
+	{0x01, 0x0035, 0x0041},
+	{0x01, 0x0053, 0x0042},
+	{0x01, 0x0069, 0x0043},
+	{0x01, 0x007c, 0x0044},
+	{0x01, 0x008c, 0x0045},
+	{0x01, 0x009a, 0x0046},
+	{0x01, 0x00a8, 0x0047},
+	{0x01, 0x00b4, 0x0048},
+	{0x01, 0x00bf, 0x0049},
+	{0x01, 0x00ca, 0x004a},
+	{0x01, 0x00d4, 0x004b},
+	{0x01, 0x00dd, 0x004c},
+	{0x01, 0x00e7, 0x004d},
+	{0x01, 0x00ef, 0x004e},
+	{0x01, 0x00f8, 0x004f},
+	{0x01, 0x00ff, 0x0050},
+	{0x01, 0x0001, 0x0056},
+	{0x01, 0x0060, 0x0057},
+	{0x01, 0x0040, 0x0058},
+	{0x01, 0x0011, 0x0059},
+	{0x01, 0x0001, 0x005a},
+	{0x02, 0x0007, 0x0005},
+	{0x02, 0xa048, 0x0000},
+	{0x02, 0x0007, 0x0005},
+	{0x02, 0x0015, 0x0006},
+	{0x02, 0x100a, 0x0007},
+	{0x02, 0xa048, 0x0000},
+	{0x02, 0xc002, 0x0001},
+	{0x02, 0x000f, 0x0005},
+	{0x02, 0xa048, 0x0000},
+	{0x05, 0x0022, 0x0004},
+	{0x05, 0x0025, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0026, 0x0001},
+	{0x05, 0x0001, 0x0000},
+	{0x05, 0x0027, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0001, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0021, 0x0001},
+	{0x05, 0x00d2, 0x0000},
+	{0x05, 0x0020, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x00, 0x0090, 0x0005},
+	{0x01, 0x00a6, 0x0000},
+	{0x02, 0x0007, 0x0005},
+	{0x02, 0x2000, 0x0000},
+	{0x05, 0x0022, 0x0004},
+	{0x05, 0x0015, 0x0001},
+	{0x05, 0x00ea, 0x0000},
+	{0x05, 0x0021, 0x0001},
+	{0x05, 0x00d2, 0x0000},
+	{0x05, 0x0023, 0x0001},
+	{0x05, 0x0003, 0x0000},
+	{0x05, 0x0030, 0x0001},
+	{0x05, 0x002b, 0x0000},
+	{0x05, 0x0031, 0x0001},
+	{0x05, 0x0023, 0x0000},
+	{0x05, 0x0032, 0x0001},
+	{0x05, 0x0023, 0x0000},
+	{0x05, 0x0033, 0x0001},
+	{0x05, 0x0023, 0x0000},
+	{0x05, 0x0034, 0x0001},
+	{0x05, 0x0002, 0x0000},
+	{0x05, 0x0050, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0051, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0052, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0054, 0x0001},
+	{0x05, 0x0001, 0x0000},
+	{0x00, 0x0000, 0x0001},
+	{0x00, 0x0000, 0x0002},
+	{0x00, 0x000c, 0x0003},
+	{0x00, 0x0000, 0x0004},
+	{0x00, 0x0090, 0x0005},
+	{0x00, 0x0000, 0x0006},
+	{0x00, 0x0040, 0x0007},
+	{0x00, 0x00c0, 0x0008},
+	{0x00, 0x004a, 0x0009},
+	{0x00, 0x0000, 0x000a},
+	{0x00, 0x0000, 0x000b},
+	{0x00, 0x0001, 0x000c},
+	{0x00, 0x0001, 0x000d},
+	{0x00, 0x0000, 0x000e},
+	{0x00, 0x0002, 0x000f},
+	{0x00, 0x0001, 0x0010},
+	{0x00, 0x0000, 0x0011},
+	{0x00, 0x0000, 0x0012},
+	{0x00, 0x0002, 0x0020},
+	{0x00, 0x0080, 0x0021},
+	{0x00, 0x0001, 0x0022},
+	{0x00, 0x00e0, 0x0023},
+	{0x00, 0x0000, 0x0024},
+	{0x00, 0x00d5, 0x0025},
+	{0x00, 0x0000, 0x0026},
+	{0x00, 0x000b, 0x0027},
+	{0x00, 0x0000, 0x0046},
+	{0x00, 0x0000, 0x0047},
+	{0x00, 0x0000, 0x0048},
+	{0x00, 0x0000, 0x0049},
+	{0x00, 0x0008, 0x004a},
+	{0xff, 0x0000, 0x00d0},
+	{0xff, 0x00d8, 0x00d1},
+	{0xff, 0x0000, 0x00d4},
+	{0xff, 0x0000, 0x00d5},
+	{0x01, 0x00a6, 0x0000},
+	{0x01, 0x0028, 0x0001},
+	{0x01, 0x0000, 0x0002},
+	{0x01, 0x000a, 0x0003},
+	{0x01, 0x0040, 0x0004},
+	{0x01, 0x0066, 0x0007},
+	{0x01, 0x0011, 0x0008},
+	{0x01, 0x0032, 0x0009},
+	{0x01, 0x00fd, 0x000a},
+	{0x01, 0x0038, 0x000b},
+	{0x01, 0x00d1, 0x000c},
+	{0x01, 0x00f7, 0x000d},
+	{0x01, 0x00ed, 0x000e},
+	{0x01, 0x00d8, 0x000f},
+	{0x01, 0x0038, 0x0010},
+	{0x01, 0x00ff, 0x0015},
+	{0x01, 0x0001, 0x0016},
+	{0x01, 0x0032, 0x0017},
+	{0x01, 0x0023, 0x0018},
+	{0x01, 0x00ce, 0x0019},
+	{0x01, 0x0023, 0x001a},
+	{0x01, 0x0032, 0x001b},
+	{0x01, 0x008d, 0x001c},
+	{0x01, 0x00ce, 0x001d},
+	{0x01, 0x008d, 0x001e},
+	{0x01, 0x0000, 0x001f},
+	{0x01, 0x0000, 0x0020},
+	{0x01, 0x00ff, 0x003e},
+	{0x01, 0x0003, 0x003f},
+	{0x01, 0x0000, 0x0040},
+	{0x01, 0x0035, 0x0041},
+	{0x01, 0x0053, 0x0042},
+	{0x01, 0x0069, 0x0043},
+	{0x01, 0x007c, 0x0044},
+	{0x01, 0x008c, 0x0045},
+	{0x01, 0x009a, 0x0046},
+	{0x01, 0x00a8, 0x0047},
+	{0x01, 0x00b4, 0x0048},
+	{0x01, 0x00bf, 0x0049},
+	{0x01, 0x00ca, 0x004a},
+	{0x01, 0x00d4, 0x004b},
+	{0x01, 0x00dd, 0x004c},
+	{0x01, 0x00e7, 0x004d},
+	{0x01, 0x00ef, 0x004e},
+	{0x01, 0x00f8, 0x004f},
+	{0x01, 0x00ff, 0x0050},
+	{0x01, 0x0001, 0x0056},
+	{0x01, 0x0060, 0x0057},
+	{0x01, 0x0040, 0x0058},
+	{0x01, 0x0011, 0x0059},
+	{0x01, 0x0001, 0x005a},
+	{0x02, 0x0007, 0x0005},
+	{0x02, 0xa048, 0x0000},
+	{0x02, 0x0007, 0x0005},
+	{0x02, 0x0015, 0x0006},
+	{0x02, 0x100a, 0x0007},
+	{0x02, 0xa048, 0x0000},
+	{0x02, 0xc002, 0x0001},
+	{0x02, 0x000f, 0x0005},
+	{0x02, 0xa048, 0x0000},
+	{0x05, 0x0022, 0x0004},
+	{0x05, 0x0025, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0026, 0x0001},
+	{0x05, 0x0001, 0x0000},
+	{0x05, 0x0027, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0001, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0021, 0x0001},
+	{0x05, 0x00d2, 0x0000},
+	{0x05, 0x0020, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x00, 0x0090, 0x0005},
+	{0x01, 0x00a6, 0x0000},
+	{0x05, 0x0026, 0x0001},
+	{0x05, 0x0001, 0x0000},
+	{0x05, 0x0027, 0x0001},
+	{0x05, 0x000f, 0x0000},
+	{0x01, 0x0003, 0x003f},
+	{0x01, 0x0001, 0x0056},
+	{0x01, 0x0011, 0x0008},
+	{0x01, 0x0032, 0x0009},
+	{0x01, 0xfffd, 0x000a},
+	{0x01, 0x0023, 0x000b},
+	{0x01, 0xffea, 0x000c},
+	{0x01, 0xfff4, 0x000d},
+	{0x01, 0xfffc, 0x000e},
+	{0x01, 0xffe3, 0x000f},
+	{0x01, 0x001f, 0x0010},
+	{0x01, 0x00a8, 0x0001},
+	{0x01, 0x0067, 0x0007},
+	{0x01, 0x0042, 0x0051},
+	{0x01, 0x0051, 0x0053},
+	{0x01, 0x000a, 0x0003},
+	{0x02, 0xc002, 0x0001},
+	{0x02, 0x0007, 0x0005},
+	{0x02, 0xc000, 0x0001},
+	{0x02, 0x0000, 0x0005},
+	{0x02, 0x0007, 0x0005},
+	{0x02, 0x2000, 0x0000},
+	{0x05, 0x0022, 0x0004},
+	{0x05, 0x0015, 0x0001},
+	{0x05, 0x00ea, 0x0000},
+	{0x05, 0x0021, 0x0001},
+	{0x05, 0x00d2, 0x0000},
+	{0x05, 0x0023, 0x0001},
+	{0x05, 0x0003, 0x0000},
+	{0x05, 0x0030, 0x0001},
+	{0x05, 0x002b, 0x0000},
+	{0x05, 0x0031, 0x0001},
+	{0x05, 0x0023, 0x0000},
+	{0x05, 0x0032, 0x0001},
+	{0x05, 0x0023, 0x0000},
+	{0x05, 0x0033, 0x0001},
+	{0x05, 0x0023, 0x0000},
+	{0x05, 0x0034, 0x0001},
+	{0x05, 0x0002, 0x0000},
+	{0x05, 0x0050, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0051, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0052, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0054, 0x0001},
+	{0x05, 0x0001, 0x0000},
+	{0x00, 0x0000, 0x0001},
+	{0x00, 0x0000, 0x0002},
+	{0x00, 0x000c, 0x0003},
+	{0x00, 0x0000, 0x0004},
+	{0x00, 0x0090, 0x0005},
+	{0x00, 0x0000, 0x0006},
+	{0x00, 0x0040, 0x0007},
+	{0x00, 0x00c0, 0x0008},
+	{0x00, 0x004a, 0x0009},
+	{0x00, 0x0000, 0x000a},
+	{0x00, 0x0000, 0x000b},
+	{0x00, 0x0001, 0x000c},
+	{0x00, 0x0001, 0x000d},
+	{0x00, 0x0000, 0x000e},
+	{0x00, 0x0002, 0x000f},
+	{0x00, 0x0001, 0x0010},
+	{0x00, 0x0000, 0x0011},
+	{0x00, 0x0000, 0x0012},
+	{0x00, 0x0002, 0x0020},
+	{0x00, 0x0080, 0x0021},
+	{0x00, 0x0001, 0x0022},
+	{0x00, 0x00e0, 0x0023},
+	{0x00, 0x0000, 0x0024},
+	{0x00, 0x00d5, 0x0025},
+	{0x00, 0x0000, 0x0026},
+	{0x00, 0x000b, 0x0027},
+	{0x00, 0x0000, 0x0046},
+	{0x00, 0x0000, 0x0047},
+	{0x00, 0x0000, 0x0048},
+	{0x00, 0x0000, 0x0049},
+	{0x00, 0x0008, 0x004a},
+	{0xff, 0x0000, 0x00d0},
+	{0xff, 0x00d8, 0x00d1},
+	{0xff, 0x0000, 0x00d4},
+	{0xff, 0x0000, 0x00d5},
+	{0x01, 0x00a6, 0x0000},
+	{0x01, 0x0028, 0x0001},
+	{0x01, 0x0000, 0x0002},
+	{0x01, 0x000a, 0x0003},
+	{0x01, 0x0040, 0x0004},
+	{0x01, 0x0066, 0x0007},
+	{0x01, 0x0011, 0x0008},
+	{0x01, 0x0032, 0x0009},
+	{0x01, 0x00fd, 0x000a},
+	{0x01, 0x0038, 0x000b},
+	{0x01, 0x00d1, 0x000c},
+	{0x01, 0x00f7, 0x000d},
+	{0x01, 0x00ed, 0x000e},
+	{0x01, 0x00d8, 0x000f},
+	{0x01, 0x0038, 0x0010},
+	{0x01, 0x00ff, 0x0015},
+	{0x01, 0x0001, 0x0016},
+	{0x01, 0x0032, 0x0017},
+	{0x01, 0x0023, 0x0018},
+	{0x01, 0x00ce, 0x0019},
+	{0x01, 0x0023, 0x001a},
+	{0x01, 0x0032, 0x001b},
+	{0x01, 0x008d, 0x001c},
+	{0x01, 0x00ce, 0x001d},
+	{0x01, 0x008d, 0x001e},
+	{0x01, 0x0000, 0x001f},
+	{0x01, 0x0000, 0x0020},
+	{0x01, 0x00ff, 0x003e},
+	{0x01, 0x0003, 0x003f},
+	{0x01, 0x0000, 0x0040},
+	{0x01, 0x0035, 0x0041},
+	{0x01, 0x0053, 0x0042},
+	{0x01, 0x0069, 0x0043},
+	{0x01, 0x007c, 0x0044},
+	{0x01, 0x008c, 0x0045},
+	{0x01, 0x009a, 0x0046},
+	{0x01, 0x00a8, 0x0047},
+	{0x01, 0x00b4, 0x0048},
+	{0x01, 0x00bf, 0x0049},
+	{0x01, 0x00ca, 0x004a},
+	{0x01, 0x00d4, 0x004b},
+	{0x01, 0x00dd, 0x004c},
+	{0x01, 0x00e7, 0x004d},
+	{0x01, 0x00ef, 0x004e},
+	{0x01, 0x00f8, 0x004f},
+	{0x01, 0x00ff, 0x0050},
+	{0x01, 0x0001, 0x0056},
+	{0x01, 0x0060, 0x0057},
+	{0x01, 0x0040, 0x0058},
+	{0x01, 0x0011, 0x0059},
+	{0x01, 0x0001, 0x005a},
+	{0x02, 0x0007, 0x0005},
+	{0x02, 0xa048, 0x0000},
+	{0x02, 0x0007, 0x0005},
+	{0x02, 0x0015, 0x0006},
+	{0x02, 0x100a, 0x0007},
+	{0x02, 0xa048, 0x0000},
+	{0x02, 0xc002, 0x0001},
+	{0x02, 0x000f, 0x0005},
+	{0x02, 0xa048, 0x0000},
+	{0x05, 0x0022, 0x0004},
+	{0x05, 0x0025, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0026, 0x0001},
+	{0x05, 0x0001, 0x0000},
+	{0x05, 0x0027, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0001, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0021, 0x0001},
+	{0x05, 0x00d2, 0x0000},
+	{0x05, 0x0020, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x00, 0x0090, 0x0005},
+	{0x01, 0x00a6, 0x0000},
+	{0x02, 0x0007, 0x0005},
+	{0x02, 0x2000, 0x0000},
+	{0x05, 0x0022, 0x0004},
+	{0x05, 0x0015, 0x0001},
+	{0x05, 0x00ea, 0x0000},
+	{0x05, 0x0021, 0x0001},
+	{0x05, 0x00d2, 0x0000},
+	{0x05, 0x0023, 0x0001},
+	{0x05, 0x0003, 0x0000},
+	{0x05, 0x0030, 0x0001},
+	{0x05, 0x002b, 0x0000},
+	{0x05, 0x0031, 0x0001},
+	{0x05, 0x0023, 0x0000},
+	{0x05, 0x0032, 0x0001},
+	{0x05, 0x0023, 0x0000},
+	{0x05, 0x0033, 0x0001},
+	{0x05, 0x0023, 0x0000},
+	{0x05, 0x0034, 0x0001},
+	{0x05, 0x0002, 0x0000},
+	{0x05, 0x0050, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0051, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0052, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0054, 0x0001},
+	{0x05, 0x0001, 0x0000},
+	{0x00, 0x0000, 0x0001},
+	{0x00, 0x0000, 0x0002},
+	{0x00, 0x000c, 0x0003},
+	{0x00, 0x0000, 0x0004},
+	{0x00, 0x0090, 0x0005},
+	{0x00, 0x0000, 0x0006},
+	{0x00, 0x0040, 0x0007},
+	{0x00, 0x00c0, 0x0008},
+	{0x00, 0x004a, 0x0009},
+	{0x00, 0x0000, 0x000a},
+	{0x00, 0x0000, 0x000b},
+	{0x00, 0x0001, 0x000c},
+	{0x00, 0x0001, 0x000d},
+	{0x00, 0x0000, 0x000e},
+	{0x00, 0x0002, 0x000f},
+	{0x00, 0x0001, 0x0010},
+	{0x00, 0x0000, 0x0011},
+	{0x00, 0x0000, 0x0012},
+	{0x00, 0x0002, 0x0020},
+	{0x00, 0x0080, 0x0021},
+	{0x00, 0x0001, 0x0022},
+	{0x00, 0x00e0, 0x0023},
+	{0x00, 0x0000, 0x0024},
+	{0x00, 0x00d5, 0x0025},
+	{0x00, 0x0000, 0x0026},
+	{0x00, 0x000b, 0x0027},
+	{0x00, 0x0000, 0x0046},
+	{0x00, 0x0000, 0x0047},
+	{0x00, 0x0000, 0x0048},
+	{0x00, 0x0000, 0x0049},
+	{0x00, 0x0008, 0x004a},
+	{0xff, 0x0000, 0x00d0},
+	{0xff, 0x00d8, 0x00d1},
+	{0xff, 0x0000, 0x00d4},
+	{0xff, 0x0000, 0x00d5},
+	{0x01, 0x00a6, 0x0000},
+	{0x01, 0x0028, 0x0001},
+	{0x01, 0x0000, 0x0002},
+	{0x01, 0x000a, 0x0003},
+	{0x01, 0x0040, 0x0004},
+	{0x01, 0x0066, 0x0007},
+	{0x01, 0x0011, 0x0008},
+	{0x01, 0x0032, 0x0009},
+	{0x01, 0x00fd, 0x000a},
+	{0x01, 0x0038, 0x000b},
+	{0x01, 0x00d1, 0x000c},
+	{0x01, 0x00f7, 0x000d},
+	{0x01, 0x00ed, 0x000e},
+	{0x01, 0x00d8, 0x000f},
+	{0x01, 0x0038, 0x0010},
+	{0x01, 0x00ff, 0x0015},
+	{0x01, 0x0001, 0x0016},
+	{0x01, 0x0032, 0x0017},
+	{0x01, 0x0023, 0x0018},
+	{0x01, 0x00ce, 0x0019},
+	{0x01, 0x0023, 0x001a},
+	{0x01, 0x0032, 0x001b},
+	{0x01, 0x008d, 0x001c},
+	{0x01, 0x00ce, 0x001d},
+	{0x01, 0x008d, 0x001e},
+	{0x01, 0x0000, 0x001f},
+	{0x01, 0x0000, 0x0020},
+	{0x01, 0x00ff, 0x003e},
+	{0x01, 0x0003, 0x003f},
+	{0x01, 0x0000, 0x0040},
+	{0x01, 0x0035, 0x0041},
+	{0x01, 0x0053, 0x0042},
+	{0x01, 0x0069, 0x0043},
+	{0x01, 0x007c, 0x0044},
+	{0x01, 0x008c, 0x0045},
+	{0x01, 0x009a, 0x0046},
+	{0x01, 0x00a8, 0x0047},
+	{0x01, 0x00b4, 0x0048},
+	{0x01, 0x00bf, 0x0049},
+	{0x01, 0x00ca, 0x004a},
+	{0x01, 0x00d4, 0x004b},
+	{0x01, 0x00dd, 0x004c},
+	{0x01, 0x00e7, 0x004d},
+	{0x01, 0x00ef, 0x004e},
+	{0x01, 0x00f8, 0x004f},
+	{0x01, 0x00ff, 0x0050},
+	{0x01, 0x0001, 0x0056},
+	{0x01, 0x0060, 0x0057},
+	{0x01, 0x0040, 0x0058},
+	{0x01, 0x0011, 0x0059},
+	{0x01, 0x0001, 0x005a},
+	{0x02, 0x0007, 0x0005},
+	{0x02, 0xa048, 0x0000},
+	{0x02, 0x0007, 0x0005},
+	{0x02, 0x0015, 0x0006},
+	{0x02, 0x100a, 0x0007},
+	{0x02, 0xa048, 0x0000},
+	{0x02, 0xc002, 0x0001},
+	{0x02, 0x000f, 0x0005},
+	{0x02, 0xa048, 0x0000},
+	{0x05, 0x0022, 0x0004},
+	{0x05, 0x0025, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0026, 0x0001},
+	{0x05, 0x0001, 0x0000},
+	{0x05, 0x0027, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0001, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0021, 0x0001},
+	{0x05, 0x00d2, 0x0000},
+	{0x05, 0x0020, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x00, 0x0090, 0x0005},
+	{0x01, 0x00a6, 0x0000},
+	{0x05, 0x0026, 0x0001},
+	{0x05, 0x0001, 0x0000},
+	{0x05, 0x0027, 0x0001},
+	{0x05, 0x001e, 0x0000},
+	{0x01, 0x0003, 0x003f},
+	{0x01, 0x0001, 0x0056},
+	{0x01, 0x0011, 0x0008},
+	{0x01, 0x0032, 0x0009},
+	{0x01, 0xfffd, 0x000a},
+	{0x01, 0x0023, 0x000b},
+	{0x01, 0xffea, 0x000c},
+	{0x01, 0xfff4, 0x000d},
+	{0x01, 0xfffc, 0x000e},
+	{0x01, 0xffe3, 0x000f},
+	{0x01, 0x001f, 0x0010},
+	{0x01, 0x00a8, 0x0001},
+	{0x01, 0x0067, 0x0007},
+	{0x01, 0x0042, 0x0051},
+	{0x01, 0x0051, 0x0053},
+	{0x01, 0x000a, 0x0003},
+	{0x02, 0xc002, 0x0001},
+	{0x02, 0x0007, 0x0005},
+	{0x01, 0x0042, 0x0051},
+	{0x01, 0x0051, 0x0053},
+	{0x05, 0x0026, 0x0001},
+	{0x05, 0x0001, 0x0000},
+	{0x05, 0x0027, 0x0001},
+	{0x05, 0x002d, 0x0000},
+	{0x01, 0x0003, 0x003f},
+	{0x01, 0x0001, 0x0056},
+	{0x02, 0xc000, 0x0001},
+	{0x02, 0x0000, 0x0005},
+	{}
+};
+
+/* Unknown camera from Ori Usbid 0x0000:0x0000 */
+/* Based on snoops from Ori Cohen */
+static const __u16 spca501c_mysterious_open_data[][3] = {
+	{0x02, 0x000f, 0x0005},
+	{0x02, 0xa048, 0x0000},
+	{0x05, 0x0022, 0x0004},
+/* DSP Registers */
+	{0x01, 0x0016, 0x0011},	/* RGB offset */
+	{0x01, 0x0000, 0x0012},
+	{0x01, 0x0006, 0x0013},
+	{0x01, 0x0078, 0x0051},
+	{0x01, 0x0040, 0x0052},
+	{0x01, 0x0046, 0x0053},
+	{0x01, 0x0040, 0x0054},
+	{0x00, 0x0025, 0x0000},
+/*	{0x00, 0x0000, 0x0000 }, */
+/* Part 2 */
+/* TG Registers */
+	{0x00, 0x0026, 0x0000},
+	{0x00, 0x0001, 0x0000},
+	{0x00, 0x0027, 0x0000},
+	{0x00, 0x008a, 0x0000},
+	{0x02, 0x0007, 0x0005},
+	{0x02, 0x2000, 0x0000},
+	{0x05, 0x0022, 0x0004},
+	{0x05, 0x0015, 0x0001},
+	{0x05, 0x00ea, 0x0000},
+	{0x05, 0x0021, 0x0001},
+	{0x05, 0x00d2, 0x0000},
+	{0x05, 0x0023, 0x0001},
+	{0x05, 0x0003, 0x0000},
+	{0x05, 0x0030, 0x0001},
+	{0x05, 0x002b, 0x0000},
+	{0x05, 0x0031, 0x0001},
+	{0x05, 0x0023, 0x0000},
+	{0x05, 0x0032, 0x0001},
+	{0x05, 0x0023, 0x0000},
+	{0x05, 0x0033, 0x0001},
+	{0x05, 0x0023, 0x0000},
+	{0x05, 0x0034, 0x0001},
+	{0x05, 0x0002, 0x0000},
+	{0x05, 0x0050, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0051, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0052, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0054, 0x0001},
+	{0x05, 0x0001, 0x0000},
+	{}
+};
+
+/* Based on snoops from Ori Cohen */
+static const __u16 spca501c_mysterious_init_data[][3] = {
+/* Part 3 */
+/* TG registers */
+/*	{0x00, 0x0000, 0x0000}, */
+	{0x00, 0x0000, 0x0001},
+	{0x00, 0x0000, 0x0002},
+	{0x00, 0x0006, 0x0003},
+	{0x00, 0x0000, 0x0004},
+	{0x00, 0x0090, 0x0005},
+	{0x00, 0x0000, 0x0006},
+	{0x00, 0x0040, 0x0007},
+	{0x00, 0x00c0, 0x0008},
+	{0x00, 0x004a, 0x0009},
+	{0x00, 0x0000, 0x000a},
+	{0x00, 0x0000, 0x000b},
+	{0x00, 0x0001, 0x000c},
+	{0x00, 0x0001, 0x000d},
+	{0x00, 0x0000, 0x000e},
+	{0x00, 0x0002, 0x000f},
+	{0x00, 0x0001, 0x0010},
+	{0x00, 0x0000, 0x0011},
+	{0x00, 0x0001, 0x0012},
+	{0x00, 0x0002, 0x0020},
+	{0x00, 0x0080, 0x0021},	/* 640 */
+	{0x00, 0x0001, 0x0022},
+	{0x00, 0x00e0, 0x0023},	/* 480 */
+	{0x00, 0x0000, 0x0024},	/* Offset H hight */
+	{0x00, 0x00d3, 0x0025},	/* low */
+	{0x00, 0x0000, 0x0026},	/* Offset V */
+	{0x00, 0x000d, 0x0027},	/* low */
+	{0x00, 0x0000, 0x0046},
+	{0x00, 0x0000, 0x0047},
+	{0x00, 0x0000, 0x0048},
+	{0x00, 0x0000, 0x0049},
+	{0x00, 0x0008, 0x004a},
+/* DSP Registers	*/
+	{0x01, 0x00a6, 0x0000},
+	{0x01, 0x0028, 0x0001},
+	{0x01, 0x0000, 0x0002},
+	{0x01, 0x000a, 0x0003},	/* Level Calc bit7 ->1 Auto */
+	{0x01, 0x0040, 0x0004},
+	{0x01, 0x0066, 0x0007},
+	{0x01, 0x000f, 0x0008},	/* A11 Color correction coeff */
+	{0x01, 0x002d, 0x0009},	/* A12 */
+	{0x01, 0x0005, 0x000a},	/* A13 */
+	{0x01, 0x0023, 0x000b},	/* A21 */
+	{0x01, 0x00e0, 0x000c},	/* A22 */
+	{0x01, 0x00fd, 0x000d},	/* A23 */
+	{0x01, 0x00f4, 0x000e},	/* A31 */
+	{0x01, 0x00e4, 0x000f},	/* A32 */
+	{0x01, 0x0028, 0x0010},	/* A33 */
+	{0x01, 0x00ff, 0x0015},	/* Reserved */
+	{0x01, 0x0001, 0x0016},	/* Reserved */
+	{0x01, 0x0032, 0x0017},	/* Win1 Start begin */
+	{0x01, 0x0023, 0x0018},
+	{0x01, 0x00ce, 0x0019},
+	{0x01, 0x0023, 0x001a},
+	{0x01, 0x0032, 0x001b},
+	{0x01, 0x008d, 0x001c},
+	{0x01, 0x00ce, 0x001d},
+	{0x01, 0x008d, 0x001e},
+	{0x01, 0x0000, 0x001f},
+	{0x01, 0x0000, 0x0020},	/* Win1 Start end */
+	{0x01, 0x00ff, 0x003e},	/* Reserved begin */
+	{0x01, 0x0002, 0x003f},
+	{0x01, 0x0000, 0x0040},
+	{0x01, 0x0035, 0x0041},
+	{0x01, 0x0053, 0x0042},
+	{0x01, 0x0069, 0x0043},
+	{0x01, 0x007c, 0x0044},
+	{0x01, 0x008c, 0x0045},
+	{0x01, 0x009a, 0x0046},
+	{0x01, 0x00a8, 0x0047},
+	{0x01, 0x00b4, 0x0048},
+	{0x01, 0x00bf, 0x0049},
+	{0x01, 0x00ca, 0x004a},
+	{0x01, 0x00d4, 0x004b},
+	{0x01, 0x00dd, 0x004c},
+	{0x01, 0x00e7, 0x004d},
+	{0x01, 0x00ef, 0x004e},
+	{0x01, 0x00f8, 0x004f},
+	{0x01, 0x00ff, 0x0050},
+	{0x01, 0x0003, 0x0056},	/* Reserved end */
+	{0x01, 0x0060, 0x0057},	/* Edge Gain */
+	{0x01, 0x0040, 0x0058},
+	{0x01, 0x0011, 0x0059},	/* Edge Bandwidth */
+	{0x01, 0x0001, 0x005a},
+	{0x02, 0x0007, 0x0005},
+	{0x02, 0xa048, 0x0000},
+	{0x02, 0x0007, 0x0005},
+	{0x02, 0x0015, 0x0006},
+	{0x02, 0x200a, 0x0007},
+	{0x02, 0xa048, 0x0000},
+	{0x02, 0xc000, 0x0001},
+	{0x02, 0x000f, 0x0005},
+	{0x02, 0xa048, 0x0000},
+	{0x05, 0x0022, 0x0004},
+	{0x05, 0x0025, 0x0001},
+	{0x05, 0x0000, 0x0000},
+/* Part 4 */
+	{0x05, 0x0026, 0x0001},
+	{0x05, 0x0001, 0x0000},
+	{0x05, 0x0027, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0001, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x05, 0x0021, 0x0001},
+	{0x05, 0x00d2, 0x0000},
+	{0x05, 0x0020, 0x0001},
+	{0x05, 0x0000, 0x0000},
+	{0x00, 0x0090, 0x0005},
+	{0x01, 0x00a6, 0x0000},
+	{0x02, 0x0000, 0x0005},
+	{0x05, 0x0026, 0x0001},
+	{0x05, 0x0001, 0x0000},
+	{0x05, 0x0027, 0x0001},
+	{0x05, 0x004e, 0x0000},
+/* Part 5 */
+	{0x01, 0x0003, 0x003f},
+	{0x01, 0x0001, 0x0056},
+	{0x01, 0x000f, 0x0008},
+	{0x01, 0x002d, 0x0009},
+	{0x01, 0x0005, 0x000a},
+	{0x01, 0x0023, 0x000b},
+	{0x01, 0xffe0, 0x000c},
+	{0x01, 0xfffd, 0x000d},
+	{0x01, 0xfff4, 0x000e},
+	{0x01, 0xffe4, 0x000f},
+	{0x01, 0x0028, 0x0010},
+	{0x01, 0x00a8, 0x0001},
+	{0x01, 0x0066, 0x0007},
+	{0x01, 0x0032, 0x0017},
+	{0x01, 0x0023, 0x0018},
+	{0x01, 0x00ce, 0x0019},
+	{0x01, 0x0023, 0x001a},
+	{0x01, 0x0032, 0x001b},
+	{0x01, 0x008d, 0x001c},
+	{0x01, 0x00ce, 0x001d},
+	{0x01, 0x008d, 0x001e},
+	{0x01, 0x00c8, 0x0015},	/* c8 Poids fort Luma */
+	{0x01, 0x0032, 0x0016},	/* 32 */
+	{0x01, 0x0016, 0x0011},	/* R 00 */
+	{0x01, 0x0016, 0x0012},	/* G 00 */
+	{0x01, 0x0016, 0x0013},	/* B 00 */
+	{0x01, 0x000a, 0x0003},
+	{0x02, 0xc002, 0x0001},
+	{0x02, 0x0007, 0x0005},
+	{}
+};
+
+static int reg_write(struct gspca_dev *gspca_dev,
+					__u16 req, __u16 index, __u16 value)
+{
+	int ret;
+	struct usb_device *dev = gspca_dev->dev;
+
+	ret = usb_control_msg(dev,
+			usb_sndctrlpipe(dev, 0),
+			req,
+			USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			value, index, NULL, 0, 500);
+	gspca_dbg(gspca_dev, D_USBO, "reg write: 0x%02x 0x%02x 0x%02x\n",
+		  req, index, value);
+	if (ret < 0)
+		pr_err("reg write: error %d\n", ret);
+	return ret;
+}
+
+
+static int write_vector(struct gspca_dev *gspca_dev, const __u16 data[][3])
+{
+	int ret, i = 0;
+
+	while (data[i][0] != 0 || data[i][1] != 0 || data[i][2] != 0) {
+		ret = reg_write(gspca_dev, data[i][0], data[i][2],
+								data[i][1]);
+		if (ret < 0) {
+			gspca_err(gspca_dev, "Reg write failed for 0x%02x,0x%02x,0x%02x\n",
+				  data[i][0], data[i][1], data[i][2]);
+			return ret;
+		}
+		i++;
+	}
+	return 0;
+}
+
+static void setbrightness(struct gspca_dev *gspca_dev, s32 val)
+{
+	reg_write(gspca_dev, SPCA501_REG_CCDSP, 0x12, val);
+}
+
+static void setcontrast(struct gspca_dev *gspca_dev, s32 val)
+{
+	reg_write(gspca_dev, 0x00, 0x00, (val >> 8) & 0xff);
+	reg_write(gspca_dev, 0x00, 0x01, val & 0xff);
+}
+
+static void setcolors(struct gspca_dev *gspca_dev, s32 val)
+{
+	reg_write(gspca_dev, SPCA501_REG_CCDSP, 0x0c, val);
+}
+
+static void setblue_balance(struct gspca_dev *gspca_dev, s32 val)
+{
+	reg_write(gspca_dev, SPCA501_REG_CCDSP, 0x11, val);
+}
+
+static void setred_balance(struct gspca_dev *gspca_dev, s32 val)
+{
+	reg_write(gspca_dev, SPCA501_REG_CCDSP, 0x13, val);
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam;
+
+	cam = &gspca_dev->cam;
+	cam->cam_mode = vga_mode;
+	cam->nmodes = ARRAY_SIZE(vga_mode);
+	sd->subtype = id->driver_info;
+
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	switch (sd->subtype) {
+	case Arowana300KCMOSCamera:
+	case SmileIntlCamera:
+		/* Arowana 300k CMOS Camera data */
+		if (write_vector(gspca_dev, spca501c_arowana_init_data))
+			goto error;
+		break;
+	case MystFromOriUnknownCamera:
+		/* Unknown Ori CMOS Camera data */
+		if (write_vector(gspca_dev, spca501c_mysterious_open_data))
+			goto error;
+		break;
+	default:
+		/* generic spca501 init data */
+		if (write_vector(gspca_dev, spca501_init_data))
+			goto error;
+		break;
+	}
+	gspca_dbg(gspca_dev, D_STREAM, "Initializing SPCA501 finished\n");
+	return 0;
+error:
+	return -EINVAL;
+}
+
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int mode;
+
+	switch (sd->subtype) {
+	case ThreeComHomeConnectLite:
+		/* Special handling for 3com data */
+		write_vector(gspca_dev, spca501_3com_open_data);
+		break;
+	case Arowana300KCMOSCamera:
+	case SmileIntlCamera:
+		/* Arowana 300k CMOS Camera data */
+		write_vector(gspca_dev, spca501c_arowana_open_data);
+		break;
+	case MystFromOriUnknownCamera:
+		/* Unknown CMOS Camera data */
+		write_vector(gspca_dev, spca501c_mysterious_init_data);
+		break;
+	default:
+		/* Generic 501 open data */
+		write_vector(gspca_dev, spca501_open_data);
+	}
+
+	/* memorize the wanted pixel format */
+	mode = gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv;
+
+	/* Enable ISO packet machine CTRL reg=2,
+	 * index=1 bitmask=0x2 (bit ordinal 1) */
+	reg_write(gspca_dev, SPCA50X_REG_USB, 0x6, 0x94);
+	switch (mode) {
+	case 0: /* 640x480 */
+		reg_write(gspca_dev, SPCA50X_REG_USB, 0x07, 0x004a);
+		break;
+	case 1: /* 320x240 */
+		reg_write(gspca_dev, SPCA50X_REG_USB, 0x07, 0x104a);
+		break;
+	default:
+/*	case 2:  * 160x120 */
+		reg_write(gspca_dev, SPCA50X_REG_USB, 0x07, 0x204a);
+		break;
+	}
+	reg_write(gspca_dev, SPCA501_REG_CTLRL, 0x01, 0x02);
+
+	return 0;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	/* Disable ISO packet
+	 * machine CTRL reg=2, index=1 bitmask=0x0 (bit ordinal 1) */
+	reg_write(gspca_dev, SPCA501_REG_CTLRL, 0x01, 0x00);
+}
+
+/* called on streamoff with alt 0 and on disconnect */
+static void sd_stop0(struct gspca_dev *gspca_dev)
+{
+	if (!gspca_dev->present)
+		return;
+	reg_write(gspca_dev, SPCA501_REG_CTLRL, 0x05, 0x00);
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	switch (data[0]) {
+	case 0:				/* start of frame */
+		gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
+		data += SPCA501_OFFSET_DATA;
+		len -= SPCA501_OFFSET_DATA;
+		gspca_frame_add(gspca_dev, FIRST_PACKET, data, len);
+		return;
+	case 0xff:			/* drop */
+/*		gspca_dev->last_packet_type = DISCARD_PACKET; */
+		return;
+	}
+	data++;
+	len--;
+	gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		setbrightness(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_CONTRAST:
+		setcontrast(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_SATURATION:
+		setcolors(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_BLUE_BALANCE:
+		setblue_balance(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_RED_BALANCE:
+		setred_balance(gspca_dev, ctrl->val);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 5);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0, 127, 1, 0);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_CONTRAST, 0, 64725, 1, 64725);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_SATURATION, 0, 63, 1, 20);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BLUE_BALANCE, 0, 127, 1, 0);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_RED_BALANCE, 0, 127, 1, 0);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	return 0;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.stop0 = sd_stop0,
+	.pkt_scan = sd_pkt_scan,
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x040a, 0x0002), .driver_info = KodakDVC325},
+	{USB_DEVICE(0x0497, 0xc001), .driver_info = SmileIntlCamera},
+	{USB_DEVICE(0x0506, 0x00df), .driver_info = ThreeComHomeConnectLite},
+	{USB_DEVICE(0x0733, 0x0401), .driver_info = IntelCreateAndShare},
+	{USB_DEVICE(0x0733, 0x0402), .driver_info = ViewQuestM318B},
+	{USB_DEVICE(0x1776, 0x501c), .driver_info = Arowana300KCMOSCamera},
+	{USB_DEVICE(0x0000, 0x0000), .driver_info = MystFromOriUnknownCamera},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/spca505.c b/drivers/media/usb/gspca/spca505.c
new file mode 100644
index 0000000..07aae9c
--- /dev/null
+++ b/drivers/media/usb/gspca/spca505.c
@@ -0,0 +1,801 @@
+/*
+ * SPCA505 chip based cameras initialization data
+ *
+ * V4L2 by Jean-Francis Moine <http://moinejf.free.fr>
+ *
+ * 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
+ * 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.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "spca505"
+
+#include "gspca.h"
+
+MODULE_AUTHOR("Michel Xhaard <mxhaard@users.sourceforge.net>");
+MODULE_DESCRIPTION("GSPCA/SPCA505 USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;		/* !! must be the first item */
+
+	u8 subtype;
+#define IntelPCCameraPro 0
+#define Nxultra 1
+};
+
+static const struct v4l2_pix_format vga_mode[] = {
+	{160, 120, V4L2_PIX_FMT_SPCA505, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 160 * 120 * 3 / 2,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 4},
+	{176, 144, V4L2_PIX_FMT_SPCA505, V4L2_FIELD_NONE,
+		.bytesperline = 176,
+		.sizeimage = 176 * 144 * 3 / 2,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 3},
+	{320, 240, V4L2_PIX_FMT_SPCA505, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3 / 2,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 2},
+	{352, 288, V4L2_PIX_FMT_SPCA505, V4L2_FIELD_NONE,
+		.bytesperline = 352,
+		.sizeimage = 352 * 288 * 3 / 2,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1},
+	{640, 480, V4L2_PIX_FMT_SPCA505, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 3 / 2,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+};
+
+#define SPCA50X_OFFSET_DATA 10
+
+#define SPCA50X_REG_USB 0x02	/* spca505 501 */
+
+#define SPCA50X_USB_CTRL 0x00	/* spca505 */
+#define SPCA50X_CUSB_ENABLE 0x01 /* spca505 */
+
+#define SPCA50X_REG_GLOBAL 0x03	/* spca505 */
+#define SPCA50X_GMISC0_IDSEL 0x01 /* Global control device ID select spca505 */
+#define SPCA50X_GLOBAL_MISC0 0x00 /* Global control miscellaneous 0 spca505 */
+
+#define SPCA50X_GLOBAL_MISC1 0x01 /* 505 */
+#define SPCA50X_GLOBAL_MISC3 0x03 /* 505 */
+#define SPCA50X_GMISC3_SAA7113RST 0x20	/* Not sure about this one spca505 */
+
+/* Image format and compression control */
+#define SPCA50X_REG_COMPRESS 0x04
+
+/*
+ * Data to initialize a SPCA505. Common to the CCD and external modes
+ */
+static const u8 spca505_init_data[][3] = {
+	/* bmRequest,value,index */
+	{SPCA50X_REG_GLOBAL, SPCA50X_GMISC3_SAA7113RST, SPCA50X_GLOBAL_MISC3},
+	/* Sensor reset */
+	{SPCA50X_REG_GLOBAL, 0x00, SPCA50X_GLOBAL_MISC3},
+	{SPCA50X_REG_GLOBAL, 0x00, SPCA50X_GLOBAL_MISC1},
+	/* Block USB reset */
+	{SPCA50X_REG_GLOBAL, SPCA50X_GMISC0_IDSEL, SPCA50X_GLOBAL_MISC0},
+
+	{0x05, 0x01, 0x10},
+					/* Maybe power down some stuff */
+	{0x05, 0x0f, 0x11},
+
+	/* Setup internal CCD  ? */
+	{0x06, 0x10, 0x08},
+	{0x06, 0x00, 0x09},
+	{0x06, 0x00, 0x0a},
+	{0x06, 0x00, 0x0b},
+	{0x06, 0x10, 0x0c},
+	{0x06, 0x00, 0x0d},
+	{0x06, 0x00, 0x0e},
+	{0x06, 0x00, 0x0f},
+	{0x06, 0x10, 0x10},
+	{0x06, 0x02, 0x11},
+	{0x06, 0x00, 0x12},
+	{0x06, 0x04, 0x13},
+	{0x06, 0x02, 0x14},
+	{0x06, 0x8a, 0x51},
+	{0x06, 0x40, 0x52},
+	{0x06, 0xb6, 0x53},
+	{0x06, 0x3d, 0x54},
+	{}
+};
+
+/*
+ * Data to initialize the camera using the internal CCD
+ */
+static const u8 spca505_open_data_ccd[][3] = {
+	/* bmRequest,value,index */
+	/* Internal CCD data set */
+	{0x03, 0x04, 0x01},
+	/* This could be a reset */
+	{0x03, 0x00, 0x01},
+
+	/* Setup compression and image registers. 0x6 and 0x7 seem to be
+	   related to H&V hold, and are resolution mode specific */
+		{0x04, 0x10, 0x01},
+		/* DIFF(0x50), was (0x10) */
+	{0x04, 0x00, 0x04},
+	{0x04, 0x00, 0x05},
+	{0x04, 0x20, 0x06},
+	{0x04, 0x20, 0x07},
+
+	{0x08, 0x0a, 0x00},
+	/* DIFF (0x4a), was (0xa) */
+
+	{0x05, 0x00, 0x10},
+	{0x05, 0x00, 0x11},
+	{0x05, 0x00, 0x00},
+	/* DIFF not written */
+	{0x05, 0x00, 0x01},
+	/* DIFF not written */
+	{0x05, 0x00, 0x02},
+	/* DIFF not written */
+	{0x05, 0x00, 0x03},
+	/* DIFF not written */
+	{0x05, 0x00, 0x04},
+	/* DIFF not written */
+		{0x05, 0x80, 0x05},
+		/* DIFF not written */
+		{0x05, 0xe0, 0x06},
+		/* DIFF not written */
+		{0x05, 0x20, 0x07},
+		/* DIFF not written */
+		{0x05, 0xa0, 0x08},
+		/* DIFF not written */
+		{0x05, 0x0, 0x12},
+		/* DIFF not written */
+	{0x05, 0x02, 0x0f},
+	/* DIFF not written */
+		{0x05, 0x10, 0x46},
+		/* DIFF not written */
+		{0x05, 0x8, 0x4a},
+		/* DIFF not written */
+
+	{0x03, 0x08, 0x03},
+	/* DIFF (0x3,0x28,0x3) */
+	{0x03, 0x08, 0x01},
+	{0x03, 0x0c, 0x03},
+	/* DIFF not written */
+		{0x03, 0x21, 0x00},
+		/* DIFF (0x39) */
+
+/* Extra block copied from init to hopefully ensure CCD is in a sane state */
+	{0x06, 0x10, 0x08},
+	{0x06, 0x00, 0x09},
+	{0x06, 0x00, 0x0a},
+	{0x06, 0x00, 0x0b},
+	{0x06, 0x10, 0x0c},
+	{0x06, 0x00, 0x0d},
+	{0x06, 0x00, 0x0e},
+	{0x06, 0x00, 0x0f},
+	{0x06, 0x10, 0x10},
+	{0x06, 0x02, 0x11},
+	{0x06, 0x00, 0x12},
+	{0x06, 0x04, 0x13},
+	{0x06, 0x02, 0x14},
+	{0x06, 0x8a, 0x51},
+	{0x06, 0x40, 0x52},
+	{0x06, 0xb6, 0x53},
+	{0x06, 0x3d, 0x54},
+	/* End of extra block */
+
+		{0x06, 0x3f, 0x1},
+		/* Block skipped */
+	{0x06, 0x10, 0x02},
+	{0x06, 0x64, 0x07},
+	{0x06, 0x10, 0x08},
+	{0x06, 0x00, 0x09},
+	{0x06, 0x00, 0x0a},
+	{0x06, 0x00, 0x0b},
+	{0x06, 0x10, 0x0c},
+	{0x06, 0x00, 0x0d},
+	{0x06, 0x00, 0x0e},
+	{0x06, 0x00, 0x0f},
+	{0x06, 0x10, 0x10},
+	{0x06, 0x02, 0x11},
+	{0x06, 0x00, 0x12},
+	{0x06, 0x04, 0x13},
+	{0x06, 0x02, 0x14},
+	{0x06, 0x8a, 0x51},
+	{0x06, 0x40, 0x52},
+	{0x06, 0xb6, 0x53},
+	{0x06, 0x3d, 0x54},
+	{0x06, 0x60, 0x57},
+	{0x06, 0x20, 0x58},
+	{0x06, 0x15, 0x59},
+	{0x06, 0x05, 0x5a},
+
+	{0x05, 0x01, 0xc0},
+	{0x05, 0x10, 0xcb},
+		{0x05, 0x80, 0xc1},
+		/* */
+		{0x05, 0x0, 0xc2},
+		/* 4 was 0 */
+	{0x05, 0x00, 0xca},
+		{0x05, 0x80, 0xc1},
+		/*  */
+	{0x05, 0x04, 0xc2},
+	{0x05, 0x00, 0xca},
+		{0x05, 0x0, 0xc1},
+		/*  */
+	{0x05, 0x00, 0xc2},
+	{0x05, 0x00, 0xca},
+		{0x05, 0x40, 0xc1},
+		/* */
+	{0x05, 0x17, 0xc2},
+	{0x05, 0x00, 0xca},
+		{0x05, 0x80, 0xc1},
+		/* */
+	{0x05, 0x06, 0xc2},
+	{0x05, 0x00, 0xca},
+		{0x05, 0x80, 0xc1},
+		/* */
+	{0x05, 0x04, 0xc2},
+	{0x05, 0x00, 0xca},
+
+	{0x03, 0x4c, 0x3},
+	{0x03, 0x18, 0x1},
+
+	{0x06, 0x70, 0x51},
+	{0x06, 0xbe, 0x53},
+	{0x06, 0x71, 0x57},
+	{0x06, 0x20, 0x58},
+	{0x06, 0x05, 0x59},
+	{0x06, 0x15, 0x5a},
+
+	{0x04, 0x00, 0x08},
+	/* Compress = OFF (0x1 to turn on) */
+	{0x04, 0x12, 0x09},
+	{0x04, 0x21, 0x0a},
+	{0x04, 0x10, 0x0b},
+	{0x04, 0x21, 0x0c},
+	{0x04, 0x05, 0x00},
+	/* was 5 (Image Type ? ) */
+	{0x04, 0x00, 0x01},
+
+	{0x06, 0x3f, 0x01},
+
+	{0x04, 0x00, 0x04},
+	{0x04, 0x00, 0x05},
+	{0x04, 0x40, 0x06},
+	{0x04, 0x40, 0x07},
+
+	{0x06, 0x1c, 0x17},
+	{0x06, 0xe2, 0x19},
+	{0x06, 0x1c, 0x1b},
+	{0x06, 0xe2, 0x1d},
+	{0x06, 0xaa, 0x1f},
+	{0x06, 0x70, 0x20},
+
+	{0x05, 0x01, 0x10},
+	{0x05, 0x00, 0x11},
+	{0x05, 0x01, 0x00},
+	{0x05, 0x05, 0x01},
+		{0x05, 0x00, 0xc1},
+		/* */
+	{0x05, 0x00, 0xc2},
+	{0x05, 0x00, 0xca},
+
+	{0x06, 0x70, 0x51},
+	{0x06, 0xbe, 0x53},
+	{}
+};
+
+/*
+ * Made by Tomasz Zablocki (skalamandra@poczta.onet.pl)
+ * SPCA505b chip based cameras initialization data
+ */
+/* jfm */
+#define initial_brightness 0x7f	/* 0x0(white)-0xff(black) */
+/* #define initial_brightness 0x0	//0x0(white)-0xff(black) */
+/*
+ * Data to initialize a SPCA505. Common to the CCD and external modes
+ */
+static const u8 spca505b_init_data[][3] = {
+/* start */
+	{0x02, 0x00, 0x00},		/* init */
+	{0x02, 0x00, 0x01},
+	{0x02, 0x00, 0x02},
+	{0x02, 0x00, 0x03},
+	{0x02, 0x00, 0x04},
+	{0x02, 0x00, 0x05},
+	{0x02, 0x00, 0x06},
+	{0x02, 0x00, 0x07},
+	{0x02, 0x00, 0x08},
+	{0x02, 0x00, 0x09},
+	{0x03, 0x00, 0x00},
+	{0x03, 0x00, 0x01},
+	{0x03, 0x00, 0x02},
+	{0x03, 0x00, 0x03},
+	{0x03, 0x00, 0x04},
+	{0x03, 0x00, 0x05},
+	{0x03, 0x00, 0x06},
+	{0x04, 0x00, 0x00},
+	{0x04, 0x00, 0x02},
+	{0x04, 0x00, 0x04},
+	{0x04, 0x00, 0x05},
+	{0x04, 0x00, 0x06},
+	{0x04, 0x00, 0x07},
+	{0x04, 0x00, 0x08},
+	{0x04, 0x00, 0x09},
+	{0x04, 0x00, 0x0a},
+	{0x04, 0x00, 0x0b},
+	{0x04, 0x00, 0x0c},
+	{0x07, 0x00, 0x00},
+	{0x07, 0x00, 0x03},
+	{0x08, 0x00, 0x00},
+	{0x08, 0x00, 0x01},
+	{0x08, 0x00, 0x02},
+	{0x06, 0x18, 0x08},
+	{0x06, 0xfc, 0x09},
+	{0x06, 0xfc, 0x0a},
+	{0x06, 0xfc, 0x0b},
+	{0x06, 0x18, 0x0c},
+	{0x06, 0xfc, 0x0d},
+	{0x06, 0xfc, 0x0e},
+	{0x06, 0xfc, 0x0f},
+	{0x06, 0x18, 0x10},
+	{0x06, 0xfe, 0x12},
+	{0x06, 0x00, 0x11},
+	{0x06, 0x00, 0x14},
+	{0x06, 0x00, 0x13},
+	{0x06, 0x28, 0x51},
+	{0x06, 0xff, 0x53},
+	{0x02, 0x00, 0x08},
+
+	{0x03, 0x00, 0x03},
+	{0x03, 0x10, 0x03},
+	{}
+};
+
+/*
+ * Data to initialize the camera using the internal CCD
+ */
+static const u8 spca505b_open_data_ccd[][3] = {
+
+/* {0x02,0x00,0x00}, */
+	{0x03, 0x04, 0x01},		/* rst */
+	{0x03, 0x00, 0x01},
+	{0x03, 0x00, 0x00},
+	{0x03, 0x21, 0x00},
+	{0x03, 0x00, 0x04},
+	{0x03, 0x00, 0x03},
+	{0x03, 0x18, 0x03},
+	{0x03, 0x08, 0x01},
+	{0x03, 0x1c, 0x03},
+	{0x03, 0x5c, 0x03},
+	{0x03, 0x5c, 0x03},
+	{0x03, 0x18, 0x01},
+
+/* same as 505 */
+	{0x04, 0x10, 0x01},
+	{0x04, 0x00, 0x04},
+	{0x04, 0x00, 0x05},
+	{0x04, 0x20, 0x06},
+	{0x04, 0x20, 0x07},
+
+	{0x08, 0x0a, 0x00},
+
+	{0x05, 0x00, 0x10},
+	{0x05, 0x00, 0x11},
+	{0x05, 0x00, 0x12},
+	{0x05, 0x6f, 0x00},
+	{0x05, initial_brightness >> 6, 0x00},
+	{0x05, (initial_brightness << 2) & 0xff, 0x01},
+	{0x05, 0x00, 0x02},
+	{0x05, 0x01, 0x03},
+	{0x05, 0x00, 0x04},
+	{0x05, 0x03, 0x05},
+	{0x05, 0xe0, 0x06},
+	{0x05, 0x20, 0x07},
+	{0x05, 0xa0, 0x08},
+	{0x05, 0x00, 0x12},
+	{0x05, 0x02, 0x0f},
+	{0x05, 0x80, 0x14},		/* max exposure off (0=on) */
+	{0x05, 0x01, 0xb0},
+	{0x05, 0x01, 0xbf},
+	{0x03, 0x02, 0x06},
+	{0x05, 0x10, 0x46},
+	{0x05, 0x08, 0x4a},
+
+	{0x06, 0x00, 0x01},
+	{0x06, 0x10, 0x02},
+	{0x06, 0x64, 0x07},
+	{0x06, 0x18, 0x08},
+	{0x06, 0xfc, 0x09},
+	{0x06, 0xfc, 0x0a},
+	{0x06, 0xfc, 0x0b},
+	{0x04, 0x00, 0x01},
+	{0x06, 0x18, 0x0c},
+	{0x06, 0xfc, 0x0d},
+	{0x06, 0xfc, 0x0e},
+	{0x06, 0xfc, 0x0f},
+	{0x06, 0x11, 0x10},		/* contrast */
+	{0x06, 0x00, 0x11},
+	{0x06, 0xfe, 0x12},
+	{0x06, 0x00, 0x13},
+	{0x06, 0x00, 0x14},
+	{0x06, 0x9d, 0x51},
+	{0x06, 0x40, 0x52},
+	{0x06, 0x7c, 0x53},
+	{0x06, 0x40, 0x54},
+	{0x06, 0x02, 0x57},
+	{0x06, 0x03, 0x58},
+	{0x06, 0x15, 0x59},
+	{0x06, 0x05, 0x5a},
+	{0x06, 0x03, 0x56},
+	{0x06, 0x02, 0x3f},
+	{0x06, 0x00, 0x40},
+	{0x06, 0x39, 0x41},
+	{0x06, 0x69, 0x42},
+	{0x06, 0x87, 0x43},
+	{0x06, 0x9e, 0x44},
+	{0x06, 0xb1, 0x45},
+	{0x06, 0xbf, 0x46},
+	{0x06, 0xcc, 0x47},
+	{0x06, 0xd5, 0x48},
+	{0x06, 0xdd, 0x49},
+	{0x06, 0xe3, 0x4a},
+	{0x06, 0xe8, 0x4b},
+	{0x06, 0xed, 0x4c},
+	{0x06, 0xf2, 0x4d},
+	{0x06, 0xf7, 0x4e},
+	{0x06, 0xfc, 0x4f},
+	{0x06, 0xff, 0x50},
+
+	{0x05, 0x01, 0xc0},
+	{0x05, 0x10, 0xcb},
+	{0x05, 0x40, 0xc1},
+	{0x05, 0x04, 0xc2},
+	{0x05, 0x00, 0xca},
+	{0x05, 0x40, 0xc1},
+	{0x05, 0x09, 0xc2},
+	{0x05, 0x00, 0xca},
+	{0x05, 0xc0, 0xc1},
+	{0x05, 0x09, 0xc2},
+	{0x05, 0x00, 0xca},
+	{0x05, 0x40, 0xc1},
+	{0x05, 0x59, 0xc2},
+	{0x05, 0x00, 0xca},
+	{0x04, 0x00, 0x01},
+	{0x05, 0x80, 0xc1},
+	{0x05, 0xec, 0xc2},
+	{0x05, 0x0, 0xca},
+
+	{0x06, 0x02, 0x57},
+	{0x06, 0x01, 0x58},
+	{0x06, 0x15, 0x59},
+	{0x06, 0x0a, 0x5a},
+	{0x06, 0x01, 0x57},
+	{0x06, 0x8a, 0x03},
+	{0x06, 0x0a, 0x6c},
+	{0x06, 0x30, 0x01},
+	{0x06, 0x20, 0x02},
+	{0x06, 0x00, 0x03},
+
+	{0x05, 0x8c, 0x25},
+
+	{0x06, 0x4d, 0x51},		/* maybe saturation (4d) */
+	{0x06, 0x84, 0x53},		/* making green (84) */
+	{0x06, 0x00, 0x57},		/* sharpness (1) */
+	{0x06, 0x18, 0x08},
+	{0x06, 0xfc, 0x09},
+	{0x06, 0xfc, 0x0a},
+	{0x06, 0xfc, 0x0b},
+	{0x06, 0x18, 0x0c},		/* maybe hue (18) */
+	{0x06, 0xfc, 0x0d},
+	{0x06, 0xfc, 0x0e},
+	{0x06, 0xfc, 0x0f},
+	{0x06, 0x18, 0x10},		/* maybe contrast (18) */
+
+	{0x05, 0x01, 0x02},
+
+	{0x04, 0x00, 0x08},		/* compression */
+	{0x04, 0x12, 0x09},
+	{0x04, 0x21, 0x0a},
+	{0x04, 0x10, 0x0b},
+	{0x04, 0x21, 0x0c},
+	{0x04, 0x1d, 0x00},		/* imagetype (1d) */
+	{0x04, 0x41, 0x01},		/* hardware snapcontrol */
+
+	{0x04, 0x00, 0x04},
+	{0x04, 0x00, 0x05},
+	{0x04, 0x10, 0x06},
+	{0x04, 0x10, 0x07},
+	{0x04, 0x40, 0x06},
+	{0x04, 0x40, 0x07},
+	{0x04, 0x00, 0x04},
+	{0x04, 0x00, 0x05},
+
+	{0x06, 0x1c, 0x17},
+	{0x06, 0xe2, 0x19},
+	{0x06, 0x1c, 0x1b},
+	{0x06, 0xe2, 0x1d},
+	{0x06, 0x5f, 0x1f},
+	{0x06, 0x32, 0x20},
+
+	{0x05, initial_brightness >> 6, 0x00},
+	{0x05, (initial_brightness << 2) & 0xff, 0x01},
+	{0x05, 0x06, 0xc1},
+	{0x05, 0x58, 0xc2},
+	{0x05, 0x00, 0xca},
+	{0x05, 0x00, 0x11},
+	{}
+};
+
+static int reg_write(struct gspca_dev *gspca_dev,
+		     u16 req, u16 index, u16 value)
+{
+	int ret;
+	struct usb_device *dev = gspca_dev->dev;
+
+	ret = usb_control_msg(dev,
+			usb_sndctrlpipe(dev, 0),
+			req,
+			USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			value, index, NULL, 0, 500);
+	gspca_dbg(gspca_dev, D_USBO, "reg write: 0x%02x,0x%02x:0x%02x, %d\n",
+		  req, index, value, ret);
+	if (ret < 0)
+		pr_err("reg write: error %d\n", ret);
+	return ret;
+}
+
+/* returns: negative is error, pos or zero is data */
+static int reg_read(struct gspca_dev *gspca_dev,
+			u16 req,	/* bRequest */
+			u16 index)	/* wIndex */
+{
+	int ret;
+
+	ret = usb_control_msg(gspca_dev->dev,
+			usb_rcvctrlpipe(gspca_dev->dev, 0),
+			req,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0,			/* value */
+			index,
+			gspca_dev->usb_buf, 2,
+			500);			/* timeout */
+	if (ret < 0)
+		return ret;
+	return (gspca_dev->usb_buf[1] << 8) + gspca_dev->usb_buf[0];
+}
+
+static int write_vector(struct gspca_dev *gspca_dev,
+			const u8 data[][3])
+{
+	int ret, i = 0;
+
+	while (data[i][0] != 0) {
+		ret = reg_write(gspca_dev, data[i][0], data[i][2],
+								data[i][1]);
+		if (ret < 0)
+			return ret;
+		i++;
+	}
+	return 0;
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam;
+
+	cam = &gspca_dev->cam;
+	cam->cam_mode = vga_mode;
+	sd->subtype = id->driver_info;
+	if (sd->subtype != IntelPCCameraPro)
+		cam->nmodes = ARRAY_SIZE(vga_mode);
+	else			/* no 640x480 for IntelPCCameraPro */
+		cam->nmodes = ARRAY_SIZE(vga_mode) - 1;
+
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (write_vector(gspca_dev,
+			 sd->subtype == Nxultra
+				? spca505b_init_data
+				: spca505_init_data))
+		return -EIO;
+	return 0;
+}
+
+static void setbrightness(struct gspca_dev *gspca_dev, s32 brightness)
+{
+	reg_write(gspca_dev, 0x05, 0x00, (255 - brightness) >> 6);
+	reg_write(gspca_dev, 0x05, 0x01, (255 - brightness) << 2);
+}
+
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int ret, mode;
+	static u8 mode_tb[][3] = {
+	/*	  r00   r06   r07	*/
+		{0x00, 0x10, 0x10},	/* 640x480 */
+		{0x01, 0x1a, 0x1a},	/* 352x288 */
+		{0x02, 0x1c, 0x1d},	/* 320x240 */
+		{0x04, 0x34, 0x34},	/* 176x144 */
+		{0x05, 0x40, 0x40}	/* 160x120 */
+	};
+
+	if (sd->subtype == Nxultra)
+		write_vector(gspca_dev, spca505b_open_data_ccd);
+	else
+		write_vector(gspca_dev, spca505_open_data_ccd);
+	ret = reg_read(gspca_dev, 0x06, 0x16);
+
+	if (ret < 0) {
+		gspca_err(gspca_dev, "register read failed err: %d\n", ret);
+		return ret;
+	}
+	if (ret != 0x0101) {
+		pr_err("After vector read returns 0x%04x should be 0x0101\n",
+		       ret);
+	}
+
+	ret = reg_write(gspca_dev, 0x06, 0x16, 0x0a);
+	if (ret < 0)
+		return ret;
+	reg_write(gspca_dev, 0x05, 0xc2, 0x12);
+
+	/* necessary because without it we can see stream
+	 * only once after loading module */
+	/* stopping usb registers Tomasz change */
+	reg_write(gspca_dev, 0x02, 0x00, 0x00);
+
+	mode = gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv;
+	reg_write(gspca_dev, SPCA50X_REG_COMPRESS, 0x00, mode_tb[mode][0]);
+	reg_write(gspca_dev, SPCA50X_REG_COMPRESS, 0x06, mode_tb[mode][1]);
+	reg_write(gspca_dev, SPCA50X_REG_COMPRESS, 0x07, mode_tb[mode][2]);
+
+	return reg_write(gspca_dev, SPCA50X_REG_USB,
+			 SPCA50X_USB_CTRL,
+			 SPCA50X_CUSB_ENABLE);
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	/* Disable ISO packet machine */
+	reg_write(gspca_dev, 0x02, 0x00, 0x00);
+}
+
+/* called on streamoff with alt 0 and on disconnect */
+static void sd_stop0(struct gspca_dev *gspca_dev)
+{
+	if (!gspca_dev->present)
+		return;
+
+	/* This maybe reset or power control */
+	reg_write(gspca_dev, 0x03, 0x03, 0x20);
+	reg_write(gspca_dev, 0x03, 0x01, 0x00);
+	reg_write(gspca_dev, 0x03, 0x00, 0x01);
+	reg_write(gspca_dev, 0x05, 0x10, 0x01);
+	reg_write(gspca_dev, 0x05, 0x11, 0x0f);
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	switch (data[0]) {
+	case 0:				/* start of frame */
+		gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
+		data += SPCA50X_OFFSET_DATA;
+		len -= SPCA50X_OFFSET_DATA;
+		gspca_frame_add(gspca_dev, FIRST_PACKET, data, len);
+		break;
+	case 0xff:			/* drop */
+		break;
+	default:
+		data += 1;
+		len -= 1;
+		gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+		break;
+	}
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		setbrightness(gspca_dev, ctrl->val);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 5);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	return 0;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init_controls = sd_init_controls,
+	.init = sd_init,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.stop0 = sd_stop0,
+	.pkt_scan = sd_pkt_scan,
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x041e, 0x401d), .driver_info = Nxultra},
+	{USB_DEVICE(0x0733, 0x0430), .driver_info = IntelPCCameraPro},
+/*fixme: may be UsbGrabberPV321 BRIDGE_SPCA506 SENSOR_SAA7113 */
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/spca506.c b/drivers/media/usb/gspca/spca506.c
new file mode 100644
index 0000000..6332b3f
--- /dev/null
+++ b/drivers/media/usb/gspca/spca506.c
@@ -0,0 +1,608 @@
+/*
+ * SPCA506 chip based cameras function
+ * M Xhaard 15/04/2004 based on different work Mark Taylor and others
+ * and my own snoopy file on a pv-321c donate by a german compagny
+ *                "Firma Frank Gmbh" from  Saarbruecken
+ *
+ * V4L2 by Jean-Francois Moine <http://moinejf.free.fr>
+ *
+ * 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
+ * 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.
+ */
+
+#define MODULE_NAME "spca506"
+
+#include "gspca.h"
+
+MODULE_AUTHOR("Michel Xhaard <mxhaard@users.sourceforge.net>");
+MODULE_DESCRIPTION("GSPCA/SPCA506 USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+
+	char norme;
+	char channel;
+};
+
+static const struct v4l2_pix_format vga_mode[] = {
+	{160, 120, V4L2_PIX_FMT_SPCA505, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 160 * 120 * 3 / 2,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 5},
+	{176, 144, V4L2_PIX_FMT_SPCA505, V4L2_FIELD_NONE,
+		.bytesperline = 176,
+		.sizeimage = 176 * 144 * 3 / 2,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 4},
+	{320, 240, V4L2_PIX_FMT_SPCA505, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3 / 2,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 2},
+	{352, 288, V4L2_PIX_FMT_SPCA505, V4L2_FIELD_NONE,
+		.bytesperline = 352,
+		.sizeimage = 352 * 288 * 3 / 2,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1},
+	{640, 480, V4L2_PIX_FMT_SPCA505, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 3 / 2,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+};
+
+#define SPCA50X_OFFSET_DATA 10
+
+#define SAA7113_bright 0x0a	/* defaults 0x80 */
+#define SAA7113_contrast 0x0b	/* defaults 0x47 */
+#define SAA7113_saturation 0x0c	/* defaults 0x40 */
+#define SAA7113_hue 0x0d	/* defaults 0x00 */
+#define SAA7113_I2C_BASE_WRITE 0x4a
+
+/* read 'len' bytes to gspca_dev->usb_buf */
+static void reg_r(struct gspca_dev *gspca_dev,
+		  __u16 req,
+		  __u16 index,
+		  __u16 length)
+{
+	usb_control_msg(gspca_dev->dev,
+			usb_rcvctrlpipe(gspca_dev->dev, 0),
+			req,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0,		/* value */
+			index, gspca_dev->usb_buf, length,
+			500);
+}
+
+static void reg_w(struct usb_device *dev,
+		  __u16 req,
+		  __u16 value,
+		  __u16 index)
+{
+	usb_control_msg(dev,
+			usb_sndctrlpipe(dev, 0),
+			req,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			value, index,
+			NULL, 0, 500);
+}
+
+static void spca506_Initi2c(struct gspca_dev *gspca_dev)
+{
+	reg_w(gspca_dev->dev, 0x07, SAA7113_I2C_BASE_WRITE, 0x0004);
+}
+
+static void spca506_WriteI2c(struct gspca_dev *gspca_dev, __u16 valeur,
+			     __u16 reg)
+{
+	int retry = 60;
+
+	reg_w(gspca_dev->dev, 0x07, reg, 0x0001);
+	reg_w(gspca_dev->dev, 0x07, valeur, 0x0000);
+	while (retry--) {
+		reg_r(gspca_dev, 0x07, 0x0003, 2);
+		if ((gspca_dev->usb_buf[0] | gspca_dev->usb_buf[1]) == 0x00)
+			break;
+	}
+}
+
+static void spca506_SetNormeInput(struct gspca_dev *gspca_dev,
+				 __u16 norme,
+				 __u16 channel)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+/* fixme: check if channel == 0..3 and 6..9 (8 values) */
+	__u8 setbit0 = 0x00;
+	__u8 setbit1 = 0x00;
+	__u8 videomask = 0x00;
+
+	gspca_dbg(gspca_dev, D_STREAM, "** Open Set Norme **\n");
+	spca506_Initi2c(gspca_dev);
+	/* NTSC bit0 -> 1(525 l) PAL SECAM bit0 -> 0 (625 l) */
+	/* Composite channel bit1 -> 1 S-video bit 1 -> 0 */
+	/* and exclude SAA7113 reserved channel set default 0 otherwise */
+	if (norme & V4L2_STD_NTSC)
+		setbit0 = 0x01;
+	if (channel == 4 || channel == 5 || channel > 9)
+		channel = 0;
+	if (channel < 4)
+		setbit1 = 0x02;
+	videomask = (0x48 | setbit0 | setbit1);
+	reg_w(gspca_dev->dev, 0x08, videomask, 0x0000);
+	spca506_WriteI2c(gspca_dev, (0xc0 | (channel & 0x0F)), 0x02);
+
+	if (norme & V4L2_STD_NTSC)
+		spca506_WriteI2c(gspca_dev, 0x33, 0x0e);
+					/* Chrominance Control NTSC N */
+	else if (norme & V4L2_STD_SECAM)
+		spca506_WriteI2c(gspca_dev, 0x53, 0x0e);
+					/* Chrominance Control SECAM */
+	else
+		spca506_WriteI2c(gspca_dev, 0x03, 0x0e);
+					/* Chrominance Control PAL BGHIV */
+
+	sd->norme = norme;
+	sd->channel = channel;
+	gspca_dbg(gspca_dev, D_STREAM, "Set Video Byte to 0x%2x\n", videomask);
+	gspca_dbg(gspca_dev, D_STREAM, "Set Norme: %08x Channel %d",
+		  norme, channel);
+}
+
+static void spca506_GetNormeInput(struct gspca_dev *gspca_dev,
+				  __u16 *norme, __u16 *channel)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	/* Read the register is not so good value change so
+	   we use your own copy in spca50x struct */
+	*norme = sd->norme;
+	*channel = sd->channel;
+	gspca_dbg(gspca_dev, D_STREAM, "Get Norme: %d Channel %d\n",
+		  *norme, *channel);
+}
+
+static void spca506_Setsize(struct gspca_dev *gspca_dev, __u16 code,
+			    __u16 xmult, __u16 ymult)
+{
+	struct usb_device *dev = gspca_dev->dev;
+
+	gspca_dbg(gspca_dev, D_STREAM, "** SetSize **\n");
+	reg_w(dev, 0x04, (0x18 | (code & 0x07)), 0x0000);
+	/* Soft snap 0x40 Hard 0x41 */
+	reg_w(dev, 0x04, 0x41, 0x0001);
+	reg_w(dev, 0x04, 0x00, 0x0002);
+	/* reserved */
+	reg_w(dev, 0x04, 0x00, 0x0003);
+
+	/* reserved */
+	reg_w(dev, 0x04, 0x00, 0x0004);
+	/* reserved */
+	reg_w(dev, 0x04, 0x01, 0x0005);
+	/* reserced */
+	reg_w(dev, 0x04, xmult, 0x0006);
+	/* reserved */
+	reg_w(dev, 0x04, ymult, 0x0007);
+	/* compression 1 */
+	reg_w(dev, 0x04, 0x00, 0x0008);
+	/* T=64 -> 2 */
+	reg_w(dev, 0x04, 0x00, 0x0009);
+	/* threshold2D */
+	reg_w(dev, 0x04, 0x21, 0x000a);
+	/* quantization */
+	reg_w(dev, 0x04, 0x00, 0x000b);
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	struct cam *cam;
+
+	cam = &gspca_dev->cam;
+	cam->cam_mode = vga_mode;
+	cam->nmodes = ARRAY_SIZE(vga_mode);
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	struct usb_device *dev = gspca_dev->dev;
+
+	reg_w(dev, 0x03, 0x00, 0x0004);
+	reg_w(dev, 0x03, 0xFF, 0x0003);
+	reg_w(dev, 0x03, 0x00, 0x0000);
+	reg_w(dev, 0x03, 0x1c, 0x0001);
+	reg_w(dev, 0x03, 0x18, 0x0001);
+	/* Init on PAL and composite input0 */
+	spca506_SetNormeInput(gspca_dev, 0, 0);
+	reg_w(dev, 0x03, 0x1c, 0x0001);
+	reg_w(dev, 0x03, 0x18, 0x0001);
+	reg_w(dev, 0x05, 0x00, 0x0000);
+	reg_w(dev, 0x05, 0xef, 0x0001);
+	reg_w(dev, 0x05, 0x00, 0x00c1);
+	reg_w(dev, 0x05, 0x00, 0x00c2);
+	reg_w(dev, 0x06, 0x18, 0x0002);
+	reg_w(dev, 0x06, 0xf5, 0x0011);
+	reg_w(dev, 0x06, 0x02, 0x0012);
+	reg_w(dev, 0x06, 0xfb, 0x0013);
+	reg_w(dev, 0x06, 0x00, 0x0014);
+	reg_w(dev, 0x06, 0xa4, 0x0051);
+	reg_w(dev, 0x06, 0x40, 0x0052);
+	reg_w(dev, 0x06, 0x71, 0x0053);
+	reg_w(dev, 0x06, 0x40, 0x0054);
+	/************************************************/
+	reg_w(dev, 0x03, 0x00, 0x0004);
+	reg_w(dev, 0x03, 0x00, 0x0003);
+	reg_w(dev, 0x03, 0x00, 0x0004);
+	reg_w(dev, 0x03, 0xFF, 0x0003);
+	reg_w(dev, 0x02, 0x00, 0x0000);
+	reg_w(dev, 0x03, 0x60, 0x0000);
+	reg_w(dev, 0x03, 0x18, 0x0001);
+	/* for a better reading mx :)	  */
+	/*sdca506_WriteI2c(value,register) */
+	spca506_Initi2c(gspca_dev);
+	spca506_WriteI2c(gspca_dev, 0x08, 0x01);
+	spca506_WriteI2c(gspca_dev, 0xc0, 0x02);
+						/* input composite video */
+	spca506_WriteI2c(gspca_dev, 0x33, 0x03);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x04);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x05);
+	spca506_WriteI2c(gspca_dev, 0x0d, 0x06);
+	spca506_WriteI2c(gspca_dev, 0xf0, 0x07);
+	spca506_WriteI2c(gspca_dev, 0x98, 0x08);
+	spca506_WriteI2c(gspca_dev, 0x03, 0x09);
+	spca506_WriteI2c(gspca_dev, 0x80, 0x0a);
+	spca506_WriteI2c(gspca_dev, 0x47, 0x0b);
+	spca506_WriteI2c(gspca_dev, 0x48, 0x0c);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x0d);
+	spca506_WriteI2c(gspca_dev, 0x03, 0x0e);	/* Chroma Pal adjust */
+	spca506_WriteI2c(gspca_dev, 0x2a, 0x0f);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x10);
+	spca506_WriteI2c(gspca_dev, 0x0c, 0x11);
+	spca506_WriteI2c(gspca_dev, 0xb8, 0x12);
+	spca506_WriteI2c(gspca_dev, 0x01, 0x13);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x14);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x15);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x16);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x17);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x18);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x19);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x1a);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x1b);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x1c);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x1d);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x1e);
+	spca506_WriteI2c(gspca_dev, 0xa1, 0x1f);
+	spca506_WriteI2c(gspca_dev, 0x02, 0x40);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x41);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x42);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x43);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x44);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x45);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x46);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x47);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x48);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x49);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x4a);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x4b);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x4c);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x4d);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x4e);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x4f);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x50);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x51);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x52);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x53);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x54);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x55);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x56);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x57);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x58);
+	spca506_WriteI2c(gspca_dev, 0x54, 0x59);
+	spca506_WriteI2c(gspca_dev, 0x07, 0x5a);
+	spca506_WriteI2c(gspca_dev, 0x83, 0x5b);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x5c);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x5d);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x5e);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x5f);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x60);
+	spca506_WriteI2c(gspca_dev, 0x05, 0x61);
+	spca506_WriteI2c(gspca_dev, 0x9f, 0x62);
+	gspca_dbg(gspca_dev, D_STREAM, "** Close Init *\n");
+	return 0;
+}
+
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct usb_device *dev = gspca_dev->dev;
+	__u16 norme;
+	__u16 channel;
+
+	/**************************************/
+	reg_w(dev, 0x03, 0x00, 0x0004);
+	reg_w(dev, 0x03, 0x00, 0x0003);
+	reg_w(dev, 0x03, 0x00, 0x0004);
+	reg_w(dev, 0x03, 0xFF, 0x0003);
+	reg_w(dev, 0x02, 0x00, 0x0000);
+	reg_w(dev, 0x03, 0x60, 0x0000);
+	reg_w(dev, 0x03, 0x18, 0x0001);
+
+	/*sdca506_WriteI2c(value,register) */
+	spca506_Initi2c(gspca_dev);
+	spca506_WriteI2c(gspca_dev, 0x08, 0x01);	/* Increment Delay */
+/*	spca506_WriteI2c(gspca_dev, 0xc0, 0x02); * Analog Input Control 1 */
+	spca506_WriteI2c(gspca_dev, 0x33, 0x03);
+						/* Analog Input Control 2 */
+	spca506_WriteI2c(gspca_dev, 0x00, 0x04);
+						/* Analog Input Control 3 */
+	spca506_WriteI2c(gspca_dev, 0x00, 0x05);
+						/* Analog Input Control 4 */
+	spca506_WriteI2c(gspca_dev, 0x0d, 0x06);
+					/* Horizontal Sync Start 0xe9-0x0d */
+	spca506_WriteI2c(gspca_dev, 0xf0, 0x07);
+					/* Horizontal Sync Stop  0x0d-0xf0 */
+
+	spca506_WriteI2c(gspca_dev, 0x98, 0x08);	/* Sync Control */
+/*		Defaults value			*/
+	spca506_WriteI2c(gspca_dev, 0x03, 0x09);	/* Luminance Control */
+	spca506_WriteI2c(gspca_dev, 0x80, 0x0a);
+						/* Luminance Brightness */
+	spca506_WriteI2c(gspca_dev, 0x47, 0x0b);	/* Luminance Contrast */
+	spca506_WriteI2c(gspca_dev, 0x48, 0x0c);
+						/* Chrominance Saturation */
+	spca506_WriteI2c(gspca_dev, 0x00, 0x0d);
+						/* Chrominance Hue Control */
+	spca506_WriteI2c(gspca_dev, 0x2a, 0x0f);
+						/* Chrominance Gain Control */
+	/**************************************/
+	spca506_WriteI2c(gspca_dev, 0x00, 0x10);
+						/* Format/Delay Control */
+	spca506_WriteI2c(gspca_dev, 0x0c, 0x11);	/* Output Control 1 */
+	spca506_WriteI2c(gspca_dev, 0xb8, 0x12);	/* Output Control 2 */
+	spca506_WriteI2c(gspca_dev, 0x01, 0x13);	/* Output Control 3 */
+	spca506_WriteI2c(gspca_dev, 0x00, 0x14);	/* reserved */
+	spca506_WriteI2c(gspca_dev, 0x00, 0x15);	/* VGATE START */
+	spca506_WriteI2c(gspca_dev, 0x00, 0x16);	/* VGATE STOP */
+	spca506_WriteI2c(gspca_dev, 0x00, 0x17);    /* VGATE Control (MSB) */
+	spca506_WriteI2c(gspca_dev, 0x00, 0x18);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x19);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x1a);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x1b);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x1c);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x1d);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x1e);
+	spca506_WriteI2c(gspca_dev, 0xa1, 0x1f);
+	spca506_WriteI2c(gspca_dev, 0x02, 0x40);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x41);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x42);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x43);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x44);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x45);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x46);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x47);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x48);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x49);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x4a);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x4b);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x4c);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x4d);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x4e);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x4f);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x50);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x51);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x52);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x53);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x54);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x55);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x56);
+	spca506_WriteI2c(gspca_dev, 0xff, 0x57);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x58);
+	spca506_WriteI2c(gspca_dev, 0x54, 0x59);
+	spca506_WriteI2c(gspca_dev, 0x07, 0x5a);
+	spca506_WriteI2c(gspca_dev, 0x83, 0x5b);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x5c);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x5d);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x5e);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x5f);
+	spca506_WriteI2c(gspca_dev, 0x00, 0x60);
+	spca506_WriteI2c(gspca_dev, 0x05, 0x61);
+	spca506_WriteI2c(gspca_dev, 0x9f, 0x62);
+	/**************************************/
+	reg_w(dev, 0x05, 0x00, 0x0003);
+	reg_w(dev, 0x05, 0x00, 0x0004);
+	reg_w(dev, 0x03, 0x10, 0x0001);
+	reg_w(dev, 0x03, 0x78, 0x0000);
+	switch (gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv) {
+	case 0:
+		spca506_Setsize(gspca_dev, 0, 0x10, 0x10);
+		break;
+	case 1:
+		spca506_Setsize(gspca_dev, 1, 0x1a, 0x1a);
+		break;
+	case 2:
+		spca506_Setsize(gspca_dev, 2, 0x1c, 0x1c);
+		break;
+	case 4:
+		spca506_Setsize(gspca_dev, 4, 0x34, 0x34);
+		break;
+	default:
+/*	case 5: */
+		spca506_Setsize(gspca_dev, 5, 0x40, 0x40);
+		break;
+	}
+
+	/* compress setting and size */
+	/* set i2c luma */
+	reg_w(dev, 0x02, 0x01, 0x0000);
+	reg_w(dev, 0x03, 0x12, 0x0000);
+	reg_r(gspca_dev, 0x04, 0x0001, 2);
+	gspca_dbg(gspca_dev, D_STREAM, "webcam started\n");
+	spca506_GetNormeInput(gspca_dev, &norme, &channel);
+	spca506_SetNormeInput(gspca_dev, norme, channel);
+	return 0;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	struct usb_device *dev = gspca_dev->dev;
+
+	reg_w(dev, 0x02, 0x00, 0x0000);
+	reg_w(dev, 0x03, 0x00, 0x0004);
+	reg_w(dev, 0x03, 0x00, 0x0003);
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	switch (data[0]) {
+	case 0:				/* start of frame */
+		gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
+		data += SPCA50X_OFFSET_DATA;
+		len -= SPCA50X_OFFSET_DATA;
+		gspca_frame_add(gspca_dev, FIRST_PACKET, data, len);
+		break;
+	case 0xff:			/* drop */
+/*		gspca_dev->last_packet_type = DISCARD_PACKET; */
+		break;
+	default:
+		data += 1;
+		len -= 1;
+		gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+		break;
+	}
+}
+
+static void setbrightness(struct gspca_dev *gspca_dev, s32 val)
+{
+	spca506_Initi2c(gspca_dev);
+	spca506_WriteI2c(gspca_dev, val, SAA7113_bright);
+	spca506_WriteI2c(gspca_dev, 0x01, 0x09);
+}
+
+static void setcontrast(struct gspca_dev *gspca_dev, s32 val)
+{
+	spca506_Initi2c(gspca_dev);
+	spca506_WriteI2c(gspca_dev, val, SAA7113_contrast);
+	spca506_WriteI2c(gspca_dev, 0x01, 0x09);
+}
+
+static void setcolors(struct gspca_dev *gspca_dev, s32 val)
+{
+	spca506_Initi2c(gspca_dev);
+	spca506_WriteI2c(gspca_dev, val, SAA7113_saturation);
+	spca506_WriteI2c(gspca_dev, 0x01, 0x09);
+}
+
+static void sethue(struct gspca_dev *gspca_dev, s32 val)
+{
+	spca506_Initi2c(gspca_dev);
+	spca506_WriteI2c(gspca_dev, val, SAA7113_hue);
+	spca506_WriteI2c(gspca_dev, 0x01, 0x09);
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		setbrightness(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_CONTRAST:
+		setcontrast(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_SATURATION:
+		setcolors(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_HUE:
+		sethue(gspca_dev, ctrl->val);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 4);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0, 255, 1, 128);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_CONTRAST, 0, 255, 1, 0x47);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_SATURATION, 0, 255, 1, 0x40);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_HUE, 0, 255, 1, 0);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	return 0;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.pkt_scan = sd_pkt_scan,
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x06e1, 0xa190)},
+/*	{USB_DEVICE(0x0733, 0x0430)}, FIXME: may be IntelPCCameraPro BRIDGE_SPCA505 */
+	{USB_DEVICE(0x0734, 0x043b)},
+	{USB_DEVICE(0x99fa, 0x8988)},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/spca508.c b/drivers/media/usb/gspca/spca508.c
new file mode 100644
index 0000000..d80fd39
--- /dev/null
+++ b/drivers/media/usb/gspca/spca508.c
@@ -0,0 +1,1535 @@
+/*
+ * SPCA508 chip based cameras subdriver
+ *
+ * Copyright (C) 2009 Jean-Francois Moine <http://moinejf.free.fr>
+ *
+ * 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
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "spca508"
+
+#include "gspca.h"
+
+MODULE_AUTHOR("Michel Xhaard <mxhaard@users.sourceforge.net>");
+MODULE_DESCRIPTION("GSPCA/SPCA508 USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;		/* !! must be the first item */
+
+	u8 subtype;
+#define CreativeVista 0
+#define HamaUSBSightcam 1
+#define HamaUSBSightcam2 2
+#define IntelEasyPCCamera 3
+#define MicroInnovationIC200 4
+#define ViewQuestVQ110 5
+};
+
+static const struct v4l2_pix_format sif_mode[] = {
+	{160, 120, V4L2_PIX_FMT_SPCA508, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 160 * 120 * 3 / 2,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 3},
+	{176, 144, V4L2_PIX_FMT_SPCA508, V4L2_FIELD_NONE,
+		.bytesperline = 176,
+		.sizeimage = 176 * 144 * 3 / 2,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 2},
+	{320, 240, V4L2_PIX_FMT_SPCA508, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3 / 2,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1},
+	{352, 288, V4L2_PIX_FMT_SPCA508, V4L2_FIELD_NONE,
+		.bytesperline = 352,
+		.sizeimage = 352 * 288 * 3 / 2,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+};
+
+/* Frame packet header offsets for the spca508 */
+#define SPCA508_OFFSET_DATA 37
+
+/*
+ * Initialization data: this is the first set-up data written to the
+ * device (before the open data).
+ */
+static const u16 spca508_init_data[][2] = {
+	{0x0000, 0x870b},
+
+	{0x0020, 0x8112},	/* Video drop enable, ISO streaming disable */
+	{0x0003, 0x8111},	/* Reset compression & memory */
+	{0x0000, 0x8110},	/* Disable all outputs */
+	/* READ {0x0000, 0x8114} -> 0000: 00  */
+	{0x0000, 0x8114},	/* SW GPIO data */
+	{0x0008, 0x8110},	/* Enable charge pump output */
+	{0x0002, 0x8116},	/* 200 kHz pump clock */
+	/* UNKNOWN DIRECTION (URB_FUNCTION_SELECT_INTERFACE:) */
+	{0x0003, 0x8111},	/* Reset compression & memory */
+	{0x0000, 0x8111},	/* Normal mode (not reset) */
+	{0x0098, 0x8110},
+		/* Enable charge pump output, sync.serial,external 2x clock */
+	{0x000d, 0x8114},	/* SW GPIO data */
+	{0x0002, 0x8116},	/* 200 kHz pump clock */
+	{0x0020, 0x8112},	/* Video drop enable, ISO streaming disable */
+/* --------------------------------------- */
+	{0x000f, 0x8402},	/* memory bank */
+	{0x0000, 0x8403},	/* ... address */
+/* --------------------------------------- */
+/* 0x88__ is Synchronous Serial Interface. */
+/* TBD: This table could be expressed more compactly */
+/* using spca508_write_i2c_vector(). */
+/* TBD: Should see if the values in spca50x_i2c_data */
+/* would work with the VQ110 instead of the values */
+/* below. */
+	{0x00c0, 0x8804},	/* SSI slave addr */
+	{0x0008, 0x8802},	/* 375 Khz SSI clock */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},	/* 375 Khz SSI clock */
+	{0x0012, 0x8801},	/* SSI reg addr */
+	{0x0080, 0x8800},	/* SSI data to write */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},	/* 375 Khz SSI clock */
+	{0x0012, 0x8801},	/* SSI reg addr */
+	{0x0000, 0x8800},	/* SSI data to write */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},	/* 375 Khz SSI clock */
+	{0x0011, 0x8801},	/* SSI reg addr */
+	{0x0040, 0x8800},	/* SSI data to write */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x0013, 0x8801},
+	{0x0000, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x0014, 0x8801},
+	{0x0000, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x0015, 0x8801},
+	{0x0001, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x0016, 0x8801},
+	{0x0003, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x0017, 0x8801},
+	{0x0036, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x0018, 0x8801},
+	{0x00ec, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x001a, 0x8801},
+	{0x0094, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x001b, 0x8801},
+	{0x0000, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x0027, 0x8801},
+	{0x00a2, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x0028, 0x8801},
+	{0x0040, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x002a, 0x8801},
+	{0x0084, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00 */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x002b, 0x8801},
+	{0x00a8, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x002c, 0x8801},
+	{0x00fe, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x002d, 0x8801},
+	{0x0003, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x0038, 0x8801},
+	{0x0083, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x0033, 0x8801},
+	{0x0081, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x0034, 0x8801},
+	{0x004a, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x0039, 0x8801},
+	{0x0000, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x0010, 0x8801},
+	{0x00a8, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x0006, 0x8801},
+	{0x0058, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00 */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x0000, 0x8801},
+	{0x0004, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x0040, 0x8801},
+	{0x0080, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x0041, 0x8801},
+	{0x000c, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x0042, 0x8801},
+	{0x000c, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x0043, 0x8801},
+	{0x0028, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x0044, 0x8801},
+	{0x0080, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x0045, 0x8801},
+	{0x0020, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x0046, 0x8801},
+	{0x0020, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x0047, 0x8801},
+	{0x0080, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x0048, 0x8801},
+	{0x004c, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x0049, 0x8801},
+	{0x0084, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x004a, 0x8801},
+	{0x0084, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x0008, 0x8802},
+	{0x004b, 0x8801},
+	{0x0084, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* --------------------------------------- */
+	{0x0012, 0x8700},	/* Clock speed 48Mhz/(2+2)/2= 6 Mhz */
+	{0x0000, 0x8701},	/* CKx1 clock delay adj */
+	{0x0000, 0x8701},	/* CKx1 clock delay adj */
+	{0x0001, 0x870c},	/* CKOx2 output */
+	/* --------------------------------------- */
+	{0x0080, 0x8600},	/* Line memory read counter (L) */
+	{0x0001, 0x8606},	/* reserved */
+	{0x0064, 0x8607},	/* Line memory read counter (H) 0x6480=25,728 */
+	{0x002a, 0x8601},	/* CDSP sharp interpolation mode,
+	 *			line sel for color sep, edge enhance enab */
+	{0x0000, 0x8602},	/* optical black level for user settng = 0 */
+	{0x0080, 0x8600},	/* Line memory read counter (L) */
+	{0x000a, 0x8603},	/* optical black level calc mode:
+				 * auto; optical black offset = 10 */
+	{0x00df, 0x865b},	/* Horiz offset for valid pixels (L)=0xdf */
+	{0x0012, 0x865c},	/* Vert offset for valid lines (L)=0x12 */
+
+/* The following two lines seem to be the "wrong" resolution. */
+/* But perhaps these indicate the actual size of the sensor */
+/* rather than the size of the current video mode. */
+	{0x0058, 0x865d},	/* Horiz valid pixels (*4) (L) = 352 */
+	{0x0048, 0x865e},	/* Vert valid lines (*4) (L) = 288 */
+
+	{0x0015, 0x8608},	/* A11 Coef ... */
+	{0x0030, 0x8609},
+	{0x00fb, 0x860a},
+	{0x003e, 0x860b},
+	{0x00ce, 0x860c},
+	{0x00f4, 0x860d},
+	{0x00eb, 0x860e},
+	{0x00dc, 0x860f},
+	{0x0039, 0x8610},
+	{0x0001, 0x8611},	/* R offset for white balance ... */
+	{0x0000, 0x8612},
+	{0x0001, 0x8613},
+	{0x0000, 0x8614},
+	{0x005b, 0x8651},	/* R gain for white balance ... */
+	{0x0040, 0x8652},
+	{0x0060, 0x8653},
+	{0x0040, 0x8654},
+	{0x0000, 0x8655},
+	{0x0001, 0x863f},	/* Fixed gamma correction enable, USB control,
+				 * lum filter disable, lum noise clip disable */
+	{0x00a1, 0x8656},	/* Window1 size 256x256, Windows2 size 64x64,
+				 * gamma look-up disable,
+				 * new edge enhancement enable */
+	{0x0018, 0x8657},	/* Edge gain high thresh */
+	{0x0020, 0x8658},	/* Edge gain low thresh */
+	{0x000a, 0x8659},	/* Edge bandwidth high threshold */
+	{0x0005, 0x865a},	/* Edge bandwidth low threshold */
+	/* -------------------------------- */
+	{0x0030, 0x8112},	/* Video drop enable, ISO streaming enable */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0xa908, 0x8802},
+	{0x0034, 0x8801},	/* SSI reg addr */
+	{0x00ca, 0x8800},
+	/* SSI data to write */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0x1f08, 0x8802},
+	{0x0006, 0x8801},
+	{0x0080, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+
+/* ----- Read back coefs we wrote earlier. */
+	/* READ { 0x0000, 0x8608 } -> 0000: 15  */
+	/* READ { 0x0000, 0x8609 } -> 0000: 30  */
+	/* READ { 0x0000, 0x860a } -> 0000: fb  */
+	/* READ { 0x0000, 0x860b } -> 0000: 3e  */
+	/* READ { 0x0000, 0x860c } -> 0000: ce  */
+	/* READ { 0x0000, 0x860d } -> 0000: f4  */
+	/* READ { 0x0000, 0x860e } -> 0000: eb  */
+	/* READ { 0x0000, 0x860f } -> 0000: dc  */
+	/* READ { 0x0000, 0x8610 } -> 0000: 39  */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 08  */
+	{0xb008, 0x8802},
+	{0x0006, 0x8801},
+	{0x007d, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+
+
+	/* This chunk is seemingly redundant with */
+	/* earlier commands (A11 Coef...), but if I disable it, */
+	/* the image appears too dark.  Maybe there was some kind of */
+	/* reset since the earlier commands, so this is necessary again. */
+	{0x0015, 0x8608},
+	{0x0030, 0x8609},
+	{0xfffb, 0x860a},
+	{0x003e, 0x860b},
+	{0xffce, 0x860c},
+	{0xfff4, 0x860d},
+	{0xffeb, 0x860e},
+	{0xffdc, 0x860f},
+	{0x0039, 0x8610},
+	{0x0018, 0x8657},
+
+	{0x0000, 0x8508},	/* Disable compression. */
+	/* Previous line was:
+	{0x0021, 0x8508},	 * Enable compression. */
+	{0x0032, 0x850b},	/* compression stuff */
+	{0x0003, 0x8509},	/* compression stuff */
+	{0x0011, 0x850a},	/* compression stuff */
+	{0x0021, 0x850d},	/* compression stuff */
+	{0x0010, 0x850c},	/* compression stuff */
+	{0x0003, 0x8500},	/* *** Video mode: 160x120 */
+	{0x0001, 0x8501},	/* Hardware-dominated snap control */
+	{0x0061, 0x8656},	/* Window1 size 128x128, Windows2 size 128x128,
+				 * gamma look-up disable,
+				 * new edge enhancement enable */
+	{0x0018, 0x8617},	/* Window1 start X (*2) */
+	{0x0008, 0x8618},	/* Window1 start Y (*2) */
+	{0x0061, 0x8656},	/* Window1 size 128x128, Windows2 size 128x128,
+				 * gamma look-up disable,
+				 * new edge enhancement enable */
+	{0x0058, 0x8619},	/* Window2 start X (*2) */
+	{0x0008, 0x861a},	/* Window2 start Y (*2) */
+	{0x00ff, 0x8615},	/* High lum thresh for white balance */
+	{0x0000, 0x8616},	/* Low lum thresh for white balance */
+	{0x0012, 0x8700},	/* Clock speed 48Mhz/(2+2)/2= 6 Mhz */
+	{0x0012, 0x8700},	/* Clock speed 48Mhz/(2+2)/2= 6 Mhz */
+	/* READ { 0x0000, 0x8656 } -> 0000: 61  */
+	{0x0028, 0x8802},    /* 375 Khz SSI clock, SSI r/w sync with VSYNC */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 28  */
+	{0x1f28, 0x8802},    /* 375 Khz SSI clock, SSI r/w sync with VSYNC */
+	{0x0010, 0x8801},	/* SSI reg addr */
+	{0x003e, 0x8800},	/* SSI data to write */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	{0x0028, 0x8802},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 28  */
+	{0x1f28, 0x8802},
+	{0x0000, 0x8801},
+	{0x001f, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	{0x0001, 0x8602},    /* optical black level for user settning = 1 */
+
+	/* Original: */
+	{0x0023, 0x8700},	/* Clock speed 48Mhz/(3+2)/4= 2.4 Mhz */
+	{0x000f, 0x8602},    /* optical black level for user settning = 15 */
+
+	{0x0028, 0x8802},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 28  */
+	{0x1f28, 0x8802},
+	{0x0010, 0x8801},
+	{0x007b, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	{0x002f, 0x8651},	/* R gain for white balance ... */
+	{0x0080, 0x8653},
+	/* READ { 0x0000, 0x8655 } -> 0000: 00  */
+	{0x0000, 0x8655},
+
+	{0x0030, 0x8112},	/* Video drop enable, ISO streaming enable */
+	{0x0020, 0x8112},	/* Video drop enable, ISO streaming disable */
+	/* UNKNOWN DIRECTION (URB_FUNCTION_SELECT_INTERFACE: (ALT=0) ) */
+	{}
+};
+
+/*
+ * Initialization data for Intel EasyPC Camera CS110
+ */
+static const u16 spca508cs110_init_data[][2] = {
+	{0x0000, 0x870b},	/* Reset CTL3 */
+	{0x0003, 0x8111},	/* Soft Reset compression, memory, TG & CDSP */
+	{0x0000, 0x8111},	/* Normal operation on reset */
+	{0x0090, 0x8110},
+		 /* External Clock 2x & Synchronous Serial Interface Output */
+	{0x0020, 0x8112},	/* Video Drop packet enable */
+	{0x0000, 0x8114},	/* Software GPIO output data */
+	{0x0001, 0x8114},
+	{0x0001, 0x8114},
+	{0x0001, 0x8114},
+	{0x0003, 0x8114},
+
+	/* Initial sequence Synchronous Serial Interface */
+	{0x000f, 0x8402},	/* Memory bank Address */
+	{0x0000, 0x8403},	/* Memory bank Address */
+	{0x00ba, 0x8804},	/* SSI Slave address */
+	{0x0010, 0x8802},	/* 93.75kHz SSI Clock Two DataByte */
+	{0x0010, 0x8802},	/* 93.75kHz SSI Clock two DataByte */
+
+	{0x0001, 0x8801},
+	{0x000a, 0x8805},	/* a - NWG: Dunno what this is about */
+	{0x0000, 0x8800},
+	{0x0010, 0x8802},
+
+	{0x0002, 0x8801},
+	{0x0000, 0x8805},
+	{0x0000, 0x8800},
+	{0x0010, 0x8802},
+
+	{0x0003, 0x8801},
+	{0x0027, 0x8805},
+	{0x0001, 0x8800},
+	{0x0010, 0x8802},
+
+	{0x0004, 0x8801},
+	{0x0065, 0x8805},
+	{0x0001, 0x8800},
+	{0x0010, 0x8802},
+
+	{0x0005, 0x8801},
+	{0x0003, 0x8805},
+	{0x0000, 0x8800},
+	{0x0010, 0x8802},
+
+	{0x0006, 0x8801},
+	{0x001c, 0x8805},
+	{0x0000, 0x8800},
+	{0x0010, 0x8802},
+
+	{0x0007, 0x8801},
+	{0x002a, 0x8805},
+	{0x0000, 0x8800},
+	{0x0010, 0x8802},
+
+	{0x0002, 0x8704},	/* External input CKIx1 */
+	{0x0001, 0x8606},    /* 1 Line memory Read Counter (H) Result: (d)410 */
+	{0x009a, 0x8600},	/* Line memory Read Counter (L) */
+	{0x0001, 0x865b},	/* 1 Horizontal Offset for Valid Pixel(L) */
+	{0x0003, 0x865c},	/* 3 Vertical Offset for Valid Lines(L) */
+	{0x0058, 0x865d},	/* 58 Horizontal Valid Pixel Window(L) */
+
+	{0x0006, 0x8660},	/* Nibble data + input order */
+
+	{0x000a, 0x8602},	/* Optical black level set to 0x0a */
+	{0x0000, 0x8603},	/* Optical black level Offset */
+
+/*	{0x0000, 0x8611},	 * 0 R  Offset for white Balance */
+/*	{0x0000, 0x8612},	 * 1 Gr Offset for white Balance */
+/*	{0x0000, 0x8613},	 * 1f B  Offset for white Balance */
+/*	{0x0000, 0x8614},	 * f0 Gb Offset for white Balance */
+
+	{0x0040, 0x8651},   /* 2b BLUE gain for white balance  good at all 60 */
+	{0x0030, 0x8652},	/* 41 Gr Gain for white Balance (L) */
+	{0x0035, 0x8653},	/* 26 RED gain for white balance */
+	{0x0035, 0x8654},	/* 40Gb Gain for white Balance (L) */
+	{0x0041, 0x863f},
+	      /* Fixed Gamma correction enabled (makes colours look better) */
+
+	{0x0000, 0x8655},
+		/* High bits for white balance*****brightness control*** */
+	{}
+};
+
+static const u16 spca508_sightcam_init_data[][2] = {
+/* This line seems to setup the frame/canvas */
+	{0x000f, 0x8402},
+
+/* These 6 lines are needed to startup the webcam */
+	{0x0090, 0x8110},
+	{0x0001, 0x8114},
+	{0x0001, 0x8114},
+	{0x0001, 0x8114},
+	{0x0003, 0x8114},
+	{0x0080, 0x8804},
+
+/* This part seems to make the pictures darker? (autobrightness?) */
+	{0x0001, 0x8801},
+	{0x0004, 0x8800},
+	{0x0003, 0x8801},
+	{0x00e0, 0x8800},
+	{0x0004, 0x8801},
+	{0x00b4, 0x8800},
+	{0x0005, 0x8801},
+	{0x0000, 0x8800},
+
+	{0x0006, 0x8801},
+	{0x00e0, 0x8800},
+	{0x0007, 0x8801},
+	{0x000c, 0x8800},
+
+/* This section is just needed, it probably
+ * does something like the previous section,
+ * but the cam won't start if it's not included.
+ */
+	{0x0014, 0x8801},
+	{0x0008, 0x8800},
+	{0x0015, 0x8801},
+	{0x0067, 0x8800},
+	{0x0016, 0x8801},
+	{0x0000, 0x8800},
+	{0x0017, 0x8801},
+	{0x0020, 0x8800},
+	{0x0018, 0x8801},
+	{0x0044, 0x8800},
+
+/* Makes the picture darker - and the
+ * cam won't start if not included
+ */
+	{0x001e, 0x8801},
+	{0x00ea, 0x8800},
+	{0x001f, 0x8801},
+	{0x0001, 0x8800},
+	{0x0003, 0x8801},
+	{0x00e0, 0x8800},
+
+/* seems to place the colors ontop of each other #1 */
+	{0x0006, 0x8704},
+	{0x0001, 0x870c},
+	{0x0016, 0x8600},
+	{0x0002, 0x8606},
+
+/* if not included the pictures becomes _very_ dark */
+	{0x0064, 0x8607},
+	{0x003a, 0x8601},
+	{0x0000, 0x8602},
+
+/* seems to place the colors ontop of each other #2 */
+	{0x0016, 0x8600},
+	{0x0018, 0x8617},
+	{0x0008, 0x8618},
+	{0x00a1, 0x8656},
+
+/* webcam won't start if not included */
+	{0x0007, 0x865b},
+	{0x0001, 0x865c},
+	{0x0058, 0x865d},
+	{0x0048, 0x865e},
+
+/* adjusts the colors */
+	{0x0049, 0x8651},
+	{0x0040, 0x8652},
+	{0x004c, 0x8653},
+	{0x0040, 0x8654},
+	{}
+};
+
+static const u16 spca508_sightcam2_init_data[][2] = {
+	{0x0020, 0x8112},
+
+	{0x000f, 0x8402},
+	{0x0000, 0x8403},
+
+	{0x0008, 0x8201},
+	{0x0008, 0x8200},
+	{0x0001, 0x8200},
+	{0x0009, 0x8201},
+	{0x0008, 0x8200},
+	{0x0001, 0x8200},
+	{0x000a, 0x8201},
+	{0x0008, 0x8200},
+	{0x0001, 0x8200},
+	{0x000b, 0x8201},
+	{0x0008, 0x8200},
+	{0x0001, 0x8200},
+	{0x000c, 0x8201},
+	{0x0008, 0x8200},
+	{0x0001, 0x8200},
+	{0x000d, 0x8201},
+	{0x0008, 0x8200},
+	{0x0001, 0x8200},
+	{0x000e, 0x8201},
+	{0x0008, 0x8200},
+	{0x0001, 0x8200},
+	{0x0007, 0x8201},
+	{0x0008, 0x8200},
+	{0x0001, 0x8200},
+	{0x000f, 0x8201},
+	{0x0008, 0x8200},
+	{0x0001, 0x8200},
+
+	{0x0018, 0x8660},
+	{0x0010, 0x8201},
+
+	{0x0008, 0x8200},
+	{0x0001, 0x8200},
+	{0x0011, 0x8201},
+	{0x0008, 0x8200},
+	{0x0001, 0x8200},
+
+	{0x0000, 0x86b0},
+	{0x0034, 0x86b1},
+	{0x0000, 0x86b2},
+	{0x0049, 0x86b3},
+	{0x0000, 0x86b4},
+	{0x0000, 0x86b4},
+
+	{0x0012, 0x8201},
+	{0x0008, 0x8200},
+	{0x0001, 0x8200},
+	{0x0013, 0x8201},
+	{0x0008, 0x8200},
+	{0x0001, 0x8200},
+
+	{0x0001, 0x86b0},
+	{0x00aa, 0x86b1},
+	{0x0000, 0x86b2},
+	{0x00e4, 0x86b3},
+	{0x0000, 0x86b4},
+	{0x0000, 0x86b4},
+
+	{0x0018, 0x8660},
+
+	{0x0090, 0x8110},
+	{0x0001, 0x8114},
+	{0x0001, 0x8114},
+	{0x0001, 0x8114},
+	{0x0003, 0x8114},
+
+	{0x0080, 0x8804},
+	{0x0003, 0x8801},
+	{0x0012, 0x8800},
+	{0x0004, 0x8801},
+	{0x0005, 0x8800},
+	{0x0005, 0x8801},
+	{0x0000, 0x8800},
+	{0x0006, 0x8801},
+	{0x0000, 0x8800},
+	{0x0007, 0x8801},
+	{0x0000, 0x8800},
+	{0x0008, 0x8801},
+	{0x0005, 0x8800},
+	{0x000a, 0x8700},
+	{0x000e, 0x8801},
+	{0x0004, 0x8800},
+	{0x0005, 0x8801},
+	{0x0047, 0x8800},
+	{0x0006, 0x8801},
+	{0x0000, 0x8800},
+	{0x0007, 0x8801},
+	{0x00c0, 0x8800},
+	{0x0008, 0x8801},
+	{0x0003, 0x8800},
+	{0x0013, 0x8801},
+	{0x0001, 0x8800},
+	{0x0009, 0x8801},
+	{0x0000, 0x8800},
+	{0x000a, 0x8801},
+	{0x0000, 0x8800},
+	{0x000b, 0x8801},
+	{0x0000, 0x8800},
+	{0x000c, 0x8801},
+	{0x0000, 0x8800},
+	{0x000e, 0x8801},
+	{0x0004, 0x8800},
+	{0x000f, 0x8801},
+	{0x0000, 0x8800},
+	{0x0010, 0x8801},
+	{0x0006, 0x8800},
+	{0x0011, 0x8801},
+	{0x0006, 0x8800},
+	{0x0012, 0x8801},
+	{0x0000, 0x8800},
+	{0x0013, 0x8801},
+	{0x0001, 0x8800},
+
+	{0x000a, 0x8700},
+	{0x0000, 0x8702},
+	{0x0000, 0x8703},
+	{0x00c2, 0x8704},
+	{0x0001, 0x870c},
+
+	{0x0044, 0x8600},
+	{0x0002, 0x8606},
+	{0x0064, 0x8607},
+	{0x003a, 0x8601},
+	{0x0008, 0x8602},
+	{0x0044, 0x8600},
+	{0x0018, 0x8617},
+	{0x0008, 0x8618},
+	{0x00a1, 0x8656},
+	{0x0004, 0x865b},
+	{0x0002, 0x865c},
+	{0x0058, 0x865d},
+	{0x0048, 0x865e},
+	{0x0012, 0x8608},
+	{0x002c, 0x8609},
+	{0x0002, 0x860a},
+	{0x002c, 0x860b},
+	{0x00db, 0x860c},
+	{0x00f9, 0x860d},
+	{0x00f1, 0x860e},
+	{0x00e3, 0x860f},
+	{0x002c, 0x8610},
+	{0x006c, 0x8651},
+	{0x0041, 0x8652},
+	{0x0059, 0x8653},
+	{0x0040, 0x8654},
+	{0x00fa, 0x8611},
+	{0x00ff, 0x8612},
+	{0x00f8, 0x8613},
+	{0x0000, 0x8614},
+	{0x0001, 0x863f},
+	{0x0000, 0x8640},
+	{0x0026, 0x8641},
+	{0x0045, 0x8642},
+	{0x0060, 0x8643},
+	{0x0075, 0x8644},
+	{0x0088, 0x8645},
+	{0x009b, 0x8646},
+	{0x00b0, 0x8647},
+	{0x00c5, 0x8648},
+	{0x00d2, 0x8649},
+	{0x00dc, 0x864a},
+	{0x00e5, 0x864b},
+	{0x00eb, 0x864c},
+	{0x00f0, 0x864d},
+	{0x00f6, 0x864e},
+	{0x00fa, 0x864f},
+	{0x00ff, 0x8650},
+	{0x0060, 0x8657},
+	{0x0010, 0x8658},
+	{0x0018, 0x8659},
+	{0x0005, 0x865a},
+	{0x0018, 0x8660},
+	{0x0003, 0x8509},
+	{0x0011, 0x850a},
+	{0x0032, 0x850b},
+	{0x0010, 0x850c},
+	{0x0021, 0x850d},
+	{0x0001, 0x8500},
+	{0x0000, 0x8508},
+	{0x0012, 0x8608},
+	{0x002c, 0x8609},
+	{0x0002, 0x860a},
+	{0x0039, 0x860b},
+	{0x00d0, 0x860c},
+	{0x00f7, 0x860d},
+	{0x00ed, 0x860e},
+	{0x00db, 0x860f},
+	{0x0039, 0x8610},
+	{0x0012, 0x8657},
+	{0x000c, 0x8619},
+	{0x0004, 0x861a},
+	{0x00a1, 0x8656},
+	{0x00c8, 0x8615},
+	{0x0032, 0x8616},
+
+	{0x0030, 0x8112},
+	{0x0020, 0x8112},
+	{0x0020, 0x8112},
+	{0x000f, 0x8402},
+	{0x0000, 0x8403},
+
+	{0x0090, 0x8110},
+	{0x0001, 0x8114},
+	{0x0001, 0x8114},
+	{0x0001, 0x8114},
+	{0x0003, 0x8114},
+	{0x0080, 0x8804},
+
+	{0x0003, 0x8801},
+	{0x0012, 0x8800},
+	{0x0004, 0x8801},
+	{0x0005, 0x8800},
+	{0x0005, 0x8801},
+	{0x0047, 0x8800},
+	{0x0006, 0x8801},
+	{0x0000, 0x8800},
+	{0x0007, 0x8801},
+	{0x00c0, 0x8800},
+	{0x0008, 0x8801},
+	{0x0003, 0x8800},
+	{0x000a, 0x8700},
+	{0x000e, 0x8801},
+	{0x0004, 0x8800},
+	{0x0005, 0x8801},
+	{0x0047, 0x8800},
+	{0x0006, 0x8801},
+	{0x0000, 0x8800},
+	{0x0007, 0x8801},
+	{0x00c0, 0x8800},
+	{0x0008, 0x8801},
+	{0x0003, 0x8800},
+	{0x0013, 0x8801},
+	{0x0001, 0x8800},
+	{0x0009, 0x8801},
+	{0x0000, 0x8800},
+	{0x000a, 0x8801},
+	{0x0000, 0x8800},
+	{0x000b, 0x8801},
+	{0x0000, 0x8800},
+	{0x000c, 0x8801},
+	{0x0000, 0x8800},
+	{0x000e, 0x8801},
+	{0x0004, 0x8800},
+	{0x000f, 0x8801},
+	{0x0000, 0x8800},
+	{0x0010, 0x8801},
+	{0x0006, 0x8800},
+	{0x0011, 0x8801},
+	{0x0006, 0x8800},
+	{0x0012, 0x8801},
+	{0x0000, 0x8800},
+	{0x0013, 0x8801},
+	{0x0001, 0x8800},
+	{0x000a, 0x8700},
+	{0x0000, 0x8702},
+	{0x0000, 0x8703},
+	{0x00c2, 0x8704},
+	{0x0001, 0x870c},
+	{0x0044, 0x8600},
+	{0x0002, 0x8606},
+	{0x0064, 0x8607},
+	{0x003a, 0x8601},
+	{0x0008, 0x8602},
+	{0x0044, 0x8600},
+	{0x0018, 0x8617},
+	{0x0008, 0x8618},
+	{0x00a1, 0x8656},
+	{0x0004, 0x865b},
+	{0x0002, 0x865c},
+	{0x0058, 0x865d},
+	{0x0048, 0x865e},
+	{0x0012, 0x8608},
+	{0x002c, 0x8609},
+	{0x0002, 0x860a},
+	{0x002c, 0x860b},
+	{0x00db, 0x860c},
+	{0x00f9, 0x860d},
+	{0x00f1, 0x860e},
+	{0x00e3, 0x860f},
+	{0x002c, 0x8610},
+	{0x006c, 0x8651},
+	{0x0041, 0x8652},
+	{0x0059, 0x8653},
+	{0x0040, 0x8654},
+	{0x00fa, 0x8611},
+	{0x00ff, 0x8612},
+	{0x00f8, 0x8613},
+	{0x0000, 0x8614},
+	{0x0001, 0x863f},
+	{0x0000, 0x8640},
+	{0x0026, 0x8641},
+	{0x0045, 0x8642},
+	{0x0060, 0x8643},
+	{0x0075, 0x8644},
+	{0x0088, 0x8645},
+	{0x009b, 0x8646},
+	{0x00b0, 0x8647},
+	{0x00c5, 0x8648},
+	{0x00d2, 0x8649},
+	{0x00dc, 0x864a},
+	{0x00e5, 0x864b},
+	{0x00eb, 0x864c},
+	{0x00f0, 0x864d},
+	{0x00f6, 0x864e},
+	{0x00fa, 0x864f},
+	{0x00ff, 0x8650},
+	{0x0060, 0x8657},
+	{0x0010, 0x8658},
+	{0x0018, 0x8659},
+	{0x0005, 0x865a},
+	{0x0018, 0x8660},
+	{0x0003, 0x8509},
+	{0x0011, 0x850a},
+	{0x0032, 0x850b},
+	{0x0010, 0x850c},
+	{0x0021, 0x850d},
+	{0x0001, 0x8500},
+	{0x0000, 0x8508},
+
+	{0x0012, 0x8608},
+	{0x002c, 0x8609},
+	{0x0002, 0x860a},
+	{0x0039, 0x860b},
+	{0x00d0, 0x860c},
+	{0x00f7, 0x860d},
+	{0x00ed, 0x860e},
+	{0x00db, 0x860f},
+	{0x0039, 0x8610},
+	{0x0012, 0x8657},
+	{0x0064, 0x8619},
+
+/* This line starts it all, it is not needed here */
+/* since it has been build into the driver */
+/* jfm: don't start now */
+/*	{0x0030, 0x8112}, */
+	{}
+};
+
+/*
+ * Initialization data for Creative Webcam Vista
+ */
+static const u16 spca508_vista_init_data[][2] = {
+	{0x0008, 0x8200},	/* Clear register */
+	{0x0000, 0x870b},	/* Reset CTL3 */
+	{0x0020, 0x8112},	/* Video Drop packet enable */
+	{0x0003, 0x8111},	/* Soft Reset compression, memory, TG & CDSP */
+	{0x0000, 0x8110},	/* Disable everything */
+	{0x0000, 0x8114},	/* Software GPIO output data */
+	{0x0000, 0x8114},
+
+	{0x0003, 0x8111},
+	{0x0000, 0x8111},
+	{0x0090, 0x8110},    /* Enable: SSI output, External 2X clock output */
+	{0x0020, 0x8112},
+	{0x0000, 0x8114},
+	{0x0001, 0x8114},
+	{0x0001, 0x8114},
+	{0x0001, 0x8114},
+	{0x0003, 0x8114},
+
+	{0x000f, 0x8402},	/* Memory bank Address */
+	{0x0000, 0x8403},	/* Memory bank Address */
+	{0x00ba, 0x8804},	/* SSI Slave address */
+	{0x0010, 0x8802},	/* 93.75kHz SSI Clock Two DataByte */
+
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 10  */
+	{0x0010, 0x8802},	/* Will write 2 bytes (DATA1+DATA2) */
+	{0x0020, 0x8801},	/* Register address for SSI read/write */
+	{0x0044, 0x8805},	/* DATA2 */
+	{0x0004, 0x8800},	/* DATA1 -> write triggered */
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 10  */
+	{0x0010, 0x8802},
+	{0x0009, 0x8801},
+	{0x0042, 0x8805},
+	{0x0001, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 10  */
+	{0x0010, 0x8802},
+	{0x003c, 0x8801},
+	{0x0001, 0x8805},
+	{0x0000, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 10  */
+	{0x0010, 0x8802},
+	{0x0001, 0x8801},
+	{0x000a, 0x8805},
+	{0x0000, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 10  */
+	{0x0010, 0x8802},
+	{0x0002, 0x8801},
+	{0x0000, 0x8805},
+	{0x0000, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 10  */
+	{0x0010, 0x8802},
+	{0x0003, 0x8801},
+	{0x0027, 0x8805},
+	{0x0001, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 10  */
+	{0x0010, 0x8802},
+	{0x0004, 0x8801},
+	{0x0065, 0x8805},
+	{0x0001, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 10  */
+	{0x0010, 0x8802},
+	{0x0005, 0x8801},
+	{0x0003, 0x8805},
+	{0x0000, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 10  */
+	{0x0010, 0x8802},
+	{0x0006, 0x8801},
+	{0x001c, 0x8805},
+	{0x0000, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 10  */
+	{0x0010, 0x8802},
+	{0x0007, 0x8801},
+	{0x002a, 0x8805},
+	{0x0000, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 10  */
+	{0x0010, 0x8802},
+	{0x000e, 0x8801},
+	{0x0000, 0x8805},
+	{0x0000, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 10  */
+	{0x0010, 0x8802},
+	{0x0028, 0x8801},
+	{0x002e, 0x8805},
+	{0x0000, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 10  */
+	{0x0010, 0x8802},
+	{0x0039, 0x8801},
+	{0x0013, 0x8805},
+	{0x0000, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 10  */
+	{0x0010, 0x8802},
+	{0x003b, 0x8801},
+	{0x000c, 0x8805},
+	{0x0000, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 10  */
+	{0x0010, 0x8802},
+	{0x0035, 0x8801},
+	{0x0028, 0x8805},
+	{0x0000, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+	/* READ { 0x0001, 0x8802 } -> 0000: 10  */
+	{0x0010, 0x8802},
+	{0x0009, 0x8801},
+	{0x0042, 0x8805},
+	{0x0001, 0x8800},
+	/* READ { 0x0001, 0x8803 } -> 0000: 00  */
+
+	{0x0050, 0x8703},
+	{0x0002, 0x8704},	/* External input CKIx1 */
+	{0x0001, 0x870c},	/* Select CKOx2 output */
+	{0x009a, 0x8600},	/* Line memory Read Counter (L) */
+	{0x0001, 0x8606},    /* 1 Line memory Read Counter (H) Result: (d)410 */
+	{0x0023, 0x8601},
+	{0x0010, 0x8602},
+	{0x000a, 0x8603},
+	{0x009a, 0x8600},
+	{0x0001, 0x865b},	/* 1 Horizontal Offset for Valid Pixel(L) */
+	{0x0003, 0x865c},	/* Vertical offset for valid lines (L) */
+	{0x0058, 0x865d},	/* Horizontal valid pixels window (L) */
+	{0x0048, 0x865e},	/* Vertical valid lines window (L) */
+	{0x0000, 0x865f},
+
+	{0x0006, 0x8660},
+		    /* Enable nibble data input, select nibble input order */
+
+	{0x0013, 0x8608},	/* A11 Coeficients for color correction */
+	{0x0028, 0x8609},
+		    /* Note: these values are confirmed at the end of array */
+	{0x0005, 0x860a},	/* ... */
+	{0x0025, 0x860b},
+	{0x00e1, 0x860c},
+	{0x00fa, 0x860d},
+	{0x00f4, 0x860e},
+	{0x00e8, 0x860f},
+	{0x0025, 0x8610},	/* A33 Coef. */
+	{0x00fc, 0x8611},	/* White balance offset: R */
+	{0x0001, 0x8612},	/* White balance offset: Gr */
+	{0x00fe, 0x8613},	/* White balance offset: B */
+	{0x0000, 0x8614},	/* White balance offset: Gb */
+
+	{0x0064, 0x8651},	/* R gain for white balance (L) */
+	{0x0040, 0x8652},	/* Gr gain for white balance (L) */
+	{0x0066, 0x8653},	/* B gain for white balance (L) */
+	{0x0040, 0x8654},	/* Gb gain for white balance (L) */
+	{0x0001, 0x863f},	/* Enable fixed gamma correction */
+
+	{0x00a1, 0x8656},	/* Size - Window1: 256x256, Window2: 128x128,
+				 * UV division: UV no change,
+				 * Enable New edge enhancement */
+	{0x0018, 0x8657},	/* Edge gain high threshold */
+	{0x0020, 0x8658},	/* Edge gain low threshold */
+	{0x000a, 0x8659},	/* Edge bandwidth high threshold */
+	{0x0005, 0x865a},	/* Edge bandwidth low threshold */
+	{0x0064, 0x8607},	/* UV filter enable */
+
+	{0x0016, 0x8660},
+	{0x0000, 0x86b0},	/* Bad pixels compensation address */
+	{0x00dc, 0x86b1},	/* X coord for bad pixels compensation (L) */
+	{0x0000, 0x86b2},
+	{0x0009, 0x86b3},	/* Y coord for bad pixels compensation (L) */
+	{0x0000, 0x86b4},
+
+	{0x0001, 0x86b0},
+	{0x00f5, 0x86b1},
+	{0x0000, 0x86b2},
+	{0x00c6, 0x86b3},
+	{0x0000, 0x86b4},
+
+	{0x0002, 0x86b0},
+	{0x001c, 0x86b1},
+	{0x0001, 0x86b2},
+	{0x00d7, 0x86b3},
+	{0x0000, 0x86b4},
+
+	{0x0003, 0x86b0},
+	{0x001c, 0x86b1},
+	{0x0001, 0x86b2},
+	{0x00d8, 0x86b3},
+	{0x0000, 0x86b4},
+
+	{0x0004, 0x86b0},
+	{0x001d, 0x86b1},
+	{0x0001, 0x86b2},
+	{0x00d8, 0x86b3},
+	{0x0000, 0x86b4},
+	{0x001e, 0x8660},
+
+	/* READ { 0x0000, 0x8608 } -> 0000: 13  */
+	/* READ { 0x0000, 0x8609 } -> 0000: 28  */
+	/* READ { 0x0000, 0x8610 } -> 0000: 05  */
+	/* READ { 0x0000, 0x8611 } -> 0000: 25  */
+	/* READ { 0x0000, 0x8612 } -> 0000: e1  */
+	/* READ { 0x0000, 0x8613 } -> 0000: fa  */
+	/* READ { 0x0000, 0x8614 } -> 0000: f4  */
+	/* READ { 0x0000, 0x8615 } -> 0000: e8  */
+	/* READ { 0x0000, 0x8616 } -> 0000: 25  */
+	{}
+};
+
+static int reg_write(struct gspca_dev *gspca_dev, u16 index, u16 value)
+{
+	int ret;
+	struct usb_device *dev = gspca_dev->dev;
+
+	ret = usb_control_msg(dev,
+			usb_sndctrlpipe(dev, 0),
+			0,		/* request */
+			USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			value, index, NULL, 0, 500);
+	gspca_dbg(gspca_dev, D_USBO, "reg write i:0x%04x = 0x%02x\n",
+		  index, value);
+	if (ret < 0)
+		pr_err("reg write: error %d\n", ret);
+	return ret;
+}
+
+/* read 1 byte */
+/* returns: negative is error, pos or zero is data */
+static int reg_read(struct gspca_dev *gspca_dev,
+			u16 index)	/* wIndex */
+{
+	int ret;
+
+	ret = usb_control_msg(gspca_dev->dev,
+			usb_rcvctrlpipe(gspca_dev->dev, 0),
+			0,			/* register */
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0,		/* value */
+			index,
+			gspca_dev->usb_buf, 1,
+			500);			/* timeout */
+	gspca_dbg(gspca_dev, D_USBI, "reg read i:%04x --> %02x\n",
+		  index, gspca_dev->usb_buf[0]);
+	if (ret < 0) {
+		pr_err("reg_read err %d\n", ret);
+		return ret;
+	}
+	return gspca_dev->usb_buf[0];
+}
+
+/* send 1 or 2 bytes to the sensor via the Synchronous Serial Interface */
+static int ssi_w(struct gspca_dev *gspca_dev,
+		u16 reg, u16 val)
+{
+	int ret, retry;
+
+	ret = reg_write(gspca_dev, 0x8802, reg >> 8);
+	if (ret < 0)
+		goto out;
+	ret = reg_write(gspca_dev, 0x8801, reg & 0x00ff);
+	if (ret < 0)
+		goto out;
+	if ((reg & 0xff00) == 0x1000) {		/* if 2 bytes */
+		ret = reg_write(gspca_dev, 0x8805, val & 0x00ff);
+		if (ret < 0)
+			goto out;
+		val >>= 8;
+	}
+	ret = reg_write(gspca_dev, 0x8800, val);
+	if (ret < 0)
+		goto out;
+
+	/* poll until not busy */
+	retry = 10;
+	for (;;) {
+		ret = reg_read(gspca_dev, 0x8803);
+		if (ret < 0)
+			break;
+		if (gspca_dev->usb_buf[0] == 0)
+			break;
+		if (--retry <= 0) {
+			gspca_err(gspca_dev, "ssi_w busy %02x\n",
+				  gspca_dev->usb_buf[0]);
+			ret = -1;
+			break;
+		}
+		msleep(8);
+	}
+
+out:
+	return ret;
+}
+
+static int write_vector(struct gspca_dev *gspca_dev,
+			const u16 (*data)[2])
+{
+	int ret = 0;
+
+	while ((*data)[1] != 0) {
+		if ((*data)[1] & 0x8000) {
+			if ((*data)[1] == 0xdd00)	/* delay */
+				msleep((*data)[0]);
+			else
+				ret = reg_write(gspca_dev, (*data)[1],
+								(*data)[0]);
+		} else {
+			ret = ssi_w(gspca_dev, (*data)[1], (*data)[0]);
+		}
+		if (ret < 0)
+			break;
+		data++;
+	}
+	return ret;
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam;
+	const u16 (*init_data)[2];
+	static const u16 (*(init_data_tb[]))[2] = {
+		spca508_vista_init_data,	/* CreativeVista 0 */
+		spca508_sightcam_init_data,	/* HamaUSBSightcam 1 */
+		spca508_sightcam2_init_data,	/* HamaUSBSightcam2 2 */
+		spca508cs110_init_data,		/* IntelEasyPCCamera 3 */
+		spca508cs110_init_data,		/* MicroInnovationIC200 4 */
+		spca508_init_data,		/* ViewQuestVQ110 5 */
+	};
+	int data1, data2;
+
+	/* Read from global register the USB product and vendor IDs, just to
+	 * prove that we can communicate with the device.  This works, which
+	 * confirms at we are communicating properly and that the device
+	 * is a 508. */
+	data1 = reg_read(gspca_dev, 0x8104);
+	data2 = reg_read(gspca_dev, 0x8105);
+	gspca_dbg(gspca_dev, D_PROBE, "Webcam Vendor ID: 0x%02x%02x\n",
+		  data2, data1);
+
+	data1 = reg_read(gspca_dev, 0x8106);
+	data2 = reg_read(gspca_dev, 0x8107);
+	gspca_dbg(gspca_dev, D_PROBE, "Webcam Product ID: 0x%02x%02x\n",
+		  data2, data1);
+
+	data1 = reg_read(gspca_dev, 0x8621);
+	gspca_dbg(gspca_dev, D_PROBE, "Window 1 average luminance: %d\n",
+		  data1);
+
+	cam = &gspca_dev->cam;
+	cam->cam_mode = sif_mode;
+	cam->nmodes = ARRAY_SIZE(sif_mode);
+
+	sd->subtype = id->driver_info;
+
+	init_data = init_data_tb[sd->subtype];
+	return write_vector(gspca_dev, init_data);
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	return 0;
+}
+
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	int mode;
+
+	mode = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].priv;
+	reg_write(gspca_dev, 0x8500, mode);
+	switch (mode) {
+	case 0:
+	case 1:
+		reg_write(gspca_dev, 0x8700, 0x28); /* clock */
+		break;
+	default:
+/*	case 2: */
+/*	case 3: */
+		reg_write(gspca_dev, 0x8700, 0x23); /* clock */
+		break;
+	}
+	reg_write(gspca_dev, 0x8112, 0x10 | 0x20);
+	return 0;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	/* Video ISO disable, Video Drop Packet enable: */
+	reg_write(gspca_dev, 0x8112, 0x20);
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	switch (data[0]) {
+	case 0:				/* start of frame */
+		gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
+		data += SPCA508_OFFSET_DATA;
+		len -= SPCA508_OFFSET_DATA;
+		gspca_frame_add(gspca_dev, FIRST_PACKET, data, len);
+		break;
+	case 0xff:			/* drop */
+		break;
+	default:
+		data += 1;
+		len -= 1;
+		gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+		break;
+	}
+}
+
+static void setbrightness(struct gspca_dev *gspca_dev, s32 brightness)
+{
+	/* MX seem contrast */
+	reg_write(gspca_dev, 0x8651, brightness);
+	reg_write(gspca_dev, 0x8652, brightness);
+	reg_write(gspca_dev, 0x8653, brightness);
+	reg_write(gspca_dev, 0x8654, brightness);
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		setbrightness(gspca_dev, ctrl->val);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 5);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0, 255, 1, 128);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	return 0;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.pkt_scan = sd_pkt_scan,
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x0130, 0x0130), .driver_info = HamaUSBSightcam},
+	{USB_DEVICE(0x041e, 0x4018), .driver_info = CreativeVista},
+	{USB_DEVICE(0x0733, 0x0110), .driver_info = ViewQuestVQ110},
+	{USB_DEVICE(0x0af9, 0x0010), .driver_info = HamaUSBSightcam},
+	{USB_DEVICE(0x0af9, 0x0011), .driver_info = HamaUSBSightcam2},
+	{USB_DEVICE(0x8086, 0x0110), .driver_info = IntelEasyPCCamera},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/spca561.c b/drivers/media/usb/gspca/spca561.c
new file mode 100644
index 0000000..f389a8d
--- /dev/null
+++ b/drivers/media/usb/gspca/spca561.c
@@ -0,0 +1,920 @@
+/*
+ * Sunplus spca561 subdriver
+ *
+ * Copyright (C) 2004 Michel Xhaard mxhaard@magic.fr
+ *
+ * V4L2 by Jean-Francois Moine <http://moinejf.free.fr>
+ *
+ * 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
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "spca561"
+
+#include <linux/input.h>
+#include "gspca.h"
+
+MODULE_AUTHOR("Michel Xhaard <mxhaard@users.sourceforge.net>");
+MODULE_DESCRIPTION("GSPCA/SPCA561 USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+#define EXPOSURE_MAX (2047 + 325)
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+
+	struct { /* hue/contrast control cluster */
+		struct v4l2_ctrl *contrast;
+		struct v4l2_ctrl *hue;
+	};
+	struct v4l2_ctrl *autogain;
+
+#define EXPO12A_DEF 3
+	__u8 expo12a;		/* expo/gain? for rev 12a */
+
+	__u8 chip_revision;
+#define Rev012A 0
+#define Rev072A 1
+
+	signed char ag_cnt;
+#define AG_CNT_START 13
+};
+
+static const struct v4l2_pix_format sif_012a_mode[] = {
+	{160, 120, V4L2_PIX_FMT_SGBRG8, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 160 * 120,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 3},
+	{176, 144, V4L2_PIX_FMT_SGBRG8, V4L2_FIELD_NONE,
+		.bytesperline = 176,
+		.sizeimage = 176 * 144,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 2},
+	{320, 240, V4L2_PIX_FMT_SPCA561, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 4 / 8,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1},
+	{352, 288, V4L2_PIX_FMT_SPCA561, V4L2_FIELD_NONE,
+		.bytesperline = 352,
+		.sizeimage = 352 * 288 * 4 / 8,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+};
+
+static const struct v4l2_pix_format sif_072a_mode[] = {
+	{160, 120, V4L2_PIX_FMT_SGBRG8, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 160 * 120,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 3},
+	{176, 144, V4L2_PIX_FMT_SGBRG8, V4L2_FIELD_NONE,
+		.bytesperline = 176,
+		.sizeimage = 176 * 144,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 2},
+	{320, 240, V4L2_PIX_FMT_SGBRG8, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1},
+	{352, 288, V4L2_PIX_FMT_SGBRG8, V4L2_FIELD_NONE,
+		.bytesperline = 352,
+		.sizeimage = 352 * 288,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+};
+
+/*
+ * Initialization data
+ * I'm not very sure how to split initialization from open data
+ * chunks. For now, we'll consider everything as initialization
+ */
+/* Frame packet header offsets for the spca561 */
+#define SPCA561_OFFSET_SNAP 1
+#define SPCA561_OFFSET_TYPE 2
+#define SPCA561_OFFSET_COMPRESS 3
+#define SPCA561_OFFSET_FRAMSEQ   4
+#define SPCA561_OFFSET_GPIO 5
+#define SPCA561_OFFSET_USBBUFF 6
+#define SPCA561_OFFSET_WIN2GRAVE 7
+#define SPCA561_OFFSET_WIN2RAVE 8
+#define SPCA561_OFFSET_WIN2BAVE 9
+#define SPCA561_OFFSET_WIN2GBAVE 10
+#define SPCA561_OFFSET_WIN1GRAVE 11
+#define SPCA561_OFFSET_WIN1RAVE 12
+#define SPCA561_OFFSET_WIN1BAVE 13
+#define SPCA561_OFFSET_WIN1GBAVE 14
+#define SPCA561_OFFSET_FREQ 15
+#define SPCA561_OFFSET_VSYNC 16
+#define SPCA561_INDEX_I2C_BASE 0x8800
+#define SPCA561_SNAPBIT 0x20
+#define SPCA561_SNAPCTRL 0x40
+
+static const u16 rev72a_reset[][2] = {
+	{0x0000, 0x8114},	/* Software GPIO output data */
+	{0x0001, 0x8114},	/* Software GPIO output data */
+	{0x0000, 0x8112},	/* Some kind of reset */
+	{}
+};
+static const __u16 rev72a_init_data1[][2] = {
+	{0x0003, 0x8701},	/* PCLK clock delay adjustment */
+	{0x0001, 0x8703},	/* HSYNC from cmos inverted */
+	{0x0011, 0x8118},	/* Enable and conf sensor */
+	{0x0001, 0x8118},	/* Conf sensor */
+	{0x0092, 0x8804},	/* I know nothing about these */
+	{0x0010, 0x8802},	/* 0x88xx registers, so I won't */
+	{}
+};
+static const u16 rev72a_init_sensor1[][2] = {
+	{0x0001, 0x000d},
+	{0x0002, 0x0018},
+	{0x0004, 0x0165},
+	{0x0005, 0x0021},
+	{0x0007, 0x00aa},
+	{0x0020, 0x1504},
+	{0x0039, 0x0002},
+	{0x0035, 0x0010},
+	{0x0009, 0x1049},
+	{0x0028, 0x000b},
+	{0x003b, 0x000f},
+	{0x003c, 0x0000},
+	{}
+};
+static const __u16 rev72a_init_data2[][2] = {
+	{0x0018, 0x8601},	/* Pixel/line selection for color separation */
+	{0x0000, 0x8602},	/* Optical black level for user setting */
+	{0x0060, 0x8604},	/* Optical black horizontal offset */
+	{0x0002, 0x8605},	/* Optical black vertical offset */
+	{0x0000, 0x8603},	/* Non-automatic optical black level */
+	{0x0002, 0x865b},	/* Horizontal offset for valid pixels */
+	{0x0000, 0x865f},	/* Vertical valid pixels window (x2) */
+	{0x00b0, 0x865d},	/* Horizontal valid pixels window (x2) */
+	{0x0090, 0x865e},	/* Vertical valid lines window (x2) */
+	{0x00e0, 0x8406},	/* Memory buffer threshold */
+	{0x0000, 0x8660},	/* Compensation memory stuff */
+	{0x0002, 0x8201},	/* Output address for r/w serial EEPROM */
+	{0x0008, 0x8200},	/* Clear valid bit for serial EEPROM */
+	{0x0001, 0x8200},	/* OprMode to be executed by hardware */
+/* from ms-win */
+	{0x0000, 0x8611},	/* R offset for white balance */
+	{0x00fd, 0x8612},	/* Gr offset for white balance */
+	{0x0003, 0x8613},	/* B offset for white balance */
+	{0x0000, 0x8614},	/* Gb offset for white balance */
+/* from ms-win */
+	{0x0035, 0x8651},	/* R gain for white balance */
+	{0x0040, 0x8652},	/* Gr gain for white balance */
+	{0x005f, 0x8653},	/* B gain for white balance */
+	{0x0040, 0x8654},	/* Gb gain for white balance */
+	{0x0002, 0x8502},	/* Maximum average bit rate stuff */
+	{0x0011, 0x8802},
+
+	{0x0087, 0x8700},	/* Set master clock (96Mhz????) */
+	{0x0081, 0x8702},	/* Master clock output enable */
+
+	{0x0000, 0x8500},	/* Set image type (352x288 no compression) */
+	/* Originally was 0x0010 (352x288 compression) */
+
+	{0x0002, 0x865b},	/* Horizontal offset for valid pixels */
+	{0x0003, 0x865c},	/* Vertical offset for valid lines */
+	{}
+};
+static const u16 rev72a_init_sensor2[][2] = {
+	{0x0003, 0x0121},
+	{0x0004, 0x0165},
+	{0x0005, 0x002f},	/* blanking control column */
+	{0x0006, 0x0000},	/* blanking mode row*/
+	{0x000a, 0x0002},
+	{0x0009, 0x1061},	/* setexposure times && pixel clock
+				 * 0001 0 | 000 0110 0001 */
+	{0x0035, 0x0014},
+	{}
+};
+
+/******************** QC Express etch2 stuff ********************/
+static const __u16 Pb100_1map8300[][2] = {
+	/* reg, value */
+	{0x8320, 0x3304},
+
+	{0x8303, 0x0125},	/* image area */
+	{0x8304, 0x0169},
+	{0x8328, 0x000b},
+	{0x833c, 0x0001},		/*fixme: win:07*/
+
+	{0x832f, 0x1904},		/*fixme: was 0419*/
+	{0x8307, 0x00aa},
+	{0x8301, 0x0003},
+	{0x8302, 0x000e},
+	{}
+};
+static const __u16 Pb100_2map8300[][2] = {
+	/* reg, value */
+	{0x8339, 0x0000},
+	{0x8307, 0x00aa},
+	{}
+};
+
+static const __u16 spca561_161rev12A_data1[][2] = {
+	{0x29, 0x8118},		/* Control register (various enable bits) */
+	{0x08, 0x8114},		/* GPIO: Led off */
+	{0x0e, 0x8112},		/* 0x0e stream off 0x3e stream on */
+	{0x00, 0x8102},		/* white balance - new */
+	{0x92, 0x8804},
+	{0x04, 0x8802},		/* windows uses 08 */
+	{}
+};
+static const __u16 spca561_161rev12A_data2[][2] = {
+	{0x21, 0x8118},
+	{0x10, 0x8500},
+	{0x07, 0x8601},
+	{0x07, 0x8602},
+	{0x04, 0x8501},
+
+	{0x07, 0x8201},		/* windows uses 02 */
+	{0x08, 0x8200},
+	{0x01, 0x8200},
+
+	{0x90, 0x8604},
+	{0x00, 0x8605},
+	{0xb0, 0x8603},
+
+	/* sensor gains */
+	{0x07, 0x8601},		/* white balance - new */
+	{0x07, 0x8602},		/* white balance - new */
+	{0x00, 0x8610},		/* *red */
+	{0x00, 0x8611},		/* 3f   *green */
+	{0x00, 0x8612},		/* green *blue */
+	{0x00, 0x8613},		/* blue *green */
+	{0x43, 0x8614},		/* green *red - white balance - was 0x35 */
+	{0x40, 0x8615},		/* 40   *green - white balance - was 0x35 */
+	{0x71, 0x8616},		/* 7a   *blue - white balance - was 0x35 */
+	{0x40, 0x8617},		/* 40   *green - white balance - was 0x35 */
+
+	{0x0c, 0x8620},		/* 0c */
+	{0xc8, 0x8631},		/* c8 */
+	{0xc8, 0x8634},		/* c8 */
+	{0x23, 0x8635},		/* 23 */
+	{0x1f, 0x8636},		/* 1f */
+	{0xdd, 0x8637},		/* dd */
+	{0xe1, 0x8638},		/* e1 */
+	{0x1d, 0x8639},		/* 1d */
+	{0x21, 0x863a},		/* 21 */
+	{0xe3, 0x863b},		/* e3 */
+	{0xdf, 0x863c},		/* df */
+	{0xf0, 0x8505},
+	{0x32, 0x850a},
+/*	{0x99, 0x8700},		 * - white balance - new (removed) */
+	/* HDG we used to do this in stop0, making the init state and the state
+	   after a start / stop different, so do this here instead. */
+	{0x29, 0x8118},
+	{}
+};
+
+static void reg_w_val(struct gspca_dev *gspca_dev, __u16 index, __u8 value)
+{
+	int ret;
+	struct usb_device *dev = gspca_dev->dev;
+
+	ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+			      0,		/* request */
+			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      value, index, NULL, 0, 500);
+	gspca_dbg(gspca_dev, D_USBO, "reg write: 0x%02x:0x%02x\n",
+		  index, value);
+	if (ret < 0)
+		pr_err("reg write: error %d\n", ret);
+}
+
+static void write_vector(struct gspca_dev *gspca_dev,
+			const __u16 data[][2])
+{
+	int i;
+
+	i = 0;
+	while (data[i][1] != 0) {
+		reg_w_val(gspca_dev, data[i][1], data[i][0]);
+		i++;
+	}
+}
+
+/* read 'len' bytes to gspca_dev->usb_buf */
+static void reg_r(struct gspca_dev *gspca_dev,
+		  __u16 index, __u16 length)
+{
+	usb_control_msg(gspca_dev->dev,
+			usb_rcvctrlpipe(gspca_dev->dev, 0),
+			0,			/* request */
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0,			/* value */
+			index, gspca_dev->usb_buf, length, 500);
+}
+
+/* write 'len' bytes from gspca_dev->usb_buf */
+static void reg_w_buf(struct gspca_dev *gspca_dev,
+		      __u16 index, __u16 len)
+{
+	usb_control_msg(gspca_dev->dev,
+			usb_sndctrlpipe(gspca_dev->dev, 0),
+			0,			/* request */
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0,			/* value */
+			index, gspca_dev->usb_buf, len, 500);
+}
+
+static void i2c_write(struct gspca_dev *gspca_dev, __u16 value, __u16 reg)
+{
+	int retry = 60;
+
+	reg_w_val(gspca_dev, 0x8801, reg);
+	reg_w_val(gspca_dev, 0x8805, value);
+	reg_w_val(gspca_dev, 0x8800, value >> 8);
+	do {
+		reg_r(gspca_dev, 0x8803, 1);
+		if (!gspca_dev->usb_buf[0])
+			return;
+		msleep(10);
+	} while (--retry);
+}
+
+static int i2c_read(struct gspca_dev *gspca_dev, __u16 reg, __u8 mode)
+{
+	int retry = 60;
+	__u8 value;
+
+	reg_w_val(gspca_dev, 0x8804, 0x92);
+	reg_w_val(gspca_dev, 0x8801, reg);
+	reg_w_val(gspca_dev, 0x8802, mode | 0x01);
+	do {
+		reg_r(gspca_dev, 0x8803, 1);
+		if (!gspca_dev->usb_buf[0]) {
+			reg_r(gspca_dev, 0x8800, 1);
+			value = gspca_dev->usb_buf[0];
+			reg_r(gspca_dev, 0x8805, 1);
+			return ((int) value << 8) | gspca_dev->usb_buf[0];
+		}
+		msleep(10);
+	} while (--retry);
+	return -1;
+}
+
+static void sensor_mapwrite(struct gspca_dev *gspca_dev,
+			    const __u16 (*sensormap)[2])
+{
+	while ((*sensormap)[0]) {
+		gspca_dev->usb_buf[0] = (*sensormap)[1];
+		gspca_dev->usb_buf[1] = (*sensormap)[1] >> 8;
+		reg_w_buf(gspca_dev, (*sensormap)[0], 2);
+		sensormap++;
+	}
+}
+
+static void write_sensor_72a(struct gspca_dev *gspca_dev,
+			    const __u16 (*sensor)[2])
+{
+	while ((*sensor)[0]) {
+		i2c_write(gspca_dev, (*sensor)[1], (*sensor)[0]);
+		sensor++;
+	}
+}
+
+static void init_161rev12A(struct gspca_dev *gspca_dev)
+{
+	write_vector(gspca_dev, spca561_161rev12A_data1);
+	sensor_mapwrite(gspca_dev, Pb100_1map8300);
+/*fixme: should be in sd_start*/
+	write_vector(gspca_dev, spca561_161rev12A_data2);
+	sensor_mapwrite(gspca_dev, Pb100_2map8300);
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+		     const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam;
+	__u16 vendor, product;
+	__u8 data1, data2;
+
+	/* Read frm global register the USB product and vendor IDs, just to
+	 * prove that we can communicate with the device.  This works, which
+	 * confirms at we are communicating properly and that the device
+	 * is a 561. */
+	reg_r(gspca_dev, 0x8104, 1);
+	data1 = gspca_dev->usb_buf[0];
+	reg_r(gspca_dev, 0x8105, 1);
+	data2 = gspca_dev->usb_buf[0];
+	vendor = (data2 << 8) | data1;
+	reg_r(gspca_dev, 0x8106, 1);
+	data1 = gspca_dev->usb_buf[0];
+	reg_r(gspca_dev, 0x8107, 1);
+	data2 = gspca_dev->usb_buf[0];
+	product = (data2 << 8) | data1;
+	if (vendor != id->idVendor || product != id->idProduct) {
+		gspca_dbg(gspca_dev, D_PROBE, "Bad vendor / product from device\n");
+		return -EINVAL;
+	}
+
+	cam = &gspca_dev->cam;
+	cam->needs_full_bandwidth = 1;
+
+	sd->chip_revision = id->driver_info;
+	if (sd->chip_revision == Rev012A) {
+		cam->cam_mode = sif_012a_mode;
+		cam->nmodes = ARRAY_SIZE(sif_012a_mode);
+	} else {
+		cam->cam_mode = sif_072a_mode;
+		cam->nmodes = ARRAY_SIZE(sif_072a_mode);
+	}
+	sd->expo12a = EXPO12A_DEF;
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init_12a(struct gspca_dev *gspca_dev)
+{
+	gspca_dbg(gspca_dev, D_STREAM, "Chip revision: 012a\n");
+	init_161rev12A(gspca_dev);
+	return 0;
+}
+static int sd_init_72a(struct gspca_dev *gspca_dev)
+{
+	gspca_dbg(gspca_dev, D_STREAM, "Chip revision: 072a\n");
+	write_vector(gspca_dev, rev72a_reset);
+	msleep(200);
+	write_vector(gspca_dev, rev72a_init_data1);
+	write_sensor_72a(gspca_dev, rev72a_init_sensor1);
+	write_vector(gspca_dev, rev72a_init_data2);
+	write_sensor_72a(gspca_dev, rev72a_init_sensor2);
+	reg_w_val(gspca_dev, 0x8112, 0x30);
+	return 0;
+}
+
+static void setbrightness(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	__u16 reg;
+
+	if (sd->chip_revision == Rev012A)
+		reg = 0x8610;
+	else
+		reg = 0x8611;
+
+	reg_w_val(gspca_dev, reg + 0, val);		/* R */
+	reg_w_val(gspca_dev, reg + 1, val);		/* Gr */
+	reg_w_val(gspca_dev, reg + 2, val);		/* B */
+	reg_w_val(gspca_dev, reg + 3, val);		/* Gb */
+}
+
+static void setwhite(struct gspca_dev *gspca_dev, s32 white, s32 contrast)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	__u8 blue, red;
+	__u16 reg;
+
+	/* try to emulate MS-win as possible */
+	red = 0x20 + white * 3 / 8;
+	blue = 0x90 - white * 5 / 8;
+	if (sd->chip_revision == Rev012A) {
+		reg = 0x8614;
+	} else {
+		reg = 0x8651;
+		red += contrast - 0x20;
+		blue += contrast - 0x20;
+		reg_w_val(gspca_dev, 0x8652, contrast + 0x20); /* Gr */
+		reg_w_val(gspca_dev, 0x8654, contrast + 0x20); /* Gb */
+	}
+	reg_w_val(gspca_dev, reg, red);
+	reg_w_val(gspca_dev, reg + 2, blue);
+}
+
+/* rev 12a only */
+static void setexposure(struct gspca_dev *gspca_dev, s32 val)
+{
+	int i, expo = 0;
+
+	/* Register 0x8309 controls exposure for the spca561,
+	   the basic exposure setting goes from 1-2047, where 1 is completely
+	   dark and 2047 is very bright. It not only influences exposure but
+	   also the framerate (to allow for longer exposure) from 1 - 300 it
+	   only raises the exposure time then from 300 - 600 it halves the
+	   framerate to be able to further raise the exposure time and for every
+	   300 more it halves the framerate again. This allows for a maximum
+	   exposure time of circa 0.2 - 0.25 seconds (30 / (2000/3000) fps).
+	   Sometimes this is not enough, the 1-2047 uses bits 0-10, bits 11-12
+	   configure a divider for the base framerate which us used at the
+	   exposure setting of 1-300. These bits configure the base framerate
+	   according to the following formula: fps = 60 / (value + 2) */
+
+	/* We choose to use the high bits setting the fixed framerate divisor
+	   asap, as setting high basic exposure setting without the fixed
+	   divider in combination with high gains makes the cam stop */
+	int table[] =  { 0, 450, 550, 625, EXPOSURE_MAX };
+
+	for (i = 0; i < ARRAY_SIZE(table) - 1; i++) {
+		if (val <= table[i + 1]) {
+			expo  = val - table[i];
+			if (i)
+				expo += 300;
+			expo |= i << 11;
+			break;
+		}
+	}
+
+	gspca_dev->usb_buf[0] = expo;
+	gspca_dev->usb_buf[1] = expo >> 8;
+	reg_w_buf(gspca_dev, 0x8309, 2);
+}
+
+/* rev 12a only */
+static void setgain(struct gspca_dev *gspca_dev, s32 val)
+{
+	/* gain reg low 6 bits  0-63 gain, bit 6 and 7, both double the
+	   sensitivity when set, so 31 + one of them set == 63, and 15
+	   with both of them set == 63 */
+	if (val < 64)
+		gspca_dev->usb_buf[0] = val;
+	else if (val < 128)
+		gspca_dev->usb_buf[0] = (val / 2) | 0x40;
+	else
+		gspca_dev->usb_buf[0] = (val / 4) | 0xc0;
+
+	gspca_dev->usb_buf[1] = 0;
+	reg_w_buf(gspca_dev, 0x8335, 2);
+}
+
+static void setautogain(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (val)
+		sd->ag_cnt = AG_CNT_START;
+	else
+		sd->ag_cnt = -1;
+}
+
+static int sd_start_12a(struct gspca_dev *gspca_dev)
+{
+	int mode;
+	static const __u8 Reg8391[8] =
+		{0x92, 0x30, 0x20, 0x00, 0x0c, 0x00, 0x00, 0x00};
+
+	mode = gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv;
+	if (mode <= 1) {
+		/* Use compression on 320x240 and above */
+		reg_w_val(gspca_dev, 0x8500, 0x10 | mode);
+	} else {
+		/* I couldn't get the compression to work below 320x240
+		 * Fortunately at these resolutions the bandwidth
+		 * is sufficient to push raw frames at ~20fps */
+		reg_w_val(gspca_dev, 0x8500, mode);
+	}		/* -- qq@kuku.eu.org */
+
+	gspca_dev->usb_buf[0] = 0xaa;
+	gspca_dev->usb_buf[1] = 0x00;
+	reg_w_buf(gspca_dev, 0x8307, 2);
+	/* clock - lower 0x8X values lead to fps > 30 */
+	reg_w_val(gspca_dev, 0x8700, 0x8a);
+					/* 0x8f 0x85 0x27 clock */
+	reg_w_val(gspca_dev, 0x8112, 0x1e | 0x20);
+	reg_w_val(gspca_dev, 0x850b, 0x03);
+	memcpy(gspca_dev->usb_buf, Reg8391, 8);
+	reg_w_buf(gspca_dev, 0x8391, 8);
+	reg_w_buf(gspca_dev, 0x8390, 8);
+
+	/* Led ON (bit 3 -> 0 */
+	reg_w_val(gspca_dev, 0x8114, 0x00);
+	return 0;
+}
+static int sd_start_72a(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int Clck;
+	int mode;
+
+	write_vector(gspca_dev, rev72a_reset);
+	msleep(200);
+	write_vector(gspca_dev, rev72a_init_data1);
+	write_sensor_72a(gspca_dev, rev72a_init_sensor1);
+
+	mode = gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv;
+	switch (mode) {
+	default:
+	case 0:
+		Clck = 0x27;		/* ms-win 0x87 */
+		break;
+	case 1:
+		Clck = 0x25;
+		break;
+	case 2:
+		Clck = 0x22;
+		break;
+	case 3:
+		Clck = 0x21;
+		break;
+	}
+	reg_w_val(gspca_dev, 0x8700, Clck);	/* 0x27 clock */
+	reg_w_val(gspca_dev, 0x8702, 0x81);
+	reg_w_val(gspca_dev, 0x8500, mode);	/* mode */
+	write_sensor_72a(gspca_dev, rev72a_init_sensor2);
+	setwhite(gspca_dev, v4l2_ctrl_g_ctrl(sd->hue),
+			v4l2_ctrl_g_ctrl(sd->contrast));
+/*	setbrightness(gspca_dev);	 * fixme: bad values */
+	setautogain(gspca_dev, v4l2_ctrl_g_ctrl(sd->autogain));
+	reg_w_val(gspca_dev, 0x8112, 0x10 | 0x20);
+	return 0;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->chip_revision == Rev012A) {
+		reg_w_val(gspca_dev, 0x8112, 0x0e);
+		/* Led Off (bit 3 -> 1 */
+		reg_w_val(gspca_dev, 0x8114, 0x08);
+	} else {
+		reg_w_val(gspca_dev, 0x8112, 0x20);
+/*		reg_w_val(gspca_dev, 0x8102, 0x00); ?? */
+	}
+}
+
+static void do_autogain(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int expotimes;
+	int pixelclk;
+	int gainG;
+	__u8 R, Gr, Gb, B;
+	int y;
+	__u8 luma_mean = 110;
+	__u8 luma_delta = 20;
+	__u8 spring = 4;
+
+	if (sd->ag_cnt < 0)
+		return;
+	if (--sd->ag_cnt >= 0)
+		return;
+	sd->ag_cnt = AG_CNT_START;
+
+	switch (sd->chip_revision) {
+	case Rev072A:
+		reg_r(gspca_dev, 0x8621, 1);
+		Gr = gspca_dev->usb_buf[0];
+		reg_r(gspca_dev, 0x8622, 1);
+		R = gspca_dev->usb_buf[0];
+		reg_r(gspca_dev, 0x8623, 1);
+		B = gspca_dev->usb_buf[0];
+		reg_r(gspca_dev, 0x8624, 1);
+		Gb = gspca_dev->usb_buf[0];
+		y = (77 * R + 75 * (Gr + Gb) + 29 * B) >> 8;
+		/* u= (128*B-(43*(Gr+Gb+R))) >> 8; */
+		/* v= (128*R-(53*(Gr+Gb))-21*B) >> 8; */
+
+		if (y < luma_mean - luma_delta ||
+		    y > luma_mean + luma_delta) {
+			expotimes = i2c_read(gspca_dev, 0x09, 0x10);
+			pixelclk = 0x0800;
+			expotimes = expotimes & 0x07ff;
+			gainG = i2c_read(gspca_dev, 0x35, 0x10);
+
+			expotimes += (luma_mean - y) >> spring;
+			gainG += (luma_mean - y) / 50;
+
+			if (gainG > 0x3f)
+				gainG = 0x3f;
+			else if (gainG < 3)
+				gainG = 3;
+			i2c_write(gspca_dev, gainG, 0x35);
+
+			if (expotimes > 0x0256)
+				expotimes = 0x0256;
+			else if (expotimes < 3)
+				expotimes = 3;
+			i2c_write(gspca_dev, expotimes | pixelclk, 0x09);
+		}
+		break;
+	}
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,		/* isoc packet */
+			int len)		/* iso packet length */
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	len--;
+	switch (*data++) {			/* sequence number */
+	case 0:					/* start of frame */
+		gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
+
+		/* This should never happen */
+		if (len < 2) {
+			gspca_err(gspca_dev, "Short SOF packet, ignoring\n\n\n\n\n");
+			gspca_dev->last_packet_type = DISCARD_PACKET;
+			return;
+		}
+
+#if IS_ENABLED(CONFIG_INPUT)
+		if (data[0] & 0x20) {
+			input_report_key(gspca_dev->input_dev, KEY_CAMERA, 1);
+			input_sync(gspca_dev->input_dev);
+			input_report_key(gspca_dev->input_dev, KEY_CAMERA, 0);
+			input_sync(gspca_dev->input_dev);
+		}
+#endif
+
+		if (data[1] & 0x10) {
+			/* compressed bayer */
+			gspca_frame_add(gspca_dev, FIRST_PACKET, data, len);
+		} else {
+			/* raw bayer (with a header, which we skip) */
+			if (sd->chip_revision == Rev012A) {
+				data += 20;
+				len -= 20;
+			} else {
+				data += 16;
+				len -= 16;
+			}
+			gspca_frame_add(gspca_dev, FIRST_PACKET, data, len);
+		}
+		return;
+	case 0xff:			/* drop (empty mpackets) */
+		return;
+	}
+	gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *)gspca_dev;
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		setbrightness(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_CONTRAST:
+		/* hue/contrast control cluster for 72a */
+		setwhite(gspca_dev, sd->hue->val, ctrl->val);
+		break;
+	case V4L2_CID_HUE:
+		/* just plain hue control for 12a */
+		setwhite(gspca_dev, ctrl->val, 0);
+		break;
+	case V4L2_CID_EXPOSURE:
+		setexposure(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_GAIN:
+		setgain(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_AUTOGAIN:
+		setautogain(gspca_dev, ctrl->val);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls_12a(struct gspca_dev *gspca_dev)
+{
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 3);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_HUE, 1, 0x7f, 1, 0x40);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, -128, 127, 1, 0);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_EXPOSURE, 1, EXPOSURE_MAX, 1, 700);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_GAIN, 0, 255, 1, 63);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	return 0;
+}
+
+static int sd_init_controls_72a(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *)gspca_dev;
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 4);
+	sd->contrast = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_CONTRAST, 0, 0x3f, 1, 0x20);
+	sd->hue = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_HUE, 1, 0x7f, 1, 0x40);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0, 0x3f, 1, 0x20);
+	sd->autogain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	v4l2_ctrl_cluster(2, &sd->contrast);
+	return 0;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc_12a = {
+	.name = MODULE_NAME,
+	.init_controls = sd_init_controls_12a,
+	.config = sd_config,
+	.init = sd_init_12a,
+	.start = sd_start_12a,
+	.stopN = sd_stopN,
+	.pkt_scan = sd_pkt_scan,
+#if IS_ENABLED(CONFIG_INPUT)
+	.other_input = 1,
+#endif
+};
+static const struct sd_desc sd_desc_72a = {
+	.name = MODULE_NAME,
+	.init_controls = sd_init_controls_72a,
+	.config = sd_config,
+	.init = sd_init_72a,
+	.start = sd_start_72a,
+	.stopN = sd_stopN,
+	.pkt_scan = sd_pkt_scan,
+	.dq_callback = do_autogain,
+#if IS_ENABLED(CONFIG_INPUT)
+	.other_input = 1,
+#endif
+};
+static const struct sd_desc *sd_desc[2] = {
+	&sd_desc_12a,
+	&sd_desc_72a
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x041e, 0x401a), .driver_info = Rev072A},
+	{USB_DEVICE(0x041e, 0x403b), .driver_info = Rev012A},
+	{USB_DEVICE(0x0458, 0x7004), .driver_info = Rev072A},
+	{USB_DEVICE(0x0461, 0x0815), .driver_info = Rev072A},
+	{USB_DEVICE(0x046d, 0x0928), .driver_info = Rev012A},
+	{USB_DEVICE(0x046d, 0x0929), .driver_info = Rev012A},
+	{USB_DEVICE(0x046d, 0x092a), .driver_info = Rev012A},
+	{USB_DEVICE(0x046d, 0x092b), .driver_info = Rev012A},
+	{USB_DEVICE(0x046d, 0x092c), .driver_info = Rev012A},
+	{USB_DEVICE(0x046d, 0x092d), .driver_info = Rev012A},
+	{USB_DEVICE(0x046d, 0x092e), .driver_info = Rev012A},
+	{USB_DEVICE(0x046d, 0x092f), .driver_info = Rev012A},
+	{USB_DEVICE(0x04fc, 0x0561), .driver_info = Rev072A},
+	{USB_DEVICE(0x060b, 0xa001), .driver_info = Rev072A},
+	{USB_DEVICE(0x10fd, 0x7e50), .driver_info = Rev072A},
+	{USB_DEVICE(0xabcd, 0xcdee), .driver_info = Rev072A},
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+		    const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id,
+				sd_desc[id->driver_info],
+				sizeof(struct sd),
+			       THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/sq905.c b/drivers/media/usb/gspca/sq905.c
new file mode 100644
index 0000000..ffea9c3
--- /dev/null
+++ b/drivers/media/usb/gspca/sq905.c
@@ -0,0 +1,434 @@
+/*
+ * SQ905 subdriver
+ *
+ * Copyright (C) 2008, 2009 Adam Baker and Theodore Kilgore
+ *
+ * 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
+ * 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.
+ */
+
+/*
+ * History and Acknowledgments
+ *
+ * The original Linux driver for SQ905 based cameras was written by
+ * Marcell Lengyel and furter developed by many other contributors
+ * and is available from http://sourceforge.net/projects/sqcam/
+ *
+ * This driver takes advantage of the reverse engineering work done for
+ * that driver and for libgphoto2 but shares no code with them.
+ *
+ * This driver has used as a base the finepix driver and other gspca
+ * based drivers and may still contain code fragments taken from those
+ * drivers.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "sq905"
+
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+#include "gspca.h"
+
+MODULE_AUTHOR("Adam Baker <linux@baker-net.org.uk>, Theodore Kilgore <kilgota@auburn.edu>");
+MODULE_DESCRIPTION("GSPCA/SQ905 USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+/* Default timeouts, in ms */
+#define SQ905_CMD_TIMEOUT 500
+#define SQ905_DATA_TIMEOUT 1000
+
+/* Maximum transfer size to use. */
+#define SQ905_MAX_TRANSFER 0x8000
+#define FRAME_HEADER_LEN 64
+
+/* The known modes, or registers. These go in the "value" slot. */
+
+/* 00 is "none" obviously */
+
+#define SQ905_BULK_READ	0x03	/* precedes any bulk read */
+#define SQ905_COMMAND	0x06	/* precedes the command codes below */
+#define SQ905_PING	0x07	/* when reading an "idling" command */
+#define SQ905_READ_DONE 0xc0    /* ack bulk read completed */
+
+/* Any non-zero value in the bottom 2 bits of the 2nd byte of
+ * the ID appears to indicate the camera can do 640*480. If the
+ * LSB of that byte is set the image is just upside down, otherwise
+ * it is rotated 180 degrees. */
+#define SQ905_HIRES_MASK	0x00000300
+#define SQ905_ORIENTATION_MASK	0x00000100
+
+/* Some command codes. These go in the "index" slot. */
+
+#define SQ905_ID      0xf0	/* asks for model string */
+#define SQ905_CONFIG  0x20	/* gets photo alloc. table, not used here */
+#define SQ905_DATA    0x30	/* accesses photo data, not used here */
+#define SQ905_CLEAR   0xa0	/* clear everything */
+#define SQ905_CAPTURE_LOW  0x60	/* Starts capture at 160x120 */
+#define SQ905_CAPTURE_MED  0x61	/* Starts capture at 320x240 */
+#define SQ905_CAPTURE_HIGH 0x62	/* Starts capture at 640x480 (some cams only) */
+/* note that the capture command also controls the output dimensions */
+
+/* Structure to hold all of our device specific stuff */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+
+	/*
+	 * Driver stuff
+	 */
+	struct work_struct work_struct;
+	struct workqueue_struct *work_thread;
+};
+
+static struct v4l2_pix_format sq905_mode[] = {
+	{ 160, 120, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 160 * 120,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+	{ 320, 240, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+	{ 640, 480, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0}
+};
+
+/*
+ * Send a command to the camera.
+ */
+static int sq905_command(struct gspca_dev *gspca_dev, u16 index)
+{
+	int ret;
+
+	gspca_dev->usb_buf[0] = '\0';
+	ret = usb_control_msg(gspca_dev->dev,
+			      usb_sndctrlpipe(gspca_dev->dev, 0),
+			      USB_REQ_SYNCH_FRAME,                /* request */
+			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      SQ905_COMMAND, index, gspca_dev->usb_buf, 1,
+			      SQ905_CMD_TIMEOUT);
+	if (ret < 0) {
+		pr_err("%s: usb_control_msg failed (%d)\n", __func__, ret);
+		return ret;
+	}
+
+	ret = usb_control_msg(gspca_dev->dev,
+			      usb_sndctrlpipe(gspca_dev->dev, 0),
+			      USB_REQ_SYNCH_FRAME,                /* request */
+			      USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      SQ905_PING, 0, gspca_dev->usb_buf, 1,
+			      SQ905_CMD_TIMEOUT);
+	if (ret < 0) {
+		pr_err("%s: usb_control_msg failed 2 (%d)\n", __func__, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * Acknowledge the end of a frame - see warning on sq905_command.
+ */
+static int sq905_ack_frame(struct gspca_dev *gspca_dev)
+{
+	int ret;
+
+	gspca_dev->usb_buf[0] = '\0';
+	ret = usb_control_msg(gspca_dev->dev,
+			      usb_sndctrlpipe(gspca_dev->dev, 0),
+			      USB_REQ_SYNCH_FRAME,                /* request */
+			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      SQ905_READ_DONE, 0, gspca_dev->usb_buf, 1,
+			      SQ905_CMD_TIMEOUT);
+	if (ret < 0) {
+		pr_err("%s: usb_control_msg failed (%d)\n", __func__, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+/*
+ *  request and read a block of data - see warning on sq905_command.
+ */
+static int
+sq905_read_data(struct gspca_dev *gspca_dev, u8 *data, int size, int need_lock)
+{
+	int ret;
+	int act_len;
+
+	gspca_dev->usb_buf[0] = '\0';
+	if (need_lock)
+		mutex_lock(&gspca_dev->usb_lock);
+	ret = usb_control_msg(gspca_dev->dev,
+			      usb_sndctrlpipe(gspca_dev->dev, 0),
+			      USB_REQ_SYNCH_FRAME,                /* request */
+			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      SQ905_BULK_READ, size, gspca_dev->usb_buf,
+			      1, SQ905_CMD_TIMEOUT);
+	if (need_lock)
+		mutex_unlock(&gspca_dev->usb_lock);
+	if (ret < 0) {
+		pr_err("%s: usb_control_msg failed (%d)\n", __func__, ret);
+		return ret;
+	}
+	ret = usb_bulk_msg(gspca_dev->dev,
+			   usb_rcvbulkpipe(gspca_dev->dev, 0x81),
+			   data, size, &act_len, SQ905_DATA_TIMEOUT);
+
+	/* successful, it returns 0, otherwise  negative */
+	if (ret < 0 || act_len != size) {
+		pr_err("bulk read fail (%d) len %d/%d\n", ret, act_len, size);
+		return -EIO;
+	}
+	return 0;
+}
+
+/*
+ * This function is called as a workqueue function and runs whenever the camera
+ * is streaming data. Because it is a workqueue function it is allowed to sleep
+ * so we can use synchronous USB calls. To avoid possible collisions with other
+ * threads attempting to use gspca_dev->usb_buf we take the usb_lock when
+ * performing USB operations using it. In practice we don't really need this
+ * as the camera doesn't provide any controls.
+ */
+static void sq905_dostream(struct work_struct *work)
+{
+	struct sd *dev = container_of(work, struct sd, work_struct);
+	struct gspca_dev *gspca_dev = &dev->gspca_dev;
+	int bytes_left; /* bytes remaining in current frame. */
+	int data_len;   /* size to use for the next read. */
+	int header_read; /* true if we have already read the frame header. */
+	int packet_type;
+	int frame_sz;
+	int ret;
+	u8 *data;
+	u8 *buffer;
+
+	buffer = kmalloc(SQ905_MAX_TRANSFER, GFP_KERNEL);
+	if (!buffer) {
+		pr_err("Couldn't allocate USB buffer\n");
+		goto quit_stream;
+	}
+
+	frame_sz = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].sizeimage
+			+ FRAME_HEADER_LEN;
+
+	while (gspca_dev->present && gspca_dev->streaming) {
+#ifdef CONFIG_PM
+		if (gspca_dev->frozen)
+			break;
+#endif
+		/* request some data and then read it until we have
+		 * a complete frame. */
+		bytes_left = frame_sz;
+		header_read = 0;
+
+		/* Note we do not check for gspca_dev->streaming here, as
+		   we must finish reading an entire frame, otherwise the
+		   next time we stream we start reading in the middle of a
+		   frame. */
+		while (bytes_left > 0 && gspca_dev->present) {
+			data_len = bytes_left > SQ905_MAX_TRANSFER ?
+				SQ905_MAX_TRANSFER : bytes_left;
+			ret = sq905_read_data(gspca_dev, buffer, data_len, 1);
+			if (ret < 0)
+				goto quit_stream;
+			gspca_dbg(gspca_dev, D_PACK,
+				  "Got %d bytes out of %d for frame\n",
+				  data_len, bytes_left);
+			bytes_left -= data_len;
+			data = buffer;
+			if (!header_read) {
+				packet_type = FIRST_PACKET;
+				/* The first 64 bytes of each frame are
+				 * a header full of FF 00 bytes */
+				data += FRAME_HEADER_LEN;
+				data_len -= FRAME_HEADER_LEN;
+				header_read = 1;
+			} else if (bytes_left == 0) {
+				packet_type = LAST_PACKET;
+			} else {
+				packet_type = INTER_PACKET;
+			}
+			gspca_frame_add(gspca_dev, packet_type,
+					data, data_len);
+			/* If entire frame fits in one packet we still
+			   need to add a LAST_PACKET */
+			if (packet_type == FIRST_PACKET &&
+			    bytes_left == 0)
+				gspca_frame_add(gspca_dev, LAST_PACKET,
+						NULL, 0);
+		}
+		if (gspca_dev->present) {
+			/* acknowledge the frame */
+			mutex_lock(&gspca_dev->usb_lock);
+			ret = sq905_ack_frame(gspca_dev);
+			mutex_unlock(&gspca_dev->usb_lock);
+			if (ret < 0)
+				goto quit_stream;
+		}
+	}
+quit_stream:
+	if (gspca_dev->present) {
+		mutex_lock(&gspca_dev->usb_lock);
+		sq905_command(gspca_dev, SQ905_CLEAR);
+		mutex_unlock(&gspca_dev->usb_lock);
+	}
+	kfree(buffer);
+}
+
+/* This function is called at probe time just before sd_init */
+static int sd_config(struct gspca_dev *gspca_dev,
+		const struct usb_device_id *id)
+{
+	struct cam *cam = &gspca_dev->cam;
+	struct sd *dev = (struct sd *) gspca_dev;
+
+	/* We don't use the buffer gspca allocates so make it small. */
+	cam->bulk = 1;
+	cam->bulk_size = 64;
+
+	INIT_WORK(&dev->work_struct, sq905_dostream);
+
+	return 0;
+}
+
+/* called on streamoff with alt==0 and on disconnect */
+/* the usb_lock is held at entry - restore on exit */
+static void sd_stop0(struct gspca_dev *gspca_dev)
+{
+	struct sd *dev = (struct sd *) gspca_dev;
+
+	/* wait for the work queue to terminate */
+	mutex_unlock(&gspca_dev->usb_lock);
+	/* This waits for sq905_dostream to finish */
+	destroy_workqueue(dev->work_thread);
+	dev->work_thread = NULL;
+	mutex_lock(&gspca_dev->usb_lock);
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	u32 ident;
+	int ret;
+
+	/* connect to the camera and read
+	 * the model ID and process that and put it away.
+	 */
+	ret = sq905_command(gspca_dev, SQ905_CLEAR);
+	if (ret < 0)
+		return ret;
+	ret = sq905_command(gspca_dev, SQ905_ID);
+	if (ret < 0)
+		return ret;
+	ret = sq905_read_data(gspca_dev, gspca_dev->usb_buf, 4, 0);
+	if (ret < 0)
+		return ret;
+	/* usb_buf is allocated with kmalloc so is aligned.
+	 * Camera model number is the right way round if we assume this
+	 * reverse engineered ID is supposed to be big endian. */
+	ident = be32_to_cpup((__be32 *)gspca_dev->usb_buf);
+	ret = sq905_command(gspca_dev, SQ905_CLEAR);
+	if (ret < 0)
+		return ret;
+	gspca_dbg(gspca_dev, D_CONF, "SQ905 camera ID %08x detected\n", ident);
+	gspca_dev->cam.cam_mode = sq905_mode;
+	gspca_dev->cam.nmodes = ARRAY_SIZE(sq905_mode);
+	if (!(ident & SQ905_HIRES_MASK))
+		gspca_dev->cam.nmodes--;
+
+	if (ident & SQ905_ORIENTATION_MASK)
+		gspca_dev->cam.input_flags = V4L2_IN_ST_VFLIP;
+	else
+		gspca_dev->cam.input_flags = V4L2_IN_ST_VFLIP |
+					     V4L2_IN_ST_HFLIP;
+	return 0;
+}
+
+/* Set up for getting frames. */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *dev = (struct sd *) gspca_dev;
+	int ret;
+
+	/* "Open the shutter" and set size, to start capture */
+	switch (gspca_dev->curr_mode) {
+	default:
+/*	case 2: */
+		gspca_dbg(gspca_dev, D_STREAM, "Start streaming at high resolution\n");
+		ret = sq905_command(&dev->gspca_dev, SQ905_CAPTURE_HIGH);
+		break;
+	case 1:
+		gspca_dbg(gspca_dev, D_STREAM, "Start streaming at medium resolution\n");
+		ret = sq905_command(&dev->gspca_dev, SQ905_CAPTURE_MED);
+		break;
+	case 0:
+		gspca_dbg(gspca_dev, D_STREAM, "Start streaming at low resolution\n");
+		ret = sq905_command(&dev->gspca_dev, SQ905_CAPTURE_LOW);
+	}
+
+	if (ret < 0) {
+		gspca_err(gspca_dev, "Start streaming command failed\n");
+		return ret;
+	}
+	/* Start the workqueue function to do the streaming */
+	dev->work_thread = create_singlethread_workqueue(MODULE_NAME);
+	queue_work(dev->work_thread, &dev->work_struct);
+
+	return 0;
+}
+
+/* Table of supported USB devices */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x2770, 0x9120)},
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name   = MODULE_NAME,
+	.config = sd_config,
+	.init   = sd_init,
+	.start  = sd_start,
+	.stop0  = sd_stop0,
+};
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id,
+			&sd_desc,
+			sizeof(struct sd),
+			THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name       = MODULE_NAME,
+	.id_table   = device_table,
+	.probe      = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume  = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/sq905c.c b/drivers/media/usb/gspca/sq905c.c
new file mode 100644
index 0000000..274921c
--- /dev/null
+++ b/drivers/media/usb/gspca/sq905c.c
@@ -0,0 +1,336 @@
+/*
+ * SQ905C subdriver
+ *
+ * Copyright (C) 2009 Theodore Kilgore
+ *
+ * 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
+ * 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.
+ */
+
+/*
+ *
+ * This driver uses work done in
+ * libgphoto2/camlibs/digigr8, Copyright (C) Theodore Kilgore.
+ *
+ * This driver has also used as a base the sq905c driver
+ * and may contain code fragments from it.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "sq905c"
+
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+#include "gspca.h"
+
+MODULE_AUTHOR("Theodore Kilgore <kilgota@auburn.edu>");
+MODULE_DESCRIPTION("GSPCA/SQ905C USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+/* Default timeouts, in ms */
+#define SQ905C_CMD_TIMEOUT 500
+#define SQ905C_DATA_TIMEOUT 1000
+
+/* Maximum transfer size to use. */
+#define SQ905C_MAX_TRANSFER 0x8000
+
+#define FRAME_HEADER_LEN 0x50
+
+/* Commands. These go in the "value" slot. */
+#define SQ905C_CLEAR   0xa0		/* clear everything */
+#define SQ905C_GET_ID  0x14f4		/* Read version number */
+#define SQ905C_CAPTURE_LOW 0xa040	/* Starts capture at 160x120 */
+#define SQ905C_CAPTURE_MED 0x1440	/* Starts capture at 320x240 */
+#define SQ905C_CAPTURE_HI 0x2840	/* Starts capture at 320x240 */
+
+/* For capture, this must go in the "index" slot. */
+#define SQ905C_CAPTURE_INDEX 0x110f
+
+/* Structure to hold all of our device specific stuff */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+	const struct v4l2_pix_format *cap_mode;
+	/* Driver stuff */
+	struct work_struct work_struct;
+	struct workqueue_struct *work_thread;
+};
+
+/*
+ * Most of these cameras will do 640x480 and 320x240. 160x120 works
+ * in theory but gives very poor output. Therefore, not supported.
+ * The 0x2770:0x9050 cameras have max resolution of 320x240.
+ */
+static struct v4l2_pix_format sq905c_mode[] = {
+	{ 320, 240, V4L2_PIX_FMT_SQ905C, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+	{ 640, 480, V4L2_PIX_FMT_SQ905C, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0}
+};
+
+/* Send a command to the camera. */
+static int sq905c_command(struct gspca_dev *gspca_dev, u16 command, u16 index)
+{
+	int ret;
+
+	ret = usb_control_msg(gspca_dev->dev,
+			      usb_sndctrlpipe(gspca_dev->dev, 0),
+			      USB_REQ_SYNCH_FRAME,                /* request */
+			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      command, index, NULL, 0,
+			      SQ905C_CMD_TIMEOUT);
+	if (ret < 0) {
+		pr_err("%s: usb_control_msg failed (%d)\n", __func__, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int sq905c_read(struct gspca_dev *gspca_dev, u16 command, u16 index,
+		       int size)
+{
+	int ret;
+
+	ret = usb_control_msg(gspca_dev->dev,
+			      usb_rcvctrlpipe(gspca_dev->dev, 0),
+			      USB_REQ_SYNCH_FRAME,		/* request */
+			      USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      command, index, gspca_dev->usb_buf, size,
+			      SQ905C_CMD_TIMEOUT);
+	if (ret < 0) {
+		pr_err("%s: usb_control_msg failed (%d)\n", __func__, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+/*
+ * This function is called as a workqueue function and runs whenever the camera
+ * is streaming data. Because it is a workqueue function it is allowed to sleep
+ * so we can use synchronous USB calls. To avoid possible collisions with other
+ * threads attempting to use gspca_dev->usb_buf we take the usb_lock when
+ * performing USB operations using it. In practice we don't really need this
+ * as the camera doesn't provide any controls.
+ */
+static void sq905c_dostream(struct work_struct *work)
+{
+	struct sd *dev = container_of(work, struct sd, work_struct);
+	struct gspca_dev *gspca_dev = &dev->gspca_dev;
+	int bytes_left; /* bytes remaining in current frame. */
+	int data_len;   /* size to use for the next read. */
+	int act_len;
+	int packet_type;
+	int ret;
+	u8 *buffer;
+
+	buffer = kmalloc(SQ905C_MAX_TRANSFER, GFP_KERNEL);
+	if (!buffer) {
+		pr_err("Couldn't allocate USB buffer\n");
+		goto quit_stream;
+	}
+
+	while (gspca_dev->present && gspca_dev->streaming) {
+#ifdef CONFIG_PM
+		if (gspca_dev->frozen)
+			break;
+#endif
+		/* Request the header, which tells the size to download */
+		ret = usb_bulk_msg(gspca_dev->dev,
+				usb_rcvbulkpipe(gspca_dev->dev, 0x81),
+				buffer, FRAME_HEADER_LEN, &act_len,
+				SQ905C_DATA_TIMEOUT);
+		gspca_dbg(gspca_dev, D_STREAM,
+			  "Got %d bytes out of %d for header\n",
+			  act_len, FRAME_HEADER_LEN);
+		if (ret < 0 || act_len < FRAME_HEADER_LEN)
+			goto quit_stream;
+		/* size is read from 4 bytes starting 0x40, little endian */
+		bytes_left = buffer[0x40]|(buffer[0x41]<<8)|(buffer[0x42]<<16)
+					|(buffer[0x43]<<24);
+		gspca_dbg(gspca_dev, D_STREAM, "bytes_left = 0x%x\n",
+			  bytes_left);
+		/* We keep the header. It has other information, too. */
+		packet_type = FIRST_PACKET;
+		gspca_frame_add(gspca_dev, packet_type,
+				buffer, FRAME_HEADER_LEN);
+		while (bytes_left > 0 && gspca_dev->present) {
+			data_len = bytes_left > SQ905C_MAX_TRANSFER ?
+				SQ905C_MAX_TRANSFER : bytes_left;
+			ret = usb_bulk_msg(gspca_dev->dev,
+				usb_rcvbulkpipe(gspca_dev->dev, 0x81),
+				buffer, data_len, &act_len,
+				SQ905C_DATA_TIMEOUT);
+			if (ret < 0 || act_len < data_len)
+				goto quit_stream;
+			gspca_dbg(gspca_dev, D_STREAM,
+				  "Got %d bytes out of %d for frame\n",
+				  data_len, bytes_left);
+			bytes_left -= data_len;
+			if (bytes_left == 0)
+				packet_type = LAST_PACKET;
+			else
+				packet_type = INTER_PACKET;
+			gspca_frame_add(gspca_dev, packet_type,
+					buffer, data_len);
+		}
+	}
+quit_stream:
+	if (gspca_dev->present) {
+		mutex_lock(&gspca_dev->usb_lock);
+		sq905c_command(gspca_dev, SQ905C_CLEAR, 0);
+		mutex_unlock(&gspca_dev->usb_lock);
+	}
+	kfree(buffer);
+}
+
+/* This function is called at probe time just before sd_init */
+static int sd_config(struct gspca_dev *gspca_dev,
+		const struct usb_device_id *id)
+{
+	struct cam *cam = &gspca_dev->cam;
+	struct sd *dev = (struct sd *) gspca_dev;
+	int ret;
+
+	gspca_dbg(gspca_dev, D_PROBE,
+		  "SQ9050 camera detected (vid/pid 0x%04X:0x%04X)\n",
+		  id->idVendor, id->idProduct);
+
+	ret = sq905c_command(gspca_dev, SQ905C_GET_ID, 0);
+	if (ret < 0) {
+		gspca_err(gspca_dev, "Get version command failed\n");
+		return ret;
+	}
+
+	ret = sq905c_read(gspca_dev, 0xf5, 0, 20);
+	if (ret < 0) {
+		gspca_err(gspca_dev, "Reading version command failed\n");
+		return ret;
+	}
+	/* Note we leave out the usb id and the manufacturing date */
+	gspca_dbg(gspca_dev, D_PROBE,
+		  "SQ9050 ID string: %02x - %*ph\n",
+		  gspca_dev->usb_buf[3], 6, gspca_dev->usb_buf + 14);
+
+	cam->cam_mode = sq905c_mode;
+	cam->nmodes = 2;
+	if (gspca_dev->usb_buf[15] == 0)
+		cam->nmodes = 1;
+	/* We don't use the buffer gspca allocates so make it small. */
+	cam->bulk_size = 32;
+	cam->bulk = 1;
+	INIT_WORK(&dev->work_struct, sq905c_dostream);
+	return 0;
+}
+
+/* called on streamoff with alt==0 and on disconnect */
+/* the usb_lock is held at entry - restore on exit */
+static void sd_stop0(struct gspca_dev *gspca_dev)
+{
+	struct sd *dev = (struct sd *) gspca_dev;
+
+	/* wait for the work queue to terminate */
+	mutex_unlock(&gspca_dev->usb_lock);
+	/* This waits for sq905c_dostream to finish */
+	destroy_workqueue(dev->work_thread);
+	dev->work_thread = NULL;
+	mutex_lock(&gspca_dev->usb_lock);
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	/* connect to the camera and reset it. */
+	return sq905c_command(gspca_dev, SQ905C_CLEAR, 0);
+}
+
+/* Set up for getting frames. */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *dev = (struct sd *) gspca_dev;
+	int ret;
+
+	dev->cap_mode = gspca_dev->cam.cam_mode;
+	/* "Open the shutter" and set size, to start capture */
+	switch (gspca_dev->pixfmt.width) {
+	case 640:
+		gspca_dbg(gspca_dev, D_STREAM, "Start streaming at high resolution\n");
+		dev->cap_mode++;
+		ret = sq905c_command(gspca_dev, SQ905C_CAPTURE_HI,
+						SQ905C_CAPTURE_INDEX);
+		break;
+	default: /* 320 */
+		gspca_dbg(gspca_dev, D_STREAM, "Start streaming at medium resolution\n");
+		ret = sq905c_command(gspca_dev, SQ905C_CAPTURE_MED,
+						SQ905C_CAPTURE_INDEX);
+	}
+
+	if (ret < 0) {
+		gspca_err(gspca_dev, "Start streaming command failed\n");
+		return ret;
+	}
+	/* Start the workqueue function to do the streaming */
+	dev->work_thread = create_singlethread_workqueue(MODULE_NAME);
+	queue_work(dev->work_thread, &dev->work_struct);
+
+	return 0;
+}
+
+/* Table of supported USB devices */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x2770, 0x905c)},
+	{USB_DEVICE(0x2770, 0x9050)},
+	{USB_DEVICE(0x2770, 0x9051)},
+	{USB_DEVICE(0x2770, 0x9052)},
+	{USB_DEVICE(0x2770, 0x913d)},
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name   = MODULE_NAME,
+	.config = sd_config,
+	.init   = sd_init,
+	.start  = sd_start,
+	.stop0  = sd_stop0,
+};
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id,
+			&sd_desc,
+			sizeof(struct sd),
+			THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name       = MODULE_NAME,
+	.id_table   = device_table,
+	.probe      = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume  = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/sq930x.c b/drivers/media/usb/gspca/sq930x.c
new file mode 100644
index 0000000..d7cbcf2
--- /dev/null
+++ b/drivers/media/usb/gspca/sq930x.c
@@ -0,0 +1,1160 @@
+/*
+ * SQ930x subdriver
+ *
+ * Copyright (C) 2010 Jean-François Moine <http://moinejf.free.fr>
+ * Copyright (C) 2006 -2008 Gerard Klaver <gerard at gkall dot hobby dot nl>
+ * Copyright (C) 2007 Sam Revitch <samr7@cs.washington.edu>
+ *
+ * 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
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "sq930x"
+
+#include "gspca.h"
+
+MODULE_AUTHOR("Jean-Francois Moine <http://moinejf.free.fr>\n"
+		"Gerard Klaver <gerard at gkall dot hobby dot nl\n"
+		"Sam Revitch <samr7@cs.washington.edu>");
+MODULE_DESCRIPTION("GSPCA/SQ930x USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+/* Structure to hold all of our device specific stuff */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+
+	struct { /* exposure/gain control cluster */
+		struct v4l2_ctrl *exposure;
+		struct v4l2_ctrl *gain;
+	};
+
+	u8 do_ctrl;
+	u8 gpio[2];
+	u8 sensor;
+	u8 type;
+#define Generic 0
+#define Creative_live_motion 1
+};
+enum sensors {
+	SENSOR_ICX098BQ,
+	SENSOR_LZ24BP,
+	SENSOR_MI0360,
+	SENSOR_MT9V111,		/* = MI360SOC */
+	SENSOR_OV7660,
+	SENSOR_OV9630,
+};
+
+static struct v4l2_pix_format vga_mode[] = {
+	{320, 240, V4L2_PIX_FMT_SRGGB8, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+	{640, 480, V4L2_PIX_FMT_SRGGB8, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1},
+};
+
+/* sq930x registers */
+#define SQ930_CTRL_UCBUS_IO	0x0001
+#define SQ930_CTRL_I2C_IO	0x0002
+#define SQ930_CTRL_GPIO		0x0005
+#define SQ930_CTRL_CAP_START	0x0010
+#define SQ930_CTRL_CAP_STOP	0x0011
+#define SQ930_CTRL_SET_EXPOSURE 0x001d
+#define SQ930_CTRL_RESET	0x001e
+#define SQ930_CTRL_GET_DEV_INFO 0x001f
+
+/* gpio 1 (8..15) */
+#define SQ930_GPIO_DFL_I2C_SDA	0x0001
+#define SQ930_GPIO_DFL_I2C_SCL	0x0002
+#define SQ930_GPIO_RSTBAR	0x0004
+#define SQ930_GPIO_EXTRA1	0x0040
+#define SQ930_GPIO_EXTRA2	0x0080
+/* gpio 3 (24..31) */
+#define SQ930_GPIO_POWER	0x0200
+#define SQ930_GPIO_DFL_LED	0x1000
+
+struct ucbus_write_cmd {
+	u16	bw_addr;
+	u8	bw_data;
+};
+struct i2c_write_cmd {
+	u8	reg;
+	u16	val;
+};
+
+static const struct ucbus_write_cmd icx098bq_start_0[] = {
+	{0x0354, 0x00}, {0x03fa, 0x00}, {0xf800, 0x02}, {0xf801, 0xce},
+	{0xf802, 0xc1}, {0xf804, 0x00}, {0xf808, 0x00}, {0xf809, 0x0e},
+	{0xf80a, 0x01}, {0xf80b, 0xee}, {0xf807, 0x60}, {0xf80c, 0x02},
+	{0xf80d, 0xf0}, {0xf80e, 0x03}, {0xf80f, 0x0a}, {0xf81c, 0x02},
+	{0xf81d, 0xf0}, {0xf81e, 0x03}, {0xf81f, 0x0a}, {0xf83a, 0x00},
+	{0xf83b, 0x10}, {0xf83c, 0x00}, {0xf83d, 0x4e}, {0xf810, 0x04},
+	{0xf811, 0x00}, {0xf812, 0x02}, {0xf813, 0x10}, {0xf803, 0x00},
+	{0xf814, 0x01}, {0xf815, 0x18}, {0xf816, 0x00}, {0xf817, 0x48},
+	{0xf818, 0x00}, {0xf819, 0x25}, {0xf81a, 0x00}, {0xf81b, 0x3c},
+	{0xf82f, 0x03}, {0xf820, 0xff}, {0xf821, 0x0d}, {0xf822, 0xff},
+	{0xf823, 0x07}, {0xf824, 0xff}, {0xf825, 0x03}, {0xf826, 0xff},
+	{0xf827, 0x06}, {0xf828, 0xff}, {0xf829, 0x03}, {0xf82a, 0xff},
+	{0xf82b, 0x0c}, {0xf82c, 0xfd}, {0xf82d, 0x01}, {0xf82e, 0x00},
+	{0xf830, 0x00}, {0xf831, 0x47}, {0xf832, 0x00}, {0xf833, 0x00},
+	{0xf850, 0x00}, {0xf851, 0x00}, {0xf852, 0x00}, {0xf853, 0x24},
+	{0xf854, 0x00}, {0xf855, 0x18}, {0xf856, 0x00}, {0xf857, 0x3c},
+	{0xf858, 0x00}, {0xf859, 0x0c}, {0xf85a, 0x00}, {0xf85b, 0x30},
+	{0xf85c, 0x00}, {0xf85d, 0x0c}, {0xf85e, 0x00}, {0xf85f, 0x30},
+	{0xf860, 0x00}, {0xf861, 0x48}, {0xf862, 0x01}, {0xf863, 0xdc},
+	{0xf864, 0xff}, {0xf865, 0x98}, {0xf866, 0xff}, {0xf867, 0xc0},
+	{0xf868, 0xff}, {0xf869, 0x70}, {0xf86c, 0xff}, {0xf86d, 0x00},
+	{0xf86a, 0xff}, {0xf86b, 0x48}, {0xf86e, 0xff}, {0xf86f, 0x00},
+	{0xf870, 0x01}, {0xf871, 0xdb}, {0xf872, 0x01}, {0xf873, 0xfa},
+	{0xf874, 0x01}, {0xf875, 0xdb}, {0xf876, 0x01}, {0xf877, 0xfa},
+	{0xf878, 0x0f}, {0xf879, 0x0f}, {0xf87a, 0xff}, {0xf87b, 0xff},
+	{0xf800, 0x03}
+};
+static const struct ucbus_write_cmd icx098bq_start_1[] = {
+	{0xf5f0, 0x00}, {0xf5f1, 0xcd}, {0xf5f2, 0x80}, {0xf5f3, 0x80},
+	{0xf5f4, 0xc0},
+	{0xf5f0, 0x49}, {0xf5f1, 0xcd}, {0xf5f2, 0x80}, {0xf5f3, 0x80},
+	{0xf5f4, 0xc0},
+	{0xf5fa, 0x00}, {0xf5f6, 0x00}, {0xf5f7, 0x00}, {0xf5f8, 0x00},
+	{0xf5f9, 0x00}
+};
+
+static const struct ucbus_write_cmd icx098bq_start_2[] = {
+	{0xf800, 0x02}, {0xf807, 0xff}, {0xf805, 0x82}, {0xf806, 0x00},
+	{0xf807, 0x7f}, {0xf800, 0x03},
+	{0xf800, 0x02}, {0xf807, 0xff}, {0xf805, 0x40}, {0xf806, 0x00},
+	{0xf807, 0x7f}, {0xf800, 0x03},
+	{0xf800, 0x02}, {0xf807, 0xff}, {0xf805, 0xcf}, {0xf806, 0xd0},
+	{0xf807, 0x7f}, {0xf800, 0x03},
+	{0xf800, 0x02}, {0xf807, 0xff}, {0xf805, 0x00}, {0xf806, 0x00},
+	{0xf807, 0x7f}, {0xf800, 0x03}
+};
+
+static const struct ucbus_write_cmd lz24bp_start_0[] = {
+	{0x0354, 0x00}, {0x03fa, 0x00}, {0xf800, 0x02}, {0xf801, 0xbe},
+	{0xf802, 0xc6}, {0xf804, 0x00}, {0xf808, 0x00}, {0xf809, 0x06},
+	{0xf80a, 0x01}, {0xf80b, 0xfe}, {0xf807, 0x84}, {0xf80c, 0x02},
+	{0xf80d, 0xf7}, {0xf80e, 0x03}, {0xf80f, 0x0b}, {0xf81c, 0x00},
+	{0xf81d, 0x49}, {0xf81e, 0x03}, {0xf81f, 0x0b}, {0xf83a, 0x00},
+	{0xf83b, 0x01}, {0xf83c, 0x00}, {0xf83d, 0x6b}, {0xf810, 0x03},
+	{0xf811, 0x10}, {0xf812, 0x02}, {0xf813, 0x6f}, {0xf803, 0x00},
+	{0xf814, 0x00}, {0xf815, 0x44}, {0xf816, 0x00}, {0xf817, 0x48},
+	{0xf818, 0x00}, {0xf819, 0x25}, {0xf81a, 0x00}, {0xf81b, 0x3c},
+	{0xf82f, 0x03}, {0xf820, 0xff}, {0xf821, 0x0d}, {0xf822, 0xff},
+	{0xf823, 0x07}, {0xf824, 0xfd}, {0xf825, 0x07}, {0xf826, 0xf0},
+	{0xf827, 0x0c}, {0xf828, 0xff}, {0xf829, 0x03}, {0xf82a, 0xff},
+	{0xf82b, 0x0c}, {0xf82c, 0xfc}, {0xf82d, 0x01}, {0xf82e, 0x00},
+	{0xf830, 0x00}, {0xf831, 0x47}, {0xf832, 0x00}, {0xf833, 0x00},
+	{0xf850, 0x00}, {0xf851, 0x00}, {0xf852, 0x00}, {0xf853, 0x24},
+	{0xf854, 0x00}, {0xf855, 0x0c}, {0xf856, 0x00}, {0xf857, 0x30},
+	{0xf858, 0x00}, {0xf859, 0x18}, {0xf85a, 0x00}, {0xf85b, 0x3c},
+	{0xf85c, 0x00}, {0xf85d, 0x18}, {0xf85e, 0x00}, {0xf85f, 0x3c},
+	{0xf860, 0xff}, {0xf861, 0x37}, {0xf862, 0xff}, {0xf863, 0x1d},
+	{0xf864, 0xff}, {0xf865, 0x98}, {0xf866, 0xff}, {0xf867, 0xc0},
+	{0xf868, 0x00}, {0xf869, 0x37}, {0xf86c, 0x02}, {0xf86d, 0x1d},
+	{0xf86a, 0x00}, {0xf86b, 0x37}, {0xf86e, 0x02}, {0xf86f, 0x1d},
+	{0xf870, 0x01}, {0xf871, 0xc6}, {0xf872, 0x02}, {0xf873, 0x04},
+	{0xf874, 0x01}, {0xf875, 0xc6}, {0xf876, 0x02}, {0xf877, 0x04},
+	{0xf878, 0x0f}, {0xf879, 0x0f}, {0xf87a, 0xff}, {0xf87b, 0xff},
+	{0xf800, 0x03}
+};
+static const struct ucbus_write_cmd lz24bp_start_1_gen[] = {
+	{0xf5f0, 0x00}, {0xf5f1, 0xff}, {0xf5f2, 0x80}, {0xf5f3, 0x80},
+	{0xf5f4, 0xb3},
+	{0xf5f0, 0x40}, {0xf5f1, 0xff}, {0xf5f2, 0x80}, {0xf5f3, 0x80},
+	{0xf5f4, 0xb3},
+	{0xf5fa, 0x00}, {0xf5f6, 0x00}, {0xf5f7, 0x00}, {0xf5f8, 0x00},
+	{0xf5f9, 0x00}
+};
+
+static const struct ucbus_write_cmd lz24bp_start_1_clm[] = {
+	{0xf5f0, 0x00}, {0xf5f1, 0xff}, {0xf5f2, 0x88}, {0xf5f3, 0x88},
+	{0xf5f4, 0xc0},
+	{0xf5f0, 0x40}, {0xf5f1, 0xff}, {0xf5f2, 0x88}, {0xf5f3, 0x88},
+	{0xf5f4, 0xc0},
+	{0xf5fa, 0x00}, {0xf5f6, 0x00}, {0xf5f7, 0x00}, {0xf5f8, 0x00},
+	{0xf5f9, 0x00}
+};
+
+static const struct ucbus_write_cmd lz24bp_start_2[] = {
+	{0xf800, 0x02}, {0xf807, 0xff}, {0xf805, 0x80}, {0xf806, 0x00},
+	{0xf807, 0x7f}, {0xf800, 0x03},
+	{0xf800, 0x02}, {0xf807, 0xff}, {0xf805, 0x4e}, {0xf806, 0x00},
+	{0xf807, 0x7f}, {0xf800, 0x03},
+	{0xf800, 0x02}, {0xf807, 0xff}, {0xf805, 0xc0}, {0xf806, 0x48},
+	{0xf807, 0x7f}, {0xf800, 0x03},
+	{0xf800, 0x02}, {0xf807, 0xff}, {0xf805, 0x00}, {0xf806, 0x00},
+	{0xf807, 0x7f}, {0xf800, 0x03}
+};
+
+static const struct ucbus_write_cmd mi0360_start_0[] = {
+	{0x0354, 0x00}, {0x03fa, 0x00}, {0xf332, 0xcc}, {0xf333, 0xcc},
+	{0xf334, 0xcc}, {0xf335, 0xcc}, {0xf33f, 0x00}
+};
+static const struct i2c_write_cmd mi0360_init_23[] = {
+	{0x30, 0x0040},		/* reserved - def 0x0005 */
+	{0x31, 0x0000},		/* reserved - def 0x002a */
+	{0x34, 0x0100},		/* reserved - def 0x0100 */
+	{0x3d, 0x068f},		/* reserved - def 0x068f */
+};
+static const struct i2c_write_cmd mi0360_init_24[] = {
+	{0x03, 0x01e5},		/* window height */
+	{0x04, 0x0285},		/* window width */
+};
+static const struct i2c_write_cmd mi0360_init_25[] = {
+	{0x35, 0x0020},		/* global gain */
+	{0x2b, 0x0020},		/* green1 gain */
+	{0x2c, 0x002a},		/* blue gain */
+	{0x2d, 0x0028},		/* red gain */
+	{0x2e, 0x0020},		/* green2 gain */
+};
+static const struct ucbus_write_cmd mi0360_start_1[] = {
+	{0xf5f0, 0x11}, {0xf5f1, 0x99}, {0xf5f2, 0x80}, {0xf5f3, 0x80},
+	{0xf5f4, 0xa6},
+	{0xf5f0, 0x51}, {0xf5f1, 0x99}, {0xf5f2, 0x80}, {0xf5f3, 0x80},
+	{0xf5f4, 0xa6},
+	{0xf5fa, 0x00}, {0xf5f6, 0x00}, {0xf5f7, 0x00}, {0xf5f8, 0x00},
+	{0xf5f9, 0x00}
+};
+static const struct i2c_write_cmd mi0360_start_2[] = {
+	{0x62, 0x041d},		/* reserved - def 0x0418 */
+};
+static const struct i2c_write_cmd mi0360_start_3[] = {
+	{0x05, 0x007b},		/* horiz blanking */
+};
+static const struct i2c_write_cmd mi0360_start_4[] = {
+	{0x05, 0x03f5},		/* horiz blanking */
+};
+
+static const struct i2c_write_cmd mt9v111_init_0[] = {
+	{0x01, 0x0001},		/* select IFP/SOC registers */
+	{0x06, 0x300c},		/* operating mode control */
+	{0x08, 0xcc00},		/* output format control (RGB) */
+	{0x01, 0x0004},		/* select sensor core registers */
+};
+static const struct i2c_write_cmd mt9v111_init_1[] = {
+	{0x03, 0x01e5},		/* window height */
+	{0x04, 0x0285},		/* window width */
+};
+static const struct i2c_write_cmd mt9v111_init_2[] = {
+	{0x30, 0x7800},
+	{0x31, 0x0000},
+	{0x07, 0x3002},		/* output control */
+	{0x35, 0x0020},		/* global gain */
+	{0x2b, 0x0020},		/* green1 gain */
+	{0x2c, 0x0020},		/* blue gain */
+	{0x2d, 0x0020},		/* red gain */
+	{0x2e, 0x0020},		/* green2 gain */
+};
+static const struct ucbus_write_cmd mt9v111_start_1[] = {
+	{0xf5f0, 0x11}, {0xf5f1, 0x96}, {0xf5f2, 0x80}, {0xf5f3, 0x80},
+	{0xf5f4, 0xaa},
+	{0xf5f0, 0x51}, {0xf5f1, 0x96}, {0xf5f2, 0x80}, {0xf5f3, 0x80},
+	{0xf5f4, 0xaa},
+	{0xf5fa, 0x00}, {0xf5f6, 0x0a}, {0xf5f7, 0x0a}, {0xf5f8, 0x0a},
+	{0xf5f9, 0x0a}
+};
+static const struct i2c_write_cmd mt9v111_init_3[] = {
+	{0x62, 0x0405},
+};
+static const struct i2c_write_cmd mt9v111_init_4[] = {
+/*	{0x05, 0x00ce}, */
+	{0x05, 0x005d},		/* horizontal blanking */
+};
+
+static const struct ucbus_write_cmd ov7660_start_0[] = {
+	{0x0354, 0x00}, {0x03fa, 0x00}, {0xf332, 0x00}, {0xf333, 0xc0},
+	{0xf334, 0x39}, {0xf335, 0xe7}, {0xf33f, 0x03}
+};
+
+static const struct ucbus_write_cmd ov9630_start_0[] = {
+	{0x0354, 0x00}, {0x03fa, 0x00}, {0xf332, 0x00}, {0xf333, 0x00},
+	{0xf334, 0x3e}, {0xf335, 0xf8}, {0xf33f, 0x03}
+};
+
+/* start parameters indexed by [sensor][mode] */
+static const struct cap_s {
+	u8	cc_sizeid;
+	u8	cc_bytes[32];
+} capconfig[4][2] = {
+	[SENSOR_ICX098BQ] = {
+		{2,				/* Bayer 320x240 */
+		  {0x05, 0x1f, 0x20, 0x0e, 0x00, 0x9f, 0x02, 0xee,
+		   0x01, 0x01, 0x00, 0x08, 0x18, 0x12, 0x78, 0xc8,
+		   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0,
+		   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} },
+		{4,				/* Bayer 640x480 */
+		  {0x01, 0x1f, 0x20, 0x0e, 0x00, 0x9f, 0x02, 0xee,
+		   0x01, 0x02, 0x00, 0x08, 0x18, 0x12, 0x78, 0xc8,
+		   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} },
+	},
+	[SENSOR_LZ24BP] = {
+		{2,				/* Bayer 320x240 */
+		  {0x05, 0x22, 0x20, 0x0e, 0x00, 0xa2, 0x02, 0xee,
+		   0x01, 0x01, 0x00, 0x08, 0x18, 0x12, 0x78, 0xc8,
+		   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} },
+		{4,				/* Bayer 640x480 */
+		  {0x01, 0x22, 0x20, 0x0e, 0x00, 0xa2, 0x02, 0xee,
+		   0x01, 0x02, 0x00, 0x08, 0x18, 0x12, 0x78, 0xc8,
+		   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} },
+	},
+	[SENSOR_MI0360] = {
+		{2,				/* Bayer 320x240 */
+		  {0x05, 0x02, 0x20, 0x01, 0x20, 0x82, 0x02, 0xe1,
+		   0x01, 0x01, 0x00, 0x08, 0x18, 0x12, 0x78, 0xc8,
+		   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} },
+		{4,				/* Bayer 640x480 */
+		  {0x01, 0x02, 0x20, 0x01, 0x20, 0x82, 0x02, 0xe1,
+		   0x01, 0x02, 0x00, 0x08, 0x18, 0x12, 0x78, 0xc8,
+		   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} },
+	},
+	[SENSOR_MT9V111] = {
+		{2,				/* Bayer 320x240 */
+		  {0x05, 0x02, 0x20, 0x01, 0x20, 0x82, 0x02, 0xe1,
+		   0x01, 0x01, 0x00, 0x08, 0x18, 0x12, 0x78, 0xc8,
+		   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} },
+		{4,				/* Bayer 640x480 */
+		  {0x01, 0x02, 0x20, 0x01, 0x20, 0x82, 0x02, 0xe1,
+		   0x01, 0x02, 0x00, 0x08, 0x18, 0x12, 0x78, 0xc8,
+		   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} },
+	},
+};
+
+struct sensor_s {
+	const char *name;
+	u8 i2c_addr;
+	u8 i2c_dum;
+	u8 gpio[5];
+	u8 cmd_len;
+	const struct ucbus_write_cmd *cmd;
+};
+
+static const struct sensor_s sensor_tb[] = {
+	[SENSOR_ICX098BQ] = {
+		"icx098bp",
+		0x00, 0x00,
+		{0,
+		 SQ930_GPIO_DFL_I2C_SDA | SQ930_GPIO_DFL_I2C_SCL,
+		 SQ930_GPIO_DFL_I2C_SDA,
+		 0,
+		 SQ930_GPIO_RSTBAR
+		},
+		8, icx098bq_start_0
+	    },
+	[SENSOR_LZ24BP] = {
+		"lz24bp",
+		0x00, 0x00,
+		{0,
+		 SQ930_GPIO_DFL_I2C_SDA | SQ930_GPIO_DFL_I2C_SCL,
+		 SQ930_GPIO_DFL_I2C_SDA,
+		 0,
+		 SQ930_GPIO_RSTBAR
+		},
+		8, lz24bp_start_0
+	    },
+	[SENSOR_MI0360] = {
+		"mi0360",
+		0x5d, 0x80,
+		{SQ930_GPIO_RSTBAR,
+		 SQ930_GPIO_DFL_I2C_SDA | SQ930_GPIO_DFL_I2C_SCL,
+		 SQ930_GPIO_DFL_I2C_SDA,
+		 0,
+		 0
+		},
+		7, mi0360_start_0
+	    },
+	[SENSOR_MT9V111] = {
+		"mt9v111",
+		0x5c, 0x7f,
+		{SQ930_GPIO_RSTBAR,
+		 SQ930_GPIO_DFL_I2C_SDA | SQ930_GPIO_DFL_I2C_SCL,
+		 SQ930_GPIO_DFL_I2C_SDA,
+		 0,
+		 0
+		},
+		7, mi0360_start_0
+	    },
+	[SENSOR_OV7660] = {
+		"ov7660",
+		0x21, 0x00,
+		{0,
+		 SQ930_GPIO_DFL_I2C_SDA | SQ930_GPIO_DFL_I2C_SCL,
+		 SQ930_GPIO_DFL_I2C_SDA,
+		 0,
+		 SQ930_GPIO_RSTBAR
+		},
+		7, ov7660_start_0
+	    },
+	[SENSOR_OV9630] = {
+		"ov9630",
+		0x30, 0x00,
+		{0,
+		 SQ930_GPIO_DFL_I2C_SDA | SQ930_GPIO_DFL_I2C_SCL,
+		 SQ930_GPIO_DFL_I2C_SDA,
+		 0,
+		 SQ930_GPIO_RSTBAR
+		},
+		7, ov9630_start_0
+	    },
+};
+
+static void reg_r(struct gspca_dev *gspca_dev,
+		u16 value, int len)
+{
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	ret = usb_control_msg(gspca_dev->dev,
+			usb_rcvctrlpipe(gspca_dev->dev, 0),
+			0x0c,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			value, 0, gspca_dev->usb_buf, len,
+			500);
+	if (ret < 0) {
+		pr_err("reg_r %04x failed %d\n", value, ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+static void reg_w(struct gspca_dev *gspca_dev, u16 value, u16 index)
+{
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	gspca_dbg(gspca_dev, D_USBO, "reg_w v: %04x i: %04x\n", value, index);
+	ret = usb_control_msg(gspca_dev->dev,
+			usb_sndctrlpipe(gspca_dev->dev, 0),
+			0x0c,			/* request */
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			value, index, NULL, 0,
+			500);
+	msleep(30);
+	if (ret < 0) {
+		pr_err("reg_w %04x %04x failed %d\n", value, index, ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+static void reg_wb(struct gspca_dev *gspca_dev, u16 value, u16 index,
+		const u8 *data, int len)
+{
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	gspca_dbg(gspca_dev, D_USBO, "reg_wb v: %04x i: %04x %02x...%02x\n",
+		  value, index, *data, data[len - 1]);
+	memcpy(gspca_dev->usb_buf, data, len);
+	ret = usb_control_msg(gspca_dev->dev,
+			usb_sndctrlpipe(gspca_dev->dev, 0),
+			0x0c,			/* request */
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			value, index, gspca_dev->usb_buf, len,
+			1000);
+	msleep(30);
+	if (ret < 0) {
+		pr_err("reg_wb %04x %04x failed %d\n", value, index, ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+static void i2c_write(struct sd *sd,
+			const struct i2c_write_cmd *cmd,
+			int ncmds)
+{
+	struct gspca_dev *gspca_dev = &sd->gspca_dev;
+	const struct sensor_s *sensor;
+	u16 val, idx;
+	u8 *buf;
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+
+	sensor = &sensor_tb[sd->sensor];
+
+	val = (sensor->i2c_addr << 8) | SQ930_CTRL_I2C_IO;
+	idx = (cmd->val & 0xff00) | cmd->reg;
+
+	buf = gspca_dev->usb_buf;
+	*buf++ = sensor->i2c_dum;
+	*buf++ = cmd->val;
+
+	while (--ncmds > 0) {
+		cmd++;
+		*buf++ = cmd->reg;
+		*buf++ = cmd->val >> 8;
+		*buf++ = sensor->i2c_dum;
+		*buf++ = cmd->val;
+	}
+
+	gspca_dbg(gspca_dev, D_USBO, "i2c_w v: %04x i: %04x %02x...%02x\n",
+		  val, idx, gspca_dev->usb_buf[0], buf[-1]);
+	ret = usb_control_msg(gspca_dev->dev,
+			usb_sndctrlpipe(gspca_dev->dev, 0),
+			0x0c,			/* request */
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			val, idx,
+			gspca_dev->usb_buf, buf - gspca_dev->usb_buf,
+			500);
+	if (ret < 0) {
+		pr_err("i2c_write failed %d\n", ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+static void ucbus_write(struct gspca_dev *gspca_dev,
+			const struct ucbus_write_cmd *cmd,
+			int ncmds,
+			int batchsize)
+{
+	u8 *buf;
+	u16 val, idx;
+	int len, ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+
+	if ((batchsize - 1) * 3 > USB_BUF_SZ) {
+		gspca_err(gspca_dev, "Bug: usb_buf overflow\n");
+		gspca_dev->usb_err = -ENOMEM;
+		return;
+	}
+
+	for (;;) {
+		len = ncmds;
+		if (len > batchsize)
+			len = batchsize;
+		ncmds -= len;
+
+		val = (cmd->bw_addr << 8) | SQ930_CTRL_UCBUS_IO;
+		idx = (cmd->bw_data << 8) | (cmd->bw_addr >> 8);
+
+		buf = gspca_dev->usb_buf;
+		while (--len > 0) {
+			cmd++;
+			*buf++ = cmd->bw_addr;
+			*buf++ = cmd->bw_addr >> 8;
+			*buf++ = cmd->bw_data;
+		}
+		if (buf != gspca_dev->usb_buf)
+			gspca_dbg(gspca_dev, D_USBO, "ucbus v: %04x i: %04x %02x...%02x\n",
+				  val, idx,
+				  gspca_dev->usb_buf[0], buf[-1]);
+		else
+			gspca_dbg(gspca_dev, D_USBO, "ucbus v: %04x i: %04x\n",
+				  val, idx);
+		ret = usb_control_msg(gspca_dev->dev,
+				usb_sndctrlpipe(gspca_dev->dev, 0),
+				0x0c,			/* request */
+			   USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+				val, idx,
+				gspca_dev->usb_buf, buf - gspca_dev->usb_buf,
+				500);
+		if (ret < 0) {
+			pr_err("ucbus_write failed %d\n", ret);
+			gspca_dev->usb_err = ret;
+			return;
+		}
+		msleep(30);
+		if (ncmds <= 0)
+			break;
+		cmd++;
+	}
+}
+
+static void gpio_set(struct sd *sd, u16 val, u16 mask)
+{
+	struct gspca_dev *gspca_dev = &sd->gspca_dev;
+
+	if (mask & 0x00ff) {
+		sd->gpio[0] &= ~mask;
+		sd->gpio[0] |= val;
+		reg_w(gspca_dev, 0x0100 | SQ930_CTRL_GPIO,
+			~sd->gpio[0] << 8);
+	}
+	mask >>= 8;
+	val >>= 8;
+	if (mask) {
+		sd->gpio[1] &= ~mask;
+		sd->gpio[1] |= val;
+		reg_w(gspca_dev, 0x0300 | SQ930_CTRL_GPIO,
+			~sd->gpio[1] << 8);
+	}
+}
+
+static void gpio_init(struct sd *sd,
+			const u8 *gpio)
+{
+	gpio_set(sd, *gpio++, 0x000f);
+	gpio_set(sd, *gpio++, 0x000f);
+	gpio_set(sd, *gpio++, 0x000f);
+	gpio_set(sd, *gpio++, 0x000f);
+	gpio_set(sd, *gpio, 0x000f);
+}
+
+static void bridge_init(struct sd *sd)
+{
+	static const struct ucbus_write_cmd clkfreq_cmd = {
+				0xf031, 0	/* SQ930_CLKFREQ_60MHZ */
+	};
+
+	ucbus_write(&sd->gspca_dev, &clkfreq_cmd, 1, 1);
+
+	gpio_set(sd, SQ930_GPIO_POWER, 0xff00);
+}
+
+static void cmos_probe(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int i;
+	const struct sensor_s *sensor;
+	static const u8 probe_order[] = {
+/*		SENSOR_LZ24BP,		(tested as ccd) */
+		SENSOR_OV9630,
+		SENSOR_MI0360,
+		SENSOR_OV7660,
+		SENSOR_MT9V111,
+	};
+
+	for (i = 0; i < ARRAY_SIZE(probe_order); i++) {
+		sensor = &sensor_tb[probe_order[i]];
+		ucbus_write(&sd->gspca_dev, sensor->cmd, sensor->cmd_len, 8);
+		gpio_init(sd, sensor->gpio);
+		msleep(100);
+		reg_r(gspca_dev, (sensor->i2c_addr << 8) | 0x001c, 1);
+		msleep(100);
+		if (gspca_dev->usb_buf[0] != 0)
+			break;
+	}
+	if (i >= ARRAY_SIZE(probe_order)) {
+		pr_err("Unknown sensor\n");
+		gspca_dev->usb_err = -EINVAL;
+		return;
+	}
+	sd->sensor = probe_order[i];
+	switch (sd->sensor) {
+	case SENSOR_OV7660:
+	case SENSOR_OV9630:
+		pr_err("Sensor %s not yet treated\n",
+		       sensor_tb[sd->sensor].name);
+		gspca_dev->usb_err = -EINVAL;
+		break;
+	}
+}
+
+static void mt9v111_init(struct gspca_dev *gspca_dev)
+{
+	int i, nwait;
+	static const u8 cmd_001b[] = {
+		0x00, 0x3b, 0xf6, 0x01, 0x03, 0x02, 0x00, 0x00,
+		0x00, 0x00, 0x00
+	};
+	static const u8 cmd_011b[][7] = {
+		{0x10, 0x01, 0x66, 0x08, 0x00, 0x00, 0x00},
+		{0x01, 0x00, 0x1a, 0x04, 0x00, 0x00, 0x00},
+		{0x20, 0x00, 0x10, 0x04, 0x00, 0x00, 0x00},
+		{0x02, 0x01, 0xae, 0x01, 0x00, 0x00, 0x00},
+	};
+
+	reg_wb(gspca_dev, 0x001b, 0x0000, cmd_001b, sizeof cmd_001b);
+	for (i = 0; i < ARRAY_SIZE(cmd_011b); i++) {
+		reg_wb(gspca_dev, 0x001b, 0x0000, cmd_011b[i],
+				ARRAY_SIZE(cmd_011b[0]));
+		msleep(400);
+		nwait = 20;
+		for (;;) {
+			reg_r(gspca_dev, 0x031b, 1);
+			if (gspca_dev->usb_buf[0] == 0
+			 || gspca_dev->usb_err != 0)
+				break;
+			if (--nwait < 0) {
+				gspca_dbg(gspca_dev, D_PROBE, "mt9v111_init timeout\n");
+				gspca_dev->usb_err = -ETIME;
+				return;
+			}
+			msleep(50);
+		}
+	}
+}
+
+static void global_init(struct sd *sd, int first_time)
+{
+	switch (sd->sensor) {
+	case SENSOR_ICX098BQ:
+		if (first_time)
+			ucbus_write(&sd->gspca_dev,
+					icx098bq_start_0,
+					8, 8);
+		gpio_init(sd, sensor_tb[sd->sensor].gpio);
+		break;
+	case SENSOR_LZ24BP:
+		if (sd->type != Creative_live_motion)
+			gpio_set(sd, SQ930_GPIO_EXTRA1, 0x00ff);
+		else
+			gpio_set(sd, 0, 0x00ff);
+		msleep(50);
+		if (first_time)
+			ucbus_write(&sd->gspca_dev,
+					lz24bp_start_0,
+					8, 8);
+		gpio_init(sd, sensor_tb[sd->sensor].gpio);
+		break;
+	case SENSOR_MI0360:
+		if (first_time)
+			ucbus_write(&sd->gspca_dev,
+					mi0360_start_0,
+					ARRAY_SIZE(mi0360_start_0),
+					8);
+		gpio_init(sd, sensor_tb[sd->sensor].gpio);
+		gpio_set(sd, SQ930_GPIO_EXTRA2, SQ930_GPIO_EXTRA2);
+		break;
+	default:
+/*	case SENSOR_MT9V111: */
+		if (first_time)
+			mt9v111_init(&sd->gspca_dev);
+		else
+			gpio_init(sd, sensor_tb[sd->sensor].gpio);
+		break;
+	}
+}
+
+static void lz24bp_ppl(struct sd *sd, u16 ppl)
+{
+	struct ucbus_write_cmd cmds[2] = {
+		{0xf810, ppl >> 8},
+		{0xf811, ppl}
+	};
+
+	ucbus_write(&sd->gspca_dev, cmds, ARRAY_SIZE(cmds), 2);
+}
+
+static void setexposure(struct gspca_dev *gspca_dev, s32 expo, s32 gain)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int i, integclks, intstartclk, frameclks, min_frclk;
+	const struct sensor_s *sensor;
+	u16 cmd;
+	u8 buf[15];
+
+	integclks = expo;
+	i = 0;
+	cmd = SQ930_CTRL_SET_EXPOSURE;
+
+	switch (sd->sensor) {
+	case SENSOR_ICX098BQ:			/* ccd */
+	case SENSOR_LZ24BP:
+		min_frclk = sd->sensor == SENSOR_ICX098BQ ? 0x210 : 0x26f;
+		if (integclks >= min_frclk) {
+			intstartclk = 0;
+			frameclks = integclks;
+		} else {
+			intstartclk = min_frclk - integclks;
+			frameclks = min_frclk;
+		}
+		buf[i++] = intstartclk >> 8;
+		buf[i++] = intstartclk;
+		buf[i++] = frameclks >> 8;
+		buf[i++] = frameclks;
+		buf[i++] = gain;
+		break;
+	default:				/* cmos */
+/*	case SENSOR_MI0360: */
+/*	case SENSOR_MT9V111: */
+		cmd |= 0x0100;
+		sensor = &sensor_tb[sd->sensor];
+		buf[i++] = sensor->i2c_addr;	/* i2c_slave_addr */
+		buf[i++] = 0x08;	/* 2 * ni2c */
+		buf[i++] = 0x09;	/* reg = shutter width */
+		buf[i++] = integclks >> 8; /* val H */
+		buf[i++] = sensor->i2c_dum;
+		buf[i++] = integclks;	/* val L */
+		buf[i++] = 0x35;	/* reg = global gain */
+		buf[i++] = 0x00;	/* val H */
+		buf[i++] = sensor->i2c_dum;
+		buf[i++] = 0x80 + gain / 2; /* val L */
+		buf[i++] = 0x00;
+		buf[i++] = 0x00;
+		buf[i++] = 0x00;
+		buf[i++] = 0x00;
+		buf[i++] = 0x83;
+		break;
+	}
+	reg_wb(gspca_dev, cmd, 0, buf, i);
+}
+
+/* This function is called at probe time just before sd_init */
+static int sd_config(struct gspca_dev *gspca_dev,
+		const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam = &gspca_dev->cam;
+
+	sd->sensor = id->driver_info >> 8;
+	sd->type = id->driver_info;
+
+	cam->cam_mode = vga_mode;
+	cam->nmodes = ARRAY_SIZE(vga_mode);
+
+	cam->bulk = 1;
+
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	sd->gpio[0] = sd->gpio[1] = 0xff;	/* force gpio rewrite */
+
+/*fixme: is this needed for icx098bp and mi0360?
+	if (sd->sensor != SENSOR_LZ24BP)
+		reg_w(gspca_dev, SQ930_CTRL_RESET, 0x0000);
+ */
+
+	reg_r(gspca_dev, SQ930_CTRL_GET_DEV_INFO, 8);
+	if (gspca_dev->usb_err < 0)
+		return gspca_dev->usb_err;
+
+/* it returns:
+ * 03 00 12 93 0b f6 c9 00	live! ultra
+ * 03 00 07 93 0b f6 ca 00	live! ultra for notebook
+ * 03 00 12 93 0b fe c8 00	Trust WB-3500T
+ * 02 00 06 93 0b fe c8 00	Joy-IT 318S
+ * 03 00 12 93 0b f6 cf 00	icam tracer - sensor icx098bq
+ * 02 00 12 93 0b fe cf 00	ProQ Motion Webcam
+ *
+ * byte
+ * 0: 02 = usb 1.0 (12Mbit) / 03 = usb2.0 (480Mbit)
+ * 1: 00
+ * 2: 06 / 07 / 12 = mode webcam? firmware??
+ * 3: 93 chip = 930b (930b or 930c)
+ * 4: 0b
+ * 5: f6 = cdd (icx098bq, lz24bp) / fe or de = cmos (i2c) (other sensors)
+ * 6: c8 / c9 / ca / cf = mode webcam?, sensor? webcam?
+ * 7: 00
+ */
+	gspca_dbg(gspca_dev, D_PROBE, "info: %*ph\n", 8, gspca_dev->usb_buf);
+
+	bridge_init(sd);
+
+	if (sd->sensor == SENSOR_MI0360) {
+
+		/* no sensor probe for icam tracer */
+		if (gspca_dev->usb_buf[5] == 0xf6)	/* if ccd */
+			sd->sensor = SENSOR_ICX098BQ;
+		else
+			cmos_probe(gspca_dev);
+	}
+	if (gspca_dev->usb_err >= 0) {
+		gspca_dbg(gspca_dev, D_PROBE, "Sensor %s\n",
+			  sensor_tb[sd->sensor].name);
+		global_init(sd, 1);
+	}
+	return gspca_dev->usb_err;
+}
+
+/* send the start/stop commands to the webcam */
+static void send_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	const struct cap_s *cap;
+	int mode;
+
+	mode = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].priv;
+	cap = &capconfig[sd->sensor][mode];
+	reg_wb(gspca_dev, 0x0900 | SQ930_CTRL_CAP_START,
+			0x0a00 | cap->cc_sizeid,
+			cap->cc_bytes, 32);
+}
+
+static void send_stop(struct gspca_dev *gspca_dev)
+{
+	reg_w(gspca_dev, SQ930_CTRL_CAP_STOP, 0);
+}
+
+/* function called at start time before URB creation */
+static int sd_isoc_init(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	gspca_dev->cam.bulk_nurbs = 1;	/* there must be one URB only */
+	sd->do_ctrl = 0;
+	gspca_dev->cam.bulk_size = gspca_dev->pixfmt.width *
+			gspca_dev->pixfmt.height + 8;
+	return 0;
+}
+
+/* start the capture */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int mode;
+
+	bridge_init(sd);
+	global_init(sd, 0);
+	msleep(100);
+
+	switch (sd->sensor) {
+	case SENSOR_ICX098BQ:
+		ucbus_write(gspca_dev, icx098bq_start_0,
+				ARRAY_SIZE(icx098bq_start_0),
+				8);
+		ucbus_write(gspca_dev, icx098bq_start_1,
+				ARRAY_SIZE(icx098bq_start_1),
+				5);
+		ucbus_write(gspca_dev, icx098bq_start_2,
+				ARRAY_SIZE(icx098bq_start_2),
+				6);
+		msleep(50);
+
+		/* 1st start */
+		send_start(gspca_dev);
+		gpio_set(sd, SQ930_GPIO_EXTRA2 | SQ930_GPIO_RSTBAR, 0x00ff);
+		msleep(70);
+		reg_w(gspca_dev, SQ930_CTRL_CAP_STOP, 0x0000);
+		gpio_set(sd, 0x7f, 0x00ff);
+
+		/* 2nd start */
+		send_start(gspca_dev);
+		gpio_set(sd, SQ930_GPIO_EXTRA2 | SQ930_GPIO_RSTBAR, 0x00ff);
+		goto out;
+	case SENSOR_LZ24BP:
+		ucbus_write(gspca_dev, lz24bp_start_0,
+				ARRAY_SIZE(lz24bp_start_0),
+				8);
+		if (sd->type != Creative_live_motion)
+			ucbus_write(gspca_dev, lz24bp_start_1_gen,
+					ARRAY_SIZE(lz24bp_start_1_gen),
+					5);
+		else
+			ucbus_write(gspca_dev, lz24bp_start_1_clm,
+					ARRAY_SIZE(lz24bp_start_1_clm),
+					5);
+		ucbus_write(gspca_dev, lz24bp_start_2,
+				ARRAY_SIZE(lz24bp_start_2),
+				6);
+		mode = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].priv;
+		lz24bp_ppl(sd, mode == 1 ? 0x0564 : 0x0310);
+		msleep(10);
+		break;
+	case SENSOR_MI0360:
+		ucbus_write(gspca_dev, mi0360_start_0,
+				ARRAY_SIZE(mi0360_start_0),
+				8);
+		i2c_write(sd, mi0360_init_23,
+				ARRAY_SIZE(mi0360_init_23));
+		i2c_write(sd, mi0360_init_24,
+				ARRAY_SIZE(mi0360_init_24));
+		i2c_write(sd, mi0360_init_25,
+				ARRAY_SIZE(mi0360_init_25));
+		ucbus_write(gspca_dev, mi0360_start_1,
+				ARRAY_SIZE(mi0360_start_1),
+				5);
+		i2c_write(sd, mi0360_start_2,
+				ARRAY_SIZE(mi0360_start_2));
+		i2c_write(sd, mi0360_start_3,
+				ARRAY_SIZE(mi0360_start_3));
+
+		/* 1st start */
+		send_start(gspca_dev);
+		msleep(60);
+		send_stop(gspca_dev);
+
+		i2c_write(sd,
+			mi0360_start_4, ARRAY_SIZE(mi0360_start_4));
+		break;
+	default:
+/*	case SENSOR_MT9V111: */
+		ucbus_write(gspca_dev, mi0360_start_0,
+				ARRAY_SIZE(mi0360_start_0),
+				8);
+		i2c_write(sd, mt9v111_init_0,
+				ARRAY_SIZE(mt9v111_init_0));
+		i2c_write(sd, mt9v111_init_1,
+				ARRAY_SIZE(mt9v111_init_1));
+		i2c_write(sd, mt9v111_init_2,
+				ARRAY_SIZE(mt9v111_init_2));
+		ucbus_write(gspca_dev, mt9v111_start_1,
+				ARRAY_SIZE(mt9v111_start_1),
+				5);
+		i2c_write(sd, mt9v111_init_3,
+				ARRAY_SIZE(mt9v111_init_3));
+		i2c_write(sd, mt9v111_init_4,
+				ARRAY_SIZE(mt9v111_init_4));
+		break;
+	}
+
+	send_start(gspca_dev);
+out:
+	msleep(1000);
+
+	if (sd->sensor == SENSOR_MT9V111)
+		gpio_set(sd, SQ930_GPIO_DFL_LED, SQ930_GPIO_DFL_LED);
+
+	sd->do_ctrl = 1;	/* set the exposure */
+
+	return gspca_dev->usb_err;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->sensor == SENSOR_MT9V111)
+		gpio_set(sd, 0, SQ930_GPIO_DFL_LED);
+	send_stop(gspca_dev);
+}
+
+/* function called when the application gets a new frame */
+/* It sets the exposure if required and restart the bulk transfer. */
+static void sd_dq_callback(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int ret;
+
+	if (!sd->do_ctrl || gspca_dev->cam.bulk_nurbs != 0)
+		return;
+	sd->do_ctrl = 0;
+
+	setexposure(gspca_dev, v4l2_ctrl_g_ctrl(sd->exposure),
+			v4l2_ctrl_g_ctrl(sd->gain));
+
+	gspca_dev->cam.bulk_nurbs = 1;
+	ret = usb_submit_urb(gspca_dev->urb[0], GFP_ATOMIC);
+	if (ret < 0)
+		pr_err("sd_dq_callback() err %d\n", ret);
+
+	/* wait a little time, otherwise the webcam crashes */
+	msleep(100);
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,		/* isoc packet */
+			int len)		/* iso packet length */
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->do_ctrl)
+		gspca_dev->cam.bulk_nurbs = 0;
+	gspca_frame_add(gspca_dev, FIRST_PACKET, NULL, 0);
+	gspca_frame_add(gspca_dev, INTER_PACKET, data, len - 8);
+	gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_EXPOSURE:
+		setexposure(gspca_dev, ctrl->val, sd->gain->val);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 2);
+	sd->exposure = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_EXPOSURE, 1, 0xfff, 1, 0x356);
+	sd->gain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_GAIN, 1, 255, 1, 0x8d);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	v4l2_ctrl_cluster(2, &sd->exposure);
+	return 0;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name   = MODULE_NAME,
+	.config = sd_config,
+	.init   = sd_init,
+	.init_controls = sd_init_controls,
+	.isoc_init = sd_isoc_init,
+	.start  = sd_start,
+	.stopN  = sd_stopN,
+	.pkt_scan = sd_pkt_scan,
+	.dq_callback = sd_dq_callback,
+};
+
+/* Table of supported USB devices */
+#define ST(sensor, type) \
+	.driver_info = (SENSOR_ ## sensor << 8) \
+			| (type)
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x041e, 0x4038), ST(MI0360, 0)},
+	{USB_DEVICE(0x041e, 0x403c), ST(LZ24BP, 0)},
+	{USB_DEVICE(0x041e, 0x403d), ST(LZ24BP, 0)},
+	{USB_DEVICE(0x041e, 0x4041), ST(LZ24BP, Creative_live_motion)},
+	{USB_DEVICE(0x2770, 0x930b), ST(MI0360, 0)},
+	{USB_DEVICE(0x2770, 0x930c), ST(MI0360, 0)},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+			THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name	    = MODULE_NAME,
+	.id_table   = device_table,
+	.probe	    = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend    = gspca_suspend,
+	.resume     = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/stk014.c b/drivers/media/usb/gspca/stk014.c
new file mode 100644
index 0000000..0d8f489
--- /dev/null
+++ b/drivers/media/usb/gspca/stk014.c
@@ -0,0 +1,443 @@
+/*
+ * Syntek DV4000 (STK014) subdriver
+ *
+ * Copyright (C) 2008 Jean-Francois Moine (http://moinejf.free.fr)
+ *
+ * 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
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "stk014"
+
+#include "gspca.h"
+#include "jpeg.h"
+
+MODULE_AUTHOR("Jean-Francois Moine <http://moinejf.free.fr>");
+MODULE_DESCRIPTION("Syntek DV4000 (STK014) USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+#define QUALITY 50
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+	u8 jpeg_hdr[JPEG_HDR_SZ];
+};
+
+static const struct v4l2_pix_format vga_mode[] = {
+	{320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 1},
+	{640, 480, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 0},
+};
+
+/* -- read a register -- */
+static u8 reg_r(struct gspca_dev *gspca_dev,
+			__u16 index)
+{
+	struct usb_device *dev = gspca_dev->dev;
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return 0;
+	ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+			0x00,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0x00,
+			index,
+			gspca_dev->usb_buf, 1,
+			500);
+	if (ret < 0) {
+		pr_err("reg_r err %d\n", ret);
+		gspca_dev->usb_err = ret;
+		return 0;
+	}
+	return gspca_dev->usb_buf[0];
+}
+
+/* -- write a register -- */
+static void reg_w(struct gspca_dev *gspca_dev,
+			__u16 index, __u16 value)
+{
+	struct usb_device *dev = gspca_dev->dev;
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+			0x01,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			value,
+			index,
+			NULL,
+			0,
+			500);
+	if (ret < 0) {
+		pr_err("reg_w err %d\n", ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+/* -- get a bulk value (4 bytes) -- */
+static void rcv_val(struct gspca_dev *gspca_dev,
+			int ads)
+{
+	struct usb_device *dev = gspca_dev->dev;
+	int alen, ret;
+
+	reg_w(gspca_dev, 0x634, (ads >> 16) & 0xff);
+	reg_w(gspca_dev, 0x635, (ads >> 8) & 0xff);
+	reg_w(gspca_dev, 0x636, ads & 0xff);
+	reg_w(gspca_dev, 0x637, 0);
+	reg_w(gspca_dev, 0x638, 4);	/* len & 0xff */
+	reg_w(gspca_dev, 0x639, 0);	/* len >> 8 */
+	reg_w(gspca_dev, 0x63a, 0);
+	reg_w(gspca_dev, 0x63b, 0);
+	reg_w(gspca_dev, 0x630, 5);
+	if (gspca_dev->usb_err < 0)
+		return;
+	ret = usb_bulk_msg(dev,
+			usb_rcvbulkpipe(dev, 0x05),
+			gspca_dev->usb_buf,
+			4,		/* length */
+			&alen,
+			500);		/* timeout in milliseconds */
+	if (ret < 0) {
+		pr_err("rcv_val err %d\n", ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+/* -- send a bulk value -- */
+static void snd_val(struct gspca_dev *gspca_dev,
+			int ads,
+			unsigned int val)
+{
+	struct usb_device *dev = gspca_dev->dev;
+	int alen, ret;
+	__u8 seq = 0;
+
+	if (ads == 0x003f08) {
+		reg_r(gspca_dev, 0x0704);
+		seq = reg_r(gspca_dev, 0x0705);
+		reg_r(gspca_dev, 0x0650);
+		reg_w(gspca_dev, 0x654, seq);
+	} else {
+		reg_w(gspca_dev, 0x654, (ads >> 16) & 0xff);
+	}
+	reg_w(gspca_dev, 0x655, (ads >> 8) & 0xff);
+	reg_w(gspca_dev, 0x656, ads & 0xff);
+	reg_w(gspca_dev, 0x657, 0);
+	reg_w(gspca_dev, 0x658, 0x04);	/* size */
+	reg_w(gspca_dev, 0x659, 0);
+	reg_w(gspca_dev, 0x65a, 0);
+	reg_w(gspca_dev, 0x65b, 0);
+	reg_w(gspca_dev, 0x650, 5);
+	if (gspca_dev->usb_err < 0)
+		return;
+	gspca_dev->usb_buf[0] = val >> 24;
+	gspca_dev->usb_buf[1] = val >> 16;
+	gspca_dev->usb_buf[2] = val >> 8;
+	gspca_dev->usb_buf[3] = val;
+	ret = usb_bulk_msg(dev,
+			usb_sndbulkpipe(dev, 6),
+			gspca_dev->usb_buf,
+			4,
+			&alen,
+			500);	/* timeout in milliseconds */
+	if (ret < 0) {
+		pr_err("snd_val err %d\n", ret);
+		gspca_dev->usb_err = ret;
+	} else {
+		if (ads == 0x003f08) {
+			seq += 4;
+			seq &= 0x3f;
+			reg_w(gspca_dev, 0x705, seq);
+		}
+	}
+}
+
+/* set a camera parameter */
+static void set_par(struct gspca_dev *gspca_dev,
+		   int parval)
+{
+	snd_val(gspca_dev, 0x003f08, parval);
+}
+
+static void setbrightness(struct gspca_dev *gspca_dev, s32 val)
+{
+	int parval;
+
+	parval = 0x06000000		/* whiteness */
+		+ (val << 16);
+	set_par(gspca_dev, parval);
+}
+
+static void setcontrast(struct gspca_dev *gspca_dev, s32 val)
+{
+	int parval;
+
+	parval = 0x07000000		/* contrast */
+		+ (val << 16);
+	set_par(gspca_dev, parval);
+}
+
+static void setcolors(struct gspca_dev *gspca_dev, s32 val)
+{
+	int parval;
+
+	parval = 0x08000000		/* saturation */
+		+ (val << 16);
+	set_par(gspca_dev, parval);
+}
+
+static void setlightfreq(struct gspca_dev *gspca_dev, s32 val)
+{
+	set_par(gspca_dev, val == 1
+			? 0x33640000		/* 50 Hz */
+			: 0x33780000);		/* 60 Hz */
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	gspca_dev->cam.cam_mode = vga_mode;
+	gspca_dev->cam.nmodes = ARRAY_SIZE(vga_mode);
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	u8 ret;
+
+	/* check if the device responds */
+	usb_set_interface(gspca_dev->dev, gspca_dev->iface, 1);
+	ret = reg_r(gspca_dev, 0x0740);
+	if (gspca_dev->usb_err >= 0) {
+		if (ret != 0xff) {
+			pr_err("init reg: 0x%02x\n", ret);
+			gspca_dev->usb_err = -EIO;
+		}
+	}
+	return gspca_dev->usb_err;
+}
+
+/* -- start the camera -- */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int ret, value;
+
+	/* create the JPEG header */
+	jpeg_define(sd->jpeg_hdr, gspca_dev->pixfmt.height,
+			gspca_dev->pixfmt.width,
+			0x22);		/* JPEG 411 */
+	jpeg_set_qual(sd->jpeg_hdr, QUALITY);
+
+	/* work on alternate 1 */
+	usb_set_interface(gspca_dev->dev, gspca_dev->iface, 1);
+
+	set_par(gspca_dev, 0x10000000);
+	set_par(gspca_dev, 0x00000000);
+	set_par(gspca_dev, 0x8002e001);
+	set_par(gspca_dev, 0x14000000);
+	if (gspca_dev->pixfmt.width > 320)
+		value = 0x8002e001;		/* 640x480 */
+	else
+		value = 0x4001f000;		/* 320x240 */
+	set_par(gspca_dev, value);
+	ret = usb_set_interface(gspca_dev->dev,
+					gspca_dev->iface,
+					gspca_dev->alt);
+	if (ret < 0) {
+		pr_err("set intf %d %d failed\n",
+		       gspca_dev->iface, gspca_dev->alt);
+		gspca_dev->usb_err = ret;
+		goto out;
+	}
+	reg_r(gspca_dev, 0x0630);
+	rcv_val(gspca_dev, 0x000020);	/* << (value ff ff ff ff) */
+	reg_r(gspca_dev, 0x0650);
+	snd_val(gspca_dev, 0x000020, 0xffffffff);
+	reg_w(gspca_dev, 0x0620, 0);
+	reg_w(gspca_dev, 0x0630, 0);
+	reg_w(gspca_dev, 0x0640, 0);
+	reg_w(gspca_dev, 0x0650, 0);
+	reg_w(gspca_dev, 0x0660, 0);
+	set_par(gspca_dev, 0x09800000);		/* Red ? */
+	set_par(gspca_dev, 0x0a800000);		/* Green ? */
+	set_par(gspca_dev, 0x0b800000);		/* Blue ? */
+	set_par(gspca_dev, 0x0d030000);		/* Gamma ? */
+
+	/* start the video flow */
+	set_par(gspca_dev, 0x01000000);
+	set_par(gspca_dev, 0x01000000);
+	if (gspca_dev->usb_err >= 0)
+		gspca_dbg(gspca_dev, D_STREAM, "camera started alt: 0x%02x\n",
+			  gspca_dev->alt);
+out:
+	return gspca_dev->usb_err;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	struct usb_device *dev = gspca_dev->dev;
+
+	set_par(gspca_dev, 0x02000000);
+	set_par(gspca_dev, 0x02000000);
+	usb_set_interface(dev, gspca_dev->iface, 1);
+	reg_r(gspca_dev, 0x0630);
+	rcv_val(gspca_dev, 0x000020);	/* << (value ff ff ff ff) */
+	reg_r(gspca_dev, 0x0650);
+	snd_val(gspca_dev, 0x000020, 0xffffffff);
+	reg_w(gspca_dev, 0x0620, 0);
+	reg_w(gspca_dev, 0x0630, 0);
+	reg_w(gspca_dev, 0x0640, 0);
+	reg_w(gspca_dev, 0x0650, 0);
+	reg_w(gspca_dev, 0x0660, 0);
+	gspca_dbg(gspca_dev, D_STREAM, "camera stopped\n");
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	static unsigned char ffd9[] = {0xff, 0xd9};
+
+	/* a frame starts with:
+	 *	- 0xff 0xfe
+	 *	- 0x08 0x00	- length (little endian ?!)
+	 *	- 4 bytes = size of whole frame (BE - including header)
+	 *	- 0x00 0x0c
+	 *	- 0xff 0xd8
+	 *	- ..	JPEG image with escape sequences (ff 00)
+	 *		(without ending - ff d9)
+	 */
+	if (data[0] == 0xff && data[1] == 0xfe) {
+		gspca_frame_add(gspca_dev, LAST_PACKET,
+				ffd9, 2);
+
+		/* put the JPEG 411 header */
+		gspca_frame_add(gspca_dev, FIRST_PACKET,
+			sd->jpeg_hdr, JPEG_HDR_SZ);
+
+		/* beginning of the frame */
+#define STKHDRSZ 12
+		data += STKHDRSZ;
+		len -= STKHDRSZ;
+	}
+	gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		setbrightness(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_CONTRAST:
+		setcontrast(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_SATURATION:
+		setcolors(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_POWER_LINE_FREQUENCY:
+		setlightfreq(gspca_dev, ctrl->val);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 4);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0, 255, 1, 127);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_CONTRAST, 0, 255, 1, 127);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_SATURATION, 0, 255, 1, 127);
+	v4l2_ctrl_new_std_menu(hdl, &sd_ctrl_ops,
+			V4L2_CID_POWER_LINE_FREQUENCY,
+			V4L2_CID_POWER_LINE_FREQUENCY_60HZ, 1,
+			V4L2_CID_POWER_LINE_FREQUENCY_50HZ);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	return 0;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.pkt_scan = sd_pkt_scan,
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x05e1, 0x0893)},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/stk1135.c b/drivers/media/usb/gspca/stk1135.c
new file mode 100644
index 0000000..6f52a48
--- /dev/null
+++ b/drivers/media/usb/gspca/stk1135.c
@@ -0,0 +1,685 @@
+/*
+ * Syntek STK1135 subdriver
+ *
+ * Copyright (c) 2013 Ondrej Zary
+ *
+ * Based on Syntekdriver (stk11xx) by Nicolas VIVIEN:
+ *   http://syntekdriver.sourceforge.net
+ *
+ * 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
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "stk1135"
+
+#include "gspca.h"
+#include "stk1135.h"
+
+MODULE_AUTHOR("Ondrej Zary");
+MODULE_DESCRIPTION("Syntek STK1135 USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+
+	u8 pkt_seq;
+	u8 sensor_page;
+
+	bool flip_status;
+	u8 flip_debounce;
+
+	struct v4l2_ctrl *hflip;
+	struct v4l2_ctrl *vflip;
+};
+
+static const struct v4l2_pix_format stk1135_modes[] = {
+	/* default mode (this driver supports variable resolution) */
+	{640, 480, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480,
+		.colorspace = V4L2_COLORSPACE_SRGB},
+};
+
+/* -- read a register -- */
+static u8 reg_r(struct gspca_dev *gspca_dev, u16 index)
+{
+	struct usb_device *dev = gspca_dev->dev;
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return 0;
+	ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+			0x00,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0x00,
+			index,
+			gspca_dev->usb_buf, 1,
+			500);
+
+	gspca_dbg(gspca_dev, D_USBI, "reg_r 0x%x=0x%02x\n",
+		  index, gspca_dev->usb_buf[0]);
+	if (ret < 0) {
+		pr_err("reg_r 0x%x err %d\n", index, ret);
+		gspca_dev->usb_err = ret;
+		return 0;
+	}
+
+	return gspca_dev->usb_buf[0];
+}
+
+/* -- write a register -- */
+static void reg_w(struct gspca_dev *gspca_dev, u16 index, u8 val)
+{
+	int ret;
+	struct usb_device *dev = gspca_dev->dev;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+			0x01,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			val,
+			index,
+			NULL,
+			0,
+			500);
+	gspca_dbg(gspca_dev, D_USBO, "reg_w 0x%x:=0x%02x\n", index, val);
+	if (ret < 0) {
+		pr_err("reg_w 0x%x err %d\n", index, ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+static void reg_w_mask(struct gspca_dev *gspca_dev, u16 index, u8 val, u8 mask)
+{
+	val = (reg_r(gspca_dev, index) & ~mask) | (val & mask);
+	reg_w(gspca_dev, index, val);
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	gspca_dev->cam.cam_mode = stk1135_modes;
+	gspca_dev->cam.nmodes = ARRAY_SIZE(stk1135_modes);
+	return 0;
+}
+
+static int stk1135_serial_wait_ready(struct gspca_dev *gspca_dev)
+{
+	int i = 0;
+	u8 val;
+
+	do {
+		val = reg_r(gspca_dev, STK1135_REG_SICTL + 1);
+		if (i++ > 500) { /* maximum retry count */
+			pr_err("serial bus timeout: status=0x%02x\n", val);
+			return -1;
+		}
+	/* repeat if BUSY or WRITE/READ not finished */
+	} while ((val & 0x10) || !(val & 0x05));
+
+	return 0;
+}
+
+static u8 sensor_read_8(struct gspca_dev *gspca_dev, u8 addr)
+{
+	reg_w(gspca_dev, STK1135_REG_SBUSR, addr);
+	/* begin read */
+	reg_w(gspca_dev, STK1135_REG_SICTL, 0x20);
+	/* wait until finished */
+	if (stk1135_serial_wait_ready(gspca_dev)) {
+		pr_err("Sensor read failed\n");
+		return 0;
+	}
+
+	return reg_r(gspca_dev, STK1135_REG_SBUSR + 1);
+}
+
+static u16 sensor_read_16(struct gspca_dev *gspca_dev, u8 addr)
+{
+	return (sensor_read_8(gspca_dev, addr) << 8) |
+		sensor_read_8(gspca_dev, 0xf1);
+}
+
+static void sensor_write_8(struct gspca_dev *gspca_dev, u8 addr, u8 data)
+{
+	/* load address and data registers */
+	reg_w(gspca_dev, STK1135_REG_SBUSW, addr);
+	reg_w(gspca_dev, STK1135_REG_SBUSW + 1, data);
+	/* begin write */
+	reg_w(gspca_dev, STK1135_REG_SICTL, 0x01);
+	/* wait until finished */
+	if (stk1135_serial_wait_ready(gspca_dev)) {
+		pr_err("Sensor write failed\n");
+		return;
+	}
+}
+
+static void sensor_write_16(struct gspca_dev *gspca_dev, u8 addr, u16 data)
+{
+	sensor_write_8(gspca_dev, addr, data >> 8);
+	sensor_write_8(gspca_dev, 0xf1, data & 0xff);
+}
+
+static void sensor_set_page(struct gspca_dev *gspca_dev, u8 page)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (page != sd->sensor_page) {
+		sensor_write_16(gspca_dev, 0xf0, page);
+		sd->sensor_page = page;
+	}
+}
+
+static u16 sensor_read(struct gspca_dev *gspca_dev, u16 reg)
+{
+	sensor_set_page(gspca_dev, reg >> 8);
+	return sensor_read_16(gspca_dev, reg & 0xff);
+}
+
+static void sensor_write(struct gspca_dev *gspca_dev, u16 reg, u16 val)
+{
+	sensor_set_page(gspca_dev, reg >> 8);
+	sensor_write_16(gspca_dev, reg & 0xff, val);
+}
+
+static void sensor_write_mask(struct gspca_dev *gspca_dev,
+			u16 reg, u16 val, u16 mask)
+{
+	val = (sensor_read(gspca_dev, reg) & ~mask) | (val & mask);
+	sensor_write(gspca_dev, reg, val);
+}
+
+struct sensor_val {
+	u16 reg;
+	u16 val;
+};
+
+/* configure MT9M112 sensor */
+static void stk1135_configure_mt9m112(struct gspca_dev *gspca_dev)
+{
+	static const struct sensor_val cfg[] = {
+		/* restart&reset, chip enable, reserved */
+		{ 0x00d, 0x000b }, { 0x00d, 0x0008 }, { 0x035, 0x0022 },
+		/* mode ctl: AWB on, AE both, clip aper corr, defect corr, AE */
+		{ 0x106, 0x700e },
+
+		{ 0x2dd, 0x18e0 }, /* B-R thresholds, */
+
+		/* AWB */
+		{ 0x21f, 0x0180 }, /* Cb and Cr limits */
+		{ 0x220, 0xc814 }, { 0x221, 0x8080 }, /* lum limits, RGB gain */
+		{ 0x222, 0xa078 }, { 0x223, 0xa078 }, /* R, B limit */
+		{ 0x224, 0x5f20 }, { 0x228, 0xea02 }, /* mtx adj lim, adv ctl */
+		{ 0x229, 0x867a }, /* wide gates */
+
+		/* Color correction */
+		/* imager gains base, delta, delta signs */
+		{ 0x25e, 0x594c }, { 0x25f, 0x4d51 }, { 0x260, 0x0002 },
+		/* AWB adv ctl 2, gain offs */
+		{ 0x2ef, 0x0008 }, { 0x2f2, 0x0000 },
+		/* base matrix signs, scale K1-5, K6-9 */
+		{ 0x202, 0x00ee }, { 0x203, 0x3923 }, { 0x204, 0x0724 },
+		/* base matrix coef */
+		{ 0x209, 0x00cd }, { 0x20a, 0x0093 }, { 0x20b, 0x0004 },/*K1-3*/
+		{ 0x20c, 0x005c }, { 0x20d, 0x00d9 }, { 0x20e, 0x0053 },/*K4-6*/
+		{ 0x20f, 0x0008 }, { 0x210, 0x0091 }, { 0x211, 0x00cf },/*K7-9*/
+		{ 0x215, 0x0000 }, /* delta mtx signs */
+		/* delta matrix coef */
+		{ 0x216, 0x0000 }, { 0x217, 0x0000 }, { 0x218, 0x0000 },/*D1-3*/
+		{ 0x219, 0x0000 }, { 0x21a, 0x0000 }, { 0x21b, 0x0000 },/*D4-6*/
+		{ 0x21c, 0x0000 }, { 0x21d, 0x0000 }, { 0x21e, 0x0000 },/*D7-9*/
+		/* enable & disable manual WB to apply color corr. settings */
+		{ 0x106, 0xf00e }, { 0x106, 0x700e },
+
+		/* Lens shading correction */
+		{ 0x180, 0x0007 }, /* control */
+		/* vertical knee 0, 2+1, 4+3 */
+		{ 0x181, 0xde13 }, { 0x182, 0xebe2 }, { 0x183, 0x00f6 }, /* R */
+		{ 0x184, 0xe114 }, { 0x185, 0xeadd }, { 0x186, 0xfdf6 }, /* G */
+		{ 0x187, 0xe511 }, { 0x188, 0xede6 }, { 0x189, 0xfbf7 }, /* B */
+		/* horizontal knee 0, 2+1, 4+3, 5 */
+		{ 0x18a, 0xd613 }, { 0x18b, 0xedec }, /* R .. */
+		{ 0x18c, 0xf9f2 }, { 0x18d, 0x0000 }, /* .. R */
+		{ 0x18e, 0xd815 }, { 0x18f, 0xe9ea }, /* G .. */
+		{ 0x190, 0xf9f1 }, { 0x191, 0x0002 }, /* .. G */
+		{ 0x192, 0xde10 }, { 0x193, 0xefef }, /* B .. */
+		{ 0x194, 0xfbf4 }, { 0x195, 0x0002 }, /* .. B */
+		/* vertical knee 6+5, 8+7 */
+		{ 0x1b6, 0x0e06 }, { 0x1b7, 0x2713 }, /* R */
+		{ 0x1b8, 0x1106 }, { 0x1b9, 0x2713 }, /* G */
+		{ 0x1ba, 0x0c03 }, { 0x1bb, 0x2a0f }, /* B */
+		/* horizontal knee 7+6, 9+8, 10 */
+		{ 0x1bc, 0x1208 }, { 0x1bd, 0x1a16 }, { 0x1be, 0x0022 }, /* R */
+		{ 0x1bf, 0x150a }, { 0x1c0, 0x1c1a }, { 0x1c1, 0x002d }, /* G */
+		{ 0x1c2, 0x1109 }, { 0x1c3, 0x1414 }, { 0x1c4, 0x002a }, /* B */
+		{ 0x106, 0x740e }, /* enable lens shading correction */
+
+		/* Gamma correction - context A */
+		{ 0x153, 0x0b03 }, { 0x154, 0x4722 }, { 0x155, 0xac82 },
+		{ 0x156, 0xdac7 }, { 0x157, 0xf5e9 }, { 0x158, 0xff00 },
+		/* Gamma correction - context B */
+		{ 0x1dc, 0x0b03 }, { 0x1dd, 0x4722 }, { 0x1de, 0xac82 },
+		{ 0x1df, 0xdac7 }, { 0x1e0, 0xf5e9 }, { 0x1e1, 0xff00 },
+
+		/* output format: RGB, invert output pixclock, output bayer */
+		{ 0x13a, 0x4300 }, { 0x19b, 0x4300 }, /* for context A, B */
+		{ 0x108, 0x0180 }, /* format control - enable bayer row flip */
+
+		{ 0x22f, 0xd100 }, { 0x29c, 0xd100 }, /* AE A, B */
+
+		/* default prg conf, prg ctl - by 0x2d2, prg advance - PA1 */
+		{ 0x2d2, 0x0000 }, { 0x2cc, 0x0004 }, { 0x2cb, 0x0001 },
+
+		{ 0x22e, 0x0c3c }, { 0x267, 0x1010 }, /* AE tgt ctl, gain lim */
+
+		/* PLL */
+		{ 0x065, 0xa000 }, /* clk ctl - enable PLL (clear bit 14) */
+		{ 0x066, 0x2003 }, { 0x067, 0x0501 }, /* PLL M=128, N=3, P=1 */
+		{ 0x065, 0x2000 }, /* disable PLL bypass (clear bit 15) */
+
+		{ 0x005, 0x01b8 }, { 0x007, 0x00d8 }, /* horiz blanking B, A */
+
+		/* AE line size, shutter delay limit */
+		{ 0x239, 0x06c0 }, { 0x23b, 0x040e }, /* for context A */
+		{ 0x23a, 0x06c0 }, { 0x23c, 0x0564 }, /* for context B */
+		/* shutter width basis 60Hz, 50Hz */
+		{ 0x257, 0x0208 }, { 0x258, 0x0271 }, /* for context A */
+		{ 0x259, 0x0209 }, { 0x25a, 0x0271 }, /* for context B */
+
+		{ 0x25c, 0x120d }, { 0x25d, 0x1712 }, /* flicker 60Hz, 50Hz */
+		{ 0x264, 0x5e1c }, /* reserved */
+		/* flicker, AE gain limits, gain zone limits */
+		{ 0x25b, 0x0003 }, { 0x236, 0x7810 }, { 0x237, 0x8304 },
+
+		{ 0x008, 0x0021 }, /* vert blanking A */
+	};
+	int i;
+	u16 width, height;
+
+	for (i = 0; i < ARRAY_SIZE(cfg); i++)
+		sensor_write(gspca_dev, cfg[i].reg, cfg[i].val);
+
+	/* set output size */
+	width = gspca_dev->pixfmt.width;
+	height = gspca_dev->pixfmt.height;
+	if (width <= 640 && height <= 512) { /* context A (half readout speed)*/
+		sensor_write(gspca_dev, 0x1a7, width);
+		sensor_write(gspca_dev, 0x1aa, height);
+		/* set read mode context A */
+		sensor_write(gspca_dev, 0x0c8, 0x0000);
+		/* set resize, read mode, vblank, hblank context A */
+		sensor_write(gspca_dev, 0x2c8, 0x0000);
+	} else { /* context B (full readout speed) */
+		sensor_write(gspca_dev, 0x1a1, width);
+		sensor_write(gspca_dev, 0x1a4, height);
+		/* set read mode context B */
+		sensor_write(gspca_dev, 0x0c8, 0x0008);
+		/* set resize, read mode, vblank, hblank context B */
+		sensor_write(gspca_dev, 0x2c8, 0x040b);
+	}
+}
+
+static void stk1135_configure_clock(struct gspca_dev *gspca_dev)
+{
+	/* configure SCLKOUT */
+	reg_w(gspca_dev, STK1135_REG_TMGEN, 0x12);
+	/* set 1 clock per pixel */
+	/* and positive edge clocked pulse high when pixel counter = 0 */
+	reg_w(gspca_dev, STK1135_REG_TCP1 + 0, 0x41);
+	reg_w(gspca_dev, STK1135_REG_TCP1 + 1, 0x00);
+	reg_w(gspca_dev, STK1135_REG_TCP1 + 2, 0x00);
+	reg_w(gspca_dev, STK1135_REG_TCP1 + 3, 0x00);
+
+	/* enable CLKOUT for sensor */
+	reg_w(gspca_dev, STK1135_REG_SENSO + 0, 0x10);
+	/* disable STOP clock */
+	reg_w(gspca_dev, STK1135_REG_SENSO + 1, 0x00);
+	/* set lower 8 bits of PLL feedback divider */
+	reg_w(gspca_dev, STK1135_REG_SENSO + 3, 0x07);
+	/* set other PLL parameters */
+	reg_w(gspca_dev, STK1135_REG_PLLFD, 0x06);
+	/* enable timing generator */
+	reg_w(gspca_dev, STK1135_REG_TMGEN, 0x80);
+	/* enable PLL */
+	reg_w(gspca_dev, STK1135_REG_SENSO + 2, 0x04);
+
+	/* set serial interface clock divider (30MHz/0x1f*16+2) = 60240 kHz) */
+	reg_w(gspca_dev, STK1135_REG_SICTL + 2, 0x1f);
+
+	/* wait a while for sensor to catch up */
+	udelay(1000);
+}
+
+static void stk1135_camera_disable(struct gspca_dev *gspca_dev)
+{
+	/* set capture end Y position to 0 */
+	reg_w(gspca_dev, STK1135_REG_CIEPO + 2, 0x00);
+	reg_w(gspca_dev, STK1135_REG_CIEPO + 3, 0x00);
+	/* disable capture */
+	reg_w_mask(gspca_dev, STK1135_REG_SCTRL, 0x00, 0x80);
+
+	/* enable sensor standby and diasble chip enable */
+	sensor_write_mask(gspca_dev, 0x00d, 0x0004, 0x000c);
+
+	/* disable PLL */
+	reg_w_mask(gspca_dev, STK1135_REG_SENSO + 2, 0x00, 0x01);
+	/* disable timing generator */
+	reg_w(gspca_dev, STK1135_REG_TMGEN, 0x00);
+	/* enable STOP clock */
+	reg_w(gspca_dev, STK1135_REG_SENSO + 1, 0x20);
+	/* disable CLKOUT for sensor */
+	reg_w(gspca_dev, STK1135_REG_SENSO, 0x00);
+
+	/* disable sensor (GPIO5) and enable GPIO0,3,6 (?) - sensor standby? */
+	reg_w(gspca_dev, STK1135_REG_GCTRL, 0x49);
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	u16 sensor_id;
+	char *sensor_name;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	/* set GPIO3,4,5,6 direction to output */
+	reg_w(gspca_dev, STK1135_REG_GCTRL + 2, 0x78);
+	/* enable sensor (GPIO5) */
+	reg_w(gspca_dev, STK1135_REG_GCTRL, (1 << 5));
+	/* disable ROM interface */
+	reg_w(gspca_dev, STK1135_REG_GCTRL + 3, 0x80);
+	/* enable interrupts from GPIO8 (flip sensor) and GPIO9 (???) */
+	reg_w(gspca_dev, STK1135_REG_ICTRL + 1, 0x00);
+	reg_w(gspca_dev, STK1135_REG_ICTRL + 3, 0x03);
+	/* enable remote wakeup from GPIO9 (???) */
+	reg_w(gspca_dev, STK1135_REG_RMCTL + 1, 0x00);
+	reg_w(gspca_dev, STK1135_REG_RMCTL + 3, 0x02);
+
+	/* reset serial interface */
+	reg_w(gspca_dev, STK1135_REG_SICTL, 0x80);
+	reg_w(gspca_dev, STK1135_REG_SICTL, 0x00);
+	/* set sensor address */
+	reg_w(gspca_dev, STK1135_REG_SICTL + 3, 0xba);
+	/* disable alt 2-wire serial interface */
+	reg_w(gspca_dev, STK1135_REG_ASIC + 3, 0x00);
+
+	stk1135_configure_clock(gspca_dev);
+
+	/* read sensor ID */
+	sd->sensor_page = 0xff;
+	sensor_id = sensor_read(gspca_dev, 0x000);
+
+	switch (sensor_id) {
+	case 0x148c:
+		sensor_name = "MT9M112";
+		break;
+	default:
+		sensor_name = "unknown";
+	}
+	pr_info("Detected sensor type %s (0x%x)\n", sensor_name, sensor_id);
+
+	stk1135_camera_disable(gspca_dev);
+
+	return gspca_dev->usb_err;
+}
+
+/* -- start the camera -- */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u16 width, height;
+
+	/* enable sensor (GPIO5) */
+	reg_w(gspca_dev, STK1135_REG_GCTRL, (1 << 5));
+
+	stk1135_configure_clock(gspca_dev);
+
+	/* set capture start position X = 0, Y = 0 */
+	reg_w(gspca_dev, STK1135_REG_CISPO + 0, 0x00);
+	reg_w(gspca_dev, STK1135_REG_CISPO + 1, 0x00);
+	reg_w(gspca_dev, STK1135_REG_CISPO + 2, 0x00);
+	reg_w(gspca_dev, STK1135_REG_CISPO + 3, 0x00);
+
+	/* set capture end position */
+	width = gspca_dev->pixfmt.width;
+	height = gspca_dev->pixfmt.height;
+	reg_w(gspca_dev, STK1135_REG_CIEPO + 0, width & 0xff);
+	reg_w(gspca_dev, STK1135_REG_CIEPO + 1, width >> 8);
+	reg_w(gspca_dev, STK1135_REG_CIEPO + 2, height & 0xff);
+	reg_w(gspca_dev, STK1135_REG_CIEPO + 3, height >> 8);
+
+	/* set 8-bit mode */
+	reg_w(gspca_dev, STK1135_REG_SCTRL, 0x20);
+
+	stk1135_configure_mt9m112(gspca_dev);
+
+	/* enable capture */
+	reg_w_mask(gspca_dev, STK1135_REG_SCTRL, 0x80, 0x80);
+
+	if (gspca_dev->usb_err >= 0)
+		gspca_dbg(gspca_dev, D_STREAM, "camera started alt: 0x%02x\n",
+			  gspca_dev->alt);
+
+	sd->pkt_seq = 0;
+
+	return gspca_dev->usb_err;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	struct usb_device *dev = gspca_dev->dev;
+
+	usb_set_interface(dev, gspca_dev->iface, 0);
+
+	stk1135_camera_disable(gspca_dev);
+
+	gspca_dbg(gspca_dev, D_STREAM, "camera stopped\n");
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int skip = sizeof(struct stk1135_pkt_header);
+	bool flip;
+	enum gspca_packet_type pkt_type = INTER_PACKET;
+	struct stk1135_pkt_header *hdr = (void *)data;
+	u8 seq;
+
+	if (len < 4) {
+		gspca_dbg(gspca_dev, D_PACK, "received short packet (less than 4 bytes)\n");
+		return;
+	}
+
+	/* GPIO 8 is flip sensor (1 = normal position, 0 = flipped to back) */
+	flip = !(le16_to_cpu(hdr->gpio) & (1 << 8));
+	/* it's a switch, needs software debounce */
+	if (sd->flip_status != flip)
+		sd->flip_debounce++;
+	else
+		sd->flip_debounce = 0;
+
+	/* check sequence number (not present in new frame packets) */
+	if (!(hdr->flags & STK1135_HDR_FRAME_START)) {
+		seq = hdr->seq & STK1135_HDR_SEQ_MASK;
+		if (seq != sd->pkt_seq) {
+			gspca_dbg(gspca_dev, D_PACK, "received out-of-sequence packet\n");
+			/* resync sequence and discard packet */
+			sd->pkt_seq = seq;
+			gspca_dev->last_packet_type = DISCARD_PACKET;
+			return;
+		}
+	}
+	sd->pkt_seq++;
+	if (sd->pkt_seq > STK1135_HDR_SEQ_MASK)
+		sd->pkt_seq = 0;
+
+	if (len == sizeof(struct stk1135_pkt_header))
+		return;
+
+	if (hdr->flags & STK1135_HDR_FRAME_START) { /* new frame */
+		skip = 8;	/* the header is longer */
+		gspca_frame_add(gspca_dev, LAST_PACKET, data, 0);
+		pkt_type = FIRST_PACKET;
+	}
+	gspca_frame_add(gspca_dev, pkt_type, data + skip, len - skip);
+}
+
+static void sethflip(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->flip_status)
+		val = !val;
+	sensor_write_mask(gspca_dev, 0x020, val ? 0x0002 : 0x0000 , 0x0002);
+}
+
+static void setvflip(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->flip_status)
+		val = !val;
+	sensor_write_mask(gspca_dev, 0x020, val ? 0x0001 : 0x0000 , 0x0001);
+}
+
+static void stk1135_dq_callback(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->flip_debounce > 100) {
+		sd->flip_status = !sd->flip_status;
+		sethflip(gspca_dev, v4l2_ctrl_g_ctrl(sd->hflip));
+		setvflip(gspca_dev, v4l2_ctrl_g_ctrl(sd->vflip));
+	}
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_HFLIP:
+		sethflip(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_VFLIP:
+		setvflip(gspca_dev, ctrl->val);
+		break;
+	}
+
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 2);
+	sd->hflip = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_HFLIP, 0, 1, 1, 0);
+	sd->vflip = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_VFLIP, 0, 1, 1, 0);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	return 0;
+}
+
+static void stk1135_try_fmt(struct gspca_dev *gspca_dev, struct v4l2_format *fmt)
+{
+	fmt->fmt.pix.width = clamp(fmt->fmt.pix.width, 32U, 1280U);
+	fmt->fmt.pix.height = clamp(fmt->fmt.pix.height, 32U, 1024U);
+	/* round up to even numbers */
+	fmt->fmt.pix.width += (fmt->fmt.pix.width & 1);
+	fmt->fmt.pix.height += (fmt->fmt.pix.height & 1);
+
+	fmt->fmt.pix.bytesperline = fmt->fmt.pix.width;
+	fmt->fmt.pix.sizeimage = fmt->fmt.pix.width * fmt->fmt.pix.height;
+}
+
+static int stk1135_enum_framesizes(struct gspca_dev *gspca_dev,
+			struct v4l2_frmsizeenum *fsize)
+{
+	if (fsize->index != 0 || fsize->pixel_format != V4L2_PIX_FMT_SBGGR8)
+		return -EINVAL;
+
+	fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
+	fsize->stepwise.min_width = 32;
+	fsize->stepwise.min_height = 32;
+	fsize->stepwise.max_width = 1280;
+	fsize->stepwise.max_height = 1024;
+	fsize->stepwise.step_width = 2;
+	fsize->stepwise.step_height = 2;
+
+	return 0;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.pkt_scan = sd_pkt_scan,
+	.dq_callback = stk1135_dq_callback,
+	.try_fmt = stk1135_try_fmt,
+	.enum_framesizes = stk1135_enum_framesizes,
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x174f, 0x6a31)},	/* ASUS laptop, MT9M112 sensor */
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/stk1135.h b/drivers/media/usb/gspca/stk1135.h
new file mode 100644
index 0000000..bd14401
--- /dev/null
+++ b/drivers/media/usb/gspca/stk1135.h
@@ -0,0 +1,53 @@
+/*
+ * STK1135 registers
+ *
+ * Copyright (c) 2013 Ondrej Zary
+ *
+ * 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
+ * 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.
+ */
+
+#define STK1135_REG_GCTRL	0x000	/* GPIO control */
+#define STK1135_REG_ICTRL	0x004	/* Interrupt control */
+#define STK1135_REG_IDATA	0x008	/* Interrupt data */
+#define STK1135_REG_RMCTL	0x00c	/* Remote wakeup control */
+#define STK1135_REG_POSVA	0x010	/* Power-on strapping data */
+
+#define STK1135_REG_SENSO	0x018	/* Sensor select options */
+#define STK1135_REG_PLLFD	0x01c	/* PLL frequency divider */
+
+#define STK1135_REG_SCTRL	0x100	/* Sensor control register */
+#define STK1135_REG_DCTRL	0x104	/* Decimation control register */
+#define STK1135_REG_CISPO	0x110	/* Capture image starting position */
+#define STK1135_REG_CIEPO	0x114	/* Capture image ending position */
+#define STK1135_REG_TCTRL	0x120	/* Test data control */
+
+#define STK1135_REG_SICTL	0x200	/* Serial interface control register */
+#define STK1135_REG_SBUSW	0x204	/* Serial bus write */
+#define STK1135_REG_SBUSR	0x208	/* Serial bus read */
+#define STK1135_REG_SCSI	0x20c	/* Software control serial interface */
+#define STK1135_REG_GSBWP	0x210	/* General serial bus write port */
+#define STK1135_REG_GSBRP	0x214	/* General serial bus read port */
+#define STK1135_REG_ASIC	0x2fc	/* Alternate serial interface control */
+
+#define STK1135_REG_TMGEN	0x300	/* Timing generator */
+#define STK1135_REG_TCP1	0x350	/* Timing control parameter 1 */
+
+struct stk1135_pkt_header {
+	u8 flags;
+	u8 seq;
+	__le16 gpio;
+} __packed;
+
+#define STK1135_HDR_FRAME_START	(1 << 7)
+#define STK1135_HDR_ODD		(1 << 6)
+#define STK1135_HDR_I2C_VBLANK	(1 << 5)
+
+#define STK1135_HDR_SEQ_MASK	0x3f
diff --git a/drivers/media/usb/gspca/stv0680.c b/drivers/media/usb/gspca/stv0680.c
new file mode 100644
index 0000000..3ff5ed7
--- /dev/null
+++ b/drivers/media/usb/gspca/stv0680.c
@@ -0,0 +1,349 @@
+/*
+ * STV0680 USB Camera Driver
+ *
+ * Copyright (C) 2009 Hans de Goede <hdegoede@redhat.com>
+ *
+ * This module is adapted from the in kernel v4l1 stv680 driver:
+ *
+ *  STV0680 USB Camera Driver, by Kevin Sisson (kjsisson@bellsouth.net)
+ *
+ * Thanks to STMicroelectronics for information on the usb commands, and
+ * to Steve Miller at STM for his help and encouragement while I was
+ * writing this driver.
+ *
+ * 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.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "stv0680"
+
+#include "gspca.h"
+
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_DESCRIPTION("STV0680 USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;		/* !! must be the first item */
+	struct v4l2_pix_format mode;
+	u8 orig_mode;
+	u8 video_mode;
+	u8 current_mode;
+};
+
+static int stv_sndctrl(struct gspca_dev *gspca_dev, int set, u8 req, u16 val,
+		       int size)
+{
+	int ret = -1;
+	u8 req_type = 0;
+	unsigned int pipe = 0;
+
+	switch (set) {
+	case 0: /*  0xc1  */
+		req_type = USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT;
+		pipe = usb_rcvctrlpipe(gspca_dev->dev, 0);
+		break;
+	case 1: /*  0x41  */
+		req_type = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT;
+		pipe = usb_sndctrlpipe(gspca_dev->dev, 0);
+		break;
+	case 2:	/*  0x80  */
+		req_type = USB_DIR_IN | USB_RECIP_DEVICE;
+		pipe = usb_rcvctrlpipe(gspca_dev->dev, 0);
+		break;
+	case 3:	/*  0x40  */
+		req_type = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE;
+		pipe = usb_sndctrlpipe(gspca_dev->dev, 0);
+		break;
+	}
+
+	ret = usb_control_msg(gspca_dev->dev, pipe,
+			      req, req_type,
+			      val, 0, gspca_dev->usb_buf, size, 500);
+
+	if ((ret < 0) && (req != 0x0a))
+		pr_err("usb_control_msg error %i, request = 0x%x, error = %i\n",
+		       set, req, ret);
+
+	return ret;
+}
+
+static int stv0680_handle_error(struct gspca_dev *gspca_dev, int ret)
+{
+	stv_sndctrl(gspca_dev, 0, 0x80, 0, 0x02); /* Get Last Error */
+	gspca_err(gspca_dev, "last error: %i,  command = 0x%x\n",
+		  gspca_dev->usb_buf[0], gspca_dev->usb_buf[1]);
+	return ret;
+}
+
+static int stv0680_get_video_mode(struct gspca_dev *gspca_dev)
+{
+	/* Note not sure if this init of usb_buf is really necessary */
+	memset(gspca_dev->usb_buf, 0, 8);
+	gspca_dev->usb_buf[0] = 0x0f;
+
+	if (stv_sndctrl(gspca_dev, 0, 0x87, 0, 0x08) != 0x08) {
+		gspca_err(gspca_dev, "Get_Camera_Mode failed\n");
+		return stv0680_handle_error(gspca_dev, -EIO);
+	}
+
+	return gspca_dev->usb_buf[0]; /* 01 = VGA, 03 = QVGA, 00 = CIF */
+}
+
+static int stv0680_set_video_mode(struct gspca_dev *gspca_dev, u8 mode)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->current_mode == mode)
+		return 0;
+
+	memset(gspca_dev->usb_buf, 0, 8);
+	gspca_dev->usb_buf[0] = mode;
+
+	if (stv_sndctrl(gspca_dev, 3, 0x07, 0x0100, 0x08) != 0x08) {
+		gspca_err(gspca_dev, "Set_Camera_Mode failed\n");
+		return stv0680_handle_error(gspca_dev, -EIO);
+	}
+
+	/* Verify we got what we've asked for */
+	if (stv0680_get_video_mode(gspca_dev) != mode) {
+		gspca_err(gspca_dev, "Error setting camera video mode!\n");
+		return -EIO;
+	}
+
+	sd->current_mode = mode;
+
+	return 0;
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	int ret;
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam = &gspca_dev->cam;
+
+	/* Give the camera some time to settle, otherwise initialization will
+	   fail on hotplug, and yes it really needs a full second. */
+	msleep(1000);
+
+	/* ping camera to be sure STV0680 is present */
+	if (stv_sndctrl(gspca_dev, 0, 0x88, 0x5678, 0x02) != 0x02 ||
+	    gspca_dev->usb_buf[0] != 0x56 || gspca_dev->usb_buf[1] != 0x78) {
+		gspca_err(gspca_dev, "STV(e): camera ping failed!!\n");
+		return stv0680_handle_error(gspca_dev, -ENODEV);
+	}
+
+	/* get camera descriptor */
+	if (stv_sndctrl(gspca_dev, 2, 0x06, 0x0200, 0x09) != 0x09)
+		return stv0680_handle_error(gspca_dev, -ENODEV);
+
+	if (stv_sndctrl(gspca_dev, 2, 0x06, 0x0200, 0x22) != 0x22 ||
+	    gspca_dev->usb_buf[7] != 0xa0 || gspca_dev->usb_buf[8] != 0x23) {
+		gspca_err(gspca_dev, "Could not get descriptor 0200\n");
+		return stv0680_handle_error(gspca_dev, -ENODEV);
+	}
+	if (stv_sndctrl(gspca_dev, 0, 0x8a, 0, 0x02) != 0x02)
+		return stv0680_handle_error(gspca_dev, -ENODEV);
+	if (stv_sndctrl(gspca_dev, 0, 0x8b, 0, 0x24) != 0x24)
+		return stv0680_handle_error(gspca_dev, -ENODEV);
+	if (stv_sndctrl(gspca_dev, 0, 0x85, 0, 0x10) != 0x10)
+		return stv0680_handle_error(gspca_dev, -ENODEV);
+
+	if (!(gspca_dev->usb_buf[7] & 0x09)) {
+		gspca_err(gspca_dev, "Camera supports neither CIF nor QVGA mode\n");
+		return -ENODEV;
+	}
+	if (gspca_dev->usb_buf[7] & 0x01)
+		gspca_dbg(gspca_dev, D_PROBE, "Camera supports CIF mode\n");
+	if (gspca_dev->usb_buf[7] & 0x02)
+		gspca_dbg(gspca_dev, D_PROBE, "Camera supports VGA mode\n");
+	if (gspca_dev->usb_buf[7] & 0x04)
+		gspca_dbg(gspca_dev, D_PROBE, "Camera supports QCIF mode\n");
+	if (gspca_dev->usb_buf[7] & 0x08)
+		gspca_dbg(gspca_dev, D_PROBE, "Camera supports QVGA mode\n");
+
+	if (gspca_dev->usb_buf[7] & 0x01)
+		sd->video_mode = 0x00; /* CIF */
+	else
+		sd->video_mode = 0x03; /* QVGA */
+
+	/* FW rev, ASIC rev, sensor ID  */
+	gspca_dbg(gspca_dev, D_PROBE, "Firmware rev is %i.%i\n",
+		  gspca_dev->usb_buf[0], gspca_dev->usb_buf[1]);
+	gspca_dbg(gspca_dev, D_PROBE, "ASIC rev is %i.%i",
+		  gspca_dev->usb_buf[2], gspca_dev->usb_buf[3]);
+	gspca_dbg(gspca_dev, D_PROBE, "Sensor ID is %i",
+		  (gspca_dev->usb_buf[4]*16) + (gspca_dev->usb_buf[5]>>4));
+
+
+	ret = stv0680_get_video_mode(gspca_dev);
+	if (ret < 0)
+		return ret;
+	sd->current_mode = sd->orig_mode = ret;
+
+	ret = stv0680_set_video_mode(gspca_dev, sd->video_mode);
+	if (ret < 0)
+		return ret;
+
+	/* Get mode details */
+	if (stv_sndctrl(gspca_dev, 0, 0x8f, 0, 0x10) != 0x10)
+		return stv0680_handle_error(gspca_dev, -EIO);
+
+	cam->bulk = 1;
+	cam->bulk_nurbs = 1; /* The cam cannot handle more */
+	cam->bulk_size = (gspca_dev->usb_buf[0] << 24) |
+			 (gspca_dev->usb_buf[1] << 16) |
+			 (gspca_dev->usb_buf[2] << 8) |
+			 (gspca_dev->usb_buf[3]);
+	sd->mode.width = (gspca_dev->usb_buf[4] << 8) |
+			 (gspca_dev->usb_buf[5]);  /* 322, 356, 644 */
+	sd->mode.height = (gspca_dev->usb_buf[6] << 8) |
+			  (gspca_dev->usb_buf[7]); /* 242, 292, 484 */
+	sd->mode.pixelformat = V4L2_PIX_FMT_STV0680;
+	sd->mode.field = V4L2_FIELD_NONE;
+	sd->mode.bytesperline = sd->mode.width;
+	sd->mode.sizeimage = cam->bulk_size;
+	sd->mode.colorspace = V4L2_COLORSPACE_SRGB;
+
+	/* origGain = gspca_dev->usb_buf[12]; */
+
+	cam->cam_mode = &sd->mode;
+	cam->nmodes = 1;
+
+
+	ret = stv0680_set_video_mode(gspca_dev, sd->orig_mode);
+	if (ret < 0)
+		return ret;
+
+	if (stv_sndctrl(gspca_dev, 2, 0x06, 0x0100, 0x12) != 0x12 ||
+	    gspca_dev->usb_buf[8] != 0x53 || gspca_dev->usb_buf[9] != 0x05) {
+		pr_err("Could not get descriptor 0100\n");
+		return stv0680_handle_error(gspca_dev, -EIO);
+	}
+
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	return 0;
+}
+
+/* -- start the camera -- */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	int ret;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	ret = stv0680_set_video_mode(gspca_dev, sd->video_mode);
+	if (ret < 0)
+		return ret;
+
+	if (stv_sndctrl(gspca_dev, 0, 0x85, 0, 0x10) != 0x10)
+		return stv0680_handle_error(gspca_dev, -EIO);
+
+	/* Start stream at:
+	   0x0000 = CIF (352x288)
+	   0x0100 = VGA (640x480)
+	   0x0300 = QVGA (320x240) */
+	if (stv_sndctrl(gspca_dev, 1, 0x09, sd->video_mode << 8, 0x0) != 0x0)
+		return stv0680_handle_error(gspca_dev, -EIO);
+
+	return 0;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	/* This is a high priority command; it stops all lower order cmds */
+	if (stv_sndctrl(gspca_dev, 1, 0x04, 0x0000, 0x0) != 0x0)
+		stv0680_handle_error(gspca_dev, -EIO);
+}
+
+static void sd_stop0(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (!sd->gspca_dev.present)
+		return;
+
+	stv0680_set_video_mode(gspca_dev, sd->orig_mode);
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,
+			int len)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	/* Every now and then the camera sends a 16 byte packet, no idea
+	   what it contains, but it is not image data, when this
+	   happens the frame received before this packet is corrupt,
+	   so discard it. */
+	if (len != sd->mode.sizeimage) {
+		gspca_dev->last_packet_type = DISCARD_PACKET;
+		return;
+	}
+
+	/* Finish the previous frame, we do this upon reception of the next
+	   packet, even though it is already complete so that the strange 16
+	   byte packets send after a corrupt frame can discard it. */
+	gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
+
+	/* Store the just received frame */
+	gspca_frame_add(gspca_dev, FIRST_PACKET, data, len);
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.stop0 = sd_stop0,
+	.pkt_scan = sd_pkt_scan,
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x0553, 0x0202)},
+	{USB_DEVICE(0x041e, 0x4007)},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/stv06xx/Kconfig b/drivers/media/usb/gspca/stv06xx/Kconfig
new file mode 100644
index 0000000..634ad38
--- /dev/null
+++ b/drivers/media/usb/gspca/stv06xx/Kconfig
@@ -0,0 +1,9 @@
+config USB_STV06XX
+	tristate "STV06XX USB Camera Driver"
+	depends on USB_GSPCA
+	help
+	  Say Y here if you want support for cameras based on
+	  the ST STV06XX chip.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called gspca_stv06xx.
diff --git a/drivers/media/usb/gspca/stv06xx/Makefile b/drivers/media/usb/gspca/stv06xx/Makefile
new file mode 100644
index 0000000..c4d7206
--- /dev/null
+++ b/drivers/media/usb/gspca/stv06xx/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_USB_STV06XX) += gspca_stv06xx.o
+
+gspca_stv06xx-objs := stv06xx.o \
+		      stv06xx_vv6410.o \
+		      stv06xx_hdcs.o \
+		      stv06xx_pb0100.o \
+		      stv06xx_st6422.o
+
+ccflags-y += -I$(srctree)/drivers/media/usb/gspca
+
diff --git a/drivers/media/usb/gspca/stv06xx/stv06xx.c b/drivers/media/usb/gspca/stv06xx/stv06xx.c
new file mode 100644
index 0000000..6080a35
--- /dev/null
+++ b/drivers/media/usb/gspca/stv06xx/stv06xx.c
@@ -0,0 +1,630 @@
+/*
+ * Copyright (c) 2001 Jean-Fredric Clere, Nikolas Zimmermann, Georg Acher
+ *		      Mark Cave-Ayland, Carlo E Prelz, Dick Streefland
+ * Copyright (c) 2002, 2003 Tuukka Toivonen
+ * Copyright (c) 2008 Erik Andrén
+ *
+ * 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.
+ *
+ * P/N 861037:      Sensor HDCS1000        ASIC STV0600
+ * P/N 861050-0010: Sensor HDCS1000        ASIC STV0600
+ * P/N 861050-0020: Sensor Photobit PB100  ASIC STV0600-1 - QuickCam Express
+ * P/N 861055:      Sensor ST VV6410       ASIC STV0610   - LEGO cam
+ * P/N 861075-0040: Sensor HDCS1000        ASIC
+ * P/N 961179-0700: Sensor ST VV6410       ASIC STV0602   - Dexxa WebCam USB
+ * P/N 861040-0000: Sensor ST VV6410       ASIC STV0610   - QuickCam Web
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/input.h>
+#include "stv06xx_sensor.h"
+
+MODULE_AUTHOR("Erik Andrén");
+MODULE_DESCRIPTION("STV06XX USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+static bool dump_bridge;
+static bool dump_sensor;
+
+int stv06xx_write_bridge(struct sd *sd, u16 address, u16 i2c_data)
+{
+	int err;
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	struct usb_device *udev = sd->gspca_dev.dev;
+	__u8 *buf = sd->gspca_dev.usb_buf;
+
+	u8 len = (i2c_data > 0xff) ? 2 : 1;
+
+	buf[0] = i2c_data & 0xff;
+	buf[1] = (i2c_data >> 8) & 0xff;
+
+	err = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+			      0x04, 0x40, address, 0, buf, len,
+			      STV06XX_URB_MSG_TIMEOUT);
+
+	gspca_dbg(gspca_dev, D_CONF, "Written 0x%x to address 0x%x, status: %d\n",
+		  i2c_data, address, err);
+
+	return (err < 0) ? err : 0;
+}
+
+int stv06xx_read_bridge(struct sd *sd, u16 address, u8 *i2c_data)
+{
+	int err;
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	struct usb_device *udev = sd->gspca_dev.dev;
+	__u8 *buf = sd->gspca_dev.usb_buf;
+
+	err = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+			      0x04, 0xc0, address, 0, buf, 1,
+			      STV06XX_URB_MSG_TIMEOUT);
+
+	*i2c_data = buf[0];
+
+	gspca_dbg(gspca_dev, D_CONF, "Reading 0x%x from address 0x%x, status %d\n",
+		  *i2c_data, address, err);
+
+	return (err < 0) ? err : 0;
+}
+
+/* Wraps the normal write sensor bytes / words functions for writing a
+   single value */
+int stv06xx_write_sensor(struct sd *sd, u8 address, u16 value)
+{
+	if (sd->sensor->i2c_len == 2) {
+		u16 data[2] = { address, value };
+		return stv06xx_write_sensor_words(sd, data, 1);
+	} else {
+		u8 data[2] = { address, value };
+		return stv06xx_write_sensor_bytes(sd, data, 1);
+	}
+}
+
+static int stv06xx_write_sensor_finish(struct sd *sd)
+{
+	int err = 0;
+
+	if (sd->bridge == BRIDGE_STV610) {
+		struct usb_device *udev = sd->gspca_dev.dev;
+		__u8 *buf = sd->gspca_dev.usb_buf;
+
+		buf[0] = 0;
+		err = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+				      0x04, 0x40, 0x1704, 0, buf, 1,
+				      STV06XX_URB_MSG_TIMEOUT);
+	}
+
+	return (err < 0) ? err : 0;
+}
+
+int stv06xx_write_sensor_bytes(struct sd *sd, const u8 *data, u8 len)
+{
+	int err, i, j;
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	struct usb_device *udev = sd->gspca_dev.dev;
+	__u8 *buf = sd->gspca_dev.usb_buf;
+
+	gspca_dbg(gspca_dev, D_CONF, "I2C: Command buffer contains %d entries\n",
+		  len);
+	for (i = 0; i < len;) {
+		/* Build the command buffer */
+		memset(buf, 0, I2C_BUFFER_LENGTH);
+		for (j = 0; j < I2C_MAX_BYTES && i < len; j++, i++) {
+			buf[j] = data[2*i];
+			buf[0x10 + j] = data[2*i+1];
+			gspca_dbg(gspca_dev, D_CONF, "I2C: Writing 0x%02x to reg 0x%02x\n",
+				  data[2*i+1], data[2*i]);
+		}
+		buf[0x20] = sd->sensor->i2c_addr;
+		buf[0x21] = j - 1; /* Number of commands to send - 1 */
+		buf[0x22] = I2C_WRITE_CMD;
+		err = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+				      0x04, 0x40, 0x0400, 0, buf,
+				      I2C_BUFFER_LENGTH,
+				      STV06XX_URB_MSG_TIMEOUT);
+		if (err < 0)
+			return err;
+	}
+	return stv06xx_write_sensor_finish(sd);
+}
+
+int stv06xx_write_sensor_words(struct sd *sd, const u16 *data, u8 len)
+{
+	int err, i, j;
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	struct usb_device *udev = sd->gspca_dev.dev;
+	__u8 *buf = sd->gspca_dev.usb_buf;
+
+	gspca_dbg(gspca_dev, D_CONF, "I2C: Command buffer contains %d entries\n",
+		  len);
+
+	for (i = 0; i < len;) {
+		/* Build the command buffer */
+		memset(buf, 0, I2C_BUFFER_LENGTH);
+		for (j = 0; j < I2C_MAX_WORDS && i < len; j++, i++) {
+			buf[j] = data[2*i];
+			buf[0x10 + j * 2] = data[2*i+1];
+			buf[0x10 + j * 2 + 1] = data[2*i+1] >> 8;
+			gspca_dbg(gspca_dev, D_CONF, "I2C: Writing 0x%04x to reg 0x%02x\n",
+				  data[2*i+1], data[2*i]);
+		}
+		buf[0x20] = sd->sensor->i2c_addr;
+		buf[0x21] = j - 1; /* Number of commands to send - 1 */
+		buf[0x22] = I2C_WRITE_CMD;
+		err = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+				0x04, 0x40, 0x0400, 0, buf,
+				I2C_BUFFER_LENGTH,
+				STV06XX_URB_MSG_TIMEOUT);
+		if (err < 0)
+			return err;
+	}
+	return stv06xx_write_sensor_finish(sd);
+}
+
+int stv06xx_read_sensor(struct sd *sd, const u8 address, u16 *value)
+{
+	int err;
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	struct usb_device *udev = sd->gspca_dev.dev;
+	__u8 *buf = sd->gspca_dev.usb_buf;
+
+	err = stv06xx_write_bridge(sd, STV_I2C_FLUSH, sd->sensor->i2c_flush);
+	if (err < 0)
+		return err;
+
+	/* Clear mem */
+	memset(buf, 0, I2C_BUFFER_LENGTH);
+
+	buf[0] = address;
+	buf[0x20] = sd->sensor->i2c_addr;
+	buf[0x21] = 0;
+
+	/* Read I2C register */
+	buf[0x22] = I2C_READ_CMD;
+
+	err = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+			      0x04, 0x40, 0x1400, 0, buf, I2C_BUFFER_LENGTH,
+			      STV06XX_URB_MSG_TIMEOUT);
+	if (err < 0) {
+		pr_err("I2C: Read error writing address: %d\n", err);
+		return err;
+	}
+
+	err = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+			      0x04, 0xc0, 0x1410, 0, buf, sd->sensor->i2c_len,
+			      STV06XX_URB_MSG_TIMEOUT);
+	if (sd->sensor->i2c_len == 2)
+		*value = buf[0] | (buf[1] << 8);
+	else
+		*value = buf[0];
+
+	gspca_dbg(gspca_dev, D_CONF, "I2C: Read 0x%x from address 0x%x, status: %d\n",
+		  *value, address, err);
+
+	return (err < 0) ? err : 0;
+}
+
+/* Dumps all bridge registers */
+static void stv06xx_dump_bridge(struct sd *sd)
+{
+	int i;
+	u8 data, buf;
+
+	pr_info("Dumping all stv06xx bridge registers\n");
+	for (i = 0x1400; i < 0x160f; i++) {
+		stv06xx_read_bridge(sd, i, &data);
+
+		pr_info("Read 0x%x from address 0x%x\n", data, i);
+	}
+
+	pr_info("Testing stv06xx bridge registers for writability\n");
+	for (i = 0x1400; i < 0x160f; i++) {
+		stv06xx_read_bridge(sd, i, &data);
+		buf = data;
+
+		stv06xx_write_bridge(sd, i, 0xff);
+		stv06xx_read_bridge(sd, i, &data);
+		if (data == 0xff)
+			pr_info("Register 0x%x is read/write\n", i);
+		else if (data != buf)
+			pr_info("Register 0x%x is read/write, but only partially\n",
+				i);
+		else
+			pr_info("Register 0x%x is read-only\n", i);
+
+		stv06xx_write_bridge(sd, i, buf);
+	}
+}
+
+/* this function is called at probe and resume time */
+static int stv06xx_init(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int err;
+
+	gspca_dbg(gspca_dev, D_PROBE, "Initializing camera\n");
+
+	/* Let the usb init settle for a bit
+	   before performing the initialization */
+	msleep(250);
+
+	err = sd->sensor->init(sd);
+
+	if (dump_sensor && sd->sensor->dump)
+		sd->sensor->dump(sd);
+
+	return (err < 0) ? err : 0;
+}
+
+/* this function is called at probe time */
+static int stv06xx_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	gspca_dbg(gspca_dev, D_PROBE, "Initializing controls\n");
+
+	gspca_dev->vdev.ctrl_handler = &gspca_dev->ctrl_handler;
+	return sd->sensor->init_controls(sd);
+}
+
+/* Start the camera */
+static int stv06xx_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct usb_host_interface *alt;
+	struct usb_interface *intf;
+	int err, packet_size;
+
+	intf = usb_ifnum_to_if(sd->gspca_dev.dev, sd->gspca_dev.iface);
+	alt = usb_altnum_to_altsetting(intf, sd->gspca_dev.alt);
+	if (!alt) {
+		gspca_err(gspca_dev, "Couldn't get altsetting\n");
+		return -EIO;
+	}
+
+	packet_size = le16_to_cpu(alt->endpoint[0].desc.wMaxPacketSize);
+	err = stv06xx_write_bridge(sd, STV_ISO_SIZE_L, packet_size);
+	if (err < 0)
+		return err;
+
+	/* Prepare the sensor for start */
+	err = sd->sensor->start(sd);
+	if (err < 0)
+		goto out;
+
+	/* Start isochronous streaming */
+	err = stv06xx_write_bridge(sd, STV_ISO_ENABLE, 1);
+
+out:
+	if (err < 0)
+		gspca_dbg(gspca_dev, D_STREAM, "Starting stream failed\n");
+	else
+		gspca_dbg(gspca_dev, D_STREAM, "Started streaming\n");
+
+	return (err < 0) ? err : 0;
+}
+
+static int stv06xx_isoc_init(struct gspca_dev *gspca_dev)
+{
+	struct usb_host_interface *alt;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	/* Start isoc bandwidth "negotiation" at max isoc bandwidth */
+	alt = &gspca_dev->dev->actconfig->intf_cache[0]->altsetting[1];
+	alt->endpoint[0].desc.wMaxPacketSize =
+		cpu_to_le16(sd->sensor->max_packet_size[gspca_dev->curr_mode]);
+
+	return 0;
+}
+
+static int stv06xx_isoc_nego(struct gspca_dev *gspca_dev)
+{
+	int ret, packet_size, min_packet_size;
+	struct usb_host_interface *alt;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	alt = &gspca_dev->dev->actconfig->intf_cache[0]->altsetting[1];
+	packet_size = le16_to_cpu(alt->endpoint[0].desc.wMaxPacketSize);
+	min_packet_size = sd->sensor->min_packet_size[gspca_dev->curr_mode];
+	if (packet_size <= min_packet_size)
+		return -EIO;
+
+	packet_size -= 100;
+	if (packet_size < min_packet_size)
+		packet_size = min_packet_size;
+	alt->endpoint[0].desc.wMaxPacketSize = cpu_to_le16(packet_size);
+
+	ret = usb_set_interface(gspca_dev->dev, gspca_dev->iface, 1);
+	if (ret < 0)
+		gspca_err(gspca_dev, "set alt 1 err %d\n", ret);
+
+	return ret;
+}
+
+static void stv06xx_stopN(struct gspca_dev *gspca_dev)
+{
+	int err;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	/* stop ISO-streaming */
+	err = stv06xx_write_bridge(sd, STV_ISO_ENABLE, 0);
+	if (err < 0)
+		goto out;
+
+	err = sd->sensor->stop(sd);
+
+out:
+	if (err < 0)
+		gspca_dbg(gspca_dev, D_STREAM, "Failed to stop stream\n");
+	else
+		gspca_dbg(gspca_dev, D_STREAM, "Stopped streaming\n");
+}
+
+/*
+ * Analyse an USB packet of the data stream and store it appropriately.
+ * Each packet contains an integral number of chunks. Each chunk has
+ * 2-bytes identification, followed by 2-bytes that describe the chunk
+ * length. Known/guessed chunk identifications are:
+ * 8001/8005/C001/C005 - Begin new frame
+ * 8002/8006/C002/C006 - End frame
+ * 0200/4200           - Contains actual image data, bayer or compressed
+ * 0005                - 11 bytes of unknown data
+ * 0100                - 2 bytes of unknown data
+ * The 0005 and 0100 chunks seem to appear only in compressed stream.
+ */
+static void stv06xx_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	gspca_dbg(gspca_dev, D_PACK, "Packet of length %d arrived\n", len);
+
+	/* A packet may contain several frames
+	   loop until the whole packet is reached */
+	while (len) {
+		int id, chunk_len;
+
+		if (len < 4) {
+			gspca_dbg(gspca_dev, D_PACK, "Packet is smaller than 4 bytes\n");
+			return;
+		}
+
+		/* Capture the id */
+		id = (data[0] << 8) | data[1];
+
+		/* Capture the chunk length */
+		chunk_len = (data[2] << 8) | data[3];
+		gspca_dbg(gspca_dev, D_PACK, "Chunk id: %x, length: %d\n",
+			  id, chunk_len);
+
+		data += 4;
+		len -= 4;
+
+		if (len < chunk_len) {
+			gspca_err(gspca_dev, "URB packet length is smaller than the specified chunk length\n");
+			gspca_dev->last_packet_type = DISCARD_PACKET;
+			return;
+		}
+
+		/* First byte seem to be 02=data 2nd byte is unknown??? */
+		if (sd->bridge == BRIDGE_ST6422 && (id & 0xff00) == 0x0200)
+			goto frame_data;
+
+		switch (id) {
+		case 0x0200:
+		case 0x4200:
+frame_data:
+			gspca_dbg(gspca_dev, D_PACK, "Frame data packet detected\n");
+
+			if (sd->to_skip) {
+				int skip = (sd->to_skip < chunk_len) ?
+					    sd->to_skip : chunk_len;
+				data += skip;
+				len -= skip;
+				chunk_len -= skip;
+				sd->to_skip -= skip;
+			}
+
+			gspca_frame_add(gspca_dev, INTER_PACKET,
+					data, chunk_len);
+			break;
+
+		case 0x8001:
+		case 0x8005:
+		case 0xc001:
+		case 0xc005:
+			gspca_dbg(gspca_dev, D_PACK, "Starting new frame\n");
+
+			/* Create a new frame, chunk length should be zero */
+			gspca_frame_add(gspca_dev, FIRST_PACKET,
+					NULL, 0);
+
+			if (sd->bridge == BRIDGE_ST6422)
+				sd->to_skip = gspca_dev->pixfmt.width * 4;
+
+			if (chunk_len)
+				gspca_err(gspca_dev, "Chunk length is non-zero on a SOF\n");
+			break;
+
+		case 0x8002:
+		case 0x8006:
+		case 0xc002:
+			gspca_dbg(gspca_dev, D_PACK, "End of frame detected\n");
+
+			/* Complete the last frame (if any) */
+			gspca_frame_add(gspca_dev, LAST_PACKET,
+					NULL, 0);
+
+			if (chunk_len)
+				gspca_err(gspca_dev, "Chunk length is non-zero on a EOF\n");
+			break;
+
+		case 0x0005:
+			gspca_dbg(gspca_dev, D_PACK, "Chunk 0x005 detected\n");
+			/* Unknown chunk with 11 bytes of data,
+			   occurs just before end of each frame
+			   in compressed mode */
+			break;
+
+		case 0x0100:
+			gspca_dbg(gspca_dev, D_PACK, "Chunk 0x0100 detected\n");
+			/* Unknown chunk with 2 bytes of data,
+			   occurs 2-3 times per USB interrupt */
+			break;
+		case 0x42ff:
+			gspca_dbg(gspca_dev, D_PACK, "Chunk 0x42ff detected\n");
+			/* Special chunk seen sometimes on the ST6422 */
+			break;
+		default:
+			gspca_dbg(gspca_dev, D_PACK, "Unknown chunk 0x%04x detected\n",
+				  id);
+			/* Unknown chunk */
+		}
+		data    += chunk_len;
+		len     -= chunk_len;
+	}
+}
+
+#if IS_ENABLED(CONFIG_INPUT)
+static int sd_int_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,		/* interrupt packet data */
+			int len)		/* interrupt packet length */
+{
+	int ret = -EINVAL;
+
+	if (len == 1 && (data[0] == 0x80 || data[0] == 0x10)) {
+		input_report_key(gspca_dev->input_dev, KEY_CAMERA, 1);
+		input_sync(gspca_dev->input_dev);
+		ret = 0;
+	}
+
+	if (len == 1 && (data[0] == 0x88 || data[0] == 0x11)) {
+		input_report_key(gspca_dev->input_dev, KEY_CAMERA, 0);
+		input_sync(gspca_dev->input_dev);
+		ret = 0;
+	}
+
+	return ret;
+}
+#endif
+
+static int stv06xx_config(struct gspca_dev *gspca_dev,
+			  const struct usb_device_id *id);
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = stv06xx_config,
+	.init = stv06xx_init,
+	.init_controls = stv06xx_init_controls,
+	.start = stv06xx_start,
+	.stopN = stv06xx_stopN,
+	.pkt_scan = stv06xx_pkt_scan,
+	.isoc_init = stv06xx_isoc_init,
+	.isoc_nego = stv06xx_isoc_nego,
+#if IS_ENABLED(CONFIG_INPUT)
+	.int_pkt_scan = sd_int_pkt_scan,
+#endif
+};
+
+/* This function is called at probe time */
+static int stv06xx_config(struct gspca_dev *gspca_dev,
+			  const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	gspca_dbg(gspca_dev, D_PROBE, "Configuring camera\n");
+
+	sd->bridge = id->driver_info;
+	gspca_dev->sd_desc = &sd_desc;
+
+	if (dump_bridge)
+		stv06xx_dump_bridge(sd);
+
+	sd->sensor = &stv06xx_sensor_st6422;
+	if (!sd->sensor->probe(sd))
+		return 0;
+
+	sd->sensor = &stv06xx_sensor_vv6410;
+	if (!sd->sensor->probe(sd))
+		return 0;
+
+	sd->sensor = &stv06xx_sensor_hdcs1x00;
+	if (!sd->sensor->probe(sd))
+		return 0;
+
+	sd->sensor = &stv06xx_sensor_hdcs1020;
+	if (!sd->sensor->probe(sd))
+		return 0;
+
+	sd->sensor = &stv06xx_sensor_pb0100;
+	if (!sd->sensor->probe(sd))
+		return 0;
+
+	sd->sensor = NULL;
+	return -ENODEV;
+}
+
+
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x046d, 0x0840), .driver_info = BRIDGE_STV600 },	/* QuickCam Express */
+	{USB_DEVICE(0x046d, 0x0850), .driver_info = BRIDGE_STV610 },	/* LEGO cam / QuickCam Web */
+	{USB_DEVICE(0x046d, 0x0870), .driver_info = BRIDGE_STV602 },	/* Dexxa WebCam USB */
+	{USB_DEVICE(0x046D, 0x08F0), .driver_info = BRIDGE_ST6422 },	/* QuickCam Messenger */
+	{USB_DEVICE(0x046D, 0x08F5), .driver_info = BRIDGE_ST6422 },	/* QuickCam Communicate */
+	{USB_DEVICE(0x046D, 0x08F6), .driver_info = BRIDGE_ST6422 },	/* QuickCam Messenger (new) */
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+			       THIS_MODULE);
+}
+
+static void sd_disconnect(struct usb_interface *intf)
+{
+	struct gspca_dev *gspca_dev = usb_get_intfdata(intf);
+	struct sd *sd = (struct sd *) gspca_dev;
+	void *priv = sd->sensor_priv;
+	gspca_dbg(gspca_dev, D_PROBE, "Disconnecting the stv06xx device\n");
+
+	sd->sensor = NULL;
+	gspca_disconnect(intf);
+	kfree(priv);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = sd_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
+
+module_param(dump_bridge, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(dump_bridge, "Dumps all usb bridge registers at startup");
+
+module_param(dump_sensor, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(dump_sensor, "Dumps all sensor registers at startup");
diff --git a/drivers/media/usb/gspca/stv06xx/stv06xx.h b/drivers/media/usb/gspca/stv06xx/stv06xx.h
new file mode 100644
index 0000000..4801867
--- /dev/null
+++ b/drivers/media/usb/gspca/stv06xx/stv06xx.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2001 Jean-Fredric Clere, Nikolas Zimmermann, Georg Acher
+ *		      Mark Cave-Ayland, Carlo E Prelz, Dick Streefland
+ * Copyright (c) 2002, 2003 Tuukka Toivonen
+ * Copyright (c) 2008 Erik Andrén
+ *
+ * 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.
+ *
+ * P/N 861037:      Sensor HDCS1000        ASIC STV0600
+ * P/N 861050-0010: Sensor HDCS1000        ASIC STV0600
+ * P/N 861050-0020: Sensor Photobit PB100  ASIC STV0600-1 - QuickCam Express
+ * P/N 861055:      Sensor ST VV6410       ASIC STV0610   - LEGO cam
+ * P/N 861075-0040: Sensor HDCS1000        ASIC
+ * P/N 961179-0700: Sensor ST VV6410       ASIC STV0602   - Dexxa WebCam USB
+ * P/N 861040-0000: Sensor ST VV6410       ASIC STV0610   - QuickCam Web
+ */
+
+#ifndef STV06XX_H_
+#define STV06XX_H_
+
+#include <linux/slab.h>
+#include "gspca.h"
+
+#define MODULE_NAME "STV06xx"
+
+#define STV_ISOC_ENDPOINT_ADDR		0x81
+
+#define STV_R                           0x0509
+
+#define STV_REG23			0x0423
+
+/* Control registers of the STV0600 ASIC */
+#define STV_I2C_PARTNER			0x1420
+#define STV_I2C_VAL_REG_VAL_PAIRS_MIN1	0x1421
+#define STV_I2C_READ_WRITE_TOGGLE	0x1422
+#define STV_I2C_FLUSH			0x1423
+#define STV_I2C_SUCC_READ_REG_VALS	0x1424
+
+#define STV_ISO_ENABLE			0x1440
+#define STV_SCAN_RATE			0x1443
+#define STV_LED_CTRL			0x1445
+#define STV_STV0600_EMULATION		0x1446
+#define STV_REG00			0x1500
+#define STV_REG01			0x1501
+#define STV_REG02			0x1502
+#define STV_REG03			0x1503
+#define STV_REG04			0x1504
+
+#define STV_ISO_SIZE_L			0x15c1
+#define STV_ISO_SIZE_H			0x15c2
+
+/* Refers to the CIF 352x288 and QCIF 176x144 */
+/* 1: 288 lines, 2: 144 lines */
+#define STV_Y_CTRL			0x15c3
+
+#define STV_RESET                       0x1620
+
+/* 0xa: 352 columns, 0x6: 176 columns */
+#define STV_X_CTRL			0x1680
+
+#define STV06XX_URB_MSG_TIMEOUT		5000
+
+#define I2C_MAX_BYTES			16
+#define I2C_MAX_WORDS			8
+
+#define I2C_BUFFER_LENGTH		0x23
+#define I2C_READ_CMD			3
+#define I2C_WRITE_CMD			1
+
+#define LED_ON				1
+#define LED_OFF				0
+
+/* STV06xx device descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;
+
+	/* A pointer to the currently connected sensor */
+	const struct stv06xx_sensor *sensor;
+
+	/* Sensor private data */
+	void *sensor_priv;
+
+	/* The first 4 lines produced by the stv6422 are no good, this keeps
+	   track of how many bytes we still need to skip during a frame */
+	int to_skip;
+
+	/* Bridge / Camera type */
+	u8 bridge;
+	#define BRIDGE_STV600 0
+	#define BRIDGE_STV602 1
+	#define BRIDGE_STV610 2
+	#define BRIDGE_ST6422 3 /* With integrated sensor */
+};
+
+int stv06xx_write_bridge(struct sd *sd, u16 address, u16 i2c_data);
+int stv06xx_read_bridge(struct sd *sd, u16 address, u8 *i2c_data);
+
+int stv06xx_write_sensor_bytes(struct sd *sd, const u8 *data, u8 len);
+int stv06xx_write_sensor_words(struct sd *sd, const u16 *data, u8 len);
+
+int stv06xx_read_sensor(struct sd *sd, const u8 address, u16 *value);
+int stv06xx_write_sensor(struct sd *sd, u8 address, u16 value);
+
+#endif
diff --git a/drivers/media/usb/gspca/stv06xx/stv06xx_hdcs.c b/drivers/media/usb/gspca/stv06xx/stv06xx_hdcs.c
new file mode 100644
index 0000000..d8db2c8
--- /dev/null
+++ b/drivers/media/usb/gspca/stv06xx/stv06xx_hdcs.c
@@ -0,0 +1,540 @@
+/*
+ * Copyright (c) 2001 Jean-Fredric Clere, Nikolas Zimmermann, Georg Acher
+ *		      Mark Cave-Ayland, Carlo E Prelz, Dick Streefland
+ * Copyright (c) 2002, 2003 Tuukka Toivonen
+ * Copyright (c) 2008 Erik Andrén
+ * Copyright (c) 2008 Chia-I Wu
+ *
+ * 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.
+ *
+ * P/N 861037:      Sensor HDCS1000        ASIC STV0600
+ * P/N 861050-0010: Sensor HDCS1000        ASIC STV0600
+ * P/N 861050-0020: Sensor Photobit PB100  ASIC STV0600-1 - QuickCam Express
+ * P/N 861055:      Sensor ST VV6410       ASIC STV0610   - LEGO cam
+ * P/N 861075-0040: Sensor HDCS1000        ASIC
+ * P/N 961179-0700: Sensor ST VV6410       ASIC STV0602   - Dexxa WebCam USB
+ * P/N 861040-0000: Sensor ST VV6410       ASIC STV0610   - QuickCam Web
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include "stv06xx_hdcs.h"
+
+static struct v4l2_pix_format hdcs1x00_mode[] = {
+	{
+		HDCS_1X00_DEF_WIDTH,
+		HDCS_1X00_DEF_HEIGHT,
+		V4L2_PIX_FMT_SGRBG8,
+		V4L2_FIELD_NONE,
+		.sizeimage =
+			HDCS_1X00_DEF_WIDTH * HDCS_1X00_DEF_HEIGHT,
+		.bytesperline = HDCS_1X00_DEF_WIDTH,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1
+	}
+};
+
+static struct v4l2_pix_format hdcs1020_mode[] = {
+	{
+		HDCS_1020_DEF_WIDTH,
+		HDCS_1020_DEF_HEIGHT,
+		V4L2_PIX_FMT_SGRBG8,
+		V4L2_FIELD_NONE,
+		.sizeimage =
+			HDCS_1020_DEF_WIDTH * HDCS_1020_DEF_HEIGHT,
+		.bytesperline = HDCS_1020_DEF_WIDTH,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1
+	}
+};
+
+enum hdcs_power_state {
+	HDCS_STATE_SLEEP,
+	HDCS_STATE_IDLE,
+	HDCS_STATE_RUN
+};
+
+/* no lock? */
+struct hdcs {
+	enum hdcs_power_state state;
+	int w, h;
+
+	/* visible area of the sensor array */
+	struct {
+		int left, top;
+		int width, height;
+		int border;
+	} array;
+
+	struct {
+		/* Column timing overhead */
+		u8 cto;
+		/* Column processing overhead */
+		u8 cpo;
+		/* Row sample period constant */
+		u16 rs;
+		/* Exposure reset duration */
+		u16 er;
+	} exp;
+
+	int psmp;
+};
+
+static int hdcs_reg_write_seq(struct sd *sd, u8 reg, u8 *vals, u8 len)
+{
+	u8 regs[I2C_MAX_BYTES * 2];
+	int i;
+
+	if (unlikely((len <= 0) || (len >= I2C_MAX_BYTES) ||
+		     (reg + len > 0xff)))
+		return -EINVAL;
+
+	for (i = 0; i < len; i++) {
+		regs[2 * i] = reg;
+		regs[2 * i + 1] = vals[i];
+		/* All addresses are shifted left one bit
+		 * as bit 0 toggles r/w */
+		reg += 2;
+	}
+
+	return stv06xx_write_sensor_bytes(sd, regs, len);
+}
+
+static int hdcs_set_state(struct sd *sd, enum hdcs_power_state state)
+{
+	struct hdcs *hdcs = sd->sensor_priv;
+	u8 val;
+	int ret;
+
+	if (hdcs->state == state)
+		return 0;
+
+	/* we need to go idle before running or sleeping */
+	if (hdcs->state != HDCS_STATE_IDLE) {
+		ret = stv06xx_write_sensor(sd, HDCS_REG_CONTROL(sd), 0);
+		if (ret)
+			return ret;
+	}
+
+	hdcs->state = HDCS_STATE_IDLE;
+
+	if (state == HDCS_STATE_IDLE)
+		return 0;
+
+	switch (state) {
+	case HDCS_STATE_SLEEP:
+		val = HDCS_SLEEP_MODE;
+		break;
+
+	case HDCS_STATE_RUN:
+		val = HDCS_RUN_ENABLE;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	ret = stv06xx_write_sensor(sd, HDCS_REG_CONTROL(sd), val);
+
+	/* Update the state if the write succeeded */
+	if (!ret)
+		hdcs->state = state;
+
+	return ret;
+}
+
+static int hdcs_reset(struct sd *sd)
+{
+	struct hdcs *hdcs = sd->sensor_priv;
+	int err;
+
+	err = stv06xx_write_sensor(sd, HDCS_REG_CONTROL(sd), 1);
+	if (err < 0)
+		return err;
+
+	err = stv06xx_write_sensor(sd, HDCS_REG_CONTROL(sd), 0);
+	if (err < 0)
+		hdcs->state = HDCS_STATE_IDLE;
+
+	return err;
+}
+
+static int hdcs_set_exposure(struct gspca_dev *gspca_dev, __s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct hdcs *hdcs = sd->sensor_priv;
+	int rowexp, srowexp;
+	int max_srowexp;
+	/* Column time period */
+	int ct;
+	/* Column processing period */
+	int cp;
+	/* Row processing period */
+	int rp;
+	/* Minimum number of column timing periods
+	   within the column processing period */
+	int mnct;
+	int cycles, err;
+	u8 exp[14];
+
+	cycles = val * HDCS_CLK_FREQ_MHZ * 257;
+
+	ct = hdcs->exp.cto + hdcs->psmp + (HDCS_ADC_START_SIG_DUR + 2);
+	cp = hdcs->exp.cto + (hdcs->w * ct / 2);
+
+	/* the cycles one row takes */
+	rp = hdcs->exp.rs + cp;
+
+	rowexp = cycles / rp;
+
+	/* the remaining cycles */
+	cycles -= rowexp * rp;
+
+	/* calculate sub-row exposure */
+	if (IS_1020(sd)) {
+		/* see HDCS-1020 datasheet 3.5.6.4, p. 63 */
+		srowexp = hdcs->w - (cycles + hdcs->exp.er + 13) / ct;
+
+		mnct = (hdcs->exp.er + 12 + ct - 1) / ct;
+		max_srowexp = hdcs->w - mnct;
+	} else {
+		/* see HDCS-1000 datasheet 3.4.5.5, p. 61 */
+		srowexp = cp - hdcs->exp.er - 6 - cycles;
+
+		mnct = (hdcs->exp.er + 5 + ct - 1) / ct;
+		max_srowexp = cp - mnct * ct - 1;
+	}
+
+	if (srowexp < 0)
+		srowexp = 0;
+	else if (srowexp > max_srowexp)
+		srowexp = max_srowexp;
+
+	if (IS_1020(sd)) {
+		exp[0] = HDCS20_CONTROL;
+		exp[1] = 0x00;		/* Stop streaming */
+		exp[2] = HDCS_ROWEXPL;
+		exp[3] = rowexp & 0xff;
+		exp[4] = HDCS_ROWEXPH;
+		exp[5] = rowexp >> 8;
+		exp[6] = HDCS20_SROWEXP;
+		exp[7] = (srowexp >> 2) & 0xff;
+		exp[8] = HDCS20_ERROR;
+		exp[9] = 0x10;		/* Clear exposure error flag*/
+		exp[10] = HDCS20_CONTROL;
+		exp[11] = 0x04;		/* Restart streaming */
+		err = stv06xx_write_sensor_bytes(sd, exp, 6);
+	} else {
+		exp[0] = HDCS00_CONTROL;
+		exp[1] = 0x00;         /* Stop streaming */
+		exp[2] = HDCS_ROWEXPL;
+		exp[3] = rowexp & 0xff;
+		exp[4] = HDCS_ROWEXPH;
+		exp[5] = rowexp >> 8;
+		exp[6] = HDCS00_SROWEXPL;
+		exp[7] = srowexp & 0xff;
+		exp[8] = HDCS00_SROWEXPH;
+		exp[9] = srowexp >> 8;
+		exp[10] = HDCS_STATUS;
+		exp[11] = 0x10;         /* Clear exposure error flag*/
+		exp[12] = HDCS00_CONTROL;
+		exp[13] = 0x04;         /* Restart streaming */
+		err = stv06xx_write_sensor_bytes(sd, exp, 7);
+		if (err < 0)
+			return err;
+	}
+	gspca_dbg(gspca_dev, D_CONF, "Writing exposure %d, rowexp %d, srowexp %d\n",
+		  val, rowexp, srowexp);
+	return err;
+}
+
+static int hdcs_set_gains(struct sd *sd, u8 g)
+{
+	int err;
+	u8 gains[4];
+
+	/* the voltage gain Av = (1 + 19 * val / 127) * (1 + bit7) */
+	if (g > 127)
+		g = 0x80 | (g / 2);
+
+	gains[0] = g;
+	gains[1] = g;
+	gains[2] = g;
+	gains[3] = g;
+
+	err = hdcs_reg_write_seq(sd, HDCS_ERECPGA, gains, 4);
+	return err;
+}
+
+static int hdcs_set_gain(struct gspca_dev *gspca_dev, __s32 val)
+{
+	gspca_dbg(gspca_dev, D_CONF, "Writing gain %d\n", val);
+	return hdcs_set_gains((struct sd *) gspca_dev,
+			       val & 0xff);
+}
+
+static int hdcs_set_size(struct sd *sd,
+		unsigned int width, unsigned int height)
+{
+	struct hdcs *hdcs = sd->sensor_priv;
+	u8 win[4];
+	unsigned int x, y;
+	int err;
+
+	/* must be multiple of 4 */
+	width = (width + 3) & ~0x3;
+	height = (height + 3) & ~0x3;
+
+	if (width > hdcs->array.width)
+		width = hdcs->array.width;
+
+	if (IS_1020(sd)) {
+		/* the borders are also invalid */
+		if (height + 2 * hdcs->array.border + HDCS_1020_BOTTOM_Y_SKIP
+				  > hdcs->array.height)
+			height = hdcs->array.height - 2 * hdcs->array.border -
+				HDCS_1020_BOTTOM_Y_SKIP;
+
+		y = (hdcs->array.height - HDCS_1020_BOTTOM_Y_SKIP - height) / 2
+				+ hdcs->array.top;
+	} else {
+		if (height > hdcs->array.height)
+			height = hdcs->array.height;
+
+		y = hdcs->array.top + (hdcs->array.height - height) / 2;
+	}
+
+	x = hdcs->array.left + (hdcs->array.width - width) / 2;
+
+	win[0] = y / 4;
+	win[1] = x / 4;
+	win[2] = (y + height) / 4 - 1;
+	win[3] = (x + width) / 4 - 1;
+
+	err = hdcs_reg_write_seq(sd, HDCS_FWROW, win, 4);
+	if (err < 0)
+		return err;
+
+	/* Update the current width and height */
+	hdcs->w = width;
+	hdcs->h = height;
+	return err;
+}
+
+static int hdcs_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	int err = -EINVAL;
+
+	switch (ctrl->id) {
+	case V4L2_CID_GAIN:
+		err = hdcs_set_gain(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_EXPOSURE:
+		err = hdcs_set_exposure(gspca_dev, ctrl->val);
+		break;
+	}
+	return err;
+}
+
+static const struct v4l2_ctrl_ops hdcs_ctrl_ops = {
+	.s_ctrl = hdcs_s_ctrl,
+};
+
+static int hdcs_init_controls(struct sd *sd)
+{
+	struct v4l2_ctrl_handler *hdl = &sd->gspca_dev.ctrl_handler;
+
+	v4l2_ctrl_handler_init(hdl, 2);
+	v4l2_ctrl_new_std(hdl, &hdcs_ctrl_ops,
+			V4L2_CID_EXPOSURE, 0, 0xff, 1, HDCS_DEFAULT_EXPOSURE);
+	v4l2_ctrl_new_std(hdl, &hdcs_ctrl_ops,
+			V4L2_CID_GAIN, 0, 0xff, 1, HDCS_DEFAULT_GAIN);
+	return hdl->error;
+}
+
+static int hdcs_probe_1x00(struct sd *sd)
+{
+	struct hdcs *hdcs;
+	u16 sensor;
+	int ret;
+
+	ret = stv06xx_read_sensor(sd, HDCS_IDENT, &sensor);
+	if (ret < 0 || sensor != 0x08)
+		return -ENODEV;
+
+	pr_info("HDCS-1000/1100 sensor detected\n");
+
+	sd->gspca_dev.cam.cam_mode = hdcs1x00_mode;
+	sd->gspca_dev.cam.nmodes = ARRAY_SIZE(hdcs1x00_mode);
+
+	hdcs = kmalloc(sizeof(struct hdcs), GFP_KERNEL);
+	if (!hdcs)
+		return -ENOMEM;
+
+	hdcs->array.left = 8;
+	hdcs->array.top = 8;
+	hdcs->array.width = HDCS_1X00_DEF_WIDTH;
+	hdcs->array.height = HDCS_1X00_DEF_HEIGHT;
+	hdcs->array.border = 4;
+
+	hdcs->exp.cto = 4;
+	hdcs->exp.cpo = 2;
+	hdcs->exp.rs = 186;
+	hdcs->exp.er = 100;
+
+	/*
+	 * Frame rate on HDCS-1000 with STV600 depends on PSMP:
+	 *  4 = doesn't work at all
+	 *  5 = 7.8 fps,
+	 *  6 = 6.9 fps,
+	 *  8 = 6.3 fps,
+	 * 10 = 5.5 fps,
+	 * 15 = 4.4 fps,
+	 * 31 = 2.8 fps
+	 *
+	 * Frame rate on HDCS-1000 with STV602 depends on PSMP:
+	 * 15 = doesn't work at all
+	 * 18 = doesn't work at all
+	 * 19 = 7.3 fps
+	 * 20 = 7.4 fps
+	 * 21 = 7.4 fps
+	 * 22 = 7.4 fps
+	 * 24 = 6.3 fps
+	 * 30 = 5.4 fps
+	 */
+	hdcs->psmp = (sd->bridge == BRIDGE_STV602) ? 20 : 5;
+
+	sd->sensor_priv = hdcs;
+
+	return 0;
+}
+
+static int hdcs_probe_1020(struct sd *sd)
+{
+	struct hdcs *hdcs;
+	u16 sensor;
+	int ret;
+
+	ret = stv06xx_read_sensor(sd, HDCS_IDENT, &sensor);
+	if (ret < 0 || sensor != 0x10)
+		return -ENODEV;
+
+	pr_info("HDCS-1020 sensor detected\n");
+
+	sd->gspca_dev.cam.cam_mode = hdcs1020_mode;
+	sd->gspca_dev.cam.nmodes = ARRAY_SIZE(hdcs1020_mode);
+
+	hdcs = kmalloc(sizeof(struct hdcs), GFP_KERNEL);
+	if (!hdcs)
+		return -ENOMEM;
+
+	/*
+	 * From Andrey's test image: looks like HDCS-1020 upper-left
+	 * visible pixel is at 24,8 (y maybe even smaller?) and lower-right
+	 * visible pixel at 375,299 (x maybe even larger?)
+	 */
+	hdcs->array.left = 24;
+	hdcs->array.top  = 4;
+	hdcs->array.width = HDCS_1020_DEF_WIDTH;
+	hdcs->array.height = 304;
+	hdcs->array.border = 4;
+
+	hdcs->psmp = 6;
+
+	hdcs->exp.cto = 3;
+	hdcs->exp.cpo = 3;
+	hdcs->exp.rs = 155;
+	hdcs->exp.er = 96;
+
+	sd->sensor_priv = hdcs;
+
+	return 0;
+}
+
+static int hdcs_start(struct sd *sd)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+
+	gspca_dbg(gspca_dev, D_STREAM, "Starting stream\n");
+
+	return hdcs_set_state(sd, HDCS_STATE_RUN);
+}
+
+static int hdcs_stop(struct sd *sd)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+
+	gspca_dbg(gspca_dev, D_STREAM, "Halting stream\n");
+
+	return hdcs_set_state(sd, HDCS_STATE_SLEEP);
+}
+
+static int hdcs_init(struct sd *sd)
+{
+	struct hdcs *hdcs = sd->sensor_priv;
+	int i, err = 0;
+
+	/* Set the STV0602AA in STV0600 emulation mode */
+	if (sd->bridge == BRIDGE_STV602)
+		stv06xx_write_bridge(sd, STV_STV0600_EMULATION, 1);
+
+	/* Execute the bridge init */
+	for (i = 0; i < ARRAY_SIZE(stv_bridge_init) && !err; i++) {
+		err = stv06xx_write_bridge(sd, stv_bridge_init[i][0],
+					   stv_bridge_init[i][1]);
+	}
+	if (err < 0)
+		return err;
+
+	/* sensor soft reset */
+	hdcs_reset(sd);
+
+	/* Execute the sensor init */
+	for (i = 0; i < ARRAY_SIZE(stv_sensor_init) && !err; i++) {
+		err = stv06xx_write_sensor(sd, stv_sensor_init[i][0],
+					     stv_sensor_init[i][1]);
+	}
+	if (err < 0)
+		return err;
+
+	/* Enable continuous frame capture, bit 2: stop when frame complete */
+	err = stv06xx_write_sensor(sd, HDCS_REG_CONFIG(sd), BIT(3));
+	if (err < 0)
+		return err;
+
+	/* Set PGA sample duration
+	(was 0x7E for the STV602, but caused slow framerate with HDCS-1020) */
+	if (IS_1020(sd))
+		err = stv06xx_write_sensor(sd, HDCS_TCTRL,
+				(HDCS_ADC_START_SIG_DUR << 6) | hdcs->psmp);
+	else
+		err = stv06xx_write_sensor(sd, HDCS_TCTRL,
+				(HDCS_ADC_START_SIG_DUR << 5) | hdcs->psmp);
+	if (err < 0)
+		return err;
+
+	return hdcs_set_size(sd, hdcs->array.width, hdcs->array.height);
+}
+
+static int hdcs_dump(struct sd *sd)
+{
+	u16 reg, val;
+
+	pr_info("Dumping sensor registers:\n");
+
+	for (reg = HDCS_IDENT; reg <= HDCS_ROWEXPH; reg++) {
+		stv06xx_read_sensor(sd, reg, &val);
+		pr_info("reg 0x%02x = 0x%02x\n", reg, val);
+	}
+	return 0;
+}
diff --git a/drivers/media/usb/gspca/stv06xx/stv06xx_hdcs.h b/drivers/media/usb/gspca/stv06xx/stv06xx_hdcs.h
new file mode 100644
index 0000000..d2da0de
--- /dev/null
+++ b/drivers/media/usb/gspca/stv06xx/stv06xx_hdcs.h
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2001 Jean-Fredric Clere, Nikolas Zimmermann, Georg Acher
+ *		      Mark Cave-Ayland, Carlo E Prelz, Dick Streefland
+ * Copyright (c) 2002, 2003 Tuukka Toivonen
+ * Copyright (c) 2008 Erik Andrén
+ * Copyright (c) 2008 Chia-I Wu
+ *
+ * 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.
+ *
+ * P/N 861037:      Sensor HDCS1000        ASIC STV0600
+ * P/N 861050-0010: Sensor HDCS1000        ASIC STV0600
+ * P/N 861050-0020: Sensor Photobit PB100  ASIC STV0600-1 - QuickCam Express
+ * P/N 861055:      Sensor ST VV6410       ASIC STV0610   - LEGO cam
+ * P/N 861075-0040: Sensor HDCS1000        ASIC
+ * P/N 961179-0700: Sensor ST VV6410       ASIC STV0602   - Dexxa WebCam USB
+ * P/N 861040-0000: Sensor ST VV6410       ASIC STV0610   - QuickCam Web
+ */
+
+#ifndef STV06XX_HDCS_H_
+#define STV06XX_HDCS_H_
+
+#include "stv06xx_sensor.h"
+
+#define HDCS_REG_CONFIG(sd)	(IS_1020(sd) ? HDCS20_CONFIG : HDCS00_CONFIG)
+#define HDCS_REG_CONTROL(sd)	(IS_1020(sd) ? HDCS20_CONTROL : HDCS00_CONTROL)
+
+#define HDCS_1X00_DEF_WIDTH	360
+#define HDCS_1X00_DEF_HEIGHT	296
+
+#define HDCS_1020_DEF_WIDTH	352
+#define HDCS_1020_DEF_HEIGHT	292
+
+#define HDCS_1020_BOTTOM_Y_SKIP	4
+
+#define HDCS_CLK_FREQ_MHZ	25
+
+#define HDCS_ADC_START_SIG_DUR	3
+
+/* LSB bit of I2C or register address signifies write (0) or read (1) */
+/* I2C Registers common for both HDCS-1000/1100 and HDCS-1020 */
+/* Identifications Register */
+#define HDCS_IDENT		(0x00 << 1)
+/* Status Register */
+#define HDCS_STATUS		(0x01 << 1)
+/* Interrupt Mask Register */
+#define HDCS_IMASK		(0x02 << 1)
+/* Pad Control Register */
+#define HDCS_PCTRL		(0x03 << 1)
+/* Pad Drive Control Register */
+#define HDCS_PDRV		(0x04 << 1)
+/* Interface Control Register */
+#define HDCS_ICTRL		(0x05 << 1)
+/* Interface Timing Register */
+#define HDCS_ITMG		(0x06 << 1)
+/* Baud Fraction Register */
+#define HDCS_BFRAC		(0x07 << 1)
+/* Baud Rate Register */
+#define HDCS_BRATE		(0x08 << 1)
+/* ADC Control Register */
+#define HDCS_ADCCTRL		(0x09 << 1)
+/* First Window Row Register */
+#define HDCS_FWROW		(0x0a << 1)
+/* First Window Column Register */
+#define HDCS_FWCOL		(0x0b << 1)
+/* Last Window Row Register */
+#define HDCS_LWROW		(0x0c << 1)
+/* Last Window Column Register */
+#define HDCS_LWCOL		(0x0d << 1)
+/* Timing Control Register */
+#define HDCS_TCTRL		(0x0e << 1)
+/* PGA Gain Register: Even Row, Even Column */
+#define HDCS_ERECPGA		(0x0f << 1)
+/* PGA Gain Register: Even Row, Odd Column */
+#define HDCS_EROCPGA		(0x10 << 1)
+/* PGA Gain Register: Odd Row, Even Column */
+#define HDCS_ORECPGA		(0x11 << 1)
+/* PGA Gain Register: Odd Row, Odd Column */
+#define HDCS_OROCPGA		(0x12 << 1)
+/* Row Exposure Low Register */
+#define HDCS_ROWEXPL		(0x13 << 1)
+/* Row Exposure High Register */
+#define HDCS_ROWEXPH		(0x14 << 1)
+
+/* I2C Registers only for HDCS-1000/1100 */
+/* Sub-Row Exposure Low Register */
+#define HDCS00_SROWEXPL		(0x15 << 1)
+/* Sub-Row Exposure High Register */
+#define HDCS00_SROWEXPH		(0x16 << 1)
+/* Configuration Register */
+#define HDCS00_CONFIG		(0x17 << 1)
+/* Control Register */
+#define HDCS00_CONTROL		(0x18 << 1)
+
+/* I2C Registers only for HDCS-1020 */
+/* Sub-Row Exposure Register */
+#define HDCS20_SROWEXP		(0x15 << 1)
+/* Error Control Register */
+#define HDCS20_ERROR		(0x16 << 1)
+/* Interface Timing 2 Register */
+#define HDCS20_ITMG2		(0x17 << 1)
+/* Interface Control 2 Register	*/
+#define HDCS20_ICTRL2		(0x18 << 1)
+/* Horizontal Blank Register */
+#define HDCS20_HBLANK		(0x19 << 1)
+/* Vertical Blank Register */
+#define HDCS20_VBLANK		(0x1a << 1)
+/* Configuration Register */
+#define HDCS20_CONFIG		(0x1b << 1)
+/* Control Register */
+#define HDCS20_CONTROL		(0x1c << 1)
+
+#define HDCS_RUN_ENABLE		(1 << 2)
+#define HDCS_SLEEP_MODE		(1 << 1)
+
+#define HDCS_DEFAULT_EXPOSURE	48
+#define HDCS_DEFAULT_GAIN	50
+
+static int hdcs_probe_1x00(struct sd *sd);
+static int hdcs_probe_1020(struct sd *sd);
+static int hdcs_start(struct sd *sd);
+static int hdcs_init(struct sd *sd);
+static int hdcs_init_controls(struct sd *sd);
+static int hdcs_stop(struct sd *sd);
+static int hdcs_dump(struct sd *sd);
+
+static int hdcs_set_exposure(struct gspca_dev *gspca_dev, __s32 val);
+static int hdcs_set_gain(struct gspca_dev *gspca_dev, __s32 val);
+
+const struct stv06xx_sensor stv06xx_sensor_hdcs1x00 = {
+	.name = "HP HDCS-1000/1100",
+	.i2c_flush = 0,
+	.i2c_addr = (0x55 << 1),
+	.i2c_len = 1,
+
+	/* FIXME (see if we can lower min_packet_size, needs testing, and also
+	   adjusting framerate when the bandwidth gets lower) */
+	.min_packet_size = { 847 },
+	.max_packet_size = { 847 },
+
+	.init = hdcs_init,
+	.init_controls = hdcs_init_controls,
+	.probe = hdcs_probe_1x00,
+	.start = hdcs_start,
+	.stop = hdcs_stop,
+	.dump = hdcs_dump,
+};
+
+const struct stv06xx_sensor stv06xx_sensor_hdcs1020 = {
+	.name = "HDCS-1020",
+	.i2c_flush = 0,
+	.i2c_addr = (0x55 << 1),
+	.i2c_len = 1,
+
+	/* FIXME (see if we can lower min_packet_size, needs testing, and also
+	   adjusting framerate when the bandwidthm gets lower) */
+	.min_packet_size = { 847 },
+	.max_packet_size = { 847 },
+
+	.init = hdcs_init,
+	.init_controls = hdcs_init_controls,
+	.probe = hdcs_probe_1020,
+	.start = hdcs_start,
+	.stop = hdcs_stop,
+	.dump = hdcs_dump,
+};
+
+static const u16 stv_bridge_init[][2] = {
+	{STV_ISO_ENABLE, 0},
+	{STV_REG23, 0},
+	{STV_REG00, 0x1d},
+	{STV_REG01, 0xb5},
+	{STV_REG02, 0xa8},
+	{STV_REG03, 0x95},
+	{STV_REG04, 0x07},
+
+	{STV_SCAN_RATE, 0x20},
+	{STV_Y_CTRL, 0x01},
+	{STV_X_CTRL, 0x0a}
+};
+
+static const u8 stv_sensor_init[][2] = {
+	/* Clear status (writing 1 will clear the corresponding status bit) */
+	{HDCS_STATUS, BIT(6) | BIT(5) | BIT(4) | BIT(3) | BIT(2) | BIT(1)},
+	/* Disable all interrupts */
+	{HDCS_IMASK, 0x00},
+	{HDCS_PCTRL, BIT(6) | BIT(5) | BIT(1) | BIT(0)},
+	{HDCS_PDRV,  0x00},
+	{HDCS_ICTRL, BIT(5)},
+	{HDCS_ITMG,  BIT(4) | BIT(1)},
+	/* ADC output resolution to 10 bits */
+	{HDCS_ADCCTRL, 10}
+};
+
+#endif
diff --git a/drivers/media/usb/gspca/stv06xx/stv06xx_pb0100.c b/drivers/media/usb/gspca/stv06xx/stv06xx_pb0100.c
new file mode 100644
index 0000000..7374aeb
--- /dev/null
+++ b/drivers/media/usb/gspca/stv06xx/stv06xx_pb0100.c
@@ -0,0 +1,437 @@
+/*
+ * Copyright (c) 2001 Jean-Fredric Clere, Nikolas Zimmermann, Georg Acher
+ *		      Mark Cave-Ayland, Carlo E Prelz, Dick Streefland
+ * Copyright (c) 2002, 2003 Tuukka Toivonen
+ * Copyright (c) 2008 Erik Andrén
+ *
+ * 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.
+ *
+ * P/N 861037:      Sensor HDCS1000        ASIC STV0600
+ * P/N 861050-0010: Sensor HDCS1000        ASIC STV0600
+ * P/N 861050-0020: Sensor Photobit PB100  ASIC STV0600-1 - QuickCam Express
+ * P/N 861055:      Sensor ST VV6410       ASIC STV0610   - LEGO cam
+ * P/N 861075-0040: Sensor HDCS1000        ASIC
+ * P/N 961179-0700: Sensor ST VV6410       ASIC STV0602   - Dexxa WebCam USB
+ * P/N 861040-0000: Sensor ST VV6410       ASIC STV0610   - QuickCam Web
+ */
+
+/*
+ * The spec file for the PB-0100 suggests the following for best quality
+ * images after the sensor has been reset :
+ *
+ * PB_ADCGAINL      = R60 = 0x03 (3 dec)      : sets low reference of ADC
+						to produce good black level
+ * PB_PREADCTRL     = R32 = 0x1400 (5120 dec) : Enables global gain changes
+						through R53
+ * PB_ADCMINGAIN    = R52 = 0x10 (16 dec)     : Sets the minimum gain for
+						auto-exposure
+ * PB_ADCGLOBALGAIN = R53 = 0x10 (16 dec)     : Sets the global gain
+ * PB_EXPGAIN       = R14 = 0x11 (17 dec)     : Sets the auto-exposure value
+ * PB_UPDATEINT     = R23 = 0x02 (2 dec)      : Sets the speed on
+						auto-exposure routine
+ * PB_CFILLIN       = R5  = 0x0E (14 dec)     : Sets the frame rate
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include "stv06xx_pb0100.h"
+
+struct pb0100_ctrls {
+	struct { /* one big happy control cluster... */
+		struct v4l2_ctrl *autogain;
+		struct v4l2_ctrl *gain;
+		struct v4l2_ctrl *exposure;
+		struct v4l2_ctrl *red;
+		struct v4l2_ctrl *blue;
+		struct v4l2_ctrl *natural;
+	};
+	struct v4l2_ctrl *target;
+};
+
+static struct v4l2_pix_format pb0100_mode[] = {
+/* low res / subsample modes disabled as they are only half res horizontal,
+   halving the vertical resolution does not seem to work */
+	{
+		320,
+		240,
+		V4L2_PIX_FMT_SGRBG8,
+		V4L2_FIELD_NONE,
+		.sizeimage = 320 * 240,
+		.bytesperline = 320,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = PB0100_CROP_TO_VGA
+	},
+	{
+		352,
+		288,
+		V4L2_PIX_FMT_SGRBG8,
+		V4L2_FIELD_NONE,
+		.sizeimage = 352 * 288,
+		.bytesperline = 352,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0
+	}
+};
+
+static int pb0100_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *)gspca_dev;
+	struct pb0100_ctrls *ctrls = sd->sensor_priv;
+	int err = -EINVAL;
+
+	switch (ctrl->id) {
+	case V4L2_CID_AUTOGAIN:
+		err = pb0100_set_autogain(gspca_dev, ctrl->val);
+		if (err)
+			break;
+		if (ctrl->val)
+			break;
+		err = pb0100_set_gain(gspca_dev, ctrls->gain->val);
+		if (err)
+			break;
+		err = pb0100_set_exposure(gspca_dev, ctrls->exposure->val);
+		break;
+	case V4L2_CTRL_CLASS_USER + 0x1001:
+		err = pb0100_set_autogain_target(gspca_dev, ctrl->val);
+		break;
+	}
+	return err;
+}
+
+static const struct v4l2_ctrl_ops pb0100_ctrl_ops = {
+	.s_ctrl = pb0100_s_ctrl,
+};
+
+static int pb0100_init_controls(struct sd *sd)
+{
+	struct v4l2_ctrl_handler *hdl = &sd->gspca_dev.ctrl_handler;
+	struct pb0100_ctrls *ctrls;
+	static const struct v4l2_ctrl_config autogain_target = {
+		.ops = &pb0100_ctrl_ops,
+		.id = V4L2_CTRL_CLASS_USER + 0x1000,
+		.type = V4L2_CTRL_TYPE_INTEGER,
+		.name = "Automatic Gain Target",
+		.max = 255,
+		.step = 1,
+		.def = 128,
+	};
+	static const struct v4l2_ctrl_config natural_light = {
+		.ops = &pb0100_ctrl_ops,
+		.id = V4L2_CTRL_CLASS_USER + 0x1001,
+		.type = V4L2_CTRL_TYPE_BOOLEAN,
+		.name = "Natural Light Source",
+		.max = 1,
+		.step = 1,
+		.def = 1,
+	};
+
+	ctrls = kzalloc(sizeof(*ctrls), GFP_KERNEL);
+	if (!ctrls)
+		return -ENOMEM;
+
+	v4l2_ctrl_handler_init(hdl, 6);
+	ctrls->autogain = v4l2_ctrl_new_std(hdl, &pb0100_ctrl_ops,
+			V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
+	ctrls->exposure = v4l2_ctrl_new_std(hdl, &pb0100_ctrl_ops,
+			V4L2_CID_EXPOSURE, 0, 511, 1, 12);
+	ctrls->gain = v4l2_ctrl_new_std(hdl, &pb0100_ctrl_ops,
+			V4L2_CID_GAIN, 0, 255, 1, 128);
+	ctrls->red = v4l2_ctrl_new_std(hdl, &pb0100_ctrl_ops,
+			V4L2_CID_RED_BALANCE, -255, 255, 1, 0);
+	ctrls->blue = v4l2_ctrl_new_std(hdl, &pb0100_ctrl_ops,
+			V4L2_CID_BLUE_BALANCE, -255, 255, 1, 0);
+	ctrls->natural = v4l2_ctrl_new_custom(hdl, &natural_light, NULL);
+	ctrls->target = v4l2_ctrl_new_custom(hdl, &autogain_target, NULL);
+	if (hdl->error) {
+		kfree(ctrls);
+		return hdl->error;
+	}
+	sd->sensor_priv = ctrls;
+	v4l2_ctrl_auto_cluster(5, &ctrls->autogain, 0, false);
+	return 0;
+}
+
+static int pb0100_probe(struct sd *sd)
+{
+	u16 sensor;
+	int err;
+
+	err = stv06xx_read_sensor(sd, PB_IDENT, &sensor);
+
+	if (err < 0)
+		return -ENODEV;
+	if ((sensor >> 8) != 0x64)
+		return -ENODEV;
+
+	pr_info("Photobit pb0100 sensor detected\n");
+
+	sd->gspca_dev.cam.cam_mode = pb0100_mode;
+	sd->gspca_dev.cam.nmodes = ARRAY_SIZE(pb0100_mode);
+
+	return 0;
+}
+
+static int pb0100_start(struct sd *sd)
+{
+	int err, packet_size, max_packet_size;
+	struct usb_host_interface *alt;
+	struct usb_interface *intf;
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	struct cam *cam = &sd->gspca_dev.cam;
+	u32 mode = cam->cam_mode[sd->gspca_dev.curr_mode].priv;
+
+	intf = usb_ifnum_to_if(sd->gspca_dev.dev, sd->gspca_dev.iface);
+	alt = usb_altnum_to_altsetting(intf, sd->gspca_dev.alt);
+	if (!alt)
+		return -ENODEV;
+	packet_size = le16_to_cpu(alt->endpoint[0].desc.wMaxPacketSize);
+
+	/* If we don't have enough bandwidth use a lower framerate */
+	max_packet_size = sd->sensor->max_packet_size[sd->gspca_dev.curr_mode];
+	if (packet_size < max_packet_size)
+		stv06xx_write_sensor(sd, PB_ROWSPEED, BIT(4)|BIT(3)|BIT(1));
+	else
+		stv06xx_write_sensor(sd, PB_ROWSPEED, BIT(5)|BIT(3)|BIT(1));
+
+	/* Setup sensor window */
+	if (mode & PB0100_CROP_TO_VGA) {
+		stv06xx_write_sensor(sd, PB_RSTART, 30);
+		stv06xx_write_sensor(sd, PB_CSTART, 20);
+		stv06xx_write_sensor(sd, PB_RWSIZE, 240 - 1);
+		stv06xx_write_sensor(sd, PB_CWSIZE, 320 - 1);
+	} else {
+		stv06xx_write_sensor(sd, PB_RSTART, 8);
+		stv06xx_write_sensor(sd, PB_CSTART, 4);
+		stv06xx_write_sensor(sd, PB_RWSIZE, 288 - 1);
+		stv06xx_write_sensor(sd, PB_CWSIZE, 352 - 1);
+	}
+
+	if (mode & PB0100_SUBSAMPLE) {
+		stv06xx_write_bridge(sd, STV_Y_CTRL, 0x02); /* Wrong, FIXME */
+		stv06xx_write_bridge(sd, STV_X_CTRL, 0x06);
+
+		stv06xx_write_bridge(sd, STV_SCAN_RATE, 0x10);
+	} else {
+		stv06xx_write_bridge(sd, STV_Y_CTRL, 0x01);
+		stv06xx_write_bridge(sd, STV_X_CTRL, 0x0a);
+		/* larger -> slower */
+		stv06xx_write_bridge(sd, STV_SCAN_RATE, 0x20);
+	}
+
+	err = stv06xx_write_sensor(sd, PB_CONTROL, BIT(5)|BIT(3)|BIT(1));
+	gspca_dbg(gspca_dev, D_STREAM, "Started stream, status: %d\n", err);
+
+	return (err < 0) ? err : 0;
+}
+
+static int pb0100_stop(struct sd *sd)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	int err;
+
+	err = stv06xx_write_sensor(sd, PB_ABORTFRAME, 1);
+
+	if (err < 0)
+		goto out;
+
+	/* Set bit 1 to zero */
+	err = stv06xx_write_sensor(sd, PB_CONTROL, BIT(5)|BIT(3));
+
+	gspca_dbg(gspca_dev, D_STREAM, "Halting stream\n");
+out:
+	return (err < 0) ? err : 0;
+}
+
+/* FIXME: Sort the init commands out and put them into tables,
+	  this is only for getting the camera to work */
+/* FIXME: No error handling for now,
+	  add this once the init has been converted to proper tables */
+static int pb0100_init(struct sd *sd)
+{
+	stv06xx_write_bridge(sd, STV_REG00, 1);
+	stv06xx_write_bridge(sd, STV_SCAN_RATE, 0);
+
+	/* Reset sensor */
+	stv06xx_write_sensor(sd, PB_RESET, 1);
+	stv06xx_write_sensor(sd, PB_RESET, 0);
+
+	/* Disable chip */
+	stv06xx_write_sensor(sd, PB_CONTROL, BIT(5)|BIT(3));
+
+	/* Gain stuff...*/
+	stv06xx_write_sensor(sd, PB_PREADCTRL, BIT(12)|BIT(10)|BIT(6));
+	stv06xx_write_sensor(sd, PB_ADCGLOBALGAIN, 12);
+
+	/* Set up auto-exposure */
+	/* ADC VREF_HI new setting for a transition
+	  from the Expose1 to the Expose2 setting */
+	stv06xx_write_sensor(sd, PB_R28, 12);
+	/* gain max for autoexposure */
+	stv06xx_write_sensor(sd, PB_ADCMAXGAIN, 180);
+	/* gain min for autoexposure  */
+	stv06xx_write_sensor(sd, PB_ADCMINGAIN, 12);
+	/* Maximum frame integration time (programmed into R8)
+	   allowed for auto-exposure routine */
+	stv06xx_write_sensor(sd, PB_R54, 3);
+	/* Minimum frame integration time (programmed into R8)
+	   allowed for auto-exposure routine */
+	stv06xx_write_sensor(sd, PB_R55, 0);
+	stv06xx_write_sensor(sd, PB_UPDATEINT, 1);
+	/* R15  Expose0 (maximum that auto-exposure may use) */
+	stv06xx_write_sensor(sd, PB_R15, 800);
+	/* R17  Expose2 (minimum that auto-exposure may use) */
+	stv06xx_write_sensor(sd, PB_R17, 10);
+
+	stv06xx_write_sensor(sd, PB_EXPGAIN, 0);
+
+	/* 0x14 */
+	stv06xx_write_sensor(sd, PB_VOFFSET, 0);
+	/* 0x0D */
+	stv06xx_write_sensor(sd, PB_ADCGAINH, 11);
+	/* Set black level (important!) */
+	stv06xx_write_sensor(sd, PB_ADCGAINL, 0);
+
+	/* ??? */
+	stv06xx_write_bridge(sd, STV_REG00, 0x11);
+	stv06xx_write_bridge(sd, STV_REG03, 0x45);
+	stv06xx_write_bridge(sd, STV_REG04, 0x07);
+
+	/* Scan/timing for the sensor */
+	stv06xx_write_sensor(sd, PB_ROWSPEED, BIT(4)|BIT(3)|BIT(1));
+	stv06xx_write_sensor(sd, PB_CFILLIN, 14);
+	stv06xx_write_sensor(sd, PB_VBL, 0);
+	stv06xx_write_sensor(sd, PB_FINTTIME, 0);
+	stv06xx_write_sensor(sd, PB_RINTTIME, 123);
+
+	stv06xx_write_bridge(sd, STV_REG01, 0xc2);
+	stv06xx_write_bridge(sd, STV_REG02, 0xb0);
+	return 0;
+}
+
+static int pb0100_dump(struct sd *sd)
+{
+	return 0;
+}
+
+static int pb0100_set_gain(struct gspca_dev *gspca_dev, __s32 val)
+{
+	int err;
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct pb0100_ctrls *ctrls = sd->sensor_priv;
+
+	err = stv06xx_write_sensor(sd, PB_G1GAIN, val);
+	if (!err)
+		err = stv06xx_write_sensor(sd, PB_G2GAIN, val);
+	gspca_dbg(gspca_dev, D_CONF, "Set green gain to %d, status: %d\n",
+		  val, err);
+
+	if (!err)
+		err = pb0100_set_red_balance(gspca_dev, ctrls->red->val);
+	if (!err)
+		err = pb0100_set_blue_balance(gspca_dev, ctrls->blue->val);
+
+	return err;
+}
+
+static int pb0100_set_red_balance(struct gspca_dev *gspca_dev, __s32 val)
+{
+	int err;
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct pb0100_ctrls *ctrls = sd->sensor_priv;
+
+	val += ctrls->gain->val;
+	if (val < 0)
+		val = 0;
+	else if (val > 255)
+		val = 255;
+
+	err = stv06xx_write_sensor(sd, PB_RGAIN, val);
+	gspca_dbg(gspca_dev, D_CONF, "Set red gain to %d, status: %d\n",
+		  val, err);
+
+	return err;
+}
+
+static int pb0100_set_blue_balance(struct gspca_dev *gspca_dev, __s32 val)
+{
+	int err;
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct pb0100_ctrls *ctrls = sd->sensor_priv;
+
+	val += ctrls->gain->val;
+	if (val < 0)
+		val = 0;
+	else if (val > 255)
+		val = 255;
+
+	err = stv06xx_write_sensor(sd, PB_BGAIN, val);
+	gspca_dbg(gspca_dev, D_CONF, "Set blue gain to %d, status: %d\n",
+		  val, err);
+
+	return err;
+}
+
+static int pb0100_set_exposure(struct gspca_dev *gspca_dev, __s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int err;
+
+	err = stv06xx_write_sensor(sd, PB_RINTTIME, val);
+	gspca_dbg(gspca_dev, D_CONF, "Set exposure to %d, status: %d\n",
+		  val, err);
+
+	return err;
+}
+
+static int pb0100_set_autogain(struct gspca_dev *gspca_dev, __s32 val)
+{
+	int err;
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct pb0100_ctrls *ctrls = sd->sensor_priv;
+
+	if (val) {
+		if (ctrls->natural->val)
+			val = BIT(6)|BIT(4)|BIT(0);
+		else
+			val = BIT(4)|BIT(0);
+	} else
+		val = 0;
+
+	err = stv06xx_write_sensor(sd, PB_EXPGAIN, val);
+	gspca_dbg(gspca_dev, D_CONF, "Set autogain to %d (natural: %d), status: %d\n",
+		  val, ctrls->natural->val, err);
+
+	return err;
+}
+
+static int pb0100_set_autogain_target(struct gspca_dev *gspca_dev, __s32 val)
+{
+	int err, totalpixels, brightpixels, darkpixels;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	/* Number of pixels counted by the sensor when subsampling the pixels.
+	 * Slightly larger than the real value to avoid oscillation */
+	totalpixels = gspca_dev->pixfmt.width * gspca_dev->pixfmt.height;
+	totalpixels = totalpixels/(8*8) + totalpixels/(64*64);
+
+	brightpixels = (totalpixels * val) >> 8;
+	darkpixels   = totalpixels - brightpixels;
+	err = stv06xx_write_sensor(sd, PB_R21, brightpixels);
+	if (!err)
+		err = stv06xx_write_sensor(sd, PB_R22, darkpixels);
+
+	gspca_dbg(gspca_dev, D_CONF, "Set autogain target to %d, status: %d\n",
+		  val, err);
+
+	return err;
+}
diff --git a/drivers/media/usb/gspca/stv06xx/stv06xx_pb0100.h b/drivers/media/usb/gspca/stv06xx/stv06xx_pb0100.h
new file mode 100644
index 0000000..33572d8
--- /dev/null
+++ b/drivers/media/usb/gspca/stv06xx/stv06xx_pb0100.h
@@ -0,0 +1,140 @@
+/*
+ * Copyright (c) 2001 Jean-Fredric Clere, Nikolas Zimmermann, Georg Acher
+ *		      Mark Cave-Ayland, Carlo E Prelz, Dick Streefland
+ * Copyright (c) 2002, 2003 Tuukka Toivonen
+ * Copyright (c) 2008 Erik Andrén
+ *
+ * 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.
+ *
+ * P/N 861037:      Sensor HDCS1000        ASIC STV0600
+ * P/N 861050-0010: Sensor HDCS1000        ASIC STV0600
+ * P/N 861050-0020: Sensor Photobit PB100  ASIC STV0600-1 - QuickCam Express
+ * P/N 861055:      Sensor ST VV6410       ASIC STV0610   - LEGO cam
+ * P/N 861075-0040: Sensor HDCS1000        ASIC
+ * P/N 961179-0700: Sensor ST VV6410       ASIC STV0602   - Dexxa WebCam USB
+ * P/N 861040-0000: Sensor ST VV6410       ASIC STV0610   - QuickCam Web
+ */
+
+#ifndef STV06XX_PB0100_H_
+#define STV06XX_PB0100_H_
+
+#include "stv06xx_sensor.h"
+
+/* mode priv field flags */
+#define PB0100_CROP_TO_VGA	0x01
+#define PB0100_SUBSAMPLE	0x02
+
+/* I2C Registers */
+#define PB_IDENT		0x00	/* Chip Version */
+#define PB_RSTART		0x01	/* Row Window Start */
+#define PB_CSTART		0x02	/* Column Window Start */
+#define PB_RWSIZE		0x03	/* Row Window Size */
+#define PB_CWSIZE		0x04	/* Column  Window Size */
+#define PB_CFILLIN		0x05	/* Column Fill-In */
+#define PB_VBL			0x06	/* Vertical Blank Count */
+#define PB_CONTROL		0x07	/* Control Mode */
+#define PB_FINTTIME		0x08	/* Integration Time/Frame Unit Count */
+#define PB_RINTTIME		0x09	/* Integration Time/Row Unit Count */
+#define PB_ROWSPEED		0x0a	/* Row Speed Control */
+#define PB_ABORTFRAME		0x0b	/* Abort Frame */
+#define PB_R12			0x0c	/* Reserved */
+#define PB_RESET		0x0d	/* Reset */
+#define PB_EXPGAIN		0x0e	/* Exposure Gain Command */
+#define PB_R15			0x0f	/* Expose0 */
+#define PB_R16			0x10	/* Expose1 */
+#define PB_R17			0x11	/* Expose2 */
+#define PB_R18			0x12	/* Low0_DAC */
+#define PB_R19			0x13	/* Low1_DAC */
+#define PB_R20			0x14	/* Low2_DAC */
+#define PB_R21			0x15	/* Threshold11 */
+#define PB_R22			0x16	/* Threshold0x */
+#define PB_UPDATEINT		0x17	/* Update Interval */
+#define PB_R24			0x18	/* High_DAC */
+#define PB_R25			0x19	/* Trans0H */
+#define PB_R26			0x1a	/* Trans1L */
+#define PB_R27			0x1b	/* Trans1H */
+#define PB_R28			0x1c	/* Trans2L */
+#define PB_R29			0x1d	/* Reserved */
+#define PB_R30			0x1e	/* Reserved */
+#define PB_R31			0x1f	/* Wait to Read */
+#define PB_PREADCTRL		0x20	/* Pixel Read Control Mode */
+#define PB_R33			0x21	/* IREF_VLN */
+#define PB_R34			0x22	/* IREF_VLP */
+#define PB_R35			0x23	/* IREF_VLN_INTEG */
+#define PB_R36			0x24	/* IREF_MASTER */
+#define PB_R37			0x25	/* IDACP */
+#define PB_R38			0x26	/* IDACN */
+#define PB_R39			0x27	/* DAC_Control_Reg */
+#define PB_R40			0x28	/* VCL */
+#define PB_R41			0x29	/* IREF_VLN_ADCIN */
+#define PB_R42			0x2a	/* Reserved */
+#define PB_G1GAIN		0x2b	/* Green 1 Gain */
+#define PB_BGAIN		0x2c	/* Blue Gain */
+#define PB_RGAIN		0x2d	/* Red Gain */
+#define PB_G2GAIN		0x2e	/* Green 2 Gain */
+#define PB_R47			0x2f	/* Dark Row Address */
+#define PB_R48			0x30	/* Dark Row Options */
+#define PB_R49			0x31	/* Reserved */
+#define PB_R50			0x32	/* Image Test Data */
+#define PB_ADCMAXGAIN		0x33	/* Maximum Gain */
+#define PB_ADCMINGAIN		0x34	/* Minimum Gain */
+#define PB_ADCGLOBALGAIN	0x35	/* Global Gain */
+#define PB_R54			0x36	/* Maximum Frame */
+#define PB_R55			0x37	/* Minimum Frame */
+#define PB_R56			0x38	/* Reserved */
+#define PB_VOFFSET		0x39	/* VOFFSET */
+#define PB_R58			0x3a	/* Snap-Shot Sequence Trigger */
+#define PB_ADCGAINH		0x3b	/* VREF_HI */
+#define PB_ADCGAINL		0x3c	/* VREF_LO */
+#define PB_R61			0x3d	/* Reserved */
+#define PB_R62			0x3e	/* Reserved */
+#define PB_R63			0x3f	/* Reserved */
+#define PB_R64			0x40	/* Red/Blue Gain */
+#define PB_R65			0x41	/* Green 2/Green 1 Gain */
+#define PB_R66			0x42	/* VREF_HI/LO */
+#define PB_R67			0x43	/* Integration Time/Row Unit Count */
+#define PB_R240			0xf0	/* ADC Test */
+#define PB_R241			0xf1    /* Chip Enable */
+#define PB_R242			0xf2	/* Reserved */
+
+static int pb0100_probe(struct sd *sd);
+static int pb0100_start(struct sd *sd);
+static int pb0100_init(struct sd *sd);
+static int pb0100_init_controls(struct sd *sd);
+static int pb0100_stop(struct sd *sd);
+static int pb0100_dump(struct sd *sd);
+
+/* V4L2 controls supported by the driver */
+static int pb0100_set_gain(struct gspca_dev *gspca_dev, __s32 val);
+static int pb0100_set_red_balance(struct gspca_dev *gspca_dev, __s32 val);
+static int pb0100_set_blue_balance(struct gspca_dev *gspca_dev, __s32 val);
+static int pb0100_set_exposure(struct gspca_dev *gspca_dev, __s32 val);
+static int pb0100_set_autogain(struct gspca_dev *gspca_dev, __s32 val);
+static int pb0100_set_autogain_target(struct gspca_dev *gspca_dev, __s32 val);
+
+const struct stv06xx_sensor stv06xx_sensor_pb0100 = {
+	.name = "PB-0100",
+	.i2c_flush = 1,
+	.i2c_addr = 0xba,
+	.i2c_len = 2,
+
+	.min_packet_size = { 635, 847 },
+	.max_packet_size = { 847, 923 },
+
+	.init = pb0100_init,
+	.init_controls = pb0100_init_controls,
+	.probe = pb0100_probe,
+	.start = pb0100_start,
+	.stop = pb0100_stop,
+	.dump = pb0100_dump,
+};
+
+#endif
diff --git a/drivers/media/usb/gspca/stv06xx/stv06xx_sensor.h b/drivers/media/usb/gspca/stv06xx/stv06xx_sensor.h
new file mode 100644
index 0000000..747d07c
--- /dev/null
+++ b/drivers/media/usb/gspca/stv06xx/stv06xx_sensor.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2001 Jean-Fredric Clere, Nikolas Zimmermann, Georg Acher
+ *		      Mark Cave-Ayland, Carlo E Prelz, Dick Streefland
+ * Copyright (c) 2002, 2003 Tuukka Toivonen
+ * Copyright (c) 2008 Erik Andrén
+ *
+ * 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.
+ *
+ * P/N 861037:      Sensor HDCS1000        ASIC STV0600
+ * P/N 861050-0010: Sensor HDCS1000        ASIC STV0600
+ * P/N 861050-0020: Sensor Photobit PB100  ASIC STV0600-1 - QuickCam Express
+ * P/N 861055:      Sensor ST VV6410       ASIC STV0610   - LEGO cam
+ * P/N 861075-0040: Sensor HDCS1000        ASIC
+ * P/N 961179-0700: Sensor ST VV6410       ASIC STV0602   - Dexxa WebCam USB
+ * P/N 861040-0000: Sensor ST VV6410       ASIC STV0610   - QuickCam Web
+ */
+
+#ifndef STV06XX_SENSOR_H_
+#define STV06XX_SENSOR_H_
+
+#include "stv06xx.h"
+
+#define IS_1020(sd)	((sd)->sensor == &stv06xx_sensor_hdcs1020)
+
+extern const struct stv06xx_sensor stv06xx_sensor_vv6410;
+extern const struct stv06xx_sensor stv06xx_sensor_hdcs1x00;
+extern const struct stv06xx_sensor stv06xx_sensor_hdcs1020;
+extern const struct stv06xx_sensor stv06xx_sensor_pb0100;
+extern const struct stv06xx_sensor stv06xx_sensor_st6422;
+
+struct stv06xx_sensor {
+	/* Defines the name of a sensor */
+	char name[32];
+
+	/* Sensor i2c address */
+	u8 i2c_addr;
+
+	/* Flush value*/
+	u8 i2c_flush;
+
+	/* length of an i2c word */
+	u8 i2c_len;
+
+	/* Isoc packet size (per mode) */
+	int min_packet_size[4];
+	int max_packet_size[4];
+
+	/* Probes if the sensor is connected */
+	int (*probe)(struct sd *sd);
+
+	/* Performs a initialization sequence */
+	int (*init)(struct sd *sd);
+
+	/* Initializes the controls */
+	int (*init_controls)(struct sd *sd);
+
+	/* Reads a sensor register */
+	int (*read_sensor)(struct sd *sd, const u8 address,
+	      u8 *i2c_data, const u8 len);
+
+	/* Writes to a sensor register */
+	int (*write_sensor)(struct sd *sd, const u8 address,
+	      u8 *i2c_data, const u8 len);
+
+	/* Instructs the sensor to start streaming */
+	int (*start)(struct sd *sd);
+
+	/* Instructs the sensor to stop streaming */
+	int (*stop)(struct sd *sd);
+
+	/* Instructs the sensor to dump all its contents */
+	int (*dump)(struct sd *sd);
+};
+
+#endif
diff --git a/drivers/media/usb/gspca/stv06xx/stv06xx_st6422.c b/drivers/media/usb/gspca/stv06xx/stv06xx_st6422.c
new file mode 100644
index 0000000..51a135c
--- /dev/null
+++ b/drivers/media/usb/gspca/stv06xx/stv06xx_st6422.c
@@ -0,0 +1,283 @@
+/*
+ * Support for the sensor part which is integrated (I think) into the
+ * st6422 stv06xx alike bridge, as its integrated there are no i2c writes
+ * but instead direct bridge writes.
+ *
+ * Copyright (c) 2009 Hans de Goede <hdegoede@redhat.com>
+ *
+ * Strongly based on qc-usb-messenger, which is:
+ * Copyright (c) 2001 Jean-Fredric Clere, Nikolas Zimmermann, Georg Acher
+ *		      Mark Cave-Ayland, Carlo E Prelz, Dick Streefland
+ * Copyright (c) 2002, 2003 Tuukka Toivonen
+ *
+ * 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.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include "stv06xx_st6422.h"
+
+static struct v4l2_pix_format st6422_mode[] = {
+	/* Note we actually get 124 lines of data, of which we skip the 4st
+	   4 as they are garbage */
+	{
+		162,
+		120,
+		V4L2_PIX_FMT_SGRBG8,
+		V4L2_FIELD_NONE,
+		.sizeimage = 162 * 120,
+		.bytesperline = 162,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1
+	},
+	/* Note we actually get 248 lines of data, of which we skip the 4st
+	   4 as they are garbage, and we tell the app it only gets the
+	   first 240 of the 244 lines it actually gets, so that it ignores
+	   the last 4. */
+	{
+		324,
+		240,
+		V4L2_PIX_FMT_SGRBG8,
+		V4L2_FIELD_NONE,
+		.sizeimage = 324 * 244,
+		.bytesperline = 324,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0
+	},
+};
+
+/* V4L2 controls supported by the driver */
+static int setbrightness(struct sd *sd, s32 val);
+static int setcontrast(struct sd *sd, s32 val);
+static int setgain(struct sd *sd, u8 gain);
+static int setexposure(struct sd *sd, s16 expo);
+
+static int st6422_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *)gspca_dev;
+	int err = -EINVAL;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		err = setbrightness(sd, ctrl->val);
+		break;
+	case V4L2_CID_CONTRAST:
+		err = setcontrast(sd, ctrl->val);
+		break;
+	case V4L2_CID_GAIN:
+		err = setgain(sd, ctrl->val);
+		break;
+	case V4L2_CID_EXPOSURE:
+		err = setexposure(sd, ctrl->val);
+		break;
+	}
+
+	/* commit settings */
+	if (err >= 0)
+		err = stv06xx_write_bridge(sd, 0x143f, 0x01);
+	sd->gspca_dev.usb_err = err;
+	return err;
+}
+
+static const struct v4l2_ctrl_ops st6422_ctrl_ops = {
+	.s_ctrl = st6422_s_ctrl,
+};
+
+static int st6422_init_controls(struct sd *sd)
+{
+	struct v4l2_ctrl_handler *hdl = &sd->gspca_dev.ctrl_handler;
+
+	v4l2_ctrl_handler_init(hdl, 4);
+	v4l2_ctrl_new_std(hdl, &st6422_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0, 31, 1, 3);
+	v4l2_ctrl_new_std(hdl, &st6422_ctrl_ops,
+			V4L2_CID_CONTRAST, 0, 15, 1, 11);
+	v4l2_ctrl_new_std(hdl, &st6422_ctrl_ops,
+			V4L2_CID_EXPOSURE, 0, 1023, 1, 256);
+	v4l2_ctrl_new_std(hdl, &st6422_ctrl_ops,
+			V4L2_CID_GAIN, 0, 255, 1, 64);
+
+	return hdl->error;
+}
+
+static int st6422_probe(struct sd *sd)
+{
+	if (sd->bridge != BRIDGE_ST6422)
+		return -ENODEV;
+
+	pr_info("st6422 sensor detected\n");
+
+	sd->gspca_dev.cam.cam_mode = st6422_mode;
+	sd->gspca_dev.cam.nmodes = ARRAY_SIZE(st6422_mode);
+	return 0;
+}
+
+static int st6422_init(struct sd *sd)
+{
+	int err = 0, i;
+
+	const u16 st6422_bridge_init[][2] = {
+		{ STV_ISO_ENABLE, 0x00 }, /* disable capture */
+		{ 0x1436, 0x00 },
+		{ 0x1432, 0x03 },	/* 0x00-0x1F brightness */
+		{ 0x143a, 0xf9 },	/* 0x00-0x0F contrast */
+		{ 0x0509, 0x38 },	/* R */
+		{ 0x050a, 0x38 },	/* G */
+		{ 0x050b, 0x38 },	/* B */
+		{ 0x050c, 0x2a },
+		{ 0x050d, 0x01 },
+
+
+		{ 0x1431, 0x00 },	/* 0x00-0x07 ??? */
+		{ 0x1433, 0x34 },	/* 160x120, 0x00-0x01 night filter */
+		{ 0x1438, 0x18 },	/* 640x480 */
+/* 18 bayes */
+/* 10 compressed? */
+
+		{ 0x1439, 0x00 },
+/* anti-noise?  0xa2 gives a perfect image */
+
+		{ 0x143b, 0x05 },
+		{ 0x143c, 0x00 },	/* 0x00-0x01 - ??? */
+
+
+/* shutter time 0x0000-0x03FF */
+/* low value  give good picures on moving objects (but requires much light) */
+/* high value gives good picures in darkness (but tends to be overexposed) */
+		{ 0x143e, 0x01 },
+		{ 0x143d, 0x00 },
+
+		{ 0x1442, 0xe2 },
+/* write: 1x1x xxxx */
+/* read:  1x1x xxxx */
+/*        bit 5 == button pressed and hold if 0 */
+/* write 0xe2,0xea */
+
+/* 0x144a */
+/* 0x00 init */
+/* bit 7 == button has been pressed, but not handled */
+
+/* interrupt */
+/* if(urb->iso_frame_desc[i].status == 0x80) { */
+/* if(urb->iso_frame_desc[i].status == 0x88) { */
+
+		{ 0x1500, 0xd0 },
+		{ 0x1500, 0xd0 },
+		{ 0x1500, 0x50 },	/* 0x00 - 0xFF  0x80 == compr ? */
+
+		{ 0x1501, 0xaf },
+/* high val-> light area gets darker */
+/* low val -> light area gets lighter */
+		{ 0x1502, 0xc2 },
+/* high val-> light area gets darker */
+/* low val -> light area gets lighter */
+		{ 0x1503, 0x45 },
+/* high val-> light area gets darker */
+/* low val -> light area gets lighter */
+		{ 0x1505, 0x02 },
+/* 2  : 324x248  80352 bytes */
+/* 7  : 248x162  40176 bytes */
+/* c+f: 162*124  20088 bytes */
+
+		{ 0x150e, 0x8e },
+		{ 0x150f, 0x37 },
+		{ 0x15c0, 0x00 },
+		{ 0x15c3, 0x08 },	/* 0x04/0x14 ... test pictures ??? */
+
+
+		{ 0x143f, 0x01 },	/* commit settings */
+
+	};
+
+	for (i = 0; i < ARRAY_SIZE(st6422_bridge_init) && !err; i++) {
+		err = stv06xx_write_bridge(sd, st6422_bridge_init[i][0],
+					       st6422_bridge_init[i][1]);
+	}
+
+	return err;
+}
+
+static int setbrightness(struct sd *sd, s32 val)
+{
+	/* val goes from 0 -> 31 */
+	return stv06xx_write_bridge(sd, 0x1432, val);
+}
+
+static int setcontrast(struct sd *sd, s32 val)
+{
+	/* Val goes from 0 -> 15 */
+	return stv06xx_write_bridge(sd, 0x143a, val | 0xf0);
+}
+
+static int setgain(struct sd *sd, u8 gain)
+{
+	int err;
+
+	/* Set red, green, blue, gain */
+	err = stv06xx_write_bridge(sd, 0x0509, gain);
+	if (err < 0)
+		return err;
+
+	err = stv06xx_write_bridge(sd, 0x050a, gain);
+	if (err < 0)
+		return err;
+
+	err = stv06xx_write_bridge(sd, 0x050b, gain);
+	if (err < 0)
+		return err;
+
+	/* 2 mystery writes */
+	err = stv06xx_write_bridge(sd, 0x050c, 0x2a);
+	if (err < 0)
+		return err;
+
+	return stv06xx_write_bridge(sd, 0x050d, 0x01);
+}
+
+static int setexposure(struct sd *sd, s16 expo)
+{
+	int err;
+
+	err = stv06xx_write_bridge(sd, 0x143d, expo & 0xff);
+	if (err < 0)
+		return err;
+
+	return stv06xx_write_bridge(sd, 0x143e, expo >> 8);
+}
+
+static int st6422_start(struct sd *sd)
+{
+	int err;
+	struct cam *cam = &sd->gspca_dev.cam;
+
+	if (cam->cam_mode[sd->gspca_dev.curr_mode].priv)
+		err = stv06xx_write_bridge(sd, 0x1505, 0x0f);
+	else
+		err = stv06xx_write_bridge(sd, 0x1505, 0x02);
+	if (err < 0)
+		return err;
+
+	/* commit settings */
+	err = stv06xx_write_bridge(sd, 0x143f, 0x01);
+	return (err < 0) ? err : 0;
+}
+
+static int st6422_stop(struct sd *sd)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+
+	gspca_dbg(gspca_dev, D_STREAM, "Halting stream\n");
+
+	return 0;
+}
diff --git a/drivers/media/usb/gspca/stv06xx/stv06xx_st6422.h b/drivers/media/usb/gspca/stv06xx/stv06xx_st6422.h
new file mode 100644
index 0000000..87324a6
--- /dev/null
+++ b/drivers/media/usb/gspca/stv06xx/stv06xx_st6422.h
@@ -0,0 +1,48 @@
+/*
+ * Support for the sensor part which is integrated (I think) into the
+ * st6422 stv06xx alike bridge, as its integrated there are no i2c writes
+ * but instead direct bridge writes.
+ *
+ * Copyright (c) 2009 Hans de Goede <hdegoede@redhat.com>
+ *
+ * Strongly based on qc-usb-messenger, which is:
+ * Copyright (c) 2001 Jean-Fredric Clere, Nikolas Zimmermann, Georg Acher
+ *		      Mark Cave-Ayland, Carlo E Prelz, Dick Streefland
+ * Copyright (c) 2002, 2003 Tuukka Toivonen
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef STV06XX_ST6422_H_
+#define STV06XX_ST6422_H_
+
+#include "stv06xx_sensor.h"
+
+static int st6422_probe(struct sd *sd);
+static int st6422_start(struct sd *sd);
+static int st6422_init(struct sd *sd);
+static int st6422_init_controls(struct sd *sd);
+static int st6422_stop(struct sd *sd);
+
+const struct stv06xx_sensor stv06xx_sensor_st6422 = {
+	.name = "ST6422",
+	/* No known way to lower framerate in case of less bandwidth */
+	.min_packet_size = { 300, 847 },
+	.max_packet_size = { 300, 847 },
+	.init = st6422_init,
+	.init_controls = st6422_init_controls,
+	.probe = st6422_probe,
+	.start = st6422_start,
+	.stop = st6422_stop,
+};
+
+#endif
diff --git a/drivers/media/usb/gspca/stv06xx/stv06xx_vv6410.c b/drivers/media/usb/gspca/stv06xx/stv06xx_vv6410.c
new file mode 100644
index 0000000..b2f16c2
--- /dev/null
+++ b/drivers/media/usb/gspca/stv06xx/stv06xx_vv6410.c
@@ -0,0 +1,274 @@
+/*
+ * Copyright (c) 2001 Jean-Fredric Clere, Nikolas Zimmermann, Georg Acher
+ *		      Mark Cave-Ayland, Carlo E Prelz, Dick Streefland
+ * Copyright (c) 2002, 2003 Tuukka Toivonen
+ * Copyright (c) 2008 Erik Andrén
+ *
+ * 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.
+ *
+ * P/N 861037:      Sensor HDCS1000        ASIC STV0600
+ * P/N 861050-0010: Sensor HDCS1000        ASIC STV0600
+ * P/N 861050-0020: Sensor Photobit PB100  ASIC STV0600-1 - QuickCam Express
+ * P/N 861055:      Sensor ST VV6410       ASIC STV0610   - LEGO cam
+ * P/N 861075-0040: Sensor HDCS1000        ASIC
+ * P/N 961179-0700: Sensor ST VV6410       ASIC STV0602   - Dexxa WebCam USB
+ * P/N 861040-0000: Sensor ST VV6410       ASIC STV0610   - QuickCam Web
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include "stv06xx_vv6410.h"
+
+static struct v4l2_pix_format vv6410_mode[] = {
+	{
+		356,
+		292,
+		V4L2_PIX_FMT_SGRBG8,
+		V4L2_FIELD_NONE,
+		.sizeimage = 356 * 292,
+		.bytesperline = 356,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0
+	}
+};
+
+static int vv6410_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	int err = -EINVAL;
+
+	switch (ctrl->id) {
+	case V4L2_CID_HFLIP:
+		if (!gspca_dev->streaming)
+			return 0;
+		err = vv6410_set_hflip(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_VFLIP:
+		if (!gspca_dev->streaming)
+			return 0;
+		err = vv6410_set_vflip(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_GAIN:
+		err = vv6410_set_analog_gain(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_EXPOSURE:
+		err = vv6410_set_exposure(gspca_dev, ctrl->val);
+		break;
+	}
+	return err;
+}
+
+static const struct v4l2_ctrl_ops vv6410_ctrl_ops = {
+	.s_ctrl = vv6410_s_ctrl,
+};
+
+static int vv6410_probe(struct sd *sd)
+{
+	u16 data;
+	int err;
+
+	err = stv06xx_read_sensor(sd, VV6410_DEVICEH, &data);
+	if (err < 0)
+		return -ENODEV;
+
+	if (data != 0x19)
+		return -ENODEV;
+
+	pr_info("vv6410 sensor detected\n");
+
+	sd->gspca_dev.cam.cam_mode = vv6410_mode;
+	sd->gspca_dev.cam.nmodes = ARRAY_SIZE(vv6410_mode);
+	return 0;
+}
+
+static int vv6410_init_controls(struct sd *sd)
+{
+	struct v4l2_ctrl_handler *hdl = &sd->gspca_dev.ctrl_handler;
+
+	v4l2_ctrl_handler_init(hdl, 2);
+	/* Disable the hardware VFLIP and HFLIP as we currently lack a
+	   mechanism to adjust the image offset in such a way that
+	   we don't need to renegotiate the announced format */
+	/* v4l2_ctrl_new_std(hdl, &vv6410_ctrl_ops, */
+	/*		V4L2_CID_HFLIP, 0, 1, 1, 0); */
+	/* v4l2_ctrl_new_std(hdl, &vv6410_ctrl_ops, */
+	/*		V4L2_CID_VFLIP, 0, 1, 1, 0); */
+	v4l2_ctrl_new_std(hdl, &vv6410_ctrl_ops,
+			V4L2_CID_EXPOSURE, 0, 32768, 1, 20000);
+	v4l2_ctrl_new_std(hdl, &vv6410_ctrl_ops,
+			V4L2_CID_GAIN, 0, 15, 1, 10);
+	return hdl->error;
+}
+
+static int vv6410_init(struct sd *sd)
+{
+	int err = 0, i;
+
+	for (i = 0; i < ARRAY_SIZE(stv_bridge_init); i++)
+		stv06xx_write_bridge(sd, stv_bridge_init[i].addr, stv_bridge_init[i].data);
+
+	err = stv06xx_write_sensor_bytes(sd, (u8 *) vv6410_sensor_init,
+					 ARRAY_SIZE(vv6410_sensor_init));
+	return (err < 0) ? err : 0;
+}
+
+static int vv6410_start(struct sd *sd)
+{
+	int err;
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	struct cam *cam = &sd->gspca_dev.cam;
+	u32 priv = cam->cam_mode[sd->gspca_dev.curr_mode].priv;
+
+	if (priv & VV6410_SUBSAMPLE) {
+		gspca_dbg(gspca_dev, D_CONF, "Enabling subsampling\n");
+		stv06xx_write_bridge(sd, STV_Y_CTRL, 0x02);
+		stv06xx_write_bridge(sd, STV_X_CTRL, 0x06);
+
+		stv06xx_write_bridge(sd, STV_SCAN_RATE, 0x10);
+	} else {
+		stv06xx_write_bridge(sd, STV_Y_CTRL, 0x01);
+		stv06xx_write_bridge(sd, STV_X_CTRL, 0x0a);
+		stv06xx_write_bridge(sd, STV_SCAN_RATE, 0x00);
+
+	}
+
+	/* Turn on LED */
+	err = stv06xx_write_bridge(sd, STV_LED_CTRL, LED_ON);
+	if (err < 0)
+		return err;
+
+	err = stv06xx_write_sensor(sd, VV6410_SETUP0, 0);
+	if (err < 0)
+		return err;
+
+	gspca_dbg(gspca_dev, D_STREAM, "Starting stream\n");
+
+	return 0;
+}
+
+static int vv6410_stop(struct sd *sd)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	int err;
+
+	/* Turn off LED */
+	err = stv06xx_write_bridge(sd, STV_LED_CTRL, LED_OFF);
+	if (err < 0)
+		return err;
+
+	err = stv06xx_write_sensor(sd, VV6410_SETUP0, VV6410_LOW_POWER_MODE);
+	if (err < 0)
+		return err;
+
+	gspca_dbg(gspca_dev, D_STREAM, "Halting stream\n");
+
+	return 0;
+}
+
+static int vv6410_dump(struct sd *sd)
+{
+	u8 i;
+	int err = 0;
+
+	pr_info("Dumping all vv6410 sensor registers\n");
+	for (i = 0; i < 0xff && !err; i++) {
+		u16 data;
+		err = stv06xx_read_sensor(sd, i, &data);
+		pr_info("Register 0x%x contained 0x%x\n", i, data);
+	}
+	return (err < 0) ? err : 0;
+}
+
+static int vv6410_set_hflip(struct gspca_dev *gspca_dev, __s32 val)
+{
+	int err;
+	u16 i2c_data;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	err = stv06xx_read_sensor(sd, VV6410_DATAFORMAT, &i2c_data);
+	if (err < 0)
+		return err;
+
+	if (val)
+		i2c_data |= VV6410_HFLIP;
+	else
+		i2c_data &= ~VV6410_HFLIP;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set horizontal flip to %d\n", val);
+	err = stv06xx_write_sensor(sd, VV6410_DATAFORMAT, i2c_data);
+
+	return (err < 0) ? err : 0;
+}
+
+static int vv6410_set_vflip(struct gspca_dev *gspca_dev, __s32 val)
+{
+	int err;
+	u16 i2c_data;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	err = stv06xx_read_sensor(sd, VV6410_DATAFORMAT, &i2c_data);
+	if (err < 0)
+		return err;
+
+	if (val)
+		i2c_data |= VV6410_VFLIP;
+	else
+		i2c_data &= ~VV6410_VFLIP;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set vertical flip to %d\n", val);
+	err = stv06xx_write_sensor(sd, VV6410_DATAFORMAT, i2c_data);
+
+	return (err < 0) ? err : 0;
+}
+
+static int vv6410_set_analog_gain(struct gspca_dev *gspca_dev, __s32 val)
+{
+	int err;
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	gspca_dbg(gspca_dev, D_CONF, "Set analog gain to %d\n", val);
+	err = stv06xx_write_sensor(sd, VV6410_ANALOGGAIN, 0xf0 | (val & 0xf));
+
+	return (err < 0) ? err : 0;
+}
+
+static int vv6410_set_exposure(struct gspca_dev *gspca_dev, __s32 val)
+{
+	int err;
+	struct sd *sd = (struct sd *) gspca_dev;
+	unsigned int fine, coarse;
+
+	val = (val * val >> 14) + val / 4;
+
+	fine = val % VV6410_CIF_LINELENGTH;
+	coarse = min(512, val / VV6410_CIF_LINELENGTH);
+
+	gspca_dbg(gspca_dev, D_CONF, "Set coarse exposure to %d, fine exposure to %d\n",
+		  coarse, fine);
+
+	err = stv06xx_write_sensor(sd, VV6410_FINEH, fine >> 8);
+	if (err < 0)
+		goto out;
+
+	err = stv06xx_write_sensor(sd, VV6410_FINEL, fine & 0xff);
+	if (err < 0)
+		goto out;
+
+	err = stv06xx_write_sensor(sd, VV6410_COARSEH, coarse >> 8);
+	if (err < 0)
+		goto out;
+
+	err = stv06xx_write_sensor(sd, VV6410_COARSEL, coarse & 0xff);
+
+out:
+	return err;
+}
diff --git a/drivers/media/usb/gspca/stv06xx/stv06xx_vv6410.h b/drivers/media/usb/gspca/stv06xx/stv06xx_vv6410.h
new file mode 100644
index 0000000..e859889
--- /dev/null
+++ b/drivers/media/usb/gspca/stv06xx/stv06xx_vv6410.h
@@ -0,0 +1,251 @@
+/*
+ * Copyright (c) 2001 Jean-Fredric Clere, Nikolas Zimmermann, Georg Acher
+ *		      Mark Cave-Ayland, Carlo E Prelz, Dick Streefland
+ * Copyright (c) 2002, 2003 Tuukka Toivonen
+ * Copyright (c) 2008 Erik Andrén
+ *
+ * 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.
+ *
+ * P/N 861037:      Sensor HDCS1000        ASIC STV0600
+ * P/N 861050-0010: Sensor HDCS1000        ASIC STV0600
+ * P/N 861050-0020: Sensor Photobit PB100  ASIC STV0600-1 - QuickCam Express
+ * P/N 861055:      Sensor ST VV6410       ASIC STV0610   - LEGO cam
+ * P/N 861075-0040: Sensor HDCS1000        ASIC
+ * P/N 961179-0700: Sensor ST VV6410       ASIC STV0602   - Dexxa WebCam USB
+ * P/N 861040-0000: Sensor ST VV6410       ASIC STV0610   - QuickCam Web
+ */
+
+#ifndef STV06XX_VV6410_H_
+#define STV06XX_VV6410_H_
+
+#include "stv06xx_sensor.h"
+
+#define VV6410_COLS			416
+#define VV6410_ROWS			320
+
+/* Status registers */
+/* Chip identification number including revision indicator */
+#define VV6410_DEVICEH			0x00
+#define VV6410_DEVICEL			0x01
+
+/* User can determine whether timed I2C data
+   has been consumed by interrogating flag states */
+#define VV6410_STATUS0			0x02
+
+/* Current line counter value */
+#define VV6410_LINECOUNTH		0x03
+#define VV6410_LINECOUNTL		0x04
+
+/* End x coordinate of image size */
+#define VV6410_XENDH			0x05
+#define VV6410_XENDL			0x06
+
+/* End y coordinate of image size */
+#define VV6410_YENDH			0x07
+#define VV6410_YENDL			0x08
+
+/* This is the average pixel value returned from the
+   dark line offset cancellation algorithm */
+#define VV6410_DARKAVGH			0x09
+#define VV6410_DARKAVGL			0x0a
+
+/* This is the average pixel value returned from the
+   black line offset cancellation algorithm  */
+#define VV6410_BLACKAVGH		0x0b
+#define VV6410_BLACKAVGL		0x0c
+
+/* Flags to indicate whether the x or y image coordinates have been clipped */
+#define VV6410_STATUS1			0x0d
+
+/* Setup registers */
+
+/* Low-power/sleep modes & video timing */
+#define VV6410_SETUP0			0x10
+
+/* Various parameters */
+#define VV6410_SETUP1			0x11
+
+/* Contains pixel counter reset value used by external sync */
+#define VV6410_SYNCVALUE		0x12
+
+/* Frame grabbing modes (FST, LST and QCK) */
+#define VV6410_FGMODES			0x14
+
+/* FST and QCK mapping modes. */
+#define VV6410_PINMAPPING		0x15
+
+/* Data resolution */
+#define VV6410_DATAFORMAT		0x16
+
+/* Output coding formats */
+#define VV6410_OPFORMAT			0x17
+
+/* Various mode select bits */
+#define VV6410_MODESELECT		0x18
+
+/* Exposure registers */
+/* Fine exposure. */
+#define VV6410_FINEH			0x20
+#define VV6410_FINEL			0x21
+
+/* Coarse exposure */
+#define VV6410_COARSEH			0x22
+#define VV6410_COARSEL			0x23
+
+/* Analog gain setting */
+#define VV6410_ANALOGGAIN		0x24
+
+/* Clock division */
+#define VV6410_CLKDIV			0x25
+
+/* Dark line offset cancellation value */
+#define VV6410_DARKOFFSETH		0x2c
+#define VV6410_DARKOFFSETL		0x2d
+
+/* Dark line offset cancellation enable */
+#define VV6410_DARKOFFSETSETUP		0x2e
+
+/* Video timing registers */
+/* Line Length (Pixel Clocks) */
+#define VV6410_LINELENGTHH		0x52
+#define VV6410_LINELENGTHL		0x53
+
+/* X-co-ordinate of top left corner of region of interest (x-offset) */
+#define VV6410_XOFFSETH			0x57
+#define VV6410_XOFFSETL			0x58
+
+/* Y-coordinate of top left corner of region of interest (y-offset) */
+#define VV6410_YOFFSETH			0x59
+#define VV6410_YOFFSETL			0x5a
+
+/* Field length (Lines) */
+#define VV6410_FIELDLENGTHH		0x61
+#define VV6410_FIELDLENGTHL		0x62
+
+/* System registers */
+/* Black offset cancellation default value */
+#define VV6410_BLACKOFFSETH		0x70
+#define VV6410_BLACKOFFSETL		0x71
+
+/* Black offset cancellation setup */
+#define VV6410_BLACKOFFSETSETUP		0x72
+
+/* Analog Control Register 0 */
+#define VV6410_CR0			0x75
+
+/* Analog Control Register 1 */
+#define VV6410_CR1			0x76
+
+/* ADC Setup Register */
+#define VV6410_AS0			0x77
+
+/* Analog Test Register */
+#define VV6410_AT0			0x78
+
+/* Audio Amplifier Setup Register */
+#define VV6410_AT1			0x79
+
+#define VV6410_HFLIP			(1 << 3)
+#define VV6410_VFLIP			(1 << 4)
+
+#define VV6410_LOW_POWER_MODE		(1 << 0)
+#define VV6410_SOFT_RESET		(1 << 2)
+#define VV6410_PAL_25_FPS		(0 << 3)
+
+#define VV6410_CLK_DIV_2		(1 << 1)
+
+#define VV6410_FINE_EXPOSURE		320
+#define VV6410_COARSE_EXPOSURE		192
+#define VV6410_DEFAULT_GAIN		5
+
+#define VV6410_SUBSAMPLE		0x01
+#define VV6410_CROP_TO_QVGA		0x02
+
+#define VV6410_CIF_LINELENGTH		415
+
+static int vv6410_probe(struct sd *sd);
+static int vv6410_start(struct sd *sd);
+static int vv6410_init(struct sd *sd);
+static int vv6410_init_controls(struct sd *sd);
+static int vv6410_stop(struct sd *sd);
+static int vv6410_dump(struct sd *sd);
+
+/* V4L2 controls supported by the driver */
+static int vv6410_set_hflip(struct gspca_dev *gspca_dev, __s32 val);
+static int vv6410_set_vflip(struct gspca_dev *gspca_dev, __s32 val);
+static int vv6410_set_analog_gain(struct gspca_dev *gspca_dev, __s32 val);
+static int vv6410_set_exposure(struct gspca_dev *gspca_dev, __s32 val);
+
+const struct stv06xx_sensor stv06xx_sensor_vv6410 = {
+	.name = "ST VV6410",
+	.i2c_flush = 5,
+	.i2c_addr = 0x20,
+	.i2c_len = 1,
+	/* FIXME (see if we can lower packet_size-s, needs testing, and also
+	   adjusting framerate when the bandwidth gets lower) */
+	.min_packet_size = { 1023 },
+	.max_packet_size = { 1023 },
+	.init = vv6410_init,
+	.init_controls = vv6410_init_controls,
+	.probe = vv6410_probe,
+	.start = vv6410_start,
+	.stop = vv6410_stop,
+	.dump = vv6410_dump,
+};
+
+/* If NULL, only single value to write, stored in len */
+struct stv_init {
+	u16 addr;
+	u8 data;
+};
+
+static const struct stv_init stv_bridge_init[] = {
+	/* This reg is written twice. Some kind of reset? */
+	{STV_RESET, 0x80},
+	{STV_RESET, 0x00},
+	{STV_SCAN_RATE, 0x00},
+	{STV_I2C_FLUSH, 0x04},
+	{STV_REG00, 0x0b},
+	{STV_REG01, 0xa7},
+	{STV_REG02, 0xb7},
+	{STV_REG03, 0x00},
+	{STV_REG04, 0x00},
+	{0x1536, 0x02},
+	{0x1537, 0x00},
+	{0x1538, 0x60},
+	{0x1539, 0x01},
+	{0x153a, 0x20},
+	{0x153b, 0x01},
+};
+
+static const u8 vv6410_sensor_init[][2] = {
+	/* Setup registers */
+	{VV6410_SETUP0,	VV6410_SOFT_RESET},
+	{VV6410_SETUP0,	VV6410_LOW_POWER_MODE},
+	/* Use shuffled read-out mode */
+	{VV6410_SETUP1,	BIT(6)},
+	/* All modes to 1, FST, Fast QCK, Free running QCK, Free running LST, FST will qualify visible pixels */
+	{VV6410_FGMODES, BIT(6) | BIT(4) | BIT(2) | BIT(0)},
+	{VV6410_PINMAPPING, 0x00},
+	/* Pre-clock generator divide off */
+	{VV6410_DATAFORMAT, BIT(7) | BIT(0)},
+
+	{VV6410_CLKDIV,	VV6410_CLK_DIV_2},
+
+	/* System registers */
+	/* Enable voltage doubler */
+	{VV6410_AS0, BIT(6) | BIT(4) | BIT(3) | BIT(2) | BIT(1)},
+	{VV6410_AT0, 0x00},
+	/* Power up audio, differential */
+	{VV6410_AT1, BIT(4) | BIT(0)},
+};
+
+#endif
diff --git a/drivers/media/usb/gspca/sunplus.c b/drivers/media/usb/gspca/sunplus.c
new file mode 100644
index 0000000..437a336
--- /dev/null
+++ b/drivers/media/usb/gspca/sunplus.c
@@ -0,0 +1,1073 @@
+/*
+ *		Sunplus spca504(abc) spca533 spca536 library
+ *		Copyright (C) 2005 Michel Xhaard mxhaard@magic.fr
+ *
+ * V4L2 by Jean-Francois Moine <http://moinejf.free.fr>
+ *
+ * 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
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "sunplus"
+
+#include "gspca.h"
+#include "jpeg.h"
+
+MODULE_AUTHOR("Michel Xhaard <mxhaard@users.sourceforge.net>");
+MODULE_DESCRIPTION("GSPCA/SPCA5xx USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+#define QUALITY 85
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+
+	bool autogain;
+
+	u8 bridge;
+#define BRIDGE_SPCA504 0
+#define BRIDGE_SPCA504B 1
+#define BRIDGE_SPCA504C 2
+#define BRIDGE_SPCA533 3
+#define BRIDGE_SPCA536 4
+	u8 subtype;
+#define AiptekMiniPenCam13 1
+#define LogitechClickSmart420 2
+#define LogitechClickSmart820 3
+#define MegapixV4 4
+#define MegaImageVI 5
+
+	u8 jpeg_hdr[JPEG_HDR_SZ];
+};
+
+static const struct v4l2_pix_format vga_mode[] = {
+	{320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 2},
+	{640, 480, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 1},
+};
+
+static const struct v4l2_pix_format custom_mode[] = {
+	{320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 2},
+	{464, 480, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 464,
+		.sizeimage = 464 * 480 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 1},
+};
+
+static const struct v4l2_pix_format vga_mode2[] = {
+	{176, 144, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 176,
+		.sizeimage = 176 * 144 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 4},
+	{320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 3},
+	{352, 288, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 352,
+		.sizeimage = 352 * 288 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 2},
+	{640, 480, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 1},
+};
+
+#define SPCA50X_OFFSET_DATA 10
+#define SPCA504_PCCAM600_OFFSET_SNAPSHOT 3
+#define SPCA504_PCCAM600_OFFSET_COMPRESS 4
+#define SPCA504_PCCAM600_OFFSET_MODE	 5
+#define SPCA504_PCCAM600_OFFSET_DATA	 14
+ /* Frame packet header offsets for the spca533 */
+#define SPCA533_OFFSET_DATA	16
+#define SPCA533_OFFSET_FRAMSEQ	15
+/* Frame packet header offsets for the spca536 */
+#define SPCA536_OFFSET_DATA	4
+#define SPCA536_OFFSET_FRAMSEQ	1
+
+struct cmd {
+	u8 req;
+	u16 val;
+	u16 idx;
+};
+
+/* Initialisation data for the Creative PC-CAM 600 */
+static const struct cmd spca504_pccam600_init_data[] = {
+/*	{0xa0, 0x0000, 0x0503},  * capture mode */
+	{0x00, 0x0000, 0x2000},
+	{0x00, 0x0013, 0x2301},
+	{0x00, 0x0003, 0x2000},
+	{0x00, 0x0001, 0x21ac},
+	{0x00, 0x0001, 0x21a6},
+	{0x00, 0x0000, 0x21a7},	/* brightness */
+	{0x00, 0x0020, 0x21a8},	/* contrast */
+	{0x00, 0x0001, 0x21ac},	/* sat/hue */
+	{0x00, 0x0000, 0x21ad},	/* hue */
+	{0x00, 0x001a, 0x21ae},	/* saturation */
+	{0x00, 0x0002, 0x21a3},	/* gamma */
+	{0x30, 0x0154, 0x0008},
+	{0x30, 0x0004, 0x0006},
+	{0x30, 0x0258, 0x0009},
+	{0x30, 0x0004, 0x0000},
+	{0x30, 0x0093, 0x0004},
+	{0x30, 0x0066, 0x0005},
+	{0x00, 0x0000, 0x2000},
+	{0x00, 0x0013, 0x2301},
+	{0x00, 0x0003, 0x2000},
+	{0x00, 0x0013, 0x2301},
+	{0x00, 0x0003, 0x2000},
+};
+
+/* Creative PC-CAM 600 specific open data, sent before using the
+ * generic initialisation data from spca504_open_data.
+ */
+static const struct cmd spca504_pccam600_open_data[] = {
+	{0x00, 0x0001, 0x2501},
+	{0x20, 0x0500, 0x0001},	/* snapshot mode */
+	{0x00, 0x0003, 0x2880},
+	{0x00, 0x0001, 0x2881},
+};
+
+/* Initialisation data for the logitech clicksmart 420 */
+static const struct cmd spca504A_clicksmart420_init_data[] = {
+/*	{0xa0, 0x0000, 0x0503},  * capture mode */
+	{0x00, 0x0000, 0x2000},
+	{0x00, 0x0013, 0x2301},
+	{0x00, 0x0003, 0x2000},
+	{0x00, 0x0001, 0x21ac},
+	{0x00, 0x0001, 0x21a6},
+	{0x00, 0x0000, 0x21a7},	/* brightness */
+	{0x00, 0x0020, 0x21a8},	/* contrast */
+	{0x00, 0x0001, 0x21ac},	/* sat/hue */
+	{0x00, 0x0000, 0x21ad},	/* hue */
+	{0x00, 0x001a, 0x21ae},	/* saturation */
+	{0x00, 0x0002, 0x21a3},	/* gamma */
+	{0x30, 0x0004, 0x000a},
+	{0xb0, 0x0001, 0x0000},
+
+	{0xa1, 0x0080, 0x0001},
+	{0x30, 0x0049, 0x0000},
+	{0x30, 0x0060, 0x0005},
+	{0x0c, 0x0004, 0x0000},
+	{0x00, 0x0000, 0x0000},
+	{0x00, 0x0000, 0x2000},
+	{0x00, 0x0013, 0x2301},
+	{0x00, 0x0003, 0x2000},
+};
+
+/* clicksmart 420 open data ? */
+static const struct cmd spca504A_clicksmart420_open_data[] = {
+	{0x00, 0x0001, 0x2501},
+	{0x20, 0x0502, 0x0000},
+	{0x06, 0x0000, 0x0000},
+	{0x00, 0x0004, 0x2880},
+	{0x00, 0x0001, 0x2881},
+
+	{0xa0, 0x0000, 0x0503},
+};
+
+static const u8 qtable_creative_pccam[2][64] = {
+	{				/* Q-table Y-components */
+	 0x05, 0x03, 0x03, 0x05, 0x07, 0x0c, 0x0f, 0x12,
+	 0x04, 0x04, 0x04, 0x06, 0x08, 0x11, 0x12, 0x11,
+	 0x04, 0x04, 0x05, 0x07, 0x0c, 0x11, 0x15, 0x11,
+	 0x04, 0x05, 0x07, 0x09, 0x0f, 0x1a, 0x18, 0x13,
+	 0x05, 0x07, 0x0b, 0x11, 0x14, 0x21, 0x1f, 0x17,
+	 0x07, 0x0b, 0x11, 0x13, 0x18, 0x1f, 0x22, 0x1c,
+	 0x0f, 0x13, 0x17, 0x1a, 0x1f, 0x24, 0x24, 0x1e,
+	 0x16, 0x1c, 0x1d, 0x1d, 0x22, 0x1e, 0x1f, 0x1e},
+	{				/* Q-table C-components */
+	 0x05, 0x05, 0x07, 0x0e, 0x1e, 0x1e, 0x1e, 0x1e,
+	 0x05, 0x06, 0x08, 0x14, 0x1e, 0x1e, 0x1e, 0x1e,
+	 0x07, 0x08, 0x11, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+	 0x0e, 0x14, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+	 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+	 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+	 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+	 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e}
+};
+
+/* FIXME: This Q-table is identical to the Creative PC-CAM one,
+ *		except for one byte. Possibly a typo?
+ *		NWG: 18/05/2003.
+ */
+static const u8 qtable_spca504_default[2][64] = {
+	{				/* Q-table Y-components */
+	 0x05, 0x03, 0x03, 0x05, 0x07, 0x0c, 0x0f, 0x12,
+	 0x04, 0x04, 0x04, 0x06, 0x08, 0x11, 0x12, 0x11,
+	 0x04, 0x04, 0x05, 0x07, 0x0c, 0x11, 0x15, 0x11,
+	 0x04, 0x05, 0x07, 0x09, 0x0f, 0x1a, 0x18, 0x13,
+	 0x05, 0x07, 0x0b, 0x11, 0x14, 0x21, 0x1f, 0x17,
+	 0x07, 0x0b, 0x11, 0x13, 0x18, 0x1f, 0x22, 0x1c,
+	 0x0f, 0x13, 0x17, 0x1a, 0x1f, 0x24, 0x24, 0x1e,
+	 0x16, 0x1c, 0x1d, 0x1d, 0x1d /* 0x22 */ , 0x1e, 0x1f, 0x1e,
+	 },
+	{				/* Q-table C-components */
+	 0x05, 0x05, 0x07, 0x0e, 0x1e, 0x1e, 0x1e, 0x1e,
+	 0x05, 0x06, 0x08, 0x14, 0x1e, 0x1e, 0x1e, 0x1e,
+	 0x07, 0x08, 0x11, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+	 0x0e, 0x14, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+	 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+	 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+	 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+	 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e}
+};
+
+/* read <len> bytes to gspca_dev->usb_buf */
+static void reg_r(struct gspca_dev *gspca_dev,
+		  u8 req,
+		  u16 index,
+		  u16 len)
+{
+	int ret;
+
+	if (len > USB_BUF_SZ) {
+		gspca_err(gspca_dev, "reg_r: buffer overflow\n");
+		return;
+	}
+	if (gspca_dev->usb_err < 0)
+		return;
+	ret = usb_control_msg(gspca_dev->dev,
+			usb_rcvctrlpipe(gspca_dev->dev, 0),
+			req,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0,		/* value */
+			index,
+			len ? gspca_dev->usb_buf : NULL, len,
+			500);
+	if (ret < 0) {
+		pr_err("reg_r err %d\n", ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+/* write one byte */
+static void reg_w_1(struct gspca_dev *gspca_dev,
+		   u8 req,
+		   u16 value,
+		   u16 index,
+		   u16 byte)
+{
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	gspca_dev->usb_buf[0] = byte;
+	ret = usb_control_msg(gspca_dev->dev,
+			usb_sndctrlpipe(gspca_dev->dev, 0),
+			req,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			value, index,
+			gspca_dev->usb_buf, 1,
+			500);
+	if (ret < 0) {
+		pr_err("reg_w_1 err %d\n", ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+/* write req / index / value */
+static void reg_w_riv(struct gspca_dev *gspca_dev,
+		     u8 req, u16 index, u16 value)
+{
+	struct usb_device *dev = gspca_dev->dev;
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	ret = usb_control_msg(dev,
+			usb_sndctrlpipe(dev, 0),
+			req,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			value, index, NULL, 0, 500);
+	if (ret < 0) {
+		pr_err("reg_w_riv err %d\n", ret);
+		gspca_dev->usb_err = ret;
+		return;
+	}
+	gspca_dbg(gspca_dev, D_USBO, "reg_w_riv: 0x%02x,0x%04x:0x%04x\n",
+		  req, index, value);
+}
+
+static void write_vector(struct gspca_dev *gspca_dev,
+			const struct cmd *data, int ncmds)
+{
+	while (--ncmds >= 0) {
+		reg_w_riv(gspca_dev, data->req, data->idx, data->val);
+		data++;
+	}
+}
+
+static void setup_qtable(struct gspca_dev *gspca_dev,
+			const u8 qtable[2][64])
+{
+	int i;
+
+	/* loop over y components */
+	for (i = 0; i < 64; i++)
+		reg_w_riv(gspca_dev, 0x00, 0x2800 + i, qtable[0][i]);
+
+	/* loop over c components */
+	for (i = 0; i < 64; i++)
+		reg_w_riv(gspca_dev, 0x00, 0x2840 + i, qtable[1][i]);
+}
+
+static void spca504_acknowledged_command(struct gspca_dev *gspca_dev,
+			     u8 req, u16 idx, u16 val)
+{
+	reg_w_riv(gspca_dev, req, idx, val);
+	reg_r(gspca_dev, 0x01, 0x0001, 1);
+	gspca_dbg(gspca_dev, D_FRAM, "before wait 0x%04x\n",
+		  gspca_dev->usb_buf[0]);
+	reg_w_riv(gspca_dev, req, idx, val);
+
+	msleep(200);
+	reg_r(gspca_dev, 0x01, 0x0001, 1);
+	gspca_dbg(gspca_dev, D_FRAM, "after wait 0x%04x\n",
+		  gspca_dev->usb_buf[0]);
+}
+
+static void spca504_read_info(struct gspca_dev *gspca_dev)
+{
+	int i;
+	u8 info[6];
+
+	if (gspca_debug < D_STREAM)
+		return;
+
+	for (i = 0; i < 6; i++) {
+		reg_r(gspca_dev, 0, i, 1);
+		info[i] = gspca_dev->usb_buf[0];
+	}
+	gspca_dbg(gspca_dev, D_STREAM,
+		  "Read info: %d %d %d %d %d %d. Should be 1,0,2,2,0,0\n",
+		  info[0], info[1], info[2],
+		  info[3], info[4], info[5]);
+}
+
+static void spca504A_acknowledged_command(struct gspca_dev *gspca_dev,
+			u8 req,
+			u16 idx, u16 val, u8 endcode, u8 count)
+{
+	u16 status;
+
+	reg_w_riv(gspca_dev, req, idx, val);
+	reg_r(gspca_dev, 0x01, 0x0001, 1);
+	if (gspca_dev->usb_err < 0)
+		return;
+	gspca_dbg(gspca_dev, D_FRAM, "Status 0x%02x Need 0x%02x\n",
+		  gspca_dev->usb_buf[0], endcode);
+	if (!count)
+		return;
+	count = 200;
+	while (--count > 0) {
+		msleep(10);
+		/* gsmart mini2 write a each wait setting 1 ms is enough */
+/*		reg_w_riv(gspca_dev, req, idx, val); */
+		reg_r(gspca_dev, 0x01, 0x0001, 1);
+		status = gspca_dev->usb_buf[0];
+		if (status == endcode) {
+			gspca_dbg(gspca_dev, D_FRAM, "status 0x%04x after wait %d\n",
+				  status, 200 - count);
+				break;
+		}
+	}
+}
+
+static void spca504B_PollingDataReady(struct gspca_dev *gspca_dev)
+{
+	int count = 10;
+
+	while (--count > 0) {
+		reg_r(gspca_dev, 0x21, 0, 1);
+		if ((gspca_dev->usb_buf[0] & 0x01) == 0)
+			break;
+		msleep(10);
+	}
+}
+
+static void spca504B_WaitCmdStatus(struct gspca_dev *gspca_dev)
+{
+	int count = 50;
+
+	while (--count > 0) {
+		reg_r(gspca_dev, 0x21, 1, 1);
+		if (gspca_dev->usb_buf[0] != 0) {
+			reg_w_1(gspca_dev, 0x21, 0, 1, 0);
+			reg_r(gspca_dev, 0x21, 1, 1);
+			spca504B_PollingDataReady(gspca_dev);
+			break;
+		}
+		msleep(10);
+	}
+}
+
+static void spca50x_GetFirmware(struct gspca_dev *gspca_dev)
+{
+	u8 *data;
+
+	if (gspca_debug < D_STREAM)
+		return;
+
+	data = gspca_dev->usb_buf;
+	reg_r(gspca_dev, 0x20, 0, 5);
+	gspca_dbg(gspca_dev, D_STREAM, "FirmWare: %d %d %d %d %d\n",
+		  data[0], data[1], data[2], data[3], data[4]);
+	reg_r(gspca_dev, 0x23, 0, 64);
+	reg_r(gspca_dev, 0x23, 1, 64);
+}
+
+static void spca504B_SetSizeType(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 Size;
+
+	Size = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].priv;
+	switch (sd->bridge) {
+	case BRIDGE_SPCA533:
+		reg_w_riv(gspca_dev, 0x31, 0, 0);
+		spca504B_WaitCmdStatus(gspca_dev);
+		spca504B_PollingDataReady(gspca_dev);
+		spca50x_GetFirmware(gspca_dev);
+
+		reg_w_1(gspca_dev, 0x24, 0, 8, 2);		/* type */
+		reg_r(gspca_dev, 0x24, 8, 1);
+
+		reg_w_1(gspca_dev, 0x25, 0, 4, Size);
+		reg_r(gspca_dev, 0x25, 4, 1);			/* size */
+		spca504B_PollingDataReady(gspca_dev);
+
+		/* Init the cam width height with some values get on init ? */
+		reg_w_riv(gspca_dev, 0x31, 0x0004, 0x00);
+		spca504B_WaitCmdStatus(gspca_dev);
+		spca504B_PollingDataReady(gspca_dev);
+		break;
+	default:
+/* case BRIDGE_SPCA504B: */
+/* case BRIDGE_SPCA536: */
+		reg_w_1(gspca_dev, 0x25, 0, 4, Size);
+		reg_r(gspca_dev, 0x25, 4, 1);			/* size */
+		reg_w_1(gspca_dev, 0x27, 0, 0, 6);
+		reg_r(gspca_dev, 0x27, 0, 1);			/* type */
+		spca504B_PollingDataReady(gspca_dev);
+		break;
+	case BRIDGE_SPCA504:
+		Size += 3;
+		if (sd->subtype == AiptekMiniPenCam13) {
+			/* spca504a aiptek */
+			spca504A_acknowledged_command(gspca_dev,
+						0x08, Size, 0,
+						0x80 | (Size & 0x0f), 1);
+			spca504A_acknowledged_command(gspca_dev,
+							1, 3, 0, 0x9f, 0);
+		} else {
+			spca504_acknowledged_command(gspca_dev, 0x08, Size, 0);
+		}
+		break;
+	case BRIDGE_SPCA504C:
+		/* capture mode */
+		reg_w_riv(gspca_dev, 0xa0, (0x0500 | (Size & 0x0f)), 0x00);
+		reg_w_riv(gspca_dev, 0x20, 0x01, 0x0500 | (Size & 0x0f));
+		break;
+	}
+}
+
+static void spca504_wait_status(struct gspca_dev *gspca_dev)
+{
+	int cnt;
+
+	cnt = 256;
+	while (--cnt > 0) {
+		/* With this we get the status, when return 0 it's all ok */
+		reg_r(gspca_dev, 0x06, 0x00, 1);
+		if (gspca_dev->usb_buf[0] == 0)
+			return;
+		msleep(10);
+	}
+}
+
+static void spca504B_setQtable(struct gspca_dev *gspca_dev)
+{
+	reg_w_1(gspca_dev, 0x26, 0, 0, 3);
+	reg_r(gspca_dev, 0x26, 0, 1);
+	spca504B_PollingDataReady(gspca_dev);
+}
+
+static void setbrightness(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u16 reg;
+
+	reg = sd->bridge == BRIDGE_SPCA536 ? 0x20f0 : 0x21a7;
+	reg_w_riv(gspca_dev, 0x00, reg, val);
+}
+
+static void setcontrast(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u16 reg;
+
+	reg = sd->bridge == BRIDGE_SPCA536 ? 0x20f1 : 0x21a8;
+	reg_w_riv(gspca_dev, 0x00, reg, val);
+}
+
+static void setcolors(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u16 reg;
+
+	reg = sd->bridge == BRIDGE_SPCA536 ? 0x20f6 : 0x21ae;
+	reg_w_riv(gspca_dev, 0x00, reg, val);
+}
+
+static void init_ctl_reg(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int pollreg = 1;
+
+	switch (sd->bridge) {
+	case BRIDGE_SPCA504:
+	case BRIDGE_SPCA504C:
+		pollreg = 0;
+		/* fall thru */
+	default:
+/*	case BRIDGE_SPCA533: */
+/*	case BRIDGE_SPCA504B: */
+		reg_w_riv(gspca_dev, 0, 0x21ad, 0x00);	/* hue */
+		reg_w_riv(gspca_dev, 0, 0x21ac, 0x01);	/* sat/hue */
+		reg_w_riv(gspca_dev, 0, 0x21a3, 0x00);	/* gamma */
+		break;
+	case BRIDGE_SPCA536:
+		reg_w_riv(gspca_dev, 0, 0x20f5, 0x40);
+		reg_w_riv(gspca_dev, 0, 0x20f4, 0x01);
+		reg_w_riv(gspca_dev, 0, 0x2089, 0x00);
+		break;
+	}
+	if (pollreg)
+		spca504B_PollingDataReady(gspca_dev);
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam;
+
+	cam = &gspca_dev->cam;
+
+	sd->bridge = id->driver_info >> 8;
+	sd->subtype = id->driver_info;
+
+	if (sd->subtype == AiptekMiniPenCam13) {
+
+		/* try to get the firmware as some cam answer 2.0.1.2.2
+		 * and should be a spca504b then overwrite that setting */
+		reg_r(gspca_dev, 0x20, 0, 1);
+		switch (gspca_dev->usb_buf[0]) {
+		case 1:
+			break;		/* (right bridge/subtype) */
+		case 2:
+			sd->bridge = BRIDGE_SPCA504B;
+			sd->subtype = 0;
+			break;
+		default:
+			return -ENODEV;
+		}
+	}
+
+	switch (sd->bridge) {
+	default:
+/*	case BRIDGE_SPCA504B: */
+/*	case BRIDGE_SPCA504: */
+/*	case BRIDGE_SPCA536: */
+		cam->cam_mode = vga_mode;
+		cam->nmodes = ARRAY_SIZE(vga_mode);
+		break;
+	case BRIDGE_SPCA533:
+		cam->cam_mode = custom_mode;
+		if (sd->subtype == MegaImageVI)		/* 320x240 only */
+			cam->nmodes = ARRAY_SIZE(custom_mode) - 1;
+		else
+			cam->nmodes = ARRAY_SIZE(custom_mode);
+		break;
+	case BRIDGE_SPCA504C:
+		cam->cam_mode = vga_mode2;
+		cam->nmodes = ARRAY_SIZE(vga_mode2);
+		break;
+	}
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	switch (sd->bridge) {
+	case BRIDGE_SPCA504B:
+		reg_w_riv(gspca_dev, 0x1d, 0x00, 0);
+		reg_w_riv(gspca_dev, 0x00, 0x2306, 0x01);
+		reg_w_riv(gspca_dev, 0x00, 0x0d04, 0x00);
+		reg_w_riv(gspca_dev, 0x00, 0x2000, 0x00);
+		reg_w_riv(gspca_dev, 0x00, 0x2301, 0x13);
+		reg_w_riv(gspca_dev, 0x00, 0x2306, 0x00);
+		/* fall thru */
+	case BRIDGE_SPCA533:
+		spca504B_PollingDataReady(gspca_dev);
+		spca50x_GetFirmware(gspca_dev);
+		break;
+	case BRIDGE_SPCA536:
+		spca50x_GetFirmware(gspca_dev);
+		reg_r(gspca_dev, 0x00, 0x5002, 1);
+		reg_w_1(gspca_dev, 0x24, 0, 0, 0);
+		reg_r(gspca_dev, 0x24, 0, 1);
+		spca504B_PollingDataReady(gspca_dev);
+		reg_w_riv(gspca_dev, 0x34, 0, 0);
+		spca504B_WaitCmdStatus(gspca_dev);
+		break;
+	case BRIDGE_SPCA504C:	/* pccam600 */
+		gspca_dbg(gspca_dev, D_STREAM, "Opening SPCA504 (PC-CAM 600)\n");
+		reg_w_riv(gspca_dev, 0xe0, 0x0000, 0x0000);
+		reg_w_riv(gspca_dev, 0xe0, 0x0000, 0x0001);	/* reset */
+		spca504_wait_status(gspca_dev);
+		if (sd->subtype == LogitechClickSmart420)
+			write_vector(gspca_dev,
+				spca504A_clicksmart420_open_data,
+				ARRAY_SIZE(spca504A_clicksmart420_open_data));
+		else
+			write_vector(gspca_dev, spca504_pccam600_open_data,
+				ARRAY_SIZE(spca504_pccam600_open_data));
+		setup_qtable(gspca_dev, qtable_creative_pccam);
+		break;
+	default:
+/*	case BRIDGE_SPCA504: */
+		gspca_dbg(gspca_dev, D_STREAM, "Opening SPCA504\n");
+		if (sd->subtype == AiptekMiniPenCam13) {
+			spca504_read_info(gspca_dev);
+
+			/* Set AE AWB Banding Type 3-> 50Hz 2-> 60Hz */
+			spca504A_acknowledged_command(gspca_dev, 0x24,
+							8, 3, 0x9e, 1);
+			/* Twice sequential need status 0xff->0x9e->0x9d */
+			spca504A_acknowledged_command(gspca_dev, 0x24,
+							8, 3, 0x9e, 0);
+
+			spca504A_acknowledged_command(gspca_dev, 0x24,
+							0, 0, 0x9d, 1);
+			/******************************/
+			/* spca504a aiptek */
+			spca504A_acknowledged_command(gspca_dev, 0x08,
+							6, 0, 0x86, 1);
+/*			reg_write (dev, 0, 0x2000, 0); */
+/*			reg_write (dev, 0, 0x2883, 1); */
+/*			spca504A_acknowledged_command (gspca_dev, 0x08,
+							6, 0, 0x86, 1); */
+/*			spca504A_acknowledged_command (gspca_dev, 0x24,
+							0, 0, 0x9D, 1); */
+			reg_w_riv(gspca_dev, 0x00, 0x270c, 0x05);
+							/* L92 sno1t.txt */
+			reg_w_riv(gspca_dev, 0x00, 0x2310, 0x05);
+			spca504A_acknowledged_command(gspca_dev, 0x01,
+							0x0f, 0, 0xff, 0);
+		}
+		/* setup qtable */
+		reg_w_riv(gspca_dev, 0, 0x2000, 0);
+		reg_w_riv(gspca_dev, 0, 0x2883, 1);
+		setup_qtable(gspca_dev, qtable_spca504_default);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int enable;
+
+	/* create the JPEG header */
+	jpeg_define(sd->jpeg_hdr, gspca_dev->pixfmt.height,
+			gspca_dev->pixfmt.width,
+			0x22);		/* JPEG 411 */
+	jpeg_set_qual(sd->jpeg_hdr, QUALITY);
+
+	if (sd->bridge == BRIDGE_SPCA504B)
+		spca504B_setQtable(gspca_dev);
+	spca504B_SetSizeType(gspca_dev);
+	switch (sd->bridge) {
+	default:
+/*	case BRIDGE_SPCA504B: */
+/*	case BRIDGE_SPCA533: */
+/*	case BRIDGE_SPCA536: */
+		switch (sd->subtype) {
+		case MegapixV4:
+		case LogitechClickSmart820:
+		case MegaImageVI:
+			reg_w_riv(gspca_dev, 0xf0, 0, 0);
+			spca504B_WaitCmdStatus(gspca_dev);
+			reg_r(gspca_dev, 0xf0, 4, 0);
+			spca504B_WaitCmdStatus(gspca_dev);
+			break;
+		default:
+			reg_w_riv(gspca_dev, 0x31, 0x0004, 0x00);
+			spca504B_WaitCmdStatus(gspca_dev);
+			spca504B_PollingDataReady(gspca_dev);
+			break;
+		}
+		break;
+	case BRIDGE_SPCA504:
+		if (sd->subtype == AiptekMiniPenCam13) {
+			spca504_read_info(gspca_dev);
+
+			/* Set AE AWB Banding Type 3-> 50Hz 2-> 60Hz */
+			spca504A_acknowledged_command(gspca_dev, 0x24,
+							8, 3, 0x9e, 1);
+			/* Twice sequential need status 0xff->0x9e->0x9d */
+			spca504A_acknowledged_command(gspca_dev, 0x24,
+							8, 3, 0x9e, 0);
+			spca504A_acknowledged_command(gspca_dev, 0x24,
+							0, 0, 0x9d, 1);
+		} else {
+			spca504_acknowledged_command(gspca_dev, 0x24, 8, 3);
+			spca504_read_info(gspca_dev);
+			spca504_acknowledged_command(gspca_dev, 0x24, 8, 3);
+			spca504_acknowledged_command(gspca_dev, 0x24, 0, 0);
+		}
+		spca504B_SetSizeType(gspca_dev);
+		reg_w_riv(gspca_dev, 0x00, 0x270c, 0x05);
+							/* L92 sno1t.txt */
+		reg_w_riv(gspca_dev, 0x00, 0x2310, 0x05);
+		break;
+	case BRIDGE_SPCA504C:
+		if (sd->subtype == LogitechClickSmart420) {
+			write_vector(gspca_dev,
+				spca504A_clicksmart420_init_data,
+				ARRAY_SIZE(spca504A_clicksmart420_init_data));
+		} else {
+			write_vector(gspca_dev, spca504_pccam600_init_data,
+				ARRAY_SIZE(spca504_pccam600_init_data));
+		}
+		enable = (sd->autogain ? 0x04 : 0x01);
+		reg_w_riv(gspca_dev, 0x0c, 0x0000, enable);
+							/* auto exposure */
+		reg_w_riv(gspca_dev, 0xb0, 0x0000, enable);
+							/* auto whiteness */
+
+		/* set default exposure compensation and whiteness balance */
+		reg_w_riv(gspca_dev, 0x30, 0x0001, 800);	/* ~ 20 fps */
+		reg_w_riv(gspca_dev, 0x30, 0x0002, 1600);
+		spca504B_SetSizeType(gspca_dev);
+		break;
+	}
+	init_ctl_reg(gspca_dev);
+	return gspca_dev->usb_err;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	switch (sd->bridge) {
+	default:
+/*	case BRIDGE_SPCA533: */
+/*	case BRIDGE_SPCA536: */
+/*	case BRIDGE_SPCA504B: */
+		reg_w_riv(gspca_dev, 0x31, 0, 0);
+		spca504B_WaitCmdStatus(gspca_dev);
+		spca504B_PollingDataReady(gspca_dev);
+		break;
+	case BRIDGE_SPCA504:
+	case BRIDGE_SPCA504C:
+		reg_w_riv(gspca_dev, 0x00, 0x2000, 0x0000);
+
+		if (sd->subtype == AiptekMiniPenCam13) {
+			/* spca504a aiptek */
+/*			spca504A_acknowledged_command(gspca_dev, 0x08,
+							 6, 0, 0x86, 1); */
+			spca504A_acknowledged_command(gspca_dev, 0x24,
+							0x00, 0x00, 0x9d, 1);
+			spca504A_acknowledged_command(gspca_dev, 0x01,
+							0x0f, 0x00, 0xff, 1);
+		} else {
+			spca504_acknowledged_command(gspca_dev, 0x24, 0, 0);
+			reg_w_riv(gspca_dev, 0x01, 0x000f, 0x0000);
+		}
+		break;
+	}
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int i, sof = 0;
+	static u8 ffd9[] = {0xff, 0xd9};
+
+/* frames are jpeg 4.1.1 without 0xff escape */
+	switch (sd->bridge) {
+	case BRIDGE_SPCA533:
+		if (data[0] == 0xff) {
+			if (data[1] != 0x01) {	/* drop packet */
+/*				gspca_dev->last_packet_type = DISCARD_PACKET; */
+				return;
+			}
+			sof = 1;
+			data += SPCA533_OFFSET_DATA;
+			len -= SPCA533_OFFSET_DATA;
+		} else {
+			data += 1;
+			len -= 1;
+		}
+		break;
+	case BRIDGE_SPCA536:
+		if (data[0] == 0xff) {
+			sof = 1;
+			data += SPCA536_OFFSET_DATA;
+			len -= SPCA536_OFFSET_DATA;
+		} else {
+			data += 2;
+			len -= 2;
+		}
+		break;
+	default:
+/*	case BRIDGE_SPCA504: */
+/*	case BRIDGE_SPCA504B: */
+		switch (data[0]) {
+		case 0xfe:			/* start of frame */
+			sof = 1;
+			data += SPCA50X_OFFSET_DATA;
+			len -= SPCA50X_OFFSET_DATA;
+			break;
+		case 0xff:			/* drop packet */
+/*			gspca_dev->last_packet_type = DISCARD_PACKET; */
+			return;
+		default:
+			data += 1;
+			len -= 1;
+			break;
+		}
+		break;
+	case BRIDGE_SPCA504C:
+		switch (data[0]) {
+		case 0xfe:			/* start of frame */
+			sof = 1;
+			data += SPCA504_PCCAM600_OFFSET_DATA;
+			len -= SPCA504_PCCAM600_OFFSET_DATA;
+			break;
+		case 0xff:			/* drop packet */
+/*			gspca_dev->last_packet_type = DISCARD_PACKET; */
+			return;
+		default:
+			data += 1;
+			len -= 1;
+			break;
+		}
+		break;
+	}
+	if (sof) {		/* start of frame */
+		gspca_frame_add(gspca_dev, LAST_PACKET,
+				ffd9, 2);
+
+		/* put the JPEG header in the new frame */
+		gspca_frame_add(gspca_dev, FIRST_PACKET,
+			sd->jpeg_hdr, JPEG_HDR_SZ);
+	}
+
+	/* add 0x00 after 0xff */
+	i = 0;
+	do {
+		if (data[i] == 0xff) {
+			gspca_frame_add(gspca_dev, INTER_PACKET,
+					data, i + 1);
+			len -= i;
+			data += i;
+			*data = 0x00;
+			i = 0;
+		}
+		i++;
+	} while (i < len);
+	gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *)gspca_dev;
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		setbrightness(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_CONTRAST:
+		setcontrast(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_SATURATION:
+		setcolors(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_AUTOGAIN:
+		sd->autogain = ctrl->val;
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 4);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, -128, 127, 1, 0);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_CONTRAST, 0, 255, 1, 0x20);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_SATURATION, 0, 255, 1, 0x1a);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	return 0;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.pkt_scan = sd_pkt_scan,
+};
+
+/* -- module initialisation -- */
+#define BS(bridge, subtype) \
+	.driver_info = (BRIDGE_ ## bridge << 8) \
+			| (subtype)
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x041e, 0x400b), BS(SPCA504C, 0)},
+	{USB_DEVICE(0x041e, 0x4012), BS(SPCA504C, 0)},
+	{USB_DEVICE(0x041e, 0x4013), BS(SPCA504C, 0)},
+	{USB_DEVICE(0x0458, 0x7006), BS(SPCA504B, 0)},
+	{USB_DEVICE(0x0461, 0x0821), BS(SPCA533, 0)},
+	{USB_DEVICE(0x046d, 0x0905), BS(SPCA533, LogitechClickSmart820)},
+	{USB_DEVICE(0x046d, 0x0960), BS(SPCA504C, LogitechClickSmart420)},
+	{USB_DEVICE(0x0471, 0x0322), BS(SPCA504B, 0)},
+	{USB_DEVICE(0x04a5, 0x3003), BS(SPCA504B, 0)},
+	{USB_DEVICE(0x04a5, 0x3008), BS(SPCA533, 0)},
+	{USB_DEVICE(0x04a5, 0x300a), BS(SPCA533, 0)},
+	{USB_DEVICE(0x04f1, 0x1001), BS(SPCA504B, 0)},
+	{USB_DEVICE(0x04fc, 0x500c), BS(SPCA504B, 0)},
+	{USB_DEVICE(0x04fc, 0x504a), BS(SPCA504, AiptekMiniPenCam13)},
+	{USB_DEVICE(0x04fc, 0x504b), BS(SPCA504B, 0)},
+	{USB_DEVICE(0x04fc, 0x5330), BS(SPCA533, 0)},
+	{USB_DEVICE(0x04fc, 0x5360), BS(SPCA536, 0)},
+	{USB_DEVICE(0x04fc, 0xffff), BS(SPCA504B, 0)},
+	{USB_DEVICE(0x052b, 0x1507), BS(SPCA533, MegapixV4)},
+	{USB_DEVICE(0x052b, 0x1513), BS(SPCA533, MegapixV4)},
+	{USB_DEVICE(0x052b, 0x1803), BS(SPCA533, MegaImageVI)},
+	{USB_DEVICE(0x0546, 0x3155), BS(SPCA533, 0)},
+	{USB_DEVICE(0x0546, 0x3191), BS(SPCA504B, 0)},
+	{USB_DEVICE(0x0546, 0x3273), BS(SPCA504B, 0)},
+	{USB_DEVICE(0x055f, 0xc211), BS(SPCA536, 0)},
+	{USB_DEVICE(0x055f, 0xc230), BS(SPCA533, 0)},
+	{USB_DEVICE(0x055f, 0xc232), BS(SPCA533, 0)},
+	{USB_DEVICE(0x055f, 0xc360), BS(SPCA536, 0)},
+	{USB_DEVICE(0x055f, 0xc420), BS(SPCA504, 0)},
+	{USB_DEVICE(0x055f, 0xc430), BS(SPCA533, 0)},
+	{USB_DEVICE(0x055f, 0xc440), BS(SPCA533, 0)},
+	{USB_DEVICE(0x055f, 0xc520), BS(SPCA504, 0)},
+	{USB_DEVICE(0x055f, 0xc530), BS(SPCA533, 0)},
+	{USB_DEVICE(0x055f, 0xc540), BS(SPCA533, 0)},
+	{USB_DEVICE(0x055f, 0xc630), BS(SPCA533, 0)},
+	{USB_DEVICE(0x055f, 0xc650), BS(SPCA533, 0)},
+	{USB_DEVICE(0x05da, 0x1018), BS(SPCA504B, 0)},
+	{USB_DEVICE(0x06d6, 0x0031), BS(SPCA533, 0)},
+	{USB_DEVICE(0x06d6, 0x0041), BS(SPCA504B, 0)},
+	{USB_DEVICE(0x0733, 0x1311), BS(SPCA533, 0)},
+	{USB_DEVICE(0x0733, 0x1314), BS(SPCA533, 0)},
+	{USB_DEVICE(0x0733, 0x2211), BS(SPCA533, 0)},
+	{USB_DEVICE(0x0733, 0x2221), BS(SPCA533, 0)},
+	{USB_DEVICE(0x0733, 0x3261), BS(SPCA536, 0)},
+	{USB_DEVICE(0x0733, 0x3281), BS(SPCA536, 0)},
+	{USB_DEVICE(0x08ca, 0x0104), BS(SPCA533, 0)},
+	{USB_DEVICE(0x08ca, 0x0106), BS(SPCA533, 0)},
+	{USB_DEVICE(0x08ca, 0x2008), BS(SPCA504B, 0)},
+	{USB_DEVICE(0x08ca, 0x2010), BS(SPCA533, 0)},
+	{USB_DEVICE(0x08ca, 0x2016), BS(SPCA504B, 0)},
+	{USB_DEVICE(0x08ca, 0x2018), BS(SPCA504B, 0)},
+	{USB_DEVICE(0x08ca, 0x2020), BS(SPCA533, 0)},
+	{USB_DEVICE(0x08ca, 0x2022), BS(SPCA533, 0)},
+	{USB_DEVICE(0x08ca, 0x2024), BS(SPCA536, 0)},
+	{USB_DEVICE(0x08ca, 0x2028), BS(SPCA533, 0)},
+	{USB_DEVICE(0x08ca, 0x2040), BS(SPCA536, 0)},
+	{USB_DEVICE(0x08ca, 0x2042), BS(SPCA536, 0)},
+	{USB_DEVICE(0x08ca, 0x2050), BS(SPCA536, 0)},
+	{USB_DEVICE(0x08ca, 0x2060), BS(SPCA536, 0)},
+	{USB_DEVICE(0x0d64, 0x0303), BS(SPCA536, 0)},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/t613.c b/drivers/media/usb/gspca/t613.c
new file mode 100644
index 0000000..4457829
--- /dev/null
+++ b/drivers/media/usb/gspca/t613.c
@@ -0,0 +1,1050 @@
+/*
+ * T613 subdriver
+ *
+ * Copyright (C) 2010 Jean-Francois Moine (http://moinejf.free.fr)
+ *
+ * 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
+ * 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.
+ *
+ *Notes: * t613  + tas5130A
+ *	* Focus to light do not balance well as in win.
+ *	  Quality in win is not good, but its kinda better.
+ *	 * Fix some "extraneous bytes", most of apps will show the image anyway
+ *	 * Gamma table, is there, but its really doing something?
+ *	 * 7~8 Fps, its ok, max on win its 10.
+ *			Costantino Leandro
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "t613"
+
+#include <linux/input.h>
+#include <linux/slab.h>
+#include "gspca.h"
+
+MODULE_AUTHOR("Leandro Costantino <le_costantino@pixartargentina.com.ar>");
+MODULE_DESCRIPTION("GSPCA/T613 (JPEG Compliance) USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+	struct v4l2_ctrl *freq;
+	struct { /* awb / color gains control cluster */
+		struct v4l2_ctrl *awb;
+		struct v4l2_ctrl *gain;
+		struct v4l2_ctrl *red_balance;
+		struct v4l2_ctrl *blue_balance;
+	};
+
+	u8 sensor;
+	u8 button_pressed;
+};
+enum sensors {
+	SENSOR_OM6802,
+	SENSOR_OTHER,
+	SENSOR_TAS5130A,
+	SENSOR_LT168G,		/* must verify if this is the actual model */
+};
+
+static const struct v4l2_pix_format vga_mode_t16[] = {
+	{160, 120, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 160 * 120 * 4 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 4},
+#if 0 /* HDG: broken with my test cam, so lets disable it */
+	{176, 144, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 176,
+		.sizeimage = 176 * 144 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 3},
+#endif
+	{320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 2},
+#if 0 /* HDG: broken with my test cam, so lets disable it */
+	{352, 288, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 352,
+		.sizeimage = 352 * 288 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 1},
+#endif
+	{640, 480, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 0},
+};
+
+/* sensor specific data */
+struct additional_sensor_data {
+	const u8 n3[6];
+	const u8 *n4, n4sz;
+	const u8 reg80, reg8e;
+	const u8 nset8[6];
+	const u8 data1[10];
+	const u8 data2[9];
+	const u8 data3[9];
+	const u8 data5[6];
+	const u8 stream[4];
+};
+
+static const u8 n4_om6802[] = {
+	0x09, 0x01, 0x12, 0x04, 0x66, 0x8a, 0x80, 0x3c,
+	0x81, 0x22, 0x84, 0x50, 0x8a, 0x78, 0x8b, 0x68,
+	0x8c, 0x88, 0x8e, 0x33, 0x8f, 0x24, 0xaa, 0xb1,
+	0xa2, 0x60, 0xa5, 0x30, 0xa6, 0x3a, 0xa8, 0xe8,
+	0xae, 0x05, 0xb1, 0x00, 0xbb, 0x04, 0xbc, 0x48,
+	0xbe, 0x36, 0xc6, 0x88, 0xe9, 0x00, 0xc5, 0xc0,
+	0x65, 0x0a, 0xbb, 0x86, 0xaf, 0x58, 0xb0, 0x68,
+	0x87, 0x40, 0x89, 0x2b, 0x8d, 0xff, 0x83, 0x40,
+	0xac, 0x84, 0xad, 0x86, 0xaf, 0x46
+};
+static const u8 n4_other[] = {
+	0x66, 0x00, 0x7f, 0x00, 0x80, 0xac, 0x81, 0x69,
+	0x84, 0x40, 0x85, 0x70, 0x86, 0x20, 0x8a, 0x68,
+	0x8b, 0x58, 0x8c, 0x88, 0x8d, 0xff, 0x8e, 0xb8,
+	0x8f, 0x28, 0xa2, 0x60, 0xa5, 0x40, 0xa8, 0xa8,
+	0xac, 0x84, 0xad, 0x84, 0xae, 0x24, 0xaf, 0x56,
+	0xb0, 0x68, 0xb1, 0x00, 0xb2, 0x88, 0xbb, 0xc5,
+	0xbc, 0x4a, 0xbe, 0x36, 0xc2, 0x88, 0xc5, 0xc0,
+	0xc6, 0xda, 0xe9, 0x26, 0xeb, 0x00
+};
+static const u8 n4_tas5130a[] = {
+	0x80, 0x3c, 0x81, 0x68, 0x83, 0xa0, 0x84, 0x20,
+	0x8a, 0x68, 0x8b, 0x58, 0x8c, 0x88, 0x8e, 0xb4,
+	0x8f, 0x24, 0xa1, 0xb1, 0xa2, 0x30, 0xa5, 0x10,
+	0xa6, 0x4a, 0xae, 0x03, 0xb1, 0x44, 0xb2, 0x08,
+	0xb7, 0x06, 0xb9, 0xe7, 0xbb, 0xc4, 0xbc, 0x4a,
+	0xbe, 0x36, 0xbf, 0xff, 0xc2, 0x88, 0xc5, 0xc8,
+	0xc6, 0xda
+};
+static const u8 n4_lt168g[] = {
+	0x66, 0x01, 0x7f, 0x00, 0x80, 0x7c, 0x81, 0x28,
+	0x83, 0x44, 0x84, 0x20, 0x86, 0x20, 0x8a, 0x70,
+	0x8b, 0x58, 0x8c, 0x88, 0x8d, 0xa0, 0x8e, 0xb3,
+	0x8f, 0x24, 0xa1, 0xb0, 0xa2, 0x38, 0xa5, 0x20,
+	0xa6, 0x4a, 0xa8, 0xe8, 0xaf, 0x38, 0xb0, 0x68,
+	0xb1, 0x44, 0xb2, 0x88, 0xbb, 0x86, 0xbd, 0x40,
+	0xbe, 0x26, 0xc1, 0x05, 0xc2, 0x88, 0xc5, 0xc0,
+	0xda, 0x8e, 0xdb, 0xca, 0xdc, 0xa8, 0xdd, 0x8c,
+	0xde, 0x44, 0xdf, 0x0c, 0xe9, 0x80
+};
+
+static const struct additional_sensor_data sensor_data[] = {
+[SENSOR_OM6802] = {
+	.n3 =
+		{0x61, 0x68, 0x65, 0x0a, 0x60, 0x04},
+	.n4 = n4_om6802,
+	.n4sz = sizeof n4_om6802,
+	.reg80 = 0x3c,
+	.reg8e = 0x33,
+	.nset8 = {0xa8, 0xf0, 0xc6, 0x88, 0xc0, 0x00},
+	.data1 =
+		{0xc2, 0x28, 0x0f, 0x22, 0xcd, 0x27, 0x2c, 0x06,
+		 0xb3, 0xfc},
+	.data2 =
+		{0x80, 0xff, 0xff, 0x80, 0xff, 0xff, 0x80, 0xff,
+		 0xff},
+	.data3 =
+		{0x80, 0xff, 0xff, 0x80, 0xff, 0xff, 0x80, 0xff,
+		 0xff},
+	.data5 =	/* this could be removed later */
+		{0x0c, 0x03, 0xab, 0x13, 0x81, 0x23},
+	.stream =
+		{0x0b, 0x04, 0x0a, 0x78},
+    },
+[SENSOR_OTHER] = {
+	.n3 =
+		{0x61, 0xc2, 0x65, 0x88, 0x60, 0x00},
+	.n4 = n4_other,
+	.n4sz = sizeof n4_other,
+	.reg80 = 0xac,
+	.reg8e = 0xb8,
+	.nset8 = {0xa8, 0xa8, 0xc6, 0xda, 0xc0, 0x00},
+	.data1 =
+		{0xc1, 0x48, 0x04, 0x1b, 0xca, 0x2e, 0x33, 0x3a,
+		 0xe8, 0xfc},
+	.data2 =
+		{0x4e, 0x9c, 0xec, 0x40, 0x80, 0xc0, 0x48, 0x96,
+		 0xd9},
+	.data3 =
+		{0x4e, 0x9c, 0xec, 0x40, 0x80, 0xc0, 0x48, 0x96,
+		 0xd9},
+	.data5 =
+		{0x0c, 0x03, 0xab, 0x29, 0x81, 0x69},
+	.stream =
+		{0x0b, 0x04, 0x0a, 0x00},
+    },
+[SENSOR_TAS5130A] = {
+	.n3 =
+		{0x61, 0xc2, 0x65, 0x0d, 0x60, 0x08},
+	.n4 = n4_tas5130a,
+	.n4sz = sizeof n4_tas5130a,
+	.reg80 = 0x3c,
+	.reg8e = 0xb4,
+	.nset8 = {0xa8, 0xf0, 0xc6, 0xda, 0xc0, 0x00},
+	.data1 =
+		{0xbb, 0x28, 0x10, 0x10, 0xbb, 0x28, 0x1e, 0x27,
+		 0xc8, 0xfc},
+	.data2 =
+		{0x60, 0xa8, 0xe0, 0x60, 0xa8, 0xe0, 0x60, 0xa8,
+		 0xe0},
+	.data3 =
+		{0x60, 0xa8, 0xe0, 0x60, 0xa8, 0xe0, 0x60, 0xa8,
+		 0xe0},
+	.data5 =
+		{0x0c, 0x03, 0xab, 0x10, 0x81, 0x20},
+	.stream =
+		{0x0b, 0x04, 0x0a, 0x40},
+    },
+[SENSOR_LT168G] = {
+	.n3 = {0x61, 0xc2, 0x65, 0x68, 0x60, 0x00},
+	.n4 = n4_lt168g,
+	.n4sz = sizeof n4_lt168g,
+	.reg80 = 0x7c,
+	.reg8e = 0xb3,
+	.nset8 = {0xa8, 0xf0, 0xc6, 0xba, 0xc0, 0x00},
+	.data1 = {0xc0, 0x38, 0x08, 0x10, 0xc0, 0x30, 0x10, 0x40,
+		 0xb0, 0xf4},
+	.data2 = {0x40, 0x80, 0xc0, 0x50, 0xa0, 0xf0, 0x53, 0xa6,
+		 0xff},
+	.data3 = {0x40, 0x80, 0xc0, 0x50, 0xa0, 0xf0, 0x53, 0xa6,
+		 0xff},
+	.data5 = {0x0c, 0x03, 0xab, 0x4b, 0x81, 0x2b},
+	.stream = {0x0b, 0x04, 0x0a, 0x28},
+    },
+};
+
+#define MAX_EFFECTS 7
+static const u8 effects_table[MAX_EFFECTS][6] = {
+	{0xa8, 0xe8, 0xc6, 0xd2, 0xc0, 0x00},	/* Normal */
+	{0xa8, 0xc8, 0xc6, 0x52, 0xc0, 0x04},	/* Repujar */
+	{0xa8, 0xe8, 0xc6, 0xd2, 0xc0, 0x20},	/* Monochrome */
+	{0xa8, 0xe8, 0xc6, 0xd2, 0xc0, 0x80},	/* Sepia */
+	{0xa8, 0xc8, 0xc6, 0x52, 0xc0, 0x02},	/* Croquis */
+	{0xa8, 0xc8, 0xc6, 0xd2, 0xc0, 0x10},	/* Sun Effect */
+	{0xa8, 0xc8, 0xc6, 0xd2, 0xc0, 0x40},	/* Negative */
+};
+
+#define GAMMA_MAX (15)
+static const u8 gamma_table[GAMMA_MAX+1][17] = {
+/* gamma table from cam1690.ini */
+	{0x00, 0x00, 0x01, 0x04, 0x08, 0x0e, 0x16, 0x21,	/* 0 */
+	 0x2e, 0x3d, 0x50, 0x65, 0x7d, 0x99, 0xb8, 0xdb,
+	 0xff},
+	{0x00, 0x01, 0x03, 0x08, 0x0e, 0x16, 0x21, 0x2d,	/* 1 */
+	 0x3c, 0x4d, 0x60, 0x75, 0x8d, 0xa6, 0xc2, 0xe1,
+	 0xff},
+	{0x00, 0x01, 0x05, 0x0b, 0x12, 0x1c, 0x28, 0x35,	/* 2 */
+	 0x45, 0x56, 0x69, 0x7e, 0x95, 0xad, 0xc7, 0xe3,
+	 0xff},
+	{0x00, 0x02, 0x07, 0x0f, 0x18, 0x24, 0x30, 0x3f,	/* 3 */
+	 0x4f, 0x61, 0x73, 0x88, 0x9d, 0xb4, 0xcd, 0xe6,
+	 0xff},
+	{0x00, 0x04, 0x0b, 0x15, 0x20, 0x2d, 0x3b, 0x4a,	/* 4 */
+	 0x5b, 0x6c, 0x7f, 0x92, 0xa7, 0xbc, 0xd2, 0xe9,
+	 0xff},
+	{0x00, 0x07, 0x11, 0x15, 0x20, 0x2d, 0x48, 0x58,	/* 5 */
+	 0x68, 0x79, 0x8b, 0x9d, 0xb0, 0xc4, 0xd7, 0xec,
+	 0xff},
+	{0x00, 0x0c, 0x1a, 0x29, 0x38, 0x47, 0x57, 0x67,	/* 6 */
+	 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee,
+	 0xff},
+	{0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70,	/* 7 */
+	 0x80, 0x90, 0xa0, 0xb0, 0xc0, 0xd0, 0xe0, 0xf0,
+	 0xff},
+	{0x00, 0x15, 0x27, 0x38, 0x49, 0x59, 0x69, 0x79,	/* 8 */
+	 0x88, 0x97, 0xa7, 0xb6, 0xc4, 0xd3, 0xe2, 0xf0,
+	 0xff},
+	{0x00, 0x1c, 0x30, 0x43, 0x54, 0x65, 0x75, 0x84,	/* 9 */
+	 0x93, 0xa1, 0xb0, 0xbd, 0xca, 0xd8, 0xe5, 0xf2,
+	 0xff},
+	{0x00, 0x24, 0x3b, 0x4f, 0x60, 0x70, 0x80, 0x8e,	/* 10 */
+	 0x9c, 0xaa, 0xb7, 0xc4, 0xd0, 0xdc, 0xe8, 0xf3,
+	 0xff},
+	{0x00, 0x2a, 0x3c, 0x5d, 0x6e, 0x7e, 0x8d, 0x9b,	/* 11 */
+	 0xa8, 0xb4, 0xc0, 0xcb, 0xd6, 0xe1, 0xeb, 0xf5,
+	 0xff},
+	{0x00, 0x3f, 0x5a, 0x6e, 0x7f, 0x8e, 0x9c, 0xa8,	/* 12 */
+	 0xb4, 0xbf, 0xc9, 0xd3, 0xdc, 0xe5, 0xee, 0xf6,
+	 0xff},
+	{0x00, 0x54, 0x6f, 0x83, 0x93, 0xa0, 0xad, 0xb7,	/* 13 */
+	 0xc2, 0xcb, 0xd4, 0xdc, 0xe4, 0xeb, 0xf2, 0xf9,
+	 0xff},
+	{0x00, 0x6e, 0x88, 0x9a, 0xa8, 0xb3, 0xbd, 0xc6,	/* 14 */
+	 0xcf, 0xd6, 0xdd, 0xe3, 0xe9, 0xef, 0xf4, 0xfa,
+	 0xff},
+	{0x00, 0x93, 0xa8, 0xb7, 0xc1, 0xca, 0xd2, 0xd8,	/* 15 */
+	 0xde, 0xe3, 0xe8, 0xed, 0xf1, 0xf5, 0xf8, 0xfc,
+	 0xff}
+};
+
+static const u8 tas5130a_sensor_init[][8] = {
+	{0x62, 0x08, 0x63, 0x70, 0x64, 0x1d, 0x60, 0x09},
+	{0x62, 0x20, 0x63, 0x01, 0x64, 0x02, 0x60, 0x09},
+	{0x62, 0x07, 0x63, 0x03, 0x64, 0x00, 0x60, 0x09},
+};
+
+static u8 sensor_reset[] = {0x61, 0x68, 0x62, 0xff, 0x60, 0x07};
+
+/* read 1 byte */
+static u8 reg_r(struct gspca_dev *gspca_dev,
+		   u16 index)
+{
+	usb_control_msg(gspca_dev->dev,
+			usb_rcvctrlpipe(gspca_dev->dev, 0),
+			0,		/* request */
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0,		/* value */
+			index,
+			gspca_dev->usb_buf, 1, 500);
+	return gspca_dev->usb_buf[0];
+}
+
+static void reg_w(struct gspca_dev *gspca_dev,
+		  u16 index)
+{
+	usb_control_msg(gspca_dev->dev,
+			usb_sndctrlpipe(gspca_dev->dev, 0),
+			0,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0, index,
+			NULL, 0, 500);
+}
+
+static void reg_w_buf(struct gspca_dev *gspca_dev,
+		  const u8 *buffer, u16 len)
+{
+	if (len <= USB_BUF_SZ) {
+		memcpy(gspca_dev->usb_buf, buffer, len);
+		usb_control_msg(gspca_dev->dev,
+				usb_sndctrlpipe(gspca_dev->dev, 0),
+				0,
+			   USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+				0x01, 0,
+				gspca_dev->usb_buf, len, 500);
+	} else {
+		u8 *tmpbuf;
+
+		tmpbuf = kmemdup(buffer, len, GFP_KERNEL);
+		if (!tmpbuf) {
+			pr_err("Out of memory\n");
+			return;
+		}
+		usb_control_msg(gspca_dev->dev,
+				usb_sndctrlpipe(gspca_dev->dev, 0),
+				0,
+			   USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+				0x01, 0,
+				tmpbuf, len, 500);
+		kfree(tmpbuf);
+	}
+}
+
+/* write values to consecutive registers */
+static void reg_w_ixbuf(struct gspca_dev *gspca_dev,
+			u8 reg,
+			const u8 *buffer, u16 len)
+{
+	int i;
+	u8 *p, *tmpbuf;
+
+	if (len * 2 <= USB_BUF_SZ) {
+		p = tmpbuf = gspca_dev->usb_buf;
+	} else {
+		p = tmpbuf = kmalloc_array(len, 2, GFP_KERNEL);
+		if (!tmpbuf) {
+			pr_err("Out of memory\n");
+			return;
+		}
+	}
+	i = len;
+	while (--i >= 0) {
+		*p++ = reg++;
+		*p++ = *buffer++;
+	}
+	usb_control_msg(gspca_dev->dev,
+			usb_sndctrlpipe(gspca_dev->dev, 0),
+			0,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0x01, 0,
+			tmpbuf, len * 2, 500);
+	if (len * 2 > USB_BUF_SZ)
+		kfree(tmpbuf);
+}
+
+static void om6802_sensor_init(struct gspca_dev *gspca_dev)
+{
+	int i;
+	const u8 *p;
+	u8 byte;
+	u8 val[6] = {0x62, 0, 0x64, 0, 0x60, 0x05};
+	static const u8 sensor_init[] = {
+		0xdf, 0x6d,
+		0xdd, 0x18,
+		0x5a, 0xe0,
+		0x5c, 0x07,
+		0x5d, 0xb0,
+		0x5e, 0x1e,
+		0x60, 0x71,
+		0xef, 0x00,
+		0xe9, 0x00,
+		0xea, 0x00,
+		0x90, 0x24,
+		0x91, 0xb2,
+		0x82, 0x32,
+		0xfd, 0x41,
+		0x00			/* table end */
+	};
+
+	reg_w_buf(gspca_dev, sensor_reset, sizeof sensor_reset);
+	msleep(100);
+	i = 4;
+	while (--i > 0) {
+		byte = reg_r(gspca_dev, 0x0060);
+		if (!(byte & 0x01))
+			break;
+		msleep(100);
+	}
+	byte = reg_r(gspca_dev, 0x0063);
+	if (byte != 0x17) {
+		pr_err("Bad sensor reset %02x\n", byte);
+		/* continue? */
+	}
+
+	p = sensor_init;
+	while (*p != 0) {
+		val[1] = *p++;
+		val[3] = *p++;
+		if (*p == 0)
+			reg_w(gspca_dev, 0x3c80);
+		reg_w_buf(gspca_dev, val, sizeof val);
+		i = 4;
+		while (--i >= 0) {
+			msleep(15);
+			byte = reg_r(gspca_dev, 0x60);
+			if (!(byte & 0x01))
+				break;
+		}
+	}
+	msleep(15);
+	reg_w(gspca_dev, 0x3c80);
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+		     const struct usb_device_id *id)
+{
+	struct cam *cam  = &gspca_dev->cam;
+
+	cam->cam_mode = vga_mode_t16;
+	cam->nmodes = ARRAY_SIZE(vga_mode_t16);
+
+	return 0;
+}
+
+static void setbrightness(struct gspca_dev *gspca_dev, s32 brightness)
+{
+	u8 set6[4] = { 0x8f, 0x24, 0xc3, 0x00 };
+
+	if (brightness < 7) {
+		set6[1] = 0x26;
+		set6[3] = 0x70 - brightness * 0x10;
+	} else {
+		set6[3] = 0x00 + ((brightness - 7) * 0x10);
+	}
+
+	reg_w_buf(gspca_dev, set6, sizeof set6);
+}
+
+static void setcontrast(struct gspca_dev *gspca_dev, s32 contrast)
+{
+	u16 reg_to_write;
+
+	if (contrast < 7)
+		reg_to_write = 0x8ea9 - contrast * 0x200;
+	else
+		reg_to_write = 0x00a9 + (contrast - 7) * 0x200;
+
+	reg_w(gspca_dev, reg_to_write);
+}
+
+static void setcolors(struct gspca_dev *gspca_dev, s32 val)
+{
+	u16 reg_to_write;
+
+	reg_to_write = 0x80bb + val * 0x100;	/* was 0xc0 */
+	reg_w(gspca_dev, reg_to_write);
+}
+
+static void setgamma(struct gspca_dev *gspca_dev, s32 val)
+{
+	gspca_dbg(gspca_dev, D_CONF, "Gamma: %d\n", val);
+	reg_w_ixbuf(gspca_dev, 0x90,
+		gamma_table[val], sizeof gamma_table[0]);
+}
+
+static void setawb_n_RGB(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 all_gain_reg[8] = {
+		0x87, 0x00, 0x88, 0x00, 0x89, 0x00, 0x80, 0x00 };
+	s32 red_gain, blue_gain, green_gain;
+
+	green_gain = sd->gain->val;
+
+	red_gain = green_gain + sd->red_balance->val;
+	if (red_gain > 0x40)
+		red_gain = 0x40;
+	else if (red_gain < 0x10)
+		red_gain = 0x10;
+
+	blue_gain = green_gain + sd->blue_balance->val;
+	if (blue_gain > 0x40)
+		blue_gain = 0x40;
+	else if (blue_gain < 0x10)
+		blue_gain = 0x10;
+
+	all_gain_reg[1] = red_gain;
+	all_gain_reg[3] = blue_gain;
+	all_gain_reg[5] = green_gain;
+	all_gain_reg[7] = sensor_data[sd->sensor].reg80;
+	if (!sd->awb->val)
+		all_gain_reg[7] &= ~0x04; /* AWB off */
+
+	reg_w_buf(gspca_dev, all_gain_reg, sizeof all_gain_reg);
+}
+
+static void setsharpness(struct gspca_dev *gspca_dev, s32 val)
+{
+	u16 reg_to_write;
+
+	reg_to_write = 0x0aa6 + 0x1000 * val;
+
+	reg_w(gspca_dev, reg_to_write);
+}
+
+static void setfreq(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 reg66;
+	u8 freq[4] = { 0x66, 0x00, 0xa8, 0xe8 };
+
+	switch (sd->sensor) {
+	case SENSOR_LT168G:
+		if (val != 0)
+			freq[3] = 0xa8;
+		reg66 = 0x41;
+		break;
+	case SENSOR_OM6802:
+		reg66 = 0xca;
+		break;
+	default:
+		reg66 = 0x40;
+		break;
+	}
+	switch (val) {
+	case 0:				/* no flicker */
+		freq[3] = 0xf0;
+		break;
+	case 2:				/* 60Hz */
+		reg66 &= ~0x40;
+		break;
+	}
+	freq[1] = reg66;
+
+	reg_w_buf(gspca_dev, freq, sizeof freq);
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	/* some of this registers are not really needed, because
+	 * they are overridden by setbrigthness, setcontrast, etc.,
+	 * but won't hurt anyway, and can help someone with similar webcam
+	 * to see the initial parameters.*/
+	struct sd *sd = (struct sd *) gspca_dev;
+	const struct additional_sensor_data *sensor;
+	int i;
+	u16 sensor_id;
+	u8 test_byte = 0;
+
+	static const u8 read_indexs[] =
+		{ 0x0a, 0x0b, 0x66, 0x80, 0x81, 0x8e, 0x8f, 0xa5,
+		  0xa6, 0xa8, 0xbb, 0xbc, 0xc6, 0x00 };
+	static const u8 n1[] =
+			{0x08, 0x03, 0x09, 0x03, 0x12, 0x04};
+	static const u8 n2[] =
+			{0x08, 0x00};
+
+	sensor_id = (reg_r(gspca_dev, 0x06) << 8)
+			| reg_r(gspca_dev, 0x07);
+	switch (sensor_id & 0xff0f) {
+	case 0x0801:
+		gspca_dbg(gspca_dev, D_PROBE, "sensor tas5130a\n");
+		sd->sensor = SENSOR_TAS5130A;
+		break;
+	case 0x0802:
+		gspca_dbg(gspca_dev, D_PROBE, "sensor lt168g\n");
+		sd->sensor = SENSOR_LT168G;
+		break;
+	case 0x0803:
+		gspca_dbg(gspca_dev, D_PROBE, "sensor 'other'\n");
+		sd->sensor = SENSOR_OTHER;
+		break;
+	case 0x0807:
+		gspca_dbg(gspca_dev, D_PROBE, "sensor om6802\n");
+		sd->sensor = SENSOR_OM6802;
+		break;
+	default:
+		pr_err("unknown sensor %04x\n", sensor_id);
+		return -EINVAL;
+	}
+
+	if (sd->sensor == SENSOR_OM6802) {
+		reg_w_buf(gspca_dev, n1, sizeof n1);
+		i = 5;
+		while (--i >= 0) {
+			reg_w_buf(gspca_dev, sensor_reset, sizeof sensor_reset);
+			test_byte = reg_r(gspca_dev, 0x0063);
+			msleep(100);
+			if (test_byte == 0x17)
+				break;		/* OK */
+		}
+		if (i < 0) {
+			pr_err("Bad sensor reset %02x\n", test_byte);
+			return -EIO;
+		}
+		reg_w_buf(gspca_dev, n2, sizeof n2);
+	}
+
+	i = 0;
+	while (read_indexs[i] != 0x00) {
+		test_byte = reg_r(gspca_dev, read_indexs[i]);
+		gspca_dbg(gspca_dev, D_STREAM, "Reg 0x%02x = 0x%02x\n",
+			  read_indexs[i], test_byte);
+		i++;
+	}
+
+	sensor = &sensor_data[sd->sensor];
+	reg_w_buf(gspca_dev, sensor->n3, sizeof sensor->n3);
+	reg_w_buf(gspca_dev, sensor->n4, sensor->n4sz);
+
+	if (sd->sensor == SENSOR_LT168G) {
+		test_byte = reg_r(gspca_dev, 0x80);
+		gspca_dbg(gspca_dev, D_STREAM, "Reg 0x%02x = 0x%02x\n", 0x80,
+			  test_byte);
+		reg_w(gspca_dev, 0x6c80);
+	}
+
+	reg_w_ixbuf(gspca_dev, 0xd0, sensor->data1, sizeof sensor->data1);
+	reg_w_ixbuf(gspca_dev, 0xc7, sensor->data2, sizeof sensor->data2);
+	reg_w_ixbuf(gspca_dev, 0xe0, sensor->data3, sizeof sensor->data3);
+
+	reg_w(gspca_dev, (sensor->reg80 << 8) + 0x80);
+	reg_w(gspca_dev, (sensor->reg80 << 8) + 0x80);
+	reg_w(gspca_dev, (sensor->reg8e << 8) + 0x8e);
+	reg_w(gspca_dev, (0x20 << 8) + 0x87);
+	reg_w(gspca_dev, (0x20 << 8) + 0x88);
+	reg_w(gspca_dev, (0x20 << 8) + 0x89);
+
+	reg_w_buf(gspca_dev, sensor->data5, sizeof sensor->data5);
+	reg_w_buf(gspca_dev, sensor->nset8, sizeof sensor->nset8);
+	reg_w_buf(gspca_dev, sensor->stream, sizeof sensor->stream);
+
+	if (sd->sensor == SENSOR_LT168G) {
+		test_byte = reg_r(gspca_dev, 0x80);
+		gspca_dbg(gspca_dev, D_STREAM, "Reg 0x%02x = 0x%02x\n", 0x80,
+			  test_byte);
+		reg_w(gspca_dev, 0x6c80);
+	}
+
+	reg_w_ixbuf(gspca_dev, 0xd0, sensor->data1, sizeof sensor->data1);
+	reg_w_ixbuf(gspca_dev, 0xc7, sensor->data2, sizeof sensor->data2);
+	reg_w_ixbuf(gspca_dev, 0xe0, sensor->data3, sizeof sensor->data3);
+
+	return 0;
+}
+
+static void setmirror(struct gspca_dev *gspca_dev, s32 val)
+{
+	u8 hflipcmd[8] =
+		{0x62, 0x07, 0x63, 0x03, 0x64, 0x00, 0x60, 0x09};
+
+	if (val)
+		hflipcmd[3] = 0x01;
+
+	reg_w_buf(gspca_dev, hflipcmd, sizeof hflipcmd);
+}
+
+static void seteffect(struct gspca_dev *gspca_dev, s32 val)
+{
+	int idx = 0;
+
+	switch (val) {
+	case V4L2_COLORFX_NONE:
+		break;
+	case V4L2_COLORFX_BW:
+		idx = 2;
+		break;
+	case V4L2_COLORFX_SEPIA:
+		idx = 3;
+		break;
+	case V4L2_COLORFX_SKETCH:
+		idx = 4;
+		break;
+	case V4L2_COLORFX_NEGATIVE:
+		idx = 6;
+		break;
+	default:
+		break;
+	}
+
+	reg_w_buf(gspca_dev, effects_table[idx],
+				sizeof effects_table[0]);
+
+	if (val == V4L2_COLORFX_SKETCH)
+		reg_w(gspca_dev, 0x4aa6);
+	else
+		reg_w(gspca_dev, 0xfaa6);
+}
+
+/* Is this really needed?
+ * i added some module parameters for test with some users */
+static void poll_sensor(struct gspca_dev *gspca_dev)
+{
+	static const u8 poll1[] =
+		{0x67, 0x05, 0x68, 0x81, 0x69, 0x80, 0x6a, 0x82,
+		 0x6b, 0x68, 0x6c, 0x69, 0x72, 0xd9, 0x73, 0x34,
+		 0x74, 0x32, 0x75, 0x92, 0x76, 0x00, 0x09, 0x01,
+		 0x60, 0x14};
+	static const u8 poll2[] =
+		{0x67, 0x02, 0x68, 0x71, 0x69, 0x72, 0x72, 0xa9,
+		 0x73, 0x02, 0x73, 0x02, 0x60, 0x14};
+	static const u8 noise03[] =	/* (some differences / ms-drv) */
+		{0xa6, 0x0a, 0xea, 0xcf, 0xbe, 0x26, 0xb1, 0x5f,
+		 0xa1, 0xb1, 0xda, 0x6b, 0xdb, 0x98, 0xdf, 0x0c,
+		 0xc2, 0x80, 0xc3, 0x10};
+
+	gspca_dbg(gspca_dev, D_STREAM, "[Sensor requires polling]\n");
+	reg_w_buf(gspca_dev, poll1, sizeof poll1);
+	reg_w_buf(gspca_dev, poll2, sizeof poll2);
+	reg_w_buf(gspca_dev, noise03, sizeof noise03);
+}
+
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	const struct additional_sensor_data *sensor;
+	int i, mode;
+	u8 t2[] = { 0x07, 0x00, 0x0d, 0x60, 0x0e, 0x80 };
+	static const u8 t3[] =
+		{ 0x07, 0x00, 0x88, 0x02, 0x06, 0x00, 0xe7, 0x01 };
+
+	mode = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].priv;
+	switch (mode) {
+	case 0:		/* 640x480 (0x00) */
+		break;
+	case 1:		/* 352x288 */
+		t2[1] = 0x40;
+		break;
+	case 2:		/* 320x240 */
+		t2[1] = 0x10;
+		break;
+	case 3:		/* 176x144 */
+		t2[1] = 0x50;
+		break;
+	default:
+/*	case 4:		 * 160x120 */
+		t2[1] = 0x20;
+		break;
+	}
+
+	switch (sd->sensor) {
+	case SENSOR_OM6802:
+		om6802_sensor_init(gspca_dev);
+		break;
+	case SENSOR_TAS5130A:
+		i = 0;
+		for (;;) {
+			reg_w_buf(gspca_dev, tas5130a_sensor_init[i],
+					 sizeof tas5130a_sensor_init[0]);
+			if (i >= ARRAY_SIZE(tas5130a_sensor_init) - 1)
+				break;
+			i++;
+		}
+		reg_w(gspca_dev, 0x3c80);
+		/* just in case and to keep sync with logs (for mine) */
+		reg_w_buf(gspca_dev, tas5130a_sensor_init[i],
+				 sizeof tas5130a_sensor_init[0]);
+		reg_w(gspca_dev, 0x3c80);
+		break;
+	}
+	sensor = &sensor_data[sd->sensor];
+	setfreq(gspca_dev, v4l2_ctrl_g_ctrl(sd->freq));
+	reg_r(gspca_dev, 0x0012);
+	reg_w_buf(gspca_dev, t2, sizeof t2);
+	reg_w_ixbuf(gspca_dev, 0xb3, t3, sizeof t3);
+	reg_w(gspca_dev, 0x0013);
+	msleep(15);
+	reg_w_buf(gspca_dev, sensor->stream, sizeof sensor->stream);
+	reg_w_buf(gspca_dev, sensor->stream, sizeof sensor->stream);
+
+	if (sd->sensor == SENSOR_OM6802)
+		poll_sensor(gspca_dev);
+
+	return 0;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	reg_w_buf(gspca_dev, sensor_data[sd->sensor].stream,
+			sizeof sensor_data[sd->sensor].stream);
+	reg_w_buf(gspca_dev, sensor_data[sd->sensor].stream,
+			sizeof sensor_data[sd->sensor].stream);
+	if (sd->sensor == SENSOR_OM6802) {
+		msleep(20);
+		reg_w(gspca_dev, 0x0309);
+	}
+#if IS_ENABLED(CONFIG_INPUT)
+	/* If the last button state is pressed, release it now! */
+	if (sd->button_pressed) {
+		input_report_key(gspca_dev->input_dev, KEY_CAMERA, 0);
+		input_sync(gspca_dev->input_dev);
+		sd->button_pressed = 0;
+	}
+#endif
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	struct sd *sd __maybe_unused = (struct sd *) gspca_dev;
+	int pkt_type;
+
+	if (data[0] == 0x5a) {
+#if IS_ENABLED(CONFIG_INPUT)
+		if (len > 20) {
+			u8 state = (data[20] & 0x80) ? 1 : 0;
+			if (sd->button_pressed != state) {
+				input_report_key(gspca_dev->input_dev,
+						 KEY_CAMERA, state);
+				input_sync(gspca_dev->input_dev);
+				sd->button_pressed = state;
+			}
+		}
+#endif
+		/* Control Packet, after this came the header again,
+		 * but extra bytes came in the packet before this,
+		 * sometimes an EOF arrives, sometimes not... */
+		return;
+	}
+	data += 2;
+	len -= 2;
+	if (data[0] == 0xff && data[1] == 0xd8)
+		pkt_type = FIRST_PACKET;
+	else if (data[len - 2] == 0xff && data[len - 1] == 0xd9)
+		pkt_type = LAST_PACKET;
+	else
+		pkt_type = INTER_PACKET;
+	gspca_frame_add(gspca_dev, pkt_type, data, len);
+}
+
+static int sd_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *)gspca_dev;
+	s32 red_gain, blue_gain, green_gain;
+
+	gspca_dev->usb_err = 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_AUTO_WHITE_BALANCE:
+		red_gain = reg_r(gspca_dev, 0x0087);
+		if (red_gain > 0x40)
+			red_gain = 0x40;
+		else if (red_gain < 0x10)
+			red_gain = 0x10;
+
+		blue_gain = reg_r(gspca_dev, 0x0088);
+		if (blue_gain > 0x40)
+			blue_gain = 0x40;
+		else if (blue_gain < 0x10)
+			blue_gain = 0x10;
+
+		green_gain = reg_r(gspca_dev, 0x0089);
+		if (green_gain > 0x40)
+			green_gain = 0x40;
+		else if (green_gain < 0x10)
+			green_gain = 0x10;
+
+		sd->gain->val = green_gain;
+		sd->red_balance->val = red_gain - green_gain;
+		sd->blue_balance->val = blue_gain - green_gain;
+		break;
+	}
+	return 0;
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		setbrightness(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_CONTRAST:
+		setcontrast(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_SATURATION:
+		setcolors(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_GAMMA:
+		setgamma(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_HFLIP:
+		setmirror(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_SHARPNESS:
+		setsharpness(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_POWER_LINE_FREQUENCY:
+		setfreq(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_BACKLIGHT_COMPENSATION:
+		reg_w(gspca_dev, ctrl->val ? 0xf48e : 0xb48e);
+		break;
+	case V4L2_CID_AUTO_WHITE_BALANCE:
+		setawb_n_RGB(gspca_dev);
+		break;
+	case V4L2_CID_COLORFX:
+		seteffect(gspca_dev, ctrl->val);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.g_volatile_ctrl = sd_g_volatile_ctrl,
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *)gspca_dev;
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 12);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0, 14, 1, 8);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_CONTRAST, 0, 0x0d, 1, 7);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_SATURATION, 0, 0xf, 1, 5);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_GAMMA, 0, GAMMA_MAX, 1, 10);
+	/* Activate lowlight, some apps dont bring up the
+	   backlight_compensation control) */
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BACKLIGHT_COMPENSATION, 0, 1, 1, 1);
+	if (sd->sensor == SENSOR_TAS5130A)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+				V4L2_CID_HFLIP, 0, 1, 1, 0);
+	sd->awb = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_AUTO_WHITE_BALANCE, 0, 1, 1, 1);
+	sd->gain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_GAIN, 0x10, 0x40, 1, 0x20);
+	sd->blue_balance = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BLUE_BALANCE, -0x30, 0x30, 1, 0);
+	sd->red_balance = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_RED_BALANCE, -0x30, 0x30, 1, 0);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_SHARPNESS, 0, 15, 1, 6);
+	v4l2_ctrl_new_std_menu(hdl, &sd_ctrl_ops,
+			V4L2_CID_COLORFX, V4L2_COLORFX_SKETCH,
+			~((1 << V4L2_COLORFX_NONE) |
+			  (1 << V4L2_COLORFX_BW) |
+			  (1 << V4L2_COLORFX_SEPIA) |
+			  (1 << V4L2_COLORFX_SKETCH) |
+			  (1 << V4L2_COLORFX_NEGATIVE)),
+			V4L2_COLORFX_NONE);
+	sd->freq = v4l2_ctrl_new_std_menu(hdl, &sd_ctrl_ops,
+			V4L2_CID_POWER_LINE_FREQUENCY,
+			V4L2_CID_POWER_LINE_FREQUENCY_60HZ, 1,
+			V4L2_CID_POWER_LINE_FREQUENCY_50HZ);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+
+	v4l2_ctrl_auto_cluster(4, &sd->awb, 0, true);
+
+	return 0;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.pkt_scan = sd_pkt_scan,
+#if IS_ENABLED(CONFIG_INPUT)
+	.other_input = 1,
+#endif
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x17a1, 0x0128)},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+		    const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+			       THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/topro.c b/drivers/media/usb/gspca/topro.c
new file mode 100644
index 0000000..6f3ec03
--- /dev/null
+++ b/drivers/media/usb/gspca/topro.c
@@ -0,0 +1,4984 @@
+/*
+ * Topro TP6800/6810 webcam driver.
+ *
+ * Copyright (C) 2011 Jean-François Moine (http://moinejf.free.fr)
+ * Copyright (C) 2009 Anders Blomdell (anders.blomdell@control.lth.se)
+ * Copyright (C) 2008 Thomas Champagne (lafeuil@gmail.com)
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include "gspca.h"
+
+MODULE_DESCRIPTION("Topro TP6800/6810 gspca webcam driver");
+MODULE_AUTHOR("Jean-Francois Moine <http://moinejf.free.fr>, Anders Blomdell <anders.blomdell@control.lth.se>");
+MODULE_LICENSE("GPL");
+
+static int force_sensor = -1;
+
+/* JPEG header */
+static const u8 jpeg_head[] = {
+	0xff, 0xd8,			/* jpeg */
+
+/* quantization table quality 50% */
+	0xff, 0xdb, 0x00, 0x84,		/* DQT */
+0,
+#define JPEG_QT0_OFFSET 7
+	0x10, 0x0b, 0x0c, 0x0e, 0x0c, 0x0a, 0x10, 0x0e,
+	0x0d, 0x0e, 0x12, 0x11, 0x10, 0x13, 0x18, 0x28,
+	0x1a, 0x18, 0x16, 0x16, 0x18, 0x31, 0x23, 0x25,
+	0x1d, 0x28, 0x3a, 0x33, 0x3d, 0x3c, 0x39, 0x33,
+	0x38, 0x37, 0x40, 0x48, 0x5c, 0x4e, 0x40, 0x44,
+	0x57, 0x45, 0x37, 0x38, 0x50, 0x6d, 0x51, 0x57,
+	0x5f, 0x62, 0x67, 0x68, 0x67, 0x3e, 0x4d, 0x71,
+	0x79, 0x70, 0x64, 0x78, 0x5c, 0x65, 0x67, 0x63,
+1,
+#define JPEG_QT1_OFFSET 72
+	0x11, 0x12, 0x12, 0x18, 0x15, 0x18, 0x2f, 0x1a,
+	0x1a, 0x2f, 0x63, 0x42, 0x38, 0x42, 0x63, 0x63,
+	0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+	0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+	0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+	0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+	0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+	0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+
+	/* Define Huffman table (thanks to Thomas Kaiser) */
+	0xff, 0xc4, 0x01, 0x5e,
+	0x00, 0x00, 0x02, 0x03,
+	0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02,
+	0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10,
+	0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04,
+	0x07, 0x05, 0x04, 0x06, 0x01, 0x00, 0x00, 0x57,
+	0x01, 0x02, 0x03, 0x00, 0x11, 0x04, 0x12, 0x21,
+	0x31, 0x13, 0x41, 0x51, 0x61, 0x05, 0x22, 0x32,
+	0x14, 0x71, 0x81, 0x91, 0x15, 0x23, 0x42, 0x52,
+	0x62, 0xa1, 0xb1, 0x06, 0x33, 0x72, 0xc1, 0xd1,
+	0x24, 0x43, 0x53, 0x82, 0x16, 0x34, 0x92, 0xa2,
+	0xe1, 0xf1, 0xf0, 0x07, 0x08, 0x17, 0x18, 0x25,
+	0x26, 0x27, 0x28, 0x35, 0x36, 0x37, 0x38, 0x44,
+	0x45, 0x46, 0x47, 0x48, 0x54, 0x55, 0x56, 0x57,
+	0x58, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x73,
+	0x74, 0x75, 0x76, 0x77, 0x78, 0x83, 0x84, 0x85,
+	0x86, 0x87, 0x88, 0x93, 0x94, 0x95, 0x96, 0x97,
+	0x98, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xb2,
+	0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xc2, 0xc3,
+	0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xd2, 0xd3, 0xd4,
+	0xd5, 0xd6, 0xd7, 0xd8, 0xe2, 0xe3, 0xe4, 0xe5,
+	0xe6, 0xe7, 0xe8, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6,
+	0xf7, 0xf8, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+	0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04,
+	0x05, 0x06, 0x07, 0x08, 0x09, 0x11, 0x00, 0x02,
+	0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05,
+	0x04, 0x06, 0x01, 0x00, 0x00, 0x57, 0x00, 0x01,
+	0x11, 0x02, 0x21, 0x03, 0x12, 0x31, 0x41, 0x13,
+	0x22, 0x51, 0x61, 0x04, 0x32, 0x71, 0x05, 0x14,
+	0x23, 0x42, 0x33, 0x52, 0x81, 0x91, 0xa1, 0xb1,
+	0xf0, 0x06, 0x15, 0xc1, 0xd1, 0xe1, 0x24, 0x43,
+	0x62, 0xf1, 0x16, 0x25, 0x34, 0x53, 0x72, 0x82,
+	0x92, 0x07, 0x08, 0x17, 0x18, 0x26, 0x27, 0x28,
+	0x35, 0x36, 0x37, 0x38, 0x44, 0x45, 0x46, 0x47,
+	0x48, 0x54, 0x55, 0x56, 0x57, 0x58, 0x63, 0x64,
+	0x65, 0x66, 0x67, 0x68, 0x73, 0x74, 0x75, 0x76,
+	0x77, 0x78, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88,
+	0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0xa2, 0xa3,
+	0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xb2, 0xb3, 0xb4,
+	0xb5, 0xb6, 0xb7, 0xb8, 0xc2, 0xc3, 0xc4, 0xc5,
+	0xc6, 0xc7, 0xc8, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6,
+	0xd7, 0xd8, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
+	0xe8, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
+	0xff, 0xc0, 0x00, 0x11,		/* SOF0 (start of frame 0 */
+	0x08,				/* data precision */
+#define JPEG_HEIGHT_OFFSET 493
+	0x01, 0xe0,			/* height */
+	0x02, 0x80,			/* width */
+	0x03,				/* component number */
+		0x01,
+			0x21,		/* samples Y = jpeg 422 */
+			0x00,		/* quant Y */
+		0x02, 0x11, 0x01,	/* samples CbCr - quant CbCr */
+		0x03, 0x11, 0x01,
+
+	0xff, 0xda, 0x00, 0x0c,		/* SOS (start of scan) */
+	0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, 0x00, 0x3f, 0x00
+#define JPEG_HDR_SZ 521
+};
+
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+	struct v4l2_ctrl *jpegqual;
+	struct v4l2_ctrl *sharpness;
+	struct v4l2_ctrl *gamma;
+	struct v4l2_ctrl *blue;
+	struct v4l2_ctrl *red;
+
+	u8 framerate;
+	u8 quality;		/* webcam current JPEG quality (0..16) */
+	s8 ag_cnt;		/* autogain / start counter for tp6810 */
+#define AG_CNT_START 13		/* check gain every N frames */
+
+	u8 bridge;
+	u8 sensor;
+
+	u8 jpeg_hdr[JPEG_HDR_SZ];
+};
+
+enum bridges {
+	BRIDGE_TP6800,
+	BRIDGE_TP6810,
+};
+
+enum sensors {
+	SENSOR_CX0342,
+	SENSOR_SOI763A,		/* ~= ov7630 / ov7648 */
+	NSENSORS
+};
+
+static const struct v4l2_pix_format vga_mode[] = {
+	{320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 4 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG},
+	{640, 480, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG}
+};
+
+/*
+ * JPEG quality
+ * index: webcam compression
+ * value: JPEG quality in %
+ */
+static const u8 jpeg_q[17] = {
+	88, 77, 67, 57, 55, 55, 45, 45, 36, 36, 30, 30, 26, 26, 22, 22, 94
+};
+
+#define BULK_OUT_SIZE		0x20
+#if BULK_OUT_SIZE > USB_BUF_SZ
+#error "USB buffer too small"
+#endif
+
+#define DEFAULT_FRAME_RATE 30
+
+static const u8 rates[] = {30, 20, 15, 10, 7, 5};
+static const struct framerates framerates[] = {
+	{
+		.rates = rates,
+		.nrates = ARRAY_SIZE(rates)
+	},
+	{
+		.rates = rates,
+		.nrates = ARRAY_SIZE(rates)
+	}
+};
+static const u8 rates_6810[] = {30, 15, 10, 7, 5};
+static const struct framerates framerates_6810[] = {
+	{
+		.rates = rates_6810,
+		.nrates = ARRAY_SIZE(rates_6810)
+	},
+	{
+		.rates = rates_6810,
+		.nrates = ARRAY_SIZE(rates_6810)
+	}
+};
+
+/*
+ * webcam quality in %
+ * the last value is the ultra fine quality
+ */
+
+/* TP6800 register offsets */
+#define TP6800_R10_SIF_TYPE		0x10
+#define TP6800_R11_SIF_CONTROL		0x11
+#define TP6800_R12_SIF_ADDR_S		0x12
+#define TP6800_R13_SIF_TX_DATA		0x13
+#define TP6800_R14_SIF_RX_DATA		0x14
+#define TP6800_R15_GPIO_PU		0x15
+#define TP6800_R16_GPIO_PD		0x16
+#define TP6800_R17_GPIO_IO		0x17
+#define TP6800_R18_GPIO_DATA		0x18
+#define TP6800_R19_SIF_ADDR_S2		0x19
+#define TP6800_R1A_SIF_TX_DATA2		0x1a
+#define TP6800_R1B_SIF_RX_DATA2		0x1b
+#define TP6800_R21_ENDP_1_CTL		0x21
+#define TP6800_R2F_TIMING_CFG		0x2f
+#define TP6800_R30_SENSOR_CFG		0x30
+#define TP6800_R31_PIXEL_START		0x31
+#define TP6800_R32_PIXEL_END_L		0x32
+#define TP6800_R33_PIXEL_END_H		0x33
+#define TP6800_R34_LINE_START		0x34
+#define TP6800_R35_LINE_END_L		0x35
+#define TP6800_R36_LINE_END_H		0x36
+#define TP6800_R37_FRONT_DARK_ST	0x37
+#define TP6800_R38_FRONT_DARK_END	0x38
+#define TP6800_R39_REAR_DARK_ST_L	0x39
+#define TP6800_R3A_REAR_DARK_ST_H	0x3a
+#define TP6800_R3B_REAR_DARK_END_L	0x3b
+#define TP6800_R3C_REAR_DARK_END_H	0x3c
+#define TP6800_R3D_HORIZ_DARK_LINE_L	0x3d
+#define TP6800_R3E_HORIZ_DARK_LINE_H	0x3e
+#define TP6800_R3F_FRAME_RATE		0x3f
+#define TP6800_R50			0x50
+#define TP6800_R51			0x51
+#define TP6800_R52			0x52
+#define TP6800_R53			0x53
+#define TP6800_R54_DARK_CFG		0x54
+#define TP6800_R55_GAMMA_R		0x55
+#define TP6800_R56_GAMMA_G		0x56
+#define TP6800_R57_GAMMA_B		0x57
+#define TP6800_R5C_EDGE_THRLD		0x5c
+#define TP6800_R5D_DEMOSAIC_CFG		0x5d
+#define TP6800_R78_FORMAT		0x78
+#define TP6800_R79_QUALITY		0x79
+#define TP6800_R7A_BLK_THRLD		0x7a
+
+/* CX0342 register offsets */
+
+#define CX0342_SENSOR_ID		0x00
+#define CX0342_VERSION_NO		0x01
+#define CX0342_ORG_X_L			0x02
+#define CX0342_ORG_X_H			0x03
+#define CX0342_ORG_Y_L			0x04
+#define CX0342_ORG_Y_H			0x05
+#define CX0342_STOP_X_L			0x06
+#define CX0342_STOP_X_H			0x07
+#define CX0342_STOP_Y_L			0x08
+#define CX0342_STOP_Y_H			0x09
+#define CX0342_FRAME_WIDTH_L		0x0a
+#define CX0342_FRAME_WIDTH_H		0x0b
+#define CX0342_FRAME_HEIGH_L		0x0c
+#define CX0342_FRAME_HEIGH_H		0x0d
+#define CX0342_EXPO_LINE_L		0x10
+#define CX0342_EXPO_LINE_H		0x11
+#define CX0342_EXPO_CLK_L		0x12
+#define CX0342_EXPO_CLK_H		0x13
+#define CX0342_RAW_GRGAIN_L		0x14
+#define CX0342_RAW_GRGAIN_H		0x15
+#define CX0342_RAW_GBGAIN_L		0x16
+#define CX0342_RAW_GBGAIN_H		0x17
+#define CX0342_RAW_RGAIN_L		0x18
+#define CX0342_RAW_RGAIN_H		0x19
+#define CX0342_RAW_BGAIN_L		0x1a
+#define CX0342_RAW_BGAIN_H		0x1b
+#define CX0342_GLOBAL_GAIN		0x1c
+#define CX0342_SYS_CTRL_0		0x20
+#define CX0342_SYS_CTRL_1		0x21
+#define CX0342_SYS_CTRL_2		0x22
+#define CX0342_BYPASS_MODE		0x23
+#define CX0342_SYS_CTRL_3		0x24
+#define CX0342_TIMING_EN		0x25
+#define CX0342_OUTPUT_CTRL		0x26
+#define CX0342_AUTO_ADC_CALIB		0x27
+#define CX0342_SYS_CTRL_4		0x28
+#define CX0342_ADCGN			0x30
+#define CX0342_SLPCR			0x31
+#define CX0342_SLPFN_LO			0x32
+#define CX0342_ADC_CTL			0x33
+#define CX0342_LVRST_BLBIAS		0x34
+#define CX0342_VTHSEL			0x35
+#define CX0342_RAMP_RIV			0x36
+#define CX0342_LDOSEL			0x37
+#define CX0342_CLOCK_GEN		0x40
+#define CX0342_SOFT_RESET		0x41
+#define CX0342_PLL			0x42
+#define CX0342_DR_ENH_PULSE_OFFSET_L	0x43
+#define CX0342_DR_ENH_PULSE_OFFSET_H	0x44
+#define CX0342_DR_ENH_PULSE_POS_L	0x45
+#define CX0342_DR_ENH_PULSE_POS_H	0x46
+#define CX0342_DR_ENH_PULSE_WIDTH	0x47
+#define CX0342_AS_CURRENT_CNT_L		0x48
+#define CX0342_AS_CURRENT_CNT_H		0x49
+#define CX0342_AS_PREVIOUS_CNT_L	0x4a
+#define CX0342_AS_PREVIOUS_CNT_H	0x4b
+#define CX0342_SPV_VALUE_L		0x4c
+#define CX0342_SPV_VALUE_H		0x4d
+#define CX0342_GPXLTHD_L		0x50
+#define CX0342_GPXLTHD_H		0x51
+#define CX0342_RBPXLTHD_L		0x52
+#define CX0342_RBPXLTHD_H		0x53
+#define CX0342_PLANETHD_L		0x54
+#define CX0342_PLANETHD_H		0x55
+#define CX0342_ROWDARK_TH		0x56
+#define CX0342_ROWDARK_TOL		0x57
+#define CX0342_RB_GAP_L			0x58
+#define CX0342_RB_GAP_H			0x59
+#define CX0342_G_GAP_L			0x5a
+#define CX0342_G_GAP_H			0x5b
+#define CX0342_AUTO_ROW_DARK		0x60
+#define CX0342_MANUAL_DARK_VALUE	0x61
+#define CX0342_GB_DARK_OFFSET		0x62
+#define CX0342_GR_DARK_OFFSET		0x63
+#define CX0342_RED_DARK_OFFSET		0x64
+#define CX0342_BLUE_DARK_OFFSET		0x65
+#define CX0342_DATA_SCALING_MULTI	0x66
+#define CX0342_AUTOD_Q_FRAME		0x67
+#define CX0342_AUTOD_ALLOW_VARI		0x68
+#define CX0342_AUTO_DARK_VALUE_L	0x69
+#define CX0342_AUTO_DARK_VALUE_H	0x6a
+#define CX0342_IO_CTRL_0		0x70
+#define CX0342_IO_CTRL_1		0x71
+#define CX0342_IO_CTRL_2		0x72
+#define CX0342_IDLE_CTRL		0x73
+#define CX0342_TEST_MODE		0x74
+#define CX0342_FRAME_FIX_DATA_TEST	0x75
+#define CX0342_FRAME_CNT_TEST		0x76
+#define CX0342_RST_OVERFLOW_L		0x80
+#define CX0342_RST_OVERFLOW_H		0x81
+#define CX0342_RST_UNDERFLOW_L		0x82
+#define CX0342_RST_UNDERFLOW_H		0x83
+#define CX0342_DATA_OVERFLOW_L		0x84
+#define CX0342_DATA_OVERFLOW_H		0x85
+#define CX0342_DATA_UNDERFLOW_L		0x86
+#define CX0342_DATA_UNDERFLOW_H		0x87
+#define CX0342_CHANNEL_0_0_L_irst	0x90
+#define CX0342_CHANNEL_0_0_H_irst	0x91
+#define CX0342_CHANNEL_0_1_L_irst	0x92
+#define CX0342_CHANNEL_0_1_H_irst	0x93
+#define CX0342_CHANNEL_0_2_L_irst	0x94
+#define CX0342_CHANNEL_0_2_H_irst	0x95
+#define CX0342_CHANNEL_0_3_L_irst	0x96
+#define CX0342_CHANNEL_0_3_H_irst	0x97
+#define CX0342_CHANNEL_0_4_L_irst	0x98
+#define CX0342_CHANNEL_0_4_H_irst	0x99
+#define CX0342_CHANNEL_0_5_L_irst	0x9a
+#define CX0342_CHANNEL_0_5_H_irst	0x9b
+#define CX0342_CHANNEL_0_6_L_irst	0x9c
+#define CX0342_CHANNEL_0_6_H_irst	0x9d
+#define CX0342_CHANNEL_0_7_L_irst	0x9e
+#define CX0342_CHANNEL_0_7_H_irst	0x9f
+#define CX0342_CHANNEL_1_0_L_itx	0xa0
+#define CX0342_CHANNEL_1_0_H_itx	0xa1
+#define CX0342_CHANNEL_1_1_L_itx	0xa2
+#define CX0342_CHANNEL_1_1_H_itx	0xa3
+#define CX0342_CHANNEL_1_2_L_itx	0xa4
+#define CX0342_CHANNEL_1_2_H_itx	0xa5
+#define CX0342_CHANNEL_1_3_L_itx	0xa6
+#define CX0342_CHANNEL_1_3_H_itx	0xa7
+#define CX0342_CHANNEL_1_4_L_itx	0xa8
+#define CX0342_CHANNEL_1_4_H_itx	0xa9
+#define CX0342_CHANNEL_1_5_L_itx	0xaa
+#define CX0342_CHANNEL_1_5_H_itx	0xab
+#define CX0342_CHANNEL_1_6_L_itx	0xac
+#define CX0342_CHANNEL_1_6_H_itx	0xad
+#define CX0342_CHANNEL_1_7_L_itx	0xae
+#define CX0342_CHANNEL_1_7_H_itx	0xaf
+#define CX0342_CHANNEL_2_0_L_iwl	0xb0
+#define CX0342_CHANNEL_2_0_H_iwl	0xb1
+#define CX0342_CHANNEL_2_1_L_iwl	0xb2
+#define CX0342_CHANNEL_2_1_H_iwl	0xb3
+#define CX0342_CHANNEL_2_2_L_iwl	0xb4
+#define CX0342_CHANNEL_2_2_H_iwl	0xb5
+#define CX0342_CHANNEL_2_3_L_iwl	0xb6
+#define CX0342_CHANNEL_2_3_H_iwl	0xb7
+#define CX0342_CHANNEL_2_4_L_iwl	0xb8
+#define CX0342_CHANNEL_2_4_H_iwl	0xb9
+#define CX0342_CHANNEL_2_5_L_iwl	0xba
+#define CX0342_CHANNEL_2_5_H_iwl	0xbb
+#define CX0342_CHANNEL_2_6_L_iwl	0xbc
+#define CX0342_CHANNEL_2_6_H_iwl	0xbd
+#define CX0342_CHANNEL_2_7_L_iwl	0xbe
+#define CX0342_CHANNEL_2_7_H_iwl	0xbf
+#define CX0342_CHANNEL_3_0_L_ensp	0xc0
+#define CX0342_CHANNEL_3_0_H_ensp	0xc1
+#define CX0342_CHANNEL_3_1_L_ensp	0xc2
+#define CX0342_CHANNEL_3_1_H_ensp	0xc3
+#define CX0342_CHANNEL_3_2_L_ensp	0xc4
+#define CX0342_CHANNEL_3_2_H_ensp	0xc5
+#define CX0342_CHANNEL_3_3_L_ensp	0xc6
+#define CX0342_CHANNEL_3_3_H_ensp	0xc7
+#define CX0342_CHANNEL_3_4_L_ensp	0xc8
+#define CX0342_CHANNEL_3_4_H_ensp	0xc9
+#define CX0342_CHANNEL_3_5_L_ensp	0xca
+#define CX0342_CHANNEL_3_5_H_ensp	0xcb
+#define CX0342_CHANNEL_3_6_L_ensp	0xcc
+#define CX0342_CHANNEL_3_6_H_ensp	0xcd
+#define CX0342_CHANNEL_3_7_L_ensp	0xce
+#define CX0342_CHANNEL_3_7_H_ensp	0xcf
+#define CX0342_CHANNEL_4_0_L_sela	0xd0
+#define CX0342_CHANNEL_4_0_H_sela	0xd1
+#define CX0342_CHANNEL_4_1_L_sela	0xd2
+#define CX0342_CHANNEL_4_1_H_sela	0xd3
+#define CX0342_CHANNEL_5_0_L_intla	0xe0
+#define CX0342_CHANNEL_5_0_H_intla	0xe1
+#define CX0342_CHANNEL_5_1_L_intla	0xe2
+#define CX0342_CHANNEL_5_1_H_intla	0xe3
+#define CX0342_CHANNEL_5_2_L_intla	0xe4
+#define CX0342_CHANNEL_5_2_H_intla	0xe5
+#define CX0342_CHANNEL_5_3_L_intla	0xe6
+#define CX0342_CHANNEL_5_3_H_intla	0xe7
+#define CX0342_CHANNEL_6_0_L_xa_sel_pos 0xf0
+#define CX0342_CHANNEL_6_0_H_xa_sel_pos 0xf1
+#define CX0342_CHANNEL_7_1_L_cds_pos	0xf2
+#define CX0342_CHANNEL_7_1_H_cds_pos	0xf3
+#define CX0342_SENSOR_HEIGHT_L		0xfb
+#define CX0342_SENSOR_HEIGHT_H		0xfc
+#define CX0342_SENSOR_WIDTH_L		0xfd
+#define CX0342_SENSOR_WIDTH_H		0xfe
+#define CX0342_VSYNC_HSYNC_READ		0xff
+
+struct cmd {
+	u8 reg;
+	u8 val;
+};
+
+static const u8 DQT[17][130] = {
+	/* Define quantization table (thanks to Thomas Kaiser) */
+	{			/* Quality 0 */
+	 0x00,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x01,
+	 0x04, 0x04, 0x04, 0x06, 0x05, 0x06, 0x0b, 0x06,
+	 0x06, 0x0b, 0x18, 0x10, 0x0e, 0x10, 0x18, 0x18,
+	 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+	 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+	 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+	 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+	 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+	 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+	 },
+	{			/* Quality 1 */
+	 0x00,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x04, 0x09, 0x09, 0x09, 0x09, 0x09,
+	 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+	 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09, 0x09,
+	 0x01,
+	 0x08, 0x09, 0x09, 0x0c, 0x0a, 0x0c, 0x17, 0x0d,
+	 0x0d, 0x17, 0x31, 0x21, 0x1c, 0x21, 0x31, 0x31,
+	 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31,
+	 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31,
+	 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31,
+	 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31,
+	 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31,
+	 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31, 0x31,
+	 },
+	{			/* Quality 2 */
+	 0x00,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x06, 0x06, 0x06, 0x04, 0x04, 0x04,
+	 0x04, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+	 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+	 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+	 0x06, 0x06, 0x06, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+	 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+	 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+	 0x01,
+	 0x0c, 0x0d, 0x0d, 0x12, 0x0f, 0x12, 0x23, 0x13,
+	 0x13, 0x23, 0x4a, 0x31, 0x2a, 0x31, 0x4a, 0x4a,
+	 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a,
+	 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a,
+	 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a,
+	 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a,
+	 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a,
+	 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a, 0x4a,
+	 },
+	{			/* Quality 3 */
+	 0x00,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x08, 0x08, 0x08, 0x04, 0x04, 0x04,
+	 0x04, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+	 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+	 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+	 0x08, 0x08, 0x08, 0x13, 0x13, 0x13, 0x13, 0x13,
+	 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
+	 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13, 0x13,
+	 0x01,
+	 0x11, 0x12, 0x12, 0x18, 0x15, 0x18, 0x2f, 0x1a,
+	 0x1a, 0x2f, 0x63, 0x42, 0x38, 0x42, 0x63, 0x63,
+	 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+	 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+	 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+	 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+	 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+	 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63,
+	 },
+	{			/* Quality 4 */
+	 0x00,
+	 0x04, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05,
+	 0x05, 0x05, 0x0a, 0x0a, 0x0a, 0x05, 0x05, 0x05,
+	 0x05, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+	 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+	 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+	 0x0a, 0x0a, 0x0a, 0x17, 0x17, 0x17, 0x17, 0x17,
+	 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17,
+	 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17, 0x17,
+	 0x01,
+	 0x11, 0x16, 0x16, 0x1e, 0x1a, 0x1e, 0x3a, 0x20,
+	 0x20, 0x3a, 0x7b, 0x52, 0x46, 0x52, 0x7b, 0x7b,
+	 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b,
+	 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b,
+	 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b,
+	 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b,
+	 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b,
+	 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b, 0x7b,
+	 },
+	{			/* Quality 5 */
+	 0x00,
+	 0x04, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+	 0x06, 0x06, 0x0c, 0x0c, 0x0c, 0x06, 0x06, 0x06,
+	 0x06, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+	 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+	 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+	 0x0c, 0x0c, 0x0c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+	 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+	 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+	 0x01,
+	 0x11, 0x1b, 0x1b, 0x24, 0x1f, 0x24, 0x46, 0x27,
+	 0x27, 0x46, 0x94, 0x63, 0x54, 0x63, 0x94, 0x94,
+	 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+	 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+	 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+	 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+	 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+	 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94, 0x94,
+	 },
+	{			/* Quality 6 */
+	 0x00,
+	 0x05, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
+	 0x07, 0x07, 0x0e, 0x0e, 0x0e, 0x07, 0x07, 0x07,
+	 0x07, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+	 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+	 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+	 0x0e, 0x0e, 0x0e, 0x21, 0x21, 0x21, 0x21, 0x21,
+	 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21,
+	 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21, 0x21,
+	 0x01,
+	 0x15, 0x1f, 0x1f, 0x2a, 0x24, 0x2a, 0x52, 0x2d,
+	 0x2d, 0x52, 0xad, 0x73, 0x62, 0x73, 0xad, 0xad,
+	 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+	 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+	 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+	 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+	 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+	 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xad,
+	 },
+	{			/* Quality 7 */
+	 0x00,
+	 0x05, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08,
+	 0x08, 0x08, 0x10, 0x10, 0x10, 0x08, 0x08, 0x08,
+	 0x08, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
+	 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
+	 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
+	 0x10, 0x10, 0x10, 0x26, 0x26, 0x26, 0x26, 0x26,
+	 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26,
+	 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26, 0x26,
+	 0x01,
+	 0x15, 0x24, 0x24, 0x30, 0x2a, 0x30, 0x5e, 0x34,
+	 0x34, 0x5e, 0xc6, 0x84, 0x70, 0x84, 0xc6, 0xc6,
+	 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+	 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+	 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+	 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+	 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+	 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+	 },
+	{			/* Quality 8 */
+	 0x00,
+	 0x06, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a, 0x0a,
+	 0x0a, 0x0a, 0x14, 0x14, 0x14, 0x0a, 0x0a, 0x0a,
+	 0x0a, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
+	 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
+	 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
+	 0x14, 0x14, 0x14, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
+	 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
+	 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f, 0x2f,
+	 0x01,
+	 0x19, 0x2d, 0x2d, 0x3c, 0x34, 0x3c, 0x75, 0x41,
+	 0x41, 0x75, 0xf7, 0xa5, 0x8c, 0xa5, 0xf7, 0xf7,
+	 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7,
+	 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7,
+	 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7,
+	 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7,
+	 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7,
+	 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7,
+	 },
+	{			/* Quality 9 */
+	 0x00,
+	 0x06, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c,
+	 0x0c, 0x0c, 0x18, 0x18, 0x18, 0x0c, 0x0c, 0x0c,
+	 0x0c, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+	 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+	 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+	 0x18, 0x18, 0x18, 0x39, 0x39, 0x39, 0x39, 0x39,
+	 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39,
+	 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39,
+	 0x01,
+	 0x19, 0x36, 0x36, 0x48, 0x3f, 0x48, 0x8d, 0x4e,
+	 0x4e, 0x8d, 0xff, 0xc6, 0xa8, 0xc6, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 },
+	{			/* Quality 10 */
+	 0x00,
+	 0x07, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e, 0x0e,
+	 0x0e, 0x0e, 0x1c, 0x1c, 0x1c, 0x0e, 0x0e, 0x0e,
+	 0x0e, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+	 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+	 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+	 0x1c, 0x1c, 0x1c, 0x42, 0x42, 0x42, 0x42, 0x42,
+	 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
+	 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42,
+	 0x01,
+	 0x1d, 0x3f, 0x3f, 0x54, 0x49, 0x54, 0xa4, 0x5b,
+	 0x5b, 0xa4, 0xff, 0xe7, 0xc4, 0xe7, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 },
+	{			/* Quality 11 */
+	 0x00,
+	 0x07, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10,
+	 0x10, 0x10, 0x20, 0x20, 0x20, 0x10, 0x10, 0x10,
+	 0x10, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+	 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+	 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+	 0x20, 0x20, 0x20, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c,
+	 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c,
+	 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c, 0x4c,
+	 0x01,
+	 0x1d, 0x48, 0x48, 0x60, 0x54, 0x60, 0xbc, 0x68,
+	 0x68, 0xbc, 0xff, 0xff, 0xe0, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 },
+	{			/* Quality 12 */
+	 0x00,
+	 0x08, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14,
+	 0x14, 0x14, 0x28, 0x28, 0x28, 0x14, 0x14, 0x14,
+	 0x14, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+	 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+	 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28, 0x28,
+	 0x28, 0x28, 0x28, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
+	 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
+	 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f, 0x5f,
+	 0x01,
+	 0x22, 0x5a, 0x5a, 0x78, 0x69, 0x78, 0xeb, 0x82,
+	 0x82, 0xeb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 },
+	{			/* Quality 13 */
+	 0x00,
+	 0x08, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18,
+	 0x18, 0x18, 0x30, 0x30, 0x30, 0x18, 0x18, 0x18,
+	 0x18, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+	 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+	 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30,
+	 0x30, 0x30, 0x30, 0x72, 0x72, 0x72, 0x72, 0x72,
+	 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72,
+	 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72,
+	 0x01,
+	 0x22, 0x6c, 0x6c, 0x90, 0x7e, 0x90, 0xff, 0x9c,
+	 0x9c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 },
+	{			/* Quality 14 */
+	 0x00,
+	 0x0a, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c, 0x1c,
+	 0x1c, 0x1c, 0x38, 0x38, 0x38, 0x1c, 0x1c, 0x1c,
+	 0x1c, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38,
+	 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38,
+	 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38, 0x38,
+	 0x38, 0x38, 0x38, 0x85, 0x85, 0x85, 0x85, 0x85,
+	 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+	 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85, 0x85,
+	 0x01,
+	 0x2a, 0x7e, 0x7e, 0xa8, 0x93, 0xa8, 0xff, 0xb6,
+	 0xb6, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 },
+	{			/* Quality 15 */
+	 0x00,
+	 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
+	 0x20, 0x20, 0x40, 0x40, 0x40, 0x20, 0x20, 0x20,
+	 0x20, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+	 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+	 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
+	 0x40, 0x40, 0x40, 0x98, 0x98, 0x98, 0x98, 0x98,
+	 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
+	 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98, 0x98,
+	 0x01,
+	 0x2a, 0x90, 0x90, 0xc0, 0xa8, 0xc0, 0xff, 0xd0,
+	 0xd0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+	 },
+	{			/* Quality 16-31 */
+	 0x00,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x01,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04,
+	 }
+};
+
+static const struct cmd tp6810_cx_init_common[] = {
+	{0x1c, 0x00},
+	{TP6800_R10_SIF_TYPE, 0x00},
+	{0x4e, 0x00},
+	{0x4f, 0x00},
+	{TP6800_R50, 0xff},
+	{TP6800_R51, 0x03},
+	{0x00, 0x07},
+	{TP6800_R79_QUALITY, 0x03},
+	{TP6800_R2F_TIMING_CFG, 0x37},
+	{TP6800_R30_SENSOR_CFG, 0x10},
+	{TP6800_R21_ENDP_1_CTL, 0x00},
+	{TP6800_R52, 0x40},
+	{TP6800_R53, 0x40},
+	{TP6800_R54_DARK_CFG, 0x40},
+	{TP6800_R30_SENSOR_CFG, 0x18},
+	{0x4b, 0x00},
+	{TP6800_R3F_FRAME_RATE, 0x83},
+	{TP6800_R79_QUALITY, 0x05},
+	{TP6800_R21_ENDP_1_CTL, 0x00},
+	{0x7c, 0x04},
+	{0x25, 0x14},
+	{0x26, 0x0f},
+	{0x7b, 0x10},
+};
+
+static const struct cmd tp6810_ov_init_common[] = {
+	{0x1c, 0x00},
+	{TP6800_R10_SIF_TYPE, 0x00},
+	{0x4e, 0x00},
+	{0x4f, 0x00},
+	{TP6800_R50, 0xff},
+	{TP6800_R51, 0x03},
+	{0x00, 0x07},
+	{TP6800_R52, 0x40},
+	{TP6800_R53, 0x40},
+	{TP6800_R54_DARK_CFG, 0x40},
+	{TP6800_R79_QUALITY, 0x03},
+	{TP6800_R2F_TIMING_CFG, 0x17},
+	{TP6800_R30_SENSOR_CFG, 0x18},
+	{TP6800_R21_ENDP_1_CTL, 0x00},
+	{TP6800_R3F_FRAME_RATE, 0x86},
+	{0x25, 0x18},
+	{0x26, 0x0f},
+	{0x7b, 0x90},
+};
+
+static const struct cmd tp6810_bridge_start[] = {
+	{0x59, 0x88},
+	{0x5a, 0x0f},
+	{0x5b, 0x4e},
+	{TP6800_R5C_EDGE_THRLD, 0x63},
+	{TP6800_R5D_DEMOSAIC_CFG, 0x00},
+	{0x03, 0x7f},
+	{0x04, 0x80},
+	{0x06, 0x00},
+	{0x00, 0x00},
+};
+
+static const struct cmd tp6810_late_start[] = {
+	{0x7d, 0x01},
+	{0xb0, 0x04},
+	{0xb1, 0x04},
+	{0xb2, 0x04},
+	{0xb3, 0x04},
+	{0xb4, 0x04},
+	{0xb5, 0x04},
+	{0xb6, 0x08},
+	{0xb7, 0x08},
+	{0xb8, 0x04},
+	{0xb9, 0x04},
+	{0xba, 0x04},
+	{0xbb, 0x04},
+	{0xbc, 0x04},
+	{0xbd, 0x08},
+	{0xbe, 0x08},
+	{0xbf, 0x08},
+	{0xc0, 0x04},
+	{0xc1, 0x04},
+	{0xc2, 0x08},
+	{0xc3, 0x08},
+	{0xc4, 0x08},
+	{0xc5, 0x08},
+	{0xc6, 0x08},
+	{0xc7, 0x13},
+	{0xc8, 0x04},
+	{0xc9, 0x08},
+	{0xca, 0x08},
+	{0xcb, 0x08},
+	{0xcc, 0x08},
+	{0xcd, 0x08},
+	{0xce, 0x13},
+	{0xcf, 0x13},
+	{0xd0, 0x08},
+	{0xd1, 0x08},
+	{0xd2, 0x08},
+	{0xd3, 0x08},
+	{0xd4, 0x08},
+	{0xd5, 0x13},
+	{0xd6, 0x13},
+	{0xd7, 0x13},
+	{0xd8, 0x08},
+	{0xd9, 0x08},
+	{0xda, 0x08},
+	{0xdb, 0x08},
+	{0xdc, 0x13},
+	{0xdd, 0x13},
+	{0xde, 0x13},
+	{0xdf, 0x13},
+	{0xe0, 0x08},
+	{0xe1, 0x08},
+	{0xe2, 0x08},
+	{0xe3, 0x13},
+	{0xe4, 0x13},
+	{0xe5, 0x13},
+	{0xe6, 0x13},
+	{0xe7, 0x13},
+	{0xe8, 0x08},
+	{0xe9, 0x08},
+	{0xea, 0x13},
+	{0xeb, 0x13},
+	{0xec, 0x13},
+	{0xed, 0x13},
+	{0xee, 0x13},
+	{0xef, 0x13},
+	{0x7d, 0x02},
+
+	/* later after isoc start */
+	{0x7d, 0x08},
+	{0x7d, 0x00},
+};
+
+static const struct cmd cx0342_timing_seq[] = {
+	{CX0342_CHANNEL_0_1_L_irst, 0x20},
+	{CX0342_CHANNEL_0_2_L_irst, 0x24},
+	{CX0342_CHANNEL_0_2_H_irst, 0x00},
+	{CX0342_CHANNEL_0_3_L_irst, 0x2f},
+	{CX0342_CHANNEL_0_3_H_irst, 0x00},
+	{CX0342_CHANNEL_1_0_L_itx, 0x02},
+	{CX0342_CHANNEL_1_0_H_itx, 0x00},
+	{CX0342_CHANNEL_1_1_L_itx, 0x20},
+	{CX0342_CHANNEL_1_1_H_itx, 0x00},
+	{CX0342_CHANNEL_1_2_L_itx, 0xe4},
+	{CX0342_CHANNEL_1_2_H_itx, 0x00},
+	{CX0342_CHANNEL_1_3_L_itx, 0xee},
+	{CX0342_CHANNEL_1_3_H_itx, 0x00},
+	{CX0342_CHANNEL_2_0_L_iwl, 0x30},
+	{CX0342_CHANNEL_2_0_H_iwl, 0x00},
+	{CX0342_CHANNEL_3_0_L_ensp, 0x34},
+	{CX0342_CHANNEL_3_1_L_ensp, 0xe2},
+	{CX0342_CHANNEL_3_1_H_ensp, 0x00},
+	{CX0342_CHANNEL_3_2_L_ensp, 0xf6},
+	{CX0342_CHANNEL_3_2_H_ensp, 0x00},
+	{CX0342_CHANNEL_3_3_L_ensp, 0xf4},
+	{CX0342_CHANNEL_3_3_H_ensp, 0x02},
+	{CX0342_CHANNEL_4_0_L_sela, 0x26},
+	{CX0342_CHANNEL_4_0_H_sela, 0x00},
+	{CX0342_CHANNEL_4_1_L_sela, 0xe2},
+	{CX0342_CHANNEL_4_1_H_sela, 0x00},
+	{CX0342_CHANNEL_5_0_L_intla, 0x26},
+	{CX0342_CHANNEL_5_1_L_intla, 0x29},
+	{CX0342_CHANNEL_5_2_L_intla, 0xf0},
+	{CX0342_CHANNEL_5_2_H_intla, 0x00},
+	{CX0342_CHANNEL_5_3_L_intla, 0xf3},
+	{CX0342_CHANNEL_5_3_H_intla, 0x00},
+	{CX0342_CHANNEL_6_0_L_xa_sel_pos, 0x24},
+	{CX0342_CHANNEL_7_1_L_cds_pos, 0x02},
+	{CX0342_TIMING_EN, 0x01},
+};
+
+/* define the JPEG header */
+static void jpeg_define(u8 *jpeg_hdr,
+			int height,
+			int width)
+{
+	memcpy(jpeg_hdr, jpeg_head, sizeof jpeg_head);
+	jpeg_hdr[JPEG_HEIGHT_OFFSET + 0] = height >> 8;
+	jpeg_hdr[JPEG_HEIGHT_OFFSET + 1] = height;
+	jpeg_hdr[JPEG_HEIGHT_OFFSET + 2] = width >> 8;
+	jpeg_hdr[JPEG_HEIGHT_OFFSET + 3] = width;
+}
+
+/* set the JPEG quality for sensor soi763a */
+static void jpeg_set_qual(u8 *jpeg_hdr,
+			  int quality)
+{
+	int i, sc;
+
+	if (quality <= 0)
+		sc = 5000;
+	else if (quality < 50)
+		sc = 5000 / quality;
+	else
+		sc = 200 - quality * 2;
+	for (i = 0; i < 64; i++) {
+		jpeg_hdr[JPEG_QT0_OFFSET + i] =
+			(jpeg_head[JPEG_QT0_OFFSET + i] * sc + 50) / 100;
+		jpeg_hdr[JPEG_QT1_OFFSET + i] =
+			(jpeg_head[JPEG_QT1_OFFSET + i] * sc + 50) / 100;
+	}
+}
+
+static void reg_w(struct gspca_dev *gspca_dev, u8 index, u8 value)
+{
+	struct usb_device *dev = gspca_dev->dev;
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+			0x0e,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			value, index, NULL, 0, 500);
+	if (ret < 0) {
+		pr_err("reg_w err %d\n", ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+/* the returned value is in gspca_dev->usb_buf */
+static void reg_r(struct gspca_dev *gspca_dev, u8 index)
+{
+	struct usb_device *dev = gspca_dev->dev;
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+			0x0d,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0, index, gspca_dev->usb_buf, 1, 500);
+	if (ret < 0) {
+		pr_err("reg_r err %d\n", ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+static void reg_w_buf(struct gspca_dev *gspca_dev,
+			const struct cmd *p, int l)
+{
+	do {
+		reg_w(gspca_dev, p->reg, p->val);
+		p++;
+	} while (--l > 0);
+}
+
+static int i2c_w(struct gspca_dev *gspca_dev, u8 index, u8 value)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	reg_w(gspca_dev, TP6800_R11_SIF_CONTROL, 0x00);
+	reg_w(gspca_dev, TP6800_R19_SIF_ADDR_S2, index);
+	reg_w(gspca_dev, TP6800_R13_SIF_TX_DATA, value);
+	reg_w(gspca_dev, TP6800_R11_SIF_CONTROL, 0x01);
+	if (sd->bridge == BRIDGE_TP6800)
+		return 0;
+	msleep(5);
+	reg_r(gspca_dev, TP6800_R11_SIF_CONTROL);
+	if (gspca_dev->usb_buf[0] == 0)
+		return 0;
+	reg_w(gspca_dev, TP6800_R11_SIF_CONTROL, 0x00);
+	return -1;				/* error */
+}
+
+static void i2c_w_buf(struct gspca_dev *gspca_dev,
+			const struct cmd *p, int l)
+{
+	do {
+		i2c_w(gspca_dev, p->reg, p->val);
+		p++;
+	} while (--l > 0);
+}
+
+static int i2c_r(struct gspca_dev *gspca_dev, u8 index, int len)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int v;
+
+	reg_w(gspca_dev, TP6800_R19_SIF_ADDR_S2, index);
+	reg_w(gspca_dev, TP6800_R11_SIF_CONTROL, 0x02);
+	msleep(5);
+	reg_r(gspca_dev, TP6800_R14_SIF_RX_DATA);
+	v = gspca_dev->usb_buf[0];
+	if (sd->bridge == BRIDGE_TP6800)
+		return v;
+	if (len > 1) {
+		reg_r(gspca_dev, TP6800_R1B_SIF_RX_DATA2);
+		v |= (gspca_dev->usb_buf[0] << 8);
+	}
+	reg_r(gspca_dev, TP6800_R11_SIF_CONTROL);
+	if (gspca_dev->usb_buf[0] == 0)
+		return v;
+	reg_w(gspca_dev, TP6800_R11_SIF_CONTROL, 0x00);
+	return -1;
+}
+
+static void bulk_w(struct gspca_dev *gspca_dev,
+		  u8 tag,
+		  const u8 *data,
+		  int length)
+{
+	struct usb_device *dev = gspca_dev->dev;
+	int count, actual_count, ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	for (;;) {
+		count = length > BULK_OUT_SIZE - 1
+				? BULK_OUT_SIZE - 1 : length;
+		gspca_dev->usb_buf[0] = tag;
+		memcpy(&gspca_dev->usb_buf[1], data, count);
+		ret = usb_bulk_msg(dev,
+				   usb_sndbulkpipe(dev, 3),
+				   gspca_dev->usb_buf, count + 1,
+				   &actual_count, 500);
+		if (ret < 0) {
+			pr_err("bulk write error %d tag=%02x\n",
+				ret, tag);
+			gspca_dev->usb_err = ret;
+			return;
+		}
+		length -= count;
+		if (length <= 0)
+			break;
+		data += count;
+	}
+}
+
+static int probe_6810(struct gspca_dev *gspca_dev)
+{
+	u8 gpio;
+	int ret;
+
+	reg_r(gspca_dev, TP6800_R18_GPIO_DATA);
+	gpio = gspca_dev->usb_buf[0];
+	reg_w(gspca_dev, TP6800_R18_GPIO_DATA, gpio);
+	reg_w(gspca_dev, TP6800_R18_GPIO_DATA, gpio | 0x20);
+	reg_w(gspca_dev, TP6800_R18_GPIO_DATA, gpio);
+	reg_w(gspca_dev, TP6800_R10_SIF_TYPE, 0x04);	/* i2c 16 bits */
+	reg_w(gspca_dev, TP6800_R12_SIF_ADDR_S, 0x21);	/* ov??? */
+	reg_w(gspca_dev, TP6800_R1A_SIF_TX_DATA2, 0x00);
+	if (i2c_w(gspca_dev, 0x00, 0x00) >= 0)
+		return SENSOR_SOI763A;
+
+	reg_w(gspca_dev, TP6800_R18_GPIO_DATA, gpio | 0x20);
+	reg_w(gspca_dev, TP6800_R10_SIF_TYPE, 0x00);	/* i2c 8 bits */
+	reg_w(gspca_dev, TP6800_R12_SIF_ADDR_S, 0x7f);	/* (unknown i2c) */
+	if (i2c_w(gspca_dev, 0x00, 0x00) >= 0)
+		return -2;
+
+	reg_w(gspca_dev, TP6800_R18_GPIO_DATA, gpio | 0x20);
+	reg_w(gspca_dev, TP6800_R18_GPIO_DATA, gpio);
+	reg_w(gspca_dev, TP6800_R18_GPIO_DATA, gpio | 0x20);
+	reg_w(gspca_dev, TP6800_R10_SIF_TYPE, 0x00);	/* i2c 8 bits */
+	reg_w(gspca_dev, TP6800_R12_SIF_ADDR_S, 0x11);	/* tas??? / hv??? */
+	ret = i2c_r(gspca_dev, 0x00, 1);
+	if (ret > 0)
+		return -3;
+
+	reg_w(gspca_dev, TP6800_R18_GPIO_DATA, gpio | 0x20);
+	reg_w(gspca_dev, TP6800_R18_GPIO_DATA, gpio);
+	reg_w(gspca_dev, TP6800_R18_GPIO_DATA, gpio | 0x20);
+	reg_w(gspca_dev, TP6800_R12_SIF_ADDR_S, 0x6e);	/* po??? */
+	ret = i2c_r(gspca_dev, 0x00, 1);
+	if (ret > 0)
+		return -4;
+
+	ret = i2c_r(gspca_dev, 0x01, 1);
+	if (ret > 0)
+		return -5;
+
+	reg_w(gspca_dev, TP6800_R18_GPIO_DATA, gpio | 0x20);
+	reg_w(gspca_dev, TP6800_R18_GPIO_DATA, gpio);
+	reg_w(gspca_dev, TP6800_R18_GPIO_DATA, gpio | 0x20);
+	reg_w(gspca_dev, TP6800_R10_SIF_TYPE, 0x04);	/* i2c 16 bits */
+	reg_w(gspca_dev, TP6800_R12_SIF_ADDR_S, 0x5d);	/* mi/mt??? */
+	ret = i2c_r(gspca_dev, 0x00, 2);
+	if (ret > 0)
+		return -6;
+
+	reg_w(gspca_dev, TP6800_R18_GPIO_DATA, gpio | 0x20);
+	reg_w(gspca_dev, TP6800_R18_GPIO_DATA, gpio);
+	reg_w(gspca_dev, TP6800_R18_GPIO_DATA, gpio | 0x20);
+	reg_w(gspca_dev, TP6800_R12_SIF_ADDR_S, 0x5c);	/* mi/mt??? */
+	ret = i2c_r(gspca_dev, 0x36, 2);
+	if (ret > 0)
+		return -7;
+
+	reg_w(gspca_dev, TP6800_R18_GPIO_DATA, gpio);
+	reg_w(gspca_dev, TP6800_R18_GPIO_DATA, gpio | 0x20);
+	reg_w(gspca_dev, TP6800_R18_GPIO_DATA, gpio);
+	reg_w(gspca_dev, TP6800_R12_SIF_ADDR_S, 0x61);	/* (unknown i2c) */
+	reg_w(gspca_dev, TP6800_R1A_SIF_TX_DATA2, 0x10);
+	if (i2c_w(gspca_dev, 0xff, 0x00) >= 0)
+		return -8;
+
+	reg_w(gspca_dev, TP6800_R18_GPIO_DATA, gpio | 0x20);
+	reg_w(gspca_dev, TP6800_R18_GPIO_DATA, gpio);
+	reg_w(gspca_dev, TP6800_R18_GPIO_DATA, gpio | 0x20);
+	reg_w(gspca_dev, TP6800_R10_SIF_TYPE, 0x00);	/* i2c 8 bits */
+	reg_w(gspca_dev, TP6800_R12_SIF_ADDR_S, 0x20);	/* cx0342 */
+	ret = i2c_r(gspca_dev, 0x00, 1);
+	if (ret > 0)
+		return SENSOR_CX0342;
+	return -9;
+}
+
+static void cx0342_6810_init(struct gspca_dev *gspca_dev)
+{
+	static const struct cmd reg_init_1[] = {
+		{TP6800_R2F_TIMING_CFG, 0x2f},
+		{0x25, 0x02},
+		{TP6800_R21_ENDP_1_CTL, 0x00},
+		{TP6800_R3F_FRAME_RATE, 0x80},
+		{TP6800_R2F_TIMING_CFG, 0x2f},
+		{TP6800_R18_GPIO_DATA, 0xe1},
+		{TP6800_R18_GPIO_DATA, 0xc1},
+		{TP6800_R18_GPIO_DATA, 0xe1},
+		{TP6800_R11_SIF_CONTROL, 0x00},
+	};
+	static const struct cmd reg_init_2[] = {
+		{TP6800_R78_FORMAT, 0x48},
+		{TP6800_R11_SIF_CONTROL, 0x00},
+	};
+	static const struct cmd sensor_init[] = {
+		{CX0342_OUTPUT_CTRL, 0x07},
+		{CX0342_BYPASS_MODE, 0x58},
+		{CX0342_GPXLTHD_L, 0x28},
+		{CX0342_RBPXLTHD_L, 0x28},
+		{CX0342_PLANETHD_L, 0x50},
+		{CX0342_PLANETHD_H, 0x03},
+		{CX0342_RB_GAP_L, 0xff},
+		{CX0342_RB_GAP_H, 0x07},
+		{CX0342_G_GAP_L, 0xff},
+		{CX0342_G_GAP_H, 0x07},
+		{CX0342_RST_OVERFLOW_L, 0x5c},
+		{CX0342_RST_OVERFLOW_H, 0x01},
+		{CX0342_DATA_OVERFLOW_L, 0xfc},
+		{CX0342_DATA_OVERFLOW_H, 0x03},
+		{CX0342_DATA_UNDERFLOW_L, 0x00},
+		{CX0342_DATA_UNDERFLOW_H, 0x00},
+		{CX0342_SYS_CTRL_0, 0x40},
+		{CX0342_GLOBAL_GAIN, 0x01},
+		{CX0342_CLOCK_GEN, 0x00},
+		{CX0342_SYS_CTRL_0, 0x02},
+		{CX0342_IDLE_CTRL, 0x05},
+		{CX0342_ADCGN, 0x00},
+		{CX0342_ADC_CTL, 0x00},
+		{CX0342_LVRST_BLBIAS, 0x01},
+		{CX0342_VTHSEL, 0x0b},
+		{CX0342_RAMP_RIV, 0x0b},
+		{CX0342_LDOSEL, 0x07},
+		{CX0342_SPV_VALUE_L, 0x40},
+		{CX0342_SPV_VALUE_H, 0x02},
+
+		{CX0342_AUTO_ADC_CALIB, 0x81},
+		{CX0342_TIMING_EN, 0x01},
+	};
+
+	reg_w_buf(gspca_dev, reg_init_1, ARRAY_SIZE(reg_init_1));
+	reg_w_buf(gspca_dev, tp6810_cx_init_common,
+			ARRAY_SIZE(tp6810_cx_init_common));
+	reg_w_buf(gspca_dev, reg_init_2, ARRAY_SIZE(reg_init_2));
+
+	reg_w(gspca_dev, TP6800_R12_SIF_ADDR_S, 0x20);	/* cx0342 I2C addr */
+	i2c_w_buf(gspca_dev, sensor_init, ARRAY_SIZE(sensor_init));
+	i2c_w_buf(gspca_dev, cx0342_timing_seq, ARRAY_SIZE(cx0342_timing_seq));
+}
+
+static void soi763a_6810_init(struct gspca_dev *gspca_dev)
+{
+	static const struct cmd reg_init_1[] = {
+		{TP6800_R2F_TIMING_CFG, 0x2f},
+		{TP6800_R18_GPIO_DATA, 0xe1},
+		{0x25, 0x02},
+		{TP6800_R21_ENDP_1_CTL, 0x00},
+		{TP6800_R3F_FRAME_RATE, 0x80},
+		{TP6800_R2F_TIMING_CFG, 0x2f},
+		{TP6800_R18_GPIO_DATA, 0xc1},
+	};
+	static const struct cmd reg_init_2[] = {
+		{TP6800_R78_FORMAT, 0x54},
+	};
+	static const struct cmd sensor_init[] = {
+		{0x00, 0x00},
+		{0x01, 0x80},
+		{0x02, 0x80},
+		{0x03, 0x90},
+		{0x04, 0x20},
+		{0x05, 0x20},
+		{0x06, 0x80},
+		{0x07, 0x00},
+		{0x08, 0xff},
+		{0x09, 0xff},
+		{0x0a, 0x76},		/* 7630 = soi673a */
+		{0x0b, 0x30},
+		{0x0c, 0x20},
+		{0x0d, 0x20},
+		{0x0e, 0xff},
+		{0x0f, 0xff},
+		{0x10, 0x41},
+		{0x15, 0x14},
+		{0x11, 0x40},
+		{0x12, 0x48},
+		{0x13, 0x80},
+		{0x14, 0x80},
+		{0x16, 0x03},
+		{0x28, 0xb0},
+		{0x71, 0x20},
+		{0x75, 0x8e},
+		{0x17, 0x1b},
+		{0x18, 0xbd},
+		{0x19, 0x05},
+		{0x1a, 0xf6},
+		{0x1b, 0x04},
+		{0x1c, 0x7f},		/* omnivision */
+		{0x1d, 0xa2},
+		{0x1e, 0x00},
+		{0x1f, 0x00},
+		{0x20, 0x45},
+		{0x21, 0x80},
+		{0x22, 0x80},
+		{0x23, 0xee},
+		{0x24, 0x50},
+		{0x25, 0x7a},
+		{0x26, 0xa0},
+		{0x27, 0x9a},
+		{0x29, 0x30},
+		{0x2a, 0x80},
+		{0x2b, 0x00},
+		{0x2c, 0xac},
+		{0x2d, 0x05},
+		{0x2e, 0x80},
+		{0x2f, 0x3c},
+		{0x30, 0x22},
+		{0x31, 0x00},
+		{0x32, 0x86},
+		{0x33, 0x08},
+		{0x34, 0xff},
+		{0x35, 0xff},
+		{0x36, 0xff},
+		{0x37, 0xff},
+		{0x38, 0xff},
+		{0x39, 0xff},
+		{0x3a, 0xfe},
+		{0x3b, 0xfe},
+		{0x3c, 0xfe},
+		{0x3d, 0xfe},
+		{0x3e, 0xfe},
+		{0x3f, 0x71},
+		{0x40, 0xff},
+		{0x41, 0xff},
+		{0x42, 0xff},
+		{0x43, 0xff},
+		{0x44, 0xff},
+		{0x45, 0xff},
+		{0x46, 0xff},
+		{0x47, 0xff},
+		{0x48, 0xff},
+		{0x49, 0xff},
+		{0x4a, 0xfe},
+		{0x4b, 0xff},
+		{0x4c, 0x00},
+		{0x4d, 0x00},
+		{0x4e, 0xff},
+		{0x4f, 0xff},
+		{0x50, 0xff},
+		{0x51, 0xff},
+		{0x52, 0xff},
+		{0x53, 0xff},
+		{0x54, 0xff},
+		{0x55, 0xff},
+		{0x56, 0xff},
+		{0x57, 0xff},
+		{0x58, 0xff},
+		{0x59, 0xff},
+		{0x5a, 0xff},
+		{0x5b, 0xfe},
+		{0x5c, 0xff},
+		{0x5d, 0x8f},
+		{0x5e, 0xff},
+		{0x5f, 0x8f},
+		{0x60, 0xa2},
+		{0x61, 0x4a},
+		{0x62, 0xf3},
+		{0x63, 0x75},
+		{0x64, 0xf0},
+		{0x65, 0x00},
+		{0x66, 0x55},
+		{0x67, 0x92},
+		{0x68, 0xa0},
+		{0x69, 0x4a},
+		{0x6a, 0x22},
+		{0x6b, 0x00},
+		{0x6c, 0x33},
+		{0x6d, 0x44},
+		{0x6e, 0x22},
+		{0x6f, 0x84},
+		{0x70, 0x0b},
+		{0x72, 0x10},
+		{0x73, 0x50},
+		{0x74, 0x21},
+		{0x76, 0x00},
+		{0x77, 0xa5},
+		{0x78, 0x80},
+		{0x79, 0x80},
+		{0x7a, 0x80},
+		{0x7b, 0xe2},
+		{0x7c, 0x00},
+		{0x7d, 0xf7},
+		{0x7e, 0x00},
+		{0x7f, 0x00},
+	};
+
+	reg_w_buf(gspca_dev, reg_init_1, ARRAY_SIZE(reg_init_1));
+	reg_w_buf(gspca_dev, tp6810_ov_init_common,
+			ARRAY_SIZE(tp6810_ov_init_common));
+	reg_w_buf(gspca_dev, reg_init_2, ARRAY_SIZE(reg_init_2));
+
+	i2c_w(gspca_dev, 0x12, 0x80);		/* sensor reset */
+	msleep(10);
+	i2c_w_buf(gspca_dev, sensor_init, ARRAY_SIZE(sensor_init));
+}
+
+/* set the gain and exposure */
+static void setexposure(struct gspca_dev *gspca_dev, s32 expo, s32 gain,
+							s32 blue, s32 red)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->sensor == SENSOR_CX0342) {
+		expo = (expo << 2) - 1;
+		i2c_w(gspca_dev, CX0342_EXPO_LINE_L, expo);
+		i2c_w(gspca_dev, CX0342_EXPO_LINE_H, expo >> 8);
+		if (sd->bridge == BRIDGE_TP6800)
+			i2c_w(gspca_dev, CX0342_RAW_GBGAIN_H,
+						gain >> 8);
+		i2c_w(gspca_dev, CX0342_RAW_GBGAIN_L, gain);
+		if (sd->bridge == BRIDGE_TP6800)
+			i2c_w(gspca_dev, CX0342_RAW_GRGAIN_H,
+					gain >> 8);
+		i2c_w(gspca_dev, CX0342_RAW_GRGAIN_L, gain);
+		if (sd->sensor == SENSOR_CX0342) {
+			if (sd->bridge == BRIDGE_TP6800)
+				i2c_w(gspca_dev, CX0342_RAW_BGAIN_H,
+						blue >> 8);
+			i2c_w(gspca_dev, CX0342_RAW_BGAIN_L, blue);
+			if (sd->bridge == BRIDGE_TP6800)
+				i2c_w(gspca_dev, CX0342_RAW_RGAIN_H,
+						red >> 8);
+			i2c_w(gspca_dev, CX0342_RAW_RGAIN_L, red);
+		}
+		i2c_w(gspca_dev, CX0342_SYS_CTRL_0,
+				sd->bridge == BRIDGE_TP6800 ? 0x80 : 0x81);
+		return;
+	}
+
+	/* soi763a */
+	i2c_w(gspca_dev, 0x10,		/* AEC_H (exposure time) */
+			 expo);
+/*	i2c_w(gspca_dev, 0x76, 0x02);	 * AEC_L ([1:0] */
+	i2c_w(gspca_dev, 0x00,		/* gain */
+			 gain);
+}
+
+/* set the JPEG quantization tables */
+static void set_dqt(struct gspca_dev *gspca_dev, u8 q)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	/* update the jpeg quantization tables */
+	gspca_dbg(gspca_dev, D_STREAM, "q %d -> %d\n", sd->quality, q);
+	sd->quality = q;
+	if (q > 16)
+		q = 16;
+	if (sd->sensor == SENSOR_SOI763A)
+		jpeg_set_qual(sd->jpeg_hdr, jpeg_q[q]);
+	else
+		memcpy(&sd->jpeg_hdr[JPEG_QT0_OFFSET - 1],
+			DQT[q], sizeof DQT[0]);
+}
+
+/* set the JPEG compression quality factor */
+static void setquality(struct gspca_dev *gspca_dev, s32 q)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (q != 16)
+		q = 15 - q;
+
+	reg_w(gspca_dev, TP6800_R7A_BLK_THRLD, 0x00);
+	reg_w(gspca_dev, TP6800_R79_QUALITY, 0x04);
+	reg_w(gspca_dev, TP6800_R79_QUALITY, q);
+
+	/* auto quality */
+	if (q == 15 && sd->bridge == BRIDGE_TP6810) {
+		msleep(4);
+		reg_w(gspca_dev, TP6800_R7A_BLK_THRLD, 0x19);
+	}
+}
+
+static const u8 color_null[18] = {
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+static const u8 color_gain[NSENSORS][18] = {
+[SENSOR_CX0342] =
+	{0x4c, 0x00, 0xa9, 0x00, 0x31, 0x00,	/* Y R/G/B (LE values) */
+	 0xb6, 0x03, 0x6c, 0x03, 0xe0, 0x00,	/* U R/G/B */
+	 0xdf, 0x00, 0x46, 0x03, 0xdc, 0x03},	/* V R/G/B */
+[SENSOR_SOI763A] =
+	{0x4c, 0x00, 0x95, 0x00, 0x1d, 0x00,	/* Y R/G/B (LE values) */
+	 0xb6, 0x03, 0x6c, 0x03, 0xd7, 0x00,	/* U R/G/B */
+	 0xd5, 0x00, 0x46, 0x03, 0xdc, 0x03},	/* V R/G/B */
+};
+
+static void setgamma(struct gspca_dev *gspca_dev, s32 gamma)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+#define NGAMMA 6
+	static const u8 gamma_tb[NGAMMA][3][1024] = {
+	    {				/* gamma 0 - from tp6800 + soi763a */
+		{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02,
+		 0x02, 0x03, 0x05, 0x07, 0x07, 0x08, 0x09, 0x09,
+		 0x0a, 0x0c, 0x0c, 0x0d, 0x0e, 0x0e, 0x10, 0x11,
+		 0x11, 0x12, 0x14, 0x14, 0x15, 0x16, 0x16, 0x17,
+		 0x17, 0x18, 0x1a, 0x1a, 0x1b, 0x1b, 0x1c, 0x1e,
+		 0x1e, 0x1f, 0x1f, 0x20, 0x20, 0x22, 0x23, 0x23,
+		 0x25, 0x25, 0x26, 0x26, 0x27, 0x27, 0x28, 0x28,
+		 0x29, 0x29, 0x2b, 0x2c, 0x2c, 0x2d, 0x2d, 0x2f,
+		 0x2f, 0x30, 0x30, 0x31, 0x31, 0x33, 0x33, 0x34,
+		 0x34, 0x34, 0x35, 0x35, 0x37, 0x37, 0x38, 0x38,
+		 0x39, 0x39, 0x3a, 0x3a, 0x3b, 0x3b, 0x3b, 0x3c,
+		 0x3c, 0x3d, 0x3d, 0x3f, 0x3f, 0x40, 0x40, 0x40,
+		 0x42, 0x42, 0x43, 0x43, 0x44, 0x44, 0x44, 0x45,
+		 0x45, 0x47, 0x47, 0x47, 0x48, 0x48, 0x49, 0x49,
+		 0x4a, 0x4a, 0x4a, 0x4b, 0x4b, 0x4b, 0x4c, 0x4c,
+		 0x4d, 0x4d, 0x4d, 0x4f, 0x4f, 0x50, 0x50, 0x50,
+		 0x52, 0x52, 0x52, 0x53, 0x53, 0x54, 0x54, 0x54,
+		 0x55, 0x55, 0x55, 0x56, 0x56, 0x58, 0x58, 0x58,
+		 0x59, 0x59, 0x59, 0x5a, 0x5a, 0x5a, 0x5b, 0x5b,
+		 0x5b, 0x5c, 0x5c, 0x5c, 0x5e, 0x5e, 0x5e, 0x5f,
+		 0x5f, 0x5f, 0x60, 0x60, 0x60, 0x61, 0x61, 0x61,
+		 0x62, 0x62, 0x62, 0x63, 0x63, 0x63, 0x65, 0x65,
+		 0x65, 0x66, 0x66, 0x66, 0x67, 0x67, 0x67, 0x68,
+		 0x68, 0x68, 0x69, 0x69, 0x69, 0x69, 0x6a, 0x6a,
+		 0x6a, 0x6c, 0x6c, 0x6c, 0x6d, 0x6d, 0x6d, 0x6e,
+		 0x6e, 0x6e, 0x6e, 0x6f, 0x6f, 0x6f, 0x70, 0x70,
+		 0x70, 0x71, 0x71, 0x71, 0x71, 0x73, 0x73, 0x73,
+		 0x74, 0x74, 0x74, 0x74, 0x75, 0x75, 0x75, 0x77,
+		 0x77, 0x77, 0x77, 0x78, 0x78, 0x78, 0x79, 0x79,
+		 0x79, 0x79, 0x7a, 0x7a, 0x7a, 0x7a, 0x7b, 0x7b,
+		 0x7b, 0x7c, 0x7c, 0x7c, 0x7c, 0x7d, 0x7d, 0x7d,
+		 0x7d, 0x7f, 0x7f, 0x7f, 0x80, 0x80, 0x80, 0x80,
+		 0x81, 0x81, 0x81, 0x81, 0x82, 0x82, 0x82, 0x82,
+		 0x84, 0x84, 0x84, 0x85, 0x85, 0x85, 0x85, 0x86,
+		 0x86, 0x86, 0x86, 0x88, 0x88, 0x88, 0x88, 0x89,
+		 0x89, 0x89, 0x89, 0x8a, 0x8a, 0x8a, 0x8a, 0x8b,
+		 0x8b, 0x8b, 0x8b, 0x8d, 0x8d, 0x8d, 0x8d, 0x8e,
+		 0x8e, 0x8e, 0x8e, 0x8f, 0x8f, 0x8f, 0x8f, 0x90,
+		 0x90, 0x90, 0x90, 0x90, 0x91, 0x91, 0x91, 0x91,
+		 0x92, 0x92, 0x92, 0x92, 0x93, 0x93, 0x93, 0x93,
+		 0x94, 0x94, 0x94, 0x94, 0x96, 0x96, 0x96, 0x96,
+		 0x96, 0x97, 0x97, 0x97, 0x97, 0x98, 0x98, 0x98,
+		 0x98, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a, 0x9a,
+		 0x9a, 0x9a, 0x9b, 0x9b, 0x9b, 0x9b, 0x9c, 0x9c,
+		 0x9c, 0x9c, 0x9c, 0x9d, 0x9d, 0x9d, 0x9d, 0x9e,
+		 0x9e, 0x9e, 0x9e, 0x9e, 0xa0, 0xa0, 0xa0, 0xa0,
+		 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa2, 0xa2, 0xa2,
+		 0xa2, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa4, 0xa4,
+		 0xa4, 0xa4, 0xa4, 0xa5, 0xa5, 0xa5, 0xa5, 0xa6,
+		 0xa6, 0xa6, 0xa6, 0xa6, 0xa8, 0xa8, 0xa8, 0xa8,
+		 0xa8, 0xa9, 0xa9, 0xa9, 0xa9, 0xab, 0xab, 0xab,
+		 0xab, 0xab, 0xac, 0xac, 0xac, 0xac, 0xac, 0xad,
+		 0xad, 0xad, 0xad, 0xae, 0xae, 0xae, 0xae, 0xae,
+		 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xb0, 0xb0, 0xb0,
+		 0xb0, 0xb0, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb2,
+		 0xb2, 0xb2, 0xb2, 0xb2, 0xb3, 0xb3, 0xb3, 0xb3,
+		 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb6, 0xb6, 0xb6,
+		 0xb6, 0xb6, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb8,
+		 0xb8, 0xb8, 0xb8, 0xb8, 0xb9, 0xb9, 0xb9, 0xb9,
+		 0xb9, 0xba, 0xba, 0xba, 0xba, 0xba, 0xbc, 0xbc,
+		 0xbc, 0xbc, 0xbc, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd,
+		 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbf, 0xbf, 0xbf,
+		 0xbf, 0xbf, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc2,
+		 0xc2, 0xc2, 0xc2, 0xc2, 0xc3, 0xc3, 0xc3, 0xc3,
+		 0xc3, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc5, 0xc5,
+		 0xc5, 0xc5, 0xc5, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+		 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc9, 0xc9, 0xc9,
+		 0xc9, 0xc9, 0xc9, 0xca, 0xca, 0xca, 0xca, 0xca,
+		 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcc, 0xcc, 0xcc,
+		 0xcc, 0xcc, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xce,
+		 0xce, 0xce, 0xce, 0xce, 0xcf, 0xcf, 0xcf, 0xcf,
+		 0xcf, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd1, 0xd1,
+		 0xd1, 0xd1, 0xd1, 0xd1, 0xd3, 0xd3, 0xd3, 0xd3,
+		 0xd3, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd6, 0xd6,
+		 0xd6, 0xd6, 0xd6, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7,
+		 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd9, 0xd9, 0xd9,
+		 0xd9, 0xd9, 0xda, 0xda, 0xda, 0xda, 0xda, 0xdb,
+		 0xdb, 0xdb, 0xdb, 0xdb, 0xdd, 0xdd, 0xdd, 0xdd,
+		 0xdd, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xdf,
+		 0xdf, 0xdf, 0xdf, 0xdf, 0xe0, 0xe0, 0xe0, 0xe0,
+		 0xe0, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe2, 0xe2,
+		 0xe2, 0xe2, 0xe2, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3,
+		 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe5, 0xe5, 0xe5,
+		 0xe5, 0xe5, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe7,
+		 0xe7, 0xe7, 0xe7, 0xe7, 0xe8, 0xe8, 0xe8, 0xe8,
+		 0xe8, 0xe9, 0xe9, 0xe9, 0xe9, 0xeb, 0xeb, 0xeb,
+		 0xeb, 0xeb, 0xec, 0xec, 0xec, 0xec, 0xec, 0xed,
+		 0xed, 0xed, 0xed, 0xed, 0xee, 0xee, 0xee, 0xee,
+		 0xee, 0xef, 0xef, 0xef, 0xef, 0xef, 0xf0, 0xf0,
+		 0xf0, 0xf0, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf3,
+		 0xf3, 0xf3, 0xf3, 0xf3, 0xf4, 0xf4, 0xf4, 0xf4,
+		 0xf4, 0xf5, 0xf5, 0xf5, 0xf5, 0xf6, 0xf6, 0xf6,
+		 0xf6, 0xf6, 0xf7, 0xf7, 0xf7, 0xf7, 0xf8, 0xf8,
+		 0xf8, 0xf8, 0xf8, 0xf9, 0xf9, 0xf9, 0xf9, 0xfa,
+		 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb},
+		{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02,
+		 0x02, 0x03, 0x05, 0x07, 0x07, 0x08, 0x09, 0x09,
+		 0x0a, 0x0c, 0x0c, 0x0d, 0x0e, 0x0e, 0x10, 0x11,
+		 0x11, 0x12, 0x14, 0x14, 0x15, 0x16, 0x16, 0x17,
+		 0x17, 0x18, 0x1a, 0x1a, 0x1b, 0x1b, 0x1c, 0x1e,
+		 0x1e, 0x1f, 0x1f, 0x20, 0x20, 0x22, 0x23, 0x23,
+		 0x25, 0x25, 0x26, 0x26, 0x27, 0x27, 0x28, 0x28,
+		 0x29, 0x29, 0x2b, 0x2c, 0x2c, 0x2d, 0x2d, 0x2f,
+		 0x2f, 0x30, 0x30, 0x31, 0x31, 0x33, 0x33, 0x34,
+		 0x34, 0x34, 0x35, 0x35, 0x37, 0x37, 0x38, 0x38,
+		 0x39, 0x39, 0x3a, 0x3a, 0x3b, 0x3b, 0x3b, 0x3c,
+		 0x3c, 0x3d, 0x3d, 0x3f, 0x3f, 0x40, 0x40, 0x40,
+		 0x42, 0x42, 0x43, 0x43, 0x44, 0x44, 0x44, 0x45,
+		 0x45, 0x47, 0x47, 0x47, 0x48, 0x48, 0x49, 0x49,
+		 0x4a, 0x4a, 0x4a, 0x4b, 0x4b, 0x4b, 0x4c, 0x4c,
+		 0x4d, 0x4d, 0x4d, 0x4f, 0x4f, 0x50, 0x50, 0x50,
+		 0x52, 0x52, 0x52, 0x53, 0x53, 0x54, 0x54, 0x54,
+		 0x55, 0x55, 0x55, 0x56, 0x56, 0x58, 0x58, 0x58,
+		 0x59, 0x59, 0x59, 0x5a, 0x5a, 0x5a, 0x5b, 0x5b,
+		 0x5b, 0x5c, 0x5c, 0x5c, 0x5e, 0x5e, 0x5e, 0x5f,
+		 0x5f, 0x5f, 0x60, 0x60, 0x60, 0x61, 0x61, 0x61,
+		 0x62, 0x62, 0x62, 0x63, 0x63, 0x63, 0x65, 0x65,
+		 0x65, 0x66, 0x66, 0x66, 0x67, 0x67, 0x67, 0x68,
+		 0x68, 0x68, 0x69, 0x69, 0x69, 0x69, 0x6a, 0x6a,
+		 0x6a, 0x6c, 0x6c, 0x6c, 0x6d, 0x6d, 0x6d, 0x6e,
+		 0x6e, 0x6e, 0x6e, 0x6f, 0x6f, 0x6f, 0x70, 0x70,
+		 0x70, 0x71, 0x71, 0x71, 0x71, 0x73, 0x73, 0x73,
+		 0x74, 0x74, 0x74, 0x74, 0x75, 0x75, 0x75, 0x77,
+		 0x77, 0x77, 0x77, 0x78, 0x78, 0x78, 0x79, 0x79,
+		 0x79, 0x79, 0x7a, 0x7a, 0x7a, 0x7a, 0x7b, 0x7b,
+		 0x7b, 0x7c, 0x7c, 0x7c, 0x7c, 0x7d, 0x7d, 0x7d,
+		 0x7d, 0x7f, 0x7f, 0x7f, 0x80, 0x80, 0x80, 0x80,
+		 0x81, 0x81, 0x81, 0x81, 0x82, 0x82, 0x82, 0x82,
+		 0x84, 0x84, 0x84, 0x85, 0x85, 0x85, 0x85, 0x86,
+		 0x86, 0x86, 0x86, 0x88, 0x88, 0x88, 0x88, 0x89,
+		 0x89, 0x89, 0x89, 0x8a, 0x8a, 0x8a, 0x8a, 0x8b,
+		 0x8b, 0x8b, 0x8b, 0x8d, 0x8d, 0x8d, 0x8d, 0x8e,
+		 0x8e, 0x8e, 0x8e, 0x8f, 0x8f, 0x8f, 0x8f, 0x90,
+		 0x90, 0x90, 0x90, 0x90, 0x91, 0x91, 0x91, 0x91,
+		 0x92, 0x92, 0x92, 0x92, 0x93, 0x93, 0x93, 0x93,
+		 0x94, 0x94, 0x94, 0x94, 0x96, 0x96, 0x96, 0x96,
+		 0x96, 0x97, 0x97, 0x97, 0x97, 0x98, 0x98, 0x98,
+		 0x98, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a, 0x9a,
+		 0x9a, 0x9a, 0x9b, 0x9b, 0x9b, 0x9b, 0x9c, 0x9c,
+		 0x9c, 0x9c, 0x9c, 0x9d, 0x9d, 0x9d, 0x9d, 0x9e,
+		 0x9e, 0x9e, 0x9e, 0x9e, 0xa0, 0xa0, 0xa0, 0xa0,
+		 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa2, 0xa2, 0xa2,
+		 0xa2, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa4, 0xa4,
+		 0xa4, 0xa4, 0xa4, 0xa5, 0xa5, 0xa5, 0xa5, 0xa6,
+		 0xa6, 0xa6, 0xa6, 0xa6, 0xa8, 0xa8, 0xa8, 0xa8,
+		 0xa8, 0xa9, 0xa9, 0xa9, 0xa9, 0xab, 0xab, 0xab,
+		 0xab, 0xab, 0xac, 0xac, 0xac, 0xac, 0xac, 0xad,
+		 0xad, 0xad, 0xad, 0xae, 0xae, 0xae, 0xae, 0xae,
+		 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xb0, 0xb0, 0xb0,
+		 0xb0, 0xb0, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb2,
+		 0xb2, 0xb2, 0xb2, 0xb2, 0xb3, 0xb3, 0xb3, 0xb3,
+		 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb6, 0xb6, 0xb6,
+		 0xb6, 0xb6, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb8,
+		 0xb8, 0xb8, 0xb8, 0xb8, 0xb9, 0xb9, 0xb9, 0xb9,
+		 0xb9, 0xba, 0xba, 0xba, 0xba, 0xba, 0xbc, 0xbc,
+		 0xbc, 0xbc, 0xbc, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd,
+		 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbf, 0xbf, 0xbf,
+		 0xbf, 0xbf, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc2,
+		 0xc2, 0xc2, 0xc2, 0xc2, 0xc3, 0xc3, 0xc3, 0xc3,
+		 0xc3, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc5, 0xc5,
+		 0xc5, 0xc5, 0xc5, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+		 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc9, 0xc9, 0xc9,
+		 0xc9, 0xc9, 0xc9, 0xca, 0xca, 0xca, 0xca, 0xca,
+		 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcc, 0xcc, 0xcc,
+		 0xcc, 0xcc, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xce,
+		 0xce, 0xce, 0xce, 0xce, 0xcf, 0xcf, 0xcf, 0xcf,
+		 0xcf, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd1, 0xd1,
+		 0xd1, 0xd1, 0xd1, 0xd1, 0xd3, 0xd3, 0xd3, 0xd3,
+		 0xd3, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd6, 0xd6,
+		 0xd6, 0xd6, 0xd6, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7,
+		 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd9, 0xd9, 0xd9,
+		 0xd9, 0xd9, 0xda, 0xda, 0xda, 0xda, 0xda, 0xdb,
+		 0xdb, 0xdb, 0xdb, 0xdb, 0xdd, 0xdd, 0xdd, 0xdd,
+		 0xdd, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xdf,
+		 0xdf, 0xdf, 0xdf, 0xdf, 0xe0, 0xe0, 0xe0, 0xe0,
+		 0xe0, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe2, 0xe2,
+		 0xe2, 0xe2, 0xe2, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3,
+		 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe5, 0xe5, 0xe5,
+		 0xe5, 0xe5, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe7,
+		 0xe7, 0xe7, 0xe7, 0xe7, 0xe8, 0xe8, 0xe8, 0xe8,
+		 0xe8, 0xe9, 0xe9, 0xe9, 0xe9, 0xeb, 0xeb, 0xeb,
+		 0xeb, 0xeb, 0xec, 0xec, 0xec, 0xec, 0xec, 0xed,
+		 0xed, 0xed, 0xed, 0xed, 0xee, 0xee, 0xee, 0xee,
+		 0xee, 0xef, 0xef, 0xef, 0xef, 0xef, 0xf0, 0xf0,
+		 0xf0, 0xf0, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf3,
+		 0xf3, 0xf3, 0xf3, 0xf3, 0xf4, 0xf4, 0xf4, 0xf4,
+		 0xf4, 0xf5, 0xf5, 0xf5, 0xf5, 0xf6, 0xf6, 0xf6,
+		 0xf6, 0xf6, 0xf7, 0xf7, 0xf7, 0xf7, 0xf8, 0xf8,
+		 0xf8, 0xf8, 0xf8, 0xf9, 0xf9, 0xf9, 0xf9, 0xfa,
+		 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb},
+		{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02,
+		 0x02, 0x03, 0x05, 0x07, 0x07, 0x08, 0x09, 0x09,
+		 0x0a, 0x0c, 0x0c, 0x0d, 0x0e, 0x0e, 0x10, 0x11,
+		 0x11, 0x12, 0x14, 0x14, 0x15, 0x16, 0x16, 0x17,
+		 0x17, 0x18, 0x1a, 0x1a, 0x1b, 0x1b, 0x1c, 0x1e,
+		 0x1e, 0x1f, 0x1f, 0x20, 0x20, 0x22, 0x23, 0x23,
+		 0x25, 0x25, 0x26, 0x26, 0x27, 0x27, 0x28, 0x28,
+		 0x29, 0x29, 0x2b, 0x2c, 0x2c, 0x2d, 0x2d, 0x2f,
+		 0x2f, 0x30, 0x30, 0x31, 0x31, 0x33, 0x33, 0x34,
+		 0x34, 0x34, 0x35, 0x35, 0x37, 0x37, 0x38, 0x38,
+		 0x39, 0x39, 0x3a, 0x3a, 0x3b, 0x3b, 0x3b, 0x3c,
+		 0x3c, 0x3d, 0x3d, 0x3f, 0x3f, 0x40, 0x40, 0x40,
+		 0x42, 0x42, 0x43, 0x43, 0x44, 0x44, 0x44, 0x45,
+		 0x45, 0x47, 0x47, 0x47, 0x48, 0x48, 0x49, 0x49,
+		 0x4a, 0x4a, 0x4a, 0x4b, 0x4b, 0x4b, 0x4c, 0x4c,
+		 0x4d, 0x4d, 0x4d, 0x4f, 0x4f, 0x50, 0x50, 0x50,
+		 0x52, 0x52, 0x52, 0x53, 0x53, 0x54, 0x54, 0x54,
+		 0x55, 0x55, 0x55, 0x56, 0x56, 0x58, 0x58, 0x58,
+		 0x59, 0x59, 0x59, 0x5a, 0x5a, 0x5a, 0x5b, 0x5b,
+		 0x5b, 0x5c, 0x5c, 0x5c, 0x5e, 0x5e, 0x5e, 0x5f,
+		 0x5f, 0x5f, 0x60, 0x60, 0x60, 0x61, 0x61, 0x61,
+		 0x62, 0x62, 0x62, 0x63, 0x63, 0x63, 0x65, 0x65,
+		 0x65, 0x66, 0x66, 0x66, 0x67, 0x67, 0x67, 0x68,
+		 0x68, 0x68, 0x69, 0x69, 0x69, 0x69, 0x6a, 0x6a,
+		 0x6a, 0x6c, 0x6c, 0x6c, 0x6d, 0x6d, 0x6d, 0x6e,
+		 0x6e, 0x6e, 0x6e, 0x6f, 0x6f, 0x6f, 0x70, 0x70,
+		 0x70, 0x71, 0x71, 0x71, 0x71, 0x73, 0x73, 0x73,
+		 0x74, 0x74, 0x74, 0x74, 0x75, 0x75, 0x75, 0x76,
+		 0x77, 0x77, 0x77, 0x78, 0x78, 0x78, 0x79, 0x79,
+		 0x79, 0x79, 0x7a, 0x7a, 0x7a, 0x7a, 0x7b, 0x7b,
+		 0x7b, 0x7c, 0x7c, 0x7c, 0x7c, 0x7d, 0x7d, 0x7d,
+		 0x7d, 0x7f, 0x7f, 0x7f, 0x80, 0x80, 0x80, 0x80,
+		 0x81, 0x81, 0x81, 0x81, 0x82, 0x82, 0x82, 0x82,
+		 0x84, 0x84, 0x84, 0x85, 0x85, 0x85, 0x85, 0x86,
+		 0x86, 0x86, 0x86, 0x88, 0x88, 0x88, 0x88, 0x89,
+		 0x89, 0x89, 0x89, 0x8a, 0x8a, 0x8a, 0x8a, 0x8b,
+		 0x8b, 0x8b, 0x8b, 0x8d, 0x8d, 0x8d, 0x8d, 0x8e,
+		 0x8e, 0x8e, 0x8e, 0x8f, 0x8f, 0x8f, 0x8f, 0x90,
+		 0x90, 0x90, 0x90, 0x90, 0x91, 0x91, 0x91, 0x91,
+		 0x92, 0x92, 0x92, 0x92, 0x93, 0x93, 0x93, 0x93,
+		 0x94, 0x94, 0x94, 0x94, 0x96, 0x96, 0x96, 0x96,
+		 0x96, 0x97, 0x97, 0x97, 0x97, 0x98, 0x98, 0x98,
+		 0x98, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a, 0x9a,
+		 0x9a, 0x9a, 0x9b, 0x9b, 0x9b, 0x9b, 0x9c, 0x9c,
+		 0x9c, 0x9c, 0x9c, 0x9d, 0x9d, 0x9d, 0x9d, 0x9e,
+		 0x9e, 0x9e, 0x9e, 0x9e, 0xa0, 0xa0, 0xa0, 0xa0,
+		 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, 0xa2, 0xa2, 0xa2,
+		 0xa2, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa4, 0xa4,
+		 0xa4, 0xa4, 0xa4, 0xa5, 0xa5, 0xa5, 0xa5, 0xa6,
+		 0xa6, 0xa6, 0xa6, 0xa6, 0xa8, 0xa8, 0xa8, 0xa8,
+		 0xa8, 0xa9, 0xa9, 0xa9, 0xa9, 0xab, 0xab, 0xab,
+		 0xab, 0xab, 0xac, 0xac, 0xac, 0xac, 0xac, 0xad,
+		 0xad, 0xad, 0xad, 0xae, 0xae, 0xae, 0xae, 0xae,
+		 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xb0, 0xb0, 0xb0,
+		 0xb0, 0xb0, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb2,
+		 0xb2, 0xb2, 0xb2, 0xb2, 0xb3, 0xb3, 0xb3, 0xb3,
+		 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb6, 0xb6, 0xb6,
+		 0xb6, 0xb6, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb8,
+		 0xb8, 0xb8, 0xb8, 0xb8, 0xb9, 0xb9, 0xb9, 0xb9,
+		 0xb9, 0xba, 0xba, 0xba, 0xba, 0xba, 0xbc, 0xbc,
+		 0xbc, 0xbc, 0xbc, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd,
+		 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbf, 0xbf, 0xbf,
+		 0xbf, 0xbf, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc2,
+		 0xc2, 0xc2, 0xc2, 0xc2, 0xc3, 0xc3, 0xc3, 0xc3,
+		 0xc3, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc5, 0xc5,
+		 0xc5, 0xc5, 0xc5, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6,
+		 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc9, 0xc9, 0xc9,
+		 0xc9, 0xc9, 0xc9, 0xca, 0xca, 0xca, 0xca, 0xca,
+		 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcc, 0xcc, 0xcc,
+		 0xcc, 0xcc, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xce,
+		 0xce, 0xce, 0xce, 0xce, 0xcf, 0xcf, 0xcf, 0xcf,
+		 0xcf, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd1, 0xd1,
+		 0xd1, 0xd1, 0xd1, 0xd1, 0xd3, 0xd3, 0xd3, 0xd3,
+		 0xd3, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd6, 0xd6,
+		 0xd6, 0xd6, 0xd6, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7,
+		 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd9, 0xd9, 0xd9,
+		 0xd9, 0xd9, 0xda, 0xda, 0xda, 0xda, 0xda, 0xdb,
+		 0xdb, 0xdb, 0xdb, 0xdb, 0xdd, 0xdd, 0xdd, 0xdd,
+		 0xdd, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xdf,
+		 0xdf, 0xdf, 0xdf, 0xdf, 0xe0, 0xe0, 0xe0, 0xe0,
+		 0xe0, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe2, 0xe2,
+		 0xe2, 0xe2, 0xe2, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3,
+		 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe5, 0xe5, 0xe5,
+		 0xe5, 0xe5, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe7,
+		 0xe7, 0xe7, 0xe7, 0xe7, 0xe8, 0xe8, 0xe8, 0xe8,
+		 0xe8, 0xe9, 0xe9, 0xe9, 0xe9, 0xeb, 0xeb, 0xeb,
+		 0xeb, 0xeb, 0xec, 0xec, 0xec, 0xec, 0xec, 0xed,
+		 0xed, 0xed, 0xed, 0xed, 0xee, 0xee, 0xee, 0xee,
+		 0xee, 0xef, 0xef, 0xef, 0xef, 0xef, 0xf0, 0xf0,
+		 0xf0, 0xf0, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf3,
+		 0xf3, 0xf3, 0xf3, 0xf3, 0xf4, 0xf4, 0xf4, 0xf4,
+		 0xf4, 0xf5, 0xf5, 0xf5, 0xf5, 0xf6, 0xf6, 0xf6,
+		 0xf6, 0xf6, 0xf7, 0xf7, 0xf7, 0xf7, 0xf8, 0xf8,
+		 0xf8, 0xf8, 0xf8, 0xf9, 0xf9, 0xf9, 0xf9, 0xfa,
+		 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb}
+	    },
+	    {				/* gamma 1 - from tp6810 + soi763a */
+		{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x01, 0x02, 0x03, 0x05, 0x07, 0x08, 0x09, 0x0a,
+		 0x0c, 0x0d, 0x0e, 0x10, 0x11, 0x12, 0x14, 0x15,
+		 0x16, 0x17, 0x18, 0x1a, 0x1a, 0x1b, 0x1c, 0x1e,
+		 0x1f, 0x20, 0x22, 0x22, 0x23, 0x25, 0x26, 0x27,
+		 0x27, 0x28, 0x29, 0x2b, 0x2b, 0x2c, 0x2d, 0x2f,
+		 0x2f, 0x30, 0x31, 0x33, 0x33, 0x34, 0x35, 0x35,
+		 0x37, 0x38, 0x38, 0x39, 0x3a, 0x3a, 0x3b, 0x3c,
+		 0x3c, 0x3d, 0x3f, 0x3f, 0x40, 0x42, 0x42, 0x43,
+		 0x43, 0x44, 0x45, 0x45, 0x47, 0x47, 0x48, 0x49,
+		 0x49, 0x4a, 0x4a, 0x4b, 0x4b, 0x4c, 0x4d, 0x4d,
+		 0x4f, 0x4f, 0x50, 0x50, 0x52, 0x52, 0x53, 0x53,
+		 0x54, 0x54, 0x55, 0x56, 0x56, 0x58, 0x58, 0x59,
+		 0x59, 0x5a, 0x5a, 0x5b, 0x5b, 0x5c, 0x5c, 0x5e,
+		 0x5e, 0x5e, 0x5f, 0x5f, 0x60, 0x60, 0x61, 0x61,
+		 0x62, 0x62, 0x63, 0x63, 0x65, 0x65, 0x65, 0x66,
+		 0x66, 0x67, 0x67, 0x68, 0x68, 0x69, 0x69, 0x69,
+		 0x6a, 0x6a, 0x6c, 0x6c, 0x6d, 0x6d, 0x6d, 0x6e,
+		 0x6e, 0x6f, 0x6f, 0x6f, 0x70, 0x70, 0x71, 0x71,
+		 0x73, 0x73, 0x73, 0x74, 0x74, 0x74, 0x75, 0x75,
+		 0x77, 0x77, 0x77, 0x78, 0x78, 0x79, 0x79, 0x79,
+		 0x7a, 0x7a, 0x7a, 0x7b, 0x7b, 0x7c, 0x7c, 0x7c,
+		 0x7d, 0x7d, 0x7d, 0x7f, 0x7f, 0x80, 0x80, 0x80,
+		 0x81, 0x81, 0x81, 0x82, 0x82, 0x82, 0x84, 0x84,
+		 0x84, 0x85, 0x85, 0x85, 0x86, 0x86, 0x86, 0x88,
+		 0x88, 0x88, 0x89, 0x89, 0x89, 0x8a, 0x8a, 0x8a,
+		 0x8b, 0x8b, 0x8b, 0x8d, 0x8d, 0x8d, 0x8e, 0x8e,
+		 0x8e, 0x8f, 0x8f, 0x8f, 0x90, 0x90, 0x90, 0x91,
+		 0x91, 0x91, 0x92, 0x92, 0x92, 0x92, 0x93, 0x93,
+		 0x93, 0x94, 0x94, 0x94, 0x96, 0x96, 0x96, 0x97,
+		 0x97, 0x97, 0x97, 0x98, 0x98, 0x98, 0x99, 0x99,
+		 0x99, 0x9a, 0x9a, 0x9a, 0x9a, 0x9b, 0x9b, 0x9b,
+		 0x9c, 0x9c, 0x9c, 0x9c, 0x9d, 0x9d, 0x9d, 0x9e,
+		 0x9e, 0x9e, 0x9e, 0xa0, 0xa0, 0xa0, 0xa1, 0xa1,
+		 0xa1, 0xa1, 0xa2, 0xa2, 0xa2, 0xa2, 0xa3, 0xa3,
+		 0xa3, 0xa4, 0xa4, 0xa4, 0xa4, 0xa5, 0xa5, 0xa5,
+		 0xa5, 0xa6, 0xa6, 0xa6, 0xa8, 0xa8, 0xa8, 0xa8,
+		 0xa9, 0xa9, 0xa9, 0xa9, 0xab, 0xab, 0xab, 0xab,
+		 0xac, 0xac, 0xac, 0xad, 0xad, 0xad, 0xad, 0xae,
+		 0xae, 0xae, 0xae, 0xaf, 0xaf, 0xaf, 0xaf, 0xb0,
+		 0xb0, 0xb0, 0xb0, 0xb1, 0xb1, 0xb1, 0xb1, 0xb2,
+		 0xb2, 0xb2, 0xb2, 0xb3, 0xb3, 0xb3, 0xb3, 0xb4,
+		 0xb4, 0xb4, 0xb4, 0xb6, 0xb6, 0xb6, 0xb6, 0xb7,
+		 0xb7, 0xb7, 0xb7, 0xb7, 0xb8, 0xb8, 0xb8, 0xb8,
+		 0xb9, 0xb9, 0xb9, 0xb9, 0xba, 0xba, 0xba, 0xba,
+		 0xbc, 0xbc, 0xbc, 0xbc, 0xbd, 0xbd, 0xbd, 0xbd,
+		 0xbd, 0xbe, 0xbe, 0xbe, 0xbe, 0xbf, 0xbf, 0xbf,
+		 0xbf, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc2, 0xc2,
+		 0xc2, 0xc2, 0xc3, 0xc3, 0xc3, 0xc3, 0xc4, 0xc4,
+		 0xc4, 0xc4, 0xc4, 0xc5, 0xc5, 0xc5, 0xc5, 0xc6,
+		 0xc6, 0xc6, 0xc6, 0xc6, 0xc7, 0xc7, 0xc7, 0xc7,
+		 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xca, 0xca, 0xca,
+		 0xca, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcc, 0xcc,
+		 0xcc, 0xcc, 0xcc, 0xcd, 0xcd, 0xcd, 0xcd, 0xce,
+		 0xce, 0xce, 0xce, 0xce, 0xcf, 0xcf, 0xcf, 0xcf,
+		 0xcf, 0xd0, 0xd0, 0xd0, 0xd0, 0xd1, 0xd1, 0xd1,
+		 0xd1, 0xd1, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd4,
+		 0xd4, 0xd4, 0xd4, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6,
+		 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd8, 0xd8, 0xd8,
+		 0xd8, 0xd8, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xda,
+		 0xda, 0xda, 0xda, 0xda, 0xdb, 0xdb, 0xdb, 0xdb,
+		 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xde, 0xde, 0xde,
+		 0xde, 0xde, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xe0,
+		 0xe0, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xe1,
+		 0xe1, 0xe2, 0xe2, 0xe2, 0xe2, 0xe2, 0xe3, 0xe3,
+		 0xe3, 0xe3, 0xe3, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4,
+		 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe6, 0xe6, 0xe6,
+		 0xe6, 0xe6, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe8,
+		 0xe8, 0xe8, 0xe8, 0xe8, 0xe9, 0xe9, 0xe9, 0xe9,
+		 0xe9, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xec, 0xec,
+		 0xec, 0xec, 0xec, 0xed, 0xed, 0xed, 0xed, 0xed,
+		 0xee, 0xee, 0xee, 0xee, 0xee, 0xef, 0xef, 0xef,
+		 0xef, 0xef, 0xef, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+		 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf3, 0xf3, 0xf3,
+		 0xf3, 0xf3, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf5,
+		 0xf5, 0xf5, 0xf5, 0xf5, 0xf6, 0xf6, 0xf6, 0xf6,
+		 0xf6, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf8, 0xf8,
+		 0xf8, 0xf8, 0xf8, 0xf8, 0xf9, 0xf9, 0xf9, 0xf9,
+		 0xf9, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa,
+		 0xfa, 0xfa, 0xfa, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc,
+		 0xfc, 0xfc, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfe,
+		 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+		{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03,
+		 0x05, 0x07, 0x07, 0x08, 0x09, 0x0a, 0x0c, 0x0d,
+		 0x0e, 0x10, 0x10, 0x11, 0x12, 0x14, 0x15, 0x15,
+		 0x16, 0x17, 0x18, 0x1a, 0x1a, 0x1b, 0x1c, 0x1e,
+		 0x1e, 0x1f, 0x20, 0x20, 0x22, 0x23, 0x25, 0x25,
+		 0x26, 0x27, 0x27, 0x28, 0x29, 0x29, 0x2b, 0x2c,
+		 0x2c, 0x2d, 0x2d, 0x2f, 0x30, 0x30, 0x31, 0x31,
+		 0x33, 0x34, 0x34, 0x35, 0x35, 0x37, 0x38, 0x38,
+		 0x39, 0x39, 0x3a, 0x3a, 0x3b, 0x3b, 0x3c, 0x3d,
+		 0x3d, 0x3f, 0x3f, 0x40, 0x40, 0x42, 0x42, 0x43,
+		 0x43, 0x44, 0x44, 0x45, 0x45, 0x47, 0x47, 0x48,
+		 0x48, 0x49, 0x49, 0x4a, 0x4a, 0x4b, 0x4b, 0x4c,
+		 0x4c, 0x4d, 0x4d, 0x4d, 0x4f, 0x4f, 0x50, 0x50,
+		 0x52, 0x52, 0x53, 0x53, 0x53, 0x54, 0x54, 0x55,
+		 0x55, 0x56, 0x56, 0x56, 0x58, 0x58, 0x59, 0x59,
+		 0x5a, 0x5a, 0x5a, 0x5b, 0x5b, 0x5c, 0x5c, 0x5c,
+		 0x5e, 0x5e, 0x5f, 0x5f, 0x5f, 0x60, 0x60, 0x60,
+		 0x61, 0x61, 0x62, 0x62, 0x62, 0x63, 0x63, 0x65,
+		 0x65, 0x65, 0x66, 0x66, 0x66, 0x67, 0x67, 0x67,
+		 0x68, 0x68, 0x69, 0x69, 0x69, 0x6a, 0x6a, 0x6a,
+		 0x6c, 0x6c, 0x6c, 0x6d, 0x6d, 0x6d, 0x6e, 0x6e,
+		 0x6e, 0x6f, 0x6f, 0x6f, 0x70, 0x70, 0x70, 0x71,
+		 0x71, 0x71, 0x73, 0x73, 0x73, 0x74, 0x74, 0x74,
+		 0x75, 0x75, 0x75, 0x77, 0x77, 0x77, 0x78, 0x78,
+		 0x78, 0x79, 0x79, 0x79, 0x79, 0x7a, 0x7a, 0x7a,
+		 0x7b, 0x7b, 0x7b, 0x7c, 0x7c, 0x7c, 0x7c, 0x7d,
+		 0x7d, 0x7d, 0x7f, 0x7f, 0x7f, 0x80, 0x80, 0x80,
+		 0x80, 0x81, 0x81, 0x81, 0x82, 0x82, 0x82, 0x82,
+		 0x84, 0x84, 0x84, 0x85, 0x85, 0x85, 0x85, 0x86,
+		 0x86, 0x86, 0x88, 0x88, 0x88, 0x88, 0x89, 0x89,
+		 0x89, 0x89, 0x8a, 0x8a, 0x8a, 0x8b, 0x8b, 0x8b,
+		 0x8b, 0x8d, 0x8d, 0x8d, 0x8d, 0x8e, 0x8e, 0x8e,
+		 0x8e, 0x8f, 0x8f, 0x8f, 0x90, 0x90, 0x90, 0x90,
+		 0x91, 0x91, 0x91, 0x91, 0x92, 0x92, 0x92, 0x92,
+		 0x93, 0x93, 0x93, 0x93, 0x94, 0x94, 0x94, 0x94,
+		 0x96, 0x96, 0x96, 0x96, 0x97, 0x97, 0x97, 0x97,
+		 0x98, 0x98, 0x98, 0x98, 0x99, 0x99, 0x99, 0x99,
+		 0x9a, 0x9a, 0x9a, 0x9a, 0x9a, 0x9b, 0x9b, 0x9b,
+		 0x9b, 0x9c, 0x9c, 0x9c, 0x9c, 0x9d, 0x9d, 0x9d,
+		 0x9d, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0xa0, 0xa0,
+		 0xa0, 0xa0, 0xa1, 0xa1, 0xa1, 0xa1, 0xa2, 0xa2,
+		 0xa2, 0xa2, 0xa2, 0xa3, 0xa3, 0xa3, 0xa3, 0xa4,
+		 0xa4, 0xa4, 0xa4, 0xa4, 0xa5, 0xa5, 0xa5, 0xa5,
+		 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa8, 0xa8, 0xa8,
+		 0xa8, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xab, 0xab,
+		 0xab, 0xab, 0xac, 0xac, 0xac, 0xac, 0xac, 0xad,
+		 0xad, 0xad, 0xad, 0xad, 0xae, 0xae, 0xae, 0xae,
+		 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xb0, 0xb0, 0xb0,
+		 0xb0, 0xb0, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb2,
+		 0xb2, 0xb2, 0xb2, 0xb2, 0xb3, 0xb3, 0xb3, 0xb3,
+		 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb6, 0xb6, 0xb6,
+		 0xb6, 0xb6, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb8,
+		 0xb8, 0xb8, 0xb8, 0xb8, 0xb9, 0xb9, 0xb9, 0xb9,
+		 0xb9, 0xba, 0xba, 0xba, 0xba, 0xba, 0xbc, 0xbc,
+		 0xbc, 0xbc, 0xbc, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd,
+		 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe, 0xbf, 0xbf,
+		 0xbf, 0xbf, 0xbf, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+		 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc3, 0xc3, 0xc3,
+		 0xc3, 0xc3, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc5,
+		 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc6, 0xc6, 0xc6,
+		 0xc6, 0xc6, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc9,
+		 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xca, 0xca, 0xca,
+		 0xca, 0xca, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcc,
+		 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcd, 0xcd, 0xcd,
+		 0xcd, 0xcd, 0xce, 0xce, 0xce, 0xce, 0xce, 0xcf,
+		 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xd0, 0xd0, 0xd0,
+		 0xd0, 0xd0, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1,
+		 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd4, 0xd4, 0xd4,
+		 0xd4, 0xd4, 0xd4, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6,
+		 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd8, 0xd8,
+		 0xd8, 0xd8, 0xd8, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9,
+		 0xd9, 0xda, 0xda, 0xda, 0xda, 0xda, 0xdb, 0xdb,
+		 0xdb, 0xdb, 0xdb, 0xdb, 0xdd, 0xdd, 0xdd, 0xdd,
+		 0xdd, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xdf,
+		 0xdf, 0xdf, 0xdf, 0xdf, 0xe0, 0xe0, 0xe0, 0xe0,
+		 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe2,
+		 0xe2, 0xe2, 0xe2, 0xe2, 0xe2, 0xe3, 0xe3, 0xe3,
+		 0xe3, 0xe3, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4,
+		 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe6, 0xe6, 0xe6,
+		 0xe6, 0xe6, 0xe6, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7,
+		 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe9, 0xe9,
+		 0xe9, 0xe9, 0xe9, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb,
+		 0xeb, 0xec, 0xec, 0xec, 0xec, 0xec, 0xed, 0xed,
+		 0xed, 0xed, 0xed, 0xed, 0xee, 0xee, 0xee, 0xee,
+		 0xee, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xf0,
+		 0xf0, 0xf0, 0xf0, 0xf0, 0xf1, 0xf1, 0xf1, 0xf1,
+		 0xf1, 0xf1, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf4,
+		 0xf4, 0xf4, 0xf4, 0xf4, 0xf5, 0xf5, 0xf5, 0xf5,
+		 0xf5, 0xf5, 0xf6, 0xf6, 0xf6, 0xf6, 0xf6, 0xf7,
+		 0xf7, 0xf7, 0xf7, 0xf7, 0xf8, 0xf8, 0xf8, 0xf8,
+		 0xf8, 0xf8, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xfa,
+		 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa,
+		 0xfa, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfc, 0xfc,
+		 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc,
+		 0xfc, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe,
+		 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+		{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0x01, 0x02, 0x03, 0x05, 0x05, 0x07,
+		 0x08, 0x09, 0x0a, 0x0a, 0x0c, 0x0d, 0x0e, 0x0e,
+		 0x10, 0x11, 0x12, 0x12, 0x14, 0x15, 0x16, 0x16,
+		 0x17, 0x18, 0x18, 0x1a, 0x1b, 0x1b, 0x1c, 0x1e,
+		 0x1e, 0x1f, 0x1f, 0x20, 0x22, 0x22, 0x23, 0x23,
+		 0x25, 0x26, 0x26, 0x27, 0x27, 0x28, 0x29, 0x29,
+		 0x2b, 0x2b, 0x2c, 0x2c, 0x2d, 0x2d, 0x2f, 0x30,
+		 0x30, 0x31, 0x31, 0x33, 0x33, 0x34, 0x34, 0x35,
+		 0x35, 0x37, 0x37, 0x38, 0x38, 0x39, 0x39, 0x3a,
+		 0x3a, 0x3b, 0x3b, 0x3b, 0x3c, 0x3c, 0x3d, 0x3d,
+		 0x3f, 0x3f, 0x40, 0x40, 0x42, 0x42, 0x42, 0x43,
+		 0x43, 0x44, 0x44, 0x45, 0x45, 0x47, 0x47, 0x47,
+		 0x48, 0x48, 0x49, 0x49, 0x49, 0x4a, 0x4a, 0x4b,
+		 0x4b, 0x4b, 0x4c, 0x4c, 0x4d, 0x4d, 0x4d, 0x4f,
+		 0x4f, 0x50, 0x50, 0x50, 0x52, 0x52, 0x52, 0x53,
+		 0x53, 0x54, 0x54, 0x54, 0x55, 0x55, 0x55, 0x56,
+		 0x56, 0x58, 0x58, 0x58, 0x59, 0x59, 0x59, 0x5a,
+		 0x5a, 0x5a, 0x5b, 0x5b, 0x5b, 0x5c, 0x5c, 0x5c,
+		 0x5e, 0x5e, 0x5e, 0x5f, 0x5f, 0x5f, 0x60, 0x60,
+		 0x60, 0x61, 0x61, 0x61, 0x62, 0x62, 0x62, 0x63,
+		 0x63, 0x63, 0x65, 0x65, 0x65, 0x66, 0x66, 0x66,
+		 0x66, 0x67, 0x67, 0x67, 0x68, 0x68, 0x68, 0x69,
+		 0x69, 0x69, 0x6a, 0x6a, 0x6a, 0x6a, 0x6c, 0x6c,
+		 0x6c, 0x6d, 0x6d, 0x6d, 0x6d, 0x6e, 0x6e, 0x6e,
+		 0x6f, 0x6f, 0x6f, 0x6f, 0x70, 0x70, 0x70, 0x71,
+		 0x71, 0x71, 0x71, 0x73, 0x73, 0x73, 0x74, 0x74,
+		 0x74, 0x74, 0x75, 0x75, 0x75, 0x75, 0x77, 0x77,
+		 0x77, 0x78, 0x78, 0x78, 0x78, 0x79, 0x79, 0x79,
+		 0x79, 0x7a, 0x7a, 0x7a, 0x7a, 0x7b, 0x7b, 0x7b,
+		 0x7b, 0x7c, 0x7c, 0x7c, 0x7c, 0x7d, 0x7d, 0x7d,
+		 0x7d, 0x7f, 0x7f, 0x7f, 0x7f, 0x80, 0x80, 0x80,
+		 0x80, 0x81, 0x81, 0x81, 0x81, 0x82, 0x82, 0x82,
+		 0x82, 0x84, 0x84, 0x84, 0x84, 0x85, 0x85, 0x85,
+		 0x85, 0x86, 0x86, 0x86, 0x86, 0x88, 0x88, 0x88,
+		 0x88, 0x88, 0x89, 0x89, 0x89, 0x89, 0x8a, 0x8a,
+		 0x8a, 0x8a, 0x8b, 0x8b, 0x8b, 0x8b, 0x8b, 0x8d,
+		 0x8d, 0x8d, 0x8d, 0x8e, 0x8e, 0x8e, 0x8e, 0x8e,
+		 0x8f, 0x8f, 0x8f, 0x8f, 0x90, 0x90, 0x90, 0x90,
+		 0x90, 0x91, 0x91, 0x91, 0x91, 0x92, 0x92, 0x92,
+		 0x92, 0x92, 0x93, 0x93, 0x93, 0x93, 0x94, 0x94,
+		 0x94, 0x94, 0x94, 0x96, 0x96, 0x96, 0x96, 0x96,
+		 0x97, 0x97, 0x97, 0x97, 0x97, 0x98, 0x98, 0x98,
+		 0x98, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a, 0x9a,
+		 0x9a, 0x9a, 0x9a, 0x9b, 0x9b, 0x9b, 0x9b, 0x9b,
+		 0x9c, 0x9c, 0x9c, 0x9c, 0x9c, 0x9d, 0x9d, 0x9d,
+		 0x9d, 0x9d, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0xa0,
+		 0xa0, 0xa0, 0xa0, 0xa0, 0xa1, 0xa1, 0xa1, 0xa1,
+		 0xa1, 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0xa3, 0xa3,
+		 0xa3, 0xa3, 0xa3, 0xa4, 0xa4, 0xa4, 0xa4, 0xa4,
+		 0xa4, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5, 0xa6, 0xa6,
+		 0xa6, 0xa6, 0xa6, 0xa8, 0xa8, 0xa8, 0xa8, 0xa8,
+		 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xab, 0xab,
+		 0xab, 0xab, 0xab, 0xac, 0xac, 0xac, 0xac, 0xac,
+		 0xad, 0xad, 0xad, 0xad, 0xad, 0xad, 0xae, 0xae,
+		 0xae, 0xae, 0xae, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf,
+		 0xaf, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb1, 0xb1,
+		 0xb1, 0xb1, 0xb1, 0xb1, 0xb2, 0xb2, 0xb2, 0xb2,
+		 0xb2, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb4,
+		 0xb4, 0xb4, 0xb4, 0xb4, 0xb6, 0xb6, 0xb6, 0xb6,
+		 0xb6, 0xb6, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7,
+		 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb9, 0xb9, 0xb9,
+		 0xb9, 0xb9, 0xb9, 0xba, 0xba, 0xba, 0xba, 0xba,
+		 0xba, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbd, 0xbd,
+		 0xbd, 0xbd, 0xbd, 0xbd, 0xbe, 0xbe, 0xbe, 0xbe,
+		 0xbe, 0xbe, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf,
+		 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc2, 0xc2,
+		 0xc2, 0xc2, 0xc2, 0xc3, 0xc3, 0xc3, 0xc3, 0xc3,
+		 0xc3, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc5,
+		 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc6, 0xc6, 0xc6,
+		 0xc6, 0xc6, 0xc6, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7,
+		 0xc7, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xca, 0xca,
+		 0xca, 0xca, 0xca, 0xca, 0xcb, 0xcb, 0xcb, 0xcb,
+		 0xcb, 0xcb, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc,
+		 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xce, 0xce,
+		 0xce, 0xce, 0xce, 0xce, 0xcf, 0xcf, 0xcf, 0xcf,
+		 0xcf, 0xcf, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0,
+		 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd3, 0xd3,
+		 0xd3, 0xd3, 0xd3, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4,
+		 0xd4, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd7,
+		 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd8, 0xd8, 0xd8,
+		 0xd8, 0xd8, 0xd8, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9,
+		 0xd9, 0xda, 0xda, 0xda, 0xda, 0xda, 0xda, 0xdb,
+		 0xdb, 0xdb, 0xdb, 0xdb, 0xdd, 0xdd, 0xdd, 0xdd,
+		 0xdd, 0xdd, 0xde, 0xde, 0xde, 0xde, 0xde, 0xde,
+		 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xe0, 0xe0,
+		 0xe0, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xe1,
+		 0xe1, 0xe2, 0xe2, 0xe2, 0xe2, 0xe2, 0xe2, 0xe3,
+		 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe4, 0xe4, 0xe4,
+		 0xe4, 0xe4, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5,
+		 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe7, 0xe7,
+		 0xe7, 0xe7, 0xe7, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8,
+		 0xe8, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xeb, 0xeb,
+		 0xeb, 0xeb, 0xeb, 0xeb, 0xec, 0xec, 0xec, 0xec,
+		 0xec, 0xed, 0xed, 0xed, 0xed, 0xed, 0xed, 0xee,
+		 0xee, 0xee, 0xee, 0xee, 0xef, 0xef, 0xef, 0xef,
+		 0xef, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf1,
+		 0xf1, 0xf1, 0xf1, 0xf1, 0xf3, 0xf3, 0xf3, 0xf3,
+		 0xf3, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf5, 0xf5,
+		 0xf5, 0xf5, 0xf5, 0xf6, 0xf6, 0xf6, 0xf6, 0xf6,
+		 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf8, 0xf8, 0xf8,
+		 0xf8, 0xf8, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xfa,
+		 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa,
+		 0xfa, 0xfb, 0xfb, 0xfb, 0xfb, 0xfc, 0xfc, 0xfc,
+		 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfd,
+		 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
+	    },
+	    {							/* gamma 2 */
+		{0x00, 0x01, 0x02, 0x05, 0x07, 0x08, 0x0a, 0x0c,
+		 0x0d, 0x0e, 0x10, 0x12, 0x14, 0x15, 0x16, 0x17,
+		 0x18, 0x1a, 0x1b, 0x1c, 0x1e, 0x1f, 0x20, 0x22,
+		 0x23, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2b, 0x2c,
+		 0x2d, 0x2d, 0x2f, 0x30, 0x31, 0x33, 0x34, 0x34,
+		 0x35, 0x37, 0x38, 0x38, 0x39, 0x3a, 0x3b, 0x3b,
+		 0x3c, 0x3d, 0x3f, 0x3f, 0x40, 0x42, 0x42, 0x43,
+		 0x44, 0x44, 0x45, 0x47, 0x47, 0x48, 0x49, 0x49,
+		 0x4a, 0x4b, 0x4b, 0x4c, 0x4c, 0x4d, 0x4f, 0x4f,
+		 0x50, 0x50, 0x52, 0x53, 0x53, 0x54, 0x54, 0x55,
+		 0x55, 0x56, 0x56, 0x58, 0x58, 0x59, 0x5a, 0x5a,
+		 0x5b, 0x5b, 0x5c, 0x5c, 0x5e, 0x5e, 0x5f, 0x5f,
+		 0x60, 0x60, 0x61, 0x61, 0x62, 0x62, 0x63, 0x63,
+		 0x65, 0x65, 0x65, 0x66, 0x66, 0x67, 0x67, 0x68,
+		 0x68, 0x69, 0x69, 0x6a, 0x6a, 0x6a, 0x6c, 0x6c,
+		 0x6d, 0x6d, 0x6e, 0x6e, 0x6e, 0x6f, 0x6f, 0x70,
+		 0x70, 0x70, 0x71, 0x71, 0x73, 0x73, 0x73, 0x74,
+		 0x74, 0x75, 0x75, 0x75, 0x77, 0x77, 0x78, 0x78,
+		 0x78, 0x79, 0x79, 0x79, 0x7a, 0x7a, 0x7b, 0x7b,
+		 0x7b, 0x7c, 0x7c, 0x7c, 0x7d, 0x7d, 0x7f, 0x7f,
+		 0x7f, 0x80, 0x80, 0x80, 0x81, 0x81, 0x81, 0x82,
+		 0x82, 0x82, 0x84, 0x84, 0x84, 0x85, 0x85, 0x85,
+		 0x86, 0x86, 0x86, 0x88, 0x88, 0x88, 0x89, 0x89,
+		 0x89, 0x8a, 0x8a, 0x8a, 0x8b, 0x8b, 0x8b, 0x8d,
+		 0x8d, 0x8d, 0x8d, 0x8e, 0x8e, 0x8e, 0x8f, 0x8f,
+		 0x8f, 0x90, 0x90, 0x90, 0x91, 0x91, 0x91, 0x91,
+		 0x92, 0x92, 0x92, 0x93, 0x93, 0x93, 0x93, 0x94,
+		 0x94, 0x94, 0x96, 0x96, 0x96, 0x97, 0x97, 0x97,
+		 0x97, 0x98, 0x98, 0x98, 0x98, 0x99, 0x99, 0x99,
+		 0x9a, 0x9a, 0x9a, 0x9a, 0x9b, 0x9b, 0x9b, 0x9b,
+		 0x9c, 0x9c, 0x9c, 0x9d, 0x9d, 0x9d, 0x9d, 0x9e,
+		 0x9e, 0x9e, 0x9e, 0xa0, 0xa0, 0xa0, 0xa0, 0xa1,
+		 0xa1, 0xa1, 0xa1, 0xa2, 0xa2, 0xa2, 0xa2, 0xa3,
+		 0xa3, 0xa3, 0xa4, 0xa4, 0xa4, 0xa4, 0xa5, 0xa5,
+		 0xa5, 0xa5, 0xa5, 0xa6, 0xa6, 0xa6, 0xa6, 0xa8,
+		 0xa8, 0xa8, 0xa8, 0xa9, 0xa9, 0xa9, 0xa9, 0xab,
+		 0xab, 0xab, 0xab, 0xac, 0xac, 0xac, 0xac, 0xad,
+		 0xad, 0xad, 0xad, 0xad, 0xae, 0xae, 0xae, 0xae,
+		 0xaf, 0xaf, 0xaf, 0xaf, 0xb0, 0xb0, 0xb0, 0xb0,
+		 0xb0, 0xb1, 0xb1, 0xb1, 0xb1, 0xb2, 0xb2, 0xb2,
+		 0xb2, 0xb2, 0xb3, 0xb3, 0xb3, 0xb3, 0xb4, 0xb4,
+		 0xb4, 0xb4, 0xb4, 0xb6, 0xb6, 0xb6, 0xb6, 0xb7,
+		 0xb7, 0xb7, 0xb7, 0xb7, 0xb8, 0xb8, 0xb8, 0xb8,
+		 0xb8, 0xb9, 0xb9, 0xb9, 0xb9, 0xba, 0xba, 0xba,
+		 0xba, 0xba, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbd,
+		 0xbd, 0xbd, 0xbd, 0xbd, 0xbe, 0xbe, 0xbe, 0xbe,
+		 0xbe, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xc0, 0xc0,
+		 0xc0, 0xc0, 0xc0, 0xc2, 0xc2, 0xc2, 0xc2, 0xc3,
+		 0xc3, 0xc3, 0xc3, 0xc3, 0xc3, 0xc4, 0xc4, 0xc4,
+		 0xc4, 0xc4, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc6,
+		 0xc6, 0xc6, 0xc6, 0xc6, 0xc7, 0xc7, 0xc7, 0xc7,
+		 0xc7, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xca, 0xca,
+		 0xca, 0xca, 0xca, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb,
+		 0xcb, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcd, 0xcd,
+		 0xcd, 0xcd, 0xcd, 0xce, 0xce, 0xce, 0xce, 0xce,
+		 0xce, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xd0, 0xd0,
+		 0xd0, 0xd0, 0xd0, 0xd0, 0xd1, 0xd1, 0xd1, 0xd1,
+		 0xd1, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd4, 0xd4,
+		 0xd4, 0xd4, 0xd4, 0xd4, 0xd6, 0xd6, 0xd6, 0xd6,
+		 0xd6, 0xd6, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd8,
+		 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd9, 0xd9, 0xd9,
+		 0xd9, 0xd9, 0xda, 0xda, 0xda, 0xda, 0xda, 0xda,
+		 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdd, 0xdd,
+		 0xdd, 0xdd, 0xdd, 0xde, 0xde, 0xde, 0xde, 0xde,
+		 0xde, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xe0,
+		 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1,
+		 0xe1, 0xe1, 0xe1, 0xe2, 0xe2, 0xe2, 0xe2, 0xe2,
+		 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe4, 0xe4,
+		 0xe4, 0xe4, 0xe4, 0xe4, 0xe5, 0xe5, 0xe5, 0xe5,
+		 0xe5, 0xe5, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6,
+		 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe8, 0xe8,
+		 0xe8, 0xe8, 0xe8, 0xe8, 0xe9, 0xe9, 0xe9, 0xe9,
+		 0xe9, 0xe9, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb,
+		 0xec, 0xec, 0xec, 0xec, 0xec, 0xed, 0xed, 0xed,
+		 0xed, 0xed, 0xed, 0xee, 0xee, 0xee, 0xee, 0xee,
+		 0xee, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xf0,
+		 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf1, 0xf1, 0xf1,
+		 0xf1, 0xf1, 0xf1, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3,
+		 0xf3, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf5,
+		 0xf5, 0xf5, 0xf5, 0xf5, 0xf5, 0xf6, 0xf6, 0xf6,
+		 0xf6, 0xf6, 0xf6, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7,
+		 0xf7, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf9,
+		 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xfa, 0xfa, 0xfa,
+		 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa,
+		 0xfa, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb},
+		{0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x05,
+		 0x07, 0x08, 0x09, 0x0a, 0x0d, 0x0e, 0x10, 0x11,
+		 0x12, 0x14, 0x15, 0x16, 0x16, 0x17, 0x18, 0x1a,
+		 0x1b, 0x1c, 0x1e, 0x1f, 0x20, 0x20, 0x22, 0x23,
+		 0x25, 0x26, 0x26, 0x27, 0x28, 0x29, 0x29, 0x2b,
+		 0x2c, 0x2d, 0x2d, 0x2f, 0x30, 0x30, 0x31, 0x33,
+		 0x33, 0x34, 0x35, 0x35, 0x37, 0x38, 0x38, 0x39,
+		 0x3a, 0x3a, 0x3b, 0x3b, 0x3c, 0x3d, 0x3d, 0x3f,
+		 0x3f, 0x40, 0x42, 0x42, 0x43, 0x43, 0x44, 0x44,
+		 0x45, 0x45, 0x47, 0x47, 0x48, 0x48, 0x49, 0x4a,
+		 0x4a, 0x4b, 0x4b, 0x4c, 0x4c, 0x4d, 0x4d, 0x4d,
+		 0x4f, 0x4f, 0x50, 0x50, 0x52, 0x52, 0x53, 0x53,
+		 0x54, 0x54, 0x55, 0x55, 0x56, 0x56, 0x56, 0x58,
+		 0x58, 0x59, 0x59, 0x5a, 0x5a, 0x5a, 0x5b, 0x5b,
+		 0x5c, 0x5c, 0x5c, 0x5e, 0x5e, 0x5f, 0x5f, 0x5f,
+		 0x60, 0x60, 0x61, 0x61, 0x61, 0x62, 0x62, 0x63,
+		 0x63, 0x63, 0x65, 0x65, 0x65, 0x66, 0x66, 0x67,
+		 0x67, 0x67, 0x68, 0x68, 0x68, 0x69, 0x69, 0x69,
+		 0x6a, 0x6a, 0x6c, 0x6c, 0x6c, 0x6d, 0x6d, 0x6d,
+		 0x6e, 0x6e, 0x6e, 0x6f, 0x6f, 0x6f, 0x70, 0x70,
+		 0x70, 0x71, 0x71, 0x71, 0x73, 0x73, 0x73, 0x73,
+		 0x74, 0x74, 0x74, 0x75, 0x75, 0x75, 0x77, 0x77,
+		 0x77, 0x78, 0x78, 0x78, 0x79, 0x79, 0x79, 0x79,
+		 0x7a, 0x7a, 0x7a, 0x7b, 0x7b, 0x7b, 0x7b, 0x7c,
+		 0x7c, 0x7c, 0x7d, 0x7d, 0x7d, 0x7d, 0x7f, 0x7f,
+		 0x7f, 0x80, 0x80, 0x80, 0x80, 0x81, 0x81, 0x81,
+		 0x82, 0x82, 0x82, 0x82, 0x84, 0x84, 0x84, 0x84,
+		 0x85, 0x85, 0x85, 0x85, 0x86, 0x86, 0x86, 0x88,
+		 0x88, 0x88, 0x88, 0x89, 0x89, 0x89, 0x89, 0x8a,
+		 0x8a, 0x8a, 0x8a, 0x8b, 0x8b, 0x8b, 0x8b, 0x8d,
+		 0x8d, 0x8d, 0x8d, 0x8e, 0x8e, 0x8e, 0x8e, 0x8f,
+		 0x8f, 0x8f, 0x8f, 0x90, 0x90, 0x90, 0x90, 0x91,
+		 0x91, 0x91, 0x91, 0x91, 0x92, 0x92, 0x92, 0x92,
+		 0x93, 0x93, 0x93, 0x93, 0x94, 0x94, 0x94, 0x94,
+		 0x94, 0x96, 0x96, 0x96, 0x96, 0x97, 0x97, 0x97,
+		 0x97, 0x98, 0x98, 0x98, 0x98, 0x98, 0x99, 0x99,
+		 0x99, 0x99, 0x99, 0x9a, 0x9a, 0x9a, 0x9a, 0x9b,
+		 0x9b, 0x9b, 0x9b, 0x9b, 0x9c, 0x9c, 0x9c, 0x9c,
+		 0x9d, 0x9d, 0x9d, 0x9d, 0x9d, 0x9e, 0x9e, 0x9e,
+		 0x9e, 0x9e, 0xa0, 0xa0, 0xa0, 0xa0, 0xa0, 0xa1,
+		 0xa1, 0xa1, 0xa1, 0xa1, 0xa2, 0xa2, 0xa2, 0xa2,
+		 0xa2, 0xa3, 0xa3, 0xa3, 0xa3, 0xa3, 0xa4, 0xa4,
+		 0xa4, 0xa4, 0xa4, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
+		 0xa6, 0xa6, 0xa6, 0xa6, 0xa6, 0xa8, 0xa8, 0xa8,
+		 0xa8, 0xa8, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xab,
+		 0xab, 0xab, 0xab, 0xab, 0xac, 0xac, 0xac, 0xac,
+		 0xac, 0xac, 0xad, 0xad, 0xad, 0xad, 0xad, 0xae,
+		 0xae, 0xae, 0xae, 0xae, 0xaf, 0xaf, 0xaf, 0xaf,
+		 0xaf, 0xaf, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb1,
+		 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb2, 0xb2, 0xb2,
+		 0xb2, 0xb2, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb3,
+		 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb6, 0xb6,
+		 0xb6, 0xb6, 0xb6, 0xb7, 0xb7, 0xb7, 0xb7, 0xb7,
+		 0xb7, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8, 0xb9,
+		 0xb9, 0xb9, 0xb9, 0xb9, 0xba, 0xba, 0xba, 0xba,
+		 0xba, 0xba, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc,
+		 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbe, 0xbe,
+		 0xbe, 0xbe, 0xbe, 0xbe, 0xbf, 0xbf, 0xbf, 0xbf,
+		 0xbf, 0xbf, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
+		 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc3, 0xc3,
+		 0xc3, 0xc3, 0xc3, 0xc3, 0xc4, 0xc4, 0xc4, 0xc4,
+		 0xc4, 0xc4, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5,
+		 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc7, 0xc7,
+		 0xc7, 0xc7, 0xc7, 0xc7, 0xc7, 0xc9, 0xc9, 0xc9,
+		 0xc9, 0xc9, 0xc9, 0xca, 0xca, 0xca, 0xca, 0xca,
+		 0xca, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcc,
+		 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcd, 0xcd,
+		 0xcd, 0xcd, 0xcd, 0xcd, 0xce, 0xce, 0xce, 0xce,
+		 0xce, 0xce, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf,
+		 0xcf, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd1,
+		 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd3, 0xd3,
+		 0xd3, 0xd3, 0xd3, 0xd3, 0xd4, 0xd4, 0xd4, 0xd4,
+		 0xd4, 0xd4, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6,
+		 0xd6, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd8,
+		 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd9, 0xd9,
+		 0xd9, 0xd9, 0xd9, 0xd9, 0xda, 0xda, 0xda, 0xda,
+		 0xda, 0xda, 0xda, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb,
+		 0xdb, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd,
+		 0xde, 0xde, 0xde, 0xde, 0xde, 0xde, 0xdf, 0xdf,
+		 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xe0, 0xe0, 0xe0,
+		 0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1,
+		 0xe1, 0xe1, 0xe2, 0xe2, 0xe2, 0xe2, 0xe2, 0xe2,
+		 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe4,
+		 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe5, 0xe5, 0xe5,
+		 0xe5, 0xe5, 0xe5, 0xe5, 0xe6, 0xe6, 0xe6, 0xe6,
+		 0xe6, 0xe6, 0xe6, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7,
+		 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe9,
+		 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xeb, 0xeb, 0xeb,
+		 0xeb, 0xeb, 0xeb, 0xeb, 0xec, 0xec, 0xec, 0xec,
+		 0xec, 0xec, 0xed, 0xed, 0xed, 0xed, 0xed, 0xed,
+		 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xee, 0xef,
+		 0xef, 0xef, 0xef, 0xef, 0xef, 0xf0, 0xf0, 0xf0,
+		 0xf0, 0xf0, 0xf0, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1,
+		 0xf1, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf4,
+		 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf5, 0xf5,
+		 0xf5, 0xf5, 0xf5, 0xf5, 0xf6, 0xf6, 0xf6, 0xf6,
+		 0xf6, 0xf6, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7,
+		 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf9, 0xf9,
+		 0xf9, 0xf9, 0xf9, 0xf9, 0xfa, 0xfa, 0xfa, 0xfa,
+		 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb},
+		{0x00, 0x00, 0x00, 0x01, 0x02, 0x05, 0x07, 0x08,
+		 0x09, 0x0a, 0x0c, 0x0e, 0x10, 0x11, 0x12, 0x14,
+		 0x15, 0x16, 0x17, 0x18, 0x1a, 0x1b, 0x1c, 0x1e,
+		 0x1f, 0x20, 0x20, 0x22, 0x23, 0x25, 0x26, 0x27,
+		 0x28, 0x28, 0x29, 0x2b, 0x2c, 0x2d, 0x2d, 0x2f,
+		 0x30, 0x31, 0x31, 0x33, 0x34, 0x35, 0x35, 0x37,
+		 0x38, 0x38, 0x39, 0x3a, 0x3a, 0x3b, 0x3c, 0x3c,
+		 0x3d, 0x3f, 0x3f, 0x40, 0x40, 0x42, 0x43, 0x43,
+		 0x44, 0x44, 0x45, 0x47, 0x47, 0x48, 0x48, 0x49,
+		 0x4a, 0x4a, 0x4b, 0x4b, 0x4c, 0x4c, 0x4d, 0x4d,
+		 0x4f, 0x4f, 0x50, 0x50, 0x52, 0x52, 0x53, 0x53,
+		 0x54, 0x54, 0x55, 0x55, 0x56, 0x56, 0x58, 0x58,
+		 0x59, 0x59, 0x5a, 0x5a, 0x5b, 0x5b, 0x5c, 0x5c,
+		 0x5c, 0x5e, 0x5e, 0x5f, 0x5f, 0x60, 0x60, 0x61,
+		 0x61, 0x61, 0x62, 0x62, 0x63, 0x63, 0x65, 0x65,
+		 0x65, 0x66, 0x66, 0x67, 0x67, 0x67, 0x68, 0x68,
+		 0x69, 0x69, 0x69, 0x6a, 0x6a, 0x6a, 0x6c, 0x6c,
+		 0x6d, 0x6d, 0x6d, 0x6e, 0x6e, 0x6e, 0x6f, 0x6f,
+		 0x70, 0x70, 0x70, 0x71, 0x71, 0x71, 0x73, 0x73,
+		 0x73, 0x74, 0x74, 0x74, 0x75, 0x75, 0x75, 0x77,
+		 0x77, 0x78, 0x78, 0x78, 0x79, 0x79, 0x79, 0x7a,
+		 0x7a, 0x7a, 0x7a, 0x7b, 0x7b, 0x7b, 0x7c, 0x7c,
+		 0x7c, 0x7d, 0x7d, 0x7d, 0x7f, 0x7f, 0x7f, 0x80,
+		 0x80, 0x80, 0x81, 0x81, 0x81, 0x81, 0x82, 0x82,
+		 0x82, 0x84, 0x84, 0x84, 0x85, 0x85, 0x85, 0x85,
+		 0x86, 0x86, 0x86, 0x88, 0x88, 0x88, 0x88, 0x89,
+		 0x89, 0x89, 0x8a, 0x8a, 0x8a, 0x8a, 0x8b, 0x8b,
+		 0x8b, 0x8b, 0x8d, 0x8d, 0x8d, 0x8e, 0x8e, 0x8e,
+		 0x8e, 0x8f, 0x8f, 0x8f, 0x8f, 0x90, 0x90, 0x90,
+		 0x91, 0x91, 0x91, 0x91, 0x92, 0x92, 0x92, 0x92,
+		 0x93, 0x93, 0x93, 0x93, 0x94, 0x94, 0x94, 0x94,
+		 0x96, 0x96, 0x96, 0x96, 0x97, 0x97, 0x97, 0x97,
+		 0x98, 0x98, 0x98, 0x98, 0x99, 0x99, 0x99, 0x99,
+		 0x9a, 0x9a, 0x9a, 0x9a, 0x9b, 0x9b, 0x9b, 0x9b,
+		 0x9b, 0x9c, 0x9c, 0x9c, 0x9c, 0x9d, 0x9d, 0x9d,
+		 0x9d, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0xa0, 0xa0,
+		 0xa0, 0xa0, 0xa1, 0xa1, 0xa1, 0xa1, 0xa2, 0xa2,
+		 0xa2, 0xa2, 0xa2, 0xa3, 0xa3, 0xa3, 0xa3, 0xa4,
+		 0xa4, 0xa4, 0xa4, 0xa4, 0xa5, 0xa5, 0xa5, 0xa5,
+		 0xa5, 0xa6, 0xa6, 0xa6, 0xa6, 0xa8, 0xa8, 0xa8,
+		 0xa8, 0xa8, 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xab,
+		 0xab, 0xab, 0xab, 0xab, 0xac, 0xac, 0xac, 0xac,
+		 0xad, 0xad, 0xad, 0xad, 0xad, 0xae, 0xae, 0xae,
+		 0xae, 0xae, 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xb0,
+		 0xb0, 0xb0, 0xb0, 0xb0, 0xb1, 0xb1, 0xb1, 0xb1,
+		 0xb1, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2, 0xb3, 0xb3,
+		 0xb3, 0xb3, 0xb3, 0xb4, 0xb3, 0xb4, 0xb4, 0xb4,
+		 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb7, 0xb7,
+		 0xb7, 0xb7, 0xb7, 0xb8, 0xb8, 0xb8, 0xb8, 0xb8,
+		 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xba, 0xba,
+		 0xba, 0xba, 0xba, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc,
+		 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbd, 0xbe, 0xbe,
+		 0xbe, 0xbe, 0xbe, 0xbf, 0xbf, 0xbf, 0xbf, 0xbf,
+		 0xbf, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc2, 0xc2,
+		 0xc2, 0xc2, 0xc2, 0xc2, 0xc3, 0xc3, 0xc3, 0xc3,
+		 0xc3, 0xc3, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc5,
+		 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc6, 0xc6, 0xc6,
+		 0xc6, 0xc6, 0xc6, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7,
+		 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xca, 0xca,
+		 0xca, 0xca, 0xca, 0xca, 0xcb, 0xcb, 0xcb, 0xcb,
+		 0xcb, 0xcb, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc,
+		 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xce, 0xce,
+		 0xce, 0xce, 0xce, 0xce, 0xcf, 0xcf, 0xcf, 0xcf,
+		 0xcf, 0xcf, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd0,
+		 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd3, 0xd3,
+		 0xd3, 0xd3, 0xd3, 0xd3, 0xd4, 0xd4, 0xd4, 0xd4,
+		 0xd4, 0xd4, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6,
+		 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd8, 0xd8,
+		 0xd8, 0xd8, 0xd8, 0xd8, 0xd9, 0xd9, 0xd9, 0xd9,
+		 0xd9, 0xd9, 0xda, 0xda, 0xda, 0xda, 0xda, 0xda,
+		 0xda, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdd,
+		 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xde, 0xde, 0xde,
+		 0xde, 0xde, 0xde, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf,
+		 0xdf, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0,
+		 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe2, 0xe2,
+		 0xe2, 0xe2, 0xe2, 0xe2, 0xe3, 0xe3, 0xe3, 0xe3,
+		 0xe3, 0xe3, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4,
+		 0xe4, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe6,
+		 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe7, 0xe7, 0xe7,
+		 0xe7, 0xe7, 0xe7, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8,
+		 0xe8, 0xe8, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9,
+		 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xec, 0xec,
+		 0xec, 0xec, 0xec, 0xec, 0xec, 0xed, 0xed, 0xed,
+		 0xed, 0xed, 0xed, 0xee, 0xee, 0xee, 0xee, 0xee,
+		 0xee, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xf0,
+		 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf1, 0xf1,
+		 0xf1, 0xf1, 0xf1, 0xf1, 0xf3, 0xf3, 0xf3, 0xf3,
+		 0xf3, 0xf3, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4,
+		 0xf5, 0xf5, 0xf5, 0xf5, 0xf5, 0xf5, 0xf6, 0xf6,
+		 0xf6, 0xf6, 0xf6, 0xf6, 0xf7, 0xf7, 0xf7, 0xf7,
+		 0xf7, 0xf7, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8,
+		 0xf8, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xfa,
+		 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa,
+		 0xfa, 0xfa, 0xfa, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb}
+	    },
+	    {				/* gamma 3 - from tp6810 + cx0342 */
+		{0x08, 0x09, 0x0c, 0x0d, 0x10, 0x11, 0x14, 0x15,
+		 0x17, 0x18, 0x1a, 0x1c, 0x1e, 0x1f, 0x20, 0x23,
+		 0x25, 0x26, 0x27, 0x28, 0x2b, 0x2c, 0x2d, 0x2f,
+		 0x30, 0x31, 0x33, 0x34, 0x35, 0x37, 0x38, 0x39,
+		 0x3a, 0x3b, 0x3c, 0x3d, 0x3f, 0x40, 0x42, 0x43,
+		 0x44, 0x45, 0x47, 0x48, 0x48, 0x49, 0x4a, 0x4b,
+		 0x4c, 0x4d, 0x4d, 0x4f, 0x50, 0x52, 0x53, 0x53,
+		 0x54, 0x55, 0x56, 0x56, 0x58, 0x59, 0x5a, 0x5a,
+		 0x5b, 0x5c, 0x5c, 0x5e, 0x5f, 0x5f, 0x60, 0x61,
+		 0x61, 0x62, 0x63, 0x63, 0x65, 0x66, 0x66, 0x67,
+		 0x68, 0x68, 0x69, 0x69, 0x6a, 0x6c, 0x6c, 0x6d,
+		 0x6d, 0x6e, 0x6f, 0x6f, 0x70, 0x70, 0x71, 0x73,
+		 0x73, 0x74, 0x74, 0x75, 0x75, 0x77, 0x77, 0x78,
+		 0x78, 0x79, 0x7a, 0x7a, 0x7b, 0x7b, 0x7c, 0x7c,
+		 0x7d, 0x7d, 0x7f, 0x7f, 0x80, 0x80, 0x81, 0x81,
+		 0x82, 0x82, 0x84, 0x84, 0x85, 0x85, 0x86, 0x86,
+		 0x86, 0x88, 0x88, 0x89, 0x89, 0x8a, 0x8a, 0x8b,
+		 0x8b, 0x8d, 0x8d, 0x8d, 0x8e, 0x8e, 0x8f, 0x8f,
+		 0x90, 0x90, 0x91, 0x91, 0x91, 0x92, 0x92, 0x93,
+		 0x93, 0x93, 0x94, 0x94, 0x96, 0x96, 0x97, 0x97,
+		 0x97, 0x98, 0x98, 0x99, 0x99, 0x99, 0x9a, 0x9a,
+		 0x9a, 0x9b, 0x9b, 0x9c, 0x9c, 0x9c, 0x9d, 0x9d,
+		 0x9e, 0x9e, 0x9e, 0xa0, 0xa0, 0xa0, 0xa1, 0xa1,
+		 0xa2, 0xa2, 0xa2, 0xa3, 0xa3, 0xa3, 0xa4, 0xa4,
+		 0xa4, 0xa5, 0xa5, 0xa5, 0xa6, 0xa6, 0xa8, 0xa8,
+		 0xa8, 0xa9, 0xa9, 0xa9, 0xab, 0xab, 0xab, 0xac,
+		 0xac, 0xac, 0xad, 0xad, 0xad, 0xae, 0xae, 0xae,
+		 0xaf, 0xaf, 0xaf, 0xb0, 0xb0, 0xb0, 0xb1, 0xb1,
+		 0xb1, 0xb2, 0xb2, 0xb2, 0xb2, 0xb3, 0xb3, 0xb3,
+		 0xb4, 0xb4, 0xb4, 0xb6, 0xb6, 0xb6, 0xb7, 0xb7,
+		 0xb7, 0xb8, 0xb8, 0xb8, 0xb8, 0xb9, 0xb9, 0xb9,
+		 0xba, 0xba, 0xba, 0xbc, 0xbc, 0xbc, 0xbc, 0xbd,
+		 0xbd, 0xbd, 0xbe, 0xbe, 0xbe, 0xbe, 0xbf, 0xbf,
+		 0xbf, 0xc0, 0xc0, 0xc0, 0xc0, 0xc2, 0xc2, 0xc2,
+		 0xc3, 0xc3, 0xc3, 0xc3, 0xc4, 0xc4, 0xc4, 0xc5,
+		 0xc5, 0xc5, 0xc5, 0xc6, 0xc6, 0xc6, 0xc6, 0xc7,
+		 0xc7, 0xc7, 0xc9, 0xc9, 0xc9, 0xc9, 0xca, 0xca,
+		 0xca, 0xca, 0xcb, 0xcb, 0xcb, 0xcb, 0xcc, 0xcc,
+		 0xcc, 0xcd, 0xcd, 0xcd, 0xcd, 0xce, 0xce, 0xce,
+		 0xce, 0xcf, 0xcf, 0xcf, 0xcf, 0xd0, 0xd0, 0xd0,
+		 0xd0, 0xd1, 0xd1, 0xd1, 0xd1, 0xd3, 0xd3, 0xd3,
+		 0xd3, 0xd4, 0xd4, 0xd4, 0xd4, 0xd6, 0xd6, 0xd6,
+		 0xd6, 0xd7, 0xd7, 0xd7, 0xd7, 0xd8, 0xd8, 0xd8,
+		 0xd8, 0xd9, 0xd9, 0xd9, 0xd9, 0xda, 0xda, 0xda,
+		 0xda, 0xda, 0xdb, 0xdb, 0xdb, 0xdb, 0xdd, 0xdd,
+		 0xdd, 0xdd, 0xde, 0xde, 0xde, 0xde, 0xdf, 0xdf,
+		 0xdf, 0xdf, 0xdf, 0xe0, 0xe0, 0xe0, 0xe0, 0xe1,
+		 0xe1, 0xe1, 0xe1, 0xe2, 0xe2, 0xe2, 0xe2, 0xe2,
+		 0xe3, 0xe3, 0xe3, 0xe3, 0xe4, 0xe4, 0xe4, 0xe4,
+		 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe6, 0xe6, 0xe6,
+		 0xe6, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe8, 0xe8,
+		 0xe8, 0xe8, 0xe8, 0xe9, 0xe9, 0xe9, 0xe9, 0xeb,
+		 0xeb, 0xeb, 0xeb, 0xeb, 0xec, 0xec, 0xec, 0xec,
+		 0xed, 0xed, 0xed, 0xed, 0xed, 0xee, 0xee, 0xee,
+		 0xee, 0xee, 0xef, 0xef, 0xef, 0xef, 0xf0, 0xf0,
+		 0xf0, 0xf0, 0xf0, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1,
+		 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf4, 0xf4, 0xf4,
+		 0xf4, 0xf4, 0xf5, 0xf5, 0xf5, 0xf5, 0xf6, 0xf6,
+		 0xf6, 0xf6, 0xf6, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7,
+		 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf9, 0xf9, 0xf9,
+		 0xf9, 0xf9, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa,
+		 0xfa, 0xfa, 0xfa, 0xfa, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc,
+		 0xfc, 0xfc, 0xfc, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd,
+		 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+		{0x03, 0x05, 0x07, 0x09, 0x0a, 0x0c, 0x0d, 0x10,
+		 0x11, 0x12, 0x14, 0x15, 0x17, 0x18, 0x1a, 0x1b,
+		 0x1c, 0x1e, 0x1f, 0x20, 0x22, 0x23, 0x25, 0x26,
+		 0x27, 0x28, 0x29, 0x2b, 0x2c, 0x2c, 0x2d, 0x2f,
+		 0x30, 0x31, 0x33, 0x33, 0x34, 0x35, 0x37, 0x38,
+		 0x38, 0x39, 0x3a, 0x3b, 0x3b, 0x3c, 0x3d, 0x3f,
+		 0x3f, 0x40, 0x42, 0x42, 0x43, 0x44, 0x45, 0x45,
+		 0x47, 0x47, 0x48, 0x49, 0x49, 0x4a, 0x4b, 0x4b,
+		 0x4c, 0x4d, 0x4d, 0x4f, 0x4f, 0x50, 0x52, 0x52,
+		 0x53, 0x53, 0x54, 0x54, 0x55, 0x55, 0x56, 0x58,
+		 0x58, 0x59, 0x59, 0x5a, 0x5a, 0x5b, 0x5b, 0x5c,
+		 0x5c, 0x5e, 0x5e, 0x5f, 0x5f, 0x60, 0x60, 0x61,
+		 0x61, 0x62, 0x62, 0x63, 0x63, 0x65, 0x65, 0x66,
+		 0x66, 0x67, 0x67, 0x67, 0x68, 0x68, 0x69, 0x69,
+		 0x6a, 0x6a, 0x6c, 0x6c, 0x6c, 0x6d, 0x6d, 0x6e,
+		 0x6e, 0x6f, 0x6f, 0x6f, 0x70, 0x70, 0x71, 0x71,
+		 0x71, 0x73, 0x73, 0x74, 0x74, 0x74, 0x75, 0x75,
+		 0x77, 0x77, 0x77, 0x78, 0x78, 0x79, 0x79, 0x79,
+		 0x7a, 0x7a, 0x7a, 0x7b, 0x7b, 0x7b, 0x7c, 0x7c,
+		 0x7d, 0x7d, 0x7d, 0x7f, 0x7f, 0x7f, 0x80, 0x80,
+		 0x80, 0x81, 0x81, 0x81, 0x82, 0x82, 0x82, 0x84,
+		 0x84, 0x84, 0x85, 0x85, 0x85, 0x86, 0x86, 0x86,
+		 0x88, 0x88, 0x88, 0x89, 0x89, 0x89, 0x8a, 0x8a,
+		 0x8a, 0x8b, 0x8b, 0x8b, 0x8d, 0x8d, 0x8d, 0x8e,
+		 0x8e, 0x8e, 0x8e, 0x8f, 0x8f, 0x8f, 0x90, 0x90,
+		 0x90, 0x91, 0x91, 0x91, 0x91, 0x92, 0x92, 0x92,
+		 0x93, 0x93, 0x93, 0x93, 0x94, 0x94, 0x94, 0x96,
+		 0x96, 0x96, 0x96, 0x97, 0x97, 0x97, 0x98, 0x98,
+		 0x98, 0x98, 0x99, 0x99, 0x99, 0x9a, 0x9a, 0x9a,
+		 0x9a, 0x9b, 0x9b, 0x9b, 0x9b, 0x9c, 0x9c, 0x9c,
+		 0x9c, 0x9d, 0x9d, 0x9d, 0x9e, 0x9e, 0x9e, 0x9e,
+		 0xa0, 0xa0, 0xa0, 0xa0, 0xa1, 0xa1, 0xa1, 0xa1,
+		 0xa2, 0xa2, 0xa2, 0xa2, 0xa3, 0xa3, 0xa3, 0xa3,
+		 0xa4, 0xa4, 0xa4, 0xa4, 0xa5, 0xa5, 0xa5, 0xa5,
+		 0xa6, 0xa6, 0xa6, 0xa6, 0xa8, 0xa8, 0xa8, 0xa8,
+		 0xa9, 0xa9, 0xa9, 0xa9, 0xa9, 0xab, 0xab, 0xab,
+		 0xab, 0xac, 0xac, 0xac, 0xac, 0xad, 0xad, 0xad,
+		 0xad, 0xad, 0xae, 0xae, 0xae, 0xae, 0xaf, 0xaf,
+		 0xaf, 0xaf, 0xb0, 0xb0, 0xb0, 0xb0, 0xb0, 0xb1,
+		 0xb1, 0xb1, 0xb1, 0xb2, 0xb2, 0xb2, 0xb2, 0xb2,
+		 0xb3, 0xb3, 0xb3, 0xb3, 0xb3, 0xb4, 0xb4, 0xb4,
+		 0xb4, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb7, 0xb7,
+		 0xb7, 0xb7, 0xb7, 0xb8, 0xb8, 0xb8, 0xb8, 0xb9,
+		 0xb9, 0xb9, 0xb9, 0xb9, 0xba, 0xba, 0xba, 0xba,
+		 0xba, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbd, 0xbd,
+		 0xbd, 0xbd, 0xbd, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe,
+		 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xc0, 0xc0, 0xc0,
+		 0xc0, 0xc0, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc3,
+		 0xc3, 0xc3, 0xc3, 0xc3, 0xc4, 0xc4, 0xc4, 0xc4,
+		 0xc4, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc6, 0xc6,
+		 0xc6, 0xc6, 0xc6, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7,
+		 0xc7, 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xca, 0xca,
+		 0xca, 0xca, 0xca, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb,
+		 0xcb, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcd, 0xcd,
+		 0xcd, 0xcd, 0xcd, 0xcd, 0xce, 0xce, 0xce, 0xce,
+		 0xce, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xd0,
+		 0xd0, 0xd0, 0xd0, 0xd0, 0xd1, 0xd1, 0xd1, 0xd1,
+		 0xd1, 0xd1, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3, 0xd4,
+		 0xd4, 0xd4, 0xd4, 0xd4, 0xd4, 0xd6, 0xd6, 0xd6,
+		 0xd6, 0xd6, 0xd6, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7,
+		 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8, 0xd9, 0xd9,
+		 0xd9, 0xd9, 0xd9, 0xd9, 0xda, 0xda, 0xda, 0xda,
+		 0xda, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdd,
+		 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xde, 0xde, 0xde,
+		 0xde, 0xde, 0xde, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf,
+		 0xdf, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe1,
+		 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe2, 0xe2, 0xe2,
+		 0xe2, 0xe2, 0xe2, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3,
+		 0xe3, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe5,
+		 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe6, 0xe6, 0xe6,
+		 0xe6, 0xe6, 0xe6, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7,
+		 0xe7, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe9,
+		 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xeb, 0xeb, 0xeb,
+		 0xeb, 0xeb, 0xeb, 0xec, 0xec, 0xec, 0xec, 0xec,
+		 0xec, 0xed, 0xed, 0xed, 0xed, 0xed, 0xed, 0xee,
+		 0xee, 0xee, 0xee, 0xee, 0xee, 0xef, 0xef, 0xef,
+		 0xef, 0xef, 0xef, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+		 0xf0, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf3,
+		 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf4, 0xf4, 0xf4,
+		 0xf4, 0xf4, 0xf4, 0xf5, 0xf5, 0xf5, 0xf5, 0xf5,
+		 0xf5, 0xf5, 0xf6, 0xf6, 0xf6, 0xf6, 0xf6, 0xf6,
+		 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf8, 0xf8,
+		 0xf8, 0xf8, 0xf8, 0xf8, 0xf9, 0xf9, 0xf9, 0xf9,
+		 0xf9, 0xf9, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa,
+		 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfc, 0xfc, 0xfc, 0xfc,
+		 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc,
+		 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe,
+		 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+		{0x07, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14,
+		 0x16, 0x17, 0x18, 0x1b, 0x1c, 0x1e, 0x1f, 0x20,
+		 0x23, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2b, 0x2d,
+		 0x2f, 0x30, 0x31, 0x33, 0x34, 0x35, 0x37, 0x38,
+		 0x39, 0x3a, 0x3b, 0x3b, 0x3c, 0x3d, 0x3f, 0x40,
+		 0x42, 0x43, 0x44, 0x44, 0x45, 0x47, 0x48, 0x49,
+		 0x4a, 0x4a, 0x4b, 0x4c, 0x4d, 0x4d, 0x4f, 0x50,
+		 0x52, 0x52, 0x53, 0x54, 0x55, 0x55, 0x56, 0x58,
+		 0x58, 0x59, 0x5a, 0x5b, 0x5b, 0x5c, 0x5e, 0x5e,
+		 0x5f, 0x5f, 0x60, 0x61, 0x61, 0x62, 0x63, 0x63,
+		 0x65, 0x65, 0x66, 0x67, 0x67, 0x68, 0x68, 0x69,
+		 0x6a, 0x6a, 0x6c, 0x6c, 0x6d, 0x6d, 0x6e, 0x6e,
+		 0x6f, 0x70, 0x70, 0x71, 0x71, 0x73, 0x73, 0x74,
+		 0x74, 0x75, 0x75, 0x77, 0x77, 0x78, 0x78, 0x79,
+		 0x79, 0x7a, 0x7a, 0x7b, 0x7b, 0x7c, 0x7c, 0x7d,
+		 0x7d, 0x7f, 0x7f, 0x80, 0x80, 0x81, 0x81, 0x81,
+		 0x82, 0x82, 0x84, 0x84, 0x85, 0x85, 0x86, 0x86,
+		 0x88, 0x88, 0x88, 0x89, 0x89, 0x8a, 0x8a, 0x8b,
+		 0x8b, 0x8b, 0x8d, 0x8d, 0x8e, 0x8e, 0x8e, 0x8f,
+		 0x8f, 0x90, 0x90, 0x90, 0x91, 0x91, 0x92, 0x92,
+		 0x92, 0x93, 0x93, 0x94, 0x94, 0x94, 0x96, 0x96,
+		 0x96, 0x97, 0x97, 0x98, 0x98, 0x98, 0x99, 0x99,
+		 0x99, 0x9a, 0x9a, 0x9b, 0x9b, 0x9b, 0x9c, 0x9c,
+		 0x9c, 0x9d, 0x9d, 0x9d, 0x9e, 0x9e, 0x9e, 0xa0,
+		 0xa0, 0xa1, 0xa1, 0xa1, 0xa2, 0xa2, 0xa2, 0xa3,
+		 0xa3, 0xa3, 0xa4, 0xa4, 0xa4, 0xa5, 0xa5, 0xa5,
+		 0xa6, 0xa6, 0xa6, 0xa8, 0xa8, 0xa8, 0xa8, 0xa9,
+		 0xa9, 0xa9, 0xab, 0xab, 0xab, 0xac, 0xac, 0xac,
+		 0xad, 0xad, 0xad, 0xae, 0xae, 0xae, 0xaf, 0xaf,
+		 0xaf, 0xaf, 0xb0, 0xb0, 0xb0, 0xb1, 0xb1, 0xb1,
+		 0xb2, 0xb2, 0xb2, 0xb2, 0xb3, 0xb3, 0xb3, 0xb4,
+		 0xb4, 0xb4, 0xb4, 0xb6, 0xb6, 0xb6, 0xb7, 0xb7,
+		 0xb7, 0xb7, 0xb8, 0xb8, 0xb8, 0xb9, 0xb9, 0xb9,
+		 0xb9, 0xba, 0xba, 0xba, 0xbc, 0xbc, 0xbc, 0xbc,
+		 0xbd, 0xbd, 0xbd, 0xbd, 0xbe, 0xbe, 0xbe, 0xbf,
+		 0xbf, 0xbf, 0xbf, 0xc0, 0xc0, 0xc0, 0xc0, 0xc2,
+		 0xc2, 0xc2, 0xc2, 0xc3, 0xc3, 0xc3, 0xc3, 0xc4,
+		 0xc4, 0xc4, 0xc5, 0xc5, 0xc5, 0xc5, 0xc6, 0xc6,
+		 0xc6, 0xc6, 0xc7, 0xc7, 0xc7, 0xc7, 0xc9, 0xc9,
+		 0xc9, 0xc9, 0xca, 0xca, 0xca, 0xca, 0xcb, 0xcb,
+		 0xcb, 0xcb, 0xcc, 0xcc, 0xcc, 0xcc, 0xcd, 0xcd,
+		 0xcd, 0xcd, 0xcd, 0xce, 0xce, 0xce, 0xce, 0xcf,
+		 0xcf, 0xcf, 0xcf, 0xd0, 0xd0, 0xd0, 0xd0, 0xd1,
+		 0xd1, 0xd1, 0xd1, 0xd3, 0xd3, 0xd3, 0xd3, 0xd3,
+		 0xd4, 0xd4, 0xd4, 0xd4, 0xd6, 0xd6, 0xd6, 0xd6,
+		 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd8, 0xd8, 0xd8,
+		 0xd8, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xda, 0xda,
+		 0xda, 0xda, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb, 0xdd,
+		 0xdd, 0xdd, 0xdd, 0xde, 0xde, 0xde, 0xde, 0xde,
+		 0xdf, 0xdf, 0xdf, 0xdf, 0xe0, 0xe0, 0xe0, 0xe0,
+		 0xe0, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe2, 0xe2,
+		 0xe2, 0xe2, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe4,
+		 0xe4, 0xe4, 0xe4, 0xe4, 0xe5, 0xe5, 0xe5, 0xe5,
+		 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe7, 0xe7, 0xe7,
+		 0xe7, 0xe7, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe9,
+		 0xe9, 0xe9, 0xe9, 0xe9, 0xeb, 0xeb, 0xeb, 0xeb,
+		 0xeb, 0xec, 0xec, 0xec, 0xec, 0xec, 0xed, 0xed,
+		 0xed, 0xed, 0xed, 0xee, 0xee, 0xee, 0xee, 0xee,
+		 0xef, 0xef, 0xef, 0xef, 0xef, 0xf0, 0xf0, 0xf0,
+		 0xf0, 0xf0, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf3,
+		 0xf3, 0xf3, 0xf3, 0xf3, 0xf4, 0xf4, 0xf4, 0xf4,
+		 0xf4, 0xf5, 0xf5, 0xf5, 0xf5, 0xf5, 0xf6, 0xf6,
+		 0xf6, 0xf6, 0xf6, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7,
+		 0xf8, 0xf8, 0xf8, 0xf8, 0xf8, 0xf9, 0xf9, 0xf9,
+		 0xf9, 0xf9, 0xf9, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa,
+		 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc,
+		 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfd, 0xfd, 0xfd,
+		 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
+	    },
+	    {				/* gamma 4 - from tp6800 + soi763a */
+		{0x11, 0x14, 0x15, 0x17, 0x1a, 0x1b, 0x1e, 0x1f,
+		 0x22, 0x23, 0x25, 0x27, 0x28, 0x2b, 0x2c, 0x2d,
+		 0x2f, 0x31, 0x33, 0x34, 0x35, 0x38, 0x39, 0x3a,
+		 0x3b, 0x3c, 0x3d, 0x40, 0x42, 0x43, 0x44, 0x45,
+		 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4f,
+		 0x50, 0x52, 0x53, 0x53, 0x54, 0x55, 0x56, 0x58,
+		 0x59, 0x5a, 0x5b, 0x5b, 0x5c, 0x5e, 0x5f, 0x60,
+		 0x61, 0x61, 0x62, 0x63, 0x65, 0x65, 0x66, 0x67,
+		 0x68, 0x68, 0x69, 0x6a, 0x6c, 0x6c, 0x6d, 0x6e,
+		 0x6f, 0x6f, 0x70, 0x71, 0x71, 0x73, 0x74, 0x74,
+		 0x75, 0x77, 0x77, 0x78, 0x79, 0x79, 0x7a, 0x7a,
+		 0x7b, 0x7c, 0x7c, 0x7d, 0x7f, 0x7f, 0x80, 0x80,
+		 0x81, 0x81, 0x82, 0x84, 0x84, 0x85, 0x85, 0x86,
+		 0x86, 0x88, 0x89, 0x89, 0x8a, 0x8a, 0x8b, 0x8b,
+		 0x8d, 0x8d, 0x8e, 0x8e, 0x8f, 0x90, 0x90, 0x91,
+		 0x91, 0x92, 0x92, 0x93, 0x93, 0x94, 0x94, 0x96,
+		 0x96, 0x97, 0x97, 0x98, 0x98, 0x98, 0x99, 0x99,
+		 0x9a, 0x9a, 0x9b, 0x9b, 0x9c, 0x9c, 0x9d, 0x9d,
+		 0x9e, 0x9e, 0xa0, 0xa0, 0xa0, 0xa1, 0xa1, 0xa2,
+		 0xa2, 0xa3, 0xa3, 0xa3, 0xa4, 0xa4, 0xa5, 0xa5,
+		 0xa6, 0xa6, 0xa6, 0xa8, 0xa8, 0xa9, 0xa9, 0xab,
+		 0xab, 0xab, 0xac, 0xac, 0xad, 0xad, 0xad, 0xae,
+		 0xae, 0xaf, 0xaf, 0xaf, 0xb0, 0xb0, 0xb1, 0xb1,
+		 0xb1, 0xb2, 0xb2, 0xb2, 0xb3, 0xb3, 0xb4, 0xb4,
+		 0xb4, 0xb6, 0xb6, 0xb6, 0xb7, 0xb7, 0xb8, 0xb8,
+		 0xb8, 0xb9, 0xb9, 0xb9, 0xba, 0xba, 0xba, 0xbc,
+		 0xbc, 0xbd, 0xbd, 0xbd, 0xbe, 0xbe, 0xbe, 0xbf,
+		 0xbf, 0xbf, 0xc0, 0xc0, 0xc0, 0xc2, 0xc2, 0xc2,
+		 0xc3, 0xc3, 0xc3, 0xc4, 0xc4, 0xc4, 0xc5, 0xc5,
+		 0xc5, 0xc6, 0xc6, 0xc6, 0xc7, 0xc7, 0xc7, 0xc9,
+		 0xc9, 0xc9, 0xca, 0xca, 0xca, 0xcb, 0xcb, 0xcb,
+		 0xcb, 0xcc, 0xcc, 0xcc, 0xcd, 0xcd, 0xcd, 0xce,
+		 0xce, 0xce, 0xcf, 0xcf, 0xcf, 0xcf, 0xd0, 0xd0,
+		 0xd0, 0xd1, 0xd1, 0xd1, 0xd3, 0xd3, 0xd3, 0xd3,
+		 0xd4, 0xd4, 0xd4, 0xd6, 0xd6, 0xd6, 0xd7, 0xd7,
+		 0xd7, 0xd7, 0xd8, 0xd8, 0xd8, 0xd9, 0xd9, 0xd9,
+		 0xd9, 0xda, 0xda, 0xda, 0xdb, 0xdb, 0xdb, 0xdb,
+		 0xdd, 0xdd, 0xdd, 0xde, 0xde, 0xde, 0xde, 0xdf,
+		 0xdf, 0xdf, 0xdf, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1,
+		 0xe1, 0xe1, 0xe2, 0xe2, 0xe2, 0xe2, 0xe3, 0xe3,
+		 0xe3, 0xe3, 0xe4, 0xe4, 0xe4, 0xe5, 0xe5, 0xe5,
+		 0xe5, 0xe6, 0xe6, 0xe6, 0xe6, 0xe7, 0xe7, 0xe7,
+		 0xe7, 0xe8, 0xe8, 0xe8, 0xe8, 0xe9, 0xe9, 0xe9,
+		 0xe9, 0xeb, 0xeb, 0xeb, 0xeb, 0xec, 0xec, 0xec,
+		 0xec, 0xed, 0xed, 0xed, 0xed, 0xee, 0xee, 0xee,
+		 0xee, 0xef, 0xef, 0xef, 0xef, 0xf0, 0xf0, 0xf0,
+		 0xf0, 0xf1, 0xf1, 0xf1, 0xf1, 0xf3, 0xf3, 0xf3,
+		 0xf3, 0xf4, 0xf4, 0xf4, 0xf4, 0xf5, 0xf5, 0xf5,
+		 0xf5, 0xf6, 0xf6, 0xf6, 0xf6, 0xf6, 0xf7, 0xf7,
+		 0xf7, 0xf7, 0xf8, 0xf8, 0xf8, 0xf8, 0xf9, 0xf9,
+		 0xf9, 0xf9, 0xfa, 0xf9, 0xfa, 0xfa, 0xfa, 0xfa,
+		 0xfa, 0xfa, 0xfa, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb},
+		{0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x11, 0x14, 0x15,
+		 0x16, 0x17, 0x1a, 0x1b, 0x1c, 0x1e, 0x1f, 0x20,
+		 0x23, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2b, 0x2c,
+		 0x2d, 0x2f, 0x30, 0x31, 0x33, 0x34, 0x34, 0x35,
+		 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3c, 0x3d,
+		 0x3f, 0x40, 0x42, 0x42, 0x43, 0x44, 0x45, 0x45,
+		 0x47, 0x48, 0x49, 0x49, 0x4a, 0x4b, 0x4b, 0x4c,
+		 0x4d, 0x4f, 0x4f, 0x50, 0x52, 0x52, 0x53, 0x54,
+		 0x54, 0x55, 0x55, 0x56, 0x58, 0x58, 0x59, 0x5a,
+		 0x5a, 0x5b, 0x5b, 0x5c, 0x5e, 0x5e, 0x5f, 0x5f,
+		 0x60, 0x60, 0x61, 0x61, 0x62, 0x63, 0x63, 0x65,
+		 0x65, 0x66, 0x66, 0x67, 0x67, 0x68, 0x68, 0x69,
+		 0x69, 0x6a, 0x6a, 0x6c, 0x6c, 0x6d, 0x6d, 0x6e,
+		 0x6e, 0x6f, 0x6f, 0x70, 0x70, 0x71, 0x71, 0x73,
+		 0x73, 0x74, 0x74, 0x74, 0x75, 0x75, 0x77, 0x77,
+		 0x78, 0x78, 0x79, 0x79, 0x79, 0x7a, 0x7a, 0x7b,
+		 0x7b, 0x7c, 0x7c, 0x7c, 0x7d, 0x7d, 0x7f, 0x7f,
+		 0x7f, 0x80, 0x80, 0x81, 0x81, 0x81, 0x82, 0x82,
+		 0x84, 0x84, 0x84, 0x85, 0x85, 0x86, 0x86, 0x86,
+		 0x88, 0x88, 0x88, 0x89, 0x89, 0x8a, 0x8a, 0x8a,
+		 0x8b, 0x8b, 0x8b, 0x8d, 0x8d, 0x8d, 0x8e, 0x8e,
+		 0x8e, 0x8f, 0x8f, 0x90, 0x90, 0x90, 0x91, 0x91,
+		 0x91, 0x92, 0x92, 0x92, 0x93, 0x93, 0x93, 0x94,
+		 0x94, 0x94, 0x96, 0x96, 0x96, 0x97, 0x97, 0x97,
+		 0x98, 0x98, 0x98, 0x98, 0x99, 0x99, 0x99, 0x9a,
+		 0x9a, 0x9a, 0x9b, 0x9b, 0x9b, 0x9c, 0x9c, 0x9c,
+		 0x9c, 0x9d, 0x9d, 0x9d, 0x9e, 0x9e, 0x9e, 0xa0,
+		 0xa0, 0xa0, 0xa0, 0xa1, 0xa1, 0xa1, 0xa2, 0xa2,
+		 0xa2, 0xa3, 0xa3, 0xa3, 0xa3, 0xa4, 0xa4, 0xa4,
+		 0xa5, 0xa5, 0xa5, 0xa5, 0xa6, 0xa6, 0xa6, 0xa6,
+		 0xa8, 0xa8, 0xa8, 0xa9, 0xa9, 0xa9, 0xa9, 0xab,
+		 0xaa, 0xab, 0xab, 0xac, 0xac, 0xac, 0xad, 0xad,
+		 0xad, 0xad, 0xae, 0xae, 0xae, 0xae, 0xaf, 0xaf,
+		 0xaf, 0xaf, 0xb0, 0xb0, 0xb0, 0xb0, 0xb1, 0xb1,
+		 0xb1, 0xb1, 0xb2, 0xb2, 0xb2, 0xb2, 0xb3, 0xb3,
+		 0xb3, 0xb3, 0xb4, 0xb4, 0xb4, 0xb4, 0xb6, 0xb6,
+		 0xb6, 0xb6, 0xb7, 0xb7, 0xb7, 0xb7, 0xb8, 0xb8,
+		 0xb8, 0xb8, 0xb9, 0xb9, 0xb9, 0xb9, 0xba, 0xba,
+		 0xba, 0xba, 0xba, 0xbc, 0xbc, 0xbc, 0xbc, 0xbd,
+		 0xbd, 0xbd, 0xbd, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe,
+		 0xbf, 0xbf, 0xbf, 0xbf, 0xc0, 0xc0, 0xc0, 0xc0,
+		 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc3, 0xc3, 0xc3,
+		 0xc3, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4, 0xc5, 0xc5,
+		 0xc5, 0xc5, 0xc6, 0xc6, 0xc6, 0xc6, 0xc6, 0xc7,
+		 0xc7, 0xc7, 0xc7, 0xc7, 0xc9, 0xc9, 0xc9, 0xc9,
+		 0xca, 0xca, 0xca, 0xca, 0xca, 0xcb, 0xcb, 0xcb,
+		 0xcb, 0xcb, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcd,
+		 0xcd, 0xcd, 0xcd, 0xcd, 0xce, 0xce, 0xce, 0xce,
+		 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xd0, 0xd0, 0xd0,
+		 0xd0, 0xd0, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1, 0xd3,
+		 0xd3, 0xd3, 0xd3, 0xd3, 0xd4, 0xd4, 0xd4, 0xd4,
+		 0xd4, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd7, 0xd7,
+		 0xd7, 0xd7, 0xd7, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8,
+		 0xd8, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xda, 0xda,
+		 0xda, 0xda, 0xda, 0xdb, 0xdb, 0xdb, 0xdb, 0xdb,
+		 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xde, 0xde, 0xde,
+		 0xde, 0xde, 0xde, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf,
+		 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1,
+		 0xe1, 0xe1, 0xe1, 0xe2, 0xe2, 0xe2, 0xe2, 0xe2,
+		 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe4, 0xe4,
+		 0xe4, 0xe4, 0xe4, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5,
+		 0xe5, 0xe6, 0xe6, 0xe6, 0xe6, 0xe6, 0xe7, 0xe7,
+		 0xe7, 0xe7, 0xe7, 0xe7, 0xe8, 0xe8, 0xe8, 0xe8,
+		 0xe8, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xeb,
+		 0xeb, 0xeb, 0xeb, 0xeb, 0xec, 0xec, 0xec, 0xec,
+		 0xec, 0xec, 0xed, 0xed, 0xed, 0xed, 0xed, 0xed,
+		 0xee, 0xee, 0xee, 0xee, 0xee, 0xef, 0xef, 0xef,
+		 0xef, 0xef, 0xef, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+		 0xf0, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf3,
+		 0xf3, 0xf3, 0xf3, 0xf3, 0xf4, 0xf4, 0xf4, 0xf4,
+		 0xf4, 0xf4, 0xf5, 0xf5, 0xf5, 0xf5, 0xf5, 0xf5,
+		 0xf6, 0xf6, 0xf6, 0xf6, 0xf6, 0xf6, 0xf7, 0xf7,
+		 0xf7, 0xf7, 0xf7, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8,
+		 0xf8, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xfa,
+		 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa,
+		 0xfa, 0xfa, 0xfa, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb},
+		{0x0d, 0x10, 0x11, 0x14, 0x15, 0x17, 0x18, 0x1b,
+		 0x1c, 0x1e, 0x20, 0x22, 0x23, 0x26, 0x27, 0x28,
+		 0x29, 0x2b, 0x2d, 0x2f, 0x30, 0x31, 0x33, 0x34,
+		 0x35, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d,
+		 0x3f, 0x40, 0x42, 0x43, 0x44, 0x45, 0x47, 0x48,
+		 0x49, 0x4a, 0x4b, 0x4b, 0x4c, 0x4d, 0x4f, 0x50,
+		 0x52, 0x52, 0x53, 0x54, 0x55, 0x56, 0x56, 0x58,
+		 0x59, 0x5a, 0x5a, 0x5b, 0x5c, 0x5e, 0x5e, 0x5f,
+		 0x60, 0x60, 0x61, 0x62, 0x62, 0x63, 0x65, 0x65,
+		 0x66, 0x67, 0x67, 0x68, 0x69, 0x69, 0x6a, 0x6c,
+		 0x6c, 0x6d, 0x6d, 0x6e, 0x6f, 0x6f, 0x70, 0x70,
+		 0x71, 0x73, 0x73, 0x74, 0x74, 0x75, 0x75, 0x77,
+		 0x78, 0x78, 0x79, 0x79, 0x7a, 0x7a, 0x7b, 0x7b,
+		 0x7c, 0x7c, 0x7d, 0x7d, 0x7f, 0x7f, 0x80, 0x80,
+		 0x81, 0x81, 0x82, 0x82, 0x84, 0x84, 0x85, 0x85,
+		 0x86, 0x86, 0x88, 0x88, 0x89, 0x89, 0x8a, 0x8a,
+		 0x8b, 0x8b, 0x8d, 0x8d, 0x8d, 0x8e, 0x8e, 0x8f,
+		 0x8f, 0x90, 0x90, 0x91, 0x91, 0x91, 0x92, 0x92,
+		 0x93, 0x93, 0x94, 0x94, 0x94, 0x96, 0x96, 0x97,
+		 0x97, 0x98, 0x98, 0x98, 0x99, 0x99, 0x9a, 0x9a,
+		 0x9a, 0x9b, 0x9b, 0x9c, 0x9c, 0x9c, 0x9d, 0x9d,
+		 0x9d, 0x9e, 0x9e, 0xa0, 0xa0, 0xa0, 0xa1, 0xa1,
+		 0xa1, 0xa2, 0xa2, 0xa3, 0xa3, 0xa3, 0xa4, 0xa4,
+		 0xa4, 0xa5, 0xa5, 0xa5, 0xa6, 0xa6, 0xa8, 0xa8,
+		 0xa8, 0xa9, 0xa9, 0xa9, 0xab, 0xab, 0xab, 0xac,
+		 0xac, 0xac, 0xad, 0xad, 0xad, 0xae, 0xae, 0xae,
+		 0xaf, 0xaf, 0xaf, 0xb0, 0xb0, 0xb0, 0xb1, 0xb1,
+		 0xb1, 0xb2, 0xb2, 0xb2, 0xb3, 0xb3, 0xb3, 0xb4,
+		 0xb4, 0xb4, 0xb6, 0xb6, 0xb6, 0xb6, 0xb7, 0xb7,
+		 0xb7, 0xb8, 0xb8, 0xb8, 0xb9, 0xb9, 0xb9, 0xba,
+		 0xba, 0xba, 0xba, 0xbc, 0xbc, 0xbc, 0xbd, 0xbd,
+		 0xbd, 0xbe, 0xbe, 0xbe, 0xbe, 0xbf, 0xbf, 0xbf,
+		 0xc0, 0xc0, 0xc0, 0xc0, 0xc2, 0xc2, 0xc2, 0xc3,
+		 0xc3, 0xc3, 0xc3, 0xc4, 0xc4, 0xc4, 0xc5, 0xc5,
+		 0xc5, 0xc5, 0xc6, 0xc6, 0xc6, 0xc6, 0xc7, 0xc7,
+		 0xc7, 0xc9, 0xc9, 0xc9, 0xc9, 0xca, 0xca, 0xca,
+		 0xca, 0xcb, 0xcb, 0xcb, 0xcc, 0xcc, 0xcc, 0xcc,
+		 0xcd, 0xcd, 0xcd, 0xcd, 0xce, 0xce, 0xce, 0xce,
+		 0xcf, 0xcf, 0xcf, 0xcf, 0xd0, 0xd0, 0xd0, 0xd0,
+		 0xd1, 0xd1, 0xd1, 0xd1, 0xd3, 0xd3, 0xd3, 0xd3,
+		 0xd4, 0xd4, 0xd4, 0xd4, 0xd6, 0xd6, 0xd6, 0xd6,
+		 0xd7, 0xd7, 0xd7, 0xd7, 0xd8, 0xd8, 0xd8, 0xd8,
+		 0xd9, 0xd9, 0xd9, 0xd9, 0xda, 0xda, 0xda, 0xda,
+		 0xdb, 0xdb, 0xdb, 0xdb, 0xdd, 0xdd, 0xdd, 0xdd,
+		 0xdd, 0xde, 0xde, 0xde, 0xde, 0xdf, 0xdf, 0xdf,
+		 0xdf, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1,
+		 0xe1, 0xe1, 0xe2, 0xe2, 0xe2, 0xe2, 0xe3, 0xe3,
+		 0xe3, 0xe3, 0xe3, 0xe4, 0xe4, 0xe4, 0xe4, 0xe5,
+		 0xe5, 0xe5, 0xe5, 0xe5, 0xe6, 0xe6, 0xe6, 0xe6,
+		 0xe7, 0xe7, 0xe7, 0xe7, 0xe7, 0xe8, 0xe8, 0xe8,
+		 0xe8, 0xe9, 0xe9, 0xe9, 0xe9, 0xe9, 0xeb, 0xeb,
+		 0xeb, 0xeb, 0xec, 0xec, 0xec, 0xec, 0xec, 0xed,
+		 0xed, 0xed, 0xed, 0xed, 0xee, 0xee, 0xee, 0xee,
+		 0xef, 0xef, 0xef, 0xef, 0xef, 0xf0, 0xf0, 0xf0,
+		 0xf0, 0xf0, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf3,
+		 0xf3, 0xf3, 0xf3, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4,
+		 0xf5, 0xf5, 0xf5, 0xf5, 0xf5, 0xf6, 0xf6, 0xf6,
+		 0xf6, 0xf6, 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf8,
+		 0xf8, 0xf8, 0xf8, 0xf8, 0xf9, 0xf9, 0xf9, 0xf9,
+		 0xf9, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfa,
+		 0xfa, 0xfa, 0xfa, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb, 0xfb}
+	    },
+	    {							/* gamma 5 */
+		{0x16, 0x18, 0x19, 0x1b, 0x1d, 0x1e, 0x20, 0x21,
+		 0x23, 0x24, 0x25, 0x27, 0x28, 0x2a, 0x2b, 0x2c,
+		 0x2d, 0x2f, 0x30, 0x31, 0x32, 0x34, 0x35, 0x36,
+		 0x37, 0x38, 0x39, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+		 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
+		 0x48, 0x49, 0x4a, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e,
+		 0x4f, 0x50, 0x51, 0x51, 0x52, 0x53, 0x54, 0x55,
+		 0x56, 0x56, 0x57, 0x58, 0x59, 0x59, 0x5a, 0x5b,
+		 0x5c, 0x5c, 0x5d, 0x5e, 0x5f, 0x5f, 0x60, 0x61,
+		 0x62, 0x62, 0x63, 0x64, 0x64, 0x65, 0x66, 0x66,
+		 0x67, 0x68, 0x68, 0x69, 0x6a, 0x6a, 0x6b, 0x6b,
+		 0x6c, 0x6d, 0x6d, 0x6e, 0x6f, 0x6f, 0x70, 0x70,
+		 0x71, 0x71, 0x72, 0x73, 0x73, 0x74, 0x74, 0x75,
+		 0x75, 0x76, 0x77, 0x77, 0x78, 0x78, 0x79, 0x79,
+		 0x7a, 0x7a, 0x7b, 0x7b, 0x7c, 0x7d, 0x7d, 0x7e,
+		 0x7e, 0x7f, 0x7f, 0x80, 0x80, 0x81, 0x81, 0x82,
+		 0x82, 0x83, 0x83, 0x84, 0x84, 0x84, 0x85, 0x85,
+		 0x86, 0x86, 0x87, 0x87, 0x88, 0x88, 0x89, 0x89,
+		 0x8a, 0x8a, 0x8b, 0x8b, 0x8b, 0x8c, 0x8c, 0x8d,
+		 0x8d, 0x8e, 0x8e, 0x8e, 0x8f, 0x8f, 0x90, 0x90,
+		 0x91, 0x91, 0x91, 0x92, 0x92, 0x93, 0x93, 0x94,
+		 0x94, 0x94, 0x95, 0x95, 0x96, 0x96, 0x96, 0x97,
+		 0x97, 0x98, 0x98, 0x98, 0x99, 0x99, 0x9a, 0x9a,
+		 0x9a, 0x9b, 0x9b, 0x9b, 0x9c, 0x9c, 0x9d, 0x9d,
+		 0x9d, 0x9e, 0x9e, 0x9e, 0x9f, 0x9f, 0xa0, 0xa0,
+		 0xa0, 0xa1, 0xa1, 0xa1, 0xa2, 0xa2, 0xa2, 0xa3,
+		 0xa3, 0xa4, 0xa4, 0xa4, 0xa5, 0xa5, 0xa5, 0xa6,
+		 0xa6, 0xa6, 0xa7, 0xa7, 0xa7, 0xa8, 0xa8, 0xa8,
+		 0xa9, 0xa9, 0xa9, 0xaa, 0xaa, 0xaa, 0xab, 0xab,
+		 0xab, 0xac, 0xac, 0xac, 0xad, 0xad, 0xad, 0xae,
+		 0xae, 0xae, 0xaf, 0xaf, 0xaf, 0xb0, 0xb0, 0xb0,
+		 0xb0, 0xb1, 0xb1, 0xb1, 0xb2, 0xb2, 0xb2, 0xb3,
+		 0xb3, 0xb3, 0xb4, 0xb4, 0xb4, 0xb4, 0xb5, 0xb5,
+		 0xb5, 0xb6, 0xb6, 0xb6, 0xb7, 0xb7, 0xb7, 0xb7,
+		 0xb8, 0xb8, 0xb8, 0xb9, 0xb9, 0xb9, 0xba, 0xba,
+		 0xba, 0xba, 0xbb, 0xbb, 0xbb, 0xbc, 0xbc, 0xbc,
+		 0xbc, 0xbd, 0xbd, 0xbd, 0xbe, 0xbe, 0xbe, 0xbe,
+		 0xbf, 0xbf, 0xbf, 0xc0, 0xc0, 0xc0, 0xc0, 0xc1,
+		 0xc1, 0xc1, 0xc1, 0xc2, 0xc2, 0xc2, 0xc3, 0xc3,
+		 0xc3, 0xc3, 0xc4, 0xc4, 0xc4, 0xc4, 0xc5, 0xc5,
+		 0xc5, 0xc5, 0xc6, 0xc6, 0xc6, 0xc7, 0xc7, 0xc7,
+		 0xc7, 0xc8, 0xc8, 0xc8, 0xc8, 0xc9, 0xc9, 0xc9,
+		 0xc9, 0xca, 0xca, 0xca, 0xca, 0xcb, 0xcb, 0xcb,
+		 0xcb, 0xcc, 0xcc, 0xcc, 0xcc, 0xcd, 0xcd, 0xcd,
+		 0xcd, 0xce, 0xce, 0xce, 0xce, 0xcf, 0xcf, 0xcf,
+		 0xcf, 0xd0, 0xd0, 0xd0, 0xd0, 0xd1, 0xd1, 0xd1,
+		 0xd1, 0xd2, 0xd2, 0xd2, 0xd2, 0xd3, 0xd3, 0xd3,
+		 0xd3, 0xd4, 0xd4, 0xd4, 0xd4, 0xd5, 0xd5, 0xd5,
+		 0xd5, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd7, 0xd7,
+		 0xd7, 0xd7, 0xd8, 0xd8, 0xd8, 0xd8, 0xd9, 0xd9,
+		 0xd9, 0xd9, 0xda, 0xda, 0xda, 0xda, 0xda, 0xdb,
+		 0xdb, 0xdb, 0xdb, 0xdc, 0xdc, 0xdc, 0xdc, 0xdd,
+		 0xdd, 0xdd, 0xdd, 0xdd, 0xde, 0xde, 0xde, 0xde,
+		 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xe0, 0xe0, 0xe0,
+		 0xe0, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe2, 0xe2,
+		 0xe2, 0xe2, 0xe3, 0xe3, 0xe3, 0xe3, 0xe3, 0xe4,
+		 0xe4, 0xe4, 0xe4, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5,
+		 0xe6, 0xe6, 0xe6, 0xe6, 0xe7, 0xe7, 0xe7, 0xe7,
+		 0xe7, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe9, 0xe9,
+		 0xe9, 0xe9, 0xea, 0xea, 0xea, 0xea, 0xea, 0xeb,
+		 0xeb, 0xeb, 0xeb, 0xeb, 0xec, 0xec, 0xec, 0xec,
+		 0xed, 0xed, 0xed, 0xed, 0xed, 0xee, 0xee, 0xee,
+		 0xee, 0xee, 0xef, 0xef, 0xef, 0xef, 0xef, 0xf0,
+		 0xf0, 0xf0, 0xf0, 0xf1, 0xf1, 0xf1, 0xf1, 0xf1,
+		 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf3, 0xf3, 0xf3,
+		 0xf3, 0xf3, 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf5,
+		 0xf5, 0xf5, 0xf5, 0xf5, 0xf6, 0xf6, 0xf6, 0xf6,
+		 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf8, 0xf8, 0xf8,
+		 0xf8, 0xf8, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xfa,
+		 0xfa, 0xfa, 0xfa, 0xfa, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfd, 0xfd,
+		 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+		{0x0f, 0x11, 0x12, 0x14, 0x15, 0x16, 0x18, 0x19,
+		 0x1a, 0x1b, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22,
+		 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
+		 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x31, 0x32,
+		 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x38, 0x39,
+		 0x3a, 0x3b, 0x3c, 0x3c, 0x3d, 0x3e, 0x3f, 0x3f,
+		 0x40, 0x41, 0x42, 0x42, 0x43, 0x44, 0x44, 0x45,
+		 0x46, 0x47, 0x47, 0x48, 0x49, 0x49, 0x4a, 0x4b,
+		 0x4b, 0x4c, 0x4c, 0x4d, 0x4e, 0x4e, 0x4f, 0x50,
+		 0x50, 0x51, 0x51, 0x52, 0x53, 0x53, 0x54, 0x54,
+		 0x55, 0x55, 0x56, 0x56, 0x57, 0x58, 0x58, 0x59,
+		 0x59, 0x5a, 0x5a, 0x5b, 0x5b, 0x5c, 0x5c, 0x5d,
+		 0x5d, 0x5e, 0x5e, 0x5f, 0x5f, 0x60, 0x60, 0x61,
+		 0x61, 0x62, 0x62, 0x63, 0x63, 0x64, 0x64, 0x65,
+		 0x65, 0x66, 0x66, 0x66, 0x67, 0x67, 0x68, 0x68,
+		 0x69, 0x69, 0x6a, 0x6a, 0x6a, 0x6b, 0x6b, 0x6c,
+		 0x6c, 0x6d, 0x6d, 0x6d, 0x6e, 0x6e, 0x6f, 0x6f,
+		 0x6f, 0x70, 0x70, 0x71, 0x71, 0x71, 0x72, 0x72,
+		 0x73, 0x73, 0x73, 0x74, 0x74, 0x75, 0x75, 0x75,
+		 0x76, 0x76, 0x76, 0x77, 0x77, 0x78, 0x78, 0x78,
+		 0x79, 0x79, 0x79, 0x7a, 0x7a, 0x7a, 0x7b, 0x7b,
+		 0x7b, 0x7c, 0x7c, 0x7d, 0x7d, 0x7d, 0x7e, 0x7e,
+		 0x7e, 0x7f, 0x7f, 0x7f, 0x80, 0x80, 0x80, 0x81,
+		 0x81, 0x81, 0x82, 0x82, 0x82, 0x83, 0x83, 0x83,
+		 0x84, 0x84, 0x84, 0x84, 0x85, 0x85, 0x85, 0x86,
+		 0x86, 0x86, 0x87, 0x87, 0x87, 0x88, 0x88, 0x88,
+		 0x88, 0x89, 0x89, 0x89, 0x8a, 0x8a, 0x8a, 0x8b,
+		 0x8b, 0x8b, 0x8b, 0x8c, 0x8c, 0x8c, 0x8d, 0x8d,
+		 0x8d, 0x8e, 0x8e, 0x8e, 0x8e, 0x8f, 0x8f, 0x8f,
+		 0x90, 0x90, 0x90, 0x90, 0x91, 0x91, 0x91, 0x91,
+		 0x92, 0x92, 0x92, 0x93, 0x93, 0x93, 0x93, 0x94,
+		 0x94, 0x94, 0x94, 0x95, 0x95, 0x95, 0x96, 0x96,
+		 0x96, 0x96, 0x97, 0x97, 0x97, 0x97, 0x98, 0x98,
+		 0x98, 0x98, 0x99, 0x99, 0x99, 0x99, 0x9a, 0x9a,
+		 0x9a, 0x9a, 0x9b, 0x9b, 0x9b, 0x9b, 0x9c, 0x9c,
+		 0x9c, 0x9c, 0x9d, 0x9d, 0x9d, 0x9d, 0x9e, 0x9e,
+		 0x9e, 0x9e, 0x9f, 0x9f, 0x9f, 0x9f, 0xa0, 0xa0,
+		 0xa0, 0xa0, 0xa1, 0xa1, 0xa1, 0xa1, 0xa2, 0xa2,
+		 0xa2, 0xa2, 0xa2, 0xa3, 0xa3, 0xa3, 0xa3, 0xa4,
+		 0xa4, 0xa4, 0xa4, 0xa5, 0xa5, 0xa5, 0xa5, 0xa5,
+		 0xa6, 0xa6, 0xa6, 0xa6, 0xa7, 0xa7, 0xa7, 0xa7,
+		 0xa8, 0xa8, 0xa8, 0xa8, 0xa8, 0xa9, 0xa9, 0xa9,
+		 0xa9, 0xaa, 0xaa, 0xaa, 0xaa, 0xaa, 0xab, 0xab,
+		 0xab, 0xab, 0xac, 0xac, 0xac, 0xac, 0xac, 0xad,
+		 0xad, 0xad, 0xad, 0xad, 0xae, 0xae, 0xae, 0xae,
+		 0xaf, 0xaf, 0xaf, 0xaf, 0xaf, 0xb0, 0xb0, 0xb0,
+		 0xb0, 0xb0, 0xb1, 0xb1, 0xb1, 0xb1, 0xb1, 0xb2,
+		 0xb2, 0xb2, 0xb2, 0xb2, 0xb3, 0xb3, 0xb3, 0xb3,
+		 0xb4, 0xb4, 0xb4, 0xb4, 0xb4, 0xb5, 0xb5, 0xb5,
+		 0xb5, 0xb5, 0xb6, 0xb6, 0xb6, 0xb6, 0xb6, 0xb7,
+		 0xb7, 0xb7, 0xb7, 0xb7, 0xb8, 0xb8, 0xb8, 0xb8,
+		 0xb8, 0xb9, 0xb9, 0xb9, 0xb9, 0xb9, 0xba, 0xba,
+		 0xba, 0xba, 0xba, 0xbb, 0xbb, 0xbb, 0xbb, 0xbb,
+		 0xbb, 0xbc, 0xbc, 0xbc, 0xbc, 0xbc, 0xbd, 0xbd,
+		 0xbd, 0xbd, 0xbd, 0xbe, 0xbe, 0xbe, 0xbe, 0xbe,
+		 0xbf, 0xbf, 0xbf, 0xbf, 0xbf, 0xc0, 0xc0, 0xc0,
+		 0xc0, 0xc0, 0xc0, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1,
+		 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc3, 0xc3, 0xc3,
+		 0xc3, 0xc3, 0xc3, 0xc4, 0xc4, 0xc4, 0xc4, 0xc4,
+		 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc5, 0xc6, 0xc6,
+		 0xc6, 0xc6, 0xc6, 0xc7, 0xc7, 0xc7, 0xc7, 0xc7,
+		 0xc7, 0xc8, 0xc8, 0xc8, 0xc8, 0xc8, 0xc9, 0xc9,
+		 0xc9, 0xc9, 0xc9, 0xc9, 0xca, 0xca, 0xca, 0xca,
+		 0xca, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcc,
+		 0xcc, 0xcc, 0xcc, 0xcc, 0xcd, 0xcd, 0xcd, 0xcd,
+		 0xcd, 0xcd, 0xce, 0xce, 0xce, 0xce, 0xce, 0xce,
+		 0xcf, 0xcf, 0xcf, 0xcf, 0xcf, 0xd0, 0xd0, 0xd0,
+		 0xd0, 0xd0, 0xd0, 0xd1, 0xd1, 0xd1, 0xd1, 0xd1,
+		 0xd1, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd3,
+		 0xd3, 0xd3, 0xd3, 0xd3, 0xd4, 0xd4, 0xd4, 0xd4,
+		 0xd4, 0xd4, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd5,
+		 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd6, 0xd7, 0xd7,
+		 0xd7, 0xd7, 0xd7, 0xd8, 0xd8, 0xd8, 0xd8, 0xd8,
+		 0xd8, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xd9, 0xda,
+		 0xda, 0xda, 0xda, 0xda, 0xda, 0xdb, 0xdb, 0xdb,
+		 0xdb, 0xdb, 0xdb, 0xdc, 0xdc, 0xdc, 0xdc, 0xdc,
+		 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xde, 0xde,
+		 0xde, 0xde, 0xde, 0xde, 0xdf, 0xdf, 0xdf, 0xdf,
+		 0xdf, 0xdf, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0,
+		 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe2, 0xe2,
+		 0xe2, 0xe2, 0xe2, 0xe2, 0xe3, 0xe3, 0xe3, 0xe3,
+		 0xe3, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4, 0xe5,
+		 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe6, 0xe6, 0xe6,
+		 0xe6, 0xe6, 0xe6, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7,
+		 0xe7, 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe9, 0xe9,
+		 0xe9, 0xe9, 0xe9, 0xe9, 0xea, 0xea, 0xea, 0xea,
+		 0xea, 0xea, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xeb,
+		 0xec, 0xec, 0xec, 0xec, 0xec, 0xec, 0xed, 0xed,
+		 0xed, 0xed, 0xed, 0xee, 0xee, 0xee, 0xee, 0xee,
+		 0xee, 0xef, 0xef, 0xef, 0xef, 0xef, 0xef, 0xf0,
+		 0xf0, 0xf0, 0xf0, 0xf0, 0xf1, 0xf1, 0xf1, 0xf1,
+		 0xf1, 0xf1, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2, 0xf2,
+		 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf4, 0xf4, 0xf4,
+		 0xf4, 0xf4, 0xf4, 0xf5, 0xf5, 0xf5, 0xf5, 0xf5,
+		 0xf6, 0xf6, 0xf6, 0xf6, 0xf6, 0xf6, 0xf7, 0xf7,
+		 0xf7, 0xf7, 0xf7, 0xf8, 0xf8, 0xf8, 0xf8, 0xf8,
+		 0xf8, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xfa, 0xfa,
+		 0xfa, 0xfa, 0xfa, 0xfa, 0xfb, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfd, 0xfd,
+		 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe,
+		 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+		{0x13, 0x15, 0x16, 0x18, 0x19, 0x1b, 0x1c, 0x1e,
+		 0x1f, 0x20, 0x22, 0x23, 0x24, 0x26, 0x27, 0x28,
+		 0x29, 0x2a, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31,
+		 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+		 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x41,
+		 0x42, 0x43, 0x44, 0x44, 0x45, 0x46, 0x47, 0x48,
+		 0x49, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4d, 0x4e,
+		 0x4f, 0x50, 0x50, 0x51, 0x52, 0x53, 0x53, 0x54,
+		 0x55, 0x55, 0x56, 0x57, 0x57, 0x58, 0x59, 0x59,
+		 0x5a, 0x5b, 0x5b, 0x5c, 0x5d, 0x5d, 0x5e, 0x5f,
+		 0x5f, 0x60, 0x60, 0x61, 0x62, 0x62, 0x63, 0x63,
+		 0x64, 0x65, 0x65, 0x66, 0x66, 0x67, 0x67, 0x68,
+		 0x69, 0x69, 0x6a, 0x6a, 0x6b, 0x6b, 0x6c, 0x6c,
+		 0x6d, 0x6d, 0x6e, 0x6e, 0x6f, 0x6f, 0x70, 0x70,
+		 0x71, 0x71, 0x72, 0x72, 0x73, 0x73, 0x74, 0x74,
+		 0x75, 0x75, 0x76, 0x76, 0x77, 0x77, 0x78, 0x78,
+		 0x79, 0x79, 0x7a, 0x7a, 0x7a, 0x7b, 0x7b, 0x7c,
+		 0x7c, 0x7d, 0x7d, 0x7e, 0x7e, 0x7e, 0x7f, 0x7f,
+		 0x80, 0x80, 0x81, 0x81, 0x81, 0x82, 0x82, 0x83,
+		 0x83, 0x84, 0x84, 0x84, 0x85, 0x85, 0x86, 0x86,
+		 0x86, 0x87, 0x87, 0x88, 0x88, 0x88, 0x89, 0x89,
+		 0x89, 0x8a, 0x8a, 0x8b, 0x8b, 0x8b, 0x8c, 0x8c,
+		 0x8c, 0x8d, 0x8d, 0x8e, 0x8e, 0x8e, 0x8f, 0x8f,
+		 0x8f, 0x90, 0x90, 0x90, 0x91, 0x91, 0x92, 0x92,
+		 0x92, 0x93, 0x93, 0x93, 0x94, 0x94, 0x94, 0x95,
+		 0x95, 0x95, 0x96, 0x96, 0x96, 0x97, 0x97, 0x97,
+		 0x98, 0x98, 0x98, 0x99, 0x99, 0x99, 0x9a, 0x9a,
+		 0x9a, 0x9b, 0x9b, 0x9b, 0x9c, 0x9c, 0x9c, 0x9d,
+		 0x9d, 0x9d, 0x9e, 0x9e, 0x9e, 0x9e, 0x9f, 0x9f,
+		 0x9f, 0xa0, 0xa0, 0xa0, 0xa1, 0xa1, 0xa1, 0xa2,
+		 0xa2, 0xa2, 0xa2, 0xa3, 0xa3, 0xa3, 0xa4, 0xa4,
+		 0xa4, 0xa5, 0xa5, 0xa5, 0xa5, 0xa6, 0xa6, 0xa6,
+		 0xa7, 0xa7, 0xa7, 0xa7, 0xa8, 0xa8, 0xa8, 0xa9,
+		 0xa9, 0xa9, 0xa9, 0xaa, 0xaa, 0xaa, 0xab, 0xab,
+		 0xab, 0xab, 0xac, 0xac, 0xac, 0xac, 0xad, 0xad,
+		 0xad, 0xae, 0xae, 0xae, 0xae, 0xaf, 0xaf, 0xaf,
+		 0xaf, 0xb0, 0xb0, 0xb0, 0xb1, 0xb1, 0xb1, 0xb1,
+		 0xb2, 0xb2, 0xb2, 0xb2, 0xb3, 0xb3, 0xb3, 0xb3,
+		 0xb4, 0xb4, 0xb4, 0xb4, 0xb5, 0xb5, 0xb5, 0xb5,
+		 0xb6, 0xb6, 0xb6, 0xb6, 0xb7, 0xb7, 0xb7, 0xb7,
+		 0xb8, 0xb8, 0xb8, 0xb8, 0xb9, 0xb9, 0xb9, 0xb9,
+		 0xba, 0xba, 0xba, 0xba, 0xbb, 0xbb, 0xbb, 0xbb,
+		 0xbc, 0xbc, 0xbc, 0xbc, 0xbd, 0xbd, 0xbd, 0xbd,
+		 0xbe, 0xbe, 0xbe, 0xbe, 0xbf, 0xbf, 0xbf, 0xbf,
+		 0xbf, 0xc0, 0xc0, 0xc0, 0xc0, 0xc1, 0xc1, 0xc1,
+		 0xc1, 0xc2, 0xc2, 0xc2, 0xc2, 0xc2, 0xc3, 0xc3,
+		 0xc3, 0xc3, 0xc4, 0xc4, 0xc4, 0xc4, 0xc5, 0xc5,
+		 0xc5, 0xc5, 0xc5, 0xc6, 0xc6, 0xc6, 0xc6, 0xc7,
+		 0xc7, 0xc7, 0xc7, 0xc7, 0xc8, 0xc8, 0xc8, 0xc8,
+		 0xc9, 0xc9, 0xc9, 0xc9, 0xc9, 0xca, 0xca, 0xca,
+		 0xca, 0xcb, 0xcb, 0xcb, 0xcb, 0xcb, 0xcc, 0xcc,
+		 0xcc, 0xcc, 0xcd, 0xcd, 0xcd, 0xcd, 0xcd, 0xce,
+		 0xce, 0xce, 0xce, 0xce, 0xcf, 0xcf, 0xcf, 0xcf,
+		 0xd0, 0xd0, 0xd0, 0xd0, 0xd0, 0xd1, 0xd1, 0xd1,
+		 0xd1, 0xd1, 0xd2, 0xd2, 0xd2, 0xd2, 0xd2, 0xd3,
+		 0xd3, 0xd3, 0xd3, 0xd4, 0xd4, 0xd4, 0xd4, 0xd4,
+		 0xd5, 0xd5, 0xd5, 0xd5, 0xd5, 0xd6, 0xd6, 0xd6,
+		 0xd6, 0xd6, 0xd7, 0xd7, 0xd7, 0xd7, 0xd7, 0xd8,
+		 0xd8, 0xd8, 0xd8, 0xd8, 0xd9, 0xd9, 0xd9, 0xd9,
+		 0xd9, 0xda, 0xda, 0xda, 0xda, 0xda, 0xdb, 0xdb,
+		 0xdb, 0xdb, 0xdb, 0xdc, 0xdc, 0xdc, 0xdc, 0xdc,
+		 0xdd, 0xdd, 0xdd, 0xdd, 0xdd, 0xde, 0xde, 0xde,
+		 0xde, 0xde, 0xdf, 0xdf, 0xdf, 0xdf, 0xdf, 0xe0,
+		 0xe0, 0xe0, 0xe0, 0xe0, 0xe1, 0xe1, 0xe1, 0xe1,
+		 0xe1, 0xe2, 0xe2, 0xe2, 0xe2, 0xe2, 0xe3, 0xe3,
+		 0xe3, 0xe3, 0xe3, 0xe4, 0xe4, 0xe4, 0xe4, 0xe4,
+		 0xe4, 0xe5, 0xe5, 0xe5, 0xe5, 0xe5, 0xe6, 0xe6,
+		 0xe6, 0xe6, 0xe6, 0xe7, 0xe7, 0xe7, 0xe7, 0xe7,
+		 0xe8, 0xe8, 0xe8, 0xe8, 0xe8, 0xe9, 0xe9, 0xe9,
+		 0xe9, 0xe9, 0xe9, 0xea, 0xea, 0xea, 0xea, 0xea,
+		 0xeb, 0xeb, 0xeb, 0xeb, 0xeb, 0xec, 0xec, 0xec,
+		 0xec, 0xec, 0xed, 0xed, 0xed, 0xed, 0xed, 0xed,
+		 0xee, 0xee, 0xee, 0xee, 0xee, 0xef, 0xef, 0xef,
+		 0xef, 0xef, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0,
+		 0xf1, 0xf1, 0xf1, 0xf1, 0xf1, 0xf2, 0xf2, 0xf2,
+		 0xf2, 0xf2, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3, 0xf3,
+		 0xf4, 0xf4, 0xf4, 0xf4, 0xf4, 0xf5, 0xf5, 0xf5,
+		 0xf5, 0xf5, 0xf6, 0xf6, 0xf6, 0xf6, 0xf6, 0xf6,
+		 0xf7, 0xf7, 0xf7, 0xf7, 0xf7, 0xf8, 0xf8, 0xf8,
+		 0xf8, 0xf8, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9, 0xf9,
+		 0xfa, 0xfa, 0xfa, 0xfa, 0xfa, 0xfb, 0xfb, 0xfb,
+		 0xfb, 0xfb, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc, 0xfc,
+		 0xfd, 0xfd, 0xfd, 0xfd, 0xfd, 0xfe, 0xfe, 0xfe,
+		 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+		 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
+	    },
+	};
+
+	reg_w(gspca_dev, TP6800_R21_ENDP_1_CTL, 0x00);
+	if (sd->bridge == BRIDGE_TP6810)
+		reg_w(gspca_dev, 0x02, 0x28);
+/*	msleep(50); */
+	bulk_w(gspca_dev, 0x00, gamma_tb[gamma][0], 1024);
+	bulk_w(gspca_dev, 0x01, gamma_tb[gamma][1], 1024);
+	bulk_w(gspca_dev, 0x02, gamma_tb[gamma][2], 1024);
+	if (sd->bridge == BRIDGE_TP6810) {
+		int i;
+
+		reg_w(gspca_dev, 0x02, 0x2b);
+		reg_w(gspca_dev, 0x02, 0x28);
+		for (i = 0; i < 6; i++)
+			reg_w(gspca_dev, TP6800_R55_GAMMA_R,
+				gamma_tb[gamma][0][i]);
+		reg_w(gspca_dev, 0x02, 0x2b);
+		reg_w(gspca_dev, 0x02, 0x28);
+		for (i = 0; i < 6; i++)
+			reg_w(gspca_dev, TP6800_R56_GAMMA_G,
+				gamma_tb[gamma][1][i]);
+		reg_w(gspca_dev, 0x02, 0x2b);
+		reg_w(gspca_dev, 0x02, 0x28);
+		for (i = 0; i < 6; i++)
+			reg_w(gspca_dev, TP6800_R57_GAMMA_B,
+				gamma_tb[gamma][2][i]);
+		reg_w(gspca_dev, 0x02, 0x28);
+	}
+	reg_w(gspca_dev, TP6800_R21_ENDP_1_CTL, 0x03);
+/*	msleep(50); */
+}
+
+static void setsharpness(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->bridge == BRIDGE_TP6800) {
+		val |= 0x08;		/* grid compensation enable */
+		if (gspca_dev->pixfmt.width == 640)
+			reg_w(gspca_dev, TP6800_R78_FORMAT, 0x00); /* vga */
+		else
+			val |= 0x04;		/* scaling down enable */
+		reg_w(gspca_dev, TP6800_R5D_DEMOSAIC_CFG, val);
+	} else {
+		val = (val << 5) | 0x08;
+		reg_w(gspca_dev, 0x59, val);
+	}
+}
+
+static void setautogain(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	sd->ag_cnt = val ? AG_CNT_START : -1;
+}
+
+/* set the resolution for sensor cx0342 */
+static void set_resolution(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	reg_w(gspca_dev, TP6800_R21_ENDP_1_CTL, 0x00);
+	if (gspca_dev->pixfmt.width == 320) {
+		reg_w(gspca_dev, TP6800_R3F_FRAME_RATE, 0x06);
+		msleep(100);
+		i2c_w(gspca_dev, CX0342_AUTO_ADC_CALIB, 0x01);
+		msleep(100);
+		reg_w(gspca_dev, TP6800_R21_ENDP_1_CTL, 0x03);
+		reg_w(gspca_dev, TP6800_R78_FORMAT, 0x01);	/* qvga */
+		reg_w(gspca_dev, TP6800_R5D_DEMOSAIC_CFG, 0x0d);
+		i2c_w(gspca_dev, CX0342_EXPO_LINE_L, 0x37);
+		i2c_w(gspca_dev, CX0342_EXPO_LINE_H, 0x01);
+	} else {
+		reg_w(gspca_dev, TP6800_R3F_FRAME_RATE, 0x05);
+		msleep(100);
+		i2c_w(gspca_dev, CX0342_AUTO_ADC_CALIB, 0x01);
+		msleep(100);
+		reg_w(gspca_dev, TP6800_R21_ENDP_1_CTL, 0x03);
+		reg_w(gspca_dev, TP6800_R78_FORMAT, 0x00);	/* vga */
+		reg_w(gspca_dev, TP6800_R5D_DEMOSAIC_CFG, 0x09);
+		i2c_w(gspca_dev, CX0342_EXPO_LINE_L, 0xcf);
+		i2c_w(gspca_dev, CX0342_EXPO_LINE_H, 0x00);
+	}
+	i2c_w(gspca_dev, CX0342_SYS_CTRL_0, 0x01);
+	bulk_w(gspca_dev, 0x03, color_gain[SENSOR_CX0342],
+				ARRAY_SIZE(color_gain[0]));
+	setgamma(gspca_dev, v4l2_ctrl_g_ctrl(sd->gamma));
+	if (sd->sensor == SENSOR_SOI763A)
+		setquality(gspca_dev, v4l2_ctrl_g_ctrl(sd->jpegqual));
+}
+
+/* convert the frame rate to a tp68x0 value */
+static int get_fr_idx(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int i;
+
+	if (sd->bridge == BRIDGE_TP6800) {
+		for (i = 0; i < ARRAY_SIZE(rates) - 1; i++) {
+			if (sd->framerate >= rates[i])
+				break;
+		}
+		i = 6 - i;		/* 1 = 5fps .. 6 = 30fps */
+
+		/* 640x480 * 30 fps does not work */
+		if (i == 6			/* if 30 fps */
+		 && gspca_dev->pixfmt.width == 640)
+			i = 0x05;		/* 15 fps */
+	} else {
+		for (i = 0; i < ARRAY_SIZE(rates_6810) - 1; i++) {
+			if (sd->framerate >= rates_6810[i])
+				break;
+		}
+		i = 7 - i;		/* 3 = 5fps .. 7 = 30fps */
+
+		/* 640x480 * 30 fps does not work */
+		if (i == 7			/* if 30 fps */
+		 && gspca_dev->pixfmt.width == 640)
+			i = 6;			/* 15 fps */
+		i |= 0x80;			/* clock * 1 */
+	}
+	return i;
+}
+
+static void setframerate(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 fr_idx;
+
+	fr_idx = get_fr_idx(gspca_dev);
+
+	if (sd->bridge == BRIDGE_TP6810) {
+		reg_r(gspca_dev, 0x7b);
+		reg_w(gspca_dev, 0x7b,
+			sd->sensor == SENSOR_CX0342 ? 0x10 : 0x90);
+		if (val >= 128)
+			fr_idx = 0xf0;		/* lower frame rate */
+	}
+
+	reg_w(gspca_dev, TP6800_R3F_FRAME_RATE, fr_idx);
+
+	if (sd->sensor == SENSOR_CX0342)
+		i2c_w(gspca_dev, CX0342_AUTO_ADC_CALIB, 0x01);
+}
+
+static void setrgain(struct gspca_dev *gspca_dev, s32 rgain)
+{
+	i2c_w(gspca_dev, CX0342_RAW_RGAIN_H, rgain >> 8);
+	i2c_w(gspca_dev, CX0342_RAW_RGAIN_L, rgain);
+	i2c_w(gspca_dev, CX0342_SYS_CTRL_0, 0x80);
+}
+
+static int sd_setgain(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	s32 val = gspca_dev->gain->val;
+
+	if (sd->sensor == SENSOR_CX0342) {
+		s32 old = gspca_dev->gain->cur.val ?
+					gspca_dev->gain->cur.val : 1;
+
+		sd->blue->val = sd->blue->val * val / old;
+		if (sd->blue->val > 4095)
+			sd->blue->val = 4095;
+		sd->red->val = sd->red->val * val / old;
+		if (sd->red->val > 4095)
+			sd->red->val = 4095;
+	}
+	if (gspca_dev->streaming) {
+		if (sd->sensor == SENSOR_CX0342)
+			setexposure(gspca_dev, gspca_dev->exposure->val,
+					gspca_dev->gain->val,
+					sd->blue->val, sd->red->val);
+		else
+			setexposure(gspca_dev, gspca_dev->exposure->val,
+					gspca_dev->gain->val, 0, 0);
+	}
+	return gspca_dev->usb_err;
+}
+
+static void setbgain(struct gspca_dev *gspca_dev, s32 bgain)
+{
+	i2c_w(gspca_dev, CX0342_RAW_BGAIN_H, bgain >> 8);
+	i2c_w(gspca_dev, CX0342_RAW_BGAIN_L, bgain);
+	i2c_w(gspca_dev, CX0342_SYS_CTRL_0, 0x80);
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+		     const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	sd->bridge = id->driver_info;
+
+	gspca_dev->cam.cam_mode = vga_mode;
+	gspca_dev->cam.nmodes = ARRAY_SIZE(vga_mode);
+	gspca_dev->cam.mode_framerates = sd->bridge == BRIDGE_TP6800 ?
+			framerates : framerates_6810;
+
+	sd->framerate = DEFAULT_FRAME_RATE;
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	static const struct cmd tp6800_preinit[] = {
+		{TP6800_R10_SIF_TYPE, 0x01},	/* sif */
+		{TP6800_R11_SIF_CONTROL, 0x01},
+		{TP6800_R15_GPIO_PU, 0x9f},
+		{TP6800_R16_GPIO_PD, 0x9f},
+		{TP6800_R17_GPIO_IO, 0x80},
+		{TP6800_R18_GPIO_DATA, 0x40},	/* LED off */
+	};
+	static const struct cmd tp6810_preinit[] = {
+		{TP6800_R2F_TIMING_CFG, 0x2f},
+		{TP6800_R15_GPIO_PU, 0x6f},
+		{TP6800_R16_GPIO_PD, 0x40},
+		{TP6800_R17_GPIO_IO, 0x9f},
+		{TP6800_R18_GPIO_DATA, 0xc1},	/* LED off */
+	};
+
+	if (sd->bridge == BRIDGE_TP6800)
+		reg_w_buf(gspca_dev, tp6800_preinit,
+				ARRAY_SIZE(tp6800_preinit));
+	else
+		reg_w_buf(gspca_dev, tp6810_preinit,
+				ARRAY_SIZE(tp6810_preinit));
+	msleep(15);
+	reg_r(gspca_dev, TP6800_R18_GPIO_DATA);
+	gspca_dbg(gspca_dev, D_PROBE, "gpio: %02x\n", gspca_dev->usb_buf[0]);
+/* values:
+ *	0x80: snapshot button
+ *	0x40: LED
+ *	0x20: (bridge / sensor) reset for tp6810 ?
+ *	0x07: sensor type ?
+ */
+
+	/* guess the sensor type */
+	if (force_sensor >= 0) {
+		sd->sensor = force_sensor;
+	} else {
+		if (sd->bridge == BRIDGE_TP6800) {
+/*fixme: not sure this is working*/
+			switch (gspca_dev->usb_buf[0] & 0x07) {
+			case 0:
+				sd->sensor = SENSOR_SOI763A;
+				break;
+			case 1:
+				sd->sensor = SENSOR_CX0342;
+				break;
+			}
+		} else {
+			int sensor;
+
+			sensor = probe_6810(gspca_dev);
+			if (sensor < 0) {
+				pr_warn("Unknown sensor %d - forced to soi763a\n",
+					-sensor);
+				sensor = SENSOR_SOI763A;
+			}
+			sd->sensor = sensor;
+		}
+	}
+	if (sd->sensor == SENSOR_SOI763A) {
+		pr_info("Sensor soi763a\n");
+		if (sd->bridge == BRIDGE_TP6810) {
+			soi763a_6810_init(gspca_dev);
+		}
+	} else {
+		pr_info("Sensor cx0342\n");
+		if (sd->bridge == BRIDGE_TP6810) {
+			cx0342_6810_init(gspca_dev);
+		}
+	}
+
+	set_dqt(gspca_dev, 0);
+	return 0;
+}
+
+/* This function is called before choosing the alt setting */
+static int sd_isoc_init(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	static const struct cmd cx_sensor_init[] = {
+		{CX0342_AUTO_ADC_CALIB, 0x81},
+		{CX0342_EXPO_LINE_L, 0x37},
+		{CX0342_EXPO_LINE_H, 0x01},
+		{CX0342_RAW_GRGAIN_L, 0x00},
+		{CX0342_RAW_GBGAIN_L, 0x00},
+		{CX0342_RAW_RGAIN_L, 0x00},
+		{CX0342_RAW_BGAIN_L, 0x00},
+		{CX0342_SYS_CTRL_0, 0x81},
+	};
+	static const struct cmd cx_bridge_init[] = {
+		{0x4d, 0x00},
+		{0x4c, 0xff},
+		{0x4e, 0xff},
+		{0x4f, 0x00},
+	};
+	static const struct cmd ov_sensor_init[] = {
+		{0x10, 0x75},		/* exposure */
+		{0x76, 0x03},
+		{0x00, 0x00},		/* gain */
+	};
+	static const struct cmd ov_bridge_init[] = {
+		{0x7b, 0x90},
+		{TP6800_R3F_FRAME_RATE, 0x87},
+	};
+
+	if (sd->bridge == BRIDGE_TP6800)
+		return 0;
+	if (sd->sensor == SENSOR_CX0342) {
+		reg_w(gspca_dev, TP6800_R12_SIF_ADDR_S, 0x20);
+		reg_w(gspca_dev, TP6800_R3F_FRAME_RATE, 0x87);
+		i2c_w_buf(gspca_dev, cx_sensor_init,
+				ARRAY_SIZE(cx_sensor_init));
+		reg_w_buf(gspca_dev, cx_bridge_init,
+				ARRAY_SIZE(cx_bridge_init));
+		bulk_w(gspca_dev, 0x03, color_null, sizeof color_null);
+		reg_w(gspca_dev, 0x59, 0x40);
+	} else {
+		reg_w(gspca_dev, TP6800_R12_SIF_ADDR_S, 0x21);
+		i2c_w_buf(gspca_dev, ov_sensor_init,
+				ARRAY_SIZE(ov_sensor_init));
+		reg_r(gspca_dev, 0x7b);
+		reg_w_buf(gspca_dev, ov_bridge_init,
+				ARRAY_SIZE(ov_bridge_init));
+	}
+	reg_w(gspca_dev, TP6800_R78_FORMAT,
+			gspca_dev->curr_mode ? 0x00 : 0x01);
+	return gspca_dev->usb_err;
+}
+
+static void set_led(struct gspca_dev *gspca_dev, int on)
+{
+	u8 data;
+
+	reg_r(gspca_dev, TP6800_R18_GPIO_DATA);
+	data = gspca_dev->usb_buf[0];
+	if (on)
+		data &= ~0x40;
+	else
+		data |= 0x40;
+	reg_w(gspca_dev, TP6800_R18_GPIO_DATA, data);
+}
+
+static void cx0342_6800_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	static const struct cmd reg_init[] = {
+		/* fixme: is this useful? */
+		{TP6800_R17_GPIO_IO, 0x9f},
+		{TP6800_R16_GPIO_PD, 0x40},
+		{TP6800_R10_SIF_TYPE, 0x00},	/* i2c 8 bits */
+		{TP6800_R50, 0x00},
+		{TP6800_R51, 0x00},
+		{TP6800_R52, 0xff},
+		{TP6800_R53, 0x03},
+		{TP6800_R54_DARK_CFG, 0x07},
+		{TP6800_R5C_EDGE_THRLD, 0x40},
+		{TP6800_R7A_BLK_THRLD, 0x40},
+		{TP6800_R2F_TIMING_CFG, 0x17},
+		{TP6800_R30_SENSOR_CFG, 0x18},	/* G1B..RG0 */
+		{TP6800_R37_FRONT_DARK_ST, 0x00},
+		{TP6800_R38_FRONT_DARK_END, 0x00},
+		{TP6800_R39_REAR_DARK_ST_L, 0x00},
+		{TP6800_R3A_REAR_DARK_ST_H, 0x00},
+		{TP6800_R3B_REAR_DARK_END_L, 0x00},
+		{TP6800_R3C_REAR_DARK_END_H, 0x00},
+		{TP6800_R3D_HORIZ_DARK_LINE_L, 0x00},
+		{TP6800_R3E_HORIZ_DARK_LINE_H, 0x00},
+		{TP6800_R21_ENDP_1_CTL, 0x03},
+
+		{TP6800_R31_PIXEL_START, 0x0b},
+		{TP6800_R32_PIXEL_END_L, 0x8a},
+		{TP6800_R33_PIXEL_END_H, 0x02},
+		{TP6800_R34_LINE_START, 0x0e},
+		{TP6800_R35_LINE_END_L, 0xf4},
+		{TP6800_R36_LINE_END_H, 0x01},
+		{TP6800_R78_FORMAT, 0x00},
+		{TP6800_R12_SIF_ADDR_S, 0x20},	/* cx0342 i2c addr */
+	};
+	static const struct cmd sensor_init[] = {
+		{CX0342_OUTPUT_CTRL, 0x07},
+		{CX0342_BYPASS_MODE, 0x58},
+		{CX0342_GPXLTHD_L, 0x16},
+		{CX0342_RBPXLTHD_L, 0x16},
+		{CX0342_PLANETHD_L, 0xc0},
+		{CX0342_PLANETHD_H, 0x03},
+		{CX0342_RB_GAP_L, 0xff},
+		{CX0342_RB_GAP_H, 0x07},
+		{CX0342_G_GAP_L, 0xff},
+		{CX0342_G_GAP_H, 0x07},
+		{CX0342_RST_OVERFLOW_L, 0x5c},
+		{CX0342_RST_OVERFLOW_H, 0x01},
+		{CX0342_DATA_OVERFLOW_L, 0xfc},
+		{CX0342_DATA_OVERFLOW_H, 0x03},
+		{CX0342_DATA_UNDERFLOW_L, 0x00},
+		{CX0342_DATA_UNDERFLOW_H, 0x00},
+		{CX0342_SYS_CTRL_0, 0x40},
+		{CX0342_GLOBAL_GAIN, 0x01},
+		{CX0342_CLOCK_GEN, 0x00},
+		{CX0342_SYS_CTRL_0, 0x02},
+		{CX0342_IDLE_CTRL, 0x05},
+		{CX0342_ADCGN, 0x00},
+		{CX0342_ADC_CTL, 0x00},
+		{CX0342_LVRST_BLBIAS, 0x01},
+		{CX0342_VTHSEL, 0x0b},
+		{CX0342_RAMP_RIV, 0x0b},
+		{CX0342_LDOSEL, 0x07},
+		{CX0342_SPV_VALUE_L, 0x40},
+		{CX0342_SPV_VALUE_H, 0x02},
+	};
+
+	reg_w_buf(gspca_dev, reg_init, ARRAY_SIZE(reg_init));
+	i2c_w_buf(gspca_dev, sensor_init, ARRAY_SIZE(sensor_init));
+	i2c_w_buf(gspca_dev, cx0342_timing_seq, ARRAY_SIZE(cx0342_timing_seq));
+	reg_w(gspca_dev, TP6800_R5C_EDGE_THRLD, 0x10);
+	reg_w(gspca_dev, TP6800_R54_DARK_CFG, 0x00);
+	i2c_w(gspca_dev, CX0342_EXPO_LINE_H, 0x00);
+	i2c_w(gspca_dev, CX0342_SYS_CTRL_0, 0x01);
+	if (sd->sensor == SENSOR_CX0342)
+		setexposure(gspca_dev, v4l2_ctrl_g_ctrl(gspca_dev->exposure),
+			v4l2_ctrl_g_ctrl(gspca_dev->gain),
+			v4l2_ctrl_g_ctrl(sd->blue),
+			v4l2_ctrl_g_ctrl(sd->red));
+	else
+		setexposure(gspca_dev, v4l2_ctrl_g_ctrl(gspca_dev->exposure),
+			v4l2_ctrl_g_ctrl(gspca_dev->gain), 0, 0);
+	set_led(gspca_dev, 1);
+	set_resolution(gspca_dev);
+}
+
+static void cx0342_6810_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	static const struct cmd sensor_init_2[] = {
+		{CX0342_EXPO_LINE_L, 0x6f},
+		{CX0342_EXPO_LINE_H, 0x02},
+		{CX0342_RAW_GRGAIN_L, 0x00},
+		{CX0342_RAW_GBGAIN_L, 0x00},
+		{CX0342_RAW_RGAIN_L, 0x00},
+		{CX0342_RAW_BGAIN_L, 0x00},
+		{CX0342_SYS_CTRL_0, 0x81},
+	};
+	static const struct cmd bridge_init_2[] = {
+		{0x4d, 0x00},
+		{0x4c, 0xff},
+		{0x4e, 0xff},
+		{0x4f, 0x00},
+		{TP6800_R7A_BLK_THRLD, 0x00},
+		{TP6800_R79_QUALITY, 0x04},
+		{TP6800_R79_QUALITY, 0x01},
+	};
+	static const struct cmd bridge_init_3[] = {
+		{TP6800_R31_PIXEL_START, 0x08},
+		{TP6800_R32_PIXEL_END_L, 0x87},
+		{TP6800_R33_PIXEL_END_H, 0x02},
+		{TP6800_R34_LINE_START, 0x0e},
+		{TP6800_R35_LINE_END_L, 0xf4},
+		{TP6800_R36_LINE_END_H, 0x01},
+	};
+	static const struct cmd sensor_init_3[] = {
+		{CX0342_AUTO_ADC_CALIB, 0x81},
+		{CX0342_EXPO_LINE_L, 0x6f},
+		{CX0342_EXPO_LINE_H, 0x02},
+		{CX0342_RAW_GRGAIN_L, 0x00},
+		{CX0342_RAW_GBGAIN_L, 0x00},
+		{CX0342_RAW_RGAIN_L, 0x00},
+		{CX0342_RAW_BGAIN_L, 0x00},
+		{CX0342_SYS_CTRL_0, 0x81},
+	};
+	static const struct cmd bridge_init_5[] = {
+		{0x4d, 0x00},
+		{0x4c, 0xff},
+		{0x4e, 0xff},
+		{0x4f, 0x00},
+	};
+	static const struct cmd sensor_init_4[] = {
+		{CX0342_EXPO_LINE_L, 0xd3},
+		{CX0342_EXPO_LINE_H, 0x01},
+/*fixme: gains, but 00..80 only*/
+		{CX0342_RAW_GRGAIN_L, 0x40},
+		{CX0342_RAW_GBGAIN_L, 0x40},
+		{CX0342_RAW_RGAIN_L, 0x40},
+		{CX0342_RAW_BGAIN_L, 0x40},
+		{CX0342_SYS_CTRL_0, 0x81},
+	};
+	static const struct cmd sensor_init_5[] = {
+		{CX0342_IDLE_CTRL, 0x05},
+		{CX0342_ADCGN, 0x00},
+		{CX0342_ADC_CTL, 0x00},
+		{CX0342_LVRST_BLBIAS, 0x01},
+		{CX0342_VTHSEL, 0x0b},
+		{CX0342_RAMP_RIV, 0x0b},
+		{CX0342_LDOSEL, 0x07},
+		{CX0342_SPV_VALUE_L, 0x40},
+		{CX0342_SPV_VALUE_H, 0x02},
+		{CX0342_AUTO_ADC_CALIB, 0x81},
+	};
+
+	reg_w(gspca_dev, 0x22, gspca_dev->alt);
+	i2c_w_buf(gspca_dev, sensor_init_2, ARRAY_SIZE(sensor_init_2));
+	reg_w_buf(gspca_dev, bridge_init_2, ARRAY_SIZE(bridge_init_2));
+	reg_w_buf(gspca_dev, tp6810_cx_init_common,
+			ARRAY_SIZE(tp6810_cx_init_common));
+	reg_w_buf(gspca_dev, bridge_init_3, ARRAY_SIZE(bridge_init_3));
+	if (gspca_dev->curr_mode) {
+		reg_w(gspca_dev, 0x4a, 0x7f);
+		reg_w(gspca_dev, 0x07, 0x05);
+		reg_w(gspca_dev, TP6800_R78_FORMAT, 0x00);	/* vga */
+	} else {
+		reg_w(gspca_dev, 0x4a, 0xff);
+		reg_w(gspca_dev, 0x07, 0x85);
+		reg_w(gspca_dev, TP6800_R78_FORMAT, 0x01);	/* qvga */
+	}
+	setgamma(gspca_dev, v4l2_ctrl_g_ctrl(sd->gamma));
+	reg_w_buf(gspca_dev, tp6810_bridge_start,
+			ARRAY_SIZE(tp6810_bridge_start));
+	setsharpness(gspca_dev, v4l2_ctrl_g_ctrl(sd->sharpness));
+	bulk_w(gspca_dev, 0x03, color_gain[SENSOR_CX0342],
+				ARRAY_SIZE(color_gain[0]));
+	reg_w(gspca_dev, TP6800_R3F_FRAME_RATE, 0x87);
+	i2c_w_buf(gspca_dev, sensor_init_3, ARRAY_SIZE(sensor_init_3));
+	reg_w_buf(gspca_dev, bridge_init_5, ARRAY_SIZE(bridge_init_5));
+	i2c_w_buf(gspca_dev, sensor_init_4, ARRAY_SIZE(sensor_init_4));
+	reg_w_buf(gspca_dev, bridge_init_5, ARRAY_SIZE(bridge_init_5));
+	i2c_w_buf(gspca_dev, sensor_init_5, ARRAY_SIZE(sensor_init_5));
+
+	set_led(gspca_dev, 1);
+/*	setquality(gspca_dev, v4l2_ctrl_g_ctrl(sd->jpegqual)); */
+}
+
+static void soi763a_6800_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	static const struct cmd reg_init[] = {
+		{TP6800_R79_QUALITY, 0x04},
+		{TP6800_R79_QUALITY, 0x01},
+		{TP6800_R10_SIF_TYPE, 0x00},	/* i2c 8 bits */
+
+		{TP6800_R50, 0x00},
+		{TP6800_R51, 0x00},
+		{TP6800_R52, 0xff},
+		{TP6800_R53, 0x03},
+		{TP6800_R54_DARK_CFG, 0x07},
+		{TP6800_R5C_EDGE_THRLD, 0x40},
+
+		{TP6800_R79_QUALITY, 0x03},
+		{TP6800_R7A_BLK_THRLD, 0x40},
+
+		{TP6800_R2F_TIMING_CFG, 0x46},
+		{TP6800_R30_SENSOR_CFG, 0x10},	/* BG1..G0R */
+		{TP6800_R37_FRONT_DARK_ST, 0x00},
+		{TP6800_R38_FRONT_DARK_END, 0x00},
+		{TP6800_R39_REAR_DARK_ST_L, 0x00},
+		{TP6800_R3A_REAR_DARK_ST_H, 0x00},
+		{TP6800_R3B_REAR_DARK_END_L, 0x00},
+		{TP6800_R3C_REAR_DARK_END_H, 0x00},
+		{TP6800_R3D_HORIZ_DARK_LINE_L, 0x00},
+		{TP6800_R3E_HORIZ_DARK_LINE_H, 0x00},
+		{TP6800_R21_ENDP_1_CTL, 0x03},
+
+		{TP6800_R3F_FRAME_RATE, 0x04},	/* 15 fps */
+		{TP6800_R5D_DEMOSAIC_CFG, 0x0e}, /* scale down - medium edge */
+
+		{TP6800_R31_PIXEL_START, 0x1b},
+		{TP6800_R32_PIXEL_END_L, 0x9a},
+		{TP6800_R33_PIXEL_END_H, 0x02},
+		{TP6800_R34_LINE_START, 0x0f},
+		{TP6800_R35_LINE_END_L, 0xf4},
+		{TP6800_R36_LINE_END_H, 0x01},
+		{TP6800_R78_FORMAT, 0x01},	/* qvga */
+		{TP6800_R12_SIF_ADDR_S, 0x21},	/* soi763a i2c addr */
+		{TP6800_R1A_SIF_TX_DATA2, 0x00},
+	};
+	static const struct cmd sensor_init[] = {
+		{0x12, 0x48},		/* mirror - RGB */
+		{0x13, 0xa0},		/* clock - no AGC nor AEC */
+		{0x03, 0xa4},		/* saturation */
+		{0x04, 0x30},		/* hue */
+		{0x05, 0x88},		/* contrast */
+		{0x06, 0x60},		/* brightness */
+		{0x10, 0x41},		/* AEC */
+		{0x11, 0x40},		/* clock rate */
+		{0x13, 0xa0},
+		{0x14, 0x00},		/* 640x480 */
+		{0x15, 0x14},
+		{0x1f, 0x41},
+		{0x20, 0x80},
+		{0x23, 0xee},
+		{0x24, 0x50},
+		{0x25, 0x7a},
+		{0x26, 0x00},
+		{0x27, 0xe2},
+		{0x28, 0xb0},
+		{0x2a, 0x00},
+		{0x2b, 0x00},
+		{0x2d, 0x81},
+		{0x2f, 0x9d},
+		{0x60, 0x80},
+		{0x61, 0x00},
+		{0x62, 0x88},
+		{0x63, 0x11},
+		{0x64, 0x89},
+		{0x65, 0x00},
+		{0x67, 0x94},
+		{0x68, 0x7a},
+		{0x69, 0x0f},
+		{0x6c, 0x80},
+		{0x6d, 0x80},
+		{0x6e, 0x80},
+		{0x6f, 0xff},
+		{0x71, 0x20},
+		{0x74, 0x20},
+		{0x75, 0x86},
+		{0x77, 0xb5},
+		{0x17, 0x18},		/* H href start */
+		{0x18, 0xbf},		/* H href end */
+		{0x19, 0x03},		/* V start */
+		{0x1a, 0xf8},		/* V end */
+		{0x01, 0x80},		/* blue gain */
+		{0x02, 0x80},		/* red gain */
+	};
+
+	reg_w_buf(gspca_dev, reg_init, ARRAY_SIZE(reg_init));
+
+	i2c_w(gspca_dev, 0x12, 0x80);		/* sensor reset */
+	msleep(10);
+
+	i2c_w_buf(gspca_dev, sensor_init, ARRAY_SIZE(sensor_init));
+
+	reg_w(gspca_dev, TP6800_R5C_EDGE_THRLD, 0x10);
+	reg_w(gspca_dev, TP6800_R54_DARK_CFG, 0x00);
+
+	setsharpness(gspca_dev, v4l2_ctrl_g_ctrl(sd->sharpness));
+
+	bulk_w(gspca_dev, 0x03, color_gain[SENSOR_SOI763A],
+				ARRAY_SIZE(color_gain[0]));
+
+	set_led(gspca_dev, 1);
+	if (sd->sensor == SENSOR_CX0342)
+		setexposure(gspca_dev, v4l2_ctrl_g_ctrl(gspca_dev->exposure),
+			v4l2_ctrl_g_ctrl(gspca_dev->gain),
+			v4l2_ctrl_g_ctrl(sd->blue),
+			v4l2_ctrl_g_ctrl(sd->red));
+	else
+		setexposure(gspca_dev, v4l2_ctrl_g_ctrl(gspca_dev->exposure),
+			v4l2_ctrl_g_ctrl(gspca_dev->gain), 0, 0);
+	if (sd->sensor == SENSOR_SOI763A)
+		setquality(gspca_dev, v4l2_ctrl_g_ctrl(sd->jpegqual));
+	setgamma(gspca_dev, v4l2_ctrl_g_ctrl(sd->gamma));
+}
+
+static void soi763a_6810_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	static const struct cmd bridge_init_2[] = {
+		{TP6800_R7A_BLK_THRLD, 0x00},
+		{TP6800_R79_QUALITY, 0x04},
+		{TP6800_R79_QUALITY, 0x01},
+	};
+	static const struct cmd bridge_init_3[] = {
+		{TP6800_R31_PIXEL_START, 0x20},
+		{TP6800_R32_PIXEL_END_L, 0x9f},
+		{TP6800_R33_PIXEL_END_H, 0x02},
+		{TP6800_R34_LINE_START, 0x13},
+		{TP6800_R35_LINE_END_L, 0xf8},
+		{TP6800_R36_LINE_END_H, 0x01},
+	};
+	static const struct cmd bridge_init_6[] = {
+		{0x08, 0xff},
+		{0x09, 0xff},
+		{0x0a, 0x5f},
+		{0x0b, 0x80},
+	};
+
+	reg_w(gspca_dev, 0x22, gspca_dev->alt);
+	bulk_w(gspca_dev, 0x03, color_null, sizeof color_null);
+	reg_w(gspca_dev, 0x59, 0x40);
+	if (sd->sensor == SENSOR_CX0342)
+		setexposure(gspca_dev, v4l2_ctrl_g_ctrl(gspca_dev->exposure),
+			v4l2_ctrl_g_ctrl(gspca_dev->gain),
+			v4l2_ctrl_g_ctrl(sd->blue),
+			v4l2_ctrl_g_ctrl(sd->red));
+	else
+		setexposure(gspca_dev, v4l2_ctrl_g_ctrl(gspca_dev->exposure),
+			v4l2_ctrl_g_ctrl(gspca_dev->gain), 0, 0);
+	reg_w_buf(gspca_dev, bridge_init_2, ARRAY_SIZE(bridge_init_2));
+	reg_w_buf(gspca_dev, tp6810_ov_init_common,
+			ARRAY_SIZE(tp6810_ov_init_common));
+	reg_w_buf(gspca_dev, bridge_init_3, ARRAY_SIZE(bridge_init_3));
+	if (gspca_dev->curr_mode) {
+		reg_w(gspca_dev, 0x4a, 0x7f);
+		reg_w(gspca_dev, 0x07, 0x05);
+		reg_w(gspca_dev, TP6800_R78_FORMAT, 0x00);	/* vga */
+	} else {
+		reg_w(gspca_dev, 0x4a, 0xff);
+		reg_w(gspca_dev, 0x07, 0x85);
+		reg_w(gspca_dev, TP6800_R78_FORMAT, 0x01);	/* qvga */
+	}
+	setgamma(gspca_dev, v4l2_ctrl_g_ctrl(sd->gamma));
+	reg_w_buf(gspca_dev, tp6810_bridge_start,
+			ARRAY_SIZE(tp6810_bridge_start));
+
+	if (gspca_dev->curr_mode) {
+		reg_w(gspca_dev, 0x4f, 0x00);
+		reg_w(gspca_dev, 0x4e, 0x7c);
+	}
+
+	reg_w(gspca_dev, 0x00, 0x00);
+
+	setsharpness(gspca_dev, v4l2_ctrl_g_ctrl(sd->sharpness));
+	bulk_w(gspca_dev, 0x03, color_gain[SENSOR_SOI763A],
+				ARRAY_SIZE(color_gain[0]));
+	set_led(gspca_dev, 1);
+	reg_w(gspca_dev, TP6800_R3F_FRAME_RATE, 0xf0);
+	if (sd->sensor == SENSOR_CX0342)
+		setexposure(gspca_dev, v4l2_ctrl_g_ctrl(gspca_dev->exposure),
+			v4l2_ctrl_g_ctrl(gspca_dev->gain),
+			v4l2_ctrl_g_ctrl(sd->blue),
+			v4l2_ctrl_g_ctrl(sd->red));
+	else
+		setexposure(gspca_dev, v4l2_ctrl_g_ctrl(gspca_dev->exposure),
+			v4l2_ctrl_g_ctrl(gspca_dev->gain), 0, 0);
+	reg_w_buf(gspca_dev, bridge_init_6, ARRAY_SIZE(bridge_init_6));
+}
+
+/* -- start the camera -- */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	jpeg_define(sd->jpeg_hdr, gspca_dev->pixfmt.height,
+			gspca_dev->pixfmt.width);
+	set_dqt(gspca_dev, sd->quality);
+	if (sd->bridge == BRIDGE_TP6800) {
+		if (sd->sensor == SENSOR_CX0342)
+			cx0342_6800_start(gspca_dev);
+		else
+			soi763a_6800_start(gspca_dev);
+	} else {
+		if (sd->sensor == SENSOR_CX0342)
+			cx0342_6810_start(gspca_dev);
+		else
+			soi763a_6810_start(gspca_dev);
+		reg_w_buf(gspca_dev, tp6810_late_start,
+				ARRAY_SIZE(tp6810_late_start));
+		reg_w(gspca_dev, 0x80, 0x03);
+		reg_w(gspca_dev, 0x82, gspca_dev->curr_mode ? 0x0a : 0x0e);
+
+		if (sd->sensor == SENSOR_CX0342)
+			setexposure(gspca_dev,
+				v4l2_ctrl_g_ctrl(gspca_dev->exposure),
+				v4l2_ctrl_g_ctrl(gspca_dev->gain),
+				v4l2_ctrl_g_ctrl(sd->blue),
+				v4l2_ctrl_g_ctrl(sd->red));
+		else
+			setexposure(gspca_dev,
+				v4l2_ctrl_g_ctrl(gspca_dev->exposure),
+				v4l2_ctrl_g_ctrl(gspca_dev->gain), 0, 0);
+		if (sd->sensor == SENSOR_SOI763A)
+			setquality(gspca_dev,
+				   v4l2_ctrl_g_ctrl(sd->jpegqual));
+		if (sd->bridge == BRIDGE_TP6810)
+			setautogain(gspca_dev,
+				    v4l2_ctrl_g_ctrl(gspca_dev->autogain));
+	}
+
+	setframerate(gspca_dev, v4l2_ctrl_g_ctrl(gspca_dev->exposure));
+
+	return gspca_dev->usb_err;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->bridge == BRIDGE_TP6800)
+		reg_w(gspca_dev, TP6800_R2F_TIMING_CFG, 0x03);
+	set_led(gspca_dev, 0);
+	reg_w(gspca_dev, TP6800_R21_ENDP_1_CTL, 0x00);
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,
+			int len)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	/* the start of frame contains:
+	 *	ff d8
+	 *	ff fe
+	 *	width / 16
+	 *	height / 8
+	 *	quality
+	 */
+	if (sd->bridge == BRIDGE_TP6810) {
+		if (*data != 0x5a) {
+/*fixme: don't discard the whole frame..*/
+			if (*data == 0xaa || *data == 0x00)
+				return;
+			if (*data > 0xc0) {
+				gspca_dbg(gspca_dev, D_FRAM, "bad frame\n");
+				gspca_dev->last_packet_type = DISCARD_PACKET;
+				return;
+			}
+		}
+		data++;
+		len--;
+		if (len < 2) {
+			gspca_dev->last_packet_type = DISCARD_PACKET;
+			return;
+		}
+		if (*data == 0xff && data[1] == 0xd8) {
+/*fixme: there may be information in the 4 high bits*/
+			if (len < 7) {
+				gspca_dev->last_packet_type = DISCARD_PACKET;
+				return;
+			}
+			if ((data[6] & 0x0f) != sd->quality)
+				set_dqt(gspca_dev, data[6] & 0x0f);
+			gspca_frame_add(gspca_dev, FIRST_PACKET,
+					sd->jpeg_hdr, JPEG_HDR_SZ);
+			gspca_frame_add(gspca_dev, INTER_PACKET,
+					data + 7, len - 7);
+		} else if (data[len - 2] == 0xff && data[len - 1] == 0xd9) {
+			gspca_frame_add(gspca_dev, LAST_PACKET,
+					data, len);
+		} else {
+			gspca_frame_add(gspca_dev, INTER_PACKET,
+					data, len);
+		}
+		return;
+	}
+
+	switch (*data) {
+	case 0x55:
+		gspca_frame_add(gspca_dev, LAST_PACKET, data, 0);
+
+		if (len < 8
+		 || data[1] != 0xff || data[2] != 0xd8
+		 || data[3] != 0xff || data[4] != 0xfe) {
+
+			/* Have only seen this with corrupt frames */
+			gspca_dev->last_packet_type = DISCARD_PACKET;
+			return;
+		}
+		if (data[7] != sd->quality)
+			set_dqt(gspca_dev, data[7]);
+		gspca_frame_add(gspca_dev, FIRST_PACKET,
+				sd->jpeg_hdr, JPEG_HDR_SZ);
+		gspca_frame_add(gspca_dev, INTER_PACKET,
+				data + 8, len - 8);
+		break;
+	case 0xaa:
+		gspca_dev->last_packet_type = DISCARD_PACKET;
+		break;
+	case 0xcc:
+		if (len >= 3 && (data[1] != 0xff || data[2] != 0xd8))
+			gspca_frame_add(gspca_dev, INTER_PACKET,
+					data + 1, len - 1);
+		else
+			gspca_dev->last_packet_type = DISCARD_PACKET;
+		break;
+	}
+}
+
+static void sd_dq_callback(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int ret, alen;
+	int luma, expo;
+
+	if (sd->ag_cnt < 0)
+		return;
+	if (--sd->ag_cnt > 5)
+		return;
+	switch (sd->ag_cnt) {
+/*	case 5: */
+	default:
+		reg_w(gspca_dev, 0x7d, 0x00);
+		break;
+	case 4:
+		reg_w(gspca_dev, 0x27, 0xb0);
+		break;
+	case 3:
+		reg_w(gspca_dev, 0x0c, 0x01);
+		break;
+	case 2:
+		ret = usb_bulk_msg(gspca_dev->dev,
+				usb_rcvbulkpipe(gspca_dev->dev, 0x02),
+				gspca_dev->usb_buf,
+				32,
+				&alen,
+				500);
+		if (ret < 0) {
+			pr_err("bulk err %d\n", ret);
+			break;
+		}
+		/* values not used (unknown) */
+		break;
+	case 1:
+		reg_w(gspca_dev, 0x27, 0xd0);
+		break;
+	case 0:
+		ret = usb_bulk_msg(gspca_dev->dev,
+				usb_rcvbulkpipe(gspca_dev->dev, 0x02),
+				gspca_dev->usb_buf,
+				32,
+				&alen,
+				500);
+		if (ret < 0) {
+			pr_err("bulk err %d\n", ret);
+			break;
+		}
+		luma = ((gspca_dev->usb_buf[8] << 8) + gspca_dev->usb_buf[7] +
+			(gspca_dev->usb_buf[11] << 8) + gspca_dev->usb_buf[10] +
+			(gspca_dev->usb_buf[14] << 8) + gspca_dev->usb_buf[13] +
+			(gspca_dev->usb_buf[17] << 8) + gspca_dev->usb_buf[16] +
+			(gspca_dev->usb_buf[20] << 8) + gspca_dev->usb_buf[19] +
+			(gspca_dev->usb_buf[23] << 8) + gspca_dev->usb_buf[22] +
+			(gspca_dev->usb_buf[26] << 8) + gspca_dev->usb_buf[25] +
+			(gspca_dev->usb_buf[29] << 8) + gspca_dev->usb_buf[28])
+				/ 8;
+		if (gspca_dev->pixfmt.width == 640)
+			luma /= 4;
+		reg_w(gspca_dev, 0x7d, 0x00);
+
+		expo = v4l2_ctrl_g_ctrl(gspca_dev->exposure);
+		ret = gspca_expo_autogain(gspca_dev, luma,
+				60,	/* desired luma */
+				6,	/* dead zone */
+				2,	/* gain knee */
+				70);	/* expo knee */
+		sd->ag_cnt = AG_CNT_START;
+		if (sd->bridge == BRIDGE_TP6810) {
+			int new_expo = v4l2_ctrl_g_ctrl(gspca_dev->exposure);
+
+			if ((expo >= 128 && new_expo < 128)
+			 || (expo < 128 && new_expo >= 128))
+				setframerate(gspca_dev, new_expo);
+		}
+		break;
+	}
+}
+
+/* get stream parameters (framerate) */
+static void sd_get_streamparm(struct gspca_dev *gspca_dev,
+			     struct v4l2_streamparm *parm)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct v4l2_captureparm *cp = &parm->parm.capture;
+	struct v4l2_fract *tpf = &cp->timeperframe;
+	int fr, i;
+
+	tpf->numerator = 1;
+	i = get_fr_idx(gspca_dev);
+	if (i & 0x80) {
+		if (sd->bridge == BRIDGE_TP6800)
+			fr = rates[6 - (i & 0x07)];
+		else
+			fr = rates_6810[7 - (i & 0x07)];
+	} else {
+		fr = rates[6 - i];
+	}
+	tpf->denominator = fr;
+}
+
+/* set stream parameters (framerate) */
+static void sd_set_streamparm(struct gspca_dev *gspca_dev,
+			     struct v4l2_streamparm *parm)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct v4l2_captureparm *cp = &parm->parm.capture;
+	struct v4l2_fract *tpf = &cp->timeperframe;
+	int fr, i;
+
+	if (tpf->numerator == 0 || tpf->denominator == 0)
+		sd->framerate = DEFAULT_FRAME_RATE;
+	else
+		sd->framerate = tpf->denominator / tpf->numerator;
+
+	if (gspca_dev->streaming)
+		setframerate(gspca_dev, v4l2_ctrl_g_ctrl(gspca_dev->exposure));
+
+	/* Return the actual framerate */
+	i = get_fr_idx(gspca_dev);
+	if (i & 0x80)
+		fr = rates_6810[7 - (i & 0x07)];
+	else
+		fr = rates[6 - i];
+	tpf->numerator = 1;
+	tpf->denominator = fr;
+}
+
+static int sd_set_jcomp(struct gspca_dev *gspca_dev,
+			const struct v4l2_jpegcompression *jcomp)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->sensor != SENSOR_SOI763A)
+		return -ENOTTY;
+	v4l2_ctrl_s_ctrl(sd->jpegqual, jcomp->quality);
+	return 0;
+}
+
+static int sd_get_jcomp(struct gspca_dev *gspca_dev,
+			struct v4l2_jpegcompression *jcomp)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->sensor != SENSOR_SOI763A)
+		return -ENOTTY;
+	memset(jcomp, 0, sizeof *jcomp);
+	jcomp->quality = v4l2_ctrl_g_ctrl(sd->jpegqual);
+	jcomp->jpeg_markers = V4L2_JPEG_MARKER_DHT
+			| V4L2_JPEG_MARKER_DQT;
+	return 0;
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *)gspca_dev;
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_SHARPNESS:
+		setsharpness(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_GAMMA:
+		setgamma(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_BLUE_BALANCE:
+		setbgain(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_RED_BALANCE:
+		setrgain(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_EXPOSURE:
+		sd_setgain(gspca_dev);
+		break;
+	case V4L2_CID_AUTOGAIN:
+		if (ctrl->val)
+			break;
+		sd_setgain(gspca_dev);
+		break;
+	case V4L2_CID_JPEG_COMPRESSION_QUALITY:
+		jpeg_set_qual(sd->jpeg_hdr, ctrl->val);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *)gspca_dev;
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 4);
+	gspca_dev->exposure = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_EXPOSURE, 1, 0xdc, 1, 0x4e);
+	if (sd->sensor == SENSOR_CX0342) {
+		sd->red = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_RED_BALANCE, 0, 4095, 1, 256);
+		sd->blue = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BLUE_BALANCE, 0, 4095, 1, 256);
+	}
+	if (sd->sensor == SENSOR_SOI763A)
+		gspca_dev->gain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_GAIN, 0, 15, 1, 3);
+	else
+		gspca_dev->gain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_GAIN, 0, 4095, 1, 256);
+	sd->sharpness = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_SHARPNESS, 0, 3, 1, 2);
+	sd->gamma = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_GAMMA, 0, NGAMMA - 1, 1,
+			(sd->sensor == SENSOR_SOI763A &&
+			 sd->bridge == BRIDGE_TP6800) ? 0 : 1);
+	if (sd->bridge == BRIDGE_TP6810)
+		gspca_dev->autogain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
+	if (sd->sensor == SENSOR_SOI763A)
+		sd->jpegqual = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_JPEG_COMPRESSION_QUALITY,
+			0, 15, 1, (sd->bridge == BRIDGE_TP6810) ? 0 : 13);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	if (gspca_dev->autogain)
+		v4l2_ctrl_auto_cluster(3, &gspca_dev->autogain, 0, false);
+	else
+		v4l2_ctrl_cluster(2, &gspca_dev->exposure);
+	return 0;
+}
+
+static const struct sd_desc sd_desc = {
+	.name = KBUILD_MODNAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.isoc_init = sd_isoc_init,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.pkt_scan = sd_pkt_scan,
+	.dq_callback = sd_dq_callback,
+	.get_streamparm = sd_get_streamparm,
+	.set_streamparm = sd_set_streamparm,
+	.get_jcomp = sd_get_jcomp,
+	.set_jcomp = sd_set_jcomp,
+};
+
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x06a2, 0x0003), .driver_info = BRIDGE_TP6800},
+	{USB_DEVICE(0x06a2, 0x6810), .driver_info = BRIDGE_TP6810},
+	{}			/* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+static int sd_probe(struct usb_interface *interface,
+		    const struct usb_device_id *id)
+{
+	return gspca_dev_probe(interface, id, &sd_desc, sizeof(struct sd),
+			       THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = KBUILD_MODNAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
+
+module_param(force_sensor, int, 0644);
+MODULE_PARM_DESC(force_sensor,
+	"Force sensor. 0: cx0342, 1: soi763a");
diff --git a/drivers/media/usb/gspca/touptek.c b/drivers/media/usb/gspca/touptek.c
new file mode 100644
index 0000000..d1b9032
--- /dev/null
+++ b/drivers/media/usb/gspca/touptek.c
@@ -0,0 +1,736 @@
+/*
+ * ToupTek UCMOS / AmScope MU series camera driver
+ * TODO: contrast with ScopeTek / AmScope MDC cameras
+ *
+ * Copyright (C) 2012-2014 John McMaster <JohnDMcMaster@gmail.com>
+ *
+ * Special thanks to Bushing for helping with the decrypt algorithm and
+ * Sean O'Sullivan / the Rensselaer Center for Open Source
+ * Software (RCOS) for helping me learn kernel development
+ *
+ * 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
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include "gspca.h"
+
+#define MODULE_NAME "touptek"
+
+MODULE_AUTHOR("John McMaster");
+MODULE_DESCRIPTION("ToupTek UCMOS / Amscope MU microscope camera driver");
+MODULE_LICENSE("GPL");
+
+/*
+ * Exposure reg is linear with exposure time
+ * Exposure (sec), E (reg)
+ * 0.000400, 0x0002
+ * 0.001000, 0x0005
+ * 0.005000, 0x0019
+ * 0.020000, 0x0064
+ * 0.080000, 0x0190
+ * 0.400000, 0x07D0
+ * 1.000000, 0x1388
+ * 2.000000, 0x2710
+ *
+ * Three gain stages
+ * 0x1000: master channel enable bit
+ * 0x007F: low gain bits
+ * 0x0080: medium gain bit
+ * 0x0100: high gain bit
+ * gain = enable * (1 + regH) * (1 + regM) * z * regL
+ *
+ * Gain implementation
+ * Want to do something similar to mt9v011.c's set_balance
+ *
+ * Gain does not vary with resolution (checked 640x480 vs 1600x1200)
+ *
+ * Constant derivation:
+ *
+ * Raw data:
+ * Gain,   GTOP,   B,	  R,	  GBOT
+ * 1.00,   0x105C, 0x1068, 0x10C8, 0x105C
+ * 1.20,   0x106E, 0x107E, 0x10D6, 0x106E
+ * 1.40,   0x10C0, 0x10CA, 0x10E5, 0x10C0
+ * 1.60,   0x10C9, 0x10D4, 0x10F3, 0x10C9
+ * 1.80,   0x10D2, 0x10DE, 0x11C1, 0x10D2
+ * 2.00,   0x10DC, 0x10E9, 0x11C8, 0x10DC
+ * 2.20,   0x10E5, 0x10F3, 0x11CF, 0x10E5
+ * 2.40,   0x10EE, 0x10FE, 0x11D7, 0x10EE
+ * 2.60,   0x10F7, 0x11C4, 0x11DE, 0x10F7
+ * 2.80,   0x11C0, 0x11CA, 0x11E5, 0x11C0
+ * 3.00,   0x11C5, 0x11CF, 0x11ED, 0x11C5
+ *
+ * zR = 0.0069605943152454778
+ *	about 3/431 = 0.0069605568445475635
+ * zB = 0.0095695970695970703
+ *	about 6/627 = 0.0095693779904306216
+ * zG = 0.010889328063241107
+ *	about 6/551 = 0.010889292196007259
+ * about 10 bits for constant + 7 bits for value => at least 17 bit
+ * intermediate with 32 bit ints should be fine for overflow etc
+ * Essentially gains are in range 0-0x001FF
+ *
+ * However, V4L expects a main gain channel + R and B balance
+ * To keep things simple for now saturate the values of balance is too high/low
+ * This isn't really ideal but easy way to fit the Linux model
+ *
+ * Converted using gain model turns out to be quite linear:
+ * Gain, GTOP, B, R, GBOT
+ * 1.00, 92, 104, 144, 92
+ * 1.20, 110, 126, 172, 110
+ * 1.40, 128, 148, 202, 128
+ * 1.60, 146, 168, 230, 146
+ * 1.80, 164, 188, 260, 164
+ * 2.00, 184, 210, 288, 184
+ * 2.20, 202, 230, 316, 202
+ * 2.40, 220, 252, 348, 220
+ * 2.60, 238, 272, 376, 238
+ * 2.80, 256, 296, 404, 256
+ * 3.00, 276, 316, 436, 276
+ *
+ * Maximum gain is 0x7FF * 2 * 2 => 0x1FFC (8188)
+ * or about 13 effective bits of gain
+ * The highest the commercial driver goes in my setup 436
+ * However, because could *maybe* damage circuits
+ * limit the gain until have a reason to go higher
+ * Solution: gain clipped and warning emitted
+ */
+#define GAIN_MAX		511
+
+/* Frame sync is a short read */
+#define BULK_SIZE		0x4000
+
+/* MT9E001 reg names to give a rough approximation */
+#define REG_COARSE_INTEGRATION_TIME_	0x3012
+#define REG_GROUPED_PARAMETER_HOLD_	0x3022
+#define REG_MODE_SELECT			0x0100
+#define REG_OP_SYS_CLK_DIV		0x030A
+#define REG_VT_SYS_CLK_DIV		0x0302
+#define REG_PRE_PLL_CLK_DIV		0x0304
+#define REG_VT_PIX_CLK_DIV		0x0300
+#define REG_OP_PIX_CLK_DIV		0x0308
+#define REG_PLL_MULTIPLIER		0x0306
+#define REG_COARSE_INTEGRATION_TIME_	0x3012
+#define REG_FRAME_LENGTH_LINES		0x0340
+#define REG_FRAME_LENGTH_LINES_		0x300A
+#define REG_GREEN1_GAIN			0x3056
+#define REG_GREEN2_GAIN			0x305C
+#define REG_GROUPED_PARAMETER_HOLD	0x0104
+#define REG_LINE_LENGTH_PCK_		0x300C
+#define REG_MODE_SELECT			0x0100
+#define REG_PLL_MULTIPLIER		0x0306
+#define REG_READ_MODE			0x3040
+#define REG_BLUE_GAIN			0x3058
+#define REG_RED_GAIN			0x305A
+#define REG_RESET_REGISTER		0x301A
+#define REG_SCALE_M			0x0404
+#define REG_SCALING_MODE		0x0400
+#define REG_SOFTWARE_RESET		0x0103
+#define REG_X_ADDR_END			0x0348
+#define REG_X_ADDR_START		0x0344
+#define REG_X_ADDR_START		0x0344
+#define REG_X_OUTPUT_SIZE		0x034C
+#define REG_Y_ADDR_END			0x034A
+#define REG_Y_ADDR_START		0x0346
+#define REG_Y_OUTPUT_SIZE		0x034E
+
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+	/* How many bytes this frame */
+	unsigned int this_f;
+
+	/*
+	Device has separate gains for each Bayer quadrant
+	V4L supports master gain which is referenced to G1/G2 and supplies
+	individual balance controls for R/B
+	*/
+	struct v4l2_ctrl *blue;
+	struct v4l2_ctrl *red;
+};
+
+/* Used to simplify reg write error handling */
+struct cmd {
+	u16 value;
+	u16 index;
+};
+
+static const struct v4l2_pix_format vga_mode[] = {
+	{800, 600,
+		V4L2_PIX_FMT_SGRBG8,
+		V4L2_FIELD_NONE,
+		.bytesperline = 800,
+		.sizeimage = 800 * 600,
+		.colorspace = V4L2_COLORSPACE_SRGB},
+	{1600, 1200,
+		V4L2_PIX_FMT_SGRBG8,
+		V4L2_FIELD_NONE,
+		.bytesperline = 1600,
+		.sizeimage = 1600 * 1200,
+		.colorspace = V4L2_COLORSPACE_SRGB},
+	{3264, 2448,
+		V4L2_PIX_FMT_SGRBG8,
+		V4L2_FIELD_NONE,
+		.bytesperline = 3264,
+		.sizeimage = 3264 * 2448,
+		.colorspace = V4L2_COLORSPACE_SRGB},
+};
+
+/*
+ * As theres no known frame sync, the only way to keep synced is to try hard
+ * to never miss any packets
+ */
+#if MAX_NURBS < 4
+#error "Not enough URBs in the gspca table"
+#endif
+
+static int val_reply(struct gspca_dev *gspca_dev, const char *reply, int rc)
+{
+	if (rc < 0) {
+		gspca_err(gspca_dev, "reply has error %d\n", rc);
+		return -EIO;
+	}
+	if (rc != 1) {
+		gspca_err(gspca_dev, "Bad reply size %d\n", rc);
+		return -EIO;
+	}
+	if (reply[0] != 0x08) {
+		gspca_err(gspca_dev, "Bad reply 0x%02x\n", (int)reply[0]);
+		return -EIO;
+	}
+	return 0;
+}
+
+static void reg_w(struct gspca_dev *gspca_dev, u16 value, u16 index)
+{
+	char *buff = gspca_dev->usb_buf;
+	int rc;
+
+	gspca_dbg(gspca_dev, D_USBO,
+		  "reg_w bReq=0x0B, bReqT=0xC0, wVal=0x%04X, wInd=0x%04X\n\n",
+		  value, index);
+	rc = usb_control_msg(gspca_dev->dev, usb_rcvctrlpipe(gspca_dev->dev, 0),
+		0x0B, 0xC0, value, index, buff, 1, 500);
+	gspca_dbg(gspca_dev, D_USBO, "rc=%d, ret={0x%02x}\n", rc, (int)buff[0]);
+	if (rc < 0) {
+		gspca_err(gspca_dev, "Failed reg_w(0x0B, 0xC0, 0x%04X, 0x%04X) w/ rc %d\n",
+			  value, index, rc);
+		gspca_dev->usb_err = rc;
+		return;
+	}
+	if (val_reply(gspca_dev, buff, rc)) {
+		gspca_err(gspca_dev, "Bad reply to reg_w(0x0B, 0xC0, 0x%04X, 0x%04X\n",
+			  value, index);
+		gspca_dev->usb_err = -EIO;
+	}
+}
+
+static void reg_w_buf(struct gspca_dev *gspca_dev,
+		const struct cmd *p, int l)
+{
+	do {
+		reg_w(gspca_dev, p->value, p->index);
+		p++;
+	} while (--l > 0);
+}
+
+static void setexposure(struct gspca_dev *gspca_dev, s32 val)
+{
+	u16 value;
+	unsigned int w = gspca_dev->pixfmt.width;
+
+	if (w == 800)
+		value = val * 5;
+	else if (w == 1600)
+		value = val * 3;
+	else if (w == 3264)
+		value = val * 3 / 2;
+	else {
+		gspca_err(gspca_dev, "Invalid width %u\n", w);
+		gspca_dev->usb_err = -EINVAL;
+		return;
+	}
+	gspca_dbg(gspca_dev, D_STREAM, "exposure: 0x%04X ms\n\n", value);
+	/* Wonder if theres a good reason for sending it twice */
+	/* probably not but leave it in because...why not */
+	reg_w(gspca_dev, value, REG_COARSE_INTEGRATION_TIME_);
+	reg_w(gspca_dev, value, REG_COARSE_INTEGRATION_TIME_);
+}
+
+static int gainify(int in)
+{
+	/*
+	 * TODO: check if there are any issues with corner cases
+	 * 0x000 (0):0x07F (127): regL
+	 * 0x080 (128) - 0x0FF (255): regM, regL
+	 * 0x100 (256) - max: regH, regM, regL
+	 */
+	if (in <= 0x7F)
+		return 0x1000 | in;
+	else if (in <= 0xFF)
+		return 0x1080 | in / 2;
+	else
+		return 0x1180 | in / 4;
+}
+
+static void setggain(struct gspca_dev *gspca_dev, u16 global_gain)
+{
+	u16 normalized;
+
+	normalized = gainify(global_gain);
+	gspca_dbg(gspca_dev, D_STREAM, "gain G1/G2 (0x%04X): 0x%04X (src 0x%04X)\n\n",
+		  REG_GREEN1_GAIN,
+		  normalized, global_gain);
+
+	reg_w(gspca_dev, normalized, REG_GREEN1_GAIN);
+	reg_w(gspca_dev, normalized, REG_GREEN2_GAIN);
+}
+
+static void setbgain(struct gspca_dev *gspca_dev,
+		u16 gain, u16 global_gain)
+{
+	u16 normalized;
+
+	normalized = global_gain +
+		((u32)global_gain) * gain / GAIN_MAX;
+	if (normalized > GAIN_MAX) {
+		gspca_dbg(gspca_dev, D_STREAM, "Truncating blue 0x%04X w/ value 0x%04X\n\n",
+			  GAIN_MAX, normalized);
+		normalized = GAIN_MAX;
+	}
+	normalized = gainify(normalized);
+	gspca_dbg(gspca_dev, D_STREAM, "gain B (0x%04X): 0x%04X w/ source 0x%04X\n\n",
+		  REG_BLUE_GAIN, normalized, gain);
+
+	reg_w(gspca_dev, normalized, REG_BLUE_GAIN);
+}
+
+static void setrgain(struct gspca_dev *gspca_dev,
+		u16 gain, u16 global_gain)
+{
+	u16 normalized;
+
+	normalized = global_gain +
+		((u32)global_gain) * gain / GAIN_MAX;
+	if (normalized > GAIN_MAX) {
+		gspca_dbg(gspca_dev, D_STREAM, "Truncating gain 0x%04X w/ value 0x%04X\n\n",
+			  GAIN_MAX, normalized);
+		normalized = GAIN_MAX;
+	}
+	normalized = gainify(normalized);
+	gspca_dbg(gspca_dev, D_STREAM, "gain R (0x%04X): 0x%04X w / source 0x%04X\n\n",
+		  REG_RED_GAIN, normalized, gain);
+
+	reg_w(gspca_dev, normalized, REG_RED_GAIN);
+}
+
+static void configure_wh(struct gspca_dev *gspca_dev)
+{
+	unsigned int w = gspca_dev->pixfmt.width;
+
+	gspca_dbg(gspca_dev, D_STREAM, "configure_wh\n\n");
+
+	if (w == 800) {
+		static const struct cmd reg_init_res[] = {
+			{0x0060, REG_X_ADDR_START},
+			{0x0CD9, REG_X_ADDR_END},
+			{0x0036, REG_Y_ADDR_START},
+			{0x098F, REG_Y_ADDR_END},
+			{0x07C7, REG_READ_MODE},
+		};
+
+		reg_w_buf(gspca_dev,
+			       reg_init_res, ARRAY_SIZE(reg_init_res));
+	} else if (w == 1600) {
+		static const struct cmd reg_init_res[] = {
+			{0x009C, REG_X_ADDR_START},
+			{0x0D19, REG_X_ADDR_END},
+			{0x0068, REG_Y_ADDR_START},
+			{0x09C5, REG_Y_ADDR_END},
+			{0x06C3, REG_READ_MODE},
+		};
+
+		reg_w_buf(gspca_dev,
+			       reg_init_res, ARRAY_SIZE(reg_init_res));
+	} else if (w == 3264) {
+		static const struct cmd reg_init_res[] = {
+			{0x00E8, REG_X_ADDR_START},
+			{0x0DA7, REG_X_ADDR_END},
+			{0x009E, REG_Y_ADDR_START},
+			{0x0A2D, REG_Y_ADDR_END},
+			{0x0241, REG_READ_MODE},
+		};
+
+		reg_w_buf(gspca_dev,
+			       reg_init_res, ARRAY_SIZE(reg_init_res));
+	} else {
+		gspca_err(gspca_dev, "bad width %u\n", w);
+		gspca_dev->usb_err = -EINVAL;
+		return;
+	}
+
+	reg_w(gspca_dev, 0x0000, REG_SCALING_MODE);
+	reg_w(gspca_dev, 0x0010, REG_SCALE_M);
+	reg_w(gspca_dev, w, REG_X_OUTPUT_SIZE);
+	reg_w(gspca_dev, gspca_dev->pixfmt.height, REG_Y_OUTPUT_SIZE);
+
+	if (w == 800) {
+		reg_w(gspca_dev, 0x0384, REG_FRAME_LENGTH_LINES_);
+		reg_w(gspca_dev, 0x0960, REG_LINE_LENGTH_PCK_);
+	} else if (w == 1600) {
+		reg_w(gspca_dev, 0x0640, REG_FRAME_LENGTH_LINES_);
+		reg_w(gspca_dev, 0x0FA0, REG_LINE_LENGTH_PCK_);
+	} else if (w == 3264) {
+		reg_w(gspca_dev, 0x0B4B, REG_FRAME_LENGTH_LINES_);
+		reg_w(gspca_dev, 0x1F40, REG_LINE_LENGTH_PCK_);
+	} else {
+		gspca_err(gspca_dev, "bad width %u\n", w);
+		gspca_dev->usb_err = -EINVAL;
+		return;
+	}
+}
+
+/* Packets that were encrypted, no idea if the grouping is significant */
+static void configure_encrypted(struct gspca_dev *gspca_dev)
+{
+	static const struct cmd reg_init_begin[] = {
+		{0x0100, REG_SOFTWARE_RESET},
+		{0x0000, REG_MODE_SELECT},
+		{0x0100, REG_GROUPED_PARAMETER_HOLD},
+		{0x0004, REG_VT_PIX_CLK_DIV},
+		{0x0001, REG_VT_SYS_CLK_DIV},
+		{0x0008, REG_OP_PIX_CLK_DIV},
+		{0x0001, REG_OP_SYS_CLK_DIV},
+		{0x0004, REG_PRE_PLL_CLK_DIV},
+		{0x0040, REG_PLL_MULTIPLIER},
+		{0x0000, REG_GROUPED_PARAMETER_HOLD},
+		{0x0100, REG_GROUPED_PARAMETER_HOLD},
+	};
+	static const struct cmd reg_init_end[] = {
+		{0x0000, REG_GROUPED_PARAMETER_HOLD},
+		{0x0301, 0x31AE},
+		{0x0805, 0x3064},
+		{0x0071, 0x3170},
+		{0x10DE, REG_RESET_REGISTER},
+		{0x0000, REG_MODE_SELECT},
+		{0x0010, REG_PLL_MULTIPLIER},
+		{0x0100, REG_MODE_SELECT},
+	};
+
+	gspca_dbg(gspca_dev, D_STREAM, "Encrypted begin, w = %u\n\n",
+		  gspca_dev->pixfmt.width);
+	reg_w_buf(gspca_dev, reg_init_begin, ARRAY_SIZE(reg_init_begin));
+	configure_wh(gspca_dev);
+	reg_w_buf(gspca_dev, reg_init_end, ARRAY_SIZE(reg_init_end));
+	reg_w(gspca_dev, 0x0100, REG_GROUPED_PARAMETER_HOLD);
+	reg_w(gspca_dev, 0x0000, REG_GROUPED_PARAMETER_HOLD);
+
+	gspca_dbg(gspca_dev, D_STREAM, "Encrypted end\n\n");
+}
+
+static int configure(struct gspca_dev *gspca_dev)
+{
+	int rc;
+	char *buff = gspca_dev->usb_buf;
+
+	gspca_dbg(gspca_dev, D_STREAM, "configure()\n\n");
+
+	/*
+	 * First driver sets a sort of encryption key
+	 * A number of futur requests of this type have wValue and wIndex
+	 * encrypted as follows:
+	 * -Compute key = this wValue rotate left by 4 bits
+	 *	(decrypt.py rotates right because we are decrypting)
+	 * -Later packets encrypt packets by XOR'ing with key
+	 *	XOR encrypt/decrypt is symmetrical
+	 *	wValue, and wIndex are encrypted
+	 *	bRequest is not and bRequestType is always 0xC0
+	 *		This allows resyncing if key is unknown?
+	 * By setting 0 we XOR with 0 and the shifting and XOR drops out
+	 */
+	rc = usb_control_msg(gspca_dev->dev, usb_rcvctrlpipe(gspca_dev->dev, 0),
+			     0x16, 0xC0, 0x0000, 0x0000, buff, 2, 500);
+	if (val_reply(gspca_dev, buff, rc)) {
+		gspca_err(gspca_dev, "failed key req\n");
+		return -EIO;
+	}
+
+	/*
+	 * Next does some sort of 2 packet challenge / response
+	 * evidence suggests its an Atmel I2C crypto part but nobody cares to
+	 * look
+	 * (to make sure its not cloned hardware?)
+	 * Ignore: I want to work with their hardware, not clone it
+	 * 16 bytes out challenge, requestType: 0x40
+	 * 16 bytes in response, requestType: 0xC0
+	 */
+
+	rc = usb_control_msg(gspca_dev->dev, usb_sndctrlpipe(gspca_dev->dev, 0),
+			     0x01, 0x40, 0x0001, 0x000F, NULL, 0, 500);
+	if (rc < 0) {
+		gspca_err(gspca_dev, "failed to replay packet 176 w/ rc %d\n",
+			  rc);
+		return rc;
+	}
+
+	rc = usb_control_msg(gspca_dev->dev, usb_sndctrlpipe(gspca_dev->dev, 0),
+			     0x01, 0x40, 0x0000, 0x000F, NULL, 0, 500);
+	if (rc < 0) {
+		gspca_err(gspca_dev, "failed to replay packet 178 w/ rc %d\n",
+			  rc);
+		return rc;
+	}
+
+	rc = usb_control_msg(gspca_dev->dev, usb_sndctrlpipe(gspca_dev->dev, 0),
+			     0x01, 0x40, 0x0001, 0x000F, NULL, 0, 500);
+	if (rc < 0) {
+		gspca_err(gspca_dev, "failed to replay packet 180 w/ rc %d\n",
+			  rc);
+		return rc;
+	}
+
+	/*
+	 * Serial number?  Doesn't seem to be required
+	 * cam1: \xE6\x0D\x00\x00, cam2: \x70\x19\x00\x00
+	 * rc = usb_control_msg(gspca_dev->dev,
+	 *			usb_rcvctrlpipe(gspca_dev->dev, 0),
+	 *			0x20, 0xC0, 0x0000, 0x0000, buff, 4, 500);
+	 */
+
+	/* Large (EEPROM?) read, skip it since no idea what to do with it */
+	gspca_dev->usb_err = 0;
+	configure_encrypted(gspca_dev);
+	if (gspca_dev->usb_err)
+		return gspca_dev->usb_err;
+
+	/* Omitted this by accident, does not work without it */
+	rc = usb_control_msg(gspca_dev->dev, usb_sndctrlpipe(gspca_dev->dev, 0),
+			     0x01, 0x40, 0x0003, 0x000F, NULL, 0, 500);
+	if (rc < 0) {
+		gspca_err(gspca_dev, "failed to replay final packet w/ rc %d\n",
+			  rc);
+		return rc;
+	}
+
+	gspca_dbg(gspca_dev, D_STREAM, "Configure complete\n\n");
+	return 0;
+}
+
+static int sd_config(struct gspca_dev *gspca_dev,
+		     const struct usb_device_id *id)
+{
+	gspca_dev->cam.cam_mode = vga_mode;
+	gspca_dev->cam.nmodes = ARRAY_SIZE(vga_mode);
+
+	/* Yes we want URBs and we want them now! */
+	gspca_dev->cam.no_urb_create = 0;
+	gspca_dev->cam.bulk_nurbs = 4;
+	/* Largest size the windows driver uses */
+	gspca_dev->cam.bulk_size = BULK_SIZE;
+	/* Def need to use bulk transfers */
+	gspca_dev->cam.bulk = 1;
+
+	return 0;
+}
+
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int rc;
+
+	sd->this_f = 0;
+
+	rc = configure(gspca_dev);
+	if (rc < 0) {
+		gspca_err(gspca_dev, "Failed configure\n");
+		return rc;
+	}
+	/* First two frames have messed up gains
+	Drop them to avoid special cases in user apps? */
+	return 0;
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,	/* isoc packet */
+			int len)	/* iso packet length */
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (len != BULK_SIZE) {
+		/* can we finish a frame? */
+		if (sd->this_f + len == gspca_dev->pixfmt.sizeimage) {
+			gspca_frame_add(gspca_dev, LAST_PACKET, data, len);
+			gspca_dbg(gspca_dev, D_FRAM, "finish frame sz %u/%u w/ len %u\n\n",
+				  sd->this_f, gspca_dev->pixfmt.sizeimage, len);
+		/* lost some data, discard the frame */
+		} else {
+			gspca_frame_add(gspca_dev, DISCARD_PACKET, NULL, 0);
+			gspca_dbg(gspca_dev, D_FRAM, "abort frame sz %u/%u w/ len %u\n\n",
+				  sd->this_f, gspca_dev->pixfmt.sizeimage, len);
+		}
+		sd->this_f = 0;
+	} else {
+		if (sd->this_f == 0)
+			gspca_frame_add(gspca_dev, FIRST_PACKET, data, len);
+		else
+			gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+		sd->this_f += len;
+	}
+}
+
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	return 0;
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_EXPOSURE:
+		setexposure(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_GAIN:
+		/* gspca_dev->gain automatically updated */
+		setggain(gspca_dev, gspca_dev->gain->val);
+		break;
+	case V4L2_CID_BLUE_BALANCE:
+		sd->blue->val = ctrl->val;
+		setbgain(gspca_dev, sd->blue->val, gspca_dev->gain->val);
+		break;
+	case V4L2_CID_RED_BALANCE:
+		sd->red->val = ctrl->val;
+		setrgain(gspca_dev, sd->red->val, gspca_dev->gain->val);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 4);
+
+	gspca_dev->exposure = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+	/* Mostly limited by URB timeouts */
+	/* XXX: make dynamic based on frame rate? */
+		V4L2_CID_EXPOSURE, 0, 800, 1, 350);
+	gspca_dev->gain = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_GAIN, 0, 511, 1, 128);
+	sd->blue = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BLUE_BALANCE, 0, 1023, 1, 80);
+	sd->red = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_RED_BALANCE, 0, 1023, 1, 295);
+
+	if (hdl->error) {
+		gspca_err(gspca_dev, "Could not initialize controls\n");
+		return hdl->error;
+	}
+	return 0;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.start = sd_start,
+	.pkt_scan = sd_pkt_scan,
+};
+
+/* Table of supported USB devices */
+static const struct usb_device_id device_table[] = {
+	/* Commented out devices should be related */
+	/* AS: AmScope, TT: ToupTek */
+	/* { USB_DEVICE(0x0547, 0x6035) },  TT UCMOS00350KPA */
+	/* { USB_DEVICE(0x0547, 0x6130) },  TT UCMOS01300KPA */
+	/* { USB_DEVICE(0x0547, 0x6200) },  TT UCMOS02000KPA */
+	/* { USB_DEVICE(0x0547, 0x6310) },  TT UCMOS03100KPA */
+	/* { USB_DEVICE(0x0547, 0x6510) },  TT UCMOS05100KPA */
+	/* { USB_DEVICE(0x0547, 0x6800) },  TT UCMOS08000KPA */
+	/* { USB_DEVICE(0x0547, 0x6801) },  TT UCMOS08000KPB */
+	{ USB_DEVICE(0x0547, 0x6801) }, /* TT UCMOS08000KPB, AS MU800 */
+	/* { USB_DEVICE(0x0547, 0x6900) },  TT UCMOS09000KPA */
+	/* { USB_DEVICE(0x0547, 0x6901) },  TT UCMOS09000KPB */
+	/* { USB_DEVICE(0x0547, 0x6010) },  TT UCMOS10000KPA */
+	/* { USB_DEVICE(0x0547, 0x6014) },  TT UCMOS14000KPA */
+	/* { USB_DEVICE(0x0547, 0x6131) },  TT UCMOS01300KMA */
+	/* { USB_DEVICE(0x0547, 0x6511) },  TT UCMOS05100KMA */
+	/* { USB_DEVICE(0x0547, 0x8080) },  TT UHCCD00800KPA */
+	/* { USB_DEVICE(0x0547, 0x8140) },  TT UHCCD01400KPA */
+	/* { USB_DEVICE(0x0547, 0x8141) },  TT EXCCD01400KPA */
+	/* { USB_DEVICE(0x0547, 0x8200) },  TT UHCCD02000KPA */
+	/* { USB_DEVICE(0x0547, 0x8201) },  TT UHCCD02000KPB */
+	/* { USB_DEVICE(0x0547, 0x8310) },  TT UHCCD03100KPA */
+	/* { USB_DEVICE(0x0547, 0x8500) },  TT UHCCD05000KPA */
+	/* { USB_DEVICE(0x0547, 0x8510) },  TT UHCCD05100KPA */
+	/* { USB_DEVICE(0x0547, 0x8600) },  TT UHCCD06000KPA */
+	/* { USB_DEVICE(0x0547, 0x8800) },  TT UHCCD08000KPA */
+	/* { USB_DEVICE(0x0547, 0x8315) },  TT UHCCD03150KPA */
+	/* { USB_DEVICE(0x0547, 0x7800) },  TT UHCCD00800KMA */
+	/* { USB_DEVICE(0x0547, 0x7140) },  TT UHCCD01400KMA */
+	/* { USB_DEVICE(0x0547, 0x7141) },  TT UHCCD01400KMB */
+	/* { USB_DEVICE(0x0547, 0x7200) },  TT UHCCD02000KMA */
+	/* { USB_DEVICE(0x0547, 0x7315) },  TT UHCCD03150KMA */
+	{ }
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+static int sd_probe(struct usb_interface *intf,
+		    const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+			     THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+#endif
+};
+
+static int __init sd_mod_init(void)
+{
+	int ret;
+
+	ret = usb_register(&sd_driver);
+	if (ret < 0)
+		return ret;
+	return 0;
+}
+static void __exit sd_mod_exit(void)
+{
+	usb_deregister(&sd_driver);
+}
+
+module_init(sd_mod_init);
+module_exit(sd_mod_exit);
diff --git a/drivers/media/usb/gspca/tv8532.c b/drivers/media/usb/gspca/tv8532.c
new file mode 100644
index 0000000..bc2720e
--- /dev/null
+++ b/drivers/media/usb/gspca/tv8532.c
@@ -0,0 +1,375 @@
+/*
+ * Quickcam cameras initialization data
+ *
+ * V4L2 by Jean-Francois Moine <http://moinejf.free.fr>
+ *
+ * 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
+ * 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.
+ *
+ */
+#define MODULE_NAME "tv8532"
+
+#include "gspca.h"
+
+MODULE_AUTHOR("Michel Xhaard <mxhaard@users.sourceforge.net>");
+MODULE_DESCRIPTION("TV8532 USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+
+	__u8 packet;
+};
+
+static const struct v4l2_pix_format sif_mode[] = {
+	{176, 144, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 176,
+		.sizeimage = 176 * 144,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1},
+	{352, 288, V4L2_PIX_FMT_SBGGR8, V4L2_FIELD_NONE,
+		.bytesperline = 352,
+		.sizeimage = 352 * 288,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+};
+
+/* TV-8532A (ICM532A) registers (LE) */
+#define R00_PART_CONTROL 0x00
+#define		LATENT_CHANGE	0x80
+#define		EXPO_CHANGE	0x04
+#define R01_TIMING_CONTROL_LOW 0x01
+#define		CMD_EEprom_Open 0x30
+#define		CMD_EEprom_Close 0x29
+#define R03_TABLE_ADDR 0x03
+#define R04_WTRAM_DATA_L 0x04
+#define R05_WTRAM_DATA_M 0x05
+#define R06_WTRAM_DATA_H 0x06
+#define R07_TABLE_LEN	0x07
+#define R08_RAM_WRITE_ACTION 0x08
+#define R0C_AD_WIDTHL	0x0c
+#define R0D_AD_WIDTHH	0x0d
+#define R0E_AD_HEIGHTL	0x0e
+#define R0F_AD_HEIGHTH	0x0f
+#define R10_AD_COL_BEGINL 0x10
+#define R11_AD_COL_BEGINH 0x11
+#define		MIRROR		0x04	/* [10] */
+#define R14_AD_ROW_BEGINL 0x14
+#define R15_AD_ROWBEGINH  0x15
+#define R1C_AD_EXPOSE_TIMEL 0x1c
+#define R20_GAIN_G1L	0x20
+#define R21_GAIN_G1H	0x21
+#define R22_GAIN_RL	0x22
+#define R23_GAIN_RH	0x23
+#define R24_GAIN_BL	0x24
+#define R25_GAIN_BH	0x25
+#define R26_GAIN_G2L	0x26
+#define R27_GAIN_G2H	0x27
+#define R28_QUANT	0x28
+#define R29_LINE	0x29
+#define R2C_POLARITY	0x2c
+#define R2D_POINT	0x2d
+#define R2E_POINTH	0x2e
+#define R2F_POINTB	0x2f
+#define R30_POINTBH	0x30
+#define R31_UPD		0x31
+#define R2A_HIGH_BUDGET 0x2a
+#define R2B_LOW_BUDGET	0x2b
+#define R34_VID		0x34
+#define R35_VIDH	0x35
+#define R36_PID		0x36
+#define R37_PIDH	0x37
+#define R39_Test1	0x39		/* GPIO */
+#define R3B_Test3	0x3b		/* GPIO */
+#define R83_AD_IDH	0x83
+#define R91_AD_SLOPEREG 0x91
+#define R94_AD_BITCONTROL 0x94
+
+static const u8 eeprom_data[][3] = {
+/*	dataH dataM dataL */
+	{0x01, 0x00, 0x01},
+	{0x01, 0x80, 0x11},
+	{0x05, 0x00, 0x14},
+	{0x05, 0x00, 0x1c},
+	{0x0d, 0x00, 0x1e},
+	{0x05, 0x00, 0x1f},
+	{0x05, 0x05, 0x19},
+	{0x05, 0x01, 0x1b},
+	{0x05, 0x09, 0x1e},
+	{0x0d, 0x89, 0x2e},
+	{0x05, 0x89, 0x2f},
+	{0x05, 0x0d, 0xd9},
+	{0x05, 0x09, 0xf1},
+};
+
+
+/* write 1 byte */
+static void reg_w1(struct gspca_dev *gspca_dev,
+		  __u16 index, __u8 value)
+{
+	gspca_dev->usb_buf[0] = value;
+	usb_control_msg(gspca_dev->dev,
+			usb_sndctrlpipe(gspca_dev->dev, 0),
+			0x02,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0,	/* value */
+			index, gspca_dev->usb_buf, 1, 500);
+}
+
+/* write 2 bytes */
+static void reg_w2(struct gspca_dev *gspca_dev,
+		  u16 index, u16 value)
+{
+	gspca_dev->usb_buf[0] = value;
+	gspca_dev->usb_buf[1] = value >> 8;
+	usb_control_msg(gspca_dev->dev,
+			usb_sndctrlpipe(gspca_dev->dev, 0),
+			0x02,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0,	/* value */
+			index, gspca_dev->usb_buf, 2, 500);
+}
+
+static void tv_8532WriteEEprom(struct gspca_dev *gspca_dev)
+{
+	int i;
+
+	reg_w1(gspca_dev, R01_TIMING_CONTROL_LOW, CMD_EEprom_Open);
+	for (i = 0; i < ARRAY_SIZE(eeprom_data); i++) {
+		reg_w1(gspca_dev, R03_TABLE_ADDR, i);
+		reg_w1(gspca_dev, R04_WTRAM_DATA_L, eeprom_data[i][2]);
+		reg_w1(gspca_dev, R05_WTRAM_DATA_M, eeprom_data[i][1]);
+		reg_w1(gspca_dev, R06_WTRAM_DATA_H, eeprom_data[i][0]);
+		reg_w1(gspca_dev, R08_RAM_WRITE_ACTION, 0);
+	}
+	reg_w1(gspca_dev, R07_TABLE_LEN, i);
+	reg_w1(gspca_dev, R01_TIMING_CONTROL_LOW, CMD_EEprom_Close);
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+		     const struct usb_device_id *id)
+{
+	struct cam *cam;
+
+	cam = &gspca_dev->cam;
+	cam->cam_mode = sif_mode;
+	cam->nmodes = ARRAY_SIZE(sif_mode);
+
+	return 0;
+}
+
+static void tv_8532_setReg(struct gspca_dev *gspca_dev)
+{
+	reg_w1(gspca_dev, R3B_Test3, 0x0a);	/* Test0Sel = 10 */
+	/******************************************************/
+	reg_w1(gspca_dev, R0E_AD_HEIGHTL, 0x90);
+	reg_w1(gspca_dev, R0F_AD_HEIGHTH, 0x01);
+	reg_w2(gspca_dev, R1C_AD_EXPOSE_TIMEL, 0x018f);
+	reg_w1(gspca_dev, R10_AD_COL_BEGINL, 0x44);
+						/* begin active line */
+	reg_w1(gspca_dev, R11_AD_COL_BEGINH, 0x00);
+						/* mirror and digital gain */
+	reg_w1(gspca_dev, R14_AD_ROW_BEGINL, 0x0a);
+
+	reg_w1(gspca_dev, R94_AD_BITCONTROL, 0x02);
+	reg_w1(gspca_dev, R91_AD_SLOPEREG, 0x00);
+	reg_w1(gspca_dev, R00_PART_CONTROL, LATENT_CHANGE | EXPO_CHANGE);
+						/* = 0x84 */
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	tv_8532WriteEEprom(gspca_dev);
+
+	return 0;
+}
+
+static void setexposure(struct gspca_dev *gspca_dev, s32 val)
+{
+	reg_w2(gspca_dev, R1C_AD_EXPOSE_TIMEL, val);
+	reg_w1(gspca_dev, R00_PART_CONTROL, LATENT_CHANGE | EXPO_CHANGE);
+						/* 0x84 */
+}
+
+static void setgain(struct gspca_dev *gspca_dev, s32 val)
+{
+	reg_w2(gspca_dev, R20_GAIN_G1L, val);
+	reg_w2(gspca_dev, R22_GAIN_RL, val);
+	reg_w2(gspca_dev, R24_GAIN_BL, val);
+	reg_w2(gspca_dev, R26_GAIN_G2L, val);
+}
+
+/* -- start the camera -- */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	reg_w1(gspca_dev, R0C_AD_WIDTHL, 0xe8);		/* 0x20; 0x0c */
+	reg_w1(gspca_dev, R0D_AD_WIDTHH, 0x03);
+
+	/************************************************/
+	reg_w1(gspca_dev, R28_QUANT, 0x90);
+					/* 0x72 compressed mode 0x28 */
+	if (gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv) {
+		/* 176x144 */
+		reg_w1(gspca_dev, R29_LINE, 0x41);
+					/* CIF - 2 lines/packet */
+	} else {
+		/* 352x288 */
+		reg_w1(gspca_dev, R29_LINE, 0x81);
+					/* CIF - 2 lines/packet */
+	}
+	/************************************************/
+	reg_w1(gspca_dev, R2C_POLARITY, 0x10);		/* slow clock */
+	reg_w1(gspca_dev, R2D_POINT, 0x14);
+	reg_w1(gspca_dev, R2E_POINTH, 0x01);
+	reg_w1(gspca_dev, R2F_POINTB, 0x12);
+	reg_w1(gspca_dev, R30_POINTBH, 0x01);
+
+	tv_8532_setReg(gspca_dev);
+
+	/************************************************/
+	reg_w1(gspca_dev, R31_UPD, 0x01);	/* update registers */
+	msleep(200);
+	reg_w1(gspca_dev, R31_UPD, 0x00);	/* end update */
+
+	gspca_dev->empty_packet = 0;		/* check the empty packets */
+	sd->packet = 0;				/* ignore the first packets */
+
+	return 0;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	reg_w1(gspca_dev, R3B_Test3, 0x0b);	/* Test0Sel = 11 = GPIO */
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int packet_type0, packet_type1;
+
+	packet_type0 = packet_type1 = INTER_PACKET;
+	if (gspca_dev->empty_packet) {
+		gspca_dev->empty_packet = 0;
+		sd->packet = gspca_dev->pixfmt.height / 2;
+		packet_type0 = FIRST_PACKET;
+	} else if (sd->packet == 0)
+		return;			/* 2 more lines in 352x288 ! */
+	sd->packet--;
+	if (sd->packet == 0)
+		packet_type1 = LAST_PACKET;
+
+	/* each packet contains:
+	 * - header 2 bytes
+	 * - RGRG line
+	 * - 4 bytes
+	 * - GBGB line
+	 * - 4 bytes
+	 */
+	gspca_frame_add(gspca_dev, packet_type0,
+			data + 2, gspca_dev->pixfmt.width);
+	gspca_frame_add(gspca_dev, packet_type1,
+			data + gspca_dev->pixfmt.width + 5,
+			gspca_dev->pixfmt.width);
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_EXPOSURE:
+		setexposure(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_GAIN:
+		setgain(gspca_dev, ctrl->val);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 2);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_EXPOSURE, 0, 0x18f, 1, 0x18f);
+	v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_GAIN, 0, 0x7ff, 1, 0x100);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	return 0;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.pkt_scan = sd_pkt_scan,
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x046d, 0x0920)},
+	{USB_DEVICE(0x046d, 0x0921)},
+	{USB_DEVICE(0x0545, 0x808b)},
+	{USB_DEVICE(0x0545, 0x8333)},
+	{USB_DEVICE(0x0923, 0x010f)},
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+		    const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+			       THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/vc032x.c b/drivers/media/usb/gspca/vc032x.c
new file mode 100644
index 0000000..52d0716
--- /dev/null
+++ b/drivers/media/usb/gspca/vc032x.c
@@ -0,0 +1,3842 @@
+/*
+ * Z-star vc0321 library
+ *
+ * Copyright (C) 2009-2010 Jean-François Moine <http://moinejf.free.fr>
+ * Copyright (C) 2006 Koninski Artur takeshi87@o2.pl
+ * Copyright (C) 2006 Michel Xhaard
+ *
+ * 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
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "vc032x"
+
+#include "gspca.h"
+
+MODULE_AUTHOR("Jean-François Moine <http://moinejf.free.fr>");
+MODULE_DESCRIPTION("GSPCA/VC032X USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+	struct { /* hvflip cluster */
+		struct v4l2_ctrl *hflip;
+		struct v4l2_ctrl *vflip;
+	};
+
+	u8 image_offset;
+
+	u8 bridge;
+	u8 sensor;
+	u8 flags;
+#define FL_SAMSUNG 0x01		/* SamsungQ1 (2 sensors) */
+#define FL_HFLIP 0x02		/* mirrored by default */
+#define FL_VFLIP 0x04		/* vertical flipped by default */
+};
+enum bridges {
+	BRIDGE_VC0321,
+	BRIDGE_VC0323,
+};
+enum sensors {
+	SENSOR_HV7131R,
+	SENSOR_MI0360,
+	SENSOR_MI1310_SOC,
+	SENSOR_MI1320,
+	SENSOR_MI1320_SOC,
+	SENSOR_OV7660,
+	SENSOR_OV7670,
+	SENSOR_PO1200,
+	SENSOR_PO3130NC,
+	SENSOR_POxxxx,
+	NSENSORS
+};
+
+
+static const struct v4l2_pix_format vc0321_mode[] = {
+	{320, 240, V4L2_PIX_FMT_YVYU, V4L2_FIELD_NONE,
+		.bytesperline = 320 * 2,
+		.sizeimage = 320 * 240 * 2,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1},
+	{640, 480, V4L2_PIX_FMT_YVYU, V4L2_FIELD_NONE,
+		.bytesperline = 640 * 2,
+		.sizeimage = 640 * 480 * 2,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+};
+static const struct v4l2_pix_format vc0323_mode[] = {
+	{320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 1},
+	{640, 480, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 0},
+	{1280, 960, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE, /* mi1310_soc only */
+		.bytesperline = 1280,
+		.sizeimage = 1280 * 960 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 2},
+};
+static const struct v4l2_pix_format bi_mode[] = {
+	{320, 240, V4L2_PIX_FMT_YUYV, V4L2_FIELD_NONE,
+		.bytesperline = 320 * 2,
+		.sizeimage = 320 * 240 * 2,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 2},
+	{640, 480, V4L2_PIX_FMT_YUYV, V4L2_FIELD_NONE,
+		.bytesperline = 640 * 2,
+		.sizeimage = 640 * 480 * 2,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 1},
+	{1280, 1024, V4L2_PIX_FMT_YUYV, V4L2_FIELD_NONE,
+		.bytesperline = 1280 * 2,
+		.sizeimage = 1280 * 1024 * 2,
+		.colorspace = V4L2_COLORSPACE_SRGB,
+		.priv = 0},
+};
+static const struct v4l2_pix_format svga_mode[] = {
+	{800, 600, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 800,
+		.sizeimage = 800 * 600 * 1 / 4 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 0},
+};
+
+/* OV7660/7670 registers */
+#define OV7660_REG_MVFP 0x1e
+#define OV7660_MVFP_MIRROR	0x20
+#define OV7660_MVFP_VFLIP	0x10
+
+static const u8 mi0360_matrix[9] = {
+	0x50, 0xf8, 0xf8, 0xf5, 0x50, 0xfb, 0xff, 0xf1, 0x50
+};
+
+static const u8 mi0360_initVGA_JPG[][4] = {
+	{0xb0, 0x03, 0x19, 0xcc},
+	{0xb0, 0x04, 0x02, 0xcc},
+	{0xb3, 0x00, 0x24, 0xcc},
+	{0xb3, 0x00, 0x25, 0xcc},
+	{0xb3, 0x08, 0x01, 0xcc},
+	{0xb3, 0x09, 0x0c, 0xcc},
+	{0xb3, 0x05, 0x01, 0xcc},
+	{0xb3, 0x06, 0x03, 0xcc},
+	{0xb3, 0x03, 0x0a, 0xcc},
+	{0xb3, 0x20, 0x00, 0xcc},
+	{0xb3, 0x21, 0x00, 0xcc},
+	{0xb3, 0x22, 0x01, 0xcc},
+	{0xb3, 0x23, 0xe0, 0xcc},
+	{0xb3, 0x04, 0x05, 0xcc},
+	{0xb3, 0x14, 0x00, 0xcc},
+	{0xb3, 0x15, 0x00, 0xcc},
+	{0xb3, 0x16, 0x02, 0xcc},
+	{0xb3, 0x17, 0x7f, 0xcc},
+	{0xb3, 0x35, 0xdd, 0xcc},	/* i2c add: 5d */
+	{0xb3, 0x34, 0x02, 0xcc},
+	{0xb3, 0x00, 0x25, 0xcc},
+	{0xbc, 0x00, 0x71, 0xcc},
+	{0xb8, 0x00, 0x13, 0xcc},
+	{0xb8, 0x27, 0x20, 0xcc},
+	{0xb8, 0x2c, 0x50, 0xcc},
+	{0xb8, 0x2d, 0xf8, 0xcc},
+	{0xb8, 0x2e, 0xf8, 0xcc},
+	{0xb8, 0x2f, 0xf8, 0xcc},
+	{0xb8, 0x30, 0x50, 0xcc},
+	{0xb8, 0x31, 0xf8, 0xcc},
+	{0xb8, 0x32, 0xf8, 0xcc},
+	{0xb8, 0x33, 0xf8, 0xcc},
+	{0xb8, 0x34, 0x50, 0xcc},
+	{0xb8, 0x35, 0x00, 0xcc},
+	{0xb8, 0x36, 0x00, 0xcc},
+	{0xb8, 0x37, 0x00, 0xcc},
+	{0xb8, 0x01, 0x79, 0xcc},
+	{0xb8, 0x08, 0xe0, 0xcc},
+	{0xb3, 0x01, 0x41, 0xcc},
+	{0xb8, 0x01, 0x79, 0xcc},
+	{0xb8, 0x14, 0x18, 0xcc},
+	{0xb8, 0xb2, 0x0a, 0xcc},
+	{0xb8, 0xb4, 0x0a, 0xcc},
+	{0xb8, 0xb5, 0x0a, 0xcc},
+	{0xb8, 0xfe, 0x00, 0xcc},
+	{0xb8, 0xff, 0x28, 0xcc},
+	{0xb9, 0x00, 0x28, 0xcc},
+	{0xb9, 0x01, 0x28, 0xcc},
+	{0xb9, 0x02, 0x28, 0xcc},
+	{0xb9, 0x03, 0x00, 0xcc},
+	{0xb9, 0x04, 0x00, 0xcc},
+	{0xb9, 0x05, 0x3c, 0xcc},
+	{0xb9, 0x06, 0x3c, 0xcc},
+	{0xb9, 0x07, 0x3c, 0xcc},
+	{0xb9, 0x08, 0x3c, 0xcc},
+	{0xb8, 0x8e, 0x00, 0xcc},
+	{0xb8, 0x8f, 0xff, 0xcc},
+	{0xb8, 0x81, 0x09, 0xcc},
+	{0x31, 0x00, 0x00, 0xbb},
+	{0x09, 0x01, 0xc7, 0xbb},
+	{0x34, 0x01, 0x00, 0xbb},
+	{0x2b, 0x00, 0x28, 0xbb},
+	{0x2c, 0x00, 0x30, 0xbb},
+	{0x2d, 0x00, 0x30, 0xbb},
+	{0x2e, 0x00, 0x28, 0xbb},
+	{0x62, 0x04, 0x11, 0xbb},
+	{0x03, 0x01, 0xe0, 0xbb},
+	{0x2c, 0x00, 0x2c, 0xbb},
+	{0x20, 0xd0, 0x00, 0xbb},
+	{0x01, 0x00, 0x08, 0xbb},
+	{0x06, 0x00, 0x10, 0xbb},
+	{0x05, 0x00, 0x20, 0xbb},
+	{0x20, 0x00, 0x00, 0xbb},
+	{0xb6, 0x00, 0x00, 0xcc},
+	{0xb6, 0x03, 0x02, 0xcc},
+	{0xb6, 0x02, 0x80, 0xcc},
+	{0xb6, 0x05, 0x01, 0xcc},
+	{0xb6, 0x04, 0xe0, 0xcc},
+	{0xb6, 0x12, 0x78, 0xcc},
+	{0xb6, 0x18, 0x02, 0xcc},
+	{0xb6, 0x17, 0x58, 0xcc},
+	{0xb6, 0x16, 0x00, 0xcc},
+	{0xb6, 0x22, 0x12, 0xcc},
+	{0xb6, 0x23, 0x0b, 0xcc},
+	{0xb3, 0x02, 0x02, 0xcc},
+	{0xbf, 0xc0, 0x39, 0xcc},
+	{0xbf, 0xc1, 0x04, 0xcc},
+	{0xbf, 0xcc, 0x10, 0xcc},
+	{0xb9, 0x12, 0x00, 0xcc},
+	{0xb9, 0x13, 0x0a, 0xcc},
+	{0xb9, 0x14, 0x0a, 0xcc},
+	{0xb9, 0x15, 0x0a, 0xcc},
+	{0xb9, 0x16, 0x0a, 0xcc},
+	{0xb9, 0x18, 0x00, 0xcc},
+	{0xb9, 0x19, 0x0f, 0xcc},
+	{0xb9, 0x1a, 0x0f, 0xcc},
+	{0xb9, 0x1b, 0x0f, 0xcc},
+	{0xb9, 0x1c, 0x0f, 0xcc},
+	{0xb8, 0x8e, 0x00, 0xcc},
+	{0xb8, 0x8f, 0xff, 0xcc},
+	{0xb6, 0x12, 0xf8, 0xcc},
+	{0xb8, 0x0c, 0x20, 0xcc},
+	{0xb8, 0x0d, 0x70, 0xcc},
+	{0xb6, 0x13, 0x13, 0xcc},
+	{0x35, 0x00, 0x60, 0xbb},
+	{0xb3, 0x5c, 0x01, 0xcc},
+	{}
+};
+static const u8 mi0360_initQVGA_JPG[][4] = {
+	{0xb0, 0x03, 0x19, 0xcc},
+	{0xb0, 0x04, 0x02, 0xcc},
+	{0xb3, 0x00, 0x24, 0xcc},
+	{0xb3, 0x00, 0x25, 0xcc},
+	{0xb3, 0x08, 0x01, 0xcc},
+	{0xb3, 0x09, 0x0c, 0xcc},
+	{0xb3, 0x05, 0x01, 0xcc},
+	{0xb3, 0x06, 0x03, 0xcc},
+	{0xb3, 0x03, 0x0a, 0xcc},
+	{0xb3, 0x20, 0x00, 0xcc},
+	{0xb3, 0x21, 0x00, 0xcc},
+	{0xb3, 0x22, 0x01, 0xcc},
+	{0xb3, 0x23, 0xe0, 0xcc},
+	{0xb3, 0x04, 0x05, 0xcc},
+	{0xb3, 0x14, 0x00, 0xcc},
+	{0xb3, 0x15, 0x00, 0xcc},
+	{0xb3, 0x16, 0x02, 0xcc},
+	{0xb3, 0x17, 0x7f, 0xcc},
+	{0xb3, 0x35, 0xdd, 0xcc},
+	{0xb3, 0x34, 0x02, 0xcc},
+	{0xb3, 0x00, 0x25, 0xcc},
+	{0xbc, 0x00, 0xd1, 0xcc},
+	{0xb8, 0x00, 0x13, 0xcc},
+	{0xb8, 0x27, 0x20, 0xcc},
+	{0xb8, 0x2c, 0x50, 0xcc},
+	{0xb8, 0x2d, 0xf8, 0xcc},
+	{0xb8, 0x2e, 0xf8, 0xcc},
+	{0xb8, 0x2f, 0xf8, 0xcc},
+	{0xb8, 0x30, 0x50, 0xcc},
+	{0xb8, 0x31, 0xf8, 0xcc},
+	{0xb8, 0x32, 0xf8, 0xcc},
+	{0xb8, 0x33, 0xf8, 0xcc},
+	{0xb8, 0x34, 0x50, 0xcc},
+	{0xb8, 0x35, 0x00, 0xcc},
+	{0xb8, 0x36, 0x00, 0xcc},
+	{0xb8, 0x37, 0x00, 0xcc},
+	{0xb8, 0x01, 0x79, 0xcc},
+	{0xb8, 0x08, 0xe0, 0xcc},
+	{0xb3, 0x01, 0x41, 0xcc},
+	{0xb8, 0x01, 0x79, 0xcc},
+	{0xb8, 0x14, 0x18, 0xcc},
+	{0xb8, 0xb2, 0x0a, 0xcc},
+	{0xb8, 0xb4, 0x0a, 0xcc},
+	{0xb8, 0xb5, 0x0a, 0xcc},
+	{0xb8, 0xfe, 0x00, 0xcc},
+	{0xb8, 0xff, 0x28, 0xcc},
+	{0xb9, 0x00, 0x28, 0xcc},
+	{0xb9, 0x01, 0x28, 0xcc},
+	{0xb9, 0x02, 0x28, 0xcc},
+	{0xb9, 0x03, 0x00, 0xcc},
+	{0xb9, 0x04, 0x00, 0xcc},
+	{0xb9, 0x05, 0x3c, 0xcc},
+	{0xb9, 0x06, 0x3c, 0xcc},
+	{0xb9, 0x07, 0x3c, 0xcc},
+	{0xb9, 0x08, 0x3c, 0xcc},
+	{0xb8, 0x8e, 0x00, 0xcc},
+	{0xb8, 0x8f, 0xff, 0xcc},
+	{0xb8, 0x81, 0x09, 0xcc},
+	{0x31, 0x00, 0x00, 0xbb},
+	{0x09, 0x01, 0xc7, 0xbb},
+	{0x34, 0x01, 0x00, 0xbb},
+	{0x2b, 0x00, 0x28, 0xbb},
+	{0x2c, 0x00, 0x30, 0xbb},
+	{0x2d, 0x00, 0x30, 0xbb},
+	{0x2e, 0x00, 0x28, 0xbb},
+	{0x62, 0x04, 0x11, 0xbb},
+	{0x03, 0x01, 0xe0, 0xbb},
+	{0x2c, 0x00, 0x2c, 0xbb},
+	{0x20, 0xd0, 0x00, 0xbb},
+	{0x01, 0x00, 0x08, 0xbb},
+	{0x06, 0x00, 0x10, 0xbb},
+	{0x05, 0x00, 0x20, 0xbb},
+	{0x20, 0x00, 0x00, 0xbb},
+	{0xb6, 0x00, 0x00, 0xcc},
+	{0xb6, 0x03, 0x01, 0xcc},
+	{0xb6, 0x02, 0x40, 0xcc},
+	{0xb6, 0x05, 0x00, 0xcc},
+	{0xb6, 0x04, 0xf0, 0xcc},
+	{0xb6, 0x12, 0x78, 0xcc},
+	{0xb6, 0x18, 0x00, 0xcc},
+	{0xb6, 0x17, 0x96, 0xcc},
+	{0xb6, 0x16, 0x00, 0xcc},
+	{0xb6, 0x22, 0x12, 0xcc},
+	{0xb6, 0x23, 0x0b, 0xcc},
+	{0xb3, 0x02, 0x02, 0xcc},
+	{0xbf, 0xc0, 0x39, 0xcc},
+	{0xbf, 0xc1, 0x04, 0xcc},
+	{0xbf, 0xcc, 0x10, 0xcc},
+	{0xb9, 0x12, 0x00, 0xcc},
+	{0xb9, 0x13, 0x0a, 0xcc},
+	{0xb9, 0x14, 0x0a, 0xcc},
+	{0xb9, 0x15, 0x0a, 0xcc},
+	{0xb9, 0x16, 0x0a, 0xcc},
+	{0xb9, 0x18, 0x00, 0xcc},
+	{0xb9, 0x19, 0x0f, 0xcc},
+	{0xb9, 0x1a, 0x0f, 0xcc},
+	{0xb9, 0x1b, 0x0f, 0xcc},
+	{0xb9, 0x1c, 0x0f, 0xcc},
+	{0xb8, 0x8e, 0x00, 0xcc},
+	{0xb8, 0x8f, 0xff, 0xcc},
+	{0xb6, 0x12, 0xf8, 0xcc},
+	{0xb6, 0x13, 0x13, 0xcc},
+	{0xbc, 0x02, 0x18, 0xcc},
+	{0xbc, 0x03, 0x50, 0xcc},
+	{0xbc, 0x04, 0x18, 0xcc},
+	{0xbc, 0x05, 0x00, 0xcc},
+	{0xbc, 0x06, 0x00, 0xcc},
+	{0xbc, 0x08, 0x30, 0xcc},
+	{0xbc, 0x09, 0x40, 0xcc},
+	{0xbc, 0x0a, 0x10, 0xcc},
+	{0xb8, 0x0c, 0x20, 0xcc},
+	{0xb8, 0x0d, 0x70, 0xcc},
+	{0xbc, 0x0b, 0x00, 0xcc},
+	{0xbc, 0x0c, 0x00, 0xcc},
+	{0x35, 0x00, 0xef, 0xbb},
+	{0xb3, 0x5c, 0x01, 0xcc},
+	{}
+};
+
+static const u8 mi1310_socinitVGA_JPG[][4] = {
+	{0xb0, 0x03, 0x19, 0xcc},
+	{0xb0, 0x04, 0x02, 0xcc},
+	{0xb3, 0x00, 0x64, 0xcc},
+	{0xb3, 0x00, 0x65, 0xcc},
+	{0xb3, 0x05, 0x00, 0xcc},
+	{0xb3, 0x06, 0x00, 0xcc},
+	{0xb3, 0x08, 0x01, 0xcc},
+	{0xb3, 0x09, 0x0c, 0xcc},
+	{0xb3, 0x34, 0x02, 0xcc},
+	{0xb3, 0x35, 0xdd, 0xcc},	/* i2c add: 5d */
+	{0xb3, 0x02, 0x00, 0xcc},
+	{0xb3, 0x03, 0x0a, 0xcc},
+	{0xb3, 0x04, 0x05, 0xcc},
+	{0xb3, 0x20, 0x00, 0xcc},
+	{0xb3, 0x21, 0x00, 0xcc},
+	{0xb3, 0x22, 0x03, 0xcc},
+	{0xb3, 0x23, 0xc0, 0xcc},
+	{0xb3, 0x14, 0x00, 0xcc},
+	{0xb3, 0x15, 0x00, 0xcc},
+	{0xb3, 0x16, 0x04, 0xcc},
+	{0xb3, 0x17, 0xff, 0xcc},
+	{0xb3, 0x00, 0x65, 0xcc},
+	{0xb8, 0x00, 0x00, 0xcc},
+	{0xbc, 0x00, 0xd0, 0xcc},
+	{0xbc, 0x01, 0x01, 0xcc},
+	{0xf0, 0x00, 0x02, 0xbb},
+	{0xc8, 0x9f, 0x0b, 0xbb},
+	{0x5b, 0x00, 0x01, 0xbb},
+	{0x2f, 0xde, 0x20, 0xbb},
+	{0xf0, 0x00, 0x00, 0xbb},
+	{0x20, 0x03, 0x02, 0xbb},	/* h/v flip */
+	{0xf0, 0x00, 0x01, 0xbb},
+	{0x05, 0x00, 0x07, 0xbb},
+	{0x34, 0x00, 0x00, 0xbb},
+	{0x35, 0xff, 0x00, 0xbb},
+	{0xdc, 0x07, 0x02, 0xbb},
+	{0xdd, 0x3c, 0x18, 0xbb},
+	{0xde, 0x92, 0x6d, 0xbb},
+	{0xdf, 0xcd, 0xb1, 0xbb},
+	{0xe0, 0xff, 0xe7, 0xbb},
+	{0x06, 0xf0, 0x0d, 0xbb},
+	{0x06, 0x70, 0x0e, 0xbb},
+	{0x4c, 0x00, 0x01, 0xbb},
+	{0x4d, 0x00, 0x01, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},
+	{0x2e, 0x0c, 0x55, 0xbb},
+	{0x21, 0xb6, 0x6e, 0xbb},
+	{0x36, 0x30, 0x10, 0xbb},
+	{0x37, 0x00, 0xc1, 0xbb},
+	{0xf0, 0x00, 0x00, 0xbb},
+	{0x07, 0x00, 0x84, 0xbb},
+	{0x08, 0x02, 0x4a, 0xbb},
+	{0x05, 0x01, 0x10, 0xbb},
+	{0x06, 0x00, 0x39, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},
+	{0x58, 0x02, 0x67, 0xbb},
+	{0x57, 0x02, 0x00, 0xbb},
+	{0x5a, 0x02, 0x67, 0xbb},
+	{0x59, 0x02, 0x00, 0xbb},
+	{0x5c, 0x12, 0x0d, 0xbb},
+	{0x5d, 0x16, 0x11, 0xbb},
+	{0x39, 0x06, 0x18, 0xbb},
+	{0x3a, 0x06, 0x18, 0xbb},
+	{0x3b, 0x06, 0x18, 0xbb},
+	{0x3c, 0x06, 0x18, 0xbb},
+	{0x64, 0x7b, 0x5b, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},
+	{0x36, 0x30, 0x10, 0xbb},
+	{0x37, 0x00, 0xc0, 0xbb},
+	{0xbc, 0x0e, 0x00, 0xcc},
+	{0xbc, 0x0f, 0x05, 0xcc},
+	{0xbc, 0x10, 0xc0, 0xcc},
+	{0xbc, 0x11, 0x03, 0xcc},
+	{0xb6, 0x00, 0x00, 0xcc},
+	{0xb6, 0x03, 0x02, 0xcc},
+	{0xb6, 0x02, 0x80, 0xcc},
+	{0xb6, 0x05, 0x01, 0xcc},
+	{0xb6, 0x04, 0xe0, 0xcc},
+	{0xb6, 0x12, 0xf8, 0xcc},
+	{0xb6, 0x13, 0x25, 0xcc},
+	{0xb6, 0x18, 0x02, 0xcc},
+	{0xb6, 0x17, 0x58, 0xcc},
+	{0xb6, 0x16, 0x00, 0xcc},
+	{0xb6, 0x22, 0x12, 0xcc},
+	{0xb6, 0x23, 0x0b, 0xcc},
+	{0xbf, 0xc0, 0x39, 0xcc},
+	{0xbf, 0xc1, 0x04, 0xcc},
+	{0xbf, 0xcc, 0x00, 0xcc},
+	{0xbc, 0x02, 0x18, 0xcc},
+	{0xbc, 0x03, 0x50, 0xcc},
+	{0xbc, 0x04, 0x18, 0xcc},
+	{0xbc, 0x05, 0x00, 0xcc},
+	{0xbc, 0x06, 0x00, 0xcc},
+	{0xbc, 0x08, 0x30, 0xcc},
+	{0xbc, 0x09, 0x40, 0xcc},
+	{0xbc, 0x0a, 0x10, 0xcc},
+	{0xbc, 0x0b, 0x00, 0xcc},
+	{0xbc, 0x0c, 0x00, 0xcc},
+	{0xb3, 0x5c, 0x01, 0xcc},
+	{0xf0, 0x00, 0x01, 0xbb},
+	{0x80, 0x00, 0x03, 0xbb},
+	{0x81, 0xc7, 0x14, 0xbb},
+	{0x82, 0xeb, 0xe8, 0xbb},
+	{0x83, 0xfe, 0xf4, 0xbb},
+	{0x84, 0xcd, 0x10, 0xbb},
+	{0x85, 0xf3, 0xee, 0xbb},
+	{0x86, 0xff, 0xf1, 0xbb},
+	{0x87, 0xcd, 0x10, 0xbb},
+	{0x88, 0xf3, 0xee, 0xbb},
+	{0x89, 0x01, 0xf1, 0xbb},
+	{0x8a, 0xe5, 0x17, 0xbb},
+	{0x8b, 0xe8, 0xe2, 0xbb},
+	{0x8c, 0xf7, 0xed, 0xbb},
+	{0x8d, 0x00, 0xff, 0xbb},
+	{0x8e, 0xec, 0x10, 0xbb},
+	{0x8f, 0xf0, 0xed, 0xbb},
+	{0x90, 0xf9, 0xf2, 0xbb},
+	{0x91, 0x00, 0x00, 0xbb},
+	{0x92, 0xe9, 0x0d, 0xbb},
+	{0x93, 0xf4, 0xf2, 0xbb},
+	{0x94, 0xfb, 0xf5, 0xbb},
+	{0x95, 0x00, 0xff, 0xbb},
+	{0xb6, 0x0f, 0x08, 0xbb},
+	{0xb7, 0x3d, 0x16, 0xbb},
+	{0xb8, 0x0c, 0x04, 0xbb},
+	{0xb9, 0x1c, 0x07, 0xbb},
+	{0xba, 0x0a, 0x03, 0xbb},
+	{0xbb, 0x1b, 0x09, 0xbb},
+	{0xbc, 0x17, 0x0d, 0xbb},
+	{0xbd, 0x23, 0x1d, 0xbb},
+	{0xbe, 0x00, 0x28, 0xbb},
+	{0xbf, 0x11, 0x09, 0xbb},
+	{0xc0, 0x16, 0x15, 0xbb},
+	{0xc1, 0x00, 0x1b, 0xbb},
+	{0xc2, 0x0e, 0x07, 0xbb},
+	{0xc3, 0x14, 0x10, 0xbb},
+	{0xc4, 0x00, 0x17, 0xbb},
+	{0x06, 0x74, 0x8e, 0xbb},
+	{0xf0, 0x00, 0x01, 0xbb},
+	{0x06, 0xf4, 0x8e, 0xbb},
+	{0x00, 0x00, 0x50, 0xdd},
+	{0x06, 0x74, 0x8e, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},
+	{0x24, 0x50, 0x20, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},
+	{0x34, 0x0c, 0x50, 0xbb},
+	{0xb3, 0x01, 0x41, 0xcc},
+	{0xf0, 0x00, 0x00, 0xbb},
+	{0x03, 0x03, 0xc0, 0xbb},
+	{},
+};
+static const u8 mi1310_socinitQVGA_JPG[][4] = {
+	{0xb0, 0x03, 0x19, 0xcc},	{0xb0, 0x04, 0x02, 0xcc},
+	{0xb3, 0x00, 0x64, 0xcc},	{0xb3, 0x00, 0x65, 0xcc},
+	{0xb3, 0x05, 0x00, 0xcc},	{0xb3, 0x06, 0x00, 0xcc},
+	{0xb3, 0x08, 0x01, 0xcc},	{0xb3, 0x09, 0x0c, 0xcc},
+	{0xb3, 0x34, 0x02, 0xcc},	{0xb3, 0x35, 0xdd, 0xcc},
+	{0xb3, 0x02, 0x00, 0xcc},	{0xb3, 0x03, 0x0a, 0xcc},
+	{0xb3, 0x04, 0x05, 0xcc},	{0xb3, 0x20, 0x00, 0xcc},
+	{0xb3, 0x21, 0x00, 0xcc},	{0xb3, 0x22, 0x03, 0xcc},
+	{0xb3, 0x23, 0xc0, 0xcc},	{0xb3, 0x14, 0x00, 0xcc},
+	{0xb3, 0x15, 0x00, 0xcc},	{0xb3, 0x16, 0x04, 0xcc},
+	{0xb3, 0x17, 0xff, 0xcc},	{0xb3, 0x00, 0x65, 0xcc},
+	{0xb8, 0x00, 0x00, 0xcc},	{0xbc, 0x00, 0xf0, 0xcc},
+	{0xbc, 0x01, 0x01, 0xcc},	{0xf0, 0x00, 0x02, 0xbb},
+	{0xc8, 0x9f, 0x0b, 0xbb},	{0x5b, 0x00, 0x01, 0xbb},
+	{0x2f, 0xde, 0x20, 0xbb},	{0xf0, 0x00, 0x00, 0xbb},
+	{0x20, 0x03, 0x02, 0xbb},	/* h/v flip */
+	{0xf0, 0x00, 0x01, 0xbb},
+	{0x05, 0x00, 0x07, 0xbb},	{0x34, 0x00, 0x00, 0xbb},
+	{0x35, 0xff, 0x00, 0xbb},	{0xdc, 0x07, 0x02, 0xbb},
+	{0xdd, 0x3c, 0x18, 0xbb},	{0xde, 0x92, 0x6d, 0xbb},
+	{0xdf, 0xcd, 0xb1, 0xbb},	{0xe0, 0xff, 0xe7, 0xbb},
+	{0x06, 0xf0, 0x0d, 0xbb},	{0x06, 0x70, 0x0e, 0xbb},
+	{0x4c, 0x00, 0x01, 0xbb},	{0x4d, 0x00, 0x01, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},	{0x2e, 0x0c, 0x55, 0xbb},
+	{0x21, 0xb6, 0x6e, 0xbb},	{0x36, 0x30, 0x10, 0xbb},
+	{0x37, 0x00, 0xc1, 0xbb},	{0xf0, 0x00, 0x00, 0xbb},
+	{0x07, 0x00, 0x84, 0xbb},	{0x08, 0x02, 0x4a, 0xbb},
+	{0x05, 0x01, 0x10, 0xbb},	{0x06, 0x00, 0x39, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},	{0x58, 0x02, 0x67, 0xbb},
+	{0x57, 0x02, 0x00, 0xbb},	{0x5a, 0x02, 0x67, 0xbb},
+	{0x59, 0x02, 0x00, 0xbb},	{0x5c, 0x12, 0x0d, 0xbb},
+	{0x5d, 0x16, 0x11, 0xbb},	{0x39, 0x06, 0x18, 0xbb},
+	{0x3a, 0x06, 0x18, 0xbb},	{0x3b, 0x06, 0x18, 0xbb},
+	{0x3c, 0x06, 0x18, 0xbb},	{0x64, 0x7b, 0x5b, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},	{0x36, 0x30, 0x10, 0xbb},
+	{0x37, 0x00, 0xc0, 0xbb},	{0xbc, 0x0e, 0x00, 0xcc},
+	{0xbc, 0x0f, 0x05, 0xcc},	{0xbc, 0x10, 0xc0, 0xcc},
+	{0xbc, 0x11, 0x03, 0xcc},	{0xb6, 0x00, 0x00, 0xcc},
+	{0xb6, 0x03, 0x01, 0xcc},	{0xb6, 0x02, 0x40, 0xcc},
+	{0xb6, 0x05, 0x00, 0xcc},	{0xb6, 0x04, 0xf0, 0xcc},
+	{0xb6, 0x12, 0xf8, 0xcc},	{0xb6, 0x13, 0x25, 0xcc},
+	{0xb6, 0x18, 0x00, 0xcc},	{0xb6, 0x17, 0x96, 0xcc},
+	{0xb6, 0x16, 0x00, 0xcc},	{0xb6, 0x22, 0x12, 0xcc},
+	{0xb6, 0x23, 0x0b, 0xcc},	{0xbf, 0xc0, 0x39, 0xcc},
+	{0xbf, 0xc1, 0x04, 0xcc},	{0xbf, 0xcc, 0x00, 0xcc},
+	{0xb3, 0x5c, 0x01, 0xcc},	{0xf0, 0x00, 0x01, 0xbb},
+	{0x80, 0x00, 0x03, 0xbb},	{0x81, 0xc7, 0x14, 0xbb},
+	{0x82, 0xeb, 0xe8, 0xbb},	{0x83, 0xfe, 0xf4, 0xbb},
+	{0x84, 0xcd, 0x10, 0xbb},	{0x85, 0xf3, 0xee, 0xbb},
+	{0x86, 0xff, 0xf1, 0xbb},	{0x87, 0xcd, 0x10, 0xbb},
+	{0x88, 0xf3, 0xee, 0xbb},	{0x89, 0x01, 0xf1, 0xbb},
+	{0x8a, 0xe5, 0x17, 0xbb},	{0x8b, 0xe8, 0xe2, 0xbb},
+	{0x8c, 0xf7, 0xed, 0xbb},	{0x8d, 0x00, 0xff, 0xbb},
+	{0x8e, 0xec, 0x10, 0xbb},	{0x8f, 0xf0, 0xed, 0xbb},
+	{0x90, 0xf9, 0xf2, 0xbb},	{0x91, 0x00, 0x00, 0xbb},
+	{0x92, 0xe9, 0x0d, 0xbb},	{0x93, 0xf4, 0xf2, 0xbb},
+	{0x94, 0xfb, 0xf5, 0xbb},	{0x95, 0x00, 0xff, 0xbb},
+	{0xb6, 0x0f, 0x08, 0xbb},	{0xb7, 0x3d, 0x16, 0xbb},
+	{0xb8, 0x0c, 0x04, 0xbb},	{0xb9, 0x1c, 0x07, 0xbb},
+	{0xba, 0x0a, 0x03, 0xbb},	{0xbb, 0x1b, 0x09, 0xbb},
+	{0xbc, 0x17, 0x0d, 0xbb},	{0xbd, 0x23, 0x1d, 0xbb},
+	{0xbe, 0x00, 0x28, 0xbb},	{0xbf, 0x11, 0x09, 0xbb},
+	{0xc0, 0x16, 0x15, 0xbb},	{0xc1, 0x00, 0x1b, 0xbb},
+	{0xc2, 0x0e, 0x07, 0xbb},	{0xc3, 0x14, 0x10, 0xbb},
+	{0xc4, 0x00, 0x17, 0xbb},	{0x06, 0x74, 0x8e, 0xbb},
+	{0xf0, 0x00, 0x01, 0xbb},	{0x06, 0xf4, 0x8e, 0xbb},
+	{0x00, 0x00, 0x50, 0xdd},	{0x06, 0x74, 0x8e, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},	{0x24, 0x50, 0x20, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},	{0x34, 0x0c, 0x50, 0xbb},
+	{0xb3, 0x01, 0x41, 0xcc},	{0xf0, 0x00, 0x00, 0xbb},
+	{0x03, 0x03, 0xc0, 0xbb},
+	{},
+};
+static const u8 mi1310_soc_InitSXGA_JPG[][4] = {
+	{0xb0, 0x03, 0x19, 0xcc},
+	{0xb0, 0x04, 0x02, 0xcc},
+	{0xb3, 0x00, 0x64, 0xcc},
+	{0xb3, 0x00, 0x65, 0xcc},
+	{0xb3, 0x05, 0x00, 0xcc},
+	{0xb3, 0x06, 0x00, 0xcc},
+	{0xb3, 0x08, 0x01, 0xcc},
+	{0xb3, 0x09, 0x0c, 0xcc},
+	{0xb3, 0x34, 0x02, 0xcc},
+	{0xb3, 0x35, 0xdd, 0xcc},
+	{0xb3, 0x02, 0x00, 0xcc},
+	{0xb3, 0x03, 0x0a, 0xcc},
+	{0xb3, 0x04, 0x0d, 0xcc},
+	{0xb3, 0x20, 0x00, 0xcc},
+	{0xb3, 0x21, 0x00, 0xcc},
+	{0xb3, 0x22, 0x03, 0xcc},
+	{0xb3, 0x23, 0xc0, 0xcc},
+	{0xb3, 0x14, 0x00, 0xcc},
+	{0xb3, 0x15, 0x00, 0xcc},
+	{0xb3, 0x16, 0x04, 0xcc},
+	{0xb3, 0x17, 0xff, 0xcc},
+	{0xb3, 0x00, 0x65, 0xcc},
+	{0xb8, 0x00, 0x00, 0xcc},
+	{0xbc, 0x00, 0x70, 0xcc},
+	{0xbc, 0x01, 0x01, 0xcc},
+	{0xf0, 0x00, 0x02, 0xbb},
+	{0xc8, 0x9f, 0x0b, 0xbb},
+	{0x5b, 0x00, 0x01, 0xbb},
+	{0xf0, 0x00, 0x00, 0xbb},
+	{0x20, 0x03, 0x02, 0xbb},	/* h/v flip */
+	{0xf0, 0x00, 0x01, 0xbb},
+	{0x05, 0x00, 0x07, 0xbb},
+	{0x34, 0x00, 0x00, 0xbb},
+	{0x35, 0xff, 0x00, 0xbb},
+	{0xdc, 0x07, 0x02, 0xbb},
+	{0xdd, 0x3c, 0x18, 0xbb},
+	{0xde, 0x92, 0x6d, 0xbb},
+	{0xdf, 0xcd, 0xb1, 0xbb},
+	{0xe0, 0xff, 0xe7, 0xbb},
+	{0x06, 0xf0, 0x0d, 0xbb},
+	{0x06, 0x70, 0x0e, 0xbb},
+	{0x4c, 0x00, 0x01, 0xbb},
+	{0x4d, 0x00, 0x01, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},
+	{0x2e, 0x0c, 0x60, 0xbb},
+	{0x21, 0xb6, 0x6e, 0xbb},
+	{0x37, 0x01, 0x40, 0xbb},
+	{0xf0, 0x00, 0x00, 0xbb},
+	{0x07, 0x00, 0x84, 0xbb},
+	{0x08, 0x02, 0x4a, 0xbb},
+	{0x05, 0x01, 0x10, 0xbb},
+	{0x06, 0x00, 0x39, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},
+	{0x58, 0x02, 0x67, 0xbb},
+	{0x57, 0x02, 0x00, 0xbb},
+	{0x5a, 0x02, 0x67, 0xbb},
+	{0x59, 0x02, 0x00, 0xbb},
+	{0x5c, 0x12, 0x0d, 0xbb},
+	{0x5d, 0x16, 0x11, 0xbb},
+	{0x39, 0x06, 0x18, 0xbb},
+	{0x3a, 0x06, 0x18, 0xbb},
+	{0x3b, 0x06, 0x18, 0xbb},
+	{0x3c, 0x06, 0x18, 0xbb},
+	{0x64, 0x7b, 0x5b, 0xbb},
+	{0xb6, 0x00, 0x00, 0xcc},
+	{0xb6, 0x03, 0x05, 0xcc},
+	{0xb6, 0x02, 0x00, 0xcc},
+	{0xb6, 0x05, 0x03, 0xcc},
+	{0xb6, 0x04, 0xc0, 0xcc},
+	{0xb6, 0x12, 0xf8, 0xcc},
+	{0xb6, 0x13, 0x29, 0xcc},
+	{0xb6, 0x18, 0x09, 0xcc},
+	{0xb6, 0x17, 0x60, 0xcc},
+	{0xb6, 0x16, 0x00, 0xcc},
+	{0xb6, 0x22, 0x12, 0xcc},
+	{0xb6, 0x23, 0x0b, 0xcc},
+	{0xbf, 0xc0, 0x39, 0xcc},
+	{0xbf, 0xc1, 0x04, 0xcc},
+	{0xbf, 0xcc, 0x00, 0xcc},
+	{0xb3, 0x01, 0x41, 0xcc},
+	{0x00, 0x00, 0x80, 0xdd},
+	{0xf0, 0x00, 0x02, 0xbb},
+	{0x00, 0x00, 0x10, 0xdd},
+	{0x22, 0xa0, 0x78, 0xbb},
+	{0x23, 0xa0, 0x78, 0xbb},
+	{0x24, 0x7f, 0x00, 0xbb},
+	{0x28, 0xea, 0x02, 0xbb},
+	{0x29, 0x86, 0x7a, 0xbb},
+	{0x5e, 0x52, 0x4c, 0xbb},
+	{0x5f, 0x20, 0x24, 0xbb},
+	{0x60, 0x00, 0x02, 0xbb},
+	{0x02, 0x00, 0xee, 0xbb},
+	{0x03, 0x39, 0x23, 0xbb},
+	{0x04, 0x07, 0x24, 0xbb},
+	{0x09, 0x00, 0xc0, 0xbb},
+	{0x0a, 0x00, 0x79, 0xbb},
+	{0x0b, 0x00, 0x04, 0xbb},
+	{0x0c, 0x00, 0x5c, 0xbb},
+	{0x0d, 0x00, 0xd9, 0xbb},
+	{0x0e, 0x00, 0x53, 0xbb},
+	{0x0f, 0x00, 0x21, 0xbb},
+	{0x10, 0x00, 0xa4, 0xbb},
+	{0x11, 0x00, 0xe5, 0xbb},
+	{0x15, 0x00, 0x00, 0xbb},
+	{0x16, 0x00, 0x00, 0xbb},
+	{0x17, 0x00, 0x00, 0xbb},
+	{0x18, 0x00, 0x00, 0xbb},
+	{0x19, 0x00, 0x00, 0xbb},
+	{0x1a, 0x00, 0x00, 0xbb},
+	{0x1b, 0x00, 0x00, 0xbb},
+	{0x1c, 0x00, 0x00, 0xbb},
+	{0x1d, 0x00, 0x00, 0xbb},
+	{0x1e, 0x00, 0x00, 0xbb},
+	{0xf0, 0x00, 0x01, 0xbb},
+	{0x00, 0x00, 0x20, 0xdd},
+	{0x06, 0xf0, 0x8e, 0xbb},
+	{0x00, 0x00, 0x80, 0xdd},
+	{0x06, 0x70, 0x8e, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},
+	{0x00, 0x00, 0x20, 0xdd},
+	{0x5e, 0x6a, 0x53, 0xbb},
+	{0x5f, 0x40, 0x2c, 0xbb},
+	{0xf0, 0x00, 0x01, 0xbb},
+	{0x00, 0x00, 0x20, 0xdd},
+	{0x58, 0x00, 0x00, 0xbb},
+	{0x53, 0x09, 0x03, 0xbb},
+	{0x54, 0x31, 0x18, 0xbb},
+	{0x55, 0x8b, 0x5f, 0xbb},
+	{0x56, 0xc0, 0xa9, 0xbb},
+	{0x57, 0xe0, 0xd2, 0xbb},
+	{0xe1, 0x00, 0x00, 0xbb},
+	{0xdc, 0x09, 0x03, 0xbb},
+	{0xdd, 0x31, 0x18, 0xbb},
+	{0xde, 0x8b, 0x5f, 0xbb},
+	{0xdf, 0xc0, 0xa9, 0xbb},
+	{0xe0, 0xe0, 0xd2, 0xbb},
+	{0xb3, 0x5c, 0x01, 0xcc},
+	{0xf0, 0x00, 0x01, 0xbb},
+	{0x06, 0xf0, 0x8e, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},
+	{0x2f, 0xde, 0x20, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},
+	{0x24, 0x50, 0x20, 0xbb},
+	{0xbc, 0x0e, 0x00, 0xcc},
+	{0xbc, 0x0f, 0x05, 0xcc},
+	{0xbc, 0x10, 0xc0, 0xcc},
+	{0xf0, 0x00, 0x02, 0xbb},
+	{0x34, 0x0c, 0x50, 0xbb},
+	{0xbc, 0x11, 0x03, 0xcc},
+	{0xf0, 0x00, 0x01, 0xbb},
+	{0x80, 0x00, 0x03, 0xbb},
+	{0x81, 0xc7, 0x14, 0xbb},
+	{0x82, 0xeb, 0xe8, 0xbb},
+	{0x83, 0xfe, 0xf4, 0xbb},
+	{0x84, 0xcd, 0x10, 0xbb},
+	{0x85, 0xf3, 0xee, 0xbb},
+	{0x86, 0xff, 0xf1, 0xbb},
+	{0x87, 0xcd, 0x10, 0xbb},
+	{0x88, 0xf3, 0xee, 0xbb},
+	{0x89, 0x01, 0xf1, 0xbb},
+	{0x8a, 0xe5, 0x17, 0xbb},
+	{0x8b, 0xe8, 0xe2, 0xbb},
+	{0x8c, 0xf7, 0xed, 0xbb},
+	{0x8d, 0x00, 0xff, 0xbb},
+	{0x8e, 0xec, 0x10, 0xbb},
+	{0x8f, 0xf0, 0xed, 0xbb},
+	{0x90, 0xf9, 0xf2, 0xbb},
+	{0x91, 0x00, 0x00, 0xbb},
+	{0x92, 0xe9, 0x0d, 0xbb},
+	{0x93, 0xf4, 0xf2, 0xbb},
+	{0x94, 0xfb, 0xf5, 0xbb},
+	{0x95, 0x00, 0xff, 0xbb},
+	{0xb6, 0x0f, 0x08, 0xbb},
+	{0xb7, 0x3d, 0x16, 0xbb},
+	{0xb8, 0x0c, 0x04, 0xbb},
+	{0xb9, 0x1c, 0x07, 0xbb},
+	{0xba, 0x0a, 0x03, 0xbb},
+	{0xbb, 0x1b, 0x09, 0xbb},
+	{0xbc, 0x17, 0x0d, 0xbb},
+	{0xbd, 0x23, 0x1d, 0xbb},
+	{0xbe, 0x00, 0x28, 0xbb},
+	{0xbf, 0x11, 0x09, 0xbb},
+	{0xc0, 0x16, 0x15, 0xbb},
+	{0xc1, 0x00, 0x1b, 0xbb},
+	{0xc2, 0x0e, 0x07, 0xbb},
+	{0xc3, 0x14, 0x10, 0xbb},
+	{0xc4, 0x00, 0x17, 0xbb},
+	{0x06, 0x74, 0x8e, 0xbb},
+	{0xf0, 0x00, 0x00, 0xbb},
+	{0x03, 0x03, 0xc0, 0xbb},
+	{}
+};
+
+static const u8 mi1320_gamma[17] = {
+	0x00, 0x13, 0x38, 0x59, 0x79, 0x92, 0xa7, 0xb9, 0xc8,
+	0xd4, 0xdf, 0xe7, 0xee, 0xf4, 0xf9, 0xfc, 0xff
+};
+static const u8 mi1320_matrix[9] = {
+	0x54, 0xda, 0x06, 0xf1, 0x50, 0xf4, 0xf7, 0xea, 0x52
+};
+static const u8 mi1320_initVGA_data[][4] = {
+	{0xb3, 0x01, 0x01, 0xcc},	{0x00, 0x00, 0x33, 0xdd},
+	{0xb0, 0x03, 0x19, 0xcc},	{0x00, 0x00, 0x33, 0xdd},
+	{0xb0, 0x04, 0x02, 0xcc},	{0x00, 0x00, 0x33, 0xdd},
+	{0xb3, 0x00, 0x64, 0xcc},	{0xb3, 0x00, 0x65, 0xcc},
+	{0xb0, 0x16, 0x03, 0xcc},	{0xb3, 0x05, 0x00, 0xcc},
+	{0xb3, 0x06, 0x00, 0xcc},	{0xb3, 0x08, 0x01, 0xcc},
+	{0xb3, 0x09, 0x0c, 0xcc},	{0xb3, 0x34, 0x02, 0xcc},
+	{0xb3, 0x35, 0xc8, 0xcc},	/* i2c add: 48 */
+	{0xb3, 0x02, 0x00, 0xcc},
+	{0xb3, 0x03, 0x0a, 0xcc},	{0xb3, 0x04, 0x05, 0xcc},
+	{0xb3, 0x20, 0x00, 0xcc},	{0xb3, 0x21, 0x00, 0xcc},
+	{0xb3, 0x22, 0x03, 0xcc},	{0xb3, 0x23, 0xc0, 0xcc},
+	{0xb3, 0x14, 0x00, 0xcc},	{0xb3, 0x15, 0x00, 0xcc},
+	{0xb3, 0x16, 0x04, 0xcc},	{0xb3, 0x17, 0xff, 0xcc},
+	{0xb3, 0x00, 0x67, 0xcc},	{0xbc, 0x00, 0xd0, 0xcc},
+	{0xbc, 0x01, 0x01, 0xcc},	{0xf0, 0x00, 0x00, 0xbb},
+	{0x0d, 0x00, 0x09, 0xbb},	{0x00, 0x01, 0x00, 0xdd},
+	{0x0d, 0x00, 0x08, 0xbb},	{0xf0, 0x00, 0x01, 0xbb},
+	{0xa1, 0x05, 0x00, 0xbb},	{0xa4, 0x03, 0xc0, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},	{0x00, 0x00, 0x10, 0xdd},
+	{0xc8, 0x9f, 0x0b, 0xbb},	{0x00, 0x00, 0x10, 0xdd},
+	{0xf0, 0x00, 0x00, 0xbb},	{0x00, 0x00, 0x10, 0xdd},
+	{0x20, 0x01, 0x00, 0xbb},	{0x00, 0x00, 0x10, 0xdd},
+	{0xf0, 0x00, 0x01, 0xbb},	{0x9d, 0x3c, 0xa0, 0xbb},
+	{0x47, 0x30, 0x30, 0xbb},	{0xf0, 0x00, 0x00, 0xbb},
+	{0x0a, 0x80, 0x11, 0xbb},	{0x35, 0x00, 0x22, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},	{0x9d, 0xc5, 0x05, 0xbb},
+	{0xdc, 0x0f, 0xfc, 0xbb},	{0xf0, 0x00, 0x01, 0xbb},
+	{0x06, 0x74, 0x0e, 0xbb},	{0x80, 0x00, 0x06, 0xbb},
+	{0x81, 0x04, 0x00, 0xbb},	{0x82, 0x01, 0x02, 0xbb},
+	{0x83, 0x03, 0x02, 0xbb},	{0x84, 0x05, 0x00, 0xbb},
+	{0x85, 0x01, 0x00, 0xbb},	{0x86, 0x03, 0x02, 0xbb},
+	{0x87, 0x05, 0x00, 0xbb},	{0x88, 0x01, 0x00, 0xbb},
+	{0x89, 0x02, 0x02, 0xbb},	{0x8a, 0xfd, 0x04, 0xbb},
+	{0x8b, 0xfc, 0xfd, 0xbb},	{0x8c, 0xff, 0xfd, 0xbb},
+	{0x8d, 0x00, 0x00, 0xbb},	{0x8e, 0xfe, 0x05, 0xbb},
+	{0x8f, 0xfc, 0xfd, 0xbb},	{0x90, 0xfe, 0xfd, 0xbb},
+	{0x91, 0x00, 0x00, 0xbb},	{0x92, 0xfe, 0x03, 0xbb},
+	{0x93, 0xfd, 0xfe, 0xbb},	{0x94, 0xff, 0xfd, 0xbb},
+	{0x95, 0x00, 0x00, 0xbb},	{0xb6, 0x07, 0x05, 0xbb},
+	{0xb7, 0x13, 0x06, 0xbb},	{0xb8, 0x08, 0x06, 0xbb},
+	{0xb9, 0x14, 0x08, 0xbb},	{0xba, 0x06, 0x05, 0xbb},
+	{0xbb, 0x13, 0x06, 0xbb},	{0xbc, 0x03, 0x01, 0xbb},
+	{0xbd, 0x03, 0x04, 0xbb},	{0xbe, 0x00, 0x02, 0xbb},
+	{0xbf, 0x03, 0x01, 0xbb},	{0xc0, 0x02, 0x04, 0xbb},
+	{0xc1, 0x00, 0x04, 0xbb},	{0xc2, 0x02, 0x01, 0xbb},
+	{0xc3, 0x01, 0x03, 0xbb},	{0xc4, 0x00, 0x04, 0xbb},
+	{0xf0, 0x00, 0x00, 0xbb},	{0x05, 0x01, 0x13, 0xbb},
+	{0x06, 0x00, 0x11, 0xbb},	{0x07, 0x00, 0x85, 0xbb},
+	{0x08, 0x00, 0x27, 0xbb},
+	{0x20, 0x01, 0x00, 0xbb},	/* h/v flips - was 03 */
+	{0x21, 0x80, 0x00, 0xbb},	{0x22, 0x0d, 0x0f, 0xbb},
+	{0x24, 0x80, 0x00, 0xbb},	{0x59, 0x00, 0xff, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},	{0x39, 0x03, 0x0d, 0xbb},
+	{0x3a, 0x06, 0x1b, 0xbb},	{0x3b, 0x00, 0x95, 0xbb},
+	{0x3c, 0x04, 0xdb, 0xbb},	{0x57, 0x02, 0x00, 0xbb},
+	{0x58, 0x02, 0x66, 0xbb},	{0x59, 0x00, 0xff, 0xbb},
+	{0x5a, 0x01, 0x33, 0xbb},	{0x5c, 0x12, 0x0d, 0xbb},
+	{0x5d, 0x16, 0x11, 0xbb},	{0x64, 0x5e, 0x1c, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},	{0x2f, 0xd1, 0x00, 0xbb},
+	{0x5b, 0x00, 0x01, 0xbb},	{0xf0, 0x00, 0x02, 0xbb},
+	{0x36, 0x68, 0x10, 0xbb},	{0x00, 0x00, 0x30, 0xdd},
+	{0x37, 0x82, 0x00, 0xbb},	{0xbc, 0x0e, 0x00, 0xcc},
+	{0xbc, 0x0f, 0x05, 0xcc},	{0xbc, 0x10, 0xc0, 0xcc},
+	{0xbc, 0x11, 0x03, 0xcc},	{0xb6, 0x00, 0x00, 0xcc},
+	{0xb6, 0x03, 0x05, 0xcc},	{0xb6, 0x02, 0x00, 0xcc},
+	{0xb6, 0x05, 0x04, 0xcc},	{0xb6, 0x04, 0x00, 0xcc},
+	{0xb6, 0x12, 0xf8, 0xcc},	{0xb6, 0x13, 0x29, 0xcc},
+	{0xb6, 0x18, 0x0a, 0xcc},	{0xb6, 0x17, 0x00, 0xcc},
+	{0xb6, 0x16, 0x00, 0xcc},	{0xb6, 0x22, 0x12, 0xcc},
+	{0xb6, 0x23, 0x0b, 0xcc},	{0xbf, 0xc0, 0x26, 0xcc},
+	{0xbf, 0xc1, 0x02, 0xcc},	{0xbf, 0xcc, 0x04, 0xcc},
+	{0xbc, 0x02, 0x18, 0xcc},	{0xbc, 0x03, 0x50, 0xcc},
+	{0xbc, 0x04, 0x18, 0xcc},	{0xbc, 0x05, 0x00, 0xcc},
+	{0xbc, 0x06, 0x00, 0xcc},	{0xbc, 0x08, 0x30, 0xcc},
+	{0xbc, 0x09, 0x40, 0xcc},	{0xbc, 0x0a, 0x10, 0xcc},
+	{0xbc, 0x0b, 0x00, 0xcc},	{0xbc, 0x0c, 0x00, 0xcc},
+	{0xb3, 0x5c, 0x01, 0xcc},	{0xb3, 0x01, 0x41, 0xcc},
+	{}
+};
+static const u8 mi1320_initQVGA_data[][4] = {
+	{0xb3, 0x01, 0x01, 0xcc},	{0x00, 0x00, 0x33, 0xdd},
+	{0xb0, 0x03, 0x19, 0xcc},	{0x00, 0x00, 0x33, 0xdd},
+	{0xb0, 0x04, 0x02, 0xcc},	{0x00, 0x00, 0x33, 0xdd},
+	{0xb3, 0x00, 0x64, 0xcc},	{0xb3, 0x00, 0x65, 0xcc},
+	{0xb0, 0x16, 0x03, 0xcc},	{0xb3, 0x05, 0x01, 0xcc},
+	{0xb3, 0x06, 0x01, 0xcc},	{0xb3, 0x08, 0x01, 0xcc},
+	{0xb3, 0x09, 0x0c, 0xcc},	{0xb3, 0x34, 0x02, 0xcc},
+	{0xb3, 0x35, 0xc8, 0xcc},	{0xb3, 0x02, 0x00, 0xcc},
+	{0xb3, 0x03, 0x0a, 0xcc},	{0xb3, 0x04, 0x05, 0xcc},
+	{0xb3, 0x20, 0x00, 0xcc},	{0xb3, 0x21, 0x00, 0xcc},
+	{0xb3, 0x22, 0x01, 0xcc},	{0xb3, 0x23, 0xe0, 0xcc},
+	{0xb3, 0x14, 0x00, 0xcc},	{0xb3, 0x15, 0x00, 0xcc},
+	{0xb3, 0x16, 0x02, 0xcc},	{0xb3, 0x17, 0x7f, 0xcc},
+	{0xb3, 0x00, 0x65, 0xcc},	{0xb8, 0x00, 0x00, 0xcc},
+	{0xbc, 0x00, 0xd0, 0xcc},	{0xbc, 0x01, 0x01, 0xcc},
+	{0xf0, 0x00, 0x00, 0xbb},	{0x0d, 0x00, 0x09, 0xbb},
+	{0x00, 0x01, 0x00, 0xdd},	{0x0d, 0x00, 0x08, 0xbb},
+	{0xf0, 0x00, 0x00, 0xbb},	{0x02, 0x00, 0x64, 0xbb},
+	{0x05, 0x01, 0x78, 0xbb},	{0x06, 0x00, 0x11, 0xbb},
+	{0x07, 0x01, 0x42, 0xbb},	{0x08, 0x00, 0x11, 0xbb},
+	{0x20, 0x01, 0x00, 0xbb},	{0x21, 0x80, 0x00, 0xbb},
+	{0x22, 0x0d, 0x0f, 0xbb},	{0x24, 0x80, 0x00, 0xbb},
+	{0x59, 0x00, 0xff, 0xbb},	{0xf0, 0x00, 0x01, 0xbb},
+	{0x9d, 0x3c, 0xa0, 0xbb},	{0x47, 0x30, 0x30, 0xbb},
+	{0xf0, 0x00, 0x00, 0xbb},	{0x0a, 0x80, 0x11, 0xbb},
+	{0x35, 0x00, 0x22, 0xbb},	{0xf0, 0x00, 0x02, 0xbb},
+	{0x9d, 0xc5, 0x05, 0xbb},	{0xdc, 0x0f, 0xfc, 0xbb},
+	{0xf0, 0x00, 0x01, 0xbb},	{0x06, 0x74, 0x0e, 0xbb},
+	{0x80, 0x00, 0x06, 0xbb},	{0x81, 0x04, 0x00, 0xbb},
+	{0x82, 0x01, 0x02, 0xbb},	{0x83, 0x03, 0x02, 0xbb},
+	{0x84, 0x05, 0x00, 0xbb},	{0x85, 0x01, 0x00, 0xbb},
+	{0x86, 0x03, 0x02, 0xbb},	{0x87, 0x05, 0x00, 0xbb},
+	{0x88, 0x01, 0x00, 0xbb},	{0x89, 0x02, 0x02, 0xbb},
+	{0x8a, 0xfd, 0x04, 0xbb},	{0x8b, 0xfc, 0xfd, 0xbb},
+	{0x8c, 0xff, 0xfd, 0xbb},	{0x8d, 0x00, 0x00, 0xbb},
+	{0x8e, 0xfe, 0x05, 0xbb},	{0x8f, 0xfc, 0xfd, 0xbb},
+	{0x90, 0xfe, 0xfd, 0xbb},	{0x91, 0x00, 0x00, 0xbb},
+	{0x92, 0xfe, 0x03, 0xbb},	{0x93, 0xfd, 0xfe, 0xbb},
+	{0x94, 0xff, 0xfd, 0xbb},	{0x95, 0x00, 0x00, 0xbb},
+	{0xb6, 0x07, 0x05, 0xbb},	{0xb7, 0x13, 0x06, 0xbb},
+	{0xb8, 0x08, 0x06, 0xbb},	{0xb9, 0x14, 0x08, 0xbb},
+	{0xba, 0x06, 0x05, 0xbb},	{0xbb, 0x13, 0x06, 0xbb},
+	{0xbc, 0x03, 0x01, 0xbb},	{0xbd, 0x03, 0x04, 0xbb},
+	{0xbe, 0x00, 0x02, 0xbb},	{0xbf, 0x03, 0x01, 0xbb},
+	{0xc0, 0x02, 0x04, 0xbb},	{0xc1, 0x00, 0x04, 0xbb},
+	{0xc2, 0x02, 0x01, 0xbb},	{0xc3, 0x01, 0x03, 0xbb},
+	{0xc4, 0x00, 0x04, 0xbb},	{0xf0, 0x00, 0x02, 0xbb},
+	{0xc8, 0x00, 0x00, 0xbb},	{0x2e, 0x00, 0x00, 0xbb},
+	{0x2e, 0x0c, 0x5b, 0xbb},	{0x2f, 0xd1, 0x00, 0xbb},
+	{0x39, 0x03, 0xca, 0xbb},	{0x3a, 0x06, 0x80, 0xbb},
+	{0x3b, 0x01, 0x52, 0xbb},	{0x3c, 0x05, 0x40, 0xbb},
+	{0x57, 0x01, 0x9c, 0xbb},	{0x58, 0x01, 0xee, 0xbb},
+	{0x59, 0x00, 0xf0, 0xbb},	{0x5a, 0x01, 0x20, 0xbb},
+	{0x5c, 0x1d, 0x17, 0xbb},	{0x5d, 0x22, 0x1c, 0xbb},
+	{0x64, 0x1e, 0x1c, 0xbb},	{0x5b, 0x00, 0x01, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},	{0x36, 0x68, 0x10, 0xbb},
+	{0x00, 0x00, 0x30, 0xdd},	{0x37, 0x81, 0x00, 0xbb},
+	{0xbc, 0x02, 0x18, 0xcc},	{0xbc, 0x03, 0x50, 0xcc},
+	{0xbc, 0x04, 0x18, 0xcc},	{0xbc, 0x05, 0x00, 0xcc},
+	{0xbc, 0x06, 0x00, 0xcc},	{0xbc, 0x08, 0x30, 0xcc},
+	{0xbc, 0x09, 0x40, 0xcc},	{0xbc, 0x0a, 0x10, 0xcc},
+	{0xbc, 0x0b, 0x00, 0xcc},	{0xbc, 0x0c, 0x00, 0xcc},
+	{0xbf, 0xc0, 0x26, 0xcc},	{0xbf, 0xc1, 0x02, 0xcc},
+	{0xbf, 0xcc, 0x04, 0xcc},	{0xb3, 0x5c, 0x01, 0xcc},
+	{0xb3, 0x01, 0x41, 0xcc},
+	{}
+};
+
+static const u8 mi1320_soc_InitVGA[][4] = {
+	{0xb3, 0x01, 0x01, 0xcc},
+	{0xb0, 0x03, 0x19, 0xcc},
+	{0xb0, 0x04, 0x02, 0xcc},
+	{0x00, 0x00, 0x30, 0xdd},
+	{0xb3, 0x00, 0x64, 0xcc},
+	{0xb3, 0x00, 0x67, 0xcc},
+	{0xb3, 0x05, 0x01, 0xcc},
+	{0xb3, 0x06, 0x01, 0xcc},
+	{0xb3, 0x08, 0x01, 0xcc},
+	{0xb3, 0x09, 0x0c, 0xcc},
+	{0xb3, 0x34, 0x02, 0xcc},
+	{0xb3, 0x35, 0xc8, 0xcc},	/* i2c add: 48 */
+	{0xb3, 0x02, 0x00, 0xcc},
+	{0xb3, 0x03, 0x0a, 0xcc},
+	{0xb3, 0x04, 0x05, 0xcc},
+	{0xb3, 0x20, 0x00, 0xcc},
+	{0xb3, 0x21, 0x00, 0xcc},
+	{0xb3, 0x22, 0x01, 0xcc},
+	{0xb3, 0x23, 0xe0, 0xcc},
+	{0xb3, 0x14, 0x00, 0xcc},
+	{0xb3, 0x15, 0x00, 0xcc},
+	{0xb3, 0x16, 0x02, 0xcc},
+	{0xb3, 0x17, 0x7f, 0xcc},
+	{0xb3, 0x00, 0x67, 0xcc},
+	{0xb8, 0x00, 0x00, 0xcc},
+	{0xbc, 0x00, 0x71, 0xcc},
+	{0xbc, 0x01, 0x01, 0xcc},
+	{0xb3, 0x5c, 0x01, 0xcc},
+	{0xf0, 0x00, 0x02, 0xbb},
+	{0x00, 0x00, 0x10, 0xdd},
+	{0xc8, 0x00, 0x00, 0xbb},
+	{0x00, 0x00, 0x30, 0xdd},
+	{0xf0, 0x00, 0x00, 0xbb},
+	{0x00, 0x00, 0x10, 0xdd},
+	{0x07, 0x00, 0xe0, 0xbb},
+	{0x08, 0x00, 0x0b, 0xbb},
+	{0x21, 0x00, 0x0c, 0xbb},
+	{0x20, 0x01, 0x03, 0xbb},	/* h/v flip */
+	{0xbf, 0xc0, 0x26, 0xcc},
+	{0xbf, 0xc1, 0x02, 0xcc},
+	{0xbf, 0xcc, 0x04, 0xcc},
+	{0xb3, 0x01, 0x41, 0xcc},
+	{0xf0, 0x00, 0x00, 0xbb},
+	{0x05, 0x01, 0x78, 0xbb},
+	{0x06, 0x00, 0x11, 0xbb},
+	{0x07, 0x01, 0x42, 0xbb},
+	{0x08, 0x00, 0x11, 0xbb},
+	{0x20, 0x01, 0x03, 0xbb},	/* h/v flip */
+	{0x21, 0x80, 0x00, 0xbb},
+	{0x22, 0x0d, 0x0f, 0xbb},
+	{0x24, 0x80, 0x00, 0xbb},
+	{0x59, 0x00, 0xff, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},
+	{0x39, 0x03, 0xca, 0xbb},
+	{0x3a, 0x06, 0x80, 0xbb},
+	{0x3b, 0x01, 0x52, 0xbb},
+	{0x3c, 0x05, 0x40, 0xbb},
+	{0x57, 0x01, 0x9c, 0xbb},
+	{0x58, 0x01, 0xee, 0xbb},
+	{0x59, 0x00, 0xf0, 0xbb},
+	{0x5a, 0x01, 0x20, 0xbb},
+	{0x5c, 0x1d, 0x17, 0xbb},
+	{0x5d, 0x22, 0x1c, 0xbb},
+	{0x64, 0x1e, 0x1c, 0xbb},
+	{0x5b, 0x00, 0x00, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},
+	{0x22, 0xa0, 0x78, 0xbb},
+	{0x23, 0xa0, 0x78, 0xbb},
+	{0x24, 0x7f, 0x00, 0xbb},
+	{0x28, 0xea, 0x02, 0xbb},
+	{0x29, 0x86, 0x7a, 0xbb},
+	{0x5e, 0x52, 0x4c, 0xbb},
+	{0x5f, 0x20, 0x24, 0xbb},
+	{0x60, 0x00, 0x02, 0xbb},
+	{0x02, 0x00, 0xee, 0xbb},
+	{0x03, 0x39, 0x23, 0xbb},
+	{0x04, 0x07, 0x24, 0xbb},
+	{0x09, 0x00, 0xc0, 0xbb},
+	{0x0a, 0x00, 0x79, 0xbb},
+	{0x0b, 0x00, 0x04, 0xbb},
+	{0x0c, 0x00, 0x5c, 0xbb},
+	{0x0d, 0x00, 0xd9, 0xbb},
+	{0x0e, 0x00, 0x53, 0xbb},
+	{0x0f, 0x00, 0x21, 0xbb},
+	{0x10, 0x00, 0xa4, 0xbb},
+	{0x11, 0x00, 0xe5, 0xbb},
+	{0x15, 0x00, 0x00, 0xbb},
+	{0x16, 0x00, 0x00, 0xbb},
+	{0x17, 0x00, 0x00, 0xbb},
+	{0x18, 0x00, 0x00, 0xbb},
+	{0x19, 0x00, 0x00, 0xbb},
+	{0x1a, 0x00, 0x00, 0xbb},
+	{0x1b, 0x00, 0x00, 0xbb},
+	{0x1c, 0x00, 0x00, 0xbb},
+	{0x1d, 0x00, 0x00, 0xbb},
+	{0x1e, 0x00, 0x00, 0xbb},
+	{0xf0, 0x00, 0x01, 0xbb},
+	{0x06, 0xe0, 0x0e, 0xbb},
+	{0x06, 0x60, 0x0e, 0xbb},
+	{0xb3, 0x5c, 0x01, 0xcc},
+	{}
+};
+static const u8 mi1320_soc_InitQVGA[][4] = {
+	{0xb3, 0x01, 0x01, 0xcc},
+	{0xb0, 0x03, 0x19, 0xcc},
+	{0xb0, 0x04, 0x02, 0xcc},
+	{0x00, 0x00, 0x30, 0xdd},
+	{0xb3, 0x00, 0x64, 0xcc},
+	{0xb3, 0x00, 0x67, 0xcc},
+	{0xb3, 0x05, 0x01, 0xcc},
+	{0xb3, 0x06, 0x01, 0xcc},
+	{0xb3, 0x08, 0x01, 0xcc},
+	{0xb3, 0x09, 0x0c, 0xcc},
+	{0xb3, 0x34, 0x02, 0xcc},
+	{0xb3, 0x35, 0xc8, 0xcc},
+	{0xb3, 0x02, 0x00, 0xcc},
+	{0xb3, 0x03, 0x0a, 0xcc},
+	{0xb3, 0x04, 0x05, 0xcc},
+	{0xb3, 0x20, 0x00, 0xcc},
+	{0xb3, 0x21, 0x00, 0xcc},
+	{0xb3, 0x22, 0x01, 0xcc},
+	{0xb3, 0x23, 0xe0, 0xcc},
+	{0xb3, 0x14, 0x00, 0xcc},
+	{0xb3, 0x15, 0x00, 0xcc},
+	{0xb3, 0x16, 0x02, 0xcc},
+	{0xb3, 0x17, 0x7f, 0xcc},
+	{0xb3, 0x00, 0x67, 0xcc},
+	{0xb8, 0x00, 0x00, 0xcc},
+	{0xbc, 0x00, 0xd1, 0xcc},
+	{0xbc, 0x01, 0x01, 0xcc},
+	{0xb3, 0x5c, 0x01, 0xcc},
+	{0xf0, 0x00, 0x02, 0xbb},
+	{0x00, 0x00, 0x10, 0xdd},
+	{0xc8, 0x00, 0x00, 0xbb},
+	{0x00, 0x00, 0x30, 0xdd},
+	{0xf0, 0x00, 0x00, 0xbb},
+	{0x00, 0x00, 0x10, 0xdd},
+	{0x07, 0x00, 0xe0, 0xbb},
+	{0x08, 0x00, 0x0b, 0xbb},
+	{0x21, 0x00, 0x0c, 0xbb},
+	{0x20, 0x01, 0x03, 0xbb},	/* h/v flip */
+	{0xbf, 0xc0, 0x26, 0xcc},
+	{0xbf, 0xc1, 0x02, 0xcc},
+	{0xbf, 0xcc, 0x04, 0xcc},
+	{0xbc, 0x02, 0x18, 0xcc},
+	{0xbc, 0x03, 0x50, 0xcc},
+	{0xbc, 0x04, 0x18, 0xcc},
+	{0xbc, 0x05, 0x00, 0xcc},
+	{0xbc, 0x06, 0x00, 0xcc},
+	{0xbc, 0x08, 0x30, 0xcc},
+	{0xbc, 0x09, 0x40, 0xcc},
+	{0xbc, 0x0a, 0x10, 0xcc},
+	{0xbc, 0x0b, 0x00, 0xcc},
+	{0xbc, 0x0c, 0x00, 0xcc},
+	{0xb3, 0x01, 0x41, 0xcc},
+	{0xf0, 0x00, 0x00, 0xbb},
+	{0x05, 0x01, 0x78, 0xbb},
+	{0x06, 0x00, 0x11, 0xbb},
+	{0x07, 0x01, 0x42, 0xbb},
+	{0x08, 0x00, 0x11, 0xbb},
+	{0x20, 0x01, 0x03, 0xbb},	/* h/v flip */
+	{0x21, 0x80, 0x00, 0xbb},
+	{0x22, 0x0d, 0x0f, 0xbb},
+	{0x24, 0x80, 0x00, 0xbb},
+	{0x59, 0x00, 0xff, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},
+	{0x39, 0x03, 0xca, 0xbb},
+	{0x3a, 0x06, 0x80, 0xbb},
+	{0x3b, 0x01, 0x52, 0xbb},
+	{0x3c, 0x05, 0x40, 0xbb},
+	{0x57, 0x01, 0x9c, 0xbb},
+	{0x58, 0x01, 0xee, 0xbb},
+	{0x59, 0x00, 0xf0, 0xbb},
+	{0x5a, 0x01, 0x20, 0xbb},
+	{0x5c, 0x1d, 0x17, 0xbb},
+	{0x5d, 0x22, 0x1c, 0xbb},
+	{0x64, 0x1e, 0x1c, 0xbb},
+	{0x5b, 0x00, 0x00, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},
+	{0x22, 0xa0, 0x78, 0xbb},
+	{0x23, 0xa0, 0x78, 0xbb},
+	{0x24, 0x7f, 0x00, 0xbb},
+	{0x28, 0xea, 0x02, 0xbb},
+	{0x29, 0x86, 0x7a, 0xbb},
+	{0x5e, 0x52, 0x4c, 0xbb},
+	{0x5f, 0x20, 0x24, 0xbb},
+	{0x60, 0x00, 0x02, 0xbb},
+	{0x02, 0x00, 0xee, 0xbb},
+	{0x03, 0x39, 0x23, 0xbb},
+	{0x04, 0x07, 0x24, 0xbb},
+	{0x09, 0x00, 0xc0, 0xbb},
+	{0x0a, 0x00, 0x79, 0xbb},
+	{0x0b, 0x00, 0x04, 0xbb},
+	{0x0c, 0x00, 0x5c, 0xbb},
+	{0x0d, 0x00, 0xd9, 0xbb},
+	{0x0e, 0x00, 0x53, 0xbb},
+	{0x0f, 0x00, 0x21, 0xbb},
+	{0x10, 0x00, 0xa4, 0xbb},
+	{0x11, 0x00, 0xe5, 0xbb},
+	{0x15, 0x00, 0x00, 0xbb},
+	{0x16, 0x00, 0x00, 0xbb},
+	{0x17, 0x00, 0x00, 0xbb},
+	{0x18, 0x00, 0x00, 0xbb},
+	{0x19, 0x00, 0x00, 0xbb},
+	{0x1a, 0x00, 0x00, 0xbb},
+	{0x1b, 0x00, 0x00, 0xbb},
+	{0x1c, 0x00, 0x00, 0xbb},
+	{0x1d, 0x00, 0x00, 0xbb},
+	{0x1e, 0x00, 0x00, 0xbb},
+	{0xf0, 0x00, 0x01, 0xbb},
+	{0x06, 0xe0, 0x0e, 0xbb},
+	{0x06, 0x60, 0x0e, 0xbb},
+	{0xb3, 0x5c, 0x01, 0xcc},
+	{}
+};
+static const u8 mi1320_soc_InitSXGA[][4] = {
+	{0xb3, 0x01, 0x01, 0xcc},
+	{0xb0, 0x03, 0x19, 0xcc},
+	{0x00, 0x00, 0x30, 0xdd},
+	{0xb3, 0x00, 0x64, 0xcc},
+	{0xb3, 0x00, 0x67, 0xcc},
+	{0xb3, 0x05, 0x01, 0xcc},
+	{0xb3, 0x06, 0x01, 0xcc},
+	{0xb3, 0x08, 0x01, 0xcc},
+	{0xb3, 0x09, 0x0c, 0xcc},
+	{0xb3, 0x34, 0x02, 0xcc},
+	{0xb3, 0x35, 0xc8, 0xcc},
+	{0xb3, 0x02, 0x00, 0xcc},
+	{0xb3, 0x03, 0x0a, 0xcc},
+	{0xb3, 0x04, 0x05, 0xcc},
+	{0xb3, 0x20, 0x00, 0xcc},
+	{0xb3, 0x21, 0x00, 0xcc},
+	{0xb3, 0x22, 0x04, 0xcc},
+	{0xb3, 0x23, 0x00, 0xcc},
+	{0xb3, 0x14, 0x00, 0xcc},
+	{0xb3, 0x15, 0x00, 0xcc},
+	{0xb3, 0x16, 0x04, 0xcc},
+	{0xb3, 0x17, 0xff, 0xcc},
+	{0xb3, 0x00, 0x67, 0xcc},
+	{0xbc, 0x00, 0x71, 0xcc},
+	{0xbc, 0x01, 0x01, 0xcc},
+	{0xb3, 0x5c, 0x01, 0xcc},
+	{0xf0, 0x00, 0x02, 0xbb},
+	{0x00, 0x00, 0x30, 0xdd},
+	{0xc8, 0x9f, 0x0b, 0xbb},
+	{0x00, 0x00, 0x20, 0xdd},
+	{0x5b, 0x00, 0x01, 0xbb},
+	{0x00, 0x00, 0x20, 0xdd},
+	{0xf0, 0x00, 0x00, 0xbb},
+	{0x00, 0x00, 0x30, 0xdd},
+	{0x20, 0x01, 0x03, 0xbb},	/* h/v flip */
+	{0x00, 0x00, 0x20, 0xdd},
+	{0xbf, 0xc0, 0x26, 0xcc},
+	{0xbf, 0xc1, 0x02, 0xcc},
+	{0xbf, 0xcc, 0x04, 0xcc},
+	{0xb3, 0x01, 0x41, 0xcc},
+	{0xf0, 0x00, 0x00, 0xbb},
+	{0x05, 0x01, 0x78, 0xbb},
+	{0x06, 0x00, 0x11, 0xbb},
+	{0x07, 0x01, 0x42, 0xbb},
+	{0x08, 0x00, 0x11, 0xbb},
+	{0x20, 0x01, 0x03, 0xbb},	/* h/v flip */
+	{0x21, 0x80, 0x00, 0xbb},
+	{0x22, 0x0d, 0x0f, 0xbb},
+	{0x24, 0x80, 0x00, 0xbb},
+	{0x59, 0x00, 0xff, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},
+	{0x39, 0x03, 0xca, 0xbb},
+	{0x3a, 0x06, 0x80, 0xbb},
+	{0x3b, 0x01, 0x52, 0xbb},
+	{0x3c, 0x05, 0x40, 0xbb},
+	{0x57, 0x01, 0x9c, 0xbb},
+	{0x58, 0x01, 0xee, 0xbb},
+	{0x59, 0x00, 0xf0, 0xbb},
+	{0x5a, 0x01, 0x20, 0xbb},
+	{0x5c, 0x1d, 0x17, 0xbb},
+	{0x5d, 0x22, 0x1c, 0xbb},
+	{0x64, 0x1e, 0x1c, 0xbb},
+	{0x5b, 0x00, 0x00, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},
+	{0x22, 0xa0, 0x78, 0xbb},
+	{0x23, 0xa0, 0x78, 0xbb},
+	{0x24, 0x7f, 0x00, 0xbb},
+	{0x28, 0xea, 0x02, 0xbb},
+	{0x29, 0x86, 0x7a, 0xbb},
+	{0x5e, 0x52, 0x4c, 0xbb},
+	{0x5f, 0x20, 0x24, 0xbb},
+	{0x60, 0x00, 0x02, 0xbb},
+	{0x02, 0x00, 0xee, 0xbb},
+	{0x03, 0x39, 0x23, 0xbb},
+	{0x04, 0x07, 0x24, 0xbb},
+	{0x09, 0x00, 0xc0, 0xbb},
+	{0x0a, 0x00, 0x79, 0xbb},
+	{0x0b, 0x00, 0x04, 0xbb},
+	{0x0c, 0x00, 0x5c, 0xbb},
+	{0x0d, 0x00, 0xd9, 0xbb},
+	{0x0e, 0x00, 0x53, 0xbb},
+	{0x0f, 0x00, 0x21, 0xbb},
+	{0x10, 0x00, 0xa4, 0xbb},
+	{0x11, 0x00, 0xe5, 0xbb},
+	{0x15, 0x00, 0x00, 0xbb},
+	{0x16, 0x00, 0x00, 0xbb},
+	{0x17, 0x00, 0x00, 0xbb},
+	{0x18, 0x00, 0x00, 0xbb},
+	{0x19, 0x00, 0x00, 0xbb},
+	{0x1a, 0x00, 0x00, 0xbb},
+	{0x1b, 0x00, 0x00, 0xbb},
+	{0x1c, 0x00, 0x00, 0xbb},
+	{0x1d, 0x00, 0x00, 0xbb},
+	{0x1e, 0x00, 0x00, 0xbb},
+	{0xf0, 0x00, 0x01, 0xbb},
+	{0x06, 0xe0, 0x0e, 0xbb},
+	{0x06, 0x60, 0x0e, 0xbb},
+	{0xb3, 0x5c, 0x01, 0xcc},
+	{0xf0, 0x00, 0x00, 0xbb},
+	{0x05, 0x01, 0x13, 0xbb},
+	{0x06, 0x00, 0x11, 0xbb},
+	{0x07, 0x00, 0x85, 0xbb},
+	{0x08, 0x00, 0x27, 0xbb},
+	{0x20, 0x01, 0x03, 0xbb},	/* h/v flip */
+	{0x21, 0x80, 0x00, 0xbb},
+	{0x22, 0x0d, 0x0f, 0xbb},
+	{0x24, 0x80, 0x00, 0xbb},
+	{0x59, 0x00, 0xff, 0xbb},
+	{0xf0, 0x00, 0x02, 0xbb},
+	{0x39, 0x03, 0x0d, 0xbb},
+	{0x3a, 0x06, 0x1b, 0xbb},
+	{0x3b, 0x00, 0x95, 0xbb},
+	{0x3c, 0x04, 0xdb, 0xbb},
+	{0x57, 0x02, 0x00, 0xbb},
+	{0x58, 0x02, 0x66, 0xbb},
+	{0x59, 0x00, 0xff, 0xbb},
+	{0x5a, 0x01, 0x33, 0xbb},
+	{0x5c, 0x12, 0x0d, 0xbb},
+	{0x5d, 0x16, 0x11, 0xbb},
+	{0x64, 0x5e, 0x1c, 0xbb},
+	{}
+};
+static const u8 po3130_gamma[17] = {
+	0x00, 0x13, 0x38, 0x59, 0x79, 0x92, 0xa7, 0xb9, 0xc8,
+	0xd4, 0xdf, 0xe7, 0xee, 0xf4, 0xf9, 0xfc, 0xff
+};
+static const u8 po3130_matrix[9] = {
+	0x5f, 0xec, 0xf5, 0xf1, 0x5a, 0xf5, 0xf1, 0xec, 0x63
+};
+
+static const u8 po3130_initVGA_data[][4] = {
+	{0xb0, 0x4d, 0x00, 0xcc},	{0xb3, 0x01, 0x01, 0xcc},
+	{0x00, 0x00, 0x50, 0xdd},	{0xb0, 0x03, 0x01, 0xcc},
+	{0xb3, 0x00, 0x04, 0xcc},	{0xb3, 0x00, 0x24, 0xcc},
+	{0xb3, 0x00, 0x25, 0xcc},	{0xb3, 0x08, 0x01, 0xcc},
+	{0xb3, 0x09, 0x0c, 0xcc},	{0xb3, 0x05, 0x00, 0xcc},
+	{0xb3, 0x06, 0x01, 0xcc},	{0xb3, 0x03, 0x1a, 0xcc},
+	{0xb3, 0x04, 0x15, 0xcc},	{0xb3, 0x20, 0x00, 0xcc},
+	{0xb3, 0x21, 0x00, 0xcc},	{0xb3, 0x22, 0x01, 0xcc},
+	{0xb3, 0x23, 0xe8, 0xcc},	{0xb8, 0x08, 0xe8, 0xcc},
+	{0xb3, 0x14, 0x00, 0xcc},	{0xb3, 0x15, 0x00, 0xcc},
+	{0xb3, 0x16, 0x02, 0xcc},	{0xb3, 0x17, 0x7f, 0xcc},
+	{0xb3, 0x34, 0x01, 0xcc},
+	{0xb3, 0x35, 0xf6, 0xcc},	/* i2c add: 76 */
+	{0xb3, 0x00, 0x27, 0xcc},	{0xbc, 0x00, 0x71, 0xcc},
+	{0xb8, 0x00, 0x21, 0xcc},	{0xb8, 0x27, 0x20, 0xcc},
+	{0xb8, 0x01, 0x79, 0xcc},	{0xb8, 0x81, 0x09, 0xcc},
+	{0xb8, 0x2c, 0x50, 0xcc},	{0xb8, 0x2d, 0xf8, 0xcc},
+	{0xb8, 0x2e, 0xf8, 0xcc},	{0xb8, 0x2f, 0xf8, 0xcc},
+	{0xb8, 0x30, 0x50, 0xcc},	{0xb8, 0x31, 0xf8, 0xcc},
+	{0xb8, 0x32, 0xf8, 0xcc},	{0xb8, 0x33, 0xf8, 0xcc},
+	{0xb8, 0x34, 0x50, 0xcc},	{0xb8, 0x35, 0x00, 0xcc},
+	{0xb8, 0x36, 0x00, 0xcc},	{0xb8, 0x37, 0x00, 0xcc},
+	{0x00, 0x1e, 0xc6, 0xaa},	{0x00, 0x20, 0x44, 0xaa},
+	{0x00, 0xad, 0x02, 0xaa},	{0x00, 0xae, 0x2c, 0xaa},
+	{0x00, 0x12, 0x08, 0xaa},	{0x00, 0x17, 0x41, 0xaa},
+	{0x00, 0x19, 0x41, 0xaa},	{0x00, 0x1e, 0x06, 0xaa},
+	{0x00, 0x21, 0x00, 0xaa},	{0x00, 0x36, 0xc0, 0xaa},
+	{0x00, 0x37, 0xc8, 0xaa},	{0x00, 0x3b, 0x36, 0xaa},
+	{0x00, 0x4b, 0xfe, 0xaa},	{0x00, 0x51, 0x1c, 0xaa},
+	{0x00, 0x52, 0x01, 0xaa},	{0x00, 0x55, 0x0a, 0xaa},
+	{0x00, 0x59, 0x02, 0xaa},	{0x00, 0x5a, 0x04, 0xaa},
+	{0x00, 0x5c, 0x10, 0xaa},	{0x00, 0x5d, 0x10, 0xaa},
+	{0x00, 0x5e, 0x10, 0xaa},	{0x00, 0x5f, 0x10, 0xaa},
+	{0x00, 0x61, 0x00, 0xaa},	{0x00, 0x62, 0x18, 0xaa},
+	{0x00, 0x63, 0x30, 0xaa},	{0x00, 0x70, 0x68, 0xaa},
+	{0x00, 0x80, 0x71, 0xaa},	{0x00, 0x81, 0x08, 0xaa},
+	{0x00, 0x82, 0x00, 0xaa},	{0x00, 0x83, 0x55, 0xaa},
+	{0x00, 0x84, 0x06, 0xaa},	{0x00, 0x85, 0x06, 0xaa},
+	{0x00, 0x86, 0x13, 0xaa},	{0x00, 0x87, 0x18, 0xaa},
+	{0x00, 0xaa, 0x3f, 0xaa},	{0x00, 0xab, 0x44, 0xaa},
+	{0x00, 0xb0, 0x68, 0xaa},	{0x00, 0xb5, 0x10, 0xaa},
+	{0x00, 0xb8, 0x20, 0xaa},	{0x00, 0xb9, 0xa0, 0xaa},
+	{0x00, 0xbc, 0x04, 0xaa},	{0x00, 0x8b, 0x40, 0xaa},
+	{0x00, 0x8c, 0x91, 0xaa},	{0x00, 0x8d, 0x8f, 0xaa},
+	{0x00, 0x8e, 0x91, 0xaa},	{0x00, 0x8f, 0x43, 0xaa},
+	{0x00, 0x90, 0x92, 0xaa},	{0x00, 0x91, 0x89, 0xaa},
+	{0x00, 0x92, 0x9d, 0xaa},	{0x00, 0x93, 0x46, 0xaa},
+	{0x00, 0xd6, 0x22, 0xaa},	{0x00, 0x73, 0x00, 0xaa},
+	{0x00, 0x74, 0x10, 0xaa},	{0x00, 0x75, 0x20, 0xaa},
+	{0x00, 0x76, 0x2b, 0xaa},	{0x00, 0x77, 0x36, 0xaa},
+	{0x00, 0x78, 0x49, 0xaa},	{0x00, 0x79, 0x5a, 0xaa},
+	{0x00, 0x7a, 0x7f, 0xaa},	{0x00, 0x7b, 0x9b, 0xaa},
+	{0x00, 0x7c, 0xba, 0xaa},	{0x00, 0x7d, 0xd4, 0xaa},
+	{0x00, 0x7e, 0xea, 0xaa},	{0x00, 0xd6, 0x62, 0xaa},
+	{0x00, 0x73, 0x00, 0xaa},	{0x00, 0x74, 0x10, 0xaa},
+	{0x00, 0x75, 0x20, 0xaa},	{0x00, 0x76, 0x2b, 0xaa},
+	{0x00, 0x77, 0x36, 0xaa},	{0x00, 0x78, 0x49, 0xaa},
+	{0x00, 0x79, 0x5a, 0xaa},	{0x00, 0x7a, 0x7f, 0xaa},
+	{0x00, 0x7b, 0x9b, 0xaa},	{0x00, 0x7c, 0xba, 0xaa},
+	{0x00, 0x7d, 0xd4, 0xaa},	{0x00, 0x7e, 0xea, 0xaa},
+	{0x00, 0xd6, 0xa2, 0xaa},	{0x00, 0x73, 0x00, 0xaa},
+	{0x00, 0x74, 0x10, 0xaa},	{0x00, 0x75, 0x20, 0xaa},
+	{0x00, 0x76, 0x2b, 0xaa},	{0x00, 0x77, 0x36, 0xaa},
+	{0x00, 0x78, 0x49, 0xaa},	{0x00, 0x79, 0x5a, 0xaa},
+	{0x00, 0x7a, 0x7f, 0xaa},	{0x00, 0x7b, 0x9b, 0xaa},
+	{0x00, 0x7c, 0xba, 0xaa},	{0x00, 0x7d, 0xd4, 0xaa},
+	{0x00, 0x7e, 0xea, 0xaa},
+	{0x00, 0x4c, 0x07, 0xaa},
+	{0x00, 0x4b, 0xe0, 0xaa},	{0x00, 0x4e, 0x77, 0xaa},
+	{0x00, 0x59, 0x02, 0xaa},	{0x00, 0x4d, 0x0a, 0xaa},
+/*	{0x00, 0xd1, 0x00, 0xaa},	{0x00, 0x20, 0xc4, 0xaa},
+	{0xb8, 0x8e, 0x00, 0xcc},	{0xb8, 0x8f, 0xff, 0xcc}, */
+	{0x00, 0xd1, 0x3c, 0xaa},	{0x00, 0x20, 0xc4, 0xaa},
+	{0xb8, 0x8e, 0x00, 0xcc},	{0xb8, 0x8f, 0xff, 0xcc},
+	{0xb8, 0xfe, 0x00, 0xcc},	{0xb8, 0xff, 0x28, 0xcc},
+	{0xb9, 0x00, 0x28, 0xcc},	{0xb9, 0x01, 0x28, 0xcc},
+	{0xb9, 0x02, 0x28, 0xcc},	{0xb9, 0x03, 0x00, 0xcc},
+	{0xb9, 0x04, 0x00, 0xcc},	{0xb9, 0x05, 0x3c, 0xcc},
+	{0xb9, 0x06, 0x3c, 0xcc},	{0xb9, 0x07, 0x3c, 0xcc},
+	{0xb9, 0x08, 0x3c, 0xcc},	{0x00, 0x05, 0x00, 0xaa},
+	{0xb3, 0x5c, 0x00, 0xcc},	{0xb3, 0x01, 0x41, 0xcc},
+	{}
+};
+static const u8 po3130_rundata[][4] = {
+	{0x00, 0x47, 0x45, 0xaa},	{0x00, 0x48, 0x9b, 0xaa},
+	{0x00, 0x49, 0x3a, 0xaa},	{0x00, 0x4a, 0x01, 0xaa},
+	{0x00, 0x44, 0x40, 0xaa},
+/*	{0x00, 0xd5, 0x7c, 0xaa}, */
+	{0x00, 0xad, 0x04, 0xaa},	{0x00, 0xae, 0x00, 0xaa},
+	{0x00, 0xb0, 0x78, 0xaa},	{0x00, 0x98, 0x02, 0xaa},
+	{0x00, 0x94, 0x25, 0xaa},	{0x00, 0x95, 0x25, 0xaa},
+	{0x00, 0x59, 0x68, 0xaa},	{0x00, 0x44, 0x20, 0xaa},
+	{0x00, 0x17, 0x50, 0xaa},	{0x00, 0x19, 0x50, 0xaa},
+	{0x00, 0xd1, 0x3c, 0xaa},	{0x00, 0xd1, 0x3c, 0xaa},
+	{0x00, 0x1e, 0x06, 0xaa},	{0x00, 0x1e, 0x06, 0xaa},
+	{}
+};
+
+static const u8 po3130_initQVGA_data[][4] = {
+	{0xb0, 0x4d, 0x00, 0xcc},	{0xb3, 0x01, 0x01, 0xcc},
+	{0x00, 0x00, 0x50, 0xdd},	{0xb0, 0x03, 0x09, 0xcc},
+	{0xb3, 0x00, 0x04, 0xcc},	{0xb3, 0x00, 0x24, 0xcc},
+	{0xb3, 0x00, 0x25, 0xcc},	{0xb3, 0x08, 0x01, 0xcc},
+	{0xb3, 0x09, 0x0c, 0xcc},	{0xb3, 0x05, 0x00, 0xcc},
+	{0xb3, 0x06, 0x01, 0xcc},	{0xb3, 0x03, 0x1a, 0xcc},
+	{0xb3, 0x04, 0x15, 0xcc},	{0xb3, 0x20, 0x00, 0xcc},
+	{0xb3, 0x21, 0x00, 0xcc},	{0xb3, 0x22, 0x01, 0xcc},
+	{0xb3, 0x23, 0xe0, 0xcc},	{0xb8, 0x08, 0xe0, 0xcc},
+	{0xb3, 0x14, 0x00, 0xcc},	{0xb3, 0x15, 0x00, 0xcc},
+	{0xb3, 0x16, 0x02, 0xcc},	{0xb3, 0x17, 0x7f, 0xcc},
+	{0xb3, 0x34, 0x01, 0xcc},	{0xb3, 0x35, 0xf6, 0xcc},
+	{0xb3, 0x00, 0x27, 0xcc},	{0xbc, 0x00, 0xd1, 0xcc},
+	{0xb8, 0x00, 0x21, 0xcc},	{0xb8, 0x27, 0x20, 0xcc},
+	{0xb8, 0x01, 0x79, 0xcc},	{0xb8, 0x81, 0x09, 0xcc},
+	{0xb8, 0x2c, 0x50, 0xcc},	{0xb8, 0x2d, 0xf8, 0xcc},
+	{0xb8, 0x2e, 0xf8, 0xcc},	{0xb8, 0x2f, 0xf8, 0xcc},
+	{0xb8, 0x30, 0x50, 0xcc},	{0xb8, 0x31, 0xf8, 0xcc},
+	{0xb8, 0x32, 0xf8, 0xcc},	{0xb8, 0x33, 0xf8, 0xcc},
+	{0xb8, 0x34, 0x50, 0xcc},	{0xb8, 0x35, 0x00, 0xcc},
+	{0xb8, 0x36, 0x00, 0xcc},	{0xb8, 0x37, 0x00, 0xcc},
+	{0x00, 0x1e, 0xc6, 0xaa},	{0x00, 0x20, 0x44, 0xaa},
+	{0x00, 0xad, 0x02, 0xaa},	{0x00, 0xae, 0x2c, 0xaa},
+	{0x00, 0x12, 0x08, 0xaa},	{0x00, 0x17, 0x41, 0xaa},
+	{0x00, 0x19, 0x41, 0xaa},	{0x00, 0x1e, 0x06, 0xaa},
+	{0x00, 0x21, 0x00, 0xaa},	{0x00, 0x36, 0xc0, 0xaa},
+	{0x00, 0x37, 0xc8, 0xaa},	{0x00, 0x3b, 0x36, 0xaa},
+	{0x00, 0x4b, 0xfe, 0xaa},	{0x00, 0x51, 0x1c, 0xaa},
+	{0x00, 0x52, 0x01, 0xaa},	{0x00, 0x55, 0x0a, 0xaa},
+	{0x00, 0x59, 0x6f, 0xaa},	{0x00, 0x5a, 0x04, 0xaa},
+	{0x00, 0x5c, 0x10, 0xaa},	{0x00, 0x5d, 0x10, 0xaa},
+	{0x00, 0x5e, 0x10, 0xaa},	{0x00, 0x5f, 0x10, 0xaa},
+	{0x00, 0x61, 0x00, 0xaa},	{0x00, 0x62, 0x18, 0xaa},
+	{0x00, 0x63, 0x30, 0xaa},	{0x00, 0x70, 0x68, 0xaa},
+	{0x00, 0x80, 0x71, 0xaa},	{0x00, 0x81, 0x08, 0xaa},
+	{0x00, 0x82, 0x00, 0xaa},	{0x00, 0x83, 0x55, 0xaa},
+	{0x00, 0x84, 0x06, 0xaa},	{0x00, 0x85, 0x06, 0xaa},
+	{0x00, 0x86, 0x13, 0xaa},	{0x00, 0x87, 0x18, 0xaa},
+	{0x00, 0xaa, 0x3f, 0xaa},	{0x00, 0xab, 0x44, 0xaa},
+	{0x00, 0xb0, 0x68, 0xaa},	{0x00, 0xb5, 0x10, 0xaa},
+	{0x00, 0xb8, 0x20, 0xaa},	{0x00, 0xb9, 0xa0, 0xaa},
+	{0x00, 0xbc, 0x04, 0xaa},	{0x00, 0x8b, 0x40, 0xaa},
+	{0x00, 0x8c, 0x91, 0xaa},	{0x00, 0x8d, 0x8f, 0xaa},
+	{0x00, 0x8e, 0x91, 0xaa},	{0x00, 0x8f, 0x43, 0xaa},
+	{0x00, 0x90, 0x92, 0xaa},	{0x00, 0x91, 0x89, 0xaa},
+	{0x00, 0x92, 0x9d, 0xaa},	{0x00, 0x93, 0x46, 0xaa},
+	{0x00, 0xd6, 0x22, 0xaa},	{0x00, 0x73, 0x00, 0xaa},
+	{0x00, 0x74, 0x10, 0xaa},	{0x00, 0x75, 0x20, 0xaa},
+	{0x00, 0x76, 0x2b, 0xaa},	{0x00, 0x77, 0x36, 0xaa},
+	{0x00, 0x78, 0x49, 0xaa},	{0x00, 0x79, 0x5a, 0xaa},
+	{0x00, 0x7a, 0x7f, 0xaa},	{0x00, 0x7b, 0x9b, 0xaa},
+	{0x00, 0x7c, 0xba, 0xaa},	{0x00, 0x7d, 0xd4, 0xaa},
+	{0x00, 0x7e, 0xea, 0xaa},	{0x00, 0xd6, 0x62, 0xaa},
+	{0x00, 0x73, 0x00, 0xaa},	{0x00, 0x74, 0x10, 0xaa},
+	{0x00, 0x75, 0x20, 0xaa},	{0x00, 0x76, 0x2b, 0xaa},
+	{0x00, 0x77, 0x36, 0xaa},	{0x00, 0x78, 0x49, 0xaa},
+	{0x00, 0x79, 0x5a, 0xaa},	{0x00, 0x7a, 0x7f, 0xaa},
+	{0x00, 0x7b, 0x9b, 0xaa},	{0x00, 0x7c, 0xba, 0xaa},
+	{0x00, 0x7d, 0xd4, 0xaa},	{0x00, 0x7e, 0xea, 0xaa},
+	{0x00, 0xd6, 0xa2, 0xaa},	{0x00, 0x73, 0x00, 0xaa},
+	{0x00, 0x74, 0x10, 0xaa},	{0x00, 0x75, 0x20, 0xaa},
+	{0x00, 0x76, 0x2b, 0xaa},	{0x00, 0x77, 0x36, 0xaa},
+	{0x00, 0x78, 0x49, 0xaa},	{0x00, 0x79, 0x5a, 0xaa},
+	{0x00, 0x7a, 0x7f, 0xaa},	{0x00, 0x7b, 0x9b, 0xaa},
+	{0x00, 0x7c, 0xba, 0xaa},	{0x00, 0x7d, 0xd4, 0xaa},
+	{0x00, 0x7e, 0xea, 0xaa},	{0x00, 0x4c, 0x07, 0xaa},
+	{0x00, 0x4b, 0xe0, 0xaa},	{0x00, 0x4e, 0x77, 0xaa},
+	{0x00, 0x59, 0x66, 0xaa},	{0x00, 0x4d, 0x0a, 0xaa},
+	{0x00, 0xd1, 0x00, 0xaa},	{0x00, 0x20, 0xc4, 0xaa},
+	{0xb8, 0x8e, 0x00, 0xcc},	{0xb8, 0x8f, 0xff, 0xcc},
+	{0xb8, 0xfe, 0x00, 0xcc},	{0xb8, 0xff, 0x28, 0xcc},
+	{0xb9, 0x00, 0x28, 0xcc},	{0xb9, 0x01, 0x28, 0xcc},
+	{0xb9, 0x02, 0x28, 0xcc},	{0xb9, 0x03, 0x00, 0xcc},
+	{0xb9, 0x04, 0x00, 0xcc},	{0xb9, 0x05, 0x3c, 0xcc},
+	{0xb9, 0x06, 0x3c, 0xcc},	{0xb9, 0x07, 0x3c, 0xcc},
+	{0xb9, 0x08, 0x3c, 0xcc},	{0xbc, 0x02, 0x18, 0xcc},
+	{0xbc, 0x03, 0x50, 0xcc},	{0xbc, 0x04, 0x18, 0xcc},
+	{0xbc, 0x05, 0x00, 0xcc},	{0xbc, 0x06, 0x00, 0xcc},
+	{0xbc, 0x08, 0x30, 0xcc},	{0xbc, 0x09, 0x40, 0xcc},
+	{0xbc, 0x0a, 0x10, 0xcc},	{0xbc, 0x0b, 0x00, 0xcc},
+	{0xbc, 0x0c, 0x00, 0xcc},	{0x00, 0x05, 0x00, 0xaa},
+	{0xb3, 0x5c, 0x00, 0xcc},	{0xb3, 0x01, 0x41, 0xcc},
+	{}
+};
+
+static const u8 hv7131r_gamma[17] = {
+	0x00, 0x13, 0x38, 0x59, 0x79, 0x92, 0xa7, 0xb9, 0xc8,
+	0xd4, 0xdf, 0xe7, 0xee, 0xf4, 0xf9, 0xfc, 0xff
+};
+static const u8 hv7131r_matrix[9] = {
+	0x5f, 0xec, 0xf5, 0xf1, 0x5a, 0xf5, 0xf1, 0xec, 0x63
+};
+static const u8 hv7131r_initVGA_data[][4] = {
+	{0xb3, 0x01, 0x01, 0xcc},
+	{0xb0, 0x03, 0x19, 0xcc},
+	{0xb0, 0x04, 0x02, 0xcc},
+	{0x00, 0x00, 0x20, 0xdd},
+	{0xb3, 0x00, 0x24, 0xcc},
+	{0xb3, 0x00, 0x25, 0xcc},
+	{0xb3, 0x08, 0x01, 0xcc},
+	{0xb3, 0x09, 0x0c, 0xcc},
+	{0xb3, 0x05, 0x01, 0xcc},
+	{0xb3, 0x06, 0x03, 0xcc},
+	{0xb3, 0x01, 0x45, 0xcc},
+	{0xb3, 0x03, 0x0b, 0xcc},
+	{0xb3, 0x04, 0x05, 0xcc},
+	{0xb3, 0x20, 0x00, 0xcc},
+	{0xb3, 0x21, 0x00, 0xcc},
+	{0xb3, 0x22, 0x01, 0xcc},
+	{0xb3, 0x23, 0xe0, 0xcc},
+	{0xb3, 0x14, 0x00, 0xcc},
+	{0xb3, 0x15, 0x02, 0xcc},
+	{0xb3, 0x16, 0x02, 0xcc},
+	{0xb3, 0x17, 0x7f, 0xcc},
+	{0xb3, 0x34, 0x01, 0xcc},
+	{0xb3, 0x35, 0x91, 0xcc},	/* i2c add: 11 */
+	{0xb3, 0x00, 0x27, 0xcc},
+	{0xbc, 0x00, 0x73, 0xcc},
+	{0xb8, 0x00, 0x23, 0xcc},
+	{0xb8, 0x2c, 0x50, 0xcc},
+	{0xb8, 0x2d, 0xf8, 0xcc},
+	{0xb8, 0x2e, 0xf8, 0xcc},
+	{0xb8, 0x2f, 0xf8, 0xcc},
+	{0xb8, 0x30, 0x50, 0xcc},
+	{0xb8, 0x31, 0xf8, 0xcc},
+	{0xb8, 0x32, 0xf8, 0xcc},
+	{0xb8, 0x33, 0xf8, 0xcc},
+	{0xb8, 0x34, 0x58, 0xcc},
+	{0xb8, 0x35, 0x00, 0xcc},
+	{0xb8, 0x36, 0x00, 0xcc},
+	{0xb8, 0x37, 0x00, 0xcc},
+	{0xb8, 0x27, 0x20, 0xcc},
+	{0xb8, 0x01, 0x7d, 0xcc},
+	{0xb8, 0x81, 0x09, 0xcc},
+	{0xb3, 0x01, 0x41, 0xcc},
+	{0xb8, 0x8e, 0x00, 0xcc},
+	{0xb8, 0x8f, 0xff, 0xcc},
+	{0x00, 0x01, 0x0c, 0xaa},
+	{0x00, 0x14, 0x01, 0xaa},
+	{0x00, 0x15, 0xe6, 0xaa},
+	{0x00, 0x16, 0x02, 0xaa},
+	{0x00, 0x17, 0x86, 0xaa},
+	{0x00, 0x23, 0x00, 0xaa},
+	{0x00, 0x25, 0x03, 0xaa},
+	{0x00, 0x26, 0xa9, 0xaa},
+	{0x00, 0x27, 0x80, 0xaa},
+	{0x00, 0x30, 0x18, 0xaa},
+	{0xb6, 0x00, 0x00, 0xcc},
+	{0xb6, 0x03, 0x02, 0xcc},
+	{0xb6, 0x02, 0x80, 0xcc},
+	{0xb6, 0x05, 0x01, 0xcc},
+	{0xb6, 0x04, 0xe0, 0xcc},
+	{0xb6, 0x12, 0x78, 0xcc},
+	{0xb6, 0x18, 0x02, 0xcc},
+	{0xb6, 0x17, 0x58, 0xcc},
+	{0xb6, 0x16, 0x00, 0xcc},
+	{0xb6, 0x22, 0x12, 0xcc},
+	{0xb6, 0x23, 0x0b, 0xcc},
+	{0xb3, 0x02, 0x02, 0xcc},
+	{0xbf, 0xc0, 0x39, 0xcc},
+	{0xbf, 0xc1, 0x04, 0xcc},
+	{0xbf, 0xcc, 0x10, 0xcc},
+	{0xb6, 0x12, 0xf8, 0xcc},
+	{0xb6, 0x13, 0x13, 0xcc},
+	{0xb9, 0x12, 0x00, 0xcc},
+	{0xb9, 0x13, 0x0a, 0xcc},
+	{0xb9, 0x14, 0x0a, 0xcc},
+	{0xb9, 0x15, 0x0a, 0xcc},
+	{0xb9, 0x16, 0x0a, 0xcc},
+	{0xb8, 0x0c, 0x20, 0xcc},
+	{0xb8, 0x0d, 0x70, 0xcc},
+	{0xb9, 0x18, 0x00, 0xcc},
+	{0xb9, 0x19, 0x0f, 0xcc},
+	{0xb9, 0x1a, 0x0f, 0xcc},
+	{0xb9, 0x1b, 0x0f, 0xcc},
+	{0xb9, 0x1c, 0x0f, 0xcc},
+	{0xb3, 0x5c, 0x01, 0xcc},
+	{}
+};
+
+static const u8 hv7131r_initQVGA_data[][4] = {
+	{0xb3, 0x01, 0x01, 0xcc},
+	{0xb0, 0x03, 0x19, 0xcc},
+	{0xb0, 0x04, 0x02, 0xcc},
+	{0x00, 0x00, 0x20, 0xdd},
+	{0xb3, 0x00, 0x24, 0xcc},
+	{0xb3, 0x00, 0x25, 0xcc},
+	{0xb3, 0x08, 0x01, 0xcc},
+	{0xb3, 0x09, 0x0c, 0xcc},
+	{0xb3, 0x05, 0x01, 0xcc},
+	{0xb3, 0x06, 0x03, 0xcc},
+	{0xb3, 0x01, 0x45, 0xcc},
+	{0xb3, 0x03, 0x0b, 0xcc},
+	{0xb3, 0x04, 0x05, 0xcc},
+	{0xb3, 0x20, 0x00, 0xcc},
+	{0xb3, 0x21, 0x00, 0xcc},
+	{0xb3, 0x22, 0x01, 0xcc},
+	{0xb3, 0x23, 0xe0, 0xcc},
+	{0xb3, 0x14, 0x00, 0xcc},
+	{0xb3, 0x15, 0x02, 0xcc},
+	{0xb3, 0x16, 0x02, 0xcc},
+	{0xb3, 0x17, 0x7f, 0xcc},
+	{0xb3, 0x34, 0x01, 0xcc},
+	{0xb3, 0x35, 0x91, 0xcc},
+	{0xb3, 0x00, 0x27, 0xcc},
+	{0xbc, 0x00, 0xd3, 0xcc},
+	{0xb8, 0x00, 0x23, 0xcc},
+	{0xb8, 0x2c, 0x50, 0xcc},
+	{0xb8, 0x2d, 0xf8, 0xcc},
+	{0xb8, 0x2e, 0xf8, 0xcc},
+	{0xb8, 0x2f, 0xf8, 0xcc},
+	{0xb8, 0x30, 0x50, 0xcc},
+	{0xb8, 0x31, 0xf8, 0xcc},
+	{0xb8, 0x32, 0xf8, 0xcc},
+	{0xb8, 0x33, 0xf8, 0xcc},
+	{0xb8, 0x34, 0x58, 0xcc},
+	{0xb8, 0x35, 0x00, 0xcc},
+	{0xb8, 0x36, 0x00, 0xcc},
+	{0xb8, 0x37, 0x00, 0xcc},
+	{0xb8, 0x27, 0x20, 0xcc},
+	{0xb8, 0x01, 0x7d, 0xcc},
+	{0xb8, 0x81, 0x09, 0xcc},
+	{0xb3, 0x01, 0x41, 0xcc},
+	{0xb8, 0x8e, 0x00, 0xcc},
+	{0xb8, 0x8f, 0xff, 0xcc},
+	{0x00, 0x01, 0x0c, 0xaa},
+	{0x00, 0x14, 0x01, 0xaa},
+	{0x00, 0x15, 0xe6, 0xaa},
+	{0x00, 0x16, 0x02, 0xaa},
+	{0x00, 0x17, 0x86, 0xaa},
+	{0x00, 0x23, 0x00, 0xaa},
+	{0x00, 0x25, 0x03, 0xaa},
+	{0x00, 0x26, 0xa9, 0xaa},
+	{0x00, 0x27, 0x80, 0xaa},
+	{0x00, 0x30, 0x18, 0xaa},
+	{0xb6, 0x00, 0x00, 0xcc},
+	{0xb6, 0x03, 0x01, 0xcc},
+	{0xb6, 0x02, 0x40, 0xcc},
+	{0xb6, 0x05, 0x00, 0xcc},
+	{0xb6, 0x04, 0xf0, 0xcc},
+	{0xb6, 0x12, 0x78, 0xcc},
+	{0xb6, 0x18, 0x00, 0xcc},
+	{0xb6, 0x17, 0x96, 0xcc},
+	{0xb6, 0x16, 0x00, 0xcc},
+	{0xb6, 0x22, 0x12, 0xcc},
+	{0xb6, 0x23, 0x0b, 0xcc},
+	{0xb3, 0x02, 0x02, 0xcc},
+	{0xbf, 0xc0, 0x39, 0xcc},
+	{0xbf, 0xc1, 0x04, 0xcc},
+	{0xbf, 0xcc, 0x10, 0xcc},
+	{0xbc, 0x02, 0x18, 0xcc},
+	{0xbc, 0x03, 0x50, 0xcc},
+	{0xbc, 0x04, 0x18, 0xcc},
+	{0xbc, 0x05, 0x00, 0xcc},
+	{0xbc, 0x06, 0x00, 0xcc},
+	{0xbc, 0x08, 0x30, 0xcc},
+	{0xbc, 0x09, 0x40, 0xcc},
+	{0xbc, 0x0a, 0x10, 0xcc},
+	{0xbc, 0x0b, 0x00, 0xcc},
+	{0xbc, 0x0c, 0x00, 0xcc},
+	{0xb9, 0x12, 0x00, 0xcc},
+	{0xb9, 0x13, 0x0a, 0xcc},
+	{0xb9, 0x14, 0x0a, 0xcc},
+	{0xb9, 0x15, 0x0a, 0xcc},
+	{0xb9, 0x16, 0x0a, 0xcc},
+	{0xb9, 0x18, 0x00, 0xcc},
+	{0xb9, 0x19, 0x0f, 0xcc},
+	{0xb8, 0x0c, 0x20, 0xcc},
+	{0xb8, 0x0d, 0x70, 0xcc},
+	{0xb9, 0x1a, 0x0f, 0xcc},
+	{0xb9, 0x1b, 0x0f, 0xcc},
+	{0xb9, 0x1c, 0x0f, 0xcc},
+	{0xb6, 0x12, 0xf8, 0xcc},
+	{0xb6, 0x13, 0x13, 0xcc},
+	{0xb3, 0x5c, 0x01, 0xcc},
+	{}
+};
+
+static const u8 ov7660_gamma[17] = {
+	0x00, 0x13, 0x38, 0x59, 0x79, 0x92, 0xa7, 0xb9, 0xc8,
+	0xd4, 0xdf, 0xe7, 0xee, 0xf4, 0xf9, 0xfc, 0xff
+};
+static const u8 ov7660_matrix[9] = {
+	0x5a, 0xf0, 0xf6, 0xf3, 0x57, 0xf6, 0xf3, 0xef, 0x62
+};
+static const u8 ov7660_initVGA_data[][4] = {
+	{0xb0, 0x4d, 0x00, 0xcc},	{0xb3, 0x01, 0x01, 0xcc},
+	{0x00, 0x00, 0x50, 0xdd},
+	{0xb0, 0x03, 0x01, 0xcc},
+	{0xb3, 0x00, 0x21, 0xcc},	{0xb3, 0x00, 0x26, 0xcc},
+	{0xb3, 0x05, 0x01, 0xcc},
+	{0xb3, 0x06, 0x03, 0xcc},
+	{0xb3, 0x03, 0x1f, 0xcc},	{0xb3, 0x04, 0x05, 0xcc},
+	{0xb3, 0x05, 0x00, 0xcc},
+	{0xb3, 0x06, 0x01, 0xcc},
+	{0xb3, 0x15, 0x00, 0xcc},/* 0xb315  <-0 href startl */
+	{0xb3, 0x16, 0x02, 0xcc},	{0xb3, 0x17, 0x7f, 0xcc},
+	{0xb3, 0x21, 0x00, 0xcc},
+	{0xb3, 0x23, 0xe0, 0xcc},	{0xb3, 0x1d, 0x01, 0xcc},
+	{0xb3, 0x1f, 0x02, 0xcc},
+	{0xb3, 0x34, 0x01, 0xcc},
+	{0xb3, 0x35, 0xa1, 0xcc},	/* i2c add: 21 */
+	{0xb3, 0x00, 0x26, 0xcc},
+	{0xb8, 0x00, 0x33, 0xcc}, /* 13 */
+	{0xb8, 0x01, 0x7d, 0xcc},
+	{0xbc, 0x00, 0x73, 0xcc},	{0xb8, 0x81, 0x09, 0xcc},
+	{0xb8, 0x27, 0x20, 0xcc},
+	{0xb8, 0x8f, 0x50, 0xcc},
+	{0x00, 0x01, 0x80, 0xaa},	{0x00, 0x02, 0x80, 0xaa},
+	{0x00, 0x12, 0x80, 0xaa},
+	{0x00, 0x12, 0x05, 0xaa},
+	{0x00, 0x1e, 0x01, 0xaa},	/* MVFP */
+	{0x00, 0x3d, 0x40, 0xaa}, /* 0x3d <-40 gamma 01 */
+	{0x00, 0x41, 0x00, 0xaa}, /* edge 00 */
+	{0x00, 0x0d, 0x48, 0xaa},	{0x00, 0x0e, 0x04, 0xaa},
+	{0x00, 0x13, 0xa7, 0xaa},
+	{0x00, 0x40, 0xc1, 0xaa},	{0x00, 0x35, 0x00, 0xaa},
+	{0x00, 0x36, 0x00, 0xaa},
+	{0x00, 0x3c, 0x68, 0xaa},	{0x00, 0x1b, 0x05, 0xaa},
+	{0x00, 0x39, 0x43, 0xaa},
+	{0x00, 0x8d, 0xcf, 0xaa},
+	{0x00, 0x8b, 0xcc, 0xaa},	{0x00, 0x8c, 0xcc, 0xaa},
+	{0x00, 0x0f, 0x62, 0xaa},
+	{0x00, 0x35, 0x84, 0xaa},
+	{0x00, 0x3b, 0x08, 0xaa}, /* 0 * Nightframe 1/4 + 50Hz -> 0xC8 */
+	{0x00, 0x3a, 0x00, 0xaa}, /* mx change yuyv format 00, 04, 01; 08, 0c*/
+	{0x00, 0x14, 0x2a, 0xaa}, /* agc ampli */
+	{0x00, 0x9e, 0x40, 0xaa},	{0xb8, 0x8f, 0x50, 0xcc},
+	{0x00, 0x01, 0x80, 0xaa},
+	{0x00, 0x02, 0x80, 0xaa},
+	{0xb8, 0xfe, 0x00, 0xcc},	{0xb8, 0xff, 0x28, 0xcc},
+	{0xb9, 0x00, 0x28, 0xcc},
+	{0xb9, 0x01, 0x28, 0xcc},	{0xb9, 0x02, 0x28, 0xcc},
+	{0xb9, 0x03, 0x00, 0xcc},
+	{0xb9, 0x04, 0x00, 0xcc},
+	{0xb9, 0x05, 0x3c, 0xcc},	{0xb9, 0x06, 0x3c, 0xcc},
+	{0xb9, 0x07, 0x3c, 0xcc},
+	{0xb9, 0x08, 0x3c, 0xcc},
+
+	{0xb8, 0x8e, 0x00, 0xcc},	{0xb8, 0x8f, 0xff, 0xcc},
+
+	{0x00, 0x29, 0x3c, 0xaa},	{0xb3, 0x01, 0x45, 0xcc},
+	{}
+};
+static const u8 ov7660_initQVGA_data[][4] = {
+	{0xb0, 0x4d, 0x00, 0xcc},	{0xb3, 0x01, 0x01, 0xcc},
+	{0x00, 0x00, 0x50, 0xdd},	{0xb0, 0x03, 0x01, 0xcc},
+	{0xb3, 0x00, 0x21, 0xcc},	{0xb3, 0x00, 0x26, 0xcc},
+	{0xb3, 0x05, 0x01, 0xcc},	{0xb3, 0x06, 0x03, 0xcc},
+	{0xb3, 0x03, 0x1f, 0xcc},	{0xb3, 0x04, 0x05, 0xcc},
+	{0xb3, 0x05, 0x00, 0xcc},	{0xb3, 0x06, 0x01, 0xcc},
+	{0xb3, 0x15, 0x00, 0xcc},/* 0xb315  <-0 href startl */
+	{0xb3, 0x16, 0x02, 0xcc},	{0xb3, 0x17, 0x7f, 0xcc},
+	{0xb3, 0x21, 0x00, 0xcc},
+	{0xb3, 0x23, 0xe0, 0xcc},	{0xb3, 0x1d, 0x01, 0xcc},
+	{0xb3, 0x1f, 0x02, 0xcc},	{0xb3, 0x34, 0x01, 0xcc},
+	{0xb3, 0x35, 0xa1, 0xcc},	{0xb3, 0x00, 0x26, 0xcc},
+	{0xb8, 0x00, 0x33, 0xcc}, /* 13 */
+	{0xb8, 0x01, 0x7d, 0xcc},
+/* sizer */
+	{0xbc, 0x00, 0xd3, 0xcc},
+	{0xb8, 0x81, 0x09, 0xcc},	{0xb8, 0x81, 0x09, 0xcc},
+	{0xb8, 0x27, 0x20, 0xcc},	{0xb8, 0x8f, 0x50, 0xcc},
+	{0x00, 0x01, 0x80, 0xaa},	{0x00, 0x02, 0x80, 0xaa},
+	{0x00, 0x12, 0x80, 0xaa},	{0x00, 0x12, 0x05, 0xaa},
+	{0x00, 0x1e, 0x01, 0xaa},	/* MVFP */
+	{0x00, 0x3d, 0x40, 0xaa}, /* 0x3d <-40 gamma 01 */
+	{0x00, 0x41, 0x00, 0xaa}, /* edge 00 */
+	{0x00, 0x0d, 0x48, 0xaa},	{0x00, 0x0e, 0x04, 0xaa},
+	{0x00, 0x13, 0xa7, 0xaa},
+	{0x00, 0x40, 0xc1, 0xaa},	{0x00, 0x35, 0x00, 0xaa},
+	{0x00, 0x36, 0x00, 0xaa},
+	{0x00, 0x3c, 0x68, 0xaa},	{0x00, 0x1b, 0x05, 0xaa},
+	{0x00, 0x39, 0x43, 0xaa},	{0x00, 0x8d, 0xcf, 0xaa},
+	{0x00, 0x8b, 0xcc, 0xaa},	{0x00, 0x8c, 0xcc, 0xaa},
+	{0x00, 0x0f, 0x62, 0xaa},	{0x00, 0x35, 0x84, 0xaa},
+	{0x00, 0x3b, 0x08, 0xaa}, /* 0  * Nightframe 1/4 + 50Hz -> 0xC8 */
+	{0x00, 0x3a, 0x00, 0xaa}, /* mx change yuyv format 00, 04, 01; 08, 0c*/
+	{0x00, 0x14, 0x2a, 0xaa}, /* agc ampli */
+	{0x00, 0x9e, 0x40, 0xaa},	{0xb8, 0x8f, 0x50, 0xcc},
+	{0x00, 0x01, 0x80, 0xaa},
+	{0x00, 0x02, 0x80, 0xaa},
+/* sizer filters */
+	{0xbc, 0x02, 0x08, 0xcc},
+	{0xbc, 0x03, 0x70, 0xcc},
+	{0xb8, 0x35, 0x00, 0xcc},
+	{0xb8, 0x36, 0x00, 0xcc},
+	{0xb8, 0x37, 0x00, 0xcc},
+	{0xbc, 0x04, 0x08, 0xcc},
+	{0xbc, 0x05, 0x00, 0xcc},
+	{0xbc, 0x06, 0x00, 0xcc},
+	{0xbc, 0x08, 0x3c, 0xcc},
+	{0xbc, 0x09, 0x40, 0xcc},
+	{0xbc, 0x0a, 0x04, 0xcc},
+	{0xbc, 0x0b, 0x00, 0xcc},
+	{0xbc, 0x0c, 0x00, 0xcc},
+/* */
+	{0xb8, 0xfe, 0x00, 0xcc},
+	{0xb8, 0xff, 0x28, 0xcc},
+/* */
+	{0xb9, 0x00, 0x28, 0xcc},	{0xb9, 0x01, 0x28, 0xcc},
+	{0xb9, 0x02, 0x28, 0xcc},	{0xb9, 0x03, 0x00, 0xcc},
+	{0xb9, 0x04, 0x00, 0xcc},	{0xb9, 0x05, 0x3c, 0xcc},
+	{0xb9, 0x06, 0x3c, 0xcc},	{0xb9, 0x07, 0x3c, 0xcc},
+	{0xb9, 0x08, 0x3c, 0xcc},
+/* */
+	{0xb8, 0x8e, 0x00, 0xcc},
+	{0xb8, 0x8f, 0xff, 0xcc}, /* ff */
+	{0x00, 0x29, 0x3c, 0xaa},
+	{0xb3, 0x01, 0x45, 0xcc}, /* 45 */
+	{}
+};
+
+static const u8 ov7660_50HZ[][4] = {
+	{0x00, 0x3b, 0x08, 0xaa},
+	{0x00, 0x9d, 0x40, 0xaa},
+	{0x00, 0x13, 0xa7, 0xaa},
+	{}
+};
+
+static const u8 ov7660_60HZ[][4] = {
+	{0x00, 0x3b, 0x00, 0xaa},
+	{0x00, 0x9e, 0x40, 0xaa},
+	{0x00, 0x13, 0xa7, 0xaa},
+	{}
+};
+
+static const u8 ov7660_NoFliker[][4] = {
+	{0x00, 0x13, 0x87, 0xaa},
+	{}
+};
+
+static const u8 ov7670_InitVGA[][4] = {
+	{0xb3, 0x01, 0x05, 0xcc},
+	{0x00, 0x00, 0x30, 0xdd},
+	{0xb0, 0x03, 0x19, 0xcc},
+	{0x00, 0x00, 0x10, 0xdd},
+	{0xb0, 0x04, 0x02, 0xcc},
+	{0x00, 0x00, 0x10, 0xdd},
+	{0xb3, 0x00, 0x66, 0xcc},
+	{0xb3, 0x00, 0x67, 0xcc},
+	{0xb0, 0x16, 0x01, 0xcc},
+	{0xb3, 0x35, 0xa1, 0xcc},	/* i2c add: 21 */
+	{0xb3, 0x34, 0x01, 0xcc},
+	{0xb3, 0x05, 0x01, 0xcc},
+	{0xb3, 0x06, 0x01, 0xcc},
+	{0xb3, 0x08, 0x01, 0xcc},
+	{0xb3, 0x09, 0x0c, 0xcc},
+	{0xb3, 0x02, 0x02, 0xcc},
+	{0xb3, 0x03, 0x1f, 0xcc},
+	{0xb3, 0x14, 0x00, 0xcc},
+	{0xb3, 0x15, 0x00, 0xcc},
+	{0xb3, 0x16, 0x02, 0xcc},
+	{0xb3, 0x17, 0x7f, 0xcc},
+	{0xb3, 0x04, 0x05, 0xcc},
+	{0xb3, 0x20, 0x00, 0xcc},
+	{0xb3, 0x21, 0x00, 0xcc},
+	{0xb3, 0x22, 0x01, 0xcc},
+	{0xb3, 0x23, 0xe0, 0xcc},
+	{0xbc, 0x00, 0x41, 0xcc},
+	{0xbc, 0x01, 0x01, 0xcc},
+	{0x00, 0x12, 0x80, 0xaa},
+	{0x00, 0x00, 0x20, 0xdd},
+	{0x00, 0x12, 0x00, 0xaa},
+	{0x00, 0x11, 0x40, 0xaa},
+	{0x00, 0x6b, 0x0a, 0xaa},
+	{0x00, 0x3a, 0x04, 0xaa},
+	{0x00, 0x40, 0xc0, 0xaa},
+	{0x00, 0x8c, 0x00, 0xaa},
+	{0x00, 0x7a, 0x29, 0xaa},
+	{0x00, 0x7b, 0x0e, 0xaa},
+	{0x00, 0x7c, 0x1a, 0xaa},
+	{0x00, 0x7d, 0x31, 0xaa},
+	{0x00, 0x7e, 0x53, 0xaa},
+	{0x00, 0x7f, 0x60, 0xaa},
+	{0x00, 0x80, 0x6b, 0xaa},
+	{0x00, 0x81, 0x73, 0xaa},
+	{0x00, 0x82, 0x7b, 0xaa},
+	{0x00, 0x83, 0x82, 0xaa},
+	{0x00, 0x84, 0x89, 0xaa},
+	{0x00, 0x85, 0x96, 0xaa},
+	{0x00, 0x86, 0xa1, 0xaa},
+	{0x00, 0x87, 0xb7, 0xaa},
+	{0x00, 0x88, 0xcc, 0xaa},
+	{0x00, 0x89, 0xe1, 0xaa},
+	{0x00, 0x13, 0xe0, 0xaa},
+	{0x00, 0x00, 0x00, 0xaa},
+	{0x00, 0x10, 0x00, 0xaa},
+	{0x00, 0x0d, 0x40, 0xaa},
+	{0x00, 0x14, 0x28, 0xaa},
+	{0x00, 0xa5, 0x05, 0xaa},
+	{0x00, 0xab, 0x07, 0xaa},
+	{0x00, 0x24, 0x95, 0xaa},
+	{0x00, 0x25, 0x33, 0xaa},
+	{0x00, 0x26, 0xe3, 0xaa},
+	{0x00, 0x9f, 0x88, 0xaa},
+	{0x00, 0xa0, 0x78, 0xaa},
+	{0x00, 0x55, 0x90, 0xaa},
+	{0x00, 0xa1, 0x03, 0xaa},
+	{0x00, 0xa6, 0xe0, 0xaa},
+	{0x00, 0xa7, 0xd8, 0xaa},
+	{0x00, 0xa8, 0xf0, 0xaa},
+	{0x00, 0xa9, 0x90, 0xaa},
+	{0x00, 0xaa, 0x14, 0xaa},
+	{0x00, 0x13, 0xe5, 0xaa},
+	{0x00, 0x0e, 0x61, 0xaa},
+	{0x00, 0x0f, 0x4b, 0xaa},
+	{0x00, 0x16, 0x02, 0xaa},
+	{0x00, 0x1e, 0x07, 0xaa},	/* MVFP */
+	{0x00, 0x21, 0x02, 0xaa},
+	{0x00, 0x22, 0x91, 0xaa},
+	{0x00, 0x29, 0x07, 0xaa},
+	{0x00, 0x33, 0x0b, 0xaa},
+	{0x00, 0x35, 0x0b, 0xaa},
+	{0x00, 0x37, 0x1d, 0xaa},
+	{0x00, 0x38, 0x71, 0xaa},
+	{0x00, 0x39, 0x2a, 0xaa},
+	{0x00, 0x3c, 0x78, 0xaa},
+	{0x00, 0x4d, 0x40, 0xaa},
+	{0x00, 0x4e, 0x20, 0xaa},
+	{0x00, 0x74, 0x19, 0xaa},
+	{0x00, 0x8d, 0x4f, 0xaa},
+	{0x00, 0x8e, 0x00, 0xaa},
+	{0x00, 0x8f, 0x00, 0xaa},
+	{0x00, 0x90, 0x00, 0xaa},
+	{0x00, 0x91, 0x00, 0xaa},
+	{0x00, 0x96, 0x00, 0xaa},
+	{0x00, 0x9a, 0x80, 0xaa},
+	{0x00, 0xb0, 0x84, 0xaa},
+	{0x00, 0xb1, 0x0c, 0xaa},
+	{0x00, 0xb2, 0x0e, 0xaa},
+	{0x00, 0xb3, 0x82, 0xaa},
+	{0x00, 0xb8, 0x0a, 0xaa},
+	{0x00, 0x43, 0x14, 0xaa},
+	{0x00, 0x44, 0xf0, 0xaa},
+	{0x00, 0x45, 0x45, 0xaa},
+	{0x00, 0x46, 0x63, 0xaa},
+	{0x00, 0x47, 0x2d, 0xaa},
+	{0x00, 0x48, 0x46, 0xaa},
+	{0x00, 0x59, 0x88, 0xaa},
+	{0x00, 0x5a, 0xa0, 0xaa},
+	{0x00, 0x5b, 0xc6, 0xaa},
+	{0x00, 0x5c, 0x7d, 0xaa},
+	{0x00, 0x5d, 0x5f, 0xaa},
+	{0x00, 0x5e, 0x19, 0xaa},
+	{0x00, 0x6c, 0x0a, 0xaa},
+	{0x00, 0x6d, 0x55, 0xaa},
+	{0x00, 0x6e, 0x11, 0xaa},
+	{0x00, 0x6f, 0x9e, 0xaa},
+	{0x00, 0x69, 0x00, 0xaa},
+	{0x00, 0x6a, 0x40, 0xaa},
+	{0x00, 0x01, 0x40, 0xaa},
+	{0x00, 0x02, 0x40, 0xaa},
+	{0x00, 0x13, 0xe7, 0xaa},
+	{0x00, 0x5f, 0xf0, 0xaa},
+	{0x00, 0x60, 0xf0, 0xaa},
+	{0x00, 0x61, 0xf0, 0xaa},
+	{0x00, 0x27, 0xa0, 0xaa},
+	{0x00, 0x28, 0x80, 0xaa},
+	{0x00, 0x2c, 0x90, 0xaa},
+	{0x00, 0x4f, 0x66, 0xaa},
+	{0x00, 0x50, 0x66, 0xaa},
+	{0x00, 0x51, 0x00, 0xaa},
+	{0x00, 0x52, 0x22, 0xaa},
+	{0x00, 0x53, 0x5e, 0xaa},
+	{0x00, 0x54, 0x80, 0xaa},
+	{0x00, 0x58, 0x9e, 0xaa},
+	{0x00, 0x41, 0x08, 0xaa},
+	{0x00, 0x3f, 0x00, 0xaa},
+	{0x00, 0x75, 0x85, 0xaa},
+	{0x00, 0x76, 0xe1, 0xaa},
+	{0x00, 0x4c, 0x00, 0xaa},
+	{0x00, 0x77, 0x0a, 0xaa},
+	{0x00, 0x3d, 0x88, 0xaa},
+	{0x00, 0x4b, 0x09, 0xaa},
+	{0x00, 0xc9, 0x60, 0xaa},
+	{0x00, 0x41, 0x38, 0xaa},
+	{0x00, 0x62, 0x30, 0xaa},
+	{0x00, 0x63, 0x30, 0xaa},
+	{0x00, 0x64, 0x08, 0xaa},
+	{0x00, 0x94, 0x07, 0xaa},
+	{0x00, 0x95, 0x0b, 0xaa},
+	{0x00, 0x65, 0x00, 0xaa},
+	{0x00, 0x66, 0x05, 0xaa},
+	{0x00, 0x56, 0x50, 0xaa},
+	{0x00, 0x34, 0x11, 0xaa},
+	{0x00, 0xa4, 0x88, 0xaa},
+	{0x00, 0x96, 0x00, 0xaa},
+	{0x00, 0x97, 0x30, 0xaa},
+	{0x00, 0x98, 0x20, 0xaa},
+	{0x00, 0x99, 0x30, 0xaa},
+	{0x00, 0x9a, 0x84, 0xaa},
+	{0x00, 0x9b, 0x29, 0xaa},
+	{0x00, 0x9c, 0x03, 0xaa},
+	{0x00, 0x78, 0x04, 0xaa},
+	{0x00, 0x79, 0x01, 0xaa},
+	{0x00, 0xc8, 0xf0, 0xaa},
+	{0x00, 0x79, 0x0f, 0xaa},
+	{0x00, 0xc8, 0x00, 0xaa},
+	{0x00, 0x79, 0x10, 0xaa},
+	{0x00, 0xc8, 0x7e, 0xaa},
+	{0x00, 0x79, 0x0a, 0xaa},
+	{0x00, 0xc8, 0x80, 0xaa},
+	{0x00, 0x79, 0x0b, 0xaa},
+	{0x00, 0xc8, 0x01, 0xaa},
+	{0x00, 0x79, 0x0c, 0xaa},
+	{0x00, 0xc8, 0x0f, 0xaa},
+	{0x00, 0x79, 0x0d, 0xaa},
+	{0x00, 0xc8, 0x20, 0xaa},
+	{0x00, 0x79, 0x09, 0xaa},
+	{0x00, 0xc8, 0x80, 0xaa},
+	{0x00, 0x79, 0x02, 0xaa},
+	{0x00, 0xc8, 0xc0, 0xaa},
+	{0x00, 0x79, 0x03, 0xaa},
+	{0x00, 0xc8, 0x40, 0xaa},
+	{0x00, 0x79, 0x05, 0xaa},
+	{0x00, 0xc8, 0x30, 0xaa},
+	{0x00, 0x79, 0x26, 0xaa},
+	{0x00, 0x11, 0x40, 0xaa},
+	{0x00, 0x3a, 0x04, 0xaa},
+	{0x00, 0x12, 0x00, 0xaa},
+	{0x00, 0x40, 0xc0, 0xaa},
+	{0x00, 0x8c, 0x00, 0xaa},
+	{0x00, 0x17, 0x14, 0xaa},
+	{0x00, 0x18, 0x02, 0xaa},
+	{0x00, 0x32, 0x92, 0xaa},
+	{0x00, 0x19, 0x02, 0xaa},
+	{0x00, 0x1a, 0x7a, 0xaa},
+	{0x00, 0x03, 0x0a, 0xaa},
+	{0x00, 0x0c, 0x00, 0xaa},
+	{0x00, 0x3e, 0x00, 0xaa},
+	{0x00, 0x70, 0x3a, 0xaa},
+	{0x00, 0x71, 0x35, 0xaa},
+	{0x00, 0x72, 0x11, 0xaa},
+	{0x00, 0x73, 0xf0, 0xaa},
+	{0x00, 0xa2, 0x02, 0xaa},
+	{0x00, 0xb1, 0x00, 0xaa},
+	{0x00, 0xb1, 0x0c, 0xaa},
+	{0x00, 0x1e, 0x37, 0xaa},	/* MVFP */
+	{0x00, 0xaa, 0x14, 0xaa},
+	{0x00, 0x24, 0x80, 0xaa},
+	{0x00, 0x25, 0x74, 0xaa},
+	{0x00, 0x26, 0xd3, 0xaa},
+	{0x00, 0x0d, 0x00, 0xaa},
+	{0x00, 0x14, 0x18, 0xaa},
+	{0x00, 0x9d, 0x99, 0xaa},
+	{0x00, 0x9e, 0x7f, 0xaa},
+	{0x00, 0x64, 0x08, 0xaa},
+	{0x00, 0x94, 0x07, 0xaa},
+	{0x00, 0x95, 0x06, 0xaa},
+	{0x00, 0x66, 0x05, 0xaa},
+	{0x00, 0x41, 0x08, 0xaa},
+	{0x00, 0x3f, 0x00, 0xaa},
+	{0x00, 0x75, 0x07, 0xaa},
+	{0x00, 0x76, 0xe1, 0xaa},
+	{0x00, 0x4c, 0x00, 0xaa},
+	{0x00, 0x77, 0x00, 0xaa},
+	{0x00, 0x3d, 0xc2, 0xaa},
+	{0x00, 0x4b, 0x09, 0xaa},
+	{0x00, 0xc9, 0x60, 0xaa},
+	{0x00, 0x41, 0x38, 0xaa},
+	{0xbf, 0xc0, 0x26, 0xcc},
+	{0xbf, 0xc1, 0x02, 0xcc},
+	{0xbf, 0xcc, 0x04, 0xcc},
+	{0xb3, 0x5c, 0x01, 0xcc},
+	{0xb3, 0x01, 0x45, 0xcc},
+	{0x00, 0x77, 0x05, 0xaa},
+	{},
+};
+
+static const u8 ov7670_InitQVGA[][4] = {
+	{0xb3, 0x01, 0x05, 0xcc},
+	{0x00, 0x00, 0x30, 0xdd},
+	{0xb0, 0x03, 0x19, 0xcc},
+	{0x00, 0x00, 0x10, 0xdd},
+	{0xb0, 0x04, 0x02, 0xcc},
+	{0x00, 0x00, 0x10, 0xdd},
+	{0xb3, 0x00, 0x66, 0xcc},
+	{0xb3, 0x00, 0x67, 0xcc},
+	{0xb0, 0x16, 0x01, 0xcc},
+	{0xb3, 0x35, 0xa1, 0xcc},	/* i2c add: 21 */
+	{0xb3, 0x34, 0x01, 0xcc},
+	{0xb3, 0x05, 0x01, 0xcc},
+	{0xb3, 0x06, 0x01, 0xcc},
+	{0xb3, 0x08, 0x01, 0xcc},
+	{0xb3, 0x09, 0x0c, 0xcc},
+	{0xb3, 0x02, 0x02, 0xcc},
+	{0xb3, 0x03, 0x1f, 0xcc},
+	{0xb3, 0x14, 0x00, 0xcc},
+	{0xb3, 0x15, 0x00, 0xcc},
+	{0xb3, 0x16, 0x02, 0xcc},
+	{0xb3, 0x17, 0x7f, 0xcc},
+	{0xb3, 0x04, 0x05, 0xcc},
+	{0xb3, 0x20, 0x00, 0xcc},
+	{0xb3, 0x21, 0x00, 0xcc},
+	{0xb3, 0x22, 0x01, 0xcc},
+	{0xb3, 0x23, 0xe0, 0xcc},
+	{0xbc, 0x00, 0xd1, 0xcc},
+	{0xbc, 0x01, 0x01, 0xcc},
+	{0x00, 0x12, 0x80, 0xaa},
+	{0x00, 0x00, 0x20, 0xdd},
+	{0x00, 0x12, 0x00, 0xaa},
+	{0x00, 0x11, 0x40, 0xaa},
+	{0x00, 0x6b, 0x0a, 0xaa},
+	{0x00, 0x3a, 0x04, 0xaa},
+	{0x00, 0x40, 0xc0, 0xaa},
+	{0x00, 0x8c, 0x00, 0xaa},
+	{0x00, 0x7a, 0x29, 0xaa},
+	{0x00, 0x7b, 0x0e, 0xaa},
+	{0x00, 0x7c, 0x1a, 0xaa},
+	{0x00, 0x7d, 0x31, 0xaa},
+	{0x00, 0x7e, 0x53, 0xaa},
+	{0x00, 0x7f, 0x60, 0xaa},
+	{0x00, 0x80, 0x6b, 0xaa},
+	{0x00, 0x81, 0x73, 0xaa},
+	{0x00, 0x82, 0x7b, 0xaa},
+	{0x00, 0x83, 0x82, 0xaa},
+	{0x00, 0x84, 0x89, 0xaa},
+	{0x00, 0x85, 0x96, 0xaa},
+	{0x00, 0x86, 0xa1, 0xaa},
+	{0x00, 0x87, 0xb7, 0xaa},
+	{0x00, 0x88, 0xcc, 0xaa},
+	{0x00, 0x89, 0xe1, 0xaa},
+	{0x00, 0x13, 0xe0, 0xaa},
+	{0x00, 0x00, 0x00, 0xaa},
+	{0x00, 0x10, 0x00, 0xaa},
+	{0x00, 0x0d, 0x40, 0xaa},
+	{0x00, 0x14, 0x28, 0xaa},
+	{0x00, 0xa5, 0x05, 0xaa},
+	{0x00, 0xab, 0x07, 0xaa},
+	{0x00, 0x24, 0x95, 0xaa},
+	{0x00, 0x25, 0x33, 0xaa},
+	{0x00, 0x26, 0xe3, 0xaa},
+	{0x00, 0x9f, 0x88, 0xaa},
+	{0x00, 0xa0, 0x78, 0xaa},
+	{0x00, 0x55, 0x90, 0xaa},
+	{0x00, 0xa1, 0x03, 0xaa},
+	{0x00, 0xa6, 0xe0, 0xaa},
+	{0x00, 0xa7, 0xd8, 0xaa},
+	{0x00, 0xa8, 0xf0, 0xaa},
+	{0x00, 0xa9, 0x90, 0xaa},
+	{0x00, 0xaa, 0x14, 0xaa},
+	{0x00, 0x13, 0xe5, 0xaa},
+	{0x00, 0x0e, 0x61, 0xaa},
+	{0x00, 0x0f, 0x4b, 0xaa},
+	{0x00, 0x16, 0x02, 0xaa},
+	{0x00, 0x1e, 0x07, 0xaa},	/* MVFP */
+	{0x00, 0x21, 0x02, 0xaa},
+	{0x00, 0x22, 0x91, 0xaa},
+	{0x00, 0x29, 0x07, 0xaa},
+	{0x00, 0x33, 0x0b, 0xaa},
+	{0x00, 0x35, 0x0b, 0xaa},
+	{0x00, 0x37, 0x1d, 0xaa},
+	{0x00, 0x38, 0x71, 0xaa},
+	{0x00, 0x39, 0x2a, 0xaa},
+	{0x00, 0x3c, 0x78, 0xaa},
+	{0x00, 0x4d, 0x40, 0xaa},
+	{0x00, 0x4e, 0x20, 0xaa},
+	{0x00, 0x74, 0x19, 0xaa},
+	{0x00, 0x8d, 0x4f, 0xaa},
+	{0x00, 0x8e, 0x00, 0xaa},
+	{0x00, 0x8f, 0x00, 0xaa},
+	{0x00, 0x90, 0x00, 0xaa},
+	{0x00, 0x91, 0x00, 0xaa},
+	{0x00, 0x96, 0x00, 0xaa},
+	{0x00, 0x9a, 0x80, 0xaa},
+	{0x00, 0xb0, 0x84, 0xaa},
+	{0x00, 0xb1, 0x0c, 0xaa},
+	{0x00, 0xb2, 0x0e, 0xaa},
+	{0x00, 0xb3, 0x82, 0xaa},
+	{0x00, 0xb8, 0x0a, 0xaa},
+	{0x00, 0x43, 0x14, 0xaa},
+	{0x00, 0x44, 0xf0, 0xaa},
+	{0x00, 0x45, 0x45, 0xaa},
+	{0x00, 0x46, 0x63, 0xaa},
+	{0x00, 0x47, 0x2d, 0xaa},
+	{0x00, 0x48, 0x46, 0xaa},
+	{0x00, 0x59, 0x88, 0xaa},
+	{0x00, 0x5a, 0xa0, 0xaa},
+	{0x00, 0x5b, 0xc6, 0xaa},
+	{0x00, 0x5c, 0x7d, 0xaa},
+	{0x00, 0x5d, 0x5f, 0xaa},
+	{0x00, 0x5e, 0x19, 0xaa},
+	{0x00, 0x6c, 0x0a, 0xaa},
+	{0x00, 0x6d, 0x55, 0xaa},
+	{0x00, 0x6e, 0x11, 0xaa},
+	{0x00, 0x6f, 0x9e, 0xaa},
+	{0x00, 0x69, 0x00, 0xaa},
+	{0x00, 0x6a, 0x40, 0xaa},
+	{0x00, 0x01, 0x40, 0xaa},
+	{0x00, 0x02, 0x40, 0xaa},
+	{0x00, 0x13, 0xe7, 0xaa},
+	{0x00, 0x5f, 0xf0, 0xaa},
+	{0x00, 0x60, 0xf0, 0xaa},
+	{0x00, 0x61, 0xf0, 0xaa},
+	{0x00, 0x27, 0xa0, 0xaa},
+	{0x00, 0x28, 0x80, 0xaa},
+	{0x00, 0x2c, 0x90, 0xaa},
+	{0x00, 0x4f, 0x66, 0xaa},
+	{0x00, 0x50, 0x66, 0xaa},
+	{0x00, 0x51, 0x00, 0xaa},
+	{0x00, 0x52, 0x22, 0xaa},
+	{0x00, 0x53, 0x5e, 0xaa},
+	{0x00, 0x54, 0x80, 0xaa},
+	{0x00, 0x58, 0x9e, 0xaa},
+	{0x00, 0x41, 0x08, 0xaa},
+	{0x00, 0x3f, 0x00, 0xaa},
+	{0x00, 0x75, 0x85, 0xaa},
+	{0x00, 0x76, 0xe1, 0xaa},
+	{0x00, 0x4c, 0x00, 0xaa},
+	{0x00, 0x77, 0x0a, 0xaa},
+	{0x00, 0x3d, 0x88, 0xaa},
+	{0x00, 0x4b, 0x09, 0xaa},
+	{0x00, 0xc9, 0x60, 0xaa},
+	{0x00, 0x41, 0x38, 0xaa},
+	{0x00, 0x62, 0x30, 0xaa},
+	{0x00, 0x63, 0x30, 0xaa},
+	{0x00, 0x64, 0x08, 0xaa},
+	{0x00, 0x94, 0x07, 0xaa},
+	{0x00, 0x95, 0x0b, 0xaa},
+	{0x00, 0x65, 0x00, 0xaa},
+	{0x00, 0x66, 0x05, 0xaa},
+	{0x00, 0x56, 0x50, 0xaa},
+	{0x00, 0x34, 0x11, 0xaa},
+	{0x00, 0xa4, 0x88, 0xaa},
+	{0x00, 0x96, 0x00, 0xaa},
+	{0x00, 0x97, 0x30, 0xaa},
+	{0x00, 0x98, 0x20, 0xaa},
+	{0x00, 0x99, 0x30, 0xaa},
+	{0x00, 0x9a, 0x84, 0xaa},
+	{0x00, 0x9b, 0x29, 0xaa},
+	{0x00, 0x9c, 0x03, 0xaa},
+	{0x00, 0x78, 0x04, 0xaa},
+	{0x00, 0x79, 0x01, 0xaa},
+	{0x00, 0xc8, 0xf0, 0xaa},
+	{0x00, 0x79, 0x0f, 0xaa},
+	{0x00, 0xc8, 0x00, 0xaa},
+	{0x00, 0x79, 0x10, 0xaa},
+	{0x00, 0xc8, 0x7e, 0xaa},
+	{0x00, 0x79, 0x0a, 0xaa},
+	{0x00, 0xc8, 0x80, 0xaa},
+	{0x00, 0x79, 0x0b, 0xaa},
+	{0x00, 0xc8, 0x01, 0xaa},
+	{0x00, 0x79, 0x0c, 0xaa},
+	{0x00, 0xc8, 0x0f, 0xaa},
+	{0x00, 0x79, 0x0d, 0xaa},
+	{0x00, 0xc8, 0x20, 0xaa},
+	{0x00, 0x79, 0x09, 0xaa},
+	{0x00, 0xc8, 0x80, 0xaa},
+	{0x00, 0x79, 0x02, 0xaa},
+	{0x00, 0xc8, 0xc0, 0xaa},
+	{0x00, 0x79, 0x03, 0xaa},
+	{0x00, 0xc8, 0x40, 0xaa},
+	{0x00, 0x79, 0x05, 0xaa},
+	{0x00, 0xc8, 0x30, 0xaa},
+	{0x00, 0x79, 0x26, 0xaa},
+	{0x00, 0x11, 0x40, 0xaa},
+	{0x00, 0x3a, 0x04, 0xaa},
+	{0x00, 0x12, 0x00, 0xaa},
+	{0x00, 0x40, 0xc0, 0xaa},
+	{0x00, 0x8c, 0x00, 0xaa},
+	{0x00, 0x17, 0x14, 0xaa},
+	{0x00, 0x18, 0x02, 0xaa},
+	{0x00, 0x32, 0x92, 0xaa},
+	{0x00, 0x19, 0x02, 0xaa},
+	{0x00, 0x1a, 0x7a, 0xaa},
+	{0x00, 0x03, 0x0a, 0xaa},
+	{0x00, 0x0c, 0x00, 0xaa},
+	{0x00, 0x3e, 0x00, 0xaa},
+	{0x00, 0x70, 0x3a, 0xaa},
+	{0x00, 0x71, 0x35, 0xaa},
+	{0x00, 0x72, 0x11, 0xaa},
+	{0x00, 0x73, 0xf0, 0xaa},
+	{0x00, 0xa2, 0x02, 0xaa},
+	{0x00, 0xb1, 0x00, 0xaa},
+	{0x00, 0xb1, 0x0c, 0xaa},
+	{0x00, 0x1e, 0x37, 0xaa},	/* MVFP */
+	{0x00, 0xaa, 0x14, 0xaa},
+	{0x00, 0x24, 0x80, 0xaa},
+	{0x00, 0x25, 0x74, 0xaa},
+	{0x00, 0x26, 0xd3, 0xaa},
+	{0x00, 0x0d, 0x00, 0xaa},
+	{0x00, 0x14, 0x18, 0xaa},
+	{0x00, 0x9d, 0x99, 0xaa},
+	{0x00, 0x9e, 0x7f, 0xaa},
+	{0x00, 0x64, 0x08, 0xaa},
+	{0x00, 0x94, 0x07, 0xaa},
+	{0x00, 0x95, 0x06, 0xaa},
+	{0x00, 0x66, 0x05, 0xaa},
+	{0x00, 0x41, 0x08, 0xaa},
+	{0x00, 0x3f, 0x00, 0xaa},
+	{0x00, 0x75, 0x07, 0xaa},
+	{0x00, 0x76, 0xe1, 0xaa},
+	{0x00, 0x4c, 0x00, 0xaa},
+	{0x00, 0x77, 0x00, 0xaa},
+	{0x00, 0x3d, 0xc2, 0xaa},
+	{0x00, 0x4b, 0x09, 0xaa},
+	{0x00, 0xc9, 0x60, 0xaa},
+	{0x00, 0x41, 0x38, 0xaa},
+	{0xbc, 0x02, 0x18, 0xcc},
+	{0xbc, 0x03, 0x50, 0xcc},
+	{0xbc, 0x04, 0x18, 0xcc},
+	{0xbc, 0x05, 0x00, 0xcc},
+	{0xbc, 0x06, 0x00, 0xcc},
+	{0xbc, 0x08, 0x30, 0xcc},
+	{0xbc, 0x09, 0x40, 0xcc},
+	{0xbc, 0x0a, 0x10, 0xcc},
+	{0xbc, 0x0b, 0x00, 0xcc},
+	{0xbc, 0x0c, 0x00, 0xcc},
+	{0xbf, 0xc0, 0x26, 0xcc},
+	{0xbf, 0xc1, 0x02, 0xcc},
+	{0xbf, 0xcc, 0x04, 0xcc},
+	{0xb3, 0x5c, 0x01, 0xcc},
+	{0xb3, 0x01, 0x45, 0xcc},
+	{0x00, 0x77, 0x05, 0xaa},
+	{},
+};
+
+/* PO1200 - values from usbvm326.inf and ms-win trace */
+static const u8 po1200_gamma[17] = {
+	0x00, 0x13, 0x38, 0x59, 0x79, 0x92, 0xa7, 0xb9, 0xc8,
+	0xd4, 0xdf, 0xe7, 0xee, 0xf4, 0xf9, 0xfc, 0xff
+};
+static const u8 po1200_matrix[9] = {
+	0x60, 0xf9, 0xe5, 0xe7, 0x50, 0x05, 0xf3, 0xe6, 0x5e
+};
+static const u8 po1200_initVGA_data[][4] = {
+	{0xb0, 0x03, 0x19, 0xcc},	/* reset? */
+	{0xb0, 0x03, 0x19, 0xcc},
+/*	{0x00, 0x00, 0x33, 0xdd}, */
+	{0xb0, 0x04, 0x02, 0xcc},
+	{0xb0, 0x02, 0x02, 0xcc},
+	{0xb3, 0x5d, 0x00, 0xcc},
+	{0xb3, 0x01, 0x01, 0xcc},
+	{0xb3, 0x00, 0x64, 0xcc},
+	{0xb3, 0x00, 0x65, 0xcc},
+	{0xb3, 0x05, 0x01, 0xcc},
+	{0xb3, 0x06, 0x01, 0xcc},
+	{0xb3, 0x5c, 0x01, 0xcc},
+	{0xb3, 0x08, 0x01, 0xcc},
+	{0xb3, 0x09, 0x0c, 0xcc},
+	{0xb3, 0x00, 0x67, 0xcc},
+	{0xb3, 0x02, 0xb2, 0xcc},
+	{0xb3, 0x03, 0x18, 0xcc},
+	{0xb3, 0x04, 0x15, 0xcc},
+	{0xb3, 0x20, 0x00, 0xcc},
+	{0xb3, 0x21, 0x00, 0xcc},
+	{0xb3, 0x22, 0x02, 0xcc},
+	{0xb3, 0x23, 0x58, 0xcc},
+	{0xb3, 0x14, 0x00, 0xcc},
+	{0xb3, 0x15, 0x00, 0xcc},
+	{0xb3, 0x16, 0x03, 0xcc},
+	{0xb3, 0x17, 0x1f, 0xcc},
+	{0xbc, 0x00, 0x71, 0xcc},
+	{0xbc, 0x01, 0x01, 0xcc},
+	{0xb0, 0x54, 0x13, 0xcc},
+	{0xb3, 0x00, 0x67, 0xcc},
+	{0xb3, 0x34, 0x01, 0xcc},
+	{0xb3, 0x35, 0xdc, 0xcc},	/* i2c add: 5c */
+	{0x00, 0x03, 0x00, 0xaa},
+	{0x00, 0x12, 0x05, 0xaa},
+	{0x00, 0x13, 0x02, 0xaa},
+	{0x00, 0x1e, 0xc6, 0xaa},	/* h/v flip */
+	{0x00, 0x21, 0x00, 0xaa},
+	{0x00, 0x25, 0x02, 0xaa},
+	{0x00, 0x3c, 0x4f, 0xaa},
+	{0x00, 0x3f, 0xe0, 0xaa},
+	{0x00, 0x42, 0xff, 0xaa},
+	{0x00, 0x45, 0x34, 0xaa},
+	{0x00, 0x55, 0xfe, 0xaa},
+	{0x00, 0x59, 0xd3, 0xaa},
+	{0x00, 0x5e, 0x04, 0xaa},
+	{0x00, 0x61, 0xb8, 0xaa},	/* sharpness */
+	{0x00, 0x62, 0x02, 0xaa},
+	{0x00, 0xa7, 0x31, 0xaa},
+	{0x00, 0xa9, 0x66, 0xaa},
+	{0x00, 0xb0, 0x00, 0xaa},
+	{0x00, 0xb1, 0x00, 0xaa},
+	{0x00, 0xb3, 0x11, 0xaa},
+	{0x00, 0xb6, 0x26, 0xaa},
+	{0x00, 0xb7, 0x20, 0xaa},
+	{0x00, 0xba, 0x04, 0xaa},
+	{0x00, 0x88, 0x42, 0xaa},
+	{0x00, 0x89, 0x9a, 0xaa},
+	{0x00, 0x8a, 0x88, 0xaa},
+	{0x00, 0x8b, 0x8e, 0xaa},
+	{0x00, 0x8c, 0x3e, 0xaa},
+	{0x00, 0x8d, 0x90, 0xaa},
+	{0x00, 0x8e, 0x87, 0xaa},
+	{0x00, 0x8f, 0x96, 0xaa},
+	{0x00, 0x90, 0x3d, 0xaa},
+	{0x00, 0x64, 0x00, 0xaa},
+	{0x00, 0x65, 0x10, 0xaa},
+	{0x00, 0x66, 0x20, 0xaa},
+	{0x00, 0x67, 0x2b, 0xaa},
+	{0x00, 0x68, 0x36, 0xaa},
+	{0x00, 0x69, 0x49, 0xaa},
+	{0x00, 0x6a, 0x5a, 0xaa},
+	{0x00, 0x6b, 0x7f, 0xaa},
+	{0x00, 0x6c, 0x9b, 0xaa},
+	{0x00, 0x6d, 0xba, 0xaa},
+	{0x00, 0x6e, 0xd4, 0xaa},
+	{0x00, 0x6f, 0xea, 0xaa},
+	{0x00, 0x70, 0x00, 0xaa},
+	{0x00, 0x71, 0x10, 0xaa},
+	{0x00, 0x72, 0x20, 0xaa},
+	{0x00, 0x73, 0x2b, 0xaa},
+	{0x00, 0x74, 0x36, 0xaa},
+	{0x00, 0x75, 0x49, 0xaa},
+	{0x00, 0x76, 0x5a, 0xaa},
+	{0x00, 0x77, 0x7f, 0xaa},
+	{0x00, 0x78, 0x9b, 0xaa},
+	{0x00, 0x79, 0xba, 0xaa},
+	{0x00, 0x7a, 0xd4, 0xaa},
+	{0x00, 0x7b, 0xea, 0xaa},
+	{0x00, 0x7c, 0x00, 0xaa},
+	{0x00, 0x7d, 0x10, 0xaa},
+	{0x00, 0x7e, 0x20, 0xaa},
+	{0x00, 0x7f, 0x2b, 0xaa},
+	{0x00, 0x80, 0x36, 0xaa},
+	{0x00, 0x81, 0x49, 0xaa},
+	{0x00, 0x82, 0x5a, 0xaa},
+	{0x00, 0x83, 0x7f, 0xaa},
+	{0x00, 0x84, 0x9b, 0xaa},
+	{0x00, 0x85, 0xba, 0xaa},
+	{0x00, 0x86, 0xd4, 0xaa},
+	{0x00, 0x87, 0xea, 0xaa},
+	{0x00, 0x57, 0x2a, 0xaa},
+	{0x00, 0x03, 0x01, 0xaa},
+	{0x00, 0x04, 0x10, 0xaa},
+	{0x00, 0x05, 0x10, 0xaa},
+	{0x00, 0x06, 0x10, 0xaa},
+	{0x00, 0x07, 0x10, 0xaa},
+	{0x00, 0x08, 0x13, 0xaa},
+	{0x00, 0x0a, 0x00, 0xaa},
+	{0x00, 0x0b, 0x10, 0xaa},
+	{0x00, 0x0c, 0x20, 0xaa},
+	{0x00, 0x0d, 0x18, 0xaa},
+	{0x00, 0x22, 0x01, 0xaa},
+	{0x00, 0x23, 0x60, 0xaa},
+	{0x00, 0x25, 0x08, 0xaa},
+	{0x00, 0x26, 0x82, 0xaa},
+	{0x00, 0x2e, 0x0f, 0xaa},
+	{0x00, 0x2f, 0x1e, 0xaa},
+	{0x00, 0x30, 0x2d, 0xaa},
+	{0x00, 0x31, 0x3c, 0xaa},
+	{0x00, 0x32, 0x4b, 0xaa},
+	{0x00, 0x33, 0x5a, 0xaa},
+	{0x00, 0x34, 0x69, 0xaa},
+	{0x00, 0x35, 0x78, 0xaa},
+	{0x00, 0x36, 0x87, 0xaa},
+	{0x00, 0x37, 0x96, 0xaa},
+	{0x00, 0x38, 0xa5, 0xaa},
+	{0x00, 0x39, 0xb4, 0xaa},
+	{0x00, 0x3a, 0xc3, 0xaa},
+	{0x00, 0x3b, 0xd2, 0xaa},
+	{0x00, 0x3c, 0xe1, 0xaa},
+	{0x00, 0x3e, 0xff, 0xaa},
+	{0x00, 0x3f, 0xff, 0xaa},
+	{0x00, 0x40, 0xff, 0xaa},
+	{0x00, 0x41, 0xff, 0xaa},
+	{0x00, 0x42, 0xff, 0xaa},
+	{0x00, 0x43, 0xff, 0xaa},
+	{0x00, 0x03, 0x00, 0xaa},
+	{0x00, 0x03, 0x00, 0xaa},
+	{0x00, 0x20, 0xc4, 0xaa},
+	{0x00, 0x13, 0x03, 0xaa},
+	{0x00, 0x3c, 0x50, 0xaa},
+	{0x00, 0x61, 0x6a, 0xaa},	/* sharpness? */
+	{0x00, 0x51, 0x5b, 0xaa},
+	{0x00, 0x52, 0x91, 0xaa},
+	{0x00, 0x53, 0x4c, 0xaa},
+	{0x00, 0x54, 0x50, 0xaa},
+	{0x00, 0x56, 0x02, 0xaa},
+	{0xb6, 0x00, 0x00, 0xcc},
+	{0xb6, 0x03, 0x03, 0xcc},
+	{0xb6, 0x02, 0x20, 0xcc},
+	{0xb6, 0x05, 0x02, 0xcc},
+	{0xb6, 0x04, 0x58, 0xcc},
+	{0xb6, 0x12, 0xf8, 0xcc},
+	{0xb6, 0x13, 0x21, 0xcc},
+	{0xb6, 0x18, 0x03, 0xcc},
+	{0xb6, 0x17, 0xa9, 0xcc},
+	{0xb6, 0x16, 0x80, 0xcc},
+	{0xb6, 0x22, 0x12, 0xcc},
+	{0xb6, 0x23, 0x0b, 0xcc},
+	{0xbf, 0xc0, 0x39, 0xcc},
+	{0xbf, 0xc1, 0x04, 0xcc},
+	{0xbf, 0xcc, 0x00, 0xcc},
+	{0xb8, 0x06, 0x20, 0xcc},
+	{0xb8, 0x07, 0x03, 0xcc},
+	{0xb8, 0x08, 0x58, 0xcc},
+	{0xb8, 0x09, 0x02, 0xcc},
+	{0xb3, 0x01, 0x41, 0xcc},
+	{0x00, 0x03, 0x00, 0xaa},
+	{0x00, 0xd9, 0x0f, 0xaa},
+	{0x00, 0xda, 0xaa, 0xaa},
+	{0x00, 0xd9, 0x10, 0xaa},
+	{0x00, 0xda, 0xaa, 0xaa},
+	{0x00, 0xd9, 0x11, 0xaa},
+	{0x00, 0xda, 0x00, 0xaa},
+	{0x00, 0xd9, 0x12, 0xaa},
+	{0x00, 0xda, 0xff, 0xaa},
+	{0x00, 0xd9, 0x13, 0xaa},
+	{0x00, 0xda, 0xff, 0xaa},
+	{0x00, 0xe8, 0x11, 0xaa},
+	{0x00, 0xe9, 0x12, 0xaa},
+	{0x00, 0xea, 0x5c, 0xaa},
+	{0x00, 0xeb, 0xff, 0xaa},
+	{0x00, 0xd8, 0x80, 0xaa},
+	{0x00, 0xe6, 0x02, 0xaa},
+	{0x00, 0xd6, 0x40, 0xaa},
+	{0x00, 0xe3, 0x05, 0xaa},
+	{0x00, 0xe0, 0x40, 0xaa},
+	{0x00, 0xde, 0x03, 0xaa},
+	{0x00, 0xdf, 0x03, 0xaa},
+	{0x00, 0xdb, 0x02, 0xaa},
+	{0x00, 0xdc, 0x00, 0xaa},
+	{0x00, 0xdd, 0x03, 0xaa},
+	{0x00, 0xe1, 0x08, 0xaa},
+	{0x00, 0xe2, 0x01, 0xaa},
+	{0x00, 0xd6, 0x40, 0xaa},
+	{0x00, 0xe4, 0x40, 0xaa},
+	{0x00, 0xa8, 0x8f, 0xaa},
+	{0x00, 0xb4, 0x16, 0xaa},
+	{0xb0, 0x02, 0x06, 0xcc},
+	{0xb0, 0x18, 0x06, 0xcc},
+	{0xb0, 0x19, 0x06, 0xcc},
+	{0xb3, 0x5d, 0x18, 0xcc},
+	{0xb3, 0x05, 0x00, 0xcc},
+	{0xb3, 0x06, 0x00, 0xcc},
+	{0x00, 0xb4, 0x0e, 0xaa},
+	{0x00, 0xb5, 0x49, 0xaa},
+	{0x00, 0xb6, 0x1c, 0xaa},
+	{0x00, 0xb7, 0x96, 0xaa},
+/* end of usbvm326.inf - start of ms-win trace */
+	{0xb6, 0x12, 0xf8, 0xcc},
+	{0xb6, 0x13, 0x3d, 0xcc},
+/*read b306*/
+	{0x00, 0x03, 0x00, 0xaa},
+	{0x00, 0x1a, 0x09, 0xaa},
+	{0x00, 0x1b, 0x8a, 0xaa},
+/*read b827*/
+	{0xb8, 0x27, 0x00, 0xcc},
+	{0xb8, 0x26, 0x60, 0xcc},
+	{0xb8, 0x26, 0x60, 0xcc},
+/*gamma - to do?*/
+	{0x00, 0x03, 0x00, 0xaa},
+	{0x00, 0xae, 0x84, 0xaa},
+/*gamma again*/
+	{0x00, 0x03, 0x00, 0xaa},
+	{0x00, 0x96, 0xa0, 0xaa},
+/*matrix*/
+	{0x00, 0x03, 0x00, 0xaa},
+	{0x00, 0x91, 0x35, 0xaa},
+	{0x00, 0x92, 0x22, 0xaa},
+/*gamma*/
+	{0x00, 0x03, 0x00, 0xaa},
+	{0x00, 0x95, 0x85, 0xaa},
+/*matrix*/
+	{0x00, 0x03, 0x00, 0xaa},
+	{0x00, 0x4d, 0x20, 0xaa},
+	{0xb8, 0x22, 0x40, 0xcc},
+	{0xb8, 0x23, 0x40, 0xcc},
+	{0xb8, 0x24, 0x40, 0xcc},
+	{0xb8, 0x81, 0x09, 0xcc},
+	{0x00, 0x00, 0x64, 0xdd},
+	{0x00, 0x03, 0x01, 0xaa},
+/*read 46*/
+	{0x00, 0x46, 0x3c, 0xaa},
+	{0x00, 0x03, 0x00, 0xaa},
+	{0x00, 0x16, 0x40, 0xaa},
+	{0x00, 0x17, 0x40, 0xaa},
+	{0x00, 0x18, 0x40, 0xaa},
+	{0x00, 0x19, 0x41, 0xaa},
+	{0x00, 0x03, 0x01, 0xaa},
+	{0x00, 0x46, 0x3c, 0xaa},
+	{0x00, 0x00, 0x18, 0xdd},
+/*read bfff*/
+	{0x00, 0x03, 0x00, 0xaa},
+	{0x00, 0xb4, 0x1c, 0xaa},
+	{0x00, 0xb5, 0x92, 0xaa},
+	{0x00, 0xb6, 0x39, 0xaa},
+	{0x00, 0xb7, 0x24, 0xaa},
+/*write 89 0400 1415*/
+	{}
+};
+
+static const u8 poxxxx_init_common[][4] = {
+	{0xb3, 0x00, 0x04, 0xcc},
+	{0x00, 0x00, 0x10, 0xdd},
+	{0xb3, 0x00, 0x64, 0xcc},
+	{0x00, 0x00, 0x10, 0xdd},
+	{0xb3, 0x00, 0x65, 0xcc},
+	{0x00, 0x00, 0x10, 0xdd},
+	{0xb3, 0x00, 0x67, 0xcc},
+	{0xb0, 0x03, 0x09, 0xcc},
+	{0xb3, 0x05, 0x00, 0xcc},
+	{0xb3, 0x06, 0x00, 0xcc},
+	{0xb3, 0x5c, 0x01, 0xcc},
+	{0xb3, 0x08, 0x01, 0xcc},
+	{0xb3, 0x09, 0x0c, 0xcc},
+	{0xb3, 0x34, 0x01, 0xcc},
+	{0xb3, 0x35, 0xf6, 0xcc},	/* i2c add: 76 */
+	{0xb3, 0x02, 0xb0, 0xcc},
+	{0xb3, 0x03, 0x18, 0xcc},
+	{0xb3, 0x04, 0x15, 0xcc},
+	{0xb3, 0x20, 0x00, 0xcc},
+	{0xb3, 0x21, 0x00, 0xcc},
+	{0xb3, 0x22, 0x04, 0xcc},	/* sensor height = 1024 */
+	{0xb3, 0x23, 0x00, 0xcc},
+	{0xb3, 0x14, 0x00, 0xcc},
+	{0xb3, 0x15, 0x00, 0xcc},
+	{0xb3, 0x16, 0x04, 0xcc},	/* sensor width = 1280 */
+	{0xb3, 0x17, 0xff, 0xcc},
+	{0xb3, 0x2c, 0x03, 0xcc},
+	{0xb3, 0x2d, 0x56, 0xcc},
+	{0xb3, 0x2e, 0x02, 0xcc},
+	{0xb3, 0x2f, 0x0a, 0xcc},
+	{0xb3, 0x40, 0x00, 0xcc},
+	{0xb3, 0x41, 0x34, 0xcc},
+	{0xb3, 0x42, 0x01, 0xcc},
+	{0xb3, 0x43, 0xe0, 0xcc},
+	{0xbc, 0x00, 0x71, 0xcc},
+	{0xbc, 0x01, 0x01, 0xcc},
+	{0xb3, 0x01, 0x41, 0xcc},
+	{0xb3, 0x4d, 0x00, 0xcc},
+	{0x00, 0x0b, 0x2a, 0xaa},
+	{0x00, 0x0e, 0x03, 0xaa},
+	{0x00, 0x0f, 0xea, 0xaa},
+	{0x00, 0x12, 0x08, 0xaa},
+	{0x00, 0x1e, 0x06, 0xaa},
+	{0x00, 0x21, 0x00, 0xaa},
+	{0x00, 0x31, 0x1f, 0xaa},
+	{0x00, 0x33, 0x38, 0xaa},
+	{0x00, 0x36, 0xc0, 0xaa},
+	{0x00, 0x37, 0xc8, 0xaa},
+	{0x00, 0x3b, 0x36, 0xaa},
+	{0x00, 0x4b, 0xfe, 0xaa},
+	{0x00, 0x4d, 0x2e, 0xaa},
+	{0x00, 0x51, 0x1c, 0xaa},
+	{0x00, 0x52, 0x01, 0xaa},
+	{0x00, 0x55, 0x0a, 0xaa},
+	{0x00, 0x56, 0x0a, 0xaa},
+	{0x00, 0x57, 0x07, 0xaa},
+	{0x00, 0x58, 0x07, 0xaa},
+	{0x00, 0x59, 0x04, 0xaa},
+	{0x00, 0x70, 0x68, 0xaa},
+	{0x00, 0x71, 0x04, 0xaa},
+	{0x00, 0x72, 0x10, 0xaa},
+	{0x00, 0x80, 0x71, 0xaa},
+	{0x00, 0x81, 0x08, 0xaa},
+	{0x00, 0x82, 0x00, 0xaa},
+	{0x00, 0x83, 0x55, 0xaa},
+	{0x00, 0x84, 0x06, 0xaa},
+	{0x00, 0x85, 0x06, 0xaa},
+	{0x00, 0x8b, 0x25, 0xaa},
+	{0x00, 0x8c, 0x00, 0xaa},
+	{0x00, 0x8d, 0x86, 0xaa},
+	{0x00, 0x8e, 0x82, 0xaa},
+	{0x00, 0x8f, 0x2d, 0xaa},
+	{0x00, 0x90, 0x8b, 0xaa},
+	{0x00, 0x91, 0x81, 0xaa},
+	{0x00, 0x92, 0x81, 0xaa},
+	{0x00, 0x93, 0x23, 0xaa},
+	{0x00, 0xa3, 0x2a, 0xaa},
+	{0x00, 0xa4, 0x03, 0xaa},
+	{0x00, 0xa5, 0xea, 0xaa},
+	{0x00, 0xb0, 0x68, 0xaa},
+	{0x00, 0xbc, 0x04, 0xaa},
+	{0x00, 0xbe, 0x3b, 0xaa},
+	{0x00, 0x4e, 0x40, 0xaa},
+	{0x00, 0x06, 0x04, 0xaa},
+	{0x00, 0x07, 0x03, 0xaa},
+	{0x00, 0xcd, 0x18, 0xaa},
+	{0x00, 0x28, 0x03, 0xaa},
+	{0x00, 0x29, 0xef, 0xaa},
+/* reinit on alt 2 (qvga) or alt7 (vga) */
+	{0xb3, 0x05, 0x00, 0xcc},
+	{0xb3, 0x06, 0x00, 0xcc},
+	{0xb8, 0x00, 0x01, 0xcc},
+
+	{0x00, 0x1d, 0x85, 0xaa},
+	{0x00, 0x1e, 0xc6, 0xaa},
+	{0x00, 0x00, 0x40, 0xdd},
+	{0x00, 0x1d, 0x05, 0xaa},
+	{}
+};
+static const u8 poxxxx_gamma[][4] = {
+	{0x00, 0xd6, 0x22, 0xaa},	/* gamma 0 */
+	{0x00, 0x73, 0x00, 0xaa},
+	{0x00, 0x74, 0x0a, 0xaa},
+	{0x00, 0x75, 0x16, 0xaa},
+	{0x00, 0x76, 0x25, 0xaa},
+	{0x00, 0x77, 0x34, 0xaa},
+	{0x00, 0x78, 0x49, 0xaa},
+	{0x00, 0x79, 0x5a, 0xaa},
+	{0x00, 0x7a, 0x7f, 0xaa},
+	{0x00, 0x7b, 0x9b, 0xaa},
+	{0x00, 0x7c, 0xba, 0xaa},
+	{0x00, 0x7d, 0xd4, 0xaa},
+	{0x00, 0x7e, 0xea, 0xaa},
+
+	{0x00, 0xd6, 0x62, 0xaa},	/* gamma 1 */
+	{0x00, 0x73, 0x00, 0xaa},
+	{0x00, 0x74, 0x0a, 0xaa},
+	{0x00, 0x75, 0x16, 0xaa},
+	{0x00, 0x76, 0x25, 0xaa},
+	{0x00, 0x77, 0x34, 0xaa},
+	{0x00, 0x78, 0x49, 0xaa},
+	{0x00, 0x79, 0x5a, 0xaa},
+	{0x00, 0x7a, 0x7f, 0xaa},
+	{0x00, 0x7b, 0x9b, 0xaa},
+	{0x00, 0x7c, 0xba, 0xaa},
+	{0x00, 0x7d, 0xd4, 0xaa},
+	{0x00, 0x7e, 0xea, 0xaa},
+
+	{0x00, 0xd6, 0xa2, 0xaa},	/* gamma 2 */
+	{0x00, 0x73, 0x00, 0xaa},
+	{0x00, 0x74, 0x0a, 0xaa},
+	{0x00, 0x75, 0x16, 0xaa},
+	{0x00, 0x76, 0x25, 0xaa},
+	{0x00, 0x77, 0x34, 0xaa},
+	{0x00, 0x78, 0x49, 0xaa},
+	{0x00, 0x79, 0x5a, 0xaa},
+	{0x00, 0x7a, 0x7f, 0xaa},
+	{0x00, 0x7b, 0x9b, 0xaa},
+	{0x00, 0x7c, 0xba, 0xaa},
+	{0x00, 0x7d, 0xd4, 0xaa},
+	{0x00, 0x7e, 0xea, 0xaa},
+	{}
+};
+static const u8 poxxxx_init_start_3[][4] = {
+	{0x00, 0xb8, 0x28, 0xaa},
+	{0x00, 0xb9, 0x1e, 0xaa},
+	{0x00, 0xb6, 0x14, 0xaa},
+	{0x00, 0xb7, 0x0f, 0xaa},
+	{0x00, 0x5c, 0x10, 0xaa},
+	{0x00, 0x5d, 0x18, 0xaa},
+	{0x00, 0x5e, 0x24, 0xaa},
+	{0x00, 0x5f, 0x24, 0xaa},
+	{0x00, 0x86, 0x1a, 0xaa},
+	{0x00, 0x60, 0x00, 0xaa},
+	{0x00, 0x61, 0x1b, 0xaa},
+	{0x00, 0x62, 0x30, 0xaa},
+	{0x00, 0x63, 0x40, 0xaa},
+	{0x00, 0x87, 0x1a, 0xaa},
+	{0x00, 0x64, 0x00, 0xaa},
+	{0x00, 0x65, 0x08, 0xaa},
+	{0x00, 0x66, 0x10, 0xaa},
+	{0x00, 0x67, 0x20, 0xaa},
+	{0x00, 0x88, 0x10, 0xaa},
+	{0x00, 0x68, 0x00, 0xaa},
+	{0x00, 0x69, 0x08, 0xaa},
+	{0x00, 0x6a, 0x0f, 0xaa},
+	{0x00, 0x6b, 0x0f, 0xaa},
+	{0x00, 0x89, 0x07, 0xaa},
+	{0x00, 0xd5, 0x4c, 0xaa},
+	{0x00, 0x0a, 0x00, 0xaa},
+	{0x00, 0x0b, 0x2a, 0xaa},
+	{0x00, 0x0e, 0x03, 0xaa},
+	{0x00, 0x0f, 0xea, 0xaa},
+	{0x00, 0xa2, 0x00, 0xaa},
+	{0x00, 0xa3, 0x2a, 0xaa},
+	{0x00, 0xa4, 0x03, 0xaa},
+	{0x00, 0xa5, 0xea, 0xaa},
+	{}
+};
+static const u8 poxxxx_initVGA[][4] = {
+	{0x00, 0x20, 0x11, 0xaa},
+	{0x00, 0x33, 0x38, 0xaa},
+	{0x00, 0xbb, 0x0d, 0xaa},
+	{0xb3, 0x22, 0x01, 0xcc},	/* change to 640x480 */
+	{0xb3, 0x23, 0xe0, 0xcc},
+	{0xb3, 0x16, 0x02, 0xcc},
+	{0xb3, 0x17, 0x7f, 0xcc},
+	{0xb3, 0x02, 0xb0, 0xcc},
+	{0xb3, 0x06, 0x00, 0xcc},
+	{0xb3, 0x5c, 0x01, 0xcc},
+	{0x00, 0x04, 0x06, 0xaa},
+	{0x00, 0x05, 0x3f, 0xaa},
+	{0x00, 0x04, 0x00, 0xdd},	/* delay 1s */
+	{}
+};
+static const u8 poxxxx_initQVGA[][4] = {
+	{0x00, 0x20, 0x33, 0xaa},
+	{0x00, 0x33, 0x38, 0xaa},
+	{0x00, 0xbb, 0x0d, 0xaa},
+	{0xb3, 0x22, 0x00, 0xcc},	/* change to 320x240 */
+	{0xb3, 0x23, 0xf0, 0xcc},
+	{0xb3, 0x16, 0x01, 0xcc},
+	{0xb3, 0x17, 0x3f, 0xcc},
+	{0xb3, 0x02, 0xb0, 0xcc},
+	{0xb3, 0x06, 0x01, 0xcc},
+	{0xb3, 0x5c, 0x00, 0xcc},
+	{0x00, 0x04, 0x06, 0xaa},
+	{0x00, 0x05, 0x3f, 0xaa},
+	{0x00, 0x04, 0x00, 0xdd},	/* delay 1s */
+	{}
+};
+static const u8 poxxxx_init_end_1[][4] = {
+	{0x00, 0x47, 0x25, 0xaa},
+	{0x00, 0x48, 0x80, 0xaa},
+	{0x00, 0x49, 0x1f, 0xaa},
+	{0x00, 0x4a, 0x40, 0xaa},
+	{0x00, 0x44, 0x40, 0xaa},
+	{0x00, 0xab, 0x4a, 0xaa},
+	{0x00, 0xb1, 0x00, 0xaa},
+	{0x00, 0xb2, 0x04, 0xaa},
+	{0x00, 0xb3, 0x08, 0xaa},
+	{0x00, 0xb4, 0x0b, 0xaa},
+	{0x00, 0xb5, 0x0d, 0xaa},
+	{}
+};
+static const u8 poxxxx_init_end_2[][4] = {
+	{0x00, 0x1d, 0x85, 0xaa},
+	{0x00, 0x1e, 0x06, 0xaa},
+	{0x00, 0x1d, 0x05, 0xaa},
+	{}
+};
+
+struct sensor_info {
+	s8 sensorId;
+	u8 I2cAdd;
+	u8 IdAdd;
+	u16 VpId;
+	u8 m1;
+	u8 m2;
+	u8 op;
+};
+
+/* probe values */
+static const struct sensor_info vc0321_probe_data[] = {
+/*      sensorId,	   I2cAdd,	IdAdd,  VpId,  m1,    m2,  op */
+/* 0 OV9640 */
+	{-1,		    0x80 | 0x30, 0x0a, 0x0000, 0x25, 0x24, 0x05},
+/* 1 ICM108T (may respond on IdAdd == 0x83 - tested in vc032x_probe_sensor) */
+	{-1,		    0x80 | 0x20, 0x82, 0x0000, 0x24, 0x25, 0x01},
+/* 2 PO2130 (may detect PO3130NC - tested in vc032x_probe_sensor)*/
+	{-1,		    0x80 | 0x76, 0x00, 0x0000, 0x24, 0x25, 0x01},
+/* 3 MI1310 */
+	{-1,		    0x80 | 0x5d, 0x00, 0x0000, 0x24, 0x25, 0x01},
+/* 4 MI360 - tested in vc032x_probe_sensor */
+/*	{SENSOR_MI0360,	    0x80 | 0x5d, 0x00, 0x8243, 0x24, 0x25, 0x01}, */
+/* 5 7131R */
+	{SENSOR_HV7131R,    0x80 | 0x11, 0x00, 0x0209, 0x24, 0x25, 0x01},
+/* 6 OV7649 */
+	{-1,		    0x80 | 0x21, 0x0a, 0x0000, 0x21, 0x20, 0x05},
+/* 7 PAS302BCW */
+	{-1,		    0x80 | 0x40, 0x00, 0x0000, 0x20, 0x22, 0x05},
+/* 8 OV7660 */
+	{SENSOR_OV7660,     0x80 | 0x21, 0x0a, 0x7660, 0x26, 0x26, 0x05},
+/* 9 PO3130NC - (tested in vc032x_probe_sensor) */
+/*	{SENSOR_PO3130NC,   0x80 | 0x76, 0x00, 0x3130, 0x24, 0x25, 0x01}, */
+/* 10 PO1030KC */
+	{-1,		    0x80 | 0x6e, 0x00, 0x0000, 0x24, 0x25, 0x01},
+/* 11 MI1310_SOC */
+	{SENSOR_MI1310_SOC, 0x80 | 0x5d, 0x00, 0x143a, 0x24, 0x25, 0x01},
+/* 12 OV9650 */
+	{-1,		    0x80 | 0x30, 0x0a, 0x0000, 0x25, 0x24, 0x05},
+/* 13 S5K532 */
+	{-1,		    0x80 | 0x11, 0x39, 0x0000, 0x24, 0x25, 0x01},
+/* 14 MI360_SOC - ??? */
+/* 15 PO1200N */
+	{SENSOR_PO1200,     0x80 | 0x5c, 0x00, 0x1200, 0x67, 0x67, 0x01},
+/* 16 PO3030K */
+	{-1,		    0x80 | 0x18, 0x00, 0x0000, 0x24, 0x25, 0x01},
+/* 17 PO2030 */
+	{-1,		    0x80 | 0x6e, 0x00, 0x0000, 0x24, 0x25, 0x01},
+/* ?? */
+	{-1,		    0x80 | 0x56, 0x01, 0x0000, 0x64, 0x67, 0x01},
+	{SENSOR_MI1320,     0x80 | 0x48, 0x00, 0x148c, 0x64, 0x65, 0x01},
+};
+static const struct sensor_info vc0323_probe_data[] = {
+/*      sensorId,	   I2cAdd,	IdAdd,  VpId,  m1,    m2,  op */
+/* 0 OV9640 */
+	{-1,		    0x80 | 0x30, 0x0a, 0x0000, 0x25, 0x24, 0x05},
+/* 1 ICM108T (may respond on IdAdd == 0x83 - tested in vc032x_probe_sensor) */
+	{-1,		    0x80 | 0x20, 0x82, 0x0000, 0x24, 0x25, 0x01},
+/* 2 PO2130 (may detect PO3130NC - tested in vc032x_probe_sensor)*/
+	{-1,		    0x80 | 0x76, 0x00, 0x0000, 0x24, 0x25, 0x01},
+/* 3 MI1310 */
+	{-1,		    0x80 | 0x5d, 0x00, 0x0000, 0x24, 0x25, 0x01},
+/* 4 MI360 - tested in vc032x_probe_sensor */
+/*	{SENSOR_MI0360,	    0x80 | 0x5d, 0x00, 0x8243, 0x24, 0x25, 0x01}, */
+/* 5 7131R */
+	{SENSOR_HV7131R,    0x80 | 0x11, 0x00, 0x0209, 0x24, 0x25, 0x01},
+/* 6 OV7649 */
+	{-1,		    0x80 | 0x21, 0x0a, 0x0000, 0x21, 0x20, 0x05},
+/* 7 PAS302BCW */
+	{-1,		    0x80 | 0x40, 0x00, 0x0000, 0x20, 0x22, 0x05},
+/* 8 OV7660 */
+	{SENSOR_OV7660,     0x80 | 0x21, 0x0a, 0x7660, 0x26, 0x26, 0x05},
+/* 9 PO3130NC - (tested in vc032x_probe_sensor) */
+/*	{SENSOR_PO3130NC,   0x80 | 0x76, 0x00, 0x3130, 0x24, 0x25, 0x01}, */
+/* 10 PO1030KC */
+	{-1,		    0x80 | 0x6e, 0x00, 0x0000, 0x24, 0x25, 0x01},
+/* 11 MI1310_SOC */
+	{SENSOR_MI1310_SOC, 0x80 | 0x5d, 0x00, 0x143a, 0x24, 0x25, 0x01},
+/* 12 OV9650 */
+	{-1,		    0x80 | 0x30, 0x0a, 0x0000, 0x25, 0x24, 0x05},
+/* 13 S5K532 */
+	{-1,		    0x80 | 0x11, 0x39, 0x0000, 0x24, 0x25, 0x01},
+/* 14 MI360_SOC - ??? */
+/* 15 PO1200N */
+	{SENSOR_PO1200,     0x80 | 0x5c, 0x00, 0x1200, 0x67, 0x67, 0x01},
+/* 16 ?? */
+	{-1,		    0x80 | 0x2d, 0x00, 0x0000, 0x65, 0x67, 0x01},
+/* 17 PO2030 */
+	{-1,		    0x80 | 0x6e, 0x00, 0x0000, 0x24, 0x25, 0x01},
+/* ?? */
+	{-1,		    0x80 | 0x56, 0x01, 0x0000, 0x64, 0x67, 0x01},
+	{SENSOR_MI1320_SOC, 0x80 | 0x48, 0x00, 0x148c, 0x64, 0x67, 0x01},
+/*fixme: not in the ms-win probe - may be found before? */
+	{SENSOR_OV7670,     0x80 | 0x21, 0x0a, 0x7673, 0x66, 0x67, 0x05},
+};
+
+/* read 'len' bytes in gspca_dev->usb_buf */
+static void reg_r_i(struct gspca_dev *gspca_dev,
+		  u16 req,
+		  u16 index,
+		  u16 len)
+{
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	ret = usb_control_msg(gspca_dev->dev,
+			usb_rcvctrlpipe(gspca_dev->dev, 0),
+			req,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			1,			/* value */
+			index, gspca_dev->usb_buf, len,
+			500);
+	if (ret < 0) {
+		pr_err("reg_r err %d\n", ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+static void reg_r(struct gspca_dev *gspca_dev,
+		  u16 req,
+		  u16 index,
+		  u16 len)
+{
+	reg_r_i(gspca_dev, req, index, len);
+	if (gspca_dev->usb_err < 0)
+		return;
+	if (len == 1)
+		gspca_dbg(gspca_dev, D_USBI, "GET %02x 0001 %04x %02x\n",
+			  req, index,
+			  gspca_dev->usb_buf[0]);
+	else
+		gspca_dbg(gspca_dev, D_USBI, "GET %02x 0001 %04x %*ph\n",
+			  req, index, 3, gspca_dev->usb_buf);
+}
+
+static void reg_w_i(struct gspca_dev *gspca_dev,
+			    u16 req,
+			    u16 value,
+			    u16 index)
+{
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	ret = usb_control_msg(gspca_dev->dev,
+			usb_sndctrlpipe(gspca_dev->dev, 0),
+			req,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			value, index, NULL, 0,
+			500);
+	if (ret < 0) {
+		pr_err("reg_w err %d\n", ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+static void reg_w(struct gspca_dev *gspca_dev,
+			    u16 req,
+			    u16 value,
+			    u16 index)
+{
+	if (gspca_dev->usb_err < 0)
+		return;
+	gspca_dbg(gspca_dev, D_USBO, "SET %02x %04x %04x\n", req, value, index);
+	reg_w_i(gspca_dev, req, value, index);
+}
+
+static u16 read_sensor_register(struct gspca_dev *gspca_dev,
+				u16 address)
+{
+	u8 ldata, mdata, hdata;
+	int retry = 50;
+
+	reg_r(gspca_dev, 0xa1, 0xb33f, 1);
+	if (!(gspca_dev->usb_buf[0] & 0x02)) {
+		pr_err("I2c Bus Busy Wait %02x\n", gspca_dev->usb_buf[0]);
+		return 0;
+	}
+	reg_w(gspca_dev, 0xa0, address, 0xb33a);
+	reg_w(gspca_dev, 0xa0, 0x02, 0xb339);
+
+	do {
+		reg_r(gspca_dev, 0xa1, 0xb33b, 1);
+		if (gspca_dev->usb_buf[0] == 0x00)
+			break;
+		msleep(40);
+	} while (--retry >= 0);
+
+	reg_r(gspca_dev, 0xa1, 0xb33e, 1);
+	ldata = gspca_dev->usb_buf[0];
+	reg_r(gspca_dev, 0xa1, 0xb33d, 1);
+	mdata = gspca_dev->usb_buf[0];
+	reg_r(gspca_dev, 0xa1, 0xb33c, 1);
+	hdata = gspca_dev->usb_buf[0];
+	if (hdata != 0 && mdata != 0 && ldata != 0)
+		gspca_dbg(gspca_dev, D_PROBE, "Read Sensor %02x%02x %02x\n",
+			  hdata, mdata, ldata);
+	reg_r(gspca_dev, 0xa1, 0xb334, 1);
+	if (gspca_dev->usb_buf[0] == 0x02)
+		return (hdata << 8) + mdata;
+	return hdata;
+}
+
+static int vc032x_probe_sensor(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int i, n;
+	u16 value;
+	const struct sensor_info *ptsensor_info;
+
+/*fixme: should also check the other sensor (back mi1320_soc, front mc501cb)*/
+	if (sd->flags & FL_SAMSUNG) {
+		reg_w(gspca_dev, 0xa0, 0x01, 0xb301);
+		reg_w(gspca_dev, 0x89, 0xf0ff, 0xffff);
+						/* select the back sensor */
+	}
+
+	reg_r(gspca_dev, 0xa1, 0xbfcf, 1);
+	gspca_dbg(gspca_dev, D_PROBE, "vc032%d check sensor header %02x\n",
+		  sd->bridge == BRIDGE_VC0321 ? 1 : 3, gspca_dev->usb_buf[0]);
+	if (sd->bridge == BRIDGE_VC0321) {
+		ptsensor_info = vc0321_probe_data;
+		n = ARRAY_SIZE(vc0321_probe_data);
+	} else {
+		ptsensor_info = vc0323_probe_data;
+		n = ARRAY_SIZE(vc0323_probe_data);
+	}
+	for (i = 0; i < n; i++) {
+		reg_w(gspca_dev, 0xa0, 0x02, 0xb334);
+		reg_w(gspca_dev, 0xa0, ptsensor_info->m1, 0xb300);
+		reg_w(gspca_dev, 0xa0, ptsensor_info->m2, 0xb300);
+		reg_w(gspca_dev, 0xa0, 0x01, 0xb308);
+		reg_w(gspca_dev, 0xa0, 0x0c, 0xb309);
+		reg_w(gspca_dev, 0xa0, ptsensor_info->I2cAdd, 0xb335);
+		reg_w(gspca_dev, 0xa0, ptsensor_info->op, 0xb301);
+		value = read_sensor_register(gspca_dev, ptsensor_info->IdAdd);
+		if (value == 0 && ptsensor_info->IdAdd == 0x82)
+			value = read_sensor_register(gspca_dev, 0x83);
+		if (value != 0) {
+			gspca_dbg(gspca_dev, D_PROBE, "Sensor ID %04x (%d)\n",
+				  value, i);
+			if (value == ptsensor_info->VpId)
+				return ptsensor_info->sensorId;
+
+			switch (value) {
+			case 0x3130:
+				return SENSOR_PO3130NC;
+			case 0x7673:
+				return SENSOR_OV7670;
+			case 0x8243:
+				return SENSOR_MI0360;
+			}
+		}
+		ptsensor_info++;
+	}
+	return -1;
+}
+
+static void i2c_write(struct gspca_dev *gspca_dev,
+			u8 reg, const u8 *val,
+			u8 size)		/* 1 or 2 */
+{
+	int retry;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	if (size == 1)
+		gspca_dbg(gspca_dev, D_USBO, "i2c_w %02x %02x\n", reg, *val);
+	else
+		gspca_dbg(gspca_dev, D_USBO, "i2c_w %02x %02x%02x\n",
+			  reg, *val, val[1]);
+	reg_r_i(gspca_dev, 0xa1, 0xb33f, 1);
+/*fixme:should check if (!(gspca_dev->usb_buf[0] & 0x02)) error*/
+	reg_w_i(gspca_dev, 0xa0, size, 0xb334);
+	reg_w_i(gspca_dev, 0xa0, reg, 0xb33a);
+	reg_w_i(gspca_dev, 0xa0, val[0], 0xb336);
+	if (size > 1)
+		reg_w_i(gspca_dev, 0xa0, val[1], 0xb337);
+	reg_w_i(gspca_dev, 0xa0, 0x01, 0xb339);
+	retry = 4;
+	do {
+		reg_r_i(gspca_dev, 0xa1, 0xb33b, 1);
+		if (gspca_dev->usb_buf[0] == 0)
+			break;
+		msleep(20);
+	} while (--retry > 0);
+	if (retry <= 0)
+		pr_err("i2c_write timeout\n");
+}
+
+static void put_tab_to_reg(struct gspca_dev *gspca_dev,
+			const u8 *tab, u8 tabsize, u16 addr)
+{
+	int j;
+	u16 ad = addr;
+
+	for (j = 0; j < tabsize; j++)
+		reg_w(gspca_dev, 0xa0, tab[j], ad++);
+}
+
+static void usb_exchange(struct gspca_dev *gspca_dev,
+			const u8 data[][4])
+{
+	int i = 0;
+
+	for (;;) {
+		switch (data[i][3]) {
+		default:
+			return;
+		case 0xcc:			/* normal write */
+			reg_w(gspca_dev, 0xa0, data[i][2],
+					(data[i][0]) << 8 | data[i][1]);
+			break;
+		case 0xaa:			/* i2c op */
+			i2c_write(gspca_dev, data[i][1], &data[i][2], 1);
+			break;
+		case 0xbb:			/* i2c op */
+			i2c_write(gspca_dev, data[i][0], &data[i][1], 2);
+			break;
+		case 0xdd:
+			msleep(data[i][1] * 256 + data[i][2] + 10);
+			break;
+		}
+		i++;
+	}
+	/*not reached*/
+}
+
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	sd->bridge = id->driver_info >> 8;
+	sd->flags = id->driver_info & 0xff;
+
+	if (id->idVendor == 0x046d &&
+	    (id->idProduct == 0x0892 || id->idProduct == 0x0896))
+		sd->sensor = SENSOR_POxxxx;	/* no probe */
+
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam;
+	int sensor;
+	/* number of packets per ISOC message */
+	static u8 npkt[NSENSORS] = {
+		[SENSOR_HV7131R] =	64,
+		[SENSOR_MI0360] =	32,
+		[SENSOR_MI1310_SOC] =	32,
+		[SENSOR_MI1320] =	64,
+		[SENSOR_MI1320_SOC] =	128,
+		[SENSOR_OV7660] =	32,
+		[SENSOR_OV7670] =	64,
+		[SENSOR_PO1200] =	128,
+		[SENSOR_PO3130NC] =	128,
+		[SENSOR_POxxxx] =	128,
+	};
+
+	if (sd->sensor != SENSOR_POxxxx)
+		sensor = vc032x_probe_sensor(gspca_dev);
+	else
+		sensor = sd->sensor;
+
+	switch (sensor) {
+	case -1:
+		pr_err("Unknown sensor...\n");
+		return -EINVAL;
+	case SENSOR_HV7131R:
+		gspca_dbg(gspca_dev, D_PROBE, "Find Sensor HV7131R\n");
+		break;
+	case SENSOR_MI0360:
+		gspca_dbg(gspca_dev, D_PROBE, "Find Sensor MI0360\n");
+		sd->bridge = BRIDGE_VC0323;
+		break;
+	case SENSOR_MI1310_SOC:
+		gspca_dbg(gspca_dev, D_PROBE, "Find Sensor MI1310_SOC\n");
+		break;
+	case SENSOR_MI1320:
+		gspca_dbg(gspca_dev, D_PROBE, "Find Sensor MI1320\n");
+		break;
+	case SENSOR_MI1320_SOC:
+		gspca_dbg(gspca_dev, D_PROBE, "Find Sensor MI1320_SOC\n");
+		break;
+	case SENSOR_OV7660:
+		gspca_dbg(gspca_dev, D_PROBE, "Find Sensor OV7660\n");
+		break;
+	case SENSOR_OV7670:
+		gspca_dbg(gspca_dev, D_PROBE, "Find Sensor OV7670\n");
+		break;
+	case SENSOR_PO1200:
+		gspca_dbg(gspca_dev, D_PROBE, "Find Sensor PO1200\n");
+		break;
+	case SENSOR_PO3130NC:
+		gspca_dbg(gspca_dev, D_PROBE, "Find Sensor PO3130NC\n");
+		break;
+	case SENSOR_POxxxx:
+		gspca_dbg(gspca_dev, D_PROBE, "Sensor POxxxx\n");
+		break;
+	}
+	sd->sensor = sensor;
+
+	cam = &gspca_dev->cam;
+	if (sd->bridge == BRIDGE_VC0321) {
+		cam->cam_mode = vc0321_mode;
+		cam->nmodes = ARRAY_SIZE(vc0321_mode);
+	} else {
+		switch (sensor) {
+		case SENSOR_PO1200:
+			cam->cam_mode = svga_mode;
+			cam->nmodes = ARRAY_SIZE(svga_mode);
+			break;
+		case SENSOR_MI1310_SOC:
+			cam->cam_mode = vc0323_mode;
+			cam->nmodes = ARRAY_SIZE(vc0323_mode);
+			break;
+		case SENSOR_MI1320_SOC:
+			cam->cam_mode = bi_mode;
+			cam->nmodes = ARRAY_SIZE(bi_mode);
+			break;
+		case SENSOR_OV7670:
+			cam->cam_mode = bi_mode;
+			cam->nmodes = ARRAY_SIZE(bi_mode) - 1;
+			break;
+		default:
+			cam->cam_mode = vc0323_mode;
+			cam->nmodes = ARRAY_SIZE(vc0323_mode) - 1;
+			break;
+		}
+	}
+	cam->npkt = npkt[sd->sensor];
+
+	if (sd->sensor == SENSOR_OV7670)
+		sd->flags |= FL_HFLIP | FL_VFLIP;
+
+	if (sd->bridge == BRIDGE_VC0321) {
+		reg_r(gspca_dev, 0x8a, 0, 3);
+		reg_w(gspca_dev, 0x87, 0x00, 0x0f0f);
+		reg_r(gspca_dev, 0x8b, 0, 3);
+		reg_w(gspca_dev, 0x88, 0x00, 0x0202);
+		if (sd->sensor == SENSOR_POxxxx) {
+			reg_r(gspca_dev, 0xa1, 0xb300, 1);
+			if (gspca_dev->usb_buf[0] != 0) {
+				reg_w(gspca_dev, 0xa0, 0x26, 0xb300);
+				reg_w(gspca_dev, 0xa0, 0x04, 0xb300);
+			}
+			reg_w(gspca_dev, 0xa0, 0x00, 0xb300);
+		}
+	}
+	return gspca_dev->usb_err;
+}
+
+static void setbrightness(struct gspca_dev *gspca_dev, s32 val)
+{
+	u8 data;
+
+	data = val;
+	if (data >= 0x80)
+		data &= 0x7f;
+	else
+		data = 0xff ^ data;
+	i2c_write(gspca_dev, 0x98, &data, 1);
+}
+
+static void setcontrast(struct gspca_dev *gspca_dev, u8 val)
+{
+	i2c_write(gspca_dev, 0x99, &val, 1);
+}
+
+static void setcolors(struct gspca_dev *gspca_dev, u8 val)
+{
+	u8 data;
+
+	data = val - (val >> 3) - 1;
+	i2c_write(gspca_dev, 0x94, &data, 1);
+	i2c_write(gspca_dev, 0x95, &val, 1);
+}
+
+static void sethvflip(struct gspca_dev *gspca_dev, bool hflip, bool vflip)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 data[2];
+
+	if (sd->flags & FL_HFLIP)
+		hflip = !hflip;
+	if (sd->flags & FL_VFLIP)
+		vflip = !vflip;
+	switch (sd->sensor) {
+	case SENSOR_MI1310_SOC:
+	case SENSOR_MI1320:
+	case SENSOR_MI1320_SOC:
+		data[0] = data[1] = 0;		/* select page 0 */
+		i2c_write(gspca_dev, 0xf0, data, 2);
+		data[0] = sd->sensor == SENSOR_MI1310_SOC ? 0x03 : 0x01;
+		data[1] = 0x02 * hflip
+			| 0x01 * vflip;
+		i2c_write(gspca_dev, 0x20, data, 2);
+		break;
+	case SENSOR_OV7660:
+	case SENSOR_OV7670:
+		data[0] = sd->sensor == SENSOR_OV7660 ? 0x01 : 0x07;
+		data[0] |= OV7660_MVFP_MIRROR * hflip
+			| OV7660_MVFP_VFLIP * vflip;
+		i2c_write(gspca_dev, OV7660_REG_MVFP, data, 1);
+		break;
+	case SENSOR_PO1200:
+		data[0] = 0;
+		i2c_write(gspca_dev, 0x03, data, 1);
+		data[0] = 0x80 * hflip
+			| 0x40 * vflip
+			| 0x06;
+		i2c_write(gspca_dev, 0x1e, data, 1);
+		break;
+	}
+}
+
+static void setlightfreq(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	static const u8 (*ov7660_freq_tb[3])[4] =
+		{ov7660_NoFliker, ov7660_50HZ, ov7660_60HZ};
+
+	if (sd->sensor != SENSOR_OV7660)
+		return;
+	usb_exchange(gspca_dev, ov7660_freq_tb[val]);
+}
+
+static void setsharpness(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 data;
+
+	switch (sd->sensor) {
+	case SENSOR_PO1200:
+		data = 0;
+		i2c_write(gspca_dev, 0x03, &data, 1);
+		if (val < 0)
+			data = 0x6a;
+		else
+			data = 0xb5 + val * 3;
+		i2c_write(gspca_dev, 0x61, &data, 1);
+		break;
+	case SENSOR_POxxxx:
+		if (val < 0)
+			data = 0x7e;	/* def = max */
+		else
+			data = 0x60 + val * 0x0f;
+		i2c_write(gspca_dev, 0x59, &data, 1);
+		break;
+	}
+}
+static void setgain(struct gspca_dev *gspca_dev, u8 val)
+{
+	i2c_write(gspca_dev, 0x15, &val, 1);
+}
+
+static void setexposure(struct gspca_dev *gspca_dev, s32 val)
+{
+	u8 data;
+
+	data = val >> 8;
+	i2c_write(gspca_dev, 0x1a, &data, 1);
+	data = val;
+	i2c_write(gspca_dev, 0x1b, &data, 1);
+}
+
+static void setautogain(struct gspca_dev *gspca_dev, s32 val)
+{
+	static const u8 data[2] = {0x28, 0x3c};
+
+	i2c_write(gspca_dev, 0xd1, &data[val], 1);
+}
+
+static void setgamma(struct gspca_dev *gspca_dev)
+{
+/*fixme:to do */
+	usb_exchange(gspca_dev, poxxxx_gamma);
+}
+
+static void setbacklight(struct gspca_dev *gspca_dev, s32 val)
+{
+	u16 v;
+	u8 data;
+
+	data = (val << 4) | 0x0f;
+	i2c_write(gspca_dev, 0xaa, &data, 1);
+	v = 613 + 12 * val;
+	data = v >> 8;
+	i2c_write(gspca_dev, 0xc4, &data, 1);
+	data = v;
+	i2c_write(gspca_dev, 0xc5, &data, 1);
+	v = 1093 - 12 * val;
+	data = v >> 8;
+	i2c_write(gspca_dev, 0xc6, &data, 1);
+	data = v;
+	i2c_write(gspca_dev, 0xc7, &data, 1);
+	v = 342 + 9 * val;
+	data = v >> 8;
+	i2c_write(gspca_dev, 0xc8, &data, 1);
+	data = v;
+	i2c_write(gspca_dev, 0xc9, &data, 1);
+	v = 702 - 9 * val;
+	data = v >> 8;
+	i2c_write(gspca_dev, 0xca, &data, 1);
+	data = v;
+	i2c_write(gspca_dev, 0xcb, &data, 1);
+}
+
+static void setwb(struct gspca_dev *gspca_dev)
+{
+/*fixme:to do - valid when reg d1 = 0x1c - (reg16 + reg15 = 0xa3)*/
+	static const u8 data[2] = {0x00, 0x00};
+
+	i2c_write(gspca_dev, 0x16, &data[0], 1);
+	i2c_write(gspca_dev, 0x18, &data[1], 1);
+}
+
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	const u8 (*init)[4];
+	const u8 *GammaT = NULL;
+	const u8 *MatrixT = NULL;
+	int mode;
+	static const u8 (*mi1320_soc_init[])[4] = {
+		mi1320_soc_InitSXGA,
+		mi1320_soc_InitVGA,
+		mi1320_soc_InitQVGA,
+	};
+
+/*fixme: back sensor only*/
+	if (sd->flags & FL_SAMSUNG) {
+		reg_w(gspca_dev, 0x89, 0xf0ff, 0xffff);
+		reg_w(gspca_dev, 0xa9, 0x8348, 0x000e);
+		reg_w(gspca_dev, 0xa9, 0x0000, 0x001a);
+	}
+
+	/* Assume start use the good resolution from gspca_dev->mode */
+	if (sd->bridge == BRIDGE_VC0321) {
+		reg_w(gspca_dev, 0xa0, 0xff, 0xbfec);
+		reg_w(gspca_dev, 0xa0, 0xff, 0xbfed);
+		reg_w(gspca_dev, 0xa0, 0xff, 0xbfee);
+		reg_w(gspca_dev, 0xa0, 0xff, 0xbfef);
+		sd->image_offset = 46;
+	} else {
+		if (gspca_dev->cam.cam_mode[gspca_dev->curr_mode].pixelformat
+				== V4L2_PIX_FMT_JPEG)
+			sd->image_offset = 0;
+		else
+			sd->image_offset = 32;
+	}
+
+	mode = gspca_dev->cam.cam_mode[(int) gspca_dev->curr_mode].priv;
+	switch (sd->sensor) {
+	case SENSOR_HV7131R:
+		GammaT = hv7131r_gamma;
+		MatrixT = hv7131r_matrix;
+		if (mode)
+			init = hv7131r_initQVGA_data;	/* 320x240 */
+		else
+			init = hv7131r_initVGA_data;	/* 640x480 */
+		break;
+	case SENSOR_OV7660:
+		GammaT = ov7660_gamma;
+		MatrixT = ov7660_matrix;
+		if (mode)
+			init = ov7660_initQVGA_data;	/* 320x240 */
+		else
+			init = ov7660_initVGA_data;	/* 640x480 */
+		break;
+	case SENSOR_MI0360:
+		GammaT = mi1320_gamma;
+		MatrixT = mi0360_matrix;
+		if (mode)
+			init = mi0360_initQVGA_JPG;	/* 320x240 */
+		else
+			init = mi0360_initVGA_JPG;	/* 640x480 */
+		break;
+	case SENSOR_MI1310_SOC:
+		GammaT = mi1320_gamma;
+		MatrixT = mi1320_matrix;
+		switch (mode) {
+		case 1:
+			init = mi1310_socinitQVGA_JPG;	/* 320x240 */
+			break;
+		case 0:
+			init = mi1310_socinitVGA_JPG;	/* 640x480 */
+			break;
+		default:
+			init = mi1310_soc_InitSXGA_JPG;	/* 1280x1024 */
+			break;
+		}
+		break;
+	case SENSOR_MI1320:
+		GammaT = mi1320_gamma;
+		MatrixT = mi1320_matrix;
+		if (mode)
+			init = mi1320_initQVGA_data;	/* 320x240 */
+		else
+			init = mi1320_initVGA_data;	/* 640x480 */
+		break;
+	case SENSOR_MI1320_SOC:
+		GammaT = mi1320_gamma;
+		MatrixT = mi1320_matrix;
+		init = mi1320_soc_init[mode];
+		break;
+	case SENSOR_OV7670:
+		init = mode == 1 ? ov7670_InitVGA : ov7670_InitQVGA;
+		break;
+	case SENSOR_PO3130NC:
+		GammaT = po3130_gamma;
+		MatrixT = po3130_matrix;
+		if (mode)
+			init = po3130_initQVGA_data;	/* 320x240 */
+		else
+			init = po3130_initVGA_data;	/* 640x480 */
+		usb_exchange(gspca_dev, init);
+		init = po3130_rundata;
+		break;
+	case SENSOR_PO1200:
+		GammaT = po1200_gamma;
+		MatrixT = po1200_matrix;
+		init = po1200_initVGA_data;
+		break;
+	default:
+/*	case SENSOR_POxxxx: */
+		usb_exchange(gspca_dev, poxxxx_init_common);
+		setgamma(gspca_dev);
+		usb_exchange(gspca_dev, poxxxx_init_start_3);
+		if (mode)
+			init = poxxxx_initQVGA;
+		else
+			init = poxxxx_initVGA;
+		usb_exchange(gspca_dev, init);
+		reg_r(gspca_dev, 0x8c, 0x0000, 3);
+		reg_w(gspca_dev, 0xa0,
+				gspca_dev->usb_buf[2] & 1 ? 0 : 1,
+				0xb35c);
+		msleep(300);
+/*fixme: i2c read 04 and 05*/
+		init = poxxxx_init_end_1;
+		break;
+	}
+	usb_exchange(gspca_dev, init);
+	if (GammaT && MatrixT) {
+		put_tab_to_reg(gspca_dev, GammaT, 17, 0xb84a);
+		put_tab_to_reg(gspca_dev, GammaT, 17, 0xb85b);
+		put_tab_to_reg(gspca_dev, GammaT, 17, 0xb86c);
+		put_tab_to_reg(gspca_dev, MatrixT, 9, 0xb82c);
+
+		switch (sd->sensor) {
+		case SENSOR_PO1200:
+		case SENSOR_HV7131R:
+			reg_w(gspca_dev, 0x89, 0x0400, 0x1415);
+			break;
+		case SENSOR_MI1310_SOC:
+			reg_w(gspca_dev, 0x89, 0x058c, 0x0000);
+			break;
+		}
+		msleep(100);
+	}
+	switch (sd->sensor) {
+	case SENSOR_OV7670:
+		reg_w(gspca_dev, 0x87, 0xffff, 0xffff);
+		reg_w(gspca_dev, 0x88, 0xff00, 0xf0f1);
+		reg_w(gspca_dev, 0xa0, 0x0000, 0xbfff);
+		break;
+	case SENSOR_POxxxx:
+		usb_exchange(gspca_dev, poxxxx_init_end_2);
+		setwb(gspca_dev);
+		msleep(80);		/* led on */
+		reg_w(gspca_dev, 0x89, 0xffff, 0xfdff);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	switch (sd->sensor) {
+	case SENSOR_MI1310_SOC:
+		reg_w(gspca_dev, 0x89, 0x058c, 0x00ff);
+		break;
+	case SENSOR_POxxxx:
+		return;
+	default:
+		if (!(sd->flags & FL_SAMSUNG))
+			reg_w(gspca_dev, 0x89, 0xffff, 0xffff);
+		break;
+	}
+	reg_w(gspca_dev, 0xa0, 0x01, 0xb301);
+	reg_w(gspca_dev, 0xa0, 0x09, 0xb003);
+}
+
+/* called on streamoff with alt 0 and on disconnect */
+static void sd_stop0(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (!gspca_dev->present)
+		return;
+/*fixme: is this useful?*/
+	if (sd->sensor == SENSOR_MI1310_SOC)
+		reg_w(gspca_dev, 0x89, 0x058c, 0x00ff);
+	else if (!(sd->flags & FL_SAMSUNG))
+		reg_w(gspca_dev, 0x89, 0xffff, 0xffff);
+
+	if (sd->sensor == SENSOR_POxxxx) {
+		reg_w(gspca_dev, 0xa0, 0x26, 0xb300);
+		reg_w(gspca_dev, 0xa0, 0x04, 0xb300);
+		reg_w(gspca_dev, 0xa0, 0x00, 0xb300);
+	}
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso pkt length */
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (data[0] == 0xff && data[1] == 0xd8) {
+		gspca_dbg(gspca_dev, D_PACK,
+			  "vc032x header packet found len %d\n", len);
+		gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
+		data += sd->image_offset;
+		len -= sd->image_offset;
+		gspca_frame_add(gspca_dev, FIRST_PACKET, data, len);
+		return;
+	}
+
+	/* The vc0321 sends some additional data after sending the complete
+	 * frame, we ignore this. */
+	if (sd->bridge == BRIDGE_VC0321) {
+		int size, l;
+
+		l = gspca_dev->image_len;
+		size = gspca_dev->pixfmt.sizeimage;
+		if (len > size - l)
+			len = size - l;
+	}
+	gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+}
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *)gspca_dev;
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming && ctrl->id != V4L2_CID_POWER_LINE_FREQUENCY)
+		return 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		setbrightness(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_CONTRAST:
+		setcontrast(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_SATURATION:
+		setcolors(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_HFLIP:
+		sethvflip(gspca_dev, sd->hflip->val, sd->vflip->val);
+		break;
+	case V4L2_CID_SHARPNESS:
+		setsharpness(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_AUTOGAIN:
+		setautogain(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_GAIN:
+		setgain(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_EXPOSURE:
+		setexposure(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_BACKLIGHT_COMPENSATION:
+		setbacklight(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_POWER_LINE_FREQUENCY:
+		setlightfreq(gspca_dev, ctrl->val);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *)gspca_dev;
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+	bool has_brightness = false;
+	bool has_contrast = false;
+	bool has_sat = false;
+	bool has_hvflip = false;
+	bool has_freq = false;
+	bool has_backlight = false;
+	bool has_exposure = false;
+	bool has_autogain = false;
+	bool has_gain = false;
+	bool has_sharpness = false;
+
+	switch (sd->sensor) {
+	case SENSOR_HV7131R:
+	case SENSOR_MI0360:
+	case SENSOR_PO3130NC:
+		break;
+	case SENSOR_MI1310_SOC:
+	case SENSOR_MI1320:
+	case SENSOR_MI1320_SOC:
+	case SENSOR_OV7660:
+		has_hvflip = true;
+		break;
+	case SENSOR_OV7670:
+		has_hvflip = has_freq = true;
+		break;
+	case SENSOR_PO1200:
+		has_hvflip = has_sharpness = true;
+		break;
+	case SENSOR_POxxxx:
+		has_brightness = has_contrast = has_sat = has_backlight =
+			has_exposure = has_autogain = has_gain =
+			has_sharpness = true;
+		break;
+	}
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 8);
+	if (has_brightness)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0, 255, 1, 128);
+	if (has_contrast)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_CONTRAST, 0, 255, 1, 127);
+	if (has_sat)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_SATURATION, 1, 127, 1, 63);
+	if (has_hvflip) {
+		sd->hflip = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_HFLIP, 0, 1, 1, 0);
+		sd->vflip = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_VFLIP, 0, 1, 1, 0);
+	}
+	if (has_sharpness)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_SHARPNESS, -1, 2, 1, -1);
+	if (has_freq)
+		v4l2_ctrl_new_std_menu(hdl, &sd_ctrl_ops,
+			V4L2_CID_POWER_LINE_FREQUENCY,
+			V4L2_CID_POWER_LINE_FREQUENCY_60HZ, 0,
+			V4L2_CID_POWER_LINE_FREQUENCY_50HZ);
+	if (has_autogain)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
+	if (has_gain)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_GAIN, 0, 78, 1, 0);
+	if (has_exposure)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_EXPOSURE, 0, 4095, 1, 450);
+	if (has_backlight)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BACKLIGHT_COMPENSATION, 0, 15, 1, 15);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	if (sd->hflip)
+		v4l2_ctrl_cluster(2, &sd->hflip);
+	return 0;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.init_controls = sd_init_controls,
+	.config = sd_config,
+	.init = sd_init,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.stop0 = sd_stop0,
+	.pkt_scan = sd_pkt_scan,
+};
+
+/* -- module initialisation -- */
+#define BF(bridge, flags) \
+	.driver_info = (BRIDGE_ ## bridge << 8) \
+		| (flags)
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x041e, 0x405b), BF(VC0323, FL_VFLIP)},
+	{USB_DEVICE(0x046d, 0x0892), BF(VC0321, 0)},
+	{USB_DEVICE(0x046d, 0x0896), BF(VC0321, 0)},
+	{USB_DEVICE(0x046d, 0x0897), BF(VC0321, 0)},
+	{USB_DEVICE(0x0ac8, 0x0321), BF(VC0321, 0)},
+	{USB_DEVICE(0x0ac8, 0x0323), BF(VC0323, 0)},
+	{USB_DEVICE(0x0ac8, 0x0328), BF(VC0321, 0)},
+	{USB_DEVICE(0x0ac8, 0xc001), BF(VC0321, 0)},
+	{USB_DEVICE(0x0ac8, 0xc002), BF(VC0321, 0)},
+	{USB_DEVICE(0x0ac8, 0xc301), BF(VC0323, FL_SAMSUNG)},
+	{USB_DEVICE(0x15b8, 0x6001), BF(VC0323, 0)},
+	{USB_DEVICE(0x15b8, 0x6002), BF(VC0323, 0)},
+	{USB_DEVICE(0x17ef, 0x4802), BF(VC0323, 0)},
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/vicam.c b/drivers/media/usb/gspca/vicam.c
new file mode 100644
index 0000000..8562bda
--- /dev/null
+++ b/drivers/media/usb/gspca/vicam.c
@@ -0,0 +1,356 @@
+/*
+ * gspca ViCam subdriver
+ *
+ * Copyright (C) 2011 Hans de Goede <hdegoede@redhat.com>
+ *
+ * Based on the usbvideo vicam driver, which is:
+ *
+ * Copyright (c) 2002 Joe Burks (jburks@wavicle.org),
+ *                    Chris Cheney (chris.cheney@gmail.com),
+ *                    Pavel Machek (pavel@ucw.cz),
+ *                    John Tyner (jtyner@cs.ucr.edu),
+ *                    Monroe Williams (monroe@pobox.com)
+ *
+ * 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
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "vicam"
+#define HEADER_SIZE 64
+
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+#include <linux/firmware.h>
+#include <linux/ihex.h>
+#include "gspca.h"
+
+#define VICAM_FIRMWARE "vicam/firmware.fw"
+
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_DESCRIPTION("GSPCA ViCam USB Camera Driver");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE(VICAM_FIRMWARE);
+
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+	struct work_struct work_struct;
+};
+
+/* The vicam sensor has a resolution of 512 x 244, with I believe square
+   pixels, but this is forced to a 4:3 ratio by optics. So it has
+   non square pixels :( */
+static struct v4l2_pix_format vicam_mode[] = {
+	{ 256, 122, V4L2_PIX_FMT_SGRBG8, V4L2_FIELD_NONE,
+		.bytesperline = 256,
+		.sizeimage = 256 * 122,
+		.colorspace = V4L2_COLORSPACE_SRGB,},
+	/* 2 modes with somewhat more square pixels */
+	{ 256, 200, V4L2_PIX_FMT_SGRBG8, V4L2_FIELD_NONE,
+		.bytesperline = 256,
+		.sizeimage = 256 * 200,
+		.colorspace = V4L2_COLORSPACE_SRGB,},
+	{ 256, 240, V4L2_PIX_FMT_SGRBG8, V4L2_FIELD_NONE,
+		.bytesperline = 256,
+		.sizeimage = 256 * 240,
+		.colorspace = V4L2_COLORSPACE_SRGB,},
+#if 0   /* This mode has extremely non square pixels, testing use only */
+	{ 512, 122, V4L2_PIX_FMT_SGRBG8, V4L2_FIELD_NONE,
+		.bytesperline = 512,
+		.sizeimage = 512 * 122,
+		.colorspace = V4L2_COLORSPACE_SRGB,},
+#endif
+	{ 512, 244, V4L2_PIX_FMT_SGRBG8, V4L2_FIELD_NONE,
+		.bytesperline = 512,
+		.sizeimage = 512 * 244,
+		.colorspace = V4L2_COLORSPACE_SRGB,},
+};
+
+static int vicam_control_msg(struct gspca_dev *gspca_dev, u8 request,
+	u16 value, u16 index, u8 *data, u16 len)
+{
+	int ret;
+
+	ret = usb_control_msg(gspca_dev->dev,
+			      usb_sndctrlpipe(gspca_dev->dev, 0),
+			      request,
+			      USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			      value, index, data, len, 1000);
+	if (ret < 0)
+		pr_err("control msg req %02X error %d\n", request, ret);
+
+	return ret;
+}
+
+static int vicam_set_camera_power(struct gspca_dev *gspca_dev, int state)
+{
+	int ret;
+
+	ret = vicam_control_msg(gspca_dev, 0x50, state, 0, NULL, 0);
+	if (ret < 0)
+		return ret;
+
+	if (state)
+		ret = vicam_control_msg(gspca_dev, 0x55, 1, 0, NULL, 0);
+
+	return ret;
+}
+
+/*
+ *  request and read a block of data
+ */
+static int vicam_read_frame(struct gspca_dev *gspca_dev, u8 *data, int size)
+{
+	int ret, unscaled_height, act_len = 0;
+	u8 *req_data = gspca_dev->usb_buf;
+	s32 expo = v4l2_ctrl_g_ctrl(gspca_dev->exposure);
+	s32 gain = v4l2_ctrl_g_ctrl(gspca_dev->gain);
+
+	memset(req_data, 0, 16);
+	req_data[0] = gain;
+	if (gspca_dev->pixfmt.width == 256)
+		req_data[1] |= 0x01; /* low nibble x-scale */
+	if (gspca_dev->pixfmt.height <= 122) {
+		req_data[1] |= 0x10; /* high nibble y-scale */
+		unscaled_height = gspca_dev->pixfmt.height * 2;
+	} else
+		unscaled_height = gspca_dev->pixfmt.height;
+	req_data[2] = 0x90; /* unknown, does not seem to do anything */
+	if (unscaled_height <= 200)
+		req_data[3] = 0x06; /* vend? */
+	else if (unscaled_height <= 242) /* Yes 242 not 240 */
+		req_data[3] = 0x07; /* vend? */
+	else /* Up to 244 lines with req_data[3] == 0x08 */
+		req_data[3] = 0x08; /* vend? */
+
+	if (expo < 256) {
+		/* Frame rate maxed out, use partial frame expo time */
+		req_data[4] = 255 - expo;
+		req_data[5] = 0x00;
+		req_data[6] = 0x00;
+		req_data[7] = 0x01;
+	} else {
+		/* Modify frame rate */
+		req_data[4] = 0x00;
+		req_data[5] = 0x00;
+		req_data[6] = expo & 0xFF;
+		req_data[7] = expo >> 8;
+	}
+	req_data[8] = ((244 - unscaled_height) / 2) & ~0x01; /* vstart */
+	/* bytes 9-15 do not seem to affect exposure or image quality */
+
+	mutex_lock(&gspca_dev->usb_lock);
+	ret = vicam_control_msg(gspca_dev, 0x51, 0x80, 0, req_data, 16);
+	mutex_unlock(&gspca_dev->usb_lock);
+	if (ret < 0)
+		return ret;
+
+	ret = usb_bulk_msg(gspca_dev->dev,
+			   usb_rcvbulkpipe(gspca_dev->dev, 0x81),
+			   data, size, &act_len, 10000);
+	/* successful, it returns 0, otherwise  negative */
+	if (ret < 0 || act_len != size) {
+		pr_err("bulk read fail (%d) len %d/%d\n",
+		       ret, act_len, size);
+		return -EIO;
+	}
+	return 0;
+}
+
+/*
+ * This function is called as a workqueue function and runs whenever the camera
+ * is streaming data. Because it is a workqueue function it is allowed to sleep
+ * so we can use synchronous USB calls. To avoid possible collisions with other
+ * threads attempting to use gspca_dev->usb_buf we take the usb_lock when
+ * performing USB operations using it. In practice we don't really need this
+ * as the cameras controls are only written from the workqueue.
+ */
+static void vicam_dostream(struct work_struct *work)
+{
+	struct sd *sd = container_of(work, struct sd, work_struct);
+	struct gspca_dev *gspca_dev = &sd->gspca_dev;
+	int ret, frame_sz;
+	u8 *buffer;
+
+	frame_sz = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].sizeimage +
+		   HEADER_SIZE;
+	buffer = kmalloc(frame_sz, GFP_KERNEL);
+	if (!buffer) {
+		pr_err("Couldn't allocate USB buffer\n");
+		goto exit;
+	}
+
+	while (gspca_dev->present && gspca_dev->streaming) {
+#ifdef CONFIG_PM
+		if (gspca_dev->frozen)
+			break;
+#endif
+		ret = vicam_read_frame(gspca_dev, buffer, frame_sz);
+		if (ret < 0)
+			break;
+
+		/* Note the frame header contents seem to be completely
+		   constant, they do not change with either image, or
+		   settings. So we simply discard it. The frames have
+		   a very similar 64 byte footer, which we don't even
+		   bother reading from the cam */
+		gspca_frame_add(gspca_dev, FIRST_PACKET,
+				buffer + HEADER_SIZE,
+				frame_sz - HEADER_SIZE);
+		gspca_frame_add(gspca_dev, LAST_PACKET, NULL, 0);
+	}
+exit:
+	kfree(buffer);
+}
+
+/* This function is called at probe time just before sd_init */
+static int sd_config(struct gspca_dev *gspca_dev,
+		const struct usb_device_id *id)
+{
+	struct cam *cam = &gspca_dev->cam;
+	struct sd *sd = (struct sd *)gspca_dev;
+
+	/* We don't use the buffer gspca allocates so make it small. */
+	cam->bulk = 1;
+	cam->bulk_size = 64;
+	cam->cam_mode = vicam_mode;
+	cam->nmodes = ARRAY_SIZE(vicam_mode);
+
+	INIT_WORK(&sd->work_struct, vicam_dostream);
+
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	int ret;
+	const struct ihex_binrec *rec;
+	const struct firmware *uninitialized_var(fw);
+	u8 *firmware_buf;
+
+	ret = request_ihex_firmware(&fw, VICAM_FIRMWARE,
+				    &gspca_dev->dev->dev);
+	if (ret) {
+		pr_err("Failed to load \"vicam/firmware.fw\": %d\n", ret);
+		return ret;
+	}
+
+	firmware_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+	if (!firmware_buf) {
+		ret = -ENOMEM;
+		goto exit;
+	}
+	for (rec = (void *)fw->data; rec; rec = ihex_next_binrec(rec)) {
+		memcpy(firmware_buf, rec->data, be16_to_cpu(rec->len));
+		ret = vicam_control_msg(gspca_dev, 0xff, 0, 0, firmware_buf,
+					be16_to_cpu(rec->len));
+		if (ret < 0)
+			break;
+	}
+
+	kfree(firmware_buf);
+exit:
+	release_firmware(fw);
+	return ret;
+}
+
+/* Set up for getting frames. */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *)gspca_dev;
+	int ret;
+
+	ret = vicam_set_camera_power(gspca_dev, 1);
+	if (ret < 0)
+		return ret;
+
+	schedule_work(&sd->work_struct);
+
+	return 0;
+}
+
+/* called on streamoff with alt==0 and on disconnect */
+/* the usb_lock is held at entry - restore on exit */
+static void sd_stop0(struct gspca_dev *gspca_dev)
+{
+	struct sd *dev = (struct sd *)gspca_dev;
+
+	/* wait for the work queue to terminate */
+	mutex_unlock(&gspca_dev->usb_lock);
+	/* This waits for vicam_dostream to finish */
+	flush_work(&dev->work_struct);
+	mutex_lock(&gspca_dev->usb_lock);
+
+	if (gspca_dev->present)
+		vicam_set_camera_power(gspca_dev, 0);
+}
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 2);
+	gspca_dev->exposure = v4l2_ctrl_new_std(hdl, NULL,
+			V4L2_CID_EXPOSURE, 0, 2047, 1, 256);
+	gspca_dev->gain = v4l2_ctrl_new_std(hdl, NULL,
+			V4L2_CID_GAIN, 0, 255, 1, 200);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	return 0;
+}
+
+/* Table of supported USB devices */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x04c1, 0x009d)},
+	{USB_DEVICE(0x0602, 0x1001)},
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name   = MODULE_NAME,
+	.config = sd_config,
+	.init   = sd_init,
+	.init_controls = sd_init_controls,
+	.start  = sd_start,
+	.stop0  = sd_stop0,
+};
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id,
+			&sd_desc,
+			sizeof(struct sd),
+			THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name       = MODULE_NAME,
+	.id_table   = device_table,
+	.probe      = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume  = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/w996Xcf.c b/drivers/media/usb/gspca/w996Xcf.c
new file mode 100644
index 0000000..abfab3d
--- /dev/null
+++ b/drivers/media/usb/gspca/w996Xcf.c
@@ -0,0 +1,575 @@
+/**
+ *
+ * GSPCA sub driver for W996[78]CF JPEG USB Dual Mode Camera Chip.
+ *
+ * Copyright (C) 2009 Hans de Goede <hdegoede@redhat.com>
+ *
+ * This module is adapted from the in kernel v4l1 w9968cf driver:
+ *
+ * Copyright (C) 2002-2004 by Luca Risolia <luca.risolia@studio.unibo.it>
+ *
+ * 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
+ * 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.
+ *
+ */
+
+/* Note this is not a stand alone driver, it gets included in ov519.c, this
+   is a bit of a hack, but it needs the driver code for a lot of different
+   ov sensors which is already present in ov519.c (the old v4l1 driver used
+   the ovchipcam framework). When we have the time we really should move
+   the sensor drivers to v4l2 sub drivers, and properly split of this
+   driver from ov519.c */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define W9968CF_I2C_BUS_DELAY    4 /* delay in us for I2C bit r/w operations */
+
+#define Y_QUANTABLE (&sd->jpeg_hdr[JPEG_QT0_OFFSET])
+#define UV_QUANTABLE (&sd->jpeg_hdr[JPEG_QT1_OFFSET])
+
+static const struct v4l2_pix_format w9968cf_vga_mode[] = {
+	{160, 120, V4L2_PIX_FMT_UYVY, V4L2_FIELD_NONE,
+		.bytesperline = 160 * 2,
+		.sizeimage = 160 * 120 * 2,
+		.colorspace = V4L2_COLORSPACE_JPEG},
+	{176, 144, V4L2_PIX_FMT_UYVY, V4L2_FIELD_NONE,
+		.bytesperline = 176 * 2,
+		.sizeimage = 176 * 144 * 2,
+		.colorspace = V4L2_COLORSPACE_JPEG},
+	{320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 320 * 2,
+		.sizeimage = 320 * 240 * 2,
+		.colorspace = V4L2_COLORSPACE_JPEG},
+	{352, 288, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 352 * 2,
+		.sizeimage = 352 * 288 * 2,
+		.colorspace = V4L2_COLORSPACE_JPEG},
+	{640, 480, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 640 * 2,
+		.sizeimage = 640 * 480 * 2,
+		.colorspace = V4L2_COLORSPACE_JPEG},
+};
+
+static void reg_w(struct sd *sd, u16 index, u16 value);
+
+/*--------------------------------------------------------------------------
+  Write 64-bit data to the fast serial bus registers.
+  Return 0 on success, -1 otherwise.
+  --------------------------------------------------------------------------*/
+static void w9968cf_write_fsb(struct sd *sd, u16* data)
+{
+	struct usb_device *udev = sd->gspca_dev.dev;
+	u16 value;
+	int ret;
+
+	if (sd->gspca_dev.usb_err < 0)
+		return;
+
+	value = *data++;
+	memcpy(sd->gspca_dev.usb_buf, data, 6);
+
+	/* Avoid things going to fast for the bridge with a xhci host */
+	udelay(150);
+	ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0,
+			      USB_TYPE_VENDOR | USB_DIR_OUT | USB_RECIP_DEVICE,
+			      value, 0x06, sd->gspca_dev.usb_buf, 6, 500);
+	if (ret < 0) {
+		pr_err("Write FSB registers failed (%d)\n", ret);
+		sd->gspca_dev.usb_err = ret;
+	}
+}
+
+/*--------------------------------------------------------------------------
+  Write data to the serial bus control register.
+  Return 0 on success, a negative number otherwise.
+  --------------------------------------------------------------------------*/
+static void w9968cf_write_sb(struct sd *sd, u16 value)
+{
+	int ret;
+
+	if (sd->gspca_dev.usb_err < 0)
+		return;
+
+	/* Avoid things going to fast for the bridge with a xhci host */
+	udelay(150);
+
+	/* We don't use reg_w here, as that would cause all writes when
+	   bitbanging i2c to be logged, making the logs impossible to read */
+	ret = usb_control_msg(sd->gspca_dev.dev,
+		usb_sndctrlpipe(sd->gspca_dev.dev, 0),
+		0,
+		USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+		value, 0x01, NULL, 0, 500);
+
+	udelay(W9968CF_I2C_BUS_DELAY);
+
+	if (ret < 0) {
+		pr_err("Write SB reg [01] %04x failed\n", value);
+		sd->gspca_dev.usb_err = ret;
+	}
+}
+
+/*--------------------------------------------------------------------------
+  Read data from the serial bus control register.
+  Return 0 on success, a negative number otherwise.
+  --------------------------------------------------------------------------*/
+static int w9968cf_read_sb(struct sd *sd)
+{
+	int ret;
+
+	if (sd->gspca_dev.usb_err < 0)
+		return -1;
+
+	/* Avoid things going to fast for the bridge with a xhci host */
+	udelay(150);
+
+	/* We don't use reg_r here, as the w9968cf is special and has 16
+	   bit registers instead of 8 bit */
+	ret = usb_control_msg(sd->gspca_dev.dev,
+			usb_rcvctrlpipe(sd->gspca_dev.dev, 0),
+			1,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0, 0x01, sd->gspca_dev.usb_buf, 2, 500);
+	if (ret >= 0) {
+		ret = sd->gspca_dev.usb_buf[0] |
+		      (sd->gspca_dev.usb_buf[1] << 8);
+	} else {
+		pr_err("Read SB reg [01] failed\n");
+		sd->gspca_dev.usb_err = ret;
+	}
+
+	udelay(W9968CF_I2C_BUS_DELAY);
+
+	return ret;
+}
+
+/*--------------------------------------------------------------------------
+  Upload quantization tables for the JPEG compression.
+  This function is called by w9968cf_start_transfer().
+  Return 0 on success, a negative number otherwise.
+  --------------------------------------------------------------------------*/
+static void w9968cf_upload_quantizationtables(struct sd *sd)
+{
+	u16 a, b;
+	int i, j;
+
+	reg_w(sd, 0x39, 0x0010); /* JPEG clock enable */
+
+	for (i = 0, j = 0; i < 32; i++, j += 2) {
+		a = Y_QUANTABLE[j] | ((unsigned)(Y_QUANTABLE[j + 1]) << 8);
+		b = UV_QUANTABLE[j] | ((unsigned)(UV_QUANTABLE[j + 1]) << 8);
+		reg_w(sd, 0x40 + i, a);
+		reg_w(sd, 0x60 + i, b);
+	}
+	reg_w(sd, 0x39, 0x0012); /* JPEG encoder enable */
+}
+
+/****************************************************************************
+ * Low-level I2C I/O functions.                                             *
+ * The adapter supports the following I2C transfer functions:               *
+ * i2c_adap_fastwrite_byte_data() (at 400 kHz bit frequency only)           *
+ * i2c_adap_read_byte_data()                                                *
+ * i2c_adap_read_byte()                                                     *
+ ****************************************************************************/
+
+static void w9968cf_smbus_start(struct sd *sd)
+{
+	w9968cf_write_sb(sd, 0x0011); /* SDE=1, SDA=0, SCL=1 */
+	w9968cf_write_sb(sd, 0x0010); /* SDE=1, SDA=0, SCL=0 */
+}
+
+static void w9968cf_smbus_stop(struct sd *sd)
+{
+	w9968cf_write_sb(sd, 0x0010); /* SDE=1, SDA=0, SCL=0 */
+	w9968cf_write_sb(sd, 0x0011); /* SDE=1, SDA=0, SCL=1 */
+	w9968cf_write_sb(sd, 0x0013); /* SDE=1, SDA=1, SCL=1 */
+}
+
+static void w9968cf_smbus_write_byte(struct sd *sd, u8 v)
+{
+	u8 bit;
+	int sda;
+
+	for (bit = 0 ; bit < 8 ; bit++) {
+		sda = (v & 0x80) ? 2 : 0;
+		v <<= 1;
+		/* SDE=1, SDA=sda, SCL=0 */
+		w9968cf_write_sb(sd, 0x10 | sda);
+		/* SDE=1, SDA=sda, SCL=1 */
+		w9968cf_write_sb(sd, 0x11 | sda);
+		/* SDE=1, SDA=sda, SCL=0 */
+		w9968cf_write_sb(sd, 0x10 | sda);
+	}
+}
+
+static void w9968cf_smbus_read_byte(struct sd *sd, u8 *v)
+{
+	u8 bit;
+
+	/* No need to ensure SDA is high as we are always called after
+	   read_ack which ends with SDA high */
+	*v = 0;
+	for (bit = 0 ; bit < 8 ; bit++) {
+		*v <<= 1;
+		/* SDE=1, SDA=1, SCL=1 */
+		w9968cf_write_sb(sd, 0x0013);
+		*v |= (w9968cf_read_sb(sd) & 0x0008) ? 1 : 0;
+		/* SDE=1, SDA=1, SCL=0 */
+		w9968cf_write_sb(sd, 0x0012);
+	}
+}
+
+static void w9968cf_smbus_write_nack(struct sd *sd)
+{
+	/* No need to ensure SDA is high as we are always called after
+	   read_byte which ends with SDA high */
+	w9968cf_write_sb(sd, 0x0013); /* SDE=1, SDA=1, SCL=1 */
+	w9968cf_write_sb(sd, 0x0012); /* SDE=1, SDA=1, SCL=0 */
+}
+
+static void w9968cf_smbus_read_ack(struct sd *sd)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	int sda;
+
+	/* Ensure SDA is high before raising clock to avoid a spurious stop */
+	w9968cf_write_sb(sd, 0x0012); /* SDE=1, SDA=1, SCL=0 */
+	w9968cf_write_sb(sd, 0x0013); /* SDE=1, SDA=1, SCL=1 */
+	sda = w9968cf_read_sb(sd);
+	w9968cf_write_sb(sd, 0x0012); /* SDE=1, SDA=1, SCL=0 */
+	if (sda >= 0 && (sda & 0x08)) {
+		gspca_dbg(gspca_dev, D_USBI, "Did not receive i2c ACK\n");
+		sd->gspca_dev.usb_err = -EIO;
+	}
+}
+
+/* SMBus protocol: S Addr Wr [A] Subaddr [A] Value [A] P */
+static void w9968cf_i2c_w(struct sd *sd, u8 reg, u8 value)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	u16* data = (u16 *)sd->gspca_dev.usb_buf;
+
+	data[0] = 0x082f | ((sd->sensor_addr & 0x80) ? 0x1500 : 0x0);
+	data[0] |= (sd->sensor_addr & 0x40) ? 0x4000 : 0x0;
+	data[1] = 0x2082 | ((sd->sensor_addr & 0x40) ? 0x0005 : 0x0);
+	data[1] |= (sd->sensor_addr & 0x20) ? 0x0150 : 0x0;
+	data[1] |= (sd->sensor_addr & 0x10) ? 0x5400 : 0x0;
+	data[2] = 0x8208 | ((sd->sensor_addr & 0x08) ? 0x0015 : 0x0);
+	data[2] |= (sd->sensor_addr & 0x04) ? 0x0540 : 0x0;
+	data[2] |= (sd->sensor_addr & 0x02) ? 0x5000 : 0x0;
+	data[3] = 0x1d20 | ((sd->sensor_addr & 0x02) ? 0x0001 : 0x0);
+	data[3] |= (sd->sensor_addr & 0x01) ? 0x0054 : 0x0;
+
+	w9968cf_write_fsb(sd, data);
+
+	data[0] = 0x8208 | ((reg & 0x80) ? 0x0015 : 0x0);
+	data[0] |= (reg & 0x40) ? 0x0540 : 0x0;
+	data[0] |= (reg & 0x20) ? 0x5000 : 0x0;
+	data[1] = 0x0820 | ((reg & 0x20) ? 0x0001 : 0x0);
+	data[1] |= (reg & 0x10) ? 0x0054 : 0x0;
+	data[1] |= (reg & 0x08) ? 0x1500 : 0x0;
+	data[1] |= (reg & 0x04) ? 0x4000 : 0x0;
+	data[2] = 0x2082 | ((reg & 0x04) ? 0x0005 : 0x0);
+	data[2] |= (reg & 0x02) ? 0x0150 : 0x0;
+	data[2] |= (reg & 0x01) ? 0x5400 : 0x0;
+	data[3] = 0x001d;
+
+	w9968cf_write_fsb(sd, data);
+
+	data[0] = 0x8208 | ((value & 0x80) ? 0x0015 : 0x0);
+	data[0] |= (value & 0x40) ? 0x0540 : 0x0;
+	data[0] |= (value & 0x20) ? 0x5000 : 0x0;
+	data[1] = 0x0820 | ((value & 0x20) ? 0x0001 : 0x0);
+	data[1] |= (value & 0x10) ? 0x0054 : 0x0;
+	data[1] |= (value & 0x08) ? 0x1500 : 0x0;
+	data[1] |= (value & 0x04) ? 0x4000 : 0x0;
+	data[2] = 0x2082 | ((value & 0x04) ? 0x0005 : 0x0);
+	data[2] |= (value & 0x02) ? 0x0150 : 0x0;
+	data[2] |= (value & 0x01) ? 0x5400 : 0x0;
+	data[3] = 0xfe1d;
+
+	w9968cf_write_fsb(sd, data);
+
+	gspca_dbg(gspca_dev, D_USBO, "i2c 0x%02x -> [0x%02x]\n", value, reg);
+}
+
+/* SMBus protocol: S Addr Wr [A] Subaddr [A] P S Addr+1 Rd [A] [Value] NA P */
+static int w9968cf_i2c_r(struct sd *sd, u8 reg)
+{
+	struct gspca_dev *gspca_dev = (struct gspca_dev *)sd;
+	int ret = 0;
+	u8 value;
+
+	/* Fast serial bus data control disable */
+	w9968cf_write_sb(sd, 0x0013); /* don't change ! */
+
+	w9968cf_smbus_start(sd);
+	w9968cf_smbus_write_byte(sd, sd->sensor_addr);
+	w9968cf_smbus_read_ack(sd);
+	w9968cf_smbus_write_byte(sd, reg);
+	w9968cf_smbus_read_ack(sd);
+	w9968cf_smbus_stop(sd);
+	w9968cf_smbus_start(sd);
+	w9968cf_smbus_write_byte(sd, sd->sensor_addr + 1);
+	w9968cf_smbus_read_ack(sd);
+	w9968cf_smbus_read_byte(sd, &value);
+	/* signal we don't want to read anymore, the v4l1 driver used to
+	   send an ack here which is very wrong! (and then fixed
+	   the issues this gave by retrying reads) */
+	w9968cf_smbus_write_nack(sd);
+	w9968cf_smbus_stop(sd);
+
+	/* Fast serial bus data control re-enable */
+	w9968cf_write_sb(sd, 0x0030);
+
+	if (sd->gspca_dev.usb_err >= 0) {
+		ret = value;
+		gspca_dbg(gspca_dev, D_USBI, "i2c [0x%02X] -> 0x%02X\n",
+			  reg, value);
+	} else
+		gspca_err(gspca_dev, "i2c read [0x%02x] failed\n", reg);
+
+	return ret;
+}
+
+/*--------------------------------------------------------------------------
+  Turn on the LED on some webcams. A beep should be heard too.
+  Return 0 on success, a negative number otherwise.
+  --------------------------------------------------------------------------*/
+static void w9968cf_configure(struct sd *sd)
+{
+	reg_w(sd, 0x00, 0xff00); /* power-down */
+	reg_w(sd, 0x00, 0xbf17); /* reset everything */
+	reg_w(sd, 0x00, 0xbf10); /* normal operation */
+	reg_w(sd, 0x01, 0x0010); /* serial bus, SDS high */
+	reg_w(sd, 0x01, 0x0000); /* serial bus, SDS low */
+	reg_w(sd, 0x01, 0x0010); /* ..high 'beep-beep' */
+	reg_w(sd, 0x01, 0x0030); /* Set sda scl to FSB mode */
+
+	sd->stopped = 1;
+}
+
+static void w9968cf_init(struct sd *sd)
+{
+	unsigned long hw_bufsize = sd->sif ? (352 * 288 * 2) : (640 * 480 * 2),
+		      y0 = 0x0000,
+		      u0 = y0 + hw_bufsize / 2,
+		      v0 = u0 + hw_bufsize / 4,
+		      y1 = v0 + hw_bufsize / 4,
+		      u1 = y1 + hw_bufsize / 2,
+		      v1 = u1 + hw_bufsize / 4;
+
+	reg_w(sd, 0x00, 0xff00); /* power off */
+	reg_w(sd, 0x00, 0xbf10); /* power on */
+
+	reg_w(sd, 0x03, 0x405d); /* DRAM timings */
+	reg_w(sd, 0x04, 0x0030); /* SDRAM timings */
+
+	reg_w(sd, 0x20, y0 & 0xffff); /* Y buf.0, low */
+	reg_w(sd, 0x21, y0 >> 16);    /* Y buf.0, high */
+	reg_w(sd, 0x24, u0 & 0xffff); /* U buf.0, low */
+	reg_w(sd, 0x25, u0 >> 16);    /* U buf.0, high */
+	reg_w(sd, 0x28, v0 & 0xffff); /* V buf.0, low */
+	reg_w(sd, 0x29, v0 >> 16);    /* V buf.0, high */
+
+	reg_w(sd, 0x22, y1 & 0xffff); /* Y buf.1, low */
+	reg_w(sd, 0x23, y1 >> 16);    /* Y buf.1, high */
+	reg_w(sd, 0x26, u1 & 0xffff); /* U buf.1, low */
+	reg_w(sd, 0x27, u1 >> 16);    /* U buf.1, high */
+	reg_w(sd, 0x2a, v1 & 0xffff); /* V buf.1, low */
+	reg_w(sd, 0x2b, v1 >> 16);    /* V buf.1, high */
+
+	reg_w(sd, 0x32, y1 & 0xffff); /* JPEG buf 0 low */
+	reg_w(sd, 0x33, y1 >> 16);    /* JPEG buf 0 high */
+
+	reg_w(sd, 0x34, y1 & 0xffff); /* JPEG buf 1 low */
+	reg_w(sd, 0x35, y1 >> 16);    /* JPEG bug 1 high */
+
+	reg_w(sd, 0x36, 0x0000);/* JPEG restart interval */
+	reg_w(sd, 0x37, 0x0804);/*JPEG VLE FIFO threshold*/
+	reg_w(sd, 0x38, 0x0000);/* disable hw up-scaling */
+	reg_w(sd, 0x3f, 0x0000); /* JPEG/MCTL test data */
+}
+
+static void w9968cf_set_crop_window(struct sd *sd)
+{
+	int start_cropx, start_cropy,  x, y, fw, fh, cw, ch,
+	    max_width, max_height;
+
+	if (sd->sif) {
+		max_width  = 352;
+		max_height = 288;
+	} else {
+		max_width  = 640;
+		max_height = 480;
+	}
+
+	if (sd->sensor == SEN_OV7620) {
+		/*
+		 * Sigh, this is dependend on the clock / framerate changes
+		 * made by the frequency control, sick.
+		 *
+		 * Note we cannot use v4l2_ctrl_g_ctrl here, as we get called
+		 * from ov519.c:setfreq() with the ctrl lock held!
+		 */
+		if (sd->freq->val == 1) {
+			start_cropx = 277;
+			start_cropy = 37;
+		} else {
+			start_cropx = 105;
+			start_cropy = 37;
+		}
+	} else {
+		start_cropx = 320;
+		start_cropy = 35;
+	}
+
+	/* Work around to avoid FP arithmetics */
+	#define SC(x) ((x) << 10)
+
+	/* Scaling factors */
+	fw = SC(sd->gspca_dev.pixfmt.width) / max_width;
+	fh = SC(sd->gspca_dev.pixfmt.height) / max_height;
+
+	cw = (fw >= fh) ? max_width : SC(sd->gspca_dev.pixfmt.width) / fh;
+	ch = (fw >= fh) ? SC(sd->gspca_dev.pixfmt.height) / fw : max_height;
+
+	sd->sensor_width = max_width;
+	sd->sensor_height = max_height;
+
+	x = (max_width - cw) / 2;
+	y = (max_height - ch) / 2;
+
+	reg_w(sd, 0x10, start_cropx + x);
+	reg_w(sd, 0x11, start_cropy + y);
+	reg_w(sd, 0x12, start_cropx + x + cw);
+	reg_w(sd, 0x13, start_cropy + y + ch);
+}
+
+static void w9968cf_mode_init_regs(struct sd *sd)
+{
+	int val, vs_polarity, hs_polarity;
+
+	w9968cf_set_crop_window(sd);
+
+	reg_w(sd, 0x14, sd->gspca_dev.pixfmt.width);
+	reg_w(sd, 0x15, sd->gspca_dev.pixfmt.height);
+
+	/* JPEG width & height */
+	reg_w(sd, 0x30, sd->gspca_dev.pixfmt.width);
+	reg_w(sd, 0x31, sd->gspca_dev.pixfmt.height);
+
+	/* Y & UV frame buffer strides (in WORD) */
+	if (w9968cf_vga_mode[sd->gspca_dev.curr_mode].pixelformat ==
+	    V4L2_PIX_FMT_JPEG) {
+		reg_w(sd, 0x2c, sd->gspca_dev.pixfmt.width / 2);
+		reg_w(sd, 0x2d, sd->gspca_dev.pixfmt.width / 4);
+	} else
+		reg_w(sd, 0x2c, sd->gspca_dev.pixfmt.width);
+
+	reg_w(sd, 0x00, 0xbf17); /* reset everything */
+	reg_w(sd, 0x00, 0xbf10); /* normal operation */
+
+	/* Transfer size in WORDS (for UYVY format only) */
+	val = sd->gspca_dev.pixfmt.width * sd->gspca_dev.pixfmt.height;
+	reg_w(sd, 0x3d, val & 0xffff); /* low bits */
+	reg_w(sd, 0x3e, val >> 16);    /* high bits */
+
+	if (w9968cf_vga_mode[sd->gspca_dev.curr_mode].pixelformat ==
+	    V4L2_PIX_FMT_JPEG) {
+		/* We may get called multiple times (usb isoc bw negotiat.) */
+		jpeg_define(sd->jpeg_hdr, sd->gspca_dev.pixfmt.height,
+			    sd->gspca_dev.pixfmt.width, 0x22); /* JPEG 420 */
+		jpeg_set_qual(sd->jpeg_hdr, v4l2_ctrl_g_ctrl(sd->jpegqual));
+		w9968cf_upload_quantizationtables(sd);
+		v4l2_ctrl_grab(sd->jpegqual, true);
+	}
+
+	/* Video Capture Control Register */
+	if (sd->sensor == SEN_OV7620) {
+		/* Seems to work around a bug in the image sensor */
+		vs_polarity = 1;
+		hs_polarity = 1;
+	} else {
+		vs_polarity = 1;
+		hs_polarity = 0;
+	}
+
+	val = (vs_polarity << 12) | (hs_polarity << 11);
+
+	/* NOTE: We may not have enough memory to do double buffering while
+	   doing compression (amount of memory differs per model cam).
+	   So we use the second image buffer also as jpeg stream buffer
+	   (see w9968cf_init), and disable double buffering. */
+	if (w9968cf_vga_mode[sd->gspca_dev.curr_mode].pixelformat ==
+	    V4L2_PIX_FMT_JPEG) {
+		/* val |= 0x0002; YUV422P */
+		val |= 0x0003; /* YUV420P */
+	} else
+		val |= 0x0080; /* Enable HW double buffering */
+
+	/* val |= 0x0020; enable clamping */
+	/* val |= 0x0008; enable (1-2-1) filter */
+	/* val |= 0x000c; enable (2-3-6-3-2) filter */
+
+	val |= 0x8000; /* capt. enable */
+
+	reg_w(sd, 0x16, val);
+
+	sd->gspca_dev.empty_packet = 0;
+}
+
+static void w9968cf_stop0(struct sd *sd)
+{
+	v4l2_ctrl_grab(sd->jpegqual, false);
+	reg_w(sd, 0x39, 0x0000); /* disable JPEG encoder */
+	reg_w(sd, 0x16, 0x0000); /* stop video capture */
+}
+
+/* The w9968cf docs say that a 0 sized packet means EOF (and also SOF
+   for the next frame). This seems to simply not be true when operating
+   in JPEG mode, in this case there may be empty packets within the
+   frame. So in JPEG mode use the JPEG SOI marker to detect SOF.
+
+   Note to make things even more interesting the w9968cf sends *PLANAR* jpeg,
+   to be precise it sends: SOI, SOF, DRI, SOS, Y-data, SOS, U-data, SOS,
+   V-data, EOI. */
+static void w9968cf_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,			/* isoc packet */
+			int len)			/* iso packet length */
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (w9968cf_vga_mode[gspca_dev->curr_mode].pixelformat ==
+	    V4L2_PIX_FMT_JPEG) {
+		if (len >= 2 &&
+		    data[0] == 0xff &&
+		    data[1] == 0xd8) {
+			gspca_frame_add(gspca_dev, LAST_PACKET,
+					NULL, 0);
+			gspca_frame_add(gspca_dev, FIRST_PACKET,
+					sd->jpeg_hdr, JPEG_HDR_SZ);
+			/* Strip the ff d8, our own header (which adds
+			   huffman and quantization tables) already has this */
+			len -= 2;
+			data += 2;
+		}
+	} else {
+		/* In UYVY mode an empty packet signals EOF */
+		if (gspca_dev->empty_packet) {
+			gspca_frame_add(gspca_dev, LAST_PACKET,
+						NULL, 0);
+			gspca_frame_add(gspca_dev, FIRST_PACKET,
+					NULL, 0);
+			gspca_dev->empty_packet = 0;
+		}
+	}
+	gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+}
diff --git a/drivers/media/usb/gspca/xirlink_cit.c b/drivers/media/usb/gspca/xirlink_cit.c
new file mode 100644
index 0000000..58deb0c
--- /dev/null
+++ b/drivers/media/usb/gspca/xirlink_cit.c
@@ -0,0 +1,3143 @@
+/*
+ * USB IBM C-It Video Camera driver
+ *
+ * Supports Xirlink C-It Video Camera, IBM PC Camera,
+ * IBM NetCamera and Veo Stingray.
+ *
+ * Copyright (C) 2010 Hans de Goede <hdegoede@redhat.com>
+ *
+ * This driver is based on earlier work of:
+ *
+ * (C) Copyright 1999 Johannes Erdfelt
+ * (C) Copyright 1999 Randy Dunlap
+ *
+ * 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.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#define MODULE_NAME "xirlink-cit"
+
+#include <linux/input.h>
+#include "gspca.h"
+
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_DESCRIPTION("Xirlink C-IT");
+MODULE_LICENSE("GPL");
+
+/* FIXME we should autodetect this */
+static int ibm_netcam_pro;
+module_param(ibm_netcam_pro, int, 0);
+MODULE_PARM_DESC(ibm_netcam_pro,
+		 "Use IBM Netcamera Pro init sequences for Model 3 cams");
+
+/* FIXME this should be handled through the V4L2 input selection API */
+static int rca_input;
+module_param(rca_input, int, 0644);
+MODULE_PARM_DESC(rca_input,
+		 "Use rca input instead of ccd sensor on Model 3 cams");
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;		/* !! must be the first item */
+	struct v4l2_ctrl *lighting;
+	u8 model;
+#define CIT_MODEL0 0 /* bcd version 0.01 cams ie the xvp-500 */
+#define CIT_MODEL1 1 /* The model 1 - 4 nomenclature comes from the old */
+#define CIT_MODEL2 2 /* ibmcam driver */
+#define CIT_MODEL3 3
+#define CIT_MODEL4 4
+#define CIT_IBM_NETCAM_PRO 5
+	u8 input_index;
+	u8 button_state;
+	u8 stop_on_control_change;
+	u8 sof_read;
+	u8 sof_len;
+};
+
+static void sd_stop0(struct gspca_dev *gspca_dev);
+
+static const struct v4l2_pix_format cif_yuv_mode[] = {
+	{176, 144, V4L2_PIX_FMT_CIT_YYVYUY, V4L2_FIELD_NONE,
+		.bytesperline = 176,
+		.sizeimage = 176 * 144 * 3 / 2 + 4,
+		.colorspace = V4L2_COLORSPACE_SRGB},
+	{352, 288, V4L2_PIX_FMT_CIT_YYVYUY, V4L2_FIELD_NONE,
+		.bytesperline = 352,
+		.sizeimage = 352 * 288 * 3 / 2 + 4,
+		.colorspace = V4L2_COLORSPACE_SRGB},
+};
+
+static const struct v4l2_pix_format vga_yuv_mode[] = {
+	{160, 120, V4L2_PIX_FMT_CIT_YYVYUY, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 160 * 120 * 3 / 2 + 4,
+		.colorspace = V4L2_COLORSPACE_SRGB},
+	{320, 240, V4L2_PIX_FMT_CIT_YYVYUY, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3 / 2 + 4,
+		.colorspace = V4L2_COLORSPACE_SRGB},
+	{640, 480, V4L2_PIX_FMT_CIT_YYVYUY, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 3 / 2 + 4,
+		.colorspace = V4L2_COLORSPACE_SRGB},
+};
+
+static const struct v4l2_pix_format model0_mode[] = {
+	{160, 120, V4L2_PIX_FMT_CIT_YYVYUY, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 160 * 120 * 3 / 2 + 4,
+		.colorspace = V4L2_COLORSPACE_SRGB},
+	{176, 144, V4L2_PIX_FMT_CIT_YYVYUY, V4L2_FIELD_NONE,
+		.bytesperline = 176,
+		.sizeimage = 176 * 144 * 3 / 2 + 4,
+		.colorspace = V4L2_COLORSPACE_SRGB},
+	{320, 240, V4L2_PIX_FMT_CIT_YYVYUY, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3 / 2 + 4,
+		.colorspace = V4L2_COLORSPACE_SRGB},
+};
+
+static const struct v4l2_pix_format model2_mode[] = {
+	{160, 120, V4L2_PIX_FMT_CIT_YYVYUY, V4L2_FIELD_NONE,
+		.bytesperline = 160,
+		.sizeimage = 160 * 120 * 3 / 2 + 4,
+		.colorspace = V4L2_COLORSPACE_SRGB},
+	{176, 144, V4L2_PIX_FMT_CIT_YYVYUY, V4L2_FIELD_NONE,
+		.bytesperline = 176,
+		.sizeimage = 176 * 144 * 3 / 2 + 4,
+		.colorspace = V4L2_COLORSPACE_SRGB},
+	{320, 240, V4L2_PIX_FMT_SGRBG8, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 + 4,
+		.colorspace = V4L2_COLORSPACE_SRGB},
+	{352, 288, V4L2_PIX_FMT_SGRBG8, V4L2_FIELD_NONE,
+		.bytesperline = 352,
+		.sizeimage = 352 * 288 + 4,
+		.colorspace = V4L2_COLORSPACE_SRGB},
+};
+
+/*
+ * 01.01.08 - Added for RCA video in support -LO
+ * This struct is used to init the Model3 cam to use the RCA video in port
+ * instead of the CCD sensor.
+ */
+static const u16 rca_initdata[][3] = {
+	{0, 0x0000, 0x010c},
+	{0, 0x0006, 0x012c},
+	{0, 0x0078, 0x012d},
+	{0, 0x0046, 0x012f},
+	{0, 0xd141, 0x0124},
+	{0, 0x0000, 0x0127},
+	{0, 0xfea8, 0x0124},
+	{1, 0x0000, 0x0116},
+	{0, 0x0064, 0x0116},
+	{1, 0x0000, 0x0115},
+	{0, 0x0003, 0x0115},
+	{0, 0x0008, 0x0123},
+	{0, 0x0000, 0x0117},
+	{0, 0x0000, 0x0112},
+	{0, 0x0080, 0x0100},
+	{0, 0x0000, 0x0100},
+	{1, 0x0000, 0x0116},
+	{0, 0x0060, 0x0116},
+	{0, 0x0002, 0x0112},
+	{0, 0x0000, 0x0123},
+	{0, 0x0001, 0x0117},
+	{0, 0x0040, 0x0108},
+	{0, 0x0019, 0x012c},
+	{0, 0x0040, 0x0116},
+	{0, 0x000a, 0x0115},
+	{0, 0x000b, 0x0115},
+	{0, 0x0078, 0x012d},
+	{0, 0x0046, 0x012f},
+	{0, 0xd141, 0x0124},
+	{0, 0x0000, 0x0127},
+	{0, 0xfea8, 0x0124},
+	{0, 0x0064, 0x0116},
+	{0, 0x0000, 0x0115},
+	{0, 0x0001, 0x0115},
+	{0, 0xffff, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x00aa, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0000, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xffff, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x00f2, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x000f, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xffff, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x00f8, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x00fc, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xffff, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x00f9, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x003c, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xffff, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0027, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0019, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0037, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0000, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0021, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0038, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0006, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0045, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0037, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0001, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x002a, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0038, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0000, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x000e, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0037, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0001, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x002b, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0038, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0001, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x00f4, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0037, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0001, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x002c, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0038, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0001, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0004, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0037, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0001, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x002d, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0038, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0000, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0014, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0037, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0001, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x002e, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0038, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0003, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0000, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0037, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0001, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x002f, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0038, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0003, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0014, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0037, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0001, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0040, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0038, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0000, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0040, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0037, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0001, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0053, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0038, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0000, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0038, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0x0000, 0x0101},
+	{0, 0x00a0, 0x0103},
+	{0, 0x0078, 0x0105},
+	{0, 0x0000, 0x010a},
+	{0, 0x0024, 0x010b},
+	{0, 0x0028, 0x0119},
+	{0, 0x0088, 0x011b},
+	{0, 0x0002, 0x011d},
+	{0, 0x0003, 0x011e},
+	{0, 0x0000, 0x0129},
+	{0, 0x00fc, 0x012b},
+	{0, 0x0008, 0x0102},
+	{0, 0x0000, 0x0104},
+	{0, 0x0008, 0x011a},
+	{0, 0x0028, 0x011c},
+	{0, 0x0021, 0x012a},
+	{0, 0x0000, 0x0118},
+	{0, 0x0000, 0x0132},
+	{0, 0x0000, 0x0109},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0037, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0001, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0031, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0038, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0000, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0000, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0037, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0001, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0040, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0038, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0000, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0040, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0037, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0000, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x00dc, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0038, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0000, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0000, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0037, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0001, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0032, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0038, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0001, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0020, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0037, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0001, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0040, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0038, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0000, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0040, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0037, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0000, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0030, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0xfff9, 0x0124},
+	{0, 0x0086, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0038, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0008, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0x0000, 0x0127},
+	{0, 0xfff8, 0x0124},
+	{0, 0xfffd, 0x0124},
+	{0, 0xfffa, 0x0124},
+	{0, 0x0003, 0x0111},
+};
+
+/* TESTME the old ibmcam driver repeats certain commands to Model1 cameras, we
+   do the same for now (testing needed to see if this is really necessary) */
+static const int cit_model1_ntries = 5;
+static const int cit_model1_ntries2 = 2;
+
+static int cit_write_reg(struct gspca_dev *gspca_dev, u16 value, u16 index)
+{
+	struct usb_device *udev = gspca_dev->dev;
+	int err;
+
+	err = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), 0x00,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT,
+			value, index, NULL, 0, 1000);
+	if (err < 0)
+		pr_err("Failed to write a register (index 0x%04X, value 0x%02X, error %d)\n",
+		       index, value, err);
+
+	return 0;
+}
+
+static int cit_read_reg(struct gspca_dev *gspca_dev, u16 index, int verbose)
+{
+	struct usb_device *udev = gspca_dev->dev;
+	__u8 *buf = gspca_dev->usb_buf;
+	int res;
+
+	res = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), 0x01,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT,
+			0x00, index, buf, 8, 1000);
+	if (res < 0) {
+		pr_err("Failed to read a register (index 0x%04X, error %d)\n",
+		       index, res);
+		return res;
+	}
+
+	if (verbose)
+		gspca_dbg(gspca_dev, D_PROBE, "Register %04x value: %02x\n",
+			  index, buf[0]);
+
+	return 0;
+}
+
+/*
+ * cit_send_FF_04_02()
+ *
+ * This procedure sends magic 3-command prefix to the camera.
+ * The purpose of this prefix is not known.
+ *
+ * History:
+ * 1/2/00   Created.
+ */
+static void cit_send_FF_04_02(struct gspca_dev *gspca_dev)
+{
+	cit_write_reg(gspca_dev, 0x00FF, 0x0127);
+	cit_write_reg(gspca_dev, 0x0004, 0x0124);
+	cit_write_reg(gspca_dev, 0x0002, 0x0124);
+}
+
+static void cit_send_00_04_06(struct gspca_dev *gspca_dev)
+{
+	cit_write_reg(gspca_dev, 0x0000, 0x0127);
+	cit_write_reg(gspca_dev, 0x0004, 0x0124);
+	cit_write_reg(gspca_dev, 0x0006, 0x0124);
+}
+
+static void cit_send_x_00(struct gspca_dev *gspca_dev, unsigned short x)
+{
+	cit_write_reg(gspca_dev, x,      0x0127);
+	cit_write_reg(gspca_dev, 0x0000, 0x0124);
+}
+
+static void cit_send_x_00_05(struct gspca_dev *gspca_dev, unsigned short x)
+{
+	cit_send_x_00(gspca_dev, x);
+	cit_write_reg(gspca_dev, 0x0005, 0x0124);
+}
+
+static void cit_send_x_00_05_02(struct gspca_dev *gspca_dev, unsigned short x)
+{
+	cit_write_reg(gspca_dev, x,      0x0127);
+	cit_write_reg(gspca_dev, 0x0000, 0x0124);
+	cit_write_reg(gspca_dev, 0x0005, 0x0124);
+	cit_write_reg(gspca_dev, 0x0002, 0x0124);
+}
+
+static void cit_send_x_01_00_05(struct gspca_dev *gspca_dev, u16 x)
+{
+	cit_write_reg(gspca_dev, x,      0x0127);
+	cit_write_reg(gspca_dev, 0x0001, 0x0124);
+	cit_write_reg(gspca_dev, 0x0000, 0x0124);
+	cit_write_reg(gspca_dev, 0x0005, 0x0124);
+}
+
+static void cit_send_x_00_05_02_01(struct gspca_dev *gspca_dev, u16 x)
+{
+	cit_write_reg(gspca_dev, x,      0x0127);
+	cit_write_reg(gspca_dev, 0x0000, 0x0124);
+	cit_write_reg(gspca_dev, 0x0005, 0x0124);
+	cit_write_reg(gspca_dev, 0x0002, 0x0124);
+	cit_write_reg(gspca_dev, 0x0001, 0x0124);
+}
+
+static void cit_send_x_00_05_02_08_01(struct gspca_dev *gspca_dev, u16 x)
+{
+	cit_write_reg(gspca_dev, x,      0x0127);
+	cit_write_reg(gspca_dev, 0x0000, 0x0124);
+	cit_write_reg(gspca_dev, 0x0005, 0x0124);
+	cit_write_reg(gspca_dev, 0x0002, 0x0124);
+	cit_write_reg(gspca_dev, 0x0008, 0x0124);
+	cit_write_reg(gspca_dev, 0x0001, 0x0124);
+}
+
+static void cit_Packet_Format1(struct gspca_dev *gspca_dev, u16 fkey, u16 val)
+{
+	cit_send_x_01_00_05(gspca_dev, 0x0088);
+	cit_send_x_00_05(gspca_dev, fkey);
+	cit_send_x_00_05_02_08_01(gspca_dev, val);
+	cit_send_x_00_05(gspca_dev, 0x0088);
+	cit_send_x_00_05_02_01(gspca_dev, fkey);
+	cit_send_x_00_05(gspca_dev, 0x0089);
+	cit_send_x_00(gspca_dev, fkey);
+	cit_send_00_04_06(gspca_dev);
+	cit_read_reg(gspca_dev, 0x0126, 0);
+	cit_send_FF_04_02(gspca_dev);
+}
+
+static void cit_PacketFormat2(struct gspca_dev *gspca_dev, u16 fkey, u16 val)
+{
+	cit_send_x_01_00_05(gspca_dev, 0x0088);
+	cit_send_x_00_05(gspca_dev, fkey);
+	cit_send_x_00_05_02(gspca_dev, val);
+}
+
+static void cit_model2_Packet2(struct gspca_dev *gspca_dev)
+{
+	cit_write_reg(gspca_dev, 0x00ff, 0x012d);
+	cit_write_reg(gspca_dev, 0xfea3, 0x0124);
+}
+
+static void cit_model2_Packet1(struct gspca_dev *gspca_dev, u16 v1, u16 v2)
+{
+	cit_write_reg(gspca_dev, 0x00aa, 0x012d);
+	cit_write_reg(gspca_dev, 0x00ff, 0x012e);
+	cit_write_reg(gspca_dev, v1,     0x012f);
+	cit_write_reg(gspca_dev, 0x00ff, 0x0130);
+	cit_write_reg(gspca_dev, 0xc719, 0x0124);
+	cit_write_reg(gspca_dev, v2,     0x0127);
+
+	cit_model2_Packet2(gspca_dev);
+}
+
+/*
+ * cit_model3_Packet1()
+ *
+ * 00_0078_012d
+ * 00_0097_012f
+ * 00_d141_0124
+ * 00_0096_0127
+ * 00_fea8_0124
+*/
+static void cit_model3_Packet1(struct gspca_dev *gspca_dev, u16 v1, u16 v2)
+{
+	cit_write_reg(gspca_dev, 0x0078, 0x012d);
+	cit_write_reg(gspca_dev, v1,     0x012f);
+	cit_write_reg(gspca_dev, 0xd141, 0x0124);
+	cit_write_reg(gspca_dev, v2,     0x0127);
+	cit_write_reg(gspca_dev, 0xfea8, 0x0124);
+}
+
+static void cit_model4_Packet1(struct gspca_dev *gspca_dev, u16 v1, u16 v2)
+{
+	cit_write_reg(gspca_dev, 0x00aa, 0x012d);
+	cit_write_reg(gspca_dev, v1,     0x012f);
+	cit_write_reg(gspca_dev, 0xd141, 0x0124);
+	cit_write_reg(gspca_dev, v2,     0x0127);
+	cit_write_reg(gspca_dev, 0xfea8, 0x0124);
+}
+
+static void cit_model4_BrightnessPacket(struct gspca_dev *gspca_dev, u16 val)
+{
+	cit_write_reg(gspca_dev, 0x00aa, 0x012d);
+	cit_write_reg(gspca_dev, 0x0026, 0x012f);
+	cit_write_reg(gspca_dev, 0xd141, 0x0124);
+	cit_write_reg(gspca_dev, val,    0x0127);
+	cit_write_reg(gspca_dev, 0x00aa, 0x0130);
+	cit_write_reg(gspca_dev, 0x82a8, 0x0124);
+	cit_write_reg(gspca_dev, 0x0038, 0x012d);
+	cit_write_reg(gspca_dev, 0x0004, 0x012f);
+	cit_write_reg(gspca_dev, 0xd145, 0x0124);
+	cit_write_reg(gspca_dev, 0xfffa, 0x0124);
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+		     const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam;
+
+	sd->model = id->driver_info;
+	if (sd->model == CIT_MODEL3 && ibm_netcam_pro)
+		sd->model = CIT_IBM_NETCAM_PRO;
+
+	cam = &gspca_dev->cam;
+	switch (sd->model) {
+	case CIT_MODEL0:
+		cam->cam_mode = model0_mode;
+		cam->nmodes = ARRAY_SIZE(model0_mode);
+		sd->sof_len = 4;
+		break;
+	case CIT_MODEL1:
+		cam->cam_mode = cif_yuv_mode;
+		cam->nmodes = ARRAY_SIZE(cif_yuv_mode);
+		sd->sof_len = 4;
+		break;
+	case CIT_MODEL2:
+		cam->cam_mode = model2_mode + 1; /* no 160x120 */
+		cam->nmodes = 3;
+		break;
+	case CIT_MODEL3:
+		cam->cam_mode = vga_yuv_mode;
+		cam->nmodes = ARRAY_SIZE(vga_yuv_mode);
+		sd->stop_on_control_change = 1;
+		sd->sof_len = 4;
+		break;
+	case CIT_MODEL4:
+		cam->cam_mode = model2_mode;
+		cam->nmodes = ARRAY_SIZE(model2_mode);
+		break;
+	case CIT_IBM_NETCAM_PRO:
+		cam->cam_mode = vga_yuv_mode;
+		cam->nmodes = 2; /* no 640 x 480 */
+		cam->input_flags = V4L2_IN_ST_VFLIP;
+		sd->stop_on_control_change = 1;
+		sd->sof_len = 4;
+		break;
+	}
+
+	return 0;
+}
+
+static int cit_init_model0(struct gspca_dev *gspca_dev)
+{
+	cit_write_reg(gspca_dev, 0x0000, 0x0100); /* turn on led */
+	cit_write_reg(gspca_dev, 0x0001, 0x0112); /* turn on autogain ? */
+	cit_write_reg(gspca_dev, 0x0000, 0x0400);
+	cit_write_reg(gspca_dev, 0x0001, 0x0400);
+	cit_write_reg(gspca_dev, 0x0000, 0x0420);
+	cit_write_reg(gspca_dev, 0x0001, 0x0420);
+	cit_write_reg(gspca_dev, 0x000d, 0x0409);
+	cit_write_reg(gspca_dev, 0x0002, 0x040a);
+	cit_write_reg(gspca_dev, 0x0018, 0x0405);
+	cit_write_reg(gspca_dev, 0x0008, 0x0435);
+	cit_write_reg(gspca_dev, 0x0026, 0x040b);
+	cit_write_reg(gspca_dev, 0x0007, 0x0437);
+	cit_write_reg(gspca_dev, 0x0015, 0x042f);
+	cit_write_reg(gspca_dev, 0x002b, 0x0439);
+	cit_write_reg(gspca_dev, 0x0026, 0x043a);
+	cit_write_reg(gspca_dev, 0x0008, 0x0438);
+	cit_write_reg(gspca_dev, 0x001e, 0x042b);
+	cit_write_reg(gspca_dev, 0x0041, 0x042c);
+
+	return 0;
+}
+
+static int cit_init_ibm_netcam_pro(struct gspca_dev *gspca_dev)
+{
+	cit_read_reg(gspca_dev, 0x128, 1);
+	cit_write_reg(gspca_dev, 0x0003, 0x0133);
+	cit_write_reg(gspca_dev, 0x0000, 0x0117);
+	cit_write_reg(gspca_dev, 0x0008, 0x0123);
+	cit_write_reg(gspca_dev, 0x0000, 0x0100);
+	cit_read_reg(gspca_dev, 0x0116, 0);
+	cit_write_reg(gspca_dev, 0x0060, 0x0116);
+	cit_write_reg(gspca_dev, 0x0002, 0x0112);
+	cit_write_reg(gspca_dev, 0x0000, 0x0133);
+	cit_write_reg(gspca_dev, 0x0000, 0x0123);
+	cit_write_reg(gspca_dev, 0x0001, 0x0117);
+	cit_write_reg(gspca_dev, 0x0040, 0x0108);
+	cit_write_reg(gspca_dev, 0x0019, 0x012c);
+	cit_write_reg(gspca_dev, 0x0060, 0x0116);
+	cit_write_reg(gspca_dev, 0x0002, 0x0115);
+	cit_write_reg(gspca_dev, 0x000b, 0x0115);
+
+	cit_write_reg(gspca_dev, 0x0078, 0x012d);
+	cit_write_reg(gspca_dev, 0x0001, 0x012f);
+	cit_write_reg(gspca_dev, 0xd141, 0x0124);
+	cit_write_reg(gspca_dev, 0x0079, 0x012d);
+	cit_write_reg(gspca_dev, 0x00ff, 0x0130);
+	cit_write_reg(gspca_dev, 0xcd41, 0x0124);
+	cit_write_reg(gspca_dev, 0xfffa, 0x0124);
+	cit_read_reg(gspca_dev, 0x0126, 1);
+
+	cit_model3_Packet1(gspca_dev, 0x0000, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0000, 0x0001);
+	cit_model3_Packet1(gspca_dev, 0x000b, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x000c, 0x0008);
+	cit_model3_Packet1(gspca_dev, 0x000d, 0x003a);
+	cit_model3_Packet1(gspca_dev, 0x000e, 0x0060);
+	cit_model3_Packet1(gspca_dev, 0x000f, 0x0060);
+	cit_model3_Packet1(gspca_dev, 0x0010, 0x0008);
+	cit_model3_Packet1(gspca_dev, 0x0011, 0x0004);
+	cit_model3_Packet1(gspca_dev, 0x0012, 0x0028);
+	cit_model3_Packet1(gspca_dev, 0x0013, 0x0002);
+	cit_model3_Packet1(gspca_dev, 0x0014, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0015, 0x00fb);
+	cit_model3_Packet1(gspca_dev, 0x0016, 0x0002);
+	cit_model3_Packet1(gspca_dev, 0x0017, 0x0037);
+	cit_model3_Packet1(gspca_dev, 0x0018, 0x0036);
+	cit_model3_Packet1(gspca_dev, 0x001e, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x001f, 0x0008);
+	cit_model3_Packet1(gspca_dev, 0x0020, 0x00c1);
+	cit_model3_Packet1(gspca_dev, 0x0021, 0x0034);
+	cit_model3_Packet1(gspca_dev, 0x0022, 0x0034);
+	cit_model3_Packet1(gspca_dev, 0x0025, 0x0002);
+	cit_model3_Packet1(gspca_dev, 0x0028, 0x0022);
+	cit_model3_Packet1(gspca_dev, 0x0029, 0x000a);
+	cit_model3_Packet1(gspca_dev, 0x002b, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x002c, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x002d, 0x00ff);
+	cit_model3_Packet1(gspca_dev, 0x002e, 0x00ff);
+	cit_model3_Packet1(gspca_dev, 0x002f, 0x00ff);
+	cit_model3_Packet1(gspca_dev, 0x0030, 0x00ff);
+	cit_model3_Packet1(gspca_dev, 0x0031, 0x00ff);
+	cit_model3_Packet1(gspca_dev, 0x0032, 0x0007);
+	cit_model3_Packet1(gspca_dev, 0x0033, 0x0005);
+	cit_model3_Packet1(gspca_dev, 0x0037, 0x0040);
+	cit_model3_Packet1(gspca_dev, 0x0039, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x003a, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x003b, 0x0001);
+	cit_model3_Packet1(gspca_dev, 0x003c, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0040, 0x000c);
+	cit_model3_Packet1(gspca_dev, 0x0041, 0x00fb);
+	cit_model3_Packet1(gspca_dev, 0x0042, 0x0002);
+	cit_model3_Packet1(gspca_dev, 0x0043, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0045, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0046, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0047, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0048, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0049, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x004a, 0x00ff);
+	cit_model3_Packet1(gspca_dev, 0x004b, 0x00ff);
+	cit_model3_Packet1(gspca_dev, 0x004c, 0x00ff);
+	cit_model3_Packet1(gspca_dev, 0x004f, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0050, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0051, 0x0002);
+	cit_model3_Packet1(gspca_dev, 0x0055, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0056, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0057, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0058, 0x0002);
+	cit_model3_Packet1(gspca_dev, 0x0059, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x005c, 0x0016);
+	cit_model3_Packet1(gspca_dev, 0x005d, 0x0022);
+	cit_model3_Packet1(gspca_dev, 0x005e, 0x003c);
+	cit_model3_Packet1(gspca_dev, 0x005f, 0x0050);
+	cit_model3_Packet1(gspca_dev, 0x0060, 0x0044);
+	cit_model3_Packet1(gspca_dev, 0x0061, 0x0005);
+	cit_model3_Packet1(gspca_dev, 0x006a, 0x007e);
+	cit_model3_Packet1(gspca_dev, 0x006f, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0072, 0x001b);
+	cit_model3_Packet1(gspca_dev, 0x0073, 0x0005);
+	cit_model3_Packet1(gspca_dev, 0x0074, 0x000a);
+	cit_model3_Packet1(gspca_dev, 0x0075, 0x001b);
+	cit_model3_Packet1(gspca_dev, 0x0076, 0x002a);
+	cit_model3_Packet1(gspca_dev, 0x0077, 0x003c);
+	cit_model3_Packet1(gspca_dev, 0x0078, 0x0050);
+	cit_model3_Packet1(gspca_dev, 0x007b, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x007c, 0x0011);
+	cit_model3_Packet1(gspca_dev, 0x007d, 0x0024);
+	cit_model3_Packet1(gspca_dev, 0x007e, 0x0043);
+	cit_model3_Packet1(gspca_dev, 0x007f, 0x005a);
+	cit_model3_Packet1(gspca_dev, 0x0084, 0x0020);
+	cit_model3_Packet1(gspca_dev, 0x0085, 0x0033);
+	cit_model3_Packet1(gspca_dev, 0x0086, 0x000a);
+	cit_model3_Packet1(gspca_dev, 0x0087, 0x0030);
+	cit_model3_Packet1(gspca_dev, 0x0088, 0x0070);
+	cit_model3_Packet1(gspca_dev, 0x008b, 0x0008);
+	cit_model3_Packet1(gspca_dev, 0x008f, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0090, 0x0006);
+	cit_model3_Packet1(gspca_dev, 0x0091, 0x0028);
+	cit_model3_Packet1(gspca_dev, 0x0092, 0x005a);
+	cit_model3_Packet1(gspca_dev, 0x0093, 0x0082);
+	cit_model3_Packet1(gspca_dev, 0x0096, 0x0014);
+	cit_model3_Packet1(gspca_dev, 0x0097, 0x0020);
+	cit_model3_Packet1(gspca_dev, 0x0098, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x00b0, 0x0046);
+	cit_model3_Packet1(gspca_dev, 0x00b1, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x00b2, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x00b3, 0x0004);
+	cit_model3_Packet1(gspca_dev, 0x00b4, 0x0007);
+	cit_model3_Packet1(gspca_dev, 0x00b6, 0x0002);
+	cit_model3_Packet1(gspca_dev, 0x00b7, 0x0004);
+	cit_model3_Packet1(gspca_dev, 0x00bb, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x00bc, 0x0001);
+	cit_model3_Packet1(gspca_dev, 0x00bd, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x00bf, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x00c0, 0x00c8);
+	cit_model3_Packet1(gspca_dev, 0x00c1, 0x0014);
+	cit_model3_Packet1(gspca_dev, 0x00c2, 0x0001);
+	cit_model3_Packet1(gspca_dev, 0x00c3, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x00c4, 0x0004);
+	cit_model3_Packet1(gspca_dev, 0x00cb, 0x00bf);
+	cit_model3_Packet1(gspca_dev, 0x00cc, 0x00bf);
+	cit_model3_Packet1(gspca_dev, 0x00cd, 0x00bf);
+	cit_model3_Packet1(gspca_dev, 0x00ce, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x00cf, 0x0020);
+	cit_model3_Packet1(gspca_dev, 0x00d0, 0x0040);
+	cit_model3_Packet1(gspca_dev, 0x00d1, 0x00bf);
+	cit_model3_Packet1(gspca_dev, 0x00d1, 0x00bf);
+	cit_model3_Packet1(gspca_dev, 0x00d2, 0x00bf);
+	cit_model3_Packet1(gspca_dev, 0x00d3, 0x00bf);
+	cit_model3_Packet1(gspca_dev, 0x00ea, 0x0008);
+	cit_model3_Packet1(gspca_dev, 0x00eb, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x00ec, 0x00e8);
+	cit_model3_Packet1(gspca_dev, 0x00ed, 0x0001);
+	cit_model3_Packet1(gspca_dev, 0x00ef, 0x0022);
+	cit_model3_Packet1(gspca_dev, 0x00f0, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x00f2, 0x0028);
+	cit_model3_Packet1(gspca_dev, 0x00f4, 0x0002);
+	cit_model3_Packet1(gspca_dev, 0x00f5, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x00fa, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x00fb, 0x0001);
+	cit_model3_Packet1(gspca_dev, 0x00fc, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x00fd, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x00fe, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x00ff, 0x0000);
+
+	cit_model3_Packet1(gspca_dev, 0x00be, 0x0003);
+	cit_model3_Packet1(gspca_dev, 0x00c8, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x00c9, 0x0020);
+	cit_model3_Packet1(gspca_dev, 0x00ca, 0x0040);
+	cit_model3_Packet1(gspca_dev, 0x0053, 0x0001);
+	cit_model3_Packet1(gspca_dev, 0x0082, 0x000e);
+	cit_model3_Packet1(gspca_dev, 0x0083, 0x0020);
+	cit_model3_Packet1(gspca_dev, 0x0034, 0x003c);
+	cit_model3_Packet1(gspca_dev, 0x006e, 0x0055);
+	cit_model3_Packet1(gspca_dev, 0x0062, 0x0005);
+	cit_model3_Packet1(gspca_dev, 0x0063, 0x0008);
+	cit_model3_Packet1(gspca_dev, 0x0066, 0x000a);
+	cit_model3_Packet1(gspca_dev, 0x0067, 0x0006);
+	cit_model3_Packet1(gspca_dev, 0x006b, 0x0010);
+	cit_model3_Packet1(gspca_dev, 0x005a, 0x0001);
+	cit_model3_Packet1(gspca_dev, 0x005b, 0x000a);
+	cit_model3_Packet1(gspca_dev, 0x0023, 0x0006);
+	cit_model3_Packet1(gspca_dev, 0x0026, 0x0004);
+	cit_model3_Packet1(gspca_dev, 0x0036, 0x0069);
+	cit_model3_Packet1(gspca_dev, 0x0038, 0x0064);
+	cit_model3_Packet1(gspca_dev, 0x003d, 0x0003);
+	cit_model3_Packet1(gspca_dev, 0x003e, 0x0001);
+	cit_model3_Packet1(gspca_dev, 0x00b8, 0x0014);
+	cit_model3_Packet1(gspca_dev, 0x00b9, 0x0014);
+	cit_model3_Packet1(gspca_dev, 0x00e6, 0x0004);
+	cit_model3_Packet1(gspca_dev, 0x00e8, 0x0001);
+
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	switch (sd->model) {
+	case CIT_MODEL0:
+		cit_init_model0(gspca_dev);
+		sd_stop0(gspca_dev);
+		break;
+	case CIT_MODEL1:
+	case CIT_MODEL2:
+	case CIT_MODEL3:
+	case CIT_MODEL4:
+		break; /* All is done in sd_start */
+	case CIT_IBM_NETCAM_PRO:
+		cit_init_ibm_netcam_pro(gspca_dev);
+		sd_stop0(gspca_dev);
+		break;
+	}
+	return 0;
+}
+
+static int cit_set_brightness(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int i;
+
+	switch (sd->model) {
+	case CIT_MODEL0:
+	case CIT_IBM_NETCAM_PRO:
+		/* No (known) brightness control for these */
+		break;
+	case CIT_MODEL1:
+		/* Model 1: Brightness range 0 - 63 */
+		cit_Packet_Format1(gspca_dev, 0x0031, val);
+		cit_Packet_Format1(gspca_dev, 0x0032, val);
+		cit_Packet_Format1(gspca_dev, 0x0033, val);
+		break;
+	case CIT_MODEL2:
+		/* Model 2: Brightness range 0x60 - 0xee */
+		/* Scale 0 - 63 to 0x60 - 0xee */
+		i = 0x60 + val * 2254 / 1000;
+		cit_model2_Packet1(gspca_dev, 0x001a, i);
+		break;
+	case CIT_MODEL3:
+		/* Model 3: Brightness range 'i' in [0x0C..0x3F] */
+		i = val;
+		if (i < 0x0c)
+			i = 0x0c;
+		cit_model3_Packet1(gspca_dev, 0x0036, i);
+		break;
+	case CIT_MODEL4:
+		/* Model 4: Brightness range 'i' in [0x04..0xb4] */
+		/* Scale 0 - 63 to 0x04 - 0xb4 */
+		i = 0x04 + val * 2794 / 1000;
+		cit_model4_BrightnessPacket(gspca_dev, i);
+		break;
+	}
+
+	return 0;
+}
+
+static int cit_set_contrast(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	switch (sd->model) {
+	case CIT_MODEL0: {
+		int i;
+		/* gain 0-15, 0-20 -> 0-15 */
+		i = val * 1000 / 1333;
+		cit_write_reg(gspca_dev, i, 0x0422);
+		/* gain 0-31, may not be lower then 0x0422, 0-20 -> 0-31 */
+		i = val * 2000 / 1333;
+		cit_write_reg(gspca_dev, i, 0x0423);
+		/* gain 0-127, may not be lower then 0x0423, 0-20 -> 0-63  */
+		i = val * 4000 / 1333;
+		cit_write_reg(gspca_dev, i, 0x0424);
+		/* gain 0-127, may not be lower then 0x0424, , 0-20 -> 0-127 */
+		i = val * 8000 / 1333;
+		cit_write_reg(gspca_dev, i, 0x0425);
+		break;
+	}
+	case CIT_MODEL2:
+	case CIT_MODEL4:
+		/* These models do not have this control. */
+		break;
+	case CIT_MODEL1:
+	{
+		/* Scale 0 - 20 to 15 - 0 */
+		int i, new_contrast = (20 - val) * 1000 / 1333;
+		for (i = 0; i < cit_model1_ntries; i++) {
+			cit_Packet_Format1(gspca_dev, 0x0014, new_contrast);
+			cit_send_FF_04_02(gspca_dev);
+		}
+		break;
+	}
+	case CIT_MODEL3:
+	{	/* Preset hardware values */
+		static const struct {
+			unsigned short cv1;
+			unsigned short cv2;
+			unsigned short cv3;
+		} cv[7] = {
+			{ 0x05, 0x05, 0x0f },	/* Minimum */
+			{ 0x04, 0x04, 0x16 },
+			{ 0x02, 0x03, 0x16 },
+			{ 0x02, 0x08, 0x16 },
+			{ 0x01, 0x0c, 0x16 },
+			{ 0x01, 0x0e, 0x16 },
+			{ 0x01, 0x10, 0x16 }	/* Maximum */
+		};
+		int i = val / 3;
+		cit_model3_Packet1(gspca_dev, 0x0067, cv[i].cv1);
+		cit_model3_Packet1(gspca_dev, 0x005b, cv[i].cv2);
+		cit_model3_Packet1(gspca_dev, 0x005c, cv[i].cv3);
+		break;
+	}
+	case CIT_IBM_NETCAM_PRO:
+		cit_model3_Packet1(gspca_dev, 0x005b, val + 1);
+		break;
+	}
+	return 0;
+}
+
+static int cit_set_hue(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	switch (sd->model) {
+	case CIT_MODEL0:
+	case CIT_MODEL1:
+	case CIT_IBM_NETCAM_PRO:
+		/* No hue control for these models */
+		break;
+	case CIT_MODEL2:
+		cit_model2_Packet1(gspca_dev, 0x0024, val);
+		/* cit_model2_Packet1(gspca_dev, 0x0020, sat); */
+		break;
+	case CIT_MODEL3: {
+		/* Model 3: Brightness range 'i' in [0x05..0x37] */
+		/* TESTME according to the ibmcam driver this does not work */
+		if (0) {
+			/* Scale 0 - 127 to 0x05 - 0x37 */
+			int i = 0x05 + val * 1000 / 2540;
+			cit_model3_Packet1(gspca_dev, 0x007e, i);
+		}
+		break;
+	}
+	case CIT_MODEL4:
+		/* HDG: taken from ibmcam, setting the color gains does not
+		 * really belong here.
+		 *
+		 * I am not sure r/g/b_gain variables exactly control gain
+		 * of those channels. Most likely they subtly change some
+		 * very internal image processing settings in the camera.
+		 * In any case, here is what they do, and feel free to tweak:
+		 *
+		 * r_gain: seriously affects red gain
+		 * g_gain: seriously affects green gain
+		 * b_gain: seriously affects blue gain
+		 * hue: changes average color from violet (0) to red (0xFF)
+		 */
+		cit_write_reg(gspca_dev, 0x00aa, 0x012d);
+		cit_write_reg(gspca_dev, 0x001e, 0x012f);
+		cit_write_reg(gspca_dev, 0xd141, 0x0124);
+		cit_write_reg(gspca_dev,    160, 0x0127);  /* Green gain */
+		cit_write_reg(gspca_dev,    160, 0x012e);  /* Red gain */
+		cit_write_reg(gspca_dev,    160, 0x0130);  /* Blue gain */
+		cit_write_reg(gspca_dev, 0x8a28, 0x0124);
+		cit_write_reg(gspca_dev, val, 0x012d); /* Hue */
+		cit_write_reg(gspca_dev, 0xf545, 0x0124);
+		break;
+	}
+	return 0;
+}
+
+static int cit_set_sharpness(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	switch (sd->model) {
+	case CIT_MODEL0:
+	case CIT_MODEL2:
+	case CIT_MODEL4:
+	case CIT_IBM_NETCAM_PRO:
+		/* These models do not have this control */
+		break;
+	case CIT_MODEL1: {
+		int i;
+		static const unsigned short sa[] = {
+			0x11, 0x13, 0x16, 0x18, 0x1a, 0x8, 0x0a };
+
+		for (i = 0; i < cit_model1_ntries; i++)
+			cit_PacketFormat2(gspca_dev, 0x0013, sa[val]);
+		break;
+	}
+	case CIT_MODEL3:
+	{	/*
+		 * "Use a table of magic numbers.
+		 *  This setting doesn't really change much.
+		 *  But that's how Windows does it."
+		 */
+		static const struct {
+			unsigned short sv1;
+			unsigned short sv2;
+			unsigned short sv3;
+			unsigned short sv4;
+		} sv[7] = {
+			{ 0x00, 0x00, 0x05, 0x14 },	/* Smoothest */
+			{ 0x01, 0x04, 0x05, 0x14 },
+			{ 0x02, 0x04, 0x05, 0x14 },
+			{ 0x03, 0x04, 0x05, 0x14 },
+			{ 0x03, 0x05, 0x05, 0x14 },
+			{ 0x03, 0x06, 0x05, 0x14 },
+			{ 0x03, 0x07, 0x05, 0x14 }	/* Sharpest */
+		};
+		cit_model3_Packet1(gspca_dev, 0x0060, sv[val].sv1);
+		cit_model3_Packet1(gspca_dev, 0x0061, sv[val].sv2);
+		cit_model3_Packet1(gspca_dev, 0x0062, sv[val].sv3);
+		cit_model3_Packet1(gspca_dev, 0x0063, sv[val].sv4);
+		break;
+	}
+	}
+	return 0;
+}
+
+/*
+ * cit_set_lighting()
+ *
+ * Camera model 1:
+ * We have 3 levels of lighting conditions: 0=Bright, 1=Medium, 2=Low.
+ *
+ * Camera model 2:
+ * We have 16 levels of lighting, 0 for bright light and up to 15 for
+ * low light. But values above 5 or so are useless because camera is
+ * not really capable to produce anything worth viewing at such light.
+ * This setting may be altered only in certain camera state.
+ *
+ * Low lighting forces slower FPS.
+ *
+ * History:
+ * 1/5/00   Created.
+ * 2/20/00  Added support for Model 2 cameras.
+ */
+static void cit_set_lighting(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	switch (sd->model) {
+	case CIT_MODEL0:
+	case CIT_MODEL2:
+	case CIT_MODEL3:
+	case CIT_MODEL4:
+	case CIT_IBM_NETCAM_PRO:
+		break;
+	case CIT_MODEL1: {
+		int i;
+		for (i = 0; i < cit_model1_ntries; i++)
+			cit_Packet_Format1(gspca_dev, 0x0027, val);
+		break;
+	}
+	}
+}
+
+static void cit_set_hflip(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	switch (sd->model) {
+	case CIT_MODEL0:
+		if (val)
+			cit_write_reg(gspca_dev, 0x0020, 0x0115);
+		else
+			cit_write_reg(gspca_dev, 0x0040, 0x0115);
+		break;
+	case CIT_MODEL1:
+	case CIT_MODEL2:
+	case CIT_MODEL3:
+	case CIT_MODEL4:
+	case CIT_IBM_NETCAM_PRO:
+		break;
+	}
+}
+
+static int cit_restart_stream(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	switch (sd->model) {
+	case CIT_MODEL0:
+	case CIT_MODEL1:
+		cit_write_reg(gspca_dev, 0x0001, 0x0114);
+		/* Fall through */
+	case CIT_MODEL2:
+	case CIT_MODEL4:
+		cit_write_reg(gspca_dev, 0x00c0, 0x010c); /* Go! */
+		usb_clear_halt(gspca_dev->dev, gspca_dev->urb[0]->pipe);
+		break;
+	case CIT_MODEL3:
+	case CIT_IBM_NETCAM_PRO:
+		cit_write_reg(gspca_dev, 0x0001, 0x0114);
+		cit_write_reg(gspca_dev, 0x00c0, 0x010c); /* Go! */
+		usb_clear_halt(gspca_dev->dev, gspca_dev->urb[0]->pipe);
+		/* Clear button events from while we were not streaming */
+		cit_write_reg(gspca_dev, 0x0001, 0x0113);
+		break;
+	}
+
+	sd->sof_read = 0;
+
+	return 0;
+}
+
+static int cit_get_packet_size(struct gspca_dev *gspca_dev)
+{
+	struct usb_host_interface *alt;
+	struct usb_interface *intf;
+
+	intf = usb_ifnum_to_if(gspca_dev->dev, gspca_dev->iface);
+	alt = usb_altnum_to_altsetting(intf, gspca_dev->alt);
+	if (!alt) {
+		pr_err("Couldn't get altsetting\n");
+		return -EIO;
+	}
+
+	return le16_to_cpu(alt->endpoint[0].desc.wMaxPacketSize);
+}
+
+/* Calculate the clockdiv giving us max fps given the available bandwidth */
+static int cit_get_clock_div(struct gspca_dev *gspca_dev)
+{
+	int clock_div = 7; /* 0=30 1=25 2=20 3=15 4=12 5=7.5 6=6 7=3fps ?? */
+	int fps[8] = { 30, 25, 20, 15, 12, 8, 6, 3 };
+	int packet_size;
+
+	packet_size = cit_get_packet_size(gspca_dev);
+	if (packet_size < 0)
+		return packet_size;
+
+	while (clock_div > 3 &&
+			1000 * packet_size >
+			gspca_dev->pixfmt.width * gspca_dev->pixfmt.height *
+			fps[clock_div - 1] * 3 / 2)
+		clock_div--;
+
+	gspca_dbg(gspca_dev, D_PROBE,
+		  "PacketSize: %d, res: %dx%d -> using clockdiv: %d (%d fps)\n",
+		  packet_size,
+		  gspca_dev->pixfmt.width, gspca_dev->pixfmt.height,
+		  clock_div, fps[clock_div]);
+
+	return clock_div;
+}
+
+static int cit_start_model0(struct gspca_dev *gspca_dev)
+{
+	const unsigned short compression = 0; /* 0=none, 7=best frame rate */
+	int clock_div;
+
+	clock_div = cit_get_clock_div(gspca_dev);
+	if (clock_div < 0)
+		return clock_div;
+
+	cit_write_reg(gspca_dev, 0x0000, 0x0100); /* turn on led */
+	cit_write_reg(gspca_dev, 0x0003, 0x0438);
+	cit_write_reg(gspca_dev, 0x001e, 0x042b);
+	cit_write_reg(gspca_dev, 0x0041, 0x042c);
+	cit_write_reg(gspca_dev, 0x0008, 0x0436);
+	cit_write_reg(gspca_dev, 0x0024, 0x0403);
+	cit_write_reg(gspca_dev, 0x002c, 0x0404);
+	cit_write_reg(gspca_dev, 0x0002, 0x0426);
+	cit_write_reg(gspca_dev, 0x0014, 0x0427);
+
+	switch (gspca_dev->pixfmt.width) {
+	case 160: /* 160x120 */
+		cit_write_reg(gspca_dev, 0x0004, 0x010b);
+		cit_write_reg(gspca_dev, 0x0001, 0x010a);
+		cit_write_reg(gspca_dev, 0x0010, 0x0102);
+		cit_write_reg(gspca_dev, 0x00a0, 0x0103);
+		cit_write_reg(gspca_dev, 0x0000, 0x0104);
+		cit_write_reg(gspca_dev, 0x0078, 0x0105);
+		break;
+
+	case 176: /* 176x144 */
+		cit_write_reg(gspca_dev, 0x0006, 0x010b);
+		cit_write_reg(gspca_dev, 0x0000, 0x010a);
+		cit_write_reg(gspca_dev, 0x0005, 0x0102);
+		cit_write_reg(gspca_dev, 0x00b0, 0x0103);
+		cit_write_reg(gspca_dev, 0x0000, 0x0104);
+		cit_write_reg(gspca_dev, 0x0090, 0x0105);
+		break;
+
+	case 320: /* 320x240 */
+		cit_write_reg(gspca_dev, 0x0008, 0x010b);
+		cit_write_reg(gspca_dev, 0x0004, 0x010a);
+		cit_write_reg(gspca_dev, 0x0005, 0x0102);
+		cit_write_reg(gspca_dev, 0x00a0, 0x0103);
+		cit_write_reg(gspca_dev, 0x0010, 0x0104);
+		cit_write_reg(gspca_dev, 0x0078, 0x0105);
+		break;
+	}
+
+	cit_write_reg(gspca_dev, compression, 0x0109);
+	cit_write_reg(gspca_dev, clock_div, 0x0111);
+
+	return 0;
+}
+
+static int cit_start_model1(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int i, clock_div;
+
+	clock_div = cit_get_clock_div(gspca_dev);
+	if (clock_div < 0)
+		return clock_div;
+
+	cit_read_reg(gspca_dev, 0x0128, 1);
+	cit_read_reg(gspca_dev, 0x0100, 0);
+	cit_write_reg(gspca_dev, 0x01, 0x0100);	/* LED On  */
+	cit_read_reg(gspca_dev, 0x0100, 0);
+	cit_write_reg(gspca_dev, 0x81, 0x0100);	/* LED Off */
+	cit_read_reg(gspca_dev, 0x0100, 0);
+	cit_write_reg(gspca_dev, 0x01, 0x0100);	/* LED On  */
+	cit_write_reg(gspca_dev, 0x01, 0x0108);
+
+	cit_write_reg(gspca_dev, 0x03, 0x0112);
+	cit_read_reg(gspca_dev, 0x0115, 0);
+	cit_write_reg(gspca_dev, 0x06, 0x0115);
+	cit_read_reg(gspca_dev, 0x0116, 0);
+	cit_write_reg(gspca_dev, 0x44, 0x0116);
+	cit_read_reg(gspca_dev, 0x0116, 0);
+	cit_write_reg(gspca_dev, 0x40, 0x0116);
+	cit_read_reg(gspca_dev, 0x0115, 0);
+	cit_write_reg(gspca_dev, 0x0e, 0x0115);
+	cit_write_reg(gspca_dev, 0x19, 0x012c);
+
+	cit_Packet_Format1(gspca_dev, 0x00, 0x1e);
+	cit_Packet_Format1(gspca_dev, 0x39, 0x0d);
+	cit_Packet_Format1(gspca_dev, 0x39, 0x09);
+	cit_Packet_Format1(gspca_dev, 0x3b, 0x00);
+	cit_Packet_Format1(gspca_dev, 0x28, 0x22);
+	cit_Packet_Format1(gspca_dev, 0x27, 0x00);
+	cit_Packet_Format1(gspca_dev, 0x2b, 0x1f);
+	cit_Packet_Format1(gspca_dev, 0x39, 0x08);
+
+	for (i = 0; i < cit_model1_ntries; i++)
+		cit_Packet_Format1(gspca_dev, 0x2c, 0x00);
+
+	for (i = 0; i < cit_model1_ntries; i++)
+		cit_Packet_Format1(gspca_dev, 0x30, 0x14);
+
+	cit_PacketFormat2(gspca_dev, 0x39, 0x02);
+	cit_PacketFormat2(gspca_dev, 0x01, 0xe1);
+	cit_PacketFormat2(gspca_dev, 0x02, 0xcd);
+	cit_PacketFormat2(gspca_dev, 0x03, 0xcd);
+	cit_PacketFormat2(gspca_dev, 0x04, 0xfa);
+	cit_PacketFormat2(gspca_dev, 0x3f, 0xff);
+	cit_PacketFormat2(gspca_dev, 0x39, 0x00);
+
+	cit_PacketFormat2(gspca_dev, 0x39, 0x02);
+	cit_PacketFormat2(gspca_dev, 0x0a, 0x37);
+	cit_PacketFormat2(gspca_dev, 0x0b, 0xb8);
+	cit_PacketFormat2(gspca_dev, 0x0c, 0xf3);
+	cit_PacketFormat2(gspca_dev, 0x0d, 0xe3);
+	cit_PacketFormat2(gspca_dev, 0x0e, 0x0d);
+	cit_PacketFormat2(gspca_dev, 0x0f, 0xf2);
+	cit_PacketFormat2(gspca_dev, 0x10, 0xd5);
+	cit_PacketFormat2(gspca_dev, 0x11, 0xba);
+	cit_PacketFormat2(gspca_dev, 0x12, 0x53);
+	cit_PacketFormat2(gspca_dev, 0x3f, 0xff);
+	cit_PacketFormat2(gspca_dev, 0x39, 0x00);
+
+	cit_PacketFormat2(gspca_dev, 0x39, 0x02);
+	cit_PacketFormat2(gspca_dev, 0x16, 0x00);
+	cit_PacketFormat2(gspca_dev, 0x17, 0x28);
+	cit_PacketFormat2(gspca_dev, 0x18, 0x7d);
+	cit_PacketFormat2(gspca_dev, 0x19, 0xbe);
+	cit_PacketFormat2(gspca_dev, 0x3f, 0xff);
+	cit_PacketFormat2(gspca_dev, 0x39, 0x00);
+
+	for (i = 0; i < cit_model1_ntries; i++)
+		cit_Packet_Format1(gspca_dev, 0x00, 0x18);
+	for (i = 0; i < cit_model1_ntries; i++)
+		cit_Packet_Format1(gspca_dev, 0x13, 0x18);
+	for (i = 0; i < cit_model1_ntries; i++)
+		cit_Packet_Format1(gspca_dev, 0x14, 0x06);
+
+	/* TESTME These are handled through controls
+	   KEEP until someone can test leaving this out is ok */
+	if (0) {
+		/* This is default brightness */
+		for (i = 0; i < cit_model1_ntries; i++)
+			cit_Packet_Format1(gspca_dev, 0x31, 0x37);
+		for (i = 0; i < cit_model1_ntries; i++)
+			cit_Packet_Format1(gspca_dev, 0x32, 0x46);
+		for (i = 0; i < cit_model1_ntries; i++)
+			cit_Packet_Format1(gspca_dev, 0x33, 0x55);
+	}
+
+	cit_Packet_Format1(gspca_dev, 0x2e, 0x04);
+	for (i = 0; i < cit_model1_ntries; i++)
+		cit_Packet_Format1(gspca_dev, 0x2d, 0x04);
+	for (i = 0; i < cit_model1_ntries; i++)
+		cit_Packet_Format1(gspca_dev, 0x29, 0x80);
+	cit_Packet_Format1(gspca_dev, 0x2c, 0x01);
+	cit_Packet_Format1(gspca_dev, 0x30, 0x17);
+	cit_Packet_Format1(gspca_dev, 0x39, 0x08);
+	for (i = 0; i < cit_model1_ntries; i++)
+		cit_Packet_Format1(gspca_dev, 0x34, 0x00);
+
+	cit_write_reg(gspca_dev, 0x00, 0x0101);
+	cit_write_reg(gspca_dev, 0x00, 0x010a);
+
+	switch (gspca_dev->pixfmt.width) {
+	case 128: /* 128x96 */
+		cit_write_reg(gspca_dev, 0x80, 0x0103);
+		cit_write_reg(gspca_dev, 0x60, 0x0105);
+		cit_write_reg(gspca_dev, 0x0c, 0x010b);
+		cit_write_reg(gspca_dev, 0x04, 0x011b);	/* Same everywhere */
+		cit_write_reg(gspca_dev, 0x0b, 0x011d);
+		cit_write_reg(gspca_dev, 0x00, 0x011e);	/* Same everywhere */
+		cit_write_reg(gspca_dev, 0x00, 0x0129);
+		break;
+	case 176: /* 176x144 */
+		cit_write_reg(gspca_dev, 0xb0, 0x0103);
+		cit_write_reg(gspca_dev, 0x8f, 0x0105);
+		cit_write_reg(gspca_dev, 0x06, 0x010b);
+		cit_write_reg(gspca_dev, 0x04, 0x011b);	/* Same everywhere */
+		cit_write_reg(gspca_dev, 0x0d, 0x011d);
+		cit_write_reg(gspca_dev, 0x00, 0x011e);	/* Same everywhere */
+		cit_write_reg(gspca_dev, 0x03, 0x0129);
+		break;
+	case 352: /* 352x288 */
+		cit_write_reg(gspca_dev, 0xb0, 0x0103);
+		cit_write_reg(gspca_dev, 0x90, 0x0105);
+		cit_write_reg(gspca_dev, 0x02, 0x010b);
+		cit_write_reg(gspca_dev, 0x04, 0x011b);	/* Same everywhere */
+		cit_write_reg(gspca_dev, 0x05, 0x011d);
+		cit_write_reg(gspca_dev, 0x00, 0x011e);	/* Same everywhere */
+		cit_write_reg(gspca_dev, 0x00, 0x0129);
+		break;
+	}
+
+	cit_write_reg(gspca_dev, 0xff, 0x012b);
+
+	/* TESTME These are handled through controls
+	   KEEP until someone can test leaving this out is ok */
+	if (0) {
+		/* This is another brightness - don't know why */
+		for (i = 0; i < cit_model1_ntries; i++)
+			cit_Packet_Format1(gspca_dev, 0x31, 0xc3);
+		for (i = 0; i < cit_model1_ntries; i++)
+			cit_Packet_Format1(gspca_dev, 0x32, 0xd2);
+		for (i = 0; i < cit_model1_ntries; i++)
+			cit_Packet_Format1(gspca_dev, 0x33, 0xe1);
+
+		/* Default contrast */
+		for (i = 0; i < cit_model1_ntries; i++)
+			cit_Packet_Format1(gspca_dev, 0x14, 0x0a);
+
+		/* Default sharpness */
+		for (i = 0; i < cit_model1_ntries2; i++)
+			cit_PacketFormat2(gspca_dev, 0x13, 0x1a);
+
+		/* Default lighting conditions */
+		cit_Packet_Format1(gspca_dev, 0x0027,
+				   v4l2_ctrl_g_ctrl(sd->lighting));
+	}
+
+	/* Assorted init */
+	switch (gspca_dev->pixfmt.width) {
+	case 128: /* 128x96 */
+		cit_Packet_Format1(gspca_dev, 0x2b, 0x1e);
+		cit_write_reg(gspca_dev, 0xc9, 0x0119);	/* Same everywhere */
+		cit_write_reg(gspca_dev, 0x80, 0x0109);	/* Same everywhere */
+		cit_write_reg(gspca_dev, 0x36, 0x0102);
+		cit_write_reg(gspca_dev, 0x1a, 0x0104);
+		cit_write_reg(gspca_dev, 0x04, 0x011a);	/* Same everywhere */
+		cit_write_reg(gspca_dev, 0x2b, 0x011c);
+		cit_write_reg(gspca_dev, 0x23, 0x012a);	/* Same everywhere */
+		break;
+	case 176: /* 176x144 */
+		cit_Packet_Format1(gspca_dev, 0x2b, 0x1e);
+		cit_write_reg(gspca_dev, 0xc9, 0x0119);	/* Same everywhere */
+		cit_write_reg(gspca_dev, 0x80, 0x0109);	/* Same everywhere */
+		cit_write_reg(gspca_dev, 0x04, 0x0102);
+		cit_write_reg(gspca_dev, 0x02, 0x0104);
+		cit_write_reg(gspca_dev, 0x04, 0x011a);	/* Same everywhere */
+		cit_write_reg(gspca_dev, 0x2b, 0x011c);
+		cit_write_reg(gspca_dev, 0x23, 0x012a);	/* Same everywhere */
+		break;
+	case 352: /* 352x288 */
+		cit_Packet_Format1(gspca_dev, 0x2b, 0x1f);
+		cit_write_reg(gspca_dev, 0xc9, 0x0119);	/* Same everywhere */
+		cit_write_reg(gspca_dev, 0x80, 0x0109);	/* Same everywhere */
+		cit_write_reg(gspca_dev, 0x08, 0x0102);
+		cit_write_reg(gspca_dev, 0x01, 0x0104);
+		cit_write_reg(gspca_dev, 0x04, 0x011a);	/* Same everywhere */
+		cit_write_reg(gspca_dev, 0x2f, 0x011c);
+		cit_write_reg(gspca_dev, 0x23, 0x012a);	/* Same everywhere */
+		break;
+	}
+
+	cit_write_reg(gspca_dev, 0x01, 0x0100);	/* LED On  */
+	cit_write_reg(gspca_dev, clock_div, 0x0111);
+
+	return 0;
+}
+
+static int cit_start_model2(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int clock_div = 0;
+
+	cit_write_reg(gspca_dev, 0x0000, 0x0100);	/* LED on */
+	cit_read_reg(gspca_dev, 0x0116, 0);
+	cit_write_reg(gspca_dev, 0x0060, 0x0116);
+	cit_write_reg(gspca_dev, 0x0002, 0x0112);
+	cit_write_reg(gspca_dev, 0x00bc, 0x012c);
+	cit_write_reg(gspca_dev, 0x0008, 0x012b);
+	cit_write_reg(gspca_dev, 0x0000, 0x0108);
+	cit_write_reg(gspca_dev, 0x0001, 0x0133);
+	cit_write_reg(gspca_dev, 0x0001, 0x0102);
+	switch (gspca_dev->pixfmt.width) {
+	case 176: /* 176x144 */
+		cit_write_reg(gspca_dev, 0x002c, 0x0103);	/* All except 320x240 */
+		cit_write_reg(gspca_dev, 0x0000, 0x0104);	/* Same */
+		cit_write_reg(gspca_dev, 0x0024, 0x0105);	/* 176x144, 352x288 */
+		cit_write_reg(gspca_dev, 0x00b9, 0x010a);	/* Unique to this mode */
+		cit_write_reg(gspca_dev, 0x0038, 0x0119);	/* Unique to this mode */
+		/* TESTME HDG: this does not seem right
+		   (it is 2 for all other resolutions) */
+		sd->sof_len = 10;
+		break;
+	case 320: /* 320x240 */
+		cit_write_reg(gspca_dev, 0x0028, 0x0103);	/* Unique to this mode */
+		cit_write_reg(gspca_dev, 0x0000, 0x0104);	/* Same */
+		cit_write_reg(gspca_dev, 0x001e, 0x0105);	/* 320x240, 352x240 */
+		cit_write_reg(gspca_dev, 0x0039, 0x010a);	/* All except 176x144 */
+		cit_write_reg(gspca_dev, 0x0070, 0x0119);	/* All except 176x144 */
+		sd->sof_len = 2;
+		break;
+#if 0
+	case VIDEOSIZE_352x240:
+		cit_write_reg(gspca_dev, 0x002c, 0x0103);	/* All except 320x240 */
+		cit_write_reg(gspca_dev, 0x0000, 0x0104);	/* Same */
+		cit_write_reg(gspca_dev, 0x001e, 0x0105);	/* 320x240, 352x240 */
+		cit_write_reg(gspca_dev, 0x0039, 0x010a);	/* All except 176x144 */
+		cit_write_reg(gspca_dev, 0x0070, 0x0119);	/* All except 176x144 */
+		sd->sof_len = 2;
+		break;
+#endif
+	case 352: /* 352x288 */
+		cit_write_reg(gspca_dev, 0x002c, 0x0103);	/* All except 320x240 */
+		cit_write_reg(gspca_dev, 0x0000, 0x0104);	/* Same */
+		cit_write_reg(gspca_dev, 0x0024, 0x0105);	/* 176x144, 352x288 */
+		cit_write_reg(gspca_dev, 0x0039, 0x010a);	/* All except 176x144 */
+		cit_write_reg(gspca_dev, 0x0070, 0x0119);	/* All except 176x144 */
+		sd->sof_len = 2;
+		break;
+	}
+
+	cit_write_reg(gspca_dev, 0x0000, 0x0100);	/* LED on */
+
+	switch (gspca_dev->pixfmt.width) {
+	case 176: /* 176x144 */
+		cit_write_reg(gspca_dev, 0x0050, 0x0111);
+		cit_write_reg(gspca_dev, 0x00d0, 0x0111);
+		break;
+	case 320: /* 320x240 */
+	case 352: /* 352x288 */
+		cit_write_reg(gspca_dev, 0x0040, 0x0111);
+		cit_write_reg(gspca_dev, 0x00c0, 0x0111);
+		break;
+	}
+	cit_write_reg(gspca_dev, 0x009b, 0x010f);
+	cit_write_reg(gspca_dev, 0x00bb, 0x010f);
+
+	/*
+	 * Hardware settings, may affect CMOS sensor; not user controls!
+	 * -------------------------------------------------------------
+	 * 0x0004: no effect
+	 * 0x0006: hardware effect
+	 * 0x0008: no effect
+	 * 0x000a: stops video stream, probably important h/w setting
+	 * 0x000c: changes color in hardware manner (not user setting)
+	 * 0x0012: changes number of colors (does not affect speed)
+	 * 0x002a: no effect
+	 * 0x002c: hardware setting (related to scan lines)
+	 * 0x002e: stops video stream, probably important h/w setting
+	 */
+	cit_model2_Packet1(gspca_dev, 0x000a, 0x005c);
+	cit_model2_Packet1(gspca_dev, 0x0004, 0x0000);
+	cit_model2_Packet1(gspca_dev, 0x0006, 0x00fb);
+	cit_model2_Packet1(gspca_dev, 0x0008, 0x0000);
+	cit_model2_Packet1(gspca_dev, 0x000c, 0x0009);
+	cit_model2_Packet1(gspca_dev, 0x0012, 0x000a);
+	cit_model2_Packet1(gspca_dev, 0x002a, 0x0000);
+	cit_model2_Packet1(gspca_dev, 0x002c, 0x0000);
+	cit_model2_Packet1(gspca_dev, 0x002e, 0x0008);
+
+	/*
+	 * Function 0x0030 pops up all over the place. Apparently
+	 * it is a hardware control register, with every bit assigned to
+	 * do something.
+	 */
+	cit_model2_Packet1(gspca_dev, 0x0030, 0x0000);
+
+	/*
+	 * Magic control of CMOS sensor. Only lower values like
+	 * 0-3 work, and picture shifts left or right. Don't change.
+	 */
+	switch (gspca_dev->pixfmt.width) {
+	case 176: /* 176x144 */
+		cit_model2_Packet1(gspca_dev, 0x0014, 0x0002);
+		cit_model2_Packet1(gspca_dev, 0x0016, 0x0002); /* Horizontal shift */
+		cit_model2_Packet1(gspca_dev, 0x0018, 0x004a); /* Another hardware setting */
+		clock_div = 6;
+		break;
+	case 320: /* 320x240 */
+		cit_model2_Packet1(gspca_dev, 0x0014, 0x0009);
+		cit_model2_Packet1(gspca_dev, 0x0016, 0x0005); /* Horizontal shift */
+		cit_model2_Packet1(gspca_dev, 0x0018, 0x0044); /* Another hardware setting */
+		clock_div = 8;
+		break;
+#if 0
+	case VIDEOSIZE_352x240:
+		/* This mode doesn't work as Windows programs it; changed to work */
+		cit_model2_Packet1(gspca_dev, 0x0014, 0x0009); /* Windows sets this to 8 */
+		cit_model2_Packet1(gspca_dev, 0x0016, 0x0003); /* Horizontal shift */
+		cit_model2_Packet1(gspca_dev, 0x0018, 0x0044); /* Windows sets this to 0x0045 */
+		clock_div = 10;
+		break;
+#endif
+	case 352: /* 352x288 */
+		cit_model2_Packet1(gspca_dev, 0x0014, 0x0003);
+		cit_model2_Packet1(gspca_dev, 0x0016, 0x0002); /* Horizontal shift */
+		cit_model2_Packet1(gspca_dev, 0x0018, 0x004a); /* Another hardware setting */
+		clock_div = 16;
+		break;
+	}
+
+	/* TESTME These are handled through controls
+	   KEEP until someone can test leaving this out is ok */
+	if (0)
+		cit_model2_Packet1(gspca_dev, 0x001a, 0x005a);
+
+	/*
+	 * We have our own frame rate setting varying from 0 (slowest) to 6
+	 * (fastest). The camera model 2 allows frame rate in range [0..0x1F]
+	 # where 0 is also the slowest setting. However for all practical
+	 # reasons high settings make no sense because USB is not fast enough
+	 # to support high FPS. Be aware that the picture datastream will be
+	 # severely disrupted if you ask for frame rate faster than allowed
+	 # for the video size - see below:
+	 *
+	 * Allowable ranges (obtained experimentally on OHCI, K6-3, 450 MHz):
+	 * -----------------------------------------------------------------
+	 * 176x144: [6..31]
+	 * 320x240: [8..31]
+	 * 352x240: [10..31]
+	 * 352x288: [16..31] I have to raise lower threshold for stability...
+	 *
+	 * As usual, slower FPS provides better sensitivity.
+	 */
+	cit_model2_Packet1(gspca_dev, 0x001c, clock_div);
+
+	/*
+	 * This setting does not visibly affect pictures; left it here
+	 * because it was present in Windows USB data stream. This function
+	 * does not allow arbitrary values and apparently is a bit mask, to
+	 * be activated only at appropriate time. Don't change it randomly!
+	 */
+	switch (gspca_dev->pixfmt.width) {
+	case 176: /* 176x144 */
+		cit_model2_Packet1(gspca_dev, 0x0026, 0x00c2);
+		break;
+	case 320: /* 320x240 */
+		cit_model2_Packet1(gspca_dev, 0x0026, 0x0044);
+		break;
+#if 0
+	case VIDEOSIZE_352x240:
+		cit_model2_Packet1(gspca_dev, 0x0026, 0x0046);
+		break;
+#endif
+	case 352: /* 352x288 */
+		cit_model2_Packet1(gspca_dev, 0x0026, 0x0048);
+		break;
+	}
+
+	cit_model2_Packet1(gspca_dev, 0x0028, v4l2_ctrl_g_ctrl(sd->lighting));
+	/* model2 cannot change the backlight compensation while streaming */
+	v4l2_ctrl_grab(sd->lighting, true);
+
+	/* color balance rg2 */
+	cit_model2_Packet1(gspca_dev, 0x001e, 0x002f);
+	/* saturation */
+	cit_model2_Packet1(gspca_dev, 0x0020, 0x0034);
+	/* color balance yb */
+	cit_model2_Packet1(gspca_dev, 0x0022, 0x00a0);
+
+	/* Hardware control command */
+	cit_model2_Packet1(gspca_dev, 0x0030, 0x0004);
+
+	return 0;
+}
+
+static int cit_start_model3(struct gspca_dev *gspca_dev)
+{
+	const unsigned short compression = 0; /* 0=none, 7=best frame rate */
+	int i, clock_div = 0;
+
+	/* HDG not in ibmcam driver, added to see if it helps with
+	   auto-detecting between model3 and ibm netcamera pro */
+	cit_read_reg(gspca_dev, 0x128, 1);
+
+	cit_write_reg(gspca_dev, 0x0000, 0x0100);
+	cit_read_reg(gspca_dev, 0x0116, 0);
+	cit_write_reg(gspca_dev, 0x0060, 0x0116);
+	cit_write_reg(gspca_dev, 0x0002, 0x0112);
+	cit_write_reg(gspca_dev, 0x0000, 0x0123);
+	cit_write_reg(gspca_dev, 0x0001, 0x0117);
+	cit_write_reg(gspca_dev, 0x0040, 0x0108);
+	cit_write_reg(gspca_dev, 0x0019, 0x012c);
+	cit_write_reg(gspca_dev, 0x0060, 0x0116);
+	cit_write_reg(gspca_dev, 0x0002, 0x0115);
+	cit_write_reg(gspca_dev, 0x0003, 0x0115);
+	cit_read_reg(gspca_dev, 0x0115, 0);
+	cit_write_reg(gspca_dev, 0x000b, 0x0115);
+
+	/* TESTME HDG not in ibmcam driver, added to see if it helps with
+	   auto-detecting between model3 and ibm netcamera pro */
+	if (0) {
+		cit_write_reg(gspca_dev, 0x0078, 0x012d);
+		cit_write_reg(gspca_dev, 0x0001, 0x012f);
+		cit_write_reg(gspca_dev, 0xd141, 0x0124);
+		cit_write_reg(gspca_dev, 0x0079, 0x012d);
+		cit_write_reg(gspca_dev, 0x00ff, 0x0130);
+		cit_write_reg(gspca_dev, 0xcd41, 0x0124);
+		cit_write_reg(gspca_dev, 0xfffa, 0x0124);
+		cit_read_reg(gspca_dev, 0x0126, 1);
+	}
+
+	cit_model3_Packet1(gspca_dev, 0x000a, 0x0040);
+	cit_model3_Packet1(gspca_dev, 0x000b, 0x00f6);
+	cit_model3_Packet1(gspca_dev, 0x000c, 0x0002);
+	cit_model3_Packet1(gspca_dev, 0x000d, 0x0020);
+	cit_model3_Packet1(gspca_dev, 0x000e, 0x0033);
+	cit_model3_Packet1(gspca_dev, 0x000f, 0x0007);
+	cit_model3_Packet1(gspca_dev, 0x0010, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0011, 0x0070);
+	cit_model3_Packet1(gspca_dev, 0x0012, 0x0030);
+	cit_model3_Packet1(gspca_dev, 0x0013, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0014, 0x0001);
+	cit_model3_Packet1(gspca_dev, 0x0015, 0x0001);
+	cit_model3_Packet1(gspca_dev, 0x0016, 0x0001);
+	cit_model3_Packet1(gspca_dev, 0x0017, 0x0001);
+	cit_model3_Packet1(gspca_dev, 0x0018, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x001e, 0x00c3);
+	cit_model3_Packet1(gspca_dev, 0x0020, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0028, 0x0010);
+	cit_model3_Packet1(gspca_dev, 0x0029, 0x0054);
+	cit_model3_Packet1(gspca_dev, 0x002a, 0x0013);
+	cit_model3_Packet1(gspca_dev, 0x002b, 0x0007);
+	cit_model3_Packet1(gspca_dev, 0x002d, 0x0028);
+	cit_model3_Packet1(gspca_dev, 0x002e, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0031, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0032, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0033, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0034, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0035, 0x0038);
+	cit_model3_Packet1(gspca_dev, 0x003a, 0x0001);
+	cit_model3_Packet1(gspca_dev, 0x003c, 0x001e);
+	cit_model3_Packet1(gspca_dev, 0x003f, 0x000a);
+	cit_model3_Packet1(gspca_dev, 0x0041, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0046, 0x003f);
+	cit_model3_Packet1(gspca_dev, 0x0047, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0050, 0x0005);
+	cit_model3_Packet1(gspca_dev, 0x0052, 0x001a);
+	cit_model3_Packet1(gspca_dev, 0x0053, 0x0003);
+	cit_model3_Packet1(gspca_dev, 0x005a, 0x006b);
+	cit_model3_Packet1(gspca_dev, 0x005d, 0x001e);
+	cit_model3_Packet1(gspca_dev, 0x005e, 0x0030);
+	cit_model3_Packet1(gspca_dev, 0x005f, 0x0041);
+	cit_model3_Packet1(gspca_dev, 0x0064, 0x0008);
+	cit_model3_Packet1(gspca_dev, 0x0065, 0x0015);
+	cit_model3_Packet1(gspca_dev, 0x0068, 0x000f);
+	cit_model3_Packet1(gspca_dev, 0x0079, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x007a, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x007c, 0x003f);
+	cit_model3_Packet1(gspca_dev, 0x0082, 0x000f);
+	cit_model3_Packet1(gspca_dev, 0x0085, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0099, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x009b, 0x0023);
+	cit_model3_Packet1(gspca_dev, 0x009c, 0x0022);
+	cit_model3_Packet1(gspca_dev, 0x009d, 0x0096);
+	cit_model3_Packet1(gspca_dev, 0x009e, 0x0096);
+	cit_model3_Packet1(gspca_dev, 0x009f, 0x000a);
+
+	switch (gspca_dev->pixfmt.width) {
+	case 160:
+		cit_write_reg(gspca_dev, 0x0000, 0x0101); /* Same on 160x120, 320x240 */
+		cit_write_reg(gspca_dev, 0x00a0, 0x0103); /* Same on 160x120, 320x240 */
+		cit_write_reg(gspca_dev, 0x0078, 0x0105); /* Same on 160x120, 320x240 */
+		cit_write_reg(gspca_dev, 0x0000, 0x010a); /* Same */
+		cit_write_reg(gspca_dev, 0x0024, 0x010b); /* Differs everywhere */
+		cit_write_reg(gspca_dev, 0x00a9, 0x0119);
+		cit_write_reg(gspca_dev, 0x0016, 0x011b);
+		cit_write_reg(gspca_dev, 0x0002, 0x011d); /* Same on 160x120, 320x240 */
+		cit_write_reg(gspca_dev, 0x0003, 0x011e); /* Same on 160x120, 640x480 */
+		cit_write_reg(gspca_dev, 0x0000, 0x0129); /* Same */
+		cit_write_reg(gspca_dev, 0x00fc, 0x012b); /* Same */
+		cit_write_reg(gspca_dev, 0x0018, 0x0102);
+		cit_write_reg(gspca_dev, 0x0004, 0x0104);
+		cit_write_reg(gspca_dev, 0x0004, 0x011a);
+		cit_write_reg(gspca_dev, 0x0028, 0x011c);
+		cit_write_reg(gspca_dev, 0x0022, 0x012a); /* Same */
+		cit_write_reg(gspca_dev, 0x0000, 0x0118);
+		cit_write_reg(gspca_dev, 0x0000, 0x0132);
+		cit_model3_Packet1(gspca_dev, 0x0021, 0x0001); /* Same */
+		cit_write_reg(gspca_dev, compression, 0x0109);
+		clock_div = 3;
+		break;
+	case 320:
+		cit_write_reg(gspca_dev, 0x0000, 0x0101); /* Same on 160x120, 320x240 */
+		cit_write_reg(gspca_dev, 0x00a0, 0x0103); /* Same on 160x120, 320x240 */
+		cit_write_reg(gspca_dev, 0x0078, 0x0105); /* Same on 160x120, 320x240 */
+		cit_write_reg(gspca_dev, 0x0000, 0x010a); /* Same */
+		cit_write_reg(gspca_dev, 0x0028, 0x010b); /* Differs everywhere */
+		cit_write_reg(gspca_dev, 0x0002, 0x011d); /* Same */
+		cit_write_reg(gspca_dev, 0x0000, 0x011e);
+		cit_write_reg(gspca_dev, 0x0000, 0x0129); /* Same */
+		cit_write_reg(gspca_dev, 0x00fc, 0x012b); /* Same */
+		/* 4 commands from 160x120 skipped */
+		cit_write_reg(gspca_dev, 0x0022, 0x012a); /* Same */
+		cit_model3_Packet1(gspca_dev, 0x0021, 0x0001); /* Same */
+		cit_write_reg(gspca_dev, compression, 0x0109);
+		cit_write_reg(gspca_dev, 0x00d9, 0x0119);
+		cit_write_reg(gspca_dev, 0x0006, 0x011b);
+		cit_write_reg(gspca_dev, 0x0021, 0x0102); /* Same on 320x240, 640x480 */
+		cit_write_reg(gspca_dev, 0x0010, 0x0104);
+		cit_write_reg(gspca_dev, 0x0004, 0x011a);
+		cit_write_reg(gspca_dev, 0x003f, 0x011c);
+		cit_write_reg(gspca_dev, 0x001c, 0x0118);
+		cit_write_reg(gspca_dev, 0x0000, 0x0132);
+		clock_div = 5;
+		break;
+	case 640:
+		cit_write_reg(gspca_dev, 0x00f0, 0x0105);
+		cit_write_reg(gspca_dev, 0x0000, 0x010a); /* Same */
+		cit_write_reg(gspca_dev, 0x0038, 0x010b); /* Differs everywhere */
+		cit_write_reg(gspca_dev, 0x00d9, 0x0119); /* Same on 320x240, 640x480 */
+		cit_write_reg(gspca_dev, 0x0006, 0x011b); /* Same on 320x240, 640x480 */
+		cit_write_reg(gspca_dev, 0x0004, 0x011d); /* NC */
+		cit_write_reg(gspca_dev, 0x0003, 0x011e); /* Same on 160x120, 640x480 */
+		cit_write_reg(gspca_dev, 0x0000, 0x0129); /* Same */
+		cit_write_reg(gspca_dev, 0x00fc, 0x012b); /* Same */
+		cit_write_reg(gspca_dev, 0x0021, 0x0102); /* Same on 320x240, 640x480 */
+		cit_write_reg(gspca_dev, 0x0016, 0x0104); /* NC */
+		cit_write_reg(gspca_dev, 0x0004, 0x011a); /* Same on 320x240, 640x480 */
+		cit_write_reg(gspca_dev, 0x003f, 0x011c); /* Same on 320x240, 640x480 */
+		cit_write_reg(gspca_dev, 0x0022, 0x012a); /* Same */
+		cit_write_reg(gspca_dev, 0x001c, 0x0118); /* Same on 320x240, 640x480 */
+		cit_model3_Packet1(gspca_dev, 0x0021, 0x0001); /* Same */
+		cit_write_reg(gspca_dev, compression, 0x0109);
+		cit_write_reg(gspca_dev, 0x0040, 0x0101);
+		cit_write_reg(gspca_dev, 0x0040, 0x0103);
+		cit_write_reg(gspca_dev, 0x0000, 0x0132); /* Same on 320x240, 640x480 */
+		clock_div = 7;
+		break;
+	}
+
+	cit_model3_Packet1(gspca_dev, 0x007e, 0x000e);	/* Hue */
+	cit_model3_Packet1(gspca_dev, 0x0036, 0x0011);	/* Brightness */
+	cit_model3_Packet1(gspca_dev, 0x0060, 0x0002);	/* Sharpness */
+	cit_model3_Packet1(gspca_dev, 0x0061, 0x0004);	/* Sharpness */
+	cit_model3_Packet1(gspca_dev, 0x0062, 0x0005);	/* Sharpness */
+	cit_model3_Packet1(gspca_dev, 0x0063, 0x0014);	/* Sharpness */
+	cit_model3_Packet1(gspca_dev, 0x0096, 0x00a0);	/* Red sharpness */
+	cit_model3_Packet1(gspca_dev, 0x0097, 0x0096);	/* Blue sharpness */
+	cit_model3_Packet1(gspca_dev, 0x0067, 0x0001);	/* Contrast */
+	cit_model3_Packet1(gspca_dev, 0x005b, 0x000c);	/* Contrast */
+	cit_model3_Packet1(gspca_dev, 0x005c, 0x0016);	/* Contrast */
+	cit_model3_Packet1(gspca_dev, 0x0098, 0x000b);
+	cit_model3_Packet1(gspca_dev, 0x002c, 0x0003);	/* Was 1, broke 640x480 */
+	cit_model3_Packet1(gspca_dev, 0x002f, 0x002a);
+	cit_model3_Packet1(gspca_dev, 0x0030, 0x0029);
+	cit_model3_Packet1(gspca_dev, 0x0037, 0x0002);
+	cit_model3_Packet1(gspca_dev, 0x0038, 0x0059);
+	cit_model3_Packet1(gspca_dev, 0x003d, 0x002e);
+	cit_model3_Packet1(gspca_dev, 0x003e, 0x0028);
+	cit_model3_Packet1(gspca_dev, 0x0078, 0x0005);
+	cit_model3_Packet1(gspca_dev, 0x007b, 0x0011);
+	cit_model3_Packet1(gspca_dev, 0x007d, 0x004b);
+	cit_model3_Packet1(gspca_dev, 0x007f, 0x0022);
+	cit_model3_Packet1(gspca_dev, 0x0080, 0x000c);
+	cit_model3_Packet1(gspca_dev, 0x0081, 0x000b);
+	cit_model3_Packet1(gspca_dev, 0x0083, 0x00fd);
+	cit_model3_Packet1(gspca_dev, 0x0086, 0x000b);
+	cit_model3_Packet1(gspca_dev, 0x0087, 0x000b);
+	cit_model3_Packet1(gspca_dev, 0x007e, 0x000e);
+	cit_model3_Packet1(gspca_dev, 0x0096, 0x00a0);	/* Red sharpness */
+	cit_model3_Packet1(gspca_dev, 0x0097, 0x0096);	/* Blue sharpness */
+	cit_model3_Packet1(gspca_dev, 0x0098, 0x000b);
+
+	/* FIXME we should probably use cit_get_clock_div() here (in
+	   combination with isoc negotiation using the programmable isoc size)
+	   like with the IBM netcam pro). */
+	cit_write_reg(gspca_dev, clock_div, 0x0111); /* Clock Divider */
+
+	switch (gspca_dev->pixfmt.width) {
+	case 160:
+		cit_model3_Packet1(gspca_dev, 0x001f, 0x0000); /* Same */
+		cit_model3_Packet1(gspca_dev, 0x0039, 0x001f); /* Same */
+		cit_model3_Packet1(gspca_dev, 0x003b, 0x003c); /* Same */
+		cit_model3_Packet1(gspca_dev, 0x0040, 0x000a);
+		cit_model3_Packet1(gspca_dev, 0x0051, 0x000a);
+		break;
+	case 320:
+		cit_model3_Packet1(gspca_dev, 0x001f, 0x0000); /* Same */
+		cit_model3_Packet1(gspca_dev, 0x0039, 0x001f); /* Same */
+		cit_model3_Packet1(gspca_dev, 0x003b, 0x003c); /* Same */
+		cit_model3_Packet1(gspca_dev, 0x0040, 0x0008);
+		cit_model3_Packet1(gspca_dev, 0x0051, 0x000b);
+		break;
+	case 640:
+		cit_model3_Packet1(gspca_dev, 0x001f, 0x0002);	/* !Same */
+		cit_model3_Packet1(gspca_dev, 0x0039, 0x003e);	/* !Same */
+		cit_model3_Packet1(gspca_dev, 0x0040, 0x0008);
+		cit_model3_Packet1(gspca_dev, 0x0051, 0x000a);
+		break;
+	}
+
+/*	if (sd->input_index) { */
+	if (rca_input) {
+		for (i = 0; i < ARRAY_SIZE(rca_initdata); i++) {
+			if (rca_initdata[i][0])
+				cit_read_reg(gspca_dev, rca_initdata[i][2], 0);
+			else
+				cit_write_reg(gspca_dev, rca_initdata[i][1],
+					      rca_initdata[i][2]);
+		}
+	}
+
+	return 0;
+}
+
+static int cit_start_model4(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	cit_write_reg(gspca_dev, 0x0000, 0x0100);
+	cit_write_reg(gspca_dev, 0x00c0, 0x0111);
+	cit_write_reg(gspca_dev, 0x00bc, 0x012c);
+	cit_write_reg(gspca_dev, 0x0080, 0x012b);
+	cit_write_reg(gspca_dev, 0x0000, 0x0108);
+	cit_write_reg(gspca_dev, 0x0001, 0x0133);
+	cit_write_reg(gspca_dev, 0x009b, 0x010f);
+	cit_write_reg(gspca_dev, 0x00bb, 0x010f);
+	cit_model4_Packet1(gspca_dev, 0x0038, 0x0000);
+	cit_model4_Packet1(gspca_dev, 0x000a, 0x005c);
+
+	cit_write_reg(gspca_dev, 0x00aa, 0x012d);
+	cit_write_reg(gspca_dev, 0x0004, 0x012f);
+	cit_write_reg(gspca_dev, 0xd141, 0x0124);
+	cit_write_reg(gspca_dev, 0x0000, 0x0127);
+	cit_write_reg(gspca_dev, 0x00fb, 0x012e);
+	cit_write_reg(gspca_dev, 0x0000, 0x0130);
+	cit_write_reg(gspca_dev, 0x8a28, 0x0124);
+	cit_write_reg(gspca_dev, 0x00aa, 0x012f);
+	cit_write_reg(gspca_dev, 0xd055, 0x0124);
+	cit_write_reg(gspca_dev, 0x000c, 0x0127);
+	cit_write_reg(gspca_dev, 0x0009, 0x012e);
+	cit_write_reg(gspca_dev, 0xaa28, 0x0124);
+
+	cit_write_reg(gspca_dev, 0x00aa, 0x012d);
+	cit_write_reg(gspca_dev, 0x0012, 0x012f);
+	cit_write_reg(gspca_dev, 0xd141, 0x0124);
+	cit_write_reg(gspca_dev, 0x0008, 0x0127);
+	cit_write_reg(gspca_dev, 0x00aa, 0x0130);
+	cit_write_reg(gspca_dev, 0x82a8, 0x0124);
+	cit_write_reg(gspca_dev, 0x002a, 0x012d);
+	cit_write_reg(gspca_dev, 0x0000, 0x012f);
+	cit_write_reg(gspca_dev, 0xd145, 0x0124);
+	cit_write_reg(gspca_dev, 0xfffa, 0x0124);
+	cit_model4_Packet1(gspca_dev, 0x0034, 0x0000);
+
+	switch (gspca_dev->pixfmt.width) {
+	case 128: /* 128x96 */
+		cit_write_reg(gspca_dev, 0x0070, 0x0119);
+		cit_write_reg(gspca_dev, 0x00d0, 0x0111);
+		cit_write_reg(gspca_dev, 0x0039, 0x010a);
+		cit_write_reg(gspca_dev, 0x0001, 0x0102);
+		cit_write_reg(gspca_dev, 0x0028, 0x0103);
+		cit_write_reg(gspca_dev, 0x0000, 0x0104);
+		cit_write_reg(gspca_dev, 0x001e, 0x0105);
+		cit_write_reg(gspca_dev, 0x00aa, 0x012d);
+		cit_write_reg(gspca_dev, 0x0016, 0x012f);
+		cit_write_reg(gspca_dev, 0xd141, 0x0124);
+		cit_write_reg(gspca_dev, 0x000a, 0x0127);
+		cit_write_reg(gspca_dev, 0x00aa, 0x0130);
+		cit_write_reg(gspca_dev, 0x82a8, 0x0124);
+		cit_write_reg(gspca_dev, 0x0014, 0x012d);
+		cit_write_reg(gspca_dev, 0x0008, 0x012f);
+		cit_write_reg(gspca_dev, 0xd145, 0x0124);
+		cit_write_reg(gspca_dev, 0x00aa, 0x012e);
+		cit_write_reg(gspca_dev, 0x001a, 0x0130);
+		cit_write_reg(gspca_dev, 0x8a0a, 0x0124);
+		cit_write_reg(gspca_dev, 0x005a, 0x012d);
+		cit_write_reg(gspca_dev, 0x9545, 0x0124);
+		cit_write_reg(gspca_dev, 0x00aa, 0x0127);
+		cit_write_reg(gspca_dev, 0x0018, 0x012e);
+		cit_write_reg(gspca_dev, 0x0043, 0x0130);
+		cit_write_reg(gspca_dev, 0x8a28, 0x0124);
+		cit_write_reg(gspca_dev, 0x00aa, 0x012f);
+		cit_write_reg(gspca_dev, 0xd055, 0x0124);
+		cit_write_reg(gspca_dev, 0x001c, 0x0127);
+		cit_write_reg(gspca_dev, 0x00eb, 0x012e);
+		cit_write_reg(gspca_dev, 0xaa28, 0x0124);
+		cit_write_reg(gspca_dev, 0x00aa, 0x012d);
+		cit_write_reg(gspca_dev, 0x0032, 0x012f);
+		cit_write_reg(gspca_dev, 0xd141, 0x0124);
+		cit_write_reg(gspca_dev, 0x0000, 0x0127);
+		cit_write_reg(gspca_dev, 0x00aa, 0x0130);
+		cit_write_reg(gspca_dev, 0x82a8, 0x0124);
+		cit_write_reg(gspca_dev, 0x0036, 0x012d);
+		cit_write_reg(gspca_dev, 0x0008, 0x012f);
+		cit_write_reg(gspca_dev, 0xd145, 0x0124);
+		cit_write_reg(gspca_dev, 0xfffa, 0x0124);
+		cit_write_reg(gspca_dev, 0x00aa, 0x012d);
+		cit_write_reg(gspca_dev, 0x001e, 0x012f);
+		cit_write_reg(gspca_dev, 0xd141, 0x0124);
+		cit_write_reg(gspca_dev, 0x0017, 0x0127);
+		cit_write_reg(gspca_dev, 0x0013, 0x012e);
+		cit_write_reg(gspca_dev, 0x0031, 0x0130);
+		cit_write_reg(gspca_dev, 0x8a28, 0x0124);
+		cit_write_reg(gspca_dev, 0x0017, 0x012d);
+		cit_write_reg(gspca_dev, 0x0078, 0x012f);
+		cit_write_reg(gspca_dev, 0xd145, 0x0124);
+		cit_write_reg(gspca_dev, 0x0000, 0x0127);
+		cit_write_reg(gspca_dev, 0xfea8, 0x0124);
+		sd->sof_len = 2;
+		break;
+	case 160: /* 160x120 */
+		cit_write_reg(gspca_dev, 0x0038, 0x0119);
+		cit_write_reg(gspca_dev, 0x00d0, 0x0111);
+		cit_write_reg(gspca_dev, 0x00b9, 0x010a);
+		cit_write_reg(gspca_dev, 0x0001, 0x0102);
+		cit_write_reg(gspca_dev, 0x0028, 0x0103);
+		cit_write_reg(gspca_dev, 0x0000, 0x0104);
+		cit_write_reg(gspca_dev, 0x001e, 0x0105);
+		cit_write_reg(gspca_dev, 0x00aa, 0x012d);
+		cit_write_reg(gspca_dev, 0x0016, 0x012f);
+		cit_write_reg(gspca_dev, 0xd141, 0x0124);
+		cit_write_reg(gspca_dev, 0x000b, 0x0127);
+		cit_write_reg(gspca_dev, 0x00aa, 0x0130);
+		cit_write_reg(gspca_dev, 0x82a8, 0x0124);
+		cit_write_reg(gspca_dev, 0x0014, 0x012d);
+		cit_write_reg(gspca_dev, 0x0008, 0x012f);
+		cit_write_reg(gspca_dev, 0xd145, 0x0124);
+		cit_write_reg(gspca_dev, 0x00aa, 0x012e);
+		cit_write_reg(gspca_dev, 0x001a, 0x0130);
+		cit_write_reg(gspca_dev, 0x8a0a, 0x0124);
+		cit_write_reg(gspca_dev, 0x005a, 0x012d);
+		cit_write_reg(gspca_dev, 0x9545, 0x0124);
+		cit_write_reg(gspca_dev, 0x00aa, 0x0127);
+		cit_write_reg(gspca_dev, 0x0018, 0x012e);
+		cit_write_reg(gspca_dev, 0x0043, 0x0130);
+		cit_write_reg(gspca_dev, 0x8a28, 0x0124);
+		cit_write_reg(gspca_dev, 0x00aa, 0x012f);
+		cit_write_reg(gspca_dev, 0xd055, 0x0124);
+		cit_write_reg(gspca_dev, 0x001c, 0x0127);
+		cit_write_reg(gspca_dev, 0x00c7, 0x012e);
+		cit_write_reg(gspca_dev, 0xaa28, 0x0124);
+		cit_write_reg(gspca_dev, 0x00aa, 0x012d);
+		cit_write_reg(gspca_dev, 0x0032, 0x012f);
+		cit_write_reg(gspca_dev, 0xd141, 0x0124);
+		cit_write_reg(gspca_dev, 0x0025, 0x0127);
+		cit_write_reg(gspca_dev, 0x00aa, 0x0130);
+		cit_write_reg(gspca_dev, 0x82a8, 0x0124);
+		cit_write_reg(gspca_dev, 0x0036, 0x012d);
+		cit_write_reg(gspca_dev, 0x0008, 0x012f);
+		cit_write_reg(gspca_dev, 0xd145, 0x0124);
+		cit_write_reg(gspca_dev, 0xfffa, 0x0124);
+		cit_write_reg(gspca_dev, 0x00aa, 0x012d);
+		cit_write_reg(gspca_dev, 0x001e, 0x012f);
+		cit_write_reg(gspca_dev, 0xd141, 0x0124);
+		cit_write_reg(gspca_dev, 0x0048, 0x0127);
+		cit_write_reg(gspca_dev, 0x0035, 0x012e);
+		cit_write_reg(gspca_dev, 0x00d0, 0x0130);
+		cit_write_reg(gspca_dev, 0x8a28, 0x0124);
+		cit_write_reg(gspca_dev, 0x0048, 0x012d);
+		cit_write_reg(gspca_dev, 0x0090, 0x012f);
+		cit_write_reg(gspca_dev, 0xd145, 0x0124);
+		cit_write_reg(gspca_dev, 0x0001, 0x0127);
+		cit_write_reg(gspca_dev, 0xfea8, 0x0124);
+		sd->sof_len = 2;
+		break;
+	case 176: /* 176x144 */
+		cit_write_reg(gspca_dev, 0x0038, 0x0119);
+		cit_write_reg(gspca_dev, 0x00d0, 0x0111);
+		cit_write_reg(gspca_dev, 0x00b9, 0x010a);
+		cit_write_reg(gspca_dev, 0x0001, 0x0102);
+		cit_write_reg(gspca_dev, 0x002c, 0x0103);
+		cit_write_reg(gspca_dev, 0x0000, 0x0104);
+		cit_write_reg(gspca_dev, 0x0024, 0x0105);
+		cit_write_reg(gspca_dev, 0x00aa, 0x012d);
+		cit_write_reg(gspca_dev, 0x0016, 0x012f);
+		cit_write_reg(gspca_dev, 0xd141, 0x0124);
+		cit_write_reg(gspca_dev, 0x0007, 0x0127);
+		cit_write_reg(gspca_dev, 0x00aa, 0x0130);
+		cit_write_reg(gspca_dev, 0x82a8, 0x0124);
+		cit_write_reg(gspca_dev, 0x0014, 0x012d);
+		cit_write_reg(gspca_dev, 0x0001, 0x012f);
+		cit_write_reg(gspca_dev, 0xd145, 0x0124);
+		cit_write_reg(gspca_dev, 0x00aa, 0x012e);
+		cit_write_reg(gspca_dev, 0x001a, 0x0130);
+		cit_write_reg(gspca_dev, 0x8a0a, 0x0124);
+		cit_write_reg(gspca_dev, 0x005e, 0x012d);
+		cit_write_reg(gspca_dev, 0x9545, 0x0124);
+		cit_write_reg(gspca_dev, 0x00aa, 0x0127);
+		cit_write_reg(gspca_dev, 0x0018, 0x012e);
+		cit_write_reg(gspca_dev, 0x0049, 0x0130);
+		cit_write_reg(gspca_dev, 0x8a28, 0x0124);
+		cit_write_reg(gspca_dev, 0x00aa, 0x012f);
+		cit_write_reg(gspca_dev, 0xd055, 0x0124);
+		cit_write_reg(gspca_dev, 0x001c, 0x0127);
+		cit_write_reg(gspca_dev, 0x00c7, 0x012e);
+		cit_write_reg(gspca_dev, 0xaa28, 0x0124);
+		cit_write_reg(gspca_dev, 0x00aa, 0x012d);
+		cit_write_reg(gspca_dev, 0x0032, 0x012f);
+		cit_write_reg(gspca_dev, 0xd141, 0x0124);
+		cit_write_reg(gspca_dev, 0x0028, 0x0127);
+		cit_write_reg(gspca_dev, 0x00aa, 0x0130);
+		cit_write_reg(gspca_dev, 0x82a8, 0x0124);
+		cit_write_reg(gspca_dev, 0x0036, 0x012d);
+		cit_write_reg(gspca_dev, 0x0008, 0x012f);
+		cit_write_reg(gspca_dev, 0xd145, 0x0124);
+		cit_write_reg(gspca_dev, 0xfffa, 0x0124);
+		cit_write_reg(gspca_dev, 0x00aa, 0x012d);
+		cit_write_reg(gspca_dev, 0x001e, 0x012f);
+		cit_write_reg(gspca_dev, 0xd141, 0x0124);
+		cit_write_reg(gspca_dev, 0x0010, 0x0127);
+		cit_write_reg(gspca_dev, 0x0013, 0x012e);
+		cit_write_reg(gspca_dev, 0x002a, 0x0130);
+		cit_write_reg(gspca_dev, 0x8a28, 0x0124);
+		cit_write_reg(gspca_dev, 0x0010, 0x012d);
+		cit_write_reg(gspca_dev, 0x006d, 0x012f);
+		cit_write_reg(gspca_dev, 0xd145, 0x0124);
+		cit_write_reg(gspca_dev, 0x0001, 0x0127);
+		cit_write_reg(gspca_dev, 0xfea8, 0x0124);
+		/* TESTME HDG: this does not seem right
+		   (it is 2 for all other resolutions) */
+		sd->sof_len = 10;
+		break;
+	case 320: /* 320x240 */
+		cit_write_reg(gspca_dev, 0x0070, 0x0119);
+		cit_write_reg(gspca_dev, 0x00d0, 0x0111);
+		cit_write_reg(gspca_dev, 0x0039, 0x010a);
+		cit_write_reg(gspca_dev, 0x0001, 0x0102);
+		cit_write_reg(gspca_dev, 0x0028, 0x0103);
+		cit_write_reg(gspca_dev, 0x0000, 0x0104);
+		cit_write_reg(gspca_dev, 0x001e, 0x0105);
+		cit_write_reg(gspca_dev, 0x00aa, 0x012d);
+		cit_write_reg(gspca_dev, 0x0016, 0x012f);
+		cit_write_reg(gspca_dev, 0xd141, 0x0124);
+		cit_write_reg(gspca_dev, 0x000a, 0x0127);
+		cit_write_reg(gspca_dev, 0x00aa, 0x0130);
+		cit_write_reg(gspca_dev, 0x82a8, 0x0124);
+		cit_write_reg(gspca_dev, 0x0014, 0x012d);
+		cit_write_reg(gspca_dev, 0x0008, 0x012f);
+		cit_write_reg(gspca_dev, 0xd145, 0x0124);
+		cit_write_reg(gspca_dev, 0x00aa, 0x012e);
+		cit_write_reg(gspca_dev, 0x001a, 0x0130);
+		cit_write_reg(gspca_dev, 0x8a0a, 0x0124);
+		cit_write_reg(gspca_dev, 0x005a, 0x012d);
+		cit_write_reg(gspca_dev, 0x9545, 0x0124);
+		cit_write_reg(gspca_dev, 0x00aa, 0x0127);
+		cit_write_reg(gspca_dev, 0x0018, 0x012e);
+		cit_write_reg(gspca_dev, 0x0043, 0x0130);
+		cit_write_reg(gspca_dev, 0x8a28, 0x0124);
+		cit_write_reg(gspca_dev, 0x00aa, 0x012f);
+		cit_write_reg(gspca_dev, 0xd055, 0x0124);
+		cit_write_reg(gspca_dev, 0x001c, 0x0127);
+		cit_write_reg(gspca_dev, 0x00eb, 0x012e);
+		cit_write_reg(gspca_dev, 0xaa28, 0x0124);
+		cit_write_reg(gspca_dev, 0x00aa, 0x012d);
+		cit_write_reg(gspca_dev, 0x0032, 0x012f);
+		cit_write_reg(gspca_dev, 0xd141, 0x0124);
+		cit_write_reg(gspca_dev, 0x0000, 0x0127);
+		cit_write_reg(gspca_dev, 0x00aa, 0x0130);
+		cit_write_reg(gspca_dev, 0x82a8, 0x0124);
+		cit_write_reg(gspca_dev, 0x0036, 0x012d);
+		cit_write_reg(gspca_dev, 0x0008, 0x012f);
+		cit_write_reg(gspca_dev, 0xd145, 0x0124);
+		cit_write_reg(gspca_dev, 0xfffa, 0x0124);
+		cit_write_reg(gspca_dev, 0x00aa, 0x012d);
+		cit_write_reg(gspca_dev, 0x001e, 0x012f);
+		cit_write_reg(gspca_dev, 0xd141, 0x0124);
+		cit_write_reg(gspca_dev, 0x0017, 0x0127);
+		cit_write_reg(gspca_dev, 0x0013, 0x012e);
+		cit_write_reg(gspca_dev, 0x0031, 0x0130);
+		cit_write_reg(gspca_dev, 0x8a28, 0x0124);
+		cit_write_reg(gspca_dev, 0x0017, 0x012d);
+		cit_write_reg(gspca_dev, 0x0078, 0x012f);
+		cit_write_reg(gspca_dev, 0xd145, 0x0124);
+		cit_write_reg(gspca_dev, 0x0000, 0x0127);
+		cit_write_reg(gspca_dev, 0xfea8, 0x0124);
+		sd->sof_len = 2;
+		break;
+	case 352: /* 352x288 */
+		cit_write_reg(gspca_dev, 0x0070, 0x0119);
+		cit_write_reg(gspca_dev, 0x00c0, 0x0111);
+		cit_write_reg(gspca_dev, 0x0039, 0x010a);
+		cit_write_reg(gspca_dev, 0x0001, 0x0102);
+		cit_write_reg(gspca_dev, 0x002c, 0x0103);
+		cit_write_reg(gspca_dev, 0x0000, 0x0104);
+		cit_write_reg(gspca_dev, 0x0024, 0x0105);
+		cit_write_reg(gspca_dev, 0x00aa, 0x012d);
+		cit_write_reg(gspca_dev, 0x0016, 0x012f);
+		cit_write_reg(gspca_dev, 0xd141, 0x0124);
+		cit_write_reg(gspca_dev, 0x0006, 0x0127);
+		cit_write_reg(gspca_dev, 0x00aa, 0x0130);
+		cit_write_reg(gspca_dev, 0x82a8, 0x0124);
+		cit_write_reg(gspca_dev, 0x0014, 0x012d);
+		cit_write_reg(gspca_dev, 0x0002, 0x012f);
+		cit_write_reg(gspca_dev, 0xd145, 0x0124);
+		cit_write_reg(gspca_dev, 0x00aa, 0x012e);
+		cit_write_reg(gspca_dev, 0x001a, 0x0130);
+		cit_write_reg(gspca_dev, 0x8a0a, 0x0124);
+		cit_write_reg(gspca_dev, 0x005e, 0x012d);
+		cit_write_reg(gspca_dev, 0x9545, 0x0124);
+		cit_write_reg(gspca_dev, 0x00aa, 0x0127);
+		cit_write_reg(gspca_dev, 0x0018, 0x012e);
+		cit_write_reg(gspca_dev, 0x0049, 0x0130);
+		cit_write_reg(gspca_dev, 0x8a28, 0x0124);
+		cit_write_reg(gspca_dev, 0x00aa, 0x012f);
+		cit_write_reg(gspca_dev, 0xd055, 0x0124);
+		cit_write_reg(gspca_dev, 0x001c, 0x0127);
+		cit_write_reg(gspca_dev, 0x00cf, 0x012e);
+		cit_write_reg(gspca_dev, 0xaa28, 0x0124);
+		cit_write_reg(gspca_dev, 0x00aa, 0x012d);
+		cit_write_reg(gspca_dev, 0x0032, 0x012f);
+		cit_write_reg(gspca_dev, 0xd141, 0x0124);
+		cit_write_reg(gspca_dev, 0x0000, 0x0127);
+		cit_write_reg(gspca_dev, 0x00aa, 0x0130);
+		cit_write_reg(gspca_dev, 0x82a8, 0x0124);
+		cit_write_reg(gspca_dev, 0x0036, 0x012d);
+		cit_write_reg(gspca_dev, 0x0008, 0x012f);
+		cit_write_reg(gspca_dev, 0xd145, 0x0124);
+		cit_write_reg(gspca_dev, 0xfffa, 0x0124);
+		cit_write_reg(gspca_dev, 0x00aa, 0x012d);
+		cit_write_reg(gspca_dev, 0x001e, 0x012f);
+		cit_write_reg(gspca_dev, 0xd141, 0x0124);
+		cit_write_reg(gspca_dev, 0x0010, 0x0127);
+		cit_write_reg(gspca_dev, 0x0013, 0x012e);
+		cit_write_reg(gspca_dev, 0x0025, 0x0130);
+		cit_write_reg(gspca_dev, 0x8a28, 0x0124);
+		cit_write_reg(gspca_dev, 0x0010, 0x012d);
+		cit_write_reg(gspca_dev, 0x0048, 0x012f);
+		cit_write_reg(gspca_dev, 0xd145, 0x0124);
+		cit_write_reg(gspca_dev, 0x0000, 0x0127);
+		cit_write_reg(gspca_dev, 0xfea8, 0x0124);
+		sd->sof_len = 2;
+		break;
+	}
+
+	cit_model4_Packet1(gspca_dev, 0x0038, 0x0004);
+
+	return 0;
+}
+
+static int cit_start_ibm_netcam_pro(struct gspca_dev *gspca_dev)
+{
+	const unsigned short compression = 0; /* 0=none, 7=best frame rate */
+	int i, clock_div;
+
+	clock_div = cit_get_clock_div(gspca_dev);
+	if (clock_div < 0)
+		return clock_div;
+
+	cit_write_reg(gspca_dev, 0x0003, 0x0133);
+	cit_write_reg(gspca_dev, 0x0000, 0x0117);
+	cit_write_reg(gspca_dev, 0x0008, 0x0123);
+	cit_write_reg(gspca_dev, 0x0000, 0x0100);
+	cit_write_reg(gspca_dev, 0x0060, 0x0116);
+	/* cit_write_reg(gspca_dev, 0x0002, 0x0112); see sd_stop0 */
+	cit_write_reg(gspca_dev, 0x0000, 0x0133);
+	cit_write_reg(gspca_dev, 0x0000, 0x0123);
+	cit_write_reg(gspca_dev, 0x0001, 0x0117);
+	cit_write_reg(gspca_dev, 0x0040, 0x0108);
+	cit_write_reg(gspca_dev, 0x0019, 0x012c);
+	cit_write_reg(gspca_dev, 0x0060, 0x0116);
+	/* cit_write_reg(gspca_dev, 0x000b, 0x0115); see sd_stop0 */
+
+	cit_model3_Packet1(gspca_dev, 0x0049, 0x0000);
+
+	cit_write_reg(gspca_dev, 0x0000, 0x0101); /* Same on 160x120, 320x240 */
+	cit_write_reg(gspca_dev, 0x003a, 0x0102); /* Hstart */
+	cit_write_reg(gspca_dev, 0x00a0, 0x0103); /* Same on 160x120, 320x240 */
+	cit_write_reg(gspca_dev, 0x0078, 0x0105); /* Same on 160x120, 320x240 */
+	cit_write_reg(gspca_dev, 0x0000, 0x010a); /* Same */
+	cit_write_reg(gspca_dev, 0x0002, 0x011d); /* Same on 160x120, 320x240 */
+	cit_write_reg(gspca_dev, 0x0000, 0x0129); /* Same */
+	cit_write_reg(gspca_dev, 0x00fc, 0x012b); /* Same */
+	cit_write_reg(gspca_dev, 0x0022, 0x012a); /* Same */
+
+	switch (gspca_dev->pixfmt.width) {
+	case 160: /* 160x120 */
+		cit_write_reg(gspca_dev, 0x0024, 0x010b);
+		cit_write_reg(gspca_dev, 0x0089, 0x0119);
+		cit_write_reg(gspca_dev, 0x000a, 0x011b);
+		cit_write_reg(gspca_dev, 0x0003, 0x011e);
+		cit_write_reg(gspca_dev, 0x0007, 0x0104);
+		cit_write_reg(gspca_dev, 0x0009, 0x011a);
+		cit_write_reg(gspca_dev, 0x008b, 0x011c);
+		cit_write_reg(gspca_dev, 0x0008, 0x0118);
+		cit_write_reg(gspca_dev, 0x0000, 0x0132);
+		break;
+	case 320: /* 320x240 */
+		cit_write_reg(gspca_dev, 0x0028, 0x010b);
+		cit_write_reg(gspca_dev, 0x00d9, 0x0119);
+		cit_write_reg(gspca_dev, 0x0006, 0x011b);
+		cit_write_reg(gspca_dev, 0x0000, 0x011e);
+		cit_write_reg(gspca_dev, 0x000e, 0x0104);
+		cit_write_reg(gspca_dev, 0x0004, 0x011a);
+		cit_write_reg(gspca_dev, 0x003f, 0x011c);
+		cit_write_reg(gspca_dev, 0x000c, 0x0118);
+		cit_write_reg(gspca_dev, 0x0000, 0x0132);
+		break;
+	}
+
+	cit_model3_Packet1(gspca_dev, 0x0019, 0x0031);
+	cit_model3_Packet1(gspca_dev, 0x001a, 0x0003);
+	cit_model3_Packet1(gspca_dev, 0x001b, 0x0038);
+	cit_model3_Packet1(gspca_dev, 0x001c, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0024, 0x0001);
+	cit_model3_Packet1(gspca_dev, 0x0027, 0x0001);
+	cit_model3_Packet1(gspca_dev, 0x002a, 0x0004);
+	cit_model3_Packet1(gspca_dev, 0x0035, 0x000b);
+	cit_model3_Packet1(gspca_dev, 0x003f, 0x0001);
+	cit_model3_Packet1(gspca_dev, 0x0044, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x0054, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x00c4, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x00e7, 0x0001);
+	cit_model3_Packet1(gspca_dev, 0x00e9, 0x0001);
+	cit_model3_Packet1(gspca_dev, 0x00ee, 0x0000);
+	cit_model3_Packet1(gspca_dev, 0x00f3, 0x00c0);
+
+	cit_write_reg(gspca_dev, compression, 0x0109);
+	cit_write_reg(gspca_dev, clock_div, 0x0111);
+
+/*	if (sd->input_index) { */
+	if (rca_input) {
+		for (i = 0; i < ARRAY_SIZE(rca_initdata); i++) {
+			if (rca_initdata[i][0])
+				cit_read_reg(gspca_dev, rca_initdata[i][2], 0);
+			else
+				cit_write_reg(gspca_dev, rca_initdata[i][1],
+					      rca_initdata[i][2]);
+		}
+	}
+
+	return 0;
+}
+
+/* -- start the camera -- */
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int packet_size;
+
+	packet_size = cit_get_packet_size(gspca_dev);
+	if (packet_size < 0)
+		return packet_size;
+
+	switch (sd->model) {
+	case CIT_MODEL0:
+		cit_start_model0(gspca_dev);
+		break;
+	case CIT_MODEL1:
+		cit_start_model1(gspca_dev);
+		break;
+	case CIT_MODEL2:
+		cit_start_model2(gspca_dev);
+		break;
+	case CIT_MODEL3:
+		cit_start_model3(gspca_dev);
+		break;
+	case CIT_MODEL4:
+		cit_start_model4(gspca_dev);
+		break;
+	case CIT_IBM_NETCAM_PRO:
+		cit_start_ibm_netcam_pro(gspca_dev);
+		break;
+	}
+
+	/* Program max isoc packet size */
+	cit_write_reg(gspca_dev, packet_size >> 8, 0x0106);
+	cit_write_reg(gspca_dev, packet_size & 0xff, 0x0107);
+
+	cit_restart_stream(gspca_dev);
+
+	return 0;
+}
+
+static int sd_isoc_init(struct gspca_dev *gspca_dev)
+{
+	struct usb_host_interface *alt;
+	int max_packet_size;
+
+	switch (gspca_dev->pixfmt.width) {
+	case 160:
+		max_packet_size = 450;
+		break;
+	case 176:
+		max_packet_size = 600;
+		break;
+	default:
+		max_packet_size = 1022;
+		break;
+	}
+
+	/* Start isoc bandwidth "negotiation" at max isoc bandwidth */
+	alt = &gspca_dev->dev->actconfig->intf_cache[0]->altsetting[1];
+	alt->endpoint[0].desc.wMaxPacketSize = cpu_to_le16(max_packet_size);
+
+	return 0;
+}
+
+static int sd_isoc_nego(struct gspca_dev *gspca_dev)
+{
+	int ret, packet_size, min_packet_size;
+	struct usb_host_interface *alt;
+
+	switch (gspca_dev->pixfmt.width) {
+	case 160:
+		min_packet_size = 200;
+		break;
+	case 176:
+		min_packet_size = 266;
+		break;
+	default:
+		min_packet_size = 400;
+		break;
+	}
+
+	alt = &gspca_dev->dev->actconfig->intf_cache[0]->altsetting[1];
+	packet_size = le16_to_cpu(alt->endpoint[0].desc.wMaxPacketSize);
+	if (packet_size <= min_packet_size)
+		return -EIO;
+
+	packet_size -= 100;
+	if (packet_size < min_packet_size)
+		packet_size = min_packet_size;
+	alt->endpoint[0].desc.wMaxPacketSize = cpu_to_le16(packet_size);
+
+	ret = usb_set_interface(gspca_dev->dev, gspca_dev->iface, 1);
+	if (ret < 0)
+		pr_err("set alt 1 err %d\n", ret);
+
+	return ret;
+}
+
+static void sd_stopN(struct gspca_dev *gspca_dev)
+{
+	cit_write_reg(gspca_dev, 0x0000, 0x010c);
+}
+
+static void sd_stop0(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (!gspca_dev->present)
+		return;
+
+	switch (sd->model) {
+	case CIT_MODEL0:
+		/* HDG windows does this, but it causes the cams autogain to
+		   restart from a gain of 0, which does not look good when
+		   changing resolutions. */
+		/* cit_write_reg(gspca_dev, 0x0000, 0x0112); */
+		cit_write_reg(gspca_dev, 0x00c0, 0x0100); /* LED Off */
+		break;
+	case CIT_MODEL1:
+		cit_send_FF_04_02(gspca_dev);
+		cit_read_reg(gspca_dev, 0x0100, 0);
+		cit_write_reg(gspca_dev, 0x81, 0x0100);	/* LED Off */
+		break;
+	case CIT_MODEL2:
+		v4l2_ctrl_grab(sd->lighting, false);
+		/* Fall through! */
+	case CIT_MODEL4:
+		cit_model2_Packet1(gspca_dev, 0x0030, 0x0004);
+
+		cit_write_reg(gspca_dev, 0x0080, 0x0100);	/* LED Off */
+		cit_write_reg(gspca_dev, 0x0020, 0x0111);
+		cit_write_reg(gspca_dev, 0x00a0, 0x0111);
+
+		cit_model2_Packet1(gspca_dev, 0x0030, 0x0002);
+
+		cit_write_reg(gspca_dev, 0x0020, 0x0111);
+		cit_write_reg(gspca_dev, 0x0000, 0x0112);
+		break;
+	case CIT_MODEL3:
+		cit_write_reg(gspca_dev, 0x0006, 0x012c);
+		cit_model3_Packet1(gspca_dev, 0x0046, 0x0000);
+		cit_read_reg(gspca_dev, 0x0116, 0);
+		cit_write_reg(gspca_dev, 0x0064, 0x0116);
+		cit_read_reg(gspca_dev, 0x0115, 0);
+		cit_write_reg(gspca_dev, 0x0003, 0x0115);
+		cit_write_reg(gspca_dev, 0x0008, 0x0123);
+		cit_write_reg(gspca_dev, 0x0000, 0x0117);
+		cit_write_reg(gspca_dev, 0x0000, 0x0112);
+		cit_write_reg(gspca_dev, 0x0080, 0x0100);
+		break;
+	case CIT_IBM_NETCAM_PRO:
+		cit_model3_Packet1(gspca_dev, 0x0049, 0x00ff);
+		cit_write_reg(gspca_dev, 0x0006, 0x012c);
+		cit_write_reg(gspca_dev, 0x0000, 0x0116);
+		/* HDG windows does this, but I cannot get the camera
+		   to restart with this without redoing the entire init
+		   sequence which makes switching modes really slow */
+		/* cit_write_reg(gspca_dev, 0x0006, 0x0115); */
+		cit_write_reg(gspca_dev, 0x0008, 0x0123);
+		cit_write_reg(gspca_dev, 0x0000, 0x0117);
+		cit_write_reg(gspca_dev, 0x0003, 0x0133);
+		cit_write_reg(gspca_dev, 0x0000, 0x0111);
+		/* HDG windows does this, but I get a green picture when
+		   restarting the stream after this */
+		/* cit_write_reg(gspca_dev, 0x0000, 0x0112); */
+		cit_write_reg(gspca_dev, 0x00c0, 0x0100);
+		break;
+	}
+
+#if IS_ENABLED(CONFIG_INPUT)
+	/* If the last button state is pressed, release it now! */
+	if (sd->button_state) {
+		input_report_key(gspca_dev->input_dev, KEY_CAMERA, 0);
+		input_sync(gspca_dev->input_dev);
+		sd->button_state = 0;
+	}
+#endif
+}
+
+static u8 *cit_find_sof(struct gspca_dev *gspca_dev, u8 *data, int len)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	u8 byte3 = 0, byte4 = 0;
+	int i;
+
+	switch (sd->model) {
+	case CIT_MODEL0:
+	case CIT_MODEL1:
+	case CIT_MODEL3:
+	case CIT_IBM_NETCAM_PRO:
+		switch (gspca_dev->pixfmt.width) {
+		case 160: /* 160x120 */
+			byte3 = 0x02;
+			byte4 = 0x0a;
+			break;
+		case 176: /* 176x144 */
+			byte3 = 0x02;
+			byte4 = 0x0e;
+			break;
+		case 320: /* 320x240 */
+			byte3 = 0x02;
+			byte4 = 0x08;
+			break;
+		case 352: /* 352x288 */
+			byte3 = 0x02;
+			byte4 = 0x00;
+			break;
+		case 640:
+			byte3 = 0x03;
+			byte4 = 0x08;
+			break;
+		}
+
+		/* These have a different byte3 */
+		if (sd->model <= CIT_MODEL1)
+			byte3 = 0x00;
+
+		for (i = 0; i < len; i++) {
+			/* For this model the SOF always starts at offset 0
+			   so no need to search the entire frame */
+			if (sd->model == CIT_MODEL0 && sd->sof_read != i)
+				break;
+
+			switch (sd->sof_read) {
+			case 0:
+				if (data[i] == 0x00)
+					sd->sof_read++;
+				break;
+			case 1:
+				if (data[i] == 0xff)
+					sd->sof_read++;
+				else if (data[i] == 0x00)
+					sd->sof_read = 1;
+				else
+					sd->sof_read = 0;
+				break;
+			case 2:
+				if (data[i] == byte3)
+					sd->sof_read++;
+				else if (data[i] == 0x00)
+					sd->sof_read = 1;
+				else
+					sd->sof_read = 0;
+				break;
+			case 3:
+				if (data[i] == byte4) {
+					sd->sof_read = 0;
+					return data + i + (sd->sof_len - 3);
+				}
+				if (byte3 == 0x00 && data[i] == 0xff)
+					sd->sof_read = 2;
+				else if (data[i] == 0x00)
+					sd->sof_read = 1;
+				else
+					sd->sof_read = 0;
+				break;
+			}
+		}
+		break;
+	case CIT_MODEL2:
+	case CIT_MODEL4:
+		/* TESTME we need to find a longer sof signature to avoid
+		   false positives */
+		for (i = 0; i < len; i++) {
+			switch (sd->sof_read) {
+			case 0:
+				if (data[i] == 0x00)
+					sd->sof_read++;
+				break;
+			case 1:
+				sd->sof_read = 0;
+				if (data[i] == 0xff) {
+					if (i >= 4)
+						gspca_dbg(gspca_dev, D_FRAM,
+							  "header found at offset: %d: %02x %02x 00 %3ph\n\n",
+							  i - 1,
+							  data[i - 4],
+							  data[i - 3],
+							  &data[i]);
+					else
+						gspca_dbg(gspca_dev, D_FRAM,
+							  "header found at offset: %d: 00 %3ph\n\n",
+							  i - 1,
+							  &data[i]);
+					return data + i + (sd->sof_len - 1);
+				}
+				break;
+			}
+		}
+		break;
+	}
+	return NULL;
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data, int len)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	unsigned char *sof;
+
+	sof = cit_find_sof(gspca_dev, data, len);
+	if (sof) {
+		int n;
+
+		/* finish decoding current frame */
+		n = sof - data;
+		if (n > sd->sof_len)
+			n -= sd->sof_len;
+		else
+			n = 0;
+		gspca_frame_add(gspca_dev, LAST_PACKET,
+				data, n);
+		gspca_frame_add(gspca_dev, FIRST_PACKET, NULL, 0);
+		len -= sof - data;
+		data = sof;
+	}
+
+	gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+}
+
+#if IS_ENABLED(CONFIG_INPUT)
+static void cit_check_button(struct gspca_dev *gspca_dev)
+{
+	int new_button_state;
+	struct sd *sd = (struct sd *)gspca_dev;
+
+	switch (sd->model) {
+	case CIT_MODEL3:
+	case CIT_IBM_NETCAM_PRO:
+		break;
+	default: /* TEST ME unknown if this works on other models too */
+		return;
+	}
+
+	/* Read the button state */
+	cit_read_reg(gspca_dev, 0x0113, 0);
+	new_button_state = !gspca_dev->usb_buf[0];
+
+	/* Tell the cam we've seen the button press, notice that this
+	   is a nop (iow the cam keeps reporting pressed) until the
+	   button is actually released. */
+	if (new_button_state)
+		cit_write_reg(gspca_dev, 0x01, 0x0113);
+
+	if (sd->button_state != new_button_state) {
+		input_report_key(gspca_dev->input_dev, KEY_CAMERA,
+				 new_button_state);
+		input_sync(gspca_dev->input_dev);
+		sd->button_state = new_button_state;
+	}
+}
+#endif
+
+static int sd_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *)gspca_dev;
+
+	gspca_dev->usb_err = 0;
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	if (sd->stop_on_control_change)
+		sd_stopN(gspca_dev);
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		cit_set_brightness(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_CONTRAST:
+		cit_set_contrast(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_HUE:
+		cit_set_hue(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_HFLIP:
+		cit_set_hflip(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_SHARPNESS:
+		cit_set_sharpness(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_BACKLIGHT_COMPENSATION:
+		cit_set_lighting(gspca_dev, ctrl->val);
+		break;
+	}
+	if (sd->stop_on_control_change)
+		cit_restart_stream(gspca_dev);
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops sd_ctrl_ops = {
+	.s_ctrl = sd_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *)gspca_dev;
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+	bool has_brightness;
+	bool has_contrast;
+	bool has_hue;
+	bool has_sharpness;
+	bool has_lighting;
+	bool has_hflip;
+
+	has_brightness = has_contrast = has_hue =
+		has_sharpness = has_hflip = has_lighting = false;
+	switch (sd->model) {
+	case CIT_MODEL0:
+		has_contrast = has_hflip = true;
+		break;
+	case CIT_MODEL1:
+		has_brightness = has_contrast =
+			has_sharpness = has_lighting = true;
+		break;
+	case CIT_MODEL2:
+		has_brightness = has_hue = has_lighting = true;
+		break;
+	case CIT_MODEL3:
+		has_brightness = has_contrast = has_sharpness = true;
+		break;
+	case CIT_MODEL4:
+		has_brightness = has_hue = true;
+		break;
+	case CIT_IBM_NETCAM_PRO:
+		has_brightness = has_hue =
+			has_sharpness = has_hflip = has_lighting = true;
+		break;
+	}
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 5);
+	if (has_brightness)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0, 63, 1, 32);
+	if (has_contrast)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_CONTRAST, 0, 20, 1, 10);
+	if (has_hue)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_HUE, 0, 127, 1, 63);
+	if (has_sharpness)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_SHARPNESS, 0, 6, 1, 3);
+	if (has_lighting)
+		sd->lighting = v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_BACKLIGHT_COMPENSATION, 0, 2, 1, 1);
+	if (has_hflip)
+		v4l2_ctrl_new_std(hdl, &sd_ctrl_ops,
+			V4L2_CID_HFLIP, 0, 1, 1, 0);
+
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	return 0;
+}
+
+/* sub-driver description */
+static const struct sd_desc sd_desc = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.start = sd_start,
+	.stopN = sd_stopN,
+	.stop0 = sd_stop0,
+	.pkt_scan = sd_pkt_scan,
+#if IS_ENABLED(CONFIG_INPUT)
+	.dq_callback = cit_check_button,
+	.other_input = 1,
+#endif
+};
+
+static const struct sd_desc sd_desc_isoc_nego = {
+	.name = MODULE_NAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.start = sd_start,
+	.isoc_init = sd_isoc_init,
+	.isoc_nego = sd_isoc_nego,
+	.stopN = sd_stopN,
+	.stop0 = sd_stop0,
+	.pkt_scan = sd_pkt_scan,
+#if IS_ENABLED(CONFIG_INPUT)
+	.dq_callback = cit_check_button,
+	.other_input = 1,
+#endif
+};
+
+/* -- module initialisation -- */
+static const struct usb_device_id device_table[] = {
+	{ USB_DEVICE_VER(0x0545, 0x8080, 0x0001, 0x0001), .driver_info = CIT_MODEL0 },
+	{ USB_DEVICE_VER(0x0545, 0x8080, 0x0002, 0x0002), .driver_info = CIT_MODEL1 },
+	{ USB_DEVICE_VER(0x0545, 0x8080, 0x030a, 0x030a), .driver_info = CIT_MODEL2 },
+	{ USB_DEVICE_VER(0x0545, 0x8080, 0x0301, 0x0301), .driver_info = CIT_MODEL3 },
+	{ USB_DEVICE_VER(0x0545, 0x8002, 0x030a, 0x030a), .driver_info = CIT_MODEL4 },
+	{ USB_DEVICE_VER(0x0545, 0x800c, 0x030a, 0x030a), .driver_info = CIT_MODEL2 },
+	{ USB_DEVICE_VER(0x0545, 0x800d, 0x030a, 0x030a), .driver_info = CIT_MODEL4 },
+	{}
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	const struct sd_desc *desc = &sd_desc;
+
+	switch (id->driver_info) {
+	case CIT_MODEL0:
+	case CIT_MODEL1:
+		if (intf->cur_altsetting->desc.bInterfaceNumber != 2)
+			return -ENODEV;
+		break;
+	case CIT_MODEL2:
+	case CIT_MODEL4:
+		if (intf->cur_altsetting->desc.bInterfaceNumber != 0)
+			return -ENODEV;
+		break;
+	case CIT_MODEL3:
+		if (intf->cur_altsetting->desc.bInterfaceNumber != 0)
+			return -ENODEV;
+		/* FIXME this likely applies to all model3 cams and probably
+		   to other models too. */
+		if (ibm_netcam_pro)
+			desc = &sd_desc_isoc_nego;
+		break;
+	}
+
+	return gspca_dev_probe2(intf, id, desc, sizeof(struct sd), THIS_MODULE);
+}
+
+static struct usb_driver sd_driver = {
+	.name = MODULE_NAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
diff --git a/drivers/media/usb/gspca/zc3xx-reg.h b/drivers/media/usb/gspca/zc3xx-reg.h
new file mode 100644
index 0000000..71fda38
--- /dev/null
+++ b/drivers/media/usb/gspca/zc3xx-reg.h
@@ -0,0 +1,251 @@
+/*
+ * zc030x registers
+ *
+ * Copyright (c) 2008 Mauro Carvalho Chehab <mchehab@kernel.org>
+ *
+ * The register aliases used here came from this driver:
+ *	http://zc0302.sourceforge.net/zc0302.php
+ *
+ * This code is placed under the terms of the GNU General Public License v2
+ */
+
+/* Define the register map */
+#define ZC3XX_R000_SYSTEMCONTROL       0x0000
+#define ZC3XX_R001_SYSTEMOPERATING     0x0001
+
+/* Picture size */
+#define ZC3XX_R002_CLOCKSELECT         0x0002
+#define ZC3XX_R003_FRAMEWIDTHHIGH      0x0003
+#define ZC3XX_R004_FRAMEWIDTHLOW       0x0004
+#define ZC3XX_R005_FRAMEHEIGHTHIGH     0x0005
+#define ZC3XX_R006_FRAMEHEIGHTLOW      0x0006
+
+/* JPEG control */
+#define ZC3XX_R008_CLOCKSETTING        0x0008
+
+/* Test mode */
+#define ZC3XX_R00B_TESTMODECONTROL     0x000b
+
+/* Frame retreiving */
+#define ZC3XX_R00C_LASTACQTIME         0x000c
+#define ZC3XX_R00D_MONITORRES          0x000d
+#define ZC3XX_R00E_TIMESTAMPHIGH       0x000e
+#define ZC3XX_R00F_TIMESTAMPLOW        0x000f
+#define ZC3XX_R018_FRAMELOST           0x0018
+#define ZC3XX_R019_AUTOADJUSTFPS       0x0019
+#define ZC3XX_R01A_LASTFRAMESTATE      0x001a
+#define ZC3XX_R025_DATACOUNTER         0x0025
+
+/* Stream and sensor specific */
+#define ZC3XX_R010_CMOSSENSORSELECT    0x0010
+#define ZC3XX_R011_VIDEOSTATUS         0x0011
+#define ZC3XX_R012_VIDEOCONTROLFUNC    0x0012
+
+/* Horizontal and vertical synchros */
+#define ZC3XX_R01D_HSYNC_0             0x001d
+#define ZC3XX_R01E_HSYNC_1             0x001e
+#define ZC3XX_R01F_HSYNC_2             0x001f
+#define ZC3XX_R020_HSYNC_3             0x0020
+
+/* Target picture size in byte */
+#define ZC3XX_R022_TARGETPICTSIZE_0    0x0022
+#define ZC3XX_R023_TARGETPICTSIZE_1    0x0023
+#define ZC3XX_R024_TARGETPICTSIZE_2    0x0024
+
+/* Audio registers */
+#define ZC3XX_R030_AUDIOADC            0x0030
+#define ZC3XX_R031_AUDIOSTREAMSTATUS   0x0031
+#define ZC3XX_R032_AUDIOSTATUS         0x0032
+
+/* Sensor interface */
+#define ZC3XX_R080_HBLANKHIGH          0x0080
+#define ZC3XX_R081_HBLANKLOW           0x0081
+#define ZC3XX_R082_RESETLEVELADDR      0x0082
+#define ZC3XX_R083_RGAINADDR           0x0083
+#define ZC3XX_R084_GGAINADDR           0x0084
+#define ZC3XX_R085_BGAINADDR           0x0085
+#define ZC3XX_R086_EXPTIMEHIGH         0x0086
+#define ZC3XX_R087_EXPTIMEMID          0x0087
+#define ZC3XX_R088_EXPTIMELOW          0x0088
+#define ZC3XX_R089_RESETBLACKHIGH      0x0089
+#define ZC3XX_R08A_RESETWHITEHIGH      0x008a
+#define ZC3XX_R08B_I2CDEVICEADDR       0x008b
+#define ZC3XX_R08C_I2CIDLEANDNACK      0x008c
+#define ZC3XX_R08D_COMPABILITYMODE     0x008d
+#define ZC3XX_R08E_COMPABILITYMODE2    0x008e
+
+/* I2C control */
+#define ZC3XX_R090_I2CCOMMAND          0x0090
+#define ZC3XX_R091_I2CSTATUS           0x0091
+#define ZC3XX_R092_I2CADDRESSSELECT    0x0092
+#define ZC3XX_R093_I2CSETVALUE         0x0093
+#define ZC3XX_R094_I2CWRITEACK         0x0094
+#define ZC3XX_R095_I2CREAD             0x0095
+#define ZC3XX_R096_I2CREADACK          0x0096
+
+/* Window inside the sensor array */
+#define ZC3XX_R097_WINYSTARTHIGH       0x0097
+#define ZC3XX_R098_WINYSTARTLOW        0x0098
+#define ZC3XX_R099_WINXSTARTHIGH       0x0099
+#define ZC3XX_R09A_WINXSTARTLOW        0x009a
+#define ZC3XX_R09B_WINHEIGHTHIGH       0x009b
+#define ZC3XX_R09C_WINHEIGHTLOW        0x009c
+#define ZC3XX_R09D_WINWIDTHHIGH        0x009d
+#define ZC3XX_R09E_WINWIDTHLOW         0x009e
+#define ZC3XX_R119_FIRSTYHIGH          0x0119
+#define ZC3XX_R11A_FIRSTYLOW           0x011a
+#define ZC3XX_R11B_FIRSTXHIGH          0x011b
+#define ZC3XX_R11C_FIRSTXLOW           0x011c
+
+/* Max sensor array size */
+#define ZC3XX_R09F_MAXXHIGH            0x009f
+#define ZC3XX_R0A0_MAXXLOW             0x00a0
+#define ZC3XX_R0A1_MAXYHIGH            0x00a1
+#define ZC3XX_R0A2_MAXYLOW             0x00a2
+#define ZC3XX_R0A3_EXPOSURETIMEHIGH    0x00a3
+#define ZC3XX_R0A4_EXPOSURETIMELOW     0x00a4
+#define ZC3XX_R0A5_EXPOSUREGAIN        0x00a5
+#define ZC3XX_R0A6_EXPOSUREBLACKLVL    0x00a6
+
+/* Other registers */
+#define ZC3XX_R100_OPERATIONMODE       0x0100
+#define ZC3XX_R101_SENSORCORRECTION    0x0101
+
+/* Gains */
+#define ZC3XX_R116_RGAIN               0x0116
+#define ZC3XX_R117_GGAIN               0x0117
+#define ZC3XX_R118_BGAIN               0x0118
+#define ZC3XX_R11D_GLOBALGAIN          0x011d
+#define ZC3XX_R1A8_DIGITALGAIN         0x01a8
+#define ZC3XX_R1A9_DIGITALLIMITDIFF    0x01a9
+#define ZC3XX_R1AA_DIGITALGAINSTEP     0x01aa
+
+/* Auto correction */
+#define ZC3XX_R180_AUTOCORRECTENABLE   0x0180
+#define ZC3XX_R181_WINXSTART           0x0181
+#define ZC3XX_R182_WINXWIDTH           0x0182
+#define ZC3XX_R183_WINXCENTER          0x0183
+#define ZC3XX_R184_WINYSTART           0x0184
+#define ZC3XX_R185_WINYWIDTH           0x0185
+#define ZC3XX_R186_WINYCENTER          0x0186
+
+/* Gain range */
+#define ZC3XX_R187_MAXGAIN             0x0187
+#define ZC3XX_R188_MINGAIN             0x0188
+
+/* Auto exposure and white balance */
+#define ZC3XX_R189_AWBSTATUS           0x0189
+#define ZC3XX_R18A_AWBFREEZE           0x018a
+#define ZC3XX_R18B_AESTATUS            0x018b
+#define ZC3XX_R18C_AEFREEZE            0x018c
+#define ZC3XX_R18F_AEUNFREEZE          0x018f
+#define ZC3XX_R190_EXPOSURELIMITHIGH   0x0190
+#define ZC3XX_R191_EXPOSURELIMITMID    0x0191
+#define ZC3XX_R192_EXPOSURELIMITLOW    0x0192
+#define ZC3XX_R195_ANTIFLICKERHIGH     0x0195
+#define ZC3XX_R196_ANTIFLICKERMID      0x0196
+#define ZC3XX_R197_ANTIFLICKERLOW      0x0197
+
+/* What is this ? */
+#define ZC3XX_R18D_YTARGET             0x018d
+#define ZC3XX_R18E_RESETLVL            0x018e
+
+/* Color */
+#define ZC3XX_R1A0_REDMEANAFTERAGC     0x01a0
+#define ZC3XX_R1A1_GREENMEANAFTERAGC   0x01a1
+#define ZC3XX_R1A2_BLUEMEANAFTERAGC    0x01a2
+#define ZC3XX_R1A3_REDMEANAFTERAWB     0x01a3
+#define ZC3XX_R1A4_GREENMEANAFTERAWB   0x01a4
+#define ZC3XX_R1A5_BLUEMEANAFTERAWB    0x01a5
+#define ZC3XX_R1A6_YMEANAFTERAE        0x01a6
+#define ZC3XX_R1A7_CALCGLOBALMEAN      0x01a7
+
+/* Matrixes */
+
+/* Color matrix is like :
+   R' = R * RGB00 + G * RGB01 + B * RGB02 + RGB03
+   G' = R * RGB10 + G * RGB11 + B * RGB22 + RGB13
+   B' = R * RGB20 + G * RGB21 + B * RGB12 + RGB23
+ */
+#define ZC3XX_R10A_RGB00               0x010a
+#define ZC3XX_R10B_RGB01               0x010b
+#define ZC3XX_R10C_RGB02               0x010c
+#define ZC3XX_R113_RGB03               0x0113
+#define ZC3XX_R10D_RGB10               0x010d
+#define ZC3XX_R10E_RGB11               0x010e
+#define ZC3XX_R10F_RGB12               0x010f
+#define ZC3XX_R114_RGB13               0x0114
+#define ZC3XX_R110_RGB20               0x0110
+#define ZC3XX_R111_RGB21               0x0111
+#define ZC3XX_R112_RGB22               0x0112
+#define ZC3XX_R115_RGB23               0x0115
+
+/* Gamma matrix */
+#define ZC3XX_R120_GAMMA00             0x0120
+#define ZC3XX_R121_GAMMA01             0x0121
+#define ZC3XX_R122_GAMMA02             0x0122
+#define ZC3XX_R123_GAMMA03             0x0123
+#define ZC3XX_R124_GAMMA04             0x0124
+#define ZC3XX_R125_GAMMA05             0x0125
+#define ZC3XX_R126_GAMMA06             0x0126
+#define ZC3XX_R127_GAMMA07             0x0127
+#define ZC3XX_R128_GAMMA08             0x0128
+#define ZC3XX_R129_GAMMA09             0x0129
+#define ZC3XX_R12A_GAMMA0A             0x012a
+#define ZC3XX_R12B_GAMMA0B             0x012b
+#define ZC3XX_R12C_GAMMA0C             0x012c
+#define ZC3XX_R12D_GAMMA0D             0x012d
+#define ZC3XX_R12E_GAMMA0E             0x012e
+#define ZC3XX_R12F_GAMMA0F             0x012f
+#define ZC3XX_R130_GAMMA10             0x0130
+#define ZC3XX_R131_GAMMA11             0x0131
+#define ZC3XX_R132_GAMMA12             0x0132
+#define ZC3XX_R133_GAMMA13             0x0133
+#define ZC3XX_R134_GAMMA14             0x0134
+#define ZC3XX_R135_GAMMA15             0x0135
+#define ZC3XX_R136_GAMMA16             0x0136
+#define ZC3XX_R137_GAMMA17             0x0137
+#define ZC3XX_R138_GAMMA18             0x0138
+#define ZC3XX_R139_GAMMA19             0x0139
+#define ZC3XX_R13A_GAMMA1A             0x013a
+#define ZC3XX_R13B_GAMMA1B             0x013b
+#define ZC3XX_R13C_GAMMA1C             0x013c
+#define ZC3XX_R13D_GAMMA1D             0x013d
+#define ZC3XX_R13E_GAMMA1E             0x013e
+#define ZC3XX_R13F_GAMMA1F             0x013f
+
+/* Luminance gamma */
+#define ZC3XX_R140_YGAMMA00            0x0140
+#define ZC3XX_R141_YGAMMA01            0x0141
+#define ZC3XX_R142_YGAMMA02            0x0142
+#define ZC3XX_R143_YGAMMA03            0x0143
+#define ZC3XX_R144_YGAMMA04            0x0144
+#define ZC3XX_R145_YGAMMA05            0x0145
+#define ZC3XX_R146_YGAMMA06            0x0146
+#define ZC3XX_R147_YGAMMA07            0x0147
+#define ZC3XX_R148_YGAMMA08            0x0148
+#define ZC3XX_R149_YGAMMA09            0x0149
+#define ZC3XX_R14A_YGAMMA0A            0x014a
+#define ZC3XX_R14B_YGAMMA0B            0x014b
+#define ZC3XX_R14C_YGAMMA0C            0x014c
+#define ZC3XX_R14D_YGAMMA0D            0x014d
+#define ZC3XX_R14E_YGAMMA0E            0x014e
+#define ZC3XX_R14F_YGAMMA0F            0x014f
+#define ZC3XX_R150_YGAMMA10            0x0150
+#define ZC3XX_R151_YGAMMA11            0x0151
+
+#define ZC3XX_R1C5_SHARPNESSMODE       0x01c5
+#define ZC3XX_R1C6_SHARPNESS00         0x01c6
+#define ZC3XX_R1C7_SHARPNESS01         0x01c7
+#define ZC3XX_R1C8_SHARPNESS02         0x01c8
+#define ZC3XX_R1C9_SHARPNESS03         0x01c9
+#define ZC3XX_R1CA_SHARPNESS04         0x01ca
+#define ZC3XX_R1CB_SHARPNESS05         0x01cb
+
+/* Dead pixels */
+#define ZC3XX_R250_DEADPIXELSMODE      0x0250
+
+/* EEPROM */
+#define ZC3XX_R300_EEPROMCONFIG        0x0300
+#define ZC3XX_R301_EEPROMACCESS        0x0301
+#define ZC3XX_R302_EEPROMSTATUS        0x0302
diff --git a/drivers/media/usb/gspca/zc3xx.c b/drivers/media/usb/gspca/zc3xx.c
new file mode 100644
index 0000000..cf21991
--- /dev/null
+++ b/drivers/media/usb/gspca/zc3xx.c
@@ -0,0 +1,7038 @@
+/*
+ * Z-Star/Vimicro zc301/zc302p/vc30x driver
+ *
+ * Copyright (C) 2009-2012 Jean-Francois Moine <http://moinejf.free.fr>
+ * Copyright (C) 2004 2005 2006 Michel Xhaard mxhaard@magic.fr
+ *
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/input.h>
+#include "gspca.h"
+#include "jpeg.h"
+
+MODULE_AUTHOR("Jean-Francois Moine <http://moinejf.free.fr>, Serge A. Suchkov <Serge.A.S@tochka.ru>");
+MODULE_DESCRIPTION("GSPCA ZC03xx/VC3xx USB Camera Driver");
+MODULE_LICENSE("GPL");
+
+static int force_sensor = -1;
+
+#define REG08_DEF 3		/* default JPEG compression (75%) */
+#include "zc3xx-reg.h"
+
+/* specific webcam descriptor */
+struct sd {
+	struct gspca_dev gspca_dev;	/* !! must be the first item */
+
+	struct { /* gamma/brightness/contrast control cluster */
+		struct v4l2_ctrl *gamma;
+		struct v4l2_ctrl *brightness;
+		struct v4l2_ctrl *contrast;
+	};
+	struct { /* autogain/exposure control cluster */
+		struct v4l2_ctrl *autogain;
+		struct v4l2_ctrl *exposure;
+	};
+	struct v4l2_ctrl *plfreq;
+	struct v4l2_ctrl *sharpness;
+	struct v4l2_ctrl *jpegqual;
+
+	struct work_struct work;
+
+	u8 reg08;		/* webcam compression quality */
+
+	u8 bridge;
+	u8 sensor;		/* Type of image sensor chip */
+	u16 chip_revision;
+
+	u8 jpeg_hdr[JPEG_HDR_SZ];
+};
+enum bridges {
+	BRIDGE_ZC301,
+	BRIDGE_ZC303,
+};
+enum sensors {
+	SENSOR_ADCM2700,
+	SENSOR_CS2102,
+	SENSOR_CS2102K,
+	SENSOR_GC0303,
+	SENSOR_GC0305,
+	SENSOR_HDCS2020,
+	SENSOR_HV7131B,
+	SENSOR_HV7131R,
+	SENSOR_ICM105A,
+	SENSOR_MC501CB,
+	SENSOR_MT9V111_1,	/* (mi360soc) zc301 */
+	SENSOR_MT9V111_3,	/* (mi360soc) zc303 */
+	SENSOR_OV7620,		/* OV7648 - same values */
+	SENSOR_OV7630C,
+	SENSOR_PAS106,
+	SENSOR_PAS202B,
+	SENSOR_PB0330,
+	SENSOR_PO2030,
+	SENSOR_TAS5130C,
+	SENSOR_MAX
+};
+
+static const struct v4l2_pix_format vga_mode[] = {
+	{320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 240 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 1},
+	{640, 480, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 480 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 0},
+};
+
+static const struct v4l2_pix_format broken_vga_mode[] = {
+	{320, 232, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 320,
+		.sizeimage = 320 * 232 * 4 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 1},
+	{640, 472, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 640,
+		.sizeimage = 640 * 472 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 0},
+};
+
+static const struct v4l2_pix_format sif_mode[] = {
+	{176, 144, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 176,
+		.sizeimage = 176 * 144 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 1},
+	{352, 288, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE,
+		.bytesperline = 352,
+		.sizeimage = 352 * 288 * 3 / 8 + 590,
+		.colorspace = V4L2_COLORSPACE_JPEG,
+		.priv = 0},
+};
+
+/*
+ * Bridge reg08 bits 1-2 -> JPEG quality conversion table. Note the highest
+ * quality setting is not usable as USB 1 does not have enough bandwidth.
+ */
+static u8 jpeg_qual[] = {50, 75, 87, /* 94 */};
+
+/* usb exchanges */
+struct usb_action {
+	u8	req;
+	u8	val;
+	u16	idx;
+};
+
+static const struct usb_action adcm2700_Initial[] = {
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},		/* 00,00,01,cc */
+	{0xa0, 0x04, ZC3XX_R002_CLOCKSELECT},		/* 00,02,04,cc */
+	{0xa0, 0x00, ZC3XX_R008_CLOCKSETTING},		/* 00,08,03,cc */
+	{0xa0, 0x0a, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,0a,cc */
+	{0xa0, 0xd3, ZC3XX_R08B_I2CDEVICEADDR},		/* 00,8b,d3,cc */
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},	/* 00,03,02,cc */
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},		/* 00,04,80,cc */
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},	/* 00,05,01,cc */
+	{0xa0, 0xd8, ZC3XX_R006_FRAMEHEIGHTLOW},	/* 00,06,d8,cc */
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},	/* 00,01,01,cc */
+	{0xa0, 0x03, ZC3XX_R012_VIDEOCONTROLFUNC},	/* 00,12,03,cc */
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC},	/* 00,12,01,cc */
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},	/* 00,12,05,cc */
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},		/* 00,98,00,cc */
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW},		/* 00,9a,00,cc */
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},		/* 01,1a,00,cc */
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW},		/* 01,1c,00,cc */
+	{0xa0, 0xde, ZC3XX_R09C_WINHEIGHTLOW},		/* 00,9c,de,cc */
+	{0xa0, 0x86, ZC3XX_R09E_WINWIDTHLOW},		/* 00,9e,86,cc */
+	{0xbb, 0x00, 0x0400},				/* 04,00,00,bb */
+	{0xdd, 0x00, 0x0010},				/* 00,00,10,dd */
+	{0xbb, 0x0f, 0x140f},				/* 14,0f,0f,bb */
+	{0xa0, 0xb7, ZC3XX_R101_SENSORCORRECTION},	/* 01,01,37,cc */
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},		/* 01,00,0d,cc */
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},		/* 01,89,06,cc */
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},		/* 01,c5,03,cc */
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},		/* 01,cb,13,cc */
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},	/* 02,50,08,cc */
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},		/* 03,01,08,cc */
+	{0xa0, 0x58, ZC3XX_R116_RGAIN},			/* 01,16,58,cc */
+	{0xa0, 0x5a, ZC3XX_R118_BGAIN},			/* 01,18,5a,cc */
+	{0xa0, 0x02, ZC3XX_R180_AUTOCORRECTENABLE},	/* 01,80,02,cc */
+	{0xa0, 0xd3, ZC3XX_R08B_I2CDEVICEADDR},		/* 00,8b,d3,cc */
+	{0xbb, 0x00, 0x0408},				/* 04,00,08,bb */
+	{0xdd, 0x00, 0x0200},				/* 00,02,00,dd */
+	{0xbb, 0x00, 0x0400},				/* 04,00,00,bb */
+	{0xdd, 0x00, 0x0010},				/* 00,00,10,dd */
+	{0xbb, 0x0f, 0x140f},				/* 14,0f,0f,bb */
+	{0xbb, 0xe0, 0x0c2e},				/* 0c,e0,2e,bb */
+	{0xbb, 0x01, 0x2000},				/* 20,01,00,bb */
+	{0xbb, 0x96, 0x2400},				/* 24,96,00,bb */
+	{0xbb, 0x06, 0x1006},				/* 10,06,06,bb */
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,01,cc */
+	{0xdd, 0x00, 0x0010},				/* 00,00,10,dd */
+	{0xaa, 0xfe, 0x0002},				/* 00,fe,02,aa */
+	{0xa0, 0x0a, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,0a,cc */
+	{0xdd, 0x00, 0x0010},				/* 00,00,10,dd */
+	{0xbb, 0x5f, 0x2090},				/* 20,5f,90,bb */
+	{0xbb, 0x01, 0x8000},				/* 80,01,00,bb */
+	{0xbb, 0x09, 0x8400},				/* 84,09,00,bb */
+	{0xbb, 0x86, 0x0002},				/* 00,86,02,bb */
+	{0xbb, 0xe6, 0x0401},				/* 04,e6,01,bb */
+	{0xbb, 0x86, 0x0802},				/* 08,86,02,bb */
+	{0xbb, 0xe6, 0x0c01},				/* 0c,e6,01,bb */
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,01,cc */
+	{0xdd, 0x00, 0x0010},				/* 00,00,10,dd */
+	{0xaa, 0xfe, 0x0000},				/* 00,fe,00,aa */
+	{0xa0, 0x0a, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,0a,cc */
+	{0xdd, 0x00, 0x0010},				/* 00,00,10,dd */
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,01,cc */
+	{0xaa, 0xfe, 0x0020},				/* 00,fe,20,aa */
+/*mswin+*/
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT},
+	{0xaa, 0xfe, 0x0002},
+	{0xa0, 0x0a, ZC3XX_R010_CMOSSENSORSELECT},
+	{0xaa, 0xb4, 0xcd37},
+	{0xaa, 0xa4, 0x0004},
+	{0xaa, 0xa8, 0x0007},
+	{0xaa, 0xac, 0x0004},
+/*mswin-*/
+	{0xa0, 0x0a, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,0a,cc */
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,01,cc */
+	{0xdd, 0x00, 0x0010},				/* 00,00,10,dd */
+	{0xaa, 0xfe, 0x0000},				/* 00,fe,00,aa */
+	{0xa0, 0x0a, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,0a,cc */
+	{0xdd, 0x00, 0x0010},				/* 00,00,10,dd */
+	{0xbb, 0x04, 0x0400},				/* 04,04,00,bb */
+	{0xdd, 0x00, 0x0100},				/* 00,01,00,dd */
+	{0xbb, 0x01, 0x0400},				/* 04,01,00,bb */
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,01,cc */
+	{0xaa, 0xfe, 0x0002},				/* 00,fe,02,aa */
+	{0xa0, 0x0a, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,0a,cc */
+	{0xbb, 0x41, 0x2803},				/* 28,41,03,bb */
+	{0xbb, 0x40, 0x2c03},				/* 2c,40,03,bb */
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,01,cc */
+	{0xaa, 0xfe, 0x0010},				/* 00,fe,10,aa */
+	{}
+};
+static const struct usb_action adcm2700_InitialScale[] = {
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},		/* 00,00,01,cc */
+	{0xa0, 0x10, ZC3XX_R002_CLOCKSELECT},		/* 00,02,10,cc */
+	{0xa0, 0x00, ZC3XX_R008_CLOCKSETTING},		/* 00,08,03,cc */
+	{0xa0, 0x0a, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,0a,cc */
+	{0xa0, 0xd3, ZC3XX_R08B_I2CDEVICEADDR},		/* 00,8b,d3,cc */
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},	/* 00,03,02,cc */
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},		/* 00,04,80,cc */
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},	/* 00,05,01,cc */
+	{0xa0, 0xd0, ZC3XX_R006_FRAMEHEIGHTLOW},	/* 00,06,d0,cc */
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},	/* 00,01,01,cc */
+	{0xa0, 0x03, ZC3XX_R012_VIDEOCONTROLFUNC},	/* 00,12,03,cc */
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC},	/* 00,12,01,cc */
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},	/* 00,12,05,cc */
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},		/* 00,98,00,cc */
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW},		/* 00,9a,00,cc */
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},		/* 01,1a,00,cc */
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW},		/* 01,1c,00,cc */
+	{0xa0, 0xd8, ZC3XX_R09C_WINHEIGHTLOW},		/* 00,9c,d8,cc */
+	{0xa0, 0x88, ZC3XX_R09E_WINWIDTHLOW},		/* 00,9e,88,cc */
+	{0xbb, 0x00, 0x0400},				/* 04,00,00,bb */
+	{0xdd, 0x00, 0x0010},				/* 00,00,10,dd */
+	{0xbb, 0x0f, 0x140f},				/* 14,0f,0f,bb */
+	{0xa0, 0xb7, ZC3XX_R101_SENSORCORRECTION},	/* 01,01,37,cc */
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},		/* 01,00,0d,cc */
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},		/* 01,89,06,cc */
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},		/* 01,c5,03,cc */
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},		/* 01,cb,13,cc */
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},	/* 02,50,08,cc */
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},		/* 03,01,08,cc */
+	{0xa0, 0x58, ZC3XX_R116_RGAIN},			/* 01,16,58,cc */
+	{0xa0, 0x5a, ZC3XX_R118_BGAIN},			/* 01,18,5a,cc */
+	{0xa0, 0x02, ZC3XX_R180_AUTOCORRECTENABLE},	/* 01,80,02,cc */
+	{0xa0, 0xd3, ZC3XX_R08B_I2CDEVICEADDR},		/* 00,8b,d3,cc */
+	{0xbb, 0x00, 0x0408},				/* 04,00,08,bb */
+	{0xdd, 0x00, 0x0200},				/* 00,02,00,dd */
+	{0xbb, 0x00, 0x0400},				/* 04,00,00,bb */
+	{0xdd, 0x00, 0x0050},				/* 00,00,50,dd */
+	{0xbb, 0x0f, 0x140f},				/* 14,0f,0f,bb */
+	{0xbb, 0xe0, 0x0c2e},				/* 0c,e0,2e,bb */
+	{0xbb, 0x01, 0x2000},				/* 20,01,00,bb */
+	{0xbb, 0x96, 0x2400},				/* 24,96,00,bb */
+	{0xbb, 0x06, 0x1006},				/* 10,06,06,bb */
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,01,cc */
+	{0xdd, 0x00, 0x0010},				/* 00,00,10,dd */
+	{0xaa, 0xfe, 0x0002},				/* 00,fe,02,aa */
+	{0xa0, 0x0a, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,0a,cc */
+	{0xdd, 0x00, 0x0010},				/* 00,00,10,dd */
+	{0xbb, 0x5f, 0x2090},				/* 20,5f,90,bb */
+	{0xbb, 0x01, 0x8000},				/* 80,01,00,bb */
+	{0xbb, 0x09, 0x8400},				/* 84,09,00,bb */
+	{0xbb, 0x86, 0x0002},				/* 00,88,02,bb */
+	{0xbb, 0xe6, 0x0401},				/* 04,e6,01,bb */
+	{0xbb, 0x86, 0x0802},				/* 08,88,02,bb */
+	{0xbb, 0xe6, 0x0c01},				/* 0c,e6,01,bb */
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,01,cc */
+	{0xdd, 0x00, 0x0010},				/* 00,00,10,dd */
+	{0xaa, 0xfe, 0x0000},				/* 00,fe,00,aa */
+	{0xa0, 0x0a, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,0a,cc */
+	{0xdd, 0x00, 0x0010},				/* 00,00,10,dd */
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,01,cc */
+	{0xaa, 0xfe, 0x0020},				/* 00,fe,20,aa */
+	/*******/
+	{0xa0, 0x0a, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,0a,cc */
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,01,cc */
+	{0xdd, 0x00, 0x0010},				/* 00,00,10,dd */
+	{0xaa, 0xfe, 0x0000},				/* 00,fe,00,aa */
+	{0xa0, 0x0a, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,0a,cc */
+	{0xdd, 0x00, 0x0010},				/* 00,00,10,dd */
+	{0xbb, 0x04, 0x0400},				/* 04,04,00,bb */
+	{0xdd, 0x00, 0x0100},				/* 00,01,00,dd */
+	{0xbb, 0x01, 0x0400},				/* 04,01,00,bb */
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,01,cc */
+	{0xaa, 0xfe, 0x0002},				/* 00,fe,02,aa */
+	{0xa0, 0x0a, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,0a,cc */
+	{0xbb, 0x41, 0x2803},				/* 28,41,03,bb */
+	{0xbb, 0x40, 0x2c03},				/* 2c,40,03,bb */
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,01,cc */
+	{0xaa, 0xfe, 0x0010},				/* 00,fe,10,aa */
+	{}
+};
+static const struct usb_action adcm2700_50HZ[] = {
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,01,cc */
+	{0xaa, 0xfe, 0x0002},				/* 00,fe,02,aa */
+	{0xa0, 0x0a, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,0a,cc */
+	{0xbb, 0x05, 0x8400},				/* 84,05,00,bb */
+	{0xbb, 0xd0, 0xb007},				/* b0,d0,07,bb */
+	{0xbb, 0xa0, 0xb80f},				/* b8,a0,0f,bb */
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,01,cc */
+	{0xaa, 0xfe, 0x0010},				/* 00,fe,10,aa */
+	{0xaa, 0x26, 0x00d0},				/* 00,26,d0,aa */
+	{0xaa, 0x28, 0x0002},				/* 00,28,02,aa */
+	{}
+};
+static const struct usb_action adcm2700_60HZ[] = {
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,01,cc */
+	{0xaa, 0xfe, 0x0002},				/* 00,fe,02,aa */
+	{0xa0, 0x0a, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,0a,cc */
+	{0xbb, 0x07, 0x8400},				/* 84,07,00,bb */
+	{0xbb, 0x82, 0xb006},				/* b0,82,06,bb */
+	{0xbb, 0x04, 0xb80d},				/* b8,04,0d,bb */
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,01,cc */
+	{0xaa, 0xfe, 0x0010},				/* 00,fe,10,aa */
+	{0xaa, 0x26, 0x0057},				/* 00,26,57,aa */
+	{0xaa, 0x28, 0x0002},				/* 00,28,02,aa */
+	{}
+};
+static const struct usb_action adcm2700_NoFliker[] = {
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,01,cc */
+	{0xaa, 0xfe, 0x0002},				/* 00,fe,02,aa */
+	{0xa0, 0x0a, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,0a,cc */
+	{0xbb, 0x07, 0x8400},				/* 84,07,00,bb */
+	{0xbb, 0x05, 0xb000},				/* b0,05,00,bb */
+	{0xbb, 0xa0, 0xb801},				/* b8,a0,01,bb */
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,01,cc */
+	{0xaa, 0xfe, 0x0010},				/* 00,fe,10,aa */
+	{}
+};
+static const struct usb_action cs2102_InitialScale[] = {	/* 320x240 */
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},
+	{0xa0, 0x10, ZC3XX_R002_CLOCKSELECT},
+	{0xa0, 0x00, ZC3XX_R010_CMOSSENSORSELECT},
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},
+	{0xa0, 0x20, ZC3XX_R080_HBLANKHIGH},
+	{0xa0, 0x21, ZC3XX_R081_HBLANKLOW},
+	{0xa0, 0x30, ZC3XX_R083_RGAINADDR},
+	{0xa0, 0x31, ZC3XX_R084_GGAINADDR},
+	{0xa0, 0x32, ZC3XX_R085_BGAINADDR},
+	{0xa0, 0x23, ZC3XX_R086_EXPTIMEHIGH},
+	{0xa0, 0x24, ZC3XX_R087_EXPTIMEMID},
+	{0xa0, 0x25, ZC3XX_R088_EXPTIMELOW},
+	{0xa0, 0xb3, ZC3XX_R08B_I2CDEVICEADDR},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},	/* 00 */
+	{0xa0, 0x03, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW},
+	{0xaa, 0x02, 0x0008},
+	{0xaa, 0x03, 0x0000},
+	{0xaa, 0x11, 0x0000},
+	{0xaa, 0x12, 0x0089},
+	{0xaa, 0x13, 0x0000},
+	{0xaa, 0x14, 0x00e9},
+	{0xaa, 0x20, 0x0000},
+	{0xaa, 0x22, 0x0000},
+	{0xaa, 0x0b, 0x0004},
+	{0xaa, 0x30, 0x0030},
+	{0xaa, 0x31, 0x0030},
+	{0xaa, 0x32, 0x0030},
+	{0xa0, 0x37, ZC3XX_R101_SENSORCORRECTION},
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},
+	{0xa0, 0x10, 0x01ae},
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},
+	{0xa0, 0x68, ZC3XX_R18D_YTARGET},
+	{0xa0, 0x00, 0x01ad},
+	{}
+};
+
+static const struct usb_action cs2102_Initial[] = {	/* 640x480 */
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},
+	{0xa0, 0x00, ZC3XX_R002_CLOCKSELECT},
+	{0xa0, 0x00, ZC3XX_R010_CMOSSENSORSELECT},
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},
+	{0xa0, 0x20, ZC3XX_R080_HBLANKHIGH},
+	{0xa0, 0x21, ZC3XX_R081_HBLANKLOW},
+	{0xa0, 0x30, ZC3XX_R083_RGAINADDR},
+	{0xa0, 0x31, ZC3XX_R084_GGAINADDR},
+	{0xa0, 0x32, ZC3XX_R085_BGAINADDR},
+	{0xa0, 0x23, ZC3XX_R086_EXPTIMEHIGH},
+	{0xa0, 0x24, ZC3XX_R087_EXPTIMEMID},
+	{0xa0, 0x25, ZC3XX_R088_EXPTIMELOW},
+	{0xa0, 0xb3, ZC3XX_R08B_I2CDEVICEADDR},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},	/* 00 */
+	{0xa0, 0x03, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW},
+	{0xaa, 0x02, 0x0008},
+	{0xaa, 0x03, 0x0000},
+	{0xaa, 0x11, 0x0001},
+	{0xaa, 0x12, 0x0087},
+	{0xaa, 0x13, 0x0001},
+	{0xaa, 0x14, 0x00e7},
+	{0xaa, 0x20, 0x0000},
+	{0xaa, 0x22, 0x0000},
+	{0xaa, 0x0b, 0x0004},
+	{0xaa, 0x30, 0x0030},
+	{0xaa, 0x31, 0x0030},
+	{0xaa, 0x32, 0x0030},
+	{0xa0, 0x77, ZC3XX_R101_SENSORCORRECTION},
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},
+	{0xa0, 0x15, 0x01ae},
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},
+	{0xa0, 0x68, ZC3XX_R18D_YTARGET},
+	{0xa0, 0x00, 0x01ad},
+	{}
+};
+static const struct usb_action cs2102_50HZScale[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xaa, 0x23, 0x0001},
+	{0xaa, 0x24, 0x005f},
+	{0xaa, 0x25, 0x0090},
+	{0xaa, 0x21, 0x00dd},
+	{0xa0, 0x02, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0xbf, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x20, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x3a, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x98, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x10, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0xdd, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0xe4, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0xf0, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},
+	{}
+};
+static const struct usb_action cs2102_50HZ[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xaa, 0x23, 0x0000},
+	{0xaa, 0x24, 0x00af},
+	{0xaa, 0x25, 0x00c8},
+	{0xaa, 0x21, 0x0068},
+	{0xa0, 0x01, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x5f, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x90, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x1d, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x4c, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x10, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x68, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0xe3, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0xf0, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},
+	{}
+};
+static const struct usb_action cs2102_60HZScale[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xaa, 0x23, 0x0001},
+	{0xaa, 0x24, 0x0055},
+	{0xaa, 0x25, 0x00cc},
+	{0xaa, 0x21, 0x003f},
+	{0xa0, 0x02, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0xab, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x98, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x30, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0xd4, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x10, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x39, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0x70, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0xb0, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},
+	{}
+};
+static const struct usb_action cs2102_60HZ[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xaa, 0x23, 0x0000},
+	{0xaa, 0x24, 0x00aa},
+	{0xaa, 0x25, 0x00e6},
+	{0xaa, 0x21, 0x003f},
+	{0xa0, 0x01, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x55, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0xcc, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x18, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x6a, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x10, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x3f, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0xa5, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0xf0, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},
+	{}
+};
+static const struct usb_action cs2102_NoFlikerScale[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xaa, 0x23, 0x0001},
+	{0xaa, 0x24, 0x005f},
+	{0xaa, 0x25, 0x0000},
+	{0xaa, 0x21, 0x0001},
+	{0xa0, 0x02, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0xbf, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x00, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x80, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x00, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x00, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x01, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0x40, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0xa0, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},
+	{}
+};
+static const struct usb_action cs2102_NoFliker[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xaa, 0x23, 0x0000},
+	{0xaa, 0x24, 0x00af},
+	{0xaa, 0x25, 0x0080},
+	{0xaa, 0x21, 0x0001},
+	{0xa0, 0x01, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x5f, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x80, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x80, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x00, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x00, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x01, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0x40, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0xa0, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},
+	{}
+};
+
+/* CS2102_KOCOM */
+static const struct usb_action cs2102K_InitialScale[] = {
+	{0xa0, 0x11, ZC3XX_R002_CLOCKSELECT},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},
+	{0xa0, 0x08, ZC3XX_R010_CMOSSENSORSELECT},
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},
+	{0xa0, 0x03, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW},
+	{0xa0, 0xe8, ZC3XX_R09C_WINHEIGHTLOW},
+	{0xa0, 0x88, ZC3XX_R09E_WINWIDTHLOW},
+	{0xa0, 0x55, ZC3XX_R08B_I2CDEVICEADDR},
+	{0xa0, 0x18, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x00, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x0a, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x02, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x0b, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x02, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x0c, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x7c, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x0d, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0xa3, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x03, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0xfb, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x05, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x00, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x06, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x03, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x09, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x08, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x0e, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x04, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x0f, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x18, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x10, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x18, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x11, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x18, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x12, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x18, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x15, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x00, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x16, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x0c, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x17, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x0c, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x18, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x04, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0xb7, ZC3XX_R101_SENSORCORRECTION},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x78, ZC3XX_R18D_YTARGET},
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},
+	{0xa0, 0x20, ZC3XX_R087_EXPTIMEMID},
+	{0xa0, 0x21, ZC3XX_R088_EXPTIMELOW},
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},
+	{0xa0, 0x00, 0x01ad},
+	{0xa0, 0x01, 0x01b1},
+	{0xa0, 0x02, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x60, ZC3XX_R116_RGAIN},
+	{0xa0, 0x40, ZC3XX_R117_GGAIN},
+	{0xa0, 0x4c, ZC3XX_R118_BGAIN},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},	/* clock ? */
+	{0xa0, 0x08, ZC3XX_R1C6_SHARPNESS00},	/* sharpness+ */
+	{0xa0, 0x0f, ZC3XX_R1CB_SHARPNESS05},	/* sharpness- */
+	{0xa0, 0x13, ZC3XX_R120_GAMMA00},	/* gamma 4 */
+	{0xa0, 0x38, ZC3XX_R121_GAMMA01},
+	{0xa0, 0x59, ZC3XX_R122_GAMMA02},
+	{0xa0, 0x79, ZC3XX_R123_GAMMA03},
+	{0xa0, 0x92, ZC3XX_R124_GAMMA04},
+	{0xa0, 0xa7, ZC3XX_R125_GAMMA05},
+	{0xa0, 0xb9, ZC3XX_R126_GAMMA06},
+	{0xa0, 0xc8, ZC3XX_R127_GAMMA07},
+	{0xa0, 0xd4, ZC3XX_R128_GAMMA08},
+	{0xa0, 0xdf, ZC3XX_R129_GAMMA09},
+	{0xa0, 0xe7, ZC3XX_R12A_GAMMA0A},
+	{0xa0, 0xee, ZC3XX_R12B_GAMMA0B},
+	{0xa0, 0xf4, ZC3XX_R12C_GAMMA0C},
+	{0xa0, 0xf9, ZC3XX_R12D_GAMMA0D},
+	{0xa0, 0xfc, ZC3XX_R12E_GAMMA0E},
+	{0xa0, 0xff, ZC3XX_R12F_GAMMA0F},
+	{0xa0, 0x26, ZC3XX_R130_GAMMA10},
+	{0xa0, 0x22, ZC3XX_R131_GAMMA11},
+	{0xa0, 0x20, ZC3XX_R132_GAMMA12},
+	{0xa0, 0x1c, ZC3XX_R133_GAMMA13},
+	{0xa0, 0x16, ZC3XX_R134_GAMMA14},
+	{0xa0, 0x13, ZC3XX_R135_GAMMA15},
+	{0xa0, 0x10, ZC3XX_R136_GAMMA16},
+	{0xa0, 0x0d, ZC3XX_R137_GAMMA17},
+	{0xa0, 0x0b, ZC3XX_R138_GAMMA18},
+	{0xa0, 0x09, ZC3XX_R139_GAMMA19},
+	{0xa0, 0x07, ZC3XX_R13A_GAMMA1A},
+	{0xa0, 0x06, ZC3XX_R13B_GAMMA1B},
+	{0xa0, 0x05, ZC3XX_R13C_GAMMA1C},
+	{0xa0, 0x04, ZC3XX_R13D_GAMMA1D},
+	{0xa0, 0x03, ZC3XX_R13E_GAMMA1E},
+	{0xa0, 0x02, ZC3XX_R13F_GAMMA1F},
+	{0xa0, 0x58, ZC3XX_R10A_RGB00},	/* matrix */
+	{0xa0, 0xf4, ZC3XX_R10B_RGB01},
+	{0xa0, 0xf4, ZC3XX_R10C_RGB02},
+	{0xa0, 0xf4, ZC3XX_R10D_RGB10},
+	{0xa0, 0x58, ZC3XX_R10E_RGB11},
+	{0xa0, 0xf4, ZC3XX_R10F_RGB12},
+	{0xa0, 0xf4, ZC3XX_R110_RGB20},
+	{0xa0, 0xf4, ZC3XX_R111_RGB21},
+	{0xa0, 0x58, ZC3XX_R112_RGB22},
+	{0xa0, 0x00, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xa0, 0x18, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x00, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x13, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x22, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x14, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x01, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x20, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x01, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x21, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x22, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x18, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x04, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x01, ZC3XX_R0A3_EXPOSURETIMEHIGH},
+	{0xa0, 0x22, ZC3XX_R0A4_EXPOSURETIMELOW},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x07, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0xee, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x3a, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x0c, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x28, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x04, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0x0f, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0x19, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0x1f, ZC3XX_R020_HSYNC_3},
+	{0xa0, 0x60, ZC3XX_R11D_GLOBALGAIN},
+	{0xa0, 0x60, ZC3XX_R11D_GLOBALGAIN},
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x60, ZC3XX_R116_RGAIN},
+	{0xa0, 0x40, ZC3XX_R117_GGAIN},
+	{0xa0, 0x4c, ZC3XX_R118_BGAIN},
+	{0xa0, 0x04, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x20, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x01, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x21, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x5c, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x18, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x00, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x13, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x5c, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x14, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x01, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x18, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x04, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x00, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x04, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x00, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x04, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x20, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x01, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x21, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x96, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x18, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x00, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x13, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x96, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x14, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x01, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x18, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x04, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x00, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x04, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x00, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{}
+};
+
+static const struct usb_action cs2102K_Initial[] = {
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},
+	{0xa0, 0x00, ZC3XX_R002_CLOCKSELECT},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},
+	{0xa0, 0x08, ZC3XX_R010_CMOSSENSORSELECT},
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},
+	{0xa0, 0x03, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW},
+	{0xa0, 0xe8, ZC3XX_R09C_WINHEIGHTLOW},
+	{0xa0, 0x88, ZC3XX_R09E_WINWIDTHLOW},
+/*fixme: next sequence = i2c exchanges*/
+	{0xa0, 0x55, ZC3XX_R08B_I2CDEVICEADDR},
+	{0xa0, 0x18, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x00, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x0a, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x02, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x0b, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x02, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x0c, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x7b, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x0d, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0xa3, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x03, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0xfb, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x05, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x00, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x06, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x03, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x09, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x08, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x0e, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x04, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x0f, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x18, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x10, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x18, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x11, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x18, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x12, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x18, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x15, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x00, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x16, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x0c, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x17, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x0c, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x18, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x04, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0xf7, ZC3XX_R101_SENSORCORRECTION},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x78, ZC3XX_R18D_YTARGET},
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},
+	{0xa0, 0x20, ZC3XX_R087_EXPTIMEMID},
+	{0xa0, 0x21, ZC3XX_R088_EXPTIMELOW},
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},
+	{0xa0, 0x00, 0x01ad},
+	{0xa0, 0x01, 0x01b1},
+	{0xa0, 0x02, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x60, ZC3XX_R116_RGAIN},
+	{0xa0, 0x40, ZC3XX_R117_GGAIN},
+	{0xa0, 0x4c, ZC3XX_R118_BGAIN},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},	/* clock ? */
+	{0xa0, 0x08, ZC3XX_R1C6_SHARPNESS00},	/* sharpness+ */
+	{0xa0, 0x0f, ZC3XX_R1CB_SHARPNESS05},	/* sharpness- */
+	{0xa0, 0x13, ZC3XX_R120_GAMMA00},	/* gamma 4 */
+	{0xa0, 0x38, ZC3XX_R121_GAMMA01},
+	{0xa0, 0x59, ZC3XX_R122_GAMMA02},
+	{0xa0, 0x79, ZC3XX_R123_GAMMA03},
+	{0xa0, 0x92, ZC3XX_R124_GAMMA04},
+	{0xa0, 0xa7, ZC3XX_R125_GAMMA05},
+	{0xa0, 0xb9, ZC3XX_R126_GAMMA06},
+	{0xa0, 0xc8, ZC3XX_R127_GAMMA07},
+	{0xa0, 0xd4, ZC3XX_R128_GAMMA08},
+	{0xa0, 0xdf, ZC3XX_R129_GAMMA09},
+	{0xa0, 0xe7, ZC3XX_R12A_GAMMA0A},
+	{0xa0, 0xee, ZC3XX_R12B_GAMMA0B},
+	{0xa0, 0xf4, ZC3XX_R12C_GAMMA0C},
+	{0xa0, 0xf9, ZC3XX_R12D_GAMMA0D},
+	{0xa0, 0xfc, ZC3XX_R12E_GAMMA0E},
+	{0xa0, 0xff, ZC3XX_R12F_GAMMA0F},
+	{0xa0, 0x26, ZC3XX_R130_GAMMA10},
+	{0xa0, 0x22, ZC3XX_R131_GAMMA11},
+	{0xa0, 0x20, ZC3XX_R132_GAMMA12},
+	{0xa0, 0x1c, ZC3XX_R133_GAMMA13},
+	{0xa0, 0x16, ZC3XX_R134_GAMMA14},
+	{0xa0, 0x13, ZC3XX_R135_GAMMA15},
+	{0xa0, 0x10, ZC3XX_R136_GAMMA16},
+	{0xa0, 0x0d, ZC3XX_R137_GAMMA17},
+	{0xa0, 0x0b, ZC3XX_R138_GAMMA18},
+	{0xa0, 0x09, ZC3XX_R139_GAMMA19},
+	{0xa0, 0x07, ZC3XX_R13A_GAMMA1A},
+	{0xa0, 0x06, ZC3XX_R13B_GAMMA1B},
+	{0xa0, 0x05, ZC3XX_R13C_GAMMA1C},
+	{0xa0, 0x04, ZC3XX_R13D_GAMMA1D},
+	{0xa0, 0x03, ZC3XX_R13E_GAMMA1E},
+	{0xa0, 0x02, ZC3XX_R13F_GAMMA1F},
+	{0xa0, 0x58, ZC3XX_R10A_RGB00},	/* matrix */
+	{0xa0, 0xf4, ZC3XX_R10B_RGB01},
+	{0xa0, 0xf4, ZC3XX_R10C_RGB02},
+	{0xa0, 0xf4, ZC3XX_R10D_RGB10},
+	{0xa0, 0x58, ZC3XX_R10E_RGB11},
+	{0xa0, 0xf4, ZC3XX_R10F_RGB12},
+	{0xa0, 0xf4, ZC3XX_R110_RGB20},
+	{0xa0, 0xf4, ZC3XX_R111_RGB21},
+	{0xa0, 0x58, ZC3XX_R112_RGB22},
+	{0xa0, 0x00, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xa0, 0x18, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x00, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x13, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x22, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x14, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x01, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x20, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x01, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x21, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x22, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x18, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x04, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x01, ZC3XX_R0A3_EXPOSURETIMEHIGH},
+	{0xa0, 0x22, ZC3XX_R0A4_EXPOSURETIMELOW},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x07, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0xee, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x3a, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x0c, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x28, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x04, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0x0f, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0x19, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0x1f, ZC3XX_R020_HSYNC_3},
+	{0xa0, 0x60, ZC3XX_R11D_GLOBALGAIN},
+	{0xa0, 0x60, ZC3XX_R11D_GLOBALGAIN},
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x60, ZC3XX_R116_RGAIN},
+	{0xa0, 0x40, ZC3XX_R117_GGAIN},
+	{0xa0, 0x4c, ZC3XX_R118_BGAIN},
+	{0xa0, 0x04, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x20, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x01, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x21, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x5c, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x18, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x00, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x13, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x5c, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x14, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x01, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x18, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x04, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x00, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x04, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x00, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x04, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x20, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x01, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x21, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x96, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x18, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x00, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x13, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x96, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x14, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x01, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x18, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x04, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x00, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x04, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x00, ZC3XX_R1A7_CALCGLOBALMEAN},
+/*fixme:what does the next sequence?*/
+	{0xa0, 0x04, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x00, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x04, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x20, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x01, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x21, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0xd0, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x18, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x00, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x13, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0xd0, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x14, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x01, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x18, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x04, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x00, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x02, ZC3XX_R008_CLOCKSETTING},
+	{0xa0, 0x04, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x00, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x04, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x20, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x02, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x21, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x0a, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x18, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x00, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x13, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x0a, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x14, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x02, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x18, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x04, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x00, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x04, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x00, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x04, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x20, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x02, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x21, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x44, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x18, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x00, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x13, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x44, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x14, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x02, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x18, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x04, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x00, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x04, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x00, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x04, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x20, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x02, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x21, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x7e, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x18, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x00, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x13, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x7e, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x14, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x02, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x18, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x04, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x00, ZC3XX_R094_I2CWRITEACK},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa0, 0x00, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x04, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x00, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x04, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{}
+};
+
+static const struct usb_action gc0305_Initial[] = {	/* 640x480 */
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},	/* 00,00,01,cc */
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},	/* 00,08,03,cc */
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,01,cc */
+	{0xa0, 0x04, ZC3XX_R002_CLOCKSELECT},	/* 00,02,04,cc */
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},	/* 00,03,02,cc */
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},	/* 00,04,80,cc */
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},	/* 00,05,01,cc */
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},	/* 00,06,e0,cc */
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},	/* 00,01,01,cc */
+	{0xa0, 0x03, ZC3XX_R012_VIDEOCONTROLFUNC},	/* 00,12,03,cc */
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC},	/* 00,12,01,cc */
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},	/* 00,98,00,cc */
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW},	/* 00,9a,00,cc */
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},	/* 01,1a,00,cc */
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW},	/* 01,1c,00,cc */
+	{0xa0, 0xe6, ZC3XX_R09C_WINHEIGHTLOW},	/* 00,9c,e6,cc */
+	{0xa0, 0x86, ZC3XX_R09E_WINWIDTHLOW},	/* 00,9e,86,cc */
+	{0xa0, 0x98, ZC3XX_R08B_I2CDEVICEADDR},	/* 00,8b,98,cc */
+	{0xaa, 0x13, 0x0002},	/* 00,13,02,aa */
+	{0xaa, 0x15, 0x0003},	/* 00,15,03,aa */
+	{0xaa, 0x01, 0x0000},	/* 00,01,00,aa */
+	{0xaa, 0x02, 0x0000},	/* 00,02,00,aa */
+	{0xaa, 0x1a, 0x0000},	/* 00,1a,00,aa */
+	{0xaa, 0x1c, 0x0017},	/* 00,1c,17,aa */
+	{0xaa, 0x1d, 0x0080},	/* 00,1d,80,aa */
+	{0xaa, 0x1f, 0x0008},	/* 00,1f,08,aa */
+	{0xaa, 0x21, 0x0012},	/* 00,21,12,aa */
+	{0xa0, 0x82, ZC3XX_R086_EXPTIMEHIGH},	/* 00,86,82,cc */
+	{0xa0, 0x83, ZC3XX_R087_EXPTIMEMID},	/* 00,87,83,cc */
+	{0xa0, 0x84, ZC3XX_R088_EXPTIMELOW},	/* 00,88,84,cc */
+	{0xaa, 0x05, 0x0000},	/* 00,05,00,aa */
+	{0xaa, 0x0a, 0x0000},	/* 00,0a,00,aa */
+	{0xaa, 0x0b, 0x00b0},	/* 00,0b,b0,aa */
+	{0xaa, 0x0c, 0x0000},	/* 00,0c,00,aa */
+	{0xaa, 0x0d, 0x00b0},	/* 00,0d,b0,aa */
+	{0xaa, 0x0e, 0x0000},	/* 00,0e,00,aa */
+	{0xaa, 0x0f, 0x00b0},	/* 00,0f,b0,aa */
+	{0xaa, 0x10, 0x0000},	/* 00,10,00,aa */
+	{0xaa, 0x11, 0x00b0},	/* 00,11,b0,aa */
+	{0xaa, 0x16, 0x0001},	/* 00,16,01,aa */
+	{0xaa, 0x17, 0x00e6},	/* 00,17,e6,aa */
+	{0xaa, 0x18, 0x0002},	/* 00,18,02,aa */
+	{0xaa, 0x19, 0x0086},	/* 00,19,86,aa */
+	{0xaa, 0x20, 0x0000},	/* 00,20,00,aa */
+	{0xaa, 0x1b, 0x0020},	/* 00,1b,20,aa */
+	{0xa0, 0xb7, ZC3XX_R101_SENSORCORRECTION},	/* 01,01,b7,cc */
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},	/* 00,12,05,cc */
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},	/* 01,00,0d,cc */
+	{0xa0, 0x76, ZC3XX_R189_AWBSTATUS},	/* 01,89,76,cc */
+	{0xa0, 0x09, 0x01ad},	/* 01,ad,09,cc */
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},	/* 01,c5,03,cc */
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},	/* 01,cb,13,cc */
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},	/* 02,50,08,cc */
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},	/* 03,01,08,cc */
+	{0xa0, 0x60, ZC3XX_R1A8_DIGITALGAIN},	/* 01,a8,60,cc */
+	{0xa0, 0x85, ZC3XX_R18D_YTARGET},	/* 01,8d,85,cc */
+	{0xa0, 0x00, 0x011e},	/* 01,1e,00,cc */
+	{0xa0, 0x52, ZC3XX_R116_RGAIN},	/* 01,16,52,cc */
+	{0xa0, 0x40, ZC3XX_R117_GGAIN},	/* 01,17,40,cc */
+	{0xa0, 0x52, ZC3XX_R118_BGAIN},	/* 01,18,52,cc */
+	{0xa0, 0x03, ZC3XX_R113_RGB03},	/* 01,13,03,cc */
+	{}
+};
+static const struct usb_action gc0305_InitialScale[] = { /* 320x240 */
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},	/* 00,00,01,cc */
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},	/* 00,08,03,cc */
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,01,cc */
+	{0xa0, 0x10, ZC3XX_R002_CLOCKSELECT},	/* 00,02,10,cc */
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},	/* 00,03,02,cc */
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},	/* 00,04,80,cc */
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},	/* 00,05,01,cc */
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},	/* 00,06,e0,cc */
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},	/* 00,01,01,cc */
+	{0xa0, 0x03, ZC3XX_R012_VIDEOCONTROLFUNC},	/* 00,12,03,cc */
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC},	/* 00,12,01,cc */
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},	/* 00,98,00,cc */
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW},	/* 00,9a,00,cc */
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},	/* 01,1a,00,cc */
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW},	/* 01,1c,00,cc */
+	{0xa0, 0xe8, ZC3XX_R09C_WINHEIGHTLOW},	/* 00,9c,e8,cc */
+	{0xa0, 0x88, ZC3XX_R09E_WINWIDTHLOW},	/* 00,9e,88,cc */
+	{0xa0, 0x98, ZC3XX_R08B_I2CDEVICEADDR},	/* 00,8b,98,cc */
+	{0xaa, 0x13, 0x0000},	/* 00,13,00,aa */
+	{0xaa, 0x15, 0x0001},	/* 00,15,01,aa */
+	{0xaa, 0x01, 0x0000},	/* 00,01,00,aa */
+	{0xaa, 0x02, 0x0000},	/* 00,02,00,aa */
+	{0xaa, 0x1a, 0x0000},	/* 00,1a,00,aa */
+	{0xaa, 0x1c, 0x0017},	/* 00,1c,17,aa */
+	{0xaa, 0x1d, 0x0080},	/* 00,1d,80,aa */
+	{0xaa, 0x1f, 0x0008},	/* 00,1f,08,aa */
+	{0xaa, 0x21, 0x0012},	/* 00,21,12,aa */
+	{0xa0, 0x82, ZC3XX_R086_EXPTIMEHIGH},	/* 00,86,82,cc */
+	{0xa0, 0x83, ZC3XX_R087_EXPTIMEMID},	/* 00,87,83,cc */
+	{0xa0, 0x84, ZC3XX_R088_EXPTIMELOW},	/* 00,88,84,cc */
+	{0xaa, 0x05, 0x0000},	/* 00,05,00,aa */
+	{0xaa, 0x0a, 0x0000},	/* 00,0a,00,aa */
+	{0xaa, 0x0b, 0x00b0},	/* 00,0b,b0,aa */
+	{0xaa, 0x0c, 0x0000},	/* 00,0c,00,aa */
+	{0xaa, 0x0d, 0x00b0},	/* 00,0d,b0,aa */
+	{0xaa, 0x0e, 0x0000},	/* 00,0e,00,aa */
+	{0xaa, 0x0f, 0x00b0},	/* 00,0f,b0,aa */
+	{0xaa, 0x10, 0x0000},	/* 00,10,00,aa */
+	{0xaa, 0x11, 0x00b0},	/* 00,11,b0,aa */
+	{0xaa, 0x16, 0x0001},	/* 00,16,01,aa */
+	{0xaa, 0x17, 0x00e8},	/* 00,17,e8,aa */
+	{0xaa, 0x18, 0x0002},	/* 00,18,02,aa */
+	{0xaa, 0x19, 0x0088},	/* 00,19,88,aa */
+	{0xaa, 0x20, 0x0000},	/* 00,20,00,aa */
+	{0xaa, 0x1b, 0x0020},	/* 00,1b,20,aa */
+	{0xa0, 0xb7, ZC3XX_R101_SENSORCORRECTION},	/* 01,01,b7,cc */
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},	/* 00,12,05,cc */
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},	/* 01,00,0d,cc */
+	{0xa0, 0x76, ZC3XX_R189_AWBSTATUS},	/* 01,89,76,cc */
+	{0xa0, 0x09, 0x01ad},	/* 01,ad,09,cc */
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},	/* 01,c5,03,cc */
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},	/* 01,cb,13,cc */
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},	/* 02,50,08,cc */
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},	/* 03,01,08,cc */
+	{0xa0, 0x60, ZC3XX_R1A8_DIGITALGAIN},	/* 01,a8,60,cc */
+	{0xa0, 0x00, 0x011e},	/* 01,1e,00,cc */
+	{0xa0, 0x52, ZC3XX_R116_RGAIN},	/* 01,16,52,cc */
+	{0xa0, 0x40, ZC3XX_R117_GGAIN},	/* 01,17,40,cc */
+	{0xa0, 0x52, ZC3XX_R118_BGAIN},	/* 01,18,52,cc */
+	{0xa0, 0x03, ZC3XX_R113_RGB03},	/* 01,13,03,cc */
+	{}
+};
+static const struct usb_action gc0305_50HZ[] = {
+	{0xaa, 0x82, 0x0000},	/* 00,82,00,aa */
+	{0xaa, 0x83, 0x0002},	/* 00,83,02,aa */
+	{0xaa, 0x84, 0x0038},	/* 00,84,38,aa */	/* win: 00,84,ec */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},	/* 01,90,00,cc */
+	{0xa0, 0x0b, ZC3XX_R191_EXPOSURELIMITMID},	/* 01,91,0b,cc */
+	{0xa0, 0x18, ZC3XX_R192_EXPOSURELIMITLOW},	/* 01,92,18,cc */
+							/* win: 01,92,10 */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},	/* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},	/* 01,96,00,cc */
+	{0xa0, 0x8e, ZC3XX_R197_ANTIFLICKERLOW},	/* 01,97,8e,cc */
+							/* win: 01,97,ec */
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},	/* 01,8c,0e,cc */
+	{0xa0, 0x15, ZC3XX_R18F_AEUNFREEZE},	/* 01,8f,15,cc */
+	{0xa0, 0x10, ZC3XX_R1A9_DIGITALLIMITDIFF},	/* 01,a9,10,cc */
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},	/* 01,aa,24,cc */
+	{0xa0, 0x62, ZC3XX_R01D_HSYNC_0},	/* 00,1d,62,cc */
+	{0xa0, 0x90, ZC3XX_R01E_HSYNC_1},	/* 00,1e,90,cc */
+	{0xa0, 0xc8, ZC3XX_R01F_HSYNC_2},	/* 00,1f,c8,cc */
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},	/* 00,20,ff,cc */
+	{0xa0, 0x60, ZC3XX_R11D_GLOBALGAIN},	/* 01,1d,60,cc */
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},	/* 01,80,42,cc */
+/*	{0xa0, 0x85, ZC3XX_R18D_YTARGET},	 * 01,8d,85,cc *
+						 * if 640x480 */
+	{}
+};
+static const struct usb_action gc0305_60HZ[] = {
+	{0xaa, 0x82, 0x0000},	/* 00,82,00,aa */
+	{0xaa, 0x83, 0x0000},	/* 00,83,00,aa */
+	{0xaa, 0x84, 0x00ec},	/* 00,84,ec,aa */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},	/* 01,90,00,cc */
+	{0xa0, 0x0b, ZC3XX_R191_EXPOSURELIMITMID},	/* 01,91,0b,cc */
+	{0xa0, 0x10, ZC3XX_R192_EXPOSURELIMITLOW},	/* 01,92,10,cc */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},	/* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},	/* 01,96,00,cc */
+	{0xa0, 0xec, ZC3XX_R197_ANTIFLICKERLOW},	/* 01,97,ec,cc */
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},	/* 01,8c,0e,cc */
+	{0xa0, 0x15, ZC3XX_R18F_AEUNFREEZE},	/* 01,8f,15,cc */
+	{0xa0, 0x10, ZC3XX_R1A9_DIGITALLIMITDIFF},	/* 01,a9,10,cc */
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},	/* 01,aa,24,cc */
+	{0xa0, 0x62, ZC3XX_R01D_HSYNC_0},	/* 00,1d,62,cc */
+	{0xa0, 0x90, ZC3XX_R01E_HSYNC_1},	/* 00,1e,90,cc */
+	{0xa0, 0xc8, ZC3XX_R01F_HSYNC_2},	/* 00,1f,c8,cc */
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},	/* 00,20,ff,cc */
+	{0xa0, 0x60, ZC3XX_R11D_GLOBALGAIN},	/* 01,1d,60,cc */
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},	/* 01,80,42,cc */
+	{0xa0, 0x80, ZC3XX_R18D_YTARGET},	/* 01,8d,80,cc */
+	{}
+};
+
+static const struct usb_action gc0305_NoFliker[] = {
+	{0xa0, 0x0c, ZC3XX_R100_OPERATIONMODE},	/* 01,00,0c,cc */
+	{0xaa, 0x82, 0x0000},	/* 00,82,00,aa */
+	{0xaa, 0x83, 0x0000},	/* 00,83,00,aa */
+	{0xaa, 0x84, 0x0020},	/* 00,84,20,aa */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},	/* 01,90,00,cc */
+	{0xa0, 0x00, ZC3XX_R191_EXPOSURELIMITMID},	/* 01,91,00,cc */
+	{0xa0, 0x48, ZC3XX_R192_EXPOSURELIMITLOW},	/* 01,92,48,cc */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},	/* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},	/* 01,96,00,cc */
+	{0xa0, 0x10, ZC3XX_R197_ANTIFLICKERLOW},	/* 01,97,10,cc */
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},	/* 01,8c,0e,cc */
+	{0xa0, 0x15, ZC3XX_R18F_AEUNFREEZE},	/* 01,8f,15,cc */
+	{0xa0, 0x62, ZC3XX_R01D_HSYNC_0},	/* 00,1d,62,cc */
+	{0xa0, 0x90, ZC3XX_R01E_HSYNC_1},	/* 00,1e,90,cc */
+	{0xa0, 0xc8, ZC3XX_R01F_HSYNC_2},	/* 00,1f,c8,cc */
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},	/* 00,20,ff,cc */
+	{0xa0, 0x60, ZC3XX_R11D_GLOBALGAIN},	/* 01,1d,60,cc */
+	{0xa0, 0x03, ZC3XX_R180_AUTOCORRECTENABLE},	/* 01,80,03,cc */
+	{0xa0, 0x80, ZC3XX_R18D_YTARGET},	/* 01,8d,80,cc */
+	{}
+};
+
+static const struct usb_action hdcs2020_InitialScale[] = {
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},
+	{0xa0, 0x11, ZC3XX_R002_CLOCKSELECT},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},	/* qtable 0x05 */
+	{0xa0, 0x08, ZC3XX_R010_CMOSSENSORSELECT},
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},
+	{0xa0, 0x03, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW},
+	{0xa0, 0xe8, ZC3XX_R09C_WINHEIGHTLOW},
+	{0xa0, 0x88, ZC3XX_R09E_WINWIDTHLOW},
+	{0xaa, 0x1c, 0x0000},
+	{0xaa, 0x0a, 0x0001},
+	{0xaa, 0x0b, 0x0006},
+	{0xaa, 0x0c, 0x007b},
+	{0xaa, 0x0d, 0x00a7},
+	{0xaa, 0x03, 0x00fb},
+	{0xaa, 0x05, 0x0000},
+	{0xaa, 0x06, 0x0003},
+	{0xaa, 0x09, 0x0008},
+
+	{0xaa, 0x0f, 0x0018},	/* set sensor gain */
+	{0xaa, 0x10, 0x0018},
+	{0xaa, 0x11, 0x0018},
+	{0xaa, 0x12, 0x0018},
+
+	{0xaa, 0x15, 0x004e},
+	{0xaa, 0x1c, 0x0004},
+	{0xa0, 0xb7, ZC3XX_R101_SENSORCORRECTION},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x70, ZC3XX_R18D_YTARGET},
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},
+	{0xa1, 0x01, 0x0002},
+	{0xa1, 0x01, 0x0008},
+	{0xa1, 0x01, 0x0180},
+	{0xa0, 0x02, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x40, ZC3XX_R116_RGAIN},
+	{0xa0, 0x40, ZC3XX_R117_GGAIN},
+	{0xa0, 0x40, ZC3XX_R118_BGAIN},
+	{0xa1, 0x01, 0x0008},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},	/* clock ? */
+	{0xa0, 0x08, ZC3XX_R1C6_SHARPNESS00},	/* sharpness+ */
+	{0xa1, 0x01, 0x01c8},
+	{0xa1, 0x01, 0x01c9},
+	{0xa1, 0x01, 0x01ca},
+	{0xa0, 0x0f, ZC3XX_R1CB_SHARPNESS05},	/* sharpness- */
+	{0xa0, 0x13, ZC3XX_R120_GAMMA00},	/* gamma 4 */
+	{0xa0, 0x38, ZC3XX_R121_GAMMA01},
+	{0xa0, 0x59, ZC3XX_R122_GAMMA02},
+	{0xa0, 0x79, ZC3XX_R123_GAMMA03},
+	{0xa0, 0x92, ZC3XX_R124_GAMMA04},
+	{0xa0, 0xa7, ZC3XX_R125_GAMMA05},
+	{0xa0, 0xb9, ZC3XX_R126_GAMMA06},
+	{0xa0, 0xc8, ZC3XX_R127_GAMMA07},
+	{0xa0, 0xd4, ZC3XX_R128_GAMMA08},
+	{0xa0, 0xdf, ZC3XX_R129_GAMMA09},
+	{0xa0, 0xe7, ZC3XX_R12A_GAMMA0A},
+	{0xa0, 0xee, ZC3XX_R12B_GAMMA0B},
+	{0xa0, 0xf4, ZC3XX_R12C_GAMMA0C},
+	{0xa0, 0xf9, ZC3XX_R12D_GAMMA0D},
+	{0xa0, 0xfc, ZC3XX_R12E_GAMMA0E},
+	{0xa0, 0xff, ZC3XX_R12F_GAMMA0F},
+	{0xa0, 0x26, ZC3XX_R130_GAMMA10},
+	{0xa0, 0x22, ZC3XX_R131_GAMMA11},
+	{0xa0, 0x20, ZC3XX_R132_GAMMA12},
+	{0xa0, 0x1c, ZC3XX_R133_GAMMA13},
+	{0xa0, 0x16, ZC3XX_R134_GAMMA14},
+	{0xa0, 0x13, ZC3XX_R135_GAMMA15},
+	{0xa0, 0x10, ZC3XX_R136_GAMMA16},
+	{0xa0, 0x0d, ZC3XX_R137_GAMMA17},
+	{0xa0, 0x0b, ZC3XX_R138_GAMMA18},
+	{0xa0, 0x09, ZC3XX_R139_GAMMA19},
+	{0xa0, 0x07, ZC3XX_R13A_GAMMA1A},
+	{0xa0, 0x06, ZC3XX_R13B_GAMMA1B},
+	{0xa0, 0x05, ZC3XX_R13C_GAMMA1C},
+	{0xa0, 0x04, ZC3XX_R13D_GAMMA1D},
+	{0xa0, 0x03, ZC3XX_R13E_GAMMA1E},
+	{0xa0, 0x02, ZC3XX_R13F_GAMMA1F},
+
+	{0xa0, 0x66, ZC3XX_R10A_RGB00},	/* matrix */
+	{0xa0, 0xed, ZC3XX_R10B_RGB01},
+	{0xa0, 0xed, ZC3XX_R10C_RGB02},
+	{0xa0, 0xed, ZC3XX_R10D_RGB10},
+	{0xa0, 0x66, ZC3XX_R10E_RGB11},
+	{0xa0, 0xed, ZC3XX_R10F_RGB12},
+	{0xa0, 0xed, ZC3XX_R110_RGB20},
+	{0xa0, 0xed, ZC3XX_R111_RGB21},
+	{0xa0, 0x66, ZC3XX_R112_RGB22},
+
+	{0xa1, 0x01, 0x0180},
+	{0xa0, 0x00, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xaa, 0x13, 0x0031},
+	{0xaa, 0x14, 0x0001},
+	{0xaa, 0x0e, 0x0004},
+	{0xaa, 0x19, 0x00cd},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x02, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x62, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x3d, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE},
+
+	{0xa0, 0x0c, ZC3XX_R1A9_DIGITALLIMITDIFF},	/* 0x14 */
+	{0xa0, 0x28, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x04, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0x18, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0x2c, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0x41, ZC3XX_R020_HSYNC_3},
+	{0xa0, 0x60, ZC3XX_R11D_GLOBALGAIN},
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa1, 0x01, 0x0180},
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x40, ZC3XX_R116_RGAIN},
+	{0xa0, 0x40, ZC3XX_R117_GGAIN},
+	{0xa0, 0x40, ZC3XX_R118_BGAIN},
+	{}
+};
+static const struct usb_action hdcs2020_Initial[] = {
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},
+	{0xa0, 0x00, ZC3XX_R002_CLOCKSELECT},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},
+	{0xa0, 0x08, ZC3XX_R010_CMOSSENSORSELECT},
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},
+	{0xa0, 0x03, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW},
+	{0xa0, 0xe8, ZC3XX_R09C_WINHEIGHTLOW},
+	{0xa0, 0x88, ZC3XX_R09E_WINWIDTHLOW},
+	{0xaa, 0x1c, 0x0000},
+	{0xaa, 0x0a, 0x0001},
+	{0xaa, 0x0b, 0x0006},
+	{0xaa, 0x0c, 0x007a},
+	{0xaa, 0x0d, 0x00a7},
+	{0xaa, 0x03, 0x00fb},
+	{0xaa, 0x05, 0x0000},
+	{0xaa, 0x06, 0x0003},
+	{0xaa, 0x09, 0x0008},
+	{0xaa, 0x0f, 0x0018},	/* original setting */
+	{0xaa, 0x10, 0x0018},
+	{0xaa, 0x11, 0x0018},
+	{0xaa, 0x12, 0x0018},
+	{0xaa, 0x15, 0x004e},
+	{0xaa, 0x1c, 0x0004},
+	{0xa0, 0xf7, ZC3XX_R101_SENSORCORRECTION},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x70, ZC3XX_R18D_YTARGET},
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},
+	{0xa1, 0x01, 0x0002},
+	{0xa1, 0x01, 0x0008},
+	{0xa1, 0x01, 0x0180},
+	{0xa0, 0x02, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x40, ZC3XX_R116_RGAIN},
+	{0xa0, 0x40, ZC3XX_R117_GGAIN},
+	{0xa0, 0x40, ZC3XX_R118_BGAIN},
+	{0xa1, 0x01, 0x0008},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},	/* clock ? */
+	{0xa0, 0x08, ZC3XX_R1C6_SHARPNESS00},	/* sharpness+ */
+	{0xa1, 0x01, 0x01c8},
+	{0xa1, 0x01, 0x01c9},
+	{0xa1, 0x01, 0x01ca},
+	{0xa0, 0x0f, ZC3XX_R1CB_SHARPNESS05},	/* sharpness- */
+	{0xa0, 0x13, ZC3XX_R120_GAMMA00},	/* gamma 4 */
+	{0xa0, 0x38, ZC3XX_R121_GAMMA01},
+	{0xa0, 0x59, ZC3XX_R122_GAMMA02},
+	{0xa0, 0x79, ZC3XX_R123_GAMMA03},
+	{0xa0, 0x92, ZC3XX_R124_GAMMA04},
+	{0xa0, 0xa7, ZC3XX_R125_GAMMA05},
+	{0xa0, 0xb9, ZC3XX_R126_GAMMA06},
+	{0xa0, 0xc8, ZC3XX_R127_GAMMA07},
+	{0xa0, 0xd4, ZC3XX_R128_GAMMA08},
+	{0xa0, 0xdf, ZC3XX_R129_GAMMA09},
+	{0xa0, 0xe7, ZC3XX_R12A_GAMMA0A},
+	{0xa0, 0xee, ZC3XX_R12B_GAMMA0B},
+	{0xa0, 0xf4, ZC3XX_R12C_GAMMA0C},
+	{0xa0, 0xf9, ZC3XX_R12D_GAMMA0D},
+	{0xa0, 0xfc, ZC3XX_R12E_GAMMA0E},
+	{0xa0, 0xff, ZC3XX_R12F_GAMMA0F},
+	{0xa0, 0x26, ZC3XX_R130_GAMMA10},
+	{0xa0, 0x22, ZC3XX_R131_GAMMA11},
+	{0xa0, 0x20, ZC3XX_R132_GAMMA12},
+	{0xa0, 0x1c, ZC3XX_R133_GAMMA13},
+	{0xa0, 0x16, ZC3XX_R134_GAMMA14},
+	{0xa0, 0x13, ZC3XX_R135_GAMMA15},
+	{0xa0, 0x10, ZC3XX_R136_GAMMA16},
+	{0xa0, 0x0d, ZC3XX_R137_GAMMA17},
+	{0xa0, 0x0b, ZC3XX_R138_GAMMA18},
+	{0xa0, 0x09, ZC3XX_R139_GAMMA19},
+	{0xa0, 0x07, ZC3XX_R13A_GAMMA1A},
+	{0xa0, 0x06, ZC3XX_R13B_GAMMA1B},
+	{0xa0, 0x05, ZC3XX_R13C_GAMMA1C},
+	{0xa0, 0x04, ZC3XX_R13D_GAMMA1D},
+	{0xa0, 0x03, ZC3XX_R13E_GAMMA1E},
+	{0xa0, 0x02, ZC3XX_R13F_GAMMA1F},
+	{0xa0, 0x66, ZC3XX_R10A_RGB00},	/* matrix */
+	{0xa0, 0xed, ZC3XX_R10B_RGB01},
+	{0xa0, 0xed, ZC3XX_R10C_RGB02},
+	{0xa0, 0xed, ZC3XX_R10D_RGB10},
+	{0xa0, 0x66, ZC3XX_R10E_RGB11},
+	{0xa0, 0xed, ZC3XX_R10F_RGB12},
+	{0xa0, 0xed, ZC3XX_R110_RGB20},
+	{0xa0, 0xed, ZC3XX_R111_RGB21},
+	{0xa0, 0x66, ZC3XX_R112_RGB22},
+	{0xa1, 0x01, 0x0180},
+	{0xa0, 0x00, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+ /**** set exposure ***/
+	{0xaa, 0x13, 0x0031},
+	{0xaa, 0x14, 0x0001},
+	{0xaa, 0x0e, 0x0004},
+	{0xaa, 0x19, 0x00cd},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x02, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x62, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x3d, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x0c, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x28, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x04, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0x18, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0x2c, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0x41, ZC3XX_R020_HSYNC_3},
+	{0xa0, 0x60, ZC3XX_R11D_GLOBALGAIN},
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa1, 0x01, 0x0180},
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x40, ZC3XX_R116_RGAIN},
+	{0xa0, 0x40, ZC3XX_R117_GGAIN},
+	{0xa0, 0x40, ZC3XX_R118_BGAIN},
+	{}
+};
+static const struct usb_action hdcs2020_50HZ[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS}, /* 00,19,00,cc */
+	{0xaa, 0x13, 0x0018},			/* 00,13,18,aa */
+	{0xaa, 0x14, 0x0001},			/* 00,14,01,aa */
+	{0xaa, 0x0e, 0x0005},			/* 00,0e,05,aa */
+	{0xaa, 0x19, 0x001f},			/* 00,19,1f,aa */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH}, /* 01,90,00,cc */
+	{0xa0, 0x02, ZC3XX_R191_EXPOSURELIMITMID}, /* 01,91,02,cc */
+	{0xa0, 0x76, ZC3XX_R192_EXPOSURELIMITLOW}, /* 01,92,76,cc */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH}, /* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID}, /* 01,96,00,cc */
+	{0xa0, 0x46, ZC3XX_R197_ANTIFLICKERLOW}, /* 01,97,46,cc */
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE}, /* 01,8c,10,cc */
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE}, /* 01,8f,20,cc */
+	{0xa0, 0x0c, ZC3XX_R1A9_DIGITALLIMITDIFF}, /* 01,a9,0c,cc */
+	{0xa0, 0x28, ZC3XX_R1AA_DIGITALGAINSTEP}, /* 01,aa,28,cc */
+	{0xa0, 0x05, ZC3XX_R01D_HSYNC_0}, /* 00,1d,05,cc */
+	{0xa0, 0x1a, ZC3XX_R01E_HSYNC_1}, /* 00,1e,1a,cc */
+	{0xa0, 0x2f, ZC3XX_R01F_HSYNC_2}, /* 00,1f,2f,cc */
+	{}
+};
+static const struct usb_action hdcs2020_60HZ[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS}, /* 00,19,00,cc */
+	{0xaa, 0x13, 0x0031},			/* 00,13,31,aa */
+	{0xaa, 0x14, 0x0001},			/* 00,14,01,aa */
+	{0xaa, 0x0e, 0x0004},			/* 00,0e,04,aa */
+	{0xaa, 0x19, 0x00cd},			/* 00,19,cd,aa */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH}, /* 01,90,00,cc */
+	{0xa0, 0x02, ZC3XX_R191_EXPOSURELIMITMID}, /* 01,91,02,cc */
+	{0xa0, 0x62, ZC3XX_R192_EXPOSURELIMITLOW}, /* 01,92,62,cc */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH}, /* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID}, /* 01,96,00,cc */
+	{0xa0, 0x3d, ZC3XX_R197_ANTIFLICKERLOW}, /* 01,97,3d,cc */
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE}, /* 01,8c,10,cc */
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE}, /* 01,8f,20,cc */
+	{0xa0, 0x0c, ZC3XX_R1A9_DIGITALLIMITDIFF}, /* 01,a9,0c,cc */
+	{0xa0, 0x28, ZC3XX_R1AA_DIGITALGAINSTEP}, /* 01,aa,28,cc */
+	{0xa0, 0x04, ZC3XX_R01D_HSYNC_0}, /* 00,1d,04,cc */
+	{0xa0, 0x18, ZC3XX_R01E_HSYNC_1}, /* 00,1e,18,cc */
+	{0xa0, 0x2c, ZC3XX_R01F_HSYNC_2}, /* 00,1f,2c,cc */
+	{}
+};
+static const struct usb_action hdcs2020_NoFliker[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS}, /* 00,19,00,cc */
+	{0xaa, 0x13, 0x0010},			/* 00,13,10,aa */
+	{0xaa, 0x14, 0x0001},			/* 00,14,01,aa */
+	{0xaa, 0x0e, 0x0004},			/* 00,0e,04,aa */
+	{0xaa, 0x19, 0x0000},			/* 00,19,00,aa */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH}, /* 01,90,00,cc */
+	{0xa0, 0x02, ZC3XX_R191_EXPOSURELIMITMID}, /* 01,91,02,cc */
+	{0xa0, 0x70, ZC3XX_R192_EXPOSURELIMITLOW}, /* 01,92,70,cc */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH}, /* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID}, /* 01,96,00,cc */
+	{0xa0, 0x10, ZC3XX_R197_ANTIFLICKERLOW}, /* 01,97,10,cc */
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE}, /* 01,8c,10,cc */
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE}, /* 01,8f,20,cc */
+	{0xa0, 0x00, ZC3XX_R1A9_DIGITALLIMITDIFF}, /* 01,a9,00,cc */
+	{0xa0, 0x00, ZC3XX_R1AA_DIGITALGAINSTEP}, /* 01,aa,00,cc */
+	{0xa0, 0x04, ZC3XX_R01D_HSYNC_0}, /* 00,1d,04,cc */
+	{0xa0, 0x17, ZC3XX_R01E_HSYNC_1}, /* 00,1e,17,cc */
+	{0xa0, 0x2a, ZC3XX_R01F_HSYNC_2}, /* 00,1f,2a,cc */
+	{}
+};
+
+static const struct usb_action hv7131b_InitialScale[] = {	/* 320x240 */
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},
+	{0xa0, 0x10, ZC3XX_R002_CLOCKSELECT},
+	{0xa0, 0x00, ZC3XX_R010_CMOSSENSORSELECT},
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},
+	{0xa0, 0x77, ZC3XX_R101_SENSORCORRECTION},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},	/* 00 */
+	{0xa0, 0x03, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW},
+	{0xaa, 0x30, 0x002d},
+	{0xaa, 0x01, 0x0005},
+	{0xaa, 0x11, 0x0000},
+	{0xaa, 0x13, 0x0001},	/* {0xaa, 0x13, 0x0000}, */
+	{0xaa, 0x14, 0x0001},
+	{0xaa, 0x15, 0x00e8},
+	{0xaa, 0x16, 0x0002},
+	{0xaa, 0x17, 0x0086},		/* 00,17,88,aa */
+	{0xaa, 0x31, 0x0038},
+	{0xaa, 0x32, 0x0038},
+	{0xaa, 0x33, 0x0038},
+	{0xaa, 0x5b, 0x0001},
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},
+	{0xa0, 0x68, ZC3XX_R18D_YTARGET},
+	{0xa0, 0x60, ZC3XX_R1A8_DIGITALGAIN},
+	{0xa0, 0x00, 0x01ad},
+	{0xa0, 0xc0, 0x019b},
+	{0xa0, 0xa0, 0x019c},
+	{0xa0, 0x02, ZC3XX_R188_MINGAIN},
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},
+	{0xaa, 0x02, 0x0090},			/* 00,02,80,aa */
+	{}
+};
+
+static const struct usb_action hv7131b_Initial[] = {	/* 640x480*/
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},
+	{0xa0, 0x00, ZC3XX_R002_CLOCKSELECT},
+	{0xa0, 0x00, ZC3XX_R010_CMOSSENSORSELECT},
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},
+	{0xa0, 0x37, ZC3XX_R101_SENSORCORRECTION},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},	/* 00 */
+	{0xa0, 0x03, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW},
+	{0xaa, 0x30, 0x002d},
+	{0xaa, 0x01, 0x0005},
+	{0xaa, 0x11, 0x0001},
+	{0xaa, 0x13, 0x0000},	/* {0xaa, 0x13, 0x0001}; */
+	{0xaa, 0x14, 0x0001},
+	{0xaa, 0x15, 0x00e6},
+	{0xaa, 0x16, 0x0002},
+	{0xaa, 0x17, 0x0086},
+	{0xaa, 0x31, 0x0038},
+	{0xaa, 0x32, 0x0038},
+	{0xaa, 0x33, 0x0038},
+	{0xaa, 0x5b, 0x0001},
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},
+	{0xa0, 0x70, ZC3XX_R18D_YTARGET},
+	{0xa0, 0x60, ZC3XX_R1A8_DIGITALGAIN},
+	{0xa0, 0x00, 0x01ad},
+	{0xa0, 0xc0, 0x019b},
+	{0xa0, 0xa0, 0x019c},
+	{0xa0, 0x02, ZC3XX_R188_MINGAIN},
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},
+	{0xaa, 0x02, 0x0090},	/* {0xaa, 0x02, 0x0080}, */
+	{}
+};
+static const struct usb_action hv7131b_50HZ[] = {	/* 640x480*/
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},	/* 00,19,00,cc */
+	{0xaa, 0x25, 0x0007},			/* 00,25,07,aa */
+	{0xaa, 0x26, 0x0053},			/* 00,26,53,aa */
+	{0xaa, 0x27, 0x0000},			/* 00,27,00,aa */
+	{0xaa, 0x20, 0x0000},			/* 00,20,00,aa */
+	{0xaa, 0x21, 0x0050},			/* 00,21,50,aa */
+	{0xaa, 0x22, 0x001b},			/* 00,22,1b,aa */
+	{0xaa, 0x23, 0x00fc},			/* 00,23,fc,aa */
+	{0xa0, 0x2f, ZC3XX_R190_EXPOSURELIMITHIGH},	/* 01,90,2f,cc */
+	{0xa0, 0x9b, ZC3XX_R191_EXPOSURELIMITMID},	/* 01,91,9b,cc */
+	{0xa0, 0x80, ZC3XX_R192_EXPOSURELIMITLOW},	/* 01,92,80,cc */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},	/* 01,95,00,cc */
+	{0xa0, 0xea, ZC3XX_R196_ANTIFLICKERMID},	/* 01,96,ea,cc */
+	{0xa0, 0x60, ZC3XX_R197_ANTIFLICKERLOW},	/* 01,97,60,cc */
+	{0xa0, 0x0c, ZC3XX_R18C_AEFREEZE},	/* 01,8c,0c,cc */
+	{0xa0, 0x18, ZC3XX_R18F_AEUNFREEZE},	/* 01,8f,18,cc */
+	{0xa0, 0x18, ZC3XX_R1A9_DIGITALLIMITDIFF},	/* 01,a9,18,cc */
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},	/* 01,aa,24,cc */
+	{0xa0, 0x00, ZC3XX_R01D_HSYNC_0},	/* 00,1d,00,cc */
+	{0xa0, 0x50, ZC3XX_R01E_HSYNC_1},	/* 00,1e,50,cc */
+	{0xa0, 0x1b, ZC3XX_R01F_HSYNC_2},	/* 00,1f,1b,cc */
+	{0xa0, 0xfc, ZC3XX_R020_HSYNC_3},	/* 00,20,fc,cc */
+	{}
+};
+static const struct usb_action hv7131b_50HZScale[] = {	/* 320x240 */
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},	/* 00,19,00,cc */
+	{0xaa, 0x25, 0x0007},			/* 00,25,07,aa */
+	{0xaa, 0x26, 0x0053},			/* 00,26,53,aa */
+	{0xaa, 0x27, 0x0000},			/* 00,27,00,aa */
+	{0xaa, 0x20, 0x0000},			/* 00,20,00,aa */
+	{0xaa, 0x21, 0x0050},			/* 00,21,50,aa */
+	{0xaa, 0x22, 0x0012},			/* 00,22,12,aa */
+	{0xaa, 0x23, 0x0080},			/* 00,23,80,aa */
+	{0xa0, 0x2f, ZC3XX_R190_EXPOSURELIMITHIGH},	/* 01,90,2f,cc */
+	{0xa0, 0x9b, ZC3XX_R191_EXPOSURELIMITMID},	/* 01,91,9b,cc */
+	{0xa0, 0x80, ZC3XX_R192_EXPOSURELIMITLOW},	/* 01,92,80,cc */
+	{0xa0, 0x01, ZC3XX_R195_ANTIFLICKERHIGH},	/* 01,95,01,cc */
+	{0xa0, 0xd4, ZC3XX_R196_ANTIFLICKERMID},	/* 01,96,d4,cc */
+	{0xa0, 0xc0, ZC3XX_R197_ANTIFLICKERLOW},	/* 01,97,c0,cc */
+	{0xa0, 0x07, ZC3XX_R18C_AEFREEZE},	/* 01,8c,07,cc */
+	{0xa0, 0x0f, ZC3XX_R18F_AEUNFREEZE},	/* 01,8f,0f,cc */
+	{0xa0, 0x18, ZC3XX_R1A9_DIGITALLIMITDIFF},	/* 01,a9,18,cc */
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},	/* 01,aa,24,cc */
+	{0xa0, 0x00, ZC3XX_R01D_HSYNC_0},	/* 00,1d,00,cc */
+	{0xa0, 0x50, ZC3XX_R01E_HSYNC_1},	/* 00,1e,50,cc */
+	{0xa0, 0x12, ZC3XX_R01F_HSYNC_2},	/* 00,1f,12,cc */
+	{0xa0, 0x80, ZC3XX_R020_HSYNC_3},	/* 00,20,80,cc */
+	{}
+};
+static const struct usb_action hv7131b_60HZ[] = {	/* 640x480*/
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},	/* 00,19,00,cc */
+	{0xaa, 0x25, 0x0007},			/* 00,25,07,aa */
+	{0xaa, 0x26, 0x00a1},			/* 00,26,a1,aa */
+	{0xaa, 0x27, 0x0020},			/* 00,27,20,aa */
+	{0xaa, 0x20, 0x0000},			/* 00,20,00,aa */
+	{0xaa, 0x21, 0x0040},			/* 00,21,40,aa */
+	{0xaa, 0x22, 0x0013},			/* 00,22,13,aa */
+	{0xaa, 0x23, 0x004c},			/* 00,23,4c,aa */
+	{0xa0, 0x2f, ZC3XX_R190_EXPOSURELIMITHIGH},	/* 01,90,2f,cc */
+	{0xa0, 0x4d, ZC3XX_R191_EXPOSURELIMITMID},	/* 01,91,4d,cc */
+	{0xa0, 0x60, ZC3XX_R192_EXPOSURELIMITLOW},	/* 01,92,60,cc */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},	/* 01,95,00,cc */
+	{0xa0, 0xc3, ZC3XX_R196_ANTIFLICKERMID},	/* 01,96,c3,cc */
+	{0xa0, 0x50, ZC3XX_R197_ANTIFLICKERLOW},	/* 01,97,50,cc */
+	{0xa0, 0x0c, ZC3XX_R18C_AEFREEZE},	/* 01,8c,0c,cc */
+	{0xa0, 0x18, ZC3XX_R18F_AEUNFREEZE},	/* 01,8f,18,cc */
+	{0xa0, 0x18, ZC3XX_R1A9_DIGITALLIMITDIFF},	/* 01,a9,18,cc */
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},	/* 01,aa,24,cc */
+	{0xa0, 0x00, ZC3XX_R01D_HSYNC_0},	/* 00,1d,00,cc */
+	{0xa0, 0x40, ZC3XX_R01E_HSYNC_1},	/* 00,1e,40,cc */
+	{0xa0, 0x13, ZC3XX_R01F_HSYNC_2},	/* 00,1f,13,cc */
+	{0xa0, 0x4c, ZC3XX_R020_HSYNC_3},	/* 00,20,4c,cc */
+	{}
+};
+static const struct usb_action hv7131b_60HZScale[] = {	/* 320x240 */
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},	/* 00,19,00,cc */
+	{0xaa, 0x25, 0x0007},			/* 00,25,07,aa */
+	{0xaa, 0x26, 0x00a1},			/* 00,26,a1,aa */
+	{0xaa, 0x27, 0x0020},			/* 00,27,20,aa */
+	{0xaa, 0x20, 0x0000},			/* 00,20,00,aa */
+	{0xaa, 0x21, 0x00a0},			/* 00,21,a0,aa */
+	{0xaa, 0x22, 0x0016},			/* 00,22,16,aa */
+	{0xaa, 0x23, 0x0040},			/* 00,23,40,aa */
+	{0xa0, 0x2f, ZC3XX_R190_EXPOSURELIMITHIGH},	/* 01,90,2f,cc */
+	{0xa0, 0x4d, ZC3XX_R191_EXPOSURELIMITMID},	/* 01,91,4d,cc */
+	{0xa0, 0x60, ZC3XX_R192_EXPOSURELIMITLOW},	/* 01,92,60,cc */
+	{0xa0, 0x01, ZC3XX_R195_ANTIFLICKERHIGH},	/* 01,95,01,cc */
+	{0xa0, 0x86, ZC3XX_R196_ANTIFLICKERMID},	/* 01,96,86,cc */
+	{0xa0, 0xa0, ZC3XX_R197_ANTIFLICKERLOW},	/* 01,97,a0,cc */
+	{0xa0, 0x07, ZC3XX_R18C_AEFREEZE},	/* 01,8c,07,cc */
+	{0xa0, 0x0f, ZC3XX_R18F_AEUNFREEZE},	/* 01,8f,0f,cc */
+	{0xa0, 0x18, ZC3XX_R1A9_DIGITALLIMITDIFF},	/* 01,a9,18,cc */
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},	/* 01,aa,24,cc */
+	{0xa0, 0x00, ZC3XX_R01D_HSYNC_0},	/* 00,1d,00,cc */
+	{0xa0, 0xa0, ZC3XX_R01E_HSYNC_1},	/* 00,1e,a0,cc */
+	{0xa0, 0x16, ZC3XX_R01F_HSYNC_2},	/* 00,1f,16,cc */
+	{0xa0, 0x40, ZC3XX_R020_HSYNC_3},	/* 00,20,40,cc */
+	{}
+};
+static const struct usb_action hv7131b_NoFliker[] = {	/* 640x480*/
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},	/* 00,19,00,cc */
+	{0xaa, 0x25, 0x0003},			/* 00,25,03,aa */
+	{0xaa, 0x26, 0x0000},			/* 00,26,00,aa */
+	{0xaa, 0x27, 0x0000},			/* 00,27,00,aa */
+	{0xaa, 0x20, 0x0000},			/* 00,20,00,aa */
+	{0xaa, 0x21, 0x0010},			/* 00,21,10,aa */
+	{0xaa, 0x22, 0x0000},			/* 00,22,00,aa */
+	{0xaa, 0x23, 0x0003},			/* 00,23,03,aa */
+	{0xa0, 0x2f, ZC3XX_R190_EXPOSURELIMITHIGH},	/* 01,90,2f,cc */
+	{0xa0, 0xf8, ZC3XX_R191_EXPOSURELIMITMID},	/* 01,91,f8,cc */
+	{0xa0, 0x00, ZC3XX_R192_EXPOSURELIMITLOW},	/* 01,92,00,cc */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},	/* 01,95,00,cc */
+	{0xa0, 0x02, ZC3XX_R196_ANTIFLICKERMID},	/* 01,96,02,cc */
+	{0xa0, 0x00, ZC3XX_R197_ANTIFLICKERLOW},	/* 01,97,00,cc */
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE},	/* 01,8c,10,cc */
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE},	/* 01,8f,20,cc */
+	{0xa0, 0x00, ZC3XX_R1A9_DIGITALLIMITDIFF},	/* 01,a9,00,cc */
+	{0xa0, 0x00, ZC3XX_R1AA_DIGITALGAINSTEP},	/* 01,aa,00,cc */
+	{0xa0, 0x00, ZC3XX_R01D_HSYNC_0},	/* 00,1d,00,cc */
+	{0xa0, 0x10, ZC3XX_R01E_HSYNC_1},	/* 00,1e,10,cc */
+	{0xa0, 0x00, ZC3XX_R01F_HSYNC_2},	/* 00,1f,00,cc */
+	{0xa0, 0x03, ZC3XX_R020_HSYNC_3},	/* 00,20,03,cc */
+	{}
+};
+static const struct usb_action hv7131b_NoFlikerScale[] = { /* 320x240 */
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},	/* 00,19,00,cc */
+	{0xaa, 0x25, 0x0003},			/* 00,25,03,aa */
+	{0xaa, 0x26, 0x0000},			/* 00,26,00,aa */
+	{0xaa, 0x27, 0x0000},			/* 00,27,00,aa */
+	{0xaa, 0x20, 0x0000},			/* 00,20,00,aa */
+	{0xaa, 0x21, 0x00a0},			/* 00,21,a0,aa */
+	{0xaa, 0x22, 0x0016},			/* 00,22,16,aa */
+	{0xaa, 0x23, 0x0040},			/* 00,23,40,aa */
+	{0xa0, 0x2f, ZC3XX_R190_EXPOSURELIMITHIGH},	/* 01,90,2f,cc */
+	{0xa0, 0xf8, ZC3XX_R191_EXPOSURELIMITMID},	/* 01,91,f8,cc */
+	{0xa0, 0x00, ZC3XX_R192_EXPOSURELIMITLOW},	/* 01,92,00,cc */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},	/* 01,95,00,cc */
+	{0xa0, 0x02, ZC3XX_R196_ANTIFLICKERMID},	/* 01,96,02,cc */
+	{0xa0, 0x00, ZC3XX_R197_ANTIFLICKERLOW},	/* 01,97,00,cc */
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE},	/* 01,8c,10,cc */
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE},	/* 01,8f,20,cc */
+	{0xa0, 0x00, ZC3XX_R1A9_DIGITALLIMITDIFF},	/* 01,a9,00,cc */
+	{0xa0, 0x00, ZC3XX_R1AA_DIGITALGAINSTEP},	/* 01,aa,00,cc */
+	{0xa0, 0x00, ZC3XX_R01D_HSYNC_0},	/* 00,1d,00,cc */
+	{0xa0, 0xa0, ZC3XX_R01E_HSYNC_1},	/* 00,1e,a0,cc */
+	{0xa0, 0x16, ZC3XX_R01F_HSYNC_2},	/* 00,1f,16,cc */
+	{0xa0, 0x40, ZC3XX_R020_HSYNC_3},	/* 00,20,40,cc */
+	{}
+};
+
+/* from lPEPI264v.inf (hv7131b!) */
+static const struct usb_action hv7131r_InitialScale[] = {
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},
+	{0xa0, 0x10, ZC3XX_R002_CLOCKSELECT},
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT},
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},
+	{0xa0, 0x77, ZC3XX_R101_SENSORCORRECTION},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},
+	{0xa0, 0x07, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW},
+	{0xa0, 0x01, ZC3XX_R09B_WINHEIGHTHIGH},
+	{0xa0, 0xe8, ZC3XX_R09C_WINHEIGHTLOW},
+	{0xa0, 0x02, ZC3XX_R09D_WINWIDTHHIGH},
+	{0xa0, 0x88, ZC3XX_R09E_WINWIDTHLOW},
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW},
+	{0xa0, 0x07, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xdd, 0x00, 0x0200},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xaa, 0x01, 0x000c},
+	{0xaa, 0x11, 0x0000},
+	{0xaa, 0x13, 0x0000},
+	{0xaa, 0x14, 0x0001},
+	{0xaa, 0x15, 0x00e8},
+	{0xaa, 0x16, 0x0002},
+	{0xaa, 0x17, 0x0088},
+	{0xaa, 0x30, 0x000b},
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},
+	{0xa0, 0x78, ZC3XX_R18D_YTARGET},
+	{0xa0, 0x50, ZC3XX_R1A8_DIGITALGAIN},
+	{0xa0, 0x00, 0x01ad},
+	{0xa0, 0xc0, 0x019b},
+	{0xa0, 0xa0, 0x019c},
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},
+	{}
+};
+static const struct usb_action hv7131r_Initial[] = {
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},
+	{0xa0, 0x00, ZC3XX_R002_CLOCKSELECT},
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT},
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},
+	{0xa0, 0x77, ZC3XX_R101_SENSORCORRECTION},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},
+	{0xa0, 0x07, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW},
+	{0xa0, 0x01, ZC3XX_R09B_WINHEIGHTHIGH},
+	{0xa0, 0xe6, ZC3XX_R09C_WINHEIGHTLOW},
+	{0xa0, 0x02, ZC3XX_R09D_WINWIDTHHIGH},
+	{0xa0, 0x86, ZC3XX_R09E_WINWIDTHLOW},
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW},
+	{0xa0, 0x07, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xdd, 0x00, 0x0200},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xaa, 0x01, 0x000c},
+	{0xaa, 0x11, 0x0000},
+	{0xaa, 0x13, 0x0000},
+	{0xaa, 0x14, 0x0001},
+	{0xaa, 0x15, 0x00e6},
+	{0xaa, 0x16, 0x0002},
+	{0xaa, 0x17, 0x0086},
+	{0xaa, 0x30, 0x000b},
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},
+	{0xa0, 0x78, ZC3XX_R18D_YTARGET},
+	{0xa0, 0x50, ZC3XX_R1A8_DIGITALGAIN},
+	{0xa0, 0x00, 0x01ad},
+	{0xa0, 0xc0, 0x019b},
+	{0xa0, 0xa0, 0x019c},
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},
+	{}
+};
+static const struct usb_action hv7131r_50HZ[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xa0, 0x06, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x68, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0xa0, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0xea, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x60, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x18, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x10, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x66, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x00, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0xd0, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0x00, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0x08, ZC3XX_R020_HSYNC_3},
+	{}
+};
+static const struct usb_action hv7131r_50HZScale[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xa0, 0x0c, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0xd1, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x40, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x01, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0xd4, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0xc0, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x18, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x10, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x66, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x00, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0xd0, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0x00, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0x08, ZC3XX_R020_HSYNC_3},
+	{}
+};
+static const struct usb_action hv7131r_60HZ[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xa0, 0x06, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x1a, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x80, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0xc3, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x50, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x18, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x10, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x66, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x00, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0xd0, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0x00, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0x08, ZC3XX_R020_HSYNC_3},
+	{}
+};
+static const struct usb_action hv7131r_60HZScale[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xa0, 0x0c, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x35, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x00, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x01, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x86, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0xa0, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x18, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x10, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x66, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x00, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0xd0, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0x00, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0x08, ZC3XX_R020_HSYNC_3},
+	{}
+};
+static const struct usb_action hv7131r_NoFliker[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xa0, 0x2f, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0xf8, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x00, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x02, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x58, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x0c, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x18, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x00, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x00, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x00, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0xd0, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0x00, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0x08, ZC3XX_R020_HSYNC_3},
+	{}
+};
+static const struct usb_action hv7131r_NoFlikerScale[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xa0, 0x2f, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0xf8, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x00, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x04, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0xb0, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x0c, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x18, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x00, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x00, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x00, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0xd0, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0x00, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0x08, ZC3XX_R020_HSYNC_3},
+	{}
+};
+
+static const struct usb_action icm105a_InitialScale[] = {
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},
+	{0xa0, 0x10, ZC3XX_R002_CLOCKSELECT},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},
+	{0xa0, 0x0c, ZC3XX_R010_CMOSSENSORSELECT},
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},
+	{0xa0, 0x03, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0xa1, ZC3XX_R08B_I2CDEVICEADDR},
+	{0xa0, 0x00, ZC3XX_R097_WINYSTARTHIGH},
+	{0xa0, 0x01, ZC3XX_R098_WINYSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R099_WINXSTARTHIGH},
+	{0xa0, 0x01, ZC3XX_R09A_WINXSTARTLOW},
+	{0xa0, 0x01, ZC3XX_R11A_FIRSTYLOW},
+	{0xa0, 0x01, ZC3XX_R11C_FIRSTXLOW},
+	{0xa0, 0x01, ZC3XX_R09B_WINHEIGHTHIGH},
+	{0xa0, 0xe8, ZC3XX_R09C_WINHEIGHTLOW},
+	{0xa0, 0x02, ZC3XX_R09D_WINWIDTHHIGH},
+	{0xa0, 0x88, ZC3XX_R09E_WINWIDTHLOW},
+	{0xa0, 0x37, ZC3XX_R101_SENSORCORRECTION},
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},
+	{0xaa, 0x01, 0x0010},
+	{0xaa, 0x03, 0x0000},
+	{0xaa, 0x04, 0x0001},
+	{0xaa, 0x05, 0x0020},
+	{0xaa, 0x06, 0x0001},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x03, 0x0001},
+	{0xaa, 0x04, 0x0011},
+	{0xaa, 0x05, 0x00a0},
+	{0xaa, 0x06, 0x0001},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x03, 0x0002},
+	{0xaa, 0x04, 0x0013},
+	{0xaa, 0x05, 0x0020},
+	{0xaa, 0x06, 0x0001},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x03, 0x0003},
+	{0xaa, 0x04, 0x0015},
+	{0xaa, 0x05, 0x0020},
+	{0xaa, 0x06, 0x0005},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x03, 0x0004},
+	{0xaa, 0x04, 0x0017},
+	{0xaa, 0x05, 0x0020},
+	{0xaa, 0x06, 0x000d},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x03, 0x0005},
+	{0xaa, 0x04, 0x0019},
+	{0xaa, 0x05, 0x0020},
+	{0xaa, 0x06, 0x0005},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x03, 0x0006},
+	{0xaa, 0x04, 0x0017},
+	{0xaa, 0x05, 0x0026},
+	{0xaa, 0x06, 0x0005},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x03, 0x0007},
+	{0xaa, 0x04, 0x0019},
+	{0xaa, 0x05, 0x0022},
+	{0xaa, 0x06, 0x0005},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x03, 0x0008},
+	{0xaa, 0x04, 0x0021},
+	{0xaa, 0x05, 0x00aa},
+	{0xaa, 0x06, 0x0005},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x03, 0x0009},
+	{0xaa, 0x04, 0x0023},
+	{0xaa, 0x05, 0x00aa},
+	{0xaa, 0x06, 0x000d},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x03, 0x000a},
+	{0xaa, 0x04, 0x0025},
+	{0xaa, 0x05, 0x00aa},
+	{0xaa, 0x06, 0x0005},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x03, 0x000b},
+	{0xaa, 0x04, 0x00ec},
+	{0xaa, 0x05, 0x002e},
+	{0xaa, 0x06, 0x0005},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x03, 0x000c},
+	{0xaa, 0x04, 0x00fa},
+	{0xaa, 0x05, 0x002a},
+	{0xaa, 0x06, 0x0005},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x07, 0x000d},
+	{0xaa, 0x01, 0x0005},
+	{0xaa, 0x94, 0x0002},
+	{0xaa, 0x90, 0x0000},
+	{0xaa, 0x91, 0x001f},
+	{0xaa, 0x10, 0x0064},
+	{0xaa, 0x9b, 0x00f0},
+	{0xaa, 0x9c, 0x0002},
+	{0xaa, 0x14, 0x001a},
+	{0xaa, 0x20, 0x0080},
+	{0xaa, 0x22, 0x0080},
+	{0xaa, 0x24, 0x0080},
+	{0xaa, 0x26, 0x0080},
+	{0xaa, 0x00, 0x0084},
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},
+	{0xaa, 0xa8, 0x00c0},
+	{0xa1, 0x01, 0x0002},
+	{0xa1, 0x01, 0x0008},
+	{0xa1, 0x01, 0x0180},
+	{0xa0, 0x02, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x40, ZC3XX_R116_RGAIN},
+	{0xa0, 0x40, ZC3XX_R117_GGAIN},
+	{0xa0, 0x40, ZC3XX_R118_BGAIN},
+	{0xa1, 0x01, 0x0008},
+
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},	/* clock ? */
+	{0xa0, 0x08, ZC3XX_R1C6_SHARPNESS00},	/* sharpness+ */
+	{0xa1, 0x01, 0x01c8},
+	{0xa1, 0x01, 0x01c9},
+	{0xa1, 0x01, 0x01ca},
+	{0xa0, 0x0f, ZC3XX_R1CB_SHARPNESS05},	/* sharpness- */
+	{0xa0, 0x52, ZC3XX_R10A_RGB00},	/* matrix */
+	{0xa0, 0xf7, ZC3XX_R10B_RGB01},
+	{0xa0, 0xf7, ZC3XX_R10C_RGB02},
+	{0xa0, 0xf7, ZC3XX_R10D_RGB10},
+	{0xa0, 0x52, ZC3XX_R10E_RGB11},
+	{0xa0, 0xf7, ZC3XX_R10F_RGB12},
+	{0xa0, 0xf7, ZC3XX_R110_RGB20},
+	{0xa0, 0xf7, ZC3XX_R111_RGB21},
+	{0xa0, 0x52, ZC3XX_R112_RGB22},
+	{0xa1, 0x01, 0x0180},
+	{0xa0, 0x00, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xaa, 0x0d, 0x0003},
+	{0xaa, 0x0c, 0x008c},
+	{0xaa, 0x0e, 0x0095},
+	{0xaa, 0x0f, 0x0002},
+	{0xaa, 0x1c, 0x0094},
+	{0xaa, 0x1d, 0x0002},
+	{0xaa, 0x20, 0x0080},
+	{0xaa, 0x22, 0x0080},
+	{0xaa, 0x24, 0x0080},
+	{0xaa, 0x26, 0x0080},
+	{0xaa, 0x00, 0x0084},
+	{0xa0, 0x02, ZC3XX_R0A3_EXPOSURETIMEHIGH},
+	{0xa0, 0x94, ZC3XX_R0A4_EXPOSURETIMELOW},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x04, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x20, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x84, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x10, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x12, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0xe3, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0xec, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0xf5, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},
+	{0xa0, 0x00, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0xc0, ZC3XX_R1A8_DIGITALGAIN},
+	{0xa0, 0xc0, ZC3XX_R11D_GLOBALGAIN},
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa1, 0x01, 0x0180},
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x40, ZC3XX_R116_RGAIN},
+	{0xa0, 0x40, ZC3XX_R117_GGAIN},
+	{0xa0, 0x40, ZC3XX_R118_BGAIN},
+	{}
+};
+
+static const struct usb_action icm105a_Initial[] = {
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},
+	{0xa0, 0x00, ZC3XX_R002_CLOCKSELECT},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},
+	{0xa0, 0x0c, ZC3XX_R010_CMOSSENSORSELECT},
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},
+	{0xa0, 0x03, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0xa1, ZC3XX_R08B_I2CDEVICEADDR},
+	{0xa0, 0x00, ZC3XX_R097_WINYSTARTHIGH},
+	{0xa0, 0x02, ZC3XX_R098_WINYSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R099_WINXSTARTHIGH},
+	{0xa0, 0x02, ZC3XX_R09A_WINXSTARTLOW},
+	{0xa0, 0x02, ZC3XX_R11A_FIRSTYLOW},
+	{0xa0, 0x02, ZC3XX_R11C_FIRSTXLOW},
+	{0xa0, 0x01, ZC3XX_R09B_WINHEIGHTHIGH},
+	{0xa0, 0xe6, ZC3XX_R09C_WINHEIGHTLOW},
+	{0xa0, 0x02, ZC3XX_R09D_WINWIDTHHIGH},
+	{0xa0, 0x86, ZC3XX_R09E_WINWIDTHLOW},
+	{0xa0, 0x77, ZC3XX_R101_SENSORCORRECTION},
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},
+	{0xaa, 0x01, 0x0010},
+	{0xaa, 0x03, 0x0000},
+	{0xaa, 0x04, 0x0001},
+	{0xaa, 0x05, 0x0020},
+	{0xaa, 0x06, 0x0001},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x03, 0x0001},
+	{0xaa, 0x04, 0x0011},
+	{0xaa, 0x05, 0x00a0},
+	{0xaa, 0x06, 0x0001},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x03, 0x0002},
+	{0xaa, 0x04, 0x0013},
+	{0xaa, 0x05, 0x0020},
+	{0xaa, 0x06, 0x0001},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x03, 0x0003},
+	{0xaa, 0x04, 0x0015},
+	{0xaa, 0x05, 0x0020},
+	{0xaa, 0x06, 0x0005},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x03, 0x0004},
+	{0xaa, 0x04, 0x0017},
+	{0xaa, 0x05, 0x0020},
+	{0xaa, 0x06, 0x000d},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x03, 0x0005},
+	{0xa0, 0x04, ZC3XX_R092_I2CADDRESSSELECT},
+	{0xa0, 0x19, ZC3XX_R093_I2CSETVALUE},
+	{0xa0, 0x01, ZC3XX_R090_I2CCOMMAND},
+	{0xa1, 0x01, 0x0091},
+	{0xaa, 0x05, 0x0020},
+	{0xaa, 0x06, 0x0005},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x03, 0x0006},
+	{0xaa, 0x04, 0x0017},
+	{0xaa, 0x05, 0x0026},
+	{0xaa, 0x06, 0x0005},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x03, 0x0007},
+	{0xaa, 0x04, 0x0019},
+	{0xaa, 0x05, 0x0022},
+	{0xaa, 0x06, 0x0005},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x03, 0x0008},
+	{0xaa, 0x04, 0x0021},
+	{0xaa, 0x05, 0x00aa},
+	{0xaa, 0x06, 0x0005},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x03, 0x0009},
+	{0xaa, 0x04, 0x0023},
+	{0xaa, 0x05, 0x00aa},
+	{0xaa, 0x06, 0x000d},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x03, 0x000a},
+	{0xaa, 0x04, 0x0025},
+	{0xaa, 0x05, 0x00aa},
+	{0xaa, 0x06, 0x0005},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x03, 0x000b},
+	{0xaa, 0x04, 0x00ec},
+	{0xaa, 0x05, 0x002e},
+	{0xaa, 0x06, 0x0005},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x03, 0x000c},
+	{0xaa, 0x04, 0x00fa},
+	{0xaa, 0x05, 0x002a},
+	{0xaa, 0x06, 0x0005},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x07, 0x000d},
+	{0xaa, 0x01, 0x0005},
+	{0xaa, 0x94, 0x0002},
+	{0xaa, 0x90, 0x0000},
+	{0xaa, 0x91, 0x0010},
+	{0xaa, 0x10, 0x0064},
+	{0xaa, 0x9b, 0x00f0},
+	{0xaa, 0x9c, 0x0002},
+	{0xaa, 0x14, 0x001a},
+	{0xaa, 0x20, 0x0080},
+	{0xaa, 0x22, 0x0080},
+	{0xaa, 0x24, 0x0080},
+	{0xaa, 0x26, 0x0080},
+	{0xaa, 0x00, 0x0084},
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},
+	{0xaa, 0xa8, 0x0080},
+	{0xa0, 0x78, ZC3XX_R18D_YTARGET},
+	{0xa1, 0x01, 0x0002},
+	{0xa1, 0x01, 0x0008},
+	{0xa1, 0x01, 0x0180},
+	{0xa0, 0x02, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x40, ZC3XX_R116_RGAIN},
+	{0xa0, 0x40, ZC3XX_R117_GGAIN},
+	{0xa0, 0x40, ZC3XX_R118_BGAIN},
+	{0xa1, 0x01, 0x0008},
+
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},	/* clock ? */
+	{0xa0, 0x08, ZC3XX_R1C6_SHARPNESS00},	/* sharpness+ */
+	{0xa1, 0x01, 0x01c8},
+	{0xa1, 0x01, 0x01c9},
+	{0xa1, 0x01, 0x01ca},
+	{0xa0, 0x0f, ZC3XX_R1CB_SHARPNESS05},	/* sharpness- */
+
+	{0xa0, 0x52, ZC3XX_R10A_RGB00},	/* matrix */
+	{0xa0, 0xf7, ZC3XX_R10B_RGB01},
+	{0xa0, 0xf7, ZC3XX_R10C_RGB02},
+	{0xa0, 0xf7, ZC3XX_R10D_RGB10},
+	{0xa0, 0x52, ZC3XX_R10E_RGB11},
+	{0xa0, 0xf7, ZC3XX_R10F_RGB12},
+	{0xa0, 0xf7, ZC3XX_R110_RGB20},
+	{0xa0, 0xf7, ZC3XX_R111_RGB21},
+	{0xa0, 0x52, ZC3XX_R112_RGB22},
+	{0xa1, 0x01, 0x0180},
+	{0xa0, 0x00, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xaa, 0x0d, 0x0003},
+	{0xaa, 0x0c, 0x0020},
+	{0xaa, 0x0e, 0x000e},
+	{0xaa, 0x0f, 0x0002},
+	{0xaa, 0x1c, 0x000d},
+	{0xaa, 0x1d, 0x0002},
+	{0xaa, 0x20, 0x0080},
+	{0xaa, 0x22, 0x0080},
+	{0xaa, 0x24, 0x0080},
+	{0xaa, 0x26, 0x0080},
+	{0xaa, 0x00, 0x0084},
+	{0xa0, 0x02, ZC3XX_R0A3_EXPOSURETIMEHIGH},
+	{0xa0, 0x0d, ZC3XX_R0A4_EXPOSURETIMELOW},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x04, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x1a, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x4b, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x10, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x12, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0xc8, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0xd8, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0xea, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},
+	{0xa0, 0x00, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa1, 0x01, 0x0180},
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x40, ZC3XX_R116_RGAIN},
+	{0xa0, 0x40, ZC3XX_R117_GGAIN},
+	{0xa0, 0x40, ZC3XX_R118_BGAIN},
+	{}
+};
+static const struct usb_action icm105a_50HZScale[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS}, /* 00,19,00,cc */
+	{0xaa, 0x0d, 0x0003}, /* 00,0d,03,aa */
+	{0xaa, 0x0c, 0x0020}, /* 00,0c,20,aa */
+	{0xaa, 0x0e, 0x000e}, /* 00,0e,0e,aa */
+	{0xaa, 0x0f, 0x0002}, /* 00,0f,02,aa */
+	{0xaa, 0x1c, 0x000d}, /* 00,1c,0d,aa */
+	{0xaa, 0x1d, 0x0002}, /* 00,1d,02,aa */
+	{0xaa, 0x20, 0x0080}, /* 00,20,80,aa */
+	{0xaa, 0x22, 0x0080}, /* 00,22,80,aa */
+	{0xaa, 0x24, 0x0080}, /* 00,24,80,aa */
+	{0xaa, 0x26, 0x0080}, /* 00,26,80,aa */
+	{0xaa, 0x00, 0x0084}, /* 00,00,84,aa */
+	{0xa0, 0x02, ZC3XX_R0A3_EXPOSURETIMEHIGH}, /* 00,a3,02,cc */
+	{0xa0, 0x0d, ZC3XX_R0A4_EXPOSURETIMELOW}, /* 00,a4,0d,cc */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH}, /* 01,90,00,cc */
+	{0xa0, 0x04, ZC3XX_R191_EXPOSURELIMITMID}, /* 01,91,04,cc */
+	{0xa0, 0x1a, ZC3XX_R192_EXPOSURELIMITLOW}, /* 01,92,1a,cc */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH}, /* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID}, /* 01,96,00,cc */
+	{0xa0, 0x4b, ZC3XX_R197_ANTIFLICKERLOW}, /* 01,97,4b,cc */
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE}, /* 01,8c,10,cc */
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE}, /* 01,8f,20,cc */
+	{0xa0, 0x10, ZC3XX_R1A9_DIGITALLIMITDIFF}, /* 01,a9,10,cc */
+	{0xa0, 0x12, ZC3XX_R1AA_DIGITALGAINSTEP}, /* 01,aa,12,cc */
+	{0xa0, 0xc8, ZC3XX_R01D_HSYNC_0}, /* 00,1d,c8,cc */
+	{0xa0, 0xd8, ZC3XX_R01E_HSYNC_1}, /* 00,1e,d8,cc */
+	{0xa0, 0xea, ZC3XX_R01F_HSYNC_2}, /* 00,1f,ea,cc */
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3}, /* 00,20,ff,cc */
+	{}
+};
+static const struct usb_action icm105a_50HZ[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS}, /* 00,19,00,cc */
+	{0xaa, 0x0d, 0x0003}, /* 00,0d,03,aa */
+	{0xaa, 0x0c, 0x008c}, /* 00,0c,8c,aa */
+	{0xaa, 0x0e, 0x0095}, /* 00,0e,95,aa */
+	{0xaa, 0x0f, 0x0002}, /* 00,0f,02,aa */
+	{0xaa, 0x1c, 0x0094}, /* 00,1c,94,aa */
+	{0xaa, 0x1d, 0x0002}, /* 00,1d,02,aa */
+	{0xaa, 0x20, 0x0080}, /* 00,20,80,aa */
+	{0xaa, 0x22, 0x0080}, /* 00,22,80,aa */
+	{0xaa, 0x24, 0x0080}, /* 00,24,80,aa */
+	{0xaa, 0x26, 0x0080}, /* 00,26,80,aa */
+	{0xaa, 0x00, 0x0084}, /* 00,00,84,aa */
+	{0xa0, 0x02, ZC3XX_R0A3_EXPOSURETIMEHIGH}, /* 00,a3,02,cc */
+	{0xa0, 0x94, ZC3XX_R0A4_EXPOSURETIMELOW}, /* 00,a4,94,cc */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH}, /* 01,90,00,cc */
+	{0xa0, 0x04, ZC3XX_R191_EXPOSURELIMITMID}, /* 01,91,04,cc */
+	{0xa0, 0x20, ZC3XX_R192_EXPOSURELIMITLOW}, /* 01,92,20,cc */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH}, /* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID}, /* 01,96,00,cc */
+	{0xa0, 0x84, ZC3XX_R197_ANTIFLICKERLOW}, /* 01,97,84,cc */
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE}, /* 01,8c,10,cc */
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE}, /* 01,8f,20,cc */
+	{0xa0, 0x10, ZC3XX_R1A9_DIGITALLIMITDIFF}, /* 01,a9,10,cc */
+	{0xa0, 0x12, ZC3XX_R1AA_DIGITALGAINSTEP}, /* 01,aa,12,cc */
+	{0xa0, 0xe3, ZC3XX_R01D_HSYNC_0}, /* 00,1d,e3,cc */
+	{0xa0, 0xec, ZC3XX_R01E_HSYNC_1}, /* 00,1e,ec,cc */
+	{0xa0, 0xf5, ZC3XX_R01F_HSYNC_2}, /* 00,1f,f5,cc */
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3}, /* 00,20,ff,cc */
+	{0xa0, 0x00, ZC3XX_R1A7_CALCGLOBALMEAN}, /* 01,a7,00,cc */
+	{0xa0, 0xc0, ZC3XX_R1A8_DIGITALGAIN}, /* 01,a8,c0,cc */
+	{}
+};
+static const struct usb_action icm105a_60HZScale[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS}, /* 00,19,00,cc */
+	{0xaa, 0x0d, 0x0003}, /* 00,0d,03,aa */
+	{0xaa, 0x0c, 0x0004}, /* 00,0c,04,aa */
+	{0xaa, 0x0e, 0x000d}, /* 00,0e,0d,aa */
+	{0xaa, 0x0f, 0x0002}, /* 00,0f,02,aa */
+	{0xaa, 0x1c, 0x0008}, /* 00,1c,08,aa */
+	{0xaa, 0x1d, 0x0002}, /* 00,1d,02,aa */
+	{0xaa, 0x20, 0x0080}, /* 00,20,80,aa */
+	{0xaa, 0x22, 0x0080}, /* 00,22,80,aa */
+	{0xaa, 0x24, 0x0080}, /* 00,24,80,aa */
+	{0xaa, 0x26, 0x0080}, /* 00,26,80,aa */
+	{0xaa, 0x00, 0x0084}, /* 00,00,84,aa */
+	{0xa0, 0x02, ZC3XX_R0A3_EXPOSURETIMEHIGH}, /* 00,a3,02,cc */
+	{0xa0, 0x08, ZC3XX_R0A4_EXPOSURETIMELOW}, /* 00,a4,08,cc */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH}, /* 01,90,00,cc */
+	{0xa0, 0x04, ZC3XX_R191_EXPOSURELIMITMID}, /* 01,91,04,cc */
+	{0xa0, 0x10, ZC3XX_R192_EXPOSURELIMITLOW}, /* 01,92,10,cc */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH}, /* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID}, /* 01,96,00,cc */
+	{0xa0, 0x41, ZC3XX_R197_ANTIFLICKERLOW}, /* 01,97,41,cc */
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE}, /* 01,8c,10,cc */
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE}, /* 01,8f,20,cc */
+	{0xa0, 0x10, ZC3XX_R1A9_DIGITALLIMITDIFF}, /* 01,a9,10,cc */
+	{0xa0, 0x12, ZC3XX_R1AA_DIGITALGAINSTEP}, /* 01,aa,12,cc */
+	{0xa0, 0xc1, ZC3XX_R01D_HSYNC_0}, /* 00,1d,c1,cc */
+	{0xa0, 0xd4, ZC3XX_R01E_HSYNC_1}, /* 00,1e,d4,cc */
+	{0xa0, 0xe8, ZC3XX_R01F_HSYNC_2}, /* 00,1f,e8,cc */
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3}, /* 00,20,ff,cc */
+	{}
+};
+static const struct usb_action icm105a_60HZ[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS}, /* 00,19,00,cc */
+	{0xaa, 0x0d, 0x0003}, /* 00,0d,03,aa */
+	{0xaa, 0x0c, 0x0008}, /* 00,0c,08,aa */
+	{0xaa, 0x0e, 0x0086}, /* 00,0e,86,aa */
+	{0xaa, 0x0f, 0x0002}, /* 00,0f,02,aa */
+	{0xaa, 0x1c, 0x0085}, /* 00,1c,85,aa */
+	{0xaa, 0x1d, 0x0002}, /* 00,1d,02,aa */
+	{0xaa, 0x20, 0x0080}, /* 00,20,80,aa */
+	{0xaa, 0x22, 0x0080}, /* 00,22,80,aa */
+	{0xaa, 0x24, 0x0080}, /* 00,24,80,aa */
+	{0xaa, 0x26, 0x0080}, /* 00,26,80,aa */
+	{0xaa, 0x00, 0x0084}, /* 00,00,84,aa */
+	{0xa0, 0x02, ZC3XX_R0A3_EXPOSURETIMEHIGH}, /* 00,a3,02,cc */
+	{0xa0, 0x85, ZC3XX_R0A4_EXPOSURETIMELOW}, /* 00,a4,85,cc */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH}, /* 01,90,00,cc */
+	{0xa0, 0x04, ZC3XX_R191_EXPOSURELIMITMID}, /* 01,91,04,cc */
+	{0xa0, 0x08, ZC3XX_R192_EXPOSURELIMITLOW}, /* 01,92,08,cc */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH}, /* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID}, /* 01,96,00,cc */
+	{0xa0, 0x81, ZC3XX_R197_ANTIFLICKERLOW}, /* 01,97,81,cc */
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE}, /* 01,8c,10,cc */
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE}, /* 01,8f,20,cc */
+	{0xa0, 0x10, ZC3XX_R1A9_DIGITALLIMITDIFF}, /* 01,a9,10,cc */
+	{0xa0, 0x12, ZC3XX_R1AA_DIGITALGAINSTEP}, /* 01,aa,12,cc */
+	{0xa0, 0xc2, ZC3XX_R01D_HSYNC_0}, /* 00,1d,c2,cc */
+	{0xa0, 0xd6, ZC3XX_R01E_HSYNC_1}, /* 00,1e,d6,cc */
+	{0xa0, 0xea, ZC3XX_R01F_HSYNC_2}, /* 00,1f,ea,cc */
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3}, /* 00,20,ff,cc */
+	{0xa0, 0x00, ZC3XX_R1A7_CALCGLOBALMEAN}, /* 01,a7,00,cc */
+	{0xa0, 0xc0, ZC3XX_R1A8_DIGITALGAIN}, /* 01,a8,c0,cc */
+	{}
+};
+static const struct usb_action icm105a_NoFlikerScale[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS}, /* 00,19,00,cc */
+	{0xaa, 0x0d, 0x0003}, /* 00,0d,03,aa */
+	{0xaa, 0x0c, 0x0004}, /* 00,0c,04,aa */
+	{0xaa, 0x0e, 0x000d}, /* 00,0e,0d,aa */
+	{0xaa, 0x0f, 0x0002}, /* 00,0f,02,aa */
+	{0xaa, 0x1c, 0x0000}, /* 00,1c,00,aa */
+	{0xaa, 0x1d, 0x0002}, /* 00,1d,02,aa */
+	{0xaa, 0x20, 0x0080}, /* 00,20,80,aa */
+	{0xaa, 0x22, 0x0080}, /* 00,22,80,aa */
+	{0xaa, 0x24, 0x0080}, /* 00,24,80,aa */
+	{0xaa, 0x26, 0x0080}, /* 00,26,80,aa */
+	{0xaa, 0x00, 0x0084}, /* 00,00,84,aa */
+	{0xa0, 0x02, ZC3XX_R0A3_EXPOSURETIMEHIGH}, /* 00,a3,02,cc */
+	{0xa0, 0x00, ZC3XX_R0A4_EXPOSURETIMELOW}, /* 00,a4,00,cc */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH}, /* 01,90,00,cc */
+	{0xa0, 0x04, ZC3XX_R191_EXPOSURELIMITMID}, /* 01,91,04,cc */
+	{0xa0, 0x20, ZC3XX_R192_EXPOSURELIMITLOW}, /* 01,92,20,cc */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH}, /* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID}, /* 01,96,00,cc */
+	{0xa0, 0x10, ZC3XX_R197_ANTIFLICKERLOW}, /* 01,97,10,cc */
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE}, /* 01,8c,10,cc */
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE}, /* 01,8f,20,cc */
+	{0xa0, 0x00, ZC3XX_R1A9_DIGITALLIMITDIFF}, /* 01,a9,00,cc */
+	{0xa0, 0x00, ZC3XX_R1AA_DIGITALGAINSTEP}, /* 01,aa,00,cc */
+	{0xa0, 0xc1, ZC3XX_R01D_HSYNC_0}, /* 00,1d,c1,cc */
+	{0xa0, 0xd4, ZC3XX_R01E_HSYNC_1}, /* 00,1e,d4,cc */
+	{0xa0, 0xe8, ZC3XX_R01F_HSYNC_2}, /* 00,1f,e8,cc */
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3}, /* 00,20,ff,cc */
+	{}
+};
+static const struct usb_action icm105a_NoFliker[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS}, /* 00,19,00,cc */
+	{0xaa, 0x0d, 0x0003}, /* 00,0d,03,aa */
+	{0xaa, 0x0c, 0x0004}, /* 00,0c,04,aa */
+	{0xaa, 0x0e, 0x0081}, /* 00,0e,81,aa */
+	{0xaa, 0x0f, 0x0002}, /* 00,0f,02,aa */
+	{0xaa, 0x1c, 0x0080}, /* 00,1c,80,aa */
+	{0xaa, 0x1d, 0x0002}, /* 00,1d,02,aa */
+	{0xaa, 0x20, 0x0080}, /* 00,20,80,aa */
+	{0xaa, 0x22, 0x0080}, /* 00,22,80,aa */
+	{0xaa, 0x24, 0x0080}, /* 00,24,80,aa */
+	{0xaa, 0x26, 0x0080}, /* 00,26,80,aa */
+	{0xaa, 0x00, 0x0084}, /* 00,00,84,aa */
+	{0xa0, 0x02, ZC3XX_R0A3_EXPOSURETIMEHIGH}, /* 00,a3,02,cc */
+	{0xa0, 0x80, ZC3XX_R0A4_EXPOSURETIMELOW}, /* 00,a4,80,cc */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH}, /* 01,90,00,cc */
+	{0xa0, 0x04, ZC3XX_R191_EXPOSURELIMITMID}, /* 01,91,04,cc */
+	{0xa0, 0x20, ZC3XX_R192_EXPOSURELIMITLOW}, /* 01,92,20,cc */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH}, /* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID}, /* 01,96,00,cc */
+	{0xa0, 0x10, ZC3XX_R197_ANTIFLICKERLOW}, /* 01,97,10,cc */
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE}, /* 01,8c,10,cc */
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE}, /* 01,8f,20,cc */
+	{0xa0, 0x00, ZC3XX_R1A9_DIGITALLIMITDIFF}, /* 01,a9,00,cc */
+	{0xa0, 0x00, ZC3XX_R1AA_DIGITALGAINSTEP}, /* 01,aa,00,cc */
+	{0xa0, 0xc1, ZC3XX_R01D_HSYNC_0}, /* 00,1d,c1,cc */
+	{0xa0, 0xd4, ZC3XX_R01E_HSYNC_1}, /* 00,1e,d4,cc */
+	{0xa0, 0xe8, ZC3XX_R01F_HSYNC_2}, /* 00,1f,e8,cc */
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3}, /* 00,20,ff,cc */
+	{0xa0, 0x00, ZC3XX_R1A7_CALCGLOBALMEAN}, /* 01,a7,00,cc */
+	{0xa0, 0xc0, ZC3XX_R1A8_DIGITALGAIN}, /* 01,a8,c0,cc */
+	{}
+};
+
+static const struct usb_action mc501cb_Initial[] = {
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL}, /* 00,00,01,cc */
+	{0xa0, 0x00, ZC3XX_R002_CLOCKSELECT}, /* 00,02,00,cc */
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT}, /* 00,10,01,cc */
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING}, /* 00,01,01,cc */
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING}, /* 00,08,03,cc */
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC}, /* 00,12,01,cc */
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC}, /* 00,12,05,cc */
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH}, /* 00,03,02,cc */
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW}, /* 00,04,80,cc */
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH}, /* 00,05,01,cc */
+	{0xa0, 0xd8, ZC3XX_R006_FRAMEHEIGHTLOW}, /* 00,06,d8,cc */
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW}, /* 00,98,00,cc */
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW}, /* 00,9a,00,cc */
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW}, /* 01,1a,00,cc */
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW}, /* 01,1c,00,cc */
+	{0xa0, 0x01, ZC3XX_R09B_WINHEIGHTHIGH}, /* 00,9b,01,cc */
+	{0xa0, 0xde, ZC3XX_R09C_WINHEIGHTLOW}, /* 00,9c,de,cc */
+	{0xa0, 0x02, ZC3XX_R09D_WINWIDTHHIGH}, /* 00,9d,02,cc */
+	{0xa0, 0x86, ZC3XX_R09E_WINWIDTHLOW}, /* 00,9e,86,cc */
+	{0xa0, 0x33, ZC3XX_R086_EXPTIMEHIGH}, /* 00,86,33,cc */
+	{0xa0, 0x34, ZC3XX_R087_EXPTIMEMID}, /* 00,87,34,cc */
+	{0xa0, 0x35, ZC3XX_R088_EXPTIMELOW}, /* 00,88,35,cc */
+	{0xa0, 0xb0, ZC3XX_R08B_I2CDEVICEADDR}, /* 00,8b,b0,cc */
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC}, /* 00,12,05,cc */
+	{0xaa, 0x01, 0x0001}, /* 00,01,01,aa */
+	{0xaa, 0x01, 0x0003}, /* 00,01,03,aa */
+	{0xaa, 0x01, 0x0001}, /* 00,01,01,aa */
+	{0xaa, 0x03, 0x0000}, /* 00,03,00,aa */
+	{0xaa, 0x10, 0x0000}, /* 00,10,00,aa */
+	{0xaa, 0x11, 0x0080}, /* 00,11,80,aa */
+	{0xaa, 0x12, 0x0000}, /* 00,12,00,aa */
+	{0xaa, 0x13, 0x0000}, /* 00,13,00,aa */
+	{0xaa, 0x14, 0x0000}, /* 00,14,00,aa */
+	{0xaa, 0x15, 0x0000}, /* 00,15,00,aa */
+	{0xaa, 0x16, 0x0000}, /* 00,16,00,aa */
+	{0xaa, 0x17, 0x0001}, /* 00,17,01,aa */
+	{0xaa, 0x18, 0x00de}, /* 00,18,de,aa */
+	{0xaa, 0x19, 0x0002}, /* 00,19,02,aa */
+	{0xaa, 0x1a, 0x0086}, /* 00,1a,86,aa */
+	{0xaa, 0x20, 0x00a8}, /* 00,20,a8,aa */
+	{0xaa, 0x22, 0x0000}, /* 00,22,00,aa */
+	{0xaa, 0x23, 0x0000}, /* 00,23,00,aa */
+	{0xaa, 0x24, 0x0000}, /* 00,24,00,aa */
+	{0xaa, 0x40, 0x0033}, /* 00,40,33,aa */
+	{0xaa, 0x41, 0x0077}, /* 00,41,77,aa */
+	{0xaa, 0x42, 0x0053}, /* 00,42,53,aa */
+	{0xaa, 0x43, 0x00b0}, /* 00,43,b0,aa */
+	{0xaa, 0x4b, 0x0001}, /* 00,4b,01,aa */
+	{0xaa, 0x72, 0x0020}, /* 00,72,20,aa */
+	{0xaa, 0x73, 0x0000}, /* 00,73,00,aa */
+	{0xaa, 0x80, 0x0000}, /* 00,80,00,aa */
+	{0xaa, 0x85, 0x0050}, /* 00,85,50,aa */
+	{0xaa, 0x91, 0x0070}, /* 00,91,70,aa */
+	{0xaa, 0x92, 0x0072}, /* 00,92,72,aa */
+	{0xaa, 0x03, 0x0001}, /* 00,03,01,aa */
+	{0xaa, 0x10, 0x00a0}, /* 00,10,a0,aa */
+	{0xaa, 0x11, 0x0001}, /* 00,11,01,aa */
+	{0xaa, 0x30, 0x0000}, /* 00,30,00,aa */
+	{0xaa, 0x60, 0x0000}, /* 00,60,00,aa */
+	{0xaa, 0xa0, 0x001a}, /* 00,a0,1a,aa */
+	{0xaa, 0xa1, 0x0000}, /* 00,a1,00,aa */
+	{0xaa, 0xa2, 0x003f}, /* 00,a2,3f,aa */
+	{0xaa, 0xa3, 0x0028}, /* 00,a3,28,aa */
+	{0xaa, 0xa4, 0x0010}, /* 00,a4,10,aa */
+	{0xaa, 0xa5, 0x0020}, /* 00,a5,20,aa */
+	{0xaa, 0xb1, 0x0044}, /* 00,b1,44,aa */
+	{0xaa, 0xd0, 0x0001}, /* 00,d0,01,aa */
+	{0xaa, 0xd1, 0x0085}, /* 00,d1,85,aa */
+	{0xaa, 0xd2, 0x0080}, /* 00,d2,80,aa */
+	{0xaa, 0xd3, 0x0080}, /* 00,d3,80,aa */
+	{0xaa, 0xd4, 0x0080}, /* 00,d4,80,aa */
+	{0xaa, 0xd5, 0x0080}, /* 00,d5,80,aa */
+	{0xaa, 0xc0, 0x00c3}, /* 00,c0,c3,aa */
+	{0xaa, 0xc2, 0x0044}, /* 00,c2,44,aa */
+	{0xaa, 0xc4, 0x0040}, /* 00,c4,40,aa */
+	{0xaa, 0xc5, 0x0020}, /* 00,c5,20,aa */
+	{0xaa, 0xc6, 0x0008}, /* 00,c6,08,aa */
+	{0xaa, 0x03, 0x0004}, /* 00,03,04,aa */
+	{0xaa, 0x10, 0x0000}, /* 00,10,00,aa */
+	{0xaa, 0x40, 0x0030}, /* 00,40,30,aa */
+	{0xaa, 0x41, 0x0020}, /* 00,41,20,aa */
+	{0xaa, 0x42, 0x002d}, /* 00,42,2d,aa */
+	{0xaa, 0x03, 0x0003}, /* 00,03,03,aa */
+	{0xaa, 0x1c, 0x0050}, /* 00,1C,50,aa */
+	{0xaa, 0x11, 0x0081}, /* 00,11,81,aa */
+	{0xaa, 0x3b, 0x001d}, /* 00,3b,1D,aa */
+	{0xaa, 0x3c, 0x004c}, /* 00,3c,4C,aa */
+	{0xaa, 0x3d, 0x0018}, /* 00,3d,18,aa */
+	{0xaa, 0x3e, 0x006a}, /* 00,3e,6A,aa */
+	{0xaa, 0x01, 0x0000}, /* 00,01,00,aa */
+	{0xaa, 0x52, 0x00ff}, /* 00,52,FF,aa */
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS}, /* 00,19,00,cc */
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE}, /* 01,00,0d,cc */
+	{0xa0, 0x37, ZC3XX_R101_SENSORCORRECTION}, /* 01,01,37,cc */
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS}, /* 01,89,06,cc */
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE}, /* 01,c5,03,cc */
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05}, /* 01,cb,13,cc */
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE}, /* 02,50,08,cc */
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS}, /* 03,01,08,cc */
+	{0xa0, 0x02, ZC3XX_R180_AUTOCORRECTENABLE}, /* 01,80,02,cc */
+	{0xaa, 0x03, 0x0002}, /* 00,03,02,aa */
+	{0xaa, 0x51, 0x0027}, /* 00,51,27,aa */
+	{0xaa, 0x52, 0x0020}, /* 00,52,20,aa */
+	{0xaa, 0x03, 0x0003}, /* 00,03,03,aa */
+	{0xaa, 0x50, 0x0010}, /* 00,50,10,aa */
+	{0xaa, 0x51, 0x0010}, /* 00,51,10,aa */
+	{0xaa, 0x54, 0x0010}, /* 00,54,10,aa */
+	{0xaa, 0x55, 0x0010}, /* 00,55,10,aa */
+	{0xa0, 0xf0, 0x0199}, /* 01,99,F0,cc */
+	{0xa0, 0x80, 0x019a}, /* 01,9A,80,cc */
+
+	{0xaa, 0x03, 0x0003}, /* 00,03,03,aa */
+	{0xaa, 0x10, 0x00fc}, /* 00,10,fc,aa */
+	{0xaa, 0x36, 0x001d}, /* 00,36,1D,aa */
+	{0xaa, 0x37, 0x004c}, /* 00,37,4C,aa */
+	{0xaa, 0x3b, 0x001d}, /* 00,3B,1D,aa */
+	{}
+};
+
+static const struct usb_action mc501cb_InitialScale[] = {	/* 320x240 */
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL}, /* 00,00,01,cc */
+	{0xa0, 0x10, ZC3XX_R002_CLOCKSELECT}, /* 00,02,10,cc */
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT}, /* 00,10,01,cc */
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING}, /* 00,01,01,cc */
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING}, /* 00,08,03,cc */
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC}, /* 00,12,01,cc */
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC}, /* 00,12,05,cc */
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH}, /* 00,03,02,cc */
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW}, /* 00,04,80,cc */
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH}, /* 00,05,01,cc */
+	{0xa0, 0xd0, ZC3XX_R006_FRAMEHEIGHTLOW}, /* 00,06,d0,cc */
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW}, /* 00,98,00,cc */
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW}, /* 00,9a,00,cc */
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW}, /* 01,1a,00,cc */
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW}, /* 01,1c,00,cc */
+	{0xa0, 0x01, ZC3XX_R09B_WINHEIGHTHIGH}, /* 00,9b,01,cc */
+	{0xa0, 0xd8, ZC3XX_R09C_WINHEIGHTLOW}, /* 00,9c,d8,cc */
+	{0xa0, 0x02, ZC3XX_R09D_WINWIDTHHIGH}, /* 00,9d,02,cc */
+	{0xa0, 0x88, ZC3XX_R09E_WINWIDTHLOW}, /* 00,9e,88,cc */
+	{0xa0, 0x33, ZC3XX_R086_EXPTIMEHIGH}, /* 00,86,33,cc */
+	{0xa0, 0x34, ZC3XX_R087_EXPTIMEMID}, /* 00,87,34,cc */
+	{0xa0, 0x35, ZC3XX_R088_EXPTIMELOW}, /* 00,88,35,cc */
+	{0xa0, 0xb0, ZC3XX_R08B_I2CDEVICEADDR}, /* 00,8b,b0,cc */
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC}, /* 00,12,05,cc */
+	{0xaa, 0x01, 0x0001}, /* 00,01,01,aa */
+	{0xaa, 0x01, 0x0003}, /* 00,01,03,aa */
+	{0xaa, 0x01, 0x0001}, /* 00,01,01,aa */
+	{0xaa, 0x03, 0x0000}, /* 00,03,00,aa */
+	{0xaa, 0x10, 0x0000}, /* 00,10,00,aa */
+	{0xaa, 0x11, 0x0080}, /* 00,11,80,aa */
+	{0xaa, 0x12, 0x0000}, /* 00,12,00,aa */
+	{0xaa, 0x13, 0x0000}, /* 00,13,00,aa */
+	{0xaa, 0x14, 0x0000}, /* 00,14,00,aa */
+	{0xaa, 0x15, 0x0000}, /* 00,15,00,aa */
+	{0xaa, 0x16, 0x0000}, /* 00,16,00,aa */
+	{0xaa, 0x17, 0x0001}, /* 00,17,01,aa */
+	{0xaa, 0x18, 0x00d8}, /* 00,18,d8,aa */
+	{0xaa, 0x19, 0x0002}, /* 00,19,02,aa */
+	{0xaa, 0x1a, 0x0088}, /* 00,1a,88,aa */
+	{0xaa, 0x20, 0x00a8}, /* 00,20,a8,aa */
+	{0xaa, 0x22, 0x0000}, /* 00,22,00,aa */
+	{0xaa, 0x23, 0x0000}, /* 00,23,00,aa */
+	{0xaa, 0x24, 0x0000}, /* 00,24,00,aa */
+	{0xaa, 0x40, 0x0033}, /* 00,40,33,aa */
+	{0xaa, 0x41, 0x0077}, /* 00,41,77,aa */
+	{0xaa, 0x42, 0x0053}, /* 00,42,53,aa */
+	{0xaa, 0x43, 0x00b0}, /* 00,43,b0,aa */
+	{0xaa, 0x4b, 0x0001}, /* 00,4b,01,aa */
+	{0xaa, 0x72, 0x0020}, /* 00,72,20,aa */
+	{0xaa, 0x73, 0x0000}, /* 00,73,00,aa */
+	{0xaa, 0x80, 0x0000}, /* 00,80,00,aa */
+	{0xaa, 0x85, 0x0050}, /* 00,85,50,aa */
+	{0xaa, 0x91, 0x0070}, /* 00,91,70,aa */
+	{0xaa, 0x92, 0x0072}, /* 00,92,72,aa */
+	{0xaa, 0x03, 0x0001}, /* 00,03,01,aa */
+	{0xaa, 0x10, 0x00a0}, /* 00,10,a0,aa */
+	{0xaa, 0x11, 0x0001}, /* 00,11,01,aa */
+	{0xaa, 0x30, 0x0000}, /* 00,30,00,aa */
+	{0xaa, 0x60, 0x0000}, /* 00,60,00,aa */
+	{0xaa, 0xa0, 0x001a}, /* 00,a0,1a,aa */
+	{0xaa, 0xa1, 0x0000}, /* 00,a1,00,aa */
+	{0xaa, 0xa2, 0x003f}, /* 00,a2,3f,aa */
+	{0xaa, 0xa3, 0x0028}, /* 00,a3,28,aa */
+	{0xaa, 0xa4, 0x0010}, /* 00,a4,10,aa */
+	{0xaa, 0xa5, 0x0020}, /* 00,a5,20,aa */
+	{0xaa, 0xb1, 0x0044}, /* 00,b1,44,aa */
+	{0xaa, 0xd0, 0x0001}, /* 00,d0,01,aa */
+	{0xaa, 0xd1, 0x0085}, /* 00,d1,85,aa */
+	{0xaa, 0xd2, 0x0080}, /* 00,d2,80,aa */
+	{0xaa, 0xd3, 0x0080}, /* 00,d3,80,aa */
+	{0xaa, 0xd4, 0x0080}, /* 00,d4,80,aa */
+	{0xaa, 0xd5, 0x0080}, /* 00,d5,80,aa */
+	{0xaa, 0xc0, 0x00c3}, /* 00,c0,c3,aa */
+	{0xaa, 0xc2, 0x0044}, /* 00,c2,44,aa */
+	{0xaa, 0xc4, 0x0040}, /* 00,c4,40,aa */
+	{0xaa, 0xc5, 0x0020}, /* 00,c5,20,aa */
+	{0xaa, 0xc6, 0x0008}, /* 00,c6,08,aa */
+	{0xaa, 0x03, 0x0004}, /* 00,03,04,aa */
+	{0xaa, 0x10, 0x0000}, /* 00,10,00,aa */
+	{0xaa, 0x40, 0x0030}, /* 00,40,30,aa */
+	{0xaa, 0x41, 0x0020}, /* 00,41,20,aa */
+	{0xaa, 0x42, 0x002d}, /* 00,42,2d,aa */
+	{0xaa, 0x03, 0x0003}, /* 00,03,03,aa */
+	{0xaa, 0x1c, 0x0050}, /* 00,1c,50,aa */
+	{0xaa, 0x11, 0x0081}, /* 00,11,81,aa */
+	{0xaa, 0x3b, 0x003a}, /* 00,3b,3A,aa */
+	{0xaa, 0x3c, 0x0098}, /* 00,3c,98,aa */
+	{0xaa, 0x3d, 0x0030}, /* 00,3d,30,aa */
+	{0xaa, 0x3e, 0x00d4}, /* 00,3E,D4,aa */
+	{0xaa, 0x01, 0x0000}, /* 00,01,00,aa */
+	{0xaa, 0x52, 0x00ff}, /* 00,52,FF,aa */
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS}, /* 00,19,00,cc */
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE}, /* 01,00,0d,cc */
+	{0xa0, 0x37, ZC3XX_R101_SENSORCORRECTION}, /* 01,01,37,cc */
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS}, /* 01,89,06,cc */
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE}, /* 01,c5,03,cc */
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05}, /* 01,cb,13,cc */
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE}, /* 02,50,08,cc */
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS}, /* 03,01,08,cc */
+	{0xa0, 0x02, ZC3XX_R180_AUTOCORRECTENABLE}, /* 01,80,02,cc */
+	{0xaa, 0x03, 0x0002}, /* 00,03,02,aa */
+	{0xaa, 0x51, 0x004e}, /* 00,51,4E,aa */
+	{0xaa, 0x52, 0x0041}, /* 00,52,41,aa */
+	{0xaa, 0x03, 0x0003}, /* 00,03,03,aa */
+	{0xaa, 0x50, 0x0010}, /* 00,50,10,aa */
+	{0xaa, 0x51, 0x0010}, /* 00,51,10,aa */
+	{0xaa, 0x54, 0x0010}, /* 00,54,10,aa */
+	{0xaa, 0x55, 0x0010}, /* 00,55,10,aa */
+	{0xa0, 0xf0, 0x0199}, /* 01,99,F0,cc */
+	{0xa0, 0x80, 0x019a}, /* 01,9A,80,cc */
+	{}
+};
+
+static const struct usb_action mc501cb_50HZ[] = {
+	{0xaa, 0x03, 0x0003}, /* 00,03,03,aa */
+	{0xaa, 0x10, 0x00fc}, /* 00,10,fc,aa */
+	{0xaa, 0x36, 0x001d}, /* 00,36,1D,aa */
+	{0xaa, 0x37, 0x004c}, /* 00,37,4C,aa */
+	{0xaa, 0x3b, 0x001d}, /* 00,3B,1D,aa */
+	{0xaa, 0x3c, 0x004c}, /* 00,3C,4C,aa */
+	{0xaa, 0x3d, 0x001d}, /* 00,3D,1D,aa */
+	{0xaa, 0x3e, 0x004c}, /* 00,3E,4C,aa */
+	{}
+};
+
+static const struct usb_action mc501cb_50HZScale[] = {
+	{0xaa, 0x03, 0x0003}, /* 00,03,03,aa */
+	{0xaa, 0x10, 0x00fc}, /* 00,10,fc,aa */
+	{0xaa, 0x36, 0x003a}, /* 00,36,3A,aa */
+	{0xaa, 0x37, 0x0098}, /* 00,37,98,aa */
+	{0xaa, 0x3b, 0x003a}, /* 00,3B,3A,aa */
+	{0xaa, 0x3c, 0x0098}, /* 00,3C,98,aa */
+	{0xaa, 0x3d, 0x003a}, /* 00,3D,3A,aa */
+	{0xaa, 0x3e, 0x0098}, /* 00,3E,98,aa */
+	{}
+};
+
+static const struct usb_action mc501cb_60HZ[] = {
+	{0xaa, 0x03, 0x0003}, /* 00,03,03,aa */
+	{0xaa, 0x10, 0x00fc}, /* 00,10,fc,aa */
+	{0xaa, 0x36, 0x0018}, /* 00,36,18,aa */
+	{0xaa, 0x37, 0x006a}, /* 00,37,6A,aa */
+	{0xaa, 0x3d, 0x0018}, /* 00,3D,18,aa */
+	{0xaa, 0x3e, 0x006a}, /* 00,3E,6A,aa */
+	{0xaa, 0x3b, 0x0018}, /* 00,3B,18,aa */
+	{0xaa, 0x3c, 0x006a}, /* 00,3C,6A,aa */
+	{}
+};
+
+static const struct usb_action mc501cb_60HZScale[] = {
+	{0xaa, 0x03, 0x0003}, /* 00,03,03,aa */
+	{0xaa, 0x10, 0x00fc}, /* 00,10,fc,aa */
+	{0xaa, 0x36, 0x0030}, /* 00,36,30,aa */
+	{0xaa, 0x37, 0x00d4}, /* 00,37,D4,aa */
+	{0xaa, 0x3d, 0x0030}, /* 00,3D,30,aa */
+	{0xaa, 0x3e, 0x00d4}, /* 00,3E,D4,aa */
+	{0xaa, 0x3b, 0x0030}, /* 00,3B,30,aa */
+	{0xaa, 0x3c, 0x00d4}, /* 00,3C,D4,aa */
+	{}
+};
+
+static const struct usb_action mc501cb_NoFliker[] = {
+	{0xaa, 0x03, 0x0003}, /* 00,03,03,aa */
+	{0xaa, 0x10, 0x00fc}, /* 00,10,fc,aa */
+	{0xaa, 0x36, 0x0018}, /* 00,36,18,aa */
+	{0xaa, 0x37, 0x006a}, /* 00,37,6A,aa */
+	{0xaa, 0x3d, 0x0018}, /* 00,3D,18,aa */
+	{0xaa, 0x3e, 0x006a}, /* 00,3E,6A,aa */
+	{0xaa, 0x3b, 0x0018}, /* 00,3B,18,aa */
+	{0xaa, 0x3c, 0x006a}, /* 00,3C,6A,aa */
+	{}
+};
+
+static const struct usb_action mc501cb_NoFlikerScale[] = {
+	{0xaa, 0x03, 0x0003}, /* 00,03,03,aa */
+	{0xaa, 0x10, 0x00fc}, /* 00,10,fc,aa */
+	{0xaa, 0x36, 0x0030}, /* 00,36,30,aa */
+	{0xaa, 0x37, 0x00d4}, /* 00,37,D4,aa */
+	{0xaa, 0x3d, 0x0030}, /* 00,3D,30,aa */
+	{0xaa, 0x3e, 0x00d4}, /* 00,3E,D4,aa */
+	{0xaa, 0x3b, 0x0030}, /* 00,3B,30,aa */
+	{0xaa, 0x3c, 0x00d4}, /* 00,3C,D4,aa */
+	{}
+};
+
+/* from zs211.inf */
+static const struct usb_action ov7620_Initial[] = {	/* 640x480 */
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL}, /* 00,00,01,cc */
+	{0xa0, 0x40, ZC3XX_R002_CLOCKSELECT}, /* 00,02,40,cc */
+	{0xa0, 0x00, ZC3XX_R008_CLOCKSETTING}, /* 00,08,00,cc */
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING}, /* 00,01,01,cc */
+	{0xa0, 0x06, ZC3XX_R010_CMOSSENSORSELECT}, /* 00,10,06,cc */
+	{0xa0, 0x02, ZC3XX_R083_RGAINADDR}, /* 00,83,02,cc */
+	{0xa0, 0x01, ZC3XX_R085_BGAINADDR}, /* 00,85,01,cc */
+	{0xa0, 0x80, ZC3XX_R086_EXPTIMEHIGH}, /* 00,86,80,cc */
+	{0xa0, 0x81, ZC3XX_R087_EXPTIMEMID}, /* 00,87,81,cc */
+	{0xa0, 0x10, ZC3XX_R088_EXPTIMELOW}, /* 00,88,10,cc */
+	{0xa0, 0xa1, ZC3XX_R08B_I2CDEVICEADDR}, /* 00,8b,a1,cc */
+	{0xa0, 0x08, ZC3XX_R08D_COMPABILITYMODE}, /* 00,8d,08,cc */
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH}, /* 00,03,02,cc */
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW}, /* 00,04,80,cc */
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH}, /* 00,05,01,cc */
+	{0xa0, 0xd8, ZC3XX_R006_FRAMEHEIGHTLOW}, /* 00,06,d8,cc */
+	{0xa0, 0x03, ZC3XX_R012_VIDEOCONTROLFUNC}, /* 00,12,03,cc */
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC}, /* 00,12,01,cc */
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW}, /* 00,98,00,cc */
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW}, /* 00,9a,00,cc */
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW}, /* 01,1a,00,cc */
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW}, /* 01,1c,00,cc */
+	{0xa0, 0xde, ZC3XX_R09C_WINHEIGHTLOW}, /* 00,9c,de,cc */
+	{0xa0, 0x86, ZC3XX_R09E_WINWIDTHLOW}, /* 00,9e,86,cc */
+	{0xaa, 0x12, 0x0088}, /* 00,12,88,aa */
+	{0xaa, 0x12, 0x0048}, /* 00,12,48,aa */
+	{0xaa, 0x75, 0x008a}, /* 00,75,8a,aa */
+	{0xaa, 0x13, 0x00a3}, /* 00,13,a3,aa */
+	{0xaa, 0x04, 0x0000}, /* 00,04,00,aa */
+	{0xaa, 0x05, 0x0000}, /* 00,05,00,aa */
+	{0xaa, 0x14, 0x0000}, /* 00,14,00,aa */
+	{0xaa, 0x15, 0x0004}, /* 00,15,04,aa */
+	{0xaa, 0x17, 0x0018}, /* 00,17,18,aa */
+	{0xaa, 0x18, 0x00ba}, /* 00,18,ba,aa */
+	{0xaa, 0x19, 0x0002}, /* 00,19,02,aa */
+	{0xaa, 0x1a, 0x00f1}, /* 00,1a,f1,aa */
+	{0xaa, 0x20, 0x0040}, /* 00,20,40,aa */
+	{0xaa, 0x24, 0x0088}, /* 00,24,88,aa */
+	{0xaa, 0x25, 0x0078}, /* 00,25,78,aa */
+	{0xaa, 0x27, 0x00f6}, /* 00,27,f6,aa */
+	{0xaa, 0x28, 0x00a0}, /* 00,28,a0,aa */
+	{0xaa, 0x21, 0x0000}, /* 00,21,00,aa */
+	{0xaa, 0x2a, 0x0083}, /* 00,2a,83,aa */
+	{0xaa, 0x2b, 0x0096}, /* 00,2b,96,aa */
+	{0xaa, 0x2d, 0x0005}, /* 00,2d,05,aa */
+	{0xaa, 0x74, 0x0020}, /* 00,74,20,aa */
+	{0xaa, 0x61, 0x0068}, /* 00,61,68,aa */
+	{0xaa, 0x64, 0x0088}, /* 00,64,88,aa */
+	{0xaa, 0x00, 0x0000}, /* 00,00,00,aa */
+	{0xaa, 0x06, 0x0080}, /* 00,06,80,aa */
+	{0xaa, 0x01, 0x0090}, /* 00,01,90,aa */
+	{0xaa, 0x02, 0x0030}, /* 00,02,30,aa */
+	{0xa0, 0x77, ZC3XX_R101_SENSORCORRECTION}, /* 01,01,77,cc */
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC}, /* 00,12,05,cc */
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE}, /* 01,00,0d,cc */
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS}, /* 01,89,06,cc */
+	{0xa0, 0x00, 0x01ad}, /* 01,ad,00,cc */
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE}, /* 01,c5,03,cc */
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05}, /* 01,cb,13,cc */
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE}, /* 02,50,08,cc */
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS}, /* 03,01,08,cc */
+	{0xa0, 0x68, ZC3XX_R116_RGAIN}, /* 01,16,68,cc */
+	{0xa0, 0x52, ZC3XX_R118_BGAIN}, /* 01,18,52,cc */
+	{0xa0, 0x40, ZC3XX_R11D_GLOBALGAIN}, /* 01,1d,40,cc */
+	{0xa0, 0x02, ZC3XX_R180_AUTOCORRECTENABLE}, /* 01,80,02,cc */
+	{0xa0, 0x50, ZC3XX_R1A8_DIGITALGAIN}, /* 01,a8,50,cc */
+	{}
+};
+static const struct usb_action ov7620_InitialScale[] = {	/* 320x240 */
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL}, /* 00,00,01,cc */
+	{0xa0, 0x50, ZC3XX_R002_CLOCKSELECT},	/* 00,02,50,cc */
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},	/* 00,08,00,cc */
+						/* mx change? */
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING}, /* 00,01,01,cc */
+	{0xa0, 0x06, ZC3XX_R010_CMOSSENSORSELECT}, /* 00,10,06,cc */
+	{0xa0, 0x02, ZC3XX_R083_RGAINADDR},	/* 00,83,02,cc */
+	{0xa0, 0x01, ZC3XX_R085_BGAINADDR},	/* 00,85,01,cc */
+	{0xa0, 0x80, ZC3XX_R086_EXPTIMEHIGH},	/* 00,86,80,cc */
+	{0xa0, 0x81, ZC3XX_R087_EXPTIMEMID},	/* 00,87,81,cc */
+	{0xa0, 0x10, ZC3XX_R088_EXPTIMELOW},	/* 00,88,10,cc */
+	{0xa0, 0xa1, ZC3XX_R08B_I2CDEVICEADDR}, /* 00,8b,a1,cc */
+	{0xa0, 0x08, ZC3XX_R08D_COMPABILITYMODE}, /* 00,8d,08,cc */
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH}, /* 00,03,02,cc */
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW}, /* 00,04,80,cc */
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH}, /* 00,05,01,cc */
+	{0xa0, 0xd0, ZC3XX_R006_FRAMEHEIGHTLOW}, /* 00,06,d0,cc */
+	{0xa0, 0x03, ZC3XX_R012_VIDEOCONTROLFUNC}, /* 00,12,03,cc */
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC}, /* 00,12,01,cc */
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},	/* 00,98,00,cc */
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW},	/* 00,9a,00,cc */
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},	/* 01,1a,00,cc */
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW},	/* 01,1c,00,cc */
+	{0xa0, 0xd6, ZC3XX_R09C_WINHEIGHTLOW},	/* 00,9c,d6,cc */
+						/* OV7648 00,9c,d8,cc */
+	{0xa0, 0x88, ZC3XX_R09E_WINWIDTHLOW},	/* 00,9e,88,cc */
+	{0xaa, 0x12, 0x0088}, /* 00,12,88,aa */
+	{0xaa, 0x12, 0x0048}, /* 00,12,48,aa */
+	{0xaa, 0x75, 0x008a}, /* 00,75,8a,aa */
+	{0xaa, 0x13, 0x00a3}, /* 00,13,a3,aa */
+	{0xaa, 0x04, 0x0000}, /* 00,04,00,aa */
+	{0xaa, 0x05, 0x0000}, /* 00,05,00,aa */
+	{0xaa, 0x14, 0x0000}, /* 00,14,00,aa */
+	{0xaa, 0x15, 0x0004}, /* 00,15,04,aa */
+	{0xaa, 0x24, 0x0088}, /* 00,24,88,aa */
+	{0xaa, 0x25, 0x0078}, /* 00,25,78,aa */
+	{0xaa, 0x17, 0x0018}, /* 00,17,18,aa */
+	{0xaa, 0x18, 0x00ba}, /* 00,18,ba,aa */
+	{0xaa, 0x19, 0x0002}, /* 00,19,02,aa */
+	{0xaa, 0x1a, 0x00f2}, /* 00,1a,f2,aa */
+	{0xaa, 0x20, 0x0040}, /* 00,20,40,aa */
+	{0xaa, 0x27, 0x00f6}, /* 00,27,f6,aa */
+	{0xaa, 0x28, 0x00a0}, /* 00,28,a0,aa */
+	{0xaa, 0x21, 0x0000}, /* 00,21,00,aa */
+	{0xaa, 0x2a, 0x0083}, /* 00,2a,83,aa */
+	{0xaa, 0x2b, 0x0096}, /* 00,2b,96,aa */
+	{0xaa, 0x2d, 0x0005}, /* 00,2d,05,aa */
+	{0xaa, 0x74, 0x0020}, /* 00,74,20,aa */
+	{0xaa, 0x61, 0x0068}, /* 00,61,68,aa */
+	{0xaa, 0x64, 0x0088}, /* 00,64,88,aa */
+	{0xaa, 0x00, 0x0000}, /* 00,00,00,aa */
+	{0xaa, 0x06, 0x0080}, /* 00,06,80,aa */
+	{0xaa, 0x01, 0x0090}, /* 00,01,90,aa */
+	{0xaa, 0x02, 0x0030}, /* 00,02,30,aa */
+	{0xa0, 0x77, ZC3XX_R101_SENSORCORRECTION}, /* 01,01,77,cc */
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC}, /* 00,12,05,cc */
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE}, /* 01,00,0d,cc */
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},	/* 01,89,06,cc */
+	{0xa0, 0x00, 0x01ad},			/* 01,ad,00,cc */
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE}, /* 01,c5,03,cc */
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},	/* 01,cb,13,cc */
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE}, /* 02,50,08,cc */
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},	/* 03,01,08,cc */
+	{0xa0, 0x68, ZC3XX_R116_RGAIN},		/* 01,16,68,cc */
+	{0xa0, 0x52, ZC3XX_R118_BGAIN},		/* 01,18,52,cc */
+	{0xa0, 0x50, ZC3XX_R11D_GLOBALGAIN},	/* 01,1d,50,cc */
+	{0xa0, 0x02, ZC3XX_R180_AUTOCORRECTENABLE}, /* 01,80,02,cc */
+	{0xa0, 0x50, ZC3XX_R1A8_DIGITALGAIN},	/* 01,a8,50,cc */
+	{}
+};
+static const struct usb_action ov7620_50HZ[] = {
+	{0xdd, 0x00, 0x0100},	/* 00,01,00,dd */
+	{0xaa, 0x2b, 0x0096},	/* 00,2b,96,aa */
+	/* enable 1/120s & 1/100s exposures for banding filter */
+	{0xaa, 0x75, 0x008e},
+	{0xaa, 0x2d, 0x0005},	/* 00,2d,05,aa */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},	/* 01,90,00,cc */
+	{0xa0, 0x04, ZC3XX_R191_EXPOSURELIMITMID},	/* 01,91,04,cc */
+	{0xa0, 0x18, ZC3XX_R192_EXPOSURELIMITLOW},	/* 01,92,18,cc */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},	/* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},	/* 01,96,00,cc */
+	{0xa0, 0x83, ZC3XX_R197_ANTIFLICKERLOW},	/* 01,97,83,cc */
+	{0xaa, 0x76, 0x0003},				/* 00,76,03,aa */
+/*	{0xa0, 0x40, ZC3XX_R002_CLOCKSELECT},		 * 00,02,40,cc
+							 * if mode0 (640x480) */
+	{}
+};
+static const struct usb_action ov7620_60HZ[] = {
+	{0xdd, 0x00, 0x0100},			/* 00,01,00,dd */
+	{0xaa, 0x2b, 0x0000},			/* 00,2b,00,aa */
+	/* enable 1/120s & 1/100s exposures for banding filter */
+	{0xaa, 0x75, 0x008e},
+	{0xaa, 0x2d, 0x0005},			/* 00,2d,05,aa */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH}, /* 01,90,00,cc */
+	{0xa0, 0x04, ZC3XX_R191_EXPOSURELIMITMID}, /* 01,91,04,cc */
+	{0xa0, 0x18, ZC3XX_R192_EXPOSURELIMITLOW}, /* 01,92,18,cc */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH}, /* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID}, /* 01,96,00,cc */
+	{0xa0, 0x83, ZC3XX_R197_ANTIFLICKERLOW}, /* 01,97,83,cc */
+	{0xaa, 0x76, 0x0003},			/* 00,76,03,aa */
+/*	{0xa0, 0x40, ZC3XX_R002_CLOCKSELECT},	 * 00,02,40,cc
+						 * if mode0 (640x480) */
+/* ?? in gspca v1, it was
+	{0xa0, 0x00, 0x0039},  * 00,00,00,dd *
+	{0xa1, 0x01, 0x0037},		*/
+	{}
+};
+static const struct usb_action ov7620_NoFliker[] = {
+	{0xdd, 0x00, 0x0100},			/* 00,01,00,dd */
+	{0xaa, 0x2b, 0x0000},			/* 00,2b,00,aa */
+	/* disable 1/120s & 1/100s exposures for banding filter */
+	{0xaa, 0x75, 0x008a},
+	{0xaa, 0x2d, 0x0001},			/* 00,2d,01,aa */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH}, /* 01,90,00,cc */
+	{0xa0, 0x04, ZC3XX_R191_EXPOSURELIMITMID}, /* 01,91,04,cc */
+	{0xa0, 0x18, ZC3XX_R192_EXPOSURELIMITLOW}, /* 01,92,18,cc */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH}, /* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID}, /* 01,96,00,cc */
+	{0xa0, 0x01, ZC3XX_R197_ANTIFLICKERLOW}, /* 01,97,01,cc */
+/*	{0xa0, 0x44, ZC3XX_R002_CLOCKSELECT},	 * 00,02,44,cc
+						 * if mode1 (320x240) */
+/* ?? was
+	{0xa0, 0x00, 0x0039},  * 00,00,00,dd *
+	{0xa1, 0x01, 0x0037},		*/
+	{}
+};
+
+static const struct usb_action ov7630c_InitialScale[] = {
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},
+	{0xa0, 0x10, ZC3XX_R002_CLOCKSELECT},
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},
+	{0xa0, 0x10, ZC3XX_R002_CLOCKSELECT},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},
+	{0xa0, 0x06, ZC3XX_R010_CMOSSENSORSELECT},
+	{0xa0, 0xa1, ZC3XX_R08B_I2CDEVICEADDR},
+	{0xa0, 0x08, ZC3XX_R08D_COMPABILITYMODE},
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xaa, 0x12, 0x0080},
+	{0xa0, 0x02, ZC3XX_R083_RGAINADDR},
+	{0xa0, 0x01, ZC3XX_R085_BGAINADDR},
+	{0xa0, 0x90, ZC3XX_R086_EXPTIMEHIGH},
+	{0xa0, 0x91, ZC3XX_R087_EXPTIMEMID},
+	{0xa0, 0x10, ZC3XX_R088_EXPTIMELOW},
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW},
+	{0xa0, 0xd8, ZC3XX_R09C_WINHEIGHTLOW},
+	{0xa0, 0x88, ZC3XX_R09E_WINWIDTHLOW},
+	{0xaa, 0x12, 0x0069},
+	{0xaa, 0x04, 0x0020},
+	{0xaa, 0x06, 0x0050},
+	{0xaa, 0x13, 0x0083},
+	{0xaa, 0x14, 0x0000},
+	{0xaa, 0x15, 0x0024},
+	{0xaa, 0x17, 0x0018},
+	{0xaa, 0x18, 0x00ba},
+	{0xaa, 0x19, 0x0002},
+	{0xaa, 0x1a, 0x00f6},
+	{0xaa, 0x1b, 0x0002},
+	{0xaa, 0x20, 0x00c2},
+	{0xaa, 0x24, 0x0060},
+	{0xaa, 0x25, 0x0040},
+	{0xaa, 0x26, 0x0030},
+	{0xaa, 0x27, 0x00ea},
+	{0xaa, 0x28, 0x00a0},
+	{0xaa, 0x21, 0x0000},
+	{0xaa, 0x2a, 0x0081},
+	{0xaa, 0x2b, 0x0096},
+	{0xaa, 0x2d, 0x0094},
+	{0xaa, 0x2f, 0x003d},
+	{0xaa, 0x30, 0x0024},
+	{0xaa, 0x60, 0x0000},
+	{0xaa, 0x61, 0x0040},
+	{0xaa, 0x68, 0x007c},
+	{0xaa, 0x6f, 0x0015},
+	{0xaa, 0x75, 0x0088},
+	{0xaa, 0x77, 0x00b5},
+	{0xaa, 0x01, 0x0060},
+	{0xaa, 0x02, 0x0060},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x77, ZC3XX_R101_SENSORCORRECTION},
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},
+	{0xa0, 0x04, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x00, 0x01ad},
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},
+	{0xa0, 0x60, ZC3XX_R116_RGAIN},
+	{0xa0, 0x46, ZC3XX_R118_BGAIN},
+	{0xa0, 0x04, ZC3XX_R113_RGB03},
+/* 0x10, */
+	{0xa1, 0x01, 0x0002},
+	{0xa0, 0x50, ZC3XX_R10A_RGB00},	/* matrix */
+	{0xa0, 0xf8, ZC3XX_R10B_RGB01},
+	{0xa0, 0xf8, ZC3XX_R10C_RGB02},
+	{0xa0, 0xf8, ZC3XX_R10D_RGB10},
+	{0xa0, 0x50, ZC3XX_R10E_RGB11},
+	{0xa0, 0xf8, ZC3XX_R10F_RGB12},
+	{0xa0, 0xf8, ZC3XX_R110_RGB20},
+	{0xa0, 0xf8, ZC3XX_R111_RGB21},
+	{0xa0, 0x50, ZC3XX_R112_RGB22},
+	{0xa1, 0x01, 0x0008},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},	/* clock ? */
+	{0xa0, 0x08, ZC3XX_R1C6_SHARPNESS00},	/* sharpness+ */
+	{0xa1, 0x01, 0x01c8},
+	{0xa1, 0x01, 0x01c9},
+	{0xa1, 0x01, 0x01ca},
+	{0xa0, 0x0f, ZC3XX_R1CB_SHARPNESS05},	/* sharpness- */
+	{0xa0, 0x01, ZC3XX_R120_GAMMA00},	/* gamma 2 ?*/
+	{0xa0, 0x0c, ZC3XX_R121_GAMMA01},
+	{0xa0, 0x1f, ZC3XX_R122_GAMMA02},
+	{0xa0, 0x3a, ZC3XX_R123_GAMMA03},
+	{0xa0, 0x53, ZC3XX_R124_GAMMA04},
+	{0xa0, 0x6d, ZC3XX_R125_GAMMA05},
+	{0xa0, 0x85, ZC3XX_R126_GAMMA06},
+	{0xa0, 0x9c, ZC3XX_R127_GAMMA07},
+	{0xa0, 0xb0, ZC3XX_R128_GAMMA08},
+	{0xa0, 0xc2, ZC3XX_R129_GAMMA09},
+	{0xa0, 0xd1, ZC3XX_R12A_GAMMA0A},
+	{0xa0, 0xde, ZC3XX_R12B_GAMMA0B},
+	{0xa0, 0xe9, ZC3XX_R12C_GAMMA0C},
+	{0xa0, 0xf2, ZC3XX_R12D_GAMMA0D},
+	{0xa0, 0xf9, ZC3XX_R12E_GAMMA0E},
+	{0xa0, 0xff, ZC3XX_R12F_GAMMA0F},
+	{0xa0, 0x05, ZC3XX_R130_GAMMA10},
+	{0xa0, 0x0f, ZC3XX_R131_GAMMA11},
+	{0xa0, 0x16, ZC3XX_R132_GAMMA12},
+	{0xa0, 0x1a, ZC3XX_R133_GAMMA13},
+	{0xa0, 0x19, ZC3XX_R134_GAMMA14},
+	{0xa0, 0x19, ZC3XX_R135_GAMMA15},
+	{0xa0, 0x17, ZC3XX_R136_GAMMA16},
+	{0xa0, 0x15, ZC3XX_R137_GAMMA17},
+	{0xa0, 0x12, ZC3XX_R138_GAMMA18},
+	{0xa0, 0x10, ZC3XX_R139_GAMMA19},
+	{0xa0, 0x0e, ZC3XX_R13A_GAMMA1A},
+	{0xa0, 0x0b, ZC3XX_R13B_GAMMA1B},
+	{0xa0, 0x09, ZC3XX_R13C_GAMMA1C},
+	{0xa0, 0x08, ZC3XX_R13D_GAMMA1D},
+	{0xa0, 0x06, ZC3XX_R13E_GAMMA1E},
+	{0xa0, 0x03, ZC3XX_R13F_GAMMA1F},
+	{0xa0, 0x50, ZC3XX_R10A_RGB00},	/* matrix */
+	{0xa0, 0xf8, ZC3XX_R10B_RGB01},
+	{0xa0, 0xf8, ZC3XX_R10C_RGB02},
+	{0xa0, 0xf8, ZC3XX_R10D_RGB10},
+	{0xa0, 0x50, ZC3XX_R10E_RGB11},
+	{0xa0, 0xf8, ZC3XX_R10F_RGB12},
+	{0xa0, 0xf8, ZC3XX_R110_RGB20},
+	{0xa0, 0xf8, ZC3XX_R111_RGB21},
+	{0xa0, 0x50, ZC3XX_R112_RGB22},
+
+	{0xa1, 0x01, 0x0180},
+	{0xa0, 0x00, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xaa, 0x10, 0x001b},
+	{0xaa, 0x76, 0x0002},
+	{0xaa, 0x2a, 0x0081},
+	{0xaa, 0x2b, 0x0000},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x01, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0xb8, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x37, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x10, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x26, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x50, ZC3XX_R11D_GLOBALGAIN},
+	{0xa0, 0x02, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x40, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xaa, 0x13, 0x0083},	/* 40 */
+	{0xa1, 0x01, 0x0180},
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+	{}
+};
+
+static const struct usb_action ov7630c_Initial[] = {
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},
+	{0xa0, 0x00, ZC3XX_R002_CLOCKSELECT},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},
+	{0xa0, 0x06, ZC3XX_R010_CMOSSENSORSELECT},
+	{0xa0, 0xa1, ZC3XX_R08B_I2CDEVICEADDR},
+	{0xa0, 0x08, ZC3XX_R08D_COMPABILITYMODE},
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC},
+
+	{0xaa, 0x12, 0x0080},
+	{0xa0, 0x02, ZC3XX_R083_RGAINADDR},
+	{0xa0, 0x01, ZC3XX_R085_BGAINADDR},
+	{0xa0, 0x90, ZC3XX_R086_EXPTIMEHIGH},
+	{0xa0, 0x91, ZC3XX_R087_EXPTIMEMID},
+	{0xa0, 0x10, ZC3XX_R088_EXPTIMELOW},
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW},
+	{0xa0, 0xe6, ZC3XX_R09C_WINHEIGHTLOW},
+	{0xa0, 0x86, ZC3XX_R09E_WINWIDTHLOW},
+	{0xaa, 0x12, 0x0069},	/* i2c */
+	{0xaa, 0x04, 0x0020},
+	{0xaa, 0x06, 0x0050},
+	{0xaa, 0x13, 0x00c3},
+	{0xaa, 0x14, 0x0000},
+	{0xaa, 0x15, 0x0024},
+	{0xaa, 0x19, 0x0003},
+	{0xaa, 0x1a, 0x00f6},
+	{0xaa, 0x1b, 0x0002},
+	{0xaa, 0x20, 0x00c2},
+	{0xaa, 0x24, 0x0060},
+	{0xaa, 0x25, 0x0040},
+	{0xaa, 0x26, 0x0030},
+	{0xaa, 0x27, 0x00ea},
+	{0xaa, 0x28, 0x00a0},
+	{0xaa, 0x21, 0x0000},
+	{0xaa, 0x2a, 0x0081},
+	{0xaa, 0x2b, 0x0096},
+	{0xaa, 0x2d, 0x0084},
+	{0xaa, 0x2f, 0x003d},
+	{0xaa, 0x30, 0x0024},
+	{0xaa, 0x60, 0x0000},
+	{0xaa, 0x61, 0x0040},
+	{0xaa, 0x68, 0x007c},
+	{0xaa, 0x6f, 0x0015},
+	{0xaa, 0x75, 0x0088},
+	{0xaa, 0x77, 0x00b5},
+	{0xaa, 0x01, 0x0060},
+	{0xaa, 0x02, 0x0060},
+	{0xaa, 0x17, 0x0018},
+	{0xaa, 0x18, 0x00ba},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x77, ZC3XX_R101_SENSORCORRECTION},
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},
+	{0xa0, 0x04, ZC3XX_R1A7_CALCGLOBALMEAN},
+	{0xa0, 0x00, 0x01ad},
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},
+	{0xa0, 0x60, ZC3XX_R116_RGAIN},
+	{0xa0, 0x46, ZC3XX_R118_BGAIN},
+	{0xa0, 0x04, ZC3XX_R113_RGB03},
+
+	{0xa1, 0x01, 0x0002},
+	{0xa0, 0x4e, ZC3XX_R10A_RGB00},	/* matrix */
+	{0xa0, 0xfe, ZC3XX_R10B_RGB01},
+	{0xa0, 0xf4, ZC3XX_R10C_RGB02},
+	{0xa0, 0xf7, ZC3XX_R10D_RGB10},
+	{0xa0, 0x4d, ZC3XX_R10E_RGB11},
+	{0xa0, 0xfc, ZC3XX_R10F_RGB12},
+	{0xa0, 0x00, ZC3XX_R110_RGB20},
+	{0xa0, 0xf6, ZC3XX_R111_RGB21},
+	{0xa0, 0x4a, ZC3XX_R112_RGB22},
+
+	{0xa1, 0x01, 0x0008},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},	/* clock ? */
+	{0xa0, 0x08, ZC3XX_R1C6_SHARPNESS00},	/* sharpness+ */
+	{0xa1, 0x01, 0x01c8},
+	{0xa1, 0x01, 0x01c9},
+	{0xa1, 0x01, 0x01ca},
+	{0xa0, 0x0f, ZC3XX_R1CB_SHARPNESS05},	/* sharpness- */
+	{0xa0, 0x16, ZC3XX_R120_GAMMA00},	/* gamma ~4 */
+	{0xa0, 0x3a, ZC3XX_R121_GAMMA01},
+	{0xa0, 0x5b, ZC3XX_R122_GAMMA02},
+	{0xa0, 0x7c, ZC3XX_R123_GAMMA03},
+	{0xa0, 0x94, ZC3XX_R124_GAMMA04},
+	{0xa0, 0xa9, ZC3XX_R125_GAMMA05},
+	{0xa0, 0xbb, ZC3XX_R126_GAMMA06},
+	{0xa0, 0xca, ZC3XX_R127_GAMMA07},
+	{0xa0, 0xd7, ZC3XX_R128_GAMMA08},
+	{0xa0, 0xe1, ZC3XX_R129_GAMMA09},
+	{0xa0, 0xea, ZC3XX_R12A_GAMMA0A},
+	{0xa0, 0xf1, ZC3XX_R12B_GAMMA0B},
+	{0xa0, 0xf7, ZC3XX_R12C_GAMMA0C},
+	{0xa0, 0xfc, ZC3XX_R12D_GAMMA0D},
+	{0xa0, 0xff, ZC3XX_R12E_GAMMA0E},
+	{0xa0, 0xff, ZC3XX_R12F_GAMMA0F},
+	{0xa0, 0x20, ZC3XX_R130_GAMMA10},
+	{0xa0, 0x22, ZC3XX_R131_GAMMA11},
+	{0xa0, 0x20, ZC3XX_R132_GAMMA12},
+	{0xa0, 0x1c, ZC3XX_R133_GAMMA13},
+	{0xa0, 0x16, ZC3XX_R134_GAMMA14},
+	{0xa0, 0x13, ZC3XX_R135_GAMMA15},
+	{0xa0, 0x10, ZC3XX_R136_GAMMA16},
+	{0xa0, 0x0d, ZC3XX_R137_GAMMA17},
+	{0xa0, 0x0b, ZC3XX_R138_GAMMA18},
+	{0xa0, 0x09, ZC3XX_R139_GAMMA19},
+	{0xa0, 0x07, ZC3XX_R13A_GAMMA1A},
+	{0xa0, 0x06, ZC3XX_R13B_GAMMA1B},
+	{0xa0, 0x05, ZC3XX_R13C_GAMMA1C},
+	{0xa0, 0x04, ZC3XX_R13D_GAMMA1D},
+	{0xa0, 0x00, ZC3XX_R13E_GAMMA1E},
+	{0xa0, 0x01, ZC3XX_R13F_GAMMA1F},
+	{0xa0, 0x4e, ZC3XX_R10A_RGB00},	/* matrix */
+	{0xa0, 0xfe, ZC3XX_R10B_RGB01},
+	{0xa0, 0xf4, ZC3XX_R10C_RGB02},
+	{0xa0, 0xf7, ZC3XX_R10D_RGB10},
+	{0xa0, 0x4d, ZC3XX_R10E_RGB11},
+	{0xa0, 0xfc, ZC3XX_R10F_RGB12},
+	{0xa0, 0x00, ZC3XX_R110_RGB20},
+	{0xa0, 0xf6, ZC3XX_R111_RGB21},
+	{0xa0, 0x4a, ZC3XX_R112_RGB22},
+
+	{0xa1, 0x01, 0x0180},
+	{0xa0, 0x00, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xaa, 0x10, 0x000d},
+	{0xaa, 0x76, 0x0002},
+	{0xaa, 0x2a, 0x0081},
+	{0xaa, 0x2b, 0x0000},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x00, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0xd8, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x1b, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x10, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x26, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x50, ZC3XX_R11D_GLOBALGAIN},
+	{0xa0, 0x02, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x40, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xaa, 0x13, 0x00c3},
+
+	{0xa1, 0x01, 0x0180},
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+	{}
+};
+
+static const struct usb_action pas106b_Initial_com[] = {
+/* Sream and Sensor specific */
+	{0xa1, 0x01, 0x0010},	/* CMOSSensorSelect */
+/* System */
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},	/* SystemControl */
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},	/* SystemControl */
+/* Picture size */
+	{0xa0, 0x00, ZC3XX_R002_CLOCKSELECT},	/* ClockSelect */
+	{0xa0, 0x03, 0x003a},
+	{0xa0, 0x0c, 0x003b},
+	{0xa0, 0x04, 0x0038},
+	{}
+};
+
+static const struct usb_action pas106b_InitialScale[] = {	/* 176x144 */
+/* JPEG control */
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},
+/* Sream and Sensor specific */
+	{0xa0, 0x0f, ZC3XX_R010_CMOSSENSORSELECT},
+/* Picture size */
+	{0xa0, 0x00, ZC3XX_R003_FRAMEWIDTHHIGH},
+	{0xa0, 0xb0, ZC3XX_R004_FRAMEWIDTHLOW},
+	{0xa0, 0x00, ZC3XX_R005_FRAMEHEIGHTHIGH},
+	{0xa0, 0x90, ZC3XX_R006_FRAMEHEIGHTLOW},
+/* System */
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},
+/* Sream and Sensor specific */
+	{0xa0, 0x03, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC},
+/* Sensor Interface */
+	{0xa0, 0x08, ZC3XX_R08D_COMPABILITYMODE},
+/* Window inside sensor array */
+	{0xa0, 0x03, ZC3XX_R09A_WINXSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},
+	{0xa0, 0x03, ZC3XX_R11C_FIRSTXLOW},
+	{0xa0, 0x28, ZC3XX_R09C_WINHEIGHTLOW},
+	{0xa0, 0x68, ZC3XX_R09E_WINWIDTHLOW},
+/* Init the sensor */
+	{0xaa, 0x02, 0x0004},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x09, 0x0005},
+	{0xaa, 0x0a, 0x0002},
+	{0xaa, 0x0b, 0x0002},
+	{0xaa, 0x0c, 0x0005},
+	{0xaa, 0x0d, 0x0000},
+	{0xaa, 0x0e, 0x0002},
+	{0xaa, 0x14, 0x0081},
+/* Other registers */
+	{0xa0, 0x37, ZC3XX_R101_SENSORCORRECTION},
+/* Frame retreiving */
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+/* Gains */
+	{0xa0, 0xa0, ZC3XX_R1A8_DIGITALGAIN},
+/* Unknown */
+	{0xa0, 0x00, 0x01ad},
+/* Sharpness */
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},
+/* Other registers */
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},
+/* Auto exposure and white balance */
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},
+/*Dead pixels */
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},
+/* EEPROM */
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},
+/* JPEG control */
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},
+	{0xa0, 0x08, ZC3XX_R1C6_SHARPNESS00},
+	{0xa0, 0x0f, ZC3XX_R1CB_SHARPNESS05},
+/* Other registers */
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},
+/* Auto exposure and white balance */
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},
+/*Dead pixels */
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},
+/* EEPROM */
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},
+/* JPEG control */
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},
+	{0xa0, 0x08, ZC3XX_R1C6_SHARPNESS00},
+	{0xa0, 0x0f, ZC3XX_R1CB_SHARPNESS05},
+
+	{0xa0, 0x58, ZC3XX_R10A_RGB00},	/* matrix */
+	{0xa0, 0xf4, ZC3XX_R10B_RGB01},
+	{0xa0, 0xf4, ZC3XX_R10C_RGB02},
+	{0xa0, 0xf4, ZC3XX_R10D_RGB10},
+	{0xa0, 0x58, ZC3XX_R10E_RGB11},
+	{0xa0, 0xf4, ZC3XX_R10F_RGB12},
+	{0xa0, 0xf4, ZC3XX_R110_RGB20},
+	{0xa0, 0xf4, ZC3XX_R111_RGB21},
+	{0xa0, 0x58, ZC3XX_R112_RGB22},
+/* Auto correction */
+	{0xa0, 0x03, ZC3XX_R181_WINXSTART},
+	{0xa0, 0x08, ZC3XX_R182_WINXWIDTH},
+	{0xa0, 0x16, ZC3XX_R183_WINXCENTER},
+	{0xa0, 0x03, ZC3XX_R184_WINYSTART},
+	{0xa0, 0x05, ZC3XX_R185_WINYWIDTH},
+	{0xa0, 0x14, ZC3XX_R186_WINYCENTER},
+	{0xa0, 0x00, ZC3XX_R180_AUTOCORRECTENABLE},
+/* Auto exposure and white balance */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x03, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0xb1, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x87, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x0c, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x18, ZC3XX_R18F_AEUNFREEZE},
+/* sensor on */
+	{0xaa, 0x07, 0x00b1},
+	{0xaa, 0x05, 0x0003},
+	{0xaa, 0x04, 0x0001},
+	{0xaa, 0x03, 0x003b},
+/* Gains */
+	{0xa0, 0x20, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x26, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0xa0, ZC3XX_R11D_GLOBALGAIN},
+	{0xa0, 0x60, ZC3XX_R11D_GLOBALGAIN},
+/* Auto correction */
+	{0xa0, 0x40, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa1, 0x01, 0x0180},				/* AutoCorrectEnable */
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+/* Gains */
+	{0xa0, 0x40, ZC3XX_R116_RGAIN},
+	{0xa0, 0x40, ZC3XX_R117_GGAIN},
+	{0xa0, 0x40, ZC3XX_R118_BGAIN},
+	{}
+};
+
+static const struct usb_action pas106b_Initial[] = {	/* 352x288 */
+/* JPEG control */
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},
+/* Sream and Sensor specific */
+	{0xa0, 0x0f, ZC3XX_R010_CMOSSENSORSELECT},
+/* Picture size */
+	{0xa0, 0x01, ZC3XX_R003_FRAMEWIDTHHIGH},
+	{0xa0, 0x60, ZC3XX_R004_FRAMEWIDTHLOW},
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},
+	{0xa0, 0x20, ZC3XX_R006_FRAMEHEIGHTLOW},
+/* System */
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},
+/* Sream and Sensor specific */
+	{0xa0, 0x03, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC},
+/* Sensor Interface */
+	{0xa0, 0x08, ZC3XX_R08D_COMPABILITYMODE},
+/* Window inside sensor array */
+	{0xa0, 0x03, ZC3XX_R09A_WINXSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},
+	{0xa0, 0x03, ZC3XX_R11C_FIRSTXLOW},
+	{0xa0, 0x28, ZC3XX_R09C_WINHEIGHTLOW},
+	{0xa0, 0x68, ZC3XX_R09E_WINWIDTHLOW},
+/* Init the sensor */
+	{0xaa, 0x02, 0x0004},
+	{0xaa, 0x08, 0x0000},
+	{0xaa, 0x09, 0x0005},
+	{0xaa, 0x0a, 0x0002},
+	{0xaa, 0x0b, 0x0002},
+	{0xaa, 0x0c, 0x0005},
+	{0xaa, 0x0d, 0x0000},
+	{0xaa, 0x0e, 0x0002},
+	{0xaa, 0x14, 0x0081},
+/* Other registers */
+	{0xa0, 0x37, ZC3XX_R101_SENSORCORRECTION},
+/* Frame retreiving */
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+/* Gains */
+	{0xa0, 0xa0, ZC3XX_R1A8_DIGITALGAIN},
+/* Unknown */
+	{0xa0, 0x00, 0x01ad},
+/* Sharpness */
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},
+/* Other registers */
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},
+/* Auto exposure and white balance */
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},
+	{0xa0, 0x80, ZC3XX_R18D_YTARGET},
+/*Dead pixels */
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},
+/* EEPROM */
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},
+/* JPEG control */
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},
+	{0xa0, 0x08, ZC3XX_R1C6_SHARPNESS00},
+	{0xa0, 0x0f, ZC3XX_R1CB_SHARPNESS05},
+/* Other registers */
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},
+/* Auto exposure and white balance */
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},
+/*Dead pixels */
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},
+/* EEPROM */
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},
+/* JPEG control */
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},
+	{0xa0, 0x08, ZC3XX_R1C6_SHARPNESS00},
+	{0xa0, 0x0f, ZC3XX_R1CB_SHARPNESS05},
+
+	{0xa0, 0x58, ZC3XX_R10A_RGB00},	/* matrix */
+	{0xa0, 0xf4, ZC3XX_R10B_RGB01},
+	{0xa0, 0xf4, ZC3XX_R10C_RGB02},
+	{0xa0, 0xf4, ZC3XX_R10D_RGB10},
+	{0xa0, 0x58, ZC3XX_R10E_RGB11},
+	{0xa0, 0xf4, ZC3XX_R10F_RGB12},
+	{0xa0, 0xf4, ZC3XX_R110_RGB20},
+	{0xa0, 0xf4, ZC3XX_R111_RGB21},
+	{0xa0, 0x58, ZC3XX_R112_RGB22},
+/* Auto correction */
+	{0xa0, 0x03, ZC3XX_R181_WINXSTART},
+	{0xa0, 0x08, ZC3XX_R182_WINXWIDTH},
+	{0xa0, 0x16, ZC3XX_R183_WINXCENTER},
+	{0xa0, 0x03, ZC3XX_R184_WINYSTART},
+	{0xa0, 0x05, ZC3XX_R185_WINYWIDTH},
+	{0xa0, 0x14, ZC3XX_R186_WINYCENTER},
+	{0xa0, 0x00, ZC3XX_R180_AUTOCORRECTENABLE},
+
+/* Auto exposure and white balance */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x03, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0xb1, ZC3XX_R192_EXPOSURELIMITLOW},
+
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x87, ZC3XX_R197_ANTIFLICKERLOW},
+
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE},
+/* sensor on */
+	{0xaa, 0x07, 0x00b1},
+	{0xaa, 0x05, 0x0003},
+	{0xaa, 0x04, 0x0001},
+	{0xaa, 0x03, 0x003b},
+/* Gains */
+	{0xa0, 0x20, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x26, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x60, ZC3XX_R11D_GLOBALGAIN},
+/* Auto correction */
+	{0xa0, 0x40, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa1, 0x01, 0x0180},				/* AutoCorrectEnable */
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+/* Gains */
+	{0xa0, 0x40, ZC3XX_R116_RGAIN},
+	{0xa0, 0x40, ZC3XX_R117_GGAIN},
+	{0xa0, 0x40, ZC3XX_R118_BGAIN},
+
+	{0xa0, 0x00, 0x0007},			/* AutoCorrectEnable */
+	{0xa0, 0xff, ZC3XX_R018_FRAMELOST},	/* Frame adjust */
+	{}
+};
+static const struct usb_action pas106b_50HZ[] = {
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH}, /* 01,90,00,cc */
+	{0xa0, 0x06, ZC3XX_R191_EXPOSURELIMITMID}, /* 01,91,06,cc */
+	{0xa0, 0x54, ZC3XX_R192_EXPOSURELIMITLOW}, /* 01,92,54,cc */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH}, /* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID}, /* 01,96,00,cc */
+	{0xa0, 0x87, ZC3XX_R197_ANTIFLICKERLOW}, /* 01,97,87,cc */
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE},	/* 01,8c,10,cc */
+	{0xa0, 0x30, ZC3XX_R18F_AEUNFREEZE},	/* 01,8f,30,cc */
+	{0xaa, 0x03, 0x0021},			/* 00,03,21,aa */
+	{0xaa, 0x04, 0x000c},			/* 00,04,0c,aa */
+	{0xaa, 0x05, 0x0002},			/* 00,05,02,aa */
+	{0xaa, 0x07, 0x001c},			/* 00,07,1c,aa */
+	{0xa0, 0x04, ZC3XX_R1A9_DIGITALLIMITDIFF}, /* 01,a9,04,cc */
+	{}
+};
+static const struct usb_action pas106b_60HZ[] = {
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH}, /* 01,90,00,cc */
+	{0xa0, 0x06, ZC3XX_R191_EXPOSURELIMITMID}, /* 01,91,06,cc */
+	{0xa0, 0x2e, ZC3XX_R192_EXPOSURELIMITLOW}, /* 01,92,2e,cc */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH}, /* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID}, /* 01,96,00,cc */
+	{0xa0, 0x71, ZC3XX_R197_ANTIFLICKERLOW}, /* 01,97,71,cc */
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE},	/* 01,8c,10,cc */
+	{0xa0, 0x30, ZC3XX_R18F_AEUNFREEZE},	/* 01,8f,30,cc */
+	{0xaa, 0x03, 0x001c},			/* 00,03,1c,aa */
+	{0xaa, 0x04, 0x0004},			/* 00,04,04,aa */
+	{0xaa, 0x05, 0x0001},			/* 00,05,01,aa */
+	{0xaa, 0x07, 0x00c4},			/* 00,07,c4,aa */
+	{0xa0, 0x04, ZC3XX_R1A9_DIGITALLIMITDIFF}, /* 01,a9,04,cc */
+	{}
+};
+static const struct usb_action pas106b_NoFliker[] = {
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH}, /* 01,90,00,cc */
+	{0xa0, 0x06, ZC3XX_R191_EXPOSURELIMITMID}, /* 01,91,06,cc */
+	{0xa0, 0x50, ZC3XX_R192_EXPOSURELIMITLOW}, /* 01,92,50,cc */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH}, /* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID}, /* 01,96,00,cc */
+	{0xa0, 0x10, ZC3XX_R197_ANTIFLICKERLOW}, /* 01,97,10,cc */
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE},	/* 01,8c,10,cc */
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE},	/* 01,8f,20,cc */
+	{0xaa, 0x03, 0x0013},			/* 00,03,13,aa */
+	{0xaa, 0x04, 0x0000},			/* 00,04,00,aa */
+	{0xaa, 0x05, 0x0001},			/* 00,05,01,aa */
+	{0xaa, 0x07, 0x0030},			/* 00,07,30,aa */
+	{0xa0, 0x00, ZC3XX_R1A9_DIGITALLIMITDIFF}, /* 01,a9,00,cc */
+	{}
+};
+
+/* from lvWIMv.inf 046d:08a2/:08aa 2007/06/03 */
+static const struct usb_action pas202b_Initial[] = {	/* 640x480 */
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},		/* 00,00,01,cc */
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},
+	{0xa0, 0x0e, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,0e,cc */
+	{0xa0, 0x00, ZC3XX_R002_CLOCKSELECT},		/* 00,02,00,cc */
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},	/* 00,03,02,cc */
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},		/* 00,04,80,cc */
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},	/* 00,05,01,cc */
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},	/* 00,06,e0,cc */
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},	/* 00,01,01,cc */
+	{0xa0, 0x03, ZC3XX_R012_VIDEOCONTROLFUNC},	/* 00,12,03,cc */
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC},	/* 00,12,01,cc */
+	{0xa0, 0x08, ZC3XX_R08D_COMPABILITYMODE},	/* 00,8d,08,cc */
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},		/* 00,98,00,cc */
+	{0xa0, 0x03, ZC3XX_R09A_WINXSTARTLOW},		/* 00,9a,03,cc */
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},		/* 01,1a,00,cc */
+	{0xa0, 0x03, ZC3XX_R11C_FIRSTXLOW},		/* 01,1c,03,cc */
+	{0xa0, 0x01, ZC3XX_R09B_WINHEIGHTHIGH},		/* 00,9b,01,cc */
+	{0xa0, 0xe6, ZC3XX_R09C_WINHEIGHTLOW},		/* 00,9c,e6,cc */
+	{0xa0, 0x02, ZC3XX_R09D_WINWIDTHHIGH},		/* 00,9d,02,cc */
+	{0xa0, 0x86, ZC3XX_R09E_WINWIDTHLOW},		/* 00,9e,86,cc */
+	{0xaa, 0x02, 0x0002},			/* 00,02,04,aa --> 02 */
+	{0xaa, 0x07, 0x0006},				/* 00,07,06,aa */
+	{0xaa, 0x08, 0x0002},				/* 00,08,02,aa */
+	{0xaa, 0x09, 0x0006},				/* 00,09,06,aa */
+	{0xaa, 0x0a, 0x0001},				/* 00,0a,01,aa */
+	{0xaa, 0x0b, 0x0001},				/* 00,0b,01,aa */
+	{0xaa, 0x0c, 0x0006},
+	{0xaa, 0x0d, 0x0000},				/* 00,0d,00,aa */
+	{0xaa, 0x10, 0x0000},				/* 00,10,00,aa */
+	{0xaa, 0x12, 0x0005},				/* 00,12,05,aa */
+	{0xaa, 0x13, 0x0063},				/* 00,13,63,aa */
+	{0xaa, 0x15, 0x0070},				/* 00,15,70,aa */
+	{0xa0, 0xb7, ZC3XX_R101_SENSORCORRECTION},	/* 01,01,b7,cc */
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},		/* 01,00,0d,cc */
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},		/* 01,89,06,cc */
+	{0xa0, 0x00, 0x01ad},				/* 01,ad,00,cc */
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},		/* 01,c5,03,cc */
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},		/* 01,cb,13,cc */
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},	/* 02,50,08,cc */
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},		/* 03,01,08,cc */
+	{0xa0, 0x70, ZC3XX_R18D_YTARGET},		/* 01,8d,70,cc */
+	{}
+};
+static const struct usb_action pas202b_InitialScale[] = {	/* 320x240 */
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},		/* 00,00,01,cc */
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},
+	{0xa0, 0x0e, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,0e,cc */
+	{0xa0, 0x10, ZC3XX_R002_CLOCKSELECT},		/* 00,02,10,cc */
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},	/* 00,03,02,cc */
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},		/* 00,04,80,cc */
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},	/* 00,05,01,cc */
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},	/* 00,01,01,cc */
+	{0xa0, 0x03, ZC3XX_R012_VIDEOCONTROLFUNC},	/* 00,12,03,cc */
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC},	/* 00,12,01,cc */
+	{0xa0, 0x08, ZC3XX_R08D_COMPABILITYMODE},	/* 00,8d,08,cc */
+	{0xa0, 0x08, ZC3XX_R098_WINYSTARTLOW},		/* 00,98,08,cc */
+	{0xa0, 0x02, ZC3XX_R09A_WINXSTARTLOW},		/* 00,9a,02,cc */
+	{0xa0, 0x08, ZC3XX_R11A_FIRSTYLOW},		/* 01,1a,08,cc */
+	{0xa0, 0x02, ZC3XX_R11C_FIRSTXLOW},		/* 01,1c,02,cc */
+	{0xa0, 0x01, ZC3XX_R09B_WINHEIGHTHIGH},		/* 00,9b,01,cc */
+	{0xa0, 0xe8, ZC3XX_R09C_WINHEIGHTLOW},
+	{0xa0, 0x02, ZC3XX_R09D_WINWIDTHHIGH},		/* 00,9d,02,cc */
+	{0xa0, 0x88, ZC3XX_R09E_WINWIDTHLOW},		/* 00,9e,88,cc */
+	{0xaa, 0x02, 0x0002},				/* 00,02,02,aa */
+	{0xaa, 0x07, 0x0006},				/* 00,07,06,aa */
+	{0xaa, 0x08, 0x0002},				/* 00,08,02,aa */
+	{0xaa, 0x09, 0x0006},				/* 00,09,06,aa */
+	{0xaa, 0x0a, 0x0001},				/* 00,0a,01,aa */
+	{0xaa, 0x0b, 0x0001},				/* 00,0b,01,aa */
+	{0xaa, 0x0c, 0x0006},
+	{0xaa, 0x0d, 0x0000},				/* 00,0d,00,aa */
+	{0xaa, 0x10, 0x0000},				/* 00,10,00,aa */
+	{0xaa, 0x12, 0x0005},				/* 00,12,05,aa */
+	{0xaa, 0x13, 0x0063},				/* 00,13,63,aa */
+	{0xaa, 0x15, 0x0070},				/* 00,15,70,aa */
+	{0xa0, 0x37, ZC3XX_R101_SENSORCORRECTION},	/* 01,01,37,cc */
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},		/* 01,00,0d,cc */
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},		/* 01,89,06,cc */
+	{0xa0, 0x00, 0x01ad},				/* 01,ad,00,cc */
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},		/* 01,c5,03,cc */
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},		/* 01,cb,13,cc */
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},	/* 02,50,08,cc */
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},		/* 03,01,08,cc */
+	{0xa0, 0x70, ZC3XX_R18D_YTARGET},		/* 01,8d,70,cc */
+	{0xa0, 0xff, ZC3XX_R097_WINYSTARTHIGH},
+	{0xa0, 0xfe, ZC3XX_R098_WINYSTARTLOW},
+	{}
+};
+static const struct usb_action pas202b_50HZ[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},		/* 00,19,00,cc */
+	{0xa0, 0x20, ZC3XX_R087_EXPTIMEMID},		/* 00,87,20,cc */
+	{0xa0, 0x21, ZC3XX_R088_EXPTIMELOW},		/* 00,88,21,cc */
+	{0xaa, 0x20, 0x0002},				/* 00,20,02,aa */
+	{0xaa, 0x21, 0x001b},
+	{0xaa, 0x03, 0x0044},				/* 00,03,44,aa */
+	{0xaa, 0x04, 0x0008},
+	{0xaa, 0x05, 0x001b},
+	{0xaa, 0x0e, 0x0001},				/* 00,0e,01,aa */
+	{0xaa, 0x0f, 0x0000},				/* 00,0f,00,aa */
+	{0xa0, 0x1c, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},	/* 01,aa,24,cc */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},	/* 01,90,00,cc */
+	{0xa0, 0x02, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x1b, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},	/* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},	/* 01,96,00,cc */
+	{0xa0, 0x4d, ZC3XX_R197_ANTIFLICKERLOW},	/* 01,97,4d,cc */
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x1b, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x44, ZC3XX_R01D_HSYNC_0},		/* 00,1d,44,cc */
+	{0xa0, 0x6f, ZC3XX_R01E_HSYNC_1},		/* 00,1e,6f,cc */
+	{0xa0, 0xad, ZC3XX_R01F_HSYNC_2},		/* 00,1f,ad,cc */
+	{0xa0, 0xeb, ZC3XX_R020_HSYNC_3},		/* 00,20,eb,cc */
+	{0xa0, 0x0f, ZC3XX_R087_EXPTIMEMID},		/* 00,87,0f,cc */
+	{0xa0, 0x0e, ZC3XX_R088_EXPTIMELOW},		/* 00,88,0e,cc */
+	{}
+};
+static const struct usb_action pas202b_50HZScale[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},		/* 00,19,00,cc */
+	{0xa0, 0x20, ZC3XX_R087_EXPTIMEMID},		/* 00,87,20,cc */
+	{0xa0, 0x21, ZC3XX_R088_EXPTIMELOW},		/* 00,88,21,cc */
+	{0xaa, 0x20, 0x0004},
+	{0xaa, 0x21, 0x003d},
+	{0xaa, 0x03, 0x0041},				/* 00,03,41,aa */
+	{0xaa, 0x04, 0x0010},
+	{0xaa, 0x05, 0x003d},
+	{0xaa, 0x0e, 0x0001},				/* 00,0e,01,aa */
+	{0xaa, 0x0f, 0x0000},				/* 00,0f,00,aa */
+	{0xa0, 0x1c, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},	/* 01,aa,24,cc */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},	/* 01,90,00,cc */
+	{0xa0, 0x04, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x3d, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},	/* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},	/* 01,96,00,cc */
+	{0xa0, 0x9b, ZC3XX_R197_ANTIFLICKERLOW},	/* 01,97,9b,cc */
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x1b, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x41, ZC3XX_R01D_HSYNC_0},		/* 00,1d,41,cc */
+	{0xa0, 0x6f, ZC3XX_R01E_HSYNC_1},		/* 00,1e,6f,cc */
+	{0xa0, 0xad, ZC3XX_R01F_HSYNC_2},		/* 00,1f,ad,cc */
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},		/* 00,20,ff,cc */
+	{0xa0, 0x0f, ZC3XX_R087_EXPTIMEMID},		/* 00,87,0f,cc */
+	{0xa0, 0x0e, ZC3XX_R088_EXPTIMELOW},		/* 00,88,0e,cc */
+	{}
+};
+static const struct usb_action pas202b_60HZ[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},		/* 00,19,00,cc */
+	{0xa0, 0x20, ZC3XX_R087_EXPTIMEMID},		/* 00,87,20,cc */
+	{0xa0, 0x21, ZC3XX_R088_EXPTIMELOW},		/* 00,88,21,cc */
+	{0xaa, 0x20, 0x0002},				/* 00,20,02,aa */
+	{0xaa, 0x21, 0x0000},				/* 00,21,00,aa */
+	{0xaa, 0x03, 0x0045},				/* 00,03,45,aa */
+	{0xaa, 0x04, 0x0008},				/* 00,04,08,aa */
+	{0xaa, 0x05, 0x0000},				/* 00,05,00,aa */
+	{0xaa, 0x0e, 0x0001},				/* 00,0e,01,aa */
+	{0xaa, 0x0f, 0x0000},				/* 00,0f,00,aa */
+	{0xa0, 0x1c, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},	/* 01,aa,24,cc */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},	/* 01,90,00,cc */
+	{0xa0, 0x02, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x00, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},	/* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},	/* 01,96,00,cc */
+	{0xa0, 0x40, ZC3XX_R197_ANTIFLICKERLOW},	/* 01,97,40,cc */
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x1b, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x45, ZC3XX_R01D_HSYNC_0},		/* 00,1d,45,cc */
+	{0xa0, 0x8e, ZC3XX_R01E_HSYNC_1},		/* 00,1e,8e,cc */
+	{0xa0, 0xc1, ZC3XX_R01F_HSYNC_2},		/* 00,1f,c1,cc */
+	{0xa0, 0xf5, ZC3XX_R020_HSYNC_3},		/* 00,20,f5,cc */
+	{0xa0, 0x0f, ZC3XX_R087_EXPTIMEMID},		/* 00,87,0f,cc */
+	{0xa0, 0x0e, ZC3XX_R088_EXPTIMELOW},		/* 00,88,0e,cc */
+	{}
+};
+static const struct usb_action pas202b_60HZScale[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},		/* 00,19,00,cc */
+	{0xa0, 0x20, ZC3XX_R087_EXPTIMEMID},		/* 00,87,20,cc */
+	{0xa0, 0x21, ZC3XX_R088_EXPTIMELOW},		/* 00,88,21,cc */
+	{0xaa, 0x20, 0x0004},
+	{0xaa, 0x21, 0x0008},
+	{0xaa, 0x03, 0x0042},				/* 00,03,42,aa */
+	{0xaa, 0x04, 0x0010},
+	{0xaa, 0x05, 0x0008},
+	{0xaa, 0x0e, 0x0001},				/* 00,0e,01,aa */
+	{0xaa, 0x0f, 0x0000},				/* 00,0f,00,aa */
+	{0xa0, 0x1c, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},	/* 01,aa,24,cc */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},	/* 01,90,00,cc */
+	{0xa0, 0x04, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x08, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},	/* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},	/* 01,96,00,cc */
+	{0xa0, 0x81, ZC3XX_R197_ANTIFLICKERLOW},	/* 01,97,81,cc */
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x1b, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x42, ZC3XX_R01D_HSYNC_0},		/* 00,1d,42,cc */
+	{0xa0, 0x6f, ZC3XX_R01E_HSYNC_1},		/* 00,1e,6f,cc */
+	{0xa0, 0xaf, ZC3XX_R01F_HSYNC_2},		/* 00,1f,af,cc */
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},		/* 00,20,ff,cc */
+	{0xa0, 0x0f, ZC3XX_R087_EXPTIMEMID},		/* 00,87,0f,cc */
+	{0xa0, 0x0e, ZC3XX_R088_EXPTIMELOW},		/* 00,88,0e,cc */
+	{}
+};
+static const struct usb_action pas202b_NoFliker[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},		/* 00,19,00,cc */
+	{0xa0, 0x20, ZC3XX_R087_EXPTIMEMID},		/* 00,87,20,cc */
+	{0xa0, 0x21, ZC3XX_R088_EXPTIMELOW},		/* 00,88,21,cc */
+	{0xaa, 0x20, 0x0002},				/* 00,20,02,aa */
+	{0xaa, 0x21, 0x0006},
+	{0xaa, 0x03, 0x0040},				/* 00,03,40,aa */
+	{0xaa, 0x04, 0x0008},				/* 00,04,08,aa */
+	{0xaa, 0x05, 0x0006},
+	{0xaa, 0x0e, 0x0001},				/* 00,0e,01,aa */
+	{0xaa, 0x0f, 0x0000},				/* 00,0f,00,aa */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},	/* 01,90,00,cc */
+	{0xa0, 0x02, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x06, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},	/* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},	/* 01,96,00,cc */
+	{0xa0, 0x01, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE},		/* 01,8c,10,cc */
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE},		/* 01,8f,20,cc */
+	{0xa0, 0x00, ZC3XX_R1A9_DIGITALLIMITDIFF},	/* 01,a9,00,cc */
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x40, ZC3XX_R01D_HSYNC_0},		/* 00,1d,40,cc */
+	{0xa0, 0x60, ZC3XX_R01E_HSYNC_1},		/* 00,1e,60,cc */
+	{0xa0, 0x90, ZC3XX_R01F_HSYNC_2},		/* 00,1f,90,cc */
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},		/* 00,20,ff,cc */
+	{0xa0, 0x0f, ZC3XX_R087_EXPTIMEMID},		/* 00,87,0f,cc */
+	{0xa0, 0x0e, ZC3XX_R088_EXPTIMELOW},		/* 00,88,0e,cc */
+	{}
+};
+static const struct usb_action pas202b_NoFlikerScale[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},		/* 00,19,00,cc */
+	{0xa0, 0x20, ZC3XX_R087_EXPTIMEMID},		/* 00,87,20,cc */
+	{0xa0, 0x21, ZC3XX_R088_EXPTIMELOW},		/* 00,88,21,cc */
+	{0xaa, 0x20, 0x0004},
+	{0xaa, 0x21, 0x000c},
+	{0xaa, 0x03, 0x0040},				/* 00,03,40,aa */
+	{0xaa, 0x04, 0x0010},
+	{0xaa, 0x05, 0x000c},
+	{0xaa, 0x0e, 0x0001},				/* 00,0e,01,aa */
+	{0xaa, 0x0f, 0x0000},				/* 00,0f,00,aa */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},	/* 01,90,00,cc */
+	{0xa0, 0x04, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x0c, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},	/* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},	/* 01,96,00,cc */
+	{0xa0, 0x02, ZC3XX_R197_ANTIFLICKERLOW},	/* 01,97,02,cc */
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE},		/* 01,8c,10,cc */
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE},		/* 01,8f,20,cc */
+	{0xa0, 0x00, ZC3XX_R1A9_DIGITALLIMITDIFF},	/* 01,a9,00,cc */
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x40, ZC3XX_R01D_HSYNC_0},		/* 00,1d,40,cc */
+	{0xa0, 0x60, ZC3XX_R01E_HSYNC_1},		/* 00,1e,60,cc */
+	{0xa0, 0x90, ZC3XX_R01F_HSYNC_2},		/* 00,1f,90,cc */
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},		/* 00,20,ff,cc */
+	{0xa0, 0x0f, ZC3XX_R087_EXPTIMEMID},		/* 00,87,0f,cc */
+	{0xa0, 0x0e, ZC3XX_R088_EXPTIMELOW},		/* 00,88,0e,cc */
+	{}
+};
+
+/* mt9v111 (mi0360soc) and pb0330 from vm30x.inf 0ac8:301b 07/02/13 */
+static const struct usb_action mt9v111_1_Initial[] = {	/* 640x480 */
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},
+	{0xa0, 0x0a, ZC3XX_R010_CMOSSENSORSELECT},
+	{0xa0, 0x00, ZC3XX_R002_CLOCKSELECT},
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},
+	{0xa0, 0xdc, ZC3XX_R08B_I2CDEVICEADDR},
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},
+	{0xa0, 0x07, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW},
+	{0xa0, 0xdc, ZC3XX_R08B_I2CDEVICEADDR},
+	{0xdd, 0x00, 0x0200},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xaa, 0x01, 0x0001},
+	{0xaa, 0x06, 0x0000},
+	{0xaa, 0x08, 0x0483},
+	{0xaa, 0x01, 0x0004},
+	{0xaa, 0x08, 0x0006},
+	{0xaa, 0x02, 0x0011},
+	{0xaa, 0x03, 0x01e5},			/*jfm: was 01e7*/
+	{0xaa, 0x04, 0x0285},			/*jfm: was 0287*/
+	{0xaa, 0x07, 0x3002},
+	{0xaa, 0x20, 0x5100},
+	{0xaa, 0x35, 0x507f},
+	{0xaa, 0x30, 0x0005},
+	{0xaa, 0x31, 0x0000},
+	{0xaa, 0x58, 0x0078},
+	{0xaa, 0x62, 0x0411},
+	{0xaa, 0x2b, 0x007f},
+	{0xaa, 0x2c, 0x007f},			/*jfm: was 0030*/
+	{0xaa, 0x2d, 0x007f},			/*jfm: was 0030*/
+	{0xaa, 0x2e, 0x007f},			/*jfm: was 0030*/
+	{0xa0, 0x10, ZC3XX_R087_EXPTIMEMID},
+	{0xa0, 0xb7, ZC3XX_R101_SENSORCORRECTION},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},
+	{0xa0, 0x09, 0x01ad},			/*jfm: was 00*/
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},
+	{0xa0, 0x60, ZC3XX_R1A8_DIGITALGAIN},
+	{0xa0, 0x6c, ZC3XX_R18D_YTARGET},
+	{0xa0, 0x61, ZC3XX_R116_RGAIN},
+	{0xa0, 0x65, ZC3XX_R118_BGAIN},
+	{}
+};
+static const struct usb_action mt9v111_1_InitialScale[] = {	/* 320x240 */
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},
+	{0xa0, 0x0a, ZC3XX_R010_CMOSSENSORSELECT},
+	{0xa0, 0x10, ZC3XX_R002_CLOCKSELECT},
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},
+	{0xa0, 0xdc, ZC3XX_R08B_I2CDEVICEADDR},
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},
+	{0xa0, 0x07, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW},
+	{0xa0, 0xdc, ZC3XX_R08B_I2CDEVICEADDR},
+	{0xdd, 0x00, 0x0200},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xaa, 0x01, 0x0001},
+	{0xaa, 0x06, 0x0000},
+	{0xaa, 0x08, 0x0483},
+	{0xaa, 0x01, 0x0004},
+	{0xaa, 0x08, 0x0006},
+	{0xaa, 0x02, 0x0011},
+	{0xaa, 0x03, 0x01e7},
+	{0xaa, 0x04, 0x0287},
+	{0xaa, 0x07, 0x3002},
+	{0xaa, 0x20, 0x5100},
+	{0xaa, 0x35, 0x007f},			/*jfm: was 0050*/
+	{0xaa, 0x30, 0x0005},
+	{0xaa, 0x31, 0x0000},
+	{0xaa, 0x58, 0x0078},
+	{0xaa, 0x62, 0x0411},
+	{0xaa, 0x2b, 0x007f},			/*jfm: was 28*/
+	{0xaa, 0x2c, 0x007f},			/*jfm: was 30*/
+	{0xaa, 0x2d, 0x007f},			/*jfm: was 30*/
+	{0xaa, 0x2e, 0x007f},			/*jfm: was 28*/
+	{0xa0, 0x10, ZC3XX_R087_EXPTIMEMID},
+	{0xa0, 0xb7, ZC3XX_R101_SENSORCORRECTION},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},
+	{0xa0, 0x09, 0x01ad},			/*jfm: was 00*/
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},
+	{0xa0, 0x60, ZC3XX_R1A8_DIGITALGAIN},
+	{0xa0, 0x6c, ZC3XX_R18D_YTARGET},
+	{0xa0, 0x61, ZC3XX_R116_RGAIN},
+	{0xa0, 0x65, ZC3XX_R118_BGAIN},
+	{}
+};
+static const struct usb_action mt9v111_1_AE50HZ[] = {
+	{0xa0, 0x00, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xbb, 0x00, 0x0562},
+	{0xbb, 0x01, 0x09aa},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x03, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x9b, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x47, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x1c, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x14, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x66, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x62, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0x90, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0xc8, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},
+	{0xa0, 0x60, ZC3XX_R11D_GLOBALGAIN},
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+	{}
+};
+static const struct usb_action mt9v111_1_AE50HZScale[] = {
+	{0xa0, 0x00, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xbb, 0x00, 0x0509},
+	{0xbb, 0x01, 0x0934},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x07, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0xd2, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x9a, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x1c, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x14, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x66, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0xd7, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0xf4, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0xf9, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+	{}
+};
+static const struct usb_action mt9v111_1_AE60HZ[] = {
+	{0xa0, 0x00, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xaa, 0x05, 0x003d},
+	{0xaa, 0x09, 0x016e},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x07, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0xdd, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x3d, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x1c, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x14, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x62, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0x90, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0xc8, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},
+	{0xa0, 0x60, ZC3XX_R11D_GLOBALGAIN},
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+	{}
+};
+static const struct usb_action mt9v111_1_AE60HZScale[] = {
+	{0xa0, 0x00, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xbb, 0x00, 0x0509},
+	{0xbb, 0x01, 0x0983},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x07, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x8f, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x81, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x1c, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x14, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0xd7, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0xf4, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0xf9, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+	{}
+};
+static const struct usb_action mt9v111_1_AENoFliker[] = {
+	{0xa0, 0x00, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xbb, 0x00, 0x0509},
+	{0xbb, 0x01, 0x0960},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x07, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0xf0, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x04, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x1c, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x00, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x00, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x09, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0x40, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0x90, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0xe0, ZC3XX_R020_HSYNC_3},
+	{0xa0, 0x60, ZC3XX_R11D_GLOBALGAIN},
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+	{}
+};
+static const struct usb_action mt9v111_1_AENoFlikerScale[] = {
+	{0xa0, 0x00, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xbb, 0x00, 0x0534},
+	{0xbb, 0x02, 0x0960},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x07, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0xf0, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x04, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x1c, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x00, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x00, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x34, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0x60, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0x90, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0xe0, ZC3XX_R020_HSYNC_3},
+	{0xa0, 0x60, ZC3XX_R11D_GLOBALGAIN},
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+	{}
+};
+/* from usbvm303.inf 0ac8:303b 07/03/25 (3 - tas5130c) */
+static const struct usb_action mt9v111_3_Initial[] = {
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},
+	{0xa0, 0x0a, ZC3XX_R010_CMOSSENSORSELECT},
+	{0xa0, 0x00, ZC3XX_R002_CLOCKSELECT},
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},
+	{0xa0, 0xdc, ZC3XX_R08B_I2CDEVICEADDR},
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},
+	{0xa0, 0x07, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW},
+	{0xa0, 0xdc, ZC3XX_R08B_I2CDEVICEADDR},
+	{0xdd, 0x00, 0x0200},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xaa, 0x01, 0x0001},		/* select IFP/SOC registers */
+	{0xaa, 0x06, 0x0000},		/* operating mode control */
+	{0xaa, 0x08, 0x0483},		/* output format control */
+					/* H red first, V red or blue first,
+					 * raw Bayer, auto flicker */
+	{0xaa, 0x01, 0x0004},		/* select sensor core registers */
+	{0xaa, 0x08, 0x0006},		/* row start */
+	{0xaa, 0x02, 0x0011},		/* column start */
+	{0xaa, 0x03, 0x01e5},		/* window height - 1 */
+	{0xaa, 0x04, 0x0285},		/* window width - 1 */
+	{0xaa, 0x07, 0x3002},		/* output control */
+	{0xaa, 0x20, 0x1100},		/* read mode: bits 8 & 12 (?) */
+	{0xaa, 0x35, 0x007f},		/* global gain */
+	{0xaa, 0x30, 0x0005},
+	{0xaa, 0x31, 0x0000},
+	{0xaa, 0x58, 0x0078},
+	{0xaa, 0x62, 0x0411},
+	{0xaa, 0x2b, 0x007f},		/* green1 gain */
+	{0xaa, 0x2c, 0x007f},		/* blue gain */
+	{0xaa, 0x2d, 0x007f},		/* red gain */
+	{0xaa, 0x2e, 0x007f},		/* green2 gain */
+	{0xa0, 0x10, ZC3XX_R087_EXPTIMEMID},
+	{0xa0, 0x37, ZC3XX_R101_SENSORCORRECTION},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},
+	{0xa0, 0x00, 0x01ad},
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},
+	{0xa0, 0x60, ZC3XX_R1A8_DIGITALGAIN},
+	{0xa0, 0x80, ZC3XX_R18D_YTARGET},
+	{0xa0, 0x61, ZC3XX_R116_RGAIN},
+	{0xa0, 0x65, ZC3XX_R118_BGAIN},
+	{}
+};
+static const struct usb_action mt9v111_3_InitialScale[] = {
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},
+	{0xa0, 0x0a, ZC3XX_R010_CMOSSENSORSELECT},
+	{0xa0, 0x10, ZC3XX_R002_CLOCKSELECT},
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},
+	{0xa0, 0xdc, ZC3XX_R08B_I2CDEVICEADDR},
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},
+	{0xa0, 0x07, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW},
+	{0xa0, 0xdc, ZC3XX_R08B_I2CDEVICEADDR},
+	{0xdd, 0x00, 0x0200},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xaa, 0x01, 0x0001},
+	{0xaa, 0x06, 0x0000},
+	{0xaa, 0x08, 0x0483},
+	{0xaa, 0x01, 0x0004},
+	{0xaa, 0x08, 0x0006},
+	{0xaa, 0x02, 0x0011},
+	{0xaa, 0x03, 0x01e7},
+	{0xaa, 0x04, 0x0287},
+	{0xaa, 0x07, 0x3002},
+	{0xaa, 0x20, 0x1100},
+	{0xaa, 0x35, 0x007f},
+	{0xaa, 0x30, 0x0005},
+	{0xaa, 0x31, 0x0000},
+	{0xaa, 0x58, 0x0078},
+	{0xaa, 0x62, 0x0411},
+	{0xaa, 0x2b, 0x007f},
+	{0xaa, 0x2c, 0x007f},
+	{0xaa, 0x2d, 0x007f},
+	{0xaa, 0x2e, 0x007f},
+	{0xa0, 0x10, ZC3XX_R087_EXPTIMEMID},
+	{0xa0, 0x37, ZC3XX_R101_SENSORCORRECTION},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},
+	{0xa0, 0x00, 0x01ad},
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},
+	{0xa0, 0x60, ZC3XX_R1A8_DIGITALGAIN},
+	{0xa0, 0x80, ZC3XX_R18D_YTARGET},
+	{0xa0, 0x61, ZC3XX_R116_RGAIN},
+	{0xa0, 0x65, ZC3XX_R118_BGAIN},
+	{}
+};
+static const struct usb_action mt9v111_3_AE50HZ[] = {
+	{0xa0, 0x00, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xaa, 0x05, 0x0009},		/* horizontal blanking */
+	{0xaa, 0x09, 0x01ce},		/* shutter width */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x07, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0xd2, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x9a, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x1c, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x14, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0xd7, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0xf4, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0xf9, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+	{}
+};
+static const struct usb_action mt9v111_3_AE50HZScale[] = {
+	{0xa0, 0x00, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xaa, 0x05, 0x0009},
+	{0xaa, 0x09, 0x01ce},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x07, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0xd2, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x9a, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x1c, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x14, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0xd7, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0xf4, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0xf9, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+	{}
+};
+static const struct usb_action mt9v111_3_AE60HZ[] = {
+	{0xa0, 0x00, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xaa, 0x05, 0x0009},
+	{0xaa, 0x09, 0x0083},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x07, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x8f, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x81, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x1c, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x14, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0xd7, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0xf4, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0xf9, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+	{}
+};
+static const struct usb_action mt9v111_3_AE60HZScale[] = {
+	{0xa0, 0x00, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xaa, 0x05, 0x0009},
+	{0xaa, 0x09, 0x0083},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x07, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x8f, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x81, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x1c, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x14, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0xd7, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0xf4, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0xf9, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+	{}
+};
+static const struct usb_action mt9v111_3_AENoFliker[] = {
+	{0xa0, 0x00, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xaa, 0x05, 0x0034},
+	{0xaa, 0x09, 0x0260},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x07, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0xf0, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x04, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x1c, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x00, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x00, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x34, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0x60, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0x90, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0xe0, ZC3XX_R020_HSYNC_3},
+	{0xa0, 0x60, ZC3XX_R11D_GLOBALGAIN},
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+	{}
+};
+static const struct usb_action mt9v111_3_AENoFlikerScale[] = {
+	{0xa0, 0x00, ZC3XX_R180_AUTOCORRECTENABLE},
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xaa, 0x05, 0x0034},
+	{0xaa, 0x09, 0x0260},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x07, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0xf0, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x04, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x1c, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x00, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x00, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x34, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0x60, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0x90, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0xe0, ZC3XX_R020_HSYNC_3},
+	{0xa0, 0x60, ZC3XX_R11D_GLOBALGAIN},
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},
+	{}
+};
+
+static const struct usb_action pb0330_Initial[] = {	/* 640x480 */
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},	/* 00 */
+	{0xa0, 0x0a, ZC3XX_R010_CMOSSENSORSELECT},
+	{0xa0, 0x00, ZC3XX_R002_CLOCKSELECT},
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x07, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW},
+	{0xdd, 0x00, 0x0200},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xaa, 0x01, 0x0006},
+	{0xaa, 0x02, 0x0011},
+	{0xaa, 0x03, 0x01e5},			/*jfm: was 1e7*/
+	{0xaa, 0x04, 0x0285},			/*jfm: was 0287*/
+	{0xaa, 0x06, 0x0003},
+	{0xaa, 0x07, 0x3002},
+	{0xaa, 0x20, 0x1100},
+	{0xaa, 0x2f, 0xf7b0},
+	{0xaa, 0x30, 0x0005},
+	{0xaa, 0x31, 0x0000},
+	{0xaa, 0x34, 0x0100},
+	{0xaa, 0x35, 0x0060},
+	{0xaa, 0x3d, 0x068f},
+	{0xaa, 0x40, 0x01e0},
+	{0xaa, 0x58, 0x0078},
+	{0xaa, 0x62, 0x0411},
+	{0xa0, 0x10, ZC3XX_R087_EXPTIMEMID},
+	{0xa0, 0x37, ZC3XX_R101_SENSORCORRECTION},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},
+	{0xa0, 0x09, 0x01ad},			/*jfm: was 00 */
+	{0xa0, 0x15, 0x01ae},
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},
+	{0xa0, 0x60, ZC3XX_R1A8_DIGITALGAIN},
+	{0xa0, 0x78, ZC3XX_R18D_YTARGET},	/*jfm: was 6c*/
+	{}
+};
+static const struct usb_action pb0330_InitialScale[] = {	/* 320x240 */
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},	/* 00 */
+	{0xa0, 0x0a, ZC3XX_R010_CMOSSENSORSELECT},
+	{0xa0, 0x10, ZC3XX_R002_CLOCKSELECT},
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x07, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW},
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW},
+	{0xdd, 0x00, 0x0200},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xaa, 0x01, 0x0006},
+	{0xaa, 0x02, 0x0011},
+	{0xaa, 0x03, 0x01e7},
+	{0xaa, 0x04, 0x0287},
+	{0xaa, 0x06, 0x0003},
+	{0xaa, 0x07, 0x3002},
+	{0xaa, 0x20, 0x1100},
+	{0xaa, 0x2f, 0xf7b0},
+	{0xaa, 0x30, 0x0005},
+	{0xaa, 0x31, 0x0000},
+	{0xaa, 0x34, 0x0100},
+	{0xaa, 0x35, 0x0060},
+	{0xaa, 0x3d, 0x068f},
+	{0xaa, 0x40, 0x01e0},
+	{0xaa, 0x58, 0x0078},
+	{0xaa, 0x62, 0x0411},
+	{0xa0, 0x10, ZC3XX_R087_EXPTIMEMID},
+	{0xa0, 0x37, ZC3XX_R101_SENSORCORRECTION},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},
+	{0xa0, 0x09, 0x01ad},
+	{0xa0, 0x15, 0x01ae},
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},
+	{0xa0, 0x60, ZC3XX_R1A8_DIGITALGAIN},
+	{0xa0, 0x78, ZC3XX_R18D_YTARGET},	/*jfm: was 6c*/
+	{}
+};
+static const struct usb_action pb0330_50HZ[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xbb, 0x00, 0x055c},
+	{0xbb, 0x01, 0x09aa},
+	{0xbb, 0x00, 0x1001},
+	{0xa0, 0x60, ZC3XX_R11D_GLOBALGAIN},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x07, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0xc4, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x47, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x1a, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x14, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x66, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x5c, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0x90, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0xc8, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},
+	{}
+};
+static const struct usb_action pb0330_50HZScale[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xbb, 0x00, 0x0566},
+	{0xbb, 0x02, 0x09b2},
+	{0xbb, 0x00, 0x1002},
+	{0xa0, 0x60, ZC3XX_R11D_GLOBALGAIN},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x07, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x8c, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x8a, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x1a, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x14, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x66, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0xd7, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0xf0, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0xf8, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},
+	{}
+};
+static const struct usb_action pb0330_60HZ[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xbb, 0x00, 0x0535},
+	{0xbb, 0x01, 0x0974},
+	{0xbb, 0x00, 0x1001},
+	{0xa0, 0x60, ZC3XX_R11D_GLOBALGAIN},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x07, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0xfe, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x3e, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x1a, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x14, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x66, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x35, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0x50, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0x90, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0xd0, ZC3XX_R020_HSYNC_3},
+	{}
+};
+static const struct usb_action pb0330_60HZScale[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xbb, 0x00, 0x0535},
+	{0xbb, 0x02, 0x096c},
+	{0xbb, 0x00, 0x1002},
+	{0xa0, 0x60, ZC3XX_R11D_GLOBALGAIN},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x07, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0xc0, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x7c, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x1a, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x14, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x66, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x35, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0x50, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0x90, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0xd0, ZC3XX_R020_HSYNC_3},
+	{}
+};
+static const struct usb_action pb0330_NoFliker[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xbb, 0x00, 0x0509},
+	{0xbb, 0x02, 0x0940},
+	{0xbb, 0x00, 0x1002},
+	{0xa0, 0x60, ZC3XX_R11D_GLOBALGAIN},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x07, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0xf0, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x01, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x00, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x00, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x09, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0x40, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0x90, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0xe0, ZC3XX_R020_HSYNC_3},
+	{}
+};
+static const struct usb_action pb0330_NoFlikerScale[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS},
+	{0xbb, 0x00, 0x0535},
+	{0xbb, 0x01, 0x0980},
+	{0xbb, 0x00, 0x1001},
+	{0xa0, 0x60, ZC3XX_R11D_GLOBALGAIN},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},
+	{0xa0, 0x07, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0xf0, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x01, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x10, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x20, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x00, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x00, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x35, ZC3XX_R01D_HSYNC_0},
+	{0xa0, 0x60, ZC3XX_R01E_HSYNC_1},
+	{0xa0, 0x90, ZC3XX_R01F_HSYNC_2},
+	{0xa0, 0xe0, ZC3XX_R020_HSYNC_3},
+	{}
+};
+
+/* from oem9.inf */
+static const struct usb_action po2030_Initial[] = {	/* 640x480 */
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL}, /* 00,00,01,cc */
+	{0xa0, 0x04, ZC3XX_R002_CLOCKSELECT},	/* 00,02,04,cc */
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT}, /* 00,10,01,cc */
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING}, /* 00,01,01,cc */
+	{0xa0, 0x04, ZC3XX_R080_HBLANKHIGH}, /* 00,80,04,cc */
+	{0xa0, 0x05, ZC3XX_R081_HBLANKLOW}, /* 00,81,05,cc */
+	{0xa0, 0x16, ZC3XX_R083_RGAINADDR}, /* 00,83,16,cc */
+	{0xa0, 0x18, ZC3XX_R085_BGAINADDR}, /* 00,85,18,cc */
+	{0xa0, 0x1a, ZC3XX_R086_EXPTIMEHIGH}, /* 00,86,1a,cc */
+	{0xa0, 0x1b, ZC3XX_R087_EXPTIMEMID}, /* 00,87,1b,cc */
+	{0xa0, 0x1c, ZC3XX_R088_EXPTIMELOW}, /* 00,88,1c,cc */
+	{0xa0, 0xee, ZC3XX_R08B_I2CDEVICEADDR}, /* 00,8b,ee,cc */
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING}, /* 00,08,03,cc */
+	{0xa0, 0x03, ZC3XX_R012_VIDEOCONTROLFUNC}, /* 00,12,03,cc */
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC}, /* 00,12,01,cc */
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH}, /* 00,03,02,cc */
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW}, /* 00,04,80,cc */
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH}, /* 00,05,01,cc */
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW}, /* 00,06,e0,cc */
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE}, /* 01,80,42,cc */
+	{0xaa, 0x8d, 0x0008},			/* 00,8d,08,aa */
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},	/* 00,98,00,cc */
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW},	/* 00,9a,00,cc */
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},	/* 01,1a,00,cc */
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW},	/* 01,1c,00,cc */
+	{0xa0, 0xe6, ZC3XX_R09C_WINHEIGHTLOW},	/* 00,9c,e6,cc */
+	{0xa0, 0x86, ZC3XX_R09E_WINWIDTHLOW},	/* 00,9e,86,cc */
+	{0xaa, 0x09, 0x00ce}, /* 00,09,ce,aa */
+	{0xaa, 0x0b, 0x0005}, /* 00,0b,05,aa */
+	{0xaa, 0x0d, 0x0054}, /* 00,0d,54,aa */
+	{0xaa, 0x0f, 0x00eb}, /* 00,0f,eb,aa */
+	{0xaa, 0x87, 0x0000}, /* 00,87,00,aa */
+	{0xaa, 0x88, 0x0004}, /* 00,88,04,aa */
+	{0xaa, 0x89, 0x0000}, /* 00,89,00,aa */
+	{0xaa, 0x8a, 0x0005}, /* 00,8a,05,aa */
+	{0xaa, 0x13, 0x0003}, /* 00,13,03,aa */
+	{0xaa, 0x16, 0x0040}, /* 00,16,40,aa */
+	{0xaa, 0x18, 0x0040}, /* 00,18,40,aa */
+	{0xaa, 0x1d, 0x0002}, /* 00,1d,02,aa */
+	{0xaa, 0x29, 0x00e8}, /* 00,29,e8,aa */
+	{0xaa, 0x45, 0x0045}, /* 00,45,45,aa */
+	{0xaa, 0x50, 0x00ed}, /* 00,50,ed,aa */
+	{0xaa, 0x51, 0x0025}, /* 00,51,25,aa */
+	{0xaa, 0x52, 0x0042}, /* 00,52,42,aa */
+	{0xaa, 0x53, 0x002f}, /* 00,53,2f,aa */
+	{0xaa, 0x79, 0x0025}, /* 00,79,25,aa */
+	{0xaa, 0x7b, 0x0000}, /* 00,7b,00,aa */
+	{0xaa, 0x7e, 0x0025}, /* 00,7e,25,aa */
+	{0xaa, 0x7f, 0x0025}, /* 00,7f,25,aa */
+	{0xaa, 0x21, 0x0000}, /* 00,21,00,aa */
+	{0xaa, 0x33, 0x0036}, /* 00,33,36,aa */
+	{0xaa, 0x36, 0x0060}, /* 00,36,60,aa */
+	{0xaa, 0x37, 0x0008}, /* 00,37,08,aa */
+	{0xaa, 0x3b, 0x0031}, /* 00,3b,31,aa */
+	{0xaa, 0x44, 0x000f}, /* 00,44,0f,aa */
+	{0xaa, 0x58, 0x0002}, /* 00,58,02,aa */
+	{0xaa, 0x66, 0x00c0}, /* 00,66,c0,aa */
+	{0xaa, 0x67, 0x0044}, /* 00,67,44,aa */
+	{0xaa, 0x6b, 0x00a0}, /* 00,6b,a0,aa */
+	{0xaa, 0x6c, 0x0054}, /* 00,6c,54,aa */
+	{0xaa, 0xd6, 0x0007}, /* 00,d6,07,aa */
+	{0xa0, 0xf7, ZC3XX_R101_SENSORCORRECTION}, /* 01,01,f7,cc */
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC}, /* 00,12,05,cc */
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE}, /* 01,00,0d,cc */
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS}, /* 01,89,06,cc */
+	{0xa0, 0x00, 0x01ad}, /* 01,ad,00,cc */
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE}, /* 01,c5,03,cc */
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05}, /* 01,cb,13,cc */
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE}, /* 02,50,08,cc */
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS}, /* 03,01,08,cc */
+	{0xa0, 0x7a, ZC3XX_R116_RGAIN}, /* 01,16,7a,cc */
+	{0xa0, 0x4a, ZC3XX_R118_BGAIN}, /* 01,18,4a,cc */
+	{}
+};
+
+/* from oem9.inf */
+static const struct usb_action po2030_InitialScale[] = {	/* 320x240 */
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL}, /* 00,00,01,cc */
+	{0xa0, 0x10, ZC3XX_R002_CLOCKSELECT}, /* 00,02,10,cc */
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT}, /* 00,10,01,cc */
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING}, /* 00,01,01,cc */
+	{0xa0, 0x04, ZC3XX_R080_HBLANKHIGH}, /* 00,80,04,cc */
+	{0xa0, 0x05, ZC3XX_R081_HBLANKLOW}, /* 00,81,05,cc */
+	{0xa0, 0x16, ZC3XX_R083_RGAINADDR}, /* 00,83,16,cc */
+	{0xa0, 0x18, ZC3XX_R085_BGAINADDR}, /* 00,85,18,cc */
+	{0xa0, 0x1a, ZC3XX_R086_EXPTIMEHIGH}, /* 00,86,1a,cc */
+	{0xa0, 0x1b, ZC3XX_R087_EXPTIMEMID}, /* 00,87,1b,cc */
+	{0xa0, 0x1c, ZC3XX_R088_EXPTIMELOW}, /* 00,88,1c,cc */
+	{0xa0, 0xee, ZC3XX_R08B_I2CDEVICEADDR}, /* 00,8b,ee,cc */
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING}, /* 00,08,03,cc */
+	{0xa0, 0x03, ZC3XX_R012_VIDEOCONTROLFUNC}, /* 00,12,03,cc */
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC}, /* 00,12,01,cc */
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH}, /* 00,03,02,cc */
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW}, /* 00,04,80,cc */
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH}, /* 00,05,01,cc */
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW}, /* 00,06,e0,cc */
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE}, /* 01,80,42,cc */
+	{0xaa, 0x8d, 0x0008},			/* 00,8d,08,aa */
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW}, /* 00,98,00,cc */
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW}, /* 00,9a,00,cc */
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW}, /* 01,1a,00,cc */
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW}, /* 01,1c,00,cc */
+	{0xa0, 0xe8, ZC3XX_R09C_WINHEIGHTLOW}, /* 00,9c,e8,cc */
+	{0xa0, 0x88, ZC3XX_R09E_WINWIDTHLOW}, /* 00,9e,88,cc */
+	{0xaa, 0x09, 0x00cc}, /* 00,09,cc,aa */
+	{0xaa, 0x0b, 0x0005}, /* 00,0b,05,aa */
+	{0xaa, 0x0d, 0x0058}, /* 00,0d,58,aa */
+	{0xaa, 0x0f, 0x00ed}, /* 00,0f,ed,aa */
+	{0xaa, 0x87, 0x0000}, /* 00,87,00,aa */
+	{0xaa, 0x88, 0x0004}, /* 00,88,04,aa */
+	{0xaa, 0x89, 0x0000}, /* 00,89,00,aa */
+	{0xaa, 0x8a, 0x0005}, /* 00,8a,05,aa */
+	{0xaa, 0x13, 0x0003}, /* 00,13,03,aa */
+	{0xaa, 0x16, 0x0040}, /* 00,16,40,aa */
+	{0xaa, 0x18, 0x0040}, /* 00,18,40,aa */
+	{0xaa, 0x1d, 0x0002}, /* 00,1d,02,aa */
+	{0xaa, 0x29, 0x00e8}, /* 00,29,e8,aa */
+	{0xaa, 0x45, 0x0045}, /* 00,45,45,aa */
+	{0xaa, 0x50, 0x00ed}, /* 00,50,ed,aa */
+	{0xaa, 0x51, 0x0025}, /* 00,51,25,aa */
+	{0xaa, 0x52, 0x0042}, /* 00,52,42,aa */
+	{0xaa, 0x53, 0x002f}, /* 00,53,2f,aa */
+	{0xaa, 0x79, 0x0025}, /* 00,79,25,aa */
+	{0xaa, 0x7b, 0x0000}, /* 00,7b,00,aa */
+	{0xaa, 0x7e, 0x0025}, /* 00,7e,25,aa */
+	{0xaa, 0x7f, 0x0025}, /* 00,7f,25,aa */
+	{0xaa, 0x21, 0x0000}, /* 00,21,00,aa */
+	{0xaa, 0x33, 0x0036}, /* 00,33,36,aa */
+	{0xaa, 0x36, 0x0060}, /* 00,36,60,aa */
+	{0xaa, 0x37, 0x0008}, /* 00,37,08,aa */
+	{0xaa, 0x3b, 0x0031}, /* 00,3b,31,aa */
+	{0xaa, 0x44, 0x000f}, /* 00,44,0f,aa */
+	{0xaa, 0x58, 0x0002}, /* 00,58,02,aa */
+	{0xaa, 0x66, 0x00c0}, /* 00,66,c0,aa */
+	{0xaa, 0x67, 0x0044}, /* 00,67,44,aa */
+	{0xaa, 0x6b, 0x00a0}, /* 00,6b,a0,aa */
+	{0xaa, 0x6c, 0x0054}, /* 00,6c,54,aa */
+	{0xaa, 0xd6, 0x0007}, /* 00,d6,07,aa */
+	{0xa0, 0xf7, ZC3XX_R101_SENSORCORRECTION}, /* 01,01,f7,cc */
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC}, /* 00,12,05,cc */
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE}, /* 01,00,0d,cc */
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS}, /* 01,89,06,cc */
+	{0xa0, 0x00, 0x01ad}, /* 01,ad,00,cc */
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE}, /* 01,c5,03,cc */
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05}, /* 01,cb,13,cc */
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE}, /* 02,50,08,cc */
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS}, /* 03,01,08,cc */
+	{0xa0, 0x7a, ZC3XX_R116_RGAIN}, /* 01,16,7a,cc */
+	{0xa0, 0x4a, ZC3XX_R118_BGAIN}, /* 01,18,4a,cc */
+	{}
+};
+
+static const struct usb_action po2030_50HZ[] = {
+	{0xaa, 0x8d, 0x0008}, /* 00,8d,08,aa */
+	{0xaa, 0x1a, 0x0001}, /* 00,1a,01,aa */
+	{0xaa, 0x1b, 0x000a}, /* 00,1b,0a,aa */
+	{0xaa, 0x1c, 0x00b0}, /* 00,1c,b0,aa */
+	{0xa0, 0x05, ZC3XX_R190_EXPOSURELIMITHIGH}, /* 01,90,05,cc */
+	{0xa0, 0x35, ZC3XX_R191_EXPOSURELIMITMID}, /* 01,91,35,cc */
+	{0xa0, 0x70, ZC3XX_R192_EXPOSURELIMITLOW}, /* 01,92,70,cc */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH}, /* 01,95,00,cc */
+	{0xa0, 0x85, ZC3XX_R196_ANTIFLICKERMID}, /* 01,96,85,cc */
+	{0xa0, 0x58, ZC3XX_R197_ANTIFLICKERLOW}, /* 01,97,58,cc */
+	{0xa0, 0x0c, ZC3XX_R18C_AEFREEZE}, /* 01,8c,0c,cc */
+	{0xa0, 0x18, ZC3XX_R18F_AEUNFREEZE}, /* 01,8f,18,cc */
+	{0xa0, 0x60, ZC3XX_R1A8_DIGITALGAIN}, /* 01,a8,60,cc */
+	{0xa0, 0x10, ZC3XX_R1A9_DIGITALLIMITDIFF}, /* 01,a9,10,cc */
+	{0xa0, 0x22, ZC3XX_R1AA_DIGITALGAINSTEP}, /* 01,aa,22,cc */
+	{0xa0, 0x88, ZC3XX_R18D_YTARGET}, /* 01,8d,88,cc */
+	{0xa0, 0x58, ZC3XX_R11D_GLOBALGAIN}, /* 01,1d,58,cc */
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE}, /* 01,80,42,cc */
+	{}
+};
+
+static const struct usb_action po2030_60HZ[] = {
+	{0xaa, 0x8d, 0x0008}, /* 00,8d,08,aa */
+	{0xaa, 0x1a, 0x0000}, /* 00,1a,00,aa */
+	{0xaa, 0x1b, 0x00de}, /* 00,1b,de,aa */
+	{0xaa, 0x1c, 0x0040}, /* 00,1c,40,aa */
+	{0xa0, 0x08, ZC3XX_R190_EXPOSURELIMITHIGH}, /* 01,90,08,cc */
+	{0xa0, 0xae, ZC3XX_R191_EXPOSURELIMITMID}, /* 01,91,ae,cc */
+	{0xa0, 0x80, ZC3XX_R192_EXPOSURELIMITLOW}, /* 01,92,80,cc */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH}, /* 01,95,00,cc */
+	{0xa0, 0x6f, ZC3XX_R196_ANTIFLICKERMID}, /* 01,96,6f,cc */
+	{0xa0, 0x20, ZC3XX_R197_ANTIFLICKERLOW}, /* 01,97,20,cc */
+	{0xa0, 0x0c, ZC3XX_R18C_AEFREEZE}, /* 01,8c,0c,cc */
+	{0xa0, 0x18, ZC3XX_R18F_AEUNFREEZE}, /* 01,8f,18,cc */
+	{0xa0, 0x60, ZC3XX_R1A8_DIGITALGAIN}, /* 01,a8,60,cc */
+	{0xa0, 0x10, ZC3XX_R1A9_DIGITALLIMITDIFF}, /* 01,a9,10,cc */
+	{0xa0, 0x22, ZC3XX_R1AA_DIGITALGAINSTEP},	/* 01,aa,22,cc */
+	{0xa0, 0x88, ZC3XX_R18D_YTARGET},		/* 01,8d,88,cc */
+							/* win: 01,8d,80 */
+	{0xa0, 0x58, ZC3XX_R11D_GLOBALGAIN},		/* 01,1d,58,cc */
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},	/* 01,80,42,cc */
+	{}
+};
+
+static const struct usb_action po2030_NoFliker[] = {
+	{0xa0, 0x02, ZC3XX_R180_AUTOCORRECTENABLE}, /* 01,80,02,cc */
+	{0xaa, 0x8d, 0x000d}, /* 00,8d,0d,aa */
+	{0xaa, 0x1a, 0x0000}, /* 00,1a,00,aa */
+	{0xaa, 0x1b, 0x0002}, /* 00,1b,02,aa */
+	{0xaa, 0x1c, 0x0078}, /* 00,1c,78,aa */
+	{0xaa, 0x46, 0x0000}, /* 00,46,00,aa */
+	{0xaa, 0x15, 0x0000}, /* 00,15,00,aa */
+	{}
+};
+
+static const struct usb_action tas5130c_InitialScale[] = {	/* 320x240 */
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},
+	{0xa0, 0x50, ZC3XX_R002_CLOCKSELECT},
+	{0xa0, 0x03, ZC3XX_R008_CLOCKSETTING},
+	{0xa0, 0x02, ZC3XX_R010_CMOSSENSORSELECT},
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},
+	{0xa0, 0x00, ZC3XX_R001_SYSTEMOPERATING},
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},
+
+	{0xa0, 0x04, ZC3XX_R098_WINYSTARTLOW},
+	{0xa0, 0x0f, ZC3XX_R09A_WINXSTARTLOW},
+	{0xa0, 0x04, ZC3XX_R11A_FIRSTYLOW},
+	{0xa0, 0x0f, ZC3XX_R11C_FIRSTXLOW},
+	{0xa0, 0xe8, ZC3XX_R09C_WINHEIGHTLOW},
+	{0xa0, 0x02, ZC3XX_R09D_WINWIDTHHIGH},
+	{0xa0, 0x88, ZC3XX_R09E_WINWIDTHLOW},
+	{0xa0, 0x06, ZC3XX_R08D_COMPABILITYMODE},
+	{0xa0, 0xf7, ZC3XX_R101_SENSORCORRECTION},
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},
+	{0xa0, 0x70, ZC3XX_R18D_YTARGET},
+	{0xa0, 0x50, ZC3XX_R1A8_DIGITALGAIN},
+	{0xa0, 0x00, 0x01ad},
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},
+	{0xa0, 0x07, ZC3XX_R0A5_EXPOSUREGAIN},
+	{0xa0, 0x02, ZC3XX_R0A6_EXPOSUREBLACKLVL},
+	{}
+};
+static const struct usb_action tas5130c_Initial[] = {	/* 640x480 */
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},
+	{0xa0, 0x40, ZC3XX_R002_CLOCKSELECT},
+	{0xa0, 0x00, ZC3XX_R008_CLOCKSETTING},
+	{0xa0, 0x02, ZC3XX_R010_CMOSSENSORSELECT},
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},
+	{0xa0, 0x00, ZC3XX_R001_SYSTEMOPERATING},
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},
+	{0xa0, 0x05, ZC3XX_R098_WINYSTARTLOW},
+	{0xa0, 0x0f, ZC3XX_R09A_WINXSTARTLOW},
+	{0xa0, 0x05, ZC3XX_R11A_FIRSTYLOW},
+	{0xa0, 0x0f, ZC3XX_R11C_FIRSTXLOW},
+	{0xa0, 0xe6, ZC3XX_R09C_WINHEIGHTLOW},
+	{0xa0, 0x02, ZC3XX_R09D_WINWIDTHHIGH},
+	{0xa0, 0x86, ZC3XX_R09E_WINWIDTHLOW},
+	{0xa0, 0x06, ZC3XX_R08D_COMPABILITYMODE},
+	{0xa0, 0x37, ZC3XX_R101_SENSORCORRECTION},
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},
+	{0xa0, 0x06, ZC3XX_R189_AWBSTATUS},
+	{0xa0, 0x70, ZC3XX_R18D_YTARGET},
+	{0xa0, 0x50, ZC3XX_R1A8_DIGITALGAIN},
+	{0xa0, 0x00, 0x01ad},
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},
+	{0xa0, 0x07, ZC3XX_R0A5_EXPOSUREGAIN},
+	{0xa0, 0x02, ZC3XX_R0A6_EXPOSUREBLACKLVL},
+	{}
+};
+static const struct usb_action tas5130c_50HZ[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS}, /* 00,19,00,cc */
+	{0xaa, 0xa3, 0x0001}, /* 00,a3,01,aa */
+	{0xaa, 0xa4, 0x0063}, /* 00,a4,63,aa */
+	{0xa0, 0x01, ZC3XX_R0A3_EXPOSURETIMEHIGH}, /* 00,a3,01,cc */
+	{0xa0, 0x63, ZC3XX_R0A4_EXPOSURETIMELOW}, /* 00,a4,63,cc */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH}, /* 01,90,00,cc */
+	{0xa0, 0x04, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0xfe, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH}, /* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID}, /* 01,96,00,cc */
+	{0xa0, 0x47, ZC3XX_R197_ANTIFLICKERLOW}, /* 01,97,47,cc */
+	{0xa0, 0x0c, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x18, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x08, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0xd3, ZC3XX_R01D_HSYNC_0}, /* 00,1d,d3,cc */
+	{0xa0, 0xda, ZC3XX_R01E_HSYNC_1}, /* 00,1e,da,cc */
+	{0xa0, 0xea, ZC3XX_R01F_HSYNC_2}, /* 00,1f,ea,cc */
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3}, /* 00,20,ff,cc */
+	{0xa0, 0x03, ZC3XX_R09F_MAXXHIGH}, /* 00,9f,03,cc */
+	{0xa0, 0x4c, ZC3XX_R0A0_MAXXLOW},
+	{0xa0, 0x50, ZC3XX_R11D_GLOBALGAIN},
+	{}
+};
+static const struct usb_action tas5130c_50HZScale[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS}, /* 00,19,00,cc */
+	{0xaa, 0xa3, 0x0001}, /* 00,a3,01,aa */
+	{0xaa, 0xa4, 0x0077}, /* 00,a4,77,aa */
+	{0xa0, 0x01, ZC3XX_R0A3_EXPOSURETIMEHIGH}, /* 00,a3,01,cc */
+	{0xa0, 0x77, ZC3XX_R0A4_EXPOSURETIMELOW}, /* 00,a4,77,cc */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH}, /* 01,90,00,cc */
+	{0xa0, 0x07, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0xd0, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH}, /* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID}, /* 01,96,00,cc */
+	{0xa0, 0x7d, ZC3XX_R197_ANTIFLICKERLOW}, /* 01,97,7d,cc */
+	{0xa0, 0x0c, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x18, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x08, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0xf0, ZC3XX_R01D_HSYNC_0}, /* 00,1d,f0,cc */
+	{0xa0, 0xf4, ZC3XX_R01E_HSYNC_1}, /* 00,1e,f4,cc */
+	{0xa0, 0xf8, ZC3XX_R01F_HSYNC_2}, /* 00,1f,f8,cc */
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3}, /* 00,20,ff,cc */
+	{0xa0, 0x03, ZC3XX_R09F_MAXXHIGH}, /* 00,9f,03,cc */
+	{0xa0, 0xc0, ZC3XX_R0A0_MAXXLOW},
+	{0xa0, 0x50, ZC3XX_R11D_GLOBALGAIN},
+	{}
+};
+static const struct usb_action tas5130c_60HZ[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS}, /* 00,19,00,cc */
+	{0xaa, 0xa3, 0x0001}, /* 00,a3,01,aa */
+	{0xaa, 0xa4, 0x0036}, /* 00,a4,36,aa */
+	{0xa0, 0x01, ZC3XX_R0A3_EXPOSURETIMEHIGH}, /* 00,a3,01,cc */
+	{0xa0, 0x36, ZC3XX_R0A4_EXPOSURETIMELOW}, /* 00,a4,36,cc */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH}, /* 01,90,00,cc */
+	{0xa0, 0x05, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x54, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH}, /* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID}, /* 01,96,00,cc */
+	{0xa0, 0x3e, ZC3XX_R197_ANTIFLICKERLOW}, /* 01,97,3e,cc */
+	{0xa0, 0x0c, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x18, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x08, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0xca, ZC3XX_R01D_HSYNC_0}, /* 00,1d,ca,cc */
+	{0xa0, 0xd0, ZC3XX_R01E_HSYNC_1}, /* 00,1e,d0,cc */
+	{0xa0, 0xe0, ZC3XX_R01F_HSYNC_2}, /* 00,1f,e0,cc */
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3}, /* 00,20,ff,cc */
+	{0xa0, 0x03, ZC3XX_R09F_MAXXHIGH}, /* 00,9f,03,cc */
+	{0xa0, 0x28, ZC3XX_R0A0_MAXXLOW},
+	{0xa0, 0x50, ZC3XX_R11D_GLOBALGAIN},
+	{}
+};
+static const struct usb_action tas5130c_60HZScale[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS}, /* 00,19,00,cc */
+	{0xaa, 0xa3, 0x0001}, /* 00,a3,01,aa */
+	{0xaa, 0xa4, 0x0077}, /* 00,a4,77,aa */
+	{0xa0, 0x01, ZC3XX_R0A3_EXPOSURETIMEHIGH}, /* 00,a3,01,cc */
+	{0xa0, 0x77, ZC3XX_R0A4_EXPOSURETIMELOW}, /* 00,a4,77,cc */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH}, /* 01,90,00,cc */
+	{0xa0, 0x09, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x47, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH}, /* 01,95,00,cc */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID}, /* 01,96,00,cc */
+	{0xa0, 0x7d, ZC3XX_R197_ANTIFLICKERLOW}, /* 01,97,7d,cc */
+	{0xa0, 0x0c, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x18, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x08, ZC3XX_R1A9_DIGITALLIMITDIFF},
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0xc8, ZC3XX_R01D_HSYNC_0}, /* 00,1d,c8,cc */
+	{0xa0, 0xd0, ZC3XX_R01E_HSYNC_1}, /* 00,1e,d0,cc */
+	{0xa0, 0xe0, ZC3XX_R01F_HSYNC_2}, /* 00,1f,e0,cc */
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3}, /* 00,20,ff,cc */
+	{0xa0, 0x03, ZC3XX_R09F_MAXXHIGH}, /* 00,9f,03,cc */
+	{0xa0, 0x20, ZC3XX_R0A0_MAXXLOW},
+	{0xa0, 0x50, ZC3XX_R11D_GLOBALGAIN},
+	{}
+};
+static const struct usb_action tas5130c_NoFliker[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS}, /* 00,19,00,cc */
+	{0xaa, 0xa3, 0x0001}, /* 00,a3,01,aa */
+	{0xaa, 0xa4, 0x0040}, /* 00,a4,40,aa */
+	{0xa0, 0x01, ZC3XX_R0A3_EXPOSURETIMEHIGH}, /* 00,a3,01,cc */
+	{0xa0, 0x40, ZC3XX_R0A4_EXPOSURETIMELOW}, /* 00,a4,40,cc */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH}, /* 01,90,00,cc */
+	{0xa0, 0x05, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0xa0, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x04, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x0c, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x18, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x00, ZC3XX_R1A9_DIGITALLIMITDIFF}, /* 01,a9,00,cc */
+	{0xa0, 0x00, ZC3XX_R1AA_DIGITALGAINSTEP}, /* 01,aa,00,cc */
+	{0xa0, 0xbc, ZC3XX_R01D_HSYNC_0}, /* 00,1d,bc,cc */
+	{0xa0, 0xd0, ZC3XX_R01E_HSYNC_1}, /* 00,1e,d0,cc */
+	{0xa0, 0xe0, ZC3XX_R01F_HSYNC_2}, /* 00,1f,e0,cc */
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3}, /* 00,20,ff,cc */
+	{0xa0, 0x02, ZC3XX_R09F_MAXXHIGH}, /* 00,9f,02,cc */
+	{0xa0, 0xf0, ZC3XX_R0A0_MAXXLOW},
+	{0xa0, 0x50, ZC3XX_R11D_GLOBALGAIN},
+	{}
+};
+
+static const struct usb_action tas5130c_NoFlikerScale[] = {
+	{0xa0, 0x00, ZC3XX_R019_AUTOADJUSTFPS}, /* 00,19,00,cc */
+	{0xaa, 0xa3, 0x0001}, /* 00,a3,01,aa */
+	{0xaa, 0xa4, 0x0090}, /* 00,a4,90,aa */
+	{0xa0, 0x01, ZC3XX_R0A3_EXPOSURETIMEHIGH}, /* 00,a3,01,cc */
+	{0xa0, 0x90, ZC3XX_R0A4_EXPOSURETIMELOW}, /* 00,a4,90,cc */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH}, /* 01,90,00,cc */
+	{0xa0, 0x0a, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x00, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},
+	{0xa0, 0x04, ZC3XX_R197_ANTIFLICKERLOW},
+	{0xa0, 0x0c, ZC3XX_R18C_AEFREEZE},
+	{0xa0, 0x18, ZC3XX_R18F_AEUNFREEZE},
+	{0xa0, 0x00, ZC3XX_R1A9_DIGITALLIMITDIFF}, /* 01,a9,00,cc */
+	{0xa0, 0x00, ZC3XX_R1AA_DIGITALGAINSTEP}, /* 01,aa,00,cc */
+	{0xa0, 0xbc, ZC3XX_R01D_HSYNC_0}, /* 00,1d,bc,cc */
+	{0xa0, 0xd0, ZC3XX_R01E_HSYNC_1}, /* 00,1e,d0,cc */
+	{0xa0, 0xe0, ZC3XX_R01F_HSYNC_2}, /* 00,1f,e0,cc */
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3}, /* 00,20,ff,cc */
+	{0xa0, 0x02, ZC3XX_R09F_MAXXHIGH}, /* 00,9f,02,cc */
+	{0xa0, 0xf0, ZC3XX_R0A0_MAXXLOW},
+	{0xa0, 0x50, ZC3XX_R11D_GLOBALGAIN},
+	{}
+};
+
+/* from usbvm305.inf 0ac8:305b 07/06/15 (3 - tas5130c) */
+static const struct usb_action gc0303_Initial[] = {
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},		/* 00,00,01,cc, */
+	{0xa0, 0x02, ZC3XX_R008_CLOCKSETTING},		/* 00,08,02,cc, */
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,01,cc, */
+	{0xa0, 0x00, ZC3XX_R002_CLOCKSELECT},
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},	/* 00,03,02,cc, */
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},		/* 00,04,80,cc, */
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},	/* 00,05,01,cc, */
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},	/* 00,06,e0,cc, */
+	{0xa0, 0x98, ZC3XX_R08B_I2CDEVICEADDR},		/* 00,8b,98,cc, */
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},	/* 00,01,01,cc, */
+	{0xa0, 0x03, ZC3XX_R012_VIDEOCONTROLFUNC},	/* 00,12,03,cc, */
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC},	/* 00,12,01,cc, */
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},		/* 00,98,00,cc, */
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW},		/* 00,9a,00,cc, */
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},		/* 01,1a,00,cc, */
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW},		/* 01,1c,00,cc, */
+	{0xa0, 0xe8, ZC3XX_R09C_WINHEIGHTLOW},		/* 00,9c,e6,cc,
+							 * 6<->8 */
+	{0xa0, 0x88, ZC3XX_R09E_WINWIDTHLOW},		/* 00,9e,86,cc,
+							 * 6<->8 */
+	{0xa0, 0x10, ZC3XX_R087_EXPTIMEMID},		/* 00,87,10,cc, */
+	{0xa0, 0x98, ZC3XX_R08B_I2CDEVICEADDR},		/* 00,8b,98,cc, */
+	{0xaa, 0x01, 0x0000},
+	{0xaa, 0x1a, 0x0000},		/* 00,1a,00,aa, */
+	{0xaa, 0x1c, 0x0017},		/* 00,1c,17,aa, */
+	{0xaa, 0x1b, 0x0000},
+	{0xa0, 0x82, ZC3XX_R086_EXPTIMEHIGH},		/* 00,86,82,cc, */
+	{0xa0, 0x83, ZC3XX_R087_EXPTIMEMID},		/* 00,87,83,cc, */
+	{0xa0, 0x84, ZC3XX_R088_EXPTIMELOW},		/* 00,88,84,cc, */
+	{0xaa, 0x05, 0x0010},		/* 00,05,10,aa, */
+	{0xaa, 0x0a, 0x0002},
+	{0xaa, 0x0b, 0x0000},
+	{0xaa, 0x0c, 0x0002},
+	{0xaa, 0x0d, 0x0000},
+	{0xaa, 0x0e, 0x0002},
+	{0xaa, 0x0f, 0x0000},
+	{0xaa, 0x10, 0x0002},
+	{0xaa, 0x11, 0x0000},
+	{0xaa, 0x16, 0x0001},		/* 00,16,01,aa, */
+	{0xaa, 0x17, 0x00e8},		/* 00,17,e6,aa, (e6 -> e8) */
+	{0xaa, 0x18, 0x0002},		/* 00,18,02,aa, */
+	{0xaa, 0x19, 0x0088},		/* 00,19,86,aa, */
+	{0xaa, 0x20, 0x0020},		/* 00,20,20,aa, */
+	{0xa0, 0xb7, ZC3XX_R101_SENSORCORRECTION},	/* 01,01,b7,cc, */
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},	/* 00,12,05,cc, */
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},		/* 01,00,0d,cc, */
+	{0xa0, 0x76, ZC3XX_R189_AWBSTATUS},		/* 01,89,76,cc, */
+	{0xa0, 0x09, 0x01ad},				/* 01,ad,09,cc, */
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},		/* 01,c5,03,cc, */
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},		/* 01,cb,13,cc, */
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},	/* 02,50,08,cc, */
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},		/* 03,01,08,cc, */
+	{0xa0, 0x58, ZC3XX_R1A8_DIGITALGAIN},
+	{0xa0, 0x61, ZC3XX_R116_RGAIN},			/* 01,16,61,cc, */
+	{0xa0, 0x65, ZC3XX_R118_BGAIN},			/* 01,18,65,cc */
+	{0xaa, 0x1b, 0x0000},
+	{}
+};
+
+static const struct usb_action gc0303_InitialScale[] = {
+	{0xa0, 0x01, ZC3XX_R000_SYSTEMCONTROL},		/* 00,00,01,cc, */
+	{0xa0, 0x02, ZC3XX_R008_CLOCKSETTING},		/* 00,08,02,cc, */
+	{0xa0, 0x01, ZC3XX_R010_CMOSSENSORSELECT},	/* 00,10,01,cc, */
+	{0xa0, 0x10, ZC3XX_R002_CLOCKSELECT},
+	{0xa0, 0x02, ZC3XX_R003_FRAMEWIDTHHIGH},	/* 00,03,02,cc, */
+	{0xa0, 0x80, ZC3XX_R004_FRAMEWIDTHLOW},		/* 00,04,80,cc, */
+	{0xa0, 0x01, ZC3XX_R005_FRAMEHEIGHTHIGH},	/* 00,05,01,cc, */
+	{0xa0, 0xe0, ZC3XX_R006_FRAMEHEIGHTLOW},	/* 00,06,e0,cc, */
+	{0xa0, 0x98, ZC3XX_R08B_I2CDEVICEADDR},		/* 00,8b,98,cc, */
+	{0xa0, 0x01, ZC3XX_R001_SYSTEMOPERATING},	/* 00,01,01,cc, */
+	{0xa0, 0x03, ZC3XX_R012_VIDEOCONTROLFUNC},	/* 00,12,03,cc, */
+	{0xa0, 0x01, ZC3XX_R012_VIDEOCONTROLFUNC},	/* 00,12,01,cc, */
+	{0xa0, 0x00, ZC3XX_R098_WINYSTARTLOW},		/* 00,98,00,cc, */
+	{0xa0, 0x00, ZC3XX_R09A_WINXSTARTLOW},		/* 00,9a,00,cc, */
+	{0xa0, 0x00, ZC3XX_R11A_FIRSTYLOW},		/* 01,1a,00,cc, */
+	{0xa0, 0x00, ZC3XX_R11C_FIRSTXLOW},		/* 01,1c,00,cc, */
+	{0xa0, 0xe8, ZC3XX_R09C_WINHEIGHTLOW},		/* 00,9c,e8,cc,
+							 * 8<->6 */
+	{0xa0, 0x88, ZC3XX_R09E_WINWIDTHLOW},		/* 00,9e,88,cc,
+							 * 8<->6 */
+	{0xa0, 0x10, ZC3XX_R087_EXPTIMEMID},		/* 00,87,10,cc, */
+	{0xa0, 0x98, ZC3XX_R08B_I2CDEVICEADDR},		/* 00,8b,98,cc, */
+	{0xaa, 0x01, 0x0000},
+	{0xaa, 0x1a, 0x0000},		/* 00,1a,00,aa, */
+	{0xaa, 0x1c, 0x0017},		/* 00,1c,17,aa, */
+	{0xaa, 0x1b, 0x0000},
+	{0xa0, 0x82, ZC3XX_R086_EXPTIMEHIGH},	/* 00,86,82,cc, */
+	{0xa0, 0x83, ZC3XX_R087_EXPTIMEMID},	/* 00,87,83,cc, */
+	{0xa0, 0x84, ZC3XX_R088_EXPTIMELOW},	/* 00,88,84,cc, */
+	{0xaa, 0x05, 0x0010},		/* 00,05,10,aa, */
+	{0xaa, 0x0a, 0x0001},
+	{0xaa, 0x0b, 0x0000},
+	{0xaa, 0x0c, 0x0001},
+	{0xaa, 0x0d, 0x0000},
+	{0xaa, 0x0e, 0x0001},
+	{0xaa, 0x0f, 0x0000},
+	{0xaa, 0x10, 0x0001},
+	{0xaa, 0x11, 0x0000},
+	{0xaa, 0x16, 0x0001},		/* 00,16,01,aa, */
+	{0xaa, 0x17, 0x00e8},		/* 00,17,e6,aa (e6 -> e8) */
+	{0xaa, 0x18, 0x0002},		/* 00,18,02,aa, */
+	{0xaa, 0x19, 0x0088},		/* 00,19,88,aa, */
+	{0xa0, 0xb7, ZC3XX_R101_SENSORCORRECTION},	/* 01,01,b7,cc, */
+	{0xa0, 0x05, ZC3XX_R012_VIDEOCONTROLFUNC},	/* 00,12,05,cc, */
+	{0xa0, 0x0d, ZC3XX_R100_OPERATIONMODE},		/* 01,00,0d,cc, */
+	{0xa0, 0x76, ZC3XX_R189_AWBSTATUS},		/* 01,89,76,cc, */
+	{0xa0, 0x09, 0x01ad},				/* 01,ad,09,cc, */
+	{0xa0, 0x03, ZC3XX_R1C5_SHARPNESSMODE},		/* 01,c5,03,cc, */
+	{0xa0, 0x13, ZC3XX_R1CB_SHARPNESS05},		/* 01,cb,13,cc, */
+	{0xa0, 0x08, ZC3XX_R250_DEADPIXELSMODE},	/* 02,50,08,cc, */
+	{0xa0, 0x08, ZC3XX_R301_EEPROMACCESS},		/* 03,01,08,cc, */
+	{0xa0, 0x58, ZC3XX_R1A8_DIGITALGAIN},
+	{0xa0, 0x61, ZC3XX_R116_RGAIN},		/* 01,16,61,cc, */
+	{0xa0, 0x65, ZC3XX_R118_BGAIN},		/* 01,18,65,cc */
+	{0xaa, 0x1b, 0x0000},
+	{}
+};
+static const struct usb_action gc0303_50HZ[] = {
+	{0xaa, 0x82, 0x0000},		/* 00,82,00,aa */
+	{0xaa, 0x83, 0x0001},		/* 00,83,01,aa */
+	{0xaa, 0x84, 0x0063},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},	/* 01,90,00,cc, */
+	{0xa0, 0x06, ZC3XX_R191_EXPOSURELIMITMID},	/* 01,91,0d,cc, */
+	{0xa0, 0xa8, ZC3XX_R192_EXPOSURELIMITLOW},	/* 01,92,50,cc, */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},	/* 01,95,00,cc, */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},	/* 01,96,00,cc, */
+	{0xa0, 0x47, ZC3XX_R197_ANTIFLICKERLOW},	/* 01,97,47,cc, */
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},		/* 01,8c,0e,cc, */
+	{0xa0, 0x15, ZC3XX_R18F_AEUNFREEZE},		/* 01,8f,15,cc, */
+	{0xa0, 0x10, ZC3XX_R1A9_DIGITALLIMITDIFF},	/* 01,a9,10,cc, */
+	{0xa0, 0x48, ZC3XX_R1AA_DIGITALGAINSTEP},
+	{0xa0, 0x62, ZC3XX_R01D_HSYNC_0},		/* 00,1d,62,cc, */
+	{0xa0, 0x90, ZC3XX_R01E_HSYNC_1},		/* 00,1e,90,cc, */
+	{0xa0, 0xc8, ZC3XX_R01F_HSYNC_2},		/* 00,1f,c8,cc, */
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},		/* 00,20,ff,cc, */
+	{0xa0, 0x58, ZC3XX_R11D_GLOBALGAIN},		/* 01,1d,58,cc, */
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},	/* 01,80,42,cc, */
+	{0xa0, 0x7f, ZC3XX_R18D_YTARGET},
+	{}
+};
+
+static const struct usb_action gc0303_50HZScale[] = {
+	{0xaa, 0x82, 0x0000},		/* 00,82,00,aa */
+	{0xaa, 0x83, 0x0003},		/* 00,83,03,aa */
+	{0xaa, 0x84, 0x0054},		/* 00,84,54,aa */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},	/* 01,90,00,cc, */
+	{0xa0, 0x0d, ZC3XX_R191_EXPOSURELIMITMID},	/* 01,91,0d,cc, */
+	{0xa0, 0x50, ZC3XX_R192_EXPOSURELIMITLOW},	/* 01,92,50,cc, */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},	/* 01,95,00,cc, */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},	/* 01,96,00,cc, */
+	{0xa0, 0x8e, ZC3XX_R197_ANTIFLICKERLOW},	/* 01,97,8e,cc, */
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},		/* 01,8c,0e,cc, */
+	{0xa0, 0x15, ZC3XX_R18F_AEUNFREEZE},		/* 01,8f,15,cc, */
+	{0xa0, 0x10, ZC3XX_R1A9_DIGITALLIMITDIFF},	/* 01,a9,10,cc, */
+	{0xa0, 0x48, ZC3XX_R1AA_DIGITALGAINSTEP},	/* 01,aa,24,cc, */
+	{0xa0, 0x62, ZC3XX_R01D_HSYNC_0},		/* 00,1d,62,cc, */
+	{0xa0, 0x90, ZC3XX_R01E_HSYNC_1},		/* 00,1e,90,cc, */
+	{0xa0, 0xc8, ZC3XX_R01F_HSYNC_2},		/* 00,1f,c8,cc, */
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},		/* 00,20,ff,cc, */
+	{0xa0, 0x58, ZC3XX_R11D_GLOBALGAIN},		/* 01,1d,58,cc, */
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},	/* 01,80,42,cc, */
+	{0xa0, 0x7f, ZC3XX_R18D_YTARGET},
+	{}
+};
+
+static const struct usb_action gc0303_60HZ[] = {
+	{0xaa, 0x82, 0x0000},		/* 00,82,00,aa */
+	{0xaa, 0x83, 0x0000},
+	{0xaa, 0x84, 0x003b},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},	/* 01,90,00,cc, */
+	{0xa0, 0x05, ZC3XX_R191_EXPOSURELIMITMID},	/* 01,91,05,cc, */
+	{0xa0, 0x88, ZC3XX_R192_EXPOSURELIMITLOW},	/* 01,92,88,cc, */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},	/* 01,95,00,cc, */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},	/* 01,96,00,cc, */
+	{0xa0, 0x3b, ZC3XX_R197_ANTIFLICKERLOW},	/* 01,97,3b,cc, */
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},		/* 01,8c,0e,cc, */
+	{0xa0, 0x15, ZC3XX_R18F_AEUNFREEZE},		/* 01,8f,15,cc, */
+	{0xa0, 0x10, ZC3XX_R1A9_DIGITALLIMITDIFF},	/* 01,a9,10,cc, */
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},	/* 01,aa,24,cc, */
+	{0xa0, 0x62, ZC3XX_R01D_HSYNC_0},		/* 00,1d,62,cc, */
+	{0xa0, 0x90, ZC3XX_R01E_HSYNC_1},		/* 00,1e,90,cc, */
+	{0xa0, 0xc8, ZC3XX_R01F_HSYNC_2},		/* 00,1f,c8,cc, */
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},		/* 00,20,ff,cc, */
+	{0xa0, 0x58, ZC3XX_R11D_GLOBALGAIN},		/* 01,1d,58,cc, */
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},	/* 01,80,42,cc, */
+	{0xa0, 0x80, ZC3XX_R18D_YTARGET},
+	{}
+};
+
+static const struct usb_action gc0303_60HZScale[] = {
+	{0xaa, 0x82, 0x0000},		/* 00,82,00,aa */
+	{0xaa, 0x83, 0x0000},
+	{0xaa, 0x84, 0x0076},
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},	/* 01,90,00,cc, */
+	{0xa0, 0x0b, ZC3XX_R191_EXPOSURELIMITMID},	/* 01,1,0b,cc, */
+	{0xa0, 0x10, ZC3XX_R192_EXPOSURELIMITLOW},	/* 01,2,10,cc, */
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},	/* 01,5,00,cc, */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},	/* 01,6,00,cc, */
+	{0xa0, 0x76, ZC3XX_R197_ANTIFLICKERLOW},	/* 01,7,76,cc, */
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},		/* 01,c,0e,cc, */
+	{0xa0, 0x15, ZC3XX_R18F_AEUNFREEZE},		/* 01,f,15,cc, */
+	{0xa0, 0x10, ZC3XX_R1A9_DIGITALLIMITDIFF},	/* 01,9,10,cc, */
+	{0xa0, 0x24, ZC3XX_R1AA_DIGITALGAINSTEP},	/* 01,a,24,cc, */
+	{0xa0, 0x62, ZC3XX_R01D_HSYNC_0},		/* 00,d,62,cc, */
+	{0xa0, 0x90, ZC3XX_R01E_HSYNC_1},		/* 00,e,90,cc, */
+	{0xa0, 0xc8, ZC3XX_R01F_HSYNC_2},		/* 00,f,c8,cc, */
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},		/* 00,0,ff,cc, */
+	{0xa0, 0x58, ZC3XX_R11D_GLOBALGAIN},		/* 01,d,58,cc, */
+	{0xa0, 0x42, ZC3XX_R180_AUTOCORRECTENABLE},	/* 01,80,42,cc, */
+	{0xa0, 0x80, ZC3XX_R18D_YTARGET},
+	{}
+};
+
+static const struct usb_action gc0303_NoFliker[] = {
+	{0xa0, 0x0c, ZC3XX_R100_OPERATIONMODE},		/* 01,00,0c,cc, */
+	{0xaa, 0x82, 0x0000},		/* 00,82,00,aa */
+	{0xaa, 0x83, 0x0000},		/* 00,83,00,aa */
+	{0xaa, 0x84, 0x0020},		/* 00,84,20,aa */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},	/* 01,0,00,cc, */
+	{0xa0, 0x00, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x48, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},	/* 01,95,00,cc, */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},	/* 01,96,00,cc, */
+	{0xa0, 0x10, ZC3XX_R197_ANTIFLICKERLOW},	/* 01,97,10,cc, */
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},		/* 01,8c,0e,cc, */
+	{0xa0, 0x15, ZC3XX_R18F_AEUNFREEZE},		/* 01,8f,15,cc, */
+	{0xa0, 0x62, ZC3XX_R01D_HSYNC_0},		/* 00,1d,62,cc, */
+	{0xa0, 0x90, ZC3XX_R01E_HSYNC_1},		/* 00,1e,90,cc, */
+	{0xa0, 0xc8, ZC3XX_R01F_HSYNC_2},		/* 00,1f,c8,cc, */
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},		/* 00,20,ff,cc, */
+	{0xa0, 0x58, ZC3XX_R11D_GLOBALGAIN},		/* 01,1d,58,cc, */
+	{0xa0, 0x03, ZC3XX_R180_AUTOCORRECTENABLE},	/* 01,80,03,cc */
+	{}
+};
+
+static const struct usb_action gc0303_NoFlikerScale[] = {
+	{0xa0, 0x0c, ZC3XX_R100_OPERATIONMODE},		/* 01,00,0c,cc, */
+	{0xaa, 0x82, 0x0000},		/* 00,82,00,aa */
+	{0xaa, 0x83, 0x0000},		/* 00,83,00,aa */
+	{0xaa, 0x84, 0x0020},		/* 00,84,20,aa */
+	{0xa0, 0x00, ZC3XX_R190_EXPOSURELIMITHIGH},	/* 01,90,00,cc, */
+	{0xa0, 0x00, ZC3XX_R191_EXPOSURELIMITMID},
+	{0xa0, 0x48, ZC3XX_R192_EXPOSURELIMITLOW},
+	{0xa0, 0x00, ZC3XX_R195_ANTIFLICKERHIGH},	/* 01,95,00,cc, */
+	{0xa0, 0x00, ZC3XX_R196_ANTIFLICKERMID},	/* 01,96,00,cc, */
+	{0xa0, 0x10, ZC3XX_R197_ANTIFLICKERLOW},	/* 01,97,10,cc, */
+	{0xa0, 0x0e, ZC3XX_R18C_AEFREEZE},		/* 01,8c,0e,cc, */
+	{0xa0, 0x15, ZC3XX_R18F_AEUNFREEZE},		/* 01,8f,15,cc, */
+	{0xa0, 0x62, ZC3XX_R01D_HSYNC_0},		/* 00,1d,62,cc, */
+	{0xa0, 0x90, ZC3XX_R01E_HSYNC_1},		/* 00,1e,90,cc, */
+	{0xa0, 0xc8, ZC3XX_R01F_HSYNC_2},		/* 00,1f,c8,cc, */
+	{0xa0, 0xff, ZC3XX_R020_HSYNC_3},		/* 00,20,ff,cc, */
+	{0xa0, 0x58, ZC3XX_R11D_GLOBALGAIN},		/* 01,1d,58,cc, */
+	{0xa0, 0x03, ZC3XX_R180_AUTOCORRECTENABLE},	/* 01,80,03,cc */
+	{}
+};
+
+static u8 reg_r(struct gspca_dev *gspca_dev,
+		u16 index)
+{
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return 0;
+	ret = usb_control_msg(gspca_dev->dev,
+			usb_rcvctrlpipe(gspca_dev->dev, 0),
+			0xa1,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0x01,			/* value */
+			index, gspca_dev->usb_buf, 1,
+			500);
+	if (ret < 0) {
+		pr_err("reg_r err %d\n", ret);
+		gspca_dev->usb_err = ret;
+		return 0;
+	}
+	return gspca_dev->usb_buf[0];
+}
+
+static void reg_w(struct gspca_dev *gspca_dev,
+			u8 value,
+			u16 index)
+{
+	int ret;
+
+	if (gspca_dev->usb_err < 0)
+		return;
+	ret = usb_control_msg(gspca_dev->dev,
+			usb_sndctrlpipe(gspca_dev->dev, 0),
+			0xa0,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			value, index, NULL, 0,
+			500);
+	if (ret < 0) {
+		pr_err("reg_w_i err %d\n", ret);
+		gspca_dev->usb_err = ret;
+	}
+}
+
+static u16 i2c_read(struct gspca_dev *gspca_dev,
+			u8 reg)
+{
+	u8 retbyte;
+	u16 retval;
+
+	if (gspca_dev->usb_err < 0)
+		return 0;
+	reg_w(gspca_dev, reg, 0x0092);
+	reg_w(gspca_dev, 0x02, 0x0090);			/* <- read command */
+	msleep(20);
+	retbyte = reg_r(gspca_dev, 0x0091);		/* read status */
+	if (retbyte != 0x00)
+		pr_err("i2c_r status error %02x\n", retbyte);
+	retval = reg_r(gspca_dev, 0x0095);		/* read Lowbyte */
+	retval |= reg_r(gspca_dev, 0x0096) << 8;	/* read Hightbyte */
+	return retval;
+}
+
+static u8 i2c_write(struct gspca_dev *gspca_dev,
+			u8 reg,
+			u8 valL,
+			u8 valH)
+{
+	u8 retbyte;
+
+	if (gspca_dev->usb_err < 0)
+		return 0;
+	reg_w(gspca_dev, reg, 0x92);
+	reg_w(gspca_dev, valL, 0x93);
+	reg_w(gspca_dev, valH, 0x94);
+	reg_w(gspca_dev, 0x01, 0x90);		/* <- write command */
+	msleep(1);
+	retbyte = reg_r(gspca_dev, 0x0091);		/* read status */
+	if (retbyte != 0x00)
+		pr_err("i2c_w status error %02x\n", retbyte);
+	return retbyte;
+}
+
+static void usb_exchange(struct gspca_dev *gspca_dev,
+			const struct usb_action *action)
+{
+	while (action->req) {
+		switch (action->req) {
+		case 0xa0:	/* write register */
+			reg_w(gspca_dev, action->val, action->idx);
+			break;
+		case 0xa1:	/* read status */
+			reg_r(gspca_dev, action->idx);
+			break;
+		case 0xaa:
+			i2c_write(gspca_dev,
+				  action->val,			/* reg */
+				  action->idx & 0xff,		/* valL */
+				  action->idx >> 8);		/* valH */
+			break;
+		case 0xbb:
+			i2c_write(gspca_dev,
+				  action->idx >> 8,		/* reg */
+				  action->idx & 0xff,		/* valL */
+				  action->val);			/* valH */
+			break;
+		default:
+/*		case 0xdd:	 * delay */
+			msleep(action->idx);
+			break;
+		}
+		action++;
+		msleep(1);
+	}
+}
+
+static void setmatrix(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int i;
+	const u8 *matrix;
+	static const u8 adcm2700_matrix[9] =
+/*		{0x66, 0xed, 0xed, 0xed, 0x66, 0xed, 0xed, 0xed, 0x66}; */
+/*ms-win*/
+		{0x74, 0xed, 0xed, 0xed, 0x74, 0xed, 0xed, 0xed, 0x74};
+	static const u8 gc0305_matrix[9] =
+		{0x50, 0xf8, 0xf8, 0xf8, 0x50, 0xf8, 0xf8, 0xf8, 0x50};
+	static const u8 ov7620_matrix[9] =
+		{0x58, 0xf4, 0xf4, 0xf4, 0x58, 0xf4, 0xf4, 0xf4, 0x58};
+	static const u8 pas202b_matrix[9] =
+		{0x4c, 0xf5, 0xff, 0xf9, 0x51, 0xf5, 0xfb, 0xed, 0x5f};
+	static const u8 po2030_matrix[9] =
+		{0x60, 0xf0, 0xf0, 0xf0, 0x60, 0xf0, 0xf0, 0xf0, 0x60};
+	static const u8 tas5130c_matrix[9] =
+		{0x68, 0xec, 0xec, 0xec, 0x68, 0xec, 0xec, 0xec, 0x68};
+	static const u8 gc0303_matrix[9] =
+		{0x6c, 0xea, 0xea, 0xea, 0x6c, 0xea, 0xea, 0xea, 0x6c};
+	static const u8 *matrix_tb[SENSOR_MAX] = {
+		[SENSOR_ADCM2700] =	adcm2700_matrix,
+		[SENSOR_CS2102] =	ov7620_matrix,
+		[SENSOR_CS2102K] =	NULL,
+		[SENSOR_GC0303] =	gc0303_matrix,
+		[SENSOR_GC0305] =	gc0305_matrix,
+		[SENSOR_HDCS2020] =	NULL,
+		[SENSOR_HV7131B] =	NULL,
+		[SENSOR_HV7131R] =	po2030_matrix,
+		[SENSOR_ICM105A] =	po2030_matrix,
+		[SENSOR_MC501CB] =	NULL,
+		[SENSOR_MT9V111_1] =	gc0305_matrix,
+		[SENSOR_MT9V111_3] =	gc0305_matrix,
+		[SENSOR_OV7620] =	ov7620_matrix,
+		[SENSOR_OV7630C] =	NULL,
+		[SENSOR_PAS106] =	NULL,
+		[SENSOR_PAS202B] =	pas202b_matrix,
+		[SENSOR_PB0330] =	gc0305_matrix,
+		[SENSOR_PO2030] =	po2030_matrix,
+		[SENSOR_TAS5130C] =	tas5130c_matrix,
+	};
+
+	matrix = matrix_tb[sd->sensor];
+	if (matrix == NULL)
+		return;		/* matrix already loaded */
+	for (i = 0; i < ARRAY_SIZE(ov7620_matrix); i++)
+		reg_w(gspca_dev, matrix[i], 0x010a + i);
+}
+
+static void setsharpness(struct gspca_dev *gspca_dev, s32 val)
+{
+	static const u8 sharpness_tb[][2] = {
+		{0x02, 0x03},
+		{0x04, 0x07},
+		{0x08, 0x0f},
+		{0x10, 0x1e}
+	};
+
+	reg_w(gspca_dev, sharpness_tb[val][0], 0x01c6);
+	reg_r(gspca_dev, 0x01c8);
+	reg_r(gspca_dev, 0x01c9);
+	reg_r(gspca_dev, 0x01ca);
+	reg_w(gspca_dev, sharpness_tb[val][1], 0x01cb);
+}
+
+static void setcontrast(struct gspca_dev *gspca_dev,
+		s32 gamma, s32 brightness, s32 contrast)
+{
+	const u8 *Tgamma;
+	int g, i, adj, gp1, gp2;
+	u8 gr[16];
+	static const u8 delta_b[16] =		/* delta for brightness */
+		{0x50, 0x38, 0x2d, 0x28, 0x24, 0x21, 0x1e, 0x1d,
+		 0x1d, 0x1b, 0x1b, 0x1b, 0x19, 0x18, 0x18, 0x18};
+	static const u8 delta_c[16] =		/* delta for contrast */
+		{0x2c, 0x1a, 0x12, 0x0c, 0x0a, 0x06, 0x06, 0x06,
+		 0x04, 0x06, 0x04, 0x04, 0x03, 0x03, 0x02, 0x02};
+	static const u8 gamma_tb[6][16] = {
+		{0x00, 0x00, 0x03, 0x0d, 0x1b, 0x2e, 0x45, 0x5f,
+		 0x79, 0x93, 0xab, 0xc1, 0xd4, 0xe5, 0xf3, 0xff},
+		{0x01, 0x0c, 0x1f, 0x3a, 0x53, 0x6d, 0x85, 0x9c,
+		 0xb0, 0xc2, 0xd1, 0xde, 0xe9, 0xf2, 0xf9, 0xff},
+		{0x04, 0x16, 0x30, 0x4e, 0x68, 0x81, 0x98, 0xac,
+		 0xbe, 0xcd, 0xda, 0xe4, 0xed, 0xf5, 0xfb, 0xff},
+		{0x13, 0x38, 0x59, 0x79, 0x92, 0xa7, 0xb9, 0xc8,
+		 0xd4, 0xdf, 0xe7, 0xee, 0xf4, 0xf9, 0xfc, 0xff},
+		{0x20, 0x4b, 0x6e, 0x8d, 0xa3, 0xb5, 0xc5, 0xd2,
+		 0xdc, 0xe5, 0xec, 0xf2, 0xf6, 0xfa, 0xfd, 0xff},
+		{0x24, 0x44, 0x64, 0x84, 0x9d, 0xb2, 0xc4, 0xd3,
+		 0xe0, 0xeb, 0xf4, 0xff, 0xff, 0xff, 0xff, 0xff},
+	};
+
+	Tgamma = gamma_tb[gamma - 1];
+
+	contrast -= 128; /* -128 / 127 */
+	brightness -= 128; /* -128 / 92 */
+	adj = 0;
+	gp1 = gp2 = 0;
+	for (i = 0; i < 16; i++) {
+		g = Tgamma[i] + delta_b[i] * brightness / 256
+				- delta_c[i] * contrast / 256 - adj / 2;
+		if (g > 0xff)
+			g = 0xff;
+		else if (g < 0)
+			g = 0;
+		reg_w(gspca_dev, g, 0x0120 + i);	/* gamma */
+		if (contrast > 0)
+			adj--;
+		else if (contrast < 0)
+			adj++;
+		if (i > 1)
+			gr[i - 1] = (g - gp2) / 2;
+		else if (i != 0)
+			gr[0] = gp1 == 0 ? 0 : (g - gp1);
+		gp2 = gp1;
+		gp1 = g;
+	}
+	gr[15] = (0xff - gp2) / 2;
+	for (i = 0; i < 16; i++)
+		reg_w(gspca_dev, gr[i], 0x0130 + i);	/* gradient */
+}
+
+static s32 getexposure(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	switch (sd->sensor) {
+	case SENSOR_HV7131R:
+		return (i2c_read(gspca_dev, 0x25) << 9)
+			| (i2c_read(gspca_dev, 0x26) << 1)
+			| (i2c_read(gspca_dev, 0x27) >> 7);
+	case SENSOR_OV7620:
+		return i2c_read(gspca_dev, 0x10);
+	default:
+		return -1;
+	}
+}
+
+static void setexposure(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	switch (sd->sensor) {
+	case SENSOR_HV7131R:
+		i2c_write(gspca_dev, 0x25, val >> 9, 0x00);
+		i2c_write(gspca_dev, 0x26, val >> 1, 0x00);
+		i2c_write(gspca_dev, 0x27, val << 7, 0x00);
+		break;
+	case SENSOR_OV7620:
+		i2c_write(gspca_dev, 0x10, val, 0x00);
+		break;
+	}
+}
+
+static void setquality(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	jpeg_set_qual(sd->jpeg_hdr, jpeg_qual[sd->reg08 >> 1]);
+	reg_w(gspca_dev, sd->reg08, ZC3XX_R008_CLOCKSETTING);
+}
+
+/* Matches the sensor's internal frame rate to the lighting frequency.
+ * Valid frequencies are:
+ *	50Hz, for European and Asian lighting (default)
+ *	60Hz, for American lighting
+ *	0 = No Fliker (for outdoore usage)
+ */
+static void setlightfreq(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int i, mode;
+	const struct usb_action *zc3_freq;
+	static const struct usb_action *freq_tb[SENSOR_MAX][6] = {
+	[SENSOR_ADCM2700] =
+		{adcm2700_NoFliker, adcm2700_NoFliker,
+		 adcm2700_50HZ, adcm2700_50HZ,
+		 adcm2700_60HZ, adcm2700_60HZ},
+	[SENSOR_CS2102] =
+		{cs2102_NoFliker, cs2102_NoFlikerScale,
+		 cs2102_50HZ, cs2102_50HZScale,
+		 cs2102_60HZ, cs2102_60HZScale},
+	[SENSOR_CS2102K] =
+		{cs2102_NoFliker, cs2102_NoFlikerScale,
+		 NULL, NULL, /* currently disabled */
+		 NULL, NULL},
+	[SENSOR_GC0303] =
+		{gc0303_NoFliker, gc0303_NoFlikerScale,
+		 gc0303_50HZ, gc0303_50HZScale,
+		 gc0303_60HZ, gc0303_60HZScale},
+	[SENSOR_GC0305] =
+		{gc0305_NoFliker, gc0305_NoFliker,
+		 gc0305_50HZ, gc0305_50HZ,
+		 gc0305_60HZ, gc0305_60HZ},
+	[SENSOR_HDCS2020] =
+		{hdcs2020_NoFliker, hdcs2020_NoFliker,
+		 hdcs2020_50HZ, hdcs2020_50HZ,
+		 hdcs2020_60HZ, hdcs2020_60HZ},
+	[SENSOR_HV7131B] =
+		{hv7131b_NoFliker, hv7131b_NoFlikerScale,
+		 hv7131b_50HZ, hv7131b_50HZScale,
+		 hv7131b_60HZ, hv7131b_60HZScale},
+	[SENSOR_HV7131R] =
+		{hv7131r_NoFliker, hv7131r_NoFlikerScale,
+		 hv7131r_50HZ, hv7131r_50HZScale,
+		 hv7131r_60HZ, hv7131r_60HZScale},
+	[SENSOR_ICM105A] =
+		{icm105a_NoFliker, icm105a_NoFlikerScale,
+		 icm105a_50HZ, icm105a_50HZScale,
+		 icm105a_60HZ, icm105a_60HZScale},
+	[SENSOR_MC501CB] =
+		{mc501cb_NoFliker, mc501cb_NoFlikerScale,
+		 mc501cb_50HZ, mc501cb_50HZScale,
+		 mc501cb_60HZ, mc501cb_60HZScale},
+	[SENSOR_MT9V111_1] =
+		{mt9v111_1_AENoFliker, mt9v111_1_AENoFlikerScale,
+		 mt9v111_1_AE50HZ, mt9v111_1_AE50HZScale,
+		 mt9v111_1_AE60HZ, mt9v111_1_AE60HZScale},
+	[SENSOR_MT9V111_3] =
+		{mt9v111_3_AENoFliker, mt9v111_3_AENoFlikerScale,
+		 mt9v111_3_AE50HZ, mt9v111_3_AE50HZScale,
+		 mt9v111_3_AE60HZ, mt9v111_3_AE60HZScale},
+	[SENSOR_OV7620] =
+		{ov7620_NoFliker, ov7620_NoFliker,
+		 ov7620_50HZ, ov7620_50HZ,
+		 ov7620_60HZ, ov7620_60HZ},
+	[SENSOR_OV7630C] =
+		{NULL, NULL,
+		 NULL, NULL,
+		 NULL, NULL},
+	[SENSOR_PAS106] =
+		{pas106b_NoFliker, pas106b_NoFliker,
+		 pas106b_50HZ, pas106b_50HZ,
+		 pas106b_60HZ, pas106b_60HZ},
+	[SENSOR_PAS202B] =
+		{pas202b_NoFliker, pas202b_NoFlikerScale,
+		 pas202b_50HZ, pas202b_50HZScale,
+		 pas202b_60HZ, pas202b_60HZScale},
+	[SENSOR_PB0330] =
+		{pb0330_NoFliker, pb0330_NoFlikerScale,
+		 pb0330_50HZ, pb0330_50HZScale,
+		 pb0330_60HZ, pb0330_60HZScale},
+	[SENSOR_PO2030] =
+		{po2030_NoFliker, po2030_NoFliker,
+		 po2030_50HZ, po2030_50HZ,
+		 po2030_60HZ, po2030_60HZ},
+	[SENSOR_TAS5130C] =
+		{tas5130c_NoFliker, tas5130c_NoFlikerScale,
+		 tas5130c_50HZ, tas5130c_50HZScale,
+		 tas5130c_60HZ, tas5130c_60HZScale},
+	};
+
+	i = val * 2;
+	mode = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].priv;
+	if (mode)
+		i++;			/* 320x240 */
+	zc3_freq = freq_tb[sd->sensor][i];
+	if (zc3_freq == NULL)
+		return;
+	usb_exchange(gspca_dev, zc3_freq);
+	switch (sd->sensor) {
+	case SENSOR_GC0305:
+		if (mode		/* if 320x240 */
+		    && val == 1)	/* and 50Hz */
+			reg_w(gspca_dev, 0x85, 0x018d);
+					/* win: 0x80, 0x018d */
+		break;
+	case SENSOR_OV7620:
+		if (!mode) {		/* if 640x480 */
+			if (val != 0)	/* and filter */
+				reg_w(gspca_dev, 0x40, 0x0002);
+			else
+				reg_w(gspca_dev, 0x44, 0x0002);
+		}
+		break;
+	case SENSOR_PAS202B:
+		reg_w(gspca_dev, 0x00, 0x01a7);
+		break;
+	}
+}
+
+static void setautogain(struct gspca_dev *gspca_dev, s32 val)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (sd->sensor == SENSOR_OV7620)
+		i2c_write(gspca_dev, 0x13, val ? 0xa3 : 0x80, 0x00);
+	else
+		reg_w(gspca_dev, val ? 0x42 : 0x02, 0x0180);
+}
+
+/*
+ * Update the transfer parameters.
+ * This function is executed from a work queue.
+ */
+static void transfer_update(struct work_struct *work)
+{
+	struct sd *sd = container_of(work, struct sd, work);
+	struct gspca_dev *gspca_dev = &sd->gspca_dev;
+	int change, good;
+	u8 reg07, reg11;
+
+	/* reg07 gets set to 0 by sd_start before starting us */
+	reg07 = 0;
+
+	good = 0;
+	while (1) {
+		msleep(100);
+
+		/* To protect gspca_dev->usb_buf and gspca_dev->usb_err */
+		mutex_lock(&gspca_dev->usb_lock);
+#ifdef CONFIG_PM
+		if (gspca_dev->frozen)
+			break;
+#endif
+		if (!gspca_dev->present || !gspca_dev->streaming)
+			break;
+
+		/* Bit 0 of register 11 indicates FIFO overflow */
+		gspca_dev->usb_err = 0;
+		reg11 = reg_r(gspca_dev, 0x0011);
+		if (gspca_dev->usb_err)
+			break;
+
+		change = reg11 & 0x01;
+		if (change) {				/* overflow */
+			good = 0;
+
+			if (reg07 == 0) /* Bit Rate Control not enabled? */
+				reg07 = 0x32; /* Allow 98 bytes / unit */
+			else if (reg07 > 2)
+				reg07 -= 2; /* Decrease allowed bytes / unit */
+			else
+				change = 0;
+		} else {				/* no overflow */
+			good++;
+			if (good >= 10) {
+				good = 0;
+				if (reg07) { /* BRC enabled? */
+					change = 1;
+					if (reg07 < 0x32)
+						reg07 += 2;
+					else
+						reg07 = 0;
+				}
+			}
+		}
+		if (change) {
+			gspca_dev->usb_err = 0;
+			reg_w(gspca_dev, reg07, 0x0007);
+			if (gspca_dev->usb_err)
+				break;
+		}
+		mutex_unlock(&gspca_dev->usb_lock);
+	}
+
+	/* Something went wrong. Unlock and return */
+	mutex_unlock(&gspca_dev->usb_lock);
+}
+
+static void send_unknown(struct gspca_dev *gspca_dev, int sensor)
+{
+	reg_w(gspca_dev, 0x01, 0x0000);		/* bridge reset */
+	switch (sensor) {
+	case SENSOR_PAS106:
+		reg_w(gspca_dev, 0x03, 0x003a);
+		reg_w(gspca_dev, 0x0c, 0x003b);
+		reg_w(gspca_dev, 0x08, 0x0038);
+		break;
+	case SENSOR_ADCM2700:
+	case SENSOR_GC0305:
+	case SENSOR_OV7620:
+	case SENSOR_MT9V111_1:
+	case SENSOR_MT9V111_3:
+	case SENSOR_PB0330:
+	case SENSOR_PO2030:
+		reg_w(gspca_dev, 0x0d, 0x003a);
+		reg_w(gspca_dev, 0x02, 0x003b);
+		reg_w(gspca_dev, 0x00, 0x0038);
+		break;
+	case SENSOR_HV7131R:
+	case SENSOR_PAS202B:
+		reg_w(gspca_dev, 0x03, 0x003b);
+		reg_w(gspca_dev, 0x0c, 0x003a);
+		reg_w(gspca_dev, 0x0b, 0x0039);
+		if (sensor == SENSOR_PAS202B)
+			reg_w(gspca_dev, 0x0b, 0x0038);
+		break;
+	}
+}
+
+/* start probe 2 wires */
+static void start_2wr_probe(struct gspca_dev *gspca_dev, int sensor)
+{
+	reg_w(gspca_dev, 0x01, 0x0000);
+	reg_w(gspca_dev, sensor, 0x0010);
+	reg_w(gspca_dev, 0x01, 0x0001);
+	reg_w(gspca_dev, 0x03, 0x0012);
+	reg_w(gspca_dev, 0x01, 0x0012);
+/*	msleep(2); */
+}
+
+static int sif_probe(struct gspca_dev *gspca_dev)
+{
+	u16 checkword;
+
+	start_2wr_probe(gspca_dev, 0x0f);		/* PAS106 */
+	reg_w(gspca_dev, 0x08, 0x008d);
+	msleep(150);
+	checkword = ((i2c_read(gspca_dev, 0x00) & 0x0f) << 4)
+			| ((i2c_read(gspca_dev, 0x01) & 0xf0) >> 4);
+	gspca_dbg(gspca_dev, D_PROBE, "probe sif 0x%04x\n", checkword);
+	if (checkword == 0x0007) {
+		send_unknown(gspca_dev, SENSOR_PAS106);
+		return 0x0f;			/* PAS106 */
+	}
+	return -1;
+}
+
+static int vga_2wr_probe(struct gspca_dev *gspca_dev)
+{
+	u16 retword;
+
+	start_2wr_probe(gspca_dev, 0x00);	/* HV7131B */
+	i2c_write(gspca_dev, 0x01, 0xaa, 0x00);
+	retword = i2c_read(gspca_dev, 0x01);
+	if (retword != 0)
+		return 0x00;			/* HV7131B */
+
+	start_2wr_probe(gspca_dev, 0x04);	/* CS2102 */
+	i2c_write(gspca_dev, 0x01, 0xaa, 0x00);
+	retword = i2c_read(gspca_dev, 0x01);
+	if (retword != 0)
+		return 0x04;			/* CS2102 */
+
+	start_2wr_probe(gspca_dev, 0x06);	/* OmniVision */
+	reg_w(gspca_dev, 0x08, 0x008d);
+	i2c_write(gspca_dev, 0x11, 0xaa, 0x00);
+	retword = i2c_read(gspca_dev, 0x11);
+	if (retword != 0) {
+		/* (should have returned 0xaa) --> Omnivision? */
+		/* reg_r 0x10 -> 0x06 -->  */
+		goto ov_check;
+	}
+
+	start_2wr_probe(gspca_dev, 0x08);	/* HDCS2020 */
+	i2c_write(gspca_dev, 0x1c, 0x00, 0x00);
+	i2c_write(gspca_dev, 0x15, 0xaa, 0x00);
+	retword = i2c_read(gspca_dev, 0x15);
+	if (retword != 0)
+		return 0x08;			/* HDCS2020 */
+
+	start_2wr_probe(gspca_dev, 0x0a);	/* PB0330 */
+	i2c_write(gspca_dev, 0x07, 0xaa, 0xaa);
+	retword = i2c_read(gspca_dev, 0x07);
+	if (retword != 0)
+		return 0x0a;			/* PB0330 */
+	retword = i2c_read(gspca_dev, 0x03);
+	if (retword != 0)
+		return 0x0a;			/* PB0330 ?? */
+	retword = i2c_read(gspca_dev, 0x04);
+	if (retword != 0)
+		return 0x0a;			/* PB0330 ?? */
+
+	start_2wr_probe(gspca_dev, 0x0c);	/* ICM105A */
+	i2c_write(gspca_dev, 0x01, 0x11, 0x00);
+	retword = i2c_read(gspca_dev, 0x01);
+	if (retword != 0)
+		return 0x0c;			/* ICM105A */
+
+	start_2wr_probe(gspca_dev, 0x0e);	/* PAS202BCB */
+	reg_w(gspca_dev, 0x08, 0x008d);
+	i2c_write(gspca_dev, 0x03, 0xaa, 0x00);
+	msleep(50);
+	retword = i2c_read(gspca_dev, 0x03);
+	if (retword != 0) {
+		send_unknown(gspca_dev, SENSOR_PAS202B);
+		return 0x0e;			/* PAS202BCB */
+	}
+
+	start_2wr_probe(gspca_dev, 0x02);	/* TAS5130C */
+	i2c_write(gspca_dev, 0x01, 0xaa, 0x00);
+	retword = i2c_read(gspca_dev, 0x01);
+	if (retword != 0)
+		return 0x02;			/* TAS5130C */
+ov_check:
+	reg_r(gspca_dev, 0x0010);		/* ?? */
+	reg_r(gspca_dev, 0x0010);
+
+	reg_w(gspca_dev, 0x01, 0x0000);
+	reg_w(gspca_dev, 0x01, 0x0001);
+	reg_w(gspca_dev, 0x06, 0x0010);		/* OmniVision */
+	reg_w(gspca_dev, 0xa1, 0x008b);
+	reg_w(gspca_dev, 0x08, 0x008d);
+	msleep(500);
+	reg_w(gspca_dev, 0x01, 0x0012);
+	i2c_write(gspca_dev, 0x12, 0x80, 0x00);	/* sensor reset */
+	retword = i2c_read(gspca_dev, 0x0a) << 8;
+	retword |= i2c_read(gspca_dev, 0x0b);
+	gspca_dbg(gspca_dev, D_PROBE, "probe 2wr ov vga 0x%04x\n", retword);
+	switch (retword) {
+	case 0x7631:				/* OV7630C */
+		reg_w(gspca_dev, 0x06, 0x0010);
+		break;
+	case 0x7620:				/* OV7620 */
+	case 0x7648:				/* OV7648 */
+		break;
+	default:
+		return -1;			/* not OmniVision */
+	}
+	return retword;
+}
+
+struct sensor_by_chipset_revision {
+	u16 revision;
+	u8 internal_sensor_id;
+};
+static const struct sensor_by_chipset_revision chipset_revision_sensor[] = {
+	{0xc000, 0x12},		/* TAS5130C */
+	{0xc001, 0x13},		/* MT9V111 */
+	{0xe001, 0x13},
+	{0x8001, 0x13},
+	{0x8000, 0x14},		/* CS2102K */
+	{0x8400, 0x15},		/* MT9V111 */
+	{0xe400, 0x15},
+};
+
+static int vga_3wr_probe(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int i;
+	u16 retword;
+
+/*fixme: lack of 8b=b3 (11,12)-> 10, 8b=e0 (14,15,16)-> 12 found in gspcav1*/
+	reg_w(gspca_dev, 0x02, 0x0010);
+	reg_r(gspca_dev, 0x0010);
+	reg_w(gspca_dev, 0x01, 0x0000);
+	reg_w(gspca_dev, 0x00, 0x0010);
+	reg_w(gspca_dev, 0x01, 0x0001);
+	reg_w(gspca_dev, 0x91, 0x008b);
+	reg_w(gspca_dev, 0x03, 0x0012);
+	reg_w(gspca_dev, 0x01, 0x0012);
+	reg_w(gspca_dev, 0x05, 0x0012);
+	retword = i2c_read(gspca_dev, 0x14);
+	if (retword != 0)
+		return 0x11;			/* HV7131R */
+	retword = i2c_read(gspca_dev, 0x15);
+	if (retword != 0)
+		return 0x11;			/* HV7131R */
+	retword = i2c_read(gspca_dev, 0x16);
+	if (retword != 0)
+		return 0x11;			/* HV7131R */
+
+	reg_w(gspca_dev, 0x02, 0x0010);
+	retword = reg_r(gspca_dev, 0x000b) << 8;
+	retword |= reg_r(gspca_dev, 0x000a);
+	gspca_dbg(gspca_dev, D_PROBE, "probe 3wr vga 1 0x%04x\n", retword);
+	reg_r(gspca_dev, 0x0010);
+	if ((retword & 0xff00) == 0x6400)
+		return 0x02;		/* TAS5130C */
+	for (i = 0; i < ARRAY_SIZE(chipset_revision_sensor); i++) {
+		if (chipset_revision_sensor[i].revision == retword) {
+			sd->chip_revision = retword;
+			send_unknown(gspca_dev, SENSOR_PB0330);
+			return chipset_revision_sensor[i].internal_sensor_id;
+		}
+	}
+
+	reg_w(gspca_dev, 0x01, 0x0000);	/* check PB0330 */
+	reg_w(gspca_dev, 0x01, 0x0001);
+	reg_w(gspca_dev, 0xdd, 0x008b);
+	reg_w(gspca_dev, 0x0a, 0x0010);
+	reg_w(gspca_dev, 0x03, 0x0012);
+	reg_w(gspca_dev, 0x01, 0x0012);
+	retword = i2c_read(gspca_dev, 0x00);
+	if (retword != 0) {
+		gspca_dbg(gspca_dev, D_PROBE, "probe 3wr vga type 0a\n");
+		return 0x0a;			/* PB0330 */
+	}
+
+	/* probe gc0303 / gc0305 */
+	reg_w(gspca_dev, 0x01, 0x0000);
+	reg_w(gspca_dev, 0x01, 0x0001);
+	reg_w(gspca_dev, 0x98, 0x008b);
+	reg_w(gspca_dev, 0x01, 0x0010);
+	reg_w(gspca_dev, 0x03, 0x0012);
+	msleep(2);
+	reg_w(gspca_dev, 0x01, 0x0012);
+	retword = i2c_read(gspca_dev, 0x00);
+	if (retword != 0) {
+		gspca_dbg(gspca_dev, D_PROBE, "probe 3wr vga type %02x\n",
+			  retword);
+		if (retword == 0x0011)			/* gc0303 */
+			return 0x0303;
+		if (retword == 0x0029)			/* gc0305 */
+			send_unknown(gspca_dev, SENSOR_GC0305);
+		return retword;
+	}
+
+	reg_w(gspca_dev, 0x01, 0x0000);	/* check OmniVision */
+	reg_w(gspca_dev, 0x01, 0x0001);
+	reg_w(gspca_dev, 0xa1, 0x008b);
+	reg_w(gspca_dev, 0x08, 0x008d);
+	reg_w(gspca_dev, 0x06, 0x0010);
+	reg_w(gspca_dev, 0x01, 0x0012);
+	reg_w(gspca_dev, 0x05, 0x0012);
+	if (i2c_read(gspca_dev, 0x1c) == 0x007f	/* OV7610 - manufacturer ID */
+	    && i2c_read(gspca_dev, 0x1d) == 0x00a2) {
+		send_unknown(gspca_dev, SENSOR_OV7620);
+		return 0x06;		/* OmniVision confirm ? */
+	}
+
+	reg_w(gspca_dev, 0x01, 0x0000);
+	reg_w(gspca_dev, 0x00, 0x0002);
+	reg_w(gspca_dev, 0x01, 0x0010);
+	reg_w(gspca_dev, 0x01, 0x0001);
+	reg_w(gspca_dev, 0xee, 0x008b);
+	reg_w(gspca_dev, 0x03, 0x0012);
+	reg_w(gspca_dev, 0x01, 0x0012);
+	reg_w(gspca_dev, 0x05, 0x0012);
+	retword = i2c_read(gspca_dev, 0x00) << 8;	/* ID 0 */
+	retword |= i2c_read(gspca_dev, 0x01);		/* ID 1 */
+	gspca_dbg(gspca_dev, D_PROBE, "probe 3wr vga 2 0x%04x\n", retword);
+	if (retword == 0x2030) {
+		u8 retbyte;
+
+		retbyte = i2c_read(gspca_dev, 0x02);	/* revision number */
+		gspca_dbg(gspca_dev, D_PROBE, "sensor PO2030 rev 0x%02x\n",
+			  retbyte);
+
+		send_unknown(gspca_dev, SENSOR_PO2030);
+		return retword;
+	}
+
+	reg_w(gspca_dev, 0x01, 0x0000);
+	reg_w(gspca_dev, 0x0a, 0x0010);
+	reg_w(gspca_dev, 0xd3, 0x008b);
+	reg_w(gspca_dev, 0x01, 0x0001);
+	reg_w(gspca_dev, 0x03, 0x0012);
+	reg_w(gspca_dev, 0x01, 0x0012);
+	reg_w(gspca_dev, 0x05, 0x0012);
+	reg_w(gspca_dev, 0xd3, 0x008b);
+	retword = i2c_read(gspca_dev, 0x01);
+	if (retword != 0) {
+		gspca_dbg(gspca_dev, D_PROBE, "probe 3wr vga type 0a ? ret: %04x\n",
+			  retword);
+		return 0x16;			/* adcm2700 (6100/6200) */
+	}
+	return -1;
+}
+
+static int zcxx_probeSensor(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int sensor;
+
+	switch (sd->sensor) {
+	case SENSOR_MC501CB:
+		return -1;		/* don't probe */
+	case SENSOR_GC0303:
+			/* may probe but with no write in reg 0x0010 */
+		return -1;		/* don't probe */
+	case SENSOR_PAS106:
+		sensor =  sif_probe(gspca_dev);
+		if (sensor >= 0)
+			return sensor;
+		break;
+	}
+	sensor = vga_2wr_probe(gspca_dev);
+	if (sensor >= 0)
+		return sensor;
+	return vga_3wr_probe(gspca_dev);
+}
+
+/* this function is called at probe time */
+static int sd_config(struct gspca_dev *gspca_dev,
+			const struct usb_device_id *id)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	if (id->idProduct == 0x301b)
+		sd->bridge = BRIDGE_ZC301;
+	else
+		sd->bridge = BRIDGE_ZC303;
+
+	/* define some sensors from the vendor/product */
+	sd->sensor = id->driver_info;
+
+	sd->reg08 = REG08_DEF;
+
+	INIT_WORK(&sd->work, transfer_update);
+
+	return 0;
+}
+
+static int zcxx_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *)gspca_dev;
+
+	switch (ctrl->id) {
+	case V4L2_CID_AUTOGAIN:
+		gspca_dev->usb_err = 0;
+		if (ctrl->val && sd->exposure && gspca_dev->streaming)
+			sd->exposure->val = getexposure(gspca_dev);
+		return gspca_dev->usb_err;
+	}
+	return -EINVAL;
+}
+
+static int zcxx_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct gspca_dev *gspca_dev =
+		container_of(ctrl->handler, struct gspca_dev, ctrl_handler);
+	struct sd *sd = (struct sd *)gspca_dev;
+	int i, qual;
+
+	gspca_dev->usb_err = 0;
+
+	if (ctrl->id == V4L2_CID_JPEG_COMPRESSION_QUALITY) {
+		qual = sd->reg08 >> 1;
+
+		for (i = 0; i < ARRAY_SIZE(jpeg_qual); i++) {
+			if (ctrl->val <= jpeg_qual[i])
+				break;
+		}
+		if (i == ARRAY_SIZE(jpeg_qual) || (i > 0 && i == qual && ctrl->val < jpeg_qual[i]))
+			i--;
+
+		/* With high quality settings we need max bandwidth */
+		if (i >= 2 && gspca_dev->streaming &&
+		    !gspca_dev->cam.needs_full_bandwidth)
+			return -EBUSY;
+
+		sd->reg08 = (i << 1) | 1;
+		ctrl->val = jpeg_qual[i];
+	}
+
+	if (!gspca_dev->streaming)
+		return 0;
+
+	switch (ctrl->id) {
+	/* gamma/brightness/contrast cluster */
+	case V4L2_CID_GAMMA:
+		setcontrast(gspca_dev, sd->gamma->val,
+				sd->brightness->val, sd->contrast->val);
+		break;
+	/* autogain/exposure cluster */
+	case V4L2_CID_AUTOGAIN:
+		setautogain(gspca_dev, ctrl->val);
+		if (!gspca_dev->usb_err && !ctrl->val && sd->exposure)
+			setexposure(gspca_dev, sd->exposure->val);
+		break;
+	case V4L2_CID_POWER_LINE_FREQUENCY:
+		setlightfreq(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_SHARPNESS:
+		setsharpness(gspca_dev, ctrl->val);
+		break;
+	case V4L2_CID_JPEG_COMPRESSION_QUALITY:
+		setquality(gspca_dev);
+		break;
+	}
+	return gspca_dev->usb_err;
+}
+
+static const struct v4l2_ctrl_ops zcxx_ctrl_ops = {
+	.g_volatile_ctrl = zcxx_g_volatile_ctrl,
+	.s_ctrl = zcxx_s_ctrl,
+};
+
+static int sd_init_controls(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *)gspca_dev;
+	struct v4l2_ctrl_handler *hdl = &gspca_dev->ctrl_handler;
+	static const u8 gamma[SENSOR_MAX] = {
+		[SENSOR_ADCM2700] =	4,
+		[SENSOR_CS2102] =	4,
+		[SENSOR_CS2102K] =	5,
+		[SENSOR_GC0303] =	3,
+		[SENSOR_GC0305] =	4,
+		[SENSOR_HDCS2020] =	4,
+		[SENSOR_HV7131B] =	4,
+		[SENSOR_HV7131R] =	4,
+		[SENSOR_ICM105A] =	4,
+		[SENSOR_MC501CB] =	4,
+		[SENSOR_MT9V111_1] =	4,
+		[SENSOR_MT9V111_3] =	4,
+		[SENSOR_OV7620] =	3,
+		[SENSOR_OV7630C] =	4,
+		[SENSOR_PAS106] =	4,
+		[SENSOR_PAS202B] =	4,
+		[SENSOR_PB0330] =	4,
+		[SENSOR_PO2030] =	4,
+		[SENSOR_TAS5130C] =	3,
+	};
+
+	gspca_dev->vdev.ctrl_handler = hdl;
+	v4l2_ctrl_handler_init(hdl, 8);
+	sd->brightness = v4l2_ctrl_new_std(hdl, &zcxx_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0, 255, 1, 128);
+	sd->contrast = v4l2_ctrl_new_std(hdl, &zcxx_ctrl_ops,
+			V4L2_CID_CONTRAST, 0, 255, 1, 128);
+	sd->gamma = v4l2_ctrl_new_std(hdl, &zcxx_ctrl_ops,
+			V4L2_CID_GAMMA, 1, 6, 1, gamma[sd->sensor]);
+	if (sd->sensor == SENSOR_HV7131R)
+		sd->exposure = v4l2_ctrl_new_std(hdl, &zcxx_ctrl_ops,
+			V4L2_CID_EXPOSURE, 0x30d, 0x493e, 1, 0x927);
+	else if (sd->sensor == SENSOR_OV7620)
+		sd->exposure = v4l2_ctrl_new_std(hdl, &zcxx_ctrl_ops,
+			V4L2_CID_EXPOSURE, 0, 255, 1, 0x41);
+	sd->autogain = v4l2_ctrl_new_std(hdl, &zcxx_ctrl_ops,
+			V4L2_CID_AUTOGAIN, 0, 1, 1, 1);
+	if (sd->sensor != SENSOR_OV7630C)
+		sd->plfreq = v4l2_ctrl_new_std_menu(hdl, &zcxx_ctrl_ops,
+			V4L2_CID_POWER_LINE_FREQUENCY,
+			V4L2_CID_POWER_LINE_FREQUENCY_60HZ, 0,
+			V4L2_CID_POWER_LINE_FREQUENCY_DISABLED);
+	sd->sharpness = v4l2_ctrl_new_std(hdl, &zcxx_ctrl_ops,
+			V4L2_CID_SHARPNESS, 0, 3, 1,
+			sd->sensor == SENSOR_PO2030 ? 0 : 2);
+	sd->jpegqual = v4l2_ctrl_new_std(hdl, &zcxx_ctrl_ops,
+			V4L2_CID_JPEG_COMPRESSION_QUALITY,
+			jpeg_qual[0], jpeg_qual[ARRAY_SIZE(jpeg_qual) - 1], 1,
+			jpeg_qual[REG08_DEF >> 1]);
+	if (hdl->error) {
+		pr_err("Could not initialize controls\n");
+		return hdl->error;
+	}
+	v4l2_ctrl_cluster(3, &sd->gamma);
+	if (sd->sensor == SENSOR_HV7131R || sd->sensor == SENSOR_OV7620)
+		v4l2_ctrl_auto_cluster(2, &sd->autogain, 0, true);
+	return 0;
+}
+
+/* this function is called at probe and resume time */
+static int sd_init(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	struct cam *cam;
+	int sensor;
+	static const u8 mode_tb[SENSOR_MAX] = {
+		[SENSOR_ADCM2700] =	2,
+		[SENSOR_CS2102] =	1,
+		[SENSOR_CS2102K] =	1,
+		[SENSOR_GC0303] =	1,
+		[SENSOR_GC0305] =	1,
+		[SENSOR_HDCS2020] =	1,
+		[SENSOR_HV7131B] =	1,
+		[SENSOR_HV7131R] =	1,
+		[SENSOR_ICM105A] =	1,
+		[SENSOR_MC501CB] =	2,
+		[SENSOR_MT9V111_1] =	1,
+		[SENSOR_MT9V111_3] =	1,
+		[SENSOR_OV7620] =	2,
+		[SENSOR_OV7630C] =	1,
+		[SENSOR_PAS106] =	0,
+		[SENSOR_PAS202B] =	1,
+		[SENSOR_PB0330] =	1,
+		[SENSOR_PO2030] =	1,
+		[SENSOR_TAS5130C] =	1,
+	};
+
+	sensor = zcxx_probeSensor(gspca_dev);
+	if (sensor >= 0)
+		gspca_dbg(gspca_dev, D_PROBE, "probe sensor -> %04x\n", sensor);
+	if ((unsigned) force_sensor < SENSOR_MAX) {
+		sd->sensor = force_sensor;
+		gspca_dbg(gspca_dev, D_PROBE, "sensor forced to %d\n",
+			  force_sensor);
+	} else {
+		switch (sensor) {
+		case -1:
+			switch (sd->sensor) {
+			case SENSOR_MC501CB:
+				gspca_dbg(gspca_dev, D_PROBE, "Sensor MC501CB\n");
+				break;
+			case SENSOR_GC0303:
+				gspca_dbg(gspca_dev, D_PROBE, "Sensor GC0303\n");
+				break;
+			default:
+				pr_warn("Unknown sensor - set to TAS5130C\n");
+				sd->sensor = SENSOR_TAS5130C;
+			}
+			break;
+		case 0:
+			/* check the sensor type */
+			sensor = i2c_read(gspca_dev, 0x00);
+			gspca_dbg(gspca_dev, D_PROBE, "Sensor hv7131 type %d\n",
+				  sensor);
+			switch (sensor) {
+			case 0:			/* hv7131b */
+			case 1:			/* hv7131e */
+				gspca_dbg(gspca_dev, D_PROBE, "Find Sensor HV7131B\n");
+				sd->sensor = SENSOR_HV7131B;
+				break;
+			default:
+/*			case 2:			 * hv7131r */
+				gspca_dbg(gspca_dev, D_PROBE, "Find Sensor HV7131R\n");
+				sd->sensor = SENSOR_HV7131R;
+				break;
+			}
+			break;
+		case 0x02:
+			gspca_dbg(gspca_dev, D_PROBE, "Sensor TAS5130C\n");
+			sd->sensor = SENSOR_TAS5130C;
+			break;
+		case 0x04:
+			gspca_dbg(gspca_dev, D_PROBE, "Find Sensor CS2102\n");
+			sd->sensor = SENSOR_CS2102;
+			break;
+		case 0x08:
+			gspca_dbg(gspca_dev, D_PROBE, "Find Sensor HDCS2020\n");
+			sd->sensor = SENSOR_HDCS2020;
+			break;
+		case 0x0a:
+			gspca_dbg(gspca_dev, D_PROBE,
+				  "Find Sensor PB0330. Chip revision %x\n",
+				  sd->chip_revision);
+			sd->sensor = SENSOR_PB0330;
+			break;
+		case 0x0c:
+			gspca_dbg(gspca_dev, D_PROBE, "Find Sensor ICM105A\n");
+			sd->sensor = SENSOR_ICM105A;
+			break;
+		case 0x0e:
+			gspca_dbg(gspca_dev, D_PROBE, "Find Sensor PAS202B\n");
+			sd->sensor = SENSOR_PAS202B;
+			break;
+		case 0x0f:
+			gspca_dbg(gspca_dev, D_PROBE, "Find Sensor PAS106\n");
+			sd->sensor = SENSOR_PAS106;
+			break;
+		case 0x10:
+		case 0x12:
+			gspca_dbg(gspca_dev, D_PROBE, "Find Sensor TAS5130C\n");
+			sd->sensor = SENSOR_TAS5130C;
+			break;
+		case 0x11:
+			gspca_dbg(gspca_dev, D_PROBE, "Find Sensor HV7131R\n");
+			sd->sensor = SENSOR_HV7131R;
+			break;
+		case 0x13:
+		case 0x15:
+			gspca_dbg(gspca_dev, D_PROBE,
+				  "Sensor MT9V111. Chip revision %04x\n",
+				  sd->chip_revision);
+			sd->sensor = sd->bridge == BRIDGE_ZC301
+					? SENSOR_MT9V111_1
+					: SENSOR_MT9V111_3;
+			break;
+		case 0x14:
+			gspca_dbg(gspca_dev, D_PROBE,
+				  "Find Sensor CS2102K?. Chip revision %x\n",
+				  sd->chip_revision);
+			sd->sensor = SENSOR_CS2102K;
+			break;
+		case 0x16:
+			gspca_dbg(gspca_dev, D_PROBE, "Find Sensor ADCM2700\n");
+			sd->sensor = SENSOR_ADCM2700;
+			break;
+		case 0x29:
+			gspca_dbg(gspca_dev, D_PROBE, "Find Sensor GC0305\n");
+			sd->sensor = SENSOR_GC0305;
+			break;
+		case 0x0303:
+			gspca_dbg(gspca_dev, D_PROBE, "Sensor GC0303\n");
+			sd->sensor =  SENSOR_GC0303;
+			break;
+		case 0x2030:
+			gspca_dbg(gspca_dev, D_PROBE, "Find Sensor PO2030\n");
+			sd->sensor = SENSOR_PO2030;
+			break;
+		case 0x7620:
+			gspca_dbg(gspca_dev, D_PROBE, "Find Sensor OV7620\n");
+			sd->sensor = SENSOR_OV7620;
+			break;
+		case 0x7631:
+			gspca_dbg(gspca_dev, D_PROBE, "Find Sensor OV7630C\n");
+			sd->sensor = SENSOR_OV7630C;
+			break;
+		case 0x7648:
+			gspca_dbg(gspca_dev, D_PROBE, "Find Sensor OV7648\n");
+			sd->sensor = SENSOR_OV7620;	/* same sensor (?) */
+			break;
+		default:
+			pr_err("Unknown sensor %04x\n", sensor);
+			return -EINVAL;
+		}
+	}
+	if (sensor < 0x20) {
+		if (sensor == -1 || sensor == 0x10 || sensor == 0x12)
+			reg_w(gspca_dev, 0x02, 0x0010);
+		reg_r(gspca_dev, 0x0010);
+	}
+
+	cam = &gspca_dev->cam;
+	switch (mode_tb[sd->sensor]) {
+	case 0:
+		cam->cam_mode = sif_mode;
+		cam->nmodes = ARRAY_SIZE(sif_mode);
+		break;
+	case 1:
+		cam->cam_mode = vga_mode;
+		cam->nmodes = ARRAY_SIZE(vga_mode);
+		break;
+	default:
+/*	case 2: */
+		cam->cam_mode = broken_vga_mode;
+		cam->nmodes = ARRAY_SIZE(broken_vga_mode);
+		break;
+	}
+
+	/* switch off the led */
+	reg_w(gspca_dev, 0x01, 0x0000);
+	return gspca_dev->usb_err;
+}
+
+static int sd_pre_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	gspca_dev->cam.needs_full_bandwidth = (sd->reg08 >= 4) ? 1 : 0;
+	return 0;
+}
+
+static int sd_start(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+	int mode;
+	static const struct usb_action *init_tb[SENSOR_MAX][2] = {
+	[SENSOR_ADCM2700] =
+			{adcm2700_Initial, adcm2700_InitialScale},
+	[SENSOR_CS2102]	=
+			{cs2102_Initial, cs2102_InitialScale},
+	[SENSOR_CS2102K] =
+			{cs2102K_Initial, cs2102K_InitialScale},
+	[SENSOR_GC0303] =
+		{gc0303_Initial, gc0303_InitialScale},
+	[SENSOR_GC0305] =
+			{gc0305_Initial, gc0305_InitialScale},
+	[SENSOR_HDCS2020] =
+			{hdcs2020_Initial, hdcs2020_InitialScale},
+	[SENSOR_HV7131B] =
+			{hv7131b_Initial, hv7131b_InitialScale},
+	[SENSOR_HV7131R] =
+			{hv7131r_Initial, hv7131r_InitialScale},
+	[SENSOR_ICM105A] =
+			{icm105a_Initial, icm105a_InitialScale},
+	[SENSOR_MC501CB] =
+			{mc501cb_Initial, mc501cb_InitialScale},
+	[SENSOR_MT9V111_1] =
+			{mt9v111_1_Initial, mt9v111_1_InitialScale},
+	[SENSOR_MT9V111_3] =
+			{mt9v111_3_Initial, mt9v111_3_InitialScale},
+	[SENSOR_OV7620] =
+			{ov7620_Initial, ov7620_InitialScale},
+	[SENSOR_OV7630C] =
+			{ov7630c_Initial, ov7630c_InitialScale},
+	[SENSOR_PAS106] =
+			{pas106b_Initial, pas106b_InitialScale},
+	[SENSOR_PAS202B] =
+			{pas202b_Initial, pas202b_InitialScale},
+	[SENSOR_PB0330] =
+			{pb0330_Initial, pb0330_InitialScale},
+	[SENSOR_PO2030] =
+			{po2030_Initial, po2030_InitialScale},
+	[SENSOR_TAS5130C] =
+			{tas5130c_Initial, tas5130c_InitialScale},
+	};
+
+	/* create the JPEG header */
+	jpeg_define(sd->jpeg_hdr, gspca_dev->pixfmt.height,
+			gspca_dev->pixfmt.width,
+			0x21);		/* JPEG 422 */
+
+	mode = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].priv;
+	switch (sd->sensor) {
+	case SENSOR_HV7131R:
+		zcxx_probeSensor(gspca_dev);
+		break;
+	case SENSOR_PAS106:
+		usb_exchange(gspca_dev, pas106b_Initial_com);
+		break;
+	}
+	usb_exchange(gspca_dev, init_tb[sd->sensor][mode]);
+
+	switch (sd->sensor) {
+	case SENSOR_ADCM2700:
+	case SENSOR_GC0305:
+	case SENSOR_OV7620:
+	case SENSOR_PO2030:
+	case SENSOR_TAS5130C:
+	case SENSOR_GC0303:
+/*		msleep(100);			 * ?? */
+		reg_r(gspca_dev, 0x0002);	/* --> 0x40 */
+		reg_w(gspca_dev, 0x09, 0x01ad);	/* (from win traces) */
+		reg_w(gspca_dev, 0x15, 0x01ae);
+		if (sd->sensor == SENSOR_TAS5130C)
+			break;
+		reg_w(gspca_dev, 0x0d, 0x003a);
+		reg_w(gspca_dev, 0x02, 0x003b);
+		reg_w(gspca_dev, 0x00, 0x0038);
+		break;
+	case SENSOR_HV7131R:
+	case SENSOR_PAS202B:
+		reg_w(gspca_dev, 0x03, 0x003b);
+		reg_w(gspca_dev, 0x0c, 0x003a);
+		reg_w(gspca_dev, 0x0b, 0x0039);
+		if (sd->sensor == SENSOR_HV7131R)
+			reg_w(gspca_dev, 0x50, ZC3XX_R11D_GLOBALGAIN);
+		break;
+	}
+
+	setmatrix(gspca_dev);
+	switch (sd->sensor) {
+	case SENSOR_ADCM2700:
+	case SENSOR_OV7620:
+		reg_r(gspca_dev, 0x0008);
+		reg_w(gspca_dev, 0x00, 0x0008);
+		break;
+	case SENSOR_PAS202B:
+	case SENSOR_GC0305:
+	case SENSOR_HV7131R:
+	case SENSOR_TAS5130C:
+		reg_r(gspca_dev, 0x0008);
+		/* fall thru */
+	case SENSOR_PO2030:
+		reg_w(gspca_dev, 0x03, 0x0008);
+		break;
+	}
+	setsharpness(gspca_dev, v4l2_ctrl_g_ctrl(sd->sharpness));
+
+	/* set the gamma tables when not set */
+	switch (sd->sensor) {
+	case SENSOR_CS2102K:		/* gamma set in xxx_Initial */
+	case SENSOR_HDCS2020:
+	case SENSOR_OV7630C:
+		break;
+	default:
+		setcontrast(gspca_dev, v4l2_ctrl_g_ctrl(sd->gamma),
+				v4l2_ctrl_g_ctrl(sd->brightness),
+				v4l2_ctrl_g_ctrl(sd->contrast));
+		break;
+	}
+	setmatrix(gspca_dev);			/* one more time? */
+	switch (sd->sensor) {
+	case SENSOR_OV7620:
+	case SENSOR_PAS202B:
+		reg_r(gspca_dev, 0x0180);	/* from win */
+		reg_w(gspca_dev, 0x00, 0x0180);
+		break;
+	}
+	setquality(gspca_dev);
+	/* Start with BRC disabled, transfer_update will enable it if needed */
+	reg_w(gspca_dev, 0x00, 0x0007);
+	if (sd->plfreq)
+		setlightfreq(gspca_dev, v4l2_ctrl_g_ctrl(sd->plfreq));
+
+	switch (sd->sensor) {
+	case SENSOR_ADCM2700:
+		reg_w(gspca_dev, 0x09, 0x01ad);	/* (from win traces) */
+		reg_w(gspca_dev, 0x15, 0x01ae);
+		reg_w(gspca_dev, 0x02, 0x0180);
+						/* ms-win + */
+		reg_w(gspca_dev, 0x40, 0x0117);
+		break;
+	case SENSOR_HV7131R:
+		setexposure(gspca_dev, v4l2_ctrl_g_ctrl(sd->exposure));
+		reg_w(gspca_dev, 0x00, ZC3XX_R1A7_CALCGLOBALMEAN);
+		break;
+	case SENSOR_GC0305:
+	case SENSOR_TAS5130C:
+		reg_w(gspca_dev, 0x09, 0x01ad);	/* (from win traces) */
+		reg_w(gspca_dev, 0x15, 0x01ae);
+		/* fall thru */
+	case SENSOR_PAS202B:
+	case SENSOR_PO2030:
+/*		reg_w(gspca_dev, 0x40, ZC3XX_R117_GGAIN); in win traces */
+		reg_r(gspca_dev, 0x0180);
+		break;
+	case SENSOR_OV7620:
+		reg_w(gspca_dev, 0x09, 0x01ad);
+		reg_w(gspca_dev, 0x15, 0x01ae);
+		i2c_read(gspca_dev, 0x13);	/*fixme: returns 0xa3 */
+		i2c_write(gspca_dev, 0x13, 0xa3, 0x00);
+					/*fixme: returned value to send? */
+		reg_w(gspca_dev, 0x40, 0x0117);
+		reg_r(gspca_dev, 0x0180);
+		break;
+	}
+
+	setautogain(gspca_dev, v4l2_ctrl_g_ctrl(sd->autogain));
+
+	if (gspca_dev->usb_err < 0)
+		return gspca_dev->usb_err;
+
+	/* Start the transfer parameters update thread */
+	schedule_work(&sd->work);
+
+	return 0;
+}
+
+/* called on streamoff with alt==0 and on disconnect */
+/* the usb_lock is held at entry - restore on exit */
+static void sd_stop0(struct gspca_dev *gspca_dev)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	mutex_unlock(&gspca_dev->usb_lock);
+	flush_work(&sd->work);
+	mutex_lock(&gspca_dev->usb_lock);
+	if (!gspca_dev->present)
+		return;
+	send_unknown(gspca_dev, sd->sensor);
+}
+
+static void sd_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,
+			int len)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	/* check the JPEG end of frame */
+	if (len >= 3
+	 && data[len - 3] == 0xff && data[len - 2] == 0xd9) {
+/*fixme: what does the last byte mean?*/
+		gspca_frame_add(gspca_dev, LAST_PACKET,
+					data, len - 1);
+		return;
+	}
+
+	/* check the JPEG start of a frame */
+	if (data[0] == 0xff && data[1] == 0xd8) {
+		/* put the JPEG header in the new frame */
+		gspca_frame_add(gspca_dev, FIRST_PACKET,
+			sd->jpeg_hdr, JPEG_HDR_SZ);
+
+		/* remove the webcam's header:
+		 * ff d8 ff fe 00 0e 00 00 ss ss 00 01 ww ww hh hh pp pp
+		 *	- 'ss ss' is the frame sequence number (BE)
+		 *	- 'ww ww' and 'hh hh' are the window dimensions (BE)
+		 *	- 'pp pp' is the packet sequence number (BE)
+		 */
+		data += 18;
+		len -= 18;
+	}
+	gspca_frame_add(gspca_dev, INTER_PACKET, data, len);
+}
+
+static int sd_set_jcomp(struct gspca_dev *gspca_dev,
+			const struct v4l2_jpegcompression *jcomp)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	return v4l2_ctrl_s_ctrl(sd->jpegqual, jcomp->quality);
+}
+
+static int sd_get_jcomp(struct gspca_dev *gspca_dev,
+			struct v4l2_jpegcompression *jcomp)
+{
+	struct sd *sd = (struct sd *) gspca_dev;
+
+	memset(jcomp, 0, sizeof *jcomp);
+	jcomp->quality = v4l2_ctrl_g_ctrl(sd->jpegqual);
+	jcomp->jpeg_markers = V4L2_JPEG_MARKER_DHT
+			| V4L2_JPEG_MARKER_DQT;
+	return 0;
+}
+
+#if IS_ENABLED(CONFIG_INPUT)
+static int sd_int_pkt_scan(struct gspca_dev *gspca_dev,
+			u8 *data,		/* interrupt packet data */
+			int len)		/* interrupt packet length */
+{
+	if (len == 8 && data[4] == 1) {
+		input_report_key(gspca_dev->input_dev, KEY_CAMERA, 1);
+		input_sync(gspca_dev->input_dev);
+		input_report_key(gspca_dev->input_dev, KEY_CAMERA, 0);
+		input_sync(gspca_dev->input_dev);
+	}
+
+	return 0;
+}
+#endif
+
+static const struct sd_desc sd_desc = {
+	.name = KBUILD_MODNAME,
+	.config = sd_config,
+	.init = sd_init,
+	.init_controls = sd_init_controls,
+	.isoc_init = sd_pre_start,
+	.start = sd_start,
+	.stop0 = sd_stop0,
+	.pkt_scan = sd_pkt_scan,
+	.get_jcomp = sd_get_jcomp,
+	.set_jcomp = sd_set_jcomp,
+#if IS_ENABLED(CONFIG_INPUT)
+	.int_pkt_scan = sd_int_pkt_scan,
+#endif
+};
+
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x03f0, 0x1b07)},
+	{USB_DEVICE(0x041e, 0x041e)},
+	{USB_DEVICE(0x041e, 0x4017)},
+	{USB_DEVICE(0x041e, 0x401c), .driver_info = SENSOR_PAS106},
+	{USB_DEVICE(0x041e, 0x401e)},
+	{USB_DEVICE(0x041e, 0x401f)},
+	{USB_DEVICE(0x041e, 0x4022)},
+	{USB_DEVICE(0x041e, 0x4029)},
+	{USB_DEVICE(0x041e, 0x4034), .driver_info = SENSOR_PAS106},
+	{USB_DEVICE(0x041e, 0x4035), .driver_info = SENSOR_PAS106},
+	{USB_DEVICE(0x041e, 0x4036)},
+	{USB_DEVICE(0x041e, 0x403a)},
+	{USB_DEVICE(0x041e, 0x4051), .driver_info = SENSOR_GC0303},
+	{USB_DEVICE(0x041e, 0x4053), .driver_info = SENSOR_GC0303},
+	{USB_DEVICE(0x0458, 0x7007)},
+	{USB_DEVICE(0x0458, 0x700c)},
+	{USB_DEVICE(0x0458, 0x700f)},
+	{USB_DEVICE(0x0461, 0x0a00)},
+	{USB_DEVICE(0x046d, 0x089d), .driver_info = SENSOR_MC501CB},
+	{USB_DEVICE(0x046d, 0x08a0)},
+	{USB_DEVICE(0x046d, 0x08a1)},
+	{USB_DEVICE(0x046d, 0x08a2)},
+	{USB_DEVICE(0x046d, 0x08a3)},
+	{USB_DEVICE(0x046d, 0x08a6)},
+	{USB_DEVICE(0x046d, 0x08a7)},
+	{USB_DEVICE(0x046d, 0x08a9)},
+	{USB_DEVICE(0x046d, 0x08aa)},
+	{USB_DEVICE(0x046d, 0x08ac)},
+	{USB_DEVICE(0x046d, 0x08ad)},
+	{USB_DEVICE(0x046d, 0x08ae)},
+	{USB_DEVICE(0x046d, 0x08af)},
+	{USB_DEVICE(0x046d, 0x08b9)},
+	{USB_DEVICE(0x046d, 0x08d7)},
+	{USB_DEVICE(0x046d, 0x08d8)},
+	{USB_DEVICE(0x046d, 0x08d9)},
+	{USB_DEVICE(0x046d, 0x08da)},
+	{USB_DEVICE(0x046d, 0x08dd), .driver_info = SENSOR_MC501CB},
+	{USB_DEVICE(0x0471, 0x0325), .driver_info = SENSOR_PAS106},
+	{USB_DEVICE(0x0471, 0x0326), .driver_info = SENSOR_PAS106},
+	{USB_DEVICE(0x0471, 0x032d), .driver_info = SENSOR_PAS106},
+	{USB_DEVICE(0x0471, 0x032e), .driver_info = SENSOR_PAS106},
+	{USB_DEVICE(0x055f, 0xc005)},
+	{USB_DEVICE(0x055f, 0xd003)},
+	{USB_DEVICE(0x055f, 0xd004)},
+	{USB_DEVICE(0x0698, 0x2003)},
+	{USB_DEVICE(0x0ac8, 0x0301), .driver_info = SENSOR_PAS106},
+	{USB_DEVICE(0x0ac8, 0x0302), .driver_info = SENSOR_PAS106},
+	{USB_DEVICE(0x0ac8, 0x301b)},
+	{USB_DEVICE(0x0ac8, 0x303b)},
+	{USB_DEVICE(0x0ac8, 0x305b)},
+	{USB_DEVICE(0x0ac8, 0x307b)},
+	{USB_DEVICE(0x10fd, 0x0128)},
+	{USB_DEVICE(0x10fd, 0x804d)},
+	{USB_DEVICE(0x10fd, 0x8050)},
+	{}			/* end of entry */
+};
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* -- device connect -- */
+static int sd_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd),
+				THIS_MODULE);
+}
+
+/* USB driver */
+static struct usb_driver sd_driver = {
+	.name = KBUILD_MODNAME,
+	.id_table = device_table,
+	.probe = sd_probe,
+	.disconnect = gspca_disconnect,
+#ifdef CONFIG_PM
+	.suspend = gspca_suspend,
+	.resume = gspca_resume,
+	.reset_resume = gspca_resume,
+#endif
+};
+
+module_usb_driver(sd_driver);
+
+module_param(force_sensor, int, 0644);
+MODULE_PARM_DESC(force_sensor,
+	"Force sensor. Only for experts!!!");
diff --git a/drivers/media/usb/hackrf/Kconfig b/drivers/media/usb/hackrf/Kconfig
new file mode 100644
index 0000000..937e6f5
--- /dev/null
+++ b/drivers/media/usb/hackrf/Kconfig
@@ -0,0 +1,10 @@
+config USB_HACKRF
+	tristate "HackRF"
+	depends on VIDEO_V4L2
+	select VIDEOBUF2_VMALLOC
+	---help---
+	  This is a video4linux2 driver for HackRF SDR device.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called hackrf
+
diff --git a/drivers/media/usb/hackrf/Makefile b/drivers/media/usb/hackrf/Makefile
new file mode 100644
index 0000000..73064a2
--- /dev/null
+++ b/drivers/media/usb/hackrf/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_USB_HACKRF)              += hackrf.o
diff --git a/drivers/media/usb/hackrf/hackrf.c b/drivers/media/usb/hackrf/hackrf.c
new file mode 100644
index 0000000..34085a0
--- /dev/null
+++ b/drivers/media/usb/hackrf/hackrf.c
@@ -0,0 +1,1563 @@
+/*
+ * HackRF driver
+ *
+ * Copyright (C) 2014 Antti Palosaari <crope@iki.fi>
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-vmalloc.h>
+
+/*
+ * Used Avago MGA-81563 RF amplifier could be destroyed pretty easily with too
+ * strong signal or transmitting to bad antenna.
+ * Set RF gain control to 'grabbed' state by default for sure.
+ */
+static bool hackrf_enable_rf_gain_ctrl;
+module_param_named(enable_rf_gain_ctrl, hackrf_enable_rf_gain_ctrl, bool, 0644);
+MODULE_PARM_DESC(enable_rf_gain_ctrl, "enable RX/TX RF amplifier control (warn: could damage amplifier)");
+
+/* HackRF USB API commands (from HackRF Library) */
+enum {
+	CMD_SET_TRANSCEIVER_MODE           = 0x01,
+	CMD_SAMPLE_RATE_SET                = 0x06,
+	CMD_BASEBAND_FILTER_BANDWIDTH_SET  = 0x07,
+	CMD_BOARD_ID_READ                  = 0x0e,
+	CMD_VERSION_STRING_READ            = 0x0f,
+	CMD_SET_FREQ                       = 0x10,
+	CMD_AMP_ENABLE                     = 0x11,
+	CMD_SET_LNA_GAIN                   = 0x13,
+	CMD_SET_VGA_GAIN                   = 0x14,
+	CMD_SET_TXVGA_GAIN                 = 0x15,
+};
+
+/*
+ *       bEndpointAddress     0x81  EP 1 IN
+ *         Transfer Type            Bulk
+ *       wMaxPacketSize     0x0200  1x 512 bytes
+ */
+#define MAX_BULK_BUFS            (6)
+#define BULK_BUFFER_SIZE         (128 * 512)
+
+static const struct v4l2_frequency_band bands_adc_dac[] = {
+	{
+		.tuner = 0,
+		.type = V4L2_TUNER_SDR,
+		.index = 0,
+		.capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS,
+		.rangelow   =   200000,
+		.rangehigh  = 24000000,
+	},
+};
+
+static const struct v4l2_frequency_band bands_rx_tx[] = {
+	{
+		.tuner = 1,
+		.type = V4L2_TUNER_RF,
+		.index = 0,
+		.capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS,
+		.rangelow   =          1,
+		.rangehigh  = 4294967294LL, /* max u32, hw goes over 7GHz */
+	},
+};
+
+/* stream formats */
+struct hackrf_format {
+	u32	pixelformat;
+	u32	buffersize;
+};
+
+/* format descriptions for capture and preview */
+static struct hackrf_format formats[] = {
+	{
+		.pixelformat	= V4L2_SDR_FMT_CS8,
+		.buffersize	= BULK_BUFFER_SIZE,
+	},
+};
+
+static const unsigned int NUM_FORMATS = ARRAY_SIZE(formats);
+
+/* intermediate buffers with raw data from the USB device */
+struct hackrf_buffer {
+	struct vb2_v4l2_buffer vb;
+	struct list_head list;
+};
+
+struct hackrf_dev {
+#define USB_STATE_URB_BUF                1 /* XXX: set manually */
+#define RX_ON                            4
+#define TX_ON                            5
+#define RX_ADC_FREQUENCY                11
+#define TX_DAC_FREQUENCY                12
+#define RX_BANDWIDTH                    13
+#define TX_BANDWIDTH                    14
+#define RX_RF_FREQUENCY                 15
+#define TX_RF_FREQUENCY                 16
+#define RX_RF_GAIN                      17
+#define TX_RF_GAIN                      18
+#define RX_IF_GAIN                      19
+#define RX_LNA_GAIN                     20
+#define TX_LNA_GAIN                     21
+	unsigned long flags;
+
+	struct usb_interface *intf;
+	struct device *dev;
+	struct usb_device *udev;
+	struct video_device rx_vdev;
+	struct video_device tx_vdev;
+	struct v4l2_device v4l2_dev;
+
+	/* videobuf2 queue and queued buffers list */
+	struct vb2_queue rx_vb2_queue;
+	struct vb2_queue tx_vb2_queue;
+	struct list_head rx_buffer_list;
+	struct list_head tx_buffer_list;
+	spinlock_t buffer_list_lock; /* Protects buffer_list */
+	unsigned int sequence;	     /* Buffer sequence counter */
+	unsigned int vb_full;        /* vb is full and packets dropped */
+	unsigned int vb_empty;       /* vb is empty and packets dropped */
+
+	/* Note if taking both locks v4l2_lock must always be locked first! */
+	struct mutex v4l2_lock;      /* Protects everything else */
+	struct mutex vb_queue_lock;  /* Protects vb_queue */
+
+	struct urb     *urb_list[MAX_BULK_BUFS];
+	int            buf_num;
+	unsigned long  buf_size;
+	u8             *buf_list[MAX_BULK_BUFS];
+	dma_addr_t     dma_addr[MAX_BULK_BUFS];
+	int            urbs_initialized;
+	int            urbs_submitted;
+
+	/* USB control message buffer */
+	#define BUF_SIZE 24
+	u8 buf[BUF_SIZE];
+
+	/* Current configuration */
+	unsigned int f_adc;
+	unsigned int f_dac;
+	unsigned int f_rx;
+	unsigned int f_tx;
+	u32 pixelformat;
+	u32 buffersize;
+
+	/* Controls */
+	struct v4l2_ctrl_handler rx_ctrl_handler;
+	struct v4l2_ctrl *rx_bandwidth_auto;
+	struct v4l2_ctrl *rx_bandwidth;
+	struct v4l2_ctrl *rx_rf_gain;
+	struct v4l2_ctrl *rx_lna_gain;
+	struct v4l2_ctrl *rx_if_gain;
+	struct v4l2_ctrl_handler tx_ctrl_handler;
+	struct v4l2_ctrl *tx_bandwidth_auto;
+	struct v4l2_ctrl *tx_bandwidth;
+	struct v4l2_ctrl *tx_rf_gain;
+	struct v4l2_ctrl *tx_lna_gain;
+
+	/* Sample rate calc */
+	unsigned long jiffies_next;
+	unsigned int sample;
+	unsigned int sample_measured;
+};
+
+#define hackrf_dbg_usb_control_msg(_dev, _r, _t, _v, _i, _b, _l) { \
+	char *_direction; \
+	if (_t & USB_DIR_IN) \
+		_direction = "<<<"; \
+	else \
+		_direction = ">>>"; \
+	dev_dbg(_dev, "%02x %02x %02x %02x %02x %02x %02x %02x %s %*ph\n", \
+			_t, _r, _v & 0xff, _v >> 8, _i & 0xff, \
+			_i >> 8, _l & 0xff, _l >> 8, _direction, _l, _b); \
+}
+
+/* execute firmware command */
+static int hackrf_ctrl_msg(struct hackrf_dev *dev, u8 request, u16 value,
+		u16 index, u8 *data, u16 size)
+{
+	int ret;
+	unsigned int pipe;
+	u8 requesttype;
+
+	switch (request) {
+	case CMD_SET_TRANSCEIVER_MODE:
+	case CMD_SET_FREQ:
+	case CMD_AMP_ENABLE:
+	case CMD_SAMPLE_RATE_SET:
+	case CMD_BASEBAND_FILTER_BANDWIDTH_SET:
+		pipe = usb_sndctrlpipe(dev->udev, 0);
+		requesttype = (USB_TYPE_VENDOR | USB_DIR_OUT);
+		break;
+	case CMD_BOARD_ID_READ:
+	case CMD_VERSION_STRING_READ:
+	case CMD_SET_LNA_GAIN:
+	case CMD_SET_VGA_GAIN:
+	case CMD_SET_TXVGA_GAIN:
+		pipe = usb_rcvctrlpipe(dev->udev, 0);
+		requesttype = (USB_TYPE_VENDOR | USB_DIR_IN);
+		break;
+	default:
+		dev_err(dev->dev, "Unknown command %02x\n", request);
+		ret = -EINVAL;
+		goto err;
+	}
+
+	/* write request */
+	if (!(requesttype & USB_DIR_IN))
+		memcpy(dev->buf, data, size);
+
+	ret = usb_control_msg(dev->udev, pipe, request, requesttype, value,
+			index, dev->buf, size, 1000);
+	hackrf_dbg_usb_control_msg(dev->dev, request, requesttype, value,
+			index, dev->buf, size);
+	if (ret < 0) {
+		dev_err(dev->dev, "usb_control_msg() failed %d request %02x\n",
+				ret, request);
+		goto err;
+	}
+
+	/* read request */
+	if (requesttype & USB_DIR_IN)
+		memcpy(data, dev->buf, size);
+
+	return 0;
+err:
+	return ret;
+}
+
+static int hackrf_set_params(struct hackrf_dev *dev)
+{
+	struct usb_interface *intf = dev->intf;
+	int ret, i;
+	u8 buf[8], u8tmp;
+	unsigned int uitmp, uitmp1, uitmp2;
+	const bool rx = test_bit(RX_ON, &dev->flags);
+	const bool tx = test_bit(TX_ON, &dev->flags);
+	static const struct {
+		u32 freq;
+	} bandwidth_lut[] = {
+		{ 1750000}, /*  1.75 MHz */
+		{ 2500000}, /*  2.5  MHz */
+		{ 3500000}, /*  3.5  MHz */
+		{ 5000000}, /*  5    MHz */
+		{ 5500000}, /*  5.5  MHz */
+		{ 6000000}, /*  6    MHz */
+		{ 7000000}, /*  7    MHz */
+		{ 8000000}, /*  8    MHz */
+		{ 9000000}, /*  9    MHz */
+		{10000000}, /* 10    MHz */
+		{12000000}, /* 12    MHz */
+		{14000000}, /* 14    MHz */
+		{15000000}, /* 15    MHz */
+		{20000000}, /* 20    MHz */
+		{24000000}, /* 24    MHz */
+		{28000000}, /* 28    MHz */
+	};
+
+	if (!rx && !tx) {
+		dev_dbg(&intf->dev, "device is sleeping\n");
+		return 0;
+	}
+
+	/* ADC / DAC frequency */
+	if (rx && test_and_clear_bit(RX_ADC_FREQUENCY, &dev->flags)) {
+		dev_dbg(&intf->dev, "RX ADC frequency=%u Hz\n", dev->f_adc);
+		uitmp1 = dev->f_adc;
+		uitmp2 = 1;
+		set_bit(TX_DAC_FREQUENCY, &dev->flags);
+	} else if (tx && test_and_clear_bit(TX_DAC_FREQUENCY, &dev->flags)) {
+		dev_dbg(&intf->dev, "TX DAC frequency=%u Hz\n", dev->f_dac);
+		uitmp1 = dev->f_dac;
+		uitmp2 = 1;
+		set_bit(RX_ADC_FREQUENCY, &dev->flags);
+	} else {
+		uitmp1 = uitmp2 = 0;
+	}
+	if (uitmp1 || uitmp2) {
+		buf[0] = (uitmp1 >>  0) & 0xff;
+		buf[1] = (uitmp1 >>  8) & 0xff;
+		buf[2] = (uitmp1 >> 16) & 0xff;
+		buf[3] = (uitmp1 >> 24) & 0xff;
+		buf[4] = (uitmp2 >>  0) & 0xff;
+		buf[5] = (uitmp2 >>  8) & 0xff;
+		buf[6] = (uitmp2 >> 16) & 0xff;
+		buf[7] = (uitmp2 >> 24) & 0xff;
+		ret = hackrf_ctrl_msg(dev, CMD_SAMPLE_RATE_SET, 0, 0, buf, 8);
+		if (ret)
+			goto err;
+	}
+
+	/* bandwidth */
+	if (rx && test_and_clear_bit(RX_BANDWIDTH, &dev->flags)) {
+		if (dev->rx_bandwidth_auto->val == true)
+			uitmp = dev->f_adc;
+		else
+			uitmp = dev->rx_bandwidth->val;
+
+		for (i = 0; i < ARRAY_SIZE(bandwidth_lut); i++) {
+			if (uitmp <= bandwidth_lut[i].freq) {
+				uitmp = bandwidth_lut[i].freq;
+				break;
+			}
+		}
+		dev->rx_bandwidth->val = uitmp;
+		dev->rx_bandwidth->cur.val = uitmp;
+		dev_dbg(&intf->dev, "RX bandwidth selected=%u\n", uitmp);
+		set_bit(TX_BANDWIDTH, &dev->flags);
+	} else if (tx && test_and_clear_bit(TX_BANDWIDTH, &dev->flags)) {
+		if (dev->tx_bandwidth_auto->val == true)
+			uitmp = dev->f_dac;
+		else
+			uitmp = dev->tx_bandwidth->val;
+
+		for (i = 0; i < ARRAY_SIZE(bandwidth_lut); i++) {
+			if (uitmp <= bandwidth_lut[i].freq) {
+				uitmp = bandwidth_lut[i].freq;
+				break;
+			}
+		}
+		dev->tx_bandwidth->val = uitmp;
+		dev->tx_bandwidth->cur.val = uitmp;
+		dev_dbg(&intf->dev, "TX bandwidth selected=%u\n", uitmp);
+		set_bit(RX_BANDWIDTH, &dev->flags);
+	} else {
+		uitmp = 0;
+	}
+	if (uitmp) {
+		uitmp1 = uitmp2 = 0;
+		uitmp1 |= ((uitmp >>  0) & 0xff) << 0;
+		uitmp1 |= ((uitmp >>  8) & 0xff) << 8;
+		uitmp2 |= ((uitmp >> 16) & 0xff) << 0;
+		uitmp2 |= ((uitmp >> 24) & 0xff) << 8;
+		ret = hackrf_ctrl_msg(dev, CMD_BASEBAND_FILTER_BANDWIDTH_SET,
+				      uitmp1, uitmp2, NULL, 0);
+		if (ret)
+			goto err;
+	}
+
+	/* RX / TX RF frequency */
+	if (rx && test_and_clear_bit(RX_RF_FREQUENCY, &dev->flags)) {
+		dev_dbg(&intf->dev, "RX RF frequency=%u Hz\n", dev->f_rx);
+		uitmp1 = dev->f_rx / 1000000;
+		uitmp2 = dev->f_rx % 1000000;
+		set_bit(TX_RF_FREQUENCY, &dev->flags);
+	} else if (tx && test_and_clear_bit(TX_RF_FREQUENCY, &dev->flags)) {
+		dev_dbg(&intf->dev, "TX RF frequency=%u Hz\n", dev->f_tx);
+		uitmp1 = dev->f_tx / 1000000;
+		uitmp2 = dev->f_tx % 1000000;
+		set_bit(RX_RF_FREQUENCY, &dev->flags);
+	} else {
+		uitmp1 = uitmp2 = 0;
+	}
+	if (uitmp1 || uitmp2) {
+		buf[0] = (uitmp1 >>  0) & 0xff;
+		buf[1] = (uitmp1 >>  8) & 0xff;
+		buf[2] = (uitmp1 >> 16) & 0xff;
+		buf[3] = (uitmp1 >> 24) & 0xff;
+		buf[4] = (uitmp2 >>  0) & 0xff;
+		buf[5] = (uitmp2 >>  8) & 0xff;
+		buf[6] = (uitmp2 >> 16) & 0xff;
+		buf[7] = (uitmp2 >> 24) & 0xff;
+		ret = hackrf_ctrl_msg(dev, CMD_SET_FREQ, 0, 0, buf, 8);
+		if (ret)
+			goto err;
+	}
+
+	/* RX RF gain */
+	if (rx && test_and_clear_bit(RX_RF_GAIN, &dev->flags)) {
+		dev_dbg(&intf->dev, "RX RF gain val=%d->%d\n",
+			dev->rx_rf_gain->cur.val, dev->rx_rf_gain->val);
+
+		u8tmp = (dev->rx_rf_gain->val) ? 1 : 0;
+		ret = hackrf_ctrl_msg(dev, CMD_AMP_ENABLE, u8tmp, 0, NULL, 0);
+		if (ret)
+			goto err;
+		set_bit(TX_RF_GAIN, &dev->flags);
+	}
+
+	/* TX RF gain */
+	if (tx && test_and_clear_bit(TX_RF_GAIN, &dev->flags)) {
+		dev_dbg(&intf->dev, "TX RF gain val=%d->%d\n",
+			dev->tx_rf_gain->cur.val, dev->tx_rf_gain->val);
+
+		u8tmp = (dev->tx_rf_gain->val) ? 1 : 0;
+		ret = hackrf_ctrl_msg(dev, CMD_AMP_ENABLE, u8tmp, 0, NULL, 0);
+		if (ret)
+			goto err;
+		set_bit(RX_RF_GAIN, &dev->flags);
+	}
+
+	/* RX LNA gain */
+	if (rx && test_and_clear_bit(RX_LNA_GAIN, &dev->flags)) {
+		dev_dbg(dev->dev, "RX LNA gain val=%d->%d\n",
+			dev->rx_lna_gain->cur.val, dev->rx_lna_gain->val);
+
+		ret = hackrf_ctrl_msg(dev, CMD_SET_LNA_GAIN, 0,
+				      dev->rx_lna_gain->val, &u8tmp, 1);
+		if (ret)
+			goto err;
+	}
+
+	/* RX IF gain */
+	if (rx && test_and_clear_bit(RX_IF_GAIN, &dev->flags)) {
+		dev_dbg(&intf->dev, "IF gain val=%d->%d\n",
+			dev->rx_if_gain->cur.val, dev->rx_if_gain->val);
+
+		ret = hackrf_ctrl_msg(dev, CMD_SET_VGA_GAIN, 0,
+				      dev->rx_if_gain->val, &u8tmp, 1);
+		if (ret)
+			goto err;
+	}
+
+	/* TX LNA gain */
+	if (tx && test_and_clear_bit(TX_LNA_GAIN, &dev->flags)) {
+		dev_dbg(&intf->dev, "TX LNA gain val=%d->%d\n",
+			dev->tx_lna_gain->cur.val, dev->tx_lna_gain->val);
+
+		ret = hackrf_ctrl_msg(dev, CMD_SET_TXVGA_GAIN, 0,
+				      dev->tx_lna_gain->val, &u8tmp, 1);
+		if (ret)
+			goto err;
+	}
+
+	return 0;
+err:
+	dev_dbg(&intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+/* Private functions */
+static struct hackrf_buffer *hackrf_get_next_buffer(struct hackrf_dev *dev,
+						    struct list_head *buffer_list)
+{
+	unsigned long flags;
+	struct hackrf_buffer *buffer = NULL;
+
+	spin_lock_irqsave(&dev->buffer_list_lock, flags);
+	if (list_empty(buffer_list))
+		goto leave;
+
+	buffer = list_entry(buffer_list->next, struct hackrf_buffer, list);
+	list_del(&buffer->list);
+leave:
+	spin_unlock_irqrestore(&dev->buffer_list_lock, flags);
+	return buffer;
+}
+
+static void hackrf_copy_stream(struct hackrf_dev *dev, void *dst, void *src,
+			       unsigned int src_len)
+{
+	memcpy(dst, src, src_len);
+
+	/* calculate sample rate and output it in 10 seconds intervals */
+	if (unlikely(time_is_before_jiffies(dev->jiffies_next))) {
+		#define MSECS 10000UL
+		unsigned int msecs = jiffies_to_msecs(jiffies -
+				dev->jiffies_next + msecs_to_jiffies(MSECS));
+		unsigned int samples = dev->sample - dev->sample_measured;
+
+		dev->jiffies_next = jiffies + msecs_to_jiffies(MSECS);
+		dev->sample_measured = dev->sample;
+		dev_dbg(dev->dev, "slen=%u samples=%u msecs=%u sample rate=%lu\n",
+				src_len, samples, msecs,
+				samples * 1000UL / msecs);
+	}
+
+	/* total number of samples */
+	dev->sample += src_len / 2;
+}
+
+/*
+ * This gets called for the bulk stream pipe. This is done in interrupt
+ * time, so it has to be fast, not crash, and not stall. Neat.
+ */
+static void hackrf_urb_complete_in(struct urb *urb)
+{
+	struct hackrf_dev *dev = urb->context;
+	struct usb_interface *intf = dev->intf;
+	struct hackrf_buffer *buffer;
+	unsigned int len;
+
+	dev_dbg_ratelimited(&intf->dev, "status=%d length=%u/%u\n", urb->status,
+			    urb->actual_length, urb->transfer_buffer_length);
+
+	switch (urb->status) {
+	case 0:             /* success */
+	case -ETIMEDOUT:    /* NAK */
+		break;
+	case -ECONNRESET:   /* kill */
+	case -ENOENT:
+	case -ESHUTDOWN:
+		return;
+	default:            /* error */
+		dev_err_ratelimited(&intf->dev, "URB failed %d\n", urb->status);
+		goto exit_usb_submit_urb;
+	}
+
+	/* get buffer to write */
+	buffer = hackrf_get_next_buffer(dev, &dev->rx_buffer_list);
+	if (unlikely(buffer == NULL)) {
+		dev->vb_full++;
+		dev_notice_ratelimited(&intf->dev,
+				       "buffer is full - %u packets dropped\n",
+				       dev->vb_full);
+		goto exit_usb_submit_urb;
+	}
+
+	len = min_t(unsigned long, vb2_plane_size(&buffer->vb.vb2_buf, 0),
+		    urb->actual_length);
+	hackrf_copy_stream(dev, vb2_plane_vaddr(&buffer->vb.vb2_buf, 0),
+		    urb->transfer_buffer, len);
+	vb2_set_plane_payload(&buffer->vb.vb2_buf, 0, len);
+	buffer->vb.sequence = dev->sequence++;
+	buffer->vb.vb2_buf.timestamp = ktime_get_ns();
+	vb2_buffer_done(&buffer->vb.vb2_buf, VB2_BUF_STATE_DONE);
+exit_usb_submit_urb:
+	usb_submit_urb(urb, GFP_ATOMIC);
+}
+
+static void hackrf_urb_complete_out(struct urb *urb)
+{
+	struct hackrf_dev *dev = urb->context;
+	struct usb_interface *intf = dev->intf;
+	struct hackrf_buffer *buffer;
+	unsigned int len;
+
+	dev_dbg_ratelimited(&intf->dev, "status=%d length=%u/%u\n", urb->status,
+			    urb->actual_length, urb->transfer_buffer_length);
+
+	switch (urb->status) {
+	case 0:             /* success */
+	case -ETIMEDOUT:    /* NAK */
+		break;
+	case -ECONNRESET:   /* kill */
+	case -ENOENT:
+	case -ESHUTDOWN:
+		return;
+	default:            /* error */
+		dev_err_ratelimited(&intf->dev, "URB failed %d\n", urb->status);
+	}
+
+	/* get buffer to read */
+	buffer = hackrf_get_next_buffer(dev, &dev->tx_buffer_list);
+	if (unlikely(buffer == NULL)) {
+		dev->vb_empty++;
+		dev_notice_ratelimited(&intf->dev,
+				       "buffer is empty - %u packets dropped\n",
+				       dev->vb_empty);
+		urb->actual_length = 0;
+		goto exit_usb_submit_urb;
+	}
+
+	len = min_t(unsigned long, urb->transfer_buffer_length,
+		    vb2_get_plane_payload(&buffer->vb.vb2_buf, 0));
+	hackrf_copy_stream(dev, urb->transfer_buffer,
+			   vb2_plane_vaddr(&buffer->vb.vb2_buf, 0), len);
+	urb->actual_length = len;
+	buffer->vb.sequence = dev->sequence++;
+	buffer->vb.vb2_buf.timestamp = ktime_get_ns();
+	vb2_buffer_done(&buffer->vb.vb2_buf, VB2_BUF_STATE_DONE);
+exit_usb_submit_urb:
+	usb_submit_urb(urb, GFP_ATOMIC);
+}
+
+static int hackrf_kill_urbs(struct hackrf_dev *dev)
+{
+	int i;
+
+	for (i = dev->urbs_submitted - 1; i >= 0; i--) {
+		dev_dbg(dev->dev, "kill urb=%d\n", i);
+		/* stop the URB */
+		usb_kill_urb(dev->urb_list[i]);
+	}
+	dev->urbs_submitted = 0;
+
+	return 0;
+}
+
+static int hackrf_submit_urbs(struct hackrf_dev *dev)
+{
+	int i, ret;
+
+	for (i = 0; i < dev->urbs_initialized; i++) {
+		dev_dbg(dev->dev, "submit urb=%d\n", i);
+		ret = usb_submit_urb(dev->urb_list[i], GFP_KERNEL);
+		if (ret) {
+			dev_err(dev->dev, "Could not submit URB no. %d - get them all back\n",
+					i);
+			hackrf_kill_urbs(dev);
+			return ret;
+		}
+		dev->urbs_submitted++;
+	}
+
+	return 0;
+}
+
+static int hackrf_free_stream_bufs(struct hackrf_dev *dev)
+{
+	if (dev->flags & USB_STATE_URB_BUF) {
+		while (dev->buf_num) {
+			dev->buf_num--;
+			dev_dbg(dev->dev, "free buf=%d\n", dev->buf_num);
+			usb_free_coherent(dev->udev, dev->buf_size,
+					  dev->buf_list[dev->buf_num],
+					  dev->dma_addr[dev->buf_num]);
+		}
+	}
+	dev->flags &= ~USB_STATE_URB_BUF;
+
+	return 0;
+}
+
+static int hackrf_alloc_stream_bufs(struct hackrf_dev *dev)
+{
+	dev->buf_num = 0;
+	dev->buf_size = BULK_BUFFER_SIZE;
+
+	dev_dbg(dev->dev, "all in all I will use %u bytes for streaming\n",
+			MAX_BULK_BUFS * BULK_BUFFER_SIZE);
+
+	for (dev->buf_num = 0; dev->buf_num < MAX_BULK_BUFS; dev->buf_num++) {
+		dev->buf_list[dev->buf_num] = usb_alloc_coherent(dev->udev,
+				BULK_BUFFER_SIZE, GFP_KERNEL,
+				&dev->dma_addr[dev->buf_num]);
+		if (!dev->buf_list[dev->buf_num]) {
+			dev_dbg(dev->dev, "alloc buf=%d failed\n",
+					dev->buf_num);
+			hackrf_free_stream_bufs(dev);
+			return -ENOMEM;
+		}
+
+		dev_dbg(dev->dev, "alloc buf=%d %p (dma %llu)\n", dev->buf_num,
+				dev->buf_list[dev->buf_num],
+				(long long)dev->dma_addr[dev->buf_num]);
+		dev->flags |= USB_STATE_URB_BUF;
+	}
+
+	return 0;
+}
+
+static int hackrf_free_urbs(struct hackrf_dev *dev)
+{
+	int i;
+
+	hackrf_kill_urbs(dev);
+
+	for (i = dev->urbs_initialized - 1; i >= 0; i--) {
+		if (dev->urb_list[i]) {
+			dev_dbg(dev->dev, "free urb=%d\n", i);
+			/* free the URBs */
+			usb_free_urb(dev->urb_list[i]);
+		}
+	}
+	dev->urbs_initialized = 0;
+
+	return 0;
+}
+
+static int hackrf_alloc_urbs(struct hackrf_dev *dev, bool rcv)
+{
+	int i, j;
+	unsigned int pipe;
+	usb_complete_t complete;
+
+	if (rcv) {
+		pipe = usb_rcvbulkpipe(dev->udev, 0x81);
+		complete = &hackrf_urb_complete_in;
+	} else {
+		pipe = usb_sndbulkpipe(dev->udev, 0x02);
+		complete = &hackrf_urb_complete_out;
+	}
+
+	/* allocate the URBs */
+	for (i = 0; i < MAX_BULK_BUFS; i++) {
+		dev_dbg(dev->dev, "alloc urb=%d\n", i);
+		dev->urb_list[i] = usb_alloc_urb(0, GFP_KERNEL);
+		if (!dev->urb_list[i]) {
+			for (j = 0; j < i; j++)
+				usb_free_urb(dev->urb_list[j]);
+			return -ENOMEM;
+		}
+		usb_fill_bulk_urb(dev->urb_list[i],
+				dev->udev,
+				pipe,
+				dev->buf_list[i],
+				BULK_BUFFER_SIZE,
+				complete, dev);
+
+		dev->urb_list[i]->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+		dev->urb_list[i]->transfer_dma = dev->dma_addr[i];
+		dev->urbs_initialized++;
+	}
+
+	return 0;
+}
+
+/* The user yanked out the cable... */
+static void hackrf_disconnect(struct usb_interface *intf)
+{
+	struct v4l2_device *v = usb_get_intfdata(intf);
+	struct hackrf_dev *dev = container_of(v, struct hackrf_dev, v4l2_dev);
+
+	dev_dbg(dev->dev, "\n");
+
+	mutex_lock(&dev->vb_queue_lock);
+	mutex_lock(&dev->v4l2_lock);
+	/* No need to keep the urbs around after disconnection */
+	dev->udev = NULL;
+	v4l2_device_disconnect(&dev->v4l2_dev);
+	video_unregister_device(&dev->tx_vdev);
+	video_unregister_device(&dev->rx_vdev);
+	mutex_unlock(&dev->v4l2_lock);
+	mutex_unlock(&dev->vb_queue_lock);
+
+	v4l2_device_put(&dev->v4l2_dev);
+}
+
+/* Videobuf2 operations */
+static void hackrf_return_all_buffers(struct vb2_queue *vq,
+				      enum vb2_buffer_state state)
+{
+	struct hackrf_dev *dev = vb2_get_drv_priv(vq);
+	struct usb_interface *intf = dev->intf;
+	struct hackrf_buffer *buffer, *node;
+	struct list_head *buffer_list;
+	unsigned long flags;
+
+	dev_dbg(&intf->dev, "\n");
+
+	if (vq->type == V4L2_BUF_TYPE_SDR_CAPTURE)
+		buffer_list = &dev->rx_buffer_list;
+	else
+		buffer_list = &dev->tx_buffer_list;
+
+	spin_lock_irqsave(&dev->buffer_list_lock, flags);
+	list_for_each_entry_safe(buffer, node, buffer_list, list) {
+		dev_dbg(&intf->dev, "list_for_each_entry_safe\n");
+		vb2_buffer_done(&buffer->vb.vb2_buf, state);
+		list_del(&buffer->list);
+	}
+	spin_unlock_irqrestore(&dev->buffer_list_lock, flags);
+}
+
+static int hackrf_queue_setup(struct vb2_queue *vq,
+		unsigned int *nbuffers,
+		unsigned int *nplanes, unsigned int sizes[], struct device *alloc_devs[])
+{
+	struct hackrf_dev *dev = vb2_get_drv_priv(vq);
+
+	dev_dbg(dev->dev, "nbuffers=%d\n", *nbuffers);
+
+	/* Need at least 8 buffers */
+	if (vq->num_buffers + *nbuffers < 8)
+		*nbuffers = 8 - vq->num_buffers;
+	*nplanes = 1;
+	sizes[0] = PAGE_ALIGN(dev->buffersize);
+
+	dev_dbg(dev->dev, "nbuffers=%d sizes[0]=%d\n", *nbuffers, sizes[0]);
+	return 0;
+}
+
+static void hackrf_buf_queue(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct vb2_queue *vq = vb->vb2_queue;
+	struct hackrf_dev *dev = vb2_get_drv_priv(vq);
+	struct hackrf_buffer *buffer = container_of(vbuf, struct hackrf_buffer, vb);
+	struct list_head *buffer_list;
+	unsigned long flags;
+
+	dev_dbg_ratelimited(&dev->intf->dev, "\n");
+
+	if (vq->type == V4L2_BUF_TYPE_SDR_CAPTURE)
+		buffer_list = &dev->rx_buffer_list;
+	else
+		buffer_list = &dev->tx_buffer_list;
+
+	spin_lock_irqsave(&dev->buffer_list_lock, flags);
+	list_add_tail(&buffer->list, buffer_list);
+	spin_unlock_irqrestore(&dev->buffer_list_lock, flags);
+}
+
+static int hackrf_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+	struct hackrf_dev *dev = vb2_get_drv_priv(vq);
+	struct usb_interface *intf = dev->intf;
+	int ret;
+	unsigned int mode;
+
+	dev_dbg(&intf->dev, "count=%i\n", count);
+
+	mutex_lock(&dev->v4l2_lock);
+
+	/* Allow only RX or TX, not both same time */
+	if (vq->type == V4L2_BUF_TYPE_SDR_CAPTURE) {
+		if (test_bit(TX_ON, &dev->flags)) {
+			ret = -EBUSY;
+			goto err_hackrf_return_all_buffers;
+		}
+
+		mode = 1;
+		set_bit(RX_ON, &dev->flags);
+	} else {
+		if (test_bit(RX_ON, &dev->flags)) {
+			ret = -EBUSY;
+			goto err_hackrf_return_all_buffers;
+		}
+
+		mode = 2;
+		set_bit(TX_ON, &dev->flags);
+	}
+
+	dev->sequence = 0;
+
+	ret = hackrf_alloc_stream_bufs(dev);
+	if (ret)
+		goto err;
+
+	ret = hackrf_alloc_urbs(dev, (mode == 1));
+	if (ret)
+		goto err;
+
+	ret = hackrf_submit_urbs(dev);
+	if (ret)
+		goto err;
+
+	ret = hackrf_set_params(dev);
+	if (ret)
+		goto err;
+
+	/* start hardware streaming */
+	ret = hackrf_ctrl_msg(dev, CMD_SET_TRANSCEIVER_MODE, mode, 0, NULL, 0);
+	if (ret)
+		goto err;
+
+	mutex_unlock(&dev->v4l2_lock);
+
+	return 0;
+err:
+	hackrf_kill_urbs(dev);
+	hackrf_free_urbs(dev);
+	hackrf_free_stream_bufs(dev);
+	clear_bit(RX_ON, &dev->flags);
+	clear_bit(TX_ON, &dev->flags);
+err_hackrf_return_all_buffers:
+	hackrf_return_all_buffers(vq, VB2_BUF_STATE_QUEUED);
+	mutex_unlock(&dev->v4l2_lock);
+	dev_dbg(&intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static void hackrf_stop_streaming(struct vb2_queue *vq)
+{
+	struct hackrf_dev *dev = vb2_get_drv_priv(vq);
+	struct usb_interface *intf = dev->intf;
+
+	dev_dbg(&intf->dev, "\n");
+
+	mutex_lock(&dev->v4l2_lock);
+
+	/* stop hardware streaming */
+	hackrf_ctrl_msg(dev, CMD_SET_TRANSCEIVER_MODE, 0, 0, NULL, 0);
+
+	hackrf_kill_urbs(dev);
+	hackrf_free_urbs(dev);
+	hackrf_free_stream_bufs(dev);
+
+	hackrf_return_all_buffers(vq, VB2_BUF_STATE_ERROR);
+
+	if (vq->type == V4L2_BUF_TYPE_SDR_CAPTURE)
+		clear_bit(RX_ON, &dev->flags);
+	else
+		clear_bit(TX_ON, &dev->flags);
+
+	mutex_unlock(&dev->v4l2_lock);
+}
+
+static const struct vb2_ops hackrf_vb2_ops = {
+	.queue_setup            = hackrf_queue_setup,
+	.buf_queue              = hackrf_buf_queue,
+	.start_streaming        = hackrf_start_streaming,
+	.stop_streaming         = hackrf_stop_streaming,
+	.wait_prepare           = vb2_ops_wait_prepare,
+	.wait_finish            = vb2_ops_wait_finish,
+};
+
+static int hackrf_querycap(struct file *file, void *fh,
+		struct v4l2_capability *cap)
+{
+	struct hackrf_dev *dev = video_drvdata(file);
+	struct usb_interface *intf = dev->intf;
+	struct video_device *vdev = video_devdata(file);
+
+	dev_dbg(&intf->dev, "\n");
+
+	cap->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_READWRITE;
+	if (vdev->vfl_dir == VFL_DIR_RX)
+		cap->device_caps |= V4L2_CAP_SDR_CAPTURE | V4L2_CAP_TUNER;
+	else
+		cap->device_caps |= V4L2_CAP_SDR_OUTPUT | V4L2_CAP_MODULATOR;
+
+	cap->capabilities = V4L2_CAP_SDR_CAPTURE | V4L2_CAP_TUNER |
+			    V4L2_CAP_SDR_OUTPUT | V4L2_CAP_MODULATOR |
+			    V4L2_CAP_DEVICE_CAPS | cap->device_caps;
+	strlcpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver));
+	strlcpy(cap->card, dev->rx_vdev.name, sizeof(cap->card));
+	usb_make_path(dev->udev, cap->bus_info, sizeof(cap->bus_info));
+
+	return 0;
+}
+
+static int hackrf_s_fmt_sdr(struct file *file, void *priv,
+			    struct v4l2_format *f)
+{
+	struct hackrf_dev *dev = video_drvdata(file);
+	struct video_device *vdev = video_devdata(file);
+	struct vb2_queue *q;
+	int i;
+
+	dev_dbg(dev->dev, "pixelformat fourcc %4.4s\n",
+			(char *)&f->fmt.sdr.pixelformat);
+
+	if (vdev->vfl_dir == VFL_DIR_RX)
+		q = &dev->rx_vb2_queue;
+	else
+		q = &dev->tx_vb2_queue;
+
+	if (vb2_is_busy(q))
+		return -EBUSY;
+
+	memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved));
+	for (i = 0; i < NUM_FORMATS; i++) {
+		if (f->fmt.sdr.pixelformat == formats[i].pixelformat) {
+			dev->pixelformat = formats[i].pixelformat;
+			dev->buffersize = formats[i].buffersize;
+			f->fmt.sdr.buffersize = formats[i].buffersize;
+			return 0;
+		}
+	}
+
+	dev->pixelformat = formats[0].pixelformat;
+	dev->buffersize = formats[0].buffersize;
+	f->fmt.sdr.pixelformat = formats[0].pixelformat;
+	f->fmt.sdr.buffersize = formats[0].buffersize;
+
+	return 0;
+}
+
+static int hackrf_g_fmt_sdr(struct file *file, void *priv,
+			    struct v4l2_format *f)
+{
+	struct hackrf_dev *dev = video_drvdata(file);
+
+	dev_dbg(dev->dev, "pixelformat fourcc %4.4s\n",
+			(char *)&dev->pixelformat);
+
+	memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved));
+	f->fmt.sdr.pixelformat = dev->pixelformat;
+	f->fmt.sdr.buffersize = dev->buffersize;
+
+	return 0;
+}
+
+static int hackrf_try_fmt_sdr(struct file *file, void *priv,
+			      struct v4l2_format *f)
+{
+	struct hackrf_dev *dev = video_drvdata(file);
+	int i;
+
+	dev_dbg(dev->dev, "pixelformat fourcc %4.4s\n",
+			(char *)&f->fmt.sdr.pixelformat);
+
+	memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved));
+	for (i = 0; i < NUM_FORMATS; i++) {
+		if (formats[i].pixelformat == f->fmt.sdr.pixelformat) {
+			f->fmt.sdr.buffersize = formats[i].buffersize;
+			return 0;
+		}
+	}
+
+	f->fmt.sdr.pixelformat = formats[0].pixelformat;
+	f->fmt.sdr.buffersize = formats[0].buffersize;
+
+	return 0;
+}
+
+static int hackrf_enum_fmt_sdr(struct file *file, void *priv,
+			       struct v4l2_fmtdesc *f)
+{
+	struct hackrf_dev *dev = video_drvdata(file);
+
+	dev_dbg(dev->dev, "index=%d\n", f->index);
+
+	if (f->index >= NUM_FORMATS)
+		return -EINVAL;
+
+	f->pixelformat = formats[f->index].pixelformat;
+
+	return 0;
+}
+
+static int hackrf_s_tuner(struct file *file, void *priv,
+		const struct v4l2_tuner *v)
+{
+	struct hackrf_dev *dev = video_drvdata(file);
+	int ret;
+
+	dev_dbg(dev->dev, "index=%d\n", v->index);
+
+	if (v->index == 0)
+		ret = 0;
+	else if (v->index == 1)
+		ret = 0;
+	else
+		ret = -EINVAL;
+
+	return ret;
+}
+
+static int hackrf_g_tuner(struct file *file, void *priv, struct v4l2_tuner *v)
+{
+	struct hackrf_dev *dev = video_drvdata(file);
+	int ret;
+
+	dev_dbg(dev->dev, "index=%d\n", v->index);
+
+	if (v->index == 0) {
+		strlcpy(v->name, "HackRF ADC", sizeof(v->name));
+		v->type = V4L2_TUNER_SDR;
+		v->capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS;
+		v->rangelow  = bands_adc_dac[0].rangelow;
+		v->rangehigh = bands_adc_dac[0].rangehigh;
+		ret = 0;
+	} else if (v->index == 1) {
+		strlcpy(v->name, "HackRF RF", sizeof(v->name));
+		v->type = V4L2_TUNER_RF;
+		v->capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS;
+		v->rangelow  = bands_rx_tx[0].rangelow;
+		v->rangehigh = bands_rx_tx[0].rangehigh;
+		ret = 0;
+	} else {
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int hackrf_s_modulator(struct file *file, void *fh,
+			      const struct v4l2_modulator *a)
+{
+	struct hackrf_dev *dev = video_drvdata(file);
+
+	dev_dbg(dev->dev, "index=%d\n", a->index);
+
+	return a->index > 1 ? -EINVAL : 0;
+}
+
+static int hackrf_g_modulator(struct file *file, void *fh,
+			      struct v4l2_modulator *a)
+{
+	struct hackrf_dev *dev = video_drvdata(file);
+	int ret;
+
+	dev_dbg(dev->dev, "index=%d\n", a->index);
+
+	if (a->index == 0) {
+		strlcpy(a->name, "HackRF DAC", sizeof(a->name));
+		a->type = V4L2_TUNER_SDR;
+		a->capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS;
+		a->rangelow  = bands_adc_dac[0].rangelow;
+		a->rangehigh = bands_adc_dac[0].rangehigh;
+		ret = 0;
+	} else if (a->index == 1) {
+		strlcpy(a->name, "HackRF RF", sizeof(a->name));
+		a->type = V4L2_TUNER_RF;
+		a->capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS;
+		a->rangelow  = bands_rx_tx[0].rangelow;
+		a->rangehigh = bands_rx_tx[0].rangehigh;
+		ret = 0;
+	} else {
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int hackrf_s_frequency(struct file *file, void *priv,
+		const struct v4l2_frequency *f)
+{
+	struct hackrf_dev *dev = video_drvdata(file);
+	struct usb_interface *intf = dev->intf;
+	struct video_device *vdev = video_devdata(file);
+	int ret;
+	unsigned int uitmp;
+
+	dev_dbg(&intf->dev, "tuner=%d type=%d frequency=%u\n",
+			f->tuner, f->type, f->frequency);
+
+	if (f->tuner == 0) {
+		uitmp = clamp(f->frequency, bands_adc_dac[0].rangelow,
+			      bands_adc_dac[0].rangehigh);
+		if (vdev->vfl_dir == VFL_DIR_RX) {
+			dev->f_adc = uitmp;
+			set_bit(RX_ADC_FREQUENCY, &dev->flags);
+		} else {
+			dev->f_dac = uitmp;
+			set_bit(TX_DAC_FREQUENCY, &dev->flags);
+		}
+	} else if (f->tuner == 1) {
+		uitmp = clamp(f->frequency, bands_rx_tx[0].rangelow,
+			      bands_rx_tx[0].rangehigh);
+		if (vdev->vfl_dir == VFL_DIR_RX) {
+			dev->f_rx = uitmp;
+			set_bit(RX_RF_FREQUENCY, &dev->flags);
+		} else {
+			dev->f_tx = uitmp;
+			set_bit(TX_RF_FREQUENCY, &dev->flags);
+		}
+	} else {
+		ret = -EINVAL;
+		goto err;
+	}
+
+	ret = hackrf_set_params(dev);
+	if (ret)
+		goto err;
+
+	return 0;
+err:
+	dev_dbg(&intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static int hackrf_g_frequency(struct file *file, void *priv,
+		struct v4l2_frequency *f)
+{
+	struct hackrf_dev *dev = video_drvdata(file);
+	struct usb_interface *intf = dev->intf;
+	struct video_device *vdev = video_devdata(file);
+	int ret;
+
+	dev_dbg(dev->dev, "tuner=%d type=%d\n", f->tuner, f->type);
+
+	if (f->tuner == 0) {
+		f->type = V4L2_TUNER_SDR;
+		if (vdev->vfl_dir == VFL_DIR_RX)
+			f->frequency = dev->f_adc;
+		else
+			f->frequency = dev->f_dac;
+	} else if (f->tuner == 1) {
+		f->type = V4L2_TUNER_RF;
+		if (vdev->vfl_dir == VFL_DIR_RX)
+			f->frequency = dev->f_rx;
+		else
+			f->frequency = dev->f_tx;
+	} else {
+		ret = -EINVAL;
+		goto err;
+	}
+
+	return 0;
+err:
+	dev_dbg(&intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static int hackrf_enum_freq_bands(struct file *file, void *priv,
+		struct v4l2_frequency_band *band)
+{
+	struct hackrf_dev *dev = video_drvdata(file);
+	int ret;
+
+	dev_dbg(dev->dev, "tuner=%d type=%d index=%d\n",
+			band->tuner, band->type, band->index);
+
+	if (band->tuner == 0) {
+		if (band->index >= ARRAY_SIZE(bands_adc_dac)) {
+			ret = -EINVAL;
+		} else {
+			*band = bands_adc_dac[band->index];
+			ret = 0;
+		}
+	} else if (band->tuner == 1) {
+		if (band->index >= ARRAY_SIZE(bands_rx_tx)) {
+			ret = -EINVAL;
+		} else {
+			*band = bands_rx_tx[band->index];
+			ret = 0;
+		}
+	} else {
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static const struct v4l2_ioctl_ops hackrf_ioctl_ops = {
+	.vidioc_querycap          = hackrf_querycap,
+
+	.vidioc_s_fmt_sdr_cap     = hackrf_s_fmt_sdr,
+	.vidioc_g_fmt_sdr_cap     = hackrf_g_fmt_sdr,
+	.vidioc_enum_fmt_sdr_cap  = hackrf_enum_fmt_sdr,
+	.vidioc_try_fmt_sdr_cap   = hackrf_try_fmt_sdr,
+
+	.vidioc_s_fmt_sdr_out     = hackrf_s_fmt_sdr,
+	.vidioc_g_fmt_sdr_out     = hackrf_g_fmt_sdr,
+	.vidioc_enum_fmt_sdr_out  = hackrf_enum_fmt_sdr,
+	.vidioc_try_fmt_sdr_out   = hackrf_try_fmt_sdr,
+
+	.vidioc_reqbufs           = vb2_ioctl_reqbufs,
+	.vidioc_create_bufs       = vb2_ioctl_create_bufs,
+	.vidioc_prepare_buf       = vb2_ioctl_prepare_buf,
+	.vidioc_querybuf          = vb2_ioctl_querybuf,
+	.vidioc_qbuf              = vb2_ioctl_qbuf,
+	.vidioc_dqbuf             = vb2_ioctl_dqbuf,
+	.vidioc_expbuf            = vb2_ioctl_expbuf,
+
+	.vidioc_streamon          = vb2_ioctl_streamon,
+	.vidioc_streamoff         = vb2_ioctl_streamoff,
+
+	.vidioc_s_tuner           = hackrf_s_tuner,
+	.vidioc_g_tuner           = hackrf_g_tuner,
+
+	.vidioc_s_modulator       = hackrf_s_modulator,
+	.vidioc_g_modulator       = hackrf_g_modulator,
+
+	.vidioc_s_frequency       = hackrf_s_frequency,
+	.vidioc_g_frequency       = hackrf_g_frequency,
+	.vidioc_enum_freq_bands   = hackrf_enum_freq_bands,
+
+	.vidioc_subscribe_event   = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+	.vidioc_log_status        = v4l2_ctrl_log_status,
+};
+
+static const struct v4l2_file_operations hackrf_fops = {
+	.owner                    = THIS_MODULE,
+	.open                     = v4l2_fh_open,
+	.release                  = vb2_fop_release,
+	.read                     = vb2_fop_read,
+	.write                    = vb2_fop_write,
+	.poll                     = vb2_fop_poll,
+	.mmap                     = vb2_fop_mmap,
+	.unlocked_ioctl           = video_ioctl2,
+};
+
+static const struct video_device hackrf_template = {
+	.name                     = "HackRF One",
+	.release                  = video_device_release_empty,
+	.fops                     = &hackrf_fops,
+	.ioctl_ops                = &hackrf_ioctl_ops,
+};
+
+static void hackrf_video_release(struct v4l2_device *v)
+{
+	struct hackrf_dev *dev = container_of(v, struct hackrf_dev, v4l2_dev);
+
+	dev_dbg(dev->dev, "\n");
+
+	v4l2_ctrl_handler_free(&dev->rx_ctrl_handler);
+	v4l2_ctrl_handler_free(&dev->tx_ctrl_handler);
+	v4l2_device_unregister(&dev->v4l2_dev);
+	kfree(dev);
+}
+
+static int hackrf_s_ctrl_rx(struct v4l2_ctrl *ctrl)
+{
+	struct hackrf_dev *dev = container_of(ctrl->handler,
+			struct hackrf_dev, rx_ctrl_handler);
+	struct usb_interface *intf = dev->intf;
+	int ret;
+
+	switch (ctrl->id) {
+	case V4L2_CID_RF_TUNER_BANDWIDTH_AUTO:
+	case V4L2_CID_RF_TUNER_BANDWIDTH:
+		set_bit(RX_BANDWIDTH, &dev->flags);
+		break;
+	case  V4L2_CID_RF_TUNER_RF_GAIN:
+		set_bit(RX_RF_GAIN, &dev->flags);
+		break;
+	case  V4L2_CID_RF_TUNER_LNA_GAIN:
+		set_bit(RX_LNA_GAIN, &dev->flags);
+		break;
+	case  V4L2_CID_RF_TUNER_IF_GAIN:
+		set_bit(RX_IF_GAIN, &dev->flags);
+		break;
+	default:
+		dev_dbg(&intf->dev, "unknown ctrl: id=%d name=%s\n",
+			ctrl->id, ctrl->name);
+		ret = -EINVAL;
+		goto err;
+	}
+
+	ret = hackrf_set_params(dev);
+	if (ret)
+		goto err;
+
+	return 0;
+err:
+	dev_dbg(&intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static int hackrf_s_ctrl_tx(struct v4l2_ctrl *ctrl)
+{
+	struct hackrf_dev *dev = container_of(ctrl->handler,
+			struct hackrf_dev, tx_ctrl_handler);
+	struct usb_interface *intf = dev->intf;
+	int ret;
+
+	switch (ctrl->id) {
+	case V4L2_CID_RF_TUNER_BANDWIDTH_AUTO:
+	case V4L2_CID_RF_TUNER_BANDWIDTH:
+		set_bit(TX_BANDWIDTH, &dev->flags);
+		break;
+	case  V4L2_CID_RF_TUNER_LNA_GAIN:
+		set_bit(TX_LNA_GAIN, &dev->flags);
+		break;
+	case  V4L2_CID_RF_TUNER_RF_GAIN:
+		set_bit(TX_RF_GAIN, &dev->flags);
+		break;
+	default:
+		dev_dbg(&intf->dev, "unknown ctrl: id=%d name=%s\n",
+			ctrl->id, ctrl->name);
+		ret = -EINVAL;
+		goto err;
+	}
+
+	ret = hackrf_set_params(dev);
+	if (ret)
+		goto err;
+
+	return 0;
+err:
+	dev_dbg(&intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+static const struct v4l2_ctrl_ops hackrf_ctrl_ops_rx = {
+	.s_ctrl = hackrf_s_ctrl_rx,
+};
+
+static const struct v4l2_ctrl_ops hackrf_ctrl_ops_tx = {
+	.s_ctrl = hackrf_s_ctrl_tx,
+};
+
+static int hackrf_probe(struct usb_interface *intf,
+		const struct usb_device_id *id)
+{
+	struct hackrf_dev *dev;
+	int ret;
+	u8 u8tmp, buf[BUF_SIZE];
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	mutex_init(&dev->v4l2_lock);
+	mutex_init(&dev->vb_queue_lock);
+	spin_lock_init(&dev->buffer_list_lock);
+	INIT_LIST_HEAD(&dev->rx_buffer_list);
+	INIT_LIST_HEAD(&dev->tx_buffer_list);
+	dev->intf = intf;
+	dev->dev = &intf->dev;
+	dev->udev = interface_to_usbdev(intf);
+	dev->pixelformat = formats[0].pixelformat;
+	dev->buffersize = formats[0].buffersize;
+	dev->f_adc = bands_adc_dac[0].rangelow;
+	dev->f_dac = bands_adc_dac[0].rangelow;
+	dev->f_rx = bands_rx_tx[0].rangelow;
+	dev->f_tx = bands_rx_tx[0].rangelow;
+	set_bit(RX_ADC_FREQUENCY, &dev->flags);
+	set_bit(TX_DAC_FREQUENCY, &dev->flags);
+	set_bit(RX_RF_FREQUENCY, &dev->flags);
+	set_bit(TX_RF_FREQUENCY, &dev->flags);
+
+	/* Detect device */
+	ret = hackrf_ctrl_msg(dev, CMD_BOARD_ID_READ, 0, 0, &u8tmp, 1);
+	if (ret == 0)
+		ret = hackrf_ctrl_msg(dev, CMD_VERSION_STRING_READ, 0, 0,
+				buf, BUF_SIZE);
+	if (ret) {
+		dev_err(dev->dev, "Could not detect board\n");
+		goto err_kfree;
+	}
+
+	buf[BUF_SIZE - 1] = '\0';
+	dev_info(dev->dev, "Board ID: %02x\n", u8tmp);
+	dev_info(dev->dev, "Firmware version: %s\n", buf);
+
+	/* Init vb2 queue structure for receiver */
+	dev->rx_vb2_queue.type = V4L2_BUF_TYPE_SDR_CAPTURE;
+	dev->rx_vb2_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF |
+				     VB2_READ;
+	dev->rx_vb2_queue.ops = &hackrf_vb2_ops;
+	dev->rx_vb2_queue.mem_ops = &vb2_vmalloc_memops;
+	dev->rx_vb2_queue.drv_priv = dev;
+	dev->rx_vb2_queue.buf_struct_size = sizeof(struct hackrf_buffer);
+	dev->rx_vb2_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	ret = vb2_queue_init(&dev->rx_vb2_queue);
+	if (ret) {
+		dev_err(dev->dev, "Could not initialize rx vb2 queue\n");
+		goto err_kfree;
+	}
+
+	/* Init vb2 queue structure for transmitter */
+	dev->tx_vb2_queue.type = V4L2_BUF_TYPE_SDR_OUTPUT;
+	dev->tx_vb2_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF |
+				     VB2_WRITE;
+	dev->tx_vb2_queue.ops = &hackrf_vb2_ops;
+	dev->tx_vb2_queue.mem_ops = &vb2_vmalloc_memops;
+	dev->tx_vb2_queue.drv_priv = dev;
+	dev->tx_vb2_queue.buf_struct_size = sizeof(struct hackrf_buffer);
+	dev->tx_vb2_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	ret = vb2_queue_init(&dev->tx_vb2_queue);
+	if (ret) {
+		dev_err(dev->dev, "Could not initialize tx vb2 queue\n");
+		goto err_kfree;
+	}
+
+	/* Register controls for receiver */
+	v4l2_ctrl_handler_init(&dev->rx_ctrl_handler, 5);
+	dev->rx_bandwidth_auto = v4l2_ctrl_new_std(&dev->rx_ctrl_handler,
+		&hackrf_ctrl_ops_rx, V4L2_CID_RF_TUNER_BANDWIDTH_AUTO,
+		0, 1, 0, 1);
+	dev->rx_bandwidth = v4l2_ctrl_new_std(&dev->rx_ctrl_handler,
+		&hackrf_ctrl_ops_rx, V4L2_CID_RF_TUNER_BANDWIDTH,
+		1750000, 28000000, 50000, 1750000);
+	v4l2_ctrl_auto_cluster(2, &dev->rx_bandwidth_auto, 0, false);
+	dev->rx_rf_gain = v4l2_ctrl_new_std(&dev->rx_ctrl_handler,
+		&hackrf_ctrl_ops_rx, V4L2_CID_RF_TUNER_RF_GAIN, 0, 12, 12, 0);
+	dev->rx_lna_gain = v4l2_ctrl_new_std(&dev->rx_ctrl_handler,
+		&hackrf_ctrl_ops_rx, V4L2_CID_RF_TUNER_LNA_GAIN, 0, 40, 8, 0);
+	dev->rx_if_gain = v4l2_ctrl_new_std(&dev->rx_ctrl_handler,
+		&hackrf_ctrl_ops_rx, V4L2_CID_RF_TUNER_IF_GAIN, 0, 62, 2, 0);
+	if (dev->rx_ctrl_handler.error) {
+		ret = dev->rx_ctrl_handler.error;
+		dev_err(dev->dev, "Could not initialize controls\n");
+		goto err_v4l2_ctrl_handler_free_rx;
+	}
+	v4l2_ctrl_grab(dev->rx_rf_gain, !hackrf_enable_rf_gain_ctrl);
+	v4l2_ctrl_handler_setup(&dev->rx_ctrl_handler);
+
+	/* Register controls for transmitter */
+	v4l2_ctrl_handler_init(&dev->tx_ctrl_handler, 4);
+	dev->tx_bandwidth_auto = v4l2_ctrl_new_std(&dev->tx_ctrl_handler,
+		&hackrf_ctrl_ops_tx, V4L2_CID_RF_TUNER_BANDWIDTH_AUTO,
+		0, 1, 0, 1);
+	dev->tx_bandwidth = v4l2_ctrl_new_std(&dev->tx_ctrl_handler,
+		&hackrf_ctrl_ops_tx, V4L2_CID_RF_TUNER_BANDWIDTH,
+		1750000, 28000000, 50000, 1750000);
+	v4l2_ctrl_auto_cluster(2, &dev->tx_bandwidth_auto, 0, false);
+	dev->tx_lna_gain = v4l2_ctrl_new_std(&dev->tx_ctrl_handler,
+		&hackrf_ctrl_ops_tx, V4L2_CID_RF_TUNER_LNA_GAIN, 0, 47, 1, 0);
+	dev->tx_rf_gain = v4l2_ctrl_new_std(&dev->tx_ctrl_handler,
+		&hackrf_ctrl_ops_tx, V4L2_CID_RF_TUNER_RF_GAIN, 0, 15, 15, 0);
+	if (dev->tx_ctrl_handler.error) {
+		ret = dev->tx_ctrl_handler.error;
+		dev_err(dev->dev, "Could not initialize controls\n");
+		goto err_v4l2_ctrl_handler_free_tx;
+	}
+	v4l2_ctrl_grab(dev->tx_rf_gain, !hackrf_enable_rf_gain_ctrl);
+	v4l2_ctrl_handler_setup(&dev->tx_ctrl_handler);
+
+	/* Register the v4l2_device structure */
+	dev->v4l2_dev.release = hackrf_video_release;
+	ret = v4l2_device_register(&intf->dev, &dev->v4l2_dev);
+	if (ret) {
+		dev_err(dev->dev, "Failed to register v4l2-device (%d)\n", ret);
+		goto err_v4l2_ctrl_handler_free_tx;
+	}
+
+	/* Init video_device structure for receiver */
+	dev->rx_vdev = hackrf_template;
+	dev->rx_vdev.queue = &dev->rx_vb2_queue;
+	dev->rx_vdev.queue->lock = &dev->vb_queue_lock;
+	dev->rx_vdev.v4l2_dev = &dev->v4l2_dev;
+	dev->rx_vdev.ctrl_handler = &dev->rx_ctrl_handler;
+	dev->rx_vdev.lock = &dev->v4l2_lock;
+	dev->rx_vdev.vfl_dir = VFL_DIR_RX;
+	video_set_drvdata(&dev->rx_vdev, dev);
+	ret = video_register_device(&dev->rx_vdev, VFL_TYPE_SDR, -1);
+	if (ret) {
+		dev_err(dev->dev,
+			"Failed to register as video device (%d)\n", ret);
+		goto err_v4l2_device_unregister;
+	}
+	dev_info(dev->dev, "Registered as %s\n",
+		 video_device_node_name(&dev->rx_vdev));
+
+	/* Init video_device structure for transmitter */
+	dev->tx_vdev = hackrf_template;
+	dev->tx_vdev.queue = &dev->tx_vb2_queue;
+	dev->tx_vdev.queue->lock = &dev->vb_queue_lock;
+	dev->tx_vdev.v4l2_dev = &dev->v4l2_dev;
+	dev->tx_vdev.ctrl_handler = &dev->tx_ctrl_handler;
+	dev->tx_vdev.lock = &dev->v4l2_lock;
+	dev->tx_vdev.vfl_dir = VFL_DIR_TX;
+	video_set_drvdata(&dev->tx_vdev, dev);
+	ret = video_register_device(&dev->tx_vdev, VFL_TYPE_SDR, -1);
+	if (ret) {
+		dev_err(dev->dev,
+			"Failed to register as video device (%d)\n", ret);
+		goto err_video_unregister_device_rx;
+	}
+	dev_info(dev->dev, "Registered as %s\n",
+		 video_device_node_name(&dev->tx_vdev));
+
+	dev_notice(dev->dev, "SDR API is still slightly experimental and functionality changes may follow\n");
+	return 0;
+err_video_unregister_device_rx:
+	video_unregister_device(&dev->rx_vdev);
+err_v4l2_device_unregister:
+	v4l2_device_unregister(&dev->v4l2_dev);
+err_v4l2_ctrl_handler_free_tx:
+	v4l2_ctrl_handler_free(&dev->tx_ctrl_handler);
+err_v4l2_ctrl_handler_free_rx:
+	v4l2_ctrl_handler_free(&dev->rx_ctrl_handler);
+err_kfree:
+	kfree(dev);
+err:
+	dev_dbg(&intf->dev, "failed=%d\n", ret);
+	return ret;
+}
+
+/* USB device ID list */
+static const struct usb_device_id hackrf_id_table[] = {
+	{ USB_DEVICE(0x1d50, 0x6089) }, /* HackRF One */
+	{ }
+};
+MODULE_DEVICE_TABLE(usb, hackrf_id_table);
+
+/* USB subsystem interface */
+static struct usb_driver hackrf_driver = {
+	.name                     = KBUILD_MODNAME,
+	.probe                    = hackrf_probe,
+	.disconnect               = hackrf_disconnect,
+	.id_table                 = hackrf_id_table,
+};
+
+module_usb_driver(hackrf_driver);
+
+MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>");
+MODULE_DESCRIPTION("HackRF");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/hdpvr/Kconfig b/drivers/media/usb/hdpvr/Kconfig
new file mode 100644
index 0000000..d73d9a1
--- /dev/null
+++ b/drivers/media/usb/hdpvr/Kconfig
@@ -0,0 +1,10 @@
+
+config VIDEO_HDPVR
+	tristate "Hauppauge HD PVR support"
+	depends on VIDEO_DEV && VIDEO_V4L2
+	---help---
+	  This is a video4linux driver for Hauppauge's HD PVR USB device.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called hdpvr
+
diff --git a/drivers/media/usb/hdpvr/Makefile b/drivers/media/usb/hdpvr/Makefile
new file mode 100644
index 0000000..644dd99
--- /dev/null
+++ b/drivers/media/usb/hdpvr/Makefile
@@ -0,0 +1,3 @@
+hdpvr-objs	:= hdpvr-control.o hdpvr-core.o hdpvr-video.o hdpvr-i2c.o
+
+obj-$(CONFIG_VIDEO_HDPVR) += hdpvr.o
diff --git a/drivers/media/usb/hdpvr/hdpvr-control.c b/drivers/media/usb/hdpvr/hdpvr-control.c
new file mode 100644
index 0000000..6e86032
--- /dev/null
+++ b/drivers/media/usb/hdpvr/hdpvr-control.c
@@ -0,0 +1,181 @@
+/*
+ * Hauppauge HD PVR USB driver - video 4 linux 2 interface
+ *
+ * Copyright (C) 2008      Janne Grunau (j@jannau.net)
+ *
+ *	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, version 2.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/mutex.h>
+
+#include <linux/videodev2.h>
+
+#include <media/v4l2-common.h>
+
+#include "hdpvr.h"
+
+
+int hdpvr_config_call(struct hdpvr_device *dev, uint value, u8 valbuf)
+{
+	int ret;
+	char request_type = 0x38, snd_request = 0x01;
+
+	mutex_lock(&dev->usbc_mutex);
+	dev->usbc_buf[0] = valbuf;
+	ret = usb_control_msg(dev->udev,
+			      usb_sndctrlpipe(dev->udev, 0),
+			      snd_request, 0x00 | request_type,
+			      value, CTRL_DEFAULT_INDEX,
+			      dev->usbc_buf, 1, 10000);
+
+	mutex_unlock(&dev->usbc_mutex);
+	v4l2_dbg(MSG_INFO, hdpvr_debug, &dev->v4l2_dev,
+		 "config call request for value 0x%x returned %d\n", value,
+		 ret);
+
+	return ret < 0 ? ret : 0;
+}
+
+int get_video_info(struct hdpvr_device *dev, struct hdpvr_video_info *vidinf)
+{
+	int ret;
+
+	vidinf->valid = false;
+	mutex_lock(&dev->usbc_mutex);
+	ret = usb_control_msg(dev->udev,
+			      usb_rcvctrlpipe(dev->udev, 0),
+			      0x81, 0x80 | 0x38,
+			      0x1400, 0x0003,
+			      dev->usbc_buf, 5,
+			      1000);
+
+#ifdef HDPVR_DEBUG
+	if (hdpvr_debug & MSG_INFO)
+		v4l2_dbg(MSG_INFO, hdpvr_debug, &dev->v4l2_dev,
+			 "get video info returned: %d, %5ph\n", ret,
+			 dev->usbc_buf);
+#endif
+	mutex_unlock(&dev->usbc_mutex);
+
+	if (ret < 0)
+		return ret;
+
+	vidinf->width	= dev->usbc_buf[1] << 8 | dev->usbc_buf[0];
+	vidinf->height	= dev->usbc_buf[3] << 8 | dev->usbc_buf[2];
+	vidinf->fps	= dev->usbc_buf[4];
+	vidinf->valid   = vidinf->width && vidinf->height && vidinf->fps;
+
+	return 0;
+}
+
+int get_input_lines_info(struct hdpvr_device *dev)
+{
+	int ret, lines;
+
+	mutex_lock(&dev->usbc_mutex);
+	ret = usb_control_msg(dev->udev,
+			      usb_rcvctrlpipe(dev->udev, 0),
+			      0x81, 0x80 | 0x38,
+			      0x1800, 0x0003,
+			      dev->usbc_buf, 3,
+			      1000);
+
+#ifdef HDPVR_DEBUG
+	if (hdpvr_debug & MSG_INFO)
+		v4l2_dbg(MSG_INFO, hdpvr_debug, &dev->v4l2_dev,
+			 "get input lines info returned: %d, %3ph\n", ret,
+			 dev->usbc_buf);
+#else
+	(void)ret;	/* suppress compiler warning */
+#endif
+	lines = dev->usbc_buf[1] << 8 | dev->usbc_buf[0];
+	mutex_unlock(&dev->usbc_mutex);
+	return lines;
+}
+
+
+int hdpvr_set_bitrate(struct hdpvr_device *dev)
+{
+	int ret;
+
+	mutex_lock(&dev->usbc_mutex);
+	memset(dev->usbc_buf, 0, 4);
+	dev->usbc_buf[0] = dev->options.bitrate;
+	dev->usbc_buf[2] = dev->options.peak_bitrate;
+
+	ret = usb_control_msg(dev->udev,
+			      usb_sndctrlpipe(dev->udev, 0),
+			      0x01, 0x38, CTRL_BITRATE_VALUE,
+			      CTRL_DEFAULT_INDEX, dev->usbc_buf, 4, 1000);
+	mutex_unlock(&dev->usbc_mutex);
+
+	return ret;
+}
+
+int hdpvr_set_audio(struct hdpvr_device *dev, u8 input,
+		    enum v4l2_mpeg_audio_encoding codec)
+{
+	int ret = 0;
+
+	if (dev->flags & HDPVR_FLAG_AC3_CAP) {
+		mutex_lock(&dev->usbc_mutex);
+		memset(dev->usbc_buf, 0, 2);
+		dev->usbc_buf[0] = input;
+		if (codec == V4L2_MPEG_AUDIO_ENCODING_AAC)
+			dev->usbc_buf[1] = 0;
+		else if (codec == V4L2_MPEG_AUDIO_ENCODING_AC3)
+			dev->usbc_buf[1] = 1;
+		else {
+			mutex_unlock(&dev->usbc_mutex);
+			v4l2_err(&dev->v4l2_dev, "invalid audio codec %d\n",
+				 codec);
+			ret = -EINVAL;
+			goto error;
+		}
+
+		ret = usb_control_msg(dev->udev,
+				      usb_sndctrlpipe(dev->udev, 0),
+				      0x01, 0x38, CTRL_AUDIO_INPUT_VALUE,
+				      CTRL_DEFAULT_INDEX, dev->usbc_buf, 2,
+				      1000);
+		mutex_unlock(&dev->usbc_mutex);
+		if (ret == 2)
+			ret = 0;
+	} else
+		ret = hdpvr_config_call(dev, CTRL_AUDIO_INPUT_VALUE, input);
+error:
+	return ret;
+}
+
+int hdpvr_set_options(struct hdpvr_device *dev)
+{
+	hdpvr_config_call(dev, CTRL_VIDEO_STD_TYPE, dev->options.video_std);
+
+	hdpvr_config_call(dev, CTRL_VIDEO_INPUT_VALUE,
+			 dev->options.video_input+1);
+
+	hdpvr_set_audio(dev, dev->options.audio_input+1,
+		       dev->options.audio_codec);
+
+	hdpvr_set_bitrate(dev);
+	hdpvr_config_call(dev, CTRL_BITRATE_MODE_VALUE,
+			 dev->options.bitrate_mode);
+	hdpvr_config_call(dev, CTRL_GOP_MODE_VALUE, dev->options.gop_mode);
+
+	hdpvr_config_call(dev, CTRL_BRIGHTNESS, dev->options.brightness);
+	hdpvr_config_call(dev, CTRL_CONTRAST,   dev->options.contrast);
+	hdpvr_config_call(dev, CTRL_HUE,        dev->options.hue);
+	hdpvr_config_call(dev, CTRL_SATURATION, dev->options.saturation);
+	hdpvr_config_call(dev, CTRL_SHARPNESS,  dev->options.sharpness);
+
+	return 0;
+}
diff --git a/drivers/media/usb/hdpvr/hdpvr-core.c b/drivers/media/usb/hdpvr/hdpvr-core.c
new file mode 100644
index 0000000..29ac7fc
--- /dev/null
+++ b/drivers/media/usb/hdpvr/hdpvr-core.c
@@ -0,0 +1,442 @@
+/*
+ * Hauppauge HD PVR USB driver
+ *
+ * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com)
+ * Copyright (C) 2008      Janne Grunau (j@jannau.net)
+ * Copyright (C) 2008      John Poet
+ *
+ *	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, version 2.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/uaccess.h>
+#include <linux/atomic.h>
+#include <linux/usb.h>
+#include <linux/mutex.h>
+#include <linux/i2c.h>
+
+#include <linux/videodev2.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-common.h>
+
+#include "hdpvr.h"
+
+static int video_nr[HDPVR_MAX] = {[0 ... (HDPVR_MAX - 1)] = UNSET};
+module_param_array(video_nr, int, NULL, 0);
+MODULE_PARM_DESC(video_nr, "video device number (-1=Auto)");
+
+/* holds the number of currently registered devices */
+static atomic_t dev_nr = ATOMIC_INIT(-1);
+
+int hdpvr_debug;
+module_param(hdpvr_debug, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(hdpvr_debug, "enable debugging output");
+
+static uint default_video_input = HDPVR_VIDEO_INPUTS;
+module_param(default_video_input, uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(default_video_input, "default video input: 0=Component / 1=S-Video / 2=Composite");
+
+static uint default_audio_input = HDPVR_AUDIO_INPUTS;
+module_param(default_audio_input, uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(default_audio_input, "default audio input: 0=RCA back / 1=RCA front / 2=S/PDIF");
+
+static bool boost_audio;
+module_param(boost_audio, bool, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(boost_audio, "boost the audio signal");
+
+
+/* table of devices that work with this driver */
+static const struct usb_device_id hdpvr_table[] = {
+	{ USB_DEVICE(HD_PVR_VENDOR_ID, HD_PVR_PRODUCT_ID) },
+	{ USB_DEVICE(HD_PVR_VENDOR_ID, HD_PVR_PRODUCT_ID1) },
+	{ USB_DEVICE(HD_PVR_VENDOR_ID, HD_PVR_PRODUCT_ID2) },
+	{ USB_DEVICE(HD_PVR_VENDOR_ID, HD_PVR_PRODUCT_ID3) },
+	{ USB_DEVICE(HD_PVR_VENDOR_ID, HD_PVR_PRODUCT_ID4) },
+	{ }					/* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, hdpvr_table);
+
+
+void hdpvr_delete(struct hdpvr_device *dev)
+{
+	hdpvr_free_buffers(dev);
+	usb_put_dev(dev->udev);
+}
+
+static void challenge(u8 *bytes)
+{
+	__le64 *i64P;
+	u64 tmp64;
+	uint i, idx;
+
+	for (idx = 0; idx < 32; ++idx) {
+
+		if (idx & 0x3)
+			bytes[(idx >> 3) + 3] = bytes[(idx >> 2) & 0x3];
+
+		switch (idx & 0x3) {
+		case 0x3:
+			bytes[2] += bytes[3] * 4 + bytes[4] + bytes[5];
+			bytes[4] += bytes[(idx & 0x1) * 2] * 9 + 9;
+			break;
+		case 0x1:
+			bytes[0] *= 8;
+			bytes[0] += 7*idx + 4;
+			bytes[6] += bytes[3] * 3;
+			break;
+		case 0x0:
+			bytes[3 - (idx >> 3)] = bytes[idx >> 2];
+			bytes[5] += bytes[6] * 3;
+			for (i = 0; i < 3; i++)
+				bytes[3] *= bytes[3] + 1;
+			break;
+		case 0x2:
+			for (i = 0; i < 3; i++)
+				bytes[1] *= bytes[6] + 1;
+			for (i = 0; i < 3; i++) {
+				i64P = (__le64 *)bytes;
+				tmp64 = le64_to_cpup(i64P);
+				tmp64 = tmp64 + (tmp64 << (bytes[7] & 0x0f));
+				*i64P = cpu_to_le64(tmp64);
+			}
+			break;
+		}
+	}
+}
+
+/* try to init the device like the windows driver */
+static int device_authorization(struct hdpvr_device *dev)
+{
+
+	int ret, retval = -ENOMEM;
+	char request_type = 0x38, rcv_request = 0x81;
+	char *response;
+
+	mutex_lock(&dev->usbc_mutex);
+	ret = usb_control_msg(dev->udev,
+			      usb_rcvctrlpipe(dev->udev, 0),
+			      rcv_request, 0x80 | request_type,
+			      0x0400, 0x0003,
+			      dev->usbc_buf, 46,
+			      10000);
+	if (ret != 46) {
+		v4l2_err(&dev->v4l2_dev,
+			 "unexpected answer of status request, len %d\n", ret);
+		goto unlock;
+	}
+#ifdef HDPVR_DEBUG
+	else {
+		v4l2_dbg(MSG_INFO, hdpvr_debug, &dev->v4l2_dev,
+			 "Status request returned, len %d: %46ph\n",
+			 ret, dev->usbc_buf);
+	}
+#endif
+
+	dev->fw_ver = dev->usbc_buf[1];
+
+	v4l2_info(&dev->v4l2_dev, "firmware version 0x%x dated %s\n",
+			  dev->fw_ver, &dev->usbc_buf[2]);
+
+	if (dev->fw_ver > 0x15) {
+		dev->options.brightness	= 0x80;
+		dev->options.contrast	= 0x40;
+		dev->options.hue	= 0xf;
+		dev->options.saturation	= 0x40;
+		dev->options.sharpness	= 0x80;
+	}
+
+	switch (dev->fw_ver) {
+	case HDPVR_FIRMWARE_VERSION:
+		dev->flags &= ~HDPVR_FLAG_AC3_CAP;
+		break;
+	case HDPVR_FIRMWARE_VERSION_AC3:
+	case HDPVR_FIRMWARE_VERSION_0X12:
+	case HDPVR_FIRMWARE_VERSION_0X15:
+	case HDPVR_FIRMWARE_VERSION_0X1E:
+		dev->flags |= HDPVR_FLAG_AC3_CAP;
+		break;
+	default:
+		v4l2_info(&dev->v4l2_dev, "untested firmware, the driver might not work.\n");
+		if (dev->fw_ver >= HDPVR_FIRMWARE_VERSION_AC3)
+			dev->flags |= HDPVR_FLAG_AC3_CAP;
+		else
+			dev->flags &= ~HDPVR_FLAG_AC3_CAP;
+	}
+
+	response = dev->usbc_buf+38;
+#ifdef HDPVR_DEBUG
+	v4l2_dbg(MSG_INFO, hdpvr_debug, &dev->v4l2_dev, "challenge: %8ph\n",
+		 response);
+#endif
+	challenge(response);
+#ifdef HDPVR_DEBUG
+	v4l2_dbg(MSG_INFO, hdpvr_debug, &dev->v4l2_dev, " response: %8ph\n",
+		 response);
+#endif
+
+	msleep(100);
+	ret = usb_control_msg(dev->udev,
+			      usb_sndctrlpipe(dev->udev, 0),
+			      0xd1, 0x00 | request_type,
+			      0x0000, 0x0000,
+			      response, 8,
+			      10000);
+	v4l2_dbg(MSG_INFO, hdpvr_debug, &dev->v4l2_dev,
+		 "magic request returned %d\n", ret);
+
+	retval = ret != 8;
+unlock:
+	mutex_unlock(&dev->usbc_mutex);
+	return retval;
+}
+
+static int hdpvr_device_init(struct hdpvr_device *dev)
+{
+	int ret;
+	u8 *buf;
+
+	if (device_authorization(dev))
+		return -EACCES;
+
+	/* default options for init */
+	hdpvr_set_options(dev);
+
+	/* set filter options */
+	mutex_lock(&dev->usbc_mutex);
+	buf = dev->usbc_buf;
+	buf[0] = 0x03; buf[1] = 0x03; buf[2] = 0x00; buf[3] = 0x00;
+	ret = usb_control_msg(dev->udev,
+			      usb_sndctrlpipe(dev->udev, 0),
+			      0x01, 0x38,
+			      CTRL_LOW_PASS_FILTER_VALUE, CTRL_DEFAULT_INDEX,
+			      buf, 4,
+			      1000);
+	v4l2_dbg(MSG_INFO, hdpvr_debug, &dev->v4l2_dev,
+		 "control request returned %d\n", ret);
+	mutex_unlock(&dev->usbc_mutex);
+
+	/* enable fan and bling leds */
+	mutex_lock(&dev->usbc_mutex);
+	buf[0] = 0x1;
+	ret = usb_control_msg(dev->udev,
+			      usb_sndctrlpipe(dev->udev, 0),
+			      0xd4, 0x38, 0, 0, buf, 1,
+			      1000);
+	v4l2_dbg(MSG_INFO, hdpvr_debug, &dev->v4l2_dev,
+		 "control request returned %d\n", ret);
+
+	/* boost analog audio */
+	buf[0] = boost_audio;
+	ret = usb_control_msg(dev->udev,
+			      usb_sndctrlpipe(dev->udev, 0),
+			      0xd5, 0x38, 0, 0, buf, 1,
+			      1000);
+	v4l2_dbg(MSG_INFO, hdpvr_debug, &dev->v4l2_dev,
+		 "control request returned %d\n", ret);
+	mutex_unlock(&dev->usbc_mutex);
+
+	dev->status = STATUS_IDLE;
+	return 0;
+}
+
+static const struct hdpvr_options hdpvr_default_options = {
+	.video_std	= HDPVR_60HZ,
+	.video_input	= HDPVR_COMPONENT,
+	.audio_input	= HDPVR_RCA_BACK,
+	.bitrate	= 65, /* 6 mbps */
+	.peak_bitrate	= 90, /* 9 mbps */
+	.bitrate_mode	= HDPVR_CONSTANT,
+	.gop_mode	= HDPVR_SIMPLE_IDR_GOP,
+	.audio_codec	= V4L2_MPEG_AUDIO_ENCODING_AAC,
+	/* original picture controls for firmware version <= 0x15 */
+	/* updated in device_authorization() for newer firmware */
+	.brightness	= 0x86,
+	.contrast	= 0x80,
+	.hue		= 0x80,
+	.saturation	= 0x80,
+	.sharpness	= 0x80,
+};
+
+static int hdpvr_probe(struct usb_interface *interface,
+		       const struct usb_device_id *id)
+{
+	struct hdpvr_device *dev;
+	struct usb_host_interface *iface_desc;
+	struct usb_endpoint_descriptor *endpoint;
+#if IS_ENABLED(CONFIG_I2C)
+	struct i2c_client *client;
+#endif
+	size_t buffer_size;
+	int i;
+	int retval = -ENOMEM;
+
+	/* allocate memory for our device state and initialize it */
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev) {
+		dev_err(&interface->dev, "Out of memory\n");
+		goto error;
+	}
+
+	/* init video transfer queues first of all */
+	/* to prevent oops in hdpvr_delete() on error paths */
+	INIT_LIST_HEAD(&dev->free_buff_list);
+	INIT_LIST_HEAD(&dev->rec_buff_list);
+
+	/* register v4l2_device early so it can be used for printks */
+	if (v4l2_device_register(&interface->dev, &dev->v4l2_dev)) {
+		dev_err(&interface->dev, "v4l2_device_register failed\n");
+		goto error_free_dev;
+	}
+
+	mutex_init(&dev->io_mutex);
+	mutex_init(&dev->i2c_mutex);
+	mutex_init(&dev->usbc_mutex);
+	dev->usbc_buf = kmalloc(64, GFP_KERNEL);
+	if (!dev->usbc_buf) {
+		v4l2_err(&dev->v4l2_dev, "Out of memory\n");
+		goto error_v4l2_unregister;
+	}
+
+	init_waitqueue_head(&dev->wait_buffer);
+	init_waitqueue_head(&dev->wait_data);
+
+	dev->options = hdpvr_default_options;
+
+	if (default_video_input < HDPVR_VIDEO_INPUTS)
+		dev->options.video_input = default_video_input;
+
+	if (default_audio_input < HDPVR_AUDIO_INPUTS) {
+		dev->options.audio_input = default_audio_input;
+		if (default_audio_input == HDPVR_SPDIF)
+			dev->options.audio_codec =
+				V4L2_MPEG_AUDIO_ENCODING_AC3;
+	}
+
+	dev->udev = usb_get_dev(interface_to_usbdev(interface));
+
+	/* set up the endpoint information */
+	/* use only the first bulk-in and bulk-out endpoints */
+	iface_desc = interface->cur_altsetting;
+	for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
+		endpoint = &iface_desc->endpoint[i].desc;
+
+		if (!dev->bulk_in_endpointAddr &&
+		    usb_endpoint_is_bulk_in(endpoint)) {
+			/* USB interface description is buggy, reported max
+			 * packet size is 512 bytes, windows driver uses 8192 */
+			buffer_size = 8192;
+			dev->bulk_in_size = buffer_size;
+			dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
+		}
+
+	}
+	if (!dev->bulk_in_endpointAddr) {
+		v4l2_err(&dev->v4l2_dev, "Could not find bulk-in endpoint\n");
+		goto error_put_usb;
+	}
+
+	/* init the device */
+	if (hdpvr_device_init(dev)) {
+		v4l2_err(&dev->v4l2_dev, "device init failed\n");
+		goto error_put_usb;
+	}
+
+	mutex_lock(&dev->io_mutex);
+	if (hdpvr_alloc_buffers(dev, NUM_BUFFERS)) {
+		mutex_unlock(&dev->io_mutex);
+		v4l2_err(&dev->v4l2_dev,
+			 "allocating transfer buffers failed\n");
+		goto error_put_usb;
+	}
+	mutex_unlock(&dev->io_mutex);
+
+#if IS_ENABLED(CONFIG_I2C)
+	retval = hdpvr_register_i2c_adapter(dev);
+	if (retval < 0) {
+		v4l2_err(&dev->v4l2_dev, "i2c adapter register failed\n");
+		goto error_free_buffers;
+	}
+
+	client = hdpvr_register_ir_i2c(dev);
+	if (!client) {
+		v4l2_err(&dev->v4l2_dev, "i2c IR device register failed\n");
+		retval = -ENODEV;
+		goto reg_fail;
+	}
+#endif
+
+	retval = hdpvr_register_videodev(dev, &interface->dev,
+				    video_nr[atomic_inc_return(&dev_nr)]);
+	if (retval < 0) {
+		v4l2_err(&dev->v4l2_dev, "registering videodev failed\n");
+		goto reg_fail;
+	}
+
+	/* let the user know what node this device is now attached to */
+	v4l2_info(&dev->v4l2_dev, "device now attached to %s\n",
+		  video_device_node_name(&dev->video_dev));
+	return 0;
+
+reg_fail:
+#if IS_ENABLED(CONFIG_I2C)
+	i2c_del_adapter(&dev->i2c_adapter);
+error_free_buffers:
+#endif
+	hdpvr_free_buffers(dev);
+error_put_usb:
+	usb_put_dev(dev->udev);
+	kfree(dev->usbc_buf);
+error_v4l2_unregister:
+	v4l2_device_unregister(&dev->v4l2_dev);
+error_free_dev:
+	kfree(dev);
+error:
+	return retval;
+}
+
+static void hdpvr_disconnect(struct usb_interface *interface)
+{
+	struct hdpvr_device *dev = to_hdpvr_dev(usb_get_intfdata(interface));
+
+	v4l2_info(&dev->v4l2_dev, "device %s disconnected\n",
+		  video_device_node_name(&dev->video_dev));
+	/* prevent more I/O from starting and stop any ongoing */
+	mutex_lock(&dev->io_mutex);
+	dev->status = STATUS_DISCONNECTED;
+	wake_up_interruptible(&dev->wait_data);
+	wake_up_interruptible(&dev->wait_buffer);
+	mutex_unlock(&dev->io_mutex);
+	v4l2_device_disconnect(&dev->v4l2_dev);
+	msleep(100);
+	flush_work(&dev->worker);
+	mutex_lock(&dev->io_mutex);
+	hdpvr_cancel_queue(dev);
+	mutex_unlock(&dev->io_mutex);
+#if IS_ENABLED(CONFIG_I2C)
+	i2c_del_adapter(&dev->i2c_adapter);
+#endif
+	video_unregister_device(&dev->video_dev);
+	atomic_dec(&dev_nr);
+}
+
+
+static struct usb_driver hdpvr_usb_driver = {
+	.name =		"hdpvr",
+	.probe =	hdpvr_probe,
+	.disconnect =	hdpvr_disconnect,
+	.id_table =	hdpvr_table,
+};
+
+module_usb_driver(hdpvr_usb_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_VERSION("0.2.1");
+MODULE_AUTHOR("Janne Grunau");
+MODULE_DESCRIPTION("Hauppauge HD PVR driver");
diff --git a/drivers/media/usb/hdpvr/hdpvr-i2c.c b/drivers/media/usb/hdpvr/hdpvr-i2c.c
new file mode 100644
index 0000000..5a3cb61
--- /dev/null
+++ b/drivers/media/usb/hdpvr/hdpvr-i2c.c
@@ -0,0 +1,214 @@
+
+/*
+ * Hauppauge HD PVR USB driver
+ *
+ * Copyright (C) 2008      Janne Grunau (j@jannau.net)
+ *
+ * IR device registration code is
+ * Copyright (C) 2010	Andy Walls <awalls@md.metrocast.net>
+ *
+ *	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, version 2.
+ *
+ */
+
+#if IS_ENABLED(CONFIG_I2C)
+
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/export.h>
+
+#include "hdpvr.h"
+
+#define CTRL_READ_REQUEST	0xb8
+#define CTRL_WRITE_REQUEST	0x38
+
+#define REQTYPE_I2C_READ	0xb1
+#define REQTYPE_I2C_WRITE	0xb0
+#define REQTYPE_I2C_WRITE_STATT	0xd0
+
+#define Z8F0811_IR_TX_I2C_ADDR	0x70
+#define Z8F0811_IR_RX_I2C_ADDR	0x71
+
+
+struct i2c_client *hdpvr_register_ir_i2c(struct hdpvr_device *dev)
+{
+	struct IR_i2c_init_data *init_data = &dev->ir_i2c_init_data;
+	struct i2c_board_info info = {
+		I2C_BOARD_INFO("ir_z8f0811_hdpvr", Z8F0811_IR_RX_I2C_ADDR),
+	};
+
+	/* Our default information for ir-kbd-i2c.c to use */
+	init_data->ir_codes = RC_MAP_HAUPPAUGE;
+	init_data->internal_get_key_func = IR_KBD_GET_KEY_HAUP_XVR;
+	init_data->type = RC_PROTO_BIT_RC5 | RC_PROTO_BIT_RC6_MCE |
+			  RC_PROTO_BIT_RC6_6A_32;
+	init_data->name = "HD-PVR";
+	init_data->polling_interval = 405; /* ms, duplicated from Windows */
+	info.platform_data = init_data;
+
+	return i2c_new_device(&dev->i2c_adapter, &info);
+}
+
+static int hdpvr_i2c_read(struct hdpvr_device *dev, int bus,
+			  unsigned char addr, char *wdata, int wlen,
+			  char *data, int len)
+{
+	int ret;
+
+	if ((len > sizeof(dev->i2c_buf)) || (wlen > sizeof(dev->i2c_buf)))
+		return -EINVAL;
+
+	if (wlen) {
+		memcpy(&dev->i2c_buf, wdata, wlen);
+		ret = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0),
+				      REQTYPE_I2C_WRITE, CTRL_WRITE_REQUEST,
+				      (bus << 8) | addr, 0, &dev->i2c_buf,
+				      wlen, 1000);
+		if (ret < 0)
+			return ret;
+	}
+
+	ret = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0),
+			      REQTYPE_I2C_READ, CTRL_READ_REQUEST,
+			      (bus << 8) | addr, 0, &dev->i2c_buf, len, 1000);
+
+	if (ret == len) {
+		memcpy(data, &dev->i2c_buf, len);
+		ret = 0;
+	} else if (ret >= 0)
+		ret = -EIO;
+
+	return ret;
+}
+
+static int hdpvr_i2c_write(struct hdpvr_device *dev, int bus,
+			   unsigned char addr, char *data, int len)
+{
+	int ret;
+
+	if (len > sizeof(dev->i2c_buf))
+		return -EINVAL;
+
+	memcpy(&dev->i2c_buf, data, len);
+	ret = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0),
+			      REQTYPE_I2C_WRITE, CTRL_WRITE_REQUEST,
+			      (bus << 8) | addr, 0, &dev->i2c_buf, len, 1000);
+
+	if (ret < 0)
+		return ret;
+
+	ret = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0),
+			      REQTYPE_I2C_WRITE_STATT, CTRL_READ_REQUEST,
+			      0, 0, &dev->i2c_buf, 2, 1000);
+
+	if ((ret == 2) && (dev->i2c_buf[1] == (len - 1)))
+		ret = 0;
+	else if (ret >= 0)
+		ret = -EIO;
+
+	return ret;
+}
+
+static int hdpvr_transfer(struct i2c_adapter *i2c_adapter, struct i2c_msg *msgs,
+			  int num)
+{
+	struct hdpvr_device *dev = i2c_get_adapdata(i2c_adapter);
+	int retval = 0, addr;
+
+	mutex_lock(&dev->i2c_mutex);
+
+	addr = msgs[0].addr << 1;
+
+	if (num == 1) {
+		if (msgs[0].flags & I2C_M_RD)
+			retval = hdpvr_i2c_read(dev, 1, addr, NULL, 0,
+						msgs[0].buf, msgs[0].len);
+		else
+			retval = hdpvr_i2c_write(dev, 1, addr, msgs[0].buf,
+						 msgs[0].len);
+	} else if (num == 2) {
+		if (msgs[0].addr != msgs[1].addr) {
+			v4l2_warn(&dev->v4l2_dev, "refusing 2-phase i2c xfer with conflicting target addresses\n");
+			retval = -EINVAL;
+			goto out;
+		}
+
+		if ((msgs[0].flags & I2C_M_RD) || !(msgs[1].flags & I2C_M_RD)) {
+			v4l2_warn(&dev->v4l2_dev, "refusing complex xfer with r0=%d, r1=%d\n",
+				  msgs[0].flags & I2C_M_RD,
+				  msgs[1].flags & I2C_M_RD);
+			retval = -EINVAL;
+			goto out;
+		}
+
+		/*
+		 * Write followed by atomic read is the only complex xfer that
+		 * we actually support here.
+		 */
+		retval = hdpvr_i2c_read(dev, 1, addr, msgs[0].buf, msgs[0].len,
+					msgs[1].buf, msgs[1].len);
+	} else {
+		v4l2_warn(&dev->v4l2_dev, "refusing %d-phase i2c xfer\n", num);
+	}
+
+out:
+	mutex_unlock(&dev->i2c_mutex);
+
+	return retval ? retval : num;
+}
+
+static u32 hdpvr_functionality(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm hdpvr_algo = {
+	.master_xfer   = hdpvr_transfer,
+	.functionality = hdpvr_functionality,
+};
+
+static const struct i2c_adapter hdpvr_i2c_adapter_template = {
+	.name   = "Hauppauge HD PVR I2C",
+	.owner  = THIS_MODULE,
+	.algo   = &hdpvr_algo,
+};
+
+static int hdpvr_activate_ir(struct hdpvr_device *dev)
+{
+	char buffer[2];
+
+	mutex_lock(&dev->i2c_mutex);
+
+	hdpvr_i2c_read(dev, 0, 0x54, NULL, 0, buffer, 1);
+
+	buffer[0] = 0;
+	buffer[1] = 0x8;
+	hdpvr_i2c_write(dev, 1, 0x54, buffer, 2);
+
+	buffer[1] = 0x18;
+	hdpvr_i2c_write(dev, 1, 0x54, buffer, 2);
+
+	mutex_unlock(&dev->i2c_mutex);
+
+	return 0;
+}
+
+int hdpvr_register_i2c_adapter(struct hdpvr_device *dev)
+{
+	int retval = -ENOMEM;
+
+	hdpvr_activate_ir(dev);
+
+	dev->i2c_adapter = hdpvr_i2c_adapter_template;
+	dev->i2c_adapter.dev.parent = &dev->udev->dev;
+
+	i2c_set_adapdata(&dev->i2c_adapter, dev);
+
+	retval = i2c_add_adapter(&dev->i2c_adapter);
+
+	return retval;
+}
+
+#endif
diff --git a/drivers/media/usb/hdpvr/hdpvr-video.c b/drivers/media/usb/hdpvr/hdpvr-video.c
new file mode 100644
index 0000000..1b89c77
--- /dev/null
+++ b/drivers/media/usb/hdpvr/hdpvr-video.c
@@ -0,0 +1,1255 @@
+/*
+ * Hauppauge HD PVR USB driver - video 4 linux 2 interface
+ *
+ * Copyright (C) 2008      Janne Grunau (j@jannau.net)
+ *
+ *	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, version 2.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+
+#include <linux/videodev2.h>
+#include <linux/v4l2-dv-timings.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-dv-timings.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-event.h>
+#include "hdpvr.h"
+
+#define BULK_URB_TIMEOUT   90 /* 0.09 seconds */
+
+#define print_buffer_status() { \
+		v4l2_dbg(MSG_BUFFER, hdpvr_debug, &dev->v4l2_dev,	\
+			 "%s:%d buffer stat: %d free, %d proc\n",	\
+			 __func__, __LINE__,				\
+			 list_size(&dev->free_buff_list),		\
+			 list_size(&dev->rec_buff_list)); }
+
+static const struct v4l2_dv_timings hdpvr_dv_timings[] = {
+	V4L2_DV_BT_CEA_720X480I59_94,
+	V4L2_DV_BT_CEA_720X576I50,
+	V4L2_DV_BT_CEA_720X480P59_94,
+	V4L2_DV_BT_CEA_720X576P50,
+	V4L2_DV_BT_CEA_1280X720P50,
+	V4L2_DV_BT_CEA_1280X720P60,
+	V4L2_DV_BT_CEA_1920X1080I50,
+	V4L2_DV_BT_CEA_1920X1080I60,
+};
+
+/* Use 480i59 as the default timings */
+#define HDPVR_DEF_DV_TIMINGS_IDX (0)
+
+struct hdpvr_fh {
+	struct v4l2_fh fh;
+	bool legacy_mode;
+};
+
+static uint list_size(struct list_head *list)
+{
+	struct list_head *tmp;
+	uint count = 0;
+
+	list_for_each(tmp, list) {
+		count++;
+	}
+
+	return count;
+}
+
+/*=========================================================================*/
+/* urb callback */
+static void hdpvr_read_bulk_callback(struct urb *urb)
+{
+	struct hdpvr_buffer *buf = (struct hdpvr_buffer *)urb->context;
+	struct hdpvr_device *dev = buf->dev;
+
+	/* marking buffer as received and wake waiting */
+	buf->status = BUFSTAT_READY;
+	wake_up_interruptible(&dev->wait_data);
+}
+
+/*=========================================================================*/
+/* buffer bits */
+
+/* function expects dev->io_mutex to be hold by caller */
+int hdpvr_cancel_queue(struct hdpvr_device *dev)
+{
+	struct hdpvr_buffer *buf;
+
+	list_for_each_entry(buf, &dev->rec_buff_list, buff_list) {
+		usb_kill_urb(buf->urb);
+		buf->status = BUFSTAT_AVAILABLE;
+	}
+
+	list_splice_init(&dev->rec_buff_list, dev->free_buff_list.prev);
+
+	return 0;
+}
+
+static int hdpvr_free_queue(struct list_head *q)
+{
+	struct list_head *tmp;
+	struct list_head *p;
+	struct hdpvr_buffer *buf;
+	struct urb *urb;
+
+	for (p = q->next; p != q;) {
+		buf = list_entry(p, struct hdpvr_buffer, buff_list);
+
+		urb = buf->urb;
+		usb_free_coherent(urb->dev, urb->transfer_buffer_length,
+				  urb->transfer_buffer, urb->transfer_dma);
+		usb_free_urb(urb);
+		tmp = p->next;
+		list_del(p);
+		kfree(buf);
+		p = tmp;
+	}
+
+	return 0;
+}
+
+/* function expects dev->io_mutex to be hold by caller */
+int hdpvr_free_buffers(struct hdpvr_device *dev)
+{
+	hdpvr_cancel_queue(dev);
+
+	hdpvr_free_queue(&dev->free_buff_list);
+	hdpvr_free_queue(&dev->rec_buff_list);
+
+	return 0;
+}
+
+/* function expects dev->io_mutex to be hold by caller */
+int hdpvr_alloc_buffers(struct hdpvr_device *dev, uint count)
+{
+	uint i;
+	int retval = -ENOMEM;
+	u8 *mem;
+	struct hdpvr_buffer *buf;
+	struct urb *urb;
+
+	v4l2_dbg(MSG_INFO, hdpvr_debug, &dev->v4l2_dev,
+		 "allocating %u buffers\n", count);
+
+	for (i = 0; i < count; i++) {
+
+		buf = kzalloc(sizeof(struct hdpvr_buffer), GFP_KERNEL);
+		if (!buf) {
+			v4l2_err(&dev->v4l2_dev, "cannot allocate buffer\n");
+			goto exit;
+		}
+		buf->dev = dev;
+
+		urb = usb_alloc_urb(0, GFP_KERNEL);
+		if (!urb)
+			goto exit_urb;
+		buf->urb = urb;
+
+		mem = usb_alloc_coherent(dev->udev, dev->bulk_in_size, GFP_KERNEL,
+					 &urb->transfer_dma);
+		if (!mem) {
+			v4l2_err(&dev->v4l2_dev,
+				 "cannot allocate usb transfer buffer\n");
+			goto exit_urb_buffer;
+		}
+
+		usb_fill_bulk_urb(buf->urb, dev->udev,
+				  usb_rcvbulkpipe(dev->udev,
+						  dev->bulk_in_endpointAddr),
+				  mem, dev->bulk_in_size,
+				  hdpvr_read_bulk_callback, buf);
+
+		buf->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+		buf->status = BUFSTAT_AVAILABLE;
+		list_add_tail(&buf->buff_list, &dev->free_buff_list);
+	}
+	return 0;
+exit_urb_buffer:
+	usb_free_urb(urb);
+exit_urb:
+	kfree(buf);
+exit:
+	hdpvr_free_buffers(dev);
+	return retval;
+}
+
+static int hdpvr_submit_buffers(struct hdpvr_device *dev)
+{
+	struct hdpvr_buffer *buf;
+	struct urb *urb;
+	int ret = 0, err_count = 0;
+
+	mutex_lock(&dev->io_mutex);
+
+	while (dev->status == STATUS_STREAMING &&
+	       !list_empty(&dev->free_buff_list)) {
+
+		buf = list_entry(dev->free_buff_list.next, struct hdpvr_buffer,
+				 buff_list);
+		if (buf->status != BUFSTAT_AVAILABLE) {
+			v4l2_err(&dev->v4l2_dev,
+				 "buffer not marked as available\n");
+			ret = -EFAULT;
+			goto err;
+		}
+
+		urb = buf->urb;
+		urb->status = 0;
+		urb->actual_length = 0;
+		ret = usb_submit_urb(urb, GFP_KERNEL);
+		if (ret) {
+			v4l2_err(&dev->v4l2_dev,
+				 "usb_submit_urb in %s returned %d\n",
+				 __func__, ret);
+			if (++err_count > 2)
+				break;
+			continue;
+		}
+		buf->status = BUFSTAT_INPROGRESS;
+		list_move_tail(&buf->buff_list, &dev->rec_buff_list);
+	}
+err:
+	print_buffer_status();
+	mutex_unlock(&dev->io_mutex);
+	return ret;
+}
+
+static struct hdpvr_buffer *hdpvr_get_next_buffer(struct hdpvr_device *dev)
+{
+	struct hdpvr_buffer *buf;
+
+	mutex_lock(&dev->io_mutex);
+
+	if (list_empty(&dev->rec_buff_list)) {
+		mutex_unlock(&dev->io_mutex);
+		return NULL;
+	}
+
+	buf = list_entry(dev->rec_buff_list.next, struct hdpvr_buffer,
+			 buff_list);
+	mutex_unlock(&dev->io_mutex);
+
+	return buf;
+}
+
+static void hdpvr_transmit_buffers(struct work_struct *work)
+{
+	struct hdpvr_device *dev = container_of(work, struct hdpvr_device,
+						worker);
+
+	while (dev->status == STATUS_STREAMING) {
+
+		if (hdpvr_submit_buffers(dev)) {
+			v4l2_err(&dev->v4l2_dev, "couldn't submit buffers\n");
+			goto error;
+		}
+		if (wait_event_interruptible(dev->wait_buffer,
+				!list_empty(&dev->free_buff_list) ||
+					     dev->status != STATUS_STREAMING))
+			goto error;
+	}
+
+	v4l2_dbg(MSG_INFO, hdpvr_debug, &dev->v4l2_dev,
+		 "transmit worker exited\n");
+	return;
+error:
+	v4l2_dbg(MSG_INFO, hdpvr_debug, &dev->v4l2_dev,
+		 "transmit buffers errored\n");
+	dev->status = STATUS_ERROR;
+}
+
+/* function expects dev->io_mutex to be hold by caller */
+static int hdpvr_start_streaming(struct hdpvr_device *dev)
+{
+	int ret;
+	struct hdpvr_video_info vidinf;
+
+	if (dev->status == STATUS_STREAMING)
+		return 0;
+	if (dev->status != STATUS_IDLE)
+		return -EAGAIN;
+
+	ret = get_video_info(dev, &vidinf);
+	if (ret < 0)
+		return ret;
+
+	if (!vidinf.valid) {
+		msleep(250);
+		v4l2_dbg(MSG_INFO, hdpvr_debug, &dev->v4l2_dev,
+				"no video signal at input %d\n", dev->options.video_input);
+		return -EAGAIN;
+	}
+
+	v4l2_dbg(MSG_BUFFER, hdpvr_debug, &dev->v4l2_dev,
+			"video signal: %dx%d@%dhz\n", vidinf.width,
+			vidinf.height, vidinf.fps);
+
+	/* start streaming 2 request */
+	ret = usb_control_msg(dev->udev,
+			usb_sndctrlpipe(dev->udev, 0),
+			0xb8, 0x38, 0x1, 0, NULL, 0, 8000);
+	v4l2_dbg(MSG_BUFFER, hdpvr_debug, &dev->v4l2_dev,
+			"encoder start control request returned %d\n", ret);
+	if (ret < 0)
+		return ret;
+
+	ret = hdpvr_config_call(dev, CTRL_START_STREAMING_VALUE, 0x00);
+	if (ret)
+		return ret;
+
+	dev->status = STATUS_STREAMING;
+
+	INIT_WORK(&dev->worker, hdpvr_transmit_buffers);
+	schedule_work(&dev->worker);
+
+	v4l2_dbg(MSG_BUFFER, hdpvr_debug, &dev->v4l2_dev,
+			"streaming started\n");
+
+	return 0;
+}
+
+
+/* function expects dev->io_mutex to be hold by caller */
+static int hdpvr_stop_streaming(struct hdpvr_device *dev)
+{
+	int actual_length;
+	uint c = 0;
+	u8 *buf;
+
+	if (dev->status == STATUS_IDLE)
+		return 0;
+	else if (dev->status != STATUS_STREAMING)
+		return -EAGAIN;
+
+	buf = kmalloc(dev->bulk_in_size, GFP_KERNEL);
+	if (!buf)
+		v4l2_err(&dev->v4l2_dev, "failed to allocate temporary buffer for emptying the internal device buffer. Next capture start will be slow\n");
+
+	dev->status = STATUS_SHUTTING_DOWN;
+	hdpvr_config_call(dev, CTRL_STOP_STREAMING_VALUE, 0x00);
+	mutex_unlock(&dev->io_mutex);
+
+	wake_up_interruptible(&dev->wait_buffer);
+	msleep(50);
+
+	flush_work(&dev->worker);
+
+	mutex_lock(&dev->io_mutex);
+	/* kill the still outstanding urbs */
+	hdpvr_cancel_queue(dev);
+
+	/* emptying the device buffer beforeshutting it down */
+	while (buf && ++c < 500 &&
+	       !usb_bulk_msg(dev->udev,
+			     usb_rcvbulkpipe(dev->udev,
+					     dev->bulk_in_endpointAddr),
+			     buf, dev->bulk_in_size, &actual_length,
+			     BULK_URB_TIMEOUT)) {
+		v4l2_dbg(MSG_BUFFER, hdpvr_debug, &dev->v4l2_dev,
+			 "%2d: got %d bytes\n", c, actual_length);
+	}
+	kfree(buf);
+	v4l2_dbg(MSG_BUFFER, hdpvr_debug, &dev->v4l2_dev,
+		 "used %d urbs to empty device buffers\n", c-1);
+	msleep(10);
+
+	dev->status = STATUS_IDLE;
+
+	return 0;
+}
+
+
+/*=======================================================================*/
+/*
+ * video 4 linux 2 file operations
+ */
+
+static int hdpvr_open(struct file *file)
+{
+	struct hdpvr_fh *fh = kzalloc(sizeof(*fh), GFP_KERNEL);
+
+	if (fh == NULL)
+		return -ENOMEM;
+	fh->legacy_mode = true;
+	v4l2_fh_init(&fh->fh, video_devdata(file));
+	v4l2_fh_add(&fh->fh);
+	file->private_data = fh;
+	return 0;
+}
+
+static int hdpvr_release(struct file *file)
+{
+	struct hdpvr_device *dev = video_drvdata(file);
+
+	mutex_lock(&dev->io_mutex);
+	if (file->private_data == dev->owner) {
+		hdpvr_stop_streaming(dev);
+		dev->owner = NULL;
+	}
+	mutex_unlock(&dev->io_mutex);
+
+	return v4l2_fh_release(file);
+}
+
+/*
+ * hdpvr_v4l2_read()
+ * will allocate buffers when called for the first time
+ */
+static ssize_t hdpvr_read(struct file *file, char __user *buffer, size_t count,
+			  loff_t *pos)
+{
+	struct hdpvr_device *dev = video_drvdata(file);
+	struct hdpvr_buffer *buf = NULL;
+	struct urb *urb;
+	unsigned int ret = 0;
+	int rem, cnt;
+
+	if (*pos)
+		return -ESPIPE;
+
+	mutex_lock(&dev->io_mutex);
+	if (dev->status == STATUS_IDLE) {
+		if (hdpvr_start_streaming(dev)) {
+			v4l2_dbg(MSG_INFO, hdpvr_debug, &dev->v4l2_dev,
+				 "start_streaming failed\n");
+			ret = -EIO;
+			msleep(200);
+			dev->status = STATUS_IDLE;
+			mutex_unlock(&dev->io_mutex);
+			goto err;
+		}
+		dev->owner = file->private_data;
+		print_buffer_status();
+	}
+	mutex_unlock(&dev->io_mutex);
+
+	/* wait for the first buffer */
+	if (!(file->f_flags & O_NONBLOCK)) {
+		if (wait_event_interruptible(dev->wait_data,
+					     hdpvr_get_next_buffer(dev)))
+			return -ERESTARTSYS;
+	}
+
+	buf = hdpvr_get_next_buffer(dev);
+
+	while (count > 0 && buf) {
+
+		if (buf->status != BUFSTAT_READY &&
+		    dev->status != STATUS_DISCONNECTED) {
+			int err;
+			/* return nonblocking */
+			if (file->f_flags & O_NONBLOCK) {
+				if (!ret)
+					ret = -EAGAIN;
+				goto err;
+			}
+
+			err = wait_event_interruptible_timeout(dev->wait_data,
+				buf->status == BUFSTAT_READY,
+				msecs_to_jiffies(1000));
+			if (err < 0) {
+				ret = err;
+				goto err;
+			}
+			if (!err) {
+				v4l2_dbg(MSG_INFO, hdpvr_debug, &dev->v4l2_dev,
+					"timeout: restart streaming\n");
+				hdpvr_stop_streaming(dev);
+				msecs_to_jiffies(4000);
+				err = hdpvr_start_streaming(dev);
+				if (err) {
+					ret = err;
+					goto err;
+				}
+			}
+		}
+
+		if (buf->status != BUFSTAT_READY)
+			break;
+
+		/* set remaining bytes to copy */
+		urb = buf->urb;
+		rem = urb->actual_length - buf->pos;
+		cnt = rem > count ? count : rem;
+
+		if (copy_to_user(buffer, urb->transfer_buffer + buf->pos,
+				 cnt)) {
+			v4l2_err(&dev->v4l2_dev, "read: copy_to_user failed\n");
+			if (!ret)
+				ret = -EFAULT;
+			goto err;
+		}
+
+		buf->pos += cnt;
+		count -= cnt;
+		buffer += cnt;
+		ret += cnt;
+
+		/* finished, take next buffer */
+		if (buf->pos == urb->actual_length) {
+			mutex_lock(&dev->io_mutex);
+			buf->pos = 0;
+			buf->status = BUFSTAT_AVAILABLE;
+
+			list_move_tail(&buf->buff_list, &dev->free_buff_list);
+
+			print_buffer_status();
+
+			mutex_unlock(&dev->io_mutex);
+
+			wake_up_interruptible(&dev->wait_buffer);
+
+			buf = hdpvr_get_next_buffer(dev);
+		}
+	}
+err:
+	if (!ret && !buf)
+		ret = -EAGAIN;
+	return ret;
+}
+
+static __poll_t hdpvr_poll(struct file *filp, poll_table *wait)
+{
+	__poll_t req_events = poll_requested_events(wait);
+	struct hdpvr_buffer *buf = NULL;
+	struct hdpvr_device *dev = video_drvdata(filp);
+	__poll_t mask = v4l2_ctrl_poll(filp, wait);
+
+	if (!(req_events & (EPOLLIN | EPOLLRDNORM)))
+		return mask;
+
+	mutex_lock(&dev->io_mutex);
+
+	if (dev->status == STATUS_IDLE) {
+		if (hdpvr_start_streaming(dev)) {
+			v4l2_dbg(MSG_BUFFER, hdpvr_debug, &dev->v4l2_dev,
+				 "start_streaming failed\n");
+			dev->status = STATUS_IDLE;
+		} else {
+			dev->owner = filp->private_data;
+		}
+
+		print_buffer_status();
+	}
+	mutex_unlock(&dev->io_mutex);
+
+	buf = hdpvr_get_next_buffer(dev);
+	/* only wait if no data is available */
+	if (!buf || buf->status != BUFSTAT_READY) {
+		poll_wait(filp, &dev->wait_data, wait);
+		buf = hdpvr_get_next_buffer(dev);
+	}
+	if (buf && buf->status == BUFSTAT_READY)
+		mask |= EPOLLIN | EPOLLRDNORM;
+
+	return mask;
+}
+
+
+static const struct v4l2_file_operations hdpvr_fops = {
+	.owner		= THIS_MODULE,
+	.open		= hdpvr_open,
+	.release	= hdpvr_release,
+	.read		= hdpvr_read,
+	.poll		= hdpvr_poll,
+	.unlocked_ioctl	= video_ioctl2,
+};
+
+/*=======================================================================*/
+/*
+ * V4L2 ioctl handling
+ */
+
+static int vidioc_querycap(struct file *file, void  *priv,
+			   struct v4l2_capability *cap)
+{
+	struct hdpvr_device *dev = video_drvdata(file);
+
+	strcpy(cap->driver, "hdpvr");
+	strcpy(cap->card, "Hauppauge HD PVR");
+	usb_make_path(dev->udev, cap->bus_info, sizeof(cap->bus_info));
+	cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_AUDIO |
+			    V4L2_CAP_READWRITE;
+	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+	return 0;
+}
+
+static int vidioc_s_std(struct file *file, void *_fh,
+			v4l2_std_id std)
+{
+	struct hdpvr_device *dev = video_drvdata(file);
+	struct hdpvr_fh *fh = _fh;
+	u8 std_type = 1;
+
+	if (!fh->legacy_mode && dev->options.video_input == HDPVR_COMPONENT)
+		return -ENODATA;
+	if (dev->status != STATUS_IDLE)
+		return -EBUSY;
+	if (std & V4L2_STD_525_60)
+		std_type = 0;
+	dev->cur_std = std;
+	dev->width = 720;
+	dev->height = std_type ? 576 : 480;
+
+	return hdpvr_config_call(dev, CTRL_VIDEO_STD_TYPE, std_type);
+}
+
+static int vidioc_g_std(struct file *file, void *_fh,
+			v4l2_std_id *std)
+{
+	struct hdpvr_device *dev = video_drvdata(file);
+	struct hdpvr_fh *fh = _fh;
+
+	if (!fh->legacy_mode && dev->options.video_input == HDPVR_COMPONENT)
+		return -ENODATA;
+	*std = dev->cur_std;
+	return 0;
+}
+
+static int vidioc_querystd(struct file *file, void *_fh, v4l2_std_id *a)
+{
+	struct hdpvr_device *dev = video_drvdata(file);
+	struct hdpvr_video_info vid_info;
+	struct hdpvr_fh *fh = _fh;
+	int ret;
+
+	*a = V4L2_STD_UNKNOWN;
+	if (dev->options.video_input == HDPVR_COMPONENT)
+		return fh->legacy_mode ? 0 : -ENODATA;
+	ret = get_video_info(dev, &vid_info);
+	if (vid_info.valid && vid_info.width == 720 &&
+	    (vid_info.height == 480 || vid_info.height == 576)) {
+		*a = (vid_info.height == 480) ?
+			V4L2_STD_525_60 : V4L2_STD_625_50;
+	}
+	return ret;
+}
+
+static int vidioc_s_dv_timings(struct file *file, void *_fh,
+				    struct v4l2_dv_timings *timings)
+{
+	struct hdpvr_device *dev = video_drvdata(file);
+	struct hdpvr_fh *fh = _fh;
+	int i;
+
+	fh->legacy_mode = false;
+	if (dev->options.video_input)
+		return -ENODATA;
+	if (dev->status != STATUS_IDLE)
+		return -EBUSY;
+	for (i = 0; i < ARRAY_SIZE(hdpvr_dv_timings); i++)
+		if (v4l2_match_dv_timings(timings, hdpvr_dv_timings + i, 0, false))
+			break;
+	if (i == ARRAY_SIZE(hdpvr_dv_timings))
+		return -EINVAL;
+	dev->cur_dv_timings = hdpvr_dv_timings[i];
+	dev->width = hdpvr_dv_timings[i].bt.width;
+	dev->height = hdpvr_dv_timings[i].bt.height;
+	return 0;
+}
+
+static int vidioc_g_dv_timings(struct file *file, void *_fh,
+				    struct v4l2_dv_timings *timings)
+{
+	struct hdpvr_device *dev = video_drvdata(file);
+	struct hdpvr_fh *fh = _fh;
+
+	fh->legacy_mode = false;
+	if (dev->options.video_input)
+		return -ENODATA;
+	*timings = dev->cur_dv_timings;
+	return 0;
+}
+
+static int vidioc_query_dv_timings(struct file *file, void *_fh,
+				    struct v4l2_dv_timings *timings)
+{
+	struct hdpvr_device *dev = video_drvdata(file);
+	struct hdpvr_fh *fh = _fh;
+	struct hdpvr_video_info vid_info;
+	bool interlaced;
+	int ret = 0;
+	int i;
+
+	fh->legacy_mode = false;
+	if (dev->options.video_input)
+		return -ENODATA;
+	ret = get_video_info(dev, &vid_info);
+	if (ret)
+		return ret;
+	if (!vid_info.valid)
+		return -ENOLCK;
+	interlaced = vid_info.fps <= 30;
+	for (i = 0; i < ARRAY_SIZE(hdpvr_dv_timings); i++) {
+		const struct v4l2_bt_timings *bt = &hdpvr_dv_timings[i].bt;
+		unsigned hsize;
+		unsigned vsize;
+		unsigned fps;
+
+		hsize = V4L2_DV_BT_FRAME_WIDTH(bt);
+		vsize = V4L2_DV_BT_FRAME_HEIGHT(bt);
+		fps = (unsigned)bt->pixelclock / (hsize * vsize);
+		if (bt->width != vid_info.width ||
+		    bt->height != vid_info.height ||
+		    bt->interlaced != interlaced ||
+		    (fps != vid_info.fps && fps + 1 != vid_info.fps))
+			continue;
+		*timings = hdpvr_dv_timings[i];
+		break;
+	}
+	if (i == ARRAY_SIZE(hdpvr_dv_timings))
+		ret = -ERANGE;
+
+	return ret;
+}
+
+static int vidioc_enum_dv_timings(struct file *file, void *_fh,
+				    struct v4l2_enum_dv_timings *timings)
+{
+	struct hdpvr_device *dev = video_drvdata(file);
+	struct hdpvr_fh *fh = _fh;
+
+	fh->legacy_mode = false;
+	memset(timings->reserved, 0, sizeof(timings->reserved));
+	if (dev->options.video_input)
+		return -ENODATA;
+	if (timings->index >= ARRAY_SIZE(hdpvr_dv_timings))
+		return -EINVAL;
+	timings->timings = hdpvr_dv_timings[timings->index];
+	return 0;
+}
+
+static int vidioc_dv_timings_cap(struct file *file, void *_fh,
+				    struct v4l2_dv_timings_cap *cap)
+{
+	struct hdpvr_device *dev = video_drvdata(file);
+	struct hdpvr_fh *fh = _fh;
+
+	fh->legacy_mode = false;
+	if (dev->options.video_input)
+		return -ENODATA;
+	cap->type = V4L2_DV_BT_656_1120;
+	cap->bt.min_width = 720;
+	cap->bt.max_width = 1920;
+	cap->bt.min_height = 480;
+	cap->bt.max_height = 1080;
+	cap->bt.min_pixelclock = 27000000;
+	cap->bt.max_pixelclock = 74250000;
+	cap->bt.standards = V4L2_DV_BT_STD_CEA861;
+	cap->bt.capabilities = V4L2_DV_BT_CAP_INTERLACED | V4L2_DV_BT_CAP_PROGRESSIVE;
+	return 0;
+}
+
+static const char *iname[] = {
+	[HDPVR_COMPONENT] = "Component",
+	[HDPVR_SVIDEO]    = "S-Video",
+	[HDPVR_COMPOSITE] = "Composite",
+};
+
+static int vidioc_enum_input(struct file *file, void *_fh, struct v4l2_input *i)
+{
+	unsigned int n;
+
+	n = i->index;
+	if (n >= HDPVR_VIDEO_INPUTS)
+		return -EINVAL;
+
+	i->type = V4L2_INPUT_TYPE_CAMERA;
+
+	strncpy(i->name, iname[n], sizeof(i->name) - 1);
+	i->name[sizeof(i->name) - 1] = '\0';
+
+	i->audioset = 1<<HDPVR_RCA_FRONT | 1<<HDPVR_RCA_BACK | 1<<HDPVR_SPDIF;
+
+	i->capabilities = n ? V4L2_IN_CAP_STD : V4L2_IN_CAP_DV_TIMINGS;
+	i->std = n ? V4L2_STD_ALL : 0;
+
+	return 0;
+}
+
+static int vidioc_s_input(struct file *file, void *_fh,
+			  unsigned int index)
+{
+	struct hdpvr_device *dev = video_drvdata(file);
+	int retval;
+
+	if (index >= HDPVR_VIDEO_INPUTS)
+		return -EINVAL;
+
+	if (dev->status != STATUS_IDLE)
+		return -EBUSY;
+
+	retval = hdpvr_config_call(dev, CTRL_VIDEO_INPUT_VALUE, index+1);
+	if (!retval) {
+		dev->options.video_input = index;
+		/*
+		 * Unfortunately gstreamer calls ENUMSTD and bails out if it
+		 * won't find any formats, even though component input is
+		 * selected. This means that we have to leave tvnorms at
+		 * V4L2_STD_ALL. We cannot use the 'legacy' trick since
+		 * tvnorms is set at the device node level and not at the
+		 * filehandle level.
+		 *
+		 * Comment this out for now, but if the legacy mode can be
+		 * removed in the future, then this code should be enabled
+		 * again.
+		dev->video_dev.tvnorms =
+			(index != HDPVR_COMPONENT) ? V4L2_STD_ALL : 0;
+		 */
+	}
+
+	return retval;
+}
+
+static int vidioc_g_input(struct file *file, void *private_data,
+			  unsigned int *index)
+{
+	struct hdpvr_device *dev = video_drvdata(file);
+
+	*index = dev->options.video_input;
+	return 0;
+}
+
+
+static const char *audio_iname[] = {
+	[HDPVR_RCA_FRONT] = "RCA front",
+	[HDPVR_RCA_BACK]  = "RCA back",
+	[HDPVR_SPDIF]     = "SPDIF",
+};
+
+static int vidioc_enumaudio(struct file *file, void *priv,
+				struct v4l2_audio *audio)
+{
+	unsigned int n;
+
+	n = audio->index;
+	if (n >= HDPVR_AUDIO_INPUTS)
+		return -EINVAL;
+
+	audio->capability = V4L2_AUDCAP_STEREO;
+
+	strncpy(audio->name, audio_iname[n], sizeof(audio->name) - 1);
+	audio->name[sizeof(audio->name) - 1] = '\0';
+
+	return 0;
+}
+
+static int vidioc_s_audio(struct file *file, void *private_data,
+			  const struct v4l2_audio *audio)
+{
+	struct hdpvr_device *dev = video_drvdata(file);
+	int retval;
+
+	if (audio->index >= HDPVR_AUDIO_INPUTS)
+		return -EINVAL;
+
+	if (dev->status != STATUS_IDLE)
+		return -EBUSY;
+
+	retval = hdpvr_set_audio(dev, audio->index+1, dev->options.audio_codec);
+	if (!retval)
+		dev->options.audio_input = audio->index;
+
+	return retval;
+}
+
+static int vidioc_g_audio(struct file *file, void *private_data,
+			  struct v4l2_audio *audio)
+{
+	struct hdpvr_device *dev = video_drvdata(file);
+
+	audio->index = dev->options.audio_input;
+	audio->capability = V4L2_AUDCAP_STEREO;
+	strlcpy(audio->name, audio_iname[audio->index], sizeof(audio->name));
+	audio->name[sizeof(audio->name) - 1] = '\0';
+	return 0;
+}
+
+static int hdpvr_try_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct hdpvr_device *dev =
+		container_of(ctrl->handler, struct hdpvr_device, hdl);
+
+	switch (ctrl->id) {
+	case V4L2_CID_MPEG_VIDEO_BITRATE_MODE:
+		if (ctrl->val == V4L2_MPEG_VIDEO_BITRATE_MODE_VBR &&
+		    dev->video_bitrate->val >= dev->video_bitrate_peak->val)
+			dev->video_bitrate_peak->val =
+					dev->video_bitrate->val + 100000;
+		break;
+	}
+	return 0;
+}
+
+static int hdpvr_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct hdpvr_device *dev =
+		container_of(ctrl->handler, struct hdpvr_device, hdl);
+	struct hdpvr_options *opt = &dev->options;
+	int ret = -EINVAL;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		ret = hdpvr_config_call(dev, CTRL_BRIGHTNESS, ctrl->val);
+		if (ret)
+			break;
+		dev->options.brightness = ctrl->val;
+		return 0;
+	case V4L2_CID_CONTRAST:
+		ret = hdpvr_config_call(dev, CTRL_CONTRAST, ctrl->val);
+		if (ret)
+			break;
+		dev->options.contrast = ctrl->val;
+		return 0;
+	case V4L2_CID_SATURATION:
+		ret = hdpvr_config_call(dev, CTRL_SATURATION, ctrl->val);
+		if (ret)
+			break;
+		dev->options.saturation = ctrl->val;
+		return 0;
+	case V4L2_CID_HUE:
+		ret = hdpvr_config_call(dev, CTRL_HUE, ctrl->val);
+		if (ret)
+			break;
+		dev->options.hue = ctrl->val;
+		return 0;
+	case V4L2_CID_SHARPNESS:
+		ret = hdpvr_config_call(dev, CTRL_SHARPNESS, ctrl->val);
+		if (ret)
+			break;
+		dev->options.sharpness = ctrl->val;
+		return 0;
+	case V4L2_CID_MPEG_AUDIO_ENCODING:
+		if (dev->flags & HDPVR_FLAG_AC3_CAP) {
+			opt->audio_codec = ctrl->val;
+			return hdpvr_set_audio(dev, opt->audio_input + 1,
+					      opt->audio_codec);
+		}
+		return 0;
+	case V4L2_CID_MPEG_VIDEO_ENCODING:
+		return 0;
+/*	case V4L2_CID_MPEG_VIDEO_B_FRAMES: */
+/*		if (ctrl->value == 0 && !(opt->gop_mode & 0x2)) { */
+/*			opt->gop_mode |= 0x2; */
+/*			hdpvr_config_call(dev, CTRL_GOP_MODE_VALUE, */
+/*					  opt->gop_mode); */
+/*		} */
+/*		if (ctrl->value == 128 && opt->gop_mode & 0x2) { */
+/*			opt->gop_mode &= ~0x2; */
+/*			hdpvr_config_call(dev, CTRL_GOP_MODE_VALUE, */
+/*					  opt->gop_mode); */
+/*		} */
+/*		break; */
+	case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: {
+		uint peak_bitrate = dev->video_bitrate_peak->val / 100000;
+		uint bitrate = dev->video_bitrate->val / 100000;
+
+		if (ctrl->is_new) {
+			if (ctrl->val == V4L2_MPEG_VIDEO_BITRATE_MODE_CBR)
+				opt->bitrate_mode = HDPVR_CONSTANT;
+			else
+				opt->bitrate_mode = HDPVR_VARIABLE_AVERAGE;
+			hdpvr_config_call(dev, CTRL_BITRATE_MODE_VALUE,
+					  opt->bitrate_mode);
+			v4l2_ctrl_activate(dev->video_bitrate_peak,
+				ctrl->val != V4L2_MPEG_VIDEO_BITRATE_MODE_CBR);
+		}
+
+		if (dev->video_bitrate_peak->is_new ||
+		    dev->video_bitrate->is_new) {
+			opt->bitrate = bitrate;
+			opt->peak_bitrate = peak_bitrate;
+			hdpvr_set_bitrate(dev);
+		}
+		return 0;
+	}
+	case V4L2_CID_MPEG_STREAM_TYPE:
+		return 0;
+	default:
+		break;
+	}
+	return ret;
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void *private_data,
+				    struct v4l2_fmtdesc *f)
+{
+	if (f->index != 0)
+		return -EINVAL;
+
+	f->flags = V4L2_FMT_FLAG_COMPRESSED;
+	strncpy(f->description, "MPEG2-TS with AVC/AAC streams", 32);
+	f->pixelformat = V4L2_PIX_FMT_MPEG;
+
+	return 0;
+}
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *_fh,
+				struct v4l2_format *f)
+{
+	struct hdpvr_device *dev = video_drvdata(file);
+	struct hdpvr_fh *fh = _fh;
+	int ret;
+
+	/*
+	 * The original driver would always returns the current detected
+	 * resolution as the format (and EFAULT if it couldn't be detected).
+	 * With the introduction of VIDIOC_QUERY_DV_TIMINGS there is now a
+	 * better way of doing this, but to stay compatible with existing
+	 * applications we assume legacy mode every time an application opens
+	 * the device. Only if one of the new DV_TIMINGS ioctls is called
+	 * will the filehandle go into 'normal' mode where g_fmt returns the
+	 * last set format.
+	 */
+	if (fh->legacy_mode) {
+		struct hdpvr_video_info vid_info;
+
+		ret = get_video_info(dev, &vid_info);
+		if (ret < 0)
+			return ret;
+		if (!vid_info.valid)
+			return -EFAULT;
+		f->fmt.pix.width = vid_info.width;
+		f->fmt.pix.height = vid_info.height;
+	} else {
+		f->fmt.pix.width = dev->width;
+		f->fmt.pix.height = dev->height;
+	}
+	f->fmt.pix.pixelformat	= V4L2_PIX_FMT_MPEG;
+	f->fmt.pix.sizeimage	= dev->bulk_in_size;
+	f->fmt.pix.bytesperline	= 0;
+	if (f->fmt.pix.width == 720) {
+		/* SDTV formats */
+		f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+		f->fmt.pix.field = V4L2_FIELD_INTERLACED;
+	} else {
+		/* HDTV formats */
+		f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709;
+		f->fmt.pix.field = V4L2_FIELD_NONE;
+	}
+	return 0;
+}
+
+static int vidioc_encoder_cmd(struct file *filp, void *priv,
+			       struct v4l2_encoder_cmd *a)
+{
+	struct hdpvr_device *dev = video_drvdata(filp);
+	int res = 0;
+
+	mutex_lock(&dev->io_mutex);
+	a->flags = 0;
+
+	switch (a->cmd) {
+	case V4L2_ENC_CMD_START:
+		if (dev->owner && filp->private_data != dev->owner) {
+			res = -EBUSY;
+			break;
+		}
+		if (dev->status == STATUS_STREAMING)
+			break;
+		res = hdpvr_start_streaming(dev);
+		if (!res)
+			dev->owner = filp->private_data;
+		else
+			dev->status = STATUS_IDLE;
+		break;
+	case V4L2_ENC_CMD_STOP:
+		if (dev->owner && filp->private_data != dev->owner) {
+			res = -EBUSY;
+			break;
+		}
+		if (dev->status == STATUS_IDLE)
+			break;
+		res = hdpvr_stop_streaming(dev);
+		if (!res)
+			dev->owner = NULL;
+		break;
+	default:
+		v4l2_dbg(MSG_INFO, hdpvr_debug, &dev->v4l2_dev,
+			 "Unsupported encoder cmd %d\n", a->cmd);
+		res = -EINVAL;
+		break;
+	}
+
+	mutex_unlock(&dev->io_mutex);
+	return res;
+}
+
+static int vidioc_try_encoder_cmd(struct file *filp, void *priv,
+					struct v4l2_encoder_cmd *a)
+{
+	a->flags = 0;
+	switch (a->cmd) {
+	case V4L2_ENC_CMD_START:
+	case V4L2_ENC_CMD_STOP:
+		return 0;
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct v4l2_ioctl_ops hdpvr_ioctl_ops = {
+	.vidioc_querycap	= vidioc_querycap,
+	.vidioc_s_std		= vidioc_s_std,
+	.vidioc_g_std		= vidioc_g_std,
+	.vidioc_querystd	= vidioc_querystd,
+	.vidioc_s_dv_timings	= vidioc_s_dv_timings,
+	.vidioc_g_dv_timings	= vidioc_g_dv_timings,
+	.vidioc_query_dv_timings= vidioc_query_dv_timings,
+	.vidioc_enum_dv_timings	= vidioc_enum_dv_timings,
+	.vidioc_dv_timings_cap	= vidioc_dv_timings_cap,
+	.vidioc_enum_input	= vidioc_enum_input,
+	.vidioc_g_input		= vidioc_g_input,
+	.vidioc_s_input		= vidioc_s_input,
+	.vidioc_enumaudio	= vidioc_enumaudio,
+	.vidioc_g_audio		= vidioc_g_audio,
+	.vidioc_s_audio		= vidioc_s_audio,
+	.vidioc_enum_fmt_vid_cap= vidioc_enum_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap	= vidioc_g_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap	= vidioc_g_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap	= vidioc_g_fmt_vid_cap,
+	.vidioc_encoder_cmd	= vidioc_encoder_cmd,
+	.vidioc_try_encoder_cmd	= vidioc_try_encoder_cmd,
+	.vidioc_log_status	= v4l2_ctrl_log_status,
+	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static void hdpvr_device_release(struct video_device *vdev)
+{
+	struct hdpvr_device *dev = video_get_drvdata(vdev);
+
+	hdpvr_delete(dev);
+	mutex_lock(&dev->io_mutex);
+	flush_work(&dev->worker);
+	mutex_unlock(&dev->io_mutex);
+
+	v4l2_device_unregister(&dev->v4l2_dev);
+	v4l2_ctrl_handler_free(&dev->hdl);
+
+	/* deregister I2C adapter */
+#if IS_ENABLED(CONFIG_I2C)
+	mutex_lock(&dev->i2c_mutex);
+	i2c_del_adapter(&dev->i2c_adapter);
+	mutex_unlock(&dev->i2c_mutex);
+#endif /* CONFIG_I2C */
+
+	kfree(dev->usbc_buf);
+	kfree(dev);
+}
+
+static const struct video_device hdpvr_video_template = {
+	.fops			= &hdpvr_fops,
+	.release		= hdpvr_device_release,
+	.ioctl_ops		= &hdpvr_ioctl_ops,
+	.tvnorms		= V4L2_STD_ALL,
+};
+
+static const struct v4l2_ctrl_ops hdpvr_ctrl_ops = {
+	.try_ctrl = hdpvr_try_ctrl,
+	.s_ctrl = hdpvr_s_ctrl,
+};
+
+int hdpvr_register_videodev(struct hdpvr_device *dev, struct device *parent,
+			    int devnum)
+{
+	struct v4l2_ctrl_handler *hdl = &dev->hdl;
+	bool ac3 = dev->flags & HDPVR_FLAG_AC3_CAP;
+	int res;
+
+	dev->cur_std = V4L2_STD_525_60;
+	dev->width = 720;
+	dev->height = 480;
+	dev->cur_dv_timings = hdpvr_dv_timings[HDPVR_DEF_DV_TIMINGS_IDX];
+	v4l2_ctrl_handler_init(hdl, 11);
+	if (dev->fw_ver > 0x15) {
+		v4l2_ctrl_new_std(hdl, &hdpvr_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0x0, 0xff, 1, 0x80);
+		v4l2_ctrl_new_std(hdl, &hdpvr_ctrl_ops,
+			V4L2_CID_CONTRAST, 0x0, 0xff, 1, 0x40);
+		v4l2_ctrl_new_std(hdl, &hdpvr_ctrl_ops,
+			V4L2_CID_SATURATION, 0x0, 0xff, 1, 0x40);
+		v4l2_ctrl_new_std(hdl, &hdpvr_ctrl_ops,
+			V4L2_CID_HUE, 0x0, 0x1e, 1, 0xf);
+		v4l2_ctrl_new_std(hdl, &hdpvr_ctrl_ops,
+			V4L2_CID_SHARPNESS, 0x0, 0xff, 1, 0x80);
+	} else {
+		v4l2_ctrl_new_std(hdl, &hdpvr_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0x0, 0xff, 1, 0x86);
+		v4l2_ctrl_new_std(hdl, &hdpvr_ctrl_ops,
+			V4L2_CID_CONTRAST, 0x0, 0xff, 1, 0x80);
+		v4l2_ctrl_new_std(hdl, &hdpvr_ctrl_ops,
+			V4L2_CID_SATURATION, 0x0, 0xff, 1, 0x80);
+		v4l2_ctrl_new_std(hdl, &hdpvr_ctrl_ops,
+			V4L2_CID_HUE, 0x0, 0xff, 1, 0x80);
+		v4l2_ctrl_new_std(hdl, &hdpvr_ctrl_ops,
+			V4L2_CID_SHARPNESS, 0x0, 0xff, 1, 0x80);
+	}
+
+	v4l2_ctrl_new_std_menu(hdl, &hdpvr_ctrl_ops,
+		V4L2_CID_MPEG_STREAM_TYPE,
+		V4L2_MPEG_STREAM_TYPE_MPEG2_TS,
+		0x1, V4L2_MPEG_STREAM_TYPE_MPEG2_TS);
+	v4l2_ctrl_new_std_menu(hdl, &hdpvr_ctrl_ops,
+		V4L2_CID_MPEG_AUDIO_ENCODING,
+		ac3 ? V4L2_MPEG_AUDIO_ENCODING_AC3 : V4L2_MPEG_AUDIO_ENCODING_AAC,
+		0x7, ac3 ? dev->options.audio_codec : V4L2_MPEG_AUDIO_ENCODING_AAC);
+	v4l2_ctrl_new_std_menu(hdl, &hdpvr_ctrl_ops,
+		V4L2_CID_MPEG_VIDEO_ENCODING,
+		V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC, 0x3,
+		V4L2_MPEG_VIDEO_ENCODING_MPEG_4_AVC);
+
+	dev->video_mode = v4l2_ctrl_new_std_menu(hdl, &hdpvr_ctrl_ops,
+		V4L2_CID_MPEG_VIDEO_BITRATE_MODE,
+		V4L2_MPEG_VIDEO_BITRATE_MODE_CBR, 0,
+		V4L2_MPEG_VIDEO_BITRATE_MODE_CBR);
+
+	dev->video_bitrate = v4l2_ctrl_new_std(hdl, &hdpvr_ctrl_ops,
+		V4L2_CID_MPEG_VIDEO_BITRATE,
+		1000000, 13500000, 100000, 6500000);
+	dev->video_bitrate_peak = v4l2_ctrl_new_std(hdl, &hdpvr_ctrl_ops,
+		V4L2_CID_MPEG_VIDEO_BITRATE_PEAK,
+		1100000, 20200000, 100000, 9000000);
+	dev->v4l2_dev.ctrl_handler = hdl;
+	if (hdl->error) {
+		res = hdl->error;
+		v4l2_err(&dev->v4l2_dev, "Could not register controls\n");
+		goto error;
+	}
+	v4l2_ctrl_cluster(3, &dev->video_mode);
+	res = v4l2_ctrl_handler_setup(hdl);
+	if (res < 0) {
+		v4l2_err(&dev->v4l2_dev, "Could not setup controls\n");
+		goto error;
+	}
+
+	/* setup and register video device */
+	dev->video_dev = hdpvr_video_template;
+	strcpy(dev->video_dev.name, "Hauppauge HD PVR");
+	dev->video_dev.v4l2_dev = &dev->v4l2_dev;
+	video_set_drvdata(&dev->video_dev, dev);
+
+	res = video_register_device(&dev->video_dev, VFL_TYPE_GRABBER, devnum);
+	if (res < 0) {
+		v4l2_err(&dev->v4l2_dev, "video_device registration failed\n");
+		goto error;
+	}
+
+	return 0;
+error:
+	v4l2_ctrl_handler_free(hdl);
+	return res;
+}
diff --git a/drivers/media/usb/hdpvr/hdpvr.h b/drivers/media/usb/hdpvr/hdpvr.h
new file mode 100644
index 0000000..1d65b41
--- /dev/null
+++ b/drivers/media/usb/hdpvr/hdpvr.h
@@ -0,0 +1,328 @@
+/*
+ * Hauppauge HD PVR USB driver
+ *
+ * Copyright (C) 2008      Janne Grunau (j@jannau.net)
+ *
+ *	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, version 2.
+ *
+ */
+
+#include <linux/usb.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+#include <linux/videodev2.h>
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/i2c/ir-kbd-i2c.h>
+
+#define HDPVR_MAX 8
+#define HDPVR_I2C_MAX_SIZE 128
+
+/* Define these values to match your devices */
+#define HD_PVR_VENDOR_ID	0x2040
+#define HD_PVR_PRODUCT_ID	0x4900
+#define HD_PVR_PRODUCT_ID1	0x4901
+#define HD_PVR_PRODUCT_ID2	0x4902
+#define HD_PVR_PRODUCT_ID4	0x4903
+#define HD_PVR_PRODUCT_ID3	0x4982
+
+#define UNSET    (-1U)
+
+#define NUM_BUFFERS 64
+
+#define HDPVR_FIRMWARE_VERSION		0x08
+#define HDPVR_FIRMWARE_VERSION_AC3	0x0d
+#define HDPVR_FIRMWARE_VERSION_0X12	0x12
+#define HDPVR_FIRMWARE_VERSION_0X15	0x15
+#define HDPVR_FIRMWARE_VERSION_0X1E	0x1e
+
+/* #define HDPVR_DEBUG */
+
+extern int hdpvr_debug;
+
+#define MSG_INFO	1
+#define MSG_BUFFER	2
+
+struct hdpvr_options {
+	u8	video_std;
+	u8	video_input;
+	u8	audio_input;
+	u8	bitrate;	/* in 100kbps */
+	u8	peak_bitrate;	/* in 100kbps */
+	u8	bitrate_mode;
+	u8	gop_mode;
+	enum v4l2_mpeg_audio_encoding	audio_codec;
+	u8	brightness;
+	u8	contrast;
+	u8	hue;
+	u8	saturation;
+	u8	sharpness;
+};
+
+/* Structure to hold all of our device specific stuff */
+struct hdpvr_device {
+	/* the v4l device for this device */
+	struct video_device	video_dev;
+	/* the control handler for this device */
+	struct v4l2_ctrl_handler hdl;
+	/* the usb device for this device */
+	struct usb_device	*udev;
+	/* v4l2-device unused */
+	struct v4l2_device	v4l2_dev;
+	struct { /* video mode/bitrate control cluster */
+		struct v4l2_ctrl *video_mode;
+		struct v4l2_ctrl *video_bitrate;
+		struct v4l2_ctrl *video_bitrate_peak;
+	};
+	/* v4l2 format */
+	uint width, height;
+
+	/* the max packet size of the bulk endpoint */
+	size_t			bulk_in_size;
+	/* the address of the bulk in endpoint */
+	__u8			bulk_in_endpointAddr;
+
+	/* holds the current device status */
+	__u8			status;
+
+	/* holds the current set options */
+	struct hdpvr_options	options;
+	v4l2_std_id		cur_std;
+	struct v4l2_dv_timings	cur_dv_timings;
+
+	uint			flags;
+
+	/* synchronize I/O */
+	struct mutex		io_mutex;
+	/* available buffers */
+	struct list_head	free_buff_list;
+	/* in progress buffers */
+	struct list_head	rec_buff_list;
+	/* waitqueue for buffers */
+	wait_queue_head_t	wait_buffer;
+	/* waitqueue for data */
+	wait_queue_head_t	wait_data;
+	/**/
+	struct work_struct	worker;
+	/* current stream owner */
+	struct v4l2_fh		*owner;
+
+	/* I2C adapter */
+	struct i2c_adapter	i2c_adapter;
+	/* I2C lock */
+	struct mutex		i2c_mutex;
+	/* I2C message buffer space */
+	char			i2c_buf[HDPVR_I2C_MAX_SIZE];
+
+	/* For passing data to ir-kbd-i2c */
+	struct IR_i2c_init_data	ir_i2c_init_data;
+
+	/* usb control transfer buffer and lock */
+	struct mutex		usbc_mutex;
+	u8			*usbc_buf;
+	u8			fw_ver;
+};
+
+static inline struct hdpvr_device *to_hdpvr_dev(struct v4l2_device *v4l2_dev)
+{
+	return container_of(v4l2_dev, struct hdpvr_device, v4l2_dev);
+}
+
+
+/* buffer one bulk urb of data */
+struct hdpvr_buffer {
+	struct list_head	buff_list;
+
+	struct urb		*urb;
+
+	struct hdpvr_device	*dev;
+
+	uint			pos;
+
+	__u8			status;
+};
+
+/* */
+
+struct hdpvr_video_info {
+	u16	width;
+	u16	height;
+	u8	fps;
+	bool	valid;
+};
+
+enum {
+	STATUS_UNINITIALIZED	= 0,
+	STATUS_IDLE,
+	STATUS_STARTING,
+	STATUS_SHUTTING_DOWN,
+	STATUS_STREAMING,
+	STATUS_ERROR,
+	STATUS_DISCONNECTED,
+};
+
+enum {
+	HDPVR_FLAG_AC3_CAP = 1,
+};
+
+enum {
+	BUFSTAT_UNINITIALIZED = 0,
+	BUFSTAT_AVAILABLE,
+	BUFSTAT_INPROGRESS,
+	BUFSTAT_READY,
+};
+
+#define CTRL_START_STREAMING_VALUE	0x0700
+#define CTRL_STOP_STREAMING_VALUE	0x0800
+#define CTRL_BITRATE_VALUE		0x1000
+#define CTRL_BITRATE_MODE_VALUE		0x1200
+#define CTRL_GOP_MODE_VALUE		0x1300
+#define CTRL_VIDEO_INPUT_VALUE		0x1500
+#define CTRL_VIDEO_STD_TYPE		0x1700
+#define CTRL_AUDIO_INPUT_VALUE		0x2500
+#define CTRL_BRIGHTNESS			0x2900
+#define CTRL_CONTRAST			0x2a00
+#define CTRL_HUE			0x2b00
+#define CTRL_SATURATION			0x2c00
+#define CTRL_SHARPNESS			0x2d00
+#define CTRL_LOW_PASS_FILTER_VALUE	0x3100
+
+#define CTRL_DEFAULT_INDEX		0x0003
+
+
+	/* :0 s 38 01 1000 0003 0004 4 = 0a00ca00
+	 * BITRATE SETTING
+	 *   1st and 2nd byte (little endian): average bitrate in 100 000 bit/s
+	 *                                     min: 1 mbit/s, max: 13.5 mbit/s
+	 *   3rd and 4th byte (little endian): peak bitrate in 100 000 bit/s
+	 *                                     min: average + 100kbit/s,
+	 *                                      max: 20.2 mbit/s
+	 */
+
+	/* :0 s 38 01 1200 0003 0001 1 = 02
+	 * BIT RATE MODE
+	 *  constant = 1, variable (peak) = 2, variable (average) = 3
+	 */
+
+	/* :0 s 38 01 1300 0003 0001 1 = 03
+	 * GOP MODE (2 bit)
+	 *    low bit 0/1: advanced/simple GOP
+	 *   high bit 0/1: IDR(4/32/128) / no IDR (4/32/0)
+	 */
+
+	/* :0 s 38 01 1700 0003 0001 1 = 00
+	 * VIDEO STANDARD or FREQUNCY 0 = 60hz, 1 = 50hz
+	 */
+
+	/* :0 s 38 01 3100 0003 0004 4 = 03030000
+	 * FILTER CONTROL
+	 *   1st byte luma low pass filter strength,
+	 *   2nd byte chroma low pass filter strength,
+	 *   3rd byte MF enable chroma, min=0, max=1
+	 *   4th byte n
+	 */
+
+
+	/* :0 s 38 b9 0001 0000 0000 0 */
+
+
+
+/* :0 s 38 d3 0000 0000 0001 1 = 00 */
+/*		ret = usb_control_msg(dev->udev, */
+/*				      usb_sndctrlpipe(dev->udev, 0), */
+/*				      0xd3, 0x38, */
+/*				      0, 0, */
+/*				      "\0", 1, */
+/*				      1000); */
+
+/*		info("control request returned %d", ret); */
+/*		msleep(5000); */
+
+
+	/* :0 s b8 81 1400 0003 0005 5 <
+	 * :0 0 5 = d0024002 19
+	 * QUERY FRAME SIZE AND RATE
+	 *   1st and 2nd byte (little endian): horizontal resolution
+	 *   3rd and 4th byte (little endian): vertical resolution
+	 *   5th byte: frame rate
+	 */
+
+	/* :0 s b8 81 1800 0003 0003 3 <
+	 * :0 0 3 = 030104
+	 * QUERY SIGNAL AND DETECTED LINES, maybe INPUT
+	 */
+
+enum hdpvr_video_std {
+	HDPVR_60HZ = 0,
+	HDPVR_50HZ,
+};
+
+enum hdpvr_video_input {
+	HDPVR_COMPONENT = 0,
+	HDPVR_SVIDEO,
+	HDPVR_COMPOSITE,
+	HDPVR_VIDEO_INPUTS
+};
+
+enum hdpvr_audio_inputs {
+	HDPVR_RCA_BACK = 0,
+	HDPVR_RCA_FRONT,
+	HDPVR_SPDIF,
+	HDPVR_AUDIO_INPUTS
+};
+
+enum hdpvr_bitrate_mode {
+	HDPVR_CONSTANT = 1,
+	HDPVR_VARIABLE_PEAK,
+	HDPVR_VARIABLE_AVERAGE,
+};
+
+enum hdpvr_gop_mode {
+	HDPVR_ADVANCED_IDR_GOP = 0,
+	HDPVR_SIMPLE_IDR_GOP,
+	HDPVR_ADVANCED_NOIDR_GOP,
+	HDPVR_SIMPLE_NOIDR_GOP,
+};
+
+void hdpvr_delete(struct hdpvr_device *dev);
+
+/*========================================================================*/
+/* hardware control functions */
+int hdpvr_set_options(struct hdpvr_device *dev);
+
+int hdpvr_set_bitrate(struct hdpvr_device *dev);
+
+int hdpvr_set_audio(struct hdpvr_device *dev, u8 input,
+		    enum v4l2_mpeg_audio_encoding codec);
+
+int hdpvr_config_call(struct hdpvr_device *dev, uint value,
+		      unsigned char valbuf);
+
+int get_video_info(struct hdpvr_device *dev, struct hdpvr_video_info *vid_info);
+
+/* :0 s b8 81 1800 0003 0003 3 < */
+/* :0 0 3 = 0301ff */
+int get_input_lines_info(struct hdpvr_device *dev);
+
+
+/*========================================================================*/
+/* v4l2 registration */
+int hdpvr_register_videodev(struct hdpvr_device *dev, struct device *parent,
+			    int devnumber);
+
+int hdpvr_cancel_queue(struct hdpvr_device *dev);
+
+/*========================================================================*/
+/* i2c adapter registration */
+int hdpvr_register_i2c_adapter(struct hdpvr_device *dev);
+
+struct i2c_client *hdpvr_register_ir_i2c(struct hdpvr_device *dev);
+
+/*========================================================================*/
+/* buffer management */
+int hdpvr_free_buffers(struct hdpvr_device *dev);
+int hdpvr_alloc_buffers(struct hdpvr_device *dev, uint count);
diff --git a/drivers/media/usb/msi2500/Kconfig b/drivers/media/usb/msi2500/Kconfig
new file mode 100644
index 0000000..9eff8a7
--- /dev/null
+++ b/drivers/media/usb/msi2500/Kconfig
@@ -0,0 +1,5 @@
+config USB_MSI2500
+	tristate "Mirics MSi2500"
+	depends on VIDEO_V4L2 && SPI
+	select VIDEOBUF2_VMALLOC
+	select MEDIA_TUNER_MSI001
diff --git a/drivers/media/usb/msi2500/Makefile b/drivers/media/usb/msi2500/Makefile
new file mode 100644
index 0000000..b3bc2e5
--- /dev/null
+++ b/drivers/media/usb/msi2500/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_USB_MSI2500)             += msi2500.o
diff --git a/drivers/media/usb/msi2500/msi2500.c b/drivers/media/usb/msi2500/msi2500.c
new file mode 100644
index 0000000..65ef755
--- /dev/null
+++ b/drivers/media/usb/msi2500/msi2500.c
@@ -0,0 +1,1330 @@
+/*
+ * Mirics MSi2500 driver
+ * Mirics MSi3101 SDR Dongle driver
+ *
+ * Copyright (C) 2013 Antti Palosaari <crope@iki.fi>
+ *
+ *    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.
+ *
+ * That driver is somehow based of pwc driver:
+ *  (C) 1999-2004 Nemosoft Unv.
+ *  (C) 2004-2006 Luc Saillard (luc@saillard.org)
+ *  (C) 2011 Hans de Goede <hdegoede@redhat.com>
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <asm/div64.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <linux/usb.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-vmalloc.h>
+#include <linux/spi/spi.h>
+
+static bool msi2500_emulated_fmt;
+module_param_named(emulated_formats, msi2500_emulated_fmt, bool, 0644);
+MODULE_PARM_DESC(emulated_formats, "enable emulated formats (disappears in future)");
+
+/*
+ *   iConfiguration          0
+ *     bInterfaceNumber        0
+ *     bAlternateSetting       1
+ *     bNumEndpoints           1
+ *       bEndpointAddress     0x81  EP 1 IN
+ *       bmAttributes            1
+ *         Transfer Type            Isochronous
+ *       wMaxPacketSize     0x1400  3x 1024 bytes
+ *       bInterval               1
+ */
+#define MAX_ISO_BUFS            (8)
+#define ISO_FRAMES_PER_DESC     (8)
+#define ISO_MAX_FRAME_SIZE      (3 * 1024)
+#define ISO_BUFFER_SIZE         (ISO_FRAMES_PER_DESC * ISO_MAX_FRAME_SIZE)
+#define MAX_ISOC_ERRORS         20
+
+/*
+ * TODO: These formats should be moved to V4L2 API. Formats are currently
+ * disabled from formats[] table, not visible to userspace.
+ */
+ /* signed 12-bit */
+#define MSI2500_PIX_FMT_SDR_S12         v4l2_fourcc('D', 'S', '1', '2')
+/* Mirics MSi2500 format 384 */
+#define MSI2500_PIX_FMT_SDR_MSI2500_384 v4l2_fourcc('M', '3', '8', '4')
+
+static const struct v4l2_frequency_band bands[] = {
+	{
+		.tuner = 0,
+		.type = V4L2_TUNER_ADC,
+		.index = 0,
+		.capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS,
+		.rangelow   =  1200000,
+		.rangehigh  = 15000000,
+	},
+};
+
+/* stream formats */
+struct msi2500_format {
+	char	*name;
+	u32	pixelformat;
+	u32	buffersize;
+};
+
+/* format descriptions for capture and preview */
+static struct msi2500_format formats[] = {
+	{
+		.name		= "Complex S8",
+		.pixelformat	= V4L2_SDR_FMT_CS8,
+		.buffersize	= 3 * 1008,
+#if 0
+	}, {
+		.name		= "10+2-bit signed",
+		.pixelformat	= MSI2500_PIX_FMT_SDR_MSI2500_384,
+	}, {
+		.name		= "12-bit signed",
+		.pixelformat	= MSI2500_PIX_FMT_SDR_S12,
+#endif
+	}, {
+		.name		= "Complex S14LE",
+		.pixelformat	= V4L2_SDR_FMT_CS14LE,
+		.buffersize	= 3 * 1008,
+	}, {
+		.name		= "Complex U8 (emulated)",
+		.pixelformat	= V4L2_SDR_FMT_CU8,
+		.buffersize	= 3 * 1008,
+	}, {
+		.name		= "Complex U16LE (emulated)",
+		.pixelformat	=  V4L2_SDR_FMT_CU16LE,
+		.buffersize	= 3 * 1008,
+	},
+};
+
+static const unsigned int NUM_FORMATS = ARRAY_SIZE(formats);
+
+/* intermediate buffers with raw data from the USB device */
+struct msi2500_frame_buf {
+	/* common v4l buffer stuff -- must be first */
+	struct vb2_v4l2_buffer vb;
+	struct list_head list;
+};
+
+struct msi2500_dev {
+	struct device *dev;
+	struct video_device vdev;
+	struct v4l2_device v4l2_dev;
+	struct v4l2_subdev *v4l2_subdev;
+	struct spi_master *master;
+
+	/* videobuf2 queue and queued buffers list */
+	struct vb2_queue vb_queue;
+	struct list_head queued_bufs;
+	spinlock_t queued_bufs_lock; /* Protects queued_bufs */
+
+	/* Note if taking both locks v4l2_lock must always be locked first! */
+	struct mutex v4l2_lock;      /* Protects everything else */
+	struct mutex vb_queue_lock;  /* Protects vb_queue and capt_file */
+
+	/* Pointer to our usb_device, will be NULL after unplug */
+	struct usb_device *udev; /* Both mutexes most be hold when setting! */
+
+	unsigned int f_adc;
+	u32 pixelformat;
+	u32 buffersize;
+	unsigned int num_formats;
+
+	unsigned int isoc_errors; /* number of contiguous ISOC errors */
+	unsigned int vb_full; /* vb is full and packets dropped */
+
+	struct urb *urbs[MAX_ISO_BUFS];
+
+	/* Controls */
+	struct v4l2_ctrl_handler hdl;
+
+	u32 next_sample; /* for track lost packets */
+	u32 sample; /* for sample rate calc */
+	unsigned long jiffies_next;
+};
+
+/* Private functions */
+static struct msi2500_frame_buf *msi2500_get_next_fill_buf(
+							struct msi2500_dev *dev)
+{
+	unsigned long flags;
+	struct msi2500_frame_buf *buf = NULL;
+
+	spin_lock_irqsave(&dev->queued_bufs_lock, flags);
+	if (list_empty(&dev->queued_bufs))
+		goto leave;
+
+	buf = list_entry(dev->queued_bufs.next, struct msi2500_frame_buf, list);
+	list_del(&buf->list);
+leave:
+	spin_unlock_irqrestore(&dev->queued_bufs_lock, flags);
+	return buf;
+}
+
+/*
+ * +===========================================================================
+ * |   00-1023 | USB packet type '504'
+ * +===========================================================================
+ * |   00-  03 | sequence number of first sample in that USB packet
+ * +---------------------------------------------------------------------------
+ * |   04-  15 | garbage
+ * +---------------------------------------------------------------------------
+ * |   16-1023 | samples
+ * +---------------------------------------------------------------------------
+ * signed 8-bit sample
+ * 504 * 2 = 1008 samples
+ *
+ *
+ * +===========================================================================
+ * |   00-1023 | USB packet type '384'
+ * +===========================================================================
+ * |   00-  03 | sequence number of first sample in that USB packet
+ * +---------------------------------------------------------------------------
+ * |   04-  15 | garbage
+ * +---------------------------------------------------------------------------
+ * |   16- 175 | samples
+ * +---------------------------------------------------------------------------
+ * |  176- 179 | control bits for previous samples
+ * +---------------------------------------------------------------------------
+ * |  180- 339 | samples
+ * +---------------------------------------------------------------------------
+ * |  340- 343 | control bits for previous samples
+ * +---------------------------------------------------------------------------
+ * |  344- 503 | samples
+ * +---------------------------------------------------------------------------
+ * |  504- 507 | control bits for previous samples
+ * +---------------------------------------------------------------------------
+ * |  508- 667 | samples
+ * +---------------------------------------------------------------------------
+ * |  668- 671 | control bits for previous samples
+ * +---------------------------------------------------------------------------
+ * |  672- 831 | samples
+ * +---------------------------------------------------------------------------
+ * |  832- 835 | control bits for previous samples
+ * +---------------------------------------------------------------------------
+ * |  836- 995 | samples
+ * +---------------------------------------------------------------------------
+ * |  996- 999 | control bits for previous samples
+ * +---------------------------------------------------------------------------
+ * | 1000-1023 | garbage
+ * +---------------------------------------------------------------------------
+ *
+ * Bytes 4 - 7 could have some meaning?
+ *
+ * Control bits for previous samples is 32-bit field, containing 16 x 2-bit
+ * numbers. This results one 2-bit number for 8 samples. It is likely used for
+ * for bit shifting sample by given bits, increasing actual sampling resolution.
+ * Number 2 (0b10) was never seen.
+ *
+ * 6 * 16 * 2 * 4 = 768 samples. 768 * 4 = 3072 bytes
+ *
+ *
+ * +===========================================================================
+ * |   00-1023 | USB packet type '336'
+ * +===========================================================================
+ * |   00-  03 | sequence number of first sample in that USB packet
+ * +---------------------------------------------------------------------------
+ * |   04-  15 | garbage
+ * +---------------------------------------------------------------------------
+ * |   16-1023 | samples
+ * +---------------------------------------------------------------------------
+ * signed 12-bit sample
+ *
+ *
+ * +===========================================================================
+ * |   00-1023 | USB packet type '252'
+ * +===========================================================================
+ * |   00-  03 | sequence number of first sample in that USB packet
+ * +---------------------------------------------------------------------------
+ * |   04-  15 | garbage
+ * +---------------------------------------------------------------------------
+ * |   16-1023 | samples
+ * +---------------------------------------------------------------------------
+ * signed 14-bit sample
+ */
+
+static int msi2500_convert_stream(struct msi2500_dev *dev, u8 *dst, u8 *src,
+				  unsigned int src_len)
+{
+	unsigned int i, j, transactions, dst_len = 0;
+	u32 sample[3];
+
+	/* There could be 1-3 1024 byte transactions per packet */
+	transactions = src_len / 1024;
+
+	for (i = 0; i < transactions; i++) {
+		sample[i] = src[3] << 24 | src[2] << 16 | src[1] << 8 |
+				src[0] << 0;
+		if (i == 0 && dev->next_sample != sample[0]) {
+			dev_dbg_ratelimited(dev->dev,
+					    "%d samples lost, %d %08x:%08x\n",
+					    sample[0] - dev->next_sample,
+					    src_len, dev->next_sample,
+					    sample[0]);
+		}
+
+		/*
+		 * Dump all unknown 'garbage' data - maybe we will discover
+		 * someday if there is something rational...
+		 */
+		dev_dbg_ratelimited(dev->dev, "%*ph\n", 12, &src[4]);
+
+		src += 16; /* skip header */
+
+		switch (dev->pixelformat) {
+		case V4L2_SDR_FMT_CU8: /* 504 x IQ samples */
+		{
+			s8 *s8src = (s8 *)src;
+			u8 *u8dst = (u8 *)dst;
+
+			for (j = 0; j < 1008; j++)
+				*u8dst++ = *s8src++ + 128;
+
+			src += 1008;
+			dst += 1008;
+			dst_len += 1008;
+			dev->next_sample = sample[i] + 504;
+			break;
+		}
+		case  V4L2_SDR_FMT_CU16LE: /* 252 x IQ samples */
+		{
+			s16 *s16src = (s16 *)src;
+			u16 *u16dst = (u16 *)dst;
+			struct {signed int x:14; } se; /* sign extension */
+			unsigned int utmp;
+
+			for (j = 0; j < 1008; j += 2) {
+				/* sign extension from 14-bit to signed int */
+				se.x = *s16src++;
+				/* from signed int to unsigned int */
+				utmp = se.x + 8192;
+				/* from 14-bit to 16-bit */
+				*u16dst++ = utmp << 2 | utmp >> 12;
+			}
+
+			src += 1008;
+			dst += 1008;
+			dst_len += 1008;
+			dev->next_sample = sample[i] + 252;
+			break;
+		}
+		case MSI2500_PIX_FMT_SDR_MSI2500_384: /* 384 x IQ samples */
+			/* Dump unknown 'garbage' data */
+			dev_dbg_ratelimited(dev->dev, "%*ph\n", 24, &src[1000]);
+			memcpy(dst, src, 984);
+			src += 984 + 24;
+			dst += 984;
+			dst_len += 984;
+			dev->next_sample = sample[i] + 384;
+			break;
+		case V4L2_SDR_FMT_CS8:         /* 504 x IQ samples */
+			memcpy(dst, src, 1008);
+			src += 1008;
+			dst += 1008;
+			dst_len += 1008;
+			dev->next_sample = sample[i] + 504;
+			break;
+		case MSI2500_PIX_FMT_SDR_S12:  /* 336 x IQ samples */
+			memcpy(dst, src, 1008);
+			src += 1008;
+			dst += 1008;
+			dst_len += 1008;
+			dev->next_sample = sample[i] + 336;
+			break;
+		case V4L2_SDR_FMT_CS14LE:      /* 252 x IQ samples */
+			memcpy(dst, src, 1008);
+			src += 1008;
+			dst += 1008;
+			dst_len += 1008;
+			dev->next_sample = sample[i] + 252;
+			break;
+		default:
+			break;
+		}
+	}
+
+	/* calculate sample rate and output it in 10 seconds intervals */
+	if (unlikely(time_is_before_jiffies(dev->jiffies_next))) {
+		#define MSECS 10000UL
+		unsigned int msecs = jiffies_to_msecs(jiffies -
+				dev->jiffies_next + msecs_to_jiffies(MSECS));
+		unsigned int samples = dev->next_sample - dev->sample;
+
+		dev->jiffies_next = jiffies + msecs_to_jiffies(MSECS);
+		dev->sample = dev->next_sample;
+		dev_dbg(dev->dev, "size=%u samples=%u msecs=%u sample rate=%lu\n",
+			src_len, samples, msecs,
+			samples * 1000UL / msecs);
+	}
+
+	return dst_len;
+}
+
+/*
+ * This gets called for the Isochronous pipe (stream). This is done in interrupt
+ * time, so it has to be fast, not crash, and not stall. Neat.
+ */
+static void msi2500_isoc_handler(struct urb *urb)
+{
+	struct msi2500_dev *dev = (struct msi2500_dev *)urb->context;
+	int i, flen, fstatus;
+	unsigned char *iso_buf = NULL;
+	struct msi2500_frame_buf *fbuf;
+
+	if (unlikely(urb->status == -ENOENT ||
+		     urb->status == -ECONNRESET ||
+		     urb->status == -ESHUTDOWN)) {
+		dev_dbg(dev->dev, "URB (%p) unlinked %ssynchronously\n",
+			urb, urb->status == -ENOENT ? "" : "a");
+		return;
+	}
+
+	if (unlikely(urb->status != 0)) {
+		dev_dbg(dev->dev, "called with status %d\n", urb->status);
+		/* Give up after a number of contiguous errors */
+		if (++dev->isoc_errors > MAX_ISOC_ERRORS)
+			dev_dbg(dev->dev, "Too many ISOC errors, bailing out\n");
+		goto handler_end;
+	} else {
+		/* Reset ISOC error counter. We did get here, after all. */
+		dev->isoc_errors = 0;
+	}
+
+	/* Compact data */
+	for (i = 0; i < urb->number_of_packets; i++) {
+		void *ptr;
+
+		/* Check frame error */
+		fstatus = urb->iso_frame_desc[i].status;
+		if (unlikely(fstatus)) {
+			dev_dbg_ratelimited(dev->dev,
+					    "frame=%d/%d has error %d skipping\n",
+					    i, urb->number_of_packets, fstatus);
+			continue;
+		}
+
+		/* Check if that frame contains data */
+		flen = urb->iso_frame_desc[i].actual_length;
+		if (unlikely(flen == 0))
+			continue;
+
+		iso_buf = urb->transfer_buffer + urb->iso_frame_desc[i].offset;
+
+		/* Get free framebuffer */
+		fbuf = msi2500_get_next_fill_buf(dev);
+		if (unlikely(fbuf == NULL)) {
+			dev->vb_full++;
+			dev_dbg_ratelimited(dev->dev,
+					    "videobuf is full, %d packets dropped\n",
+					    dev->vb_full);
+			continue;
+		}
+
+		/* fill framebuffer */
+		ptr = vb2_plane_vaddr(&fbuf->vb.vb2_buf, 0);
+		flen = msi2500_convert_stream(dev, ptr, iso_buf, flen);
+		vb2_set_plane_payload(&fbuf->vb.vb2_buf, 0, flen);
+		vb2_buffer_done(&fbuf->vb.vb2_buf, VB2_BUF_STATE_DONE);
+	}
+
+handler_end:
+	i = usb_submit_urb(urb, GFP_ATOMIC);
+	if (unlikely(i != 0))
+		dev_dbg(dev->dev, "Error (%d) re-submitting urb\n", i);
+}
+
+static void msi2500_iso_stop(struct msi2500_dev *dev)
+{
+	int i;
+
+	dev_dbg(dev->dev, "\n");
+
+	/* Unlinking ISOC buffers one by one */
+	for (i = 0; i < MAX_ISO_BUFS; i++) {
+		if (dev->urbs[i]) {
+			dev_dbg(dev->dev, "Unlinking URB %p\n", dev->urbs[i]);
+			usb_kill_urb(dev->urbs[i]);
+		}
+	}
+}
+
+static void msi2500_iso_free(struct msi2500_dev *dev)
+{
+	int i;
+
+	dev_dbg(dev->dev, "\n");
+
+	/* Freeing ISOC buffers one by one */
+	for (i = 0; i < MAX_ISO_BUFS; i++) {
+		if (dev->urbs[i]) {
+			dev_dbg(dev->dev, "Freeing URB\n");
+			if (dev->urbs[i]->transfer_buffer) {
+				usb_free_coherent(dev->udev,
+					dev->urbs[i]->transfer_buffer_length,
+					dev->urbs[i]->transfer_buffer,
+					dev->urbs[i]->transfer_dma);
+			}
+			usb_free_urb(dev->urbs[i]);
+			dev->urbs[i] = NULL;
+		}
+	}
+}
+
+/* Both v4l2_lock and vb_queue_lock should be locked when calling this */
+static void msi2500_isoc_cleanup(struct msi2500_dev *dev)
+{
+	dev_dbg(dev->dev, "\n");
+
+	msi2500_iso_stop(dev);
+	msi2500_iso_free(dev);
+}
+
+/* Both v4l2_lock and vb_queue_lock should be locked when calling this */
+static int msi2500_isoc_init(struct msi2500_dev *dev)
+{
+	struct urb *urb;
+	int i, j, ret;
+
+	dev_dbg(dev->dev, "\n");
+
+	dev->isoc_errors = 0;
+
+	ret = usb_set_interface(dev->udev, 0, 1);
+	if (ret)
+		return ret;
+
+	/* Allocate and init Isochronuous urbs */
+	for (i = 0; i < MAX_ISO_BUFS; i++) {
+		urb = usb_alloc_urb(ISO_FRAMES_PER_DESC, GFP_KERNEL);
+		if (urb == NULL) {
+			msi2500_isoc_cleanup(dev);
+			return -ENOMEM;
+		}
+		dev->urbs[i] = urb;
+		dev_dbg(dev->dev, "Allocated URB at 0x%p\n", urb);
+
+		urb->interval = 1;
+		urb->dev = dev->udev;
+		urb->pipe = usb_rcvisocpipe(dev->udev, 0x81);
+		urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
+		urb->transfer_buffer = usb_alloc_coherent(dev->udev,
+				ISO_BUFFER_SIZE,
+				GFP_KERNEL, &urb->transfer_dma);
+		if (urb->transfer_buffer == NULL) {
+			dev_err(dev->dev,
+				"Failed to allocate urb buffer %d\n", i);
+			msi2500_isoc_cleanup(dev);
+			return -ENOMEM;
+		}
+		urb->transfer_buffer_length = ISO_BUFFER_SIZE;
+		urb->complete = msi2500_isoc_handler;
+		urb->context = dev;
+		urb->start_frame = 0;
+		urb->number_of_packets = ISO_FRAMES_PER_DESC;
+		for (j = 0; j < ISO_FRAMES_PER_DESC; j++) {
+			urb->iso_frame_desc[j].offset = j * ISO_MAX_FRAME_SIZE;
+			urb->iso_frame_desc[j].length = ISO_MAX_FRAME_SIZE;
+		}
+	}
+
+	/* link */
+	for (i = 0; i < MAX_ISO_BUFS; i++) {
+		ret = usb_submit_urb(dev->urbs[i], GFP_KERNEL);
+		if (ret) {
+			dev_err(dev->dev,
+				"usb_submit_urb %d failed with error %d\n",
+				i, ret);
+			msi2500_isoc_cleanup(dev);
+			return ret;
+		}
+		dev_dbg(dev->dev, "URB 0x%p submitted.\n", dev->urbs[i]);
+	}
+
+	/* All is done... */
+	return 0;
+}
+
+/* Must be called with vb_queue_lock hold */
+static void msi2500_cleanup_queued_bufs(struct msi2500_dev *dev)
+{
+	unsigned long flags;
+
+	dev_dbg(dev->dev, "\n");
+
+	spin_lock_irqsave(&dev->queued_bufs_lock, flags);
+	while (!list_empty(&dev->queued_bufs)) {
+		struct msi2500_frame_buf *buf;
+
+		buf = list_entry(dev->queued_bufs.next,
+				 struct msi2500_frame_buf, list);
+		list_del(&buf->list);
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+	}
+	spin_unlock_irqrestore(&dev->queued_bufs_lock, flags);
+}
+
+/* The user yanked out the cable... */
+static void msi2500_disconnect(struct usb_interface *intf)
+{
+	struct v4l2_device *v = usb_get_intfdata(intf);
+	struct msi2500_dev *dev =
+			container_of(v, struct msi2500_dev, v4l2_dev);
+
+	dev_dbg(dev->dev, "\n");
+
+	mutex_lock(&dev->vb_queue_lock);
+	mutex_lock(&dev->v4l2_lock);
+	/* No need to keep the urbs around after disconnection */
+	dev->udev = NULL;
+	v4l2_device_disconnect(&dev->v4l2_dev);
+	video_unregister_device(&dev->vdev);
+	spi_unregister_master(dev->master);
+	mutex_unlock(&dev->v4l2_lock);
+	mutex_unlock(&dev->vb_queue_lock);
+
+	v4l2_device_put(&dev->v4l2_dev);
+}
+
+static int msi2500_querycap(struct file *file, void *fh,
+			    struct v4l2_capability *cap)
+{
+	struct msi2500_dev *dev = video_drvdata(file);
+
+	dev_dbg(dev->dev, "\n");
+
+	strlcpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver));
+	strlcpy(cap->card, dev->vdev.name, sizeof(cap->card));
+	usb_make_path(dev->udev, cap->bus_info, sizeof(cap->bus_info));
+	cap->device_caps = V4L2_CAP_SDR_CAPTURE | V4L2_CAP_STREAMING |
+			V4L2_CAP_READWRITE | V4L2_CAP_TUNER;
+	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+	return 0;
+}
+
+/* Videobuf2 operations */
+static int msi2500_queue_setup(struct vb2_queue *vq,
+			       unsigned int *nbuffers,
+			       unsigned int *nplanes, unsigned int sizes[],
+			       struct device *alloc_devs[])
+{
+	struct msi2500_dev *dev = vb2_get_drv_priv(vq);
+
+	dev_dbg(dev->dev, "nbuffers=%d\n", *nbuffers);
+
+	/* Absolute min and max number of buffers available for mmap() */
+	*nbuffers = clamp_t(unsigned int, *nbuffers, 8, 32);
+	*nplanes = 1;
+	sizes[0] = PAGE_ALIGN(dev->buffersize);
+	dev_dbg(dev->dev, "nbuffers=%d sizes[0]=%d\n", *nbuffers, sizes[0]);
+	return 0;
+}
+
+static void msi2500_buf_queue(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct msi2500_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
+	struct msi2500_frame_buf *buf = container_of(vbuf,
+						     struct msi2500_frame_buf,
+						     vb);
+	unsigned long flags;
+
+	/* Check the device has not disconnected between prep and queuing */
+	if (unlikely(!dev->udev)) {
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+		return;
+	}
+
+	spin_lock_irqsave(&dev->queued_bufs_lock, flags);
+	list_add_tail(&buf->list, &dev->queued_bufs);
+	spin_unlock_irqrestore(&dev->queued_bufs_lock, flags);
+}
+
+#define CMD_WREG               0x41
+#define CMD_START_STREAMING    0x43
+#define CMD_STOP_STREAMING     0x45
+#define CMD_READ_UNKNOWN       0x48
+
+#define msi2500_dbg_usb_control_msg(_dev, _r, _t, _v, _i, _b, _l) { \
+	char *_direction; \
+	if (_t & USB_DIR_IN) \
+		_direction = "<<<"; \
+	else \
+		_direction = ">>>"; \
+	dev_dbg(_dev, "%02x %02x %02x %02x %02x %02x %02x %02x %s %*ph\n", \
+			_t, _r, _v & 0xff, _v >> 8, _i & 0xff, _i >> 8, \
+			_l & 0xff, _l >> 8, _direction, _l, _b); \
+}
+
+static int msi2500_ctrl_msg(struct msi2500_dev *dev, u8 cmd, u32 data)
+{
+	int ret;
+	u8 request = cmd;
+	u8 requesttype = USB_DIR_OUT | USB_TYPE_VENDOR;
+	u16 value = (data >> 0) & 0xffff;
+	u16 index = (data >> 16) & 0xffff;
+
+	msi2500_dbg_usb_control_msg(dev->dev, request, requesttype,
+				    value, index, NULL, 0);
+	ret = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), request,
+			      requesttype, value, index, NULL, 0, 2000);
+	if (ret)
+		dev_err(dev->dev, "failed %d, cmd %02x, data %04x\n",
+			ret, cmd, data);
+
+	return ret;
+}
+
+static int msi2500_set_usb_adc(struct msi2500_dev *dev)
+{
+	int ret;
+	unsigned int f_vco, f_sr, div_n, k, k_cw, div_out;
+	u32 reg3, reg4, reg7;
+	struct v4l2_ctrl *bandwidth_auto;
+	struct v4l2_ctrl *bandwidth;
+
+	f_sr = dev->f_adc;
+
+	/* set tuner, subdev, filters according to sampling rate */
+	bandwidth_auto = v4l2_ctrl_find(&dev->hdl,
+			V4L2_CID_RF_TUNER_BANDWIDTH_AUTO);
+	if (v4l2_ctrl_g_ctrl(bandwidth_auto)) {
+		bandwidth = v4l2_ctrl_find(&dev->hdl,
+				V4L2_CID_RF_TUNER_BANDWIDTH);
+		v4l2_ctrl_s_ctrl(bandwidth, dev->f_adc);
+	}
+
+	/* select stream format */
+	switch (dev->pixelformat) {
+	case V4L2_SDR_FMT_CU8:
+		reg7 = 0x000c9407; /* 504 */
+		break;
+	case  V4L2_SDR_FMT_CU16LE:
+		reg7 = 0x00009407; /* 252 */
+		break;
+	case V4L2_SDR_FMT_CS8:
+		reg7 = 0x000c9407; /* 504 */
+		break;
+	case MSI2500_PIX_FMT_SDR_MSI2500_384:
+		reg7 = 0x0000a507; /* 384 */
+		break;
+	case MSI2500_PIX_FMT_SDR_S12:
+		reg7 = 0x00008507; /* 336 */
+		break;
+	case V4L2_SDR_FMT_CS14LE:
+		reg7 = 0x00009407; /* 252 */
+		break;
+	default:
+		reg7 = 0x000c9407; /* 504 */
+		break;
+	}
+
+	/*
+	 * Fractional-N synthesizer
+	 *
+	 *           +----------------------------------------+
+	 *           v                                        |
+	 *  Fref   +----+     +-------+     +-----+         +------+     +---+
+	 * ------> | PD | --> |  VCO  | --> | /2  | ------> | /N.F | <-- | K |
+	 *         +----+     +-------+     +-----+         +------+     +---+
+	 *                      |
+	 *                      |
+	 *                      v
+	 *                    +-------+     +-----+  Fout
+	 *                    | /Rout | --> | /12 | ------>
+	 *                    +-------+     +-----+
+	 */
+	/*
+	 * Synthesizer config is just a educated guess...
+	 *
+	 * [7:0]   0x03, register address
+	 * [8]     1, power control
+	 * [9]     ?, power control
+	 * [12:10] output divider
+	 * [13]    0 ?
+	 * [14]    0 ?
+	 * [15]    fractional MSB, bit 20
+	 * [16:19] N
+	 * [23:20] ?
+	 * [24:31] 0x01
+	 *
+	 * output divider
+	 * val   div
+	 *   0     - (invalid)
+	 *   1     4
+	 *   2     6
+	 *   3     8
+	 *   4    10
+	 *   5    12
+	 *   6    14
+	 *   7    16
+	 *
+	 * VCO 202000000 - 720000000++
+	 */
+
+	#define F_REF 24000000
+	#define DIV_PRE_N 2
+	#define DIV_LO_OUT 12
+	reg3 = 0x01000303;
+	reg4 = 0x00000004;
+
+	/* XXX: Filters? AGC? VCO band? */
+	if (f_sr < 6000000)
+		reg3 |= 0x1 << 20;
+	else if (f_sr < 7000000)
+		reg3 |= 0x5 << 20;
+	else if (f_sr < 8500000)
+		reg3 |= 0x9 << 20;
+	else
+		reg3 |= 0xd << 20;
+
+	for (div_out = 4; div_out < 16; div_out += 2) {
+		f_vco = f_sr * div_out * DIV_LO_OUT;
+		dev_dbg(dev->dev, "div_out=%u f_vco=%u\n", div_out, f_vco);
+		if (f_vco >= 202000000)
+			break;
+	}
+
+	/* Calculate PLL integer and fractional control word. */
+	div_n = div_u64_rem(f_vco, DIV_PRE_N * F_REF, &k);
+	k_cw = div_u64((u64) k * 0x200000, DIV_PRE_N * F_REF);
+
+	reg3 |= div_n << 16;
+	reg3 |= (div_out / 2 - 1) << 10;
+	reg3 |= ((k_cw >> 20) & 0x000001) << 15; /* [20] */
+	reg4 |= ((k_cw >>  0) & 0x0fffff) <<  8; /* [19:0] */
+
+	dev_dbg(dev->dev,
+		"f_sr=%u f_vco=%u div_n=%u k=%u div_out=%u reg3=%08x reg4=%08x\n",
+		f_sr, f_vco, div_n, k, div_out, reg3, reg4);
+
+	ret = msi2500_ctrl_msg(dev, CMD_WREG, 0x00608008);
+	if (ret)
+		goto err;
+
+	ret = msi2500_ctrl_msg(dev, CMD_WREG, 0x00000c05);
+	if (ret)
+		goto err;
+
+	ret = msi2500_ctrl_msg(dev, CMD_WREG, 0x00020000);
+	if (ret)
+		goto err;
+
+	ret = msi2500_ctrl_msg(dev, CMD_WREG, 0x00480102);
+	if (ret)
+		goto err;
+
+	ret = msi2500_ctrl_msg(dev, CMD_WREG, 0x00f38008);
+	if (ret)
+		goto err;
+
+	ret = msi2500_ctrl_msg(dev, CMD_WREG, reg7);
+	if (ret)
+		goto err;
+
+	ret = msi2500_ctrl_msg(dev, CMD_WREG, reg4);
+	if (ret)
+		goto err;
+
+	ret = msi2500_ctrl_msg(dev, CMD_WREG, reg3);
+err:
+	return ret;
+}
+
+static int msi2500_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+	struct msi2500_dev *dev = vb2_get_drv_priv(vq);
+	int ret;
+
+	dev_dbg(dev->dev, "\n");
+
+	if (!dev->udev)
+		return -ENODEV;
+
+	if (mutex_lock_interruptible(&dev->v4l2_lock))
+		return -ERESTARTSYS;
+
+	/* wake-up tuner */
+	v4l2_subdev_call(dev->v4l2_subdev, core, s_power, 1);
+
+	ret = msi2500_set_usb_adc(dev);
+
+	ret = msi2500_isoc_init(dev);
+	if (ret)
+		msi2500_cleanup_queued_bufs(dev);
+
+	ret = msi2500_ctrl_msg(dev, CMD_START_STREAMING, 0);
+
+	mutex_unlock(&dev->v4l2_lock);
+
+	return ret;
+}
+
+static void msi2500_stop_streaming(struct vb2_queue *vq)
+{
+	struct msi2500_dev *dev = vb2_get_drv_priv(vq);
+
+	dev_dbg(dev->dev, "\n");
+
+	mutex_lock(&dev->v4l2_lock);
+
+	if (dev->udev)
+		msi2500_isoc_cleanup(dev);
+
+	msi2500_cleanup_queued_bufs(dev);
+
+	/* according to tests, at least 700us delay is required  */
+	msleep(20);
+	if (!msi2500_ctrl_msg(dev, CMD_STOP_STREAMING, 0)) {
+		/* sleep USB IF / ADC */
+		msi2500_ctrl_msg(dev, CMD_WREG, 0x01000003);
+	}
+
+	/* sleep tuner */
+	v4l2_subdev_call(dev->v4l2_subdev, core, s_power, 0);
+
+	mutex_unlock(&dev->v4l2_lock);
+}
+
+static const struct vb2_ops msi2500_vb2_ops = {
+	.queue_setup            = msi2500_queue_setup,
+	.buf_queue              = msi2500_buf_queue,
+	.start_streaming        = msi2500_start_streaming,
+	.stop_streaming         = msi2500_stop_streaming,
+	.wait_prepare           = vb2_ops_wait_prepare,
+	.wait_finish            = vb2_ops_wait_finish,
+};
+
+static int msi2500_enum_fmt_sdr_cap(struct file *file, void *priv,
+				    struct v4l2_fmtdesc *f)
+{
+	struct msi2500_dev *dev = video_drvdata(file);
+
+	dev_dbg(dev->dev, "index=%d\n", f->index);
+
+	if (f->index >= dev->num_formats)
+		return -EINVAL;
+
+	strlcpy(f->description, formats[f->index].name, sizeof(f->description));
+	f->pixelformat = formats[f->index].pixelformat;
+
+	return 0;
+}
+
+static int msi2500_g_fmt_sdr_cap(struct file *file, void *priv,
+				 struct v4l2_format *f)
+{
+	struct msi2500_dev *dev = video_drvdata(file);
+
+	dev_dbg(dev->dev, "pixelformat fourcc %4.4s\n",
+		(char *)&dev->pixelformat);
+
+	f->fmt.sdr.pixelformat = dev->pixelformat;
+	f->fmt.sdr.buffersize = dev->buffersize;
+	memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved));
+
+	return 0;
+}
+
+static int msi2500_s_fmt_sdr_cap(struct file *file, void *priv,
+				 struct v4l2_format *f)
+{
+	struct msi2500_dev *dev = video_drvdata(file);
+	struct vb2_queue *q = &dev->vb_queue;
+	int i;
+
+	dev_dbg(dev->dev, "pixelformat fourcc %4.4s\n",
+		(char *)&f->fmt.sdr.pixelformat);
+
+	if (vb2_is_busy(q))
+		return -EBUSY;
+
+	memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved));
+	for (i = 0; i < dev->num_formats; i++) {
+		if (formats[i].pixelformat == f->fmt.sdr.pixelformat) {
+			dev->pixelformat = formats[i].pixelformat;
+			dev->buffersize = formats[i].buffersize;
+			f->fmt.sdr.buffersize = formats[i].buffersize;
+			return 0;
+		}
+	}
+
+	dev->pixelformat = formats[0].pixelformat;
+	dev->buffersize = formats[0].buffersize;
+	f->fmt.sdr.pixelformat = formats[0].pixelformat;
+	f->fmt.sdr.buffersize = formats[0].buffersize;
+
+	return 0;
+}
+
+static int msi2500_try_fmt_sdr_cap(struct file *file, void *priv,
+				   struct v4l2_format *f)
+{
+	struct msi2500_dev *dev = video_drvdata(file);
+	int i;
+
+	dev_dbg(dev->dev, "pixelformat fourcc %4.4s\n",
+		(char *)&f->fmt.sdr.pixelformat);
+
+	memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved));
+	for (i = 0; i < dev->num_formats; i++) {
+		if (formats[i].pixelformat == f->fmt.sdr.pixelformat) {
+			f->fmt.sdr.buffersize = formats[i].buffersize;
+			return 0;
+		}
+	}
+
+	f->fmt.sdr.pixelformat = formats[0].pixelformat;
+	f->fmt.sdr.buffersize = formats[0].buffersize;
+
+	return 0;
+}
+
+static int msi2500_s_tuner(struct file *file, void *priv,
+			   const struct v4l2_tuner *v)
+{
+	struct msi2500_dev *dev = video_drvdata(file);
+	int ret;
+
+	dev_dbg(dev->dev, "index=%d\n", v->index);
+
+	if (v->index == 0)
+		ret = 0;
+	else if (v->index == 1)
+		ret = v4l2_subdev_call(dev->v4l2_subdev, tuner, s_tuner, v);
+	else
+		ret = -EINVAL;
+
+	return ret;
+}
+
+static int msi2500_g_tuner(struct file *file, void *priv, struct v4l2_tuner *v)
+{
+	struct msi2500_dev *dev = video_drvdata(file);
+	int ret;
+
+	dev_dbg(dev->dev, "index=%d\n", v->index);
+
+	if (v->index == 0) {
+		strlcpy(v->name, "Mirics MSi2500", sizeof(v->name));
+		v->type = V4L2_TUNER_ADC;
+		v->capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS;
+		v->rangelow =   1200000;
+		v->rangehigh = 15000000;
+		ret = 0;
+	} else if (v->index == 1) {
+		ret = v4l2_subdev_call(dev->v4l2_subdev, tuner, g_tuner, v);
+	} else {
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int msi2500_g_frequency(struct file *file, void *priv,
+			       struct v4l2_frequency *f)
+{
+	struct msi2500_dev *dev = video_drvdata(file);
+	int ret  = 0;
+
+	dev_dbg(dev->dev, "tuner=%d type=%d\n", f->tuner, f->type);
+
+	if (f->tuner == 0) {
+		f->frequency = dev->f_adc;
+		ret = 0;
+	} else if (f->tuner == 1) {
+		f->type = V4L2_TUNER_RF;
+		ret = v4l2_subdev_call(dev->v4l2_subdev, tuner, g_frequency, f);
+	} else {
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int msi2500_s_frequency(struct file *file, void *priv,
+			       const struct v4l2_frequency *f)
+{
+	struct msi2500_dev *dev = video_drvdata(file);
+	int ret;
+
+	dev_dbg(dev->dev, "tuner=%d type=%d frequency=%u\n",
+		f->tuner, f->type, f->frequency);
+
+	if (f->tuner == 0) {
+		dev->f_adc = clamp_t(unsigned int, f->frequency,
+				     bands[0].rangelow,
+				     bands[0].rangehigh);
+		dev_dbg(dev->dev, "ADC frequency=%u Hz\n", dev->f_adc);
+		ret = msi2500_set_usb_adc(dev);
+	} else if (f->tuner == 1) {
+		ret = v4l2_subdev_call(dev->v4l2_subdev, tuner, s_frequency, f);
+	} else {
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int msi2500_enum_freq_bands(struct file *file, void *priv,
+				   struct v4l2_frequency_band *band)
+{
+	struct msi2500_dev *dev = video_drvdata(file);
+	int ret;
+
+	dev_dbg(dev->dev, "tuner=%d type=%d index=%d\n",
+		band->tuner, band->type, band->index);
+
+	if (band->tuner == 0) {
+		if (band->index >= ARRAY_SIZE(bands)) {
+			ret = -EINVAL;
+		} else {
+			*band = bands[band->index];
+			ret = 0;
+		}
+	} else if (band->tuner == 1) {
+		ret = v4l2_subdev_call(dev->v4l2_subdev, tuner,
+				       enum_freq_bands, band);
+	} else {
+		ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static const struct v4l2_ioctl_ops msi2500_ioctl_ops = {
+	.vidioc_querycap          = msi2500_querycap,
+
+	.vidioc_enum_fmt_sdr_cap  = msi2500_enum_fmt_sdr_cap,
+	.vidioc_g_fmt_sdr_cap     = msi2500_g_fmt_sdr_cap,
+	.vidioc_s_fmt_sdr_cap     = msi2500_s_fmt_sdr_cap,
+	.vidioc_try_fmt_sdr_cap   = msi2500_try_fmt_sdr_cap,
+
+	.vidioc_reqbufs           = vb2_ioctl_reqbufs,
+	.vidioc_create_bufs       = vb2_ioctl_create_bufs,
+	.vidioc_prepare_buf       = vb2_ioctl_prepare_buf,
+	.vidioc_querybuf          = vb2_ioctl_querybuf,
+	.vidioc_qbuf              = vb2_ioctl_qbuf,
+	.vidioc_dqbuf             = vb2_ioctl_dqbuf,
+
+	.vidioc_streamon          = vb2_ioctl_streamon,
+	.vidioc_streamoff         = vb2_ioctl_streamoff,
+
+	.vidioc_g_tuner           = msi2500_g_tuner,
+	.vidioc_s_tuner           = msi2500_s_tuner,
+
+	.vidioc_g_frequency       = msi2500_g_frequency,
+	.vidioc_s_frequency       = msi2500_s_frequency,
+	.vidioc_enum_freq_bands   = msi2500_enum_freq_bands,
+
+	.vidioc_subscribe_event   = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+	.vidioc_log_status        = v4l2_ctrl_log_status,
+};
+
+static const struct v4l2_file_operations msi2500_fops = {
+	.owner                    = THIS_MODULE,
+	.open                     = v4l2_fh_open,
+	.release                  = vb2_fop_release,
+	.read                     = vb2_fop_read,
+	.poll                     = vb2_fop_poll,
+	.mmap                     = vb2_fop_mmap,
+	.unlocked_ioctl           = video_ioctl2,
+};
+
+static const struct video_device msi2500_template = {
+	.name                     = "Mirics MSi3101 SDR Dongle",
+	.release                  = video_device_release_empty,
+	.fops                     = &msi2500_fops,
+	.ioctl_ops                = &msi2500_ioctl_ops,
+};
+
+static void msi2500_video_release(struct v4l2_device *v)
+{
+	struct msi2500_dev *dev = container_of(v, struct msi2500_dev, v4l2_dev);
+
+	v4l2_ctrl_handler_free(&dev->hdl);
+	v4l2_device_unregister(&dev->v4l2_dev);
+	kfree(dev);
+}
+
+static int msi2500_transfer_one_message(struct spi_master *master,
+					struct spi_message *m)
+{
+	struct msi2500_dev *dev = spi_master_get_devdata(master);
+	struct spi_transfer *t;
+	int ret = 0;
+	u32 data;
+
+	list_for_each_entry(t, &m->transfers, transfer_list) {
+		dev_dbg(dev->dev, "msg=%*ph\n", t->len, t->tx_buf);
+		data = 0x09; /* reg 9 is SPI adapter */
+		data |= ((u8 *)t->tx_buf)[0] << 8;
+		data |= ((u8 *)t->tx_buf)[1] << 16;
+		data |= ((u8 *)t->tx_buf)[2] << 24;
+		ret = msi2500_ctrl_msg(dev, CMD_WREG, data);
+	}
+
+	m->status = ret;
+	spi_finalize_current_message(master);
+	return ret;
+}
+
+static int msi2500_probe(struct usb_interface *intf,
+			 const struct usb_device_id *id)
+{
+	struct msi2500_dev *dev;
+	struct v4l2_subdev *sd;
+	struct spi_master *master;
+	int ret;
+	static struct spi_board_info board_info = {
+		.modalias		= "msi001",
+		.bus_num		= 0,
+		.chip_select		= 0,
+		.max_speed_hz		= 12000000,
+	};
+
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	mutex_init(&dev->v4l2_lock);
+	mutex_init(&dev->vb_queue_lock);
+	spin_lock_init(&dev->queued_bufs_lock);
+	INIT_LIST_HEAD(&dev->queued_bufs);
+	dev->dev = &intf->dev;
+	dev->udev = interface_to_usbdev(intf);
+	dev->f_adc = bands[0].rangelow;
+	dev->pixelformat = formats[0].pixelformat;
+	dev->buffersize = formats[0].buffersize;
+	dev->num_formats = NUM_FORMATS;
+	if (!msi2500_emulated_fmt)
+		dev->num_formats -= 2;
+
+	/* Init videobuf2 queue structure */
+	dev->vb_queue.type = V4L2_BUF_TYPE_SDR_CAPTURE;
+	dev->vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
+	dev->vb_queue.drv_priv = dev;
+	dev->vb_queue.buf_struct_size = sizeof(struct msi2500_frame_buf);
+	dev->vb_queue.ops = &msi2500_vb2_ops;
+	dev->vb_queue.mem_ops = &vb2_vmalloc_memops;
+	dev->vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	ret = vb2_queue_init(&dev->vb_queue);
+	if (ret) {
+		dev_err(dev->dev, "Could not initialize vb2 queue\n");
+		goto err_free_mem;
+	}
+
+	/* Init video_device structure */
+	dev->vdev = msi2500_template;
+	dev->vdev.queue = &dev->vb_queue;
+	dev->vdev.queue->lock = &dev->vb_queue_lock;
+	video_set_drvdata(&dev->vdev, dev);
+
+	/* Register the v4l2_device structure */
+	dev->v4l2_dev.release = msi2500_video_release;
+	ret = v4l2_device_register(&intf->dev, &dev->v4l2_dev);
+	if (ret) {
+		dev_err(dev->dev, "Failed to register v4l2-device (%d)\n", ret);
+		goto err_free_mem;
+	}
+
+	/* SPI master adapter */
+	master = spi_alloc_master(dev->dev, 0);
+	if (master == NULL) {
+		ret = -ENOMEM;
+		goto err_unregister_v4l2_dev;
+	}
+
+	dev->master = master;
+	master->bus_num = 0;
+	master->num_chipselect = 1;
+	master->transfer_one_message = msi2500_transfer_one_message;
+	spi_master_set_devdata(master, dev);
+	ret = spi_register_master(master);
+	if (ret) {
+		spi_master_put(master);
+		goto err_unregister_v4l2_dev;
+	}
+
+	/* load v4l2 subdevice */
+	sd = v4l2_spi_new_subdev(&dev->v4l2_dev, master, &board_info);
+	dev->v4l2_subdev = sd;
+	if (sd == NULL) {
+		dev_err(dev->dev, "cannot get v4l2 subdevice\n");
+		ret = -ENODEV;
+		goto err_unregister_master;
+	}
+
+	/* Register controls */
+	v4l2_ctrl_handler_init(&dev->hdl, 0);
+	if (dev->hdl.error) {
+		ret = dev->hdl.error;
+		dev_err(dev->dev, "Could not initialize controls\n");
+		goto err_free_controls;
+	}
+
+	/* currently all controls are from subdev */
+	v4l2_ctrl_add_handler(&dev->hdl, sd->ctrl_handler, NULL);
+
+	dev->v4l2_dev.ctrl_handler = &dev->hdl;
+	dev->vdev.v4l2_dev = &dev->v4l2_dev;
+	dev->vdev.lock = &dev->v4l2_lock;
+
+	ret = video_register_device(&dev->vdev, VFL_TYPE_SDR, -1);
+	if (ret) {
+		dev_err(dev->dev,
+			"Failed to register as video device (%d)\n", ret);
+		goto err_unregister_v4l2_dev;
+	}
+	dev_info(dev->dev, "Registered as %s\n",
+		 video_device_node_name(&dev->vdev));
+	dev_notice(dev->dev,
+		   "SDR API is still slightly experimental and functionality changes may follow\n");
+	return 0;
+err_free_controls:
+	v4l2_ctrl_handler_free(&dev->hdl);
+err_unregister_master:
+	spi_unregister_master(dev->master);
+err_unregister_v4l2_dev:
+	v4l2_device_unregister(&dev->v4l2_dev);
+err_free_mem:
+	kfree(dev);
+err:
+	return ret;
+}
+
+/* USB device ID list */
+static const struct usb_device_id msi2500_id_table[] = {
+	{USB_DEVICE(0x1df7, 0x2500)}, /* Mirics MSi3101 SDR Dongle */
+	{USB_DEVICE(0x2040, 0xd300)}, /* Hauppauge WinTV 133559 LF */
+	{}
+};
+MODULE_DEVICE_TABLE(usb, msi2500_id_table);
+
+/* USB subsystem interface */
+static struct usb_driver msi2500_driver = {
+	.name                     = KBUILD_MODNAME,
+	.probe                    = msi2500_probe,
+	.disconnect               = msi2500_disconnect,
+	.id_table                 = msi2500_id_table,
+};
+
+module_usb_driver(msi2500_driver);
+
+MODULE_AUTHOR("Antti Palosaari <crope@iki.fi>");
+MODULE_DESCRIPTION("Mirics MSi3101 SDR Dongle");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/pulse8-cec/Kconfig b/drivers/media/usb/pulse8-cec/Kconfig
new file mode 100644
index 0000000..18ead44
--- /dev/null
+++ b/drivers/media/usb/pulse8-cec/Kconfig
@@ -0,0 +1,11 @@
+config USB_PULSE8_CEC
+	tristate "Pulse Eight HDMI CEC"
+	depends on USB_ACM
+	select CEC_CORE
+	select SERIO
+	select SERIO_SERPORT
+	---help---
+	  This is a cec driver for the Pulse Eight HDMI CEC device.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called pulse8-cec.
diff --git a/drivers/media/usb/pulse8-cec/Makefile b/drivers/media/usb/pulse8-cec/Makefile
new file mode 100644
index 0000000..9800690
--- /dev/null
+++ b/drivers/media/usb/pulse8-cec/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_USB_PULSE8_CEC) += pulse8-cec.o
diff --git a/drivers/media/usb/pulse8-cec/pulse8-cec.c b/drivers/media/usb/pulse8-cec/pulse8-cec.c
new file mode 100644
index 0000000..3506358
--- /dev/null
+++ b/drivers/media/usb/pulse8-cec/pulse8-cec.c
@@ -0,0 +1,757 @@
+/*
+ * Pulse Eight HDMI CEC driver
+ *
+ * Copyright 2016 Hans Verkuil <hverkuil@xs4all.nl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version of 2 of the License, or (at your
+ * option) any later version. See the file COPYING in the main directory of
+ * this archive for more details.
+ */
+
+/*
+ * Notes:
+ *
+ * - Devices with firmware version < 2 do not store their configuration in
+ *   EEPROM.
+ *
+ * - In autonomous mode, only messages from a TV will be acknowledged, even
+ *   polling messages. Upon receiving a message from a TV, the dongle will
+ *   respond to messages from any logical address.
+ *
+ * - In autonomous mode, the dongle will by default reply Feature Abort
+ *   [Unrecognized Opcode] when it receives Give Device Vendor ID. It will
+ *   however observe vendor ID's reported by other devices and possibly
+ *   alter this behavior. When TV's (and TV's only) report that their vendor ID
+ *   is LG (0x00e091), the dongle will itself reply that it has the same vendor
+ *   ID, and it will respond to at least one vendor specific command.
+ *
+ * - In autonomous mode, the dongle is known to attempt wakeup if it receives
+ *   <User Control Pressed> ["Power On"], ["Power] or ["Power Toggle"], or if it
+ *   receives <Set Stream Path> with its own physical address. It also does this
+ *   if it receives <Vendor Specific Command> [0x03 0x00] from an LG TV.
+ */
+
+#include <linux/completion.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/workqueue.h>
+#include <linux/serio.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/delay.h>
+
+#include <media/cec.h>
+
+MODULE_AUTHOR("Hans Verkuil <hverkuil@xs4all.nl>");
+MODULE_DESCRIPTION("Pulse Eight HDMI CEC driver");
+MODULE_LICENSE("GPL");
+
+static int debug;
+static int persistent_config;
+module_param(debug, int, 0644);
+module_param(persistent_config, int, 0644);
+MODULE_PARM_DESC(debug, "debug level (0-1)");
+MODULE_PARM_DESC(persistent_config, "read config from persistent memory (0-1)");
+
+enum pulse8_msgcodes {
+	MSGCODE_NOTHING = 0,
+	MSGCODE_PING,
+	MSGCODE_TIMEOUT_ERROR,
+	MSGCODE_HIGH_ERROR,
+	MSGCODE_LOW_ERROR,
+	MSGCODE_FRAME_START,
+	MSGCODE_FRAME_DATA,
+	MSGCODE_RECEIVE_FAILED,
+	MSGCODE_COMMAND_ACCEPTED,	/* 0x08 */
+	MSGCODE_COMMAND_REJECTED,
+	MSGCODE_SET_ACK_MASK,
+	MSGCODE_TRANSMIT,
+	MSGCODE_TRANSMIT_EOM,
+	MSGCODE_TRANSMIT_IDLETIME,
+	MSGCODE_TRANSMIT_ACK_POLARITY,
+	MSGCODE_TRANSMIT_LINE_TIMEOUT,
+	MSGCODE_TRANSMIT_SUCCEEDED,	/* 0x10 */
+	MSGCODE_TRANSMIT_FAILED_LINE,
+	MSGCODE_TRANSMIT_FAILED_ACK,
+	MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA,
+	MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE,
+	MSGCODE_FIRMWARE_VERSION,
+	MSGCODE_START_BOOTLOADER,
+	MSGCODE_GET_BUILDDATE,
+	MSGCODE_SET_CONTROLLED,		/* 0x18 */
+	MSGCODE_GET_AUTO_ENABLED,
+	MSGCODE_SET_AUTO_ENABLED,
+	MSGCODE_GET_DEFAULT_LOGICAL_ADDRESS,
+	MSGCODE_SET_DEFAULT_LOGICAL_ADDRESS,
+	MSGCODE_GET_LOGICAL_ADDRESS_MASK,
+	MSGCODE_SET_LOGICAL_ADDRESS_MASK,
+	MSGCODE_GET_PHYSICAL_ADDRESS,
+	MSGCODE_SET_PHYSICAL_ADDRESS,	/* 0x20 */
+	MSGCODE_GET_DEVICE_TYPE,
+	MSGCODE_SET_DEVICE_TYPE,
+	MSGCODE_GET_HDMI_VERSION,
+	MSGCODE_SET_HDMI_VERSION,
+	MSGCODE_GET_OSD_NAME,
+	MSGCODE_SET_OSD_NAME,
+	MSGCODE_WRITE_EEPROM,
+	MSGCODE_GET_ADAPTER_TYPE,	/* 0x28 */
+	MSGCODE_SET_ACTIVE_SOURCE,
+
+	MSGCODE_FRAME_EOM = 0x80,
+	MSGCODE_FRAME_ACK = 0x40,
+};
+
+#define MSGSTART	0xff
+#define MSGEND		0xfe
+#define MSGESC		0xfd
+#define MSGOFFSET	3
+
+#define DATA_SIZE 256
+
+#define PING_PERIOD	(15 * HZ)
+
+struct pulse8 {
+	struct device *dev;
+	struct serio *serio;
+	struct cec_adapter *adap;
+	unsigned int vers;
+	struct completion cmd_done;
+	struct work_struct work;
+	struct delayed_work ping_eeprom_work;
+	struct cec_msg rx_msg;
+	u8 data[DATA_SIZE];
+	unsigned int len;
+	u8 buf[DATA_SIZE];
+	unsigned int idx;
+	bool escape;
+	bool started;
+	struct mutex config_lock;
+	struct mutex write_lock;
+	bool config_pending;
+	bool restoring_config;
+	bool autonomous;
+};
+
+static void pulse8_ping_eeprom_work_handler(struct work_struct *work);
+
+static void pulse8_irq_work_handler(struct work_struct *work)
+{
+	struct pulse8 *pulse8 =
+		container_of(work, struct pulse8, work);
+
+	switch (pulse8->data[0] & 0x3f) {
+	case MSGCODE_FRAME_DATA:
+		cec_received_msg(pulse8->adap, &pulse8->rx_msg);
+		break;
+	case MSGCODE_TRANSMIT_SUCCEEDED:
+		cec_transmit_attempt_done(pulse8->adap, CEC_TX_STATUS_OK);
+		break;
+	case MSGCODE_TRANSMIT_FAILED_ACK:
+		cec_transmit_attempt_done(pulse8->adap, CEC_TX_STATUS_NACK);
+		break;
+	case MSGCODE_TRANSMIT_FAILED_LINE:
+	case MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA:
+	case MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE:
+		cec_transmit_attempt_done(pulse8->adap, CEC_TX_STATUS_ERROR);
+		break;
+	}
+}
+
+static irqreturn_t pulse8_interrupt(struct serio *serio, unsigned char data,
+				    unsigned int flags)
+{
+	struct pulse8 *pulse8 = serio_get_drvdata(serio);
+
+	if (!pulse8->started && data != MSGSTART)
+		return IRQ_HANDLED;
+	if (data == MSGESC) {
+		pulse8->escape = true;
+		return IRQ_HANDLED;
+	}
+	if (pulse8->escape) {
+		data += MSGOFFSET;
+		pulse8->escape = false;
+	} else if (data == MSGEND) {
+		struct cec_msg *msg = &pulse8->rx_msg;
+
+		if (debug)
+			dev_info(pulse8->dev, "received: %*ph\n",
+				 pulse8->idx, pulse8->buf);
+		pulse8->data[0] = pulse8->buf[0];
+		switch (pulse8->buf[0] & 0x3f) {
+		case MSGCODE_FRAME_START:
+			msg->len = 1;
+			msg->msg[0] = pulse8->buf[1];
+			break;
+		case MSGCODE_FRAME_DATA:
+			if (msg->len == CEC_MAX_MSG_SIZE)
+				break;
+			msg->msg[msg->len++] = pulse8->buf[1];
+			if (pulse8->buf[0] & MSGCODE_FRAME_EOM)
+				schedule_work(&pulse8->work);
+			break;
+		case MSGCODE_TRANSMIT_SUCCEEDED:
+		case MSGCODE_TRANSMIT_FAILED_LINE:
+		case MSGCODE_TRANSMIT_FAILED_ACK:
+		case MSGCODE_TRANSMIT_FAILED_TIMEOUT_DATA:
+		case MSGCODE_TRANSMIT_FAILED_TIMEOUT_LINE:
+			schedule_work(&pulse8->work);
+			break;
+		case MSGCODE_HIGH_ERROR:
+		case MSGCODE_LOW_ERROR:
+		case MSGCODE_RECEIVE_FAILED:
+		case MSGCODE_TIMEOUT_ERROR:
+			break;
+		case MSGCODE_COMMAND_ACCEPTED:
+		case MSGCODE_COMMAND_REJECTED:
+		default:
+			if (pulse8->idx == 0)
+				break;
+			memcpy(pulse8->data, pulse8->buf, pulse8->idx);
+			pulse8->len = pulse8->idx;
+			complete(&pulse8->cmd_done);
+			break;
+		}
+		pulse8->idx = 0;
+		pulse8->started = false;
+		return IRQ_HANDLED;
+	} else if (data == MSGSTART) {
+		pulse8->idx = 0;
+		pulse8->started = true;
+		return IRQ_HANDLED;
+	}
+
+	if (pulse8->idx >= DATA_SIZE) {
+		dev_dbg(pulse8->dev,
+			"throwing away %d bytes of garbage\n", pulse8->idx);
+		pulse8->idx = 0;
+	}
+	pulse8->buf[pulse8->idx++] = data;
+	return IRQ_HANDLED;
+}
+
+static void pulse8_disconnect(struct serio *serio)
+{
+	struct pulse8 *pulse8 = serio_get_drvdata(serio);
+
+	cec_unregister_adapter(pulse8->adap);
+	cancel_delayed_work_sync(&pulse8->ping_eeprom_work);
+	dev_info(&serio->dev, "disconnected\n");
+	serio_close(serio);
+	serio_set_drvdata(serio, NULL);
+	kfree(pulse8);
+}
+
+static int pulse8_send(struct serio *serio, const u8 *command, u8 cmd_len)
+{
+	int err = 0;
+
+	err = serio_write(serio, MSGSTART);
+	if (err)
+		return err;
+	for (; !err && cmd_len; command++, cmd_len--) {
+		if (*command >= MSGESC) {
+			err = serio_write(serio, MSGESC);
+			if (!err)
+				err = serio_write(serio, *command - MSGOFFSET);
+		} else {
+			err = serio_write(serio, *command);
+		}
+	}
+	if (!err)
+		err = serio_write(serio, MSGEND);
+
+	return err;
+}
+
+static int pulse8_send_and_wait_once(struct pulse8 *pulse8,
+				     const u8 *cmd, u8 cmd_len,
+				     u8 response, u8 size)
+{
+	int err;
+
+	/*dev_info(pulse8->dev, "transmit: %*ph\n", cmd_len, cmd);*/
+	init_completion(&pulse8->cmd_done);
+
+	err = pulse8_send(pulse8->serio, cmd, cmd_len);
+	if (err)
+		return err;
+
+	if (!wait_for_completion_timeout(&pulse8->cmd_done, HZ))
+		return -ETIMEDOUT;
+	if ((pulse8->data[0] & 0x3f) == MSGCODE_COMMAND_REJECTED &&
+	    cmd[0] != MSGCODE_SET_CONTROLLED &&
+	    cmd[0] != MSGCODE_SET_AUTO_ENABLED &&
+	    cmd[0] != MSGCODE_GET_BUILDDATE)
+		return -ENOTTY;
+	if (response &&
+	    ((pulse8->data[0] & 0x3f) != response || pulse8->len < size + 1)) {
+		dev_info(pulse8->dev, "transmit: failed %02x\n",
+			 pulse8->data[0] & 0x3f);
+		return -EIO;
+	}
+	return 0;
+}
+
+static int pulse8_send_and_wait(struct pulse8 *pulse8,
+				const u8 *cmd, u8 cmd_len, u8 response, u8 size)
+{
+	u8 cmd_sc[2];
+	int err;
+
+	mutex_lock(&pulse8->write_lock);
+	err = pulse8_send_and_wait_once(pulse8, cmd, cmd_len, response, size);
+
+	if (err == -ENOTTY) {
+		cmd_sc[0] = MSGCODE_SET_CONTROLLED;
+		cmd_sc[1] = 1;
+		err = pulse8_send_and_wait_once(pulse8, cmd_sc, 2,
+						MSGCODE_COMMAND_ACCEPTED, 1);
+		if (err)
+			goto unlock;
+		err = pulse8_send_and_wait_once(pulse8, cmd, cmd_len,
+						response, size);
+	}
+
+unlock:
+	mutex_unlock(&pulse8->write_lock);
+	return err == -ENOTTY ? -EIO : err;
+}
+
+static int pulse8_setup(struct pulse8 *pulse8, struct serio *serio,
+			struct cec_log_addrs *log_addrs, u16 *pa)
+{
+	u8 *data = pulse8->data + 1;
+	u8 cmd[2];
+	int err;
+	struct tm tm;
+	time64_t date;
+
+	pulse8->vers = 0;
+
+	cmd[0] = MSGCODE_FIRMWARE_VERSION;
+	err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 2);
+	if (err)
+		return err;
+	pulse8->vers = (data[0] << 8) | data[1];
+	dev_info(pulse8->dev, "Firmware version %04x\n", pulse8->vers);
+	if (pulse8->vers < 2) {
+		*pa = CEC_PHYS_ADDR_INVALID;
+		return 0;
+	}
+
+	cmd[0] = MSGCODE_GET_BUILDDATE;
+	err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 4);
+	if (err)
+		return err;
+	date = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
+	time64_to_tm(date, 0, &tm);
+	dev_info(pulse8->dev, "Firmware build date %04ld.%02d.%02d %02d:%02d:%02d\n",
+		 tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
+		 tm.tm_hour, tm.tm_min, tm.tm_sec);
+
+	dev_dbg(pulse8->dev, "Persistent config:\n");
+	cmd[0] = MSGCODE_GET_AUTO_ENABLED;
+	err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 1);
+	if (err)
+		return err;
+	pulse8->autonomous = data[0];
+	dev_dbg(pulse8->dev, "Autonomous mode: %s",
+		data[0] ? "on" : "off");
+
+	cmd[0] = MSGCODE_GET_DEVICE_TYPE;
+	err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 1);
+	if (err)
+		return err;
+	log_addrs->primary_device_type[0] = data[0];
+	dev_dbg(pulse8->dev, "Primary device type: %d\n", data[0]);
+	switch (log_addrs->primary_device_type[0]) {
+	case CEC_OP_PRIM_DEVTYPE_TV:
+		log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_TV;
+		log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_TV;
+		break;
+	case CEC_OP_PRIM_DEVTYPE_RECORD:
+		log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_RECORD;
+		log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_RECORD;
+		break;
+	case CEC_OP_PRIM_DEVTYPE_TUNER:
+		log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_TUNER;
+		log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_TUNER;
+		break;
+	case CEC_OP_PRIM_DEVTYPE_PLAYBACK:
+		log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_PLAYBACK;
+		log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_PLAYBACK;
+		break;
+	case CEC_OP_PRIM_DEVTYPE_AUDIOSYSTEM:
+		log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_PLAYBACK;
+		log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_AUDIOSYSTEM;
+		break;
+	case CEC_OP_PRIM_DEVTYPE_SWITCH:
+		log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_UNREGISTERED;
+		log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_SWITCH;
+		break;
+	case CEC_OP_PRIM_DEVTYPE_PROCESSOR:
+		log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_SPECIFIC;
+		log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_SWITCH;
+		break;
+	default:
+		log_addrs->log_addr_type[0] = CEC_LOG_ADDR_TYPE_UNREGISTERED;
+		log_addrs->all_device_types[0] = CEC_OP_ALL_DEVTYPE_SWITCH;
+		dev_info(pulse8->dev, "Unknown Primary Device Type: %d\n",
+			 log_addrs->primary_device_type[0]);
+		break;
+	}
+
+	cmd[0] = MSGCODE_GET_LOGICAL_ADDRESS_MASK;
+	err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 2);
+	if (err)
+		return err;
+	log_addrs->log_addr_mask = (data[0] << 8) | data[1];
+	dev_dbg(pulse8->dev, "Logical address ACK mask: %x\n",
+		log_addrs->log_addr_mask);
+	if (log_addrs->log_addr_mask)
+		log_addrs->num_log_addrs = 1;
+
+	cmd[0] = MSGCODE_GET_PHYSICAL_ADDRESS;
+	err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 1);
+	if (err)
+		return err;
+	*pa = (data[0] << 8) | data[1];
+	dev_dbg(pulse8->dev, "Physical address: %x.%x.%x.%x\n",
+		cec_phys_addr_exp(*pa));
+
+	cmd[0] = MSGCODE_GET_HDMI_VERSION;
+	err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 1);
+	if (err)
+		return err;
+	log_addrs->cec_version = data[0];
+	dev_dbg(pulse8->dev, "CEC version: %d\n", log_addrs->cec_version);
+
+	cmd[0] = MSGCODE_GET_OSD_NAME;
+	err = pulse8_send_and_wait(pulse8, cmd, 1, cmd[0], 0);
+	if (err)
+		return err;
+	strncpy(log_addrs->osd_name, data, 13);
+	dev_dbg(pulse8->dev, "OSD name: %s\n", log_addrs->osd_name);
+
+	return 0;
+}
+
+static int pulse8_apply_persistent_config(struct pulse8 *pulse8,
+					  struct cec_log_addrs *log_addrs,
+					  u16 pa)
+{
+	int err;
+
+	err = cec_s_log_addrs(pulse8->adap, log_addrs, false);
+	if (err)
+		return err;
+
+	cec_s_phys_addr(pulse8->adap, pa, false);
+
+	return 0;
+}
+
+static int pulse8_cec_adap_enable(struct cec_adapter *adap, bool enable)
+{
+	struct pulse8 *pulse8 = cec_get_drvdata(adap);
+	u8 cmd[16];
+	int err;
+
+	cmd[0] = MSGCODE_SET_CONTROLLED;
+	cmd[1] = enable;
+	err = pulse8_send_and_wait(pulse8, cmd, 2,
+				   MSGCODE_COMMAND_ACCEPTED, 1);
+	return enable ? err : 0;
+}
+
+static int pulse8_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr)
+{
+	struct pulse8 *pulse8 = cec_get_drvdata(adap);
+	u16 mask = 0;
+	u16 pa = adap->phys_addr;
+	u8 cmd[16];
+	int err = 0;
+
+	mutex_lock(&pulse8->config_lock);
+	if (log_addr != CEC_LOG_ADDR_INVALID)
+		mask = 1 << log_addr;
+	cmd[0] = MSGCODE_SET_ACK_MASK;
+	cmd[1] = mask >> 8;
+	cmd[2] = mask & 0xff;
+	err = pulse8_send_and_wait(pulse8, cmd, 3,
+				   MSGCODE_COMMAND_ACCEPTED, 0);
+	if ((err && mask != 0) || pulse8->restoring_config)
+		goto unlock;
+
+	cmd[0] = MSGCODE_SET_AUTO_ENABLED;
+	cmd[1] = log_addr == CEC_LOG_ADDR_INVALID ? 0 : 1;
+	err = pulse8_send_and_wait(pulse8, cmd, 2,
+				   MSGCODE_COMMAND_ACCEPTED, 0);
+	if (err)
+		goto unlock;
+	pulse8->autonomous = cmd[1];
+	if (log_addr == CEC_LOG_ADDR_INVALID)
+		goto unlock;
+
+	cmd[0] = MSGCODE_SET_DEVICE_TYPE;
+	cmd[1] = adap->log_addrs.primary_device_type[0];
+	err = pulse8_send_and_wait(pulse8, cmd, 2,
+				   MSGCODE_COMMAND_ACCEPTED, 0);
+	if (err)
+		goto unlock;
+
+	switch (adap->log_addrs.primary_device_type[0]) {
+	case CEC_OP_PRIM_DEVTYPE_TV:
+		mask = CEC_LOG_ADDR_MASK_TV;
+		break;
+	case CEC_OP_PRIM_DEVTYPE_RECORD:
+		mask = CEC_LOG_ADDR_MASK_RECORD;
+		break;
+	case CEC_OP_PRIM_DEVTYPE_TUNER:
+		mask = CEC_LOG_ADDR_MASK_TUNER;
+		break;
+	case CEC_OP_PRIM_DEVTYPE_PLAYBACK:
+		mask = CEC_LOG_ADDR_MASK_PLAYBACK;
+		break;
+	case CEC_OP_PRIM_DEVTYPE_AUDIOSYSTEM:
+		mask = CEC_LOG_ADDR_MASK_AUDIOSYSTEM;
+		break;
+	case CEC_OP_PRIM_DEVTYPE_SWITCH:
+		mask = CEC_LOG_ADDR_MASK_UNREGISTERED;
+		break;
+	case CEC_OP_PRIM_DEVTYPE_PROCESSOR:
+		mask = CEC_LOG_ADDR_MASK_SPECIFIC;
+		break;
+	default:
+		mask = 0;
+		break;
+	}
+	cmd[0] = MSGCODE_SET_LOGICAL_ADDRESS_MASK;
+	cmd[1] = mask >> 8;
+	cmd[2] = mask & 0xff;
+	err = pulse8_send_and_wait(pulse8, cmd, 3,
+				   MSGCODE_COMMAND_ACCEPTED, 0);
+	if (err)
+		goto unlock;
+
+	cmd[0] = MSGCODE_SET_DEFAULT_LOGICAL_ADDRESS;
+	cmd[1] = log_addr;
+	err = pulse8_send_and_wait(pulse8, cmd, 2,
+				   MSGCODE_COMMAND_ACCEPTED, 0);
+	if (err)
+		goto unlock;
+
+	cmd[0] = MSGCODE_SET_PHYSICAL_ADDRESS;
+	cmd[1] = pa >> 8;
+	cmd[2] = pa & 0xff;
+	err = pulse8_send_and_wait(pulse8, cmd, 3,
+				   MSGCODE_COMMAND_ACCEPTED, 0);
+	if (err)
+		goto unlock;
+
+	cmd[0] = MSGCODE_SET_HDMI_VERSION;
+	cmd[1] = adap->log_addrs.cec_version;
+	err = pulse8_send_and_wait(pulse8, cmd, 2,
+				   MSGCODE_COMMAND_ACCEPTED, 0);
+	if (err)
+		goto unlock;
+
+	if (adap->log_addrs.osd_name[0]) {
+		size_t osd_len = strlen(adap->log_addrs.osd_name);
+		char *osd_str = cmd + 1;
+
+		cmd[0] = MSGCODE_SET_OSD_NAME;
+		strncpy(cmd + 1, adap->log_addrs.osd_name, 13);
+		if (osd_len < 4) {
+			memset(osd_str + osd_len, ' ', 4 - osd_len);
+			osd_len = 4;
+			osd_str[osd_len] = '\0';
+			strcpy(adap->log_addrs.osd_name, osd_str);
+		}
+		err = pulse8_send_and_wait(pulse8, cmd, 1 + osd_len,
+					   MSGCODE_COMMAND_ACCEPTED, 0);
+		if (err)
+			goto unlock;
+	}
+
+unlock:
+	if (pulse8->restoring_config)
+		pulse8->restoring_config = false;
+	else
+		pulse8->config_pending = true;
+	mutex_unlock(&pulse8->config_lock);
+	return err;
+}
+
+static int pulse8_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
+				    u32 signal_free_time, struct cec_msg *msg)
+{
+	struct pulse8 *pulse8 = cec_get_drvdata(adap);
+	u8 cmd[2];
+	unsigned int i;
+	int err;
+
+	cmd[0] = MSGCODE_TRANSMIT_IDLETIME;
+	cmd[1] = signal_free_time;
+	err = pulse8_send_and_wait(pulse8, cmd, 2,
+				   MSGCODE_COMMAND_ACCEPTED, 1);
+	cmd[0] = MSGCODE_TRANSMIT_ACK_POLARITY;
+	cmd[1] = cec_msg_is_broadcast(msg);
+	if (!err)
+		err = pulse8_send_and_wait(pulse8, cmd, 2,
+					   MSGCODE_COMMAND_ACCEPTED, 1);
+	cmd[0] = msg->len == 1 ? MSGCODE_TRANSMIT_EOM : MSGCODE_TRANSMIT;
+	cmd[1] = msg->msg[0];
+	if (!err)
+		err = pulse8_send_and_wait(pulse8, cmd, 2,
+					   MSGCODE_COMMAND_ACCEPTED, 1);
+	if (!err && msg->len > 1) {
+		cmd[0] = msg->len == 2 ? MSGCODE_TRANSMIT_EOM :
+					 MSGCODE_TRANSMIT;
+		cmd[1] = msg->msg[1];
+		err = pulse8_send_and_wait(pulse8, cmd, 2,
+					   MSGCODE_COMMAND_ACCEPTED, 1);
+		for (i = 0; !err && i + 2 < msg->len; i++) {
+			cmd[0] = (i + 2 == msg->len - 1) ?
+				MSGCODE_TRANSMIT_EOM : MSGCODE_TRANSMIT;
+			cmd[1] = msg->msg[i + 2];
+			err = pulse8_send_and_wait(pulse8, cmd, 2,
+						   MSGCODE_COMMAND_ACCEPTED, 1);
+		}
+	}
+
+	return err;
+}
+
+static int pulse8_received(struct cec_adapter *adap, struct cec_msg *msg)
+{
+	return -ENOMSG;
+}
+
+static const struct cec_adap_ops pulse8_cec_adap_ops = {
+	.adap_enable = pulse8_cec_adap_enable,
+	.adap_log_addr = pulse8_cec_adap_log_addr,
+	.adap_transmit = pulse8_cec_adap_transmit,
+	.received = pulse8_received,
+};
+
+static int pulse8_connect(struct serio *serio, struct serio_driver *drv)
+{
+	u32 caps = CEC_CAP_DEFAULTS | CEC_CAP_PHYS_ADDR | CEC_CAP_MONITOR_ALL;
+	struct pulse8 *pulse8;
+	int err = -ENOMEM;
+	struct cec_log_addrs log_addrs = {};
+	u16 pa = CEC_PHYS_ADDR_INVALID;
+
+	pulse8 = kzalloc(sizeof(*pulse8), GFP_KERNEL);
+
+	if (!pulse8)
+		return -ENOMEM;
+
+	pulse8->serio = serio;
+	pulse8->adap = cec_allocate_adapter(&pulse8_cec_adap_ops, pulse8,
+					    dev_name(&serio->dev), caps, 1);
+	err = PTR_ERR_OR_ZERO(pulse8->adap);
+	if (err < 0)
+		goto free_device;
+
+	pulse8->dev = &serio->dev;
+	serio_set_drvdata(serio, pulse8);
+	INIT_WORK(&pulse8->work, pulse8_irq_work_handler);
+	mutex_init(&pulse8->write_lock);
+	mutex_init(&pulse8->config_lock);
+	pulse8->config_pending = false;
+
+	err = serio_open(serio, drv);
+	if (err)
+		goto delete_adap;
+
+	err = pulse8_setup(pulse8, serio, &log_addrs, &pa);
+	if (err)
+		goto close_serio;
+
+	err = cec_register_adapter(pulse8->adap, &serio->dev);
+	if (err < 0)
+		goto close_serio;
+
+	pulse8->dev = &pulse8->adap->devnode.dev;
+
+	if (persistent_config && pulse8->autonomous) {
+		err = pulse8_apply_persistent_config(pulse8, &log_addrs, pa);
+		if (err)
+			goto close_serio;
+		pulse8->restoring_config = true;
+	}
+
+	INIT_DELAYED_WORK(&pulse8->ping_eeprom_work,
+			  pulse8_ping_eeprom_work_handler);
+	schedule_delayed_work(&pulse8->ping_eeprom_work, PING_PERIOD);
+
+	return 0;
+
+close_serio:
+	serio_close(serio);
+delete_adap:
+	cec_delete_adapter(pulse8->adap);
+	serio_set_drvdata(serio, NULL);
+free_device:
+	kfree(pulse8);
+	return err;
+}
+
+static void pulse8_ping_eeprom_work_handler(struct work_struct *work)
+{
+	struct pulse8 *pulse8 =
+		container_of(work, struct pulse8, ping_eeprom_work.work);
+	u8 cmd;
+
+	schedule_delayed_work(&pulse8->ping_eeprom_work, PING_PERIOD);
+	cmd = MSGCODE_PING;
+	pulse8_send_and_wait(pulse8, &cmd, 1,
+			     MSGCODE_COMMAND_ACCEPTED, 0);
+
+	if (pulse8->vers < 2)
+		return;
+
+	mutex_lock(&pulse8->config_lock);
+	if (pulse8->config_pending && persistent_config) {
+		dev_dbg(pulse8->dev, "writing pending config to EEPROM\n");
+		cmd = MSGCODE_WRITE_EEPROM;
+		if (pulse8_send_and_wait(pulse8, &cmd, 1,
+					 MSGCODE_COMMAND_ACCEPTED, 0))
+			dev_info(pulse8->dev, "failed to write pending config to EEPROM\n");
+		else
+			pulse8->config_pending = false;
+	}
+	mutex_unlock(&pulse8->config_lock);
+}
+
+static const struct serio_device_id pulse8_serio_ids[] = {
+	{
+		.type	= SERIO_RS232,
+		.proto	= SERIO_PULSE8_CEC,
+		.id	= SERIO_ANY,
+		.extra	= SERIO_ANY,
+	},
+	{ 0 }
+};
+
+MODULE_DEVICE_TABLE(serio, pulse8_serio_ids);
+
+static struct serio_driver pulse8_drv = {
+	.driver		= {
+		.name	= "pulse8-cec",
+	},
+	.description	= "Pulse Eight HDMI CEC driver",
+	.id_table	= pulse8_serio_ids,
+	.interrupt	= pulse8_interrupt,
+	.connect	= pulse8_connect,
+	.disconnect	= pulse8_disconnect,
+};
+
+module_serio_driver(pulse8_drv);
diff --git a/drivers/media/usb/pvrusb2/Kconfig b/drivers/media/usb/pvrusb2/Kconfig
new file mode 100644
index 0000000..1ad913f
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/Kconfig
@@ -0,0 +1,64 @@
+config VIDEO_PVRUSB2
+	tristate "Hauppauge WinTV-PVR USB2 support"
+	depends on VIDEO_V4L2 && I2C
+	select VIDEO_TUNER
+	select VIDEO_TVEEPROM
+	select VIDEO_CX2341X
+	select VIDEO_SAA711X
+	select VIDEO_CX25840
+	select VIDEO_MSP3400
+	select VIDEO_WM8775
+	select VIDEO_CS53L32A
+	---help---
+	  This is a video4linux driver for Conexant 23416 based
+	  usb2 personal video recorder devices.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called pvrusb2
+
+config VIDEO_PVRUSB2_SYSFS
+	bool "pvrusb2 sysfs support"
+	default y
+	depends on VIDEO_PVRUSB2 && SYSFS
+	---help---
+	  This option enables the operation of a sysfs based
+	  interface for query and control of the pvrusb2 driver.
+
+	  This is not generally needed for v4l applications,
+	  although certain applications are optimized to take
+	  advantage of this feature.
+
+	  If you are in doubt, say Y.
+
+	  Note: This feature is experimental and subject to change.
+
+config VIDEO_PVRUSB2_DVB
+	bool "pvrusb2 ATSC/DVB support"
+	default y
+	depends on VIDEO_PVRUSB2 && DVB_CORE
+	select DVB_LGDT330X if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_S5H1409 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_S5H1411 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_TDA10048 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_TDA18271 if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_SIMPLE if MEDIA_SUBDRV_AUTOSELECT
+	select MEDIA_TUNER_TDA8290 if MEDIA_SUBDRV_AUTOSELECT
+	---help---
+	  This option enables a DVB interface for the pvrusb2 driver.
+	  If your device does not support digital television, this
+	  feature will have no affect on the driver's operation.
+
+	  If you are in doubt, say Y.
+
+config VIDEO_PVRUSB2_DEBUGIFC
+	bool "pvrusb2 debug interface"
+	depends on VIDEO_PVRUSB2_SYSFS
+	---help---
+	  This option enables the inclusion of a debug interface
+	  in the pvrusb2 driver, hosted through sysfs.
+
+	  You do not need to select this option unless you plan
+	  on debugging the driver or performing a manual firmware
+	  extraction.
+
+	  If you are in doubt, say N.
diff --git a/drivers/media/usb/pvrusb2/Makefile b/drivers/media/usb/pvrusb2/Makefile
new file mode 100644
index 0000000..9facf68
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/Makefile
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-pvrusb2-sysfs-$(CONFIG_VIDEO_PVRUSB2_SYSFS) := pvrusb2-sysfs.o
+obj-pvrusb2-debugifc-$(CONFIG_VIDEO_PVRUSB2_DEBUGIFC) := pvrusb2-debugifc.o
+obj-pvrusb2-dvb-$(CONFIG_VIDEO_PVRUSB2_DVB) := pvrusb2-dvb.o
+
+pvrusb2-objs	:= pvrusb2-i2c-core.o \
+		   pvrusb2-audio.o \
+		   pvrusb2-encoder.o pvrusb2-video-v4l.o \
+		   pvrusb2-eeprom.o \
+		   pvrusb2-main.o pvrusb2-hdw.o pvrusb2-v4l2.o \
+		   pvrusb2-ctrl.o pvrusb2-std.o pvrusb2-devattr.o \
+		   pvrusb2-context.o pvrusb2-io.o pvrusb2-ioread.o \
+		   pvrusb2-cx2584x-v4l.o pvrusb2-wm8775.o \
+		   pvrusb2-cs53l32a.o \
+		   $(obj-pvrusb2-dvb-y) \
+		   $(obj-pvrusb2-sysfs-y) $(obj-pvrusb2-debugifc-y)
+
+obj-$(CONFIG_VIDEO_PVRUSB2) += pvrusb2.o
+
+ccflags-y += -Idrivers/media/tuners
+ccflags-y += -Idrivers/media/dvb-frontends
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-audio.c b/drivers/media/usb/pvrusb2/pvrusb2-audio.c
new file mode 100644
index 0000000..356afa2
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-audio.c
@@ -0,0 +1,80 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#include "pvrusb2-audio.h"
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-debug.h"
+#include <linux/videodev2.h>
+#include <media/drv-intf/msp3400.h>
+#include <media/v4l2-common.h>
+
+
+struct routing_scheme {
+	const int *def;
+	unsigned int cnt;
+};
+
+static const int routing_scheme0[] = {
+	[PVR2_CVAL_INPUT_TV]        = MSP_INPUT_DEFAULT,
+	[PVR2_CVAL_INPUT_RADIO]     = MSP_INPUT(MSP_IN_SCART2,
+						MSP_IN_TUNER1,
+						MSP_DSP_IN_SCART,
+						MSP_DSP_IN_SCART),
+	[PVR2_CVAL_INPUT_COMPOSITE] = MSP_INPUT(MSP_IN_SCART1,
+						MSP_IN_TUNER1,
+						MSP_DSP_IN_SCART,
+						MSP_DSP_IN_SCART),
+	[PVR2_CVAL_INPUT_SVIDEO]    = MSP_INPUT(MSP_IN_SCART1,
+						MSP_IN_TUNER1,
+						MSP_DSP_IN_SCART,
+						MSP_DSP_IN_SCART),
+};
+
+static const struct routing_scheme routing_def0 = {
+	.def = routing_scheme0,
+	.cnt = ARRAY_SIZE(routing_scheme0),
+};
+
+static const struct routing_scheme *routing_schemes[] = {
+	[PVR2_ROUTING_SCHEME_HAUPPAUGE] = &routing_def0,
+};
+
+void pvr2_msp3400_subdev_update(struct pvr2_hdw *hdw, struct v4l2_subdev *sd)
+{
+	if (hdw->input_dirty || hdw->force_dirty) {
+		const struct routing_scheme *sp;
+		unsigned int sid = hdw->hdw_desc->signal_routing_scheme;
+		u32 input;
+
+		pvr2_trace(PVR2_TRACE_CHIPS, "subdev msp3400 v4l2 set_stereo");
+		sp = (sid < ARRAY_SIZE(routing_schemes)) ?
+			routing_schemes[sid] : NULL;
+
+		if ((sp != NULL) &&
+		    (hdw->input_val >= 0) &&
+		    (hdw->input_val < sp->cnt)) {
+			input = sp->def[hdw->input_val];
+		} else {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "*** WARNING *** subdev msp3400 set_input: Invalid routing scheme (%u) and/or input (%d)",
+				   sid, hdw->input_val);
+			return;
+		}
+		sd->ops->audio->s_routing(sd, input,
+			MSP_OUTPUT(MSP_SC_IN_DSP_SCART1), 0);
+	}
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-audio.h b/drivers/media/usb/pvrusb2/pvrusb2-audio.h
new file mode 100644
index 0000000..4f38984
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-audio.h
@@ -0,0 +1,23 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#ifndef __PVRUSB2_AUDIO_H
+#define __PVRUSB2_AUDIO_H
+
+#include "pvrusb2-hdw-internal.h"
+void pvr2_msp3400_subdev_update(struct pvr2_hdw *, struct v4l2_subdev *);
+#endif /* __PVRUSB2_AUDIO_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-context.c b/drivers/media/usb/pvrusb2/pvrusb2-context.c
new file mode 100644
index 0000000..d9e8481
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-context.c
@@ -0,0 +1,417 @@
+/*
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#include "pvrusb2-context.h"
+#include "pvrusb2-io.h"
+#include "pvrusb2-ioread.h"
+#include "pvrusb2-hdw.h"
+#include "pvrusb2-debug.h"
+#include <linux/wait.h>
+#include <linux/kthread.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+
+static struct pvr2_context *pvr2_context_exist_first;
+static struct pvr2_context *pvr2_context_exist_last;
+static struct pvr2_context *pvr2_context_notify_first;
+static struct pvr2_context *pvr2_context_notify_last;
+static DEFINE_MUTEX(pvr2_context_mutex);
+static DECLARE_WAIT_QUEUE_HEAD(pvr2_context_sync_data);
+static DECLARE_WAIT_QUEUE_HEAD(pvr2_context_cleanup_data);
+static int pvr2_context_cleanup_flag;
+static int pvr2_context_cleaned_flag;
+static struct task_struct *pvr2_context_thread_ptr;
+
+
+static void pvr2_context_set_notify(struct pvr2_context *mp, int fl)
+{
+	int signal_flag = 0;
+	mutex_lock(&pvr2_context_mutex);
+	if (fl) {
+		if (!mp->notify_flag) {
+			signal_flag = (pvr2_context_notify_first == NULL);
+			mp->notify_prev = pvr2_context_notify_last;
+			mp->notify_next = NULL;
+			pvr2_context_notify_last = mp;
+			if (mp->notify_prev) {
+				mp->notify_prev->notify_next = mp;
+			} else {
+				pvr2_context_notify_first = mp;
+			}
+			mp->notify_flag = !0;
+		}
+	} else {
+		if (mp->notify_flag) {
+			mp->notify_flag = 0;
+			if (mp->notify_next) {
+				mp->notify_next->notify_prev = mp->notify_prev;
+			} else {
+				pvr2_context_notify_last = mp->notify_prev;
+			}
+			if (mp->notify_prev) {
+				mp->notify_prev->notify_next = mp->notify_next;
+			} else {
+				pvr2_context_notify_first = mp->notify_next;
+			}
+		}
+	}
+	mutex_unlock(&pvr2_context_mutex);
+	if (signal_flag) wake_up(&pvr2_context_sync_data);
+}
+
+
+static void pvr2_context_destroy(struct pvr2_context *mp)
+{
+	pvr2_trace(PVR2_TRACE_CTXT,"pvr2_context %p (destroy)",mp);
+	pvr2_hdw_destroy(mp->hdw);
+	pvr2_context_set_notify(mp, 0);
+	mutex_lock(&pvr2_context_mutex);
+	if (mp->exist_next) {
+		mp->exist_next->exist_prev = mp->exist_prev;
+	} else {
+		pvr2_context_exist_last = mp->exist_prev;
+	}
+	if (mp->exist_prev) {
+		mp->exist_prev->exist_next = mp->exist_next;
+	} else {
+		pvr2_context_exist_first = mp->exist_next;
+	}
+	if (!pvr2_context_exist_first) {
+		/* Trigger wakeup on control thread in case it is waiting
+		   for an exit condition. */
+		wake_up(&pvr2_context_sync_data);
+	}
+	mutex_unlock(&pvr2_context_mutex);
+	kfree(mp);
+}
+
+
+static void pvr2_context_notify(struct pvr2_context *mp)
+{
+	pvr2_context_set_notify(mp,!0);
+}
+
+
+static void pvr2_context_check(struct pvr2_context *mp)
+{
+	struct pvr2_channel *ch1, *ch2;
+	pvr2_trace(PVR2_TRACE_CTXT,
+		   "pvr2_context %p (notify)", mp);
+	if (!mp->initialized_flag && !mp->disconnect_flag) {
+		mp->initialized_flag = !0;
+		pvr2_trace(PVR2_TRACE_CTXT,
+			   "pvr2_context %p (initialize)", mp);
+		/* Finish hardware initialization */
+		if (pvr2_hdw_initialize(mp->hdw,
+					(void (*)(void *))pvr2_context_notify,
+					mp)) {
+			mp->video_stream.stream =
+				pvr2_hdw_get_video_stream(mp->hdw);
+			/* Trigger interface initialization.  By doing this
+			   here initialization runs in our own safe and
+			   cozy thread context. */
+			if (mp->setup_func) mp->setup_func(mp);
+		} else {
+			pvr2_trace(PVR2_TRACE_CTXT,
+				   "pvr2_context %p (thread skipping setup)",
+				   mp);
+			/* Even though initialization did not succeed,
+			   we're still going to continue anyway.  We need
+			   to do this in order to await the expected
+			   disconnect (which we will detect in the normal
+			   course of operation). */
+		}
+	}
+
+	for (ch1 = mp->mc_first; ch1; ch1 = ch2) {
+		ch2 = ch1->mc_next;
+		if (ch1->check_func) ch1->check_func(ch1);
+	}
+
+	if (mp->disconnect_flag && !mp->mc_first) {
+		/* Go away... */
+		pvr2_context_destroy(mp);
+		return;
+	}
+}
+
+
+static int pvr2_context_shutok(void)
+{
+	return pvr2_context_cleanup_flag && (pvr2_context_exist_first == NULL);
+}
+
+
+static int pvr2_context_thread_func(void *foo)
+{
+	struct pvr2_context *mp;
+
+	pvr2_trace(PVR2_TRACE_CTXT,"pvr2_context thread start");
+
+	do {
+		while ((mp = pvr2_context_notify_first) != NULL) {
+			pvr2_context_set_notify(mp, 0);
+			pvr2_context_check(mp);
+		}
+		wait_event_interruptible(
+			pvr2_context_sync_data,
+			((pvr2_context_notify_first != NULL) ||
+			 pvr2_context_shutok()));
+	} while (!pvr2_context_shutok());
+
+	pvr2_context_cleaned_flag = !0;
+	wake_up(&pvr2_context_cleanup_data);
+
+	pvr2_trace(PVR2_TRACE_CTXT,"pvr2_context thread cleaned up");
+
+	wait_event_interruptible(
+		pvr2_context_sync_data,
+		kthread_should_stop());
+
+	pvr2_trace(PVR2_TRACE_CTXT,"pvr2_context thread end");
+
+	return 0;
+}
+
+
+int pvr2_context_global_init(void)
+{
+	pvr2_context_thread_ptr = kthread_run(pvr2_context_thread_func,
+					      NULL,
+					      "pvrusb2-context");
+	return IS_ERR(pvr2_context_thread_ptr) ? -ENOMEM : 0;
+}
+
+
+void pvr2_context_global_done(void)
+{
+	pvr2_context_cleanup_flag = !0;
+	wake_up(&pvr2_context_sync_data);
+	wait_event_interruptible(
+		pvr2_context_cleanup_data,
+		pvr2_context_cleaned_flag);
+	kthread_stop(pvr2_context_thread_ptr);
+}
+
+
+struct pvr2_context *pvr2_context_create(
+	struct usb_interface *intf,
+	const struct usb_device_id *devid,
+	void (*setup_func)(struct pvr2_context *))
+{
+	struct pvr2_context *mp = NULL;
+	mp = kzalloc(sizeof(*mp),GFP_KERNEL);
+	if (!mp) goto done;
+	pvr2_trace(PVR2_TRACE_CTXT,"pvr2_context %p (create)",mp);
+	mp->setup_func = setup_func;
+	mutex_init(&mp->mutex);
+	mutex_lock(&pvr2_context_mutex);
+	mp->exist_prev = pvr2_context_exist_last;
+	mp->exist_next = NULL;
+	pvr2_context_exist_last = mp;
+	if (mp->exist_prev) {
+		mp->exist_prev->exist_next = mp;
+	} else {
+		pvr2_context_exist_first = mp;
+	}
+	mutex_unlock(&pvr2_context_mutex);
+	mp->hdw = pvr2_hdw_create(intf,devid);
+	if (!mp->hdw) {
+		pvr2_context_destroy(mp);
+		mp = NULL;
+		goto done;
+	}
+	pvr2_context_set_notify(mp, !0);
+ done:
+	return mp;
+}
+
+
+static void pvr2_context_reset_input_limits(struct pvr2_context *mp)
+{
+	unsigned int tmsk,mmsk;
+	struct pvr2_channel *cp;
+	struct pvr2_hdw *hdw = mp->hdw;
+	mmsk = pvr2_hdw_get_input_available(hdw);
+	tmsk = mmsk;
+	for (cp = mp->mc_first; cp; cp = cp->mc_next) {
+		if (!cp->input_mask) continue;
+		tmsk &= cp->input_mask;
+	}
+	pvr2_hdw_set_input_allowed(hdw,mmsk,tmsk);
+	pvr2_hdw_commit_ctl(hdw);
+}
+
+
+static void pvr2_context_enter(struct pvr2_context *mp)
+{
+	mutex_lock(&mp->mutex);
+}
+
+
+static void pvr2_context_exit(struct pvr2_context *mp)
+{
+	int destroy_flag = 0;
+	if (!(mp->mc_first || !mp->disconnect_flag)) {
+		destroy_flag = !0;
+	}
+	mutex_unlock(&mp->mutex);
+	if (destroy_flag) pvr2_context_notify(mp);
+}
+
+
+void pvr2_context_disconnect(struct pvr2_context *mp)
+{
+	pvr2_hdw_disconnect(mp->hdw);
+	mp->disconnect_flag = !0;
+	pvr2_context_notify(mp);
+}
+
+
+void pvr2_channel_init(struct pvr2_channel *cp,struct pvr2_context *mp)
+{
+	pvr2_context_enter(mp);
+	cp->hdw = mp->hdw;
+	cp->mc_head = mp;
+	cp->mc_next = NULL;
+	cp->mc_prev = mp->mc_last;
+	if (mp->mc_last) {
+		mp->mc_last->mc_next = cp;
+	} else {
+		mp->mc_first = cp;
+	}
+	mp->mc_last = cp;
+	pvr2_context_exit(mp);
+}
+
+
+static void pvr2_channel_disclaim_stream(struct pvr2_channel *cp)
+{
+	if (!cp->stream) return;
+	pvr2_stream_kill(cp->stream->stream);
+	cp->stream->user = NULL;
+	cp->stream = NULL;
+}
+
+
+void pvr2_channel_done(struct pvr2_channel *cp)
+{
+	struct pvr2_context *mp = cp->mc_head;
+	pvr2_context_enter(mp);
+	cp->input_mask = 0;
+	pvr2_channel_disclaim_stream(cp);
+	pvr2_context_reset_input_limits(mp);
+	if (cp->mc_next) {
+		cp->mc_next->mc_prev = cp->mc_prev;
+	} else {
+		mp->mc_last = cp->mc_prev;
+	}
+	if (cp->mc_prev) {
+		cp->mc_prev->mc_next = cp->mc_next;
+	} else {
+		mp->mc_first = cp->mc_next;
+	}
+	cp->hdw = NULL;
+	pvr2_context_exit(mp);
+}
+
+
+int pvr2_channel_limit_inputs(struct pvr2_channel *cp,unsigned int cmsk)
+{
+	unsigned int tmsk,mmsk;
+	int ret = 0;
+	struct pvr2_channel *p2;
+	struct pvr2_hdw *hdw = cp->hdw;
+
+	mmsk = pvr2_hdw_get_input_available(hdw);
+	cmsk &= mmsk;
+	if (cmsk == cp->input_mask) {
+		/* No change; nothing to do */
+		return 0;
+	}
+
+	pvr2_context_enter(cp->mc_head);
+	do {
+		if (!cmsk) {
+			cp->input_mask = 0;
+			pvr2_context_reset_input_limits(cp->mc_head);
+			break;
+		}
+		tmsk = mmsk;
+		for (p2 = cp->mc_head->mc_first; p2; p2 = p2->mc_next) {
+			if (p2 == cp) continue;
+			if (!p2->input_mask) continue;
+			tmsk &= p2->input_mask;
+		}
+		if (!(tmsk & cmsk)) {
+			ret = -EPERM;
+			break;
+		}
+		tmsk &= cmsk;
+		if ((ret = pvr2_hdw_set_input_allowed(hdw,mmsk,tmsk)) != 0) {
+			/* Internal failure changing allowed list; probably
+			   should not happen, but react if it does. */
+			break;
+		}
+		cp->input_mask = cmsk;
+		pvr2_hdw_commit_ctl(hdw);
+	} while (0);
+	pvr2_context_exit(cp->mc_head);
+	return ret;
+}
+
+
+unsigned int pvr2_channel_get_limited_inputs(struct pvr2_channel *cp)
+{
+	return cp->input_mask;
+}
+
+
+int pvr2_channel_claim_stream(struct pvr2_channel *cp,
+			      struct pvr2_context_stream *sp)
+{
+	int code = 0;
+	pvr2_context_enter(cp->mc_head); do {
+		if (sp == cp->stream) break;
+		if (sp && sp->user) {
+			code = -EBUSY;
+			break;
+		}
+		pvr2_channel_disclaim_stream(cp);
+		if (!sp) break;
+		sp->user = cp;
+		cp->stream = sp;
+	} while (0);
+	pvr2_context_exit(cp->mc_head);
+	return code;
+}
+
+
+// This is the marker for the real beginning of a legitimate mpeg2 stream.
+static char stream_sync_key[] = {
+	0x00, 0x00, 0x01, 0xba,
+};
+
+struct pvr2_ioread *pvr2_channel_create_mpeg_stream(
+	struct pvr2_context_stream *sp)
+{
+	struct pvr2_ioread *cp;
+	cp = pvr2_ioread_create();
+	if (!cp) return NULL;
+	pvr2_ioread_setup(cp,sp->stream);
+	pvr2_ioread_set_sync_key(cp,stream_sync_key,sizeof(stream_sync_key));
+	return cp;
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-context.h b/drivers/media/usb/pvrusb2/pvrusb2-context.h
new file mode 100644
index 0000000..13e00c5
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-context.h
@@ -0,0 +1,81 @@
+/*
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+#ifndef __PVRUSB2_CONTEXT_H
+#define __PVRUSB2_CONTEXT_H
+
+#include <linux/mutex.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+
+struct pvr2_hdw;     /* hardware interface - defined elsewhere */
+struct pvr2_stream;  /* stream interface - defined elsewhere */
+
+struct pvr2_context;        /* All central state */
+struct pvr2_channel;        /* One I/O pathway to a user */
+struct pvr2_context_stream; /* Wrapper for a stream */
+struct pvr2_ioread;         /* Low level stream structure */
+
+struct pvr2_context_stream {
+	struct pvr2_channel *user;
+	struct pvr2_stream *stream;
+};
+
+struct pvr2_context {
+	struct pvr2_channel *mc_first;
+	struct pvr2_channel *mc_last;
+	struct pvr2_context *exist_next;
+	struct pvr2_context *exist_prev;
+	struct pvr2_context *notify_next;
+	struct pvr2_context *notify_prev;
+	struct pvr2_hdw *hdw;
+	struct pvr2_context_stream video_stream;
+	struct mutex mutex;
+	int notify_flag;
+	int initialized_flag;
+	int disconnect_flag;
+
+	/* Called after pvr2_context initialization is complete */
+	void (*setup_func)(struct pvr2_context *);
+
+};
+
+struct pvr2_channel {
+	struct pvr2_context *mc_head;
+	struct pvr2_channel *mc_next;
+	struct pvr2_channel *mc_prev;
+	struct pvr2_context_stream *stream;
+	struct pvr2_hdw *hdw;
+	unsigned int input_mask;
+	void (*check_func)(struct pvr2_channel *);
+};
+
+struct pvr2_context *pvr2_context_create(struct usb_interface *intf,
+					 const struct usb_device_id *devid,
+					 void (*setup_func)(struct pvr2_context *));
+void pvr2_context_disconnect(struct pvr2_context *);
+
+void pvr2_channel_init(struct pvr2_channel *,struct pvr2_context *);
+void pvr2_channel_done(struct pvr2_channel *);
+int pvr2_channel_limit_inputs(struct pvr2_channel *,unsigned int);
+unsigned int pvr2_channel_get_limited_inputs(struct pvr2_channel *);
+int pvr2_channel_claim_stream(struct pvr2_channel *,
+			      struct pvr2_context_stream *);
+struct pvr2_ioread *pvr2_channel_create_mpeg_stream(
+	struct pvr2_context_stream *);
+
+int pvr2_context_global_init(void);
+void pvr2_context_global_done(void);
+
+#endif /* __PVRUSB2_CONTEXT_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-cs53l32a.c b/drivers/media/usb/pvrusb2/pvrusb2-cs53l32a.c
new file mode 100644
index 0000000..679f3ff
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-cs53l32a.c
@@ -0,0 +1,78 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  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.
+ *
+ */
+
+/*
+
+   This source file is specifically designed to interface with the
+   v4l-dvb cs53l32a module.
+
+*/
+
+#include "pvrusb2-cs53l32a.h"
+
+
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-debug.h"
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <linux/errno.h>
+
+struct routing_scheme {
+	const int *def;
+	unsigned int cnt;
+};
+
+
+static const int routing_scheme1[] = {
+	[PVR2_CVAL_INPUT_TV] = 2,  /* 1 or 2 seems to work here */
+	[PVR2_CVAL_INPUT_RADIO] = 2,
+	[PVR2_CVAL_INPUT_COMPOSITE] = 0,
+	[PVR2_CVAL_INPUT_SVIDEO] =  0,
+};
+
+static const struct routing_scheme routing_def1 = {
+	.def = routing_scheme1,
+	.cnt = ARRAY_SIZE(routing_scheme1),
+};
+
+static const struct routing_scheme *routing_schemes[] = {
+	[PVR2_ROUTING_SCHEME_ONAIR] = &routing_def1,
+};
+
+
+void pvr2_cs53l32a_subdev_update(struct pvr2_hdw *hdw, struct v4l2_subdev *sd)
+{
+	if (hdw->input_dirty || hdw->force_dirty) {
+		const struct routing_scheme *sp;
+		unsigned int sid = hdw->hdw_desc->signal_routing_scheme;
+		u32 input;
+		pvr2_trace(PVR2_TRACE_CHIPS, "subdev v4l2 set_input(%d)",
+			   hdw->input_val);
+		sp = (sid < ARRAY_SIZE(routing_schemes)) ?
+			routing_schemes[sid] : NULL;
+		if ((sp == NULL) ||
+		    (hdw->input_val < 0) ||
+		    (hdw->input_val >= sp->cnt)) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "*** WARNING *** subdev v4l2 set_input: Invalid routing scheme (%u) and/or input (%d)",
+				   sid, hdw->input_val);
+			return;
+		}
+		input = sp->def[hdw->input_val];
+		sd->ops->audio->s_routing(sd, input, 0, 0);
+	}
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-cs53l32a.h b/drivers/media/usb/pvrusb2/pvrusb2-cs53l32a.h
new file mode 100644
index 0000000..90dfb8b
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-cs53l32a.h
@@ -0,0 +1,34 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#ifndef __PVRUSB2_CS53L32A_H
+#define __PVRUSB2_CS53L32A_H
+
+/*
+
+   This module connects the pvrusb2 driver to the I2C chip level
+   driver which handles device video processing.  This interface is
+   used internally by the driver; higher level code should only
+   interact through the interface provided by pvrusb2-hdw.h.
+
+*/
+
+
+#include "pvrusb2-hdw-internal.h"
+void pvr2_cs53l32a_subdev_update(struct pvr2_hdw *, struct v4l2_subdev *);
+
+#endif /* __PVRUSB2_AUDIO_CS53L32A_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-ctrl.c b/drivers/media/usb/pvrusb2/pvrusb2-ctrl.c
new file mode 100644
index 0000000..5f4ba84
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-ctrl.c
@@ -0,0 +1,594 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#include "pvrusb2-ctrl.h"
+#include "pvrusb2-hdw-internal.h"
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/mutex.h>
+
+
+static int pvr2_ctrl_range_check(struct pvr2_ctrl *cptr,int val)
+{
+	if (cptr->info->check_value) {
+		if (!cptr->info->check_value(cptr,val)) return -ERANGE;
+	} else if (cptr->info->type == pvr2_ctl_enum) {
+		if (val < 0) return -ERANGE;
+		if (val >= cptr->info->def.type_enum.count) return -ERANGE;
+	} else {
+		int lim;
+		lim = cptr->info->def.type_int.min_value;
+		if (cptr->info->get_min_value) {
+			cptr->info->get_min_value(cptr,&lim);
+		}
+		if (val < lim) return -ERANGE;
+		lim = cptr->info->def.type_int.max_value;
+		if (cptr->info->get_max_value) {
+			cptr->info->get_max_value(cptr,&lim);
+		}
+		if (val > lim) return -ERANGE;
+	}
+	return 0;
+}
+
+
+/* Set the given control. */
+int pvr2_ctrl_set_value(struct pvr2_ctrl *cptr,int val)
+{
+	return pvr2_ctrl_set_mask_value(cptr,~0,val);
+}
+
+
+/* Set/clear specific bits of the given control. */
+int pvr2_ctrl_set_mask_value(struct pvr2_ctrl *cptr,int mask,int val)
+{
+	int ret = 0;
+	if (!cptr) return -EINVAL;
+	LOCK_TAKE(cptr->hdw->big_lock); do {
+		if (cptr->info->set_value) {
+			if (cptr->info->type == pvr2_ctl_bitmask) {
+				mask &= cptr->info->def.type_bitmask.valid_bits;
+			} else if ((cptr->info->type == pvr2_ctl_int)||
+				   (cptr->info->type == pvr2_ctl_enum)) {
+				ret = pvr2_ctrl_range_check(cptr,val);
+				if (ret < 0) break;
+			} else if (cptr->info->type != pvr2_ctl_bool) {
+				break;
+			}
+			ret = cptr->info->set_value(cptr,mask,val);
+		} else {
+			ret = -EPERM;
+		}
+	} while(0); LOCK_GIVE(cptr->hdw->big_lock);
+	return ret;
+}
+
+
+/* Get the current value of the given control. */
+int pvr2_ctrl_get_value(struct pvr2_ctrl *cptr,int *valptr)
+{
+	int ret = 0;
+	if (!cptr) return -EINVAL;
+	LOCK_TAKE(cptr->hdw->big_lock); do {
+		ret = cptr->info->get_value(cptr,valptr);
+	} while(0); LOCK_GIVE(cptr->hdw->big_lock);
+	return ret;
+}
+
+
+/* Retrieve control's type */
+enum pvr2_ctl_type pvr2_ctrl_get_type(struct pvr2_ctrl *cptr)
+{
+	if (!cptr) return pvr2_ctl_int;
+	return cptr->info->type;
+}
+
+
+/* Retrieve control's maximum value (int type) */
+int pvr2_ctrl_get_max(struct pvr2_ctrl *cptr)
+{
+	int ret = 0;
+	if (!cptr) return 0;
+	LOCK_TAKE(cptr->hdw->big_lock); do {
+		if (cptr->info->get_max_value) {
+			cptr->info->get_max_value(cptr,&ret);
+		} else if (cptr->info->type == pvr2_ctl_int) {
+			ret = cptr->info->def.type_int.max_value;
+		}
+	} while(0); LOCK_GIVE(cptr->hdw->big_lock);
+	return ret;
+}
+
+
+/* Retrieve control's minimum value (int type) */
+int pvr2_ctrl_get_min(struct pvr2_ctrl *cptr)
+{
+	int ret = 0;
+	if (!cptr) return 0;
+	LOCK_TAKE(cptr->hdw->big_lock); do {
+		if (cptr->info->get_min_value) {
+			cptr->info->get_min_value(cptr,&ret);
+		} else if (cptr->info->type == pvr2_ctl_int) {
+			ret = cptr->info->def.type_int.min_value;
+		}
+	} while(0); LOCK_GIVE(cptr->hdw->big_lock);
+	return ret;
+}
+
+
+/* Retrieve control's default value (any type) */
+int pvr2_ctrl_get_def(struct pvr2_ctrl *cptr, int *valptr)
+{
+	int ret = 0;
+	if (!cptr) return -EINVAL;
+	LOCK_TAKE(cptr->hdw->big_lock); do {
+		if (cptr->info->get_def_value) {
+			ret = cptr->info->get_def_value(cptr, valptr);
+		} else {
+			*valptr = cptr->info->default_value;
+		}
+	} while(0); LOCK_GIVE(cptr->hdw->big_lock);
+	return ret;
+}
+
+
+/* Retrieve control's enumeration count (enum only) */
+int pvr2_ctrl_get_cnt(struct pvr2_ctrl *cptr)
+{
+	int ret = 0;
+	if (!cptr) return 0;
+	LOCK_TAKE(cptr->hdw->big_lock); do {
+		if (cptr->info->type == pvr2_ctl_enum) {
+			ret = cptr->info->def.type_enum.count;
+		}
+	} while(0); LOCK_GIVE(cptr->hdw->big_lock);
+	return ret;
+}
+
+
+/* Retrieve control's valid mask bits (bit mask only) */
+int pvr2_ctrl_get_mask(struct pvr2_ctrl *cptr)
+{
+	int ret = 0;
+	if (!cptr) return 0;
+	LOCK_TAKE(cptr->hdw->big_lock); do {
+		if (cptr->info->type == pvr2_ctl_bitmask) {
+			ret = cptr->info->def.type_bitmask.valid_bits;
+		}
+	} while(0); LOCK_GIVE(cptr->hdw->big_lock);
+	return ret;
+}
+
+
+/* Retrieve the control's name */
+const char *pvr2_ctrl_get_name(struct pvr2_ctrl *cptr)
+{
+	if (!cptr) return NULL;
+	return cptr->info->name;
+}
+
+
+/* Retrieve the control's desc */
+const char *pvr2_ctrl_get_desc(struct pvr2_ctrl *cptr)
+{
+	if (!cptr) return NULL;
+	return cptr->info->desc;
+}
+
+
+/* Retrieve a control enumeration or bit mask value */
+int pvr2_ctrl_get_valname(struct pvr2_ctrl *cptr,int val,
+			  char *bptr,unsigned int bmax,
+			  unsigned int *blen)
+{
+	int ret = -EINVAL;
+	if (!cptr) return 0;
+	*blen = 0;
+	LOCK_TAKE(cptr->hdw->big_lock); do {
+		if (cptr->info->type == pvr2_ctl_enum) {
+			const char * const *names;
+			names = cptr->info->def.type_enum.value_names;
+			if (pvr2_ctrl_range_check(cptr,val) == 0) {
+				if (names[val]) {
+					*blen = scnprintf(
+						bptr,bmax,"%s",
+						names[val]);
+				} else {
+					*blen = 0;
+				}
+				ret = 0;
+			}
+		} else if (cptr->info->type == pvr2_ctl_bitmask) {
+			const char **names;
+			unsigned int idx;
+			int msk;
+			names = cptr->info->def.type_bitmask.bit_names;
+			val &= cptr->info->def.type_bitmask.valid_bits;
+			for (idx = 0, msk = 1; val; idx++, msk <<= 1) {
+				if (val & msk) {
+					*blen = scnprintf(bptr,bmax,"%s",
+							  names[idx]);
+					ret = 0;
+					break;
+				}
+			}
+		}
+	} while(0); LOCK_GIVE(cptr->hdw->big_lock);
+	return ret;
+}
+
+
+/* Return V4L ID for this control or zero if none */
+int pvr2_ctrl_get_v4lid(struct pvr2_ctrl *cptr)
+{
+	if (!cptr) return 0;
+	return cptr->info->v4l_id;
+}
+
+
+unsigned int pvr2_ctrl_get_v4lflags(struct pvr2_ctrl *cptr)
+{
+	unsigned int flags = 0;
+
+	if (cptr->info->get_v4lflags) {
+		flags = cptr->info->get_v4lflags(cptr);
+	}
+
+	if (cptr->info->set_value) {
+		flags &= ~V4L2_CTRL_FLAG_READ_ONLY;
+	} else {
+		flags |= V4L2_CTRL_FLAG_READ_ONLY;
+	}
+
+	return flags;
+}
+
+
+/* Return true if control is writable */
+int pvr2_ctrl_is_writable(struct pvr2_ctrl *cptr)
+{
+	if (!cptr) return 0;
+	return cptr->info->set_value != NULL;
+}
+
+
+/* Return true if control has custom symbolic representation */
+int pvr2_ctrl_has_custom_symbols(struct pvr2_ctrl *cptr)
+{
+	if (!cptr) return 0;
+	if (!cptr->info->val_to_sym) return 0;
+	if (!cptr->info->sym_to_val) return 0;
+	return !0;
+}
+
+
+/* Convert a given mask/val to a custom symbolic value */
+int pvr2_ctrl_custom_value_to_sym(struct pvr2_ctrl *cptr,
+				  int mask,int val,
+				  char *buf,unsigned int maxlen,
+				  unsigned int *len)
+{
+	if (!cptr) return -EINVAL;
+	if (!cptr->info->val_to_sym) return -EINVAL;
+	return cptr->info->val_to_sym(cptr,mask,val,buf,maxlen,len);
+}
+
+
+/* Convert a symbolic value to a mask/value pair */
+int pvr2_ctrl_custom_sym_to_value(struct pvr2_ctrl *cptr,
+				  const char *buf,unsigned int len,
+				  int *maskptr,int *valptr)
+{
+	if (!cptr) return -EINVAL;
+	if (!cptr->info->sym_to_val) return -EINVAL;
+	return cptr->info->sym_to_val(cptr,buf,len,maskptr,valptr);
+}
+
+
+static unsigned int gen_bitmask_string(int msk,int val,int msk_only,
+				       const char **names,
+				       char *ptr,unsigned int len)
+{
+	unsigned int idx;
+	long sm,um;
+	int spcFl;
+	unsigned int uc,cnt;
+	const char *idStr;
+
+	spcFl = 0;
+	uc = 0;
+	um = 0;
+	for (idx = 0, sm = 1; msk; idx++, sm <<= 1) {
+		if (sm & msk) {
+			msk &= ~sm;
+			idStr = names[idx];
+			if (idStr) {
+				cnt = scnprintf(ptr,len,"%s%s%s",
+						(spcFl ? " " : ""),
+						(msk_only ? "" :
+						 ((val & sm) ? "+" : "-")),
+						idStr);
+				ptr += cnt; len -= cnt; uc += cnt;
+				spcFl = !0;
+			} else {
+				um |= sm;
+			}
+		}
+	}
+	if (um) {
+		if (msk_only) {
+			cnt = scnprintf(ptr,len,"%s0x%lx",
+					(spcFl ? " " : ""),
+					um);
+			ptr += cnt; len -= cnt; uc += cnt;
+			spcFl = !0;
+		} else if (um & val) {
+			cnt = scnprintf(ptr,len,"%s+0x%lx",
+					(spcFl ? " " : ""),
+					um & val);
+			ptr += cnt; len -= cnt; uc += cnt;
+			spcFl = !0;
+		} else if (um & ~val) {
+			cnt = scnprintf(ptr,len,"%s+0x%lx",
+					(spcFl ? " " : ""),
+					um & ~val);
+			ptr += cnt; len -= cnt; uc += cnt;
+			spcFl = !0;
+		}
+	}
+	return uc;
+}
+
+
+static const char *boolNames[] = {
+	"false",
+	"true",
+	"no",
+	"yes",
+};
+
+
+static int parse_token(const char *ptr,unsigned int len,
+		       int *valptr,
+		       const char * const *names, unsigned int namecnt)
+{
+	char buf[33];
+	unsigned int slen;
+	unsigned int idx;
+	int negfl;
+	char *p2;
+	*valptr = 0;
+	if (!names) namecnt = 0;
+	for (idx = 0; idx < namecnt; idx++) {
+		if (!names[idx]) continue;
+		slen = strlen(names[idx]);
+		if (slen != len) continue;
+		if (memcmp(names[idx],ptr,slen)) continue;
+		*valptr = idx;
+		return 0;
+	}
+	negfl = 0;
+	if ((*ptr == '-') || (*ptr == '+')) {
+		negfl = (*ptr == '-');
+		ptr++; len--;
+	}
+	if (len >= sizeof(buf)) return -EINVAL;
+	memcpy(buf,ptr,len);
+	buf[len] = 0;
+	*valptr = simple_strtol(buf,&p2,0);
+	if (negfl) *valptr = -(*valptr);
+	if (*p2) return -EINVAL;
+	return 1;
+}
+
+
+static int parse_mtoken(const char *ptr,unsigned int len,
+			int *valptr,
+			const char **names,int valid_bits)
+{
+	char buf[33];
+	unsigned int slen;
+	unsigned int idx;
+	char *p2;
+	int msk;
+	*valptr = 0;
+	for (idx = 0, msk = 1; valid_bits; idx++, msk <<= 1) {
+		if (!(msk & valid_bits)) continue;
+		valid_bits &= ~msk;
+		if (!names[idx]) continue;
+		slen = strlen(names[idx]);
+		if (slen != len) continue;
+		if (memcmp(names[idx],ptr,slen)) continue;
+		*valptr = msk;
+		return 0;
+	}
+	if (len >= sizeof(buf)) return -EINVAL;
+	memcpy(buf,ptr,len);
+	buf[len] = 0;
+	*valptr = simple_strtol(buf,&p2,0);
+	if (*p2) return -EINVAL;
+	return 0;
+}
+
+
+static int parse_tlist(const char *ptr,unsigned int len,
+		       int *maskptr,int *valptr,
+		       const char **names,int valid_bits)
+{
+	unsigned int cnt;
+	int mask,val,kv,mode,ret;
+	mask = 0;
+	val = 0;
+	ret = 0;
+	while (len) {
+		cnt = 0;
+		while ((cnt < len) &&
+		       ((ptr[cnt] <= 32) ||
+			(ptr[cnt] >= 127))) cnt++;
+		ptr += cnt;
+		len -= cnt;
+		mode = 0;
+		if ((*ptr == '-') || (*ptr == '+')) {
+			mode = (*ptr == '-') ? -1 : 1;
+			ptr++;
+			len--;
+		}
+		cnt = 0;
+		while (cnt < len) {
+			if (ptr[cnt] <= 32) break;
+			if (ptr[cnt] >= 127) break;
+			cnt++;
+		}
+		if (!cnt) break;
+		if (parse_mtoken(ptr,cnt,&kv,names,valid_bits)) {
+			ret = -EINVAL;
+			break;
+		}
+		ptr += cnt;
+		len -= cnt;
+		switch (mode) {
+		case 0:
+			mask = valid_bits;
+			val |= kv;
+			break;
+		case -1:
+			mask |= kv;
+			val &= ~kv;
+			break;
+		case 1:
+			mask |= kv;
+			val |= kv;
+			break;
+		default:
+			break;
+		}
+	}
+	*maskptr = mask;
+	*valptr = val;
+	return ret;
+}
+
+
+/* Convert a symbolic value to a mask/value pair */
+int pvr2_ctrl_sym_to_value(struct pvr2_ctrl *cptr,
+			   const char *ptr,unsigned int len,
+			   int *maskptr,int *valptr)
+{
+	int ret = -EINVAL;
+	unsigned int cnt;
+
+	*maskptr = 0;
+	*valptr = 0;
+
+	cnt = 0;
+	while ((cnt < len) && ((ptr[cnt] <= 32) || (ptr[cnt] >= 127))) cnt++;
+	len -= cnt; ptr += cnt;
+	cnt = 0;
+	while ((cnt < len) && ((ptr[len-(cnt+1)] <= 32) ||
+			       (ptr[len-(cnt+1)] >= 127))) cnt++;
+	len -= cnt;
+
+	if (!len) return -EINVAL;
+
+	LOCK_TAKE(cptr->hdw->big_lock); do {
+		if (cptr->info->type == pvr2_ctl_int) {
+			ret = parse_token(ptr,len,valptr,NULL,0);
+			if (ret >= 0) {
+				ret = pvr2_ctrl_range_check(cptr,*valptr);
+			}
+			*maskptr = ~0;
+		} else if (cptr->info->type == pvr2_ctl_bool) {
+			ret = parse_token(ptr,len,valptr,boolNames,
+					  ARRAY_SIZE(boolNames));
+			if (ret == 1) {
+				*valptr = *valptr ? !0 : 0;
+			} else if (ret == 0) {
+				*valptr = (*valptr & 1) ? !0 : 0;
+			}
+			*maskptr = 1;
+		} else if (cptr->info->type == pvr2_ctl_enum) {
+			ret = parse_token(
+				ptr,len,valptr,
+				cptr->info->def.type_enum.value_names,
+				cptr->info->def.type_enum.count);
+			if (ret >= 0) {
+				ret = pvr2_ctrl_range_check(cptr,*valptr);
+			}
+			*maskptr = ~0;
+		} else if (cptr->info->type == pvr2_ctl_bitmask) {
+			ret = parse_tlist(
+				ptr,len,maskptr,valptr,
+				cptr->info->def.type_bitmask.bit_names,
+				cptr->info->def.type_bitmask.valid_bits);
+		}
+	} while(0); LOCK_GIVE(cptr->hdw->big_lock);
+	return ret;
+}
+
+
+/* Convert a given mask/val to a symbolic value */
+int pvr2_ctrl_value_to_sym_internal(struct pvr2_ctrl *cptr,
+				    int mask,int val,
+				    char *buf,unsigned int maxlen,
+				    unsigned int *len)
+{
+	int ret = -EINVAL;
+
+	*len = 0;
+	if (cptr->info->type == pvr2_ctl_int) {
+		*len = scnprintf(buf,maxlen,"%d",val);
+		ret = 0;
+	} else if (cptr->info->type == pvr2_ctl_bool) {
+		*len = scnprintf(buf,maxlen,"%s",val ? "true" : "false");
+		ret = 0;
+	} else if (cptr->info->type == pvr2_ctl_enum) {
+		const char * const *names;
+		names = cptr->info->def.type_enum.value_names;
+		if ((val >= 0) &&
+		    (val < cptr->info->def.type_enum.count)) {
+			if (names[val]) {
+				*len = scnprintf(
+					buf,maxlen,"%s",
+					names[val]);
+			} else {
+				*len = 0;
+			}
+			ret = 0;
+		}
+	} else if (cptr->info->type == pvr2_ctl_bitmask) {
+		*len = gen_bitmask_string(
+			val & mask & cptr->info->def.type_bitmask.valid_bits,
+			~0,!0,
+			cptr->info->def.type_bitmask.bit_names,
+			buf,maxlen);
+	}
+	return ret;
+}
+
+
+/* Convert a given mask/val to a symbolic value */
+int pvr2_ctrl_value_to_sym(struct pvr2_ctrl *cptr,
+			   int mask,int val,
+			   char *buf,unsigned int maxlen,
+			   unsigned int *len)
+{
+	int ret;
+	LOCK_TAKE(cptr->hdw->big_lock); do {
+		ret = pvr2_ctrl_value_to_sym_internal(cptr,mask,val,
+						      buf,maxlen,len);
+	} while(0); LOCK_GIVE(cptr->hdw->big_lock);
+	return ret;
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-ctrl.h b/drivers/media/usb/pvrusb2/pvrusb2-ctrl.h
new file mode 100644
index 0000000..4b9152e
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-ctrl.h
@@ -0,0 +1,108 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+#ifndef __PVRUSB2_CTRL_H
+#define __PVRUSB2_CTRL_H
+
+struct pvr2_ctrl;
+
+enum pvr2_ctl_type {
+	pvr2_ctl_int = 0,
+	pvr2_ctl_enum = 1,
+	pvr2_ctl_bitmask = 2,
+	pvr2_ctl_bool = 3,
+};
+
+
+/* Set the given control. */
+int pvr2_ctrl_set_value(struct pvr2_ctrl *,int val);
+
+/* Set/clear specific bits of the given control. */
+int pvr2_ctrl_set_mask_value(struct pvr2_ctrl *,int mask,int val);
+
+/* Get the current value of the given control. */
+int pvr2_ctrl_get_value(struct pvr2_ctrl *,int *valptr);
+
+/* Retrieve control's type */
+enum pvr2_ctl_type pvr2_ctrl_get_type(struct pvr2_ctrl *);
+
+/* Retrieve control's maximum value (int type) */
+int pvr2_ctrl_get_max(struct pvr2_ctrl *);
+
+/* Retrieve control's minimum value (int type) */
+int pvr2_ctrl_get_min(struct pvr2_ctrl *);
+
+/* Retrieve control's default value (any type) */
+int pvr2_ctrl_get_def(struct pvr2_ctrl *, int *valptr);
+
+/* Retrieve control's enumeration count (enum only) */
+int pvr2_ctrl_get_cnt(struct pvr2_ctrl *);
+
+/* Retrieve control's valid mask bits (bit mask only) */
+int pvr2_ctrl_get_mask(struct pvr2_ctrl *);
+
+/* Retrieve the control's name */
+const char *pvr2_ctrl_get_name(struct pvr2_ctrl *);
+
+/* Retrieve the control's desc */
+const char *pvr2_ctrl_get_desc(struct pvr2_ctrl *);
+
+/* Retrieve a control enumeration or bit mask value */
+int pvr2_ctrl_get_valname(struct pvr2_ctrl *,int,char *,unsigned int,
+			  unsigned int *);
+
+/* Return true if control is writable */
+int pvr2_ctrl_is_writable(struct pvr2_ctrl *);
+
+/* Return V4L flags value for control (or zero if there is no v4l control
+   actually under this control) */
+unsigned int pvr2_ctrl_get_v4lflags(struct pvr2_ctrl *);
+
+/* Return V4L ID for this control or zero if none */
+int pvr2_ctrl_get_v4lid(struct pvr2_ctrl *);
+
+/* Return true if control has custom symbolic representation */
+int pvr2_ctrl_has_custom_symbols(struct pvr2_ctrl *);
+
+/* Convert a given mask/val to a custom symbolic value */
+int pvr2_ctrl_custom_value_to_sym(struct pvr2_ctrl *,
+				  int mask,int val,
+				  char *buf,unsigned int maxlen,
+				  unsigned int *len);
+
+/* Convert a symbolic value to a mask/value pair */
+int pvr2_ctrl_custom_sym_to_value(struct pvr2_ctrl *,
+				  const char *buf,unsigned int len,
+				  int *maskptr,int *valptr);
+
+/* Convert a given mask/val to a symbolic value */
+int pvr2_ctrl_value_to_sym(struct pvr2_ctrl *,
+			   int mask,int val,
+			   char *buf,unsigned int maxlen,
+			   unsigned int *len);
+
+/* Convert a symbolic value to a mask/value pair */
+int pvr2_ctrl_sym_to_value(struct pvr2_ctrl *,
+			   const char *buf,unsigned int len,
+			   int *maskptr,int *valptr);
+
+/* Convert a given mask/val to a symbolic value - must already be
+   inside of critical region. */
+int pvr2_ctrl_value_to_sym_internal(struct pvr2_ctrl *,
+			   int mask,int val,
+			   char *buf,unsigned int maxlen,
+			   unsigned int *len);
+
+#endif /* __PVRUSB2_CTRL_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-cx2584x-v4l.c b/drivers/media/usb/pvrusb2/pvrusb2-cx2584x-v4l.c
new file mode 100644
index 0000000..d5bec0f
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-cx2584x-v4l.c
@@ -0,0 +1,147 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  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.
+ *
+ */
+
+/*
+
+   This source file is specifically designed to interface with the
+   cx2584x, in kernels 2.6.16 or newer.
+
+*/
+
+#include "pvrusb2-cx2584x-v4l.h"
+
+
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-debug.h"
+#include <media/drv-intf/cx25840.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <linux/errno.h>
+
+
+struct routing_scheme_item {
+	int vid;
+	int aud;
+};
+
+struct routing_scheme {
+	const struct routing_scheme_item *def;
+	unsigned int cnt;
+};
+
+static const struct routing_scheme_item routing_scheme0[] = {
+	[PVR2_CVAL_INPUT_TV] = {
+		.vid = CX25840_COMPOSITE7,
+		.aud = CX25840_AUDIO8,
+	},
+	[PVR2_CVAL_INPUT_RADIO] = { /* Treat the same as composite */
+		.vid = CX25840_COMPOSITE3,
+		.aud = CX25840_AUDIO_SERIAL,
+	},
+	[PVR2_CVAL_INPUT_COMPOSITE] = {
+		.vid = CX25840_COMPOSITE3,
+		.aud = CX25840_AUDIO_SERIAL,
+	},
+	[PVR2_CVAL_INPUT_SVIDEO] = {
+		.vid = CX25840_SVIDEO1,
+		.aud = CX25840_AUDIO_SERIAL,
+	},
+};
+
+static const struct routing_scheme routing_def0 = {
+	.def = routing_scheme0,
+	.cnt = ARRAY_SIZE(routing_scheme0),
+};
+
+/* Specific to gotview device */
+static const struct routing_scheme_item routing_schemegv[] = {
+	[PVR2_CVAL_INPUT_TV] = {
+		.vid = CX25840_COMPOSITE2,
+		.aud = CX25840_AUDIO5,
+	},
+	[PVR2_CVAL_INPUT_RADIO] = {
+		/* line-in is used for radio and composite.  A GPIO is
+		   used to switch between the two choices. */
+		.vid = CX25840_COMPOSITE1,
+		.aud = CX25840_AUDIO_SERIAL,
+	},
+	[PVR2_CVAL_INPUT_COMPOSITE] = {
+		.vid = CX25840_COMPOSITE1,
+		.aud = CX25840_AUDIO_SERIAL,
+	},
+	[PVR2_CVAL_INPUT_SVIDEO] = {
+		.vid = (CX25840_SVIDEO_LUMA3|CX25840_SVIDEO_CHROMA4),
+		.aud = CX25840_AUDIO_SERIAL,
+	},
+};
+
+static const struct routing_scheme routing_defgv = {
+	.def = routing_schemegv,
+	.cnt = ARRAY_SIZE(routing_schemegv),
+};
+
+/* Specific to grabster av400 device */
+static const struct routing_scheme_item routing_schemeav400[] = {
+	[PVR2_CVAL_INPUT_COMPOSITE] = {
+		.vid = CX25840_COMPOSITE1,
+		.aud = CX25840_AUDIO_SERIAL,
+	},
+	[PVR2_CVAL_INPUT_SVIDEO] = {
+		.vid = (CX25840_SVIDEO_LUMA2|CX25840_SVIDEO_CHROMA4),
+		.aud = CX25840_AUDIO_SERIAL,
+	},
+};
+
+static const struct routing_scheme routing_defav400 = {
+	.def = routing_schemeav400,
+	.cnt = ARRAY_SIZE(routing_schemeav400),
+};
+
+static const struct routing_scheme *routing_schemes[] = {
+	[PVR2_ROUTING_SCHEME_HAUPPAUGE] = &routing_def0,
+	[PVR2_ROUTING_SCHEME_GOTVIEW] = &routing_defgv,
+	[PVR2_ROUTING_SCHEME_AV400] = &routing_defav400,
+};
+
+void pvr2_cx25840_subdev_update(struct pvr2_hdw *hdw, struct v4l2_subdev *sd)
+{
+	pvr2_trace(PVR2_TRACE_CHIPS, "subdev cx2584x update...");
+	if (hdw->input_dirty || hdw->force_dirty) {
+		enum cx25840_video_input vid_input;
+		enum cx25840_audio_input aud_input;
+		const struct routing_scheme *sp;
+		unsigned int sid = hdw->hdw_desc->signal_routing_scheme;
+
+		sp = (sid < ARRAY_SIZE(routing_schemes)) ?
+			routing_schemes[sid] : NULL;
+		if ((sp == NULL) ||
+		    (hdw->input_val < 0) ||
+		    (hdw->input_val >= sp->cnt)) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "*** WARNING *** subdev cx2584x set_input: Invalid routing scheme (%u) and/or input (%d)",
+				   sid, hdw->input_val);
+			return;
+		}
+		vid_input = sp->def[hdw->input_val].vid;
+		aud_input = sp->def[hdw->input_val].aud;
+		pvr2_trace(PVR2_TRACE_CHIPS,
+			   "subdev cx2584x set_input vid=0x%x aud=0x%x",
+			   vid_input, aud_input);
+		sd->ops->video->s_routing(sd, (u32)vid_input, 0, 0);
+		sd->ops->audio->s_routing(sd, (u32)aud_input, 0, 0);
+	}
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-cx2584x-v4l.h b/drivers/media/usb/pvrusb2/pvrusb2-cx2584x-v4l.h
new file mode 100644
index 0000000..dfddc88
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-cx2584x-v4l.h
@@ -0,0 +1,38 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#ifndef __PVRUSB2_CX2584X_V4L_H
+#define __PVRUSB2_CX2584X_V4L_H
+
+/*
+
+   This module connects the pvrusb2 driver to the I2C chip level
+   driver which handles combined device audio & video processing.
+   This interface is used internally by the driver; higher level code
+   should only interact through the interface provided by
+   pvrusb2-hdw.h.
+
+*/
+
+
+
+#include "pvrusb2-hdw-internal.h"
+
+void pvr2_cx25840_subdev_update(struct pvr2_hdw *, struct v4l2_subdev *sd);
+
+
+#endif /* __PVRUSB2_CX2584X_V4L_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-debug.h b/drivers/media/usb/pvrusb2/pvrusb2-debug.h
new file mode 100644
index 0000000..5cd1629
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-debug.h
@@ -0,0 +1,55 @@
+/*
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+#ifndef __PVRUSB2_DEBUG_H
+#define __PVRUSB2_DEBUG_H
+
+extern int pvrusb2_debug;
+
+#define pvr2_trace(msk, fmt, arg...) do {if(msk & pvrusb2_debug) printk(KERN_INFO "pvrusb2: " fmt "\n", ##arg); } while (0)
+
+/* These are listed in *rough* order of decreasing usefulness and
+   increasing noise level. */
+#define PVR2_TRACE_INFO       (1 <<  0) /* Normal messages */
+#define PVR2_TRACE_ERROR_LEGS (1 <<  1) /* error messages */
+#define PVR2_TRACE_TOLERANCE  (1 <<  2) /* track tolerance-affected errors */
+#define PVR2_TRACE_TRAP       (1 <<  3) /* Trap & report app misbehavior */
+#define PVR2_TRACE_STD        (1 <<  4) /* Log video standard stuff */
+#define PVR2_TRACE_INIT       (1 <<  5) /* misc initialization steps */
+#define PVR2_TRACE_START_STOP (1 <<  6) /* Streaming start / stop */
+#define PVR2_TRACE_CTL        (1 <<  7) /* commit of control changes */
+#define PVR2_TRACE_STATE      (1 <<  8) /* Device state changes */
+#define PVR2_TRACE_STBITS     (1 <<  9) /* Individual bit state changes */
+#define PVR2_TRACE_EEPROM     (1 << 10) /* eeprom parsing / report */
+#define PVR2_TRACE_STRUCT     (1 << 11) /* internal struct creation */
+#define PVR2_TRACE_OPEN_CLOSE (1 << 12) /* application open / close */
+#define PVR2_TRACE_CTXT       (1 << 13) /* Main context tracking */
+#define PVR2_TRACE_SYSFS      (1 << 14) /* Sysfs driven I/O */
+#define PVR2_TRACE_FIRMWARE   (1 << 15) /* firmware upload actions */
+#define PVR2_TRACE_CHIPS      (1 << 16) /* chip broadcast operation */
+#define PVR2_TRACE_I2C        (1 << 17) /* I2C related stuff */
+#define PVR2_TRACE_I2C_CMD    (1 << 18) /* Software commands to I2C modules */
+#define PVR2_TRACE_I2C_CORE   (1 << 19) /* I2C core debugging */
+#define PVR2_TRACE_I2C_TRAF   (1 << 20) /* I2C traffic through the adapter */
+#define PVR2_TRACE_V4LIOCTL   (1 << 21) /* v4l ioctl details */
+#define PVR2_TRACE_ENCODER    (1 << 22) /* mpeg2 encoder operation */
+#define PVR2_TRACE_BUF_POOL   (1 << 23) /* Track buffer pool management */
+#define PVR2_TRACE_BUF_FLOW   (1 << 24) /* Track buffer flow in system */
+#define PVR2_TRACE_DATA_FLOW  (1 << 25) /* Track data flow */
+#define PVR2_TRACE_DEBUGIFC   (1 << 26) /* Debug interface actions */
+#define PVR2_TRACE_GPIO       (1 << 27) /* GPIO state bit changes */
+#define PVR2_TRACE_DVB_FEED   (1 << 28) /* DVB transport feed debug */
+
+
+#endif /* __PVRUSB2_HDW_INTERNAL_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-debugifc.c b/drivers/media/usb/pvrusb2/pvrusb2-debugifc.c
new file mode 100644
index 0000000..d3f3bd9
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-debugifc.c
@@ -0,0 +1,318 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#include <linux/string.h>
+#include "pvrusb2-debugifc.h"
+#include "pvrusb2-hdw.h"
+#include "pvrusb2-debug.h"
+
+struct debugifc_mask_item {
+	const char *name;
+	unsigned long msk;
+};
+
+
+static unsigned int debugifc_count_whitespace(const char *buf,
+					      unsigned int count)
+{
+	unsigned int scnt;
+	char ch;
+
+	for (scnt = 0; scnt < count; scnt++) {
+		ch = buf[scnt];
+		if (ch == ' ') continue;
+		if (ch == '\t') continue;
+		if (ch == '\n') continue;
+		break;
+	}
+	return scnt;
+}
+
+
+static unsigned int debugifc_count_nonwhitespace(const char *buf,
+						 unsigned int count)
+{
+	unsigned int scnt;
+	char ch;
+
+	for (scnt = 0; scnt < count; scnt++) {
+		ch = buf[scnt];
+		if (ch == ' ') break;
+		if (ch == '\t') break;
+		if (ch == '\n') break;
+	}
+	return scnt;
+}
+
+
+static unsigned int debugifc_isolate_word(const char *buf,unsigned int count,
+					  const char **wstrPtr,
+					  unsigned int *wlenPtr)
+{
+	const char *wptr;
+	unsigned int consume_cnt = 0;
+	unsigned int wlen;
+	unsigned int scnt;
+
+	wptr = NULL;
+	wlen = 0;
+	scnt = debugifc_count_whitespace(buf,count);
+	consume_cnt += scnt; count -= scnt; buf += scnt;
+	if (!count) goto done;
+
+	scnt = debugifc_count_nonwhitespace(buf,count);
+	if (!scnt) goto done;
+	wptr = buf;
+	wlen = scnt;
+	consume_cnt += scnt; count -= scnt; buf += scnt;
+
+ done:
+	*wstrPtr = wptr;
+	*wlenPtr = wlen;
+	return consume_cnt;
+}
+
+
+static int debugifc_parse_unsigned_number(const char *buf,unsigned int count,
+					  u32 *num_ptr)
+{
+	u32 result = 0;
+	int radix = 10;
+	if ((count >= 2) && (buf[0] == '0') &&
+	    ((buf[1] == 'x') || (buf[1] == 'X'))) {
+		radix = 16;
+		count -= 2;
+		buf += 2;
+	} else if ((count >= 1) && (buf[0] == '0')) {
+		radix = 8;
+	}
+
+	while (count--) {
+		int val = hex_to_bin(*buf++);
+		if (val < 0 || val >= radix)
+			return -EINVAL;
+		result *= radix;
+		result += val;
+	}
+	*num_ptr = result;
+	return 0;
+}
+
+
+static int debugifc_match_keyword(const char *buf,unsigned int count,
+				  const char *keyword)
+{
+	unsigned int kl;
+	if (!keyword) return 0;
+	kl = strlen(keyword);
+	if (kl != count) return 0;
+	return !memcmp(buf,keyword,kl);
+}
+
+
+int pvr2_debugifc_print_info(struct pvr2_hdw *hdw,char *buf,unsigned int acnt)
+{
+	int bcnt = 0;
+	int ccnt;
+	ccnt = scnprintf(buf, acnt, "Driver hardware description: %s\n",
+			 pvr2_hdw_get_desc(hdw));
+	bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+	ccnt = scnprintf(buf,acnt,"Driver state info:\n");
+	bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+	ccnt = pvr2_hdw_state_report(hdw,buf,acnt);
+	bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+
+	return bcnt;
+}
+
+
+int pvr2_debugifc_print_status(struct pvr2_hdw *hdw,
+			       char *buf,unsigned int acnt)
+{
+	int bcnt = 0;
+	int ccnt;
+	int ret;
+	u32 gpio_dir,gpio_in,gpio_out;
+	struct pvr2_stream_stats stats;
+	struct pvr2_stream *sp;
+
+	ret = pvr2_hdw_is_hsm(hdw);
+	ccnt = scnprintf(buf,acnt,"USB link speed: %s\n",
+			 (ret < 0 ? "FAIL" : (ret ? "high" : "full")));
+	bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+
+	gpio_dir = 0; gpio_in = 0; gpio_out = 0;
+	pvr2_hdw_gpio_get_dir(hdw,&gpio_dir);
+	pvr2_hdw_gpio_get_out(hdw,&gpio_out);
+	pvr2_hdw_gpio_get_in(hdw,&gpio_in);
+	ccnt = scnprintf(buf,acnt,"GPIO state: dir=0x%x in=0x%x out=0x%x\n",
+			 gpio_dir,gpio_in,gpio_out);
+	bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+
+	ccnt = scnprintf(buf,acnt,"Streaming is %s\n",
+			 pvr2_hdw_get_streaming(hdw) ? "on" : "off");
+	bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+
+
+	sp = pvr2_hdw_get_video_stream(hdw);
+	if (sp) {
+		pvr2_stream_get_stats(sp, &stats, 0);
+		ccnt = scnprintf(
+			buf,acnt,
+			"Bytes streamed=%u URBs: queued=%u idle=%u ready=%u processed=%u failed=%u\n",
+			stats.bytes_processed,
+			stats.buffers_in_queue,
+			stats.buffers_in_idle,
+			stats.buffers_in_ready,
+			stats.buffers_processed,
+			stats.buffers_failed);
+		bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+	}
+
+	return bcnt;
+}
+
+
+static int pvr2_debugifc_do1cmd(struct pvr2_hdw *hdw,const char *buf,
+				unsigned int count)
+{
+	const char *wptr;
+	unsigned int wlen;
+	unsigned int scnt;
+
+	scnt = debugifc_isolate_word(buf,count,&wptr,&wlen);
+	if (!scnt) return 0;
+	count -= scnt; buf += scnt;
+	if (!wptr) return 0;
+
+	pvr2_trace(PVR2_TRACE_DEBUGIFC,"debugifc cmd: \"%.*s\"",wlen,wptr);
+	if (debugifc_match_keyword(wptr,wlen,"reset")) {
+		scnt = debugifc_isolate_word(buf,count,&wptr,&wlen);
+		if (!scnt) return -EINVAL;
+		count -= scnt; buf += scnt;
+		if (!wptr) return -EINVAL;
+		if (debugifc_match_keyword(wptr,wlen,"cpu")) {
+			pvr2_hdw_cpureset_assert(hdw,!0);
+			pvr2_hdw_cpureset_assert(hdw,0);
+			return 0;
+		} else if (debugifc_match_keyword(wptr,wlen,"bus")) {
+			pvr2_hdw_device_reset(hdw);
+		} else if (debugifc_match_keyword(wptr,wlen,"soft")) {
+			return pvr2_hdw_cmd_powerup(hdw);
+		} else if (debugifc_match_keyword(wptr,wlen,"deep")) {
+			return pvr2_hdw_cmd_deep_reset(hdw);
+		} else if (debugifc_match_keyword(wptr,wlen,"firmware")) {
+			return pvr2_upload_firmware2(hdw);
+		} else if (debugifc_match_keyword(wptr,wlen,"decoder")) {
+			return pvr2_hdw_cmd_decoder_reset(hdw);
+		} else if (debugifc_match_keyword(wptr,wlen,"worker")) {
+			return pvr2_hdw_untrip(hdw);
+		} else if (debugifc_match_keyword(wptr,wlen,"usbstats")) {
+			pvr2_stream_get_stats(pvr2_hdw_get_video_stream(hdw),
+					      NULL, !0);
+			return 0;
+		}
+		return -EINVAL;
+	} else if (debugifc_match_keyword(wptr,wlen,"cpufw")) {
+		scnt = debugifc_isolate_word(buf,count,&wptr,&wlen);
+		if (!scnt) return -EINVAL;
+		count -= scnt; buf += scnt;
+		if (!wptr) return -EINVAL;
+		if (debugifc_match_keyword(wptr,wlen,"fetch")) {
+			scnt = debugifc_isolate_word(buf,count,&wptr,&wlen);
+			if (scnt && wptr) {
+				count -= scnt; buf += scnt;
+				if (debugifc_match_keyword(wptr, wlen,
+							   "prom")) {
+					pvr2_hdw_cpufw_set_enabled(hdw, 2, !0);
+				} else if (debugifc_match_keyword(wptr, wlen,
+								  "ram8k")) {
+					pvr2_hdw_cpufw_set_enabled(hdw, 0, !0);
+				} else if (debugifc_match_keyword(wptr, wlen,
+								  "ram16k")) {
+					pvr2_hdw_cpufw_set_enabled(hdw, 1, !0);
+				} else {
+					return -EINVAL;
+				}
+			}
+			pvr2_hdw_cpufw_set_enabled(hdw,0,!0);
+			return 0;
+		} else if (debugifc_match_keyword(wptr,wlen,"done")) {
+			pvr2_hdw_cpufw_set_enabled(hdw,0,0);
+			return 0;
+		} else {
+			return -EINVAL;
+		}
+	} else if (debugifc_match_keyword(wptr,wlen,"gpio")) {
+		int dir_fl = 0;
+		int ret;
+		u32 msk,val;
+		scnt = debugifc_isolate_word(buf,count,&wptr,&wlen);
+		if (!scnt) return -EINVAL;
+		count -= scnt; buf += scnt;
+		if (!wptr) return -EINVAL;
+		if (debugifc_match_keyword(wptr,wlen,"dir")) {
+			dir_fl = !0;
+		} else if (!debugifc_match_keyword(wptr,wlen,"out")) {
+			return -EINVAL;
+		}
+		scnt = debugifc_isolate_word(buf,count,&wptr,&wlen);
+		if (!scnt) return -EINVAL;
+		count -= scnt; buf += scnt;
+		if (!wptr) return -EINVAL;
+		ret = debugifc_parse_unsigned_number(wptr,wlen,&msk);
+		if (ret) return ret;
+		scnt = debugifc_isolate_word(buf,count,&wptr,&wlen);
+		if (wptr) {
+			ret = debugifc_parse_unsigned_number(wptr,wlen,&val);
+			if (ret) return ret;
+		} else {
+			val = msk;
+			msk = 0xffffffff;
+		}
+		if (dir_fl) {
+			ret = pvr2_hdw_gpio_chg_dir(hdw,msk,val);
+		} else {
+			ret = pvr2_hdw_gpio_chg_out(hdw,msk,val);
+		}
+		return ret;
+	}
+	pvr2_trace(PVR2_TRACE_DEBUGIFC,
+		   "debugifc failed to recognize cmd: \"%.*s\"",wlen,wptr);
+	return -EINVAL;
+}
+
+
+int pvr2_debugifc_docmd(struct pvr2_hdw *hdw,const char *buf,
+			unsigned int count)
+{
+	unsigned int bcnt = 0;
+	int ret;
+
+	while (count) {
+		for (bcnt = 0; bcnt < count; bcnt++) {
+			if (buf[bcnt] == '\n') break;
+		}
+
+		ret = pvr2_debugifc_do1cmd(hdw,buf,bcnt);
+		if (ret < 0) return ret;
+		if (bcnt < count) bcnt++;
+		buf += bcnt;
+		count -= bcnt;
+	}
+
+	return 0;
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-debugifc.h b/drivers/media/usb/pvrusb2/pvrusb2-debugifc.h
new file mode 100644
index 0000000..fcaaa8d
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-debugifc.h
@@ -0,0 +1,38 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+#ifndef __PVRUSB2_DEBUGIFC_H
+#define __PVRUSB2_DEBUGIFC_H
+
+struct pvr2_hdw;
+
+/* Print general status of driver.  This will also trigger a probe of
+   the USB link.  Unlike print_info(), this one synchronizes with the
+   driver so the information should be self-consistent (but it will
+   hang if the driver is wedged). */
+int pvr2_debugifc_print_info(struct pvr2_hdw *,
+			     char *buf_ptr, unsigned int buf_size);
+
+/* Non-intrusively print some useful debugging info from inside the
+   driver.  This should work even if the driver appears to be
+   wedged. */
+int pvr2_debugifc_print_status(struct pvr2_hdw *,
+			       char *buf_ptr,unsigned int buf_size);
+
+/* Parse a string command into a driver action. */
+int pvr2_debugifc_docmd(struct pvr2_hdw *,
+			const char *buf_ptr,unsigned int buf_size);
+
+#endif /* __PVRUSB2_DEBUGIFC_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-devattr.c b/drivers/media/usb/pvrusb2/pvrusb2-devattr.c
new file mode 100644
index 0000000..06de1c8
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-devattr.c
@@ -0,0 +1,562 @@
+/*
+ *
+ *
+ *  Copyright (C) 2007 Mike Isely <isely@pobox.com>
+ *
+ *  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
+ *
+ *  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.
+ *
+ */
+
+/*
+
+This source file should encompass ALL per-device type information for the
+driver.  To define a new device, add elements to the pvr2_device_table and
+pvr2_device_desc structures.
+
+*/
+
+#include "pvrusb2-devattr.h"
+#include <linux/usb.h>
+#include <linux/module.h>
+/* This is needed in order to pull in tuner type ids... */
+#include <linux/i2c.h>
+#include <media/tuner.h>
+#ifdef CONFIG_VIDEO_PVRUSB2_DVB
+#include "pvrusb2-hdw-internal.h"
+#include "lgdt330x.h"
+#include "s5h1409.h"
+#include "s5h1411.h"
+#include "tda10048.h"
+#include "tda18271.h"
+#include "tda8290.h"
+#include "tuner-simple.h"
+#endif
+
+
+/*------------------------------------------------------------------------*/
+/* Hauppauge PVR-USB2 Model 29xxx */
+
+static const struct pvr2_device_client_desc pvr2_cli_29xxx[] = {
+	{ .module_id = PVR2_CLIENT_ID_SAA7115 },
+	{ .module_id = PVR2_CLIENT_ID_MSP3400 },
+	{ .module_id = PVR2_CLIENT_ID_TUNER },
+	{ .module_id = PVR2_CLIENT_ID_DEMOD },
+};
+
+#define PVR2_FIRMWARE_29xxx "v4l-pvrusb2-29xxx-01.fw"
+static const char *pvr2_fw1_names_29xxx[] = {
+		PVR2_FIRMWARE_29xxx,
+};
+
+static const struct pvr2_device_desc pvr2_device_29xxx = {
+		.description = "WinTV PVR USB2 Model 29xxx",
+		.shortname = "29xxx",
+		.client_table.lst = pvr2_cli_29xxx,
+		.client_table.cnt = ARRAY_SIZE(pvr2_cli_29xxx),
+		.fx2_firmware.lst = pvr2_fw1_names_29xxx,
+		.fx2_firmware.cnt = ARRAY_SIZE(pvr2_fw1_names_29xxx),
+		.flag_has_hauppauge_rom = !0,
+		.flag_has_analogtuner = !0,
+		.flag_has_fmradio = !0,
+		.flag_has_composite = !0,
+		.flag_has_svideo = !0,
+		.signal_routing_scheme = PVR2_ROUTING_SCHEME_HAUPPAUGE,
+		.led_scheme = PVR2_LED_SCHEME_HAUPPAUGE,
+		.ir_scheme = PVR2_IR_SCHEME_29XXX,
+};
+
+
+
+/*------------------------------------------------------------------------*/
+/* Hauppauge PVR-USB2 Model 24xxx */
+
+static const struct pvr2_device_client_desc pvr2_cli_24xxx[] = {
+	{ .module_id = PVR2_CLIENT_ID_CX25840 },
+	{ .module_id = PVR2_CLIENT_ID_TUNER },
+	{ .module_id = PVR2_CLIENT_ID_WM8775 },
+	{ .module_id = PVR2_CLIENT_ID_DEMOD },
+};
+
+#define PVR2_FIRMWARE_24xxx "v4l-pvrusb2-24xxx-01.fw"
+static const char *pvr2_fw1_names_24xxx[] = {
+		PVR2_FIRMWARE_24xxx,
+};
+
+static const struct pvr2_device_desc pvr2_device_24xxx = {
+		.description = "WinTV PVR USB2 Model 24xxx",
+		.shortname = "24xxx",
+		.client_table.lst = pvr2_cli_24xxx,
+		.client_table.cnt = ARRAY_SIZE(pvr2_cli_24xxx),
+		.fx2_firmware.lst = pvr2_fw1_names_24xxx,
+		.fx2_firmware.cnt = ARRAY_SIZE(pvr2_fw1_names_24xxx),
+		.flag_has_cx25840 = !0,
+		.flag_has_wm8775 = !0,
+		.flag_has_hauppauge_rom = !0,
+		.flag_has_analogtuner = !0,
+		.flag_has_fmradio = !0,
+		.flag_has_composite = !0,
+		.flag_has_svideo = !0,
+		.signal_routing_scheme = PVR2_ROUTING_SCHEME_HAUPPAUGE,
+		.led_scheme = PVR2_LED_SCHEME_HAUPPAUGE,
+		.ir_scheme = PVR2_IR_SCHEME_24XXX,
+};
+
+
+
+/*------------------------------------------------------------------------*/
+/* GOTVIEW USB2.0 DVD2 */
+
+static const struct pvr2_device_client_desc pvr2_cli_gotview_2[] = {
+	{ .module_id = PVR2_CLIENT_ID_CX25840 },
+	{ .module_id = PVR2_CLIENT_ID_TUNER },
+	{ .module_id = PVR2_CLIENT_ID_DEMOD },
+};
+
+static const struct pvr2_device_desc pvr2_device_gotview_2 = {
+		.description = "Gotview USB 2.0 DVD 2",
+		.shortname = "gv2",
+		.client_table.lst = pvr2_cli_gotview_2,
+		.client_table.cnt = ARRAY_SIZE(pvr2_cli_gotview_2),
+		.flag_has_cx25840 = !0,
+		.default_tuner_type = TUNER_PHILIPS_FM1216ME_MK3,
+		.flag_has_analogtuner = !0,
+		.flag_has_fmradio = !0,
+		.flag_has_composite = !0,
+		.flag_has_svideo = !0,
+		.signal_routing_scheme = PVR2_ROUTING_SCHEME_GOTVIEW,
+};
+
+
+
+/*------------------------------------------------------------------------*/
+/* GOTVIEW USB2.0 DVD Deluxe */
+
+/* (same module list as gotview_2) */
+
+static const struct pvr2_device_desc pvr2_device_gotview_2d = {
+		.description = "Gotview USB 2.0 DVD Deluxe",
+		.shortname = "gv2d",
+		.client_table.lst = pvr2_cli_gotview_2,
+		.client_table.cnt = ARRAY_SIZE(pvr2_cli_gotview_2),
+		.flag_has_cx25840 = !0,
+		.default_tuner_type = TUNER_PHILIPS_FM1216ME_MK3,
+		.flag_has_analogtuner = !0,
+		.flag_has_composite = !0,
+		.flag_has_svideo = !0,
+		.signal_routing_scheme = PVR2_ROUTING_SCHEME_GOTVIEW,
+};
+
+
+
+/*------------------------------------------------------------------------*/
+/* Terratec Grabster AV400 */
+
+static const struct pvr2_device_client_desc pvr2_cli_av400[] = {
+	{ .module_id = PVR2_CLIENT_ID_CX25840 },
+};
+
+static const struct pvr2_device_desc pvr2_device_av400 = {
+		.description = "Terratec Grabster AV400",
+		.shortname = "av400",
+		.flag_is_experimental = 1,
+		.client_table.lst = pvr2_cli_av400,
+		.client_table.cnt = ARRAY_SIZE(pvr2_cli_av400),
+		.flag_has_cx25840 = !0,
+		.flag_has_analogtuner = 0,
+		.flag_has_composite = !0,
+		.flag_has_svideo = !0,
+		.signal_routing_scheme = PVR2_ROUTING_SCHEME_AV400,
+};
+
+
+
+/*------------------------------------------------------------------------*/
+/* OnAir Creator */
+
+#ifdef CONFIG_VIDEO_PVRUSB2_DVB
+static struct lgdt330x_config pvr2_lgdt3303_config = {
+	.demod_chip          = LGDT3303,
+	.clock_polarity_flip = 1,
+};
+
+static int pvr2_lgdt3303_attach(struct pvr2_dvb_adapter *adap)
+{
+	adap->fe = dvb_attach(lgdt330x_attach, &pvr2_lgdt3303_config,
+			      0x0e,
+			      &adap->channel.hdw->i2c_adap);
+	if (adap->fe)
+		return 0;
+
+	return -EIO;
+}
+
+static int pvr2_lgh06xf_attach(struct pvr2_dvb_adapter *adap)
+{
+	dvb_attach(simple_tuner_attach, adap->fe,
+		   &adap->channel.hdw->i2c_adap, 0x61,
+		   TUNER_LG_TDVS_H06XF);
+
+	return 0;
+}
+
+static const struct pvr2_dvb_props pvr2_onair_creator_fe_props = {
+	.frontend_attach = pvr2_lgdt3303_attach,
+	.tuner_attach    = pvr2_lgh06xf_attach,
+};
+#endif
+
+static const struct pvr2_device_client_desc pvr2_cli_onair_creator[] = {
+	{ .module_id = PVR2_CLIENT_ID_SAA7115 },
+	{ .module_id = PVR2_CLIENT_ID_CS53L32A },
+	{ .module_id = PVR2_CLIENT_ID_TUNER },
+};
+
+static const struct pvr2_device_desc pvr2_device_onair_creator = {
+		.description = "OnAir Creator Hybrid USB tuner",
+		.shortname = "oac",
+		.client_table.lst = pvr2_cli_onair_creator,
+		.client_table.cnt = ARRAY_SIZE(pvr2_cli_onair_creator),
+		.default_tuner_type = TUNER_LG_TDVS_H06XF,
+		.flag_has_analogtuner = !0,
+		.flag_has_composite = !0,
+		.flag_has_svideo = !0,
+		.flag_digital_requires_cx23416 = !0,
+		.signal_routing_scheme = PVR2_ROUTING_SCHEME_ONAIR,
+		.digital_control_scheme = PVR2_DIGITAL_SCHEME_ONAIR,
+		.default_std_mask = V4L2_STD_NTSC_M,
+#ifdef CONFIG_VIDEO_PVRUSB2_DVB
+		.dvb_props = &pvr2_onair_creator_fe_props,
+#endif
+};
+
+
+
+/*------------------------------------------------------------------------*/
+/* OnAir USB 2.0 */
+
+#ifdef CONFIG_VIDEO_PVRUSB2_DVB
+static struct lgdt330x_config pvr2_lgdt3302_config = {
+	.demod_chip          = LGDT3302,
+};
+
+static int pvr2_lgdt3302_attach(struct pvr2_dvb_adapter *adap)
+{
+	adap->fe = dvb_attach(lgdt330x_attach, &pvr2_lgdt3302_config,
+			      0x0e,
+			      &adap->channel.hdw->i2c_adap);
+	if (adap->fe)
+		return 0;
+
+	return -EIO;
+}
+
+static int pvr2_fcv1236d_attach(struct pvr2_dvb_adapter *adap)
+{
+	dvb_attach(simple_tuner_attach, adap->fe,
+		   &adap->channel.hdw->i2c_adap, 0x61,
+		   TUNER_PHILIPS_FCV1236D);
+
+	return 0;
+}
+
+static const struct pvr2_dvb_props pvr2_onair_usb2_fe_props = {
+	.frontend_attach = pvr2_lgdt3302_attach,
+	.tuner_attach    = pvr2_fcv1236d_attach,
+};
+#endif
+
+static const struct pvr2_device_client_desc pvr2_cli_onair_usb2[] = {
+	{ .module_id = PVR2_CLIENT_ID_SAA7115 },
+	{ .module_id = PVR2_CLIENT_ID_CS53L32A },
+	{ .module_id = PVR2_CLIENT_ID_TUNER },
+};
+
+static const struct pvr2_device_desc pvr2_device_onair_usb2 = {
+		.description = "OnAir USB2 Hybrid USB tuner",
+		.shortname = "oa2",
+		.client_table.lst = pvr2_cli_onair_usb2,
+		.client_table.cnt = ARRAY_SIZE(pvr2_cli_onair_usb2),
+		.default_tuner_type = TUNER_PHILIPS_FCV1236D,
+		.flag_has_analogtuner = !0,
+		.flag_has_composite = !0,
+		.flag_has_svideo = !0,
+		.flag_digital_requires_cx23416 = !0,
+		.signal_routing_scheme = PVR2_ROUTING_SCHEME_ONAIR,
+		.digital_control_scheme = PVR2_DIGITAL_SCHEME_ONAIR,
+		.default_std_mask = V4L2_STD_NTSC_M,
+#ifdef CONFIG_VIDEO_PVRUSB2_DVB
+		.dvb_props = &pvr2_onair_usb2_fe_props,
+#endif
+};
+
+
+
+/*------------------------------------------------------------------------*/
+/* Hauppauge PVR-USB2 Model 73xxx */
+
+#ifdef CONFIG_VIDEO_PVRUSB2_DVB
+static struct tda10048_config hauppauge_tda10048_config = {
+	.demod_address  = 0x10 >> 1,
+	.output_mode    = TDA10048_PARALLEL_OUTPUT,
+	.fwbulkwritelen = TDA10048_BULKWRITE_50,
+	.inversion      = TDA10048_INVERSION_ON,
+	.dtv6_if_freq_khz = TDA10048_IF_3300,
+	.dtv7_if_freq_khz = TDA10048_IF_3800,
+	.dtv8_if_freq_khz = TDA10048_IF_4300,
+	.clk_freq_khz   = TDA10048_CLK_16000,
+	.disable_gate_access = 1,
+};
+
+static struct tda829x_config tda829x_no_probe = {
+	.probe_tuner = TDA829X_DONT_PROBE,
+};
+
+static struct tda18271_std_map hauppauge_tda18271_dvbt_std_map = {
+	.dvbt_6   = { .if_freq = 3300, .agc_mode = 3, .std = 4,
+		      .if_lvl = 1, .rfagc_top = 0x37, },
+	.dvbt_7   = { .if_freq = 3800, .agc_mode = 3, .std = 5,
+		      .if_lvl = 1, .rfagc_top = 0x37, },
+	.dvbt_8   = { .if_freq = 4300, .agc_mode = 3, .std = 6,
+		      .if_lvl = 1, .rfagc_top = 0x37, },
+};
+
+static struct tda18271_config hauppauge_tda18271_dvb_config = {
+	.std_map = &hauppauge_tda18271_dvbt_std_map,
+	.gate    = TDA18271_GATE_ANALOG,
+	.output_opt = TDA18271_OUTPUT_LT_OFF,
+};
+
+static int pvr2_tda10048_attach(struct pvr2_dvb_adapter *adap)
+{
+	adap->fe = dvb_attach(tda10048_attach, &hauppauge_tda10048_config,
+			      &adap->channel.hdw->i2c_adap);
+	if (adap->fe)
+		return 0;
+
+	return -EIO;
+}
+
+static int pvr2_73xxx_tda18271_8295_attach(struct pvr2_dvb_adapter *adap)
+{
+	dvb_attach(tda829x_attach, adap->fe,
+		   &adap->channel.hdw->i2c_adap, 0x42,
+		   &tda829x_no_probe);
+	dvb_attach(tda18271_attach, adap->fe, 0x60,
+		   &adap->channel.hdw->i2c_adap,
+		   &hauppauge_tda18271_dvb_config);
+
+	return 0;
+}
+
+static const struct pvr2_dvb_props pvr2_73xxx_dvb_props = {
+	.frontend_attach = pvr2_tda10048_attach,
+	.tuner_attach    = pvr2_73xxx_tda18271_8295_attach,
+};
+#endif
+
+static const struct pvr2_device_client_desc pvr2_cli_73xxx[] = {
+	{ .module_id = PVR2_CLIENT_ID_CX25840 },
+	{ .module_id = PVR2_CLIENT_ID_TUNER,
+	  .i2c_address_list = "\x42"},
+};
+
+#define PVR2_FIRMWARE_73xxx "v4l-pvrusb2-73xxx-01.fw"
+static const char *pvr2_fw1_names_73xxx[] = {
+		PVR2_FIRMWARE_73xxx,
+};
+
+static const struct pvr2_device_desc pvr2_device_73xxx = {
+		.description = "WinTV HVR-1900 Model 73xxx",
+		.shortname = "73xxx",
+		.client_table.lst = pvr2_cli_73xxx,
+		.client_table.cnt = ARRAY_SIZE(pvr2_cli_73xxx),
+		.fx2_firmware.lst = pvr2_fw1_names_73xxx,
+		.fx2_firmware.cnt = ARRAY_SIZE(pvr2_fw1_names_73xxx),
+		.flag_has_cx25840 = !0,
+		.flag_has_hauppauge_rom = !0,
+		.flag_has_analogtuner = !0,
+		.flag_has_composite = !0,
+		.flag_has_svideo = !0,
+		.flag_fx2_16kb = !0,
+		.signal_routing_scheme = PVR2_ROUTING_SCHEME_HAUPPAUGE,
+		.digital_control_scheme = PVR2_DIGITAL_SCHEME_HAUPPAUGE,
+		.led_scheme = PVR2_LED_SCHEME_HAUPPAUGE,
+		.ir_scheme = PVR2_IR_SCHEME_ZILOG,
+#ifdef CONFIG_VIDEO_PVRUSB2_DVB
+		.dvb_props = &pvr2_73xxx_dvb_props,
+#endif
+};
+
+
+
+/*------------------------------------------------------------------------*/
+/* Hauppauge PVR-USB2 Model 75xxx */
+
+#ifdef CONFIG_VIDEO_PVRUSB2_DVB
+static struct s5h1409_config pvr2_s5h1409_config = {
+	.demod_address = 0x32 >> 1,
+	.output_mode   = S5H1409_PARALLEL_OUTPUT,
+	.gpio          = S5H1409_GPIO_OFF,
+	.qam_if        = 4000,
+	.inversion     = S5H1409_INVERSION_ON,
+	.status_mode   = S5H1409_DEMODLOCKING,
+};
+
+static struct s5h1411_config pvr2_s5h1411_config = {
+	.output_mode   = S5H1411_PARALLEL_OUTPUT,
+	.gpio          = S5H1411_GPIO_OFF,
+	.vsb_if        = S5H1411_IF_44000,
+	.qam_if        = S5H1411_IF_4000,
+	.inversion     = S5H1411_INVERSION_ON,
+	.status_mode   = S5H1411_DEMODLOCKING,
+};
+
+static struct tda18271_std_map hauppauge_tda18271_std_map = {
+	.atsc_6   = { .if_freq = 5380, .agc_mode = 3, .std = 3,
+		      .if_lvl = 6, .rfagc_top = 0x37, },
+	.qam_6    = { .if_freq = 4000, .agc_mode = 3, .std = 0,
+		      .if_lvl = 6, .rfagc_top = 0x37, },
+};
+
+static struct tda18271_config hauppauge_tda18271_config = {
+	.std_map = &hauppauge_tda18271_std_map,
+	.gate    = TDA18271_GATE_ANALOG,
+	.output_opt = TDA18271_OUTPUT_LT_OFF,
+};
+
+static int pvr2_s5h1409_attach(struct pvr2_dvb_adapter *adap)
+{
+	adap->fe = dvb_attach(s5h1409_attach, &pvr2_s5h1409_config,
+			      &adap->channel.hdw->i2c_adap);
+	if (adap->fe)
+		return 0;
+
+	return -EIO;
+}
+
+static int pvr2_s5h1411_attach(struct pvr2_dvb_adapter *adap)
+{
+	adap->fe = dvb_attach(s5h1411_attach, &pvr2_s5h1411_config,
+			      &adap->channel.hdw->i2c_adap);
+	if (adap->fe)
+		return 0;
+
+	return -EIO;
+}
+
+static int pvr2_tda18271_8295_attach(struct pvr2_dvb_adapter *adap)
+{
+	dvb_attach(tda829x_attach, adap->fe,
+		   &adap->channel.hdw->i2c_adap, 0x42,
+		   &tda829x_no_probe);
+	dvb_attach(tda18271_attach, adap->fe, 0x60,
+		   &adap->channel.hdw->i2c_adap,
+		   &hauppauge_tda18271_config);
+
+	return 0;
+}
+
+static const struct pvr2_dvb_props pvr2_750xx_dvb_props = {
+	.frontend_attach = pvr2_s5h1409_attach,
+	.tuner_attach    = pvr2_tda18271_8295_attach,
+};
+
+static const struct pvr2_dvb_props pvr2_751xx_dvb_props = {
+	.frontend_attach = pvr2_s5h1411_attach,
+	.tuner_attach    = pvr2_tda18271_8295_attach,
+};
+#endif
+
+#define PVR2_FIRMWARE_75xxx "v4l-pvrusb2-73xxx-01.fw"
+static const char *pvr2_fw1_names_75xxx[] = {
+		PVR2_FIRMWARE_75xxx,
+};
+
+static const struct pvr2_device_desc pvr2_device_750xx = {
+		.description = "WinTV HVR-1950 Model 750xx",
+		.shortname = "750xx",
+		.client_table.lst = pvr2_cli_73xxx,
+		.client_table.cnt = ARRAY_SIZE(pvr2_cli_73xxx),
+		.fx2_firmware.lst = pvr2_fw1_names_75xxx,
+		.fx2_firmware.cnt = ARRAY_SIZE(pvr2_fw1_names_75xxx),
+		.flag_has_cx25840 = !0,
+		.flag_has_hauppauge_rom = !0,
+		.flag_has_analogtuner = !0,
+		.flag_has_composite = !0,
+		.flag_has_svideo = !0,
+		.flag_fx2_16kb = !0,
+		.signal_routing_scheme = PVR2_ROUTING_SCHEME_HAUPPAUGE,
+		.digital_control_scheme = PVR2_DIGITAL_SCHEME_HAUPPAUGE,
+		.default_std_mask = V4L2_STD_NTSC_M,
+		.led_scheme = PVR2_LED_SCHEME_HAUPPAUGE,
+		.ir_scheme = PVR2_IR_SCHEME_ZILOG,
+#ifdef CONFIG_VIDEO_PVRUSB2_DVB
+		.dvb_props = &pvr2_750xx_dvb_props,
+#endif
+};
+
+static const struct pvr2_device_desc pvr2_device_751xx = {
+		.description = "WinTV HVR-1950 Model 751xx",
+		.shortname = "751xx",
+		.client_table.lst = pvr2_cli_73xxx,
+		.client_table.cnt = ARRAY_SIZE(pvr2_cli_73xxx),
+		.fx2_firmware.lst = pvr2_fw1_names_75xxx,
+		.fx2_firmware.cnt = ARRAY_SIZE(pvr2_fw1_names_75xxx),
+		.flag_has_cx25840 = !0,
+		.flag_has_hauppauge_rom = !0,
+		.flag_has_analogtuner = !0,
+		.flag_has_composite = !0,
+		.flag_has_svideo = !0,
+		.flag_fx2_16kb = !0,
+		.signal_routing_scheme = PVR2_ROUTING_SCHEME_HAUPPAUGE,
+		.digital_control_scheme = PVR2_DIGITAL_SCHEME_HAUPPAUGE,
+		.default_std_mask = V4L2_STD_NTSC_M,
+		.led_scheme = PVR2_LED_SCHEME_HAUPPAUGE,
+		.ir_scheme = PVR2_IR_SCHEME_ZILOG,
+#ifdef CONFIG_VIDEO_PVRUSB2_DVB
+		.dvb_props = &pvr2_751xx_dvb_props,
+#endif
+};
+
+
+
+/*------------------------------------------------------------------------*/
+
+struct usb_device_id pvr2_device_table[] = {
+	{ USB_DEVICE(0x2040, 0x2900),
+	  .driver_info = (kernel_ulong_t)&pvr2_device_29xxx},
+	{ USB_DEVICE(0x2040, 0x2950), /* Logically identical to 2900 */
+	  .driver_info = (kernel_ulong_t)&pvr2_device_29xxx},
+	{ USB_DEVICE(0x2040, 0x2400),
+	  .driver_info = (kernel_ulong_t)&pvr2_device_24xxx},
+	{ USB_DEVICE(0x1164, 0x0622),
+	  .driver_info = (kernel_ulong_t)&pvr2_device_gotview_2},
+	{ USB_DEVICE(0x1164, 0x0602),
+	  .driver_info = (kernel_ulong_t)&pvr2_device_gotview_2d},
+	{ USB_DEVICE(0x11ba, 0x1003),
+	  .driver_info = (kernel_ulong_t)&pvr2_device_onair_creator},
+	{ USB_DEVICE(0x11ba, 0x1001),
+	  .driver_info = (kernel_ulong_t)&pvr2_device_onair_usb2},
+	{ USB_DEVICE(0x2040, 0x7300),
+	  .driver_info = (kernel_ulong_t)&pvr2_device_73xxx},
+	{ USB_DEVICE(0x2040, 0x7500),
+	  .driver_info = (kernel_ulong_t)&pvr2_device_750xx},
+	{ USB_DEVICE(0x2040, 0x7501),
+	  .driver_info = (kernel_ulong_t)&pvr2_device_751xx},
+	{ USB_DEVICE(0x0ccd, 0x0039),
+	  .driver_info = (kernel_ulong_t)&pvr2_device_av400},
+	{ }
+};
+
+MODULE_DEVICE_TABLE(usb, pvr2_device_table);
+MODULE_FIRMWARE(PVR2_FIRMWARE_29xxx);
+MODULE_FIRMWARE(PVR2_FIRMWARE_24xxx);
+MODULE_FIRMWARE(PVR2_FIRMWARE_73xxx);
+MODULE_FIRMWARE(PVR2_FIRMWARE_75xxx);
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-devattr.h b/drivers/media/usb/pvrusb2/pvrusb2-devattr.h
new file mode 100644
index 0000000..c1e7d48
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-devattr.h
@@ -0,0 +1,185 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+#ifndef __PVRUSB2_DEVATTR_H
+#define __PVRUSB2_DEVATTR_H
+
+#include <linux/mod_devicetable.h>
+#include <linux/videodev2.h>
+#ifdef CONFIG_VIDEO_PVRUSB2_DVB
+#include "pvrusb2-dvb.h"
+#endif
+
+/*
+
+  This header defines structures used to describe attributes of a device.
+
+*/
+
+
+#define PVR2_CLIENT_ID_NULL 0
+#define PVR2_CLIENT_ID_MSP3400 1
+#define PVR2_CLIENT_ID_CX25840 2
+#define PVR2_CLIENT_ID_SAA7115 3
+#define PVR2_CLIENT_ID_TUNER 4
+#define PVR2_CLIENT_ID_CS53L32A 5
+#define PVR2_CLIENT_ID_WM8775 6
+#define PVR2_CLIENT_ID_DEMOD 7
+
+struct pvr2_device_client_desc {
+	/* One ovr PVR2_CLIENT_ID_xxxx */
+	unsigned char module_id;
+
+	/* Null-terminated array of I2C addresses to try in order
+	   initialize the module.  It's safe to make this null terminated
+	   since we're never going to encounter an i2c device with an
+	   address of zero.  If this is a null pointer or zero-length,
+	   then no I2C addresses have been specified, in which case we'll
+	   try some compiled in defaults for now. */
+	unsigned char *i2c_address_list;
+};
+
+struct pvr2_device_client_table {
+	const struct pvr2_device_client_desc *lst;
+	unsigned char cnt;
+};
+
+
+struct pvr2_string_table {
+	const char **lst;
+	unsigned int cnt;
+};
+
+#define PVR2_ROUTING_SCHEME_HAUPPAUGE 0
+#define PVR2_ROUTING_SCHEME_GOTVIEW 1
+#define PVR2_ROUTING_SCHEME_ONAIR 2
+#define PVR2_ROUTING_SCHEME_AV400 3
+
+#define PVR2_DIGITAL_SCHEME_NONE 0
+#define PVR2_DIGITAL_SCHEME_HAUPPAUGE 1
+#define PVR2_DIGITAL_SCHEME_ONAIR 2
+
+#define PVR2_LED_SCHEME_NONE 0
+#define PVR2_LED_SCHEME_HAUPPAUGE 1
+
+#define PVR2_IR_SCHEME_NONE 0
+#define PVR2_IR_SCHEME_24XXX 1 /* FX2-controlled IR */
+#define PVR2_IR_SCHEME_ZILOG 2 /* HVR-1950 style (must be taken out of reset) */
+#define PVR2_IR_SCHEME_24XXX_MCE 3 /* 24xxx MCE device */
+#define PVR2_IR_SCHEME_29XXX 4 /* Original 29xxx device */
+
+/* This describes a particular hardware type (except for the USB device ID
+   which must live in a separate structure due to environmental
+   constraints).  See the top of pvrusb2-hdw.c for where this is
+   instantiated. */
+struct pvr2_device_desc {
+	/* Single line text description of hardware */
+	const char *description;
+
+	/* Single token identifier for hardware */
+	const char *shortname;
+
+	/* List of additional client modules we need to load */
+	struct pvr2_string_table client_modules;
+
+	/* List of defined client modules we need to load */
+	struct pvr2_device_client_table client_table;
+
+	/* List of FX2 firmware file names we should search; if empty then
+	   FX2 firmware check / load is skipped and we assume the device
+	   was initialized from internal ROM. */
+	struct pvr2_string_table fx2_firmware;
+
+#ifdef CONFIG_VIDEO_PVRUSB2_DVB
+	/* callback functions to handle attachment of digital tuner & demod */
+	const struct pvr2_dvb_props *dvb_props;
+
+#endif
+	/* Initial standard bits to use for this device, if not zero.
+	   Anything set here is also implied as an available standard.
+	   Note: This is ignored if overridden on the module load line via
+	   the video_std module option. */
+	v4l2_std_id default_std_mask;
+
+	/* V4L tuner type ID to use with this device (only used if the
+	   driver could not discover the type any other way). */
+	int default_tuner_type;
+
+	/* Signal routing scheme used by device, contains one of
+	   PVR2_ROUTING_SCHEME_XXX.  Schemes have to be defined as we
+	   encounter them.  This is an arbitrary integer scheme id; its
+	   meaning is contained entirely within the driver and is
+	   interpreted by logic which must send commands to the chip-level
+	   drivers (search for things which touch this field). */
+	unsigned char signal_routing_scheme;
+
+	/* Indicates scheme for controlling device's LED (if any).  The
+	   driver will turn on the LED when streaming is underway.  This
+	   contains one of PVR2_LED_SCHEME_XXX. */
+	unsigned char led_scheme;
+
+	/* Control scheme to use if there is a digital tuner.  This
+	   contains one of PVR2_DIGITAL_SCHEME_XXX.  This is an arbitrary
+	   integer scheme id; its meaning is contained entirely within the
+	   driver and is interpreted by logic which must control the
+	   streaming pathway (search for things which touch this field). */
+	unsigned char digital_control_scheme;
+
+	/* If set, we don't bother trying to load cx23416 firmware. */
+	unsigned int flag_skip_cx23416_firmware:1;
+
+	/* If set, the encoder must be healthy in order for digital mode to
+	   work (otherwise we assume that digital streaming will work even
+	   if we fail to locate firmware for the encoder).  If the device
+	   doesn't support digital streaming then this flag has no
+	   effect. */
+	unsigned int flag_digital_requires_cx23416:1;
+
+	/* Device has a hauppauge eeprom which we can interrogate. */
+	unsigned int flag_has_hauppauge_rom:1;
+
+	/* Device does not require a powerup command to be issued. */
+	unsigned int flag_no_powerup:1;
+
+	/* Device has a cx25840 - this enables special additional logic to
+	   handle it. */
+	unsigned int flag_has_cx25840:1;
+
+	/* Device has a wm8775 - this enables special additional logic to
+	   ensure that it is found. */
+	unsigned int flag_has_wm8775:1;
+
+	/* Indicate IR scheme of hardware.  If not set, then it is assumed
+	   that IR can work without any help from the driver. */
+	unsigned int ir_scheme:3;
+
+	/* These bits define which kinds of sources the device can handle.
+	   Note: Digital tuner presence is inferred by the
+	   digital_control_scheme enumeration. */
+	unsigned int flag_has_fmradio:1;       /* Has FM radio receiver */
+	unsigned int flag_has_analogtuner:1;   /* Has analog tuner */
+	unsigned int flag_has_composite:1;     /* Has composite input */
+	unsigned int flag_has_svideo:1;        /* Has s-video input */
+	unsigned int flag_fx2_16kb:1;          /* 16KB FX2 firmware OK here */
+
+	/* If this driver is considered experimental, i.e. not all aspects
+	   are working correctly and/or it is untested, mark that fact
+	   with this flag. */
+	unsigned int flag_is_experimental:1;
+};
+
+extern struct usb_device_id pvr2_device_table[];
+
+#endif /* __PVRUSB2_HDW_INTERNAL_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-dvb.c b/drivers/media/usb/pvrusb2/pvrusb2-dvb.c
new file mode 100644
index 0000000..4b32b21
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-dvb.c
@@ -0,0 +1,431 @@
+/*
+ *  pvrusb2-dvb.c - linux-dvb api interface to the pvrusb2 driver.
+ *
+ *  Copyright (C) 2007, 2008 Michael Krufky <mkrufky@linuxtv.org>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#include <linux/kthread.h>
+#include <linux/freezer.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <media/dvbdev.h>
+#include "pvrusb2-debug.h"
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-hdw.h"
+#include "pvrusb2-io.h"
+#include "pvrusb2-dvb.h"
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static int pvr2_dvb_feed_func(struct pvr2_dvb_adapter *adap)
+{
+	int ret;
+	unsigned int count;
+	struct pvr2_buffer *bp;
+	struct pvr2_stream *stream;
+
+	pvr2_trace(PVR2_TRACE_DVB_FEED, "dvb feed thread started");
+	set_freezable();
+
+	stream = adap->channel.stream->stream;
+
+	for (;;) {
+		if (kthread_should_stop()) break;
+
+		/* Not sure about this... */
+		try_to_freeze();
+
+		bp = pvr2_stream_get_ready_buffer(stream);
+		if (bp != NULL) {
+			count = pvr2_buffer_get_count(bp);
+			if (count) {
+				dvb_dmx_swfilter(
+					&adap->demux,
+					adap->buffer_storage[
+					    pvr2_buffer_get_id(bp)],
+					count);
+			} else {
+				ret = pvr2_buffer_get_status(bp);
+				if (ret < 0) break;
+			}
+			ret = pvr2_buffer_queue(bp);
+			if (ret < 0) break;
+
+			/* Since we know we did something to a buffer,
+			   just go back and try again.  No point in
+			   blocking unless we really ran out of
+			   buffers to process. */
+			continue;
+		}
+
+
+		/* Wait until more buffers become available or we're
+		   told not to wait any longer. */
+		ret = wait_event_interruptible(
+		    adap->buffer_wait_data,
+		    (pvr2_stream_get_ready_count(stream) > 0) ||
+		    kthread_should_stop());
+		if (ret < 0) break;
+	}
+
+	/* If we get here and ret is < 0, then an error has occurred.
+	   Probably would be a good idea to communicate that to DVB core... */
+
+	pvr2_trace(PVR2_TRACE_DVB_FEED, "dvb feed thread stopped");
+
+	return 0;
+}
+
+static int pvr2_dvb_feed_thread(void *data)
+{
+	int stat = pvr2_dvb_feed_func(data);
+	/* from videobuf-dvb.c: */
+	while (!kthread_should_stop()) {
+		set_current_state(TASK_INTERRUPTIBLE);
+		schedule();
+	}
+	return stat;
+}
+
+static void pvr2_dvb_notify(struct pvr2_dvb_adapter *adap)
+{
+	wake_up(&adap->buffer_wait_data);
+}
+
+static void pvr2_dvb_stream_end(struct pvr2_dvb_adapter *adap)
+{
+	unsigned int idx;
+	struct pvr2_stream *stream;
+
+	if (adap->thread) {
+		kthread_stop(adap->thread);
+		adap->thread = NULL;
+	}
+
+	if (adap->channel.stream) {
+		stream = adap->channel.stream->stream;
+	} else {
+		stream = NULL;
+	}
+	if (stream) {
+		pvr2_hdw_set_streaming(adap->channel.hdw, 0);
+		pvr2_stream_set_callback(stream, NULL, NULL);
+		pvr2_stream_kill(stream);
+		pvr2_stream_set_buffer_count(stream, 0);
+		pvr2_channel_claim_stream(&adap->channel, NULL);
+	}
+
+	if (adap->stream_run) {
+		for (idx = 0; idx < PVR2_DVB_BUFFER_COUNT; idx++) {
+			if (!(adap->buffer_storage[idx])) continue;
+			kfree(adap->buffer_storage[idx]);
+			adap->buffer_storage[idx] = NULL;
+		}
+		adap->stream_run = 0;
+	}
+}
+
+static int pvr2_dvb_stream_do_start(struct pvr2_dvb_adapter *adap)
+{
+	struct pvr2_context *pvr = adap->channel.mc_head;
+	unsigned int idx;
+	int ret;
+	struct pvr2_buffer *bp;
+	struct pvr2_stream *stream = NULL;
+
+	if (adap->stream_run) return -EIO;
+
+	ret = pvr2_channel_claim_stream(&adap->channel, &pvr->video_stream);
+	/* somebody else already has the stream */
+	if (ret < 0) return ret;
+
+	stream = adap->channel.stream->stream;
+
+	for (idx = 0; idx < PVR2_DVB_BUFFER_COUNT; idx++) {
+		adap->buffer_storage[idx] = kmalloc(PVR2_DVB_BUFFER_SIZE,
+						    GFP_KERNEL);
+		if (!(adap->buffer_storage[idx])) return -ENOMEM;
+	}
+
+	pvr2_stream_set_callback(pvr->video_stream.stream,
+				 (pvr2_stream_callback) pvr2_dvb_notify, adap);
+
+	ret = pvr2_stream_set_buffer_count(stream, PVR2_DVB_BUFFER_COUNT);
+	if (ret < 0) return ret;
+
+	for (idx = 0; idx < PVR2_DVB_BUFFER_COUNT; idx++) {
+		bp = pvr2_stream_get_buffer(stream, idx);
+		pvr2_buffer_set_buffer(bp,
+				       adap->buffer_storage[idx],
+				       PVR2_DVB_BUFFER_SIZE);
+	}
+
+	ret = pvr2_hdw_set_streaming(adap->channel.hdw, 1);
+	if (ret < 0) return ret;
+
+	while ((bp = pvr2_stream_get_idle_buffer(stream)) != NULL) {
+		ret = pvr2_buffer_queue(bp);
+		if (ret < 0) return ret;
+	}
+
+	adap->thread = kthread_run(pvr2_dvb_feed_thread, adap, "pvrusb2-dvb");
+
+	if (IS_ERR(adap->thread)) {
+		ret = PTR_ERR(adap->thread);
+		adap->thread = NULL;
+		return ret;
+	}
+
+	adap->stream_run = !0;
+
+	return 0;
+}
+
+static int pvr2_dvb_stream_start(struct pvr2_dvb_adapter *adap)
+{
+	int ret = pvr2_dvb_stream_do_start(adap);
+	if (ret < 0) pvr2_dvb_stream_end(adap);
+	return ret;
+}
+
+static int pvr2_dvb_ctrl_feed(struct dvb_demux_feed *dvbdmxfeed, int onoff)
+{
+	struct pvr2_dvb_adapter *adap = dvbdmxfeed->demux->priv;
+	int ret = 0;
+
+	if (adap == NULL) return -ENODEV;
+
+	mutex_lock(&adap->lock);
+	do {
+		if (onoff) {
+			if (!adap->feedcount) {
+				pvr2_trace(PVR2_TRACE_DVB_FEED,
+					   "start feeding demux");
+				ret = pvr2_dvb_stream_start(adap);
+				if (ret < 0) break;
+			}
+			(adap->feedcount)++;
+		} else if (adap->feedcount > 0) {
+			(adap->feedcount)--;
+			if (!adap->feedcount) {
+				pvr2_trace(PVR2_TRACE_DVB_FEED,
+					   "stop feeding demux");
+				pvr2_dvb_stream_end(adap);
+			}
+		}
+	} while (0);
+	mutex_unlock(&adap->lock);
+
+	return ret;
+}
+
+static int pvr2_dvb_start_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+	pvr2_trace(PVR2_TRACE_DVB_FEED, "start pid: 0x%04x", dvbdmxfeed->pid);
+	return pvr2_dvb_ctrl_feed(dvbdmxfeed, 1);
+}
+
+static int pvr2_dvb_stop_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+	pvr2_trace(PVR2_TRACE_DVB_FEED, "stop pid: 0x%04x", dvbdmxfeed->pid);
+	return pvr2_dvb_ctrl_feed(dvbdmxfeed, 0);
+}
+
+static int pvr2_dvb_bus_ctrl(struct dvb_frontend *fe, int acquire)
+{
+	struct pvr2_dvb_adapter *adap = fe->dvb->priv;
+	return pvr2_channel_limit_inputs(
+	    &adap->channel,
+	    (acquire ? (1 << PVR2_CVAL_INPUT_DTV) : 0));
+}
+
+static int pvr2_dvb_adapter_init(struct pvr2_dvb_adapter *adap)
+{
+	int ret;
+
+	ret = dvb_register_adapter(&adap->dvb_adap, "pvrusb2-dvb",
+				   THIS_MODULE/*&hdw->usb_dev->owner*/,
+				   &adap->channel.hdw->usb_dev->dev,
+				   adapter_nr);
+	if (ret < 0) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "dvb_register_adapter failed: error %d", ret);
+		goto err;
+	}
+	adap->dvb_adap.priv = adap;
+
+	adap->demux.dmx.capabilities = DMX_TS_FILTERING |
+				       DMX_SECTION_FILTERING |
+				       DMX_MEMORY_BASED_FILTERING;
+	adap->demux.priv             = adap;
+	adap->demux.filternum        = 256;
+	adap->demux.feednum          = 256;
+	adap->demux.start_feed       = pvr2_dvb_start_feed;
+	adap->demux.stop_feed        = pvr2_dvb_stop_feed;
+	adap->demux.write_to_decoder = NULL;
+
+	ret = dvb_dmx_init(&adap->demux);
+	if (ret < 0) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "dvb_dmx_init failed: error %d", ret);
+		goto err_dmx;
+	}
+
+	adap->dmxdev.filternum       = adap->demux.filternum;
+	adap->dmxdev.demux           = &adap->demux.dmx;
+	adap->dmxdev.capabilities    = 0;
+
+	ret = dvb_dmxdev_init(&adap->dmxdev, &adap->dvb_adap);
+	if (ret < 0) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "dvb_dmxdev_init failed: error %d", ret);
+		goto err_dmx_dev;
+	}
+
+	dvb_net_init(&adap->dvb_adap, &adap->dvb_net, &adap->demux.dmx);
+
+	return 0;
+
+err_dmx_dev:
+	dvb_dmx_release(&adap->demux);
+err_dmx:
+	dvb_unregister_adapter(&adap->dvb_adap);
+err:
+	return ret;
+}
+
+static int pvr2_dvb_adapter_exit(struct pvr2_dvb_adapter *adap)
+{
+	pvr2_trace(PVR2_TRACE_INFO, "unregistering DVB devices");
+	dvb_net_release(&adap->dvb_net);
+	adap->demux.dmx.close(&adap->demux.dmx);
+	dvb_dmxdev_release(&adap->dmxdev);
+	dvb_dmx_release(&adap->demux);
+	dvb_unregister_adapter(&adap->dvb_adap);
+	return 0;
+}
+
+static int pvr2_dvb_frontend_init(struct pvr2_dvb_adapter *adap)
+{
+	struct pvr2_hdw *hdw = adap->channel.hdw;
+	const struct pvr2_dvb_props *dvb_props = hdw->hdw_desc->dvb_props;
+	int ret = 0;
+
+	if (dvb_props == NULL) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS, "fe_props not defined!");
+		return -EINVAL;
+	}
+
+	ret = pvr2_channel_limit_inputs(
+	    &adap->channel,
+	    (1 << PVR2_CVAL_INPUT_DTV));
+	if (ret) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "failed to grab control of dtv input (code=%d)",
+		    ret);
+		return ret;
+	}
+
+	if (dvb_props->frontend_attach == NULL) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "frontend_attach not defined!");
+		ret = -EINVAL;
+		goto done;
+	}
+
+	if ((dvb_props->frontend_attach(adap) == 0) && (adap->fe)) {
+
+		if (dvb_register_frontend(&adap->dvb_adap, adap->fe)) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "frontend registration failed!");
+			dvb_frontend_detach(adap->fe);
+			adap->fe = NULL;
+			ret = -ENODEV;
+			goto done;
+		}
+
+		if (dvb_props->tuner_attach)
+			dvb_props->tuner_attach(adap);
+
+		if (adap->fe->ops.analog_ops.standby)
+			adap->fe->ops.analog_ops.standby(adap->fe);
+
+		/* Ensure all frontends negotiate bus access */
+		adap->fe->ops.ts_bus_ctrl = pvr2_dvb_bus_ctrl;
+
+	} else {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "no frontend was attached!");
+		ret = -ENODEV;
+		return ret;
+	}
+
+ done:
+	pvr2_channel_limit_inputs(&adap->channel, 0);
+	return ret;
+}
+
+static int pvr2_dvb_frontend_exit(struct pvr2_dvb_adapter *adap)
+{
+	if (adap->fe != NULL) {
+		dvb_unregister_frontend(adap->fe);
+		dvb_frontend_detach(adap->fe);
+	}
+	return 0;
+}
+
+static void pvr2_dvb_destroy(struct pvr2_dvb_adapter *adap)
+{
+	pvr2_dvb_stream_end(adap);
+	pvr2_dvb_frontend_exit(adap);
+	pvr2_dvb_adapter_exit(adap);
+	pvr2_channel_done(&adap->channel);
+	kfree(adap);
+}
+
+static void pvr2_dvb_internal_check(struct pvr2_channel *chp)
+{
+	struct pvr2_dvb_adapter *adap;
+	adap = container_of(chp, struct pvr2_dvb_adapter, channel);
+	if (!adap->channel.mc_head->disconnect_flag) return;
+	pvr2_dvb_destroy(adap);
+}
+
+struct pvr2_dvb_adapter *pvr2_dvb_create(struct pvr2_context *pvr)
+{
+	int ret = 0;
+	struct pvr2_dvb_adapter *adap;
+	if (!pvr->hdw->hdw_desc->dvb_props) {
+		/* Device lacks a digital interface so don't set up
+		   the DVB side of the driver either.  For now. */
+		return NULL;
+	}
+	adap = kzalloc(sizeof(*adap), GFP_KERNEL);
+	if (!adap) return adap;
+	pvr2_channel_init(&adap->channel, pvr);
+	adap->channel.check_func = pvr2_dvb_internal_check;
+	init_waitqueue_head(&adap->buffer_wait_data);
+	mutex_init(&adap->lock);
+	ret = pvr2_dvb_adapter_init(adap);
+	if (ret < 0) goto fail1;
+	ret = pvr2_dvb_frontend_init(adap);
+	if (ret < 0) goto fail2;
+	return adap;
+
+fail2:
+	pvr2_dvb_adapter_exit(adap);
+fail1:
+	pvr2_channel_done(&adap->channel);
+	return NULL;
+}
+
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-dvb.h b/drivers/media/usb/pvrusb2/pvrusb2-dvb.h
new file mode 100644
index 0000000..e7f71fb
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-dvb.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __PVRUSB2_DVB_H__
+#define __PVRUSB2_DVB_H__
+
+#include <media/dvb_frontend.h>
+#include <media/dvb_demux.h>
+#include <media/dvb_net.h>
+#include <media/dmxdev.h>
+#include "pvrusb2-context.h"
+
+#define PVR2_DVB_BUFFER_COUNT 32
+#define PVR2_DVB_BUFFER_SIZE PAGE_ALIGN(0x4000)
+
+struct pvr2_dvb_adapter {
+	struct pvr2_channel	channel;
+
+	struct dvb_adapter	dvb_adap;
+	struct dmxdev		dmxdev;
+	struct dvb_demux	demux;
+	struct dvb_net		dvb_net;
+	struct dvb_frontend	*fe;
+
+	int			feedcount;
+	int			max_feed_count;
+
+	struct task_struct	*thread;
+	struct mutex		lock;
+
+	unsigned int		stream_run:1;
+
+	wait_queue_head_t	buffer_wait_data;
+	char			*buffer_storage[PVR2_DVB_BUFFER_COUNT];
+};
+
+struct pvr2_dvb_props {
+	int (*frontend_attach) (struct pvr2_dvb_adapter *);
+	int (*tuner_attach) (struct pvr2_dvb_adapter *);
+};
+
+struct pvr2_dvb_adapter *pvr2_dvb_create(struct pvr2_context *pvr);
+
+#endif /* __PVRUSB2_DVB_H__ */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-eeprom.c b/drivers/media/usb/pvrusb2/pvrusb2-eeprom.c
new file mode 100644
index 0000000..8b643d5
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-eeprom.c
@@ -0,0 +1,144 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#include <linux/slab.h>
+#include "pvrusb2-eeprom.h"
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-debug.h"
+
+#define trace_eeprom(...) pvr2_trace(PVR2_TRACE_EEPROM,__VA_ARGS__)
+
+
+
+/*
+
+   Read and analyze data in the eeprom.  Use tveeprom to figure out
+   the packet structure, since this is another Hauppauge device and
+   internally it has a family resemblance to ivtv-type devices
+
+*/
+
+#include <media/tveeprom.h>
+
+/* We seem to only be interested in the last 128 bytes of the EEPROM */
+#define EEPROM_SIZE 128
+
+/* Grab EEPROM contents, needed for direct method. */
+static u8 *pvr2_eeprom_fetch(struct pvr2_hdw *hdw)
+{
+	struct i2c_msg msg[2];
+	u8 *eeprom;
+	u8 iadd[2];
+	u8 addr;
+	u16 eepromSize;
+	unsigned int offs;
+	int ret;
+	int mode16 = 0;
+	unsigned pcnt,tcnt;
+	eeprom = kmalloc(EEPROM_SIZE,GFP_KERNEL);
+	if (!eeprom) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Failed to allocate memory required to read eeprom");
+		return NULL;
+	}
+
+	trace_eeprom("Value for eeprom addr from controller was 0x%x",
+		     hdw->eeprom_addr);
+	addr = hdw->eeprom_addr;
+	/* Seems that if the high bit is set, then the *real* eeprom
+	   address is shifted right now bit position (noticed this in
+	   newer PVR USB2 hardware) */
+	if (addr & 0x80) addr >>= 1;
+
+	/* FX2 documentation states that a 16bit-addressed eeprom is
+	   expected if the I2C address is an odd number (yeah, this is
+	   strange but it's what they do) */
+	mode16 = (addr & 1);
+	eepromSize = (mode16 ? 4096 : 256);
+	trace_eeprom("Examining %d byte eeprom at location 0x%x using %d bit addressing",
+		     eepromSize, addr,
+		     mode16 ? 16 : 8);
+
+	msg[0].addr = addr;
+	msg[0].flags = 0;
+	msg[0].len = mode16 ? 2 : 1;
+	msg[0].buf = iadd;
+	msg[1].addr = addr;
+	msg[1].flags = I2C_M_RD;
+
+	/* We have to do the actual eeprom data fetch ourselves, because
+	   (1) we're only fetching part of the eeprom, and (2) if we were
+	   getting the whole thing our I2C driver can't grab it in one
+	   pass - which is what tveeprom is otherwise going to attempt */
+	memset(eeprom,0,EEPROM_SIZE);
+	for (tcnt = 0; tcnt < EEPROM_SIZE; tcnt += pcnt) {
+		pcnt = 16;
+		if (pcnt + tcnt > EEPROM_SIZE) pcnt = EEPROM_SIZE-tcnt;
+		offs = tcnt + (eepromSize - EEPROM_SIZE);
+		if (mode16) {
+			iadd[0] = offs >> 8;
+			iadd[1] = offs;
+		} else {
+			iadd[0] = offs;
+		}
+		msg[1].len = pcnt;
+		msg[1].buf = eeprom+tcnt;
+		if ((ret = i2c_transfer(&hdw->i2c_adap,
+					msg,ARRAY_SIZE(msg))) != 2) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "eeprom fetch set offs err=%d",ret);
+			kfree(eeprom);
+			return NULL;
+		}
+	}
+	return eeprom;
+}
+
+
+/* Directly call eeprom analysis function within tveeprom. */
+int pvr2_eeprom_analyze(struct pvr2_hdw *hdw)
+{
+	u8 *eeprom;
+	struct tveeprom tvdata;
+
+	memset(&tvdata,0,sizeof(tvdata));
+
+	eeprom = pvr2_eeprom_fetch(hdw);
+	if (!eeprom)
+		return -EINVAL;
+
+	tveeprom_hauppauge_analog(&tvdata, eeprom);
+
+	trace_eeprom("eeprom assumed v4l tveeprom module");
+	trace_eeprom("eeprom direct call results:");
+	trace_eeprom("has_radio=%d",tvdata.has_radio);
+	trace_eeprom("tuner_type=%d",tvdata.tuner_type);
+	trace_eeprom("tuner_formats=0x%x",tvdata.tuner_formats);
+	trace_eeprom("audio_processor=%d",tvdata.audio_processor);
+	trace_eeprom("model=%d",tvdata.model);
+	trace_eeprom("revision=%d",tvdata.revision);
+	trace_eeprom("serial_number=%d",tvdata.serial_number);
+	trace_eeprom("rev_str=%s",tvdata.rev_str);
+	hdw->tuner_type = tvdata.tuner_type;
+	hdw->tuner_updated = !0;
+	hdw->serial_number = tvdata.serial_number;
+	hdw->std_mask_eeprom = tvdata.tuner_formats;
+
+	kfree(eeprom);
+
+	return 0;
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-eeprom.h b/drivers/media/usb/pvrusb2/pvrusb2-eeprom.h
new file mode 100644
index 0000000..1d81cac
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-eeprom.h
@@ -0,0 +1,25 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#ifndef __PVRUSB2_EEPROM_H
+#define __PVRUSB2_EEPROM_H
+
+struct pvr2_hdw;
+
+int pvr2_eeprom_analyze(struct pvr2_hdw *);
+
+#endif /* __PVRUSB2_EEPROM_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-encoder.c b/drivers/media/usb/pvrusb2/pvrusb2-encoder.c
new file mode 100644
index 0000000..43e4340
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-encoder.c
@@ -0,0 +1,523 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#include <linux/device.h>   // for linux/firmware.h
+#include <linux/firmware.h>
+#include "pvrusb2-util.h"
+#include "pvrusb2-encoder.h"
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-debug.h"
+#include "pvrusb2-fx2-cmd.h"
+
+
+
+/* Firmware mailbox flags - definitions found from ivtv */
+#define IVTV_MBOX_FIRMWARE_DONE 0x00000004
+#define IVTV_MBOX_DRIVER_DONE 0x00000002
+#define IVTV_MBOX_DRIVER_BUSY 0x00000001
+
+#define MBOX_BASE 0x44
+
+
+static int pvr2_encoder_write_words(struct pvr2_hdw *hdw,
+				    unsigned int offs,
+				    const u32 *data, unsigned int dlen)
+{
+	unsigned int idx,addr;
+	unsigned int bAddr;
+	int ret;
+	unsigned int chunkCnt;
+
+	/*
+
+	Format: First byte must be 0x01.  Remaining 32 bit words are
+	spread out into chunks of 7 bytes each, with the first 4 bytes
+	being the data word (little endian), and the next 3 bytes
+	being the address where that data word is to be written (big
+	endian).  Repeat request for additional words, with offset
+	adjusted accordingly.
+
+	*/
+	while (dlen) {
+		chunkCnt = 8;
+		if (chunkCnt > dlen) chunkCnt = dlen;
+		memset(hdw->cmd_buffer,0,sizeof(hdw->cmd_buffer));
+		bAddr = 0;
+		hdw->cmd_buffer[bAddr++] = FX2CMD_MEM_WRITE_DWORD;
+		for (idx = 0; idx < chunkCnt; idx++) {
+			addr = idx + offs;
+			hdw->cmd_buffer[bAddr+6] = (addr & 0xffu);
+			hdw->cmd_buffer[bAddr+5] = ((addr>>8) & 0xffu);
+			hdw->cmd_buffer[bAddr+4] = ((addr>>16) & 0xffu);
+			PVR2_DECOMPOSE_LE(hdw->cmd_buffer, bAddr,data[idx]);
+			bAddr += 7;
+		}
+		ret = pvr2_send_request(hdw,
+					hdw->cmd_buffer,1+(chunkCnt*7),
+					NULL,0);
+		if (ret) return ret;
+		data += chunkCnt;
+		dlen -= chunkCnt;
+		offs += chunkCnt;
+	}
+
+	return 0;
+}
+
+
+static int pvr2_encoder_read_words(struct pvr2_hdw *hdw,
+				   unsigned int offs,
+				   u32 *data, unsigned int dlen)
+{
+	unsigned int idx;
+	int ret;
+	unsigned int chunkCnt;
+
+	/*
+
+	Format: First byte must be 0x02 (status check) or 0x28 (read
+	back block of 32 bit words).  Next 6 bytes must be zero,
+	followed by a single byte of MBOX_BASE+offset for portion to
+	be read.  Returned data is packed set of 32 bits words that
+	were read.
+
+	*/
+
+	while (dlen) {
+		chunkCnt = 16;
+		if (chunkCnt > dlen) chunkCnt = dlen;
+		if (chunkCnt < 16) chunkCnt = 1;
+		hdw->cmd_buffer[0] =
+			((chunkCnt == 1) ?
+			 FX2CMD_MEM_READ_DWORD : FX2CMD_MEM_READ_64BYTES);
+		hdw->cmd_buffer[1] = 0;
+		hdw->cmd_buffer[2] = 0;
+		hdw->cmd_buffer[3] = 0;
+		hdw->cmd_buffer[4] = 0;
+		hdw->cmd_buffer[5] = ((offs>>16) & 0xffu);
+		hdw->cmd_buffer[6] = ((offs>>8) & 0xffu);
+		hdw->cmd_buffer[7] = (offs & 0xffu);
+		ret = pvr2_send_request(hdw,
+					hdw->cmd_buffer,8,
+					hdw->cmd_buffer,
+					(chunkCnt == 1 ? 4 : 16 * 4));
+		if (ret) return ret;
+
+		for (idx = 0; idx < chunkCnt; idx++) {
+			data[idx] = PVR2_COMPOSE_LE(hdw->cmd_buffer,idx*4);
+		}
+		data += chunkCnt;
+		dlen -= chunkCnt;
+		offs += chunkCnt;
+	}
+
+	return 0;
+}
+
+
+/* This prototype is set up to be compatible with the
+   cx2341x_mbox_func prototype in cx2341x.h, which should be in
+   kernels 2.6.18 or later.  We do this so that we can enable
+   cx2341x.ko to write to our encoder (by handing it a pointer to this
+   function).  For earlier kernels this doesn't really matter. */
+static int pvr2_encoder_cmd(void *ctxt,
+			    u32 cmd,
+			    int arg_cnt_send,
+			    int arg_cnt_recv,
+			    u32 *argp)
+{
+	unsigned int poll_count;
+	unsigned int try_count = 0;
+	int retry_flag;
+	int ret = 0;
+	unsigned int idx;
+	/* These sizes look to be limited by the FX2 firmware implementation */
+	u32 wrData[16];
+	u32 rdData[16];
+	struct pvr2_hdw *hdw = (struct pvr2_hdw *)ctxt;
+
+
+	/*
+
+	The encoder seems to speak entirely using blocks 32 bit words.
+	In ivtv driver terms, this is a mailbox at MBOX_BASE which we
+	populate with data and watch what the hardware does with it.
+	The first word is a set of flags used to control the
+	transaction, the second word is the command to execute, the
+	third byte is zero (ivtv driver suggests that this is some
+	kind of return value), and the fourth byte is a specified
+	timeout (windows driver always uses 0x00060000 except for one
+	case when it is zero).  All successive words are the argument
+	words for the command.
+
+	First, write out the entire set of words, with the first word
+	being zero.
+
+	Next, write out just the first word again, but set it to
+	IVTV_MBOX_DRIVER_DONE | IVTV_DRIVER_BUSY this time (which
+	probably means "go").
+
+	Next, read back the return count words.  Check the first word,
+	which should have IVTV_MBOX_FIRMWARE_DONE set.  If however
+	that bit is not set, then the command isn't done so repeat the
+	read until it is set.
+
+	Finally, write out just the first word again, but set it to
+	0x0 this time (which probably means "idle").
+
+	*/
+
+	if (arg_cnt_send > (ARRAY_SIZE(wrData) - 4)) {
+		pvr2_trace(
+			PVR2_TRACE_ERROR_LEGS,
+			"Failed to write cx23416 command - too many input arguments (was given %u limit %lu)",
+			arg_cnt_send, (long unsigned) ARRAY_SIZE(wrData) - 4);
+		return -EINVAL;
+	}
+
+	if (arg_cnt_recv > (ARRAY_SIZE(rdData) - 4)) {
+		pvr2_trace(
+			PVR2_TRACE_ERROR_LEGS,
+			"Failed to write cx23416 command - too many return arguments (was given %u limit %lu)",
+			arg_cnt_recv, (long unsigned) ARRAY_SIZE(rdData) - 4);
+		return -EINVAL;
+	}
+
+
+	LOCK_TAKE(hdw->ctl_lock); while (1) {
+
+		if (!hdw->state_encoder_ok) {
+			ret = -EIO;
+			break;
+		}
+
+		retry_flag = 0;
+		try_count++;
+		ret = 0;
+		wrData[0] = 0;
+		wrData[1] = cmd;
+		wrData[2] = 0;
+		wrData[3] = 0x00060000;
+		for (idx = 0; idx < arg_cnt_send; idx++) {
+			wrData[idx+4] = argp[idx];
+		}
+		for (; idx < ARRAY_SIZE(wrData) - 4; idx++) {
+			wrData[idx+4] = 0;
+		}
+
+		ret = pvr2_encoder_write_words(hdw,MBOX_BASE,wrData,idx);
+		if (ret) break;
+		wrData[0] = IVTV_MBOX_DRIVER_DONE|IVTV_MBOX_DRIVER_BUSY;
+		ret = pvr2_encoder_write_words(hdw,MBOX_BASE,wrData,1);
+		if (ret) break;
+		poll_count = 0;
+		while (1) {
+			poll_count++;
+			ret = pvr2_encoder_read_words(hdw,MBOX_BASE,rdData,
+						      arg_cnt_recv+4);
+			if (ret) {
+				break;
+			}
+			if (rdData[0] & IVTV_MBOX_FIRMWARE_DONE) {
+				break;
+			}
+			if (rdData[0] && (poll_count < 1000)) continue;
+			if (!rdData[0]) {
+				retry_flag = !0;
+				pvr2_trace(
+					PVR2_TRACE_ERROR_LEGS,
+					"Encoder timed out waiting for us; arranging to retry");
+			} else {
+				pvr2_trace(
+					PVR2_TRACE_ERROR_LEGS,
+					"***WARNING*** device's encoder appears to be stuck (status=0x%08x)",
+rdData[0]);
+			}
+			pvr2_trace(
+				PVR2_TRACE_ERROR_LEGS,
+				"Encoder command: 0x%02x",cmd);
+			for (idx = 4; idx < arg_cnt_send; idx++) {
+				pvr2_trace(
+					PVR2_TRACE_ERROR_LEGS,
+					"Encoder arg%d: 0x%08x",
+					idx-3,wrData[idx]);
+			}
+			ret = -EBUSY;
+			break;
+		}
+		if (retry_flag) {
+			if (try_count < 20) continue;
+			pvr2_trace(
+				PVR2_TRACE_ERROR_LEGS,
+				"Too many retries...");
+			ret = -EBUSY;
+		}
+		if (ret) {
+			del_timer_sync(&hdw->encoder_run_timer);
+			hdw->state_encoder_ok = 0;
+			pvr2_trace(PVR2_TRACE_STBITS,
+				   "State bit %s <-- %s",
+				   "state_encoder_ok",
+				   (hdw->state_encoder_ok ? "true" : "false"));
+			if (hdw->state_encoder_runok) {
+				hdw->state_encoder_runok = 0;
+				pvr2_trace(PVR2_TRACE_STBITS,
+				   "State bit %s <-- %s",
+					   "state_encoder_runok",
+					   (hdw->state_encoder_runok ?
+					    "true" : "false"));
+			}
+			pvr2_trace(
+				PVR2_TRACE_ERROR_LEGS,
+				"Giving up on command.  This is normally recovered via a firmware reload and re-initialization; concern is only warranted if this happens repeatedly and rapidly.");
+			break;
+		}
+		wrData[0] = 0x7;
+		for (idx = 0; idx < arg_cnt_recv; idx++) {
+			argp[idx] = rdData[idx+4];
+		}
+
+		wrData[0] = 0x0;
+		ret = pvr2_encoder_write_words(hdw,MBOX_BASE,wrData,1);
+		break;
+
+	}; LOCK_GIVE(hdw->ctl_lock);
+
+	return ret;
+}
+
+
+static int pvr2_encoder_vcmd(struct pvr2_hdw *hdw, int cmd,
+			     int args, ...)
+{
+	va_list vl;
+	unsigned int idx;
+	u32 data[12];
+
+	if (args > ARRAY_SIZE(data)) {
+		pvr2_trace(
+			PVR2_TRACE_ERROR_LEGS,
+			"Failed to write cx23416 command - too many arguments (was given %u limit %lu)",
+			args, (long unsigned) ARRAY_SIZE(data));
+		return -EINVAL;
+	}
+
+	va_start(vl, args);
+	for (idx = 0; idx < args; idx++) {
+		data[idx] = va_arg(vl, u32);
+	}
+	va_end(vl);
+
+	return pvr2_encoder_cmd(hdw,cmd,args,0,data);
+}
+
+
+/* This implements some extra setup for the encoder that seems to be
+   specific to the PVR USB2 hardware. */
+static int pvr2_encoder_prep_config(struct pvr2_hdw *hdw)
+{
+	int ret = 0;
+	int encMisc3Arg = 0;
+
+#if 0
+	/* This inexplicable bit happens in the Hauppauge windows
+	   driver (for both 24xxx and 29xxx devices).  However I
+	   currently see no difference in behavior with or without
+	   this stuff.  Leave this here as a note of its existence,
+	   but don't use it. */
+	LOCK_TAKE(hdw->ctl_lock); do {
+		u32 dat[1];
+		dat[0] = 0x80000640;
+		pvr2_encoder_write_words(hdw,0x01fe,dat,1);
+		pvr2_encoder_write_words(hdw,0x023e,dat,1);
+	} while(0); LOCK_GIVE(hdw->ctl_lock);
+#endif
+
+	/* Mike Isely <isely@pobox.com> 26-Jan-2006 The windows driver
+	   sends the following list of ENC_MISC commands (for both
+	   24xxx and 29xxx devices).  Meanings are not entirely clear,
+	   however without the ENC_MISC(3,1) command then we risk
+	   random perpetual video corruption whenever the video input
+	   breaks up for a moment (like when switching channels). */
+
+
+#if 0
+	/* This ENC_MISC(5,0) command seems to hurt 29xxx sync
+	   performance on channel changes, but is not a problem on
+	   24xxx devices. */
+	ret |= pvr2_encoder_vcmd(hdw, CX2341X_ENC_MISC,4, 5,0,0,0);
+#endif
+
+	/* This ENC_MISC(3,encMisc3Arg) command is critical - without
+	   it there will eventually be video corruption.  Also, the
+	   saa7115 case is strange - the Windows driver is passing 1
+	   regardless of device type but if we have 1 for saa7115
+	   devices the video turns sluggish.  */
+	if (hdw->hdw_desc->flag_has_cx25840) {
+		encMisc3Arg = 1;
+	} else {
+		encMisc3Arg = 0;
+	}
+	ret |= pvr2_encoder_vcmd(hdw, CX2341X_ENC_MISC,4, 3,
+				 encMisc3Arg,0,0);
+
+	ret |= pvr2_encoder_vcmd(hdw, CX2341X_ENC_MISC,4, 8,0,0,0);
+
+#if 0
+	/* This ENC_MISC(4,1) command is poisonous, so it is commented
+	   out.  But I'm leaving it here anyway to document its
+	   existence in the Windows driver.  The effect of this
+	   command is that apps displaying the stream become sluggish
+	   with stuttering video. */
+	ret |= pvr2_encoder_vcmd(hdw, CX2341X_ENC_MISC,4, 4,1,0,0);
+#endif
+
+	ret |= pvr2_encoder_vcmd(hdw, CX2341X_ENC_MISC,4, 0,3,0,0);
+	ret |= pvr2_encoder_vcmd(hdw, CX2341X_ENC_MISC,4,15,0,0,0);
+
+	/* prevent the PTSs from slowly drifting away in the generated
+	   MPEG stream */
+	ret |= pvr2_encoder_vcmd(hdw, CX2341X_ENC_MISC, 2, 4, 1);
+
+	return ret;
+}
+
+int pvr2_encoder_adjust(struct pvr2_hdw *hdw)
+{
+	int ret;
+	ret = cx2341x_update(hdw,pvr2_encoder_cmd,
+			     (hdw->enc_cur_valid ? &hdw->enc_cur_state : NULL),
+			     &hdw->enc_ctl_state);
+	if (ret) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Error from cx2341x module code=%d",ret);
+	} else {
+		hdw->enc_cur_state = hdw->enc_ctl_state;
+		hdw->enc_cur_valid = !0;
+	}
+	return ret;
+}
+
+
+int pvr2_encoder_configure(struct pvr2_hdw *hdw)
+{
+	int ret;
+	int val;
+	pvr2_trace(PVR2_TRACE_ENCODER, "pvr2_encoder_configure (cx2341x module)");
+	hdw->enc_ctl_state.port = CX2341X_PORT_STREAMING;
+	hdw->enc_ctl_state.width = hdw->res_hor_val;
+	hdw->enc_ctl_state.height = hdw->res_ver_val;
+	hdw->enc_ctl_state.is_50hz = ((hdw->std_mask_cur & V4L2_STD_525_60) ?
+				      0 : 1);
+
+	ret = 0;
+
+	ret |= pvr2_encoder_prep_config(hdw);
+
+	/* saa7115: 0xf0 */
+	val = 0xf0;
+	if (hdw->hdw_desc->flag_has_cx25840) {
+		/* ivtv cx25840: 0x140 */
+		val = 0x140;
+	}
+
+	if (!ret) ret = pvr2_encoder_vcmd(
+		hdw,CX2341X_ENC_SET_NUM_VSYNC_LINES, 2,
+		val, val);
+
+	/* setup firmware to notify us about some events (don't know why...) */
+	if (!ret) ret = pvr2_encoder_vcmd(
+		hdw,CX2341X_ENC_SET_EVENT_NOTIFICATION, 4,
+		0, 0, 0x10000000, 0xffffffff);
+
+	if (!ret) ret = pvr2_encoder_vcmd(
+		hdw,CX2341X_ENC_SET_VBI_LINE, 5,
+		0xffffffff,0,0,0,0);
+
+	if (ret) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Failed to configure cx23416");
+		return ret;
+	}
+
+	ret = pvr2_encoder_adjust(hdw);
+	if (ret) return ret;
+
+	ret = pvr2_encoder_vcmd(
+		hdw, CX2341X_ENC_INITIALIZE_INPUT, 0);
+
+	if (ret) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Failed to initialize cx23416 video input");
+		return ret;
+	}
+
+	return 0;
+}
+
+
+int pvr2_encoder_start(struct pvr2_hdw *hdw)
+{
+	int status;
+
+	/* unmask some interrupts */
+	pvr2_write_register(hdw, 0x0048, 0xbfffffff);
+
+	pvr2_encoder_vcmd(hdw,CX2341X_ENC_MUTE_VIDEO,1,
+			  hdw->input_val == PVR2_CVAL_INPUT_RADIO ? 1 : 0);
+
+	switch (hdw->active_stream_type) {
+	case pvr2_config_vbi:
+		status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_START_CAPTURE,2,
+					   0x01,0x14);
+		break;
+	case pvr2_config_mpeg:
+		status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_START_CAPTURE,2,
+					   0,0x13);
+		break;
+	default: /* Unhandled cases for now */
+		status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_START_CAPTURE,2,
+					   0,0x13);
+		break;
+	}
+	return status;
+}
+
+int pvr2_encoder_stop(struct pvr2_hdw *hdw)
+{
+	int status;
+
+	/* mask all interrupts */
+	pvr2_write_register(hdw, 0x0048, 0xffffffff);
+
+	switch (hdw->active_stream_type) {
+	case pvr2_config_vbi:
+		status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_STOP_CAPTURE,3,
+					   0x01,0x01,0x14);
+		break;
+	case pvr2_config_mpeg:
+		status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_STOP_CAPTURE,3,
+					   0x01,0,0x13);
+		break;
+	default: /* Unhandled cases for now */
+		status = pvr2_encoder_vcmd(hdw,CX2341X_ENC_STOP_CAPTURE,3,
+					   0x01,0,0x13);
+		break;
+	}
+
+	return status;
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-encoder.h b/drivers/media/usb/pvrusb2/pvrusb2-encoder.h
new file mode 100644
index 0000000..10d7f0b
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-encoder.h
@@ -0,0 +1,28 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#ifndef __PVRUSB2_ENCODER_H
+#define __PVRUSB2_ENCODER_H
+
+struct pvr2_hdw;
+
+int pvr2_encoder_adjust(struct pvr2_hdw *);
+int pvr2_encoder_configure(struct pvr2_hdw *);
+int pvr2_encoder_start(struct pvr2_hdw *);
+int pvr2_encoder_stop(struct pvr2_hdw *);
+
+#endif /* __PVRUSB2_ENCODER_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-fx2-cmd.h b/drivers/media/usb/pvrusb2/pvrusb2-fx2-cmd.h
new file mode 100644
index 0000000..0a01de4
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-fx2-cmd.h
@@ -0,0 +1,58 @@
+/*
+ *
+ *
+ *  Copyright (C) 2007 Michael Krufky <mkrufky@linuxtv.org>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#ifndef _PVRUSB2_FX2_CMD_H_
+#define _PVRUSB2_FX2_CMD_H_
+
+#define FX2CMD_MEM_WRITE_DWORD  0x01u
+#define FX2CMD_MEM_READ_DWORD   0x02u
+
+#define FX2CMD_HCW_ZILOG_RESET  0x10u /* 1=reset 0=release */
+
+#define FX2CMD_MEM_READ_64BYTES 0x28u
+
+#define FX2CMD_REG_WRITE        0x04u
+#define FX2CMD_REG_READ         0x05u
+#define FX2CMD_MEMSEL           0x06u
+
+#define FX2CMD_I2C_WRITE        0x08u
+#define FX2CMD_I2C_READ         0x09u
+
+#define FX2CMD_GET_USB_SPEED    0x0bu
+
+#define FX2CMD_STREAMING_ON     0x36u
+#define FX2CMD_STREAMING_OFF    0x37u
+
+#define FX2CMD_FWPOST1          0x52u
+
+#define FX2CMD_POWER_OFF        0xdcu
+#define FX2CMD_POWER_ON         0xdeu
+
+#define FX2CMD_DEEP_RESET       0xddu
+
+#define FX2CMD_GET_EEPROM_ADDR  0xebu
+#define FX2CMD_GET_IR_CODE      0xecu
+
+#define FX2CMD_HCW_DEMOD_RESETIN       0xf0u
+#define FX2CMD_HCW_DTV_STREAMING_ON    0xf1u
+#define FX2CMD_HCW_DTV_STREAMING_OFF   0xf2u
+
+#define FX2CMD_ONAIR_DTV_STREAMING_ON  0xa0u
+#define FX2CMD_ONAIR_DTV_STREAMING_OFF 0xa1u
+#define FX2CMD_ONAIR_DTV_POWER_ON      0xa2u
+#define FX2CMD_ONAIR_DTV_POWER_OFF     0xa3u
+
+#endif /* _PVRUSB2_FX2_CMD_H_ */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-hdw-internal.h b/drivers/media/usb/pvrusb2/pvrusb2-hdw-internal.h
new file mode 100644
index 0000000..7a82419
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-hdw-internal.h
@@ -0,0 +1,391 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+#ifndef __PVRUSB2_HDW_INTERNAL_H
+#define __PVRUSB2_HDW_INTERNAL_H
+
+/*
+
+  This header sets up all the internal structures and definitions needed to
+  track and coordinate the driver's interaction with the hardware.  ONLY
+  source files which actually implement part of that whole circus should be
+  including this header.  Higher levels, like the external layers to the
+  various public APIs (V4L, sysfs, etc) should NOT ever include this
+  private, internal header.  This means that pvrusb2-hdw, pvrusb2-encoder,
+  etc will include this, but pvrusb2-v4l should not.
+
+*/
+
+#include <linux/videodev2.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+#include <linux/mutex.h>
+#include "pvrusb2-hdw.h"
+#include "pvrusb2-io.h"
+#include <media/v4l2-device.h>
+#include <media/drv-intf/cx2341x.h>
+#include <media/i2c/ir-kbd-i2c.h>
+#include "pvrusb2-devattr.h"
+
+/* Legal values for PVR2_CID_HSM */
+#define PVR2_CVAL_HSM_FAIL 0
+#define PVR2_CVAL_HSM_FULL 1
+#define PVR2_CVAL_HSM_HIGH 2
+
+#define PVR2_VID_ENDPOINT        0x84
+#define PVR2_UNK_ENDPOINT        0x86    /* maybe raw yuv ? */
+#define PVR2_VBI_ENDPOINT        0x88
+
+#define PVR2_CTL_BUFFSIZE        64
+
+#define FREQTABLE_SIZE 500
+
+#define LOCK_TAKE(x) do { mutex_lock(&x##_mutex); x##_held = !0; } while (0)
+#define LOCK_GIVE(x) do { x##_held = 0; mutex_unlock(&x##_mutex); } while (0)
+
+typedef int (*pvr2_ctlf_is_dirty)(struct pvr2_ctrl *);
+typedef void (*pvr2_ctlf_clear_dirty)(struct pvr2_ctrl *);
+typedef int (*pvr2_ctlf_check_value)(struct pvr2_ctrl *,int);
+typedef int (*pvr2_ctlf_get_value)(struct pvr2_ctrl *,int *);
+typedef int (*pvr2_ctlf_set_value)(struct pvr2_ctrl *,int msk,int val);
+typedef int (*pvr2_ctlf_val_to_sym)(struct pvr2_ctrl *,int msk,int val,
+				    char *,unsigned int,unsigned int *);
+typedef int (*pvr2_ctlf_sym_to_val)(struct pvr2_ctrl *,
+				    const char *,unsigned int,
+				    int *mskp,int *valp);
+typedef unsigned int (*pvr2_ctlf_get_v4lflags)(struct pvr2_ctrl *);
+
+/* This structure describes a specific control.  A table of these is set up
+   in pvrusb2-hdw.c. */
+struct pvr2_ctl_info {
+	/* Control's name suitable for use as an identifier */
+	const char *name;
+
+	/* Short description of control */
+	const char *desc;
+
+	/* Control's implementation */
+	pvr2_ctlf_get_value get_value;      /* Get its value */
+	pvr2_ctlf_get_value get_def_value;  /* Get its default value */
+	pvr2_ctlf_get_value get_min_value;  /* Get minimum allowed value */
+	pvr2_ctlf_get_value get_max_value;  /* Get maximum allowed value */
+	pvr2_ctlf_set_value set_value;      /* Set its value */
+	pvr2_ctlf_check_value check_value;  /* Check that value is valid */
+	pvr2_ctlf_val_to_sym val_to_sym;    /* Custom convert value->symbol */
+	pvr2_ctlf_sym_to_val sym_to_val;    /* Custom convert symbol->value */
+	pvr2_ctlf_is_dirty is_dirty;        /* Return true if dirty */
+	pvr2_ctlf_clear_dirty clear_dirty;  /* Clear dirty state */
+	pvr2_ctlf_get_v4lflags get_v4lflags;/* Retrieve v4l flags */
+
+	/* Control's type (int, enum, bitmask) */
+	enum pvr2_ctl_type type;
+
+	/* Associated V4L control ID, if any */
+	int v4l_id;
+
+	/* Associated driver internal ID, if any */
+	int internal_id;
+
+	/* Don't implicitly initialize this control's value */
+	int skip_init;
+
+	/* Starting value for this control */
+	int default_value;
+
+	/* Type-specific control information */
+	union {
+		struct { /* Integer control */
+			long min_value; /* lower limit */
+			long max_value; /* upper limit */
+		} type_int;
+		struct { /* enumerated control */
+			unsigned int count;       /* enum value count */
+			const char * const *value_names; /* symbol names */
+		} type_enum;
+		struct { /* bitmask control */
+			unsigned int valid_bits; /* bits in use */
+			const char **bit_names;  /* symbol name/bit */
+		} type_bitmask;
+	} def;
+};
+
+
+/* Same as pvr2_ctl_info, but includes storage for the control description */
+#define PVR2_CTLD_INFO_DESC_SIZE 32
+struct pvr2_ctld_info {
+	struct pvr2_ctl_info info;
+	char desc[PVR2_CTLD_INFO_DESC_SIZE];
+};
+
+struct pvr2_ctrl {
+	const struct pvr2_ctl_info *info;
+	struct pvr2_hdw *hdw;
+};
+
+
+
+/* Disposition of firmware1 loading situation */
+#define FW1_STATE_UNKNOWN 0
+#define FW1_STATE_MISSING 1
+#define FW1_STATE_FAILED 2
+#define FW1_STATE_RELOAD 3
+#define FW1_STATE_OK 4
+
+/* What state the device is in if it is a hybrid */
+#define PVR2_PATHWAY_UNKNOWN 0
+#define PVR2_PATHWAY_ANALOG 1
+#define PVR2_PATHWAY_DIGITAL 2
+
+typedef int (*pvr2_i2c_func)(struct pvr2_hdw *,u8,u8 *,u16,u8 *, u16);
+#define PVR2_I2C_FUNC_CNT 128
+
+/* This structure contains all state data directly needed to
+   manipulate the hardware (as opposed to complying with a kernel
+   interface) */
+struct pvr2_hdw {
+	/* Underlying USB device handle */
+	struct usb_device *usb_dev;
+	struct usb_interface *usb_intf;
+
+	/* Our handle into the v4l2 sub-device architecture */
+	struct v4l2_device v4l2_dev;
+	/* Device description, anything that must adjust behavior based on
+	   device specific info will use information held here. */
+	const struct pvr2_device_desc *hdw_desc;
+
+	/* Kernel worker thread handling */
+	struct work_struct workpoll;     /* Update driver state */
+
+	/* Video spigot */
+	struct pvr2_stream *vid_stream;
+
+	/* Mutex for all hardware state control */
+	struct mutex big_lock_mutex;
+	int big_lock_held;  /* For debugging */
+
+	/* This is a simple string which identifies the instance of this
+	   driver.  It is unique within the set of existing devices, but
+	   there is no attempt to keep the name consistent with the same
+	   physical device each time. */
+	char name[32];
+
+	/* This is a simple string which identifies the physical device
+	   instance itself - if possible.  (If not possible, then it is
+	   based on the specific driver instance, similar to name above.)
+	   The idea here is that userspace might hopefully be able to use
+	   this recognize specific tuners.  It will encode a serial number,
+	   if available. */
+	char identifier[32];
+
+	/* I2C stuff */
+	struct i2c_adapter i2c_adap;
+	struct i2c_algorithm i2c_algo;
+	pvr2_i2c_func i2c_func[PVR2_I2C_FUNC_CNT];
+	int i2c_cx25840_hack_state;
+	int i2c_linked;
+
+	/* IR related */
+	unsigned int ir_scheme_active; /* IR scheme as seen from the outside */
+	struct IR_i2c_init_data ir_init_data; /* params passed to IR modules */
+
+	/* Frequency table */
+	unsigned int freqTable[FREQTABLE_SIZE];
+	unsigned int freqProgSlot;
+
+	/* Stuff for handling low level control interaction with device */
+	struct mutex ctl_lock_mutex;
+	int ctl_lock_held;  /* For debugging */
+	struct urb *ctl_write_urb;
+	struct urb *ctl_read_urb;
+	unsigned char *ctl_write_buffer;
+	unsigned char *ctl_read_buffer;
+	int ctl_write_pend_flag;
+	int ctl_read_pend_flag;
+	int ctl_timeout_flag;
+	struct completion ctl_done;
+	unsigned char cmd_buffer[PVR2_CTL_BUFFSIZE];
+	int cmd_debug_state;               // Low level command debugging info
+	unsigned char cmd_debug_code;      //
+	unsigned int cmd_debug_write_len;  //
+	unsigned int cmd_debug_read_len;   //
+
+	/* Bits of state that describe what is going on with various parts
+	   of the driver. */
+	int state_pathway_ok;         /* Pathway config is ok */
+	int state_encoder_ok;         /* Encoder is operational */
+	int state_encoder_run;        /* Encoder is running */
+	int state_encoder_config;     /* Encoder is configured */
+	int state_encoder_waitok;     /* Encoder pre-wait done */
+	int state_encoder_runok;      /* Encoder has run for >= .25 sec */
+	int state_decoder_run;        /* Decoder is running */
+	int state_decoder_ready;      /* Decoder is stabilized & streamable */
+	int state_usbstream_run;      /* FX2 is streaming */
+	int state_decoder_quiescent;  /* Decoder idle for minimal interval */
+	int state_pipeline_config;    /* Pipeline is configured */
+	int state_pipeline_req;       /* Somebody wants to stream */
+	int state_pipeline_pause;     /* Pipeline must be paused */
+	int state_pipeline_idle;      /* Pipeline not running */
+
+	/* This is the master state of the driver.  It is the combined
+	   result of other bits of state.  Examining this will indicate the
+	   overall state of the driver.  Values here are one of
+	   PVR2_STATE_xxxx */
+	unsigned int master_state;
+
+	/* True if device led is currently on */
+	int led_on;
+
+	/* True if states must be re-evaluated */
+	int state_stale;
+
+	void (*state_func)(void *);
+	void *state_data;
+
+	/* Timer for measuring required decoder settling time before we're
+	   allowed to fire it up again. */
+	struct timer_list quiescent_timer;
+
+	/* Timer for measuring decoder stabilization time, which is the
+	   amount of time we need to let the decoder run before we can
+	   trust its output (otherwise the encoder might see garbage and
+	   then fail to start correctly). */
+	struct timer_list decoder_stabilization_timer;
+
+	/* Timer for measuring encoder pre-wait time */
+	struct timer_list encoder_wait_timer;
+
+	/* Timer for measuring encoder minimum run time */
+	struct timer_list encoder_run_timer;
+
+	/* Place to block while waiting for state changes */
+	wait_queue_head_t state_wait_data;
+
+
+	int force_dirty;        /* consider all controls dirty if true */
+	int flag_ok;            /* device in known good state */
+	int flag_modulefail;    /* true if at least one module failed to load */
+	int flag_disconnected;  /* flag_ok == 0 due to disconnect */
+	int flag_init_ok;       /* true if structure is fully initialized */
+	int fw1_state;          /* current situation with fw1 */
+	int pathway_state;      /* one of PVR2_PATHWAY_xxx */
+	int flag_decoder_missed;/* We've noticed missing decoder */
+	int flag_tripped;       /* Indicates overall failure to start */
+
+	unsigned int decoder_client_id;
+
+	// CPU firmware info (used to help find / save firmware data)
+	char *fw_buffer;
+	unsigned int fw_size;
+	int fw_cpu_flag; /* True if we are dealing with the CPU */
+
+	/* Tuner / frequency control stuff */
+	unsigned int tuner_type;
+	int tuner_updated;
+	unsigned int freqValTelevision;  /* Current freq for tv mode */
+	unsigned int freqValRadio;       /* Current freq for radio mode */
+	unsigned int freqSlotTelevision; /* Current slot for tv mode */
+	unsigned int freqSlotRadio;      /* Current slot for radio mode */
+	unsigned int freqSelector;       /* 0=radio 1=television */
+	int freqDirty;
+
+	/* Current tuner info - this information is polled from the I2C bus */
+	struct v4l2_tuner tuner_signal_info;
+	int tuner_signal_stale;
+
+	/* Cropping capability info */
+	struct v4l2_cropcap cropcap_info;
+	int cropcap_stale;
+
+	/* Video standard handling */
+	v4l2_std_id std_mask_eeprom; // Hardware supported selections
+	v4l2_std_id std_mask_avail;  // Which standards we may select from
+	v4l2_std_id std_mask_cur;    // Currently selected standard(s)
+	int std_enum_cur;            // selected standard enumeration value
+	int std_dirty;               // True if std_mask_cur has changed
+	struct pvr2_ctl_info std_info_enum;
+	struct pvr2_ctl_info std_info_avail;
+	struct pvr2_ctl_info std_info_cur;
+	struct pvr2_ctl_info std_info_detect;
+
+	// Generated string names, one per actual V4L2 standard
+	const char *std_mask_ptrs[32];
+	char std_mask_names[32][16];
+
+	int unit_number;             /* ID for driver instance */
+	unsigned long serial_number; /* ID for hardware itself */
+
+	char bus_info[32]; /* Bus location info */
+
+	/* Minor numbers used by v4l logic (yes, this is a hack, as there
+	   should be no v4l junk here).  Probably a better way to do this. */
+	int v4l_minor_number_video;
+	int v4l_minor_number_vbi;
+	int v4l_minor_number_radio;
+
+	/* Bit mask of PVR2_CVAL_INPUT choices which are valid for the hardware */
+	unsigned int input_avail_mask;
+	/* Bit mask of PVR2_CVAL_INPUT choices which are currently allowed */
+	unsigned int input_allowed_mask;
+
+	/* Location of eeprom or a negative number if none */
+	int eeprom_addr;
+
+	enum pvr2_config active_stream_type;
+	enum pvr2_config desired_stream_type;
+
+	/* Control state needed for cx2341x module */
+	struct cx2341x_mpeg_params enc_cur_state;
+	struct cx2341x_mpeg_params enc_ctl_state;
+	/* True if an encoder attribute has changed */
+	int enc_stale;
+	/* True if an unsafe encoder attribute has changed */
+	int enc_unsafe_stale;
+	/* True if enc_cur_state is valid */
+	int enc_cur_valid;
+
+	/* Control state */
+#define VCREATE_DATA(lab) int lab##_val; int lab##_dirty
+	VCREATE_DATA(brightness);
+	VCREATE_DATA(contrast);
+	VCREATE_DATA(saturation);
+	VCREATE_DATA(hue);
+	VCREATE_DATA(volume);
+	VCREATE_DATA(balance);
+	VCREATE_DATA(bass);
+	VCREATE_DATA(treble);
+	VCREATE_DATA(mute);
+	VCREATE_DATA(cropl);
+	VCREATE_DATA(cropt);
+	VCREATE_DATA(cropw);
+	VCREATE_DATA(croph);
+	VCREATE_DATA(input);
+	VCREATE_DATA(audiomode);
+	VCREATE_DATA(res_hor);
+	VCREATE_DATA(res_ver);
+	VCREATE_DATA(srate);
+#undef VCREATE_DATA
+
+	struct pvr2_ctld_info *mpeg_ctrl_info;
+
+	struct pvr2_ctrl *controls;
+	unsigned int control_cnt;
+};
+
+/* This function gets the current frequency */
+unsigned long pvr2_hdw_get_cur_freq(struct pvr2_hdw *);
+
+void pvr2_hdw_status_poll(struct pvr2_hdw *);
+
+#endif /* __PVRUSB2_HDW_INTERNAL_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-hdw.c b/drivers/media/usb/pvrusb2/pvrusb2-hdw.c
new file mode 100644
index 0000000..a8519da
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-hdw.c
@@ -0,0 +1,5114 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <media/tuner.h>
+#include "pvrusb2.h"
+#include "pvrusb2-std.h"
+#include "pvrusb2-util.h"
+#include "pvrusb2-hdw.h"
+#include "pvrusb2-i2c-core.h"
+#include "pvrusb2-eeprom.h"
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-encoder.h"
+#include "pvrusb2-debug.h"
+#include "pvrusb2-fx2-cmd.h"
+#include "pvrusb2-wm8775.h"
+#include "pvrusb2-video-v4l.h"
+#include "pvrusb2-cx2584x-v4l.h"
+#include "pvrusb2-cs53l32a.h"
+#include "pvrusb2-audio.h"
+
+#define TV_MIN_FREQ     55250000L
+#define TV_MAX_FREQ    850000000L
+
+/* This defines a minimum interval that the decoder must remain quiet
+   before we are allowed to start it running. */
+#define TIME_MSEC_DECODER_WAIT 50
+
+/* This defines a minimum interval that the decoder must be allowed to run
+   before we can safely begin using its streaming output. */
+#define TIME_MSEC_DECODER_STABILIZATION_WAIT 300
+
+/* This defines a minimum interval that the encoder must remain quiet
+   before we are allowed to configure it. */
+#define TIME_MSEC_ENCODER_WAIT 50
+
+/* This defines the minimum interval that the encoder must successfully run
+   before we consider that the encoder has run at least once since its
+   firmware has been loaded.  This measurement is in important for cases
+   where we can't do something until we know that the encoder has been run
+   at least once. */
+#define TIME_MSEC_ENCODER_OK 250
+
+static struct pvr2_hdw *unit_pointers[PVR_NUM] = {[ 0 ... PVR_NUM-1 ] = NULL};
+static DEFINE_MUTEX(pvr2_unit_mtx);
+
+static int ctlchg;
+static int procreload;
+static int tuner[PVR_NUM] = { [0 ... PVR_NUM-1] = -1 };
+static int tolerance[PVR_NUM] = { [0 ... PVR_NUM-1] = 0 };
+static int video_std[PVR_NUM] = { [0 ... PVR_NUM-1] = 0 };
+static int init_pause_msec;
+
+module_param(ctlchg, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(ctlchg, "0=optimize ctl change 1=always accept new ctl value");
+module_param(init_pause_msec, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(init_pause_msec, "hardware initialization settling delay");
+module_param(procreload, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(procreload,
+		 "Attempt init failure recovery with firmware reload");
+module_param_array(tuner,    int, NULL, 0444);
+MODULE_PARM_DESC(tuner,"specify installed tuner type");
+module_param_array(video_std,    int, NULL, 0444);
+MODULE_PARM_DESC(video_std,"specify initial video standard");
+module_param_array(tolerance,    int, NULL, 0444);
+MODULE_PARM_DESC(tolerance,"specify stream error tolerance");
+
+/* US Broadcast channel 3 (61.25 MHz), to help with testing */
+static int default_tv_freq    = 61250000L;
+/* 104.3 MHz, a usable FM station for my area */
+static int default_radio_freq = 104300000L;
+
+module_param_named(tv_freq, default_tv_freq, int, 0444);
+MODULE_PARM_DESC(tv_freq, "specify initial television frequency");
+module_param_named(radio_freq, default_radio_freq, int, 0444);
+MODULE_PARM_DESC(radio_freq, "specify initial radio frequency");
+
+#define PVR2_CTL_WRITE_ENDPOINT  0x01
+#define PVR2_CTL_READ_ENDPOINT   0x81
+
+#define PVR2_GPIO_IN 0x9008
+#define PVR2_GPIO_OUT 0x900c
+#define PVR2_GPIO_DIR 0x9020
+
+#define trace_firmware(...) pvr2_trace(PVR2_TRACE_FIRMWARE,__VA_ARGS__)
+
+#define PVR2_FIRMWARE_ENDPOINT   0x02
+
+/* size of a firmware chunk */
+#define FIRMWARE_CHUNK_SIZE 0x2000
+
+typedef void (*pvr2_subdev_update_func)(struct pvr2_hdw *,
+					struct v4l2_subdev *);
+
+static const pvr2_subdev_update_func pvr2_module_update_functions[] = {
+	[PVR2_CLIENT_ID_WM8775] = pvr2_wm8775_subdev_update,
+	[PVR2_CLIENT_ID_SAA7115] = pvr2_saa7115_subdev_update,
+	[PVR2_CLIENT_ID_MSP3400] = pvr2_msp3400_subdev_update,
+	[PVR2_CLIENT_ID_CX25840] = pvr2_cx25840_subdev_update,
+	[PVR2_CLIENT_ID_CS53L32A] = pvr2_cs53l32a_subdev_update,
+};
+
+static const char *module_names[] = {
+	[PVR2_CLIENT_ID_MSP3400] = "msp3400",
+	[PVR2_CLIENT_ID_CX25840] = "cx25840",
+	[PVR2_CLIENT_ID_SAA7115] = "saa7115",
+	[PVR2_CLIENT_ID_TUNER] = "tuner",
+	[PVR2_CLIENT_ID_DEMOD] = "tuner",
+	[PVR2_CLIENT_ID_CS53L32A] = "cs53l32a",
+	[PVR2_CLIENT_ID_WM8775] = "wm8775",
+};
+
+
+static const unsigned char *module_i2c_addresses[] = {
+	[PVR2_CLIENT_ID_TUNER] = "\x60\x61\x62\x63",
+	[PVR2_CLIENT_ID_DEMOD] = "\x43",
+	[PVR2_CLIENT_ID_MSP3400] = "\x40",
+	[PVR2_CLIENT_ID_SAA7115] = "\x21",
+	[PVR2_CLIENT_ID_WM8775] = "\x1b",
+	[PVR2_CLIENT_ID_CX25840] = "\x44",
+	[PVR2_CLIENT_ID_CS53L32A] = "\x11",
+};
+
+
+static const char *ir_scheme_names[] = {
+	[PVR2_IR_SCHEME_NONE] = "none",
+	[PVR2_IR_SCHEME_29XXX] = "29xxx",
+	[PVR2_IR_SCHEME_24XXX] = "24xxx (29xxx emulation)",
+	[PVR2_IR_SCHEME_24XXX_MCE] = "24xxx (MCE device)",
+	[PVR2_IR_SCHEME_ZILOG] = "Zilog",
+};
+
+
+/* Define the list of additional controls we'll dynamically construct based
+   on query of the cx2341x module. */
+struct pvr2_mpeg_ids {
+	const char *strid;
+	int id;
+};
+static const struct pvr2_mpeg_ids mpeg_ids[] = {
+	{
+		.strid = "audio_layer",
+		.id = V4L2_CID_MPEG_AUDIO_ENCODING,
+	},{
+		.strid = "audio_bitrate",
+		.id = V4L2_CID_MPEG_AUDIO_L2_BITRATE,
+	},{
+		/* Already using audio_mode elsewhere :-( */
+		.strid = "mpeg_audio_mode",
+		.id = V4L2_CID_MPEG_AUDIO_MODE,
+	},{
+		.strid = "mpeg_audio_mode_extension",
+		.id = V4L2_CID_MPEG_AUDIO_MODE_EXTENSION,
+	},{
+		.strid = "audio_emphasis",
+		.id = V4L2_CID_MPEG_AUDIO_EMPHASIS,
+	},{
+		.strid = "audio_crc",
+		.id = V4L2_CID_MPEG_AUDIO_CRC,
+	},{
+		.strid = "video_aspect",
+		.id = V4L2_CID_MPEG_VIDEO_ASPECT,
+	},{
+		.strid = "video_b_frames",
+		.id = V4L2_CID_MPEG_VIDEO_B_FRAMES,
+	},{
+		.strid = "video_gop_size",
+		.id = V4L2_CID_MPEG_VIDEO_GOP_SIZE,
+	},{
+		.strid = "video_gop_closure",
+		.id = V4L2_CID_MPEG_VIDEO_GOP_CLOSURE,
+	},{
+		.strid = "video_bitrate_mode",
+		.id = V4L2_CID_MPEG_VIDEO_BITRATE_MODE,
+	},{
+		.strid = "video_bitrate",
+		.id = V4L2_CID_MPEG_VIDEO_BITRATE,
+	},{
+		.strid = "video_bitrate_peak",
+		.id = V4L2_CID_MPEG_VIDEO_BITRATE_PEAK,
+	},{
+		.strid = "video_temporal_decimation",
+		.id = V4L2_CID_MPEG_VIDEO_TEMPORAL_DECIMATION,
+	},{
+		.strid = "stream_type",
+		.id = V4L2_CID_MPEG_STREAM_TYPE,
+	},{
+		.strid = "video_spatial_filter_mode",
+		.id = V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER_MODE,
+	},{
+		.strid = "video_spatial_filter",
+		.id = V4L2_CID_MPEG_CX2341X_VIDEO_SPATIAL_FILTER,
+	},{
+		.strid = "video_luma_spatial_filter_type",
+		.id = V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_SPATIAL_FILTER_TYPE,
+	},{
+		.strid = "video_chroma_spatial_filter_type",
+		.id = V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_SPATIAL_FILTER_TYPE,
+	},{
+		.strid = "video_temporal_filter_mode",
+		.id = V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER_MODE,
+	},{
+		.strid = "video_temporal_filter",
+		.id = V4L2_CID_MPEG_CX2341X_VIDEO_TEMPORAL_FILTER,
+	},{
+		.strid = "video_median_filter_type",
+		.id = V4L2_CID_MPEG_CX2341X_VIDEO_MEDIAN_FILTER_TYPE,
+	},{
+		.strid = "video_luma_median_filter_top",
+		.id = V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_TOP,
+	},{
+		.strid = "video_luma_median_filter_bottom",
+		.id = V4L2_CID_MPEG_CX2341X_VIDEO_LUMA_MEDIAN_FILTER_BOTTOM,
+	},{
+		.strid = "video_chroma_median_filter_top",
+		.id = V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_TOP,
+	},{
+		.strid = "video_chroma_median_filter_bottom",
+		.id = V4L2_CID_MPEG_CX2341X_VIDEO_CHROMA_MEDIAN_FILTER_BOTTOM,
+	}
+};
+#define MPEGDEF_COUNT ARRAY_SIZE(mpeg_ids)
+
+
+static const char *control_values_srate[] = {
+	[V4L2_MPEG_AUDIO_SAMPLING_FREQ_44100]   = "44.1 kHz",
+	[V4L2_MPEG_AUDIO_SAMPLING_FREQ_48000]   = "48 kHz",
+	[V4L2_MPEG_AUDIO_SAMPLING_FREQ_32000]   = "32 kHz",
+};
+
+
+
+static const char *control_values_input[] = {
+	[PVR2_CVAL_INPUT_TV]        = "television",  /*xawtv needs this name*/
+	[PVR2_CVAL_INPUT_DTV]       = "dtv",
+	[PVR2_CVAL_INPUT_RADIO]     = "radio",
+	[PVR2_CVAL_INPUT_SVIDEO]    = "s-video",
+	[PVR2_CVAL_INPUT_COMPOSITE] = "composite",
+};
+
+
+static const char *control_values_audiomode[] = {
+	[V4L2_TUNER_MODE_MONO]   = "Mono",
+	[V4L2_TUNER_MODE_STEREO] = "Stereo",
+	[V4L2_TUNER_MODE_LANG1]  = "Lang1",
+	[V4L2_TUNER_MODE_LANG2]  = "Lang2",
+	[V4L2_TUNER_MODE_LANG1_LANG2] = "Lang1+Lang2",
+};
+
+
+static const char *control_values_hsm[] = {
+	[PVR2_CVAL_HSM_FAIL] = "Fail",
+	[PVR2_CVAL_HSM_HIGH] = "High",
+	[PVR2_CVAL_HSM_FULL] = "Full",
+};
+
+
+static const char *pvr2_state_names[] = {
+	[PVR2_STATE_NONE] =    "none",
+	[PVR2_STATE_DEAD] =    "dead",
+	[PVR2_STATE_COLD] =    "cold",
+	[PVR2_STATE_WARM] =    "warm",
+	[PVR2_STATE_ERROR] =   "error",
+	[PVR2_STATE_READY] =   "ready",
+	[PVR2_STATE_RUN] =     "run",
+};
+
+
+struct pvr2_fx2cmd_descdef {
+	unsigned char id;
+	unsigned char *desc;
+};
+
+static const struct pvr2_fx2cmd_descdef pvr2_fx2cmd_desc[] = {
+	{FX2CMD_MEM_WRITE_DWORD, "write encoder dword"},
+	{FX2CMD_MEM_READ_DWORD, "read encoder dword"},
+	{FX2CMD_HCW_ZILOG_RESET, "zilog IR reset control"},
+	{FX2CMD_MEM_READ_64BYTES, "read encoder 64bytes"},
+	{FX2CMD_REG_WRITE, "write encoder register"},
+	{FX2CMD_REG_READ, "read encoder register"},
+	{FX2CMD_MEMSEL, "encoder memsel"},
+	{FX2CMD_I2C_WRITE, "i2c write"},
+	{FX2CMD_I2C_READ, "i2c read"},
+	{FX2CMD_GET_USB_SPEED, "get USB speed"},
+	{FX2CMD_STREAMING_ON, "stream on"},
+	{FX2CMD_STREAMING_OFF, "stream off"},
+	{FX2CMD_FWPOST1, "fwpost1"},
+	{FX2CMD_POWER_OFF, "power off"},
+	{FX2CMD_POWER_ON, "power on"},
+	{FX2CMD_DEEP_RESET, "deep reset"},
+	{FX2CMD_GET_EEPROM_ADDR, "get rom addr"},
+	{FX2CMD_GET_IR_CODE, "get IR code"},
+	{FX2CMD_HCW_DEMOD_RESETIN, "hcw demod resetin"},
+	{FX2CMD_HCW_DTV_STREAMING_ON, "hcw dtv stream on"},
+	{FX2CMD_HCW_DTV_STREAMING_OFF, "hcw dtv stream off"},
+	{FX2CMD_ONAIR_DTV_STREAMING_ON, "onair dtv stream on"},
+	{FX2CMD_ONAIR_DTV_STREAMING_OFF, "onair dtv stream off"},
+	{FX2CMD_ONAIR_DTV_POWER_ON, "onair dtv power on"},
+	{FX2CMD_ONAIR_DTV_POWER_OFF, "onair dtv power off"},
+};
+
+
+static int pvr2_hdw_set_input(struct pvr2_hdw *hdw,int v);
+static void pvr2_hdw_state_sched(struct pvr2_hdw *);
+static int pvr2_hdw_state_eval(struct pvr2_hdw *);
+static void pvr2_hdw_set_cur_freq(struct pvr2_hdw *,unsigned long);
+static void pvr2_hdw_worker_poll(struct work_struct *work);
+static int pvr2_hdw_wait(struct pvr2_hdw *,int state);
+static int pvr2_hdw_untrip_unlocked(struct pvr2_hdw *);
+static void pvr2_hdw_state_log_state(struct pvr2_hdw *);
+static int pvr2_hdw_cmd_usbstream(struct pvr2_hdw *hdw,int runFl);
+static int pvr2_hdw_commit_setup(struct pvr2_hdw *hdw);
+static int pvr2_hdw_get_eeprom_addr(struct pvr2_hdw *hdw);
+static void pvr2_hdw_quiescent_timeout(struct timer_list *);
+static void pvr2_hdw_decoder_stabilization_timeout(struct timer_list *);
+static void pvr2_hdw_encoder_wait_timeout(struct timer_list *);
+static void pvr2_hdw_encoder_run_timeout(struct timer_list *);
+static int pvr2_issue_simple_cmd(struct pvr2_hdw *,u32);
+static int pvr2_send_request_ex(struct pvr2_hdw *hdw,
+				unsigned int timeout,int probe_fl,
+				void *write_data,unsigned int write_len,
+				void *read_data,unsigned int read_len);
+static int pvr2_hdw_check_cropcap(struct pvr2_hdw *hdw);
+static v4l2_std_id pvr2_hdw_get_detected_std(struct pvr2_hdw *hdw);
+
+static void trace_stbit(const char *name,int val)
+{
+	pvr2_trace(PVR2_TRACE_STBITS,
+		   "State bit %s <-- %s",
+		   name,(val ? "true" : "false"));
+}
+
+static int ctrl_channelfreq_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	struct pvr2_hdw *hdw = cptr->hdw;
+	if ((hdw->freqProgSlot > 0) && (hdw->freqProgSlot <= FREQTABLE_SIZE)) {
+		*vp = hdw->freqTable[hdw->freqProgSlot-1];
+	} else {
+		*vp = 0;
+	}
+	return 0;
+}
+
+static int ctrl_channelfreq_set(struct pvr2_ctrl *cptr,int m,int v)
+{
+	struct pvr2_hdw *hdw = cptr->hdw;
+	unsigned int slotId = hdw->freqProgSlot;
+	if ((slotId > 0) && (slotId <= FREQTABLE_SIZE)) {
+		hdw->freqTable[slotId-1] = v;
+		/* Handle side effects correctly - if we're tuned to this
+		   slot, then forgot the slot id relation since the stored
+		   frequency has been changed. */
+		if (hdw->freqSelector) {
+			if (hdw->freqSlotRadio == slotId) {
+				hdw->freqSlotRadio = 0;
+			}
+		} else {
+			if (hdw->freqSlotTelevision == slotId) {
+				hdw->freqSlotTelevision = 0;
+			}
+		}
+	}
+	return 0;
+}
+
+static int ctrl_channelprog_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	*vp = cptr->hdw->freqProgSlot;
+	return 0;
+}
+
+static int ctrl_channelprog_set(struct pvr2_ctrl *cptr,int m,int v)
+{
+	struct pvr2_hdw *hdw = cptr->hdw;
+	if ((v >= 0) && (v <= FREQTABLE_SIZE)) {
+		hdw->freqProgSlot = v;
+	}
+	return 0;
+}
+
+static int ctrl_channel_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	struct pvr2_hdw *hdw = cptr->hdw;
+	*vp = hdw->freqSelector ? hdw->freqSlotRadio : hdw->freqSlotTelevision;
+	return 0;
+}
+
+static int ctrl_channel_set(struct pvr2_ctrl *cptr,int m,int slotId)
+{
+	unsigned freq = 0;
+	struct pvr2_hdw *hdw = cptr->hdw;
+	if ((slotId < 0) || (slotId > FREQTABLE_SIZE)) return 0;
+	if (slotId > 0) {
+		freq = hdw->freqTable[slotId-1];
+		if (!freq) return 0;
+		pvr2_hdw_set_cur_freq(hdw,freq);
+	}
+	if (hdw->freqSelector) {
+		hdw->freqSlotRadio = slotId;
+	} else {
+		hdw->freqSlotTelevision = slotId;
+	}
+	return 0;
+}
+
+static int ctrl_freq_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	*vp = pvr2_hdw_get_cur_freq(cptr->hdw);
+	return 0;
+}
+
+static int ctrl_freq_is_dirty(struct pvr2_ctrl *cptr)
+{
+	return cptr->hdw->freqDirty != 0;
+}
+
+static void ctrl_freq_clear_dirty(struct pvr2_ctrl *cptr)
+{
+	cptr->hdw->freqDirty = 0;
+}
+
+static int ctrl_freq_set(struct pvr2_ctrl *cptr,int m,int v)
+{
+	pvr2_hdw_set_cur_freq(cptr->hdw,v);
+	return 0;
+}
+
+static int ctrl_cropl_min_get(struct pvr2_ctrl *cptr, int *left)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*left = cap->bounds.left;
+	return 0;
+}
+
+static int ctrl_cropl_max_get(struct pvr2_ctrl *cptr, int *left)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*left = cap->bounds.left;
+	if (cap->bounds.width > cptr->hdw->cropw_val) {
+		*left += cap->bounds.width - cptr->hdw->cropw_val;
+	}
+	return 0;
+}
+
+static int ctrl_cropt_min_get(struct pvr2_ctrl *cptr, int *top)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*top = cap->bounds.top;
+	return 0;
+}
+
+static int ctrl_cropt_max_get(struct pvr2_ctrl *cptr, int *top)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*top = cap->bounds.top;
+	if (cap->bounds.height > cptr->hdw->croph_val) {
+		*top += cap->bounds.height - cptr->hdw->croph_val;
+	}
+	return 0;
+}
+
+static int ctrl_cropw_max_get(struct pvr2_ctrl *cptr, int *width)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat, bleftend, cleft;
+
+	stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	bleftend = cap->bounds.left+cap->bounds.width;
+	cleft = cptr->hdw->cropl_val;
+
+	*width = cleft < bleftend ? bleftend-cleft : 0;
+	return 0;
+}
+
+static int ctrl_croph_max_get(struct pvr2_ctrl *cptr, int *height)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat, btopend, ctop;
+
+	stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	btopend = cap->bounds.top+cap->bounds.height;
+	ctop = cptr->hdw->cropt_val;
+
+	*height = ctop < btopend ? btopend-ctop : 0;
+	return 0;
+}
+
+static int ctrl_get_cropcapbl(struct pvr2_ctrl *cptr, int *val)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*val = cap->bounds.left;
+	return 0;
+}
+
+static int ctrl_get_cropcapbt(struct pvr2_ctrl *cptr, int *val)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*val = cap->bounds.top;
+	return 0;
+}
+
+static int ctrl_get_cropcapbw(struct pvr2_ctrl *cptr, int *val)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*val = cap->bounds.width;
+	return 0;
+}
+
+static int ctrl_get_cropcapbh(struct pvr2_ctrl *cptr, int *val)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*val = cap->bounds.height;
+	return 0;
+}
+
+static int ctrl_get_cropcapdl(struct pvr2_ctrl *cptr, int *val)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*val = cap->defrect.left;
+	return 0;
+}
+
+static int ctrl_get_cropcapdt(struct pvr2_ctrl *cptr, int *val)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*val = cap->defrect.top;
+	return 0;
+}
+
+static int ctrl_get_cropcapdw(struct pvr2_ctrl *cptr, int *val)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*val = cap->defrect.width;
+	return 0;
+}
+
+static int ctrl_get_cropcapdh(struct pvr2_ctrl *cptr, int *val)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*val = cap->defrect.height;
+	return 0;
+}
+
+static int ctrl_get_cropcappan(struct pvr2_ctrl *cptr, int *val)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*val = cap->pixelaspect.numerator;
+	return 0;
+}
+
+static int ctrl_get_cropcappad(struct pvr2_ctrl *cptr, int *val)
+{
+	struct v4l2_cropcap *cap = &cptr->hdw->cropcap_info;
+	int stat = pvr2_hdw_check_cropcap(cptr->hdw);
+	if (stat != 0) {
+		return stat;
+	}
+	*val = cap->pixelaspect.denominator;
+	return 0;
+}
+
+static int ctrl_vres_max_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	/* Actual maximum depends on the video standard in effect. */
+	if (cptr->hdw->std_mask_cur & V4L2_STD_525_60) {
+		*vp = 480;
+	} else {
+		*vp = 576;
+	}
+	return 0;
+}
+
+static int ctrl_vres_min_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	/* Actual minimum depends on device digitizer type. */
+	if (cptr->hdw->hdw_desc->flag_has_cx25840) {
+		*vp = 75;
+	} else {
+		*vp = 17;
+	}
+	return 0;
+}
+
+static int ctrl_get_input(struct pvr2_ctrl *cptr,int *vp)
+{
+	*vp = cptr->hdw->input_val;
+	return 0;
+}
+
+static int ctrl_check_input(struct pvr2_ctrl *cptr,int v)
+{
+	return ((1 << v) & cptr->hdw->input_allowed_mask) != 0;
+}
+
+static int ctrl_set_input(struct pvr2_ctrl *cptr,int m,int v)
+{
+	return pvr2_hdw_set_input(cptr->hdw,v);
+}
+
+static int ctrl_isdirty_input(struct pvr2_ctrl *cptr)
+{
+	return cptr->hdw->input_dirty != 0;
+}
+
+static void ctrl_cleardirty_input(struct pvr2_ctrl *cptr)
+{
+	cptr->hdw->input_dirty = 0;
+}
+
+
+static int ctrl_freq_max_get(struct pvr2_ctrl *cptr, int *vp)
+{
+	unsigned long fv;
+	struct pvr2_hdw *hdw = cptr->hdw;
+	if (hdw->tuner_signal_stale) {
+		pvr2_hdw_status_poll(hdw);
+	}
+	fv = hdw->tuner_signal_info.rangehigh;
+	if (!fv) {
+		/* Safety fallback */
+		*vp = TV_MAX_FREQ;
+		return 0;
+	}
+	if (hdw->tuner_signal_info.capability & V4L2_TUNER_CAP_LOW) {
+		fv = (fv * 125) / 2;
+	} else {
+		fv = fv * 62500;
+	}
+	*vp = fv;
+	return 0;
+}
+
+static int ctrl_freq_min_get(struct pvr2_ctrl *cptr, int *vp)
+{
+	unsigned long fv;
+	struct pvr2_hdw *hdw = cptr->hdw;
+	if (hdw->tuner_signal_stale) {
+		pvr2_hdw_status_poll(hdw);
+	}
+	fv = hdw->tuner_signal_info.rangelow;
+	if (!fv) {
+		/* Safety fallback */
+		*vp = TV_MIN_FREQ;
+		return 0;
+	}
+	if (hdw->tuner_signal_info.capability & V4L2_TUNER_CAP_LOW) {
+		fv = (fv * 125) / 2;
+	} else {
+		fv = fv * 62500;
+	}
+	*vp = fv;
+	return 0;
+}
+
+static int ctrl_cx2341x_is_dirty(struct pvr2_ctrl *cptr)
+{
+	return cptr->hdw->enc_stale != 0;
+}
+
+static void ctrl_cx2341x_clear_dirty(struct pvr2_ctrl *cptr)
+{
+	cptr->hdw->enc_stale = 0;
+	cptr->hdw->enc_unsafe_stale = 0;
+}
+
+static int ctrl_cx2341x_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	int ret;
+	struct v4l2_ext_controls cs;
+	struct v4l2_ext_control c1;
+	memset(&cs,0,sizeof(cs));
+	memset(&c1,0,sizeof(c1));
+	cs.controls = &c1;
+	cs.count = 1;
+	c1.id = cptr->info->v4l_id;
+	ret = cx2341x_ext_ctrls(&cptr->hdw->enc_ctl_state, 0, &cs,
+				VIDIOC_G_EXT_CTRLS);
+	if (ret) return ret;
+	*vp = c1.value;
+	return 0;
+}
+
+static int ctrl_cx2341x_set(struct pvr2_ctrl *cptr,int m,int v)
+{
+	int ret;
+	struct pvr2_hdw *hdw = cptr->hdw;
+	struct v4l2_ext_controls cs;
+	struct v4l2_ext_control c1;
+	memset(&cs,0,sizeof(cs));
+	memset(&c1,0,sizeof(c1));
+	cs.controls = &c1;
+	cs.count = 1;
+	c1.id = cptr->info->v4l_id;
+	c1.value = v;
+	ret = cx2341x_ext_ctrls(&hdw->enc_ctl_state,
+				hdw->state_encoder_run, &cs,
+				VIDIOC_S_EXT_CTRLS);
+	if (ret == -EBUSY) {
+		/* Oops.  cx2341x is telling us it's not safe to change
+		   this control while we're capturing.  Make a note of this
+		   fact so that the pipeline will be stopped the next time
+		   controls are committed.  Then go on ahead and store this
+		   change anyway. */
+		ret = cx2341x_ext_ctrls(&hdw->enc_ctl_state,
+					0, &cs,
+					VIDIOC_S_EXT_CTRLS);
+		if (!ret) hdw->enc_unsafe_stale = !0;
+	}
+	if (ret) return ret;
+	hdw->enc_stale = !0;
+	return 0;
+}
+
+static unsigned int ctrl_cx2341x_getv4lflags(struct pvr2_ctrl *cptr)
+{
+	struct v4l2_queryctrl qctrl;
+	struct pvr2_ctl_info *info;
+	qctrl.id = cptr->info->v4l_id;
+	cx2341x_ctrl_query(&cptr->hdw->enc_ctl_state,&qctrl);
+	/* Strip out the const so we can adjust a function pointer.  It's
+	   OK to do this here because we know this is a dynamically created
+	   control, so the underlying storage for the info pointer is (a)
+	   private to us, and (b) not in read-only storage.  Either we do
+	   this or we significantly complicate the underlying control
+	   implementation. */
+	info = (struct pvr2_ctl_info *)(cptr->info);
+	if (qctrl.flags & V4L2_CTRL_FLAG_READ_ONLY) {
+		if (info->set_value) {
+			info->set_value = NULL;
+		}
+	} else {
+		if (!(info->set_value)) {
+			info->set_value = ctrl_cx2341x_set;
+		}
+	}
+	return qctrl.flags;
+}
+
+static int ctrl_streamingenabled_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	*vp = cptr->hdw->state_pipeline_req;
+	return 0;
+}
+
+static int ctrl_masterstate_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	*vp = cptr->hdw->master_state;
+	return 0;
+}
+
+static int ctrl_hsm_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	int result = pvr2_hdw_is_hsm(cptr->hdw);
+	*vp = PVR2_CVAL_HSM_FULL;
+	if (result < 0) *vp = PVR2_CVAL_HSM_FAIL;
+	if (result) *vp = PVR2_CVAL_HSM_HIGH;
+	return 0;
+}
+
+static int ctrl_stddetect_get(struct pvr2_ctrl *cptr, int *vp)
+{
+	*vp = pvr2_hdw_get_detected_std(cptr->hdw);
+	return 0;
+}
+
+static int ctrl_stdavail_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	*vp = cptr->hdw->std_mask_avail;
+	return 0;
+}
+
+static int ctrl_stdavail_set(struct pvr2_ctrl *cptr,int m,int v)
+{
+	struct pvr2_hdw *hdw = cptr->hdw;
+	v4l2_std_id ns;
+	ns = hdw->std_mask_avail;
+	ns = (ns & ~m) | (v & m);
+	if (ns == hdw->std_mask_avail) return 0;
+	hdw->std_mask_avail = ns;
+	hdw->std_info_cur.def.type_bitmask.valid_bits = hdw->std_mask_avail;
+	return 0;
+}
+
+static int ctrl_std_val_to_sym(struct pvr2_ctrl *cptr,int msk,int val,
+			       char *bufPtr,unsigned int bufSize,
+			       unsigned int *len)
+{
+	*len = pvr2_std_id_to_str(bufPtr,bufSize,msk & val);
+	return 0;
+}
+
+static int ctrl_std_sym_to_val(struct pvr2_ctrl *cptr,
+			       const char *bufPtr,unsigned int bufSize,
+			       int *mskp,int *valp)
+{
+	int ret;
+	v4l2_std_id id;
+	ret = pvr2_std_str_to_id(&id,bufPtr,bufSize);
+	if (ret < 0) return ret;
+	if (mskp) *mskp = id;
+	if (valp) *valp = id;
+	return 0;
+}
+
+static int ctrl_stdcur_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	*vp = cptr->hdw->std_mask_cur;
+	return 0;
+}
+
+static int ctrl_stdcur_set(struct pvr2_ctrl *cptr,int m,int v)
+{
+	struct pvr2_hdw *hdw = cptr->hdw;
+	v4l2_std_id ns;
+	ns = hdw->std_mask_cur;
+	ns = (ns & ~m) | (v & m);
+	if (ns == hdw->std_mask_cur) return 0;
+	hdw->std_mask_cur = ns;
+	hdw->std_dirty = !0;
+	return 0;
+}
+
+static int ctrl_stdcur_is_dirty(struct pvr2_ctrl *cptr)
+{
+	return cptr->hdw->std_dirty != 0;
+}
+
+static void ctrl_stdcur_clear_dirty(struct pvr2_ctrl *cptr)
+{
+	cptr->hdw->std_dirty = 0;
+}
+
+static int ctrl_signal_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	struct pvr2_hdw *hdw = cptr->hdw;
+	pvr2_hdw_status_poll(hdw);
+	*vp = hdw->tuner_signal_info.signal;
+	return 0;
+}
+
+static int ctrl_audio_modes_present_get(struct pvr2_ctrl *cptr,int *vp)
+{
+	int val = 0;
+	unsigned int subchan;
+	struct pvr2_hdw *hdw = cptr->hdw;
+	pvr2_hdw_status_poll(hdw);
+	subchan = hdw->tuner_signal_info.rxsubchans;
+	if (subchan & V4L2_TUNER_SUB_MONO) {
+		val |= (1 << V4L2_TUNER_MODE_MONO);
+	}
+	if (subchan & V4L2_TUNER_SUB_STEREO) {
+		val |= (1 << V4L2_TUNER_MODE_STEREO);
+	}
+	if (subchan & V4L2_TUNER_SUB_LANG1) {
+		val |= (1 << V4L2_TUNER_MODE_LANG1);
+	}
+	if (subchan & V4L2_TUNER_SUB_LANG2) {
+		val |= (1 << V4L2_TUNER_MODE_LANG2);
+	}
+	*vp = val;
+	return 0;
+}
+
+
+#define DEFINT(vmin,vmax) \
+	.type = pvr2_ctl_int, \
+	.def.type_int.min_value = vmin, \
+	.def.type_int.max_value = vmax
+
+#define DEFENUM(tab) \
+	.type = pvr2_ctl_enum, \
+	.def.type_enum.count = ARRAY_SIZE(tab), \
+	.def.type_enum.value_names = tab
+
+#define DEFBOOL \
+	.type = pvr2_ctl_bool
+
+#define DEFMASK(msk,tab) \
+	.type = pvr2_ctl_bitmask, \
+	.def.type_bitmask.valid_bits = msk, \
+	.def.type_bitmask.bit_names = tab
+
+#define DEFREF(vname) \
+	.set_value = ctrl_set_##vname, \
+	.get_value = ctrl_get_##vname, \
+	.is_dirty = ctrl_isdirty_##vname, \
+	.clear_dirty = ctrl_cleardirty_##vname
+
+
+#define VCREATE_FUNCS(vname) \
+static int ctrl_get_##vname(struct pvr2_ctrl *cptr,int *vp) \
+{*vp = cptr->hdw->vname##_val; return 0;} \
+static int ctrl_set_##vname(struct pvr2_ctrl *cptr,int m,int v) \
+{cptr->hdw->vname##_val = v; cptr->hdw->vname##_dirty = !0; return 0;} \
+static int ctrl_isdirty_##vname(struct pvr2_ctrl *cptr) \
+{return cptr->hdw->vname##_dirty != 0;} \
+static void ctrl_cleardirty_##vname(struct pvr2_ctrl *cptr) \
+{cptr->hdw->vname##_dirty = 0;}
+
+VCREATE_FUNCS(brightness)
+VCREATE_FUNCS(contrast)
+VCREATE_FUNCS(saturation)
+VCREATE_FUNCS(hue)
+VCREATE_FUNCS(volume)
+VCREATE_FUNCS(balance)
+VCREATE_FUNCS(bass)
+VCREATE_FUNCS(treble)
+VCREATE_FUNCS(mute)
+VCREATE_FUNCS(cropl)
+VCREATE_FUNCS(cropt)
+VCREATE_FUNCS(cropw)
+VCREATE_FUNCS(croph)
+VCREATE_FUNCS(audiomode)
+VCREATE_FUNCS(res_hor)
+VCREATE_FUNCS(res_ver)
+VCREATE_FUNCS(srate)
+
+/* Table definition of all controls which can be manipulated */
+static const struct pvr2_ctl_info control_defs[] = {
+	{
+		.v4l_id = V4L2_CID_BRIGHTNESS,
+		.desc = "Brightness",
+		.name = "brightness",
+		.default_value = 128,
+		DEFREF(brightness),
+		DEFINT(0,255),
+	},{
+		.v4l_id = V4L2_CID_CONTRAST,
+		.desc = "Contrast",
+		.name = "contrast",
+		.default_value = 68,
+		DEFREF(contrast),
+		DEFINT(0,127),
+	},{
+		.v4l_id = V4L2_CID_SATURATION,
+		.desc = "Saturation",
+		.name = "saturation",
+		.default_value = 64,
+		DEFREF(saturation),
+		DEFINT(0,127),
+	},{
+		.v4l_id = V4L2_CID_HUE,
+		.desc = "Hue",
+		.name = "hue",
+		.default_value = 0,
+		DEFREF(hue),
+		DEFINT(-128,127),
+	},{
+		.v4l_id = V4L2_CID_AUDIO_VOLUME,
+		.desc = "Volume",
+		.name = "volume",
+		.default_value = 62000,
+		DEFREF(volume),
+		DEFINT(0,65535),
+	},{
+		.v4l_id = V4L2_CID_AUDIO_BALANCE,
+		.desc = "Balance",
+		.name = "balance",
+		.default_value = 0,
+		DEFREF(balance),
+		DEFINT(-32768,32767),
+	},{
+		.v4l_id = V4L2_CID_AUDIO_BASS,
+		.desc = "Bass",
+		.name = "bass",
+		.default_value = 0,
+		DEFREF(bass),
+		DEFINT(-32768,32767),
+	},{
+		.v4l_id = V4L2_CID_AUDIO_TREBLE,
+		.desc = "Treble",
+		.name = "treble",
+		.default_value = 0,
+		DEFREF(treble),
+		DEFINT(-32768,32767),
+	},{
+		.v4l_id = V4L2_CID_AUDIO_MUTE,
+		.desc = "Mute",
+		.name = "mute",
+		.default_value = 0,
+		DEFREF(mute),
+		DEFBOOL,
+	}, {
+		.desc = "Capture crop left margin",
+		.name = "crop_left",
+		.internal_id = PVR2_CID_CROPL,
+		.default_value = 0,
+		DEFREF(cropl),
+		DEFINT(-129, 340),
+		.get_min_value = ctrl_cropl_min_get,
+		.get_max_value = ctrl_cropl_max_get,
+		.get_def_value = ctrl_get_cropcapdl,
+	}, {
+		.desc = "Capture crop top margin",
+		.name = "crop_top",
+		.internal_id = PVR2_CID_CROPT,
+		.default_value = 0,
+		DEFREF(cropt),
+		DEFINT(-35, 544),
+		.get_min_value = ctrl_cropt_min_get,
+		.get_max_value = ctrl_cropt_max_get,
+		.get_def_value = ctrl_get_cropcapdt,
+	}, {
+		.desc = "Capture crop width",
+		.name = "crop_width",
+		.internal_id = PVR2_CID_CROPW,
+		.default_value = 720,
+		DEFREF(cropw),
+		DEFINT(0, 864),
+		.get_max_value = ctrl_cropw_max_get,
+		.get_def_value = ctrl_get_cropcapdw,
+	}, {
+		.desc = "Capture crop height",
+		.name = "crop_height",
+		.internal_id = PVR2_CID_CROPH,
+		.default_value = 480,
+		DEFREF(croph),
+		DEFINT(0, 576),
+		.get_max_value = ctrl_croph_max_get,
+		.get_def_value = ctrl_get_cropcapdh,
+	}, {
+		.desc = "Capture capability pixel aspect numerator",
+		.name = "cropcap_pixel_numerator",
+		.internal_id = PVR2_CID_CROPCAPPAN,
+		.get_value = ctrl_get_cropcappan,
+	}, {
+		.desc = "Capture capability pixel aspect denominator",
+		.name = "cropcap_pixel_denominator",
+		.internal_id = PVR2_CID_CROPCAPPAD,
+		.get_value = ctrl_get_cropcappad,
+	}, {
+		.desc = "Capture capability bounds top",
+		.name = "cropcap_bounds_top",
+		.internal_id = PVR2_CID_CROPCAPBT,
+		.get_value = ctrl_get_cropcapbt,
+	}, {
+		.desc = "Capture capability bounds left",
+		.name = "cropcap_bounds_left",
+		.internal_id = PVR2_CID_CROPCAPBL,
+		.get_value = ctrl_get_cropcapbl,
+	}, {
+		.desc = "Capture capability bounds width",
+		.name = "cropcap_bounds_width",
+		.internal_id = PVR2_CID_CROPCAPBW,
+		.get_value = ctrl_get_cropcapbw,
+	}, {
+		.desc = "Capture capability bounds height",
+		.name = "cropcap_bounds_height",
+		.internal_id = PVR2_CID_CROPCAPBH,
+		.get_value = ctrl_get_cropcapbh,
+	},{
+		.desc = "Video Source",
+		.name = "input",
+		.internal_id = PVR2_CID_INPUT,
+		.default_value = PVR2_CVAL_INPUT_TV,
+		.check_value = ctrl_check_input,
+		DEFREF(input),
+		DEFENUM(control_values_input),
+	},{
+		.desc = "Audio Mode",
+		.name = "audio_mode",
+		.internal_id = PVR2_CID_AUDIOMODE,
+		.default_value = V4L2_TUNER_MODE_STEREO,
+		DEFREF(audiomode),
+		DEFENUM(control_values_audiomode),
+	},{
+		.desc = "Horizontal capture resolution",
+		.name = "resolution_hor",
+		.internal_id = PVR2_CID_HRES,
+		.default_value = 720,
+		DEFREF(res_hor),
+		DEFINT(19,720),
+	},{
+		.desc = "Vertical capture resolution",
+		.name = "resolution_ver",
+		.internal_id = PVR2_CID_VRES,
+		.default_value = 480,
+		DEFREF(res_ver),
+		DEFINT(17,576),
+		/* Hook in check for video standard and adjust maximum
+		   depending on the standard. */
+		.get_max_value = ctrl_vres_max_get,
+		.get_min_value = ctrl_vres_min_get,
+	},{
+		.v4l_id = V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ,
+		.default_value = V4L2_MPEG_AUDIO_SAMPLING_FREQ_48000,
+		.desc = "Audio Sampling Frequency",
+		.name = "srate",
+		DEFREF(srate),
+		DEFENUM(control_values_srate),
+	},{
+		.desc = "Tuner Frequency (Hz)",
+		.name = "frequency",
+		.internal_id = PVR2_CID_FREQUENCY,
+		.default_value = 0,
+		.set_value = ctrl_freq_set,
+		.get_value = ctrl_freq_get,
+		.is_dirty = ctrl_freq_is_dirty,
+		.clear_dirty = ctrl_freq_clear_dirty,
+		DEFINT(0,0),
+		/* Hook in check for input value (tv/radio) and adjust
+		   max/min values accordingly */
+		.get_max_value = ctrl_freq_max_get,
+		.get_min_value = ctrl_freq_min_get,
+	},{
+		.desc = "Channel",
+		.name = "channel",
+		.set_value = ctrl_channel_set,
+		.get_value = ctrl_channel_get,
+		DEFINT(0,FREQTABLE_SIZE),
+	},{
+		.desc = "Channel Program Frequency",
+		.name = "freq_table_value",
+		.set_value = ctrl_channelfreq_set,
+		.get_value = ctrl_channelfreq_get,
+		DEFINT(0,0),
+		/* Hook in check for input value (tv/radio) and adjust
+		   max/min values accordingly */
+		.get_max_value = ctrl_freq_max_get,
+		.get_min_value = ctrl_freq_min_get,
+	},{
+		.desc = "Channel Program ID",
+		.name = "freq_table_channel",
+		.set_value = ctrl_channelprog_set,
+		.get_value = ctrl_channelprog_get,
+		DEFINT(0,FREQTABLE_SIZE),
+	},{
+		.desc = "Streaming Enabled",
+		.name = "streaming_enabled",
+		.get_value = ctrl_streamingenabled_get,
+		DEFBOOL,
+	},{
+		.desc = "USB Speed",
+		.name = "usb_speed",
+		.get_value = ctrl_hsm_get,
+		DEFENUM(control_values_hsm),
+	},{
+		.desc = "Master State",
+		.name = "master_state",
+		.get_value = ctrl_masterstate_get,
+		DEFENUM(pvr2_state_names),
+	},{
+		.desc = "Signal Present",
+		.name = "signal_present",
+		.get_value = ctrl_signal_get,
+		DEFINT(0,65535),
+	},{
+		.desc = "Audio Modes Present",
+		.name = "audio_modes_present",
+		.get_value = ctrl_audio_modes_present_get,
+		/* For this type we "borrow" the V4L2_TUNER_MODE enum from
+		   v4l.  Nothing outside of this module cares about this,
+		   but I reuse it in order to also reuse the
+		   control_values_audiomode string table. */
+		DEFMASK(((1 << V4L2_TUNER_MODE_MONO)|
+			 (1 << V4L2_TUNER_MODE_STEREO)|
+			 (1 << V4L2_TUNER_MODE_LANG1)|
+			 (1 << V4L2_TUNER_MODE_LANG2)),
+			control_values_audiomode),
+	},{
+		.desc = "Video Standards Available Mask",
+		.name = "video_standard_mask_available",
+		.internal_id = PVR2_CID_STDAVAIL,
+		.skip_init = !0,
+		.get_value = ctrl_stdavail_get,
+		.set_value = ctrl_stdavail_set,
+		.val_to_sym = ctrl_std_val_to_sym,
+		.sym_to_val = ctrl_std_sym_to_val,
+		.type = pvr2_ctl_bitmask,
+	},{
+		.desc = "Video Standards In Use Mask",
+		.name = "video_standard_mask_active",
+		.internal_id = PVR2_CID_STDCUR,
+		.skip_init = !0,
+		.get_value = ctrl_stdcur_get,
+		.set_value = ctrl_stdcur_set,
+		.is_dirty = ctrl_stdcur_is_dirty,
+		.clear_dirty = ctrl_stdcur_clear_dirty,
+		.val_to_sym = ctrl_std_val_to_sym,
+		.sym_to_val = ctrl_std_sym_to_val,
+		.type = pvr2_ctl_bitmask,
+	},{
+		.desc = "Video Standards Detected Mask",
+		.name = "video_standard_mask_detected",
+		.internal_id = PVR2_CID_STDDETECT,
+		.skip_init = !0,
+		.get_value = ctrl_stddetect_get,
+		.val_to_sym = ctrl_std_val_to_sym,
+		.sym_to_val = ctrl_std_sym_to_val,
+		.type = pvr2_ctl_bitmask,
+	}
+};
+
+#define CTRLDEF_COUNT ARRAY_SIZE(control_defs)
+
+
+const char *pvr2_config_get_name(enum pvr2_config cfg)
+{
+	switch (cfg) {
+	case pvr2_config_empty: return "empty";
+	case pvr2_config_mpeg: return "mpeg";
+	case pvr2_config_vbi: return "vbi";
+	case pvr2_config_pcm: return "pcm";
+	case pvr2_config_rawvideo: return "raw video";
+	}
+	return "<unknown>";
+}
+
+
+struct usb_device *pvr2_hdw_get_dev(struct pvr2_hdw *hdw)
+{
+	return hdw->usb_dev;
+}
+
+
+unsigned long pvr2_hdw_get_sn(struct pvr2_hdw *hdw)
+{
+	return hdw->serial_number;
+}
+
+
+const char *pvr2_hdw_get_bus_info(struct pvr2_hdw *hdw)
+{
+	return hdw->bus_info;
+}
+
+
+const char *pvr2_hdw_get_device_identifier(struct pvr2_hdw *hdw)
+{
+	return hdw->identifier;
+}
+
+
+unsigned long pvr2_hdw_get_cur_freq(struct pvr2_hdw *hdw)
+{
+	return hdw->freqSelector ? hdw->freqValTelevision : hdw->freqValRadio;
+}
+
+/* Set the currently tuned frequency and account for all possible
+   driver-core side effects of this action. */
+static void pvr2_hdw_set_cur_freq(struct pvr2_hdw *hdw,unsigned long val)
+{
+	if (hdw->input_val == PVR2_CVAL_INPUT_RADIO) {
+		if (hdw->freqSelector) {
+			/* Swing over to radio frequency selection */
+			hdw->freqSelector = 0;
+			hdw->freqDirty = !0;
+		}
+		if (hdw->freqValRadio != val) {
+			hdw->freqValRadio = val;
+			hdw->freqSlotRadio = 0;
+			hdw->freqDirty = !0;
+		}
+	} else {
+		if (!(hdw->freqSelector)) {
+			/* Swing over to television frequency selection */
+			hdw->freqSelector = 1;
+			hdw->freqDirty = !0;
+		}
+		if (hdw->freqValTelevision != val) {
+			hdw->freqValTelevision = val;
+			hdw->freqSlotTelevision = 0;
+			hdw->freqDirty = !0;
+		}
+	}
+}
+
+int pvr2_hdw_get_unit_number(struct pvr2_hdw *hdw)
+{
+	return hdw->unit_number;
+}
+
+
+/* Attempt to locate one of the given set of files.  Messages are logged
+   appropriate to what has been found.  The return value will be 0 or
+   greater on success (it will be the index of the file name found) and
+   fw_entry will be filled in.  Otherwise a negative error is returned on
+   failure.  If the return value is -ENOENT then no viable firmware file
+   could be located. */
+static int pvr2_locate_firmware(struct pvr2_hdw *hdw,
+				const struct firmware **fw_entry,
+				const char *fwtypename,
+				unsigned int fwcount,
+				const char *fwnames[])
+{
+	unsigned int idx;
+	int ret = -EINVAL;
+	for (idx = 0; idx < fwcount; idx++) {
+		ret = request_firmware(fw_entry,
+				       fwnames[idx],
+				       &hdw->usb_dev->dev);
+		if (!ret) {
+			trace_firmware("Located %s firmware: %s; uploading...",
+				       fwtypename,
+				       fwnames[idx]);
+			return idx;
+		}
+		if (ret == -ENOENT) continue;
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "request_firmware fatal error with code=%d",ret);
+		return ret;
+	}
+	pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+		   "***WARNING*** Device %s firmware seems to be missing.",
+		   fwtypename);
+	pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+		   "Did you install the pvrusb2 firmware files in their proper location?");
+	if (fwcount == 1) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "request_firmware unable to locate %s file %s",
+			   fwtypename,fwnames[0]);
+	} else {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "request_firmware unable to locate one of the following %s files:",
+			   fwtypename);
+		for (idx = 0; idx < fwcount; idx++) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "request_firmware: Failed to find %s",
+				   fwnames[idx]);
+		}
+	}
+	return ret;
+}
+
+
+/*
+ * pvr2_upload_firmware1().
+ *
+ * Send the 8051 firmware to the device.  After the upload, arrange for
+ * device to re-enumerate.
+ *
+ * NOTE : the pointer to the firmware data given by request_firmware()
+ * is not suitable for an usb transaction.
+ *
+ */
+static int pvr2_upload_firmware1(struct pvr2_hdw *hdw)
+{
+	const struct firmware *fw_entry = NULL;
+	void  *fw_ptr;
+	unsigned int pipe;
+	unsigned int fwsize;
+	int ret;
+	u16 address;
+
+	if (!hdw->hdw_desc->fx2_firmware.cnt) {
+		hdw->fw1_state = FW1_STATE_OK;
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Connected device type defines no firmware to upload; ignoring firmware");
+		return -ENOTTY;
+	}
+
+	hdw->fw1_state = FW1_STATE_FAILED; // default result
+
+	trace_firmware("pvr2_upload_firmware1");
+
+	ret = pvr2_locate_firmware(hdw,&fw_entry,"fx2 controller",
+				   hdw->hdw_desc->fx2_firmware.cnt,
+				   hdw->hdw_desc->fx2_firmware.lst);
+	if (ret < 0) {
+		if (ret == -ENOENT) hdw->fw1_state = FW1_STATE_MISSING;
+		return ret;
+	}
+
+	usb_clear_halt(hdw->usb_dev, usb_sndbulkpipe(hdw->usb_dev, 0 & 0x7f));
+
+	pipe = usb_sndctrlpipe(hdw->usb_dev, 0);
+	fwsize = fw_entry->size;
+
+	if ((fwsize != 0x2000) &&
+	    (!(hdw->hdw_desc->flag_fx2_16kb && (fwsize == 0x4000)))) {
+		if (hdw->hdw_desc->flag_fx2_16kb) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "Wrong fx2 firmware size (expected 8192 or 16384, got %u)",
+				   fwsize);
+		} else {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "Wrong fx2 firmware size (expected 8192, got %u)",
+				   fwsize);
+		}
+		release_firmware(fw_entry);
+		return -ENOMEM;
+	}
+
+	fw_ptr = kmalloc(0x800, GFP_KERNEL);
+	if (fw_ptr == NULL){
+		release_firmware(fw_entry);
+		return -ENOMEM;
+	}
+
+	/* We have to hold the CPU during firmware upload. */
+	pvr2_hdw_cpureset_assert(hdw,1);
+
+	/* upload the firmware to address 0000-1fff in 2048 (=0x800) bytes
+	   chunk. */
+
+	ret = 0;
+	for (address = 0; address < fwsize; address += 0x800) {
+		memcpy(fw_ptr, fw_entry->data + address, 0x800);
+		ret += usb_control_msg(hdw->usb_dev, pipe, 0xa0, 0x40, address,
+				       0, fw_ptr, 0x800, HZ);
+	}
+
+	trace_firmware("Upload done, releasing device's CPU");
+
+	/* Now release the CPU.  It will disconnect and reconnect later. */
+	pvr2_hdw_cpureset_assert(hdw,0);
+
+	kfree(fw_ptr);
+	release_firmware(fw_entry);
+
+	trace_firmware("Upload done (%d bytes sent)",ret);
+
+	/* We should have written fwsize bytes */
+	if (ret == fwsize) {
+		hdw->fw1_state = FW1_STATE_RELOAD;
+		return 0;
+	}
+
+	return -EIO;
+}
+
+
+/*
+ * pvr2_upload_firmware2()
+ *
+ * This uploads encoder firmware on endpoint 2.
+ *
+ */
+
+int pvr2_upload_firmware2(struct pvr2_hdw *hdw)
+{
+	const struct firmware *fw_entry = NULL;
+	void  *fw_ptr;
+	unsigned int pipe, fw_len, fw_done, bcnt, icnt;
+	int actual_length;
+	int ret = 0;
+	int fwidx;
+	static const char *fw_files[] = {
+		CX2341X_FIRM_ENC_FILENAME,
+	};
+
+	if (hdw->hdw_desc->flag_skip_cx23416_firmware) {
+		return 0;
+	}
+
+	trace_firmware("pvr2_upload_firmware2");
+
+	ret = pvr2_locate_firmware(hdw,&fw_entry,"encoder",
+				   ARRAY_SIZE(fw_files), fw_files);
+	if (ret < 0) return ret;
+	fwidx = ret;
+	ret = 0;
+	/* Since we're about to completely reinitialize the encoder,
+	   invalidate our cached copy of its configuration state.  Next
+	   time we configure the encoder, then we'll fully configure it. */
+	hdw->enc_cur_valid = 0;
+
+	/* Encoder is about to be reset so note that as far as we're
+	   concerned now, the encoder has never been run. */
+	del_timer_sync(&hdw->encoder_run_timer);
+	if (hdw->state_encoder_runok) {
+		hdw->state_encoder_runok = 0;
+		trace_stbit("state_encoder_runok",hdw->state_encoder_runok);
+	}
+
+	/* First prepare firmware loading */
+	ret |= pvr2_write_register(hdw, 0x0048, 0xffffffff); /*interrupt mask*/
+	ret |= pvr2_hdw_gpio_chg_dir(hdw,0xffffffff,0x00000088); /*gpio dir*/
+	ret |= pvr2_hdw_gpio_chg_out(hdw,0xffffffff,0x00000008); /*gpio output state*/
+	ret |= pvr2_hdw_cmd_deep_reset(hdw);
+	ret |= pvr2_write_register(hdw, 0xa064, 0x00000000); /*APU command*/
+	ret |= pvr2_hdw_gpio_chg_dir(hdw,0xffffffff,0x00000408); /*gpio dir*/
+	ret |= pvr2_hdw_gpio_chg_out(hdw,0xffffffff,0x00000008); /*gpio output state*/
+	ret |= pvr2_write_register(hdw, 0x9058, 0xffffffed); /*VPU ctrl*/
+	ret |= pvr2_write_register(hdw, 0x9054, 0xfffffffd); /*reset hw blocks*/
+	ret |= pvr2_write_register(hdw, 0x07f8, 0x80000800); /*encoder SDRAM refresh*/
+	ret |= pvr2_write_register(hdw, 0x07fc, 0x0000001a); /*encoder SDRAM pre-charge*/
+	ret |= pvr2_write_register(hdw, 0x0700, 0x00000000); /*I2C clock*/
+	ret |= pvr2_write_register(hdw, 0xaa00, 0x00000000); /*unknown*/
+	ret |= pvr2_write_register(hdw, 0xaa04, 0x00057810); /*unknown*/
+	ret |= pvr2_write_register(hdw, 0xaa10, 0x00148500); /*unknown*/
+	ret |= pvr2_write_register(hdw, 0xaa18, 0x00840000); /*unknown*/
+	ret |= pvr2_issue_simple_cmd(hdw,FX2CMD_FWPOST1);
+	ret |= pvr2_issue_simple_cmd(hdw,FX2CMD_MEMSEL | (1 << 8) | (0 << 16));
+
+	if (ret) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "firmware2 upload prep failed, ret=%d",ret);
+		release_firmware(fw_entry);
+		goto done;
+	}
+
+	/* Now send firmware */
+
+	fw_len = fw_entry->size;
+
+	if (fw_len % sizeof(u32)) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "size of %s firmware must be a multiple of %zu bytes",
+			   fw_files[fwidx],sizeof(u32));
+		release_firmware(fw_entry);
+		ret = -EINVAL;
+		goto done;
+	}
+
+	fw_ptr = kmalloc(FIRMWARE_CHUNK_SIZE, GFP_KERNEL);
+	if (fw_ptr == NULL){
+		release_firmware(fw_entry);
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "failed to allocate memory for firmware2 upload");
+		ret = -ENOMEM;
+		goto done;
+	}
+
+	pipe = usb_sndbulkpipe(hdw->usb_dev, PVR2_FIRMWARE_ENDPOINT);
+
+	fw_done = 0;
+	for (fw_done = 0; fw_done < fw_len;) {
+		bcnt = fw_len - fw_done;
+		if (bcnt > FIRMWARE_CHUNK_SIZE) bcnt = FIRMWARE_CHUNK_SIZE;
+		memcpy(fw_ptr, fw_entry->data + fw_done, bcnt);
+		/* Usbsnoop log shows that we must swap bytes... */
+		/* Some background info: The data being swapped here is a
+		   firmware image destined for the mpeg encoder chip that
+		   lives at the other end of a USB endpoint.  The encoder
+		   chip always talks in 32 bit chunks and its storage is
+		   organized into 32 bit words.  However from the file
+		   system to the encoder chip everything is purely a byte
+		   stream.  The firmware file's contents are always 32 bit
+		   swapped from what the encoder expects.  Thus the need
+		   always exists to swap the bytes regardless of the endian
+		   type of the host processor and therefore swab32() makes
+		   the most sense. */
+		for (icnt = 0; icnt < bcnt/4 ; icnt++)
+			((u32 *)fw_ptr)[icnt] = swab32(((u32 *)fw_ptr)[icnt]);
+
+		ret |= usb_bulk_msg(hdw->usb_dev, pipe, fw_ptr,bcnt,
+				    &actual_length, HZ);
+		ret |= (actual_length != bcnt);
+		if (ret) break;
+		fw_done += bcnt;
+	}
+
+	trace_firmware("upload of %s : %i / %i ",
+		       fw_files[fwidx],fw_done,fw_len);
+
+	kfree(fw_ptr);
+	release_firmware(fw_entry);
+
+	if (ret) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "firmware2 upload transfer failure");
+		goto done;
+	}
+
+	/* Finish upload */
+
+	ret |= pvr2_write_register(hdw, 0x9054, 0xffffffff); /*reset hw blocks*/
+	ret |= pvr2_write_register(hdw, 0x9058, 0xffffffe8); /*VPU ctrl*/
+	ret |= pvr2_issue_simple_cmd(hdw,FX2CMD_MEMSEL | (1 << 8) | (0 << 16));
+
+	if (ret) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "firmware2 upload post-proc failure");
+	}
+
+ done:
+	if (hdw->hdw_desc->signal_routing_scheme ==
+	    PVR2_ROUTING_SCHEME_GOTVIEW) {
+		/* Ensure that GPIO 11 is set to output for GOTVIEW
+		   hardware. */
+		pvr2_hdw_gpio_chg_dir(hdw,(1 << 11),~0);
+	}
+	return ret;
+}
+
+
+static const char *pvr2_get_state_name(unsigned int st)
+{
+	if (st < ARRAY_SIZE(pvr2_state_names)) {
+		return pvr2_state_names[st];
+	}
+	return "???";
+}
+
+static int pvr2_decoder_enable(struct pvr2_hdw *hdw,int enablefl)
+{
+	/* Even though we really only care about the video decoder chip at
+	   this point, we'll broadcast stream on/off to all sub-devices
+	   anyway, just in case somebody else wants to hear the
+	   command... */
+	pvr2_trace(PVR2_TRACE_CHIPS, "subdev v4l2 stream=%s",
+		   (enablefl ? "on" : "off"));
+	v4l2_device_call_all(&hdw->v4l2_dev, 0, video, s_stream, enablefl);
+	v4l2_device_call_all(&hdw->v4l2_dev, 0, audio, s_stream, enablefl);
+	if (hdw->decoder_client_id) {
+		/* We get here if the encoder has been noticed.  Otherwise
+		   we'll issue a warning to the user (which should
+		   normally never happen). */
+		return 0;
+	}
+	if (!hdw->flag_decoder_missed) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "WARNING: No decoder present");
+		hdw->flag_decoder_missed = !0;
+		trace_stbit("flag_decoder_missed",
+			    hdw->flag_decoder_missed);
+	}
+	return -EIO;
+}
+
+
+int pvr2_hdw_get_state(struct pvr2_hdw *hdw)
+{
+	return hdw->master_state;
+}
+
+
+static int pvr2_hdw_untrip_unlocked(struct pvr2_hdw *hdw)
+{
+	if (!hdw->flag_tripped) return 0;
+	hdw->flag_tripped = 0;
+	pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+		   "Clearing driver error statuss");
+	return !0;
+}
+
+
+int pvr2_hdw_untrip(struct pvr2_hdw *hdw)
+{
+	int fl;
+	LOCK_TAKE(hdw->big_lock); do {
+		fl = pvr2_hdw_untrip_unlocked(hdw);
+	} while (0); LOCK_GIVE(hdw->big_lock);
+	if (fl) pvr2_hdw_state_sched(hdw);
+	return 0;
+}
+
+
+
+
+int pvr2_hdw_get_streaming(struct pvr2_hdw *hdw)
+{
+	return hdw->state_pipeline_req != 0;
+}
+
+
+int pvr2_hdw_set_streaming(struct pvr2_hdw *hdw,int enable_flag)
+{
+	int ret,st;
+	LOCK_TAKE(hdw->big_lock); do {
+		pvr2_hdw_untrip_unlocked(hdw);
+		if ((!enable_flag) != !(hdw->state_pipeline_req)) {
+			hdw->state_pipeline_req = enable_flag != 0;
+			pvr2_trace(PVR2_TRACE_START_STOP,
+				   "/*--TRACE_STREAM--*/ %s",
+				   enable_flag ? "enable" : "disable");
+		}
+		pvr2_hdw_state_sched(hdw);
+	} while (0); LOCK_GIVE(hdw->big_lock);
+	if ((ret = pvr2_hdw_wait(hdw,0)) < 0) return ret;
+	if (enable_flag) {
+		while ((st = hdw->master_state) != PVR2_STATE_RUN) {
+			if (st != PVR2_STATE_READY) return -EIO;
+			if ((ret = pvr2_hdw_wait(hdw,st)) < 0) return ret;
+		}
+	}
+	return 0;
+}
+
+
+int pvr2_hdw_set_stream_type(struct pvr2_hdw *hdw,enum pvr2_config config)
+{
+	int fl;
+	LOCK_TAKE(hdw->big_lock);
+	if ((fl = (hdw->desired_stream_type != config)) != 0) {
+		hdw->desired_stream_type = config;
+		hdw->state_pipeline_config = 0;
+		trace_stbit("state_pipeline_config",
+			    hdw->state_pipeline_config);
+		pvr2_hdw_state_sched(hdw);
+	}
+	LOCK_GIVE(hdw->big_lock);
+	if (fl) return 0;
+	return pvr2_hdw_wait(hdw,0);
+}
+
+
+static int get_default_tuner_type(struct pvr2_hdw *hdw)
+{
+	int unit_number = hdw->unit_number;
+	int tp = -1;
+	if ((unit_number >= 0) && (unit_number < PVR_NUM)) {
+		tp = tuner[unit_number];
+	}
+	if (tp < 0) return -EINVAL;
+	hdw->tuner_type = tp;
+	hdw->tuner_updated = !0;
+	return 0;
+}
+
+
+static v4l2_std_id get_default_standard(struct pvr2_hdw *hdw)
+{
+	int unit_number = hdw->unit_number;
+	int tp = 0;
+	if ((unit_number >= 0) && (unit_number < PVR_NUM)) {
+		tp = video_std[unit_number];
+		if (tp) return tp;
+	}
+	return 0;
+}
+
+
+static unsigned int get_default_error_tolerance(struct pvr2_hdw *hdw)
+{
+	int unit_number = hdw->unit_number;
+	int tp = 0;
+	if ((unit_number >= 0) && (unit_number < PVR_NUM)) {
+		tp = tolerance[unit_number];
+	}
+	return tp;
+}
+
+
+static int pvr2_hdw_check_firmware(struct pvr2_hdw *hdw)
+{
+	/* Try a harmless request to fetch the eeprom's address over
+	   endpoint 1.  See what happens.  Only the full FX2 image can
+	   respond to this.  If this probe fails then likely the FX2
+	   firmware needs be loaded. */
+	int result;
+	LOCK_TAKE(hdw->ctl_lock); do {
+		hdw->cmd_buffer[0] = FX2CMD_GET_EEPROM_ADDR;
+		result = pvr2_send_request_ex(hdw,HZ*1,!0,
+					   hdw->cmd_buffer,1,
+					   hdw->cmd_buffer,1);
+		if (result < 0) break;
+	} while(0); LOCK_GIVE(hdw->ctl_lock);
+	if (result) {
+		pvr2_trace(PVR2_TRACE_INIT,
+			   "Probe of device endpoint 1 result status %d",
+			   result);
+	} else {
+		pvr2_trace(PVR2_TRACE_INIT,
+			   "Probe of device endpoint 1 succeeded");
+	}
+	return result == 0;
+}
+
+struct pvr2_std_hack {
+	v4l2_std_id pat;  /* Pattern to match */
+	v4l2_std_id msk;  /* Which bits we care about */
+	v4l2_std_id std;  /* What additional standards or default to set */
+};
+
+/* This data structure labels specific combinations of standards from
+   tveeprom that we'll try to recognize.  If we recognize one, then assume
+   a specified default standard to use.  This is here because tveeprom only
+   tells us about available standards not the intended default standard (if
+   any) for the device in question.  We guess the default based on what has
+   been reported as available.  Note that this is only for guessing a
+   default - which can always be overridden explicitly - and if the user
+   has otherwise named a default then that default will always be used in
+   place of this table. */
+static const struct pvr2_std_hack std_eeprom_maps[] = {
+	{	/* PAL(B/G) */
+		.pat = V4L2_STD_B|V4L2_STD_GH,
+		.std = V4L2_STD_PAL_B|V4L2_STD_PAL_B1|V4L2_STD_PAL_G,
+	},
+	{	/* NTSC(M) */
+		.pat = V4L2_STD_MN,
+		.std = V4L2_STD_NTSC_M,
+	},
+	{	/* PAL(I) */
+		.pat = V4L2_STD_PAL_I,
+		.std = V4L2_STD_PAL_I,
+	},
+	{	/* SECAM(L/L') */
+		.pat = V4L2_STD_SECAM_L|V4L2_STD_SECAM_LC,
+		.std = V4L2_STD_SECAM_L|V4L2_STD_SECAM_LC,
+	},
+	{	/* PAL(D/D1/K) */
+		.pat = V4L2_STD_DK,
+		.std = V4L2_STD_PAL_D|V4L2_STD_PAL_D1|V4L2_STD_PAL_K,
+	},
+};
+
+static void pvr2_hdw_setup_std(struct pvr2_hdw *hdw)
+{
+	char buf[40];
+	unsigned int bcnt;
+	v4l2_std_id std1,std2,std3;
+
+	std1 = get_default_standard(hdw);
+	std3 = std1 ? 0 : hdw->hdw_desc->default_std_mask;
+
+	bcnt = pvr2_std_id_to_str(buf,sizeof(buf),hdw->std_mask_eeprom);
+	pvr2_trace(PVR2_TRACE_STD,
+		   "Supported video standard(s) reported available in hardware: %.*s",
+		   bcnt,buf);
+
+	hdw->std_mask_avail = hdw->std_mask_eeprom;
+
+	std2 = (std1|std3) & ~hdw->std_mask_avail;
+	if (std2) {
+		bcnt = pvr2_std_id_to_str(buf,sizeof(buf),std2);
+		pvr2_trace(PVR2_TRACE_STD,
+			   "Expanding supported video standards to include: %.*s",
+			   bcnt,buf);
+		hdw->std_mask_avail |= std2;
+	}
+
+	hdw->std_info_cur.def.type_bitmask.valid_bits = hdw->std_mask_avail;
+
+	if (std1) {
+		bcnt = pvr2_std_id_to_str(buf,sizeof(buf),std1);
+		pvr2_trace(PVR2_TRACE_STD,
+			   "Initial video standard forced to %.*s",
+			   bcnt,buf);
+		hdw->std_mask_cur = std1;
+		hdw->std_dirty = !0;
+		return;
+	}
+	if (std3) {
+		bcnt = pvr2_std_id_to_str(buf,sizeof(buf),std3);
+		pvr2_trace(PVR2_TRACE_STD,
+			   "Initial video standard (determined by device type): %.*s",
+			   bcnt, buf);
+		hdw->std_mask_cur = std3;
+		hdw->std_dirty = !0;
+		return;
+	}
+
+	{
+		unsigned int idx;
+		for (idx = 0; idx < ARRAY_SIZE(std_eeprom_maps); idx++) {
+			if (std_eeprom_maps[idx].msk ?
+			    ((std_eeprom_maps[idx].pat ^
+			     hdw->std_mask_eeprom) &
+			     std_eeprom_maps[idx].msk) :
+			    (std_eeprom_maps[idx].pat !=
+			     hdw->std_mask_eeprom)) continue;
+			bcnt = pvr2_std_id_to_str(buf,sizeof(buf),
+						  std_eeprom_maps[idx].std);
+			pvr2_trace(PVR2_TRACE_STD,
+				   "Initial video standard guessed as %.*s",
+				   bcnt,buf);
+			hdw->std_mask_cur = std_eeprom_maps[idx].std;
+			hdw->std_dirty = !0;
+			return;
+		}
+	}
+
+}
+
+
+static unsigned int pvr2_copy_i2c_addr_list(
+	unsigned short *dst, const unsigned char *src,
+	unsigned int dst_max)
+{
+	unsigned int cnt = 0;
+	if (!src) return 0;
+	while (src[cnt] && (cnt + 1) < dst_max) {
+		dst[cnt] = src[cnt];
+		cnt++;
+	}
+	dst[cnt] = I2C_CLIENT_END;
+	return cnt;
+}
+
+
+static void pvr2_hdw_cx25840_vbi_hack(struct pvr2_hdw *hdw)
+{
+	/*
+	  Mike Isely <isely@pobox.com> 19-Nov-2006 - This bit of nuttiness
+	  for cx25840 causes that module to correctly set up its video
+	  scaling.  This is really a problem in the cx25840 module itself,
+	  but we work around it here.  The problem has not been seen in
+	  ivtv because there VBI is supported and set up.  We don't do VBI
+	  here (at least not yet) and thus we never attempted to even set
+	  it up.
+	*/
+	struct v4l2_format fmt;
+	if (hdw->decoder_client_id != PVR2_CLIENT_ID_CX25840) {
+		/* We're not using a cx25840 so don't enable the hack */
+		return;
+	}
+
+	pvr2_trace(PVR2_TRACE_INIT,
+		   "Module ID %u: Executing cx25840 VBI hack",
+		   hdw->decoder_client_id);
+	memset(&fmt, 0, sizeof(fmt));
+	fmt.type = V4L2_BUF_TYPE_SLICED_VBI_CAPTURE;
+	fmt.fmt.sliced.service_lines[0][21] = V4L2_SLICED_CAPTION_525;
+	fmt.fmt.sliced.service_lines[1][21] = V4L2_SLICED_CAPTION_525;
+	v4l2_device_call_all(&hdw->v4l2_dev, hdw->decoder_client_id,
+			     vbi, s_sliced_fmt, &fmt.fmt.sliced);
+}
+
+
+static int pvr2_hdw_load_subdev(struct pvr2_hdw *hdw,
+				const struct pvr2_device_client_desc *cd)
+{
+	const char *fname;
+	unsigned char mid;
+	struct v4l2_subdev *sd;
+	unsigned int i2ccnt;
+	const unsigned char *p;
+	/* Arbitrary count - max # i2c addresses we will probe */
+	unsigned short i2caddr[25];
+
+	mid = cd->module_id;
+	fname = (mid < ARRAY_SIZE(module_names)) ? module_names[mid] : NULL;
+	if (!fname) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Module ID %u for device %s has no name?  The driver might have a configuration problem.",
+			   mid,
+			   hdw->hdw_desc->description);
+		return -EINVAL;
+	}
+	pvr2_trace(PVR2_TRACE_INIT,
+		   "Module ID %u (%s) for device %s being loaded...",
+		   mid, fname,
+		   hdw->hdw_desc->description);
+
+	i2ccnt = pvr2_copy_i2c_addr_list(i2caddr, cd->i2c_address_list,
+					 ARRAY_SIZE(i2caddr));
+	if (!i2ccnt && ((p = (mid < ARRAY_SIZE(module_i2c_addresses)) ?
+			 module_i2c_addresses[mid] : NULL) != NULL)) {
+		/* Second chance: Try default i2c address list */
+		i2ccnt = pvr2_copy_i2c_addr_list(i2caddr, p,
+						 ARRAY_SIZE(i2caddr));
+		if (i2ccnt) {
+			pvr2_trace(PVR2_TRACE_INIT,
+				   "Module ID %u: Using default i2c address list",
+				   mid);
+		}
+	}
+
+	if (!i2ccnt) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Module ID %u (%s) for device %s: No i2c addresses.	The driver might have a configuration problem.",
+			   mid, fname, hdw->hdw_desc->description);
+		return -EINVAL;
+	}
+
+	if (i2ccnt == 1) {
+		pvr2_trace(PVR2_TRACE_INIT,
+			   "Module ID %u: Setting up with specified i2c address 0x%x",
+			   mid, i2caddr[0]);
+		sd = v4l2_i2c_new_subdev(&hdw->v4l2_dev, &hdw->i2c_adap,
+					 fname, i2caddr[0], NULL);
+	} else {
+		pvr2_trace(PVR2_TRACE_INIT,
+			   "Module ID %u: Setting up with address probe list",
+			   mid);
+		sd = v4l2_i2c_new_subdev(&hdw->v4l2_dev, &hdw->i2c_adap,
+					 fname, 0, i2caddr);
+	}
+
+	if (!sd) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Module ID %u (%s) for device %s failed to load.  Possible missing sub-device kernel module or initialization failure within module.",
+			   mid, fname, hdw->hdw_desc->description);
+		return -EIO;
+	}
+
+	/* Tag this sub-device instance with the module ID we know about.
+	   In other places we'll use that tag to determine if the instance
+	   requires special handling. */
+	sd->grp_id = mid;
+
+	pvr2_trace(PVR2_TRACE_INFO, "Attached sub-driver %s", fname);
+
+
+	/* client-specific setup... */
+	switch (mid) {
+	case PVR2_CLIENT_ID_CX25840:
+	case PVR2_CLIENT_ID_SAA7115:
+		hdw->decoder_client_id = mid;
+		break;
+	default: break;
+	}
+
+	return 0;
+}
+
+
+static void pvr2_hdw_load_modules(struct pvr2_hdw *hdw)
+{
+	unsigned int idx;
+	const struct pvr2_string_table *cm;
+	const struct pvr2_device_client_table *ct;
+	int okFl = !0;
+
+	cm = &hdw->hdw_desc->client_modules;
+	for (idx = 0; idx < cm->cnt; idx++) {
+		request_module(cm->lst[idx]);
+	}
+
+	ct = &hdw->hdw_desc->client_table;
+	for (idx = 0; idx < ct->cnt; idx++) {
+		if (pvr2_hdw_load_subdev(hdw, &ct->lst[idx]) < 0) okFl = 0;
+	}
+	if (!okFl) {
+		hdw->flag_modulefail = !0;
+		pvr2_hdw_render_useless(hdw);
+	}
+}
+
+
+static void pvr2_hdw_setup_low(struct pvr2_hdw *hdw)
+{
+	int ret;
+	unsigned int idx;
+	struct pvr2_ctrl *cptr;
+	int reloadFl = 0;
+	if (hdw->hdw_desc->fx2_firmware.cnt) {
+		if (!reloadFl) {
+			reloadFl =
+				(hdw->usb_intf->cur_altsetting->desc.bNumEndpoints
+				 == 0);
+			if (reloadFl) {
+				pvr2_trace(PVR2_TRACE_INIT,
+					   "USB endpoint config looks strange; possibly firmware needs to be loaded");
+			}
+		}
+		if (!reloadFl) {
+			reloadFl = !pvr2_hdw_check_firmware(hdw);
+			if (reloadFl) {
+				pvr2_trace(PVR2_TRACE_INIT,
+					   "Check for FX2 firmware failed; possibly firmware needs to be loaded");
+			}
+		}
+		if (reloadFl) {
+			if (pvr2_upload_firmware1(hdw) != 0) {
+				pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+					   "Failure uploading firmware1");
+			}
+			return;
+		}
+	}
+	hdw->fw1_state = FW1_STATE_OK;
+
+	if (!pvr2_hdw_dev_ok(hdw)) return;
+
+	hdw->force_dirty = !0;
+
+	if (!hdw->hdw_desc->flag_no_powerup) {
+		pvr2_hdw_cmd_powerup(hdw);
+		if (!pvr2_hdw_dev_ok(hdw)) return;
+	}
+
+	/* Take the IR chip out of reset, if appropriate */
+	if (hdw->ir_scheme_active == PVR2_IR_SCHEME_ZILOG) {
+		pvr2_issue_simple_cmd(hdw,
+				      FX2CMD_HCW_ZILOG_RESET |
+				      (1 << 8) |
+				      ((0) << 16));
+	}
+
+	// This step MUST happen after the earlier powerup step.
+	pvr2_i2c_core_init(hdw);
+	if (!pvr2_hdw_dev_ok(hdw)) return;
+
+	pvr2_hdw_load_modules(hdw);
+	if (!pvr2_hdw_dev_ok(hdw)) return;
+
+	v4l2_device_call_all(&hdw->v4l2_dev, 0, core, load_fw);
+
+	for (idx = 0; idx < CTRLDEF_COUNT; idx++) {
+		cptr = hdw->controls + idx;
+		if (cptr->info->skip_init) continue;
+		if (!cptr->info->set_value) continue;
+		cptr->info->set_value(cptr,~0,cptr->info->default_value);
+	}
+
+	pvr2_hdw_cx25840_vbi_hack(hdw);
+
+	/* Set up special default values for the television and radio
+	   frequencies here.  It's not really important what these defaults
+	   are, but I set them to something usable in the Chicago area just
+	   to make driver testing a little easier. */
+
+	hdw->freqValTelevision = default_tv_freq;
+	hdw->freqValRadio = default_radio_freq;
+
+	// Do not use pvr2_reset_ctl_endpoints() here.  It is not
+	// thread-safe against the normal pvr2_send_request() mechanism.
+	// (We should make it thread safe).
+
+	if (hdw->hdw_desc->flag_has_hauppauge_rom) {
+		ret = pvr2_hdw_get_eeprom_addr(hdw);
+		if (!pvr2_hdw_dev_ok(hdw)) return;
+		if (ret < 0) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "Unable to determine location of eeprom, skipping");
+		} else {
+			hdw->eeprom_addr = ret;
+			pvr2_eeprom_analyze(hdw);
+			if (!pvr2_hdw_dev_ok(hdw)) return;
+		}
+	} else {
+		hdw->tuner_type = hdw->hdw_desc->default_tuner_type;
+		hdw->tuner_updated = !0;
+		hdw->std_mask_eeprom = V4L2_STD_ALL;
+	}
+
+	if (hdw->serial_number) {
+		idx = scnprintf(hdw->identifier, sizeof(hdw->identifier) - 1,
+				"sn-%lu", hdw->serial_number);
+	} else if (hdw->unit_number >= 0) {
+		idx = scnprintf(hdw->identifier, sizeof(hdw->identifier) - 1,
+				"unit-%c",
+				hdw->unit_number + 'a');
+	} else {
+		idx = scnprintf(hdw->identifier, sizeof(hdw->identifier) - 1,
+				"unit-??");
+	}
+	hdw->identifier[idx] = 0;
+
+	pvr2_hdw_setup_std(hdw);
+
+	if (!get_default_tuner_type(hdw)) {
+		pvr2_trace(PVR2_TRACE_INIT,
+			   "pvr2_hdw_setup: Tuner type overridden to %d",
+			   hdw->tuner_type);
+	}
+
+
+	if (!pvr2_hdw_dev_ok(hdw)) return;
+
+	if (hdw->hdw_desc->signal_routing_scheme ==
+	    PVR2_ROUTING_SCHEME_GOTVIEW) {
+		/* Ensure that GPIO 11 is set to output for GOTVIEW
+		   hardware. */
+		pvr2_hdw_gpio_chg_dir(hdw,(1 << 11),~0);
+	}
+
+	pvr2_hdw_commit_setup(hdw);
+
+	hdw->vid_stream = pvr2_stream_create();
+	if (!pvr2_hdw_dev_ok(hdw)) return;
+	pvr2_trace(PVR2_TRACE_INIT,
+		   "pvr2_hdw_setup: video stream is %p",hdw->vid_stream);
+	if (hdw->vid_stream) {
+		idx = get_default_error_tolerance(hdw);
+		if (idx) {
+			pvr2_trace(PVR2_TRACE_INIT,
+				   "pvr2_hdw_setup: video stream %p setting tolerance %u",
+				   hdw->vid_stream,idx);
+		}
+		pvr2_stream_setup(hdw->vid_stream,hdw->usb_dev,
+				  PVR2_VID_ENDPOINT,idx);
+	}
+
+	if (!pvr2_hdw_dev_ok(hdw)) return;
+
+	hdw->flag_init_ok = !0;
+
+	pvr2_hdw_state_sched(hdw);
+}
+
+
+/* Set up the structure and attempt to put the device into a usable state.
+   This can be a time-consuming operation, which is why it is not done
+   internally as part of the create() step. */
+static void pvr2_hdw_setup(struct pvr2_hdw *hdw)
+{
+	pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_setup(hdw=%p) begin",hdw);
+	do {
+		pvr2_hdw_setup_low(hdw);
+		pvr2_trace(PVR2_TRACE_INIT,
+			   "pvr2_hdw_setup(hdw=%p) done, ok=%d init_ok=%d",
+			   hdw,pvr2_hdw_dev_ok(hdw),hdw->flag_init_ok);
+		if (pvr2_hdw_dev_ok(hdw)) {
+			if (hdw->flag_init_ok) {
+				pvr2_trace(
+					PVR2_TRACE_INFO,
+					"Device initialization completed successfully.");
+				break;
+			}
+			if (hdw->fw1_state == FW1_STATE_RELOAD) {
+				pvr2_trace(
+					PVR2_TRACE_INFO,
+					"Device microcontroller firmware (re)loaded; it should now reset and reconnect.");
+				break;
+			}
+			pvr2_trace(
+				PVR2_TRACE_ERROR_LEGS,
+				"Device initialization was not successful.");
+			if (hdw->fw1_state == FW1_STATE_MISSING) {
+				pvr2_trace(
+					PVR2_TRACE_ERROR_LEGS,
+					"Giving up since device microcontroller firmware appears to be missing.");
+				break;
+			}
+		}
+		if (hdw->flag_modulefail) {
+			pvr2_trace(
+				PVR2_TRACE_ERROR_LEGS,
+				"***WARNING*** pvrusb2 driver initialization failed due to the failure of one or more sub-device kernel modules.");
+			pvr2_trace(
+				PVR2_TRACE_ERROR_LEGS,
+				"You need to resolve the failing condition before this driver can function.  There should be some earlier messages giving more information about the problem.");
+			break;
+		}
+		if (procreload) {
+			pvr2_trace(
+				PVR2_TRACE_ERROR_LEGS,
+				"Attempting pvrusb2 recovery by reloading primary firmware.");
+			pvr2_trace(
+				PVR2_TRACE_ERROR_LEGS,
+				"If this works, device should disconnect and reconnect in a sane state.");
+			hdw->fw1_state = FW1_STATE_UNKNOWN;
+			pvr2_upload_firmware1(hdw);
+		} else {
+			pvr2_trace(
+				PVR2_TRACE_ERROR_LEGS,
+				"***WARNING*** pvrusb2 device hardware appears to be jammed and I can't clear it.");
+			pvr2_trace(
+				PVR2_TRACE_ERROR_LEGS,
+				"You might need to power cycle the pvrusb2 device in order to recover.");
+		}
+	} while (0);
+	pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_setup(hdw=%p) end",hdw);
+}
+
+
+/* Perform second stage initialization.  Set callback pointer first so that
+   we can avoid a possible initialization race (if the kernel thread runs
+   before the callback has been set). */
+int pvr2_hdw_initialize(struct pvr2_hdw *hdw,
+			void (*callback_func)(void *),
+			void *callback_data)
+{
+	LOCK_TAKE(hdw->big_lock); do {
+		if (hdw->flag_disconnected) {
+			/* Handle a race here: If we're already
+			   disconnected by this point, then give up.  If we
+			   get past this then we'll remain connected for
+			   the duration of initialization since the entire
+			   initialization sequence is now protected by the
+			   big_lock. */
+			break;
+		}
+		hdw->state_data = callback_data;
+		hdw->state_func = callback_func;
+		pvr2_hdw_setup(hdw);
+	} while (0); LOCK_GIVE(hdw->big_lock);
+	return hdw->flag_init_ok;
+}
+
+
+/* Create, set up, and return a structure for interacting with the
+   underlying hardware.  */
+struct pvr2_hdw *pvr2_hdw_create(struct usb_interface *intf,
+				 const struct usb_device_id *devid)
+{
+	unsigned int idx,cnt1,cnt2,m;
+	struct pvr2_hdw *hdw = NULL;
+	int valid_std_mask;
+	struct pvr2_ctrl *cptr;
+	struct usb_device *usb_dev;
+	const struct pvr2_device_desc *hdw_desc;
+	__u8 ifnum;
+	struct v4l2_queryctrl qctrl;
+	struct pvr2_ctl_info *ciptr;
+
+	usb_dev = interface_to_usbdev(intf);
+
+	hdw_desc = (const struct pvr2_device_desc *)(devid->driver_info);
+
+	if (hdw_desc == NULL) {
+		pvr2_trace(PVR2_TRACE_INIT, "pvr2_hdw_create: No device description pointer, unable to continue.");
+		pvr2_trace(PVR2_TRACE_INIT,
+			   "If you have a new device type, please contact Mike Isely <isely@pobox.com> to get it included in the driver");
+		goto fail;
+	}
+
+	hdw = kzalloc(sizeof(*hdw),GFP_KERNEL);
+	pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_create: hdw=%p, type \"%s\"",
+		   hdw,hdw_desc->description);
+	pvr2_trace(PVR2_TRACE_INFO, "Hardware description: %s",
+		hdw_desc->description);
+	if (hdw_desc->flag_is_experimental) {
+		pvr2_trace(PVR2_TRACE_INFO, "**********");
+		pvr2_trace(PVR2_TRACE_INFO,
+			   "WARNING: Support for this device (%s) is experimental.",
+							      hdw_desc->description);
+		pvr2_trace(PVR2_TRACE_INFO,
+			   "Important functionality might not be entirely working.");
+		pvr2_trace(PVR2_TRACE_INFO,
+			   "Please consider contacting the driver author to help with further stabilization of the driver.");
+		pvr2_trace(PVR2_TRACE_INFO, "**********");
+	}
+	if (!hdw) goto fail;
+
+	timer_setup(&hdw->quiescent_timer, pvr2_hdw_quiescent_timeout, 0);
+
+	timer_setup(&hdw->decoder_stabilization_timer,
+		    pvr2_hdw_decoder_stabilization_timeout, 0);
+
+	timer_setup(&hdw->encoder_wait_timer, pvr2_hdw_encoder_wait_timeout,
+		    0);
+
+	timer_setup(&hdw->encoder_run_timer, pvr2_hdw_encoder_run_timeout, 0);
+
+	hdw->master_state = PVR2_STATE_DEAD;
+
+	init_waitqueue_head(&hdw->state_wait_data);
+
+	hdw->tuner_signal_stale = !0;
+	cx2341x_fill_defaults(&hdw->enc_ctl_state);
+
+	/* Calculate which inputs are OK */
+	m = 0;
+	if (hdw_desc->flag_has_analogtuner) m |= 1 << PVR2_CVAL_INPUT_TV;
+	if (hdw_desc->digital_control_scheme != PVR2_DIGITAL_SCHEME_NONE) {
+		m |= 1 << PVR2_CVAL_INPUT_DTV;
+	}
+	if (hdw_desc->flag_has_svideo) m |= 1 << PVR2_CVAL_INPUT_SVIDEO;
+	if (hdw_desc->flag_has_composite) m |= 1 << PVR2_CVAL_INPUT_COMPOSITE;
+	if (hdw_desc->flag_has_fmradio) m |= 1 << PVR2_CVAL_INPUT_RADIO;
+	hdw->input_avail_mask = m;
+	hdw->input_allowed_mask = hdw->input_avail_mask;
+
+	/* If not a hybrid device, pathway_state never changes.  So
+	   initialize it here to what it should forever be. */
+	if (!(hdw->input_avail_mask & (1 << PVR2_CVAL_INPUT_DTV))) {
+		hdw->pathway_state = PVR2_PATHWAY_ANALOG;
+	} else if (!(hdw->input_avail_mask & (1 << PVR2_CVAL_INPUT_TV))) {
+		hdw->pathway_state = PVR2_PATHWAY_DIGITAL;
+	}
+
+	hdw->control_cnt = CTRLDEF_COUNT;
+	hdw->control_cnt += MPEGDEF_COUNT;
+	hdw->controls = kcalloc(hdw->control_cnt, sizeof(struct pvr2_ctrl),
+				GFP_KERNEL);
+	if (!hdw->controls) goto fail;
+	hdw->hdw_desc = hdw_desc;
+	hdw->ir_scheme_active = hdw->hdw_desc->ir_scheme;
+	for (idx = 0; idx < hdw->control_cnt; idx++) {
+		cptr = hdw->controls + idx;
+		cptr->hdw = hdw;
+	}
+	for (idx = 0; idx < 32; idx++) {
+		hdw->std_mask_ptrs[idx] = hdw->std_mask_names[idx];
+	}
+	for (idx = 0; idx < CTRLDEF_COUNT; idx++) {
+		cptr = hdw->controls + idx;
+		cptr->info = control_defs+idx;
+	}
+
+	/* Ensure that default input choice is a valid one. */
+	m = hdw->input_avail_mask;
+	if (m) for (idx = 0; idx < (sizeof(m) << 3); idx++) {
+		if (!((1 << idx) & m)) continue;
+		hdw->input_val = idx;
+		break;
+	}
+
+	/* Define and configure additional controls from cx2341x module. */
+	hdw->mpeg_ctrl_info = kcalloc(MPEGDEF_COUNT,
+				      sizeof(*(hdw->mpeg_ctrl_info)),
+				      GFP_KERNEL);
+	if (!hdw->mpeg_ctrl_info) goto fail;
+	for (idx = 0; idx < MPEGDEF_COUNT; idx++) {
+		cptr = hdw->controls + idx + CTRLDEF_COUNT;
+		ciptr = &(hdw->mpeg_ctrl_info[idx].info);
+		ciptr->desc = hdw->mpeg_ctrl_info[idx].desc;
+		ciptr->name = mpeg_ids[idx].strid;
+		ciptr->v4l_id = mpeg_ids[idx].id;
+		ciptr->skip_init = !0;
+		ciptr->get_value = ctrl_cx2341x_get;
+		ciptr->get_v4lflags = ctrl_cx2341x_getv4lflags;
+		ciptr->is_dirty = ctrl_cx2341x_is_dirty;
+		if (!idx) ciptr->clear_dirty = ctrl_cx2341x_clear_dirty;
+		qctrl.id = ciptr->v4l_id;
+		cx2341x_ctrl_query(&hdw->enc_ctl_state,&qctrl);
+		if (!(qctrl.flags & V4L2_CTRL_FLAG_READ_ONLY)) {
+			ciptr->set_value = ctrl_cx2341x_set;
+		}
+		strncpy(hdw->mpeg_ctrl_info[idx].desc,qctrl.name,
+			PVR2_CTLD_INFO_DESC_SIZE);
+		hdw->mpeg_ctrl_info[idx].desc[PVR2_CTLD_INFO_DESC_SIZE-1] = 0;
+		ciptr->default_value = qctrl.default_value;
+		switch (qctrl.type) {
+		default:
+		case V4L2_CTRL_TYPE_INTEGER:
+			ciptr->type = pvr2_ctl_int;
+			ciptr->def.type_int.min_value = qctrl.minimum;
+			ciptr->def.type_int.max_value = qctrl.maximum;
+			break;
+		case V4L2_CTRL_TYPE_BOOLEAN:
+			ciptr->type = pvr2_ctl_bool;
+			break;
+		case V4L2_CTRL_TYPE_MENU:
+			ciptr->type = pvr2_ctl_enum;
+			ciptr->def.type_enum.value_names =
+				cx2341x_ctrl_get_menu(&hdw->enc_ctl_state,
+								ciptr->v4l_id);
+			for (cnt1 = 0;
+			     ciptr->def.type_enum.value_names[cnt1] != NULL;
+			     cnt1++) { }
+			ciptr->def.type_enum.count = cnt1;
+			break;
+		}
+		cptr->info = ciptr;
+	}
+
+	// Initialize control data regarding video standard masks
+	valid_std_mask = pvr2_std_get_usable();
+	for (idx = 0; idx < 32; idx++) {
+		if (!(valid_std_mask & (1 << idx))) continue;
+		cnt1 = pvr2_std_id_to_str(
+			hdw->std_mask_names[idx],
+			sizeof(hdw->std_mask_names[idx])-1,
+			1 << idx);
+		hdw->std_mask_names[idx][cnt1] = 0;
+	}
+	cptr = pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_STDAVAIL);
+	if (cptr) {
+		memcpy(&hdw->std_info_avail,cptr->info,
+		       sizeof(hdw->std_info_avail));
+		cptr->info = &hdw->std_info_avail;
+		hdw->std_info_avail.def.type_bitmask.bit_names =
+			hdw->std_mask_ptrs;
+		hdw->std_info_avail.def.type_bitmask.valid_bits =
+			valid_std_mask;
+	}
+	cptr = pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_STDCUR);
+	if (cptr) {
+		memcpy(&hdw->std_info_cur,cptr->info,
+		       sizeof(hdw->std_info_cur));
+		cptr->info = &hdw->std_info_cur;
+		hdw->std_info_cur.def.type_bitmask.bit_names =
+			hdw->std_mask_ptrs;
+		hdw->std_info_cur.def.type_bitmask.valid_bits =
+			valid_std_mask;
+	}
+	cptr = pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_STDDETECT);
+	if (cptr) {
+		memcpy(&hdw->std_info_detect,cptr->info,
+		       sizeof(hdw->std_info_detect));
+		cptr->info = &hdw->std_info_detect;
+		hdw->std_info_detect.def.type_bitmask.bit_names =
+			hdw->std_mask_ptrs;
+		hdw->std_info_detect.def.type_bitmask.valid_bits =
+			valid_std_mask;
+	}
+
+	hdw->cropcap_stale = !0;
+	hdw->eeprom_addr = -1;
+	hdw->unit_number = -1;
+	hdw->v4l_minor_number_video = -1;
+	hdw->v4l_minor_number_vbi = -1;
+	hdw->v4l_minor_number_radio = -1;
+	hdw->ctl_write_buffer = kmalloc(PVR2_CTL_BUFFSIZE,GFP_KERNEL);
+	if (!hdw->ctl_write_buffer) goto fail;
+	hdw->ctl_read_buffer = kmalloc(PVR2_CTL_BUFFSIZE,GFP_KERNEL);
+	if (!hdw->ctl_read_buffer) goto fail;
+	hdw->ctl_write_urb = usb_alloc_urb(0,GFP_KERNEL);
+	if (!hdw->ctl_write_urb) goto fail;
+	hdw->ctl_read_urb = usb_alloc_urb(0,GFP_KERNEL);
+	if (!hdw->ctl_read_urb) goto fail;
+
+	if (v4l2_device_register(&intf->dev, &hdw->v4l2_dev) != 0) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Error registering with v4l core, giving up");
+		goto fail;
+	}
+	mutex_lock(&pvr2_unit_mtx);
+	do {
+		for (idx = 0; idx < PVR_NUM; idx++) {
+			if (unit_pointers[idx]) continue;
+			hdw->unit_number = idx;
+			unit_pointers[idx] = hdw;
+			break;
+		}
+	} while (0);
+	mutex_unlock(&pvr2_unit_mtx);
+
+	cnt1 = 0;
+	cnt2 = scnprintf(hdw->name+cnt1,sizeof(hdw->name)-cnt1,"pvrusb2");
+	cnt1 += cnt2;
+	if (hdw->unit_number >= 0) {
+		cnt2 = scnprintf(hdw->name+cnt1,sizeof(hdw->name)-cnt1,"_%c",
+				 ('a' + hdw->unit_number));
+		cnt1 += cnt2;
+	}
+	if (cnt1 >= sizeof(hdw->name)) cnt1 = sizeof(hdw->name)-1;
+	hdw->name[cnt1] = 0;
+
+	INIT_WORK(&hdw->workpoll,pvr2_hdw_worker_poll);
+
+	pvr2_trace(PVR2_TRACE_INIT,"Driver unit number is %d, name is %s",
+		   hdw->unit_number,hdw->name);
+
+	hdw->tuner_type = -1;
+	hdw->flag_ok = !0;
+
+	hdw->usb_intf = intf;
+	hdw->usb_dev = usb_dev;
+
+	usb_make_path(hdw->usb_dev, hdw->bus_info, sizeof(hdw->bus_info));
+
+	ifnum = hdw->usb_intf->cur_altsetting->desc.bInterfaceNumber;
+	usb_set_interface(hdw->usb_dev,ifnum,0);
+
+	mutex_init(&hdw->ctl_lock_mutex);
+	mutex_init(&hdw->big_lock_mutex);
+
+	return hdw;
+ fail:
+	if (hdw) {
+		del_timer_sync(&hdw->quiescent_timer);
+		del_timer_sync(&hdw->decoder_stabilization_timer);
+		del_timer_sync(&hdw->encoder_run_timer);
+		del_timer_sync(&hdw->encoder_wait_timer);
+		flush_work(&hdw->workpoll);
+		usb_free_urb(hdw->ctl_read_urb);
+		usb_free_urb(hdw->ctl_write_urb);
+		kfree(hdw->ctl_read_buffer);
+		kfree(hdw->ctl_write_buffer);
+		kfree(hdw->controls);
+		kfree(hdw->mpeg_ctrl_info);
+		kfree(hdw);
+	}
+	return NULL;
+}
+
+
+/* Remove _all_ associations between this driver and the underlying USB
+   layer. */
+static void pvr2_hdw_remove_usb_stuff(struct pvr2_hdw *hdw)
+{
+	if (hdw->flag_disconnected) return;
+	pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_remove_usb_stuff: hdw=%p",hdw);
+	if (hdw->ctl_read_urb) {
+		usb_kill_urb(hdw->ctl_read_urb);
+		usb_free_urb(hdw->ctl_read_urb);
+		hdw->ctl_read_urb = NULL;
+	}
+	if (hdw->ctl_write_urb) {
+		usb_kill_urb(hdw->ctl_write_urb);
+		usb_free_urb(hdw->ctl_write_urb);
+		hdw->ctl_write_urb = NULL;
+	}
+	if (hdw->ctl_read_buffer) {
+		kfree(hdw->ctl_read_buffer);
+		hdw->ctl_read_buffer = NULL;
+	}
+	if (hdw->ctl_write_buffer) {
+		kfree(hdw->ctl_write_buffer);
+		hdw->ctl_write_buffer = NULL;
+	}
+	hdw->flag_disconnected = !0;
+	/* If we don't do this, then there will be a dangling struct device
+	   reference to our disappearing device persisting inside the V4L
+	   core... */
+	v4l2_device_disconnect(&hdw->v4l2_dev);
+	hdw->usb_dev = NULL;
+	hdw->usb_intf = NULL;
+	pvr2_hdw_render_useless(hdw);
+}
+
+void pvr2_hdw_set_v4l2_dev(struct pvr2_hdw *hdw, struct video_device *vdev)
+{
+	vdev->v4l2_dev = &hdw->v4l2_dev;
+}
+
+/* Destroy hardware interaction structure */
+void pvr2_hdw_destroy(struct pvr2_hdw *hdw)
+{
+	if (!hdw) return;
+	pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_destroy: hdw=%p",hdw);
+	flush_work(&hdw->workpoll);
+	del_timer_sync(&hdw->quiescent_timer);
+	del_timer_sync(&hdw->decoder_stabilization_timer);
+	del_timer_sync(&hdw->encoder_run_timer);
+	del_timer_sync(&hdw->encoder_wait_timer);
+	if (hdw->fw_buffer) {
+		kfree(hdw->fw_buffer);
+		hdw->fw_buffer = NULL;
+	}
+	if (hdw->vid_stream) {
+		pvr2_stream_destroy(hdw->vid_stream);
+		hdw->vid_stream = NULL;
+	}
+	pvr2_i2c_core_done(hdw);
+	v4l2_device_unregister(&hdw->v4l2_dev);
+	pvr2_hdw_remove_usb_stuff(hdw);
+	mutex_lock(&pvr2_unit_mtx);
+	do {
+		if ((hdw->unit_number >= 0) &&
+		    (hdw->unit_number < PVR_NUM) &&
+		    (unit_pointers[hdw->unit_number] == hdw)) {
+			unit_pointers[hdw->unit_number] = NULL;
+		}
+	} while (0);
+	mutex_unlock(&pvr2_unit_mtx);
+	kfree(hdw->controls);
+	kfree(hdw->mpeg_ctrl_info);
+	kfree(hdw);
+}
+
+
+int pvr2_hdw_dev_ok(struct pvr2_hdw *hdw)
+{
+	return (hdw && hdw->flag_ok);
+}
+
+
+/* Called when hardware has been unplugged */
+void pvr2_hdw_disconnect(struct pvr2_hdw *hdw)
+{
+	pvr2_trace(PVR2_TRACE_INIT,"pvr2_hdw_disconnect(hdw=%p)",hdw);
+	LOCK_TAKE(hdw->big_lock);
+	LOCK_TAKE(hdw->ctl_lock);
+	pvr2_hdw_remove_usb_stuff(hdw);
+	LOCK_GIVE(hdw->ctl_lock);
+	LOCK_GIVE(hdw->big_lock);
+}
+
+
+/* Get the number of defined controls */
+unsigned int pvr2_hdw_get_ctrl_count(struct pvr2_hdw *hdw)
+{
+	return hdw->control_cnt;
+}
+
+
+/* Retrieve a control handle given its index (0..count-1) */
+struct pvr2_ctrl *pvr2_hdw_get_ctrl_by_index(struct pvr2_hdw *hdw,
+					     unsigned int idx)
+{
+	if (idx >= hdw->control_cnt) return NULL;
+	return hdw->controls + idx;
+}
+
+
+/* Retrieve a control handle given its index (0..count-1) */
+struct pvr2_ctrl *pvr2_hdw_get_ctrl_by_id(struct pvr2_hdw *hdw,
+					  unsigned int ctl_id)
+{
+	struct pvr2_ctrl *cptr;
+	unsigned int idx;
+	int i;
+
+	/* This could be made a lot more efficient, but for now... */
+	for (idx = 0; idx < hdw->control_cnt; idx++) {
+		cptr = hdw->controls + idx;
+		i = cptr->info->internal_id;
+		if (i && (i == ctl_id)) return cptr;
+	}
+	return NULL;
+}
+
+
+/* Given a V4L ID, retrieve the control structure associated with it. */
+struct pvr2_ctrl *pvr2_hdw_get_ctrl_v4l(struct pvr2_hdw *hdw,unsigned int ctl_id)
+{
+	struct pvr2_ctrl *cptr;
+	unsigned int idx;
+	int i;
+
+	/* This could be made a lot more efficient, but for now... */
+	for (idx = 0; idx < hdw->control_cnt; idx++) {
+		cptr = hdw->controls + idx;
+		i = cptr->info->v4l_id;
+		if (i && (i == ctl_id)) return cptr;
+	}
+	return NULL;
+}
+
+
+/* Given a V4L ID for its immediate predecessor, retrieve the control
+   structure associated with it. */
+struct pvr2_ctrl *pvr2_hdw_get_ctrl_nextv4l(struct pvr2_hdw *hdw,
+					    unsigned int ctl_id)
+{
+	struct pvr2_ctrl *cptr,*cp2;
+	unsigned int idx;
+	int i;
+
+	/* This could be made a lot more efficient, but for now... */
+	cp2 = NULL;
+	for (idx = 0; idx < hdw->control_cnt; idx++) {
+		cptr = hdw->controls + idx;
+		i = cptr->info->v4l_id;
+		if (!i) continue;
+		if (i <= ctl_id) continue;
+		if (cp2 && (cp2->info->v4l_id < i)) continue;
+		cp2 = cptr;
+	}
+	return cp2;
+	return NULL;
+}
+
+
+static const char *get_ctrl_typename(enum pvr2_ctl_type tp)
+{
+	switch (tp) {
+	case pvr2_ctl_int: return "integer";
+	case pvr2_ctl_enum: return "enum";
+	case pvr2_ctl_bool: return "boolean";
+	case pvr2_ctl_bitmask: return "bitmask";
+	}
+	return "";
+}
+
+
+static void pvr2_subdev_set_control(struct pvr2_hdw *hdw, int id,
+				    const char *name, int val)
+{
+	struct v4l2_control ctrl;
+	struct v4l2_subdev *sd;
+
+	pvr2_trace(PVR2_TRACE_CHIPS, "subdev v4l2 %s=%d", name, val);
+	memset(&ctrl, 0, sizeof(ctrl));
+	ctrl.id = id;
+	ctrl.value = val;
+
+	v4l2_device_for_each_subdev(sd, &hdw->v4l2_dev)
+		v4l2_s_ctrl(NULL, sd->ctrl_handler, &ctrl);
+}
+
+#define PVR2_SUBDEV_SET_CONTROL(hdw, id, lab) \
+	if ((hdw)->lab##_dirty || (hdw)->force_dirty) {		\
+		pvr2_subdev_set_control(hdw, id, #lab, (hdw)->lab##_val); \
+	}
+
+static v4l2_std_id pvr2_hdw_get_detected_std(struct pvr2_hdw *hdw)
+{
+	v4l2_std_id std;
+	std = (v4l2_std_id)hdw->std_mask_avail;
+	v4l2_device_call_all(&hdw->v4l2_dev, 0,
+			     video, querystd, &std);
+	return std;
+}
+
+/* Execute whatever commands are required to update the state of all the
+   sub-devices so that they match our current control values. */
+static void pvr2_subdev_update(struct pvr2_hdw *hdw)
+{
+	struct v4l2_subdev *sd;
+	unsigned int id;
+	pvr2_subdev_update_func fp;
+
+	pvr2_trace(PVR2_TRACE_CHIPS, "subdev update...");
+
+	if (hdw->tuner_updated || hdw->force_dirty) {
+		struct tuner_setup setup;
+		pvr2_trace(PVR2_TRACE_CHIPS, "subdev tuner set_type(%d)",
+			   hdw->tuner_type);
+		if (((int)(hdw->tuner_type)) >= 0) {
+			memset(&setup, 0, sizeof(setup));
+			setup.addr = ADDR_UNSET;
+			setup.type = hdw->tuner_type;
+			setup.mode_mask = T_RADIO | T_ANALOG_TV;
+			v4l2_device_call_all(&hdw->v4l2_dev, 0,
+					     tuner, s_type_addr, &setup);
+		}
+	}
+
+	if (hdw->input_dirty || hdw->std_dirty || hdw->force_dirty) {
+		pvr2_trace(PVR2_TRACE_CHIPS, "subdev v4l2 set_standard");
+		if (hdw->input_val == PVR2_CVAL_INPUT_RADIO) {
+			v4l2_device_call_all(&hdw->v4l2_dev, 0,
+					     tuner, s_radio);
+		} else {
+			v4l2_std_id vs;
+			vs = hdw->std_mask_cur;
+			v4l2_device_call_all(&hdw->v4l2_dev, 0,
+					     video, s_std, vs);
+			pvr2_hdw_cx25840_vbi_hack(hdw);
+		}
+		hdw->tuner_signal_stale = !0;
+		hdw->cropcap_stale = !0;
+	}
+
+	PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_BRIGHTNESS, brightness);
+	PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_CONTRAST, contrast);
+	PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_SATURATION, saturation);
+	PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_HUE, hue);
+	PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_AUDIO_MUTE, mute);
+	PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_AUDIO_VOLUME, volume);
+	PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_AUDIO_BALANCE, balance);
+	PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_AUDIO_BASS, bass);
+	PVR2_SUBDEV_SET_CONTROL(hdw, V4L2_CID_AUDIO_TREBLE, treble);
+
+	if (hdw->input_dirty || hdw->audiomode_dirty || hdw->force_dirty) {
+		struct v4l2_tuner vt;
+		memset(&vt, 0, sizeof(vt));
+		vt.type = (hdw->input_val == PVR2_CVAL_INPUT_RADIO) ?
+			V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV;
+		vt.audmode = hdw->audiomode_val;
+		v4l2_device_call_all(&hdw->v4l2_dev, 0, tuner, s_tuner, &vt);
+	}
+
+	if (hdw->freqDirty || hdw->force_dirty) {
+		unsigned long fv;
+		struct v4l2_frequency freq;
+		fv = pvr2_hdw_get_cur_freq(hdw);
+		pvr2_trace(PVR2_TRACE_CHIPS, "subdev v4l2 set_freq(%lu)", fv);
+		if (hdw->tuner_signal_stale) pvr2_hdw_status_poll(hdw);
+		memset(&freq, 0, sizeof(freq));
+		if (hdw->tuner_signal_info.capability & V4L2_TUNER_CAP_LOW) {
+			/* ((fv * 1000) / 62500) */
+			freq.frequency = (fv * 2) / 125;
+		} else {
+			freq.frequency = fv / 62500;
+		}
+		/* tuner-core currently doesn't seem to care about this, but
+		   let's set it anyway for completeness. */
+		if (hdw->input_val == PVR2_CVAL_INPUT_RADIO) {
+			freq.type = V4L2_TUNER_RADIO;
+		} else {
+			freq.type = V4L2_TUNER_ANALOG_TV;
+		}
+		freq.tuner = 0;
+		v4l2_device_call_all(&hdw->v4l2_dev, 0, tuner,
+				     s_frequency, &freq);
+	}
+
+	if (hdw->res_hor_dirty || hdw->res_ver_dirty || hdw->force_dirty) {
+		struct v4l2_subdev_format format = {
+			.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+		};
+
+		format.format.width = hdw->res_hor_val;
+		format.format.height = hdw->res_ver_val;
+		format.format.code = MEDIA_BUS_FMT_FIXED;
+		pvr2_trace(PVR2_TRACE_CHIPS, "subdev v4l2 set_size(%dx%d)",
+			   format.format.width, format.format.height);
+		v4l2_device_call_all(&hdw->v4l2_dev, 0, pad, set_fmt,
+				     NULL, &format);
+	}
+
+	if (hdw->srate_dirty || hdw->force_dirty) {
+		u32 val;
+		pvr2_trace(PVR2_TRACE_CHIPS, "subdev v4l2 set_audio %d",
+			   hdw->srate_val);
+		switch (hdw->srate_val) {
+		default:
+		case V4L2_MPEG_AUDIO_SAMPLING_FREQ_48000:
+			val = 48000;
+			break;
+		case V4L2_MPEG_AUDIO_SAMPLING_FREQ_44100:
+			val = 44100;
+			break;
+		case V4L2_MPEG_AUDIO_SAMPLING_FREQ_32000:
+			val = 32000;
+			break;
+		}
+		v4l2_device_call_all(&hdw->v4l2_dev, 0,
+				     audio, s_clock_freq, val);
+	}
+
+	/* Unable to set crop parameters; there is apparently no equivalent
+	   for VIDIOC_S_CROP */
+
+	v4l2_device_for_each_subdev(sd, &hdw->v4l2_dev) {
+		id = sd->grp_id;
+		if (id >= ARRAY_SIZE(pvr2_module_update_functions)) continue;
+		fp = pvr2_module_update_functions[id];
+		if (!fp) continue;
+		(*fp)(hdw, sd);
+	}
+
+	if (hdw->tuner_signal_stale || hdw->cropcap_stale) {
+		pvr2_hdw_status_poll(hdw);
+	}
+}
+
+
+/* Figure out if we need to commit control changes.  If so, mark internal
+   state flags to indicate this fact and return true.  Otherwise do nothing
+   else and return false. */
+static int pvr2_hdw_commit_setup(struct pvr2_hdw *hdw)
+{
+	unsigned int idx;
+	struct pvr2_ctrl *cptr;
+	int value;
+	int commit_flag = hdw->force_dirty;
+	char buf[100];
+	unsigned int bcnt,ccnt;
+
+	for (idx = 0; idx < hdw->control_cnt; idx++) {
+		cptr = hdw->controls + idx;
+		if (!cptr->info->is_dirty) continue;
+		if (!cptr->info->is_dirty(cptr)) continue;
+		commit_flag = !0;
+
+		if (!(pvrusb2_debug & PVR2_TRACE_CTL)) continue;
+		bcnt = scnprintf(buf,sizeof(buf),"\"%s\" <-- ",
+				 cptr->info->name);
+		value = 0;
+		cptr->info->get_value(cptr,&value);
+		pvr2_ctrl_value_to_sym_internal(cptr,~0,value,
+						buf+bcnt,
+						sizeof(buf)-bcnt,&ccnt);
+		bcnt += ccnt;
+		bcnt += scnprintf(buf+bcnt,sizeof(buf)-bcnt," <%s>",
+				  get_ctrl_typename(cptr->info->type));
+		pvr2_trace(PVR2_TRACE_CTL,
+			   "/*--TRACE_COMMIT--*/ %.*s",
+			   bcnt,buf);
+	}
+
+	if (!commit_flag) {
+		/* Nothing has changed */
+		return 0;
+	}
+
+	hdw->state_pipeline_config = 0;
+	trace_stbit("state_pipeline_config",hdw->state_pipeline_config);
+	pvr2_hdw_state_sched(hdw);
+
+	return !0;
+}
+
+
+/* Perform all operations needed to commit all control changes.  This must
+   be performed in synchronization with the pipeline state and is thus
+   expected to be called as part of the driver's worker thread.  Return
+   true if commit successful, otherwise return false to indicate that
+   commit isn't possible at this time. */
+static int pvr2_hdw_commit_execute(struct pvr2_hdw *hdw)
+{
+	unsigned int idx;
+	struct pvr2_ctrl *cptr;
+	int disruptive_change;
+
+	if (hdw->input_dirty && hdw->state_pathway_ok &&
+	    (((hdw->input_val == PVR2_CVAL_INPUT_DTV) ?
+	      PVR2_PATHWAY_DIGITAL : PVR2_PATHWAY_ANALOG) !=
+	     hdw->pathway_state)) {
+		/* Change of mode being asked for... */
+		hdw->state_pathway_ok = 0;
+		trace_stbit("state_pathway_ok", hdw->state_pathway_ok);
+	}
+	if (!hdw->state_pathway_ok) {
+		/* Can't commit anything until pathway is ok. */
+		return 0;
+	}
+
+	/* Handle some required side effects when the video standard is
+	   changed.... */
+	if (hdw->std_dirty) {
+		int nvres;
+		int gop_size;
+		if (hdw->std_mask_cur & V4L2_STD_525_60) {
+			nvres = 480;
+			gop_size = 15;
+		} else {
+			nvres = 576;
+			gop_size = 12;
+		}
+		/* Rewrite the vertical resolution to be appropriate to the
+		   video standard that has been selected. */
+		if (nvres != hdw->res_ver_val) {
+			hdw->res_ver_val = nvres;
+			hdw->res_ver_dirty = !0;
+		}
+		/* Rewrite the GOP size to be appropriate to the video
+		   standard that has been selected. */
+		if (gop_size != hdw->enc_ctl_state.video_gop_size) {
+			struct v4l2_ext_controls cs;
+			struct v4l2_ext_control c1;
+			memset(&cs, 0, sizeof(cs));
+			memset(&c1, 0, sizeof(c1));
+			cs.controls = &c1;
+			cs.count = 1;
+			c1.id = V4L2_CID_MPEG_VIDEO_GOP_SIZE;
+			c1.value = gop_size;
+			cx2341x_ext_ctrls(&hdw->enc_ctl_state, 0, &cs,
+					  VIDIOC_S_EXT_CTRLS);
+		}
+	}
+
+	/* The broadcast decoder can only scale down, so if
+	 * res_*_dirty && crop window < output format ==> enlarge crop.
+	 *
+	 * The mpeg encoder receives fields of res_hor_val dots and
+	 * res_ver_val halflines.  Limits: hor<=720, ver<=576.
+	 */
+	if (hdw->res_hor_dirty && hdw->cropw_val < hdw->res_hor_val) {
+		hdw->cropw_val = hdw->res_hor_val;
+		hdw->cropw_dirty = !0;
+	} else if (hdw->cropw_dirty) {
+		hdw->res_hor_dirty = !0;           /* must rescale */
+		hdw->res_hor_val = min(720, hdw->cropw_val);
+	}
+	if (hdw->res_ver_dirty && hdw->croph_val < hdw->res_ver_val) {
+		hdw->croph_val = hdw->res_ver_val;
+		hdw->croph_dirty = !0;
+	} else if (hdw->croph_dirty) {
+		int nvres = hdw->std_mask_cur & V4L2_STD_525_60 ? 480 : 576;
+		hdw->res_ver_dirty = !0;
+		hdw->res_ver_val = min(nvres, hdw->croph_val);
+	}
+
+	/* If any of the below has changed, then we can't do the update
+	   while the pipeline is running.  Pipeline must be paused first
+	   and decoder -> encoder connection be made quiescent before we
+	   can proceed. */
+	disruptive_change =
+		(hdw->std_dirty ||
+		 hdw->enc_unsafe_stale ||
+		 hdw->srate_dirty ||
+		 hdw->res_ver_dirty ||
+		 hdw->res_hor_dirty ||
+		 hdw->cropw_dirty ||
+		 hdw->croph_dirty ||
+		 hdw->input_dirty ||
+		 (hdw->active_stream_type != hdw->desired_stream_type));
+	if (disruptive_change && !hdw->state_pipeline_idle) {
+		/* Pipeline is not idle; we can't proceed.  Arrange to
+		   cause pipeline to stop so that we can try this again
+		   later.... */
+		hdw->state_pipeline_pause = !0;
+		return 0;
+	}
+
+	if (hdw->srate_dirty) {
+		/* Write new sample rate into control structure since
+		 * the master copy is stale.  We must track srate
+		 * separate from the mpeg control structure because
+		 * other logic also uses this value. */
+		struct v4l2_ext_controls cs;
+		struct v4l2_ext_control c1;
+		memset(&cs,0,sizeof(cs));
+		memset(&c1,0,sizeof(c1));
+		cs.controls = &c1;
+		cs.count = 1;
+		c1.id = V4L2_CID_MPEG_AUDIO_SAMPLING_FREQ;
+		c1.value = hdw->srate_val;
+		cx2341x_ext_ctrls(&hdw->enc_ctl_state, 0, &cs,VIDIOC_S_EXT_CTRLS);
+	}
+
+	if (hdw->active_stream_type != hdw->desired_stream_type) {
+		/* Handle any side effects of stream config here */
+		hdw->active_stream_type = hdw->desired_stream_type;
+	}
+
+	if (hdw->hdw_desc->signal_routing_scheme ==
+	    PVR2_ROUTING_SCHEME_GOTVIEW) {
+		u32 b;
+		/* Handle GOTVIEW audio switching */
+		pvr2_hdw_gpio_get_out(hdw,&b);
+		if (hdw->input_val == PVR2_CVAL_INPUT_RADIO) {
+			/* Set GPIO 11 */
+			pvr2_hdw_gpio_chg_out(hdw,(1 << 11),~0);
+		} else {
+			/* Clear GPIO 11 */
+			pvr2_hdw_gpio_chg_out(hdw,(1 << 11),0);
+		}
+	}
+
+	/* Check and update state for all sub-devices. */
+	pvr2_subdev_update(hdw);
+
+	hdw->tuner_updated = 0;
+	hdw->force_dirty = 0;
+	for (idx = 0; idx < hdw->control_cnt; idx++) {
+		cptr = hdw->controls + idx;
+		if (!cptr->info->clear_dirty) continue;
+		cptr->info->clear_dirty(cptr);
+	}
+
+	if ((hdw->pathway_state == PVR2_PATHWAY_ANALOG) &&
+	    hdw->state_encoder_run) {
+		/* If encoder isn't running or it can't be touched, then
+		   this will get worked out later when we start the
+		   encoder. */
+		if (pvr2_encoder_adjust(hdw) < 0) return !0;
+	}
+
+	hdw->state_pipeline_config = !0;
+	/* Hardware state may have changed in a way to cause the cropping
+	   capabilities to have changed.  So mark it stale, which will
+	   cause a later re-fetch. */
+	trace_stbit("state_pipeline_config",hdw->state_pipeline_config);
+	return !0;
+}
+
+
+int pvr2_hdw_commit_ctl(struct pvr2_hdw *hdw)
+{
+	int fl;
+	LOCK_TAKE(hdw->big_lock);
+	fl = pvr2_hdw_commit_setup(hdw);
+	LOCK_GIVE(hdw->big_lock);
+	if (!fl) return 0;
+	return pvr2_hdw_wait(hdw,0);
+}
+
+
+static void pvr2_hdw_worker_poll(struct work_struct *work)
+{
+	int fl = 0;
+	struct pvr2_hdw *hdw = container_of(work,struct pvr2_hdw,workpoll);
+	LOCK_TAKE(hdw->big_lock); do {
+		fl = pvr2_hdw_state_eval(hdw);
+	} while (0); LOCK_GIVE(hdw->big_lock);
+	if (fl && hdw->state_func) {
+		hdw->state_func(hdw->state_data);
+	}
+}
+
+
+static int pvr2_hdw_wait(struct pvr2_hdw *hdw,int state)
+{
+	return wait_event_interruptible(
+		hdw->state_wait_data,
+		(hdw->state_stale == 0) &&
+		(!state || (hdw->master_state != state)));
+}
+
+
+/* Return name for this driver instance */
+const char *pvr2_hdw_get_driver_name(struct pvr2_hdw *hdw)
+{
+	return hdw->name;
+}
+
+
+const char *pvr2_hdw_get_desc(struct pvr2_hdw *hdw)
+{
+	return hdw->hdw_desc->description;
+}
+
+
+const char *pvr2_hdw_get_type(struct pvr2_hdw *hdw)
+{
+	return hdw->hdw_desc->shortname;
+}
+
+
+int pvr2_hdw_is_hsm(struct pvr2_hdw *hdw)
+{
+	int result;
+	LOCK_TAKE(hdw->ctl_lock); do {
+		hdw->cmd_buffer[0] = FX2CMD_GET_USB_SPEED;
+		result = pvr2_send_request(hdw,
+					   hdw->cmd_buffer,1,
+					   hdw->cmd_buffer,1);
+		if (result < 0) break;
+		result = (hdw->cmd_buffer[0] != 0);
+	} while(0); LOCK_GIVE(hdw->ctl_lock);
+	return result;
+}
+
+
+/* Execute poll of tuner status */
+void pvr2_hdw_execute_tuner_poll(struct pvr2_hdw *hdw)
+{
+	LOCK_TAKE(hdw->big_lock); do {
+		pvr2_hdw_status_poll(hdw);
+	} while (0); LOCK_GIVE(hdw->big_lock);
+}
+
+
+static int pvr2_hdw_check_cropcap(struct pvr2_hdw *hdw)
+{
+	if (!hdw->cropcap_stale) {
+		return 0;
+	}
+	pvr2_hdw_status_poll(hdw);
+	if (hdw->cropcap_stale) {
+		return -EIO;
+	}
+	return 0;
+}
+
+
+/* Return information about cropping capabilities */
+int pvr2_hdw_get_cropcap(struct pvr2_hdw *hdw, struct v4l2_cropcap *pp)
+{
+	int stat = 0;
+	LOCK_TAKE(hdw->big_lock);
+	stat = pvr2_hdw_check_cropcap(hdw);
+	if (!stat) {
+		memcpy(pp, &hdw->cropcap_info, sizeof(hdw->cropcap_info));
+	}
+	LOCK_GIVE(hdw->big_lock);
+	return stat;
+}
+
+
+/* Return information about the tuner */
+int pvr2_hdw_get_tuner_status(struct pvr2_hdw *hdw,struct v4l2_tuner *vtp)
+{
+	LOCK_TAKE(hdw->big_lock); do {
+		if (hdw->tuner_signal_stale) {
+			pvr2_hdw_status_poll(hdw);
+		}
+		memcpy(vtp,&hdw->tuner_signal_info,sizeof(struct v4l2_tuner));
+	} while (0); LOCK_GIVE(hdw->big_lock);
+	return 0;
+}
+
+
+/* Get handle to video output stream */
+struct pvr2_stream *pvr2_hdw_get_video_stream(struct pvr2_hdw *hp)
+{
+	return hp->vid_stream;
+}
+
+
+void pvr2_hdw_trigger_module_log(struct pvr2_hdw *hdw)
+{
+	int nr = pvr2_hdw_get_unit_number(hdw);
+	LOCK_TAKE(hdw->big_lock);
+	do {
+		printk(KERN_INFO "pvrusb2: =================  START STATUS CARD #%d  =================\n", nr);
+		v4l2_device_call_all(&hdw->v4l2_dev, 0, core, log_status);
+		pvr2_trace(PVR2_TRACE_INFO,"cx2341x config:");
+		cx2341x_log_status(&hdw->enc_ctl_state, "pvrusb2");
+		pvr2_hdw_state_log_state(hdw);
+		printk(KERN_INFO "pvrusb2: ==================  END STATUS CARD #%d  ==================\n", nr);
+	} while (0);
+	LOCK_GIVE(hdw->big_lock);
+}
+
+
+/* Grab EEPROM contents, needed for direct method. */
+#define EEPROM_SIZE 8192
+#define trace_eeprom(...) pvr2_trace(PVR2_TRACE_EEPROM,__VA_ARGS__)
+static u8 *pvr2_full_eeprom_fetch(struct pvr2_hdw *hdw)
+{
+	struct i2c_msg msg[2];
+	u8 *eeprom;
+	u8 iadd[2];
+	u8 addr;
+	u16 eepromSize;
+	unsigned int offs;
+	int ret;
+	int mode16 = 0;
+	unsigned pcnt,tcnt;
+	eeprom = kmalloc(EEPROM_SIZE,GFP_KERNEL);
+	if (!eeprom) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Failed to allocate memory required to read eeprom");
+		return NULL;
+	}
+
+	trace_eeprom("Value for eeprom addr from controller was 0x%x",
+		     hdw->eeprom_addr);
+	addr = hdw->eeprom_addr;
+	/* Seems that if the high bit is set, then the *real* eeprom
+	   address is shifted right now bit position (noticed this in
+	   newer PVR USB2 hardware) */
+	if (addr & 0x80) addr >>= 1;
+
+	/* FX2 documentation states that a 16bit-addressed eeprom is
+	   expected if the I2C address is an odd number (yeah, this is
+	   strange but it's what they do) */
+	mode16 = (addr & 1);
+	eepromSize = (mode16 ? EEPROM_SIZE : 256);
+	trace_eeprom("Examining %d byte eeprom at location 0x%x using %d bit addressing",
+		     eepromSize, addr,
+		     mode16 ? 16 : 8);
+
+	msg[0].addr = addr;
+	msg[0].flags = 0;
+	msg[0].len = mode16 ? 2 : 1;
+	msg[0].buf = iadd;
+	msg[1].addr = addr;
+	msg[1].flags = I2C_M_RD;
+
+	/* We have to do the actual eeprom data fetch ourselves, because
+	   (1) we're only fetching part of the eeprom, and (2) if we were
+	   getting the whole thing our I2C driver can't grab it in one
+	   pass - which is what tveeprom is otherwise going to attempt */
+	memset(eeprom,0,EEPROM_SIZE);
+	for (tcnt = 0; tcnt < EEPROM_SIZE; tcnt += pcnt) {
+		pcnt = 16;
+		if (pcnt + tcnt > EEPROM_SIZE) pcnt = EEPROM_SIZE-tcnt;
+		offs = tcnt + (eepromSize - EEPROM_SIZE);
+		if (mode16) {
+			iadd[0] = offs >> 8;
+			iadd[1] = offs;
+		} else {
+			iadd[0] = offs;
+		}
+		msg[1].len = pcnt;
+		msg[1].buf = eeprom+tcnt;
+		if ((ret = i2c_transfer(&hdw->i2c_adap,
+					msg,ARRAY_SIZE(msg))) != 2) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "eeprom fetch set offs err=%d",ret);
+			kfree(eeprom);
+			return NULL;
+		}
+	}
+	return eeprom;
+}
+
+
+void pvr2_hdw_cpufw_set_enabled(struct pvr2_hdw *hdw,
+				int mode,
+				int enable_flag)
+{
+	int ret;
+	u16 address;
+	unsigned int pipe;
+	LOCK_TAKE(hdw->big_lock); do {
+		if ((hdw->fw_buffer == NULL) == !enable_flag) break;
+
+		if (!enable_flag) {
+			pvr2_trace(PVR2_TRACE_FIRMWARE,
+				   "Cleaning up after CPU firmware fetch");
+			kfree(hdw->fw_buffer);
+			hdw->fw_buffer = NULL;
+			hdw->fw_size = 0;
+			if (hdw->fw_cpu_flag) {
+				/* Now release the CPU.  It will disconnect
+				   and reconnect later. */
+				pvr2_hdw_cpureset_assert(hdw,0);
+			}
+			break;
+		}
+
+		hdw->fw_cpu_flag = (mode != 2);
+		if (hdw->fw_cpu_flag) {
+			hdw->fw_size = (mode == 1) ? 0x4000 : 0x2000;
+			pvr2_trace(PVR2_TRACE_FIRMWARE,
+				   "Preparing to suck out CPU firmware (size=%u)",
+				   hdw->fw_size);
+			hdw->fw_buffer = kzalloc(hdw->fw_size,GFP_KERNEL);
+			if (!hdw->fw_buffer) {
+				hdw->fw_size = 0;
+				break;
+			}
+
+			/* We have to hold the CPU during firmware upload. */
+			pvr2_hdw_cpureset_assert(hdw,1);
+
+			/* download the firmware from address 0000-1fff in 2048
+			   (=0x800) bytes chunk. */
+
+			pvr2_trace(PVR2_TRACE_FIRMWARE,
+				   "Grabbing CPU firmware");
+			pipe = usb_rcvctrlpipe(hdw->usb_dev, 0);
+			for(address = 0; address < hdw->fw_size;
+			    address += 0x800) {
+				ret = usb_control_msg(hdw->usb_dev,pipe,
+						      0xa0,0xc0,
+						      address,0,
+						      hdw->fw_buffer+address,
+						      0x800,HZ);
+				if (ret < 0) break;
+			}
+
+			pvr2_trace(PVR2_TRACE_FIRMWARE,
+				   "Done grabbing CPU firmware");
+		} else {
+			pvr2_trace(PVR2_TRACE_FIRMWARE,
+				   "Sucking down EEPROM contents");
+			hdw->fw_buffer = pvr2_full_eeprom_fetch(hdw);
+			if (!hdw->fw_buffer) {
+				pvr2_trace(PVR2_TRACE_FIRMWARE,
+					   "EEPROM content suck failed.");
+				break;
+			}
+			hdw->fw_size = EEPROM_SIZE;
+			pvr2_trace(PVR2_TRACE_FIRMWARE,
+				   "Done sucking down EEPROM contents");
+		}
+
+	} while (0); LOCK_GIVE(hdw->big_lock);
+}
+
+
+/* Return true if we're in a mode for retrieval CPU firmware */
+int pvr2_hdw_cpufw_get_enabled(struct pvr2_hdw *hdw)
+{
+	return hdw->fw_buffer != NULL;
+}
+
+
+int pvr2_hdw_cpufw_get(struct pvr2_hdw *hdw,unsigned int offs,
+		       char *buf,unsigned int cnt)
+{
+	int ret = -EINVAL;
+	LOCK_TAKE(hdw->big_lock); do {
+		if (!buf) break;
+		if (!cnt) break;
+
+		if (!hdw->fw_buffer) {
+			ret = -EIO;
+			break;
+		}
+
+		if (offs >= hdw->fw_size) {
+			pvr2_trace(PVR2_TRACE_FIRMWARE,
+				   "Read firmware data offs=%d EOF",
+				   offs);
+			ret = 0;
+			break;
+		}
+
+		if (offs + cnt > hdw->fw_size) cnt = hdw->fw_size - offs;
+
+		memcpy(buf,hdw->fw_buffer+offs,cnt);
+
+		pvr2_trace(PVR2_TRACE_FIRMWARE,
+			   "Read firmware data offs=%d cnt=%d",
+			   offs,cnt);
+		ret = cnt;
+	} while (0); LOCK_GIVE(hdw->big_lock);
+
+	return ret;
+}
+
+
+int pvr2_hdw_v4l_get_minor_number(struct pvr2_hdw *hdw,
+				  enum pvr2_v4l_type index)
+{
+	switch (index) {
+	case pvr2_v4l_type_video: return hdw->v4l_minor_number_video;
+	case pvr2_v4l_type_vbi: return hdw->v4l_minor_number_vbi;
+	case pvr2_v4l_type_radio: return hdw->v4l_minor_number_radio;
+	default: return -1;
+	}
+}
+
+
+/* Store a v4l minor device number */
+void pvr2_hdw_v4l_store_minor_number(struct pvr2_hdw *hdw,
+				     enum pvr2_v4l_type index,int v)
+{
+	switch (index) {
+	case pvr2_v4l_type_video: hdw->v4l_minor_number_video = v;break;
+	case pvr2_v4l_type_vbi: hdw->v4l_minor_number_vbi = v;break;
+	case pvr2_v4l_type_radio: hdw->v4l_minor_number_radio = v;break;
+	default: break;
+	}
+}
+
+
+static void pvr2_ctl_write_complete(struct urb *urb)
+{
+	struct pvr2_hdw *hdw = urb->context;
+	hdw->ctl_write_pend_flag = 0;
+	if (hdw->ctl_read_pend_flag) return;
+	complete(&hdw->ctl_done);
+}
+
+
+static void pvr2_ctl_read_complete(struct urb *urb)
+{
+	struct pvr2_hdw *hdw = urb->context;
+	hdw->ctl_read_pend_flag = 0;
+	if (hdw->ctl_write_pend_flag) return;
+	complete(&hdw->ctl_done);
+}
+
+struct hdw_timer {
+	struct timer_list timer;
+	struct pvr2_hdw *hdw;
+};
+
+static void pvr2_ctl_timeout(struct timer_list *t)
+{
+	struct hdw_timer *timer = from_timer(timer, t, timer);
+	struct pvr2_hdw *hdw = timer->hdw;
+
+	if (hdw->ctl_write_pend_flag || hdw->ctl_read_pend_flag) {
+		hdw->ctl_timeout_flag = !0;
+		if (hdw->ctl_write_pend_flag)
+			usb_unlink_urb(hdw->ctl_write_urb);
+		if (hdw->ctl_read_pend_flag)
+			usb_unlink_urb(hdw->ctl_read_urb);
+	}
+}
+
+
+/* Issue a command and get a response from the device.  This extended
+   version includes a probe flag (which if set means that device errors
+   should not be logged or treated as fatal) and a timeout in jiffies.
+   This can be used to non-lethally probe the health of endpoint 1. */
+static int pvr2_send_request_ex(struct pvr2_hdw *hdw,
+				unsigned int timeout,int probe_fl,
+				void *write_data,unsigned int write_len,
+				void *read_data,unsigned int read_len)
+{
+	unsigned int idx;
+	int status = 0;
+	struct hdw_timer timer = {
+		.hdw = hdw,
+	};
+
+	if (!hdw->ctl_lock_held) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Attempted to execute control transfer without lock!!");
+		return -EDEADLK;
+	}
+	if (!hdw->flag_ok && !probe_fl) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Attempted to execute control transfer when device not ok");
+		return -EIO;
+	}
+	if (!(hdw->ctl_read_urb && hdw->ctl_write_urb)) {
+		if (!probe_fl) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "Attempted to execute control transfer when USB is disconnected");
+		}
+		return -ENOTTY;
+	}
+
+	/* Ensure that we have sane parameters */
+	if (!write_data) write_len = 0;
+	if (!read_data) read_len = 0;
+	if (write_len > PVR2_CTL_BUFFSIZE) {
+		pvr2_trace(
+			PVR2_TRACE_ERROR_LEGS,
+			"Attempted to execute %d byte control-write transfer (limit=%d)",
+			write_len,PVR2_CTL_BUFFSIZE);
+		return -EINVAL;
+	}
+	if (read_len > PVR2_CTL_BUFFSIZE) {
+		pvr2_trace(
+			PVR2_TRACE_ERROR_LEGS,
+			"Attempted to execute %d byte control-read transfer (limit=%d)",
+			write_len,PVR2_CTL_BUFFSIZE);
+		return -EINVAL;
+	}
+	if ((!write_len) && (!read_len)) {
+		pvr2_trace(
+			PVR2_TRACE_ERROR_LEGS,
+			"Attempted to execute null control transfer?");
+		return -EINVAL;
+	}
+
+
+	hdw->cmd_debug_state = 1;
+	if (write_len && write_data)
+		hdw->cmd_debug_code = ((unsigned char *)write_data)[0];
+	else
+		hdw->cmd_debug_code = 0;
+	hdw->cmd_debug_write_len = write_len;
+	hdw->cmd_debug_read_len = read_len;
+
+	/* Initialize common stuff */
+	init_completion(&hdw->ctl_done);
+	hdw->ctl_timeout_flag = 0;
+	hdw->ctl_write_pend_flag = 0;
+	hdw->ctl_read_pend_flag = 0;
+	timer_setup_on_stack(&timer.timer, pvr2_ctl_timeout, 0);
+	timer.timer.expires = jiffies + timeout;
+
+	if (write_len && write_data) {
+		hdw->cmd_debug_state = 2;
+		/* Transfer write data to internal buffer */
+		for (idx = 0; idx < write_len; idx++) {
+			hdw->ctl_write_buffer[idx] =
+				((unsigned char *)write_data)[idx];
+		}
+		/* Initiate a write request */
+		usb_fill_bulk_urb(hdw->ctl_write_urb,
+				  hdw->usb_dev,
+				  usb_sndbulkpipe(hdw->usb_dev,
+						  PVR2_CTL_WRITE_ENDPOINT),
+				  hdw->ctl_write_buffer,
+				  write_len,
+				  pvr2_ctl_write_complete,
+				  hdw);
+		hdw->ctl_write_urb->actual_length = 0;
+		hdw->ctl_write_pend_flag = !0;
+		if (usb_urb_ep_type_check(hdw->ctl_write_urb)) {
+			pvr2_trace(
+				PVR2_TRACE_ERROR_LEGS,
+				"Invalid write control endpoint");
+			return -EINVAL;
+		}
+		status = usb_submit_urb(hdw->ctl_write_urb,GFP_KERNEL);
+		if (status < 0) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "Failed to submit write-control URB status=%d",
+status);
+			hdw->ctl_write_pend_flag = 0;
+			goto done;
+		}
+	}
+
+	if (read_len) {
+		hdw->cmd_debug_state = 3;
+		memset(hdw->ctl_read_buffer,0x43,read_len);
+		/* Initiate a read request */
+		usb_fill_bulk_urb(hdw->ctl_read_urb,
+				  hdw->usb_dev,
+				  usb_rcvbulkpipe(hdw->usb_dev,
+						  PVR2_CTL_READ_ENDPOINT),
+				  hdw->ctl_read_buffer,
+				  read_len,
+				  pvr2_ctl_read_complete,
+				  hdw);
+		hdw->ctl_read_urb->actual_length = 0;
+		hdw->ctl_read_pend_flag = !0;
+		if (usb_urb_ep_type_check(hdw->ctl_read_urb)) {
+			pvr2_trace(
+				PVR2_TRACE_ERROR_LEGS,
+				"Invalid read control endpoint");
+			return -EINVAL;
+		}
+		status = usb_submit_urb(hdw->ctl_read_urb,GFP_KERNEL);
+		if (status < 0) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "Failed to submit read-control URB status=%d",
+status);
+			hdw->ctl_read_pend_flag = 0;
+			goto done;
+		}
+	}
+
+	/* Start timer */
+	add_timer(&timer.timer);
+
+	/* Now wait for all I/O to complete */
+	hdw->cmd_debug_state = 4;
+	while (hdw->ctl_write_pend_flag || hdw->ctl_read_pend_flag) {
+		wait_for_completion(&hdw->ctl_done);
+	}
+	hdw->cmd_debug_state = 5;
+
+	/* Stop timer */
+	del_timer_sync(&timer.timer);
+
+	hdw->cmd_debug_state = 6;
+	status = 0;
+
+	if (hdw->ctl_timeout_flag) {
+		status = -ETIMEDOUT;
+		if (!probe_fl) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "Timed out control-write");
+		}
+		goto done;
+	}
+
+	if (write_len) {
+		/* Validate results of write request */
+		if ((hdw->ctl_write_urb->status != 0) &&
+		    (hdw->ctl_write_urb->status != -ENOENT) &&
+		    (hdw->ctl_write_urb->status != -ESHUTDOWN) &&
+		    (hdw->ctl_write_urb->status != -ECONNRESET)) {
+			/* USB subsystem is reporting some kind of failure
+			   on the write */
+			status = hdw->ctl_write_urb->status;
+			if (!probe_fl) {
+				pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+					   "control-write URB failure, status=%d",
+					   status);
+			}
+			goto done;
+		}
+		if (hdw->ctl_write_urb->actual_length < write_len) {
+			/* Failed to write enough data */
+			status = -EIO;
+			if (!probe_fl) {
+				pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+					   "control-write URB short, expected=%d got=%d",
+					   write_len,
+					   hdw->ctl_write_urb->actual_length);
+			}
+			goto done;
+		}
+	}
+	if (read_len && read_data) {
+		/* Validate results of read request */
+		if ((hdw->ctl_read_urb->status != 0) &&
+		    (hdw->ctl_read_urb->status != -ENOENT) &&
+		    (hdw->ctl_read_urb->status != -ESHUTDOWN) &&
+		    (hdw->ctl_read_urb->status != -ECONNRESET)) {
+			/* USB subsystem is reporting some kind of failure
+			   on the read */
+			status = hdw->ctl_read_urb->status;
+			if (!probe_fl) {
+				pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+					   "control-read URB failure, status=%d",
+					   status);
+			}
+			goto done;
+		}
+		if (hdw->ctl_read_urb->actual_length < read_len) {
+			/* Failed to read enough data */
+			status = -EIO;
+			if (!probe_fl) {
+				pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+					   "control-read URB short, expected=%d got=%d",
+					   read_len,
+					   hdw->ctl_read_urb->actual_length);
+			}
+			goto done;
+		}
+		/* Transfer retrieved data out from internal buffer */
+		for (idx = 0; idx < read_len; idx++) {
+			((unsigned char *)read_data)[idx] =
+				hdw->ctl_read_buffer[idx];
+		}
+	}
+
+ done:
+
+	hdw->cmd_debug_state = 0;
+	if ((status < 0) && (!probe_fl)) {
+		pvr2_hdw_render_useless(hdw);
+	}
+	destroy_timer_on_stack(&timer.timer);
+
+	return status;
+}
+
+
+int pvr2_send_request(struct pvr2_hdw *hdw,
+		      void *write_data,unsigned int write_len,
+		      void *read_data,unsigned int read_len)
+{
+	return pvr2_send_request_ex(hdw,HZ*4,0,
+				    write_data,write_len,
+				    read_data,read_len);
+}
+
+
+static int pvr2_issue_simple_cmd(struct pvr2_hdw *hdw,u32 cmdcode)
+{
+	int ret;
+	unsigned int cnt = 1;
+	unsigned int args = 0;
+	LOCK_TAKE(hdw->ctl_lock);
+	hdw->cmd_buffer[0] = cmdcode & 0xffu;
+	args = (cmdcode >> 8) & 0xffu;
+	args = (args > 2) ? 2 : args;
+	if (args) {
+		cnt += args;
+		hdw->cmd_buffer[1] = (cmdcode >> 16) & 0xffu;
+		if (args > 1) {
+			hdw->cmd_buffer[2] = (cmdcode >> 24) & 0xffu;
+		}
+	}
+	if (pvrusb2_debug & PVR2_TRACE_INIT) {
+		unsigned int idx;
+		unsigned int ccnt,bcnt;
+		char tbuf[50];
+		cmdcode &= 0xffu;
+		bcnt = 0;
+		ccnt = scnprintf(tbuf+bcnt,
+				 sizeof(tbuf)-bcnt,
+				 "Sending FX2 command 0x%x",cmdcode);
+		bcnt += ccnt;
+		for (idx = 0; idx < ARRAY_SIZE(pvr2_fx2cmd_desc); idx++) {
+			if (pvr2_fx2cmd_desc[idx].id == cmdcode) {
+				ccnt = scnprintf(tbuf+bcnt,
+						 sizeof(tbuf)-bcnt,
+						 " \"%s\"",
+						 pvr2_fx2cmd_desc[idx].desc);
+				bcnt += ccnt;
+				break;
+			}
+		}
+		if (args) {
+			ccnt = scnprintf(tbuf+bcnt,
+					 sizeof(tbuf)-bcnt,
+					 " (%u",hdw->cmd_buffer[1]);
+			bcnt += ccnt;
+			if (args > 1) {
+				ccnt = scnprintf(tbuf+bcnt,
+						 sizeof(tbuf)-bcnt,
+						 ",%u",hdw->cmd_buffer[2]);
+				bcnt += ccnt;
+			}
+			ccnt = scnprintf(tbuf+bcnt,
+					 sizeof(tbuf)-bcnt,
+					 ")");
+			bcnt += ccnt;
+		}
+		pvr2_trace(PVR2_TRACE_INIT,"%.*s",bcnt,tbuf);
+	}
+	ret = pvr2_send_request(hdw,hdw->cmd_buffer,cnt,NULL,0);
+	LOCK_GIVE(hdw->ctl_lock);
+	return ret;
+}
+
+
+int pvr2_write_register(struct pvr2_hdw *hdw, u16 reg, u32 data)
+{
+	int ret;
+
+	LOCK_TAKE(hdw->ctl_lock);
+
+	hdw->cmd_buffer[0] = FX2CMD_REG_WRITE;  /* write register prefix */
+	PVR2_DECOMPOSE_LE(hdw->cmd_buffer,1,data);
+	hdw->cmd_buffer[5] = 0;
+	hdw->cmd_buffer[6] = (reg >> 8) & 0xff;
+	hdw->cmd_buffer[7] = reg & 0xff;
+
+
+	ret = pvr2_send_request(hdw, hdw->cmd_buffer, 8, hdw->cmd_buffer, 0);
+
+	LOCK_GIVE(hdw->ctl_lock);
+
+	return ret;
+}
+
+
+static int pvr2_read_register(struct pvr2_hdw *hdw, u16 reg, u32 *data)
+{
+	int ret = 0;
+
+	LOCK_TAKE(hdw->ctl_lock);
+
+	hdw->cmd_buffer[0] = FX2CMD_REG_READ;  /* read register prefix */
+	hdw->cmd_buffer[1] = 0;
+	hdw->cmd_buffer[2] = 0;
+	hdw->cmd_buffer[3] = 0;
+	hdw->cmd_buffer[4] = 0;
+	hdw->cmd_buffer[5] = 0;
+	hdw->cmd_buffer[6] = (reg >> 8) & 0xff;
+	hdw->cmd_buffer[7] = reg & 0xff;
+
+	ret |= pvr2_send_request(hdw, hdw->cmd_buffer, 8, hdw->cmd_buffer, 4);
+	*data = PVR2_COMPOSE_LE(hdw->cmd_buffer,0);
+
+	LOCK_GIVE(hdw->ctl_lock);
+
+	return ret;
+}
+
+
+void pvr2_hdw_render_useless(struct pvr2_hdw *hdw)
+{
+	if (!hdw->flag_ok) return;
+	pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+		   "Device being rendered inoperable");
+	if (hdw->vid_stream) {
+		pvr2_stream_setup(hdw->vid_stream,NULL,0,0);
+	}
+	hdw->flag_ok = 0;
+	trace_stbit("flag_ok",hdw->flag_ok);
+	pvr2_hdw_state_sched(hdw);
+}
+
+
+void pvr2_hdw_device_reset(struct pvr2_hdw *hdw)
+{
+	int ret;
+	pvr2_trace(PVR2_TRACE_INIT,"Performing a device reset...");
+	ret = usb_lock_device_for_reset(hdw->usb_dev,NULL);
+	if (ret == 0) {
+		ret = usb_reset_device(hdw->usb_dev);
+		usb_unlock_device(hdw->usb_dev);
+	} else {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Failed to lock USB device ret=%d",ret);
+	}
+	if (init_pause_msec) {
+		pvr2_trace(PVR2_TRACE_INFO,
+			   "Waiting %u msec for hardware to settle",
+			   init_pause_msec);
+		msleep(init_pause_msec);
+	}
+
+}
+
+
+void pvr2_hdw_cpureset_assert(struct pvr2_hdw *hdw,int val)
+{
+	char *da;
+	unsigned int pipe;
+	int ret;
+
+	if (!hdw->usb_dev) return;
+
+	da = kmalloc(16, GFP_KERNEL);
+
+	if (da == NULL) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Unable to allocate memory to control CPU reset");
+		return;
+	}
+
+	pvr2_trace(PVR2_TRACE_INIT,"cpureset_assert(%d)",val);
+
+	da[0] = val ? 0x01 : 0x00;
+
+	/* Write the CPUCS register on the 8051.  The lsb of the register
+	   is the reset bit; a 1 asserts reset while a 0 clears it. */
+	pipe = usb_sndctrlpipe(hdw->usb_dev, 0);
+	ret = usb_control_msg(hdw->usb_dev,pipe,0xa0,0x40,0xe600,0,da,1,HZ);
+	if (ret < 0) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "cpureset_assert(%d) error=%d",val,ret);
+		pvr2_hdw_render_useless(hdw);
+	}
+
+	kfree(da);
+}
+
+
+int pvr2_hdw_cmd_deep_reset(struct pvr2_hdw *hdw)
+{
+	return pvr2_issue_simple_cmd(hdw,FX2CMD_DEEP_RESET);
+}
+
+
+int pvr2_hdw_cmd_powerup(struct pvr2_hdw *hdw)
+{
+	return pvr2_issue_simple_cmd(hdw,FX2CMD_POWER_ON);
+}
+
+
+
+int pvr2_hdw_cmd_decoder_reset(struct pvr2_hdw *hdw)
+{
+	pvr2_trace(PVR2_TRACE_INIT,
+		   "Requesting decoder reset");
+	if (hdw->decoder_client_id) {
+		v4l2_device_call_all(&hdw->v4l2_dev, hdw->decoder_client_id,
+				     core, reset, 0);
+		pvr2_hdw_cx25840_vbi_hack(hdw);
+		return 0;
+	}
+	pvr2_trace(PVR2_TRACE_INIT,
+		   "Unable to reset decoder: nothing attached");
+	return -ENOTTY;
+}
+
+
+static int pvr2_hdw_cmd_hcw_demod_reset(struct pvr2_hdw *hdw, int onoff)
+{
+	hdw->flag_ok = !0;
+	return pvr2_issue_simple_cmd(hdw,
+				     FX2CMD_HCW_DEMOD_RESETIN |
+				     (1 << 8) |
+				     ((onoff ? 1 : 0) << 16));
+}
+
+
+static int pvr2_hdw_cmd_onair_fe_power_ctrl(struct pvr2_hdw *hdw, int onoff)
+{
+	hdw->flag_ok = !0;
+	return pvr2_issue_simple_cmd(hdw,(onoff ?
+					  FX2CMD_ONAIR_DTV_POWER_ON :
+					  FX2CMD_ONAIR_DTV_POWER_OFF));
+}
+
+
+static int pvr2_hdw_cmd_onair_digital_path_ctrl(struct pvr2_hdw *hdw,
+						int onoff)
+{
+	return pvr2_issue_simple_cmd(hdw,(onoff ?
+					  FX2CMD_ONAIR_DTV_STREAMING_ON :
+					  FX2CMD_ONAIR_DTV_STREAMING_OFF));
+}
+
+
+static void pvr2_hdw_cmd_modeswitch(struct pvr2_hdw *hdw,int digitalFl)
+{
+	int cmode;
+	/* Compare digital/analog desired setting with current setting.  If
+	   they don't match, fix it... */
+	cmode = (digitalFl ? PVR2_PATHWAY_DIGITAL : PVR2_PATHWAY_ANALOG);
+	if (cmode == hdw->pathway_state) {
+		/* They match; nothing to do */
+		return;
+	}
+
+	switch (hdw->hdw_desc->digital_control_scheme) {
+	case PVR2_DIGITAL_SCHEME_HAUPPAUGE:
+		pvr2_hdw_cmd_hcw_demod_reset(hdw,digitalFl);
+		if (cmode == PVR2_PATHWAY_ANALOG) {
+			/* If moving to analog mode, also force the decoder
+			   to reset.  If no decoder is attached, then it's
+			   ok to ignore this because if/when the decoder
+			   attaches, it will reset itself at that time. */
+			pvr2_hdw_cmd_decoder_reset(hdw);
+		}
+		break;
+	case PVR2_DIGITAL_SCHEME_ONAIR:
+		/* Supposedly we should always have the power on whether in
+		   digital or analog mode.  But for now do what appears to
+		   work... */
+		pvr2_hdw_cmd_onair_fe_power_ctrl(hdw,digitalFl);
+		break;
+	default: break;
+	}
+
+	pvr2_hdw_untrip_unlocked(hdw);
+	hdw->pathway_state = cmode;
+}
+
+
+static void pvr2_led_ctrl_hauppauge(struct pvr2_hdw *hdw, int onoff)
+{
+	/* change some GPIO data
+	 *
+	 * note: bit d7 of dir appears to control the LED,
+	 * so we shut it off here.
+	 *
+	 */
+	if (onoff) {
+		pvr2_hdw_gpio_chg_dir(hdw, 0xffffffff, 0x00000481);
+	} else {
+		pvr2_hdw_gpio_chg_dir(hdw, 0xffffffff, 0x00000401);
+	}
+	pvr2_hdw_gpio_chg_out(hdw, 0xffffffff, 0x00000000);
+}
+
+
+typedef void (*led_method_func)(struct pvr2_hdw *,int);
+
+static led_method_func led_methods[] = {
+	[PVR2_LED_SCHEME_HAUPPAUGE] = pvr2_led_ctrl_hauppauge,
+};
+
+
+/* Toggle LED */
+static void pvr2_led_ctrl(struct pvr2_hdw *hdw,int onoff)
+{
+	unsigned int scheme_id;
+	led_method_func fp;
+
+	if ((!onoff) == (!hdw->led_on)) return;
+
+	hdw->led_on = onoff != 0;
+
+	scheme_id = hdw->hdw_desc->led_scheme;
+	if (scheme_id < ARRAY_SIZE(led_methods)) {
+		fp = led_methods[scheme_id];
+	} else {
+		fp = NULL;
+	}
+
+	if (fp) (*fp)(hdw,onoff);
+}
+
+
+/* Stop / start video stream transport */
+static int pvr2_hdw_cmd_usbstream(struct pvr2_hdw *hdw,int runFl)
+{
+	int ret;
+
+	/* If we're in analog mode, then just issue the usual analog
+	   command. */
+	if (hdw->pathway_state == PVR2_PATHWAY_ANALOG) {
+		return pvr2_issue_simple_cmd(hdw,
+					     (runFl ?
+					      FX2CMD_STREAMING_ON :
+					      FX2CMD_STREAMING_OFF));
+		/*Note: Not reached */
+	}
+
+	if (hdw->pathway_state != PVR2_PATHWAY_DIGITAL) {
+		/* Whoops, we don't know what mode we're in... */
+		return -EINVAL;
+	}
+
+	/* To get here we have to be in digital mode.  The mechanism here
+	   is unfortunately different for different vendors.  So we switch
+	   on the device's digital scheme attribute in order to figure out
+	   what to do. */
+	switch (hdw->hdw_desc->digital_control_scheme) {
+	case PVR2_DIGITAL_SCHEME_HAUPPAUGE:
+		return pvr2_issue_simple_cmd(hdw,
+					     (runFl ?
+					      FX2CMD_HCW_DTV_STREAMING_ON :
+					      FX2CMD_HCW_DTV_STREAMING_OFF));
+	case PVR2_DIGITAL_SCHEME_ONAIR:
+		ret = pvr2_issue_simple_cmd(hdw,
+					    (runFl ?
+					     FX2CMD_STREAMING_ON :
+					     FX2CMD_STREAMING_OFF));
+		if (ret) return ret;
+		return pvr2_hdw_cmd_onair_digital_path_ctrl(hdw,runFl);
+	default:
+		return -EINVAL;
+	}
+}
+
+
+/* Evaluate whether or not state_pathway_ok can change */
+static int state_eval_pathway_ok(struct pvr2_hdw *hdw)
+{
+	if (hdw->state_pathway_ok) {
+		/* Nothing to do if pathway is already ok */
+		return 0;
+	}
+	if (!hdw->state_pipeline_idle) {
+		/* Not allowed to change anything if pipeline is not idle */
+		return 0;
+	}
+	pvr2_hdw_cmd_modeswitch(hdw,hdw->input_val == PVR2_CVAL_INPUT_DTV);
+	hdw->state_pathway_ok = !0;
+	trace_stbit("state_pathway_ok",hdw->state_pathway_ok);
+	return !0;
+}
+
+
+/* Evaluate whether or not state_encoder_ok can change */
+static int state_eval_encoder_ok(struct pvr2_hdw *hdw)
+{
+	if (hdw->state_encoder_ok) return 0;
+	if (hdw->flag_tripped) return 0;
+	if (hdw->state_encoder_run) return 0;
+	if (hdw->state_encoder_config) return 0;
+	if (hdw->state_decoder_run) return 0;
+	if (hdw->state_usbstream_run) return 0;
+	if (hdw->pathway_state == PVR2_PATHWAY_DIGITAL) {
+		if (!hdw->hdw_desc->flag_digital_requires_cx23416) return 0;
+	} else if (hdw->pathway_state != PVR2_PATHWAY_ANALOG) {
+		return 0;
+	}
+
+	if (pvr2_upload_firmware2(hdw) < 0) {
+		hdw->flag_tripped = !0;
+		trace_stbit("flag_tripped",hdw->flag_tripped);
+		return !0;
+	}
+	hdw->state_encoder_ok = !0;
+	trace_stbit("state_encoder_ok",hdw->state_encoder_ok);
+	return !0;
+}
+
+
+/* Evaluate whether or not state_encoder_config can change */
+static int state_eval_encoder_config(struct pvr2_hdw *hdw)
+{
+	if (hdw->state_encoder_config) {
+		if (hdw->state_encoder_ok) {
+			if (hdw->state_pipeline_req &&
+			    !hdw->state_pipeline_pause) return 0;
+		}
+		hdw->state_encoder_config = 0;
+		hdw->state_encoder_waitok = 0;
+		trace_stbit("state_encoder_waitok",hdw->state_encoder_waitok);
+		/* paranoia - solve race if timer just completed */
+		del_timer_sync(&hdw->encoder_wait_timer);
+	} else {
+		if (!hdw->state_pathway_ok ||
+		    (hdw->pathway_state != PVR2_PATHWAY_ANALOG) ||
+		    !hdw->state_encoder_ok ||
+		    !hdw->state_pipeline_idle ||
+		    hdw->state_pipeline_pause ||
+		    !hdw->state_pipeline_req ||
+		    !hdw->state_pipeline_config) {
+			/* We must reset the enforced wait interval if
+			   anything has happened that might have disturbed
+			   the encoder.  This should be a rare case. */
+			if (timer_pending(&hdw->encoder_wait_timer)) {
+				del_timer_sync(&hdw->encoder_wait_timer);
+			}
+			if (hdw->state_encoder_waitok) {
+				/* Must clear the state - therefore we did
+				   something to a state bit and must also
+				   return true. */
+				hdw->state_encoder_waitok = 0;
+				trace_stbit("state_encoder_waitok",
+					    hdw->state_encoder_waitok);
+				return !0;
+			}
+			return 0;
+		}
+		if (!hdw->state_encoder_waitok) {
+			if (!timer_pending(&hdw->encoder_wait_timer)) {
+				/* waitok flag wasn't set and timer isn't
+				   running.  Check flag once more to avoid
+				   a race then start the timer.  This is
+				   the point when we measure out a minimal
+				   quiet interval before doing something to
+				   the encoder. */
+				if (!hdw->state_encoder_waitok) {
+					hdw->encoder_wait_timer.expires =
+						jiffies + msecs_to_jiffies(
+						TIME_MSEC_ENCODER_WAIT);
+					add_timer(&hdw->encoder_wait_timer);
+				}
+			}
+			/* We can't continue until we know we have been
+			   quiet for the interval measured by this
+			   timer. */
+			return 0;
+		}
+		pvr2_encoder_configure(hdw);
+		if (hdw->state_encoder_ok) hdw->state_encoder_config = !0;
+	}
+	trace_stbit("state_encoder_config",hdw->state_encoder_config);
+	return !0;
+}
+
+
+/* Return true if the encoder should not be running. */
+static int state_check_disable_encoder_run(struct pvr2_hdw *hdw)
+{
+	if (!hdw->state_encoder_ok) {
+		/* Encoder isn't healthy at the moment, so stop it. */
+		return !0;
+	}
+	if (!hdw->state_pathway_ok) {
+		/* Mode is not understood at the moment (i.e. it wants to
+		   change), so encoder must be stopped. */
+		return !0;
+	}
+
+	switch (hdw->pathway_state) {
+	case PVR2_PATHWAY_ANALOG:
+		if (!hdw->state_decoder_run) {
+			/* We're in analog mode and the decoder is not
+			   running; thus the encoder should be stopped as
+			   well. */
+			return !0;
+		}
+		break;
+	case PVR2_PATHWAY_DIGITAL:
+		if (hdw->state_encoder_runok) {
+			/* This is a funny case.  We're in digital mode so
+			   really the encoder should be stopped.  However
+			   if it really is running, only kill it after
+			   runok has been set.  This gives a chance for the
+			   onair quirk to function (encoder must run
+			   briefly first, at least once, before onair
+			   digital streaming can work). */
+			return !0;
+		}
+		break;
+	default:
+		/* Unknown mode; so encoder should be stopped. */
+		return !0;
+	}
+
+	/* If we get here, we haven't found a reason to stop the
+	   encoder. */
+	return 0;
+}
+
+
+/* Return true if the encoder should be running. */
+static int state_check_enable_encoder_run(struct pvr2_hdw *hdw)
+{
+	if (!hdw->state_encoder_ok) {
+		/* Don't run the encoder if it isn't healthy... */
+		return 0;
+	}
+	if (!hdw->state_pathway_ok) {
+		/* Don't run the encoder if we don't (yet) know what mode
+		   we need to be in... */
+		return 0;
+	}
+
+	switch (hdw->pathway_state) {
+	case PVR2_PATHWAY_ANALOG:
+		if (hdw->state_decoder_run && hdw->state_decoder_ready) {
+			/* In analog mode, if the decoder is running, then
+			   run the encoder. */
+			return !0;
+		}
+		break;
+	case PVR2_PATHWAY_DIGITAL:
+		if ((hdw->hdw_desc->digital_control_scheme ==
+		     PVR2_DIGITAL_SCHEME_ONAIR) &&
+		    !hdw->state_encoder_runok) {
+			/* This is a quirk.  OnAir hardware won't stream
+			   digital until the encoder has been run at least
+			   once, for a minimal period of time (empiricially
+			   measured to be 1/4 second).  So if we're on
+			   OnAir hardware and the encoder has never been
+			   run at all, then start the encoder.  Normal
+			   state machine logic in the driver will
+			   automatically handle the remaining bits. */
+			return !0;
+		}
+		break;
+	default:
+		/* For completeness (unknown mode; encoder won't run ever) */
+		break;
+	}
+	/* If we get here, then we haven't found any reason to run the
+	   encoder, so don't run it. */
+	return 0;
+}
+
+
+/* Evaluate whether or not state_encoder_run can change */
+static int state_eval_encoder_run(struct pvr2_hdw *hdw)
+{
+	if (hdw->state_encoder_run) {
+		if (!state_check_disable_encoder_run(hdw)) return 0;
+		if (hdw->state_encoder_ok) {
+			del_timer_sync(&hdw->encoder_run_timer);
+			if (pvr2_encoder_stop(hdw) < 0) return !0;
+		}
+		hdw->state_encoder_run = 0;
+	} else {
+		if (!state_check_enable_encoder_run(hdw)) return 0;
+		if (pvr2_encoder_start(hdw) < 0) return !0;
+		hdw->state_encoder_run = !0;
+		if (!hdw->state_encoder_runok) {
+			hdw->encoder_run_timer.expires = jiffies +
+				 msecs_to_jiffies(TIME_MSEC_ENCODER_OK);
+			add_timer(&hdw->encoder_run_timer);
+		}
+	}
+	trace_stbit("state_encoder_run",hdw->state_encoder_run);
+	return !0;
+}
+
+
+/* Timeout function for quiescent timer. */
+static void pvr2_hdw_quiescent_timeout(struct timer_list *t)
+{
+	struct pvr2_hdw *hdw = from_timer(hdw, t, quiescent_timer);
+	hdw->state_decoder_quiescent = !0;
+	trace_stbit("state_decoder_quiescent",hdw->state_decoder_quiescent);
+	hdw->state_stale = !0;
+	schedule_work(&hdw->workpoll);
+}
+
+
+/* Timeout function for decoder stabilization timer. */
+static void pvr2_hdw_decoder_stabilization_timeout(struct timer_list *t)
+{
+	struct pvr2_hdw *hdw = from_timer(hdw, t, decoder_stabilization_timer);
+	hdw->state_decoder_ready = !0;
+	trace_stbit("state_decoder_ready", hdw->state_decoder_ready);
+	hdw->state_stale = !0;
+	schedule_work(&hdw->workpoll);
+}
+
+
+/* Timeout function for encoder wait timer. */
+static void pvr2_hdw_encoder_wait_timeout(struct timer_list *t)
+{
+	struct pvr2_hdw *hdw = from_timer(hdw, t, encoder_wait_timer);
+	hdw->state_encoder_waitok = !0;
+	trace_stbit("state_encoder_waitok",hdw->state_encoder_waitok);
+	hdw->state_stale = !0;
+	schedule_work(&hdw->workpoll);
+}
+
+
+/* Timeout function for encoder run timer. */
+static void pvr2_hdw_encoder_run_timeout(struct timer_list *t)
+{
+	struct pvr2_hdw *hdw = from_timer(hdw, t, encoder_run_timer);
+	if (!hdw->state_encoder_runok) {
+		hdw->state_encoder_runok = !0;
+		trace_stbit("state_encoder_runok",hdw->state_encoder_runok);
+		hdw->state_stale = !0;
+		schedule_work(&hdw->workpoll);
+	}
+}
+
+
+/* Evaluate whether or not state_decoder_run can change */
+static int state_eval_decoder_run(struct pvr2_hdw *hdw)
+{
+	if (hdw->state_decoder_run) {
+		if (hdw->state_encoder_ok) {
+			if (hdw->state_pipeline_req &&
+			    !hdw->state_pipeline_pause &&
+			    hdw->state_pathway_ok) return 0;
+		}
+		if (!hdw->flag_decoder_missed) {
+			pvr2_decoder_enable(hdw,0);
+		}
+		hdw->state_decoder_quiescent = 0;
+		hdw->state_decoder_run = 0;
+		/* paranoia - solve race if timer(s) just completed */
+		del_timer_sync(&hdw->quiescent_timer);
+		/* Kill the stabilization timer, in case we're killing the
+		   encoder before the previous stabilization interval has
+		   been properly timed. */
+		del_timer_sync(&hdw->decoder_stabilization_timer);
+		hdw->state_decoder_ready = 0;
+	} else {
+		if (!hdw->state_decoder_quiescent) {
+			if (!timer_pending(&hdw->quiescent_timer)) {
+				/* We don't do something about the
+				   quiescent timer until right here because
+				   we also want to catch cases where the
+				   decoder was already not running (like
+				   after initialization) as opposed to
+				   knowing that we had just stopped it.
+				   The second flag check is here to cover a
+				   race - the timer could have run and set
+				   this flag just after the previous check
+				   but before we did the pending check. */
+				if (!hdw->state_decoder_quiescent) {
+					hdw->quiescent_timer.expires =
+						jiffies + msecs_to_jiffies(
+						TIME_MSEC_DECODER_WAIT);
+					add_timer(&hdw->quiescent_timer);
+				}
+			}
+			/* Don't allow decoder to start again until it has
+			   been quiesced first.  This little detail should
+			   hopefully further stabilize the encoder. */
+			return 0;
+		}
+		if (!hdw->state_pathway_ok ||
+		    (hdw->pathway_state != PVR2_PATHWAY_ANALOG) ||
+		    !hdw->state_pipeline_req ||
+		    hdw->state_pipeline_pause ||
+		    !hdw->state_pipeline_config ||
+		    !hdw->state_encoder_config ||
+		    !hdw->state_encoder_ok) return 0;
+		del_timer_sync(&hdw->quiescent_timer);
+		if (hdw->flag_decoder_missed) return 0;
+		if (pvr2_decoder_enable(hdw,!0) < 0) return 0;
+		hdw->state_decoder_quiescent = 0;
+		hdw->state_decoder_ready = 0;
+		hdw->state_decoder_run = !0;
+		if (hdw->decoder_client_id == PVR2_CLIENT_ID_SAA7115) {
+			hdw->decoder_stabilization_timer.expires =
+				jiffies + msecs_to_jiffies(
+				TIME_MSEC_DECODER_STABILIZATION_WAIT);
+			add_timer(&hdw->decoder_stabilization_timer);
+		} else {
+			hdw->state_decoder_ready = !0;
+		}
+	}
+	trace_stbit("state_decoder_quiescent",hdw->state_decoder_quiescent);
+	trace_stbit("state_decoder_run",hdw->state_decoder_run);
+	trace_stbit("state_decoder_ready", hdw->state_decoder_ready);
+	return !0;
+}
+
+
+/* Evaluate whether or not state_usbstream_run can change */
+static int state_eval_usbstream_run(struct pvr2_hdw *hdw)
+{
+	if (hdw->state_usbstream_run) {
+		int fl = !0;
+		if (hdw->pathway_state == PVR2_PATHWAY_ANALOG) {
+			fl = (hdw->state_encoder_ok &&
+			      hdw->state_encoder_run);
+		} else if ((hdw->pathway_state == PVR2_PATHWAY_DIGITAL) &&
+			   (hdw->hdw_desc->flag_digital_requires_cx23416)) {
+			fl = hdw->state_encoder_ok;
+		}
+		if (fl &&
+		    hdw->state_pipeline_req &&
+		    !hdw->state_pipeline_pause &&
+		    hdw->state_pathway_ok) {
+			return 0;
+		}
+		pvr2_hdw_cmd_usbstream(hdw,0);
+		hdw->state_usbstream_run = 0;
+	} else {
+		if (!hdw->state_pipeline_req ||
+		    hdw->state_pipeline_pause ||
+		    !hdw->state_pathway_ok) return 0;
+		if (hdw->pathway_state == PVR2_PATHWAY_ANALOG) {
+			if (!hdw->state_encoder_ok ||
+			    !hdw->state_encoder_run) return 0;
+		} else if ((hdw->pathway_state == PVR2_PATHWAY_DIGITAL) &&
+			   (hdw->hdw_desc->flag_digital_requires_cx23416)) {
+			if (!hdw->state_encoder_ok) return 0;
+			if (hdw->state_encoder_run) return 0;
+			if (hdw->hdw_desc->digital_control_scheme ==
+			    PVR2_DIGITAL_SCHEME_ONAIR) {
+				/* OnAir digital receivers won't stream
+				   unless the analog encoder has run first.
+				   Why?  I have no idea.  But don't even
+				   try until we know the analog side is
+				   known to have run. */
+				if (!hdw->state_encoder_runok) return 0;
+			}
+		}
+		if (pvr2_hdw_cmd_usbstream(hdw,!0) < 0) return 0;
+		hdw->state_usbstream_run = !0;
+	}
+	trace_stbit("state_usbstream_run",hdw->state_usbstream_run);
+	return !0;
+}
+
+
+/* Attempt to configure pipeline, if needed */
+static int state_eval_pipeline_config(struct pvr2_hdw *hdw)
+{
+	if (hdw->state_pipeline_config ||
+	    hdw->state_pipeline_pause) return 0;
+	pvr2_hdw_commit_execute(hdw);
+	return !0;
+}
+
+
+/* Update pipeline idle and pipeline pause tracking states based on other
+   inputs.  This must be called whenever the other relevant inputs have
+   changed. */
+static int state_update_pipeline_state(struct pvr2_hdw *hdw)
+{
+	unsigned int st;
+	int updatedFl = 0;
+	/* Update pipeline state */
+	st = !(hdw->state_encoder_run ||
+	       hdw->state_decoder_run ||
+	       hdw->state_usbstream_run ||
+	       (!hdw->state_decoder_quiescent));
+	if (!st != !hdw->state_pipeline_idle) {
+		hdw->state_pipeline_idle = st;
+		updatedFl = !0;
+	}
+	if (hdw->state_pipeline_idle && hdw->state_pipeline_pause) {
+		hdw->state_pipeline_pause = 0;
+		updatedFl = !0;
+	}
+	return updatedFl;
+}
+
+
+typedef int (*state_eval_func)(struct pvr2_hdw *);
+
+/* Set of functions to be run to evaluate various states in the driver. */
+static const state_eval_func eval_funcs[] = {
+	state_eval_pathway_ok,
+	state_eval_pipeline_config,
+	state_eval_encoder_ok,
+	state_eval_encoder_config,
+	state_eval_decoder_run,
+	state_eval_encoder_run,
+	state_eval_usbstream_run,
+};
+
+
+/* Process various states and return true if we did anything interesting. */
+static int pvr2_hdw_state_update(struct pvr2_hdw *hdw)
+{
+	unsigned int i;
+	int state_updated = 0;
+	int check_flag;
+
+	if (!hdw->state_stale) return 0;
+	if ((hdw->fw1_state != FW1_STATE_OK) ||
+	    !hdw->flag_ok) {
+		hdw->state_stale = 0;
+		return !0;
+	}
+	/* This loop is the heart of the entire driver.  It keeps trying to
+	   evaluate various bits of driver state until nothing changes for
+	   one full iteration.  Each "bit of state" tracks some global
+	   aspect of the driver, e.g. whether decoder should run, if
+	   pipeline is configured, usb streaming is on, etc.  We separately
+	   evaluate each of those questions based on other driver state to
+	   arrive at the correct running configuration. */
+	do {
+		check_flag = 0;
+		state_update_pipeline_state(hdw);
+		/* Iterate over each bit of state */
+		for (i = 0; (i<ARRAY_SIZE(eval_funcs)) && hdw->flag_ok; i++) {
+			if ((*eval_funcs[i])(hdw)) {
+				check_flag = !0;
+				state_updated = !0;
+				state_update_pipeline_state(hdw);
+			}
+		}
+	} while (check_flag && hdw->flag_ok);
+	hdw->state_stale = 0;
+	trace_stbit("state_stale",hdw->state_stale);
+	return state_updated;
+}
+
+
+static unsigned int print_input_mask(unsigned int msk,
+				     char *buf,unsigned int acnt)
+{
+	unsigned int idx,ccnt;
+	unsigned int tcnt = 0;
+	for (idx = 0; idx < ARRAY_SIZE(control_values_input); idx++) {
+		if (!((1 << idx) & msk)) continue;
+		ccnt = scnprintf(buf+tcnt,
+				 acnt-tcnt,
+				 "%s%s",
+				 (tcnt ? ", " : ""),
+				 control_values_input[idx]);
+		tcnt += ccnt;
+	}
+	return tcnt;
+}
+
+
+static const char *pvr2_pathway_state_name(int id)
+{
+	switch (id) {
+	case PVR2_PATHWAY_ANALOG: return "analog";
+	case PVR2_PATHWAY_DIGITAL: return "digital";
+	default: return "unknown";
+	}
+}
+
+
+static unsigned int pvr2_hdw_report_unlocked(struct pvr2_hdw *hdw,int which,
+					     char *buf,unsigned int acnt)
+{
+	switch (which) {
+	case 0:
+		return scnprintf(
+			buf,acnt,
+			"driver:%s%s%s%s%s <mode=%s>",
+			(hdw->flag_ok ? " <ok>" : " <fail>"),
+			(hdw->flag_init_ok ? " <init>" : " <uninitialized>"),
+			(hdw->flag_disconnected ? " <disconnected>" :
+			 " <connected>"),
+			(hdw->flag_tripped ? " <tripped>" : ""),
+			(hdw->flag_decoder_missed ? " <no decoder>" : ""),
+			pvr2_pathway_state_name(hdw->pathway_state));
+
+	case 1:
+		return scnprintf(
+			buf,acnt,
+			"pipeline:%s%s%s%s",
+			(hdw->state_pipeline_idle ? " <idle>" : ""),
+			(hdw->state_pipeline_config ?
+			 " <configok>" : " <stale>"),
+			(hdw->state_pipeline_req ? " <req>" : ""),
+			(hdw->state_pipeline_pause ? " <pause>" : ""));
+	case 2:
+		return scnprintf(
+			buf,acnt,
+			"worker:%s%s%s%s%s%s%s",
+			(hdw->state_decoder_run ?
+			 (hdw->state_decoder_ready ?
+			  "<decode:run>" : " <decode:start>") :
+			 (hdw->state_decoder_quiescent ?
+			  "" : " <decode:stop>")),
+			(hdw->state_decoder_quiescent ?
+			 " <decode:quiescent>" : ""),
+			(hdw->state_encoder_ok ?
+			 "" : " <encode:init>"),
+			(hdw->state_encoder_run ?
+			 (hdw->state_encoder_runok ?
+			  " <encode:run>" :
+			  " <encode:firstrun>") :
+			 (hdw->state_encoder_runok ?
+			  " <encode:stop>" :
+			  " <encode:virgin>")),
+			(hdw->state_encoder_config ?
+			 " <encode:configok>" :
+			 (hdw->state_encoder_waitok ?
+			  "" : " <encode:waitok>")),
+			(hdw->state_usbstream_run ?
+			 " <usb:run>" : " <usb:stop>"),
+			(hdw->state_pathway_ok ?
+			 " <pathway:ok>" : ""));
+	case 3:
+		return scnprintf(
+			buf,acnt,
+			"state: %s",
+			pvr2_get_state_name(hdw->master_state));
+	case 4: {
+		unsigned int tcnt = 0;
+		unsigned int ccnt;
+
+		ccnt = scnprintf(buf,
+				 acnt,
+				 "Hardware supported inputs: ");
+		tcnt += ccnt;
+		tcnt += print_input_mask(hdw->input_avail_mask,
+					 buf+tcnt,
+					 acnt-tcnt);
+		if (hdw->input_avail_mask != hdw->input_allowed_mask) {
+			ccnt = scnprintf(buf+tcnt,
+					 acnt-tcnt,
+					 "; allowed inputs: ");
+			tcnt += ccnt;
+			tcnt += print_input_mask(hdw->input_allowed_mask,
+						 buf+tcnt,
+						 acnt-tcnt);
+		}
+		return tcnt;
+	}
+	case 5: {
+		struct pvr2_stream_stats stats;
+		if (!hdw->vid_stream) break;
+		pvr2_stream_get_stats(hdw->vid_stream,
+				      &stats,
+				      0);
+		return scnprintf(
+			buf,acnt,
+			"Bytes streamed=%u URBs: queued=%u idle=%u ready=%u processed=%u failed=%u",
+			stats.bytes_processed,
+			stats.buffers_in_queue,
+			stats.buffers_in_idle,
+			stats.buffers_in_ready,
+			stats.buffers_processed,
+			stats.buffers_failed);
+	}
+	case 6: {
+		unsigned int id = hdw->ir_scheme_active;
+		return scnprintf(buf, acnt, "ir scheme: id=%d %s", id,
+				 (id >= ARRAY_SIZE(ir_scheme_names) ?
+				  "?" : ir_scheme_names[id]));
+	}
+	default: break;
+	}
+	return 0;
+}
+
+
+/* Generate report containing info about attached sub-devices and attached
+   i2c clients, including an indication of which attached i2c clients are
+   actually sub-devices. */
+static unsigned int pvr2_hdw_report_clients(struct pvr2_hdw *hdw,
+					    char *buf, unsigned int acnt)
+{
+	struct v4l2_subdev *sd;
+	unsigned int tcnt = 0;
+	unsigned int ccnt;
+	struct i2c_client *client;
+	const char *p;
+	unsigned int id;
+
+	ccnt = scnprintf(buf, acnt, "Associated v4l2-subdev drivers and I2C clients:\n");
+	tcnt += ccnt;
+	v4l2_device_for_each_subdev(sd, &hdw->v4l2_dev) {
+		id = sd->grp_id;
+		p = NULL;
+		if (id < ARRAY_SIZE(module_names)) p = module_names[id];
+		if (p) {
+			ccnt = scnprintf(buf + tcnt, acnt - tcnt, "  %s:", p);
+			tcnt += ccnt;
+		} else {
+			ccnt = scnprintf(buf + tcnt, acnt - tcnt,
+					 "  (unknown id=%u):", id);
+			tcnt += ccnt;
+		}
+		client = v4l2_get_subdevdata(sd);
+		if (client) {
+			ccnt = scnprintf(buf + tcnt, acnt - tcnt,
+					 " %s @ %02x\n", client->name,
+					 client->addr);
+			tcnt += ccnt;
+		} else {
+			ccnt = scnprintf(buf + tcnt, acnt - tcnt,
+					 " no i2c client\n");
+			tcnt += ccnt;
+		}
+	}
+	return tcnt;
+}
+
+
+unsigned int pvr2_hdw_state_report(struct pvr2_hdw *hdw,
+				   char *buf,unsigned int acnt)
+{
+	unsigned int bcnt,ccnt,idx;
+	bcnt = 0;
+	LOCK_TAKE(hdw->big_lock);
+	for (idx = 0; ; idx++) {
+		ccnt = pvr2_hdw_report_unlocked(hdw,idx,buf,acnt);
+		if (!ccnt) break;
+		bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+		if (!acnt) break;
+		buf[0] = '\n'; ccnt = 1;
+		bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+	}
+	ccnt = pvr2_hdw_report_clients(hdw, buf, acnt);
+	bcnt += ccnt; acnt -= ccnt; buf += ccnt;
+	LOCK_GIVE(hdw->big_lock);
+	return bcnt;
+}
+
+
+static void pvr2_hdw_state_log_state(struct pvr2_hdw *hdw)
+{
+	char buf[256];
+	unsigned int idx, ccnt;
+	unsigned int lcnt, ucnt;
+
+	for (idx = 0; ; idx++) {
+		ccnt = pvr2_hdw_report_unlocked(hdw,idx,buf,sizeof(buf));
+		if (!ccnt) break;
+		printk(KERN_INFO "%s %.*s\n",hdw->name,ccnt,buf);
+	}
+	ccnt = pvr2_hdw_report_clients(hdw, buf, sizeof(buf));
+	if (ccnt >= sizeof(buf))
+		ccnt = sizeof(buf);
+
+	ucnt = 0;
+	while (ucnt < ccnt) {
+		lcnt = 0;
+		while ((lcnt + ucnt < ccnt) && (buf[lcnt + ucnt] != '\n')) {
+			lcnt++;
+		}
+		printk(KERN_INFO "%s %.*s\n", hdw->name, lcnt, buf + ucnt);
+		ucnt += lcnt + 1;
+	}
+}
+
+
+/* Evaluate and update the driver's current state, taking various actions
+   as appropriate for the update. */
+static int pvr2_hdw_state_eval(struct pvr2_hdw *hdw)
+{
+	unsigned int st;
+	int state_updated = 0;
+	int callback_flag = 0;
+	int analog_mode;
+
+	pvr2_trace(PVR2_TRACE_STBITS,
+		   "Drive state check START");
+	if (pvrusb2_debug & PVR2_TRACE_STBITS) {
+		pvr2_hdw_state_log_state(hdw);
+	}
+
+	/* Process all state and get back over disposition */
+	state_updated = pvr2_hdw_state_update(hdw);
+
+	analog_mode = (hdw->pathway_state != PVR2_PATHWAY_DIGITAL);
+
+	/* Update master state based upon all other states. */
+	if (!hdw->flag_ok) {
+		st = PVR2_STATE_DEAD;
+	} else if (hdw->fw1_state != FW1_STATE_OK) {
+		st = PVR2_STATE_COLD;
+	} else if ((analog_mode ||
+		    hdw->hdw_desc->flag_digital_requires_cx23416) &&
+		   !hdw->state_encoder_ok) {
+		st = PVR2_STATE_WARM;
+	} else if (hdw->flag_tripped ||
+		   (analog_mode && hdw->flag_decoder_missed)) {
+		st = PVR2_STATE_ERROR;
+	} else if (hdw->state_usbstream_run &&
+		   (!analog_mode ||
+		    (hdw->state_encoder_run && hdw->state_decoder_run))) {
+		st = PVR2_STATE_RUN;
+	} else {
+		st = PVR2_STATE_READY;
+	}
+	if (hdw->master_state != st) {
+		pvr2_trace(PVR2_TRACE_STATE,
+			   "Device state change from %s to %s",
+			   pvr2_get_state_name(hdw->master_state),
+			   pvr2_get_state_name(st));
+		pvr2_led_ctrl(hdw,st == PVR2_STATE_RUN);
+		hdw->master_state = st;
+		state_updated = !0;
+		callback_flag = !0;
+	}
+	if (state_updated) {
+		/* Trigger anyone waiting on any state changes here. */
+		wake_up(&hdw->state_wait_data);
+	}
+
+	if (pvrusb2_debug & PVR2_TRACE_STBITS) {
+		pvr2_hdw_state_log_state(hdw);
+	}
+	pvr2_trace(PVR2_TRACE_STBITS,
+		   "Drive state check DONE callback=%d",callback_flag);
+
+	return callback_flag;
+}
+
+
+/* Cause kernel thread to check / update driver state */
+static void pvr2_hdw_state_sched(struct pvr2_hdw *hdw)
+{
+	if (hdw->state_stale) return;
+	hdw->state_stale = !0;
+	trace_stbit("state_stale",hdw->state_stale);
+	schedule_work(&hdw->workpoll);
+}
+
+
+int pvr2_hdw_gpio_get_dir(struct pvr2_hdw *hdw,u32 *dp)
+{
+	return pvr2_read_register(hdw,PVR2_GPIO_DIR,dp);
+}
+
+
+int pvr2_hdw_gpio_get_out(struct pvr2_hdw *hdw,u32 *dp)
+{
+	return pvr2_read_register(hdw,PVR2_GPIO_OUT,dp);
+}
+
+
+int pvr2_hdw_gpio_get_in(struct pvr2_hdw *hdw,u32 *dp)
+{
+	return pvr2_read_register(hdw,PVR2_GPIO_IN,dp);
+}
+
+
+int pvr2_hdw_gpio_chg_dir(struct pvr2_hdw *hdw,u32 msk,u32 val)
+{
+	u32 cval,nval;
+	int ret;
+	if (~msk) {
+		ret = pvr2_read_register(hdw,PVR2_GPIO_DIR,&cval);
+		if (ret) return ret;
+		nval = (cval & ~msk) | (val & msk);
+		pvr2_trace(PVR2_TRACE_GPIO,
+			   "GPIO direction changing 0x%x:0x%x from 0x%x to 0x%x",
+			   msk,val,cval,nval);
+	} else {
+		nval = val;
+		pvr2_trace(PVR2_TRACE_GPIO,
+			   "GPIO direction changing to 0x%x",nval);
+	}
+	return pvr2_write_register(hdw,PVR2_GPIO_DIR,nval);
+}
+
+
+int pvr2_hdw_gpio_chg_out(struct pvr2_hdw *hdw,u32 msk,u32 val)
+{
+	u32 cval,nval;
+	int ret;
+	if (~msk) {
+		ret = pvr2_read_register(hdw,PVR2_GPIO_OUT,&cval);
+		if (ret) return ret;
+		nval = (cval & ~msk) | (val & msk);
+		pvr2_trace(PVR2_TRACE_GPIO,
+			   "GPIO output changing 0x%x:0x%x from 0x%x to 0x%x",
+			   msk,val,cval,nval);
+	} else {
+		nval = val;
+		pvr2_trace(PVR2_TRACE_GPIO,
+			   "GPIO output changing to 0x%x",nval);
+	}
+	return pvr2_write_register(hdw,PVR2_GPIO_OUT,nval);
+}
+
+
+void pvr2_hdw_status_poll(struct pvr2_hdw *hdw)
+{
+	struct v4l2_tuner *vtp = &hdw->tuner_signal_info;
+	memset(vtp, 0, sizeof(*vtp));
+	vtp->type = (hdw->input_val == PVR2_CVAL_INPUT_RADIO) ?
+		V4L2_TUNER_RADIO : V4L2_TUNER_ANALOG_TV;
+	hdw->tuner_signal_stale = 0;
+	/* Note: There apparently is no replacement for VIDIOC_CROPCAP
+	   using v4l2-subdev - therefore we can't support that AT ALL right
+	   now.  (Of course, no sub-drivers seem to implement it either.
+	   But now it's a a chicken and egg problem...) */
+	v4l2_device_call_all(&hdw->v4l2_dev, 0, tuner, g_tuner, vtp);
+	pvr2_trace(PVR2_TRACE_CHIPS, "subdev status poll type=%u strength=%u audio=0x%x cap=0x%x low=%u hi=%u",
+		   vtp->type,
+		   vtp->signal, vtp->rxsubchans, vtp->capability,
+		   vtp->rangelow, vtp->rangehigh);
+
+	/* We have to do this to avoid getting into constant polling if
+	   there's nobody to answer a poll of cropcap info. */
+	hdw->cropcap_stale = 0;
+}
+
+
+unsigned int pvr2_hdw_get_input_available(struct pvr2_hdw *hdw)
+{
+	return hdw->input_avail_mask;
+}
+
+
+unsigned int pvr2_hdw_get_input_allowed(struct pvr2_hdw *hdw)
+{
+	return hdw->input_allowed_mask;
+}
+
+
+static int pvr2_hdw_set_input(struct pvr2_hdw *hdw,int v)
+{
+	if (hdw->input_val != v) {
+		hdw->input_val = v;
+		hdw->input_dirty = !0;
+	}
+
+	/* Handle side effects - if we switch to a mode that needs the RF
+	   tuner, then select the right frequency choice as well and mark
+	   it dirty. */
+	if (hdw->input_val == PVR2_CVAL_INPUT_RADIO) {
+		hdw->freqSelector = 0;
+		hdw->freqDirty = !0;
+	} else if ((hdw->input_val == PVR2_CVAL_INPUT_TV) ||
+		   (hdw->input_val == PVR2_CVAL_INPUT_DTV)) {
+		hdw->freqSelector = 1;
+		hdw->freqDirty = !0;
+	}
+	return 0;
+}
+
+
+int pvr2_hdw_set_input_allowed(struct pvr2_hdw *hdw,
+			       unsigned int change_mask,
+			       unsigned int change_val)
+{
+	int ret = 0;
+	unsigned int nv,m,idx;
+	LOCK_TAKE(hdw->big_lock);
+	do {
+		nv = hdw->input_allowed_mask & ~change_mask;
+		nv |= (change_val & change_mask);
+		nv &= hdw->input_avail_mask;
+		if (!nv) {
+			/* No legal modes left; return error instead. */
+			ret = -EPERM;
+			break;
+		}
+		hdw->input_allowed_mask = nv;
+		if ((1 << hdw->input_val) & hdw->input_allowed_mask) {
+			/* Current mode is still in the allowed mask, so
+			   we're done. */
+			break;
+		}
+		/* Select and switch to a mode that is still in the allowed
+		   mask */
+		if (!hdw->input_allowed_mask) {
+			/* Nothing legal; give up */
+			break;
+		}
+		m = hdw->input_allowed_mask;
+		for (idx = 0; idx < (sizeof(m) << 3); idx++) {
+			if (!((1 << idx) & m)) continue;
+			pvr2_hdw_set_input(hdw,idx);
+			break;
+		}
+	} while (0);
+	LOCK_GIVE(hdw->big_lock);
+	return ret;
+}
+
+
+/* Find I2C address of eeprom */
+static int pvr2_hdw_get_eeprom_addr(struct pvr2_hdw *hdw)
+{
+	int result;
+	LOCK_TAKE(hdw->ctl_lock); do {
+		hdw->cmd_buffer[0] = FX2CMD_GET_EEPROM_ADDR;
+		result = pvr2_send_request(hdw,
+					   hdw->cmd_buffer,1,
+					   hdw->cmd_buffer,1);
+		if (result < 0) break;
+		result = hdw->cmd_buffer[0];
+	} while(0); LOCK_GIVE(hdw->ctl_lock);
+	return result;
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-hdw.h b/drivers/media/usb/pvrusb2/pvrusb2-hdw.h
new file mode 100644
index 0000000..25648ad
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-hdw.h
@@ -0,0 +1,338 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+#ifndef __PVRUSB2_HDW_H
+#define __PVRUSB2_HDW_H
+
+#include <linux/usb.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-dev.h>
+#include "pvrusb2-io.h"
+#include "pvrusb2-ctrl.h"
+
+
+/* Private internal control ids, look these up with
+   pvr2_hdw_get_ctrl_by_id() - these are NOT visible in V4L */
+#define PVR2_CID_STDCUR 2
+#define PVR2_CID_STDAVAIL 3
+#define PVR2_CID_INPUT 4
+#define PVR2_CID_AUDIOMODE 5
+#define PVR2_CID_FREQUENCY 6
+#define PVR2_CID_HRES 7
+#define PVR2_CID_VRES 8
+#define PVR2_CID_CROPL 9
+#define PVR2_CID_CROPT 10
+#define PVR2_CID_CROPW 11
+#define PVR2_CID_CROPH 12
+#define PVR2_CID_CROPCAPPAN 13
+#define PVR2_CID_CROPCAPPAD 14
+#define PVR2_CID_CROPCAPBL 15
+#define PVR2_CID_CROPCAPBT 16
+#define PVR2_CID_CROPCAPBW 17
+#define PVR2_CID_CROPCAPBH 18
+#define PVR2_CID_STDDETECT 19
+
+/* Legal values for the INPUT state variable */
+#define PVR2_CVAL_INPUT_TV 0
+#define PVR2_CVAL_INPUT_DTV 1
+#define PVR2_CVAL_INPUT_COMPOSITE 2
+#define PVR2_CVAL_INPUT_SVIDEO 3
+#define PVR2_CVAL_INPUT_RADIO 4
+
+enum pvr2_config {
+	pvr2_config_empty,    /* No configuration */
+	pvr2_config_mpeg,     /* Encoded / compressed video */
+	pvr2_config_vbi,      /* Standard vbi info */
+	pvr2_config_pcm,      /* Audio raw pcm stream */
+	pvr2_config_rawvideo, /* Video raw frames */
+};
+
+enum pvr2_v4l_type {
+	pvr2_v4l_type_video,
+	pvr2_v4l_type_vbi,
+	pvr2_v4l_type_radio,
+};
+
+/* Major states that we can be in:
+ *
+ *  DEAD - Device is in an unusable state and cannot be recovered.  This
+ *  can happen if we completely lose the ability to communicate with it
+ *  (but it might still on the bus).  In this state there's nothing we can
+ *  do; it must be replugged in order to recover.
+ *
+ *  COLD - Device is in an unusable state, needs microcontroller firmware.
+ *
+ *  WARM - We can communicate with the device and the proper
+ *  microcontroller firmware is running, but other device initialization is
+ *  still needed (e.g. encoder firmware).
+ *
+ *  ERROR - A problem prevents capture operation (e.g. encoder firmware
+ *  missing).
+ *
+ *  READY - Device is operational, but not streaming.
+ *
+ *  RUN - Device is streaming.
+ *
+ */
+#define PVR2_STATE_NONE 0
+#define PVR2_STATE_DEAD 1
+#define PVR2_STATE_COLD 2
+#define PVR2_STATE_WARM 3
+#define PVR2_STATE_ERROR 4
+#define PVR2_STATE_READY 5
+#define PVR2_STATE_RUN 6
+
+/* Translate configuration enum to a string label */
+const char *pvr2_config_get_name(enum pvr2_config);
+
+struct pvr2_hdw;
+
+/* Create and return a structure for interacting with the underlying
+   hardware */
+struct pvr2_hdw *pvr2_hdw_create(struct usb_interface *intf,
+				 const struct usb_device_id *devid);
+
+/* Perform second stage initialization, passing in a notification callback
+   for when the master state changes. */
+int pvr2_hdw_initialize(struct pvr2_hdw *,
+			void (*callback_func)(void *),
+			void *callback_data);
+
+/* Destroy hardware interaction structure */
+void pvr2_hdw_destroy(struct pvr2_hdw *);
+
+/* Return true if in the ready (normal) state */
+int pvr2_hdw_dev_ok(struct pvr2_hdw *);
+
+/* Return small integer number [1..N] for logical instance number of this
+   device.  This is useful for indexing array-valued module parameters. */
+int pvr2_hdw_get_unit_number(struct pvr2_hdw *);
+
+/* Get pointer to underlying USB device */
+struct usb_device *pvr2_hdw_get_dev(struct pvr2_hdw *);
+
+/* Retrieve serial number of device */
+unsigned long pvr2_hdw_get_sn(struct pvr2_hdw *);
+
+/* Retrieve bus location info of device */
+const char *pvr2_hdw_get_bus_info(struct pvr2_hdw *);
+
+/* Retrieve per-instance string identifier for this specific device */
+const char *pvr2_hdw_get_device_identifier(struct pvr2_hdw *);
+
+/* Called when hardware has been unplugged */
+void pvr2_hdw_disconnect(struct pvr2_hdw *);
+
+/* Sets v4l2_dev of a video_device struct */
+void pvr2_hdw_set_v4l2_dev(struct pvr2_hdw *, struct video_device *);
+
+/* Get the number of defined controls */
+unsigned int pvr2_hdw_get_ctrl_count(struct pvr2_hdw *);
+
+/* Retrieve a control handle given its index (0..count-1) */
+struct pvr2_ctrl *pvr2_hdw_get_ctrl_by_index(struct pvr2_hdw *,unsigned int);
+
+/* Retrieve a control handle given its internal ID (if any) */
+struct pvr2_ctrl *pvr2_hdw_get_ctrl_by_id(struct pvr2_hdw *,unsigned int);
+
+/* Retrieve a control handle given its V4L ID (if any) */
+struct pvr2_ctrl *pvr2_hdw_get_ctrl_v4l(struct pvr2_hdw *,unsigned int ctl_id);
+
+/* Retrieve a control handle given its immediate predecessor V4L ID (if any) */
+struct pvr2_ctrl *pvr2_hdw_get_ctrl_nextv4l(struct pvr2_hdw *,
+					    unsigned int ctl_id);
+
+/* Commit all control changes made up to this point */
+int pvr2_hdw_commit_ctl(struct pvr2_hdw *);
+
+/* Return a bit mask of valid input selections for this device.  Mask bits
+ * will be according to PVR_CVAL_INPUT_xxxx definitions. */
+unsigned int pvr2_hdw_get_input_available(struct pvr2_hdw *);
+
+/* Return a bit mask of allowed input selections for this device.  Mask bits
+ * will be according to PVR_CVAL_INPUT_xxxx definitions. */
+unsigned int pvr2_hdw_get_input_allowed(struct pvr2_hdw *);
+
+/* Change the set of allowed input selections for this device.  Both
+   change_mask and change_valu are mask bits according to
+   PVR_CVAL_INPUT_xxxx definitions.  The change_mask parameter indicate
+   which settings are being changed and the change_val parameter indicates
+   whether corresponding settings are being set or cleared. */
+int pvr2_hdw_set_input_allowed(struct pvr2_hdw *,
+			       unsigned int change_mask,
+			       unsigned int change_val);
+
+/* Return name for this driver instance */
+const char *pvr2_hdw_get_driver_name(struct pvr2_hdw *);
+
+/* Mark tuner status stale so that it will be re-fetched */
+void pvr2_hdw_execute_tuner_poll(struct pvr2_hdw *);
+
+/* Return information about the tuner */
+int pvr2_hdw_get_tuner_status(struct pvr2_hdw *,struct v4l2_tuner *);
+
+/* Return information about cropping capabilities */
+int pvr2_hdw_get_cropcap(struct pvr2_hdw *, struct v4l2_cropcap *);
+
+/* Query device and see if it thinks it is on a high-speed USB link */
+int pvr2_hdw_is_hsm(struct pvr2_hdw *);
+
+/* Return a string token representative of the hardware type */
+const char *pvr2_hdw_get_type(struct pvr2_hdw *);
+
+/* Return a single line description of the hardware type */
+const char *pvr2_hdw_get_desc(struct pvr2_hdw *);
+
+/* Turn streaming on/off */
+int pvr2_hdw_set_streaming(struct pvr2_hdw *,int);
+
+/* Find out if streaming is on */
+int pvr2_hdw_get_streaming(struct pvr2_hdw *);
+
+/* Retrieve driver overall state */
+int pvr2_hdw_get_state(struct pvr2_hdw *);
+
+/* Configure the type of stream to generate */
+int pvr2_hdw_set_stream_type(struct pvr2_hdw *, enum pvr2_config);
+
+/* Get handle to video output stream */
+struct pvr2_stream *pvr2_hdw_get_video_stream(struct pvr2_hdw *);
+
+/* Enable / disable retrieval of CPU firmware or prom contents.  This must
+   be enabled before pvr2_hdw_cpufw_get() will function.  Note that doing
+   this may prevent the device from running (and leaving this mode may
+   imply a device reset). */
+void pvr2_hdw_cpufw_set_enabled(struct pvr2_hdw *,
+				int mode, /* 0=8KB FX2, 1=16KB FX2, 2=PROM */
+				int enable_flag);
+
+/* Return true if we're in a mode for retrieval CPU firmware */
+int pvr2_hdw_cpufw_get_enabled(struct pvr2_hdw *);
+
+/* Retrieve a piece of the CPU's firmware at the given offset.  Return
+   value is the number of bytes retrieved or zero if we're past the end or
+   an error otherwise (e.g. if firmware retrieval is not enabled). */
+int pvr2_hdw_cpufw_get(struct pvr2_hdw *,unsigned int offs,
+		       char *buf,unsigned int cnt);
+
+/* Retrieve a previously stored v4l minor device number */
+int pvr2_hdw_v4l_get_minor_number(struct pvr2_hdw *,enum pvr2_v4l_type index);
+
+/* Store a v4l minor device number */
+void pvr2_hdw_v4l_store_minor_number(struct pvr2_hdw *,
+				     enum pvr2_v4l_type index,int);
+
+/* The following entry points are all lower level things you normally don't
+   want to worry about. */
+
+/* Issue a command and get a response from the device.  LOTS of higher
+   level stuff is built on this. */
+int pvr2_send_request(struct pvr2_hdw *,
+		      void *write_ptr,unsigned int write_len,
+		      void *read_ptr,unsigned int read_len);
+
+/* Slightly higher level device communication functions. */
+int pvr2_write_register(struct pvr2_hdw *, u16, u32);
+
+/* Call if for any reason we can't talk to the hardware anymore - this will
+   cause the driver to stop flailing on the device. */
+void pvr2_hdw_render_useless(struct pvr2_hdw *);
+
+/* Set / clear 8051's reset bit */
+void pvr2_hdw_cpureset_assert(struct pvr2_hdw *,int);
+
+/* Execute a USB-commanded device reset */
+void pvr2_hdw_device_reset(struct pvr2_hdw *);
+
+/* Reset worker's error trapping circuit breaker */
+int pvr2_hdw_untrip(struct pvr2_hdw *);
+
+/* Execute hard reset command (after this point it's likely that the
+   encoder will have to be reconfigured).  This also clears the "useless"
+   state. */
+int pvr2_hdw_cmd_deep_reset(struct pvr2_hdw *);
+
+/* Execute simple reset command */
+int pvr2_hdw_cmd_powerup(struct pvr2_hdw *);
+
+/* Order decoder to reset */
+int pvr2_hdw_cmd_decoder_reset(struct pvr2_hdw *);
+
+/* Direct manipulation of GPIO bits */
+int pvr2_hdw_gpio_get_dir(struct pvr2_hdw *hdw,u32 *);
+int pvr2_hdw_gpio_get_out(struct pvr2_hdw *hdw,u32 *);
+int pvr2_hdw_gpio_get_in(struct pvr2_hdw *hdw,u32 *);
+int pvr2_hdw_gpio_chg_dir(struct pvr2_hdw *hdw,u32 msk,u32 val);
+int pvr2_hdw_gpio_chg_out(struct pvr2_hdw *hdw,u32 msk,u32 val);
+
+/* This data structure is specifically for the next function... */
+struct pvr2_hdw_debug_info {
+	int big_lock_held;
+	int ctl_lock_held;
+	int flag_disconnected;
+	int flag_init_ok;
+	int flag_ok;
+	int fw1_state;
+	int flag_decoder_missed;
+	int flag_tripped;
+	int state_encoder_ok;
+	int state_encoder_run;
+	int state_decoder_run;
+	int state_decoder_ready;
+	int state_usbstream_run;
+	int state_decoder_quiescent;
+	int state_pipeline_config;
+	int state_pipeline_req;
+	int state_pipeline_pause;
+	int state_pipeline_idle;
+	int cmd_debug_state;
+	int cmd_debug_write_len;
+	int cmd_debug_read_len;
+	int cmd_debug_write_pend;
+	int cmd_debug_read_pend;
+	int cmd_debug_timeout;
+	int cmd_debug_rstatus;
+	int cmd_debug_wstatus;
+	unsigned char cmd_code;
+};
+
+/* Non-intrusively retrieve internal state info - this is useful for
+   diagnosing lockups.  Note that this operation is completed without any
+   kind of locking and so it is not atomic and may yield inconsistent
+   results.  This is *purely* a debugging aid. */
+void pvr2_hdw_get_debug_info_unlocked(const struct pvr2_hdw *hdw,
+				      struct pvr2_hdw_debug_info *);
+
+/* Intrusively retrieve internal state info - this is useful for
+   diagnosing overall driver state.  This operation synchronizes against
+   the overall driver mutex - so if there are locking problems this will
+   likely hang!  This is *purely* a debugging aid. */
+void pvr2_hdw_get_debug_info_locked(struct pvr2_hdw *hdw,
+				    struct pvr2_hdw_debug_info *);
+
+/* Report out several lines of text that describes driver internal state.
+   Results are written into the passed-in buffer. */
+unsigned int pvr2_hdw_state_report(struct pvr2_hdw *hdw,
+				   char *buf_ptr,unsigned int buf_size);
+
+/* Cause modules to log their state once */
+void pvr2_hdw_trigger_module_log(struct pvr2_hdw *hdw);
+
+/* Cause encoder firmware to be uploaded into the device.  This is normally
+   done autonomously, but the interface is exported here because it is also
+   a debugging aid. */
+int pvr2_upload_firmware2(struct pvr2_hdw *hdw);
+
+#endif /* __PVRUSB2_HDW_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-i2c-core.c b/drivers/media/usb/pvrusb2/pvrusb2-i2c-core.c
new file mode 100644
index 0000000..f3003ca
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-i2c-core.c
@@ -0,0 +1,667 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <media/i2c/ir-kbd-i2c.h>
+#include "pvrusb2-i2c-core.h"
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-debug.h"
+#include "pvrusb2-fx2-cmd.h"
+#include "pvrusb2.h"
+
+#define trace_i2c(...) pvr2_trace(PVR2_TRACE_I2C,__VA_ARGS__)
+
+/*
+
+  This module attempts to implement a compliant I2C adapter for the pvrusb2
+  device.
+
+*/
+
+static unsigned int i2c_scan;
+module_param(i2c_scan, int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(i2c_scan,"scan i2c bus at insmod time");
+
+static int ir_mode[PVR_NUM] = { [0 ... PVR_NUM-1] = 1 };
+module_param_array(ir_mode, int, NULL, 0444);
+MODULE_PARM_DESC(ir_mode,"specify: 0=disable IR reception, 1=normal IR");
+
+static int pvr2_disable_ir_video;
+module_param_named(disable_autoload_ir_video, pvr2_disable_ir_video,
+		   int, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(disable_autoload_ir_video,
+		 "1=do not try to autoload ir_video IR receiver");
+
+static int pvr2_i2c_write(struct pvr2_hdw *hdw, /* Context */
+			  u8 i2c_addr,      /* I2C address we're talking to */
+			  u8 *data,         /* Data to write */
+			  u16 length)       /* Size of data to write */
+{
+	/* Return value - default 0 means success */
+	int ret;
+
+
+	if (!data) length = 0;
+	if (length > (sizeof(hdw->cmd_buffer) - 3)) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Killing an I2C write to %u that is too large (desired=%u limit=%u)",
+			   i2c_addr,
+			   length,(unsigned int)(sizeof(hdw->cmd_buffer) - 3));
+		return -ENOTSUPP;
+	}
+
+	LOCK_TAKE(hdw->ctl_lock);
+
+	/* Clear the command buffer (likely to be paranoia) */
+	memset(hdw->cmd_buffer, 0, sizeof(hdw->cmd_buffer));
+
+	/* Set up command buffer for an I2C write */
+	hdw->cmd_buffer[0] = FX2CMD_I2C_WRITE;      /* write prefix */
+	hdw->cmd_buffer[1] = i2c_addr;  /* i2c addr of chip */
+	hdw->cmd_buffer[2] = length;    /* length of what follows */
+	if (length) memcpy(hdw->cmd_buffer + 3, data, length);
+
+	/* Do the operation */
+	ret = pvr2_send_request(hdw,
+				hdw->cmd_buffer,
+				length + 3,
+				hdw->cmd_buffer,
+				1);
+	if (!ret) {
+		if (hdw->cmd_buffer[0] != 8) {
+			ret = -EIO;
+			if (hdw->cmd_buffer[0] != 7) {
+				trace_i2c("unexpected status from i2_write[%d]: %d",
+					  i2c_addr,hdw->cmd_buffer[0]);
+			}
+		}
+	}
+
+	LOCK_GIVE(hdw->ctl_lock);
+
+	return ret;
+}
+
+static int pvr2_i2c_read(struct pvr2_hdw *hdw, /* Context */
+			 u8 i2c_addr,       /* I2C address we're talking to */
+			 u8 *data,          /* Data to write */
+			 u16 dlen,          /* Size of data to write */
+			 u8 *res,           /* Where to put data we read */
+			 u16 rlen)          /* Amount of data to read */
+{
+	/* Return value - default 0 means success */
+	int ret;
+
+
+	if (!data) dlen = 0;
+	if (dlen > (sizeof(hdw->cmd_buffer) - 4)) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Killing an I2C read to %u that has wlen too large (desired=%u limit=%u)",
+			   i2c_addr,
+			   dlen,(unsigned int)(sizeof(hdw->cmd_buffer) - 4));
+		return -ENOTSUPP;
+	}
+	if (res && (rlen > (sizeof(hdw->cmd_buffer) - 1))) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Killing an I2C read to %u that has rlen too large (desired=%u limit=%u)",
+			   i2c_addr,
+			   rlen,(unsigned int)(sizeof(hdw->cmd_buffer) - 1));
+		return -ENOTSUPP;
+	}
+
+	LOCK_TAKE(hdw->ctl_lock);
+
+	/* Clear the command buffer (likely to be paranoia) */
+	memset(hdw->cmd_buffer, 0, sizeof(hdw->cmd_buffer));
+
+	/* Set up command buffer for an I2C write followed by a read */
+	hdw->cmd_buffer[0] = FX2CMD_I2C_READ;  /* read prefix */
+	hdw->cmd_buffer[1] = dlen;  /* arg length */
+	hdw->cmd_buffer[2] = rlen;  /* answer length. Device will send one
+				       more byte (status). */
+	hdw->cmd_buffer[3] = i2c_addr;  /* i2c addr of chip */
+	if (dlen) memcpy(hdw->cmd_buffer + 4, data, dlen);
+
+	/* Do the operation */
+	ret = pvr2_send_request(hdw,
+				hdw->cmd_buffer,
+				4 + dlen,
+				hdw->cmd_buffer,
+				rlen + 1);
+	if (!ret) {
+		if (hdw->cmd_buffer[0] != 8) {
+			ret = -EIO;
+			if (hdw->cmd_buffer[0] != 7) {
+				trace_i2c("unexpected status from i2_read[%d]: %d",
+					  i2c_addr,hdw->cmd_buffer[0]);
+			}
+		}
+	}
+
+	/* Copy back the result */
+	if (res && rlen) {
+		if (ret) {
+			/* Error, just blank out the return buffer */
+			memset(res, 0, rlen);
+		} else {
+			memcpy(res, hdw->cmd_buffer + 1, rlen);
+		}
+	}
+
+	LOCK_GIVE(hdw->ctl_lock);
+
+	return ret;
+}
+
+/* This is the common low level entry point for doing I2C operations to the
+   hardware. */
+static int pvr2_i2c_basic_op(struct pvr2_hdw *hdw,
+			     u8 i2c_addr,
+			     u8 *wdata,
+			     u16 wlen,
+			     u8 *rdata,
+			     u16 rlen)
+{
+	if (!rdata) rlen = 0;
+	if (!wdata) wlen = 0;
+	if (rlen || !wlen) {
+		return pvr2_i2c_read(hdw,i2c_addr,wdata,wlen,rdata,rlen);
+	} else {
+		return pvr2_i2c_write(hdw,i2c_addr,wdata,wlen);
+	}
+}
+
+
+/* This is a special entry point for cases of I2C transaction attempts to
+   the IR receiver.  The implementation here simulates the IR receiver by
+   issuing a command to the FX2 firmware and using that response to return
+   what the real I2C receiver would have returned.  We use this for 24xxx
+   devices, where the IR receiver chip has been removed and replaced with
+   FX2 related logic. */
+static int i2c_24xxx_ir(struct pvr2_hdw *hdw,
+			u8 i2c_addr,u8 *wdata,u16 wlen,u8 *rdata,u16 rlen)
+{
+	u8 dat[4];
+	unsigned int stat;
+
+	if (!(rlen || wlen)) {
+		/* This is a probe attempt.  Just let it succeed. */
+		return 0;
+	}
+
+	/* We don't understand this kind of transaction */
+	if ((wlen != 0) || (rlen == 0)) return -EIO;
+
+	if (rlen < 3) {
+		/* Mike Isely <isely@pobox.com> Appears to be a probe
+		   attempt from lirc.  Just fill in zeroes and return.  If
+		   we try instead to do the full transaction here, then bad
+		   things seem to happen within the lirc driver module
+		   (version 0.8.0-7 sources from Debian, when run under
+		   vanilla 2.6.17.6 kernel) - and I don't have the patience
+		   to chase it down. */
+		if (rlen > 0) rdata[0] = 0;
+		if (rlen > 1) rdata[1] = 0;
+		return 0;
+	}
+
+	/* Issue a command to the FX2 to read the IR receiver. */
+	LOCK_TAKE(hdw->ctl_lock); do {
+		hdw->cmd_buffer[0] = FX2CMD_GET_IR_CODE;
+		stat = pvr2_send_request(hdw,
+					 hdw->cmd_buffer,1,
+					 hdw->cmd_buffer,4);
+		dat[0] = hdw->cmd_buffer[0];
+		dat[1] = hdw->cmd_buffer[1];
+		dat[2] = hdw->cmd_buffer[2];
+		dat[3] = hdw->cmd_buffer[3];
+	} while (0); LOCK_GIVE(hdw->ctl_lock);
+
+	/* Give up if that operation failed. */
+	if (stat != 0) return stat;
+
+	/* Mangle the results into something that looks like the real IR
+	   receiver. */
+	rdata[2] = 0xc1;
+	if (dat[0] != 1) {
+		/* No code received. */
+		rdata[0] = 0;
+		rdata[1] = 0;
+	} else {
+		u16 val;
+		/* Mash the FX2 firmware-provided IR code into something
+		   that the normal i2c chip-level driver expects. */
+		val = dat[1];
+		val <<= 8;
+		val |= dat[2];
+		val >>= 1;
+		val &= ~0x0003;
+		val |= 0x8000;
+		rdata[0] = (val >> 8) & 0xffu;
+		rdata[1] = val & 0xffu;
+	}
+
+	return 0;
+}
+
+/* This is a special entry point that is entered if an I2C operation is
+   attempted to a wm8775 chip on model 24xxx hardware.  Autodetect of this
+   part doesn't work, but we know it is really there.  So let's look for
+   the autodetect attempt and just return success if we see that. */
+static int i2c_hack_wm8775(struct pvr2_hdw *hdw,
+			   u8 i2c_addr,u8 *wdata,u16 wlen,u8 *rdata,u16 rlen)
+{
+	if (!(rlen || wlen)) {
+		// This is a probe attempt.  Just let it succeed.
+		return 0;
+	}
+	return pvr2_i2c_basic_op(hdw,i2c_addr,wdata,wlen,rdata,rlen);
+}
+
+/* This is an entry point designed to always fail any attempt to perform a
+   transfer.  We use this to cause certain I2C addresses to not be
+   probed. */
+static int i2c_black_hole(struct pvr2_hdw *hdw,
+			   u8 i2c_addr,u8 *wdata,u16 wlen,u8 *rdata,u16 rlen)
+{
+	return -EIO;
+}
+
+/* This is a special entry point that is entered if an I2C operation is
+   attempted to a cx25840 chip on model 24xxx hardware.  This chip can
+   sometimes wedge itself.  Worse still, when this happens msp3400 can
+   falsely detect this part and then the system gets hosed up after msp3400
+   gets confused and dies.  What we want to do here is try to keep msp3400
+   away and also try to notice if the chip is wedged and send a warning to
+   the system log. */
+static int i2c_hack_cx25840(struct pvr2_hdw *hdw,
+			    u8 i2c_addr,u8 *wdata,u16 wlen,u8 *rdata,u16 rlen)
+{
+	int ret;
+	unsigned int subaddr;
+	u8 wbuf[2];
+	int state = hdw->i2c_cx25840_hack_state;
+
+	if (!(rlen || wlen)) {
+		// Probe attempt - always just succeed and don't bother the
+		// hardware (this helps to make the state machine further
+		// down somewhat easier).
+		return 0;
+	}
+
+	if (state == 3) {
+		return pvr2_i2c_basic_op(hdw,i2c_addr,wdata,wlen,rdata,rlen);
+	}
+
+	/* We're looking for the exact pattern where the revision register
+	   is being read.  The cx25840 module will always look at the
+	   revision register first.  Any other pattern of access therefore
+	   has to be a probe attempt from somebody else so we'll reject it.
+	   Normally we could just let each client just probe the part
+	   anyway, but when the cx25840 is wedged, msp3400 will get a false
+	   positive and that just screws things up... */
+
+	if (wlen == 0) {
+		switch (state) {
+		case 1: subaddr = 0x0100; break;
+		case 2: subaddr = 0x0101; break;
+		default: goto fail;
+		}
+	} else if (wlen == 2) {
+		subaddr = (wdata[0] << 8) | wdata[1];
+		switch (subaddr) {
+		case 0x0100: state = 1; break;
+		case 0x0101: state = 2; break;
+		default: goto fail;
+		}
+	} else {
+		goto fail;
+	}
+	if (!rlen) goto success;
+	state = 0;
+	if (rlen != 1) goto fail;
+
+	/* If we get to here then we have a legitimate read for one of the
+	   two revision bytes, so pass it through. */
+	wbuf[0] = subaddr >> 8;
+	wbuf[1] = subaddr;
+	ret = pvr2_i2c_basic_op(hdw,i2c_addr,wbuf,2,rdata,rlen);
+
+	if ((ret != 0) || (*rdata == 0x04) || (*rdata == 0x0a)) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "WARNING: Detected a wedged cx25840 chip; the device will not work.");
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "WARNING: Try power cycling the pvrusb2 device.");
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "WARNING: Disabling further access to the device to prevent other foul-ups.");
+		// This blocks all further communication with the part.
+		hdw->i2c_func[0x44] = NULL;
+		pvr2_hdw_render_useless(hdw);
+		goto fail;
+	}
+
+	/* Success! */
+	pvr2_trace(PVR2_TRACE_CHIPS,"cx25840 appears to be OK.");
+	state = 3;
+
+ success:
+	hdw->i2c_cx25840_hack_state = state;
+	return 0;
+
+ fail:
+	hdw->i2c_cx25840_hack_state = state;
+	return -EIO;
+}
+
+/* This is a very, very limited I2C adapter implementation.  We can only
+   support what we actually know will work on the device... */
+static int pvr2_i2c_xfer(struct i2c_adapter *i2c_adap,
+			 struct i2c_msg msgs[],
+			 int num)
+{
+	int ret = -ENOTSUPP;
+	pvr2_i2c_func funcp = NULL;
+	struct pvr2_hdw *hdw = (struct pvr2_hdw *)(i2c_adap->algo_data);
+
+	if (!num) {
+		ret = -EINVAL;
+		goto done;
+	}
+	if (msgs[0].addr < PVR2_I2C_FUNC_CNT) {
+		funcp = hdw->i2c_func[msgs[0].addr];
+	}
+	if (!funcp) {
+		ret = -EIO;
+		goto done;
+	}
+
+	if (num == 1) {
+		if (msgs[0].flags & I2C_M_RD) {
+			/* Simple read */
+			u16 tcnt,bcnt,offs;
+			if (!msgs[0].len) {
+				/* Length == 0 read.  This is a probe. */
+				if (funcp(hdw,msgs[0].addr,NULL,0,NULL,0)) {
+					ret = -EIO;
+					goto done;
+				}
+				ret = 1;
+				goto done;
+			}
+			/* If the read is short enough we'll do the whole
+			   thing atomically.  Otherwise we have no choice
+			   but to break apart the reads. */
+			tcnt = msgs[0].len;
+			offs = 0;
+			while (tcnt) {
+				bcnt = tcnt;
+				if (bcnt > sizeof(hdw->cmd_buffer)-1) {
+					bcnt = sizeof(hdw->cmd_buffer)-1;
+				}
+				if (funcp(hdw,msgs[0].addr,NULL,0,
+					  msgs[0].buf+offs,bcnt)) {
+					ret = -EIO;
+					goto done;
+				}
+				offs += bcnt;
+				tcnt -= bcnt;
+			}
+			ret = 1;
+			goto done;
+		} else {
+			/* Simple write */
+			ret = 1;
+			if (funcp(hdw,msgs[0].addr,
+				  msgs[0].buf,msgs[0].len,NULL,0)) {
+				ret = -EIO;
+			}
+			goto done;
+		}
+	} else if (num == 2) {
+		if (msgs[0].addr != msgs[1].addr) {
+			trace_i2c("i2c refusing 2 phase transfer with conflicting target addresses");
+			ret = -ENOTSUPP;
+			goto done;
+		}
+		if ((!((msgs[0].flags & I2C_M_RD))) &&
+		    (msgs[1].flags & I2C_M_RD)) {
+			u16 tcnt,bcnt,wcnt,offs;
+			/* Write followed by atomic read.  If the read
+			   portion is short enough we'll do the whole thing
+			   atomically.  Otherwise we have no choice but to
+			   break apart the reads. */
+			tcnt = msgs[1].len;
+			wcnt = msgs[0].len;
+			offs = 0;
+			while (tcnt || wcnt) {
+				bcnt = tcnt;
+				if (bcnt > sizeof(hdw->cmd_buffer)-1) {
+					bcnt = sizeof(hdw->cmd_buffer)-1;
+				}
+				if (funcp(hdw,msgs[0].addr,
+					  msgs[0].buf,wcnt,
+					  msgs[1].buf+offs,bcnt)) {
+					ret = -EIO;
+					goto done;
+				}
+				offs += bcnt;
+				tcnt -= bcnt;
+				wcnt = 0;
+			}
+			ret = 2;
+			goto done;
+		} else {
+			trace_i2c("i2c refusing complex transfer read0=%d read1=%d",
+				  (msgs[0].flags & I2C_M_RD),
+				  (msgs[1].flags & I2C_M_RD));
+		}
+	} else {
+		trace_i2c("i2c refusing %d phase transfer",num);
+	}
+
+ done:
+	if (pvrusb2_debug & PVR2_TRACE_I2C_TRAF) {
+		unsigned int idx,offs,cnt;
+		for (idx = 0; idx < num; idx++) {
+			cnt = msgs[idx].len;
+			printk(KERN_INFO
+			       "pvrusb2 i2c xfer %u/%u: addr=0x%x len=%d %s",
+			       idx+1,num,
+			       msgs[idx].addr,
+			       cnt,
+			       (msgs[idx].flags & I2C_M_RD ?
+				"read" : "write"));
+			if ((ret > 0) || !(msgs[idx].flags & I2C_M_RD)) {
+				if (cnt > 8) cnt = 8;
+				printk(KERN_CONT " [");
+				for (offs = 0; offs < cnt; offs++) {
+					if (offs) printk(KERN_CONT " ");
+					printk(KERN_CONT "%02x",msgs[idx].buf[offs]);
+				}
+				if (offs < cnt) printk(KERN_CONT " ...");
+				printk(KERN_CONT "]");
+			}
+			if (idx+1 == num) {
+				printk(KERN_CONT " result=%d",ret);
+			}
+			printk(KERN_CONT "\n");
+		}
+		if (!num) {
+			printk(KERN_INFO
+			       "pvrusb2 i2c xfer null transfer result=%d\n",
+			       ret);
+		}
+	}
+	return ret;
+}
+
+static u32 pvr2_i2c_functionality(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_SMBUS_EMUL | I2C_FUNC_I2C;
+}
+
+static const struct i2c_algorithm pvr2_i2c_algo_template = {
+	.master_xfer   = pvr2_i2c_xfer,
+	.functionality = pvr2_i2c_functionality,
+};
+
+static const struct i2c_adapter pvr2_i2c_adap_template = {
+	.owner         = THIS_MODULE,
+	.class	       = 0,
+};
+
+
+/* Return true if device exists at given address */
+static int do_i2c_probe(struct pvr2_hdw *hdw, int addr)
+{
+	struct i2c_msg msg[1];
+	int rc;
+	msg[0].addr = 0;
+	msg[0].flags = I2C_M_RD;
+	msg[0].len = 0;
+	msg[0].buf = NULL;
+	msg[0].addr = addr;
+	rc = i2c_transfer(&hdw->i2c_adap, msg, ARRAY_SIZE(msg));
+	return rc == 1;
+}
+
+static void do_i2c_scan(struct pvr2_hdw *hdw)
+{
+	int i;
+	printk(KERN_INFO "%s: i2c scan beginning\n", hdw->name);
+	for (i = 0; i < 128; i++) {
+		if (do_i2c_probe(hdw, i)) {
+			printk(KERN_INFO "%s: i2c scan: found device @ 0x%x\n",
+			       hdw->name, i);
+		}
+	}
+	printk(KERN_INFO "%s: i2c scan done.\n", hdw->name);
+}
+
+static void pvr2_i2c_register_ir(struct pvr2_hdw *hdw)
+{
+	struct i2c_board_info info;
+	struct IR_i2c_init_data *init_data = &hdw->ir_init_data;
+	if (pvr2_disable_ir_video) {
+		pvr2_trace(PVR2_TRACE_INFO,
+			   "Automatic binding of ir_video has been disabled.");
+		return;
+	}
+	memset(&info, 0, sizeof(struct i2c_board_info));
+	switch (hdw->ir_scheme_active) {
+	case PVR2_IR_SCHEME_24XXX: /* FX2-controlled IR */
+	case PVR2_IR_SCHEME_29XXX: /* Original 29xxx device */
+		init_data->ir_codes              = RC_MAP_HAUPPAUGE;
+		init_data->internal_get_key_func = IR_KBD_GET_KEY_HAUP;
+		init_data->type                  = RC_PROTO_BIT_RC5;
+		init_data->name                  = hdw->hdw_desc->description;
+		init_data->polling_interval      = 100; /* ms From ir-kbd-i2c */
+		/* IR Receiver */
+		info.addr          = 0x18;
+		info.platform_data = init_data;
+		strlcpy(info.type, "ir_video", I2C_NAME_SIZE);
+		pvr2_trace(PVR2_TRACE_INFO, "Binding %s to i2c address 0x%02x.",
+			   info.type, info.addr);
+		i2c_new_device(&hdw->i2c_adap, &info);
+		break;
+	case PVR2_IR_SCHEME_ZILOG:     /* HVR-1950 style */
+	case PVR2_IR_SCHEME_24XXX_MCE: /* 24xxx MCE device */
+		init_data->ir_codes = RC_MAP_HAUPPAUGE;
+		init_data->internal_get_key_func = IR_KBD_GET_KEY_HAUP_XVR;
+		init_data->type = RC_PROTO_BIT_RC5 | RC_PROTO_BIT_RC6_MCE |
+							RC_PROTO_BIT_RC6_6A_32;
+		init_data->name = hdw->hdw_desc->description;
+		/* IR Transceiver */
+		info.addr = 0x71;
+		info.platform_data = init_data;
+		strlcpy(info.type, "ir_z8f0811_haup", I2C_NAME_SIZE);
+		pvr2_trace(PVR2_TRACE_INFO, "Binding %s to i2c address 0x%02x.",
+			   info.type, info.addr);
+		i2c_new_device(&hdw->i2c_adap, &info);
+		break;
+	default:
+		/* The device either doesn't support I2C-based IR or we
+		   don't know (yet) how to operate IR on the device. */
+		break;
+	}
+}
+
+void pvr2_i2c_core_init(struct pvr2_hdw *hdw)
+{
+	unsigned int idx;
+
+	/* The default action for all possible I2C addresses is just to do
+	   the transfer normally. */
+	for (idx = 0; idx < PVR2_I2C_FUNC_CNT; idx++) {
+		hdw->i2c_func[idx] = pvr2_i2c_basic_op;
+	}
+
+	/* However, deal with various special cases for 24xxx hardware. */
+	if (ir_mode[hdw->unit_number] == 0) {
+		printk(KERN_INFO "%s: IR disabled\n",hdw->name);
+		hdw->i2c_func[0x18] = i2c_black_hole;
+	} else if (ir_mode[hdw->unit_number] == 1) {
+		if (hdw->ir_scheme_active == PVR2_IR_SCHEME_24XXX) {
+			/* Set up translation so that our IR looks like a
+			   29xxx device */
+			hdw->i2c_func[0x18] = i2c_24xxx_ir;
+		}
+	}
+	if (hdw->hdw_desc->flag_has_cx25840) {
+		hdw->i2c_func[0x44] = i2c_hack_cx25840;
+	}
+	if (hdw->hdw_desc->flag_has_wm8775) {
+		hdw->i2c_func[0x1b] = i2c_hack_wm8775;
+	}
+
+	// Configure the adapter and set up everything else related to it.
+	hdw->i2c_adap = pvr2_i2c_adap_template;
+	hdw->i2c_algo = pvr2_i2c_algo_template;
+	strlcpy(hdw->i2c_adap.name,hdw->name,sizeof(hdw->i2c_adap.name));
+	hdw->i2c_adap.dev.parent = &hdw->usb_dev->dev;
+	hdw->i2c_adap.algo = &hdw->i2c_algo;
+	hdw->i2c_adap.algo_data = hdw;
+	hdw->i2c_linked = !0;
+	i2c_set_adapdata(&hdw->i2c_adap, &hdw->v4l2_dev);
+	i2c_add_adapter(&hdw->i2c_adap);
+	if (hdw->i2c_func[0x18] == i2c_24xxx_ir) {
+		/* Probe for a different type of IR receiver on this
+		   device.  This is really the only way to differentiate
+		   older 24xxx devices from 24xxx variants that include an
+		   IR blaster.  If the IR blaster is present, the IR
+		   receiver is part of that chip and thus we must disable
+		   the emulated IR receiver. */
+		if (do_i2c_probe(hdw, 0x71)) {
+			pvr2_trace(PVR2_TRACE_INFO,
+				   "Device has newer IR hardware; disabling unneeded virtual IR device");
+			hdw->i2c_func[0x18] = NULL;
+			/* Remember that this is a different device... */
+			hdw->ir_scheme_active = PVR2_IR_SCHEME_24XXX_MCE;
+		}
+	}
+	if (i2c_scan) do_i2c_scan(hdw);
+
+	pvr2_i2c_register_ir(hdw);
+}
+
+void pvr2_i2c_core_done(struct pvr2_hdw *hdw)
+{
+	if (hdw->i2c_linked) {
+		i2c_del_adapter(&hdw->i2c_adap);
+		hdw->i2c_linked = 0;
+	}
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-i2c-core.h b/drivers/media/usb/pvrusb2/pvrusb2-i2c-core.h
new file mode 100644
index 0000000..1c44dee
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-i2c-core.h
@@ -0,0 +1,25 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+#ifndef __PVRUSB2_I2C_CORE_H
+#define __PVRUSB2_I2C_CORE_H
+
+struct pvr2_hdw;
+
+void pvr2_i2c_core_init(struct pvr2_hdw *);
+void pvr2_i2c_core_done(struct pvr2_hdw *);
+
+
+#endif /* __PVRUSB2_I2C_ADAPTER_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-io.c b/drivers/media/usb/pvrusb2/pvrusb2-io.c
new file mode 100644
index 0000000..6d153fc
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-io.c
@@ -0,0 +1,682 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#include "pvrusb2-io.h"
+#include "pvrusb2-debug.h"
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+
+static const char *pvr2_buffer_state_decode(enum pvr2_buffer_state);
+
+#define BUFFER_SIG 0x47653271
+
+// #define SANITY_CHECK_BUFFERS
+
+
+#ifdef SANITY_CHECK_BUFFERS
+#define BUFFER_CHECK(bp) do { \
+	if ((bp)->signature != BUFFER_SIG) { \
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS, \
+		"Buffer %p is bad at %s:%d", \
+		(bp), __FILE__, __LINE__); \
+		pvr2_buffer_describe(bp, "BadSig"); \
+		BUG(); \
+	} \
+} while (0)
+#else
+#define BUFFER_CHECK(bp) do {} while (0)
+#endif
+
+struct pvr2_stream {
+	/* Buffers queued for reading */
+	struct list_head queued_list;
+	unsigned int q_count;
+	unsigned int q_bcount;
+	/* Buffers with retrieved data */
+	struct list_head ready_list;
+	unsigned int r_count;
+	unsigned int r_bcount;
+	/* Buffers available for use */
+	struct list_head idle_list;
+	unsigned int i_count;
+	unsigned int i_bcount;
+	/* Pointers to all buffers */
+	struct pvr2_buffer **buffers;
+	/* Array size of buffers */
+	unsigned int buffer_slot_count;
+	/* Total buffers actually in circulation */
+	unsigned int buffer_total_count;
+	/* Designed number of buffers to be in circulation */
+	unsigned int buffer_target_count;
+	/* Executed when ready list become non-empty */
+	pvr2_stream_callback callback_func;
+	void *callback_data;
+	/* Context for transfer endpoint */
+	struct usb_device *dev;
+	int endpoint;
+	/* Overhead for mutex enforcement */
+	spinlock_t list_lock;
+	struct mutex mutex;
+	/* Tracking state for tolerating errors */
+	unsigned int fail_count;
+	unsigned int fail_tolerance;
+
+	unsigned int buffers_processed;
+	unsigned int buffers_failed;
+	unsigned int bytes_processed;
+};
+
+struct pvr2_buffer {
+	int id;
+	int signature;
+	enum pvr2_buffer_state state;
+	void *ptr;               /* Pointer to storage area */
+	unsigned int max_count;  /* Size of storage area */
+	unsigned int used_count; /* Amount of valid data in storage area */
+	int status;              /* Transfer result status */
+	struct pvr2_stream *stream;
+	struct list_head list_overhead;
+	struct urb *purb;
+};
+
+static const char *pvr2_buffer_state_decode(enum pvr2_buffer_state st)
+{
+	switch (st) {
+	case pvr2_buffer_state_none: return "none";
+	case pvr2_buffer_state_idle: return "idle";
+	case pvr2_buffer_state_queued: return "queued";
+	case pvr2_buffer_state_ready: return "ready";
+	}
+	return "unknown";
+}
+
+#ifdef SANITY_CHECK_BUFFERS
+static void pvr2_buffer_describe(struct pvr2_buffer *bp, const char *msg)
+{
+	pvr2_trace(PVR2_TRACE_INFO,
+		   "buffer%s%s %p state=%s id=%d status=%d stream=%p purb=%p sig=0x%x",
+		   (msg ? " " : ""),
+		   (msg ? msg : ""),
+		   bp,
+		   (bp ? pvr2_buffer_state_decode(bp->state) : "(invalid)"),
+		   (bp ? bp->id : 0),
+		   (bp ? bp->status : 0),
+		   (bp ? bp->stream : NULL),
+		   (bp ? bp->purb : NULL),
+		   (bp ? bp->signature : 0));
+}
+#endif  /*  SANITY_CHECK_BUFFERS  */
+
+static void pvr2_buffer_remove(struct pvr2_buffer *bp)
+{
+	unsigned int *cnt;
+	unsigned int *bcnt;
+	unsigned int ccnt;
+	struct pvr2_stream *sp = bp->stream;
+	switch (bp->state) {
+	case pvr2_buffer_state_idle:
+		cnt = &sp->i_count;
+		bcnt = &sp->i_bcount;
+		ccnt = bp->max_count;
+		break;
+	case pvr2_buffer_state_queued:
+		cnt = &sp->q_count;
+		bcnt = &sp->q_bcount;
+		ccnt = bp->max_count;
+		break;
+	case pvr2_buffer_state_ready:
+		cnt = &sp->r_count;
+		bcnt = &sp->r_bcount;
+		ccnt = bp->used_count;
+		break;
+	default:
+		return;
+	}
+	list_del_init(&bp->list_overhead);
+	(*cnt)--;
+	(*bcnt) -= ccnt;
+	pvr2_trace(PVR2_TRACE_BUF_FLOW,
+		   "/*---TRACE_FLOW---*/ bufferPool	%8s dec cap=%07d cnt=%02d",
+		   pvr2_buffer_state_decode(bp->state), *bcnt, *cnt);
+	bp->state = pvr2_buffer_state_none;
+}
+
+static void pvr2_buffer_set_none(struct pvr2_buffer *bp)
+{
+	unsigned long irq_flags;
+	struct pvr2_stream *sp;
+	BUFFER_CHECK(bp);
+	sp = bp->stream;
+	pvr2_trace(PVR2_TRACE_BUF_FLOW,
+		   "/*---TRACE_FLOW---*/ bufferState    %p %6s --> %6s",
+		   bp,
+		   pvr2_buffer_state_decode(bp->state),
+		   pvr2_buffer_state_decode(pvr2_buffer_state_none));
+	spin_lock_irqsave(&sp->list_lock, irq_flags);
+	pvr2_buffer_remove(bp);
+	spin_unlock_irqrestore(&sp->list_lock, irq_flags);
+}
+
+static int pvr2_buffer_set_ready(struct pvr2_buffer *bp)
+{
+	int fl;
+	unsigned long irq_flags;
+	struct pvr2_stream *sp;
+	BUFFER_CHECK(bp);
+	sp = bp->stream;
+	pvr2_trace(PVR2_TRACE_BUF_FLOW,
+		   "/*---TRACE_FLOW---*/ bufferState    %p %6s --> %6s",
+		   bp,
+		   pvr2_buffer_state_decode(bp->state),
+		   pvr2_buffer_state_decode(pvr2_buffer_state_ready));
+	spin_lock_irqsave(&sp->list_lock, irq_flags);
+	fl = (sp->r_count == 0);
+	pvr2_buffer_remove(bp);
+	list_add_tail(&bp->list_overhead, &sp->ready_list);
+	bp->state = pvr2_buffer_state_ready;
+	(sp->r_count)++;
+	sp->r_bcount += bp->used_count;
+	pvr2_trace(PVR2_TRACE_BUF_FLOW,
+		   "/*---TRACE_FLOW---*/ bufferPool	%8s inc cap=%07d cnt=%02d",
+		   pvr2_buffer_state_decode(bp->state),
+		   sp->r_bcount, sp->r_count);
+	spin_unlock_irqrestore(&sp->list_lock, irq_flags);
+	return fl;
+}
+
+static void pvr2_buffer_set_idle(struct pvr2_buffer *bp)
+{
+	unsigned long irq_flags;
+	struct pvr2_stream *sp;
+	BUFFER_CHECK(bp);
+	sp = bp->stream;
+	pvr2_trace(PVR2_TRACE_BUF_FLOW,
+		   "/*---TRACE_FLOW---*/ bufferState    %p %6s --> %6s",
+		   bp,
+		   pvr2_buffer_state_decode(bp->state),
+		   pvr2_buffer_state_decode(pvr2_buffer_state_idle));
+	spin_lock_irqsave(&sp->list_lock, irq_flags);
+	pvr2_buffer_remove(bp);
+	list_add_tail(&bp->list_overhead, &sp->idle_list);
+	bp->state = pvr2_buffer_state_idle;
+	(sp->i_count)++;
+	sp->i_bcount += bp->max_count;
+	pvr2_trace(PVR2_TRACE_BUF_FLOW,
+		   "/*---TRACE_FLOW---*/ bufferPool	%8s inc cap=%07d cnt=%02d",
+		   pvr2_buffer_state_decode(bp->state),
+		   sp->i_bcount, sp->i_count);
+	spin_unlock_irqrestore(&sp->list_lock, irq_flags);
+}
+
+static void pvr2_buffer_set_queued(struct pvr2_buffer *bp)
+{
+	unsigned long irq_flags;
+	struct pvr2_stream *sp;
+	BUFFER_CHECK(bp);
+	sp = bp->stream;
+	pvr2_trace(PVR2_TRACE_BUF_FLOW,
+		   "/*---TRACE_FLOW---*/ bufferState    %p %6s --> %6s",
+		   bp,
+		   pvr2_buffer_state_decode(bp->state),
+		   pvr2_buffer_state_decode(pvr2_buffer_state_queued));
+	spin_lock_irqsave(&sp->list_lock, irq_flags);
+	pvr2_buffer_remove(bp);
+	list_add_tail(&bp->list_overhead, &sp->queued_list);
+	bp->state = pvr2_buffer_state_queued;
+	(sp->q_count)++;
+	sp->q_bcount += bp->max_count;
+	pvr2_trace(PVR2_TRACE_BUF_FLOW,
+		   "/*---TRACE_FLOW---*/ bufferPool	%8s inc cap=%07d cnt=%02d",
+		   pvr2_buffer_state_decode(bp->state),
+		   sp->q_bcount, sp->q_count);
+	spin_unlock_irqrestore(&sp->list_lock, irq_flags);
+}
+
+static void pvr2_buffer_wipe(struct pvr2_buffer *bp)
+{
+	if (bp->state == pvr2_buffer_state_queued) {
+		usb_kill_urb(bp->purb);
+	}
+}
+
+static int pvr2_buffer_init(struct pvr2_buffer *bp,
+			    struct pvr2_stream *sp,
+			    unsigned int id)
+{
+	memset(bp, 0, sizeof(*bp));
+	bp->signature = BUFFER_SIG;
+	bp->id = id;
+	pvr2_trace(PVR2_TRACE_BUF_POOL,
+		   "/*---TRACE_FLOW---*/ bufferInit     %p stream=%p", bp, sp);
+	bp->stream = sp;
+	bp->state = pvr2_buffer_state_none;
+	INIT_LIST_HEAD(&bp->list_overhead);
+	bp->purb = usb_alloc_urb(0, GFP_KERNEL);
+	if (! bp->purb) return -ENOMEM;
+#ifdef SANITY_CHECK_BUFFERS
+	pvr2_buffer_describe(bp, "create");
+#endif
+	return 0;
+}
+
+static void pvr2_buffer_done(struct pvr2_buffer *bp)
+{
+#ifdef SANITY_CHECK_BUFFERS
+	pvr2_buffer_describe(bp, "delete");
+#endif
+	pvr2_buffer_wipe(bp);
+	pvr2_buffer_set_none(bp);
+	bp->signature = 0;
+	bp->stream = NULL;
+	usb_free_urb(bp->purb);
+	pvr2_trace(PVR2_TRACE_BUF_POOL, "/*---TRACE_FLOW---*/ bufferDone     %p",
+		   bp);
+}
+
+static int pvr2_stream_buffer_count(struct pvr2_stream *sp, unsigned int cnt)
+{
+	int ret;
+	unsigned int scnt;
+
+	/* Allocate buffers pointer array in multiples of 32 entries */
+	if (cnt == sp->buffer_total_count) return 0;
+
+	pvr2_trace(PVR2_TRACE_BUF_POOL,
+		   "/*---TRACE_FLOW---*/ poolResize	stream=%p cur=%d adj=%+d",
+		   sp,
+		   sp->buffer_total_count,
+		   cnt-sp->buffer_total_count);
+
+	scnt = cnt & ~0x1f;
+	if (cnt > scnt) scnt += 0x20;
+
+	if (cnt > sp->buffer_total_count) {
+		if (scnt > sp->buffer_slot_count) {
+			struct pvr2_buffer **nb;
+
+			nb = kmalloc_array(scnt, sizeof(*nb), GFP_KERNEL);
+			if (!nb) return -ENOMEM;
+			if (sp->buffer_slot_count) {
+				memcpy(nb, sp->buffers,
+				       sp->buffer_slot_count * sizeof(*nb));
+				kfree(sp->buffers);
+			}
+			sp->buffers = nb;
+			sp->buffer_slot_count = scnt;
+		}
+		while (sp->buffer_total_count < cnt) {
+			struct pvr2_buffer *bp;
+			bp = kmalloc(sizeof(*bp), GFP_KERNEL);
+			if (!bp) return -ENOMEM;
+			ret = pvr2_buffer_init(bp, sp, sp->buffer_total_count);
+			if (ret) {
+				kfree(bp);
+				return -ENOMEM;
+			}
+			sp->buffers[sp->buffer_total_count] = bp;
+			(sp->buffer_total_count)++;
+			pvr2_buffer_set_idle(bp);
+		}
+	} else {
+		while (sp->buffer_total_count > cnt) {
+			struct pvr2_buffer *bp;
+			bp = sp->buffers[sp->buffer_total_count - 1];
+			/* Paranoia */
+			sp->buffers[sp->buffer_total_count - 1] = NULL;
+			(sp->buffer_total_count)--;
+			pvr2_buffer_done(bp);
+			kfree(bp);
+		}
+		if (scnt < sp->buffer_slot_count) {
+			struct pvr2_buffer **nb = NULL;
+			if (scnt) {
+				nb = kmemdup(sp->buffers, scnt * sizeof(*nb),
+					     GFP_KERNEL);
+				if (!nb) return -ENOMEM;
+			}
+			kfree(sp->buffers);
+			sp->buffers = nb;
+			sp->buffer_slot_count = scnt;
+		}
+	}
+	return 0;
+}
+
+static int pvr2_stream_achieve_buffer_count(struct pvr2_stream *sp)
+{
+	struct pvr2_buffer *bp;
+	unsigned int cnt;
+
+	if (sp->buffer_total_count == sp->buffer_target_count) return 0;
+
+	pvr2_trace(PVR2_TRACE_BUF_POOL,
+		   "/*---TRACE_FLOW---*/ poolCheck	stream=%p cur=%d tgt=%d",
+		   sp, sp->buffer_total_count, sp->buffer_target_count);
+
+	if (sp->buffer_total_count < sp->buffer_target_count) {
+		return pvr2_stream_buffer_count(sp, sp->buffer_target_count);
+	}
+
+	cnt = 0;
+	while ((sp->buffer_total_count - cnt) > sp->buffer_target_count) {
+		bp = sp->buffers[sp->buffer_total_count - (cnt + 1)];
+		if (bp->state != pvr2_buffer_state_idle) break;
+		cnt++;
+	}
+	if (cnt) {
+		pvr2_stream_buffer_count(sp, sp->buffer_total_count - cnt);
+	}
+
+	return 0;
+}
+
+static void pvr2_stream_internal_flush(struct pvr2_stream *sp)
+{
+	struct list_head *lp;
+	struct pvr2_buffer *bp1;
+	while ((lp = sp->queued_list.next) != &sp->queued_list) {
+		bp1 = list_entry(lp, struct pvr2_buffer, list_overhead);
+		pvr2_buffer_wipe(bp1);
+		/* At this point, we should be guaranteed that no
+		   completion callback may happen on this buffer.  But it's
+		   possible that it might have completed after we noticed
+		   it but before we wiped it.  So double check its status
+		   here first. */
+		if (bp1->state != pvr2_buffer_state_queued) continue;
+		pvr2_buffer_set_idle(bp1);
+	}
+	if (sp->buffer_total_count != sp->buffer_target_count) {
+		pvr2_stream_achieve_buffer_count(sp);
+	}
+}
+
+static void pvr2_stream_init(struct pvr2_stream *sp)
+{
+	spin_lock_init(&sp->list_lock);
+	mutex_init(&sp->mutex);
+	INIT_LIST_HEAD(&sp->queued_list);
+	INIT_LIST_HEAD(&sp->ready_list);
+	INIT_LIST_HEAD(&sp->idle_list);
+}
+
+static void pvr2_stream_done(struct pvr2_stream *sp)
+{
+	mutex_lock(&sp->mutex); do {
+		pvr2_stream_internal_flush(sp);
+		pvr2_stream_buffer_count(sp, 0);
+	} while (0); mutex_unlock(&sp->mutex);
+}
+
+static void buffer_complete(struct urb *urb)
+{
+	struct pvr2_buffer *bp = urb->context;
+	struct pvr2_stream *sp;
+	unsigned long irq_flags;
+	BUFFER_CHECK(bp);
+	sp = bp->stream;
+	bp->used_count = 0;
+	bp->status = 0;
+	pvr2_trace(PVR2_TRACE_BUF_FLOW,
+		   "/*---TRACE_FLOW---*/ bufferComplete %p stat=%d cnt=%d",
+		   bp, urb->status, urb->actual_length);
+	spin_lock_irqsave(&sp->list_lock, irq_flags);
+	if ((!(urb->status)) ||
+	    (urb->status == -ENOENT) ||
+	    (urb->status == -ECONNRESET) ||
+	    (urb->status == -ESHUTDOWN)) {
+		(sp->buffers_processed)++;
+		sp->bytes_processed += urb->actual_length;
+		bp->used_count = urb->actual_length;
+		if (sp->fail_count) {
+			pvr2_trace(PVR2_TRACE_TOLERANCE,
+				   "stream %p transfer ok - fail count reset",
+				   sp);
+			sp->fail_count = 0;
+		}
+	} else if (sp->fail_count < sp->fail_tolerance) {
+		// We can tolerate this error, because we're below the
+		// threshold...
+		(sp->fail_count)++;
+		(sp->buffers_failed)++;
+		pvr2_trace(PVR2_TRACE_TOLERANCE,
+			   "stream %p ignoring error %d - fail count increased to %u",
+			   sp, urb->status, sp->fail_count);
+	} else {
+		(sp->buffers_failed)++;
+		bp->status = urb->status;
+	}
+	spin_unlock_irqrestore(&sp->list_lock, irq_flags);
+	pvr2_buffer_set_ready(bp);
+	if (sp->callback_func) {
+		sp->callback_func(sp->callback_data);
+	}
+}
+
+struct pvr2_stream *pvr2_stream_create(void)
+{
+	struct pvr2_stream *sp;
+	sp = kzalloc(sizeof(*sp), GFP_KERNEL);
+	if (!sp) return sp;
+	pvr2_trace(PVR2_TRACE_INIT, "pvr2_stream_create: sp=%p", sp);
+	pvr2_stream_init(sp);
+	return sp;
+}
+
+void pvr2_stream_destroy(struct pvr2_stream *sp)
+{
+	if (!sp) return;
+	pvr2_trace(PVR2_TRACE_INIT, "pvr2_stream_destroy: sp=%p", sp);
+	pvr2_stream_done(sp);
+	kfree(sp);
+}
+
+void pvr2_stream_setup(struct pvr2_stream *sp,
+		       struct usb_device *dev,
+		       int endpoint,
+		       unsigned int tolerance)
+{
+	mutex_lock(&sp->mutex); do {
+		pvr2_stream_internal_flush(sp);
+		sp->dev = dev;
+		sp->endpoint = endpoint;
+		sp->fail_tolerance = tolerance;
+	} while (0); mutex_unlock(&sp->mutex);
+}
+
+void pvr2_stream_set_callback(struct pvr2_stream *sp,
+			      pvr2_stream_callback func,
+			      void *data)
+{
+	unsigned long irq_flags;
+	mutex_lock(&sp->mutex);
+	do {
+		spin_lock_irqsave(&sp->list_lock, irq_flags);
+		sp->callback_data = data;
+		sp->callback_func = func;
+		spin_unlock_irqrestore(&sp->list_lock, irq_flags);
+	} while (0);
+	mutex_unlock(&sp->mutex);
+}
+
+void pvr2_stream_get_stats(struct pvr2_stream *sp,
+			   struct pvr2_stream_stats *stats,
+			   int zero_counts)
+{
+	unsigned long irq_flags;
+	spin_lock_irqsave(&sp->list_lock, irq_flags);
+	if (stats) {
+		stats->buffers_in_queue = sp->q_count;
+		stats->buffers_in_idle = sp->i_count;
+		stats->buffers_in_ready = sp->r_count;
+		stats->buffers_processed = sp->buffers_processed;
+		stats->buffers_failed = sp->buffers_failed;
+		stats->bytes_processed = sp->bytes_processed;
+	}
+	if (zero_counts) {
+		sp->buffers_processed = 0;
+		sp->buffers_failed = 0;
+		sp->bytes_processed = 0;
+	}
+	spin_unlock_irqrestore(&sp->list_lock, irq_flags);
+}
+
+/* Query / set the nominal buffer count */
+int pvr2_stream_get_buffer_count(struct pvr2_stream *sp)
+{
+	return sp->buffer_target_count;
+}
+
+int pvr2_stream_set_buffer_count(struct pvr2_stream *sp, unsigned int cnt)
+{
+	int ret;
+	if (sp->buffer_target_count == cnt) return 0;
+	mutex_lock(&sp->mutex);
+	do {
+		sp->buffer_target_count = cnt;
+		ret = pvr2_stream_achieve_buffer_count(sp);
+	} while (0);
+	mutex_unlock(&sp->mutex);
+	return ret;
+}
+
+struct pvr2_buffer *pvr2_stream_get_idle_buffer(struct pvr2_stream *sp)
+{
+	struct list_head *lp = sp->idle_list.next;
+	if (lp == &sp->idle_list) return NULL;
+	return list_entry(lp, struct pvr2_buffer, list_overhead);
+}
+
+struct pvr2_buffer *pvr2_stream_get_ready_buffer(struct pvr2_stream *sp)
+{
+	struct list_head *lp = sp->ready_list.next;
+	if (lp == &sp->ready_list) return NULL;
+	return list_entry(lp, struct pvr2_buffer, list_overhead);
+}
+
+struct pvr2_buffer *pvr2_stream_get_buffer(struct pvr2_stream *sp, int id)
+{
+	if (id < 0) return NULL;
+	if (id >= sp->buffer_total_count) return NULL;
+	return sp->buffers[id];
+}
+
+int pvr2_stream_get_ready_count(struct pvr2_stream *sp)
+{
+	return sp->r_count;
+}
+
+void pvr2_stream_kill(struct pvr2_stream *sp)
+{
+	struct pvr2_buffer *bp;
+	mutex_lock(&sp->mutex);
+	do {
+		pvr2_stream_internal_flush(sp);
+		while ((bp = pvr2_stream_get_ready_buffer(sp)) != NULL) {
+			pvr2_buffer_set_idle(bp);
+		}
+		if (sp->buffer_total_count != sp->buffer_target_count) {
+			pvr2_stream_achieve_buffer_count(sp);
+		}
+	} while (0);
+	mutex_unlock(&sp->mutex);
+}
+
+int pvr2_buffer_queue(struct pvr2_buffer *bp)
+{
+#undef SEED_BUFFER
+#ifdef SEED_BUFFER
+	unsigned int idx;
+	unsigned int val;
+#endif
+	int ret = 0;
+	struct pvr2_stream *sp;
+	if (!bp) return -EINVAL;
+	sp = bp->stream;
+	mutex_lock(&sp->mutex);
+	do {
+		pvr2_buffer_wipe(bp);
+		if (!sp->dev) {
+			ret = -EIO;
+			break;
+		}
+		pvr2_buffer_set_queued(bp);
+#ifdef SEED_BUFFER
+		for (idx = 0; idx < (bp->max_count) / 4; idx++) {
+			val = bp->id << 24;
+			val |= idx;
+			((unsigned int *)(bp->ptr))[idx] = val;
+		}
+#endif
+		bp->status = -EINPROGRESS;
+		usb_fill_bulk_urb(bp->purb,      // struct urb *urb
+				  sp->dev,       // struct usb_device *dev
+				  // endpoint (below)
+				  usb_rcvbulkpipe(sp->dev, sp->endpoint),
+				  bp->ptr,       // void *transfer_buffer
+				  bp->max_count, // int buffer_length
+				  buffer_complete,
+				  bp);
+		usb_submit_urb(bp->purb, GFP_KERNEL);
+	} while (0);
+	mutex_unlock(&sp->mutex);
+	return ret;
+}
+
+int pvr2_buffer_set_buffer(struct pvr2_buffer *bp, void *ptr, unsigned int cnt)
+{
+	int ret = 0;
+	unsigned long irq_flags;
+	struct pvr2_stream *sp;
+	if (!bp) return -EINVAL;
+	sp = bp->stream;
+	mutex_lock(&sp->mutex);
+	do {
+		spin_lock_irqsave(&sp->list_lock, irq_flags);
+		if (bp->state != pvr2_buffer_state_idle) {
+			ret = -EPERM;
+		} else {
+			bp->ptr = ptr;
+			bp->stream->i_bcount -= bp->max_count;
+			bp->max_count = cnt;
+			bp->stream->i_bcount += bp->max_count;
+			pvr2_trace(PVR2_TRACE_BUF_FLOW,
+				   "/*---TRACE_FLOW---*/ bufferPool	%8s cap cap=%07d cnt=%02d",
+				   pvr2_buffer_state_decode(
+					   pvr2_buffer_state_idle),
+				   bp->stream->i_bcount, bp->stream->i_count);
+		}
+		spin_unlock_irqrestore(&sp->list_lock, irq_flags);
+	} while (0);
+	mutex_unlock(&sp->mutex);
+	return ret;
+}
+
+unsigned int pvr2_buffer_get_count(struct pvr2_buffer *bp)
+{
+	return bp->used_count;
+}
+
+int pvr2_buffer_get_status(struct pvr2_buffer *bp)
+{
+	return bp->status;
+}
+
+int pvr2_buffer_get_id(struct pvr2_buffer *bp)
+{
+	return bp->id;
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-io.h b/drivers/media/usb/pvrusb2/pvrusb2-io.h
new file mode 100644
index 0000000..e769aeb
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-io.h
@@ -0,0 +1,88 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+#ifndef __PVRUSB2_IO_H
+#define __PVRUSB2_IO_H
+
+#include <linux/usb.h>
+#include <linux/list.h>
+
+typedef void (*pvr2_stream_callback)(void *);
+
+enum pvr2_buffer_state {
+	pvr2_buffer_state_none = 0,   // Not on any list
+	pvr2_buffer_state_idle = 1,   // Buffer is ready to be used again
+	pvr2_buffer_state_queued = 2, // Buffer has been queued for filling
+	pvr2_buffer_state_ready = 3,  // Buffer has data available
+};
+
+struct pvr2_stream;
+struct pvr2_buffer;
+
+struct pvr2_stream_stats {
+	unsigned int buffers_in_queue;
+	unsigned int buffers_in_idle;
+	unsigned int buffers_in_ready;
+	unsigned int buffers_processed;
+	unsigned int buffers_failed;
+	unsigned int bytes_processed;
+};
+
+/* Initialize / tear down stream structure */
+struct pvr2_stream *pvr2_stream_create(void);
+void pvr2_stream_destroy(struct pvr2_stream *);
+void pvr2_stream_setup(struct pvr2_stream *,
+		       struct usb_device *dev,int endpoint,
+		       unsigned int tolerance);
+void pvr2_stream_set_callback(struct pvr2_stream *,
+			      pvr2_stream_callback func,
+			      void *data);
+void pvr2_stream_get_stats(struct pvr2_stream *,
+			   struct pvr2_stream_stats *,
+			   int zero_counts);
+
+/* Query / set the nominal buffer count */
+int pvr2_stream_get_buffer_count(struct pvr2_stream *);
+int pvr2_stream_set_buffer_count(struct pvr2_stream *,unsigned int);
+
+/* Get a pointer to a buffer that is either idle, ready, or is specified
+   named. */
+struct pvr2_buffer *pvr2_stream_get_idle_buffer(struct pvr2_stream *);
+struct pvr2_buffer *pvr2_stream_get_ready_buffer(struct pvr2_stream *);
+struct pvr2_buffer *pvr2_stream_get_buffer(struct pvr2_stream *sp,int id);
+
+/* Find out how many buffers are idle or ready */
+int pvr2_stream_get_ready_count(struct pvr2_stream *);
+
+
+/* Kill all pending buffers and throw away any ready buffers as well */
+void pvr2_stream_kill(struct pvr2_stream *);
+
+/* Set up the actual storage for a buffer */
+int pvr2_buffer_set_buffer(struct pvr2_buffer *,void *ptr,unsigned int cnt);
+
+/* Find out size of data in the given ready buffer */
+unsigned int pvr2_buffer_get_count(struct pvr2_buffer *);
+
+/* Retrieve completion code for given ready buffer */
+int pvr2_buffer_get_status(struct pvr2_buffer *);
+
+/* Retrieve ID of given buffer */
+int pvr2_buffer_get_id(struct pvr2_buffer *);
+
+/* Start reading into given buffer (kill it if needed) */
+int pvr2_buffer_queue(struct pvr2_buffer *);
+
+#endif /* __PVRUSB2_IO_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-ioread.c b/drivers/media/usb/pvrusb2/pvrusb2-ioread.c
new file mode 100644
index 0000000..602097b
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-ioread.c
@@ -0,0 +1,495 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#include "pvrusb2-ioread.h"
+#include "pvrusb2-debug.h"
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/uaccess.h>
+
+#define BUFFER_COUNT 32
+#define BUFFER_SIZE PAGE_ALIGN(0x4000)
+
+struct pvr2_ioread {
+	struct pvr2_stream *stream;
+	char *buffer_storage[BUFFER_COUNT];
+	char *sync_key_ptr;
+	unsigned int sync_key_len;
+	unsigned int sync_buf_offs;
+	unsigned int sync_state;
+	unsigned int sync_trashed_count;
+	int enabled;         // Streaming is on
+	int spigot_open;     // OK to pass data to client
+	int stream_running;  // Passing data to client now
+
+	/* State relevant to current buffer being read */
+	struct pvr2_buffer *c_buf;
+	char *c_data_ptr;
+	unsigned int c_data_len;
+	unsigned int c_data_offs;
+	struct mutex mutex;
+};
+
+static int pvr2_ioread_init(struct pvr2_ioread *cp)
+{
+	unsigned int idx;
+
+	cp->stream = NULL;
+	mutex_init(&cp->mutex);
+
+	for (idx = 0; idx < BUFFER_COUNT; idx++) {
+		cp->buffer_storage[idx] = kmalloc(BUFFER_SIZE,GFP_KERNEL);
+		if (!(cp->buffer_storage[idx])) break;
+	}
+
+	if (idx < BUFFER_COUNT) {
+		// An allocation appears to have failed
+		for (idx = 0; idx < BUFFER_COUNT; idx++) {
+			if (!(cp->buffer_storage[idx])) continue;
+			kfree(cp->buffer_storage[idx]);
+		}
+		return -ENOMEM;
+	}
+	return 0;
+}
+
+static void pvr2_ioread_done(struct pvr2_ioread *cp)
+{
+	unsigned int idx;
+
+	pvr2_ioread_setup(cp,NULL);
+	for (idx = 0; idx < BUFFER_COUNT; idx++) {
+		if (!(cp->buffer_storage[idx])) continue;
+		kfree(cp->buffer_storage[idx]);
+	}
+}
+
+struct pvr2_ioread *pvr2_ioread_create(void)
+{
+	struct pvr2_ioread *cp;
+	cp = kzalloc(sizeof(*cp),GFP_KERNEL);
+	if (!cp) return NULL;
+	pvr2_trace(PVR2_TRACE_STRUCT,"pvr2_ioread_create id=%p",cp);
+	if (pvr2_ioread_init(cp) < 0) {
+		kfree(cp);
+		return NULL;
+	}
+	return cp;
+}
+
+void pvr2_ioread_destroy(struct pvr2_ioread *cp)
+{
+	if (!cp) return;
+	pvr2_ioread_done(cp);
+	pvr2_trace(PVR2_TRACE_STRUCT,"pvr2_ioread_destroy id=%p",cp);
+	if (cp->sync_key_ptr) {
+		kfree(cp->sync_key_ptr);
+		cp->sync_key_ptr = NULL;
+	}
+	kfree(cp);
+}
+
+void pvr2_ioread_set_sync_key(struct pvr2_ioread *cp,
+			      const char *sync_key_ptr,
+			      unsigned int sync_key_len)
+{
+	if (!cp) return;
+
+	if (!sync_key_ptr) sync_key_len = 0;
+	if ((sync_key_len == cp->sync_key_len) &&
+	    ((!sync_key_len) ||
+	     (!memcmp(sync_key_ptr,cp->sync_key_ptr,sync_key_len)))) return;
+
+	if (sync_key_len != cp->sync_key_len) {
+		if (cp->sync_key_ptr) {
+			kfree(cp->sync_key_ptr);
+			cp->sync_key_ptr = NULL;
+		}
+		cp->sync_key_len = 0;
+		if (sync_key_len) {
+			cp->sync_key_ptr = kmalloc(sync_key_len,GFP_KERNEL);
+			if (cp->sync_key_ptr) {
+				cp->sync_key_len = sync_key_len;
+			}
+		}
+	}
+	if (!cp->sync_key_len) return;
+	memcpy(cp->sync_key_ptr,sync_key_ptr,cp->sync_key_len);
+}
+
+static void pvr2_ioread_stop(struct pvr2_ioread *cp)
+{
+	if (!(cp->enabled)) return;
+	pvr2_trace(PVR2_TRACE_START_STOP,
+		   "/*---TRACE_READ---*/ pvr2_ioread_stop id=%p",cp);
+	pvr2_stream_kill(cp->stream);
+	cp->c_buf = NULL;
+	cp->c_data_ptr = NULL;
+	cp->c_data_len = 0;
+	cp->c_data_offs = 0;
+	cp->enabled = 0;
+	cp->stream_running = 0;
+	cp->spigot_open = 0;
+	if (cp->sync_state) {
+		pvr2_trace(PVR2_TRACE_DATA_FLOW,
+			   "/*---TRACE_READ---*/ sync_state <== 0");
+		cp->sync_state = 0;
+	}
+}
+
+static int pvr2_ioread_start(struct pvr2_ioread *cp)
+{
+	int stat;
+	struct pvr2_buffer *bp;
+	if (cp->enabled) return 0;
+	if (!(cp->stream)) return 0;
+	pvr2_trace(PVR2_TRACE_START_STOP,
+		   "/*---TRACE_READ---*/ pvr2_ioread_start id=%p",cp);
+	while ((bp = pvr2_stream_get_idle_buffer(cp->stream)) != NULL) {
+		stat = pvr2_buffer_queue(bp);
+		if (stat < 0) {
+			pvr2_trace(PVR2_TRACE_DATA_FLOW,
+				   "/*---TRACE_READ---*/ pvr2_ioread_start id=%p error=%d",
+				   cp,stat);
+			pvr2_ioread_stop(cp);
+			return stat;
+		}
+	}
+	cp->enabled = !0;
+	cp->c_buf = NULL;
+	cp->c_data_ptr = NULL;
+	cp->c_data_len = 0;
+	cp->c_data_offs = 0;
+	cp->stream_running = 0;
+	if (cp->sync_key_len) {
+		pvr2_trace(PVR2_TRACE_DATA_FLOW,
+			   "/*---TRACE_READ---*/ sync_state <== 1");
+		cp->sync_state = 1;
+		cp->sync_trashed_count = 0;
+		cp->sync_buf_offs = 0;
+	}
+	cp->spigot_open = 0;
+	return 0;
+}
+
+struct pvr2_stream *pvr2_ioread_get_stream(struct pvr2_ioread *cp)
+{
+	return cp->stream;
+}
+
+int pvr2_ioread_setup(struct pvr2_ioread *cp,struct pvr2_stream *sp)
+{
+	int ret;
+	unsigned int idx;
+	struct pvr2_buffer *bp;
+
+	mutex_lock(&cp->mutex);
+	do {
+		if (cp->stream) {
+			pvr2_trace(PVR2_TRACE_START_STOP,
+				   "/*---TRACE_READ---*/ pvr2_ioread_setup (tear-down) id=%p",
+				   cp);
+			pvr2_ioread_stop(cp);
+			pvr2_stream_kill(cp->stream);
+			if (pvr2_stream_get_buffer_count(cp->stream)) {
+				pvr2_stream_set_buffer_count(cp->stream,0);
+			}
+			cp->stream = NULL;
+		}
+		if (sp) {
+			pvr2_trace(PVR2_TRACE_START_STOP,
+				   "/*---TRACE_READ---*/ pvr2_ioread_setup (setup) id=%p",
+				   cp);
+			pvr2_stream_kill(sp);
+			ret = pvr2_stream_set_buffer_count(sp,BUFFER_COUNT);
+			if (ret < 0) {
+				mutex_unlock(&cp->mutex);
+				return ret;
+			}
+			for (idx = 0; idx < BUFFER_COUNT; idx++) {
+				bp = pvr2_stream_get_buffer(sp,idx);
+				pvr2_buffer_set_buffer(bp,
+						       cp->buffer_storage[idx],
+						       BUFFER_SIZE);
+			}
+			cp->stream = sp;
+		}
+	} while (0);
+	mutex_unlock(&cp->mutex);
+
+	return 0;
+}
+
+int pvr2_ioread_set_enabled(struct pvr2_ioread *cp,int fl)
+{
+	int ret = 0;
+	if ((!fl) == (!(cp->enabled))) return ret;
+
+	mutex_lock(&cp->mutex);
+	do {
+		if (fl) {
+			ret = pvr2_ioread_start(cp);
+		} else {
+			pvr2_ioread_stop(cp);
+		}
+	} while (0);
+	mutex_unlock(&cp->mutex);
+	return ret;
+}
+
+static int pvr2_ioread_get_buffer(struct pvr2_ioread *cp)
+{
+	int stat;
+
+	while (cp->c_data_len <= cp->c_data_offs) {
+		if (cp->c_buf) {
+			// Flush out current buffer first.
+			stat = pvr2_buffer_queue(cp->c_buf);
+			if (stat < 0) {
+				// Streaming error...
+				pvr2_trace(PVR2_TRACE_DATA_FLOW,
+					   "/*---TRACE_READ---*/ pvr2_ioread_read id=%p queue_error=%d",
+					   cp,stat);
+				pvr2_ioread_stop(cp);
+				return 0;
+			}
+			cp->c_buf = NULL;
+			cp->c_data_ptr = NULL;
+			cp->c_data_len = 0;
+			cp->c_data_offs = 0;
+		}
+		// Now get a freshly filled buffer.
+		cp->c_buf = pvr2_stream_get_ready_buffer(cp->stream);
+		if (!cp->c_buf) break; // Nothing ready; done.
+		cp->c_data_len = pvr2_buffer_get_count(cp->c_buf);
+		if (!cp->c_data_len) {
+			// Nothing transferred.  Was there an error?
+			stat = pvr2_buffer_get_status(cp->c_buf);
+			if (stat < 0) {
+				// Streaming error...
+				pvr2_trace(PVR2_TRACE_DATA_FLOW,
+					   "/*---TRACE_READ---*/ pvr2_ioread_read id=%p buffer_error=%d",
+					   cp,stat);
+				pvr2_ioread_stop(cp);
+				// Give up.
+				return 0;
+			}
+			// Start over...
+			continue;
+		}
+		cp->c_data_offs = 0;
+		cp->c_data_ptr = cp->buffer_storage[
+			pvr2_buffer_get_id(cp->c_buf)];
+	}
+	return !0;
+}
+
+static void pvr2_ioread_filter(struct pvr2_ioread *cp)
+{
+	unsigned int idx;
+	if (!cp->enabled) return;
+	if (cp->sync_state != 1) return;
+
+	// Search the stream for our synchronization key.  This is made
+	// complicated by the fact that in order to be honest with
+	// ourselves here we must search across buffer boundaries...
+	mutex_lock(&cp->mutex);
+	while (1) {
+		// Ensure we have a buffer
+		if (!pvr2_ioread_get_buffer(cp)) break;
+		if (!cp->c_data_len) break;
+
+		// Now walk the buffer contents until we match the key or
+		// run out of buffer data.
+		for (idx = cp->c_data_offs; idx < cp->c_data_len; idx++) {
+			if (cp->sync_buf_offs >= cp->sync_key_len) break;
+			if (cp->c_data_ptr[idx] ==
+			    cp->sync_key_ptr[cp->sync_buf_offs]) {
+				// Found the next key byte
+				(cp->sync_buf_offs)++;
+			} else {
+				// Whoops, mismatched.  Start key over...
+				cp->sync_buf_offs = 0;
+			}
+		}
+
+		// Consume what we've walked through
+		cp->c_data_offs += idx;
+		cp->sync_trashed_count += idx;
+
+		// If we've found the key, then update state and get out.
+		if (cp->sync_buf_offs >= cp->sync_key_len) {
+			cp->sync_trashed_count -= cp->sync_key_len;
+			pvr2_trace(PVR2_TRACE_DATA_FLOW,
+				   "/*---TRACE_READ---*/ sync_state <== 2 (skipped %u bytes)",
+				   cp->sync_trashed_count);
+			cp->sync_state = 2;
+			cp->sync_buf_offs = 0;
+			break;
+		}
+
+		if (cp->c_data_offs < cp->c_data_len) {
+			// Sanity check - should NEVER get here
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "ERROR: pvr2_ioread filter sync problem len=%u offs=%u",
+				   cp->c_data_len,cp->c_data_offs);
+			// Get out so we don't get stuck in an infinite
+			// loop.
+			break;
+		}
+
+		continue; // (for clarity)
+	}
+	mutex_unlock(&cp->mutex);
+}
+
+int pvr2_ioread_avail(struct pvr2_ioread *cp)
+{
+	int ret;
+	if (!(cp->enabled)) {
+		// Stream is not enabled; so this is an I/O error
+		return -EIO;
+	}
+
+	if (cp->sync_state == 1) {
+		pvr2_ioread_filter(cp);
+		if (cp->sync_state == 1) return -EAGAIN;
+	}
+
+	ret = 0;
+	if (cp->stream_running) {
+		if (!pvr2_stream_get_ready_count(cp->stream)) {
+			// No data available at all right now.
+			ret = -EAGAIN;
+		}
+	} else {
+		if (pvr2_stream_get_ready_count(cp->stream) < BUFFER_COUNT/2) {
+			// Haven't buffered up enough yet; try again later
+			ret = -EAGAIN;
+		}
+	}
+
+	if ((!(cp->spigot_open)) != (!(ret == 0))) {
+		cp->spigot_open = (ret == 0);
+		pvr2_trace(PVR2_TRACE_DATA_FLOW,
+			   "/*---TRACE_READ---*/ data is %s",
+			   cp->spigot_open ? "available" : "pending");
+	}
+
+	return ret;
+}
+
+int pvr2_ioread_read(struct pvr2_ioread *cp,void __user *buf,unsigned int cnt)
+{
+	unsigned int copied_cnt;
+	unsigned int bcnt;
+	const char *src;
+	int stat;
+	int ret = 0;
+	unsigned int req_cnt = cnt;
+
+	if (!cnt) {
+		pvr2_trace(PVR2_TRACE_TRAP,
+			   "/*---TRACE_READ---*/ pvr2_ioread_read id=%p ZERO Request? Returning zero.",
+cp);
+		return 0;
+	}
+
+	stat = pvr2_ioread_avail(cp);
+	if (stat < 0) return stat;
+
+	cp->stream_running = !0;
+
+	mutex_lock(&cp->mutex);
+	do {
+
+		// Suck data out of the buffers and copy to the user
+		copied_cnt = 0;
+		if (!buf) cnt = 0;
+		while (1) {
+			if (!pvr2_ioread_get_buffer(cp)) {
+				ret = -EIO;
+				break;
+			}
+
+			if (!cnt) break;
+
+			if (cp->sync_state == 2) {
+				// We're repeating the sync key data into
+				// the stream.
+				src = cp->sync_key_ptr + cp->sync_buf_offs;
+				bcnt = cp->sync_key_len - cp->sync_buf_offs;
+			} else {
+				// Normal buffer copy
+				src = cp->c_data_ptr + cp->c_data_offs;
+				bcnt = cp->c_data_len - cp->c_data_offs;
+			}
+
+			if (!bcnt) break;
+
+			// Don't run past user's buffer
+			if (bcnt > cnt) bcnt = cnt;
+
+			if (copy_to_user(buf,src,bcnt)) {
+				// User supplied a bad pointer?
+				// Give up - this *will* cause data
+				// to be lost.
+				ret = -EFAULT;
+				break;
+			}
+			cnt -= bcnt;
+			buf += bcnt;
+			copied_cnt += bcnt;
+
+			if (cp->sync_state == 2) {
+				// Update offset inside sync key that we're
+				// repeating back out.
+				cp->sync_buf_offs += bcnt;
+				if (cp->sync_buf_offs >= cp->sync_key_len) {
+					// Consumed entire key; switch mode
+					// to normal.
+					pvr2_trace(PVR2_TRACE_DATA_FLOW,
+						   "/*---TRACE_READ---*/ sync_state <== 0");
+					cp->sync_state = 0;
+				}
+			} else {
+				// Update buffer offset.
+				cp->c_data_offs += bcnt;
+			}
+		}
+
+	} while (0);
+	mutex_unlock(&cp->mutex);
+
+	if (!ret) {
+		if (copied_cnt) {
+			// If anything was copied, return that count
+			ret = copied_cnt;
+		} else {
+			// Nothing copied; suggest to caller that another
+			// attempt should be tried again later
+			ret = -EAGAIN;
+		}
+	}
+
+	pvr2_trace(PVR2_TRACE_DATA_FLOW,
+		   "/*---TRACE_READ---*/ pvr2_ioread_read id=%p request=%d result=%d",
+		   cp,req_cnt,ret);
+	return ret;
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-ioread.h b/drivers/media/usb/pvrusb2/pvrusb2-ioread.h
new file mode 100644
index 0000000..5827ea0
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-ioread.h
@@ -0,0 +1,34 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+#ifndef __PVRUSB2_IOREAD_H
+#define __PVRUSB2_IOREAD_H
+
+#include "pvrusb2-io.h"
+
+struct pvr2_ioread;
+
+struct pvr2_ioread *pvr2_ioread_create(void);
+void pvr2_ioread_destroy(struct pvr2_ioread *);
+int pvr2_ioread_setup(struct pvr2_ioread *,struct pvr2_stream *);
+struct pvr2_stream *pvr2_ioread_get_stream(struct pvr2_ioread *);
+void pvr2_ioread_set_sync_key(struct pvr2_ioread *,
+			      const char *sync_key_ptr,
+			      unsigned int sync_key_len);
+int pvr2_ioread_set_enabled(struct pvr2_ioread *,int fl);
+int pvr2_ioread_read(struct pvr2_ioread *,void __user *buf,unsigned int cnt);
+int pvr2_ioread_avail(struct pvr2_ioread *);
+
+#endif /* __PVRUSB2_IOREAD_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-main.c b/drivers/media/usb/pvrusb2/pvrusb2-main.c
new file mode 100644
index 0000000..cbe2c3a
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-main.c
@@ -0,0 +1,167 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/videodev2.h>
+
+#include "pvrusb2-hdw.h"
+#include "pvrusb2-devattr.h"
+#include "pvrusb2-context.h"
+#include "pvrusb2-debug.h"
+#include "pvrusb2-v4l2.h"
+#ifdef CONFIG_VIDEO_PVRUSB2_SYSFS
+#include "pvrusb2-sysfs.h"
+#endif /* CONFIG_VIDEO_PVRUSB2_SYSFS */
+
+#define DRIVER_AUTHOR "Mike Isely <isely@pobox.com>"
+#define DRIVER_DESC "Hauppauge WinTV-PVR-USB2 MPEG2 Encoder/Tuner"
+#define DRIVER_VERSION "V4L in-tree version"
+
+#define DEFAULT_DEBUG_MASK (PVR2_TRACE_ERROR_LEGS| \
+			    PVR2_TRACE_INFO| \
+			    PVR2_TRACE_STD| \
+			    PVR2_TRACE_TOLERANCE| \
+			    PVR2_TRACE_TRAP| \
+			    0)
+
+int pvrusb2_debug = DEFAULT_DEBUG_MASK;
+
+module_param_named(debug,pvrusb2_debug,int,S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(debug, "Debug trace mask");
+
+#ifdef CONFIG_VIDEO_PVRUSB2_SYSFS
+static struct pvr2_sysfs_class *class_ptr = NULL;
+#endif /* CONFIG_VIDEO_PVRUSB2_SYSFS */
+
+static void pvr_setup_attach(struct pvr2_context *pvr)
+{
+	/* Create association with v4l layer */
+	pvr2_v4l2_create(pvr);
+#ifdef CONFIG_VIDEO_PVRUSB2_DVB
+	/* Create association with dvb layer */
+	pvr2_dvb_create(pvr);
+#endif
+#ifdef CONFIG_VIDEO_PVRUSB2_SYSFS
+	pvr2_sysfs_create(pvr,class_ptr);
+#endif /* CONFIG_VIDEO_PVRUSB2_SYSFS */
+}
+
+static int pvr_probe(struct usb_interface *intf,
+		     const struct usb_device_id *devid)
+{
+	struct pvr2_context *pvr;
+
+	/* Create underlying hardware interface */
+	pvr = pvr2_context_create(intf,devid,pvr_setup_attach);
+	if (!pvr) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "Failed to create hdw handler");
+		return -ENOMEM;
+	}
+
+	pvr2_trace(PVR2_TRACE_INIT,"pvr_probe(pvr=%p)",pvr);
+
+	usb_set_intfdata(intf, pvr);
+
+	return 0;
+}
+
+/*
+ * pvr_disconnect()
+ *
+ */
+static void pvr_disconnect(struct usb_interface *intf)
+{
+	struct pvr2_context *pvr = usb_get_intfdata(intf);
+
+	pvr2_trace(PVR2_TRACE_INIT,"pvr_disconnect(pvr=%p) BEGIN",pvr);
+
+	usb_set_intfdata (intf, NULL);
+	pvr2_context_disconnect(pvr);
+
+	pvr2_trace(PVR2_TRACE_INIT,"pvr_disconnect(pvr=%p) DONE",pvr);
+
+}
+
+static struct usb_driver pvr_driver = {
+	.name =         "pvrusb2",
+	.id_table =     pvr2_device_table,
+	.probe =        pvr_probe,
+	.disconnect =   pvr_disconnect
+};
+
+/*
+ * pvr_init() / pvr_exit()
+ *
+ * This code is run to initialize/exit the driver.
+ *
+ */
+static int __init pvr_init(void)
+{
+	int ret;
+
+	pvr2_trace(PVR2_TRACE_INIT,"pvr_init");
+
+	ret = pvr2_context_global_init();
+	if (ret != 0) {
+		pvr2_trace(PVR2_TRACE_INIT,"pvr_init failure code=%d",ret);
+		return ret;
+	}
+
+#ifdef CONFIG_VIDEO_PVRUSB2_SYSFS
+	class_ptr = pvr2_sysfs_class_create();
+#endif /* CONFIG_VIDEO_PVRUSB2_SYSFS */
+
+	ret = usb_register(&pvr_driver);
+
+	if (ret == 0)
+		printk(KERN_INFO "pvrusb2: " DRIVER_VERSION ":"
+		       DRIVER_DESC "\n");
+	if (pvrusb2_debug)
+		printk(KERN_INFO "pvrusb2: Debug mask is %d (0x%x)\n",
+		       pvrusb2_debug,pvrusb2_debug);
+
+	pvr2_trace(PVR2_TRACE_INIT,"pvr_init complete");
+
+	return ret;
+}
+
+static void __exit pvr_exit(void)
+{
+	pvr2_trace(PVR2_TRACE_INIT,"pvr_exit");
+
+	usb_deregister(&pvr_driver);
+
+	pvr2_context_global_done();
+
+#ifdef CONFIG_VIDEO_PVRUSB2_SYSFS
+	pvr2_sysfs_class_destroy(class_ptr);
+#endif /* CONFIG_VIDEO_PVRUSB2_SYSFS */
+
+	pvr2_trace(PVR2_TRACE_INIT,"pvr_exit complete");
+}
+
+module_init(pvr_init);
+module_exit(pvr_exit);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+MODULE_VERSION("0.9.1");
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-std.c b/drivers/media/usb/pvrusb2/pvrusb2-std.c
new file mode 100644
index 0000000..6b651f8
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-std.c
@@ -0,0 +1,395 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#include "pvrusb2-std.h"
+#include "pvrusb2-debug.h"
+#include <asm/string.h>
+#include <linux/slab.h>
+
+struct std_name {
+	const char *name;
+	v4l2_std_id id;
+};
+
+
+#define CSTD_PAL \
+	(V4L2_STD_PAL_B| \
+	 V4L2_STD_PAL_B1| \
+	 V4L2_STD_PAL_G| \
+	 V4L2_STD_PAL_H| \
+	 V4L2_STD_PAL_I| \
+	 V4L2_STD_PAL_D| \
+	 V4L2_STD_PAL_D1| \
+	 V4L2_STD_PAL_K| \
+	 V4L2_STD_PAL_M| \
+	 V4L2_STD_PAL_N| \
+	 V4L2_STD_PAL_Nc| \
+	 V4L2_STD_PAL_60)
+
+#define CSTD_NTSC \
+	(V4L2_STD_NTSC_M| \
+	 V4L2_STD_NTSC_M_JP| \
+	 V4L2_STD_NTSC_M_KR| \
+	 V4L2_STD_NTSC_443)
+
+#define CSTD_ATSC \
+	(V4L2_STD_ATSC_8_VSB| \
+	 V4L2_STD_ATSC_16_VSB)
+
+#define CSTD_SECAM \
+	(V4L2_STD_SECAM_B| \
+	 V4L2_STD_SECAM_D| \
+	 V4L2_STD_SECAM_G| \
+	 V4L2_STD_SECAM_H| \
+	 V4L2_STD_SECAM_K| \
+	 V4L2_STD_SECAM_K1| \
+	 V4L2_STD_SECAM_L| \
+	 V4L2_STD_SECAM_LC)
+
+#define TSTD_B   (V4L2_STD_PAL_B|V4L2_STD_SECAM_B)
+#define TSTD_B1  (V4L2_STD_PAL_B1)
+#define TSTD_D   (V4L2_STD_PAL_D|V4L2_STD_SECAM_D)
+#define TSTD_D1  (V4L2_STD_PAL_D1)
+#define TSTD_G   (V4L2_STD_PAL_G|V4L2_STD_SECAM_G)
+#define TSTD_H   (V4L2_STD_PAL_H|V4L2_STD_SECAM_H)
+#define TSTD_I   (V4L2_STD_PAL_I)
+#define TSTD_K   (V4L2_STD_PAL_K|V4L2_STD_SECAM_K)
+#define TSTD_K1  (V4L2_STD_SECAM_K1)
+#define TSTD_L   (V4L2_STD_SECAM_L)
+#define TSTD_M   (V4L2_STD_PAL_M|V4L2_STD_NTSC_M)
+#define TSTD_N   (V4L2_STD_PAL_N)
+#define TSTD_Nc  (V4L2_STD_PAL_Nc)
+#define TSTD_60  (V4L2_STD_PAL_60)
+
+#define CSTD_ALL (CSTD_PAL|CSTD_NTSC|CSTD_ATSC|CSTD_SECAM)
+
+/* Mapping of standard bits to color system */
+static const struct std_name std_groups[] = {
+	{"PAL",CSTD_PAL},
+	{"NTSC",CSTD_NTSC},
+	{"SECAM",CSTD_SECAM},
+	{"ATSC",CSTD_ATSC},
+};
+
+/* Mapping of standard bits to modulation system */
+static const struct std_name std_items[] = {
+	{"B",TSTD_B},
+	{"B1",TSTD_B1},
+	{"D",TSTD_D},
+	{"D1",TSTD_D1},
+	{"G",TSTD_G},
+	{"H",TSTD_H},
+	{"I",TSTD_I},
+	{"K",TSTD_K},
+	{"K1",TSTD_K1},
+	{"L",TSTD_L},
+	{"LC",V4L2_STD_SECAM_LC},
+	{"M",TSTD_M},
+	{"Mj",V4L2_STD_NTSC_M_JP},
+	{"443",V4L2_STD_NTSC_443},
+	{"Mk",V4L2_STD_NTSC_M_KR},
+	{"N",TSTD_N},
+	{"Nc",TSTD_Nc},
+	{"60",TSTD_60},
+	{"8VSB",V4L2_STD_ATSC_8_VSB},
+	{"16VSB",V4L2_STD_ATSC_16_VSB},
+};
+
+
+// Search an array of std_name structures and return a pointer to the
+// element with the matching name.
+static const struct std_name *find_std_name(const struct std_name *arrPtr,
+					    unsigned int arrSize,
+					    const char *bufPtr,
+					    unsigned int bufSize)
+{
+	unsigned int idx;
+	const struct std_name *p;
+	for (idx = 0; idx < arrSize; idx++) {
+		p = arrPtr + idx;
+		if (strlen(p->name) != bufSize) continue;
+		if (!memcmp(bufPtr,p->name,bufSize)) return p;
+	}
+	return NULL;
+}
+
+
+int pvr2_std_str_to_id(v4l2_std_id *idPtr,const char *bufPtr,
+		       unsigned int bufSize)
+{
+	v4l2_std_id id = 0;
+	v4l2_std_id cmsk = 0;
+	v4l2_std_id t;
+	int mMode = 0;
+	unsigned int cnt;
+	char ch;
+	const struct std_name *sp;
+
+	while (bufSize) {
+		if (!mMode) {
+			cnt = 0;
+			while ((cnt < bufSize) && (bufPtr[cnt] != '-')) cnt++;
+			if (cnt >= bufSize) return 0; // No more characters
+			sp = find_std_name(std_groups, ARRAY_SIZE(std_groups),
+					   bufPtr,cnt);
+			if (!sp) return 0; // Illegal color system name
+			cnt++;
+			bufPtr += cnt;
+			bufSize -= cnt;
+			mMode = !0;
+			cmsk = sp->id;
+			continue;
+		}
+		cnt = 0;
+		while (cnt < bufSize) {
+			ch = bufPtr[cnt];
+			if (ch == ';') {
+				mMode = 0;
+				break;
+			}
+			if (ch == '/') break;
+			cnt++;
+		}
+		sp = find_std_name(std_items, ARRAY_SIZE(std_items),
+				   bufPtr,cnt);
+		if (!sp) return 0; // Illegal modulation system ID
+		t = sp->id & cmsk;
+		if (!t) return 0; // Specific color + modulation system illegal
+		id |= t;
+		if (cnt < bufSize) cnt++;
+		bufPtr += cnt;
+		bufSize -= cnt;
+	}
+
+	if (idPtr) *idPtr = id;
+	return !0;
+}
+
+
+unsigned int pvr2_std_id_to_str(char *bufPtr, unsigned int bufSize,
+				v4l2_std_id id)
+{
+	unsigned int idx1,idx2;
+	const struct std_name *ip,*gp;
+	int gfl,cfl;
+	unsigned int c1,c2;
+	cfl = 0;
+	c1 = 0;
+	for (idx1 = 0; idx1 < ARRAY_SIZE(std_groups); idx1++) {
+		gp = std_groups + idx1;
+		gfl = 0;
+		for (idx2 = 0; idx2 < ARRAY_SIZE(std_items); idx2++) {
+			ip = std_items + idx2;
+			if (!(gp->id & ip->id & id)) continue;
+			if (!gfl) {
+				if (cfl) {
+					c2 = scnprintf(bufPtr,bufSize,";");
+					c1 += c2;
+					bufSize -= c2;
+					bufPtr += c2;
+				}
+				cfl = !0;
+				c2 = scnprintf(bufPtr,bufSize,
+					       "%s-",gp->name);
+				gfl = !0;
+			} else {
+				c2 = scnprintf(bufPtr,bufSize,"/");
+			}
+			c1 += c2;
+			bufSize -= c2;
+			bufPtr += c2;
+			c2 = scnprintf(bufPtr,bufSize,
+				       ip->name);
+			c1 += c2;
+			bufSize -= c2;
+			bufPtr += c2;
+		}
+	}
+	return c1;
+}
+
+
+// Template data for possible enumerated video standards.  Here we group
+// standards which share common frame rates and resolution.
+static struct v4l2_standard generic_standards[] = {
+	{
+		.id             = (TSTD_B|TSTD_B1|
+				   TSTD_D|TSTD_D1|
+				   TSTD_G|
+				   TSTD_H|
+				   TSTD_I|
+				   TSTD_K|TSTD_K1|
+				   TSTD_L|
+				   V4L2_STD_SECAM_LC |
+				   TSTD_N|TSTD_Nc),
+		.frameperiod    =
+		{
+			.numerator  = 1,
+			.denominator= 25
+		},
+		.framelines     = 625,
+		.reserved       = {0,0,0,0}
+	}, {
+		.id             = (TSTD_M|
+				   V4L2_STD_NTSC_M_JP|
+				   V4L2_STD_NTSC_M_KR),
+		.frameperiod    =
+		{
+			.numerator  = 1001,
+			.denominator= 30000
+		},
+		.framelines     = 525,
+		.reserved       = {0,0,0,0}
+	}, { // This is a total wild guess
+		.id             = (TSTD_60),
+		.frameperiod    =
+		{
+			.numerator  = 1001,
+			.denominator= 30000
+		},
+		.framelines     = 525,
+		.reserved       = {0,0,0,0}
+	}, { // This is total wild guess
+		.id             = V4L2_STD_NTSC_443,
+		.frameperiod    =
+		{
+			.numerator  = 1001,
+			.denominator= 30000
+		},
+		.framelines     = 525,
+		.reserved       = {0,0,0,0}
+	}
+};
+
+static struct v4l2_standard *match_std(v4l2_std_id id)
+{
+	unsigned int idx;
+	for (idx = 0; idx < ARRAY_SIZE(generic_standards); idx++) {
+		if (generic_standards[idx].id & id) {
+			return generic_standards + idx;
+		}
+	}
+	return NULL;
+}
+
+static int pvr2_std_fill(struct v4l2_standard *std,v4l2_std_id id)
+{
+	struct v4l2_standard *template;
+	int idx;
+	unsigned int bcnt;
+	template = match_std(id);
+	if (!template) return 0;
+	idx = std->index;
+	memcpy(std,template,sizeof(*template));
+	std->index = idx;
+	std->id = id;
+	bcnt = pvr2_std_id_to_str(std->name,sizeof(std->name)-1,id);
+	std->name[bcnt] = 0;
+	pvr2_trace(PVR2_TRACE_STD,"Set up standard idx=%u name=%s",
+		   std->index,std->name);
+	return !0;
+}
+
+/* These are special cases of combined standards that we should enumerate
+   separately if the component pieces are present. */
+static v4l2_std_id std_mixes[] = {
+	V4L2_STD_PAL_B | V4L2_STD_PAL_G,
+	V4L2_STD_PAL_D | V4L2_STD_PAL_K,
+	V4L2_STD_SECAM_B | V4L2_STD_SECAM_G,
+	V4L2_STD_SECAM_D | V4L2_STD_SECAM_K,
+};
+
+struct v4l2_standard *pvr2_std_create_enum(unsigned int *countptr,
+					   v4l2_std_id id)
+{
+	unsigned int std_cnt = 0;
+	unsigned int idx,bcnt,idx2;
+	v4l2_std_id idmsk,cmsk,fmsk;
+	struct v4l2_standard *stddefs;
+
+	if (pvrusb2_debug & PVR2_TRACE_STD) {
+		char buf[100];
+		bcnt = pvr2_std_id_to_str(buf,sizeof(buf),id);
+		pvr2_trace(
+			PVR2_TRACE_STD,"Mapping standards mask=0x%x (%.*s)",
+			(int)id,bcnt,buf);
+	}
+
+	*countptr = 0;
+	std_cnt = 0;
+	fmsk = 0;
+	for (idmsk = 1, cmsk = id; cmsk; idmsk <<= 1) {
+		if (!(idmsk & cmsk)) continue;
+		cmsk &= ~idmsk;
+		if (match_std(idmsk)) {
+			std_cnt++;
+			continue;
+		}
+		fmsk |= idmsk;
+	}
+
+	for (idx2 = 0; idx2 < ARRAY_SIZE(std_mixes); idx2++) {
+		if ((id & std_mixes[idx2]) == std_mixes[idx2]) std_cnt++;
+	}
+
+	/* Don't complain about ATSC standard values */
+	fmsk &= ~CSTD_ATSC;
+
+	if (fmsk) {
+		char buf[100];
+		bcnt = pvr2_std_id_to_str(buf,sizeof(buf),fmsk);
+		pvr2_trace(
+			PVR2_TRACE_ERROR_LEGS,
+			"WARNING: Failed to classify the following standard(s): %.*s",
+			bcnt,buf);
+	}
+
+	pvr2_trace(PVR2_TRACE_STD,"Setting up %u unique standard(s)",
+		   std_cnt);
+	if (!std_cnt) return NULL; // paranoia
+
+	stddefs = kcalloc(std_cnt, sizeof(struct v4l2_standard),
+			  GFP_KERNEL);
+	if (!stddefs)
+		return NULL;
+
+	for (idx = 0; idx < std_cnt; idx++)
+		stddefs[idx].index = idx;
+
+	idx = 0;
+
+	/* Enumerate potential special cases */
+	for (idx2 = 0; (idx2 < ARRAY_SIZE(std_mixes)) && (idx < std_cnt);
+	     idx2++) {
+		if (!(id & std_mixes[idx2])) continue;
+		if (pvr2_std_fill(stddefs+idx,std_mixes[idx2])) idx++;
+	}
+	/* Now enumerate individual pieces */
+	for (idmsk = 1, cmsk = id; cmsk && (idx < std_cnt); idmsk <<= 1) {
+		if (!(idmsk & cmsk)) continue;
+		cmsk &= ~idmsk;
+		if (!pvr2_std_fill(stddefs+idx,idmsk)) continue;
+		idx++;
+	}
+
+	*countptr = std_cnt;
+	return stddefs;
+}
+
+v4l2_std_id pvr2_std_get_usable(void)
+{
+	return CSTD_ALL;
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-std.h b/drivers/media/usb/pvrusb2/pvrusb2-std.h
new file mode 100644
index 0000000..b48304f
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-std.h
@@ -0,0 +1,45 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+#ifndef __PVRUSB2_STD_H
+#define __PVRUSB2_STD_H
+
+#include <linux/videodev2.h>
+
+// Convert string describing one or more video standards into a mask of V4L
+// standard bits.  Return true if conversion succeeds otherwise return
+// false.  String is expected to be of the form: C1-x/y;C2-a/b where C1 and
+// C2 are color system names (e.g. "PAL", "NTSC") and x, y, a, and b are
+// modulation schemes (e.g. "M", "B", "G", etc).
+int pvr2_std_str_to_id(v4l2_std_id *idPtr,const char *bufPtr,
+		       unsigned int bufSize);
+
+// Convert any arbitrary set of video standard bits into an unambiguous
+// readable string.  Return value is the number of bytes consumed in the
+// buffer.  The formatted string is of a form that can be parsed by our
+// sibling std_std_to_id() function.
+unsigned int pvr2_std_id_to_str(char *bufPtr, unsigned int bufSize,
+				v4l2_std_id id);
+
+// Create an array of suitable v4l2_standard structures given a bit mask of
+// video standards to support.  The array is allocated from the heap, and
+// the number of elements is returned in the first argument.
+struct v4l2_standard *pvr2_std_create_enum(unsigned int *countptr,
+					   v4l2_std_id id);
+
+// Return mask of which video standard bits are valid
+v4l2_std_id pvr2_std_get_usable(void);
+
+#endif /* __PVRUSB2_STD_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-sysfs.c b/drivers/media/usb/pvrusb2/pvrusb2-sysfs.c
new file mode 100644
index 0000000..7bc6d09
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-sysfs.c
@@ -0,0 +1,845 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#include <linux/string.h>
+#include <linux/slab.h>
+#include "pvrusb2-sysfs.h"
+#include "pvrusb2-hdw.h"
+#include "pvrusb2-debug.h"
+#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC
+#include "pvrusb2-debugifc.h"
+#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */
+
+#define pvr2_sysfs_trace(...) pvr2_trace(PVR2_TRACE_SYSFS,__VA_ARGS__)
+
+struct pvr2_sysfs {
+	struct pvr2_channel channel;
+	struct device *class_dev;
+#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC
+	struct pvr2_sysfs_debugifc *debugifc;
+#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */
+	struct pvr2_sysfs_ctl_item *item_first;
+	struct pvr2_sysfs_ctl_item *item_last;
+	struct device_attribute attr_v4l_minor_number;
+	struct device_attribute attr_v4l_radio_minor_number;
+	struct device_attribute attr_unit_number;
+	struct device_attribute attr_bus_info;
+	struct device_attribute attr_hdw_name;
+	struct device_attribute attr_hdw_desc;
+	int v4l_minor_number_created_ok;
+	int v4l_radio_minor_number_created_ok;
+	int unit_number_created_ok;
+	int bus_info_created_ok;
+	int hdw_name_created_ok;
+	int hdw_desc_created_ok;
+};
+
+#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC
+struct pvr2_sysfs_debugifc {
+	struct device_attribute attr_debugcmd;
+	struct device_attribute attr_debuginfo;
+	int debugcmd_created_ok;
+	int debuginfo_created_ok;
+};
+#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */
+
+struct pvr2_sysfs_ctl_item {
+	struct device_attribute attr_name;
+	struct device_attribute attr_type;
+	struct device_attribute attr_min;
+	struct device_attribute attr_max;
+	struct device_attribute attr_def;
+	struct device_attribute attr_enum;
+	struct device_attribute attr_bits;
+	struct device_attribute attr_val;
+	struct device_attribute attr_custom;
+	struct pvr2_ctrl *cptr;
+	int ctl_id;
+	struct pvr2_sysfs *chptr;
+	struct pvr2_sysfs_ctl_item *item_next;
+	struct attribute *attr_gen[8];
+	struct attribute_group grp;
+	int created_ok;
+	char name[80];
+};
+
+struct pvr2_sysfs_class {
+	struct class class;
+};
+
+static ssize_t show_name(struct device *class_dev,
+			 struct device_attribute *attr,
+			 char *buf)
+{
+	struct pvr2_sysfs_ctl_item *cip;
+	const char *name;
+	cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_name);
+	name = pvr2_ctrl_get_desc(cip->cptr);
+	pvr2_sysfs_trace("pvr2_sysfs(%p) show_name(cid=%d) is %s",
+			 cip->chptr, cip->ctl_id, name);
+	if (!name) return -EINVAL;
+	return scnprintf(buf, PAGE_SIZE, "%s\n", name);
+}
+
+static ssize_t show_type(struct device *class_dev,
+			 struct device_attribute *attr,
+			 char *buf)
+{
+	struct pvr2_sysfs_ctl_item *cip;
+	const char *name;
+	enum pvr2_ctl_type tp;
+	cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_type);
+	tp = pvr2_ctrl_get_type(cip->cptr);
+	switch (tp) {
+	case pvr2_ctl_int: name = "integer"; break;
+	case pvr2_ctl_enum: name = "enum"; break;
+	case pvr2_ctl_bitmask: name = "bitmask"; break;
+	case pvr2_ctl_bool: name = "boolean"; break;
+	default: name = "?"; break;
+	}
+	pvr2_sysfs_trace("pvr2_sysfs(%p) show_type(cid=%d) is %s",
+			 cip->chptr, cip->ctl_id, name);
+	return scnprintf(buf, PAGE_SIZE, "%s\n", name);
+}
+
+static ssize_t show_min(struct device *class_dev,
+			struct device_attribute *attr,
+			char *buf)
+{
+	struct pvr2_sysfs_ctl_item *cip;
+	long val;
+	cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_min);
+	val = pvr2_ctrl_get_min(cip->cptr);
+	pvr2_sysfs_trace("pvr2_sysfs(%p) show_min(cid=%d) is %ld",
+			 cip->chptr, cip->ctl_id, val);
+	return scnprintf(buf, PAGE_SIZE, "%ld\n", val);
+}
+
+static ssize_t show_max(struct device *class_dev,
+			struct device_attribute *attr,
+			char *buf)
+{
+	struct pvr2_sysfs_ctl_item *cip;
+	long val;
+	cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_max);
+	val = pvr2_ctrl_get_max(cip->cptr);
+	pvr2_sysfs_trace("pvr2_sysfs(%p) show_max(cid=%d) is %ld",
+			 cip->chptr, cip->ctl_id, val);
+	return scnprintf(buf, PAGE_SIZE, "%ld\n", val);
+}
+
+static ssize_t show_def(struct device *class_dev,
+			struct device_attribute *attr,
+			char *buf)
+{
+	struct pvr2_sysfs_ctl_item *cip;
+	int val;
+	int ret;
+	unsigned int cnt = 0;
+	cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_def);
+	ret = pvr2_ctrl_get_def(cip->cptr, &val);
+	if (ret < 0) return ret;
+	ret = pvr2_ctrl_value_to_sym(cip->cptr, ~0, val,
+				     buf, PAGE_SIZE - 1, &cnt);
+	pvr2_sysfs_trace("pvr2_sysfs(%p) show_def(cid=%d) is %.*s (%d)",
+			 cip->chptr, cip->ctl_id, cnt, buf, val);
+	buf[cnt] = '\n';
+	return cnt + 1;
+}
+
+static ssize_t show_val_norm(struct device *class_dev,
+			     struct device_attribute *attr,
+			     char *buf)
+{
+	struct pvr2_sysfs_ctl_item *cip;
+	int val;
+	int ret;
+	unsigned int cnt = 0;
+	cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_val);
+	ret = pvr2_ctrl_get_value(cip->cptr, &val);
+	if (ret < 0) return ret;
+	ret = pvr2_ctrl_value_to_sym(cip->cptr, ~0, val,
+				     buf, PAGE_SIZE - 1, &cnt);
+	pvr2_sysfs_trace("pvr2_sysfs(%p) show_val_norm(cid=%d) is %.*s (%d)",
+			 cip->chptr, cip->ctl_id, cnt, buf, val);
+	buf[cnt] = '\n';
+	return cnt+1;
+}
+
+static ssize_t show_val_custom(struct device *class_dev,
+			       struct device_attribute *attr,
+			       char *buf)
+{
+	struct pvr2_sysfs_ctl_item *cip;
+	int val;
+	int ret;
+	unsigned int cnt = 0;
+	cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_custom);
+	ret = pvr2_ctrl_get_value(cip->cptr, &val);
+	if (ret < 0) return ret;
+	ret = pvr2_ctrl_custom_value_to_sym(cip->cptr, ~0, val,
+					    buf, PAGE_SIZE - 1, &cnt);
+	pvr2_sysfs_trace("pvr2_sysfs(%p) show_val_custom(cid=%d) is %.*s (%d)",
+			 cip->chptr, cip->ctl_id, cnt, buf, val);
+	buf[cnt] = '\n';
+	return cnt+1;
+}
+
+static ssize_t show_enum(struct device *class_dev,
+			 struct device_attribute *attr,
+			 char *buf)
+{
+	struct pvr2_sysfs_ctl_item *cip;
+	long val;
+	unsigned int bcnt, ccnt, ecnt;
+	cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_enum);
+	ecnt = pvr2_ctrl_get_cnt(cip->cptr);
+	bcnt = 0;
+	for (val = 0; val < ecnt; val++) {
+		pvr2_ctrl_get_valname(cip->cptr, val, buf + bcnt,
+				      PAGE_SIZE - bcnt, &ccnt);
+		if (!ccnt) continue;
+		bcnt += ccnt;
+		if (bcnt >= PAGE_SIZE) break;
+		buf[bcnt] = '\n';
+		bcnt++;
+	}
+	pvr2_sysfs_trace("pvr2_sysfs(%p) show_enum(cid=%d)",
+			 cip->chptr, cip->ctl_id);
+	return bcnt;
+}
+
+static ssize_t show_bits(struct device *class_dev,
+			 struct device_attribute *attr,
+			 char *buf)
+{
+	struct pvr2_sysfs_ctl_item *cip;
+	int valid_bits, msk;
+	unsigned int bcnt, ccnt;
+	cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_bits);
+	valid_bits = pvr2_ctrl_get_mask(cip->cptr);
+	bcnt = 0;
+	for (msk = 1; valid_bits; msk <<= 1) {
+		if (!(msk & valid_bits)) continue;
+		valid_bits &= ~msk;
+		pvr2_ctrl_get_valname(cip->cptr, msk, buf + bcnt,
+				      PAGE_SIZE - bcnt, &ccnt);
+		bcnt += ccnt;
+		if (bcnt >= PAGE_SIZE) break;
+		buf[bcnt] = '\n';
+		bcnt++;
+	}
+	pvr2_sysfs_trace("pvr2_sysfs(%p) show_bits(cid=%d)",
+			 cip->chptr, cip->ctl_id);
+	return bcnt;
+}
+
+static int store_val_any(struct pvr2_sysfs_ctl_item *cip, int customfl,
+			 const char *buf,unsigned int count)
+{
+	int ret;
+	int mask,val;
+	if (customfl) {
+		ret = pvr2_ctrl_custom_sym_to_value(cip->cptr, buf, count,
+						    &mask, &val);
+	} else {
+		ret = pvr2_ctrl_sym_to_value(cip->cptr, buf, count,
+					     &mask, &val);
+	}
+	if (ret < 0) return ret;
+	ret = pvr2_ctrl_set_mask_value(cip->cptr, mask, val);
+	pvr2_hdw_commit_ctl(cip->chptr->channel.hdw);
+	return ret;
+}
+
+static ssize_t store_val_norm(struct device *class_dev,
+			      struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct pvr2_sysfs_ctl_item *cip;
+	int ret;
+	cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_val);
+	pvr2_sysfs_trace("pvr2_sysfs(%p) store_val_norm(cid=%d) \"%.*s\"",
+			 cip->chptr, cip->ctl_id, (int)count, buf);
+	ret = store_val_any(cip, 0, buf, count);
+	if (!ret) ret = count;
+	return ret;
+}
+
+static ssize_t store_val_custom(struct device *class_dev,
+				struct device_attribute *attr,
+				const char *buf, size_t count)
+{
+	struct pvr2_sysfs_ctl_item *cip;
+	int ret;
+	cip = container_of(attr, struct pvr2_sysfs_ctl_item, attr_custom);
+	pvr2_sysfs_trace("pvr2_sysfs(%p) store_val_custom(cid=%d) \"%.*s\"",
+			 cip->chptr, cip->ctl_id, (int)count, buf);
+	ret = store_val_any(cip, 1, buf, count);
+	if (!ret) ret = count;
+	return ret;
+}
+
+static void pvr2_sysfs_add_control(struct pvr2_sysfs *sfp,int ctl_id)
+{
+	struct pvr2_sysfs_ctl_item *cip;
+	struct pvr2_ctrl *cptr;
+	unsigned int cnt,acnt;
+	int ret;
+
+	cptr = pvr2_hdw_get_ctrl_by_index(sfp->channel.hdw,ctl_id);
+	if (!cptr) return;
+
+	cip = kzalloc(sizeof(*cip),GFP_KERNEL);
+	if (!cip) return;
+	pvr2_sysfs_trace("Creating pvr2_sysfs_ctl_item id=%p",cip);
+
+	cip->cptr = cptr;
+	cip->ctl_id = ctl_id;
+
+	cip->chptr = sfp;
+	cip->item_next = NULL;
+	if (sfp->item_last) {
+		sfp->item_last->item_next = cip;
+	} else {
+		sfp->item_first = cip;
+	}
+	sfp->item_last = cip;
+
+	sysfs_attr_init(&cip->attr_name.attr);
+	cip->attr_name.attr.name = "name";
+	cip->attr_name.attr.mode = S_IRUGO;
+	cip->attr_name.show = show_name;
+
+	sysfs_attr_init(&cip->attr_type.attr);
+	cip->attr_type.attr.name = "type";
+	cip->attr_type.attr.mode = S_IRUGO;
+	cip->attr_type.show = show_type;
+
+	sysfs_attr_init(&cip->attr_min.attr);
+	cip->attr_min.attr.name = "min_val";
+	cip->attr_min.attr.mode = S_IRUGO;
+	cip->attr_min.show = show_min;
+
+	sysfs_attr_init(&cip->attr_max.attr);
+	cip->attr_max.attr.name = "max_val";
+	cip->attr_max.attr.mode = S_IRUGO;
+	cip->attr_max.show = show_max;
+
+	sysfs_attr_init(&cip->attr_def.attr);
+	cip->attr_def.attr.name = "def_val";
+	cip->attr_def.attr.mode = S_IRUGO;
+	cip->attr_def.show = show_def;
+
+	sysfs_attr_init(&cip->attr_val.attr);
+	cip->attr_val.attr.name = "cur_val";
+	cip->attr_val.attr.mode = S_IRUGO;
+
+	sysfs_attr_init(&cip->attr_custom.attr);
+	cip->attr_custom.attr.name = "custom_val";
+	cip->attr_custom.attr.mode = S_IRUGO;
+
+	sysfs_attr_init(&cip->attr_enum.attr);
+	cip->attr_enum.attr.name = "enum_val";
+	cip->attr_enum.attr.mode = S_IRUGO;
+	cip->attr_enum.show = show_enum;
+
+	sysfs_attr_init(&cip->attr_bits.attr);
+	cip->attr_bits.attr.name = "bit_val";
+	cip->attr_bits.attr.mode = S_IRUGO;
+	cip->attr_bits.show = show_bits;
+
+	if (pvr2_ctrl_is_writable(cptr)) {
+		cip->attr_val.attr.mode |= S_IWUSR|S_IWGRP;
+		cip->attr_custom.attr.mode |= S_IWUSR|S_IWGRP;
+	}
+
+	acnt = 0;
+	cip->attr_gen[acnt++] = &cip->attr_name.attr;
+	cip->attr_gen[acnt++] = &cip->attr_type.attr;
+	cip->attr_gen[acnt++] = &cip->attr_val.attr;
+	cip->attr_gen[acnt++] = &cip->attr_def.attr;
+	cip->attr_val.show = show_val_norm;
+	cip->attr_val.store = store_val_norm;
+	if (pvr2_ctrl_has_custom_symbols(cptr)) {
+		cip->attr_gen[acnt++] = &cip->attr_custom.attr;
+		cip->attr_custom.show = show_val_custom;
+		cip->attr_custom.store = store_val_custom;
+	}
+	switch (pvr2_ctrl_get_type(cptr)) {
+	case pvr2_ctl_enum:
+		// Control is an enumeration
+		cip->attr_gen[acnt++] = &cip->attr_enum.attr;
+		break;
+	case pvr2_ctl_int:
+		// Control is an integer
+		cip->attr_gen[acnt++] = &cip->attr_min.attr;
+		cip->attr_gen[acnt++] = &cip->attr_max.attr;
+		break;
+	case pvr2_ctl_bitmask:
+		// Control is an bitmask
+		cip->attr_gen[acnt++] = &cip->attr_bits.attr;
+		break;
+	default: break;
+	}
+
+	cnt = scnprintf(cip->name,sizeof(cip->name)-1,"ctl_%s",
+			pvr2_ctrl_get_name(cptr));
+	cip->name[cnt] = 0;
+	cip->grp.name = cip->name;
+	cip->grp.attrs = cip->attr_gen;
+
+	ret = sysfs_create_group(&sfp->class_dev->kobj,&cip->grp);
+	if (ret) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "sysfs_create_group error: %d",
+			   ret);
+		return;
+	}
+	cip->created_ok = !0;
+}
+
+#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC
+static ssize_t debuginfo_show(struct device *, struct device_attribute *,
+			      char *);
+static ssize_t debugcmd_show(struct device *, struct device_attribute *,
+			     char *);
+static ssize_t debugcmd_store(struct device *, struct device_attribute *,
+			      const char *, size_t count);
+
+static void pvr2_sysfs_add_debugifc(struct pvr2_sysfs *sfp)
+{
+	struct pvr2_sysfs_debugifc *dip;
+	int ret;
+
+	dip = kzalloc(sizeof(*dip),GFP_KERNEL);
+	if (!dip) return;
+	sysfs_attr_init(&dip->attr_debugcmd.attr);
+	dip->attr_debugcmd.attr.name = "debugcmd";
+	dip->attr_debugcmd.attr.mode = S_IRUGO|S_IWUSR|S_IWGRP;
+	dip->attr_debugcmd.show = debugcmd_show;
+	dip->attr_debugcmd.store = debugcmd_store;
+	sysfs_attr_init(&dip->attr_debuginfo.attr);
+	dip->attr_debuginfo.attr.name = "debuginfo";
+	dip->attr_debuginfo.attr.mode = S_IRUGO;
+	dip->attr_debuginfo.show = debuginfo_show;
+	sfp->debugifc = dip;
+	ret = device_create_file(sfp->class_dev,&dip->attr_debugcmd);
+	if (ret < 0) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "device_create_file error: %d",
+			   ret);
+	} else {
+		dip->debugcmd_created_ok = !0;
+	}
+	ret = device_create_file(sfp->class_dev,&dip->attr_debuginfo);
+	if (ret < 0) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "device_create_file error: %d",
+			   ret);
+	} else {
+		dip->debuginfo_created_ok = !0;
+	}
+}
+
+
+static void pvr2_sysfs_tear_down_debugifc(struct pvr2_sysfs *sfp)
+{
+	if (!sfp->debugifc) return;
+	if (sfp->debugifc->debuginfo_created_ok) {
+		device_remove_file(sfp->class_dev,
+					 &sfp->debugifc->attr_debuginfo);
+	}
+	if (sfp->debugifc->debugcmd_created_ok) {
+		device_remove_file(sfp->class_dev,
+					 &sfp->debugifc->attr_debugcmd);
+	}
+	kfree(sfp->debugifc);
+	sfp->debugifc = NULL;
+}
+#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */
+
+
+static void pvr2_sysfs_add_controls(struct pvr2_sysfs *sfp)
+{
+	unsigned int idx,cnt;
+	cnt = pvr2_hdw_get_ctrl_count(sfp->channel.hdw);
+	for (idx = 0; idx < cnt; idx++) {
+		pvr2_sysfs_add_control(sfp,idx);
+	}
+}
+
+
+static void pvr2_sysfs_tear_down_controls(struct pvr2_sysfs *sfp)
+{
+	struct pvr2_sysfs_ctl_item *cip1,*cip2;
+	for (cip1 = sfp->item_first; cip1; cip1 = cip2) {
+		cip2 = cip1->item_next;
+		if (cip1->created_ok) {
+			sysfs_remove_group(&sfp->class_dev->kobj,&cip1->grp);
+		}
+		pvr2_sysfs_trace("Destroying pvr2_sysfs_ctl_item id=%p",cip1);
+		kfree(cip1);
+	}
+}
+
+
+static void pvr2_sysfs_class_release(struct class *class)
+{
+	struct pvr2_sysfs_class *clp;
+	clp = container_of(class,struct pvr2_sysfs_class,class);
+	pvr2_sysfs_trace("Destroying pvr2_sysfs_class id=%p",clp);
+	kfree(clp);
+}
+
+
+static void pvr2_sysfs_release(struct device *class_dev)
+{
+	pvr2_sysfs_trace("Releasing class_dev id=%p",class_dev);
+	kfree(class_dev);
+}
+
+
+static void class_dev_destroy(struct pvr2_sysfs *sfp)
+{
+	struct device *dev;
+	if (!sfp->class_dev) return;
+#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC
+	pvr2_sysfs_tear_down_debugifc(sfp);
+#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */
+	pvr2_sysfs_tear_down_controls(sfp);
+	if (sfp->hdw_desc_created_ok) {
+		device_remove_file(sfp->class_dev,
+				   &sfp->attr_hdw_desc);
+	}
+	if (sfp->hdw_name_created_ok) {
+		device_remove_file(sfp->class_dev,
+				   &sfp->attr_hdw_name);
+	}
+	if (sfp->bus_info_created_ok) {
+		device_remove_file(sfp->class_dev,
+					 &sfp->attr_bus_info);
+	}
+	if (sfp->v4l_minor_number_created_ok) {
+		device_remove_file(sfp->class_dev,
+					 &sfp->attr_v4l_minor_number);
+	}
+	if (sfp->v4l_radio_minor_number_created_ok) {
+		device_remove_file(sfp->class_dev,
+					 &sfp->attr_v4l_radio_minor_number);
+	}
+	if (sfp->unit_number_created_ok) {
+		device_remove_file(sfp->class_dev,
+					 &sfp->attr_unit_number);
+	}
+	pvr2_sysfs_trace("Destroying class_dev id=%p",sfp->class_dev);
+	dev_set_drvdata(sfp->class_dev, NULL);
+	dev = sfp->class_dev->parent;
+	sfp->class_dev->parent = NULL;
+	put_device(dev);
+	device_unregister(sfp->class_dev);
+	sfp->class_dev = NULL;
+}
+
+
+static ssize_t v4l_minor_number_show(struct device *class_dev,
+				     struct device_attribute *attr, char *buf)
+{
+	struct pvr2_sysfs *sfp;
+	sfp = dev_get_drvdata(class_dev);
+	if (!sfp) return -EINVAL;
+	return scnprintf(buf,PAGE_SIZE,"%d\n",
+			 pvr2_hdw_v4l_get_minor_number(sfp->channel.hdw,
+						       pvr2_v4l_type_video));
+}
+
+
+static ssize_t bus_info_show(struct device *class_dev,
+			     struct device_attribute *attr, char *buf)
+{
+	struct pvr2_sysfs *sfp;
+	sfp = dev_get_drvdata(class_dev);
+	if (!sfp) return -EINVAL;
+	return scnprintf(buf,PAGE_SIZE,"%s\n",
+			 pvr2_hdw_get_bus_info(sfp->channel.hdw));
+}
+
+
+static ssize_t hdw_name_show(struct device *class_dev,
+			     struct device_attribute *attr, char *buf)
+{
+	struct pvr2_sysfs *sfp;
+	sfp = dev_get_drvdata(class_dev);
+	if (!sfp) return -EINVAL;
+	return scnprintf(buf,PAGE_SIZE,"%s\n",
+			 pvr2_hdw_get_type(sfp->channel.hdw));
+}
+
+
+static ssize_t hdw_desc_show(struct device *class_dev,
+			     struct device_attribute *attr, char *buf)
+{
+	struct pvr2_sysfs *sfp;
+	sfp = dev_get_drvdata(class_dev);
+	if (!sfp) return -EINVAL;
+	return scnprintf(buf,PAGE_SIZE,"%s\n",
+			 pvr2_hdw_get_desc(sfp->channel.hdw));
+}
+
+
+static ssize_t v4l_radio_minor_number_show(struct device *class_dev,
+					   struct device_attribute *attr,
+					   char *buf)
+{
+	struct pvr2_sysfs *sfp;
+	sfp = dev_get_drvdata(class_dev);
+	if (!sfp) return -EINVAL;
+	return scnprintf(buf,PAGE_SIZE,"%d\n",
+			 pvr2_hdw_v4l_get_minor_number(sfp->channel.hdw,
+						       pvr2_v4l_type_radio));
+}
+
+
+static ssize_t unit_number_show(struct device *class_dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct pvr2_sysfs *sfp;
+	sfp = dev_get_drvdata(class_dev);
+	if (!sfp) return -EINVAL;
+	return scnprintf(buf,PAGE_SIZE,"%d\n",
+			 pvr2_hdw_get_unit_number(sfp->channel.hdw));
+}
+
+
+static void class_dev_create(struct pvr2_sysfs *sfp,
+			     struct pvr2_sysfs_class *class_ptr)
+{
+	struct usb_device *usb_dev;
+	struct device *class_dev;
+	int ret;
+
+	usb_dev = pvr2_hdw_get_dev(sfp->channel.hdw);
+	if (!usb_dev) return;
+	class_dev = kzalloc(sizeof(*class_dev),GFP_KERNEL);
+	if (!class_dev) return;
+
+	pvr2_sysfs_trace("Creating class_dev id=%p",class_dev);
+
+	class_dev->class = &class_ptr->class;
+
+	dev_set_name(class_dev, "%s",
+		     pvr2_hdw_get_device_identifier(sfp->channel.hdw));
+
+	class_dev->parent = get_device(&usb_dev->dev);
+
+	sfp->class_dev = class_dev;
+	dev_set_drvdata(class_dev, sfp);
+	ret = device_register(class_dev);
+	if (ret) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "device_register failed");
+		put_device(class_dev);
+		return;
+	}
+
+	sysfs_attr_init(&sfp->attr_v4l_minor_number.attr);
+	sfp->attr_v4l_minor_number.attr.name = "v4l_minor_number";
+	sfp->attr_v4l_minor_number.attr.mode = S_IRUGO;
+	sfp->attr_v4l_minor_number.show = v4l_minor_number_show;
+	sfp->attr_v4l_minor_number.store = NULL;
+	ret = device_create_file(sfp->class_dev,
+				       &sfp->attr_v4l_minor_number);
+	if (ret < 0) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "device_create_file error: %d",
+			   ret);
+	} else {
+		sfp->v4l_minor_number_created_ok = !0;
+	}
+
+	sysfs_attr_init(&sfp->attr_v4l_radio_minor_number.attr);
+	sfp->attr_v4l_radio_minor_number.attr.name = "v4l_radio_minor_number";
+	sfp->attr_v4l_radio_minor_number.attr.mode = S_IRUGO;
+	sfp->attr_v4l_radio_minor_number.show = v4l_radio_minor_number_show;
+	sfp->attr_v4l_radio_minor_number.store = NULL;
+	ret = device_create_file(sfp->class_dev,
+				       &sfp->attr_v4l_radio_minor_number);
+	if (ret < 0) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "device_create_file error: %d",
+			   ret);
+	} else {
+		sfp->v4l_radio_minor_number_created_ok = !0;
+	}
+
+	sysfs_attr_init(&sfp->attr_unit_number.attr);
+	sfp->attr_unit_number.attr.name = "unit_number";
+	sfp->attr_unit_number.attr.mode = S_IRUGO;
+	sfp->attr_unit_number.show = unit_number_show;
+	sfp->attr_unit_number.store = NULL;
+	ret = device_create_file(sfp->class_dev,&sfp->attr_unit_number);
+	if (ret < 0) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "device_create_file error: %d",
+			   ret);
+	} else {
+		sfp->unit_number_created_ok = !0;
+	}
+
+	sysfs_attr_init(&sfp->attr_bus_info.attr);
+	sfp->attr_bus_info.attr.name = "bus_info_str";
+	sfp->attr_bus_info.attr.mode = S_IRUGO;
+	sfp->attr_bus_info.show = bus_info_show;
+	sfp->attr_bus_info.store = NULL;
+	ret = device_create_file(sfp->class_dev,
+				       &sfp->attr_bus_info);
+	if (ret < 0) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "device_create_file error: %d",
+			   ret);
+	} else {
+		sfp->bus_info_created_ok = !0;
+	}
+
+	sysfs_attr_init(&sfp->attr_hdw_name.attr);
+	sfp->attr_hdw_name.attr.name = "device_hardware_type";
+	sfp->attr_hdw_name.attr.mode = S_IRUGO;
+	sfp->attr_hdw_name.show = hdw_name_show;
+	sfp->attr_hdw_name.store = NULL;
+	ret = device_create_file(sfp->class_dev,
+				 &sfp->attr_hdw_name);
+	if (ret < 0) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "device_create_file error: %d",
+			   ret);
+	} else {
+		sfp->hdw_name_created_ok = !0;
+	}
+
+	sysfs_attr_init(&sfp->attr_hdw_desc.attr);
+	sfp->attr_hdw_desc.attr.name = "device_hardware_description";
+	sfp->attr_hdw_desc.attr.mode = S_IRUGO;
+	sfp->attr_hdw_desc.show = hdw_desc_show;
+	sfp->attr_hdw_desc.store = NULL;
+	ret = device_create_file(sfp->class_dev,
+				 &sfp->attr_hdw_desc);
+	if (ret < 0) {
+		pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+			   "device_create_file error: %d",
+			   ret);
+	} else {
+		sfp->hdw_desc_created_ok = !0;
+	}
+
+	pvr2_sysfs_add_controls(sfp);
+#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC
+	pvr2_sysfs_add_debugifc(sfp);
+#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */
+}
+
+
+static void pvr2_sysfs_internal_check(struct pvr2_channel *chp)
+{
+	struct pvr2_sysfs *sfp;
+	sfp = container_of(chp,struct pvr2_sysfs,channel);
+	if (!sfp->channel.mc_head->disconnect_flag) return;
+	pvr2_trace(PVR2_TRACE_STRUCT,"Destroying pvr2_sysfs id=%p",sfp);
+	class_dev_destroy(sfp);
+	pvr2_channel_done(&sfp->channel);
+	kfree(sfp);
+}
+
+
+struct pvr2_sysfs *pvr2_sysfs_create(struct pvr2_context *mp,
+				     struct pvr2_sysfs_class *class_ptr)
+{
+	struct pvr2_sysfs *sfp;
+	sfp = kzalloc(sizeof(*sfp),GFP_KERNEL);
+	if (!sfp) return sfp;
+	pvr2_trace(PVR2_TRACE_STRUCT,"Creating pvr2_sysfs id=%p",sfp);
+	pvr2_channel_init(&sfp->channel,mp);
+	sfp->channel.check_func = pvr2_sysfs_internal_check;
+
+	class_dev_create(sfp,class_ptr);
+	return sfp;
+}
+
+
+
+struct pvr2_sysfs_class *pvr2_sysfs_class_create(void)
+{
+	struct pvr2_sysfs_class *clp;
+	clp = kzalloc(sizeof(*clp),GFP_KERNEL);
+	if (!clp) return clp;
+	pvr2_sysfs_trace("Creating and registering pvr2_sysfs_class id=%p",
+			 clp);
+	clp->class.name = "pvrusb2";
+	clp->class.class_release = pvr2_sysfs_class_release;
+	clp->class.dev_release = pvr2_sysfs_release;
+	if (class_register(&clp->class)) {
+		pvr2_sysfs_trace(
+			"Registration failed for pvr2_sysfs_class id=%p",clp);
+		kfree(clp);
+		clp = NULL;
+	}
+	return clp;
+}
+
+
+void pvr2_sysfs_class_destroy(struct pvr2_sysfs_class *clp)
+{
+	pvr2_sysfs_trace("Unregistering pvr2_sysfs_class id=%p", clp);
+	class_unregister(&clp->class);
+}
+
+
+#ifdef CONFIG_VIDEO_PVRUSB2_DEBUGIFC
+static ssize_t debuginfo_show(struct device *class_dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct pvr2_sysfs *sfp;
+	sfp = dev_get_drvdata(class_dev);
+	if (!sfp) return -EINVAL;
+	pvr2_hdw_trigger_module_log(sfp->channel.hdw);
+	return pvr2_debugifc_print_info(sfp->channel.hdw,buf,PAGE_SIZE);
+}
+
+
+static ssize_t debugcmd_show(struct device *class_dev,
+			     struct device_attribute *attr, char *buf)
+{
+	struct pvr2_sysfs *sfp;
+	sfp = dev_get_drvdata(class_dev);
+	if (!sfp) return -EINVAL;
+	return pvr2_debugifc_print_status(sfp->channel.hdw,buf,PAGE_SIZE);
+}
+
+
+static ssize_t debugcmd_store(struct device *class_dev,
+			      struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct pvr2_sysfs *sfp;
+	int ret;
+
+	sfp = dev_get_drvdata(class_dev);
+	if (!sfp) return -EINVAL;
+
+	ret = pvr2_debugifc_docmd(sfp->channel.hdw,buf,count);
+	if (ret < 0) return ret;
+	return count;
+}
+#endif /* CONFIG_VIDEO_PVRUSB2_DEBUGIFC */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-sysfs.h b/drivers/media/usb/pvrusb2/pvrusb2-sysfs.h
new file mode 100644
index 0000000..431f4fd
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-sysfs.h
@@ -0,0 +1,32 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+#ifndef __PVRUSB2_SYSFS_H
+#define __PVRUSB2_SYSFS_H
+
+#include <linux/list.h>
+#include <linux/sysfs.h>
+#include "pvrusb2-context.h"
+
+struct pvr2_sysfs;
+struct pvr2_sysfs_class;
+
+struct pvr2_sysfs_class *pvr2_sysfs_class_create(void);
+void pvr2_sysfs_class_destroy(struct pvr2_sysfs_class *);
+
+struct pvr2_sysfs *pvr2_sysfs_create(struct pvr2_context *,
+				     struct pvr2_sysfs_class *);
+
+#endif /* __PVRUSB2_SYSFS_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-util.h b/drivers/media/usb/pvrusb2/pvrusb2-util.h
new file mode 100644
index 0000000..b03ca3e
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-util.h
@@ -0,0 +1,48 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+#ifndef __PVRUSB2_UTIL_H
+#define __PVRUSB2_UTIL_H
+
+#define PVR2_DECOMPOSE_LE(t,i,d) \
+    do {    \
+	(t)[i] = (d) & 0xff;\
+	(t)[i+1] = ((d) >> 8) & 0xff;\
+	(t)[i+2] = ((d) >> 16) & 0xff;\
+	(t)[i+3] = ((d) >> 24) & 0xff;\
+    } while(0)
+
+#define PVR2_DECOMPOSE_BE(t,i,d) \
+    do {    \
+	(t)[i+3] = (d) & 0xff;\
+	(t)[i+2] = ((d) >> 8) & 0xff;\
+	(t)[i+1] = ((d) >> 16) & 0xff;\
+	(t)[i] = ((d) >> 24) & 0xff;\
+    } while(0)
+
+#define PVR2_COMPOSE_LE(t,i) \
+    ((((u32)((t)[i+3])) << 24) | \
+     (((u32)((t)[i+2])) << 16) | \
+     (((u32)((t)[i+1])) << 8) | \
+     ((u32)((t)[i])))
+
+#define PVR2_COMPOSE_BE(t,i) \
+    ((((u32)((t)[i])) << 24) | \
+     (((u32)((t)[i+1])) << 16) | \
+     (((u32)((t)[i+2])) << 8) | \
+     ((u32)((t)[i+3])))
+
+
+#endif /* __PVRUSB2_UTIL_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-v4l2.c b/drivers/media/usb/pvrusb2/pvrusb2-v4l2.c
new file mode 100644
index 0000000..e53a80b
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-v4l2.c
@@ -0,0 +1,1299 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include "pvrusb2-context.h"
+#include "pvrusb2-hdw.h"
+#include "pvrusb2.h"
+#include "pvrusb2-debug.h"
+#include "pvrusb2-v4l2.h"
+#include "pvrusb2-ioread.h"
+#include <linux/videodev2.h>
+#include <linux/module.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fh.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+
+struct pvr2_v4l2_dev;
+struct pvr2_v4l2_fh;
+struct pvr2_v4l2;
+
+struct pvr2_v4l2_dev {
+	struct video_device devbase; /* MUST be first! */
+	struct pvr2_v4l2 *v4lp;
+	struct pvr2_context_stream *stream;
+	/* Information about this device: */
+	enum pvr2_config config; /* Expected stream format */
+	int v4l_type; /* V4L defined type for this device node */
+	enum pvr2_v4l_type minor_type; /* pvr2-understood minor device type */
+};
+
+struct pvr2_v4l2_fh {
+	struct v4l2_fh fh;
+	struct pvr2_channel channel;
+	struct pvr2_v4l2_dev *pdi;
+	struct pvr2_ioread *rhp;
+	struct file *file;
+	wait_queue_head_t wait_data;
+	int fw_mode_flag;
+	/* Map contiguous ordinal value to input id */
+	unsigned char *input_map;
+	unsigned int input_cnt;
+};
+
+struct pvr2_v4l2 {
+	struct pvr2_channel channel;
+
+	/* streams - Note that these must be separately, individually,
+	 * allocated pointers.  This is because the v4l core is going to
+	 * manage their deletion - separately, individually...  */
+	struct pvr2_v4l2_dev *dev_video;
+	struct pvr2_v4l2_dev *dev_radio;
+};
+
+static int video_nr[PVR_NUM] = {[0 ... PVR_NUM-1] = -1};
+module_param_array(video_nr, int, NULL, 0444);
+MODULE_PARM_DESC(video_nr, "Offset for device's video dev minor");
+static int radio_nr[PVR_NUM] = {[0 ... PVR_NUM-1] = -1};
+module_param_array(radio_nr, int, NULL, 0444);
+MODULE_PARM_DESC(radio_nr, "Offset for device's radio dev minor");
+static int vbi_nr[PVR_NUM] = {[0 ... PVR_NUM-1] = -1};
+module_param_array(vbi_nr, int, NULL, 0444);
+MODULE_PARM_DESC(vbi_nr, "Offset for device's vbi dev minor");
+
+#define PVR_FORMAT_PIX  0
+#define PVR_FORMAT_VBI  1
+
+static struct v4l2_format pvr_format [] = {
+	[PVR_FORMAT_PIX] = {
+		.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE,
+		.fmt    = {
+			.pix        = {
+				.width          = 720,
+				.height         = 576,
+				.pixelformat    = V4L2_PIX_FMT_MPEG,
+				.field          = V4L2_FIELD_INTERLACED,
+				/* FIXME : Don't know what to put here... */
+				.sizeimage      = 32 * 1024,
+			}
+		}
+	},
+	[PVR_FORMAT_VBI] = {
+		.type   = V4L2_BUF_TYPE_VBI_CAPTURE,
+		.fmt    = {
+			.vbi        = {
+				.sampling_rate = 27000000,
+				.offset = 248,
+				.samples_per_line = 1443,
+				.sample_format = V4L2_PIX_FMT_GREY,
+				.start = { 0, 0 },
+				.count = { 0, 0 },
+				.flags = 0,
+			}
+		}
+	}
+};
+
+
+
+/*
+ * This is part of Video 4 Linux API. These procedures handle ioctl() calls.
+ */
+static int pvr2_querycap(struct file *file, void *priv, struct v4l2_capability *cap)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+
+	strlcpy(cap->driver, "pvrusb2", sizeof(cap->driver));
+	strlcpy(cap->bus_info, pvr2_hdw_get_bus_info(hdw),
+			sizeof(cap->bus_info));
+	strlcpy(cap->card, pvr2_hdw_get_desc(hdw), sizeof(cap->card));
+	cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TUNER |
+			    V4L2_CAP_AUDIO | V4L2_CAP_RADIO |
+			    V4L2_CAP_READWRITE | V4L2_CAP_DEVICE_CAPS;
+	switch (fh->pdi->devbase.vfl_type) {
+	case VFL_TYPE_GRABBER:
+		cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_AUDIO;
+		break;
+	case VFL_TYPE_RADIO:
+		cap->device_caps = V4L2_CAP_RADIO;
+		break;
+	default:
+		return -EINVAL;
+	}
+	cap->device_caps |= V4L2_CAP_TUNER | V4L2_CAP_READWRITE;
+	return 0;
+}
+
+static int pvr2_g_std(struct file *file, void *priv, v4l2_std_id *std)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	int val = 0;
+	int ret;
+
+	ret = pvr2_ctrl_get_value(
+			pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_STDCUR), &val);
+	*std = val;
+	return ret;
+}
+
+static int pvr2_s_std(struct file *file, void *priv, v4l2_std_id std)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	int ret;
+
+	ret = pvr2_ctrl_set_value(
+		pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_STDCUR), std);
+	pvr2_hdw_commit_ctl(hdw);
+	return ret;
+}
+
+static int pvr2_querystd(struct file *file, void *priv, v4l2_std_id *std)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	int val = 0;
+	int ret;
+
+	ret = pvr2_ctrl_get_value(
+		pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_STDDETECT), &val);
+	*std = val;
+	return ret;
+}
+
+static int pvr2_enum_input(struct file *file, void *priv, struct v4l2_input *vi)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	struct pvr2_ctrl *cptr;
+	struct v4l2_input tmp;
+	unsigned int cnt;
+	int val;
+
+	cptr = pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_INPUT);
+
+	memset(&tmp, 0, sizeof(tmp));
+	tmp.index = vi->index;
+	if (vi->index >= fh->input_cnt)
+		return -EINVAL;
+	val = fh->input_map[vi->index];
+	switch (val) {
+	case PVR2_CVAL_INPUT_TV:
+	case PVR2_CVAL_INPUT_DTV:
+	case PVR2_CVAL_INPUT_RADIO:
+		tmp.type = V4L2_INPUT_TYPE_TUNER;
+		break;
+	case PVR2_CVAL_INPUT_SVIDEO:
+	case PVR2_CVAL_INPUT_COMPOSITE:
+		tmp.type = V4L2_INPUT_TYPE_CAMERA;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	cnt = 0;
+	pvr2_ctrl_get_valname(cptr, val,
+			tmp.name, sizeof(tmp.name) - 1, &cnt);
+	tmp.name[cnt] = 0;
+
+	/* Don't bother with audioset, since this driver currently
+	   always switches the audio whenever the video is
+	   switched. */
+
+	/* Handling std is a tougher problem.  It doesn't make
+	   sense in cases where a device might be multi-standard.
+	   We could just copy out the current value for the
+	   standard, but it can change over time.  For now just
+	   leave it zero. */
+	*vi = tmp;
+	return 0;
+}
+
+static int pvr2_g_input(struct file *file, void *priv, unsigned int *i)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	unsigned int idx;
+	struct pvr2_ctrl *cptr;
+	int val;
+	int ret;
+
+	cptr = pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_INPUT);
+	val = 0;
+	ret = pvr2_ctrl_get_value(cptr, &val);
+	*i = 0;
+	for (idx = 0; idx < fh->input_cnt; idx++) {
+		if (fh->input_map[idx] == val) {
+			*i = idx;
+			break;
+		}
+	}
+	return ret;
+}
+
+static int pvr2_s_input(struct file *file, void *priv, unsigned int inp)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	int ret;
+
+	if (inp >= fh->input_cnt)
+		return -EINVAL;
+	ret = pvr2_ctrl_set_value(
+			pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_INPUT),
+			fh->input_map[inp]);
+	pvr2_hdw_commit_ctl(hdw);
+	return ret;
+}
+
+static int pvr2_enumaudio(struct file *file, void *priv, struct v4l2_audio *vin)
+{
+	/* pkt: FIXME: We are returning one "fake" input here
+	   which could very well be called "whatever_we_like".
+	   This is for apps that want to see an audio input
+	   just to feel comfortable, as well as to test if
+	   it can do stereo or sth. There is actually no guarantee
+	   that the actual audio input cannot change behind the app's
+	   back, but most applications should not mind that either.
+
+	   Hopefully, mplayer people will work with us on this (this
+	   whole mess is to support mplayer pvr://), or Hans will come
+	   up with a more standard way to say "we have inputs but we
+	   don 't want you to change them independent of video" which
+	   will sort this mess.
+	 */
+
+	if (vin->index > 0)
+		return -EINVAL;
+	strncpy(vin->name, "PVRUSB2 Audio", 14);
+	vin->capability = V4L2_AUDCAP_STEREO;
+	return 0;
+}
+
+static int pvr2_g_audio(struct file *file, void *priv, struct v4l2_audio *vin)
+{
+	/* pkt: FIXME: see above comment (VIDIOC_ENUMAUDIO) */
+	vin->index = 0;
+	strncpy(vin->name, "PVRUSB2 Audio", 14);
+	vin->capability = V4L2_AUDCAP_STEREO;
+	return 0;
+}
+
+static int pvr2_s_audio(struct file *file, void *priv, const struct v4l2_audio *vout)
+{
+	if (vout->index)
+		return -EINVAL;
+	return 0;
+}
+
+static int pvr2_g_tuner(struct file *file, void *priv, struct v4l2_tuner *vt)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+
+	if (vt->index != 0)
+		return -EINVAL; /* Only answer for the 1st tuner */
+
+	pvr2_hdw_execute_tuner_poll(hdw);
+	return pvr2_hdw_get_tuner_status(hdw, vt);
+}
+
+static int pvr2_s_tuner(struct file *file, void *priv, const struct v4l2_tuner *vt)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	int ret;
+
+	if (vt->index != 0)
+		return -EINVAL;
+
+	ret = pvr2_ctrl_set_value(
+			pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_AUDIOMODE),
+			vt->audmode);
+	pvr2_hdw_commit_ctl(hdw);
+	return ret;
+}
+
+static int pvr2_s_frequency(struct file *file, void *priv, const struct v4l2_frequency *vf)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	unsigned long fv;
+	struct v4l2_tuner vt;
+	int cur_input;
+	struct pvr2_ctrl *ctrlp;
+	int ret;
+
+	ret = pvr2_hdw_get_tuner_status(hdw, &vt);
+	if (ret != 0)
+		return ret;
+	ctrlp = pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_INPUT);
+	ret = pvr2_ctrl_get_value(ctrlp, &cur_input);
+	if (ret != 0)
+		return ret;
+	if (vf->type == V4L2_TUNER_RADIO) {
+		if (cur_input != PVR2_CVAL_INPUT_RADIO)
+			pvr2_ctrl_set_value(ctrlp, PVR2_CVAL_INPUT_RADIO);
+	} else {
+		if (cur_input == PVR2_CVAL_INPUT_RADIO)
+			pvr2_ctrl_set_value(ctrlp, PVR2_CVAL_INPUT_TV);
+	}
+	fv = vf->frequency;
+	if (vt.capability & V4L2_TUNER_CAP_LOW)
+		fv = (fv * 125) / 2;
+	else
+		fv = fv * 62500;
+	ret = pvr2_ctrl_set_value(
+			pvr2_hdw_get_ctrl_by_id(hdw,PVR2_CID_FREQUENCY),fv);
+	pvr2_hdw_commit_ctl(hdw);
+	return ret;
+}
+
+static int pvr2_g_frequency(struct file *file, void *priv, struct v4l2_frequency *vf)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	int val = 0;
+	int cur_input;
+	struct v4l2_tuner vt;
+	int ret;
+
+	ret = pvr2_hdw_get_tuner_status(hdw, &vt);
+	if (ret != 0)
+		return ret;
+	ret = pvr2_ctrl_get_value(
+			pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_FREQUENCY),
+			&val);
+	if (ret != 0)
+		return ret;
+	pvr2_ctrl_get_value(
+			pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_INPUT),
+			&cur_input);
+	if (cur_input == PVR2_CVAL_INPUT_RADIO)
+		vf->type = V4L2_TUNER_RADIO;
+	else
+		vf->type = V4L2_TUNER_ANALOG_TV;
+	if (vt.capability & V4L2_TUNER_CAP_LOW)
+		val = (val * 2) / 125;
+	else
+		val /= 62500;
+	vf->frequency = val;
+	return 0;
+}
+
+static int pvr2_enum_fmt_vid_cap(struct file *file, void *priv, struct v4l2_fmtdesc *fd)
+{
+	/* Only one format is supported: MPEG. */
+	if (fd->index)
+		return -EINVAL;
+
+	fd->pixelformat = V4L2_PIX_FMT_MPEG;
+	return 0;
+}
+
+static int pvr2_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *vf)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	int val;
+
+	memcpy(vf, &pvr_format[PVR_FORMAT_PIX], sizeof(struct v4l2_format));
+	val = 0;
+	pvr2_ctrl_get_value(
+			pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_HRES),
+			&val);
+	vf->fmt.pix.width = val;
+	val = 0;
+	pvr2_ctrl_get_value(
+			pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_VRES),
+			&val);
+	vf->fmt.pix.height = val;
+	return 0;
+}
+
+static int pvr2_try_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *vf)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	int lmin, lmax, ldef;
+	struct pvr2_ctrl *hcp, *vcp;
+	int h = vf->fmt.pix.height;
+	int w = vf->fmt.pix.width;
+
+	hcp = pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_HRES);
+	vcp = pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_VRES);
+
+	lmin = pvr2_ctrl_get_min(hcp);
+	lmax = pvr2_ctrl_get_max(hcp);
+	pvr2_ctrl_get_def(hcp, &ldef);
+	if (w == -1)
+		w = ldef;
+	else if (w < lmin)
+		w = lmin;
+	else if (w > lmax)
+		w = lmax;
+	lmin = pvr2_ctrl_get_min(vcp);
+	lmax = pvr2_ctrl_get_max(vcp);
+	pvr2_ctrl_get_def(vcp, &ldef);
+	if (h == -1)
+		h = ldef;
+	else if (h < lmin)
+		h = lmin;
+	else if (h > lmax)
+		h = lmax;
+
+	memcpy(vf, &pvr_format[PVR_FORMAT_PIX],
+			sizeof(struct v4l2_format));
+	vf->fmt.pix.width = w;
+	vf->fmt.pix.height = h;
+	return 0;
+}
+
+static int pvr2_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *vf)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	struct pvr2_ctrl *hcp, *vcp;
+	int ret = pvr2_try_fmt_vid_cap(file, fh, vf);
+
+	if (ret)
+		return ret;
+	hcp = pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_HRES);
+	vcp = pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_VRES);
+	pvr2_ctrl_set_value(hcp, vf->fmt.pix.width);
+	pvr2_ctrl_set_value(vcp, vf->fmt.pix.height);
+	pvr2_hdw_commit_ctl(hdw);
+	return 0;
+}
+
+static int pvr2_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	struct pvr2_v4l2_dev *pdi = fh->pdi;
+	int ret;
+
+	if (!fh->pdi->stream) {
+		/* No stream defined for this node.  This means
+		   that we're not currently allowed to stream from
+		   this node. */
+		return -EPERM;
+	}
+	ret = pvr2_hdw_set_stream_type(hdw, pdi->config);
+	if (ret < 0)
+		return ret;
+	return pvr2_hdw_set_streaming(hdw, !0);
+}
+
+static int pvr2_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+
+	if (!fh->pdi->stream) {
+		/* No stream defined for this node.  This means
+		   that we're not currently allowed to stream from
+		   this node. */
+		return -EPERM;
+	}
+	return pvr2_hdw_set_streaming(hdw, 0);
+}
+
+static int pvr2_queryctrl(struct file *file, void *priv,
+		struct v4l2_queryctrl *vc)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	struct pvr2_ctrl *cptr;
+	int val;
+
+	if (vc->id & V4L2_CTRL_FLAG_NEXT_CTRL) {
+		cptr = pvr2_hdw_get_ctrl_nextv4l(
+				hdw, (vc->id & ~V4L2_CTRL_FLAG_NEXT_CTRL));
+		if (cptr)
+			vc->id = pvr2_ctrl_get_v4lid(cptr);
+	} else {
+		cptr = pvr2_hdw_get_ctrl_v4l(hdw, vc->id);
+	}
+	if (!cptr) {
+		pvr2_trace(PVR2_TRACE_V4LIOCTL,
+				"QUERYCTRL id=0x%x not implemented here",
+				vc->id);
+		return -EINVAL;
+	}
+
+	pvr2_trace(PVR2_TRACE_V4LIOCTL,
+			"QUERYCTRL id=0x%x mapping name=%s (%s)",
+			vc->id, pvr2_ctrl_get_name(cptr),
+			pvr2_ctrl_get_desc(cptr));
+	strlcpy(vc->name, pvr2_ctrl_get_desc(cptr), sizeof(vc->name));
+	vc->flags = pvr2_ctrl_get_v4lflags(cptr);
+	pvr2_ctrl_get_def(cptr, &val);
+	vc->default_value = val;
+	switch (pvr2_ctrl_get_type(cptr)) {
+	case pvr2_ctl_enum:
+		vc->type = V4L2_CTRL_TYPE_MENU;
+		vc->minimum = 0;
+		vc->maximum = pvr2_ctrl_get_cnt(cptr) - 1;
+		vc->step = 1;
+		break;
+	case pvr2_ctl_bool:
+		vc->type = V4L2_CTRL_TYPE_BOOLEAN;
+		vc->minimum = 0;
+		vc->maximum = 1;
+		vc->step = 1;
+		break;
+	case pvr2_ctl_int:
+		vc->type = V4L2_CTRL_TYPE_INTEGER;
+		vc->minimum = pvr2_ctrl_get_min(cptr);
+		vc->maximum = pvr2_ctrl_get_max(cptr);
+		vc->step = 1;
+		break;
+	default:
+		pvr2_trace(PVR2_TRACE_V4LIOCTL,
+				"QUERYCTRL id=0x%x name=%s not mappable",
+				vc->id, pvr2_ctrl_get_name(cptr));
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int pvr2_querymenu(struct file *file, void *priv, struct v4l2_querymenu *vm)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	unsigned int cnt = 0;
+	int ret;
+
+	ret = pvr2_ctrl_get_valname(pvr2_hdw_get_ctrl_v4l(hdw, vm->id),
+			vm->index,
+			vm->name, sizeof(vm->name) - 1,
+			&cnt);
+	vm->name[cnt] = 0;
+	return ret;
+}
+
+static int pvr2_g_ctrl(struct file *file, void *priv, struct v4l2_control *vc)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	int val = 0;
+	int ret;
+
+	ret = pvr2_ctrl_get_value(pvr2_hdw_get_ctrl_v4l(hdw, vc->id),
+			&val);
+	vc->value = val;
+	return ret;
+}
+
+static int pvr2_s_ctrl(struct file *file, void *priv, struct v4l2_control *vc)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	int ret;
+
+	ret = pvr2_ctrl_set_value(pvr2_hdw_get_ctrl_v4l(hdw, vc->id),
+			vc->value);
+	pvr2_hdw_commit_ctl(hdw);
+	return ret;
+}
+
+static int pvr2_g_ext_ctrls(struct file *file, void *priv,
+					struct v4l2_ext_controls *ctls)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	struct v4l2_ext_control *ctrl;
+	struct pvr2_ctrl *cptr;
+	unsigned int idx;
+	int val;
+	int ret;
+
+	ret = 0;
+	for (idx = 0; idx < ctls->count; idx++) {
+		ctrl = ctls->controls + idx;
+		cptr = pvr2_hdw_get_ctrl_v4l(hdw, ctrl->id);
+		if (cptr) {
+			if (ctls->which == V4L2_CTRL_WHICH_DEF_VAL)
+				pvr2_ctrl_get_def(cptr, &val);
+			else
+				ret = pvr2_ctrl_get_value(cptr, &val);
+		} else
+			ret = -EINVAL;
+
+		if (ret) {
+			ctls->error_idx = idx;
+			return ret;
+		}
+		/* Ensure that if read as a 64 bit value, the user
+		   will still get a hopefully sane value */
+		ctrl->value64 = 0;
+		ctrl->value = val;
+	}
+	return 0;
+}
+
+static int pvr2_s_ext_ctrls(struct file *file, void *priv,
+		struct v4l2_ext_controls *ctls)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	struct v4l2_ext_control *ctrl;
+	unsigned int idx;
+	int ret;
+
+	/* Default value cannot be changed */
+	if (ctls->which == V4L2_CTRL_WHICH_DEF_VAL)
+		return -EINVAL;
+
+	ret = 0;
+	for (idx = 0; idx < ctls->count; idx++) {
+		ctrl = ctls->controls + idx;
+		ret = pvr2_ctrl_set_value(
+				pvr2_hdw_get_ctrl_v4l(hdw, ctrl->id),
+				ctrl->value);
+		if (ret) {
+			ctls->error_idx = idx;
+			goto commit;
+		}
+	}
+commit:
+	pvr2_hdw_commit_ctl(hdw);
+	return ret;
+}
+
+static int pvr2_try_ext_ctrls(struct file *file, void *priv,
+		struct v4l2_ext_controls *ctls)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	struct v4l2_ext_control *ctrl;
+	struct pvr2_ctrl *pctl;
+	unsigned int idx;
+
+	/* For the moment just validate that the requested control
+	   actually exists. */
+	for (idx = 0; idx < ctls->count; idx++) {
+		ctrl = ctls->controls + idx;
+		pctl = pvr2_hdw_get_ctrl_v4l(hdw, ctrl->id);
+		if (!pctl) {
+			ctls->error_idx = idx;
+			return -EINVAL;
+		}
+	}
+	return 0;
+}
+
+static int pvr2_cropcap(struct file *file, void *priv, struct v4l2_cropcap *cap)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	int ret;
+
+	if (cap->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+	ret = pvr2_hdw_get_cropcap(hdw, cap);
+	cap->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; /* paranoia */
+	return ret;
+}
+
+static int pvr2_g_selection(struct file *file, void *priv,
+			    struct v4l2_selection *sel)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	struct v4l2_cropcap cap;
+	int val = 0;
+	int ret;
+
+	if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	cap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+	switch (sel->target) {
+	case V4L2_SEL_TGT_CROP:
+		ret = pvr2_ctrl_get_value(
+			  pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_CROPL), &val);
+		if (ret != 0)
+			return -EINVAL;
+		sel->r.left = val;
+		ret = pvr2_ctrl_get_value(
+			  pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_CROPT), &val);
+		if (ret != 0)
+			return -EINVAL;
+		sel->r.top = val;
+		ret = pvr2_ctrl_get_value(
+			  pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_CROPW), &val);
+		if (ret != 0)
+			return -EINVAL;
+		sel->r.width = val;
+		ret = pvr2_ctrl_get_value(
+			  pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_CROPH), &val);
+		if (ret != 0)
+			return -EINVAL;
+		sel->r.height = val;
+		break;
+	case V4L2_SEL_TGT_CROP_DEFAULT:
+		ret = pvr2_hdw_get_cropcap(hdw, &cap);
+		sel->r = cap.defrect;
+		break;
+	case V4L2_SEL_TGT_CROP_BOUNDS:
+		ret = pvr2_hdw_get_cropcap(hdw, &cap);
+		sel->r = cap.bounds;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return ret;
+}
+
+static int pvr2_s_selection(struct file *file, void *priv,
+			    struct v4l2_selection *sel)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+	int ret;
+
+	if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE ||
+	    sel->target != V4L2_SEL_TGT_CROP)
+		return -EINVAL;
+	ret = pvr2_ctrl_set_value(
+			pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_CROPL),
+			sel->r.left);
+	if (ret != 0)
+		goto commit;
+	ret = pvr2_ctrl_set_value(
+			pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_CROPT),
+			sel->r.top);
+	if (ret != 0)
+		goto commit;
+	ret = pvr2_ctrl_set_value(
+			pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_CROPW),
+			sel->r.width);
+	if (ret != 0)
+		goto commit;
+	ret = pvr2_ctrl_set_value(
+			pvr2_hdw_get_ctrl_by_id(hdw, PVR2_CID_CROPH),
+			sel->r.height);
+commit:
+	pvr2_hdw_commit_ctl(hdw);
+	return ret;
+}
+
+static int pvr2_log_status(struct file *file, void *priv)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+
+	pvr2_hdw_trigger_module_log(hdw);
+	return 0;
+}
+
+static const struct v4l2_ioctl_ops pvr2_ioctl_ops = {
+	.vidioc_querycap		    = pvr2_querycap,
+	.vidioc_s_audio			    = pvr2_s_audio,
+	.vidioc_g_audio			    = pvr2_g_audio,
+	.vidioc_enumaudio		    = pvr2_enumaudio,
+	.vidioc_enum_input		    = pvr2_enum_input,
+	.vidioc_cropcap			    = pvr2_cropcap,
+	.vidioc_s_selection		    = pvr2_s_selection,
+	.vidioc_g_selection		    = pvr2_g_selection,
+	.vidioc_g_input			    = pvr2_g_input,
+	.vidioc_s_input			    = pvr2_s_input,
+	.vidioc_g_frequency		    = pvr2_g_frequency,
+	.vidioc_s_frequency		    = pvr2_s_frequency,
+	.vidioc_s_tuner			    = pvr2_s_tuner,
+	.vidioc_g_tuner			    = pvr2_g_tuner,
+	.vidioc_g_std			    = pvr2_g_std,
+	.vidioc_s_std			    = pvr2_s_std,
+	.vidioc_querystd		    = pvr2_querystd,
+	.vidioc_log_status		    = pvr2_log_status,
+	.vidioc_enum_fmt_vid_cap	    = pvr2_enum_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap		    = pvr2_g_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap		    = pvr2_s_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap		    = pvr2_try_fmt_vid_cap,
+	.vidioc_streamon		    = pvr2_streamon,
+	.vidioc_streamoff		    = pvr2_streamoff,
+	.vidioc_queryctrl		    = pvr2_queryctrl,
+	.vidioc_querymenu		    = pvr2_querymenu,
+	.vidioc_g_ctrl			    = pvr2_g_ctrl,
+	.vidioc_s_ctrl			    = pvr2_s_ctrl,
+	.vidioc_g_ext_ctrls		    = pvr2_g_ext_ctrls,
+	.vidioc_s_ext_ctrls		    = pvr2_s_ext_ctrls,
+	.vidioc_try_ext_ctrls		    = pvr2_try_ext_ctrls,
+};
+
+static void pvr2_v4l2_dev_destroy(struct pvr2_v4l2_dev *dip)
+{
+	struct pvr2_hdw *hdw = dip->v4lp->channel.mc_head->hdw;
+	enum pvr2_config cfg = dip->config;
+	char msg[80];
+	unsigned int mcnt;
+
+	/* Construct the unregistration message *before* we actually
+	   perform the unregistration step.  By doing it this way we don't
+	   have to worry about potentially touching deleted resources. */
+	mcnt = scnprintf(msg, sizeof(msg) - 1,
+			 "pvrusb2: unregistered device %s [%s]",
+			 video_device_node_name(&dip->devbase),
+			 pvr2_config_get_name(cfg));
+	msg[mcnt] = 0;
+
+	pvr2_hdw_v4l_store_minor_number(hdw,dip->minor_type,-1);
+
+	/* Paranoia */
+	dip->v4lp = NULL;
+	dip->stream = NULL;
+
+	/* Actual deallocation happens later when all internal references
+	   are gone. */
+	video_unregister_device(&dip->devbase);
+
+	printk(KERN_INFO "%s\n", msg);
+
+}
+
+
+static void pvr2_v4l2_dev_disassociate_parent(struct pvr2_v4l2_dev *dip)
+{
+	if (!dip) return;
+	if (!dip->devbase.v4l2_dev->dev) return;
+	dip->devbase.v4l2_dev->dev = NULL;
+	device_move(&dip->devbase.dev, NULL, DPM_ORDER_NONE);
+}
+
+
+static void pvr2_v4l2_destroy_no_lock(struct pvr2_v4l2 *vp)
+{
+	if (vp->dev_video) {
+		pvr2_v4l2_dev_destroy(vp->dev_video);
+		vp->dev_video = NULL;
+	}
+	if (vp->dev_radio) {
+		pvr2_v4l2_dev_destroy(vp->dev_radio);
+		vp->dev_radio = NULL;
+	}
+
+	pvr2_trace(PVR2_TRACE_STRUCT,"Destroying pvr2_v4l2 id=%p",vp);
+	pvr2_channel_done(&vp->channel);
+	kfree(vp);
+}
+
+
+static void pvr2_video_device_release(struct video_device *vdev)
+{
+	struct pvr2_v4l2_dev *dev;
+	dev = container_of(vdev,struct pvr2_v4l2_dev,devbase);
+	kfree(dev);
+}
+
+
+static void pvr2_v4l2_internal_check(struct pvr2_channel *chp)
+{
+	struct pvr2_v4l2 *vp;
+	vp = container_of(chp,struct pvr2_v4l2,channel);
+	if (!vp->channel.mc_head->disconnect_flag) return;
+	pvr2_v4l2_dev_disassociate_parent(vp->dev_video);
+	pvr2_v4l2_dev_disassociate_parent(vp->dev_radio);
+	if (!list_empty(&vp->dev_video->devbase.fh_list) ||
+	    !list_empty(&vp->dev_radio->devbase.fh_list))
+		return;
+	pvr2_v4l2_destroy_no_lock(vp);
+}
+
+
+static int pvr2_v4l2_release(struct file *file)
+{
+	struct pvr2_v4l2_fh *fhp = file->private_data;
+	struct pvr2_v4l2 *vp = fhp->pdi->v4lp;
+	struct pvr2_hdw *hdw = fhp->channel.mc_head->hdw;
+
+	pvr2_trace(PVR2_TRACE_OPEN_CLOSE,"pvr2_v4l2_release");
+
+	if (fhp->rhp) {
+		struct pvr2_stream *sp;
+		pvr2_hdw_set_streaming(hdw,0);
+		sp = pvr2_ioread_get_stream(fhp->rhp);
+		if (sp) pvr2_stream_set_callback(sp,NULL,NULL);
+		pvr2_ioread_destroy(fhp->rhp);
+		fhp->rhp = NULL;
+	}
+
+	v4l2_fh_del(&fhp->fh);
+	v4l2_fh_exit(&fhp->fh);
+	file->private_data = NULL;
+
+	pvr2_channel_done(&fhp->channel);
+	pvr2_trace(PVR2_TRACE_STRUCT,
+		   "Destroying pvr_v4l2_fh id=%p",fhp);
+	if (fhp->input_map) {
+		kfree(fhp->input_map);
+		fhp->input_map = NULL;
+	}
+	kfree(fhp);
+	if (vp->channel.mc_head->disconnect_flag &&
+	    list_empty(&vp->dev_video->devbase.fh_list) &&
+	    list_empty(&vp->dev_radio->devbase.fh_list)) {
+		pvr2_v4l2_destroy_no_lock(vp);
+	}
+	return 0;
+}
+
+
+static int pvr2_v4l2_open(struct file *file)
+{
+	struct pvr2_v4l2_dev *dip; /* Our own context pointer */
+	struct pvr2_v4l2_fh *fhp;
+	struct pvr2_v4l2 *vp;
+	struct pvr2_hdw *hdw;
+	unsigned int input_mask = 0;
+	unsigned int input_cnt,idx;
+	int ret = 0;
+
+	dip = container_of(video_devdata(file),struct pvr2_v4l2_dev,devbase);
+
+	vp = dip->v4lp;
+	hdw = vp->channel.hdw;
+
+	pvr2_trace(PVR2_TRACE_OPEN_CLOSE,"pvr2_v4l2_open");
+
+	if (!pvr2_hdw_dev_ok(hdw)) {
+		pvr2_trace(PVR2_TRACE_OPEN_CLOSE,
+			   "pvr2_v4l2_open: hardware not ready");
+		return -EIO;
+	}
+
+	fhp = kzalloc(sizeof(*fhp),GFP_KERNEL);
+	if (!fhp) {
+		return -ENOMEM;
+	}
+
+	v4l2_fh_init(&fhp->fh, &dip->devbase);
+	init_waitqueue_head(&fhp->wait_data);
+	fhp->pdi = dip;
+
+	pvr2_trace(PVR2_TRACE_STRUCT,"Creating pvr_v4l2_fh id=%p",fhp);
+	pvr2_channel_init(&fhp->channel,vp->channel.mc_head);
+
+	if (dip->v4l_type == VFL_TYPE_RADIO) {
+		/* Opening device as a radio, legal input selection subset
+		   is just the radio. */
+		input_mask = (1 << PVR2_CVAL_INPUT_RADIO);
+	} else {
+		/* Opening the main V4L device, legal input selection
+		   subset includes all analog inputs. */
+		input_mask = ((1 << PVR2_CVAL_INPUT_RADIO) |
+			      (1 << PVR2_CVAL_INPUT_TV) |
+			      (1 << PVR2_CVAL_INPUT_COMPOSITE) |
+			      (1 << PVR2_CVAL_INPUT_SVIDEO));
+	}
+	ret = pvr2_channel_limit_inputs(&fhp->channel,input_mask);
+	if (ret) {
+		pvr2_channel_done(&fhp->channel);
+		pvr2_trace(PVR2_TRACE_STRUCT,
+			   "Destroying pvr_v4l2_fh id=%p (input mask error)",
+			   fhp);
+		v4l2_fh_exit(&fhp->fh);
+		kfree(fhp);
+		return ret;
+	}
+
+	input_mask &= pvr2_hdw_get_input_available(hdw);
+	input_cnt = 0;
+	for (idx = 0; idx < (sizeof(input_mask) << 3); idx++) {
+		if (input_mask & (1 << idx)) input_cnt++;
+	}
+	fhp->input_cnt = input_cnt;
+	fhp->input_map = kzalloc(input_cnt,GFP_KERNEL);
+	if (!fhp->input_map) {
+		pvr2_channel_done(&fhp->channel);
+		pvr2_trace(PVR2_TRACE_STRUCT,
+			   "Destroying pvr_v4l2_fh id=%p (input map failure)",
+			   fhp);
+		v4l2_fh_exit(&fhp->fh);
+		kfree(fhp);
+		return -ENOMEM;
+	}
+	input_cnt = 0;
+	for (idx = 0; idx < (sizeof(input_mask) << 3); idx++) {
+		if (!(input_mask & (1 << idx))) continue;
+		fhp->input_map[input_cnt++] = idx;
+	}
+
+	fhp->file = file;
+	file->private_data = fhp;
+
+	fhp->fw_mode_flag = pvr2_hdw_cpufw_get_enabled(hdw);
+	v4l2_fh_add(&fhp->fh);
+
+	return 0;
+}
+
+
+static void pvr2_v4l2_notify(struct pvr2_v4l2_fh *fhp)
+{
+	wake_up(&fhp->wait_data);
+}
+
+static int pvr2_v4l2_iosetup(struct pvr2_v4l2_fh *fh)
+{
+	int ret;
+	struct pvr2_stream *sp;
+	struct pvr2_hdw *hdw;
+	if (fh->rhp) return 0;
+
+	if (!fh->pdi->stream) {
+		/* No stream defined for this node.  This means that we're
+		   not currently allowed to stream from this node. */
+		return -EPERM;
+	}
+
+	/* First read() attempt.  Try to claim the stream and start
+	   it... */
+	if ((ret = pvr2_channel_claim_stream(&fh->channel,
+					     fh->pdi->stream)) != 0) {
+		/* Someone else must already have it */
+		return ret;
+	}
+
+	fh->rhp = pvr2_channel_create_mpeg_stream(fh->pdi->stream);
+	if (!fh->rhp) {
+		pvr2_channel_claim_stream(&fh->channel,NULL);
+		return -ENOMEM;
+	}
+
+	hdw = fh->channel.mc_head->hdw;
+	sp = fh->pdi->stream->stream;
+	pvr2_stream_set_callback(sp,(pvr2_stream_callback)pvr2_v4l2_notify,fh);
+	pvr2_hdw_set_stream_type(hdw,fh->pdi->config);
+	if ((ret = pvr2_hdw_set_streaming(hdw,!0)) < 0) return ret;
+	return pvr2_ioread_set_enabled(fh->rhp,!0);
+}
+
+
+static ssize_t pvr2_v4l2_read(struct file *file,
+			      char __user *buff, size_t count, loff_t *ppos)
+{
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	int ret;
+
+	if (fh->fw_mode_flag) {
+		struct pvr2_hdw *hdw = fh->channel.mc_head->hdw;
+		char *tbuf;
+		int c1,c2;
+		int tcnt = 0;
+		unsigned int offs = *ppos;
+
+		tbuf = kmalloc(PAGE_SIZE,GFP_KERNEL);
+		if (!tbuf) return -ENOMEM;
+
+		while (count) {
+			c1 = count;
+			if (c1 > PAGE_SIZE) c1 = PAGE_SIZE;
+			c2 = pvr2_hdw_cpufw_get(hdw,offs,tbuf,c1);
+			if (c2 < 0) {
+				tcnt = c2;
+				break;
+			}
+			if (!c2) break;
+			if (copy_to_user(buff,tbuf,c2)) {
+				tcnt = -EFAULT;
+				break;
+			}
+			offs += c2;
+			tcnt += c2;
+			buff += c2;
+			count -= c2;
+			*ppos += c2;
+		}
+		kfree(tbuf);
+		return tcnt;
+	}
+
+	if (!fh->rhp) {
+		ret = pvr2_v4l2_iosetup(fh);
+		if (ret) {
+			return ret;
+		}
+	}
+
+	for (;;) {
+		ret = pvr2_ioread_read(fh->rhp,buff,count);
+		if (ret >= 0) break;
+		if (ret != -EAGAIN) break;
+		if (file->f_flags & O_NONBLOCK) break;
+		/* Doing blocking I/O.  Wait here. */
+		ret = wait_event_interruptible(
+			fh->wait_data,
+			pvr2_ioread_avail(fh->rhp) >= 0);
+		if (ret < 0) break;
+	}
+
+	return ret;
+}
+
+
+static __poll_t pvr2_v4l2_poll(struct file *file, poll_table *wait)
+{
+	__poll_t mask = 0;
+	struct pvr2_v4l2_fh *fh = file->private_data;
+	int ret;
+
+	if (fh->fw_mode_flag) {
+		mask |= EPOLLIN | EPOLLRDNORM;
+		return mask;
+	}
+
+	if (!fh->rhp) {
+		ret = pvr2_v4l2_iosetup(fh);
+		if (ret) return EPOLLERR;
+	}
+
+	poll_wait(file,&fh->wait_data,wait);
+
+	if (pvr2_ioread_avail(fh->rhp) >= 0) {
+		mask |= EPOLLIN | EPOLLRDNORM;
+	}
+
+	return mask;
+}
+
+
+static const struct v4l2_file_operations vdev_fops = {
+	.owner      = THIS_MODULE,
+	.open       = pvr2_v4l2_open,
+	.release    = pvr2_v4l2_release,
+	.read       = pvr2_v4l2_read,
+	.unlocked_ioctl = video_ioctl2,
+	.poll       = pvr2_v4l2_poll,
+};
+
+
+static const struct video_device vdev_template = {
+	.fops       = &vdev_fops,
+};
+
+
+static void pvr2_v4l2_dev_init(struct pvr2_v4l2_dev *dip,
+			       struct pvr2_v4l2 *vp,
+			       int v4l_type)
+{
+	int mindevnum;
+	int unit_number;
+	struct pvr2_hdw *hdw;
+	int *nr_ptr = NULL;
+	dip->v4lp = vp;
+
+	hdw = vp->channel.mc_head->hdw;
+	dip->v4l_type = v4l_type;
+	switch (v4l_type) {
+	case VFL_TYPE_GRABBER:
+		dip->stream = &vp->channel.mc_head->video_stream;
+		dip->config = pvr2_config_mpeg;
+		dip->minor_type = pvr2_v4l_type_video;
+		nr_ptr = video_nr;
+		if (!dip->stream) {
+			pr_err(KBUILD_MODNAME
+				": Failed to set up pvrusb2 v4l video dev due to missing stream instance\n");
+			return;
+		}
+		break;
+	case VFL_TYPE_VBI:
+		dip->config = pvr2_config_vbi;
+		dip->minor_type = pvr2_v4l_type_vbi;
+		nr_ptr = vbi_nr;
+		break;
+	case VFL_TYPE_RADIO:
+		dip->stream = &vp->channel.mc_head->video_stream;
+		dip->config = pvr2_config_mpeg;
+		dip->minor_type = pvr2_v4l_type_radio;
+		nr_ptr = radio_nr;
+		break;
+	default:
+		/* Bail out (this should be impossible) */
+		pr_err(KBUILD_MODNAME ": Failed to set up pvrusb2 v4l dev due to unrecognized config\n");
+		return;
+	}
+
+	dip->devbase = vdev_template;
+	dip->devbase.release = pvr2_video_device_release;
+	dip->devbase.ioctl_ops = &pvr2_ioctl_ops;
+	{
+		int val;
+		pvr2_ctrl_get_value(
+			pvr2_hdw_get_ctrl_by_id(hdw,
+						PVR2_CID_STDAVAIL), &val);
+		dip->devbase.tvnorms = (v4l2_std_id)val;
+	}
+
+	mindevnum = -1;
+	unit_number = pvr2_hdw_get_unit_number(hdw);
+	if (nr_ptr && (unit_number >= 0) && (unit_number < PVR_NUM)) {
+		mindevnum = nr_ptr[unit_number];
+	}
+	pvr2_hdw_set_v4l2_dev(hdw, &dip->devbase);
+	if ((video_register_device(&dip->devbase,
+				   dip->v4l_type, mindevnum) < 0) &&
+	    (video_register_device(&dip->devbase,
+				   dip->v4l_type, -1) < 0)) {
+		pr_err(KBUILD_MODNAME
+			": Failed to register pvrusb2 v4l device\n");
+	}
+
+	printk(KERN_INFO "pvrusb2: registered device %s [%s]\n",
+	       video_device_node_name(&dip->devbase),
+	       pvr2_config_get_name(dip->config));
+
+	pvr2_hdw_v4l_store_minor_number(hdw,
+					dip->minor_type,dip->devbase.minor);
+}
+
+
+struct pvr2_v4l2 *pvr2_v4l2_create(struct pvr2_context *mnp)
+{
+	struct pvr2_v4l2 *vp;
+
+	vp = kzalloc(sizeof(*vp),GFP_KERNEL);
+	if (!vp) return vp;
+	pvr2_channel_init(&vp->channel,mnp);
+	pvr2_trace(PVR2_TRACE_STRUCT,"Creating pvr2_v4l2 id=%p",vp);
+
+	vp->channel.check_func = pvr2_v4l2_internal_check;
+
+	/* register streams */
+	vp->dev_video = kzalloc(sizeof(*vp->dev_video),GFP_KERNEL);
+	if (!vp->dev_video) goto fail;
+	pvr2_v4l2_dev_init(vp->dev_video,vp,VFL_TYPE_GRABBER);
+	if (pvr2_hdw_get_input_available(vp->channel.mc_head->hdw) &
+	    (1 << PVR2_CVAL_INPUT_RADIO)) {
+		vp->dev_radio = kzalloc(sizeof(*vp->dev_radio),GFP_KERNEL);
+		if (!vp->dev_radio) goto fail;
+		pvr2_v4l2_dev_init(vp->dev_radio,vp,VFL_TYPE_RADIO);
+	}
+
+	return vp;
+ fail:
+	pvr2_trace(PVR2_TRACE_STRUCT,"Failure creating pvr2_v4l2 id=%p",vp);
+	pvr2_v4l2_destroy_no_lock(vp);
+	return NULL;
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-v4l2.h b/drivers/media/usb/pvrusb2/pvrusb2-v4l2.h
new file mode 100644
index 0000000..ec755ee
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-v4l2.h
@@ -0,0 +1,25 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+#ifndef __PVRUSB2_V4L2_H
+#define __PVRUSB2_V4L2_H
+
+#include "pvrusb2-context.h"
+
+struct pvr2_v4l2;
+
+struct pvr2_v4l2 *pvr2_v4l2_create(struct pvr2_context *);
+
+#endif /* __PVRUSB2_V4L2_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-video-v4l.c b/drivers/media/usb/pvrusb2/pvrusb2-video-v4l.c
new file mode 100644
index 0000000..b68aec2
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-video-v4l.c
@@ -0,0 +1,97 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  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.
+ *
+ */
+
+/*
+
+   This source file is specifically designed to interface with the
+   saa711x support that is available in the v4l available starting
+   with linux 2.6.15.
+
+*/
+
+#include "pvrusb2-video-v4l.h"
+
+
+
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-debug.h"
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <media/i2c/saa7115.h>
+#include <linux/errno.h>
+
+struct routing_scheme {
+	const int *def;
+	unsigned int cnt;
+};
+
+
+static const int routing_scheme0[] = {
+	[PVR2_CVAL_INPUT_TV] = SAA7115_COMPOSITE4,
+	/* In radio mode, we mute the video, but point at one
+	   spot just to stay consistent */
+	[PVR2_CVAL_INPUT_RADIO] = SAA7115_COMPOSITE5,
+	[PVR2_CVAL_INPUT_COMPOSITE] = SAA7115_COMPOSITE5,
+	[PVR2_CVAL_INPUT_SVIDEO] =  SAA7115_SVIDEO2,
+};
+
+static const struct routing_scheme routing_def0 = {
+	.def = routing_scheme0,
+	.cnt = ARRAY_SIZE(routing_scheme0),
+};
+
+static const int routing_scheme1[] = {
+	[PVR2_CVAL_INPUT_TV] = SAA7115_COMPOSITE4,
+	[PVR2_CVAL_INPUT_RADIO] = SAA7115_COMPOSITE5,
+	[PVR2_CVAL_INPUT_COMPOSITE] = SAA7115_COMPOSITE3,
+	[PVR2_CVAL_INPUT_SVIDEO] =  SAA7115_SVIDEO2, /* or SVIDEO0, it seems */
+};
+
+static const struct routing_scheme routing_def1 = {
+	.def = routing_scheme1,
+	.cnt = ARRAY_SIZE(routing_scheme1),
+};
+
+static const struct routing_scheme *routing_schemes[] = {
+	[PVR2_ROUTING_SCHEME_HAUPPAUGE] = &routing_def0,
+	[PVR2_ROUTING_SCHEME_ONAIR] = &routing_def1,
+};
+
+void pvr2_saa7115_subdev_update(struct pvr2_hdw *hdw, struct v4l2_subdev *sd)
+{
+	if (hdw->input_dirty || hdw->force_dirty) {
+		const struct routing_scheme *sp;
+		unsigned int sid = hdw->hdw_desc->signal_routing_scheme;
+		u32 input;
+
+		pvr2_trace(PVR2_TRACE_CHIPS, "subdev v4l2 set_input(%d)",
+			   hdw->input_val);
+
+		sp = (sid < ARRAY_SIZE(routing_schemes)) ?
+			routing_schemes[sid] : NULL;
+		if ((sp == NULL) ||
+		    (hdw->input_val < 0) ||
+		    (hdw->input_val >= sp->cnt)) {
+			pvr2_trace(PVR2_TRACE_ERROR_LEGS,
+				   "*** WARNING *** subdev v4l2 set_input: Invalid routing scheme (%u) and/or input (%d)",
+				   sid, hdw->input_val);
+			return;
+		}
+		input = sp->def[hdw->input_val];
+		sd->ops->video->s_routing(sd, input, 0, 0);
+	}
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-video-v4l.h b/drivers/media/usb/pvrusb2/pvrusb2-video-v4l.h
new file mode 100644
index 0000000..fa33f20
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-video-v4l.h
@@ -0,0 +1,34 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#ifndef __PVRUSB2_VIDEO_V4L_H
+#define __PVRUSB2_VIDEO_V4L_H
+
+/*
+
+   This module connects the pvrusb2 driver to the I2C chip level
+   driver which handles device video processing.  This interface is
+   used internally by the driver; higher level code should only
+   interact through the interface provided by pvrusb2-hdw.h.
+
+*/
+
+
+#include "pvrusb2-hdw-internal.h"
+void pvr2_saa7115_subdev_update(struct pvr2_hdw *, struct v4l2_subdev *);
+
+#endif /* __PVRUSB2_VIDEO_V4L_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-wm8775.c b/drivers/media/usb/pvrusb2/pvrusb2-wm8775.c
new file mode 100644
index 0000000..8f357f7
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-wm8775.c
@@ -0,0 +1,53 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  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.
+ *
+ */
+
+/*
+
+   This source file is specifically designed to interface with the
+   wm8775.
+
+*/
+
+#include "pvrusb2-wm8775.h"
+
+
+#include "pvrusb2-hdw-internal.h"
+#include "pvrusb2-debug.h"
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <linux/errno.h>
+
+void pvr2_wm8775_subdev_update(struct pvr2_hdw *hdw, struct v4l2_subdev *sd)
+{
+	if (hdw->input_dirty || hdw->force_dirty) {
+		u32 input;
+
+		switch (hdw->input_val) {
+		case PVR2_CVAL_INPUT_RADIO:
+			input = 1;
+			break;
+		default:
+			/* All other cases just use the second input */
+			input = 2;
+			break;
+		}
+		pvr2_trace(PVR2_TRACE_CHIPS, "subdev wm8775 set_input(val=%d route=0x%x)",
+			   hdw->input_val, input);
+
+		sd->ops->audio->s_routing(sd, input, 0, 0);
+	}
+}
diff --git a/drivers/media/usb/pvrusb2/pvrusb2-wm8775.h b/drivers/media/usb/pvrusb2/pvrusb2-wm8775.h
new file mode 100644
index 0000000..c4ac7c2
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2-wm8775.h
@@ -0,0 +1,38 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#ifndef __PVRUSB2_WM8775_H
+#define __PVRUSB2_WM8775_H
+
+/*
+
+   This module connects the pvrusb2 driver to the I2C chip level
+   driver which performs analog -> digital audio conversion for
+   external audio inputs.  This interface is used internally by the
+   driver; higher level code should only interact through the
+   interface provided by pvrusb2-hdw.h.
+
+*/
+
+
+
+#include "pvrusb2-hdw-internal.h"
+
+void pvr2_wm8775_subdev_update(struct pvr2_hdw *, struct v4l2_subdev *sd);
+
+
+#endif /* __PVRUSB2_WM8775_H */
diff --git a/drivers/media/usb/pvrusb2/pvrusb2.h b/drivers/media/usb/pvrusb2/pvrusb2.h
new file mode 100644
index 0000000..955290b
--- /dev/null
+++ b/drivers/media/usb/pvrusb2/pvrusb2.h
@@ -0,0 +1,28 @@
+/*
+ *
+ *
+ *  Copyright (C) 2005 Mike Isely <isely@pobox.com>
+ *  Copyright (C) 2004 Aurelien Alleaume <slts@free.fr>
+ *
+ *  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
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#ifndef __PVRUSB2_H
+#define __PVRUSB2_H
+
+/* Maximum number of pvrusb2 instances we can track at once.  You
+   might want to increase this - however the driver operation will not
+   be impaired if it is too small.  Instead additional units just
+   won't have an ID assigned and it might not be possible to specify
+   module parameters for those extra units. */
+#define PVR_NUM 20
+
+#endif /* __PVRUSB2_H */
diff --git a/drivers/media/usb/pwc/Kconfig b/drivers/media/usb/pwc/Kconfig
new file mode 100644
index 0000000..d63d0a8
--- /dev/null
+++ b/drivers/media/usb/pwc/Kconfig
@@ -0,0 +1,48 @@
+config USB_PWC
+	tristate "USB Philips Cameras"
+	depends on VIDEO_V4L2
+	select VIDEOBUF2_VMALLOC
+	---help---
+	  Say Y or M here if you want to use one of these Philips & OEM
+	  webcams:
+	   * Philips PCA645, PCA646
+	   * Philips PCVC675, PCVC680, PCVC690
+	   * Philips PCVC720/40, PCVC730, PCVC740, PCVC750
+	   * Philips SPC900NC
+	   * Askey VC010
+	   * Logitech QuickCam Pro 3000, 4000, 'Zoom', 'Notebook Pro'
+	     and 'Orbit'/'Sphere'
+	   * Samsung MPC-C10, MPC-C30
+	   * Creative Webcam 5, Pro Ex
+	   * SOTEC Afina Eye
+	   * Visionite VCS-UC300, VCS-UM100
+
+	  The PCA635, PCVC665 and PCVC720/20 are not supported by this driver
+	  and never will be, but the 665 and 720/20 are supported by other
+	  drivers.
+
+	  Some newer logitech webcams are not handled by this driver but by the
+	  Usb Video Class driver (linux-uvc).
+
+	  The built-in microphone is enabled by selecting USB Audio support.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called pwc.
+
+config USB_PWC_DEBUG
+	bool "USB Philips Cameras verbose debug"
+	depends on USB_PWC
+	help
+	  Say Y here in order to have the pwc driver generate verbose debugging
+	  messages.
+	  A special module options 'trace' is used to control the verbosity.
+
+config USB_PWC_INPUT_EVDEV
+	bool "USB Philips Cameras input events device support"
+	default y
+	depends on USB_PWC && (USB_PWC=INPUT || INPUT=y)
+	---help---
+	  This option makes USB Philips cameras register the snapshot button as
+	  an input device to report button events.
+
+	  If you are in doubt, say Y.
diff --git a/drivers/media/usb/pwc/Makefile b/drivers/media/usb/pwc/Makefile
new file mode 100644
index 0000000..d7fdbcb
--- /dev/null
+++ b/drivers/media/usb/pwc/Makefile
@@ -0,0 +1,4 @@
+pwc-objs	+= pwc-if.o pwc-misc.o pwc-ctrl.o pwc-v4l.o pwc-uncompress.o
+pwc-objs	+= pwc-dec1.o pwc-dec23.o pwc-kiara.o pwc-timon.o
+
+obj-$(CONFIG_USB_PWC) += pwc.o
diff --git a/drivers/media/usb/pwc/pwc-ctrl.c b/drivers/media/usb/pwc/pwc-ctrl.c
new file mode 100644
index 0000000..655cef3
--- /dev/null
+++ b/drivers/media/usb/pwc/pwc-ctrl.c
@@ -0,0 +1,555 @@
+/* Driver for Philips webcam
+   Functions that send various control messages to the webcam, including
+   video modes.
+   (C) 1999-2003 Nemosoft Unv.
+   (C) 2004-2006 Luc Saillard (luc@saillard.org)
+   (C) 2011 Hans de Goede <hdegoede@redhat.com>
+
+   NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx
+   driver and thus may have bugs that are not present in the original version.
+   Please send bug reports and support requests to <luc@saillard.org>.
+
+   NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx
+   driver and thus may have bugs that are not present in the original version.
+   Please send bug reports and support requests to <luc@saillard.org>.
+   The decompression routines have been implemented by reverse-engineering the
+   Nemosoft binary pwcx module. Caveat emptor.
+
+   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
+*/
+
+/*
+   Changes
+   2001/08/03  Alvarado   Added methods for changing white balance and
+			  red/green gains
+ */
+
+/* Control functions for the cam; brightness, contrast, video mode, etc. */
+
+#ifdef __KERNEL__
+#include <linux/uaccess.h>
+#endif
+#include <asm/errno.h>
+
+#include "pwc.h"
+#include "pwc-kiara.h"
+#include "pwc-timon.h"
+#include "pwc-dec1.h"
+#include "pwc-dec23.h"
+
+/* Selectors for status controls used only in this file */
+#define GET_STATUS_B00				0x0B00
+#define SENSOR_TYPE_FORMATTER1			0x0C00
+#define GET_STATUS_3000				0x3000
+#define READ_RAW_Y_MEAN_FORMATTER		0x3100
+#define SET_POWER_SAVE_MODE_FORMATTER		0x3200
+#define MIRROR_IMAGE_FORMATTER			0x3300
+#define LED_FORMATTER				0x3400
+#define LOWLIGHT				0x3500
+#define GET_STATUS_3600				0x3600
+#define SENSOR_TYPE_FORMATTER2			0x3700
+#define GET_STATUS_3800				0x3800
+#define GET_STATUS_4000				0x4000
+#define GET_STATUS_4100				0x4100	/* Get */
+#define CTL_STATUS_4200				0x4200	/* [GS] 1 */
+
+/* Formatters for the Video Endpoint controls [GS]ET_EP_STREAM_CTL */
+#define VIDEO_OUTPUT_CONTROL_FORMATTER		0x0100
+
+static const char *size2name[PSZ_MAX] =
+{
+	"subQCIF",
+	"QSIF",
+	"QCIF",
+	"SIF",
+	"CIF",
+	"VGA",
+};
+
+/********/
+
+/* Entries for the Nala (645/646) camera; the Nala doesn't have compression
+   preferences, so you either get compressed or non-compressed streams.
+
+   An alternate value of 0 means this mode is not available at all.
+ */
+
+#define PWC_FPS_MAX_NALA 8
+
+struct Nala_table_entry {
+	char alternate;			/* USB alternate setting */
+	int compressed;			/* Compressed yes/no */
+
+	unsigned char mode[3];		/* precomputed mode table */
+};
+
+static unsigned int Nala_fps_vector[PWC_FPS_MAX_NALA] = { 4, 5, 7, 10, 12, 15, 20, 24 };
+
+static struct Nala_table_entry Nala_table[PSZ_MAX][PWC_FPS_MAX_NALA] =
+{
+#include "pwc-nala.h"
+};
+
+/****************************************************************************/
+
+static int recv_control_msg(struct pwc_device *pdev,
+	u8 request, u16 value, int recv_count)
+{
+	int rc;
+
+	rc = usb_control_msg(pdev->udev, usb_rcvctrlpipe(pdev->udev, 0),
+		request,
+		USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+		value, pdev->vcinterface,
+		pdev->ctrl_buf, recv_count, USB_CTRL_GET_TIMEOUT);
+	if (rc < 0)
+		PWC_ERROR("recv_control_msg error %d req %02x val %04x\n",
+			  rc, request, value);
+	return rc;
+}
+
+static inline int send_video_command(struct pwc_device *pdev,
+	int index, const unsigned char *buf, int buflen)
+{
+	int rc;
+
+	memcpy(pdev->ctrl_buf, buf, buflen);
+
+	rc = usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0),
+			SET_EP_STREAM_CTL,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			VIDEO_OUTPUT_CONTROL_FORMATTER, index,
+			pdev->ctrl_buf, buflen, USB_CTRL_SET_TIMEOUT);
+	if (rc >= 0)
+		memcpy(pdev->cmd_buf, buf, buflen);
+	else
+		PWC_ERROR("send_video_command error %d\n", rc);
+
+	return rc;
+}
+
+int send_control_msg(struct pwc_device *pdev,
+	u8 request, u16 value, void *buf, int buflen)
+{
+	return usb_control_msg(pdev->udev, usb_sndctrlpipe(pdev->udev, 0),
+			request,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			value, pdev->vcinterface,
+			buf, buflen, USB_CTRL_SET_TIMEOUT);
+}
+
+static int set_video_mode_Nala(struct pwc_device *pdev, int size, int pixfmt,
+			       int frames, int *compression, int send_to_cam)
+{
+	int fps, ret = 0;
+	struct Nala_table_entry *pEntry;
+	int frames2frames[31] =
+	{ /* closest match of framerate */
+	   0,  0,  0,  0,  4,  /*  0-4  */
+	   5,  5,  7,  7, 10,  /*  5-9  */
+	  10, 10, 12, 12, 15,  /* 10-14 */
+	  15, 15, 15, 20, 20,  /* 15-19 */
+	  20, 20, 20, 24, 24,  /* 20-24 */
+	  24, 24, 24, 24, 24,  /* 25-29 */
+	  24                   /* 30    */
+	};
+	int frames2table[31] =
+	{ 0, 0, 0, 0, 0, /*  0-4  */
+	  1, 1, 1, 2, 2, /*  5-9  */
+	  3, 3, 4, 4, 4, /* 10-14 */
+	  5, 5, 5, 5, 5, /* 15-19 */
+	  6, 6, 6, 6, 7, /* 20-24 */
+	  7, 7, 7, 7, 7, /* 25-29 */
+	  7              /* 30    */
+	};
+
+	if (size < 0 || size > PSZ_CIF)
+		return -EINVAL;
+	if (frames < 4)
+		frames = 4;
+	else if (size > PSZ_QCIF && frames > 15)
+		frames = 15;
+	else if (frames > 25)
+		frames = 25;
+	frames = frames2frames[frames];
+	fps = frames2table[frames];
+	pEntry = &Nala_table[size][fps];
+	if (pEntry->alternate == 0)
+		return -EINVAL;
+
+	if (send_to_cam)
+		ret = send_video_command(pdev, pdev->vendpoint,
+					 pEntry->mode, 3);
+	if (ret < 0)
+		return ret;
+
+	if (pEntry->compressed && pixfmt == V4L2_PIX_FMT_YUV420)
+		pwc_dec1_init(pdev, pEntry->mode);
+
+	/* Set various parameters */
+	pdev->pixfmt = pixfmt;
+	pdev->vframes = frames;
+	pdev->valternate = pEntry->alternate;
+	pdev->width  = pwc_image_sizes[size][0];
+	pdev->height = pwc_image_sizes[size][1];
+	pdev->frame_size = (pdev->width * pdev->height * 3) / 2;
+	if (pEntry->compressed) {
+		if (pdev->release < 5) { /* 4 fold compression */
+			pdev->vbandlength = 528;
+			pdev->frame_size /= 4;
+		}
+		else {
+			pdev->vbandlength = 704;
+			pdev->frame_size /= 3;
+		}
+	}
+	else
+		pdev->vbandlength = 0;
+
+	/* Let pwc-if.c:isoc_init know we don't support higher compression */
+	*compression = 3;
+
+	return 0;
+}
+
+
+static int set_video_mode_Timon(struct pwc_device *pdev, int size, int pixfmt,
+				int frames, int *compression, int send_to_cam)
+{
+	const struct Timon_table_entry *pChoose;
+	int fps, ret = 0;
+
+	if (size >= PSZ_MAX || *compression < 0 || *compression > 3)
+		return -EINVAL;
+	if (frames < 5)
+		frames = 5;
+	else if (size == PSZ_VGA && frames > 15)
+		frames = 15;
+	else if (frames > 30)
+		frames = 30;
+	fps = (frames / 5) - 1;
+
+	/* Find a supported framerate with progressively higher compression */
+	pChoose = NULL;
+	while (*compression <= 3) {
+		pChoose = &Timon_table[size][fps][*compression];
+		if (pChoose->alternate != 0)
+			break;
+		(*compression)++;
+	}
+	if (pChoose == NULL || pChoose->alternate == 0)
+		return -ENOENT; /* Not supported. */
+
+	if (send_to_cam)
+		ret = send_video_command(pdev, pdev->vendpoint,
+					 pChoose->mode, 13);
+	if (ret < 0)
+		return ret;
+
+	if (pChoose->bandlength > 0 && pixfmt == V4L2_PIX_FMT_YUV420)
+		pwc_dec23_init(pdev, pChoose->mode);
+
+	/* Set various parameters */
+	pdev->pixfmt = pixfmt;
+	pdev->vframes = (fps + 1) * 5;
+	pdev->valternate = pChoose->alternate;
+	pdev->width  = pwc_image_sizes[size][0];
+	pdev->height = pwc_image_sizes[size][1];
+	pdev->vbandlength = pChoose->bandlength;
+	if (pChoose->bandlength > 0)
+		pdev->frame_size = (pChoose->bandlength * pdev->height) / 4;
+	else
+		pdev->frame_size = (pdev->width * pdev->height * 12) / 8;
+	return 0;
+}
+
+
+static int set_video_mode_Kiara(struct pwc_device *pdev, int size, int pixfmt,
+				int frames, int *compression, int send_to_cam)
+{
+	const struct Kiara_table_entry *pChoose = NULL;
+	int fps, ret = 0;
+
+	if (size >= PSZ_MAX || *compression < 0 || *compression > 3)
+		return -EINVAL;
+	if (frames < 5)
+		frames = 5;
+	else if (size == PSZ_VGA && frames > 15)
+		frames = 15;
+	else if (frames > 30)
+		frames = 30;
+	fps = (frames / 5) - 1;
+
+	/* Find a supported framerate with progressively higher compression */
+	while (*compression <= 3) {
+		pChoose = &Kiara_table[size][fps][*compression];
+		if (pChoose->alternate != 0)
+			break;
+		(*compression)++;
+	}
+	if (pChoose == NULL || pChoose->alternate == 0)
+		return -ENOENT; /* Not supported. */
+
+	/* Firmware bug: video endpoint is 5, but commands are sent to endpoint 4 */
+	if (send_to_cam)
+		ret = send_video_command(pdev, 4, pChoose->mode, 12);
+	if (ret < 0)
+		return ret;
+
+	if (pChoose->bandlength > 0 && pixfmt == V4L2_PIX_FMT_YUV420)
+		pwc_dec23_init(pdev, pChoose->mode);
+
+	/* All set and go */
+	pdev->pixfmt = pixfmt;
+	pdev->vframes = (fps + 1) * 5;
+	pdev->valternate = pChoose->alternate;
+	pdev->width  = pwc_image_sizes[size][0];
+	pdev->height = pwc_image_sizes[size][1];
+	pdev->vbandlength = pChoose->bandlength;
+	if (pdev->vbandlength > 0)
+		pdev->frame_size = (pdev->vbandlength * pdev->height) / 4;
+	else
+		pdev->frame_size = (pdev->width * pdev->height * 12) / 8;
+	PWC_TRACE("frame_size=%d, vframes=%d, vsize=%d, vbandlength=%d\n",
+	    pdev->frame_size, pdev->vframes, size, pdev->vbandlength);
+	return 0;
+}
+
+int pwc_set_video_mode(struct pwc_device *pdev, int width, int height,
+	int pixfmt, int frames, int *compression, int send_to_cam)
+{
+	int ret, size;
+
+	PWC_DEBUG_FLOW("set_video_mode(%dx%d @ %d, pixfmt %08x).\n",
+		       width, height, frames, pixfmt);
+	size = pwc_get_size(pdev, width, height);
+	PWC_TRACE("decode_size = %d.\n", size);
+
+	if (DEVICE_USE_CODEC1(pdev->type)) {
+		ret = set_video_mode_Nala(pdev, size, pixfmt, frames,
+					  compression, send_to_cam);
+	} else if (DEVICE_USE_CODEC3(pdev->type)) {
+		ret = set_video_mode_Kiara(pdev, size, pixfmt, frames,
+					   compression, send_to_cam);
+	} else {
+		ret = set_video_mode_Timon(pdev, size, pixfmt, frames,
+					   compression, send_to_cam);
+	}
+	if (ret < 0) {
+		PWC_ERROR("Failed to set video mode %s@%d fps; return code = %d\n", size2name[size], frames, ret);
+		return ret;
+	}
+	pdev->frame_total_size = pdev->frame_size + pdev->frame_header_size + pdev->frame_trailer_size;
+	PWC_DEBUG_SIZE("Set resolution to %dx%d\n", pdev->width, pdev->height);
+	return 0;
+}
+
+static unsigned int pwc_get_fps_Nala(struct pwc_device *pdev, unsigned int index, unsigned int size)
+{
+	unsigned int i;
+
+	for (i = 0; i < PWC_FPS_MAX_NALA; i++) {
+		if (Nala_table[size][i].alternate) {
+			if (index--==0) return Nala_fps_vector[i];
+		}
+	}
+	return 0;
+}
+
+static unsigned int pwc_get_fps_Kiara(struct pwc_device *pdev, unsigned int index, unsigned int size)
+{
+	unsigned int i;
+
+	for (i = 0; i < PWC_FPS_MAX_KIARA; i++) {
+		if (Kiara_table[size][i][3].alternate) {
+			if (index--==0) return Kiara_fps_vector[i];
+		}
+	}
+	return 0;
+}
+
+static unsigned int pwc_get_fps_Timon(struct pwc_device *pdev, unsigned int index, unsigned int size)
+{
+	unsigned int i;
+
+	for (i=0; i < PWC_FPS_MAX_TIMON; i++) {
+		if (Timon_table[size][i][3].alternate) {
+			if (index--==0) return Timon_fps_vector[i];
+		}
+	}
+	return 0;
+}
+
+unsigned int pwc_get_fps(struct pwc_device *pdev, unsigned int index, unsigned int size)
+{
+	unsigned int ret;
+
+	if (DEVICE_USE_CODEC1(pdev->type)) {
+		ret = pwc_get_fps_Nala(pdev, index, size);
+
+	} else if (DEVICE_USE_CODEC3(pdev->type)) {
+		ret = pwc_get_fps_Kiara(pdev, index, size);
+
+	} else {
+		ret = pwc_get_fps_Timon(pdev, index, size);
+	}
+
+	return ret;
+}
+
+int pwc_get_u8_ctrl(struct pwc_device *pdev, u8 request, u16 value, int *data)
+{
+	int ret;
+
+	ret = recv_control_msg(pdev, request, value, 1);
+	if (ret < 0)
+		return ret;
+
+	*data = pdev->ctrl_buf[0];
+	return 0;
+}
+
+int pwc_set_u8_ctrl(struct pwc_device *pdev, u8 request, u16 value, u8 data)
+{
+	int ret;
+
+	pdev->ctrl_buf[0] = data;
+	ret = send_control_msg(pdev, request, value, pdev->ctrl_buf, 1);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+int pwc_get_s8_ctrl(struct pwc_device *pdev, u8 request, u16 value, int *data)
+{
+	int ret;
+
+	ret = recv_control_msg(pdev, request, value, 1);
+	if (ret < 0)
+		return ret;
+
+	*data = ((s8 *)pdev->ctrl_buf)[0];
+	return 0;
+}
+
+int pwc_get_u16_ctrl(struct pwc_device *pdev, u8 request, u16 value, int *data)
+{
+	int ret;
+
+	ret = recv_control_msg(pdev, request, value, 2);
+	if (ret < 0)
+		return ret;
+
+	*data = (pdev->ctrl_buf[1] << 8) | pdev->ctrl_buf[0];
+	return 0;
+}
+
+int pwc_set_u16_ctrl(struct pwc_device *pdev, u8 request, u16 value, u16 data)
+{
+	int ret;
+
+	pdev->ctrl_buf[0] = data & 0xff;
+	pdev->ctrl_buf[1] = data >> 8;
+	ret = send_control_msg(pdev, request, value, pdev->ctrl_buf, 2);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+int pwc_button_ctrl(struct pwc_device *pdev, u16 value)
+{
+	int ret;
+
+	ret = send_control_msg(pdev, SET_STATUS_CTL, value, NULL, 0);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+/* POWER */
+void pwc_camera_power(struct pwc_device *pdev, int power)
+{
+	int r;
+
+	if (!pdev->power_save)
+		return;
+
+	if (pdev->type < 675 || (pdev->type < 730 && pdev->release < 6))
+		return;	/* Not supported by Nala or Timon < release 6 */
+
+	if (power)
+		pdev->ctrl_buf[0] = 0x00; /* active */
+	else
+		pdev->ctrl_buf[0] = 0xFF; /* power save */
+	r = send_control_msg(pdev, SET_STATUS_CTL,
+		SET_POWER_SAVE_MODE_FORMATTER, pdev->ctrl_buf, 1);
+	if (r < 0)
+		PWC_ERROR("Failed to power %s camera (%d)\n",
+			  power ? "on" : "off", r);
+}
+
+int pwc_set_leds(struct pwc_device *pdev, int on_value, int off_value)
+{
+	int r;
+
+	if (pdev->type < 730)
+		return 0;
+	on_value /= 100;
+	off_value /= 100;
+	if (on_value < 0)
+		on_value = 0;
+	if (on_value > 0xff)
+		on_value = 0xff;
+	if (off_value < 0)
+		off_value = 0;
+	if (off_value > 0xff)
+		off_value = 0xff;
+
+	pdev->ctrl_buf[0] = on_value;
+	pdev->ctrl_buf[1] = off_value;
+
+	r = send_control_msg(pdev,
+		SET_STATUS_CTL, LED_FORMATTER, pdev->ctrl_buf, 2);
+	if (r < 0)
+		PWC_ERROR("Failed to set LED on/off time (%d)\n", r);
+
+	return r;
+}
+
+#ifdef CONFIG_USB_PWC_DEBUG
+int pwc_get_cmos_sensor(struct pwc_device *pdev, int *sensor)
+{
+	int ret = -1, request;
+
+	if (pdev->type < 675)
+		request = SENSOR_TYPE_FORMATTER1;
+	else if (pdev->type < 730)
+		return -1; /* The Vesta series doesn't have this call */
+	else
+		request = SENSOR_TYPE_FORMATTER2;
+
+	ret = recv_control_msg(pdev, GET_STATUS_CTL, request, 1);
+	if (ret < 0)
+		return ret;
+	if (pdev->type < 675)
+		*sensor = pdev->ctrl_buf[0] | 0x100;
+	else
+		*sensor = pdev->ctrl_buf[0];
+	return 0;
+}
+#endif
diff --git a/drivers/media/usb/pwc/pwc-dec1.c b/drivers/media/usb/pwc/pwc-dec1.c
new file mode 100644
index 0000000..e899036
--- /dev/null
+++ b/drivers/media/usb/pwc/pwc-dec1.c
@@ -0,0 +1,32 @@
+/* Linux driver for Philips webcam
+   Decompression for chipset version 1
+   (C) 2004-2006 Luc Saillard (luc@saillard.org)
+
+   NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx
+   driver and thus may have bugs that are not present in the original version.
+   Please send bug reports and support requests to <luc@saillard.org>.
+   The decompression routines have been implemented by reverse-engineering the
+   Nemosoft binary pwcx module. Caveat emptor.
+
+   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 "pwc.h"
+
+void pwc_dec1_init(struct pwc_device *pdev, const unsigned char *cmd)
+{
+	struct pwc_dec1_private *pdec = &pdev->dec1;
+
+	pdec->version = pdev->release;
+}
diff --git a/drivers/media/usb/pwc/pwc-dec1.h b/drivers/media/usb/pwc/pwc-dec1.h
new file mode 100644
index 0000000..c565ef8
--- /dev/null
+++ b/drivers/media/usb/pwc/pwc-dec1.h
@@ -0,0 +1,39 @@
+/* Linux driver for Philips webcam
+   (C) 2004-2006 Luc Saillard (luc@saillard.org)
+
+   NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx
+   driver and thus may have bugs that are not present in the original version.
+   Please send bug reports and support requests to <luc@saillard.org>.
+   The decompression routines have been implemented by reverse-engineering the
+   Nemosoft binary pwcx module. Caveat emptor.
+
+   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
+*/
+
+#ifndef PWC_DEC1_H
+#define PWC_DEC1_H
+
+#include <linux/mutex.h>
+
+struct pwc_device;
+
+struct pwc_dec1_private
+{
+	int version;
+};
+
+void pwc_dec1_init(struct pwc_device *pdev, const unsigned char *cmd);
+
+#endif
diff --git a/drivers/media/usb/pwc/pwc-dec23.c b/drivers/media/usb/pwc/pwc-dec23.c
new file mode 100644
index 0000000..1283b3b
--- /dev/null
+++ b/drivers/media/usb/pwc/pwc-dec23.c
@@ -0,0 +1,690 @@
+/* Linux driver for Philips webcam
+   Decompression for chipset version 2 et 3
+   (C) 2004-2006  Luc Saillard (luc@saillard.org)
+
+   NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx
+   driver and thus may have bugs that are not present in the original version.
+   Please send bug reports and support requests to <luc@saillard.org>.
+   The decompression routines have been implemented by reverse-engineering the
+   Nemosoft binary pwcx module. Caveat emptor.
+
+   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 "pwc-timon.h"
+#include "pwc-kiara.h"
+#include "pwc-dec23.h"
+
+#include <linux/string.h>
+#include <linux/slab.h>
+
+/*
+ * USE_LOOKUP_TABLE_TO_CLAMP
+ *   0: use a C version of this tests:  {  a<0?0:(a>255?255:a) }
+ *   1: use a faster lookup table for cpu with a big cache (intel)
+ */
+#define USE_LOOKUP_TABLE_TO_CLAMP	1
+/*
+ * UNROLL_LOOP_FOR_COPYING_BLOCK
+ *   0: use a loop for a smaller code (but little slower)
+ *   1: when unrolling the loop, gcc produces some faster code (perhaps only
+ *   valid for intel processor class). Activating this option, automaticaly
+ *   activate USE_LOOKUP_TABLE_TO_CLAMP
+ */
+#define UNROLL_LOOP_FOR_COPY		1
+#if UNROLL_LOOP_FOR_COPY
+# undef USE_LOOKUP_TABLE_TO_CLAMP
+# define USE_LOOKUP_TABLE_TO_CLAMP 1
+#endif
+
+static void build_subblock_pattern(struct pwc_dec23_private *pdec)
+{
+	static const unsigned int initial_values[12] = {
+		-0x526500, -0x221200, 0x221200, 0x526500,
+			   -0x3de200, 0x3de200,
+		-0x6db480, -0x2d5d00, 0x2d5d00, 0x6db480,
+			   -0x12c200, 0x12c200
+
+	};
+	static const unsigned int values_derivated[12] = {
+		0xa4ca, 0x4424, -0x4424, -0xa4ca,
+			0x7bc4, -0x7bc4,
+		0xdb69, 0x5aba, -0x5aba, -0xdb69,
+			0x2584, -0x2584
+	};
+	unsigned int temp_values[12];
+	int i, j;
+
+	memcpy(temp_values, initial_values, sizeof(initial_values));
+	for (i = 0; i < 256; i++) {
+		for (j = 0; j < 12; j++) {
+			pdec->table_subblock[i][j] = temp_values[j];
+			temp_values[j] += values_derivated[j];
+		}
+	}
+}
+
+static void build_bit_powermask_table(struct pwc_dec23_private *pdec)
+{
+	unsigned char *p;
+	unsigned int bit, byte, mask, val;
+	unsigned int bitpower = 1;
+
+	for (bit = 0; bit < 8; bit++) {
+		mask = bitpower - 1;
+		p = pdec->table_bitpowermask[bit];
+		for (byte = 0; byte < 256; byte++) {
+			val = (byte & mask);
+			if (byte & bitpower)
+				val = -val;
+			*p++ = val;
+		}
+		bitpower<<=1;
+	}
+}
+
+
+static void build_table_color(const unsigned int romtable[16][8],
+			      unsigned char p0004[16][1024],
+			      unsigned char p8004[16][256])
+{
+	int compression_mode, j, k, bit, pw;
+	unsigned char *p0, *p8;
+	const unsigned int *r;
+
+	/* We have 16 compressions tables */
+	for (compression_mode = 0; compression_mode < 16; compression_mode++) {
+		p0 = p0004[compression_mode];
+		p8 = p8004[compression_mode];
+		r  = romtable[compression_mode];
+
+		for (j = 0; j < 8; j++, r++, p0 += 128) {
+
+			for (k = 0; k < 16; k++) {
+				if (k == 0)
+					bit = 1;
+				else if (k >= 1 && k < 3)
+					bit = (r[0] >> 15) & 7;
+				else if (k >= 3 && k < 6)
+					bit = (r[0] >> 12) & 7;
+				else if (k >= 6 && k < 10)
+					bit = (r[0] >> 9) & 7;
+				else if (k >= 10 && k < 13)
+					bit = (r[0] >> 6) & 7;
+				else if (k >= 13 && k < 15)
+					bit = (r[0] >> 3) & 7;
+				else
+					bit = (r[0]) & 7;
+				if (k == 0)
+					*p8++ = 8;
+				else
+					*p8++ = j - bit;
+				*p8++ = bit;
+
+				pw = 1 << bit;
+				p0[k + 0x00] = (1 * pw) + 0x80;
+				p0[k + 0x10] = (2 * pw) + 0x80;
+				p0[k + 0x20] = (3 * pw) + 0x80;
+				p0[k + 0x30] = (4 * pw) + 0x80;
+				p0[k + 0x40] = (-1 * pw) + 0x80;
+				p0[k + 0x50] = (-2 * pw) + 0x80;
+				p0[k + 0x60] = (-3 * pw) + 0x80;
+				p0[k + 0x70] = (-4 * pw) + 0x80;
+			}	/* end of for (k=0; k<16; k++, p8++) */
+		}	/* end of for (j=0; j<8; j++ , table++) */
+	} /* end of foreach compression_mode */
+}
+
+/*
+ *
+ */
+static void fill_table_dc00_d800(struct pwc_dec23_private *pdec)
+{
+#define SCALEBITS 15
+#define ONE_HALF  (1UL << (SCALEBITS - 1))
+	int i;
+	unsigned int offset1 = ONE_HALF;
+	unsigned int offset2 = 0x0000;
+
+	for (i=0; i<256; i++) {
+		pdec->table_dc00[i] = offset1 & ~(ONE_HALF);
+		pdec->table_d800[i] = offset2;
+
+		offset1 += 0x7bc4;
+		offset2 += 0x7bc4;
+	}
+}
+
+/*
+ * To decode the stream:
+ *   if look_bits(2) == 0:	# op == 2 in the lookup table
+ *      skip_bits(2)
+ *      end of the stream
+ *   elif look_bits(3) == 7:	# op == 1 in the lookup table
+ *      skip_bits(3)
+ *      yyyy = get_bits(4)
+ *      xxxx = get_bits(8)
+ *   else:			# op == 0 in the lookup table
+ *      skip_bits(x)
+ *
+ * For speedup processing, we build a lookup table and we takes the first 6 bits.
+ *
+ * struct {
+ *   unsigned char op;	    // operation to execute
+ *   unsigned char bits;    // bits use to perform operation
+ *   unsigned char offset1; // offset to add to access in the table_0004 % 16
+ *   unsigned char offset2; // offset to add to access in the table_0004
+ * }
+ *
+ * How to build this table ?
+ *   op == 2 when (i%4)==0
+ *   op == 1 when (i%8)==7
+ *   op == 0 otherwise
+ *
+ */
+static const unsigned char hash_table_ops[64*4] = {
+	0x02, 0x00, 0x00, 0x00,
+	0x00, 0x03, 0x01, 0x00,
+	0x00, 0x04, 0x01, 0x10,
+	0x00, 0x06, 0x01, 0x30,
+	0x02, 0x00, 0x00, 0x00,
+	0x00, 0x03, 0x01, 0x40,
+	0x00, 0x05, 0x01, 0x20,
+	0x01, 0x00, 0x00, 0x00,
+	0x02, 0x00, 0x00, 0x00,
+	0x00, 0x03, 0x01, 0x00,
+	0x00, 0x04, 0x01, 0x50,
+	0x00, 0x05, 0x02, 0x00,
+	0x02, 0x00, 0x00, 0x00,
+	0x00, 0x03, 0x01, 0x40,
+	0x00, 0x05, 0x03, 0x00,
+	0x01, 0x00, 0x00, 0x00,
+	0x02, 0x00, 0x00, 0x00,
+	0x00, 0x03, 0x01, 0x00,
+	0x00, 0x04, 0x01, 0x10,
+	0x00, 0x06, 0x02, 0x10,
+	0x02, 0x00, 0x00, 0x00,
+	0x00, 0x03, 0x01, 0x40,
+	0x00, 0x05, 0x01, 0x60,
+	0x01, 0x00, 0x00, 0x00,
+	0x02, 0x00, 0x00, 0x00,
+	0x00, 0x03, 0x01, 0x00,
+	0x00, 0x04, 0x01, 0x50,
+	0x00, 0x05, 0x02, 0x40,
+	0x02, 0x00, 0x00, 0x00,
+	0x00, 0x03, 0x01, 0x40,
+	0x00, 0x05, 0x03, 0x40,
+	0x01, 0x00, 0x00, 0x00,
+	0x02, 0x00, 0x00, 0x00,
+	0x00, 0x03, 0x01, 0x00,
+	0x00, 0x04, 0x01, 0x10,
+	0x00, 0x06, 0x01, 0x70,
+	0x02, 0x00, 0x00, 0x00,
+	0x00, 0x03, 0x01, 0x40,
+	0x00, 0x05, 0x01, 0x20,
+	0x01, 0x00, 0x00, 0x00,
+	0x02, 0x00, 0x00, 0x00,
+	0x00, 0x03, 0x01, 0x00,
+	0x00, 0x04, 0x01, 0x50,
+	0x00, 0x05, 0x02, 0x00,
+	0x02, 0x00, 0x00, 0x00,
+	0x00, 0x03, 0x01, 0x40,
+	0x00, 0x05, 0x03, 0x00,
+	0x01, 0x00, 0x00, 0x00,
+	0x02, 0x00, 0x00, 0x00,
+	0x00, 0x03, 0x01, 0x00,
+	0x00, 0x04, 0x01, 0x10,
+	0x00, 0x06, 0x02, 0x50,
+	0x02, 0x00, 0x00, 0x00,
+	0x00, 0x03, 0x01, 0x40,
+	0x00, 0x05, 0x01, 0x60,
+	0x01, 0x00, 0x00, 0x00,
+	0x02, 0x00, 0x00, 0x00,
+	0x00, 0x03, 0x01, 0x00,
+	0x00, 0x04, 0x01, 0x50,
+	0x00, 0x05, 0x02, 0x40,
+	0x02, 0x00, 0x00, 0x00,
+	0x00, 0x03, 0x01, 0x40,
+	0x00, 0x05, 0x03, 0x40,
+	0x01, 0x00, 0x00, 0x00
+};
+
+/*
+ *
+ */
+static const unsigned int MulIdx[16][16] = {
+	{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,},
+	{0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3, 0, 1, 2, 3,},
+	{0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3,},
+	{4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 4, 4, 4, 4,},
+	{6, 7, 8, 9, 7, 10, 11, 8, 8, 11, 10, 7, 9, 8, 7, 6,},
+	{4, 5, 5, 4, 4, 5, 5, 4, 4, 5, 5, 4, 4, 5, 5, 4,},
+	{1, 3, 0, 2, 1, 3, 0, 2, 1, 3, 0, 2, 1, 3, 0, 2,},
+	{0, 3, 3, 0, 1, 2, 2, 1, 2, 1, 1, 2, 3, 0, 0, 3,},
+	{0, 1, 2, 3, 3, 2, 1, 0, 3, 2, 1, 0, 0, 1, 2, 3,},
+	{1, 1, 1, 1, 3, 3, 3, 3, 0, 0, 0, 0, 2, 2, 2, 2,},
+	{7, 10, 11, 8, 9, 8, 7, 6, 6, 7, 8, 9, 8, 11, 10, 7,},
+	{4, 5, 5, 4, 5, 4, 4, 5, 5, 4, 4, 5, 4, 5, 5, 4,},
+	{7, 9, 6, 8, 10, 8, 7, 11, 11, 7, 8, 10, 8, 6, 9, 7,},
+	{1, 3, 0, 2, 2, 0, 3, 1, 2, 0, 3, 1, 1, 3, 0, 2,},
+	{1, 2, 2, 1, 3, 0, 0, 3, 0, 3, 3, 0, 2, 1, 1, 2,},
+	{10, 8, 7, 11, 8, 6, 9, 7, 7, 9, 6, 8, 11, 7, 8, 10}
+};
+
+#if USE_LOOKUP_TABLE_TO_CLAMP
+#define MAX_OUTER_CROP_VALUE	(512)
+static unsigned char pwc_crop_table[256 + 2*MAX_OUTER_CROP_VALUE];
+#define CLAMP(x) (pwc_crop_table[MAX_OUTER_CROP_VALUE+(x)])
+#else
+#define CLAMP(x) ((x)>255?255:((x)<0?0:x))
+#endif
+
+
+/* If the type or the command change, we rebuild the lookup table */
+void pwc_dec23_init(struct pwc_device *pdev, const unsigned char *cmd)
+{
+	int flags, version, shift, i;
+	struct pwc_dec23_private *pdec = &pdev->dec23;
+
+	mutex_init(&pdec->lock);
+
+	if (pdec->last_cmd_valid && pdec->last_cmd == cmd[2])
+		return;
+
+	if (DEVICE_USE_CODEC3(pdev->type)) {
+		flags = cmd[2] & 0x18;
+		if (flags == 8)
+			pdec->nbits = 7;	/* More bits, mean more bits to encode the stream, but better quality */
+		else if (flags == 0x10)
+			pdec->nbits = 8;
+		else
+			pdec->nbits = 6;
+
+		version = cmd[2] >> 5;
+		build_table_color(KiaraRomTable[version][0], pdec->table_0004_pass1, pdec->table_8004_pass1);
+		build_table_color(KiaraRomTable[version][1], pdec->table_0004_pass2, pdec->table_8004_pass2);
+
+	} else {
+
+		flags = cmd[2] & 6;
+		if (flags == 2)
+			pdec->nbits = 7;
+		else if (flags == 4)
+			pdec->nbits = 8;
+		else
+			pdec->nbits = 6;
+
+		version = cmd[2] >> 3;
+		build_table_color(TimonRomTable[version][0], pdec->table_0004_pass1, pdec->table_8004_pass1);
+		build_table_color(TimonRomTable[version][1], pdec->table_0004_pass2, pdec->table_8004_pass2);
+	}
+
+	/* Informations can be coded on a variable number of bits but never less than 8 */
+	shift = 8 - pdec->nbits;
+	pdec->scalebits = SCALEBITS - shift;
+	pdec->nbitsmask = 0xFF >> shift;
+
+	fill_table_dc00_d800(pdec);
+	build_subblock_pattern(pdec);
+	build_bit_powermask_table(pdec);
+
+#if USE_LOOKUP_TABLE_TO_CLAMP
+	/* Build the static table to clamp value [0-255] */
+	for (i=0;i<MAX_OUTER_CROP_VALUE;i++)
+		pwc_crop_table[i] = 0;
+	for (i=0; i<256; i++)
+		pwc_crop_table[MAX_OUTER_CROP_VALUE+i] = i;
+	for (i=0; i<MAX_OUTER_CROP_VALUE; i++)
+		pwc_crop_table[MAX_OUTER_CROP_VALUE+256+i] = 255;
+#endif
+
+	pdec->last_cmd = cmd[2];
+	pdec->last_cmd_valid = 1;
+}
+
+/*
+ * Copy the 4x4 image block to Y plane buffer
+ */
+static void copy_image_block_Y(const int *src, unsigned char *dst, unsigned int bytes_per_line, unsigned int scalebits)
+{
+#if UNROLL_LOOP_FOR_COPY
+	const unsigned char *cm = pwc_crop_table+MAX_OUTER_CROP_VALUE;
+	const int *c = src;
+	unsigned char *d = dst;
+
+	*d++ = cm[c[0] >> scalebits];
+	*d++ = cm[c[1] >> scalebits];
+	*d++ = cm[c[2] >> scalebits];
+	*d++ = cm[c[3] >> scalebits];
+
+	d = dst + bytes_per_line;
+	*d++ = cm[c[4] >> scalebits];
+	*d++ = cm[c[5] >> scalebits];
+	*d++ = cm[c[6] >> scalebits];
+	*d++ = cm[c[7] >> scalebits];
+
+	d = dst + bytes_per_line*2;
+	*d++ = cm[c[8] >> scalebits];
+	*d++ = cm[c[9] >> scalebits];
+	*d++ = cm[c[10] >> scalebits];
+	*d++ = cm[c[11] >> scalebits];
+
+	d = dst + bytes_per_line*3;
+	*d++ = cm[c[12] >> scalebits];
+	*d++ = cm[c[13] >> scalebits];
+	*d++ = cm[c[14] >> scalebits];
+	*d++ = cm[c[15] >> scalebits];
+#else
+	int i;
+	const int *c = src;
+	unsigned char *d = dst;
+	for (i = 0; i < 4; i++, c++)
+		*d++ = CLAMP((*c) >> scalebits);
+
+	d = dst + bytes_per_line;
+	for (i = 0; i < 4; i++, c++)
+		*d++ = CLAMP((*c) >> scalebits);
+
+	d = dst + bytes_per_line*2;
+	for (i = 0; i < 4; i++, c++)
+		*d++ = CLAMP((*c) >> scalebits);
+
+	d = dst + bytes_per_line*3;
+	for (i = 0; i < 4; i++, c++)
+		*d++ = CLAMP((*c) >> scalebits);
+#endif
+}
+
+/*
+ * Copy the 4x4 image block to a CrCb plane buffer
+ *
+ */
+static void copy_image_block_CrCb(const int *src, unsigned char *dst, unsigned int bytes_per_line, unsigned int scalebits)
+{
+#if UNROLL_LOOP_FOR_COPY
+	/* Unroll all loops */
+	const unsigned char *cm = pwc_crop_table+MAX_OUTER_CROP_VALUE;
+	const int *c = src;
+	unsigned char *d = dst;
+
+	*d++ = cm[c[0] >> scalebits];
+	*d++ = cm[c[4] >> scalebits];
+	*d++ = cm[c[1] >> scalebits];
+	*d++ = cm[c[5] >> scalebits];
+	*d++ = cm[c[2] >> scalebits];
+	*d++ = cm[c[6] >> scalebits];
+	*d++ = cm[c[3] >> scalebits];
+	*d++ = cm[c[7] >> scalebits];
+
+	d = dst + bytes_per_line;
+	*d++ = cm[c[12] >> scalebits];
+	*d++ = cm[c[8] >> scalebits];
+	*d++ = cm[c[13] >> scalebits];
+	*d++ = cm[c[9] >> scalebits];
+	*d++ = cm[c[14] >> scalebits];
+	*d++ = cm[c[10] >> scalebits];
+	*d++ = cm[c[15] >> scalebits];
+	*d++ = cm[c[11] >> scalebits];
+#else
+	int i;
+	const int *c1 = src;
+	const int *c2 = src + 4;
+	unsigned char *d = dst;
+
+	for (i = 0; i < 4; i++, c1++, c2++) {
+		*d++ = CLAMP((*c1) >> scalebits);
+		*d++ = CLAMP((*c2) >> scalebits);
+	}
+	c1 = src + 12;
+	d = dst + bytes_per_line;
+	for (i = 0; i < 4; i++, c1++, c2++) {
+		*d++ = CLAMP((*c1) >> scalebits);
+		*d++ = CLAMP((*c2) >> scalebits);
+	}
+#endif
+}
+
+/*
+ * To manage the stream, we keep bits in a 32 bits register.
+ * fill_nbits(n): fill the reservoir with at least n bits
+ * skip_bits(n): discard n bits from the reservoir
+ * get_bits(n): fill the reservoir, returns the first n bits and discard the
+ *              bits from the reservoir.
+ * __get_nbits(n): faster version of get_bits(n), but asumes that the reservoir
+ *                 contains at least n bits. bits returned is discarded.
+ */
+#define fill_nbits(pdec, nbits_wanted) do { \
+   while (pdec->nbits_in_reservoir<(nbits_wanted)) \
+    { \
+      pdec->reservoir |= (*(pdec->stream)++) << (pdec->nbits_in_reservoir); \
+      pdec->nbits_in_reservoir += 8; \
+    } \
+}  while(0);
+
+#define skip_nbits(pdec, nbits_to_skip) do { \
+   pdec->reservoir >>= (nbits_to_skip); \
+   pdec->nbits_in_reservoir -= (nbits_to_skip); \
+}  while(0);
+
+#define get_nbits(pdec, nbits_wanted, result) do { \
+   fill_nbits(pdec, nbits_wanted); \
+   result = (pdec->reservoir) & ((1U<<(nbits_wanted))-1); \
+   skip_nbits(pdec, nbits_wanted); \
+}  while(0);
+
+#define __get_nbits(pdec, nbits_wanted, result) do { \
+   result = (pdec->reservoir) & ((1U<<(nbits_wanted))-1); \
+   skip_nbits(pdec, nbits_wanted); \
+}  while(0);
+
+#define look_nbits(pdec, nbits_wanted) \
+   ((pdec->reservoir) & ((1U<<(nbits_wanted))-1))
+
+/*
+ * Decode a 4x4 pixel block
+ */
+static void decode_block(struct pwc_dec23_private *pdec,
+			 const unsigned char *ptable0004,
+			 const unsigned char *ptable8004)
+{
+	unsigned int primary_color;
+	unsigned int channel_v, offset1, op;
+	int i;
+
+	fill_nbits(pdec, 16);
+	__get_nbits(pdec, pdec->nbits, primary_color);
+
+	if (look_nbits(pdec,2) == 0) {
+		skip_nbits(pdec, 2);
+		/* Very simple, the color is the same for all pixels of the square */
+		for (i = 0; i < 16; i++)
+			pdec->temp_colors[i] = pdec->table_dc00[primary_color];
+
+		return;
+	}
+
+	/* This block is encoded with small pattern */
+	for (i = 0; i < 16; i++)
+		pdec->temp_colors[i] = pdec->table_d800[primary_color];
+
+	__get_nbits(pdec, 3, channel_v);
+	channel_v = ((channel_v & 1) << 2) | (channel_v & 2) | ((channel_v & 4) >> 2);
+
+	ptable0004 += (channel_v * 128);
+	ptable8004 += (channel_v * 32);
+
+	offset1 = 0;
+	do
+	{
+		unsigned int htable_idx, rows = 0;
+		const unsigned int *block;
+
+		/* [  zzzz y x x ]
+		 *     xx == 00 :=> end of the block def, remove the two bits from the stream
+		 *    yxx == 111
+		 *    yxx == any other value
+		 *
+		 */
+		fill_nbits(pdec, 16);
+		htable_idx = look_nbits(pdec, 6);
+		op = hash_table_ops[htable_idx * 4];
+
+		if (op == 2) {
+			skip_nbits(pdec, 2);
+
+		} else if (op == 1) {
+			/* 15bits [ xxxx xxxx yyyy 111 ]
+			 * yyy => offset in the table8004
+			 * xxx => offset in the tabled004 (tree)
+			 */
+			unsigned int mask, shift;
+			unsigned int nbits, col1;
+			unsigned int yyyy;
+
+			skip_nbits(pdec, 3);
+			/* offset1 += yyyy */
+			__get_nbits(pdec, 4, yyyy);
+			offset1 += 1 + yyyy;
+			offset1 &= 0x0F;
+			nbits = ptable8004[offset1 * 2];
+
+			/* col1 = xxxx xxxx */
+			__get_nbits(pdec, nbits+1, col1);
+
+			/* Bit mask table */
+			mask = pdec->table_bitpowermask[nbits][col1];
+			shift = ptable8004[offset1 * 2 + 1];
+			rows = ((mask << shift) + 0x80) & 0xFF;
+
+			block = pdec->table_subblock[rows];
+			for (i = 0; i < 16; i++)
+				pdec->temp_colors[i] += block[MulIdx[offset1][i]];
+
+		} else {
+			/* op == 0
+			 * offset1 is coded on 3 bits
+			 */
+			unsigned int shift;
+
+			offset1 += hash_table_ops [htable_idx * 4 + 2];
+			offset1 &= 0x0F;
+
+			rows = ptable0004[offset1 + hash_table_ops [htable_idx * 4 + 3]];
+			block = pdec->table_subblock[rows];
+			for (i = 0; i < 16; i++)
+				pdec->temp_colors[i] += block[MulIdx[offset1][i]];
+
+			shift = hash_table_ops[htable_idx * 4 + 1];
+			skip_nbits(pdec, shift);
+		}
+
+	} while (op != 2);
+
+}
+
+static void DecompressBand23(struct pwc_dec23_private *pdec,
+			     const unsigned char *rawyuv,
+			     unsigned char *planar_y,
+			     unsigned char *planar_u,
+			     unsigned char *planar_v,
+			     unsigned int   compressed_image_width,
+			     unsigned int   real_image_width)
+{
+	int compression_index, nblocks;
+	const unsigned char *ptable0004;
+	const unsigned char *ptable8004;
+
+	pdec->reservoir = 0;
+	pdec->nbits_in_reservoir = 0;
+	pdec->stream = rawyuv + 1;	/* The first byte of the stream is skipped */
+
+	get_nbits(pdec, 4, compression_index);
+
+	/* pass 1: uncompress Y component */
+	nblocks = compressed_image_width / 4;
+
+	ptable0004 = pdec->table_0004_pass1[compression_index];
+	ptable8004 = pdec->table_8004_pass1[compression_index];
+
+	/* Each block decode a square of 4x4 */
+	while (nblocks) {
+		decode_block(pdec, ptable0004, ptable8004);
+		copy_image_block_Y(pdec->temp_colors, planar_y, real_image_width, pdec->scalebits);
+		planar_y += 4;
+		nblocks--;
+	}
+
+	/* pass 2: uncompress UV component */
+	nblocks = compressed_image_width / 8;
+
+	ptable0004 = pdec->table_0004_pass2[compression_index];
+	ptable8004 = pdec->table_8004_pass2[compression_index];
+
+	/* Each block decode a square of 4x4 */
+	while (nblocks) {
+		decode_block(pdec, ptable0004, ptable8004);
+		copy_image_block_CrCb(pdec->temp_colors, planar_u, real_image_width/2, pdec->scalebits);
+
+		decode_block(pdec, ptable0004, ptable8004);
+		copy_image_block_CrCb(pdec->temp_colors, planar_v, real_image_width/2, pdec->scalebits);
+
+		planar_v += 8;
+		planar_u += 8;
+		nblocks -= 2;
+	}
+
+}
+
+/**
+ * Uncompress a pwc23 buffer.
+ * @pdev: pointer to pwc device's internal struct
+ * @src: raw data
+ * @dst: image output
+ */
+void pwc_dec23_decompress(struct pwc_device *pdev,
+			  const void *src,
+			  void *dst)
+{
+	int bandlines_left, bytes_per_block;
+	struct pwc_dec23_private *pdec = &pdev->dec23;
+
+	/* YUV420P image format */
+	unsigned char *pout_planar_y;
+	unsigned char *pout_planar_u;
+	unsigned char *pout_planar_v;
+	unsigned int   plane_size;
+
+	mutex_lock(&pdec->lock);
+
+	bandlines_left = pdev->height / 4;
+	bytes_per_block = pdev->width * 4;
+	plane_size = pdev->height * pdev->width;
+
+	pout_planar_y = dst;
+	pout_planar_u = dst + plane_size;
+	pout_planar_v = dst + plane_size + plane_size / 4;
+
+	while (bandlines_left--) {
+		DecompressBand23(pdec, src,
+				 pout_planar_y, pout_planar_u, pout_planar_v,
+				 pdev->width, pdev->width);
+		src += pdev->vbandlength;
+		pout_planar_y += bytes_per_block;
+		pout_planar_u += pdev->width;
+		pout_planar_v += pdev->width;
+	}
+	mutex_unlock(&pdec->lock);
+}
diff --git a/drivers/media/usb/pwc/pwc-dec23.h b/drivers/media/usb/pwc/pwc-dec23.h
new file mode 100644
index 0000000..c655b1c
--- /dev/null
+++ b/drivers/media/usb/pwc/pwc-dec23.h
@@ -0,0 +1,61 @@
+/* Linux driver for Philips webcam
+   (C) 2004-2006 Luc Saillard (luc@saillard.org)
+
+   NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx
+   driver and thus may have bugs that are not present in the original version.
+   Please send bug reports and support requests to <luc@saillard.org>.
+   The decompression routines have been implemented by reverse-engineering the
+   Nemosoft binary pwcx module. Caveat emptor.
+
+   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
+*/
+
+#ifndef PWC_DEC23_H
+#define PWC_DEC23_H
+
+struct pwc_device;
+
+struct pwc_dec23_private
+{
+	struct mutex lock;
+
+	unsigned char last_cmd, last_cmd_valid;
+
+  unsigned int scalebits;
+  unsigned int nbitsmask, nbits; /* Number of bits of a color in the compressed stream */
+
+  unsigned int reservoir;
+  unsigned int nbits_in_reservoir;
+
+  const unsigned char *stream;
+  int temp_colors[16];
+
+  unsigned char table_0004_pass1[16][1024];
+  unsigned char table_0004_pass2[16][1024];
+  unsigned char table_8004_pass1[16][256];
+  unsigned char table_8004_pass2[16][256];
+  unsigned int  table_subblock[256][12];
+
+  unsigned char table_bitpowermask[8][256];
+  unsigned int  table_d800[256];
+  unsigned int  table_dc00[256];
+
+};
+
+void pwc_dec23_init(struct pwc_device *pdev, const unsigned char *cmd);
+void pwc_dec23_decompress(struct pwc_device *pdev,
+			  const void *src,
+			  void *dst);
+#endif
diff --git a/drivers/media/usb/pwc/pwc-if.c b/drivers/media/usb/pwc/pwc-if.c
new file mode 100644
index 0000000..54b036d
--- /dev/null
+++ b/drivers/media/usb/pwc/pwc-if.c
@@ -0,0 +1,1186 @@
+/* Linux driver for Philips webcam
+   USB and Video4Linux interface part.
+   (C) 1999-2004 Nemosoft Unv.
+   (C) 2004-2006 Luc Saillard (luc@saillard.org)
+   (C) 2011 Hans de Goede <hdegoede@redhat.com>
+
+   NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx
+   driver and thus may have bugs that are not present in the original version.
+   Please send bug reports and support requests to <luc@saillard.org>.
+   The decompression routines have been implemented by reverse-engineering the
+   Nemosoft binary pwcx module. Caveat emptor.
+
+   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
+
+*/
+
+/*
+   This code forms the interface between the USB layers and the Philips
+   specific stuff. Some adanved stuff of the driver falls under an
+   NDA, signed between me and Philips B.V., Eindhoven, the Netherlands, and
+   is thus not distributed in source form. The binary pwcx.o module
+   contains the code that falls under the NDA.
+
+   In case you're wondering: 'pwc' stands for "Philips WebCam", but
+   I really didn't want to type 'philips_web_cam' every time (I'm lazy as
+   any Linux kernel hacker, but I don't like uncomprehensible abbreviations
+   without explanation).
+
+   Oh yes, convention: to disctinguish between all the various pointers to
+   device-structures, I use these names for the pointer variables:
+   udev: struct usb_device *
+   vdev: struct video_device (member of pwc_dev)
+   pdev: struct pwc_devive *
+*/
+
+/* Contributors:
+   - Alvarado: adding whitebalance code
+   - Alistar Moire: QuickCam 3000 Pro device/product ID
+   - Tony Hoyle: Creative Labs Webcam 5 device/product ID
+   - Mark Burazin: solving hang in VIDIOCSYNC when camera gets unplugged
+   - Jk Fang: Sotec Afina Eye ID
+   - Xavier Roche: QuickCam Pro 4000 ID
+   - Jens Knudsen: QuickCam Zoom ID
+   - J. Debert: QuickCam for Notebooks ID
+   - Pham Thanh Nam: webcam snapshot button as an event input device
+*/
+
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/poll.h>
+#include <linux/slab.h>
+#ifdef CONFIG_USB_PWC_INPUT_EVDEV
+#include <linux/usb/input.h>
+#endif
+#include <linux/vmalloc.h>
+#include <asm/io.h>
+#include <linux/kernel.h>		/* simple_strtol() */
+
+#include "pwc.h"
+#include "pwc-kiara.h"
+#include "pwc-timon.h"
+#include "pwc-dec23.h"
+#include "pwc-dec1.h"
+
+/* Function prototypes and driver templates */
+
+/* hotplug device table support */
+static const struct usb_device_id pwc_device_table [] = {
+	{ USB_DEVICE(0x0471, 0x0302) }, /* Philips models */
+	{ USB_DEVICE(0x0471, 0x0303) },
+	{ USB_DEVICE(0x0471, 0x0304) },
+	{ USB_DEVICE(0x0471, 0x0307) },
+	{ USB_DEVICE(0x0471, 0x0308) },
+	{ USB_DEVICE(0x0471, 0x030C) },
+	{ USB_DEVICE(0x0471, 0x0310) },
+	{ USB_DEVICE(0x0471, 0x0311) }, /* Philips ToUcam PRO II */
+	{ USB_DEVICE(0x0471, 0x0312) },
+	{ USB_DEVICE(0x0471, 0x0313) }, /* the 'new' 720K */
+	{ USB_DEVICE(0x0471, 0x0329) }, /* Philips SPC 900NC PC Camera */
+	{ USB_DEVICE(0x0471, 0x032C) }, /* Philips SPC 880NC PC Camera */
+	{ USB_DEVICE(0x069A, 0x0001) }, /* Askey */
+	{ USB_DEVICE(0x046D, 0x08B0) }, /* Logitech QuickCam Pro 3000 */
+	{ USB_DEVICE(0x046D, 0x08B1) }, /* Logitech QuickCam Notebook Pro */
+	{ USB_DEVICE(0x046D, 0x08B2) }, /* Logitech QuickCam Pro 4000 */
+	{ USB_DEVICE(0x046D, 0x08B3) }, /* Logitech QuickCam Zoom (old model) */
+	{ USB_DEVICE(0x046D, 0x08B4) }, /* Logitech QuickCam Zoom (new model) */
+	{ USB_DEVICE(0x046D, 0x08B5) }, /* Logitech QuickCam Orbit/Sphere */
+	{ USB_DEVICE(0x046D, 0x08B6) }, /* Cisco VT Camera */
+	{ USB_DEVICE(0x046D, 0x08B7) }, /* Logitech ViewPort AV 100 */
+	{ USB_DEVICE(0x046D, 0x08B8) }, /* Logitech (reserved) */
+	{ USB_DEVICE(0x055D, 0x9000) }, /* Samsung MPC-C10 */
+	{ USB_DEVICE(0x055D, 0x9001) }, /* Samsung MPC-C30 */
+	{ USB_DEVICE(0x055D, 0x9002) },	/* Samsung SNC-35E (Ver3.0) */
+	{ USB_DEVICE(0x041E, 0x400C) }, /* Creative Webcam 5 */
+	{ USB_DEVICE(0x041E, 0x4011) }, /* Creative Webcam Pro Ex */
+	{ USB_DEVICE(0x04CC, 0x8116) }, /* Afina Eye */
+	{ USB_DEVICE(0x06BE, 0x8116) }, /* new Afina Eye */
+	{ USB_DEVICE(0x0d81, 0x1910) }, /* Visionite */
+	{ USB_DEVICE(0x0d81, 0x1900) },
+	{ }
+};
+MODULE_DEVICE_TABLE(usb, pwc_device_table);
+
+static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id *id);
+static void usb_pwc_disconnect(struct usb_interface *intf);
+static void pwc_isoc_cleanup(struct pwc_device *pdev);
+
+static struct usb_driver pwc_driver = {
+	.name =			"Philips webcam",	/* name */
+	.id_table =		pwc_device_table,
+	.probe =		usb_pwc_probe,		/* probe() */
+	.disconnect =		usb_pwc_disconnect,	/* disconnect() */
+};
+
+#define MAX_DEV_HINTS	20
+#define MAX_ISOC_ERRORS	20
+
+#ifdef CONFIG_USB_PWC_DEBUG
+	int pwc_trace = PWC_DEBUG_LEVEL;
+#endif
+static int power_save = -1;
+static int leds[2] = { 100, 0 };
+
+/***/
+
+static const struct v4l2_file_operations pwc_fops = {
+	.owner =	THIS_MODULE,
+	.open =		v4l2_fh_open,
+	.release =	vb2_fop_release,
+	.read =		vb2_fop_read,
+	.poll =		vb2_fop_poll,
+	.mmap =		vb2_fop_mmap,
+	.unlocked_ioctl = video_ioctl2,
+};
+static const struct video_device pwc_template = {
+	.name =		"Philips Webcam",	/* Filled in later */
+	.release =	video_device_release_empty,
+	.fops =         &pwc_fops,
+	.ioctl_ops =	&pwc_ioctl_ops,
+};
+
+/***************************************************************************/
+/* Private functions */
+
+static struct pwc_frame_buf *pwc_get_next_fill_buf(struct pwc_device *pdev)
+{
+	unsigned long flags = 0;
+	struct pwc_frame_buf *buf = NULL;
+
+	spin_lock_irqsave(&pdev->queued_bufs_lock, flags);
+	if (list_empty(&pdev->queued_bufs))
+		goto leave;
+
+	buf = list_entry(pdev->queued_bufs.next, struct pwc_frame_buf, list);
+	list_del(&buf->list);
+leave:
+	spin_unlock_irqrestore(&pdev->queued_bufs_lock, flags);
+	return buf;
+}
+
+static void pwc_snapshot_button(struct pwc_device *pdev, int down)
+{
+	if (down) {
+		PWC_TRACE("Snapshot button pressed.\n");
+	} else {
+		PWC_TRACE("Snapshot button released.\n");
+	}
+
+#ifdef CONFIG_USB_PWC_INPUT_EVDEV
+	if (pdev->button_dev) {
+		input_report_key(pdev->button_dev, KEY_CAMERA, down);
+		input_sync(pdev->button_dev);
+	}
+#endif
+}
+
+static void pwc_frame_complete(struct pwc_device *pdev)
+{
+	struct pwc_frame_buf *fbuf = pdev->fill_buf;
+
+	/* The ToUCam Fun CMOS sensor causes the firmware to send 2 or 3 bogus
+	   frames on the USB wire after an exposure change. This conditition is
+	   however detected  in the cam and a bit is set in the header.
+	   */
+	if (pdev->type == 730) {
+		unsigned char *ptr = (unsigned char *)fbuf->data;
+
+		if (ptr[1] == 1 && ptr[0] & 0x10) {
+			PWC_TRACE("Hyundai CMOS sensor bug. Dropping frame.\n");
+			pdev->drop_frames += 2;
+		}
+		if ((ptr[0] ^ pdev->vmirror) & 0x01) {
+			pwc_snapshot_button(pdev, ptr[0] & 0x01);
+		}
+		if ((ptr[0] ^ pdev->vmirror) & 0x02) {
+			if (ptr[0] & 0x02)
+				PWC_TRACE("Image is mirrored.\n");
+			else
+				PWC_TRACE("Image is normal.\n");
+		}
+		pdev->vmirror = ptr[0] & 0x03;
+		/* Sometimes the trailer of the 730 is still sent as a 4 byte packet
+		   after a short frame; this condition is filtered out specifically. A 4 byte
+		   frame doesn't make sense anyway.
+		   So we get either this sequence:
+		   drop_bit set -> 4 byte frame -> short frame -> good frame
+		   Or this one:
+		   drop_bit set -> short frame -> good frame
+		   So we drop either 3 or 2 frames in all!
+		   */
+		if (fbuf->filled == 4)
+			pdev->drop_frames++;
+	} else if (pdev->type == 740 || pdev->type == 720) {
+		unsigned char *ptr = (unsigned char *)fbuf->data;
+		if ((ptr[0] ^ pdev->vmirror) & 0x01) {
+			pwc_snapshot_button(pdev, ptr[0] & 0x01);
+		}
+		pdev->vmirror = ptr[0] & 0x03;
+	}
+
+	/* In case we were instructed to drop the frame, do so silently. */
+	if (pdev->drop_frames > 0) {
+		pdev->drop_frames--;
+	} else {
+		/* Check for underflow first */
+		if (fbuf->filled < pdev->frame_total_size) {
+			PWC_DEBUG_FLOW("Frame buffer underflow (%d bytes); discarded.\n",
+				       fbuf->filled);
+		} else {
+			fbuf->vb.field = V4L2_FIELD_NONE;
+			fbuf->vb.sequence = pdev->vframe_count;
+			vb2_buffer_done(&fbuf->vb.vb2_buf, VB2_BUF_STATE_DONE);
+			pdev->fill_buf = NULL;
+			pdev->vsync = 0;
+		}
+	} /* !drop_frames */
+	pdev->vframe_count++;
+}
+
+/* This gets called for the Isochronous pipe (video). This is done in
+ * interrupt time, so it has to be fast, not crash, and not stall. Neat.
+ */
+static void pwc_isoc_handler(struct urb *urb)
+{
+	struct pwc_device *pdev = (struct pwc_device *)urb->context;
+	int i, fst, flen;
+	unsigned char *iso_buf = NULL;
+
+	if (urb->status == -ENOENT || urb->status == -ECONNRESET ||
+	    urb->status == -ESHUTDOWN) {
+		PWC_DEBUG_OPEN("URB (%p) unlinked %ssynchronously.\n",
+			       urb, urb->status == -ENOENT ? "" : "a");
+		return;
+	}
+
+	if (pdev->fill_buf == NULL)
+		pdev->fill_buf = pwc_get_next_fill_buf(pdev);
+
+	if (urb->status != 0) {
+		const char *errmsg;
+
+		errmsg = "Unknown";
+		switch(urb->status) {
+			case -ENOSR:		errmsg = "Buffer error (overrun)"; break;
+			case -EPIPE:		errmsg = "Stalled (device not responding)"; break;
+			case -EOVERFLOW:	errmsg = "Babble (bad cable?)"; break;
+			case -EPROTO:		errmsg = "Bit-stuff error (bad cable?)"; break;
+			case -EILSEQ:		errmsg = "CRC/Timeout (could be anything)"; break;
+			case -ETIME:		errmsg = "Device does not respond"; break;
+		}
+		PWC_ERROR("pwc_isoc_handler() called with status %d [%s].\n",
+			  urb->status, errmsg);
+		/* Give up after a number of contiguous errors */
+		if (++pdev->visoc_errors > MAX_ISOC_ERRORS)
+		{
+			PWC_ERROR("Too many ISOC errors, bailing out.\n");
+			if (pdev->fill_buf) {
+				vb2_buffer_done(&pdev->fill_buf->vb.vb2_buf,
+						VB2_BUF_STATE_ERROR);
+				pdev->fill_buf = NULL;
+			}
+		}
+		pdev->vsync = 0; /* Drop the current frame */
+		goto handler_end;
+	}
+
+	/* Reset ISOC error counter. We did get here, after all. */
+	pdev->visoc_errors = 0;
+
+	/* vsync: 0 = don't copy data
+		  1 = sync-hunt
+		  2 = synched
+	 */
+	/* Compact data */
+	for (i = 0; i < urb->number_of_packets; i++) {
+		fst  = urb->iso_frame_desc[i].status;
+		flen = urb->iso_frame_desc[i].actual_length;
+		iso_buf = urb->transfer_buffer + urb->iso_frame_desc[i].offset;
+		if (fst != 0) {
+			PWC_ERROR("Iso frame %d has error %d\n", i, fst);
+			continue;
+		}
+		if (flen > 0 && pdev->vsync) {
+			struct pwc_frame_buf *fbuf = pdev->fill_buf;
+
+			if (pdev->vsync == 1) {
+				fbuf->vb.vb2_buf.timestamp = ktime_get_ns();
+				pdev->vsync = 2;
+			}
+
+			if (flen + fbuf->filled > pdev->frame_total_size) {
+				PWC_ERROR("Frame overflow (%d > %d)\n",
+					  flen + fbuf->filled,
+					  pdev->frame_total_size);
+				pdev->vsync = 0; /* Let's wait for an EOF */
+			} else {
+				memcpy(fbuf->data + fbuf->filled, iso_buf,
+				       flen);
+				fbuf->filled += flen;
+			}
+		}
+		if (flen < pdev->vlast_packet_size) {
+			/* Shorter packet... end of frame */
+			if (pdev->vsync == 2)
+				pwc_frame_complete(pdev);
+			if (pdev->fill_buf == NULL)
+				pdev->fill_buf = pwc_get_next_fill_buf(pdev);
+			if (pdev->fill_buf) {
+				pdev->fill_buf->filled = 0;
+				pdev->vsync = 1;
+			}
+		}
+		pdev->vlast_packet_size = flen;
+	}
+
+handler_end:
+	i = usb_submit_urb(urb, GFP_ATOMIC);
+	if (i != 0)
+		PWC_ERROR("Error (%d) re-submitting urb in pwc_isoc_handler.\n", i);
+}
+
+/* Both v4l2_lock and vb_queue_lock should be locked when calling this */
+static int pwc_isoc_init(struct pwc_device *pdev)
+{
+	struct usb_device *udev;
+	struct urb *urb;
+	int i, j, ret;
+	struct usb_interface *intf;
+	struct usb_host_interface *idesc = NULL;
+	int compression = 0; /* 0..3 = uncompressed..high */
+
+	pdev->vsync = 0;
+	pdev->vlast_packet_size = 0;
+	pdev->fill_buf = NULL;
+	pdev->vframe_count = 0;
+	pdev->visoc_errors = 0;
+	udev = pdev->udev;
+
+retry:
+	/* We first try with low compression and then retry with a higher
+	   compression setting if there is not enough bandwidth. */
+	ret = pwc_set_video_mode(pdev, pdev->width, pdev->height, pdev->pixfmt,
+				 pdev->vframes, &compression, 1);
+
+	/* Get the current alternate interface, adjust packet size */
+	intf = usb_ifnum_to_if(udev, 0);
+	if (intf)
+		idesc = usb_altnum_to_altsetting(intf, pdev->valternate);
+	if (!idesc)
+		return -EIO;
+
+	/* Search video endpoint */
+	pdev->vmax_packet_size = -1;
+	for (i = 0; i < idesc->desc.bNumEndpoints; i++) {
+		if ((idesc->endpoint[i].desc.bEndpointAddress & 0xF) == pdev->vendpoint) {
+			pdev->vmax_packet_size = le16_to_cpu(idesc->endpoint[i].desc.wMaxPacketSize);
+			break;
+		}
+	}
+
+	if (pdev->vmax_packet_size < 0 || pdev->vmax_packet_size > ISO_MAX_FRAME_SIZE) {
+		PWC_ERROR("Failed to find packet size for video endpoint in current alternate setting.\n");
+		return -ENFILE; /* Odd error, that should be noticeable */
+	}
+
+	/* Set alternate interface */
+	PWC_DEBUG_OPEN("Setting alternate interface %d\n", pdev->valternate);
+	ret = usb_set_interface(pdev->udev, 0, pdev->valternate);
+	if (ret == -ENOSPC && compression < 3) {
+		compression++;
+		goto retry;
+	}
+	if (ret < 0)
+		return ret;
+
+	/* Allocate and init Isochronuous urbs */
+	for (i = 0; i < MAX_ISO_BUFS; i++) {
+		urb = usb_alloc_urb(ISO_FRAMES_PER_DESC, GFP_KERNEL);
+		if (urb == NULL) {
+			pwc_isoc_cleanup(pdev);
+			return -ENOMEM;
+		}
+		pdev->urbs[i] = urb;
+		PWC_DEBUG_MEMORY("Allocated URB at 0x%p\n", urb);
+
+		urb->interval = 1; // devik
+		urb->dev = udev;
+		urb->pipe = usb_rcvisocpipe(udev, pdev->vendpoint);
+		urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
+		urb->transfer_buffer = usb_alloc_coherent(udev,
+							  ISO_BUFFER_SIZE,
+							  GFP_KERNEL,
+							  &urb->transfer_dma);
+		if (urb->transfer_buffer == NULL) {
+			PWC_ERROR("Failed to allocate urb buffer %d\n", i);
+			pwc_isoc_cleanup(pdev);
+			return -ENOMEM;
+		}
+		urb->transfer_buffer_length = ISO_BUFFER_SIZE;
+		urb->complete = pwc_isoc_handler;
+		urb->context = pdev;
+		urb->start_frame = 0;
+		urb->number_of_packets = ISO_FRAMES_PER_DESC;
+		for (j = 0; j < ISO_FRAMES_PER_DESC; j++) {
+			urb->iso_frame_desc[j].offset = j * ISO_MAX_FRAME_SIZE;
+			urb->iso_frame_desc[j].length = pdev->vmax_packet_size;
+		}
+	}
+
+	/* link */
+	for (i = 0; i < MAX_ISO_BUFS; i++) {
+		ret = usb_submit_urb(pdev->urbs[i], GFP_KERNEL);
+		if (ret == -ENOSPC && compression < 3) {
+			compression++;
+			pwc_isoc_cleanup(pdev);
+			goto retry;
+		}
+		if (ret) {
+			PWC_ERROR("isoc_init() submit_urb %d failed with error %d\n", i, ret);
+			pwc_isoc_cleanup(pdev);
+			return ret;
+		}
+		PWC_DEBUG_MEMORY("URB 0x%p submitted.\n", pdev->urbs[i]);
+	}
+
+	/* All is done... */
+	PWC_DEBUG_OPEN("<< pwc_isoc_init()\n");
+	return 0;
+}
+
+static void pwc_iso_stop(struct pwc_device *pdev)
+{
+	int i;
+
+	/* Unlinking ISOC buffers one by one */
+	for (i = 0; i < MAX_ISO_BUFS; i++) {
+		if (pdev->urbs[i]) {
+			PWC_DEBUG_MEMORY("Unlinking URB %p\n", pdev->urbs[i]);
+			usb_kill_urb(pdev->urbs[i]);
+		}
+	}
+}
+
+static void pwc_iso_free(struct pwc_device *pdev)
+{
+	int i;
+
+	/* Freeing ISOC buffers one by one */
+	for (i = 0; i < MAX_ISO_BUFS; i++) {
+		if (pdev->urbs[i]) {
+			PWC_DEBUG_MEMORY("Freeing URB\n");
+			if (pdev->urbs[i]->transfer_buffer) {
+				usb_free_coherent(pdev->udev,
+					pdev->urbs[i]->transfer_buffer_length,
+					pdev->urbs[i]->transfer_buffer,
+					pdev->urbs[i]->transfer_dma);
+			}
+			usb_free_urb(pdev->urbs[i]);
+			pdev->urbs[i] = NULL;
+		}
+	}
+}
+
+/* Both v4l2_lock and vb_queue_lock should be locked when calling this */
+static void pwc_isoc_cleanup(struct pwc_device *pdev)
+{
+	PWC_DEBUG_OPEN(">> pwc_isoc_cleanup()\n");
+
+	pwc_iso_stop(pdev);
+	pwc_iso_free(pdev);
+	usb_set_interface(pdev->udev, 0, 0);
+
+	PWC_DEBUG_OPEN("<< pwc_isoc_cleanup()\n");
+}
+
+/* Must be called with vb_queue_lock hold */
+static void pwc_cleanup_queued_bufs(struct pwc_device *pdev,
+				    enum vb2_buffer_state state)
+{
+	unsigned long flags = 0;
+
+	spin_lock_irqsave(&pdev->queued_bufs_lock, flags);
+	while (!list_empty(&pdev->queued_bufs)) {
+		struct pwc_frame_buf *buf;
+
+		buf = list_entry(pdev->queued_bufs.next, struct pwc_frame_buf,
+				 list);
+		list_del(&buf->list);
+		vb2_buffer_done(&buf->vb.vb2_buf, state);
+	}
+	spin_unlock_irqrestore(&pdev->queued_bufs_lock, flags);
+}
+
+#ifdef CONFIG_USB_PWC_DEBUG
+static const char *pwc_sensor_type_to_string(unsigned int sensor_type)
+{
+	switch(sensor_type) {
+		case 0x00:
+			return "Hyundai CMOS sensor";
+		case 0x20:
+			return "Sony CCD sensor + TDA8787";
+		case 0x2E:
+			return "Sony CCD sensor + Exas 98L59";
+		case 0x2F:
+			return "Sony CCD sensor + ADI 9804";
+		case 0x30:
+			return "Sharp CCD sensor + TDA8787";
+		case 0x3E:
+			return "Sharp CCD sensor + Exas 98L59";
+		case 0x3F:
+			return "Sharp CCD sensor + ADI 9804";
+		case 0x40:
+			return "UPA 1021 sensor";
+		case 0x100:
+			return "VGA sensor";
+		case 0x101:
+			return "PAL MR sensor";
+		default:
+			return "unknown type of sensor";
+	}
+}
+#endif
+
+/***************************************************************************/
+/* Video4Linux functions */
+
+static void pwc_video_release(struct v4l2_device *v)
+{
+	struct pwc_device *pdev = container_of(v, struct pwc_device, v4l2_dev);
+
+	v4l2_ctrl_handler_free(&pdev->ctrl_handler);
+	v4l2_device_unregister(&pdev->v4l2_dev);
+	kfree(pdev->ctrl_buf);
+	kfree(pdev);
+}
+
+/***************************************************************************/
+/* Videobuf2 operations */
+
+static int queue_setup(struct vb2_queue *vq,
+				unsigned int *nbuffers, unsigned int *nplanes,
+				unsigned int sizes[], struct device *alloc_devs[])
+{
+	struct pwc_device *pdev = vb2_get_drv_priv(vq);
+	int size;
+
+	if (*nbuffers < MIN_FRAMES)
+		*nbuffers = MIN_FRAMES;
+	else if (*nbuffers > MAX_FRAMES)
+		*nbuffers = MAX_FRAMES;
+
+	*nplanes = 1;
+
+	size = pwc_get_size(pdev, MAX_WIDTH, MAX_HEIGHT);
+	sizes[0] = PAGE_ALIGN(pwc_image_sizes[size][0] *
+			      pwc_image_sizes[size][1] * 3 / 2);
+
+	return 0;
+}
+
+static int buffer_init(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct pwc_frame_buf *buf =
+		container_of(vbuf, struct pwc_frame_buf, vb);
+
+	/* need vmalloc since frame buffer > 128K */
+	buf->data = vzalloc(PWC_FRAME_SIZE);
+	if (buf->data == NULL)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static int buffer_prepare(struct vb2_buffer *vb)
+{
+	struct pwc_device *pdev = vb2_get_drv_priv(vb->vb2_queue);
+
+	/* Don't allow queing new buffers after device disconnection */
+	if (!pdev->udev)
+		return -ENODEV;
+
+	return 0;
+}
+
+static void buffer_finish(struct vb2_buffer *vb)
+{
+	struct pwc_device *pdev = vb2_get_drv_priv(vb->vb2_queue);
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct pwc_frame_buf *buf =
+		container_of(vbuf, struct pwc_frame_buf, vb);
+
+	if (vb->state == VB2_BUF_STATE_DONE) {
+		/*
+		 * Application has called dqbuf and is getting back a buffer
+		 * we've filled, take the pwc data we've stored in buf->data
+		 * and decompress it into a usable format, storing the result
+		 * in the vb2_buffer.
+		 */
+		pwc_decompress(pdev, buf);
+	}
+}
+
+static void buffer_cleanup(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct pwc_frame_buf *buf =
+		container_of(vbuf, struct pwc_frame_buf, vb);
+
+	vfree(buf->data);
+}
+
+static void buffer_queue(struct vb2_buffer *vb)
+{
+	struct pwc_device *pdev = vb2_get_drv_priv(vb->vb2_queue);
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct pwc_frame_buf *buf =
+		container_of(vbuf, struct pwc_frame_buf, vb);
+	unsigned long flags = 0;
+
+	/* Check the device has not disconnected between prep and queuing */
+	if (!pdev->udev) {
+		vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
+		return;
+	}
+
+	spin_lock_irqsave(&pdev->queued_bufs_lock, flags);
+	list_add_tail(&buf->list, &pdev->queued_bufs);
+	spin_unlock_irqrestore(&pdev->queued_bufs_lock, flags);
+}
+
+static int start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+	struct pwc_device *pdev = vb2_get_drv_priv(vq);
+	int r;
+
+	if (!pdev->udev)
+		return -ENODEV;
+
+	if (mutex_lock_interruptible(&pdev->v4l2_lock))
+		return -ERESTARTSYS;
+	/* Turn on camera and set LEDS on */
+	pwc_camera_power(pdev, 1);
+	pwc_set_leds(pdev, leds[0], leds[1]);
+
+	r = pwc_isoc_init(pdev);
+	if (r) {
+		/* If we failed turn camera and LEDS back off */
+		pwc_set_leds(pdev, 0, 0);
+		pwc_camera_power(pdev, 0);
+		/* And cleanup any queued bufs!! */
+		pwc_cleanup_queued_bufs(pdev, VB2_BUF_STATE_QUEUED);
+	}
+	mutex_unlock(&pdev->v4l2_lock);
+
+	return r;
+}
+
+static void stop_streaming(struct vb2_queue *vq)
+{
+	struct pwc_device *pdev = vb2_get_drv_priv(vq);
+
+	mutex_lock(&pdev->v4l2_lock);
+	if (pdev->udev) {
+		pwc_set_leds(pdev, 0, 0);
+		pwc_camera_power(pdev, 0);
+		pwc_isoc_cleanup(pdev);
+	}
+
+	pwc_cleanup_queued_bufs(pdev, VB2_BUF_STATE_ERROR);
+	if (pdev->fill_buf)
+		vb2_buffer_done(&pdev->fill_buf->vb.vb2_buf,
+				VB2_BUF_STATE_ERROR);
+	mutex_unlock(&pdev->v4l2_lock);
+}
+
+static const struct vb2_ops pwc_vb_queue_ops = {
+	.queue_setup		= queue_setup,
+	.buf_init		= buffer_init,
+	.buf_prepare		= buffer_prepare,
+	.buf_finish		= buffer_finish,
+	.buf_cleanup		= buffer_cleanup,
+	.buf_queue		= buffer_queue,
+	.start_streaming	= start_streaming,
+	.stop_streaming		= stop_streaming,
+	.wait_prepare		= vb2_ops_wait_prepare,
+	.wait_finish		= vb2_ops_wait_finish,
+};
+
+/***************************************************************************/
+/* USB functions */
+
+/* This function gets called when a new device is plugged in or the usb core
+ * is loaded.
+ */
+
+static int usb_pwc_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+	struct usb_device *udev = interface_to_usbdev(intf);
+	struct pwc_device *pdev = NULL;
+	int vendor_id, product_id, type_id;
+	int rc;
+	int features = 0;
+	int compression = 0;
+	int my_power_save = power_save;
+	char serial_number[30], *name;
+
+	vendor_id = le16_to_cpu(udev->descriptor.idVendor);
+	product_id = le16_to_cpu(udev->descriptor.idProduct);
+
+	/* Check if we can handle this device */
+	PWC_DEBUG_PROBE("probe() called [%04X %04X], if %d\n",
+		vendor_id, product_id,
+		intf->altsetting->desc.bInterfaceNumber);
+
+	/* the interfaces are probed one by one. We are only interested in the
+	   video interface (0) now.
+	   Interface 1 is the Audio Control, and interface 2 Audio itself.
+	 */
+	if (intf->altsetting->desc.bInterfaceNumber > 0)
+		return -ENODEV;
+
+	if (vendor_id == 0x0471) {
+		switch (product_id) {
+		case 0x0302:
+			PWC_INFO("Philips PCA645VC USB webcam detected.\n");
+			name = "Philips 645 webcam";
+			type_id = 645;
+			break;
+		case 0x0303:
+			PWC_INFO("Philips PCA646VC USB webcam detected.\n");
+			name = "Philips 646 webcam";
+			type_id = 646;
+			break;
+		case 0x0304:
+			PWC_INFO("Askey VC010 type 2 USB webcam detected.\n");
+			name = "Askey VC010 webcam";
+			type_id = 646;
+			break;
+		case 0x0307:
+			PWC_INFO("Philips PCVC675K (Vesta) USB webcam detected.\n");
+			name = "Philips 675 webcam";
+			type_id = 675;
+			break;
+		case 0x0308:
+			PWC_INFO("Philips PCVC680K (Vesta Pro) USB webcam detected.\n");
+			name = "Philips 680 webcam";
+			type_id = 680;
+			break;
+		case 0x030C:
+			PWC_INFO("Philips PCVC690K (Vesta Pro Scan) USB webcam detected.\n");
+			name = "Philips 690 webcam";
+			type_id = 690;
+			break;
+		case 0x0310:
+			PWC_INFO("Philips PCVC730K (ToUCam Fun)/PCVC830 (ToUCam II) USB webcam detected.\n");
+			name = "Philips 730 webcam";
+			type_id = 730;
+			break;
+		case 0x0311:
+			PWC_INFO("Philips PCVC740K (ToUCam Pro)/PCVC840 (ToUCam II) USB webcam detected.\n");
+			name = "Philips 740 webcam";
+			type_id = 740;
+			break;
+		case 0x0312:
+			PWC_INFO("Philips PCVC750K (ToUCam Pro Scan) USB webcam detected.\n");
+			name = "Philips 750 webcam";
+			type_id = 750;
+			break;
+		case 0x0313:
+			PWC_INFO("Philips PCVC720K/40 (ToUCam XS) USB webcam detected.\n");
+			name = "Philips 720K/40 webcam";
+			type_id = 720;
+			break;
+		case 0x0329:
+			PWC_INFO("Philips SPC 900NC USB webcam detected.\n");
+			name = "Philips SPC 900NC webcam";
+			type_id = 740;
+			break;
+		case 0x032C:
+			PWC_INFO("Philips SPC 880NC USB webcam detected.\n");
+			name = "Philips SPC 880NC webcam";
+			type_id = 740;
+			break;
+		default:
+			return -ENODEV;
+			break;
+		}
+	}
+	else if (vendor_id == 0x069A) {
+		switch(product_id) {
+		case 0x0001:
+			PWC_INFO("Askey VC010 type 1 USB webcam detected.\n");
+			name = "Askey VC010 webcam";
+			type_id = 645;
+			break;
+		default:
+			return -ENODEV;
+			break;
+		}
+	}
+	else if (vendor_id == 0x046d) {
+		switch(product_id) {
+		case 0x08b0:
+			PWC_INFO("Logitech QuickCam Pro 3000 USB webcam detected.\n");
+			name = "Logitech QuickCam Pro 3000";
+			type_id = 740; /* CCD sensor */
+			break;
+		case 0x08b1:
+			PWC_INFO("Logitech QuickCam Notebook Pro USB webcam detected.\n");
+			name = "Logitech QuickCam Notebook Pro";
+			type_id = 740; /* CCD sensor */
+			break;
+		case 0x08b2:
+			PWC_INFO("Logitech QuickCam 4000 Pro USB webcam detected.\n");
+			name = "Logitech QuickCam Pro 4000";
+			type_id = 740; /* CCD sensor */
+			if (my_power_save == -1)
+				my_power_save = 1;
+			break;
+		case 0x08b3:
+			PWC_INFO("Logitech QuickCam Zoom USB webcam detected.\n");
+			name = "Logitech QuickCam Zoom";
+			type_id = 740; /* CCD sensor */
+			break;
+		case 0x08B4:
+			PWC_INFO("Logitech QuickCam Zoom (new model) USB webcam detected.\n");
+			name = "Logitech QuickCam Zoom";
+			type_id = 740; /* CCD sensor */
+			if (my_power_save == -1)
+				my_power_save = 1;
+			break;
+		case 0x08b5:
+			PWC_INFO("Logitech QuickCam Orbit/Sphere USB webcam detected.\n");
+			name = "Logitech QuickCam Orbit";
+			type_id = 740; /* CCD sensor */
+			if (my_power_save == -1)
+				my_power_save = 1;
+			features |= FEATURE_MOTOR_PANTILT;
+			break;
+		case 0x08b6:
+			PWC_INFO("Logitech/Cisco VT Camera webcam detected.\n");
+			name = "Cisco VT Camera";
+			type_id = 740; /* CCD sensor */
+			break;
+		case 0x08b7:
+			PWC_INFO("Logitech ViewPort AV 100 webcam detected.\n");
+			name = "Logitech ViewPort AV 100";
+			type_id = 740; /* CCD sensor */
+			break;
+		case 0x08b8: /* Where this released? */
+			PWC_INFO("Logitech QuickCam detected (reserved ID).\n");
+			name = "Logitech QuickCam (res.)";
+			type_id = 730; /* Assuming CMOS */
+			break;
+		default:
+			return -ENODEV;
+			break;
+		}
+	}
+	else if (vendor_id == 0x055d) {
+		/* I don't know the difference between the C10 and the C30;
+		   I suppose the difference is the sensor, but both cameras
+		   work equally well with a type_id of 675
+		 */
+		switch(product_id) {
+		case 0x9000:
+			PWC_INFO("Samsung MPC-C10 USB webcam detected.\n");
+			name = "Samsung MPC-C10";
+			type_id = 675;
+			break;
+		case 0x9001:
+			PWC_INFO("Samsung MPC-C30 USB webcam detected.\n");
+			name = "Samsung MPC-C30";
+			type_id = 675;
+			break;
+		case 0x9002:
+			PWC_INFO("Samsung SNC-35E (v3.0) USB webcam detected.\n");
+			name = "Samsung MPC-C30";
+			type_id = 740;
+			break;
+		default:
+			return -ENODEV;
+			break;
+		}
+	}
+	else if (vendor_id == 0x041e) {
+		switch(product_id) {
+		case 0x400c:
+			PWC_INFO("Creative Labs Webcam 5 detected.\n");
+			name = "Creative Labs Webcam 5";
+			type_id = 730;
+			if (my_power_save == -1)
+				my_power_save = 1;
+			break;
+		case 0x4011:
+			PWC_INFO("Creative Labs Webcam Pro Ex detected.\n");
+			name = "Creative Labs Webcam Pro Ex";
+			type_id = 740;
+			break;
+		default:
+			return -ENODEV;
+			break;
+		}
+	}
+	else if (vendor_id == 0x04cc) {
+		switch(product_id) {
+		case 0x8116:
+			PWC_INFO("Sotec Afina Eye USB webcam detected.\n");
+			name = "Sotec Afina Eye";
+			type_id = 730;
+			break;
+		default:
+			return -ENODEV;
+			break;
+		}
+	}
+	else if (vendor_id == 0x06be) {
+		switch(product_id) {
+		case 0x8116:
+			/* This is essentially the same cam as the Sotec Afina Eye */
+			PWC_INFO("AME Co. Afina Eye USB webcam detected.\n");
+			name = "AME Co. Afina Eye";
+			type_id = 750;
+			break;
+		default:
+			return -ENODEV;
+			break;
+		}
+
+	}
+	else if (vendor_id == 0x0d81) {
+		switch(product_id) {
+		case 0x1900:
+			PWC_INFO("Visionite VCS-UC300 USB webcam detected.\n");
+			name = "Visionite VCS-UC300";
+			type_id = 740; /* CCD sensor */
+			break;
+		case 0x1910:
+			PWC_INFO("Visionite VCS-UM100 USB webcam detected.\n");
+			name = "Visionite VCS-UM100";
+			type_id = 730; /* CMOS sensor */
+			break;
+		default:
+			return -ENODEV;
+			break;
+		}
+	}
+	else
+		return -ENODEV; /* Not any of the know types; but the list keeps growing. */
+
+	if (my_power_save == -1)
+		my_power_save = 0;
+
+	memset(serial_number, 0, 30);
+	usb_string(udev, udev->descriptor.iSerialNumber, serial_number, 29);
+	PWC_DEBUG_PROBE("Device serial number is %s\n", serial_number);
+
+	if (udev->descriptor.bNumConfigurations > 1)
+		PWC_WARNING("Warning: more than 1 configuration available.\n");
+
+	/* Allocate structure, initialize pointers, mutexes, etc. and link it to the usb_device */
+	pdev = kzalloc(sizeof(struct pwc_device), GFP_KERNEL);
+	if (pdev == NULL) {
+		PWC_ERROR("Oops, could not allocate memory for pwc_device.\n");
+		return -ENOMEM;
+	}
+	pdev->type = type_id;
+	pdev->features = features;
+	pwc_construct(pdev); /* set min/max sizes correct */
+
+	mutex_init(&pdev->v4l2_lock);
+	mutex_init(&pdev->vb_queue_lock);
+	spin_lock_init(&pdev->queued_bufs_lock);
+	INIT_LIST_HEAD(&pdev->queued_bufs);
+
+	pdev->udev = udev;
+	pdev->power_save = my_power_save;
+
+	/* Init videobuf2 queue structure */
+	pdev->vb_queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	pdev->vb_queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
+	pdev->vb_queue.drv_priv = pdev;
+	pdev->vb_queue.buf_struct_size = sizeof(struct pwc_frame_buf);
+	pdev->vb_queue.ops = &pwc_vb_queue_ops;
+	pdev->vb_queue.mem_ops = &vb2_vmalloc_memops;
+	pdev->vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	rc = vb2_queue_init(&pdev->vb_queue);
+	if (rc < 0) {
+		PWC_ERROR("Oops, could not initialize vb2 queue.\n");
+		goto err_free_mem;
+	}
+
+	/* Init video_device structure */
+	pdev->vdev = pwc_template;
+	strcpy(pdev->vdev.name, name);
+	pdev->vdev.queue = &pdev->vb_queue;
+	pdev->vdev.queue->lock = &pdev->vb_queue_lock;
+	video_set_drvdata(&pdev->vdev, pdev);
+
+	pdev->release = le16_to_cpu(udev->descriptor.bcdDevice);
+	PWC_DEBUG_PROBE("Release: %04x\n", pdev->release);
+
+	/* Allocate USB command buffers */
+	pdev->ctrl_buf = kmalloc(sizeof(pdev->cmd_buf), GFP_KERNEL);
+	if (!pdev->ctrl_buf) {
+		PWC_ERROR("Oops, could not allocate memory for pwc_device.\n");
+		rc = -ENOMEM;
+		goto err_free_mem;
+	}
+
+#ifdef CONFIG_USB_PWC_DEBUG
+	/* Query sensor type */
+	if (pwc_get_cmos_sensor(pdev, &rc) >= 0) {
+		PWC_DEBUG_OPEN("This %s camera is equipped with a %s (%d).\n",
+				pdev->vdev.name,
+				pwc_sensor_type_to_string(rc), rc);
+	}
+#endif
+
+	/* Set the leds off */
+	pwc_set_leds(pdev, 0, 0);
+
+	/* Setup initial videomode */
+	rc = pwc_set_video_mode(pdev, MAX_WIDTH, MAX_HEIGHT,
+				V4L2_PIX_FMT_YUV420, 30, &compression, 1);
+	if (rc)
+		goto err_free_mem;
+
+	/* Register controls (and read default values from camera */
+	rc = pwc_init_controls(pdev);
+	if (rc) {
+		PWC_ERROR("Failed to register v4l2 controls (%d).\n", rc);
+		goto err_free_mem;
+	}
+
+	/* And powerdown the camera until streaming starts */
+	pwc_camera_power(pdev, 0);
+
+	/* Register the v4l2_device structure */
+	pdev->v4l2_dev.release = pwc_video_release;
+	rc = v4l2_device_register(&intf->dev, &pdev->v4l2_dev);
+	if (rc) {
+		PWC_ERROR("Failed to register v4l2-device (%d).\n", rc);
+		goto err_free_controls;
+	}
+
+	pdev->v4l2_dev.ctrl_handler = &pdev->ctrl_handler;
+	pdev->vdev.v4l2_dev = &pdev->v4l2_dev;
+	pdev->vdev.lock = &pdev->v4l2_lock;
+
+	rc = video_register_device(&pdev->vdev, VFL_TYPE_GRABBER, -1);
+	if (rc < 0) {
+		PWC_ERROR("Failed to register as video device (%d).\n", rc);
+		goto err_unregister_v4l2_dev;
+	}
+	PWC_INFO("Registered as %s.\n", video_device_node_name(&pdev->vdev));
+
+#ifdef CONFIG_USB_PWC_INPUT_EVDEV
+	/* register webcam snapshot button input device */
+	pdev->button_dev = input_allocate_device();
+	if (!pdev->button_dev) {
+		rc = -ENOMEM;
+		goto err_video_unreg;
+	}
+
+	usb_make_path(udev, pdev->button_phys, sizeof(pdev->button_phys));
+	strlcat(pdev->button_phys, "/input0", sizeof(pdev->button_phys));
+
+	pdev->button_dev->name = "PWC snapshot button";
+	pdev->button_dev->phys = pdev->button_phys;
+	usb_to_input_id(pdev->udev, &pdev->button_dev->id);
+	pdev->button_dev->dev.parent = &pdev->udev->dev;
+	pdev->button_dev->evbit[0] = BIT_MASK(EV_KEY);
+	pdev->button_dev->keybit[BIT_WORD(KEY_CAMERA)] = BIT_MASK(KEY_CAMERA);
+
+	rc = input_register_device(pdev->button_dev);
+	if (rc) {
+		input_free_device(pdev->button_dev);
+		pdev->button_dev = NULL;
+		goto err_video_unreg;
+	}
+#endif
+
+	return 0;
+
+#ifdef CONFIG_USB_PWC_INPUT_EVDEV
+err_video_unreg:
+	video_unregister_device(&pdev->vdev);
+#endif
+err_unregister_v4l2_dev:
+	v4l2_device_unregister(&pdev->v4l2_dev);
+err_free_controls:
+	v4l2_ctrl_handler_free(&pdev->ctrl_handler);
+err_free_mem:
+	kfree(pdev->ctrl_buf);
+	kfree(pdev);
+	return rc;
+}
+
+/* The user yanked out the cable... */
+static void usb_pwc_disconnect(struct usb_interface *intf)
+{
+	struct v4l2_device *v = usb_get_intfdata(intf);
+	struct pwc_device *pdev = container_of(v, struct pwc_device, v4l2_dev);
+
+	mutex_lock(&pdev->vb_queue_lock);
+	mutex_lock(&pdev->v4l2_lock);
+	/* No need to keep the urbs around after disconnection */
+	if (pdev->vb_queue.streaming)
+		pwc_isoc_cleanup(pdev);
+	pdev->udev = NULL;
+
+	v4l2_device_disconnect(&pdev->v4l2_dev);
+	video_unregister_device(&pdev->vdev);
+	mutex_unlock(&pdev->v4l2_lock);
+	mutex_unlock(&pdev->vb_queue_lock);
+
+#ifdef CONFIG_USB_PWC_INPUT_EVDEV
+	if (pdev->button_dev)
+		input_unregister_device(pdev->button_dev);
+#endif
+
+	v4l2_device_put(&pdev->v4l2_dev);
+}
+
+
+/*
+ * Initialization code & module stuff
+ */
+
+static unsigned int leds_nargs;
+
+#ifdef CONFIG_USB_PWC_DEBUG
+module_param_named(trace, pwc_trace, int, 0644);
+#endif
+module_param(power_save, int, 0644);
+module_param_array(leds, int, &leds_nargs, 0444);
+
+#ifdef CONFIG_USB_PWC_DEBUG
+MODULE_PARM_DESC(trace, "For debugging purposes");
+#endif
+MODULE_PARM_DESC(power_save, "Turn power saving for new cameras on or off");
+MODULE_PARM_DESC(leds, "LED on,off time in milliseconds");
+
+MODULE_DESCRIPTION("Philips & OEM USB webcam driver");
+MODULE_AUTHOR("Luc Saillard <luc@saillard.org>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("pwcx");
+MODULE_VERSION( PWC_VERSION );
+
+module_usb_driver(pwc_driver);
diff --git a/drivers/media/usb/pwc/pwc-kiara.c b/drivers/media/usb/pwc/pwc-kiara.c
new file mode 100644
index 0000000..e5f4fd8
--- /dev/null
+++ b/drivers/media/usb/pwc/pwc-kiara.c
@@ -0,0 +1,892 @@
+/* Linux driver for Philips webcam
+   (C) 2004-2006 Luc Saillard (luc@saillard.org)
+
+   NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx
+   driver and thus may have bugs that are not present in the original version.
+   Please send bug reports and support requests to <luc@saillard.org>.
+   The decompression routines have been implemented by reverse-engineering the
+   Nemosoft binary pwcx module. Caveat emptor.
+
+   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
+*/
+
+
+/* This tables contains entries for the 730/740/750 (Kiara) camera, with
+   4 different qualities (no compression, low, medium, high).
+   It lists the bandwidth requirements for said mode by its alternate interface
+   number. An alternate of 0 means that the mode is unavailable.
+
+   There are 6 * 4 * 4 entries:
+     6 different resolutions subqcif, qsif, qcif, sif, cif, vga
+     6 framerates: 5, 10, 15, 20, 25, 30
+     4 compression modi: none, low, medium, high
+
+   When an uncompressed mode is not available, the next available compressed mode
+   will be chosen (unless the decompressor is absent). Sometimes there are only
+   1 or 2 compressed modes available; in that case entries are duplicated.
+*/
+
+
+#include "pwc-kiara.h"
+
+const unsigned int Kiara_fps_vector[PWC_FPS_MAX_KIARA] = { 5, 10, 15, 20, 25, 30 };
+
+const struct Kiara_table_entry Kiara_table[PSZ_MAX][6][4] =
+{
+   /* SQCIF */
+   {
+      /* 5 fps */
+      {
+	 {0, },
+	 {0, },
+	 {0, },
+	 {0, },
+      },
+      /* 10 fps */
+      {
+	 {0, },
+	 {0, },
+	 {0, },
+	 {0, },
+      },
+      /* 15 fps */
+      {
+	 {0, },
+	 {0, },
+	 {0, },
+	 {0, },
+      },
+      /* 20 fps */
+      {
+	 {0, },
+	 {0, },
+	 {0, },
+	 {0, },
+      },
+      /* 25 fps */
+      {
+	 {0, },
+	 {0, },
+	 {0, },
+	 {0, },
+      },
+      /* 30 fps */
+      {
+	 {0, },
+	 {0, },
+	 {0, },
+	 {0, },
+      },
+   },
+   /* QSIF */
+   {
+      /* 5 fps */
+      {
+	 {1, 146,    0, {0x1D, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x92, 0x00, 0x80}},
+	 {1, 146,    0, {0x1D, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x92, 0x00, 0x80}},
+	 {1, 146,    0, {0x1D, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x92, 0x00, 0x80}},
+	 {1, 146,    0, {0x1D, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x92, 0x00, 0x80}},
+      },
+      /* 10 fps */
+      {
+	 {2, 291,    0, {0x1C, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x23, 0x01, 0x80}},
+	 {1, 192,  630, {0x14, 0xF4, 0x30, 0x13, 0xA9, 0x12, 0xE1, 0x17, 0x08, 0xC0, 0x00, 0x80}},
+	 {1, 192,  630, {0x14, 0xF4, 0x30, 0x13, 0xA9, 0x12, 0xE1, 0x17, 0x08, 0xC0, 0x00, 0x80}},
+	 {1, 192,  630, {0x14, 0xF4, 0x30, 0x13, 0xA9, 0x12, 0xE1, 0x17, 0x08, 0xC0, 0x00, 0x80}},
+      },
+      /* 15 fps */
+      {
+	 {3, 437,    0, {0x1B, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0xB5, 0x01, 0x80}},
+	 {2, 292,  640, {0x13, 0xF4, 0x30, 0x13, 0xF7, 0x13, 0x2F, 0x13, 0x20, 0x24, 0x01, 0x80}},
+	 {2, 292,  640, {0x13, 0xF4, 0x30, 0x13, 0xF7, 0x13, 0x2F, 0x13, 0x20, 0x24, 0x01, 0x80}},
+	 {1, 192,  420, {0x13, 0xF4, 0x30, 0x0D, 0x1B, 0x0C, 0x53, 0x1E, 0x18, 0xC0, 0x00, 0x80}},
+      },
+      /* 20 fps */
+      {
+	 {4, 589,    0, {0x1A, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x4D, 0x02, 0x80}},
+	 {3, 448,  730, {0x12, 0xF4, 0x30, 0x16, 0xC9, 0x16, 0x01, 0x0E, 0x18, 0xC0, 0x01, 0x80}},
+	 {2, 292,  476, {0x12, 0xF4, 0x30, 0x0E, 0xD8, 0x0E, 0x10, 0x19, 0x18, 0x24, 0x01, 0x80}},
+	 {1, 192,  312, {0x12, 0xF4, 0x50, 0x09, 0xB3, 0x08, 0xEB, 0x1E, 0x18, 0xC0, 0x00, 0x80}},
+      },
+      /* 25 fps */
+      {
+	 {5, 703,    0, {0x19, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0xBF, 0x02, 0x80}},
+	 {3, 447,  610, {0x11, 0xF4, 0x30, 0x13, 0x0B, 0x12, 0x43, 0x14, 0x28, 0xBF, 0x01, 0x80}},
+	 {2, 292,  398, {0x11, 0xF4, 0x50, 0x0C, 0x6C, 0x0B, 0xA4, 0x1E, 0x28, 0x24, 0x01, 0x80}},
+	 {1, 193,  262, {0x11, 0xF4, 0x50, 0x08, 0x23, 0x07, 0x5B, 0x1E, 0x28, 0xC1, 0x00, 0x80}},
+      },
+      /* 30 fps */
+      {
+	 {8, 874,    0, {0x18, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x6A, 0x03, 0x80}},
+	 {5, 704,  730, {0x10, 0xF4, 0x30, 0x16, 0xC9, 0x16, 0x01, 0x0E, 0x28, 0xC0, 0x02, 0x80}},
+	 {3, 448,  492, {0x10, 0xF4, 0x30, 0x0F, 0x5D, 0x0E, 0x95, 0x15, 0x28, 0xC0, 0x01, 0x80}},
+	 {2, 292,  320, {0x10, 0xF4, 0x50, 0x09, 0xFB, 0x09, 0x33, 0x1E, 0x28, 0x24, 0x01, 0x80}},
+      },
+   },
+   /* QCIF */
+   {
+      /* 5 fps */
+      {
+	 {0, },
+	 {0, },
+	 {0, },
+	 {0, },
+      },
+      /* 10 fps */
+      {
+	 {0, },
+	 {0, },
+	 {0, },
+	 {0, },
+      },
+      /* 15 fps */
+      {
+	 {0, },
+	 {0, },
+	 {0, },
+	 {0, },
+      },
+      /* 20 fps */
+      {
+	 {0, },
+	 {0, },
+	 {0, },
+	 {0, },
+      },
+      /* 25 fps */
+      {
+	 {0, },
+	 {0, },
+	 {0, },
+	 {0, },
+      },
+      /* 30 fps */
+      {
+	 {0, },
+	 {0, },
+	 {0, },
+	 {0, },
+      },
+   },
+   /* SIF */
+   {
+      /* 5 fps */
+      {
+	 {4, 582,    0, {0x0D, 0xF4, 0x30, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x46, 0x02, 0x80}},
+	 {3, 387, 1276, {0x05, 0xF4, 0x30, 0x27, 0xD8, 0x26, 0x48, 0x03, 0x10, 0x83, 0x01, 0x80}},
+	 {2, 291,  960, {0x05, 0xF4, 0x30, 0x1D, 0xF2, 0x1C, 0x62, 0x04, 0x10, 0x23, 0x01, 0x80}},
+	 {1, 191,  630, {0x05, 0xF4, 0x50, 0x13, 0xA9, 0x12, 0x19, 0x05, 0x18, 0xBF, 0x00, 0x80}},
+      },
+      /* 10 fps */
+      {
+	 {0, },
+	 {6, 775, 1278, {0x04, 0xF4, 0x30, 0x27, 0xE8, 0x26, 0x58, 0x05, 0x30, 0x07, 0x03, 0x80}},
+	 {3, 447,  736, {0x04, 0xF4, 0x30, 0x16, 0xFB, 0x15, 0x6B, 0x05, 0x28, 0xBF, 0x01, 0x80}},
+	 {2, 292,  480, {0x04, 0xF4, 0x70, 0x0E, 0xF9, 0x0D, 0x69, 0x09, 0x28, 0x24, 0x01, 0x80}},
+      },
+      /* 15 fps */
+      {
+	 {0, },
+	 {9, 955, 1050, {0x03, 0xF4, 0x30, 0x20, 0xCF, 0x1F, 0x3F, 0x06, 0x48, 0xBB, 0x03, 0x80}},
+	 {4, 592,  650, {0x03, 0xF4, 0x30, 0x14, 0x44, 0x12, 0xB4, 0x08, 0x30, 0x50, 0x02, 0x80}},
+	 {3, 448,  492, {0x03, 0xF4, 0x50, 0x0F, 0x52, 0x0D, 0xC2, 0x09, 0x38, 0xC0, 0x01, 0x80}},
+      },
+      /* 20 fps */
+      {
+	 {0, },
+	 {9, 958,  782, {0x02, 0xF4, 0x30, 0x18, 0x6A, 0x16, 0xDA, 0x0B, 0x58, 0xBE, 0x03, 0x80}},
+	 {5, 703,  574, {0x02, 0xF4, 0x50, 0x11, 0xE7, 0x10, 0x57, 0x0B, 0x40, 0xBF, 0x02, 0x80}},
+	 {3, 446,  364, {0x02, 0xF4, 0x90, 0x0B, 0x5C, 0x09, 0xCC, 0x0E, 0x38, 0xBE, 0x01, 0x80}},
+      },
+      /* 25 fps */
+      {
+	 {0, },
+	 {9, 958,  654, {0x01, 0xF4, 0x30, 0x14, 0x66, 0x12, 0xD6, 0x0B, 0x50, 0xBE, 0x03, 0x80}},
+	 {6, 776,  530, {0x01, 0xF4, 0x50, 0x10, 0x8C, 0x0E, 0xFC, 0x0C, 0x48, 0x08, 0x03, 0x80}},
+	 {4, 592,  404, {0x01, 0xF4, 0x70, 0x0C, 0x96, 0x0B, 0x06, 0x0B, 0x48, 0x50, 0x02, 0x80}},
+      },
+      /* 30 fps */
+      {
+	 {0, },
+	 {9, 957,  526, {0x00, 0xF4, 0x50, 0x10, 0x68, 0x0E, 0xD8, 0x0D, 0x58, 0xBD, 0x03, 0x80}},
+	 {6, 775,  426, {0x00, 0xF4, 0x70, 0x0D, 0x48, 0x0B, 0xB8, 0x0F, 0x50, 0x07, 0x03, 0x80}},
+	 {4, 590,  324, {0x00, 0x7A, 0x88, 0x0A, 0x1C, 0x08, 0xB4, 0x0E, 0x50, 0x4E, 0x02, 0x80}},
+      },
+   },
+   /* CIF */
+   {
+      /* 5 fps */
+      {
+	 {0, },
+	 {0, },
+	 {0, },
+	 {0, },
+      },
+      /* 10 fps */
+      {
+	 {0, },
+	 {0, },
+	 {0, },
+	 {0, },
+      },
+      /* 15 fps */
+      {
+	 {0, },
+	 {0, },
+	 {0, },
+	 {0, },
+      },
+      /* 20 fps */
+      {
+	 {0, },
+	 {0, },
+	 {0, },
+	 {0, },
+      },
+      /* 25 fps */
+      {
+	 {0, },
+	 {0, },
+	 {0, },
+	 {0, },
+      },
+      /* 30 fps */
+      {
+	 {0, },
+	 {0, },
+	 {0, },
+	 {0, },
+      },
+   },
+   /* VGA */
+   {
+      /* 5 fps */
+      {
+	 {0, },
+	 {6, 773, 1272, {0x25, 0xF4, 0x30, 0x27, 0xB6, 0x24, 0x96, 0x02, 0x30, 0x05, 0x03, 0x80}},
+	 {4, 592,  976, {0x25, 0xF4, 0x50, 0x1E, 0x78, 0x1B, 0x58, 0x03, 0x30, 0x50, 0x02, 0x80}},
+	 {3, 448,  738, {0x25, 0xF4, 0x90, 0x17, 0x0C, 0x13, 0xEC, 0x04, 0x30, 0xC0, 0x01, 0x80}},
+      },
+      /* 10 fps */
+      {
+	 {0, },
+	 {9, 956,  788, {0x24, 0xF4, 0x70, 0x18, 0x9C, 0x15, 0x7C, 0x03, 0x48, 0xBC, 0x03, 0x80}},
+	 {6, 776,  640, {0x24, 0xF4, 0xB0, 0x13, 0xFC, 0x11, 0x2C, 0x04, 0x48, 0x08, 0x03, 0x80}},
+	 {4, 592,  488, {0x24, 0x7A, 0xE8, 0x0F, 0x3C, 0x0C, 0x6C, 0x06, 0x48, 0x50, 0x02, 0x80}},
+      },
+      /* 15 fps */
+      {
+	 {0, },
+	 {9, 957,  526, {0x23, 0x7A, 0xE8, 0x10, 0x68, 0x0D, 0x98, 0x06, 0x58, 0xBD, 0x03, 0x80}},
+	 {9, 957,  526, {0x23, 0x7A, 0xE8, 0x10, 0x68, 0x0D, 0x98, 0x06, 0x58, 0xBD, 0x03, 0x80}},
+	 {8, 895,  492, {0x23, 0x7A, 0xE8, 0x0F, 0x5D, 0x0C, 0x8D, 0x06, 0x58, 0x7F, 0x03, 0x80}},
+      },
+      /* 20 fps */
+      {
+	 {0, },
+	 {0, },
+	 {0, },
+	 {0, },
+      },
+      /* 25 fps */
+      {
+	 {0, },
+	 {0, },
+	 {0, },
+	 {0, },
+      },
+      /* 30 fps */
+      {
+	 {0, },
+	 {0, },
+	 {0, },
+	 {0, },
+      },
+   },
+};
+
+
+/*
+ * Rom table for kiara chips
+ *
+ * 32 roms tables (one for each resolution ?)
+ *  2 tables per roms (one for each passes) (Y, and U&V)
+ * 128 bytes per passes
+ */
+
+const unsigned int KiaraRomTable [8][2][16][8] =
+{
+ { /* version 0 */
+  { /* version 0, passes 0 */
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000001,0x00000001},
+   {0x00000000,0x00000000,0x00000009,0x00000009,
+    0x00000009,0x00000009,0x00000009,0x00000009},
+   {0x00000000,0x00000000,0x00000009,0x00000049,
+    0x00000049,0x00000049,0x00000049,0x00000049},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000249,0x0000024a,0x00000049},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000249,0x00000249,0x0000024a,0x0000024a},
+   {0x00000000,0x00000000,0x00000049,0x00000249,
+    0x00000249,0x0000124a,0x0000024a,0x0000024a},
+   {0x00000000,0x00000000,0x00000049,0x00000249,
+    0x0000124a,0x00009252,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00009252,0x00009292,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x00000249,0x00001249,
+    0x00009292,0x00009292,0x00009493,0x000124db},
+   {0x00000000,0x00000000,0x00000249,0x0000924a,
+    0x00009492,0x0000a49b,0x0000a49b,0x000124db},
+   {0x00000000,0x00000000,0x00001249,0x00009252,
+    0x0000a493,0x000124db,0x000124db,0x000126dc},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x000124db,0x000126dc,0x000136e4,0x000126dc},
+   {0x00000000,0x00000000,0x00009292,0x0000a49b,
+    0x000124db,0x000136e4,0x000136e4,0x000136e4},
+   {0x00000000,0x00000000,0x00009292,0x0000a49b,
+    0x000126dc,0x0001b724,0x0001b92d,0x0001b925},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x000136e4,0x0001b925,0x0001c96e,0x0001c92d},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  },
+  { /* version 0, passes 1 */
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000},
+   {0x00000000,0x00000000,0x00000001,0x00000009,
+    0x00000009,0x00000009,0x00000009,0x00000001},
+   {0x00000000,0x00000000,0x00000009,0x00000009,
+    0x00000049,0x00000049,0x00000049,0x00000049},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000049,0x0000024a,0x0000024a},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000249,0x00000249,0x0000024a,0x0000024a},
+   {0x00000000,0x00000000,0x00000049,0x00000249,
+    0x00000249,0x00000249,0x0000024a,0x00001252},
+   {0x00000000,0x00000000,0x00000049,0x00001249,
+    0x0000124a,0x0000124a,0x00001252,0x00009292},
+   {0x00000000,0x00000000,0x00000249,0x00001249,
+    0x00009252,0x00009252,0x00009292,0x00009493},
+   {0x00000000,0x00000000,0x00000249,0x0000924a,
+    0x00009292,0x00009292,0x00009292,0x00009493},
+   {0x00000000,0x00000000,0x00000249,0x00009292,
+    0x00009492,0x00009493,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x0000a493,0x000124db,0x000126dc,0x000126dc},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x000126dc,0x000136e4,0x000136e4},
+   {0x00000000,0x00000000,0x00009252,0x00009493,
+    0x000126dc,0x000126dc,0x000136e4,0x000136e4},
+   {0x00000000,0x00000000,0x00009292,0x0000a49b,
+    0x000136e4,0x000136e4,0x0001b725,0x0001b724},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  }
+ },
+ { /* version 1 */
+  { /* version 1, passes 0 */
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000001},
+   {0x00000000,0x00000000,0x00000009,0x00000009,
+    0x00000009,0x00000009,0x00000009,0x00000009},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000049,0x00000049,0x00000049},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000249,0x0000024a,0x0000024a},
+   {0x00000000,0x00000000,0x00000049,0x00000249,
+    0x00000249,0x00000249,0x0000024a,0x00001252},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00000249,0x0000124a,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x0000124a,0x0000124a,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x00000249,0x00001249,
+    0x0000124a,0x00009252,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x00000249,0x00001249,
+    0x00009252,0x00009292,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x00000249,0x00001249,
+    0x00009252,0x00009292,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x00000249,0x0000924a,
+    0x00009252,0x00009493,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x00000249,0x0000924a,
+    0x00009292,0x00009493,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x00000249,0x00009252,
+    0x00009492,0x00009493,0x0000a49b,0x0000a49b},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x00009492,0x000124db,0x000124db,0x000124db},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x000126dc,0x000126dc,0x000126dc},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  },
+  { /* version 1, passes 1 */
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000},
+   {0x00000000,0x00000000,0x00000049,0x00000009,
+    0x00000049,0x00000009,0x00000001,0x00000000},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000049,0x00000049,0x00000000},
+   {0x00000000,0x00000000,0x00000249,0x00000049,
+    0x00000249,0x00000049,0x0000024a,0x00000001},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00000249,0x00000249,0x0000024a,0x00000001},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00000249,0x00000249,0x0000024a,0x00000001},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00000249,0x00000249,0x0000024a,0x00000009},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x0000124a,0x0000124a,0x0000024a,0x00000009},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x0000124a,0x0000124a,0x0000024a,0x00000009},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x0000124a,0x00009252,0x00001252,0x00000049},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x0000124a,0x00009292,0x00001252,0x00000049},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x0000124a,0x00009292,0x00001252,0x00000049},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x00009292,0x00001252,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009292,0x00009292,0x00001252,0x0000024a},
+   {0x00000000,0x00000000,0x0000924a,0x0000924a,
+    0x00009492,0x00009493,0x00009292,0x00001252},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  }
+ },
+ { /* version 2 */
+  { /* version 2, passes 0 */
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000049,0x0000024a,0x0000024a},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00000249,0x0000124a,0x00001252,0x00009292},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x0000124a,0x00009252,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x00000249,0x00001249,
+    0x0000124a,0x00009292,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x00000249,0x00001249,
+    0x00009252,0x00009493,0x00009493,0x0000a49b},
+   {0x00000000,0x00000000,0x00000249,0x0000924a,
+    0x00009292,0x00009493,0x0000a49b,0x0000a49b},
+   {0x00000000,0x00000000,0x00001249,0x0000924a,
+    0x00009292,0x00009493,0x0000a49b,0x000124db},
+   {0x00000000,0x00000000,0x00001249,0x00009252,
+    0x00009492,0x0000a49b,0x0000a49b,0x000124db},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x00009492,0x000124db,0x000124db,0x000126dc},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x0000a493,0x000124db,0x000126dc,0x000126dc},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x000124db,0x000126dc,0x000136e4},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x000126dc,0x000136e4,0x000136e4},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0001249b,0x000126dc,0x000136e4,0x000136e4},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000124db,0x000136e4,0x000136e4,0x0001b724},
+   {0x00000000,0x00000000,0x00009252,0x000124db,
+    0x000126dc,0x0001b724,0x0001b725,0x0001b925},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  },
+  { /* version 2, passes 1 */
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000049,0x00000049,0x00000049},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00000249,0x00000249,0x0000024a,0x00000049},
+   {0x00000000,0x00000000,0x00001249,0x00000249,
+    0x0000124a,0x0000124a,0x00001252,0x00000049},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x0000124a,0x0000124a,0x00009292,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x00009292,0x00009292,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x00009292,0x0000a49b,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009292,0x00009493,0x0000a49b,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009292,0x00009493,0x0000a49b,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x0000924a,
+    0x00009492,0x0000a49b,0x0000a49b,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x00009252,
+    0x00009492,0x0000a49b,0x0000a49b,0x00009292},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x00009492,0x0000a49b,0x0000a49b,0x00009292},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x0000a49b,0x0000a49b,0x00009292},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x0000a49b,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x000124db,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x00009252,0x0000a49b,
+    0x0001249b,0x000126dc,0x000124db,0x0000a49b},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  }
+ },
+ { /* version 3 */
+  { /* version 3, passes 0 */
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x0000124a,0x0000124a,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009292,0x00009493,0x0000a49b,0x0000a49b},
+   {0x00000000,0x00000000,0x00001249,0x0000924a,
+    0x00009492,0x0000a49b,0x0000a49b,0x000124db},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x00009492,0x000124db,0x000126dc,0x000126dc},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x000124db,0x000126dc,0x000126dc},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x000126dc,0x000136e4,0x000136e4},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x000126dc,0x000136e4,0x0001b724},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0001249b,0x000126dc,0x000136e4,0x0001b724},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000126dc,0x000136e4,0x0001b724},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000136e4,0x0001b725,0x0001b724},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000124db,0x000136e4,0x0001b725,0x0001b925},
+   {0x00000000,0x00000000,0x00009292,0x0000a49b,
+    0x000126dc,0x000136e4,0x0001b92d,0x0001b925},
+   {0x00000000,0x00000000,0x00009292,0x0000a49b,
+    0x000126dc,0x0001b724,0x0001b92d,0x0001c92d},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x000126dc,0x0001b724,0x0001c96e,0x0001c92d},
+   {0x00000000,0x00000000,0x0000a492,0x000126db,
+    0x000136e4,0x0001b925,0x00025bb6,0x00024b77},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  },
+  { /* version 3, passes 1 */
+   {0x00000000,0x00000000,0x00001249,0x00000249,
+    0x0000124a,0x0000124a,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x00009292,0x00009292,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x0000924a,
+    0x00009492,0x00009493,0x0000a49b,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x00009252,
+    0x00009492,0x0000a49b,0x0000a49b,0x00009292},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x00009492,0x0000a49b,0x0000a49b,0x00009292},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x0000a49b,0x000126dc,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x0000a49b,0x000126dc,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x0000a49b,0x000126dc,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x000124db,0x000126dc,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x000124db,0x000126dc,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x000124db,0x000126dc,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000126dc,0x000126dc,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000124db,0x000136e4,0x000126dc,0x000124db},
+   {0x00000000,0x00000000,0x00009492,0x0000a49b,
+    0x000136e4,0x000136e4,0x000126dc,0x000124db},
+   {0x00000000,0x00000000,0x0000a492,0x000124db,
+    0x0001b724,0x0001b724,0x000136e4,0x000126dc},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  }
+ },
+ { /* version 4 */
+  { /* version 4, passes 0 */
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000049,0x00000049,0x00000049},
+   {0x00000000,0x00000000,0x00000249,0x00000049,
+    0x00000249,0x00000249,0x0000024a,0x00000049},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x0000124a,0x00009252,0x00001252,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x00009292,0x00009493,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x0000924a,
+    0x00009292,0x00009493,0x00009493,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x00009492,0x0000a49b,0x0000a49b,0x00009292},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x000124db,0x000124db,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x000124db,0x000126dc,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x000124db,0x000126dc,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0001249b,0x000126dc,0x000126dc,0x000124db},
+   {0x00000000,0x00000000,0x00009252,0x00009493,
+    0x000124db,0x000136e4,0x000136e4,0x000126dc},
+   {0x00000000,0x00000000,0x00009252,0x0000a49b,
+    0x000124db,0x000136e4,0x000136e4,0x000126dc},
+   {0x00000000,0x00000000,0x00009292,0x0000a49b,
+    0x000126dc,0x000136e4,0x000136e4,0x000136e4},
+   {0x00000000,0x00000000,0x00009492,0x0000a49b,
+    0x000126dc,0x0001b724,0x0001b725,0x0001b724},
+   {0x00000000,0x00000000,0x0000a492,0x000124db,
+    0x000136e4,0x0001b925,0x0001b92d,0x0001b925},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  },
+  { /* version 4, passes 1 */
+   {0x00000000,0x00000000,0x00000249,0x00000049,
+    0x00000009,0x00000009,0x00000009,0x00000009},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00000049,0x00000049,0x00000009,0x00000009},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x0000124a,0x00000249,0x00000049,0x00000049},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x0000124a,0x0000124a,0x00000049,0x00000049},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x0000124a,0x0000024a,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x0000924a,
+    0x00009252,0x0000124a,0x0000024a,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x00009492,0x00009252,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x00009292,0x00009292,0x00001252},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x00009292,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x00009493,0x00009493,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x0000a49b,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x0000a49b,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000124db,0x0000a49b,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000136e4,0x000126dc,0x000124db,0x0000a49b},
+   {0x00000000,0x00000000,0x00009252,0x000124db,
+    0x0001b724,0x000136e4,0x000126dc,0x000124db},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  }
+ },
+ { /* version 5 */
+  { /* version 5, passes 0 */
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00000249,0x00000249,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x00009292,0x00009292,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x0000924a,
+    0x00009492,0x0000a49b,0x0000a49b,0x00009292},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x0000a49b,0x000124db,0x00009493},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x000124db,0x000126dc,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x000126dc,0x000126dc,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000126dc,0x000136e4,0x000124db},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000126dc,0x000136e4,0x000136e4,0x000126dc},
+   {0x00000000,0x00000000,0x00009292,0x0000a49b,
+    0x000126dc,0x000136e4,0x000136e4,0x000126dc},
+   {0x00000000,0x00000000,0x00009292,0x0000a49b,
+    0x000126dc,0x0001b724,0x0001b725,0x000136e4},
+   {0x00000000,0x00000000,0x00009292,0x0000a49b,
+    0x000136e4,0x0001b724,0x0001b92d,0x0001b724},
+   {0x00000000,0x00000000,0x00009492,0x0000a49b,
+    0x000136e4,0x0001b724,0x0001b92d,0x0001b724},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x000136e4,0x0001b925,0x0001c96e,0x0001b925},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x0001b724,0x0001b925,0x0001c96e,0x0001c92d},
+   {0x00000000,0x00000000,0x0000a492,0x000126db,
+    0x0001c924,0x0002496d,0x00025bb6,0x00024b77},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  },
+  { /* version 5, passes 1 */
+   {0x00000000,0x00000000,0x00001249,0x00000249,
+    0x00000249,0x00000249,0x0000024a,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x0000124a,0x0000124a,0x0000024a,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x0000924a,
+    0x00009252,0x00009252,0x0000024a,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x00009492,0x0000a49b,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x0000a49b,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x0000a49b,0x00009292,0x00001252},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x0000a49b,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x0000a49b,0x00009493,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000124db,0x00009493,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000124db,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000124db,0x000124db,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x000124db,
+    0x000126dc,0x000126dc,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x000124db,
+    0x000136e4,0x000126dc,0x000124db,0x0000a49b},
+   {0x00000000,0x00000000,0x00009292,0x000124db,
+    0x000136e4,0x000126dc,0x000124db,0x0000a49b},
+   {0x00000000,0x00000000,0x00009492,0x000126db,
+    0x0001b724,0x000136e4,0x000126dc,0x000124db},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  }
+ },
+ { /* version 6 */
+  { /* version 6, passes 0 */
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x00009292,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x0000a493,0x0000a49b,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x000124db,0x000124db,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x000126dc,0x000126dc,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000126dc,0x000136e4,0x000124db},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000126dc,0x000136e4,0x000136e4,0x000126dc},
+   {0x00000000,0x00000000,0x00009292,0x0000a49b,
+    0x000126dc,0x0001b724,0x0001b725,0x000126dc},
+   {0x00000000,0x00000000,0x00009292,0x0000a49b,
+    0x000136e4,0x0001b724,0x0001b92d,0x000136e4},
+   {0x00000000,0x00000000,0x00009492,0x0000a49b,
+    0x000136e4,0x0001b724,0x0001b92d,0x0001b724},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x000136e4,0x0001b724,0x0001b92d,0x0001b724},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x000136e4,0x0001b925,0x0001b92d,0x0001b925},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x0001b724,0x0001b925,0x0001c96e,0x0001c92d},
+   {0x00000000,0x00000000,0x0000a492,0x000124db,
+    0x0001b724,0x0001c92d,0x0001c96e,0x0001c92d},
+   {0x00000000,0x00000000,0x0000a492,0x000124db,
+    0x0001b724,0x0001c92d,0x00024b76,0x0002496e},
+   {0x00000000,0x00000000,0x00012492,0x000126db,
+    0x0001c924,0x00024b6d,0x0002ddb6,0x00025bbf},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  },
+  { /* version 6, passes 1 */
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x0000124a,0x0000124a,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x00009492,0x00009252,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x00009292,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x0000a49b,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x0000a49b,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x0000a49b,0x00009493,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000124db,0x000124db,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000124db,0x000124db,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x000124db,
+    0x000126dc,0x000124db,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x000124db,
+    0x000126dc,0x000126dc,0x0000a49b,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x000124db,
+    0x000136e4,0x000126dc,0x000124db,0x0000a49b},
+   {0x00000000,0x00000000,0x00009492,0x000126db,
+    0x000136e4,0x000126dc,0x000124db,0x0000a49b},
+   {0x00000000,0x00000000,0x00009492,0x000126db,
+    0x0001b724,0x000136e4,0x000126dc,0x000124db},
+   {0x00000000,0x00000000,0x00009492,0x000126db,
+    0x0001b724,0x000136e4,0x000126dc,0x000124db},
+   {0x00000000,0x00000000,0x0000a492,0x000136db,
+    0x0001c924,0x0001b724,0x000136e4,0x000126dc},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  }
+ },
+ { /* version 7 */
+  { /* version 7, passes 0 */
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x00009292,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x000124db,0x000126dc,0x00009493},
+   {0x00000000,0x00000000,0x00001249,0x0000a49b,
+    0x0001249b,0x000126dc,0x000126dc,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000126dc,0x000136e4,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000126dc,0x000136e4,0x0001b725,0x000124db},
+   {0x00000000,0x00000000,0x00009292,0x0000a49b,
+    0x000136e4,0x0001b724,0x0001b725,0x000126dc},
+   {0x00000000,0x00000000,0x00009292,0x000124db,
+    0x000136e4,0x0001b724,0x0001b725,0x000126dc},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x000136e4,0x0001b724,0x0001c96e,0x000136e4},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x000136e4,0x0001c92d,0x0001c96e,0x0001b724},
+   {0x00000000,0x00000000,0x0000a492,0x000124db,
+    0x000136e4,0x0001c92d,0x0001c96e,0x0001b724},
+   {0x00000000,0x00000000,0x0000a492,0x000124db,
+    0x0001b724,0x0001c92d,0x0001c96e,0x0001b925},
+   {0x00000000,0x00000000,0x0000a492,0x000126db,
+    0x0001b724,0x0001c92d,0x00024b76,0x0001c92d},
+   {0x00000000,0x00000000,0x0000a492,0x000126db,
+    0x0001b924,0x0001c92d,0x00024b76,0x0001c92d},
+   {0x00000000,0x00000000,0x0000a492,0x000126db,
+    0x0001b924,0x0001c92d,0x00024b76,0x0002496e},
+   {0x00000000,0x00000000,0x00012492,0x000136db,
+    0x00024924,0x00024b6d,0x0002ddb6,0x00025bbf},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  },
+  { /* version 7, passes 1 */
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x0000124a,0x0000124a,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x00009492,0x00009292,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x0000a49b,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x0000a49b,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x0000a49b,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000126dc,0x0000a49b,0x00009493,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x000124db,
+    0x000126dc,0x000124db,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x000124db,
+    0x000136e4,0x000124db,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x000136db,
+    0x0001b724,0x000124db,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x000136db,
+    0x0001b724,0x000126dc,0x0000a49b,0x0000a49b},
+   {0x00000000,0x00000000,0x00009292,0x000136db,
+    0x0001b724,0x000126dc,0x000124db,0x0000a49b},
+   {0x00000000,0x00000000,0x00009492,0x000136db,
+    0x0001b724,0x000126dc,0x000124db,0x0000a49b},
+   {0x00000000,0x00000000,0x0000a492,0x000136db,
+    0x0001b724,0x000136e4,0x000126dc,0x000124db},
+   {0x00000000,0x00000000,0x0000a492,0x000136db,
+    0x0001b724,0x000136e4,0x000126dc,0x000124db},
+   {0x00000000,0x00000000,0x00012492,0x0001b6db,
+    0x0001c924,0x0001b724,0x000136e4,0x000126dc},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  }
+ }
+};
+
diff --git a/drivers/media/usb/pwc/pwc-kiara.h b/drivers/media/usb/pwc/pwc-kiara.h
new file mode 100644
index 0000000..8e02b7a
--- /dev/null
+++ b/drivers/media/usb/pwc/pwc-kiara.h
@@ -0,0 +1,48 @@
+/* Linux driver for Philips webcam
+   (C) 2004-2006 Luc Saillard (luc@saillard.org)
+
+   NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx
+   driver and thus may have bugs that are not present in the original version.
+   Please send bug reports and support requests to <luc@saillard.org>.
+   The decompression routines have been implemented by reverse-engineering the
+   Nemosoft binary pwcx module. Caveat emptor.
+
+   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
+*/
+
+/* Entries for the Kiara (730/740/750) camera */
+
+#ifndef PWC_KIARA_H
+#define PWC_KIARA_H
+
+#include "pwc.h"
+
+#define PWC_FPS_MAX_KIARA 6
+
+struct Kiara_table_entry
+{
+	char alternate;			/* USB alternate interface */
+	unsigned short packetsize;	/* Normal packet size */
+	unsigned short bandlength;	/* Bandlength when decompressing */
+	unsigned char mode[12];		/* precomputed mode settings for cam */
+};
+
+extern const struct Kiara_table_entry Kiara_table[PSZ_MAX][PWC_FPS_MAX_KIARA][4];
+extern const unsigned int KiaraRomTable[8][2][16][8];
+extern const unsigned int Kiara_fps_vector[PWC_FPS_MAX_KIARA];
+
+#endif
+
+
diff --git a/drivers/media/usb/pwc/pwc-misc.c b/drivers/media/usb/pwc/pwc-misc.c
new file mode 100644
index 0000000..9be5adf
--- /dev/null
+++ b/drivers/media/usb/pwc/pwc-misc.c
@@ -0,0 +1,93 @@
+/* Linux driver for Philips webcam
+   Various miscellaneous functions and tables.
+   (C) 1999-2003 Nemosoft Unv.
+   (C) 2004-2006 Luc Saillard (luc@saillard.org)
+
+   NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx
+   driver and thus may have bugs that are not present in the original version.
+   Please send bug reports and support requests to <luc@saillard.org>.
+   The decompression routines have been implemented by reverse-engineering the
+   Nemosoft binary pwcx module. Caveat emptor.
+
+   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 "pwc.h"
+
+const int pwc_image_sizes[PSZ_MAX][2] =
+{
+	{ 128,  96 }, /* sqcif */
+	{ 160, 120 }, /* qsif */
+	{ 176, 144 }, /* qcif */
+	{ 320, 240 }, /* sif */
+	{ 352, 288 }, /* cif */
+	{ 640, 480 }, /* vga */
+};
+
+/* x,y -> PSZ_ */
+int pwc_get_size(struct pwc_device *pdev, int width, int height)
+{
+	int i;
+
+	/* Find the largest size supported by the camera that fits into the
+	   requested size. */
+	for (i = PSZ_MAX - 1; i >= 0; i--) {
+		if (!(pdev->image_mask & (1 << i)))
+			continue;
+
+		if (pwc_image_sizes[i][0] <= width &&
+		    pwc_image_sizes[i][1] <= height)
+			return i;
+	}
+
+	/* No mode found, return the smallest mode we have */
+	for (i = 0; i < PSZ_MAX; i++) {
+		if (pdev->image_mask & (1 << i))
+			return i;
+	}
+
+	/* Never reached there always is atleast one supported mode */
+	return 0;
+}
+
+/* initialize variables depending on type and decompressor */
+void pwc_construct(struct pwc_device *pdev)
+{
+	if (DEVICE_USE_CODEC1(pdev->type)) {
+
+		pdev->image_mask = 1 << PSZ_SQCIF | 1 << PSZ_QCIF | 1 << PSZ_CIF;
+		pdev->vcinterface = 2;
+		pdev->vendpoint = 4;
+		pdev->frame_header_size = 0;
+		pdev->frame_trailer_size = 0;
+
+	} else if (DEVICE_USE_CODEC3(pdev->type)) {
+
+		pdev->image_mask = 1 << PSZ_QSIF | 1 << PSZ_SIF | 1 << PSZ_VGA;
+		pdev->vcinterface = 3;
+		pdev->vendpoint = 5;
+		pdev->frame_header_size = TOUCAM_HEADER_SIZE;
+		pdev->frame_trailer_size = TOUCAM_TRAILER_SIZE;
+
+	} else /* if (DEVICE_USE_CODEC2(pdev->type)) */ {
+
+		pdev->image_mask = 1 << PSZ_SQCIF | 1 << PSZ_QSIF | 1 << PSZ_QCIF | 1 << PSZ_SIF | 1 << PSZ_CIF | 1 << PSZ_VGA;
+		pdev->vcinterface = 3;
+		pdev->vendpoint = 4;
+		pdev->frame_header_size = 0;
+		pdev->frame_trailer_size = 0;
+	}
+}
diff --git a/drivers/media/usb/pwc/pwc-nala.h b/drivers/media/usb/pwc/pwc-nala.h
new file mode 100644
index 0000000..0fe9d47
--- /dev/null
+++ b/drivers/media/usb/pwc/pwc-nala.h
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+   /* SQCIF */
+   {
+      {0, 0, {0x04, 0x01, 0x03}},
+      {8, 0, {0x05, 0x01, 0x03}},
+      {7, 0, {0x08, 0x01, 0x03}},
+      {7, 0, {0x0A, 0x01, 0x03}},
+      {6, 0, {0x0C, 0x01, 0x03}},
+      {5, 0, {0x0F, 0x01, 0x03}},
+      {4, 0, {0x14, 0x01, 0x03}},
+      {3, 0, {0x18, 0x01, 0x03}},
+   },
+   /* QSIF */
+   {
+      {0},
+      {0},
+      {0},
+      {0},
+      {0},
+      {0},
+      {0},
+      {0},
+   },
+   /* QCIF */
+   {
+      {0, 0, {0x04, 0x01, 0x02}},
+      {8, 0, {0x05, 0x01, 0x02}},
+      {7, 0, {0x08, 0x01, 0x02}},
+      {6, 0, {0x0A, 0x01, 0x02}},
+      {5, 0, {0x0C, 0x01, 0x02}},
+      {4, 0, {0x0F, 0x01, 0x02}},
+      {1, 0, {0x14, 0x01, 0x02}},
+      {1, 0, {0x18, 0x01, 0x02}},
+   },
+   /* SIF */
+   {
+      {0},
+      {0},
+      {0},
+      {0},
+      {0},
+      {0},
+      {0},
+      {0},
+   },
+   /* CIF */
+   {
+      {4, 0, {0x04, 0x01, 0x01}},
+      {7, 1, {0x05, 0x03, 0x01}},
+      {6, 1, {0x08, 0x03, 0x01}},
+      {4, 1, {0x0A, 0x03, 0x01}},
+      {3, 1, {0x0C, 0x03, 0x01}},
+      {2, 1, {0x0F, 0x03, 0x01}},
+      {0},
+      {0},
+   },
+   /* VGA */
+   {
+      {0},
+      {0},
+      {0},
+      {0},
+      {0},
+      {0},
+      {0},
+      {0},
+   },
diff --git a/drivers/media/usb/pwc/pwc-timon.c b/drivers/media/usb/pwc/pwc-timon.c
new file mode 100644
index 0000000..c56c174
--- /dev/null
+++ b/drivers/media/usb/pwc/pwc-timon.c
@@ -0,0 +1,1448 @@
+/* Linux driver for Philips webcam
+   (C) 2004-2006 Luc Saillard (luc@saillard.org)
+
+   NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx
+   driver and thus may have bugs that are not present in the original version.
+   Please send bug reports and support requests to <luc@saillard.org>.
+   The decompression routines have been implemented by reverse-engineering the
+   Nemosoft binary pwcx module. Caveat emptor.
+
+   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
+*/
+
+
+/* This tables contains entries for the 675/680/690 (Timon) camera, with
+   4 different qualities (no compression, low, medium, high).
+   It lists the bandwidth requirements for said mode by its alternate interface
+   number. An alternate of 0 means that the mode is unavailable.
+
+   There are 6 * 4 * 4 entries:
+     6 different resolutions subqcif, qsif, qcif, sif, cif, vga
+     6 framerates: 5, 10, 15, 20, 25, 30
+     4 compression modi: none, low, medium, high
+
+   When an uncompressed mode is not available, the next available compressed mode
+   will be chosen (unless the decompressor is absent). Sometimes there are only
+   1 or 2 compressed modes available; in that case entries are duplicated.
+*/
+
+#include "pwc-timon.h"
+
+const unsigned int Timon_fps_vector[PWC_FPS_MAX_TIMON] = { 5, 10, 15, 20, 25, 30 };
+
+const struct Timon_table_entry Timon_table[PSZ_MAX][PWC_FPS_MAX_TIMON][4] =
+{
+   /* SQCIF */
+   {
+      /* 5 fps */
+      {
+	 {1, 140,    0, {0x05, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x8C, 0xFC, 0x80, 0x02}},
+	 {1, 140,    0, {0x05, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x8C, 0xFC, 0x80, 0x02}},
+	 {1, 140,    0, {0x05, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x8C, 0xFC, 0x80, 0x02}},
+	 {1, 140,    0, {0x05, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x8C, 0xFC, 0x80, 0x02}},
+      },
+      /* 10 fps */
+      {
+	 {2, 280,    0, {0x04, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x18, 0xA9, 0x80, 0x02}},
+	 {2, 280,    0, {0x04, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x18, 0xA9, 0x80, 0x02}},
+	 {2, 280,    0, {0x04, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x18, 0xA9, 0x80, 0x02}},
+	 {2, 280,    0, {0x04, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x18, 0xA9, 0x80, 0x02}},
+      },
+      /* 15 fps */
+      {
+	 {3, 410,    0, {0x03, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x9A, 0x71, 0x80, 0x02}},
+	 {3, 410,    0, {0x03, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x9A, 0x71, 0x80, 0x02}},
+	 {3, 410,    0, {0x03, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x9A, 0x71, 0x80, 0x02}},
+	 {3, 410,    0, {0x03, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x9A, 0x71, 0x80, 0x02}},
+      },
+      /* 20 fps */
+      {
+	 {4, 559,    0, {0x02, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x2F, 0x56, 0x80, 0x02}},
+	 {4, 559,    0, {0x02, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x2F, 0x56, 0x80, 0x02}},
+	 {4, 559,    0, {0x02, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x2F, 0x56, 0x80, 0x02}},
+	 {4, 559,    0, {0x02, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x2F, 0x56, 0x80, 0x02}},
+      },
+      /* 25 fps */
+      {
+	 {5, 659,    0, {0x01, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x93, 0x46, 0x80, 0x02}},
+	 {5, 659,    0, {0x01, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x93, 0x46, 0x80, 0x02}},
+	 {5, 659,    0, {0x01, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x93, 0x46, 0x80, 0x02}},
+	 {5, 659,    0, {0x01, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x93, 0x46, 0x80, 0x02}},
+      },
+      /* 30 fps */
+      {
+	 {7, 838,    0, {0x00, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x46, 0x3B, 0x80, 0x02}},
+	 {7, 838,    0, {0x00, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x46, 0x3B, 0x80, 0x02}},
+	 {7, 838,    0, {0x00, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x46, 0x3B, 0x80, 0x02}},
+	 {7, 838,    0, {0x00, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x46, 0x3B, 0x80, 0x02}},
+      },
+   },
+   /* QSIF */
+   {
+      /* 5 fps */
+      {
+	 {1, 146,    0, {0x2D, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x92, 0xFC, 0xC0, 0x02}},
+	 {1, 146,    0, {0x2D, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x92, 0xFC, 0xC0, 0x02}},
+	 {1, 146,    0, {0x2D, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x92, 0xFC, 0xC0, 0x02}},
+	 {1, 146,    0, {0x2D, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x92, 0xFC, 0xC0, 0x02}},
+      },
+      /* 10 fps */
+      {
+	 {2, 291,    0, {0x2C, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x23, 0xA1, 0xC0, 0x02}},
+	 {1, 191,  630, {0x2C, 0xF4, 0x05, 0x13, 0xA9, 0x12, 0xE1, 0x17, 0x08, 0xBF, 0xF4, 0xC0, 0x02}},
+	 {1, 191,  630, {0x2C, 0xF4, 0x05, 0x13, 0xA9, 0x12, 0xE1, 0x17, 0x08, 0xBF, 0xF4, 0xC0, 0x02}},
+	 {1, 191,  630, {0x2C, 0xF4, 0x05, 0x13, 0xA9, 0x12, 0xE1, 0x17, 0x08, 0xBF, 0xF4, 0xC0, 0x02}},
+      },
+      /* 15 fps */
+      {
+	 {3, 437,    0, {0x2B, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0xB5, 0x6D, 0xC0, 0x02}},
+	 {2, 291,  640, {0x2B, 0xF4, 0x05, 0x13, 0xF7, 0x13, 0x2F, 0x13, 0x08, 0x23, 0xA1, 0xC0, 0x02}},
+	 {2, 291,  640, {0x2B, 0xF4, 0x05, 0x13, 0xF7, 0x13, 0x2F, 0x13, 0x08, 0x23, 0xA1, 0xC0, 0x02}},
+	 {1, 191,  420, {0x2B, 0xF4, 0x0D, 0x0D, 0x1B, 0x0C, 0x53, 0x1E, 0x08, 0xBF, 0xF4, 0xC0, 0x02}},
+      },
+      /* 20 fps */
+      {
+	 {4, 588,    0, {0x2A, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x4C, 0x52, 0xC0, 0x02}},
+	 {3, 447,  730, {0x2A, 0xF4, 0x05, 0x16, 0xC9, 0x16, 0x01, 0x0E, 0x18, 0xBF, 0x69, 0xC0, 0x02}},
+	 {2, 292,  476, {0x2A, 0xF4, 0x0D, 0x0E, 0xD8, 0x0E, 0x10, 0x19, 0x18, 0x24, 0xA1, 0xC0, 0x02}},
+	 {1, 192,  312, {0x2A, 0xF4, 0x1D, 0x09, 0xB3, 0x08, 0xEB, 0x1E, 0x18, 0xC0, 0xF4, 0xC0, 0x02}},
+      },
+      /* 25 fps */
+      {
+	 {5, 703,    0, {0x29, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0xBF, 0x42, 0xC0, 0x02}},
+	 {3, 447,  610, {0x29, 0xF4, 0x05, 0x13, 0x0B, 0x12, 0x43, 0x14, 0x18, 0xBF, 0x69, 0xC0, 0x02}},
+	 {2, 292,  398, {0x29, 0xF4, 0x0D, 0x0C, 0x6C, 0x0B, 0xA4, 0x1E, 0x18, 0x24, 0xA1, 0xC0, 0x02}},
+	 {1, 192,  262, {0x29, 0xF4, 0x25, 0x08, 0x23, 0x07, 0x5B, 0x1E, 0x18, 0xC0, 0xF4, 0xC0, 0x02}},
+      },
+      /* 30 fps */
+      {
+	 {8, 873,    0, {0x28, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x69, 0x37, 0xC0, 0x02}},
+	 {5, 704,  774, {0x28, 0xF4, 0x05, 0x18, 0x21, 0x17, 0x59, 0x0F, 0x18, 0xC0, 0x42, 0xC0, 0x02}},
+	 {3, 448,  492, {0x28, 0xF4, 0x05, 0x0F, 0x5D, 0x0E, 0x95, 0x15, 0x18, 0xC0, 0x69, 0xC0, 0x02}},
+	 {2, 291,  320, {0x28, 0xF4, 0x1D, 0x09, 0xFB, 0x09, 0x33, 0x1E, 0x18, 0x23, 0xA1, 0xC0, 0x02}},
+      },
+   },
+   /* QCIF */
+   {
+      /* 5 fps */
+      {
+	 {1, 193,    0, {0x0D, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0xC1, 0xF4, 0xC0, 0x02}},
+	 {1, 193,    0, {0x0D, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0xC1, 0xF4, 0xC0, 0x02}},
+	 {1, 193,    0, {0x0D, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0xC1, 0xF4, 0xC0, 0x02}},
+	 {1, 193,    0, {0x0D, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0xC1, 0xF4, 0xC0, 0x02}},
+      },
+      /* 10 fps */
+      {
+	 {3, 385,    0, {0x0C, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x81, 0x79, 0xC0, 0x02}},
+	 {2, 291,  800, {0x0C, 0xF4, 0x05, 0x18, 0xF4, 0x18, 0x18, 0x11, 0x08, 0x23, 0xA1, 0xC0, 0x02}},
+	 {2, 291,  800, {0x0C, 0xF4, 0x05, 0x18, 0xF4, 0x18, 0x18, 0x11, 0x08, 0x23, 0xA1, 0xC0, 0x02}},
+	 {1, 194,  532, {0x0C, 0xF4, 0x05, 0x10, 0x9A, 0x0F, 0xBE, 0x1B, 0x08, 0xC2, 0xF0, 0xC0, 0x02}},
+      },
+      /* 15 fps */
+      {
+	 {4, 577,    0, {0x0B, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x41, 0x52, 0xC0, 0x02}},
+	 {3, 447,  818, {0x0B, 0xF4, 0x05, 0x19, 0x89, 0x18, 0xAD, 0x0F, 0x10, 0xBF, 0x69, 0xC0, 0x02}},
+	 {2, 292,  534, {0x0B, 0xF4, 0x05, 0x10, 0xA3, 0x0F, 0xC7, 0x19, 0x10, 0x24, 0xA1, 0xC0, 0x02}},
+	 {1, 195,  356, {0x0B, 0xF4, 0x15, 0x0B, 0x11, 0x0A, 0x35, 0x1E, 0x10, 0xC3, 0xF0, 0xC0, 0x02}},
+      },
+      /* 20 fps */
+      {
+	 {6, 776,    0, {0x0A, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x08, 0x3F, 0xC0, 0x02}},
+	 {4, 591,  804, {0x0A, 0xF4, 0x05, 0x19, 0x1E, 0x18, 0x42, 0x0F, 0x18, 0x4F, 0x4E, 0xC0, 0x02}},
+	 {3, 447,  608, {0x0A, 0xF4, 0x05, 0x12, 0xFD, 0x12, 0x21, 0x15, 0x18, 0xBF, 0x69, 0xC0, 0x02}},
+	 {2, 291,  396, {0x0A, 0xF4, 0x15, 0x0C, 0x5E, 0x0B, 0x82, 0x1E, 0x18, 0x23, 0xA1, 0xC0, 0x02}},
+      },
+      /* 25 fps */
+      {
+	 {9, 928,    0, {0x09, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0xA0, 0x33, 0xC0, 0x02}},
+	 {5, 703,  800, {0x09, 0xF4, 0x05, 0x18, 0xF4, 0x18, 0x18, 0x10, 0x18, 0xBF, 0x42, 0xC0, 0x02}},
+	 {3, 447,  508, {0x09, 0xF4, 0x0D, 0x0F, 0xD2, 0x0E, 0xF6, 0x1B, 0x18, 0xBF, 0x69, 0xC0, 0x02}},
+	 {2, 292,  332, {0x09, 0xF4, 0x1D, 0x0A, 0x5A, 0x09, 0x7E, 0x1E, 0x18, 0x24, 0xA1, 0xC0, 0x02}},
+      },
+      /* 30 fps */
+      {
+	 {0, },
+	 {9, 956,  876, {0x08, 0xF4, 0x05, 0x1B, 0x58, 0x1A, 0x7C, 0x0E, 0x20, 0xBC, 0x33, 0x10, 0x02}},
+	 {4, 592,  542, {0x08, 0xF4, 0x05, 0x10, 0xE4, 0x10, 0x08, 0x17, 0x20, 0x50, 0x4E, 0x10, 0x02}},
+	 {2, 291,  266, {0x08, 0xF4, 0x25, 0x08, 0x48, 0x07, 0x6C, 0x1E, 0x20, 0x23, 0xA1, 0x10, 0x02}},
+      },
+   },
+   /* SIF */
+   {
+      /* 5 fps */
+      {
+	 {4, 582,    0, {0x35, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x46, 0x52, 0x60, 0x02}},
+	 {3, 387, 1276, {0x35, 0xF4, 0x05, 0x27, 0xD8, 0x26, 0x48, 0x03, 0x10, 0x83, 0x79, 0x60, 0x02}},
+	 {2, 291,  960, {0x35, 0xF4, 0x0D, 0x1D, 0xF2, 0x1C, 0x62, 0x04, 0x10, 0x23, 0xA1, 0x60, 0x02}},
+	 {1, 191,  630, {0x35, 0xF4, 0x1D, 0x13, 0xA9, 0x12, 0x19, 0x05, 0x08, 0xBF, 0xF4, 0x60, 0x02}},
+      },
+      /* 10 fps */
+      {
+	 {0, },
+	 {6, 775, 1278, {0x34, 0xF4, 0x05, 0x27, 0xE8, 0x26, 0x58, 0x05, 0x30, 0x07, 0x3F, 0x10, 0x02}},
+	 {3, 447,  736, {0x34, 0xF4, 0x15, 0x16, 0xFB, 0x15, 0x6B, 0x05, 0x18, 0xBF, 0x69, 0x10, 0x02}},
+	 {2, 291,  480, {0x34, 0xF4, 0x2D, 0x0E, 0xF9, 0x0D, 0x69, 0x09, 0x18, 0x23, 0xA1, 0x10, 0x02}},
+      },
+      /* 15 fps */
+      {
+	 {0, },
+	 {9, 955, 1050, {0x33, 0xF4, 0x05, 0x20, 0xCF, 0x1F, 0x3F, 0x06, 0x48, 0xBB, 0x33, 0x10, 0x02}},
+	 {4, 591,  650, {0x33, 0xF4, 0x15, 0x14, 0x44, 0x12, 0xB4, 0x08, 0x30, 0x4F, 0x4E, 0x10, 0x02}},
+	 {3, 448,  492, {0x33, 0xF4, 0x25, 0x0F, 0x52, 0x0D, 0xC2, 0x09, 0x28, 0xC0, 0x69, 0x10, 0x02}},
+      },
+      /* 20 fps */
+      {
+	 {0, },
+	 {9, 958,  782, {0x32, 0xF4, 0x0D, 0x18, 0x6A, 0x16, 0xDA, 0x0B, 0x58, 0xBE, 0x33, 0xD0, 0x02}},
+	 {5, 703,  574, {0x32, 0xF4, 0x1D, 0x11, 0xE7, 0x10, 0x57, 0x0B, 0x40, 0xBF, 0x42, 0xD0, 0x02}},
+	 {3, 446,  364, {0x32, 0xF4, 0x3D, 0x0B, 0x5C, 0x09, 0xCC, 0x0E, 0x30, 0xBE, 0x69, 0xD0, 0x02}},
+      },
+      /* 25 fps */
+      {
+	 {0, },
+	 {9, 958,  654, {0x31, 0xF4, 0x15, 0x14, 0x66, 0x12, 0xD6, 0x0B, 0x50, 0xBE, 0x33, 0x90, 0x02}},
+	 {6, 776,  530, {0x31, 0xF4, 0x25, 0x10, 0x8C, 0x0E, 0xFC, 0x0C, 0x48, 0x08, 0x3F, 0x90, 0x02}},
+	 {4, 592,  404, {0x31, 0xF4, 0x35, 0x0C, 0x96, 0x0B, 0x06, 0x0B, 0x38, 0x50, 0x4E, 0x90, 0x02}},
+      },
+      /* 30 fps */
+      {
+	 {0, },
+	 {9, 957,  526, {0x30, 0xF4, 0x25, 0x10, 0x68, 0x0E, 0xD8, 0x0D, 0x58, 0xBD, 0x33, 0x60, 0x02}},
+	 {6, 775,  426, {0x30, 0xF4, 0x35, 0x0D, 0x48, 0x0B, 0xB8, 0x0F, 0x50, 0x07, 0x3F, 0x60, 0x02}},
+	 {4, 590,  324, {0x30, 0x7A, 0x4B, 0x0A, 0x1C, 0x08, 0xB4, 0x0E, 0x40, 0x4E, 0x52, 0x60, 0x02}},
+      },
+   },
+   /* CIF */
+   {
+      /* 5 fps */
+      {
+	 {6, 771,    0, {0x15, 0xF4, 0x04, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x03, 0x3F, 0x80, 0x02}},
+	 {4, 465, 1278, {0x15, 0xF4, 0x05, 0x27, 0xEE, 0x26, 0x36, 0x03, 0x18, 0xD1, 0x65, 0x80, 0x02}},
+	 {2, 291,  800, {0x15, 0xF4, 0x15, 0x18, 0xF4, 0x17, 0x3C, 0x05, 0x18, 0x23, 0xA1, 0x80, 0x02}},
+	 {1, 193,  528, {0x15, 0xF4, 0x2D, 0x10, 0x7E, 0x0E, 0xC6, 0x0A, 0x18, 0xC1, 0xF4, 0x80, 0x02}},
+      },
+      /* 10 fps */
+      {
+	 {0, },
+	 {9, 932, 1278, {0x14, 0xF4, 0x05, 0x27, 0xEE, 0x26, 0x36, 0x04, 0x30, 0xA4, 0x33, 0x10, 0x02}},
+	 {4, 591,  812, {0x14, 0xF4, 0x15, 0x19, 0x56, 0x17, 0x9E, 0x06, 0x28, 0x4F, 0x4E, 0x10, 0x02}},
+	 {2, 291,  400, {0x14, 0xF4, 0x3D, 0x0C, 0x7A, 0x0A, 0xC2, 0x0E, 0x28, 0x23, 0xA1, 0x10, 0x02}},
+      },
+      /* 15 fps */
+      {
+	 {0, },
+	 {9, 956,  876, {0x13, 0xF4, 0x0D, 0x1B, 0x58, 0x19, 0xA0, 0x05, 0x38, 0xBC, 0x33, 0x60, 0x02}},
+	 {5, 703,  644, {0x13, 0xF4, 0x1D, 0x14, 0x1C, 0x12, 0x64, 0x08, 0x38, 0xBF, 0x42, 0x60, 0x02}},
+	 {3, 448,  410, {0x13, 0xF4, 0x3D, 0x0C, 0xC4, 0x0B, 0x0C, 0x0E, 0x38, 0xC0, 0x69, 0x60, 0x02}},
+      },
+      /* 20 fps */
+      {
+	 {0, },
+	 {9, 956,  650, {0x12, 0xF4, 0x1D, 0x14, 0x4A, 0x12, 0x92, 0x09, 0x48, 0xBC, 0x33, 0x10, 0x03}},
+	 {6, 776,  528, {0x12, 0xF4, 0x2D, 0x10, 0x7E, 0x0E, 0xC6, 0x0A, 0x40, 0x08, 0x3F, 0x10, 0x03}},
+	 {4, 591,  402, {0x12, 0xF4, 0x3D, 0x0C, 0x8F, 0x0A, 0xD7, 0x0E, 0x40, 0x4F, 0x4E, 0x10, 0x03}},
+      },
+      /* 25 fps */
+      {
+	 {0, },
+	 {9, 956,  544, {0x11, 0xF4, 0x25, 0x10, 0xF4, 0x0F, 0x3C, 0x0A, 0x48, 0xBC, 0x33, 0xC0, 0x02}},
+	 {7, 840,  478, {0x11, 0xF4, 0x2D, 0x0E, 0xEB, 0x0D, 0x33, 0x0B, 0x48, 0x48, 0x3B, 0xC0, 0x02}},
+	 {5, 703,  400, {0x11, 0xF4, 0x3D, 0x0C, 0x7A, 0x0A, 0xC2, 0x0E, 0x48, 0xBF, 0x42, 0xC0, 0x02}},
+      },
+      /* 30 fps */
+      {
+	 {0, },
+	 {9, 956,  438, {0x10, 0xF4, 0x35, 0x0D, 0xAC, 0x0B, 0xF4, 0x0D, 0x50, 0xBC, 0x33, 0x10, 0x02}},
+	 {7, 838,  384, {0x10, 0xF4, 0x45, 0x0B, 0xFD, 0x0A, 0x45, 0x0F, 0x50, 0x46, 0x3B, 0x10, 0x02}},
+	 {6, 773,  354, {0x10, 0x7A, 0x4B, 0x0B, 0x0C, 0x09, 0x80, 0x10, 0x50, 0x05, 0x3F, 0x10, 0x02}},
+      },
+   },
+   /* VGA */
+   {
+      /* 5 fps */
+      {
+	 {0, },
+	 {6, 773, 1272, {0x1D, 0xF4, 0x15, 0x27, 0xB6, 0x24, 0x96, 0x02, 0x30, 0x05, 0x3F, 0x10, 0x02}},
+	 {4, 592,  976, {0x1D, 0xF4, 0x25, 0x1E, 0x78, 0x1B, 0x58, 0x03, 0x30, 0x50, 0x4E, 0x10, 0x02}},
+	 {3, 448,  738, {0x1D, 0xF4, 0x3D, 0x17, 0x0C, 0x13, 0xEC, 0x04, 0x30, 0xC0, 0x69, 0x10, 0x02}},
+      },
+      /* 10 fps */
+      {
+	 {0, },
+	 {9, 956,  788, {0x1C, 0xF4, 0x35, 0x18, 0x9C, 0x15, 0x7C, 0x03, 0x48, 0xBC, 0x33, 0x10, 0x02}},
+	 {6, 776,  640, {0x1C, 0x7A, 0x53, 0x13, 0xFC, 0x11, 0x2C, 0x04, 0x48, 0x08, 0x3F, 0x10, 0x02}},
+	 {4, 592,  488, {0x1C, 0x7A, 0x6B, 0x0F, 0x3C, 0x0C, 0x6C, 0x06, 0x48, 0x50, 0x4E, 0x10, 0x02}},
+      },
+      /* 15 fps */
+      {
+	 {0, },
+	 {9, 957,  526, {0x1B, 0x7A, 0x63, 0x10, 0x68, 0x0D, 0x98, 0x06, 0x58, 0xBD, 0x33, 0x80, 0x02}},
+	 {9, 957,  526, {0x1B, 0x7A, 0x63, 0x10, 0x68, 0x0D, 0x98, 0x06, 0x58, 0xBD, 0x33, 0x80, 0x02}},
+	 {8, 895,  492, {0x1B, 0x7A, 0x6B, 0x0F, 0x5D, 0x0C, 0x8D, 0x06, 0x58, 0x7F, 0x37, 0x80, 0x02}},
+      },
+      /* 20 fps */
+      {
+	 {0, },
+	 {0, },
+	 {0, },
+	 {0, },
+      },
+      /* 25 fps */
+      {
+	 {0, },
+	 {0, },
+	 {0, },
+	 {0, },
+      },
+      /* 30 fps */
+      {
+	 {0, },
+	 {0, },
+	 {0, },
+	 {0, },
+      },
+   },
+};
+
+/*
+ * 16 versions:
+ *   2 tables  (one for Y, and one for U&V)
+ *   16 levels of details per tables
+ *   8 blocs
+ */
+
+const unsigned int TimonRomTable [16][2][16][8] =
+{
+ { /* version 0 */
+  { /* version 0, passes 0 */
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000001},
+   {0x00000000,0x00000000,0x00000001,0x00000001,
+    0x00000001,0x00000001,0x00000001,0x00000001},
+   {0x00000000,0x00000000,0x00000001,0x00000001,
+    0x00000001,0x00000009,0x00000009,0x00000009},
+   {0x00000000,0x00000000,0x00000009,0x00000001,
+    0x00000009,0x00000009,0x00000009,0x00000009},
+   {0x00000000,0x00000000,0x00000009,0x00000009,
+    0x00000009,0x00000009,0x00000049,0x00000009},
+   {0x00000000,0x00000000,0x00000009,0x00000009,
+    0x00000009,0x00000049,0x00000049,0x00000049},
+   {0x00000000,0x00000000,0x00000009,0x00000009,
+    0x00000049,0x00000049,0x00000049,0x00000049},
+   {0x00000000,0x00000000,0x00000009,0x00000049,
+    0x00000049,0x00000049,0x00000049,0x00000049},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000049,0x0000024a,0x0000024a},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000249,0x0000024a,0x0000024a},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000249,0x00000249,0x0000024a,0x0000024a},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000249,0x00000249,0x00001252,0x0000024a},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000249,0x0000124a,0x00001252,0x0000024a},
+   {0x00000000,0x00000000,0x00000049,0x00000249,
+    0x00000249,0x0000124a,0x00001252,0x0000024a},
+   {0x00000000,0x00000000,0x00000249,0x00001249,
+    0x0000124a,0x00009252,0x00009292,0x00001252},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  },
+  { /* version 0, passes 1 */
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000},
+   {0x00000000,0x00000000,0x00000001,0x00000001,
+    0x00000001,0x00000001,0x00000000,0x00000000},
+   {0x00000000,0x00000000,0x00000009,0x00000001,
+    0x00000001,0x00000009,0x00000000,0x00000000},
+   {0x00000000,0x00000000,0x00000009,0x00000009,
+    0x00000009,0x00000009,0x00000000,0x00000000},
+   {0x00000000,0x00000000,0x00000009,0x00000009,
+    0x00000009,0x00000009,0x00000001,0x00000000},
+   {0x00000000,0x00000000,0x00000049,0x00000009,
+    0x00000009,0x00000049,0x00000001,0x00000001},
+   {0x00000000,0x00000000,0x00000049,0x00000009,
+    0x00000009,0x00000049,0x00000001,0x00000001},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000049,0x00000009,0x00000001},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000049,0x00000009,0x00000001},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000049,0x00000009,0x00000001},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000049,0x00000009,0x00000009},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000249,0x00000049,0x00000009},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000249,0x00000049,0x00000009},
+   {0x00000000,0x00000000,0x00000249,0x00000049,
+    0x00000249,0x00000249,0x00000049,0x00000009},
+   {0x00000000,0x00000000,0x00001249,0x00000249,
+    0x0000124a,0x0000124a,0x0000024a,0x00000049},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  }
+ },
+ { /* version 1 */
+  { /* version 1, passes 0 */
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000001},
+   {0x00000000,0x00000000,0x00000001,0x00000001,
+    0x00000001,0x00000009,0x00000009,0x00000009},
+   {0x00000000,0x00000000,0x00000009,0x00000009,
+    0x00000009,0x00000009,0x00000009,0x00000009},
+   {0x00000000,0x00000000,0x00000009,0x00000009,
+    0x00000009,0x00000049,0x00000049,0x00000049},
+   {0x00000000,0x00000000,0x00000009,0x00000049,
+    0x00000049,0x00000049,0x00000049,0x00000049},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000249,0x0000024a,0x0000024a},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000249,0x00000249,0x0000024a,0x0000024a},
+   {0x00000000,0x00000000,0x00000049,0x00000249,
+    0x00000249,0x00000249,0x0000024a,0x00001252},
+   {0x00000000,0x00000000,0x00000049,0x00000249,
+    0x00000249,0x0000124a,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x00000049,0x00000249,
+    0x0000124a,0x0000124a,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x0000124a,0x0000124a,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x00000249,0x00001249,
+    0x0000124a,0x00009252,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x00000249,0x00001249,
+    0x00009252,0x00009252,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x00000249,0x0000924a,
+    0x00009292,0x00009493,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x00001249,0x00009252,
+    0x00009492,0x0000a49b,0x0000a49b,0x0000a49b},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  },
+  { /* version 1, passes 1 */
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000},
+   {0x00000000,0x00000000,0x00000009,0x00000009,
+    0x00000009,0x00000001,0x00000001,0x00000000},
+   {0x00000000,0x00000000,0x00000009,0x00000009,
+    0x00000009,0x00000009,0x00000001,0x00000000},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000009,0x00000001,0x00000000},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000049,0x00000001,0x00000001},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000049,0x00000009,0x00000001},
+   {0x00000000,0x00000000,0x00000249,0x00000049,
+    0x00000049,0x00000249,0x00000009,0x00000001},
+   {0x00000000,0x00000000,0x00000249,0x00000049,
+    0x00000249,0x00000249,0x00000009,0x00000009},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00000249,0x00000249,0x00000049,0x00000009},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00000249,0x0000124a,0x00000049,0x00000009},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00000249,0x0000124a,0x00000049,0x00000009},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00000249,0x0000124a,0x0000024a,0x00000049},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x0000124a,0x0000124a,0x0000024a,0x00000049},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x0000124a,0x0000124a,0x0000024a,0x00000049},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x00009252,0x00001252,0x0000024a},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  }
+ },
+ { /* version 2 */
+  { /* version 2, passes 0 */
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000001},
+   {0x00000000,0x00000000,0x00000009,0x00000009,
+    0x00000009,0x00000009,0x00000009,0x00000009},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000049,0x00000049,0x00000049},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000249,0x0000024a,0x0000024a},
+   {0x00000000,0x00000000,0x00000049,0x00000249,
+    0x00000249,0x00000249,0x0000024a,0x00001252},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00000249,0x0000124a,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x0000124a,0x0000124a,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x00000249,0x00001249,
+    0x0000124a,0x00009252,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x00000249,0x00001249,
+    0x00009252,0x00009292,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x00000249,0x00001249,
+    0x00009252,0x00009292,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x00000249,0x0000924a,
+    0x00009252,0x00009493,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x00000249,0x0000924a,
+    0x00009292,0x00009493,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x00000249,0x00009252,
+    0x00009492,0x00009493,0x0000a49b,0x0000a49b},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x00009492,0x000124db,0x000124db,0x000124db},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x000126dc,0x000126dc,0x000126dc},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  },
+  { /* version 2, passes 1 */
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000},
+   {0x00000000,0x00000000,0x00000049,0x00000009,
+    0x00000049,0x00000009,0x00000001,0x00000000},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000049,0x00000049,0x00000000},
+   {0x00000000,0x00000000,0x00000249,0x00000049,
+    0x00000249,0x00000049,0x0000024a,0x00000001},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00000249,0x00000249,0x0000024a,0x00000001},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00000249,0x00000249,0x0000024a,0x00000001},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00000249,0x00000249,0x0000024a,0x00000009},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x0000124a,0x0000124a,0x0000024a,0x00000009},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x0000124a,0x0000124a,0x0000024a,0x00000009},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x0000124a,0x00009252,0x00001252,0x00000049},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x0000124a,0x00009292,0x00001252,0x00000049},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x0000124a,0x00009292,0x00001252,0x00000049},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x00009292,0x00001252,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009292,0x00009292,0x00001252,0x0000024a},
+   {0x00000000,0x00000000,0x0000924a,0x0000924a,
+    0x00009492,0x00009493,0x00009292,0x00001252},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  }
+ },
+ { /* version 3 */
+  { /* version 3, passes 0 */
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000001},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000049,0x00000049,0x00000049},
+   {0x00000000,0x00000000,0x00000049,0x00000249,
+    0x00000249,0x00000249,0x00001252,0x0000024a},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00000249,0x0000124a,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x0000124a,0x00009252,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x00000249,0x00001249,
+    0x0000124a,0x00009292,0x00009292,0x00009493},
+   {0x00000000,0x00000000,0x00000249,0x00001249,
+    0x00009252,0x00009292,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x00000249,0x00001249,
+    0x00009292,0x00009493,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x00000249,0x00009252,
+    0x00009292,0x00009493,0x0000a49b,0x0000a49b},
+   {0x00000000,0x00000000,0x00001249,0x00009252,
+    0x00009292,0x0000a49b,0x0000a49b,0x0000a49b},
+   {0x00000000,0x00000000,0x00001249,0x00009252,
+    0x00009492,0x0000a49b,0x0000a49b,0x0000a49b},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x00009492,0x0000a49b,0x000124db,0x000124db},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x0000a493,0x0000a49b,0x000124db,0x000124db},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0001249b,0x000126dc,0x000136e4,0x000126dc},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000124db,0x000136e4,0x0001b725,0x000136e4},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  },
+  { /* version 3, passes 1 */
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000},
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000049,0x00000001,0x00000000},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00000249,0x00000249,0x00000049,0x00000001},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00000249,0x0000124a,0x00001252,0x00000001},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x0000124a,0x0000124a,0x00001252,0x00000009},
+   {0x00000000,0x00000000,0x00000249,0x00001249,
+    0x0000124a,0x00009252,0x00009292,0x00000009},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x0000124a,0x00009252,0x00009292,0x00000049},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x00009252,0x00009292,0x00000049},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x00009493,0x00009292,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x00009493,0x00009292,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x00009493,0x00009493,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x0000924a,
+    0x00009292,0x00009493,0x00009493,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x0000924a,
+    0x00009492,0x00009493,0x00009493,0x00009292},
+   {0x00000000,0x00000000,0x00001249,0x00009252,
+    0x00009492,0x0000a49b,0x00009493,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x00009292,
+    0x0000a493,0x000124db,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  }
+ },
+ { /* version 4 */
+  { /* version 4, passes 0 */
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000049,0x0000024a,0x0000024a},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00000249,0x0000124a,0x00001252,0x00009292},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x0000124a,0x00009252,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x00000249,0x00001249,
+    0x0000124a,0x00009292,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x00000249,0x00001249,
+    0x00009252,0x00009493,0x00009493,0x0000a49b},
+   {0x00000000,0x00000000,0x00000249,0x0000924a,
+    0x00009292,0x00009493,0x0000a49b,0x0000a49b},
+   {0x00000000,0x00000000,0x00001249,0x0000924a,
+    0x00009292,0x00009493,0x0000a49b,0x000124db},
+   {0x00000000,0x00000000,0x00001249,0x00009252,
+    0x00009492,0x0000a49b,0x0000a49b,0x000124db},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x00009492,0x000124db,0x000124db,0x000126dc},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x0000a493,0x000124db,0x000126dc,0x000126dc},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x000124db,0x000126dc,0x000136e4},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x000126dc,0x000136e4,0x000136e4},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0001249b,0x000126dc,0x000136e4,0x000136e4},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000124db,0x000136e4,0x000136e4,0x0001b724},
+   {0x00000000,0x00000000,0x00009252,0x000124db,
+    0x000126dc,0x0001b724,0x0001b725,0x0001b925},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  },
+  { /* version 4, passes 1 */
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000049,0x00000049,0x00000049},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00000249,0x00000249,0x0000024a,0x00000049},
+   {0x00000000,0x00000000,0x00001249,0x00000249,
+    0x0000124a,0x0000124a,0x00001252,0x00000049},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x0000124a,0x0000124a,0x00009292,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x00009292,0x00009292,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x00009292,0x0000a49b,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009292,0x00009493,0x0000a49b,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009292,0x00009493,0x0000a49b,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x0000924a,
+    0x00009492,0x0000a49b,0x0000a49b,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x00009252,
+    0x00009492,0x0000a49b,0x0000a49b,0x00009292},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x00009492,0x0000a49b,0x0000a49b,0x00009292},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x0000a49b,0x0000a49b,0x00009292},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x0000a49b,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x000124db,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x00009252,0x0000a49b,
+    0x0001249b,0x000126dc,0x000124db,0x0000a49b},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  }
+ },
+ { /* version 5 */
+  { /* version 5, passes 0 */
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00000249,0x0000124a,0x00001252,0x00009292},
+   {0x00000000,0x00000000,0x00000249,0x00001249,
+    0x0000124a,0x00009292,0x00009292,0x00009493},
+   {0x00000000,0x00000000,0x00000249,0x0000924a,
+    0x00009292,0x00009493,0x0000a49b,0x0000a49b},
+   {0x00000000,0x00000000,0x00001249,0x0000924a,
+    0x00009292,0x00009493,0x0000a49b,0x0000a49b},
+   {0x00000000,0x00000000,0x00001249,0x0000924a,
+    0x00009492,0x0000a49b,0x0000a49b,0x000124db},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x00009492,0x0000a49b,0x000124db,0x000124db},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x0000a493,0x000124db,0x000124db,0x000126dc},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x000124db,0x000126dc,0x000126dc},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x000126dc,0x000136e4,0x000136e4},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0001249b,0x000126dc,0x000136e4,0x000136e4},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0001249b,0x000126dc,0x000136e4,0x000136e4},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0001249b,0x000126dc,0x0001b725,0x0001b724},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000124db,0x000126dc,0x0001b725,0x0001b724},
+   {0x00000000,0x00000000,0x00009292,0x0000a49b,
+    0x000126dc,0x000136e4,0x0001b92d,0x0001b925},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x000136e4,0x0001b724,0x0001c96e,0x0001c92d},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  },
+  { /* version 5, passes 1 */
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x0000124a,0x00000249,0x0000024a,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x0000124a,0x0000124a,0x00001252,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009292,0x00009493,0x00009493,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009292,0x00009493,0x00009493,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009292,0x00009493,0x0000a49b,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x0000924a,
+    0x00009492,0x00009493,0x000124db,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x00009492,0x00009493,0x000124db,0x00009292},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x00009492,0x0000a49b,0x000124db,0x00009292},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x0000a49b,0x000124db,0x00009292},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x000124db,0x000124db,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x000124db,0x000124db,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x000124db,0x000124db,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x000124db,0x000124db,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000124db,0x000126dc,0x000124db,0x0000a49b},
+   {0x00000000,0x00000000,0x00009252,0x000124db,
+    0x000126dc,0x000136e4,0x000126dc,0x000124db},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  }
+ },
+ { /* version 6 */
+  { /* version 6, passes 0 */
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x0000124a,0x0000124a,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009292,0x00009493,0x0000a49b,0x0000a49b},
+   {0x00000000,0x00000000,0x00001249,0x0000924a,
+    0x00009492,0x0000a49b,0x0000a49b,0x000124db},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x00009492,0x000124db,0x000126dc,0x000126dc},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x000124db,0x000126dc,0x000126dc},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x000126dc,0x000136e4,0x000136e4},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x000126dc,0x000136e4,0x0001b724},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0001249b,0x000126dc,0x000136e4,0x0001b724},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000126dc,0x000136e4,0x0001b724},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000136e4,0x0001b725,0x0001b724},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000124db,0x000136e4,0x0001b725,0x0001b925},
+   {0x00000000,0x00000000,0x00009292,0x0000a49b,
+    0x000126dc,0x000136e4,0x0001b92d,0x0001b925},
+   {0x00000000,0x00000000,0x00009292,0x0000a49b,
+    0x000126dc,0x0001b724,0x0001b92d,0x0001c92d},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x000126dc,0x0001b724,0x0001c96e,0x0001c92d},
+   {0x00000000,0x00000000,0x0000a492,0x000126db,
+    0x000136e4,0x0001b925,0x00025bb6,0x00024b77},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  },
+  { /* version 6, passes 1 */
+   {0x00000000,0x00000000,0x00001249,0x00000249,
+    0x0000124a,0x0000124a,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x00009292,0x00009292,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x0000924a,
+    0x00009492,0x00009493,0x0000a49b,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x00009252,
+    0x00009492,0x0000a49b,0x0000a49b,0x00009292},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x00009492,0x0000a49b,0x0000a49b,0x00009292},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x0000a49b,0x000126dc,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x0000a49b,0x000126dc,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x0000a49b,0x000126dc,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x000124db,0x000126dc,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x000124db,0x000126dc,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x000124db,0x000126dc,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000126dc,0x000126dc,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000124db,0x000136e4,0x000126dc,0x000124db},
+   {0x00000000,0x00000000,0x00009492,0x0000a49b,
+    0x000136e4,0x000136e4,0x000126dc,0x000124db},
+   {0x00000000,0x00000000,0x0000a492,0x000124db,
+    0x0001b724,0x0001b724,0x000136e4,0x000126dc},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  }
+ },
+ { /* version 7 */
+  { /* version 7, passes 0 */
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009292,0x00009493,0x0000a49b,0x000124db},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x0000a493,0x0000a49b,0x000124db,0x000126dc},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x000124db,0x000126dc,0x000136e4},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x000124db,0x000136e4,0x000136e4},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0001249b,0x000126dc,0x000136e4,0x000136e4},
+   {0x00000000,0x00000000,0x00001249,0x0000a49b,
+    0x0001249b,0x000126dc,0x000136e4,0x0001b724},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000126dc,0x000136e4,0x0001b724},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000124db,0x000136e4,0x0001b725,0x0001b724},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000126dc,0x000136e4,0x0001b725,0x0001b925},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000126dc,0x0001b724,0x0001b92d,0x0001b925},
+   {0x00000000,0x00000000,0x00009292,0x0000a49b,
+    0x000126dc,0x0001b724,0x0001c96e,0x0001c92d},
+   {0x00000000,0x00000000,0x00009292,0x000124db,
+    0x000126dc,0x0001b724,0x0001c96e,0x0001c92d},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x000136e4,0x0001b724,0x0001c96e,0x0002496e},
+   {0x00000000,0x00000000,0x00009492,0x000126db,
+    0x000136e4,0x0001b925,0x0001c96e,0x0002496e},
+   {0x00000000,0x00000000,0x0000a492,0x000136db,
+    0x0001b724,0x0002496d,0x00025bb6,0x00025bbf},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  },
+  { /* version 7, passes 1 */
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x00009292,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x00001249,0x0000924a,
+    0x00009492,0x00009493,0x00009493,0x00009292},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x0000a49b,0x0000a49b,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x0000a49b,0x000124db,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x000124db,0x000124db,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x000124db,0x000136e4,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x000124db,0x000136e4,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000124db,0x000136e4,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000126dc,0x000136e4,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000126dc,0x000136e4,0x000124db},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000126dc,0x000136e4,0x000136e4,0x000124db},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000126dc,0x000136e4,0x000136e4,0x000124db},
+   {0x00000000,0x00000000,0x0000924a,0x000124db,
+    0x000136e4,0x000136e4,0x000136e4,0x000126dc},
+   {0x00000000,0x00000000,0x0000a492,0x000124db,
+    0x000136e4,0x0001b724,0x000136e4,0x000126dc},
+   {0x00000000,0x00000000,0x00012492,0x000126db,
+    0x0001b724,0x0001b925,0x0001b725,0x000136e4},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  }
+ },
+ { /* version 8 */
+  { /* version 8, passes 0 */
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009292,0x00009493,0x0000a49b,0x000124db},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x0000a493,0x000124db,0x000126dc,0x000126dc},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x000124db,0x000126dc,0x000136e4},
+   {0x00000000,0x00000000,0x00001249,0x0000a49b,
+    0x0001249b,0x000126dc,0x000136e4,0x0001b724},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000126dc,0x000136e4,0x0001b724},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000124db,0x000136e4,0x0001b725,0x0001b724},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000126dc,0x000136e4,0x0001b725,0x0001b925},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000126dc,0x0001b724,0x0001b92d,0x0001c92d},
+   {0x00000000,0x00000000,0x00009252,0x000124db,
+    0x000126dc,0x0001b724,0x0001b92d,0x0001c92d},
+   {0x00000000,0x00000000,0x00009292,0x000124db,
+    0x000126dc,0x0001b925,0x0001c96e,0x0001c92d},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x000136e4,0x0001b925,0x0001c96e,0x0001c92d},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x000136e4,0x0001b925,0x00024b76,0x00024b77},
+   {0x00000000,0x00000000,0x00009492,0x000126db,
+    0x000136e4,0x0001b925,0x00024b76,0x00025bbf},
+   {0x00000000,0x00000000,0x0000a492,0x000126db,
+    0x000136e4,0x0001c92d,0x00024b76,0x00025bbf},
+   {0x00000000,0x00000000,0x00012492,0x000136db,
+    0x0001b724,0x00024b6d,0x0002ddb6,0x0002efff},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  },
+  { /* version 8, passes 1 */
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x00009493,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x0000a493,0x0000a49b,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x0000a49b,0x000124db,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x000124db,0x000126dc,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x000124db,0x000126dc,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x000124db,0x000136e4,0x000124db},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000126dc,0x000136e4,0x000124db},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000126dc,0x000126dc,0x000136e4,0x000126dc},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000126dc,0x000136e4,0x000136e4,0x000126dc},
+   {0x00000000,0x00000000,0x0000924a,0x000124db,
+    0x000126dc,0x000136e4,0x000136e4,0x000126dc},
+   {0x00000000,0x00000000,0x0000924a,0x000124db,
+    0x000126dc,0x000136e4,0x000136e4,0x000136e4},
+   {0x00000000,0x00000000,0x00009292,0x000124db,
+    0x000136e4,0x0001b724,0x0001b725,0x000136e4},
+   {0x00000000,0x00000000,0x00009492,0x000126db,
+    0x000136e4,0x0001b925,0x0001b725,0x0001b724},
+   {0x00000000,0x00000000,0x00009492,0x000126db,
+    0x000136e4,0x0001b925,0x0001b725,0x0001b724},
+   {0x00000000,0x00000000,0x0000a492,0x000136db,
+    0x0001b724,0x0002496d,0x0001b92d,0x0001b925},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  }
+ },
+ { /* version 9 */
+  { /* version 9, passes 0 */
+   {0x00000000,0x00000000,0x00000049,0x00000049,
+    0x00000049,0x00000049,0x00000049,0x00000049},
+   {0x00000000,0x00000000,0x00000249,0x00000049,
+    0x00000249,0x00000249,0x0000024a,0x00000049},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x0000124a,0x00009252,0x00001252,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x00009292,0x00009493,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x0000924a,
+    0x00009292,0x00009493,0x00009493,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x00009492,0x0000a49b,0x0000a49b,0x00009292},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x000124db,0x000124db,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x000124db,0x000126dc,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x000124db,0x000126dc,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0001249b,0x000126dc,0x000126dc,0x000124db},
+   {0x00000000,0x00000000,0x00009252,0x00009493,
+    0x000124db,0x000136e4,0x000136e4,0x000126dc},
+   {0x00000000,0x00000000,0x00009252,0x0000a49b,
+    0x000124db,0x000136e4,0x000136e4,0x000126dc},
+   {0x00000000,0x00000000,0x00009292,0x0000a49b,
+    0x000126dc,0x000136e4,0x000136e4,0x000136e4},
+   {0x00000000,0x00000000,0x00009492,0x0000a49b,
+    0x000126dc,0x0001b724,0x0001b725,0x0001b724},
+   {0x00000000,0x00000000,0x0000a492,0x000124db,
+    0x000136e4,0x0001b925,0x0001b92d,0x0001b925},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  },
+  { /* version 9, passes 1 */
+   {0x00000000,0x00000000,0x00000249,0x00000049,
+    0x00000009,0x00000009,0x00000009,0x00000009},
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00000049,0x00000049,0x00000009,0x00000009},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x0000124a,0x00000249,0x00000049,0x00000049},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x0000124a,0x0000124a,0x00000049,0x00000049},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x0000124a,0x0000024a,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x0000924a,
+    0x00009252,0x0000124a,0x0000024a,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x00009492,0x00009252,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x00009292,0x00009292,0x00001252},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x00009292,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x00009493,0x00009493,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x0000a49b,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x0000a49b,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000124db,0x0000a49b,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000136e4,0x000126dc,0x000124db,0x0000a49b},
+   {0x00000000,0x00000000,0x00009252,0x000124db,
+    0x0001b724,0x000136e4,0x000126dc,0x000124db},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  }
+ },
+ { /* version 10 */
+  { /* version 10, passes 0 */
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00000249,0x00000249,0x0000024a,0x0000024a},
+   {0x00000000,0x00000000,0x00000249,0x00001249,
+    0x00009252,0x00009292,0x00009292,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x00009292,0x00009292,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x0000924a,
+    0x00009492,0x00009493,0x0000a49b,0x00009292},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x00009492,0x000124db,0x000124db,0x00009292},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x000124db,0x000124db,0x00009493},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x000124db,0x000126dc,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x000124db,0x000126dc,0x000124db},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0001249b,0x000126dc,0x000126dc,0x000124db},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000124db,0x000126dc,0x000136e4,0x000126dc},
+   {0x00000000,0x00000000,0x00009252,0x0000a49b,
+    0x000124db,0x000136e4,0x000136e4,0x000136e4},
+   {0x00000000,0x00000000,0x00009292,0x0000a49b,
+    0x000126dc,0x000136e4,0x000136e4,0x000136e4},
+   {0x00000000,0x00000000,0x00009492,0x0000a49b,
+    0x000126dc,0x0001b724,0x0001b92d,0x0001b724},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x000126dc,0x0001b925,0x0001b92d,0x0001b925},
+   {0x00000000,0x00000000,0x0000a492,0x000126db,
+    0x000136e4,0x0002496d,0x0001c96e,0x0001c92d},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  },
+  { /* version 10, passes 1 */
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00000049,0x00000049,0x00000049,0x00000049},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x0000124a,0x00000249,0x00000049,0x00000049},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x0000124a,0x00009252,0x0000024a,0x00000049},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x00009493,0x0000024a,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x00009252,
+    0x00009492,0x00009493,0x00001252,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x00009492,0x00009493,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x00009492,0x00009493,0x00009292,0x00001252},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x00009493,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x0000a49b,0x00009493,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x0000a49b,0x00009493,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x000124db,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x000124db,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x000124db,
+    0x000136e4,0x000126dc,0x000124db,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x000124db,
+    0x000136e4,0x000126dc,0x000124db,0x0000a49b},
+   {0x00000000,0x00000000,0x00009252,0x000126db,
+    0x0001b724,0x000136e4,0x000126dc,0x000124db},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  }
+ },
+ { /* version 11 */
+  { /* version 11, passes 0 */
+   {0x00000000,0x00000000,0x00000249,0x00000249,
+    0x00000249,0x00000249,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x00009292,0x00009292,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x0000924a,
+    0x00009492,0x0000a49b,0x0000a49b,0x00009292},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x0000a49b,0x000124db,0x00009493},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x000124db,0x000126dc,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x000126dc,0x000126dc,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000126dc,0x000136e4,0x000124db},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000126dc,0x000136e4,0x000136e4,0x000126dc},
+   {0x00000000,0x00000000,0x00009292,0x0000a49b,
+    0x000126dc,0x000136e4,0x000136e4,0x000126dc},
+   {0x00000000,0x00000000,0x00009292,0x0000a49b,
+    0x000126dc,0x0001b724,0x0001b725,0x000136e4},
+   {0x00000000,0x00000000,0x00009292,0x0000a49b,
+    0x000136e4,0x0001b724,0x0001b92d,0x0001b724},
+   {0x00000000,0x00000000,0x00009492,0x0000a49b,
+    0x000136e4,0x0001b724,0x0001b92d,0x0001b724},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x000136e4,0x0001b925,0x0001c96e,0x0001b925},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x0001b724,0x0001b925,0x0001c96e,0x0001c92d},
+   {0x00000000,0x00000000,0x0000a492,0x000126db,
+    0x0001c924,0x0002496d,0x00025bb6,0x00024b77},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  },
+  { /* version 11, passes 1 */
+   {0x00000000,0x00000000,0x00001249,0x00000249,
+    0x00000249,0x00000249,0x0000024a,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x0000124a,0x0000124a,0x0000024a,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x0000924a,
+    0x00009252,0x00009252,0x0000024a,0x0000024a},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x00009492,0x0000a49b,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x0000a49b,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x0000a49b,0x00009292,0x00001252},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x0000a49b,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x0000a49b,0x00009493,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000124db,0x00009493,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000124db,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000124db,0x000124db,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x000124db,
+    0x000126dc,0x000126dc,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x000124db,
+    0x000136e4,0x000126dc,0x000124db,0x0000a49b},
+   {0x00000000,0x00000000,0x00009292,0x000124db,
+    0x000136e4,0x000126dc,0x000124db,0x0000a49b},
+   {0x00000000,0x00000000,0x00009492,0x000126db,
+    0x0001b724,0x000136e4,0x000126dc,0x000124db},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  }
+ },
+ { /* version 12 */
+  { /* version 12, passes 0 */
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x00009292,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x0000a493,0x0000a49b,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x000124db,0x000124db,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x000126dc,0x000126dc,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000126dc,0x000136e4,0x000124db},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000126dc,0x000136e4,0x000136e4,0x000126dc},
+   {0x00000000,0x00000000,0x00009292,0x0000a49b,
+    0x000126dc,0x0001b724,0x0001b725,0x000126dc},
+   {0x00000000,0x00000000,0x00009292,0x0000a49b,
+    0x000136e4,0x0001b724,0x0001b92d,0x000136e4},
+   {0x00000000,0x00000000,0x00009492,0x0000a49b,
+    0x000136e4,0x0001b724,0x0001b92d,0x0001b724},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x000136e4,0x0001b724,0x0001b92d,0x0001b724},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x000136e4,0x0001b925,0x0001b92d,0x0001b925},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x0001b724,0x0001b925,0x0001c96e,0x0001c92d},
+   {0x00000000,0x00000000,0x0000a492,0x000124db,
+    0x0001b724,0x0001c92d,0x0001c96e,0x0001c92d},
+   {0x00000000,0x00000000,0x0000a492,0x000124db,
+    0x0001b724,0x0001c92d,0x00024b76,0x0002496e},
+   {0x00000000,0x00000000,0x00012492,0x000126db,
+    0x0001c924,0x00024b6d,0x0002ddb6,0x00025bbf},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  },
+  { /* version 12, passes 1 */
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x0000124a,0x0000124a,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x00001249,0x00009292,
+    0x00009492,0x00009252,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x00009292,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x0000a49b,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x0000a49b,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x0000a49b,0x00009493,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000124db,0x000124db,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000124db,0x000124db,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x000124db,
+    0x000126dc,0x000124db,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x000124db,
+    0x000126dc,0x000126dc,0x0000a49b,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x000124db,
+    0x000136e4,0x000126dc,0x000124db,0x0000a49b},
+   {0x00000000,0x00000000,0x00009492,0x000126db,
+    0x000136e4,0x000126dc,0x000124db,0x0000a49b},
+   {0x00000000,0x00000000,0x00009492,0x000126db,
+    0x0001b724,0x000136e4,0x000126dc,0x000124db},
+   {0x00000000,0x00000000,0x00009492,0x000126db,
+    0x0001b724,0x000136e4,0x000126dc,0x000124db},
+   {0x00000000,0x00000000,0x0000a492,0x000136db,
+    0x0001c924,0x0001b724,0x000136e4,0x000126dc},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  }
+ },
+ { /* version 13 */
+  { /* version 13, passes 0 */
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x00009252,0x00009292,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x000124db,0x000126dc,0x00009493},
+   {0x00000000,0x00000000,0x00001249,0x0000a49b,
+    0x0001249b,0x000126dc,0x000126dc,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000126dc,0x000136e4,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000126dc,0x000136e4,0x0001b725,0x000124db},
+   {0x00000000,0x00000000,0x00009292,0x0000a49b,
+    0x000136e4,0x0001b724,0x0001b725,0x000126dc},
+   {0x00000000,0x00000000,0x00009292,0x000124db,
+    0x000136e4,0x0001b724,0x0001b725,0x000126dc},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x000136e4,0x0001b724,0x0001c96e,0x000136e4},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x000136e4,0x0001c92d,0x0001c96e,0x0001b724},
+   {0x00000000,0x00000000,0x0000a492,0x000124db,
+    0x000136e4,0x0001c92d,0x0001c96e,0x0001b724},
+   {0x00000000,0x00000000,0x0000a492,0x000124db,
+    0x0001b724,0x0001c92d,0x0001c96e,0x0001b925},
+   {0x00000000,0x00000000,0x0000a492,0x000126db,
+    0x0001b724,0x0001c92d,0x00024b76,0x0001c92d},
+   {0x00000000,0x00000000,0x0000a492,0x000126db,
+    0x0001b924,0x0001c92d,0x00024b76,0x0001c92d},
+   {0x00000000,0x00000000,0x0000a492,0x000126db,
+    0x0001b924,0x0001c92d,0x00024b76,0x0002496e},
+   {0x00000000,0x00000000,0x00012492,0x000136db,
+    0x00024924,0x00024b6d,0x0002ddb6,0x00025bbf},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  },
+  { /* version 13, passes 1 */
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x0000124a,0x0000124a,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x00009492,0x00009292,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x0000a49b,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x0000a49b,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x0000a49b,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000126dc,0x0000a49b,0x00009493,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x000124db,
+    0x000126dc,0x000124db,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x000124db,
+    0x000136e4,0x000124db,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x000136db,
+    0x0001b724,0x000124db,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x000136db,
+    0x0001b724,0x000126dc,0x0000a49b,0x0000a49b},
+   {0x00000000,0x00000000,0x00009292,0x000136db,
+    0x0001b724,0x000126dc,0x000124db,0x0000a49b},
+   {0x00000000,0x00000000,0x00009492,0x000136db,
+    0x0001b724,0x000126dc,0x000124db,0x0000a49b},
+   {0x00000000,0x00000000,0x0000a492,0x000136db,
+    0x0001b724,0x000136e4,0x000126dc,0x000124db},
+   {0x00000000,0x00000000,0x0000a492,0x000136db,
+    0x0001b724,0x000136e4,0x000126dc,0x000124db},
+   {0x00000000,0x00000000,0x00012492,0x0001b6db,
+    0x0001c924,0x0001b724,0x000136e4,0x000126dc},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  }
+ },
+ { /* version 14 */
+  { /* version 14, passes 0 */
+   {0x00000000,0x00000000,0x00001249,0x0000924a,
+    0x00009292,0x00009493,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x00001249,0x0000a49b,
+    0x0000a493,0x000124db,0x000126dc,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000126dc,0x000136e4,0x0000a49b},
+   {0x00000000,0x00000000,0x0000924a,0x000124db,
+    0x000126dc,0x000136e4,0x0001b725,0x000124db},
+   {0x00000000,0x00000000,0x00009292,0x000124db,
+    0x000126dc,0x0001b724,0x0001b92d,0x000126dc},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x000136e4,0x0001b724,0x0001b92d,0x000126dc},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x000136e4,0x0001c92d,0x0001c96e,0x000136e4},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x0001b724,0x0001c92d,0x0001c96e,0x0001b724},
+   {0x00000000,0x00000000,0x0000a492,0x000124db,
+    0x0001b724,0x0001c92d,0x00024b76,0x0001b925},
+   {0x00000000,0x00000000,0x0000a492,0x000126db,
+    0x0001b724,0x0001c92d,0x00024b76,0x0001c92d},
+   {0x00000000,0x00000000,0x0000a492,0x000126db,
+    0x0001b724,0x0001c92d,0x00024b76,0x0001c92d},
+   {0x00000000,0x00000000,0x0000a492,0x000136db,
+    0x0001b724,0x0001c92d,0x00024b76,0x0002496e},
+   {0x00000000,0x00000000,0x0000a492,0x000136db,
+    0x0001b924,0x0002496d,0x00024b76,0x00024b77},
+   {0x00000000,0x00000000,0x0000a492,0x000136db,
+    0x0001b924,0x00024b6d,0x0002ddb6,0x00025bbf},
+   {0x00000000,0x00000000,0x00012492,0x0001b6db,
+    0x00024924,0x0002db6d,0x00036db6,0x0002efff},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  },
+  { /* version 14, passes 1 */
+   {0x00000000,0x00000000,0x00001249,0x00001249,
+    0x0000124a,0x0000124a,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x0000924a,0x00009493,
+    0x0000a493,0x00009292,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x0000a49b,0x00001252,0x00001252},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000136e4,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000136e4,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x000124db,
+    0x000136e4,0x000136e4,0x00009493,0x00009292},
+   {0x00000000,0x00000000,0x00009492,0x000136db,
+    0x0001b724,0x000136e4,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x00009492,0x000136db,
+    0x0001b724,0x000136e4,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x00009492,0x000136db,
+    0x0001b724,0x000136e4,0x0000a49b,0x00009493},
+   {0x00000000,0x00000000,0x00009492,0x000136db,
+    0x0001b724,0x000136e4,0x0000a49b,0x0000a49b},
+   {0x00000000,0x00000000,0x0000a492,0x000136db,
+    0x0001b724,0x000136e4,0x000124db,0x0000a49b},
+   {0x00000000,0x00000000,0x0000a492,0x000136db,
+    0x0001b724,0x000136e4,0x000124db,0x0000a49b},
+   {0x00000000,0x00000000,0x0000a492,0x000136db,
+    0x0001b724,0x000136e4,0x000126dc,0x000124db},
+   {0x00000000,0x00000000,0x0000a492,0x000136db,
+    0x0001b724,0x000136e4,0x000126dc,0x000124db},
+   {0x00000000,0x00000000,0x00012492,0x0001b6db,
+    0x0001c924,0x0001b724,0x000136e4,0x000126dc},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  }
+ },
+ { /* version 15 */
+  { /* version 15, passes 0 */
+   {0x00000000,0x00000000,0x00001249,0x00009493,
+    0x0000a493,0x0000a49b,0x000124db,0x000124db},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0001249b,0x000126dc,0x000136e4,0x000124db},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x000126dc,0x0001b724,0x0001b725,0x000126dc},
+   {0x00000000,0x00000000,0x0000924a,0x000124db,
+    0x000136e4,0x0001b724,0x0001b92d,0x000126dc},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x000136e4,0x0001b925,0x0001c96e,0x000136e4},
+   {0x00000000,0x00000000,0x00009492,0x000124db,
+    0x0001b724,0x0001c92d,0x0001c96e,0x0001b724},
+   {0x00000000,0x00000000,0x0000a492,0x000124db,
+    0x0001b724,0x0001c92d,0x0001c96e,0x0001b724},
+   {0x00000000,0x00000000,0x0000a492,0x000126db,
+    0x0001b724,0x0001c92d,0x0001c96e,0x0001b925},
+   {0x00000000,0x00000000,0x0000a492,0x000126db,
+    0x0001b924,0x0001c92d,0x00024b76,0x0001c92d},
+   {0x00000000,0x00000000,0x0000a492,0x000136db,
+    0x0001b924,0x0001c92d,0x00024b76,0x0001c92d},
+   {0x00000000,0x00000000,0x0000a492,0x000136db,
+    0x0001b924,0x0002496d,0x00024b76,0x0002496e},
+   {0x00000000,0x00000000,0x0000a492,0x000136db,
+    0x0001c924,0x0002496d,0x00025bb6,0x00024b77},
+   {0x00000000,0x00000000,0x0000a492,0x000136db,
+    0x0001c924,0x00024b6d,0x00025bb6,0x00024b77},
+   {0x00000000,0x00000000,0x00012492,0x000136db,
+    0x0001c924,0x00024b6d,0x0002ddb6,0x00025bbf},
+   {0x00000000,0x00000000,0x00012492,0x0001b6db,
+    0x00024924,0x0002db6d,0x00036db6,0x0002efff},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  },
+  { /* version 15, passes 1 */
+   {0x00000000,0x00000000,0x0000924a,0x0000924a,
+    0x00009292,0x00009292,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x0000a49b,
+    0x0000a493,0x000124db,0x00009292,0x00009292},
+   {0x00000000,0x00000000,0x0000924a,0x000124db,
+    0x000124db,0x0001b724,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x000124db,
+    0x000126dc,0x0001b724,0x00009493,0x00009493},
+   {0x00000000,0x00000000,0x0000924a,0x000124db,
+    0x000136e4,0x0001b724,0x0000a49b,0x0000a49b},
+   {0x00000000,0x00000000,0x00009292,0x000136db,
+    0x0001b724,0x0001b724,0x0000a49b,0x0000a49b},
+   {0x00000000,0x00000000,0x00009492,0x000136db,
+    0x0001c924,0x0001b724,0x000124db,0x000124db},
+   {0x00000000,0x00000000,0x00009492,0x000136db,
+    0x0001c924,0x0001b724,0x000124db,0x000124db},
+   {0x00000000,0x00000000,0x0000a492,0x000136db,
+    0x0001c924,0x0001b724,0x000126dc,0x000126dc},
+   {0x00000000,0x00000000,0x0000a492,0x000136db,
+    0x0001c924,0x0001b925,0x000126dc,0x000126dc},
+   {0x00000000,0x00000000,0x0000a492,0x000136db,
+    0x0001c924,0x0001b925,0x000136e4,0x000136e4},
+   {0x00000000,0x00000000,0x0000a492,0x000136db,
+    0x0001c924,0x0001b925,0x000136e4,0x000136e4},
+   {0x00000000,0x00000000,0x0000a492,0x000136db,
+    0x0001c924,0x0001b925,0x0001b725,0x0001b724},
+   {0x00000000,0x00000000,0x00012492,0x000136db,
+    0x0001c924,0x0001b925,0x0001b725,0x0001b724},
+   {0x00000000,0x00000000,0x00012492,0x0001b6db,
+    0x00024924,0x0002496d,0x0001b92d,0x0001b925},
+   {0x00000000,0x00000000,0x00000000,0x00000000,
+    0x00000000,0x00000000,0x00000000,0x00000000}
+  }
+ }
+};
diff --git a/drivers/media/usb/pwc/pwc-timon.h b/drivers/media/usb/pwc/pwc-timon.h
new file mode 100644
index 0000000..270c5b9
--- /dev/null
+++ b/drivers/media/usb/pwc/pwc-timon.h
@@ -0,0 +1,63 @@
+/* Linux driver for Philips webcam
+   (C) 2004-2006 Luc Saillard (luc@saillard.org)
+
+   NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx
+   driver and thus may have bugs that are not present in the original version.
+   Please send bug reports and support requests to <luc@saillard.org>.
+   The decompression routines have been implemented by reverse-engineering the
+   Nemosoft binary pwcx module. Caveat emptor.
+
+   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
+*/
+
+
+
+/* This tables contains entries for the 675/680/690 (Timon) camera, with
+   4 different qualities (no compression, low, medium, high).
+   It lists the bandwidth requirements for said mode by its alternate interface
+   number. An alternate of 0 means that the mode is unavailable.
+
+   There are 6 * 4 * 4 entries:
+     6 different resolutions subqcif, qsif, qcif, sif, cif, vga
+     6 framerates: 5, 10, 15, 20, 25, 30
+     4 compression modi: none, low, medium, high
+
+   When an uncompressed mode is not available, the next available compressed mode
+   will be chosen (unless the decompressor is absent). Sometimes there are only
+   1 or 2 compressed modes available; in that case entries are duplicated.
+*/
+
+#ifndef PWC_TIMON_H
+#define PWC_TIMON_H
+
+#include "pwc.h"
+
+#define PWC_FPS_MAX_TIMON 6
+
+struct Timon_table_entry
+{
+	char alternate;			/* USB alternate interface */
+	unsigned short packetsize;	/* Normal packet size */
+	unsigned short bandlength;	/* Bandlength when decompressing */
+	unsigned char mode[13];		/* precomputed mode settings for cam */
+};
+
+extern const struct Timon_table_entry Timon_table[PSZ_MAX][PWC_FPS_MAX_TIMON][4];
+extern const unsigned int TimonRomTable [16][2][16][8];
+extern const unsigned int Timon_fps_vector[PWC_FPS_MAX_TIMON];
+
+#endif
+
+
diff --git a/drivers/media/usb/pwc/pwc-uncompress.c b/drivers/media/usb/pwc/pwc-uncompress.c
new file mode 100644
index 0000000..98c46f9
--- /dev/null
+++ b/drivers/media/usb/pwc/pwc-uncompress.c
@@ -0,0 +1,107 @@
+/* Linux driver for Philips webcam
+   Decompression frontend.
+   (C) 1999-2003 Nemosoft Unv.
+   (C) 2004-2006 Luc Saillard (luc@saillard.org)
+
+   NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx
+   driver and thus may have bugs that are not present in the original version.
+   Please send bug reports and support requests to <luc@saillard.org>.
+   The decompression routines have been implemented by reverse-engineering the
+   Nemosoft binary pwcx module. Caveat emptor.
+
+   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
+
+   vim: set ts=8:
+*/
+
+#include <asm/current.h>
+#include <asm/types.h>
+
+#include "pwc.h"
+#include "pwc-dec1.h"
+#include "pwc-dec23.h"
+
+int pwc_decompress(struct pwc_device *pdev, struct pwc_frame_buf *fbuf)
+{
+	int n, line, col;
+	void *yuv, *image;
+	u16 *src;
+	u16 *dsty, *dstu, *dstv;
+
+	image = vb2_plane_vaddr(&fbuf->vb.vb2_buf, 0);
+
+	yuv = fbuf->data + pdev->frame_header_size;  /* Skip header */
+
+	/* Raw format; that's easy... */
+	if (pdev->pixfmt != V4L2_PIX_FMT_YUV420)
+	{
+		struct pwc_raw_frame *raw_frame = image;
+		raw_frame->type = cpu_to_le16(pdev->type);
+		raw_frame->vbandlength = cpu_to_le16(pdev->vbandlength);
+			/* cmd_buf is always 4 bytes, but sometimes, only the
+			 * first 3 bytes is filled (Nala case). We can
+			 * determine this using the type of the webcam */
+		memcpy(raw_frame->cmd, pdev->cmd_buf, 4);
+		memcpy(raw_frame+1, yuv, pdev->frame_size);
+		vb2_set_plane_payload(&fbuf->vb.vb2_buf, 0,
+			pdev->frame_size + sizeof(struct pwc_raw_frame));
+		return 0;
+	}
+
+	vb2_set_plane_payload(&fbuf->vb.vb2_buf, 0,
+			      pdev->width * pdev->height * 3 / 2);
+
+	if (pdev->vbandlength == 0) {
+		/* Uncompressed mode.
+		 *
+		 * We do some byte shuffling here to go from the
+		 * native format to YUV420P.
+		 */
+		src = (u16 *)yuv;
+		n = pdev->width * pdev->height;
+		dsty = (u16 *)(image);
+		dstu = (u16 *)(image + n);
+		dstv = (u16 *)(image + n + n / 4);
+
+		for (line = 0; line < pdev->height; line++) {
+			for (col = 0; col < pdev->width; col += 4) {
+				*dsty++ = *src++;
+				*dsty++ = *src++;
+				if (line & 1)
+					*dstv++ = *src++;
+				else
+					*dstu++ = *src++;
+			}
+		}
+
+		return 0;
+	}
+
+	/*
+	 * Compressed;
+	 * the decompressor routines will write the data in planar format
+	 * immediately.
+	 */
+	if (DEVICE_USE_CODEC1(pdev->type)) {
+
+		/* TODO & FIXME */
+		PWC_ERROR("This chipset is not supported for now\n");
+		return -ENXIO; /* No such device or address: missing decompressor */
+
+	} else {
+		pwc_dec23_decompress(pdev, yuv, image);
+	}
+	return 0;
+}
diff --git a/drivers/media/usb/pwc/pwc-v4l.c b/drivers/media/usb/pwc/pwc-v4l.c
new file mode 100644
index 0000000..043b2b9
--- /dev/null
+++ b/drivers/media/usb/pwc/pwc-v4l.c
@@ -0,0 +1,1051 @@
+/* Linux driver for Philips webcam
+   USB and Video4Linux interface part.
+   (C) 1999-2004 Nemosoft Unv.
+   (C) 2004-2006 Luc Saillard (luc@saillard.org)
+   (C) 2011 Hans de Goede <hdegoede@redhat.com>
+
+   NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx
+   driver and thus may have bugs that are not present in the original version.
+   Please send bug reports and support requests to <luc@saillard.org>.
+   The decompression routines have been implemented by reverse-engineering the
+   Nemosoft binary pwcx module. Caveat emptor.
+
+   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/errno.h>
+#include <linux/init.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/poll.h>
+#include <linux/vmalloc.h>
+#include <linux/jiffies.h>
+#include <asm/io.h>
+
+#include "pwc.h"
+
+#define PWC_CID_CUSTOM(ctrl) ((V4L2_CID_USER_BASE | 0xf000) + custom_ ## ctrl)
+
+static int pwc_g_volatile_ctrl(struct v4l2_ctrl *ctrl);
+static int pwc_s_ctrl(struct v4l2_ctrl *ctrl);
+
+static const struct v4l2_ctrl_ops pwc_ctrl_ops = {
+	.g_volatile_ctrl = pwc_g_volatile_ctrl,
+	.s_ctrl = pwc_s_ctrl,
+};
+
+enum { awb_indoor, awb_outdoor, awb_fl, awb_manual, awb_auto };
+enum { custom_autocontour, custom_contour, custom_noise_reduction,
+	custom_awb_speed, custom_awb_delay,
+	custom_save_user, custom_restore_user, custom_restore_factory };
+
+static const char * const pwc_auto_whitebal_qmenu[] = {
+	"Indoor (Incandescant Lighting) Mode",
+	"Outdoor (Sunlight) Mode",
+	"Indoor (Fluorescent Lighting) Mode",
+	"Manual Mode",
+	"Auto Mode",
+	NULL
+};
+
+static const struct v4l2_ctrl_config pwc_auto_white_balance_cfg = {
+	.ops	= &pwc_ctrl_ops,
+	.id	= V4L2_CID_AUTO_WHITE_BALANCE,
+	.type	= V4L2_CTRL_TYPE_MENU,
+	.max	= awb_auto,
+	.qmenu	= pwc_auto_whitebal_qmenu,
+};
+
+static const struct v4l2_ctrl_config pwc_autocontour_cfg = {
+	.ops	= &pwc_ctrl_ops,
+	.id	= PWC_CID_CUSTOM(autocontour),
+	.type	= V4L2_CTRL_TYPE_BOOLEAN,
+	.name	= "Auto contour",
+	.min	= 0,
+	.max	= 1,
+	.step	= 1,
+};
+
+static const struct v4l2_ctrl_config pwc_contour_cfg = {
+	.ops	= &pwc_ctrl_ops,
+	.id	= PWC_CID_CUSTOM(contour),
+	.type	= V4L2_CTRL_TYPE_INTEGER,
+	.name	= "Contour",
+	.flags  = V4L2_CTRL_FLAG_SLIDER,
+	.min	= 0,
+	.max	= 63,
+	.step	= 1,
+};
+
+static const struct v4l2_ctrl_config pwc_backlight_cfg = {
+	.ops	= &pwc_ctrl_ops,
+	.id	= V4L2_CID_BACKLIGHT_COMPENSATION,
+	.type	= V4L2_CTRL_TYPE_BOOLEAN,
+	.min	= 0,
+	.max	= 1,
+	.step	= 1,
+};
+
+static const struct v4l2_ctrl_config pwc_flicker_cfg = {
+	.ops	= &pwc_ctrl_ops,
+	.id	= V4L2_CID_BAND_STOP_FILTER,
+	.type	= V4L2_CTRL_TYPE_BOOLEAN,
+	.min	= 0,
+	.max	= 1,
+	.step	= 1,
+};
+
+static const struct v4l2_ctrl_config pwc_noise_reduction_cfg = {
+	.ops	= &pwc_ctrl_ops,
+	.id	= PWC_CID_CUSTOM(noise_reduction),
+	.type	= V4L2_CTRL_TYPE_INTEGER,
+	.name	= "Dynamic Noise Reduction",
+	.min	= 0,
+	.max	= 3,
+	.step	= 1,
+};
+
+static const struct v4l2_ctrl_config pwc_save_user_cfg = {
+	.ops	= &pwc_ctrl_ops,
+	.id	= PWC_CID_CUSTOM(save_user),
+	.type	= V4L2_CTRL_TYPE_BUTTON,
+	.name    = "Save User Settings",
+};
+
+static const struct v4l2_ctrl_config pwc_restore_user_cfg = {
+	.ops	= &pwc_ctrl_ops,
+	.id	= PWC_CID_CUSTOM(restore_user),
+	.type	= V4L2_CTRL_TYPE_BUTTON,
+	.name    = "Restore User Settings",
+};
+
+static const struct v4l2_ctrl_config pwc_restore_factory_cfg = {
+	.ops	= &pwc_ctrl_ops,
+	.id	= PWC_CID_CUSTOM(restore_factory),
+	.type	= V4L2_CTRL_TYPE_BUTTON,
+	.name    = "Restore Factory Settings",
+};
+
+static const struct v4l2_ctrl_config pwc_awb_speed_cfg = {
+	.ops	= &pwc_ctrl_ops,
+	.id	= PWC_CID_CUSTOM(awb_speed),
+	.type	= V4L2_CTRL_TYPE_INTEGER,
+	.name	= "Auto White Balance Speed",
+	.min	= 1,
+	.max	= 32,
+	.step	= 1,
+};
+
+static const struct v4l2_ctrl_config pwc_awb_delay_cfg = {
+	.ops	= &pwc_ctrl_ops,
+	.id	= PWC_CID_CUSTOM(awb_delay),
+	.type	= V4L2_CTRL_TYPE_INTEGER,
+	.name	= "Auto White Balance Delay",
+	.min	= 0,
+	.max	= 63,
+	.step	= 1,
+};
+
+int pwc_init_controls(struct pwc_device *pdev)
+{
+	struct v4l2_ctrl_handler *hdl;
+	struct v4l2_ctrl_config cfg;
+	int r, def;
+
+	hdl = &pdev->ctrl_handler;
+	r = v4l2_ctrl_handler_init(hdl, 20);
+	if (r)
+		return r;
+
+	/* Brightness, contrast, saturation, gamma */
+	r = pwc_get_u8_ctrl(pdev, GET_LUM_CTL, BRIGHTNESS_FORMATTER, &def);
+	if (r || def > 127)
+		def = 63;
+	pdev->brightness = v4l2_ctrl_new_std(hdl, &pwc_ctrl_ops,
+				V4L2_CID_BRIGHTNESS, 0, 127, 1, def);
+
+	r = pwc_get_u8_ctrl(pdev, GET_LUM_CTL, CONTRAST_FORMATTER, &def);
+	if (r || def > 63)
+		def = 31;
+	pdev->contrast = v4l2_ctrl_new_std(hdl, &pwc_ctrl_ops,
+				V4L2_CID_CONTRAST, 0, 63, 1, def);
+
+	if (pdev->type >= 675) {
+		if (pdev->type < 730)
+			pdev->saturation_fmt = SATURATION_MODE_FORMATTER2;
+		else
+			pdev->saturation_fmt = SATURATION_MODE_FORMATTER1;
+		r = pwc_get_s8_ctrl(pdev, GET_CHROM_CTL, pdev->saturation_fmt,
+				    &def);
+		if (r || def < -100 || def > 100)
+			def = 0;
+		pdev->saturation = v4l2_ctrl_new_std(hdl, &pwc_ctrl_ops,
+				      V4L2_CID_SATURATION, -100, 100, 1, def);
+	}
+
+	r = pwc_get_u8_ctrl(pdev, GET_LUM_CTL, GAMMA_FORMATTER, &def);
+	if (r || def > 31)
+		def = 15;
+	pdev->gamma = v4l2_ctrl_new_std(hdl, &pwc_ctrl_ops,
+				V4L2_CID_GAMMA, 0, 31, 1, def);
+
+	/* auto white balance, red gain, blue gain */
+	r = pwc_get_u8_ctrl(pdev, GET_CHROM_CTL, WB_MODE_FORMATTER, &def);
+	if (r || def > awb_auto)
+		def = awb_auto;
+	cfg = pwc_auto_white_balance_cfg;
+	cfg.name = v4l2_ctrl_get_name(cfg.id);
+	cfg.def = def;
+	pdev->auto_white_balance = v4l2_ctrl_new_custom(hdl, &cfg, NULL);
+	/* check auto controls to avoid NULL deref in v4l2_ctrl_auto_cluster */
+	if (!pdev->auto_white_balance)
+		return hdl->error;
+
+	r = pwc_get_u8_ctrl(pdev, GET_CHROM_CTL,
+			    PRESET_MANUAL_RED_GAIN_FORMATTER, &def);
+	if (r)
+		def = 127;
+	pdev->red_balance = v4l2_ctrl_new_std(hdl, &pwc_ctrl_ops,
+				V4L2_CID_RED_BALANCE, 0, 255, 1, def);
+
+	r = pwc_get_u8_ctrl(pdev, GET_CHROM_CTL,
+			    PRESET_MANUAL_BLUE_GAIN_FORMATTER, &def);
+	if (r)
+		def = 127;
+	pdev->blue_balance = v4l2_ctrl_new_std(hdl, &pwc_ctrl_ops,
+				V4L2_CID_BLUE_BALANCE, 0, 255, 1, def);
+
+	v4l2_ctrl_auto_cluster(3, &pdev->auto_white_balance, awb_manual, true);
+
+	/* autogain, gain */
+	r = pwc_get_u8_ctrl(pdev, GET_LUM_CTL, AGC_MODE_FORMATTER, &def);
+	if (r || (def != 0 && def != 0xff))
+		def = 0;
+	/* Note a register value if 0 means auto gain is on */
+	pdev->autogain = v4l2_ctrl_new_std(hdl, &pwc_ctrl_ops,
+				V4L2_CID_AUTOGAIN, 0, 1, 1, def == 0);
+	if (!pdev->autogain)
+		return hdl->error;
+
+	r = pwc_get_u8_ctrl(pdev, GET_LUM_CTL, PRESET_AGC_FORMATTER, &def);
+	if (r || def > 63)
+		def = 31;
+	pdev->gain = v4l2_ctrl_new_std(hdl, &pwc_ctrl_ops,
+				V4L2_CID_GAIN, 0, 63, 1, def);
+
+	/* auto exposure, exposure */
+	if (DEVICE_USE_CODEC2(pdev->type)) {
+		r = pwc_get_u8_ctrl(pdev, GET_LUM_CTL, SHUTTER_MODE_FORMATTER,
+				    &def);
+		if (r || (def != 0 && def != 0xff))
+			def = 0;
+		/*
+		 * def = 0 auto, def = ff manual
+		 * menu idx 0 = auto, idx 1 = manual
+		 */
+		pdev->exposure_auto = v4l2_ctrl_new_std_menu(hdl,
+					&pwc_ctrl_ops,
+					V4L2_CID_EXPOSURE_AUTO,
+					1, 0, def != 0);
+		if (!pdev->exposure_auto)
+			return hdl->error;
+
+		/* GET_LUM_CTL, PRESET_SHUTTER_FORMATTER is unreliable */
+		r = pwc_get_u16_ctrl(pdev, GET_STATUS_CTL,
+				     READ_SHUTTER_FORMATTER, &def);
+		if (r || def > 655)
+			def = 655;
+		pdev->exposure = v4l2_ctrl_new_std(hdl, &pwc_ctrl_ops,
+					V4L2_CID_EXPOSURE, 0, 655, 1, def);
+		/* CODEC2: separate auto gain & auto exposure */
+		v4l2_ctrl_auto_cluster(2, &pdev->autogain, 0, true);
+		v4l2_ctrl_auto_cluster(2, &pdev->exposure_auto,
+				       V4L2_EXPOSURE_MANUAL, true);
+	} else if (DEVICE_USE_CODEC3(pdev->type)) {
+		/* GET_LUM_CTL, PRESET_SHUTTER_FORMATTER is unreliable */
+		r = pwc_get_u16_ctrl(pdev, GET_STATUS_CTL,
+				     READ_SHUTTER_FORMATTER, &def);
+		if (r || def > 255)
+			def = 255;
+		pdev->exposure = v4l2_ctrl_new_std(hdl, &pwc_ctrl_ops,
+					V4L2_CID_EXPOSURE, 0, 255, 1, def);
+		/* CODEC3: both gain and exposure controlled by autogain */
+		pdev->autogain_expo_cluster[0] = pdev->autogain;
+		pdev->autogain_expo_cluster[1] = pdev->gain;
+		pdev->autogain_expo_cluster[2] = pdev->exposure;
+		v4l2_ctrl_auto_cluster(3, pdev->autogain_expo_cluster,
+				       0, true);
+	}
+
+	/* color / bw setting */
+	r = pwc_get_u8_ctrl(pdev, GET_CHROM_CTL, COLOUR_MODE_FORMATTER,
+			 &def);
+	if (r || (def != 0 && def != 0xff))
+		def = 0xff;
+	/* def = 0 bw, def = ff color, menu idx 0 = color, idx 1 = bw */
+	pdev->colorfx = v4l2_ctrl_new_std_menu(hdl, &pwc_ctrl_ops,
+				V4L2_CID_COLORFX, 1, 0, def == 0);
+
+	/* autocontour, contour */
+	r = pwc_get_u8_ctrl(pdev, GET_LUM_CTL, AUTO_CONTOUR_FORMATTER, &def);
+	if (r || (def != 0 && def != 0xff))
+		def = 0;
+	cfg = pwc_autocontour_cfg;
+	cfg.def = def == 0;
+	pdev->autocontour = v4l2_ctrl_new_custom(hdl, &cfg, NULL);
+	if (!pdev->autocontour)
+		return hdl->error;
+
+	r = pwc_get_u8_ctrl(pdev, GET_LUM_CTL, PRESET_CONTOUR_FORMATTER, &def);
+	if (r || def > 63)
+		def = 31;
+	cfg = pwc_contour_cfg;
+	cfg.def = def;
+	pdev->contour = v4l2_ctrl_new_custom(hdl, &cfg, NULL);
+
+	v4l2_ctrl_auto_cluster(2, &pdev->autocontour, 0, false);
+
+	/* backlight */
+	r = pwc_get_u8_ctrl(pdev, GET_LUM_CTL,
+			    BACK_LIGHT_COMPENSATION_FORMATTER, &def);
+	if (r || (def != 0 && def != 0xff))
+		def = 0;
+	cfg = pwc_backlight_cfg;
+	cfg.name = v4l2_ctrl_get_name(cfg.id);
+	cfg.def = def == 0;
+	pdev->backlight = v4l2_ctrl_new_custom(hdl, &cfg, NULL);
+
+	/* flikker rediction */
+	r = pwc_get_u8_ctrl(pdev, GET_LUM_CTL,
+			    FLICKERLESS_MODE_FORMATTER, &def);
+	if (r || (def != 0 && def != 0xff))
+		def = 0;
+	cfg = pwc_flicker_cfg;
+	cfg.name = v4l2_ctrl_get_name(cfg.id);
+	cfg.def = def == 0;
+	pdev->flicker = v4l2_ctrl_new_custom(hdl, &cfg, NULL);
+
+	/* Dynamic noise reduction */
+	r = pwc_get_u8_ctrl(pdev, GET_LUM_CTL,
+			    DYNAMIC_NOISE_CONTROL_FORMATTER, &def);
+	if (r || def > 3)
+		def = 2;
+	cfg = pwc_noise_reduction_cfg;
+	cfg.def = def;
+	pdev->noise_reduction = v4l2_ctrl_new_custom(hdl, &cfg, NULL);
+
+	/* Save / Restore User / Factory Settings */
+	pdev->save_user = v4l2_ctrl_new_custom(hdl, &pwc_save_user_cfg, NULL);
+	pdev->restore_user = v4l2_ctrl_new_custom(hdl, &pwc_restore_user_cfg,
+						  NULL);
+	if (pdev->restore_user)
+		pdev->restore_user->flags |= V4L2_CTRL_FLAG_UPDATE;
+	pdev->restore_factory = v4l2_ctrl_new_custom(hdl,
+						     &pwc_restore_factory_cfg,
+						     NULL);
+	if (pdev->restore_factory)
+		pdev->restore_factory->flags |= V4L2_CTRL_FLAG_UPDATE;
+
+	/* Auto White Balance speed & delay */
+	r = pwc_get_u8_ctrl(pdev, GET_CHROM_CTL,
+			    AWB_CONTROL_SPEED_FORMATTER, &def);
+	if (r || def < 1 || def > 32)
+		def = 1;
+	cfg = pwc_awb_speed_cfg;
+	cfg.def = def;
+	pdev->awb_speed = v4l2_ctrl_new_custom(hdl, &cfg, NULL);
+
+	r = pwc_get_u8_ctrl(pdev, GET_CHROM_CTL,
+			    AWB_CONTROL_DELAY_FORMATTER, &def);
+	if (r || def > 63)
+		def = 0;
+	cfg = pwc_awb_delay_cfg;
+	cfg.def = def;
+	pdev->awb_delay = v4l2_ctrl_new_custom(hdl, &cfg, NULL);
+
+	if (!(pdev->features & FEATURE_MOTOR_PANTILT))
+		return hdl->error;
+
+	/* Motor pan / tilt / reset */
+	pdev->motor_pan = v4l2_ctrl_new_std(hdl, &pwc_ctrl_ops,
+				V4L2_CID_PAN_RELATIVE, -4480, 4480, 64, 0);
+	if (!pdev->motor_pan)
+		return hdl->error;
+	pdev->motor_tilt = v4l2_ctrl_new_std(hdl, &pwc_ctrl_ops,
+				V4L2_CID_TILT_RELATIVE, -1920, 1920, 64, 0);
+	pdev->motor_pan_reset = v4l2_ctrl_new_std(hdl, &pwc_ctrl_ops,
+				V4L2_CID_PAN_RESET, 0, 0, 0, 0);
+	pdev->motor_tilt_reset = v4l2_ctrl_new_std(hdl, &pwc_ctrl_ops,
+				V4L2_CID_TILT_RESET, 0, 0, 0, 0);
+	v4l2_ctrl_cluster(4, &pdev->motor_pan);
+
+	return hdl->error;
+}
+
+static void pwc_vidioc_fill_fmt(struct v4l2_format *f,
+	int width, int height, u32 pixfmt)
+{
+	memset(&f->fmt.pix, 0, sizeof(struct v4l2_pix_format));
+	f->fmt.pix.width        = width;
+	f->fmt.pix.height       = height;
+	f->fmt.pix.field        = V4L2_FIELD_NONE;
+	f->fmt.pix.pixelformat  = pixfmt;
+	f->fmt.pix.bytesperline = f->fmt.pix.width;
+	f->fmt.pix.sizeimage	= f->fmt.pix.height * f->fmt.pix.width * 3 / 2;
+	f->fmt.pix.colorspace	= V4L2_COLORSPACE_SRGB;
+	PWC_DEBUG_IOCTL("pwc_vidioc_fill_fmt() width=%d, height=%d, bytesperline=%d, sizeimage=%d, pixelformat=%c%c%c%c\n",
+			f->fmt.pix.width,
+			f->fmt.pix.height,
+			f->fmt.pix.bytesperline,
+			f->fmt.pix.sizeimage,
+			(f->fmt.pix.pixelformat)&255,
+			(f->fmt.pix.pixelformat>>8)&255,
+			(f->fmt.pix.pixelformat>>16)&255,
+			(f->fmt.pix.pixelformat>>24)&255);
+}
+
+/* ioctl(VIDIOC_TRY_FMT) */
+static int pwc_vidioc_try_fmt(struct pwc_device *pdev, struct v4l2_format *f)
+{
+	int size;
+
+	if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+		PWC_DEBUG_IOCTL("Bad video type must be V4L2_BUF_TYPE_VIDEO_CAPTURE\n");
+		return -EINVAL;
+	}
+
+	switch (f->fmt.pix.pixelformat) {
+		case V4L2_PIX_FMT_YUV420:
+			break;
+		case V4L2_PIX_FMT_PWC1:
+			if (DEVICE_USE_CODEC23(pdev->type)) {
+				PWC_DEBUG_IOCTL("codec1 is only supported for old pwc webcam\n");
+				f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420;
+			}
+			break;
+		case V4L2_PIX_FMT_PWC2:
+			if (DEVICE_USE_CODEC1(pdev->type)) {
+				PWC_DEBUG_IOCTL("codec23 is only supported for new pwc webcam\n");
+				f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420;
+			}
+			break;
+		default:
+			PWC_DEBUG_IOCTL("Unsupported pixel format\n");
+			f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420;
+	}
+
+	size = pwc_get_size(pdev, f->fmt.pix.width, f->fmt.pix.height);
+	pwc_vidioc_fill_fmt(f,
+			    pwc_image_sizes[size][0],
+			    pwc_image_sizes[size][1],
+			    f->fmt.pix.pixelformat);
+
+	return 0;
+}
+
+/* ioctl(VIDIOC_SET_FMT) */
+
+static int pwc_s_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f)
+{
+	struct pwc_device *pdev = video_drvdata(file);
+	int ret, pixelformat, compression = 0;
+
+	ret = pwc_vidioc_try_fmt(pdev, f);
+	if (ret < 0)
+		return ret;
+
+	if (vb2_is_busy(&pdev->vb_queue))
+		return -EBUSY;
+
+	pixelformat = f->fmt.pix.pixelformat;
+
+	PWC_DEBUG_IOCTL("Trying to set format to: width=%d height=%d fps=%d format=%c%c%c%c\n",
+			f->fmt.pix.width, f->fmt.pix.height, pdev->vframes,
+			(pixelformat)&255,
+			(pixelformat>>8)&255,
+			(pixelformat>>16)&255,
+			(pixelformat>>24)&255);
+
+	ret = pwc_set_video_mode(pdev, f->fmt.pix.width, f->fmt.pix.height,
+				 pixelformat, 30, &compression, 0);
+
+	PWC_DEBUG_IOCTL("pwc_set_video_mode(), return=%d\n", ret);
+
+	pwc_vidioc_fill_fmt(f, pdev->width, pdev->height, pdev->pixfmt);
+	return ret;
+}
+
+static int pwc_querycap(struct file *file, void *fh, struct v4l2_capability *cap)
+{
+	struct pwc_device *pdev = video_drvdata(file);
+
+	strcpy(cap->driver, PWC_NAME);
+	strlcpy(cap->card, pdev->vdev.name, sizeof(cap->card));
+	usb_make_path(pdev->udev, cap->bus_info, sizeof(cap->bus_info));
+	cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING |
+					V4L2_CAP_READWRITE;
+	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+	return 0;
+}
+
+static int pwc_enum_input(struct file *file, void *fh, struct v4l2_input *i)
+{
+	if (i->index)	/* Only one INPUT is supported */
+		return -EINVAL;
+
+	strlcpy(i->name, "Camera", sizeof(i->name));
+	i->type = V4L2_INPUT_TYPE_CAMERA;
+	return 0;
+}
+
+static int pwc_g_input(struct file *file, void *fh, unsigned int *i)
+{
+	*i = 0;
+	return 0;
+}
+
+static int pwc_s_input(struct file *file, void *fh, unsigned int i)
+{
+	return i ? -EINVAL : 0;
+}
+
+static int pwc_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct pwc_device *pdev =
+		container_of(ctrl->handler, struct pwc_device, ctrl_handler);
+	int ret = 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_AUTO_WHITE_BALANCE:
+		if (pdev->color_bal_valid &&
+			(pdev->auto_white_balance->val != awb_auto ||
+			 time_before(jiffies,
+				pdev->last_color_bal_update + HZ / 4))) {
+			pdev->red_balance->val  = pdev->last_red_balance;
+			pdev->blue_balance->val = pdev->last_blue_balance;
+			break;
+		}
+		ret = pwc_get_u8_ctrl(pdev, GET_STATUS_CTL,
+				      READ_RED_GAIN_FORMATTER,
+				      &pdev->red_balance->val);
+		if (ret)
+			break;
+		ret = pwc_get_u8_ctrl(pdev, GET_STATUS_CTL,
+				      READ_BLUE_GAIN_FORMATTER,
+				      &pdev->blue_balance->val);
+		if (ret)
+			break;
+		pdev->last_red_balance  = pdev->red_balance->val;
+		pdev->last_blue_balance = pdev->blue_balance->val;
+		pdev->last_color_bal_update = jiffies;
+		pdev->color_bal_valid = true;
+		break;
+	case V4L2_CID_AUTOGAIN:
+		if (pdev->gain_valid && time_before(jiffies,
+				pdev->last_gain_update + HZ / 4)) {
+			pdev->gain->val = pdev->last_gain;
+			break;
+		}
+		ret = pwc_get_u8_ctrl(pdev, GET_STATUS_CTL,
+				      READ_AGC_FORMATTER, &pdev->gain->val);
+		if (ret)
+			break;
+		pdev->last_gain = pdev->gain->val;
+		pdev->last_gain_update = jiffies;
+		pdev->gain_valid = true;
+		if (!DEVICE_USE_CODEC3(pdev->type))
+			break;
+		/* For CODEC3 where autogain also controls expo */
+		/* fall through */
+	case V4L2_CID_EXPOSURE_AUTO:
+		if (pdev->exposure_valid && time_before(jiffies,
+				pdev->last_exposure_update + HZ / 4)) {
+			pdev->exposure->val = pdev->last_exposure;
+			break;
+		}
+		ret = pwc_get_u16_ctrl(pdev, GET_STATUS_CTL,
+				       READ_SHUTTER_FORMATTER,
+				       &pdev->exposure->val);
+		if (ret)
+			break;
+		pdev->last_exposure = pdev->exposure->val;
+		pdev->last_exposure_update = jiffies;
+		pdev->exposure_valid = true;
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	if (ret)
+		PWC_ERROR("g_ctrl %s error %d\n", ctrl->name, ret);
+
+	return ret;
+}
+
+static int pwc_set_awb(struct pwc_device *pdev)
+{
+	int ret;
+
+	if (pdev->auto_white_balance->is_new) {
+		ret = pwc_set_u8_ctrl(pdev, SET_CHROM_CTL,
+				      WB_MODE_FORMATTER,
+				      pdev->auto_white_balance->val);
+		if (ret)
+			return ret;
+
+		if (pdev->auto_white_balance->val != awb_manual)
+			pdev->color_bal_valid = false; /* Force cache update */
+
+		/*
+		 * If this is a preset, update our red / blue balance values
+		 * so that events get generated for the new preset values
+		 */
+		if (pdev->auto_white_balance->val == awb_indoor ||
+		    pdev->auto_white_balance->val == awb_outdoor ||
+		    pdev->auto_white_balance->val == awb_fl)
+			pwc_g_volatile_ctrl(pdev->auto_white_balance);
+	}
+	if (pdev->auto_white_balance->val != awb_manual)
+		return 0;
+
+	if (pdev->red_balance->is_new) {
+		ret = pwc_set_u8_ctrl(pdev, SET_CHROM_CTL,
+				      PRESET_MANUAL_RED_GAIN_FORMATTER,
+				      pdev->red_balance->val);
+		if (ret)
+			return ret;
+	}
+
+	if (pdev->blue_balance->is_new) {
+		ret = pwc_set_u8_ctrl(pdev, SET_CHROM_CTL,
+				      PRESET_MANUAL_BLUE_GAIN_FORMATTER,
+				      pdev->blue_balance->val);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+/* For CODEC2 models which have separate autogain and auto exposure */
+static int pwc_set_autogain(struct pwc_device *pdev)
+{
+	int ret;
+
+	if (pdev->autogain->is_new) {
+		ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL,
+				      AGC_MODE_FORMATTER,
+				      pdev->autogain->val ? 0 : 0xff);
+		if (ret)
+			return ret;
+
+		if (pdev->autogain->val)
+			pdev->gain_valid = false; /* Force cache update */
+	}
+
+	if (pdev->autogain->val)
+		return 0;
+
+	if (pdev->gain->is_new) {
+		ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL,
+				      PRESET_AGC_FORMATTER,
+				      pdev->gain->val);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+/* For CODEC2 models which have separate autogain and auto exposure */
+static int pwc_set_exposure_auto(struct pwc_device *pdev)
+{
+	int ret;
+	int is_auto = pdev->exposure_auto->val == V4L2_EXPOSURE_AUTO;
+
+	if (pdev->exposure_auto->is_new) {
+		ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL,
+				      SHUTTER_MODE_FORMATTER,
+				      is_auto ? 0 : 0xff);
+		if (ret)
+			return ret;
+
+		if (is_auto)
+			pdev->exposure_valid = false; /* Force cache update */
+	}
+
+	if (is_auto)
+		return 0;
+
+	if (pdev->exposure->is_new) {
+		ret = pwc_set_u16_ctrl(pdev, SET_LUM_CTL,
+				       PRESET_SHUTTER_FORMATTER,
+				       pdev->exposure->val);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+/* For CODEC3 models which have autogain controlling both gain and exposure */
+static int pwc_set_autogain_expo(struct pwc_device *pdev)
+{
+	int ret;
+
+	if (pdev->autogain->is_new) {
+		ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL,
+				      AGC_MODE_FORMATTER,
+				      pdev->autogain->val ? 0 : 0xff);
+		if (ret)
+			return ret;
+
+		if (pdev->autogain->val) {
+			pdev->gain_valid     = false; /* Force cache update */
+			pdev->exposure_valid = false; /* Force cache update */
+		}
+	}
+
+	if (pdev->autogain->val)
+		return 0;
+
+	if (pdev->gain->is_new) {
+		ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL,
+				      PRESET_AGC_FORMATTER,
+				      pdev->gain->val);
+		if (ret)
+			return ret;
+	}
+
+	if (pdev->exposure->is_new) {
+		ret = pwc_set_u16_ctrl(pdev, SET_LUM_CTL,
+				       PRESET_SHUTTER_FORMATTER,
+				       pdev->exposure->val);
+		if (ret)
+			return ret;
+	}
+	return 0;
+}
+
+static int pwc_set_motor(struct pwc_device *pdev)
+{
+	int ret;
+
+	pdev->ctrl_buf[0] = 0;
+	if (pdev->motor_pan_reset->is_new)
+		pdev->ctrl_buf[0] |= 0x01;
+	if (pdev->motor_tilt_reset->is_new)
+		pdev->ctrl_buf[0] |= 0x02;
+	if (pdev->motor_pan_reset->is_new || pdev->motor_tilt_reset->is_new) {
+		ret = send_control_msg(pdev, SET_MPT_CTL,
+				       PT_RESET_CONTROL_FORMATTER,
+				       pdev->ctrl_buf, 1);
+		if (ret < 0)
+			return ret;
+	}
+
+	memset(pdev->ctrl_buf, 0, 4);
+	if (pdev->motor_pan->is_new) {
+		pdev->ctrl_buf[0] = pdev->motor_pan->val & 0xFF;
+		pdev->ctrl_buf[1] = (pdev->motor_pan->val >> 8);
+	}
+	if (pdev->motor_tilt->is_new) {
+		pdev->ctrl_buf[2] = pdev->motor_tilt->val & 0xFF;
+		pdev->ctrl_buf[3] = (pdev->motor_tilt->val >> 8);
+	}
+	if (pdev->motor_pan->is_new || pdev->motor_tilt->is_new) {
+		ret = send_control_msg(pdev, SET_MPT_CTL,
+				       PT_RELATIVE_CONTROL_FORMATTER,
+				       pdev->ctrl_buf, 4);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int pwc_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct pwc_device *pdev =
+		container_of(ctrl->handler, struct pwc_device, ctrl_handler);
+	int ret = 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL,
+				      BRIGHTNESS_FORMATTER, ctrl->val);
+		break;
+	case V4L2_CID_CONTRAST:
+		ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL,
+				      CONTRAST_FORMATTER, ctrl->val);
+		break;
+	case V4L2_CID_SATURATION:
+		ret = pwc_set_s8_ctrl(pdev, SET_CHROM_CTL,
+				      pdev->saturation_fmt, ctrl->val);
+		break;
+	case V4L2_CID_GAMMA:
+		ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL,
+				      GAMMA_FORMATTER, ctrl->val);
+		break;
+	case V4L2_CID_AUTO_WHITE_BALANCE:
+		ret = pwc_set_awb(pdev);
+		break;
+	case V4L2_CID_AUTOGAIN:
+		if (DEVICE_USE_CODEC2(pdev->type))
+			ret = pwc_set_autogain(pdev);
+		else if (DEVICE_USE_CODEC3(pdev->type))
+			ret = pwc_set_autogain_expo(pdev);
+		else
+			ret = -EINVAL;
+		break;
+	case V4L2_CID_EXPOSURE_AUTO:
+		if (DEVICE_USE_CODEC2(pdev->type))
+			ret = pwc_set_exposure_auto(pdev);
+		else
+			ret = -EINVAL;
+		break;
+	case V4L2_CID_COLORFX:
+		ret = pwc_set_u8_ctrl(pdev, SET_CHROM_CTL,
+				      COLOUR_MODE_FORMATTER,
+				      ctrl->val ? 0 : 0xff);
+		break;
+	case PWC_CID_CUSTOM(autocontour):
+		if (pdev->autocontour->is_new) {
+			ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL,
+					AUTO_CONTOUR_FORMATTER,
+					pdev->autocontour->val ? 0 : 0xff);
+		}
+		if (ret == 0 && pdev->contour->is_new) {
+			ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL,
+					      PRESET_CONTOUR_FORMATTER,
+					      pdev->contour->val);
+		}
+		break;
+	case V4L2_CID_BACKLIGHT_COMPENSATION:
+		ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL,
+				      BACK_LIGHT_COMPENSATION_FORMATTER,
+				      ctrl->val ? 0 : 0xff);
+		break;
+	case V4L2_CID_BAND_STOP_FILTER:
+		ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL,
+				      FLICKERLESS_MODE_FORMATTER,
+				      ctrl->val ? 0 : 0xff);
+		break;
+	case PWC_CID_CUSTOM(noise_reduction):
+		ret = pwc_set_u8_ctrl(pdev, SET_LUM_CTL,
+				      DYNAMIC_NOISE_CONTROL_FORMATTER,
+				      ctrl->val);
+		break;
+	case PWC_CID_CUSTOM(save_user):
+		ret = pwc_button_ctrl(pdev, SAVE_USER_DEFAULTS_FORMATTER);
+		break;
+	case PWC_CID_CUSTOM(restore_user):
+		ret = pwc_button_ctrl(pdev, RESTORE_USER_DEFAULTS_FORMATTER);
+		break;
+	case PWC_CID_CUSTOM(restore_factory):
+		ret = pwc_button_ctrl(pdev,
+				      RESTORE_FACTORY_DEFAULTS_FORMATTER);
+		break;
+	case PWC_CID_CUSTOM(awb_speed):
+		ret = pwc_set_u8_ctrl(pdev, SET_CHROM_CTL,
+				      AWB_CONTROL_SPEED_FORMATTER,
+				      ctrl->val);
+		break;
+	case PWC_CID_CUSTOM(awb_delay):
+		ret = pwc_set_u8_ctrl(pdev, SET_CHROM_CTL,
+				      AWB_CONTROL_DELAY_FORMATTER,
+				      ctrl->val);
+		break;
+	case V4L2_CID_PAN_RELATIVE:
+		ret = pwc_set_motor(pdev);
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	if (ret)
+		PWC_ERROR("s_ctrl %s error %d\n", ctrl->name, ret);
+
+	return ret;
+}
+
+static int pwc_enum_fmt_vid_cap(struct file *file, void *fh, struct v4l2_fmtdesc *f)
+{
+	struct pwc_device *pdev = video_drvdata(file);
+
+	/* We only support two format: the raw format, and YUV */
+	switch (f->index) {
+	case 0:
+		/* RAW format */
+		f->pixelformat = pdev->type <= 646 ? V4L2_PIX_FMT_PWC1 : V4L2_PIX_FMT_PWC2;
+		f->flags = V4L2_FMT_FLAG_COMPRESSED;
+		strlcpy(f->description, "Raw Philips Webcam", sizeof(f->description));
+		break;
+	case 1:
+		f->pixelformat = V4L2_PIX_FMT_YUV420;
+		strlcpy(f->description, "4:2:0, planar, Y-Cb-Cr", sizeof(f->description));
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int pwc_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f)
+{
+	struct pwc_device *pdev = video_drvdata(file);
+
+	if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	PWC_DEBUG_IOCTL("ioctl(VIDIOC_G_FMT) return size %dx%d\n",
+			pdev->width, pdev->height);
+	pwc_vidioc_fill_fmt(f, pdev->width, pdev->height, pdev->pixfmt);
+	return 0;
+}
+
+static int pwc_try_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *f)
+{
+	struct pwc_device *pdev = video_drvdata(file);
+
+	return pwc_vidioc_try_fmt(pdev, f);
+}
+
+static int pwc_enum_framesizes(struct file *file, void *fh,
+					 struct v4l2_frmsizeenum *fsize)
+{
+	struct pwc_device *pdev = video_drvdata(file);
+	unsigned int i = 0, index = fsize->index;
+
+	if (fsize->pixel_format == V4L2_PIX_FMT_YUV420 ||
+	    (fsize->pixel_format == V4L2_PIX_FMT_PWC1 &&
+			DEVICE_USE_CODEC1(pdev->type)) ||
+	    (fsize->pixel_format == V4L2_PIX_FMT_PWC2 &&
+			DEVICE_USE_CODEC23(pdev->type))) {
+		for (i = 0; i < PSZ_MAX; i++) {
+			if (!(pdev->image_mask & (1UL << i)))
+				continue;
+			if (!index--) {
+				fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+				fsize->discrete.width = pwc_image_sizes[i][0];
+				fsize->discrete.height = pwc_image_sizes[i][1];
+				return 0;
+			}
+		}
+	}
+	return -EINVAL;
+}
+
+static int pwc_enum_frameintervals(struct file *file, void *fh,
+					   struct v4l2_frmivalenum *fival)
+{
+	struct pwc_device *pdev = video_drvdata(file);
+	int size = -1;
+	unsigned int i;
+
+	for (i = 0; i < PSZ_MAX; i++) {
+		if (pwc_image_sizes[i][0] == fival->width &&
+				pwc_image_sizes[i][1] == fival->height) {
+			size = i;
+			break;
+		}
+	}
+
+	/* TODO: Support raw format */
+	if (size < 0 || fival->pixel_format != V4L2_PIX_FMT_YUV420)
+		return -EINVAL;
+
+	i = pwc_get_fps(pdev, fival->index, size);
+	if (!i)
+		return -EINVAL;
+
+	fival->type = V4L2_FRMIVAL_TYPE_DISCRETE;
+	fival->discrete.numerator = 1;
+	fival->discrete.denominator = i;
+
+	return 0;
+}
+
+static int pwc_g_parm(struct file *file, void *fh,
+		      struct v4l2_streamparm *parm)
+{
+	struct pwc_device *pdev = video_drvdata(file);
+
+	if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	memset(parm, 0, sizeof(*parm));
+
+	parm->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	parm->parm.capture.readbuffers = MIN_FRAMES;
+	parm->parm.capture.capability |= V4L2_CAP_TIMEPERFRAME;
+	parm->parm.capture.timeperframe.denominator = pdev->vframes;
+	parm->parm.capture.timeperframe.numerator = 1;
+
+	return 0;
+}
+
+static int pwc_s_parm(struct file *file, void *fh,
+		      struct v4l2_streamparm *parm)
+{
+	struct pwc_device *pdev = video_drvdata(file);
+	int compression = 0;
+	int ret, fps;
+
+	if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	/* If timeperframe == 0, then reset the framerate to the nominal value.
+	   We pick a high framerate here, and let pwc_set_video_mode() figure
+	   out the best match. */
+	if (parm->parm.capture.timeperframe.numerator == 0 ||
+	    parm->parm.capture.timeperframe.denominator == 0)
+		fps = 30;
+	else
+		fps = parm->parm.capture.timeperframe.denominator /
+		      parm->parm.capture.timeperframe.numerator;
+
+	if (vb2_is_busy(&pdev->vb_queue))
+		return -EBUSY;
+
+	ret = pwc_set_video_mode(pdev, pdev->width, pdev->height, pdev->pixfmt,
+				 fps, &compression, 0);
+
+	pwc_g_parm(file, fh, parm);
+
+	return ret;
+}
+
+const struct v4l2_ioctl_ops pwc_ioctl_ops = {
+	.vidioc_querycap		    = pwc_querycap,
+	.vidioc_enum_input		    = pwc_enum_input,
+	.vidioc_g_input			    = pwc_g_input,
+	.vidioc_s_input			    = pwc_s_input,
+	.vidioc_enum_fmt_vid_cap	    = pwc_enum_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap		    = pwc_g_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap		    = pwc_s_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap		    = pwc_try_fmt_vid_cap,
+	.vidioc_reqbufs			    = vb2_ioctl_reqbufs,
+	.vidioc_querybuf		    = vb2_ioctl_querybuf,
+	.vidioc_qbuf			    = vb2_ioctl_qbuf,
+	.vidioc_dqbuf			    = vb2_ioctl_dqbuf,
+	.vidioc_streamon		    = vb2_ioctl_streamon,
+	.vidioc_streamoff		    = vb2_ioctl_streamoff,
+	.vidioc_log_status		    = v4l2_ctrl_log_status,
+	.vidioc_enum_framesizes		    = pwc_enum_framesizes,
+	.vidioc_enum_frameintervals	    = pwc_enum_frameintervals,
+	.vidioc_g_parm			    = pwc_g_parm,
+	.vidioc_s_parm			    = pwc_s_parm,
+	.vidioc_subscribe_event		    = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event	    = v4l2_event_unsubscribe,
+};
diff --git a/drivers/media/usb/pwc/pwc.h b/drivers/media/usb/pwc/pwc.h
new file mode 100644
index 0000000..6701001
--- /dev/null
+++ b/drivers/media/usb/pwc/pwc.h
@@ -0,0 +1,395 @@
+/* (C) 1999-2003 Nemosoft Unv.
+   (C) 2004-2006 Luc Saillard (luc@saillard.org)
+
+   NOTE: this version of pwc is an unofficial (modified) release of pwc & pcwx
+   driver and thus may have bugs that are not present in the original version.
+   Please send bug reports and support requests to <luc@saillard.org>.
+   The decompression routines have been implemented by reverse-engineering the
+   Nemosoft binary pwcx module. Caveat emptor.
+
+   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
+*/
+
+#ifndef PWC_H
+#define PWC_H
+
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/spinlock.h>
+#include <linux/wait.h>
+#include <linux/mutex.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <asm/errno.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-fh.h>
+#include <media/v4l2-event.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-vmalloc.h>
+#ifdef CONFIG_USB_PWC_INPUT_EVDEV
+#include <linux/input.h>
+#endif
+#include "pwc-dec1.h"
+#include "pwc-dec23.h"
+
+/* Version block */
+#define PWC_VERSION	"10.0.15"
+#define PWC_NAME	"pwc"
+#define PFX		PWC_NAME ": "
+
+
+/* Trace certain actions in the driver */
+#define PWC_DEBUG_LEVEL_MODULE	(1<<0)
+#define PWC_DEBUG_LEVEL_PROBE	(1<<1)
+#define PWC_DEBUG_LEVEL_OPEN	(1<<2)
+#define PWC_DEBUG_LEVEL_READ	(1<<3)
+#define PWC_DEBUG_LEVEL_MEMORY	(1<<4)
+#define PWC_DEBUG_LEVEL_FLOW	(1<<5)
+#define PWC_DEBUG_LEVEL_SIZE	(1<<6)
+#define PWC_DEBUG_LEVEL_IOCTL	(1<<7)
+#define PWC_DEBUG_LEVEL_TRACE	(1<<8)
+
+#define PWC_DEBUG_MODULE(fmt, args...) PWC_DEBUG(MODULE, fmt, ##args)
+#define PWC_DEBUG_PROBE(fmt, args...) PWC_DEBUG(PROBE, fmt, ##args)
+#define PWC_DEBUG_OPEN(fmt, args...) PWC_DEBUG(OPEN, fmt, ##args)
+#define PWC_DEBUG_READ(fmt, args...) PWC_DEBUG(READ, fmt, ##args)
+#define PWC_DEBUG_MEMORY(fmt, args...) PWC_DEBUG(MEMORY, fmt, ##args)
+#define PWC_DEBUG_FLOW(fmt, args...) PWC_DEBUG(FLOW, fmt, ##args)
+#define PWC_DEBUG_SIZE(fmt, args...) PWC_DEBUG(SIZE, fmt, ##args)
+#define PWC_DEBUG_IOCTL(fmt, args...) PWC_DEBUG(IOCTL, fmt, ##args)
+#define PWC_DEBUG_TRACE(fmt, args...) PWC_DEBUG(TRACE, fmt, ##args)
+
+
+#ifdef CONFIG_USB_PWC_DEBUG
+
+#define PWC_DEBUG_LEVEL	(PWC_DEBUG_LEVEL_MODULE)
+
+#define PWC_DEBUG(level, fmt, args...) do {\
+	if ((PWC_DEBUG_LEVEL_ ##level) & pwc_trace) \
+		printk(KERN_DEBUG PFX fmt, ##args); \
+	} while (0)
+
+#define PWC_ERROR(fmt, args...) printk(KERN_ERR PFX fmt, ##args)
+#define PWC_WARNING(fmt, args...) printk(KERN_WARNING PFX fmt, ##args)
+#define PWC_INFO(fmt, args...) printk(KERN_INFO PFX fmt, ##args)
+#define PWC_TRACE(fmt, args...) PWC_DEBUG(TRACE, fmt, ##args)
+
+#else /* if ! CONFIG_USB_PWC_DEBUG */
+
+#define PWC_ERROR(fmt, args...) printk(KERN_ERR PFX fmt, ##args)
+#define PWC_WARNING(fmt, args...) printk(KERN_WARNING PFX fmt, ##args)
+#define PWC_INFO(fmt, args...) printk(KERN_INFO PFX fmt, ##args)
+#define PWC_TRACE(fmt, args...) do { } while(0)
+#define PWC_DEBUG(level, fmt, args...) do { } while(0)
+
+#define pwc_trace 0
+
+#endif
+
+/* Defines for ToUCam cameras */
+#define TOUCAM_HEADER_SIZE		8
+#define TOUCAM_TRAILER_SIZE		4
+
+#define FEATURE_MOTOR_PANTILT		0x0001
+#define FEATURE_CODEC1			0x0002
+#define FEATURE_CODEC2			0x0004
+
+#define MAX_WIDTH		640
+#define MAX_HEIGHT		480
+
+/* Ignore errors in the first N frames, to allow for startup delays */
+#define FRAME_LOWMARK 5
+
+/* Size and number of buffers for the ISO pipe. */
+#define MAX_ISO_BUFS		3
+#define ISO_FRAMES_PER_DESC	10
+#define ISO_MAX_FRAME_SIZE	960
+#define ISO_BUFFER_SIZE		(ISO_FRAMES_PER_DESC * ISO_MAX_FRAME_SIZE)
+
+/* Maximum size after decompression is 640x480 YUV data, 1.5 * 640 * 480 */
+#define PWC_FRAME_SIZE		(460800 + TOUCAM_HEADER_SIZE + TOUCAM_TRAILER_SIZE)
+
+/* Absolute minimum and maximum number of buffers available for mmap() */
+#define MIN_FRAMES		2
+#define MAX_FRAMES		16
+
+/* Some macros to quickly find the type of a webcam */
+#define DEVICE_USE_CODEC1(x) ((x)<675)
+#define DEVICE_USE_CODEC2(x) ((x)>=675 && (x)<700)
+#define DEVICE_USE_CODEC3(x) ((x)>=700)
+#define DEVICE_USE_CODEC23(x) ((x)>=675)
+
+/* Request types: video */
+#define SET_LUM_CTL			0x01
+#define GET_LUM_CTL			0x02
+#define SET_CHROM_CTL			0x03
+#define GET_CHROM_CTL			0x04
+#define SET_STATUS_CTL			0x05
+#define GET_STATUS_CTL			0x06
+#define SET_EP_STREAM_CTL		0x07
+#define GET_EP_STREAM_CTL		0x08
+#define GET_XX_CTL			0x09
+#define SET_XX_CTL			0x0A
+#define GET_XY_CTL			0x0B
+#define SET_XY_CTL			0x0C
+#define SET_MPT_CTL			0x0D
+#define GET_MPT_CTL			0x0E
+
+/* Selectors for the Luminance controls [GS]ET_LUM_CTL */
+#define AGC_MODE_FORMATTER			0x2000
+#define PRESET_AGC_FORMATTER			0x2100
+#define SHUTTER_MODE_FORMATTER			0x2200
+#define PRESET_SHUTTER_FORMATTER		0x2300
+#define PRESET_CONTOUR_FORMATTER		0x2400
+#define AUTO_CONTOUR_FORMATTER			0x2500
+#define BACK_LIGHT_COMPENSATION_FORMATTER	0x2600
+#define CONTRAST_FORMATTER			0x2700
+#define DYNAMIC_NOISE_CONTROL_FORMATTER		0x2800
+#define FLICKERLESS_MODE_FORMATTER		0x2900
+#define AE_CONTROL_SPEED			0x2A00
+#define BRIGHTNESS_FORMATTER			0x2B00
+#define GAMMA_FORMATTER				0x2C00
+
+/* Selectors for the Chrominance controls [GS]ET_CHROM_CTL */
+#define WB_MODE_FORMATTER			0x1000
+#define AWB_CONTROL_SPEED_FORMATTER		0x1100
+#define AWB_CONTROL_DELAY_FORMATTER		0x1200
+#define PRESET_MANUAL_RED_GAIN_FORMATTER	0x1300
+#define PRESET_MANUAL_BLUE_GAIN_FORMATTER	0x1400
+#define COLOUR_MODE_FORMATTER			0x1500
+#define SATURATION_MODE_FORMATTER1		0x1600
+#define SATURATION_MODE_FORMATTER2		0x1700
+
+/* Selectors for the Status controls [GS]ET_STATUS_CTL */
+#define SAVE_USER_DEFAULTS_FORMATTER		0x0200
+#define RESTORE_USER_DEFAULTS_FORMATTER		0x0300
+#define RESTORE_FACTORY_DEFAULTS_FORMATTER	0x0400
+#define READ_AGC_FORMATTER			0x0500
+#define READ_SHUTTER_FORMATTER			0x0600
+#define READ_RED_GAIN_FORMATTER			0x0700
+#define READ_BLUE_GAIN_FORMATTER		0x0800
+
+/* Formatters for the motorized pan & tilt [GS]ET_MPT_CTL */
+#define PT_RELATIVE_CONTROL_FORMATTER		0x01
+#define PT_RESET_CONTROL_FORMATTER		0x02
+#define PT_STATUS_FORMATTER			0x03
+
+/* Enumeration of image sizes */
+#define PSZ_SQCIF	0x00
+#define PSZ_QSIF	0x01
+#define PSZ_QCIF	0x02
+#define PSZ_SIF		0x03
+#define PSZ_CIF		0x04
+#define PSZ_VGA		0x05
+#define PSZ_MAX		6
+
+struct pwc_raw_frame {
+	__le16 type;		/* type of the webcam */
+	__le16 vbandlength;	/* Size of 4 lines compressed (used by the
+				   decompressor) */
+	__u8   cmd[4];		/* the four byte of the command (in case of
+				   nala, only the first 3 bytes is filled) */
+	__u8   rawframe[0];	/* frame_size = H / 4 * vbandlength */
+} __packed;
+
+/* intermediate buffers with raw data from the USB cam */
+struct pwc_frame_buf
+{
+	/* common v4l buffer stuff -- must be first */
+	struct vb2_v4l2_buffer vb;
+	struct list_head list;
+	void *data;
+	int filled;		/* number of bytes filled */
+};
+
+struct pwc_device
+{
+	struct video_device vdev;
+	struct v4l2_device v4l2_dev;
+
+	/* videobuf2 queue and queued buffers list */
+	struct vb2_queue vb_queue;
+	struct list_head queued_bufs;
+	spinlock_t queued_bufs_lock; /* Protects queued_bufs */
+
+	/* If taking both locks vb_queue_lock must always be locked first! */
+	struct mutex v4l2_lock;      /* Protects everything else */
+	struct mutex vb_queue_lock;  /* Protects vb_queue and capt_file */
+
+	/* Pointer to our usb_device, will be NULL after unplug */
+	struct usb_device *udev; /* Both mutexes most be hold when setting! */
+
+	/* type of cam (645, 646, 675, 680, 690, 720, 730, 740, 750) */
+	int type;
+	int release;		/* release number */
+	int features;		/* feature bits */
+
+	/*** Video data ***/
+	int vendpoint;		/* video isoc endpoint */
+	int vcinterface;	/* video control interface */
+	int valternate;		/* alternate interface needed */
+	int vframes;		/* frames-per-second */
+	int pixfmt;		/* pixelformat: V4L2_PIX_FMT_YUV420 or _PWCX */
+	int vframe_count;	/* received frames */
+	int vmax_packet_size;	/* USB maxpacket size */
+	int vlast_packet_size;	/* for frame synchronisation */
+	int visoc_errors;	/* number of contiguous ISOC errors */
+	int vbandlength;	/* compressed band length; 0 is uncompressed */
+	char vsync;		/* used by isoc handler */
+	char vmirror;		/* for ToUCaM series */
+	char power_save;	/* Do powersaving for this cam */
+
+	unsigned char cmd_buf[13];
+	unsigned char *ctrl_buf;
+
+	struct urb *urbs[MAX_ISO_BUFS];
+
+	/*
+	 * Frame currently being filled, this only gets touched by the
+	 * isoc urb complete handler, and by stream start / stop since
+	 * start / stop touch it before / after starting / killing the urbs
+	 * no locking is needed around this
+	 */
+	struct pwc_frame_buf *fill_buf;
+
+	int frame_header_size, frame_trailer_size;
+	int frame_size;
+	int frame_total_size;	/* including header & trailer */
+	int drop_frames;
+
+	union {	/* private data for decompression engine */
+		struct pwc_dec1_private dec1;
+		struct pwc_dec23_private dec23;
+	};
+
+	/*
+	 * We have an 'image' and a 'view', where 'image' is the fixed-size img
+	 * as delivered by the camera, and 'view' is the size requested by the
+	 * program. The camera image is centered in this viewport, laced with
+	 * a gray or black border. view_min <= image <= view <= view_max;
+	 */
+	int image_mask;				/* supported sizes */
+	int width, height;			/* current resolution */
+
+#ifdef CONFIG_USB_PWC_INPUT_EVDEV
+	struct input_dev *button_dev;	/* webcam snapshot button input */
+	char button_phys[64];
+#endif
+
+	/* controls */
+	struct v4l2_ctrl_handler	ctrl_handler;
+	u16				saturation_fmt;
+	struct v4l2_ctrl		*brightness;
+	struct v4l2_ctrl		*contrast;
+	struct v4l2_ctrl		*saturation;
+	struct v4l2_ctrl		*gamma;
+	struct {
+		/* awb / red-blue balance cluster */
+		struct v4l2_ctrl	*auto_white_balance;
+		struct v4l2_ctrl	*red_balance;
+		struct v4l2_ctrl	*blue_balance;
+		/* usb ctrl transfers are slow, so we cache things */
+		int			color_bal_valid;
+		unsigned long		last_color_bal_update; /* In jiffies */
+		s32			last_red_balance;
+		s32			last_blue_balance;
+	};
+	struct {
+		/* autogain / gain cluster */
+		struct v4l2_ctrl	*autogain;
+		struct v4l2_ctrl	*gain;
+		int			gain_valid;
+		unsigned long		last_gain_update; /* In jiffies */
+		s32			last_gain;
+	};
+	struct {
+		/* exposure_auto / exposure cluster */
+		struct v4l2_ctrl	*exposure_auto;
+		struct v4l2_ctrl	*exposure;
+		int			exposure_valid;
+		unsigned long		last_exposure_update; /* In jiffies */
+		s32			last_exposure;
+	};
+	struct v4l2_ctrl		*colorfx;
+	struct {
+		/* autocontour/contour cluster */
+		struct v4l2_ctrl	*autocontour;
+		struct v4l2_ctrl	*contour;
+	};
+	struct v4l2_ctrl		*backlight;
+	struct v4l2_ctrl		*flicker;
+	struct v4l2_ctrl		*noise_reduction;
+	struct v4l2_ctrl		*save_user;
+	struct v4l2_ctrl		*restore_user;
+	struct v4l2_ctrl		*restore_factory;
+	struct v4l2_ctrl		*awb_speed;
+	struct v4l2_ctrl		*awb_delay;
+	struct {
+		/* motor control cluster */
+		struct v4l2_ctrl	*motor_pan;
+		struct v4l2_ctrl	*motor_tilt;
+		struct v4l2_ctrl	*motor_pan_reset;
+		struct v4l2_ctrl	*motor_tilt_reset;
+	};
+	/* CODEC3 models have both gain and exposure controlled by autogain */
+	struct v4l2_ctrl		*autogain_expo_cluster[3];
+};
+
+/* Global variables */
+#ifdef CONFIG_USB_PWC_DEBUG
+extern int pwc_trace;
+#endif
+
+/** Functions in pwc-misc.c */
+/* sizes in pixels */
+extern const int pwc_image_sizes[PSZ_MAX][2];
+
+int pwc_get_size(struct pwc_device *pdev, int width, int height);
+void pwc_construct(struct pwc_device *pdev);
+
+/** Functions in pwc-ctrl.c */
+/* Request a certain video mode. Returns < 0 if not possible */
+extern int pwc_set_video_mode(struct pwc_device *pdev, int width, int height,
+	int pixfmt, int frames, int *compression, int send_to_cam);
+extern unsigned int pwc_get_fps(struct pwc_device *pdev, unsigned int index, unsigned int size);
+extern int pwc_set_leds(struct pwc_device *pdev, int on_value, int off_value);
+extern int pwc_get_cmos_sensor(struct pwc_device *pdev, int *sensor);
+extern int send_control_msg(struct pwc_device *pdev,
+			    u8 request, u16 value, void *buf, int buflen);
+
+/* Control get / set helpers */
+int pwc_get_u8_ctrl(struct pwc_device *pdev, u8 request, u16 value, int *data);
+int pwc_set_u8_ctrl(struct pwc_device *pdev, u8 request, u16 value, u8 data);
+int pwc_get_s8_ctrl(struct pwc_device *pdev, u8 request, u16 value, int *data);
+#define pwc_set_s8_ctrl pwc_set_u8_ctrl
+int pwc_get_u16_ctrl(struct pwc_device *pdev, u8 request, u16 value, int *dat);
+int pwc_set_u16_ctrl(struct pwc_device *pdev, u8 request, u16 value, u16 data);
+int pwc_button_ctrl(struct pwc_device *pdev, u16 value);
+int pwc_init_controls(struct pwc_device *pdev);
+
+/* Power down or up the camera; not supported by all models */
+extern void pwc_camera_power(struct pwc_device *pdev, int power);
+
+extern const struct v4l2_ioctl_ops pwc_ioctl_ops;
+
+/** pwc-uncompress.c */
+/* Expand frame to image, possibly including decompression. Uses read_frame and fill_image */
+int pwc_decompress(struct pwc_device *pdev, struct pwc_frame_buf *fbuf);
+
+#endif
diff --git a/drivers/media/usb/rainshadow-cec/Kconfig b/drivers/media/usb/rainshadow-cec/Kconfig
new file mode 100644
index 0000000..030ef01
--- /dev/null
+++ b/drivers/media/usb/rainshadow-cec/Kconfig
@@ -0,0 +1,11 @@
+config USB_RAINSHADOW_CEC
+	tristate "RainShadow Tech HDMI CEC"
+	depends on USB_ACM
+	select CEC_CORE
+	select SERIO
+	select SERIO_SERPORT
+	---help---
+	  This is a cec driver for the RainShadow Tech HDMI CEC device.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called rainshadow-cec.
diff --git a/drivers/media/usb/rainshadow-cec/Makefile b/drivers/media/usb/rainshadow-cec/Makefile
new file mode 100644
index 0000000..a79fbc7
--- /dev/null
+++ b/drivers/media/usb/rainshadow-cec/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_USB_RAINSHADOW_CEC) += rainshadow-cec.o
diff --git a/drivers/media/usb/rainshadow-cec/rainshadow-cec.c b/drivers/media/usb/rainshadow-cec/rainshadow-cec.c
new file mode 100644
index 0000000..cecdcbc
--- /dev/null
+++ b/drivers/media/usb/rainshadow-cec/rainshadow-cec.c
@@ -0,0 +1,384 @@
+/*
+ * RainShadow Tech HDMI CEC driver
+ *
+ * Copyright 2016 Hans Verkuil <hverkuil@xs4all.nl
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version of 2 of the License, or (at your
+ * option) any later version. See the file COPYING in the main directory of
+ * this archive for more details.
+ */
+
+/*
+ * Notes:
+ *
+ * The higher level protocols are currently disabled. This can be added
+ * later, similar to how this is done for the Pulse Eight CEC driver.
+ *
+ * Documentation of the protocol is available here:
+ *
+ * http://rainshadowtech.com/doc/HDMICECtoUSBandRS232v2.0.pdf
+ */
+
+#include <linux/completion.h>
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/serio.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/time.h>
+#include <linux/workqueue.h>
+
+#include <media/cec.h>
+
+MODULE_AUTHOR("Hans Verkuil <hverkuil@xs4all.nl>");
+MODULE_DESCRIPTION("RainShadow Tech HDMI CEC driver");
+MODULE_LICENSE("GPL");
+
+#define DATA_SIZE 256
+
+struct rain {
+	struct device *dev;
+	struct serio *serio;
+	struct cec_adapter *adap;
+	struct completion cmd_done;
+	struct work_struct work;
+
+	/* Low-level ringbuffer, collecting incoming characters */
+	char buf[DATA_SIZE];
+	unsigned int buf_rd_idx;
+	unsigned int buf_wr_idx;
+	unsigned int buf_len;
+	spinlock_t buf_lock;
+
+	/* command buffer */
+	char cmd[DATA_SIZE];
+	unsigned int cmd_idx;
+	bool cmd_started;
+
+	/* reply to a command, only used to store the firmware version */
+	char cmd_reply[DATA_SIZE];
+
+	struct mutex write_lock;
+};
+
+static void rain_process_msg(struct rain *rain)
+{
+	struct cec_msg msg = {};
+	const char *cmd = rain->cmd + 3;
+	int stat = -1;
+
+	for (; *cmd; cmd++) {
+		if (!isxdigit(*cmd))
+			continue;
+		if (isxdigit(cmd[0]) && isxdigit(cmd[1])) {
+			if (msg.len == CEC_MAX_MSG_SIZE)
+				break;
+			if (hex2bin(msg.msg + msg.len, cmd, 1))
+				continue;
+			msg.len++;
+			cmd++;
+			continue;
+		}
+		if (!cmd[1])
+			stat = hex_to_bin(cmd[0]);
+		break;
+	}
+
+	if (rain->cmd[0] == 'R') {
+		if (stat == 1 || stat == 2)
+			cec_received_msg(rain->adap, &msg);
+		return;
+	}
+
+	switch (stat) {
+	case 1:
+		cec_transmit_attempt_done(rain->adap, CEC_TX_STATUS_OK);
+		break;
+	case 2:
+		cec_transmit_attempt_done(rain->adap, CEC_TX_STATUS_NACK);
+		break;
+	default:
+		cec_transmit_attempt_done(rain->adap, CEC_TX_STATUS_LOW_DRIVE);
+		break;
+	}
+}
+
+static void rain_irq_work_handler(struct work_struct *work)
+{
+	struct rain *rain =
+		container_of(work, struct rain, work);
+
+	while (true) {
+		unsigned long flags;
+		char data;
+
+		spin_lock_irqsave(&rain->buf_lock, flags);
+		if (!rain->buf_len) {
+			spin_unlock_irqrestore(&rain->buf_lock, flags);
+			break;
+		}
+
+		data = rain->buf[rain->buf_rd_idx];
+		rain->buf_len--;
+		rain->buf_rd_idx = (rain->buf_rd_idx + 1) & 0xff;
+
+		spin_unlock_irqrestore(&rain->buf_lock, flags);
+
+		if (!rain->cmd_started && data != '?')
+			continue;
+
+		switch (data) {
+		case '\r':
+			rain->cmd[rain->cmd_idx] = '\0';
+			dev_dbg(rain->dev, "received: %s\n", rain->cmd);
+			if (!memcmp(rain->cmd, "REC", 3) ||
+			    !memcmp(rain->cmd, "STA", 3)) {
+				rain_process_msg(rain);
+			} else {
+				strcpy(rain->cmd_reply, rain->cmd);
+				complete(&rain->cmd_done);
+			}
+			rain->cmd_idx = 0;
+			rain->cmd_started = false;
+			break;
+
+		case '\n':
+			rain->cmd_idx = 0;
+			rain->cmd_started = false;
+			break;
+
+		case '?':
+			rain->cmd_idx = 0;
+			rain->cmd_started = true;
+			break;
+
+		default:
+			if (rain->cmd_idx >= DATA_SIZE - 1) {
+				dev_dbg(rain->dev,
+					"throwing away %d bytes of garbage\n", rain->cmd_idx);
+				rain->cmd_idx = 0;
+			}
+			rain->cmd[rain->cmd_idx++] = data;
+			break;
+		}
+	}
+}
+
+static irqreturn_t rain_interrupt(struct serio *serio, unsigned char data,
+				    unsigned int flags)
+{
+	struct rain *rain = serio_get_drvdata(serio);
+
+	if (rain->buf_len == DATA_SIZE) {
+		dev_warn_once(rain->dev, "buffer overflow\n");
+		return IRQ_HANDLED;
+	}
+	spin_lock(&rain->buf_lock);
+	rain->buf_len++;
+	rain->buf[rain->buf_wr_idx] = data;
+	rain->buf_wr_idx = (rain->buf_wr_idx + 1) & 0xff;
+	spin_unlock(&rain->buf_lock);
+	schedule_work(&rain->work);
+	return IRQ_HANDLED;
+}
+
+static void rain_disconnect(struct serio *serio)
+{
+	struct rain *rain = serio_get_drvdata(serio);
+
+	cancel_work_sync(&rain->work);
+	cec_unregister_adapter(rain->adap);
+	dev_info(&serio->dev, "disconnected\n");
+	serio_close(serio);
+	serio_set_drvdata(serio, NULL);
+	kfree(rain);
+}
+
+static int rain_send(struct rain *rain, const char *command)
+{
+	int err = serio_write(rain->serio, '!');
+
+	dev_dbg(rain->dev, "send: %s\n", command);
+	while (!err && *command)
+		err = serio_write(rain->serio, *command++);
+	if (!err)
+		err = serio_write(rain->serio, '~');
+
+	return err;
+}
+
+static int rain_send_and_wait(struct rain *rain,
+			      const char *cmd, const char *reply)
+{
+	int err;
+
+	init_completion(&rain->cmd_done);
+
+	mutex_lock(&rain->write_lock);
+	err = rain_send(rain, cmd);
+	if (err)
+		goto err;
+
+	if (!wait_for_completion_timeout(&rain->cmd_done, HZ)) {
+		err = -ETIMEDOUT;
+		goto err;
+	}
+	if (reply && strncmp(rain->cmd_reply, reply, strlen(reply))) {
+		dev_dbg(rain->dev,
+			 "transmit of '%s': received '%s' instead of '%s'\n",
+			 cmd, rain->cmd_reply, reply);
+		err = -EIO;
+	}
+err:
+	mutex_unlock(&rain->write_lock);
+	return err;
+}
+
+static int rain_setup(struct rain *rain, struct serio *serio,
+			struct cec_log_addrs *log_addrs, u16 *pa)
+{
+	int err;
+
+	err = rain_send_and_wait(rain, "R", "REV");
+	if (err)
+		return err;
+	dev_info(rain->dev, "Firmware version %s\n", rain->cmd_reply + 4);
+
+	err = rain_send_and_wait(rain, "Q 1", "QTY");
+	if (err)
+		return err;
+	err = rain_send_and_wait(rain, "c0000", "CFG");
+	if (err)
+		return err;
+	return rain_send_and_wait(rain, "A F 0000", "ADR");
+}
+
+static int rain_cec_adap_enable(struct cec_adapter *adap, bool enable)
+{
+	return 0;
+}
+
+static int rain_cec_adap_log_addr(struct cec_adapter *adap, u8 log_addr)
+{
+	struct rain *rain = cec_get_drvdata(adap);
+	u8 cmd[16];
+
+	if (log_addr == CEC_LOG_ADDR_INVALID)
+		log_addr = CEC_LOG_ADDR_UNREGISTERED;
+	snprintf(cmd, sizeof(cmd), "A %x", log_addr);
+	return rain_send_and_wait(rain, cmd, "ADR");
+}
+
+static int rain_cec_adap_transmit(struct cec_adapter *adap, u8 attempts,
+				    u32 signal_free_time, struct cec_msg *msg)
+{
+	struct rain *rain = cec_get_drvdata(adap);
+	char cmd[2 * CEC_MAX_MSG_SIZE + 16];
+	unsigned int i;
+	int err;
+
+	if (msg->len == 1) {
+		snprintf(cmd, sizeof(cmd), "x%x", cec_msg_destination(msg));
+	} else {
+		char hex[3];
+
+		snprintf(cmd, sizeof(cmd), "x%x %02x ",
+			 cec_msg_destination(msg), msg->msg[1]);
+		for (i = 2; i < msg->len; i++) {
+			snprintf(hex, sizeof(hex), "%02x", msg->msg[i]);
+			strlcat(cmd, hex, sizeof(cmd));
+		}
+	}
+	mutex_lock(&rain->write_lock);
+	err = rain_send(rain, cmd);
+	mutex_unlock(&rain->write_lock);
+	return err;
+}
+
+static const struct cec_adap_ops rain_cec_adap_ops = {
+	.adap_enable = rain_cec_adap_enable,
+	.adap_log_addr = rain_cec_adap_log_addr,
+	.adap_transmit = rain_cec_adap_transmit,
+};
+
+static int rain_connect(struct serio *serio, struct serio_driver *drv)
+{
+	u32 caps = CEC_CAP_DEFAULTS | CEC_CAP_PHYS_ADDR | CEC_CAP_MONITOR_ALL;
+	struct rain *rain;
+	int err = -ENOMEM;
+	struct cec_log_addrs log_addrs = {};
+	u16 pa = CEC_PHYS_ADDR_INVALID;
+
+	rain = kzalloc(sizeof(*rain), GFP_KERNEL);
+
+	if (!rain)
+		return -ENOMEM;
+
+	rain->serio = serio;
+	rain->adap = cec_allocate_adapter(&rain_cec_adap_ops, rain,
+					  dev_name(&serio->dev), caps, 1);
+	err = PTR_ERR_OR_ZERO(rain->adap);
+	if (err < 0)
+		goto free_device;
+
+	rain->dev = &serio->dev;
+	serio_set_drvdata(serio, rain);
+	INIT_WORK(&rain->work, rain_irq_work_handler);
+	mutex_init(&rain->write_lock);
+	spin_lock_init(&rain->buf_lock);
+
+	err = serio_open(serio, drv);
+	if (err)
+		goto delete_adap;
+
+	err = rain_setup(rain, serio, &log_addrs, &pa);
+	if (err)
+		goto close_serio;
+
+	err = cec_register_adapter(rain->adap, &serio->dev);
+	if (err < 0)
+		goto close_serio;
+
+	rain->dev = &rain->adap->devnode.dev;
+	return 0;
+
+close_serio:
+	serio_close(serio);
+delete_adap:
+	cec_delete_adapter(rain->adap);
+	serio_set_drvdata(serio, NULL);
+free_device:
+	kfree(rain);
+	return err;
+}
+
+static const struct serio_device_id rain_serio_ids[] = {
+	{
+		.type	= SERIO_RS232,
+		.proto	= SERIO_RAINSHADOW_CEC,
+		.id	= SERIO_ANY,
+		.extra	= SERIO_ANY,
+	},
+	{ 0 }
+};
+
+MODULE_DEVICE_TABLE(serio, rain_serio_ids);
+
+static struct serio_driver rain_drv = {
+	.driver		= {
+		.name	= "rainshadow-cec",
+	},
+	.description	= "RainShadow Tech HDMI CEC driver",
+	.id_table	= rain_serio_ids,
+	.interrupt	= rain_interrupt,
+	.connect	= rain_connect,
+	.disconnect	= rain_disconnect,
+};
+
+module_serio_driver(rain_drv);
diff --git a/drivers/media/usb/s2255/Kconfig b/drivers/media/usb/s2255/Kconfig
new file mode 100644
index 0000000..8c3fcee
--- /dev/null
+++ b/drivers/media/usb/s2255/Kconfig
@@ -0,0 +1,9 @@
+config USB_S2255
+	tristate "USB Sensoray 2255 video capture device"
+	depends on VIDEO_V4L2
+	select VIDEOBUF2_VMALLOC
+	default n
+	help
+	  Say Y here if you want support for the Sensoray 2255 USB device.
+	  This driver can be compiled as a module, called s2255drv.
+
diff --git a/drivers/media/usb/s2255/Makefile b/drivers/media/usb/s2255/Makefile
new file mode 100644
index 0000000..197d0bb
--- /dev/null
+++ b/drivers/media/usb/s2255/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_USB_S2255)		+= s2255drv.o
+
diff --git a/drivers/media/usb/s2255/s2255drv.c b/drivers/media/usb/s2255/s2255drv.c
new file mode 100644
index 0000000..82927eb
--- /dev/null
+++ b/drivers/media/usb/s2255/s2255drv.c
@@ -0,0 +1,2401 @@
+/*
+ *  s2255drv.c - a driver for the Sensoray 2255 USB video capture device
+ *
+ *   Copyright (C) 2007-2014 by Sensoray Company Inc.
+ *                              Dean Anderson
+ *
+ * Some video buffer code based on vivi driver:
+ *
+ * Sensoray 2255 device supports 4 simultaneous channels.
+ * The channels are not "crossbar" inputs, they are physically
+ * attached to separate video decoders.
+ *
+ * Because of USB2.0 bandwidth limitations. There is only a
+ * certain amount of data which may be transferred at one time.
+ *
+ * Example maximum bandwidth utilization:
+ *
+ * -full size, color mode YUYV or YUV422P: 2 channels at once
+ * -full or half size Grey scale: all 4 channels at once
+ * -half size, color mode YUYV or YUV422P: all 4 channels at once
+ * -full size, color mode YUYV or YUV422P 1/2 frame rate: all 4 channels
+ *  at once.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/firmware.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/videodev2.h>
+#include <linux/mm.h>
+#include <linux/vmalloc.h>
+#include <linux/usb.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-vmalloc.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+
+#define S2255_VERSION		"1.25.1"
+#define FIRMWARE_FILE_NAME "f2255usb.bin"
+
+/* default JPEG quality */
+#define S2255_DEF_JPEG_QUAL     50
+/* vendor request in */
+#define S2255_VR_IN		0
+/* vendor request out */
+#define S2255_VR_OUT		1
+/* firmware query */
+#define S2255_VR_FW		0x30
+/* USB endpoint number for configuring the device */
+#define S2255_CONFIG_EP         2
+/* maximum time for DSP to start responding after last FW word loaded(ms) */
+#define S2255_DSP_BOOTTIME      800
+/* maximum time to wait for firmware to load (ms) */
+#define S2255_LOAD_TIMEOUT      (5000 + S2255_DSP_BOOTTIME)
+#define S2255_MIN_BUFS          2
+#define S2255_SETMODE_TIMEOUT   500
+#define S2255_VIDSTATUS_TIMEOUT 350
+#define S2255_MARKER_FRAME	cpu_to_le32(0x2255DA4AL)
+#define S2255_MARKER_RESPONSE	cpu_to_le32(0x2255ACACL)
+#define S2255_RESPONSE_SETMODE  cpu_to_le32(0x01)
+#define S2255_RESPONSE_FW       cpu_to_le32(0x10)
+#define S2255_RESPONSE_STATUS   cpu_to_le32(0x20)
+#define S2255_USB_XFER_SIZE	(16 * 1024)
+#define MAX_CHANNELS		4
+#define SYS_FRAMES		4
+/* maximum size is PAL full size plus room for the marker header(s) */
+#define SYS_FRAMES_MAXSIZE	(720*288*2*2 + 4096)
+#define DEF_USB_BLOCK		S2255_USB_XFER_SIZE
+#define LINE_SZ_4CIFS_NTSC	640
+#define LINE_SZ_2CIFS_NTSC	640
+#define LINE_SZ_1CIFS_NTSC	320
+#define LINE_SZ_4CIFS_PAL	704
+#define LINE_SZ_2CIFS_PAL	704
+#define LINE_SZ_1CIFS_PAL	352
+#define NUM_LINES_4CIFS_NTSC	240
+#define NUM_LINES_2CIFS_NTSC	240
+#define NUM_LINES_1CIFS_NTSC	240
+#define NUM_LINES_4CIFS_PAL	288
+#define NUM_LINES_2CIFS_PAL	288
+#define NUM_LINES_1CIFS_PAL	288
+#define LINE_SZ_DEF		640
+#define NUM_LINES_DEF		240
+
+
+/* predefined settings */
+#define FORMAT_NTSC	1
+#define FORMAT_PAL	2
+
+#define SCALE_4CIFS	1	/* 640x480(NTSC) or 704x576(PAL) */
+#define SCALE_2CIFS	2	/* 640x240(NTSC) or 704x288(PAL) */
+#define SCALE_1CIFS	3	/* 320x240(NTSC) or 352x288(PAL) */
+/* SCALE_4CIFSI is the 2 fields interpolated into one */
+#define SCALE_4CIFSI	4	/* 640x480(NTSC) or 704x576(PAL) high quality */
+
+#define COLOR_YUVPL	1	/* YUV planar */
+#define COLOR_YUVPK	2	/* YUV packed */
+#define COLOR_Y8	4	/* monochrome */
+#define COLOR_JPG       5       /* JPEG */
+
+#define MASK_COLOR       0x000000ff
+#define MASK_JPG_QUALITY 0x0000ff00
+#define MASK_INPUT_TYPE  0x000f0000
+/* frame decimation. */
+#define FDEC_1		1	/* capture every frame. default */
+#define FDEC_2		2	/* capture every 2nd frame */
+#define FDEC_3		3	/* capture every 3rd frame */
+#define FDEC_5		5	/* capture every 5th frame */
+
+/*-------------------------------------------------------
+ * Default mode parameters.
+ *-------------------------------------------------------*/
+#define DEF_SCALE	SCALE_4CIFS
+#define DEF_COLOR	COLOR_YUVPL
+#define DEF_FDEC	FDEC_1
+#define DEF_BRIGHT	0
+#define DEF_CONTRAST	0x5c
+#define DEF_SATURATION	0x80
+#define DEF_HUE		0
+
+/* usb config commands */
+#define IN_DATA_TOKEN	cpu_to_le32(0x2255c0de)
+#define CMD_2255	0xc2255000
+#define CMD_SET_MODE	cpu_to_le32((CMD_2255 | 0x10))
+#define CMD_START	cpu_to_le32((CMD_2255 | 0x20))
+#define CMD_STOP	cpu_to_le32((CMD_2255 | 0x30))
+#define CMD_STATUS	cpu_to_le32((CMD_2255 | 0x40))
+
+struct s2255_mode {
+	u32 format;	/* input video format (NTSC, PAL) */
+	u32 scale;	/* output video scale */
+	u32 color;	/* output video color format */
+	u32 fdec;	/* frame decimation */
+	u32 bright;	/* brightness */
+	u32 contrast;	/* contrast */
+	u32 saturation;	/* saturation */
+	u32 hue;	/* hue (NTSC only)*/
+	u32 single;	/* capture 1 frame at a time (!=0), continuously (==0)*/
+	u32 usb_block;	/* block size. should be 4096 of DEF_USB_BLOCK */
+	u32 restart;	/* if DSP requires restart */
+};
+
+
+#define S2255_READ_IDLE		0
+#define S2255_READ_FRAME	1
+
+/* frame structure */
+struct s2255_framei {
+	unsigned long size;
+	unsigned long ulState;	/* ulState:S2255_READ_IDLE, S2255_READ_FRAME*/
+	void *lpvbits;		/* image data */
+	unsigned long cur_size;	/* current data copied to it */
+};
+
+/* image buffer structure */
+struct s2255_bufferi {
+	unsigned long dwFrames;			/* number of frames in buffer */
+	struct s2255_framei frame[SYS_FRAMES];	/* array of FRAME structures */
+};
+
+#define DEF_MODEI_NTSC_CONT	{FORMAT_NTSC, DEF_SCALE, DEF_COLOR,	\
+			DEF_FDEC, DEF_BRIGHT, DEF_CONTRAST, DEF_SATURATION, \
+			DEF_HUE, 0, DEF_USB_BLOCK, 0}
+
+/* for firmware loading, fw_state */
+#define S2255_FW_NOTLOADED	0
+#define S2255_FW_LOADED_DSPWAIT	1
+#define S2255_FW_SUCCESS	2
+#define S2255_FW_FAILED		3
+#define S2255_FW_DISCONNECTING  4
+#define S2255_FW_MARKER		cpu_to_le32(0x22552f2f)
+/* 2255 read states */
+#define S2255_READ_IDLE         0
+#define S2255_READ_FRAME        1
+struct s2255_fw {
+	int		      fw_loaded;
+	int		      fw_size;
+	struct urb	      *fw_urb;
+	atomic_t	      fw_state;
+	void		      *pfw_data;
+	wait_queue_head_t     wait_fw;
+	const struct firmware *fw;
+};
+
+struct s2255_pipeinfo {
+	u32 max_transfer_size;
+	u32 cur_transfer_size;
+	u8 *transfer_buffer;
+	u32 state;
+	void *stream_urb;
+	void *dev;	/* back pointer to s2255_dev struct*/
+	u32 err_count;
+	u32 idx;
+};
+
+struct s2255_fmt; /*forward declaration */
+struct s2255_dev;
+
+/* 2255 video channel */
+struct s2255_vc {
+	struct s2255_dev        *dev;
+	struct video_device	vdev;
+	struct v4l2_ctrl_handler hdl;
+	struct v4l2_ctrl	*jpegqual_ctrl;
+	int			resources;
+	struct list_head        buf_list;
+	struct s2255_bufferi	buffer;
+	struct s2255_mode	mode;
+	v4l2_std_id		std;
+	/* jpeg compression */
+	unsigned		jpegqual;
+	/* capture parameters (for high quality mode full size) */
+	struct v4l2_captureparm cap_parm;
+	int			cur_frame;
+	int			last_frame;
+	/* allocated image size */
+	unsigned long		req_image_size;
+	/* received packet size */
+	unsigned long		pkt_size;
+	int			bad_payload;
+	unsigned long		frame_count;
+	/* if JPEG image */
+	int                     jpg_size;
+	/* if channel configured to default state */
+	int                     configured;
+	wait_queue_head_t       wait_setmode;
+	int                     setmode_ready;
+	/* video status items */
+	int                     vidstatus;
+	wait_queue_head_t       wait_vidstatus;
+	int                     vidstatus_ready;
+	unsigned int		width;
+	unsigned int		height;
+	enum v4l2_field         field;
+	const struct s2255_fmt	*fmt;
+	int idx; /* channel number on device, 0-3 */
+	struct vb2_queue vb_vidq;
+	struct mutex vb_lock; /* streaming lock */
+	spinlock_t qlock;
+};
+
+
+struct s2255_dev {
+	struct s2255_vc         vc[MAX_CHANNELS];
+	struct v4l2_device      v4l2_dev;
+	atomic_t                num_channels;
+	int			frames;
+	struct mutex		lock;	/* channels[].vdev.lock */
+	struct mutex		cmdlock; /* protects cmdbuf */
+	struct usb_device	*udev;
+	struct usb_interface	*interface;
+	u8			read_endpoint;
+	struct timer_list	timer;
+	struct s2255_fw	*fw_data;
+	struct s2255_pipeinfo	pipe;
+	u32			cc;	/* current channel */
+	int			frame_ready;
+	int                     chn_ready;
+	/* dsp firmware version (f2255usb.bin) */
+	int                     dsp_fw_ver;
+	u16                     pid; /* product id */
+#define S2255_CMDBUF_SIZE 512
+	__le32                  *cmdbuf;
+};
+
+static inline struct s2255_dev *to_s2255_dev(struct v4l2_device *v4l2_dev)
+{
+	return container_of(v4l2_dev, struct s2255_dev, v4l2_dev);
+}
+
+struct s2255_fmt {
+	char *name;
+	u32 fourcc;
+	int depth;
+};
+
+/* buffer for one video frame */
+struct s2255_buffer {
+	/* common v4l buffer stuff -- must be first */
+	struct vb2_v4l2_buffer vb;
+	struct list_head list;
+};
+
+
+/* current cypress EEPROM firmware version */
+#define S2255_CUR_USB_FWVER	((3 << 8) | 12)
+/* current DSP FW version */
+#define S2255_CUR_DSP_FWVER     10104
+/* Need DSP version 5+ for video status feature */
+#define S2255_MIN_DSP_STATUS      5
+#define S2255_MIN_DSP_COLORFILTER 8
+#define S2255_NORMS		(V4L2_STD_ALL)
+
+/* private V4L2 controls */
+
+/*
+ * The following chart displays how COLORFILTER should be set
+ *  =========================================================
+ *  =     fourcc              =     COLORFILTER             =
+ *  =                         ===============================
+ *  =                         =   0             =    1      =
+ *  =========================================================
+ *  =  V4L2_PIX_FMT_GREY(Y8)  = monochrome from = monochrome=
+ *  =                         = s-video or      = composite =
+ *  =                         = B/W camera      = input     =
+ *  =========================================================
+ *  =    other                = color, svideo   = color,    =
+ *  =                         =                 = composite =
+ *  =========================================================
+ *
+ * Notes:
+ *   channels 0-3 on 2255 are composite
+ *   channels 0-1 on 2257 are composite, 2-3 are s-video
+ * If COLORFILTER is 0 with a composite color camera connected,
+ * the output will appear monochrome but hatching
+ * will occur.
+ * COLORFILTER is different from "color killer" and "color effects"
+ * for reasons above.
+ */
+#define S2255_V4L2_YC_ON  1
+#define S2255_V4L2_YC_OFF 0
+#define V4L2_CID_S2255_COLORFILTER (V4L2_CID_USER_S2255_BASE + 0)
+
+/* frame prefix size (sent once every frame) */
+#define PREFIX_SIZE		512
+
+/* Channels on box are in reverse order */
+static unsigned long G_chnmap[MAX_CHANNELS] = {3, 2, 1, 0};
+
+static int debug;
+
+static int s2255_start_readpipe(struct s2255_dev *dev);
+static void s2255_stop_readpipe(struct s2255_dev *dev);
+static int s2255_start_acquire(struct s2255_vc *vc);
+static int s2255_stop_acquire(struct s2255_vc *vc);
+static void s2255_fillbuff(struct s2255_vc *vc, struct s2255_buffer *buf,
+			   int jpgsize);
+static int s2255_set_mode(struct s2255_vc *vc, struct s2255_mode *mode);
+static int s2255_board_shutdown(struct s2255_dev *dev);
+static void s2255_fwload_start(struct s2255_dev *dev);
+static void s2255_destroy(struct s2255_dev *dev);
+static long s2255_vendor_req(struct s2255_dev *dev, unsigned char req,
+			     u16 index, u16 value, void *buf,
+			     s32 buf_len, int bOut);
+
+/* dev_err macro with driver name */
+#define S2255_DRIVER_NAME "s2255"
+#define s2255_dev_err(dev, fmt, arg...)					\
+		dev_err(dev, S2255_DRIVER_NAME " - " fmt, ##arg)
+
+#define dprintk(dev, level, fmt, arg...) \
+	v4l2_dbg(level, debug, &dev->v4l2_dev, fmt, ## arg)
+
+static struct usb_driver s2255_driver;
+
+/* start video number */
+static int video_nr = -1;	/* /dev/videoN, -1 for autodetect */
+
+/* Enable jpeg capture. */
+static int jpeg_enable = 1;
+
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "Debug level(0-100) default 0");
+module_param(video_nr, int, 0644);
+MODULE_PARM_DESC(video_nr, "start video minor(-1 default autodetect)");
+module_param(jpeg_enable, int, 0644);
+MODULE_PARM_DESC(jpeg_enable, "Jpeg enable(1-on 0-off) default 1");
+
+/* USB device table */
+#define USB_SENSORAY_VID	0x1943
+static const struct usb_device_id s2255_table[] = {
+	{USB_DEVICE(USB_SENSORAY_VID, 0x2255)},
+	{USB_DEVICE(USB_SENSORAY_VID, 0x2257)}, /*same family as 2255*/
+	{ }			/* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, s2255_table);
+
+#define BUFFER_TIMEOUT msecs_to_jiffies(400)
+
+/* image formats.  */
+/* JPEG formats must be defined last to support jpeg_enable parameter */
+static const struct s2255_fmt formats[] = {
+	{
+		.name = "4:2:2, packed, YUYV",
+		.fourcc = V4L2_PIX_FMT_YUYV,
+		.depth = 16
+
+	}, {
+		.name = "4:2:2, packed, UYVY",
+		.fourcc = V4L2_PIX_FMT_UYVY,
+		.depth = 16
+	}, {
+		.name = "4:2:2, planar, YUV422P",
+		.fourcc = V4L2_PIX_FMT_YUV422P,
+		.depth = 16
+
+	}, {
+		.name = "8bpp GREY",
+		.fourcc = V4L2_PIX_FMT_GREY,
+		.depth = 8
+	}, {
+		.name = "JPG",
+		.fourcc = V4L2_PIX_FMT_JPEG,
+		.depth = 24
+	}, {
+		.name = "MJPG",
+		.fourcc = V4L2_PIX_FMT_MJPEG,
+		.depth = 24
+	}
+};
+
+static int norm_maxw(struct s2255_vc *vc)
+{
+	return (vc->std & V4L2_STD_525_60) ?
+	    LINE_SZ_4CIFS_NTSC : LINE_SZ_4CIFS_PAL;
+}
+
+static int norm_maxh(struct s2255_vc *vc)
+{
+	return (vc->std & V4L2_STD_525_60) ?
+	    (NUM_LINES_1CIFS_NTSC * 2) : (NUM_LINES_1CIFS_PAL * 2);
+}
+
+static int norm_minw(struct s2255_vc *vc)
+{
+	return (vc->std & V4L2_STD_525_60) ?
+	    LINE_SZ_1CIFS_NTSC : LINE_SZ_1CIFS_PAL;
+}
+
+static int norm_minh(struct s2255_vc *vc)
+{
+	return (vc->std & V4L2_STD_525_60) ?
+	    (NUM_LINES_1CIFS_NTSC) : (NUM_LINES_1CIFS_PAL);
+}
+
+
+/*
+ * TODO: fixme: move YUV reordering to hardware
+ * converts 2255 planar format to yuyv or uyvy
+ */
+static void planar422p_to_yuv_packed(const unsigned char *in,
+				     unsigned char *out,
+				     int width, int height,
+				     int fmt)
+{
+	unsigned char *pY;
+	unsigned char *pCb;
+	unsigned char *pCr;
+	unsigned long size = height * width;
+	unsigned int i;
+	pY = (unsigned char *)in;
+	pCr = (unsigned char *)in + height * width;
+	pCb = (unsigned char *)in + height * width + (height * width / 2);
+	for (i = 0; i < size * 2; i += 4) {
+		out[i] = (fmt == V4L2_PIX_FMT_YUYV) ? *pY++ : *pCr++;
+		out[i + 1] = (fmt == V4L2_PIX_FMT_YUYV) ? *pCr++ : *pY++;
+		out[i + 2] = (fmt == V4L2_PIX_FMT_YUYV) ? *pY++ : *pCb++;
+		out[i + 3] = (fmt == V4L2_PIX_FMT_YUYV) ? *pCb++ : *pY++;
+	}
+	return;
+}
+
+static void s2255_reset_dsppower(struct s2255_dev *dev)
+{
+	s2255_vendor_req(dev, 0x40, 0x0000, 0x0001, NULL, 0, 1);
+	msleep(50);
+	s2255_vendor_req(dev, 0x50, 0x0000, 0x0000, NULL, 0, 1);
+	msleep(600);
+	s2255_vendor_req(dev, 0x10, 0x0000, 0x0000, NULL, 0, 1);
+	return;
+}
+
+/* kickstarts the firmware loading. from probe
+ */
+static void s2255_timer(struct timer_list *t)
+{
+	struct s2255_dev *dev = from_timer(dev, t, timer);
+	struct s2255_fw *data = dev->fw_data;
+	if (usb_submit_urb(data->fw_urb, GFP_ATOMIC) < 0) {
+		pr_err("s2255: can't submit urb\n");
+		atomic_set(&data->fw_state, S2255_FW_FAILED);
+		/* wake up anything waiting for the firmware */
+		wake_up(&data->wait_fw);
+		return;
+	}
+}
+
+
+/* this loads the firmware asynchronously.
+   Originally this was done synchronously in probe.
+   But it is better to load it asynchronously here than block
+   inside the probe function. Blocking inside probe affects boot time.
+   FW loading is triggered by the timer in the probe function
+*/
+static void s2255_fwchunk_complete(struct urb *urb)
+{
+	struct s2255_fw *data = urb->context;
+	struct usb_device *udev = urb->dev;
+	int len;
+	if (urb->status) {
+		dev_err(&udev->dev, "URB failed with status %d\n", urb->status);
+		atomic_set(&data->fw_state, S2255_FW_FAILED);
+		/* wake up anything waiting for the firmware */
+		wake_up(&data->wait_fw);
+		return;
+	}
+	if (data->fw_urb == NULL) {
+		s2255_dev_err(&udev->dev, "disconnected\n");
+		atomic_set(&data->fw_state, S2255_FW_FAILED);
+		/* wake up anything waiting for the firmware */
+		wake_up(&data->wait_fw);
+		return;
+	}
+#define CHUNK_SIZE 512
+	/* all USB transfers must be done with continuous kernel memory.
+	   can't allocate more than 128k in current linux kernel, so
+	   upload the firmware in chunks
+	 */
+	if (data->fw_loaded < data->fw_size) {
+		len = (data->fw_loaded + CHUNK_SIZE) > data->fw_size ?
+		    data->fw_size % CHUNK_SIZE : CHUNK_SIZE;
+
+		if (len < CHUNK_SIZE)
+			memset(data->pfw_data, 0, CHUNK_SIZE);
+
+		memcpy(data->pfw_data,
+		       (char *) data->fw->data + data->fw_loaded, len);
+
+		usb_fill_bulk_urb(data->fw_urb, udev, usb_sndbulkpipe(udev, 2),
+				  data->pfw_data, CHUNK_SIZE,
+				  s2255_fwchunk_complete, data);
+		if (usb_submit_urb(data->fw_urb, GFP_ATOMIC) < 0) {
+			dev_err(&udev->dev, "failed submit URB\n");
+			atomic_set(&data->fw_state, S2255_FW_FAILED);
+			/* wake up anything waiting for the firmware */
+			wake_up(&data->wait_fw);
+			return;
+		}
+		data->fw_loaded += len;
+	} else
+		atomic_set(&data->fw_state, S2255_FW_LOADED_DSPWAIT);
+	return;
+
+}
+
+static void s2255_got_frame(struct s2255_vc *vc, int jpgsize)
+{
+	struct s2255_buffer *buf;
+	struct s2255_dev *dev = to_s2255_dev(vc->vdev.v4l2_dev);
+	unsigned long flags = 0;
+
+	spin_lock_irqsave(&vc->qlock, flags);
+	if (list_empty(&vc->buf_list)) {
+		dprintk(dev, 1, "No active queue to serve\n");
+		spin_unlock_irqrestore(&vc->qlock, flags);
+		return;
+	}
+	buf = list_entry(vc->buf_list.next,
+			 struct s2255_buffer, list);
+	list_del(&buf->list);
+	buf->vb.vb2_buf.timestamp = ktime_get_ns();
+	buf->vb.field = vc->field;
+	buf->vb.sequence = vc->frame_count;
+	spin_unlock_irqrestore(&vc->qlock, flags);
+
+	s2255_fillbuff(vc, buf, jpgsize);
+	/* tell v4l buffer was filled */
+	vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
+	dprintk(dev, 2, "%s: [buf] [%p]\n", __func__, buf);
+}
+
+static const struct s2255_fmt *format_by_fourcc(int fourcc)
+{
+	unsigned int i;
+	for (i = 0; i < ARRAY_SIZE(formats); i++) {
+		if (-1 == formats[i].fourcc)
+			continue;
+		if (!jpeg_enable && ((formats[i].fourcc == V4L2_PIX_FMT_JPEG) ||
+				     (formats[i].fourcc == V4L2_PIX_FMT_MJPEG)))
+			continue;
+		if (formats[i].fourcc == fourcc)
+			return formats + i;
+	}
+	return NULL;
+}
+
+/* video buffer vmalloc implementation based partly on VIVI driver which is
+ *          Copyright (c) 2006 by
+ *                  Mauro Carvalho Chehab <mchehab--a.t--infradead.org>
+ *                  Ted Walther <ted--a.t--enumera.com>
+ *                  John Sokol <sokol--a.t--videotechnology.com>
+ *                  http://v4l.videotechnology.com/
+ *
+ */
+static void s2255_fillbuff(struct s2255_vc *vc,
+			   struct s2255_buffer *buf, int jpgsize)
+{
+	int pos = 0;
+	const char *tmpbuf;
+	char *vbuf = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
+	unsigned long last_frame;
+	struct s2255_dev *dev = vc->dev;
+
+	if (!vbuf)
+		return;
+	last_frame = vc->last_frame;
+	if (last_frame != -1) {
+		tmpbuf =
+		    (const char *)vc->buffer.frame[last_frame].lpvbits;
+		switch (vc->fmt->fourcc) {
+		case V4L2_PIX_FMT_YUYV:
+		case V4L2_PIX_FMT_UYVY:
+			planar422p_to_yuv_packed((const unsigned char *)tmpbuf,
+						 vbuf, vc->width,
+						 vc->height,
+						 vc->fmt->fourcc);
+			break;
+		case V4L2_PIX_FMT_GREY:
+			memcpy(vbuf, tmpbuf, vc->width * vc->height);
+			break;
+		case V4L2_PIX_FMT_JPEG:
+		case V4L2_PIX_FMT_MJPEG:
+			vb2_set_plane_payload(&buf->vb.vb2_buf, 0, jpgsize);
+			memcpy(vbuf, tmpbuf, jpgsize);
+			break;
+		case V4L2_PIX_FMT_YUV422P:
+			memcpy(vbuf, tmpbuf,
+			       vc->width * vc->height * 2);
+			break;
+		default:
+			pr_info("s2255: unknown format?\n");
+		}
+		vc->last_frame = -1;
+	} else {
+		pr_err("s2255: =======no frame\n");
+		return;
+	}
+	dprintk(dev, 2, "s2255fill at : Buffer %p size= %d\n",
+		vbuf, pos);
+}
+
+
+/* ------------------------------------------------------------------
+   Videobuf operations
+   ------------------------------------------------------------------*/
+
+static int queue_setup(struct vb2_queue *vq,
+		       unsigned int *nbuffers, unsigned int *nplanes,
+		       unsigned int sizes[], struct device *alloc_devs[])
+{
+	struct s2255_vc *vc = vb2_get_drv_priv(vq);
+	if (*nbuffers < S2255_MIN_BUFS)
+		*nbuffers = S2255_MIN_BUFS;
+	*nplanes = 1;
+	sizes[0] = vc->width * vc->height * (vc->fmt->depth >> 3);
+	return 0;
+}
+
+static int buffer_prepare(struct vb2_buffer *vb)
+{
+	struct s2255_vc *vc = vb2_get_drv_priv(vb->vb2_queue);
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct s2255_buffer *buf = container_of(vbuf, struct s2255_buffer, vb);
+	int w = vc->width;
+	int h = vc->height;
+	unsigned long size;
+
+	dprintk(vc->dev, 4, "%s\n", __func__);
+	if (vc->fmt == NULL)
+		return -EINVAL;
+
+	if ((w < norm_minw(vc)) ||
+	    (w > norm_maxw(vc)) ||
+	    (h < norm_minh(vc)) ||
+	    (h > norm_maxh(vc))) {
+		dprintk(vc->dev, 4, "invalid buffer prepare\n");
+		return -EINVAL;
+	}
+	size = w * h * (vc->fmt->depth >> 3);
+	if (vb2_plane_size(vb, 0) < size) {
+		dprintk(vc->dev, 4, "invalid buffer prepare\n");
+		return -EINVAL;
+	}
+
+	vb2_set_plane_payload(&buf->vb.vb2_buf, 0, size);
+	return 0;
+}
+
+static void buffer_queue(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct s2255_buffer *buf = container_of(vbuf, struct s2255_buffer, vb);
+	struct s2255_vc *vc = vb2_get_drv_priv(vb->vb2_queue);
+	unsigned long flags = 0;
+	dprintk(vc->dev, 1, "%s\n", __func__);
+	spin_lock_irqsave(&vc->qlock, flags);
+	list_add_tail(&buf->list, &vc->buf_list);
+	spin_unlock_irqrestore(&vc->qlock, flags);
+}
+
+static int start_streaming(struct vb2_queue *vq, unsigned int count);
+static void stop_streaming(struct vb2_queue *vq);
+
+static const struct vb2_ops s2255_video_qops = {
+	.queue_setup = queue_setup,
+	.buf_prepare = buffer_prepare,
+	.buf_queue = buffer_queue,
+	.start_streaming = start_streaming,
+	.stop_streaming = stop_streaming,
+	.wait_prepare = vb2_ops_wait_prepare,
+	.wait_finish = vb2_ops_wait_finish,
+};
+
+static int vidioc_querycap(struct file *file, void *priv,
+			   struct v4l2_capability *cap)
+{
+	struct s2255_vc *vc = video_drvdata(file);
+	struct s2255_dev *dev = vc->dev;
+
+	strlcpy(cap->driver, "s2255", sizeof(cap->driver));
+	strlcpy(cap->card, "s2255", sizeof(cap->card));
+	usb_make_path(dev->udev, cap->bus_info, sizeof(cap->bus_info));
+	cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING |
+		V4L2_CAP_READWRITE;
+	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+	return 0;
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
+			       struct v4l2_fmtdesc *f)
+{
+	int index = f->index;
+
+	if (index >= ARRAY_SIZE(formats))
+		return -EINVAL;
+	if (!jpeg_enable && ((formats[index].fourcc == V4L2_PIX_FMT_JPEG) ||
+			(formats[index].fourcc == V4L2_PIX_FMT_MJPEG)))
+		return -EINVAL;
+	strlcpy(f->description, formats[index].name, sizeof(f->description));
+	f->pixelformat = formats[index].fourcc;
+	return 0;
+}
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+			    struct v4l2_format *f)
+{
+	struct s2255_vc *vc = video_drvdata(file);
+	int is_ntsc = vc->std & V4L2_STD_525_60;
+
+	f->fmt.pix.width = vc->width;
+	f->fmt.pix.height = vc->height;
+	if (f->fmt.pix.height >=
+	    (is_ntsc ? NUM_LINES_1CIFS_NTSC : NUM_LINES_1CIFS_PAL) * 2)
+		f->fmt.pix.field = V4L2_FIELD_INTERLACED;
+	else
+		f->fmt.pix.field = V4L2_FIELD_TOP;
+	f->fmt.pix.pixelformat = vc->fmt->fourcc;
+	f->fmt.pix.bytesperline = f->fmt.pix.width * (vc->fmt->depth >> 3);
+	f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
+	f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+	f->fmt.pix.priv = 0;
+	return 0;
+}
+
+static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
+			      struct v4l2_format *f)
+{
+	const struct s2255_fmt *fmt;
+	enum v4l2_field field;
+	struct s2255_vc *vc = video_drvdata(file);
+	int is_ntsc = vc->std & V4L2_STD_525_60;
+
+	fmt = format_by_fourcc(f->fmt.pix.pixelformat);
+
+	if (fmt == NULL)
+		return -EINVAL;
+
+	field = f->fmt.pix.field;
+
+	dprintk(vc->dev, 50, "%s NTSC: %d suggested width: %d, height: %d\n",
+		__func__, is_ntsc, f->fmt.pix.width, f->fmt.pix.height);
+	if (is_ntsc) {
+		/* NTSC */
+		if (f->fmt.pix.height >= NUM_LINES_1CIFS_NTSC * 2) {
+			f->fmt.pix.height = NUM_LINES_1CIFS_NTSC * 2;
+			field = V4L2_FIELD_INTERLACED;
+		} else {
+			f->fmt.pix.height = NUM_LINES_1CIFS_NTSC;
+			field = V4L2_FIELD_TOP;
+		}
+		if (f->fmt.pix.width >= LINE_SZ_4CIFS_NTSC)
+			f->fmt.pix.width = LINE_SZ_4CIFS_NTSC;
+		else
+			f->fmt.pix.width = LINE_SZ_1CIFS_NTSC;
+	} else {
+		/* PAL */
+		if (f->fmt.pix.height >= NUM_LINES_1CIFS_PAL * 2) {
+			f->fmt.pix.height = NUM_LINES_1CIFS_PAL * 2;
+			field = V4L2_FIELD_INTERLACED;
+		} else {
+			f->fmt.pix.height = NUM_LINES_1CIFS_PAL;
+			field = V4L2_FIELD_TOP;
+		}
+		if (f->fmt.pix.width >= LINE_SZ_4CIFS_PAL)
+			f->fmt.pix.width = LINE_SZ_4CIFS_PAL;
+		else
+			f->fmt.pix.width = LINE_SZ_1CIFS_PAL;
+	}
+	f->fmt.pix.field = field;
+	f->fmt.pix.bytesperline = (f->fmt.pix.width * fmt->depth) >> 3;
+	f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
+	f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+	f->fmt.pix.priv = 0;
+	dprintk(vc->dev, 50, "%s: set width %d height %d field %d\n", __func__,
+		f->fmt.pix.width, f->fmt.pix.height, f->fmt.pix.field);
+	return 0;
+}
+
+static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+			    struct v4l2_format *f)
+{
+	struct s2255_vc *vc = video_drvdata(file);
+	const struct s2255_fmt *fmt;
+	struct vb2_queue *q = &vc->vb_vidq;
+	struct s2255_mode mode;
+	int ret;
+
+	ret = vidioc_try_fmt_vid_cap(file, vc, f);
+
+	if (ret < 0)
+		return ret;
+
+	fmt = format_by_fourcc(f->fmt.pix.pixelformat);
+
+	if (fmt == NULL)
+		return -EINVAL;
+
+	if (vb2_is_busy(q)) {
+		dprintk(vc->dev, 1, "queue busy\n");
+		return -EBUSY;
+	}
+
+	mode = vc->mode;
+	vc->fmt = fmt;
+	vc->width = f->fmt.pix.width;
+	vc->height = f->fmt.pix.height;
+	vc->field = f->fmt.pix.field;
+	if (vc->width > norm_minw(vc)) {
+		if (vc->height > norm_minh(vc)) {
+			if (vc->cap_parm.capturemode &
+			    V4L2_MODE_HIGHQUALITY)
+				mode.scale = SCALE_4CIFSI;
+			else
+				mode.scale = SCALE_4CIFS;
+		} else
+			mode.scale = SCALE_2CIFS;
+
+	} else {
+		mode.scale = SCALE_1CIFS;
+	}
+	/* color mode */
+	switch (vc->fmt->fourcc) {
+	case V4L2_PIX_FMT_GREY:
+		mode.color &= ~MASK_COLOR;
+		mode.color |= COLOR_Y8;
+		break;
+	case V4L2_PIX_FMT_JPEG:
+	case V4L2_PIX_FMT_MJPEG:
+		mode.color &= ~MASK_COLOR;
+		mode.color |= COLOR_JPG;
+		mode.color |= (vc->jpegqual << 8);
+		break;
+	case V4L2_PIX_FMT_YUV422P:
+		mode.color &= ~MASK_COLOR;
+		mode.color |= COLOR_YUVPL;
+		break;
+	case V4L2_PIX_FMT_YUYV:
+	case V4L2_PIX_FMT_UYVY:
+	default:
+		mode.color &= ~MASK_COLOR;
+		mode.color |= COLOR_YUVPK;
+		break;
+	}
+	if ((mode.color & MASK_COLOR) != (vc->mode.color & MASK_COLOR))
+		mode.restart = 1;
+	else if (mode.scale != vc->mode.scale)
+		mode.restart = 1;
+	else if (mode.format != vc->mode.format)
+		mode.restart = 1;
+	vc->mode = mode;
+	(void) s2255_set_mode(vc, &mode);
+	return 0;
+}
+
+
+/* write to the configuration pipe, synchronously */
+static int s2255_write_config(struct usb_device *udev, unsigned char *pbuf,
+			      int size)
+{
+	int pipe;
+	int done;
+	long retval = -1;
+	if (udev) {
+		pipe = usb_sndbulkpipe(udev, S2255_CONFIG_EP);
+		retval = usb_bulk_msg(udev, pipe, pbuf, size, &done, 500);
+	}
+	return retval;
+}
+
+static u32 get_transfer_size(struct s2255_mode *mode)
+{
+	int linesPerFrame = LINE_SZ_DEF;
+	int pixelsPerLine = NUM_LINES_DEF;
+	u32 outImageSize;
+	u32 usbInSize;
+	unsigned int mask_mult;
+
+	if (mode == NULL)
+		return 0;
+
+	if (mode->format == FORMAT_NTSC) {
+		switch (mode->scale) {
+		case SCALE_4CIFS:
+		case SCALE_4CIFSI:
+			linesPerFrame = NUM_LINES_4CIFS_NTSC * 2;
+			pixelsPerLine = LINE_SZ_4CIFS_NTSC;
+			break;
+		case SCALE_2CIFS:
+			linesPerFrame = NUM_LINES_2CIFS_NTSC;
+			pixelsPerLine = LINE_SZ_2CIFS_NTSC;
+			break;
+		case SCALE_1CIFS:
+			linesPerFrame = NUM_LINES_1CIFS_NTSC;
+			pixelsPerLine = LINE_SZ_1CIFS_NTSC;
+			break;
+		default:
+			break;
+		}
+	} else if (mode->format == FORMAT_PAL) {
+		switch (mode->scale) {
+		case SCALE_4CIFS:
+		case SCALE_4CIFSI:
+			linesPerFrame = NUM_LINES_4CIFS_PAL * 2;
+			pixelsPerLine = LINE_SZ_4CIFS_PAL;
+			break;
+		case SCALE_2CIFS:
+			linesPerFrame = NUM_LINES_2CIFS_PAL;
+			pixelsPerLine = LINE_SZ_2CIFS_PAL;
+			break;
+		case SCALE_1CIFS:
+			linesPerFrame = NUM_LINES_1CIFS_PAL;
+			pixelsPerLine = LINE_SZ_1CIFS_PAL;
+			break;
+		default:
+			break;
+		}
+	}
+	outImageSize = linesPerFrame * pixelsPerLine;
+	if ((mode->color & MASK_COLOR) != COLOR_Y8) {
+		/* 2 bytes/pixel if not monochrome */
+		outImageSize *= 2;
+	}
+
+	/* total bytes to send including prefix and 4K padding;
+	   must be a multiple of USB_READ_SIZE */
+	usbInSize = outImageSize + PREFIX_SIZE;	/* always send prefix */
+	mask_mult = 0xffffffffUL - DEF_USB_BLOCK + 1;
+	/* if size not a multiple of USB_READ_SIZE */
+	if (usbInSize & ~mask_mult)
+		usbInSize = (usbInSize & mask_mult) + (DEF_USB_BLOCK);
+	return usbInSize;
+}
+
+static void s2255_print_cfg(struct s2255_dev *sdev, struct s2255_mode *mode)
+{
+	struct device *dev = &sdev->udev->dev;
+	dev_info(dev, "------------------------------------------------\n");
+	dev_info(dev, "format: %d\nscale %d\n", mode->format, mode->scale);
+	dev_info(dev, "fdec: %d\ncolor %d\n", mode->fdec, mode->color);
+	dev_info(dev, "bright: 0x%x\n", mode->bright);
+	dev_info(dev, "------------------------------------------------\n");
+}
+
+/*
+ * set mode is the function which controls the DSP.
+ * the restart parameter in struct s2255_mode should be set whenever
+ * the image size could change via color format, video system or image
+ * size.
+ * When the restart parameter is set, we sleep for ONE frame to allow the
+ * DSP time to get the new frame
+ */
+static int s2255_set_mode(struct s2255_vc *vc,
+			  struct s2255_mode *mode)
+{
+	int res;
+	unsigned long chn_rev;
+	struct s2255_dev *dev = to_s2255_dev(vc->vdev.v4l2_dev);
+	int i;
+	__le32 *buffer = dev->cmdbuf;
+
+	mutex_lock(&dev->cmdlock);
+	chn_rev = G_chnmap[vc->idx];
+	dprintk(dev, 3, "%s channel: %d\n", __func__, vc->idx);
+	/* if JPEG, set the quality */
+	if ((mode->color & MASK_COLOR) == COLOR_JPG) {
+		mode->color &= ~MASK_COLOR;
+		mode->color |= COLOR_JPG;
+		mode->color &= ~MASK_JPG_QUALITY;
+		mode->color |= (vc->jpegqual << 8);
+	}
+	/* save the mode */
+	vc->mode = *mode;
+	vc->req_image_size = get_transfer_size(mode);
+	dprintk(dev, 1, "%s: reqsize %ld\n", __func__, vc->req_image_size);
+	/* set the mode */
+	buffer[0] = IN_DATA_TOKEN;
+	buffer[1] = (__le32) cpu_to_le32(chn_rev);
+	buffer[2] = CMD_SET_MODE;
+	for (i = 0; i < sizeof(struct s2255_mode) / sizeof(u32); i++)
+		buffer[3 + i] = cpu_to_le32(((u32 *)&vc->mode)[i]);
+	vc->setmode_ready = 0;
+	res = s2255_write_config(dev->udev, (unsigned char *)buffer, 512);
+	if (debug)
+		s2255_print_cfg(dev, mode);
+	/* wait at least 3 frames before continuing */
+	if (mode->restart) {
+		wait_event_timeout(vc->wait_setmode,
+				   (vc->setmode_ready != 0),
+				   msecs_to_jiffies(S2255_SETMODE_TIMEOUT));
+		if (vc->setmode_ready != 1) {
+			dprintk(dev, 0, "s2255: no set mode response\n");
+			res = -EFAULT;
+		}
+	}
+	/* clear the restart flag */
+	vc->mode.restart = 0;
+	dprintk(dev, 1, "%s chn %d, result: %d\n", __func__, vc->idx, res);
+	mutex_unlock(&dev->cmdlock);
+	return res;
+}
+
+static int s2255_cmd_status(struct s2255_vc *vc, u32 *pstatus)
+{
+	int res;
+	u32 chn_rev;
+	struct s2255_dev *dev = to_s2255_dev(vc->vdev.v4l2_dev);
+	__le32 *buffer = dev->cmdbuf;
+
+	mutex_lock(&dev->cmdlock);
+	chn_rev = G_chnmap[vc->idx];
+	dprintk(dev, 4, "%s chan %d\n", __func__, vc->idx);
+	/* form the get vid status command */
+	buffer[0] = IN_DATA_TOKEN;
+	buffer[1] = (__le32) cpu_to_le32(chn_rev);
+	buffer[2] = CMD_STATUS;
+	*pstatus = 0;
+	vc->vidstatus_ready = 0;
+	res = s2255_write_config(dev->udev, (unsigned char *)buffer, 512);
+	wait_event_timeout(vc->wait_vidstatus,
+			   (vc->vidstatus_ready != 0),
+			   msecs_to_jiffies(S2255_VIDSTATUS_TIMEOUT));
+	if (vc->vidstatus_ready != 1) {
+		dprintk(dev, 0, "s2255: no vidstatus response\n");
+		res = -EFAULT;
+	}
+	*pstatus = vc->vidstatus;
+	dprintk(dev, 4, "%s, vid status %d\n", __func__, *pstatus);
+	mutex_unlock(&dev->cmdlock);
+	return res;
+}
+
+static int start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+	struct s2255_vc *vc = vb2_get_drv_priv(vq);
+	int j;
+
+	vc->last_frame = -1;
+	vc->bad_payload = 0;
+	vc->cur_frame = 0;
+	vc->frame_count = 0;
+	for (j = 0; j < SYS_FRAMES; j++) {
+		vc->buffer.frame[j].ulState = S2255_READ_IDLE;
+		vc->buffer.frame[j].cur_size = 0;
+	}
+	return s2255_start_acquire(vc);
+}
+
+/* abort streaming and wait for last buffer */
+static void stop_streaming(struct vb2_queue *vq)
+{
+	struct s2255_vc *vc = vb2_get_drv_priv(vq);
+	struct s2255_buffer *buf, *node;
+	unsigned long flags;
+	(void) s2255_stop_acquire(vc);
+	spin_lock_irqsave(&vc->qlock, flags);
+	list_for_each_entry_safe(buf, node, &vc->buf_list, list) {
+		list_del(&buf->list);
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+		dprintk(vc->dev, 2, "[%p/%d] done\n",
+			buf, buf->vb.vb2_buf.index);
+	}
+	spin_unlock_irqrestore(&vc->qlock, flags);
+}
+
+static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id i)
+{
+	struct s2255_vc *vc = video_drvdata(file);
+	struct s2255_mode mode;
+	struct vb2_queue *q = &vc->vb_vidq;
+
+	/*
+	 * Changing the standard implies a format change, which is not allowed
+	 * while buffers for use with streaming have already been allocated.
+	 */
+	if (vb2_is_busy(q))
+		return -EBUSY;
+
+	mode = vc->mode;
+	if (i & V4L2_STD_525_60) {
+		dprintk(vc->dev, 4, "%s 60 Hz\n", __func__);
+		/* if changing format, reset frame decimation/intervals */
+		if (mode.format != FORMAT_NTSC) {
+			mode.restart = 1;
+			mode.format = FORMAT_NTSC;
+			mode.fdec = FDEC_1;
+			vc->width = LINE_SZ_4CIFS_NTSC;
+			vc->height = NUM_LINES_4CIFS_NTSC * 2;
+		}
+	} else if (i & V4L2_STD_625_50) {
+		dprintk(vc->dev, 4, "%s 50 Hz\n", __func__);
+		if (mode.format != FORMAT_PAL) {
+			mode.restart = 1;
+			mode.format = FORMAT_PAL;
+			mode.fdec = FDEC_1;
+			vc->width = LINE_SZ_4CIFS_PAL;
+			vc->height = NUM_LINES_4CIFS_PAL * 2;
+		}
+	} else
+		return -EINVAL;
+	vc->std = i;
+	if (mode.restart)
+		s2255_set_mode(vc, &mode);
+	return 0;
+}
+
+static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *i)
+{
+	struct s2255_vc *vc = video_drvdata(file);
+
+	*i = vc->std;
+	return 0;
+}
+
+/* Sensoray 2255 is a multiple channel capture device.
+   It does not have a "crossbar" of inputs.
+   We use one V4L device per channel. The user must
+   be aware that certain combinations are not allowed.
+   For instance, you cannot do full FPS on more than 2 channels(2 videodevs)
+   at once in color(you can do full fps on 4 channels with greyscale.
+*/
+static int vidioc_enum_input(struct file *file, void *priv,
+			     struct v4l2_input *inp)
+{
+	struct s2255_vc *vc = video_drvdata(file);
+	struct s2255_dev *dev = vc->dev;
+	u32 status = 0;
+
+	if (inp->index != 0)
+		return -EINVAL;
+	inp->type = V4L2_INPUT_TYPE_CAMERA;
+	inp->std = S2255_NORMS;
+	inp->status = 0;
+	if (dev->dsp_fw_ver >= S2255_MIN_DSP_STATUS) {
+		int rc;
+		rc = s2255_cmd_status(vc, &status);
+		dprintk(dev, 4, "s2255_cmd_status rc: %d status %x\n",
+			rc, status);
+		if (rc == 0)
+			inp->status =  (status & 0x01) ? 0
+				: V4L2_IN_ST_NO_SIGNAL;
+	}
+	switch (dev->pid) {
+	case 0x2255:
+	default:
+		strlcpy(inp->name, "Composite", sizeof(inp->name));
+		break;
+	case 0x2257:
+		strlcpy(inp->name, (vc->idx < 2) ? "Composite" : "S-Video",
+			sizeof(inp->name));
+		break;
+	}
+	return 0;
+}
+
+static int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
+{
+	*i = 0;
+	return 0;
+}
+static int vidioc_s_input(struct file *file, void *priv, unsigned int i)
+{
+	if (i > 0)
+		return -EINVAL;
+	return 0;
+}
+
+static int s2255_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct s2255_vc *vc =
+		container_of(ctrl->handler, struct s2255_vc, hdl);
+	struct s2255_mode mode;
+	mode = vc->mode;
+	/* update the mode to the corresponding value */
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		mode.bright = ctrl->val;
+		break;
+	case V4L2_CID_CONTRAST:
+		mode.contrast = ctrl->val;
+		break;
+	case V4L2_CID_HUE:
+		mode.hue = ctrl->val;
+		break;
+	case V4L2_CID_SATURATION:
+		mode.saturation = ctrl->val;
+		break;
+	case V4L2_CID_S2255_COLORFILTER:
+		mode.color &= ~MASK_INPUT_TYPE;
+		mode.color |= !ctrl->val << 16;
+		break;
+	case V4L2_CID_JPEG_COMPRESSION_QUALITY:
+		vc->jpegqual = ctrl->val;
+		return 0;
+	default:
+		return -EINVAL;
+	}
+	mode.restart = 0;
+	/* set mode here.  Note: stream does not need restarted.
+	   some V4L programs restart stream unnecessarily
+	   after a s_crtl.
+	*/
+	s2255_set_mode(vc, &mode);
+	return 0;
+}
+
+static int vidioc_g_jpegcomp(struct file *file, void *priv,
+			 struct v4l2_jpegcompression *jc)
+{
+	struct s2255_vc *vc = video_drvdata(file);
+
+	memset(jc, 0, sizeof(*jc));
+	jc->quality = vc->jpegqual;
+	dprintk(vc->dev, 2, "%s: quality %d\n", __func__, jc->quality);
+	return 0;
+}
+
+static int vidioc_s_jpegcomp(struct file *file, void *priv,
+			 const struct v4l2_jpegcompression *jc)
+{
+	struct s2255_vc *vc = video_drvdata(file);
+
+	if (jc->quality < 0 || jc->quality > 100)
+		return -EINVAL;
+	v4l2_ctrl_s_ctrl(vc->jpegqual_ctrl, jc->quality);
+	dprintk(vc->dev, 2, "%s: quality %d\n", __func__, jc->quality);
+	return 0;
+}
+
+static int vidioc_g_parm(struct file *file, void *priv,
+			 struct v4l2_streamparm *sp)
+{
+	__u32 def_num, def_dem;
+	struct s2255_vc *vc = video_drvdata(file);
+
+	if (sp->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+	sp->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
+	sp->parm.capture.capturemode = vc->cap_parm.capturemode;
+	sp->parm.capture.readbuffers = S2255_MIN_BUFS;
+	def_num = (vc->mode.format == FORMAT_NTSC) ? 1001 : 1000;
+	def_dem = (vc->mode.format == FORMAT_NTSC) ? 30000 : 25000;
+	sp->parm.capture.timeperframe.denominator = def_dem;
+	switch (vc->mode.fdec) {
+	default:
+	case FDEC_1:
+		sp->parm.capture.timeperframe.numerator = def_num;
+		break;
+	case FDEC_2:
+		sp->parm.capture.timeperframe.numerator = def_num * 2;
+		break;
+	case FDEC_3:
+		sp->parm.capture.timeperframe.numerator = def_num * 3;
+		break;
+	case FDEC_5:
+		sp->parm.capture.timeperframe.numerator = def_num * 5;
+		break;
+	}
+	dprintk(vc->dev, 4, "%s capture mode, %d timeperframe %d/%d\n",
+		__func__,
+		sp->parm.capture.capturemode,
+		sp->parm.capture.timeperframe.numerator,
+		sp->parm.capture.timeperframe.denominator);
+	return 0;
+}
+
+static int vidioc_s_parm(struct file *file, void *priv,
+			 struct v4l2_streamparm *sp)
+{
+	struct s2255_vc *vc = video_drvdata(file);
+	struct s2255_mode mode;
+	int fdec = FDEC_1;
+	__u32 def_num, def_dem;
+	if (sp->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+	mode = vc->mode;
+	/* high quality capture mode requires a stream restart */
+	if ((vc->cap_parm.capturemode != sp->parm.capture.capturemode)
+	    && vb2_is_streaming(&vc->vb_vidq))
+		return -EBUSY;
+	def_num = (mode.format == FORMAT_NTSC) ? 1001 : 1000;
+	def_dem = (mode.format == FORMAT_NTSC) ? 30000 : 25000;
+	if (def_dem != sp->parm.capture.timeperframe.denominator)
+		sp->parm.capture.timeperframe.numerator = def_num;
+	else if (sp->parm.capture.timeperframe.numerator <= def_num)
+		sp->parm.capture.timeperframe.numerator = def_num;
+	else if (sp->parm.capture.timeperframe.numerator <= (def_num * 2)) {
+		sp->parm.capture.timeperframe.numerator = def_num * 2;
+		fdec = FDEC_2;
+	} else if (sp->parm.capture.timeperframe.numerator <= (def_num * 3)) {
+		sp->parm.capture.timeperframe.numerator = def_num * 3;
+		fdec = FDEC_3;
+	} else {
+		sp->parm.capture.timeperframe.numerator = def_num * 5;
+		fdec = FDEC_5;
+	}
+	mode.fdec = fdec;
+	sp->parm.capture.timeperframe.denominator = def_dem;
+	sp->parm.capture.readbuffers = S2255_MIN_BUFS;
+	s2255_set_mode(vc, &mode);
+	dprintk(vc->dev, 4, "%s capture mode, %d timeperframe %d/%d, fdec %d\n",
+		__func__,
+		sp->parm.capture.capturemode,
+		sp->parm.capture.timeperframe.numerator,
+		sp->parm.capture.timeperframe.denominator, fdec);
+	return 0;
+}
+
+#define NUM_SIZE_ENUMS 3
+static const struct v4l2_frmsize_discrete ntsc_sizes[] = {
+	{ 640, 480 },
+	{ 640, 240 },
+	{ 320, 240 },
+};
+static const struct v4l2_frmsize_discrete pal_sizes[] = {
+	{ 704, 576 },
+	{ 704, 288 },
+	{ 352, 288 },
+};
+
+static int vidioc_enum_framesizes(struct file *file, void *priv,
+			    struct v4l2_frmsizeenum *fe)
+{
+	struct s2255_vc *vc = video_drvdata(file);
+	int is_ntsc = vc->std & V4L2_STD_525_60;
+	const struct s2255_fmt *fmt;
+
+	if (fe->index >= NUM_SIZE_ENUMS)
+		return -EINVAL;
+
+	fmt = format_by_fourcc(fe->pixel_format);
+	if (fmt == NULL)
+		return -EINVAL;
+	fe->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+	fe->discrete = is_ntsc ?  ntsc_sizes[fe->index] : pal_sizes[fe->index];
+	return 0;
+}
+
+static int vidioc_enum_frameintervals(struct file *file, void *priv,
+			    struct v4l2_frmivalenum *fe)
+{
+	struct s2255_vc *vc = video_drvdata(file);
+	const struct s2255_fmt *fmt;
+	const struct v4l2_frmsize_discrete *sizes;
+	int is_ntsc = vc->std & V4L2_STD_525_60;
+#define NUM_FRAME_ENUMS 4
+	int frm_dec[NUM_FRAME_ENUMS] = {1, 2, 3, 5};
+	int i;
+
+	if (fe->index >= NUM_FRAME_ENUMS)
+		return -EINVAL;
+
+	fmt = format_by_fourcc(fe->pixel_format);
+	if (fmt == NULL)
+		return -EINVAL;
+
+	sizes = is_ntsc ? ntsc_sizes : pal_sizes;
+	for (i = 0; i < NUM_SIZE_ENUMS; i++, sizes++)
+		if (fe->width == sizes->width &&
+		    fe->height == sizes->height)
+			break;
+	if (i == NUM_SIZE_ENUMS)
+		return -EINVAL;
+
+	fe->type = V4L2_FRMIVAL_TYPE_DISCRETE;
+	fe->discrete.denominator = is_ntsc ? 30000 : 25000;
+	fe->discrete.numerator = (is_ntsc ? 1001 : 1000) * frm_dec[fe->index];
+	dprintk(vc->dev, 4, "%s discrete %d/%d\n", __func__,
+		fe->discrete.numerator,
+		fe->discrete.denominator);
+	return 0;
+}
+
+static int s2255_open(struct file *file)
+{
+	struct s2255_vc *vc = video_drvdata(file);
+	struct s2255_dev *dev = vc->dev;
+	int state;
+	int rc = 0;
+
+	rc = v4l2_fh_open(file);
+	if (rc != 0)
+		return rc;
+
+	dprintk(dev, 1, "s2255: %s\n", __func__);
+	state = atomic_read(&dev->fw_data->fw_state);
+	switch (state) {
+	case S2255_FW_DISCONNECTING:
+		return -ENODEV;
+	case S2255_FW_FAILED:
+		s2255_dev_err(&dev->udev->dev,
+			"firmware load failed. retrying.\n");
+		s2255_fwload_start(dev);
+		wait_event_timeout(dev->fw_data->wait_fw,
+				   ((atomic_read(&dev->fw_data->fw_state)
+				     == S2255_FW_SUCCESS) ||
+				    (atomic_read(&dev->fw_data->fw_state)
+				     == S2255_FW_DISCONNECTING)),
+				   msecs_to_jiffies(S2255_LOAD_TIMEOUT));
+		/* state may have changed, re-read */
+		state = atomic_read(&dev->fw_data->fw_state);
+		break;
+	case S2255_FW_NOTLOADED:
+	case S2255_FW_LOADED_DSPWAIT:
+		/* give S2255_LOAD_TIMEOUT time for firmware to load in case
+		   driver loaded and then device immediately opened */
+		pr_info("%s waiting for firmware load\n", __func__);
+		wait_event_timeout(dev->fw_data->wait_fw,
+				   ((atomic_read(&dev->fw_data->fw_state)
+				     == S2255_FW_SUCCESS) ||
+				    (atomic_read(&dev->fw_data->fw_state)
+				     == S2255_FW_DISCONNECTING)),
+				   msecs_to_jiffies(S2255_LOAD_TIMEOUT));
+		/* state may have changed, re-read */
+		state = atomic_read(&dev->fw_data->fw_state);
+		break;
+	case S2255_FW_SUCCESS:
+	default:
+		break;
+	}
+	/* state may have changed in above switch statement */
+	switch (state) {
+	case S2255_FW_SUCCESS:
+		break;
+	case S2255_FW_FAILED:
+		pr_info("2255 firmware load failed.\n");
+		return -ENODEV;
+	case S2255_FW_DISCONNECTING:
+		pr_info("%s: disconnecting\n", __func__);
+		return -ENODEV;
+	case S2255_FW_LOADED_DSPWAIT:
+	case S2255_FW_NOTLOADED:
+		pr_info("%s: firmware not loaded, please retry\n",
+			__func__);
+		/*
+		 * Timeout on firmware load means device unusable.
+		 * Set firmware failure state.
+		 * On next s2255_open the firmware will be reloaded.
+		 */
+		atomic_set(&dev->fw_data->fw_state,
+			   S2255_FW_FAILED);
+		return -EAGAIN;
+	default:
+		pr_info("%s: unknown state\n", __func__);
+		return -EFAULT;
+	}
+	if (!vc->configured) {
+		/* configure channel to default state */
+		vc->fmt = &formats[0];
+		s2255_set_mode(vc, &vc->mode);
+		vc->configured = 1;
+	}
+	return 0;
+}
+
+static void s2255_destroy(struct s2255_dev *dev)
+{
+	dprintk(dev, 1, "%s", __func__);
+	/* board shutdown stops the read pipe if it is running */
+	s2255_board_shutdown(dev);
+	/* make sure firmware still not trying to load */
+	del_timer_sync(&dev->timer);  /* only started in .probe and .open */
+	if (dev->fw_data->fw_urb) {
+		usb_kill_urb(dev->fw_data->fw_urb);
+		usb_free_urb(dev->fw_data->fw_urb);
+		dev->fw_data->fw_urb = NULL;
+	}
+	release_firmware(dev->fw_data->fw);
+	kfree(dev->fw_data->pfw_data);
+	kfree(dev->fw_data);
+	/* reset the DSP so firmware can be reloaded next time */
+	s2255_reset_dsppower(dev);
+	mutex_destroy(&dev->lock);
+	usb_put_dev(dev->udev);
+	v4l2_device_unregister(&dev->v4l2_dev);
+	kfree(dev->cmdbuf);
+	kfree(dev);
+}
+
+static const struct v4l2_file_operations s2255_fops_v4l = {
+	.owner = THIS_MODULE,
+	.open = s2255_open,
+	.release = vb2_fop_release,
+	.poll = vb2_fop_poll,
+	.unlocked_ioctl = video_ioctl2,	/* V4L2 ioctl handler */
+	.mmap = vb2_fop_mmap,
+	.read = vb2_fop_read,
+};
+
+static const struct v4l2_ioctl_ops s2255_ioctl_ops = {
+	.vidioc_querycap = vidioc_querycap,
+	.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
+	.vidioc_reqbufs = vb2_ioctl_reqbufs,
+	.vidioc_querybuf = vb2_ioctl_querybuf,
+	.vidioc_qbuf = vb2_ioctl_qbuf,
+	.vidioc_dqbuf = vb2_ioctl_dqbuf,
+	.vidioc_s_std = vidioc_s_std,
+	.vidioc_g_std = vidioc_g_std,
+	.vidioc_enum_input = vidioc_enum_input,
+	.vidioc_g_input = vidioc_g_input,
+	.vidioc_s_input = vidioc_s_input,
+	.vidioc_streamon = vb2_ioctl_streamon,
+	.vidioc_streamoff = vb2_ioctl_streamoff,
+	.vidioc_s_jpegcomp = vidioc_s_jpegcomp,
+	.vidioc_g_jpegcomp = vidioc_g_jpegcomp,
+	.vidioc_s_parm = vidioc_s_parm,
+	.vidioc_g_parm = vidioc_g_parm,
+	.vidioc_enum_framesizes = vidioc_enum_framesizes,
+	.vidioc_enum_frameintervals = vidioc_enum_frameintervals,
+	.vidioc_log_status  = v4l2_ctrl_log_status,
+	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static void s2255_video_device_release(struct video_device *vdev)
+{
+	struct s2255_dev *dev = to_s2255_dev(vdev->v4l2_dev);
+	struct s2255_vc *vc =
+		container_of(vdev, struct s2255_vc, vdev);
+
+	dprintk(dev, 4, "%s, chnls: %d\n", __func__,
+		atomic_read(&dev->num_channels));
+
+	v4l2_ctrl_handler_free(&vc->hdl);
+
+	if (atomic_dec_and_test(&dev->num_channels))
+		s2255_destroy(dev);
+	return;
+}
+
+static const struct video_device template = {
+	.name = "s2255v",
+	.fops = &s2255_fops_v4l,
+	.ioctl_ops = &s2255_ioctl_ops,
+	.release = s2255_video_device_release,
+	.tvnorms = S2255_NORMS,
+};
+
+static const struct v4l2_ctrl_ops s2255_ctrl_ops = {
+	.s_ctrl = s2255_s_ctrl,
+};
+
+static const struct v4l2_ctrl_config color_filter_ctrl = {
+	.ops = &s2255_ctrl_ops,
+	.name = "Color Filter",
+	.id = V4L2_CID_S2255_COLORFILTER,
+	.type = V4L2_CTRL_TYPE_BOOLEAN,
+	.max = 1,
+	.step = 1,
+	.def = 1,
+};
+
+static int s2255_probe_v4l(struct s2255_dev *dev)
+{
+	int ret;
+	int i;
+	int cur_nr = video_nr;
+	struct s2255_vc *vc;
+	struct vb2_queue *q;
+
+	ret = v4l2_device_register(&dev->interface->dev, &dev->v4l2_dev);
+	if (ret)
+		return ret;
+	/* initialize all video 4 linux */
+	/* register 4 video devices */
+	for (i = 0; i < MAX_CHANNELS; i++) {
+		vc = &dev->vc[i];
+		INIT_LIST_HEAD(&vc->buf_list);
+
+		v4l2_ctrl_handler_init(&vc->hdl, 6);
+		v4l2_ctrl_new_std(&vc->hdl, &s2255_ctrl_ops,
+				V4L2_CID_BRIGHTNESS, -127, 127, 1, DEF_BRIGHT);
+		v4l2_ctrl_new_std(&vc->hdl, &s2255_ctrl_ops,
+				V4L2_CID_CONTRAST, 0, 255, 1, DEF_CONTRAST);
+		v4l2_ctrl_new_std(&vc->hdl, &s2255_ctrl_ops,
+				V4L2_CID_SATURATION, 0, 255, 1, DEF_SATURATION);
+		v4l2_ctrl_new_std(&vc->hdl, &s2255_ctrl_ops,
+				V4L2_CID_HUE, 0, 255, 1, DEF_HUE);
+		vc->jpegqual_ctrl = v4l2_ctrl_new_std(&vc->hdl,
+				&s2255_ctrl_ops,
+				V4L2_CID_JPEG_COMPRESSION_QUALITY,
+				0, 100, 1, S2255_DEF_JPEG_QUAL);
+		if (dev->dsp_fw_ver >= S2255_MIN_DSP_COLORFILTER &&
+		    (dev->pid != 0x2257 || vc->idx <= 1))
+			v4l2_ctrl_new_custom(&vc->hdl, &color_filter_ctrl,
+					     NULL);
+		if (vc->hdl.error) {
+			ret = vc->hdl.error;
+			v4l2_ctrl_handler_free(&vc->hdl);
+			dev_err(&dev->udev->dev, "couldn't register control\n");
+			break;
+		}
+		q = &vc->vb_vidq;
+		q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+		q->io_modes = VB2_MMAP | VB2_READ | VB2_USERPTR;
+		q->drv_priv = vc;
+		q->lock = &vc->vb_lock;
+		q->buf_struct_size = sizeof(struct s2255_buffer);
+		q->mem_ops = &vb2_vmalloc_memops;
+		q->ops = &s2255_video_qops;
+		q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+		ret = vb2_queue_init(q);
+		if (ret != 0) {
+			dev_err(&dev->udev->dev,
+				"%s vb2_queue_init 0x%x\n", __func__, ret);
+			break;
+		}
+		/* register video devices */
+		vc->vdev = template;
+		vc->vdev.queue = q;
+		vc->vdev.ctrl_handler = &vc->hdl;
+		vc->vdev.lock = &dev->lock;
+		vc->vdev.v4l2_dev = &dev->v4l2_dev;
+		video_set_drvdata(&vc->vdev, vc);
+		if (video_nr == -1)
+			ret = video_register_device(&vc->vdev,
+						    VFL_TYPE_GRABBER,
+						    video_nr);
+		else
+			ret = video_register_device(&vc->vdev,
+						    VFL_TYPE_GRABBER,
+						    cur_nr + i);
+
+		if (ret) {
+			dev_err(&dev->udev->dev,
+				"failed to register video device!\n");
+			break;
+		}
+		atomic_inc(&dev->num_channels);
+		v4l2_info(&dev->v4l2_dev, "V4L2 device registered as %s\n",
+			  video_device_node_name(&vc->vdev));
+
+	}
+	pr_info("Sensoray 2255 V4L driver Revision: %s\n",
+		S2255_VERSION);
+	/* if no channels registered, return error and probe will fail*/
+	if (atomic_read(&dev->num_channels) == 0) {
+		v4l2_device_unregister(&dev->v4l2_dev);
+		return ret;
+	}
+	if (atomic_read(&dev->num_channels) != MAX_CHANNELS)
+		pr_warn("s2255: Not all channels available.\n");
+	return 0;
+}
+
+/* this function moves the usb stream read pipe data
+ * into the system buffers.
+ * returns 0 on success, EAGAIN if more data to process( call this
+ * function again).
+ *
+ * Received frame structure:
+ * bytes 0-3:  marker : 0x2255DA4AL (S2255_MARKER_FRAME)
+ * bytes 4-7:  channel: 0-3
+ * bytes 8-11: payload size:  size of the frame
+ * bytes 12-payloadsize+12:  frame data
+ */
+static int save_frame(struct s2255_dev *dev, struct s2255_pipeinfo *pipe_info)
+{
+	char *pdest;
+	u32 offset = 0;
+	int bframe = 0;
+	char *psrc;
+	unsigned long copy_size;
+	unsigned long size;
+	s32 idx = -1;
+	struct s2255_framei *frm;
+	unsigned char *pdata;
+	struct s2255_vc *vc;
+	dprintk(dev, 100, "buffer to user\n");
+	vc = &dev->vc[dev->cc];
+	idx = vc->cur_frame;
+	frm = &vc->buffer.frame[idx];
+	if (frm->ulState == S2255_READ_IDLE) {
+		int jj;
+		unsigned int cc;
+		__le32 *pdword; /*data from dsp is little endian */
+		int payload;
+		/* search for marker codes */
+		pdata = (unsigned char *)pipe_info->transfer_buffer;
+		pdword = (__le32 *)pdata;
+		for (jj = 0; jj < (pipe_info->cur_transfer_size - 12); jj++) {
+			switch (*pdword) {
+			case S2255_MARKER_FRAME:
+				dprintk(dev, 4, "marker @ offset: %d [%x %x]\n",
+					jj, pdata[0], pdata[1]);
+				offset = jj + PREFIX_SIZE;
+				bframe = 1;
+				cc = le32_to_cpu(pdword[1]);
+				if (cc >= MAX_CHANNELS) {
+					dprintk(dev, 0,
+						"bad channel\n");
+					return -EINVAL;
+				}
+				/* reverse it */
+				dev->cc = G_chnmap[cc];
+				vc = &dev->vc[dev->cc];
+				payload =  le32_to_cpu(pdword[3]);
+				if (payload > vc->req_image_size) {
+					vc->bad_payload++;
+					/* discard the bad frame */
+					return -EINVAL;
+				}
+				vc->pkt_size = payload;
+				vc->jpg_size = le32_to_cpu(pdword[4]);
+				break;
+			case S2255_MARKER_RESPONSE:
+
+				pdata += DEF_USB_BLOCK;
+				jj += DEF_USB_BLOCK;
+				if (le32_to_cpu(pdword[1]) >= MAX_CHANNELS)
+					break;
+				cc = G_chnmap[le32_to_cpu(pdword[1])];
+				if (cc >= MAX_CHANNELS)
+					break;
+				vc = &dev->vc[cc];
+				switch (pdword[2]) {
+				case S2255_RESPONSE_SETMODE:
+					/* check if channel valid */
+					/* set mode ready */
+					vc->setmode_ready = 1;
+					wake_up(&vc->wait_setmode);
+					dprintk(dev, 5, "setmode rdy %d\n", cc);
+					break;
+				case S2255_RESPONSE_FW:
+					dev->chn_ready |= (1 << cc);
+					if ((dev->chn_ready & 0x0f) != 0x0f)
+						break;
+					/* all channels ready */
+					pr_info("s2255: fw loaded\n");
+					atomic_set(&dev->fw_data->fw_state,
+						   S2255_FW_SUCCESS);
+					wake_up(&dev->fw_data->wait_fw);
+					break;
+				case S2255_RESPONSE_STATUS:
+					vc->vidstatus = le32_to_cpu(pdword[3]);
+					vc->vidstatus_ready = 1;
+					wake_up(&vc->wait_vidstatus);
+					dprintk(dev, 5, "vstat %x chan %d\n",
+						le32_to_cpu(pdword[3]), cc);
+					break;
+				default:
+					pr_info("s2255 unknown resp\n");
+				}
+				pdata++;
+				break;
+			default:
+				pdata++;
+				break;
+			}
+			if (bframe)
+				break;
+		} /* for */
+		if (!bframe)
+			return -EINVAL;
+	}
+	vc = &dev->vc[dev->cc];
+	idx = vc->cur_frame;
+	frm = &vc->buffer.frame[idx];
+	/* search done.  now find out if should be acquiring on this channel */
+	if (!vb2_is_streaming(&vc->vb_vidq)) {
+		/* we found a frame, but this channel is turned off */
+		frm->ulState = S2255_READ_IDLE;
+		return -EINVAL;
+	}
+
+	if (frm->ulState == S2255_READ_IDLE) {
+		frm->ulState = S2255_READ_FRAME;
+		frm->cur_size = 0;
+	}
+
+	/* skip the marker 512 bytes (and offset if out of sync) */
+	psrc = (u8 *)pipe_info->transfer_buffer + offset;
+
+
+	if (frm->lpvbits == NULL) {
+		dprintk(dev, 1, "s2255 frame buffer == NULL.%p %p %d %d",
+			frm, dev, dev->cc, idx);
+		return -ENOMEM;
+	}
+
+	pdest = frm->lpvbits + frm->cur_size;
+
+	copy_size = (pipe_info->cur_transfer_size - offset);
+
+	size = vc->pkt_size - PREFIX_SIZE;
+
+	/* sanity check on pdest */
+	if ((copy_size + frm->cur_size) < vc->req_image_size)
+		memcpy(pdest, psrc, copy_size);
+
+	frm->cur_size += copy_size;
+	dprintk(dev, 4, "cur_size: %lu, size: %lu\n", frm->cur_size, size);
+
+	if (frm->cur_size >= size) {
+		dprintk(dev, 2, "******[%d]Buffer[%d]full*******\n",
+			dev->cc, idx);
+		vc->last_frame = vc->cur_frame;
+		vc->cur_frame++;
+		/* end of system frame ring buffer, start at zero */
+		if ((vc->cur_frame == SYS_FRAMES) ||
+		    (vc->cur_frame == vc->buffer.dwFrames))
+			vc->cur_frame = 0;
+		/* frame ready */
+		if (vb2_is_streaming(&vc->vb_vidq))
+			s2255_got_frame(vc, vc->jpg_size);
+		vc->frame_count++;
+		frm->ulState = S2255_READ_IDLE;
+		frm->cur_size = 0;
+
+	}
+	/* done successfully */
+	return 0;
+}
+
+static void s2255_read_video_callback(struct s2255_dev *dev,
+				      struct s2255_pipeinfo *pipe_info)
+{
+	int res;
+	dprintk(dev, 50, "callback read video\n");
+
+	if (dev->cc >= MAX_CHANNELS) {
+		dev->cc = 0;
+		dev_err(&dev->udev->dev, "invalid channel\n");
+		return;
+	}
+	/* otherwise copy to the system buffers */
+	res = save_frame(dev, pipe_info);
+	if (res != 0)
+		dprintk(dev, 4, "s2255: read callback failed\n");
+
+	dprintk(dev, 50, "callback read video done\n");
+	return;
+}
+
+static long s2255_vendor_req(struct s2255_dev *dev, unsigned char Request,
+			     u16 Index, u16 Value, void *TransferBuffer,
+			     s32 TransferBufferLength, int bOut)
+{
+	int r;
+	unsigned char *buf;
+
+	buf = kmalloc(TransferBufferLength, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	if (!bOut) {
+		r = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0),
+				    Request,
+				    USB_TYPE_VENDOR | USB_RECIP_DEVICE |
+				    USB_DIR_IN,
+				    Value, Index, buf,
+				    TransferBufferLength, HZ * 5);
+
+		if (r >= 0)
+			memcpy(TransferBuffer, buf, TransferBufferLength);
+	} else {
+		memcpy(buf, TransferBuffer, TransferBufferLength);
+		r = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0),
+				    Request, USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+				    Value, Index, buf,
+				    TransferBufferLength, HZ * 5);
+	}
+	kfree(buf);
+	return r;
+}
+
+/*
+ * retrieve FX2 firmware version. future use.
+ * @param dev pointer to device extension
+ * @return -1 for fail, else returns firmware version as an int(16 bits)
+ */
+static int s2255_get_fx2fw(struct s2255_dev *dev)
+{
+	int fw;
+	int ret;
+	unsigned char transBuffer[64];
+	ret = s2255_vendor_req(dev, S2255_VR_FW, 0, 0, transBuffer, 2,
+			       S2255_VR_IN);
+	if (ret < 0)
+		dprintk(dev, 2, "get fw error: %x\n", ret);
+	fw = transBuffer[0] + (transBuffer[1] << 8);
+	dprintk(dev, 2, "Get FW %x %x\n", transBuffer[0], transBuffer[1]);
+	return fw;
+}
+
+/*
+ * Create the system ring buffer to copy frames into from the
+ * usb read pipe.
+ */
+static int s2255_create_sys_buffers(struct s2255_vc *vc)
+{
+	unsigned long i;
+	unsigned long reqsize;
+	vc->buffer.dwFrames = SYS_FRAMES;
+	/* always allocate maximum size(PAL) for system buffers */
+	reqsize = SYS_FRAMES_MAXSIZE;
+
+	if (reqsize > SYS_FRAMES_MAXSIZE)
+		reqsize = SYS_FRAMES_MAXSIZE;
+
+	for (i = 0; i < SYS_FRAMES; i++) {
+		/* allocate the frames */
+		vc->buffer.frame[i].lpvbits = vmalloc(reqsize);
+		vc->buffer.frame[i].size = reqsize;
+		if (vc->buffer.frame[i].lpvbits == NULL) {
+			pr_info("out of memory.  using less frames\n");
+			vc->buffer.dwFrames = i;
+			break;
+		}
+	}
+
+	/* make sure internal states are set */
+	for (i = 0; i < SYS_FRAMES; i++) {
+		vc->buffer.frame[i].ulState = 0;
+		vc->buffer.frame[i].cur_size = 0;
+	}
+
+	vc->cur_frame = 0;
+	vc->last_frame = -1;
+	return 0;
+}
+
+static int s2255_release_sys_buffers(struct s2255_vc *vc)
+{
+	unsigned long i;
+	for (i = 0; i < SYS_FRAMES; i++) {
+		vfree(vc->buffer.frame[i].lpvbits);
+		vc->buffer.frame[i].lpvbits = NULL;
+	}
+	return 0;
+}
+
+static int s2255_board_init(struct s2255_dev *dev)
+{
+	struct s2255_mode mode_def = DEF_MODEI_NTSC_CONT;
+	int fw_ver;
+	int j;
+	struct s2255_pipeinfo *pipe = &dev->pipe;
+	dprintk(dev, 4, "board init: %p", dev);
+	memset(pipe, 0, sizeof(*pipe));
+	pipe->dev = dev;
+	pipe->cur_transfer_size = S2255_USB_XFER_SIZE;
+	pipe->max_transfer_size = S2255_USB_XFER_SIZE;
+
+	pipe->transfer_buffer = kzalloc(pipe->max_transfer_size,
+					GFP_KERNEL);
+	if (pipe->transfer_buffer == NULL) {
+		dprintk(dev, 1, "out of memory!\n");
+		return -ENOMEM;
+	}
+	/* query the firmware */
+	fw_ver = s2255_get_fx2fw(dev);
+
+	pr_info("s2255: usb firmware version %d.%d\n",
+		(fw_ver >> 8) & 0xff,
+		fw_ver & 0xff);
+
+	if (fw_ver < S2255_CUR_USB_FWVER)
+		pr_info("s2255: newer USB firmware available\n");
+
+	for (j = 0; j < MAX_CHANNELS; j++) {
+		struct s2255_vc *vc = &dev->vc[j];
+		vc->mode = mode_def;
+		if (dev->pid == 0x2257 && j > 1)
+			vc->mode.color |= (1 << 16);
+		vc->jpegqual = S2255_DEF_JPEG_QUAL;
+		vc->width = LINE_SZ_4CIFS_NTSC;
+		vc->height = NUM_LINES_4CIFS_NTSC * 2;
+		vc->std = V4L2_STD_NTSC_M;
+		vc->fmt = &formats[0];
+		vc->mode.restart = 1;
+		vc->req_image_size = get_transfer_size(&mode_def);
+		vc->frame_count = 0;
+		/* create the system buffers */
+		s2255_create_sys_buffers(vc);
+	}
+	/* start read pipe */
+	s2255_start_readpipe(dev);
+	dprintk(dev, 1, "%s: success\n", __func__);
+	return 0;
+}
+
+static int s2255_board_shutdown(struct s2255_dev *dev)
+{
+	u32 i;
+	dprintk(dev, 1, "%s: dev: %p", __func__,  dev);
+
+	for (i = 0; i < MAX_CHANNELS; i++) {
+		if (vb2_is_streaming(&dev->vc[i].vb_vidq))
+			s2255_stop_acquire(&dev->vc[i]);
+	}
+	s2255_stop_readpipe(dev);
+	for (i = 0; i < MAX_CHANNELS; i++)
+		s2255_release_sys_buffers(&dev->vc[i]);
+	/* release transfer buffer */
+	kfree(dev->pipe.transfer_buffer);
+	return 0;
+}
+
+static void read_pipe_completion(struct urb *purb)
+{
+	struct s2255_pipeinfo *pipe_info;
+	struct s2255_dev *dev;
+	int status;
+	int pipe;
+	pipe_info = purb->context;
+	if (pipe_info == NULL) {
+		dev_err(&purb->dev->dev, "no context!\n");
+		return;
+	}
+	dev = pipe_info->dev;
+	if (dev == NULL) {
+		dev_err(&purb->dev->dev, "no context!\n");
+		return;
+	}
+	status = purb->status;
+	/* if shutting down, do not resubmit, exit immediately */
+	if (status == -ESHUTDOWN) {
+		dprintk(dev, 2, "%s: err shutdown\n", __func__);
+		pipe_info->err_count++;
+		return;
+	}
+
+	if (pipe_info->state == 0) {
+		dprintk(dev, 2, "%s: exiting USB pipe", __func__);
+		return;
+	}
+
+	if (status == 0)
+		s2255_read_video_callback(dev, pipe_info);
+	else {
+		pipe_info->err_count++;
+		dprintk(dev, 1, "%s: failed URB %d\n", __func__, status);
+	}
+
+	pipe = usb_rcvbulkpipe(dev->udev, dev->read_endpoint);
+	/* reuse urb */
+	usb_fill_bulk_urb(pipe_info->stream_urb, dev->udev,
+			  pipe,
+			  pipe_info->transfer_buffer,
+			  pipe_info->cur_transfer_size,
+			  read_pipe_completion, pipe_info);
+
+	if (pipe_info->state != 0) {
+		if (usb_submit_urb(pipe_info->stream_urb, GFP_ATOMIC))
+			dev_err(&dev->udev->dev, "error submitting urb\n");
+	} else {
+		dprintk(dev, 2, "%s :complete state 0\n", __func__);
+	}
+	return;
+}
+
+static int s2255_start_readpipe(struct s2255_dev *dev)
+{
+	int pipe;
+	int retval;
+	struct s2255_pipeinfo *pipe_info = &dev->pipe;
+	pipe = usb_rcvbulkpipe(dev->udev, dev->read_endpoint);
+	dprintk(dev, 2, "%s: IN %d\n", __func__, dev->read_endpoint);
+	pipe_info->state = 1;
+	pipe_info->err_count = 0;
+	pipe_info->stream_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!pipe_info->stream_urb)
+		return -ENOMEM;
+	/* transfer buffer allocated in board_init */
+	usb_fill_bulk_urb(pipe_info->stream_urb, dev->udev,
+			  pipe,
+			  pipe_info->transfer_buffer,
+			  pipe_info->cur_transfer_size,
+			  read_pipe_completion, pipe_info);
+	retval = usb_submit_urb(pipe_info->stream_urb, GFP_KERNEL);
+	if (retval) {
+		pr_err("s2255: start read pipe failed\n");
+		return retval;
+	}
+	return 0;
+}
+
+/* starts acquisition process */
+static int s2255_start_acquire(struct s2255_vc *vc)
+{
+	int res;
+	unsigned long chn_rev;
+	int j;
+	struct s2255_dev *dev = to_s2255_dev(vc->vdev.v4l2_dev);
+	__le32 *buffer = dev->cmdbuf;
+
+	mutex_lock(&dev->cmdlock);
+	chn_rev = G_chnmap[vc->idx];
+	vc->last_frame = -1;
+	vc->bad_payload = 0;
+	vc->cur_frame = 0;
+	for (j = 0; j < SYS_FRAMES; j++) {
+		vc->buffer.frame[j].ulState = 0;
+		vc->buffer.frame[j].cur_size = 0;
+	}
+
+	/* send the start command */
+	buffer[0] = IN_DATA_TOKEN;
+	buffer[1] = (__le32) cpu_to_le32(chn_rev);
+	buffer[2] = CMD_START;
+	res = s2255_write_config(dev->udev, (unsigned char *)buffer, 512);
+	if (res != 0)
+		dev_err(&dev->udev->dev, "CMD_START error\n");
+
+	dprintk(dev, 2, "start acquire exit[%d] %d\n", vc->idx, res);
+	mutex_unlock(&dev->cmdlock);
+	return res;
+}
+
+static int s2255_stop_acquire(struct s2255_vc *vc)
+{
+	int res;
+	unsigned long chn_rev;
+	struct s2255_dev *dev = to_s2255_dev(vc->vdev.v4l2_dev);
+	__le32 *buffer = dev->cmdbuf;
+
+	mutex_lock(&dev->cmdlock);
+	chn_rev = G_chnmap[vc->idx];
+	/* send the stop command */
+	buffer[0] = IN_DATA_TOKEN;
+	buffer[1] = (__le32) cpu_to_le32(chn_rev);
+	buffer[2] = CMD_STOP;
+
+	res = s2255_write_config(dev->udev, (unsigned char *)buffer, 512);
+	if (res != 0)
+		dev_err(&dev->udev->dev, "CMD_STOP error\n");
+
+	dprintk(dev, 4, "%s: chn %d, res %d\n", __func__, vc->idx, res);
+	mutex_unlock(&dev->cmdlock);
+	return res;
+}
+
+static void s2255_stop_readpipe(struct s2255_dev *dev)
+{
+	struct s2255_pipeinfo *pipe = &dev->pipe;
+
+	pipe->state = 0;
+	if (pipe->stream_urb) {
+		/* cancel urb */
+		usb_kill_urb(pipe->stream_urb);
+		usb_free_urb(pipe->stream_urb);
+		pipe->stream_urb = NULL;
+	}
+	dprintk(dev, 4, "%s", __func__);
+	return;
+}
+
+static void s2255_fwload_start(struct s2255_dev *dev)
+{
+	s2255_reset_dsppower(dev);
+	dev->fw_data->fw_size = dev->fw_data->fw->size;
+	atomic_set(&dev->fw_data->fw_state, S2255_FW_NOTLOADED);
+	memcpy(dev->fw_data->pfw_data,
+	       dev->fw_data->fw->data, CHUNK_SIZE);
+	dev->fw_data->fw_loaded = CHUNK_SIZE;
+	usb_fill_bulk_urb(dev->fw_data->fw_urb, dev->udev,
+			  usb_sndbulkpipe(dev->udev, 2),
+			  dev->fw_data->pfw_data,
+			  CHUNK_SIZE, s2255_fwchunk_complete,
+			  dev->fw_data);
+	mod_timer(&dev->timer, jiffies + HZ);
+}
+
+/* standard usb probe function */
+static int s2255_probe(struct usb_interface *interface,
+		       const struct usb_device_id *id)
+{
+	struct s2255_dev *dev = NULL;
+	struct usb_host_interface *iface_desc;
+	struct usb_endpoint_descriptor *endpoint;
+	int i;
+	int retval = -ENOMEM;
+	__le32 *pdata;
+	int fw_size;
+
+	/* allocate memory for our device state and initialize it to zero */
+	dev = kzalloc(sizeof(struct s2255_dev), GFP_KERNEL);
+	if (dev == NULL) {
+		s2255_dev_err(&interface->dev, "out of memory\n");
+		return -ENOMEM;
+	}
+
+	dev->cmdbuf = kzalloc(S2255_CMDBUF_SIZE, GFP_KERNEL);
+	if (dev->cmdbuf == NULL) {
+		s2255_dev_err(&interface->dev, "out of memory\n");
+		goto errorFWDATA1;
+	}
+
+	atomic_set(&dev->num_channels, 0);
+	dev->pid = id->idProduct;
+	dev->fw_data = kzalloc(sizeof(struct s2255_fw), GFP_KERNEL);
+	if (!dev->fw_data)
+		goto errorFWDATA1;
+	mutex_init(&dev->lock);
+	mutex_init(&dev->cmdlock);
+	/* grab usb_device and save it */
+	dev->udev = usb_get_dev(interface_to_usbdev(interface));
+	if (dev->udev == NULL) {
+		dev_err(&interface->dev, "null usb device\n");
+		retval = -ENODEV;
+		goto errorUDEV;
+	}
+	dev_dbg(&interface->dev, "dev: %p, udev %p interface %p\n",
+		dev, dev->udev, interface);
+	dev->interface = interface;
+	/* set up the endpoint information  */
+	iface_desc = interface->cur_altsetting;
+	dev_dbg(&interface->dev, "num EP: %d\n",
+		iface_desc->desc.bNumEndpoints);
+	for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
+		endpoint = &iface_desc->endpoint[i].desc;
+		if (!dev->read_endpoint && usb_endpoint_is_bulk_in(endpoint)) {
+			/* we found the bulk in endpoint */
+			dev->read_endpoint = endpoint->bEndpointAddress;
+		}
+	}
+
+	if (!dev->read_endpoint) {
+		dev_err(&interface->dev, "Could not find bulk-in endpoint\n");
+		goto errorEP;
+	}
+	timer_setup(&dev->timer, s2255_timer, 0);
+	init_waitqueue_head(&dev->fw_data->wait_fw);
+	for (i = 0; i < MAX_CHANNELS; i++) {
+		struct s2255_vc *vc = &dev->vc[i];
+		vc->idx = i;
+		vc->dev = dev;
+		init_waitqueue_head(&vc->wait_setmode);
+		init_waitqueue_head(&vc->wait_vidstatus);
+		spin_lock_init(&vc->qlock);
+		mutex_init(&vc->vb_lock);
+	}
+
+	dev->fw_data->fw_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!dev->fw_data->fw_urb)
+		goto errorFWURB;
+
+	dev->fw_data->pfw_data = kzalloc(CHUNK_SIZE, GFP_KERNEL);
+	if (!dev->fw_data->pfw_data) {
+		dev_err(&interface->dev, "out of memory!\n");
+		goto errorFWDATA2;
+	}
+	/* load the first chunk */
+	if (request_firmware(&dev->fw_data->fw,
+			     FIRMWARE_FILE_NAME, &dev->udev->dev)) {
+		dev_err(&interface->dev, "sensoray 2255 failed to get firmware\n");
+		goto errorREQFW;
+	}
+	/* check the firmware is valid */
+	fw_size = dev->fw_data->fw->size;
+	pdata = (__le32 *) &dev->fw_data->fw->data[fw_size - 8];
+
+	if (*pdata != S2255_FW_MARKER) {
+		dev_err(&interface->dev, "Firmware invalid.\n");
+		retval = -ENODEV;
+		goto errorFWMARKER;
+	} else {
+		/* make sure firmware is the latest */
+		__le32 *pRel;
+		pRel = (__le32 *) &dev->fw_data->fw->data[fw_size - 4];
+		pr_info("s2255 dsp fw version %x\n", le32_to_cpu(*pRel));
+		dev->dsp_fw_ver = le32_to_cpu(*pRel);
+		if (dev->dsp_fw_ver < S2255_CUR_DSP_FWVER)
+			pr_info("s2255: f2255usb.bin out of date.\n");
+		if (dev->pid == 0x2257 &&
+				dev->dsp_fw_ver < S2255_MIN_DSP_COLORFILTER)
+			pr_warn("2257 needs firmware %d or above.\n",
+				S2255_MIN_DSP_COLORFILTER);
+	}
+	usb_reset_device(dev->udev);
+	/* load 2255 board specific */
+	retval = s2255_board_init(dev);
+	if (retval)
+		goto errorBOARDINIT;
+	s2255_fwload_start(dev);
+	/* loads v4l specific */
+	retval = s2255_probe_v4l(dev);
+	if (retval)
+		goto errorBOARDINIT;
+	dev_info(&interface->dev, "Sensoray 2255 detected\n");
+	return 0;
+errorBOARDINIT:
+	s2255_board_shutdown(dev);
+errorFWMARKER:
+	release_firmware(dev->fw_data->fw);
+errorREQFW:
+	kfree(dev->fw_data->pfw_data);
+errorFWDATA2:
+	usb_free_urb(dev->fw_data->fw_urb);
+errorFWURB:
+	del_timer_sync(&dev->timer);
+errorEP:
+	usb_put_dev(dev->udev);
+errorUDEV:
+	kfree(dev->fw_data);
+	mutex_destroy(&dev->lock);
+errorFWDATA1:
+	kfree(dev->cmdbuf);
+	kfree(dev);
+	pr_warn("Sensoray 2255 driver load failed: 0x%x\n", retval);
+	return retval;
+}
+
+/* disconnect routine. when board is removed physically or with rmmod */
+static void s2255_disconnect(struct usb_interface *interface)
+{
+	struct s2255_dev *dev = to_s2255_dev(usb_get_intfdata(interface));
+	int i;
+	int channels = atomic_read(&dev->num_channels);
+	mutex_lock(&dev->lock);
+	v4l2_device_disconnect(&dev->v4l2_dev);
+	mutex_unlock(&dev->lock);
+	/*see comments in the uvc_driver.c usb disconnect function */
+	atomic_inc(&dev->num_channels);
+	/* unregister each video device. */
+	for (i = 0; i < channels; i++)
+		video_unregister_device(&dev->vc[i].vdev);
+	/* wake up any of our timers */
+	atomic_set(&dev->fw_data->fw_state, S2255_FW_DISCONNECTING);
+	wake_up(&dev->fw_data->wait_fw);
+	for (i = 0; i < MAX_CHANNELS; i++) {
+		dev->vc[i].setmode_ready = 1;
+		wake_up(&dev->vc[i].wait_setmode);
+		dev->vc[i].vidstatus_ready = 1;
+		wake_up(&dev->vc[i].wait_vidstatus);
+	}
+	if (atomic_dec_and_test(&dev->num_channels))
+		s2255_destroy(dev);
+	dev_info(&interface->dev, "%s\n", __func__);
+}
+
+static struct usb_driver s2255_driver = {
+	.name = S2255_DRIVER_NAME,
+	.probe = s2255_probe,
+	.disconnect = s2255_disconnect,
+	.id_table = s2255_table,
+};
+
+module_usb_driver(s2255_driver);
+
+MODULE_DESCRIPTION("Sensoray 2255 Video for Linux driver");
+MODULE_AUTHOR("Dean Anderson (Sensoray Company Inc.)");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(S2255_VERSION);
+MODULE_FIRMWARE(FIRMWARE_FILE_NAME);
diff --git a/drivers/media/usb/siano/Kconfig b/drivers/media/usb/siano/Kconfig
new file mode 100644
index 0000000..d37b742
--- /dev/null
+++ b/drivers/media/usb/siano/Kconfig
@@ -0,0 +1,13 @@
+#
+# Siano Mobile Silicon Digital TV device configuration
+#
+
+config SMS_USB_DRV
+	tristate "Siano SMS1xxx based MDTV receiver"
+	depends on DVB_CORE && HAS_DMA
+	depends on !RC_CORE || RC_CORE
+	select MEDIA_COMMON_OPTIONS
+	select SMS_SIANO_MDTV
+	---help---
+	  Choose if you would like to have Siano's support for USB interface
+
diff --git a/drivers/media/usb/siano/Makefile b/drivers/media/usb/siano/Makefile
new file mode 100644
index 0000000..7d48864
--- /dev/null
+++ b/drivers/media/usb/siano/Makefile
@@ -0,0 +1,5 @@
+obj-$(CONFIG_SMS_USB_DRV) += smsusb.o
+
+ccflags-y += -Idrivers/media/common/siano
+ccflags-y += $(extra-cflags-y) $(extra-cflags-m)
+
diff --git a/drivers/media/usb/siano/smsusb.c b/drivers/media/usb/siano/smsusb.c
new file mode 100644
index 0000000..be36344
--- /dev/null
+++ b/drivers/media/usb/siano/smsusb.c
@@ -0,0 +1,736 @@
+/****************************************************************
+
+Siano Mobile Silicon, Inc.
+MDTV receiver kernel modules.
+Copyright (C) 2005-2009, Uri Shkolnik, Anatoly Greenblat
+
+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, see <http://www.gnu.org/licenses/>.
+
+****************************************************************/
+
+#include "smscoreapi.h"
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/usb.h>
+#include <linux/firmware.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <media/media-device.h>
+
+#include "sms-cards.h"
+#include "smsendian.h"
+
+#define USB1_BUFFER_SIZE		0x1000
+#define USB2_BUFFER_SIZE		0x2000
+
+#define MAX_BUFFERS		50
+#define MAX_URBS		10
+
+struct smsusb_device_t;
+
+enum smsusb_state {
+	SMSUSB_DISCONNECTED,
+	SMSUSB_SUSPENDED,
+	SMSUSB_ACTIVE
+};
+
+struct smsusb_urb_t {
+	struct list_head entry;
+	struct smscore_buffer_t *cb;
+	struct smsusb_device_t *dev;
+
+	struct urb urb;
+
+	/* For the bottom half */
+	struct work_struct wq;
+};
+
+struct smsusb_device_t {
+	struct usb_device *udev;
+	struct smscore_device_t *coredev;
+
+	struct smsusb_urb_t	surbs[MAX_URBS];
+
+	int		response_alignment;
+	int		buffer_size;
+
+	unsigned char in_ep;
+	unsigned char out_ep;
+	enum smsusb_state state;
+};
+
+static int smsusb_submit_urb(struct smsusb_device_t *dev,
+			     struct smsusb_urb_t *surb);
+
+/*
+ * Completing URB's callback handler - bottom half (proccess context)
+ * submits the URB prepared on smsusb_onresponse()
+ */
+static void do_submit_urb(struct work_struct *work)
+{
+	struct smsusb_urb_t *surb = container_of(work, struct smsusb_urb_t, wq);
+	struct smsusb_device_t *dev = surb->dev;
+
+	smsusb_submit_urb(dev, surb);
+}
+
+/*
+ * Completing URB's callback handler - top half (interrupt context)
+ * adds completing sms urb to the global surbs list and activtes the worker
+ * thread the surb
+ * IMPORTANT - blocking functions must not be called from here !!!
+
+ * @param urb pointer to a completing urb object
+ */
+static void smsusb_onresponse(struct urb *urb)
+{
+	struct smsusb_urb_t *surb = (struct smsusb_urb_t *) urb->context;
+	struct smsusb_device_t *dev = surb->dev;
+
+	if (urb->status == -ESHUTDOWN) {
+		pr_err("error, urb status %d (-ESHUTDOWN), %d bytes\n",
+			urb->status, urb->actual_length);
+		return;
+	}
+
+	if ((urb->actual_length > 0) && (urb->status == 0)) {
+		struct sms_msg_hdr *phdr = (struct sms_msg_hdr *)surb->cb->p;
+
+		smsendian_handle_message_header(phdr);
+		if (urb->actual_length >= phdr->msg_length) {
+			surb->cb->size = phdr->msg_length;
+
+			if (dev->response_alignment &&
+			    (phdr->msg_flags & MSG_HDR_FLAG_SPLIT_MSG)) {
+
+				surb->cb->offset =
+					dev->response_alignment +
+					((phdr->msg_flags >> 8) & 3);
+
+				/* sanity check */
+				if (((int) phdr->msg_length +
+				     surb->cb->offset) > urb->actual_length) {
+					pr_err("invalid response msglen %d offset %d size %d\n",
+						phdr->msg_length,
+						surb->cb->offset,
+						urb->actual_length);
+					goto exit_and_resubmit;
+				}
+
+				/* move buffer pointer and
+				 * copy header to its new location */
+				memcpy((char *) phdr + surb->cb->offset,
+				       phdr, sizeof(struct sms_msg_hdr));
+			} else
+				surb->cb->offset = 0;
+
+			pr_debug("received %s(%d) size: %d\n",
+				  smscore_translate_msg(phdr->msg_type),
+				  phdr->msg_type, phdr->msg_length);
+
+			smsendian_handle_rx_message((struct sms_msg_data *) phdr);
+
+			smscore_onresponse(dev->coredev, surb->cb);
+			surb->cb = NULL;
+		} else {
+			pr_err("invalid response msglen %d actual %d\n",
+				phdr->msg_length, urb->actual_length);
+		}
+	} else
+		pr_err("error, urb status %d, %d bytes\n",
+			urb->status, urb->actual_length);
+
+
+exit_and_resubmit:
+	INIT_WORK(&surb->wq, do_submit_urb);
+	schedule_work(&surb->wq);
+}
+
+static int smsusb_submit_urb(struct smsusb_device_t *dev,
+			     struct smsusb_urb_t *surb)
+{
+	if (!surb->cb) {
+		/* This function can sleep */
+		surb->cb = smscore_getbuffer(dev->coredev);
+		if (!surb->cb) {
+			pr_err("smscore_getbuffer(...) returned NULL\n");
+			return -ENOMEM;
+		}
+	}
+
+	usb_fill_bulk_urb(
+		&surb->urb,
+		dev->udev,
+		usb_rcvbulkpipe(dev->udev, dev->in_ep),
+		surb->cb->p,
+		dev->buffer_size,
+		smsusb_onresponse,
+		surb
+	);
+	surb->urb.transfer_flags |= URB_FREE_BUFFER;
+
+	return usb_submit_urb(&surb->urb, GFP_ATOMIC);
+}
+
+static void smsusb_stop_streaming(struct smsusb_device_t *dev)
+{
+	int i;
+
+	for (i = 0; i < MAX_URBS; i++) {
+		usb_kill_urb(&dev->surbs[i].urb);
+
+		if (dev->surbs[i].cb) {
+			smscore_putbuffer(dev->coredev, dev->surbs[i].cb);
+			dev->surbs[i].cb = NULL;
+		}
+	}
+}
+
+static int smsusb_start_streaming(struct smsusb_device_t *dev)
+{
+	int i, rc;
+
+	for (i = 0; i < MAX_URBS; i++) {
+		rc = smsusb_submit_urb(dev, &dev->surbs[i]);
+		if (rc < 0) {
+			pr_err("smsusb_submit_urb(...) failed\n");
+			smsusb_stop_streaming(dev);
+			break;
+		}
+	}
+
+	return rc;
+}
+
+static int smsusb_sendrequest(void *context, void *buffer, size_t size)
+{
+	struct smsusb_device_t *dev = (struct smsusb_device_t *) context;
+	struct sms_msg_hdr *phdr;
+	int dummy, ret;
+
+	if (dev->state != SMSUSB_ACTIVE) {
+		pr_debug("Device not active yet\n");
+		return -ENOENT;
+	}
+
+	phdr = kmalloc(size, GFP_KERNEL);
+	if (!phdr)
+		return -ENOMEM;
+	memcpy(phdr, buffer, size);
+
+	pr_debug("sending %s(%d) size: %d\n",
+		  smscore_translate_msg(phdr->msg_type), phdr->msg_type,
+		  phdr->msg_length);
+
+	smsendian_handle_tx_message((struct sms_msg_data *) phdr);
+	smsendian_handle_message_header((struct sms_msg_hdr *)phdr);
+	ret = usb_bulk_msg(dev->udev, usb_sndbulkpipe(dev->udev, 2),
+			    phdr, size, &dummy, 1000);
+
+	kfree(phdr);
+	return ret;
+}
+
+static char *smsusb1_fw_lkup[] = {
+	"dvbt_stellar_usb.inp",
+	"dvbh_stellar_usb.inp",
+	"tdmb_stellar_usb.inp",
+	"none",
+	"dvbt_bda_stellar_usb.inp",
+};
+
+static inline char *sms_get_fw_name(int mode, int board_id)
+{
+	char **fw = sms_get_board(board_id)->fw;
+	return (fw && fw[mode]) ? fw[mode] : smsusb1_fw_lkup[mode];
+}
+
+static int smsusb1_load_firmware(struct usb_device *udev, int id, int board_id)
+{
+	const struct firmware *fw;
+	u8 *fw_buffer;
+	int rc, dummy;
+	char *fw_filename;
+
+	if (id < 0)
+		id = sms_get_board(board_id)->default_mode;
+
+	if (id < DEVICE_MODE_DVBT || id > DEVICE_MODE_DVBT_BDA) {
+		pr_err("invalid firmware id specified %d\n", id);
+		return -EINVAL;
+	}
+
+	fw_filename = sms_get_fw_name(id, board_id);
+
+	rc = request_firmware(&fw, fw_filename, &udev->dev);
+	if (rc < 0) {
+		pr_warn("failed to open '%s' mode %d, trying again with default firmware\n",
+			fw_filename, id);
+
+		fw_filename = smsusb1_fw_lkup[id];
+		rc = request_firmware(&fw, fw_filename, &udev->dev);
+		if (rc < 0) {
+			pr_warn("failed to open '%s' mode %d\n",
+				 fw_filename, id);
+
+			return rc;
+		}
+	}
+
+	fw_buffer = kmalloc(fw->size, GFP_KERNEL);
+	if (fw_buffer) {
+		memcpy(fw_buffer, fw->data, fw->size);
+
+		rc = usb_bulk_msg(udev, usb_sndbulkpipe(udev, 2),
+				  fw_buffer, fw->size, &dummy, 1000);
+
+		pr_debug("sent %zu(%d) bytes, rc %d\n", fw->size, dummy, rc);
+
+		kfree(fw_buffer);
+	} else {
+		pr_err("failed to allocate firmware buffer\n");
+		rc = -ENOMEM;
+	}
+	pr_debug("read FW %s, size=%zu\n", fw_filename, fw->size);
+
+	release_firmware(fw);
+
+	return rc;
+}
+
+static void smsusb1_detectmode(void *context, int *mode)
+{
+	char *product_string =
+		((struct smsusb_device_t *) context)->udev->product;
+
+	*mode = DEVICE_MODE_NONE;
+
+	if (!product_string) {
+		product_string = "none";
+		pr_err("product string not found\n");
+	} else if (strstr(product_string, "DVBH"))
+		*mode = 1;
+	else if (strstr(product_string, "BDA"))
+		*mode = 4;
+	else if (strstr(product_string, "DVBT"))
+		*mode = 0;
+	else if (strstr(product_string, "TDMB"))
+		*mode = 2;
+
+	pr_debug("%d \"%s\"\n", *mode, product_string);
+}
+
+static int smsusb1_setmode(void *context, int mode)
+{
+	struct sms_msg_hdr msg = { MSG_SW_RELOAD_REQ, 0, HIF_TASK,
+			     sizeof(struct sms_msg_hdr), 0 };
+
+	if (mode < DEVICE_MODE_DVBT || mode > DEVICE_MODE_DVBT_BDA) {
+		pr_err("invalid firmware id specified %d\n", mode);
+		return -EINVAL;
+	}
+
+	return smsusb_sendrequest(context, &msg, sizeof(msg));
+}
+
+static void smsusb_term_device(struct usb_interface *intf)
+{
+	struct smsusb_device_t *dev = usb_get_intfdata(intf);
+
+	if (dev) {
+		dev->state = SMSUSB_DISCONNECTED;
+
+		smsusb_stop_streaming(dev);
+
+		/* unregister from smscore */
+		if (dev->coredev)
+			smscore_unregister_device(dev->coredev);
+
+		pr_debug("device 0x%p destroyed\n", dev);
+		kfree(dev);
+	}
+
+	usb_set_intfdata(intf, NULL);
+}
+
+static void *siano_media_device_register(struct smsusb_device_t *dev,
+					int board_id)
+{
+#ifdef CONFIG_MEDIA_CONTROLLER_DVB
+	struct media_device *mdev;
+	struct usb_device *udev = dev->udev;
+	struct sms_board *board = sms_get_board(board_id);
+	int ret;
+
+	mdev = kzalloc(sizeof(*mdev), GFP_KERNEL);
+	if (!mdev)
+		return NULL;
+
+	media_device_usb_init(mdev, udev, board->name);
+
+	ret = media_device_register(mdev);
+	if (ret) {
+		media_device_cleanup(mdev);
+		kfree(mdev);
+		return NULL;
+	}
+
+	pr_info("media controller created\n");
+
+	return mdev;
+#else
+	return NULL;
+#endif
+}
+
+static int smsusb_init_device(struct usb_interface *intf, int board_id)
+{
+	struct smsdevice_params_t params;
+	struct smsusb_device_t *dev;
+	void *mdev;
+	int i, rc;
+
+	/* create device object */
+	dev = kzalloc(sizeof(struct smsusb_device_t), GFP_KERNEL);
+	if (!dev)
+		return -ENOMEM;
+
+	memset(&params, 0, sizeof(params));
+	usb_set_intfdata(intf, dev);
+	dev->udev = interface_to_usbdev(intf);
+	dev->state = SMSUSB_DISCONNECTED;
+
+	params.device_type = sms_get_board(board_id)->type;
+
+	switch (params.device_type) {
+	case SMS_STELLAR:
+		dev->buffer_size = USB1_BUFFER_SIZE;
+
+		params.setmode_handler = smsusb1_setmode;
+		params.detectmode_handler = smsusb1_detectmode;
+		break;
+	case SMS_UNKNOWN_TYPE:
+		pr_err("Unspecified sms device type!\n");
+		/* fall-thru */
+	default:
+		dev->buffer_size = USB2_BUFFER_SIZE;
+		dev->response_alignment =
+		    le16_to_cpu(dev->udev->ep_in[1]->desc.wMaxPacketSize) -
+		    sizeof(struct sms_msg_hdr);
+
+		params.flags |= SMS_DEVICE_FAMILY2;
+		break;
+	}
+
+	for (i = 0; i < intf->cur_altsetting->desc.bNumEndpoints; i++) {
+		if (intf->cur_altsetting->endpoint[i].desc. bEndpointAddress & USB_DIR_IN)
+			dev->in_ep = intf->cur_altsetting->endpoint[i].desc.bEndpointAddress;
+		else
+			dev->out_ep = intf->cur_altsetting->endpoint[i].desc.bEndpointAddress;
+	}
+
+	pr_debug("in_ep = %02x, out_ep = %02x\n",
+		dev->in_ep, dev->out_ep);
+
+	params.device = &dev->udev->dev;
+	params.usb_device = dev->udev;
+	params.buffer_size = dev->buffer_size;
+	params.num_buffers = MAX_BUFFERS;
+	params.sendrequest_handler = smsusb_sendrequest;
+	params.context = dev;
+	usb_make_path(dev->udev, params.devpath, sizeof(params.devpath));
+
+	mdev = siano_media_device_register(dev, board_id);
+
+	/* register in smscore */
+	rc = smscore_register_device(&params, &dev->coredev, 0, mdev);
+	if (rc < 0) {
+		pr_err("smscore_register_device(...) failed, rc %d\n", rc);
+		smsusb_term_device(intf);
+#ifdef CONFIG_MEDIA_CONTROLLER_DVB
+		media_device_unregister(mdev);
+#endif
+		kfree(mdev);
+		return rc;
+	}
+
+	smscore_set_board_id(dev->coredev, board_id);
+
+	dev->coredev->is_usb_device = true;
+
+	/* initialize urbs */
+	for (i = 0; i < MAX_URBS; i++) {
+		dev->surbs[i].dev = dev;
+		usb_init_urb(&dev->surbs[i].urb);
+	}
+
+	pr_debug("smsusb_start_streaming(...).\n");
+	rc = smsusb_start_streaming(dev);
+	if (rc < 0) {
+		pr_err("smsusb_start_streaming(...) failed\n");
+		smsusb_term_device(intf);
+		return rc;
+	}
+
+	dev->state = SMSUSB_ACTIVE;
+
+	rc = smscore_start_device(dev->coredev);
+	if (rc < 0) {
+		pr_err("smscore_start_device(...) failed\n");
+		smsusb_term_device(intf);
+		return rc;
+	}
+
+	pr_debug("device 0x%p created\n", dev);
+
+	return rc;
+}
+
+static int smsusb_probe(struct usb_interface *intf,
+			const struct usb_device_id *id)
+{
+	struct usb_device *udev = interface_to_usbdev(intf);
+	char devpath[32];
+	int i, rc;
+
+	pr_info("board id=%lu, interface number %d\n",
+		 id->driver_info,
+		 intf->cur_altsetting->desc.bInterfaceNumber);
+
+	if (sms_get_board(id->driver_info)->intf_num !=
+	    intf->cur_altsetting->desc.bInterfaceNumber) {
+		pr_debug("interface %d won't be used. Expecting interface %d to popup\n",
+			intf->cur_altsetting->desc.bInterfaceNumber,
+			sms_get_board(id->driver_info)->intf_num);
+		return -ENODEV;
+	}
+
+	if (intf->num_altsetting > 1) {
+		rc = usb_set_interface(udev,
+				       intf->cur_altsetting->desc.bInterfaceNumber,
+				       0);
+		if (rc < 0) {
+			pr_err("usb_set_interface failed, rc %d\n", rc);
+			return rc;
+		}
+	}
+
+	pr_debug("smsusb_probe %d\n",
+	       intf->cur_altsetting->desc.bInterfaceNumber);
+	for (i = 0; i < intf->cur_altsetting->desc.bNumEndpoints; i++) {
+		pr_debug("endpoint %d %02x %02x %d\n", i,
+		       intf->cur_altsetting->endpoint[i].desc.bEndpointAddress,
+		       intf->cur_altsetting->endpoint[i].desc.bmAttributes,
+		       intf->cur_altsetting->endpoint[i].desc.wMaxPacketSize);
+		if (intf->cur_altsetting->endpoint[i].desc.bEndpointAddress &
+		    USB_DIR_IN)
+			rc = usb_clear_halt(udev, usb_rcvbulkpipe(udev,
+				intf->cur_altsetting->endpoint[i].desc.bEndpointAddress));
+		else
+			rc = usb_clear_halt(udev, usb_sndbulkpipe(udev,
+				intf->cur_altsetting->endpoint[i].desc.bEndpointAddress));
+	}
+	if ((udev->actconfig->desc.bNumInterfaces == 2) &&
+	    (intf->cur_altsetting->desc.bInterfaceNumber == 0)) {
+		pr_debug("rom interface 0 is not used\n");
+		return -ENODEV;
+	}
+
+	if (id->driver_info == SMS1XXX_BOARD_SIANO_STELLAR_ROM) {
+		/* Detected a Siano Stellar uninitialized */
+
+		snprintf(devpath, sizeof(devpath), "usb\\%d-%s",
+			 udev->bus->busnum, udev->devpath);
+		pr_info("stellar device in cold state was found at %s.\n",
+			devpath);
+		rc = smsusb1_load_firmware(
+				udev, smscore_registry_getmode(devpath),
+				id->driver_info);
+
+		/* This device will reset and gain another USB ID */
+		if (!rc)
+			pr_info("stellar device now in warm state\n");
+		else
+			pr_err("Failed to put stellar in warm state. Error: %d\n",
+			       rc);
+
+		return rc;
+	} else {
+		rc = smsusb_init_device(intf, id->driver_info);
+	}
+
+	pr_info("Device initialized with return code %d\n", rc);
+	sms_board_load_modules(id->driver_info);
+	return rc;
+}
+
+static void smsusb_disconnect(struct usb_interface *intf)
+{
+	smsusb_term_device(intf);
+}
+
+static int smsusb_suspend(struct usb_interface *intf, pm_message_t msg)
+{
+	struct smsusb_device_t *dev = usb_get_intfdata(intf);
+	printk(KERN_INFO "%s  Entering status %d.\n", __func__, msg.event);
+	dev->state = SMSUSB_SUSPENDED;
+	/*smscore_set_power_mode(dev, SMS_POWER_MODE_SUSPENDED);*/
+	smsusb_stop_streaming(dev);
+	return 0;
+}
+
+static int smsusb_resume(struct usb_interface *intf)
+{
+	int rc, i;
+	struct smsusb_device_t *dev = usb_get_intfdata(intf);
+	struct usb_device *udev = interface_to_usbdev(intf);
+
+	printk(KERN_INFO "%s  Entering.\n", __func__);
+	usb_clear_halt(udev, usb_rcvbulkpipe(udev, dev->in_ep));
+	usb_clear_halt(udev, usb_sndbulkpipe(udev, dev->out_ep));
+
+	for (i = 0; i < intf->cur_altsetting->desc.bNumEndpoints; i++)
+		printk(KERN_INFO "endpoint %d %02x %02x %d\n", i,
+		       intf->cur_altsetting->endpoint[i].desc.bEndpointAddress,
+		       intf->cur_altsetting->endpoint[i].desc.bmAttributes,
+		       intf->cur_altsetting->endpoint[i].desc.wMaxPacketSize);
+
+	if (intf->num_altsetting > 0) {
+		rc = usb_set_interface(udev,
+				       intf->cur_altsetting->desc.
+				       bInterfaceNumber, 0);
+		if (rc < 0) {
+			printk(KERN_INFO "%s usb_set_interface failed, rc %d\n",
+			       __func__, rc);
+			return rc;
+		}
+	}
+
+	smsusb_start_streaming(dev);
+	return 0;
+}
+
+static const struct usb_device_id smsusb_id_table[] = {
+	/* This device is only present before firmware load */
+	{ USB_DEVICE(0x187f, 0x0010),
+		.driver_info = SMS1XXX_BOARD_SIANO_STELLAR_ROM },
+	/* This device pops up after firmware load */
+	{ USB_DEVICE(0x187f, 0x0100),
+		.driver_info = SMS1XXX_BOARD_SIANO_STELLAR },
+
+	{ USB_DEVICE(0x187f, 0x0200),
+		.driver_info = SMS1XXX_BOARD_SIANO_NOVA_A },
+	{ USB_DEVICE(0x187f, 0x0201),
+		.driver_info = SMS1XXX_BOARD_SIANO_NOVA_B },
+	{ USB_DEVICE(0x187f, 0x0300),
+		.driver_info = SMS1XXX_BOARD_SIANO_VEGA },
+	{ USB_DEVICE(0x2040, 0x1700),
+		.driver_info = SMS1XXX_BOARD_HAUPPAUGE_CATAMOUNT },
+	{ USB_DEVICE(0x2040, 0x1800),
+		.driver_info = SMS1XXX_BOARD_HAUPPAUGE_OKEMO_A },
+	{ USB_DEVICE(0x2040, 0x1801),
+		.driver_info = SMS1XXX_BOARD_HAUPPAUGE_OKEMO_B },
+	{ USB_DEVICE(0x2040, 0x2000),
+		.driver_info = SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD },
+	{ USB_DEVICE(0x2040, 0x2009),
+		.driver_info = SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD_R2 },
+	{ USB_DEVICE(0x2040, 0x200a),
+		.driver_info = SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD },
+	{ USB_DEVICE(0x2040, 0x2010),
+		.driver_info = SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD },
+	{ USB_DEVICE(0x2040, 0x2011),
+		.driver_info = SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD },
+	{ USB_DEVICE(0x2040, 0x2019),
+		.driver_info = SMS1XXX_BOARD_HAUPPAUGE_TIGER_MINICARD },
+	{ USB_DEVICE(0x2040, 0x5500),
+		.driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+	{ USB_DEVICE(0x2040, 0x5510),
+		.driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+	{ USB_DEVICE(0x2040, 0x5520),
+		.driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+	{ USB_DEVICE(0x2040, 0x5530),
+		.driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+	{ USB_DEVICE(0x2040, 0x5580),
+		.driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+	{ USB_DEVICE(0x2040, 0x5590),
+		.driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+	{ USB_DEVICE(0x187f, 0x0202),
+		.driver_info = SMS1XXX_BOARD_SIANO_NICE },
+	{ USB_DEVICE(0x187f, 0x0301),
+		.driver_info = SMS1XXX_BOARD_SIANO_VENICE },
+	{ USB_DEVICE(0x2040, 0xb900),
+		.driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+	{ USB_DEVICE(0x2040, 0xb910),
+		.driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+	{ USB_DEVICE(0x2040, 0xb980),
+		.driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+	{ USB_DEVICE(0x2040, 0xb990),
+		.driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+	{ USB_DEVICE(0x2040, 0xc000),
+		.driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+	{ USB_DEVICE(0x2040, 0xc010),
+		.driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+	{ USB_DEVICE(0x2040, 0xc080),
+		.driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+	{ USB_DEVICE(0x2040, 0xc090),
+		.driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+	{ USB_DEVICE(0x2040, 0xc0a0),
+		.driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+	{ USB_DEVICE(0x2040, 0xf5a0),
+		.driver_info = SMS1XXX_BOARD_HAUPPAUGE_WINDHAM },
+	{ USB_DEVICE(0x187f, 0x0202),
+		.driver_info = SMS1XXX_BOARD_SIANO_NICE },
+	{ USB_DEVICE(0x187f, 0x0301),
+		.driver_info = SMS1XXX_BOARD_SIANO_VENICE },
+	{ USB_DEVICE(0x187f, 0x0302),
+		.driver_info = SMS1XXX_BOARD_SIANO_VENICE },
+	{ USB_DEVICE(0x187f, 0x0310),
+		.driver_info = SMS1XXX_BOARD_SIANO_MING },
+	{ USB_DEVICE(0x187f, 0x0500),
+		.driver_info = SMS1XXX_BOARD_SIANO_PELE },
+	{ USB_DEVICE(0x187f, 0x0600),
+		.driver_info = SMS1XXX_BOARD_SIANO_RIO },
+	{ USB_DEVICE(0x187f, 0x0700),
+		.driver_info = SMS1XXX_BOARD_SIANO_DENVER_2160 },
+	{ USB_DEVICE(0x187f, 0x0800),
+		.driver_info = SMS1XXX_BOARD_SIANO_DENVER_1530 },
+	{ USB_DEVICE(0x19D2, 0x0086),
+		.driver_info = SMS1XXX_BOARD_ZTE_DVB_DATA_CARD },
+	{ USB_DEVICE(0x19D2, 0x0078),
+		.driver_info = SMS1XXX_BOARD_ONDA_MDTV_DATA_CARD },
+	{ USB_DEVICE(0x3275, 0x0080),
+		.driver_info = SMS1XXX_BOARD_SIANO_RIO },
+	{ USB_DEVICE(0x2013, 0x0257),
+		.driver_info = SMS1XXX_BOARD_PCTV_77E },
+	{ } /* Terminating entry */
+	};
+
+MODULE_DEVICE_TABLE(usb, smsusb_id_table);
+
+static struct usb_driver smsusb_driver = {
+	.name			= "smsusb",
+	.probe			= smsusb_probe,
+	.disconnect		= smsusb_disconnect,
+	.id_table		= smsusb_id_table,
+
+	.suspend		= smsusb_suspend,
+	.resume			= smsusb_resume,
+};
+
+module_usb_driver(smsusb_driver);
+
+MODULE_DESCRIPTION("Driver for the Siano SMS1xxx USB dongle");
+MODULE_AUTHOR("Siano Mobile Silicon, INC. (uris@siano-ms.com)");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/usb/stk1160/Kconfig b/drivers/media/usb/stk1160/Kconfig
new file mode 100644
index 0000000..425ed00
--- /dev/null
+++ b/drivers/media/usb/stk1160/Kconfig
@@ -0,0 +1,20 @@
+config VIDEO_STK1160_COMMON
+	tristate "STK1160 USB video capture support"
+	depends on VIDEO_DEV && I2C
+
+	---help---
+	  This is a video4linux driver for STK1160 based video capture devices.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called stk1160.
+
+	  This driver only provides support for video capture. For audio
+	  capture, you need to select the snd-usb-audio driver (i.e.
+	  CONFIG_SND_USB_AUDIO).
+
+config VIDEO_STK1160
+	tristate
+	depends on VIDEO_STK1160_COMMON
+	default y
+	select VIDEOBUF2_VMALLOC
+	select VIDEO_SAA711X
diff --git a/drivers/media/usb/stk1160/Makefile b/drivers/media/usb/stk1160/Makefile
new file mode 100644
index 0000000..b943db0
--- /dev/null
+++ b/drivers/media/usb/stk1160/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+stk1160-y :=	stk1160-core.o \
+		stk1160-v4l.o \
+		stk1160-video.o \
+		stk1160-i2c.o \
+		stk1160-ac97.o
+
+obj-$(CONFIG_VIDEO_STK1160) += stk1160.o
diff --git a/drivers/media/usb/stk1160/stk1160-ac97.c b/drivers/media/usb/stk1160/stk1160-ac97.c
new file mode 100644
index 0000000..2169be8
--- /dev/null
+++ b/drivers/media/usb/stk1160/stk1160-ac97.c
@@ -0,0 +1,165 @@
+/*
+ * STK1160 driver
+ *
+ * Copyright (C) 2012 Ezequiel Garcia
+ * <elezegarcia--a.t--gmail.com>
+ *
+ * Copyright (C) 2016 Marcel Hasler
+ * <mahasler--a.t--gmail.com>
+ *
+ * Based on Easycap driver by R.M. Thomas
+ *	Copyright (C) 2010 R.M. Thomas
+ *	<rmthomas--a.t--sciolus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/delay.h>
+
+#include "stk1160.h"
+#include "stk1160-reg.h"
+
+static int stk1160_ac97_wait_transfer_complete(struct stk1160 *dev)
+{
+	unsigned long timeout = jiffies + msecs_to_jiffies(STK1160_AC97_TIMEOUT);
+	u8 value;
+
+	/* Wait for AC97 transfer to complete */
+	while (time_is_after_jiffies(timeout)) {
+		stk1160_read_reg(dev, STK1160_AC97CTL_0, &value);
+
+		if (!(value & (STK1160_AC97CTL_0_CR | STK1160_AC97CTL_0_CW)))
+			return 0;
+
+		usleep_range(50, 100);
+	}
+
+	stk1160_err("AC97 transfer took too long, this should never happen!");
+	return -EBUSY;
+}
+
+static void stk1160_write_ac97(struct stk1160 *dev, u16 reg, u16 value)
+{
+	/* Set codec register address */
+	stk1160_write_reg(dev, STK1160_AC97_ADDR, reg);
+
+	/* Set codec command */
+	stk1160_write_reg(dev, STK1160_AC97_CMD, value & 0xff);
+	stk1160_write_reg(dev, STK1160_AC97_CMD + 1, (value & 0xff00) >> 8);
+
+	/* Set command write bit to initiate write operation */
+	stk1160_write_reg(dev, STK1160_AC97CTL_0, 0x8c);
+
+	/* Wait for command write bit to be cleared */
+	stk1160_ac97_wait_transfer_complete(dev);
+}
+
+#ifdef DEBUG
+static u16 stk1160_read_ac97(struct stk1160 *dev, u16 reg)
+{
+	u8 vall = 0;
+	u8 valh = 0;
+
+	/* Set codec register address */
+	stk1160_write_reg(dev, STK1160_AC97_ADDR, reg);
+
+	/* Set command read bit to initiate read operation */
+	stk1160_write_reg(dev, STK1160_AC97CTL_0, 0x8b);
+
+	/* Wait for command read bit to be cleared */
+	if (stk1160_ac97_wait_transfer_complete(dev) < 0)
+		return 0;
+
+
+	/* Retrieve register value */
+	stk1160_read_reg(dev, STK1160_AC97_CMD, &vall);
+	stk1160_read_reg(dev, STK1160_AC97_CMD + 1, &valh);
+
+	return (valh << 8) | vall;
+}
+
+void stk1160_ac97_dump_regs(struct stk1160 *dev)
+{
+	u16 value;
+
+	value = stk1160_read_ac97(dev, 0x12); /* CD volume */
+	stk1160_dbg("0x12 == 0x%04x", value);
+
+	value = stk1160_read_ac97(dev, 0x10); /* Line-in volume */
+	stk1160_dbg("0x10 == 0x%04x", value);
+
+	value = stk1160_read_ac97(dev, 0x0e); /* MIC volume (mono) */
+	stk1160_dbg("0x0e == 0x%04x", value);
+
+	value = stk1160_read_ac97(dev, 0x16); /* Aux volume */
+	stk1160_dbg("0x16 == 0x%04x", value);
+
+	value = stk1160_read_ac97(dev, 0x1a); /* Record select */
+	stk1160_dbg("0x1a == 0x%04x", value);
+
+	value = stk1160_read_ac97(dev, 0x02); /* Master volume */
+	stk1160_dbg("0x02 == 0x%04x", value);
+
+	value = stk1160_read_ac97(dev, 0x1c); /* Record gain */
+	stk1160_dbg("0x1c == 0x%04x", value);
+}
+#endif
+
+static int stk1160_has_audio(struct stk1160 *dev)
+{
+	u8 value;
+
+	stk1160_read_reg(dev, STK1160_POSV_L, &value);
+	return !(value & STK1160_POSV_L_ACDOUT);
+}
+
+static int stk1160_has_ac97(struct stk1160 *dev)
+{
+	u8 value;
+
+	stk1160_read_reg(dev, STK1160_POSV_L, &value);
+	return !(value & STK1160_POSV_L_ACSYNC);
+}
+
+void stk1160_ac97_setup(struct stk1160 *dev)
+{
+	if (!stk1160_has_audio(dev)) {
+		stk1160_info("Device doesn't support audio, skipping AC97 setup.");
+		return;
+	}
+
+	if (!stk1160_has_ac97(dev)) {
+		stk1160_info("Device uses internal 8-bit ADC, skipping AC97 setup.");
+		return;
+	}
+
+	/* Two-step reset AC97 interface and hardware codec */
+	stk1160_write_reg(dev, STK1160_AC97CTL_0, 0x94);
+	stk1160_write_reg(dev, STK1160_AC97CTL_0, 0x8c);
+
+	/* Set 16-bit audio data and choose L&R channel*/
+	stk1160_write_reg(dev, STK1160_AC97CTL_1 + 2, 0x01);
+	stk1160_write_reg(dev, STK1160_AC97CTL_1 + 3, 0x00);
+
+	/* Setup channels */
+	stk1160_write_ac97(dev, 0x12, 0x8808); /* CD volume */
+	stk1160_write_ac97(dev, 0x10, 0x0808); /* Line-in volume */
+	stk1160_write_ac97(dev, 0x0e, 0x0008); /* MIC volume (mono) */
+	stk1160_write_ac97(dev, 0x16, 0x0808); /* Aux volume */
+	stk1160_write_ac97(dev, 0x1a, 0x0404); /* Record select */
+	stk1160_write_ac97(dev, 0x02, 0x0000); /* Master volume */
+	stk1160_write_ac97(dev, 0x1c, 0x0808); /* Record gain */
+
+#ifdef DEBUG
+	stk1160_ac97_dump_regs(dev);
+#endif
+}
diff --git a/drivers/media/usb/stk1160/stk1160-core.c b/drivers/media/usb/stk1160/stk1160-core.c
new file mode 100644
index 0000000..468f5cc
--- /dev/null
+++ b/drivers/media/usb/stk1160/stk1160-core.c
@@ -0,0 +1,441 @@
+/*
+ * STK1160 driver
+ *
+ * Copyright (C) 2012 Ezequiel Garcia
+ * <elezegarcia--a.t--gmail.com>
+ *
+ * Based on Easycap driver by R.M. Thomas
+ *	Copyright (C) 2010 R.M. Thomas
+ *	<rmthomas--a.t--sciolus.org>
+ *
+ * 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.
+ *
+ * TODO:
+ *
+ * 1. Support stream at lower speed: lower frame rate or lower frame size.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+
+#include <linux/usb.h>
+#include <linux/mm.h>
+#include <linux/vmalloc.h>
+#include <media/i2c/saa7115.h>
+
+#include "stk1160.h"
+#include "stk1160-reg.h"
+
+static unsigned int input;
+module_param(input, int, 0644);
+MODULE_PARM_DESC(input, "Set default input");
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Ezequiel Garcia");
+MODULE_DESCRIPTION("STK1160 driver");
+
+/* Devices supported by this driver */
+static const struct usb_device_id stk1160_id_table[] = {
+	{ USB_DEVICE(0x05e1, 0x0408) },
+	{ }
+};
+MODULE_DEVICE_TABLE(usb, stk1160_id_table);
+
+/* saa7113 I2C address */
+static unsigned short saa7113_addrs[] = {
+	0x4a >> 1,
+	I2C_CLIENT_END
+};
+
+/*
+ * Read/Write stk registers
+ */
+int stk1160_read_reg(struct stk1160 *dev, u16 reg, u8 *value)
+{
+	int ret;
+	int pipe = usb_rcvctrlpipe(dev->udev, 0);
+	u8 *buf;
+
+	*value = 0;
+
+	buf = kmalloc(sizeof(u8), GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+	ret = usb_control_msg(dev->udev, pipe, 0x00,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0x00, reg, buf, sizeof(u8), HZ);
+	if (ret < 0) {
+		stk1160_err("read failed on reg 0x%x (%d)\n",
+			reg, ret);
+		kfree(buf);
+		return ret;
+	}
+
+	*value = *buf;
+	kfree(buf);
+	return 0;
+}
+
+int stk1160_write_reg(struct stk1160 *dev, u16 reg, u16 value)
+{
+	int ret;
+	int pipe = usb_sndctrlpipe(dev->udev, 0);
+
+	ret =  usb_control_msg(dev->udev, pipe, 0x01,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			value, reg, NULL, 0, HZ);
+	if (ret < 0) {
+		stk1160_err("write failed on reg 0x%x (%d)\n",
+			reg, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+void stk1160_select_input(struct stk1160 *dev)
+{
+	int route;
+	static const u8 gctrl[] = {
+		0x98, 0x90, 0x88, 0x80, 0x98
+	};
+
+	if (dev->ctl_input == STK1160_SVIDEO_INPUT)
+		route = SAA7115_SVIDEO3;
+	else
+		route = SAA7115_COMPOSITE0;
+
+	if (dev->ctl_input < ARRAY_SIZE(gctrl)) {
+		v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_routing,
+				route, 0, 0);
+		stk1160_write_reg(dev, STK1160_GCTRL, gctrl[dev->ctl_input]);
+	}
+}
+
+/* TODO: We should break this into pieces */
+static void stk1160_reg_reset(struct stk1160 *dev)
+{
+	int i;
+
+	static const struct regval ctl[] = {
+		{STK1160_GCTRL+2, 0x0078},
+
+		{STK1160_RMCTL+1, 0x0000},
+		{STK1160_RMCTL+3, 0x0002},
+
+		{STK1160_PLLSO,   0x0010},
+		{STK1160_PLLSO+1, 0x0000},
+		{STK1160_PLLSO+2, 0x0014},
+		{STK1160_PLLSO+3, 0x000E},
+
+		{STK1160_PLLFD,   0x0046},
+
+		/* Timing generator setup */
+		{STK1160_TIGEN,   0x0012},
+		{STK1160_TICTL,   0x002D},
+		{STK1160_TICTL+1, 0x0001},
+		{STK1160_TICTL+2, 0x0000},
+		{STK1160_TICTL+3, 0x0000},
+		{STK1160_TIGEN,   0x0080},
+
+		{0xffff, 0xffff}
+	};
+
+	for (i = 0; ctl[i].reg != 0xffff; i++)
+		stk1160_write_reg(dev, ctl[i].reg, ctl[i].val);
+}
+
+static void stk1160_release(struct v4l2_device *v4l2_dev)
+{
+	struct stk1160 *dev = container_of(v4l2_dev, struct stk1160, v4l2_dev);
+
+	stk1160_dbg("releasing all resources\n");
+
+	stk1160_i2c_unregister(dev);
+
+	v4l2_ctrl_handler_free(&dev->ctrl_handler);
+	v4l2_device_unregister(&dev->v4l2_dev);
+	mutex_destroy(&dev->v4l_lock);
+	mutex_destroy(&dev->vb_queue_lock);
+	kfree(dev->alt_max_pkt_size);
+	kfree(dev);
+}
+
+/* high bandwidth multiplier, as encoded in highspeed endpoint descriptors */
+#define hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03))
+
+/*
+ * Scan usb interface and populate max_pkt_size array
+ * with information on each alternate setting.
+ * The array should be allocated by the caller.
+ */
+static int stk1160_scan_usb(struct usb_interface *intf, struct usb_device *udev,
+		unsigned int *max_pkt_size)
+{
+	int i, e, sizedescr, size, ifnum;
+	const struct usb_endpoint_descriptor *desc;
+
+	bool has_video = false, has_audio = false;
+	const char *speed;
+
+	ifnum = intf->altsetting[0].desc.bInterfaceNumber;
+
+	/* Get endpoints */
+	for (i = 0; i < intf->num_altsetting; i++) {
+
+		for (e = 0; e < intf->altsetting[i].desc.bNumEndpoints; e++) {
+
+			/* This isn't clear enough, at least to me */
+			desc = &intf->altsetting[i].endpoint[e].desc;
+			sizedescr = le16_to_cpu(desc->wMaxPacketSize);
+			size = sizedescr & 0x7ff;
+
+			if (udev->speed == USB_SPEED_HIGH)
+				size = size * hb_mult(sizedescr);
+
+			if (usb_endpoint_xfer_isoc(desc) &&
+			    usb_endpoint_dir_in(desc)) {
+				switch (desc->bEndpointAddress) {
+				case STK1160_EP_AUDIO:
+					has_audio = true;
+					break;
+				case STK1160_EP_VIDEO:
+					has_video = true;
+					max_pkt_size[i] = size;
+					break;
+				}
+			}
+		}
+	}
+
+	/* Is this even possible? */
+	if (!(has_audio || has_video)) {
+		dev_err(&udev->dev, "no audio or video endpoints found\n");
+		return -ENODEV;
+	}
+
+	switch (udev->speed) {
+	case USB_SPEED_LOW:
+		speed = "1.5";
+		break;
+	case USB_SPEED_FULL:
+		speed = "12";
+		break;
+	case USB_SPEED_HIGH:
+		speed = "480";
+		break;
+	default:
+		speed = "unknown";
+	}
+
+	dev_info(&udev->dev, "New device %s %s @ %s Mbps (%04x:%04x, interface %d, class %d)\n",
+		udev->manufacturer ? udev->manufacturer : "",
+		udev->product ? udev->product : "",
+		speed,
+		le16_to_cpu(udev->descriptor.idVendor),
+		le16_to_cpu(udev->descriptor.idProduct),
+		ifnum,
+		intf->altsetting->desc.bInterfaceNumber);
+
+	/* This should never happen, since we rejected audio interfaces */
+	if (has_audio)
+		dev_warn(&udev->dev, "audio interface %d found.\n\
+				This is not implemented by this driver,\
+				you should use snd-usb-audio instead\n", ifnum);
+
+	if (has_video)
+		dev_info(&udev->dev, "video interface %d found\n",
+				ifnum);
+
+	/*
+	 * Make sure we have 480 Mbps of bandwidth, otherwise things like
+	 * video stream wouldn't likely work, since 12 Mbps is generally
+	 * not enough even for most streams.
+	 */
+	if (udev->speed != USB_SPEED_HIGH)
+		dev_warn(&udev->dev, "must be connected to a high-speed USB 2.0 port\n\
+				You may not be able to stream video smoothly\n");
+
+	return 0;
+}
+
+static int stk1160_probe(struct usb_interface *interface,
+		const struct usb_device_id *id)
+{
+	int rc = 0;
+
+	unsigned int *alt_max_pkt_size;	/* array of wMaxPacketSize */
+	struct usb_device *udev;
+	struct stk1160 *dev;
+
+	udev = interface_to_usbdev(interface);
+
+	/*
+	 * Since usb audio class is supported by snd-usb-audio,
+	 * we reject audio interface.
+	 */
+	if (interface->altsetting[0].desc.bInterfaceClass == USB_CLASS_AUDIO)
+		return -ENODEV;
+
+	/* Alloc an array for all possible max_pkt_size */
+	alt_max_pkt_size = kmalloc_array(interface->num_altsetting,
+					 sizeof(alt_max_pkt_size[0]),
+					 GFP_KERNEL);
+	if (alt_max_pkt_size == NULL)
+		return -ENOMEM;
+
+	/*
+	 * Scan usb posibilities and populate alt_max_pkt_size array.
+	 * Also, check if device speed is fast enough.
+	 */
+	rc = stk1160_scan_usb(interface, udev, alt_max_pkt_size);
+	if (rc < 0) {
+		kfree(alt_max_pkt_size);
+		return rc;
+	}
+
+	dev = kzalloc(sizeof(struct stk1160), GFP_KERNEL);
+	if (dev == NULL) {
+		kfree(alt_max_pkt_size);
+		return -ENOMEM;
+	}
+
+	dev->alt_max_pkt_size = alt_max_pkt_size;
+	dev->udev = udev;
+	dev->num_alt = interface->num_altsetting;
+	dev->ctl_input = input;
+
+	/* We save struct device for debug purposes only */
+	dev->dev = &interface->dev;
+
+	usb_set_intfdata(interface, dev);
+
+	/* initialize videobuf2 stuff */
+	rc = stk1160_vb2_setup(dev);
+	if (rc < 0)
+		goto free_err;
+
+	/*
+	 * There is no need to take any locks here in probe
+	 * because we register the device node as the *last* thing.
+	 */
+	spin_lock_init(&dev->buf_lock);
+	mutex_init(&dev->v4l_lock);
+	mutex_init(&dev->vb_queue_lock);
+
+	rc = v4l2_ctrl_handler_init(&dev->ctrl_handler, 0);
+	if (rc) {
+		stk1160_err("v4l2_ctrl_handler_init failed (%d)\n", rc);
+		goto free_err;
+	}
+
+	/*
+	 * We obtain a v4l2_dev but defer
+	 * registration of video device node as the last thing.
+	 * There is no need to set the name if we give a device struct
+	 */
+	dev->v4l2_dev.release = stk1160_release;
+	dev->v4l2_dev.ctrl_handler = &dev->ctrl_handler;
+	rc = v4l2_device_register(dev->dev, &dev->v4l2_dev);
+	if (rc) {
+		stk1160_err("v4l2_device_register failed (%d)\n", rc);
+		goto free_ctrl;
+	}
+
+	rc = stk1160_i2c_register(dev);
+	if (rc < 0)
+		goto unreg_v4l2;
+
+	/*
+	 * To the best of my knowledge stk1160 boards only have
+	 * saa7113, but it doesn't hurt to support them all.
+	 */
+	dev->sd_saa7115 = v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap,
+		"saa7115_auto", 0, saa7113_addrs);
+
+	/* i2c reset saa711x */
+	v4l2_device_call_all(&dev->v4l2_dev, 0, core, reset, 0);
+	v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_stream, 0);
+
+	/* reset stk1160 to default values */
+	stk1160_reg_reset(dev);
+
+	/* select default input */
+	stk1160_select_input(dev);
+
+	stk1160_ac97_setup(dev);
+
+	rc = stk1160_video_register(dev);
+	if (rc < 0)
+		goto unreg_i2c;
+
+	return 0;
+
+unreg_i2c:
+	stk1160_i2c_unregister(dev);
+unreg_v4l2:
+	v4l2_device_unregister(&dev->v4l2_dev);
+free_ctrl:
+	v4l2_ctrl_handler_free(&dev->ctrl_handler);
+free_err:
+	kfree(alt_max_pkt_size);
+	kfree(dev);
+
+	return rc;
+}
+
+static void stk1160_disconnect(struct usb_interface *interface)
+{
+	struct stk1160 *dev;
+
+	dev = usb_get_intfdata(interface);
+	usb_set_intfdata(interface, NULL);
+
+	/*
+	 * Wait until all current v4l2 operation are finished
+	 * then deallocate resources
+	 */
+	mutex_lock(&dev->vb_queue_lock);
+	mutex_lock(&dev->v4l_lock);
+
+	/* Here is the only place where isoc get released */
+	stk1160_uninit_isoc(dev);
+
+	stk1160_clear_queue(dev);
+
+	video_unregister_device(&dev->vdev);
+	v4l2_device_disconnect(&dev->v4l2_dev);
+
+	/* This way current users can detect device is gone */
+	dev->udev = NULL;
+
+	mutex_unlock(&dev->v4l_lock);
+	mutex_unlock(&dev->vb_queue_lock);
+
+	/*
+	 * This calls stk1160_release if it's the last reference.
+	 * Otherwise, release is posponed until there are no users left.
+	 */
+	v4l2_device_put(&dev->v4l2_dev);
+}
+
+static struct usb_driver stk1160_usb_driver = {
+	.name = "stk1160",
+	.id_table = stk1160_id_table,
+	.probe = stk1160_probe,
+	.disconnect = stk1160_disconnect,
+};
+
+module_usb_driver(stk1160_usb_driver);
diff --git a/drivers/media/usb/stk1160/stk1160-i2c.c b/drivers/media/usb/stk1160/stk1160-i2c.c
new file mode 100644
index 0000000..62a12d5
--- /dev/null
+++ b/drivers/media/usb/stk1160/stk1160-i2c.c
@@ -0,0 +1,294 @@
+/*
+ * STK1160 driver
+ *
+ * Copyright (C) 2012 Ezequiel Garcia
+ * <elezegarcia--a.t--gmail.com>
+ *
+ * Based on Easycap driver by R.M. Thomas
+ *	Copyright (C) 2010 R.M. Thomas
+ *	<rmthomas--a.t--sciolus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/i2c.h>
+
+#include "stk1160.h"
+#include "stk1160-reg.h"
+
+static unsigned int i2c_debug;
+module_param(i2c_debug, int, 0644);
+MODULE_PARM_DESC(i2c_debug, "enable debug messages [i2c]");
+
+#define dprintk_i2c(fmt, args...)				\
+do {								\
+	if (i2c_debug)						\
+		printk(KERN_DEBUG fmt, ##args);			\
+} while (0)
+
+static int stk1160_i2c_busy_wait(struct stk1160 *dev, u8 wait_bit_mask)
+{
+	unsigned long end;
+	u8 flag;
+
+	/* Wait until read/write finish bit is set */
+	end = jiffies + msecs_to_jiffies(STK1160_I2C_TIMEOUT);
+	while (time_is_after_jiffies(end)) {
+
+		stk1160_read_reg(dev, STK1160_SICTL+1, &flag);
+		/* read/write done? */
+		if (flag & wait_bit_mask)
+			goto done;
+
+		usleep_range(10 * USEC_PER_MSEC, 20 * USEC_PER_MSEC);
+	}
+
+	return -ETIMEDOUT;
+
+done:
+	return 0;
+}
+
+static int stk1160_i2c_write_reg(struct stk1160 *dev, u8 addr,
+		u8 reg, u8 value)
+{
+	int rc;
+
+	/* Set serial device address */
+	rc = stk1160_write_reg(dev, STK1160_SICTL_SDA, addr);
+	if (rc < 0)
+		return rc;
+
+	/* Set i2c device register sub-address */
+	rc = stk1160_write_reg(dev, STK1160_SBUSW_WA, reg);
+	if (rc < 0)
+		return rc;
+
+	/* Set i2c device register value */
+	rc = stk1160_write_reg(dev, STK1160_SBUSW_WD, value);
+	if (rc < 0)
+		return rc;
+
+	/* Start write now */
+	rc = stk1160_write_reg(dev, STK1160_SICTL, 0x01);
+	if (rc < 0)
+		return rc;
+
+	rc = stk1160_i2c_busy_wait(dev, 0x04);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+static int stk1160_i2c_read_reg(struct stk1160 *dev, u8 addr,
+		u8 reg, u8 *value)
+{
+	int rc;
+
+	/* Set serial device address */
+	rc = stk1160_write_reg(dev, STK1160_SICTL_SDA, addr);
+	if (rc < 0)
+		return rc;
+
+	/* Set i2c device register sub-address */
+	rc = stk1160_write_reg(dev, STK1160_SBUSR_RA, reg);
+	if (rc < 0)
+		return rc;
+
+	/* Start read now */
+	rc = stk1160_write_reg(dev, STK1160_SICTL, 0x20);
+	if (rc < 0)
+		return rc;
+
+	rc = stk1160_i2c_busy_wait(dev, 0x01);
+	if (rc < 0)
+		return rc;
+
+	rc = stk1160_read_reg(dev, STK1160_SBUSR_RD, value);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+/*
+ * stk1160_i2c_check_for_device()
+ * check if there is a i2c_device at the supplied address
+ */
+static int stk1160_i2c_check_for_device(struct stk1160 *dev,
+		unsigned char addr)
+{
+	int rc;
+
+	/* Set serial device address */
+	rc = stk1160_write_reg(dev, STK1160_SICTL_SDA, addr);
+	if (rc < 0)
+		return rc;
+
+	/* Set device sub-address, we'll chip version reg */
+	rc = stk1160_write_reg(dev, STK1160_SBUSR_RA, 0x00);
+	if (rc < 0)
+		return rc;
+
+	/* Start read now */
+	rc = stk1160_write_reg(dev, STK1160_SICTL, 0x20);
+	if (rc < 0)
+		return rc;
+
+	rc = stk1160_i2c_busy_wait(dev, 0x01);
+	if (rc < 0)
+		return -ENODEV;
+
+	return 0;
+}
+
+/*
+ * stk1160_i2c_xfer()
+ * the main i2c transfer function
+ */
+static int stk1160_i2c_xfer(struct i2c_adapter *i2c_adap,
+			   struct i2c_msg msgs[], int num)
+{
+	struct stk1160 *dev = i2c_adap->algo_data;
+	int addr, rc, i;
+
+	for (i = 0; i < num; i++) {
+		addr = msgs[i].addr << 1;
+		dprintk_i2c("%s: addr=%x", __func__, addr);
+
+		if (!msgs[i].len) {
+			/* no len: check only for device presence */
+			rc = stk1160_i2c_check_for_device(dev, addr);
+			if (rc < 0) {
+				dprintk_i2c(" no device\n");
+				return rc;
+			}
+
+		} else if (msgs[i].flags & I2C_M_RD) {
+			/* read request without preceding register selection */
+			dprintk_i2c(" subaddr not selected");
+			rc = -EOPNOTSUPP;
+			goto err;
+
+		} else if (i + 1 < num && msgs[i].len <= 2 &&
+			   (msgs[i + 1].flags & I2C_M_RD) &&
+			   msgs[i].addr == msgs[i + 1].addr) {
+
+			if (msgs[i].len != 1 || msgs[i + 1].len != 1) {
+				dprintk_i2c(" len not supported");
+				rc = -EOPNOTSUPP;
+				goto err;
+			}
+
+			dprintk_i2c(" subaddr=%x", msgs[i].buf[0]);
+
+			rc = stk1160_i2c_read_reg(dev, addr, msgs[i].buf[0],
+				msgs[i + 1].buf);
+
+			dprintk_i2c(" read=%x", *msgs[i + 1].buf);
+
+			/* consumed two msgs, so we skip one of them */
+			i++;
+
+		} else {
+			if (msgs[i].len != 2) {
+				dprintk_i2c(" len not supported");
+				rc = -EOPNOTSUPP;
+				goto err;
+			}
+
+			dprintk_i2c(" subaddr=%x write=%x",
+				msgs[i].buf[0],  msgs[i].buf[1]);
+
+			rc = stk1160_i2c_write_reg(dev, addr, msgs[i].buf[0],
+				msgs[i].buf[1]);
+		}
+
+		if (rc < 0)
+			goto err;
+		dprintk_i2c(" OK\n");
+	}
+
+	return num;
+err:
+	dprintk_i2c(" ERROR: %d\n", rc);
+	return num;
+}
+
+/*
+ * functionality(), what da heck is this?
+ */
+static u32 functionality(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm algo = {
+	.master_xfer   = stk1160_i2c_xfer,
+	.functionality = functionality,
+};
+
+static const struct i2c_adapter adap_template = {
+	.owner = THIS_MODULE,
+	.name = "stk1160",
+	.algo = &algo,
+};
+
+static const struct i2c_client client_template = {
+	.name = "stk1160 internal",
+};
+
+/*
+ * stk1160_i2c_register()
+ * register i2c bus
+ */
+int stk1160_i2c_register(struct stk1160 *dev)
+{
+	int rc;
+
+	dev->i2c_adap = adap_template;
+	dev->i2c_adap.dev.parent = dev->dev;
+	strcpy(dev->i2c_adap.name, "stk1160");
+	dev->i2c_adap.algo_data = dev;
+
+	i2c_set_adapdata(&dev->i2c_adap, &dev->v4l2_dev);
+
+	rc = i2c_add_adapter(&dev->i2c_adap);
+	if (rc < 0) {
+		stk1160_err("cannot add i2c adapter (%d)\n", rc);
+		return rc;
+	}
+
+	dev->i2c_client = client_template;
+	dev->i2c_client.adapter = &dev->i2c_adap;
+
+	/* Set i2c clock divider device address */
+	stk1160_write_reg(dev, STK1160_SICTL_CD,  0x0f);
+
+	/* ??? */
+	stk1160_write_reg(dev, STK1160_ASIC + 3,  0x00);
+
+	return 0;
+}
+
+/*
+ * stk1160_i2c_unregister()
+ * unregister i2c_bus
+ */
+int stk1160_i2c_unregister(struct stk1160 *dev)
+{
+	i2c_del_adapter(&dev->i2c_adap);
+	return 0;
+}
diff --git a/drivers/media/usb/stk1160/stk1160-reg.h b/drivers/media/usb/stk1160/stk1160-reg.h
new file mode 100644
index 0000000..7b08a3c
--- /dev/null
+++ b/drivers/media/usb/stk1160/stk1160-reg.h
@@ -0,0 +1,137 @@
+/*
+ * STK1160 driver
+ *
+ * Copyright (C) 2012 Ezequiel Garcia
+ * <elezegarcia--a.t--gmail.com>
+ *
+ * Based on Easycap driver by R.M. Thomas
+ *	Copyright (C) 2010 R.M. Thomas
+ *	<rmthomas--a.t--sciolus.org>
+ *
+ * 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.
+ *
+ */
+
+/* GPIO Control */
+#define STK1160_GCTRL			0x000
+
+/* Remote Wakup Control */
+#define STK1160_RMCTL			0x00c
+
+/* Power-on Strapping Data */
+#define STK1160_POSVA			0x010
+#define STK1160_POSV_L			0x010
+#define STK1160_POSV_M			0x011
+#define STK1160_POSV_H			0x012
+#define  STK1160_POSV_L_ACDOUT		BIT(3)
+#define  STK1160_POSV_L_ACSYNC		BIT(2)
+
+/*
+ * Decoder Control Register:
+ * This byte controls capture start/stop
+ * with bit #7 (0x?? OR 0x80 to activate).
+ */
+#define STK1160_DCTRL			0x100
+
+/*
+ * Decimation Control Register:
+ * Byte 104: Horizontal Decimation Line Unit Count
+ * Byte 105: Vertical Decimation Line Unit Count
+ * Byte 106: Decimation Control
+ * Bit 0 - Horizontal Decimation Control
+ *   0 Horizontal decimation is disabled.
+ *   1 Horizontal decimation is enabled.
+ * Bit 1 - Decimates Half or More Column
+ *   0 Decimates less than half from original column,
+ *     send count unit (0x105) before each unit skipped.
+ *   1 Decimates half or more from original column,
+ *     skip count unit (0x105) before each unit sent.
+ * Bit 2 - Vertical Decimation Control
+ *   0 Vertical decimation is disabled.
+ *   1 Vertical decimation is enabled.
+ * Bit 3 - Vertical Greater or Equal to Half
+ *   0 Decimates less than half from original row,
+ *     send count unit (0x105) before each unit skipped.
+ *   1 Decimates half or more from original row,
+ *     skip count unit (0x105) before each unit sent.
+ * Bit 4 - Decimation Unit
+ *  0 Decimation will work with 2 rows or columns per unit.
+ *  1 Decimation will work with 4 rows or columns per unit.
+ */
+#define STK1160_DMCTRL_H_UNITS		0x104
+#define STK1160_DMCTRL_V_UNITS		0x105
+#define STK1160_DMCTRL			0x106
+#define  STK1160_H_DEC_EN		BIT(0)
+#define  STK1160_H_DEC_MODE		BIT(1)
+#define  STK1160_V_DEC_EN		BIT(2)
+#define  STK1160_V_DEC_MODE		BIT(3)
+#define  STK1160_DEC_UNIT_SIZE		BIT(4)
+
+/* Capture Frame Start Position */
+#define STK116_CFSPO			0x110
+#define STK116_CFSPO_STX_L		0x110
+#define STK116_CFSPO_STX_H		0x111
+#define STK116_CFSPO_STY_L		0x112
+#define STK116_CFSPO_STY_H		0x113
+
+/* Capture Frame End Position */
+#define STK116_CFEPO			0x114
+#define STK116_CFEPO_ENX_L		0x114
+#define STK116_CFEPO_ENX_H		0x115
+#define STK116_CFEPO_ENY_L		0x116
+#define STK116_CFEPO_ENY_H		0x117
+
+/* Serial Interface Control  */
+#define STK1160_SICTL			0x200
+#define STK1160_SICTL_CD		0x202
+#define STK1160_SICTL_SDA		0x203
+
+/* Serial Bus Write */
+#define STK1160_SBUSW			0x204
+#define STK1160_SBUSW_WA		0x204
+#define STK1160_SBUSW_WD		0x205
+
+/* Serial Bus Read */
+#define STK1160_SBUSR			0x208
+#define STK1160_SBUSR_RA		0x208
+#define STK1160_SBUSR_RD		0x209
+
+/* Alternate Serial Inteface Control */
+#define STK1160_ASIC			0x2fc
+
+/* PLL Select Options */
+#define STK1160_PLLSO			0x018
+
+/* PLL Frequency Divider */
+#define STK1160_PLLFD			0x01c
+
+/* Timing Generator */
+#define STK1160_TIGEN			0x300
+
+/* Timing Control Parameter */
+#define STK1160_TICTL			0x350
+
+/* AC97 Audio Control */
+#define STK1160_AC97CTL_0		0x500
+#define STK1160_AC97CTL_1		0x504
+#define  STK1160_AC97CTL_0_CR		BIT(1)
+#define  STK1160_AC97CTL_0_CW		BIT(2)
+
+/* Use [0:6] bits of register 0x504 to set codec command address */
+#define STK1160_AC97_ADDR		0x504
+/* Use [16:31] bits of register 0x500 to set codec command data */
+#define STK1160_AC97_CMD		0x502
+
+/* Audio I2S Interface */
+#define STK1160_I2SCTL			0x50c
+
+/* EEPROM Interface */
+#define STK1160_EEPROM_SZ		0x5f0
diff --git a/drivers/media/usb/stk1160/stk1160-v4l.c b/drivers/media/usb/stk1160/stk1160-v4l.c
new file mode 100644
index 0000000..504e413
--- /dev/null
+++ b/drivers/media/usb/stk1160/stk1160-v4l.c
@@ -0,0 +1,858 @@
+/*
+ * STK1160 driver
+ *
+ * Copyright (C) 2012 Ezequiel Garcia
+ * <elezegarcia--a.t--gmail.com>
+ *
+ * Based on Easycap driver by R.M. Thomas
+ *	Copyright (C) 2010 R.M. Thomas
+ *	<rmthomas--a.t--sciolus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+
+#include <linux/videodev2.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-fh.h>
+#include <media/v4l2-event.h>
+#include <media/videobuf2-vmalloc.h>
+
+#include <media/i2c/saa7115.h>
+
+#include "stk1160.h"
+#include "stk1160-reg.h"
+
+static bool keep_buffers;
+module_param(keep_buffers, bool, 0644);
+MODULE_PARM_DESC(keep_buffers, "don't release buffers upon stop streaming");
+
+enum stk1160_decimate_mode {
+	STK1160_DECIMATE_MORE_THAN_HALF,
+	STK1160_DECIMATE_LESS_THAN_HALF,
+};
+
+struct stk1160_decimate_ctrl {
+	bool col_en, row_en;
+	enum stk1160_decimate_mode col_mode, row_mode;
+	unsigned int col_n, row_n;
+};
+
+/* supported video standards */
+static struct stk1160_fmt format[] = {
+	{
+		.name     = "16 bpp YUY2, 4:2:2, packed",
+		.fourcc   = V4L2_PIX_FMT_UYVY,
+		.depth    = 16,
+	}
+};
+
+/*
+ * Helper to find the next divisor that results in modulo being zero.
+ * This is required to guarantee valid decimation unit counts.
+ */
+static unsigned int
+div_round_integer(unsigned int x, unsigned int y)
+{
+	for (;; y++) {
+		if (x % y == 0)
+			return x / y;
+	}
+}
+
+static void stk1160_set_std(struct stk1160 *dev)
+{
+	int i;
+
+	static struct regval std525[] = {
+
+		/* 720x480 */
+
+		/* Frame start */
+		{STK116_CFSPO_STX_L, 0x0000},
+		{STK116_CFSPO_STX_H, 0x0000},
+		{STK116_CFSPO_STY_L, 0x0003},
+		{STK116_CFSPO_STY_H, 0x0000},
+
+		/* Frame end */
+		{STK116_CFEPO_ENX_L, 0x05a0},
+		{STK116_CFEPO_ENX_H, 0x0005},
+		{STK116_CFEPO_ENY_L, 0x00f3},
+		{STK116_CFEPO_ENY_H, 0x0000},
+
+		{0xffff, 0xffff}
+	};
+
+	static struct regval std625[] = {
+
+		/* 720x576 */
+
+		/* TODO: Each line of frame has some junk at the end */
+		/* Frame start */
+		{STK116_CFSPO,   0x0000},
+		{STK116_CFSPO+1, 0x0000},
+		{STK116_CFSPO+2, 0x0001},
+		{STK116_CFSPO+3, 0x0000},
+
+		/* Frame end */
+		{STK116_CFEPO,   0x05a0},
+		{STK116_CFEPO+1, 0x0005},
+		{STK116_CFEPO+2, 0x0121},
+		{STK116_CFEPO+3, 0x0001},
+
+		{0xffff, 0xffff}
+	};
+
+	if (dev->norm & V4L2_STD_525_60) {
+		stk1160_dbg("registers to NTSC like standard\n");
+		for (i = 0; std525[i].reg != 0xffff; i++)
+			stk1160_write_reg(dev, std525[i].reg, std525[i].val);
+	} else {
+		stk1160_dbg("registers to PAL like standard\n");
+		for (i = 0; std625[i].reg != 0xffff; i++)
+			stk1160_write_reg(dev, std625[i].reg, std625[i].val);
+	}
+
+}
+
+static void stk1160_set_fmt(struct stk1160 *dev,
+			    struct stk1160_decimate_ctrl *ctrl)
+{
+	u32 val = 0;
+
+	if (ctrl) {
+		/*
+		 * Since the format is UYVY, the device must skip or send
+		 * a number of rows/columns multiple of four. This way, the
+		 * colour format is preserved. The STK1160_DEC_UNIT_SIZE bit
+		 * does exactly this.
+		 */
+		val |= STK1160_DEC_UNIT_SIZE;
+		val |= ctrl->col_en ? STK1160_H_DEC_EN : 0;
+		val |= ctrl->row_en ? STK1160_V_DEC_EN : 0;
+		val |= ctrl->col_mode ==
+			STK1160_DECIMATE_MORE_THAN_HALF ?
+			STK1160_H_DEC_MODE : 0;
+		val |= ctrl->row_mode ==
+			STK1160_DECIMATE_MORE_THAN_HALF ?
+			STK1160_V_DEC_MODE : 0;
+
+		/* Horizontal count units */
+		stk1160_write_reg(dev, STK1160_DMCTRL_H_UNITS, ctrl->col_n);
+		/* Vertical count units */
+		stk1160_write_reg(dev, STK1160_DMCTRL_V_UNITS, ctrl->row_n);
+
+		stk1160_dbg("decimate 0x%x, column units %d, row units %d\n",
+			    val, ctrl->col_n, ctrl->row_n);
+	}
+
+	/* Decimation control */
+	stk1160_write_reg(dev, STK1160_DMCTRL, val);
+}
+
+/*
+ * Set a new alternate setting.
+ * Returns true is dev->max_pkt_size has changed, false otherwise.
+ */
+static bool stk1160_set_alternate(struct stk1160 *dev)
+{
+	int i, prev_alt = dev->alt;
+	unsigned int min_pkt_size;
+	bool new_pkt_size;
+
+	/*
+	 * If we don't set right alternate,
+	 * then we will get a green screen with junk.
+	 */
+	min_pkt_size = STK1160_MIN_PKT_SIZE;
+
+	for (i = 0; i < dev->num_alt; i++) {
+		/* stop when the selected alt setting offers enough bandwidth */
+		if (dev->alt_max_pkt_size[i] >= min_pkt_size) {
+			dev->alt = i;
+			break;
+		/*
+		 * otherwise make sure that we end up with the maximum bandwidth
+		 * because the min_pkt_size equation might be wrong...
+		 */
+		} else if (dev->alt_max_pkt_size[i] >
+			   dev->alt_max_pkt_size[dev->alt])
+			dev->alt = i;
+	}
+
+	stk1160_dbg("setting alternate %d\n", dev->alt);
+
+	if (dev->alt != prev_alt) {
+		stk1160_dbg("minimum isoc packet size: %u (alt=%d)\n",
+				min_pkt_size, dev->alt);
+		stk1160_dbg("setting alt %d with wMaxPacketSize=%u\n",
+			       dev->alt, dev->alt_max_pkt_size[dev->alt]);
+		usb_set_interface(dev->udev, 0, dev->alt);
+	}
+
+	new_pkt_size = dev->max_pkt_size != dev->alt_max_pkt_size[dev->alt];
+	dev->max_pkt_size = dev->alt_max_pkt_size[dev->alt];
+
+	return new_pkt_size;
+}
+
+static int stk1160_start_streaming(struct stk1160 *dev)
+{
+	bool new_pkt_size;
+	int rc = 0;
+	int i;
+
+	/* Check device presence */
+	if (!dev->udev)
+		return -ENODEV;
+
+	if (mutex_lock_interruptible(&dev->v4l_lock))
+		return -ERESTARTSYS;
+	/*
+	 * For some reason it is mandatory to set alternate *first*
+	 * and only *then* initialize isoc urbs.
+	 * Someone please explain me why ;)
+	 */
+	new_pkt_size = stk1160_set_alternate(dev);
+
+	/*
+	 * We (re)allocate isoc urbs if:
+	 * there is no allocated isoc urbs, OR
+	 * a new dev->max_pkt_size is detected
+	 */
+	if (!dev->isoc_ctl.num_bufs || new_pkt_size) {
+		rc = stk1160_alloc_isoc(dev);
+		if (rc < 0)
+			goto out_stop_hw;
+	}
+
+	/* submit urbs and enables IRQ */
+	for (i = 0; i < dev->isoc_ctl.num_bufs; i++) {
+		rc = usb_submit_urb(dev->isoc_ctl.urb[i], GFP_KERNEL);
+		if (rc) {
+			stk1160_err("cannot submit urb[%d] (%d)\n", i, rc);
+			goto out_uninit;
+		}
+	}
+
+	/* Start saa711x */
+	v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_stream, 1);
+
+	dev->sequence = 0;
+
+	/* Start stk1160 */
+	stk1160_write_reg(dev, STK1160_DCTRL, 0xb3);
+	stk1160_write_reg(dev, STK1160_DCTRL+3, 0x00);
+
+	stk1160_dbg("streaming started\n");
+
+	mutex_unlock(&dev->v4l_lock);
+
+	return 0;
+
+out_uninit:
+	stk1160_uninit_isoc(dev);
+out_stop_hw:
+	usb_set_interface(dev->udev, 0, 0);
+	stk1160_clear_queue(dev);
+
+	mutex_unlock(&dev->v4l_lock);
+
+	return rc;
+}
+
+/* Must be called with v4l_lock hold */
+static void stk1160_stop_hw(struct stk1160 *dev)
+{
+	/* If the device is not physically present, there is nothing to do */
+	if (!dev->udev)
+		return;
+
+	/* set alternate 0 */
+	dev->alt = 0;
+	stk1160_dbg("setting alternate %d\n", dev->alt);
+	usb_set_interface(dev->udev, 0, 0);
+
+	/* Stop stk1160 */
+	stk1160_write_reg(dev, STK1160_DCTRL, 0x00);
+	stk1160_write_reg(dev, STK1160_DCTRL+3, 0x00);
+
+	/* Stop saa711x */
+	v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_stream, 0);
+}
+
+static int stk1160_stop_streaming(struct stk1160 *dev)
+{
+	if (mutex_lock_interruptible(&dev->v4l_lock))
+		return -ERESTARTSYS;
+
+	/*
+	 * Once URBs are cancelled, the URB complete handler
+	 * won't be running. This is required to safely release the
+	 * current buffer (dev->isoc_ctl.buf).
+	 */
+	stk1160_cancel_isoc(dev);
+
+	/*
+	 * It is possible to keep buffers around using a module parameter.
+	 * This is intended to avoid memory fragmentation.
+	 */
+	if (!keep_buffers)
+		stk1160_free_isoc(dev);
+
+	stk1160_stop_hw(dev);
+
+	stk1160_clear_queue(dev);
+
+	stk1160_dbg("streaming stopped\n");
+
+	mutex_unlock(&dev->v4l_lock);
+
+	return 0;
+}
+
+static const struct v4l2_file_operations stk1160_fops = {
+	.owner = THIS_MODULE,
+	.open = v4l2_fh_open,
+	.release = vb2_fop_release,
+	.read = vb2_fop_read,
+	.poll = vb2_fop_poll,
+	.mmap = vb2_fop_mmap,
+	.unlocked_ioctl = video_ioctl2,
+};
+
+/*
+ * vidioc ioctls
+ */
+static int vidioc_querycap(struct file *file,
+		void *priv, struct v4l2_capability *cap)
+{
+	struct stk1160 *dev = video_drvdata(file);
+
+	strcpy(cap->driver, "stk1160");
+	strcpy(cap->card, "stk1160");
+	usb_make_path(dev->udev, cap->bus_info, sizeof(cap->bus_info));
+	cap->device_caps =
+		V4L2_CAP_VIDEO_CAPTURE |
+		V4L2_CAP_STREAMING |
+		V4L2_CAP_READWRITE;
+	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+	return 0;
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
+		struct v4l2_fmtdesc *f)
+{
+	if (f->index != 0)
+		return -EINVAL;
+
+	strlcpy(f->description, format[f->index].name, sizeof(f->description));
+	f->pixelformat = format[f->index].fourcc;
+	return 0;
+}
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+					struct v4l2_format *f)
+{
+	struct stk1160 *dev = video_drvdata(file);
+
+	f->fmt.pix.width = dev->width;
+	f->fmt.pix.height = dev->height;
+	f->fmt.pix.field = V4L2_FIELD_INTERLACED;
+	f->fmt.pix.pixelformat = dev->fmt->fourcc;
+	f->fmt.pix.bytesperline = dev->width * 2;
+	f->fmt.pix.sizeimage = dev->height * f->fmt.pix.bytesperline;
+	f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+
+	return 0;
+}
+
+static int stk1160_try_fmt(struct stk1160 *dev, struct v4l2_format *f,
+			    struct stk1160_decimate_ctrl *ctrl)
+{
+	unsigned int width, height;
+	unsigned int base_width, base_height;
+	unsigned int col_n, row_n;
+	enum stk1160_decimate_mode col_mode, row_mode;
+	bool col_en, row_en;
+
+	base_width = 720;
+	base_height = (dev->norm & V4L2_STD_525_60) ? 480 : 576;
+
+	/* Minimum width and height is 5% the frame size */
+	width = clamp_t(unsigned int, f->fmt.pix.width,
+			base_width / 20, base_width);
+	height = clamp_t(unsigned int, f->fmt.pix.height,
+			base_height / 20, base_height);
+
+	/* Let's set default no decimation values */
+	col_n = 0;
+	row_n = 0;
+	col_en = false;
+	row_en = false;
+	f->fmt.pix.width = base_width;
+	f->fmt.pix.height = base_height;
+	row_mode = STK1160_DECIMATE_LESS_THAN_HALF;
+	col_mode = STK1160_DECIMATE_LESS_THAN_HALF;
+
+	if (width < base_width && width > base_width / 2) {
+		/*
+		 * The device will send count units for each
+		 * unit skipped. This means count unit is:
+		 *
+		 * n = width / (frame width - width)
+		 *
+		 * And the width is:
+		 *
+		 * width = (n / n + 1) * frame width
+		 */
+		col_n = div_round_integer(width, base_width - width);
+		if (col_n > 0 && col_n <= 255) {
+			col_en = true;
+			col_mode = STK1160_DECIMATE_LESS_THAN_HALF;
+			f->fmt.pix.width = (base_width * col_n) / (col_n + 1);
+		}
+
+	} else if (width <= base_width / 2) {
+
+		/*
+		 * The device will skip count units for each
+		 * unit sent. This means count is:
+		 *
+		 * n = (frame width / width) - 1
+		 *
+		 * And the width is:
+		 *
+		 * width = frame width / (n + 1)
+		 */
+		col_n = div_round_integer(base_width, width) - 1;
+		if (col_n > 0 && col_n <= 255) {
+			col_en = true;
+			col_mode = STK1160_DECIMATE_MORE_THAN_HALF;
+			f->fmt.pix.width = base_width / (col_n + 1);
+		}
+	}
+
+	if (height < base_height && height > base_height / 2) {
+		row_n = div_round_integer(height, base_height - height);
+		if (row_n > 0 && row_n <= 255) {
+			row_en = true;
+			row_mode = STK1160_DECIMATE_LESS_THAN_HALF;
+			f->fmt.pix.height = (base_height * row_n) / (row_n + 1);
+		}
+
+	} else if (height <= base_height / 2) {
+		row_n = div_round_integer(base_height, height) - 1;
+		if (row_n > 0 && row_n <= 255) {
+			row_en = true;
+			row_mode = STK1160_DECIMATE_MORE_THAN_HALF;
+			f->fmt.pix.height = base_height / (row_n + 1);
+		}
+	}
+
+	f->fmt.pix.pixelformat = dev->fmt->fourcc;
+	f->fmt.pix.field = V4L2_FIELD_INTERLACED;
+	f->fmt.pix.bytesperline = f->fmt.pix.width * 2;
+	f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
+	f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+
+	if (ctrl) {
+		ctrl->col_en = col_en;
+		ctrl->col_n = col_n;
+		ctrl->col_mode = col_mode;
+		ctrl->row_en = row_en;
+		ctrl->row_n = row_n;
+		ctrl->row_mode = row_mode;
+	}
+
+	stk1160_dbg("width %d, height %d\n",
+		    f->fmt.pix.width, f->fmt.pix.height);
+	return 0;
+}
+
+static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
+				  struct v4l2_format *f)
+{
+	struct stk1160 *dev = video_drvdata(file);
+
+	return stk1160_try_fmt(dev, f, NULL);
+}
+
+static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+					struct v4l2_format *f)
+{
+	struct stk1160 *dev = video_drvdata(file);
+	struct vb2_queue *q = &dev->vb_vidq;
+	struct stk1160_decimate_ctrl ctrl;
+	int rc;
+
+	if (vb2_is_busy(q))
+		return -EBUSY;
+
+	rc = stk1160_try_fmt(dev, f, &ctrl);
+	if (rc < 0)
+		return rc;
+	dev->width = f->fmt.pix.width;
+	dev->height = f->fmt.pix.height;
+	stk1160_set_fmt(dev, &ctrl);
+
+	return 0;
+}
+
+static int vidioc_querystd(struct file *file, void *priv, v4l2_std_id *norm)
+{
+	struct stk1160 *dev = video_drvdata(file);
+	v4l2_device_call_all(&dev->v4l2_dev, 0, video, querystd, norm);
+	return 0;
+}
+
+static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *norm)
+{
+	struct stk1160 *dev = video_drvdata(file);
+
+	*norm = dev->norm;
+	return 0;
+}
+
+static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id norm)
+{
+	struct stk1160 *dev = video_drvdata(file);
+	struct vb2_queue *q = &dev->vb_vidq;
+
+	if (dev->norm == norm)
+		return 0;
+
+	if (vb2_is_busy(q))
+		return -EBUSY;
+
+	/* Check device presence */
+	if (!dev->udev)
+		return -ENODEV;
+
+	/* We need to set this now, before we call stk1160_set_std */
+	dev->width = 720;
+	dev->height = (norm & V4L2_STD_525_60) ? 480 : 576;
+	dev->norm = norm;
+
+	stk1160_set_std(dev);
+
+	/* Calling with NULL disables frame decimation */
+	stk1160_set_fmt(dev, NULL);
+
+	v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_std,
+			dev->norm);
+
+	return 0;
+}
+
+
+static int vidioc_enum_input(struct file *file, void *priv,
+				struct v4l2_input *i)
+{
+	struct stk1160 *dev = video_drvdata(file);
+
+	if (i->index > STK1160_MAX_INPUT)
+		return -EINVAL;
+
+	/* S-Video special handling */
+	if (i->index == STK1160_SVIDEO_INPUT)
+		sprintf(i->name, "S-Video");
+	else
+		sprintf(i->name, "Composite%d", i->index);
+
+	i->type = V4L2_INPUT_TYPE_CAMERA;
+	i->std = dev->vdev.tvnorms;
+	return 0;
+}
+
+static int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
+{
+	struct stk1160 *dev = video_drvdata(file);
+	*i = dev->ctl_input;
+	return 0;
+}
+
+static int vidioc_s_input(struct file *file, void *priv, unsigned int i)
+{
+	struct stk1160 *dev = video_drvdata(file);
+
+	if (i > STK1160_MAX_INPUT)
+		return -EINVAL;
+
+	dev->ctl_input = i;
+
+	stk1160_select_input(dev);
+
+	return 0;
+}
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int vidioc_g_register(struct file *file, void *priv,
+			     struct v4l2_dbg_register *reg)
+{
+	struct stk1160 *dev = video_drvdata(file);
+	int rc;
+	u8 val;
+
+	/* Match host */
+	rc = stk1160_read_reg(dev, reg->reg, &val);
+	reg->val = val;
+	reg->size = 1;
+
+	return rc;
+}
+
+static int vidioc_s_register(struct file *file, void *priv,
+			     const struct v4l2_dbg_register *reg)
+{
+	struct stk1160 *dev = video_drvdata(file);
+
+	/* Match host */
+	return stk1160_write_reg(dev, reg->reg, reg->val);
+}
+#endif
+
+static const struct v4l2_ioctl_ops stk1160_ioctl_ops = {
+	.vidioc_querycap      = vidioc_querycap,
+	.vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap     = vidioc_g_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap   = vidioc_try_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap     = vidioc_s_fmt_vid_cap,
+	.vidioc_querystd      = vidioc_querystd,
+	.vidioc_g_std         = vidioc_g_std,
+	.vidioc_s_std         = vidioc_s_std,
+	.vidioc_enum_input    = vidioc_enum_input,
+	.vidioc_g_input       = vidioc_g_input,
+	.vidioc_s_input       = vidioc_s_input,
+
+	/* vb2 takes care of these */
+	.vidioc_reqbufs       = vb2_ioctl_reqbufs,
+	.vidioc_querybuf      = vb2_ioctl_querybuf,
+	.vidioc_qbuf          = vb2_ioctl_qbuf,
+	.vidioc_dqbuf         = vb2_ioctl_dqbuf,
+	.vidioc_streamon      = vb2_ioctl_streamon,
+	.vidioc_streamoff     = vb2_ioctl_streamoff,
+	.vidioc_expbuf        = vb2_ioctl_expbuf,
+
+	.vidioc_log_status  = v4l2_ctrl_log_status,
+	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.vidioc_g_register = vidioc_g_register,
+	.vidioc_s_register = vidioc_s_register,
+#endif
+};
+
+/********************************************************************/
+
+/*
+ * Videobuf2 operations
+ */
+static int queue_setup(struct vb2_queue *vq,
+				unsigned int *nbuffers, unsigned int *nplanes,
+				unsigned int sizes[], struct device *alloc_devs[])
+{
+	struct stk1160 *dev = vb2_get_drv_priv(vq);
+	unsigned long size;
+
+	size = dev->width * dev->height * 2;
+
+	/*
+	 * Here we can change the number of buffers being requested.
+	 * So, we set a minimum and a maximum like this:
+	 */
+	*nbuffers = clamp_t(unsigned int, *nbuffers,
+			STK1160_MIN_VIDEO_BUFFERS, STK1160_MAX_VIDEO_BUFFERS);
+
+	if (*nplanes)
+		return sizes[0] < size ? -EINVAL : 0;
+
+	/* This means a packed colorformat */
+	*nplanes = 1;
+
+	sizes[0] = size;
+
+	stk1160_dbg("%s: buffer count %d, each %ld bytes\n",
+		    __func__, *nbuffers, size);
+
+	return 0;
+}
+
+static void buffer_queue(struct vb2_buffer *vb)
+{
+	unsigned long flags;
+	struct stk1160 *dev = vb2_get_drv_priv(vb->vb2_queue);
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct stk1160_buffer *buf =
+		container_of(vbuf, struct stk1160_buffer, vb);
+
+	spin_lock_irqsave(&dev->buf_lock, flags);
+	if (!dev->udev) {
+		/*
+		 * If the device is disconnected return the buffer to userspace
+		 * directly. The next QBUF call will fail with -ENODEV.
+		 */
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+	} else {
+
+		buf->mem = vb2_plane_vaddr(vb, 0);
+		buf->length = vb2_plane_size(vb, 0);
+		buf->bytesused = 0;
+		buf->pos = 0;
+
+		/*
+		 * If buffer length is less from expected then we return
+		 * the buffer to userspace directly.
+		 */
+		if (buf->length < dev->width * dev->height * 2)
+			vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+		else
+			list_add_tail(&buf->list, &dev->avail_bufs);
+
+	}
+	spin_unlock_irqrestore(&dev->buf_lock, flags);
+}
+
+static int start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+	struct stk1160 *dev = vb2_get_drv_priv(vq);
+	return stk1160_start_streaming(dev);
+}
+
+/* abort streaming and wait for last buffer */
+static void stop_streaming(struct vb2_queue *vq)
+{
+	struct stk1160 *dev = vb2_get_drv_priv(vq);
+	stk1160_stop_streaming(dev);
+}
+
+static const struct vb2_ops stk1160_video_qops = {
+	.queue_setup		= queue_setup,
+	.buf_queue		= buffer_queue,
+	.start_streaming	= start_streaming,
+	.stop_streaming		= stop_streaming,
+	.wait_prepare		= vb2_ops_wait_prepare,
+	.wait_finish		= vb2_ops_wait_finish,
+};
+
+static const struct video_device v4l_template = {
+	.name = "stk1160",
+	.tvnorms = V4L2_STD_525_60 | V4L2_STD_625_50,
+	.fops = &stk1160_fops,
+	.ioctl_ops = &stk1160_ioctl_ops,
+	.release = video_device_release_empty,
+};
+
+/********************************************************************/
+
+/* Must be called with both v4l_lock and vb_queue_lock hold */
+void stk1160_clear_queue(struct stk1160 *dev)
+{
+	struct stk1160_buffer *buf;
+	unsigned long flags;
+
+	/* Release all active buffers */
+	spin_lock_irqsave(&dev->buf_lock, flags);
+	while (!list_empty(&dev->avail_bufs)) {
+		buf = list_first_entry(&dev->avail_bufs,
+			struct stk1160_buffer, list);
+		list_del(&buf->list);
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+		stk1160_dbg("buffer [%p/%d] aborted\n",
+			    buf, buf->vb.vb2_buf.index);
+	}
+
+	/* It's important to release the current buffer */
+	if (dev->isoc_ctl.buf) {
+		buf = dev->isoc_ctl.buf;
+		dev->isoc_ctl.buf = NULL;
+
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+		stk1160_dbg("buffer [%p/%d] aborted\n",
+			    buf, buf->vb.vb2_buf.index);
+	}
+	spin_unlock_irqrestore(&dev->buf_lock, flags);
+}
+
+int stk1160_vb2_setup(struct stk1160 *dev)
+{
+	int rc;
+	struct vb2_queue *q;
+
+	q = &dev->vb_vidq;
+	q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	q->io_modes = VB2_READ | VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
+	q->drv_priv = dev;
+	q->buf_struct_size = sizeof(struct stk1160_buffer);
+	q->ops = &stk1160_video_qops;
+	q->mem_ops = &vb2_vmalloc_memops;
+	q->lock = &dev->vb_queue_lock;
+	q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+
+	rc = vb2_queue_init(q);
+	if (rc < 0)
+		return rc;
+
+	/* initialize video dma queue */
+	INIT_LIST_HEAD(&dev->avail_bufs);
+
+	return 0;
+}
+
+int stk1160_video_register(struct stk1160 *dev)
+{
+	int rc;
+
+	/* Initialize video_device with a template structure */
+	dev->vdev = v4l_template;
+	dev->vdev.queue = &dev->vb_vidq;
+
+	/*
+	 * Provide mutexes for v4l2 core and for videobuf2 queue.
+	 * It will be used to protect *only* v4l2 ioctls.
+	 */
+	dev->vdev.lock = &dev->v4l_lock;
+
+	/* This will be used to set video_device parent */
+	dev->vdev.v4l2_dev = &dev->v4l2_dev;
+
+	/* NTSC is default */
+	dev->norm = V4L2_STD_NTSC_M;
+	dev->width = 720;
+	dev->height = 480;
+
+	/* set default format */
+	dev->fmt = &format[0];
+	stk1160_set_std(dev);
+
+	v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_std,
+			dev->norm);
+
+	video_set_drvdata(&dev->vdev, dev);
+	rc = video_register_device(&dev->vdev, VFL_TYPE_GRABBER, -1);
+	if (rc < 0) {
+		stk1160_err("video_register_device failed (%d)\n", rc);
+		return rc;
+	}
+
+	v4l2_info(&dev->v4l2_dev, "V4L2 device registered as %s\n",
+		  video_device_node_name(&dev->vdev));
+
+	return 0;
+}
diff --git a/drivers/media/usb/stk1160/stk1160-video.c b/drivers/media/usb/stk1160/stk1160-video.c
new file mode 100644
index 0000000..2811f61
--- /dev/null
+++ b/drivers/media/usb/stk1160/stk1160-video.c
@@ -0,0 +1,536 @@
+/*
+ * STK1160 driver
+ *
+ * Copyright (C) 2012 Ezequiel Garcia
+ * <elezegarcia--a.t--gmail.com>
+ *
+ * Based on Easycap driver by R.M. Thomas
+ *	Copyright (C) 2010 R.M. Thomas
+ *	<rmthomas--a.t--sciolus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/slab.h>
+#include <linux/ratelimit.h>
+
+#include "stk1160.h"
+
+static unsigned int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "enable debug messages");
+
+static inline void print_err_status(struct stk1160 *dev,
+				     int packet, int status)
+{
+	char *errmsg = "Unknown";
+
+	switch (status) {
+	case -ENOENT:
+		errmsg = "unlinked synchronously";
+		break;
+	case -ECONNRESET:
+		errmsg = "unlinked asynchronously";
+		break;
+	case -ENOSR:
+		errmsg = "Buffer error (overrun)";
+		break;
+	case -EPIPE:
+		errmsg = "Stalled (device not responding)";
+		break;
+	case -EOVERFLOW:
+		errmsg = "Babble (bad cable?)";
+		break;
+	case -EPROTO:
+		errmsg = "Bit-stuff error (bad cable?)";
+		break;
+	case -EILSEQ:
+		errmsg = "CRC/Timeout (could be anything)";
+		break;
+	case -ETIME:
+		errmsg = "Device does not respond";
+		break;
+	}
+
+	if (packet < 0)
+		printk_ratelimited(KERN_WARNING "URB status %d [%s].\n",
+				status, errmsg);
+	else
+		printk_ratelimited(KERN_INFO "URB packet %d, status %d [%s].\n",
+			       packet, status, errmsg);
+}
+
+static inline
+struct stk1160_buffer *stk1160_next_buffer(struct stk1160 *dev)
+{
+	struct stk1160_buffer *buf = NULL;
+	unsigned long flags = 0;
+
+	/* Current buffer must be NULL when this functions gets called */
+	WARN_ON(dev->isoc_ctl.buf);
+
+	spin_lock_irqsave(&dev->buf_lock, flags);
+	if (!list_empty(&dev->avail_bufs)) {
+		buf = list_first_entry(&dev->avail_bufs,
+				struct stk1160_buffer, list);
+		list_del(&buf->list);
+	}
+	spin_unlock_irqrestore(&dev->buf_lock, flags);
+
+	return buf;
+}
+
+static inline
+void stk1160_buffer_done(struct stk1160 *dev)
+{
+	struct stk1160_buffer *buf = dev->isoc_ctl.buf;
+
+	buf->vb.sequence = dev->sequence++;
+	buf->vb.field = V4L2_FIELD_INTERLACED;
+	buf->vb.vb2_buf.timestamp = ktime_get_ns();
+
+	vb2_set_plane_payload(&buf->vb.vb2_buf, 0, buf->bytesused);
+	vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
+
+	dev->isoc_ctl.buf = NULL;
+}
+
+static inline
+void stk1160_copy_video(struct stk1160 *dev, u8 *src, int len)
+{
+	int linesdone, lineoff, lencopy;
+	int bytesperline = dev->width * 2;
+	struct stk1160_buffer *buf = dev->isoc_ctl.buf;
+	u8 *dst = buf->mem;
+	int remain;
+
+	/*
+	 * TODO: These stk1160_dbg are very spammy!
+	 * We should 1) check why we are getting them
+	 * and 2) add ratelimit.
+	 *
+	 * UPDATE: One of the reasons (the only one?) for getting these
+	 * is incorrect standard (mismatch between expected and configured).
+	 * So perhaps, we could add a counter for errors. When the counter
+	 * reaches some value, we simply stop streaming.
+	 */
+
+	len -= 4;
+	src += 4;
+
+	remain = len;
+
+	linesdone = buf->pos / bytesperline;
+	lineoff = buf->pos % bytesperline; /* offset in current line */
+
+	if (!buf->odd)
+		dst += bytesperline;
+
+	/* Multiply linesdone by two, to take account of the other field */
+	dst += linesdone * bytesperline * 2 + lineoff;
+
+	/* Copy the remaining of current line */
+	if (remain < (bytesperline - lineoff))
+		lencopy = remain;
+	else
+		lencopy = bytesperline - lineoff;
+
+	/*
+	 * Check if we have enough space left in the buffer.
+	 * In that case, we force loop exit after copy.
+	 */
+	if (lencopy > buf->bytesused - buf->length) {
+		lencopy = buf->bytesused - buf->length;
+		remain = lencopy;
+	}
+
+	/* Check if the copy is done */
+	if (lencopy == 0 || remain == 0)
+		return;
+
+	/* Let the bug hunt begin! sanity checks! */
+	if (lencopy < 0) {
+		stk1160_dbg("copy skipped: negative lencopy\n");
+		return;
+	}
+
+	if ((unsigned long)dst + lencopy >
+		(unsigned long)buf->mem + buf->length) {
+		printk_ratelimited(KERN_WARNING "stk1160: buffer overflow detected\n");
+		return;
+	}
+
+	memcpy(dst, src, lencopy);
+
+	buf->bytesused += lencopy;
+	buf->pos += lencopy;
+	remain -= lencopy;
+
+	/* Copy current field line by line, interlacing with the other field */
+	while (remain > 0) {
+
+		dst += lencopy + bytesperline;
+		src += lencopy;
+
+		/* Copy one line at a time */
+		if (remain < bytesperline)
+			lencopy = remain;
+		else
+			lencopy = bytesperline;
+
+		/*
+		 * Check if we have enough space left in the buffer.
+		 * In that case, we force loop exit after copy.
+		 */
+		if (lencopy > buf->bytesused - buf->length) {
+			lencopy = buf->bytesused - buf->length;
+			remain = lencopy;
+		}
+
+		/* Check if the copy is done */
+		if (lencopy == 0 || remain == 0)
+			return;
+
+		if (lencopy < 0) {
+			printk_ratelimited(KERN_WARNING "stk1160: negative lencopy detected\n");
+			return;
+		}
+
+		if ((unsigned long)dst + lencopy >
+			(unsigned long)buf->mem + buf->length) {
+			printk_ratelimited(KERN_WARNING "stk1160: buffer overflow detected\n");
+			return;
+		}
+
+		memcpy(dst, src, lencopy);
+		remain -= lencopy;
+
+		buf->bytesused += lencopy;
+		buf->pos += lencopy;
+	}
+}
+
+/*
+ * Controls the isoc copy of each urb packet
+ */
+static void stk1160_process_isoc(struct stk1160 *dev, struct urb *urb)
+{
+	int i, len, status;
+	u8 *p;
+
+	if (!dev) {
+		stk1160_warn("%s called with null device\n", __func__);
+		return;
+	}
+
+	if (urb->status < 0) {
+		/* Print status and drop current packet (or field?) */
+		print_err_status(dev, -1, urb->status);
+		return;
+	}
+
+	for (i = 0; i < urb->number_of_packets; i++) {
+		status = urb->iso_frame_desc[i].status;
+		if (status < 0) {
+			print_err_status(dev, i, status);
+			continue;
+		}
+
+		/* Get packet actual length and pointer to data */
+		p = urb->transfer_buffer + urb->iso_frame_desc[i].offset;
+		len = urb->iso_frame_desc[i].actual_length;
+
+		/* Empty packet */
+		if (len <= 4)
+			continue;
+
+		/*
+		 * An 8-byte packet sequence means end of field.
+		 * So if we don't have any packet, we start receiving one now
+		 * and if we do have a packet, then we are done with it.
+		 *
+		 * These end of field packets are always 0xc0 or 0x80,
+		 * but not always 8-byte long so we don't check packet length.
+		 */
+		if (p[0] == 0xc0) {
+
+			/*
+			 * If first byte is 0xc0 then we received
+			 * second field, and frame has ended.
+			 */
+			if (dev->isoc_ctl.buf != NULL)
+				stk1160_buffer_done(dev);
+
+			dev->isoc_ctl.buf = stk1160_next_buffer(dev);
+			if (dev->isoc_ctl.buf == NULL)
+				return;
+		}
+
+		/*
+		 * If we don't have a buffer here, then it means we
+		 * haven't found the start mark sequence.
+		 */
+		if (dev->isoc_ctl.buf == NULL)
+			continue;
+
+		if (p[0] == 0xc0 || p[0] == 0x80) {
+
+			/* We set next packet parity and
+			 * continue to get next one
+			 */
+			dev->isoc_ctl.buf->odd = *p & 0x40;
+			dev->isoc_ctl.buf->pos = 0;
+			continue;
+		}
+
+		stk1160_copy_video(dev, p, len);
+	}
+}
+
+
+/*
+ * IRQ callback, called by URB callback
+ */
+static void stk1160_isoc_irq(struct urb *urb)
+{
+	int i, rc;
+	struct stk1160 *dev = urb->context;
+
+	switch (urb->status) {
+	case 0:
+		break;
+	case -ECONNRESET:   /* kill */
+	case -ENOENT:
+	case -ESHUTDOWN:
+		/* TODO: check uvc driver: he frees the queue here */
+		return;
+	default:
+		stk1160_err("urb error! status %d\n", urb->status);
+		return;
+	}
+
+	stk1160_process_isoc(dev, urb);
+
+	/* Reset urb buffers */
+	for (i = 0; i < urb->number_of_packets; i++) {
+		urb->iso_frame_desc[i].status = 0;
+		urb->iso_frame_desc[i].actual_length = 0;
+	}
+
+	rc = usb_submit_urb(urb, GFP_ATOMIC);
+	if (rc)
+		stk1160_err("urb re-submit failed (%d)\n", rc);
+}
+
+/*
+ * Cancel urbs
+ * This function can't be called in atomic context
+ */
+void stk1160_cancel_isoc(struct stk1160 *dev)
+{
+	int i, num_bufs = dev->isoc_ctl.num_bufs;
+
+	/*
+	 * This check is not necessary, but we add it
+	 * to avoid a spurious debug message
+	 */
+	if (!num_bufs)
+		return;
+
+	stk1160_dbg("killing %d urbs...\n", num_bufs);
+
+	for (i = 0; i < num_bufs; i++) {
+
+		/*
+		 * To kill urbs we can't be in atomic context.
+		 * We don't care for NULL pointer since
+		 * usb_kill_urb allows it.
+		 */
+		usb_kill_urb(dev->isoc_ctl.urb[i]);
+	}
+
+	stk1160_dbg("all urbs killed\n");
+}
+
+/*
+ * Releases urb and transfer buffers
+ * Obviusly, associated urb must be killed before releasing it.
+ */
+void stk1160_free_isoc(struct stk1160 *dev)
+{
+	struct urb *urb;
+	int i, num_bufs = dev->isoc_ctl.num_bufs;
+
+	stk1160_dbg("freeing %d urb buffers...\n", num_bufs);
+
+	for (i = 0; i < num_bufs; i++) {
+
+		urb = dev->isoc_ctl.urb[i];
+		if (urb) {
+
+			if (dev->isoc_ctl.transfer_buffer[i]) {
+#ifndef CONFIG_DMA_NONCOHERENT
+				usb_free_coherent(dev->udev,
+					urb->transfer_buffer_length,
+					dev->isoc_ctl.transfer_buffer[i],
+					urb->transfer_dma);
+#else
+				kfree(dev->isoc_ctl.transfer_buffer[i]);
+#endif
+			}
+			usb_free_urb(urb);
+			dev->isoc_ctl.urb[i] = NULL;
+		}
+		dev->isoc_ctl.transfer_buffer[i] = NULL;
+	}
+
+	kfree(dev->isoc_ctl.urb);
+	kfree(dev->isoc_ctl.transfer_buffer);
+
+	dev->isoc_ctl.urb = NULL;
+	dev->isoc_ctl.transfer_buffer = NULL;
+	dev->isoc_ctl.num_bufs = 0;
+
+	stk1160_dbg("all urb buffers freed\n");
+}
+
+/*
+ * Helper for cancelling and freeing urbs
+ * This function can't be called in atomic context
+ */
+void stk1160_uninit_isoc(struct stk1160 *dev)
+{
+	stk1160_cancel_isoc(dev);
+	stk1160_free_isoc(dev);
+}
+
+/*
+ * Allocate URBs
+ */
+int stk1160_alloc_isoc(struct stk1160 *dev)
+{
+	struct urb *urb;
+	int i, j, k, sb_size, max_packets, num_bufs;
+
+	/*
+	 * It may be necessary to release isoc here,
+	 * since isoc are only released on disconnection.
+	 * (see new_pkt_size flag)
+	 */
+	if (dev->isoc_ctl.num_bufs)
+		stk1160_uninit_isoc(dev);
+
+	stk1160_dbg("allocating urbs...\n");
+
+	num_bufs = STK1160_NUM_BUFS;
+	max_packets = STK1160_NUM_PACKETS;
+	sb_size = max_packets * dev->max_pkt_size;
+
+	dev->isoc_ctl.buf = NULL;
+	dev->isoc_ctl.max_pkt_size = dev->max_pkt_size;
+	dev->isoc_ctl.urb = kcalloc(num_bufs, sizeof(void *), GFP_KERNEL);
+	if (!dev->isoc_ctl.urb) {
+		stk1160_err("out of memory for urb array\n");
+		return -ENOMEM;
+	}
+
+	dev->isoc_ctl.transfer_buffer = kcalloc(num_bufs, sizeof(void *),
+						GFP_KERNEL);
+	if (!dev->isoc_ctl.transfer_buffer) {
+		stk1160_err("out of memory for usb transfers\n");
+		kfree(dev->isoc_ctl.urb);
+		return -ENOMEM;
+	}
+
+	/* allocate urbs and transfer buffers */
+	for (i = 0; i < num_bufs; i++) {
+
+		urb = usb_alloc_urb(max_packets, GFP_KERNEL);
+		if (!urb)
+			goto free_i_bufs;
+		dev->isoc_ctl.urb[i] = urb;
+
+#ifndef CONFIG_DMA_NONCOHERENT
+		dev->isoc_ctl.transfer_buffer[i] = usb_alloc_coherent(dev->udev,
+			sb_size, GFP_KERNEL, &urb->transfer_dma);
+#else
+		dev->isoc_ctl.transfer_buffer[i] = kmalloc(sb_size, GFP_KERNEL);
+#endif
+		if (!dev->isoc_ctl.transfer_buffer[i]) {
+			stk1160_err("cannot alloc %d bytes for tx[%d] buffer\n",
+				sb_size, i);
+
+			/* Not enough transfer buffers, so just give up */
+			if (i < STK1160_MIN_BUFS)
+				goto free_i_bufs;
+			goto nomore_tx_bufs;
+		}
+		memset(dev->isoc_ctl.transfer_buffer[i], 0, sb_size);
+
+		/*
+		 * FIXME: Where can I get the endpoint?
+		 */
+		urb->dev = dev->udev;
+		urb->pipe = usb_rcvisocpipe(dev->udev, STK1160_EP_VIDEO);
+		urb->transfer_buffer = dev->isoc_ctl.transfer_buffer[i];
+		urb->transfer_buffer_length = sb_size;
+		urb->complete = stk1160_isoc_irq;
+		urb->context = dev;
+		urb->interval = 1;
+		urb->start_frame = 0;
+		urb->number_of_packets = max_packets;
+#ifndef CONFIG_DMA_NONCOHERENT
+		urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
+#else
+		urb->transfer_flags = URB_ISO_ASAP;
+#endif
+
+		k = 0;
+		for (j = 0; j < max_packets; j++) {
+			urb->iso_frame_desc[j].offset = k;
+			urb->iso_frame_desc[j].length =
+					dev->isoc_ctl.max_pkt_size;
+			k += dev->isoc_ctl.max_pkt_size;
+		}
+	}
+
+	stk1160_dbg("%d urbs allocated\n", num_bufs);
+
+	/* At last we can say we have some buffers */
+	dev->isoc_ctl.num_bufs = num_bufs;
+
+	return 0;
+
+nomore_tx_bufs:
+	/*
+	 * Failed to allocate desired buffer count. However, we may have
+	 * enough to work fine, so we just free the extra urb,
+	 * store the allocated count and keep going, fingers crossed!
+	 */
+	usb_free_urb(dev->isoc_ctl.urb[i]);
+	dev->isoc_ctl.urb[i] = NULL;
+
+	stk1160_warn("%d urbs allocated. Trying to continue...\n", i - 1);
+
+	dev->isoc_ctl.num_bufs = i - 1;
+
+	return 0;
+
+free_i_bufs:
+	/* Save the allocated buffers so far, so we can properly free them */
+	dev->isoc_ctl.num_bufs = i+1;
+	stk1160_free_isoc(dev);
+	return -ENOMEM;
+}
+
diff --git a/drivers/media/usb/stk1160/stk1160.h b/drivers/media/usb/stk1160/stk1160.h
new file mode 100644
index 0000000..acd1c81
--- /dev/null
+++ b/drivers/media/usb/stk1160/stk1160.h
@@ -0,0 +1,202 @@
+/*
+ * STK1160 driver
+ *
+ * Copyright (C) 2012 Ezequiel Garcia
+ * <elezegarcia--a.t--gmail.com>
+ *
+ * Based on Easycap driver by R.M. Thomas
+ *	Copyright (C) 2010 R.M. Thomas
+ *	<rmthomas--a.t--sciolus.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/i2c.h>
+#include <sound/core.h>
+#include <sound/ac97_codec.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+
+#define STK1160_VERSION		"0.9.5"
+#define STK1160_VERSION_NUM	0x000905
+
+/* Decide on number of packets for each buffer */
+#define STK1160_NUM_PACKETS 64
+
+/* Number of buffers for isoc transfers */
+#define STK1160_NUM_BUFS 16
+#define STK1160_MIN_BUFS 1
+
+/* TODO: This endpoint address should be retrieved */
+#define STK1160_EP_VIDEO 0x82
+#define STK1160_EP_AUDIO 0x81
+
+/* Max and min video buffers */
+#define STK1160_MIN_VIDEO_BUFFERS 8
+#define STK1160_MAX_VIDEO_BUFFERS 32
+
+#define STK1160_MIN_PKT_SIZE 3072
+
+#define STK1160_MAX_INPUT 4
+#define STK1160_SVIDEO_INPUT 4
+
+#define STK1160_AC97_TIMEOUT 50
+
+#define STK1160_I2C_TIMEOUT 100
+
+/* TODO: Print helpers
+ * I could use dev_xxx, pr_xxx, v4l2_xxx or printk.
+ * However, there isn't a solid consensus on which
+ * new drivers should use.
+ *
+ */
+#ifdef DEBUG
+#define stk1160_dbg(fmt, args...) \
+	printk(KERN_DEBUG "stk1160: " fmt,  ## args)
+#else
+#define stk1160_dbg(fmt, args...)
+#endif
+
+#define stk1160_info(fmt, args...) \
+	pr_info("stk1160: " fmt, ## args)
+
+#define stk1160_warn(fmt, args...) \
+	pr_warn("stk1160: " fmt, ## args)
+
+#define stk1160_err(fmt, args...) \
+	pr_err("stk1160: " fmt, ## args)
+
+/* Buffer for one video frame */
+struct stk1160_buffer {
+	/* common v4l buffer stuff -- must be first */
+	struct vb2_v4l2_buffer vb;
+	struct list_head list;
+
+	void *mem;
+	unsigned int length;		/* buffer length */
+	unsigned int bytesused;		/* bytes written */
+	int odd;			/* current oddity */
+
+	/*
+	 * Since we interlace two fields per frame,
+	 * this is different from bytesused.
+	 */
+	unsigned int pos;		/* current pos inside buffer */
+};
+
+struct stk1160_isoc_ctl {
+	/* max packet size of isoc transaction */
+	int max_pkt_size;
+
+	/* number of allocated urbs */
+	int num_bufs;
+
+	/* urb for isoc transfers */
+	struct urb **urb;
+
+	/* transfer buffers for isoc transfer */
+	char **transfer_buffer;
+
+	/* current buffer */
+	struct stk1160_buffer *buf;
+};
+
+struct stk1160_fmt {
+	char  *name;
+	u32   fourcc;          /* v4l2 format id */
+	int   depth;
+};
+
+struct stk1160 {
+	struct v4l2_device v4l2_dev;
+	struct video_device vdev;
+	struct v4l2_ctrl_handler ctrl_handler;
+
+	struct device *dev;
+	struct usb_device *udev;
+
+	/* saa7115 subdev */
+	struct v4l2_subdev *sd_saa7115;
+
+	/* isoc control struct */
+	struct list_head avail_bufs;
+
+	/* video capture */
+	struct vb2_queue vb_vidq;
+
+	/* max packet size of isoc transaction */
+	int max_pkt_size;
+	/* array of wMaxPacketSize */
+	unsigned int *alt_max_pkt_size;
+	/* alternate */
+	int alt;
+	/* Number of alternative settings */
+	int num_alt;
+
+	struct stk1160_isoc_ctl isoc_ctl;
+
+	/* frame properties */
+	int width;		  /* current frame width */
+	int height;		  /* current frame height */
+	unsigned int ctl_input;	  /* selected input */
+	v4l2_std_id norm;	  /* current norm */
+	struct stk1160_fmt *fmt;  /* selected format */
+
+	unsigned int sequence;
+
+	/* i2c i/o */
+	struct i2c_adapter i2c_adap;
+	struct i2c_client i2c_client;
+
+	struct mutex v4l_lock;
+	struct mutex vb_queue_lock;
+	spinlock_t buf_lock;
+
+	struct file *fh_owner;	/* filehandle ownership */
+
+	/* EXPERIMENTAL */
+	struct snd_card *snd_card;
+};
+
+struct regval {
+	u16 reg;
+	u16 val;
+};
+
+/* Provided by stk1160-v4l.c */
+int stk1160_vb2_setup(struct stk1160 *dev);
+int stk1160_video_register(struct stk1160 *dev);
+void stk1160_video_unregister(struct stk1160 *dev);
+void stk1160_clear_queue(struct stk1160 *dev);
+
+/* Provided by stk1160-video.c */
+int stk1160_alloc_isoc(struct stk1160 *dev);
+void stk1160_free_isoc(struct stk1160 *dev);
+void stk1160_cancel_isoc(struct stk1160 *dev);
+void stk1160_uninit_isoc(struct stk1160 *dev);
+
+/* Provided by stk1160-i2c.c */
+int stk1160_i2c_register(struct stk1160 *dev);
+int stk1160_i2c_unregister(struct stk1160 *dev);
+
+/* Provided by stk1160-core.c */
+int stk1160_read_reg(struct stk1160 *dev, u16 reg, u8 *value);
+int stk1160_write_reg(struct stk1160 *dev, u16 reg, u16 value);
+int stk1160_write_regs_req(struct stk1160 *dev, u8 req, u16 reg,
+		char *buf, int len);
+int stk1160_read_reg_req_len(struct stk1160 *dev, u8 req, u16 reg,
+		char *buf, int len);
+void stk1160_select_input(struct stk1160 *dev);
+
+/* Provided by stk1160-ac97.c */
+void stk1160_ac97_setup(struct stk1160 *dev);
diff --git a/drivers/media/usb/stkwebcam/Kconfig b/drivers/media/usb/stkwebcam/Kconfig
new file mode 100644
index 0000000..a6a00aa
--- /dev/null
+++ b/drivers/media/usb/stkwebcam/Kconfig
@@ -0,0 +1,13 @@
+config USB_STKWEBCAM
+	tristate "USB Syntek DC1125 Camera support"
+	depends on VIDEO_V4L2
+	---help---
+	  Say Y here if you want to use this type of camera.
+	  Supported devices are typically found in some Asus laptops,
+	  with USB id 174f:a311 and 05e1:0501. Other Syntek cameras
+	  may be supported by the stk11xx driver, from which this is
+	  derived, see <http://sourceforge.net/projects/syntekdriver/>
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called stkwebcam.
+
diff --git a/drivers/media/usb/stkwebcam/Makefile b/drivers/media/usb/stkwebcam/Makefile
new file mode 100644
index 0000000..20ef8a4
--- /dev/null
+++ b/drivers/media/usb/stkwebcam/Makefile
@@ -0,0 +1,4 @@
+stkwebcam-objs	:=	stk-webcam.o stk-sensor.o
+
+obj-$(CONFIG_USB_STKWEBCAM)     += stkwebcam.o
+
diff --git a/drivers/media/usb/stkwebcam/stk-sensor.c b/drivers/media/usb/stkwebcam/stk-sensor.c
new file mode 100644
index 0000000..9a7dbef
--- /dev/null
+++ b/drivers/media/usb/stkwebcam/stk-sensor.c
@@ -0,0 +1,595 @@
+/* stk-sensor.c: Driver for ov96xx sensor (used in some Syntek webcams)
+ *
+ * Copyright 2007-2008 Jaime Velasco Juan <jsagarribay@gmail.com>
+ *
+ * Some parts derived from ov7670.c:
+ * Copyright 2006 One Laptop Per Child Association, Inc.  Written
+ * by Jonathan Corbet with substantial inspiration from Mark
+ * McClelland's ovcamchip code.
+ *
+ * Copyright 2006-7 Jonathan Corbet <corbet@lwn.net>
+ *
+ * This file may be distributed under the terms of the GNU General
+ * 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.
+ */
+
+/* Controlling the sensor via the STK1125 vendor specific control interface:
+ * The camera uses an OmniVision sensor and the stk1125 provides an
+ * SCCB(i2c)-USB bridge which let us program the sensor.
+ * In my case the sensor id is 0x9652, it can be read from sensor's register
+ * 0x0A and 0x0B as follows:
+ * - read register #R:
+ *   output #R to index 0x0208
+ *   output 0x0070 to index 0x0200
+ *   input 1 byte from index 0x0201 (some kind of status register)
+ *     until its value is 0x01
+ *   input 1 byte from index 0x0209. This is the value of #R
+ * - write value V to register #R
+ *   output #R to index 0x0204
+ *   output V to index 0x0205
+ *   output 0x0005 to index 0x0200
+ *   input 1 byte from index 0x0201 until its value becomes 0x04
+ */
+
+/* It seems the i2c bus is controlled with these registers */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include "stk-webcam.h"
+
+#define STK_IIC_BASE		(0x0200)
+#  define STK_IIC_OP		(STK_IIC_BASE)
+#    define STK_IIC_OP_TX	(0x05)
+#    define STK_IIC_OP_RX	(0x70)
+#  define STK_IIC_STAT		(STK_IIC_BASE+1)
+#    define STK_IIC_STAT_TX_OK	(0x04)
+#    define STK_IIC_STAT_RX_OK	(0x01)
+/* I don't know what does this register.
+ * when it is 0x00 or 0x01, we cannot talk to the sensor,
+ * other values work */
+#  define STK_IIC_ENABLE	(STK_IIC_BASE+2)
+#    define STK_IIC_ENABLE_NO	(0x00)
+/* This is what the driver writes in windows */
+#    define STK_IIC_ENABLE_YES	(0x1e)
+/*
+ * Address of the slave. Seems like the binary driver look for the
+ * sensor in multiple places, attempting a reset sequence.
+ * We only know about the ov9650
+ */
+#  define STK_IIC_ADDR		(STK_IIC_BASE+3)
+#  define STK_IIC_TX_INDEX	(STK_IIC_BASE+4)
+#  define STK_IIC_TX_VALUE	(STK_IIC_BASE+5)
+#  define STK_IIC_RX_INDEX	(STK_IIC_BASE+8)
+#  define STK_IIC_RX_VALUE	(STK_IIC_BASE+9)
+
+#define MAX_RETRIES		(50)
+
+#define SENSOR_ADDRESS		(0x60)
+
+/* From ov7670.c (These registers aren't fully accurate) */
+
+/* Registers */
+#define REG_GAIN	0x00	/* Gain lower 8 bits (rest in vref) */
+#define REG_BLUE	0x01	/* blue gain */
+#define REG_RED		0x02	/* red gain */
+#define REG_VREF	0x03	/* Pieces of GAIN, VSTART, VSTOP */
+#define REG_COM1	0x04	/* Control 1 */
+#define  COM1_CCIR656	  0x40  /* CCIR656 enable */
+#define  COM1_QFMT	  0x20  /* QVGA/QCIF format */
+#define  COM1_SKIP_0	  0x00  /* Do not skip any row */
+#define  COM1_SKIP_2	  0x04  /* Skip 2 rows of 4 */
+#define  COM1_SKIP_3	  0x08  /* Skip 3 rows of 4 */
+#define REG_BAVE	0x05	/* U/B Average level */
+#define REG_GbAVE	0x06	/* Y/Gb Average level */
+#define REG_AECHH	0x07	/* AEC MS 5 bits */
+#define REG_RAVE	0x08	/* V/R Average level */
+#define REG_COM2	0x09	/* Control 2 */
+#define  COM2_SSLEEP	  0x10	/* Soft sleep mode */
+#define REG_PID		0x0a	/* Product ID MSB */
+#define REG_VER		0x0b	/* Product ID LSB */
+#define REG_COM3	0x0c	/* Control 3 */
+#define  COM3_SWAP	  0x40	  /* Byte swap */
+#define  COM3_SCALEEN	  0x08	  /* Enable scaling */
+#define  COM3_DCWEN	  0x04	  /* Enable downsamp/crop/window */
+#define REG_COM4	0x0d	/* Control 4 */
+#define REG_COM5	0x0e	/* All "reserved" */
+#define REG_COM6	0x0f	/* Control 6 */
+#define REG_AECH	0x10	/* More bits of AEC value */
+#define REG_CLKRC	0x11	/* Clock control */
+#define   CLK_PLL	  0x80	  /* Enable internal PLL */
+#define   CLK_EXT	  0x40	  /* Use external clock directly */
+#define   CLK_SCALE	  0x3f	  /* Mask for internal clock scale */
+#define REG_COM7	0x12	/* Control 7 */
+#define   COM7_RESET	  0x80	  /* Register reset */
+#define   COM7_FMT_MASK	  0x38
+#define   COM7_FMT_SXGA	  0x00
+#define   COM7_FMT_VGA	  0x40
+#define	  COM7_FMT_CIF	  0x20	  /* CIF format */
+#define   COM7_FMT_QVGA	  0x10	  /* QVGA format */
+#define   COM7_FMT_QCIF	  0x08	  /* QCIF format */
+#define	  COM7_RGB	  0x04	  /* bits 0 and 2 - RGB format */
+#define	  COM7_YUV	  0x00	  /* YUV */
+#define	  COM7_BAYER	  0x01	  /* Bayer format */
+#define	  COM7_PBAYER	  0x05	  /* "Processed bayer" */
+#define REG_COM8	0x13	/* Control 8 */
+#define   COM8_FASTAEC	  0x80	  /* Enable fast AGC/AEC */
+#define   COM8_AECSTEP	  0x40	  /* Unlimited AEC step size */
+#define   COM8_BFILT	  0x20	  /* Band filter enable */
+#define   COM8_AGC	  0x04	  /* Auto gain enable */
+#define   COM8_AWB	  0x02	  /* White balance enable */
+#define   COM8_AEC	  0x01	  /* Auto exposure enable */
+#define REG_COM9	0x14	/* Control 9  - gain ceiling */
+#define REG_COM10	0x15	/* Control 10 */
+#define   COM10_HSYNC	  0x40	  /* HSYNC instead of HREF */
+#define   COM10_PCLK_HB	  0x20	  /* Suppress PCLK on horiz blank */
+#define   COM10_HREF_REV  0x08	  /* Reverse HREF */
+#define   COM10_VS_LEAD	  0x04	  /* VSYNC on clock leading edge */
+#define   COM10_VS_NEG	  0x02	  /* VSYNC negative */
+#define   COM10_HS_NEG	  0x01	  /* HSYNC negative */
+#define REG_HSTART	0x17	/* Horiz start high bits */
+#define REG_HSTOP	0x18	/* Horiz stop high bits */
+#define REG_VSTART	0x19	/* Vert start high bits */
+#define REG_VSTOP	0x1a	/* Vert stop high bits */
+#define REG_PSHFT	0x1b	/* Pixel delay after HREF */
+#define REG_MIDH	0x1c	/* Manuf. ID high */
+#define REG_MIDL	0x1d	/* Manuf. ID low */
+#define REG_MVFP	0x1e	/* Mirror / vflip */
+#define   MVFP_MIRROR	  0x20	  /* Mirror image */
+#define   MVFP_FLIP	  0x10	  /* Vertical flip */
+
+#define REG_AEW		0x24	/* AGC upper limit */
+#define REG_AEB		0x25	/* AGC lower limit */
+#define REG_VPT		0x26	/* AGC/AEC fast mode op region */
+#define REG_ADVFL	0x2d	/* Insert dummy lines (LSB) */
+#define REG_ADVFH	0x2e	/* Insert dummy lines (MSB) */
+#define REG_HSYST	0x30	/* HSYNC rising edge delay */
+#define REG_HSYEN	0x31	/* HSYNC falling edge delay */
+#define REG_HREF	0x32	/* HREF pieces */
+#define REG_TSLB	0x3a	/* lots of stuff */
+#define   TSLB_YLAST	  0x04	  /* UYVY or VYUY - see com13 */
+#define   TSLB_BYTEORD	  0x08	  /* swap bytes in 16bit mode? */
+#define REG_COM11	0x3b	/* Control 11 */
+#define   COM11_NIGHT	  0x80	  /* NIght mode enable */
+#define   COM11_NMFR	  0x60	  /* Two bit NM frame rate */
+#define   COM11_HZAUTO	  0x10	  /* Auto detect 50/60 Hz */
+#define	  COM11_50HZ	  0x08	  /* Manual 50Hz select */
+#define   COM11_EXP	  0x02
+#define REG_COM12	0x3c	/* Control 12 */
+#define   COM12_HREF	  0x80	  /* HREF always */
+#define REG_COM13	0x3d	/* Control 13 */
+#define   COM13_GAMMA	  0x80	  /* Gamma enable */
+#define	  COM13_UVSAT	  0x40	  /* UV saturation auto adjustment */
+#define	  COM13_CMATRIX	  0x10	  /* Enable color matrix for RGB or YUV */
+#define   COM13_UVSWAP	  0x01	  /* V before U - w/TSLB */
+#define REG_COM14	0x3e	/* Control 14 */
+#define   COM14_DCWEN	  0x10	  /* DCW/PCLK-scale enable */
+#define REG_EDGE	0x3f	/* Edge enhancement factor */
+#define REG_COM15	0x40	/* Control 15 */
+#define   COM15_R10F0	  0x00	  /* Data range 10 to F0 */
+#define	  COM15_R01FE	  0x80	  /*            01 to FE */
+#define   COM15_R00FF	  0xc0	  /*            00 to FF */
+#define   COM15_RGB565	  0x10	  /* RGB565 output */
+#define   COM15_RGBFIXME	  0x20	  /* FIXME  */
+#define   COM15_RGB555	  0x30	  /* RGB555 output */
+#define REG_COM16	0x41	/* Control 16 */
+#define   COM16_AWBGAIN   0x08	  /* AWB gain enable */
+#define REG_COM17	0x42	/* Control 17 */
+#define   COM17_AECWIN	  0xc0	  /* AEC window - must match COM4 */
+#define   COM17_CBAR	  0x08	  /* DSP Color bar */
+
+/*
+ * This matrix defines how the colors are generated, must be
+ * tweaked to adjust hue and saturation.
+ *
+ * Order: v-red, v-green, v-blue, u-red, u-green, u-blue
+ *
+ * They are nine-bit signed quantities, with the sign bit
+ * stored in 0x58.  Sign for v-red is bit 0, and up from there.
+ */
+#define	REG_CMATRIX_BASE 0x4f
+#define   CMATRIX_LEN 6
+#define REG_CMATRIX_SIGN 0x58
+
+
+#define REG_BRIGHT	0x55	/* Brightness */
+#define REG_CONTRAS	0x56	/* Contrast control */
+
+#define REG_GFIX	0x69	/* Fix gain control */
+
+#define REG_RGB444	0x8c	/* RGB 444 control */
+#define   R444_ENABLE	  0x02	  /* Turn on RGB444, overrides 5x5 */
+#define   R444_RGBX	  0x01	  /* Empty nibble at end */
+
+#define REG_HAECC1	0x9f	/* Hist AEC/AGC control 1 */
+#define REG_HAECC2	0xa0	/* Hist AEC/AGC control 2 */
+
+#define REG_BD50MAX	0xa5	/* 50hz banding step limit */
+#define REG_HAECC3	0xa6	/* Hist AEC/AGC control 3 */
+#define REG_HAECC4	0xa7	/* Hist AEC/AGC control 4 */
+#define REG_HAECC5	0xa8	/* Hist AEC/AGC control 5 */
+#define REG_HAECC6	0xa9	/* Hist AEC/AGC control 6 */
+#define REG_HAECC7	0xaa	/* Hist AEC/AGC control 7 */
+#define REG_BD60MAX	0xab	/* 60hz banding step limit */
+
+
+
+
+/* Returns 0 if OK */
+static int stk_sensor_outb(struct stk_camera *dev, u8 reg, u8 val)
+{
+	int i = 0;
+	u8 tmpval = 0;
+
+	if (stk_camera_write_reg(dev, STK_IIC_TX_INDEX, reg))
+		return 1;
+	if (stk_camera_write_reg(dev, STK_IIC_TX_VALUE, val))
+		return 1;
+	if (stk_camera_write_reg(dev, STK_IIC_OP, STK_IIC_OP_TX))
+		return 1;
+	do {
+		if (stk_camera_read_reg(dev, STK_IIC_STAT, &tmpval))
+			return 1;
+		i++;
+	} while (tmpval == 0 && i < MAX_RETRIES);
+	if (tmpval != STK_IIC_STAT_TX_OK) {
+		if (tmpval)
+			pr_err("stk_sensor_outb failed, status=0x%02x\n",
+			       tmpval);
+		return 1;
+	} else
+		return 0;
+}
+
+static int stk_sensor_inb(struct stk_camera *dev, u8 reg, u8 *val)
+{
+	int i = 0;
+	u8 tmpval = 0;
+
+	if (stk_camera_write_reg(dev, STK_IIC_RX_INDEX, reg))
+		return 1;
+	if (stk_camera_write_reg(dev, STK_IIC_OP, STK_IIC_OP_RX))
+		return 1;
+	do {
+		if (stk_camera_read_reg(dev, STK_IIC_STAT, &tmpval))
+			return 1;
+		i++;
+	} while (tmpval == 0 && i < MAX_RETRIES);
+	if (tmpval != STK_IIC_STAT_RX_OK) {
+		if (tmpval)
+			pr_err("stk_sensor_inb failed, status=0x%02x\n",
+			       tmpval);
+		return 1;
+	}
+
+	if (stk_camera_read_reg(dev, STK_IIC_RX_VALUE, &tmpval))
+		return 1;
+
+	*val = tmpval;
+	return 0;
+}
+
+static int stk_sensor_write_regvals(struct stk_camera *dev,
+		struct regval *rv)
+{
+	int ret;
+	if (rv == NULL)
+		return 0;
+	while (rv->reg != 0xff || rv->val != 0xff) {
+		ret = stk_sensor_outb(dev, rv->reg, rv->val);
+		if (ret != 0)
+			return ret;
+		rv++;
+	}
+	return 0;
+}
+
+int stk_sensor_sleep(struct stk_camera *dev)
+{
+	u8 tmp;
+	return stk_sensor_inb(dev, REG_COM2, &tmp)
+		|| stk_sensor_outb(dev, REG_COM2, tmp|COM2_SSLEEP);
+}
+
+int stk_sensor_wakeup(struct stk_camera *dev)
+{
+	u8 tmp;
+	return stk_sensor_inb(dev, REG_COM2, &tmp)
+		|| stk_sensor_outb(dev, REG_COM2, tmp&~COM2_SSLEEP);
+}
+
+static struct regval ov_initvals[] = {
+	{REG_CLKRC, CLK_PLL},
+	{REG_COM11, 0x01},
+	{0x6a, 0x7d},
+	{REG_AECH, 0x40},
+	{REG_GAIN, 0x00},
+	{REG_BLUE, 0x80},
+	{REG_RED, 0x80},
+	/* Do not enable fast AEC for now */
+	/*{REG_COM8, COM8_FASTAEC|COM8_AECSTEP|COM8_BFILT|COM8_AGC|COM8_AEC},*/
+	{REG_COM8, COM8_AECSTEP|COM8_BFILT|COM8_AGC|COM8_AEC},
+	{0x39, 0x50}, {0x38, 0x93},
+	{0x37, 0x00}, {0x35, 0x81},
+	{REG_COM5, 0x20},
+	{REG_COM1, 0x00},
+	{REG_COM3, 0x00},
+	{REG_COM4, 0x00},
+	{REG_PSHFT, 0x00},
+	{0x16, 0x07},
+	{0x33, 0xe2}, {0x34, 0xbf},
+	{REG_COM16, 0x00},
+	{0x96, 0x04},
+	/* Gamma curve values */
+/*	{ 0x7a, 0x20 },		{ 0x7b, 0x10 },
+	{ 0x7c, 0x1e },		{ 0x7d, 0x35 },
+	{ 0x7e, 0x5a },		{ 0x7f, 0x69 },
+	{ 0x80, 0x76 },		{ 0x81, 0x80 },
+	{ 0x82, 0x88 },		{ 0x83, 0x8f },
+	{ 0x84, 0x96 },		{ 0x85, 0xa3 },
+	{ 0x86, 0xaf },		{ 0x87, 0xc4 },
+	{ 0x88, 0xd7 },		{ 0x89, 0xe8 },
+*/
+	{REG_GFIX, 0x40},
+	{0x8e, 0x00},
+	{REG_COM12, 0x73},
+	{0x8f, 0xdf}, {0x8b, 0x06},
+	{0x8c, 0x20},
+	{0x94, 0x88}, {0x95, 0x88},
+/*	{REG_COM15, 0xc1}, TODO */
+	{0x29, 0x3f},
+	{REG_COM6, 0x42},
+	{REG_BD50MAX, 0x80},
+	{REG_HAECC6, 0xb8}, {REG_HAECC7, 0x92},
+	{REG_BD60MAX, 0x0a},
+	{0x90, 0x00}, {0x91, 0x00},
+	{REG_HAECC1, 0x00}, {REG_HAECC2, 0x00},
+	{REG_AEW, 0x68}, {REG_AEB, 0x5c},
+	{REG_VPT, 0xc3},
+	{REG_COM9, 0x2e},
+	{0x2a, 0x00}, {0x2b, 0x00},
+
+	{0xff, 0xff}, /* END MARKER */
+};
+
+/* Probe the I2C bus and initialise the sensor chip */
+int stk_sensor_init(struct stk_camera *dev)
+{
+	u8 idl = 0;
+	u8 idh = 0;
+
+	if (stk_camera_write_reg(dev, STK_IIC_ENABLE, STK_IIC_ENABLE_YES)
+		|| stk_camera_write_reg(dev, STK_IIC_ADDR, SENSOR_ADDRESS)
+		|| stk_sensor_outb(dev, REG_COM7, COM7_RESET)) {
+		pr_err("Sensor resetting failed\n");
+		return -ENODEV;
+	}
+	msleep(10);
+	/* Read the manufacturer ID: ov = 0x7FA2 */
+	if (stk_sensor_inb(dev, REG_MIDH, &idh)
+	    || stk_sensor_inb(dev, REG_MIDL, &idl)) {
+		pr_err("Strange error reading sensor ID\n");
+		return -ENODEV;
+	}
+	if (idh != 0x7f || idl != 0xa2) {
+		pr_err("Huh? you don't have a sensor from ovt\n");
+		return -ENODEV;
+	}
+	if (stk_sensor_inb(dev, REG_PID, &idh)
+	    || stk_sensor_inb(dev, REG_VER, &idl)) {
+		pr_err("Could not read sensor model\n");
+		return -ENODEV;
+	}
+	stk_sensor_write_regvals(dev, ov_initvals);
+	msleep(10);
+	pr_info("OmniVision sensor detected, id %02X%02X at address %x\n",
+		idh, idl, SENSOR_ADDRESS);
+	return 0;
+}
+
+/* V4L2_PIX_FMT_UYVY */
+static struct regval ov_fmt_uyvy[] = {
+	{REG_TSLB, TSLB_YLAST|0x08 },
+	{ 0x4f, 0x80 },		/* "matrix coefficient 1" */
+	{ 0x50, 0x80 },		/* "matrix coefficient 2" */
+	{ 0x51, 0    },		/* vb */
+	{ 0x52, 0x22 },		/* "matrix coefficient 4" */
+	{ 0x53, 0x5e },		/* "matrix coefficient 5" */
+	{ 0x54, 0x80 },		/* "matrix coefficient 6" */
+	{REG_COM13, COM13_UVSAT|COM13_CMATRIX},
+	{REG_COM15, COM15_R00FF },
+	{0xff, 0xff}, /* END MARKER */
+};
+/* V4L2_PIX_FMT_YUYV */
+static struct regval ov_fmt_yuyv[] = {
+	{REG_TSLB, 0 },
+	{ 0x4f, 0x80 },		/* "matrix coefficient 1" */
+	{ 0x50, 0x80 },		/* "matrix coefficient 2" */
+	{ 0x51, 0    },		/* vb */
+	{ 0x52, 0x22 },		/* "matrix coefficient 4" */
+	{ 0x53, 0x5e },		/* "matrix coefficient 5" */
+	{ 0x54, 0x80 },		/* "matrix coefficient 6" */
+	{REG_COM13, COM13_UVSAT|COM13_CMATRIX},
+	{REG_COM15, COM15_R00FF },
+	{0xff, 0xff}, /* END MARKER */
+};
+
+/* V4L2_PIX_FMT_RGB565X rrrrrggg gggbbbbb */
+static struct regval ov_fmt_rgbr[] = {
+	{ REG_RGB444, 0 },	/* No RGB444 please */
+	{REG_TSLB, 0x00},
+	{ REG_COM1, 0x0 },
+	{ REG_COM9, 0x38 },	/* 16x gain ceiling; 0x8 is reserved bit */
+	{ 0x4f, 0xb3 },		/* "matrix coefficient 1" */
+	{ 0x50, 0xb3 },		/* "matrix coefficient 2" */
+	{ 0x51, 0    },		/* vb */
+	{ 0x52, 0x3d },		/* "matrix coefficient 4" */
+	{ 0x53, 0xa7 },		/* "matrix coefficient 5" */
+	{ 0x54, 0xe4 },		/* "matrix coefficient 6" */
+	{ REG_COM13, COM13_GAMMA },
+	{ REG_COM15, COM15_RGB565|COM15_R00FF },
+	{ 0xff, 0xff },
+};
+
+/* V4L2_PIX_FMT_RGB565 gggbbbbb rrrrrggg */
+static struct regval ov_fmt_rgbp[] = {
+	{ REG_RGB444, 0 },	/* No RGB444 please */
+	{REG_TSLB, TSLB_BYTEORD },
+	{ REG_COM1, 0x0 },
+	{ REG_COM9, 0x38 },	/* 16x gain ceiling; 0x8 is reserved bit */
+	{ 0x4f, 0xb3 },		/* "matrix coefficient 1" */
+	{ 0x50, 0xb3 },		/* "matrix coefficient 2" */
+	{ 0x51, 0    },		/* vb */
+	{ 0x52, 0x3d },		/* "matrix coefficient 4" */
+	{ 0x53, 0xa7 },		/* "matrix coefficient 5" */
+	{ 0x54, 0xe4 },		/* "matrix coefficient 6" */
+	{ REG_COM13, COM13_GAMMA },
+	{ REG_COM15, COM15_RGB565|COM15_R00FF },
+	{ 0xff, 0xff },
+};
+
+/* V4L2_PIX_FMT_SRGGB8 */
+static struct regval ov_fmt_bayer[] = {
+	/* This changes color order */
+	{REG_TSLB, 0x40}, /* BGGR */
+	/* {REG_TSLB, 0x08}, */ /* BGGR with vertical image flipping */
+	{REG_COM15, COM15_R00FF },
+	{0xff, 0xff}, /* END MARKER */
+};
+/*
+ * Store a set of start/stop values into the camera.
+ */
+static int stk_sensor_set_hw(struct stk_camera *dev,
+		int hstart, int hstop, int vstart, int vstop)
+{
+	int ret;
+	unsigned char v;
+/*
+ * Horizontal: 11 bits, top 8 live in hstart and hstop.  Bottom 3 of
+ * hstart are in href[2:0], bottom 3 of hstop in href[5:3].  There is
+ * a mystery "edge offset" value in the top two bits of href.
+ */
+	ret =  stk_sensor_outb(dev, REG_HSTART, (hstart >> 3) & 0xff);
+	ret += stk_sensor_outb(dev, REG_HSTOP, (hstop >> 3) & 0xff);
+	ret += stk_sensor_inb(dev, REG_HREF, &v);
+	v = (v & 0xc0) | ((hstop & 0x7) << 3) | (hstart & 0x7);
+	msleep(10);
+	ret += stk_sensor_outb(dev, REG_HREF, v);
+/*
+ * Vertical: similar arrangement (note: this is different from ov7670.c)
+ */
+	ret += stk_sensor_outb(dev, REG_VSTART, (vstart >> 3) & 0xff);
+	ret += stk_sensor_outb(dev, REG_VSTOP, (vstop >> 3) & 0xff);
+	ret += stk_sensor_inb(dev, REG_VREF, &v);
+	v = (v & 0xc0) | ((vstop & 0x7) << 3) | (vstart & 0x7);
+	msleep(10);
+	ret += stk_sensor_outb(dev, REG_VREF, v);
+	return ret;
+}
+
+
+int stk_sensor_configure(struct stk_camera *dev)
+{
+	int com7;
+	/*
+	 * We setup the sensor to output dummy lines in low-res modes,
+	 * so we don't get absurdly hight framerates.
+	 */
+	unsigned dummylines;
+	int flip;
+	struct regval *rv;
+
+	switch (dev->vsettings.mode) {
+	case MODE_QCIF: com7 = COM7_FMT_QCIF;
+		dummylines = 604;
+		break;
+	case MODE_QVGA: com7 = COM7_FMT_QVGA;
+		dummylines = 267;
+		break;
+	case MODE_CIF: com7 = COM7_FMT_CIF;
+		dummylines = 412;
+		break;
+	case MODE_VGA: com7 = COM7_FMT_VGA;
+		dummylines = 11;
+		break;
+	case MODE_SXGA: com7 = COM7_FMT_SXGA;
+		dummylines = 0;
+		break;
+	default:
+		pr_err("Unsupported mode %d\n", dev->vsettings.mode);
+		return -EFAULT;
+	}
+	switch (dev->vsettings.palette) {
+	case V4L2_PIX_FMT_UYVY:
+		com7 |= COM7_YUV;
+		rv = ov_fmt_uyvy;
+		break;
+	case V4L2_PIX_FMT_YUYV:
+		com7 |= COM7_YUV;
+		rv = ov_fmt_yuyv;
+		break;
+	case V4L2_PIX_FMT_RGB565:
+		com7 |= COM7_RGB;
+		rv = ov_fmt_rgbp;
+		break;
+	case V4L2_PIX_FMT_RGB565X:
+		com7 |= COM7_RGB;
+		rv = ov_fmt_rgbr;
+		break;
+	case V4L2_PIX_FMT_SBGGR8:
+		com7 |= COM7_PBAYER;
+		rv = ov_fmt_bayer;
+		break;
+	default:
+		pr_err("Unsupported colorspace\n");
+		return -EFAULT;
+	}
+	/*FIXME sometimes the sensor go to a bad state
+	stk_sensor_write_regvals(dev, ov_initvals); */
+	stk_sensor_outb(dev, REG_COM7, com7);
+	msleep(50);
+	stk_sensor_write_regvals(dev, rv);
+	flip = (dev->vsettings.vflip?MVFP_FLIP:0)
+		| (dev->vsettings.hflip?MVFP_MIRROR:0);
+	stk_sensor_outb(dev, REG_MVFP, flip);
+	if (dev->vsettings.palette == V4L2_PIX_FMT_SBGGR8
+			&& !dev->vsettings.vflip)
+		stk_sensor_outb(dev, REG_TSLB, 0x08);
+	stk_sensor_outb(dev, REG_ADVFH, dummylines >> 8);
+	stk_sensor_outb(dev, REG_ADVFL, dummylines & 0xff);
+	msleep(50);
+	switch (dev->vsettings.mode) {
+	case MODE_VGA:
+		if (stk_sensor_set_hw(dev, 302, 1582, 6, 486))
+			pr_err("stk_sensor_set_hw failed (VGA)\n");
+		break;
+	case MODE_SXGA:
+	case MODE_CIF:
+	case MODE_QVGA:
+	case MODE_QCIF:
+		/*FIXME These settings seem ignored by the sensor
+		if (stk_sensor_set_hw(dev, 220, 1500, 10, 1034))
+			pr_err("stk_sensor_set_hw failed (SXGA)\n");
+		*/
+		break;
+	}
+	msleep(10);
+	return 0;
+}
+
+int stk_sensor_set_brightness(struct stk_camera *dev, int br)
+{
+	if (br < 0 || br > 0xff)
+		return -EINVAL;
+	stk_sensor_outb(dev, REG_AEB, max(0x00, br - 6));
+	stk_sensor_outb(dev, REG_AEW, min(0xff, br + 6));
+	return 0;
+}
+
diff --git a/drivers/media/usb/stkwebcam/stk-webcam.c b/drivers/media/usb/stkwebcam/stk-webcam.c
new file mode 100644
index 0000000..5accb52
--- /dev/null
+++ b/drivers/media/usb/stkwebcam/stk-webcam.c
@@ -0,0 +1,1435 @@
+/*
+ * stk-webcam.c : Driver for Syntek 1125 USB webcam controller
+ *
+ * Copyright (C) 2006 Nicolas VIVIEN
+ * Copyright 2007-2008 Jaime Velasco Juan <jsagarribay@gmail.com>
+ *
+ * Some parts are inspired from cafe_ccic.c
+ * Copyright 2006-2007 Jonathan Corbet
+ *
+ * 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
+ * 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.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+
+#include <linux/dmi.h>
+#include <linux/usb.h>
+#include <linux/mm.h>
+#include <linux/vmalloc.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-event.h>
+
+#include "stk-webcam.h"
+
+
+static int hflip = -1;
+module_param(hflip, int, 0444);
+MODULE_PARM_DESC(hflip, "Horizontal image flip (mirror). Defaults to 0");
+
+static int vflip = -1;
+module_param(vflip, int, 0444);
+MODULE_PARM_DESC(vflip, "Vertical image flip. Defaults to 0");
+
+static int debug;
+module_param(debug, int, 0444);
+MODULE_PARM_DESC(debug, "Debug v4l ioctls. Defaults to 0");
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Jaime Velasco Juan <jsagarribay@gmail.com> and Nicolas VIVIEN");
+MODULE_DESCRIPTION("Syntek DC1125 webcam driver");
+
+/* Some cameras have audio interfaces, we aren't interested in those */
+static const struct usb_device_id stkwebcam_table[] = {
+	{ USB_DEVICE_AND_INTERFACE_INFO(0x174f, 0xa311, 0xff, 0xff, 0xff) },
+	{ USB_DEVICE_AND_INTERFACE_INFO(0x05e1, 0x0501, 0xff, 0xff, 0xff) },
+	{ }
+};
+MODULE_DEVICE_TABLE(usb, stkwebcam_table);
+
+/*
+ * The stk webcam laptop module is mounted upside down in some laptops :(
+ *
+ * Some background information (thanks to Hans de Goede for providing this):
+ *
+ * 1) Once upon a time the stkwebcam driver was written
+ *
+ * 2) The webcam in question was used mostly in Asus laptop models, including
+ * the laptop of the original author of the driver, and in these models, in
+ * typical Asus fashion (see the long long list for uvc cams inside v4l-utils),
+ * they mounted the webcam-module the wrong way up. So the hflip and vflip
+ * module options were given a default value of 1 (the correct value for
+ * upside down mounted models)
+ *
+ * 3) Years later I got a bug report from a user with a laptop with stkwebcam,
+ * where the module was actually mounted the right way up, and thus showed
+ * upside down under Linux. So now I was facing the choice of 2 options:
+ *
+ * a) Add a not-upside-down list to stkwebcam, which overrules the default.
+ *
+ * b) Do it like all the other drivers do, and make the default right for
+ *    cams mounted the proper way and add an upside-down model list, with
+ *    models where we need to flip-by-default.
+ *
+ * Despite knowing that going b) would cause a period of pain where we were
+ * building the table I opted to go for option b), since a) is just too ugly,
+ * and worse different from how every other driver does it leading to
+ * confusion in the long run. This change was made in kernel 3.6.
+ *
+ * So for any user report about upside-down images since kernel 3.6 ask them
+ * to provide the output of 'sudo dmidecode' so the laptop can be added in
+ * the table below.
+ */
+static const struct dmi_system_id stk_upside_down_dmi_table[] = {
+	{
+		.ident = "ASUS G1",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "G1")
+		}
+	}, {
+		.ident = "ASUS F3JC",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "F3JC")
+		}
+	},
+	{
+		.ident = "T12Rg-H",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "HCL Infosystems Limited"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "T12Rg-H")
+		}
+	},
+	{}
+};
+
+
+/*
+ * Basic stuff
+ */
+int stk_camera_write_reg(struct stk_camera *dev, u16 index, u8 value)
+{
+	struct usb_device *udev = dev->udev;
+	int ret;
+
+	ret =  usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+			0x01,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			value,
+			index,
+			NULL,
+			0,
+			500);
+	if (ret < 0)
+		return ret;
+	else
+		return 0;
+}
+
+int stk_camera_read_reg(struct stk_camera *dev, u16 index, u8 *value)
+{
+	struct usb_device *udev = dev->udev;
+	unsigned char *buf;
+	int ret;
+
+	buf = kmalloc(sizeof(u8), GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+			0x00,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0x00,
+			index,
+			buf,
+			sizeof(u8),
+			500);
+	if (ret >= 0)
+		*value = *buf;
+
+	kfree(buf);
+	return ret;
+}
+
+static int stk_start_stream(struct stk_camera *dev)
+{
+	u8 value;
+	int i, ret;
+	u8 value_116, value_117;
+
+
+	if (!is_present(dev))
+		return -ENODEV;
+	if (!is_memallocd(dev) || !is_initialised(dev)) {
+		pr_err("FIXME: Buffers are not allocated\n");
+		return -EFAULT;
+	}
+	ret = usb_set_interface(dev->udev, 0, 5);
+
+	if (ret < 0)
+		pr_err("usb_set_interface failed !\n");
+	if (stk_sensor_wakeup(dev))
+		pr_err("error awaking the sensor\n");
+
+	stk_camera_read_reg(dev, 0x0116, &value_116);
+	stk_camera_read_reg(dev, 0x0117, &value_117);
+
+	stk_camera_write_reg(dev, 0x0116, 0x0000);
+	stk_camera_write_reg(dev, 0x0117, 0x0000);
+
+	stk_camera_read_reg(dev, 0x0100, &value);
+	stk_camera_write_reg(dev, 0x0100, value | 0x80);
+
+	stk_camera_write_reg(dev, 0x0116, value_116);
+	stk_camera_write_reg(dev, 0x0117, value_117);
+	for (i = 0; i < MAX_ISO_BUFS; i++) {
+		if (dev->isobufs[i].urb) {
+			ret = usb_submit_urb(dev->isobufs[i].urb, GFP_KERNEL);
+			atomic_inc(&dev->urbs_used);
+			if (ret)
+				return ret;
+		}
+	}
+	set_streaming(dev);
+	return 0;
+}
+
+static int stk_stop_stream(struct stk_camera *dev)
+{
+	u8 value;
+	int i;
+	if (is_present(dev)) {
+		stk_camera_read_reg(dev, 0x0100, &value);
+		stk_camera_write_reg(dev, 0x0100, value & ~0x80);
+		if (dev->isobufs != NULL) {
+			for (i = 0; i < MAX_ISO_BUFS; i++) {
+				if (dev->isobufs[i].urb)
+					usb_kill_urb(dev->isobufs[i].urb);
+			}
+		}
+		unset_streaming(dev);
+
+		if (usb_set_interface(dev->udev, 0, 0))
+			pr_err("usb_set_interface failed !\n");
+		if (stk_sensor_sleep(dev))
+			pr_err("error suspending the sensor\n");
+	}
+	return 0;
+}
+
+/*
+ * This seems to be the shortest init sequence we
+ * must do in order to find the sensor
+ * Bit 5 of reg. 0x0000 here is important, when reset to 0 the sensor
+ * is also reset. Maybe powers down it?
+ * Rest of values don't make a difference
+ */
+
+static struct regval stk1125_initvals[] = {
+	/*TODO: What means this sequence? */
+	{0x0000, 0x24},
+	{0x0100, 0x21},
+	{0x0002, 0x68},
+	{0x0003, 0x80},
+	{0x0005, 0x00},
+	{0x0007, 0x03},
+	{0x000d, 0x00},
+	{0x000f, 0x02},
+	{0x0300, 0x12},
+	{0x0350, 0x41},
+	{0x0351, 0x00},
+	{0x0352, 0x00},
+	{0x0353, 0x00},
+	{0x0018, 0x10},
+	{0x0019, 0x00},
+	{0x001b, 0x0e},
+	{0x001c, 0x46},
+	{0x0300, 0x80},
+	{0x001a, 0x04},
+	{0x0110, 0x00},
+	{0x0111, 0x00},
+	{0x0112, 0x00},
+	{0x0113, 0x00},
+
+	{0xffff, 0xff},
+};
+
+
+static int stk_initialise(struct stk_camera *dev)
+{
+	struct regval *rv;
+	int ret;
+	if (!is_present(dev))
+		return -ENODEV;
+	if (is_initialised(dev))
+		return 0;
+	rv = stk1125_initvals;
+	while (rv->reg != 0xffff) {
+		ret = stk_camera_write_reg(dev, rv->reg, rv->val);
+		if (ret)
+			return ret;
+		rv++;
+	}
+	if (stk_sensor_init(dev) == 0) {
+		set_initialised(dev);
+		return 0;
+	} else
+		return -1;
+}
+
+/* *********************************************** */
+/*
+ * This function is called as an URB transfert is complete (Isochronous pipe).
+ * So, the traitement is done in interrupt time, so it has be fast, not crash,
+ * and not stall. Neat.
+ */
+static void stk_isoc_handler(struct urb *urb)
+{
+	int i;
+	int ret;
+	int framelen;
+	unsigned long flags;
+
+	unsigned char *fill = NULL;
+	unsigned char *iso_buf = NULL;
+
+	struct stk_camera *dev;
+	struct stk_sio_buffer *fb;
+
+	dev = (struct stk_camera *) urb->context;
+
+	if (dev == NULL) {
+		pr_err("isoc_handler called with NULL device !\n");
+		return;
+	}
+
+	if (urb->status == -ENOENT || urb->status == -ECONNRESET
+		|| urb->status == -ESHUTDOWN) {
+		atomic_dec(&dev->urbs_used);
+		return;
+	}
+
+	spin_lock_irqsave(&dev->spinlock, flags);
+
+	if (urb->status != -EINPROGRESS && urb->status != 0) {
+		pr_err("isoc_handler: urb->status == %d\n", urb->status);
+		goto resubmit;
+	}
+
+	if (list_empty(&dev->sio_avail)) {
+		/*FIXME Stop streaming after a while */
+		pr_err_ratelimited("isoc_handler without available buffer!\n");
+		goto resubmit;
+	}
+	fb = list_first_entry(&dev->sio_avail,
+			struct stk_sio_buffer, list);
+	fill = fb->buffer + fb->v4lbuf.bytesused;
+
+	for (i = 0; i < urb->number_of_packets; i++) {
+		if (urb->iso_frame_desc[i].status != 0) {
+			if (urb->iso_frame_desc[i].status != -EXDEV)
+				pr_err("Frame %d has error %d\n",
+				       i, urb->iso_frame_desc[i].status);
+			continue;
+		}
+		framelen = urb->iso_frame_desc[i].actual_length;
+		iso_buf = urb->transfer_buffer + urb->iso_frame_desc[i].offset;
+
+		if (framelen <= 4)
+			continue; /* no data */
+
+		/*
+		 * we found something informational from there
+		 * the isoc frames have to type of headers
+		 * type1: 00 xx 00 00 or 20 xx 00 00
+		 * type2: 80 xx 00 00 00 00 00 00 or a0 xx 00 00 00 00 00 00
+		 * xx is a sequencer which has never been seen over 0x3f
+		 * imho data written down looks like bayer, i see similarities
+		 * after every 640 bytes
+		 */
+		if (*iso_buf & 0x80) {
+			framelen -= 8;
+			iso_buf += 8;
+			/* This marks a new frame */
+			if (fb->v4lbuf.bytesused != 0
+				&& fb->v4lbuf.bytesused != dev->frame_size) {
+				pr_err_ratelimited("frame %d, bytesused=%d, skipping\n",
+						   i, fb->v4lbuf.bytesused);
+				fb->v4lbuf.bytesused = 0;
+				fill = fb->buffer;
+			} else if (fb->v4lbuf.bytesused == dev->frame_size) {
+				if (list_is_singular(&dev->sio_avail)) {
+					/* Always reuse the last buffer */
+					fb->v4lbuf.bytesused = 0;
+					fill = fb->buffer;
+				} else {
+					list_move_tail(dev->sio_avail.next,
+						&dev->sio_full);
+					wake_up(&dev->wait_frame);
+					fb = list_first_entry(&dev->sio_avail,
+						struct stk_sio_buffer, list);
+					fb->v4lbuf.bytesused = 0;
+					fill = fb->buffer;
+				}
+			}
+		} else {
+			framelen -= 4;
+			iso_buf += 4;
+		}
+
+		/* Our buffer is full !!! */
+		if (framelen + fb->v4lbuf.bytesused > dev->frame_size) {
+			pr_err_ratelimited("Frame buffer overflow, lost sync\n");
+			/*FIXME Do something here? */
+			continue;
+		}
+		spin_unlock_irqrestore(&dev->spinlock, flags);
+		memcpy(fill, iso_buf, framelen);
+		spin_lock_irqsave(&dev->spinlock, flags);
+		fill += framelen;
+
+		/* New size of our buffer */
+		fb->v4lbuf.bytesused += framelen;
+	}
+
+resubmit:
+	spin_unlock_irqrestore(&dev->spinlock, flags);
+	urb->dev = dev->udev;
+	ret = usb_submit_urb(urb, GFP_ATOMIC);
+	if (ret != 0) {
+		pr_err("Error (%d) re-submitting urb in stk_isoc_handler\n",
+		       ret);
+	}
+}
+
+/* -------------------------------------------- */
+
+static int stk_prepare_iso(struct stk_camera *dev)
+{
+	void *kbuf;
+	int i, j;
+	struct urb *urb;
+	struct usb_device *udev;
+
+	if (dev == NULL)
+		return -ENXIO;
+	udev = dev->udev;
+
+	if (dev->isobufs)
+		pr_err("isobufs already allocated. Bad\n");
+	else
+		dev->isobufs = kcalloc(MAX_ISO_BUFS, sizeof(*dev->isobufs),
+				       GFP_KERNEL);
+	if (dev->isobufs == NULL) {
+		pr_err("Unable to allocate iso buffers\n");
+		return -ENOMEM;
+	}
+	for (i = 0; i < MAX_ISO_BUFS; i++) {
+		if (dev->isobufs[i].data == NULL) {
+			kbuf = kzalloc(ISO_BUFFER_SIZE, GFP_KERNEL);
+			if (kbuf == NULL) {
+				pr_err("Failed to allocate iso buffer %d\n", i);
+				goto isobufs_out;
+			}
+			dev->isobufs[i].data = kbuf;
+		} else
+			pr_err("isobuf data already allocated\n");
+		if (dev->isobufs[i].urb == NULL) {
+			urb = usb_alloc_urb(ISO_FRAMES_PER_DESC, GFP_KERNEL);
+			if (urb == NULL)
+				goto isobufs_out;
+			dev->isobufs[i].urb = urb;
+		} else {
+			pr_err("Killing URB\n");
+			usb_kill_urb(dev->isobufs[i].urb);
+			urb = dev->isobufs[i].urb;
+		}
+		urb->interval = 1;
+		urb->dev = udev;
+		urb->pipe = usb_rcvisocpipe(udev, dev->isoc_ep);
+		urb->transfer_flags = URB_ISO_ASAP;
+		urb->transfer_buffer = dev->isobufs[i].data;
+		urb->transfer_buffer_length = ISO_BUFFER_SIZE;
+		urb->complete = stk_isoc_handler;
+		urb->context = dev;
+		urb->start_frame = 0;
+		urb->number_of_packets = ISO_FRAMES_PER_DESC;
+
+		for (j = 0; j < ISO_FRAMES_PER_DESC; j++) {
+			urb->iso_frame_desc[j].offset = j * ISO_MAX_FRAME_SIZE;
+			urb->iso_frame_desc[j].length = ISO_MAX_FRAME_SIZE;
+		}
+	}
+	set_memallocd(dev);
+	return 0;
+
+isobufs_out:
+	for (i = 0; i < MAX_ISO_BUFS && dev->isobufs[i].data; i++)
+		kfree(dev->isobufs[i].data);
+	for (i = 0; i < MAX_ISO_BUFS && dev->isobufs[i].urb; i++)
+		usb_free_urb(dev->isobufs[i].urb);
+	kfree(dev->isobufs);
+	dev->isobufs = NULL;
+	return -ENOMEM;
+}
+
+static void stk_clean_iso(struct stk_camera *dev)
+{
+	int i;
+
+	if (dev == NULL || dev->isobufs == NULL)
+		return;
+
+	for (i = 0; i < MAX_ISO_BUFS; i++) {
+		struct urb *urb;
+
+		urb = dev->isobufs[i].urb;
+		if (urb) {
+			if (atomic_read(&dev->urbs_used) && is_present(dev))
+				usb_kill_urb(urb);
+			usb_free_urb(urb);
+		}
+		kfree(dev->isobufs[i].data);
+	}
+	kfree(dev->isobufs);
+	dev->isobufs = NULL;
+	unset_memallocd(dev);
+}
+
+static int stk_setup_siobuf(struct stk_camera *dev, int index)
+{
+	struct stk_sio_buffer *buf = dev->sio_bufs + index;
+	INIT_LIST_HEAD(&buf->list);
+	buf->v4lbuf.length = PAGE_ALIGN(dev->frame_size);
+	buf->buffer = vmalloc_user(buf->v4lbuf.length);
+	if (buf->buffer == NULL)
+		return -ENOMEM;
+	buf->mapcount = 0;
+	buf->dev = dev;
+	buf->v4lbuf.index = index;
+	buf->v4lbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	buf->v4lbuf.flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	buf->v4lbuf.field = V4L2_FIELD_NONE;
+	buf->v4lbuf.memory = V4L2_MEMORY_MMAP;
+	buf->v4lbuf.m.offset = 2*index*buf->v4lbuf.length;
+	return 0;
+}
+
+static int stk_free_sio_buffers(struct stk_camera *dev)
+{
+	int i;
+	int nbufs;
+	unsigned long flags;
+	if (dev->n_sbufs == 0 || dev->sio_bufs == NULL)
+		return 0;
+	/*
+	* If any buffers are mapped, we cannot free them at all.
+	*/
+	for (i = 0; i < dev->n_sbufs; i++) {
+		if (dev->sio_bufs[i].mapcount > 0)
+			return -EBUSY;
+	}
+	/*
+	* OK, let's do it.
+	*/
+	spin_lock_irqsave(&dev->spinlock, flags);
+	INIT_LIST_HEAD(&dev->sio_avail);
+	INIT_LIST_HEAD(&dev->sio_full);
+	nbufs = dev->n_sbufs;
+	dev->n_sbufs = 0;
+	spin_unlock_irqrestore(&dev->spinlock, flags);
+	for (i = 0; i < nbufs; i++)
+		vfree(dev->sio_bufs[i].buffer);
+	kfree(dev->sio_bufs);
+	dev->sio_bufs = NULL;
+	return 0;
+}
+
+static int stk_prepare_sio_buffers(struct stk_camera *dev, unsigned n_sbufs)
+{
+	int i;
+	if (dev->sio_bufs != NULL)
+		pr_err("sio_bufs already allocated\n");
+	else {
+		dev->sio_bufs = kcalloc(n_sbufs,
+					sizeof(struct stk_sio_buffer),
+					GFP_KERNEL);
+		if (dev->sio_bufs == NULL)
+			return -ENOMEM;
+		for (i = 0; i < n_sbufs; i++) {
+			if (stk_setup_siobuf(dev, i))
+				return (dev->n_sbufs > 1 ? 0 : -ENOMEM);
+			dev->n_sbufs = i+1;
+		}
+	}
+	return 0;
+}
+
+static int stk_allocate_buffers(struct stk_camera *dev, unsigned n_sbufs)
+{
+	int err;
+	err = stk_prepare_iso(dev);
+	if (err) {
+		stk_clean_iso(dev);
+		return err;
+	}
+	err = stk_prepare_sio_buffers(dev, n_sbufs);
+	if (err) {
+		stk_free_sio_buffers(dev);
+		return err;
+	}
+	return 0;
+}
+
+static void stk_free_buffers(struct stk_camera *dev)
+{
+	stk_clean_iso(dev);
+	stk_free_sio_buffers(dev);
+}
+/* -------------------------------------------- */
+
+/* v4l file operations */
+
+static int v4l_stk_open(struct file *fp)
+{
+	struct stk_camera *dev = video_drvdata(fp);
+	int err;
+
+	if (dev == NULL || !is_present(dev))
+		return -ENXIO;
+
+	if (mutex_lock_interruptible(&dev->lock))
+		return -ERESTARTSYS;
+	if (!dev->first_init)
+		stk_camera_write_reg(dev, 0x0, 0x24);
+	else
+		dev->first_init = 0;
+
+	err = v4l2_fh_open(fp);
+	if (!err)
+		usb_autopm_get_interface(dev->interface);
+	mutex_unlock(&dev->lock);
+	return err;
+}
+
+static int v4l_stk_release(struct file *fp)
+{
+	struct stk_camera *dev = video_drvdata(fp);
+
+	mutex_lock(&dev->lock);
+	if (dev->owner == fp) {
+		stk_stop_stream(dev);
+		stk_free_buffers(dev);
+		stk_camera_write_reg(dev, 0x0, 0x49); /* turn off the LED */
+		unset_initialised(dev);
+		dev->owner = NULL;
+	}
+
+	if (is_present(dev))
+		usb_autopm_put_interface(dev->interface);
+	mutex_unlock(&dev->lock);
+	return v4l2_fh_release(fp);
+}
+
+static ssize_t stk_read(struct file *fp, char __user *buf,
+		size_t count, loff_t *f_pos)
+{
+	int i;
+	int ret;
+	unsigned long flags;
+	struct stk_sio_buffer *sbuf;
+	struct stk_camera *dev = video_drvdata(fp);
+
+	if (!is_present(dev))
+		return -EIO;
+	if (dev->owner && (!dev->reading || dev->owner != fp))
+		return -EBUSY;
+	dev->owner = fp;
+	if (!is_streaming(dev)) {
+		if (stk_initialise(dev)
+			|| stk_allocate_buffers(dev, 3)
+			|| stk_start_stream(dev))
+			return -ENOMEM;
+		dev->reading = 1;
+		spin_lock_irqsave(&dev->spinlock, flags);
+		for (i = 0; i < dev->n_sbufs; i++) {
+			list_add_tail(&dev->sio_bufs[i].list, &dev->sio_avail);
+			dev->sio_bufs[i].v4lbuf.flags = V4L2_BUF_FLAG_QUEUED;
+		}
+		spin_unlock_irqrestore(&dev->spinlock, flags);
+	}
+	if (*f_pos == 0) {
+		if (fp->f_flags & O_NONBLOCK && list_empty(&dev->sio_full))
+			return -EWOULDBLOCK;
+		ret = wait_event_interruptible(dev->wait_frame,
+			!list_empty(&dev->sio_full) || !is_present(dev));
+		if (ret)
+			return ret;
+		if (!is_present(dev))
+			return -EIO;
+	}
+	if (count + *f_pos > dev->frame_size)
+		count = dev->frame_size - *f_pos;
+	spin_lock_irqsave(&dev->spinlock, flags);
+	if (list_empty(&dev->sio_full)) {
+		spin_unlock_irqrestore(&dev->spinlock, flags);
+		pr_err("BUG: No siobufs ready\n");
+		return 0;
+	}
+	sbuf = list_first_entry(&dev->sio_full, struct stk_sio_buffer, list);
+	spin_unlock_irqrestore(&dev->spinlock, flags);
+
+	if (copy_to_user(buf, sbuf->buffer + *f_pos, count))
+		return -EFAULT;
+
+	*f_pos += count;
+
+	if (*f_pos >= dev->frame_size) {
+		*f_pos = 0;
+		spin_lock_irqsave(&dev->spinlock, flags);
+		list_move_tail(&sbuf->list, &dev->sio_avail);
+		spin_unlock_irqrestore(&dev->spinlock, flags);
+	}
+	return count;
+}
+
+static ssize_t v4l_stk_read(struct file *fp, char __user *buf,
+		size_t count, loff_t *f_pos)
+{
+	struct stk_camera *dev = video_drvdata(fp);
+	int ret;
+
+	if (mutex_lock_interruptible(&dev->lock))
+		return -ERESTARTSYS;
+	ret = stk_read(fp, buf, count, f_pos);
+	mutex_unlock(&dev->lock);
+	return ret;
+}
+
+static __poll_t v4l_stk_poll(struct file *fp, poll_table *wait)
+{
+	struct stk_camera *dev = video_drvdata(fp);
+	__poll_t res = v4l2_ctrl_poll(fp, wait);
+
+	poll_wait(fp, &dev->wait_frame, wait);
+
+	if (!is_present(dev))
+		return EPOLLERR;
+
+	if (!list_empty(&dev->sio_full))
+		return res | EPOLLIN | EPOLLRDNORM;
+
+	return res;
+}
+
+
+static void stk_v4l_vm_open(struct vm_area_struct *vma)
+{
+	struct stk_sio_buffer *sbuf = vma->vm_private_data;
+	sbuf->mapcount++;
+}
+static void stk_v4l_vm_close(struct vm_area_struct *vma)
+{
+	struct stk_sio_buffer *sbuf = vma->vm_private_data;
+	sbuf->mapcount--;
+	if (sbuf->mapcount == 0)
+		sbuf->v4lbuf.flags &= ~V4L2_BUF_FLAG_MAPPED;
+}
+static const struct vm_operations_struct stk_v4l_vm_ops = {
+	.open = stk_v4l_vm_open,
+	.close = stk_v4l_vm_close
+};
+
+static int v4l_stk_mmap(struct file *fp, struct vm_area_struct *vma)
+{
+	unsigned int i;
+	int ret;
+	unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
+	struct stk_camera *dev = video_drvdata(fp);
+	struct stk_sio_buffer *sbuf = NULL;
+
+	if (!(vma->vm_flags & VM_WRITE) || !(vma->vm_flags & VM_SHARED))
+		return -EINVAL;
+
+	for (i = 0; i < dev->n_sbufs; i++) {
+		if (dev->sio_bufs[i].v4lbuf.m.offset == offset) {
+			sbuf = dev->sio_bufs + i;
+			break;
+		}
+	}
+	if (sbuf == NULL)
+		return -EINVAL;
+	ret = remap_vmalloc_range(vma, sbuf->buffer, 0);
+	if (ret)
+		return ret;
+	vma->vm_flags |= VM_DONTEXPAND;
+	vma->vm_private_data = sbuf;
+	vma->vm_ops = &stk_v4l_vm_ops;
+	sbuf->v4lbuf.flags |= V4L2_BUF_FLAG_MAPPED;
+	stk_v4l_vm_open(vma);
+	return 0;
+}
+
+/* v4l ioctl handlers */
+
+static int stk_vidioc_querycap(struct file *filp,
+		void *priv, struct v4l2_capability *cap)
+{
+	struct stk_camera *dev = video_drvdata(filp);
+
+	strcpy(cap->driver, "stk");
+	strcpy(cap->card, "stk");
+	usb_make_path(dev->udev, cap->bus_info, sizeof(cap->bus_info));
+
+	cap->device_caps = V4L2_CAP_VIDEO_CAPTURE
+		| V4L2_CAP_READWRITE | V4L2_CAP_STREAMING;
+	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+	return 0;
+}
+
+static int stk_vidioc_enum_input(struct file *filp,
+		void *priv, struct v4l2_input *input)
+{
+	if (input->index != 0)
+		return -EINVAL;
+
+	strcpy(input->name, "Syntek USB Camera");
+	input->type = V4L2_INPUT_TYPE_CAMERA;
+	return 0;
+}
+
+
+static int stk_vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
+{
+	*i = 0;
+	return 0;
+}
+
+static int stk_vidioc_s_input(struct file *filp, void *priv, unsigned int i)
+{
+	return i ? -EINVAL : 0;
+}
+
+static int stk_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct stk_camera *dev =
+		container_of(ctrl->handler, struct stk_camera, hdl);
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		return stk_sensor_set_brightness(dev, ctrl->val);
+	case V4L2_CID_HFLIP:
+		if (dmi_check_system(stk_upside_down_dmi_table))
+			dev->vsettings.hflip = !ctrl->val;
+		else
+			dev->vsettings.hflip = ctrl->val;
+		return 0;
+	case V4L2_CID_VFLIP:
+		if (dmi_check_system(stk_upside_down_dmi_table))
+			dev->vsettings.vflip = !ctrl->val;
+		else
+			dev->vsettings.vflip = ctrl->val;
+		return 0;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+
+static int stk_vidioc_enum_fmt_vid_cap(struct file *filp,
+		void *priv, struct v4l2_fmtdesc *fmtd)
+{
+	switch (fmtd->index) {
+	case 0:
+		fmtd->pixelformat = V4L2_PIX_FMT_RGB565;
+		strcpy(fmtd->description, "r5g6b5");
+		break;
+	case 1:
+		fmtd->pixelformat = V4L2_PIX_FMT_RGB565X;
+		strcpy(fmtd->description, "r5g6b5BE");
+		break;
+	case 2:
+		fmtd->pixelformat = V4L2_PIX_FMT_UYVY;
+		strcpy(fmtd->description, "yuv4:2:2");
+		break;
+	case 3:
+		fmtd->pixelformat = V4L2_PIX_FMT_SBGGR8;
+		strcpy(fmtd->description, "Raw bayer");
+		break;
+	case 4:
+		fmtd->pixelformat = V4L2_PIX_FMT_YUYV;
+		strcpy(fmtd->description, "yuv4:2:2");
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static struct stk_size {
+	unsigned w;
+	unsigned h;
+	enum stk_mode m;
+} stk_sizes[] = {
+	{ .w = 1280, .h = 1024, .m = MODE_SXGA, },
+	{ .w = 640,  .h = 480,  .m = MODE_VGA,  },
+	{ .w = 352,  .h = 288,  .m = MODE_CIF,  },
+	{ .w = 320,  .h = 240,  .m = MODE_QVGA, },
+	{ .w = 176,  .h = 144,  .m = MODE_QCIF, },
+};
+
+static int stk_vidioc_g_fmt_vid_cap(struct file *filp,
+		void *priv, struct v4l2_format *f)
+{
+	struct v4l2_pix_format *pix_format = &f->fmt.pix;
+	struct stk_camera *dev = video_drvdata(filp);
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(stk_sizes) &&
+			stk_sizes[i].m != dev->vsettings.mode; i++)
+		;
+	if (i == ARRAY_SIZE(stk_sizes)) {
+		pr_err("ERROR: mode invalid\n");
+		return -EINVAL;
+	}
+	pix_format->width = stk_sizes[i].w;
+	pix_format->height = stk_sizes[i].h;
+	pix_format->field = V4L2_FIELD_NONE;
+	pix_format->colorspace = V4L2_COLORSPACE_SRGB;
+	pix_format->pixelformat = dev->vsettings.palette;
+	if (dev->vsettings.palette == V4L2_PIX_FMT_SBGGR8)
+		pix_format->bytesperline = pix_format->width;
+	else
+		pix_format->bytesperline = 2 * pix_format->width;
+	pix_format->sizeimage = pix_format->bytesperline
+				* pix_format->height;
+	return 0;
+}
+
+static int stk_try_fmt_vid_cap(struct file *filp,
+		struct v4l2_format *fmtd, int *idx)
+{
+	int i;
+	switch (fmtd->fmt.pix.pixelformat) {
+	case V4L2_PIX_FMT_RGB565:
+	case V4L2_PIX_FMT_RGB565X:
+	case V4L2_PIX_FMT_UYVY:
+	case V4L2_PIX_FMT_YUYV:
+	case V4L2_PIX_FMT_SBGGR8:
+		break;
+	default:
+		return -EINVAL;
+	}
+	for (i = 1; i < ARRAY_SIZE(stk_sizes); i++) {
+		if (fmtd->fmt.pix.width > stk_sizes[i].w)
+			break;
+	}
+	if (i == ARRAY_SIZE(stk_sizes)
+		|| (abs(fmtd->fmt.pix.width - stk_sizes[i-1].w)
+			< abs(fmtd->fmt.pix.width - stk_sizes[i].w))) {
+		fmtd->fmt.pix.height = stk_sizes[i-1].h;
+		fmtd->fmt.pix.width = stk_sizes[i-1].w;
+		if (idx)
+			*idx = i - 1;
+	} else {
+		fmtd->fmt.pix.height = stk_sizes[i].h;
+		fmtd->fmt.pix.width = stk_sizes[i].w;
+		if (idx)
+			*idx = i;
+	}
+
+	fmtd->fmt.pix.field = V4L2_FIELD_NONE;
+	fmtd->fmt.pix.colorspace = V4L2_COLORSPACE_SRGB;
+	if (fmtd->fmt.pix.pixelformat == V4L2_PIX_FMT_SBGGR8)
+		fmtd->fmt.pix.bytesperline = fmtd->fmt.pix.width;
+	else
+		fmtd->fmt.pix.bytesperline = 2 * fmtd->fmt.pix.width;
+	fmtd->fmt.pix.sizeimage = fmtd->fmt.pix.bytesperline
+		* fmtd->fmt.pix.height;
+	return 0;
+}
+
+static int stk_vidioc_try_fmt_vid_cap(struct file *filp,
+		void *priv, struct v4l2_format *fmtd)
+{
+	return stk_try_fmt_vid_cap(filp, fmtd, NULL);
+}
+
+static int stk_setup_format(struct stk_camera *dev)
+{
+	int i = 0;
+	int depth;
+	if (dev->vsettings.palette == V4L2_PIX_FMT_SBGGR8)
+		depth = 1;
+	else
+		depth = 2;
+	while (i < ARRAY_SIZE(stk_sizes) &&
+			stk_sizes[i].m != dev->vsettings.mode)
+		i++;
+	if (i == ARRAY_SIZE(stk_sizes)) {
+		pr_err("Something is broken in %s\n", __func__);
+		return -EFAULT;
+	}
+	/* This registers controls some timings, not sure of what. */
+	stk_camera_write_reg(dev, 0x001b, 0x0e);
+	if (dev->vsettings.mode == MODE_SXGA)
+		stk_camera_write_reg(dev, 0x001c, 0x0e);
+	else
+		stk_camera_write_reg(dev, 0x001c, 0x46);
+	/*
+	 * Registers 0x0115 0x0114 are the size of each line (bytes),
+	 * regs 0x0117 0x0116 are the heigth of the image.
+	 */
+	stk_camera_write_reg(dev, 0x0115,
+		((stk_sizes[i].w * depth) >> 8) & 0xff);
+	stk_camera_write_reg(dev, 0x0114,
+		(stk_sizes[i].w * depth) & 0xff);
+	stk_camera_write_reg(dev, 0x0117,
+		(stk_sizes[i].h >> 8) & 0xff);
+	stk_camera_write_reg(dev, 0x0116,
+		stk_sizes[i].h & 0xff);
+	return stk_sensor_configure(dev);
+}
+
+static int stk_vidioc_s_fmt_vid_cap(struct file *filp,
+		void *priv, struct v4l2_format *fmtd)
+{
+	int ret;
+	int idx;
+	struct stk_camera *dev = video_drvdata(filp);
+
+	if (dev == NULL)
+		return -ENODEV;
+	if (!is_present(dev))
+		return -ENODEV;
+	if (is_streaming(dev))
+		return -EBUSY;
+	if (dev->owner)
+		return -EBUSY;
+	ret = stk_try_fmt_vid_cap(filp, fmtd, &idx);
+	if (ret)
+		return ret;
+
+	dev->vsettings.palette = fmtd->fmt.pix.pixelformat;
+	stk_free_buffers(dev);
+	dev->frame_size = fmtd->fmt.pix.sizeimage;
+	dev->vsettings.mode = stk_sizes[idx].m;
+
+	stk_initialise(dev);
+	return stk_setup_format(dev);
+}
+
+static int stk_vidioc_reqbufs(struct file *filp,
+		void *priv, struct v4l2_requestbuffers *rb)
+{
+	struct stk_camera *dev = video_drvdata(filp);
+
+	if (dev == NULL)
+		return -ENODEV;
+	if (rb->memory != V4L2_MEMORY_MMAP)
+		return -EINVAL;
+	if (is_streaming(dev)
+		|| (dev->owner && dev->owner != filp))
+		return -EBUSY;
+	stk_free_buffers(dev);
+	if (rb->count == 0) {
+		stk_camera_write_reg(dev, 0x0, 0x49); /* turn off the LED */
+		unset_initialised(dev);
+		dev->owner = NULL;
+		return 0;
+	}
+	dev->owner = filp;
+
+	/*FIXME If they ask for zero, we must stop streaming and free */
+	if (rb->count < 3)
+		rb->count = 3;
+	/* Arbitrary limit */
+	else if (rb->count > 5)
+		rb->count = 5;
+
+	stk_allocate_buffers(dev, rb->count);
+	rb->count = dev->n_sbufs;
+	return 0;
+}
+
+static int stk_vidioc_querybuf(struct file *filp,
+		void *priv, struct v4l2_buffer *buf)
+{
+	struct stk_camera *dev = video_drvdata(filp);
+	struct stk_sio_buffer *sbuf;
+
+	if (buf->index >= dev->n_sbufs)
+		return -EINVAL;
+	sbuf = dev->sio_bufs + buf->index;
+	*buf = sbuf->v4lbuf;
+	return 0;
+}
+
+static int stk_vidioc_qbuf(struct file *filp,
+		void *priv, struct v4l2_buffer *buf)
+{
+	struct stk_camera *dev = video_drvdata(filp);
+	struct stk_sio_buffer *sbuf;
+	unsigned long flags;
+
+	if (buf->memory != V4L2_MEMORY_MMAP)
+		return -EINVAL;
+
+	if (buf->index >= dev->n_sbufs)
+		return -EINVAL;
+	sbuf = dev->sio_bufs + buf->index;
+	if (sbuf->v4lbuf.flags & V4L2_BUF_FLAG_QUEUED)
+		return 0;
+	sbuf->v4lbuf.flags |= V4L2_BUF_FLAG_QUEUED;
+	sbuf->v4lbuf.flags &= ~V4L2_BUF_FLAG_DONE;
+	spin_lock_irqsave(&dev->spinlock, flags);
+	list_add_tail(&sbuf->list, &dev->sio_avail);
+	*buf = sbuf->v4lbuf;
+	spin_unlock_irqrestore(&dev->spinlock, flags);
+	return 0;
+}
+
+static int stk_vidioc_dqbuf(struct file *filp,
+		void *priv, struct v4l2_buffer *buf)
+{
+	struct stk_camera *dev = video_drvdata(filp);
+	struct stk_sio_buffer *sbuf;
+	unsigned long flags;
+	int ret;
+
+	if (!is_streaming(dev))
+		return -EINVAL;
+
+	if (filp->f_flags & O_NONBLOCK && list_empty(&dev->sio_full))
+		return -EWOULDBLOCK;
+	ret = wait_event_interruptible(dev->wait_frame,
+		!list_empty(&dev->sio_full) || !is_present(dev));
+	if (ret)
+		return ret;
+	if (!is_present(dev))
+		return -EIO;
+
+	spin_lock_irqsave(&dev->spinlock, flags);
+	sbuf = list_first_entry(&dev->sio_full, struct stk_sio_buffer, list);
+	list_del_init(&sbuf->list);
+	spin_unlock_irqrestore(&dev->spinlock, flags);
+	sbuf->v4lbuf.flags &= ~V4L2_BUF_FLAG_QUEUED;
+	sbuf->v4lbuf.flags |= V4L2_BUF_FLAG_DONE;
+	sbuf->v4lbuf.sequence = ++dev->sequence;
+	v4l2_get_timestamp(&sbuf->v4lbuf.timestamp);
+
+	*buf = sbuf->v4lbuf;
+	return 0;
+}
+
+static int stk_vidioc_streamon(struct file *filp,
+		void *priv, enum v4l2_buf_type type)
+{
+	struct stk_camera *dev = video_drvdata(filp);
+	if (is_streaming(dev))
+		return 0;
+	if (dev->sio_bufs == NULL)
+		return -EINVAL;
+	dev->sequence = 0;
+	return stk_start_stream(dev);
+}
+
+static int stk_vidioc_streamoff(struct file *filp,
+		void *priv, enum v4l2_buf_type type)
+{
+	struct stk_camera *dev = video_drvdata(filp);
+	unsigned long flags;
+	int i;
+	stk_stop_stream(dev);
+	spin_lock_irqsave(&dev->spinlock, flags);
+	INIT_LIST_HEAD(&dev->sio_avail);
+	INIT_LIST_HEAD(&dev->sio_full);
+	for (i = 0; i < dev->n_sbufs; i++) {
+		INIT_LIST_HEAD(&dev->sio_bufs[i].list);
+		dev->sio_bufs[i].v4lbuf.flags = 0;
+	}
+	spin_unlock_irqrestore(&dev->spinlock, flags);
+	return 0;
+}
+
+
+static int stk_vidioc_g_parm(struct file *filp,
+		void *priv, struct v4l2_streamparm *sp)
+{
+	/*FIXME This is not correct */
+	sp->parm.capture.timeperframe.numerator = 1;
+	sp->parm.capture.timeperframe.denominator = 30;
+	sp->parm.capture.readbuffers = 2;
+	return 0;
+}
+
+static int stk_vidioc_enum_framesizes(struct file *filp,
+		void *priv, struct v4l2_frmsizeenum *frms)
+{
+	if (frms->index >= ARRAY_SIZE(stk_sizes))
+		return -EINVAL;
+	switch (frms->pixel_format) {
+	case V4L2_PIX_FMT_RGB565:
+	case V4L2_PIX_FMT_RGB565X:
+	case V4L2_PIX_FMT_UYVY:
+	case V4L2_PIX_FMT_YUYV:
+	case V4L2_PIX_FMT_SBGGR8:
+		frms->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+		frms->discrete.width = stk_sizes[frms->index].w;
+		frms->discrete.height = stk_sizes[frms->index].h;
+		return 0;
+	default: return -EINVAL;
+	}
+}
+
+static const struct v4l2_ctrl_ops stk_ctrl_ops = {
+	.s_ctrl = stk_s_ctrl,
+};
+
+static const struct v4l2_file_operations v4l_stk_fops = {
+	.owner = THIS_MODULE,
+	.open = v4l_stk_open,
+	.release = v4l_stk_release,
+	.read = v4l_stk_read,
+	.poll = v4l_stk_poll,
+	.mmap = v4l_stk_mmap,
+	.unlocked_ioctl = video_ioctl2,
+};
+
+static const struct v4l2_ioctl_ops v4l_stk_ioctl_ops = {
+	.vidioc_querycap = stk_vidioc_querycap,
+	.vidioc_enum_fmt_vid_cap = stk_vidioc_enum_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap = stk_vidioc_try_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap = stk_vidioc_s_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap = stk_vidioc_g_fmt_vid_cap,
+	.vidioc_enum_input = stk_vidioc_enum_input,
+	.vidioc_s_input = stk_vidioc_s_input,
+	.vidioc_g_input = stk_vidioc_g_input,
+	.vidioc_reqbufs = stk_vidioc_reqbufs,
+	.vidioc_querybuf = stk_vidioc_querybuf,
+	.vidioc_qbuf = stk_vidioc_qbuf,
+	.vidioc_dqbuf = stk_vidioc_dqbuf,
+	.vidioc_streamon = stk_vidioc_streamon,
+	.vidioc_streamoff = stk_vidioc_streamoff,
+	.vidioc_g_parm = stk_vidioc_g_parm,
+	.vidioc_enum_framesizes = stk_vidioc_enum_framesizes,
+	.vidioc_log_status = v4l2_ctrl_log_status,
+	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static void stk_v4l_dev_release(struct video_device *vd)
+{
+	struct stk_camera *dev = vdev_to_camera(vd);
+
+	if (dev->sio_bufs != NULL || dev->isobufs != NULL)
+		pr_err("We are leaking memory\n");
+	usb_put_intf(dev->interface);
+}
+
+static const struct video_device stk_v4l_data = {
+	.name = "stkwebcam",
+	.fops = &v4l_stk_fops,
+	.ioctl_ops = &v4l_stk_ioctl_ops,
+	.release = stk_v4l_dev_release,
+};
+
+
+static int stk_register_video_device(struct stk_camera *dev)
+{
+	int err;
+
+	dev->vdev = stk_v4l_data;
+	dev->vdev.lock = &dev->lock;
+	dev->vdev.v4l2_dev = &dev->v4l2_dev;
+	video_set_drvdata(&dev->vdev, dev);
+	err = video_register_device(&dev->vdev, VFL_TYPE_GRABBER, -1);
+	if (err)
+		pr_err("v4l registration failed\n");
+	else
+		pr_info("Syntek USB2.0 Camera is now controlling device %s\n",
+			video_device_node_name(&dev->vdev));
+	return err;
+}
+
+
+/* USB Stuff */
+
+static int stk_camera_probe(struct usb_interface *interface,
+		const struct usb_device_id *id)
+{
+	struct v4l2_ctrl_handler *hdl;
+	int err = 0;
+	int i;
+
+	struct stk_camera *dev = NULL;
+	struct usb_device *udev = interface_to_usbdev(interface);
+	struct usb_host_interface *iface_desc;
+	struct usb_endpoint_descriptor *endpoint;
+
+	dev = kzalloc(sizeof(struct stk_camera), GFP_KERNEL);
+	if (dev == NULL) {
+		pr_err("Out of memory !\n");
+		return -ENOMEM;
+	}
+	err = v4l2_device_register(&interface->dev, &dev->v4l2_dev);
+	if (err < 0) {
+		dev_err(&udev->dev, "couldn't register v4l2_device\n");
+		kfree(dev);
+		return err;
+	}
+	hdl = &dev->hdl;
+	v4l2_ctrl_handler_init(hdl, 3);
+	v4l2_ctrl_new_std(hdl, &stk_ctrl_ops,
+			  V4L2_CID_BRIGHTNESS, 0, 0xff, 0x1, 0x60);
+	v4l2_ctrl_new_std(hdl, &stk_ctrl_ops,
+			  V4L2_CID_HFLIP, 0, 1, 1, 1);
+	v4l2_ctrl_new_std(hdl, &stk_ctrl_ops,
+			  V4L2_CID_VFLIP, 0, 1, 1, 1);
+	if (hdl->error) {
+		err = hdl->error;
+		dev_err(&udev->dev, "couldn't register control\n");
+		goto error;
+	}
+	dev->v4l2_dev.ctrl_handler = hdl;
+
+	spin_lock_init(&dev->spinlock);
+	mutex_init(&dev->lock);
+	init_waitqueue_head(&dev->wait_frame);
+	dev->first_init = 1; /* webcam LED management */
+
+	dev->udev = udev;
+	dev->interface = interface;
+	usb_get_intf(interface);
+
+	if (hflip != -1)
+		dev->vsettings.hflip = hflip;
+	else if (dmi_check_system(stk_upside_down_dmi_table))
+		dev->vsettings.hflip = 1;
+	else
+		dev->vsettings.hflip = 0;
+	if (vflip != -1)
+		dev->vsettings.vflip = vflip;
+	else if (dmi_check_system(stk_upside_down_dmi_table))
+		dev->vsettings.vflip = 1;
+	else
+		dev->vsettings.vflip = 0;
+	dev->n_sbufs = 0;
+	set_present(dev);
+
+	/* Set up the endpoint information
+	 * use only the first isoc-in endpoint
+	 * for the current alternate setting */
+	iface_desc = interface->cur_altsetting;
+
+	for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
+		endpoint = &iface_desc->endpoint[i].desc;
+
+		if (!dev->isoc_ep
+			&& usb_endpoint_is_isoc_in(endpoint)) {
+			/* we found an isoc in endpoint */
+			dev->isoc_ep = usb_endpoint_num(endpoint);
+			break;
+		}
+	}
+	if (!dev->isoc_ep) {
+		pr_err("Could not find isoc-in endpoint\n");
+		err = -ENODEV;
+		goto error;
+	}
+	dev->vsettings.palette = V4L2_PIX_FMT_RGB565;
+	dev->vsettings.mode = MODE_VGA;
+	dev->frame_size = 640 * 480 * 2;
+
+	INIT_LIST_HEAD(&dev->sio_avail);
+	INIT_LIST_HEAD(&dev->sio_full);
+
+	usb_set_intfdata(interface, dev);
+
+	err = stk_register_video_device(dev);
+	if (err)
+		goto error;
+
+	return 0;
+
+error:
+	v4l2_ctrl_handler_free(hdl);
+	v4l2_device_unregister(&dev->v4l2_dev);
+	kfree(dev);
+	return err;
+}
+
+static void stk_camera_disconnect(struct usb_interface *interface)
+{
+	struct stk_camera *dev = usb_get_intfdata(interface);
+
+	usb_set_intfdata(interface, NULL);
+	unset_present(dev);
+
+	wake_up_interruptible(&dev->wait_frame);
+
+	pr_info("Syntek USB2.0 Camera release resources device %s\n",
+		video_device_node_name(&dev->vdev));
+
+	video_unregister_device(&dev->vdev);
+	v4l2_ctrl_handler_free(&dev->hdl);
+	v4l2_device_unregister(&dev->v4l2_dev);
+	kfree(dev);
+}
+
+#ifdef CONFIG_PM
+static int stk_camera_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	struct stk_camera *dev = usb_get_intfdata(intf);
+	if (is_streaming(dev)) {
+		stk_stop_stream(dev);
+		/* yes, this is ugly */
+		set_streaming(dev);
+	}
+	return 0;
+}
+
+static int stk_camera_resume(struct usb_interface *intf)
+{
+	struct stk_camera *dev = usb_get_intfdata(intf);
+	if (!is_initialised(dev))
+		return 0;
+	unset_initialised(dev);
+	stk_initialise(dev);
+	stk_camera_write_reg(dev, 0x0, 0x49);
+	stk_setup_format(dev);
+	if (is_streaming(dev))
+		stk_start_stream(dev);
+	return 0;
+}
+#endif
+
+static struct usb_driver stk_camera_driver = {
+	.name = "stkwebcam",
+	.probe = stk_camera_probe,
+	.disconnect = stk_camera_disconnect,
+	.id_table = stkwebcam_table,
+#ifdef CONFIG_PM
+	.suspend = stk_camera_suspend,
+	.resume = stk_camera_resume,
+#endif
+};
+
+module_usb_driver(stk_camera_driver);
diff --git a/drivers/media/usb/stkwebcam/stk-webcam.h b/drivers/media/usb/stkwebcam/stk-webcam.h
new file mode 100644
index 0000000..5cecbdc
--- /dev/null
+++ b/drivers/media/usb/stkwebcam/stk-webcam.h
@@ -0,0 +1,130 @@
+/*
+ * stk-webcam.h : Driver for Syntek 1125 USB webcam controller
+ *
+ * Copyright (C) 2006 Nicolas VIVIEN
+ * Copyright 2007-2008 Jaime Velasco Juan <jsagarribay@gmail.com>
+ *
+ * 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
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef STKWEBCAM_H
+#define STKWEBCAM_H
+
+#include <linux/usb.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-common.h>
+
+#define DRIVER_VERSION		"v0.0.1"
+#define DRIVER_VERSION_NUM	0x000001
+
+#define MAX_ISO_BUFS		3
+#define ISO_FRAMES_PER_DESC	16
+#define ISO_MAX_FRAME_SIZE	3 * 1024
+#define ISO_BUFFER_SIZE		(ISO_FRAMES_PER_DESC * ISO_MAX_FRAME_SIZE)
+
+struct stk_iso_buf {
+	void *data;
+	int length;
+	int read;
+	struct urb *urb;
+};
+
+/* Streaming IO buffers */
+struct stk_sio_buffer {
+	struct v4l2_buffer v4lbuf;
+	char *buffer;
+	int mapcount;
+	struct stk_camera *dev;
+	struct list_head list;
+};
+
+enum stk_mode {MODE_VGA, MODE_SXGA, MODE_CIF, MODE_QVGA, MODE_QCIF};
+
+struct stk_video {
+	enum stk_mode mode;
+	__u32 palette;
+	int hflip;
+	int vflip;
+};
+
+enum stk_status {
+	S_PRESENT = 1,
+	S_INITIALISED = 2,
+	S_MEMALLOCD = 4,
+	S_STREAMING = 8,
+};
+#define is_present(dev)		((dev)->status & S_PRESENT)
+#define is_initialised(dev)	((dev)->status & S_INITIALISED)
+#define is_streaming(dev)	((dev)->status & S_STREAMING)
+#define is_memallocd(dev)	((dev)->status & S_MEMALLOCD)
+#define set_present(dev)	((dev)->status = S_PRESENT)
+#define unset_present(dev)	((dev)->status &= \
+					~(S_PRESENT|S_INITIALISED|S_STREAMING))
+#define set_initialised(dev)	((dev)->status |= S_INITIALISED)
+#define unset_initialised(dev)	((dev)->status &= ~S_INITIALISED)
+#define set_memallocd(dev)	((dev)->status |= S_MEMALLOCD)
+#define unset_memallocd(dev)	((dev)->status &= ~S_MEMALLOCD)
+#define set_streaming(dev)	((dev)->status |= S_STREAMING)
+#define unset_streaming(dev)	((dev)->status &= ~S_STREAMING)
+
+struct regval {
+	unsigned reg;
+	unsigned val;
+};
+
+struct stk_camera {
+	struct v4l2_device v4l2_dev;
+	struct v4l2_ctrl_handler hdl;
+	struct video_device vdev;
+	struct usb_device *udev;
+	struct usb_interface *interface;
+	int webcam_model;
+	struct file *owner;
+	struct mutex lock;
+	int first_init;
+
+	u8 isoc_ep;
+
+	/* Not sure if this is right */
+	atomic_t urbs_used;
+
+	struct stk_video vsettings;
+
+	enum stk_status status;
+
+	spinlock_t spinlock;
+	wait_queue_head_t wait_frame;
+
+	struct stk_iso_buf *isobufs;
+
+	int frame_size;
+	/* Streaming buffers */
+	int reading;
+	unsigned int n_sbufs;
+	struct stk_sio_buffer *sio_bufs;
+	struct list_head sio_avail;
+	struct list_head sio_full;
+	unsigned sequence;
+};
+
+#define vdev_to_camera(d) container_of(d, struct stk_camera, vdev)
+
+int stk_camera_write_reg(struct stk_camera *, u16, u8);
+int stk_camera_read_reg(struct stk_camera *, u16, u8 *);
+
+int stk_sensor_init(struct stk_camera *);
+int stk_sensor_configure(struct stk_camera *);
+int stk_sensor_sleep(struct stk_camera *dev);
+int stk_sensor_wakeup(struct stk_camera *dev);
+int stk_sensor_set_brightness(struct stk_camera *dev, int br);
+
+#endif
diff --git a/drivers/media/usb/tm6000/Kconfig b/drivers/media/usb/tm6000/Kconfig
new file mode 100644
index 0000000..a43b77a
--- /dev/null
+++ b/drivers/media/usb/tm6000/Kconfig
@@ -0,0 +1,33 @@
+config VIDEO_TM6000
+	tristate "TV Master TM5600/6000/6010 driver"
+	depends on VIDEO_DEV && I2C && INPUT && RC_CORE && USB
+	select VIDEO_TUNER
+	select MEDIA_TUNER_XC2028
+	select MEDIA_TUNER_XC5000
+	select VIDEOBUF_VMALLOC
+	help
+	  Support for TM5600/TM6000/TM6010 USB Device
+
+	  Since these cards have no MPEG decoder onboard, they transmit
+	  only compressed MPEG data over the usb bus, so you need
+	  an external software decoder to watch TV on your computer.
+
+	  Say Y if you own such a device and want to use it.
+
+config VIDEO_TM6000_ALSA
+	tristate "TV Master TM5600/6000/6010 audio support"
+	depends on VIDEO_TM6000 && SND
+	select SND_PCM
+	---help---
+	  This is a video4linux driver for direct (DMA) audio for
+	  TM5600/TM6000/TM6010 USB Devices.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called tm6000-alsa.
+
+config VIDEO_TM6000_DVB
+	tristate "DVB Support for tm6000 based TV cards"
+	depends on VIDEO_TM6000 && DVB_CORE && USB
+	select DVB_ZL10353
+	---help---
+	  This adds support for DVB cards based on the tm5600/tm6000 chip.
diff --git a/drivers/media/usb/tm6000/Makefile b/drivers/media/usb/tm6000/Makefile
new file mode 100644
index 0000000..744c039
--- /dev/null
+++ b/drivers/media/usb/tm6000/Makefile
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0
+tm6000-y := tm6000-cards.o \
+		   tm6000-core.o  \
+		   tm6000-i2c.o   \
+		   tm6000-video.o \
+		   tm6000-stds.o \
+		   tm6000-input.o
+
+obj-$(CONFIG_VIDEO_TM6000) += tm6000.o
+obj-$(CONFIG_VIDEO_TM6000_ALSA) += tm6000-alsa.o
+obj-$(CONFIG_VIDEO_TM6000_DVB) += tm6000-dvb.o
+
+ccflags-y += -Idrivers/media/tuners
+ccflags-y += -Idrivers/media/dvb-frontends
diff --git a/drivers/media/usb/tm6000/tm6000-alsa.c b/drivers/media/usb/tm6000/tm6000-alsa.c
new file mode 100644
index 0000000..f18cffa
--- /dev/null
+++ b/drivers/media/usb/tm6000/tm6000-alsa.c
@@ -0,0 +1,521 @@
+// SPDX-License-Identifier: GPL-2.0
+// Support for audio capture for tm5600/6000/6010
+// Copyright (c) 2007-2008 Mauro Carvalho Chehab <mchehab@kernel.org>
+//
+// Based on cx88-alsa.c
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/usb.h>
+#include <linux/slab.h>
+#include <linux/vmalloc.h>
+
+#include <linux/delay.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/control.h>
+#include <sound/initval.h>
+
+
+#include "tm6000.h"
+#include "tm6000-regs.h"
+
+#undef dprintk
+
+#define dprintk(level, fmt, arg...) do {				   \
+	if (debug >= level)						   \
+		printk(KERN_INFO "%s/1: " fmt, chip->core->name , ## arg); \
+	} while (0)
+
+/****************************************************************************
+			Module global static vars
+ ****************************************************************************/
+
+static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
+
+static bool enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP;
+
+module_param_array(enable, bool, NULL, 0444);
+MODULE_PARM_DESC(enable, "Enable tm6000x soundcard. default enabled.");
+
+module_param_array(index, int, NULL, 0444);
+MODULE_PARM_DESC(index, "Index value for tm6000x capture interface(s).");
+
+
+/****************************************************************************
+				Module macros
+ ****************************************************************************/
+
+MODULE_DESCRIPTION("ALSA driver module for tm5600/tm6000/tm6010 based TV cards");
+MODULE_AUTHOR("Mauro Carvalho Chehab");
+MODULE_LICENSE("GPL v2");
+MODULE_SUPPORTED_DEVICE("{{Trident,tm5600},{{Trident,tm6000},{{Trident,tm6010}");
+static unsigned int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "enable debug messages");
+
+/****************************************************************************
+			Module specific funtions
+ ****************************************************************************/
+
+/*
+ * BOARD Specific: Sets audio DMA
+ */
+
+static int _tm6000_start_audio_dma(struct snd_tm6000_card *chip)
+{
+	struct tm6000_core *core = chip->core;
+
+	dprintk(1, "Starting audio DMA\n");
+
+	/* Enables audio */
+	tm6000_set_reg_mask(core, TM6010_REQ07_RCC_ACTIVE_IF, 0x40, 0x40);
+
+	tm6000_set_audio_bitrate(core, 48000);
+
+	return 0;
+}
+
+/*
+ * BOARD Specific: Resets audio DMA
+ */
+static int _tm6000_stop_audio_dma(struct snd_tm6000_card *chip)
+{
+	struct tm6000_core *core = chip->core;
+
+	dprintk(1, "Stopping audio DMA\n");
+
+	/* Disables audio */
+	tm6000_set_reg_mask(core, TM6010_REQ07_RCC_ACTIVE_IF, 0x00, 0x40);
+
+	return 0;
+}
+
+static void dsp_buffer_free(struct snd_pcm_substream *substream)
+{
+	struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream);
+
+	dprintk(2, "Freeing buffer\n");
+
+	vfree(substream->runtime->dma_area);
+	substream->runtime->dma_area = NULL;
+	substream->runtime->dma_bytes = 0;
+}
+
+static int dsp_buffer_alloc(struct snd_pcm_substream *substream, int size)
+{
+	struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream);
+
+	dprintk(2, "Allocating buffer\n");
+
+	if (substream->runtime->dma_area) {
+		if (substream->runtime->dma_bytes > size)
+			return 0;
+
+		dsp_buffer_free(substream);
+	}
+
+	substream->runtime->dma_area = vmalloc(size);
+	if (!substream->runtime->dma_area)
+		return -ENOMEM;
+
+	substream->runtime->dma_bytes = size;
+
+	return 0;
+}
+
+
+/****************************************************************************
+				ALSA PCM Interface
+ ****************************************************************************/
+
+/*
+ * Digital hardware definition
+ */
+#define DEFAULT_FIFO_SIZE	4096
+
+static const struct snd_pcm_hardware snd_tm6000_digital_hw = {
+	.info = SNDRV_PCM_INFO_BATCH |
+		SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_MMAP_VALID,
+	.formats = SNDRV_PCM_FMTBIT_S16_LE,
+
+	.rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_KNOT,
+	.rate_min = 48000,
+	.rate_max = 48000,
+	.channels_min = 2,
+	.channels_max = 2,
+	.period_bytes_min = 64,
+	.period_bytes_max = 12544,
+	.periods_min = 2,
+	.periods_max = 98,
+	.buffer_bytes_max = 62720 * 8,
+};
+
+/*
+ * audio pcm capture open callback
+ */
+static int snd_tm6000_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int err;
+
+	err = snd_pcm_hw_constraint_pow2(runtime, 0,
+					 SNDRV_PCM_HW_PARAM_PERIODS);
+	if (err < 0)
+		goto _error;
+
+	chip->substream = substream;
+
+	runtime->hw = snd_tm6000_digital_hw;
+	snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+
+	return 0;
+_error:
+	dprintk(1, "Error opening PCM!\n");
+	return err;
+}
+
+/*
+ * audio close callback
+ */
+static int snd_tm6000_close(struct snd_pcm_substream *substream)
+{
+	struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream);
+	struct tm6000_core *core = chip->core;
+
+	if (atomic_read(&core->stream_started) > 0) {
+		atomic_set(&core->stream_started, 0);
+		schedule_work(&core->wq_trigger);
+	}
+
+	return 0;
+}
+
+static int tm6000_fillbuf(struct tm6000_core *core, char *buf, int size)
+{
+	struct snd_tm6000_card *chip = core->adev;
+	struct snd_pcm_substream *substream = chip->substream;
+	struct snd_pcm_runtime *runtime;
+	int period_elapsed = 0;
+	unsigned int stride, buf_pos;
+	int length;
+
+	if (atomic_read(&core->stream_started) == 0)
+		return 0;
+
+	if (!size || !substream) {
+		dprintk(1, "substream was NULL\n");
+		return -EINVAL;
+	}
+
+	runtime = substream->runtime;
+	if (!runtime || !runtime->dma_area) {
+		dprintk(1, "runtime was NULL\n");
+		return -EINVAL;
+	}
+
+	buf_pos = chip->buf_pos;
+	stride = runtime->frame_bits >> 3;
+
+	if (stride == 0) {
+		dprintk(1, "stride is zero\n");
+		return -EINVAL;
+	}
+
+	length = size / stride;
+	if (length == 0) {
+		dprintk(1, "%s: length was zero\n", __func__);
+		return -EINVAL;
+	}
+
+	dprintk(1, "Copying %d bytes at %p[%d] - buf size=%d x %d\n", size,
+		runtime->dma_area, buf_pos,
+		(unsigned int)runtime->buffer_size, stride);
+
+	if (buf_pos + length >= runtime->buffer_size) {
+		unsigned int cnt = runtime->buffer_size - buf_pos;
+		memcpy(runtime->dma_area + buf_pos * stride, buf, cnt * stride);
+		memcpy(runtime->dma_area, buf + cnt * stride,
+			length * stride - cnt * stride);
+	} else
+		memcpy(runtime->dma_area + buf_pos * stride, buf,
+			length * stride);
+
+	snd_pcm_stream_lock(substream);
+
+	chip->buf_pos += length;
+	if (chip->buf_pos >= runtime->buffer_size)
+		chip->buf_pos -= runtime->buffer_size;
+
+	chip->period_pos += length;
+	if (chip->period_pos >= runtime->period_size) {
+		chip->period_pos -= runtime->period_size;
+		period_elapsed = 1;
+	}
+
+	snd_pcm_stream_unlock(substream);
+
+	if (period_elapsed)
+		snd_pcm_period_elapsed(substream);
+
+	return 0;
+}
+
+/*
+ * hw_params callback
+ */
+static int snd_tm6000_hw_params(struct snd_pcm_substream *substream,
+			      struct snd_pcm_hw_params *hw_params)
+{
+	int size, rc;
+
+	size = params_period_bytes(hw_params) * params_periods(hw_params);
+
+	rc = dsp_buffer_alloc(substream, size);
+	if (rc < 0)
+		return rc;
+
+	return 0;
+}
+
+/*
+ * hw free callback
+ */
+static int snd_tm6000_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream);
+	struct tm6000_core *core = chip->core;
+
+	if (atomic_read(&core->stream_started) > 0) {
+		atomic_set(&core->stream_started, 0);
+		schedule_work(&core->wq_trigger);
+	}
+
+	dsp_buffer_free(substream);
+	return 0;
+}
+
+/*
+ * prepare callback
+ */
+static int snd_tm6000_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream);
+
+	chip->buf_pos = 0;
+	chip->period_pos = 0;
+
+	return 0;
+}
+
+
+/*
+ * trigger callback
+ */
+static void audio_trigger(struct work_struct *work)
+{
+	struct tm6000_core *core = container_of(work, struct tm6000_core,
+						wq_trigger);
+	struct snd_tm6000_card *chip = core->adev;
+
+	if (atomic_read(&core->stream_started)) {
+		dprintk(1, "starting capture");
+		_tm6000_start_audio_dma(chip);
+	} else {
+		dprintk(1, "stopping capture");
+		_tm6000_stop_audio_dma(chip);
+	}
+}
+
+static int snd_tm6000_card_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream);
+	struct tm6000_core *core = chip->core;
+	int err = 0;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE: /* fall through */
+	case SNDRV_PCM_TRIGGER_RESUME: /* fall through */
+	case SNDRV_PCM_TRIGGER_START:
+		atomic_set(&core->stream_started, 1);
+		break;
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH: /* fall through */
+	case SNDRV_PCM_TRIGGER_SUSPEND: /* fall through */
+	case SNDRV_PCM_TRIGGER_STOP:
+		atomic_set(&core->stream_started, 0);
+		break;
+	default:
+		err = -EINVAL;
+		break;
+	}
+	schedule_work(&core->wq_trigger);
+
+	return err;
+}
+/*
+ * pointer callback
+ */
+static snd_pcm_uframes_t snd_tm6000_pointer(struct snd_pcm_substream *substream)
+{
+	struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream);
+
+	return chip->buf_pos;
+}
+
+static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs,
+					     unsigned long offset)
+{
+	void *pageptr = subs->runtime->dma_area + offset;
+
+	return vmalloc_to_page(pageptr);
+}
+
+/*
+ * operators
+ */
+static const struct snd_pcm_ops snd_tm6000_pcm_ops = {
+	.open = snd_tm6000_pcm_open,
+	.close = snd_tm6000_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = snd_tm6000_hw_params,
+	.hw_free = snd_tm6000_hw_free,
+	.prepare = snd_tm6000_prepare,
+	.trigger = snd_tm6000_card_trigger,
+	.pointer = snd_tm6000_pointer,
+	.page = snd_pcm_get_vmalloc_page,
+};
+
+/*
+ * create a PCM device
+ */
+
+/* FIXME: Control interface - How to control volume/mute? */
+
+/****************************************************************************
+			Basic Flow for Sound Devices
+ ****************************************************************************/
+
+/*
+ * Alsa Constructor - Component probe
+ */
+static int tm6000_audio_init(struct tm6000_core *dev)
+{
+	struct snd_card		*card;
+	struct snd_tm6000_card	*chip;
+	int			rc;
+	static int		devnr;
+	char			component[14];
+	struct snd_pcm		*pcm;
+
+	if (!dev)
+		return 0;
+
+	if (devnr >= SNDRV_CARDS)
+		return -ENODEV;
+
+	if (!enable[devnr])
+		return -ENOENT;
+
+	rc = snd_card_new(&dev->udev->dev, index[devnr], "tm6000",
+			  THIS_MODULE, 0, &card);
+	if (rc < 0) {
+		snd_printk(KERN_ERR "cannot create card instance %d\n", devnr);
+		return rc;
+	}
+	strcpy(card->driver, "tm6000-alsa");
+	strcpy(card->shortname, "TM5600/60x0");
+	sprintf(card->longname, "TM5600/60x0 Audio at bus %d device %d",
+		dev->udev->bus->busnum, dev->udev->devnum);
+
+	sprintf(component, "USB%04x:%04x",
+		le16_to_cpu(dev->udev->descriptor.idVendor),
+		le16_to_cpu(dev->udev->descriptor.idProduct));
+	snd_component_add(card, component);
+
+	chip = kzalloc(sizeof(struct snd_tm6000_card), GFP_KERNEL);
+	if (!chip) {
+		rc = -ENOMEM;
+		goto error;
+	}
+
+	chip->core = dev;
+	chip->card = card;
+	dev->adev = chip;
+	spin_lock_init(&chip->reg_lock);
+
+	rc = snd_pcm_new(card, "TM6000 Audio", 0, 0, 1, &pcm);
+	if (rc < 0)
+		goto error_chip;
+
+	pcm->info_flags = 0;
+	pcm->private_data = chip;
+	strcpy(pcm->name, "Trident TM5600/60x0");
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_tm6000_pcm_ops);
+
+	INIT_WORK(&dev->wq_trigger, audio_trigger);
+	rc = snd_card_register(card);
+	if (rc < 0)
+		goto error_chip;
+
+	dprintk(1, "Registered audio driver for %s\n", card->longname);
+
+	return 0;
+
+error_chip:
+	kfree(chip);
+	dev->adev = NULL;
+error:
+	snd_card_free(card);
+	return rc;
+}
+
+static int tm6000_audio_fini(struct tm6000_core *dev)
+{
+	struct snd_tm6000_card *chip;
+
+	if (!dev)
+		return 0;
+	chip = dev->adev;
+
+	if (!chip)
+		return 0;
+
+	if (!chip->card)
+		return 0;
+
+	snd_card_free(chip->card);
+	chip->card = NULL;
+	kfree(chip);
+	dev->adev = NULL;
+
+	return 0;
+}
+
+static struct tm6000_ops audio_ops = {
+	.type	= TM6000_AUDIO,
+	.name	= "TM6000 Audio Extension",
+	.init	= tm6000_audio_init,
+	.fini	= tm6000_audio_fini,
+	.fillbuf = tm6000_fillbuf,
+};
+
+static int __init tm6000_alsa_register(void)
+{
+	return tm6000_register_extension(&audio_ops);
+}
+
+static void __exit tm6000_alsa_unregister(void)
+{
+	tm6000_unregister_extension(&audio_ops);
+}
+
+module_init(tm6000_alsa_register);
+module_exit(tm6000_alsa_unregister);
diff --git a/drivers/media/usb/tm6000/tm6000-cards.c b/drivers/media/usb/tm6000/tm6000-cards.c
new file mode 100644
index 0000000..23df50a
--- /dev/null
+++ b/drivers/media/usb/tm6000/tm6000-cards.c
@@ -0,0 +1,1397 @@
+// SPDX-License-Identifier: GPL-2.0
+// tm6000-cards.c - driver for TM5600/TM6000/TM6010 USB video capture devices
+//
+// Copyright (c) 2006-2007 Mauro Carvalho Chehab <mchehab@kernel.org>
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/usb.h>
+#include <linux/slab.h>
+#include <media/v4l2-common.h>
+#include <media/tuner.h>
+#include <media/i2c/tvaudio.h>
+#include <media/rc-map.h>
+
+#include "tm6000.h"
+#include "tm6000-regs.h"
+#include "tuner-xc2028.h"
+#include "xc5000.h"
+
+#define TM6000_BOARD_UNKNOWN			0
+#define TM5600_BOARD_GENERIC			1
+#define TM6000_BOARD_GENERIC			2
+#define TM6010_BOARD_GENERIC			3
+#define TM5600_BOARD_10MOONS_UT821		4
+#define TM5600_BOARD_10MOONS_UT330		5
+#define TM6000_BOARD_ADSTECH_DUAL_TV		6
+#define TM6000_BOARD_FREECOM_AND_SIMILAR	7
+#define TM6000_BOARD_ADSTECH_MINI_DUAL_TV	8
+#define TM6010_BOARD_HAUPPAUGE_900H		9
+#define TM6010_BOARD_BEHOLD_WANDER		10
+#define TM6010_BOARD_BEHOLD_VOYAGER		11
+#define TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE	12
+#define TM6010_BOARD_TWINHAN_TU501		13
+#define TM6010_BOARD_BEHOLD_WANDER_LITE		14
+#define TM6010_BOARD_BEHOLD_VOYAGER_LITE	15
+#define TM5600_BOARD_TERRATEC_GRABSTER		16
+
+#define is_generic(model) ((model == TM6000_BOARD_UNKNOWN) || \
+			   (model == TM5600_BOARD_GENERIC) || \
+			   (model == TM6000_BOARD_GENERIC) || \
+			   (model == TM6010_BOARD_GENERIC))
+
+#define TM6000_MAXBOARDS        16
+static unsigned int card[]     = {[0 ... (TM6000_MAXBOARDS - 1)] = UNSET };
+
+module_param_array(card,  int, NULL, 0444);
+
+static unsigned long tm6000_devused;
+
+
+struct tm6000_board {
+	char            *name;
+	char		eename[16];		/* EEPROM name */
+	unsigned	eename_size;		/* size of EEPROM name */
+	unsigned	eename_pos;		/* Position where it appears at ROM */
+
+	struct tm6000_capabilities caps;
+
+	enum		tm6000_devtype type;	/* variant of the chipset */
+	int             tuner_type;     /* type of the tuner */
+	int             tuner_addr;     /* tuner address */
+	int             demod_addr;     /* demodulator address */
+
+	struct tm6000_gpio gpio;
+
+	struct tm6000_input	vinput[3];
+	struct tm6000_input	rinput;
+
+	char		*ir_codes;
+};
+
+static struct tm6000_board tm6000_boards[] = {
+	[TM6000_BOARD_UNKNOWN] = {
+		.name         = "Unknown tm6000 video grabber",
+		.caps = {
+			.has_tuner	= 1,
+			.has_eeprom	= 1,
+		},
+		.gpio = {
+			.tuner_reset	= TM6000_GPIO_1,
+		},
+		.vinput = { {
+			.type	= TM6000_INPUT_TV,
+			.vmux	= TM6000_VMUX_VIDEO_B,
+			.amux	= TM6000_AMUX_ADC1,
+			}, {
+			.type	= TM6000_INPUT_COMPOSITE1,
+			.vmux	= TM6000_VMUX_VIDEO_A,
+			.amux	= TM6000_AMUX_ADC2,
+			}, {
+			.type	= TM6000_INPUT_SVIDEO,
+			.vmux	= TM6000_VMUX_VIDEO_AB,
+			.amux	= TM6000_AMUX_ADC2,
+			},
+		},
+	},
+	[TM5600_BOARD_GENERIC] = {
+		.name         = "Generic tm5600 board",
+		.type         = TM5600,
+		.tuner_type   = TUNER_XC2028,
+		.tuner_addr   = 0xc2 >> 1,
+		.caps = {
+			.has_tuner	= 1,
+			.has_eeprom	= 1,
+		},
+		.gpio = {
+			.tuner_reset	= TM6000_GPIO_1,
+		},
+		.vinput = { {
+			.type	= TM6000_INPUT_TV,
+			.vmux	= TM6000_VMUX_VIDEO_B,
+			.amux	= TM6000_AMUX_ADC1,
+			}, {
+			.type	= TM6000_INPUT_COMPOSITE1,
+			.vmux	= TM6000_VMUX_VIDEO_A,
+			.amux	= TM6000_AMUX_ADC2,
+			}, {
+			.type	= TM6000_INPUT_SVIDEO,
+			.vmux	= TM6000_VMUX_VIDEO_AB,
+			.amux	= TM6000_AMUX_ADC2,
+			},
+		},
+	},
+	[TM6000_BOARD_GENERIC] = {
+		.name         = "Generic tm6000 board",
+		.tuner_type   = TUNER_XC2028,
+		.tuner_addr   = 0xc2 >> 1,
+		.caps = {
+			.has_tuner	= 1,
+			.has_eeprom	= 1,
+		},
+		.gpio = {
+			.tuner_reset	= TM6000_GPIO_1,
+		},
+		.vinput = { {
+			.type	= TM6000_INPUT_TV,
+			.vmux	= TM6000_VMUX_VIDEO_B,
+			.amux	= TM6000_AMUX_ADC1,
+			}, {
+			.type	= TM6000_INPUT_COMPOSITE1,
+			.vmux	= TM6000_VMUX_VIDEO_A,
+			.amux	= TM6000_AMUX_ADC2,
+			}, {
+			.type	= TM6000_INPUT_SVIDEO,
+			.vmux	= TM6000_VMUX_VIDEO_AB,
+			.amux	= TM6000_AMUX_ADC2,
+			},
+		},
+	},
+	[TM6010_BOARD_GENERIC] = {
+		.name         = "Generic tm6010 board",
+		.type         = TM6010,
+		.tuner_type   = TUNER_XC2028,
+		.tuner_addr   = 0xc2 >> 1,
+		.demod_addr   = 0x1e >> 1,
+		.caps = {
+			.has_tuner	= 1,
+			.has_dvb	= 1,
+			.has_zl10353	= 1,
+			.has_eeprom	= 1,
+			.has_remote	= 1,
+		},
+		.gpio = {
+			.tuner_reset	= TM6010_GPIO_2,
+			.tuner_on	= TM6010_GPIO_3,
+			.demod_reset	= TM6010_GPIO_1,
+			.demod_on	= TM6010_GPIO_4,
+			.power_led	= TM6010_GPIO_7,
+			.dvb_led	= TM6010_GPIO_5,
+			.ir		= TM6010_GPIO_0,
+		},
+		.vinput = { {
+			.type	= TM6000_INPUT_TV,
+			.vmux	= TM6000_VMUX_VIDEO_B,
+			.amux	= TM6000_AMUX_SIF1,
+			}, {
+			.type	= TM6000_INPUT_COMPOSITE1,
+			.vmux	= TM6000_VMUX_VIDEO_A,
+			.amux	= TM6000_AMUX_ADC2,
+			}, {
+			.type	= TM6000_INPUT_SVIDEO,
+			.vmux	= TM6000_VMUX_VIDEO_AB,
+			.amux	= TM6000_AMUX_ADC2,
+			},
+		},
+	},
+	[TM5600_BOARD_10MOONS_UT821] = {
+		.name         = "10Moons UT 821",
+		.tuner_type   = TUNER_XC2028,
+		.eename       = { '1', '0', 'M', 'O', 'O', 'N', 'S', '5', '6', '0', '0', 0xff, 0x45, 0x5b},
+		.eename_size  = 14,
+		.eename_pos   = 0x14,
+		.type         = TM5600,
+		.tuner_addr   = 0xc2 >> 1,
+		.caps = {
+			.has_tuner    = 1,
+			.has_eeprom   = 1,
+		},
+		.gpio = {
+			.tuner_reset	= TM6000_GPIO_1,
+		},
+		.vinput = { {
+			.type	= TM6000_INPUT_TV,
+			.vmux	= TM6000_VMUX_VIDEO_B,
+			.amux	= TM6000_AMUX_ADC1,
+			}, {
+			.type	= TM6000_INPUT_COMPOSITE1,
+			.vmux	= TM6000_VMUX_VIDEO_A,
+			.amux	= TM6000_AMUX_ADC2,
+			}, {
+			.type	= TM6000_INPUT_SVIDEO,
+			.vmux	= TM6000_VMUX_VIDEO_AB,
+			.amux	= TM6000_AMUX_ADC2,
+			},
+		},
+	},
+	[TM5600_BOARD_10MOONS_UT330] = {
+		.name         = "10Moons UT 330",
+		.tuner_type   = TUNER_PHILIPS_FQ1216AME_MK4,
+		.tuner_addr   = 0xc8 >> 1,
+		.caps = {
+			.has_tuner    = 1,
+			.has_dvb      = 0,
+			.has_zl10353  = 0,
+			.has_eeprom   = 1,
+		},
+		.vinput = { {
+			.type	= TM6000_INPUT_TV,
+			.vmux	= TM6000_VMUX_VIDEO_B,
+			.amux	= TM6000_AMUX_ADC1,
+			}, {
+			.type	= TM6000_INPUT_COMPOSITE1,
+			.vmux	= TM6000_VMUX_VIDEO_A,
+			.amux	= TM6000_AMUX_ADC2,
+			}, {
+			.type	= TM6000_INPUT_SVIDEO,
+			.vmux	= TM6000_VMUX_VIDEO_AB,
+			.amux	= TM6000_AMUX_ADC2,
+			},
+		},
+	},
+	[TM6000_BOARD_ADSTECH_DUAL_TV] = {
+		.name         = "ADSTECH Dual TV USB",
+		.tuner_type   = TUNER_XC2028,
+		.tuner_addr   = 0xc8 >> 1,
+		.caps = {
+			.has_tuner    = 1,
+			.has_tda9874  = 1,
+			.has_dvb      = 1,
+			.has_zl10353  = 1,
+			.has_eeprom   = 1,
+		},
+		.vinput = { {
+			.type	= TM6000_INPUT_TV,
+			.vmux	= TM6000_VMUX_VIDEO_B,
+			.amux	= TM6000_AMUX_ADC1,
+			}, {
+			.type	= TM6000_INPUT_COMPOSITE1,
+			.vmux	= TM6000_VMUX_VIDEO_A,
+			.amux	= TM6000_AMUX_ADC2,
+			}, {
+			.type	= TM6000_INPUT_SVIDEO,
+			.vmux	= TM6000_VMUX_VIDEO_AB,
+			.amux	= TM6000_AMUX_ADC2,
+			},
+		},
+	},
+	[TM6000_BOARD_FREECOM_AND_SIMILAR] = {
+		.name         = "Freecom Hybrid Stick / Moka DVB-T Receiver Dual",
+		.tuner_type   = TUNER_XC2028, /* has a XC3028 */
+		.tuner_addr   = 0xc2 >> 1,
+		.demod_addr   = 0x1e >> 1,
+		.caps = {
+			.has_tuner    = 1,
+			.has_dvb      = 1,
+			.has_zl10353  = 1,
+			.has_eeprom   = 0,
+			.has_remote   = 1,
+		},
+		.gpio = {
+			.tuner_reset	= TM6000_GPIO_4,
+		},
+		.vinput = { {
+			.type	= TM6000_INPUT_TV,
+			.vmux	= TM6000_VMUX_VIDEO_B,
+			.amux	= TM6000_AMUX_ADC1,
+			}, {
+			.type	= TM6000_INPUT_COMPOSITE1,
+			.vmux	= TM6000_VMUX_VIDEO_A,
+			.amux	= TM6000_AMUX_ADC2,
+			}, {
+			.type	= TM6000_INPUT_SVIDEO,
+			.vmux	= TM6000_VMUX_VIDEO_AB,
+			.amux	= TM6000_AMUX_ADC2,
+			},
+		},
+	},
+	[TM6000_BOARD_ADSTECH_MINI_DUAL_TV] = {
+		.name         = "ADSTECH Mini Dual TV USB",
+		.tuner_type   = TUNER_XC2028, /* has a XC3028 */
+		.tuner_addr   = 0xc8 >> 1,
+		.demod_addr   = 0x1e >> 1,
+		.caps = {
+			.has_tuner    = 1,
+			.has_dvb      = 1,
+			.has_zl10353  = 1,
+			.has_eeprom   = 0,
+		},
+		.gpio = {
+			.tuner_reset	= TM6000_GPIO_4,
+		},
+		.vinput = { {
+			.type	= TM6000_INPUT_TV,
+			.vmux	= TM6000_VMUX_VIDEO_B,
+			.amux	= TM6000_AMUX_ADC1,
+			}, {
+			.type	= TM6000_INPUT_COMPOSITE1,
+			.vmux	= TM6000_VMUX_VIDEO_A,
+			.amux	= TM6000_AMUX_ADC2,
+			}, {
+			.type	= TM6000_INPUT_SVIDEO,
+			.vmux	= TM6000_VMUX_VIDEO_AB,
+			.amux	= TM6000_AMUX_ADC2,
+			},
+		},
+	},
+	[TM6010_BOARD_HAUPPAUGE_900H] = {
+		.name         = "Hauppauge WinTV HVR-900H / WinTV USB2-Stick",
+		.eename       = { 'H', 0, 'V', 0, 'R', 0, '9', 0, '0', 0, '0', 0, 'H', 0 },
+		.eename_size  = 14,
+		.eename_pos   = 0x42,
+		.tuner_type   = TUNER_XC2028, /* has a XC3028 */
+		.tuner_addr   = 0xc2 >> 1,
+		.demod_addr   = 0x1e >> 1,
+		.type         = TM6010,
+		.ir_codes = RC_MAP_HAUPPAUGE,
+		.caps = {
+			.has_tuner    = 1,
+			.has_dvb      = 1,
+			.has_zl10353  = 1,
+			.has_eeprom   = 1,
+			.has_remote   = 1,
+		},
+		.gpio = {
+			.tuner_reset	= TM6010_GPIO_2,
+			.tuner_on	= TM6010_GPIO_3,
+			.demod_reset	= TM6010_GPIO_1,
+			.demod_on	= TM6010_GPIO_4,
+			.power_led	= TM6010_GPIO_7,
+			.dvb_led	= TM6010_GPIO_5,
+			.ir		= TM6010_GPIO_0,
+		},
+		.vinput = { {
+			.type	= TM6000_INPUT_TV,
+			.vmux	= TM6000_VMUX_VIDEO_B,
+			.amux	= TM6000_AMUX_SIF1,
+			}, {
+			.type	= TM6000_INPUT_COMPOSITE1,
+			.vmux	= TM6000_VMUX_VIDEO_A,
+			.amux	= TM6000_AMUX_ADC2,
+			}, {
+			.type	= TM6000_INPUT_SVIDEO,
+			.vmux	= TM6000_VMUX_VIDEO_AB,
+			.amux	= TM6000_AMUX_ADC2,
+			},
+		},
+	},
+	[TM6010_BOARD_BEHOLD_WANDER] = {
+		.name         = "Beholder Wander DVB-T/TV/FM USB2.0",
+		.tuner_type   = TUNER_XC5000,
+		.tuner_addr   = 0xc2 >> 1,
+		.demod_addr   = 0x1e >> 1,
+		.type         = TM6010,
+		.caps = {
+			.has_tuner      = 1,
+			.has_dvb        = 1,
+			.has_zl10353    = 1,
+			.has_eeprom     = 1,
+			.has_remote     = 1,
+			.has_radio	= 1,
+		},
+		.gpio = {
+			.tuner_reset	= TM6010_GPIO_0,
+			.demod_reset	= TM6010_GPIO_1,
+			.power_led	= TM6010_GPIO_6,
+		},
+		.vinput = { {
+			.type	= TM6000_INPUT_TV,
+			.vmux	= TM6000_VMUX_VIDEO_B,
+			.amux	= TM6000_AMUX_SIF1,
+			}, {
+			.type	= TM6000_INPUT_COMPOSITE1,
+			.vmux	= TM6000_VMUX_VIDEO_A,
+			.amux	= TM6000_AMUX_ADC2,
+			}, {
+			.type	= TM6000_INPUT_SVIDEO,
+			.vmux	= TM6000_VMUX_VIDEO_AB,
+			.amux	= TM6000_AMUX_ADC2,
+			},
+		},
+		.rinput = {
+			.type	= TM6000_INPUT_RADIO,
+			.amux	= TM6000_AMUX_ADC1,
+		},
+	},
+	[TM6010_BOARD_BEHOLD_VOYAGER] = {
+		.name         = "Beholder Voyager TV/FM USB2.0",
+		.tuner_type   = TUNER_XC5000,
+		.tuner_addr   = 0xc2 >> 1,
+		.type         = TM6010,
+		.caps = {
+			.has_tuner      = 1,
+			.has_dvb        = 0,
+			.has_zl10353    = 0,
+			.has_eeprom     = 1,
+			.has_remote     = 1,
+			.has_radio	= 1,
+		},
+		.gpio = {
+			.tuner_reset	= TM6010_GPIO_0,
+			.power_led	= TM6010_GPIO_6,
+		},
+		.vinput = { {
+			.type	= TM6000_INPUT_TV,
+			.vmux	= TM6000_VMUX_VIDEO_B,
+			.amux	= TM6000_AMUX_SIF1,
+			}, {
+			.type	= TM6000_INPUT_COMPOSITE1,
+			.vmux	= TM6000_VMUX_VIDEO_A,
+			.amux	= TM6000_AMUX_ADC2,
+			}, {
+			.type	= TM6000_INPUT_SVIDEO,
+			.vmux	= TM6000_VMUX_VIDEO_AB,
+			.amux	= TM6000_AMUX_ADC2,
+			},
+		},
+		.rinput = {
+			.type	= TM6000_INPUT_RADIO,
+			.amux	= TM6000_AMUX_ADC1,
+		},
+	},
+	[TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE] = {
+		.name         = "Terratec Cinergy Hybrid XE / Cinergy Hybrid-Stick",
+		.tuner_type   = TUNER_XC2028, /* has a XC3028 */
+		.tuner_addr   = 0xc2 >> 1,
+		.demod_addr   = 0x1e >> 1,
+		.type         = TM6010,
+		.caps = {
+			.has_tuner    = 1,
+			.has_dvb      = 1,
+			.has_zl10353  = 1,
+			.has_eeprom   = 1,
+			.has_remote   = 1,
+			.has_radio    = 1,
+		},
+		.gpio = {
+			.tuner_reset	= TM6010_GPIO_2,
+			.tuner_on	= TM6010_GPIO_3,
+			.demod_reset	= TM6010_GPIO_1,
+			.demod_on	= TM6010_GPIO_4,
+			.power_led	= TM6010_GPIO_7,
+			.dvb_led	= TM6010_GPIO_5,
+			.ir		= TM6010_GPIO_0,
+		},
+		.ir_codes = RC_MAP_NEC_TERRATEC_CINERGY_XS,
+		.vinput = { {
+			.type	= TM6000_INPUT_TV,
+			.vmux	= TM6000_VMUX_VIDEO_B,
+			.amux	= TM6000_AMUX_SIF1,
+			}, {
+			.type	= TM6000_INPUT_COMPOSITE1,
+			.vmux	= TM6000_VMUX_VIDEO_A,
+			.amux	= TM6000_AMUX_ADC2,
+			}, {
+			.type	= TM6000_INPUT_SVIDEO,
+			.vmux	= TM6000_VMUX_VIDEO_AB,
+			.amux	= TM6000_AMUX_ADC2,
+			},
+		},
+		.rinput = {
+			.type = TM6000_INPUT_RADIO,
+			.amux = TM6000_AMUX_SIF1,
+		},
+	},
+	[TM5600_BOARD_TERRATEC_GRABSTER] = {
+		.name         = "Terratec Grabster AV 150/250 MX",
+		.type         = TM5600,
+		.tuner_type   = TUNER_ABSENT,
+		.vinput = { {
+			.type	= TM6000_INPUT_TV,
+			.vmux	= TM6000_VMUX_VIDEO_B,
+			.amux	= TM6000_AMUX_ADC1,
+			}, {
+			.type	= TM6000_INPUT_COMPOSITE1,
+			.vmux	= TM6000_VMUX_VIDEO_A,
+			.amux	= TM6000_AMUX_ADC2,
+			}, {
+			.type	= TM6000_INPUT_SVIDEO,
+			.vmux	= TM6000_VMUX_VIDEO_AB,
+			.amux	= TM6000_AMUX_ADC2,
+			},
+		},
+	},
+	[TM6010_BOARD_TWINHAN_TU501] = {
+		.name         = "Twinhan TU501(704D1)",
+		.tuner_type   = TUNER_XC2028, /* has a XC3028 */
+		.tuner_addr   = 0xc2 >> 1,
+		.demod_addr   = 0x1e >> 1,
+		.type         = TM6010,
+		.caps = {
+			.has_tuner    = 1,
+			.has_dvb      = 1,
+			.has_zl10353  = 1,
+			.has_eeprom   = 1,
+			.has_remote   = 1,
+		},
+		.gpio = {
+			.tuner_reset	= TM6010_GPIO_2,
+			.tuner_on	= TM6010_GPIO_3,
+			.demod_reset	= TM6010_GPIO_1,
+			.demod_on	= TM6010_GPIO_4,
+			.power_led	= TM6010_GPIO_7,
+			.dvb_led	= TM6010_GPIO_5,
+			.ir		= TM6010_GPIO_0,
+		},
+		.vinput = { {
+			.type	= TM6000_INPUT_TV,
+			.vmux	= TM6000_VMUX_VIDEO_B,
+			.amux	= TM6000_AMUX_SIF1,
+			}, {
+			.type	= TM6000_INPUT_COMPOSITE1,
+			.vmux	= TM6000_VMUX_VIDEO_A,
+			.amux	= TM6000_AMUX_ADC2,
+			}, {
+			.type	= TM6000_INPUT_SVIDEO,
+			.vmux	= TM6000_VMUX_VIDEO_AB,
+			.amux	= TM6000_AMUX_ADC2,
+			},
+		},
+	},
+	[TM6010_BOARD_BEHOLD_WANDER_LITE] = {
+		.name         = "Beholder Wander Lite DVB-T/TV/FM USB2.0",
+		.tuner_type   = TUNER_XC5000,
+		.tuner_addr   = 0xc2 >> 1,
+		.demod_addr   = 0x1e >> 1,
+		.type         = TM6010,
+		.caps = {
+			.has_tuner      = 1,
+			.has_dvb        = 1,
+			.has_zl10353    = 1,
+			.has_eeprom     = 1,
+			.has_remote     = 0,
+			.has_radio	= 1,
+		},
+		.gpio = {
+			.tuner_reset	= TM6010_GPIO_0,
+			.demod_reset	= TM6010_GPIO_1,
+			.power_led	= TM6010_GPIO_6,
+		},
+		.vinput = { {
+			.type	= TM6000_INPUT_TV,
+			.vmux	= TM6000_VMUX_VIDEO_B,
+			.amux	= TM6000_AMUX_SIF1,
+			},
+		},
+		.rinput = {
+			.type	= TM6000_INPUT_RADIO,
+			.amux	= TM6000_AMUX_ADC1,
+		},
+	},
+	[TM6010_BOARD_BEHOLD_VOYAGER_LITE] = {
+		.name         = "Beholder Voyager Lite TV/FM USB2.0",
+		.tuner_type   = TUNER_XC5000,
+		.tuner_addr   = 0xc2 >> 1,
+		.type         = TM6010,
+		.caps = {
+			.has_tuner      = 1,
+			.has_dvb        = 0,
+			.has_zl10353    = 0,
+			.has_eeprom     = 1,
+			.has_remote     = 0,
+			.has_radio	= 1,
+		},
+		.gpio = {
+			.tuner_reset	= TM6010_GPIO_0,
+			.power_led	= TM6010_GPIO_6,
+		},
+		.vinput = { {
+			.type	= TM6000_INPUT_TV,
+			.vmux	= TM6000_VMUX_VIDEO_B,
+			.amux	= TM6000_AMUX_SIF1,
+			},
+		},
+		.rinput = {
+			.type	= TM6000_INPUT_RADIO,
+			.amux	= TM6000_AMUX_ADC1,
+		},
+	},
+};
+
+/* table of devices that work with this driver */
+static const struct usb_device_id tm6000_id_table[] = {
+	{ USB_DEVICE(0x6000, 0x0001), .driver_info = TM5600_BOARD_GENERIC },
+	{ USB_DEVICE(0x6000, 0x0002), .driver_info = TM6010_BOARD_GENERIC },
+	{ USB_DEVICE(0x06e1, 0xf332), .driver_info = TM6000_BOARD_ADSTECH_DUAL_TV },
+	{ USB_DEVICE(0x14aa, 0x0620), .driver_info = TM6000_BOARD_FREECOM_AND_SIMILAR },
+	{ USB_DEVICE(0x06e1, 0xb339), .driver_info = TM6000_BOARD_ADSTECH_MINI_DUAL_TV },
+	{ USB_DEVICE(0x2040, 0x6600), .driver_info = TM6010_BOARD_HAUPPAUGE_900H },
+	{ USB_DEVICE(0x2040, 0x6601), .driver_info = TM6010_BOARD_HAUPPAUGE_900H },
+	{ USB_DEVICE(0x2040, 0x6610), .driver_info = TM6010_BOARD_HAUPPAUGE_900H },
+	{ USB_DEVICE(0x2040, 0x6611), .driver_info = TM6010_BOARD_HAUPPAUGE_900H },
+	{ USB_DEVICE(0x6000, 0xdec0), .driver_info = TM6010_BOARD_BEHOLD_WANDER },
+	{ USB_DEVICE(0x6000, 0xdec1), .driver_info = TM6010_BOARD_BEHOLD_VOYAGER },
+	{ USB_DEVICE(0x0ccd, 0x0086), .driver_info = TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE },
+	{ USB_DEVICE(0x0ccd, 0x00A5), .driver_info = TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE },
+	{ USB_DEVICE(0x0ccd, 0x0079), .driver_info = TM5600_BOARD_TERRATEC_GRABSTER },
+	{ USB_DEVICE(0x13d3, 0x3240), .driver_info = TM6010_BOARD_TWINHAN_TU501 },
+	{ USB_DEVICE(0x13d3, 0x3241), .driver_info = TM6010_BOARD_TWINHAN_TU501 },
+	{ USB_DEVICE(0x13d3, 0x3243), .driver_info = TM6010_BOARD_TWINHAN_TU501 },
+	{ USB_DEVICE(0x13d3, 0x3264), .driver_info = TM6010_BOARD_TWINHAN_TU501 },
+	{ USB_DEVICE(0x6000, 0xdec2), .driver_info = TM6010_BOARD_BEHOLD_WANDER_LITE },
+	{ USB_DEVICE(0x6000, 0xdec3), .driver_info = TM6010_BOARD_BEHOLD_VOYAGER_LITE },
+	{ }
+};
+MODULE_DEVICE_TABLE(usb, tm6000_id_table);
+
+/* Control power led for show some activity */
+void tm6000_flash_led(struct tm6000_core *dev, u8 state)
+{
+	/* Power LED unconfigured */
+	if (!dev->gpio.power_led)
+		return;
+
+	/* ON Power LED */
+	if (state) {
+		switch (dev->model) {
+		case TM6010_BOARD_HAUPPAUGE_900H:
+		case TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE:
+		case TM6010_BOARD_TWINHAN_TU501:
+			tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+				dev->gpio.power_led, 0x00);
+			break;
+		case TM6010_BOARD_BEHOLD_WANDER:
+		case TM6010_BOARD_BEHOLD_VOYAGER:
+		case TM6010_BOARD_BEHOLD_WANDER_LITE:
+		case TM6010_BOARD_BEHOLD_VOYAGER_LITE:
+			tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+				dev->gpio.power_led, 0x01);
+			break;
+		}
+	}
+	/* OFF Power LED */
+	else {
+		switch (dev->model) {
+		case TM6010_BOARD_HAUPPAUGE_900H:
+		case TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE:
+		case TM6010_BOARD_TWINHAN_TU501:
+			tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+				dev->gpio.power_led, 0x01);
+			break;
+		case TM6010_BOARD_BEHOLD_WANDER:
+		case TM6010_BOARD_BEHOLD_VOYAGER:
+		case TM6010_BOARD_BEHOLD_WANDER_LITE:
+		case TM6010_BOARD_BEHOLD_VOYAGER_LITE:
+			tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+				dev->gpio.power_led, 0x00);
+			break;
+		}
+	}
+}
+
+/* Tuner callback to provide the proper gpio changes needed for xc5000 */
+int tm6000_xc5000_callback(void *ptr, int component, int command, int arg)
+{
+	int rc = 0;
+	struct tm6000_core *dev = ptr;
+
+	if (dev->tuner_type != TUNER_XC5000)
+		return 0;
+
+	switch (command) {
+	case XC5000_TUNER_RESET:
+		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+			       dev->gpio.tuner_reset, 0x01);
+		msleep(15);
+		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+			       dev->gpio.tuner_reset, 0x00);
+		msleep(15);
+		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+			       dev->gpio.tuner_reset, 0x01);
+		break;
+	}
+	return rc;
+}
+EXPORT_SYMBOL_GPL(tm6000_xc5000_callback);
+
+/* Tuner callback to provide the proper gpio changes needed for xc2028 */
+
+int tm6000_tuner_callback(void *ptr, int component, int command, int arg)
+{
+	int rc = 0;
+	struct tm6000_core *dev = ptr;
+
+	if (dev->tuner_type != TUNER_XC2028)
+		return 0;
+
+	switch (command) {
+	case XC2028_RESET_CLK:
+		tm6000_ir_wait(dev, 0);
+
+		tm6000_set_reg(dev, REQ_04_EN_DISABLE_MCU_INT,
+					0x02, arg);
+		msleep(10);
+		rc = tm6000_i2c_reset(dev, 10);
+		break;
+	case XC2028_TUNER_RESET:
+		/* Reset codes during load firmware */
+		switch (arg) {
+		case 0:
+			/* newer tuner can faster reset */
+			switch (dev->model) {
+			case TM5600_BOARD_10MOONS_UT821:
+				tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+					       dev->gpio.tuner_reset, 0x01);
+				tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+					       0x300, 0x01);
+				msleep(10);
+				tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+					       dev->gpio.tuner_reset, 0x00);
+				tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+					       0x300, 0x00);
+				msleep(10);
+				tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+					       dev->gpio.tuner_reset, 0x01);
+				tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+					       0x300, 0x01);
+				break;
+			case TM6010_BOARD_HAUPPAUGE_900H:
+			case TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE:
+			case TM6010_BOARD_TWINHAN_TU501:
+				tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+					       dev->gpio.tuner_reset, 0x01);
+				msleep(60);
+				tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+					       dev->gpio.tuner_reset, 0x00);
+				msleep(75);
+				tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+					       dev->gpio.tuner_reset, 0x01);
+				msleep(60);
+				break;
+			default:
+				tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+					       dev->gpio.tuner_reset, 0x00);
+				msleep(130);
+				tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+					       dev->gpio.tuner_reset, 0x01);
+				msleep(130);
+				break;
+			}
+
+			tm6000_ir_wait(dev, 1);
+			break;
+		case 1:
+			tm6000_set_reg(dev, REQ_04_EN_DISABLE_MCU_INT,
+						0x02, 0x01);
+			msleep(10);
+			break;
+		case 2:
+			rc = tm6000_i2c_reset(dev, 100);
+			break;
+		}
+		break;
+	case XC2028_I2C_FLUSH:
+		tm6000_set_reg(dev, REQ_50_SET_START, 0, 0);
+		tm6000_set_reg(dev, REQ_51_SET_STOP, 0, 0);
+		break;
+	}
+	return rc;
+}
+EXPORT_SYMBOL_GPL(tm6000_tuner_callback);
+
+int tm6000_cards_setup(struct tm6000_core *dev)
+{
+	/*
+	 * Board-specific initialization sequence. Handles all GPIO
+	 * initialization sequences that are board-specific.
+	 * Up to now, all found devices use GPIO1 and GPIO4 at the same way.
+	 * Probably, they're all based on some reference device. Due to that,
+	 * there's a common routine at the end to handle those GPIO's. Devices
+	 * that use different pinups or init sequences can just return at
+	 * the board-specific session.
+	 */
+	switch (dev->model) {
+	case TM6010_BOARD_HAUPPAUGE_900H:
+	case TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE:
+	case TM6010_BOARD_TWINHAN_TU501:
+	case TM6010_BOARD_GENERIC:
+		/* Turn xceive 3028 on */
+		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.tuner_on, 0x01);
+		msleep(15);
+		/* Turn zarlink zl10353 on */
+		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.demod_on, 0x00);
+		msleep(15);
+		/* Reset zarlink zl10353 */
+		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.demod_reset, 0x00);
+		msleep(50);
+		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.demod_reset, 0x01);
+		msleep(15);
+		/* Turn zarlink zl10353 off */
+		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.demod_on, 0x01);
+		msleep(15);
+		/* ir ? */
+		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.ir, 0x01);
+		msleep(15);
+		/* Power led on (blue) */
+		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.power_led, 0x00);
+		msleep(15);
+		/* DVB led off (orange) */
+		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.dvb_led, 0x01);
+		msleep(15);
+		/* Turn zarlink zl10353 on */
+		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.demod_on, 0x00);
+		msleep(15);
+		break;
+	case TM6010_BOARD_BEHOLD_WANDER:
+	case TM6010_BOARD_BEHOLD_WANDER_LITE:
+		/* Power led on (blue) */
+		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.power_led, 0x01);
+		msleep(15);
+		/* Reset zarlink zl10353 */
+		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.demod_reset, 0x00);
+		msleep(50);
+		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.demod_reset, 0x01);
+		msleep(15);
+		break;
+	case TM6010_BOARD_BEHOLD_VOYAGER:
+	case TM6010_BOARD_BEHOLD_VOYAGER_LITE:
+		/* Power led on (blue) */
+		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, dev->gpio.power_led, 0x01);
+		msleep(15);
+		break;
+	default:
+		break;
+	}
+
+	/*
+	 * Default initialization. Most of the devices seem to use GPIO1
+	 * and GPIO4.on the same way, so, this handles the common sequence
+	 * used by most devices.
+	 * If a device uses a different sequence or different GPIO pins for
+	 * reset, just add the code at the board-specific part
+	 */
+
+	if (dev->gpio.tuner_reset) {
+		int rc;
+		int i;
+
+		for (i = 0; i < 2; i++) {
+			rc = tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+						dev->gpio.tuner_reset, 0x00);
+			if (rc < 0) {
+				printk(KERN_ERR "Error %i doing tuner reset\n", rc);
+				return rc;
+			}
+
+			msleep(10); /* Just to be conservative */
+			rc = tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+						dev->gpio.tuner_reset, 0x01);
+			if (rc < 0) {
+				printk(KERN_ERR "Error %i doing tuner reset\n", rc);
+				return rc;
+			}
+		}
+	} else {
+		printk(KERN_ERR "Tuner reset is not configured\n");
+		return -1;
+	}
+
+	msleep(50);
+
+	return 0;
+};
+
+static void tm6000_config_tuner(struct tm6000_core *dev)
+{
+	struct tuner_setup tun_setup;
+
+	/* Load tuner module */
+	v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap,
+		"tuner", dev->tuner_addr, NULL);
+
+	memset(&tun_setup, 0, sizeof(tun_setup));
+	tun_setup.type = dev->tuner_type;
+	tun_setup.addr = dev->tuner_addr;
+
+	tun_setup.mode_mask = 0;
+	if (dev->caps.has_tuner)
+		tun_setup.mode_mask |= (T_ANALOG_TV | T_RADIO);
+
+	switch (dev->tuner_type) {
+	case TUNER_XC2028:
+		tun_setup.tuner_callback = tm6000_tuner_callback;
+		break;
+	case TUNER_XC5000:
+		tun_setup.tuner_callback = tm6000_xc5000_callback;
+		break;
+	}
+
+	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_type_addr, &tun_setup);
+
+	switch (dev->tuner_type) {
+	case TUNER_XC2028: {
+		struct v4l2_priv_tun_config xc2028_cfg;
+		struct xc2028_ctrl ctl;
+
+		memset(&xc2028_cfg, 0, sizeof(xc2028_cfg));
+		memset(&ctl, 0, sizeof(ctl));
+
+		ctl.demod = XC3028_FE_ZARLINK456;
+
+		xc2028_cfg.tuner = TUNER_XC2028;
+		xc2028_cfg.priv  = &ctl;
+
+		switch (dev->model) {
+		case TM6010_BOARD_HAUPPAUGE_900H:
+		case TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE:
+		case TM6010_BOARD_TWINHAN_TU501:
+			ctl.max_len = 80;
+			ctl.fname = "xc3028L-v36.fw";
+			break;
+		default:
+			if (dev->dev_type == TM6010)
+				ctl.fname = "xc3028-v27.fw";
+			else
+				ctl.fname = "xc3028-v24.fw";
+		}
+
+		printk(KERN_INFO "Setting firmware parameters for xc2028\n");
+		v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_config,
+				     &xc2028_cfg);
+
+		}
+		break;
+	case TUNER_XC5000:
+		{
+		struct v4l2_priv_tun_config  xc5000_cfg;
+		struct xc5000_config ctl = {
+			.i2c_address = dev->tuner_addr,
+			.if_khz      = 4570,
+			.radio_input = XC5000_RADIO_FM1_MONO,
+			};
+
+		xc5000_cfg.tuner = TUNER_XC5000;
+		xc5000_cfg.priv  = &ctl;
+
+		v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_config,
+				     &xc5000_cfg);
+		}
+		break;
+	default:
+		printk(KERN_INFO "Unknown tuner type. Tuner is not configured.\n");
+		break;
+	}
+}
+
+static int fill_board_specific_data(struct tm6000_core *dev)
+{
+	int rc;
+
+	dev->dev_type   = tm6000_boards[dev->model].type;
+	dev->tuner_type = tm6000_boards[dev->model].tuner_type;
+	dev->tuner_addr = tm6000_boards[dev->model].tuner_addr;
+
+	dev->gpio = tm6000_boards[dev->model].gpio;
+
+	dev->ir_codes = tm6000_boards[dev->model].ir_codes;
+
+	dev->demod_addr = tm6000_boards[dev->model].demod_addr;
+
+	dev->caps = tm6000_boards[dev->model].caps;
+
+	dev->vinput[0] = tm6000_boards[dev->model].vinput[0];
+	dev->vinput[1] = tm6000_boards[dev->model].vinput[1];
+	dev->vinput[2] = tm6000_boards[dev->model].vinput[2];
+	dev->rinput = tm6000_boards[dev->model].rinput;
+
+	/* setup per-model quirks */
+	switch (dev->model) {
+	case TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE:
+	case TM6010_BOARD_HAUPPAUGE_900H:
+		dev->quirks |= TM6000_QUIRK_NO_USB_DELAY;
+		break;
+
+	default:
+		break;
+	}
+
+	/* initialize hardware */
+	rc = tm6000_init(dev);
+	if (rc < 0)
+		return rc;
+
+	return v4l2_device_register(&dev->udev->dev, &dev->v4l2_dev);
+}
+
+
+static void use_alternative_detection_method(struct tm6000_core *dev)
+{
+	int i, model = -1;
+
+	if (!dev->eedata_size)
+		return;
+
+	for (i = 0; i < ARRAY_SIZE(tm6000_boards); i++) {
+		if (!tm6000_boards[i].eename_size)
+			continue;
+		if (dev->eedata_size < tm6000_boards[i].eename_pos +
+				       tm6000_boards[i].eename_size)
+			continue;
+
+		if (!memcmp(&dev->eedata[tm6000_boards[i].eename_pos],
+			    tm6000_boards[i].eename,
+			    tm6000_boards[i].eename_size)) {
+			model = i;
+			break;
+		}
+	}
+	if (model < 0) {
+		printk(KERN_INFO "Device has eeprom but is currently unknown\n");
+		return;
+	}
+
+	dev->model = model;
+
+	printk(KERN_INFO "Device identified via eeprom as %s (type = %d)\n",
+	       tm6000_boards[model].name, model);
+}
+
+#if defined(CONFIG_MODULES) && defined(MODULE)
+static void request_module_async(struct work_struct *work)
+{
+	struct tm6000_core *dev = container_of(work, struct tm6000_core,
+					       request_module_wk);
+
+	request_module("tm6000-alsa");
+
+	if (dev->caps.has_dvb)
+		request_module("tm6000-dvb");
+}
+
+static void request_modules(struct tm6000_core *dev)
+{
+	INIT_WORK(&dev->request_module_wk, request_module_async);
+	schedule_work(&dev->request_module_wk);
+}
+
+static void flush_request_modules(struct tm6000_core *dev)
+{
+	flush_work(&dev->request_module_wk);
+}
+#else
+#define request_modules(dev)
+#define flush_request_modules(dev)
+#endif /* CONFIG_MODULES */
+
+static int tm6000_init_dev(struct tm6000_core *dev)
+{
+	struct v4l2_frequency f;
+	int rc = 0;
+
+	mutex_init(&dev->lock);
+	mutex_lock(&dev->lock);
+
+	if (!is_generic(dev->model)) {
+		rc = fill_board_specific_data(dev);
+		if (rc < 0)
+			goto err;
+
+		/* register i2c bus */
+		rc = tm6000_i2c_register(dev);
+		if (rc < 0)
+			goto err;
+	} else {
+		/* register i2c bus */
+		rc = tm6000_i2c_register(dev);
+		if (rc < 0)
+			goto err;
+
+		use_alternative_detection_method(dev);
+
+		rc = fill_board_specific_data(dev);
+		if (rc < 0)
+			goto err;
+	}
+
+	/* Default values for STD and resolutions */
+	dev->width = 720;
+	dev->height = 480;
+	dev->norm = V4L2_STD_NTSC_M;
+
+	/* Configure tuner */
+	tm6000_config_tuner(dev);
+
+	/* Set video standard */
+	v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_std, dev->norm);
+
+	/* Set tuner frequency - also loads firmware on xc2028/xc3028 */
+	f.tuner = 0;
+	f.type = V4L2_TUNER_ANALOG_TV;
+	f.frequency = 3092;	/* 193.25 MHz */
+	dev->freq = f.frequency;
+	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_frequency, &f);
+
+	if (dev->caps.has_tda9874)
+		v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap,
+			"tvaudio", I2C_ADDR_TDA9874, NULL);
+
+	/* register and initialize V4L2 */
+	rc = tm6000_v4l2_register(dev);
+	if (rc < 0)
+		goto err;
+
+	tm6000_add_into_devlist(dev);
+	tm6000_init_extension(dev);
+
+	tm6000_ir_init(dev);
+
+	request_modules(dev);
+
+	mutex_unlock(&dev->lock);
+	return 0;
+
+err:
+	mutex_unlock(&dev->lock);
+	return rc;
+}
+
+/* high bandwidth multiplier, as encoded in highspeed endpoint descriptors */
+#define hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03))
+
+static void get_max_endpoint(struct usb_device *udev,
+			     struct usb_host_interface *alt,
+			     char *msgtype,
+			     struct usb_host_endpoint *curr_e,
+			     struct tm6000_endpoint *tm_ep)
+{
+	u16 tmp = le16_to_cpu(curr_e->desc.wMaxPacketSize);
+	unsigned int size = tmp & 0x7ff;
+
+	if (udev->speed == USB_SPEED_HIGH)
+		size = size * hb_mult(tmp);
+
+	if (size > tm_ep->maxsize) {
+		tm_ep->endp = curr_e;
+		tm_ep->maxsize = size;
+		tm_ep->bInterfaceNumber = alt->desc.bInterfaceNumber;
+		tm_ep->bAlternateSetting = alt->desc.bAlternateSetting;
+
+		printk(KERN_INFO "tm6000: %s endpoint: 0x%02x (max size=%u bytes)\n",
+					msgtype, curr_e->desc.bEndpointAddress,
+					size);
+	}
+}
+
+/*
+ * tm6000_usb_probe()
+ * checks for supported devices
+ */
+static int tm6000_usb_probe(struct usb_interface *interface,
+			    const struct usb_device_id *id)
+{
+	struct usb_device *usbdev;
+	struct tm6000_core *dev;
+	int i, rc;
+	int nr = 0;
+	char *speed;
+
+	usbdev = usb_get_dev(interface_to_usbdev(interface));
+
+	/* Selects the proper interface */
+	rc = usb_set_interface(usbdev, 0, 1);
+	if (rc < 0)
+		goto report_failure;
+
+	/* Check to see next free device and mark as used */
+	nr = find_first_zero_bit(&tm6000_devused, TM6000_MAXBOARDS);
+	if (nr >= TM6000_MAXBOARDS) {
+		printk(KERN_ERR "tm6000: Supports only %i tm60xx boards.\n", TM6000_MAXBOARDS);
+		rc = -ENOMEM;
+		goto put_device;
+	}
+
+	/* Create and initialize dev struct */
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (!dev) {
+		rc = -ENOMEM;
+		goto put_device;
+	}
+	spin_lock_init(&dev->slock);
+	mutex_init(&dev->usb_lock);
+
+	/* Increment usage count */
+	set_bit(nr, &tm6000_devused);
+	snprintf(dev->name, 29, "tm6000 #%d", nr);
+
+	dev->model = id->driver_info;
+	if (card[nr] < ARRAY_SIZE(tm6000_boards))
+		dev->model = card[nr];
+
+	dev->udev = usbdev;
+	dev->devno = nr;
+
+	switch (usbdev->speed) {
+	case USB_SPEED_LOW:
+		speed = "1.5";
+		break;
+	case USB_SPEED_UNKNOWN:
+	case USB_SPEED_FULL:
+		speed = "12";
+		break;
+	case USB_SPEED_HIGH:
+		speed = "480";
+		break;
+	default:
+		speed = "unknown";
+	}
+
+	/* Get endpoints */
+	for (i = 0; i < interface->num_altsetting; i++) {
+		int ep;
+
+		for (ep = 0; ep < interface->altsetting[i].desc.bNumEndpoints; ep++) {
+			struct usb_host_endpoint	*e;
+			int dir_out;
+
+			e = &interface->altsetting[i].endpoint[ep];
+
+			dir_out = ((e->desc.bEndpointAddress &
+					USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT);
+
+			printk(KERN_INFO "tm6000: alt %d, interface %i, class %i\n",
+			       i,
+			       interface->altsetting[i].desc.bInterfaceNumber,
+			       interface->altsetting[i].desc.bInterfaceClass);
+
+			switch (e->desc.bmAttributes) {
+			case USB_ENDPOINT_XFER_BULK:
+				if (!dir_out) {
+					get_max_endpoint(usbdev,
+							 &interface->altsetting[i],
+							 "Bulk IN", e,
+							 &dev->bulk_in);
+				} else {
+					get_max_endpoint(usbdev,
+							 &interface->altsetting[i],
+							 "Bulk OUT", e,
+							 &dev->bulk_out);
+				}
+				break;
+			case USB_ENDPOINT_XFER_ISOC:
+				if (!dir_out) {
+					get_max_endpoint(usbdev,
+							 &interface->altsetting[i],
+							 "ISOC IN", e,
+							 &dev->isoc_in);
+				} else {
+					get_max_endpoint(usbdev,
+							 &interface->altsetting[i],
+							 "ISOC OUT", e,
+							 &dev->isoc_out);
+				}
+				break;
+			case USB_ENDPOINT_XFER_INT:
+				if (!dir_out) {
+					get_max_endpoint(usbdev,
+							&interface->altsetting[i],
+							"INT IN", e,
+							&dev->int_in);
+				} else {
+					get_max_endpoint(usbdev,
+							&interface->altsetting[i],
+							"INT OUT", e,
+							&dev->int_out);
+				}
+				break;
+			}
+		}
+	}
+
+
+	printk(KERN_INFO "tm6000: New video device @ %s Mbps (%04x:%04x, ifnum %d)\n",
+		speed,
+		le16_to_cpu(dev->udev->descriptor.idVendor),
+		le16_to_cpu(dev->udev->descriptor.idProduct),
+		interface->altsetting->desc.bInterfaceNumber);
+
+/* check if the the device has the iso in endpoint at the correct place */
+	if (!dev->isoc_in.endp) {
+		printk(KERN_ERR "tm6000: probing error: no IN ISOC endpoint!\n");
+		rc = -ENODEV;
+		goto free_device;
+	}
+
+	/* save our data pointer in this interface device */
+	usb_set_intfdata(interface, dev);
+
+	printk(KERN_INFO "tm6000: Found %s\n", tm6000_boards[dev->model].name);
+
+	rc = tm6000_init_dev(dev);
+	if (rc < 0)
+		goto free_device;
+
+	return 0;
+
+free_device:
+	kfree(dev);
+report_failure:
+	printk(KERN_ERR "tm6000: Error %d while registering\n", rc);
+
+	clear_bit(nr, &tm6000_devused);
+put_device:
+	usb_put_dev(usbdev);
+	return rc;
+}
+
+/*
+ * tm6000_usb_disconnect()
+ * called when the device gets diconencted
+ * video device will be unregistered on v4l2_close in case it is still open
+ */
+static void tm6000_usb_disconnect(struct usb_interface *interface)
+{
+	struct tm6000_core *dev = usb_get_intfdata(interface);
+	usb_set_intfdata(interface, NULL);
+
+	if (!dev)
+		return;
+
+	printk(KERN_INFO "tm6000: disconnecting %s\n", dev->name);
+
+	flush_request_modules(dev);
+
+	tm6000_ir_fini(dev);
+
+	if (dev->gpio.power_led) {
+		switch (dev->model) {
+		case TM6010_BOARD_HAUPPAUGE_900H:
+		case TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE:
+		case TM6010_BOARD_TWINHAN_TU501:
+			/* Power led off */
+			tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+				dev->gpio.power_led, 0x01);
+			msleep(15);
+			break;
+		case TM6010_BOARD_BEHOLD_WANDER:
+		case TM6010_BOARD_BEHOLD_VOYAGER:
+		case TM6010_BOARD_BEHOLD_WANDER_LITE:
+		case TM6010_BOARD_BEHOLD_VOYAGER_LITE:
+			/* Power led off */
+			tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+				dev->gpio.power_led, 0x00);
+			msleep(15);
+			break;
+		}
+	}
+	tm6000_v4l2_unregister(dev);
+
+	tm6000_i2c_unregister(dev);
+
+	v4l2_device_unregister(&dev->v4l2_dev);
+
+	dev->state |= DEV_DISCONNECTED;
+
+	usb_put_dev(dev->udev);
+
+	tm6000_close_extension(dev);
+	tm6000_remove_from_devlist(dev);
+
+	clear_bit(dev->devno, &tm6000_devused);
+	kfree(dev);
+}
+
+static struct usb_driver tm6000_usb_driver = {
+		.name = "tm6000",
+		.probe = tm6000_usb_probe,
+		.disconnect = tm6000_usb_disconnect,
+		.id_table = tm6000_id_table,
+};
+
+module_usb_driver(tm6000_usb_driver);
+
+MODULE_DESCRIPTION("Trident TVMaster TM5600/TM6000/TM6010 USB2 adapter");
+MODULE_AUTHOR("Mauro Carvalho Chehab");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/usb/tm6000/tm6000-core.c b/drivers/media/usb/tm6000/tm6000-core.c
new file mode 100644
index 0000000..d3229aa
--- /dev/null
+++ b/drivers/media/usb/tm6000/tm6000-core.c
@@ -0,0 +1,922 @@
+// SPDX-License-Identifier: GPL-2.0
+// tm6000-core.c - driver for TM5600/TM6000/TM6010 USB video capture devices
+//
+// Copyright (c) 2006-2007 Mauro Carvalho Chehab <mchehab@kernel.org>
+//
+// Copyright (c) 2007 Michel Ludwig <michel.ludwig@gmail.com>
+//     - DVB-T support
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/i2c.h>
+#include "tm6000.h"
+#include "tm6000-regs.h"
+#include <media/v4l2-common.h>
+#include <media/tuner.h>
+
+#define USB_TIMEOUT	(5 * HZ) /* ms */
+
+int tm6000_read_write_usb(struct tm6000_core *dev, u8 req_type, u8 req,
+			  u16 value, u16 index, u8 *buf, u16 len)
+{
+	int          ret, i;
+	unsigned int pipe;
+	u8	     *data = NULL;
+	int delay = 5000;
+
+	if (len) {
+		data = kzalloc(len, GFP_KERNEL);
+		if (!data)
+			return -ENOMEM;
+	}
+
+	mutex_lock(&dev->usb_lock);
+
+	if (req_type & USB_DIR_IN)
+		pipe = usb_rcvctrlpipe(dev->udev, 0);
+	else {
+		pipe = usb_sndctrlpipe(dev->udev, 0);
+		memcpy(data, buf, len);
+	}
+
+	if (tm6000_debug & V4L2_DEBUG_I2C) {
+		printk(KERN_DEBUG "(dev %p, pipe %08x): ", dev->udev, pipe);
+
+		printk(KERN_CONT "%s: %02x %02x %02x %02x %02x %02x %02x %02x ",
+			(req_type & USB_DIR_IN) ? " IN" : "OUT",
+			req_type, req, value&0xff, value>>8, index&0xff,
+			index>>8, len&0xff, len>>8);
+
+		if (!(req_type & USB_DIR_IN)) {
+			printk(KERN_CONT ">>> ");
+			for (i = 0; i < len; i++)
+				printk(KERN_CONT " %02x", buf[i]);
+			printk(KERN_CONT "\n");
+		}
+	}
+
+	ret = usb_control_msg(dev->udev, pipe, req, req_type, value, index,
+			      data, len, USB_TIMEOUT);
+
+	if (req_type &  USB_DIR_IN)
+		memcpy(buf, data, len);
+
+	if (tm6000_debug & V4L2_DEBUG_I2C) {
+		if (ret < 0) {
+			if (req_type &  USB_DIR_IN)
+				printk(KERN_DEBUG "<<< (len=%d)\n", len);
+
+			printk(KERN_CONT "%s: Error #%d\n", __func__, ret);
+		} else if (req_type &  USB_DIR_IN) {
+			printk(KERN_CONT "<<< ");
+			for (i = 0; i < len; i++)
+				printk(KERN_CONT " %02x", buf[i]);
+			printk(KERN_CONT "\n");
+		}
+	}
+
+	kfree(data);
+
+	if (dev->quirks & TM6000_QUIRK_NO_USB_DELAY)
+		delay = 0;
+
+	if (req == REQ_16_SET_GET_I2C_WR1_RDN && !(req_type & USB_DIR_IN)) {
+		unsigned int tsleep;
+		/* Calculate delay time, 14000us for 64 bytes */
+		tsleep = (len * 200) + 200;
+		if (tsleep < delay)
+			tsleep = delay;
+		usleep_range(tsleep, tsleep + 1000);
+	}
+	else if (delay)
+		usleep_range(delay, delay + 1000);
+
+	mutex_unlock(&dev->usb_lock);
+	return ret;
+}
+
+int tm6000_set_reg(struct tm6000_core *dev, u8 req, u16 value, u16 index)
+{
+	return
+		tm6000_read_write_usb(dev, USB_DIR_OUT | USB_TYPE_VENDOR,
+				      req, value, index, NULL, 0);
+}
+EXPORT_SYMBOL_GPL(tm6000_set_reg);
+
+int tm6000_get_reg(struct tm6000_core *dev, u8 req, u16 value, u16 index)
+{
+	int rc;
+	u8 buf[1];
+
+	rc = tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR, req,
+					value, index, buf, 1);
+
+	if (rc < 0)
+		return rc;
+
+	return *buf;
+}
+EXPORT_SYMBOL_GPL(tm6000_get_reg);
+
+int tm6000_set_reg_mask(struct tm6000_core *dev, u8 req, u16 value,
+						u16 index, u16 mask)
+{
+	int rc;
+	u8 buf[1];
+	u8 new_index;
+
+	rc = tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR, req,
+					value, 0, buf, 1);
+
+	if (rc < 0)
+		return rc;
+
+	new_index = (buf[0] & ~mask) | (index & mask);
+
+	if (new_index == buf[0])
+		return 0;
+
+	return tm6000_read_write_usb(dev, USB_DIR_OUT | USB_TYPE_VENDOR,
+				      req, value, new_index, NULL, 0);
+}
+EXPORT_SYMBOL_GPL(tm6000_set_reg_mask);
+
+int tm6000_get_reg16(struct tm6000_core *dev, u8 req, u16 value, u16 index)
+{
+	int rc;
+	u8 buf[2];
+
+	rc = tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR, req,
+					value, index, buf, 2);
+
+	if (rc < 0)
+		return rc;
+
+	return buf[1]|buf[0]<<8;
+}
+
+int tm6000_get_reg32(struct tm6000_core *dev, u8 req, u16 value, u16 index)
+{
+	int rc;
+	u8 buf[4];
+
+	rc = tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR, req,
+					value, index, buf, 4);
+
+	if (rc < 0)
+		return rc;
+
+	return buf[3] | buf[2] << 8 | buf[1] << 16 | buf[0] << 24;
+}
+
+int tm6000_i2c_reset(struct tm6000_core *dev, u16 tsleep)
+{
+	int rc;
+
+	rc = tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, TM6000_GPIO_CLK, 0);
+	if (rc < 0)
+		return rc;
+
+	msleep(tsleep);
+
+	rc = tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, TM6000_GPIO_CLK, 1);
+	msleep(tsleep);
+
+	return rc;
+}
+
+void tm6000_set_fourcc_format(struct tm6000_core *dev)
+{
+	if (dev->dev_type == TM6010) {
+		int val;
+
+		val = tm6000_get_reg(dev, TM6010_REQ07_RCC_ACTIVE_IF, 0) & 0xfc;
+		if (dev->fourcc == V4L2_PIX_FMT_UYVY)
+			tm6000_set_reg(dev, TM6010_REQ07_RCC_ACTIVE_IF, val);
+		else
+			tm6000_set_reg(dev, TM6010_REQ07_RCC_ACTIVE_IF, val | 1);
+	} else {
+		if (dev->fourcc == V4L2_PIX_FMT_UYVY)
+			tm6000_set_reg(dev, TM6010_REQ07_RC1_TRESHOLD, 0xd0);
+		else
+			tm6000_set_reg(dev, TM6010_REQ07_RC1_TRESHOLD, 0x90);
+	}
+}
+
+static void tm6000_set_vbi(struct tm6000_core *dev)
+{
+	/*
+	 * FIXME:
+	 * VBI lines and start/end are different between 60Hz and 50Hz
+	 * So, it is very likely that we need to change the config to
+	 * something that takes it into account, doing something different
+	 * if (dev->norm & V4L2_STD_525_60)
+	 */
+
+	if (dev->dev_type == TM6010) {
+		tm6000_set_reg(dev, TM6010_REQ07_R3F_RESET, 0x01);
+		tm6000_set_reg(dev, TM6010_REQ07_R41_TELETEXT_VBI_CODE1, 0x27);
+		tm6000_set_reg(dev, TM6010_REQ07_R42_VBI_DATA_HIGH_LEVEL, 0x55);
+		tm6000_set_reg(dev, TM6010_REQ07_R43_VBI_DATA_TYPE_LINE7, 0x66);
+		tm6000_set_reg(dev, TM6010_REQ07_R44_VBI_DATA_TYPE_LINE8, 0x66);
+		tm6000_set_reg(dev, TM6010_REQ07_R45_VBI_DATA_TYPE_LINE9, 0x66);
+		tm6000_set_reg(dev,
+			TM6010_REQ07_R46_VBI_DATA_TYPE_LINE10, 0x66);
+		tm6000_set_reg(dev,
+			TM6010_REQ07_R47_VBI_DATA_TYPE_LINE11, 0x66);
+		tm6000_set_reg(dev,
+			TM6010_REQ07_R48_VBI_DATA_TYPE_LINE12, 0x66);
+		tm6000_set_reg(dev,
+			TM6010_REQ07_R49_VBI_DATA_TYPE_LINE13, 0x66);
+		tm6000_set_reg(dev,
+			TM6010_REQ07_R4A_VBI_DATA_TYPE_LINE14, 0x66);
+		tm6000_set_reg(dev,
+			TM6010_REQ07_R4B_VBI_DATA_TYPE_LINE15, 0x66);
+		tm6000_set_reg(dev,
+			TM6010_REQ07_R4C_VBI_DATA_TYPE_LINE16, 0x66);
+		tm6000_set_reg(dev,
+			TM6010_REQ07_R4D_VBI_DATA_TYPE_LINE17, 0x66);
+		tm6000_set_reg(dev,
+			TM6010_REQ07_R4E_VBI_DATA_TYPE_LINE18, 0x66);
+		tm6000_set_reg(dev,
+			TM6010_REQ07_R4F_VBI_DATA_TYPE_LINE19, 0x66);
+		tm6000_set_reg(dev,
+			TM6010_REQ07_R50_VBI_DATA_TYPE_LINE20, 0x66);
+		tm6000_set_reg(dev,
+			TM6010_REQ07_R51_VBI_DATA_TYPE_LINE21, 0x66);
+		tm6000_set_reg(dev,
+			TM6010_REQ07_R52_VBI_DATA_TYPE_LINE22, 0x66);
+		tm6000_set_reg(dev,
+			TM6010_REQ07_R53_VBI_DATA_TYPE_LINE23, 0x00);
+		tm6000_set_reg(dev,
+			TM6010_REQ07_R54_VBI_DATA_TYPE_RLINES, 0x00);
+		tm6000_set_reg(dev,
+			TM6010_REQ07_R55_VBI_LOOP_FILTER_GAIN, 0x01);
+		tm6000_set_reg(dev,
+			TM6010_REQ07_R56_VBI_LOOP_FILTER_I_GAIN, 0x00);
+		tm6000_set_reg(dev,
+			TM6010_REQ07_R57_VBI_LOOP_FILTER_P_GAIN, 0x02);
+		tm6000_set_reg(dev, TM6010_REQ07_R58_VBI_CAPTION_DTO1, 0x35);
+		tm6000_set_reg(dev, TM6010_REQ07_R59_VBI_CAPTION_DTO0, 0xa0);
+		tm6000_set_reg(dev, TM6010_REQ07_R5A_VBI_TELETEXT_DTO1, 0x11);
+		tm6000_set_reg(dev, TM6010_REQ07_R5B_VBI_TELETEXT_DTO0, 0x4c);
+		tm6000_set_reg(dev, TM6010_REQ07_R40_TELETEXT_VBI_CODE0, 0x01);
+		tm6000_set_reg(dev, TM6010_REQ07_R3F_RESET, 0x00);
+	}
+}
+
+int tm6000_init_analog_mode(struct tm6000_core *dev)
+{
+	struct v4l2_frequency f;
+
+	if (dev->dev_type == TM6010) {
+		u8 active = TM6010_REQ07_RCC_ACTIVE_IF_AUDIO_ENABLE;
+
+		if (!dev->radio)
+			active |= TM6010_REQ07_RCC_ACTIVE_IF_VIDEO_ENABLE;
+
+		/* Enable video and audio */
+		tm6000_set_reg_mask(dev, TM6010_REQ07_RCC_ACTIVE_IF,
+							active, 0x60);
+		/* Disable TS input */
+		tm6000_set_reg_mask(dev, TM6010_REQ07_RC0_ACTIVE_VIDEO_SOURCE,
+							0x00, 0x40);
+	} else {
+		/* Enables soft reset */
+		tm6000_set_reg(dev, TM6010_REQ07_R3F_RESET, 0x01);
+
+		if (dev->scaler)
+			/* Disable Hfilter and Enable TS Drop err */
+			tm6000_set_reg(dev, TM6010_REQ07_RC0_ACTIVE_VIDEO_SOURCE, 0x20);
+		else	/* Enable Hfilter and disable TS Drop err */
+			tm6000_set_reg(dev, TM6010_REQ07_RC0_ACTIVE_VIDEO_SOURCE, 0x80);
+
+		tm6000_set_reg(dev, TM6010_REQ07_RC3_HSTART1, 0x88);
+		tm6000_set_reg(dev, TM6000_REQ07_RDA_CLK_SEL, 0x23);
+		tm6000_set_reg(dev, TM6010_REQ07_RD1_ADDR_FOR_REQ1, 0xc0);
+		tm6000_set_reg(dev, TM6010_REQ07_RD2_ADDR_FOR_REQ2, 0xd8);
+		tm6000_set_reg(dev, TM6010_REQ07_RD6_ENDP_REQ1_REQ2, 0x06);
+		tm6000_set_reg(dev, TM6000_REQ07_RDF_PWDOWN_ACLK, 0x1f);
+
+		/* AP Software reset */
+		tm6000_set_reg(dev, TM6010_REQ07_RFF_SOFT_RESET, 0x08);
+		tm6000_set_reg(dev, TM6010_REQ07_RFF_SOFT_RESET, 0x00);
+
+		tm6000_set_fourcc_format(dev);
+
+		/* Disables soft reset */
+		tm6000_set_reg(dev, TM6010_REQ07_R3F_RESET, 0x00);
+	}
+	msleep(20);
+
+	/* Tuner firmware can now be loaded */
+
+	/*
+	 * FIXME: This is a hack! xc3028 "sleeps" when no channel is detected
+	 * for more than a few seconds. Not sure why, as this behavior does
+	 * not happen on other devices with xc3028. So, I suspect that it
+	 * is yet another bug at tm6000. After start sleeping, decoding
+	 * doesn't start automatically. Instead, it requires some
+	 * I2C commands to wake it up. As we want to have image at the
+	 * beginning, we needed to add this hack. The better would be to
+	 * discover some way to make tm6000 to wake up without this hack.
+	 */
+	f.frequency = dev->freq;
+	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_frequency, &f);
+
+	msleep(100);
+	tm6000_set_standard(dev);
+	tm6000_set_vbi(dev);
+	tm6000_set_audio_bitrate(dev, 48000);
+
+	/* switch dvb led off */
+	if (dev->gpio.dvb_led) {
+		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+			dev->gpio.dvb_led, 0x01);
+	}
+
+	return 0;
+}
+
+int tm6000_init_digital_mode(struct tm6000_core *dev)
+{
+	if (dev->dev_type == TM6010) {
+		/* Disable video and audio */
+		tm6000_set_reg_mask(dev, TM6010_REQ07_RCC_ACTIVE_IF,
+				0x00, 0x60);
+		/* Enable TS input */
+		tm6000_set_reg_mask(dev, TM6010_REQ07_RC0_ACTIVE_VIDEO_SOURCE,
+				0x40, 0x40);
+		/* all power down, but not the digital data port */
+		tm6000_set_reg(dev, TM6010_REQ07_RFE_POWER_DOWN, 0x28);
+		tm6000_set_reg(dev, TM6010_REQ08_RE2_POWER_DOWN_CTRL1, 0xfc);
+		tm6000_set_reg(dev, TM6010_REQ08_RE6_POWER_DOWN_CTRL2, 0xff);
+	} else  {
+		tm6000_set_reg(dev, TM6010_REQ07_RFF_SOFT_RESET, 0x08);
+		tm6000_set_reg(dev, TM6010_REQ07_RFF_SOFT_RESET, 0x00);
+		tm6000_set_reg(dev, TM6010_REQ07_R3F_RESET, 0x01);
+		tm6000_set_reg(dev, TM6000_REQ07_RDF_PWDOWN_ACLK, 0x08);
+		tm6000_set_reg(dev, TM6000_REQ07_RE2_VADC_STATUS_CTL, 0x0c);
+		tm6000_set_reg(dev, TM6000_REQ07_RE8_VADC_PWDOWN_CTL, 0xff);
+		tm6000_set_reg(dev, TM6000_REQ07_REB_VADC_AADC_MODE, 0xd8);
+		tm6000_set_reg(dev, TM6010_REQ07_RC0_ACTIVE_VIDEO_SOURCE, 0x40);
+		tm6000_set_reg(dev, TM6010_REQ07_RC1_TRESHOLD, 0xd0);
+		tm6000_set_reg(dev, TM6010_REQ07_RC3_HSTART1, 0x09);
+		tm6000_set_reg(dev, TM6000_REQ07_RDA_CLK_SEL, 0x37);
+		tm6000_set_reg(dev, TM6010_REQ07_RD1_ADDR_FOR_REQ1, 0xd8);
+		tm6000_set_reg(dev, TM6010_REQ07_RD2_ADDR_FOR_REQ2, 0xc0);
+		tm6000_set_reg(dev, TM6010_REQ07_RD6_ENDP_REQ1_REQ2, 0x60);
+
+		tm6000_set_reg(dev, TM6000_REQ07_RE2_VADC_STATUS_CTL, 0x0c);
+		tm6000_set_reg(dev, TM6000_REQ07_RE8_VADC_PWDOWN_CTL, 0xff);
+		tm6000_set_reg(dev, TM6000_REQ07_REB_VADC_AADC_MODE, 0x08);
+		msleep(50);
+
+		tm6000_set_reg(dev, REQ_04_EN_DISABLE_MCU_INT, 0x0020, 0x00);
+		msleep(50);
+		tm6000_set_reg(dev, REQ_04_EN_DISABLE_MCU_INT, 0x0020, 0x01);
+		msleep(50);
+		tm6000_set_reg(dev, REQ_04_EN_DISABLE_MCU_INT, 0x0020, 0x00);
+		msleep(100);
+	}
+
+	/* switch dvb led on */
+	if (dev->gpio.dvb_led) {
+		tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN,
+			dev->gpio.dvb_led, 0x00);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(tm6000_init_digital_mode);
+
+struct reg_init {
+	u8 req;
+	u8 reg;
+	u8 val;
+};
+
+/* The meaning of those initializations are unknown */
+static struct reg_init tm6000_init_tab[] = {
+	/* REG  VALUE */
+	{ TM6000_REQ07_RDF_PWDOWN_ACLK, 0x1f },
+	{ TM6010_REQ07_RFF_SOFT_RESET, 0x08 },
+	{ TM6010_REQ07_RFF_SOFT_RESET, 0x00 },
+	{ TM6010_REQ07_RD5_POWERSAVE, 0x4f },
+	{ TM6000_REQ07_RDA_CLK_SEL, 0x23 },
+	{ TM6000_REQ07_RDB_OUT_SEL, 0x08 },
+	{ TM6000_REQ07_RE2_VADC_STATUS_CTL, 0x00 },
+	{ TM6000_REQ07_RE3_VADC_INP_LPF_SEL1, 0x10 },
+	{ TM6000_REQ07_RE5_VADC_INP_LPF_SEL2, 0x00 },
+	{ TM6000_REQ07_RE8_VADC_PWDOWN_CTL, 0x00 },
+	{ TM6000_REQ07_REB_VADC_AADC_MODE, 0x64 },	/* 48000 bits/sample, external input */
+	{ TM6000_REQ07_REE_VADC_CTRL_SEL_CONTROL, 0xc2 },
+
+	{ TM6010_REQ07_R3F_RESET, 0x01 },		/* Start of soft reset */
+	{ TM6010_REQ07_R00_VIDEO_CONTROL0, 0x00 },
+	{ TM6010_REQ07_R01_VIDEO_CONTROL1, 0x07 },
+	{ TM6010_REQ07_R02_VIDEO_CONTROL2, 0x5f },
+	{ TM6010_REQ07_R03_YC_SEP_CONTROL, 0x00 },
+	{ TM6010_REQ07_R05_NOISE_THRESHOLD, 0x64 },
+	{ TM6010_REQ07_R07_OUTPUT_CONTROL, 0x01 },
+	{ TM6010_REQ07_R08_LUMA_CONTRAST_ADJ, 0x82 },
+	{ TM6010_REQ07_R09_LUMA_BRIGHTNESS_ADJ, 0x36 },
+	{ TM6010_REQ07_R0A_CHROMA_SATURATION_ADJ, 0x50 },
+	{ TM6010_REQ07_R0C_CHROMA_AGC_CONTROL, 0x6a },
+	{ TM6010_REQ07_R11_AGC_PEAK_CONTROL, 0xc9 },
+	{ TM6010_REQ07_R12_AGC_GATE_STARTH, 0x07 },
+	{ TM6010_REQ07_R13_AGC_GATE_STARTL, 0x3b },
+	{ TM6010_REQ07_R14_AGC_GATE_WIDTH, 0x47 },
+	{ TM6010_REQ07_R15_AGC_BP_DELAY, 0x6f },
+	{ TM6010_REQ07_R17_HLOOP_MAXSTATE, 0xcd },
+	{ TM6010_REQ07_R18_CHROMA_DTO_INCREMENT3, 0x1e },
+	{ TM6010_REQ07_R19_CHROMA_DTO_INCREMENT2, 0x8b },
+	{ TM6010_REQ07_R1A_CHROMA_DTO_INCREMENT1, 0xa2 },
+	{ TM6010_REQ07_R1B_CHROMA_DTO_INCREMENT0, 0xe9 },
+	{ TM6010_REQ07_R1C_HSYNC_DTO_INCREMENT3, 0x1c },
+	{ TM6010_REQ07_R1D_HSYNC_DTO_INCREMENT2, 0xcc },
+	{ TM6010_REQ07_R1E_HSYNC_DTO_INCREMENT1, 0xcc },
+	{ TM6010_REQ07_R1F_HSYNC_DTO_INCREMENT0, 0xcd },
+	{ TM6010_REQ07_R20_HSYNC_RISING_EDGE_TIME, 0x3c },
+	{ TM6010_REQ07_R21_HSYNC_PHASE_OFFSET, 0x3c },
+	{ TM6010_REQ07_R2D_CHROMA_BURST_END, 0x48 },
+	{ TM6010_REQ07_R2E_ACTIVE_VIDEO_HSTART, 0x88 },
+	{ TM6010_REQ07_R30_ACTIVE_VIDEO_VSTART, 0x22 },
+	{ TM6010_REQ07_R31_ACTIVE_VIDEO_VHIGHT, 0x61 },
+	{ TM6010_REQ07_R32_VSYNC_HLOCK_MIN, 0x74 },
+	{ TM6010_REQ07_R33_VSYNC_HLOCK_MAX, 0x1c },
+	{ TM6010_REQ07_R34_VSYNC_AGC_MIN, 0x74 },
+	{ TM6010_REQ07_R35_VSYNC_AGC_MAX, 0x1c },
+	{ TM6010_REQ07_R36_VSYNC_VBI_MIN, 0x7a },
+	{ TM6010_REQ07_R37_VSYNC_VBI_MAX, 0x26 },
+	{ TM6010_REQ07_R38_VSYNC_THRESHOLD, 0x40 },
+	{ TM6010_REQ07_R39_VSYNC_TIME_CONSTANT, 0x0a },
+	{ TM6010_REQ07_R42_VBI_DATA_HIGH_LEVEL, 0x55 },
+	{ TM6010_REQ07_R51_VBI_DATA_TYPE_LINE21, 0x11 },
+	{ TM6010_REQ07_R55_VBI_LOOP_FILTER_GAIN, 0x01 },
+	{ TM6010_REQ07_R57_VBI_LOOP_FILTER_P_GAIN, 0x02 },
+	{ TM6010_REQ07_R58_VBI_CAPTION_DTO1, 0x35 },
+	{ TM6010_REQ07_R59_VBI_CAPTION_DTO0, 0xa0 },
+	{ TM6010_REQ07_R80_COMB_FILTER_TRESHOLD, 0x15 },
+	{ TM6010_REQ07_R82_COMB_FILTER_CONFIG, 0x42 },
+	{ TM6010_REQ07_RC1_TRESHOLD, 0xd0 },
+	{ TM6010_REQ07_RC3_HSTART1, 0x88 },
+	{ TM6010_REQ07_R3F_RESET, 0x00 },		/* End of the soft reset */
+	{ TM6010_REQ05_R18_IMASK7, 0x00 },
+};
+
+static struct reg_init tm6010_init_tab[] = {
+	{ TM6010_REQ07_RC0_ACTIVE_VIDEO_SOURCE, 0x00 },
+	{ TM6010_REQ07_RC4_HSTART0, 0xa0 },
+	{ TM6010_REQ07_RC6_HEND0, 0x40 },
+	{ TM6010_REQ07_RCA_VEND0, 0x31 },
+	{ TM6010_REQ07_RCC_ACTIVE_IF, 0xe1 },
+	{ TM6010_REQ07_RE0_DVIDEO_SOURCE, 0x03 },
+	{ TM6010_REQ07_RFE_POWER_DOWN, 0x7f },
+
+	{ TM6010_REQ08_RE2_POWER_DOWN_CTRL1, 0xf0 },
+	{ TM6010_REQ08_RE3_ADC_IN1_SEL, 0xf4 },
+	{ TM6010_REQ08_RE4_ADC_IN2_SEL, 0xf8 },
+	{ TM6010_REQ08_RE6_POWER_DOWN_CTRL2, 0x00 },
+	{ TM6010_REQ08_REA_BUFF_DRV_CTRL, 0xf2 },
+	{ TM6010_REQ08_REB_SIF_GAIN_CTRL, 0xf0 },
+	{ TM6010_REQ08_REC_REVERSE_YC_CTRL, 0xc2 },
+	{ TM6010_REQ08_RF0_DAUDIO_INPUT_CONFIG, 0x60 },
+	{ TM6010_REQ08_RF1_AADC_POWER_DOWN, 0xfc },
+
+	{ TM6010_REQ07_R3F_RESET, 0x01 },
+	{ TM6010_REQ07_R00_VIDEO_CONTROL0, 0x00 },
+	{ TM6010_REQ07_R01_VIDEO_CONTROL1, 0x07 },
+	{ TM6010_REQ07_R02_VIDEO_CONTROL2, 0x5f },
+	{ TM6010_REQ07_R03_YC_SEP_CONTROL, 0x00 },
+	{ TM6010_REQ07_R05_NOISE_THRESHOLD, 0x64 },
+	{ TM6010_REQ07_R07_OUTPUT_CONTROL, 0x01 },
+	{ TM6010_REQ07_R08_LUMA_CONTRAST_ADJ, 0x82 },
+	{ TM6010_REQ07_R09_LUMA_BRIGHTNESS_ADJ, 0x36 },
+	{ TM6010_REQ07_R0A_CHROMA_SATURATION_ADJ, 0x50 },
+	{ TM6010_REQ07_R0C_CHROMA_AGC_CONTROL, 0x6a },
+	{ TM6010_REQ07_R11_AGC_PEAK_CONTROL, 0xc9 },
+	{ TM6010_REQ07_R12_AGC_GATE_STARTH, 0x07 },
+	{ TM6010_REQ07_R13_AGC_GATE_STARTL, 0x3b },
+	{ TM6010_REQ07_R14_AGC_GATE_WIDTH, 0x47 },
+	{ TM6010_REQ07_R15_AGC_BP_DELAY, 0x6f },
+	{ TM6010_REQ07_R17_HLOOP_MAXSTATE, 0xcd },
+	{ TM6010_REQ07_R18_CHROMA_DTO_INCREMENT3, 0x1e },
+	{ TM6010_REQ07_R19_CHROMA_DTO_INCREMENT2, 0x8b },
+	{ TM6010_REQ07_R1A_CHROMA_DTO_INCREMENT1, 0xa2 },
+	{ TM6010_REQ07_R1B_CHROMA_DTO_INCREMENT0, 0xe9 },
+	{ TM6010_REQ07_R1C_HSYNC_DTO_INCREMENT3, 0x1c },
+	{ TM6010_REQ07_R1D_HSYNC_DTO_INCREMENT2, 0xcc },
+	{ TM6010_REQ07_R1E_HSYNC_DTO_INCREMENT1, 0xcc },
+	{ TM6010_REQ07_R1F_HSYNC_DTO_INCREMENT0, 0xcd },
+	{ TM6010_REQ07_R20_HSYNC_RISING_EDGE_TIME, 0x3c },
+	{ TM6010_REQ07_R21_HSYNC_PHASE_OFFSET, 0x3c },
+	{ TM6010_REQ07_R2D_CHROMA_BURST_END, 0x48 },
+	{ TM6010_REQ07_R2E_ACTIVE_VIDEO_HSTART, 0x88 },
+	{ TM6010_REQ07_R30_ACTIVE_VIDEO_VSTART, 0x22 },
+	{ TM6010_REQ07_R31_ACTIVE_VIDEO_VHIGHT, 0x61 },
+	{ TM6010_REQ07_R32_VSYNC_HLOCK_MIN, 0x74 },
+	{ TM6010_REQ07_R33_VSYNC_HLOCK_MAX, 0x1c },
+	{ TM6010_REQ07_R34_VSYNC_AGC_MIN, 0x74 },
+	{ TM6010_REQ07_R35_VSYNC_AGC_MAX, 0x1c },
+	{ TM6010_REQ07_R36_VSYNC_VBI_MIN, 0x7a },
+	{ TM6010_REQ07_R37_VSYNC_VBI_MAX, 0x26 },
+	{ TM6010_REQ07_R38_VSYNC_THRESHOLD, 0x40 },
+	{ TM6010_REQ07_R39_VSYNC_TIME_CONSTANT, 0x0a },
+	{ TM6010_REQ07_R42_VBI_DATA_HIGH_LEVEL, 0x55 },
+	{ TM6010_REQ07_R51_VBI_DATA_TYPE_LINE21, 0x11 },
+	{ TM6010_REQ07_R55_VBI_LOOP_FILTER_GAIN, 0x01 },
+	{ TM6010_REQ07_R57_VBI_LOOP_FILTER_P_GAIN, 0x02 },
+	{ TM6010_REQ07_R58_VBI_CAPTION_DTO1, 0x35 },
+	{ TM6010_REQ07_R59_VBI_CAPTION_DTO0, 0xa0 },
+	{ TM6010_REQ07_R80_COMB_FILTER_TRESHOLD, 0x15 },
+	{ TM6010_REQ07_R82_COMB_FILTER_CONFIG, 0x42 },
+	{ TM6010_REQ07_RC1_TRESHOLD, 0xd0 },
+	{ TM6010_REQ07_RC3_HSTART1, 0x88 },
+	{ TM6010_REQ07_R3F_RESET, 0x00 },
+
+	{ TM6010_REQ05_R18_IMASK7, 0x00 },
+
+	{ TM6010_REQ07_RDC_IR_LEADER1, 0xaa },
+	{ TM6010_REQ07_RDD_IR_LEADER0, 0x30 },
+	{ TM6010_REQ07_RDE_IR_PULSE_CNT1, 0x20 },
+	{ TM6010_REQ07_RDF_IR_PULSE_CNT0, 0xd0 },
+	{ REQ_04_EN_DISABLE_MCU_INT, 0x02, 0x00 },
+	{ TM6010_REQ07_RD8_IR, 0x0f },
+
+	/* set remote wakeup key:any key wakeup */
+	{ TM6010_REQ07_RE5_REMOTE_WAKEUP,  0xfe },
+	{ TM6010_REQ07_RDA_IR_WAKEUP_SEL,  0xff },
+};
+
+int tm6000_init(struct tm6000_core *dev)
+{
+	int board, rc = 0, i, size;
+	struct reg_init *tab;
+
+	/* Check board revision */
+	board = tm6000_get_reg32(dev, REQ_40_GET_VERSION, 0, 0);
+	if (board >= 0) {
+		switch (board & 0xff) {
+		case 0xf3:
+			printk(KERN_INFO "Found tm6000\n");
+			if (dev->dev_type != TM6000)
+				dev->dev_type = TM6000;
+			break;
+		case 0xf4:
+			printk(KERN_INFO "Found tm6010\n");
+			if (dev->dev_type != TM6010)
+				dev->dev_type = TM6010;
+			break;
+		default:
+			printk(KERN_INFO "Unknown board version = 0x%08x\n", board);
+		}
+	} else
+		printk(KERN_ERR "Error %i while retrieving board version\n", board);
+
+	if (dev->dev_type == TM6010) {
+		tab = tm6010_init_tab;
+		size = ARRAY_SIZE(tm6010_init_tab);
+	} else {
+		tab = tm6000_init_tab;
+		size = ARRAY_SIZE(tm6000_init_tab);
+	}
+
+	/* Load board's initialization table */
+	for (i = 0; i < size; i++) {
+		rc = tm6000_set_reg(dev, tab[i].req, tab[i].reg, tab[i].val);
+		if (rc < 0) {
+			printk(KERN_ERR "Error %i while setting req %d, reg %d to value %d\n",
+			       rc,
+					tab[i].req, tab[i].reg, tab[i].val);
+			return rc;
+		}
+	}
+
+	msleep(5); /* Just to be conservative */
+
+	rc = tm6000_cards_setup(dev);
+
+	return rc;
+}
+
+
+int tm6000_set_audio_bitrate(struct tm6000_core *dev, int bitrate)
+{
+	int val = 0;
+	u8 areg_f0 = 0x60; /* ADC MCLK = 250 Fs */
+	u8 areg_0a = 0x91; /* SIF 48KHz */
+
+	switch (bitrate) {
+	case 48000:
+		areg_f0 = 0x60; /* ADC MCLK = 250 Fs */
+		areg_0a = 0x91; /* SIF 48KHz */
+		dev->audio_bitrate = bitrate;
+		break;
+	case 32000:
+		areg_f0 = 0x00; /* ADC MCLK = 375 Fs */
+		areg_0a = 0x90; /* SIF 32KHz */
+		dev->audio_bitrate = bitrate;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+
+	/* enable I2S, if we use sif or external I2S device */
+	if (dev->dev_type == TM6010) {
+		val = tm6000_set_reg(dev, TM6010_REQ08_R0A_A_I2S_MOD, areg_0a);
+		if (val < 0)
+			return val;
+
+		val = tm6000_set_reg_mask(dev, TM6010_REQ08_RF0_DAUDIO_INPUT_CONFIG,
+							areg_f0, 0xf0);
+		if (val < 0)
+			return val;
+	} else {
+		val = tm6000_set_reg_mask(dev, TM6000_REQ07_REB_VADC_AADC_MODE,
+							areg_f0, 0xf0);
+		if (val < 0)
+			return val;
+	}
+	return 0;
+}
+EXPORT_SYMBOL_GPL(tm6000_set_audio_bitrate);
+
+int tm6000_set_audio_rinput(struct tm6000_core *dev)
+{
+	if (dev->dev_type == TM6010) {
+		/* Audio crossbar setting, default SIF1 */
+		u8 areg_f0;
+		u8 areg_07 = 0x10;
+
+		switch (dev->rinput.amux) {
+		case TM6000_AMUX_SIF1:
+		case TM6000_AMUX_SIF2:
+			areg_f0 = 0x03;
+			areg_07 = 0x30;
+			break;
+		case TM6000_AMUX_ADC1:
+			areg_f0 = 0x00;
+			break;
+		case TM6000_AMUX_ADC2:
+			areg_f0 = 0x08;
+			break;
+		case TM6000_AMUX_I2S:
+			areg_f0 = 0x04;
+			break;
+		default:
+			printk(KERN_INFO "%s: audio input dosn't support\n",
+				dev->name);
+			return 0;
+			break;
+		}
+		/* Set audio input crossbar */
+		tm6000_set_reg_mask(dev, TM6010_REQ08_RF0_DAUDIO_INPUT_CONFIG,
+							areg_f0, 0x0f);
+		/* Mux overflow workaround */
+		tm6000_set_reg_mask(dev, TM6010_REQ07_R07_OUTPUT_CONTROL,
+			areg_07, 0xf0);
+	} else {
+		u8 areg_eb;
+		/* Audio setting, default LINE1 */
+		switch (dev->rinput.amux) {
+		case TM6000_AMUX_ADC1:
+			areg_eb = 0x00;
+			break;
+		case TM6000_AMUX_ADC2:
+			areg_eb = 0x04;
+			break;
+		default:
+			printk(KERN_INFO "%s: audio input dosn't support\n",
+				dev->name);
+			return 0;
+			break;
+		}
+		/* Set audio input */
+		tm6000_set_reg_mask(dev, TM6000_REQ07_REB_VADC_AADC_MODE,
+							areg_eb, 0x0f);
+	}
+	return 0;
+}
+
+static void tm6010_set_mute_sif(struct tm6000_core *dev, u8 mute)
+{
+	u8 mute_reg = 0;
+
+	if (mute)
+		mute_reg = 0x08;
+
+	tm6000_set_reg_mask(dev, TM6010_REQ08_R0A_A_I2S_MOD, mute_reg, 0x08);
+}
+
+static void tm6010_set_mute_adc(struct tm6000_core *dev, u8 mute)
+{
+	u8 mute_reg = 0;
+
+	if (mute)
+		mute_reg = 0x20;
+
+	if (dev->dev_type == TM6010) {
+		tm6000_set_reg_mask(dev, TM6010_REQ08_RF2_LEFT_CHANNEL_VOL,
+							mute_reg, 0x20);
+		tm6000_set_reg_mask(dev, TM6010_REQ08_RF3_RIGHT_CHANNEL_VOL,
+							mute_reg, 0x20);
+	} else {
+		tm6000_set_reg_mask(dev, TM6000_REQ07_REC_VADC_AADC_LVOL,
+							mute_reg, 0x20);
+		tm6000_set_reg_mask(dev, TM6000_REQ07_RED_VADC_AADC_RVOL,
+							mute_reg, 0x20);
+	}
+}
+
+int tm6000_tvaudio_set_mute(struct tm6000_core *dev, u8 mute)
+{
+	enum tm6000_mux mux;
+
+	if (dev->radio)
+		mux = dev->rinput.amux;
+	else
+		mux = dev->vinput[dev->input].amux;
+
+	switch (mux) {
+	case TM6000_AMUX_SIF1:
+	case TM6000_AMUX_SIF2:
+		if (dev->dev_type == TM6010)
+			tm6010_set_mute_sif(dev, mute);
+		else {
+			printk(KERN_INFO "ERROR: TM5600 and TM6000 don't has SIF audio inputs. Please check the %s configuration.\n",
+			       dev->name);
+			return -EINVAL;
+		}
+		break;
+	case TM6000_AMUX_ADC1:
+	case TM6000_AMUX_ADC2:
+		tm6010_set_mute_adc(dev, mute);
+		break;
+	default:
+		return -EINVAL;
+		break;
+	}
+	return 0;
+}
+
+static void tm6010_set_volume_sif(struct tm6000_core *dev, int vol)
+{
+	u8 vol_reg;
+
+	vol_reg = vol & 0x0F;
+
+	if (vol < 0)
+		vol_reg |= 0x40;
+
+	tm6000_set_reg(dev, TM6010_REQ08_R07_A_LEFT_VOL, vol_reg);
+	tm6000_set_reg(dev, TM6010_REQ08_R08_A_RIGHT_VOL, vol_reg);
+}
+
+static void tm6010_set_volume_adc(struct tm6000_core *dev, int vol)
+{
+	u8 vol_reg;
+
+	vol_reg = (vol + 0x10) & 0x1f;
+
+	if (dev->dev_type == TM6010) {
+		tm6000_set_reg(dev, TM6010_REQ08_RF2_LEFT_CHANNEL_VOL, vol_reg);
+		tm6000_set_reg(dev, TM6010_REQ08_RF3_RIGHT_CHANNEL_VOL, vol_reg);
+	} else {
+		tm6000_set_reg(dev, TM6000_REQ07_REC_VADC_AADC_LVOL, vol_reg);
+		tm6000_set_reg(dev, TM6000_REQ07_RED_VADC_AADC_RVOL, vol_reg);
+	}
+}
+
+void tm6000_set_volume(struct tm6000_core *dev, int vol)
+{
+	enum tm6000_mux mux;
+
+	if (dev->radio) {
+		mux = dev->rinput.amux;
+		vol += 8; /* Offset to 0 dB */
+	} else
+		mux = dev->vinput[dev->input].amux;
+
+	switch (mux) {
+	case TM6000_AMUX_SIF1:
+	case TM6000_AMUX_SIF2:
+		if (dev->dev_type == TM6010)
+			tm6010_set_volume_sif(dev, vol);
+		else
+			printk(KERN_INFO "ERROR: TM5600 and TM6000 don't has SIF audio inputs. Please check the %s configuration.\n",
+			       dev->name);
+		break;
+	case TM6000_AMUX_ADC1:
+	case TM6000_AMUX_ADC2:
+		tm6010_set_volume_adc(dev, vol);
+		break;
+	default:
+		break;
+	}
+}
+
+static LIST_HEAD(tm6000_devlist);
+static DEFINE_MUTEX(tm6000_devlist_mutex);
+
+/*
+ * tm6000_realease_resource()
+ */
+
+void tm6000_remove_from_devlist(struct tm6000_core *dev)
+{
+	mutex_lock(&tm6000_devlist_mutex);
+	list_del(&dev->devlist);
+	mutex_unlock(&tm6000_devlist_mutex);
+};
+
+void tm6000_add_into_devlist(struct tm6000_core *dev)
+{
+	mutex_lock(&tm6000_devlist_mutex);
+	list_add_tail(&dev->devlist, &tm6000_devlist);
+	mutex_unlock(&tm6000_devlist_mutex);
+};
+
+/*
+ * Extension interface
+ */
+
+static LIST_HEAD(tm6000_extension_devlist);
+
+int tm6000_call_fillbuf(struct tm6000_core *dev, enum tm6000_ops_type type,
+			char *buf, int size)
+{
+	struct tm6000_ops *ops = NULL;
+
+	/* FIXME: tm6000_extension_devlist_lock should be a spinlock */
+
+	if (!list_empty(&tm6000_extension_devlist)) {
+		list_for_each_entry(ops, &tm6000_extension_devlist, next) {
+			if (ops->fillbuf && ops->type == type)
+				ops->fillbuf(dev, buf, size);
+		}
+	}
+
+	return 0;
+}
+
+int tm6000_register_extension(struct tm6000_ops *ops)
+{
+	struct tm6000_core *dev = NULL;
+
+	mutex_lock(&tm6000_devlist_mutex);
+	list_add_tail(&ops->next, &tm6000_extension_devlist);
+	list_for_each_entry(dev, &tm6000_devlist, devlist) {
+		ops->init(dev);
+		printk(KERN_INFO "%s: Initialized (%s) extension\n",
+		       dev->name, ops->name);
+	}
+	mutex_unlock(&tm6000_devlist_mutex);
+	return 0;
+}
+EXPORT_SYMBOL(tm6000_register_extension);
+
+void tm6000_unregister_extension(struct tm6000_ops *ops)
+{
+	struct tm6000_core *dev = NULL;
+
+	mutex_lock(&tm6000_devlist_mutex);
+	list_for_each_entry(dev, &tm6000_devlist, devlist)
+		ops->fini(dev);
+
+	printk(KERN_INFO "tm6000: Remove (%s) extension\n", ops->name);
+	list_del(&ops->next);
+	mutex_unlock(&tm6000_devlist_mutex);
+}
+EXPORT_SYMBOL(tm6000_unregister_extension);
+
+void tm6000_init_extension(struct tm6000_core *dev)
+{
+	struct tm6000_ops *ops = NULL;
+
+	mutex_lock(&tm6000_devlist_mutex);
+	if (!list_empty(&tm6000_extension_devlist)) {
+		list_for_each_entry(ops, &tm6000_extension_devlist, next) {
+			if (ops->init)
+				ops->init(dev);
+		}
+	}
+	mutex_unlock(&tm6000_devlist_mutex);
+}
+
+void tm6000_close_extension(struct tm6000_core *dev)
+{
+	struct tm6000_ops *ops = NULL;
+
+	mutex_lock(&tm6000_devlist_mutex);
+	if (!list_empty(&tm6000_extension_devlist)) {
+		list_for_each_entry(ops, &tm6000_extension_devlist, next) {
+			if (ops->fini)
+				ops->fini(dev);
+		}
+	}
+	mutex_unlock(&tm6000_devlist_mutex);
+}
diff --git a/drivers/media/usb/tm6000/tm6000-dvb.c b/drivers/media/usb/tm6000/tm6000-dvb.c
new file mode 100644
index 0000000..3a4e545
--- /dev/null
+++ b/drivers/media/usb/tm6000/tm6000-dvb.c
@@ -0,0 +1,457 @@
+/*
+ *  tm6000-dvb.c - dvb-t support for TM5600/TM6000/TM6010 USB video capture devices
+ *
+ *  Copyright (C) 2007 Michel Ludwig <michel.ludwig@gmail.com>
+ *
+ *  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 version 2
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+
+#include "tm6000.h"
+#include "tm6000-regs.h"
+
+#include "zl10353.h"
+
+#include <media/tuner.h>
+
+#include "tuner-xc2028.h"
+#include "xc5000.h"
+
+MODULE_DESCRIPTION("DVB driver extension module for tm5600/6000/6010 based TV cards");
+MODULE_AUTHOR("Mauro Carvalho Chehab");
+MODULE_LICENSE("GPL");
+
+MODULE_SUPPORTED_DEVICE("{{Trident, tm5600},{{Trident, tm6000},{{Trident, tm6010}");
+
+static int debug;
+
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "enable debug message");
+
+static inline void print_err_status(struct tm6000_core *dev,
+				    int packet, int status)
+{
+	char *errmsg = "Unknown";
+
+	switch (status) {
+	case -ENOENT:
+		errmsg = "unlinked synchronously";
+		break;
+	case -ECONNRESET:
+		errmsg = "unlinked asynchronously";
+		break;
+	case -ENOSR:
+		errmsg = "Buffer error (overrun)";
+		break;
+	case -EPIPE:
+		errmsg = "Stalled (device not responding)";
+		break;
+	case -EOVERFLOW:
+		errmsg = "Babble (bad cable?)";
+		break;
+	case -EPROTO:
+		errmsg = "Bit-stuff error (bad cable?)";
+		break;
+	case -EILSEQ:
+		errmsg = "CRC/Timeout (could be anything)";
+		break;
+	case -ETIME:
+		errmsg = "Device does not respond";
+		break;
+	}
+	if (packet < 0) {
+		dprintk(dev, 1, "URB status %d [%s].\n",
+			status, errmsg);
+	} else {
+		dprintk(dev, 1, "URB packet %d, status %d [%s].\n",
+			packet, status, errmsg);
+	}
+}
+
+static void tm6000_urb_received(struct urb *urb)
+{
+	int ret;
+	struct tm6000_core *dev = urb->context;
+
+	switch (urb->status) {
+	case 0:
+	case -ETIMEDOUT:
+		break;
+	case -ENOENT:
+	case -ECONNRESET:
+	case -ESHUTDOWN:
+		return;
+	default:
+		print_err_status(dev, 0, urb->status);
+	}
+
+	if (urb->actual_length > 0)
+		dvb_dmx_swfilter(&dev->dvb->demux, urb->transfer_buffer,
+						   urb->actual_length);
+
+	if (dev->dvb->streams > 0) {
+		ret = usb_submit_urb(urb, GFP_ATOMIC);
+		if (ret < 0) {
+			printk(KERN_ERR "tm6000:  error %s\n", __func__);
+			kfree(urb->transfer_buffer);
+			usb_free_urb(urb);
+		}
+	}
+}
+
+static int tm6000_start_stream(struct tm6000_core *dev)
+{
+	int ret;
+	unsigned int pipe, size;
+	struct tm6000_dvb *dvb = dev->dvb;
+
+	printk(KERN_INFO "tm6000: got start stream request %s\n", __func__);
+
+	if (dev->mode != TM6000_MODE_DIGITAL) {
+		tm6000_init_digital_mode(dev);
+		dev->mode = TM6000_MODE_DIGITAL;
+	}
+
+	dvb->bulk_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!dvb->bulk_urb)
+		return -ENOMEM;
+
+	pipe = usb_rcvbulkpipe(dev->udev, dev->bulk_in.endp->desc.bEndpointAddress
+							  & USB_ENDPOINT_NUMBER_MASK);
+
+	size = usb_maxpacket(dev->udev, pipe, usb_pipeout(pipe));
+	size = size * 15; /* 512 x 8 or 12 or 15 */
+
+	dvb->bulk_urb->transfer_buffer = kzalloc(size, GFP_KERNEL);
+	if (!dvb->bulk_urb->transfer_buffer) {
+		usb_free_urb(dvb->bulk_urb);
+		return -ENOMEM;
+	}
+
+	usb_fill_bulk_urb(dvb->bulk_urb, dev->udev, pipe,
+						 dvb->bulk_urb->transfer_buffer,
+						 size,
+						 tm6000_urb_received, dev);
+
+	ret = usb_clear_halt(dev->udev, pipe);
+	if (ret < 0) {
+		printk(KERN_ERR "tm6000: error %i in %s during pipe reset\n",
+							ret, __func__);
+		return ret;
+	} else
+		printk(KERN_ERR "tm6000: pipe resetted\n");
+
+/*	mutex_lock(&tm6000_driver.open_close_mutex); */
+	ret = usb_submit_urb(dvb->bulk_urb, GFP_ATOMIC);
+
+/*	mutex_unlock(&tm6000_driver.open_close_mutex); */
+	if (ret) {
+		printk(KERN_ERR "tm6000: submit of urb failed (error=%i)\n",
+									ret);
+
+		kfree(dvb->bulk_urb->transfer_buffer);
+		usb_free_urb(dvb->bulk_urb);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void tm6000_stop_stream(struct tm6000_core *dev)
+{
+	struct tm6000_dvb *dvb = dev->dvb;
+
+	if (dvb->bulk_urb) {
+		printk(KERN_INFO "urb killing\n");
+		usb_kill_urb(dvb->bulk_urb);
+		printk(KERN_INFO "urb buffer free\n");
+		kfree(dvb->bulk_urb->transfer_buffer);
+		usb_free_urb(dvb->bulk_urb);
+		dvb->bulk_urb = NULL;
+	}
+}
+
+static int tm6000_start_feed(struct dvb_demux_feed *feed)
+{
+	struct dvb_demux *demux = feed->demux;
+	struct tm6000_core *dev = demux->priv;
+	struct tm6000_dvb *dvb = dev->dvb;
+	printk(KERN_INFO "tm6000: got start feed request %s\n", __func__);
+
+	mutex_lock(&dvb->mutex);
+	if (dvb->streams == 0) {
+		dvb->streams = 1;
+/*		mutex_init(&tm6000_dev->streming_mutex); */
+		tm6000_start_stream(dev);
+	} else
+		++(dvb->streams);
+	mutex_unlock(&dvb->mutex);
+
+	return 0;
+}
+
+static int tm6000_stop_feed(struct dvb_demux_feed *feed)
+{
+	struct dvb_demux *demux = feed->demux;
+	struct tm6000_core *dev = demux->priv;
+	struct tm6000_dvb *dvb = dev->dvb;
+
+	printk(KERN_INFO "tm6000: got stop feed request %s\n", __func__);
+
+	mutex_lock(&dvb->mutex);
+
+	printk(KERN_INFO "stream %#x\n", dvb->streams);
+	--(dvb->streams);
+	if (dvb->streams == 0) {
+		printk(KERN_INFO "stop stream\n");
+		tm6000_stop_stream(dev);
+/*		mutex_destroy(&tm6000_dev->streaming_mutex); */
+	}
+	mutex_unlock(&dvb->mutex);
+/*	mutex_destroy(&tm6000_dev->streaming_mutex); */
+
+	return 0;
+}
+
+static int tm6000_dvb_attach_frontend(struct tm6000_core *dev)
+{
+	struct tm6000_dvb *dvb = dev->dvb;
+
+	if (dev->caps.has_zl10353) {
+		struct zl10353_config config = {
+				     .demod_address = dev->demod_addr,
+				     .no_tuner = 1,
+				     .parallel_ts = 1,
+				     .if2 = 45700,
+				     .disable_i2c_gate_ctrl = 1,
+				    };
+
+		dvb->frontend = dvb_attach(zl10353_attach, &config,
+							   &dev->i2c_adap);
+	} else {
+		printk(KERN_ERR "tm6000: no frontend defined for the device!\n");
+		return -1;
+	}
+
+	return (!dvb->frontend) ? -1 : 0;
+}
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+static int register_dvb(struct tm6000_core *dev)
+{
+	int ret = -1;
+	struct tm6000_dvb *dvb = dev->dvb;
+
+	mutex_init(&dvb->mutex);
+
+	dvb->streams = 0;
+
+	/* attach the frontend */
+	ret = tm6000_dvb_attach_frontend(dev);
+	if (ret < 0) {
+		printk(KERN_ERR "tm6000: couldn't attach the frontend!\n");
+		goto err;
+	}
+
+	ret = dvb_register_adapter(&dvb->adapter, "Trident TVMaster 6000 DVB-T",
+					THIS_MODULE, &dev->udev->dev, adapter_nr);
+	if (ret < 0) {
+		pr_err("tm6000: couldn't register the adapter!\n");
+		goto err;
+	}
+
+	dvb->adapter.priv = dev;
+
+	if (dvb->frontend) {
+		switch (dev->tuner_type) {
+		case TUNER_XC2028: {
+			struct xc2028_config cfg = {
+				.i2c_adap = &dev->i2c_adap,
+				.i2c_addr = dev->tuner_addr,
+			};
+
+			dvb->frontend->callback = tm6000_tuner_callback;
+			ret = dvb_register_frontend(&dvb->adapter, dvb->frontend);
+			if (ret < 0) {
+				printk(KERN_ERR
+					"tm6000: couldn't register frontend\n");
+				goto adapter_err;
+			}
+
+			if (!dvb_attach(xc2028_attach, dvb->frontend, &cfg)) {
+				printk(KERN_ERR "tm6000: couldn't register frontend (xc3028)\n");
+				ret = -EINVAL;
+				goto frontend_err;
+			}
+			printk(KERN_INFO "tm6000: XC2028/3028 asked to be attached to frontend!\n");
+			break;
+			}
+		case TUNER_XC5000: {
+			struct xc5000_config cfg = {
+				.i2c_address = dev->tuner_addr,
+			};
+
+			dvb->frontend->callback = tm6000_xc5000_callback;
+			ret = dvb_register_frontend(&dvb->adapter, dvb->frontend);
+			if (ret < 0) {
+				printk(KERN_ERR
+					"tm6000: couldn't register frontend\n");
+				goto adapter_err;
+			}
+
+			if (!dvb_attach(xc5000_attach, dvb->frontend, &dev->i2c_adap, &cfg)) {
+				printk(KERN_ERR "tm6000: couldn't register frontend (xc5000)\n");
+				ret = -EINVAL;
+				goto frontend_err;
+			}
+			printk(KERN_INFO "tm6000: XC5000 asked to be attached to frontend!\n");
+			break;
+			}
+		}
+	} else
+		printk(KERN_ERR "tm6000: no frontend found\n");
+
+	dvb->demux.dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING
+							    | DMX_MEMORY_BASED_FILTERING;
+	dvb->demux.priv = dev;
+	dvb->demux.filternum = 8;
+	dvb->demux.feednum = 8;
+	dvb->demux.start_feed = tm6000_start_feed;
+	dvb->demux.stop_feed = tm6000_stop_feed;
+	dvb->demux.write_to_decoder = NULL;
+	ret = dvb_dmx_init(&dvb->demux);
+	if (ret < 0) {
+		printk(KERN_ERR "tm6000: dvb_dmx_init failed (errno = %d)\n", ret);
+		goto frontend_err;
+	}
+
+	dvb->dmxdev.filternum = dev->dvb->demux.filternum;
+	dvb->dmxdev.demux = &dev->dvb->demux.dmx;
+	dvb->dmxdev.capabilities = 0;
+
+	ret =  dvb_dmxdev_init(&dvb->dmxdev, &dvb->adapter);
+	if (ret < 0) {
+		printk(KERN_ERR "tm6000: dvb_dmxdev_init failed (errno = %d)\n", ret);
+		goto dvb_dmx_err;
+	}
+
+	return 0;
+
+dvb_dmx_err:
+	dvb_dmx_release(&dvb->demux);
+frontend_err:
+	if (dvb->frontend) {
+		dvb_unregister_frontend(dvb->frontend);
+		dvb_frontend_detach(dvb->frontend);
+	}
+adapter_err:
+	dvb_unregister_adapter(&dvb->adapter);
+err:
+	return ret;
+}
+
+static void unregister_dvb(struct tm6000_core *dev)
+{
+	struct tm6000_dvb *dvb = dev->dvb;
+
+	if (dvb->bulk_urb) {
+		struct urb *bulk_urb = dvb->bulk_urb;
+
+		kfree(bulk_urb->transfer_buffer);
+		bulk_urb->transfer_buffer = NULL;
+		usb_unlink_urb(bulk_urb);
+		usb_free_urb(bulk_urb);
+	}
+
+/*	mutex_lock(&tm6000_driver.open_close_mutex); */
+	if (dvb->frontend) {
+		dvb_unregister_frontend(dvb->frontend);
+		dvb_frontend_detach(dvb->frontend);
+	}
+
+	dvb_dmxdev_release(&dvb->dmxdev);
+	dvb_dmx_release(&dvb->demux);
+	dvb_unregister_adapter(&dvb->adapter);
+	mutex_destroy(&dvb->mutex);
+/*	mutex_unlock(&tm6000_driver.open_close_mutex); */
+}
+
+static int dvb_init(struct tm6000_core *dev)
+{
+	struct tm6000_dvb *dvb;
+	int rc;
+
+	if (!dev)
+		return 0;
+
+	if (!dev->caps.has_dvb)
+		return 0;
+
+	if (dev->udev->speed == USB_SPEED_FULL) {
+		printk(KERN_INFO "This USB2.0 device cannot be run on a USB1.1 port. (it lacks a hardware PID filter)\n");
+		return 0;
+	}
+
+	dvb = kzalloc(sizeof(struct tm6000_dvb), GFP_KERNEL);
+	if (!dvb)
+		return -ENOMEM;
+
+	dev->dvb = dvb;
+
+	rc = register_dvb(dev);
+	if (rc < 0) {
+		kfree(dvb);
+		dev->dvb = NULL;
+		return 0;
+	}
+
+	return 0;
+}
+
+static int dvb_fini(struct tm6000_core *dev)
+{
+	if (!dev)
+		return 0;
+
+	if (!dev->caps.has_dvb)
+		return 0;
+
+	if (dev->dvb) {
+		unregister_dvb(dev);
+		kfree(dev->dvb);
+		dev->dvb = NULL;
+	}
+
+	return 0;
+}
+
+static struct tm6000_ops dvb_ops = {
+	.type	= TM6000_DVB,
+	.name	= "TM6000 dvb Extension",
+	.init	= dvb_init,
+	.fini	= dvb_fini,
+};
+
+static int __init tm6000_dvb_register(void)
+{
+	return tm6000_register_extension(&dvb_ops);
+}
+
+static void __exit tm6000_dvb_unregister(void)
+{
+	tm6000_unregister_extension(&dvb_ops);
+}
+
+module_init(tm6000_dvb_register);
+module_exit(tm6000_dvb_unregister);
diff --git a/drivers/media/usb/tm6000/tm6000-i2c.c b/drivers/media/usb/tm6000/tm6000-i2c.c
new file mode 100644
index 0000000..ccd1adf
--- /dev/null
+++ b/drivers/media/usb/tm6000/tm6000-i2c.c
@@ -0,0 +1,317 @@
+// SPDX-License-Identifier: GPL-2.0
+// tm6000-i2c.c - driver for TM5600/TM6000/TM6010 USB video capture devices
+//
+// Copyright (c) 2006-2007 Mauro Carvalho Chehab <mchehab@kernel.org>
+//
+// Copyright (c) 2007 Michel Ludwig <michel.ludwig@gmail.com>
+//	- Fix SMBus Read Byte command
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/usb.h>
+#include <linux/i2c.h>
+
+#include "tm6000.h"
+#include "tm6000-regs.h"
+#include <media/v4l2-common.h>
+#include <media/tuner.h>
+#include "tuner-xc2028.h"
+
+
+/* ----------------------------------------------------------- */
+
+static unsigned int i2c_debug;
+module_param(i2c_debug, int, 0644);
+MODULE_PARM_DESC(i2c_debug, "enable debug messages [i2c]");
+
+#define i2c_dprintk(lvl, fmt, args...) if (i2c_debug >= lvl) do { \
+			printk(KERN_DEBUG "%s at %s: " fmt, \
+			dev->name, __func__, ##args); } while (0)
+
+static int tm6000_i2c_send_regs(struct tm6000_core *dev, unsigned char addr,
+				__u8 reg, char *buf, int len)
+{
+	int rc;
+	unsigned int i2c_packet_limit = 16;
+
+	if (dev->dev_type == TM6010)
+		i2c_packet_limit = 80;
+
+	if (!buf)
+		return -1;
+
+	if (len < 1 || len > i2c_packet_limit) {
+		printk(KERN_ERR "Incorrect length of i2c packet = %d, limit set to %d\n",
+			len, i2c_packet_limit);
+		return -1;
+	}
+
+	/* capture mutex */
+	rc = tm6000_read_write_usb(dev, USB_DIR_OUT | USB_TYPE_VENDOR |
+		USB_RECIP_DEVICE, REQ_16_SET_GET_I2C_WR1_RDN,
+		addr | reg << 8, 0, buf, len);
+
+	if (rc < 0) {
+		/* release mutex */
+		return rc;
+	}
+
+	/* release mutex */
+	return rc;
+}
+
+/* Generic read - doesn't work fine with 16bit registers */
+static int tm6000_i2c_recv_regs(struct tm6000_core *dev, unsigned char addr,
+				__u8 reg, char *buf, int len)
+{
+	int rc;
+	u8 b[2];
+	unsigned int i2c_packet_limit = 16;
+
+	if (dev->dev_type == TM6010)
+		i2c_packet_limit = 64;
+
+	if (!buf)
+		return -1;
+
+	if (len < 1 || len > i2c_packet_limit) {
+		printk(KERN_ERR "Incorrect length of i2c packet = %d, limit set to %d\n",
+			len, i2c_packet_limit);
+		return -1;
+	}
+
+	/* capture mutex */
+	if ((dev->caps.has_zl10353) && (dev->demod_addr << 1 == addr) && (reg % 2 == 0)) {
+		/*
+		 * Workaround an I2C bug when reading from zl10353
+		 */
+		reg -= 1;
+		len += 1;
+
+		rc = tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			REQ_16_SET_GET_I2C_WR1_RDN, addr | reg << 8, 0, b, len);
+
+		*buf = b[1];
+	} else {
+		rc = tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			REQ_16_SET_GET_I2C_WR1_RDN, addr | reg << 8, 0, buf, len);
+	}
+
+	/* release mutex */
+	return rc;
+}
+
+/*
+ * read from a 16bit register
+ * for example xc2028, xc3028 or xc3028L
+ */
+static int tm6000_i2c_recv_regs16(struct tm6000_core *dev, unsigned char addr,
+				  __u16 reg, char *buf, int len)
+{
+	int rc;
+	unsigned char ureg;
+
+	if (!buf || len != 2)
+		return -1;
+
+	/* capture mutex */
+	if (dev->dev_type == TM6010) {
+		ureg = reg & 0xFF;
+		rc = tm6000_read_write_usb(dev, USB_DIR_OUT | USB_TYPE_VENDOR |
+			USB_RECIP_DEVICE, REQ_16_SET_GET_I2C_WR1_RDN,
+			addr | (reg & 0xFF00), 0, &ureg, 1);
+
+		if (rc < 0) {
+			/* release mutex */
+			return rc;
+		}
+
+		rc = tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR |
+			USB_RECIP_DEVICE, REQ_35_AFTEK_TUNER_READ,
+			reg, 0, buf, len);
+	} else {
+		rc = tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR |
+			USB_RECIP_DEVICE, REQ_14_SET_GET_I2C_WR2_RDN,
+			addr, reg, buf, len);
+	}
+
+	/* release mutex */
+	return rc;
+}
+
+static int tm6000_i2c_xfer(struct i2c_adapter *i2c_adap,
+			   struct i2c_msg msgs[], int num)
+{
+	struct tm6000_core *dev = i2c_adap->algo_data;
+	int addr, rc, i, byte;
+
+	for (i = 0; i < num; i++) {
+		addr = (msgs[i].addr << 1) & 0xff;
+		i2c_dprintk(2, "%s %s addr=0x%x len=%d:",
+			 (msgs[i].flags & I2C_M_RD) ? "read" : "write",
+			 i == num - 1 ? "stop" : "nonstop", addr, msgs[i].len);
+		if (msgs[i].flags & I2C_M_RD) {
+			/* read request without preceding register selection */
+			/*
+			 * The TM6000 only supports a read transaction
+			 * immediately after a 1 or 2 byte write to select
+			 * a register.  We cannot fulfil this request.
+			 */
+			i2c_dprintk(2, " read without preceding write not supported");
+			rc = -EOPNOTSUPP;
+			goto err;
+		} else if (i + 1 < num && msgs[i].len <= 2 &&
+			   (msgs[i + 1].flags & I2C_M_RD) &&
+			   msgs[i].addr == msgs[i + 1].addr) {
+			/* 1 or 2 byte write followed by a read */
+			if (i2c_debug >= 2)
+				for (byte = 0; byte < msgs[i].len; byte++)
+					printk(KERN_CONT " %02x", msgs[i].buf[byte]);
+			i2c_dprintk(2, "; joined to read %s len=%d:",
+				    i == num - 2 ? "stop" : "nonstop",
+				    msgs[i + 1].len);
+
+			if (msgs[i].len == 2) {
+				rc = tm6000_i2c_recv_regs16(dev, addr,
+					msgs[i].buf[0] << 8 | msgs[i].buf[1],
+					msgs[i + 1].buf, msgs[i + 1].len);
+			} else {
+				rc = tm6000_i2c_recv_regs(dev, addr, msgs[i].buf[0],
+					msgs[i + 1].buf, msgs[i + 1].len);
+			}
+
+			i++;
+
+			if (addr == dev->tuner_addr << 1) {
+				tm6000_set_reg(dev, REQ_50_SET_START, 0, 0);
+				tm6000_set_reg(dev, REQ_51_SET_STOP, 0, 0);
+			}
+			if (i2c_debug >= 2)
+				for (byte = 0; byte < msgs[i].len; byte++)
+					printk(KERN_CONT " %02x", msgs[i].buf[byte]);
+		} else {
+			/* write bytes */
+			if (i2c_debug >= 2)
+				for (byte = 0; byte < msgs[i].len; byte++)
+					printk(KERN_CONT " %02x", msgs[i].buf[byte]);
+			rc = tm6000_i2c_send_regs(dev, addr, msgs[i].buf[0],
+				msgs[i].buf + 1, msgs[i].len - 1);
+		}
+		if (i2c_debug >= 2)
+			printk(KERN_CONT "\n");
+		if (rc < 0)
+			goto err;
+	}
+
+	return num;
+err:
+	i2c_dprintk(2, " ERROR: %i\n", rc);
+	return rc;
+}
+
+static int tm6000_i2c_eeprom(struct tm6000_core *dev)
+{
+	int i, rc;
+	unsigned char *p = dev->eedata;
+	unsigned char bytes[17];
+
+	dev->i2c_client.addr = 0xa0 >> 1;
+	dev->eedata_size = 0;
+
+	bytes[16] = '\0';
+	for (i = 0; i < sizeof(dev->eedata); ) {
+		*p = i;
+		rc = tm6000_i2c_recv_regs(dev, 0xa0, i, p, 1);
+		if (rc < 1) {
+			if (p == dev->eedata)
+				goto noeeprom;
+			else {
+				printk(KERN_WARNING
+				"%s: i2c eeprom read error (err=%d)\n",
+				dev->name, rc);
+			}
+			return -EINVAL;
+		}
+		dev->eedata_size++;
+		p++;
+		if (0 == (i % 16))
+			printk(KERN_INFO "%s: i2c eeprom %02x:", dev->name, i);
+		printk(KERN_CONT " %02x", dev->eedata[i]);
+		if ((dev->eedata[i] >= ' ') && (dev->eedata[i] <= 'z'))
+			bytes[i%16] = dev->eedata[i];
+		else
+			bytes[i%16] = '.';
+
+		i++;
+
+		if (0 == (i % 16)) {
+			bytes[16] = '\0';
+			printk(KERN_CONT "  %s\n", bytes);
+		}
+	}
+	if (0 != (i%16)) {
+		bytes[i%16] = '\0';
+		for (i %= 16; i < 16; i++)
+			printk(KERN_CONT "   ");
+		printk(KERN_CONT "  %s\n", bytes);
+	}
+
+	return 0;
+
+noeeprom:
+	printk(KERN_INFO "%s: Huh, no eeprom present (err=%d)?\n",
+	       dev->name, rc);
+	return -EINVAL;
+}
+
+/* ----------------------------------------------------------- */
+
+/*
+ * functionality()
+ */
+static u32 functionality(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_SMBUS_EMUL;
+}
+
+static const struct i2c_algorithm tm6000_algo = {
+	.master_xfer   = tm6000_i2c_xfer,
+	.functionality = functionality,
+};
+
+/* ----------------------------------------------------------- */
+
+/*
+ * tm6000_i2c_register()
+ * register i2c bus
+ */
+int tm6000_i2c_register(struct tm6000_core *dev)
+{
+	int rc;
+
+	dev->i2c_adap.owner = THIS_MODULE;
+	dev->i2c_adap.algo = &tm6000_algo;
+	dev->i2c_adap.dev.parent = &dev->udev->dev;
+	strlcpy(dev->i2c_adap.name, dev->name, sizeof(dev->i2c_adap.name));
+	dev->i2c_adap.algo_data = dev;
+	i2c_set_adapdata(&dev->i2c_adap, &dev->v4l2_dev);
+	rc = i2c_add_adapter(&dev->i2c_adap);
+	if (rc)
+		return rc;
+
+	dev->i2c_client.adapter = &dev->i2c_adap;
+	strlcpy(dev->i2c_client.name, "tm6000 internal", I2C_NAME_SIZE);
+	tm6000_i2c_eeprom(dev);
+
+	return 0;
+}
+
+/*
+ * tm6000_i2c_unregister()
+ * unregister i2c_bus
+ */
+int tm6000_i2c_unregister(struct tm6000_core *dev)
+{
+	i2c_del_adapter(&dev->i2c_adap);
+	return 0;
+}
diff --git a/drivers/media/usb/tm6000/tm6000-input.c b/drivers/media/usb/tm6000/tm6000-input.c
new file mode 100644
index 0000000..397990a
--- /dev/null
+++ b/drivers/media/usb/tm6000/tm6000-input.c
@@ -0,0 +1,511 @@
+/*
+ *  tm6000-input.c - driver for TM5600/TM6000/TM6010 USB video capture devices
+ *
+ *  Copyright (C) 2010 Stefan Ringel <stefan.ringel@arcor.de>
+ *
+ *  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 version 2
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+
+#include <linux/input.h>
+#include <linux/usb.h>
+
+#include <media/rc-core.h>
+
+#include "tm6000.h"
+#include "tm6000-regs.h"
+
+static unsigned int ir_debug;
+module_param(ir_debug, int, 0644);
+MODULE_PARM_DESC(ir_debug, "debug message level");
+
+static unsigned int enable_ir = 1;
+module_param(enable_ir, int, 0644);
+MODULE_PARM_DESC(enable_ir, "enable ir (default is enable)");
+
+static unsigned int ir_clock_mhz = 12;
+module_param(ir_clock_mhz, int, 0644);
+MODULE_PARM_DESC(ir_clock_mhz, "ir clock, in MHz");
+
+#define URB_SUBMIT_DELAY	100	/* ms - Delay to submit an URB request on retrial and init */
+#define URB_INT_LED_DELAY	100	/* ms - Delay to turn led on again on int mode */
+
+#undef dprintk
+
+#define dprintk(level, fmt, arg...) do {\
+	if (ir_debug >= level) \
+		printk(KERN_DEBUG "%s/ir: " fmt, ir->name , ## arg); \
+	} while (0)
+
+struct tm6000_ir_poll_result {
+	u16 rc_data;
+};
+
+struct tm6000_IR {
+	struct tm6000_core	*dev;
+	struct rc_dev		*rc;
+	char			name[32];
+	char			phys[32];
+
+	/* poll expernal decoder */
+	int			polling;
+	struct delayed_work	work;
+	u8			wait:1;
+	u8			pwled:2;
+	u8			submit_urb:1;
+	struct urb		*int_urb;
+
+	/* IR device properties */
+	u64			rc_proto;
+};
+
+void tm6000_ir_wait(struct tm6000_core *dev, u8 state)
+{
+	struct tm6000_IR *ir = dev->ir;
+
+	if (!dev->ir)
+		return;
+
+	dprintk(2, "%s: %i\n",__func__, ir->wait);
+
+	if (state)
+		ir->wait = 1;
+	else
+		ir->wait = 0;
+}
+
+static int tm6000_ir_config(struct tm6000_IR *ir)
+{
+	struct tm6000_core *dev = ir->dev;
+	u32 pulse = 0, leader = 0;
+
+	dprintk(2, "%s\n",__func__);
+
+	/*
+	 * The IR decoder supports RC-5 or NEC, with a configurable timing.
+	 * The timing configuration there is not that accurate, as it uses
+	 * approximate values. The NEC spec mentions a 562.5 unit period,
+	 * and RC-5 uses a 888.8 period.
+	 * Currently, driver assumes a clock provided by a 12 MHz XTAL, but
+	 * a modprobe parameter can adjust it.
+	 * Adjustments are required for other timings.
+	 * It seems that the 900ms timing for NEC is used to detect a RC-5
+	 * IR, in order to discard such decoding
+	 */
+
+	switch (ir->rc_proto) {
+	case RC_PROTO_BIT_NEC:
+		leader = 900;	/* ms */
+		pulse  = 700;	/* ms - the actual value would be 562 */
+		break;
+	default:
+	case RC_PROTO_BIT_RC5:
+		leader = 900;	/* ms - from the NEC decoding */
+		pulse  = 1780;	/* ms - The actual value would be 1776 */
+		break;
+	}
+
+	pulse = ir_clock_mhz * pulse;
+	leader = ir_clock_mhz * leader;
+	if (ir->rc_proto == RC_PROTO_BIT_NEC)
+		leader = leader | 0x8000;
+
+	dprintk(2, "%s: %s, %d MHz, leader = 0x%04x, pulse = 0x%06x \n",
+		__func__,
+		(ir->rc_proto == RC_PROTO_BIT_NEC) ? "NEC" : "RC-5",
+		ir_clock_mhz, leader, pulse);
+
+	/* Remote WAKEUP = enable, normal mode, from IR decoder output */
+	tm6000_set_reg(dev, TM6010_REQ07_RE5_REMOTE_WAKEUP, 0xfe);
+
+	/* Enable IR reception on non-busrt mode */
+	tm6000_set_reg(dev, TM6010_REQ07_RD8_IR, 0x2f);
+
+	/* IR_WKUP_SEL = Low byte in decoded IR data */
+	tm6000_set_reg(dev, TM6010_REQ07_RDA_IR_WAKEUP_SEL, 0xff);
+	/* IR_WKU_ADD code */
+	tm6000_set_reg(dev, TM6010_REQ07_RDB_IR_WAKEUP_ADD, 0xff);
+
+	tm6000_set_reg(dev, TM6010_REQ07_RDC_IR_LEADER1, leader >> 8);
+	tm6000_set_reg(dev, TM6010_REQ07_RDD_IR_LEADER0, leader);
+
+	tm6000_set_reg(dev, TM6010_REQ07_RDE_IR_PULSE_CNT1, pulse >> 8);
+	tm6000_set_reg(dev, TM6010_REQ07_RDF_IR_PULSE_CNT0, pulse);
+
+	if (!ir->polling)
+		tm6000_set_reg(dev, REQ_04_EN_DISABLE_MCU_INT, 2, 0);
+	else
+		tm6000_set_reg(dev, REQ_04_EN_DISABLE_MCU_INT, 2, 1);
+	msleep(10);
+
+	/* Shows that IR is working via the LED */
+	tm6000_flash_led(dev, 0);
+	msleep(100);
+	tm6000_flash_led(dev, 1);
+	ir->pwled = 1;
+
+	return 0;
+}
+
+static void tm6000_ir_keydown(struct tm6000_IR *ir,
+			      const char *buf, unsigned int len)
+{
+	u8 device, command;
+	u32 scancode;
+	enum rc_proto protocol;
+
+	if (len < 1)
+		return;
+
+	command = buf[0];
+	device = (len > 1 ? buf[1] : 0x0);
+	switch (ir->rc_proto) {
+	case RC_PROTO_BIT_RC5:
+		protocol = RC_PROTO_RC5;
+		scancode = RC_SCANCODE_RC5(device, command);
+		break;
+	case RC_PROTO_BIT_NEC:
+		protocol = RC_PROTO_NEC;
+		scancode = RC_SCANCODE_NEC(device, command);
+		break;
+	default:
+		protocol = RC_PROTO_OTHER;
+		scancode = RC_SCANCODE_OTHER(device << 8 | command);
+		break;
+	}
+
+	dprintk(1, "%s, protocol: 0x%04x, scancode: 0x%08x\n",
+		__func__, protocol, scancode);
+	rc_keydown(ir->rc, protocol, scancode, 0);
+}
+
+static void tm6000_ir_urb_received(struct urb *urb)
+{
+	struct tm6000_core *dev = urb->context;
+	struct tm6000_IR *ir = dev->ir;
+	char *buf;
+
+	dprintk(2, "%s\n",__func__);
+	if (urb->status < 0 || urb->actual_length <= 0) {
+		printk(KERN_INFO "tm6000: IR URB failure: status: %i, length %i\n",
+		       urb->status, urb->actual_length);
+		ir->submit_urb = 1;
+		schedule_delayed_work(&ir->work, msecs_to_jiffies(URB_SUBMIT_DELAY));
+		return;
+	}
+	buf = urb->transfer_buffer;
+
+	if (ir_debug)
+		print_hex_dump(KERN_DEBUG, "tm6000: IR data: ",
+			       DUMP_PREFIX_OFFSET,16, 1,
+			       buf, urb->actual_length, false);
+
+	tm6000_ir_keydown(ir, urb->transfer_buffer, urb->actual_length);
+
+	usb_submit_urb(urb, GFP_ATOMIC);
+	/*
+	 * Flash the led. We can't do it here, as it is running on IRQ context.
+	 * So, use the scheduler to do it, in a few ms.
+	 */
+	ir->pwled = 2;
+	schedule_delayed_work(&ir->work, msecs_to_jiffies(10));
+}
+
+static void tm6000_ir_handle_key(struct work_struct *work)
+{
+	struct tm6000_IR *ir = container_of(work, struct tm6000_IR, work.work);
+	struct tm6000_core *dev = ir->dev;
+	int rc;
+	u8 buf[2];
+
+	if (ir->wait)
+		return;
+
+	dprintk(3, "%s\n",__func__);
+
+	rc = tm6000_read_write_usb(dev, USB_DIR_IN |
+		USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+		REQ_02_GET_IR_CODE, 0, 0, buf, 2);
+	if (rc < 0)
+		return;
+
+	/* Check if something was read */
+	if ((buf[0] & 0xff) == 0xff) {
+		if (!ir->pwled) {
+			tm6000_flash_led(dev, 1);
+			ir->pwled = 1;
+		}
+		return;
+	}
+
+	tm6000_ir_keydown(ir, buf, rc);
+	tm6000_flash_led(dev, 0);
+	ir->pwled = 0;
+
+	/* Re-schedule polling */
+	schedule_delayed_work(&ir->work, msecs_to_jiffies(ir->polling));
+}
+
+static void tm6000_ir_int_work(struct work_struct *work)
+{
+	struct tm6000_IR *ir = container_of(work, struct tm6000_IR, work.work);
+	struct tm6000_core *dev = ir->dev;
+	int rc;
+
+	dprintk(3, "%s, submit_urb = %d, pwled = %d\n",__func__, ir->submit_urb,
+		ir->pwled);
+
+	if (ir->submit_urb) {
+		dprintk(3, "Resubmit urb\n");
+		tm6000_set_reg(dev, REQ_04_EN_DISABLE_MCU_INT, 2, 0);
+
+		rc = usb_submit_urb(ir->int_urb, GFP_ATOMIC);
+		if (rc < 0) {
+			printk(KERN_ERR "tm6000: Can't submit an IR interrupt. Error %i\n",
+			       rc);
+			/* Retry in 100 ms */
+			schedule_delayed_work(&ir->work, msecs_to_jiffies(URB_SUBMIT_DELAY));
+			return;
+		}
+		ir->submit_urb = 0;
+	}
+
+	/* Led is enabled only if USB submit doesn't fail */
+	if (ir->pwled == 2) {
+		tm6000_flash_led(dev, 0);
+		ir->pwled = 0;
+		schedule_delayed_work(&ir->work, msecs_to_jiffies(URB_INT_LED_DELAY));
+	} else if (!ir->pwled) {
+		tm6000_flash_led(dev, 1);
+		ir->pwled = 1;
+	}
+}
+
+static int tm6000_ir_start(struct rc_dev *rc)
+{
+	struct tm6000_IR *ir = rc->priv;
+
+	dprintk(2, "%s\n",__func__);
+
+	schedule_delayed_work(&ir->work, 0);
+
+	return 0;
+}
+
+static void tm6000_ir_stop(struct rc_dev *rc)
+{
+	struct tm6000_IR *ir = rc->priv;
+
+	dprintk(2, "%s\n",__func__);
+
+	cancel_delayed_work_sync(&ir->work);
+}
+
+static int tm6000_ir_change_protocol(struct rc_dev *rc, u64 *rc_proto)
+{
+	struct tm6000_IR *ir = rc->priv;
+
+	if (!ir)
+		return 0;
+
+	dprintk(2, "%s\n",__func__);
+
+	ir->rc_proto = *rc_proto;
+
+	tm6000_ir_config(ir);
+	/* TODO */
+	return 0;
+}
+
+static int __tm6000_ir_int_start(struct rc_dev *rc)
+{
+	struct tm6000_IR *ir = rc->priv;
+	struct tm6000_core *dev;
+	int pipe, size;
+	int err = -ENOMEM;
+
+	if (!ir)
+		return -ENODEV;
+	dev = ir->dev;
+
+	dprintk(2, "%s\n",__func__);
+
+	ir->int_urb = usb_alloc_urb(0, GFP_ATOMIC);
+	if (!ir->int_urb)
+		return -ENOMEM;
+
+	pipe = usb_rcvintpipe(dev->udev,
+		dev->int_in.endp->desc.bEndpointAddress
+		& USB_ENDPOINT_NUMBER_MASK);
+
+	size = usb_maxpacket(dev->udev, pipe, usb_pipeout(pipe));
+	dprintk(1, "IR max size: %d\n", size);
+
+	ir->int_urb->transfer_buffer = kzalloc(size, GFP_ATOMIC);
+	if (!ir->int_urb->transfer_buffer) {
+		usb_free_urb(ir->int_urb);
+		return err;
+	}
+	dprintk(1, "int interval: %d\n", dev->int_in.endp->desc.bInterval);
+
+	usb_fill_int_urb(ir->int_urb, dev->udev, pipe,
+		ir->int_urb->transfer_buffer, size,
+		tm6000_ir_urb_received, dev,
+		dev->int_in.endp->desc.bInterval);
+
+	ir->submit_urb = 1;
+	schedule_delayed_work(&ir->work, msecs_to_jiffies(URB_SUBMIT_DELAY));
+
+	return 0;
+}
+
+static void __tm6000_ir_int_stop(struct rc_dev *rc)
+{
+	struct tm6000_IR *ir = rc->priv;
+
+	if (!ir || !ir->int_urb)
+		return;
+
+	dprintk(2, "%s\n",__func__);
+
+	usb_kill_urb(ir->int_urb);
+	kfree(ir->int_urb->transfer_buffer);
+	usb_free_urb(ir->int_urb);
+	ir->int_urb = NULL;
+}
+
+int tm6000_ir_int_start(struct tm6000_core *dev)
+{
+	struct tm6000_IR *ir = dev->ir;
+
+	if (!ir)
+		return 0;
+
+	return __tm6000_ir_int_start(ir->rc);
+}
+
+void tm6000_ir_int_stop(struct tm6000_core *dev)
+{
+	struct tm6000_IR *ir = dev->ir;
+
+	if (!ir || !ir->rc)
+		return;
+
+	__tm6000_ir_int_stop(ir->rc);
+}
+
+int tm6000_ir_init(struct tm6000_core *dev)
+{
+	struct tm6000_IR *ir;
+	struct rc_dev *rc;
+	int err = -ENOMEM;
+	u64 rc_proto;
+
+	if (!enable_ir)
+		return -ENODEV;
+
+	if (!dev->caps.has_remote)
+		return 0;
+
+	if (!dev->ir_codes)
+		return 0;
+
+	ir = kzalloc(sizeof(*ir), GFP_ATOMIC);
+	rc = rc_allocate_device(RC_DRIVER_SCANCODE);
+	if (!ir || !rc)
+		goto out;
+
+	dprintk(2, "%s\n", __func__);
+
+	/* record handles to ourself */
+	ir->dev = dev;
+	dev->ir = ir;
+	ir->rc = rc;
+
+	/* input setup */
+	rc->allowed_protocols = RC_PROTO_BIT_RC5 | RC_PROTO_BIT_NEC;
+	/* Needed, in order to support NEC remotes with 24 or 32 bits */
+	rc->scancode_mask = 0xffff;
+	rc->priv = ir;
+	rc->change_protocol = tm6000_ir_change_protocol;
+	if (dev->int_in.endp) {
+		rc->open    = __tm6000_ir_int_start;
+		rc->close   = __tm6000_ir_int_stop;
+		INIT_DELAYED_WORK(&ir->work, tm6000_ir_int_work);
+	} else {
+		rc->open  = tm6000_ir_start;
+		rc->close = tm6000_ir_stop;
+		ir->polling = 50;
+		INIT_DELAYED_WORK(&ir->work, tm6000_ir_handle_key);
+	}
+
+	snprintf(ir->name, sizeof(ir->name), "tm5600/60x0 IR (%s)",
+						dev->name);
+
+	usb_make_path(dev->udev, ir->phys, sizeof(ir->phys));
+	strlcat(ir->phys, "/input0", sizeof(ir->phys));
+
+	rc_proto = RC_PROTO_BIT_UNKNOWN;
+	tm6000_ir_change_protocol(rc, &rc_proto);
+
+	rc->device_name = ir->name;
+	rc->input_phys = ir->phys;
+	rc->input_id.bustype = BUS_USB;
+	rc->input_id.version = 1;
+	rc->input_id.vendor = le16_to_cpu(dev->udev->descriptor.idVendor);
+	rc->input_id.product = le16_to_cpu(dev->udev->descriptor.idProduct);
+	rc->map_name = dev->ir_codes;
+	rc->driver_name = "tm6000";
+	rc->dev.parent = &dev->udev->dev;
+
+	/* ir register */
+	err = rc_register_device(rc);
+	if (err)
+		goto out;
+
+	return 0;
+
+out:
+	dev->ir = NULL;
+	rc_free_device(rc);
+	kfree(ir);
+	return err;
+}
+
+int tm6000_ir_fini(struct tm6000_core *dev)
+{
+	struct tm6000_IR *ir = dev->ir;
+
+	/* skip detach on non attached board */
+
+	if (!ir)
+		return 0;
+
+	dprintk(2, "%s\n",__func__);
+
+	if (!ir->polling)
+		__tm6000_ir_int_stop(ir->rc);
+
+	tm6000_ir_stop(ir->rc);
+
+	/* Turn off the led */
+	tm6000_flash_led(dev, 0);
+	ir->pwled = 0;
+
+	rc_unregister_device(ir->rc);
+
+	kfree(ir);
+	dev->ir = NULL;
+
+	return 0;
+}
diff --git a/drivers/media/usb/tm6000/tm6000-regs.h b/drivers/media/usb/tm6000/tm6000-regs.h
new file mode 100644
index 0000000..d104246
--- /dev/null
+++ b/drivers/media/usb/tm6000/tm6000-regs.h
@@ -0,0 +1,588 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0
+ * tm6000-regs.h - driver for TM5600/TM6000/TM6010 USB video capture devices
+ *
+ * Copyright (c) 2006-2007 Mauro Carvalho Chehab <mchehab@kernel.org>
+ */
+
+/*
+ * Define TV Master TM5600/TM6000/TM6010 Request codes
+ */
+#define REQ_00_SET_IR_VALUE		0
+#define REQ_01_SET_WAKEUP_IRCODE	1
+#define REQ_02_GET_IR_CODE		2
+#define REQ_03_SET_GET_MCU_PIN		3
+#define REQ_04_EN_DISABLE_MCU_INT	4
+#define REQ_05_SET_GET_USBREG		5
+	/* Write: RegNum, Value, 0 */
+	/* Read : RegNum, Value, 1, RegStatus */
+#define REQ_06_SET_GET_USBREG_BIT	6
+#define REQ_07_SET_GET_AVREG		7
+	/* Write: RegNum, Value, 0 */
+	/* Read : RegNum, Value, 1, RegStatus */
+#define REQ_08_SET_GET_AVREG_BIT	8
+#define REQ_09_SET_GET_TUNER_FQ		9
+#define REQ_10_SET_TUNER_SYSTEM		10
+#define REQ_11_SET_EEPROM_ADDR		11
+#define REQ_12_SET_GET_EEPROMBYTE	12
+#define REQ_13_GET_EEPROM_SEQREAD	13
+#define REQ_14_SET_GET_I2C_WR2_RDN	14
+#define REQ_15_SET_GET_I2CBYTE		15
+	/* Write: Subaddr, Slave Addr, value, 0 */
+	/* Read : Subaddr, Slave Addr, value, 1 */
+#define REQ_16_SET_GET_I2C_WR1_RDN	16
+	/* Subaddr, Slave Addr, 0, length */
+#define REQ_17_SET_GET_I2CFP		17
+	/* Write: Slave Addr, register, value */
+	/* Read : Slave Addr, register, 2, data */
+#define REQ_20_DATA_TRANSFER		20
+#define REQ_30_I2C_WRITE		30
+#define REQ_31_I2C_READ			31
+#define REQ_35_AFTEK_TUNER_READ		35
+#define REQ_40_GET_VERSION		40
+#define REQ_50_SET_START		50
+#define REQ_51_SET_STOP			51
+#define REQ_52_TRANSMIT_DATA		52
+#define REQ_53_SPI_INITIAL		53
+#define REQ_54_SPI_SETSTART		54
+#define REQ_55_SPI_INOUTDATA		55
+#define REQ_56_SPI_SETSTOP		56
+
+/*
+ * Define TV Master TM5600/TM6000/TM6010 GPIO lines
+ */
+
+#define TM6000_GPIO_CLK		0x101
+#define TM6000_GPIO_DATA	0x100
+
+#define TM6000_GPIO_1		0x102
+#define TM6000_GPIO_2		0x103
+#define TM6000_GPIO_3		0x104
+#define TM6000_GPIO_4		0x300
+#define TM6000_GPIO_5		0x301
+#define TM6000_GPIO_6		0x304
+#define TM6000_GPIO_7		0x305
+
+/* tm6010 defines GPIO with different values */
+#define TM6010_GPIO_0      0x0102
+#define TM6010_GPIO_1      0x0103
+#define TM6010_GPIO_2      0x0104
+#define TM6010_GPIO_3      0x0105
+#define TM6010_GPIO_4      0x0106
+#define TM6010_GPIO_5      0x0107
+#define TM6010_GPIO_6      0x0300
+#define TM6010_GPIO_7      0x0301
+#define TM6010_GPIO_9      0x0305
+/*
+ * Define TV Master TM5600/TM6000/TM6010 URB message codes and length
+ */
+
+enum {
+	TM6000_URB_MSG_VIDEO = 1,
+	TM6000_URB_MSG_AUDIO,
+	TM6000_URB_MSG_VBI,
+	TM6000_URB_MSG_PTS,
+	TM6000_URB_MSG_ERR,
+};
+
+/* Define specific TM6000 Video decoder registers */
+#define TM6000_REQ07_RD8_TEST_SEL			0x07, 0xd8
+#define TM6000_REQ07_RD9_A_SIM_SEL			0x07, 0xd9
+#define TM6000_REQ07_RDA_CLK_SEL			0x07, 0xda
+#define TM6000_REQ07_RDB_OUT_SEL			0x07, 0xdb
+#define TM6000_REQ07_RDC_NSEL_I2S			0x07, 0xdc
+#define TM6000_REQ07_RDD_GPIO2_MDRV			0x07, 0xdd
+#define TM6000_REQ07_RDE_GPIO1_MDRV			0x07, 0xde
+#define TM6000_REQ07_RDF_PWDOWN_ACLK			0x07, 0xdf
+#define TM6000_REQ07_RE0_VADC_REF_CTL			0x07, 0xe0
+#define TM6000_REQ07_RE1_VADC_DACLIMP			0x07, 0xe1
+#define TM6000_REQ07_RE2_VADC_STATUS_CTL		0x07, 0xe2
+#define TM6000_REQ07_RE3_VADC_INP_LPF_SEL1		0x07, 0xe3
+#define TM6000_REQ07_RE4_VADC_TARGET1			0x07, 0xe4
+#define TM6000_REQ07_RE5_VADC_INP_LPF_SEL2		0x07, 0xe5
+#define TM6000_REQ07_RE6_VADC_TARGET2			0x07, 0xe6
+#define TM6000_REQ07_RE7_VADC_AGAIN_CTL			0x07, 0xe7
+#define TM6000_REQ07_RE8_VADC_PWDOWN_CTL		0x07, 0xe8
+#define TM6000_REQ07_RE9_VADC_INPUT_CTL1		0x07, 0xe9
+#define TM6000_REQ07_REA_VADC_INPUT_CTL2		0x07, 0xea
+#define TM6000_REQ07_REB_VADC_AADC_MODE			0x07, 0xeb
+#define TM6000_REQ07_REC_VADC_AADC_LVOL			0x07, 0xec
+#define TM6000_REQ07_RED_VADC_AADC_RVOL			0x07, 0xed
+#define TM6000_REQ07_REE_VADC_CTRL_SEL_CONTROL		0x07, 0xee
+#define TM6000_REQ07_REF_VADC_GAIN_MAP_CTL		0x07, 0xef
+#define TM6000_REQ07_RFD_BIST_ERR_VST_LOW		0x07, 0xfd
+#define TM6000_REQ07_RFE_BIST_ERR_VST_HIGH		0x07, 0xfe
+
+/* Define TM6000/TM6010 Video decoder registers */
+#define TM6010_REQ07_R00_VIDEO_CONTROL0			0x07, 0x00
+#define TM6010_REQ07_R01_VIDEO_CONTROL1			0x07, 0x01
+#define TM6010_REQ07_R02_VIDEO_CONTROL2			0x07, 0x02
+#define TM6010_REQ07_R03_YC_SEP_CONTROL			0x07, 0x03
+#define TM6010_REQ07_R04_LUMA_HAGC_CONTROL		0x07, 0x04
+#define TM6010_REQ07_R05_NOISE_THRESHOLD		0x07, 0x05
+#define TM6010_REQ07_R06_AGC_GATE_THRESHOLD		0x07, 0x06
+#define TM6010_REQ07_R07_OUTPUT_CONTROL			0x07, 0x07
+#define TM6010_REQ07_R08_LUMA_CONTRAST_ADJ		0x07, 0x08
+#define TM6010_REQ07_R09_LUMA_BRIGHTNESS_ADJ		0x07, 0x09
+#define TM6010_REQ07_R0A_CHROMA_SATURATION_ADJ		0x07, 0x0a
+#define TM6010_REQ07_R0B_CHROMA_HUE_PHASE_ADJ		0x07, 0x0b
+#define TM6010_REQ07_R0C_CHROMA_AGC_CONTROL		0x07, 0x0c
+#define TM6010_REQ07_R0D_CHROMA_KILL_LEVEL		0x07, 0x0d
+#define TM6010_REQ07_R0F_CHROMA_AUTO_POSITION		0x07, 0x0f
+#define TM6010_REQ07_R10_AGC_PEAK_NOMINAL		0x07, 0x10
+#define TM6010_REQ07_R11_AGC_PEAK_CONTROL		0x07, 0x11
+#define TM6010_REQ07_R12_AGC_GATE_STARTH		0x07, 0x12
+#define TM6010_REQ07_R13_AGC_GATE_STARTL		0x07, 0x13
+#define TM6010_REQ07_R14_AGC_GATE_WIDTH			0x07, 0x14
+#define TM6010_REQ07_R15_AGC_BP_DELAY			0x07, 0x15
+#define TM6010_REQ07_R16_LOCK_COUNT			0x07, 0x16
+#define TM6010_REQ07_R17_HLOOP_MAXSTATE			0x07, 0x17
+#define TM6010_REQ07_R18_CHROMA_DTO_INCREMENT3		0x07, 0x18
+#define TM6010_REQ07_R19_CHROMA_DTO_INCREMENT2		0x07, 0x19
+#define TM6010_REQ07_R1A_CHROMA_DTO_INCREMENT1		0x07, 0x1a
+#define TM6010_REQ07_R1B_CHROMA_DTO_INCREMENT0		0x07, 0x1b
+#define TM6010_REQ07_R1C_HSYNC_DTO_INCREMENT3		0x07, 0x1c
+#define TM6010_REQ07_R1D_HSYNC_DTO_INCREMENT2		0x07, 0x1d
+#define TM6010_REQ07_R1E_HSYNC_DTO_INCREMENT1		0x07, 0x1e
+#define TM6010_REQ07_R1F_HSYNC_DTO_INCREMENT0		0x07, 0x1f
+#define TM6010_REQ07_R20_HSYNC_RISING_EDGE_TIME		0x07, 0x20
+#define TM6010_REQ07_R21_HSYNC_PHASE_OFFSET		0x07, 0x21
+#define TM6010_REQ07_R22_HSYNC_PLL_START_TIME		0x07, 0x22
+#define TM6010_REQ07_R23_HSYNC_PLL_END_TIME		0x07, 0x23
+#define TM6010_REQ07_R24_HSYNC_TIP_START_TIME		0x07, 0x24
+#define TM6010_REQ07_R25_HSYNC_TIP_END_TIME		0x07, 0x25
+#define TM6010_REQ07_R26_HSYNC_RISING_EDGE_START	0x07, 0x26
+#define TM6010_REQ07_R27_HSYNC_RISING_EDGE_END		0x07, 0x27
+#define TM6010_REQ07_R28_BACKPORCH_START		0x07, 0x28
+#define TM6010_REQ07_R29_BACKPORCH_END			0x07, 0x29
+#define TM6010_REQ07_R2A_HSYNC_FILTER_START		0x07, 0x2a
+#define TM6010_REQ07_R2B_HSYNC_FILTER_END		0x07, 0x2b
+#define TM6010_REQ07_R2C_CHROMA_BURST_START		0x07, 0x2c
+#define TM6010_REQ07_R2D_CHROMA_BURST_END		0x07, 0x2d
+#define TM6010_REQ07_R2E_ACTIVE_VIDEO_HSTART		0x07, 0x2e
+#define TM6010_REQ07_R2F_ACTIVE_VIDEO_HWIDTH		0x07, 0x2f
+#define TM6010_REQ07_R30_ACTIVE_VIDEO_VSTART		0x07, 0x30
+#define TM6010_REQ07_R31_ACTIVE_VIDEO_VHIGHT		0x07, 0x31
+#define TM6010_REQ07_R32_VSYNC_HLOCK_MIN		0x07, 0x32
+#define TM6010_REQ07_R33_VSYNC_HLOCK_MAX		0x07, 0x33
+#define TM6010_REQ07_R34_VSYNC_AGC_MIN			0x07, 0x34
+#define TM6010_REQ07_R35_VSYNC_AGC_MAX			0x07, 0x35
+#define TM6010_REQ07_R36_VSYNC_VBI_MIN			0x07, 0x36
+#define TM6010_REQ07_R37_VSYNC_VBI_MAX			0x07, 0x37
+#define TM6010_REQ07_R38_VSYNC_THRESHOLD		0x07, 0x38
+#define TM6010_REQ07_R39_VSYNC_TIME_CONSTANT		0x07, 0x39
+#define TM6010_REQ07_R3A_STATUS1			0x07, 0x3a
+#define TM6010_REQ07_R3B_STATUS2			0x07, 0x3b
+#define TM6010_REQ07_R3C_STATUS3			0x07, 0x3c
+#define TM6010_REQ07_R3F_RESET				0x07, 0x3f
+#define TM6010_REQ07_R40_TELETEXT_VBI_CODE0		0x07, 0x40
+#define TM6010_REQ07_R41_TELETEXT_VBI_CODE1		0x07, 0x41
+#define TM6010_REQ07_R42_VBI_DATA_HIGH_LEVEL		0x07, 0x42
+#define TM6010_REQ07_R43_VBI_DATA_TYPE_LINE7		0x07, 0x43
+#define TM6010_REQ07_R44_VBI_DATA_TYPE_LINE8		0x07, 0x44
+#define TM6010_REQ07_R45_VBI_DATA_TYPE_LINE9		0x07, 0x45
+#define TM6010_REQ07_R46_VBI_DATA_TYPE_LINE10		0x07, 0x46
+#define TM6010_REQ07_R47_VBI_DATA_TYPE_LINE11		0x07, 0x47
+#define TM6010_REQ07_R48_VBI_DATA_TYPE_LINE12		0x07, 0x48
+#define TM6010_REQ07_R49_VBI_DATA_TYPE_LINE13		0x07, 0x49
+#define TM6010_REQ07_R4A_VBI_DATA_TYPE_LINE14		0x07, 0x4a
+#define TM6010_REQ07_R4B_VBI_DATA_TYPE_LINE15		0x07, 0x4b
+#define TM6010_REQ07_R4C_VBI_DATA_TYPE_LINE16		0x07, 0x4c
+#define TM6010_REQ07_R4D_VBI_DATA_TYPE_LINE17		0x07, 0x4d
+#define TM6010_REQ07_R4E_VBI_DATA_TYPE_LINE18		0x07, 0x4e
+#define TM6010_REQ07_R4F_VBI_DATA_TYPE_LINE19		0x07, 0x4f
+#define TM6010_REQ07_R50_VBI_DATA_TYPE_LINE20		0x07, 0x50
+#define TM6010_REQ07_R51_VBI_DATA_TYPE_LINE21		0x07, 0x51
+#define TM6010_REQ07_R52_VBI_DATA_TYPE_LINE22		0x07, 0x52
+#define TM6010_REQ07_R53_VBI_DATA_TYPE_LINE23		0x07, 0x53
+#define TM6010_REQ07_R54_VBI_DATA_TYPE_RLINES		0x07, 0x54
+#define TM6010_REQ07_R55_VBI_LOOP_FILTER_GAIN		0x07, 0x55
+#define TM6010_REQ07_R56_VBI_LOOP_FILTER_I_GAIN		0x07, 0x56
+#define TM6010_REQ07_R57_VBI_LOOP_FILTER_P_GAIN		0x07, 0x57
+#define TM6010_REQ07_R58_VBI_CAPTION_DTO1		0x07, 0x58
+#define TM6010_REQ07_R59_VBI_CAPTION_DTO0		0x07, 0x59
+#define TM6010_REQ07_R5A_VBI_TELETEXT_DTO1		0x07, 0x5a
+#define TM6010_REQ07_R5B_VBI_TELETEXT_DTO0		0x07, 0x5b
+#define TM6010_REQ07_R5C_VBI_WSS625_DTO1		0x07, 0x5c
+#define TM6010_REQ07_R5D_VBI_WSS625_DTO0		0x07, 0x5d
+#define TM6010_REQ07_R5E_VBI_CAPTION_FRAME_START	0x07, 0x5e
+#define TM6010_REQ07_R5F_VBI_WSS625_FRAME_START		0x07, 0x5f
+#define TM6010_REQ07_R60_TELETEXT_FRAME_START		0x07, 0x60
+#define TM6010_REQ07_R61_VBI_CCDATA1			0x07, 0x61
+#define TM6010_REQ07_R62_VBI_CCDATA2			0x07, 0x62
+#define TM6010_REQ07_R63_VBI_WSS625_DATA1		0x07, 0x63
+#define TM6010_REQ07_R64_VBI_WSS625_DATA2		0x07, 0x64
+#define TM6010_REQ07_R65_VBI_DATA_STATUS		0x07, 0x65
+#define TM6010_REQ07_R66_VBI_CAPTION_START		0x07, 0x66
+#define TM6010_REQ07_R67_VBI_WSS625_START		0x07, 0x67
+#define TM6010_REQ07_R68_VBI_TELETEXT_START		0x07, 0x68
+#define TM6010_REQ07_R70_HSYNC_DTO_INC_STATUS3		0x07, 0x70
+#define TM6010_REQ07_R71_HSYNC_DTO_INC_STATUS2		0x07, 0x71
+#define TM6010_REQ07_R72_HSYNC_DTO_INC_STATUS1		0x07, 0x72
+#define TM6010_REQ07_R73_HSYNC_DTO_INC_STATUS0		0x07, 0x73
+#define TM6010_REQ07_R74_CHROMA_DTO_INC_STATUS3		0x07, 0x74
+#define TM6010_REQ07_R75_CHROMA_DTO_INC_STATUS2		0x07, 0x75
+#define TM6010_REQ07_R76_CHROMA_DTO_INC_STATUS1		0x07, 0x76
+#define TM6010_REQ07_R77_CHROMA_DTO_INC_STATUS0		0x07, 0x77
+#define TM6010_REQ07_R78_AGC_AGAIN_STATUS		0x07, 0x78
+#define TM6010_REQ07_R79_AGC_DGAIN_STATUS		0x07, 0x79
+#define TM6010_REQ07_R7A_CHROMA_MAG_STATUS		0x07, 0x7a
+#define TM6010_REQ07_R7B_CHROMA_GAIN_STATUS1		0x07, 0x7b
+#define TM6010_REQ07_R7C_CHROMA_GAIN_STATUS0		0x07, 0x7c
+#define TM6010_REQ07_R7D_CORDIC_FREQ_STATUS		0x07, 0x7d
+#define TM6010_REQ07_R7F_STATUS_NOISE			0x07, 0x7f
+#define TM6010_REQ07_R80_COMB_FILTER_TRESHOLD		0x07, 0x80
+#define TM6010_REQ07_R82_COMB_FILTER_CONFIG		0x07, 0x82
+#define TM6010_REQ07_R83_CHROMA_LOCK_CONFIG		0x07, 0x83
+#define TM6010_REQ07_R84_NOISE_NTSC_C			0x07, 0x84
+#define TM6010_REQ07_R85_NOISE_PAL_C			0x07, 0x85
+#define TM6010_REQ07_R86_NOISE_PHASE_C			0x07, 0x86
+#define TM6010_REQ07_R87_NOISE_PHASE_Y			0x07, 0x87
+#define TM6010_REQ07_R8A_CHROMA_LOOPFILTER_STATE	0x07, 0x8a
+#define TM6010_REQ07_R8B_CHROMA_HRESAMPLER		0x07, 0x8b
+#define TM6010_REQ07_R8D_CPUMP_DELAY_ADJ		0x07, 0x8d
+#define TM6010_REQ07_R8E_CPUMP_ADJ			0x07, 0x8e
+#define TM6010_REQ07_R8F_CPUMP_DELAY			0x07, 0x8f
+
+/* Define TM6000/TM6010 Miscellaneous registers */
+#define TM6010_REQ07_RC0_ACTIVE_VIDEO_SOURCE		0x07, 0xc0
+#define TM6010_REQ07_RC1_TRESHOLD			0x07, 0xc1
+#define TM6010_REQ07_RC2_HSYNC_WIDTH			0x07, 0xc2
+#define TM6010_REQ07_RC3_HSTART1			0x07, 0xc3
+#define TM6010_REQ07_RC4_HSTART0			0x07, 0xc4
+#define TM6010_REQ07_RC5_HEND1				0x07, 0xc5
+#define TM6010_REQ07_RC6_HEND0				0x07, 0xc6
+#define TM6010_REQ07_RC7_VSTART1			0x07, 0xc7
+#define TM6010_REQ07_RC8_VSTART0			0x07, 0xc8
+#define TM6010_REQ07_RC9_VEND1				0x07, 0xc9
+#define TM6010_REQ07_RCA_VEND0				0x07, 0xca
+#define TM6010_REQ07_RCB_DELAY				0x07, 0xcb
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RCC_ACTIVE_IF			0x07, 0xcc
+#define TM6010_REQ07_RCC_ACTIVE_IF_VIDEO_ENABLE (1 << 5)
+#define TM6010_REQ07_RCC_ACTIVE_IF_AUDIO_ENABLE (1 << 6)
+#define TM6010_REQ07_RD0_USB_PERIPHERY_CONTROL		0x07, 0xd0
+#define TM6010_REQ07_RD1_ADDR_FOR_REQ1			0x07, 0xd1
+#define TM6010_REQ07_RD2_ADDR_FOR_REQ2			0x07, 0xd2
+#define TM6010_REQ07_RD3_ADDR_FOR_REQ3			0x07, 0xd3
+#define TM6010_REQ07_RD4_ADDR_FOR_REQ4			0x07, 0xd4
+#define TM6010_REQ07_RD5_POWERSAVE			0x07, 0xd5
+#define TM6010_REQ07_RD6_ENDP_REQ1_REQ2			0x07, 0xd6
+#define TM6010_REQ07_RD7_ENDP_REQ3_REQ4			0x07, 0xd7
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RD8_IR				0x07, 0xd8
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RD9_IR_BSIZE			0x07, 0xd9
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RDA_IR_WAKEUP_SEL			0x07, 0xda
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RDB_IR_WAKEUP_ADD			0x07, 0xdb
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RDC_IR_LEADER1			0x07, 0xdc
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RDD_IR_LEADER0			0x07, 0xdd
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RDE_IR_PULSE_CNT1			0x07, 0xde
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RDF_IR_PULSE_CNT0			0x07, 0xdf
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RE0_DVIDEO_SOURCE			0x07, 0xe0
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RE0_DVIDEO_SOURCE_IF		0x07, 0xe1
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RE2_OUT_SEL2			0x07, 0xe2
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RE3_OUT_SEL1			0x07, 0xe3
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RE4_OUT_SEL0			0x07, 0xe4
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RE5_REMOTE_WAKEUP			0x07, 0xe5
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RE7_PUB_GPIO			0x07, 0xe7
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RE8_TYPESEL_MOS_I2S		0x07, 0xe8
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RE9_TYPESEL_MOS_TS			0x07, 0xe9
+/* ONLY for TM6010 */
+#define TM6010_REQ07_REA_TYPESEL_MOS_CCIR		0x07, 0xea
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RF0_BIST_CRC_RESULT0		0x07, 0xf0
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RF1_BIST_CRC_RESULT1		0x07, 0xf1
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RF2_BIST_CRC_RESULT2		0x07, 0xf2
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RF3_BIST_CRC_RESULT3		0x07, 0xf3
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RF4_BIST_ERR_VST2			0x07, 0xf4
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RF5_BIST_ERR_VST1			0x07, 0xf5
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RF6_BIST_ERR_VST0			0x07, 0xf6
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RF7_BIST				0x07, 0xf7
+/* ONLY for TM6010 */
+#define TM6010_REQ07_RFE_POWER_DOWN			0x07, 0xfe
+#define TM6010_REQ07_RFF_SOFT_RESET			0x07, 0xff
+
+/* Define TM6000/TM6010 USB registers */
+#define TM6010_REQ05_R00_MAIN_CTRL		0x05, 0x00
+#define TM6010_REQ05_R01_DEVADDR		0x05, 0x01
+#define TM6010_REQ05_R02_TEST			0x05, 0x02
+#define TM6010_REQ05_R04_SOFN0			0x05, 0x04
+#define TM6010_REQ05_R05_SOFN1			0x05, 0x05
+#define TM6010_REQ05_R06_SOFTM0			0x05, 0x06
+#define TM6010_REQ05_R07_SOFTM1			0x05, 0x07
+#define TM6010_REQ05_R08_PHY_TEST		0x05, 0x08
+#define TM6010_REQ05_R09_VCTL			0x05, 0x09
+#define TM6010_REQ05_R0A_VSTA			0x05, 0x0a
+#define TM6010_REQ05_R0B_CX_CFG			0x05, 0x0b
+#define TM6010_REQ05_R0C_ENDP0_REG0		0x05, 0x0c
+#define TM6010_REQ05_R10_GMASK			0x05, 0x10
+#define TM6010_REQ05_R11_IMASK0			0x05, 0x11
+#define TM6010_REQ05_R12_IMASK1			0x05, 0x12
+#define TM6010_REQ05_R13_IMASK2			0x05, 0x13
+#define TM6010_REQ05_R14_IMASK3			0x05, 0x14
+#define TM6010_REQ05_R15_IMASK4			0x05, 0x15
+#define TM6010_REQ05_R16_IMASK5			0x05, 0x16
+#define TM6010_REQ05_R17_IMASK6			0x05, 0x17
+#define TM6010_REQ05_R18_IMASK7			0x05, 0x18
+#define TM6010_REQ05_R19_ZEROP0			0x05, 0x19
+#define TM6010_REQ05_R1A_ZEROP1			0x05, 0x1a
+#define TM6010_REQ05_R1C_FIFO_EMP0		0x05, 0x1c
+#define TM6010_REQ05_R1D_FIFO_EMP1		0x05, 0x1d
+#define TM6010_REQ05_R20_IRQ_GROUP		0x05, 0x20
+#define TM6010_REQ05_R21_IRQ_SOURCE0		0x05, 0x21
+#define TM6010_REQ05_R22_IRQ_SOURCE1		0x05, 0x22
+#define TM6010_REQ05_R23_IRQ_SOURCE2		0x05, 0x23
+#define TM6010_REQ05_R24_IRQ_SOURCE3		0x05, 0x24
+#define TM6010_REQ05_R25_IRQ_SOURCE4		0x05, 0x25
+#define TM6010_REQ05_R26_IRQ_SOURCE5		0x05, 0x26
+#define TM6010_REQ05_R27_IRQ_SOURCE6		0x05, 0x27
+#define TM6010_REQ05_R28_IRQ_SOURCE7		0x05, 0x28
+#define TM6010_REQ05_R29_SEQ_ERR0		0x05, 0x29
+#define TM6010_REQ05_R2A_SEQ_ERR1		0x05, 0x2a
+#define TM6010_REQ05_R2B_SEQ_ABORT0		0x05, 0x2b
+#define TM6010_REQ05_R2C_SEQ_ABORT1		0x05, 0x2c
+#define TM6010_REQ05_R2D_TX_ZERO0		0x05, 0x2d
+#define TM6010_REQ05_R2E_TX_ZERO1		0x05, 0x2e
+#define TM6010_REQ05_R2F_IDLE_CNT		0x05, 0x2f
+#define TM6010_REQ05_R30_FNO_P1			0x05, 0x30
+#define TM6010_REQ05_R31_FNO_P2			0x05, 0x31
+#define TM6010_REQ05_R32_FNO_P3			0x05, 0x32
+#define TM6010_REQ05_R33_FNO_P4			0x05, 0x33
+#define TM6010_REQ05_R34_FNO_P5			0x05, 0x34
+#define TM6010_REQ05_R35_FNO_P6			0x05, 0x35
+#define TM6010_REQ05_R36_FNO_P7			0x05, 0x36
+#define TM6010_REQ05_R37_FNO_P8			0x05, 0x37
+#define TM6010_REQ05_R38_FNO_P9			0x05, 0x38
+#define TM6010_REQ05_R30_FNO_P10		0x05, 0x39
+#define TM6010_REQ05_R30_FNO_P11		0x05, 0x3a
+#define TM6010_REQ05_R30_FNO_P12		0x05, 0x3b
+#define TM6010_REQ05_R30_FNO_P13		0x05, 0x3c
+#define TM6010_REQ05_R30_FNO_P14		0x05, 0x3d
+#define TM6010_REQ05_R30_FNO_P15		0x05, 0x3e
+#define TM6010_REQ05_R40_IN_MAXPS_LOW1		0x05, 0x40
+#define TM6010_REQ05_R41_IN_MAXPS_HIGH1		0x05, 0x41
+#define TM6010_REQ05_R42_IN_MAXPS_LOW2		0x05, 0x42
+#define TM6010_REQ05_R43_IN_MAXPS_HIGH2		0x05, 0x43
+#define TM6010_REQ05_R44_IN_MAXPS_LOW3		0x05, 0x44
+#define TM6010_REQ05_R45_IN_MAXPS_HIGH3		0x05, 0x45
+#define TM6010_REQ05_R46_IN_MAXPS_LOW4		0x05, 0x46
+#define TM6010_REQ05_R47_IN_MAXPS_HIGH4		0x05, 0x47
+#define TM6010_REQ05_R48_IN_MAXPS_LOW5		0x05, 0x48
+#define TM6010_REQ05_R49_IN_MAXPS_HIGH5		0x05, 0x49
+#define TM6010_REQ05_R4A_IN_MAXPS_LOW6		0x05, 0x4a
+#define TM6010_REQ05_R4B_IN_MAXPS_HIGH6		0x05, 0x4b
+#define TM6010_REQ05_R4C_IN_MAXPS_LOW7		0x05, 0x4c
+#define TM6010_REQ05_R4D_IN_MAXPS_HIGH7		0x05, 0x4d
+#define TM6010_REQ05_R4E_IN_MAXPS_LOW8		0x05, 0x4e
+#define TM6010_REQ05_R4F_IN_MAXPS_HIGH8		0x05, 0x4f
+#define TM6010_REQ05_R50_IN_MAXPS_LOW9		0x05, 0x50
+#define TM6010_REQ05_R51_IN_MAXPS_HIGH9		0x05, 0x51
+#define TM6010_REQ05_R40_IN_MAXPS_LOW10		0x05, 0x52
+#define TM6010_REQ05_R41_IN_MAXPS_HIGH10	0x05, 0x53
+#define TM6010_REQ05_R40_IN_MAXPS_LOW11		0x05, 0x54
+#define TM6010_REQ05_R41_IN_MAXPS_HIGH11	0x05, 0x55
+#define TM6010_REQ05_R40_IN_MAXPS_LOW12		0x05, 0x56
+#define TM6010_REQ05_R41_IN_MAXPS_HIGH12	0x05, 0x57
+#define TM6010_REQ05_R40_IN_MAXPS_LOW13		0x05, 0x58
+#define TM6010_REQ05_R41_IN_MAXPS_HIGH13	0x05, 0x59
+#define TM6010_REQ05_R40_IN_MAXPS_LOW14		0x05, 0x5a
+#define TM6010_REQ05_R41_IN_MAXPS_HIGH14	0x05, 0x5b
+#define TM6010_REQ05_R40_IN_MAXPS_LOW15		0x05, 0x5c
+#define TM6010_REQ05_R41_IN_MAXPS_HIGH15	0x05, 0x5d
+#define TM6010_REQ05_R60_OUT_MAXPS_LOW1		0x05, 0x60
+#define TM6010_REQ05_R61_OUT_MAXPS_HIGH1	0x05, 0x61
+#define TM6010_REQ05_R62_OUT_MAXPS_LOW2		0x05, 0x62
+#define TM6010_REQ05_R63_OUT_MAXPS_HIGH2	0x05, 0x63
+#define TM6010_REQ05_R64_OUT_MAXPS_LOW3		0x05, 0x64
+#define TM6010_REQ05_R65_OUT_MAXPS_HIGH3	0x05, 0x65
+#define TM6010_REQ05_R66_OUT_MAXPS_LOW4		0x05, 0x66
+#define TM6010_REQ05_R67_OUT_MAXPS_HIGH4	0x05, 0x67
+#define TM6010_REQ05_R68_OUT_MAXPS_LOW5		0x05, 0x68
+#define TM6010_REQ05_R69_OUT_MAXPS_HIGH5	0x05, 0x69
+#define TM6010_REQ05_R6A_OUT_MAXPS_LOW6		0x05, 0x6a
+#define TM6010_REQ05_R6B_OUT_MAXPS_HIGH6	0x05, 0x6b
+#define TM6010_REQ05_R6C_OUT_MAXPS_LOW7		0x05, 0x6c
+#define TM6010_REQ05_R6D_OUT_MAXPS_HIGH7	0x05, 0x6d
+#define TM6010_REQ05_R6E_OUT_MAXPS_LOW8		0x05, 0x6e
+#define TM6010_REQ05_R6F_OUT_MAXPS_HIGH8	0x05, 0x6f
+#define TM6010_REQ05_R70_OUT_MAXPS_LOW9		0x05, 0x70
+#define TM6010_REQ05_R71_OUT_MAXPS_HIGH9	0x05, 0x71
+#define TM6010_REQ05_R60_OUT_MAXPS_LOW10	0x05, 0x72
+#define TM6010_REQ05_R61_OUT_MAXPS_HIGH10	0x05, 0x73
+#define TM6010_REQ05_R60_OUT_MAXPS_LOW11	0x05, 0x74
+#define TM6010_REQ05_R61_OUT_MAXPS_HIGH11	0x05, 0x75
+#define TM6010_REQ05_R60_OUT_MAXPS_LOW12	0x05, 0x76
+#define TM6010_REQ05_R61_OUT_MAXPS_HIGH12	0x05, 0x77
+#define TM6010_REQ05_R60_OUT_MAXPS_LOW13	0x05, 0x78
+#define TM6010_REQ05_R61_OUT_MAXPS_HIGH13	0x05, 0x79
+#define TM6010_REQ05_R60_OUT_MAXPS_LOW14	0x05, 0x7a
+#define TM6010_REQ05_R61_OUT_MAXPS_HIGH14	0x05, 0x7b
+#define TM6010_REQ05_R60_OUT_MAXPS_LOW15	0x05, 0x7c
+#define TM6010_REQ05_R61_OUT_MAXPS_HIGH15	0x05, 0x7d
+#define TM6010_REQ05_R80_FIFO0			0x05, 0x80
+#define TM6010_REQ05_R81_FIFO1			0x05, 0x81
+#define TM6010_REQ05_R82_FIFO2			0x05, 0x82
+#define TM6010_REQ05_R83_FIFO3			0x05, 0x83
+#define TM6010_REQ05_R84_FIFO4			0x05, 0x84
+#define TM6010_REQ05_R85_FIFO5			0x05, 0x85
+#define TM6010_REQ05_R86_FIFO6			0x05, 0x86
+#define TM6010_REQ05_R87_FIFO7			0x05, 0x87
+#define TM6010_REQ05_R88_FIFO8			0x05, 0x88
+#define TM6010_REQ05_R89_FIFO9			0x05, 0x89
+#define TM6010_REQ05_R81_FIFO10			0x05, 0x8a
+#define TM6010_REQ05_R81_FIFO11			0x05, 0x8b
+#define TM6010_REQ05_R81_FIFO12			0x05, 0x8c
+#define TM6010_REQ05_R81_FIFO13			0x05, 0x8d
+#define TM6010_REQ05_R81_FIFO14			0x05, 0x8e
+#define TM6010_REQ05_R81_FIFO15			0x05, 0x8f
+#define TM6010_REQ05_R90_CFG_FIFO0		0x05, 0x90
+#define TM6010_REQ05_R91_CFG_FIFO1		0x05, 0x91
+#define TM6010_REQ05_R92_CFG_FIFO2		0x05, 0x92
+#define TM6010_REQ05_R93_CFG_FIFO3		0x05, 0x93
+#define TM6010_REQ05_R94_CFG_FIFO4		0x05, 0x94
+#define TM6010_REQ05_R95_CFG_FIFO5		0x05, 0x95
+#define TM6010_REQ05_R96_CFG_FIFO6		0x05, 0x96
+#define TM6010_REQ05_R97_CFG_FIFO7		0x05, 0x97
+#define TM6010_REQ05_R98_CFG_FIFO8		0x05, 0x98
+#define TM6010_REQ05_R99_CFG_FIFO9		0x05, 0x99
+#define TM6010_REQ05_R91_CFG_FIFO10		0x05, 0x9a
+#define TM6010_REQ05_R91_CFG_FIFO11		0x05, 0x9b
+#define TM6010_REQ05_R91_CFG_FIFO12		0x05, 0x9c
+#define TM6010_REQ05_R91_CFG_FIFO13		0x05, 0x9d
+#define TM6010_REQ05_R91_CFG_FIFO14		0x05, 0x9e
+#define TM6010_REQ05_R91_CFG_FIFO15		0x05, 0x9f
+#define TM6010_REQ05_RA0_CTL_FIFO0		0x05, 0xa0
+#define TM6010_REQ05_RA1_CTL_FIFO1		0x05, 0xa1
+#define TM6010_REQ05_RA2_CTL_FIFO2		0x05, 0xa2
+#define TM6010_REQ05_RA3_CTL_FIFO3		0x05, 0xa3
+#define TM6010_REQ05_RA4_CTL_FIFO4		0x05, 0xa4
+#define TM6010_REQ05_RA5_CTL_FIFO5		0x05, 0xa5
+#define TM6010_REQ05_RA6_CTL_FIFO6		0x05, 0xa6
+#define TM6010_REQ05_RA7_CTL_FIFO7		0x05, 0xa7
+#define TM6010_REQ05_RA8_CTL_FIFO8		0x05, 0xa8
+#define TM6010_REQ05_RA9_CTL_FIFO9		0x05, 0xa9
+#define TM6010_REQ05_RA1_CTL_FIFO10		0x05, 0xaa
+#define TM6010_REQ05_RA1_CTL_FIFO11		0x05, 0xab
+#define TM6010_REQ05_RA1_CTL_FIFO12		0x05, 0xac
+#define TM6010_REQ05_RA1_CTL_FIFO13		0x05, 0xad
+#define TM6010_REQ05_RA1_CTL_FIFO14		0x05, 0xae
+#define TM6010_REQ05_RA1_CTL_FIFO15		0x05, 0xaf
+#define TM6010_REQ05_RB0_BC_LOW_FIFO0		0x05, 0xb0
+#define TM6010_REQ05_RB1_BC_LOW_FIFO1		0x05, 0xb1
+#define TM6010_REQ05_RB2_BC_LOW_FIFO2		0x05, 0xb2
+#define TM6010_REQ05_RB3_BC_LOW_FIFO3		0x05, 0xb3
+#define TM6010_REQ05_RB4_BC_LOW_FIFO4		0x05, 0xb4
+#define TM6010_REQ05_RB5_BC_LOW_FIFO5		0x05, 0xb5
+#define TM6010_REQ05_RB6_BC_LOW_FIFO6		0x05, 0xb6
+#define TM6010_REQ05_RB7_BC_LOW_FIFO7		0x05, 0xb7
+#define TM6010_REQ05_RB8_BC_LOW_FIFO8		0x05, 0xb8
+#define TM6010_REQ05_RB9_BC_LOW_FIFO9		0x05, 0xb9
+#define TM6010_REQ05_RB1_BC_LOW_FIFO10		0x05, 0xba
+#define TM6010_REQ05_RB1_BC_LOW_FIFO11		0x05, 0xbb
+#define TM6010_REQ05_RB1_BC_LOW_FIFO12		0x05, 0xbc
+#define TM6010_REQ05_RB1_BC_LOW_FIFO13		0x05, 0xbd
+#define TM6010_REQ05_RB1_BC_LOW_FIFO14		0x05, 0xbe
+#define TM6010_REQ05_RB1_BC_LOW_FIFO15		0x05, 0xbf
+#define TM6010_REQ05_RC0_DATA_FIFO0		0x05, 0xc0
+#define TM6010_REQ05_RC4_DATA_FIFO1		0x05, 0xc4
+#define TM6010_REQ05_RC8_DATA_FIFO2		0x05, 0xc8
+#define TM6010_REQ05_RCC_DATA_FIFO3		0x05, 0xcc
+#define TM6010_REQ05_RD0_DATA_FIFO4		0x05, 0xd0
+#define TM6010_REQ05_RD4_DATA_FIFO5		0x05, 0xd4
+#define TM6010_REQ05_RD8_DATA_FIFO6		0x05, 0xd8
+#define TM6010_REQ05_RDC_DATA_FIFO7		0x05, 0xdc
+#define TM6010_REQ05_RE0_DATA_FIFO8		0x05, 0xe0
+#define TM6010_REQ05_RE4_DATA_FIFO9		0x05, 0xe4
+#define TM6010_REQ05_RC4_DATA_FIFO10		0x05, 0xe8
+#define TM6010_REQ05_RC4_DATA_FIFO11		0x05, 0xec
+#define TM6010_REQ05_RC4_DATA_FIFO12		0x05, 0xf0
+#define TM6010_REQ05_RC4_DATA_FIFO13		0x05, 0xf4
+#define TM6010_REQ05_RC4_DATA_FIFO14		0x05, 0xf8
+#define TM6010_REQ05_RC4_DATA_FIFO15		0x05, 0xfc
+
+/* Define TM6010 Audio decoder registers */
+/* This core available only in TM6010 */
+#define TM6010_REQ08_R00_A_VERSION		0x08, 0x00
+#define TM6010_REQ08_R01_A_INIT			0x08, 0x01
+#define TM6010_REQ08_R02_A_FIX_GAIN_CTRL	0x08, 0x02
+#define TM6010_REQ08_R03_A_AUTO_GAIN_CTRL	0x08, 0x03
+#define TM6010_REQ08_R04_A_SIF_AMP_CTRL		0x08, 0x04
+#define TM6010_REQ08_R05_A_STANDARD_MOD		0x08, 0x05
+#define TM6010_REQ08_R06_A_SOUND_MOD		0x08, 0x06
+#define TM6010_REQ08_R07_A_LEFT_VOL		0x08, 0x07
+#define TM6010_REQ08_R08_A_RIGHT_VOL		0x08, 0x08
+#define TM6010_REQ08_R09_A_MAIN_VOL		0x08, 0x09
+#define TM6010_REQ08_R0A_A_I2S_MOD		0x08, 0x0a
+#define TM6010_REQ08_R0B_A_ASD_THRES1		0x08, 0x0b
+#define TM6010_REQ08_R0C_A_ASD_THRES2		0x08, 0x0c
+#define TM6010_REQ08_R0D_A_AMD_THRES		0x08, 0x0d
+#define TM6010_REQ08_R0E_A_MONO_THRES1		0x08, 0x0e
+#define TM6010_REQ08_R0F_A_MONO_THRES2		0x08, 0x0f
+#define TM6010_REQ08_R10_A_MUTE_THRES1		0x08, 0x10
+#define TM6010_REQ08_R11_A_MUTE_THRES2		0x08, 0x11
+#define TM6010_REQ08_R12_A_AGC_U		0x08, 0x12
+#define TM6010_REQ08_R13_A_AGC_ERR_T		0x08, 0x13
+#define TM6010_REQ08_R14_A_AGC_GAIN_INIT	0x08, 0x14
+#define TM6010_REQ08_R15_A_AGC_STEP_THR		0x08, 0x15
+#define TM6010_REQ08_R16_A_AGC_GAIN_MAX		0x08, 0x16
+#define TM6010_REQ08_R17_A_AGC_GAIN_MIN		0x08, 0x17
+#define TM6010_REQ08_R18_A_TR_CTRL		0x08, 0x18
+#define TM6010_REQ08_R19_A_FH_2FH_GAIN		0x08, 0x19
+#define TM6010_REQ08_R1A_A_NICAM_SER_MAX	0x08, 0x1a
+#define TM6010_REQ08_R1B_A_NICAM_SER_MIN	0x08, 0x1b
+#define TM6010_REQ08_R1E_A_GAIN_DEEMPH_OUT	0x08, 0x1e
+#define TM6010_REQ08_R1F_A_TEST_INTF_SEL	0x08, 0x1f
+#define TM6010_REQ08_R20_A_TEST_PIN_SEL		0x08, 0x20
+#define TM6010_REQ08_R21_A_AGC_ERR		0x08, 0x21
+#define TM6010_REQ08_R22_A_AGC_GAIN		0x08, 0x22
+#define TM6010_REQ08_R23_A_NICAM_INFO		0x08, 0x23
+#define TM6010_REQ08_R24_A_SER			0x08, 0x24
+#define TM6010_REQ08_R25_A_C1_AMP		0x08, 0x25
+#define TM6010_REQ08_R26_A_C2_AMP		0x08, 0x26
+#define TM6010_REQ08_R27_A_NOISE_AMP		0x08, 0x27
+#define TM6010_REQ08_R28_A_AUDIO_MODE_RES	0x08, 0x28
+
+/* Define TM6010 Video ADC registers */
+#define TM6010_REQ08_RE0_ADC_REF		0x08, 0xe0
+#define TM6010_REQ08_RE1_DAC_CLMP		0x08, 0xe1
+#define TM6010_REQ08_RE2_POWER_DOWN_CTRL1	0x08, 0xe2
+#define TM6010_REQ08_RE3_ADC_IN1_SEL		0x08, 0xe3
+#define TM6010_REQ08_RE4_ADC_IN2_SEL		0x08, 0xe4
+#define TM6010_REQ08_RE5_GAIN_PARAM		0x08, 0xe5
+#define TM6010_REQ08_RE6_POWER_DOWN_CTRL2	0x08, 0xe6
+#define TM6010_REQ08_RE7_REG_GAIN_Y		0x08, 0xe7
+#define TM6010_REQ08_RE8_REG_GAIN_C		0x08, 0xe8
+#define TM6010_REQ08_RE9_BIAS_CTRL		0x08, 0xe9
+#define TM6010_REQ08_REA_BUFF_DRV_CTRL		0x08, 0xea
+#define TM6010_REQ08_REB_SIF_GAIN_CTRL		0x08, 0xeb
+#define TM6010_REQ08_REC_REVERSE_YC_CTRL	0x08, 0xec
+#define TM6010_REQ08_RED_GAIN_SEL		0x08, 0xed
+
+/* Define TM6010 Audio ADC registers */
+#define TM6010_REQ08_RF0_DAUDIO_INPUT_CONFIG	0x08, 0xf0
+#define TM6010_REQ08_RF1_AADC_POWER_DOWN	0x08, 0xf1
+#define TM6010_REQ08_RF2_LEFT_CHANNEL_VOL	0x08, 0xf2
+#define TM6010_REQ08_RF3_RIGHT_CHANNEL_VOL	0x08, 0xf3
diff --git a/drivers/media/usb/tm6000/tm6000-stds.c b/drivers/media/usb/tm6000/tm6000-stds.c
new file mode 100644
index 0000000..c0c7595
--- /dev/null
+++ b/drivers/media/usb/tm6000/tm6000-stds.c
@@ -0,0 +1,623 @@
+// SPDX-License-Identifier: GPL-2.0
+// tm6000-stds.c - driver for TM5600/TM6000/TM6010 USB video capture devices
+//
+// Copyright (c) 2007 Mauro Carvalho Chehab <mchehab@kernel.org>
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include "tm6000.h"
+#include "tm6000-regs.h"
+
+static unsigned int tm6010_a_mode;
+module_param(tm6010_a_mode, int, 0644);
+MODULE_PARM_DESC(tm6010_a_mode, "set tm6010 sif audio mode");
+
+struct tm6000_reg_settings {
+	unsigned char req;
+	unsigned char reg;
+	unsigned char value;
+};
+
+
+struct tm6000_std_settings {
+	v4l2_std_id id;
+	struct tm6000_reg_settings *common;
+};
+
+static struct tm6000_reg_settings composite_pal_m[] = {
+	{ TM6010_REQ07_R3F_RESET, 0x01 },
+	{ TM6010_REQ07_R00_VIDEO_CONTROL0, 0x04 },
+	{ TM6010_REQ07_R01_VIDEO_CONTROL1, 0x0e },
+	{ TM6010_REQ07_R02_VIDEO_CONTROL2, 0x5f },
+	{ TM6010_REQ07_R03_YC_SEP_CONTROL, 0x00 },
+	{ TM6010_REQ07_R07_OUTPUT_CONTROL, 0x31 },
+	{ TM6010_REQ07_R18_CHROMA_DTO_INCREMENT3, 0x1e },
+	{ TM6010_REQ07_R19_CHROMA_DTO_INCREMENT2, 0x83 },
+	{ TM6010_REQ07_R1A_CHROMA_DTO_INCREMENT1, 0x0a },
+	{ TM6010_REQ07_R1B_CHROMA_DTO_INCREMENT0, 0xe0 },
+	{ TM6010_REQ07_R1C_HSYNC_DTO_INCREMENT3, 0x1c },
+	{ TM6010_REQ07_R1D_HSYNC_DTO_INCREMENT2, 0xcc },
+	{ TM6010_REQ07_R1E_HSYNC_DTO_INCREMENT1, 0xcc },
+	{ TM6010_REQ07_R1F_HSYNC_DTO_INCREMENT0, 0xcd },
+	{ TM6010_REQ07_R2E_ACTIVE_VIDEO_HSTART, 0x88 },
+	{ TM6010_REQ07_R30_ACTIVE_VIDEO_VSTART, 0x20 },
+	{ TM6010_REQ07_R31_ACTIVE_VIDEO_VHIGHT, 0x61 },
+	{ TM6010_REQ07_R33_VSYNC_HLOCK_MAX, 0x0c },
+	{ TM6010_REQ07_R35_VSYNC_AGC_MAX, 0x1c },
+	{ TM6010_REQ07_R82_COMB_FILTER_CONFIG, 0x52 },
+	{ TM6010_REQ07_R83_CHROMA_LOCK_CONFIG, 0x6f },
+	{ TM6010_REQ07_R04_LUMA_HAGC_CONTROL, 0xdc },
+	{ TM6010_REQ07_R0D_CHROMA_KILL_LEVEL, 0x07 },
+	{ TM6010_REQ07_R3F_RESET, 0x00 },
+	{ 0, 0, 0 }
+};
+
+static struct tm6000_reg_settings composite_pal_nc[] = {
+	{ TM6010_REQ07_R3F_RESET, 0x01 },
+	{ TM6010_REQ07_R00_VIDEO_CONTROL0, 0x36 },
+	{ TM6010_REQ07_R01_VIDEO_CONTROL1, 0x0e },
+	{ TM6010_REQ07_R02_VIDEO_CONTROL2, 0x5f },
+	{ TM6010_REQ07_R03_YC_SEP_CONTROL, 0x02 },
+	{ TM6010_REQ07_R07_OUTPUT_CONTROL, 0x31 },
+	{ TM6010_REQ07_R18_CHROMA_DTO_INCREMENT3, 0x1e },
+	{ TM6010_REQ07_R19_CHROMA_DTO_INCREMENT2, 0x91 },
+	{ TM6010_REQ07_R1A_CHROMA_DTO_INCREMENT1, 0x1f },
+	{ TM6010_REQ07_R1B_CHROMA_DTO_INCREMENT0, 0x0c },
+	{ TM6010_REQ07_R1C_HSYNC_DTO_INCREMENT3, 0x1c },
+	{ TM6010_REQ07_R1D_HSYNC_DTO_INCREMENT2, 0xcc },
+	{ TM6010_REQ07_R1E_HSYNC_DTO_INCREMENT1, 0xcc },
+	{ TM6010_REQ07_R1F_HSYNC_DTO_INCREMENT0, 0xcd },
+	{ TM6010_REQ07_R2E_ACTIVE_VIDEO_HSTART, 0x8c },
+	{ TM6010_REQ07_R30_ACTIVE_VIDEO_VSTART, 0x2c },
+	{ TM6010_REQ07_R31_ACTIVE_VIDEO_VHIGHT, 0xc1 },
+	{ TM6010_REQ07_R33_VSYNC_HLOCK_MAX, 0x0c },
+	{ TM6010_REQ07_R35_VSYNC_AGC_MAX, 0x1c },
+	{ TM6010_REQ07_R82_COMB_FILTER_CONFIG, 0x52 },
+	{ TM6010_REQ07_R83_CHROMA_LOCK_CONFIG, 0x6f },
+	{ TM6010_REQ07_R04_LUMA_HAGC_CONTROL, 0xdc },
+	{ TM6010_REQ07_R0D_CHROMA_KILL_LEVEL, 0x07 },
+	{ TM6010_REQ07_R3F_RESET, 0x00 },
+	{ 0, 0, 0 }
+};
+
+static struct tm6000_reg_settings composite_pal[] = {
+	{ TM6010_REQ07_R3F_RESET, 0x01 },
+	{ TM6010_REQ07_R00_VIDEO_CONTROL0, 0x32 },
+	{ TM6010_REQ07_R01_VIDEO_CONTROL1, 0x0e },
+	{ TM6010_REQ07_R02_VIDEO_CONTROL2, 0x5f },
+	{ TM6010_REQ07_R03_YC_SEP_CONTROL, 0x02 },
+	{ TM6010_REQ07_R07_OUTPUT_CONTROL, 0x31 },
+	{ TM6010_REQ07_R18_CHROMA_DTO_INCREMENT3, 0x25 },
+	{ TM6010_REQ07_R19_CHROMA_DTO_INCREMENT2, 0xd5 },
+	{ TM6010_REQ07_R1A_CHROMA_DTO_INCREMENT1, 0x63 },
+	{ TM6010_REQ07_R1B_CHROMA_DTO_INCREMENT0, 0x50 },
+	{ TM6010_REQ07_R1C_HSYNC_DTO_INCREMENT3, 0x1c },
+	{ TM6010_REQ07_R1D_HSYNC_DTO_INCREMENT2, 0xcc },
+	{ TM6010_REQ07_R1E_HSYNC_DTO_INCREMENT1, 0xcc },
+	{ TM6010_REQ07_R1F_HSYNC_DTO_INCREMENT0, 0xcd },
+	{ TM6010_REQ07_R2E_ACTIVE_VIDEO_HSTART, 0x8c },
+	{ TM6010_REQ07_R30_ACTIVE_VIDEO_VSTART, 0x2c },
+	{ TM6010_REQ07_R31_ACTIVE_VIDEO_VHIGHT, 0xc1 },
+	{ TM6010_REQ07_R33_VSYNC_HLOCK_MAX, 0x0c },
+	{ TM6010_REQ07_R35_VSYNC_AGC_MAX, 0x1c },
+	{ TM6010_REQ07_R82_COMB_FILTER_CONFIG, 0x52 },
+	{ TM6010_REQ07_R83_CHROMA_LOCK_CONFIG, 0x6f },
+	{ TM6010_REQ07_R04_LUMA_HAGC_CONTROL, 0xdc },
+	{ TM6010_REQ07_R0D_CHROMA_KILL_LEVEL, 0x07 },
+	{ TM6010_REQ07_R3F_RESET, 0x00 },
+	{ 0, 0, 0 }
+};
+
+static struct tm6000_reg_settings composite_secam[] = {
+	{ TM6010_REQ07_R3F_RESET, 0x01 },
+	{ TM6010_REQ07_R00_VIDEO_CONTROL0, 0x38 },
+	{ TM6010_REQ07_R01_VIDEO_CONTROL1, 0x0e },
+	{ TM6010_REQ07_R02_VIDEO_CONTROL2, 0x5f },
+	{ TM6010_REQ07_R03_YC_SEP_CONTROL, 0x02 },
+	{ TM6010_REQ07_R07_OUTPUT_CONTROL, 0x31 },
+	{ TM6010_REQ07_R18_CHROMA_DTO_INCREMENT3, 0x24 },
+	{ TM6010_REQ07_R19_CHROMA_DTO_INCREMENT2, 0x92 },
+	{ TM6010_REQ07_R1A_CHROMA_DTO_INCREMENT1, 0xe8 },
+	{ TM6010_REQ07_R1B_CHROMA_DTO_INCREMENT0, 0xed },
+	{ TM6010_REQ07_R1C_HSYNC_DTO_INCREMENT3, 0x1c },
+	{ TM6010_REQ07_R1D_HSYNC_DTO_INCREMENT2, 0xcc },
+	{ TM6010_REQ07_R1E_HSYNC_DTO_INCREMENT1, 0xcc },
+	{ TM6010_REQ07_R1F_HSYNC_DTO_INCREMENT0, 0xcd },
+	{ TM6010_REQ07_R2E_ACTIVE_VIDEO_HSTART, 0x8c },
+	{ TM6010_REQ07_R30_ACTIVE_VIDEO_VSTART, 0x2c },
+	{ TM6010_REQ07_R31_ACTIVE_VIDEO_VHIGHT, 0xc1 },
+	{ TM6010_REQ07_R33_VSYNC_HLOCK_MAX, 0x2c },
+	{ TM6010_REQ07_R35_VSYNC_AGC_MAX, 0x18 },
+	{ TM6010_REQ07_R82_COMB_FILTER_CONFIG, 0x42 },
+	{ TM6010_REQ07_R83_CHROMA_LOCK_CONFIG, 0xff },
+	{ TM6010_REQ07_R0D_CHROMA_KILL_LEVEL, 0x07 },
+	{ TM6010_REQ07_R3F_RESET, 0x00 },
+	{ 0, 0, 0 }
+};
+
+static struct tm6000_reg_settings composite_ntsc[] = {
+	{ TM6010_REQ07_R3F_RESET, 0x01 },
+	{ TM6010_REQ07_R00_VIDEO_CONTROL0, 0x00 },
+	{ TM6010_REQ07_R01_VIDEO_CONTROL1, 0x0f },
+	{ TM6010_REQ07_R02_VIDEO_CONTROL2, 0x5f },
+	{ TM6010_REQ07_R03_YC_SEP_CONTROL, 0x00 },
+	{ TM6010_REQ07_R07_OUTPUT_CONTROL, 0x31 },
+	{ TM6010_REQ07_R18_CHROMA_DTO_INCREMENT3, 0x1e },
+	{ TM6010_REQ07_R19_CHROMA_DTO_INCREMENT2, 0x8b },
+	{ TM6010_REQ07_R1A_CHROMA_DTO_INCREMENT1, 0xa2 },
+	{ TM6010_REQ07_R1B_CHROMA_DTO_INCREMENT0, 0xe9 },
+	{ TM6010_REQ07_R1C_HSYNC_DTO_INCREMENT3, 0x1c },
+	{ TM6010_REQ07_R1D_HSYNC_DTO_INCREMENT2, 0xcc },
+	{ TM6010_REQ07_R1E_HSYNC_DTO_INCREMENT1, 0xcc },
+	{ TM6010_REQ07_R1F_HSYNC_DTO_INCREMENT0, 0xcd },
+	{ TM6010_REQ07_R2E_ACTIVE_VIDEO_HSTART, 0x88 },
+	{ TM6010_REQ07_R30_ACTIVE_VIDEO_VSTART, 0x22 },
+	{ TM6010_REQ07_R31_ACTIVE_VIDEO_VHIGHT, 0x61 },
+	{ TM6010_REQ07_R33_VSYNC_HLOCK_MAX, 0x1c },
+	{ TM6010_REQ07_R35_VSYNC_AGC_MAX, 0x1c },
+	{ TM6010_REQ07_R82_COMB_FILTER_CONFIG, 0x42 },
+	{ TM6010_REQ07_R83_CHROMA_LOCK_CONFIG, 0x6f },
+	{ TM6010_REQ07_R04_LUMA_HAGC_CONTROL, 0xdd },
+	{ TM6010_REQ07_R0D_CHROMA_KILL_LEVEL, 0x07 },
+	{ TM6010_REQ07_R3F_RESET, 0x00 },
+	{ 0, 0, 0 }
+};
+
+static struct tm6000_std_settings composite_stds[] = {
+	{ .id = V4L2_STD_PAL_M, .common = composite_pal_m, },
+	{ .id = V4L2_STD_PAL_Nc, .common = composite_pal_nc, },
+	{ .id = V4L2_STD_PAL, .common = composite_pal, },
+	{ .id = V4L2_STD_SECAM, .common = composite_secam, },
+	{ .id = V4L2_STD_NTSC, .common = composite_ntsc, },
+};
+
+static struct tm6000_reg_settings svideo_pal_m[] = {
+	{ TM6010_REQ07_R3F_RESET, 0x01 },
+	{ TM6010_REQ07_R00_VIDEO_CONTROL0, 0x05 },
+	{ TM6010_REQ07_R01_VIDEO_CONTROL1, 0x0e },
+	{ TM6010_REQ07_R02_VIDEO_CONTROL2, 0x5f },
+	{ TM6010_REQ07_R03_YC_SEP_CONTROL, 0x04 },
+	{ TM6010_REQ07_R07_OUTPUT_CONTROL, 0x31 },
+	{ TM6010_REQ07_R18_CHROMA_DTO_INCREMENT3, 0x1e },
+	{ TM6010_REQ07_R19_CHROMA_DTO_INCREMENT2, 0x83 },
+	{ TM6010_REQ07_R1A_CHROMA_DTO_INCREMENT1, 0x0a },
+	{ TM6010_REQ07_R1B_CHROMA_DTO_INCREMENT0, 0xe0 },
+	{ TM6010_REQ07_R1C_HSYNC_DTO_INCREMENT3, 0x1c },
+	{ TM6010_REQ07_R1D_HSYNC_DTO_INCREMENT2, 0xcc },
+	{ TM6010_REQ07_R1E_HSYNC_DTO_INCREMENT1, 0xcc },
+	{ TM6010_REQ07_R1F_HSYNC_DTO_INCREMENT0, 0xcd },
+	{ TM6010_REQ07_R2E_ACTIVE_VIDEO_HSTART, 0x88 },
+	{ TM6010_REQ07_R30_ACTIVE_VIDEO_VSTART, 0x22 },
+	{ TM6010_REQ07_R31_ACTIVE_VIDEO_VHIGHT, 0x61 },
+	{ TM6010_REQ07_R33_VSYNC_HLOCK_MAX, 0x0c },
+	{ TM6010_REQ07_R35_VSYNC_AGC_MAX, 0x1c },
+	{ TM6010_REQ07_R82_COMB_FILTER_CONFIG, 0x52 },
+	{ TM6010_REQ07_R83_CHROMA_LOCK_CONFIG, 0x6f },
+	{ TM6010_REQ07_R04_LUMA_HAGC_CONTROL, 0xdc },
+	{ TM6010_REQ07_R0D_CHROMA_KILL_LEVEL, 0x07 },
+	{ TM6010_REQ07_R3F_RESET, 0x00 },
+	{ 0, 0, 0 }
+};
+
+static struct tm6000_reg_settings svideo_pal_nc[] = {
+	{ TM6010_REQ07_R3F_RESET, 0x01 },
+	{ TM6010_REQ07_R00_VIDEO_CONTROL0, 0x37 },
+	{ TM6010_REQ07_R01_VIDEO_CONTROL1, 0x0e },
+	{ TM6010_REQ07_R02_VIDEO_CONTROL2, 0x5f },
+	{ TM6010_REQ07_R03_YC_SEP_CONTROL, 0x04 },
+	{ TM6010_REQ07_R07_OUTPUT_CONTROL, 0x31 },
+	{ TM6010_REQ07_R18_CHROMA_DTO_INCREMENT3, 0x1e },
+	{ TM6010_REQ07_R19_CHROMA_DTO_INCREMENT2, 0x91 },
+	{ TM6010_REQ07_R1A_CHROMA_DTO_INCREMENT1, 0x1f },
+	{ TM6010_REQ07_R1B_CHROMA_DTO_INCREMENT0, 0x0c },
+	{ TM6010_REQ07_R1C_HSYNC_DTO_INCREMENT3, 0x1c },
+	{ TM6010_REQ07_R1D_HSYNC_DTO_INCREMENT2, 0xcc },
+	{ TM6010_REQ07_R1E_HSYNC_DTO_INCREMENT1, 0xcc },
+	{ TM6010_REQ07_R1F_HSYNC_DTO_INCREMENT0, 0xcd },
+	{ TM6010_REQ07_R2E_ACTIVE_VIDEO_HSTART, 0x88 },
+	{ TM6010_REQ07_R30_ACTIVE_VIDEO_VSTART, 0x22 },
+	{ TM6010_REQ07_R31_ACTIVE_VIDEO_VHIGHT, 0xc1 },
+	{ TM6010_REQ07_R33_VSYNC_HLOCK_MAX, 0x0c },
+	{ TM6010_REQ07_R35_VSYNC_AGC_MAX, 0x1c },
+	{ TM6010_REQ07_R82_COMB_FILTER_CONFIG, 0x52 },
+	{ TM6010_REQ07_R83_CHROMA_LOCK_CONFIG, 0x6f },
+	{ TM6010_REQ07_R04_LUMA_HAGC_CONTROL, 0xdc },
+	{ TM6010_REQ07_R0D_CHROMA_KILL_LEVEL, 0x07 },
+	{ TM6010_REQ07_R3F_RESET, 0x00 },
+	{ 0, 0, 0 }
+};
+
+static struct tm6000_reg_settings svideo_pal[] = {
+	{ TM6010_REQ07_R3F_RESET, 0x01 },
+	{ TM6010_REQ07_R00_VIDEO_CONTROL0, 0x33 },
+	{ TM6010_REQ07_R01_VIDEO_CONTROL1, 0x0e },
+	{ TM6010_REQ07_R02_VIDEO_CONTROL2, 0x5f },
+	{ TM6010_REQ07_R03_YC_SEP_CONTROL, 0x04 },
+	{ TM6010_REQ07_R07_OUTPUT_CONTROL, 0x30 },
+	{ TM6010_REQ07_R18_CHROMA_DTO_INCREMENT3, 0x25 },
+	{ TM6010_REQ07_R19_CHROMA_DTO_INCREMENT2, 0xd5 },
+	{ TM6010_REQ07_R1A_CHROMA_DTO_INCREMENT1, 0x63 },
+	{ TM6010_REQ07_R1B_CHROMA_DTO_INCREMENT0, 0x50 },
+	{ TM6010_REQ07_R1C_HSYNC_DTO_INCREMENT3, 0x1c },
+	{ TM6010_REQ07_R1D_HSYNC_DTO_INCREMENT2, 0xcc },
+	{ TM6010_REQ07_R1E_HSYNC_DTO_INCREMENT1, 0xcc },
+	{ TM6010_REQ07_R1F_HSYNC_DTO_INCREMENT0, 0xcd },
+	{ TM6010_REQ07_R2E_ACTIVE_VIDEO_HSTART, 0x8c },
+	{ TM6010_REQ07_R30_ACTIVE_VIDEO_VSTART, 0x2a },
+	{ TM6010_REQ07_R31_ACTIVE_VIDEO_VHIGHT, 0xc1 },
+	{ TM6010_REQ07_R33_VSYNC_HLOCK_MAX, 0x0c },
+	{ TM6010_REQ07_R35_VSYNC_AGC_MAX, 0x1c },
+	{ TM6010_REQ07_R82_COMB_FILTER_CONFIG, 0x52 },
+	{ TM6010_REQ07_R83_CHROMA_LOCK_CONFIG, 0x6f },
+	{ TM6010_REQ07_R04_LUMA_HAGC_CONTROL, 0xdc },
+	{ TM6010_REQ07_R0D_CHROMA_KILL_LEVEL, 0x07 },
+	{ TM6010_REQ07_R3F_RESET, 0x00 },
+	{ 0, 0, 0 }
+};
+
+static struct tm6000_reg_settings svideo_secam[] = {
+	{ TM6010_REQ07_R3F_RESET, 0x01 },
+	{ TM6010_REQ07_R00_VIDEO_CONTROL0, 0x39 },
+	{ TM6010_REQ07_R01_VIDEO_CONTROL1, 0x0e },
+	{ TM6010_REQ07_R02_VIDEO_CONTROL2, 0x5f },
+	{ TM6010_REQ07_R03_YC_SEP_CONTROL, 0x03 },
+	{ TM6010_REQ07_R07_OUTPUT_CONTROL, 0x31 },
+	{ TM6010_REQ07_R18_CHROMA_DTO_INCREMENT3, 0x24 },
+	{ TM6010_REQ07_R19_CHROMA_DTO_INCREMENT2, 0x92 },
+	{ TM6010_REQ07_R1A_CHROMA_DTO_INCREMENT1, 0xe8 },
+	{ TM6010_REQ07_R1B_CHROMA_DTO_INCREMENT0, 0xed },
+	{ TM6010_REQ07_R1C_HSYNC_DTO_INCREMENT3, 0x1c },
+	{ TM6010_REQ07_R1D_HSYNC_DTO_INCREMENT2, 0xcc },
+	{ TM6010_REQ07_R1E_HSYNC_DTO_INCREMENT1, 0xcc },
+	{ TM6010_REQ07_R1F_HSYNC_DTO_INCREMENT0, 0xcd },
+	{ TM6010_REQ07_R2E_ACTIVE_VIDEO_HSTART, 0x8c },
+	{ TM6010_REQ07_R30_ACTIVE_VIDEO_VSTART, 0x2a },
+	{ TM6010_REQ07_R31_ACTIVE_VIDEO_VHIGHT, 0xc1 },
+	{ TM6010_REQ07_R33_VSYNC_HLOCK_MAX, 0x2c },
+	{ TM6010_REQ07_R35_VSYNC_AGC_MAX, 0x18 },
+	{ TM6010_REQ07_R82_COMB_FILTER_CONFIG, 0x42 },
+	{ TM6010_REQ07_R83_CHROMA_LOCK_CONFIG, 0xff },
+	{ TM6010_REQ07_R0D_CHROMA_KILL_LEVEL, 0x07 },
+	{ TM6010_REQ07_R3F_RESET, 0x00 },
+	{ 0, 0, 0 }
+};
+
+static struct tm6000_reg_settings svideo_ntsc[] = {
+	{ TM6010_REQ07_R3F_RESET, 0x01 },
+	{ TM6010_REQ07_R00_VIDEO_CONTROL0, 0x01 },
+	{ TM6010_REQ07_R01_VIDEO_CONTROL1, 0x0f },
+	{ TM6010_REQ07_R02_VIDEO_CONTROL2, 0x5f },
+	{ TM6010_REQ07_R03_YC_SEP_CONTROL, 0x03 },
+	{ TM6010_REQ07_R07_OUTPUT_CONTROL, 0x30 },
+	{ TM6010_REQ07_R17_HLOOP_MAXSTATE, 0x8b },
+	{ TM6010_REQ07_R18_CHROMA_DTO_INCREMENT3, 0x1e },
+	{ TM6010_REQ07_R19_CHROMA_DTO_INCREMENT2, 0x8b },
+	{ TM6010_REQ07_R1A_CHROMA_DTO_INCREMENT1, 0xa2 },
+	{ TM6010_REQ07_R1B_CHROMA_DTO_INCREMENT0, 0xe9 },
+	{ TM6010_REQ07_R1C_HSYNC_DTO_INCREMENT3, 0x1c },
+	{ TM6010_REQ07_R1D_HSYNC_DTO_INCREMENT2, 0xcc },
+	{ TM6010_REQ07_R1E_HSYNC_DTO_INCREMENT1, 0xcc },
+	{ TM6010_REQ07_R1F_HSYNC_DTO_INCREMENT0, 0xcd },
+	{ TM6010_REQ07_R2E_ACTIVE_VIDEO_HSTART, 0x88 },
+	{ TM6010_REQ07_R30_ACTIVE_VIDEO_VSTART, 0x22 },
+	{ TM6010_REQ07_R31_ACTIVE_VIDEO_VHIGHT, 0x61 },
+	{ TM6010_REQ07_R33_VSYNC_HLOCK_MAX, 0x1c },
+	{ TM6010_REQ07_R35_VSYNC_AGC_MAX, 0x1c },
+	{ TM6010_REQ07_R82_COMB_FILTER_CONFIG, 0x42 },
+	{ TM6010_REQ07_R83_CHROMA_LOCK_CONFIG, 0x6f },
+	{ TM6010_REQ07_R04_LUMA_HAGC_CONTROL, 0xdd },
+	{ TM6010_REQ07_R0D_CHROMA_KILL_LEVEL, 0x07 },
+	{ TM6010_REQ07_R3F_RESET, 0x00 },
+	{ 0, 0, 0 }
+};
+
+static struct tm6000_std_settings svideo_stds[] = {
+	{ .id = V4L2_STD_PAL_M, .common = svideo_pal_m, },
+	{ .id = V4L2_STD_PAL_Nc, .common = svideo_pal_nc, },
+	{ .id = V4L2_STD_PAL, .common = svideo_pal, },
+	{ .id = V4L2_STD_SECAM, .common = svideo_secam, },
+	{ .id = V4L2_STD_NTSC, .common = svideo_ntsc, },
+};
+
+static int tm6000_set_audio_std(struct tm6000_core *dev)
+{
+	uint8_t areg_02 = 0x04; /* GC1 Fixed gain 0dB */
+	uint8_t areg_05 = 0x01; /* Auto 4.5 = M Japan, Auto 6.5 = DK */
+	uint8_t areg_06 = 0x02; /* Auto de-emphasis, mannual channel mode */
+
+	if (dev->radio) {
+		tm6000_set_reg(dev, TM6010_REQ08_R01_A_INIT, 0x00);
+		tm6000_set_reg(dev, TM6010_REQ08_R02_A_FIX_GAIN_CTRL, 0x04);
+		tm6000_set_reg(dev, TM6010_REQ08_R03_A_AUTO_GAIN_CTRL, 0x00);
+		tm6000_set_reg(dev, TM6010_REQ08_R04_A_SIF_AMP_CTRL, 0x80);
+		tm6000_set_reg(dev, TM6010_REQ08_R05_A_STANDARD_MOD, 0x0c);
+		/* set mono or stereo */
+		if (dev->amode == V4L2_TUNER_MODE_MONO)
+			tm6000_set_reg(dev, TM6010_REQ08_R06_A_SOUND_MOD, 0x00);
+		else if (dev->amode == V4L2_TUNER_MODE_STEREO)
+			tm6000_set_reg(dev, TM6010_REQ08_R06_A_SOUND_MOD, 0x02);
+		tm6000_set_reg(dev, TM6010_REQ08_R09_A_MAIN_VOL, 0x18);
+		tm6000_set_reg(dev, TM6010_REQ08_R0C_A_ASD_THRES2, 0x0a);
+		tm6000_set_reg(dev, TM6010_REQ08_R0D_A_AMD_THRES, 0x40);
+		tm6000_set_reg(dev, TM6010_REQ08_RF1_AADC_POWER_DOWN, 0xfe);
+		tm6000_set_reg(dev, TM6010_REQ08_R1E_A_GAIN_DEEMPH_OUT, 0x13);
+		tm6000_set_reg(dev, TM6010_REQ08_R01_A_INIT, 0x80);
+		tm6000_set_reg(dev, TM6010_REQ07_RFE_POWER_DOWN, 0xff);
+		return 0;
+	}
+
+	/*
+	 * STD/MN shouldn't be affected by tm6010_a_mode, as there's just one
+	 * audio standard for each V4L2_STD type.
+	 */
+	if ((dev->norm & V4L2_STD_NTSC) == V4L2_STD_NTSC_M_KR) {
+		areg_05 |= 0x04;
+	} else if ((dev->norm & V4L2_STD_NTSC) == V4L2_STD_NTSC_M_JP) {
+		areg_05 |= 0x43;
+	} else if (dev->norm & V4L2_STD_MN) {
+		areg_05 |= 0x22;
+	} else switch (tm6010_a_mode) {
+	/* auto */
+	case 0:
+		if ((dev->norm & V4L2_STD_SECAM) == V4L2_STD_SECAM_L)
+			areg_05 |= 0x00;
+		else	/* Other PAL/SECAM standards */
+			areg_05 |= 0x10;
+		break;
+	/* A2 */
+	case 1:
+		if (dev->norm & V4L2_STD_DK)
+			areg_05 = 0x09;
+		else
+			areg_05 = 0x05;
+		break;
+	/* NICAM */
+	case 2:
+		if (dev->norm & V4L2_STD_DK) {
+			areg_05 = 0x06;
+		} else if (dev->norm & V4L2_STD_PAL_I) {
+			areg_05 = 0x08;
+		} else if (dev->norm & V4L2_STD_SECAM_L) {
+			areg_05 = 0x0a;
+			areg_02 = 0x02;
+		} else {
+			areg_05 = 0x07;
+		}
+		break;
+	/* other */
+	case 3:
+		if (dev->norm & V4L2_STD_DK) {
+			areg_05 = 0x0b;
+		} else {
+			areg_05 = 0x02;
+		}
+		break;
+	}
+
+	tm6000_set_reg(dev, TM6010_REQ08_R01_A_INIT, 0x00);
+	tm6000_set_reg(dev, TM6010_REQ08_R02_A_FIX_GAIN_CTRL, areg_02);
+	tm6000_set_reg(dev, TM6010_REQ08_R03_A_AUTO_GAIN_CTRL, 0x00);
+	tm6000_set_reg(dev, TM6010_REQ08_R04_A_SIF_AMP_CTRL, 0xa0);
+	tm6000_set_reg(dev, TM6010_REQ08_R05_A_STANDARD_MOD, areg_05);
+	tm6000_set_reg(dev, TM6010_REQ08_R06_A_SOUND_MOD, areg_06);
+	tm6000_set_reg(dev, TM6010_REQ08_R07_A_LEFT_VOL, 0x00);
+	tm6000_set_reg(dev, TM6010_REQ08_R08_A_RIGHT_VOL, 0x00);
+	tm6000_set_reg(dev, TM6010_REQ08_R09_A_MAIN_VOL, 0x08);
+	tm6000_set_reg(dev, TM6010_REQ08_R0A_A_I2S_MOD, 0x91);
+	tm6000_set_reg(dev, TM6010_REQ08_R0B_A_ASD_THRES1, 0x20);
+	tm6000_set_reg(dev, TM6010_REQ08_R0C_A_ASD_THRES2, 0x12);
+	tm6000_set_reg(dev, TM6010_REQ08_R0D_A_AMD_THRES, 0x20);
+	tm6000_set_reg(dev, TM6010_REQ08_R0E_A_MONO_THRES1, 0xf0);
+	tm6000_set_reg(dev, TM6010_REQ08_R0F_A_MONO_THRES2, 0x80);
+	tm6000_set_reg(dev, TM6010_REQ08_R10_A_MUTE_THRES1, 0xc0);
+	tm6000_set_reg(dev, TM6010_REQ08_R11_A_MUTE_THRES2, 0x80);
+	tm6000_set_reg(dev, TM6010_REQ08_R12_A_AGC_U, 0x12);
+	tm6000_set_reg(dev, TM6010_REQ08_R13_A_AGC_ERR_T, 0xfe);
+	tm6000_set_reg(dev, TM6010_REQ08_R14_A_AGC_GAIN_INIT, 0x20);
+	tm6000_set_reg(dev, TM6010_REQ08_R15_A_AGC_STEP_THR, 0x14);
+	tm6000_set_reg(dev, TM6010_REQ08_R16_A_AGC_GAIN_MAX, 0xfe);
+	tm6000_set_reg(dev, TM6010_REQ08_R17_A_AGC_GAIN_MIN, 0x01);
+	tm6000_set_reg(dev, TM6010_REQ08_R18_A_TR_CTRL, 0xa0);
+	tm6000_set_reg(dev, TM6010_REQ08_R19_A_FH_2FH_GAIN, 0x32);
+	tm6000_set_reg(dev, TM6010_REQ08_R1A_A_NICAM_SER_MAX, 0x64);
+	tm6000_set_reg(dev, TM6010_REQ08_R1B_A_NICAM_SER_MIN, 0x20);
+	tm6000_set_reg(dev, REQ_08_SET_GET_AVREG_BIT, 0x1c, 0x00);
+	tm6000_set_reg(dev, REQ_08_SET_GET_AVREG_BIT, 0x1d, 0x00);
+	tm6000_set_reg(dev, TM6010_REQ08_R1E_A_GAIN_DEEMPH_OUT, 0x13);
+	tm6000_set_reg(dev, TM6010_REQ08_R1F_A_TEST_INTF_SEL, 0x00);
+	tm6000_set_reg(dev, TM6010_REQ08_R20_A_TEST_PIN_SEL, 0x00);
+	tm6000_set_reg(dev, TM6010_REQ08_R01_A_INIT, 0x80);
+
+	return 0;
+}
+
+void tm6000_get_std_res(struct tm6000_core *dev)
+{
+	/* Currently, those are the only supported resoltions */
+	if (dev->norm & V4L2_STD_525_60)
+		dev->height = 480;
+	else
+		dev->height = 576;
+
+	dev->width = 720;
+}
+
+static int tm6000_load_std(struct tm6000_core *dev, struct tm6000_reg_settings *set)
+{
+	int i, rc;
+
+	/* Load board's initialization table */
+	for (i = 0; set[i].req; i++) {
+		rc = tm6000_set_reg(dev, set[i].req, set[i].reg, set[i].value);
+		if (rc < 0) {
+			printk(KERN_ERR "Error %i while setting req %d, reg %d to value %d\n",
+			       rc, set[i].req, set[i].reg, set[i].value);
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+int tm6000_set_standard(struct tm6000_core *dev)
+{
+	struct tm6000_input *input;
+	int i, rc = 0;
+	u8 reg_07_fe = 0x8a;
+	u8 reg_08_f1 = 0xfc;
+	u8 reg_08_e2 = 0xf0;
+	u8 reg_08_e6 = 0x0f;
+
+	tm6000_get_std_res(dev);
+
+	if (!dev->radio)
+		input = &dev->vinput[dev->input];
+	else
+		input = &dev->rinput;
+
+	if (dev->dev_type == TM6010) {
+		switch (input->vmux) {
+		case TM6000_VMUX_VIDEO_A:
+			tm6000_set_reg(dev, TM6010_REQ08_RE3_ADC_IN1_SEL, 0xf4);
+			tm6000_set_reg(dev, TM6010_REQ08_REA_BUFF_DRV_CTRL, 0xf1);
+			tm6000_set_reg(dev, TM6010_REQ08_REB_SIF_GAIN_CTRL, 0xe0);
+			tm6000_set_reg(dev, TM6010_REQ08_REC_REVERSE_YC_CTRL, 0xc2);
+			tm6000_set_reg(dev, TM6010_REQ08_RED_GAIN_SEL, 0xe8);
+			reg_07_fe |= 0x01;
+			break;
+		case TM6000_VMUX_VIDEO_B:
+			tm6000_set_reg(dev, TM6010_REQ08_RE3_ADC_IN1_SEL, 0xf8);
+			tm6000_set_reg(dev, TM6010_REQ08_REA_BUFF_DRV_CTRL, 0xf1);
+			tm6000_set_reg(dev, TM6010_REQ08_REB_SIF_GAIN_CTRL, 0xe0);
+			tm6000_set_reg(dev, TM6010_REQ08_REC_REVERSE_YC_CTRL, 0xc2);
+			tm6000_set_reg(dev, TM6010_REQ08_RED_GAIN_SEL, 0xe8);
+			reg_07_fe |= 0x01;
+			break;
+		case TM6000_VMUX_VIDEO_AB:
+			tm6000_set_reg(dev, TM6010_REQ08_RE3_ADC_IN1_SEL, 0xfc);
+			tm6000_set_reg(dev, TM6010_REQ08_RE4_ADC_IN2_SEL, 0xf8);
+			reg_08_e6 = 0x00;
+			tm6000_set_reg(dev, TM6010_REQ08_REA_BUFF_DRV_CTRL, 0xf2);
+			tm6000_set_reg(dev, TM6010_REQ08_REB_SIF_GAIN_CTRL, 0xf0);
+			tm6000_set_reg(dev, TM6010_REQ08_REC_REVERSE_YC_CTRL, 0xc2);
+			tm6000_set_reg(dev, TM6010_REQ08_RED_GAIN_SEL, 0xe0);
+			break;
+		default:
+			break;
+		}
+		switch (input->amux) {
+		case TM6000_AMUX_ADC1:
+			tm6000_set_reg_mask(dev, TM6010_REQ08_RF0_DAUDIO_INPUT_CONFIG,
+				0x00, 0x0f);
+			/* Mux overflow workaround */
+			tm6000_set_reg_mask(dev, TM6010_REQ07_R07_OUTPUT_CONTROL,
+				0x10, 0xf0);
+			break;
+		case TM6000_AMUX_ADC2:
+			tm6000_set_reg_mask(dev, TM6010_REQ08_RF0_DAUDIO_INPUT_CONFIG,
+				0x08, 0x0f);
+			/* Mux overflow workaround */
+			tm6000_set_reg_mask(dev, TM6010_REQ07_R07_OUTPUT_CONTROL,
+				0x10, 0xf0);
+			break;
+		case TM6000_AMUX_SIF1:
+			reg_08_e2 |= 0x02;
+			reg_08_e6 = 0x08;
+			reg_07_fe |= 0x40;
+			reg_08_f1 |= 0x02;
+			tm6000_set_reg(dev, TM6010_REQ08_RE4_ADC_IN2_SEL, 0xf3);
+			tm6000_set_reg_mask(dev, TM6010_REQ08_RF0_DAUDIO_INPUT_CONFIG,
+				0x02, 0x0f);
+			/* Mux overflow workaround */
+			tm6000_set_reg_mask(dev, TM6010_REQ07_R07_OUTPUT_CONTROL,
+				0x30, 0xf0);
+			break;
+		case TM6000_AMUX_SIF2:
+			reg_08_e2 |= 0x02;
+			reg_08_e6 = 0x08;
+			reg_07_fe |= 0x40;
+			reg_08_f1 |= 0x02;
+			tm6000_set_reg(dev, TM6010_REQ08_RE4_ADC_IN2_SEL, 0xf7);
+			tm6000_set_reg_mask(dev, TM6010_REQ08_RF0_DAUDIO_INPUT_CONFIG,
+				0x02, 0x0f);
+			/* Mux overflow workaround */
+			tm6000_set_reg_mask(dev, TM6010_REQ07_R07_OUTPUT_CONTROL,
+				0x30, 0xf0);
+			break;
+		default:
+			break;
+		}
+		tm6000_set_reg(dev, TM6010_REQ08_RE2_POWER_DOWN_CTRL1, reg_08_e2);
+		tm6000_set_reg(dev, TM6010_REQ08_RE6_POWER_DOWN_CTRL2, reg_08_e6);
+		tm6000_set_reg(dev, TM6010_REQ08_RF1_AADC_POWER_DOWN, reg_08_f1);
+		tm6000_set_reg(dev, TM6010_REQ07_RFE_POWER_DOWN, reg_07_fe);
+	} else {
+		switch (input->vmux) {
+		case TM6000_VMUX_VIDEO_A:
+			tm6000_set_reg(dev, TM6000_REQ07_RE3_VADC_INP_LPF_SEL1, 0x10);
+			tm6000_set_reg(dev, TM6000_REQ07_RE5_VADC_INP_LPF_SEL2, 0x00);
+			tm6000_set_reg(dev, TM6000_REQ07_RE8_VADC_PWDOWN_CTL, 0x0f);
+			tm6000_set_reg(dev,
+			    REQ_03_SET_GET_MCU_PIN, input->v_gpio, 0);
+			break;
+		case TM6000_VMUX_VIDEO_B:
+			tm6000_set_reg(dev, TM6000_REQ07_RE3_VADC_INP_LPF_SEL1, 0x00);
+			tm6000_set_reg(dev, TM6000_REQ07_RE5_VADC_INP_LPF_SEL2, 0x00);
+			tm6000_set_reg(dev, TM6000_REQ07_RE8_VADC_PWDOWN_CTL, 0x0f);
+			tm6000_set_reg(dev,
+			    REQ_03_SET_GET_MCU_PIN, input->v_gpio, 0);
+			break;
+		case TM6000_VMUX_VIDEO_AB:
+			tm6000_set_reg(dev, TM6000_REQ07_RE3_VADC_INP_LPF_SEL1, 0x10);
+			tm6000_set_reg(dev, TM6000_REQ07_RE5_VADC_INP_LPF_SEL2, 0x10);
+			tm6000_set_reg(dev, TM6000_REQ07_RE8_VADC_PWDOWN_CTL, 0x00);
+			tm6000_set_reg(dev,
+			    REQ_03_SET_GET_MCU_PIN, input->v_gpio, 1);
+			break;
+		default:
+			break;
+		}
+		switch (input->amux) {
+		case TM6000_AMUX_ADC1:
+			tm6000_set_reg_mask(dev,
+				TM6000_REQ07_REB_VADC_AADC_MODE, 0x00, 0x0f);
+			break;
+		case TM6000_AMUX_ADC2:
+			tm6000_set_reg_mask(dev,
+				TM6000_REQ07_REB_VADC_AADC_MODE, 0x04, 0x0f);
+			break;
+		default:
+			break;
+		}
+	}
+	if (input->type == TM6000_INPUT_SVIDEO) {
+		for (i = 0; i < ARRAY_SIZE(svideo_stds); i++) {
+			if (dev->norm & svideo_stds[i].id) {
+				rc = tm6000_load_std(dev, svideo_stds[i].common);
+				goto ret;
+			}
+		}
+		return -EINVAL;
+	} else {
+		for (i = 0; i < ARRAY_SIZE(composite_stds); i++) {
+			if (dev->norm & composite_stds[i].id) {
+				rc = tm6000_load_std(dev, composite_stds[i].common);
+				goto ret;
+			}
+		}
+		return -EINVAL;
+	}
+
+ret:
+	if (rc < 0)
+		return rc;
+
+	if ((dev->dev_type == TM6010) &&
+	    ((input->amux == TM6000_AMUX_SIF1) ||
+	    (input->amux == TM6000_AMUX_SIF2)))
+		tm6000_set_audio_std(dev);
+
+	msleep(40);
+
+	return 0;
+}
diff --git a/drivers/media/usb/tm6000/tm6000-usb-isoc.h b/drivers/media/usb/tm6000/tm6000-usb-isoc.h
new file mode 100644
index 0000000..b275dbc
--- /dev/null
+++ b/drivers/media/usb/tm6000/tm6000-usb-isoc.h
@@ -0,0 +1,38 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0
+ * tm6000-buf.c - driver for TM5600/TM6000/TM6010 USB video capture devices
+ *
+ * Copyright (c) 2006-2007 Mauro Carvalho Chehab <mchehab@kernel.org>
+ */
+
+#include <linux/videodev2.h>
+
+#define TM6000_URB_MSG_LEN 180
+
+struct usb_isoc_ctl {
+		/* max packet size of isoc transaction */
+	int				max_pkt_size;
+
+		/* number of allocated urbs */
+	int				num_bufs;
+
+		/* urb for isoc transfers */
+	struct urb			**urb;
+
+		/* transfer buffers for isoc transfer */
+	char				**transfer_buffer;
+
+		/* Last buffer command and region */
+	u8				cmd;
+	int				pos, size, pktsize;
+
+		/* Last field: ODD or EVEN? */
+	int				vfield, field;
+
+		/* Stores incomplete commands */
+	u32				tmp_buf;
+	int				tmp_buf_len;
+
+		/* Stores already requested buffers */
+	struct tm6000_buffer		*buf;
+};
diff --git a/drivers/media/usb/tm6000/tm6000-video.c b/drivers/media/usb/tm6000/tm6000-video.c
new file mode 100644
index 0000000..96055de
--- /dev/null
+++ b/drivers/media/usb/tm6000/tm6000-video.c
@@ -0,0 +1,1709 @@
+// SPDX-License-Identifier: GPL-2.0
+// tm6000-video.c - driver for TM5600/TM6000/TM6010 USB video capture devices
+//
+// Copyright (c) 2006-2007 Mauro Carvalho Chehab <mchehab@kernel.org>
+//
+// Copyright (c) 2007 Michel Ludwig <michel.ludwig@gmail.com>
+//	- Fixed module load/unload
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/ioport.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/random.h>
+#include <linux/usb.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-event.h>
+#include <media/tuner.h>
+#include <linux/interrupt.h>
+#include <linux/kthread.h>
+#include <linux/highmem.h>
+#include <linux/freezer.h>
+
+#include "tm6000-regs.h"
+#include "tm6000.h"
+
+#define BUFFER_TIMEOUT     msecs_to_jiffies(2000)  /* 2 seconds */
+
+/* Limits minimum and default number of buffers */
+#define TM6000_MIN_BUF 4
+#define TM6000_DEF_BUF 8
+
+#define TM6000_NUM_URB_BUF 8
+
+#define TM6000_MAX_ISO_PACKETS	46	/* Max number of ISO packets */
+
+/* Declare static vars that will be used as parameters */
+static unsigned int vid_limit = 16;	/* Video memory limit, in Mb */
+static int video_nr = -1;		/* /dev/videoN, -1 for autodetect */
+static int radio_nr = -1;		/* /dev/radioN, -1 for autodetect */
+static bool keep_urb;			/* keep urb buffers allocated */
+
+/* Debug level */
+int tm6000_debug;
+EXPORT_SYMBOL_GPL(tm6000_debug);
+
+static struct tm6000_fmt format[] = {
+	{
+		.name     = "4:2:2, packed, YVY2",
+		.fourcc   = V4L2_PIX_FMT_YUYV,
+		.depth    = 16,
+	}, {
+		.name     = "4:2:2, packed, UYVY",
+		.fourcc   = V4L2_PIX_FMT_UYVY,
+		.depth    = 16,
+	}, {
+		.name     = "A/V + VBI mux packet",
+		.fourcc   = V4L2_PIX_FMT_TM6000,
+		.depth    = 16,
+	}
+};
+
+/* ------------------------------------------------------------------
+ *	DMA and thread functions
+ * ------------------------------------------------------------------
+ */
+
+#define norm_maxw(a) 720
+#define norm_maxh(a) 576
+
+#define norm_minw(a) norm_maxw(a)
+#define norm_minh(a) norm_maxh(a)
+
+/*
+ * video-buf generic routine to get the next available buffer
+ */
+static inline void get_next_buf(struct tm6000_dmaqueue *dma_q,
+			       struct tm6000_buffer   **buf)
+{
+	struct tm6000_core *dev = container_of(dma_q, struct tm6000_core, vidq);
+
+	if (list_empty(&dma_q->active)) {
+		dprintk(dev, V4L2_DEBUG_QUEUE, "No active queue to serve\n");
+		*buf = NULL;
+		return;
+	}
+
+	*buf = list_entry(dma_q->active.next,
+			struct tm6000_buffer, vb.queue);
+}
+
+/*
+ * Announces that a buffer were filled and request the next
+ */
+static inline void buffer_filled(struct tm6000_core *dev,
+				 struct tm6000_dmaqueue *dma_q,
+				 struct tm6000_buffer *buf)
+{
+	/* Advice that buffer was filled */
+	dprintk(dev, V4L2_DEBUG_ISOC, "[%p/%d] wakeup\n", buf, buf->vb.i);
+	buf->vb.state = VIDEOBUF_DONE;
+	buf->vb.field_count++;
+	v4l2_get_timestamp(&buf->vb.ts);
+
+	list_del(&buf->vb.queue);
+	wake_up(&buf->vb.done);
+}
+
+/*
+ * Identify the tm5600/6000 buffer header type and properly handles
+ */
+static int copy_streams(u8 *data, unsigned long len,
+			struct urb *urb)
+{
+	struct tm6000_dmaqueue  *dma_q = urb->context;
+	struct tm6000_core *dev = container_of(dma_q, struct tm6000_core, vidq);
+	u8 *ptr = data, *endp = data+len;
+	unsigned long header = 0;
+	int rc = 0;
+	unsigned int cmd, cpysize, pktsize, size, field, block, line, pos = 0;
+	struct tm6000_buffer *vbuf = NULL;
+	char *voutp = NULL;
+	unsigned int linewidth;
+
+	if (!dev->radio) {
+		/* get video buffer */
+		get_next_buf(dma_q, &vbuf);
+
+		if (!vbuf)
+			return rc;
+		voutp = videobuf_to_vmalloc(&vbuf->vb);
+
+		if (!voutp)
+			return 0;
+	}
+
+	for (ptr = data; ptr < endp;) {
+		if (!dev->isoc_ctl.cmd) {
+			/* Header */
+			if (dev->isoc_ctl.tmp_buf_len > 0) {
+				/* from last urb or packet */
+				header = dev->isoc_ctl.tmp_buf;
+				if (4 - dev->isoc_ctl.tmp_buf_len > 0) {
+					memcpy((u8 *)&header +
+						dev->isoc_ctl.tmp_buf_len,
+						ptr,
+						4 - dev->isoc_ctl.tmp_buf_len);
+					ptr += 4 - dev->isoc_ctl.tmp_buf_len;
+				}
+				dev->isoc_ctl.tmp_buf_len = 0;
+			} else {
+				if (ptr + 3 >= endp) {
+					/* have incomplete header */
+					dev->isoc_ctl.tmp_buf_len = endp - ptr;
+					memcpy(&dev->isoc_ctl.tmp_buf, ptr,
+						dev->isoc_ctl.tmp_buf_len);
+					return rc;
+				}
+				/* Seek for sync */
+				for (; ptr < endp - 3; ptr++) {
+					if (*(ptr + 3) == 0x47)
+						break;
+				}
+				/* Get message header */
+				header = *(unsigned long *)ptr;
+				ptr += 4;
+			}
+
+			/* split the header fields */
+			size = ((header & 0x7e) << 1);
+			if (size > 0)
+				size -= 4;
+			block = (header >> 7) & 0xf;
+			field = (header >> 11) & 0x1;
+			line  = (header >> 12) & 0x1ff;
+			cmd   = (header >> 21) & 0x7;
+			/* Validates haeder fields */
+			if (size > TM6000_URB_MSG_LEN)
+				size = TM6000_URB_MSG_LEN;
+			pktsize = TM6000_URB_MSG_LEN;
+			/*
+			 * calculate position in buffer and change the buffer
+			 */
+			switch (cmd) {
+			case TM6000_URB_MSG_VIDEO:
+				if (!dev->radio) {
+					if ((dev->isoc_ctl.vfield != field) &&
+						(field == 1)) {
+						/*
+						 * Announces that a new buffer
+						 * were filled
+						 */
+						buffer_filled(dev, dma_q, vbuf);
+						dprintk(dev, V4L2_DEBUG_ISOC,
+							"new buffer filled\n");
+						get_next_buf(dma_q, &vbuf);
+						if (!vbuf)
+							return rc;
+						voutp = videobuf_to_vmalloc(&vbuf->vb);
+						if (!voutp)
+							return rc;
+						memset(voutp, 0, vbuf->vb.size);
+					}
+					linewidth = vbuf->vb.width << 1;
+					pos = ((line << 1) - field - 1) *
+					linewidth + block * TM6000_URB_MSG_LEN;
+					/* Don't allow to write out of the buffer */
+					if (pos + size > vbuf->vb.size)
+						cmd = TM6000_URB_MSG_ERR;
+					dev->isoc_ctl.vfield = field;
+				}
+				break;
+			case TM6000_URB_MSG_VBI:
+				break;
+			case TM6000_URB_MSG_AUDIO:
+			case TM6000_URB_MSG_PTS:
+				size = pktsize; /* Size is always 180 bytes */
+				break;
+			}
+		} else {
+			/* Continue the last copy */
+			cmd = dev->isoc_ctl.cmd;
+			size = dev->isoc_ctl.size;
+			pos = dev->isoc_ctl.pos;
+			pktsize = dev->isoc_ctl.pktsize;
+			field = dev->isoc_ctl.field;
+		}
+		cpysize = (endp - ptr > size) ? size : endp - ptr;
+		if (cpysize) {
+			/* copy data in different buffers */
+			switch (cmd) {
+			case TM6000_URB_MSG_VIDEO:
+				/* Fills video buffer */
+				if (vbuf)
+					memcpy(&voutp[pos], ptr, cpysize);
+				break;
+			case TM6000_URB_MSG_AUDIO: {
+				int i;
+				for (i = 0; i < cpysize; i += 2)
+					swab16s((u16 *)(ptr + i));
+
+				tm6000_call_fillbuf(dev, TM6000_AUDIO, ptr, cpysize);
+				break;
+			}
+			case TM6000_URB_MSG_VBI:
+				/* Need some code to copy vbi buffer */
+				break;
+			case TM6000_URB_MSG_PTS: {
+				/* Need some code to copy pts */
+				u32 pts;
+				pts = *(u32 *)ptr;
+				dprintk(dev, V4L2_DEBUG_ISOC, "field %d, PTS %x",
+					field, pts);
+				break;
+			}
+			}
+		}
+		if (ptr + pktsize > endp) {
+			/*
+			 * End of URB packet, but cmd processing is not
+			 * complete. Preserve the state for a next packet
+			 */
+			dev->isoc_ctl.pos = pos + cpysize;
+			dev->isoc_ctl.size = size - cpysize;
+			dev->isoc_ctl.cmd = cmd;
+			dev->isoc_ctl.field = field;
+			dev->isoc_ctl.pktsize = pktsize - (endp - ptr);
+			ptr += endp - ptr;
+		} else {
+			dev->isoc_ctl.cmd = 0;
+			ptr += pktsize;
+		}
+	}
+	return 0;
+}
+
+/*
+ * Identify the tm5600/6000 buffer header type and properly handles
+ */
+static int copy_multiplexed(u8 *ptr, unsigned long len,
+			struct urb *urb)
+{
+	struct tm6000_dmaqueue  *dma_q = urb->context;
+	struct tm6000_core *dev = container_of(dma_q, struct tm6000_core, vidq);
+	unsigned int pos = dev->isoc_ctl.pos, cpysize;
+	int rc = 1;
+	struct tm6000_buffer *buf;
+	char *outp = NULL;
+
+	get_next_buf(dma_q, &buf);
+	if (buf)
+		outp = videobuf_to_vmalloc(&buf->vb);
+
+	if (!outp)
+		return 0;
+
+	while (len > 0) {
+		cpysize = min(len, buf->vb.size-pos);
+		memcpy(&outp[pos], ptr, cpysize);
+		pos += cpysize;
+		ptr += cpysize;
+		len -= cpysize;
+		if (pos >= buf->vb.size) {
+			pos = 0;
+			/* Announces that a new buffer were filled */
+			buffer_filled(dev, dma_q, buf);
+			dprintk(dev, V4L2_DEBUG_ISOC, "new buffer filled\n");
+			get_next_buf(dma_q, &buf);
+			if (!buf)
+				break;
+			outp = videobuf_to_vmalloc(&(buf->vb));
+			if (!outp)
+				return rc;
+			pos = 0;
+		}
+	}
+
+	dev->isoc_ctl.pos = pos;
+	return rc;
+}
+
+static inline void print_err_status(struct tm6000_core *dev,
+				     int packet, int status)
+{
+	char *errmsg = "Unknown";
+
+	switch (status) {
+	case -ENOENT:
+		errmsg = "unlinked synchronously";
+		break;
+	case -ECONNRESET:
+		errmsg = "unlinked asynchronously";
+		break;
+	case -ENOSR:
+		errmsg = "Buffer error (overrun)";
+		break;
+	case -EPIPE:
+		errmsg = "Stalled (device not responding)";
+		break;
+	case -EOVERFLOW:
+		errmsg = "Babble (bad cable?)";
+		break;
+	case -EPROTO:
+		errmsg = "Bit-stuff error (bad cable?)";
+		break;
+	case -EILSEQ:
+		errmsg = "CRC/Timeout (could be anything)";
+		break;
+	case -ETIME:
+		errmsg = "Device does not respond";
+		break;
+	}
+	if (packet < 0) {
+		dprintk(dev, V4L2_DEBUG_QUEUE, "URB status %d [%s].\n",
+			status, errmsg);
+	} else {
+		dprintk(dev, V4L2_DEBUG_QUEUE, "URB packet %d, status %d [%s].\n",
+			packet, status, errmsg);
+	}
+}
+
+
+/*
+ * Controls the isoc copy of each urb packet
+ */
+static inline int tm6000_isoc_copy(struct urb *urb)
+{
+	struct tm6000_dmaqueue  *dma_q = urb->context;
+	struct tm6000_core *dev = container_of(dma_q, struct tm6000_core, vidq);
+	int i, len = 0, rc = 1, status;
+	char *p;
+
+	if (urb->status < 0) {
+		print_err_status(dev, -1, urb->status);
+		return 0;
+	}
+
+	for (i = 0; i < urb->number_of_packets; i++) {
+		status = urb->iso_frame_desc[i].status;
+
+		if (status < 0) {
+			print_err_status(dev, i, status);
+			continue;
+		}
+
+		len = urb->iso_frame_desc[i].actual_length;
+
+		if (len > 0) {
+			p = urb->transfer_buffer + urb->iso_frame_desc[i].offset;
+			if (!urb->iso_frame_desc[i].status) {
+				if ((dev->fourcc) == V4L2_PIX_FMT_TM6000) {
+					rc = copy_multiplexed(p, len, urb);
+					if (rc <= 0)
+						return rc;
+				} else {
+					copy_streams(p, len, urb);
+				}
+			}
+		}
+	}
+	return rc;
+}
+
+/* ------------------------------------------------------------------
+ *	URB control
+ * ------------------------------------------------------------------
+ */
+
+/*
+ * IRQ callback, called by URB callback
+ */
+static void tm6000_irq_callback(struct urb *urb)
+{
+	struct tm6000_dmaqueue  *dma_q = urb->context;
+	struct tm6000_core *dev = container_of(dma_q, struct tm6000_core, vidq);
+	int i;
+
+	switch (urb->status) {
+	case 0:
+	case -ETIMEDOUT:
+		break;
+
+	case -ECONNRESET:
+	case -ENOENT:
+	case -ESHUTDOWN:
+		return;
+
+	default:
+		tm6000_err("urb completion error %d.\n", urb->status);
+		break;
+	}
+
+	spin_lock(&dev->slock);
+	tm6000_isoc_copy(urb);
+	spin_unlock(&dev->slock);
+
+	/* Reset urb buffers */
+	for (i = 0; i < urb->number_of_packets; i++) {
+		urb->iso_frame_desc[i].status = 0;
+		urb->iso_frame_desc[i].actual_length = 0;
+	}
+
+	urb->status = usb_submit_urb(urb, GFP_ATOMIC);
+	if (urb->status)
+		tm6000_err("urb resubmit failed (error=%i)\n",
+			urb->status);
+}
+
+/*
+ * Allocate URB buffers
+ */
+static int tm6000_alloc_urb_buffers(struct tm6000_core *dev)
+{
+	int num_bufs = TM6000_NUM_URB_BUF;
+	int i;
+
+	if (dev->urb_buffer)
+		return 0;
+
+	dev->urb_buffer = kmalloc_array(num_bufs, sizeof(void *), GFP_KERNEL);
+	if (!dev->urb_buffer)
+		return -ENOMEM;
+
+	dev->urb_dma = kmalloc_array(num_bufs, sizeof(dma_addr_t *),
+				     GFP_KERNEL);
+	if (!dev->urb_dma)
+		return -ENOMEM;
+
+	for (i = 0; i < num_bufs; i++) {
+		dev->urb_buffer[i] = usb_alloc_coherent(
+					dev->udev, dev->urb_size,
+					GFP_KERNEL, &dev->urb_dma[i]);
+		if (!dev->urb_buffer[i]) {
+			tm6000_err("unable to allocate %i bytes for transfer buffer %i\n",
+				    dev->urb_size, i);
+			return -ENOMEM;
+		}
+		memset(dev->urb_buffer[i], 0, dev->urb_size);
+	}
+
+	return 0;
+}
+
+/*
+ * Free URB buffers
+ */
+static int tm6000_free_urb_buffers(struct tm6000_core *dev)
+{
+	int i;
+
+	if (!dev->urb_buffer)
+		return 0;
+
+	for (i = 0; i < TM6000_NUM_URB_BUF; i++) {
+		if (dev->urb_buffer[i]) {
+			usb_free_coherent(dev->udev,
+					dev->urb_size,
+					dev->urb_buffer[i],
+					dev->urb_dma[i]);
+			dev->urb_buffer[i] = NULL;
+		}
+	}
+	kfree(dev->urb_buffer);
+	kfree(dev->urb_dma);
+	dev->urb_buffer = NULL;
+	dev->urb_dma = NULL;
+
+	return 0;
+}
+
+/*
+ * Stop and Deallocate URBs
+ */
+static void tm6000_uninit_isoc(struct tm6000_core *dev)
+{
+	struct urb *urb;
+	int i;
+
+	dev->isoc_ctl.buf = NULL;
+	for (i = 0; i < dev->isoc_ctl.num_bufs; i++) {
+		urb = dev->isoc_ctl.urb[i];
+		if (urb) {
+			usb_kill_urb(urb);
+			usb_unlink_urb(urb);
+			usb_free_urb(urb);
+			dev->isoc_ctl.urb[i] = NULL;
+		}
+		dev->isoc_ctl.transfer_buffer[i] = NULL;
+	}
+
+	if (!keep_urb)
+		tm6000_free_urb_buffers(dev);
+
+	kfree(dev->isoc_ctl.urb);
+	kfree(dev->isoc_ctl.transfer_buffer);
+
+	dev->isoc_ctl.urb = NULL;
+	dev->isoc_ctl.transfer_buffer = NULL;
+	dev->isoc_ctl.num_bufs = 0;
+}
+
+/*
+ * Assign URBs and start IRQ
+ */
+static int tm6000_prepare_isoc(struct tm6000_core *dev)
+{
+	struct tm6000_dmaqueue *dma_q = &dev->vidq;
+	int i, j, sb_size, pipe, size, max_packets;
+	int num_bufs = TM6000_NUM_URB_BUF;
+	struct urb *urb;
+
+	/* De-allocates all pending stuff */
+	tm6000_uninit_isoc(dev);
+	/* Stop interrupt USB pipe */
+	tm6000_ir_int_stop(dev);
+
+	usb_set_interface(dev->udev,
+			  dev->isoc_in.bInterfaceNumber,
+			  dev->isoc_in.bAlternateSetting);
+
+	/* Start interrupt USB pipe */
+	tm6000_ir_int_start(dev);
+
+	pipe = usb_rcvisocpipe(dev->udev,
+			       dev->isoc_in.endp->desc.bEndpointAddress &
+			       USB_ENDPOINT_NUMBER_MASK);
+
+	size = usb_maxpacket(dev->udev, pipe, usb_pipeout(pipe));
+
+	if (size > dev->isoc_in.maxsize)
+		size = dev->isoc_in.maxsize;
+
+	dev->isoc_ctl.max_pkt_size = size;
+
+	max_packets = TM6000_MAX_ISO_PACKETS;
+	sb_size = max_packets * size;
+	dev->urb_size = sb_size;
+
+	dev->isoc_ctl.num_bufs = num_bufs;
+
+	dev->isoc_ctl.urb = kmalloc_array(num_bufs, sizeof(void *),
+					  GFP_KERNEL);
+	if (!dev->isoc_ctl.urb)
+		return -ENOMEM;
+
+	dev->isoc_ctl.transfer_buffer = kmalloc_array(num_bufs,
+						      sizeof(void *),
+						      GFP_KERNEL);
+	if (!dev->isoc_ctl.transfer_buffer) {
+		kfree(dev->isoc_ctl.urb);
+		return -ENOMEM;
+	}
+
+	dprintk(dev, V4L2_DEBUG_QUEUE, "Allocating %d x %d packets (%d bytes) of %d bytes each to handle %u size\n",
+		    max_packets, num_bufs, sb_size,
+		    dev->isoc_in.maxsize, size);
+
+
+	if (tm6000_alloc_urb_buffers(dev) < 0) {
+		tm6000_err("cannot allocate memory for urb buffers\n");
+
+		/* call free, as some buffers might have been allocated */
+		tm6000_free_urb_buffers(dev);
+		kfree(dev->isoc_ctl.urb);
+		kfree(dev->isoc_ctl.transfer_buffer);
+		return -ENOMEM;
+	}
+
+	/* allocate urbs and transfer buffers */
+	for (i = 0; i < dev->isoc_ctl.num_bufs; i++) {
+		urb = usb_alloc_urb(max_packets, GFP_KERNEL);
+		if (!urb) {
+			tm6000_uninit_isoc(dev);
+			tm6000_free_urb_buffers(dev);
+			return -ENOMEM;
+		}
+		dev->isoc_ctl.urb[i] = urb;
+
+		urb->transfer_dma = dev->urb_dma[i];
+		dev->isoc_ctl.transfer_buffer[i] = dev->urb_buffer[i];
+
+		usb_fill_bulk_urb(urb, dev->udev, pipe,
+				  dev->isoc_ctl.transfer_buffer[i], sb_size,
+				  tm6000_irq_callback, dma_q);
+		urb->interval = dev->isoc_in.endp->desc.bInterval;
+		urb->number_of_packets = max_packets;
+		urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
+
+		for (j = 0; j < max_packets; j++) {
+			urb->iso_frame_desc[j].offset = size * j;
+			urb->iso_frame_desc[j].length = size;
+		}
+	}
+
+	return 0;
+}
+
+static int tm6000_start_thread(struct tm6000_core *dev)
+{
+	struct tm6000_dmaqueue *dma_q = &dev->vidq;
+	int i;
+
+	dma_q->frame = 0;
+	dma_q->ini_jiffies = jiffies;
+
+	init_waitqueue_head(&dma_q->wq);
+
+	/* submit urbs and enables IRQ */
+	for (i = 0; i < dev->isoc_ctl.num_bufs; i++) {
+		int rc = usb_submit_urb(dev->isoc_ctl.urb[i], GFP_ATOMIC);
+		if (rc) {
+			tm6000_err("submit of urb %i failed (error=%i)\n", i,
+				   rc);
+			tm6000_uninit_isoc(dev);
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+/* ------------------------------------------------------------------
+ *	Videobuf operations
+ * ------------------------------------------------------------------
+ */
+
+static int
+buffer_setup(struct videobuf_queue *vq, unsigned int *count, unsigned int *size)
+{
+	struct tm6000_fh *fh = vq->priv_data;
+
+	*size = fh->fmt->depth * fh->width * fh->height >> 3;
+	if (0 == *count)
+		*count = TM6000_DEF_BUF;
+
+	if (*count < TM6000_MIN_BUF)
+		*count = TM6000_MIN_BUF;
+
+	while (*size * *count > vid_limit * 1024 * 1024)
+		(*count)--;
+
+	return 0;
+}
+
+static void free_buffer(struct videobuf_queue *vq, struct tm6000_buffer *buf)
+{
+	struct tm6000_fh *fh = vq->priv_data;
+	struct tm6000_core   *dev = fh->dev;
+	unsigned long flags;
+
+	BUG_ON(in_interrupt());
+
+	/* We used to wait for the buffer to finish here, but this didn't work
+	   because, as we were keeping the state as VIDEOBUF_QUEUED,
+	   videobuf_queue_cancel marked it as finished for us.
+	   (Also, it could wedge forever if the hardware was misconfigured.)
+
+	   This should be safe; by the time we get here, the buffer isn't
+	   queued anymore. If we ever start marking the buffers as
+	   VIDEOBUF_ACTIVE, it won't be, though.
+	*/
+	spin_lock_irqsave(&dev->slock, flags);
+	if (dev->isoc_ctl.buf == buf)
+		dev->isoc_ctl.buf = NULL;
+	spin_unlock_irqrestore(&dev->slock, flags);
+
+	videobuf_vmalloc_free(&buf->vb);
+	buf->vb.state = VIDEOBUF_NEEDS_INIT;
+}
+
+static int
+buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb,
+						enum v4l2_field field)
+{
+	struct tm6000_fh     *fh  = vq->priv_data;
+	struct tm6000_buffer *buf = container_of(vb, struct tm6000_buffer, vb);
+	struct tm6000_core   *dev = fh->dev;
+	int rc = 0;
+
+	BUG_ON(NULL == fh->fmt);
+
+
+	/* FIXME: It assumes depth=2 */
+	/* The only currently supported format is 16 bits/pixel */
+	buf->vb.size = fh->fmt->depth*fh->width*fh->height >> 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) {
+		buf->fmt       = fh->fmt;
+		buf->vb.width  = fh->width;
+		buf->vb.height = fh->height;
+		buf->vb.field  = field;
+		buf->vb.state = VIDEOBUF_NEEDS_INIT;
+	}
+
+	if (VIDEOBUF_NEEDS_INIT == buf->vb.state) {
+		rc = videobuf_iolock(vq, &buf->vb, NULL);
+		if (rc != 0)
+			goto fail;
+	}
+
+	if (!dev->isoc_ctl.num_bufs) {
+		rc = tm6000_prepare_isoc(dev);
+		if (rc < 0)
+			goto fail;
+
+		rc = tm6000_start_thread(dev);
+		if (rc < 0)
+			goto fail;
+
+	}
+
+	buf->vb.state = VIDEOBUF_PREPARED;
+	return 0;
+
+fail:
+	free_buffer(vq, buf);
+	return rc;
+}
+
+static void
+buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb)
+{
+	struct tm6000_buffer    *buf     = container_of(vb, struct tm6000_buffer, vb);
+	struct tm6000_fh        *fh      = vq->priv_data;
+	struct tm6000_core      *dev     = fh->dev;
+	struct tm6000_dmaqueue  *vidq    = &dev->vidq;
+
+	buf->vb.state = VIDEOBUF_QUEUED;
+	list_add_tail(&buf->vb.queue, &vidq->active);
+}
+
+static void buffer_release(struct videobuf_queue *vq, struct videobuf_buffer *vb)
+{
+	struct tm6000_buffer   *buf  = container_of(vb, struct tm6000_buffer, vb);
+
+	free_buffer(vq, buf);
+}
+
+static const struct videobuf_queue_ops tm6000_video_qops = {
+	.buf_setup      = buffer_setup,
+	.buf_prepare    = buffer_prepare,
+	.buf_queue      = buffer_queue,
+	.buf_release    = buffer_release,
+};
+
+/* ------------------------------------------------------------------
+ *	IOCTL handling
+ * ------------------------------------------------------------------
+ */
+
+static bool is_res_read(struct tm6000_core *dev, struct tm6000_fh *fh)
+{
+	/* Is the current fh handling it? if so, that's OK */
+	if (dev->resources == fh && dev->is_res_read)
+		return true;
+
+	return false;
+}
+
+static bool is_res_streaming(struct tm6000_core *dev, struct tm6000_fh *fh)
+{
+	/* Is the current fh handling it? if so, that's OK */
+	if (dev->resources == fh)
+		return true;
+
+	return false;
+}
+
+static bool res_get(struct tm6000_core *dev, struct tm6000_fh *fh,
+		   bool is_res_read)
+{
+	/* Is the current fh handling it? if so, that's OK */
+	if (dev->resources == fh && dev->is_res_read == is_res_read)
+		return true;
+
+	/* is it free? */
+	if (dev->resources)
+		return false;
+
+	/* grab it */
+	dev->resources = fh;
+	dev->is_res_read = is_res_read;
+	dprintk(dev, V4L2_DEBUG_RES_LOCK, "res: get\n");
+	return true;
+}
+
+static void res_free(struct tm6000_core *dev, struct tm6000_fh *fh)
+{
+	/* Is the current fh handling it? if so, that's OK */
+	if (dev->resources != fh)
+		return;
+
+	dev->resources = NULL;
+	dprintk(dev, V4L2_DEBUG_RES_LOCK, "res: put\n");
+}
+
+/* ------------------------------------------------------------------
+ *	IOCTL vidioc handling
+ * ------------------------------------------------------------------
+ */
+static int vidioc_querycap(struct file *file, void  *priv,
+					struct v4l2_capability *cap)
+{
+	struct tm6000_core *dev = ((struct tm6000_fh *)priv)->dev;
+	struct video_device *vdev = video_devdata(file);
+
+	strlcpy(cap->driver, "tm6000", sizeof(cap->driver));
+	strlcpy(cap->card, "Trident TVMaster TM5600/6000/6010", sizeof(cap->card));
+	usb_make_path(dev->udev, cap->bus_info, sizeof(cap->bus_info));
+	if (dev->tuner_type != TUNER_ABSENT)
+		cap->device_caps |= V4L2_CAP_TUNER;
+	if (vdev->vfl_type == VFL_TYPE_GRABBER)
+		cap->device_caps |= V4L2_CAP_VIDEO_CAPTURE |
+				V4L2_CAP_STREAMING |
+				V4L2_CAP_READWRITE;
+	else
+		cap->device_caps |= V4L2_CAP_RADIO;
+	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS |
+		V4L2_CAP_RADIO | V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE;
+
+	return 0;
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
+					struct v4l2_fmtdesc *f)
+{
+	if (f->index >= ARRAY_SIZE(format))
+		return -EINVAL;
+
+	strlcpy(f->description, format[f->index].name, sizeof(f->description));
+	f->pixelformat = format[f->index].fourcc;
+	return 0;
+}
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+					struct v4l2_format *f)
+{
+	struct tm6000_fh  *fh = priv;
+
+	f->fmt.pix.width        = fh->width;
+	f->fmt.pix.height       = fh->height;
+	f->fmt.pix.field        = fh->vb_vidq.field;
+	f->fmt.pix.pixelformat  = fh->fmt->fourcc;
+	f->fmt.pix.colorspace   = V4L2_COLORSPACE_SMPTE170M;
+	f->fmt.pix.bytesperline =
+		(f->fmt.pix.width * fh->fmt->depth) >> 3;
+	f->fmt.pix.sizeimage =
+		f->fmt.pix.height * f->fmt.pix.bytesperline;
+
+	return 0;
+}
+
+static struct tm6000_fmt *format_by_fourcc(unsigned int fourcc)
+{
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(format); i++)
+		if (format[i].fourcc == fourcc)
+			return format+i;
+	return NULL;
+}
+
+static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
+			struct v4l2_format *f)
+{
+	struct tm6000_core *dev = ((struct tm6000_fh *)priv)->dev;
+	struct tm6000_fmt *fmt;
+	enum v4l2_field field;
+
+	fmt = format_by_fourcc(f->fmt.pix.pixelformat);
+	if (NULL == fmt) {
+		dprintk(dev, 2, "Fourcc format (0x%08x) invalid.\n",
+			f->fmt.pix.pixelformat);
+		return -EINVAL;
+	}
+
+	field = f->fmt.pix.field;
+
+	field = V4L2_FIELD_INTERLACED;
+
+	tm6000_get_std_res(dev);
+
+	f->fmt.pix.width  = dev->width;
+	f->fmt.pix.height = dev->height;
+
+	f->fmt.pix.width &= ~0x01;
+
+	f->fmt.pix.field = field;
+
+	f->fmt.pix.bytesperline =
+		(f->fmt.pix.width * fmt->depth) >> 3;
+	f->fmt.pix.sizeimage =
+		f->fmt.pix.height * f->fmt.pix.bytesperline;
+	f->fmt.pix.colorspace   = V4L2_COLORSPACE_SMPTE170M;
+
+	return 0;
+}
+
+/*FIXME: This seems to be generic enough to be at videodev2 */
+static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+					struct v4l2_format *f)
+{
+	struct tm6000_fh  *fh = priv;
+	struct tm6000_core *dev = fh->dev;
+	int ret = vidioc_try_fmt_vid_cap(file, fh, f);
+	if (ret < 0)
+		return ret;
+
+	fh->fmt           = format_by_fourcc(f->fmt.pix.pixelformat);
+	fh->width         = f->fmt.pix.width;
+	fh->height        = f->fmt.pix.height;
+	fh->vb_vidq.field = f->fmt.pix.field;
+	fh->type          = f->type;
+
+	dev->fourcc       = f->fmt.pix.pixelformat;
+
+	tm6000_set_fourcc_format(dev);
+
+	return 0;
+}
+
+static int vidioc_reqbufs(struct file *file, void *priv,
+			   struct v4l2_requestbuffers *p)
+{
+	struct tm6000_fh  *fh = priv;
+
+	return videobuf_reqbufs(&fh->vb_vidq, p);
+}
+
+static int vidioc_querybuf(struct file *file, void *priv,
+			    struct v4l2_buffer *p)
+{
+	struct tm6000_fh  *fh = priv;
+
+	return videobuf_querybuf(&fh->vb_vidq, p);
+}
+
+static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
+{
+	struct tm6000_fh  *fh = priv;
+
+	return videobuf_qbuf(&fh->vb_vidq, p);
+}
+
+static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p)
+{
+	struct tm6000_fh  *fh = priv;
+
+	return videobuf_dqbuf(&fh->vb_vidq, p,
+				file->f_flags & O_NONBLOCK);
+}
+
+static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
+{
+	struct tm6000_fh *fh = priv;
+	struct tm6000_core *dev = fh->dev;
+
+	if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+	if (i != fh->type)
+		return -EINVAL;
+
+	if (!res_get(dev, fh, false))
+		return -EBUSY;
+	return videobuf_streamon(&fh->vb_vidq);
+}
+
+static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
+{
+	struct tm6000_fh *fh = priv;
+	struct tm6000_core *dev = fh->dev;
+
+	if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	if (i != fh->type)
+		return -EINVAL;
+
+	videobuf_streamoff(&fh->vb_vidq);
+	res_free(dev, fh);
+
+	return 0;
+}
+
+static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id norm)
+{
+	int rc = 0;
+	struct tm6000_fh *fh = priv;
+	struct tm6000_core *dev = fh->dev;
+
+	dev->norm = norm;
+	rc = tm6000_init_analog_mode(dev);
+
+	fh->width  = dev->width;
+	fh->height = dev->height;
+
+	if (rc < 0)
+		return rc;
+
+	v4l2_device_call_all(&dev->v4l2_dev, 0, video, s_std, dev->norm);
+
+	return 0;
+}
+
+static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *norm)
+{
+	struct tm6000_fh *fh = priv;
+	struct tm6000_core *dev = fh->dev;
+
+	*norm = dev->norm;
+	return 0;
+}
+
+static const char *iname[] = {
+	[TM6000_INPUT_TV] = "Television",
+	[TM6000_INPUT_COMPOSITE1] = "Composite 1",
+	[TM6000_INPUT_COMPOSITE2] = "Composite 2",
+	[TM6000_INPUT_SVIDEO] = "S-Video",
+};
+
+static int vidioc_enum_input(struct file *file, void *priv,
+				struct v4l2_input *i)
+{
+	struct tm6000_fh   *fh = priv;
+	struct tm6000_core *dev = fh->dev;
+	unsigned int n;
+
+	n = i->index;
+	if (n >= 3)
+		return -EINVAL;
+
+	if (!dev->vinput[n].type)
+		return -EINVAL;
+
+	i->index = n;
+
+	if (dev->vinput[n].type == TM6000_INPUT_TV)
+		i->type = V4L2_INPUT_TYPE_TUNER;
+	else
+		i->type = V4L2_INPUT_TYPE_CAMERA;
+
+	strcpy(i->name, iname[dev->vinput[n].type]);
+
+	i->std = TM6000_STD;
+
+	return 0;
+}
+
+static int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
+{
+	struct tm6000_fh   *fh = priv;
+	struct tm6000_core *dev = fh->dev;
+
+	*i = dev->input;
+
+	return 0;
+}
+
+static int vidioc_s_input(struct file *file, void *priv, unsigned int i)
+{
+	struct tm6000_fh   *fh = priv;
+	struct tm6000_core *dev = fh->dev;
+	int rc = 0;
+
+	if (i >= 3)
+		return -EINVAL;
+	if (!dev->vinput[i].type)
+		return -EINVAL;
+
+	dev->input = i;
+
+	rc = vidioc_s_std(file, priv, dev->norm);
+
+	return rc;
+}
+
+/* --- controls ---------------------------------------------- */
+
+static int tm6000_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct tm6000_core *dev = container_of(ctrl->handler, struct tm6000_core, ctrl_handler);
+	u8  val = ctrl->val;
+
+	switch (ctrl->id) {
+	case V4L2_CID_CONTRAST:
+		tm6000_set_reg(dev, TM6010_REQ07_R08_LUMA_CONTRAST_ADJ, val);
+		return 0;
+	case V4L2_CID_BRIGHTNESS:
+		tm6000_set_reg(dev, TM6010_REQ07_R09_LUMA_BRIGHTNESS_ADJ, val);
+		return 0;
+	case V4L2_CID_SATURATION:
+		tm6000_set_reg(dev, TM6010_REQ07_R0A_CHROMA_SATURATION_ADJ, val);
+		return 0;
+	case V4L2_CID_HUE:
+		tm6000_set_reg(dev, TM6010_REQ07_R0B_CHROMA_HUE_PHASE_ADJ, val);
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static const struct v4l2_ctrl_ops tm6000_ctrl_ops = {
+	.s_ctrl = tm6000_s_ctrl,
+};
+
+static int tm6000_radio_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct tm6000_core *dev = container_of(ctrl->handler,
+			struct tm6000_core, radio_ctrl_handler);
+	u8  val = ctrl->val;
+
+	switch (ctrl->id) {
+	case V4L2_CID_AUDIO_MUTE:
+		dev->ctl_mute = val;
+		tm6000_tvaudio_set_mute(dev, val);
+		return 0;
+	case V4L2_CID_AUDIO_VOLUME:
+		dev->ctl_volume = val;
+		tm6000_set_volume(dev, val);
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static const struct v4l2_ctrl_ops tm6000_radio_ctrl_ops = {
+	.s_ctrl = tm6000_radio_s_ctrl,
+};
+
+static int vidioc_g_tuner(struct file *file, void *priv,
+				struct v4l2_tuner *t)
+{
+	struct tm6000_fh   *fh  = priv;
+	struct tm6000_core *dev = fh->dev;
+
+	if (UNSET == dev->tuner_type)
+		return -ENOTTY;
+	if (0 != t->index)
+		return -EINVAL;
+
+	strcpy(t->name, "Television");
+	t->type       = V4L2_TUNER_ANALOG_TV;
+	t->capability = V4L2_TUNER_CAP_NORM | V4L2_TUNER_CAP_STEREO;
+	t->rangehigh  = 0xffffffffUL;
+	t->rxsubchans = V4L2_TUNER_SUB_STEREO;
+
+	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, g_tuner, t);
+
+	t->audmode = dev->amode;
+
+	return 0;
+}
+
+static int vidioc_s_tuner(struct file *file, void *priv,
+				const struct v4l2_tuner *t)
+{
+	struct tm6000_fh   *fh  = priv;
+	struct tm6000_core *dev = fh->dev;
+
+	if (UNSET == dev->tuner_type)
+		return -ENOTTY;
+	if (0 != t->index)
+		return -EINVAL;
+
+	if (t->audmode > V4L2_TUNER_MODE_STEREO)
+		dev->amode = V4L2_TUNER_MODE_STEREO;
+	else
+		dev->amode = t->audmode;
+	dprintk(dev, 3, "audio mode: %x\n", t->audmode);
+
+	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_tuner, t);
+
+	return 0;
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+				struct v4l2_frequency *f)
+{
+	struct tm6000_fh   *fh  = priv;
+	struct tm6000_core *dev = fh->dev;
+
+	if (UNSET == dev->tuner_type)
+		return -ENOTTY;
+	if (f->tuner)
+		return -EINVAL;
+
+	f->frequency = dev->freq;
+
+	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, g_frequency, f);
+
+	return 0;
+}
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+				const struct v4l2_frequency *f)
+{
+	struct tm6000_fh   *fh  = priv;
+	struct tm6000_core *dev = fh->dev;
+
+	if (UNSET == dev->tuner_type)
+		return -ENOTTY;
+	if (f->tuner != 0)
+		return -EINVAL;
+
+	dev->freq = f->frequency;
+	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_frequency, f);
+
+	return 0;
+}
+
+static int radio_g_tuner(struct file *file, void *priv,
+					struct v4l2_tuner *t)
+{
+	struct tm6000_fh *fh = file->private_data;
+	struct tm6000_core *dev = fh->dev;
+
+	if (0 != t->index)
+		return -EINVAL;
+
+	memset(t, 0, sizeof(*t));
+	strcpy(t->name, "Radio");
+	t->type = V4L2_TUNER_RADIO;
+	t->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO;
+	t->rxsubchans = V4L2_TUNER_SUB_STEREO;
+	t->audmode = V4L2_TUNER_MODE_STEREO;
+
+	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, g_tuner, t);
+
+	return 0;
+}
+
+static int radio_s_tuner(struct file *file, void *priv,
+					const struct v4l2_tuner *t)
+{
+	struct tm6000_fh *fh = file->private_data;
+	struct tm6000_core *dev = fh->dev;
+
+	if (0 != t->index)
+		return -EINVAL;
+	v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_tuner, t);
+	return 0;
+}
+
+/* ------------------------------------------------------------------
+	File operations for the device
+   ------------------------------------------------------------------*/
+
+static int __tm6000_open(struct file *file)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct tm6000_core *dev = video_drvdata(file);
+	struct tm6000_fh *fh;
+	enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	int rc;
+	int radio = 0;
+
+	dprintk(dev, V4L2_DEBUG_OPEN, "tm6000: open called (dev=%s)\n",
+		video_device_node_name(vdev));
+
+	switch (vdev->vfl_type) {
+	case VFL_TYPE_GRABBER:
+		type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+		break;
+	case VFL_TYPE_VBI:
+		type = V4L2_BUF_TYPE_VBI_CAPTURE;
+		break;
+	case VFL_TYPE_RADIO:
+		radio = 1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* If more than one user, mutex should be added */
+	dev->users++;
+
+	dprintk(dev, V4L2_DEBUG_OPEN, "open dev=%s type=%s users=%d\n",
+		video_device_node_name(vdev), v4l2_type_names[type],
+		dev->users);
+
+	/* allocate + initialize per filehandle data */
+	fh = kzalloc(sizeof(*fh), GFP_KERNEL);
+	if (NULL == fh) {
+		dev->users--;
+		return -ENOMEM;
+	}
+
+	v4l2_fh_init(&fh->fh, vdev);
+	file->private_data = fh;
+	fh->dev      = dev;
+	fh->radio    = radio;
+	dev->radio   = radio;
+	fh->type     = type;
+	dev->fourcc  = format[0].fourcc;
+
+	fh->fmt      = format_by_fourcc(dev->fourcc);
+
+	tm6000_get_std_res(dev);
+
+	fh->width = dev->width;
+	fh->height = dev->height;
+
+	dprintk(dev, V4L2_DEBUG_OPEN, "Open: fh=%p, dev=%p, dev->vidq=%p\n",
+		fh, dev, &dev->vidq);
+	dprintk(dev, V4L2_DEBUG_OPEN, "Open: list_empty queued=%d\n",
+		list_empty(&dev->vidq.queued));
+	dprintk(dev, V4L2_DEBUG_OPEN, "Open: list_empty active=%d\n",
+		list_empty(&dev->vidq.active));
+
+	/* initialize hardware on analog mode */
+	rc = tm6000_init_analog_mode(dev);
+	if (rc < 0) {
+		v4l2_fh_exit(&fh->fh);
+		kfree(fh);
+		return rc;
+	}
+
+	dev->mode = TM6000_MODE_ANALOG;
+
+	if (!fh->radio) {
+		videobuf_queue_vmalloc_init(&fh->vb_vidq, &tm6000_video_qops,
+				NULL, &dev->slock,
+				fh->type,
+				V4L2_FIELD_INTERLACED,
+				sizeof(struct tm6000_buffer), fh, &dev->lock);
+	} else {
+		dprintk(dev, V4L2_DEBUG_OPEN, "video_open: setting radio device\n");
+		tm6000_set_audio_rinput(dev);
+		v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_radio);
+		tm6000_prepare_isoc(dev);
+		tm6000_start_thread(dev);
+	}
+	v4l2_fh_add(&fh->fh);
+
+	return 0;
+}
+
+static int tm6000_open(struct file *file)
+{
+	struct video_device *vdev = video_devdata(file);
+	int res;
+
+	mutex_lock(vdev->lock);
+	res = __tm6000_open(file);
+	mutex_unlock(vdev->lock);
+	return res;
+}
+
+static ssize_t
+tm6000_read(struct file *file, char __user *data, size_t count, loff_t *pos)
+{
+	struct tm6000_fh *fh = file->private_data;
+	struct tm6000_core *dev = fh->dev;
+
+	if (fh->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+		int res;
+
+		if (!res_get(fh->dev, fh, true))
+			return -EBUSY;
+
+		if (mutex_lock_interruptible(&dev->lock))
+			return -ERESTARTSYS;
+		res = videobuf_read_stream(&fh->vb_vidq, data, count, pos, 0,
+					file->f_flags & O_NONBLOCK);
+		mutex_unlock(&dev->lock);
+		return res;
+	}
+	return 0;
+}
+
+static __poll_t
+__tm6000_poll(struct file *file, struct poll_table_struct *wait)
+{
+	__poll_t req_events = poll_requested_events(wait);
+	struct tm6000_fh        *fh = file->private_data;
+	struct tm6000_buffer    *buf;
+	__poll_t res = 0;
+
+	if (v4l2_event_pending(&fh->fh))
+		res = EPOLLPRI;
+	else if (req_events & EPOLLPRI)
+		poll_wait(file, &fh->fh.wait, wait);
+	if (V4L2_BUF_TYPE_VIDEO_CAPTURE != fh->type)
+		return res | EPOLLERR;
+
+	if (!!is_res_streaming(fh->dev, fh))
+		return res | EPOLLERR;
+
+	if (!is_res_read(fh->dev, fh)) {
+		/* streaming capture */
+		if (list_empty(&fh->vb_vidq.stream))
+			return res | EPOLLERR;
+		buf = list_entry(fh->vb_vidq.stream.next, struct tm6000_buffer, vb.stream);
+		poll_wait(file, &buf->vb.done, wait);
+		if (buf->vb.state == VIDEOBUF_DONE ||
+		    buf->vb.state == VIDEOBUF_ERROR)
+			return res | EPOLLIN | EPOLLRDNORM;
+	} else if (req_events & (EPOLLIN | EPOLLRDNORM)) {
+		/* read() capture */
+		return res | videobuf_poll_stream(file, &fh->vb_vidq, wait);
+	}
+	return res;
+}
+
+static __poll_t tm6000_poll(struct file *file, struct poll_table_struct *wait)
+{
+	struct tm6000_fh *fh = file->private_data;
+	struct tm6000_core *dev = fh->dev;
+	__poll_t res;
+
+	mutex_lock(&dev->lock);
+	res = __tm6000_poll(file, wait);
+	mutex_unlock(&dev->lock);
+	return res;
+}
+
+static int tm6000_release(struct file *file)
+{
+	struct tm6000_fh         *fh = file->private_data;
+	struct tm6000_core      *dev = fh->dev;
+	struct video_device    *vdev = video_devdata(file);
+
+	dprintk(dev, V4L2_DEBUG_OPEN, "tm6000: close called (dev=%s, users=%d)\n",
+		video_device_node_name(vdev), dev->users);
+
+	mutex_lock(&dev->lock);
+	dev->users--;
+
+	res_free(dev, fh);
+
+	if (!dev->users) {
+		tm6000_uninit_isoc(dev);
+
+		/* Stop interrupt USB pipe */
+		tm6000_ir_int_stop(dev);
+
+		usb_reset_configuration(dev->udev);
+
+		if (dev->int_in.endp)
+			usb_set_interface(dev->udev,
+					dev->isoc_in.bInterfaceNumber, 2);
+		else
+			usb_set_interface(dev->udev,
+					dev->isoc_in.bInterfaceNumber, 0);
+
+		/* Start interrupt USB pipe */
+		tm6000_ir_int_start(dev);
+
+		if (!fh->radio)
+			videobuf_mmap_free(&fh->vb_vidq);
+	}
+	v4l2_fh_del(&fh->fh);
+	v4l2_fh_exit(&fh->fh);
+	kfree(fh);
+	mutex_unlock(&dev->lock);
+
+	return 0;
+}
+
+static int tm6000_mmap(struct file *file, struct vm_area_struct * vma)
+{
+	struct tm6000_fh *fh = file->private_data;
+	struct tm6000_core *dev = fh->dev;
+	int res;
+
+	if (mutex_lock_interruptible(&dev->lock))
+		return -ERESTARTSYS;
+	res = videobuf_mmap_mapper(&fh->vb_vidq, vma);
+	mutex_unlock(&dev->lock);
+	return res;
+}
+
+static const struct v4l2_file_operations tm6000_fops = {
+	.owner = THIS_MODULE,
+	.open = tm6000_open,
+	.release = tm6000_release,
+	.unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
+	.read = tm6000_read,
+	.poll = tm6000_poll,
+	.mmap = tm6000_mmap,
+};
+
+static const struct v4l2_ioctl_ops video_ioctl_ops = {
+	.vidioc_querycap          = vidioc_querycap,
+	.vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap     = vidioc_g_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap   = vidioc_try_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap     = vidioc_s_fmt_vid_cap,
+	.vidioc_s_std             = vidioc_s_std,
+	.vidioc_g_std             = vidioc_g_std,
+	.vidioc_enum_input        = vidioc_enum_input,
+	.vidioc_g_input           = vidioc_g_input,
+	.vidioc_s_input           = vidioc_s_input,
+	.vidioc_g_tuner           = vidioc_g_tuner,
+	.vidioc_s_tuner           = vidioc_s_tuner,
+	.vidioc_g_frequency       = vidioc_g_frequency,
+	.vidioc_s_frequency       = vidioc_s_frequency,
+	.vidioc_streamon          = vidioc_streamon,
+	.vidioc_streamoff         = vidioc_streamoff,
+	.vidioc_reqbufs           = vidioc_reqbufs,
+	.vidioc_querybuf          = vidioc_querybuf,
+	.vidioc_qbuf              = vidioc_qbuf,
+	.vidioc_dqbuf             = vidioc_dqbuf,
+	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static struct video_device tm6000_template = {
+	.name		= "tm6000",
+	.fops           = &tm6000_fops,
+	.ioctl_ops      = &video_ioctl_ops,
+	.release	= video_device_release_empty,
+	.tvnorms        = TM6000_STD,
+};
+
+static const struct v4l2_file_operations radio_fops = {
+	.owner		= THIS_MODULE,
+	.open		= tm6000_open,
+	.poll		= v4l2_ctrl_poll,
+	.release	= tm6000_release,
+	.unlocked_ioctl	= video_ioctl2,
+};
+
+static const struct v4l2_ioctl_ops radio_ioctl_ops = {
+	.vidioc_querycap	= vidioc_querycap,
+	.vidioc_g_tuner		= radio_g_tuner,
+	.vidioc_s_tuner		= radio_s_tuner,
+	.vidioc_g_frequency	= vidioc_g_frequency,
+	.vidioc_s_frequency	= vidioc_s_frequency,
+	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static struct video_device tm6000_radio_template = {
+	.name			= "tm6000",
+	.fops			= &radio_fops,
+	.ioctl_ops		= &radio_ioctl_ops,
+};
+
+/* -----------------------------------------------------------------
+ *	Initialization and module stuff
+ * ------------------------------------------------------------------
+ */
+
+static void vdev_init(struct tm6000_core *dev,
+		struct video_device *vfd,
+		const struct video_device
+		*template, const char *type_name)
+{
+	*vfd = *template;
+	vfd->v4l2_dev = &dev->v4l2_dev;
+	vfd->release = video_device_release_empty;
+	vfd->lock = &dev->lock;
+
+	snprintf(vfd->name, sizeof(vfd->name), "%s %s", dev->name, type_name);
+
+	video_set_drvdata(vfd, dev);
+}
+
+int tm6000_v4l2_register(struct tm6000_core *dev)
+{
+	int ret = 0;
+
+	v4l2_ctrl_handler_init(&dev->ctrl_handler, 6);
+	v4l2_ctrl_handler_init(&dev->radio_ctrl_handler, 2);
+	v4l2_ctrl_new_std(&dev->radio_ctrl_handler, &tm6000_radio_ctrl_ops,
+			V4L2_CID_AUDIO_MUTE, 0, 1, 1, 0);
+	v4l2_ctrl_new_std(&dev->radio_ctrl_handler, &tm6000_radio_ctrl_ops,
+			V4L2_CID_AUDIO_VOLUME, -15, 15, 1, 0);
+	v4l2_ctrl_new_std(&dev->ctrl_handler, &tm6000_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0, 255, 1, 54);
+	v4l2_ctrl_new_std(&dev->ctrl_handler, &tm6000_ctrl_ops,
+			V4L2_CID_CONTRAST, 0, 255, 1, 119);
+	v4l2_ctrl_new_std(&dev->ctrl_handler, &tm6000_ctrl_ops,
+			V4L2_CID_SATURATION, 0, 255, 1, 112);
+	v4l2_ctrl_new_std(&dev->ctrl_handler, &tm6000_ctrl_ops,
+			V4L2_CID_HUE, -128, 127, 1, 0);
+	v4l2_ctrl_add_handler(&dev->ctrl_handler,
+			&dev->radio_ctrl_handler, NULL);
+
+	if (dev->radio_ctrl_handler.error)
+		ret = dev->radio_ctrl_handler.error;
+	if (!ret && dev->ctrl_handler.error)
+		ret = dev->ctrl_handler.error;
+	if (ret)
+		goto free_ctrl;
+
+	vdev_init(dev, &dev->vfd, &tm6000_template, "video");
+
+	dev->vfd.ctrl_handler = &dev->ctrl_handler;
+
+	/* init video dma queues */
+	INIT_LIST_HEAD(&dev->vidq.active);
+	INIT_LIST_HEAD(&dev->vidq.queued);
+
+	ret = video_register_device(&dev->vfd, VFL_TYPE_GRABBER, video_nr);
+
+	if (ret < 0) {
+		printk(KERN_INFO "%s: can't register video device\n",
+		       dev->name);
+		goto free_ctrl;
+	}
+
+	printk(KERN_INFO "%s: registered device %s\n",
+	       dev->name, video_device_node_name(&dev->vfd));
+
+	if (dev->caps.has_radio) {
+		vdev_init(dev, &dev->radio_dev, &tm6000_radio_template,
+							   "radio");
+		dev->radio_dev.ctrl_handler = &dev->radio_ctrl_handler;
+		ret = video_register_device(&dev->radio_dev, VFL_TYPE_RADIO,
+					    radio_nr);
+		if (ret < 0) {
+			printk(KERN_INFO "%s: can't register radio device\n",
+			       dev->name);
+			goto unreg_video;
+		}
+
+		printk(KERN_INFO "%s: registered device %s\n",
+		       dev->name, video_device_node_name(&dev->radio_dev));
+	}
+
+	printk(KERN_INFO "Trident TVMaster TM5600/TM6000/TM6010 USB2 board (Load status: %d)\n", ret);
+	return ret;
+
+unreg_video:
+	video_unregister_device(&dev->vfd);
+free_ctrl:
+	v4l2_ctrl_handler_free(&dev->ctrl_handler);
+	v4l2_ctrl_handler_free(&dev->radio_ctrl_handler);
+	return ret;
+}
+
+int tm6000_v4l2_unregister(struct tm6000_core *dev)
+{
+	video_unregister_device(&dev->vfd);
+
+	/* if URB buffers are still allocated free them now */
+	tm6000_free_urb_buffers(dev);
+
+	video_unregister_device(&dev->radio_dev);
+	return 0;
+}
+
+int tm6000_v4l2_exit(void)
+{
+	return 0;
+}
+
+module_param(video_nr, int, 0);
+MODULE_PARM_DESC(video_nr, "Allow changing video device number");
+
+module_param_named(debug, tm6000_debug, int, 0444);
+MODULE_PARM_DESC(debug, "activates debug info");
+
+module_param(vid_limit, int, 0644);
+MODULE_PARM_DESC(vid_limit, "capture memory limit in megabytes");
+
+module_param(keep_urb, bool, 0);
+MODULE_PARM_DESC(keep_urb, "Keep urb buffers allocated even when the device is closed by the user");
diff --git a/drivers/media/usb/tm6000/tm6000.h b/drivers/media/usb/tm6000/tm6000.h
new file mode 100644
index 0000000..0864ed7
--- /dev/null
+++ b/drivers/media/usb/tm6000/tm6000.h
@@ -0,0 +1,397 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0
+ * tm6000.h - driver for TM5600/TM6000/TM6010 USB video capture devices
+ *
+ * Copyright (c) 2006-2007 Mauro Carvalho Chehab <mchehab@kernel.org>
+ *
+ * Copyright (c) 2007 Michel Ludwig <michel.ludwig@gmail.com>
+ *	- DVB-T support
+ */
+
+#include <linux/videodev2.h>
+#include <media/v4l2-common.h>
+#include <media/videobuf-vmalloc.h>
+#include "tm6000-usb-isoc.h"
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-fh.h>
+
+#include <linux/dvb/frontend.h>
+#include <media/dvb_demux.h>
+#include <media/dvb_frontend.h>
+#include <media/dmxdev.h>
+
+/* Inputs */
+enum tm6000_itype {
+	TM6000_INPUT_TV	= 1,
+	TM6000_INPUT_COMPOSITE1,
+	TM6000_INPUT_COMPOSITE2,
+	TM6000_INPUT_SVIDEO,
+	TM6000_INPUT_DVB,
+	TM6000_INPUT_RADIO,
+};
+
+enum tm6000_mux {
+	TM6000_VMUX_VIDEO_A = 1,
+	TM6000_VMUX_VIDEO_B,
+	TM6000_VMUX_VIDEO_AB,
+	TM6000_AMUX_ADC1,
+	TM6000_AMUX_ADC2,
+	TM6000_AMUX_SIF1,
+	TM6000_AMUX_SIF2,
+	TM6000_AMUX_I2S,
+};
+
+enum tm6000_devtype {
+	TM6000 = 0,
+	TM5600,
+	TM6010,
+};
+
+struct tm6000_input {
+	enum tm6000_itype	type;
+	enum tm6000_mux		vmux;
+	enum tm6000_mux		amux;
+	unsigned int		v_gpio;
+	unsigned int		a_gpio;
+};
+
+/* ------------------------------------------------------------------
+ *	Basic structures
+ * ------------------------------------------------------------------
+ */
+
+struct tm6000_fmt {
+	char  *name;
+	u32   fourcc;          /* v4l2 format id */
+	int   depth;
+};
+
+/* buffer for one video frame */
+struct tm6000_buffer {
+	/* common v4l buffer stuff -- must be first */
+	struct videobuf_buffer vb;
+
+	struct tm6000_fmt      *fmt;
+};
+
+struct tm6000_dmaqueue {
+	struct list_head       active;
+	struct list_head       queued;
+
+	/* thread for generating video stream*/
+	struct task_struct         *kthread;
+	wait_queue_head_t          wq;
+	/* Counters to control fps rate */
+	int                        frame;
+	int                        ini_jiffies;
+};
+
+/* device states */
+enum tm6000_core_state {
+	DEV_INITIALIZED   = 0x01,
+	DEV_DISCONNECTED  = 0x02,
+	DEV_MISCONFIGURED = 0x04,
+};
+
+/* io methods */
+enum tm6000_io_method {
+	IO_NONE,
+	IO_READ,
+	IO_MMAP,
+};
+
+enum tm6000_mode {
+	TM6000_MODE_UNKNOWN = 0,
+	TM6000_MODE_ANALOG,
+	TM6000_MODE_DIGITAL,
+};
+
+struct tm6000_gpio {
+	int		tuner_reset;
+	int		tuner_on;
+	int		demod_reset;
+	int		demod_on;
+	int		power_led;
+	int		dvb_led;
+	int		ir;
+};
+
+struct tm6000_capabilities {
+	unsigned int    has_tuner:1;
+	unsigned int    has_tda9874:1;
+	unsigned int    has_dvb:1;
+	unsigned int    has_zl10353:1;
+	unsigned int    has_eeprom:1;
+	unsigned int    has_remote:1;
+	unsigned int    has_radio:1;
+};
+
+struct tm6000_dvb {
+	struct dvb_adapter	adapter;
+	struct dvb_demux	demux;
+	struct dvb_frontend	*frontend;
+	struct dmxdev		dmxdev;
+	unsigned int		streams;
+	struct urb		*bulk_urb;
+	struct mutex		mutex;
+};
+
+struct snd_tm6000_card {
+	struct snd_card			*card;
+	spinlock_t			reg_lock;
+	struct tm6000_core		*core;
+	struct snd_pcm_substream	*substream;
+
+	/* temporary data for buffer fill processing */
+	unsigned			buf_pos;
+	unsigned			period_pos;
+};
+
+struct tm6000_endpoint {
+	struct usb_host_endpoint	*endp;
+	__u8				bInterfaceNumber;
+	__u8				bAlternateSetting;
+	unsigned			maxsize;
+};
+
+#define TM6000_QUIRK_NO_USB_DELAY (1 << 0)
+
+struct tm6000_core {
+	/* generic device properties */
+	char				name[30];	/* name (including minor) of the device */
+	int				model;		/* index in the device_data struct */
+	int				devno;		/* marks the number of this device */
+	enum tm6000_devtype		dev_type;	/* type of device */
+	unsigned char			eedata[256];	/* Eeprom data */
+	unsigned			eedata_size;	/* Size of the eeprom info */
+
+	v4l2_std_id                     norm;           /* Current norm */
+	int				width, height;	/* Selected resolution */
+
+	enum tm6000_core_state		state;
+
+	/* Device Capabilities*/
+	struct tm6000_capabilities	caps;
+
+	/* Used to load alsa/dvb */
+	struct work_struct		request_module_wk;
+
+	/* Tuner configuration */
+	int				tuner_type;		/* type of the tuner */
+	int				tuner_addr;		/* tuner address */
+
+	struct tm6000_gpio		gpio;
+
+	char				*ir_codes;
+
+	__u8				radio;
+
+	/* Demodulator configuration */
+	int				demod_addr;	/* demodulator address */
+
+	int				audio_bitrate;
+	/* i2c i/o */
+	struct i2c_adapter		i2c_adap;
+	struct i2c_client		i2c_client;
+
+
+	/* extension */
+	struct list_head		devlist;
+
+	/* video for linux */
+	int				users;
+
+	/* various device info */
+	struct tm6000_fh		*resources;	/* Points to fh that is streaming */
+	bool				is_res_read;
+
+	struct video_device		vfd;
+	struct video_device		radio_dev;
+	struct tm6000_dmaqueue		vidq;
+	struct v4l2_device		v4l2_dev;
+	struct v4l2_ctrl_handler	ctrl_handler;
+	struct v4l2_ctrl_handler	radio_ctrl_handler;
+
+	int				input;
+	struct tm6000_input		vinput[3];	/* video input */
+	struct tm6000_input		rinput;		/* radio input */
+
+	int				freq;
+	unsigned int			fourcc;
+
+	enum tm6000_mode		mode;
+
+	int				ctl_mute;             /* audio */
+	int				ctl_volume;
+	int				amode;
+
+	/* DVB-T support */
+	struct tm6000_dvb		*dvb;
+
+	/* audio support */
+	struct snd_tm6000_card		*adev;
+	struct work_struct		wq_trigger;   /* Trigger to start/stop audio for alsa module */
+	atomic_t			stream_started;  /* stream should be running if true */
+
+	struct tm6000_IR		*ir;
+
+	/* locks */
+	struct mutex			lock;
+	struct mutex			usb_lock;
+
+	/* usb transfer */
+	struct usb_device		*udev;		/* the usb device */
+
+	struct tm6000_endpoint		bulk_in, bulk_out, isoc_in, isoc_out;
+	struct tm6000_endpoint		int_in, int_out;
+
+	/* scaler!=0 if scaler is active*/
+	int				scaler;
+
+		/* Isoc control struct */
+	struct usb_isoc_ctl          isoc_ctl;
+
+	spinlock_t                   slock;
+
+	/* urb dma buffers */
+	char				**urb_buffer;
+	dma_addr_t			*urb_dma;
+	unsigned int			urb_size;
+
+	unsigned long quirks;
+};
+
+enum tm6000_ops_type {
+	TM6000_AUDIO = 0x10,
+	TM6000_DVB = 0x20,
+};
+
+struct tm6000_ops {
+	struct list_head	next;
+	char			*name;
+	enum tm6000_ops_type	type;
+	int (*init)(struct tm6000_core *);
+	int (*fini)(struct tm6000_core *);
+	int (*fillbuf)(struct tm6000_core *, char *buf, int size);
+};
+
+struct tm6000_fh {
+	struct v4l2_fh		     fh;
+	struct tm6000_core           *dev;
+	unsigned int                 radio;
+
+	/* video capture */
+	struct tm6000_fmt            *fmt;
+	unsigned int                 width, height;
+	struct videobuf_queue        vb_vidq;
+
+	enum v4l2_buf_type           type;
+};
+
+#define TM6000_STD	(V4L2_STD_PAL|V4L2_STD_PAL_N|V4L2_STD_PAL_Nc|    \
+			V4L2_STD_PAL_M|V4L2_STD_PAL_60|V4L2_STD_NTSC_M| \
+			V4L2_STD_NTSC_M_JP|V4L2_STD_SECAM)
+
+/* In tm6000-cards.c */
+
+int tm6000_tuner_callback(void *ptr, int component, int command, int arg);
+int tm6000_xc5000_callback(void *ptr, int component, int command, int arg);
+int tm6000_cards_setup(struct tm6000_core *dev);
+void tm6000_flash_led(struct tm6000_core *dev, u8 state);
+
+/* In tm6000-core.c */
+
+int tm6000_read_write_usb(struct tm6000_core *dev, u8 reqtype, u8 req,
+			   u16 value, u16 index, u8 *buf, u16 len);
+int tm6000_get_reg(struct tm6000_core *dev, u8 req, u16 value, u16 index);
+int tm6000_get_reg16(struct tm6000_core *dev, u8 req, u16 value, u16 index);
+int tm6000_get_reg32(struct tm6000_core *dev, u8 req, u16 value, u16 index);
+int tm6000_set_reg(struct tm6000_core *dev, u8 req, u16 value, u16 index);
+int tm6000_set_reg_mask(struct tm6000_core *dev, u8 req, u16 value,
+						u16 index, u16 mask);
+int tm6000_i2c_reset(struct tm6000_core *dev, u16 tsleep);
+int tm6000_init(struct tm6000_core *dev);
+int tm6000_reset(struct tm6000_core *dev);
+
+int tm6000_init_analog_mode(struct tm6000_core *dev);
+int tm6000_init_digital_mode(struct tm6000_core *dev);
+int tm6000_set_audio_bitrate(struct tm6000_core *dev, int bitrate);
+int tm6000_set_audio_rinput(struct tm6000_core *dev);
+int tm6000_tvaudio_set_mute(struct tm6000_core *dev, u8 mute);
+void tm6000_set_volume(struct tm6000_core *dev, int vol);
+
+int tm6000_v4l2_register(struct tm6000_core *dev);
+int tm6000_v4l2_unregister(struct tm6000_core *dev);
+int tm6000_v4l2_exit(void);
+void tm6000_set_fourcc_format(struct tm6000_core *dev);
+
+void tm6000_remove_from_devlist(struct tm6000_core *dev);
+void tm6000_add_into_devlist(struct tm6000_core *dev);
+int tm6000_register_extension(struct tm6000_ops *ops);
+void tm6000_unregister_extension(struct tm6000_ops *ops);
+void tm6000_init_extension(struct tm6000_core *dev);
+void tm6000_close_extension(struct tm6000_core *dev);
+int tm6000_call_fillbuf(struct tm6000_core *dev, enum tm6000_ops_type type,
+			char *buf, int size);
+
+
+/* In tm6000-stds.c */
+void tm6000_get_std_res(struct tm6000_core *dev);
+int tm6000_set_standard(struct tm6000_core *dev);
+
+/* In tm6000-i2c.c */
+int tm6000_i2c_register(struct tm6000_core *dev);
+int tm6000_i2c_unregister(struct tm6000_core *dev);
+
+/* In tm6000-queue.c */
+
+int tm6000_v4l2_mmap(struct file *filp, struct vm_area_struct *vma);
+
+int tm6000_vidioc_streamon(struct file *file, void *priv,
+			   enum v4l2_buf_type i);
+int tm6000_vidioc_streamoff(struct file *file, void *priv,
+			    enum v4l2_buf_type i);
+int tm6000_vidioc_reqbufs(struct file *file, void *priv,
+			  struct v4l2_requestbuffers *rb);
+int tm6000_vidioc_querybuf(struct file *file, void *priv,
+			   struct v4l2_buffer *b);
+int tm6000_vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *b);
+int tm6000_vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *b);
+ssize_t tm6000_v4l2_read(struct file *filp, char __user * buf, size_t count,
+			 loff_t *f_pos);
+unsigned int tm6000_v4l2_poll(struct file *file,
+			      struct poll_table_struct *wait);
+int tm6000_queue_init(struct tm6000_core *dev);
+
+/* In tm6000-alsa.c */
+/*int tm6000_audio_init(struct tm6000_core *dev, int idx);*/
+
+/* In tm6000-input.c */
+int tm6000_ir_init(struct tm6000_core *dev);
+int tm6000_ir_fini(struct tm6000_core *dev);
+void tm6000_ir_wait(struct tm6000_core *dev, u8 state);
+int tm6000_ir_int_start(struct tm6000_core *dev);
+void tm6000_ir_int_stop(struct tm6000_core *dev);
+
+/* Debug stuff */
+
+extern int tm6000_debug;
+
+#define dprintk(dev, level, fmt, arg...) do {\
+	if (tm6000_debug & level) \
+		printk(KERN_INFO "(%lu) %s %s :"fmt, jiffies, \
+			 dev->name, __func__ , ##arg); } while (0)
+
+#define V4L2_DEBUG_REG		0x0004
+#define V4L2_DEBUG_I2C		0x0008
+#define V4L2_DEBUG_QUEUE	0x0010
+#define V4L2_DEBUG_ISOC		0x0020
+#define V4L2_DEBUG_RES_LOCK	0x0040	/* Resource locking */
+#define V4L2_DEBUG_OPEN		0x0080	/* video open/close debug */
+
+#define tm6000_err(fmt, arg...) do {\
+	printk(KERN_ERR "tm6000 %s :"fmt, \
+		__func__ , ##arg); } while (0)
diff --git a/drivers/media/usb/ttusb-budget/Kconfig b/drivers/media/usb/ttusb-budget/Kconfig
new file mode 100644
index 0000000..97bad7d
--- /dev/null
+++ b/drivers/media/usb/ttusb-budget/Kconfig
@@ -0,0 +1,18 @@
+config DVB_TTUSB_BUDGET
+	tristate "Technotrend/Hauppauge Nova-USB devices"
+	depends on DVB_CORE && USB && I2C && PCI
+	select DVB_CX22700 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_TDA1004X if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_VES1820 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_TDA8083 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_STV0299 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_STV0297 if MEDIA_SUBDRV_AUTOSELECT
+	select DVB_LNBP21 if MEDIA_SUBDRV_AUTOSELECT
+	help
+	  Support for external USB adapters designed by Technotrend and
+	  produced by Hauppauge, shipped under the brand name 'Nova-USB'.
+
+	  These devices don't have a MPEG decoder built in, so you need
+	  an external software decoder to watch TV.
+
+	  Say Y if you own such a device and want to use it.
diff --git a/drivers/media/usb/ttusb-budget/Makefile b/drivers/media/usb/ttusb-budget/Makefile
new file mode 100644
index 0000000..fe4372d
--- /dev/null
+++ b/drivers/media/usb/ttusb-budget/Makefile
@@ -0,0 +1,3 @@
+obj-$(CONFIG_DVB_TTUSB_BUDGET) += dvb-ttusb-budget.o
+
+ccflags-y += -Idrivers/media/dvb-frontends
diff --git a/drivers/media/usb/ttusb-budget/dvb-ttusb-budget.c b/drivers/media/usb/ttusb-budget/dvb-ttusb-budget.c
new file mode 100644
index 0000000..eed5689
--- /dev/null
+++ b/drivers/media/usb/ttusb-budget/dvb-ttusb-budget.c
@@ -0,0 +1,1809 @@
+/*
+ * TTUSB DVB driver
+ *
+ * Copyright (c) 2002 Holger Waechtler <holger@convergence.de>
+ * Copyright (c) 2003 Felix Domke <tmbinc@elitedvb.net>
+ *
+ *	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.
+ */
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/fs.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <linux/errno.h>
+#include <linux/jiffies.h>
+#include <linux/mutex.h>
+#include <linux/firmware.h>
+
+#include <media/dvb_frontend.h>
+#include <media/dmxdev.h>
+#include <media/dvb_demux.h>
+#include <media/dvb_net.h>
+#include "ves1820.h"
+#include "cx22700.h"
+#include "tda1004x.h"
+#include "stv0299.h"
+#include "tda8083.h"
+#include "stv0297.h"
+#include "lnbp21.h"
+
+#include <linux/dvb/frontend.h>
+#include <linux/dvb/dmx.h>
+#include <linux/pci.h>
+
+/*
+  TTUSB_HWSECTIONS:
+    the DSP supports filtering in hardware, however, since the "muxstream"
+    is a bit braindead (no matching channel masks or no matching filter mask),
+    we won't support this - yet. it doesn't event support negative filters,
+    so the best way is maybe to keep TTUSB_HWSECTIONS undef'd and just
+    parse TS data. USB bandwidth will be a problem when having large
+    datastreams, especially for dvb-net, but hey, that's not my problem.
+
+  TTUSB_DISEQC, TTUSB_TONE:
+    let the STC do the diseqc/tone stuff. this isn't supported at least with
+    my TTUSB, so let it undef'd unless you want to implement another
+    frontend. never tested.
+
+  debug:
+    define it to > 3 for really hardcore debugging. you probably don't want
+    this unless the device doesn't load at all. > 2 for bandwidth statistics.
+*/
+
+static int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off).");
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+#define dprintk(x...) do { if (debug) printk(KERN_DEBUG x); } while (0)
+
+#define ISO_BUF_COUNT      4
+#define FRAMES_PER_ISO_BUF 4
+#define ISO_FRAME_SIZE     912
+#define TTUSB_MAXCHANNEL   32
+#ifdef TTUSB_HWSECTIONS
+#define TTUSB_MAXFILTER    16	/* ??? */
+#endif
+
+#define TTUSB_REV_2_2	0x22
+#define TTUSB_BUDGET_NAME "ttusb_stc_fw"
+
+/*
+ *  since we're casting (struct ttusb*) <-> (struct dvb_demux*) around
+ *  the dvb_demux field must be the first in struct!!
+ */
+struct ttusb {
+	struct dvb_demux dvb_demux;
+	struct dmxdev dmxdev;
+	struct dvb_net dvbnet;
+
+	/* and one for USB access. */
+	struct mutex semi2c;
+	struct mutex semusb;
+
+	struct dvb_adapter adapter;
+	struct usb_device *dev;
+
+	struct i2c_adapter i2c_adap;
+
+	int disconnecting;
+	int iso_streaming;
+
+	unsigned int bulk_out_pipe;
+	unsigned int bulk_in_pipe;
+	unsigned int isoc_in_pipe;
+
+	void *iso_buffer;
+
+	struct urb *iso_urb[ISO_BUF_COUNT];
+
+	int running_feed_count;
+	int last_channel;
+	int last_filter;
+
+	u8 c;			/* transaction counter, wraps around...  */
+	enum fe_sec_tone_mode tone;
+	enum fe_sec_voltage voltage;
+
+	int mux_state;		// 0..2 - MuxSyncWord, 3 - nMuxPacks,    4 - muxpack
+	u8 mux_npacks;
+	u8 muxpack[256 + 8];
+	int muxpack_ptr, muxpack_len;
+
+	int insync;
+
+	int cc;			/* MuxCounter - will increment on EVERY MUX PACKET */
+	/* (including stuffing. yes. really.) */
+
+	u8 last_result[32];
+
+	int revision;
+
+	struct dvb_frontend* fe;
+};
+
+/* ugly workaround ... don't know why it's necessary to read */
+/* all result codes. */
+
+static int ttusb_cmd(struct ttusb *ttusb,
+	      const u8 * data, int len, int needresult)
+{
+	int actual_len;
+	int err;
+	int i;
+
+	if (debug >= 3) {
+		printk(KERN_DEBUG ">");
+		for (i = 0; i < len; ++i)
+			printk(KERN_CONT " %02x", data[i]);
+		printk(KERN_CONT "\n");
+	}
+
+	if (mutex_lock_interruptible(&ttusb->semusb) < 0)
+		return -EAGAIN;
+
+	err = usb_bulk_msg(ttusb->dev, ttusb->bulk_out_pipe,
+			   (u8 *) data, len, &actual_len, 1000);
+	if (err != 0) {
+		dprintk("%s: usb_bulk_msg(send) failed, err == %i!\n",
+			__func__, err);
+		mutex_unlock(&ttusb->semusb);
+		return err;
+	}
+	if (actual_len != len) {
+		dprintk("%s: only wrote %d of %d bytes\n", __func__,
+			actual_len, len);
+		mutex_unlock(&ttusb->semusb);
+		return -1;
+	}
+
+	err = usb_bulk_msg(ttusb->dev, ttusb->bulk_in_pipe,
+			   ttusb->last_result, 32, &actual_len, 1000);
+
+	if (err != 0) {
+		printk("%s: failed, receive error %d\n", __func__,
+		       err);
+		mutex_unlock(&ttusb->semusb);
+		return err;
+	}
+
+	if (debug >= 3) {
+		actual_len = ttusb->last_result[3] + 4;
+		printk(KERN_DEBUG "<");
+		for (i = 0; i < actual_len; ++i)
+			printk(KERN_CONT " %02x", ttusb->last_result[i]);
+		printk(KERN_CONT "\n");
+	}
+
+	if (!needresult)
+		mutex_unlock(&ttusb->semusb);
+	return 0;
+}
+
+static int ttusb_result(struct ttusb *ttusb, u8 * data, int len)
+{
+	memcpy(data, ttusb->last_result, len);
+	mutex_unlock(&ttusb->semusb);
+	return 0;
+}
+
+static int ttusb_i2c_msg(struct ttusb *ttusb,
+		  u8 addr, u8 * snd_buf, u8 snd_len, u8 * rcv_buf,
+		  u8 rcv_len)
+{
+	u8 b[0x28];
+	u8 id = ++ttusb->c;
+	int i, err;
+
+	if (snd_len > 0x28 - 7 || rcv_len > 0x20 - 7)
+		return -EINVAL;
+
+	b[0] = 0xaa;
+	b[1] = id;
+	b[2] = 0x31;
+	b[3] = snd_len + 3;
+	b[4] = addr << 1;
+	b[5] = snd_len;
+	b[6] = rcv_len;
+
+	for (i = 0; i < snd_len; i++)
+		b[7 + i] = snd_buf[i];
+
+	err = ttusb_cmd(ttusb, b, snd_len + 7, 1);
+
+	if (err)
+		return -EREMOTEIO;
+
+	err = ttusb_result(ttusb, b, 0x20);
+
+	/* check if the i2c transaction was successful */
+	if ((snd_len != b[5]) || (rcv_len != b[6])) return -EREMOTEIO;
+
+	if (rcv_len > 0) {
+
+		if (err || b[0] != 0x55 || b[1] != id) {
+			dprintk
+			    ("%s: usb_bulk_msg(recv) failed, err == %i, id == %02x, b == ",
+			     __func__, err, id);
+			return -EREMOTEIO;
+		}
+
+		for (i = 0; i < rcv_len; i++)
+			rcv_buf[i] = b[7 + i];
+	}
+
+	return rcv_len;
+}
+
+static int master_xfer(struct i2c_adapter* adapter, struct i2c_msg *msg, int num)
+{
+	struct ttusb *ttusb = i2c_get_adapdata(adapter);
+	int i = 0;
+	int inc;
+
+	if (mutex_lock_interruptible(&ttusb->semi2c) < 0)
+		return -EAGAIN;
+
+	while (i < num) {
+		u8 addr, snd_len, rcv_len, *snd_buf, *rcv_buf;
+		int err;
+
+		if (num > i + 1 && (msg[i + 1].flags & I2C_M_RD)) {
+			addr = msg[i].addr;
+			snd_buf = msg[i].buf;
+			snd_len = msg[i].len;
+			rcv_buf = msg[i + 1].buf;
+			rcv_len = msg[i + 1].len;
+			inc = 2;
+		} else {
+			addr = msg[i].addr;
+			snd_buf = msg[i].buf;
+			snd_len = msg[i].len;
+			rcv_buf = NULL;
+			rcv_len = 0;
+			inc = 1;
+		}
+
+		err = ttusb_i2c_msg(ttusb, addr,
+				    snd_buf, snd_len, rcv_buf, rcv_len);
+
+		if (err < rcv_len) {
+			dprintk("%s: i == %i\n", __func__, i);
+			break;
+		}
+
+		i += inc;
+	}
+
+	mutex_unlock(&ttusb->semi2c);
+	return i;
+}
+
+static int ttusb_boot_dsp(struct ttusb *ttusb)
+{
+	const struct firmware *fw;
+	int i, err;
+	u8 b[40];
+
+	err = request_firmware(&fw, "ttusb-budget/dspbootcode.bin",
+			       &ttusb->dev->dev);
+	if (err) {
+		printk(KERN_ERR "ttusb-budget: failed to request firmware\n");
+		return err;
+	}
+
+	/* BootBlock */
+	b[0] = 0xaa;
+	b[2] = 0x13;
+	b[3] = 28;
+
+	/* upload dsp code in 32 byte steps (36 didn't work for me ...) */
+	/* 32 is max packet size, no messages should be splitted. */
+	for (i = 0; i < fw->size; i += 28) {
+		memcpy(&b[4], &fw->data[i], 28);
+
+		b[1] = ++ttusb->c;
+
+		err = ttusb_cmd(ttusb, b, 32, 0);
+		if (err)
+			goto done;
+	}
+
+	/* last block ... */
+	b[1] = ++ttusb->c;
+	b[2] = 0x13;
+	b[3] = 0;
+
+	err = ttusb_cmd(ttusb, b, 4, 0);
+	if (err)
+		goto done;
+
+	/* BootEnd */
+	b[1] = ++ttusb->c;
+	b[2] = 0x14;
+	b[3] = 0;
+
+	err = ttusb_cmd(ttusb, b, 4, 0);
+
+      done:
+	release_firmware(fw);
+	if (err) {
+		dprintk("%s: usb_bulk_msg() failed, return value %i!\n",
+			__func__, err);
+	}
+
+	return err;
+}
+
+static int ttusb_set_channel(struct ttusb *ttusb, int chan_id, int filter_type,
+		      int pid)
+{
+	int err;
+	/* SetChannel */
+	u8 b[] = { 0xaa, ++ttusb->c, 0x22, 4, chan_id, filter_type,
+		(pid >> 8) & 0xff, pid & 0xff
+	};
+
+	err = ttusb_cmd(ttusb, b, sizeof(b), 0);
+	return err;
+}
+
+static int ttusb_del_channel(struct ttusb *ttusb, int channel_id)
+{
+	int err;
+	/* DelChannel */
+	u8 b[] = { 0xaa, ++ttusb->c, 0x23, 1, channel_id };
+
+	err = ttusb_cmd(ttusb, b, sizeof(b), 0);
+	return err;
+}
+
+#ifdef TTUSB_HWSECTIONS
+static int ttusb_set_filter(struct ttusb *ttusb, int filter_id,
+		     int associated_chan, u8 filter[8], u8 mask[8])
+{
+	int err;
+	/* SetFilter */
+	u8 b[] = { 0xaa, 0, 0x24, 0x1a, filter_id, associated_chan,
+		filter[0], filter[1], filter[2], filter[3],
+		filter[4], filter[5], filter[6], filter[7],
+		filter[8], filter[9], filter[10], filter[11],
+		mask[0], mask[1], mask[2], mask[3],
+		mask[4], mask[5], mask[6], mask[7],
+		mask[8], mask[9], mask[10], mask[11]
+	};
+
+	err = ttusb_cmd(ttusb, b, sizeof(b), 0);
+	return err;
+}
+
+static int ttusb_del_filter(struct ttusb *ttusb, int filter_id)
+{
+	int err;
+	/* DelFilter */
+	u8 b[] = { 0xaa, ++ttusb->c, 0x25, 1, filter_id };
+
+	err = ttusb_cmd(ttusb, b, sizeof(b), 0);
+	return err;
+}
+#endif
+
+static int ttusb_init_controller(struct ttusb *ttusb)
+{
+	u8 b0[] = { 0xaa, ++ttusb->c, 0x15, 1, 0 };
+	u8 b1[] = { 0xaa, ++ttusb->c, 0x15, 1, 1 };
+	u8 b2[] = { 0xaa, ++ttusb->c, 0x32, 1, 0 };
+	/* i2c write read: 5 bytes, addr 0x10, 0x02 bytes write, 1 bytes read. */
+	u8 b3[] =
+	    { 0xaa, ++ttusb->c, 0x31, 5, 0x10, 0x02, 0x01, 0x00, 0x1e };
+	u8 b4[] =
+	    { 0x55, ttusb->c, 0x31, 4, 0x10, 0x02, 0x01, 0x00, 0x1e };
+
+	u8 get_version[] = { 0xaa, ++ttusb->c, 0x17, 5, 0, 0, 0, 0, 0 };
+	u8 get_dsp_version[0x20] =
+	    { 0xaa, ++ttusb->c, 0x26, 28, 0, 0, 0, 0, 0 };
+	int err;
+
+	/* reset board */
+	if ((err = ttusb_cmd(ttusb, b0, sizeof(b0), 0)))
+		return err;
+
+	/* reset board (again?) */
+	if ((err = ttusb_cmd(ttusb, b1, sizeof(b1), 0)))
+		return err;
+
+	ttusb_boot_dsp(ttusb);
+
+	/* set i2c bit rate */
+	if ((err = ttusb_cmd(ttusb, b2, sizeof(b2), 0)))
+		return err;
+
+	if ((err = ttusb_cmd(ttusb, b3, sizeof(b3), 1)))
+		return err;
+
+	err = ttusb_result(ttusb, b4, sizeof(b4));
+
+	if ((err = ttusb_cmd(ttusb, get_version, sizeof(get_version), 1)))
+		return err;
+
+	if ((err = ttusb_result(ttusb, get_version, sizeof(get_version))))
+		return err;
+
+	dprintk("%s: stc-version: %c%c%c%c%c\n", __func__,
+		get_version[4], get_version[5], get_version[6],
+		get_version[7], get_version[8]);
+
+	if (memcmp(get_version + 4, "V 0.0", 5) &&
+	    memcmp(get_version + 4, "V 1.1", 5) &&
+	    memcmp(get_version + 4, "V 2.1", 5) &&
+	    memcmp(get_version + 4, "V 2.2", 5)) {
+		printk
+		    ("%s: unknown STC version %c%c%c%c%c, please report!\n",
+		     __func__, get_version[4], get_version[5],
+		     get_version[6], get_version[7], get_version[8]);
+	}
+
+	ttusb->revision = ((get_version[6] - '0') << 4) |
+			   (get_version[8] - '0');
+
+	err =
+	    ttusb_cmd(ttusb, get_dsp_version, sizeof(get_dsp_version), 1);
+	if (err)
+		return err;
+
+	err =
+	    ttusb_result(ttusb, get_dsp_version, sizeof(get_dsp_version));
+	if (err)
+		return err;
+	printk("%s: dsp-version: %c%c%c\n", __func__,
+	       get_dsp_version[4], get_dsp_version[5], get_dsp_version[6]);
+	return 0;
+}
+
+#ifdef TTUSB_DISEQC
+static int ttusb_send_diseqc(struct dvb_frontend* fe,
+			     const struct dvb_diseqc_master_cmd *cmd)
+{
+	struct ttusb* ttusb = (struct ttusb*) fe->dvb->priv;
+	u8 b[12] = { 0xaa, ++ttusb->c, 0x18 };
+
+	int err;
+
+	b[3] = 4 + 2 + cmd->msg_len;
+	b[4] = 0xFF;		/* send diseqc master, not burst */
+	b[5] = cmd->msg_len;
+
+	memcpy(b + 5, cmd->msg, cmd->msg_len);
+
+	/* Diseqc */
+	if ((err = ttusb_cmd(ttusb, b, 4 + b[3], 0))) {
+		dprintk("%s: usb_bulk_msg() failed, return value %i!\n",
+			__func__, err);
+	}
+
+	return err;
+}
+#endif
+
+static int ttusb_update_lnb(struct ttusb *ttusb)
+{
+	u8 b[] = { 0xaa, ++ttusb->c, 0x16, 5, /*power: */ 1,
+		ttusb->voltage == SEC_VOLTAGE_18 ? 0 : 1,
+		ttusb->tone == SEC_TONE_ON ? 1 : 0, 1, 1
+	};
+	int err;
+
+	/* SetLNB */
+	if ((err = ttusb_cmd(ttusb, b, sizeof(b), 0))) {
+		dprintk("%s: usb_bulk_msg() failed, return value %i!\n",
+			__func__, err);
+	}
+
+	return err;
+}
+
+static int ttusb_set_voltage(struct dvb_frontend *fe,
+			     enum fe_sec_voltage voltage)
+{
+	struct ttusb* ttusb = (struct ttusb*) fe->dvb->priv;
+
+	ttusb->voltage = voltage;
+	return ttusb_update_lnb(ttusb);
+}
+
+#ifdef TTUSB_TONE
+static int ttusb_set_tone(struct dvb_frontend *fe, enum fe_sec_tone_mode tone)
+{
+	struct ttusb* ttusb = (struct ttusb*) fe->dvb->priv;
+
+	ttusb->tone = tone;
+	return ttusb_update_lnb(ttusb);
+}
+#endif
+
+
+#if 0
+static void ttusb_set_led_freq(struct ttusb *ttusb, u8 freq)
+{
+	u8 b[] = { 0xaa, ++ttusb->c, 0x19, 1, freq };
+	int err, actual_len;
+
+	err = ttusb_cmd(ttusb, b, sizeof(b), 0);
+	if (err) {
+		dprintk("%s: usb_bulk_msg() failed, return value %i!\n",
+			__func__, err);
+	}
+}
+#endif
+
+/*****************************************************************************/
+
+#ifdef TTUSB_HWSECTIONS
+static void ttusb_handle_ts_data(struct ttusb_channel *channel,
+				 const u8 * data, int len);
+static void ttusb_handle_sec_data(struct ttusb_channel *channel,
+				  const u8 * data, int len);
+#endif
+
+static int numpkt, numts, numstuff, numsec, numinvalid;
+static unsigned long lastj;
+
+static void ttusb_process_muxpack(struct ttusb *ttusb, const u8 * muxpack,
+			   int len)
+{
+	u16 csum = 0, cc;
+	int i;
+
+	if (len < 4 || len & 0x1) {
+		pr_warn("%s: muxpack has invalid len %d\n", __func__, len);
+		numinvalid++;
+		return;
+	}
+
+	for (i = 0; i < len; i += 2)
+		csum ^= le16_to_cpup((__le16 *) (muxpack + i));
+	if (csum) {
+		printk("%s: muxpack with incorrect checksum, ignoring\n",
+		       __func__);
+		numinvalid++;
+		return;
+	}
+
+	cc = (muxpack[len - 4] << 8) | muxpack[len - 3];
+	cc &= 0x7FFF;
+	if ((cc != ttusb->cc) && (ttusb->cc != -1))
+		printk("%s: cc discontinuity (%d frames missing)\n",
+		       __func__, (cc - ttusb->cc) & 0x7FFF);
+	ttusb->cc = (cc + 1) & 0x7FFF;
+	if (muxpack[0] & 0x80) {
+#ifdef TTUSB_HWSECTIONS
+		/* section data */
+		int pusi = muxpack[0] & 0x40;
+		int channel = muxpack[0] & 0x1F;
+		int payload = muxpack[1];
+		const u8 *data = muxpack + 2;
+		/* check offset flag */
+		if (muxpack[0] & 0x20)
+			data++;
+
+		ttusb_handle_sec_data(ttusb->channel + channel, data,
+				      payload);
+		data += payload;
+
+		if ((!!(ttusb->muxpack[0] & 0x20)) ^
+		    !!(ttusb->muxpack[1] & 1))
+			data++;
+#warning TODO: pusi
+		printk("cc: %04x\n", (data[0] << 8) | data[1]);
+#endif
+		numsec++;
+	} else if (muxpack[0] == 0x47) {
+#ifdef TTUSB_HWSECTIONS
+		/* we have TS data here! */
+		int pid = ((muxpack[1] & 0x0F) << 8) | muxpack[2];
+		int channel;
+		for (channel = 0; channel < TTUSB_MAXCHANNEL; ++channel)
+			if (ttusb->channel[channel].active
+			    && (pid == ttusb->channel[channel].pid))
+				ttusb_handle_ts_data(ttusb->channel +
+						     channel, muxpack,
+						     188);
+#endif
+		numts++;
+		dvb_dmx_swfilter_packets(&ttusb->dvb_demux, muxpack, 1);
+	} else if (muxpack[0] != 0) {
+		numinvalid++;
+		printk("illegal muxpack type %02x\n", muxpack[0]);
+	} else
+		numstuff++;
+}
+
+static void ttusb_process_frame(struct ttusb *ttusb, u8 * data, int len)
+{
+	int maxwork = 1024;
+	while (len) {
+		if (!(maxwork--)) {
+			printk("%s: too much work\n", __func__);
+			break;
+		}
+
+		switch (ttusb->mux_state) {
+		case 0:
+		case 1:
+		case 2:
+			len--;
+			if (*data++ == 0xAA)
+				++ttusb->mux_state;
+			else {
+				ttusb->mux_state = 0;
+				if (ttusb->insync) {
+					dprintk("%s: %02x\n",
+						__func__, data[-1]);
+					printk(KERN_INFO "%s: lost sync.\n",
+					       __func__);
+					ttusb->insync = 0;
+				}
+			}
+			break;
+		case 3:
+			ttusb->insync = 1;
+			len--;
+			ttusb->mux_npacks = *data++;
+			++ttusb->mux_state;
+			ttusb->muxpack_ptr = 0;
+			/* maximum bytes, until we know the length */
+			ttusb->muxpack_len = 2;
+			break;
+		case 4:
+			{
+				int avail;
+				avail = len;
+				if (avail >
+				    (ttusb->muxpack_len -
+				     ttusb->muxpack_ptr))
+					avail =
+					    ttusb->muxpack_len -
+					    ttusb->muxpack_ptr;
+				memcpy(ttusb->muxpack + ttusb->muxpack_ptr,
+				       data, avail);
+				ttusb->muxpack_ptr += avail;
+				BUG_ON(ttusb->muxpack_ptr > 264);
+				data += avail;
+				len -= avail;
+				/* determine length */
+				if (ttusb->muxpack_ptr == 2) {
+					if (ttusb->muxpack[0] & 0x80) {
+						ttusb->muxpack_len =
+						    ttusb->muxpack[1] + 2;
+						if (ttusb->
+						    muxpack[0] & 0x20)
+							ttusb->
+							    muxpack_len++;
+						if ((!!
+						     (ttusb->
+						      muxpack[0] & 0x20)) ^
+						    !!(ttusb->
+						       muxpack[1] & 1))
+							ttusb->
+							    muxpack_len++;
+						ttusb->muxpack_len += 4;
+					} else if (ttusb->muxpack[0] ==
+						   0x47)
+						ttusb->muxpack_len =
+						    188 + 4;
+					else if (ttusb->muxpack[0] == 0x00)
+						ttusb->muxpack_len =
+						    ttusb->muxpack[1] + 2 +
+						    4;
+					else {
+						dprintk
+						    ("%s: invalid state: first byte is %x\n",
+						     __func__,
+						     ttusb->muxpack[0]);
+						ttusb->mux_state = 0;
+					}
+				}
+
+			/*
+			 * if length is valid and we reached the end:
+			 * goto next muxpack
+			 */
+				if ((ttusb->muxpack_ptr >= 2) &&
+				    (ttusb->muxpack_ptr ==
+				     ttusb->muxpack_len)) {
+					ttusb_process_muxpack(ttusb,
+							      ttusb->
+							      muxpack,
+							      ttusb->
+							      muxpack_ptr);
+					ttusb->muxpack_ptr = 0;
+					/* maximum bytes, until we know the length */
+					ttusb->muxpack_len = 2;
+
+				/*
+				 * no muxpacks left?
+				 * return to search-sync state
+				 */
+					if (!ttusb->mux_npacks--) {
+						ttusb->mux_state = 0;
+						break;
+					}
+				}
+				break;
+			}
+		default:
+			BUG();
+			break;
+		}
+	}
+}
+
+static void ttusb_iso_irq(struct urb *urb)
+{
+	struct ttusb *ttusb = urb->context;
+	struct usb_iso_packet_descriptor *d;
+	u8 *data;
+	int len, i;
+
+	if (!ttusb->iso_streaming)
+		return;
+
+#if 0
+	printk("%s: status %d, errcount == %d, length == %i\n",
+	       __func__,
+	       urb->status, urb->error_count, urb->actual_length);
+#endif
+
+	if (!urb->status) {
+		for (i = 0; i < urb->number_of_packets; ++i) {
+			numpkt++;
+			if (time_after_eq(jiffies, lastj + HZ)) {
+				dprintk("frames/s: %lu (ts: %d, stuff %d, sec: %d, invalid: %d, all: %d)\n",
+					numpkt * HZ / (jiffies - lastj),
+					numts, numstuff, numsec, numinvalid,
+					numts + numstuff + numsec + numinvalid);
+				numts = numstuff = numsec = numinvalid = 0;
+				lastj = jiffies;
+				numpkt = 0;
+			}
+			d = &urb->iso_frame_desc[i];
+			data = urb->transfer_buffer + d->offset;
+			len = d->actual_length;
+			d->actual_length = 0;
+			d->status = 0;
+			ttusb_process_frame(ttusb, data, len);
+		}
+	}
+	usb_submit_urb(urb, GFP_ATOMIC);
+}
+
+static void ttusb_free_iso_urbs(struct ttusb *ttusb)
+{
+	int i;
+
+	for (i = 0; i < ISO_BUF_COUNT; i++)
+		usb_free_urb(ttusb->iso_urb[i]);
+	kfree(ttusb->iso_buffer);
+}
+
+static int ttusb_alloc_iso_urbs(struct ttusb *ttusb)
+{
+	int i;
+
+	ttusb->iso_buffer = kcalloc(FRAMES_PER_ISO_BUF * ISO_BUF_COUNT,
+			ISO_FRAME_SIZE, GFP_KERNEL);
+	if (!ttusb->iso_buffer)
+		return -ENOMEM;
+
+	for (i = 0; i < ISO_BUF_COUNT; i++) {
+		struct urb *urb;
+
+		if (!
+		    (urb =
+		     usb_alloc_urb(FRAMES_PER_ISO_BUF, GFP_ATOMIC))) {
+			ttusb_free_iso_urbs(ttusb);
+			return -ENOMEM;
+		}
+
+		ttusb->iso_urb[i] = urb;
+	}
+
+	return 0;
+}
+
+static void ttusb_stop_iso_xfer(struct ttusb *ttusb)
+{
+	int i;
+
+	for (i = 0; i < ISO_BUF_COUNT; i++)
+		usb_kill_urb(ttusb->iso_urb[i]);
+
+	ttusb->iso_streaming = 0;
+}
+
+static int ttusb_start_iso_xfer(struct ttusb *ttusb)
+{
+	int i, j, err, buffer_offset = 0;
+
+	if (ttusb->iso_streaming) {
+		printk("%s: iso xfer already running!\n", __func__);
+		return 0;
+	}
+
+	ttusb->cc = -1;
+	ttusb->insync = 0;
+	ttusb->mux_state = 0;
+
+	for (i = 0; i < ISO_BUF_COUNT; i++) {
+		int frame_offset = 0;
+		struct urb *urb = ttusb->iso_urb[i];
+
+		urb->dev = ttusb->dev;
+		urb->context = ttusb;
+		urb->complete = ttusb_iso_irq;
+		urb->pipe = ttusb->isoc_in_pipe;
+		urb->transfer_flags = URB_ISO_ASAP;
+		urb->interval = 1;
+		urb->number_of_packets = FRAMES_PER_ISO_BUF;
+		urb->transfer_buffer_length =
+		    ISO_FRAME_SIZE * FRAMES_PER_ISO_BUF;
+		urb->transfer_buffer = ttusb->iso_buffer + buffer_offset;
+		buffer_offset += ISO_FRAME_SIZE * FRAMES_PER_ISO_BUF;
+
+		for (j = 0; j < FRAMES_PER_ISO_BUF; j++) {
+			urb->iso_frame_desc[j].offset = frame_offset;
+			urb->iso_frame_desc[j].length = ISO_FRAME_SIZE;
+			frame_offset += ISO_FRAME_SIZE;
+		}
+	}
+
+	for (i = 0; i < ISO_BUF_COUNT; i++) {
+		if ((err = usb_submit_urb(ttusb->iso_urb[i], GFP_ATOMIC))) {
+			ttusb_stop_iso_xfer(ttusb);
+			printk
+			    ("%s: failed urb submission (%i: err = %i)!\n",
+			     __func__, i, err);
+			return err;
+		}
+	}
+
+	ttusb->iso_streaming = 1;
+
+	return 0;
+}
+
+#ifdef TTUSB_HWSECTIONS
+static void ttusb_handle_ts_data(struct dvb_demux_feed *dvbdmxfeed, const u8 * data,
+			  int len)
+{
+	dvbdmxfeed->cb.ts(data, len, 0, 0, &dvbdmxfeed->feed.ts, 0);
+}
+
+static void ttusb_handle_sec_data(struct dvb_demux_feed *dvbdmxfeed, const u8 * data,
+			   int len)
+{
+//      struct dvb_demux_feed *dvbdmxfeed = channel->dvbdmxfeed;
+#error TODO: handle ugly stuff
+//      dvbdmxfeed->cb.sec(data, len, 0, 0, &dvbdmxfeed->feed.sec, 0);
+}
+#endif
+
+static int ttusb_start_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+	struct ttusb *ttusb = (struct ttusb *) dvbdmxfeed->demux;
+	int feed_type = 1;
+
+	dprintk("ttusb_start_feed\n");
+
+	switch (dvbdmxfeed->type) {
+	case DMX_TYPE_TS:
+		break;
+	case DMX_TYPE_SEC:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (dvbdmxfeed->type == DMX_TYPE_TS) {
+		switch (dvbdmxfeed->pes_type) {
+		case DMX_PES_VIDEO:
+		case DMX_PES_AUDIO:
+		case DMX_PES_TELETEXT:
+		case DMX_PES_PCR:
+		case DMX_PES_OTHER:
+			break;
+		default:
+			return -EINVAL;
+		}
+	}
+
+#ifdef TTUSB_HWSECTIONS
+#error TODO: allocate filters
+	if (dvbdmxfeed->type == DMX_TYPE_TS) {
+		feed_type = 1;
+	} else if (dvbdmxfeed->type == DMX_TYPE_SEC) {
+		feed_type = 2;
+	}
+#endif
+
+	ttusb_set_channel(ttusb, dvbdmxfeed->index, feed_type, dvbdmxfeed->pid);
+
+	if (0 == ttusb->running_feed_count++)
+		ttusb_start_iso_xfer(ttusb);
+
+	return 0;
+}
+
+static int ttusb_stop_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+	struct ttusb *ttusb = (struct ttusb *) dvbdmxfeed->demux;
+
+	ttusb_del_channel(ttusb, dvbdmxfeed->index);
+
+	if (--ttusb->running_feed_count == 0)
+		ttusb_stop_iso_xfer(ttusb);
+
+	return 0;
+}
+
+static int ttusb_setup_interfaces(struct ttusb *ttusb)
+{
+	usb_set_interface(ttusb->dev, 1, 1);
+
+	ttusb->bulk_out_pipe = usb_sndbulkpipe(ttusb->dev, 1);
+	ttusb->bulk_in_pipe = usb_rcvbulkpipe(ttusb->dev, 1);
+	ttusb->isoc_in_pipe = usb_rcvisocpipe(ttusb->dev, 2);
+
+	return 0;
+}
+
+#if 0
+static u8 stc_firmware[8192];
+
+static int stc_open(struct inode *inode, struct file *file)
+{
+	struct ttusb *ttusb = file->private_data;
+	int addr;
+
+	for (addr = 0; addr < 8192; addr += 16) {
+		u8 snd_buf[2] = { addr >> 8, addr & 0xFF };
+		ttusb_i2c_msg(ttusb, 0x50, snd_buf, 2, stc_firmware + addr,
+			      16);
+	}
+
+	return 0;
+}
+
+static ssize_t stc_read(struct file *file, char *buf, size_t count,
+		 loff_t *offset)
+{
+	return simple_read_from_buffer(buf, count, offset, stc_firmware, 8192);
+}
+
+static int stc_release(struct inode *inode, struct file *file)
+{
+	return 0;
+}
+
+static const struct file_operations stc_fops = {
+	.owner = THIS_MODULE,
+	.read = stc_read,
+	.open = stc_open,
+	.release = stc_release,
+};
+#endif
+
+static u32 functionality(struct i2c_adapter *adapter)
+{
+	return I2C_FUNC_I2C;
+}
+
+
+
+static int alps_tdmb7_tuner_set_params(struct dvb_frontend *fe)
+{
+	struct dtv_frontend_properties *p = &fe->dtv_property_cache;
+	struct ttusb* ttusb = (struct ttusb*) fe->dvb->priv;
+	u8 data[4];
+	struct i2c_msg msg = {.addr=0x61, .flags=0, .buf=data, .len=sizeof(data) };
+	u32 div;
+
+	div = (p->frequency + 36166667) / 166667;
+
+	data[0] = (div >> 8) & 0x7f;
+	data[1] = div & 0xff;
+	data[2] = ((div >> 10) & 0x60) | 0x85;
+	data[3] = p->frequency < 592000000 ? 0x40 : 0x80;
+
+	if (fe->ops.i2c_gate_ctrl)
+		fe->ops.i2c_gate_ctrl(fe, 1);
+	if (i2c_transfer(&ttusb->i2c_adap, &msg, 1) != 1) return -EIO;
+	return 0;
+}
+
+static struct cx22700_config alps_tdmb7_config = {
+	.demod_address = 0x43,
+};
+
+
+
+
+
+static int philips_tdm1316l_tuner_init(struct dvb_frontend* fe)
+{
+	struct ttusb* ttusb = (struct ttusb*) fe->dvb->priv;
+	static u8 td1316_init[] = { 0x0b, 0xf5, 0x85, 0xab };
+	static u8 disable_mc44BC374c[] = { 0x1d, 0x74, 0xa0, 0x68 };
+	struct i2c_msg tuner_msg = { .addr=0x60, .flags=0, .buf=td1316_init, .len=sizeof(td1316_init) };
+
+	// setup PLL configuration
+	if (fe->ops.i2c_gate_ctrl)
+		fe->ops.i2c_gate_ctrl(fe, 1);
+	if (i2c_transfer(&ttusb->i2c_adap, &tuner_msg, 1) != 1) return -EIO;
+	msleep(1);
+
+	// disable the mc44BC374c (do not check for errors)
+	tuner_msg.addr = 0x65;
+	tuner_msg.buf = disable_mc44BC374c;
+	tuner_msg.len = sizeof(disable_mc44BC374c);
+	if (fe->ops.i2c_gate_ctrl)
+		fe->ops.i2c_gate_ctrl(fe, 1);
+	if (i2c_transfer(&ttusb->i2c_adap, &tuner_msg, 1) != 1) {
+		i2c_transfer(&ttusb->i2c_adap, &tuner_msg, 1);
+	}
+
+	return 0;
+}
+
+static int philips_tdm1316l_tuner_set_params(struct dvb_frontend *fe)
+{
+	struct dtv_frontend_properties *p = &fe->dtv_property_cache;
+	struct ttusb* ttusb = (struct ttusb*) fe->dvb->priv;
+	u8 tuner_buf[4];
+	struct i2c_msg tuner_msg = {.addr=0x60, .flags=0, .buf=tuner_buf, .len=sizeof(tuner_buf) };
+	int tuner_frequency = 0;
+	u8 band, cp, filter;
+
+	// determine charge pump
+	tuner_frequency = p->frequency + 36130000;
+	if (tuner_frequency < 87000000) return -EINVAL;
+	else if (tuner_frequency < 130000000) cp = 3;
+	else if (tuner_frequency < 160000000) cp = 5;
+	else if (tuner_frequency < 200000000) cp = 6;
+	else if (tuner_frequency < 290000000) cp = 3;
+	else if (tuner_frequency < 420000000) cp = 5;
+	else if (tuner_frequency < 480000000) cp = 6;
+	else if (tuner_frequency < 620000000) cp = 3;
+	else if (tuner_frequency < 830000000) cp = 5;
+	else if (tuner_frequency < 895000000) cp = 7;
+	else return -EINVAL;
+
+	// determine band
+	if (p->frequency < 49000000)
+		return -EINVAL;
+	else if (p->frequency < 159000000)
+		band = 1;
+	else if (p->frequency < 444000000)
+		band = 2;
+	else if (p->frequency < 861000000)
+		band = 4;
+	else return -EINVAL;
+
+	// setup PLL filter
+	switch (p->bandwidth_hz) {
+	case 6000000:
+		tda1004x_writereg(fe, 0x0C, 0);
+		filter = 0;
+		break;
+
+	case 7000000:
+		tda1004x_writereg(fe, 0x0C, 0);
+		filter = 0;
+		break;
+
+	case 8000000:
+		tda1004x_writereg(fe, 0x0C, 0xFF);
+		filter = 1;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	// calculate divisor
+	// ((36130000+((1000000/6)/2)) + Finput)/(1000000/6)
+	tuner_frequency = (((p->frequency / 1000) * 6) + 217280) / 1000;
+
+	// setup tuner buffer
+	tuner_buf[0] = tuner_frequency >> 8;
+	tuner_buf[1] = tuner_frequency & 0xff;
+	tuner_buf[2] = 0xca;
+	tuner_buf[3] = (cp << 5) | (filter << 3) | band;
+
+	if (fe->ops.i2c_gate_ctrl)
+		fe->ops.i2c_gate_ctrl(fe, 1);
+	if (i2c_transfer(&ttusb->i2c_adap, &tuner_msg, 1) != 1)
+		return -EIO;
+
+	msleep(1);
+	return 0;
+}
+
+static int philips_tdm1316l_request_firmware(struct dvb_frontend* fe, const struct firmware **fw, char* name)
+{
+	struct ttusb* ttusb = (struct ttusb*) fe->dvb->priv;
+
+	return request_firmware(fw, name, &ttusb->dev->dev);
+}
+
+static struct tda1004x_config philips_tdm1316l_config = {
+
+	.demod_address = 0x8,
+	.invert = 1,
+	.invert_oclk = 0,
+	.request_firmware = philips_tdm1316l_request_firmware,
+};
+
+static u8 alps_bsbe1_inittab[] = {
+	0x01, 0x15,
+	0x02, 0x30,
+	0x03, 0x00,
+	0x04, 0x7d,             /* F22FR = 0x7d, F22 = f_VCO / 128 / 0x7d = 22 kHz */
+	0x05, 0x35,             /* I2CT = 0, SCLT = 1, SDAT = 1 */
+	0x06, 0x40,             /* DAC not used, set to high impendance mode */
+	0x07, 0x00,             /* DAC LSB */
+	0x08, 0x40,             /* DiSEqC off, LNB power on OP2/LOCK pin on */
+	0x09, 0x00,             /* FIFO */
+	0x0c, 0x51,             /* OP1 ctl = Normal, OP1 val = 1 (LNB Power ON) */
+	0x0d, 0x82,             /* DC offset compensation = ON, beta_agc1 = 2 */
+	0x0e, 0x23,             /* alpha_tmg = 2, beta_tmg = 3 */
+	0x10, 0x3f,             // AGC2  0x3d
+	0x11, 0x84,
+	0x12, 0xb9,
+	0x15, 0xc9,             // lock detector threshold
+	0x16, 0x00,
+	0x17, 0x00,
+	0x18, 0x00,
+	0x19, 0x00,
+	0x1a, 0x00,
+	0x1f, 0x50,
+	0x20, 0x00,
+	0x21, 0x00,
+	0x22, 0x00,
+	0x23, 0x00,
+	0x28, 0x00,             // out imp: normal  out type: parallel FEC mode:0
+	0x29, 0x1e,             // 1/2 threshold
+	0x2a, 0x14,             // 2/3 threshold
+	0x2b, 0x0f,             // 3/4 threshold
+	0x2c, 0x09,             // 5/6 threshold
+	0x2d, 0x05,             // 7/8 threshold
+	0x2e, 0x01,
+	0x31, 0x1f,             // test all FECs
+	0x32, 0x19,             // viterbi and synchro search
+	0x33, 0xfc,             // rs control
+	0x34, 0x93,             // error control
+	0x0f, 0x92,
+	0xff, 0xff
+};
+
+static u8 alps_bsru6_inittab[] = {
+	0x01, 0x15,
+	0x02, 0x30,
+	0x03, 0x00,
+	0x04, 0x7d,		/* F22FR = 0x7d, F22 = f_VCO / 128 / 0x7d = 22 kHz */
+	0x05, 0x35,		/* I2CT = 0, SCLT = 1, SDAT = 1 */
+	0x06, 0x40,		/* DAC not used, set to high impendance mode */
+	0x07, 0x00,		/* DAC LSB */
+	0x08, 0x40,		/* DiSEqC off, LNB power on OP2/LOCK pin on */
+	0x09, 0x00,		/* FIFO */
+	0x0c, 0x51,		/* OP1 ctl = Normal, OP1 val = 1 (LNB Power ON) */
+	0x0d, 0x82,		/* DC offset compensation = ON, beta_agc1 = 2 */
+	0x0e, 0x23,		/* alpha_tmg = 2, beta_tmg = 3 */
+	0x10, 0x3f,		// AGC2  0x3d
+	0x11, 0x84,
+	0x12, 0xb9,
+	0x15, 0xc9,		// lock detector threshold
+	0x16, 0x00,
+	0x17, 0x00,
+	0x18, 0x00,
+	0x19, 0x00,
+	0x1a, 0x00,
+	0x1f, 0x50,
+	0x20, 0x00,
+	0x21, 0x00,
+	0x22, 0x00,
+	0x23, 0x00,
+	0x28, 0x00,		// out imp: normal  out type: parallel FEC mode:0
+	0x29, 0x1e,		// 1/2 threshold
+	0x2a, 0x14,		// 2/3 threshold
+	0x2b, 0x0f,		// 3/4 threshold
+	0x2c, 0x09,		// 5/6 threshold
+	0x2d, 0x05,		// 7/8 threshold
+	0x2e, 0x01,
+	0x31, 0x1f,		// test all FECs
+	0x32, 0x19,		// viterbi and synchro search
+	0x33, 0xfc,		// rs control
+	0x34, 0x93,		// error control
+	0x0f, 0x52,
+	0xff, 0xff
+};
+
+static int alps_stv0299_set_symbol_rate(struct dvb_frontend *fe, u32 srate, u32 ratio)
+{
+	u8 aclk = 0;
+	u8 bclk = 0;
+
+	if (srate < 1500000) {
+		aclk = 0xb7;
+		bclk = 0x47;
+	} else if (srate < 3000000) {
+		aclk = 0xb7;
+		bclk = 0x4b;
+	} else if (srate < 7000000) {
+		aclk = 0xb7;
+		bclk = 0x4f;
+	} else if (srate < 14000000) {
+		aclk = 0xb7;
+		bclk = 0x53;
+	} else if (srate < 30000000) {
+		aclk = 0xb6;
+		bclk = 0x53;
+	} else if (srate < 45000000) {
+		aclk = 0xb4;
+		bclk = 0x51;
+	}
+
+	stv0299_writereg(fe, 0x13, aclk);
+	stv0299_writereg(fe, 0x14, bclk);
+	stv0299_writereg(fe, 0x1f, (ratio >> 16) & 0xff);
+	stv0299_writereg(fe, 0x20, (ratio >> 8) & 0xff);
+	stv0299_writereg(fe, 0x21, (ratio) & 0xf0);
+
+	return 0;
+}
+
+static int philips_tsa5059_tuner_set_params(struct dvb_frontend *fe)
+{
+	struct dtv_frontend_properties *p = &fe->dtv_property_cache;
+	struct ttusb* ttusb = (struct ttusb*) fe->dvb->priv;
+	u8 buf[4];
+	u32 div;
+	struct i2c_msg msg = {.addr = 0x61,.flags = 0,.buf = buf,.len = sizeof(buf) };
+
+	if ((p->frequency < 950000) || (p->frequency > 2150000))
+		return -EINVAL;
+
+	div = (p->frequency + (125 - 1)) / 125;	/* round correctly */
+	buf[0] = (div >> 8) & 0x7f;
+	buf[1] = div & 0xff;
+	buf[2] = 0x80 | ((div & 0x18000) >> 10) | 4;
+	buf[3] = 0xC4;
+
+	if (p->frequency > 1530000)
+		buf[3] = 0xC0;
+
+	/* BSBE1 wants XCE bit set */
+	if (ttusb->revision == TTUSB_REV_2_2)
+		buf[3] |= 0x20;
+
+	if (fe->ops.i2c_gate_ctrl)
+		fe->ops.i2c_gate_ctrl(fe, 1);
+	if (i2c_transfer(&ttusb->i2c_adap, &msg, 1) != 1)
+		return -EIO;
+
+	return 0;
+}
+
+static struct stv0299_config alps_stv0299_config = {
+	.demod_address = 0x68,
+	.inittab = alps_bsru6_inittab,
+	.mclk = 88000000UL,
+	.invert = 1,
+	.skip_reinit = 0,
+	.lock_output = STV0299_LOCKOUTPUT_1,
+	.volt13_op0_op1 = STV0299_VOLT13_OP1,
+	.min_delay_ms = 100,
+	.set_symbol_rate = alps_stv0299_set_symbol_rate,
+};
+
+static int ttusb_novas_grundig_29504_491_tuner_set_params(struct dvb_frontend *fe)
+{
+	struct dtv_frontend_properties *p = &fe->dtv_property_cache;
+	struct ttusb* ttusb = (struct ttusb*) fe->dvb->priv;
+	u8 buf[4];
+	u32 div;
+	struct i2c_msg msg = {.addr = 0x61,.flags = 0,.buf = buf,.len = sizeof(buf) };
+
+	div = p->frequency / 125;
+
+	buf[0] = (div >> 8) & 0x7f;
+	buf[1] = div & 0xff;
+	buf[2] = 0x8e;
+	buf[3] = 0x00;
+
+	if (fe->ops.i2c_gate_ctrl)
+		fe->ops.i2c_gate_ctrl(fe, 1);
+	if (i2c_transfer(&ttusb->i2c_adap, &msg, 1) != 1)
+		return -EIO;
+
+	return 0;
+}
+
+static struct tda8083_config ttusb_novas_grundig_29504_491_config = {
+
+	.demod_address = 0x68,
+};
+
+static int alps_tdbe2_tuner_set_params(struct dvb_frontend *fe)
+{
+	struct dtv_frontend_properties *p = &fe->dtv_property_cache;
+	struct ttusb* ttusb = fe->dvb->priv;
+	u32 div;
+	u8 data[4];
+	struct i2c_msg msg = { .addr = 0x62, .flags = 0, .buf = data, .len = sizeof(data) };
+
+	div = (p->frequency + 35937500 + 31250) / 62500;
+
+	data[0] = (div >> 8) & 0x7f;
+	data[1] = div & 0xff;
+	data[2] = 0x85 | ((div >> 10) & 0x60);
+	data[3] = (p->frequency < 174000000 ? 0x88 : p->frequency < 470000000 ? 0x84 : 0x81);
+
+	if (fe->ops.i2c_gate_ctrl)
+		fe->ops.i2c_gate_ctrl(fe, 1);
+	if (i2c_transfer (&ttusb->i2c_adap, &msg, 1) != 1)
+		return -EIO;
+
+	return 0;
+}
+
+
+static struct ves1820_config alps_tdbe2_config = {
+	.demod_address = 0x09,
+	.xin = 57840000UL,
+	.invert = 1,
+	.selagc = VES1820_SELAGC_SIGNAMPERR,
+};
+
+static u8 read_pwm(struct ttusb* ttusb)
+{
+	u8 b = 0xff;
+	u8 pwm;
+	struct i2c_msg msg[] = { { .addr = 0x50,.flags = 0,.buf = &b,.len = 1 },
+				{ .addr = 0x50,.flags = I2C_M_RD,.buf = &pwm,.len = 1} };
+
+	if ((i2c_transfer(&ttusb->i2c_adap, msg, 2) != 2) || (pwm == 0xff))
+		pwm = 0x48;
+
+	return pwm;
+}
+
+
+static int dvbc_philips_tdm1316l_tuner_set_params(struct dvb_frontend *fe)
+{
+	struct dtv_frontend_properties *p = &fe->dtv_property_cache;
+	struct ttusb *ttusb = (struct ttusb *) fe->dvb->priv;
+	u8 tuner_buf[5];
+	struct i2c_msg tuner_msg = {.addr = 0x60,
+				    .flags = 0,
+				    .buf = tuner_buf,
+				    .len = sizeof(tuner_buf) };
+	int tuner_frequency = 0;
+	u8 band, cp, filter;
+
+	// determine charge pump
+	tuner_frequency = p->frequency;
+	if      (tuner_frequency <  87000000) {return -EINVAL;}
+	else if (tuner_frequency < 130000000) {cp = 3; band = 1;}
+	else if (tuner_frequency < 160000000) {cp = 5; band = 1;}
+	else if (tuner_frequency < 200000000) {cp = 6; band = 1;}
+	else if (tuner_frequency < 290000000) {cp = 3; band = 2;}
+	else if (tuner_frequency < 420000000) {cp = 5; band = 2;}
+	else if (tuner_frequency < 480000000) {cp = 6; band = 2;}
+	else if (tuner_frequency < 620000000) {cp = 3; band = 4;}
+	else if (tuner_frequency < 830000000) {cp = 5; band = 4;}
+	else if (tuner_frequency < 895000000) {cp = 7; band = 4;}
+	else {return -EINVAL;}
+
+	// assume PLL filter should always be 8MHz for the moment.
+	filter = 1;
+
+	// calculate divisor
+	// (Finput + Fif)/Fref; Fif = 36125000 Hz, Fref = 62500 Hz
+	tuner_frequency = ((p->frequency + 36125000) / 62500);
+
+	// setup tuner buffer
+	tuner_buf[0] = tuner_frequency >> 8;
+	tuner_buf[1] = tuner_frequency & 0xff;
+	tuner_buf[2] = 0xc8;
+	tuner_buf[3] = (cp << 5) | (filter << 3) | band;
+	tuner_buf[4] = 0x80;
+
+	if (fe->ops.i2c_gate_ctrl)
+		fe->ops.i2c_gate_ctrl(fe, 1);
+	if (i2c_transfer(&ttusb->i2c_adap, &tuner_msg, 1) != 1) {
+		printk("dvb-ttusb-budget: dvbc_philips_tdm1316l_pll_set Error 1\n");
+		return -EIO;
+	}
+
+	msleep(50);
+
+	if (fe->ops.i2c_gate_ctrl)
+		fe->ops.i2c_gate_ctrl(fe, 1);
+	if (i2c_transfer(&ttusb->i2c_adap, &tuner_msg, 1) != 1) {
+		printk("dvb-ttusb-budget: dvbc_philips_tdm1316l_pll_set Error 2\n");
+		return -EIO;
+	}
+
+	msleep(1);
+
+	return 0;
+}
+
+static u8 dvbc_philips_tdm1316l_inittab[] = {
+	0x80, 0x21,
+	0x80, 0x20,
+	0x81, 0x01,
+	0x81, 0x00,
+	0x00, 0x09,
+	0x01, 0x69,
+	0x03, 0x00,
+	0x04, 0x00,
+	0x07, 0x00,
+	0x08, 0x00,
+	0x20, 0x00,
+	0x21, 0x40,
+	0x22, 0x00,
+	0x23, 0x00,
+	0x24, 0x40,
+	0x25, 0x88,
+	0x30, 0xff,
+	0x31, 0x00,
+	0x32, 0xff,
+	0x33, 0x00,
+	0x34, 0x50,
+	0x35, 0x7f,
+	0x36, 0x00,
+	0x37, 0x20,
+	0x38, 0x00,
+	0x40, 0x1c,
+	0x41, 0xff,
+	0x42, 0x29,
+	0x43, 0x20,
+	0x44, 0xff,
+	0x45, 0x00,
+	0x46, 0x00,
+	0x49, 0x04,
+	0x4a, 0xff,
+	0x4b, 0x7f,
+	0x52, 0x30,
+	0x55, 0xae,
+	0x56, 0x47,
+	0x57, 0xe1,
+	0x58, 0x3a,
+	0x5a, 0x1e,
+	0x5b, 0x34,
+	0x60, 0x00,
+	0x63, 0x00,
+	0x64, 0x00,
+	0x65, 0x00,
+	0x66, 0x00,
+	0x67, 0x00,
+	0x68, 0x00,
+	0x69, 0x00,
+	0x6a, 0x02,
+	0x6b, 0x00,
+	0x70, 0xff,
+	0x71, 0x00,
+	0x72, 0x00,
+	0x73, 0x00,
+	0x74, 0x0c,
+	0x80, 0x00,
+	0x81, 0x00,
+	0x82, 0x00,
+	0x83, 0x00,
+	0x84, 0x04,
+	0x85, 0x80,
+	0x86, 0x24,
+	0x87, 0x78,
+	0x88, 0x00,
+	0x89, 0x00,
+	0x90, 0x01,
+	0x91, 0x01,
+	0xa0, 0x00,
+	0xa1, 0x00,
+	0xa2, 0x00,
+	0xb0, 0x91,
+	0xb1, 0x0b,
+	0xc0, 0x4b,
+	0xc1, 0x00,
+	0xc2, 0x00,
+	0xd0, 0x00,
+	0xd1, 0x00,
+	0xd2, 0x00,
+	0xd3, 0x00,
+	0xd4, 0x00,
+	0xd5, 0x00,
+	0xde, 0x00,
+	0xdf, 0x00,
+	0x61, 0x38,
+	0x62, 0x0a,
+	0x53, 0x13,
+	0x59, 0x08,
+	0x55, 0x00,
+	0x56, 0x40,
+	0x57, 0x08,
+	0x58, 0x3d,
+	0x88, 0x10,
+	0xa0, 0x00,
+	0xa0, 0x00,
+	0xa0, 0x00,
+	0xa0, 0x04,
+	0xff, 0xff,
+};
+
+static struct stv0297_config dvbc_philips_tdm1316l_config = {
+	.demod_address = 0x1c,
+	.inittab = dvbc_philips_tdm1316l_inittab,
+	.invert = 0,
+};
+
+static void frontend_init(struct ttusb* ttusb)
+{
+	switch(le16_to_cpu(ttusb->dev->descriptor.idProduct)) {
+	case 0x1003: // Hauppauge/TT Nova-USB-S budget (stv0299/ALPS BSRU6|BSBE1(tsa5059))
+		// try the stv0299 based first
+		ttusb->fe = dvb_attach(stv0299_attach, &alps_stv0299_config, &ttusb->i2c_adap);
+		if (ttusb->fe != NULL) {
+			ttusb->fe->ops.tuner_ops.set_params = philips_tsa5059_tuner_set_params;
+
+			if(ttusb->revision == TTUSB_REV_2_2) { // ALPS BSBE1
+				alps_stv0299_config.inittab = alps_bsbe1_inittab;
+				dvb_attach(lnbp21_attach, ttusb->fe, &ttusb->i2c_adap, 0, 0);
+			} else { // ALPS BSRU6
+				ttusb->fe->ops.set_voltage = ttusb_set_voltage;
+			}
+			break;
+		}
+
+		// Grundig 29504-491
+		ttusb->fe = dvb_attach(tda8083_attach, &ttusb_novas_grundig_29504_491_config, &ttusb->i2c_adap);
+		if (ttusb->fe != NULL) {
+			ttusb->fe->ops.tuner_ops.set_params = ttusb_novas_grundig_29504_491_tuner_set_params;
+			ttusb->fe->ops.set_voltage = ttusb_set_voltage;
+			break;
+		}
+		break;
+
+	case 0x1004: // Hauppauge/TT DVB-C budget (ves1820/ALPS TDBE2(sp5659))
+		ttusb->fe = dvb_attach(ves1820_attach, &alps_tdbe2_config, &ttusb->i2c_adap, read_pwm(ttusb));
+		if (ttusb->fe != NULL) {
+			ttusb->fe->ops.tuner_ops.set_params = alps_tdbe2_tuner_set_params;
+			break;
+		}
+
+		ttusb->fe = dvb_attach(stv0297_attach, &dvbc_philips_tdm1316l_config, &ttusb->i2c_adap);
+		if (ttusb->fe != NULL) {
+			ttusb->fe->ops.tuner_ops.set_params = dvbc_philips_tdm1316l_tuner_set_params;
+			break;
+		}
+		break;
+
+	case 0x1005: // Hauppauge/TT Nova-USB-t budget (tda10046/Philips td1316(tda6651tt) OR cx22700/ALPS TDMB7(??))
+		// try the ALPS TDMB7 first
+		ttusb->fe = dvb_attach(cx22700_attach, &alps_tdmb7_config, &ttusb->i2c_adap);
+		if (ttusb->fe != NULL) {
+			ttusb->fe->ops.tuner_ops.set_params = alps_tdmb7_tuner_set_params;
+			break;
+		}
+
+		// Philips td1316
+		ttusb->fe = dvb_attach(tda10046_attach, &philips_tdm1316l_config, &ttusb->i2c_adap);
+		if (ttusb->fe != NULL) {
+			ttusb->fe->ops.tuner_ops.init = philips_tdm1316l_tuner_init;
+			ttusb->fe->ops.tuner_ops.set_params = philips_tdm1316l_tuner_set_params;
+			break;
+		}
+		break;
+	}
+
+	if (ttusb->fe == NULL) {
+		printk("dvb-ttusb-budget: A frontend driver was not found for device [%04x:%04x]\n",
+		       le16_to_cpu(ttusb->dev->descriptor.idVendor),
+		       le16_to_cpu(ttusb->dev->descriptor.idProduct));
+	} else {
+		if (dvb_register_frontend(&ttusb->adapter, ttusb->fe)) {
+			printk("dvb-ttusb-budget: Frontend registration failed!\n");
+			dvb_frontend_detach(ttusb->fe);
+			ttusb->fe = NULL;
+		}
+	}
+}
+
+
+
+static const struct i2c_algorithm ttusb_dec_algo = {
+	.master_xfer	= master_xfer,
+	.functionality	= functionality,
+};
+
+static int ttusb_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+	struct usb_device *udev;
+	struct ttusb *ttusb;
+	int result;
+
+	dprintk("%s: TTUSB DVB connected\n", __func__);
+
+	udev = interface_to_usbdev(intf);
+
+	if (intf->altsetting->desc.bInterfaceNumber != 1) return -ENODEV;
+
+	if (!(ttusb = kzalloc(sizeof(struct ttusb), GFP_KERNEL)))
+		return -ENOMEM;
+
+	ttusb->dev = udev;
+	ttusb->c = 0;
+	ttusb->mux_state = 0;
+	mutex_init(&ttusb->semi2c);
+
+	mutex_lock(&ttusb->semi2c);
+
+	mutex_init(&ttusb->semusb);
+
+	ttusb_setup_interfaces(ttusb);
+
+	result = ttusb_alloc_iso_urbs(ttusb);
+	if (result < 0) {
+		dprintk("%s: ttusb_alloc_iso_urbs - failed\n", __func__);
+		mutex_unlock(&ttusb->semi2c);
+		kfree(ttusb);
+		return result;
+	}
+
+	if (ttusb_init_controller(ttusb))
+		printk("ttusb_init_controller: error\n");
+
+	mutex_unlock(&ttusb->semi2c);
+
+	result = dvb_register_adapter(&ttusb->adapter,
+				      "Technotrend/Hauppauge Nova-USB",
+				      THIS_MODULE, &udev->dev, adapter_nr);
+	if (result < 0) {
+		ttusb_free_iso_urbs(ttusb);
+		kfree(ttusb);
+		return result;
+	}
+	ttusb->adapter.priv = ttusb;
+
+	/* i2c */
+	memset(&ttusb->i2c_adap, 0, sizeof(struct i2c_adapter));
+	strcpy(ttusb->i2c_adap.name, "TTUSB DEC");
+
+	i2c_set_adapdata(&ttusb->i2c_adap, ttusb);
+
+	ttusb->i2c_adap.algo              = &ttusb_dec_algo;
+	ttusb->i2c_adap.algo_data         = NULL;
+	ttusb->i2c_adap.dev.parent	  = &udev->dev;
+
+	result = i2c_add_adapter(&ttusb->i2c_adap);
+	if (result)
+		goto err_unregister_adapter;
+
+	memset(&ttusb->dvb_demux, 0, sizeof(ttusb->dvb_demux));
+
+	ttusb->dvb_demux.dmx.capabilities =
+	    DMX_TS_FILTERING | DMX_SECTION_FILTERING;
+	ttusb->dvb_demux.priv = NULL;
+#ifdef TTUSB_HWSECTIONS
+	ttusb->dvb_demux.filternum = TTUSB_MAXFILTER;
+#else
+	ttusb->dvb_demux.filternum = 32;
+#endif
+	ttusb->dvb_demux.feednum = TTUSB_MAXCHANNEL;
+	ttusb->dvb_demux.start_feed = ttusb_start_feed;
+	ttusb->dvb_demux.stop_feed = ttusb_stop_feed;
+	ttusb->dvb_demux.write_to_decoder = NULL;
+
+	result = dvb_dmx_init(&ttusb->dvb_demux);
+	if (result < 0) {
+		printk("ttusb_dvb: dvb_dmx_init failed (errno = %d)\n", result);
+		result = -ENODEV;
+		goto err_i2c_del_adapter;
+	}
+//FIXME dmxdev (nur WAS?)
+	ttusb->dmxdev.filternum = ttusb->dvb_demux.filternum;
+	ttusb->dmxdev.demux = &ttusb->dvb_demux.dmx;
+	ttusb->dmxdev.capabilities = 0;
+
+	result = dvb_dmxdev_init(&ttusb->dmxdev, &ttusb->adapter);
+	if (result < 0) {
+		printk("ttusb_dvb: dvb_dmxdev_init failed (errno = %d)\n",
+		       result);
+		result = -ENODEV;
+		goto err_release_dmx;
+	}
+
+	if (dvb_net_init(&ttusb->adapter, &ttusb->dvbnet, &ttusb->dvb_demux.dmx)) {
+		printk("ttusb_dvb: dvb_net_init failed!\n");
+		result = -ENODEV;
+		goto err_release_dmxdev;
+	}
+
+	usb_set_intfdata(intf, (void *) ttusb);
+
+	frontend_init(ttusb);
+
+	return 0;
+
+err_release_dmxdev:
+	dvb_dmxdev_release(&ttusb->dmxdev);
+err_release_dmx:
+	dvb_dmx_release(&ttusb->dvb_demux);
+err_i2c_del_adapter:
+	i2c_del_adapter(&ttusb->i2c_adap);
+err_unregister_adapter:
+	dvb_unregister_adapter (&ttusb->adapter);
+	ttusb_free_iso_urbs(ttusb);
+	kfree(ttusb);
+	return result;
+}
+
+static void ttusb_disconnect(struct usb_interface *intf)
+{
+	struct ttusb *ttusb = usb_get_intfdata(intf);
+
+	usb_set_intfdata(intf, NULL);
+
+	ttusb->disconnecting = 1;
+
+	ttusb_stop_iso_xfer(ttusb);
+
+	ttusb->dvb_demux.dmx.close(&ttusb->dvb_demux.dmx);
+	dvb_net_release(&ttusb->dvbnet);
+	dvb_dmxdev_release(&ttusb->dmxdev);
+	dvb_dmx_release(&ttusb->dvb_demux);
+	if (ttusb->fe != NULL) {
+		dvb_unregister_frontend(ttusb->fe);
+		dvb_frontend_detach(ttusb->fe);
+	}
+	i2c_del_adapter(&ttusb->i2c_adap);
+	dvb_unregister_adapter(&ttusb->adapter);
+
+	ttusb_free_iso_urbs(ttusb);
+
+	kfree(ttusb);
+
+	dprintk("%s: TTUSB DVB disconnected\n", __func__);
+}
+
+static const struct usb_device_id ttusb_table[] = {
+	{USB_DEVICE(0xb48, 0x1003)},
+	{USB_DEVICE(0xb48, 0x1004)},
+	{USB_DEVICE(0xb48, 0x1005)},
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, ttusb_table);
+
+static struct usb_driver ttusb_driver = {
+      .name		= "ttusb",
+      .probe		= ttusb_probe,
+      .disconnect	= ttusb_disconnect,
+      .id_table		= ttusb_table,
+};
+
+module_usb_driver(ttusb_driver);
+
+MODULE_AUTHOR("Holger Waechtler <holger@convergence.de>");
+MODULE_DESCRIPTION("TTUSB DVB Driver");
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE("ttusb-budget/dspbootcode.bin");
diff --git a/drivers/media/usb/ttusb-dec/Kconfig b/drivers/media/usb/ttusb-dec/Kconfig
new file mode 100644
index 0000000..b205903
--- /dev/null
+++ b/drivers/media/usb/ttusb-dec/Kconfig
@@ -0,0 +1,21 @@
+config DVB_TTUSB_DEC
+	tristate "Technotrend/Hauppauge USB DEC devices"
+	depends on DVB_CORE && USB && INPUT && PCI
+	select CRC32
+	help
+	  Support for external USB adapters designed by Technotrend and
+	  produced by Hauppauge, shipped under the brand name 'DEC2000-t'
+	  and 'DEC3000-s'.
+
+	  Even if these devices have a MPEG decoder built in, they transmit
+	  only compressed MPEG data over the USB bus, so you need
+	  an external software decoder to watch TV on your computer.
+
+	  This driver needs external firmware. Please use the commands
+	  "<kerneldir>/scripts/get_dvb_firmware dec2000t",
+	  "<kerneldir>/scripts/get_dvb_firmware dec2540t",
+	  "<kerneldir>/scripts/get_dvb_firmware dec3000s",
+	  download/extract them, and then copy them to /usr/lib/hotplug/firmware
+	  or /lib/firmware (depending on configuration of firmware hotplug).
+
+	  Say Y if you own such a device and want to use it.
diff --git a/drivers/media/usb/ttusb-dec/Makefile b/drivers/media/usb/ttusb-dec/Makefile
new file mode 100644
index 0000000..dde9168
--- /dev/null
+++ b/drivers/media/usb/ttusb-dec/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_DVB_TTUSB_DEC) += ttusb_dec.o ttusbdecfe.o
diff --git a/drivers/media/usb/ttusb-dec/ttusb_dec.c b/drivers/media/usb/ttusb-dec/ttusb_dec.c
new file mode 100644
index 0000000..44ca66c
--- /dev/null
+++ b/drivers/media/usb/ttusb-dec/ttusb_dec.c
@@ -0,0 +1,1804 @@
+/*
+ * TTUSB DEC Driver
+ *
+ * Copyright (C) 2003-2004 Alex Woods <linux-dvb@giblets.org>
+ * IR support by Peter Beutner <p.beutner@gmx.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/usb.h>
+#include <linux/interrupt.h>
+#include <linux/firmware.h>
+#include <linux/crc32.h>
+#include <linux/init.h>
+#include <linux/input.h>
+
+#include <linux/mutex.h>
+
+#include <media/dmxdev.h>
+#include <media/dvb_demux.h>
+#include <media/dvb_frontend.h>
+#include <media/dvb_net.h>
+#include "ttusbdecfe.h"
+
+static int debug;
+static int output_pva;
+static int enable_rc;
+
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "Turn on/off debugging (default:off).");
+module_param(output_pva, int, 0444);
+MODULE_PARM_DESC(output_pva, "Output PVA from dvr device (default:off)");
+module_param(enable_rc, int, 0644);
+MODULE_PARM_DESC(enable_rc, "Turn on/off IR remote control(default: off)");
+
+DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr);
+
+#define dprintk	if (debug) printk
+
+#define DRIVER_NAME		"TechnoTrend/Hauppauge DEC USB"
+
+#define COMMAND_PIPE		0x03
+#define RESULT_PIPE		0x04
+#define IN_PIPE			0x08
+#define OUT_PIPE		0x07
+#define IRQ_PIPE		0x0A
+
+#define COMMAND_PACKET_SIZE	0x3c
+#define ARM_PACKET_SIZE		0x1000
+#define IRQ_PACKET_SIZE		0x8
+
+#define ISO_BUF_COUNT		0x04
+#define FRAMES_PER_ISO_BUF	0x04
+#define ISO_FRAME_SIZE		0x0380
+
+#define	MAX_PVA_LENGTH		6144
+
+enum ttusb_dec_model {
+	TTUSB_DEC2000T,
+	TTUSB_DEC2540T,
+	TTUSB_DEC3000S
+};
+
+enum ttusb_dec_packet_type {
+	TTUSB_DEC_PACKET_PVA,
+	TTUSB_DEC_PACKET_SECTION,
+	TTUSB_DEC_PACKET_EMPTY
+};
+
+enum ttusb_dec_interface {
+	TTUSB_DEC_INTERFACE_INITIAL,
+	TTUSB_DEC_INTERFACE_IN,
+	TTUSB_DEC_INTERFACE_OUT
+};
+
+typedef int (dvb_filter_pes2ts_cb_t) (void *, unsigned char *);
+
+struct dvb_filter_pes2ts {
+	unsigned char buf[188];
+	unsigned char cc;
+	dvb_filter_pes2ts_cb_t *cb;
+	void *priv;
+};
+
+struct ttusb_dec {
+	enum ttusb_dec_model		model;
+	char				*model_name;
+	char				*firmware_name;
+	int				can_playback;
+
+	/* DVB bits */
+	struct dvb_adapter		adapter;
+	struct dmxdev			dmxdev;
+	struct dvb_demux		demux;
+	struct dmx_frontend		frontend;
+	struct dvb_net			dvb_net;
+	struct dvb_frontend*		fe;
+
+	u16			pid[DMX_PES_OTHER];
+
+	/* USB bits */
+	struct usb_device		*udev;
+	u8				trans_count;
+	unsigned int			command_pipe;
+	unsigned int			result_pipe;
+	unsigned int			in_pipe;
+	unsigned int			out_pipe;
+	unsigned int			irq_pipe;
+	enum ttusb_dec_interface	interface;
+	struct mutex			usb_mutex;
+
+	void			*irq_buffer;
+	struct urb		*irq_urb;
+	dma_addr_t		irq_dma_handle;
+	void			*iso_buffer;
+	struct urb		*iso_urb[ISO_BUF_COUNT];
+	int			iso_stream_count;
+	struct mutex		iso_mutex;
+
+	u8				packet[MAX_PVA_LENGTH + 4];
+	enum ttusb_dec_packet_type	packet_type;
+	int				packet_state;
+	int				packet_length;
+	int				packet_payload_length;
+	u16				next_packet_id;
+
+	int				pva_stream_count;
+	int				filter_stream_count;
+
+	struct dvb_filter_pes2ts	a_pes2ts;
+	struct dvb_filter_pes2ts	v_pes2ts;
+
+	u8			v_pes[16 + MAX_PVA_LENGTH];
+	int			v_pes_length;
+	int			v_pes_postbytes;
+
+	struct list_head	urb_frame_list;
+	struct tasklet_struct	urb_tasklet;
+	spinlock_t		urb_frame_list_lock;
+
+	struct dvb_demux_filter	*audio_filter;
+	struct dvb_demux_filter	*video_filter;
+	struct list_head	filter_info_list;
+	spinlock_t		filter_info_list_lock;
+
+	struct input_dev	*rc_input_dev;
+	char			rc_phys[64];
+
+	int			active; /* Loaded successfully */
+};
+
+struct urb_frame {
+	u8			data[ISO_FRAME_SIZE];
+	int			length;
+	struct list_head	urb_frame_list;
+};
+
+struct filter_info {
+	u8			stream_id;
+	struct dvb_demux_filter	*filter;
+	struct list_head	filter_info_list;
+};
+
+static u16 rc_keys[] = {
+	KEY_POWER,
+	KEY_MUTE,
+	KEY_1,
+	KEY_2,
+	KEY_3,
+	KEY_4,
+	KEY_5,
+	KEY_6,
+	KEY_7,
+	KEY_8,
+	KEY_9,
+	KEY_0,
+	KEY_CHANNELUP,
+	KEY_VOLUMEDOWN,
+	KEY_OK,
+	KEY_VOLUMEUP,
+	KEY_CHANNELDOWN,
+	KEY_PREVIOUS,
+	KEY_ESC,
+	KEY_RED,
+	KEY_GREEN,
+	KEY_YELLOW,
+	KEY_BLUE,
+	KEY_OPTION,
+	KEY_M,
+	KEY_RADIO
+};
+
+static void dvb_filter_pes2ts_init(struct dvb_filter_pes2ts *p2ts,
+				   unsigned short pid,
+				   dvb_filter_pes2ts_cb_t *cb, void *priv)
+{
+	unsigned char *buf=p2ts->buf;
+
+	buf[0]=0x47;
+	buf[1]=(pid>>8);
+	buf[2]=pid&0xff;
+	p2ts->cc=0;
+	p2ts->cb=cb;
+	p2ts->priv=priv;
+}
+
+static int dvb_filter_pes2ts(struct dvb_filter_pes2ts *p2ts,
+			     unsigned char *pes, int len, int payload_start)
+{
+	unsigned char *buf=p2ts->buf;
+	int ret=0, rest;
+
+	//len=6+((pes[4]<<8)|pes[5]);
+
+	if (payload_start)
+		buf[1]|=0x40;
+	else
+		buf[1]&=~0x40;
+	while (len>=184) {
+		buf[3]=0x10|((p2ts->cc++)&0x0f);
+		memcpy(buf+4, pes, 184);
+		if ((ret=p2ts->cb(p2ts->priv, buf)))
+			return ret;
+		len-=184; pes+=184;
+		buf[1]&=~0x40;
+	}
+	if (!len)
+		return 0;
+	buf[3]=0x30|((p2ts->cc++)&0x0f);
+	rest=183-len;
+	if (rest) {
+		buf[5]=0x00;
+		if (rest-1)
+			memset(buf+6, 0xff, rest-1);
+	}
+	buf[4]=rest;
+	memcpy(buf+5+rest, pes, len);
+	return p2ts->cb(p2ts->priv, buf);
+}
+
+static void ttusb_dec_set_model(struct ttusb_dec *dec,
+				enum ttusb_dec_model model);
+
+static void ttusb_dec_handle_irq( struct urb *urb)
+{
+	struct ttusb_dec *dec = urb->context;
+	char *buffer = dec->irq_buffer;
+	int retval;
+
+	switch(urb->status) {
+		case 0: /*success*/
+			break;
+		case -ECONNRESET:
+		case -ENOENT:
+		case -ESHUTDOWN:
+		case -ETIME:
+			/* this urb is dead, cleanup */
+			dprintk("%s:urb shutting down with status: %d\n",
+					__func__, urb->status);
+			return;
+		default:
+			dprintk("%s:nonzero status received: %d\n",
+					__func__,urb->status);
+			goto exit;
+	}
+
+	if ((buffer[0] == 0x1) && (buffer[2] == 0x15))  {
+		/*
+		 * IR - Event
+		 *
+		 * this is an fact a bit too simple implementation;
+		 * the box also reports a keyrepeat signal
+		 * (with buffer[3] == 0x40) in an intervall of ~100ms.
+		 * But to handle this correctly we had to imlemenent some
+		 * kind of timer which signals a 'key up' event if no
+		 * keyrepeat signal is received for lets say 200ms.
+		 * this should/could be added later ...
+		 * for now lets report each signal as a key down and up
+		 */
+		if (buffer[4] - 1 < ARRAY_SIZE(rc_keys)) {
+			dprintk("%s:rc signal:%d\n", __func__, buffer[4]);
+			input_report_key(dec->rc_input_dev, rc_keys[buffer[4] - 1], 1);
+			input_sync(dec->rc_input_dev);
+			input_report_key(dec->rc_input_dev, rc_keys[buffer[4] - 1], 0);
+			input_sync(dec->rc_input_dev);
+		}
+	}
+
+exit:
+	retval = usb_submit_urb(urb, GFP_ATOMIC);
+	if (retval)
+		printk("%s - usb_commit_urb failed with result: %d\n",
+			__func__, retval);
+}
+
+static u16 crc16(u16 crc, const u8 *buf, size_t len)
+{
+	u16 tmp;
+
+	while (len--) {
+		crc ^= *buf++;
+		crc ^= (u8)crc >> 4;
+		tmp = (u8)crc;
+		crc ^= (tmp ^ (tmp << 1)) << 4;
+	}
+	return crc;
+}
+
+static int ttusb_dec_send_command(struct ttusb_dec *dec, const u8 command,
+				  int param_length, const u8 params[],
+				  int *result_length, u8 cmd_result[])
+{
+	int result, actual_len;
+	u8 *b;
+
+	dprintk("%s\n", __func__);
+
+	b = kmalloc(COMMAND_PACKET_SIZE + 4, GFP_KERNEL);
+	if (!b)
+		return -ENOMEM;
+
+	if ((result = mutex_lock_interruptible(&dec->usb_mutex))) {
+		kfree(b);
+		printk("%s: Failed to lock usb mutex.\n", __func__);
+		return result;
+	}
+
+	b[0] = 0xaa;
+	b[1] = ++dec->trans_count;
+	b[2] = command;
+	b[3] = param_length;
+
+	if (params)
+		memcpy(&b[4], params, param_length);
+
+	if (debug) {
+		printk(KERN_DEBUG "%s: command: %*ph\n",
+		       __func__, param_length, b);
+	}
+
+	result = usb_bulk_msg(dec->udev, dec->command_pipe, b,
+			      COMMAND_PACKET_SIZE + 4, &actual_len, 1000);
+
+	if (result) {
+		printk("%s: command bulk message failed: error %d\n",
+		       __func__, result);
+		mutex_unlock(&dec->usb_mutex);
+		kfree(b);
+		return result;
+	}
+
+	result = usb_bulk_msg(dec->udev, dec->result_pipe, b,
+			      COMMAND_PACKET_SIZE + 4, &actual_len, 1000);
+
+	if (result) {
+		printk("%s: result bulk message failed: error %d\n",
+		       __func__, result);
+		mutex_unlock(&dec->usb_mutex);
+		kfree(b);
+		return result;
+	} else {
+		if (debug) {
+			printk(KERN_DEBUG "%s: result: %*ph\n",
+			       __func__, actual_len, b);
+		}
+
+		if (result_length)
+			*result_length = b[3];
+		if (cmd_result && b[3] > 0)
+			memcpy(cmd_result, &b[4], b[3]);
+
+		mutex_unlock(&dec->usb_mutex);
+
+		kfree(b);
+		return 0;
+	}
+}
+
+static int ttusb_dec_get_stb_state (struct ttusb_dec *dec, unsigned int *mode,
+				    unsigned int *model, unsigned int *version)
+{
+	u8 c[COMMAND_PACKET_SIZE];
+	int c_length;
+	int result;
+	__be32 tmp;
+
+	dprintk("%s\n", __func__);
+
+	result = ttusb_dec_send_command(dec, 0x08, 0, NULL, &c_length, c);
+	if (result)
+		return result;
+
+	if (c_length >= 0x0c) {
+		if (mode != NULL) {
+			memcpy(&tmp, c, 4);
+			*mode = ntohl(tmp);
+		}
+		if (model != NULL) {
+			memcpy(&tmp, &c[4], 4);
+			*model = ntohl(tmp);
+		}
+		if (version != NULL) {
+			memcpy(&tmp, &c[8], 4);
+			*version = ntohl(tmp);
+		}
+		return 0;
+	} else {
+		return -ENOENT;
+	}
+}
+
+static int ttusb_dec_audio_pes2ts_cb(void *priv, unsigned char *data)
+{
+	struct ttusb_dec *dec = priv;
+
+	dec->audio_filter->feed->cb.ts(data, 188, NULL, 0,
+				       &dec->audio_filter->feed->feed.ts, NULL);
+
+	return 0;
+}
+
+static int ttusb_dec_video_pes2ts_cb(void *priv, unsigned char *data)
+{
+	struct ttusb_dec *dec = priv;
+
+	dec->video_filter->feed->cb.ts(data, 188, NULL, 0,
+				       &dec->video_filter->feed->feed.ts, NULL);
+
+	return 0;
+}
+
+static void ttusb_dec_set_pids(struct ttusb_dec *dec)
+{
+	u8 b[] = { 0x00, 0x00, 0x00, 0x00,
+		   0x00, 0x00, 0xff, 0xff,
+		   0xff, 0xff, 0xff, 0xff };
+
+	__be16 pcr = htons(dec->pid[DMX_PES_PCR]);
+	__be16 audio = htons(dec->pid[DMX_PES_AUDIO]);
+	__be16 video = htons(dec->pid[DMX_PES_VIDEO]);
+
+	dprintk("%s\n", __func__);
+
+	memcpy(&b[0], &pcr, 2);
+	memcpy(&b[2], &audio, 2);
+	memcpy(&b[4], &video, 2);
+
+	ttusb_dec_send_command(dec, 0x50, sizeof(b), b, NULL, NULL);
+
+	dvb_filter_pes2ts_init(&dec->a_pes2ts, dec->pid[DMX_PES_AUDIO],
+			       ttusb_dec_audio_pes2ts_cb, dec);
+	dvb_filter_pes2ts_init(&dec->v_pes2ts, dec->pid[DMX_PES_VIDEO],
+			       ttusb_dec_video_pes2ts_cb, dec);
+	dec->v_pes_length = 0;
+	dec->v_pes_postbytes = 0;
+}
+
+static void ttusb_dec_process_pva(struct ttusb_dec *dec, u8 *pva, int length)
+{
+	if (length < 8) {
+		printk("%s: packet too short - discarding\n", __func__);
+		return;
+	}
+
+	if (length > 8 + MAX_PVA_LENGTH) {
+		printk("%s: packet too long - discarding\n", __func__);
+		return;
+	}
+
+	switch (pva[2]) {
+
+	case 0x01: {		/* VideoStream */
+		int prebytes = pva[5] & 0x03;
+		int postbytes = (pva[5] & 0x0c) >> 2;
+		__be16 v_pes_payload_length;
+
+		if (output_pva) {
+			dec->video_filter->feed->cb.ts(pva, length, NULL, 0,
+				&dec->video_filter->feed->feed.ts, NULL);
+			return;
+		}
+
+		if (dec->v_pes_postbytes > 0 &&
+		    dec->v_pes_postbytes == prebytes) {
+			memcpy(&dec->v_pes[dec->v_pes_length],
+			       &pva[12], prebytes);
+
+			dvb_filter_pes2ts(&dec->v_pes2ts, dec->v_pes,
+					  dec->v_pes_length + prebytes, 1);
+		}
+
+		if (pva[5] & 0x10) {
+			dec->v_pes[7] = 0x80;
+			dec->v_pes[8] = 0x05;
+
+			dec->v_pes[9] = 0x21 | ((pva[8] & 0xc0) >> 5);
+			dec->v_pes[10] = ((pva[8] & 0x3f) << 2) |
+					 ((pva[9] & 0xc0) >> 6);
+			dec->v_pes[11] = 0x01 |
+					 ((pva[9] & 0x3f) << 2) |
+					 ((pva[10] & 0x80) >> 6);
+			dec->v_pes[12] = ((pva[10] & 0x7f) << 1) |
+					 ((pva[11] & 0xc0) >> 7);
+			dec->v_pes[13] = 0x01 | ((pva[11] & 0x7f) << 1);
+
+			memcpy(&dec->v_pes[14], &pva[12 + prebytes],
+			       length - 12 - prebytes);
+			dec->v_pes_length = 14 + length - 12 - prebytes;
+		} else {
+			dec->v_pes[7] = 0x00;
+			dec->v_pes[8] = 0x00;
+
+			memcpy(&dec->v_pes[9], &pva[8], length - 8);
+			dec->v_pes_length = 9 + length - 8;
+		}
+
+		dec->v_pes_postbytes = postbytes;
+
+		if (dec->v_pes[9 + dec->v_pes[8]] == 0x00 &&
+		    dec->v_pes[10 + dec->v_pes[8]] == 0x00 &&
+		    dec->v_pes[11 + dec->v_pes[8]] == 0x01)
+			dec->v_pes[6] = 0x84;
+		else
+			dec->v_pes[6] = 0x80;
+
+		v_pes_payload_length = htons(dec->v_pes_length - 6 +
+					     postbytes);
+		memcpy(&dec->v_pes[4], &v_pes_payload_length, 2);
+
+		if (postbytes == 0)
+			dvb_filter_pes2ts(&dec->v_pes2ts, dec->v_pes,
+					  dec->v_pes_length, 1);
+
+		break;
+	}
+
+	case 0x02:		/* MainAudioStream */
+		if (output_pva) {
+			dec->audio_filter->feed->cb.ts(pva, length, NULL, 0,
+				&dec->audio_filter->feed->feed.ts, NULL);
+			return;
+		}
+
+		dvb_filter_pes2ts(&dec->a_pes2ts, &pva[8], length - 8,
+				  pva[5] & 0x10);
+		break;
+
+	default:
+		printk("%s: unknown PVA type: %02x.\n", __func__,
+		       pva[2]);
+		break;
+	}
+}
+
+static void ttusb_dec_process_filter(struct ttusb_dec *dec, u8 *packet,
+				     int length)
+{
+	struct list_head *item;
+	struct filter_info *finfo;
+	struct dvb_demux_filter *filter = NULL;
+	unsigned long flags;
+	u8 sid;
+
+	sid = packet[1];
+	spin_lock_irqsave(&dec->filter_info_list_lock, flags);
+	for (item = dec->filter_info_list.next; item != &dec->filter_info_list;
+	     item = item->next) {
+		finfo = list_entry(item, struct filter_info, filter_info_list);
+		if (finfo->stream_id == sid) {
+			filter = finfo->filter;
+			break;
+		}
+	}
+	spin_unlock_irqrestore(&dec->filter_info_list_lock, flags);
+
+	if (filter)
+		filter->feed->cb.sec(&packet[2], length - 2, NULL, 0,
+				     &filter->filter, NULL);
+}
+
+static void ttusb_dec_process_packet(struct ttusb_dec *dec)
+{
+	int i;
+	u16 csum = 0;
+	u16 packet_id;
+
+	if (dec->packet_length % 2) {
+		printk("%s: odd sized packet - discarding\n", __func__);
+		return;
+	}
+
+	for (i = 0; i < dec->packet_length; i += 2)
+		csum ^= ((dec->packet[i] << 8) + dec->packet[i + 1]);
+
+	if (csum) {
+		printk("%s: checksum failed - discarding\n", __func__);
+		return;
+	}
+
+	packet_id = dec->packet[dec->packet_length - 4] << 8;
+	packet_id += dec->packet[dec->packet_length - 3];
+
+	if ((packet_id != dec->next_packet_id) && dec->next_packet_id) {
+		printk("%s: warning: lost packets between %u and %u\n",
+		       __func__, dec->next_packet_id - 1, packet_id);
+	}
+
+	if (packet_id == 0xffff)
+		dec->next_packet_id = 0x8000;
+	else
+		dec->next_packet_id = packet_id + 1;
+
+	switch (dec->packet_type) {
+	case TTUSB_DEC_PACKET_PVA:
+		if (dec->pva_stream_count)
+			ttusb_dec_process_pva(dec, dec->packet,
+					      dec->packet_payload_length);
+		break;
+
+	case TTUSB_DEC_PACKET_SECTION:
+		if (dec->filter_stream_count)
+			ttusb_dec_process_filter(dec, dec->packet,
+						 dec->packet_payload_length);
+		break;
+
+	case TTUSB_DEC_PACKET_EMPTY:
+		break;
+	}
+}
+
+static void swap_bytes(u8 *b, int length)
+{
+	length -= length % 2;
+	for (; length; b += 2, length -= 2)
+		swap(*b, *(b + 1));
+}
+
+static void ttusb_dec_process_urb_frame(struct ttusb_dec *dec, u8 *b,
+					int length)
+{
+	swap_bytes(b, length);
+
+	while (length) {
+		switch (dec->packet_state) {
+
+		case 0:
+		case 1:
+		case 2:
+			if (*b++ == 0xaa)
+				dec->packet_state++;
+			else
+				dec->packet_state = 0;
+
+			length--;
+			break;
+
+		case 3:
+			if (*b == 0x00) {
+				dec->packet_state++;
+				dec->packet_length = 0;
+			} else if (*b != 0xaa) {
+				dec->packet_state = 0;
+			}
+
+			b++;
+			length--;
+			break;
+
+		case 4:
+			dec->packet[dec->packet_length++] = *b++;
+
+			if (dec->packet_length == 2) {
+				if (dec->packet[0] == 'A' &&
+				    dec->packet[1] == 'V') {
+					dec->packet_type =
+						TTUSB_DEC_PACKET_PVA;
+					dec->packet_state++;
+				} else if (dec->packet[0] == 'S') {
+					dec->packet_type =
+						TTUSB_DEC_PACKET_SECTION;
+					dec->packet_state++;
+				} else if (dec->packet[0] == 0x00) {
+					dec->packet_type =
+						TTUSB_DEC_PACKET_EMPTY;
+					dec->packet_payload_length = 2;
+					dec->packet_state = 7;
+				} else {
+					printk("%s: unknown packet type: %02x%02x\n",
+					       __func__,
+					       dec->packet[0], dec->packet[1]);
+					dec->packet_state = 0;
+				}
+			}
+
+			length--;
+			break;
+
+		case 5:
+			dec->packet[dec->packet_length++] = *b++;
+
+			if (dec->packet_type == TTUSB_DEC_PACKET_PVA &&
+			    dec->packet_length == 8) {
+				dec->packet_state++;
+				dec->packet_payload_length = 8 +
+					(dec->packet[6] << 8) +
+					dec->packet[7];
+			} else if (dec->packet_type ==
+					TTUSB_DEC_PACKET_SECTION &&
+				   dec->packet_length == 5) {
+				dec->packet_state++;
+				dec->packet_payload_length = 5 +
+					((dec->packet[3] & 0x0f) << 8) +
+					dec->packet[4];
+			}
+
+			length--;
+			break;
+
+		case 6: {
+			int remainder = dec->packet_payload_length -
+					dec->packet_length;
+
+			if (length >= remainder) {
+				memcpy(dec->packet + dec->packet_length,
+				       b, remainder);
+				dec->packet_length += remainder;
+				b += remainder;
+				length -= remainder;
+				dec->packet_state++;
+			} else {
+				memcpy(&dec->packet[dec->packet_length],
+				       b, length);
+				dec->packet_length += length;
+				length = 0;
+			}
+
+			break;
+		}
+
+		case 7: {
+			int tail = 4;
+
+			dec->packet[dec->packet_length++] = *b++;
+
+			if (dec->packet_type == TTUSB_DEC_PACKET_SECTION &&
+			    dec->packet_payload_length % 2)
+				tail++;
+
+			if (dec->packet_length ==
+			    dec->packet_payload_length + tail) {
+				ttusb_dec_process_packet(dec);
+				dec->packet_state = 0;
+			}
+
+			length--;
+			break;
+		}
+
+		default:
+			printk("%s: illegal packet state encountered.\n",
+			       __func__);
+			dec->packet_state = 0;
+		}
+	}
+}
+
+static void ttusb_dec_process_urb_frame_list(unsigned long data)
+{
+	struct ttusb_dec *dec = (struct ttusb_dec *)data;
+	struct list_head *item;
+	struct urb_frame *frame;
+	unsigned long flags;
+
+	while (1) {
+		spin_lock_irqsave(&dec->urb_frame_list_lock, flags);
+		if ((item = dec->urb_frame_list.next) != &dec->urb_frame_list) {
+			frame = list_entry(item, struct urb_frame,
+					   urb_frame_list);
+			list_del(&frame->urb_frame_list);
+		} else {
+			spin_unlock_irqrestore(&dec->urb_frame_list_lock,
+					       flags);
+			return;
+		}
+		spin_unlock_irqrestore(&dec->urb_frame_list_lock, flags);
+
+		ttusb_dec_process_urb_frame(dec, frame->data, frame->length);
+		kfree(frame);
+	}
+}
+
+static void ttusb_dec_process_urb(struct urb *urb)
+{
+	struct ttusb_dec *dec = urb->context;
+
+	if (!urb->status) {
+		int i;
+
+		for (i = 0; i < FRAMES_PER_ISO_BUF; i++) {
+			struct usb_iso_packet_descriptor *d;
+			u8 *b;
+			int length;
+			struct urb_frame *frame;
+
+			d = &urb->iso_frame_desc[i];
+			b = urb->transfer_buffer + d->offset;
+			length = d->actual_length;
+
+			if ((frame = kmalloc(sizeof(struct urb_frame),
+					     GFP_ATOMIC))) {
+				unsigned long flags;
+
+				memcpy(frame->data, b, length);
+				frame->length = length;
+
+				spin_lock_irqsave(&dec->urb_frame_list_lock,
+						     flags);
+				list_add_tail(&frame->urb_frame_list,
+					      &dec->urb_frame_list);
+				spin_unlock_irqrestore(&dec->urb_frame_list_lock,
+						       flags);
+
+				tasklet_schedule(&dec->urb_tasklet);
+			}
+		}
+	} else {
+		 /* -ENOENT is expected when unlinking urbs */
+		if (urb->status != -ENOENT)
+			dprintk("%s: urb error: %d\n", __func__,
+				urb->status);
+	}
+
+	if (dec->iso_stream_count)
+		usb_submit_urb(urb, GFP_ATOMIC);
+}
+
+static void ttusb_dec_setup_urbs(struct ttusb_dec *dec)
+{
+	int i, j, buffer_offset = 0;
+
+	dprintk("%s\n", __func__);
+
+	for (i = 0; i < ISO_BUF_COUNT; i++) {
+		int frame_offset = 0;
+		struct urb *urb = dec->iso_urb[i];
+
+		urb->dev = dec->udev;
+		urb->context = dec;
+		urb->complete = ttusb_dec_process_urb;
+		urb->pipe = dec->in_pipe;
+		urb->transfer_flags = URB_ISO_ASAP;
+		urb->interval = 1;
+		urb->number_of_packets = FRAMES_PER_ISO_BUF;
+		urb->transfer_buffer_length = ISO_FRAME_SIZE *
+					      FRAMES_PER_ISO_BUF;
+		urb->transfer_buffer = dec->iso_buffer + buffer_offset;
+		buffer_offset += ISO_FRAME_SIZE * FRAMES_PER_ISO_BUF;
+
+		for (j = 0; j < FRAMES_PER_ISO_BUF; j++) {
+			urb->iso_frame_desc[j].offset = frame_offset;
+			urb->iso_frame_desc[j].length = ISO_FRAME_SIZE;
+			frame_offset += ISO_FRAME_SIZE;
+		}
+	}
+}
+
+static void ttusb_dec_stop_iso_xfer(struct ttusb_dec *dec)
+{
+	int i;
+
+	dprintk("%s\n", __func__);
+
+	if (mutex_lock_interruptible(&dec->iso_mutex))
+		return;
+
+	dec->iso_stream_count--;
+
+	if (!dec->iso_stream_count) {
+		for (i = 0; i < ISO_BUF_COUNT; i++)
+			usb_kill_urb(dec->iso_urb[i]);
+	}
+
+	mutex_unlock(&dec->iso_mutex);
+}
+
+/* Setting the interface of the DEC tends to take down the USB communications
+ * for a short period, so it's important not to call this function just before
+ * trying to talk to it.
+ */
+static int ttusb_dec_set_interface(struct ttusb_dec *dec,
+				   enum ttusb_dec_interface interface)
+{
+	int result = 0;
+	u8 b[] = { 0x05 };
+
+	if (interface != dec->interface) {
+		switch (interface) {
+		case TTUSB_DEC_INTERFACE_INITIAL:
+			result = usb_set_interface(dec->udev, 0, 0);
+			break;
+		case TTUSB_DEC_INTERFACE_IN:
+			result = ttusb_dec_send_command(dec, 0x80, sizeof(b),
+							b, NULL, NULL);
+			if (result)
+				return result;
+			result = usb_set_interface(dec->udev, 0, 8);
+			break;
+		case TTUSB_DEC_INTERFACE_OUT:
+			result = usb_set_interface(dec->udev, 0, 1);
+			break;
+		}
+
+		if (result)
+			return result;
+
+		dec->interface = interface;
+	}
+
+	return 0;
+}
+
+static int ttusb_dec_start_iso_xfer(struct ttusb_dec *dec)
+{
+	int i, result;
+
+	dprintk("%s\n", __func__);
+
+	if (mutex_lock_interruptible(&dec->iso_mutex))
+		return -EAGAIN;
+
+	if (!dec->iso_stream_count) {
+		ttusb_dec_setup_urbs(dec);
+
+		dec->packet_state = 0;
+		dec->v_pes_postbytes = 0;
+		dec->next_packet_id = 0;
+
+		for (i = 0; i < ISO_BUF_COUNT; i++) {
+			if ((result = usb_submit_urb(dec->iso_urb[i],
+						     GFP_ATOMIC))) {
+				printk("%s: failed urb submission %d: error %d\n",
+				       __func__, i, result);
+
+				while (i) {
+					usb_kill_urb(dec->iso_urb[i - 1]);
+					i--;
+				}
+
+				mutex_unlock(&dec->iso_mutex);
+				return result;
+			}
+		}
+	}
+
+	dec->iso_stream_count++;
+
+	mutex_unlock(&dec->iso_mutex);
+
+	return 0;
+}
+
+static int ttusb_dec_start_ts_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+	struct dvb_demux *dvbdmx = dvbdmxfeed->demux;
+	struct ttusb_dec *dec = dvbdmx->priv;
+	u8 b0[] = { 0x05 };
+	int result = 0;
+
+	dprintk("%s\n", __func__);
+
+	dprintk("  ts_type:");
+
+	if (dvbdmxfeed->ts_type & TS_DECODER)
+		dprintk(" TS_DECODER");
+
+	if (dvbdmxfeed->ts_type & TS_PACKET)
+		dprintk(" TS_PACKET");
+
+	if (dvbdmxfeed->ts_type & TS_PAYLOAD_ONLY)
+		dprintk(" TS_PAYLOAD_ONLY");
+
+	dprintk("\n");
+
+	switch (dvbdmxfeed->pes_type) {
+
+	case DMX_PES_VIDEO:
+		dprintk("  pes_type: DMX_PES_VIDEO\n");
+		dec->pid[DMX_PES_PCR] = dvbdmxfeed->pid;
+		dec->pid[DMX_PES_VIDEO] = dvbdmxfeed->pid;
+		dec->video_filter = dvbdmxfeed->filter;
+		ttusb_dec_set_pids(dec);
+		break;
+
+	case DMX_PES_AUDIO:
+		dprintk("  pes_type: DMX_PES_AUDIO\n");
+		dec->pid[DMX_PES_AUDIO] = dvbdmxfeed->pid;
+		dec->audio_filter = dvbdmxfeed->filter;
+		ttusb_dec_set_pids(dec);
+		break;
+
+	case DMX_PES_TELETEXT:
+		dec->pid[DMX_PES_TELETEXT] = dvbdmxfeed->pid;
+		dprintk("  pes_type: DMX_PES_TELETEXT(not supported)\n");
+		return -ENOSYS;
+
+	case DMX_PES_PCR:
+		dprintk("  pes_type: DMX_PES_PCR\n");
+		dec->pid[DMX_PES_PCR] = dvbdmxfeed->pid;
+		ttusb_dec_set_pids(dec);
+		break;
+
+	case DMX_PES_OTHER:
+		dprintk("  pes_type: DMX_PES_OTHER(not supported)\n");
+		return -ENOSYS;
+
+	default:
+		dprintk("  pes_type: unknown (%d)\n", dvbdmxfeed->pes_type);
+		return -EINVAL;
+
+	}
+
+	result = ttusb_dec_send_command(dec, 0x80, sizeof(b0), b0, NULL, NULL);
+	if (result)
+		return result;
+
+	dec->pva_stream_count++;
+	return ttusb_dec_start_iso_xfer(dec);
+}
+
+static int ttusb_dec_start_sec_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+	struct ttusb_dec *dec = dvbdmxfeed->demux->priv;
+	u8 b0[] = { 0x00, 0x00, 0x00, 0x01,
+		    0x00, 0x00, 0x00, 0x00,
+		    0x00, 0x00, 0x00, 0x00,
+		    0x00, 0x00, 0x00, 0x00,
+		    0x00, 0xff, 0x00, 0x00,
+		    0x00, 0x00, 0x00, 0x00,
+		    0x00, 0x00, 0x00, 0x00,
+		    0x00 };
+	__be16 pid;
+	u8 c[COMMAND_PACKET_SIZE];
+	int c_length;
+	int result;
+	struct filter_info *finfo;
+	unsigned long flags;
+	u8 x = 1;
+
+	dprintk("%s\n", __func__);
+
+	pid = htons(dvbdmxfeed->pid);
+	memcpy(&b0[0], &pid, 2);
+	memcpy(&b0[4], &x, 1);
+	memcpy(&b0[5], &dvbdmxfeed->filter->filter.filter_value[0], 1);
+
+	result = ttusb_dec_send_command(dec, 0x60, sizeof(b0), b0,
+					&c_length, c);
+
+	if (!result) {
+		if (c_length == 2) {
+			if (!(finfo = kmalloc(sizeof(struct filter_info),
+					      GFP_ATOMIC)))
+				return -ENOMEM;
+
+			finfo->stream_id = c[1];
+			finfo->filter = dvbdmxfeed->filter;
+
+			spin_lock_irqsave(&dec->filter_info_list_lock, flags);
+			list_add_tail(&finfo->filter_info_list,
+				      &dec->filter_info_list);
+			spin_unlock_irqrestore(&dec->filter_info_list_lock,
+					       flags);
+
+			dvbdmxfeed->priv = finfo;
+
+			dec->filter_stream_count++;
+			return ttusb_dec_start_iso_xfer(dec);
+		}
+
+		return -EAGAIN;
+	} else
+		return result;
+}
+
+static int ttusb_dec_start_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+	struct dvb_demux *dvbdmx = dvbdmxfeed->demux;
+
+	dprintk("%s\n", __func__);
+
+	if (!dvbdmx->dmx.frontend)
+		return -EINVAL;
+
+	dprintk("  pid: 0x%04X\n", dvbdmxfeed->pid);
+
+	switch (dvbdmxfeed->type) {
+
+	case DMX_TYPE_TS:
+		return ttusb_dec_start_ts_feed(dvbdmxfeed);
+		break;
+
+	case DMX_TYPE_SEC:
+		return ttusb_dec_start_sec_feed(dvbdmxfeed);
+		break;
+
+	default:
+		dprintk("  type: unknown (%d)\n", dvbdmxfeed->type);
+		return -EINVAL;
+
+	}
+}
+
+static int ttusb_dec_stop_ts_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+	struct ttusb_dec *dec = dvbdmxfeed->demux->priv;
+	u8 b0[] = { 0x00 };
+
+	ttusb_dec_send_command(dec, 0x81, sizeof(b0), b0, NULL, NULL);
+
+	dec->pva_stream_count--;
+
+	ttusb_dec_stop_iso_xfer(dec);
+
+	return 0;
+}
+
+static int ttusb_dec_stop_sec_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+	struct ttusb_dec *dec = dvbdmxfeed->demux->priv;
+	u8 b0[] = { 0x00, 0x00 };
+	struct filter_info *finfo = (struct filter_info *)dvbdmxfeed->priv;
+	unsigned long flags;
+
+	b0[1] = finfo->stream_id;
+	spin_lock_irqsave(&dec->filter_info_list_lock, flags);
+	list_del(&finfo->filter_info_list);
+	spin_unlock_irqrestore(&dec->filter_info_list_lock, flags);
+	kfree(finfo);
+	ttusb_dec_send_command(dec, 0x62, sizeof(b0), b0, NULL, NULL);
+
+	dec->filter_stream_count--;
+
+	ttusb_dec_stop_iso_xfer(dec);
+
+	return 0;
+}
+
+static int ttusb_dec_stop_feed(struct dvb_demux_feed *dvbdmxfeed)
+{
+	dprintk("%s\n", __func__);
+
+	switch (dvbdmxfeed->type) {
+	case DMX_TYPE_TS:
+		return ttusb_dec_stop_ts_feed(dvbdmxfeed);
+		break;
+
+	case DMX_TYPE_SEC:
+		return ttusb_dec_stop_sec_feed(dvbdmxfeed);
+		break;
+	}
+
+	return 0;
+}
+
+static void ttusb_dec_free_iso_urbs(struct ttusb_dec *dec)
+{
+	int i;
+
+	dprintk("%s\n", __func__);
+
+	for (i = 0; i < ISO_BUF_COUNT; i++)
+		usb_free_urb(dec->iso_urb[i]);
+	kfree(dec->iso_buffer);
+}
+
+static int ttusb_dec_alloc_iso_urbs(struct ttusb_dec *dec)
+{
+	int i;
+
+	dprintk("%s\n", __func__);
+
+	dec->iso_buffer = kcalloc(FRAMES_PER_ISO_BUF * ISO_BUF_COUNT,
+			ISO_FRAME_SIZE, GFP_KERNEL);
+	if (!dec->iso_buffer)
+		return -ENOMEM;
+
+	for (i = 0; i < ISO_BUF_COUNT; i++) {
+		struct urb *urb;
+
+		if (!(urb = usb_alloc_urb(FRAMES_PER_ISO_BUF, GFP_ATOMIC))) {
+			ttusb_dec_free_iso_urbs(dec);
+			return -ENOMEM;
+		}
+
+		dec->iso_urb[i] = urb;
+	}
+
+	ttusb_dec_setup_urbs(dec);
+
+	return 0;
+}
+
+static void ttusb_dec_init_tasklet(struct ttusb_dec *dec)
+{
+	spin_lock_init(&dec->urb_frame_list_lock);
+	INIT_LIST_HEAD(&dec->urb_frame_list);
+	tasklet_init(&dec->urb_tasklet, ttusb_dec_process_urb_frame_list,
+		     (unsigned long)dec);
+}
+
+static int ttusb_init_rc( struct ttusb_dec *dec)
+{
+	struct input_dev *input_dev;
+	u8 b[] = { 0x00, 0x01 };
+	int i;
+	int err;
+
+	usb_make_path(dec->udev, dec->rc_phys, sizeof(dec->rc_phys));
+	strlcat(dec->rc_phys, "/input0", sizeof(dec->rc_phys));
+
+	input_dev = input_allocate_device();
+	if (!input_dev)
+		return -ENOMEM;
+
+	input_dev->name = "ttusb_dec remote control";
+	input_dev->phys = dec->rc_phys;
+	input_dev->evbit[0] = BIT_MASK(EV_KEY);
+	input_dev->keycodesize = sizeof(u16);
+	input_dev->keycodemax = 0x1a;
+	input_dev->keycode = rc_keys;
+
+	for (i = 0; i < ARRAY_SIZE(rc_keys); i++)
+		  set_bit(rc_keys[i], input_dev->keybit);
+
+	err = input_register_device(input_dev);
+	if (err) {
+		input_free_device(input_dev);
+		return err;
+	}
+
+	dec->rc_input_dev = input_dev;
+	if (usb_submit_urb(dec->irq_urb, GFP_KERNEL))
+		printk("%s: usb_submit_urb failed\n",__func__);
+	/* enable irq pipe */
+	ttusb_dec_send_command(dec,0xb0,sizeof(b),b,NULL,NULL);
+
+	return 0;
+}
+
+static void ttusb_dec_init_v_pes(struct ttusb_dec *dec)
+{
+	dprintk("%s\n", __func__);
+
+	dec->v_pes[0] = 0x00;
+	dec->v_pes[1] = 0x00;
+	dec->v_pes[2] = 0x01;
+	dec->v_pes[3] = 0xe0;
+}
+
+static int ttusb_dec_init_usb(struct ttusb_dec *dec)
+{
+	int result;
+
+	dprintk("%s\n", __func__);
+
+	mutex_init(&dec->usb_mutex);
+	mutex_init(&dec->iso_mutex);
+
+	dec->command_pipe = usb_sndbulkpipe(dec->udev, COMMAND_PIPE);
+	dec->result_pipe = usb_rcvbulkpipe(dec->udev, RESULT_PIPE);
+	dec->in_pipe = usb_rcvisocpipe(dec->udev, IN_PIPE);
+	dec->out_pipe = usb_sndisocpipe(dec->udev, OUT_PIPE);
+	dec->irq_pipe = usb_rcvintpipe(dec->udev, IRQ_PIPE);
+
+	if(enable_rc) {
+		dec->irq_urb = usb_alloc_urb(0, GFP_KERNEL);
+		if(!dec->irq_urb) {
+			return -ENOMEM;
+		}
+		dec->irq_buffer = usb_alloc_coherent(dec->udev,IRQ_PACKET_SIZE,
+					GFP_KERNEL, &dec->irq_dma_handle);
+		if(!dec->irq_buffer) {
+			usb_free_urb(dec->irq_urb);
+			return -ENOMEM;
+		}
+		usb_fill_int_urb(dec->irq_urb, dec->udev,dec->irq_pipe,
+				 dec->irq_buffer, IRQ_PACKET_SIZE,
+				 ttusb_dec_handle_irq, dec, 1);
+		dec->irq_urb->transfer_dma = dec->irq_dma_handle;
+		dec->irq_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+	}
+
+	result = ttusb_dec_alloc_iso_urbs(dec);
+	if (result) {
+		usb_free_urb(dec->irq_urb);
+		usb_free_coherent(dec->udev, IRQ_PACKET_SIZE,
+				  dec->irq_buffer, dec->irq_dma_handle);
+	}
+	return result;
+}
+
+static int ttusb_dec_boot_dsp(struct ttusb_dec *dec)
+{
+	int i, j, actual_len, result, size, trans_count;
+	u8 b0[] = { 0x00, 0x00, 0x00, 0x00,
+		    0x00, 0x00, 0x00, 0x00,
+		    0x61, 0x00 };
+	u8 b1[] = { 0x61 };
+	u8 *b;
+	char idstring[21];
+	const u8 *firmware = NULL;
+	size_t firmware_size = 0;
+	u16 firmware_csum = 0;
+	__be16 firmware_csum_ns;
+	__be32 firmware_size_nl;
+	u32 crc32_csum, crc32_check;
+	__be32 tmp;
+	const struct firmware *fw_entry = NULL;
+
+	dprintk("%s\n", __func__);
+
+	result = request_firmware(&fw_entry, dec->firmware_name, &dec->udev->dev);
+	if (result) {
+		printk(KERN_ERR "%s: Firmware (%s) unavailable.\n",
+		       __func__, dec->firmware_name);
+		return result;
+	}
+
+	firmware = fw_entry->data;
+	firmware_size = fw_entry->size;
+
+	if (firmware_size < 60) {
+		printk("%s: firmware size too small for DSP code (%zu < 60).\n",
+			__func__, firmware_size);
+		release_firmware(fw_entry);
+		return -ENOENT;
+	}
+
+	/* a 32 bit checksum over the first 56 bytes of the DSP Code is stored
+	   at offset 56 of file, so use it to check if the firmware file is
+	   valid. */
+	crc32_csum = crc32(~0L, firmware, 56) ^ ~0L;
+	memcpy(&tmp, &firmware[56], 4);
+	crc32_check = ntohl(tmp);
+	if (crc32_csum != crc32_check) {
+		printk("%s: crc32 check of DSP code failed (calculated 0x%08x != 0x%08x in file), file invalid.\n",
+			__func__, crc32_csum, crc32_check);
+		release_firmware(fw_entry);
+		return -ENOENT;
+	}
+	memcpy(idstring, &firmware[36], 20);
+	idstring[20] = '\0';
+	printk(KERN_INFO "ttusb_dec: found DSP code \"%s\".\n", idstring);
+
+	firmware_size_nl = htonl(firmware_size);
+	memcpy(b0, &firmware_size_nl, 4);
+	firmware_csum = crc16(~0, firmware, firmware_size) ^ ~0;
+	firmware_csum_ns = htons(firmware_csum);
+	memcpy(&b0[6], &firmware_csum_ns, 2);
+
+	result = ttusb_dec_send_command(dec, 0x41, sizeof(b0), b0, NULL, NULL);
+
+	if (result) {
+		release_firmware(fw_entry);
+		return result;
+	}
+
+	trans_count = 0;
+	j = 0;
+
+	b = kmalloc(ARM_PACKET_SIZE, GFP_KERNEL);
+	if (b == NULL) {
+		release_firmware(fw_entry);
+		return -ENOMEM;
+	}
+
+	for (i = 0; i < firmware_size; i += COMMAND_PACKET_SIZE) {
+		size = firmware_size - i;
+		if (size > COMMAND_PACKET_SIZE)
+			size = COMMAND_PACKET_SIZE;
+
+		b[j + 0] = 0xaa;
+		b[j + 1] = trans_count++;
+		b[j + 2] = 0xf0;
+		b[j + 3] = size;
+		memcpy(&b[j + 4], &firmware[i], size);
+
+		j += COMMAND_PACKET_SIZE + 4;
+
+		if (j >= ARM_PACKET_SIZE) {
+			result = usb_bulk_msg(dec->udev, dec->command_pipe, b,
+					      ARM_PACKET_SIZE, &actual_len,
+					      100);
+			j = 0;
+		} else if (size < COMMAND_PACKET_SIZE) {
+			result = usb_bulk_msg(dec->udev, dec->command_pipe, b,
+					      j - COMMAND_PACKET_SIZE + size,
+					      &actual_len, 100);
+		}
+	}
+
+	result = ttusb_dec_send_command(dec, 0x43, sizeof(b1), b1, NULL, NULL);
+
+	release_firmware(fw_entry);
+	kfree(b);
+
+	return result;
+}
+
+static int ttusb_dec_init_stb(struct ttusb_dec *dec)
+{
+	int result;
+	unsigned int mode = 0, model = 0, version = 0;
+
+	dprintk("%s\n", __func__);
+
+	result = ttusb_dec_get_stb_state(dec, &mode, &model, &version);
+	if (result)
+		return result;
+
+	if (!mode) {
+		if (version == 0xABCDEFAB)
+			printk(KERN_INFO "ttusb_dec: no version info in Firmware\n");
+		else
+			printk(KERN_INFO "ttusb_dec: Firmware %x.%02x%c%c\n",
+			       version >> 24, (version >> 16) & 0xff,
+			       (version >> 8) & 0xff, version & 0xff);
+
+		result = ttusb_dec_boot_dsp(dec);
+		if (result)
+			return result;
+	} else {
+		/* We can't trust the USB IDs that some firmwares
+		   give the box */
+		switch (model) {
+		case 0x00070001:
+		case 0x00070008:
+		case 0x0007000c:
+			ttusb_dec_set_model(dec, TTUSB_DEC3000S);
+			break;
+		case 0x00070009:
+		case 0x00070013:
+			ttusb_dec_set_model(dec, TTUSB_DEC2000T);
+			break;
+		case 0x00070011:
+			ttusb_dec_set_model(dec, TTUSB_DEC2540T);
+			break;
+		default:
+			printk(KERN_ERR "%s: unknown model returned by firmware (%08x) - please report\n",
+			       __func__, model);
+			return -ENOENT;
+		}
+		if (version >= 0x01770000)
+			dec->can_playback = 1;
+	}
+	return 0;
+}
+
+static int ttusb_dec_init_dvb(struct ttusb_dec *dec)
+{
+	int result;
+
+	dprintk("%s\n", __func__);
+
+	if ((result = dvb_register_adapter(&dec->adapter,
+					   dec->model_name, THIS_MODULE,
+					   &dec->udev->dev,
+					   adapter_nr)) < 0) {
+		printk("%s: dvb_register_adapter failed: error %d\n",
+		       __func__, result);
+
+		return result;
+	}
+
+	dec->demux.dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING;
+
+	dec->demux.priv = (void *)dec;
+	dec->demux.filternum = 31;
+	dec->demux.feednum = 31;
+	dec->demux.start_feed = ttusb_dec_start_feed;
+	dec->demux.stop_feed = ttusb_dec_stop_feed;
+	dec->demux.write_to_decoder = NULL;
+
+	if ((result = dvb_dmx_init(&dec->demux)) < 0) {
+		printk("%s: dvb_dmx_init failed: error %d\n", __func__,
+		       result);
+
+		dvb_unregister_adapter(&dec->adapter);
+
+		return result;
+	}
+
+	dec->dmxdev.filternum = 32;
+	dec->dmxdev.demux = &dec->demux.dmx;
+	dec->dmxdev.capabilities = 0;
+
+	if ((result = dvb_dmxdev_init(&dec->dmxdev, &dec->adapter)) < 0) {
+		printk("%s: dvb_dmxdev_init failed: error %d\n",
+		       __func__, result);
+
+		dvb_dmx_release(&dec->demux);
+		dvb_unregister_adapter(&dec->adapter);
+
+		return result;
+	}
+
+	dec->frontend.source = DMX_FRONTEND_0;
+
+	if ((result = dec->demux.dmx.add_frontend(&dec->demux.dmx,
+						  &dec->frontend)) < 0) {
+		printk("%s: dvb_dmx_init failed: error %d\n", __func__,
+		       result);
+
+		dvb_dmxdev_release(&dec->dmxdev);
+		dvb_dmx_release(&dec->demux);
+		dvb_unregister_adapter(&dec->adapter);
+
+		return result;
+	}
+
+	if ((result = dec->demux.dmx.connect_frontend(&dec->demux.dmx,
+						      &dec->frontend)) < 0) {
+		printk("%s: dvb_dmx_init failed: error %d\n", __func__,
+		       result);
+
+		dec->demux.dmx.remove_frontend(&dec->demux.dmx, &dec->frontend);
+		dvb_dmxdev_release(&dec->dmxdev);
+		dvb_dmx_release(&dec->demux);
+		dvb_unregister_adapter(&dec->adapter);
+
+		return result;
+	}
+
+	dvb_net_init(&dec->adapter, &dec->dvb_net, &dec->demux.dmx);
+
+	return 0;
+}
+
+static void ttusb_dec_exit_dvb(struct ttusb_dec *dec)
+{
+	dprintk("%s\n", __func__);
+
+	dvb_net_release(&dec->dvb_net);
+	dec->demux.dmx.close(&dec->demux.dmx);
+	dec->demux.dmx.remove_frontend(&dec->demux.dmx, &dec->frontend);
+	dvb_dmxdev_release(&dec->dmxdev);
+	dvb_dmx_release(&dec->demux);
+	if (dec->fe) {
+		dvb_unregister_frontend(dec->fe);
+		if (dec->fe->ops.release)
+			dec->fe->ops.release(dec->fe);
+	}
+	dvb_unregister_adapter(&dec->adapter);
+}
+
+static void ttusb_dec_exit_rc(struct ttusb_dec *dec)
+{
+	dprintk("%s\n", __func__);
+
+	if (dec->rc_input_dev) {
+		input_unregister_device(dec->rc_input_dev);
+		dec->rc_input_dev = NULL;
+	}
+}
+
+
+static void ttusb_dec_exit_usb(struct ttusb_dec *dec)
+{
+	int i;
+
+	dprintk("%s\n", __func__);
+
+	if (enable_rc) {
+		/* we have to check whether the irq URB is already submitted.
+		 * As the irq is submitted after the interface is changed,
+		 * this is the best method i figured out.
+		 * Any others?*/
+		if (dec->interface == TTUSB_DEC_INTERFACE_IN)
+			usb_kill_urb(dec->irq_urb);
+
+		usb_free_urb(dec->irq_urb);
+
+		usb_free_coherent(dec->udev, IRQ_PACKET_SIZE,
+				  dec->irq_buffer, dec->irq_dma_handle);
+	}
+
+	dec->iso_stream_count = 0;
+
+	for (i = 0; i < ISO_BUF_COUNT; i++)
+		usb_kill_urb(dec->iso_urb[i]);
+
+	ttusb_dec_free_iso_urbs(dec);
+}
+
+static void ttusb_dec_exit_tasklet(struct ttusb_dec *dec)
+{
+	struct list_head *item;
+	struct urb_frame *frame;
+
+	tasklet_kill(&dec->urb_tasklet);
+
+	while ((item = dec->urb_frame_list.next) != &dec->urb_frame_list) {
+		frame = list_entry(item, struct urb_frame, urb_frame_list);
+		list_del(&frame->urb_frame_list);
+		kfree(frame);
+	}
+}
+
+static void ttusb_dec_init_filters(struct ttusb_dec *dec)
+{
+	INIT_LIST_HEAD(&dec->filter_info_list);
+	spin_lock_init(&dec->filter_info_list_lock);
+}
+
+static void ttusb_dec_exit_filters(struct ttusb_dec *dec)
+{
+	struct list_head *item;
+	struct filter_info *finfo;
+
+	while ((item = dec->filter_info_list.next) != &dec->filter_info_list) {
+		finfo = list_entry(item, struct filter_info, filter_info_list);
+		list_del(&finfo->filter_info_list);
+		kfree(finfo);
+	}
+}
+
+static int fe_send_command(struct dvb_frontend* fe, const u8 command,
+			   int param_length, const u8 params[],
+			   int *result_length, u8 cmd_result[])
+{
+	struct ttusb_dec* dec = fe->dvb->priv;
+	return ttusb_dec_send_command(dec, command, param_length, params, result_length, cmd_result);
+}
+
+static const struct ttusbdecfe_config fe_config = {
+	.send_command = fe_send_command
+};
+
+static int ttusb_dec_probe(struct usb_interface *intf,
+			   const struct usb_device_id *id)
+{
+	struct usb_device *udev;
+	struct ttusb_dec *dec;
+	int result;
+
+	dprintk("%s\n", __func__);
+
+	udev = interface_to_usbdev(intf);
+
+	if (!(dec = kzalloc(sizeof(struct ttusb_dec), GFP_KERNEL))) {
+		printk("%s: couldn't allocate memory.\n", __func__);
+		return -ENOMEM;
+	}
+
+	usb_set_intfdata(intf, (void *)dec);
+
+	switch (id->idProduct) {
+	case 0x1006:
+		ttusb_dec_set_model(dec, TTUSB_DEC3000S);
+		break;
+
+	case 0x1008:
+		ttusb_dec_set_model(dec, TTUSB_DEC2000T);
+		break;
+
+	case 0x1009:
+		ttusb_dec_set_model(dec, TTUSB_DEC2540T);
+		break;
+	}
+
+	dec->udev = udev;
+
+	result = ttusb_dec_init_usb(dec);
+	if (result)
+		goto err_usb;
+	result = ttusb_dec_init_stb(dec);
+	if (result)
+		goto err_stb;
+	result = ttusb_dec_init_dvb(dec);
+	if (result)
+		goto err_stb;
+
+	dec->adapter.priv = dec;
+	switch (id->idProduct) {
+	case 0x1006:
+		dec->fe = ttusbdecfe_dvbs_attach(&fe_config);
+		break;
+
+	case 0x1008:
+	case 0x1009:
+		dec->fe = ttusbdecfe_dvbt_attach(&fe_config);
+		break;
+	}
+
+	if (dec->fe == NULL) {
+		printk("dvb-ttusb-dec: A frontend driver was not found for device [%04x:%04x]\n",
+		       le16_to_cpu(dec->udev->descriptor.idVendor),
+		       le16_to_cpu(dec->udev->descriptor.idProduct));
+	} else {
+		if (dvb_register_frontend(&dec->adapter, dec->fe)) {
+			printk("budget-ci: Frontend registration failed!\n");
+			if (dec->fe->ops.release)
+				dec->fe->ops.release(dec->fe);
+			dec->fe = NULL;
+		}
+	}
+
+	ttusb_dec_init_v_pes(dec);
+	ttusb_dec_init_filters(dec);
+	ttusb_dec_init_tasklet(dec);
+
+	dec->active = 1;
+
+	ttusb_dec_set_interface(dec, TTUSB_DEC_INTERFACE_IN);
+
+	if (enable_rc)
+		ttusb_init_rc(dec);
+
+	return 0;
+err_stb:
+	ttusb_dec_exit_usb(dec);
+err_usb:
+	kfree(dec);
+	return result;
+}
+
+static void ttusb_dec_disconnect(struct usb_interface *intf)
+{
+	struct ttusb_dec *dec = usb_get_intfdata(intf);
+
+	usb_set_intfdata(intf, NULL);
+
+	dprintk("%s\n", __func__);
+
+	if (dec->active) {
+		ttusb_dec_exit_tasklet(dec);
+		ttusb_dec_exit_filters(dec);
+		if(enable_rc)
+			ttusb_dec_exit_rc(dec);
+		ttusb_dec_exit_usb(dec);
+		ttusb_dec_exit_dvb(dec);
+	}
+
+	kfree(dec);
+}
+
+static void ttusb_dec_set_model(struct ttusb_dec *dec,
+				enum ttusb_dec_model model)
+{
+	dec->model = model;
+
+	switch (model) {
+	case TTUSB_DEC2000T:
+		dec->model_name = "DEC2000-t";
+		dec->firmware_name = "dvb-ttusb-dec-2000t.fw";
+		break;
+
+	case TTUSB_DEC2540T:
+		dec->model_name = "DEC2540-t";
+		dec->firmware_name = "dvb-ttusb-dec-2540t.fw";
+		break;
+
+	case TTUSB_DEC3000S:
+		dec->model_name = "DEC3000-s";
+		dec->firmware_name = "dvb-ttusb-dec-3000s.fw";
+		break;
+	}
+}
+
+static const struct usb_device_id ttusb_dec_table[] = {
+	{USB_DEVICE(0x0b48, 0x1006)},	/* DEC3000-s */
+	/*{USB_DEVICE(0x0b48, 0x1007)},	   Unconfirmed */
+	{USB_DEVICE(0x0b48, 0x1008)},	/* DEC2000-t */
+	{USB_DEVICE(0x0b48, 0x1009)},	/* DEC2540-t */
+	{}
+};
+
+static struct usb_driver ttusb_dec_driver = {
+	.name		= "ttusb-dec",
+	.probe		= ttusb_dec_probe,
+	.disconnect	= ttusb_dec_disconnect,
+	.id_table	= ttusb_dec_table,
+};
+
+module_usb_driver(ttusb_dec_driver);
+
+MODULE_AUTHOR("Alex Woods <linux-dvb@giblets.org>");
+MODULE_DESCRIPTION(DRIVER_NAME);
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(usb, ttusb_dec_table);
diff --git a/drivers/media/usb/ttusb-dec/ttusbdecfe.c b/drivers/media/usb/ttusb-dec/ttusbdecfe.c
new file mode 100644
index 0000000..278bf6c
--- /dev/null
+++ b/drivers/media/usb/ttusb-dec/ttusbdecfe.c
@@ -0,0 +1,299 @@
+/*
+ * TTUSB DEC Frontend Driver
+ *
+ * Copyright (C) 2003-2004 Alex Woods <linux-dvb@giblets.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <media/dvb_frontend.h>
+#include "ttusbdecfe.h"
+
+
+#define LOF_HI			10600000
+#define LOF_LO			9750000
+
+struct ttusbdecfe_state {
+
+	/* configuration settings */
+	const struct ttusbdecfe_config* config;
+
+	struct dvb_frontend frontend;
+
+	u8 hi_band;
+	u8 voltage;
+};
+
+
+static int ttusbdecfe_dvbs_read_status(struct dvb_frontend *fe,
+				       enum fe_status *status)
+{
+	*status = FE_HAS_SIGNAL | FE_HAS_VITERBI |
+		FE_HAS_SYNC | FE_HAS_CARRIER | FE_HAS_LOCK;
+	return 0;
+}
+
+
+static int ttusbdecfe_dvbt_read_status(struct dvb_frontend *fe,
+				       enum fe_status *status)
+{
+	struct ttusbdecfe_state* state = fe->demodulator_priv;
+	u8 b[] = { 0x00, 0x00, 0x00, 0x00,
+		   0x00, 0x00, 0x00, 0x00 };
+	u8 result[4];
+	int len, ret;
+
+	*status=0;
+
+	ret=state->config->send_command(fe, 0x73, sizeof(b), b, &len, result);
+	if(ret)
+		return ret;
+
+	if(len != 4) {
+		printk(KERN_ERR "%s: unexpected reply\n", __func__);
+		return -EIO;
+	}
+
+	switch(result[3]) {
+		case 1:  /* not tuned yet */
+		case 2:  /* no signal/no lock*/
+			break;
+		case 3:	 /* signal found and locked*/
+			*status = FE_HAS_SIGNAL | FE_HAS_VITERBI |
+			FE_HAS_SYNC | FE_HAS_CARRIER | FE_HAS_LOCK;
+			break;
+		case 4:
+			*status = FE_TIMEDOUT;
+			break;
+		default:
+			pr_info("%s: returned unknown value: %d\n",
+				__func__, result[3]);
+			return -EIO;
+	}
+
+	return 0;
+}
+
+static int ttusbdecfe_dvbt_set_frontend(struct dvb_frontend *fe)
+{
+	struct dtv_frontend_properties *p = &fe->dtv_property_cache;
+	struct ttusbdecfe_state* state = (struct ttusbdecfe_state*) fe->demodulator_priv;
+	u8 b[] = { 0x00, 0x00, 0x00, 0x03,
+		   0x00, 0x00, 0x00, 0x00,
+		   0x00, 0x00, 0x00, 0x01,
+		   0x00, 0x00, 0x00, 0xff,
+		   0x00, 0x00, 0x00, 0xff };
+
+	__be32 freq = htonl(p->frequency / 1000);
+	memcpy(&b[4], &freq, sizeof (u32));
+	state->config->send_command(fe, 0x71, sizeof(b), b, NULL, NULL);
+
+	return 0;
+}
+
+static int ttusbdecfe_dvbt_get_tune_settings(struct dvb_frontend* fe,
+					struct dvb_frontend_tune_settings* fesettings)
+{
+		fesettings->min_delay_ms = 1500;
+		/* Drift compensation makes no sense for DVB-T */
+		fesettings->step_size = 0;
+		fesettings->max_drift = 0;
+		return 0;
+}
+
+static int ttusbdecfe_dvbs_set_frontend(struct dvb_frontend *fe)
+{
+	struct dtv_frontend_properties *p = &fe->dtv_property_cache;
+	struct ttusbdecfe_state* state = (struct ttusbdecfe_state*) fe->demodulator_priv;
+
+	u8 b[] = { 0x00, 0x00, 0x00, 0x01,
+		   0x00, 0x00, 0x00, 0x00,
+		   0x00, 0x00, 0x00, 0x01,
+		   0x00, 0x00, 0x00, 0x00,
+		   0x00, 0x00, 0x00, 0x00,
+		   0x00, 0x00, 0x00, 0x00,
+		   0x00, 0x00, 0x00, 0x00,
+		   0x00, 0x00, 0x00, 0x00,
+		   0x00, 0x00, 0x00, 0x00,
+		   0x00, 0x00, 0x00, 0x00 };
+	__be32 freq;
+	__be32 sym_rate;
+	__be32 band;
+	__be32 lnb_voltage;
+
+	freq = htonl(p->frequency +
+	       (state->hi_band ? LOF_HI : LOF_LO));
+	memcpy(&b[4], &freq, sizeof(u32));
+	sym_rate = htonl(p->symbol_rate);
+	memcpy(&b[12], &sym_rate, sizeof(u32));
+	band = htonl(state->hi_band ? LOF_HI : LOF_LO);
+	memcpy(&b[24], &band, sizeof(u32));
+	lnb_voltage = htonl(state->voltage);
+	memcpy(&b[28], &lnb_voltage, sizeof(u32));
+
+	state->config->send_command(fe, 0x71, sizeof(b), b, NULL, NULL);
+
+	return 0;
+}
+
+static int ttusbdecfe_dvbs_diseqc_send_master_cmd(struct dvb_frontend* fe, struct dvb_diseqc_master_cmd *cmd)
+{
+	struct ttusbdecfe_state* state = (struct ttusbdecfe_state*) fe->demodulator_priv;
+	u8 b[] = { 0x00, 0xff, 0x00, 0x00,
+		   0x00, 0x00, 0x00, 0x00,
+		   0x00, 0x00 };
+
+	if (cmd->msg_len > sizeof(b) - 4)
+		return -EINVAL;
+
+	memcpy(&b[4], cmd->msg, cmd->msg_len);
+
+	state->config->send_command(fe, 0x72,
+				    sizeof(b) - (6 - cmd->msg_len), b,
+				    NULL, NULL);
+
+	return 0;
+}
+
+
+static int ttusbdecfe_dvbs_set_tone(struct dvb_frontend *fe,
+				    enum fe_sec_tone_mode tone)
+{
+	struct ttusbdecfe_state* state = (struct ttusbdecfe_state*) fe->demodulator_priv;
+
+	state->hi_band = (SEC_TONE_ON == tone);
+
+	return 0;
+}
+
+
+static int ttusbdecfe_dvbs_set_voltage(struct dvb_frontend *fe,
+				       enum fe_sec_voltage voltage)
+{
+	struct ttusbdecfe_state* state = (struct ttusbdecfe_state*) fe->demodulator_priv;
+
+	switch (voltage) {
+	case SEC_VOLTAGE_13:
+		state->voltage = 13;
+		break;
+	case SEC_VOLTAGE_18:
+		state->voltage = 18;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void ttusbdecfe_release(struct dvb_frontend* fe)
+{
+	struct ttusbdecfe_state* state = (struct ttusbdecfe_state*) fe->demodulator_priv;
+	kfree(state);
+}
+
+static const struct dvb_frontend_ops ttusbdecfe_dvbt_ops;
+
+struct dvb_frontend* ttusbdecfe_dvbt_attach(const struct ttusbdecfe_config* config)
+{
+	struct ttusbdecfe_state* state = NULL;
+
+	/* allocate memory for the internal state */
+	state = kmalloc(sizeof(struct ttusbdecfe_state), GFP_KERNEL);
+	if (state == NULL)
+		return NULL;
+
+	/* setup the state */
+	state->config = config;
+
+	/* create dvb_frontend */
+	memcpy(&state->frontend.ops, &ttusbdecfe_dvbt_ops, sizeof(struct dvb_frontend_ops));
+	state->frontend.demodulator_priv = state;
+	return &state->frontend;
+}
+
+static const struct dvb_frontend_ops ttusbdecfe_dvbs_ops;
+
+struct dvb_frontend* ttusbdecfe_dvbs_attach(const struct ttusbdecfe_config* config)
+{
+	struct ttusbdecfe_state* state = NULL;
+
+	/* allocate memory for the internal state */
+	state = kmalloc(sizeof(struct ttusbdecfe_state), GFP_KERNEL);
+	if (state == NULL)
+		return NULL;
+
+	/* setup the state */
+	state->config = config;
+	state->voltage = 0;
+	state->hi_band = 0;
+
+	/* create dvb_frontend */
+	memcpy(&state->frontend.ops, &ttusbdecfe_dvbs_ops, sizeof(struct dvb_frontend_ops));
+	state->frontend.demodulator_priv = state;
+	return &state->frontend;
+}
+
+static const struct dvb_frontend_ops ttusbdecfe_dvbt_ops = {
+	.delsys = { SYS_DVBT },
+	.info = {
+		.name			= "TechnoTrend/Hauppauge DEC2000-t Frontend",
+		.frequency_min_hz	=  51 * MHz,
+		.frequency_max_hz	= 858 * MHz,
+		.frequency_stepsize_hz	= 62500,
+		.caps =	FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 |
+			FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO |
+			FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO |
+			FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO |
+			FE_CAN_HIERARCHY_AUTO,
+	},
+
+	.release = ttusbdecfe_release,
+
+	.set_frontend = ttusbdecfe_dvbt_set_frontend,
+
+	.get_tune_settings = ttusbdecfe_dvbt_get_tune_settings,
+
+	.read_status = ttusbdecfe_dvbt_read_status,
+};
+
+static const struct dvb_frontend_ops ttusbdecfe_dvbs_ops = {
+	.delsys = { SYS_DVBS },
+	.info = {
+		.name			= "TechnoTrend/Hauppauge DEC3000-s Frontend",
+		.frequency_min_hz	=  950 * MHz,
+		.frequency_max_hz	= 2150 * MHz,
+		.frequency_stepsize_hz	=  125 * kHz,
+		.symbol_rate_min        = 1000000,  /* guessed */
+		.symbol_rate_max        = 45000000, /* guessed */
+		.caps =	FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 |
+			FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO |
+			FE_CAN_QPSK
+	},
+
+	.release = ttusbdecfe_release,
+
+	.set_frontend = ttusbdecfe_dvbs_set_frontend,
+
+	.read_status = ttusbdecfe_dvbs_read_status,
+
+	.diseqc_send_master_cmd = ttusbdecfe_dvbs_diseqc_send_master_cmd,
+	.set_voltage = ttusbdecfe_dvbs_set_voltage,
+	.set_tone = ttusbdecfe_dvbs_set_tone,
+};
+
+MODULE_DESCRIPTION("TTUSB DEC DVB-T/S Demodulator driver");
+MODULE_AUTHOR("Alex Woods/Andrew de Quincey");
+MODULE_LICENSE("GPL");
+
+EXPORT_SYMBOL(ttusbdecfe_dvbt_attach);
+EXPORT_SYMBOL(ttusbdecfe_dvbs_attach);
diff --git a/drivers/media/usb/ttusb-dec/ttusbdecfe.h b/drivers/media/usb/ttusb-dec/ttusbdecfe.h
new file mode 100644
index 0000000..5aff58c
--- /dev/null
+++ b/drivers/media/usb/ttusb-dec/ttusbdecfe.h
@@ -0,0 +1,34 @@
+/*
+ * TTUSB DEC Driver
+ *
+ * Copyright (C) 2003-2004 Alex Woods <linux-dvb@giblets.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef TTUSBDECFE_H
+#define TTUSBDECFE_H
+
+#include <linux/dvb/frontend.h>
+
+struct ttusbdecfe_config
+{
+	int (*send_command)(struct dvb_frontend* fe, const u8 command,
+			    int param_length, const u8 params[],
+			    int *result_length, u8 cmd_result[]);
+};
+
+extern struct dvb_frontend* ttusbdecfe_dvbs_attach(const struct ttusbdecfe_config* config);
+
+extern struct dvb_frontend* ttusbdecfe_dvbt_attach(const struct ttusbdecfe_config* config);
+
+#endif // TTUSBDECFE_H
diff --git a/drivers/media/usb/usbtv/Kconfig b/drivers/media/usb/usbtv/Kconfig
new file mode 100644
index 0000000..14a0941
--- /dev/null
+++ b/drivers/media/usb/usbtv/Kconfig
@@ -0,0 +1,11 @@
+config VIDEO_USBTV
+	tristate "USBTV007 video capture support"
+	depends on VIDEO_V4L2 && SND
+	select SND_PCM
+	select VIDEOBUF2_VMALLOC
+
+	---help---
+	  This is a video4linux2 driver for USBTV007 based video capture devices.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called usbtv
diff --git a/drivers/media/usb/usbtv/Makefile b/drivers/media/usb/usbtv/Makefile
new file mode 100644
index 0000000..f555cf8
--- /dev/null
+++ b/drivers/media/usb/usbtv/Makefile
@@ -0,0 +1,5 @@
+usbtv-y := usbtv-core.o \
+	usbtv-video.o \
+	usbtv-audio.o
+
+obj-$(CONFIG_VIDEO_USBTV) += usbtv.o
diff --git a/drivers/media/usb/usbtv/usbtv-audio.c b/drivers/media/usb/usbtv/usbtv-audio.c
new file mode 100644
index 0000000..4ce3824
--- /dev/null
+++ b/drivers/media/usb/usbtv/usbtv-audio.c
@@ -0,0 +1,405 @@
+/*
+ * Copyright (c) 2013 Federico Simoncelli
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL").
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * Fushicai USBTV007 Audio-Video Grabber Driver
+ *
+ * Product web site:
+ * http://www.fushicai.com/products_detail/&productId=d05449ee-b690-42f9-a661-aa7353894bed.html
+ *
+ * No physical hardware was harmed running Windows during the
+ * reverse-engineering activity
+ */
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/ac97_codec.h>
+#include <sound/pcm_params.h>
+
+#include "usbtv.h"
+
+static const struct snd_pcm_hardware snd_usbtv_digital_hw = {
+	.info = SNDRV_PCM_INFO_BATCH |
+		SNDRV_PCM_INFO_MMAP |
+		SNDRV_PCM_INFO_INTERLEAVED |
+		SNDRV_PCM_INFO_BLOCK_TRANSFER |
+		SNDRV_PCM_INFO_MMAP_VALID,
+	.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	.rates = SNDRV_PCM_RATE_48000,
+	.rate_min = 48000,
+	.rate_max = 48000,
+	.channels_min = 2,
+	.channels_max = 2,
+	.period_bytes_min = 11059,
+	.period_bytes_max = 13516,
+	.periods_min = 2,
+	.periods_max = 98,
+	.buffer_bytes_max = 62720 * 8, /* value in usbaudio.c */
+};
+
+static int snd_usbtv_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct usbtv *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+
+	chip->snd_substream = substream;
+	runtime->hw = snd_usbtv_digital_hw;
+
+	return 0;
+}
+
+static int snd_usbtv_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct usbtv *chip = snd_pcm_substream_chip(substream);
+
+	if (atomic_read(&chip->snd_stream)) {
+		atomic_set(&chip->snd_stream, 0);
+		schedule_work(&chip->snd_trigger);
+	}
+
+	return 0;
+}
+
+static int snd_usbtv_hw_params(struct snd_pcm_substream *substream,
+		struct snd_pcm_hw_params *hw_params)
+{
+	int rv;
+	struct usbtv *chip = snd_pcm_substream_chip(substream);
+
+	rv = snd_pcm_lib_malloc_pages(substream,
+		params_buffer_bytes(hw_params));
+
+	if (rv < 0) {
+		dev_warn(chip->dev, "pcm audio buffer allocation failure %i\n",
+			rv);
+		return rv;
+	}
+
+	return 0;
+}
+
+static int snd_usbtv_hw_free(struct snd_pcm_substream *substream)
+{
+	snd_pcm_lib_free_pages(substream);
+	return 0;
+}
+
+static int snd_usbtv_prepare(struct snd_pcm_substream *substream)
+{
+	struct usbtv *chip = snd_pcm_substream_chip(substream);
+
+	chip->snd_buffer_pos = 0;
+	chip->snd_period_pos = 0;
+
+	return 0;
+}
+
+static void usbtv_audio_urb_received(struct urb *urb)
+{
+	struct usbtv *chip = urb->context;
+	struct snd_pcm_substream *substream = chip->snd_substream;
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	size_t i, frame_bytes, chunk_length, buffer_pos, period_pos;
+	int period_elapsed;
+	unsigned long flags;
+	void *urb_current;
+
+	switch (urb->status) {
+	case 0:
+	case -ETIMEDOUT:
+		break;
+	case -ENOENT:
+	case -EPROTO:
+	case -ECONNRESET:
+	case -ESHUTDOWN:
+		return;
+	default:
+		dev_warn(chip->dev, "unknown audio urb status %i\n",
+			urb->status);
+	}
+
+	if (!atomic_read(&chip->snd_stream))
+		return;
+
+	frame_bytes = runtime->frame_bits >> 3;
+	chunk_length = USBTV_CHUNK / frame_bytes;
+
+	buffer_pos = chip->snd_buffer_pos;
+	period_pos = chip->snd_period_pos;
+	period_elapsed = 0;
+
+	for (i = 0; i < urb->actual_length; i += USBTV_CHUNK_SIZE) {
+		urb_current = urb->transfer_buffer + i + USBTV_AUDIO_HDRSIZE;
+
+		if (buffer_pos + chunk_length >= runtime->buffer_size) {
+			size_t cnt = (runtime->buffer_size - buffer_pos) *
+				frame_bytes;
+			memcpy(runtime->dma_area + buffer_pos * frame_bytes,
+				urb_current, cnt);
+			memcpy(runtime->dma_area, urb_current + cnt,
+				chunk_length * frame_bytes - cnt);
+		} else {
+			memcpy(runtime->dma_area + buffer_pos * frame_bytes,
+				urb_current, chunk_length * frame_bytes);
+		}
+
+		buffer_pos += chunk_length;
+		period_pos += chunk_length;
+
+		if (buffer_pos >= runtime->buffer_size)
+			buffer_pos -= runtime->buffer_size;
+
+		if (period_pos >= runtime->period_size) {
+			period_pos -= runtime->period_size;
+			period_elapsed = 1;
+		}
+	}
+
+	snd_pcm_stream_lock_irqsave(substream, flags);
+
+	chip->snd_buffer_pos = buffer_pos;
+	chip->snd_period_pos = period_pos;
+
+	snd_pcm_stream_unlock_irqrestore(substream, flags);
+
+	if (period_elapsed)
+		snd_pcm_period_elapsed(substream);
+
+	usb_submit_urb(urb, GFP_ATOMIC);
+}
+
+static int usbtv_audio_start(struct usbtv *chip)
+{
+	unsigned int pipe;
+	static const u16 setup[][2] = {
+		/* These seem to enable the device. */
+		{ USBTV_BASE + 0x0008, 0x0001 },
+		{ USBTV_BASE + 0x01d0, 0x00ff },
+		{ USBTV_BASE + 0x01d9, 0x0002 },
+
+		{ USBTV_BASE + 0x01da, 0x0013 },
+		{ USBTV_BASE + 0x01db, 0x0012 },
+		{ USBTV_BASE + 0x01e9, 0x0002 },
+		{ USBTV_BASE + 0x01ec, 0x006c },
+		{ USBTV_BASE + 0x0294, 0x0020 },
+		{ USBTV_BASE + 0x0255, 0x00cf },
+		{ USBTV_BASE + 0x0256, 0x0020 },
+		{ USBTV_BASE + 0x01eb, 0x0030 },
+		{ USBTV_BASE + 0x027d, 0x00a6 },
+		{ USBTV_BASE + 0x0280, 0x0011 },
+		{ USBTV_BASE + 0x0281, 0x0040 },
+		{ USBTV_BASE + 0x0282, 0x0011 },
+		{ USBTV_BASE + 0x0283, 0x0040 },
+		{ 0xf891, 0x0010 },
+
+		/* this sets the input from composite */
+		{ USBTV_BASE + 0x0284, 0x00aa },
+	};
+
+	chip->snd_bulk_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (chip->snd_bulk_urb == NULL)
+		goto err_alloc_urb;
+
+	pipe = usb_rcvbulkpipe(chip->udev, USBTV_AUDIO_ENDP);
+
+	chip->snd_bulk_urb->transfer_buffer = kzalloc(
+		USBTV_AUDIO_URBSIZE, GFP_KERNEL);
+	if (chip->snd_bulk_urb->transfer_buffer == NULL)
+		goto err_transfer_buffer;
+
+	usb_fill_bulk_urb(chip->snd_bulk_urb, chip->udev, pipe,
+		chip->snd_bulk_urb->transfer_buffer, USBTV_AUDIO_URBSIZE,
+		usbtv_audio_urb_received, chip);
+
+	/* starting the stream */
+	usbtv_set_regs(chip, setup, ARRAY_SIZE(setup));
+
+	usb_clear_halt(chip->udev, pipe);
+	usb_submit_urb(chip->snd_bulk_urb, GFP_ATOMIC);
+
+	return 0;
+
+err_transfer_buffer:
+	usb_free_urb(chip->snd_bulk_urb);
+	chip->snd_bulk_urb = NULL;
+
+err_alloc_urb:
+	return -ENOMEM;
+}
+
+static int usbtv_audio_stop(struct usbtv *chip)
+{
+	static const u16 setup[][2] = {
+	/* The original windows driver sometimes sends also:
+	 *   { USBTV_BASE + 0x00a2, 0x0013 }
+	 * but it seems useless and its real effects are untested at
+	 * the moment.
+	 */
+		{ USBTV_BASE + 0x027d, 0x0000 },
+		{ USBTV_BASE + 0x0280, 0x0010 },
+		{ USBTV_BASE + 0x0282, 0x0010 },
+	};
+
+	if (chip->snd_bulk_urb) {
+		usb_kill_urb(chip->snd_bulk_urb);
+		kfree(chip->snd_bulk_urb->transfer_buffer);
+		usb_free_urb(chip->snd_bulk_urb);
+		chip->snd_bulk_urb = NULL;
+	}
+
+	usbtv_set_regs(chip, setup, ARRAY_SIZE(setup));
+
+	return 0;
+}
+
+void usbtv_audio_suspend(struct usbtv *usbtv)
+{
+	if (atomic_read(&usbtv->snd_stream) && usbtv->snd_bulk_urb)
+		usb_kill_urb(usbtv->snd_bulk_urb);
+}
+
+void usbtv_audio_resume(struct usbtv *usbtv)
+{
+	if (atomic_read(&usbtv->snd_stream) && usbtv->snd_bulk_urb)
+		usb_submit_urb(usbtv->snd_bulk_urb, GFP_ATOMIC);
+}
+
+static void snd_usbtv_trigger(struct work_struct *work)
+{
+	struct usbtv *chip = container_of(work, struct usbtv, snd_trigger);
+
+	if (!chip->snd)
+		return;
+
+	if (atomic_read(&chip->snd_stream))
+		usbtv_audio_start(chip);
+	else
+		usbtv_audio_stop(chip);
+}
+
+static int snd_usbtv_card_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct usbtv *chip = snd_pcm_substream_chip(substream);
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		atomic_set(&chip->snd_stream, 1);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		atomic_set(&chip->snd_stream, 0);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	schedule_work(&chip->snd_trigger);
+
+	return 0;
+}
+
+static snd_pcm_uframes_t snd_usbtv_pointer(struct snd_pcm_substream *substream)
+{
+	struct usbtv *chip = snd_pcm_substream_chip(substream);
+
+	return chip->snd_buffer_pos;
+}
+
+static const struct snd_pcm_ops snd_usbtv_pcm_ops = {
+	.open = snd_usbtv_pcm_open,
+	.close = snd_usbtv_pcm_close,
+	.ioctl = snd_pcm_lib_ioctl,
+	.hw_params = snd_usbtv_hw_params,
+	.hw_free = snd_usbtv_hw_free,
+	.prepare = snd_usbtv_prepare,
+	.trigger = snd_usbtv_card_trigger,
+	.pointer = snd_usbtv_pointer,
+};
+
+int usbtv_audio_init(struct usbtv *usbtv)
+{
+	int rv;
+	struct snd_card *card;
+	struct snd_pcm *pcm;
+
+	INIT_WORK(&usbtv->snd_trigger, snd_usbtv_trigger);
+	atomic_set(&usbtv->snd_stream, 0);
+
+	rv = snd_card_new(&usbtv->udev->dev, SNDRV_DEFAULT_IDX1, "usbtv",
+		THIS_MODULE, 0, &card);
+	if (rv < 0)
+		return rv;
+
+	strlcpy(card->driver, usbtv->dev->driver->name, sizeof(card->driver));
+	strlcpy(card->shortname, "usbtv", sizeof(card->shortname));
+	snprintf(card->longname, sizeof(card->longname),
+		"USBTV Audio at bus %d device %d", usbtv->udev->bus->busnum,
+		usbtv->udev->devnum);
+
+	snd_card_set_dev(card, usbtv->dev);
+
+	usbtv->snd = card;
+
+	rv = snd_pcm_new(card, "USBTV Audio", 0, 0, 1, &pcm);
+	if (rv < 0)
+		goto err;
+
+	strlcpy(pcm->name, "USBTV Audio Input", sizeof(pcm->name));
+	pcm->info_flags = 0;
+	pcm->private_data = usbtv;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_usbtv_pcm_ops);
+	snd_pcm_lib_preallocate_pages_for_all(pcm, SNDRV_DMA_TYPE_CONTINUOUS,
+		snd_dma_continuous_data(GFP_KERNEL), USBTV_AUDIO_BUFFER,
+		USBTV_AUDIO_BUFFER);
+
+	rv = snd_card_register(card);
+	if (rv)
+		goto err;
+
+	return 0;
+
+err:
+	usbtv->snd = NULL;
+	snd_card_free(card);
+
+	return rv;
+}
+
+void usbtv_audio_free(struct usbtv *usbtv)
+{
+	cancel_work_sync(&usbtv->snd_trigger);
+
+	if (usbtv->snd && usbtv->udev) {
+		snd_card_free(usbtv->snd);
+		usbtv->snd = NULL;
+	}
+}
diff --git a/drivers/media/usb/usbtv/usbtv-core.c b/drivers/media/usb/usbtv/usbtv-core.c
new file mode 100644
index 0000000..5095c38
--- /dev/null
+++ b/drivers/media/usb/usbtv/usbtv-core.c
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2013 Lubomir Rintel
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL").
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * Fushicai USBTV007 Audio-Video Grabber Driver
+ *
+ * Product web site:
+ * http://www.fushicai.com/products_detail/&productId=d05449ee-b690-42f9-a661-aa7353894bed.html
+ *
+ * Following LWN articles were very useful in construction of this driver:
+ * Video4Linux2 API series: http://lwn.net/Articles/203924/
+ * videobuf2 API explanation: http://lwn.net/Articles/447435/
+ * Thanks go to Jonathan Corbet for providing this quality documentation.
+ * He is awesome.
+ *
+ * No physical hardware was harmed running Windows during the
+ * reverse-engineering activity
+ */
+
+#include "usbtv.h"
+
+int usbtv_set_regs(struct usbtv *usbtv, const u16 regs[][2], int size)
+{
+	int ret;
+	int pipe = usb_rcvctrlpipe(usbtv->udev, 0);
+	int i;
+
+	for (i = 0; i < size; i++) {
+		u16 index = regs[i][0];
+		u16 value = regs[i][1];
+
+		ret = usb_control_msg(usbtv->udev, pipe, USBTV_REQUEST_REG,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			value, index, NULL, 0, 0);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int usbtv_probe(struct usb_interface *intf,
+	const struct usb_device_id *id)
+{
+	int ret;
+	int size;
+	struct device *dev = &intf->dev;
+	struct usbtv *usbtv;
+	struct usb_host_endpoint *ep;
+
+	/* Checks that the device is what we think it is. */
+	if (intf->num_altsetting != 2)
+		return -ENODEV;
+	if (intf->altsetting[1].desc.bNumEndpoints != 4)
+		return -ENODEV;
+
+	ep = &intf->altsetting[1].endpoint[0];
+
+	/* Packet size is split into 11 bits of base size and count of
+	 * extra multiplies of it.*/
+	size = usb_endpoint_maxp(&ep->desc);
+	size = size * usb_endpoint_maxp_mult(&ep->desc);
+
+	/* Device structure */
+	usbtv = kzalloc(sizeof(struct usbtv), GFP_KERNEL);
+	if (usbtv == NULL)
+		return -ENOMEM;
+	usbtv->dev = dev;
+	usbtv->udev = usb_get_dev(interface_to_usbdev(intf));
+
+	usbtv->iso_size = size;
+
+	usb_set_intfdata(intf, usbtv);
+
+	ret = usbtv_video_init(usbtv);
+	if (ret < 0)
+		goto usbtv_video_fail;
+
+	ret = usbtv_audio_init(usbtv);
+	if (ret < 0)
+		goto usbtv_audio_fail;
+
+	/* for simplicity we exploit the v4l2_device reference counting */
+	v4l2_device_get(&usbtv->v4l2_dev);
+
+	dev_info(dev, "Fushicai USBTV007 Audio-Video Grabber\n");
+	return 0;
+
+usbtv_audio_fail:
+	/* we must not free at this point */
+	usb_get_dev(usbtv->udev);
+	usbtv_video_free(usbtv);
+
+usbtv_video_fail:
+	usb_set_intfdata(intf, NULL);
+	usb_put_dev(usbtv->udev);
+	kfree(usbtv);
+
+	return ret;
+}
+
+static void usbtv_disconnect(struct usb_interface *intf)
+{
+	struct usbtv *usbtv = usb_get_intfdata(intf);
+
+	usb_set_intfdata(intf, NULL);
+
+	if (!usbtv)
+		return;
+
+	usbtv_audio_free(usbtv);
+	usbtv_video_free(usbtv);
+
+	usb_put_dev(usbtv->udev);
+	usbtv->udev = NULL;
+
+	/* the usbtv structure will be deallocated when v4l2 will be
+	   done using it */
+	v4l2_device_put(&usbtv->v4l2_dev);
+}
+
+static const struct usb_device_id usbtv_id_table[] = {
+	{ USB_DEVICE(0x1b71, 0x3002) },
+	{ USB_DEVICE(0x1f71, 0x3301) },
+	{ USB_DEVICE(0x1f71, 0x3306) },
+	{}
+};
+MODULE_DEVICE_TABLE(usb, usbtv_id_table);
+
+MODULE_AUTHOR("Lubomir Rintel, Federico Simoncelli");
+MODULE_DESCRIPTION("Fushicai USBTV007 Audio-Video Grabber Driver");
+MODULE_LICENSE("Dual BSD/GPL");
+
+static struct usb_driver usbtv_usb_driver = {
+	.name = "usbtv",
+	.id_table = usbtv_id_table,
+	.probe = usbtv_probe,
+	.disconnect = usbtv_disconnect,
+};
+
+module_usb_driver(usbtv_usb_driver);
diff --git a/drivers/media/usb/usbtv/usbtv-video.c b/drivers/media/usb/usbtv/usbtv-video.c
new file mode 100644
index 0000000..36a9a40
--- /dev/null
+++ b/drivers/media/usb/usbtv/usbtv-video.c
@@ -0,0 +1,977 @@
+/*
+ * Copyright (c) 2013,2016 Lubomir Rintel
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL").
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * Fushicai USBTV007 Audio-Video Grabber Driver
+ *
+ * Product web site:
+ * http://www.fushicai.com/products_detail/&productId=d05449ee-b690-42f9-a661-aa7353894bed.html
+ *
+ * Following LWN articles were very useful in construction of this driver:
+ * Video4Linux2 API series: http://lwn.net/Articles/203924/
+ * videobuf2 API explanation: http://lwn.net/Articles/447435/
+ * Thanks go to Jonathan Corbet for providing this quality documentation.
+ * He is awesome.
+ *
+ * No physical hardware was harmed running Windows during the
+ * reverse-engineering activity
+ */
+
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-v4l2.h>
+
+#include "usbtv.h"
+
+static struct usbtv_norm_params norm_params[] = {
+	{
+		.norm = V4L2_STD_525_60,
+		.cap_width = 720,
+		.cap_height = 480,
+	},
+	{
+		.norm = V4L2_STD_625_50,
+		.cap_width = 720,
+		.cap_height = 576,
+	}
+};
+
+static int usbtv_configure_for_norm(struct usbtv *usbtv, v4l2_std_id norm)
+{
+	int i, ret = 0;
+	struct usbtv_norm_params *params = NULL;
+
+	for (i = 0; i < ARRAY_SIZE(norm_params); i++) {
+		if (norm_params[i].norm & norm) {
+			params = &norm_params[i];
+			break;
+		}
+	}
+
+	if (params) {
+		usbtv->width = params->cap_width;
+		usbtv->height = params->cap_height;
+		usbtv->n_chunks = usbtv->width * usbtv->height
+						/ 4 / USBTV_CHUNK;
+		usbtv->norm = norm;
+	} else
+		ret = -EINVAL;
+
+	return ret;
+}
+
+static int usbtv_select_input(struct usbtv *usbtv, int input)
+{
+	int ret;
+
+	static const u16 composite[][2] = {
+		{ USBTV_BASE + 0x0105, 0x0060 },
+		{ USBTV_BASE + 0x011f, 0x00f2 },
+		{ USBTV_BASE + 0x0127, 0x0060 },
+		{ USBTV_BASE + 0x00ae, 0x0010 },
+		{ USBTV_BASE + 0x0239, 0x0060 },
+	};
+
+	static const u16 svideo[][2] = {
+		{ USBTV_BASE + 0x0105, 0x0010 },
+		{ USBTV_BASE + 0x011f, 0x00ff },
+		{ USBTV_BASE + 0x0127, 0x0060 },
+		{ USBTV_BASE + 0x00ae, 0x0030 },
+		{ USBTV_BASE + 0x0239, 0x0060 },
+	};
+
+	switch (input) {
+	case USBTV_COMPOSITE_INPUT:
+		ret = usbtv_set_regs(usbtv, composite, ARRAY_SIZE(composite));
+		break;
+	case USBTV_SVIDEO_INPUT:
+		ret = usbtv_set_regs(usbtv, svideo, ARRAY_SIZE(svideo));
+		break;
+	default:
+		ret = -EINVAL;
+	}
+
+	if (!ret)
+		usbtv->input = input;
+
+	return ret;
+}
+
+static uint16_t usbtv_norm_to_16f_reg(v4l2_std_id norm)
+{
+	/* NTSC M/M-JP/M-KR */
+	if (norm & V4L2_STD_NTSC)
+		return 0x00b8;
+	/* PAL BG/DK/H/I */
+	if (norm & V4L2_STD_PAL)
+		return 0x00ee;
+	/* SECAM B/D/G/H/K/K1/L/Lc */
+	if (norm & V4L2_STD_SECAM)
+		return 0x00ff;
+	if (norm & V4L2_STD_NTSC_443)
+		return 0x00a8;
+	if (norm & (V4L2_STD_PAL_M | V4L2_STD_PAL_60))
+		return 0x00bc;
+	/* Fallback to automatic detection for other standards */
+	return 0x0000;
+}
+
+static int usbtv_select_norm(struct usbtv *usbtv, v4l2_std_id norm)
+{
+	int ret;
+	/* These are the series of register values used to configure the
+	 * decoder for a specific standard.
+	 * The first 21 register writes are copied from the
+	 * Settings\DecoderDefaults registry keys present in the Windows driver
+	 * .INF file, and control various image tuning parameters (color
+	 * correction, sharpness, ...).
+	 */
+	static const u16 pal[][2] = {
+		/* "AVPAL" tuning sequence from .INF file */
+		{ USBTV_BASE + 0x0003, 0x0004 },
+		{ USBTV_BASE + 0x001a, 0x0068 },
+		{ USBTV_BASE + 0x0100, 0x00d3 },
+		{ USBTV_BASE + 0x010e, 0x0072 },
+		{ USBTV_BASE + 0x010f, 0x00a2 },
+		{ USBTV_BASE + 0x0112, 0x00b0 },
+		{ USBTV_BASE + 0x0115, 0x0015 },
+		{ USBTV_BASE + 0x0117, 0x0001 },
+		{ USBTV_BASE + 0x0118, 0x002c },
+		{ USBTV_BASE + 0x012d, 0x0010 },
+		{ USBTV_BASE + 0x012f, 0x0020 },
+		{ USBTV_BASE + 0x0220, 0x002e },
+		{ USBTV_BASE + 0x0225, 0x0008 },
+		{ USBTV_BASE + 0x024e, 0x0002 },
+		{ USBTV_BASE + 0x024f, 0x0002 },
+		{ USBTV_BASE + 0x0254, 0x0059 },
+		{ USBTV_BASE + 0x025a, 0x0016 },
+		{ USBTV_BASE + 0x025b, 0x0035 },
+		{ USBTV_BASE + 0x0263, 0x0017 },
+		{ USBTV_BASE + 0x0266, 0x0016 },
+		{ USBTV_BASE + 0x0267, 0x0036 },
+		/* End image tuning */
+		{ USBTV_BASE + 0x024e, 0x0002 },
+		{ USBTV_BASE + 0x024f, 0x0002 },
+	};
+
+	static const u16 ntsc[][2] = {
+		/* "AVNTSC" tuning sequence from .INF file */
+		{ USBTV_BASE + 0x0003, 0x0004 },
+		{ USBTV_BASE + 0x001a, 0x0079 },
+		{ USBTV_BASE + 0x0100, 0x00d3 },
+		{ USBTV_BASE + 0x010e, 0x0068 },
+		{ USBTV_BASE + 0x010f, 0x009c },
+		{ USBTV_BASE + 0x0112, 0x00f0 },
+		{ USBTV_BASE + 0x0115, 0x0015 },
+		{ USBTV_BASE + 0x0117, 0x0000 },
+		{ USBTV_BASE + 0x0118, 0x00fc },
+		{ USBTV_BASE + 0x012d, 0x0004 },
+		{ USBTV_BASE + 0x012f, 0x0008 },
+		{ USBTV_BASE + 0x0220, 0x002e },
+		{ USBTV_BASE + 0x0225, 0x0008 },
+		{ USBTV_BASE + 0x024e, 0x0002 },
+		{ USBTV_BASE + 0x024f, 0x0001 },
+		{ USBTV_BASE + 0x0254, 0x005f },
+		{ USBTV_BASE + 0x025a, 0x0012 },
+		{ USBTV_BASE + 0x025b, 0x0001 },
+		{ USBTV_BASE + 0x0263, 0x001c },
+		{ USBTV_BASE + 0x0266, 0x0011 },
+		{ USBTV_BASE + 0x0267, 0x0005 },
+		/* End image tuning */
+		{ USBTV_BASE + 0x024e, 0x0002 },
+		{ USBTV_BASE + 0x024f, 0x0002 },
+	};
+
+	static const u16 secam[][2] = {
+		/* "AVSECAM" tuning sequence from .INF file */
+		{ USBTV_BASE + 0x0003, 0x0004 },
+		{ USBTV_BASE + 0x001a, 0x0073 },
+		{ USBTV_BASE + 0x0100, 0x00dc },
+		{ USBTV_BASE + 0x010e, 0x0072 },
+		{ USBTV_BASE + 0x010f, 0x00a2 },
+		{ USBTV_BASE + 0x0112, 0x0090 },
+		{ USBTV_BASE + 0x0115, 0x0035 },
+		{ USBTV_BASE + 0x0117, 0x0001 },
+		{ USBTV_BASE + 0x0118, 0x0030 },
+		{ USBTV_BASE + 0x012d, 0x0004 },
+		{ USBTV_BASE + 0x012f, 0x0008 },
+		{ USBTV_BASE + 0x0220, 0x002d },
+		{ USBTV_BASE + 0x0225, 0x0028 },
+		{ USBTV_BASE + 0x024e, 0x0008 },
+		{ USBTV_BASE + 0x024f, 0x0002 },
+		{ USBTV_BASE + 0x0254, 0x0069 },
+		{ USBTV_BASE + 0x025a, 0x0016 },
+		{ USBTV_BASE + 0x025b, 0x0035 },
+		{ USBTV_BASE + 0x0263, 0x0021 },
+		{ USBTV_BASE + 0x0266, 0x0016 },
+		{ USBTV_BASE + 0x0267, 0x0036 },
+		/* End image tuning */
+		{ USBTV_BASE + 0x024e, 0x0002 },
+		{ USBTV_BASE + 0x024f, 0x0002 },
+	};
+
+	ret = usbtv_configure_for_norm(usbtv, norm);
+
+	if (!ret) {
+		/* Masks for norms using a NTSC or PAL color encoding. */
+		static const v4l2_std_id ntsc_mask =
+			V4L2_STD_NTSC | V4L2_STD_NTSC_443;
+		static const v4l2_std_id pal_mask =
+			V4L2_STD_PAL | V4L2_STD_PAL_60 | V4L2_STD_PAL_M;
+
+		if (norm & ntsc_mask)
+			ret = usbtv_set_regs(usbtv, ntsc, ARRAY_SIZE(ntsc));
+		else if (norm & pal_mask)
+			ret = usbtv_set_regs(usbtv, pal, ARRAY_SIZE(pal));
+		else if (norm & V4L2_STD_SECAM)
+			ret = usbtv_set_regs(usbtv, secam, ARRAY_SIZE(secam));
+		else
+			ret = -EINVAL;
+	}
+
+	if (!ret) {
+		/* Configure the decoder for the color standard */
+		const u16 cfg[][2] = {
+			{ USBTV_BASE + 0x016f, usbtv_norm_to_16f_reg(norm) }
+		};
+		ret = usbtv_set_regs(usbtv, cfg, ARRAY_SIZE(cfg));
+	}
+
+	return ret;
+}
+
+static int usbtv_setup_capture(struct usbtv *usbtv)
+{
+	int ret;
+	static const u16 setup[][2] = {
+		/* These seem to enable the device. */
+		{ USBTV_BASE + 0x0008, 0x0001 },
+		{ USBTV_BASE + 0x01d0, 0x00ff },
+		{ USBTV_BASE + 0x01d9, 0x0002 },
+
+		/* These seem to influence color parameters, such as
+		 * brightness, etc. */
+		{ USBTV_BASE + 0x0239, 0x0040 },
+		{ USBTV_BASE + 0x0240, 0x0000 },
+		{ USBTV_BASE + 0x0241, 0x0000 },
+		{ USBTV_BASE + 0x0242, 0x0002 },
+		{ USBTV_BASE + 0x0243, 0x0080 },
+		{ USBTV_BASE + 0x0244, 0x0012 },
+		{ USBTV_BASE + 0x0245, 0x0090 },
+		{ USBTV_BASE + 0x0246, 0x0000 },
+
+		{ USBTV_BASE + 0x0278, 0x002d },
+		{ USBTV_BASE + 0x0279, 0x000a },
+		{ USBTV_BASE + 0x027a, 0x0032 },
+		{ 0xf890, 0x000c },
+		{ 0xf894, 0x0086 },
+
+		{ USBTV_BASE + 0x00ac, 0x00c0 },
+		{ USBTV_BASE + 0x00ad, 0x0000 },
+		{ USBTV_BASE + 0x00a2, 0x0012 },
+		{ USBTV_BASE + 0x00a3, 0x00e0 },
+		{ USBTV_BASE + 0x00a4, 0x0028 },
+		{ USBTV_BASE + 0x00a5, 0x0082 },
+		{ USBTV_BASE + 0x00a7, 0x0080 },
+		{ USBTV_BASE + 0x0000, 0x0014 },
+		{ USBTV_BASE + 0x0006, 0x0003 },
+		{ USBTV_BASE + 0x0090, 0x0099 },
+		{ USBTV_BASE + 0x0091, 0x0090 },
+		{ USBTV_BASE + 0x0094, 0x0068 },
+		{ USBTV_BASE + 0x0095, 0x0070 },
+		{ USBTV_BASE + 0x009c, 0x0030 },
+		{ USBTV_BASE + 0x009d, 0x00c0 },
+		{ USBTV_BASE + 0x009e, 0x00e0 },
+		{ USBTV_BASE + 0x0019, 0x0006 },
+		{ USBTV_BASE + 0x008c, 0x00ba },
+		{ USBTV_BASE + 0x0101, 0x00ff },
+		{ USBTV_BASE + 0x010c, 0x00b3 },
+		{ USBTV_BASE + 0x01b2, 0x0080 },
+		{ USBTV_BASE + 0x01b4, 0x00a0 },
+		{ USBTV_BASE + 0x014c, 0x00ff },
+		{ USBTV_BASE + 0x014d, 0x00ca },
+		{ USBTV_BASE + 0x0113, 0x0053 },
+		{ USBTV_BASE + 0x0119, 0x008a },
+		{ USBTV_BASE + 0x013c, 0x0003 },
+		{ USBTV_BASE + 0x0150, 0x009c },
+		{ USBTV_BASE + 0x0151, 0x0071 },
+		{ USBTV_BASE + 0x0152, 0x00c6 },
+		{ USBTV_BASE + 0x0153, 0x0084 },
+		{ USBTV_BASE + 0x0154, 0x00bc },
+		{ USBTV_BASE + 0x0155, 0x00a0 },
+		{ USBTV_BASE + 0x0156, 0x00a0 },
+		{ USBTV_BASE + 0x0157, 0x009c },
+		{ USBTV_BASE + 0x0158, 0x001f },
+		{ USBTV_BASE + 0x0159, 0x0006 },
+		{ USBTV_BASE + 0x015d, 0x0000 },
+	};
+
+	ret = usbtv_set_regs(usbtv, setup, ARRAY_SIZE(setup));
+	if (ret)
+		return ret;
+
+	ret = usbtv_select_norm(usbtv, usbtv->norm);
+	if (ret)
+		return ret;
+
+	ret = usbtv_select_input(usbtv, usbtv->input);
+	if (ret)
+		return ret;
+
+	ret = v4l2_ctrl_handler_setup(&usbtv->ctrl);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+/* Copy data from chunk into a frame buffer, deinterlacing the data
+ * into every second line. Unfortunately, they don't align nicely into
+ * 720 pixel lines, as the chunk is 240 words long, which is 480 pixels.
+ * Therefore, we break down the chunk into two halves before copying,
+ * so that we can interleave a line if needed.
+ *
+ * Each "chunk" is 240 words; a word in this context equals 4 bytes.
+ * Image format is YUYV/YUV 4:2:2, consisting of Y Cr Y Cb, defining two
+ * pixels, the Cr and Cb shared between the two pixels, but each having
+ * separate Y values. Thus, the 240 words equal 480 pixels. It therefore,
+ * takes 1.5 chunks to make a 720 pixel-wide line for the frame.
+ * The image is interlaced, so there is a "scan" of odd lines, followed
+ * by "scan" of even numbered lines.
+ *
+ * Following code is writing the chunks in correct sequence, skipping
+ * the rows based on "odd" value.
+ * line 1: chunk[0][  0..479] chunk[0][480..959] chunk[1][  0..479]
+ * line 3: chunk[1][480..959] chunk[2][  0..479] chunk[2][480..959]
+ * ...etc.
+ */
+static void usbtv_chunk_to_vbuf(u32 *frame, __be32 *src, int chunk_no, int odd)
+{
+	int half;
+
+	for (half = 0; half < 2; half++) {
+		int part_no = chunk_no * 2 + half;
+		int line = part_no / 3;
+		int part_index = (line * 2 + !odd) * 3 + (part_no % 3);
+
+		u32 *dst = &frame[part_index * USBTV_CHUNK/2];
+
+		memcpy(dst, src, USBTV_CHUNK/2 * sizeof(*src));
+		src += USBTV_CHUNK/2;
+	}
+}
+
+/* Called for each 256-byte image chunk.
+ * First word identifies the chunk, followed by 240 words of image
+ * data and padding. */
+static void usbtv_image_chunk(struct usbtv *usbtv, __be32 *chunk)
+{
+	int frame_id, odd, chunk_no;
+	u32 *frame;
+	struct usbtv_buf *buf;
+	unsigned long flags;
+
+	/* Ignore corrupted lines. */
+	if (!USBTV_MAGIC_OK(chunk))
+		return;
+	frame_id = USBTV_FRAME_ID(chunk);
+	odd = USBTV_ODD(chunk);
+	chunk_no = USBTV_CHUNK_NO(chunk);
+	if (chunk_no >= usbtv->n_chunks)
+		return;
+
+	/* Beginning of a frame. */
+	if (chunk_no == 0) {
+		usbtv->frame_id = frame_id;
+		usbtv->chunks_done = 0;
+	}
+
+	if (usbtv->frame_id != frame_id)
+		return;
+
+	spin_lock_irqsave(&usbtv->buflock, flags);
+	if (list_empty(&usbtv->bufs)) {
+		/* No free buffers. Userspace likely too slow. */
+		spin_unlock_irqrestore(&usbtv->buflock, flags);
+		return;
+	}
+
+	/* First available buffer. */
+	buf = list_first_entry(&usbtv->bufs, struct usbtv_buf, list);
+	frame = vb2_plane_vaddr(&buf->vb.vb2_buf, 0);
+
+	/* Copy the chunk data. */
+	usbtv_chunk_to_vbuf(frame, &chunk[1], chunk_no, odd);
+	usbtv->chunks_done++;
+
+	/* Last chunk in a field */
+	if (chunk_no == usbtv->n_chunks-1) {
+		/* Last chunk in a frame, signalling an end */
+		if (odd && !usbtv->last_odd) {
+			int size = vb2_plane_size(&buf->vb.vb2_buf, 0);
+			enum vb2_buffer_state state = usbtv->chunks_done ==
+				usbtv->n_chunks ?
+				VB2_BUF_STATE_DONE :
+				VB2_BUF_STATE_ERROR;
+
+			buf->vb.field = V4L2_FIELD_INTERLACED;
+			buf->vb.sequence = usbtv->sequence++;
+			buf->vb.vb2_buf.timestamp = ktime_get_ns();
+			vb2_set_plane_payload(&buf->vb.vb2_buf, 0, size);
+			vb2_buffer_done(&buf->vb.vb2_buf, state);
+			list_del(&buf->list);
+		}
+		usbtv->last_odd = odd;
+	}
+
+	spin_unlock_irqrestore(&usbtv->buflock, flags);
+}
+
+/* Got image data. Each packet contains a number of 256-word chunks we
+ * compose the image from. */
+static void usbtv_iso_cb(struct urb *ip)
+{
+	int ret;
+	int i;
+	struct usbtv *usbtv = (struct usbtv *)ip->context;
+
+	switch (ip->status) {
+	/* All fine. */
+	case 0:
+		break;
+	/* Device disconnected or capture stopped? */
+	case -ENODEV:
+	case -ENOENT:
+	case -ECONNRESET:
+	case -ESHUTDOWN:
+		return;
+	/* Unknown error. Retry. */
+	default:
+		dev_warn(usbtv->dev, "Bad response for ISO request.\n");
+		goto resubmit;
+	}
+
+	for (i = 0; i < ip->number_of_packets; i++) {
+		int size = ip->iso_frame_desc[i].actual_length;
+		unsigned char *data = ip->transfer_buffer +
+				ip->iso_frame_desc[i].offset;
+		int offset;
+
+		for (offset = 0; USBTV_CHUNK_SIZE * offset < size; offset++)
+			usbtv_image_chunk(usbtv,
+				(__be32 *)&data[USBTV_CHUNK_SIZE * offset]);
+	}
+
+resubmit:
+	ret = usb_submit_urb(ip, GFP_ATOMIC);
+	if (ret < 0)
+		dev_warn(usbtv->dev, "Could not resubmit ISO URB\n");
+}
+
+static struct urb *usbtv_setup_iso_transfer(struct usbtv *usbtv)
+{
+	struct urb *ip;
+	int size = usbtv->iso_size;
+	int i;
+
+	ip = usb_alloc_urb(USBTV_ISOC_PACKETS, GFP_KERNEL);
+	if (ip == NULL)
+		return NULL;
+
+	ip->dev = usbtv->udev;
+	ip->context = usbtv;
+	ip->pipe = usb_rcvisocpipe(usbtv->udev, USBTV_VIDEO_ENDP);
+	ip->interval = 1;
+	ip->transfer_flags = URB_ISO_ASAP;
+	ip->transfer_buffer = kcalloc(USBTV_ISOC_PACKETS, size,
+						GFP_KERNEL);
+	if (!ip->transfer_buffer) {
+		usb_free_urb(ip);
+		return NULL;
+	}
+	ip->complete = usbtv_iso_cb;
+	ip->number_of_packets = USBTV_ISOC_PACKETS;
+	ip->transfer_buffer_length = size * USBTV_ISOC_PACKETS;
+	for (i = 0; i < USBTV_ISOC_PACKETS; i++) {
+		ip->iso_frame_desc[i].offset = size * i;
+		ip->iso_frame_desc[i].length = size;
+	}
+
+	return ip;
+}
+
+static void usbtv_stop(struct usbtv *usbtv)
+{
+	int i;
+	unsigned long flags;
+
+	/* Cancel running transfers. */
+	for (i = 0; i < USBTV_ISOC_TRANSFERS; i++) {
+		struct urb *ip = usbtv->isoc_urbs[i];
+
+		if (ip == NULL)
+			continue;
+		usb_kill_urb(ip);
+		kfree(ip->transfer_buffer);
+		usb_free_urb(ip);
+		usbtv->isoc_urbs[i] = NULL;
+	}
+
+	/* Return buffers to userspace. */
+	spin_lock_irqsave(&usbtv->buflock, flags);
+	while (!list_empty(&usbtv->bufs)) {
+		struct usbtv_buf *buf = list_first_entry(&usbtv->bufs,
+						struct usbtv_buf, list);
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+		list_del(&buf->list);
+	}
+	spin_unlock_irqrestore(&usbtv->buflock, flags);
+}
+
+static int usbtv_start(struct usbtv *usbtv)
+{
+	int i;
+	int ret;
+
+	usbtv_audio_suspend(usbtv);
+
+	ret = usb_set_interface(usbtv->udev, 0, 0);
+	if (ret < 0)
+		return ret;
+
+	ret = usbtv_setup_capture(usbtv);
+	if (ret < 0)
+		return ret;
+
+	ret = usb_set_interface(usbtv->udev, 0, 1);
+	if (ret < 0)
+		return ret;
+
+	usbtv_audio_resume(usbtv);
+
+	for (i = 0; i < USBTV_ISOC_TRANSFERS; i++) {
+		struct urb *ip;
+
+		ip = usbtv_setup_iso_transfer(usbtv);
+		if (ip == NULL) {
+			ret = -ENOMEM;
+			goto start_fail;
+		}
+		usbtv->isoc_urbs[i] = ip;
+
+		ret = usb_submit_urb(ip, GFP_KERNEL);
+		if (ret < 0)
+			goto start_fail;
+	}
+
+	return 0;
+
+start_fail:
+	usbtv_stop(usbtv);
+	return ret;
+}
+
+static int usbtv_querycap(struct file *file, void *priv,
+				struct v4l2_capability *cap)
+{
+	struct usbtv *dev = video_drvdata(file);
+
+	strlcpy(cap->driver, "usbtv", sizeof(cap->driver));
+	strlcpy(cap->card, "usbtv", sizeof(cap->card));
+	usb_make_path(dev->udev, cap->bus_info, sizeof(cap->bus_info));
+	cap->device_caps = V4L2_CAP_VIDEO_CAPTURE;
+	cap->device_caps |= V4L2_CAP_READWRITE | V4L2_CAP_STREAMING;
+	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+	return 0;
+}
+
+static int usbtv_enum_input(struct file *file, void *priv,
+					struct v4l2_input *i)
+{
+	struct usbtv *dev = video_drvdata(file);
+
+	switch (i->index) {
+	case USBTV_COMPOSITE_INPUT:
+		strlcpy(i->name, "Composite", sizeof(i->name));
+		break;
+	case USBTV_SVIDEO_INPUT:
+		strlcpy(i->name, "S-Video", sizeof(i->name));
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	i->type = V4L2_INPUT_TYPE_CAMERA;
+	i->std = dev->vdev.tvnorms;
+	return 0;
+}
+
+static int usbtv_enum_fmt_vid_cap(struct file *file, void  *priv,
+					struct v4l2_fmtdesc *f)
+{
+	if (f->index > 0)
+		return -EINVAL;
+
+	strlcpy(f->description, "16 bpp YUY2, 4:2:2, packed",
+					sizeof(f->description));
+	f->pixelformat = V4L2_PIX_FMT_YUYV;
+	return 0;
+}
+
+static int usbtv_fmt_vid_cap(struct file *file, void *priv,
+					struct v4l2_format *f)
+{
+	struct usbtv *usbtv = video_drvdata(file);
+
+	f->fmt.pix.width = usbtv->width;
+	f->fmt.pix.height = usbtv->height;
+	f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
+	f->fmt.pix.field = V4L2_FIELD_INTERLACED;
+	f->fmt.pix.bytesperline = usbtv->width * 2;
+	f->fmt.pix.sizeimage = (f->fmt.pix.bytesperline * f->fmt.pix.height);
+	f->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+
+	return 0;
+}
+
+static int usbtv_g_std(struct file *file, void *priv, v4l2_std_id *norm)
+{
+	struct usbtv *usbtv = video_drvdata(file);
+	*norm = usbtv->norm;
+	return 0;
+}
+
+static int usbtv_s_std(struct file *file, void *priv, v4l2_std_id norm)
+{
+	int ret = -EINVAL;
+	struct usbtv *usbtv = video_drvdata(file);
+
+	if (norm & USBTV_TV_STD)
+		ret = usbtv_select_norm(usbtv, norm);
+
+	return ret;
+}
+
+static int usbtv_g_input(struct file *file, void *priv, unsigned int *i)
+{
+	struct usbtv *usbtv = video_drvdata(file);
+	*i = usbtv->input;
+	return 0;
+}
+
+static int usbtv_s_input(struct file *file, void *priv, unsigned int i)
+{
+	struct usbtv *usbtv = video_drvdata(file);
+
+	return usbtv_select_input(usbtv, i);
+}
+
+static struct v4l2_ioctl_ops usbtv_ioctl_ops = {
+	.vidioc_querycap = usbtv_querycap,
+	.vidioc_enum_input = usbtv_enum_input,
+	.vidioc_enum_fmt_vid_cap = usbtv_enum_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap = usbtv_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap = usbtv_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap = usbtv_fmt_vid_cap,
+	.vidioc_g_std = usbtv_g_std,
+	.vidioc_s_std = usbtv_s_std,
+	.vidioc_g_input = usbtv_g_input,
+	.vidioc_s_input = usbtv_s_input,
+
+	.vidioc_reqbufs = vb2_ioctl_reqbufs,
+	.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+	.vidioc_querybuf = vb2_ioctl_querybuf,
+	.vidioc_create_bufs = vb2_ioctl_create_bufs,
+	.vidioc_qbuf = vb2_ioctl_qbuf,
+	.vidioc_dqbuf = vb2_ioctl_dqbuf,
+	.vidioc_streamon = vb2_ioctl_streamon,
+	.vidioc_streamoff = vb2_ioctl_streamoff,
+};
+
+static const struct v4l2_file_operations usbtv_fops = {
+	.owner = THIS_MODULE,
+	.unlocked_ioctl = video_ioctl2,
+	.mmap = vb2_fop_mmap,
+	.open = v4l2_fh_open,
+	.release = vb2_fop_release,
+	.read = vb2_fop_read,
+	.poll = vb2_fop_poll,
+};
+
+static int usbtv_queue_setup(struct vb2_queue *vq,
+	unsigned int *nbuffers,
+	unsigned int *nplanes, unsigned int sizes[], struct device *alloc_devs[])
+{
+	struct usbtv *usbtv = vb2_get_drv_priv(vq);
+	unsigned size = USBTV_CHUNK * usbtv->n_chunks * 2 * sizeof(u32);
+
+	if (vq->num_buffers + *nbuffers < 2)
+		*nbuffers = 2 - vq->num_buffers;
+	if (*nplanes)
+		return sizes[0] < size ? -EINVAL : 0;
+	*nplanes = 1;
+	sizes[0] = size;
+
+	return 0;
+}
+
+static void usbtv_buf_queue(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct usbtv *usbtv = vb2_get_drv_priv(vb->vb2_queue);
+	struct usbtv_buf *buf = container_of(vbuf, struct usbtv_buf, vb);
+	unsigned long flags;
+
+	if (usbtv->udev == NULL) {
+		vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
+		return;
+	}
+
+	spin_lock_irqsave(&usbtv->buflock, flags);
+	list_add_tail(&buf->list, &usbtv->bufs);
+	spin_unlock_irqrestore(&usbtv->buflock, flags);
+}
+
+static int usbtv_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+	struct usbtv *usbtv = vb2_get_drv_priv(vq);
+
+	if (usbtv->udev == NULL)
+		return -ENODEV;
+
+	usbtv->last_odd = 1;
+	usbtv->sequence = 0;
+	return usbtv_start(usbtv);
+}
+
+static void usbtv_stop_streaming(struct vb2_queue *vq)
+{
+	struct usbtv *usbtv = vb2_get_drv_priv(vq);
+
+	if (usbtv->udev)
+		usbtv_stop(usbtv);
+}
+
+static const struct vb2_ops usbtv_vb2_ops = {
+	.queue_setup = usbtv_queue_setup,
+	.buf_queue = usbtv_buf_queue,
+	.start_streaming = usbtv_start_streaming,
+	.stop_streaming = usbtv_stop_streaming,
+	.wait_prepare = vb2_ops_wait_prepare,
+	.wait_finish = vb2_ops_wait_finish,
+};
+
+static int usbtv_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct usbtv *usbtv = container_of(ctrl->handler, struct usbtv,
+								ctrl);
+	u8 *data;
+	u16 index, size;
+	int ret;
+
+	data = kmalloc(3, GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	/*
+	 * Read in the current brightness/contrast registers. We need them
+	 * both, because the values are for some reason interleaved.
+	 */
+	if (ctrl->id == V4L2_CID_BRIGHTNESS || ctrl->id == V4L2_CID_CONTRAST) {
+		ret = usb_control_msg(usbtv->udev,
+			usb_rcvctrlpipe(usbtv->udev, 0), USBTV_CONTROL_REG,
+			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0, USBTV_BASE + 0x0244, (void *)data, 3, 0);
+		if (ret < 0)
+			goto error;
+	}
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		index = USBTV_BASE + 0x0244;
+		size = 3;
+		data[0] &= 0xf0;
+		data[0] |= (ctrl->val >> 8) & 0xf;
+		data[2] = ctrl->val & 0xff;
+		break;
+	case V4L2_CID_CONTRAST:
+		index = USBTV_BASE + 0x0244;
+		size = 3;
+		data[0] &= 0x0f;
+		data[0] |= (ctrl->val >> 4) & 0xf0;
+		data[1] = ctrl->val & 0xff;
+		break;
+	case V4L2_CID_SATURATION:
+		index = USBTV_BASE + 0x0242;
+		data[0] = ctrl->val >> 8;
+		data[1] = ctrl->val & 0xff;
+		size = 2;
+		break;
+	case V4L2_CID_HUE:
+		index = USBTV_BASE + 0x0240;
+		size = 2;
+		if (ctrl->val > 0) {
+			data[0] = 0x92 + (ctrl->val >> 8);
+			data[1] = ctrl->val & 0xff;
+		} else {
+			data[0] = 0x82 + (-ctrl->val >> 8);
+			data[1] = -ctrl->val & 0xff;
+		}
+		break;
+	case V4L2_CID_SHARPNESS:
+		index = USBTV_BASE + 0x0239;
+		data[0] = 0;
+		data[1] = ctrl->val;
+		size = 2;
+		break;
+	default:
+		kfree(data);
+		return -EINVAL;
+	}
+
+	ret = usb_control_msg(usbtv->udev, usb_sndctrlpipe(usbtv->udev, 0),
+			USBTV_CONTROL_REG,
+			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			0, index, (void *)data, size, 0);
+
+error:
+	if (ret < 0)
+		dev_warn(usbtv->dev, "Failed to submit a control request.\n");
+
+	kfree(data);
+	return ret;
+}
+
+static const struct v4l2_ctrl_ops usbtv_ctrl_ops = {
+	.s_ctrl = usbtv_s_ctrl,
+};
+
+static void usbtv_release(struct v4l2_device *v4l2_dev)
+{
+	struct usbtv *usbtv = container_of(v4l2_dev, struct usbtv, v4l2_dev);
+
+	v4l2_device_unregister(&usbtv->v4l2_dev);
+	v4l2_ctrl_handler_free(&usbtv->ctrl);
+	vb2_queue_release(&usbtv->vb2q);
+	kfree(usbtv);
+}
+
+int usbtv_video_init(struct usbtv *usbtv)
+{
+	int ret;
+
+	(void)usbtv_configure_for_norm(usbtv, V4L2_STD_525_60);
+
+	spin_lock_init(&usbtv->buflock);
+	mutex_init(&usbtv->v4l2_lock);
+	mutex_init(&usbtv->vb2q_lock);
+	INIT_LIST_HEAD(&usbtv->bufs);
+
+	/* videobuf2 structure */
+	usbtv->vb2q.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	usbtv->vb2q.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
+	usbtv->vb2q.drv_priv = usbtv;
+	usbtv->vb2q.buf_struct_size = sizeof(struct usbtv_buf);
+	usbtv->vb2q.ops = &usbtv_vb2_ops;
+	usbtv->vb2q.mem_ops = &vb2_vmalloc_memops;
+	usbtv->vb2q.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	usbtv->vb2q.lock = &usbtv->vb2q_lock;
+	ret = vb2_queue_init(&usbtv->vb2q);
+	if (ret < 0) {
+		dev_warn(usbtv->dev, "Could not initialize videobuf2 queue\n");
+		return ret;
+	}
+
+	/* controls */
+	v4l2_ctrl_handler_init(&usbtv->ctrl, 4);
+	v4l2_ctrl_new_std(&usbtv->ctrl, &usbtv_ctrl_ops,
+			V4L2_CID_CONTRAST, 0, 0x3ff, 1, 0x1d0);
+	v4l2_ctrl_new_std(&usbtv->ctrl, &usbtv_ctrl_ops,
+			V4L2_CID_BRIGHTNESS, 0, 0x3ff, 1, 0x1c0);
+	v4l2_ctrl_new_std(&usbtv->ctrl, &usbtv_ctrl_ops,
+			V4L2_CID_SATURATION, 0, 0x3ff, 1, 0x200);
+	v4l2_ctrl_new_std(&usbtv->ctrl, &usbtv_ctrl_ops,
+			V4L2_CID_HUE, -0xdff, 0xdff, 1, 0x000);
+	v4l2_ctrl_new_std(&usbtv->ctrl, &usbtv_ctrl_ops,
+			V4L2_CID_SHARPNESS, 0x0, 0xff, 1, 0x60);
+	ret = usbtv->ctrl.error;
+	if (ret < 0) {
+		dev_warn(usbtv->dev, "Could not initialize controls\n");
+		goto ctrl_fail;
+	}
+
+	/* v4l2 structure */
+	usbtv->v4l2_dev.ctrl_handler = &usbtv->ctrl;
+	usbtv->v4l2_dev.release = usbtv_release;
+	ret = v4l2_device_register(usbtv->dev, &usbtv->v4l2_dev);
+	if (ret < 0) {
+		dev_warn(usbtv->dev, "Could not register v4l2 device\n");
+		goto v4l2_fail;
+	}
+
+	/* Video structure */
+	strlcpy(usbtv->vdev.name, "usbtv", sizeof(usbtv->vdev.name));
+	usbtv->vdev.v4l2_dev = &usbtv->v4l2_dev;
+	usbtv->vdev.release = video_device_release_empty;
+	usbtv->vdev.fops = &usbtv_fops;
+	usbtv->vdev.ioctl_ops = &usbtv_ioctl_ops;
+	usbtv->vdev.tvnorms = USBTV_TV_STD;
+	usbtv->vdev.queue = &usbtv->vb2q;
+	usbtv->vdev.lock = &usbtv->v4l2_lock;
+	video_set_drvdata(&usbtv->vdev, usbtv);
+	ret = video_register_device(&usbtv->vdev, VFL_TYPE_GRABBER, -1);
+	if (ret < 0) {
+		dev_warn(usbtv->dev, "Could not register video device\n");
+		goto vdev_fail;
+	}
+
+	return 0;
+
+vdev_fail:
+	v4l2_device_unregister(&usbtv->v4l2_dev);
+v4l2_fail:
+ctrl_fail:
+	v4l2_ctrl_handler_free(&usbtv->ctrl);
+	vb2_queue_release(&usbtv->vb2q);
+
+	return ret;
+}
+
+void usbtv_video_free(struct usbtv *usbtv)
+{
+	mutex_lock(&usbtv->vb2q_lock);
+	mutex_lock(&usbtv->v4l2_lock);
+
+	usbtv_stop(usbtv);
+	video_unregister_device(&usbtv->vdev);
+	v4l2_device_disconnect(&usbtv->v4l2_dev);
+
+	mutex_unlock(&usbtv->v4l2_lock);
+	mutex_unlock(&usbtv->vb2q_lock);
+
+	v4l2_device_put(&usbtv->v4l2_dev);
+}
diff --git a/drivers/media/usb/usbtv/usbtv.h b/drivers/media/usb/usbtv/usbtv.h
new file mode 100644
index 0000000..77a368e
--- /dev/null
+++ b/drivers/media/usb/usbtv/usbtv.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2013 Lubomir Rintel
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL").
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * Fushicai USBTV007 Audio-Video Grabber Driver
+ *
+ * No physical hardware was harmed running Windows during the
+ * reverse-engineering activity
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-vmalloc.h>
+
+/* Hardware. */
+#define USBTV_VIDEO_ENDP	0x81
+#define USBTV_AUDIO_ENDP	0x83
+#define USBTV_BASE		0xc000
+#define USBTV_CONTROL_REG	11
+#define USBTV_REQUEST_REG	12
+
+/* Number of concurrent isochronous urbs submitted.
+ * Higher numbers was seen to overly saturate the USB bus. */
+#define USBTV_ISOC_TRANSFERS	16
+#define USBTV_ISOC_PACKETS	8
+
+#define USBTV_CHUNK_SIZE	256
+#define USBTV_CHUNK		240
+
+#define USBTV_AUDIO_URBSIZE	20480
+#define USBTV_AUDIO_HDRSIZE	4
+#define USBTV_AUDIO_BUFFER	65536
+
+/* Chunk header. */
+#define USBTV_MAGIC_OK(chunk)	((be32_to_cpu(chunk[0]) & 0xff000000) \
+							== 0x88000000)
+#define USBTV_FRAME_ID(chunk)	((be32_to_cpu(chunk[0]) & 0x00ff0000) >> 16)
+#define USBTV_ODD(chunk)	((be32_to_cpu(chunk[0]) & 0x0000f000) >> 15)
+#define USBTV_CHUNK_NO(chunk)	(be32_to_cpu(chunk[0]) & 0x00000fff)
+
+#define USBTV_TV_STD  (V4L2_STD_525_60 | V4L2_STD_PAL | V4L2_STD_SECAM)
+
+/* parameters for supported TV norms */
+struct usbtv_norm_params {
+	v4l2_std_id norm;
+	int cap_width, cap_height;
+};
+
+/* A single videobuf2 frame buffer. */
+struct usbtv_buf {
+	struct vb2_v4l2_buffer vb;
+	struct list_head list;
+};
+
+/* Per-device structure. */
+struct usbtv {
+	struct device *dev;
+	struct usb_device *udev;
+
+	/* video */
+	struct v4l2_device v4l2_dev;
+	struct v4l2_ctrl_handler ctrl;
+	struct video_device vdev;
+	struct vb2_queue vb2q;
+	struct mutex v4l2_lock;
+	struct mutex vb2q_lock;
+
+	/* List of videobuf2 buffers protected by a lock. */
+	spinlock_t buflock;
+	struct list_head bufs;
+
+	/* Number of currently processed frame, useful find
+	 * out when a new one begins. */
+	u32 frame_id;
+	int chunks_done;
+
+	enum {
+		USBTV_COMPOSITE_INPUT,
+		USBTV_SVIDEO_INPUT,
+	} input;
+	v4l2_std_id norm;
+	int width, height;
+	int n_chunks;
+	int iso_size;
+	int last_odd;
+	unsigned int sequence;
+	struct urb *isoc_urbs[USBTV_ISOC_TRANSFERS];
+
+	/* audio */
+	struct snd_card *snd;
+	struct snd_pcm_substream *snd_substream;
+	atomic_t snd_stream;
+	struct work_struct snd_trigger;
+	struct urb *snd_bulk_urb;
+	size_t snd_buffer_pos;
+	size_t snd_period_pos;
+};
+
+int usbtv_set_regs(struct usbtv *usbtv, const u16 regs[][2], int size);
+
+int usbtv_video_init(struct usbtv *usbtv);
+void usbtv_video_free(struct usbtv *usbtv);
+
+int usbtv_audio_init(struct usbtv *usbtv);
+void usbtv_audio_free(struct usbtv *usbtv);
+void usbtv_audio_suspend(struct usbtv *usbtv);
+void usbtv_audio_resume(struct usbtv *usbtv);
diff --git a/drivers/media/usb/usbvision/Kconfig b/drivers/media/usb/usbvision/Kconfig
new file mode 100644
index 0000000..6b6afc5
--- /dev/null
+++ b/drivers/media/usb/usbvision/Kconfig
@@ -0,0 +1,12 @@
+config VIDEO_USBVISION
+	tristate "USB video devices based on Nogatech NT1003/1004/1005"
+	depends on I2C && VIDEO_V4L2
+	select VIDEO_TUNER
+	select VIDEO_SAA711X if MEDIA_SUBDRV_AUTOSELECT
+	---help---
+	  There are more than 50 different USB video devices based on
+	  NT1003/1004/1005 USB Bridges. This driver enables using those
+	  devices.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called usbvision.
diff --git a/drivers/media/usb/usbvision/Makefile b/drivers/media/usb/usbvision/Makefile
new file mode 100644
index 0000000..494d030
--- /dev/null
+++ b/drivers/media/usb/usbvision/Makefile
@@ -0,0 +1,5 @@
+usbvision-objs  := usbvision-core.o usbvision-video.o usbvision-i2c.o usbvision-cards.o
+
+obj-$(CONFIG_VIDEO_USBVISION) += usbvision.o
+
+ccflags-y += -Idrivers/media/tuners
diff --git a/drivers/media/usb/usbvision/usbvision-cards.c b/drivers/media/usb/usbvision/usbvision-cards.c
new file mode 100644
index 0000000..fc2418b
--- /dev/null
+++ b/drivers/media/usb/usbvision/usbvision-cards.c
@@ -0,0 +1,1129 @@
+/*
+ *  usbvision-cards.c
+ *  usbvision cards definition file
+ *
+ * Copyright (c) 1999-2005 Joerg Heckenbach <joerg@heckenbach-aw.de>
+ *
+ * This module is part of usbvision driver project.
+ * Updates to driver completed by Dwaine P. Garden
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+
+#include <linux/list.h>
+#include <linux/module.h>
+#include <media/v4l2-dev.h>
+#include <media/tuner.h>
+#include "usbvision.h"
+#include "usbvision-cards.h"
+
+/* Supported Devices: A table for usbvision.c*/
+struct usbvision_device_data_st  usbvision_device_data[] = {
+	[XANBOO] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 4,
+		.video_norm     = V4L2_STD_NTSC,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 0,
+		.tuner_type     = 0,
+		.x_offset       = -1,
+		.y_offset       = -1,
+		.model_string   = "Xanboo",
+	},
+	[BELKIN_VIDEOBUS_II] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 2,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 0,
+		.tuner_type     = 0,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Belkin USB VideoBus II Adapter",
+	},
+	[BELKIN_VIDEOBUS] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7111,
+		.video_channels = 2,
+		.video_norm     = V4L2_STD_NTSC,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 0,
+		.tuner_type     = 0,
+		.x_offset       = -1,
+		.y_offset       = -1,
+		.model_string   = "Belkin Components USB VideoBus",
+	},
+	[BELKIN_USB_VIDEOBUS_II] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 2,
+		.video_norm     = V4L2_STD_NTSC,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 0,
+		.tuner_type     = 0,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Belkin USB VideoBus II",
+	},
+	[ECHOFX_INTERVIEW_LITE] = {
+		.interface      = 0,
+		.codec          = CODEC_SAA7111,
+		.video_channels = 2,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 0,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 0,
+		.tuner_type     = 0,
+		.x_offset       = -1,
+		.y_offset       = -1,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "echoFX InterView Lite",
+	},
+	[USBGEAR_USBG_V1] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7111,
+		.video_channels = 2,
+		.video_norm     = V4L2_STD_NTSC,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 0,
+		.tuner_type     = 0,
+		.x_offset       = -1,
+		.y_offset       = -1,
+		.model_string   = "USBGear USBG-V1 resp. HAMA USB",
+	},
+	[D_LINK_V100] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 4,
+		.video_norm     = V4L2_STD_NTSC,
+		.audio_channels = 0,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 0,
+		.tuner_type     = 0,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "D-Link V100",
+	},
+	[X10_USB_CAMERA] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7111,
+		.video_channels = 2,
+		.video_norm     = V4L2_STD_NTSC,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 0,
+		.tuner_type     = 0,
+		.x_offset       = -1,
+		.y_offset       = -1,
+		.model_string   = "X10 USB Camera",
+	},
+	[HPG_WINTV_LIVE_PAL_BG] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7111,
+		.video_channels = 2,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 0,
+		.tuner_type     = 0,
+		.x_offset       = -1,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Hauppauge WinTV USB Live (PAL B/G)",
+	},
+	[HPG_WINTV_LIVE_PRO_NTSC_MN] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 2,
+		.video_norm     = V4L2_STD_NTSC,
+		.audio_channels = 0,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 0,
+		.tuner_type     = 0,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Hauppauge WinTV USB Live Pro (NTSC M/N)",
+	},
+	[ZORAN_PMD_NOGATECH] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 2,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 2,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 0,
+		.tuner_type     = 0,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Zoran Co. PMD (Nogatech) AV-grabber Manhattan",
+	},
+	[NOGATECH_USB_TV_NTSC_FM] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7111,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_NTSC,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_NTSC_M,
+		.x_offset       = -1,
+		.y_offset       = 20,
+		.model_string   = "Nogatech USB-TV (NTSC) FM",
+	},
+	[PNY_USB_TV_NTSC_FM] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7111,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_NTSC,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_NTSC_M,
+		.x_offset       = -1,
+		.y_offset       = 20,
+		.model_string   = "PNY USB-TV (NTSC) FM",
+	},
+	[PV_PLAYTV_USB_PRO_PAL_FM] = {
+		.interface      = 0,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_PAL,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "PixelView PlayTv-USB PRO (PAL) FM",
+	},
+	[ZT_721] = {
+		.interface      = 0,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_PAL,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "ZTV ZT-721 2.4GHz USB A/V Receiver",
+	},
+	[HPG_WINTV_NTSC_MN] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7111,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_NTSC,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_NTSC_M,
+		.x_offset       = -1,
+		.y_offset       = 20,
+		.model_string   = "Hauppauge WinTV USB (NTSC M/N)",
+	},
+	[HPG_WINTV_PAL_BG] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7111,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_PAL,
+		.x_offset       = -1,
+		.y_offset       = -1,
+		.model_string   = "Hauppauge WinTV USB (PAL B/G)",
+	},
+	[HPG_WINTV_PAL_I] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7111,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_PAL,
+		.x_offset       = -1,
+		.y_offset       = -1,
+		.model_string   = "Hauppauge WinTV USB (PAL I)",
+	},
+	[HPG_WINTV_PAL_SECAM_L] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7111,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_SECAM,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_SECAM,
+		.x_offset       = 0x80,
+		.y_offset       = 0x16,
+		.model_string   = "Hauppauge WinTV USB (PAL/SECAM L)",
+	},
+	[HPG_WINTV_PAL_D_K] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7111,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_PAL,
+		.x_offset       = -1,
+		.y_offset       = -1,
+		.model_string   = "Hauppauge WinTV USB (PAL D/K)",
+	},
+	[HPG_WINTV_NTSC_FM] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7111,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_NTSC,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_NTSC_M,
+		.x_offset       = -1,
+		.y_offset       = -1,
+		.model_string   = "Hauppauge WinTV USB (NTSC FM)",
+	},
+	[HPG_WINTV_PAL_BG_FM] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7111,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_PAL,
+		.x_offset       = -1,
+		.y_offset       = -1,
+		.model_string   = "Hauppauge WinTV USB (PAL B/G FM)",
+	},
+	[HPG_WINTV_PAL_I_FM] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7111,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_PAL,
+		.x_offset       = -1,
+		.y_offset       = -1,
+		.model_string   = "Hauppauge WinTV USB (PAL I FM)",
+	},
+	[HPG_WINTV_PAL_D_K_FM] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7111,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_PAL,
+		.x_offset       = -1,
+		.y_offset       = -1,
+		.model_string   = "Hauppauge WinTV USB (PAL D/K FM)",
+	},
+	[HPG_WINTV_PRO_NTSC_MN] = {
+		.interface      = 0,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_NTSC,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_MICROTUNE_4049FM5,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Hauppauge WinTV USB Pro (NTSC M/N)",
+	},
+	[HPG_WINTV_PRO_NTSC_MN_V2] = {
+		.interface      = 0,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_NTSC,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_MICROTUNE_4049FM5,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Hauppauge WinTV USB Pro (NTSC M/N) V2",
+	},
+	[HPG_WINTV_PRO_PAL] = {
+		.interface      = 0,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_FM1216ME_MK3,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Hauppauge WinTV USB Pro (PAL/SECAM B/G/I/D/K/L)",
+	},
+	[HPG_WINTV_PRO_NTSC_MN_V3] = {
+		.interface      = 0,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_NTSC,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_NTSC_M,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Hauppauge WinTV USB Pro (NTSC M/N) V3",
+	},
+	[HPG_WINTV_PRO_PAL_BG] = {
+		.interface      = 0,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_PAL,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Hauppauge WinTV USB Pro (PAL B/G)",
+	},
+	[HPG_WINTV_PRO_PAL_I] = {
+		.interface      = 0,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_PAL,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Hauppauge WinTV USB Pro (PAL I)",
+	},
+	[HPG_WINTV_PRO_PAL_SECAM_L] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_SECAM,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_SECAM,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Hauppauge WinTV USB Pro (PAL/SECAM L)",
+	},
+	[HPG_WINTV_PRO_PAL_D_K] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_PAL,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Hauppauge WinTV USB Pro (PAL D/K)",
+	},
+	[HPG_WINTV_PRO_PAL_SECAM] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_SECAM,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_SECAM,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Hauppauge WinTV USB Pro (PAL/SECAM BGDK/I/L)",
+	},
+	[HPG_WINTV_PRO_PAL_SECAM_V2] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_SECAM,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_SECAM,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Hauppauge WinTV USB Pro (PAL/SECAM BGDK/I/L) V2",
+	},
+	[HPG_WINTV_PRO_PAL_BG_V2] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_ALPS_TSBE1_PAL,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Hauppauge WinTV USB Pro (PAL B/G) V2",
+	},
+	[HPG_WINTV_PRO_PAL_BG_D_K] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_ALPS_TSBE1_PAL,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Hauppauge WinTV USB Pro (PAL B/G,D/K)",
+	},
+	[HPG_WINTV_PRO_PAL_I_D_K] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_LG_PAL_NEW_TAPC,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Hauppauge WinTV USB Pro (PAL I,D/K)",
+	},
+	[HPG_WINTV_PRO_NTSC_MN_FM] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_NTSC,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_NTSC_M,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Hauppauge WinTV USB Pro (NTSC M/N FM)",
+	},
+	[HPG_WINTV_PRO_PAL_BG_FM] = {
+		.interface      = 0,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_PAL,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Hauppauge WinTV USB Pro (PAL B/G FM)",
+	},
+	[HPG_WINTV_PRO_PAL_I_FM] = {
+		.interface      = 0,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_PAL,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Hauppauge WinTV USB Pro (PAL I FM)",
+	},
+	[HPG_WINTV_PRO_PAL_D_K_FM] = {
+		.interface      = 0,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_PAL,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Hauppauge WinTV USB Pro (PAL D/K FM)",
+	},
+	[HPG_WINTV_PRO_TEMIC_PAL_FM] = {
+		.interface      = 0,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_MICROTUNE_4049FM5,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Hauppauge WinTV USB Pro (Temic PAL/SECAM B/G/I/D/K/L FM)",
+	},
+	[HPG_WINTV_PRO_TEMIC_PAL_BG_FM] = {
+		.interface      = 0,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_MICROTUNE_4049FM5,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Hauppauge WinTV USB Pro (Temic PAL B/G FM)",
+	},
+	[HPG_WINTV_PRO_PAL_FM] = {
+		.interface      = 0,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_FM1216ME_MK3,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Hauppauge WinTV USB Pro (PAL/SECAM B/G/I/D/K/L FM)",
+	},
+	[HPG_WINTV_PRO_NTSC_MN_FM_V2] = {
+		.interface      = 0,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_NTSC,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_NTSC_M,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Hauppauge WinTV USB Pro (NTSC M/N FM) V2",
+	},
+	[CAMTEL_TVB330] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_NTSC,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_NTSC_M,
+		.x_offset       = 5,
+		.y_offset       = 5,
+		.model_string   = "Camtel Technology USB TV Genie Pro FM Model TVB330",
+	},
+	[DIGITAL_VIDEO_CREATOR_I] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 2,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 0,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 0,
+		.tuner_type     = 0,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Digital Video Creator I",
+	},
+	[GLOBAL_VILLAGE_GV_007_NTSC] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7111,
+		.video_channels = 2,
+		.video_norm     = V4L2_STD_NTSC,
+		.audio_channels = 0,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 0,
+		.tuner_type     = 0,
+		.x_offset       = 82,
+		.y_offset       = 20,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Global Village GV-007 (NTSC)",
+	},
+	[DAZZLE_DVC_50_REV_1_NTSC] = {
+		.interface      = 0,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 2,
+		.video_norm     = V4L2_STD_NTSC,
+		.audio_channels = 0,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 0,
+		.tuner_type     = 0,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Dazzle Fusion Model DVC-50 Rev 1 (NTSC)",
+	},
+	[DAZZLE_DVC_80_REV_1_PAL] = {
+		.interface      = 0,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 2,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 0,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 0,
+		.tuner_type     = 0,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Dazzle Fusion Model DVC-80 Rev 1 (PAL)",
+	},
+	[DAZZLE_DVC_90_REV_1_SECAM] = {
+		.interface      = 0,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 2,
+		.video_norm     = V4L2_STD_SECAM,
+		.audio_channels = 0,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 0,
+		.tuner_type     = 0,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Dazzle Fusion Model DVC-90 Rev 1 (SECAM)",
+	},
+	[ESKAPE_LABS_MYTV2GO] = {
+		.interface      = 0,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 2,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_FM1216ME_MK3,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Eskape Labs MyTV2Go",
+	},
+	[PINNA_PCTV_USB_PAL] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7111,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 0,
+		.tuner          = 1,
+		.tuner_type     = TUNER_TEMIC_4066FY5_PAL_I,
+		.x_offset       = -1,
+		.y_offset       = -1,
+		.model_string   = "Pinnacle Studio PCTV USB (PAL)",
+	},
+	[PINNA_PCTV_USB_SECAM] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7111,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_SECAM,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_SECAM,
+		.x_offset       = -1,
+		.y_offset       = -1,
+		.model_string   = "Pinnacle Studio PCTV USB (SECAM)",
+	},
+	[PINNA_PCTV_USB_PAL_FM] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7111,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_PAL,
+		.x_offset       = 128,
+		.y_offset       = 23,
+		.model_string   = "Pinnacle Studio PCTV USB (PAL) FM",
+	},
+	[MIRO_PCTV_USB] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7111,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_PAL,
+		.x_offset       = -1,
+		.y_offset       = -1,
+		.model_string   = "Miro PCTV USB",
+	},
+	[PINNA_PCTV_USB_NTSC_FM] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7111,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_NTSC,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_NTSC_M,
+		.x_offset       = -1,
+		.y_offset       = -1,
+		.model_string   = "Pinnacle Studio PCTV USB (NTSC) FM",
+	},
+	[PINNA_PCTV_USB_NTSC_FM_V3] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7111,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_NTSC,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_NTSC_M,
+		.x_offset       = -1,
+		.y_offset       = -1,
+		.model_string   = "Pinnacle Studio PCTV USB (NTSC) FM V3",
+	},
+	[PINNA_PCTV_USB_PAL_FM_V2] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_TEMIC_4009FR5_PAL,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Pinnacle Studio PCTV USB (PAL) FM V2",
+	},
+	[PINNA_PCTV_USB_NTSC_FM_V2] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7111,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_NTSC,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_TEMIC_4039FR5_NTSC,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Pinnacle Studio PCTV USB (NTSC) FM V2",
+	},
+	[PINNA_PCTV_USB_PAL_FM_V3] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_TEMIC_4009FR5_PAL,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Pinnacle Studio PCTV USB (PAL) FM V3",
+	},
+	[PINNA_LINX_VD_IN_CAB_NTSC] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 2,
+		.video_norm     = V4L2_STD_NTSC,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 0,
+		.tuner_type     = 0,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Pinnacle Studio Linx Video input cable (NTSC)",
+	},
+	[PINNA_LINX_VD_IN_CAB_PAL] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 2,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 0,
+		.tuner_type     = 0,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Pinnacle Studio Linx Video input cable (PAL)",
+	},
+	[PINNA_PCTV_BUNGEE_PAL_FM] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7113,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 1,
+		.radio          = 1,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_TEMIC_4009FR5_PAL,
+		.x_offset       = 0,
+		.y_offset       = 3,
+		.dvi_yuv_override = 1,
+		.dvi_yuv        = 7,
+		.model_string   = "Pinnacle PCTV Bungee USB (PAL) FM",
+	},
+	[HPG_WINTV] = {
+		.interface      = -1,
+		.codec          = CODEC_SAA7111,
+		.video_channels = 3,
+		.video_norm     = V4L2_STD_NTSC,
+		.audio_channels = 1,
+		.radio          = 0,
+		.vbi            = 1,
+		.tuner          = 1,
+		.tuner_type     = TUNER_PHILIPS_NTSC_M,
+		.x_offset       = -1,
+		.y_offset       = -1,
+		.model_string   = "Hauppauge WinTv-USB",
+	},
+	[MICROCAM_NTSC] = {
+		.interface      = -1,
+		.codec          = CODEC_WEBCAM,
+		.video_channels = 1,
+		.video_norm     = V4L2_STD_NTSC,
+		.audio_channels = 0,
+		.radio          = 0,
+		.vbi            = 0,
+		.tuner          = 0,
+		.tuner_type     = 0,
+		.x_offset       = 71,
+		.y_offset       = 15,
+		.model_string   = "Nogatech USB MicroCam NTSC (NV3000N)",
+	},
+	[MICROCAM_PAL] = {
+		.interface      = -1,
+		.codec          = CODEC_WEBCAM,
+		.video_channels = 1,
+		.video_norm     = V4L2_STD_PAL,
+		.audio_channels = 0,
+		.radio          = 0,
+		.vbi            = 0,
+		.tuner          = 0,
+		.tuner_type     = 0,
+		.x_offset       = 71,
+		.y_offset       = 18,
+		.model_string   = "Nogatech USB MicroCam PAL (NV3001P)",
+	},
+};
+const int usbvision_device_data_size = ARRAY_SIZE(usbvision_device_data);
+
+/* Supported Devices */
+
+struct usb_device_id usbvision_table[] = {
+	{ USB_DEVICE(0x0a6f, 0x0400), .driver_info = XANBOO },
+	{ USB_DEVICE(0x050d, 0x0106), .driver_info = BELKIN_VIDEOBUS_II },
+	{ USB_DEVICE(0x050d, 0x0207), .driver_info = BELKIN_VIDEOBUS },
+	{ USB_DEVICE(0x050d, 0x0208), .driver_info = BELKIN_USB_VIDEOBUS_II },
+	{ USB_DEVICE(0x0571, 0x0002), .driver_info = ECHOFX_INTERVIEW_LITE },
+	{ USB_DEVICE(0x0573, 0x0003), .driver_info = USBGEAR_USBG_V1 },
+	{ USB_DEVICE(0x0573, 0x0400), .driver_info = D_LINK_V100 },
+	{ USB_DEVICE(0x0573, 0x2000), .driver_info = X10_USB_CAMERA },
+	{ USB_DEVICE(0x0573, 0x2d00), .driver_info = HPG_WINTV_LIVE_PAL_BG },
+	{ USB_DEVICE(0x0573, 0x2d01), .driver_info = HPG_WINTV_LIVE_PRO_NTSC_MN },
+	{ USB_DEVICE(0x0573, 0x2101), .driver_info = ZORAN_PMD_NOGATECH },
+	{ USB_DEVICE(0x0573, 0x3000), .driver_info = MICROCAM_NTSC },
+	{ USB_DEVICE(0x0573, 0x3001), .driver_info = MICROCAM_PAL },
+	{ USB_DEVICE(0x0573, 0x4100), .driver_info = NOGATECH_USB_TV_NTSC_FM },
+	{ USB_DEVICE(0x0573, 0x4110), .driver_info = PNY_USB_TV_NTSC_FM },
+	{ USB_DEVICE(0x0573, 0x4450), .driver_info = PV_PLAYTV_USB_PRO_PAL_FM },
+	{ USB_DEVICE(0x0573, 0x4550), .driver_info = ZT_721 },
+	{ USB_DEVICE(0x0573, 0x4d00), .driver_info = HPG_WINTV_NTSC_MN },
+	{ USB_DEVICE(0x0573, 0x4d01), .driver_info = HPG_WINTV_PAL_BG },
+	{ USB_DEVICE(0x0573, 0x4d02), .driver_info = HPG_WINTV_PAL_I },
+	{ USB_DEVICE(0x0573, 0x4d03), .driver_info = HPG_WINTV_PAL_SECAM_L },
+	{ USB_DEVICE(0x0573, 0x4d04), .driver_info = HPG_WINTV_PAL_D_K },
+	{ USB_DEVICE(0x0573, 0x4d10), .driver_info = HPG_WINTV_NTSC_FM },
+	{ USB_DEVICE(0x0573, 0x4d11), .driver_info = HPG_WINTV_PAL_BG_FM },
+	{ USB_DEVICE(0x0573, 0x4d12), .driver_info = HPG_WINTV_PAL_I_FM },
+	{ USB_DEVICE(0x0573, 0x4d14), .driver_info = HPG_WINTV_PAL_D_K_FM },
+	{ USB_DEVICE(0x0573, 0x4d2a), .driver_info = HPG_WINTV_PRO_NTSC_MN },
+	{ USB_DEVICE(0x0573, 0x4d2b), .driver_info = HPG_WINTV_PRO_NTSC_MN_V2 },
+	{ USB_DEVICE(0x0573, 0x4d2c), .driver_info = HPG_WINTV_PRO_PAL },
+	{ USB_DEVICE(0x0573, 0x4d20), .driver_info = HPG_WINTV_PRO_NTSC_MN_V3 },
+	{ USB_DEVICE(0x0573, 0x4d21), .driver_info = HPG_WINTV_PRO_PAL_BG },
+	{ USB_DEVICE(0x0573, 0x4d22), .driver_info = HPG_WINTV_PRO_PAL_I },
+	{ USB_DEVICE(0x0573, 0x4d23), .driver_info = HPG_WINTV_PRO_PAL_SECAM_L },
+	{ USB_DEVICE(0x0573, 0x4d24), .driver_info = HPG_WINTV_PRO_PAL_D_K },
+	{ USB_DEVICE(0x0573, 0x4d25), .driver_info = HPG_WINTV_PRO_PAL_SECAM },
+	{ USB_DEVICE(0x0573, 0x4d26), .driver_info = HPG_WINTV_PRO_PAL_SECAM_V2 },
+	{ USB_DEVICE(0x0573, 0x4d27), .driver_info = HPG_WINTV_PRO_PAL_BG_V2 },
+	{ USB_DEVICE(0x0573, 0x4d28), .driver_info = HPG_WINTV_PRO_PAL_BG_D_K },
+	{ USB_DEVICE(0x0573, 0x4d29), .driver_info = HPG_WINTV_PRO_PAL_I_D_K },
+	{ USB_DEVICE(0x0573, 0x4d30), .driver_info = HPG_WINTV_PRO_NTSC_MN_FM },
+	{ USB_DEVICE(0x0573, 0x4d31), .driver_info = HPG_WINTV_PRO_PAL_BG_FM },
+	{ USB_DEVICE(0x0573, 0x4d32), .driver_info = HPG_WINTV_PRO_PAL_I_FM },
+	{ USB_DEVICE(0x0573, 0x4d34), .driver_info = HPG_WINTV_PRO_PAL_D_K_FM },
+	{ USB_DEVICE(0x0573, 0x4d35), .driver_info = HPG_WINTV_PRO_TEMIC_PAL_FM },
+	{ USB_DEVICE(0x0573, 0x4d36), .driver_info = HPG_WINTV_PRO_TEMIC_PAL_BG_FM },
+	{ USB_DEVICE(0x0573, 0x4d37), .driver_info = HPG_WINTV_PRO_PAL_FM },
+	{ USB_DEVICE(0x0573, 0x4d38), .driver_info = HPG_WINTV_PRO_NTSC_MN_FM_V2 },
+	{ USB_DEVICE(0x0768, 0x0006), .driver_info = CAMTEL_TVB330 },
+	{ USB_DEVICE(0x07d0, 0x0001), .driver_info = DIGITAL_VIDEO_CREATOR_I },
+	{ USB_DEVICE(0x07d0, 0x0002), .driver_info = GLOBAL_VILLAGE_GV_007_NTSC },
+	{ USB_DEVICE(0x07d0, 0x0003), .driver_info = DAZZLE_DVC_50_REV_1_NTSC },
+	{ USB_DEVICE(0x07d0, 0x0004), .driver_info = DAZZLE_DVC_80_REV_1_PAL },
+	{ USB_DEVICE(0x07d0, 0x0005), .driver_info = DAZZLE_DVC_90_REV_1_SECAM },
+	{ USB_DEVICE(0x07f8, 0x9104), .driver_info = ESKAPE_LABS_MYTV2GO },
+	{ USB_DEVICE(0x2304, 0x010d), .driver_info = PINNA_PCTV_USB_PAL },
+	{ USB_DEVICE(0x2304, 0x0109), .driver_info = PINNA_PCTV_USB_SECAM },
+	{ USB_DEVICE(0x2304, 0x0110), .driver_info = PINNA_PCTV_USB_PAL_FM },
+	{ USB_DEVICE(0x2304, 0x0111), .driver_info = MIRO_PCTV_USB },
+	{ USB_DEVICE(0x2304, 0x0112), .driver_info = PINNA_PCTV_USB_NTSC_FM },
+	{ USB_DEVICE(0x2304, 0x0113), .driver_info = PINNA_PCTV_USB_NTSC_FM_V3 },
+	{ USB_DEVICE(0x2304, 0x0210), .driver_info = PINNA_PCTV_USB_PAL_FM_V2 },
+	{ USB_DEVICE(0x2304, 0x0212), .driver_info = PINNA_PCTV_USB_NTSC_FM_V2 },
+	{ USB_DEVICE(0x2304, 0x0214), .driver_info = PINNA_PCTV_USB_PAL_FM_V3 },
+	{ USB_DEVICE(0x2304, 0x0300), .driver_info = PINNA_LINX_VD_IN_CAB_NTSC },
+	{ USB_DEVICE(0x2304, 0x0301), .driver_info = PINNA_LINX_VD_IN_CAB_PAL },
+	{ USB_DEVICE(0x2304, 0x0419), .driver_info = PINNA_PCTV_BUNGEE_PAL_FM },
+	{ USB_DEVICE(0x2400, 0x4200), .driver_info = HPG_WINTV },
+	{ },    /* terminate list */
+};
+
+MODULE_DEVICE_TABLE(usb, usbvision_table);
diff --git a/drivers/media/usb/usbvision/usbvision-cards.h b/drivers/media/usb/usbvision/usbvision-cards.h
new file mode 100644
index 0000000..07ec835
--- /dev/null
+++ b/drivers/media/usb/usbvision/usbvision-cards.h
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#define XANBOO                                   0
+#define BELKIN_VIDEOBUS_II                       1
+#define BELKIN_VIDEOBUS                          2
+#define BELKIN_USB_VIDEOBUS_II                   3
+#define ECHOFX_INTERVIEW_LITE                    4
+#define USBGEAR_USBG_V1                          5
+#define D_LINK_V100                              6
+#define X10_USB_CAMERA                           7
+#define HPG_WINTV_LIVE_PAL_BG                    8
+#define HPG_WINTV_LIVE_PRO_NTSC_MN               9
+#define ZORAN_PMD_NOGATECH                       10
+#define NOGATECH_USB_TV_NTSC_FM                  11
+#define PNY_USB_TV_NTSC_FM                       12
+#define PV_PLAYTV_USB_PRO_PAL_FM                 13
+#define ZT_721                                   14
+#define HPG_WINTV_NTSC_MN                        15
+#define HPG_WINTV_PAL_BG                         16
+#define HPG_WINTV_PAL_I                          17
+#define HPG_WINTV_PAL_SECAM_L                    18
+#define HPG_WINTV_PAL_D_K                        19
+#define HPG_WINTV_NTSC_FM                        20
+#define HPG_WINTV_PAL_BG_FM                      21
+#define HPG_WINTV_PAL_I_FM                       22
+#define HPG_WINTV_PAL_D_K_FM                     23
+#define HPG_WINTV_PRO_NTSC_MN                    24
+#define HPG_WINTV_PRO_NTSC_MN_V2                 25
+#define HPG_WINTV_PRO_PAL                        26
+#define HPG_WINTV_PRO_NTSC_MN_V3                 27
+#define HPG_WINTV_PRO_PAL_BG                     28
+#define HPG_WINTV_PRO_PAL_I                      29
+#define HPG_WINTV_PRO_PAL_SECAM_L                30
+#define HPG_WINTV_PRO_PAL_D_K                    31
+#define HPG_WINTV_PRO_PAL_SECAM                  32
+#define HPG_WINTV_PRO_PAL_SECAM_V2               33
+#define HPG_WINTV_PRO_PAL_BG_V2                  34
+#define HPG_WINTV_PRO_PAL_BG_D_K                 35
+#define HPG_WINTV_PRO_PAL_I_D_K                  36
+#define HPG_WINTV_PRO_NTSC_MN_FM                 37
+#define HPG_WINTV_PRO_PAL_BG_FM                  38
+#define HPG_WINTV_PRO_PAL_I_FM                   39
+#define HPG_WINTV_PRO_PAL_D_K_FM                 40
+#define HPG_WINTV_PRO_TEMIC_PAL_FM               41
+#define HPG_WINTV_PRO_TEMIC_PAL_BG_FM            42
+#define HPG_WINTV_PRO_PAL_FM                     43
+#define HPG_WINTV_PRO_NTSC_MN_FM_V2              44
+#define CAMTEL_TVB330                            45
+#define DIGITAL_VIDEO_CREATOR_I                  46
+#define GLOBAL_VILLAGE_GV_007_NTSC               47
+#define DAZZLE_DVC_50_REV_1_NTSC                 48
+#define DAZZLE_DVC_80_REV_1_PAL                  49
+#define DAZZLE_DVC_90_REV_1_SECAM                50
+#define ESKAPE_LABS_MYTV2GO                      51
+#define PINNA_PCTV_USB_PAL                       52
+#define PINNA_PCTV_USB_SECAM                     53
+#define PINNA_PCTV_USB_PAL_FM                    54
+#define MIRO_PCTV_USB                            55
+#define PINNA_PCTV_USB_NTSC_FM                   56
+#define PINNA_PCTV_USB_PAL_FM_V2                 57
+#define PINNA_PCTV_USB_NTSC_FM_V2                58
+#define PINNA_PCTV_USB_PAL_FM_V3                 59
+#define PINNA_LINX_VD_IN_CAB_NTSC                60
+#define PINNA_LINX_VD_IN_CAB_PAL                 61
+#define PINNA_PCTV_BUNGEE_PAL_FM                 62
+#define HPG_WINTV                                63
+#define PINNA_PCTV_USB_NTSC_FM_V3                64
+#define MICROCAM_NTSC                            65
+#define MICROCAM_PAL                             66
+
+extern const int usbvision_device_data_size;
diff --git a/drivers/media/usb/usbvision/usbvision-core.c b/drivers/media/usb/usbvision/usbvision-core.c
new file mode 100644
index 0000000..7138c2b
--- /dev/null
+++ b/drivers/media/usb/usbvision/usbvision-core.c
@@ -0,0 +1,2438 @@
+/*
+ * usbvision-core.c - driver for NT100x USB video capture devices
+ *
+ *
+ * Copyright (c) 1999-2005 Joerg Heckenbach <joerg@heckenbach-aw.de>
+ *                         Dwaine Garden <dwainegarden@rogers.com>
+ *
+ * This module is part of usbvision driver project.
+ * Updates to driver completed by Dwaine P. Garden
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/timer.h>
+#include <linux/gfp.h>
+#include <linux/mm.h>
+#include <linux/highmem.h>
+#include <linux/vmalloc.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/io.h>
+#include <linux/videodev2.h>
+#include <linux/i2c.h>
+
+#include <media/i2c/saa7115.h>
+#include <media/v4l2-common.h>
+#include <media/tuner.h>
+
+#include <linux/workqueue.h>
+
+#include "usbvision.h"
+
+static unsigned int core_debug;
+module_param(core_debug, int, 0644);
+MODULE_PARM_DESC(core_debug, "enable debug messages [core]");
+
+static int adjust_compression = 1;	/* Set the compression to be adaptive */
+module_param(adjust_compression, int, 0444);
+MODULE_PARM_DESC(adjust_compression, " Set the ADPCM compression for the device.  Default: 1 (On)");
+
+/* To help people with Black and White output with using s-video input.
+ * Some cables and input device are wired differently. */
+static int switch_svideo_input;
+module_param(switch_svideo_input, int, 0444);
+MODULE_PARM_DESC(switch_svideo_input, " Set the S-Video input.  Some cables and input device are wired differently. Default: 0 (Off)");
+
+static unsigned int adjust_x_offset = -1;
+module_param(adjust_x_offset, int, 0644);
+MODULE_PARM_DESC(adjust_x_offset, "adjust X offset display [core]");
+
+static unsigned int adjust_y_offset = -1;
+module_param(adjust_y_offset, int, 0644);
+MODULE_PARM_DESC(adjust_y_offset, "adjust Y offset display [core]");
+
+
+#define	ENABLE_HEXDUMP	0	/* Enable if you need it */
+
+
+#ifdef USBVISION_DEBUG
+	#define PDEBUG(level, fmt, args...) { \
+		if (core_debug & (level)) \
+			printk(KERN_INFO KBUILD_MODNAME ":[%s:%d] " fmt, \
+				__func__, __LINE__ , ## args); \
+	}
+#else
+	#define PDEBUG(level, fmt, args...) do {} while (0)
+#endif
+
+#define DBG_HEADER	(1 << 0)
+#define DBG_IRQ		(1 << 1)
+#define DBG_ISOC	(1 << 2)
+#define DBG_PARSE	(1 << 3)
+#define DBG_SCRATCH	(1 << 4)
+#define DBG_FUNC	(1 << 5)
+
+/* The value of 'scratch_buf_size' affects quality of the picture
+ * in many ways. Shorter buffers may cause loss of data when client
+ * is too slow. Larger buffers are memory-consuming and take longer
+ * to work with. This setting can be adjusted, but the default value
+ * should be OK for most desktop users.
+ */
+#define DEFAULT_SCRATCH_BUF_SIZE	(0x20000)		/* 128kB memory scratch buffer */
+static const int scratch_buf_size = DEFAULT_SCRATCH_BUF_SIZE;
+
+/* Function prototypes */
+static int usbvision_request_intra(struct usb_usbvision *usbvision);
+static int usbvision_unrequest_intra(struct usb_usbvision *usbvision);
+static int usbvision_adjust_compression(struct usb_usbvision *usbvision);
+static int usbvision_measure_bandwidth(struct usb_usbvision *usbvision);
+
+/*******************************/
+/* Memory management functions */
+/*******************************/
+
+/*
+ * Here we want the physical address of the memory.
+ * This is used when initializing the contents of the area.
+ */
+
+static void *usbvision_rvmalloc(unsigned long size)
+{
+	void *mem;
+	unsigned long adr;
+
+	size = PAGE_ALIGN(size);
+	mem = vmalloc_32(size);
+	if (!mem)
+		return NULL;
+
+	memset(mem, 0, size); /* Clear the ram out, no junk to the user */
+	adr = (unsigned long) mem;
+	while (size > 0) {
+		SetPageReserved(vmalloc_to_page((void *)adr));
+		adr += PAGE_SIZE;
+		size -= PAGE_SIZE;
+	}
+
+	return mem;
+}
+
+static void usbvision_rvfree(void *mem, unsigned long size)
+{
+	unsigned long adr;
+
+	if (!mem)
+		return;
+
+	size = PAGE_ALIGN(size);
+
+	adr = (unsigned long) mem;
+	while ((long) size > 0) {
+		ClearPageReserved(vmalloc_to_page((void *)adr));
+		adr += PAGE_SIZE;
+		size -= PAGE_SIZE;
+	}
+
+	vfree(mem);
+}
+
+
+#if ENABLE_HEXDUMP
+static void usbvision_hexdump(const unsigned char *data, int len)
+{
+	char tmp[80];
+	int i, k;
+
+	for (i = k = 0; len > 0; i++, len--) {
+		if (i > 0 && (i % 16 == 0)) {
+			printk("%s\n", tmp);
+			k = 0;
+		}
+		k += sprintf(&tmp[k], "%02x ", data[i]);
+	}
+	if (k > 0)
+		printk(KERN_CONT "%s\n", tmp);
+}
+#endif
+
+/********************************
+ * scratch ring buffer handling
+ ********************************/
+static int scratch_len(struct usb_usbvision *usbvision)    /* This returns the amount of data actually in the buffer */
+{
+	int len = usbvision->scratch_write_ptr - usbvision->scratch_read_ptr;
+
+	if (len < 0)
+		len += scratch_buf_size;
+	PDEBUG(DBG_SCRATCH, "scratch_len() = %d\n", len);
+
+	return len;
+}
+
+
+/* This returns the free space left in the buffer */
+static int scratch_free(struct usb_usbvision *usbvision)
+{
+	int free = usbvision->scratch_read_ptr - usbvision->scratch_write_ptr;
+	if (free <= 0)
+		free += scratch_buf_size;
+	if (free) {
+		free -= 1;							/* at least one byte in the buffer must */
+										/* left blank, otherwise there is no chance to differ between full and empty */
+	}
+	PDEBUG(DBG_SCRATCH, "return %d\n", free);
+
+	return free;
+}
+
+
+/* This puts data into the buffer */
+static int scratch_put(struct usb_usbvision *usbvision, unsigned char *data,
+		       int len)
+{
+	int len_part;
+
+	if (usbvision->scratch_write_ptr + len < scratch_buf_size) {
+		memcpy(usbvision->scratch + usbvision->scratch_write_ptr, data, len);
+		usbvision->scratch_write_ptr += len;
+	} else {
+		len_part = scratch_buf_size - usbvision->scratch_write_ptr;
+		memcpy(usbvision->scratch + usbvision->scratch_write_ptr, data, len_part);
+		if (len == len_part) {
+			usbvision->scratch_write_ptr = 0;			/* just set write_ptr to zero */
+		} else {
+			memcpy(usbvision->scratch, data + len_part, len - len_part);
+			usbvision->scratch_write_ptr = len - len_part;
+		}
+	}
+
+	PDEBUG(DBG_SCRATCH, "len=%d, new write_ptr=%d\n", len, usbvision->scratch_write_ptr);
+
+	return len;
+}
+
+/* This marks the write_ptr as position of new frame header */
+static void scratch_mark_header(struct usb_usbvision *usbvision)
+{
+	PDEBUG(DBG_SCRATCH, "header at write_ptr=%d\n", usbvision->scratch_headermarker_write_ptr);
+
+	usbvision->scratch_headermarker[usbvision->scratch_headermarker_write_ptr] =
+				usbvision->scratch_write_ptr;
+	usbvision->scratch_headermarker_write_ptr += 1;
+	usbvision->scratch_headermarker_write_ptr %= USBVISION_NUM_HEADERMARKER;
+}
+
+/* This gets data from the buffer at the given "ptr" position */
+static int scratch_get_extra(struct usb_usbvision *usbvision,
+			     unsigned char *data, int *ptr, int len)
+{
+	int len_part;
+
+	if (*ptr + len < scratch_buf_size) {
+		memcpy(data, usbvision->scratch + *ptr, len);
+		*ptr += len;
+	} else {
+		len_part = scratch_buf_size - *ptr;
+		memcpy(data, usbvision->scratch + *ptr, len_part);
+		if (len == len_part) {
+			*ptr = 0;							/* just set the y_ptr to zero */
+		} else {
+			memcpy(data + len_part, usbvision->scratch, len - len_part);
+			*ptr = len - len_part;
+		}
+	}
+
+	PDEBUG(DBG_SCRATCH, "len=%d, new ptr=%d\n", len, *ptr);
+
+	return len;
+}
+
+
+/* This sets the scratch extra read pointer */
+static void scratch_set_extra_ptr(struct usb_usbvision *usbvision, int *ptr,
+				  int len)
+{
+	*ptr = (usbvision->scratch_read_ptr + len) % scratch_buf_size;
+
+	PDEBUG(DBG_SCRATCH, "ptr=%d\n", *ptr);
+}
+
+
+/* This increments the scratch extra read pointer */
+static void scratch_inc_extra_ptr(int *ptr, int len)
+{
+	*ptr = (*ptr + len) % scratch_buf_size;
+
+	PDEBUG(DBG_SCRATCH, "ptr=%d\n", *ptr);
+}
+
+
+/* This gets data from the buffer */
+static int scratch_get(struct usb_usbvision *usbvision, unsigned char *data,
+		       int len)
+{
+	int len_part;
+
+	if (usbvision->scratch_read_ptr + len < scratch_buf_size) {
+		memcpy(data, usbvision->scratch + usbvision->scratch_read_ptr, len);
+		usbvision->scratch_read_ptr += len;
+	} else {
+		len_part = scratch_buf_size - usbvision->scratch_read_ptr;
+		memcpy(data, usbvision->scratch + usbvision->scratch_read_ptr, len_part);
+		if (len == len_part) {
+			usbvision->scratch_read_ptr = 0;				/* just set the read_ptr to zero */
+		} else {
+			memcpy(data + len_part, usbvision->scratch, len - len_part);
+			usbvision->scratch_read_ptr = len - len_part;
+		}
+	}
+
+	PDEBUG(DBG_SCRATCH, "len=%d, new read_ptr=%d\n", len, usbvision->scratch_read_ptr);
+
+	return len;
+}
+
+
+/* This sets read pointer to next header and returns it */
+static int scratch_get_header(struct usb_usbvision *usbvision,
+			      struct usbvision_frame_header *header)
+{
+	int err_code = 0;
+
+	PDEBUG(DBG_SCRATCH, "from read_ptr=%d", usbvision->scratch_headermarker_read_ptr);
+
+	while (usbvision->scratch_headermarker_write_ptr -
+		usbvision->scratch_headermarker_read_ptr != 0) {
+		usbvision->scratch_read_ptr =
+			usbvision->scratch_headermarker[usbvision->scratch_headermarker_read_ptr];
+		usbvision->scratch_headermarker_read_ptr += 1;
+		usbvision->scratch_headermarker_read_ptr %= USBVISION_NUM_HEADERMARKER;
+		scratch_get(usbvision, (unsigned char *)header, USBVISION_HEADER_LENGTH);
+		if ((header->magic_1 == USBVISION_MAGIC_1)
+			 && (header->magic_2 == USBVISION_MAGIC_2)
+			 && (header->header_length == USBVISION_HEADER_LENGTH)) {
+			err_code = USBVISION_HEADER_LENGTH;
+			header->frame_width  = header->frame_width_lo  + (header->frame_width_hi << 8);
+			header->frame_height = header->frame_height_lo + (header->frame_height_hi << 8);
+			break;
+		}
+	}
+
+	return err_code;
+}
+
+
+/* This removes len bytes of old data from the buffer */
+static void scratch_rm_old(struct usb_usbvision *usbvision, int len)
+{
+	usbvision->scratch_read_ptr += len;
+	usbvision->scratch_read_ptr %= scratch_buf_size;
+	PDEBUG(DBG_SCRATCH, "read_ptr is now %d\n", usbvision->scratch_read_ptr);
+}
+
+
+/* This resets the buffer - kills all data in it too */
+static void scratch_reset(struct usb_usbvision *usbvision)
+{
+	PDEBUG(DBG_SCRATCH, "\n");
+
+	usbvision->scratch_read_ptr = 0;
+	usbvision->scratch_write_ptr = 0;
+	usbvision->scratch_headermarker_read_ptr = 0;
+	usbvision->scratch_headermarker_write_ptr = 0;
+	usbvision->isocstate = isoc_state_no_frame;
+}
+
+int usbvision_scratch_alloc(struct usb_usbvision *usbvision)
+{
+	usbvision->scratch = vmalloc_32(scratch_buf_size);
+	scratch_reset(usbvision);
+	if (usbvision->scratch == NULL) {
+		dev_err(&usbvision->dev->dev,
+			"%s: unable to allocate %d bytes for scratch\n",
+				__func__, scratch_buf_size);
+		return -ENOMEM;
+	}
+	return 0;
+}
+
+void usbvision_scratch_free(struct usb_usbvision *usbvision)
+{
+	vfree(usbvision->scratch);
+	usbvision->scratch = NULL;
+}
+
+/*
+ * usbvision_decompress_alloc()
+ *
+ * allocates intermediate buffer for decompression
+ */
+int usbvision_decompress_alloc(struct usb_usbvision *usbvision)
+{
+	int IFB_size = MAX_FRAME_WIDTH * MAX_FRAME_HEIGHT * 3 / 2;
+
+	usbvision->intra_frame_buffer = vmalloc_32(IFB_size);
+	if (usbvision->intra_frame_buffer == NULL) {
+		dev_err(&usbvision->dev->dev,
+			"%s: unable to allocate %d for compr. frame buffer\n",
+				__func__, IFB_size);
+		return -ENOMEM;
+	}
+	return 0;
+}
+
+/*
+ * usbvision_decompress_free()
+ *
+ * frees intermediate buffer for decompression
+ */
+void usbvision_decompress_free(struct usb_usbvision *usbvision)
+{
+	vfree(usbvision->intra_frame_buffer);
+	usbvision->intra_frame_buffer = NULL;
+
+}
+
+/************************************************************
+ * Here comes the data parsing stuff that is run as interrupt
+ ************************************************************/
+/*
+ * usbvision_find_header()
+ *
+ * Locate one of supported header markers in the scratch buffer.
+ */
+static enum parse_state usbvision_find_header(struct usb_usbvision *usbvision)
+{
+	struct usbvision_frame *frame;
+	int found_header = 0;
+
+	frame = usbvision->cur_frame;
+
+	while (scratch_get_header(usbvision, &frame->isoc_header) == USBVISION_HEADER_LENGTH) {
+		/* found header in scratch */
+		PDEBUG(DBG_HEADER, "found header: 0x%02x%02x %d %d %d %d %#x 0x%02x %u %u",
+				frame->isoc_header.magic_2,
+				frame->isoc_header.magic_1,
+				frame->isoc_header.header_length,
+				frame->isoc_header.frame_num,
+				frame->isoc_header.frame_phase,
+				frame->isoc_header.frame_latency,
+				frame->isoc_header.data_format,
+				frame->isoc_header.format_param,
+				frame->isoc_header.frame_width,
+				frame->isoc_header.frame_height);
+
+		if (usbvision->request_intra) {
+			if (frame->isoc_header.format_param & 0x80) {
+				found_header = 1;
+				usbvision->last_isoc_frame_num = -1; /* do not check for lost frames this time */
+				usbvision_unrequest_intra(usbvision);
+				break;
+			}
+		} else {
+			found_header = 1;
+			break;
+		}
+	}
+
+	if (found_header) {
+		frame->frmwidth = frame->isoc_header.frame_width * usbvision->stretch_width;
+		frame->frmheight = frame->isoc_header.frame_height * usbvision->stretch_height;
+		frame->v4l2_linesize = (frame->frmwidth * frame->v4l2_format.depth) >> 3;
+	} else { /* no header found */
+		PDEBUG(DBG_HEADER, "skipping scratch data, no header");
+		scratch_reset(usbvision);
+		return parse_state_end_parse;
+	}
+
+	/* found header */
+	if (frame->isoc_header.data_format == ISOC_MODE_COMPRESS) {
+		/* check isoc_header.frame_num for lost frames */
+		if (usbvision->last_isoc_frame_num >= 0) {
+			if (((usbvision->last_isoc_frame_num + 1) % 32) != frame->isoc_header.frame_num) {
+				/* unexpected frame drop: need to request new intra frame */
+				PDEBUG(DBG_HEADER, "Lost frame before %d on USB", frame->isoc_header.frame_num);
+				usbvision_request_intra(usbvision);
+				return parse_state_next_frame;
+			}
+		}
+		usbvision->last_isoc_frame_num = frame->isoc_header.frame_num;
+	}
+	usbvision->header_count++;
+	frame->scanstate = scan_state_lines;
+	frame->curline = 0;
+
+	return parse_state_continue;
+}
+
+static enum parse_state usbvision_parse_lines_422(struct usb_usbvision *usbvision,
+					   long *pcopylen)
+{
+	volatile struct usbvision_frame *frame;
+	unsigned char *f;
+	int len;
+	int i;
+	unsigned char yuyv[4] = { 180, 128, 10, 128 }; /* YUV components */
+	unsigned char rv, gv, bv;	/* RGB components */
+	int clipmask_index, bytes_per_pixel;
+	int stretch_bytes, clipmask_add;
+
+	frame  = usbvision->cur_frame;
+	f = frame->data + (frame->v4l2_linesize * frame->curline);
+
+	/* Make sure there's enough data for the entire line */
+	len = (frame->isoc_header.frame_width * 2) + 5;
+	if (scratch_len(usbvision) < len) {
+		PDEBUG(DBG_PARSE, "out of data in line %d, need %u.\n", frame->curline, len);
+		return parse_state_out;
+	}
+
+	if ((frame->curline + 1) >= frame->frmheight)
+		return parse_state_next_frame;
+
+	bytes_per_pixel = frame->v4l2_format.bytes_per_pixel;
+	stretch_bytes = (usbvision->stretch_width - 1) * bytes_per_pixel;
+	clipmask_index = frame->curline * MAX_FRAME_WIDTH;
+	clipmask_add = usbvision->stretch_width;
+
+	for (i = 0; i < frame->frmwidth; i += (2 * usbvision->stretch_width)) {
+		scratch_get(usbvision, &yuyv[0], 4);
+
+		if (frame->v4l2_format.format == V4L2_PIX_FMT_YUYV) {
+			*f++ = yuyv[0]; /* Y */
+			*f++ = yuyv[3]; /* U */
+		} else {
+			YUV_TO_RGB_BY_THE_BOOK(yuyv[0], yuyv[1], yuyv[3], rv, gv, bv);
+			switch (frame->v4l2_format.format) {
+			case V4L2_PIX_FMT_RGB565:
+				*f++ = (0x1F & rv) |
+					(0xE0 & (gv << 5));
+				*f++ = (0x07 & (gv >> 3)) |
+					(0xF8 &  bv);
+				break;
+			case V4L2_PIX_FMT_RGB24:
+				*f++ = rv;
+				*f++ = gv;
+				*f++ = bv;
+				break;
+			case V4L2_PIX_FMT_RGB32:
+				*f++ = rv;
+				*f++ = gv;
+				*f++ = bv;
+				f++;
+				break;
+			case V4L2_PIX_FMT_RGB555:
+				*f++ = (0x1F & rv) |
+					(0xE0 & (gv << 5));
+				*f++ = (0x03 & (gv >> 3)) |
+					(0x7C & (bv << 2));
+				break;
+			}
+		}
+		clipmask_index += clipmask_add;
+		f += stretch_bytes;
+
+		if (frame->v4l2_format.format == V4L2_PIX_FMT_YUYV) {
+			*f++ = yuyv[2]; /* Y */
+			*f++ = yuyv[1]; /* V */
+		} else {
+			YUV_TO_RGB_BY_THE_BOOK(yuyv[2], yuyv[1], yuyv[3], rv, gv, bv);
+			switch (frame->v4l2_format.format) {
+			case V4L2_PIX_FMT_RGB565:
+				*f++ = (0x1F & rv) |
+					(0xE0 & (gv << 5));
+				*f++ = (0x07 & (gv >> 3)) |
+					(0xF8 &  bv);
+				break;
+			case V4L2_PIX_FMT_RGB24:
+				*f++ = rv;
+				*f++ = gv;
+				*f++ = bv;
+				break;
+			case V4L2_PIX_FMT_RGB32:
+				*f++ = rv;
+				*f++ = gv;
+				*f++ = bv;
+				f++;
+				break;
+			case V4L2_PIX_FMT_RGB555:
+				*f++ = (0x1F & rv) |
+					(0xE0 & (gv << 5));
+				*f++ = (0x03 & (gv >> 3)) |
+					(0x7C & (bv << 2));
+				break;
+			}
+		}
+		clipmask_index += clipmask_add;
+		f += stretch_bytes;
+	}
+
+	frame->curline += usbvision->stretch_height;
+	*pcopylen += frame->v4l2_linesize * usbvision->stretch_height;
+
+	if (frame->curline >= frame->frmheight)
+		return parse_state_next_frame;
+	return parse_state_continue;
+}
+
+/* The decompression routine  */
+static int usbvision_decompress(struct usb_usbvision *usbvision, unsigned char *compressed,
+								unsigned char *decompressed, int *start_pos,
+								int *block_typestart_pos, int len)
+{
+	int rest_pixel, idx, pos, extra_pos, block_len, block_type_pos, block_type_len;
+	unsigned char block_byte, block_code, block_type, block_type_byte, integrator;
+
+	integrator = 0;
+	pos = *start_pos;
+	block_type_pos = *block_typestart_pos;
+	extra_pos = pos;
+	block_len = 0;
+	block_byte = 0;
+	block_code = 0;
+	block_type = 0;
+	block_type_byte = 0;
+	block_type_len = 0;
+	rest_pixel = len;
+
+	for (idx = 0; idx < len; idx++) {
+		if (block_len == 0) {
+			if (block_type_len == 0) {
+				block_type_byte = compressed[block_type_pos];
+				block_type_pos++;
+				block_type_len = 4;
+			}
+			block_type = (block_type_byte & 0xC0) >> 6;
+
+			/* statistic: */
+			usbvision->compr_block_types[block_type]++;
+
+			pos = extra_pos;
+			if (block_type == 0) {
+				if (rest_pixel >= 24) {
+					idx += 23;
+					rest_pixel -= 24;
+					integrator = decompressed[idx];
+				} else {
+					idx += rest_pixel - 1;
+					rest_pixel = 0;
+				}
+			} else {
+				block_code = compressed[pos];
+				pos++;
+				if (rest_pixel >= 24)
+					block_len  = 24;
+				else
+					block_len = rest_pixel;
+				rest_pixel -= block_len;
+				extra_pos = pos + (block_len / 4);
+			}
+			block_type_byte <<= 2;
+			block_type_len -= 1;
+		}
+		if (block_len > 0) {
+			if ((block_len % 4) == 0) {
+				block_byte = compressed[pos];
+				pos++;
+			}
+			if (block_type == 1) /* inter Block */
+				integrator = decompressed[idx];
+			switch (block_byte & 0xC0) {
+			case 0x03 << 6:
+				integrator += compressed[extra_pos];
+				extra_pos++;
+				break;
+			case 0x02 << 6:
+				integrator += block_code;
+				break;
+			case 0x00:
+				integrator -= block_code;
+				break;
+			}
+			decompressed[idx] = integrator;
+			block_byte <<= 2;
+			block_len -= 1;
+		}
+	}
+	*start_pos = extra_pos;
+	*block_typestart_pos = block_type_pos;
+	return idx;
+}
+
+
+/*
+ * usbvision_parse_compress()
+ *
+ * Parse compressed frame from the scratch buffer, put
+ * decoded RGB value into the current frame buffer and add the written
+ * number of bytes (RGB) to the *pcopylen.
+ *
+ */
+static enum parse_state usbvision_parse_compress(struct usb_usbvision *usbvision,
+					   long *pcopylen)
+{
+#define USBVISION_STRIP_MAGIC		0x5A
+#define USBVISION_STRIP_LEN_MAX		400
+#define USBVISION_STRIP_HEADER_LEN	3
+
+	struct usbvision_frame *frame;
+	unsigned char *f, *u = NULL, *v = NULL;
+	unsigned char strip_data[USBVISION_STRIP_LEN_MAX];
+	unsigned char strip_header[USBVISION_STRIP_HEADER_LEN];
+	int idx, idx_end, strip_len, strip_ptr, startblock_pos, block_pos, block_type_pos;
+	int clipmask_index;
+	int image_size;
+	unsigned char rv, gv, bv;
+	static unsigned char *Y, *U, *V;
+
+	frame = usbvision->cur_frame;
+	image_size = frame->frmwidth * frame->frmheight;
+	if ((frame->v4l2_format.format == V4L2_PIX_FMT_YUV422P) ||
+	    (frame->v4l2_format.format == V4L2_PIX_FMT_YVU420)) {       /* this is a planar format */
+		/* ... v4l2_linesize not used here. */
+		f = frame->data + (frame->width * frame->curline);
+	} else
+		f = frame->data + (frame->v4l2_linesize * frame->curline);
+
+	if (frame->v4l2_format.format == V4L2_PIX_FMT_YUYV) { /* initialise u and v pointers */
+		/* get base of u and b planes add halfoffset */
+		u = frame->data
+			+ image_size
+			+ (frame->frmwidth >> 1) * frame->curline;
+		v = u + (image_size >> 1);
+	} else if (frame->v4l2_format.format == V4L2_PIX_FMT_YVU420) {
+		v = frame->data + image_size + ((frame->curline * (frame->width)) >> 2);
+		u = v + (image_size >> 2);
+	}
+
+	if (frame->curline == 0)
+		usbvision_adjust_compression(usbvision);
+
+	if (scratch_len(usbvision) < USBVISION_STRIP_HEADER_LEN)
+		return parse_state_out;
+
+	/* get strip header without changing the scratch_read_ptr */
+	scratch_set_extra_ptr(usbvision, &strip_ptr, 0);
+	scratch_get_extra(usbvision, &strip_header[0], &strip_ptr,
+				USBVISION_STRIP_HEADER_LEN);
+
+	if (strip_header[0] != USBVISION_STRIP_MAGIC) {
+		/* wrong strip magic */
+		usbvision->strip_magic_errors++;
+		return parse_state_next_frame;
+	}
+
+	if (frame->curline != (int)strip_header[2]) {
+		/* line number mismatch error */
+		usbvision->strip_line_number_errors++;
+	}
+
+	strip_len = 2 * (unsigned int)strip_header[1];
+	if (strip_len > USBVISION_STRIP_LEN_MAX) {
+		/* strip overrun */
+		/* I think this never happens */
+		usbvision_request_intra(usbvision);
+	}
+
+	if (scratch_len(usbvision) < strip_len) {
+		/* there is not enough data for the strip */
+		return parse_state_out;
+	}
+
+	if (usbvision->intra_frame_buffer) {
+		Y = usbvision->intra_frame_buffer + frame->frmwidth * frame->curline;
+		U = usbvision->intra_frame_buffer + image_size + (frame->frmwidth / 2) * (frame->curline / 2);
+		V = usbvision->intra_frame_buffer + image_size / 4 * 5 + (frame->frmwidth / 2) * (frame->curline / 2);
+	} else {
+		return parse_state_next_frame;
+	}
+
+	clipmask_index = frame->curline * MAX_FRAME_WIDTH;
+
+	scratch_get(usbvision, strip_data, strip_len);
+
+	idx_end = frame->frmwidth;
+	block_type_pos = USBVISION_STRIP_HEADER_LEN;
+	startblock_pos = block_type_pos + (idx_end - 1) / 96 + (idx_end / 2 - 1) / 96 + 2;
+	block_pos = startblock_pos;
+
+	usbvision->block_pos = block_pos;
+
+	usbvision_decompress(usbvision, strip_data, Y, &block_pos, &block_type_pos, idx_end);
+	if (strip_len > usbvision->max_strip_len)
+		usbvision->max_strip_len = strip_len;
+
+	if (frame->curline % 2)
+		usbvision_decompress(usbvision, strip_data, V, &block_pos, &block_type_pos, idx_end / 2);
+	else
+		usbvision_decompress(usbvision, strip_data, U, &block_pos, &block_type_pos, idx_end / 2);
+
+	if (block_pos > usbvision->comprblock_pos)
+		usbvision->comprblock_pos = block_pos;
+	if (block_pos > strip_len)
+		usbvision->strip_len_errors++;
+
+	for (idx = 0; idx < idx_end; idx++) {
+		if (frame->v4l2_format.format == V4L2_PIX_FMT_YUYV) {
+			*f++ = Y[idx];
+			*f++ = idx & 0x01 ? U[idx / 2] : V[idx / 2];
+		} else if (frame->v4l2_format.format == V4L2_PIX_FMT_YUV422P) {
+			*f++ = Y[idx];
+			if (idx & 0x01)
+				*u++ = U[idx >> 1];
+			else
+				*v++ = V[idx >> 1];
+		} else if (frame->v4l2_format.format == V4L2_PIX_FMT_YVU420) {
+			*f++ = Y[idx];
+			if (!((idx & 0x01) | (frame->curline & 0x01))) {
+				/* only need do this for 1 in 4 pixels */
+				/* intraframe buffer is YUV420 format */
+				*u++ = U[idx >> 1];
+				*v++ = V[idx >> 1];
+			}
+		} else {
+			YUV_TO_RGB_BY_THE_BOOK(Y[idx], U[idx / 2], V[idx / 2], rv, gv, bv);
+			switch (frame->v4l2_format.format) {
+			case V4L2_PIX_FMT_GREY:
+				*f++ = Y[idx];
+				break;
+			case V4L2_PIX_FMT_RGB555:
+				*f++ = (0x1F & rv) |
+					(0xE0 & (gv << 5));
+				*f++ = (0x03 & (gv >> 3)) |
+					(0x7C & (bv << 2));
+				break;
+			case V4L2_PIX_FMT_RGB565:
+				*f++ = (0x1F & rv) |
+					(0xE0 & (gv << 5));
+				*f++ = (0x07 & (gv >> 3)) |
+					(0xF8 & bv);
+				break;
+			case V4L2_PIX_FMT_RGB24:
+				*f++ = rv;
+				*f++ = gv;
+				*f++ = bv;
+				break;
+			case V4L2_PIX_FMT_RGB32:
+				*f++ = rv;
+				*f++ = gv;
+				*f++ = bv;
+				f++;
+				break;
+			}
+		}
+		clipmask_index++;
+	}
+	/* Deal with non-integer no. of bytes for YUV420P */
+	if (frame->v4l2_format.format != V4L2_PIX_FMT_YVU420)
+		*pcopylen += frame->v4l2_linesize;
+	else
+		*pcopylen += frame->curline & 0x01 ? frame->v4l2_linesize : frame->v4l2_linesize << 1;
+
+	frame->curline += 1;
+
+	if (frame->curline >= frame->frmheight)
+		return parse_state_next_frame;
+	return parse_state_continue;
+
+}
+
+
+/*
+ * usbvision_parse_lines_420()
+ *
+ * Parse two lines from the scratch buffer, put
+ * decoded RGB value into the current frame buffer and add the written
+ * number of bytes (RGB) to the *pcopylen.
+ *
+ */
+static enum parse_state usbvision_parse_lines_420(struct usb_usbvision *usbvision,
+					   long *pcopylen)
+{
+	struct usbvision_frame *frame;
+	unsigned char *f_even = NULL, *f_odd = NULL;
+	unsigned int pixel_per_line, block;
+	int pixel, block_split;
+	int y_ptr, u_ptr, v_ptr, y_odd_offset;
+	const int y_block_size = 128;
+	const int uv_block_size = 64;
+	const int sub_block_size = 32;
+	const int y_step[] = { 0, 0, 0, 2 }, y_step_size = 4;
+	const int uv_step[] = { 0, 0, 0, 4 }, uv_step_size = 4;
+	unsigned char y[2], u, v;	/* YUV components */
+	int y_, u_, v_, vb, uvg, ur;
+	int r_, g_, b_;			/* RGB components */
+	unsigned char g;
+	int clipmask_even_index, clipmask_odd_index, bytes_per_pixel;
+	int clipmask_add, stretch_bytes;
+
+	frame  = usbvision->cur_frame;
+	f_even = frame->data + (frame->v4l2_linesize * frame->curline);
+	f_odd  = f_even + frame->v4l2_linesize * usbvision->stretch_height;
+
+	/* Make sure there's enough data for the entire line */
+	/* In this mode usbvision transfer 3 bytes for every 2 pixels */
+	/* I need two lines to decode the color */
+	bytes_per_pixel = frame->v4l2_format.bytes_per_pixel;
+	stretch_bytes = (usbvision->stretch_width - 1) * bytes_per_pixel;
+	clipmask_even_index = frame->curline * MAX_FRAME_WIDTH;
+	clipmask_odd_index  = clipmask_even_index + MAX_FRAME_WIDTH;
+	clipmask_add = usbvision->stretch_width;
+	pixel_per_line = frame->isoc_header.frame_width;
+
+	if (scratch_len(usbvision) < (int)pixel_per_line * 3) {
+		/* printk(KERN_DEBUG "out of data, need %d\n", len); */
+		return parse_state_out;
+	}
+
+	if ((frame->curline + 1) >= frame->frmheight)
+		return parse_state_next_frame;
+
+	block_split = (pixel_per_line%y_block_size) ? 1 : 0;	/* are some blocks splitted into different lines? */
+
+	y_odd_offset = (pixel_per_line / y_block_size) * (y_block_size + uv_block_size)
+			+ block_split * uv_block_size;
+
+	scratch_set_extra_ptr(usbvision, &y_ptr, y_odd_offset);
+	scratch_set_extra_ptr(usbvision, &u_ptr, y_block_size);
+	scratch_set_extra_ptr(usbvision, &v_ptr, y_odd_offset
+			+ (4 - block_split) * sub_block_size);
+
+	for (block = 0; block < (pixel_per_line / sub_block_size); block++) {
+		for (pixel = 0; pixel < sub_block_size; pixel += 2) {
+			scratch_get(usbvision, &y[0], 2);
+			scratch_get_extra(usbvision, &u, &u_ptr, 1);
+			scratch_get_extra(usbvision, &v, &v_ptr, 1);
+
+			/* I don't use the YUV_TO_RGB macro for better performance */
+			v_ = v - 128;
+			u_ = u - 128;
+			vb = 132252 * v_;
+			uvg = -53281 * u_ - 25625 * v_;
+			ur = 104595 * u_;
+
+			if (frame->v4l2_format.format == V4L2_PIX_FMT_YUYV) {
+				*f_even++ = y[0];
+				*f_even++ = v;
+			} else {
+				y_ = 76284 * (y[0] - 16);
+
+				b_ = (y_ + vb) >> 16;
+				g_ = (y_ + uvg) >> 16;
+				r_ = (y_ + ur) >> 16;
+
+				switch (frame->v4l2_format.format) {
+				case V4L2_PIX_FMT_RGB565:
+					g = LIMIT_RGB(g_);
+					*f_even++ =
+						(0x1F & LIMIT_RGB(r_)) |
+						(0xE0 & (g << 5));
+					*f_even++ =
+						(0x07 & (g >> 3)) |
+						(0xF8 &  LIMIT_RGB(b_));
+					break;
+				case V4L2_PIX_FMT_RGB24:
+					*f_even++ = LIMIT_RGB(r_);
+					*f_even++ = LIMIT_RGB(g_);
+					*f_even++ = LIMIT_RGB(b_);
+					break;
+				case V4L2_PIX_FMT_RGB32:
+					*f_even++ = LIMIT_RGB(r_);
+					*f_even++ = LIMIT_RGB(g_);
+					*f_even++ = LIMIT_RGB(b_);
+					f_even++;
+					break;
+				case V4L2_PIX_FMT_RGB555:
+					g = LIMIT_RGB(g_);
+					*f_even++ = (0x1F & LIMIT_RGB(r_)) |
+						(0xE0 & (g << 5));
+					*f_even++ = (0x03 & (g >> 3)) |
+						(0x7C & (LIMIT_RGB(b_) << 2));
+					break;
+				}
+			}
+			clipmask_even_index += clipmask_add;
+			f_even += stretch_bytes;
+
+			if (frame->v4l2_format.format == V4L2_PIX_FMT_YUYV) {
+				*f_even++ = y[1];
+				*f_even++ = u;
+			} else {
+				y_ = 76284 * (y[1] - 16);
+
+				b_ = (y_ + vb) >> 16;
+				g_ = (y_ + uvg) >> 16;
+				r_ = (y_ + ur) >> 16;
+
+				switch (frame->v4l2_format.format) {
+				case V4L2_PIX_FMT_RGB565:
+					g = LIMIT_RGB(g_);
+					*f_even++ =
+						(0x1F & LIMIT_RGB(r_)) |
+						(0xE0 & (g << 5));
+					*f_even++ =
+						(0x07 & (g >> 3)) |
+						(0xF8 &  LIMIT_RGB(b_));
+					break;
+				case V4L2_PIX_FMT_RGB24:
+					*f_even++ = LIMIT_RGB(r_);
+					*f_even++ = LIMIT_RGB(g_);
+					*f_even++ = LIMIT_RGB(b_);
+					break;
+				case V4L2_PIX_FMT_RGB32:
+					*f_even++ = LIMIT_RGB(r_);
+					*f_even++ = LIMIT_RGB(g_);
+					*f_even++ = LIMIT_RGB(b_);
+					f_even++;
+					break;
+				case V4L2_PIX_FMT_RGB555:
+					g = LIMIT_RGB(g_);
+					*f_even++ = (0x1F & LIMIT_RGB(r_)) |
+						(0xE0 & (g << 5));
+					*f_even++ = (0x03 & (g >> 3)) |
+						(0x7C & (LIMIT_RGB(b_) << 2));
+					break;
+				}
+			}
+			clipmask_even_index += clipmask_add;
+			f_even += stretch_bytes;
+
+			scratch_get_extra(usbvision, &y[0], &y_ptr, 2);
+
+			if (frame->v4l2_format.format == V4L2_PIX_FMT_YUYV) {
+				*f_odd++ = y[0];
+				*f_odd++ = v;
+			} else {
+				y_ = 76284 * (y[0] - 16);
+
+				b_ = (y_ + vb) >> 16;
+				g_ = (y_ + uvg) >> 16;
+				r_ = (y_ + ur) >> 16;
+
+				switch (frame->v4l2_format.format) {
+				case V4L2_PIX_FMT_RGB565:
+					g = LIMIT_RGB(g_);
+					*f_odd++ =
+						(0x1F & LIMIT_RGB(r_)) |
+						(0xE0 & (g << 5));
+					*f_odd++ =
+						(0x07 & (g >> 3)) |
+						(0xF8 &  LIMIT_RGB(b_));
+					break;
+				case V4L2_PIX_FMT_RGB24:
+					*f_odd++ = LIMIT_RGB(r_);
+					*f_odd++ = LIMIT_RGB(g_);
+					*f_odd++ = LIMIT_RGB(b_);
+					break;
+				case V4L2_PIX_FMT_RGB32:
+					*f_odd++ = LIMIT_RGB(r_);
+					*f_odd++ = LIMIT_RGB(g_);
+					*f_odd++ = LIMIT_RGB(b_);
+					f_odd++;
+					break;
+				case V4L2_PIX_FMT_RGB555:
+					g = LIMIT_RGB(g_);
+					*f_odd++ = (0x1F & LIMIT_RGB(r_)) |
+						(0xE0 & (g << 5));
+					*f_odd++ = (0x03 & (g >> 3)) |
+						(0x7C & (LIMIT_RGB(b_) << 2));
+					break;
+				}
+			}
+			clipmask_odd_index += clipmask_add;
+			f_odd += stretch_bytes;
+
+			if (frame->v4l2_format.format == V4L2_PIX_FMT_YUYV) {
+				*f_odd++ = y[1];
+				*f_odd++ = u;
+			} else {
+				y_ = 76284 * (y[1] - 16);
+
+				b_ = (y_ + vb) >> 16;
+				g_ = (y_ + uvg) >> 16;
+				r_ = (y_ + ur) >> 16;
+
+				switch (frame->v4l2_format.format) {
+				case V4L2_PIX_FMT_RGB565:
+					g = LIMIT_RGB(g_);
+					*f_odd++ =
+						(0x1F & LIMIT_RGB(r_)) |
+						(0xE0 & (g << 5));
+					*f_odd++ =
+						(0x07 & (g >> 3)) |
+						(0xF8 &  LIMIT_RGB(b_));
+					break;
+				case V4L2_PIX_FMT_RGB24:
+					*f_odd++ = LIMIT_RGB(r_);
+					*f_odd++ = LIMIT_RGB(g_);
+					*f_odd++ = LIMIT_RGB(b_);
+					break;
+				case V4L2_PIX_FMT_RGB32:
+					*f_odd++ = LIMIT_RGB(r_);
+					*f_odd++ = LIMIT_RGB(g_);
+					*f_odd++ = LIMIT_RGB(b_);
+					f_odd++;
+					break;
+				case V4L2_PIX_FMT_RGB555:
+					g = LIMIT_RGB(g_);
+					*f_odd++ = (0x1F & LIMIT_RGB(r_)) |
+						(0xE0 & (g << 5));
+					*f_odd++ = (0x03 & (g >> 3)) |
+						(0x7C & (LIMIT_RGB(b_) << 2));
+					break;
+				}
+			}
+			clipmask_odd_index += clipmask_add;
+			f_odd += stretch_bytes;
+		}
+
+		scratch_rm_old(usbvision, y_step[block % y_step_size] * sub_block_size);
+		scratch_inc_extra_ptr(&y_ptr, y_step[(block + 2 * block_split) % y_step_size]
+				* sub_block_size);
+		scratch_inc_extra_ptr(&u_ptr, uv_step[block % uv_step_size]
+				* sub_block_size);
+		scratch_inc_extra_ptr(&v_ptr, uv_step[(block + 2 * block_split) % uv_step_size]
+				* sub_block_size);
+	}
+
+	scratch_rm_old(usbvision, pixel_per_line * 3 / 2
+			+ block_split * sub_block_size);
+
+	frame->curline += 2 * usbvision->stretch_height;
+	*pcopylen += frame->v4l2_linesize * 2 * usbvision->stretch_height;
+
+	if (frame->curline >= frame->frmheight)
+		return parse_state_next_frame;
+	return parse_state_continue;
+}
+
+/*
+ * usbvision_parse_data()
+ *
+ * Generic routine to parse the scratch buffer. It employs either
+ * usbvision_find_header() or usbvision_parse_lines() to do most
+ * of work.
+ *
+ */
+static void usbvision_parse_data(struct usb_usbvision *usbvision)
+{
+	struct usbvision_frame *frame;
+	enum parse_state newstate;
+	long copylen = 0;
+	unsigned long lock_flags;
+
+	frame = usbvision->cur_frame;
+
+	PDEBUG(DBG_PARSE, "parsing len=%d\n", scratch_len(usbvision));
+
+	while (1) {
+		newstate = parse_state_out;
+		if (scratch_len(usbvision)) {
+			if (frame->scanstate == scan_state_scanning) {
+				newstate = usbvision_find_header(usbvision);
+			} else if (frame->scanstate == scan_state_lines) {
+				if (usbvision->isoc_mode == ISOC_MODE_YUV420)
+					newstate = usbvision_parse_lines_420(usbvision, &copylen);
+				else if (usbvision->isoc_mode == ISOC_MODE_YUV422)
+					newstate = usbvision_parse_lines_422(usbvision, &copylen);
+				else if (usbvision->isoc_mode == ISOC_MODE_COMPRESS)
+					newstate = usbvision_parse_compress(usbvision, &copylen);
+			}
+		}
+		if (newstate == parse_state_continue)
+			continue;
+		if ((newstate == parse_state_next_frame) || (newstate == parse_state_out))
+			break;
+		return;	/* parse_state_end_parse */
+	}
+
+	if (newstate == parse_state_next_frame) {
+		frame->grabstate = frame_state_done;
+		v4l2_get_timestamp(&(frame->timestamp));
+		frame->sequence = usbvision->frame_num;
+
+		spin_lock_irqsave(&usbvision->queue_lock, lock_flags);
+		list_move_tail(&(frame->frame), &usbvision->outqueue);
+		usbvision->cur_frame = NULL;
+		spin_unlock_irqrestore(&usbvision->queue_lock, lock_flags);
+
+		usbvision->frame_num++;
+
+		/* This will cause the process to request another frame. */
+		if (waitqueue_active(&usbvision->wait_frame)) {
+			PDEBUG(DBG_PARSE, "Wake up !");
+			wake_up_interruptible(&usbvision->wait_frame);
+		}
+	} else {
+		frame->grabstate = frame_state_grabbing;
+	}
+
+	/* Update the frame's uncompressed length. */
+	frame->scanlength += copylen;
+}
+
+
+/*
+ * Make all of the blocks of data contiguous
+ */
+static int usbvision_compress_isochronous(struct usb_usbvision *usbvision,
+					  struct urb *urb)
+{
+	unsigned char *packet_data;
+	int i, totlen = 0;
+
+	for (i = 0; i < urb->number_of_packets; i++) {
+		int packet_len = urb->iso_frame_desc[i].actual_length;
+		int packet_stat = urb->iso_frame_desc[i].status;
+
+		packet_data = urb->transfer_buffer + urb->iso_frame_desc[i].offset;
+
+		/* Detect and ignore errored packets */
+		if (packet_stat) {	/* packet_stat != 0 ????????????? */
+			PDEBUG(DBG_ISOC, "data error: [%d] len=%d, status=%X", i, packet_len, packet_stat);
+			usbvision->isoc_err_count++;
+			continue;
+		}
+
+		/* Detect and ignore empty packets */
+		if (packet_len < 0) {
+			PDEBUG(DBG_ISOC, "error packet [%d]", i);
+			usbvision->isoc_skip_count++;
+			continue;
+		} else if (packet_len == 0) {	/* Frame end ????? */
+			PDEBUG(DBG_ISOC, "null packet [%d]", i);
+			usbvision->isocstate = isoc_state_no_frame;
+			usbvision->isoc_skip_count++;
+			continue;
+		} else if (packet_len > usbvision->isoc_packet_size) {
+			PDEBUG(DBG_ISOC, "packet[%d] > isoc_packet_size", i);
+			usbvision->isoc_skip_count++;
+			continue;
+		}
+
+		PDEBUG(DBG_ISOC, "packet ok [%d] len=%d", i, packet_len);
+
+		if (usbvision->isocstate == isoc_state_no_frame) { /* new frame begins */
+			usbvision->isocstate = isoc_state_in_frame;
+			scratch_mark_header(usbvision);
+			usbvision_measure_bandwidth(usbvision);
+			PDEBUG(DBG_ISOC, "packet with header");
+		}
+
+		/*
+		 * If usbvision continues to feed us with data but there is no
+		 * consumption (if, for example, V4L client fell asleep) we
+		 * may overflow the buffer. We have to move old data over to
+		 * free room for new data. This is bad for old data. If we
+		 * just drop new data then it's bad for new data... choose
+		 * your favorite evil here.
+		 */
+		if (scratch_free(usbvision) < packet_len) {
+			usbvision->scratch_ovf_count++;
+			PDEBUG(DBG_ISOC, "scratch buf overflow! scr_len: %d, n: %d",
+			       scratch_len(usbvision), packet_len);
+			scratch_rm_old(usbvision, packet_len - scratch_free(usbvision));
+		}
+
+		/* Now we know that there is enough room in scratch buffer */
+		scratch_put(usbvision, packet_data, packet_len);
+		totlen += packet_len;
+		usbvision->isoc_data_count += packet_len;
+		usbvision->isoc_packet_count++;
+	}
+#if ENABLE_HEXDUMP
+	if (totlen > 0) {
+		static int foo;
+
+		if (foo < 1) {
+			printk(KERN_DEBUG "+%d.\n", usbvision->scratchlen);
+			usbvision_hexdump(data0, (totlen > 64) ? 64 : totlen);
+			++foo;
+		}
+	}
+#endif
+	return totlen;
+}
+
+static void usbvision_isoc_irq(struct urb *urb)
+{
+	int err_code = 0;
+	int len;
+	struct usb_usbvision *usbvision = urb->context;
+	int i;
+	unsigned long start_time = jiffies;
+	struct usbvision_frame **f;
+
+	/* We don't want to do anything if we are about to be removed! */
+	if (!USBVISION_IS_OPERATIONAL(usbvision))
+		return;
+
+	/* any urb with wrong status is ignored without acknowledgement */
+	if (urb->status == -ENOENT)
+		return;
+
+	f = &usbvision->cur_frame;
+
+	/* Manage streaming interruption */
+	if (usbvision->streaming == stream_interrupt) {
+		usbvision->streaming = stream_idle;
+		if ((*f)) {
+			(*f)->grabstate = frame_state_ready;
+			(*f)->scanstate = scan_state_scanning;
+		}
+		PDEBUG(DBG_IRQ, "stream interrupted");
+		wake_up_interruptible(&usbvision->wait_stream);
+	}
+
+	/* Copy the data received into our scratch buffer */
+	len = usbvision_compress_isochronous(usbvision, urb);
+
+	usbvision->isoc_urb_count++;
+	usbvision->urb_length = len;
+
+	if (usbvision->streaming == stream_on) {
+		/* If we collected enough data let's parse! */
+		if (scratch_len(usbvision) > USBVISION_HEADER_LENGTH &&
+		    !list_empty(&(usbvision->inqueue))) {
+			if (!(*f)) {
+				(*f) = list_entry(usbvision->inqueue.next,
+						  struct usbvision_frame,
+						  frame);
+			}
+			usbvision_parse_data(usbvision);
+		} else {
+			/* If we don't have a frame
+			  we're current working on, complain */
+			PDEBUG(DBG_IRQ,
+			       "received data, but no one needs it");
+			scratch_reset(usbvision);
+		}
+	} else {
+		PDEBUG(DBG_IRQ, "received data, but no one needs it");
+		scratch_reset(usbvision);
+	}
+
+	usbvision->time_in_irq += jiffies - start_time;
+
+	for (i = 0; i < USBVISION_URB_FRAMES; i++) {
+		urb->iso_frame_desc[i].status = 0;
+		urb->iso_frame_desc[i].actual_length = 0;
+	}
+
+	urb->status = 0;
+	urb->dev = usbvision->dev;
+	err_code = usb_submit_urb(urb, GFP_ATOMIC);
+
+	if (err_code) {
+		dev_err(&usbvision->dev->dev,
+			"%s: usb_submit_urb failed: error %d\n",
+				__func__, err_code);
+	}
+
+	return;
+}
+
+/*************************************/
+/* Low level usbvision access functions */
+/*************************************/
+
+/*
+ * usbvision_read_reg()
+ *
+ * return  < 0 -> Error
+ *        >= 0 -> Data
+ */
+
+int usbvision_read_reg(struct usb_usbvision *usbvision, unsigned char reg)
+{
+	int err_code = 0;
+	unsigned char *buffer = usbvision->ctrl_urb_buffer;
+
+	if (!USBVISION_IS_OPERATIONAL(usbvision))
+		return -1;
+
+	err_code = usb_control_msg(usbvision->dev, usb_rcvctrlpipe(usbvision->dev, 1),
+				USBVISION_OP_CODE,
+				USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT,
+				0, (__u16) reg, buffer, 1, HZ);
+
+	if (err_code < 0) {
+		dev_err(&usbvision->dev->dev,
+			"%s: failed: error %d\n", __func__, err_code);
+		return err_code;
+	}
+	return buffer[0];
+}
+
+/*
+ * usbvision_write_reg()
+ *
+ * return 1 -> Reg written
+ *        0 -> usbvision is not yet ready
+ *       -1 -> Something went wrong
+ */
+
+int usbvision_write_reg(struct usb_usbvision *usbvision, unsigned char reg,
+			    unsigned char value)
+{
+	int err_code = 0;
+
+	if (!USBVISION_IS_OPERATIONAL(usbvision))
+		return 0;
+
+	usbvision->ctrl_urb_buffer[0] = value;
+	err_code = usb_control_msg(usbvision->dev, usb_sndctrlpipe(usbvision->dev, 1),
+				USBVISION_OP_CODE,
+				USB_DIR_OUT | USB_TYPE_VENDOR |
+				USB_RECIP_ENDPOINT, 0, (__u16) reg,
+				usbvision->ctrl_urb_buffer, 1, HZ);
+
+	if (err_code < 0) {
+		dev_err(&usbvision->dev->dev,
+			"%s: failed: error %d\n", __func__, err_code);
+	}
+	return err_code;
+}
+
+
+static void usbvision_ctrl_urb_complete(struct urb *urb)
+{
+	struct usb_usbvision *usbvision = (struct usb_usbvision *)urb->context;
+
+	PDEBUG(DBG_IRQ, "");
+	usbvision->ctrl_urb_busy = 0;
+}
+
+
+static int usbvision_write_reg_irq(struct usb_usbvision *usbvision, int address,
+				unsigned char *data, int len)
+{
+	int err_code = 0;
+
+	PDEBUG(DBG_IRQ, "");
+	if (len > 8)
+		return -EFAULT;
+	if (usbvision->ctrl_urb_busy)
+		return -EBUSY;
+	usbvision->ctrl_urb_busy = 1;
+
+	usbvision->ctrl_urb_setup.bRequestType = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT;
+	usbvision->ctrl_urb_setup.bRequest     = USBVISION_OP_CODE;
+	usbvision->ctrl_urb_setup.wValue       = 0;
+	usbvision->ctrl_urb_setup.wIndex       = cpu_to_le16(address);
+	usbvision->ctrl_urb_setup.wLength      = cpu_to_le16(len);
+	usb_fill_control_urb(usbvision->ctrl_urb, usbvision->dev,
+							usb_sndctrlpipe(usbvision->dev, 1),
+							(unsigned char *)&usbvision->ctrl_urb_setup,
+							(void *)usbvision->ctrl_urb_buffer, len,
+							usbvision_ctrl_urb_complete,
+							(void *)usbvision);
+
+	memcpy(usbvision->ctrl_urb_buffer, data, len);
+
+	err_code = usb_submit_urb(usbvision->ctrl_urb, GFP_ATOMIC);
+	if (err_code < 0) {
+		/* error in usb_submit_urb() */
+		usbvision->ctrl_urb_busy = 0;
+	}
+	PDEBUG(DBG_IRQ, "submit %d byte: error %d", len, err_code);
+	return err_code;
+}
+
+
+static int usbvision_init_compression(struct usb_usbvision *usbvision)
+{
+	usbvision->last_isoc_frame_num = -1;
+	usbvision->isoc_data_count = 0;
+	usbvision->isoc_packet_count = 0;
+	usbvision->isoc_skip_count = 0;
+	usbvision->compr_level = 50;
+	usbvision->last_compr_level = -1;
+	usbvision->isoc_urb_count = 0;
+	usbvision->request_intra = 1;
+	usbvision->isoc_measure_bandwidth_count = 0;
+
+	return 0;
+}
+
+/* this function measures the used bandwidth since last call
+ * return:    0 : no error
+ * sets used_bandwidth to 1-100 : 1-100% of full bandwidth resp. to isoc_packet_size
+ */
+static int usbvision_measure_bandwidth(struct usb_usbvision *usbvision)
+{
+	if (usbvision->isoc_measure_bandwidth_count < 2) { /* this gives an average bandwidth of 3 frames */
+		usbvision->isoc_measure_bandwidth_count++;
+		return 0;
+	}
+	if ((usbvision->isoc_packet_size > 0) && (usbvision->isoc_packet_count > 0)) {
+		usbvision->used_bandwidth = usbvision->isoc_data_count /
+					(usbvision->isoc_packet_count + usbvision->isoc_skip_count) *
+					100 / usbvision->isoc_packet_size;
+	}
+	usbvision->isoc_measure_bandwidth_count = 0;
+	usbvision->isoc_data_count = 0;
+	usbvision->isoc_packet_count = 0;
+	usbvision->isoc_skip_count = 0;
+	return 0;
+}
+
+static int usbvision_adjust_compression(struct usb_usbvision *usbvision)
+{
+	int err_code = 0;
+	unsigned char buffer[6];
+
+	PDEBUG(DBG_IRQ, "");
+	if ((adjust_compression) && (usbvision->used_bandwidth > 0)) {
+		usbvision->compr_level += (usbvision->used_bandwidth - 90) / 2;
+		RESTRICT_TO_RANGE(usbvision->compr_level, 0, 100);
+		if (usbvision->compr_level != usbvision->last_compr_level) {
+			int distortion;
+
+			if (usbvision->bridge_type == BRIDGE_NT1004 || usbvision->bridge_type == BRIDGE_NT1005) {
+				buffer[0] = (unsigned char)(4 + 16 * usbvision->compr_level / 100);	/* PCM Threshold 1 */
+				buffer[1] = (unsigned char)(4 + 8 * usbvision->compr_level / 100);	/* PCM Threshold 2 */
+				distortion = 7 + 248 * usbvision->compr_level / 100;
+				buffer[2] = (unsigned char)(distortion & 0xFF);				/* Average distortion Threshold (inter) */
+				buffer[3] = (unsigned char)(distortion & 0xFF);				/* Average distortion Threshold (intra) */
+				distortion = 1 + 42 * usbvision->compr_level / 100;
+				buffer[4] = (unsigned char)(distortion & 0xFF);				/* Maximum distortion Threshold (inter) */
+				buffer[5] = (unsigned char)(distortion & 0xFF);				/* Maximum distortion Threshold (intra) */
+			} else { /* BRIDGE_NT1003 */
+				buffer[0] = (unsigned char)(4 + 16 * usbvision->compr_level / 100);	/* PCM threshold 1 */
+				buffer[1] = (unsigned char)(4 + 8 * usbvision->compr_level / 100);	/* PCM threshold 2 */
+				distortion = 2 + 253 * usbvision->compr_level / 100;
+				buffer[2] = (unsigned char)(distortion & 0xFF);				/* distortion threshold bit0-7 */
+				buffer[3] = 0;	/* (unsigned char)((distortion >> 8) & 0x0F);		distortion threshold bit 8-11 */
+				distortion = 0 + 43 * usbvision->compr_level / 100;
+				buffer[4] = (unsigned char)(distortion & 0xFF);				/* maximum distortion bit0-7 */
+				buffer[5] = 0; /* (unsigned char)((distortion >> 8) & 0x01);		maximum distortion bit 8 */
+			}
+			err_code = usbvision_write_reg_irq(usbvision, USBVISION_PCM_THR1, buffer, 6);
+			if (err_code == 0) {
+				PDEBUG(DBG_IRQ, "new compr params %#02x %#02x %#02x %#02x %#02x %#02x", buffer[0],
+								buffer[1], buffer[2], buffer[3], buffer[4], buffer[5]);
+				usbvision->last_compr_level = usbvision->compr_level;
+			}
+		}
+	}
+	return err_code;
+}
+
+static int usbvision_request_intra(struct usb_usbvision *usbvision)
+{
+	unsigned char buffer[1];
+
+	PDEBUG(DBG_IRQ, "");
+	usbvision->request_intra = 1;
+	buffer[0] = 1;
+	usbvision_write_reg_irq(usbvision, USBVISION_FORCE_INTRA, buffer, 1);
+	return 0;
+}
+
+static int usbvision_unrequest_intra(struct usb_usbvision *usbvision)
+{
+	unsigned char buffer[1];
+
+	PDEBUG(DBG_IRQ, "");
+	usbvision->request_intra = 0;
+	buffer[0] = 0;
+	usbvision_write_reg_irq(usbvision, USBVISION_FORCE_INTRA, buffer, 1);
+	return 0;
+}
+
+/*******************************
+ * usbvision utility functions
+ *******************************/
+
+int usbvision_power_off(struct usb_usbvision *usbvision)
+{
+	int err_code = 0;
+
+	PDEBUG(DBG_FUNC, "");
+
+	err_code = usbvision_write_reg(usbvision, USBVISION_PWR_REG, USBVISION_SSPND_EN);
+	if (err_code == 1)
+		usbvision->power = 0;
+	PDEBUG(DBG_FUNC, "%s: err_code %d", (err_code != 1) ? "ERROR" : "power is off", err_code);
+	return err_code;
+}
+
+/* configure webcam image sensor using the serial port */
+static int usbvision_init_webcam(struct usb_usbvision *usbvision)
+{
+	int rc;
+	int i;
+	static char init_values[38][3] = {
+		{ 0x04, 0x12, 0x08 }, { 0x05, 0xff, 0xc8 }, { 0x06, 0x18, 0x07 }, { 0x07, 0x90, 0x00 },
+		{ 0x09, 0x00, 0x00 }, { 0x0a, 0x00, 0x00 }, { 0x0b, 0x08, 0x00 }, { 0x0d, 0xcc, 0xcc },
+		{ 0x0e, 0x13, 0x14 }, { 0x10, 0x9b, 0x83 }, { 0x11, 0x5a, 0x3f }, { 0x12, 0xe4, 0x73 },
+		{ 0x13, 0x88, 0x84 }, { 0x14, 0x89, 0x80 }, { 0x15, 0x00, 0x20 }, { 0x16, 0x00, 0x00 },
+		{ 0x17, 0xff, 0xa0 }, { 0x18, 0x6b, 0x20 }, { 0x19, 0x22, 0x40 }, { 0x1a, 0x10, 0x07 },
+		{ 0x1b, 0x00, 0x47 }, { 0x1c, 0x03, 0xe0 }, { 0x1d, 0x00, 0x00 }, { 0x1e, 0x00, 0x00 },
+		{ 0x1f, 0x00, 0x00 }, { 0x20, 0x00, 0x00 }, { 0x21, 0x00, 0x00 }, { 0x22, 0x00, 0x00 },
+		{ 0x23, 0x00, 0x00 }, { 0x24, 0x00, 0x00 }, { 0x25, 0x00, 0x00 }, { 0x26, 0x00, 0x00 },
+		{ 0x27, 0x00, 0x00 }, { 0x28, 0x00, 0x00 }, { 0x29, 0x00, 0x00 }, { 0x08, 0x80, 0x60 },
+		{ 0x0f, 0x2d, 0x24 }, { 0x0c, 0x80, 0x80 }
+	};
+	unsigned char *value = usbvision->ctrl_urb_buffer;
+
+	/* the only difference between PAL and NTSC init_values */
+	if (usbvision_device_data[usbvision->dev_model].video_norm == V4L2_STD_NTSC)
+		init_values[4][1] = 0x34;
+
+	for (i = 0; i < sizeof(init_values) / 3; i++) {
+		usbvision_write_reg(usbvision, USBVISION_SER_MODE, USBVISION_SER_MODE_SOFT);
+		memcpy(value, init_values[i], 3);
+		rc = usb_control_msg(usbvision->dev,
+				     usb_sndctrlpipe(usbvision->dev, 1),
+				     USBVISION_OP_CODE,
+				     USB_DIR_OUT | USB_TYPE_VENDOR |
+				     USB_RECIP_ENDPOINT, 0,
+				     (__u16) USBVISION_SER_DAT1, value,
+				     3, HZ);
+		if (rc < 0)
+			return rc;
+		usbvision_write_reg(usbvision, USBVISION_SER_MODE, USBVISION_SER_MODE_SIO);
+		/* write 3 bytes to the serial port using SIO mode */
+		usbvision_write_reg(usbvision, USBVISION_SER_CONT, 3 | 0x10);
+		usbvision_write_reg(usbvision, USBVISION_IOPIN_REG, 0);
+		usbvision_write_reg(usbvision, USBVISION_SER_MODE, USBVISION_SER_MODE_SOFT);
+		usbvision_write_reg(usbvision, USBVISION_IOPIN_REG, USBVISION_IO_2);
+		usbvision_write_reg(usbvision, USBVISION_SER_MODE, USBVISION_SER_MODE_SOFT | USBVISION_CLK_OUT);
+		usbvision_write_reg(usbvision, USBVISION_SER_MODE, USBVISION_SER_MODE_SOFT | USBVISION_DAT_IO);
+		usbvision_write_reg(usbvision, USBVISION_SER_MODE, USBVISION_SER_MODE_SOFT | USBVISION_CLK_OUT | USBVISION_DAT_IO);
+	}
+
+	return 0;
+}
+
+/*
+ * usbvision_set_video_format()
+ *
+ */
+static int usbvision_set_video_format(struct usb_usbvision *usbvision, int format)
+{
+	static const char proc[] = "usbvision_set_video_format";
+	unsigned char *value = usbvision->ctrl_urb_buffer;
+	int rc;
+
+	if (!USBVISION_IS_OPERATIONAL(usbvision))
+		return 0;
+
+	PDEBUG(DBG_FUNC, "isoc_mode %#02x", format);
+
+	if ((format != ISOC_MODE_YUV422)
+	    && (format != ISOC_MODE_YUV420)
+	    && (format != ISOC_MODE_COMPRESS)) {
+		printk(KERN_ERR "usbvision: unknown video format %02x, using default YUV420",
+		       format);
+		format = ISOC_MODE_YUV420;
+	}
+	value[0] = 0x0A;  /* TODO: See the effect of the filter */
+	value[1] = format; /* Sets the VO_MODE register which follows FILT_CONT */
+	rc = usb_control_msg(usbvision->dev, usb_sndctrlpipe(usbvision->dev, 1),
+			     USBVISION_OP_CODE,
+			     USB_DIR_OUT | USB_TYPE_VENDOR |
+			     USB_RECIP_ENDPOINT, 0,
+			     (__u16) USBVISION_FILT_CONT, value, 2, HZ);
+
+	if (rc < 0) {
+		printk(KERN_ERR "%s: ERROR=%d. USBVISION stopped - reconnect or reload driver.\n",
+		       proc, rc);
+	}
+	usbvision->isoc_mode = format;
+	return rc;
+}
+
+/*
+ * usbvision_set_output()
+ *
+ */
+
+int usbvision_set_output(struct usb_usbvision *usbvision, int width,
+			 int height)
+{
+	int err_code = 0;
+	int usb_width, usb_height;
+	unsigned int frame_rate = 0, frame_drop = 0;
+	unsigned char *value = usbvision->ctrl_urb_buffer;
+
+	if (!USBVISION_IS_OPERATIONAL(usbvision))
+		return 0;
+
+	if (width > MAX_USB_WIDTH) {
+		usb_width = width / 2;
+		usbvision->stretch_width = 2;
+	} else {
+		usb_width = width;
+		usbvision->stretch_width = 1;
+	}
+
+	if (height > MAX_USB_HEIGHT) {
+		usb_height = height / 2;
+		usbvision->stretch_height = 2;
+	} else {
+		usb_height = height;
+		usbvision->stretch_height = 1;
+	}
+
+	RESTRICT_TO_RANGE(usb_width, MIN_FRAME_WIDTH, MAX_USB_WIDTH);
+	usb_width &= ~(MIN_FRAME_WIDTH-1);
+	RESTRICT_TO_RANGE(usb_height, MIN_FRAME_HEIGHT, MAX_USB_HEIGHT);
+	usb_height &= ~(1);
+
+	PDEBUG(DBG_FUNC, "usb %dx%d; screen %dx%d; stretch %dx%d",
+						usb_width, usb_height, width, height,
+						usbvision->stretch_width, usbvision->stretch_height);
+
+	/* I'll not rewrite the same values */
+	if ((usb_width != usbvision->curwidth) || (usb_height != usbvision->curheight)) {
+		value[0] = usb_width & 0xff;		/* LSB */
+		value[1] = (usb_width >> 8) & 0x03;	/* MSB */
+		value[2] = usb_height & 0xff;		/* LSB */
+		value[3] = (usb_height >> 8) & 0x03;	/* MSB */
+
+		err_code = usb_control_msg(usbvision->dev, usb_sndctrlpipe(usbvision->dev, 1),
+			     USBVISION_OP_CODE,
+			     USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT,
+				 0, (__u16) USBVISION_LXSIZE_O, value, 4, HZ);
+
+		if (err_code < 0) {
+			dev_err(&usbvision->dev->dev,
+				"%s failed: error %d\n", __func__, err_code);
+			return err_code;
+		}
+		usbvision->curwidth = usbvision->stretch_width * usb_width;
+		usbvision->curheight = usbvision->stretch_height * usb_height;
+	}
+
+	if (usbvision->isoc_mode == ISOC_MODE_YUV422)
+		frame_rate = (usbvision->isoc_packet_size * 1000) / (usb_width * usb_height * 2);
+	else if (usbvision->isoc_mode == ISOC_MODE_YUV420)
+		frame_rate = (usbvision->isoc_packet_size * 1000) / ((usb_width * usb_height * 12) / 8);
+	else
+		frame_rate = FRAMERATE_MAX;
+
+	if (usbvision->tvnorm_id & V4L2_STD_625_50)
+		frame_drop = frame_rate * 32 / 25 - 1;
+	else if (usbvision->tvnorm_id & V4L2_STD_525_60)
+		frame_drop = frame_rate * 32 / 30 - 1;
+
+	RESTRICT_TO_RANGE(frame_drop, FRAMERATE_MIN, FRAMERATE_MAX);
+
+	PDEBUG(DBG_FUNC, "frame_rate %d fps, frame_drop %d", frame_rate, frame_drop);
+
+	frame_drop = FRAMERATE_MAX;	/* We can allow the maximum here, because dropping is controlled */
+
+	if (usbvision_device_data[usbvision->dev_model].codec == CODEC_WEBCAM) {
+		if (usbvision_device_data[usbvision->dev_model].video_norm == V4L2_STD_PAL)
+			frame_drop = 25;
+		else
+			frame_drop = 30;
+	}
+
+	/* frame_drop = 7; => frame_phase = 1, 5, 9, 13, 17, 21, 25, 0, 4, 8, ...
+		=> frame_skip = 4;
+		=> frame_rate = (7 + 1) * 25 / 32 = 200 / 32 = 6.25;
+
+	   frame_drop = 9; => frame_phase = 1, 5, 8, 11, 14, 17, 21, 24, 27, 1, 4, 8, ...
+	    => frame_skip = 4, 3, 3, 3, 3, 4, 3, 3, 3, 3, 4, ...
+		=> frame_rate = (9 + 1) * 25 / 32 = 250 / 32 = 7.8125;
+	*/
+	err_code = usbvision_write_reg(usbvision, USBVISION_FRM_RATE, frame_drop);
+	return err_code;
+}
+
+
+/*
+ * usbvision_frames_alloc
+ * allocate the required frames
+ */
+int usbvision_frames_alloc(struct usb_usbvision *usbvision, int number_of_frames)
+{
+	int i;
+
+	/* needs to be page aligned cause the buffers can be mapped individually! */
+	usbvision->max_frame_size = PAGE_ALIGN(usbvision->curwidth *
+						usbvision->curheight *
+						usbvision->palette.bytes_per_pixel);
+
+	/* Try to do my best to allocate the frames the user want in the remaining memory */
+	usbvision->num_frames = number_of_frames;
+	while (usbvision->num_frames > 0) {
+		usbvision->fbuf_size = usbvision->num_frames * usbvision->max_frame_size;
+		usbvision->fbuf = usbvision_rvmalloc(usbvision->fbuf_size);
+		if (usbvision->fbuf)
+			break;
+		usbvision->num_frames--;
+	}
+
+	/* Allocate all buffers */
+	for (i = 0; i < usbvision->num_frames; i++) {
+		usbvision->frame[i].index = i;
+		usbvision->frame[i].grabstate = frame_state_unused;
+		usbvision->frame[i].data = usbvision->fbuf +
+			i * usbvision->max_frame_size;
+		/*
+		 * Set default sizes for read operation.
+		 */
+		usbvision->stretch_width = 1;
+		usbvision->stretch_height = 1;
+		usbvision->frame[i].width = usbvision->curwidth;
+		usbvision->frame[i].height = usbvision->curheight;
+		usbvision->frame[i].bytes_read = 0;
+	}
+	PDEBUG(DBG_FUNC, "allocated %d frames (%d bytes per frame)",
+			usbvision->num_frames, usbvision->max_frame_size);
+	return usbvision->num_frames;
+}
+
+/*
+ * usbvision_frames_free
+ * frees memory allocated for the frames
+ */
+void usbvision_frames_free(struct usb_usbvision *usbvision)
+{
+	/* Have to free all that memory */
+	PDEBUG(DBG_FUNC, "free %d frames", usbvision->num_frames);
+
+	if (usbvision->fbuf != NULL) {
+		usbvision_rvfree(usbvision->fbuf, usbvision->fbuf_size);
+		usbvision->fbuf = NULL;
+
+		usbvision->num_frames = 0;
+	}
+}
+/*
+ * usbvision_empty_framequeues()
+ * prepare queues for incoming and outgoing frames
+ */
+void usbvision_empty_framequeues(struct usb_usbvision *usbvision)
+{
+	u32 i;
+
+	INIT_LIST_HEAD(&(usbvision->inqueue));
+	INIT_LIST_HEAD(&(usbvision->outqueue));
+
+	for (i = 0; i < USBVISION_NUMFRAMES; i++) {
+		usbvision->frame[i].grabstate = frame_state_unused;
+		usbvision->frame[i].bytes_read = 0;
+	}
+}
+
+/*
+ * usbvision_stream_interrupt()
+ * stops streaming
+ */
+int usbvision_stream_interrupt(struct usb_usbvision *usbvision)
+{
+	int ret = 0;
+
+	/* stop reading from the device */
+
+	usbvision->streaming = stream_interrupt;
+	ret = wait_event_timeout(usbvision->wait_stream,
+				 (usbvision->streaming == stream_idle),
+				 msecs_to_jiffies(USBVISION_NUMSBUF*USBVISION_URB_FRAMES));
+	return ret;
+}
+
+/*
+ * usbvision_set_compress_params()
+ *
+ */
+
+static int usbvision_set_compress_params(struct usb_usbvision *usbvision)
+{
+	static const char proc[] = "usbvision_set_compression_params: ";
+	int rc;
+	unsigned char *value = usbvision->ctrl_urb_buffer;
+
+	value[0] = 0x0F;    /* Intra-Compression cycle */
+	value[1] = 0x01;    /* Reg.45 one line per strip */
+	value[2] = 0x00;    /* Reg.46 Force intra mode on all new frames */
+	value[3] = 0x00;    /* Reg.47 FORCE_UP <- 0 normal operation (not force) */
+	value[4] = 0xA2;    /* Reg.48 BUF_THR I'm not sure if this does something in not compressed mode. */
+	value[5] = 0x00;    /* Reg.49 DVI_YUV This has nothing to do with compression */
+
+	/* catched values for NT1004 */
+	/* value[0] = 0xFF; Never apply intra mode automatically */
+	/* value[1] = 0xF1; Use full frame height for virtual strip width; One line per strip */
+	/* value[2] = 0x01; Force intra mode on all new frames */
+	/* value[3] = 0x00; Strip size 400 Bytes; do not force up */
+	/* value[4] = 0xA2; */
+	if (!USBVISION_IS_OPERATIONAL(usbvision))
+		return 0;
+
+	rc = usb_control_msg(usbvision->dev, usb_sndctrlpipe(usbvision->dev, 1),
+			     USBVISION_OP_CODE,
+			     USB_DIR_OUT | USB_TYPE_VENDOR |
+			     USB_RECIP_ENDPOINT, 0,
+			     (__u16) USBVISION_INTRA_CYC, value, 5, HZ);
+
+	if (rc < 0) {
+		printk(KERN_ERR "%sERROR=%d. USBVISION stopped - reconnect or reload driver.\n",
+		       proc, rc);
+		return rc;
+	}
+
+	if (usbvision->bridge_type == BRIDGE_NT1004) {
+		value[0] =  20; /* PCM Threshold 1 */
+		value[1] =  12; /* PCM Threshold 2 */
+		value[2] = 255; /* Distortion Threshold inter */
+		value[3] = 255; /* Distortion Threshold intra */
+		value[4] =  43; /* Max Distortion inter */
+		value[5] =  43; /* Max Distortion intra */
+	} else {
+		value[0] =  20; /* PCM Threshold 1 */
+		value[1] =  12; /* PCM Threshold 2 */
+		value[2] = 255; /* Distortion Threshold d7-d0 */
+		value[3] =   0; /* Distortion Threshold d11-d8 */
+		value[4] =  43; /* Max Distortion d7-d0 */
+		value[5] =   0; /* Max Distortion d8 */
+	}
+
+	if (!USBVISION_IS_OPERATIONAL(usbvision))
+		return 0;
+
+	rc = usb_control_msg(usbvision->dev, usb_sndctrlpipe(usbvision->dev, 1),
+			     USBVISION_OP_CODE,
+			     USB_DIR_OUT | USB_TYPE_VENDOR |
+			     USB_RECIP_ENDPOINT, 0,
+			     (__u16) USBVISION_PCM_THR1, value, 6, HZ);
+
+	if (rc < 0) {
+		printk(KERN_ERR "%sERROR=%d. USBVISION stopped - reconnect or reload driver.\n",
+		       proc, rc);
+	}
+	return rc;
+}
+
+
+/*
+ * usbvision_set_input()
+ *
+ * Set the input (saa711x, ...) size x y and other misc input params
+ * I've no idea if this parameters are right
+ *
+ */
+int usbvision_set_input(struct usb_usbvision *usbvision)
+{
+	static const char proc[] = "usbvision_set_input: ";
+	int rc;
+	unsigned char *value = usbvision->ctrl_urb_buffer;
+	unsigned char dvi_yuv_value;
+
+	if (!USBVISION_IS_OPERATIONAL(usbvision))
+		return 0;
+
+	/* Set input format expected from decoder*/
+	if (usbvision_device_data[usbvision->dev_model].vin_reg1_override) {
+		value[0] = usbvision_device_data[usbvision->dev_model].vin_reg1;
+	} else if (usbvision_device_data[usbvision->dev_model].codec == CODEC_SAA7113) {
+		/* SAA7113 uses 8 bit output */
+		value[0] = USBVISION_8_422_SYNC;
+	} else {
+		/* I'm sure only about d2-d0 [010] 16 bit 4:2:2 usin sync pulses
+		 * as that is how saa7111 is configured */
+		value[0] = USBVISION_16_422_SYNC;
+		/* | USBVISION_VSNC_POL | USBVISION_VCLK_POL);*/
+	}
+
+	rc = usbvision_write_reg(usbvision, USBVISION_VIN_REG1, value[0]);
+	if (rc < 0) {
+		printk(KERN_ERR "%sERROR=%d. USBVISION stopped - reconnect or reload driver.\n",
+		       proc, rc);
+		return rc;
+	}
+
+
+	if (usbvision->tvnorm_id & V4L2_STD_PAL) {
+		value[0] = 0xC0;
+		value[1] = 0x02;	/* 0x02C0 -> 704 Input video line length */
+		value[2] = 0x20;
+		value[3] = 0x01;	/* 0x0120 -> 288 Input video n. of lines */
+		value[4] = 0x60;
+		value[5] = 0x00;	/* 0x0060 -> 96 Input video h offset */
+		value[6] = 0x16;
+		value[7] = 0x00;	/* 0x0016 -> 22 Input video v offset */
+	} else if (usbvision->tvnorm_id & V4L2_STD_SECAM) {
+		value[0] = 0xC0;
+		value[1] = 0x02;	/* 0x02C0 -> 704 Input video line length */
+		value[2] = 0x20;
+		value[3] = 0x01;	/* 0x0120 -> 288 Input video n. of lines */
+		value[4] = 0x01;
+		value[5] = 0x00;	/* 0x0001 -> 01 Input video h offset */
+		value[6] = 0x01;
+		value[7] = 0x00;	/* 0x0001 -> 01 Input video v offset */
+	} else {	/* V4L2_STD_NTSC */
+		value[0] = 0xD0;
+		value[1] = 0x02;	/* 0x02D0 -> 720 Input video line length */
+		value[2] = 0xF0;
+		value[3] = 0x00;	/* 0x00F0 -> 240 Input video number of lines */
+		value[4] = 0x50;
+		value[5] = 0x00;	/* 0x0050 -> 80 Input video h offset */
+		value[6] = 0x10;
+		value[7] = 0x00;	/* 0x0010 -> 16 Input video v offset */
+	}
+
+	/* webcam is only 480 pixels wide, both PAL and NTSC version */
+	if (usbvision_device_data[usbvision->dev_model].codec == CODEC_WEBCAM) {
+		value[0] = 0xe0;
+		value[1] = 0x01;	/* 0x01E0 -> 480 Input video line length */
+	}
+
+	if (usbvision_device_data[usbvision->dev_model].x_offset >= 0) {
+		value[4] = usbvision_device_data[usbvision->dev_model].x_offset & 0xff;
+		value[5] = (usbvision_device_data[usbvision->dev_model].x_offset & 0x0300) >> 8;
+	}
+
+	if (adjust_x_offset != -1) {
+		value[4] = adjust_x_offset & 0xff;
+		value[5] = (adjust_x_offset & 0x0300) >> 8;
+	}
+
+	if (usbvision_device_data[usbvision->dev_model].y_offset >= 0) {
+		value[6] = usbvision_device_data[usbvision->dev_model].y_offset & 0xff;
+		value[7] = (usbvision_device_data[usbvision->dev_model].y_offset & 0x0300) >> 8;
+	}
+
+	if (adjust_y_offset != -1) {
+		value[6] = adjust_y_offset & 0xff;
+		value[7] = (adjust_y_offset & 0x0300) >> 8;
+	}
+
+	rc = usb_control_msg(usbvision->dev, usb_sndctrlpipe(usbvision->dev, 1),
+			     USBVISION_OP_CODE,	/* USBVISION specific code */
+			     USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_ENDPOINT, 0,
+			     (__u16) USBVISION_LXSIZE_I, value, 8, HZ);
+	if (rc < 0) {
+		printk(KERN_ERR "%sERROR=%d. USBVISION stopped - reconnect or reload driver.\n",
+		       proc, rc);
+		return rc;
+	}
+
+
+	dvi_yuv_value = 0x00;	/* U comes after V, Ya comes after U/V, Yb comes after Yb */
+
+	if (usbvision_device_data[usbvision->dev_model].dvi_yuv_override) {
+		dvi_yuv_value = usbvision_device_data[usbvision->dev_model].dvi_yuv;
+	} else if (usbvision_device_data[usbvision->dev_model].codec == CODEC_SAA7113) {
+		/* This changes as the fine sync control changes. Further investigation necessary */
+		dvi_yuv_value = 0x06;
+	}
+
+	return usbvision_write_reg(usbvision, USBVISION_DVI_YUV, dvi_yuv_value);
+}
+
+
+/*
+ * usbvision_set_dram_settings()
+ *
+ * Set the buffer address needed by the usbvision dram to operate
+ * This values has been taken with usbsnoop.
+ *
+ */
+
+static int usbvision_set_dram_settings(struct usb_usbvision *usbvision)
+{
+	unsigned char *value = usbvision->ctrl_urb_buffer;
+	int rc;
+
+	if (usbvision->isoc_mode == ISOC_MODE_COMPRESS) {
+		value[0] = 0x42;
+		value[1] = 0x71;
+		value[2] = 0xff;
+		value[3] = 0x00;
+		value[4] = 0x98;
+		value[5] = 0xe0;
+		value[6] = 0x71;
+		value[7] = 0xff;
+		/* UR:  0x0E200-0x3FFFF = 204288 Words (1 Word = 2 Byte) */
+		/* FDL: 0x00000-0x0E099 =  57498 Words */
+		/* VDW: 0x0E3FF-0x3FFFF */
+	} else {
+		value[0] = 0x42;
+		value[1] = 0x00;
+		value[2] = 0xff;
+		value[3] = 0x00;
+		value[4] = 0x00;
+		value[5] = 0x00;
+		value[6] = 0x00;
+		value[7] = 0xff;
+	}
+	/* These are the values of the address of the video buffer,
+	 * they have to be loaded into the USBVISION_DRM_PRM1-8
+	 *
+	 * Start address of video output buffer for read:	drm_prm1-2 -> 0x00000
+	 * End address of video output buffer for read:		drm_prm1-3 -> 0x1ffff
+	 * Start address of video frame delay buffer:		drm_prm1-4 -> 0x20000
+	 *    Only used in compressed mode
+	 * End address of video frame delay buffer:		drm_prm1-5-6 -> 0x3ffff
+	 *    Only used in compressed mode
+	 * Start address of video output buffer for write:	drm_prm1-7 -> 0x00000
+	 * End address of video output buffer for write:	drm_prm1-8 -> 0x1ffff
+	 */
+
+	if (!USBVISION_IS_OPERATIONAL(usbvision))
+		return 0;
+
+	rc = usb_control_msg(usbvision->dev, usb_sndctrlpipe(usbvision->dev, 1),
+			     USBVISION_OP_CODE,	/* USBVISION specific code */
+			     USB_DIR_OUT | USB_TYPE_VENDOR |
+			     USB_RECIP_ENDPOINT, 0,
+			     (__u16) USBVISION_DRM_PRM1, value, 8, HZ);
+
+	if (rc < 0) {
+		dev_err(&usbvision->dev->dev, "%s: ERROR=%d\n", __func__, rc);
+		return rc;
+	}
+
+	/* Restart the video buffer logic */
+	rc = usbvision_write_reg(usbvision, USBVISION_DRM_CONT, USBVISION_RES_UR |
+				   USBVISION_RES_FDL | USBVISION_RES_VDW);
+	if (rc < 0)
+		return rc;
+	rc = usbvision_write_reg(usbvision, USBVISION_DRM_CONT, 0x00);
+
+	return rc;
+}
+
+/*
+ * ()
+ *
+ * Power on the device, enables suspend-resume logic
+ * &  reset the isoc End-Point
+ *
+ */
+
+int usbvision_power_on(struct usb_usbvision *usbvision)
+{
+	int err_code = 0;
+
+	PDEBUG(DBG_FUNC, "");
+
+	usbvision_write_reg(usbvision, USBVISION_PWR_REG, USBVISION_SSPND_EN);
+	usbvision_write_reg(usbvision, USBVISION_PWR_REG,
+			USBVISION_SSPND_EN | USBVISION_RES2);
+
+	if (usbvision_device_data[usbvision->dev_model].codec == CODEC_WEBCAM) {
+		usbvision_write_reg(usbvision, USBVISION_VIN_REG1,
+				USBVISION_16_422_SYNC | USBVISION_HVALID_PO);
+		usbvision_write_reg(usbvision, USBVISION_VIN_REG2,
+				USBVISION_NOHVALID | USBVISION_KEEP_BLANK);
+	}
+	usbvision_write_reg(usbvision, USBVISION_PWR_REG,
+			USBVISION_SSPND_EN | USBVISION_PWR_VID);
+	mdelay(10);
+	err_code = usbvision_write_reg(usbvision, USBVISION_PWR_REG,
+			USBVISION_SSPND_EN | USBVISION_PWR_VID | USBVISION_RES2);
+	if (err_code == 1)
+		usbvision->power = 1;
+	PDEBUG(DBG_FUNC, "%s: err_code %d", (err_code < 0) ? "ERROR" : "power is on", err_code);
+	return err_code;
+}
+
+
+/*
+ * usbvision_begin_streaming()
+ * Sure you have to put bit 7 to 0, if not incoming frames are droped, but no
+ * idea about the rest
+ */
+int usbvision_begin_streaming(struct usb_usbvision *usbvision)
+{
+	if (usbvision->isoc_mode == ISOC_MODE_COMPRESS)
+		usbvision_init_compression(usbvision);
+	return usbvision_write_reg(usbvision, USBVISION_VIN_REG2,
+		USBVISION_NOHVALID | usbvision->vin_reg2_preset);
+}
+
+/*
+ * usbvision_restart_isoc()
+ * Not sure yet if touching here PWR_REG make loose the config
+ */
+
+int usbvision_restart_isoc(struct usb_usbvision *usbvision)
+{
+	int ret;
+
+	ret = usbvision_write_reg(usbvision, USBVISION_PWR_REG,
+			      USBVISION_SSPND_EN | USBVISION_PWR_VID);
+	if (ret < 0)
+		return ret;
+	ret = usbvision_write_reg(usbvision, USBVISION_PWR_REG,
+			      USBVISION_SSPND_EN | USBVISION_PWR_VID |
+			      USBVISION_RES2);
+	if (ret < 0)
+		return ret;
+	ret = usbvision_write_reg(usbvision, USBVISION_VIN_REG2,
+			      USBVISION_KEEP_BLANK | USBVISION_NOHVALID |
+				  usbvision->vin_reg2_preset);
+	if (ret < 0)
+		return ret;
+
+	/* TODO: schedule timeout */
+	while ((usbvision_read_reg(usbvision, USBVISION_STATUS_REG) & 0x01) != 1)
+		;
+
+	return 0;
+}
+
+int usbvision_audio_off(struct usb_usbvision *usbvision)
+{
+	if (usbvision_write_reg(usbvision, USBVISION_IOPIN_REG, USBVISION_AUDIO_MUTE) < 0) {
+		printk(KERN_ERR "usbvision_audio_off: can't write reg\n");
+		return -1;
+	}
+	usbvision->audio_mute = 0;
+	usbvision->audio_channel = USBVISION_AUDIO_MUTE;
+	return 0;
+}
+
+int usbvision_set_audio(struct usb_usbvision *usbvision, int audio_channel)
+{
+	if (!usbvision->audio_mute) {
+		if (usbvision_write_reg(usbvision, USBVISION_IOPIN_REG, audio_channel) < 0) {
+			printk(KERN_ERR "usbvision_set_audio: can't write iopin register for audio switching\n");
+			return -1;
+		}
+	}
+	usbvision->audio_channel = audio_channel;
+	return 0;
+}
+
+int usbvision_setup(struct usb_usbvision *usbvision, int format)
+{
+	if (usbvision_device_data[usbvision->dev_model].codec == CODEC_WEBCAM)
+		usbvision_init_webcam(usbvision);
+	usbvision_set_video_format(usbvision, format);
+	usbvision_set_dram_settings(usbvision);
+	usbvision_set_compress_params(usbvision);
+	usbvision_set_input(usbvision);
+	usbvision_set_output(usbvision, MAX_USB_WIDTH, MAX_USB_HEIGHT);
+	usbvision_restart_isoc(usbvision);
+
+	/* cosas del PCM */
+	return USBVISION_IS_OPERATIONAL(usbvision);
+}
+
+int usbvision_set_alternate(struct usb_usbvision *dev)
+{
+	int err_code, prev_alt = dev->iface_alt;
+	int i;
+
+	dev->iface_alt = 0;
+	for (i = 0; i < dev->num_alt; i++)
+		if (dev->alt_max_pkt_size[i] > dev->alt_max_pkt_size[dev->iface_alt])
+			dev->iface_alt = i;
+
+	if (dev->iface_alt != prev_alt) {
+		dev->isoc_packet_size = dev->alt_max_pkt_size[dev->iface_alt];
+		PDEBUG(DBG_FUNC, "setting alternate %d with max_packet_size=%u",
+				dev->iface_alt, dev->isoc_packet_size);
+		err_code = usb_set_interface(dev->dev, dev->iface, dev->iface_alt);
+		if (err_code < 0) {
+			dev_err(&dev->dev->dev,
+				"cannot change alternate number to %d (error=%i)\n",
+					dev->iface_alt, err_code);
+			return err_code;
+		}
+	}
+
+	PDEBUG(DBG_ISOC, "ISO Packet Length:%d", dev->isoc_packet_size);
+
+	return 0;
+}
+
+/*
+ * usbvision_init_isoc()
+ *
+ */
+int usbvision_init_isoc(struct usb_usbvision *usbvision)
+{
+	struct usb_device *dev = usbvision->dev;
+	int buf_idx, err_code, reg_value;
+	int sb_size;
+
+	if (!USBVISION_IS_OPERATIONAL(usbvision))
+		return -EFAULT;
+
+	usbvision->cur_frame = NULL;
+	scratch_reset(usbvision);
+
+	/* Alternate interface 1 is is the biggest frame size */
+	err_code = usbvision_set_alternate(usbvision);
+	if (err_code < 0) {
+		usbvision->last_error = err_code;
+		return -EBUSY;
+	}
+	sb_size = USBVISION_URB_FRAMES * usbvision->isoc_packet_size;
+
+	reg_value = (16 - usbvision_read_reg(usbvision,
+					    USBVISION_ALTER_REG)) & 0x0F;
+
+	usbvision->usb_bandwidth = reg_value >> 1;
+	PDEBUG(DBG_ISOC, "USB Bandwidth Usage: %dMbit/Sec",
+	       usbvision->usb_bandwidth);
+
+
+
+	/* We double buffer the Iso lists */
+
+	for (buf_idx = 0; buf_idx < USBVISION_NUMSBUF; buf_idx++) {
+		int j, k;
+		struct urb *urb;
+
+		urb = usb_alloc_urb(USBVISION_URB_FRAMES, GFP_KERNEL);
+		if (urb == NULL)
+			return -ENOMEM;
+		usbvision->sbuf[buf_idx].urb = urb;
+		usbvision->sbuf[buf_idx].data =
+			usb_alloc_coherent(usbvision->dev,
+					   sb_size,
+					   GFP_KERNEL,
+					   &urb->transfer_dma);
+		urb->dev = dev;
+		urb->context = usbvision;
+		urb->pipe = usb_rcvisocpipe(dev, usbvision->video_endp);
+		urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
+		urb->interval = 1;
+		urb->transfer_buffer = usbvision->sbuf[buf_idx].data;
+		urb->complete = usbvision_isoc_irq;
+		urb->number_of_packets = USBVISION_URB_FRAMES;
+		urb->transfer_buffer_length =
+		    usbvision->isoc_packet_size * USBVISION_URB_FRAMES;
+		for (j = k = 0; j < USBVISION_URB_FRAMES; j++,
+		     k += usbvision->isoc_packet_size) {
+			urb->iso_frame_desc[j].offset = k;
+			urb->iso_frame_desc[j].length =
+				usbvision->isoc_packet_size;
+		}
+	}
+
+	/* Submit all URBs */
+	for (buf_idx = 0; buf_idx < USBVISION_NUMSBUF; buf_idx++) {
+		err_code = usb_submit_urb(usbvision->sbuf[buf_idx].urb,
+					 GFP_KERNEL);
+		if (err_code) {
+			dev_err(&usbvision->dev->dev,
+				"%s: usb_submit_urb(%d) failed: error %d\n",
+					__func__, buf_idx, err_code);
+		}
+	}
+
+	usbvision->streaming = stream_idle;
+	PDEBUG(DBG_ISOC, "%s: streaming=1 usbvision->video_endp=$%02x",
+	       __func__,
+	       usbvision->video_endp);
+	return 0;
+}
+
+/*
+ * usbvision_stop_isoc()
+ *
+ * This procedure stops streaming and deallocates URBs. Then it
+ * activates zero-bandwidth alt. setting of the video interface.
+ *
+ */
+void usbvision_stop_isoc(struct usb_usbvision *usbvision)
+{
+	int buf_idx, err_code, reg_value;
+	int sb_size = USBVISION_URB_FRAMES * usbvision->isoc_packet_size;
+
+	if ((usbvision->streaming == stream_off) || (usbvision->dev == NULL))
+		return;
+
+	/* Unschedule all of the iso td's */
+	for (buf_idx = 0; buf_idx < USBVISION_NUMSBUF; buf_idx++) {
+		usb_kill_urb(usbvision->sbuf[buf_idx].urb);
+		if (usbvision->sbuf[buf_idx].data) {
+			usb_free_coherent(usbvision->dev,
+					  sb_size,
+					  usbvision->sbuf[buf_idx].data,
+					  usbvision->sbuf[buf_idx].urb->transfer_dma);
+		}
+		usb_free_urb(usbvision->sbuf[buf_idx].urb);
+		usbvision->sbuf[buf_idx].urb = NULL;
+	}
+
+	PDEBUG(DBG_ISOC, "%s: streaming=stream_off\n", __func__);
+	usbvision->streaming = stream_off;
+
+	if (!usbvision->remove_pending) {
+		/* Set packet size to 0 */
+		usbvision->iface_alt = 0;
+		err_code = usb_set_interface(usbvision->dev, usbvision->iface,
+					    usbvision->iface_alt);
+		if (err_code < 0) {
+			dev_err(&usbvision->dev->dev,
+				"%s: usb_set_interface() failed: error %d\n",
+					__func__, err_code);
+			usbvision->last_error = err_code;
+		}
+		reg_value = (16-usbvision_read_reg(usbvision, USBVISION_ALTER_REG)) & 0x0F;
+		usbvision->isoc_packet_size =
+			(reg_value == 0) ? 0 : (reg_value * 64) - 1;
+		PDEBUG(DBG_ISOC, "ISO Packet Length:%d",
+		       usbvision->isoc_packet_size);
+
+		usbvision->usb_bandwidth = reg_value >> 1;
+		PDEBUG(DBG_ISOC, "USB Bandwidth Usage: %dMbit/Sec",
+		       usbvision->usb_bandwidth);
+	}
+}
+
+int usbvision_muxsel(struct usb_usbvision *usbvision, int channel)
+{
+	/* inputs #0 and #3 are constant for every SAA711x. */
+	/* inputs #1 and #2 are variable for SAA7111 and SAA7113 */
+	int mode[4] = { SAA7115_COMPOSITE0, 0, 0, SAA7115_COMPOSITE3 };
+	int audio[] = { 1, 0, 0, 0 };
+	/* channel 0 is TV with audiochannel 1 (tuner mono) */
+	/* channel 1 is Composite with audio channel 0 (line in) */
+	/* channel 2 is S-Video with audio channel 0 (line in) */
+	/* channel 3 is additional video inputs to the device with audio channel 0 (line in) */
+
+	RESTRICT_TO_RANGE(channel, 0, usbvision->video_inputs);
+	usbvision->ctl_input = channel;
+
+	/* set the new channel */
+	/* Regular USB TV Tuners -> channel: 0 = Television, 1 = Composite, 2 = S-Video */
+	/* Four video input devices -> channel: 0 = Chan White, 1 = Chan Green, 2 = Chan Yellow, 3 = Chan Red */
+
+	switch (usbvision_device_data[usbvision->dev_model].codec) {
+	case CODEC_SAA7113:
+		mode[1] = SAA7115_COMPOSITE2;
+		if (switch_svideo_input) {
+			/* To handle problems with S-Video Input for
+			 * some devices.  Use switch_svideo_input
+			 * parameter when loading the module.*/
+			mode[2] = SAA7115_COMPOSITE1;
+		} else {
+			mode[2] = SAA7115_SVIDEO1;
+		}
+		break;
+	case CODEC_SAA7111:
+	default:
+		/* modes for saa7111 */
+		mode[1] = SAA7115_COMPOSITE1;
+		mode[2] = SAA7115_SVIDEO1;
+		break;
+	}
+	call_all(usbvision, video, s_routing, mode[channel], 0, 0);
+	usbvision_set_audio(usbvision, audio[channel]);
+	return 0;
+}
diff --git a/drivers/media/usb/usbvision/usbvision-i2c.c b/drivers/media/usb/usbvision/usbvision-i2c.c
new file mode 100644
index 0000000..837bd4d
--- /dev/null
+++ b/drivers/media/usb/usbvision/usbvision-i2c.c
@@ -0,0 +1,447 @@
+/*
+ * usbvision_i2c.c
+ *  i2c algorithm for USB-I2C Bridges
+ *
+ * Copyright (c) 1999-2007 Joerg Heckenbach <joerg@heckenbach-aw.de>
+ *                         Dwaine Garden <dwainegarden@rogers.com>
+ *
+ * This module is part of usbvision driver project.
+ * Updates to driver completed by Dwaine P. Garden
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/uaccess.h>
+#include <linux/ioport.h>
+#include <linux/errno.h>
+#include <linux/usb.h>
+#include <linux/i2c.h>
+#include "usbvision.h"
+
+#define DBG_I2C		(1 << 0)
+
+static int i2c_debug;
+
+module_param(i2c_debug, int, 0644);			/* debug_i2c_usb mode of the device driver */
+MODULE_PARM_DESC(i2c_debug, "enable debug messages [i2c]");
+
+#define PDEBUG(level, fmt, args...) { \
+		if (i2c_debug & (level)) \
+			printk(KERN_INFO KBUILD_MODNAME ":[%s:%d] " fmt, \
+				__func__, __LINE__ , ## args); \
+	}
+
+static int usbvision_i2c_write(struct usb_usbvision *usbvision, unsigned char addr, char *buf,
+			    short len);
+static int usbvision_i2c_read(struct usb_usbvision *usbvision, unsigned char addr, char *buf,
+			   short len);
+
+static inline int try_write_address(struct i2c_adapter *i2c_adap,
+				    unsigned char addr, int retries)
+{
+	struct usb_usbvision *usbvision;
+	int i, ret = -1;
+	char buf[4];
+
+	usbvision = (struct usb_usbvision *)i2c_get_adapdata(i2c_adap);
+	buf[0] = 0x00;
+	for (i = 0; i <= retries; i++) {
+		ret = (usbvision_i2c_write(usbvision, addr, buf, 1));
+		if (ret == 1)
+			break;	/* success! */
+		udelay(5);
+		if (i == retries)	/* no success */
+			break;
+		udelay(10);
+	}
+	if (i) {
+		PDEBUG(DBG_I2C, "Needed %d retries for address %#2x", i, addr);
+		PDEBUG(DBG_I2C, "Maybe there's no device at this address");
+	}
+	return ret;
+}
+
+static inline int try_read_address(struct i2c_adapter *i2c_adap,
+				   unsigned char addr, int retries)
+{
+	struct usb_usbvision *usbvision;
+	int i, ret = -1;
+	char buf[4];
+
+	usbvision = (struct usb_usbvision *)i2c_get_adapdata(i2c_adap);
+	for (i = 0; i <= retries; i++) {
+		ret = (usbvision_i2c_read(usbvision, addr, buf, 1));
+		if (ret == 1)
+			break;	/* success! */
+		udelay(5);
+		if (i == retries)	/* no success */
+			break;
+		udelay(10);
+	}
+	if (i) {
+		PDEBUG(DBG_I2C, "Needed %d retries for address %#2x", i, addr);
+		PDEBUG(DBG_I2C, "Maybe there's no device at this address");
+	}
+	return ret;
+}
+
+static inline int usb_find_address(struct i2c_adapter *i2c_adap,
+				   struct i2c_msg *msg, int retries,
+				   unsigned char *add)
+{
+	unsigned short flags = msg->flags;
+
+	unsigned char addr;
+	int ret;
+
+	addr = (msg->addr << 1);
+	if (flags & I2C_M_RD)
+		addr |= 1;
+
+	add[0] = addr;
+	if (flags & I2C_M_RD)
+		ret = try_read_address(i2c_adap, addr, retries);
+	else
+		ret = try_write_address(i2c_adap, addr, retries);
+
+	if (ret != 1)
+		return -EREMOTEIO;
+
+	return 0;
+}
+
+static int
+usbvision_i2c_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg msgs[], int num)
+{
+	struct i2c_msg *pmsg;
+	struct usb_usbvision *usbvision;
+	int i, ret;
+	unsigned char addr = 0;
+
+	usbvision = (struct usb_usbvision *)i2c_get_adapdata(i2c_adap);
+
+	for (i = 0; i < num; i++) {
+		pmsg = &msgs[i];
+		ret = usb_find_address(i2c_adap, pmsg, i2c_adap->retries, &addr);
+		if (ret != 0) {
+			PDEBUG(DBG_I2C, "got NAK from device, message #%d", i);
+			return (ret < 0) ? ret : -EREMOTEIO;
+		}
+
+		if (pmsg->flags & I2C_M_RD) {
+			/* read bytes into buffer */
+			ret = (usbvision_i2c_read(usbvision, addr, pmsg->buf, pmsg->len));
+			if (ret < pmsg->len)
+				return (ret < 0) ? ret : -EREMOTEIO;
+		} else {
+			/* write bytes from buffer */
+			ret = (usbvision_i2c_write(usbvision, addr, pmsg->buf, pmsg->len));
+			if (ret < pmsg->len)
+				return (ret < 0) ? ret : -EREMOTEIO;
+		}
+	}
+	return num;
+}
+
+static u32 functionality(struct i2c_adapter *adap)
+{
+	return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
+}
+
+/* -----exported algorithm data: -------------------------------------	*/
+
+static const struct i2c_algorithm usbvision_algo = {
+	.master_xfer   = usbvision_i2c_xfer,
+	.smbus_xfer    = NULL,
+	.functionality = functionality,
+};
+
+
+/* ----------------------------------------------------------------------- */
+/* usbvision specific I2C functions                                        */
+/* ----------------------------------------------------------------------- */
+static const struct i2c_adapter i2c_adap_template;
+
+int usbvision_i2c_register(struct usb_usbvision *usbvision)
+{
+	static unsigned short saa711x_addrs[] = {
+		0x4a >> 1, 0x48 >> 1,	/* SAA7111, SAA7111A and SAA7113 */
+		0x42 >> 1, 0x40 >> 1,	/* SAA7114, SAA7115 and SAA7118 */
+		I2C_CLIENT_END };
+
+	if (usbvision->registered_i2c)
+		return 0;
+
+	usbvision->i2c_adap = i2c_adap_template;
+
+	snprintf(usbvision->i2c_adap.name, sizeof(usbvision->i2c_adap.name),
+		 "usbvision-%d-%s",
+		 usbvision->dev->bus->busnum, usbvision->dev->devpath);
+	PDEBUG(DBG_I2C, "Adaptername: %s", usbvision->i2c_adap.name);
+	usbvision->i2c_adap.dev.parent = &usbvision->dev->dev;
+
+	i2c_set_adapdata(&usbvision->i2c_adap, &usbvision->v4l2_dev);
+
+	if (usbvision_write_reg(usbvision, USBVISION_SER_MODE, USBVISION_IIC_LRNACK) < 0) {
+		printk(KERN_ERR "usbvision_i2c_register: can't write reg\n");
+		return -EBUSY;
+	}
+
+	PDEBUG(DBG_I2C, "I2C   debugging is enabled [i2c]");
+	PDEBUG(DBG_I2C, "ALGO   debugging is enabled [i2c]");
+
+	/* register new adapter to i2c module... */
+
+	usbvision->i2c_adap.algo = &usbvision_algo;
+
+	usbvision->i2c_adap.timeout = 100;	/* default values, should       */
+	usbvision->i2c_adap.retries = 3;	/* be replaced by defines       */
+
+	i2c_add_adapter(&usbvision->i2c_adap);
+
+	PDEBUG(DBG_I2C, "i2c bus for %s registered", usbvision->i2c_adap.name);
+
+	/* Request the load of the i2c modules we need */
+	switch (usbvision_device_data[usbvision->dev_model].codec) {
+	case CODEC_SAA7113:
+	case CODEC_SAA7111:
+		/* Without this delay the detection of the saa711x is
+		   hit-and-miss. */
+		mdelay(10);
+		v4l2_i2c_new_subdev(&usbvision->v4l2_dev,
+				&usbvision->i2c_adap,
+				"saa7115_auto", 0, saa711x_addrs);
+		break;
+	}
+	if (usbvision_device_data[usbvision->dev_model].tuner == 1) {
+		struct v4l2_subdev *sd;
+		enum v4l2_i2c_tuner_type type;
+		struct tuner_setup tun_setup;
+
+		sd = v4l2_i2c_new_subdev(&usbvision->v4l2_dev,
+				&usbvision->i2c_adap,
+				"tuner", 0, v4l2_i2c_tuner_addrs(ADDRS_DEMOD));
+		/* depending on whether we found a demod or not, select
+		   the tuner type. */
+		type = sd ? ADDRS_TV_WITH_DEMOD : ADDRS_TV;
+
+		sd = v4l2_i2c_new_subdev(&usbvision->v4l2_dev,
+				&usbvision->i2c_adap,
+				"tuner", 0, v4l2_i2c_tuner_addrs(type));
+
+		if (sd == NULL)
+			return -ENODEV;
+		if (usbvision->tuner_type != -1) {
+			tun_setup.mode_mask = T_ANALOG_TV | T_RADIO;
+			tun_setup.type = usbvision->tuner_type;
+			tun_setup.addr = v4l2_i2c_subdev_addr(sd);
+			call_all(usbvision, tuner, s_type_addr, &tun_setup);
+		}
+	}
+	usbvision->registered_i2c = 1;
+
+	return 0;
+}
+
+int usbvision_i2c_unregister(struct usb_usbvision *usbvision)
+{
+	if (!usbvision->registered_i2c)
+		return 0;
+
+	i2c_del_adapter(&(usbvision->i2c_adap));
+	usbvision->registered_i2c = 0;
+
+	PDEBUG(DBG_I2C, "i2c bus for %s unregistered", usbvision->i2c_adap.name);
+
+	return 0;
+}
+
+static int
+usbvision_i2c_read_max4(struct usb_usbvision *usbvision, unsigned char addr,
+		     char *buf, short len)
+{
+	int rc, retries;
+
+	for (retries = 5;;) {
+		rc = usbvision_write_reg(usbvision, USBVISION_SER_ADRS, addr);
+		if (rc < 0)
+			return rc;
+
+		/* Initiate byte read cycle                    */
+		/* USBVISION_SER_CONT <- d0-d2 n. of bytes to r/w */
+		/*                    d3 0=Wr 1=Rd             */
+		rc = usbvision_write_reg(usbvision, USBVISION_SER_CONT,
+				      (len & 0x07) | 0x18);
+		if (rc < 0)
+			return rc;
+
+		/* Test for Busy and ACK */
+		do {
+			/* USBVISION_SER_CONT -> d4 == 0 busy */
+			rc = usbvision_read_reg(usbvision, USBVISION_SER_CONT);
+		} while (rc > 0 && ((rc & 0x10) != 0));	/* Retry while busy */
+		if (rc < 0)
+			return rc;
+
+		/* USBVISION_SER_CONT -> d5 == 1 Not ack */
+		if ((rc & 0x20) == 0)	/* Ack? */
+			break;
+
+		/* I2C abort */
+		rc = usbvision_write_reg(usbvision, USBVISION_SER_CONT, 0x00);
+		if (rc < 0)
+			return rc;
+
+		if (--retries < 0)
+			return -1;
+	}
+
+	switch (len) {
+	case 4:
+		buf[3] = usbvision_read_reg(usbvision, USBVISION_SER_DAT4);
+		/* fall through */
+	case 3:
+		buf[2] = usbvision_read_reg(usbvision, USBVISION_SER_DAT3);
+		/* fall through */
+	case 2:
+		buf[1] = usbvision_read_reg(usbvision, USBVISION_SER_DAT2);
+		/* fall through */
+	case 1:
+		buf[0] = usbvision_read_reg(usbvision, USBVISION_SER_DAT1);
+		break;
+	default:
+		printk(KERN_ERR
+		       "usbvision_i2c_read_max4: buffer length > 4\n");
+	}
+
+	if (i2c_debug & DBG_I2C) {
+		int idx;
+
+		for (idx = 0; idx < len; idx++)
+			PDEBUG(DBG_I2C, "read %x from address %x", (unsigned char)buf[idx], addr);
+	}
+	return len;
+}
+
+
+static int usbvision_i2c_write_max4(struct usb_usbvision *usbvision,
+				 unsigned char addr, const char *buf,
+				 short len)
+{
+	int rc, retries;
+	int i;
+	unsigned char *value = usbvision->ctrl_urb_buffer;
+	unsigned char ser_cont;
+
+	ser_cont = (len & 0x07) | 0x10;
+
+	value[0] = addr;
+	value[1] = ser_cont;
+	for (i = 0; i < len; i++)
+		value[i + 2] = buf[i];
+
+	for (retries = 5;;) {
+		rc = usb_control_msg(usbvision->dev,
+				     usb_sndctrlpipe(usbvision->dev, 1),
+				     USBVISION_OP_CODE,
+				     USB_DIR_OUT | USB_TYPE_VENDOR |
+				     USB_RECIP_ENDPOINT, 0,
+				     (__u16) USBVISION_SER_ADRS, value,
+				     len + 2, HZ);
+
+		if (rc < 0)
+			return rc;
+
+		rc = usbvision_write_reg(usbvision, USBVISION_SER_CONT,
+				      (len & 0x07) | 0x10);
+		if (rc < 0)
+			return rc;
+
+		/* Test for Busy and ACK */
+		do {
+			rc = usbvision_read_reg(usbvision, USBVISION_SER_CONT);
+		} while (rc > 0 && ((rc & 0x10) != 0));	/* Retry while busy */
+		if (rc < 0)
+			return rc;
+
+		if ((rc & 0x20) == 0)	/* Ack? */
+			break;
+
+		/* I2C abort */
+		usbvision_write_reg(usbvision, USBVISION_SER_CONT, 0x00);
+
+		if (--retries < 0)
+			return -1;
+
+	}
+
+	if (i2c_debug & DBG_I2C) {
+		int idx;
+
+		for (idx = 0; idx < len; idx++)
+			PDEBUG(DBG_I2C, "wrote %x at address %x", (unsigned char)buf[idx], addr);
+	}
+	return len;
+}
+
+static int usbvision_i2c_write(struct usb_usbvision *usbvision, unsigned char addr, char *buf,
+			    short len)
+{
+	char *buf_ptr = buf;
+	int retval;
+	int wrcount = 0;
+	int count;
+	int max_len = 4;
+
+	while (len > 0) {
+		count = (len > max_len) ? max_len : len;
+		retval = usbvision_i2c_write_max4(usbvision, addr, buf_ptr, count);
+		if (retval > 0) {
+			len -= count;
+			buf_ptr += count;
+			wrcount += count;
+		} else
+			return (retval < 0) ? retval : -EFAULT;
+	}
+	return wrcount;
+}
+
+static int usbvision_i2c_read(struct usb_usbvision *usbvision, unsigned char addr, char *buf,
+			   short len)
+{
+	char temp[4];
+	int retval, i;
+	int rdcount = 0;
+	int count;
+
+	while (len > 0) {
+		count = (len > 3) ? 4 : len;
+		retval = usbvision_i2c_read_max4(usbvision, addr, temp, count);
+		if (retval > 0) {
+			for (i = 0; i < len; i++)
+				buf[rdcount + i] = temp[i];
+			len -= count;
+			rdcount += count;
+		} else
+			return (retval < 0) ? retval : -EFAULT;
+	}
+	return rdcount;
+}
+
+static const struct i2c_adapter i2c_adap_template = {
+	.owner = THIS_MODULE,
+	.name              = "usbvision",
+};
diff --git a/drivers/media/usb/usbvision/usbvision-video.c b/drivers/media/usb/usbvision/usbvision-video.c
new file mode 100644
index 0000000..f29d1be
--- /dev/null
+++ b/drivers/media/usb/usbvision/usbvision-video.c
@@ -0,0 +1,1647 @@
+/*
+ * USB USBVISION Video device driver 0.9.10
+ *
+ *
+ *
+ * Copyright (c) 1999-2005 Joerg Heckenbach <joerg@heckenbach-aw.de>
+ *
+ * This module is part of usbvision driver project.
+ *
+ * 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.
+ *
+ * Let's call the version 0.... until compression decoding is completely
+ * implemented.
+ *
+ * This driver is written by Jose Ignacio Gijon and Joerg Heckenbach.
+ * It was based on USB CPiA driver written by Peter Pregler,
+ * Scott J. Bertin and Johannes Erdfelt
+ * Ideas are taken from bttv driver by Ralph Metzler, Marcus Metzler &
+ * Gerd Knorr and zoran 36120/36125 driver by Pauline Middelink
+ * Updates to driver completed by Dwaine P. Garden
+ *
+ *
+ * TODO:
+ *     - use submit_urb for all setup packets
+ *     - Fix memory settings for nt1004. It is 4 times as big as the
+ *       nt1003 memory.
+ *     - Add audio on endpoint 3 for nt1004 chip.
+ *         Seems impossible, needs a codec interface.  Which one?
+ *     - Clean up the driver.
+ *     - optimization for performance.
+ *     - Add Videotext capability (VBI).  Working on it.....
+ *     - Check audio for other devices
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/timer.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/highmem.h>
+#include <linux/vmalloc.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/spinlock.h>
+#include <linux/io.h>
+#include <linux/videodev2.h>
+#include <linux/i2c.h>
+
+#include <media/i2c/saa7115.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-event.h>
+#include <media/tuner.h>
+
+#include <linux/workqueue.h>
+
+#include "usbvision.h"
+#include "usbvision-cards.h"
+
+#define DRIVER_AUTHOR					\
+	"Joerg Heckenbach <joerg@heckenbach-aw.de>, "	\
+	"Dwaine Garden <DwaineGarden@rogers.com>"
+#define DRIVER_NAME "usbvision"
+#define DRIVER_ALIAS "USBVision"
+#define DRIVER_DESC "USBVision USB Video Device Driver for Linux"
+#define USBVISION_VERSION_STRING "0.9.11"
+
+#define	ENABLE_HEXDUMP	0	/* Enable if you need it */
+
+
+#ifdef USBVISION_DEBUG
+	#define PDEBUG(level, fmt, args...) { \
+		if (video_debug & (level)) \
+			printk(KERN_INFO KBUILD_MODNAME ":[%s:%d] " fmt, \
+				__func__, __LINE__ , ## args); \
+	}
+#else
+	#define PDEBUG(level, fmt, args...) do {} while (0)
+#endif
+
+#define DBG_IO		(1 << 1)
+#define DBG_PROBE	(1 << 2)
+#define DBG_MMAP	(1 << 3)
+
+/* String operations */
+#define rmspace(str)	while (*str == ' ') str++;
+#define goto2next(str)	while (*str != ' ') str++; while (*str == ' ') str++;
+
+
+/* sequential number of usbvision device */
+static int usbvision_nr;
+
+static struct usbvision_v4l2_format_st usbvision_v4l2_format[] = {
+	{ 1, 1,  8, V4L2_PIX_FMT_GREY    , "GREY" },
+	{ 1, 2, 16, V4L2_PIX_FMT_RGB565  , "RGB565" },
+	{ 1, 3, 24, V4L2_PIX_FMT_RGB24   , "RGB24" },
+	{ 1, 4, 32, V4L2_PIX_FMT_RGB32   , "RGB32" },
+	{ 1, 2, 16, V4L2_PIX_FMT_RGB555  , "RGB555" },
+	{ 1, 2, 16, V4L2_PIX_FMT_YUYV    , "YUV422" },
+	{ 1, 2, 12, V4L2_PIX_FMT_YVU420  , "YUV420P" }, /* 1.5 ! */
+	{ 1, 2, 16, V4L2_PIX_FMT_YUV422P , "YUV422P" }
+};
+
+/* Function prototypes */
+static void usbvision_release(struct usb_usbvision *usbvision);
+
+/* Default initialization of device driver parameters */
+/* Set the default format for ISOC endpoint */
+static int isoc_mode = ISOC_MODE_COMPRESS;
+/* Set the default Debug Mode of the device driver */
+static int video_debug;
+/* Sequential Number of Video Device */
+static int video_nr = -1;
+/* Sequential Number of Radio Device */
+static int radio_nr = -1;
+
+/* Grab parameters for the device driver */
+
+/* Showing parameters under SYSFS */
+module_param(isoc_mode, int, 0444);
+module_param(video_debug, int, 0444);
+module_param(video_nr, int, 0444);
+module_param(radio_nr, int, 0444);
+
+MODULE_PARM_DESC(isoc_mode, " Set the default format for ISOC endpoint.  Default: 0x60 (Compression On)");
+MODULE_PARM_DESC(video_debug, " Set the default Debug Mode of the device driver.  Default: 0 (Off)");
+MODULE_PARM_DESC(video_nr, "Set video device number (/dev/videoX).  Default: -1 (autodetect)");
+MODULE_PARM_DESC(radio_nr, "Set radio device number (/dev/radioX).  Default: -1 (autodetect)");
+
+
+/* Misc stuff */
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+MODULE_VERSION(USBVISION_VERSION_STRING);
+MODULE_ALIAS(DRIVER_ALIAS);
+
+
+/*****************************************************************************/
+/* SYSFS Code - Copied from the stv680.c usb module.			     */
+/* Device information is located at /sys/class/video4linux/video0            */
+/* Device parameters information is located at /sys/module/usbvision         */
+/* Device USB Information is located at                                      */
+/*   /sys/bus/usb/drivers/USBVision Video Grabber                            */
+/*****************************************************************************/
+
+#define YES_NO(x) ((x) ? "Yes" : "No")
+
+static inline struct usb_usbvision *cd_to_usbvision(struct device *cd)
+{
+	struct video_device *vdev = to_video_device(cd);
+	return video_get_drvdata(vdev);
+}
+
+static ssize_t show_version(struct device *cd,
+			    struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, "%s\n", USBVISION_VERSION_STRING);
+}
+static DEVICE_ATTR(version, S_IRUGO, show_version, NULL);
+
+static ssize_t show_model(struct device *cd,
+			  struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(cd);
+	struct usb_usbvision *usbvision = video_get_drvdata(vdev);
+	return sprintf(buf, "%s\n",
+		       usbvision_device_data[usbvision->dev_model].model_string);
+}
+static DEVICE_ATTR(model, S_IRUGO, show_model, NULL);
+
+static ssize_t show_hue(struct device *cd,
+			struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(cd);
+	struct usb_usbvision *usbvision = video_get_drvdata(vdev);
+	s32 val = v4l2_ctrl_g_ctrl(v4l2_ctrl_find(&usbvision->hdl,
+						  V4L2_CID_HUE));
+
+	return sprintf(buf, "%d\n", val);
+}
+static DEVICE_ATTR(hue, S_IRUGO, show_hue, NULL);
+
+static ssize_t show_contrast(struct device *cd,
+			     struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(cd);
+	struct usb_usbvision *usbvision = video_get_drvdata(vdev);
+	s32 val = v4l2_ctrl_g_ctrl(v4l2_ctrl_find(&usbvision->hdl,
+						  V4L2_CID_CONTRAST));
+
+	return sprintf(buf, "%d\n", val);
+}
+static DEVICE_ATTR(contrast, S_IRUGO, show_contrast, NULL);
+
+static ssize_t show_brightness(struct device *cd,
+			       struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(cd);
+	struct usb_usbvision *usbvision = video_get_drvdata(vdev);
+	s32 val = v4l2_ctrl_g_ctrl(v4l2_ctrl_find(&usbvision->hdl,
+						  V4L2_CID_BRIGHTNESS));
+
+	return sprintf(buf, "%d\n", val);
+}
+static DEVICE_ATTR(brightness, S_IRUGO, show_brightness, NULL);
+
+static ssize_t show_saturation(struct device *cd,
+			       struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(cd);
+	struct usb_usbvision *usbvision = video_get_drvdata(vdev);
+	s32 val = v4l2_ctrl_g_ctrl(v4l2_ctrl_find(&usbvision->hdl,
+						  V4L2_CID_SATURATION));
+
+	return sprintf(buf, "%d\n", val);
+}
+static DEVICE_ATTR(saturation, S_IRUGO, show_saturation, NULL);
+
+static ssize_t show_streaming(struct device *cd,
+			      struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(cd);
+	struct usb_usbvision *usbvision = video_get_drvdata(vdev);
+	return sprintf(buf, "%s\n",
+		       YES_NO(usbvision->streaming == stream_on ? 1 : 0));
+}
+static DEVICE_ATTR(streaming, S_IRUGO, show_streaming, NULL);
+
+static ssize_t show_compression(struct device *cd,
+				struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(cd);
+	struct usb_usbvision *usbvision = video_get_drvdata(vdev);
+	return sprintf(buf, "%s\n",
+		       YES_NO(usbvision->isoc_mode == ISOC_MODE_COMPRESS));
+}
+static DEVICE_ATTR(compression, S_IRUGO, show_compression, NULL);
+
+static ssize_t show_device_bridge(struct device *cd,
+				  struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(cd);
+	struct usb_usbvision *usbvision = video_get_drvdata(vdev);
+	return sprintf(buf, "%d\n", usbvision->bridge_type);
+}
+static DEVICE_ATTR(bridge, S_IRUGO, show_device_bridge, NULL);
+
+static void usbvision_create_sysfs(struct video_device *vdev)
+{
+	int res;
+
+	if (!vdev)
+		return;
+	do {
+		res = device_create_file(&vdev->dev, &dev_attr_version);
+		if (res < 0)
+			break;
+		res = device_create_file(&vdev->dev, &dev_attr_model);
+		if (res < 0)
+			break;
+		res = device_create_file(&vdev->dev, &dev_attr_hue);
+		if (res < 0)
+			break;
+		res = device_create_file(&vdev->dev, &dev_attr_contrast);
+		if (res < 0)
+			break;
+		res = device_create_file(&vdev->dev, &dev_attr_brightness);
+		if (res < 0)
+			break;
+		res = device_create_file(&vdev->dev, &dev_attr_saturation);
+		if (res < 0)
+			break;
+		res = device_create_file(&vdev->dev, &dev_attr_streaming);
+		if (res < 0)
+			break;
+		res = device_create_file(&vdev->dev, &dev_attr_compression);
+		if (res < 0)
+			break;
+		res = device_create_file(&vdev->dev, &dev_attr_bridge);
+		if (res >= 0)
+			return;
+	} while (0);
+
+	dev_err(&vdev->dev, "%s error: %d\n", __func__, res);
+}
+
+static void usbvision_remove_sysfs(struct video_device *vdev)
+{
+	if (vdev) {
+		device_remove_file(&vdev->dev, &dev_attr_version);
+		device_remove_file(&vdev->dev, &dev_attr_model);
+		device_remove_file(&vdev->dev, &dev_attr_hue);
+		device_remove_file(&vdev->dev, &dev_attr_contrast);
+		device_remove_file(&vdev->dev, &dev_attr_brightness);
+		device_remove_file(&vdev->dev, &dev_attr_saturation);
+		device_remove_file(&vdev->dev, &dev_attr_streaming);
+		device_remove_file(&vdev->dev, &dev_attr_compression);
+		device_remove_file(&vdev->dev, &dev_attr_bridge);
+	}
+}
+
+/*
+ * usbvision_open()
+ *
+ * This is part of Video 4 Linux API. The driver can be opened by one
+ * client only (checks internal counter 'usbvision->user'). The procedure
+ * then allocates buffers needed for video processing.
+ *
+ */
+static int usbvision_v4l2_open(struct file *file)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+	int err_code = 0;
+
+	PDEBUG(DBG_IO, "open");
+
+	if (mutex_lock_interruptible(&usbvision->v4l2_lock))
+		return -ERESTARTSYS;
+
+	if (usbvision->user) {
+		err_code = -EBUSY;
+	} else {
+		err_code = v4l2_fh_open(file);
+		if (err_code)
+			goto unlock;
+
+		/* Allocate memory for the scratch ring buffer */
+		err_code = usbvision_scratch_alloc(usbvision);
+		if (isoc_mode == ISOC_MODE_COMPRESS) {
+			/* Allocate intermediate decompression buffers
+			   only if needed */
+			err_code = usbvision_decompress_alloc(usbvision);
+		}
+		if (err_code) {
+			/* Deallocate all buffers if trouble */
+			usbvision_scratch_free(usbvision);
+			usbvision_decompress_free(usbvision);
+		}
+	}
+
+	/* If so far no errors then we shall start the camera */
+	if (!err_code) {
+		/* Send init sequence only once, it's large! */
+		if (!usbvision->initialized) {
+			int setup_ok = 0;
+			setup_ok = usbvision_setup(usbvision, isoc_mode);
+			if (setup_ok)
+				usbvision->initialized = 1;
+			else
+				err_code = -EBUSY;
+		}
+
+		if (!err_code) {
+			usbvision_begin_streaming(usbvision);
+			err_code = usbvision_init_isoc(usbvision);
+			/* device must be initialized before isoc transfer */
+			usbvision_muxsel(usbvision, 0);
+
+			/* prepare queues */
+			usbvision_empty_framequeues(usbvision);
+			usbvision->user++;
+		}
+	}
+
+unlock:
+	mutex_unlock(&usbvision->v4l2_lock);
+
+	PDEBUG(DBG_IO, "success");
+	return err_code;
+}
+
+/*
+ * usbvision_v4l2_close()
+ *
+ * This is part of Video 4 Linux API. The procedure
+ * stops streaming and deallocates all buffers that were earlier
+ * allocated in usbvision_v4l2_open().
+ *
+ */
+static int usbvision_v4l2_close(struct file *file)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+
+	PDEBUG(DBG_IO, "close");
+
+	mutex_lock(&usbvision->v4l2_lock);
+	usbvision_audio_off(usbvision);
+	usbvision_restart_isoc(usbvision);
+	usbvision_stop_isoc(usbvision);
+
+	usbvision_decompress_free(usbvision);
+	usbvision_frames_free(usbvision);
+	usbvision_empty_framequeues(usbvision);
+	usbvision_scratch_free(usbvision);
+
+	usbvision->user--;
+	mutex_unlock(&usbvision->v4l2_lock);
+
+	if (usbvision->remove_pending) {
+		printk(KERN_INFO "%s: Final disconnect\n", __func__);
+		usbvision_release(usbvision);
+		return 0;
+	}
+
+	PDEBUG(DBG_IO, "success");
+	return v4l2_fh_release(file);
+}
+
+
+/*
+ * usbvision_ioctl()
+ *
+ * This is part of Video 4 Linux API. The procedure handles ioctl() calls.
+ *
+ */
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int vidioc_g_register(struct file *file, void *priv,
+				struct v4l2_dbg_register *reg)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+	int err_code;
+
+	/* NT100x has a 8-bit register space */
+	err_code = usbvision_read_reg(usbvision, reg->reg&0xff);
+	if (err_code < 0) {
+		dev_err(&usbvision->vdev.dev,
+			"%s: VIDIOC_DBG_G_REGISTER failed: error %d\n",
+				__func__, err_code);
+		return err_code;
+	}
+	reg->val = err_code;
+	reg->size = 1;
+	return 0;
+}
+
+static int vidioc_s_register(struct file *file, void *priv,
+				const struct v4l2_dbg_register *reg)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+	int err_code;
+
+	/* NT100x has a 8-bit register space */
+	err_code = usbvision_write_reg(usbvision, reg->reg & 0xff, reg->val);
+	if (err_code < 0) {
+		dev_err(&usbvision->vdev.dev,
+			"%s: VIDIOC_DBG_S_REGISTER failed: error %d\n",
+				__func__, err_code);
+		return err_code;
+	}
+	return 0;
+}
+#endif
+
+static int vidioc_querycap(struct file *file, void  *priv,
+					struct v4l2_capability *vc)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+	struct video_device *vdev = video_devdata(file);
+
+	strlcpy(vc->driver, "USBVision", sizeof(vc->driver));
+	strlcpy(vc->card,
+		usbvision_device_data[usbvision->dev_model].model_string,
+		sizeof(vc->card));
+	usb_make_path(usbvision->dev, vc->bus_info, sizeof(vc->bus_info));
+	vc->device_caps = usbvision->have_tuner ? V4L2_CAP_TUNER : 0;
+	if (vdev->vfl_type == VFL_TYPE_GRABBER)
+		vc->device_caps |= V4L2_CAP_VIDEO_CAPTURE |
+			V4L2_CAP_READWRITE | V4L2_CAP_STREAMING;
+	else
+		vc->device_caps |= V4L2_CAP_RADIO;
+
+	vc->capabilities = vc->device_caps | V4L2_CAP_VIDEO_CAPTURE |
+		V4L2_CAP_READWRITE | V4L2_CAP_STREAMING | V4L2_CAP_DEVICE_CAPS;
+	if (usbvision_device_data[usbvision->dev_model].radio)
+		vc->capabilities |= V4L2_CAP_RADIO;
+	return 0;
+}
+
+static int vidioc_enum_input(struct file *file, void *priv,
+				struct v4l2_input *vi)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+	int chan;
+
+	if (vi->index >= usbvision->video_inputs)
+		return -EINVAL;
+	if (usbvision->have_tuner)
+		chan = vi->index;
+	else
+		chan = vi->index + 1; /* skip Television string*/
+
+	/* Determine the requested input characteristics
+	   specific for each usbvision card model */
+	switch (chan) {
+	case 0:
+		if (usbvision_device_data[usbvision->dev_model].video_channels == 4) {
+			strcpy(vi->name, "White Video Input");
+		} else {
+			strcpy(vi->name, "Television");
+			vi->type = V4L2_INPUT_TYPE_TUNER;
+			vi->tuner = chan;
+			vi->std = USBVISION_NORMS;
+		}
+		break;
+	case 1:
+		vi->type = V4L2_INPUT_TYPE_CAMERA;
+		if (usbvision_device_data[usbvision->dev_model].video_channels == 4)
+			strcpy(vi->name, "Green Video Input");
+		else
+			strcpy(vi->name, "Composite Video Input");
+		vi->std = USBVISION_NORMS;
+		break;
+	case 2:
+		vi->type = V4L2_INPUT_TYPE_CAMERA;
+		if (usbvision_device_data[usbvision->dev_model].video_channels == 4)
+			strcpy(vi->name, "Yellow Video Input");
+		else
+			strcpy(vi->name, "S-Video Input");
+		vi->std = USBVISION_NORMS;
+		break;
+	case 3:
+		vi->type = V4L2_INPUT_TYPE_CAMERA;
+		strcpy(vi->name, "Red Video Input");
+		vi->std = USBVISION_NORMS;
+		break;
+	}
+	return 0;
+}
+
+static int vidioc_g_input(struct file *file, void *priv, unsigned int *input)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+
+	*input = usbvision->ctl_input;
+	return 0;
+}
+
+static int vidioc_s_input(struct file *file, void *priv, unsigned int input)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+
+	if (input >= usbvision->video_inputs)
+		return -EINVAL;
+
+	usbvision_muxsel(usbvision, input);
+	usbvision_set_input(usbvision);
+	usbvision_set_output(usbvision,
+			     usbvision->curwidth,
+			     usbvision->curheight);
+	return 0;
+}
+
+static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id id)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+
+	usbvision->tvnorm_id = id;
+
+	call_all(usbvision, video, s_std, usbvision->tvnorm_id);
+	/* propagate the change to the decoder */
+	usbvision_muxsel(usbvision, usbvision->ctl_input);
+
+	return 0;
+}
+
+static int vidioc_g_std(struct file *file, void *priv, v4l2_std_id *id)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+
+	*id = usbvision->tvnorm_id;
+	return 0;
+}
+
+static int vidioc_g_tuner(struct file *file, void *priv,
+				struct v4l2_tuner *vt)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+
+	if (vt->index)	/* Only tuner 0 */
+		return -EINVAL;
+	if (vt->type == V4L2_TUNER_RADIO)
+		strcpy(vt->name, "Radio");
+	else
+		strcpy(vt->name, "Television");
+
+	/* Let clients fill in the remainder of this struct */
+	call_all(usbvision, tuner, g_tuner, vt);
+
+	return 0;
+}
+
+static int vidioc_s_tuner(struct file *file, void *priv,
+				const struct v4l2_tuner *vt)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+
+	/* Only one tuner for now */
+	if (vt->index)
+		return -EINVAL;
+	/* let clients handle this */
+	call_all(usbvision, tuner, s_tuner, vt);
+
+	return 0;
+}
+
+static int vidioc_g_frequency(struct file *file, void *priv,
+				struct v4l2_frequency *freq)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+
+	/* Only one tuner */
+	if (freq->tuner)
+		return -EINVAL;
+	if (freq->type == V4L2_TUNER_RADIO)
+		freq->frequency = usbvision->radio_freq;
+	else
+		freq->frequency = usbvision->tv_freq;
+
+	return 0;
+}
+
+static int vidioc_s_frequency(struct file *file, void *priv,
+				const struct v4l2_frequency *freq)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+	struct v4l2_frequency new_freq = *freq;
+
+	/* Only one tuner for now */
+	if (freq->tuner)
+		return -EINVAL;
+
+	call_all(usbvision, tuner, s_frequency, freq);
+	call_all(usbvision, tuner, g_frequency, &new_freq);
+	if (freq->type == V4L2_TUNER_RADIO)
+		usbvision->radio_freq = new_freq.frequency;
+	else
+		usbvision->tv_freq = new_freq.frequency;
+
+	return 0;
+}
+
+static int vidioc_reqbufs(struct file *file,
+			   void *priv, struct v4l2_requestbuffers *vr)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+	int ret;
+
+	RESTRICT_TO_RANGE(vr->count, 1, USBVISION_NUMFRAMES);
+
+	/* Check input validity:
+	   the user must do a VIDEO CAPTURE and MMAP method. */
+	if (vr->memory != V4L2_MEMORY_MMAP)
+		return -EINVAL;
+
+	if (usbvision->streaming == stream_on) {
+		ret = usbvision_stream_interrupt(usbvision);
+		if (ret)
+			return ret;
+	}
+
+	usbvision_frames_free(usbvision);
+	usbvision_empty_framequeues(usbvision);
+	vr->count = usbvision_frames_alloc(usbvision, vr->count);
+
+	usbvision->cur_frame = NULL;
+
+	return 0;
+}
+
+static int vidioc_querybuf(struct file *file,
+			    void *priv, struct v4l2_buffer *vb)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+	struct usbvision_frame *frame;
+
+	/* FIXME : must control
+	   that buffers are mapped (VIDIOC_REQBUFS has been called) */
+	if (vb->index >= usbvision->num_frames)
+		return -EINVAL;
+	/* Updating the corresponding frame state */
+	vb->flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	frame = &usbvision->frame[vb->index];
+	if (frame->grabstate >= frame_state_ready)
+		vb->flags |= V4L2_BUF_FLAG_QUEUED;
+	if (frame->grabstate >= frame_state_done)
+		vb->flags |= V4L2_BUF_FLAG_DONE;
+	if (frame->grabstate == frame_state_unused)
+		vb->flags |= V4L2_BUF_FLAG_MAPPED;
+	vb->memory = V4L2_MEMORY_MMAP;
+
+	vb->m.offset = vb->index * PAGE_ALIGN(usbvision->max_frame_size);
+
+	vb->memory = V4L2_MEMORY_MMAP;
+	vb->field = V4L2_FIELD_NONE;
+	vb->length = usbvision->curwidth *
+		usbvision->curheight *
+		usbvision->palette.bytes_per_pixel;
+	vb->timestamp = usbvision->frame[vb->index].timestamp;
+	vb->sequence = usbvision->frame[vb->index].sequence;
+	return 0;
+}
+
+static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *vb)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+	struct usbvision_frame *frame;
+	unsigned long lock_flags;
+
+	/* FIXME : works only on VIDEO_CAPTURE MODE, MMAP. */
+	if (vb->index >= usbvision->num_frames)
+		return -EINVAL;
+
+	frame = &usbvision->frame[vb->index];
+
+	if (frame->grabstate != frame_state_unused)
+		return -EAGAIN;
+
+	/* Mark it as ready and enqueue frame */
+	frame->grabstate = frame_state_ready;
+	frame->scanstate = scan_state_scanning;
+	frame->scanlength = 0;	/* Accumulated in usbvision_parse_data() */
+
+	vb->flags &= ~V4L2_BUF_FLAG_DONE;
+
+	/* set v4l2_format index */
+	frame->v4l2_format = usbvision->palette;
+
+	spin_lock_irqsave(&usbvision->queue_lock, lock_flags);
+	list_add_tail(&usbvision->frame[vb->index].frame, &usbvision->inqueue);
+	spin_unlock_irqrestore(&usbvision->queue_lock, lock_flags);
+
+	return 0;
+}
+
+static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *vb)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+	int ret;
+	struct usbvision_frame *f;
+	unsigned long lock_flags;
+
+	if (list_empty(&(usbvision->outqueue))) {
+		if (usbvision->streaming == stream_idle)
+			return -EINVAL;
+		ret = wait_event_interruptible
+			(usbvision->wait_frame,
+			 !list_empty(&(usbvision->outqueue)));
+		if (ret)
+			return ret;
+	}
+
+	spin_lock_irqsave(&usbvision->queue_lock, lock_flags);
+	f = list_entry(usbvision->outqueue.next,
+		       struct usbvision_frame, frame);
+	list_del(usbvision->outqueue.next);
+	spin_unlock_irqrestore(&usbvision->queue_lock, lock_flags);
+
+	f->grabstate = frame_state_unused;
+
+	vb->memory = V4L2_MEMORY_MMAP;
+	vb->flags = V4L2_BUF_FLAG_MAPPED |
+		V4L2_BUF_FLAG_QUEUED |
+		V4L2_BUF_FLAG_DONE |
+		V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	vb->index = f->index;
+	vb->sequence = f->sequence;
+	vb->timestamp = f->timestamp;
+	vb->field = V4L2_FIELD_NONE;
+	vb->bytesused = f->scanlength;
+
+	return 0;
+}
+
+static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+
+	usbvision->streaming = stream_on;
+	call_all(usbvision, video, s_stream, 1);
+
+	return 0;
+}
+
+static int vidioc_streamoff(struct file *file,
+			    void *priv, enum v4l2_buf_type type)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+
+	if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	if (usbvision->streaming == stream_on) {
+		usbvision_stream_interrupt(usbvision);
+		/* Stop all video streamings */
+		call_all(usbvision, video, s_stream, 0);
+	}
+	usbvision_empty_framequeues(usbvision);
+
+	return 0;
+}
+
+static int vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
+					struct v4l2_fmtdesc *vfd)
+{
+	if (vfd->index >= USBVISION_SUPPORTED_PALETTES - 1)
+		return -EINVAL;
+	strcpy(vfd->description, usbvision_v4l2_format[vfd->index].desc);
+	vfd->pixelformat = usbvision_v4l2_format[vfd->index].format;
+	return 0;
+}
+
+static int vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+					struct v4l2_format *vf)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+	vf->fmt.pix.width = usbvision->curwidth;
+	vf->fmt.pix.height = usbvision->curheight;
+	vf->fmt.pix.pixelformat = usbvision->palette.format;
+	vf->fmt.pix.bytesperline =
+		usbvision->curwidth * usbvision->palette.bytes_per_pixel;
+	vf->fmt.pix.sizeimage = vf->fmt.pix.bytesperline * usbvision->curheight;
+	vf->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+	vf->fmt.pix.field = V4L2_FIELD_NONE; /* Always progressive image */
+
+	return 0;
+}
+
+static int vidioc_try_fmt_vid_cap(struct file *file, void *priv,
+			       struct v4l2_format *vf)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+	int format_idx;
+
+	/* Find requested format in available ones */
+	for (format_idx = 0; format_idx < USBVISION_SUPPORTED_PALETTES; format_idx++) {
+		if (vf->fmt.pix.pixelformat ==
+		   usbvision_v4l2_format[format_idx].format) {
+			usbvision->palette = usbvision_v4l2_format[format_idx];
+			break;
+		}
+	}
+	/* robustness */
+	if (format_idx == USBVISION_SUPPORTED_PALETTES)
+		return -EINVAL;
+	RESTRICT_TO_RANGE(vf->fmt.pix.width, MIN_FRAME_WIDTH, MAX_FRAME_WIDTH);
+	RESTRICT_TO_RANGE(vf->fmt.pix.height, MIN_FRAME_HEIGHT, MAX_FRAME_HEIGHT);
+
+	vf->fmt.pix.bytesperline = vf->fmt.pix.width*
+		usbvision->palette.bytes_per_pixel;
+	vf->fmt.pix.sizeimage = vf->fmt.pix.bytesperline*vf->fmt.pix.height;
+	vf->fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M;
+	vf->fmt.pix.field = V4L2_FIELD_NONE; /* Always progressive image */
+
+	return 0;
+}
+
+static int vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+			       struct v4l2_format *vf)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+	int ret;
+
+	ret = vidioc_try_fmt_vid_cap(file, priv, vf);
+	if (ret)
+		return ret;
+
+	/* stop io in case it is already in progress */
+	if (usbvision->streaming == stream_on) {
+		ret = usbvision_stream_interrupt(usbvision);
+		if (ret)
+			return ret;
+	}
+	usbvision_frames_free(usbvision);
+	usbvision_empty_framequeues(usbvision);
+
+	usbvision->cur_frame = NULL;
+
+	/* by now we are committed to the new data... */
+	usbvision_set_output(usbvision, vf->fmt.pix.width, vf->fmt.pix.height);
+
+	return 0;
+}
+
+static ssize_t usbvision_read(struct file *file, char __user *buf,
+		      size_t count, loff_t *ppos)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+	int noblock = file->f_flags & O_NONBLOCK;
+	unsigned long lock_flags;
+	int ret, i;
+	struct usbvision_frame *frame;
+
+	PDEBUG(DBG_IO, "%s: %ld bytes, noblock=%d", __func__,
+	       (unsigned long)count, noblock);
+
+	if (!USBVISION_IS_OPERATIONAL(usbvision) || !buf)
+		return -EFAULT;
+
+	/* This entry point is compatible with the mmap routines
+	   so that a user can do either VIDIOC_QBUF/VIDIOC_DQBUF
+	   to get frames or call read on the device. */
+	if (!usbvision->num_frames) {
+		/* First, allocate some frames to work with
+		   if this has not been done with VIDIOC_REQBUF */
+		usbvision_frames_free(usbvision);
+		usbvision_empty_framequeues(usbvision);
+		usbvision_frames_alloc(usbvision, USBVISION_NUMFRAMES);
+	}
+
+	if (usbvision->streaming != stream_on) {
+		/* no stream is running, make it running ! */
+		usbvision->streaming = stream_on;
+		call_all(usbvision, video, s_stream, 1);
+	}
+
+	/* Then, enqueue as many frames as possible
+	   (like a user of VIDIOC_QBUF would do) */
+	for (i = 0; i < usbvision->num_frames; i++) {
+		frame = &usbvision->frame[i];
+		if (frame->grabstate == frame_state_unused) {
+			/* Mark it as ready and enqueue frame */
+			frame->grabstate = frame_state_ready;
+			frame->scanstate = scan_state_scanning;
+			/* Accumulated in usbvision_parse_data() */
+			frame->scanlength = 0;
+
+			/* set v4l2_format index */
+			frame->v4l2_format = usbvision->palette;
+
+			spin_lock_irqsave(&usbvision->queue_lock, lock_flags);
+			list_add_tail(&frame->frame, &usbvision->inqueue);
+			spin_unlock_irqrestore(&usbvision->queue_lock,
+					       lock_flags);
+		}
+	}
+
+	/* Then try to steal a frame (like a VIDIOC_DQBUF would do) */
+	if (list_empty(&(usbvision->outqueue))) {
+		if (noblock)
+			return -EAGAIN;
+
+		ret = wait_event_interruptible
+			(usbvision->wait_frame,
+			 !list_empty(&(usbvision->outqueue)));
+		if (ret)
+			return ret;
+	}
+
+	spin_lock_irqsave(&usbvision->queue_lock, lock_flags);
+	frame = list_entry(usbvision->outqueue.next,
+			   struct usbvision_frame, frame);
+	list_del(usbvision->outqueue.next);
+	spin_unlock_irqrestore(&usbvision->queue_lock, lock_flags);
+
+	/* An error returns an empty frame */
+	if (frame->grabstate == frame_state_error) {
+		frame->bytes_read = 0;
+		return 0;
+	}
+
+	PDEBUG(DBG_IO, "%s: frmx=%d, bytes_read=%ld, scanlength=%ld",
+	       __func__,
+	       frame->index, frame->bytes_read, frame->scanlength);
+
+	/* copy bytes to user space; we allow for partials reads */
+	if ((count + frame->bytes_read) > (unsigned long)frame->scanlength)
+		count = frame->scanlength - frame->bytes_read;
+
+	if (copy_to_user(buf, frame->data + frame->bytes_read, count))
+		return -EFAULT;
+
+	frame->bytes_read += count;
+	PDEBUG(DBG_IO, "%s: {copy} count used=%ld, new bytes_read=%ld",
+	       __func__,
+	       (unsigned long)count, frame->bytes_read);
+
+#if 1
+	/*
+	 * FIXME:
+	 * For now, forget the frame if it has not been read in one shot.
+	 */
+	frame->bytes_read = 0;
+
+	/* Mark it as available to be used again. */
+	frame->grabstate = frame_state_unused;
+#else
+	if (frame->bytes_read >= frame->scanlength) {
+		/* All data has been read */
+		frame->bytes_read = 0;
+
+		/* Mark it as available to be used again. */
+		frame->grabstate = frame_state_unused;
+	}
+#endif
+
+	return count;
+}
+
+static ssize_t usbvision_v4l2_read(struct file *file, char __user *buf,
+		      size_t count, loff_t *ppos)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+	int res;
+
+	if (mutex_lock_interruptible(&usbvision->v4l2_lock))
+		return -ERESTARTSYS;
+	res = usbvision_read(file, buf, count, ppos);
+	mutex_unlock(&usbvision->v4l2_lock);
+	return res;
+}
+
+static int usbvision_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	unsigned long size = vma->vm_end - vma->vm_start,
+		start = vma->vm_start;
+	void *pos;
+	u32 i;
+	struct usb_usbvision *usbvision = video_drvdata(file);
+
+	PDEBUG(DBG_MMAP, "mmap");
+
+	if (!USBVISION_IS_OPERATIONAL(usbvision))
+		return -EFAULT;
+
+	if (!(vma->vm_flags & VM_WRITE) ||
+	    size != PAGE_ALIGN(usbvision->max_frame_size)) {
+		return -EINVAL;
+	}
+
+	for (i = 0; i < usbvision->num_frames; i++) {
+		if (((PAGE_ALIGN(usbvision->max_frame_size)*i) >> PAGE_SHIFT) ==
+		    vma->vm_pgoff)
+			break;
+	}
+	if (i == usbvision->num_frames) {
+		PDEBUG(DBG_MMAP,
+		       "mmap: user supplied mapping address is out of range");
+		return -EINVAL;
+	}
+
+	/* VM_IO is eventually going to replace PageReserved altogether */
+	vma->vm_flags |= VM_IO | VM_DONTEXPAND | VM_DONTDUMP;
+
+	pos = usbvision->frame[i].data;
+	while (size > 0) {
+		if (vm_insert_page(vma, start, vmalloc_to_page(pos))) {
+			PDEBUG(DBG_MMAP, "mmap: vm_insert_page failed");
+			return -EAGAIN;
+		}
+		start += PAGE_SIZE;
+		pos += PAGE_SIZE;
+		size -= PAGE_SIZE;
+	}
+
+	return 0;
+}
+
+static int usbvision_v4l2_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+	int res;
+
+	if (mutex_lock_interruptible(&usbvision->v4l2_lock))
+		return -ERESTARTSYS;
+	res = usbvision_mmap(file, vma);
+	mutex_unlock(&usbvision->v4l2_lock);
+	return res;
+}
+
+/*
+ * Here comes the stuff for radio on usbvision based devices
+ *
+ */
+static int usbvision_radio_open(struct file *file)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+	int err_code = 0;
+
+	PDEBUG(DBG_IO, "%s:", __func__);
+
+	if (mutex_lock_interruptible(&usbvision->v4l2_lock))
+		return -ERESTARTSYS;
+	err_code = v4l2_fh_open(file);
+	if (err_code)
+		goto out;
+	if (usbvision->user) {
+		dev_err(&usbvision->rdev.dev,
+			"%s: Someone tried to open an already opened USBVision Radio!\n",
+				__func__);
+		err_code = -EBUSY;
+	} else {
+		/* Alternate interface 1 is is the biggest frame size */
+		err_code = usbvision_set_alternate(usbvision);
+		if (err_code < 0) {
+			usbvision->last_error = err_code;
+			err_code = -EBUSY;
+			goto out;
+		}
+
+		/* If so far no errors then we shall start the radio */
+		usbvision->radio = 1;
+		call_all(usbvision, tuner, s_radio);
+		usbvision_set_audio(usbvision, USBVISION_AUDIO_RADIO);
+		usbvision->user++;
+	}
+out:
+	mutex_unlock(&usbvision->v4l2_lock);
+	return err_code;
+}
+
+
+static int usbvision_radio_close(struct file *file)
+{
+	struct usb_usbvision *usbvision = video_drvdata(file);
+
+	PDEBUG(DBG_IO, "");
+
+	mutex_lock(&usbvision->v4l2_lock);
+	/* Set packet size to 0 */
+	usbvision->iface_alt = 0;
+	usb_set_interface(usbvision->dev, usbvision->iface,
+				    usbvision->iface_alt);
+
+	usbvision_audio_off(usbvision);
+	usbvision->radio = 0;
+	usbvision->user--;
+	mutex_unlock(&usbvision->v4l2_lock);
+
+	if (usbvision->remove_pending) {
+		printk(KERN_INFO "%s: Final disconnect\n", __func__);
+		v4l2_fh_release(file);
+		usbvision_release(usbvision);
+		return 0;
+	}
+
+	PDEBUG(DBG_IO, "success");
+	return v4l2_fh_release(file);
+}
+
+/* Video registration stuff */
+
+/* Video template */
+static const struct v4l2_file_operations usbvision_fops = {
+	.owner             = THIS_MODULE,
+	.open		= usbvision_v4l2_open,
+	.release	= usbvision_v4l2_close,
+	.read		= usbvision_v4l2_read,
+	.mmap		= usbvision_v4l2_mmap,
+	.unlocked_ioctl	= video_ioctl2,
+};
+
+static const struct v4l2_ioctl_ops usbvision_ioctl_ops = {
+	.vidioc_querycap      = vidioc_querycap,
+	.vidioc_enum_fmt_vid_cap  = vidioc_enum_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap     = vidioc_g_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap   = vidioc_try_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap     = vidioc_s_fmt_vid_cap,
+	.vidioc_reqbufs       = vidioc_reqbufs,
+	.vidioc_querybuf      = vidioc_querybuf,
+	.vidioc_qbuf          = vidioc_qbuf,
+	.vidioc_dqbuf         = vidioc_dqbuf,
+	.vidioc_s_std         = vidioc_s_std,
+	.vidioc_g_std         = vidioc_g_std,
+	.vidioc_enum_input    = vidioc_enum_input,
+	.vidioc_g_input       = vidioc_g_input,
+	.vidioc_s_input       = vidioc_s_input,
+	.vidioc_streamon      = vidioc_streamon,
+	.vidioc_streamoff     = vidioc_streamoff,
+	.vidioc_g_tuner       = vidioc_g_tuner,
+	.vidioc_s_tuner       = vidioc_s_tuner,
+	.vidioc_g_frequency   = vidioc_g_frequency,
+	.vidioc_s_frequency   = vidioc_s_frequency,
+	.vidioc_log_status    = v4l2_ctrl_log_status,
+	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+	.vidioc_g_register    = vidioc_g_register,
+	.vidioc_s_register    = vidioc_s_register,
+#endif
+};
+
+static struct video_device usbvision_video_template = {
+	.fops		= &usbvision_fops,
+	.ioctl_ops	= &usbvision_ioctl_ops,
+	.name           = "usbvision-video",
+	.release	= video_device_release_empty,
+	.tvnorms        = USBVISION_NORMS,
+};
+
+
+/* Radio template */
+static const struct v4l2_file_operations usbvision_radio_fops = {
+	.owner             = THIS_MODULE,
+	.open		= usbvision_radio_open,
+	.release	= usbvision_radio_close,
+	.poll		= v4l2_ctrl_poll,
+	.unlocked_ioctl	= video_ioctl2,
+};
+
+static const struct v4l2_ioctl_ops usbvision_radio_ioctl_ops = {
+	.vidioc_querycap      = vidioc_querycap,
+	.vidioc_g_tuner       = vidioc_g_tuner,
+	.vidioc_s_tuner       = vidioc_s_tuner,
+	.vidioc_g_frequency   = vidioc_g_frequency,
+	.vidioc_s_frequency   = vidioc_s_frequency,
+	.vidioc_log_status    = v4l2_ctrl_log_status,
+	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static struct video_device usbvision_radio_template = {
+	.fops		= &usbvision_radio_fops,
+	.name		= "usbvision-radio",
+	.release	= video_device_release_empty,
+	.ioctl_ops	= &usbvision_radio_ioctl_ops,
+};
+
+
+static void usbvision_vdev_init(struct usb_usbvision *usbvision,
+				struct video_device *vdev,
+				const struct video_device *vdev_template,
+				const char *name)
+{
+	struct usb_device *usb_dev = usbvision->dev;
+
+	if (!usb_dev) {
+		dev_err(&usbvision->dev->dev,
+			"%s: usbvision->dev is not set\n", __func__);
+		return;
+	}
+
+	*vdev = *vdev_template;
+	vdev->lock = &usbvision->v4l2_lock;
+	vdev->v4l2_dev = &usbvision->v4l2_dev;
+	snprintf(vdev->name, sizeof(vdev->name), "%s", name);
+	video_set_drvdata(vdev, usbvision);
+}
+
+/* unregister video4linux devices */
+static void usbvision_unregister_video(struct usb_usbvision *usbvision)
+{
+	/* Radio Device: */
+	if (video_is_registered(&usbvision->rdev)) {
+		PDEBUG(DBG_PROBE, "unregister %s [v4l2]",
+		       video_device_node_name(&usbvision->rdev));
+		video_unregister_device(&usbvision->rdev);
+	}
+
+	/* Video Device: */
+	if (video_is_registered(&usbvision->vdev)) {
+		PDEBUG(DBG_PROBE, "unregister %s [v4l2]",
+		       video_device_node_name(&usbvision->vdev));
+		video_unregister_device(&usbvision->vdev);
+	}
+}
+
+/* register video4linux devices */
+static int usbvision_register_video(struct usb_usbvision *usbvision)
+{
+	int res = -ENOMEM;
+
+	/* Video Device: */
+	usbvision_vdev_init(usbvision, &usbvision->vdev,
+			      &usbvision_video_template, "USBVision Video");
+	if (!usbvision->have_tuner) {
+		v4l2_disable_ioctl(&usbvision->vdev, VIDIOC_G_FREQUENCY);
+		v4l2_disable_ioctl(&usbvision->vdev, VIDIOC_S_TUNER);
+		v4l2_disable_ioctl(&usbvision->vdev, VIDIOC_G_FREQUENCY);
+		v4l2_disable_ioctl(&usbvision->vdev, VIDIOC_S_TUNER);
+	}
+	if (video_register_device(&usbvision->vdev, VFL_TYPE_GRABBER, video_nr) < 0)
+		goto err_exit;
+	printk(KERN_INFO "USBVision[%d]: registered USBVision Video device %s [v4l2]\n",
+	       usbvision->nr, video_device_node_name(&usbvision->vdev));
+
+	/* Radio Device: */
+	if (usbvision_device_data[usbvision->dev_model].radio) {
+		/* usbvision has radio */
+		usbvision_vdev_init(usbvision, &usbvision->rdev,
+			      &usbvision_radio_template, "USBVision Radio");
+		if (video_register_device(&usbvision->rdev, VFL_TYPE_RADIO, radio_nr) < 0)
+			goto err_exit;
+		printk(KERN_INFO "USBVision[%d]: registered USBVision Radio device %s [v4l2]\n",
+		       usbvision->nr, video_device_node_name(&usbvision->rdev));
+	}
+	/* all done */
+	return 0;
+
+ err_exit:
+	dev_err(&usbvision->dev->dev,
+		"USBVision[%d]: video_register_device() failed\n",
+			usbvision->nr);
+	usbvision_unregister_video(usbvision);
+	return res;
+}
+
+/*
+ * usbvision_alloc()
+ *
+ * This code allocates the struct usb_usbvision.
+ * It is filled with default values.
+ *
+ * Returns NULL on error, a pointer to usb_usbvision else.
+ *
+ */
+static struct usb_usbvision *usbvision_alloc(struct usb_device *dev,
+					     struct usb_interface *intf)
+{
+	struct usb_usbvision *usbvision;
+
+	usbvision = kzalloc(sizeof(*usbvision), GFP_KERNEL);
+	if (!usbvision)
+		return NULL;
+
+	usbvision->dev = dev;
+	if (v4l2_device_register(&intf->dev, &usbvision->v4l2_dev))
+		goto err_free;
+
+	if (v4l2_ctrl_handler_init(&usbvision->hdl, 4))
+		goto err_unreg;
+	usbvision->v4l2_dev.ctrl_handler = &usbvision->hdl;
+	mutex_init(&usbvision->v4l2_lock);
+
+	/* prepare control urb for control messages during interrupts */
+	usbvision->ctrl_urb = usb_alloc_urb(USBVISION_URB_FRAMES, GFP_KERNEL);
+	if (!usbvision->ctrl_urb)
+		goto err_unreg;
+
+	return usbvision;
+
+err_unreg:
+	v4l2_ctrl_handler_free(&usbvision->hdl);
+	v4l2_device_unregister(&usbvision->v4l2_dev);
+err_free:
+	kfree(usbvision);
+	return NULL;
+}
+
+/*
+ * usbvision_release()
+ *
+ * This code does final release of struct usb_usbvision. This happens
+ * after the device is disconnected -and- all clients closed their files.
+ *
+ */
+static void usbvision_release(struct usb_usbvision *usbvision)
+{
+	PDEBUG(DBG_PROBE, "");
+
+	usbvision->initialized = 0;
+
+	usbvision_remove_sysfs(&usbvision->vdev);
+	usbvision_unregister_video(usbvision);
+	kfree(usbvision->alt_max_pkt_size);
+
+	usb_free_urb(usbvision->ctrl_urb);
+
+	v4l2_ctrl_handler_free(&usbvision->hdl);
+	v4l2_device_unregister(&usbvision->v4l2_dev);
+	kfree(usbvision);
+
+	PDEBUG(DBG_PROBE, "success");
+}
+
+
+/*********************** usb interface **********************************/
+
+static void usbvision_configure_video(struct usb_usbvision *usbvision)
+{
+	int model;
+
+	if (!usbvision)
+		return;
+
+	model = usbvision->dev_model;
+	usbvision->palette = usbvision_v4l2_format[2]; /* V4L2_PIX_FMT_RGB24; */
+
+	if (usbvision_device_data[usbvision->dev_model].vin_reg2_override) {
+		usbvision->vin_reg2_preset =
+			usbvision_device_data[usbvision->dev_model].vin_reg2;
+	} else {
+		usbvision->vin_reg2_preset = 0;
+	}
+
+	usbvision->tvnorm_id = usbvision_device_data[model].video_norm;
+	usbvision->video_inputs = usbvision_device_data[model].video_channels;
+	usbvision->ctl_input = 0;
+	usbvision->radio_freq = 87.5 * 16000;
+	usbvision->tv_freq = 400 * 16;
+
+	/* This should be here to make i2c clients to be able to register */
+	/* first switch off audio */
+	if (usbvision_device_data[model].audio_channels > 0)
+		usbvision_audio_off(usbvision);
+	/* and then power up the tuner */
+	usbvision_power_on(usbvision);
+	usbvision_i2c_register(usbvision);
+}
+
+/*
+ * usbvision_probe()
+ *
+ * This procedure queries device descriptor and accepts the interface
+ * if it looks like USBVISION video device
+ *
+ */
+static int usbvision_probe(struct usb_interface *intf,
+			   const struct usb_device_id *devid)
+{
+	struct usb_device *dev = usb_get_dev(interface_to_usbdev(intf));
+	struct usb_interface *uif;
+	__u8 ifnum = intf->altsetting->desc.bInterfaceNumber;
+	const struct usb_host_interface *interface;
+	struct usb_usbvision *usbvision = NULL;
+	const struct usb_endpoint_descriptor *endpoint;
+	int model, i, ret;
+
+	PDEBUG(DBG_PROBE, "VID=%#04x, PID=%#04x, ifnum=%u",
+				le16_to_cpu(dev->descriptor.idVendor),
+				le16_to_cpu(dev->descriptor.idProduct), ifnum);
+
+	model = devid->driver_info;
+	if (model < 0 || model >= usbvision_device_data_size) {
+		PDEBUG(DBG_PROBE, "model out of bounds %d", model);
+		ret = -ENODEV;
+		goto err_usb;
+	}
+	printk(KERN_INFO "%s: %s found\n", __func__,
+				usbvision_device_data[model].model_string);
+
+	if (usbvision_device_data[model].interface >= 0)
+		interface = &dev->actconfig->interface[usbvision_device_data[model].interface]->altsetting[0];
+	else if (ifnum < dev->actconfig->desc.bNumInterfaces)
+		interface = &dev->actconfig->interface[ifnum]->altsetting[0];
+	else {
+		dev_err(&intf->dev, "interface %d is invalid, max is %d\n",
+		    ifnum, dev->actconfig->desc.bNumInterfaces - 1);
+		ret = -ENODEV;
+		goto err_usb;
+	}
+
+	if (interface->desc.bNumEndpoints < 2) {
+		dev_err(&intf->dev, "interface %d has %d endpoints, but must have minimum 2\n",
+			ifnum, interface->desc.bNumEndpoints);
+		ret = -ENODEV;
+		goto err_usb;
+	}
+	endpoint = &interface->endpoint[1].desc;
+
+	if (!usb_endpoint_xfer_isoc(endpoint)) {
+		dev_err(&intf->dev, "%s: interface %d. has non-ISO endpoint!\n",
+		    __func__, ifnum);
+		dev_err(&intf->dev, "%s: Endpoint attributes %d",
+		    __func__, endpoint->bmAttributes);
+		ret = -ENODEV;
+		goto err_usb;
+	}
+	if (usb_endpoint_dir_out(endpoint)) {
+		dev_err(&intf->dev, "%s: interface %d. has ISO OUT endpoint!\n",
+		    __func__, ifnum);
+		ret = -ENODEV;
+		goto err_usb;
+	}
+
+	usbvision = usbvision_alloc(dev, intf);
+	if (!usbvision) {
+		dev_err(&intf->dev, "%s: couldn't allocate USBVision struct\n", __func__);
+		ret = -ENOMEM;
+		goto err_usb;
+	}
+
+	if (dev->descriptor.bNumConfigurations > 1)
+		usbvision->bridge_type = BRIDGE_NT1004;
+	else if (model == DAZZLE_DVC_90_REV_1_SECAM)
+		usbvision->bridge_type = BRIDGE_NT1005;
+	else
+		usbvision->bridge_type = BRIDGE_NT1003;
+	PDEBUG(DBG_PROBE, "bridge_type %d", usbvision->bridge_type);
+
+	/* compute alternate max packet sizes */
+	uif = dev->actconfig->interface[0];
+
+	usbvision->num_alt = uif->num_altsetting;
+	PDEBUG(DBG_PROBE, "Alternate settings: %i", usbvision->num_alt);
+	usbvision->alt_max_pkt_size = kmalloc_array(32, usbvision->num_alt,
+						    GFP_KERNEL);
+	if (!usbvision->alt_max_pkt_size) {
+		ret = -ENOMEM;
+		goto err_pkt;
+	}
+
+	for (i = 0; i < usbvision->num_alt; i++) {
+		u16 tmp;
+
+		if (uif->altsetting[i].desc.bNumEndpoints < 2) {
+			ret = -ENODEV;
+			goto err_pkt;
+		}
+
+		tmp = le16_to_cpu(uif->altsetting[i].endpoint[1].desc.
+				      wMaxPacketSize);
+		usbvision->alt_max_pkt_size[i] =
+			(tmp & 0x07ff) * (((tmp & 0x1800) >> 11) + 1);
+		PDEBUG(DBG_PROBE, "Alternate setting %i, max size= %i", i,
+		       usbvision->alt_max_pkt_size[i]);
+	}
+
+
+	usbvision->nr = usbvision_nr++;
+
+	spin_lock_init(&usbvision->queue_lock);
+	init_waitqueue_head(&usbvision->wait_frame);
+	init_waitqueue_head(&usbvision->wait_stream);
+
+	usbvision->have_tuner = usbvision_device_data[model].tuner;
+	if (usbvision->have_tuner)
+		usbvision->tuner_type = usbvision_device_data[model].tuner_type;
+
+	usbvision->dev_model = model;
+	usbvision->remove_pending = 0;
+	usbvision->iface = ifnum;
+	usbvision->iface_alt = 0;
+	usbvision->video_endp = endpoint->bEndpointAddress;
+	usbvision->isoc_packet_size = 0;
+	usbvision->usb_bandwidth = 0;
+	usbvision->user = 0;
+	usbvision->streaming = stream_off;
+	usbvision_configure_video(usbvision);
+	usbvision_register_video(usbvision);
+
+	usbvision_create_sysfs(&usbvision->vdev);
+
+	PDEBUG(DBG_PROBE, "success");
+	return 0;
+
+err_pkt:
+	usbvision_release(usbvision);
+err_usb:
+	usb_put_dev(dev);
+	return ret;
+}
+
+
+/*
+ * usbvision_disconnect()
+ *
+ * This procedure stops all driver activity, deallocates interface-private
+ * structure (pointed by 'ptr') and after that driver should be removable
+ * with no ill consequences.
+ *
+ */
+static void usbvision_disconnect(struct usb_interface *intf)
+{
+	struct usb_usbvision *usbvision = to_usbvision(usb_get_intfdata(intf));
+
+	PDEBUG(DBG_PROBE, "");
+
+	if (!usbvision) {
+		pr_err("%s: usb_get_intfdata() failed\n", __func__);
+		return;
+	}
+
+	mutex_lock(&usbvision->v4l2_lock);
+
+	/* At this time we ask to cancel outstanding URBs */
+	usbvision_stop_isoc(usbvision);
+
+	v4l2_device_disconnect(&usbvision->v4l2_dev);
+	usbvision_i2c_unregister(usbvision);
+	usbvision->remove_pending = 1;	/* Now all ISO data will be ignored */
+
+	usb_put_dev(usbvision->dev);
+	usbvision->dev = NULL;	/* USB device is no more */
+
+	mutex_unlock(&usbvision->v4l2_lock);
+
+	if (usbvision->user) {
+		printk(KERN_INFO "%s: In use, disconnect pending\n",
+		       __func__);
+		wake_up_interruptible(&usbvision->wait_frame);
+		wake_up_interruptible(&usbvision->wait_stream);
+	} else {
+		usbvision_release(usbvision);
+	}
+
+	PDEBUG(DBG_PROBE, "success");
+}
+
+static struct usb_driver usbvision_driver = {
+	.name		= "usbvision",
+	.id_table	= usbvision_table,
+	.probe		= usbvision_probe,
+	.disconnect	= usbvision_disconnect,
+};
+
+/*
+ * usbvision_init()
+ *
+ * This code is run to initialize the driver.
+ *
+ */
+static int __init usbvision_init(void)
+{
+	int err_code;
+
+	PDEBUG(DBG_PROBE, "");
+
+	PDEBUG(DBG_IO,  "IO      debugging is enabled [video]");
+	PDEBUG(DBG_PROBE, "PROBE   debugging is enabled [video]");
+	PDEBUG(DBG_MMAP, "MMAP    debugging is enabled [video]");
+
+	/* disable planar mode support unless compression enabled */
+	if (isoc_mode != ISOC_MODE_COMPRESS) {
+		/* FIXME : not the right way to set supported flag */
+		usbvision_v4l2_format[6].supported = 0; /* V4L2_PIX_FMT_YVU420 */
+		usbvision_v4l2_format[7].supported = 0; /* V4L2_PIX_FMT_YUV422P */
+	}
+
+	err_code = usb_register(&usbvision_driver);
+
+	if (err_code == 0) {
+		printk(KERN_INFO DRIVER_DESC " : " USBVISION_VERSION_STRING "\n");
+		PDEBUG(DBG_PROBE, "success");
+	}
+	return err_code;
+}
+
+static void __exit usbvision_exit(void)
+{
+	PDEBUG(DBG_PROBE, "");
+
+	usb_deregister(&usbvision_driver);
+	PDEBUG(DBG_PROBE, "success");
+}
+
+module_init(usbvision_init);
+module_exit(usbvision_exit);
diff --git a/drivers/media/usb/usbvision/usbvision.h b/drivers/media/usb/usbvision/usbvision.h
new file mode 100644
index 0000000..6ecdcd5
--- /dev/null
+++ b/drivers/media/usb/usbvision/usbvision.h
@@ -0,0 +1,512 @@
+/*
+ * USBVISION.H
+ *  usbvision header file
+ *
+ * Copyright (c) 1999-2005 Joerg Heckenbach <joerg@heckenbach-aw.de>
+ *                         Dwaine Garden <dwainegarden@rogers.com>
+ *
+ *
+ * Report problems to v4l MailingList: linux-media@vger.kernel.org
+ *
+ * This module is part of usbvision driver project.
+ * Updates to driver completed by Dwaine P. Garden
+ * v4l2 conversion by Thierry Merle <thierry.merle@free.fr>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+
+#ifndef __LINUX_USBVISION_H
+#define __LINUX_USBVISION_H
+
+#include <linux/list.h>
+#include <linux/usb.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/tuner.h>
+#include <linux/videodev2.h>
+
+#define USBVISION_DEBUG		/* Turn on debug messages */
+
+#define USBVISION_PWR_REG		0x00
+	#define USBVISION_SSPND_EN		(1 << 1)
+	#define USBVISION_RES2			(1 << 2)
+	#define USBVISION_PWR_VID		(1 << 5)
+	#define USBVISION_E2_EN			(1 << 7)
+#define USBVISION_CONFIG_REG		0x01
+#define USBVISION_ADRS_REG		0x02
+#define USBVISION_ALTER_REG		0x03
+#define USBVISION_FORCE_ALTER_REG	0x04
+#define USBVISION_STATUS_REG		0x05
+#define USBVISION_IOPIN_REG		0x06
+	#define USBVISION_IO_1			(1 << 0)
+	#define USBVISION_IO_2			(1 << 1)
+	#define USBVISION_AUDIO_IN		0
+	#define USBVISION_AUDIO_TV		1
+	#define USBVISION_AUDIO_RADIO		2
+	#define USBVISION_AUDIO_MUTE		3
+#define USBVISION_SER_MODE		0x07
+	#define USBVISION_CLK_OUT		(1 << 0)
+	#define USBVISION_DAT_IO		(1 << 1)
+	#define USBVISION_SENS_OUT		(1 << 2)
+	#define USBVISION_SER_MODE_SOFT		(0 << 4)
+	#define USBVISION_SER_MODE_SIO		(1 << 4)
+#define USBVISION_SER_ADRS		0x08
+#define USBVISION_SER_CONT		0x09
+#define USBVISION_SER_DAT1		0x0A
+#define USBVISION_SER_DAT2		0x0B
+#define USBVISION_SER_DAT3		0x0C
+#define USBVISION_SER_DAT4		0x0D
+#define USBVISION_EE_DATA		0x0E
+#define USBVISION_EE_LSBAD		0x0F
+#define USBVISION_EE_CONT		0x10
+#define USBVISION_DRM_CONT			0x12
+	#define USBVISION_REF			(1 << 0)
+	#define USBVISION_RES_UR		(1 << 2)
+	#define USBVISION_RES_FDL		(1 << 3)
+	#define USBVISION_RES_VDW		(1 << 4)
+#define USBVISION_DRM_PRM1		0x13
+#define USBVISION_DRM_PRM2		0x14
+#define USBVISION_DRM_PRM3		0x15
+#define USBVISION_DRM_PRM4		0x16
+#define USBVISION_DRM_PRM5		0x17
+#define USBVISION_DRM_PRM6		0x18
+#define USBVISION_DRM_PRM7		0x19
+#define USBVISION_DRM_PRM8		0x1A
+#define USBVISION_VIN_REG1		0x1B
+	#define USBVISION_8_422_SYNC		0x01
+	#define USBVISION_16_422_SYNC		0x02
+	#define USBVISION_VSNC_POL		(1 << 3)
+	#define USBVISION_HSNC_POL		(1 << 4)
+	#define USBVISION_FID_POL		(1 << 5)
+	#define USBVISION_HVALID_PO		(1 << 6)
+	#define USBVISION_VCLK_POL		(1 << 7)
+#define USBVISION_VIN_REG2		0x1C
+	#define USBVISION_AUTO_FID		(1 << 0)
+	#define USBVISION_NONE_INTER		(1 << 1)
+	#define USBVISION_NOHVALID		(1 << 2)
+	#define USBVISION_UV_ID			(1 << 3)
+	#define USBVISION_FIX_2C		(1 << 4)
+	#define USBVISION_SEND_FID		(1 << 5)
+	#define USBVISION_KEEP_BLANK		(1 << 7)
+#define USBVISION_LXSIZE_I		0x1D
+#define USBVISION_MXSIZE_I		0x1E
+#define USBVISION_LYSIZE_I		0x1F
+#define USBVISION_MYSIZE_I		0x20
+#define USBVISION_LX_OFFST		0x21
+#define USBVISION_MX_OFFST		0x22
+#define USBVISION_LY_OFFST		0x23
+#define USBVISION_MY_OFFST		0x24
+#define USBVISION_FRM_RATE		0x25
+#define USBVISION_LXSIZE_O		0x26
+#define USBVISION_MXSIZE_O		0x27
+#define USBVISION_LYSIZE_O		0x28
+#define USBVISION_MYSIZE_O		0x29
+#define USBVISION_FILT_CONT		0x2A
+#define USBVISION_VO_MODE		0x2B
+#define USBVISION_INTRA_CYC		0x2C
+#define USBVISION_STRIP_SZ		0x2D
+#define USBVISION_FORCE_INTRA		0x2E
+#define USBVISION_FORCE_UP		0x2F
+#define USBVISION_BUF_THR		0x30
+#define USBVISION_DVI_YUV		0x31
+#define USBVISION_AUDIO_CONT		0x32
+#define USBVISION_AUD_PK_LEN		0x33
+#define USBVISION_BLK_PK_LEN		0x34
+#define USBVISION_PCM_THR1		0x38
+#define USBVISION_PCM_THR2		0x39
+#define USBVISION_DIST_THR_L		0x3A
+#define USBVISION_DIST_THR_H		0x3B
+#define USBVISION_MAX_DIST_L		0x3C
+#define USBVISION_MAX_DIST_H		0x3D
+#define USBVISION_OP_CODE		0x33
+
+#define MAX_BYTES_PER_PIXEL		4
+
+#define MIN_FRAME_WIDTH			64
+#define MAX_USB_WIDTH			320  /* 384 */
+#define MAX_FRAME_WIDTH			320  /* 384 */			/* streching sometimes causes crashes*/
+
+#define MIN_FRAME_HEIGHT		48
+#define MAX_USB_HEIGHT			240  /* 288 */
+#define MAX_FRAME_HEIGHT		240  /* 288 */			/* Streching sometimes causes crashes*/
+
+#define MAX_FRAME_SIZE			(MAX_FRAME_WIDTH * MAX_FRAME_HEIGHT * MAX_BYTES_PER_PIXEL)
+#define USBVISION_CLIPMASK_SIZE		(MAX_FRAME_WIDTH * MAX_FRAME_HEIGHT / 8) /* bytesize of clipmask */
+
+#define USBVISION_URB_FRAMES		32
+
+#define USBVISION_NUM_HEADERMARKER	20
+#define USBVISION_NUMFRAMES		3  /* Maximum number of frames an application can get */
+#define USBVISION_NUMSBUF		2 /* Dimensioning the USB S buffering */
+
+#define USBVISION_POWEROFF_TIME		(3 * HZ)		/* 3 seconds */
+
+
+#define FRAMERATE_MIN	0
+#define FRAMERATE_MAX	31
+
+enum {
+	ISOC_MODE_YUV422 = 0x03,
+	ISOC_MODE_YUV420 = 0x14,
+	ISOC_MODE_COMPRESS = 0x60,
+};
+
+/* This macro restricts an int variable to an inclusive range */
+#define RESTRICT_TO_RANGE(v, mi, ma) \
+	{ if (((int)v) < (mi)) (v) = (mi); else if ((v) > (ma)) (v) = (ma); }
+
+/*
+ * We use macros to do YUV -> RGB conversion because this is
+ * very important for speed and totally unimportant for size.
+ *
+ * YUV -> RGB Conversion
+ * ---------------------
+ *
+ * B = 1.164*(Y-16)		    + 2.018*(V-128)
+ * G = 1.164*(Y-16) - 0.813*(U-128) - 0.391*(V-128)
+ * R = 1.164*(Y-16) + 1.596*(U-128)
+ *
+ * If you fancy integer arithmetics (as you should), hear this:
+ *
+ * 65536*B = 76284*(Y-16)		  + 132252*(V-128)
+ * 65536*G = 76284*(Y-16) -  53281*(U-128) -  25625*(V-128)
+ * 65536*R = 76284*(Y-16) + 104595*(U-128)
+ *
+ * Make sure the output values are within [0..255] range.
+ */
+#define LIMIT_RGB(x) (((x) < 0) ? 0 : (((x) > 255) ? 255 : (x)))
+#define YUV_TO_RGB_BY_THE_BOOK(my, mu, mv, mr, mg, mb) { \
+	int mm_y, mm_yc, mm_u, mm_v, mm_r, mm_g, mm_b; \
+	mm_y = (my) - 16; \
+	mm_u = (mu) - 128; \
+	mm_v = (mv) - 128; \
+	mm_yc = mm_y * 76284; \
+	mm_b = (mm_yc + 132252 * mm_v) >> 16; \
+	mm_g = (mm_yc - 53281 * mm_u - 25625 * mm_v) >> 16; \
+	mm_r = (mm_yc + 104595 * mm_u) >> 16; \
+	mb = LIMIT_RGB(mm_b); \
+	mg = LIMIT_RGB(mm_g); \
+	mr = LIMIT_RGB(mm_r); \
+}
+
+/*
+ * This macro checks if usbvision is still operational. The 'usbvision'
+ * pointer must be valid, usbvision->dev must be valid, we are not
+ * removing the device and the device has not erred on us.
+ */
+#define USBVISION_IS_OPERATIONAL(udevice) (\
+	(udevice != NULL) && \
+	((udevice)->dev != NULL) && \
+	((udevice)->last_error == 0) && \
+	(!(udevice)->remove_pending))
+
+#define I2C_USB_ADAP_MAX	16
+
+#define USBVISION_NORMS (V4L2_STD_PAL | V4L2_STD_NTSC | V4L2_STD_SECAM | V4L2_STD_PAL_M)
+
+/* ----------------------------------------------------------------- */
+/* usbvision video structures                                        */
+/* ----------------------------------------------------------------- */
+enum scan_state {
+	scan_state_scanning,	/* Scanning for header */
+	scan_state_lines	/* Parsing lines */
+};
+
+/* Completion states of the data parser */
+enum parse_state {
+	parse_state_continue,	/* Just parse next item */
+	parse_state_next_frame,	/* Frame done, send it to V4L */
+	parse_state_out,	/* Not enough data for frame */
+	parse_state_end_parse	/* End parsing */
+};
+
+enum frame_state {
+	frame_state_unused,	/* Unused (no MCAPTURE) */
+	frame_state_ready,	/* Ready to start grabbing */
+	frame_state_grabbing,	/* In the process of being grabbed into */
+	frame_state_done,	/* Finished grabbing, but not been synced yet */
+	frame_state_done_hold,	/* Are syncing or reading */
+	frame_state_error,	/* Something bad happened while processing */
+};
+
+/* stream states */
+enum stream_state {
+	stream_off,		/* Driver streaming is completely OFF */
+	stream_idle,		/* Driver streaming is ready to be put ON by the application */
+	stream_interrupt,	/* Driver streaming must be interrupted */
+	stream_on,		/* Driver streaming is put ON by the application */
+};
+
+enum isoc_state {
+	isoc_state_in_frame,	/* Isoc packet is member of frame */
+	isoc_state_no_frame,	/* Isoc packet is not member of any frame */
+};
+
+struct usb_device;
+
+struct usbvision_sbuf {
+	char *data;
+	struct urb *urb;
+};
+
+#define USBVISION_MAGIC_1			0x55
+#define USBVISION_MAGIC_2			0xAA
+#define USBVISION_HEADER_LENGTH			0x0c
+#define USBVISION_SAA7111_ADDR			0x48
+#define USBVISION_SAA7113_ADDR			0x4a
+#define USBVISION_IIC_LRACK			0x20
+#define USBVISION_IIC_LRNACK			0x30
+#define USBVISION_FRAME_FORMAT_PARAM_INTRA	(1<<7)
+
+struct usbvision_v4l2_format_st {
+	int		supported;
+	int		bytes_per_pixel;
+	int		depth;
+	int		format;
+	char		*desc;
+};
+#define USBVISION_SUPPORTED_PALETTES ARRAY_SIZE(usbvision_v4l2_format)
+
+struct usbvision_frame_header {
+	unsigned char magic_1;				/* 0 magic */
+	unsigned char magic_2;				/* 1  magic */
+	unsigned char header_length;			/* 2 */
+	unsigned char frame_num;			/* 3 */
+	unsigned char frame_phase;			/* 4 */
+	unsigned char frame_latency;			/* 5 */
+	unsigned char data_format;			/* 6 */
+	unsigned char format_param;			/* 7 */
+	unsigned char frame_width_lo;			/* 8 */
+	unsigned char frame_width_hi;			/* 9 */
+	unsigned char frame_height_lo;			/* 10 */
+	unsigned char frame_height_hi;			/* 11 */
+	__u16 frame_width;				/* 8 - 9 after endian correction*/
+	__u16 frame_height;				/* 10 - 11 after endian correction*/
+};
+
+struct usbvision_frame {
+	char *data;					/* Frame buffer */
+	struct usbvision_frame_header isoc_header;	/* Header from stream */
+
+	int width;					/* Width application is expecting */
+	int height;					/* Height */
+	int index;					/* Frame index */
+	int frmwidth;					/* Width the frame actually is */
+	int frmheight;					/* Height */
+
+	volatile int grabstate;				/* State of grabbing */
+	int scanstate;					/* State of scanning */
+
+	struct list_head frame;
+
+	int curline;					/* Line of frame we're working on */
+
+	long scanlength;				/* uncompressed, raw data length of frame */
+	long bytes_read;				/* amount of scanlength that has been read from data */
+	struct usbvision_v4l2_format_st v4l2_format;	/* format the user needs*/
+	int v4l2_linesize;				/* bytes for one videoline*/
+	struct timeval timestamp;
+	int sequence;					/* How many video frames we send to user */
+};
+
+#define CODEC_SAA7113	7113
+#define CODEC_SAA7111	7111
+#define CODEC_WEBCAM	3000
+#define BRIDGE_NT1003	1003
+#define BRIDGE_NT1004	1004
+#define BRIDGE_NT1005   1005
+
+struct usbvision_device_data_st {
+	__u64 video_norm;
+	const char *model_string;
+	int interface; /* to handle special interface number like BELKIN and Hauppauge WinTV-USB II */
+	__u16 codec;
+	unsigned video_channels:3;
+	unsigned audio_channels:2;
+	unsigned radio:1;
+	unsigned vbi:1;
+	unsigned tuner:1;
+	unsigned vin_reg1_override:1;	/* Override default value with */
+	unsigned vin_reg2_override:1;   /* vin_reg1, vin_reg2, etc. */
+	unsigned dvi_yuv_override:1;
+	__u8 vin_reg1;
+	__u8 vin_reg2;
+	__u8 dvi_yuv;
+	__u8 tuner_type;
+	__s16 x_offset;
+	__s16 y_offset;
+};
+
+/* Declared on usbvision-cards.c */
+extern struct usbvision_device_data_st usbvision_device_data[];
+extern struct usb_device_id usbvision_table[];
+
+struct usb_usbvision {
+	struct v4l2_device v4l2_dev;
+	struct v4l2_ctrl_handler hdl;
+	struct video_device vdev;					/* Video Device */
+	struct video_device rdev;					/* Radio Device */
+
+	/* i2c Declaration Section*/
+	struct i2c_adapter i2c_adap;
+	int registered_i2c;
+
+	struct urb *ctrl_urb;
+	unsigned char ctrl_urb_buffer[8];
+	int ctrl_urb_busy;
+	struct usb_ctrlrequest ctrl_urb_setup;
+
+	/* configuration part */
+	int have_tuner;
+	int tuner_type;
+	int bridge_type;						/* NT1003, NT1004, NT1005 */
+	int radio;
+	int video_inputs;						/* # of inputs */
+	unsigned long radio_freq;
+	unsigned long tv_freq;
+	int audio_mute;
+	int audio_channel;
+	int isoc_mode;							/* format of video data for the usb isoc-transfer */
+	unsigned int nr;						/* Number of the device */
+
+	/* Device structure */
+	struct usb_device *dev;
+	/* usb transfer */
+	int num_alt;		/* Number of alternative settings */
+	unsigned int *alt_max_pkt_size;	/* array of max_packet_size */
+	unsigned char iface;						/* Video interface number */
+	unsigned char iface_alt;					/* Alt settings */
+	unsigned char vin_reg2_preset;
+	struct mutex v4l2_lock;
+	int power;							/* is the device powered on? */
+	int user;							/* user count for exclusive use */
+	int initialized;						/* Had we already sent init sequence? */
+	int dev_model;							/* What type of USBVISION device we got? */
+	enum stream_state streaming;					/* Are we streaming Isochronous? */
+	int last_error;							/* What calamity struck us? */
+	int curwidth;							/* width of the frame the device is currently set to*/
+	int curheight;							/* height of the frame the device is currently set to*/
+	int stretch_width;						/* stretch-factor for frame width (from usb to screen)*/
+	int stretch_height;						/* stretch-factor for frame height (from usb to screen)*/
+	char *fbuf;							/* Videodev buffer area for mmap*/
+	int max_frame_size;						/* Bytes in one video frame */
+	int fbuf_size;							/* Videodev buffer size */
+	spinlock_t queue_lock;						/* spinlock for protecting mods on inqueue and outqueue */
+	struct list_head inqueue, outqueue;                             /* queued frame list and ready to dequeue frame list */
+	wait_queue_head_t wait_frame;					/* Processes waiting */
+	wait_queue_head_t wait_stream;					/* Processes waiting */
+	struct usbvision_frame *cur_frame;				/* pointer to current frame, set by usbvision_find_header */
+	struct usbvision_frame frame[USBVISION_NUMFRAMES];		/* frame buffer */
+	int num_frames;							/* number of frames allocated */
+	struct usbvision_sbuf sbuf[USBVISION_NUMSBUF];			/* S buffering */
+	volatile int remove_pending;					/* If set then about to exit */
+
+	/* Scratch space from the Isochronous Pipe.*/
+	unsigned char *scratch;
+	int scratch_read_ptr;
+	int scratch_write_ptr;
+	int scratch_headermarker[USBVISION_NUM_HEADERMARKER];
+	int scratch_headermarker_read_ptr;
+	int scratch_headermarker_write_ptr;
+	enum isoc_state isocstate;
+	struct usbvision_v4l2_format_st palette;
+
+	struct v4l2_capability vcap;					/* Video capabilities */
+	unsigned int ctl_input;						/* selected input */
+	v4l2_std_id tvnorm_id;						/* selected tv norm */
+	unsigned char video_endp;					/* 0x82 for USBVISION devices based */
+
+	/* Decompression stuff: */
+	unsigned char *intra_frame_buffer;				/* Buffer for reference frame */
+	int block_pos;							/* for test only */
+	int request_intra;						/* 0 = normal; 1 = intra frame is requested; */
+	int last_isoc_frame_num;					/* check for lost isoc frames */
+	int isoc_packet_size;						/* need to calculate used_bandwidth */
+	int used_bandwidth;						/* used bandwidth 0-100%, need to set compr_level */
+	int compr_level;						/* How strong (100) or weak (0) is compression */
+	int last_compr_level;						/* How strong (100) or weak (0) was compression */
+	int usb_bandwidth;						/* Mbit/s */
+
+	/* Statistics that can be overlayed on the screen */
+	unsigned long isoc_urb_count;			/* How many URBs we received so far */
+	unsigned long urb_length;			/* Length of last URB */
+	unsigned long isoc_data_count;			/* How many bytes we received */
+	unsigned long header_count;			/* How many frame headers we found */
+	unsigned long scratch_ovf_count;		/* How many times we overflowed scratch */
+	unsigned long isoc_skip_count;			/* How many empty ISO packets received */
+	unsigned long isoc_err_count;			/* How many bad ISO packets received */
+	unsigned long isoc_packet_count;		/* How many packets we totally got */
+	unsigned long time_in_irq;			/* How long do we need for interrupt */
+	int isoc_measure_bandwidth_count;
+	int frame_num;					/* How many video frames we send to user */
+	int max_strip_len;				/* How big is the biggest strip */
+	int comprblock_pos;
+	int strip_len_errors;				/* How many times was block_pos greater than strip_len */
+	int strip_magic_errors;
+	int strip_line_number_errors;
+	int compr_block_types[4];
+};
+
+static inline struct usb_usbvision *to_usbvision(struct v4l2_device *v4l2_dev)
+{
+	return container_of(v4l2_dev, struct usb_usbvision, v4l2_dev);
+}
+
+#define call_all(usbvision, o, f, args...) \
+	v4l2_device_call_all(&usbvision->v4l2_dev, 0, o, f, ##args)
+
+/* --------------------------------------------------------------- */
+/* defined in usbvision-i2c.c                                      */
+/* i2c-algo-usb declaration                                        */
+/* --------------------------------------------------------------- */
+
+/* ----------------------------------------------------------------------- */
+/* usbvision specific I2C functions                                        */
+/* ----------------------------------------------------------------------- */
+int usbvision_i2c_register(struct usb_usbvision *usbvision);
+int usbvision_i2c_unregister(struct usb_usbvision *usbvision);
+
+/* defined in usbvision-core.c                                      */
+int usbvision_read_reg(struct usb_usbvision *usbvision, unsigned char reg);
+int usbvision_write_reg(struct usb_usbvision *usbvision, unsigned char reg,
+			unsigned char value);
+
+int usbvision_frames_alloc(struct usb_usbvision *usbvision, int number_of_frames);
+void usbvision_frames_free(struct usb_usbvision *usbvision);
+int usbvision_scratch_alloc(struct usb_usbvision *usbvision);
+void usbvision_scratch_free(struct usb_usbvision *usbvision);
+int usbvision_decompress_alloc(struct usb_usbvision *usbvision);
+void usbvision_decompress_free(struct usb_usbvision *usbvision);
+
+int usbvision_setup(struct usb_usbvision *usbvision, int format);
+int usbvision_init_isoc(struct usb_usbvision *usbvision);
+int usbvision_restart_isoc(struct usb_usbvision *usbvision);
+void usbvision_stop_isoc(struct usb_usbvision *usbvision);
+int usbvision_set_alternate(struct usb_usbvision *dev);
+
+int usbvision_set_audio(struct usb_usbvision *usbvision, int audio_channel);
+int usbvision_audio_off(struct usb_usbvision *usbvision);
+
+int usbvision_begin_streaming(struct usb_usbvision *usbvision);
+void usbvision_empty_framequeues(struct usb_usbvision *dev);
+int usbvision_stream_interrupt(struct usb_usbvision *dev);
+
+int usbvision_muxsel(struct usb_usbvision *usbvision, int channel);
+int usbvision_set_input(struct usb_usbvision *usbvision);
+int usbvision_set_output(struct usb_usbvision *usbvision, int width, int height);
+
+int usbvision_power_off(struct usb_usbvision *usbvision);
+int usbvision_power_on(struct usb_usbvision *usbvision);
+
+#endif									/* __LINUX_USBVISION_H */
diff --git a/drivers/media/usb/uvc/Kconfig b/drivers/media/usb/uvc/Kconfig
new file mode 100644
index 0000000..6ed85ef
--- /dev/null
+++ b/drivers/media/usb/uvc/Kconfig
@@ -0,0 +1,20 @@
+config USB_VIDEO_CLASS
+	tristate "USB Video Class (UVC)"
+	depends on VIDEO_V4L2
+	select VIDEOBUF2_VMALLOC
+	---help---
+	  Support for the USB Video Class (UVC).  Currently only video
+	  input devices, such as webcams, are supported.
+
+	  For more information see: <http://linux-uvc.berlios.de/>
+
+config USB_VIDEO_CLASS_INPUT_EVDEV
+	bool "UVC input events device support"
+	default y
+	depends on USB_VIDEO_CLASS
+	depends on USB_VIDEO_CLASS=INPUT || INPUT=y
+	---help---
+	  This option makes USB Video Class devices register an input device
+	  to report button events.
+
+	  If you are in doubt, say Y.
diff --git a/drivers/media/usb/uvc/Makefile b/drivers/media/usb/uvc/Makefile
new file mode 100644
index 0000000..4f9eee4
--- /dev/null
+++ b/drivers/media/usb/uvc/Makefile
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: GPL-2.0
+uvcvideo-objs  := uvc_driver.o uvc_queue.o uvc_v4l2.o uvc_video.o uvc_ctrl.o \
+		  uvc_status.o uvc_isight.o uvc_debugfs.o uvc_metadata.o
+ifeq ($(CONFIG_MEDIA_CONTROLLER),y)
+uvcvideo-objs  += uvc_entity.o
+endif
+obj-$(CONFIG_USB_VIDEO_CLASS) += uvcvideo.o
diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c
new file mode 100644
index 0000000..c2ad102
--- /dev/null
+++ b/drivers/media/usb/uvc/uvc_ctrl.c
@@ -0,0 +1,2369 @@
+/*
+ *      uvc_ctrl.c  --  USB Video Class driver - Controls
+ *
+ *      Copyright (C) 2005-2010
+ *          Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ *
+ *      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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/videodev2.h>
+#include <linux/vmalloc.h>
+#include <linux/wait.h>
+#include <linux/workqueue.h>
+#include <linux/atomic.h>
+#include <media/v4l2-ctrls.h>
+
+#include "uvcvideo.h"
+
+#define UVC_CTRL_DATA_CURRENT	0
+#define UVC_CTRL_DATA_BACKUP	1
+#define UVC_CTRL_DATA_MIN	2
+#define UVC_CTRL_DATA_MAX	3
+#define UVC_CTRL_DATA_RES	4
+#define UVC_CTRL_DATA_DEF	5
+#define UVC_CTRL_DATA_LAST	6
+
+/* ------------------------------------------------------------------------
+ * Controls
+ */
+
+static struct uvc_control_info uvc_ctrls[] = {
+	{
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_BRIGHTNESS_CONTROL,
+		.index		= 0,
+		.size		= 2,
+		.flags		= UVC_CTRL_FLAG_SET_CUR
+				| UVC_CTRL_FLAG_GET_RANGE
+				| UVC_CTRL_FLAG_RESTORE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_CONTRAST_CONTROL,
+		.index		= 1,
+		.size		= 2,
+		.flags		= UVC_CTRL_FLAG_SET_CUR
+				| UVC_CTRL_FLAG_GET_RANGE
+				| UVC_CTRL_FLAG_RESTORE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_HUE_CONTROL,
+		.index		= 2,
+		.size		= 2,
+		.flags		= UVC_CTRL_FLAG_SET_CUR
+				| UVC_CTRL_FLAG_GET_RANGE
+				| UVC_CTRL_FLAG_RESTORE
+				| UVC_CTRL_FLAG_AUTO_UPDATE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_SATURATION_CONTROL,
+		.index		= 3,
+		.size		= 2,
+		.flags		= UVC_CTRL_FLAG_SET_CUR
+				| UVC_CTRL_FLAG_GET_RANGE
+				| UVC_CTRL_FLAG_RESTORE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_SHARPNESS_CONTROL,
+		.index		= 4,
+		.size		= 2,
+		.flags		= UVC_CTRL_FLAG_SET_CUR
+				| UVC_CTRL_FLAG_GET_RANGE
+				| UVC_CTRL_FLAG_RESTORE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_GAMMA_CONTROL,
+		.index		= 5,
+		.size		= 2,
+		.flags		= UVC_CTRL_FLAG_SET_CUR
+				| UVC_CTRL_FLAG_GET_RANGE
+				| UVC_CTRL_FLAG_RESTORE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_WHITE_BALANCE_TEMPERATURE_CONTROL,
+		.index		= 6,
+		.size		= 2,
+		.flags		= UVC_CTRL_FLAG_SET_CUR
+				| UVC_CTRL_FLAG_GET_RANGE
+				| UVC_CTRL_FLAG_RESTORE
+				| UVC_CTRL_FLAG_AUTO_UPDATE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_WHITE_BALANCE_COMPONENT_CONTROL,
+		.index		= 7,
+		.size		= 4,
+		.flags		= UVC_CTRL_FLAG_SET_CUR
+				| UVC_CTRL_FLAG_GET_RANGE
+				| UVC_CTRL_FLAG_RESTORE
+				| UVC_CTRL_FLAG_AUTO_UPDATE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_BACKLIGHT_COMPENSATION_CONTROL,
+		.index		= 8,
+		.size		= 2,
+		.flags		= UVC_CTRL_FLAG_SET_CUR
+				| UVC_CTRL_FLAG_GET_RANGE
+				| UVC_CTRL_FLAG_RESTORE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_GAIN_CONTROL,
+		.index		= 9,
+		.size		= 2,
+		.flags		= UVC_CTRL_FLAG_SET_CUR
+				| UVC_CTRL_FLAG_GET_RANGE
+				| UVC_CTRL_FLAG_RESTORE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_POWER_LINE_FREQUENCY_CONTROL,
+		.index		= 10,
+		.size		= 1,
+		.flags		= UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR
+				| UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_RESTORE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_HUE_AUTO_CONTROL,
+		.index		= 11,
+		.size		= 1,
+		.flags		= UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR
+				| UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_RESTORE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL,
+		.index		= 12,
+		.size		= 1,
+		.flags		= UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR
+				| UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_RESTORE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_WHITE_BALANCE_COMPONENT_AUTO_CONTROL,
+		.index		= 13,
+		.size		= 1,
+		.flags		= UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR
+				| UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_RESTORE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_DIGITAL_MULTIPLIER_CONTROL,
+		.index		= 14,
+		.size		= 2,
+		.flags		= UVC_CTRL_FLAG_SET_CUR
+				| UVC_CTRL_FLAG_GET_RANGE
+				| UVC_CTRL_FLAG_RESTORE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_DIGITAL_MULTIPLIER_LIMIT_CONTROL,
+		.index		= 15,
+		.size		= 2,
+		.flags		= UVC_CTRL_FLAG_SET_CUR
+				| UVC_CTRL_FLAG_GET_RANGE
+				| UVC_CTRL_FLAG_RESTORE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_ANALOG_VIDEO_STANDARD_CONTROL,
+		.index		= 16,
+		.size		= 1,
+		.flags		= UVC_CTRL_FLAG_GET_CUR,
+	},
+	{
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_ANALOG_LOCK_STATUS_CONTROL,
+		.index		= 17,
+		.size		= 1,
+		.flags		= UVC_CTRL_FLAG_GET_CUR,
+	},
+	{
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_SCANNING_MODE_CONTROL,
+		.index		= 0,
+		.size		= 1,
+		.flags		= UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR
+				| UVC_CTRL_FLAG_RESTORE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_AE_MODE_CONTROL,
+		.index		= 1,
+		.size		= 1,
+		.flags		= UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR
+				| UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_GET_RES
+				| UVC_CTRL_FLAG_RESTORE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_AE_PRIORITY_CONTROL,
+		.index		= 2,
+		.size		= 1,
+		.flags		= UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR
+				| UVC_CTRL_FLAG_RESTORE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_EXPOSURE_TIME_ABSOLUTE_CONTROL,
+		.index		= 3,
+		.size		= 4,
+		.flags		= UVC_CTRL_FLAG_SET_CUR
+				| UVC_CTRL_FLAG_GET_RANGE
+				| UVC_CTRL_FLAG_RESTORE
+				| UVC_CTRL_FLAG_AUTO_UPDATE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_EXPOSURE_TIME_RELATIVE_CONTROL,
+		.index		= 4,
+		.size		= 1,
+		.flags		= UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_RESTORE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_FOCUS_ABSOLUTE_CONTROL,
+		.index		= 5,
+		.size		= 2,
+		.flags		= UVC_CTRL_FLAG_SET_CUR
+				| UVC_CTRL_FLAG_GET_RANGE
+				| UVC_CTRL_FLAG_RESTORE
+				| UVC_CTRL_FLAG_AUTO_UPDATE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_FOCUS_RELATIVE_CONTROL,
+		.index		= 6,
+		.size		= 2,
+		.flags		= UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_MIN
+				| UVC_CTRL_FLAG_GET_MAX | UVC_CTRL_FLAG_GET_RES
+				| UVC_CTRL_FLAG_GET_DEF
+				| UVC_CTRL_FLAG_AUTO_UPDATE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_IRIS_ABSOLUTE_CONTROL,
+		.index		= 7,
+		.size		= 2,
+		.flags		= UVC_CTRL_FLAG_SET_CUR
+				| UVC_CTRL_FLAG_GET_RANGE
+				| UVC_CTRL_FLAG_RESTORE
+				| UVC_CTRL_FLAG_AUTO_UPDATE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_IRIS_RELATIVE_CONTROL,
+		.index		= 8,
+		.size		= 1,
+		.flags		= UVC_CTRL_FLAG_SET_CUR
+				| UVC_CTRL_FLAG_AUTO_UPDATE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_ZOOM_ABSOLUTE_CONTROL,
+		.index		= 9,
+		.size		= 2,
+		.flags		= UVC_CTRL_FLAG_SET_CUR
+				| UVC_CTRL_FLAG_GET_RANGE
+				| UVC_CTRL_FLAG_RESTORE
+				| UVC_CTRL_FLAG_AUTO_UPDATE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_ZOOM_RELATIVE_CONTROL,
+		.index		= 10,
+		.size		= 3,
+		.flags		= UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_MIN
+				| UVC_CTRL_FLAG_GET_MAX | UVC_CTRL_FLAG_GET_RES
+				| UVC_CTRL_FLAG_GET_DEF
+				| UVC_CTRL_FLAG_AUTO_UPDATE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_PANTILT_ABSOLUTE_CONTROL,
+		.index		= 11,
+		.size		= 8,
+		.flags		= UVC_CTRL_FLAG_SET_CUR
+				| UVC_CTRL_FLAG_GET_RANGE
+				| UVC_CTRL_FLAG_RESTORE
+				| UVC_CTRL_FLAG_AUTO_UPDATE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_PANTILT_RELATIVE_CONTROL,
+		.index		= 12,
+		.size		= 4,
+		.flags		= UVC_CTRL_FLAG_SET_CUR
+				| UVC_CTRL_FLAG_GET_RANGE
+				| UVC_CTRL_FLAG_AUTO_UPDATE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_ROLL_ABSOLUTE_CONTROL,
+		.index		= 13,
+		.size		= 2,
+		.flags		= UVC_CTRL_FLAG_SET_CUR
+				| UVC_CTRL_FLAG_GET_RANGE
+				| UVC_CTRL_FLAG_RESTORE
+				| UVC_CTRL_FLAG_AUTO_UPDATE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_ROLL_RELATIVE_CONTROL,
+		.index		= 14,
+		.size		= 2,
+		.flags		= UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_MIN
+				| UVC_CTRL_FLAG_GET_MAX | UVC_CTRL_FLAG_GET_RES
+				| UVC_CTRL_FLAG_GET_DEF
+				| UVC_CTRL_FLAG_AUTO_UPDATE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_FOCUS_AUTO_CONTROL,
+		.index		= 17,
+		.size		= 1,
+		.flags		= UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR
+				| UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_RESTORE,
+	},
+	{
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_PRIVACY_CONTROL,
+		.index		= 18,
+		.size		= 1,
+		.flags		= UVC_CTRL_FLAG_SET_CUR | UVC_CTRL_FLAG_GET_CUR
+				| UVC_CTRL_FLAG_RESTORE
+				| UVC_CTRL_FLAG_AUTO_UPDATE,
+	},
+};
+
+static struct uvc_menu_info power_line_frequency_controls[] = {
+	{ 0, "Disabled" },
+	{ 1, "50 Hz" },
+	{ 2, "60 Hz" },
+};
+
+static struct uvc_menu_info exposure_auto_controls[] = {
+	{ 2, "Auto Mode" },
+	{ 1, "Manual Mode" },
+	{ 4, "Shutter Priority Mode" },
+	{ 8, "Aperture Priority Mode" },
+};
+
+static s32 uvc_ctrl_get_zoom(struct uvc_control_mapping *mapping,
+	u8 query, const u8 *data)
+{
+	s8 zoom = (s8)data[0];
+
+	switch (query) {
+	case UVC_GET_CUR:
+		return (zoom == 0) ? 0 : (zoom > 0 ? data[2] : -data[2]);
+
+	case UVC_GET_MIN:
+	case UVC_GET_MAX:
+	case UVC_GET_RES:
+	case UVC_GET_DEF:
+	default:
+		return data[2];
+	}
+}
+
+static void uvc_ctrl_set_zoom(struct uvc_control_mapping *mapping,
+	s32 value, u8 *data)
+{
+	data[0] = value == 0 ? 0 : (value > 0) ? 1 : 0xff;
+	data[2] = min((int)abs(value), 0xff);
+}
+
+static s32 uvc_ctrl_get_rel_speed(struct uvc_control_mapping *mapping,
+	u8 query, const u8 *data)
+{
+	unsigned int first = mapping->offset / 8;
+	s8 rel = (s8)data[first];
+
+	switch (query) {
+	case UVC_GET_CUR:
+		return (rel == 0) ? 0 : (rel > 0 ? data[first+1]
+						 : -data[first+1]);
+	case UVC_GET_MIN:
+		return -data[first+1];
+	case UVC_GET_MAX:
+	case UVC_GET_RES:
+	case UVC_GET_DEF:
+	default:
+		return data[first+1];
+	}
+}
+
+static void uvc_ctrl_set_rel_speed(struct uvc_control_mapping *mapping,
+	s32 value, u8 *data)
+{
+	unsigned int first = mapping->offset / 8;
+
+	data[first] = value == 0 ? 0 : (value > 0) ? 1 : 0xff;
+	data[first+1] = min_t(int, abs(value), 0xff);
+}
+
+static struct uvc_control_mapping uvc_ctrl_mappings[] = {
+	{
+		.id		= V4L2_CID_BRIGHTNESS,
+		.name		= "Brightness",
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_BRIGHTNESS_CONTROL,
+		.size		= 16,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_INTEGER,
+		.data_type	= UVC_CTRL_DATA_TYPE_SIGNED,
+	},
+	{
+		.id		= V4L2_CID_CONTRAST,
+		.name		= "Contrast",
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_CONTRAST_CONTROL,
+		.size		= 16,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_INTEGER,
+		.data_type	= UVC_CTRL_DATA_TYPE_UNSIGNED,
+	},
+	{
+		.id		= V4L2_CID_HUE,
+		.name		= "Hue",
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_HUE_CONTROL,
+		.size		= 16,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_INTEGER,
+		.data_type	= UVC_CTRL_DATA_TYPE_SIGNED,
+		.master_id	= V4L2_CID_HUE_AUTO,
+		.master_manual	= 0,
+	},
+	{
+		.id		= V4L2_CID_SATURATION,
+		.name		= "Saturation",
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_SATURATION_CONTROL,
+		.size		= 16,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_INTEGER,
+		.data_type	= UVC_CTRL_DATA_TYPE_UNSIGNED,
+	},
+	{
+		.id		= V4L2_CID_SHARPNESS,
+		.name		= "Sharpness",
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_SHARPNESS_CONTROL,
+		.size		= 16,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_INTEGER,
+		.data_type	= UVC_CTRL_DATA_TYPE_UNSIGNED,
+	},
+	{
+		.id		= V4L2_CID_GAMMA,
+		.name		= "Gamma",
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_GAMMA_CONTROL,
+		.size		= 16,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_INTEGER,
+		.data_type	= UVC_CTRL_DATA_TYPE_UNSIGNED,
+	},
+	{
+		.id		= V4L2_CID_BACKLIGHT_COMPENSATION,
+		.name		= "Backlight Compensation",
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_BACKLIGHT_COMPENSATION_CONTROL,
+		.size		= 16,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_INTEGER,
+		.data_type	= UVC_CTRL_DATA_TYPE_UNSIGNED,
+	},
+	{
+		.id		= V4L2_CID_GAIN,
+		.name		= "Gain",
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_GAIN_CONTROL,
+		.size		= 16,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_INTEGER,
+		.data_type	= UVC_CTRL_DATA_TYPE_UNSIGNED,
+	},
+	{
+		.id		= V4L2_CID_POWER_LINE_FREQUENCY,
+		.name		= "Power Line Frequency",
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_POWER_LINE_FREQUENCY_CONTROL,
+		.size		= 2,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_MENU,
+		.data_type	= UVC_CTRL_DATA_TYPE_ENUM,
+		.menu_info	= power_line_frequency_controls,
+		.menu_count	= ARRAY_SIZE(power_line_frequency_controls),
+	},
+	{
+		.id		= V4L2_CID_HUE_AUTO,
+		.name		= "Hue, Auto",
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_HUE_AUTO_CONTROL,
+		.size		= 1,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_BOOLEAN,
+		.data_type	= UVC_CTRL_DATA_TYPE_BOOLEAN,
+		.slave_ids	= { V4L2_CID_HUE, },
+	},
+	{
+		.id		= V4L2_CID_EXPOSURE_AUTO,
+		.name		= "Exposure, Auto",
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_AE_MODE_CONTROL,
+		.size		= 4,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_MENU,
+		.data_type	= UVC_CTRL_DATA_TYPE_BITMASK,
+		.menu_info	= exposure_auto_controls,
+		.menu_count	= ARRAY_SIZE(exposure_auto_controls),
+		.slave_ids	= { V4L2_CID_EXPOSURE_ABSOLUTE, },
+	},
+	{
+		.id		= V4L2_CID_EXPOSURE_AUTO_PRIORITY,
+		.name		= "Exposure, Auto Priority",
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_AE_PRIORITY_CONTROL,
+		.size		= 1,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_BOOLEAN,
+		.data_type	= UVC_CTRL_DATA_TYPE_BOOLEAN,
+	},
+	{
+		.id		= V4L2_CID_EXPOSURE_ABSOLUTE,
+		.name		= "Exposure (Absolute)",
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_EXPOSURE_TIME_ABSOLUTE_CONTROL,
+		.size		= 32,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_INTEGER,
+		.data_type	= UVC_CTRL_DATA_TYPE_UNSIGNED,
+		.master_id	= V4L2_CID_EXPOSURE_AUTO,
+		.master_manual	= V4L2_EXPOSURE_MANUAL,
+	},
+	{
+		.id		= V4L2_CID_AUTO_WHITE_BALANCE,
+		.name		= "White Balance Temperature, Auto",
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_WHITE_BALANCE_TEMPERATURE_AUTO_CONTROL,
+		.size		= 1,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_BOOLEAN,
+		.data_type	= UVC_CTRL_DATA_TYPE_BOOLEAN,
+		.slave_ids	= { V4L2_CID_WHITE_BALANCE_TEMPERATURE, },
+	},
+	{
+		.id		= V4L2_CID_WHITE_BALANCE_TEMPERATURE,
+		.name		= "White Balance Temperature",
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_WHITE_BALANCE_TEMPERATURE_CONTROL,
+		.size		= 16,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_INTEGER,
+		.data_type	= UVC_CTRL_DATA_TYPE_UNSIGNED,
+		.master_id	= V4L2_CID_AUTO_WHITE_BALANCE,
+		.master_manual	= 0,
+	},
+	{
+		.id		= V4L2_CID_AUTO_WHITE_BALANCE,
+		.name		= "White Balance Component, Auto",
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_WHITE_BALANCE_COMPONENT_AUTO_CONTROL,
+		.size		= 1,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_BOOLEAN,
+		.data_type	= UVC_CTRL_DATA_TYPE_BOOLEAN,
+		.slave_ids	= { V4L2_CID_BLUE_BALANCE,
+				    V4L2_CID_RED_BALANCE },
+	},
+	{
+		.id		= V4L2_CID_BLUE_BALANCE,
+		.name		= "White Balance Blue Component",
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_WHITE_BALANCE_COMPONENT_CONTROL,
+		.size		= 16,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_INTEGER,
+		.data_type	= UVC_CTRL_DATA_TYPE_SIGNED,
+		.master_id	= V4L2_CID_AUTO_WHITE_BALANCE,
+		.master_manual	= 0,
+	},
+	{
+		.id		= V4L2_CID_RED_BALANCE,
+		.name		= "White Balance Red Component",
+		.entity		= UVC_GUID_UVC_PROCESSING,
+		.selector	= UVC_PU_WHITE_BALANCE_COMPONENT_CONTROL,
+		.size		= 16,
+		.offset		= 16,
+		.v4l2_type	= V4L2_CTRL_TYPE_INTEGER,
+		.data_type	= UVC_CTRL_DATA_TYPE_SIGNED,
+		.master_id	= V4L2_CID_AUTO_WHITE_BALANCE,
+		.master_manual	= 0,
+	},
+	{
+		.id		= V4L2_CID_FOCUS_ABSOLUTE,
+		.name		= "Focus (absolute)",
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_FOCUS_ABSOLUTE_CONTROL,
+		.size		= 16,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_INTEGER,
+		.data_type	= UVC_CTRL_DATA_TYPE_UNSIGNED,
+		.master_id	= V4L2_CID_FOCUS_AUTO,
+		.master_manual	= 0,
+	},
+	{
+		.id		= V4L2_CID_FOCUS_AUTO,
+		.name		= "Focus, Auto",
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_FOCUS_AUTO_CONTROL,
+		.size		= 1,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_BOOLEAN,
+		.data_type	= UVC_CTRL_DATA_TYPE_BOOLEAN,
+		.slave_ids	= { V4L2_CID_FOCUS_ABSOLUTE, },
+	},
+	{
+		.id		= V4L2_CID_IRIS_ABSOLUTE,
+		.name		= "Iris, Absolute",
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_IRIS_ABSOLUTE_CONTROL,
+		.size		= 16,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_INTEGER,
+		.data_type	= UVC_CTRL_DATA_TYPE_UNSIGNED,
+	},
+	{
+		.id		= V4L2_CID_IRIS_RELATIVE,
+		.name		= "Iris, Relative",
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_IRIS_RELATIVE_CONTROL,
+		.size		= 8,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_INTEGER,
+		.data_type	= UVC_CTRL_DATA_TYPE_SIGNED,
+	},
+	{
+		.id		= V4L2_CID_ZOOM_ABSOLUTE,
+		.name		= "Zoom, Absolute",
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_ZOOM_ABSOLUTE_CONTROL,
+		.size		= 16,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_INTEGER,
+		.data_type	= UVC_CTRL_DATA_TYPE_UNSIGNED,
+	},
+	{
+		.id		= V4L2_CID_ZOOM_CONTINUOUS,
+		.name		= "Zoom, Continuous",
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_ZOOM_RELATIVE_CONTROL,
+		.size		= 0,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_INTEGER,
+		.data_type	= UVC_CTRL_DATA_TYPE_SIGNED,
+		.get		= uvc_ctrl_get_zoom,
+		.set		= uvc_ctrl_set_zoom,
+	},
+	{
+		.id		= V4L2_CID_PAN_ABSOLUTE,
+		.name		= "Pan (Absolute)",
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_PANTILT_ABSOLUTE_CONTROL,
+		.size		= 32,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_INTEGER,
+		.data_type	= UVC_CTRL_DATA_TYPE_SIGNED,
+	},
+	{
+		.id		= V4L2_CID_TILT_ABSOLUTE,
+		.name		= "Tilt (Absolute)",
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_PANTILT_ABSOLUTE_CONTROL,
+		.size		= 32,
+		.offset		= 32,
+		.v4l2_type	= V4L2_CTRL_TYPE_INTEGER,
+		.data_type	= UVC_CTRL_DATA_TYPE_SIGNED,
+	},
+	{
+		.id		= V4L2_CID_PAN_SPEED,
+		.name		= "Pan (Speed)",
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_PANTILT_RELATIVE_CONTROL,
+		.size		= 16,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_INTEGER,
+		.data_type	= UVC_CTRL_DATA_TYPE_SIGNED,
+		.get		= uvc_ctrl_get_rel_speed,
+		.set		= uvc_ctrl_set_rel_speed,
+	},
+	{
+		.id		= V4L2_CID_TILT_SPEED,
+		.name		= "Tilt (Speed)",
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_PANTILT_RELATIVE_CONTROL,
+		.size		= 16,
+		.offset		= 16,
+		.v4l2_type	= V4L2_CTRL_TYPE_INTEGER,
+		.data_type	= UVC_CTRL_DATA_TYPE_SIGNED,
+		.get		= uvc_ctrl_get_rel_speed,
+		.set		= uvc_ctrl_set_rel_speed,
+	},
+	{
+		.id		= V4L2_CID_PRIVACY,
+		.name		= "Privacy",
+		.entity		= UVC_GUID_UVC_CAMERA,
+		.selector	= UVC_CT_PRIVACY_CONTROL,
+		.size		= 1,
+		.offset		= 0,
+		.v4l2_type	= V4L2_CTRL_TYPE_BOOLEAN,
+		.data_type	= UVC_CTRL_DATA_TYPE_BOOLEAN,
+	},
+};
+
+/* ------------------------------------------------------------------------
+ * Utility functions
+ */
+
+static inline u8 *uvc_ctrl_data(struct uvc_control *ctrl, int id)
+{
+	return ctrl->uvc_data + id * ctrl->info.size;
+}
+
+static inline int uvc_test_bit(const u8 *data, int bit)
+{
+	return (data[bit >> 3] >> (bit & 7)) & 1;
+}
+
+static inline void uvc_clear_bit(u8 *data, int bit)
+{
+	data[bit >> 3] &= ~(1 << (bit & 7));
+}
+
+/* Extract the bit string specified by mapping->offset and mapping->size
+ * from the little-endian data stored at 'data' and return the result as
+ * a signed 32bit integer. Sign extension will be performed if the mapping
+ * references a signed data type.
+ */
+static s32 uvc_get_le_value(struct uvc_control_mapping *mapping,
+	u8 query, const u8 *data)
+{
+	int bits = mapping->size;
+	int offset = mapping->offset;
+	s32 value = 0;
+	u8 mask;
+
+	data += offset / 8;
+	offset &= 7;
+	mask = ((1LL << bits) - 1) << offset;
+
+	for (; bits > 0; data++) {
+		u8 byte = *data & mask;
+		value |= offset > 0 ? (byte >> offset) : (byte << (-offset));
+		bits -= 8 - (offset > 0 ? offset : 0);
+		offset -= 8;
+		mask = (1 << bits) - 1;
+	}
+
+	/* Sign-extend the value if needed. */
+	if (mapping->data_type == UVC_CTRL_DATA_TYPE_SIGNED)
+		value |= -(value & (1 << (mapping->size - 1)));
+
+	return value;
+}
+
+/* Set the bit string specified by mapping->offset and mapping->size
+ * in the little-endian data stored at 'data' to the value 'value'.
+ */
+static void uvc_set_le_value(struct uvc_control_mapping *mapping,
+	s32 value, u8 *data)
+{
+	int bits = mapping->size;
+	int offset = mapping->offset;
+	u8 mask;
+
+	/* According to the v4l2 spec, writing any value to a button control
+	 * should result in the action belonging to the button control being
+	 * triggered. UVC devices however want to see a 1 written -> override
+	 * value.
+	 */
+	if (mapping->v4l2_type == V4L2_CTRL_TYPE_BUTTON)
+		value = -1;
+
+	data += offset / 8;
+	offset &= 7;
+
+	for (; bits > 0; data++) {
+		mask = ((1LL << bits) - 1) << offset;
+		*data = (*data & ~mask) | ((value << offset) & mask);
+		value >>= offset ? offset : 8;
+		bits -= 8 - offset;
+		offset = 0;
+	}
+}
+
+/* ------------------------------------------------------------------------
+ * Terminal and unit management
+ */
+
+static const u8 uvc_processing_guid[16] = UVC_GUID_UVC_PROCESSING;
+static const u8 uvc_camera_guid[16] = UVC_GUID_UVC_CAMERA;
+static const u8 uvc_media_transport_input_guid[16] =
+	UVC_GUID_UVC_MEDIA_TRANSPORT_INPUT;
+
+static int uvc_entity_match_guid(const struct uvc_entity *entity,
+	const u8 guid[16])
+{
+	switch (UVC_ENTITY_TYPE(entity)) {
+	case UVC_ITT_CAMERA:
+		return memcmp(uvc_camera_guid, guid, 16) == 0;
+
+	case UVC_ITT_MEDIA_TRANSPORT_INPUT:
+		return memcmp(uvc_media_transport_input_guid, guid, 16) == 0;
+
+	case UVC_VC_PROCESSING_UNIT:
+		return memcmp(uvc_processing_guid, guid, 16) == 0;
+
+	case UVC_VC_EXTENSION_UNIT:
+		return memcmp(entity->extension.guidExtensionCode,
+			      guid, 16) == 0;
+
+	default:
+		return 0;
+	}
+}
+
+/* ------------------------------------------------------------------------
+ * UVC Controls
+ */
+
+static void __uvc_find_control(struct uvc_entity *entity, u32 v4l2_id,
+	struct uvc_control_mapping **mapping, struct uvc_control **control,
+	int next)
+{
+	struct uvc_control *ctrl;
+	struct uvc_control_mapping *map;
+	unsigned int i;
+
+	if (entity == NULL)
+		return;
+
+	for (i = 0; i < entity->ncontrols; ++i) {
+		ctrl = &entity->controls[i];
+		if (!ctrl->initialized)
+			continue;
+
+		list_for_each_entry(map, &ctrl->info.mappings, list) {
+			if ((map->id == v4l2_id) && !next) {
+				*control = ctrl;
+				*mapping = map;
+				return;
+			}
+
+			if ((*mapping == NULL || (*mapping)->id > map->id) &&
+			    (map->id > v4l2_id) && next) {
+				*control = ctrl;
+				*mapping = map;
+			}
+		}
+	}
+}
+
+static struct uvc_control *uvc_find_control(struct uvc_video_chain *chain,
+	u32 v4l2_id, struct uvc_control_mapping **mapping)
+{
+	struct uvc_control *ctrl = NULL;
+	struct uvc_entity *entity;
+	int next = v4l2_id & V4L2_CTRL_FLAG_NEXT_CTRL;
+
+	*mapping = NULL;
+
+	/* Mask the query flags. */
+	v4l2_id &= V4L2_CTRL_ID_MASK;
+
+	/* Find the control. */
+	list_for_each_entry(entity, &chain->entities, chain) {
+		__uvc_find_control(entity, v4l2_id, mapping, &ctrl, next);
+		if (ctrl && !next)
+			return ctrl;
+	}
+
+	if (ctrl == NULL && !next)
+		uvc_trace(UVC_TRACE_CONTROL, "Control 0x%08x not found.\n",
+				v4l2_id);
+
+	return ctrl;
+}
+
+static int uvc_ctrl_populate_cache(struct uvc_video_chain *chain,
+	struct uvc_control *ctrl)
+{
+	int ret;
+
+	if (ctrl->info.flags & UVC_CTRL_FLAG_GET_DEF) {
+		ret = uvc_query_ctrl(chain->dev, UVC_GET_DEF, ctrl->entity->id,
+				     chain->dev->intfnum, ctrl->info.selector,
+				     uvc_ctrl_data(ctrl, UVC_CTRL_DATA_DEF),
+				     ctrl->info.size);
+		if (ret < 0)
+			return ret;
+	}
+
+	if (ctrl->info.flags & UVC_CTRL_FLAG_GET_MIN) {
+		ret = uvc_query_ctrl(chain->dev, UVC_GET_MIN, ctrl->entity->id,
+				     chain->dev->intfnum, ctrl->info.selector,
+				     uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MIN),
+				     ctrl->info.size);
+		if (ret < 0)
+			return ret;
+	}
+	if (ctrl->info.flags & UVC_CTRL_FLAG_GET_MAX) {
+		ret = uvc_query_ctrl(chain->dev, UVC_GET_MAX, ctrl->entity->id,
+				     chain->dev->intfnum, ctrl->info.selector,
+				     uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MAX),
+				     ctrl->info.size);
+		if (ret < 0)
+			return ret;
+	}
+	if (ctrl->info.flags & UVC_CTRL_FLAG_GET_RES) {
+		ret = uvc_query_ctrl(chain->dev, UVC_GET_RES, ctrl->entity->id,
+				     chain->dev->intfnum, ctrl->info.selector,
+				     uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES),
+				     ctrl->info.size);
+		if (ret < 0) {
+			if (UVC_ENTITY_TYPE(ctrl->entity) !=
+			    UVC_VC_EXTENSION_UNIT)
+				return ret;
+
+			/* GET_RES is mandatory for XU controls, but some
+			 * cameras still choke on it. Ignore errors and set the
+			 * resolution value to zero.
+			 */
+			uvc_warn_once(chain->dev, UVC_WARN_XU_GET_RES,
+				      "UVC non compliance - GET_RES failed on "
+				      "an XU control. Enabling workaround.\n");
+			memset(uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES), 0,
+			       ctrl->info.size);
+		}
+	}
+
+	ctrl->cached = 1;
+	return 0;
+}
+
+static s32 __uvc_ctrl_get_value(struct uvc_control_mapping *mapping,
+				const u8 *data)
+{
+	s32 value = mapping->get(mapping, UVC_GET_CUR, data);
+
+	if (mapping->v4l2_type == V4L2_CTRL_TYPE_MENU) {
+		struct uvc_menu_info *menu = mapping->menu_info;
+		unsigned int i;
+
+		for (i = 0; i < mapping->menu_count; ++i, ++menu) {
+			if (menu->value == value) {
+				value = i;
+				break;
+			}
+		}
+	}
+
+	return value;
+}
+
+static int __uvc_ctrl_get(struct uvc_video_chain *chain,
+	struct uvc_control *ctrl, struct uvc_control_mapping *mapping,
+	s32 *value)
+{
+	int ret;
+
+	if ((ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR) == 0)
+		return -EACCES;
+
+	if (!ctrl->loaded) {
+		ret = uvc_query_ctrl(chain->dev, UVC_GET_CUR, ctrl->entity->id,
+				chain->dev->intfnum, ctrl->info.selector,
+				uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
+				ctrl->info.size);
+		if (ret < 0)
+			return ret;
+
+		ctrl->loaded = 1;
+	}
+
+	*value = __uvc_ctrl_get_value(mapping,
+				uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT));
+
+	return 0;
+}
+
+static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
+	struct uvc_control *ctrl,
+	struct uvc_control_mapping *mapping,
+	struct v4l2_queryctrl *v4l2_ctrl)
+{
+	struct uvc_control_mapping *master_map = NULL;
+	struct uvc_control *master_ctrl = NULL;
+	struct uvc_menu_info *menu;
+	unsigned int i;
+
+	memset(v4l2_ctrl, 0, sizeof(*v4l2_ctrl));
+	v4l2_ctrl->id = mapping->id;
+	v4l2_ctrl->type = mapping->v4l2_type;
+	strlcpy(v4l2_ctrl->name, mapping->name, sizeof(v4l2_ctrl->name));
+	v4l2_ctrl->flags = 0;
+
+	if (!(ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR))
+		v4l2_ctrl->flags |= V4L2_CTRL_FLAG_WRITE_ONLY;
+	if (!(ctrl->info.flags & UVC_CTRL_FLAG_SET_CUR))
+		v4l2_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+
+	if (mapping->master_id)
+		__uvc_find_control(ctrl->entity, mapping->master_id,
+				   &master_map, &master_ctrl, 0);
+	if (master_ctrl && (master_ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR)) {
+		s32 val;
+		int ret = __uvc_ctrl_get(chain, master_ctrl, master_map, &val);
+		if (ret < 0)
+			return ret;
+
+		if (val != mapping->master_manual)
+				v4l2_ctrl->flags |= V4L2_CTRL_FLAG_INACTIVE;
+	}
+
+	if (!ctrl->cached) {
+		int ret = uvc_ctrl_populate_cache(chain, ctrl);
+		if (ret < 0)
+			return ret;
+	}
+
+	if (ctrl->info.flags & UVC_CTRL_FLAG_GET_DEF) {
+		v4l2_ctrl->default_value = mapping->get(mapping, UVC_GET_DEF,
+				uvc_ctrl_data(ctrl, UVC_CTRL_DATA_DEF));
+	}
+
+	switch (mapping->v4l2_type) {
+	case V4L2_CTRL_TYPE_MENU:
+		v4l2_ctrl->minimum = 0;
+		v4l2_ctrl->maximum = mapping->menu_count - 1;
+		v4l2_ctrl->step = 1;
+
+		menu = mapping->menu_info;
+		for (i = 0; i < mapping->menu_count; ++i, ++menu) {
+			if (menu->value == v4l2_ctrl->default_value) {
+				v4l2_ctrl->default_value = i;
+				break;
+			}
+		}
+
+		return 0;
+
+	case V4L2_CTRL_TYPE_BOOLEAN:
+		v4l2_ctrl->minimum = 0;
+		v4l2_ctrl->maximum = 1;
+		v4l2_ctrl->step = 1;
+		return 0;
+
+	case V4L2_CTRL_TYPE_BUTTON:
+		v4l2_ctrl->minimum = 0;
+		v4l2_ctrl->maximum = 0;
+		v4l2_ctrl->step = 0;
+		return 0;
+
+	default:
+		break;
+	}
+
+	if (ctrl->info.flags & UVC_CTRL_FLAG_GET_MIN)
+		v4l2_ctrl->minimum = mapping->get(mapping, UVC_GET_MIN,
+				     uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MIN));
+
+	if (ctrl->info.flags & UVC_CTRL_FLAG_GET_MAX)
+		v4l2_ctrl->maximum = mapping->get(mapping, UVC_GET_MAX,
+				     uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MAX));
+
+	if (ctrl->info.flags & UVC_CTRL_FLAG_GET_RES)
+		v4l2_ctrl->step = mapping->get(mapping, UVC_GET_RES,
+				  uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES));
+
+	return 0;
+}
+
+int uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
+	struct v4l2_queryctrl *v4l2_ctrl)
+{
+	struct uvc_control *ctrl;
+	struct uvc_control_mapping *mapping;
+	int ret;
+
+	ret = mutex_lock_interruptible(&chain->ctrl_mutex);
+	if (ret < 0)
+		return -ERESTARTSYS;
+
+	ctrl = uvc_find_control(chain, v4l2_ctrl->id, &mapping);
+	if (ctrl == NULL) {
+		ret = -EINVAL;
+		goto done;
+	}
+
+	ret = __uvc_query_v4l2_ctrl(chain, ctrl, mapping, v4l2_ctrl);
+done:
+	mutex_unlock(&chain->ctrl_mutex);
+	return ret;
+}
+
+/*
+ * Mapping V4L2 controls to UVC controls can be straightforward if done well.
+ * Most of the UVC controls exist in V4L2, and can be mapped directly. Some
+ * must be grouped (for instance the Red Balance, Blue Balance and Do White
+ * Balance V4L2 controls use the White Balance Component UVC control) or
+ * otherwise translated. The approach we take here is to use a translation
+ * table for the controls that can be mapped directly, and handle the others
+ * manually.
+ */
+int uvc_query_v4l2_menu(struct uvc_video_chain *chain,
+	struct v4l2_querymenu *query_menu)
+{
+	struct uvc_menu_info *menu_info;
+	struct uvc_control_mapping *mapping;
+	struct uvc_control *ctrl;
+	u32 index = query_menu->index;
+	u32 id = query_menu->id;
+	int ret;
+
+	memset(query_menu, 0, sizeof(*query_menu));
+	query_menu->id = id;
+	query_menu->index = index;
+
+	ret = mutex_lock_interruptible(&chain->ctrl_mutex);
+	if (ret < 0)
+		return -ERESTARTSYS;
+
+	ctrl = uvc_find_control(chain, query_menu->id, &mapping);
+	if (ctrl == NULL || mapping->v4l2_type != V4L2_CTRL_TYPE_MENU) {
+		ret = -EINVAL;
+		goto done;
+	}
+
+	if (query_menu->index >= mapping->menu_count) {
+		ret = -EINVAL;
+		goto done;
+	}
+
+	menu_info = &mapping->menu_info[query_menu->index];
+
+	if (mapping->data_type == UVC_CTRL_DATA_TYPE_BITMASK &&
+	    (ctrl->info.flags & UVC_CTRL_FLAG_GET_RES)) {
+		s32 bitmap;
+
+		if (!ctrl->cached) {
+			ret = uvc_ctrl_populate_cache(chain, ctrl);
+			if (ret < 0)
+				goto done;
+		}
+
+		bitmap = mapping->get(mapping, UVC_GET_RES,
+				      uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES));
+		if (!(bitmap & menu_info->value)) {
+			ret = -EINVAL;
+			goto done;
+		}
+	}
+
+	strlcpy(query_menu->name, menu_info->name, sizeof(query_menu->name));
+
+done:
+	mutex_unlock(&chain->ctrl_mutex);
+	return ret;
+}
+
+/* --------------------------------------------------------------------------
+ * Ctrl event handling
+ */
+
+static void uvc_ctrl_fill_event(struct uvc_video_chain *chain,
+	struct v4l2_event *ev,
+	struct uvc_control *ctrl,
+	struct uvc_control_mapping *mapping,
+	s32 value, u32 changes)
+{
+	struct v4l2_queryctrl v4l2_ctrl;
+
+	__uvc_query_v4l2_ctrl(chain, ctrl, mapping, &v4l2_ctrl);
+
+	memset(ev->reserved, 0, sizeof(ev->reserved));
+	ev->type = V4L2_EVENT_CTRL;
+	ev->id = v4l2_ctrl.id;
+	ev->u.ctrl.value = value;
+	ev->u.ctrl.changes = changes;
+	ev->u.ctrl.type = v4l2_ctrl.type;
+	ev->u.ctrl.flags = v4l2_ctrl.flags;
+	ev->u.ctrl.minimum = v4l2_ctrl.minimum;
+	ev->u.ctrl.maximum = v4l2_ctrl.maximum;
+	ev->u.ctrl.step = v4l2_ctrl.step;
+	ev->u.ctrl.default_value = v4l2_ctrl.default_value;
+}
+
+/*
+ * Send control change events to all subscribers for the @ctrl control. By
+ * default the subscriber that generated the event, as identified by @handle,
+ * is not notified unless it has set the V4L2_EVENT_SUB_FL_ALLOW_FEEDBACK flag.
+ * @handle can be NULL for asynchronous events related to auto-update controls,
+ * in which case all subscribers are notified.
+ */
+static void uvc_ctrl_send_event(struct uvc_video_chain *chain,
+	struct uvc_fh *handle, struct uvc_control *ctrl,
+	struct uvc_control_mapping *mapping, s32 value, u32 changes)
+{
+	struct v4l2_fh *originator = handle ? &handle->vfh : NULL;
+	struct v4l2_subscribed_event *sev;
+	struct v4l2_event ev;
+
+	if (list_empty(&mapping->ev_subs))
+		return;
+
+	uvc_ctrl_fill_event(chain, &ev, ctrl, mapping, value, changes);
+
+	list_for_each_entry(sev, &mapping->ev_subs, node) {
+		if (sev->fh != originator ||
+		    (sev->flags & V4L2_EVENT_SUB_FL_ALLOW_FEEDBACK) ||
+		    (changes & V4L2_EVENT_CTRL_CH_FLAGS))
+			v4l2_event_queue_fh(sev->fh, &ev);
+	}
+}
+
+/*
+ * Send control change events for the slave of the @master control identified
+ * by the V4L2 ID @slave_id. The @handle identifies the event subscriber that
+ * generated the event and may be NULL for auto-update events.
+ */
+static void uvc_ctrl_send_slave_event(struct uvc_video_chain *chain,
+	struct uvc_fh *handle, struct uvc_control *master, u32 slave_id)
+{
+	struct uvc_control_mapping *mapping = NULL;
+	struct uvc_control *ctrl = NULL;
+	u32 changes = V4L2_EVENT_CTRL_CH_FLAGS;
+	s32 val = 0;
+
+	__uvc_find_control(master->entity, slave_id, &mapping, &ctrl, 0);
+	if (ctrl == NULL)
+		return;
+
+	if (__uvc_ctrl_get(chain, ctrl, mapping, &val) == 0)
+		changes |= V4L2_EVENT_CTRL_CH_VALUE;
+
+	uvc_ctrl_send_event(chain, handle, ctrl, mapping, val, changes);
+}
+
+static void uvc_ctrl_status_event_work(struct work_struct *work)
+{
+	struct uvc_device *dev = container_of(work, struct uvc_device,
+					      async_ctrl.work);
+	struct uvc_ctrl_work *w = &dev->async_ctrl;
+	struct uvc_video_chain *chain = w->chain;
+	struct uvc_control_mapping *mapping;
+	struct uvc_control *ctrl = w->ctrl;
+	struct uvc_fh *handle;
+	unsigned int i;
+	int ret;
+
+	mutex_lock(&chain->ctrl_mutex);
+
+	handle = ctrl->handle;
+	ctrl->handle = NULL;
+
+	list_for_each_entry(mapping, &ctrl->info.mappings, list) {
+		s32 value = __uvc_ctrl_get_value(mapping, w->data);
+
+		/*
+		 * handle may be NULL here if the device sends auto-update
+		 * events without a prior related control set from userspace.
+		 */
+		for (i = 0; i < ARRAY_SIZE(mapping->slave_ids); ++i) {
+			if (!mapping->slave_ids[i])
+				break;
+
+			uvc_ctrl_send_slave_event(chain, handle, ctrl,
+						  mapping->slave_ids[i]);
+		}
+
+		uvc_ctrl_send_event(chain, handle, ctrl, mapping, value,
+				    V4L2_EVENT_CTRL_CH_VALUE);
+	}
+
+	mutex_unlock(&chain->ctrl_mutex);
+
+	/* Resubmit the URB. */
+	w->urb->interval = dev->int_ep->desc.bInterval;
+	ret = usb_submit_urb(w->urb, GFP_KERNEL);
+	if (ret < 0)
+		uvc_printk(KERN_ERR, "Failed to resubmit status URB (%d).\n",
+			   ret);
+}
+
+bool uvc_ctrl_status_event(struct urb *urb, struct uvc_video_chain *chain,
+			   struct uvc_control *ctrl, const u8 *data)
+{
+	struct uvc_device *dev = chain->dev;
+	struct uvc_ctrl_work *w = &dev->async_ctrl;
+
+	if (list_empty(&ctrl->info.mappings)) {
+		ctrl->handle = NULL;
+		return false;
+	}
+
+	w->data = data;
+	w->urb = urb;
+	w->chain = chain;
+	w->ctrl = ctrl;
+
+	schedule_work(&w->work);
+
+	return true;
+}
+
+static bool uvc_ctrl_xctrls_has_control(const struct v4l2_ext_control *xctrls,
+					unsigned int xctrls_count, u32 id)
+{
+	unsigned int i;
+
+	for (i = 0; i < xctrls_count; ++i) {
+		if (xctrls[i].id == id)
+			return true;
+	}
+
+	return false;
+}
+
+static void uvc_ctrl_send_events(struct uvc_fh *handle,
+	const struct v4l2_ext_control *xctrls, unsigned int xctrls_count)
+{
+	struct uvc_control_mapping *mapping;
+	struct uvc_control *ctrl;
+	u32 changes = V4L2_EVENT_CTRL_CH_VALUE;
+	unsigned int i;
+	unsigned int j;
+
+	for (i = 0; i < xctrls_count; ++i) {
+		ctrl = uvc_find_control(handle->chain, xctrls[i].id, &mapping);
+
+		if (ctrl->info.flags & UVC_CTRL_FLAG_ASYNCHRONOUS)
+			/* Notification will be sent from an Interrupt event. */
+			continue;
+
+		for (j = 0; j < ARRAY_SIZE(mapping->slave_ids); ++j) {
+			u32 slave_id = mapping->slave_ids[j];
+
+			if (!slave_id)
+				break;
+
+			/*
+			 * We can skip sending an event for the slave if the
+			 * slave is being modified in the same transaction.
+			 */
+			if (uvc_ctrl_xctrls_has_control(xctrls, xctrls_count,
+							slave_id))
+				continue;
+
+			uvc_ctrl_send_slave_event(handle->chain, handle, ctrl,
+						  slave_id);
+		}
+
+		/*
+		 * If the master is being modified in the same transaction
+		 * flags may change too.
+		 */
+		if (mapping->master_id &&
+		    uvc_ctrl_xctrls_has_control(xctrls, xctrls_count,
+						mapping->master_id))
+			changes |= V4L2_EVENT_CTRL_CH_FLAGS;
+
+		uvc_ctrl_send_event(handle->chain, handle, ctrl, mapping,
+				    xctrls[i].value, changes);
+	}
+}
+
+static int uvc_ctrl_add_event(struct v4l2_subscribed_event *sev, unsigned elems)
+{
+	struct uvc_fh *handle = container_of(sev->fh, struct uvc_fh, vfh);
+	struct uvc_control_mapping *mapping;
+	struct uvc_control *ctrl;
+	int ret;
+
+	ret = mutex_lock_interruptible(&handle->chain->ctrl_mutex);
+	if (ret < 0)
+		return -ERESTARTSYS;
+
+	ctrl = uvc_find_control(handle->chain, sev->id, &mapping);
+	if (ctrl == NULL) {
+		ret = -EINVAL;
+		goto done;
+	}
+
+	list_add_tail(&sev->node, &mapping->ev_subs);
+	if (sev->flags & V4L2_EVENT_SUB_FL_SEND_INITIAL) {
+		struct v4l2_event ev;
+		u32 changes = V4L2_EVENT_CTRL_CH_FLAGS;
+		s32 val = 0;
+
+		if (__uvc_ctrl_get(handle->chain, ctrl, mapping, &val) == 0)
+			changes |= V4L2_EVENT_CTRL_CH_VALUE;
+
+		uvc_ctrl_fill_event(handle->chain, &ev, ctrl, mapping, val,
+				    changes);
+		/* Mark the queue as active, allowing this initial
+		   event to be accepted. */
+		sev->elems = elems;
+		v4l2_event_queue_fh(sev->fh, &ev);
+	}
+
+done:
+	mutex_unlock(&handle->chain->ctrl_mutex);
+	return ret;
+}
+
+static void uvc_ctrl_del_event(struct v4l2_subscribed_event *sev)
+{
+	struct uvc_fh *handle = container_of(sev->fh, struct uvc_fh, vfh);
+
+	mutex_lock(&handle->chain->ctrl_mutex);
+	list_del(&sev->node);
+	mutex_unlock(&handle->chain->ctrl_mutex);
+}
+
+const struct v4l2_subscribed_event_ops uvc_ctrl_sub_ev_ops = {
+	.add = uvc_ctrl_add_event,
+	.del = uvc_ctrl_del_event,
+	.replace = v4l2_ctrl_replace,
+	.merge = v4l2_ctrl_merge,
+};
+
+/* --------------------------------------------------------------------------
+ * Control transactions
+ *
+ * To make extended set operations as atomic as the hardware allows, controls
+ * are handled using begin/commit/rollback operations.
+ *
+ * At the beginning of a set request, uvc_ctrl_begin should be called to
+ * initialize the request. This function acquires the control lock.
+ *
+ * When setting a control, the new value is stored in the control data field
+ * at position UVC_CTRL_DATA_CURRENT. The control is then marked as dirty for
+ * later processing. If the UVC and V4L2 control sizes differ, the current
+ * value is loaded from the hardware before storing the new value in the data
+ * field.
+ *
+ * After processing all controls in the transaction, uvc_ctrl_commit or
+ * uvc_ctrl_rollback must be called to apply the pending changes to the
+ * hardware or revert them. When applying changes, all controls marked as
+ * dirty will be modified in the UVC device, and the dirty flag will be
+ * cleared. When reverting controls, the control data field
+ * UVC_CTRL_DATA_CURRENT is reverted to its previous value
+ * (UVC_CTRL_DATA_BACKUP) for all dirty controls. Both functions release the
+ * control lock.
+ */
+int uvc_ctrl_begin(struct uvc_video_chain *chain)
+{
+	return mutex_lock_interruptible(&chain->ctrl_mutex) ? -ERESTARTSYS : 0;
+}
+
+static int uvc_ctrl_commit_entity(struct uvc_device *dev,
+	struct uvc_entity *entity, int rollback)
+{
+	struct uvc_control *ctrl;
+	unsigned int i;
+	int ret;
+
+	if (entity == NULL)
+		return 0;
+
+	for (i = 0; i < entity->ncontrols; ++i) {
+		ctrl = &entity->controls[i];
+		if (!ctrl->initialized)
+			continue;
+
+		/* Reset the loaded flag for auto-update controls that were
+		 * marked as loaded in uvc_ctrl_get/uvc_ctrl_set to prevent
+		 * uvc_ctrl_get from using the cached value, and for write-only
+		 * controls to prevent uvc_ctrl_set from setting bits not
+		 * explicitly set by the user.
+		 */
+		if (ctrl->info.flags & UVC_CTRL_FLAG_AUTO_UPDATE ||
+		    !(ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR))
+			ctrl->loaded = 0;
+
+		if (!ctrl->dirty)
+			continue;
+
+		if (!rollback)
+			ret = uvc_query_ctrl(dev, UVC_SET_CUR, ctrl->entity->id,
+				dev->intfnum, ctrl->info.selector,
+				uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
+				ctrl->info.size);
+		else
+			ret = 0;
+
+		if (rollback || ret < 0)
+			memcpy(uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
+			       uvc_ctrl_data(ctrl, UVC_CTRL_DATA_BACKUP),
+			       ctrl->info.size);
+
+		ctrl->dirty = 0;
+
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+int __uvc_ctrl_commit(struct uvc_fh *handle, int rollback,
+		      const struct v4l2_ext_control *xctrls,
+		      unsigned int xctrls_count)
+{
+	struct uvc_video_chain *chain = handle->chain;
+	struct uvc_entity *entity;
+	int ret = 0;
+
+	/* Find the control. */
+	list_for_each_entry(entity, &chain->entities, chain) {
+		ret = uvc_ctrl_commit_entity(chain->dev, entity, rollback);
+		if (ret < 0)
+			goto done;
+	}
+
+	if (!rollback)
+		uvc_ctrl_send_events(handle, xctrls, xctrls_count);
+done:
+	mutex_unlock(&chain->ctrl_mutex);
+	return ret;
+}
+
+int uvc_ctrl_get(struct uvc_video_chain *chain,
+	struct v4l2_ext_control *xctrl)
+{
+	struct uvc_control *ctrl;
+	struct uvc_control_mapping *mapping;
+
+	ctrl = uvc_find_control(chain, xctrl->id, &mapping);
+	if (ctrl == NULL)
+		return -EINVAL;
+
+	return __uvc_ctrl_get(chain, ctrl, mapping, &xctrl->value);
+}
+
+int uvc_ctrl_set(struct uvc_fh *handle,
+	struct v4l2_ext_control *xctrl)
+{
+	struct uvc_video_chain *chain = handle->chain;
+	struct uvc_control *ctrl;
+	struct uvc_control_mapping *mapping;
+	s32 value;
+	u32 step;
+	s32 min;
+	s32 max;
+	int ret;
+
+	ctrl = uvc_find_control(chain, xctrl->id, &mapping);
+	if (ctrl == NULL)
+		return -EINVAL;
+	if (!(ctrl->info.flags & UVC_CTRL_FLAG_SET_CUR))
+		return -EACCES;
+
+	/* Clamp out of range values. */
+	switch (mapping->v4l2_type) {
+	case V4L2_CTRL_TYPE_INTEGER:
+		if (!ctrl->cached) {
+			ret = uvc_ctrl_populate_cache(chain, ctrl);
+			if (ret < 0)
+				return ret;
+		}
+
+		min = mapping->get(mapping, UVC_GET_MIN,
+				   uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MIN));
+		max = mapping->get(mapping, UVC_GET_MAX,
+				   uvc_ctrl_data(ctrl, UVC_CTRL_DATA_MAX));
+		step = mapping->get(mapping, UVC_GET_RES,
+				    uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES));
+		if (step == 0)
+			step = 1;
+
+		xctrl->value = min + ((u32)(xctrl->value - min) + step / 2)
+			     / step * step;
+		if (mapping->data_type == UVC_CTRL_DATA_TYPE_SIGNED)
+			xctrl->value = clamp(xctrl->value, min, max);
+		else
+			xctrl->value = clamp_t(u32, xctrl->value, min, max);
+		value = xctrl->value;
+		break;
+
+	case V4L2_CTRL_TYPE_BOOLEAN:
+		xctrl->value = clamp(xctrl->value, 0, 1);
+		value = xctrl->value;
+		break;
+
+	case V4L2_CTRL_TYPE_MENU:
+		if (xctrl->value < 0 || xctrl->value >= mapping->menu_count)
+			return -ERANGE;
+		value = mapping->menu_info[xctrl->value].value;
+
+		/* Valid menu indices are reported by the GET_RES request for
+		 * UVC controls that support it.
+		 */
+		if (mapping->data_type == UVC_CTRL_DATA_TYPE_BITMASK &&
+		    (ctrl->info.flags & UVC_CTRL_FLAG_GET_RES)) {
+			if (!ctrl->cached) {
+				ret = uvc_ctrl_populate_cache(chain, ctrl);
+				if (ret < 0)
+					return ret;
+			}
+
+			step = mapping->get(mapping, UVC_GET_RES,
+					uvc_ctrl_data(ctrl, UVC_CTRL_DATA_RES));
+			if (!(step & value))
+				return -EINVAL;
+		}
+
+		break;
+
+	default:
+		value = xctrl->value;
+		break;
+	}
+
+	/* If the mapping doesn't span the whole UVC control, the current value
+	 * needs to be loaded from the device to perform the read-modify-write
+	 * operation.
+	 */
+	if (!ctrl->loaded && (ctrl->info.size * 8) != mapping->size) {
+		if ((ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR) == 0) {
+			memset(uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
+				0, ctrl->info.size);
+		} else {
+			ret = uvc_query_ctrl(chain->dev, UVC_GET_CUR,
+				ctrl->entity->id, chain->dev->intfnum,
+				ctrl->info.selector,
+				uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
+				ctrl->info.size);
+			if (ret < 0)
+				return ret;
+		}
+
+		ctrl->loaded = 1;
+	}
+
+	/* Backup the current value in case we need to rollback later. */
+	if (!ctrl->dirty) {
+		memcpy(uvc_ctrl_data(ctrl, UVC_CTRL_DATA_BACKUP),
+		       uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT),
+		       ctrl->info.size);
+	}
+
+	mapping->set(mapping, value,
+		uvc_ctrl_data(ctrl, UVC_CTRL_DATA_CURRENT));
+
+	if (ctrl->info.flags & UVC_CTRL_FLAG_ASYNCHRONOUS)
+		ctrl->handle = handle;
+
+	ctrl->dirty = 1;
+	ctrl->modified = 1;
+	return 0;
+}
+
+/* --------------------------------------------------------------------------
+ * Dynamic controls
+ */
+
+/*
+ * Retrieve flags for a given control
+ */
+static int uvc_ctrl_get_flags(struct uvc_device *dev,
+			      const struct uvc_control *ctrl,
+			      struct uvc_control_info *info)
+{
+	u8 *data;
+	int ret;
+
+	data = kmalloc(1, GFP_KERNEL);
+	if (data == NULL)
+		return -ENOMEM;
+
+	ret = uvc_query_ctrl(dev, UVC_GET_INFO, ctrl->entity->id, dev->intfnum,
+			     info->selector, data, 1);
+	if (!ret)
+		info->flags |= (data[0] & UVC_CONTROL_CAP_GET ?
+				UVC_CTRL_FLAG_GET_CUR : 0)
+			    |  (data[0] & UVC_CONTROL_CAP_SET ?
+				UVC_CTRL_FLAG_SET_CUR : 0)
+			    |  (data[0] & UVC_CONTROL_CAP_AUTOUPDATE ?
+				UVC_CTRL_FLAG_AUTO_UPDATE : 0)
+			    |  (data[0] & UVC_CONTROL_CAP_ASYNCHRONOUS ?
+				UVC_CTRL_FLAG_ASYNCHRONOUS : 0);
+
+	kfree(data);
+	return ret;
+}
+
+static void uvc_ctrl_fixup_xu_info(struct uvc_device *dev,
+	const struct uvc_control *ctrl, struct uvc_control_info *info)
+{
+	struct uvc_ctrl_fixup {
+		struct usb_device_id id;
+		u8 entity;
+		u8 selector;
+		u8 flags;
+	};
+
+	static const struct uvc_ctrl_fixup fixups[] = {
+		{ { USB_DEVICE(0x046d, 0x08c2) }, 9, 1,
+			UVC_CTRL_FLAG_GET_MIN | UVC_CTRL_FLAG_GET_MAX |
+			UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_SET_CUR |
+			UVC_CTRL_FLAG_AUTO_UPDATE },
+		{ { USB_DEVICE(0x046d, 0x08cc) }, 9, 1,
+			UVC_CTRL_FLAG_GET_MIN | UVC_CTRL_FLAG_GET_MAX |
+			UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_SET_CUR |
+			UVC_CTRL_FLAG_AUTO_UPDATE },
+		{ { USB_DEVICE(0x046d, 0x0994) }, 9, 1,
+			UVC_CTRL_FLAG_GET_MIN | UVC_CTRL_FLAG_GET_MAX |
+			UVC_CTRL_FLAG_GET_DEF | UVC_CTRL_FLAG_SET_CUR |
+			UVC_CTRL_FLAG_AUTO_UPDATE },
+	};
+
+	unsigned int i;
+
+	for (i = 0; i < ARRAY_SIZE(fixups); ++i) {
+		if (!usb_match_one_id(dev->intf, &fixups[i].id))
+			continue;
+
+		if (fixups[i].entity == ctrl->entity->id &&
+		    fixups[i].selector == info->selector) {
+			info->flags = fixups[i].flags;
+			return;
+		}
+	}
+}
+
+/*
+ * Query control information (size and flags) for XU controls.
+ */
+static int uvc_ctrl_fill_xu_info(struct uvc_device *dev,
+	const struct uvc_control *ctrl, struct uvc_control_info *info)
+{
+	u8 *data;
+	int ret;
+
+	data = kmalloc(2, GFP_KERNEL);
+	if (data == NULL)
+		return -ENOMEM;
+
+	memcpy(info->entity, ctrl->entity->extension.guidExtensionCode,
+	       sizeof(info->entity));
+	info->index = ctrl->index;
+	info->selector = ctrl->index + 1;
+
+	/* Query and verify the control length (GET_LEN) */
+	ret = uvc_query_ctrl(dev, UVC_GET_LEN, ctrl->entity->id, dev->intfnum,
+			     info->selector, data, 2);
+	if (ret < 0) {
+		uvc_trace(UVC_TRACE_CONTROL,
+			  "GET_LEN failed on control %pUl/%u (%d).\n",
+			   info->entity, info->selector, ret);
+		goto done;
+	}
+
+	info->size = le16_to_cpup((__le16 *)data);
+
+	info->flags = UVC_CTRL_FLAG_GET_MIN | UVC_CTRL_FLAG_GET_MAX
+		    | UVC_CTRL_FLAG_GET_RES | UVC_CTRL_FLAG_GET_DEF;
+
+	ret = uvc_ctrl_get_flags(dev, ctrl, info);
+	if (ret < 0) {
+		uvc_trace(UVC_TRACE_CONTROL,
+			  "Failed to get flags for control %pUl/%u (%d).\n",
+			  info->entity, info->selector, ret);
+		goto done;
+	}
+
+	uvc_ctrl_fixup_xu_info(dev, ctrl, info);
+
+	uvc_trace(UVC_TRACE_CONTROL, "XU control %pUl/%u queried: len %u, "
+		  "flags { get %u set %u auto %u }.\n",
+		  info->entity, info->selector, info->size,
+		  (info->flags & UVC_CTRL_FLAG_GET_CUR) ? 1 : 0,
+		  (info->flags & UVC_CTRL_FLAG_SET_CUR) ? 1 : 0,
+		  (info->flags & UVC_CTRL_FLAG_AUTO_UPDATE) ? 1 : 0);
+
+done:
+	kfree(data);
+	return ret;
+}
+
+static int uvc_ctrl_add_info(struct uvc_device *dev, struct uvc_control *ctrl,
+	const struct uvc_control_info *info);
+
+static int uvc_ctrl_init_xu_ctrl(struct uvc_device *dev,
+	struct uvc_control *ctrl)
+{
+	struct uvc_control_info info;
+	int ret;
+
+	if (ctrl->initialized)
+		return 0;
+
+	ret = uvc_ctrl_fill_xu_info(dev, ctrl, &info);
+	if (ret < 0)
+		return ret;
+
+	ret = uvc_ctrl_add_info(dev, ctrl, &info);
+	if (ret < 0)
+		uvc_trace(UVC_TRACE_CONTROL, "Failed to initialize control "
+			  "%pUl/%u on device %s entity %u\n", info.entity,
+			  info.selector, dev->udev->devpath, ctrl->entity->id);
+
+	return ret;
+}
+
+int uvc_xu_ctrl_query(struct uvc_video_chain *chain,
+	struct uvc_xu_control_query *xqry)
+{
+	struct uvc_entity *entity;
+	struct uvc_control *ctrl;
+	unsigned int i, found = 0;
+	u32 reqflags;
+	u16 size;
+	u8 *data = NULL;
+	int ret;
+
+	/* Find the extension unit. */
+	list_for_each_entry(entity, &chain->entities, chain) {
+		if (UVC_ENTITY_TYPE(entity) == UVC_VC_EXTENSION_UNIT &&
+		    entity->id == xqry->unit)
+			break;
+	}
+
+	if (entity->id != xqry->unit) {
+		uvc_trace(UVC_TRACE_CONTROL, "Extension unit %u not found.\n",
+			xqry->unit);
+		return -ENOENT;
+	}
+
+	/* Find the control and perform delayed initialization if needed. */
+	for (i = 0; i < entity->ncontrols; ++i) {
+		ctrl = &entity->controls[i];
+		if (ctrl->index == xqry->selector - 1) {
+			found = 1;
+			break;
+		}
+	}
+
+	if (!found) {
+		uvc_trace(UVC_TRACE_CONTROL, "Control %pUl/%u not found.\n",
+			entity->extension.guidExtensionCode, xqry->selector);
+		return -ENOENT;
+	}
+
+	if (mutex_lock_interruptible(&chain->ctrl_mutex))
+		return -ERESTARTSYS;
+
+	ret = uvc_ctrl_init_xu_ctrl(chain->dev, ctrl);
+	if (ret < 0) {
+		ret = -ENOENT;
+		goto done;
+	}
+
+	/* Validate the required buffer size and flags for the request */
+	reqflags = 0;
+	size = ctrl->info.size;
+
+	switch (xqry->query) {
+	case UVC_GET_CUR:
+		reqflags = UVC_CTRL_FLAG_GET_CUR;
+		break;
+	case UVC_GET_MIN:
+		reqflags = UVC_CTRL_FLAG_GET_MIN;
+		break;
+	case UVC_GET_MAX:
+		reqflags = UVC_CTRL_FLAG_GET_MAX;
+		break;
+	case UVC_GET_DEF:
+		reqflags = UVC_CTRL_FLAG_GET_DEF;
+		break;
+	case UVC_GET_RES:
+		reqflags = UVC_CTRL_FLAG_GET_RES;
+		break;
+	case UVC_SET_CUR:
+		reqflags = UVC_CTRL_FLAG_SET_CUR;
+		break;
+	case UVC_GET_LEN:
+		size = 2;
+		break;
+	case UVC_GET_INFO:
+		size = 1;
+		break;
+	default:
+		ret = -EINVAL;
+		goto done;
+	}
+
+	if (size != xqry->size) {
+		ret = -ENOBUFS;
+		goto done;
+	}
+
+	if (reqflags && !(ctrl->info.flags & reqflags)) {
+		ret = -EBADRQC;
+		goto done;
+	}
+
+	data = kmalloc(size, GFP_KERNEL);
+	if (data == NULL) {
+		ret = -ENOMEM;
+		goto done;
+	}
+
+	if (xqry->query == UVC_SET_CUR &&
+	    copy_from_user(data, xqry->data, size)) {
+		ret = -EFAULT;
+		goto done;
+	}
+
+	ret = uvc_query_ctrl(chain->dev, xqry->query, xqry->unit,
+			     chain->dev->intfnum, xqry->selector, data, size);
+	if (ret < 0)
+		goto done;
+
+	if (xqry->query != UVC_SET_CUR &&
+	    copy_to_user(xqry->data, data, size))
+		ret = -EFAULT;
+done:
+	kfree(data);
+	mutex_unlock(&chain->ctrl_mutex);
+	return ret;
+}
+
+/* --------------------------------------------------------------------------
+ * Suspend/resume
+ */
+
+/*
+ * Restore control values after resume, skipping controls that haven't been
+ * changed.
+ *
+ * TODO
+ * - Don't restore modified controls that are back to their default value.
+ * - Handle restore order (Auto-Exposure Mode should be restored before
+ *   Exposure Time).
+ */
+int uvc_ctrl_restore_values(struct uvc_device *dev)
+{
+	struct uvc_control *ctrl;
+	struct uvc_entity *entity;
+	unsigned int i;
+	int ret;
+
+	/* Walk the entities list and restore controls when possible. */
+	list_for_each_entry(entity, &dev->entities, list) {
+
+		for (i = 0; i < entity->ncontrols; ++i) {
+			ctrl = &entity->controls[i];
+
+			if (!ctrl->initialized || !ctrl->modified ||
+			    (ctrl->info.flags & UVC_CTRL_FLAG_RESTORE) == 0)
+				continue;
+
+			printk(KERN_INFO "restoring control %pUl/%u/%u\n",
+				ctrl->info.entity, ctrl->info.index,
+				ctrl->info.selector);
+			ctrl->dirty = 1;
+		}
+
+		ret = uvc_ctrl_commit_entity(dev, entity, 0);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+/* --------------------------------------------------------------------------
+ * Control and mapping handling
+ */
+
+/*
+ * Add control information to a given control.
+ */
+static int uvc_ctrl_add_info(struct uvc_device *dev, struct uvc_control *ctrl,
+	const struct uvc_control_info *info)
+{
+	int ret = 0;
+
+	ctrl->info = *info;
+	INIT_LIST_HEAD(&ctrl->info.mappings);
+
+	/* Allocate an array to save control values (cur, def, max, etc.) */
+	ctrl->uvc_data = kzalloc(ctrl->info.size * UVC_CTRL_DATA_LAST + 1,
+				 GFP_KERNEL);
+	if (ctrl->uvc_data == NULL) {
+		ret = -ENOMEM;
+		goto done;
+	}
+
+	/*
+	 * Retrieve control flags from the device. Ignore errors and work with
+	 * default flag values from the uvc_ctrl array when the device doesn't
+	 * properly implement GET_INFO on standard controls.
+	 */
+	uvc_ctrl_get_flags(dev, ctrl, &ctrl->info);
+
+	ctrl->initialized = 1;
+
+	uvc_trace(UVC_TRACE_CONTROL, "Added control %pUl/%u to device %s "
+		"entity %u\n", ctrl->info.entity, ctrl->info.selector,
+		dev->udev->devpath, ctrl->entity->id);
+
+done:
+	if (ret < 0)
+		kfree(ctrl->uvc_data);
+	return ret;
+}
+
+/*
+ * Add a control mapping to a given control.
+ */
+static int __uvc_ctrl_add_mapping(struct uvc_device *dev,
+	struct uvc_control *ctrl, const struct uvc_control_mapping *mapping)
+{
+	struct uvc_control_mapping *map;
+	unsigned int size;
+
+	/* Most mappings come from static kernel data and need to be duplicated.
+	 * Mappings that come from userspace will be unnecessarily duplicated,
+	 * this could be optimized.
+	 */
+	map = kmemdup(mapping, sizeof(*mapping), GFP_KERNEL);
+	if (map == NULL)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&map->ev_subs);
+
+	size = sizeof(*mapping->menu_info) * mapping->menu_count;
+	map->menu_info = kmemdup(mapping->menu_info, size, GFP_KERNEL);
+	if (map->menu_info == NULL) {
+		kfree(map);
+		return -ENOMEM;
+	}
+
+	if (map->get == NULL)
+		map->get = uvc_get_le_value;
+	if (map->set == NULL)
+		map->set = uvc_set_le_value;
+
+	list_add_tail(&map->list, &ctrl->info.mappings);
+	uvc_trace(UVC_TRACE_CONTROL,
+		"Adding mapping '%s' to control %pUl/%u.\n",
+		map->name, ctrl->info.entity, ctrl->info.selector);
+
+	return 0;
+}
+
+int uvc_ctrl_add_mapping(struct uvc_video_chain *chain,
+	const struct uvc_control_mapping *mapping)
+{
+	struct uvc_device *dev = chain->dev;
+	struct uvc_control_mapping *map;
+	struct uvc_entity *entity;
+	struct uvc_control *ctrl;
+	int found = 0;
+	int ret;
+
+	if (mapping->id & ~V4L2_CTRL_ID_MASK) {
+		uvc_trace(UVC_TRACE_CONTROL, "Can't add mapping '%s', control "
+			"id 0x%08x is invalid.\n", mapping->name,
+			mapping->id);
+		return -EINVAL;
+	}
+
+	/* Search for the matching (GUID/CS) control on the current chain */
+	list_for_each_entry(entity, &chain->entities, chain) {
+		unsigned int i;
+
+		if (UVC_ENTITY_TYPE(entity) != UVC_VC_EXTENSION_UNIT ||
+		    !uvc_entity_match_guid(entity, mapping->entity))
+			continue;
+
+		for (i = 0; i < entity->ncontrols; ++i) {
+			ctrl = &entity->controls[i];
+			if (ctrl->index == mapping->selector - 1) {
+				found = 1;
+				break;
+			}
+		}
+
+		if (found)
+			break;
+	}
+	if (!found)
+		return -ENOENT;
+
+	if (mutex_lock_interruptible(&chain->ctrl_mutex))
+		return -ERESTARTSYS;
+
+	/* Perform delayed initialization of XU controls */
+	ret = uvc_ctrl_init_xu_ctrl(dev, ctrl);
+	if (ret < 0) {
+		ret = -ENOENT;
+		goto done;
+	}
+
+	/* Validate the user-provided bit-size and offset */
+	if (mapping->size > 32 ||
+	    mapping->offset + mapping->size > ctrl->info.size * 8) {
+		ret = -EINVAL;
+		goto done;
+	}
+
+	list_for_each_entry(map, &ctrl->info.mappings, list) {
+		if (mapping->id == map->id) {
+			uvc_trace(UVC_TRACE_CONTROL, "Can't add mapping '%s', "
+				"control id 0x%08x already exists.\n",
+				mapping->name, mapping->id);
+			ret = -EEXIST;
+			goto done;
+		}
+	}
+
+	/* Prevent excess memory consumption */
+	if (atomic_inc_return(&dev->nmappings) > UVC_MAX_CONTROL_MAPPINGS) {
+		atomic_dec(&dev->nmappings);
+		uvc_trace(UVC_TRACE_CONTROL, "Can't add mapping '%s', maximum "
+			"mappings count (%u) exceeded.\n", mapping->name,
+			UVC_MAX_CONTROL_MAPPINGS);
+		ret = -ENOMEM;
+		goto done;
+	}
+
+	ret = __uvc_ctrl_add_mapping(dev, ctrl, mapping);
+	if (ret < 0)
+		atomic_dec(&dev->nmappings);
+
+done:
+	mutex_unlock(&chain->ctrl_mutex);
+	return ret;
+}
+
+/*
+ * Prune an entity of its bogus controls using a blacklist. Bogus controls
+ * are currently the ones that crash the camera or unconditionally return an
+ * error when queried.
+ */
+static void uvc_ctrl_prune_entity(struct uvc_device *dev,
+	struct uvc_entity *entity)
+{
+	struct uvc_ctrl_blacklist {
+		struct usb_device_id id;
+		u8 index;
+	};
+
+	static const struct uvc_ctrl_blacklist processing_blacklist[] = {
+		{ { USB_DEVICE(0x13d3, 0x509b) }, 9 }, /* Gain */
+		{ { USB_DEVICE(0x1c4f, 0x3000) }, 6 }, /* WB Temperature */
+		{ { USB_DEVICE(0x5986, 0x0241) }, 2 }, /* Hue */
+	};
+	static const struct uvc_ctrl_blacklist camera_blacklist[] = {
+		{ { USB_DEVICE(0x06f8, 0x3005) }, 9 }, /* Zoom, Absolute */
+	};
+
+	const struct uvc_ctrl_blacklist *blacklist;
+	unsigned int size;
+	unsigned int count;
+	unsigned int i;
+	u8 *controls;
+
+	switch (UVC_ENTITY_TYPE(entity)) {
+	case UVC_VC_PROCESSING_UNIT:
+		blacklist = processing_blacklist;
+		count = ARRAY_SIZE(processing_blacklist);
+		controls = entity->processing.bmControls;
+		size = entity->processing.bControlSize;
+		break;
+
+	case UVC_ITT_CAMERA:
+		blacklist = camera_blacklist;
+		count = ARRAY_SIZE(camera_blacklist);
+		controls = entity->camera.bmControls;
+		size = entity->camera.bControlSize;
+		break;
+
+	default:
+		return;
+	}
+
+	for (i = 0; i < count; ++i) {
+		if (!usb_match_one_id(dev->intf, &blacklist[i].id))
+			continue;
+
+		if (blacklist[i].index >= 8 * size ||
+		    !uvc_test_bit(controls, blacklist[i].index))
+			continue;
+
+		uvc_trace(UVC_TRACE_CONTROL, "%u/%u control is black listed, "
+			"removing it.\n", entity->id, blacklist[i].index);
+
+		uvc_clear_bit(controls, blacklist[i].index);
+	}
+}
+
+/*
+ * Add control information and hardcoded stock control mappings to the given
+ * device.
+ */
+static void uvc_ctrl_init_ctrl(struct uvc_device *dev, struct uvc_control *ctrl)
+{
+	const struct uvc_control_info *info = uvc_ctrls;
+	const struct uvc_control_info *iend = info + ARRAY_SIZE(uvc_ctrls);
+	const struct uvc_control_mapping *mapping = uvc_ctrl_mappings;
+	const struct uvc_control_mapping *mend =
+		mapping + ARRAY_SIZE(uvc_ctrl_mappings);
+
+	/* XU controls initialization requires querying the device for control
+	 * information. As some buggy UVC devices will crash when queried
+	 * repeatedly in a tight loop, delay XU controls initialization until
+	 * first use.
+	 */
+	if (UVC_ENTITY_TYPE(ctrl->entity) == UVC_VC_EXTENSION_UNIT)
+		return;
+
+	for (; info < iend; ++info) {
+		if (uvc_entity_match_guid(ctrl->entity, info->entity) &&
+		    ctrl->index == info->index) {
+			uvc_ctrl_add_info(dev, ctrl, info);
+			break;
+		 }
+	}
+
+	if (!ctrl->initialized)
+		return;
+
+	for (; mapping < mend; ++mapping) {
+		if (uvc_entity_match_guid(ctrl->entity, mapping->entity) &&
+		    ctrl->info.selector == mapping->selector)
+			__uvc_ctrl_add_mapping(dev, ctrl, mapping);
+	}
+}
+
+/*
+ * Initialize device controls.
+ */
+int uvc_ctrl_init_device(struct uvc_device *dev)
+{
+	struct uvc_entity *entity;
+	unsigned int i;
+
+	INIT_WORK(&dev->async_ctrl.work, uvc_ctrl_status_event_work);
+
+	/* Walk the entities list and instantiate controls */
+	list_for_each_entry(entity, &dev->entities, list) {
+		struct uvc_control *ctrl;
+		unsigned int bControlSize = 0, ncontrols;
+		u8 *bmControls = NULL;
+
+		if (UVC_ENTITY_TYPE(entity) == UVC_VC_EXTENSION_UNIT) {
+			bmControls = entity->extension.bmControls;
+			bControlSize = entity->extension.bControlSize;
+		} else if (UVC_ENTITY_TYPE(entity) == UVC_VC_PROCESSING_UNIT) {
+			bmControls = entity->processing.bmControls;
+			bControlSize = entity->processing.bControlSize;
+		} else if (UVC_ENTITY_TYPE(entity) == UVC_ITT_CAMERA) {
+			bmControls = entity->camera.bmControls;
+			bControlSize = entity->camera.bControlSize;
+		}
+
+		/* Remove bogus/blacklisted controls */
+		uvc_ctrl_prune_entity(dev, entity);
+
+		/* Count supported controls and allocate the controls array */
+		ncontrols = memweight(bmControls, bControlSize);
+		if (ncontrols == 0)
+			continue;
+
+		entity->controls = kcalloc(ncontrols, sizeof(*ctrl),
+					   GFP_KERNEL);
+		if (entity->controls == NULL)
+			return -ENOMEM;
+		entity->ncontrols = ncontrols;
+
+		/* Initialize all supported controls */
+		ctrl = entity->controls;
+		for (i = 0; i < bControlSize * 8; ++i) {
+			if (uvc_test_bit(bmControls, i) == 0)
+				continue;
+
+			ctrl->entity = entity;
+			ctrl->index = i;
+
+			uvc_ctrl_init_ctrl(dev, ctrl);
+			ctrl++;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Cleanup device controls.
+ */
+static void uvc_ctrl_cleanup_mappings(struct uvc_device *dev,
+	struct uvc_control *ctrl)
+{
+	struct uvc_control_mapping *mapping, *nm;
+
+	list_for_each_entry_safe(mapping, nm, &ctrl->info.mappings, list) {
+		list_del(&mapping->list);
+		kfree(mapping->menu_info);
+		kfree(mapping);
+	}
+}
+
+void uvc_ctrl_cleanup_device(struct uvc_device *dev)
+{
+	struct uvc_entity *entity;
+	unsigned int i;
+
+	cancel_work_sync(&dev->async_ctrl.work);
+
+	/* Free controls and control mappings for all entities. */
+	list_for_each_entry(entity, &dev->entities, list) {
+		for (i = 0; i < entity->ncontrols; ++i) {
+			struct uvc_control *ctrl = &entity->controls[i];
+
+			if (!ctrl->initialized)
+				continue;
+
+			uvc_ctrl_cleanup_mappings(dev, ctrl);
+			kfree(ctrl->uvc_data);
+		}
+
+		kfree(entity->controls);
+	}
+}
diff --git a/drivers/media/usb/uvc/uvc_debugfs.c b/drivers/media/usb/uvc/uvc_debugfs.c
new file mode 100644
index 0000000..368f8f8
--- /dev/null
+++ b/drivers/media/usb/uvc/uvc_debugfs.c
@@ -0,0 +1,133 @@
+/*
+ *      uvc_debugfs.c --  USB Video Class driver - Debugging support
+ *
+ *      Copyright (C) 2011
+ *          Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ *
+ *      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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/debugfs.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+
+#include "uvcvideo.h"
+
+/* -----------------------------------------------------------------------------
+ * Statistics
+ */
+
+#define UVC_DEBUGFS_BUF_SIZE	1024
+
+struct uvc_debugfs_buffer {
+	size_t count;
+	char data[UVC_DEBUGFS_BUF_SIZE];
+};
+
+static int uvc_debugfs_stats_open(struct inode *inode, struct file *file)
+{
+	struct uvc_streaming *stream = inode->i_private;
+	struct uvc_debugfs_buffer *buf;
+
+	buf = kmalloc(sizeof(*buf), GFP_KERNEL);
+	if (buf == NULL)
+		return -ENOMEM;
+
+	buf->count = uvc_video_stats_dump(stream, buf->data, sizeof(buf->data));
+
+	file->private_data = buf;
+	return 0;
+}
+
+static ssize_t uvc_debugfs_stats_read(struct file *file, char __user *user_buf,
+				      size_t nbytes, loff_t *ppos)
+{
+	struct uvc_debugfs_buffer *buf = file->private_data;
+
+	return simple_read_from_buffer(user_buf, nbytes, ppos, buf->data,
+				       buf->count);
+}
+
+static int uvc_debugfs_stats_release(struct inode *inode, struct file *file)
+{
+	kfree(file->private_data);
+	file->private_data = NULL;
+
+	return 0;
+}
+
+static const struct file_operations uvc_debugfs_stats_fops = {
+	.owner = THIS_MODULE,
+	.open = uvc_debugfs_stats_open,
+	.llseek = no_llseek,
+	.read = uvc_debugfs_stats_read,
+	.release = uvc_debugfs_stats_release,
+};
+
+/* -----------------------------------------------------------------------------
+ * Global and stream initialization/cleanup
+ */
+
+static struct dentry *uvc_debugfs_root_dir;
+
+void uvc_debugfs_init_stream(struct uvc_streaming *stream)
+{
+	struct usb_device *udev = stream->dev->udev;
+	struct dentry *dent;
+	char dir_name[32];
+
+	if (uvc_debugfs_root_dir == NULL)
+		return;
+
+	sprintf(dir_name, "%u-%u", udev->bus->busnum, udev->devnum);
+
+	dent = debugfs_create_dir(dir_name, uvc_debugfs_root_dir);
+	if (IS_ERR_OR_NULL(dent)) {
+		uvc_printk(KERN_INFO, "Unable to create debugfs %s "
+			   "directory.\n", dir_name);
+		return;
+	}
+
+	stream->debugfs_dir = dent;
+
+	dent = debugfs_create_file("stats", 0444, stream->debugfs_dir,
+				   stream, &uvc_debugfs_stats_fops);
+	if (IS_ERR_OR_NULL(dent)) {
+		uvc_printk(KERN_INFO, "Unable to create debugfs stats file.\n");
+		uvc_debugfs_cleanup_stream(stream);
+		return;
+	}
+}
+
+void uvc_debugfs_cleanup_stream(struct uvc_streaming *stream)
+{
+	if (stream->debugfs_dir == NULL)
+		return;
+
+	debugfs_remove_recursive(stream->debugfs_dir);
+	stream->debugfs_dir = NULL;
+}
+
+void uvc_debugfs_init(void)
+{
+	struct dentry *dir;
+
+	dir = debugfs_create_dir("uvcvideo", usb_debug_root);
+	if (IS_ERR_OR_NULL(dir)) {
+		uvc_printk(KERN_INFO, "Unable to create debugfs directory\n");
+		return;
+	}
+
+	uvc_debugfs_root_dir = dir;
+}
+
+void uvc_debugfs_cleanup(void)
+{
+	if (uvc_debugfs_root_dir != NULL)
+		debugfs_remove_recursive(uvc_debugfs_root_dir);
+}
diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c
new file mode 100644
index 0000000..d46dc43
--- /dev/null
+++ b/drivers/media/usb/uvc/uvc_driver.c
@@ -0,0 +1,2871 @@
+/*
+ *      uvc_driver.c  --  USB Video Class driver
+ *
+ *      Copyright (C) 2005-2010
+ *          Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ *
+ *      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.
+ *
+ */
+
+#include <linux/atomic.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/videodev2.h>
+#include <linux/vmalloc.h>
+#include <linux/wait.h>
+#include <linux/version.h>
+#include <asm/unaligned.h>
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+
+#include "uvcvideo.h"
+
+#define DRIVER_AUTHOR		"Laurent Pinchart " \
+				"<laurent.pinchart@ideasonboard.com>"
+#define DRIVER_DESC		"USB Video Class driver"
+
+unsigned int uvc_clock_param = CLOCK_MONOTONIC;
+unsigned int uvc_hw_timestamps_param;
+unsigned int uvc_no_drop_param;
+static unsigned int uvc_quirks_param = -1;
+unsigned int uvc_trace_param;
+unsigned int uvc_timeout_param = UVC_CTRL_STREAMING_TIMEOUT;
+
+/* ------------------------------------------------------------------------
+ * Video formats
+ */
+
+static struct uvc_format_desc uvc_fmts[] = {
+	{
+		.name		= "YUV 4:2:2 (YUYV)",
+		.guid		= UVC_GUID_FORMAT_YUY2,
+		.fcc		= V4L2_PIX_FMT_YUYV,
+	},
+	{
+		.name		= "YUV 4:2:2 (YUYV)",
+		.guid		= UVC_GUID_FORMAT_YUY2_ISIGHT,
+		.fcc		= V4L2_PIX_FMT_YUYV,
+	},
+	{
+		.name		= "YUV 4:2:0 (NV12)",
+		.guid		= UVC_GUID_FORMAT_NV12,
+		.fcc		= V4L2_PIX_FMT_NV12,
+	},
+	{
+		.name		= "MJPEG",
+		.guid		= UVC_GUID_FORMAT_MJPEG,
+		.fcc		= V4L2_PIX_FMT_MJPEG,
+	},
+	{
+		.name		= "YVU 4:2:0 (YV12)",
+		.guid		= UVC_GUID_FORMAT_YV12,
+		.fcc		= V4L2_PIX_FMT_YVU420,
+	},
+	{
+		.name		= "YUV 4:2:0 (I420)",
+		.guid		= UVC_GUID_FORMAT_I420,
+		.fcc		= V4L2_PIX_FMT_YUV420,
+	},
+	{
+		.name		= "YUV 4:2:0 (M420)",
+		.guid		= UVC_GUID_FORMAT_M420,
+		.fcc		= V4L2_PIX_FMT_M420,
+	},
+	{
+		.name		= "YUV 4:2:2 (UYVY)",
+		.guid		= UVC_GUID_FORMAT_UYVY,
+		.fcc		= V4L2_PIX_FMT_UYVY,
+	},
+	{
+		.name		= "Greyscale 8-bit (Y800)",
+		.guid		= UVC_GUID_FORMAT_Y800,
+		.fcc		= V4L2_PIX_FMT_GREY,
+	},
+	{
+		.name		= "Greyscale 8-bit (Y8  )",
+		.guid		= UVC_GUID_FORMAT_Y8,
+		.fcc		= V4L2_PIX_FMT_GREY,
+	},
+	{
+		.name		= "Greyscale 8-bit (D3DFMT_L8)",
+		.guid		= UVC_GUID_FORMAT_D3DFMT_L8,
+		.fcc		= V4L2_PIX_FMT_GREY,
+	},
+	{
+		.name		= "IR 8-bit (L8_IR)",
+		.guid		= UVC_GUID_FORMAT_KSMEDIA_L8_IR,
+		.fcc		= V4L2_PIX_FMT_GREY,
+	},
+	{
+		.name		= "Greyscale 10-bit (Y10 )",
+		.guid		= UVC_GUID_FORMAT_Y10,
+		.fcc		= V4L2_PIX_FMT_Y10,
+	},
+	{
+		.name		= "Greyscale 12-bit (Y12 )",
+		.guid		= UVC_GUID_FORMAT_Y12,
+		.fcc		= V4L2_PIX_FMT_Y12,
+	},
+	{
+		.name		= "Greyscale 16-bit (Y16 )",
+		.guid		= UVC_GUID_FORMAT_Y16,
+		.fcc		= V4L2_PIX_FMT_Y16,
+	},
+	{
+		.name		= "BGGR Bayer (BY8 )",
+		.guid		= UVC_GUID_FORMAT_BY8,
+		.fcc		= V4L2_PIX_FMT_SBGGR8,
+	},
+	{
+		.name		= "BGGR Bayer (BA81)",
+		.guid		= UVC_GUID_FORMAT_BA81,
+		.fcc		= V4L2_PIX_FMT_SBGGR8,
+	},
+	{
+		.name		= "GBRG Bayer (GBRG)",
+		.guid		= UVC_GUID_FORMAT_GBRG,
+		.fcc		= V4L2_PIX_FMT_SGBRG8,
+	},
+	{
+		.name		= "GRBG Bayer (GRBG)",
+		.guid		= UVC_GUID_FORMAT_GRBG,
+		.fcc		= V4L2_PIX_FMT_SGRBG8,
+	},
+	{
+		.name		= "RGGB Bayer (RGGB)",
+		.guid		= UVC_GUID_FORMAT_RGGB,
+		.fcc		= V4L2_PIX_FMT_SRGGB8,
+	},
+	{
+		.name		= "RGB565",
+		.guid		= UVC_GUID_FORMAT_RGBP,
+		.fcc		= V4L2_PIX_FMT_RGB565,
+	},
+	{
+		.name		= "BGR 8:8:8 (BGR3)",
+		.guid		= UVC_GUID_FORMAT_BGR3,
+		.fcc		= V4L2_PIX_FMT_BGR24,
+	},
+	{
+		.name		= "H.264",
+		.guid		= UVC_GUID_FORMAT_H264,
+		.fcc		= V4L2_PIX_FMT_H264,
+	},
+	{
+		.name		= "Greyscale 8 L/R (Y8I)",
+		.guid		= UVC_GUID_FORMAT_Y8I,
+		.fcc		= V4L2_PIX_FMT_Y8I,
+	},
+	{
+		.name		= "Greyscale 12 L/R (Y12I)",
+		.guid		= UVC_GUID_FORMAT_Y12I,
+		.fcc		= V4L2_PIX_FMT_Y12I,
+	},
+	{
+		.name		= "Depth data 16-bit (Z16)",
+		.guid		= UVC_GUID_FORMAT_Z16,
+		.fcc		= V4L2_PIX_FMT_Z16,
+	},
+	{
+		.name		= "Bayer 10-bit (SRGGB10P)",
+		.guid		= UVC_GUID_FORMAT_RW10,
+		.fcc		= V4L2_PIX_FMT_SRGGB10P,
+	},
+	{
+		.name		= "Bayer 16-bit (SBGGR16)",
+		.guid		= UVC_GUID_FORMAT_BG16,
+		.fcc		= V4L2_PIX_FMT_SBGGR16,
+	},
+	{
+		.name		= "Bayer 16-bit (SGBRG16)",
+		.guid		= UVC_GUID_FORMAT_GB16,
+		.fcc		= V4L2_PIX_FMT_SGBRG16,
+	},
+	{
+		.name		= "Bayer 16-bit (SRGGB16)",
+		.guid		= UVC_GUID_FORMAT_RG16,
+		.fcc		= V4L2_PIX_FMT_SRGGB16,
+	},
+	{
+		.name		= "Bayer 16-bit (SGRBG16)",
+		.guid		= UVC_GUID_FORMAT_GR16,
+		.fcc		= V4L2_PIX_FMT_SGRBG16,
+	},
+	{
+		.name		= "Depth data 16-bit (Z16)",
+		.guid		= UVC_GUID_FORMAT_INVZ,
+		.fcc		= V4L2_PIX_FMT_Z16,
+	},
+	{
+		.name		= "Greyscale 10-bit (Y10 )",
+		.guid		= UVC_GUID_FORMAT_INVI,
+		.fcc		= V4L2_PIX_FMT_Y10,
+	},
+	{
+		.name		= "IR:Depth 26-bit (INZI)",
+		.guid		= UVC_GUID_FORMAT_INZI,
+		.fcc		= V4L2_PIX_FMT_INZI,
+	},
+};
+
+/* ------------------------------------------------------------------------
+ * Utility functions
+ */
+
+struct usb_host_endpoint *uvc_find_endpoint(struct usb_host_interface *alts,
+		u8 epaddr)
+{
+	struct usb_host_endpoint *ep;
+	unsigned int i;
+
+	for (i = 0; i < alts->desc.bNumEndpoints; ++i) {
+		ep = &alts->endpoint[i];
+		if (ep->desc.bEndpointAddress == epaddr)
+			return ep;
+	}
+
+	return NULL;
+}
+
+static struct uvc_format_desc *uvc_format_by_guid(const u8 guid[16])
+{
+	unsigned int len = ARRAY_SIZE(uvc_fmts);
+	unsigned int i;
+
+	for (i = 0; i < len; ++i) {
+		if (memcmp(guid, uvc_fmts[i].guid, 16) == 0)
+			return &uvc_fmts[i];
+	}
+
+	return NULL;
+}
+
+static u32 uvc_colorspace(const u8 primaries)
+{
+	static const u8 colorprimaries[] = {
+		0,
+		V4L2_COLORSPACE_SRGB,
+		V4L2_COLORSPACE_470_SYSTEM_M,
+		V4L2_COLORSPACE_470_SYSTEM_BG,
+		V4L2_COLORSPACE_SMPTE170M,
+		V4L2_COLORSPACE_SMPTE240M,
+	};
+
+	if (primaries < ARRAY_SIZE(colorprimaries))
+		return colorprimaries[primaries];
+
+	return 0;
+}
+
+/* Simplify a fraction using a simple continued fraction decomposition. The
+ * idea here is to convert fractions such as 333333/10000000 to 1/30 using
+ * 32 bit arithmetic only. The algorithm is not perfect and relies upon two
+ * arbitrary parameters to remove non-significative terms from the simple
+ * continued fraction decomposition. Using 8 and 333 for n_terms and threshold
+ * respectively seems to give nice results.
+ */
+void uvc_simplify_fraction(u32 *numerator, u32 *denominator,
+		unsigned int n_terms, unsigned int threshold)
+{
+	u32 *an;
+	u32 x, y, r;
+	unsigned int i, n;
+
+	an = kmalloc_array(n_terms, sizeof(*an), GFP_KERNEL);
+	if (an == NULL)
+		return;
+
+	/* Convert the fraction to a simple continued fraction. See
+	 * http://mathforum.org/dr.math/faq/faq.fractions.html
+	 * Stop if the current term is bigger than or equal to the given
+	 * threshold.
+	 */
+	x = *numerator;
+	y = *denominator;
+
+	for (n = 0; n < n_terms && y != 0; ++n) {
+		an[n] = x / y;
+		if (an[n] >= threshold) {
+			if (n < 2)
+				n++;
+			break;
+		}
+
+		r = x - an[n] * y;
+		x = y;
+		y = r;
+	}
+
+	/* Expand the simple continued fraction back to an integer fraction. */
+	x = 0;
+	y = 1;
+
+	for (i = n; i > 0; --i) {
+		r = y;
+		y = an[i-1] * y + x;
+		x = r;
+	}
+
+	*numerator = y;
+	*denominator = x;
+	kfree(an);
+}
+
+/* Convert a fraction to a frame interval in 100ns multiples. The idea here is
+ * to compute numerator / denominator * 10000000 using 32 bit fixed point
+ * arithmetic only.
+ */
+u32 uvc_fraction_to_interval(u32 numerator, u32 denominator)
+{
+	u32 multiplier;
+
+	/* Saturate the result if the operation would overflow. */
+	if (denominator == 0 ||
+	    numerator/denominator >= ((u32)-1)/10000000)
+		return (u32)-1;
+
+	/* Divide both the denominator and the multiplier by two until
+	 * numerator * multiplier doesn't overflow. If anyone knows a better
+	 * algorithm please let me know.
+	 */
+	multiplier = 10000000;
+	while (numerator > ((u32)-1)/multiplier) {
+		multiplier /= 2;
+		denominator /= 2;
+	}
+
+	return denominator ? numerator * multiplier / denominator : 0;
+}
+
+/* ------------------------------------------------------------------------
+ * Terminal and unit management
+ */
+
+struct uvc_entity *uvc_entity_by_id(struct uvc_device *dev, int id)
+{
+	struct uvc_entity *entity;
+
+	list_for_each_entry(entity, &dev->entities, list) {
+		if (entity->id == id)
+			return entity;
+	}
+
+	return NULL;
+}
+
+static struct uvc_entity *uvc_entity_by_reference(struct uvc_device *dev,
+	int id, struct uvc_entity *entity)
+{
+	unsigned int i;
+
+	if (entity == NULL)
+		entity = list_entry(&dev->entities, struct uvc_entity, list);
+
+	list_for_each_entry_continue(entity, &dev->entities, list) {
+		for (i = 0; i < entity->bNrInPins; ++i)
+			if (entity->baSourceID[i] == id)
+				return entity;
+	}
+
+	return NULL;
+}
+
+static struct uvc_streaming *uvc_stream_by_id(struct uvc_device *dev, int id)
+{
+	struct uvc_streaming *stream;
+
+	list_for_each_entry(stream, &dev->streams, list) {
+		if (stream->header.bTerminalLink == id)
+			return stream;
+	}
+
+	return NULL;
+}
+
+/* ------------------------------------------------------------------------
+ * Descriptors parsing
+ */
+
+static int uvc_parse_format(struct uvc_device *dev,
+	struct uvc_streaming *streaming, struct uvc_format *format,
+	u32 **intervals, unsigned char *buffer, int buflen)
+{
+	struct usb_interface *intf = streaming->intf;
+	struct usb_host_interface *alts = intf->cur_altsetting;
+	struct uvc_format_desc *fmtdesc;
+	struct uvc_frame *frame;
+	const unsigned char *start = buffer;
+	unsigned int width_multiplier = 1;
+	unsigned int interval;
+	unsigned int i, n;
+	u8 ftype;
+
+	format->type = buffer[2];
+	format->index = buffer[3];
+
+	switch (buffer[2]) {
+	case UVC_VS_FORMAT_UNCOMPRESSED:
+	case UVC_VS_FORMAT_FRAME_BASED:
+		n = buffer[2] == UVC_VS_FORMAT_UNCOMPRESSED ? 27 : 28;
+		if (buflen < n) {
+			uvc_trace(UVC_TRACE_DESCR, "device %d videostreaming "
+			       "interface %d FORMAT error\n",
+			       dev->udev->devnum,
+			       alts->desc.bInterfaceNumber);
+			return -EINVAL;
+		}
+
+		/* Find the format descriptor from its GUID. */
+		fmtdesc = uvc_format_by_guid(&buffer[5]);
+
+		if (fmtdesc != NULL) {
+			strlcpy(format->name, fmtdesc->name,
+				sizeof(format->name));
+			format->fcc = fmtdesc->fcc;
+		} else {
+			uvc_printk(KERN_INFO, "Unknown video format %pUl\n",
+				&buffer[5]);
+			snprintf(format->name, sizeof(format->name), "%pUl\n",
+				&buffer[5]);
+			format->fcc = 0;
+		}
+
+		format->bpp = buffer[21];
+
+		/* Some devices report a format that doesn't match what they
+		 * really send.
+		 */
+		if (dev->quirks & UVC_QUIRK_FORCE_Y8) {
+			if (format->fcc == V4L2_PIX_FMT_YUYV) {
+				strlcpy(format->name, "Greyscale 8-bit (Y8  )",
+					sizeof(format->name));
+				format->fcc = V4L2_PIX_FMT_GREY;
+				format->bpp = 8;
+				width_multiplier = 2;
+			}
+		}
+
+		if (buffer[2] == UVC_VS_FORMAT_UNCOMPRESSED) {
+			ftype = UVC_VS_FRAME_UNCOMPRESSED;
+		} else {
+			ftype = UVC_VS_FRAME_FRAME_BASED;
+			if (buffer[27])
+				format->flags = UVC_FMT_FLAG_COMPRESSED;
+		}
+		break;
+
+	case UVC_VS_FORMAT_MJPEG:
+		if (buflen < 11) {
+			uvc_trace(UVC_TRACE_DESCR, "device %d videostreaming "
+			       "interface %d FORMAT error\n",
+			       dev->udev->devnum,
+			       alts->desc.bInterfaceNumber);
+			return -EINVAL;
+		}
+
+		strlcpy(format->name, "MJPEG", sizeof(format->name));
+		format->fcc = V4L2_PIX_FMT_MJPEG;
+		format->flags = UVC_FMT_FLAG_COMPRESSED;
+		format->bpp = 0;
+		ftype = UVC_VS_FRAME_MJPEG;
+		break;
+
+	case UVC_VS_FORMAT_DV:
+		if (buflen < 9) {
+			uvc_trace(UVC_TRACE_DESCR, "device %d videostreaming "
+			       "interface %d FORMAT error\n",
+			       dev->udev->devnum,
+			       alts->desc.bInterfaceNumber);
+			return -EINVAL;
+		}
+
+		switch (buffer[8] & 0x7f) {
+		case 0:
+			strlcpy(format->name, "SD-DV", sizeof(format->name));
+			break;
+		case 1:
+			strlcpy(format->name, "SDL-DV", sizeof(format->name));
+			break;
+		case 2:
+			strlcpy(format->name, "HD-DV", sizeof(format->name));
+			break;
+		default:
+			uvc_trace(UVC_TRACE_DESCR, "device %d videostreaming "
+			       "interface %d: unknown DV format %u\n",
+			       dev->udev->devnum,
+			       alts->desc.bInterfaceNumber, buffer[8]);
+			return -EINVAL;
+		}
+
+		strlcat(format->name, buffer[8] & (1 << 7) ? " 60Hz" : " 50Hz",
+			sizeof(format->name));
+
+		format->fcc = V4L2_PIX_FMT_DV;
+		format->flags = UVC_FMT_FLAG_COMPRESSED | UVC_FMT_FLAG_STREAM;
+		format->bpp = 0;
+		ftype = 0;
+
+		/* Create a dummy frame descriptor. */
+		frame = &format->frame[0];
+		memset(&format->frame[0], 0, sizeof(format->frame[0]));
+		frame->bFrameIntervalType = 1;
+		frame->dwDefaultFrameInterval = 1;
+		frame->dwFrameInterval = *intervals;
+		*(*intervals)++ = 1;
+		format->nframes = 1;
+		break;
+
+	case UVC_VS_FORMAT_MPEG2TS:
+	case UVC_VS_FORMAT_STREAM_BASED:
+		/* Not supported yet. */
+	default:
+		uvc_trace(UVC_TRACE_DESCR, "device %d videostreaming "
+		       "interface %d unsupported format %u\n",
+		       dev->udev->devnum, alts->desc.bInterfaceNumber,
+		       buffer[2]);
+		return -EINVAL;
+	}
+
+	uvc_trace(UVC_TRACE_DESCR, "Found format %s.\n", format->name);
+
+	buflen -= buffer[0];
+	buffer += buffer[0];
+
+	/* Parse the frame descriptors. Only uncompressed, MJPEG and frame
+	 * based formats have frame descriptors.
+	 */
+	while (buflen > 2 && buffer[1] == USB_DT_CS_INTERFACE &&
+	       buffer[2] == ftype) {
+		frame = &format->frame[format->nframes];
+		if (ftype != UVC_VS_FRAME_FRAME_BASED)
+			n = buflen > 25 ? buffer[25] : 0;
+		else
+			n = buflen > 21 ? buffer[21] : 0;
+
+		n = n ? n : 3;
+
+		if (buflen < 26 + 4*n) {
+			uvc_trace(UVC_TRACE_DESCR, "device %d videostreaming "
+			       "interface %d FRAME error\n", dev->udev->devnum,
+			       alts->desc.bInterfaceNumber);
+			return -EINVAL;
+		}
+
+		frame->bFrameIndex = buffer[3];
+		frame->bmCapabilities = buffer[4];
+		frame->wWidth = get_unaligned_le16(&buffer[5])
+			      * width_multiplier;
+		frame->wHeight = get_unaligned_le16(&buffer[7]);
+		frame->dwMinBitRate = get_unaligned_le32(&buffer[9]);
+		frame->dwMaxBitRate = get_unaligned_le32(&buffer[13]);
+		if (ftype != UVC_VS_FRAME_FRAME_BASED) {
+			frame->dwMaxVideoFrameBufferSize =
+				get_unaligned_le32(&buffer[17]);
+			frame->dwDefaultFrameInterval =
+				get_unaligned_le32(&buffer[21]);
+			frame->bFrameIntervalType = buffer[25];
+		} else {
+			frame->dwMaxVideoFrameBufferSize = 0;
+			frame->dwDefaultFrameInterval =
+				get_unaligned_le32(&buffer[17]);
+			frame->bFrameIntervalType = buffer[21];
+		}
+		frame->dwFrameInterval = *intervals;
+
+		/* Several UVC chipsets screw up dwMaxVideoFrameBufferSize
+		 * completely. Observed behaviours range from setting the
+		 * value to 1.1x the actual frame size to hardwiring the
+		 * 16 low bits to 0. This results in a higher than necessary
+		 * memory usage as well as a wrong image size information. For
+		 * uncompressed formats this can be fixed by computing the
+		 * value from the frame size.
+		 */
+		if (!(format->flags & UVC_FMT_FLAG_COMPRESSED))
+			frame->dwMaxVideoFrameBufferSize = format->bpp
+				* frame->wWidth * frame->wHeight / 8;
+
+		/* Some bogus devices report dwMinFrameInterval equal to
+		 * dwMaxFrameInterval and have dwFrameIntervalStep set to
+		 * zero. Setting all null intervals to 1 fixes the problem and
+		 * some other divisions by zero that could happen.
+		 */
+		for (i = 0; i < n; ++i) {
+			interval = get_unaligned_le32(&buffer[26+4*i]);
+			*(*intervals)++ = interval ? interval : 1;
+		}
+
+		/* Make sure that the default frame interval stays between
+		 * the boundaries.
+		 */
+		n -= frame->bFrameIntervalType ? 1 : 2;
+		frame->dwDefaultFrameInterval =
+			min(frame->dwFrameInterval[n],
+			    max(frame->dwFrameInterval[0],
+				frame->dwDefaultFrameInterval));
+
+		if (dev->quirks & UVC_QUIRK_RESTRICT_FRAME_RATE) {
+			frame->bFrameIntervalType = 1;
+			frame->dwFrameInterval[0] =
+				frame->dwDefaultFrameInterval;
+		}
+
+		uvc_trace(UVC_TRACE_DESCR, "- %ux%u (%u.%u fps)\n",
+			frame->wWidth, frame->wHeight,
+			10000000/frame->dwDefaultFrameInterval,
+			(100000000/frame->dwDefaultFrameInterval)%10);
+
+		format->nframes++;
+		buflen -= buffer[0];
+		buffer += buffer[0];
+	}
+
+	if (buflen > 2 && buffer[1] == USB_DT_CS_INTERFACE &&
+	    buffer[2] == UVC_VS_STILL_IMAGE_FRAME) {
+		buflen -= buffer[0];
+		buffer += buffer[0];
+	}
+
+	if (buflen > 2 && buffer[1] == USB_DT_CS_INTERFACE &&
+	    buffer[2] == UVC_VS_COLORFORMAT) {
+		if (buflen < 6) {
+			uvc_trace(UVC_TRACE_DESCR, "device %d videostreaming "
+			       "interface %d COLORFORMAT error\n",
+			       dev->udev->devnum,
+			       alts->desc.bInterfaceNumber);
+			return -EINVAL;
+		}
+
+		format->colorspace = uvc_colorspace(buffer[3]);
+
+		buflen -= buffer[0];
+		buffer += buffer[0];
+	}
+
+	return buffer - start;
+}
+
+static int uvc_parse_streaming(struct uvc_device *dev,
+	struct usb_interface *intf)
+{
+	struct uvc_streaming *streaming = NULL;
+	struct uvc_format *format;
+	struct uvc_frame *frame;
+	struct usb_host_interface *alts = &intf->altsetting[0];
+	unsigned char *_buffer, *buffer = alts->extra;
+	int _buflen, buflen = alts->extralen;
+	unsigned int nformats = 0, nframes = 0, nintervals = 0;
+	unsigned int size, i, n, p;
+	u32 *interval;
+	u16 psize;
+	int ret = -EINVAL;
+
+	if (intf->cur_altsetting->desc.bInterfaceSubClass
+		!= UVC_SC_VIDEOSTREAMING) {
+		uvc_trace(UVC_TRACE_DESCR, "device %d interface %d isn't a "
+			"video streaming interface\n", dev->udev->devnum,
+			intf->altsetting[0].desc.bInterfaceNumber);
+		return -EINVAL;
+	}
+
+	if (usb_driver_claim_interface(&uvc_driver.driver, intf, dev)) {
+		uvc_trace(UVC_TRACE_DESCR, "device %d interface %d is already "
+			"claimed\n", dev->udev->devnum,
+			intf->altsetting[0].desc.bInterfaceNumber);
+		return -EINVAL;
+	}
+
+	streaming = kzalloc(sizeof(*streaming), GFP_KERNEL);
+	if (streaming == NULL) {
+		usb_driver_release_interface(&uvc_driver.driver, intf);
+		return -EINVAL;
+	}
+
+	mutex_init(&streaming->mutex);
+	streaming->dev = dev;
+	streaming->intf = usb_get_intf(intf);
+	streaming->intfnum = intf->cur_altsetting->desc.bInterfaceNumber;
+
+	/* The Pico iMage webcam has its class-specific interface descriptors
+	 * after the endpoint descriptors.
+	 */
+	if (buflen == 0) {
+		for (i = 0; i < alts->desc.bNumEndpoints; ++i) {
+			struct usb_host_endpoint *ep = &alts->endpoint[i];
+
+			if (ep->extralen == 0)
+				continue;
+
+			if (ep->extralen > 2 &&
+			    ep->extra[1] == USB_DT_CS_INTERFACE) {
+				uvc_trace(UVC_TRACE_DESCR, "trying extra data "
+					"from endpoint %u.\n", i);
+				buffer = alts->endpoint[i].extra;
+				buflen = alts->endpoint[i].extralen;
+				break;
+			}
+		}
+	}
+
+	/* Skip the standard interface descriptors. */
+	while (buflen > 2 && buffer[1] != USB_DT_CS_INTERFACE) {
+		buflen -= buffer[0];
+		buffer += buffer[0];
+	}
+
+	if (buflen <= 2) {
+		uvc_trace(UVC_TRACE_DESCR, "no class-specific streaming "
+			"interface descriptors found.\n");
+		goto error;
+	}
+
+	/* Parse the header descriptor. */
+	switch (buffer[2]) {
+	case UVC_VS_OUTPUT_HEADER:
+		streaming->type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+		size = 9;
+		break;
+
+	case UVC_VS_INPUT_HEADER:
+		streaming->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+		size = 13;
+		break;
+
+	default:
+		uvc_trace(UVC_TRACE_DESCR, "device %d videostreaming interface "
+			"%d HEADER descriptor not found.\n", dev->udev->devnum,
+			alts->desc.bInterfaceNumber);
+		goto error;
+	}
+
+	p = buflen >= 4 ? buffer[3] : 0;
+	n = buflen >= size ? buffer[size-1] : 0;
+
+	if (buflen < size + p*n) {
+		uvc_trace(UVC_TRACE_DESCR, "device %d videostreaming "
+			"interface %d HEADER descriptor is invalid.\n",
+			dev->udev->devnum, alts->desc.bInterfaceNumber);
+		goto error;
+	}
+
+	streaming->header.bNumFormats = p;
+	streaming->header.bEndpointAddress = buffer[6];
+	if (buffer[2] == UVC_VS_INPUT_HEADER) {
+		streaming->header.bmInfo = buffer[7];
+		streaming->header.bTerminalLink = buffer[8];
+		streaming->header.bStillCaptureMethod = buffer[9];
+		streaming->header.bTriggerSupport = buffer[10];
+		streaming->header.bTriggerUsage = buffer[11];
+	} else {
+		streaming->header.bTerminalLink = buffer[7];
+	}
+	streaming->header.bControlSize = n;
+
+	streaming->header.bmaControls = kmemdup(&buffer[size], p * n,
+						GFP_KERNEL);
+	if (streaming->header.bmaControls == NULL) {
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	buflen -= buffer[0];
+	buffer += buffer[0];
+
+	_buffer = buffer;
+	_buflen = buflen;
+
+	/* Count the format and frame descriptors. */
+	while (_buflen > 2 && _buffer[1] == USB_DT_CS_INTERFACE) {
+		switch (_buffer[2]) {
+		case UVC_VS_FORMAT_UNCOMPRESSED:
+		case UVC_VS_FORMAT_MJPEG:
+		case UVC_VS_FORMAT_FRAME_BASED:
+			nformats++;
+			break;
+
+		case UVC_VS_FORMAT_DV:
+			/* DV format has no frame descriptor. We will create a
+			 * dummy frame descriptor with a dummy frame interval.
+			 */
+			nformats++;
+			nframes++;
+			nintervals++;
+			break;
+
+		case UVC_VS_FORMAT_MPEG2TS:
+		case UVC_VS_FORMAT_STREAM_BASED:
+			uvc_trace(UVC_TRACE_DESCR, "device %d videostreaming "
+				"interface %d FORMAT %u is not supported.\n",
+				dev->udev->devnum,
+				alts->desc.bInterfaceNumber, _buffer[2]);
+			break;
+
+		case UVC_VS_FRAME_UNCOMPRESSED:
+		case UVC_VS_FRAME_MJPEG:
+			nframes++;
+			if (_buflen > 25)
+				nintervals += _buffer[25] ? _buffer[25] : 3;
+			break;
+
+		case UVC_VS_FRAME_FRAME_BASED:
+			nframes++;
+			if (_buflen > 21)
+				nintervals += _buffer[21] ? _buffer[21] : 3;
+			break;
+		}
+
+		_buflen -= _buffer[0];
+		_buffer += _buffer[0];
+	}
+
+	if (nformats == 0) {
+		uvc_trace(UVC_TRACE_DESCR, "device %d videostreaming interface "
+			"%d has no supported formats defined.\n",
+			dev->udev->devnum, alts->desc.bInterfaceNumber);
+		goto error;
+	}
+
+	size = nformats * sizeof(*format) + nframes * sizeof(*frame)
+	     + nintervals * sizeof(*interval);
+	format = kzalloc(size, GFP_KERNEL);
+	if (format == NULL) {
+		ret = -ENOMEM;
+		goto error;
+	}
+
+	frame = (struct uvc_frame *)&format[nformats];
+	interval = (u32 *)&frame[nframes];
+
+	streaming->format = format;
+	streaming->nformats = nformats;
+
+	/* Parse the format descriptors. */
+	while (buflen > 2 && buffer[1] == USB_DT_CS_INTERFACE) {
+		switch (buffer[2]) {
+		case UVC_VS_FORMAT_UNCOMPRESSED:
+		case UVC_VS_FORMAT_MJPEG:
+		case UVC_VS_FORMAT_DV:
+		case UVC_VS_FORMAT_FRAME_BASED:
+			format->frame = frame;
+			ret = uvc_parse_format(dev, streaming, format,
+				&interval, buffer, buflen);
+			if (ret < 0)
+				goto error;
+
+			frame += format->nframes;
+			format++;
+
+			buflen -= ret;
+			buffer += ret;
+			continue;
+
+		default:
+			break;
+		}
+
+		buflen -= buffer[0];
+		buffer += buffer[0];
+	}
+
+	if (buflen)
+		uvc_trace(UVC_TRACE_DESCR, "device %d videostreaming interface "
+			"%d has %u bytes of trailing descriptor garbage.\n",
+			dev->udev->devnum, alts->desc.bInterfaceNumber, buflen);
+
+	/* Parse the alternate settings to find the maximum bandwidth. */
+	for (i = 0; i < intf->num_altsetting; ++i) {
+		struct usb_host_endpoint *ep;
+		alts = &intf->altsetting[i];
+		ep = uvc_find_endpoint(alts,
+				streaming->header.bEndpointAddress);
+		if (ep == NULL)
+			continue;
+
+		psize = le16_to_cpu(ep->desc.wMaxPacketSize);
+		psize = (psize & 0x07ff) * (1 + ((psize >> 11) & 3));
+		if (psize > streaming->maxpsize)
+			streaming->maxpsize = psize;
+	}
+
+	list_add_tail(&streaming->list, &dev->streams);
+	return 0;
+
+error:
+	usb_driver_release_interface(&uvc_driver.driver, intf);
+	usb_put_intf(intf);
+	kfree(streaming->format);
+	kfree(streaming->header.bmaControls);
+	kfree(streaming);
+	return ret;
+}
+
+static struct uvc_entity *uvc_alloc_entity(u16 type, u8 id,
+		unsigned int num_pads, unsigned int extra_size)
+{
+	struct uvc_entity *entity;
+	unsigned int num_inputs;
+	unsigned int size;
+	unsigned int i;
+
+	extra_size = ALIGN(extra_size, sizeof(*entity->pads));
+	num_inputs = (type & UVC_TERM_OUTPUT) ? num_pads : num_pads - 1;
+	size = sizeof(*entity) + extra_size + sizeof(*entity->pads) * num_pads
+	     + num_inputs;
+	entity = kzalloc(size, GFP_KERNEL);
+	if (entity == NULL)
+		return NULL;
+
+	entity->id = id;
+	entity->type = type;
+
+	entity->num_links = 0;
+	entity->num_pads = num_pads;
+	entity->pads = ((void *)(entity + 1)) + extra_size;
+
+	for (i = 0; i < num_inputs; ++i)
+		entity->pads[i].flags = MEDIA_PAD_FL_SINK;
+	if (!UVC_ENTITY_IS_OTERM(entity))
+		entity->pads[num_pads-1].flags = MEDIA_PAD_FL_SOURCE;
+
+	entity->bNrInPins = num_inputs;
+	entity->baSourceID = (u8 *)(&entity->pads[num_pads]);
+
+	return entity;
+}
+
+/* Parse vendor-specific extensions. */
+static int uvc_parse_vendor_control(struct uvc_device *dev,
+	const unsigned char *buffer, int buflen)
+{
+	struct usb_device *udev = dev->udev;
+	struct usb_host_interface *alts = dev->intf->cur_altsetting;
+	struct uvc_entity *unit;
+	unsigned int n, p;
+	int handled = 0;
+
+	switch (le16_to_cpu(dev->udev->descriptor.idVendor)) {
+	case 0x046d:		/* Logitech */
+		if (buffer[1] != 0x41 || buffer[2] != 0x01)
+			break;
+
+		/* Logitech implements several vendor specific functions
+		 * through vendor specific extension units (LXU).
+		 *
+		 * The LXU descriptors are similar to XU descriptors
+		 * (see "USB Device Video Class for Video Devices", section
+		 * 3.7.2.6 "Extension Unit Descriptor") with the following
+		 * differences:
+		 *
+		 * ----------------------------------------------------------
+		 * 0		bLength		1	 Number
+		 *	Size of this descriptor, in bytes: 24+p+n*2
+		 * ----------------------------------------------------------
+		 * 23+p+n	bmControlsType	N	Bitmap
+		 *	Individual bits in the set are defined:
+		 *	0: Absolute
+		 *	1: Relative
+		 *
+		 *	This bitset is mapped exactly the same as bmControls.
+		 * ----------------------------------------------------------
+		 * 23+p+n*2	bReserved	1	Boolean
+		 * ----------------------------------------------------------
+		 * 24+p+n*2	iExtension	1	Index
+		 *	Index of a string descriptor that describes this
+		 *	extension unit.
+		 * ----------------------------------------------------------
+		 */
+		p = buflen >= 22 ? buffer[21] : 0;
+		n = buflen >= 25 + p ? buffer[22+p] : 0;
+
+		if (buflen < 25 + p + 2*n) {
+			uvc_trace(UVC_TRACE_DESCR, "device %d videocontrol "
+				"interface %d EXTENSION_UNIT error\n",
+				udev->devnum, alts->desc.bInterfaceNumber);
+			break;
+		}
+
+		unit = uvc_alloc_entity(UVC_VC_EXTENSION_UNIT, buffer[3],
+					p + 1, 2*n);
+		if (unit == NULL)
+			return -ENOMEM;
+
+		memcpy(unit->extension.guidExtensionCode, &buffer[4], 16);
+		unit->extension.bNumControls = buffer[20];
+		memcpy(unit->baSourceID, &buffer[22], p);
+		unit->extension.bControlSize = buffer[22+p];
+		unit->extension.bmControls = (u8 *)unit + sizeof(*unit);
+		unit->extension.bmControlsType = (u8 *)unit + sizeof(*unit)
+					       + n;
+		memcpy(unit->extension.bmControls, &buffer[23+p], 2*n);
+
+		if (buffer[24+p+2*n] != 0)
+			usb_string(udev, buffer[24+p+2*n], unit->name,
+				   sizeof(unit->name));
+		else
+			sprintf(unit->name, "Extension %u", buffer[3]);
+
+		list_add_tail(&unit->list, &dev->entities);
+		handled = 1;
+		break;
+	}
+
+	return handled;
+}
+
+static int uvc_parse_standard_control(struct uvc_device *dev,
+	const unsigned char *buffer, int buflen)
+{
+	struct usb_device *udev = dev->udev;
+	struct uvc_entity *unit, *term;
+	struct usb_interface *intf;
+	struct usb_host_interface *alts = dev->intf->cur_altsetting;
+	unsigned int i, n, p, len;
+	u16 type;
+
+	switch (buffer[2]) {
+	case UVC_VC_HEADER:
+		n = buflen >= 12 ? buffer[11] : 0;
+
+		if (buflen < 12 + n) {
+			uvc_trace(UVC_TRACE_DESCR, "device %d videocontrol "
+				"interface %d HEADER error\n", udev->devnum,
+				alts->desc.bInterfaceNumber);
+			return -EINVAL;
+		}
+
+		dev->uvc_version = get_unaligned_le16(&buffer[3]);
+		dev->clock_frequency = get_unaligned_le32(&buffer[7]);
+
+		/* Parse all USB Video Streaming interfaces. */
+		for (i = 0; i < n; ++i) {
+			intf = usb_ifnum_to_if(udev, buffer[12+i]);
+			if (intf == NULL) {
+				uvc_trace(UVC_TRACE_DESCR, "device %d "
+					"interface %d doesn't exists\n",
+					udev->devnum, i);
+				continue;
+			}
+
+			uvc_parse_streaming(dev, intf);
+		}
+		break;
+
+	case UVC_VC_INPUT_TERMINAL:
+		if (buflen < 8) {
+			uvc_trace(UVC_TRACE_DESCR, "device %d videocontrol "
+				"interface %d INPUT_TERMINAL error\n",
+				udev->devnum, alts->desc.bInterfaceNumber);
+			return -EINVAL;
+		}
+
+		/* Make sure the terminal type MSB is not null, otherwise it
+		 * could be confused with a unit.
+		 */
+		type = get_unaligned_le16(&buffer[4]);
+		if ((type & 0xff00) == 0) {
+			uvc_trace(UVC_TRACE_DESCR, "device %d videocontrol "
+				"interface %d INPUT_TERMINAL %d has invalid "
+				"type 0x%04x, skipping\n", udev->devnum,
+				alts->desc.bInterfaceNumber,
+				buffer[3], type);
+			return 0;
+		}
+
+		n = 0;
+		p = 0;
+		len = 8;
+
+		if (type == UVC_ITT_CAMERA) {
+			n = buflen >= 15 ? buffer[14] : 0;
+			len = 15;
+
+		} else if (type == UVC_ITT_MEDIA_TRANSPORT_INPUT) {
+			n = buflen >= 9 ? buffer[8] : 0;
+			p = buflen >= 10 + n ? buffer[9+n] : 0;
+			len = 10;
+		}
+
+		if (buflen < len + n + p) {
+			uvc_trace(UVC_TRACE_DESCR, "device %d videocontrol "
+				"interface %d INPUT_TERMINAL error\n",
+				udev->devnum, alts->desc.bInterfaceNumber);
+			return -EINVAL;
+		}
+
+		term = uvc_alloc_entity(type | UVC_TERM_INPUT, buffer[3],
+					1, n + p);
+		if (term == NULL)
+			return -ENOMEM;
+
+		if (UVC_ENTITY_TYPE(term) == UVC_ITT_CAMERA) {
+			term->camera.bControlSize = n;
+			term->camera.bmControls = (u8 *)term + sizeof(*term);
+			term->camera.wObjectiveFocalLengthMin =
+				get_unaligned_le16(&buffer[8]);
+			term->camera.wObjectiveFocalLengthMax =
+				get_unaligned_le16(&buffer[10]);
+			term->camera.wOcularFocalLength =
+				get_unaligned_le16(&buffer[12]);
+			memcpy(term->camera.bmControls, &buffer[15], n);
+		} else if (UVC_ENTITY_TYPE(term) ==
+			   UVC_ITT_MEDIA_TRANSPORT_INPUT) {
+			term->media.bControlSize = n;
+			term->media.bmControls = (u8 *)term + sizeof(*term);
+			term->media.bTransportModeSize = p;
+			term->media.bmTransportModes = (u8 *)term
+						     + sizeof(*term) + n;
+			memcpy(term->media.bmControls, &buffer[9], n);
+			memcpy(term->media.bmTransportModes, &buffer[10+n], p);
+		}
+
+		if (buffer[7] != 0)
+			usb_string(udev, buffer[7], term->name,
+				   sizeof(term->name));
+		else if (UVC_ENTITY_TYPE(term) == UVC_ITT_CAMERA)
+			sprintf(term->name, "Camera %u", buffer[3]);
+		else if (UVC_ENTITY_TYPE(term) == UVC_ITT_MEDIA_TRANSPORT_INPUT)
+			sprintf(term->name, "Media %u", buffer[3]);
+		else
+			sprintf(term->name, "Input %u", buffer[3]);
+
+		list_add_tail(&term->list, &dev->entities);
+		break;
+
+	case UVC_VC_OUTPUT_TERMINAL:
+		if (buflen < 9) {
+			uvc_trace(UVC_TRACE_DESCR, "device %d videocontrol "
+				"interface %d OUTPUT_TERMINAL error\n",
+				udev->devnum, alts->desc.bInterfaceNumber);
+			return -EINVAL;
+		}
+
+		/* Make sure the terminal type MSB is not null, otherwise it
+		 * could be confused with a unit.
+		 */
+		type = get_unaligned_le16(&buffer[4]);
+		if ((type & 0xff00) == 0) {
+			uvc_trace(UVC_TRACE_DESCR, "device %d videocontrol "
+				"interface %d OUTPUT_TERMINAL %d has invalid "
+				"type 0x%04x, skipping\n", udev->devnum,
+				alts->desc.bInterfaceNumber, buffer[3], type);
+			return 0;
+		}
+
+		term = uvc_alloc_entity(type | UVC_TERM_OUTPUT, buffer[3],
+					1, 0);
+		if (term == NULL)
+			return -ENOMEM;
+
+		memcpy(term->baSourceID, &buffer[7], 1);
+
+		if (buffer[8] != 0)
+			usb_string(udev, buffer[8], term->name,
+				   sizeof(term->name));
+		else
+			sprintf(term->name, "Output %u", buffer[3]);
+
+		list_add_tail(&term->list, &dev->entities);
+		break;
+
+	case UVC_VC_SELECTOR_UNIT:
+		p = buflen >= 5 ? buffer[4] : 0;
+
+		if (buflen < 5 || buflen < 6 + p) {
+			uvc_trace(UVC_TRACE_DESCR, "device %d videocontrol "
+				"interface %d SELECTOR_UNIT error\n",
+				udev->devnum, alts->desc.bInterfaceNumber);
+			return -EINVAL;
+		}
+
+		unit = uvc_alloc_entity(buffer[2], buffer[3], p + 1, 0);
+		if (unit == NULL)
+			return -ENOMEM;
+
+		memcpy(unit->baSourceID, &buffer[5], p);
+
+		if (buffer[5+p] != 0)
+			usb_string(udev, buffer[5+p], unit->name,
+				   sizeof(unit->name));
+		else
+			sprintf(unit->name, "Selector %u", buffer[3]);
+
+		list_add_tail(&unit->list, &dev->entities);
+		break;
+
+	case UVC_VC_PROCESSING_UNIT:
+		n = buflen >= 8 ? buffer[7] : 0;
+		p = dev->uvc_version >= 0x0110 ? 10 : 9;
+
+		if (buflen < p + n) {
+			uvc_trace(UVC_TRACE_DESCR, "device %d videocontrol "
+				"interface %d PROCESSING_UNIT error\n",
+				udev->devnum, alts->desc.bInterfaceNumber);
+			return -EINVAL;
+		}
+
+		unit = uvc_alloc_entity(buffer[2], buffer[3], 2, n);
+		if (unit == NULL)
+			return -ENOMEM;
+
+		memcpy(unit->baSourceID, &buffer[4], 1);
+		unit->processing.wMaxMultiplier =
+			get_unaligned_le16(&buffer[5]);
+		unit->processing.bControlSize = buffer[7];
+		unit->processing.bmControls = (u8 *)unit + sizeof(*unit);
+		memcpy(unit->processing.bmControls, &buffer[8], n);
+		if (dev->uvc_version >= 0x0110)
+			unit->processing.bmVideoStandards = buffer[9+n];
+
+		if (buffer[8+n] != 0)
+			usb_string(udev, buffer[8+n], unit->name,
+				   sizeof(unit->name));
+		else
+			sprintf(unit->name, "Processing %u", buffer[3]);
+
+		list_add_tail(&unit->list, &dev->entities);
+		break;
+
+	case UVC_VC_EXTENSION_UNIT:
+		p = buflen >= 22 ? buffer[21] : 0;
+		n = buflen >= 24 + p ? buffer[22+p] : 0;
+
+		if (buflen < 24 + p + n) {
+			uvc_trace(UVC_TRACE_DESCR, "device %d videocontrol "
+				"interface %d EXTENSION_UNIT error\n",
+				udev->devnum, alts->desc.bInterfaceNumber);
+			return -EINVAL;
+		}
+
+		unit = uvc_alloc_entity(buffer[2], buffer[3], p + 1, n);
+		if (unit == NULL)
+			return -ENOMEM;
+
+		memcpy(unit->extension.guidExtensionCode, &buffer[4], 16);
+		unit->extension.bNumControls = buffer[20];
+		memcpy(unit->baSourceID, &buffer[22], p);
+		unit->extension.bControlSize = buffer[22+p];
+		unit->extension.bmControls = (u8 *)unit + sizeof(*unit);
+		memcpy(unit->extension.bmControls, &buffer[23+p], n);
+
+		if (buffer[23+p+n] != 0)
+			usb_string(udev, buffer[23+p+n], unit->name,
+				   sizeof(unit->name));
+		else
+			sprintf(unit->name, "Extension %u", buffer[3]);
+
+		list_add_tail(&unit->list, &dev->entities);
+		break;
+
+	default:
+		uvc_trace(UVC_TRACE_DESCR, "Found an unknown CS_INTERFACE "
+			"descriptor (%u)\n", buffer[2]);
+		break;
+	}
+
+	return 0;
+}
+
+static int uvc_parse_control(struct uvc_device *dev)
+{
+	struct usb_host_interface *alts = dev->intf->cur_altsetting;
+	unsigned char *buffer = alts->extra;
+	int buflen = alts->extralen;
+	int ret;
+
+	/* Parse the default alternate setting only, as the UVC specification
+	 * defines a single alternate setting, the default alternate setting
+	 * zero.
+	 */
+
+	while (buflen > 2) {
+		if (uvc_parse_vendor_control(dev, buffer, buflen) ||
+		    buffer[1] != USB_DT_CS_INTERFACE)
+			goto next_descriptor;
+
+		if ((ret = uvc_parse_standard_control(dev, buffer, buflen)) < 0)
+			return ret;
+
+next_descriptor:
+		buflen -= buffer[0];
+		buffer += buffer[0];
+	}
+
+	/* Check if the optional status endpoint is present. Built-in iSight
+	 * webcams have an interrupt endpoint but spit proprietary data that
+	 * don't conform to the UVC status endpoint messages. Don't try to
+	 * handle the interrupt endpoint for those cameras.
+	 */
+	if (alts->desc.bNumEndpoints == 1 &&
+	    !(dev->quirks & UVC_QUIRK_BUILTIN_ISIGHT)) {
+		struct usb_host_endpoint *ep = &alts->endpoint[0];
+		struct usb_endpoint_descriptor *desc = &ep->desc;
+
+		if (usb_endpoint_is_int_in(desc) &&
+		    le16_to_cpu(desc->wMaxPacketSize) >= 8 &&
+		    desc->bInterval != 0) {
+			uvc_trace(UVC_TRACE_DESCR, "Found a Status endpoint "
+				"(addr %02x).\n", desc->bEndpointAddress);
+			dev->int_ep = ep;
+		}
+	}
+
+	return 0;
+}
+
+/* ------------------------------------------------------------------------
+ * UVC device scan
+ */
+
+/*
+ * Scan the UVC descriptors to locate a chain starting at an Output Terminal
+ * and containing the following units:
+ *
+ * - one or more Output Terminals (USB Streaming or Display)
+ * - zero or one Processing Unit
+ * - zero, one or more single-input Selector Units
+ * - zero or one multiple-input Selector Units, provided all inputs are
+ *   connected to input terminals
+ * - zero, one or mode single-input Extension Units
+ * - one or more Input Terminals (Camera, External or USB Streaming)
+ *
+ * The terminal and units must match on of the following structures:
+ *
+ * ITT_*(0) -> +---------+    +---------+    +---------+ -> TT_STREAMING(0)
+ * ...         | SU{0,1} | -> | PU{0,1} | -> | XU{0,n} |    ...
+ * ITT_*(n) -> +---------+    +---------+    +---------+ -> TT_STREAMING(n)
+ *
+ *                 +---------+    +---------+ -> OTT_*(0)
+ * TT_STREAMING -> | PU{0,1} | -> | XU{0,n} |    ...
+ *                 +---------+    +---------+ -> OTT_*(n)
+ *
+ * The Processing Unit and Extension Units can be in any order. Additional
+ * Extension Units connected to the main chain as single-unit branches are
+ * also supported. Single-input Selector Units are ignored.
+ */
+static int uvc_scan_chain_entity(struct uvc_video_chain *chain,
+	struct uvc_entity *entity)
+{
+	switch (UVC_ENTITY_TYPE(entity)) {
+	case UVC_VC_EXTENSION_UNIT:
+		if (uvc_trace_param & UVC_TRACE_PROBE)
+			printk(KERN_CONT " <- XU %d", entity->id);
+
+		if (entity->bNrInPins != 1) {
+			uvc_trace(UVC_TRACE_DESCR, "Extension unit %d has more "
+				"than 1 input pin.\n", entity->id);
+			return -1;
+		}
+
+		break;
+
+	case UVC_VC_PROCESSING_UNIT:
+		if (uvc_trace_param & UVC_TRACE_PROBE)
+			printk(KERN_CONT " <- PU %d", entity->id);
+
+		if (chain->processing != NULL) {
+			uvc_trace(UVC_TRACE_DESCR, "Found multiple "
+				"Processing Units in chain.\n");
+			return -1;
+		}
+
+		chain->processing = entity;
+		break;
+
+	case UVC_VC_SELECTOR_UNIT:
+		if (uvc_trace_param & UVC_TRACE_PROBE)
+			printk(KERN_CONT " <- SU %d", entity->id);
+
+		/* Single-input selector units are ignored. */
+		if (entity->bNrInPins == 1)
+			break;
+
+		if (chain->selector != NULL) {
+			uvc_trace(UVC_TRACE_DESCR, "Found multiple Selector "
+				"Units in chain.\n");
+			return -1;
+		}
+
+		chain->selector = entity;
+		break;
+
+	case UVC_ITT_VENDOR_SPECIFIC:
+	case UVC_ITT_CAMERA:
+	case UVC_ITT_MEDIA_TRANSPORT_INPUT:
+		if (uvc_trace_param & UVC_TRACE_PROBE)
+			printk(KERN_CONT " <- IT %d\n", entity->id);
+
+		break;
+
+	case UVC_OTT_VENDOR_SPECIFIC:
+	case UVC_OTT_DISPLAY:
+	case UVC_OTT_MEDIA_TRANSPORT_OUTPUT:
+		if (uvc_trace_param & UVC_TRACE_PROBE)
+			printk(KERN_CONT " OT %d", entity->id);
+
+		break;
+
+	case UVC_TT_STREAMING:
+		if (UVC_ENTITY_IS_ITERM(entity)) {
+			if (uvc_trace_param & UVC_TRACE_PROBE)
+				printk(KERN_CONT " <- IT %d\n", entity->id);
+		} else {
+			if (uvc_trace_param & UVC_TRACE_PROBE)
+				printk(KERN_CONT " OT %d", entity->id);
+		}
+
+		break;
+
+	default:
+		uvc_trace(UVC_TRACE_DESCR, "Unsupported entity type "
+			"0x%04x found in chain.\n", UVC_ENTITY_TYPE(entity));
+		return -1;
+	}
+
+	list_add_tail(&entity->chain, &chain->entities);
+	return 0;
+}
+
+static int uvc_scan_chain_forward(struct uvc_video_chain *chain,
+	struct uvc_entity *entity, struct uvc_entity *prev)
+{
+	struct uvc_entity *forward;
+	int found;
+
+	/* Forward scan */
+	forward = NULL;
+	found = 0;
+
+	while (1) {
+		forward = uvc_entity_by_reference(chain->dev, entity->id,
+			forward);
+		if (forward == NULL)
+			break;
+		if (forward == prev)
+			continue;
+
+		switch (UVC_ENTITY_TYPE(forward)) {
+		case UVC_VC_EXTENSION_UNIT:
+			if (forward->bNrInPins != 1) {
+				uvc_trace(UVC_TRACE_DESCR, "Extension unit %d "
+					  "has more than 1 input pin.\n",
+					  entity->id);
+				return -EINVAL;
+			}
+
+			list_add_tail(&forward->chain, &chain->entities);
+			if (uvc_trace_param & UVC_TRACE_PROBE) {
+				if (!found)
+					printk(KERN_CONT " (->");
+
+				printk(KERN_CONT " XU %d", forward->id);
+				found = 1;
+			}
+			break;
+
+		case UVC_OTT_VENDOR_SPECIFIC:
+		case UVC_OTT_DISPLAY:
+		case UVC_OTT_MEDIA_TRANSPORT_OUTPUT:
+		case UVC_TT_STREAMING:
+			if (UVC_ENTITY_IS_ITERM(forward)) {
+				uvc_trace(UVC_TRACE_DESCR, "Unsupported input "
+					"terminal %u.\n", forward->id);
+				return -EINVAL;
+			}
+
+			list_add_tail(&forward->chain, &chain->entities);
+			if (uvc_trace_param & UVC_TRACE_PROBE) {
+				if (!found)
+					printk(KERN_CONT " (->");
+
+				printk(KERN_CONT " OT %d", forward->id);
+				found = 1;
+			}
+			break;
+		}
+	}
+	if (found)
+		printk(KERN_CONT ")");
+
+	return 0;
+}
+
+static int uvc_scan_chain_backward(struct uvc_video_chain *chain,
+	struct uvc_entity **_entity)
+{
+	struct uvc_entity *entity = *_entity;
+	struct uvc_entity *term;
+	int id = -EINVAL, i;
+
+	switch (UVC_ENTITY_TYPE(entity)) {
+	case UVC_VC_EXTENSION_UNIT:
+	case UVC_VC_PROCESSING_UNIT:
+		id = entity->baSourceID[0];
+		break;
+
+	case UVC_VC_SELECTOR_UNIT:
+		/* Single-input selector units are ignored. */
+		if (entity->bNrInPins == 1) {
+			id = entity->baSourceID[0];
+			break;
+		}
+
+		if (uvc_trace_param & UVC_TRACE_PROBE)
+			printk(KERN_CONT " <- IT");
+
+		chain->selector = entity;
+		for (i = 0; i < entity->bNrInPins; ++i) {
+			id = entity->baSourceID[i];
+			term = uvc_entity_by_id(chain->dev, id);
+			if (term == NULL || !UVC_ENTITY_IS_ITERM(term)) {
+				uvc_trace(UVC_TRACE_DESCR, "Selector unit %d "
+					"input %d isn't connected to an "
+					"input terminal\n", entity->id, i);
+				return -1;
+			}
+
+			if (uvc_trace_param & UVC_TRACE_PROBE)
+				printk(KERN_CONT " %d", term->id);
+
+			list_add_tail(&term->chain, &chain->entities);
+			uvc_scan_chain_forward(chain, term, entity);
+		}
+
+		if (uvc_trace_param & UVC_TRACE_PROBE)
+			printk(KERN_CONT "\n");
+
+		id = 0;
+		break;
+
+	case UVC_ITT_VENDOR_SPECIFIC:
+	case UVC_ITT_CAMERA:
+	case UVC_ITT_MEDIA_TRANSPORT_INPUT:
+	case UVC_OTT_VENDOR_SPECIFIC:
+	case UVC_OTT_DISPLAY:
+	case UVC_OTT_MEDIA_TRANSPORT_OUTPUT:
+	case UVC_TT_STREAMING:
+		id = UVC_ENTITY_IS_OTERM(entity) ? entity->baSourceID[0] : 0;
+		break;
+	}
+
+	if (id <= 0) {
+		*_entity = NULL;
+		return id;
+	}
+
+	entity = uvc_entity_by_id(chain->dev, id);
+	if (entity == NULL) {
+		uvc_trace(UVC_TRACE_DESCR, "Found reference to "
+			"unknown entity %d.\n", id);
+		return -EINVAL;
+	}
+
+	*_entity = entity;
+	return 0;
+}
+
+static int uvc_scan_chain(struct uvc_video_chain *chain,
+			  struct uvc_entity *term)
+{
+	struct uvc_entity *entity, *prev;
+
+	uvc_trace(UVC_TRACE_PROBE, "Scanning UVC chain:");
+
+	entity = term;
+	prev = NULL;
+
+	while (entity != NULL) {
+		/* Entity must not be part of an existing chain */
+		if (entity->chain.next || entity->chain.prev) {
+			uvc_trace(UVC_TRACE_DESCR, "Found reference to "
+				"entity %d already in chain.\n", entity->id);
+			return -EINVAL;
+		}
+
+		/* Process entity */
+		if (uvc_scan_chain_entity(chain, entity) < 0)
+			return -EINVAL;
+
+		/* Forward scan */
+		if (uvc_scan_chain_forward(chain, entity, prev) < 0)
+			return -EINVAL;
+
+		/* Backward scan */
+		prev = entity;
+		if (uvc_scan_chain_backward(chain, &entity) < 0)
+			return -EINVAL;
+	}
+
+	return 0;
+}
+
+static unsigned int uvc_print_terms(struct list_head *terms, u16 dir,
+		char *buffer)
+{
+	struct uvc_entity *term;
+	unsigned int nterms = 0;
+	char *p = buffer;
+
+	list_for_each_entry(term, terms, chain) {
+		if (!UVC_ENTITY_IS_TERM(term) ||
+		    UVC_TERM_DIRECTION(term) != dir)
+			continue;
+
+		if (nterms)
+			p += sprintf(p, ",");
+		if (++nterms >= 4) {
+			p += sprintf(p, "...");
+			break;
+		}
+		p += sprintf(p, "%u", term->id);
+	}
+
+	return p - buffer;
+}
+
+static const char *uvc_print_chain(struct uvc_video_chain *chain)
+{
+	static char buffer[43];
+	char *p = buffer;
+
+	p += uvc_print_terms(&chain->entities, UVC_TERM_INPUT, p);
+	p += sprintf(p, " -> ");
+	uvc_print_terms(&chain->entities, UVC_TERM_OUTPUT, p);
+
+	return buffer;
+}
+
+static struct uvc_video_chain *uvc_alloc_chain(struct uvc_device *dev)
+{
+	struct uvc_video_chain *chain;
+
+	chain = kzalloc(sizeof(*chain), GFP_KERNEL);
+	if (chain == NULL)
+		return NULL;
+
+	INIT_LIST_HEAD(&chain->entities);
+	mutex_init(&chain->ctrl_mutex);
+	chain->dev = dev;
+	v4l2_prio_init(&chain->prio);
+
+	return chain;
+}
+
+/*
+ * Fallback heuristic for devices that don't connect units and terminals in a
+ * valid chain.
+ *
+ * Some devices have invalid baSourceID references, causing uvc_scan_chain()
+ * to fail, but if we just take the entities we can find and put them together
+ * in the most sensible chain we can think of, turns out they do work anyway.
+ * Note: This heuristic assumes there is a single chain.
+ *
+ * At the time of writing, devices known to have such a broken chain are
+ *  - Acer Integrated Camera (5986:055a)
+ *  - Realtek rtl157a7 (0bda:57a7)
+ */
+static int uvc_scan_fallback(struct uvc_device *dev)
+{
+	struct uvc_video_chain *chain;
+	struct uvc_entity *iterm = NULL;
+	struct uvc_entity *oterm = NULL;
+	struct uvc_entity *entity;
+	struct uvc_entity *prev;
+
+	/*
+	 * Start by locating the input and output terminals. We only support
+	 * devices with exactly one of each for now.
+	 */
+	list_for_each_entry(entity, &dev->entities, list) {
+		if (UVC_ENTITY_IS_ITERM(entity)) {
+			if (iterm)
+				return -EINVAL;
+			iterm = entity;
+		}
+
+		if (UVC_ENTITY_IS_OTERM(entity)) {
+			if (oterm)
+				return -EINVAL;
+			oterm = entity;
+		}
+	}
+
+	if (iterm == NULL || oterm == NULL)
+		return -EINVAL;
+
+	/* Allocate the chain and fill it. */
+	chain = uvc_alloc_chain(dev);
+	if (chain == NULL)
+		return -ENOMEM;
+
+	if (uvc_scan_chain_entity(chain, oterm) < 0)
+		goto error;
+
+	prev = oterm;
+
+	/*
+	 * Add all Processing and Extension Units with two pads. The order
+	 * doesn't matter much, use reverse list traversal to connect units in
+	 * UVC descriptor order as we build the chain from output to input. This
+	 * leads to units appearing in the order meant by the manufacturer for
+	 * the cameras known to require this heuristic.
+	 */
+	list_for_each_entry_reverse(entity, &dev->entities, list) {
+		if (entity->type != UVC_VC_PROCESSING_UNIT &&
+		    entity->type != UVC_VC_EXTENSION_UNIT)
+			continue;
+
+		if (entity->num_pads != 2)
+			continue;
+
+		if (uvc_scan_chain_entity(chain, entity) < 0)
+			goto error;
+
+		prev->baSourceID[0] = entity->id;
+		prev = entity;
+	}
+
+	if (uvc_scan_chain_entity(chain, iterm) < 0)
+		goto error;
+
+	prev->baSourceID[0] = iterm->id;
+
+	list_add_tail(&chain->list, &dev->chains);
+
+	uvc_trace(UVC_TRACE_PROBE,
+		  "Found a video chain by fallback heuristic (%s).\n",
+		  uvc_print_chain(chain));
+
+	return 0;
+
+error:
+	kfree(chain);
+	return -EINVAL;
+}
+
+/*
+ * Scan the device for video chains and register video devices.
+ *
+ * Chains are scanned starting at their output terminals and walked backwards.
+ */
+static int uvc_scan_device(struct uvc_device *dev)
+{
+	struct uvc_video_chain *chain;
+	struct uvc_entity *term;
+
+	list_for_each_entry(term, &dev->entities, list) {
+		if (!UVC_ENTITY_IS_OTERM(term))
+			continue;
+
+		/* If the terminal is already included in a chain, skip it.
+		 * This can happen for chains that have multiple output
+		 * terminals, where all output terminals beside the first one
+		 * will be inserted in the chain in forward scans.
+		 */
+		if (term->chain.next || term->chain.prev)
+			continue;
+
+		chain = uvc_alloc_chain(dev);
+		if (chain == NULL)
+			return -ENOMEM;
+
+		term->flags |= UVC_ENTITY_FLAG_DEFAULT;
+
+		if (uvc_scan_chain(chain, term) < 0) {
+			kfree(chain);
+			continue;
+		}
+
+		uvc_trace(UVC_TRACE_PROBE, "Found a valid video chain (%s).\n",
+			  uvc_print_chain(chain));
+
+		list_add_tail(&chain->list, &dev->chains);
+	}
+
+	if (list_empty(&dev->chains))
+		uvc_scan_fallback(dev);
+
+	if (list_empty(&dev->chains)) {
+		uvc_printk(KERN_INFO, "No valid video chain found.\n");
+		return -1;
+	}
+
+	return 0;
+}
+
+/* ------------------------------------------------------------------------
+ * Video device registration and unregistration
+ */
+
+/*
+ * Delete the UVC device.
+ *
+ * Called by the kernel when the last reference to the uvc_device structure
+ * is released.
+ *
+ * As this function is called after or during disconnect(), all URBs have
+ * already been canceled by the USB core. There is no need to kill the
+ * interrupt URB manually.
+ */
+static void uvc_delete(struct kref *kref)
+{
+	struct uvc_device *dev = container_of(kref, struct uvc_device, ref);
+	struct list_head *p, *n;
+
+	uvc_status_cleanup(dev);
+	uvc_ctrl_cleanup_device(dev);
+
+	usb_put_intf(dev->intf);
+	usb_put_dev(dev->udev);
+
+	if (dev->vdev.dev)
+		v4l2_device_unregister(&dev->vdev);
+#ifdef CONFIG_MEDIA_CONTROLLER
+	if (media_devnode_is_registered(dev->mdev.devnode))
+		media_device_unregister(&dev->mdev);
+	media_device_cleanup(&dev->mdev);
+#endif
+
+	list_for_each_safe(p, n, &dev->chains) {
+		struct uvc_video_chain *chain;
+		chain = list_entry(p, struct uvc_video_chain, list);
+		kfree(chain);
+	}
+
+	list_for_each_safe(p, n, &dev->entities) {
+		struct uvc_entity *entity;
+		entity = list_entry(p, struct uvc_entity, list);
+#ifdef CONFIG_MEDIA_CONTROLLER
+		uvc_mc_cleanup_entity(entity);
+#endif
+		kfree(entity);
+	}
+
+	list_for_each_safe(p, n, &dev->streams) {
+		struct uvc_streaming *streaming;
+		streaming = list_entry(p, struct uvc_streaming, list);
+		usb_driver_release_interface(&uvc_driver.driver,
+			streaming->intf);
+		usb_put_intf(streaming->intf);
+		kfree(streaming->format);
+		kfree(streaming->header.bmaControls);
+		kfree(streaming);
+	}
+
+	kfree(dev);
+}
+
+static void uvc_release(struct video_device *vdev)
+{
+	struct uvc_streaming *stream = video_get_drvdata(vdev);
+	struct uvc_device *dev = stream->dev;
+
+	kref_put(&dev->ref, uvc_delete);
+}
+
+/*
+ * Unregister the video devices.
+ */
+static void uvc_unregister_video(struct uvc_device *dev)
+{
+	struct uvc_streaming *stream;
+
+	list_for_each_entry(stream, &dev->streams, list) {
+		if (!video_is_registered(&stream->vdev))
+			continue;
+
+		video_unregister_device(&stream->vdev);
+		video_unregister_device(&stream->meta.vdev);
+
+		uvc_debugfs_cleanup_stream(stream);
+	}
+}
+
+int uvc_register_video_device(struct uvc_device *dev,
+			      struct uvc_streaming *stream,
+			      struct video_device *vdev,
+			      struct uvc_video_queue *queue,
+			      enum v4l2_buf_type type,
+			      const struct v4l2_file_operations *fops,
+			      const struct v4l2_ioctl_ops *ioctl_ops)
+{
+	int ret;
+
+	/* Initialize the video buffers queue. */
+	ret = uvc_queue_init(queue, type, !uvc_no_drop_param);
+	if (ret)
+		return ret;
+
+	/* Register the device with V4L. */
+
+	/*
+	 * We already hold a reference to dev->udev. The video device will be
+	 * unregistered before the reference is released, so we don't need to
+	 * get another one.
+	 */
+	vdev->v4l2_dev = &dev->vdev;
+	vdev->fops = fops;
+	vdev->ioctl_ops = ioctl_ops;
+	vdev->release = uvc_release;
+	vdev->prio = &stream->chain->prio;
+	if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
+		vdev->vfl_dir = VFL_DIR_TX;
+	else
+		vdev->vfl_dir = VFL_DIR_RX;
+
+	switch (type) {
+	case V4L2_BUF_TYPE_VIDEO_CAPTURE:
+	default:
+		vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
+		break;
+	case V4L2_BUF_TYPE_VIDEO_OUTPUT:
+		vdev->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;
+		break;
+	case V4L2_BUF_TYPE_META_CAPTURE:
+		vdev->device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING;
+		break;
+	}
+
+	strlcpy(vdev->name, dev->name, sizeof(vdev->name));
+
+	/*
+	 * Set the driver data before calling video_register_device, otherwise
+	 * the file open() handler might race us.
+	 */
+	video_set_drvdata(vdev, stream);
+
+	ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
+	if (ret < 0) {
+		uvc_printk(KERN_ERR, "Failed to register %s device (%d).\n",
+			   v4l2_type_names[type], ret);
+		return ret;
+	}
+
+	kref_get(&dev->ref);
+	return 0;
+}
+
+static int uvc_register_video(struct uvc_device *dev,
+		struct uvc_streaming *stream)
+{
+	int ret;
+
+	/* Initialize the streaming interface with default parameters. */
+	ret = uvc_video_init(stream);
+	if (ret < 0) {
+		uvc_printk(KERN_ERR, "Failed to initialize the device (%d).\n",
+			   ret);
+		return ret;
+	}
+
+	if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		stream->chain->caps |= V4L2_CAP_VIDEO_CAPTURE
+			| V4L2_CAP_META_CAPTURE;
+	else
+		stream->chain->caps |= V4L2_CAP_VIDEO_OUTPUT;
+
+	uvc_debugfs_init_stream(stream);
+
+	/* Register the device with V4L. */
+	return uvc_register_video_device(dev, stream, &stream->vdev,
+					 &stream->queue, stream->type,
+					 &uvc_fops, &uvc_ioctl_ops);
+}
+
+/*
+ * Register all video devices in all chains.
+ */
+static int uvc_register_terms(struct uvc_device *dev,
+	struct uvc_video_chain *chain)
+{
+	struct uvc_streaming *stream;
+	struct uvc_entity *term;
+	int ret;
+
+	list_for_each_entry(term, &chain->entities, chain) {
+		if (UVC_ENTITY_TYPE(term) != UVC_TT_STREAMING)
+			continue;
+
+		stream = uvc_stream_by_id(dev, term->id);
+		if (stream == NULL) {
+			uvc_printk(KERN_INFO, "No streaming interface found "
+				   "for terminal %u.", term->id);
+			continue;
+		}
+
+		stream->chain = chain;
+		ret = uvc_register_video(dev, stream);
+		if (ret < 0)
+			return ret;
+
+		/* Register a metadata node, but ignore a possible failure,
+		 * complete registration of video nodes anyway.
+		 */
+		uvc_meta_register(stream);
+
+		term->vdev = &stream->vdev;
+	}
+
+	return 0;
+}
+
+static int uvc_register_chains(struct uvc_device *dev)
+{
+	struct uvc_video_chain *chain;
+	int ret;
+
+	list_for_each_entry(chain, &dev->chains, list) {
+		ret = uvc_register_terms(dev, chain);
+		if (ret < 0)
+			return ret;
+
+#ifdef CONFIG_MEDIA_CONTROLLER
+		ret = uvc_mc_register_entities(chain);
+		if (ret < 0) {
+			uvc_printk(KERN_INFO, "Failed to register entites "
+				"(%d).\n", ret);
+		}
+#endif
+	}
+
+	return 0;
+}
+
+/* ------------------------------------------------------------------------
+ * USB probe, disconnect, suspend and resume
+ */
+
+struct uvc_device_info {
+	u32	quirks;
+	u32	meta_format;
+};
+
+static int uvc_probe(struct usb_interface *intf,
+		     const struct usb_device_id *id)
+{
+	struct usb_device *udev = interface_to_usbdev(intf);
+	struct uvc_device *dev;
+	const struct uvc_device_info *info =
+		(const struct uvc_device_info *)id->driver_info;
+	u32 quirks = info ? info->quirks : 0;
+	int function;
+	int ret;
+
+	if (id->idVendor && id->idProduct)
+		uvc_trace(UVC_TRACE_PROBE, "Probing known UVC device %s "
+				"(%04x:%04x)\n", udev->devpath, id->idVendor,
+				id->idProduct);
+	else
+		uvc_trace(UVC_TRACE_PROBE, "Probing generic UVC device %s\n",
+				udev->devpath);
+
+	/* Allocate memory for the device and initialize it. */
+	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+	if (dev == NULL)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&dev->entities);
+	INIT_LIST_HEAD(&dev->chains);
+	INIT_LIST_HEAD(&dev->streams);
+	kref_init(&dev->ref);
+	atomic_set(&dev->nmappings, 0);
+	mutex_init(&dev->lock);
+
+	dev->udev = usb_get_dev(udev);
+	dev->intf = usb_get_intf(intf);
+	dev->intfnum = intf->cur_altsetting->desc.bInterfaceNumber;
+	dev->quirks = (uvc_quirks_param == -1)
+		    ? quirks : uvc_quirks_param;
+	if (info)
+		dev->meta_format = info->meta_format;
+
+	if (udev->product != NULL)
+		strlcpy(dev->name, udev->product, sizeof(dev->name));
+	else
+		snprintf(dev->name, sizeof(dev->name),
+			 "UVC Camera (%04x:%04x)",
+			 le16_to_cpu(udev->descriptor.idVendor),
+			 le16_to_cpu(udev->descriptor.idProduct));
+
+	/*
+	 * Add iFunction or iInterface to names when available as additional
+	 * distinguishers between interfaces. iFunction is prioritized over
+	 * iInterface which matches Windows behavior at the point of writing.
+	 */
+	if (intf->intf_assoc && intf->intf_assoc->iFunction != 0)
+		function = intf->intf_assoc->iFunction;
+	else
+		function = intf->cur_altsetting->desc.iInterface;
+	if (function != 0) {
+		size_t len;
+
+		strlcat(dev->name, ": ", sizeof(dev->name));
+		len = strlen(dev->name);
+		usb_string(udev, function, dev->name + len,
+			   sizeof(dev->name) - len);
+	}
+
+	/* Parse the Video Class control descriptor. */
+	if (uvc_parse_control(dev) < 0) {
+		uvc_trace(UVC_TRACE_PROBE, "Unable to parse UVC "
+			"descriptors.\n");
+		goto error;
+	}
+
+	uvc_printk(KERN_INFO, "Found UVC %u.%02x device %s (%04x:%04x)\n",
+		dev->uvc_version >> 8, dev->uvc_version & 0xff,
+		udev->product ? udev->product : "<unnamed>",
+		le16_to_cpu(udev->descriptor.idVendor),
+		le16_to_cpu(udev->descriptor.idProduct));
+
+	if (dev->quirks != quirks) {
+		uvc_printk(KERN_INFO, "Forcing device quirks to 0x%x by module "
+			"parameter for testing purpose.\n", dev->quirks);
+		uvc_printk(KERN_INFO, "Please report required quirks to the "
+			"linux-uvc-devel mailing list.\n");
+	}
+
+	/* Initialize the media device and register the V4L2 device. */
+#ifdef CONFIG_MEDIA_CONTROLLER
+	dev->mdev.dev = &intf->dev;
+	strlcpy(dev->mdev.model, dev->name, sizeof(dev->mdev.model));
+	if (udev->serial)
+		strlcpy(dev->mdev.serial, udev->serial,
+			sizeof(dev->mdev.serial));
+	strcpy(dev->mdev.bus_info, udev->devpath);
+	dev->mdev.hw_revision = le16_to_cpu(udev->descriptor.bcdDevice);
+	media_device_init(&dev->mdev);
+
+	dev->vdev.mdev = &dev->mdev;
+#endif
+	if (v4l2_device_register(&intf->dev, &dev->vdev) < 0)
+		goto error;
+
+	/* Initialize controls. */
+	if (uvc_ctrl_init_device(dev) < 0)
+		goto error;
+
+	/* Scan the device for video chains. */
+	if (uvc_scan_device(dev) < 0)
+		goto error;
+
+	/* Register video device nodes. */
+	if (uvc_register_chains(dev) < 0)
+		goto error;
+
+#ifdef CONFIG_MEDIA_CONTROLLER
+	/* Register the media device node */
+	if (media_device_register(&dev->mdev) < 0)
+		goto error;
+#endif
+	/* Save our data pointer in the interface data. */
+	usb_set_intfdata(intf, dev);
+
+	/* Initialize the interrupt URB. */
+	if ((ret = uvc_status_init(dev)) < 0) {
+		uvc_printk(KERN_INFO, "Unable to initialize the status "
+			"endpoint (%d), status interrupt will not be "
+			"supported.\n", ret);
+	}
+
+	uvc_trace(UVC_TRACE_PROBE, "UVC device initialized.\n");
+	usb_enable_autosuspend(udev);
+	return 0;
+
+error:
+	uvc_unregister_video(dev);
+	kref_put(&dev->ref, uvc_delete);
+	return -ENODEV;
+}
+
+static void uvc_disconnect(struct usb_interface *intf)
+{
+	struct uvc_device *dev = usb_get_intfdata(intf);
+
+	/* Set the USB interface data to NULL. This can be done outside the
+	 * lock, as there's no other reader.
+	 */
+	usb_set_intfdata(intf, NULL);
+
+	if (intf->cur_altsetting->desc.bInterfaceSubClass ==
+	    UVC_SC_VIDEOSTREAMING)
+		return;
+
+	uvc_unregister_video(dev);
+	kref_put(&dev->ref, uvc_delete);
+}
+
+static int uvc_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	struct uvc_device *dev = usb_get_intfdata(intf);
+	struct uvc_streaming *stream;
+
+	uvc_trace(UVC_TRACE_SUSPEND, "Suspending interface %u\n",
+		intf->cur_altsetting->desc.bInterfaceNumber);
+
+	/* Controls are cached on the fly so they don't need to be saved. */
+	if (intf->cur_altsetting->desc.bInterfaceSubClass ==
+	    UVC_SC_VIDEOCONTROL) {
+		mutex_lock(&dev->lock);
+		if (dev->users)
+			uvc_status_stop(dev);
+		mutex_unlock(&dev->lock);
+		return 0;
+	}
+
+	list_for_each_entry(stream, &dev->streams, list) {
+		if (stream->intf == intf)
+			return uvc_video_suspend(stream);
+	}
+
+	uvc_trace(UVC_TRACE_SUSPEND, "Suspend: video streaming USB interface "
+			"mismatch.\n");
+	return -EINVAL;
+}
+
+static int __uvc_resume(struct usb_interface *intf, int reset)
+{
+	struct uvc_device *dev = usb_get_intfdata(intf);
+	struct uvc_streaming *stream;
+	int ret = 0;
+
+	uvc_trace(UVC_TRACE_SUSPEND, "Resuming interface %u\n",
+		intf->cur_altsetting->desc.bInterfaceNumber);
+
+	if (intf->cur_altsetting->desc.bInterfaceSubClass ==
+	    UVC_SC_VIDEOCONTROL) {
+		if (reset) {
+			ret = uvc_ctrl_restore_values(dev);
+			if (ret < 0)
+				return ret;
+		}
+
+		mutex_lock(&dev->lock);
+		if (dev->users)
+			ret = uvc_status_start(dev, GFP_NOIO);
+		mutex_unlock(&dev->lock);
+
+		return ret;
+	}
+
+	list_for_each_entry(stream, &dev->streams, list) {
+		if (stream->intf == intf) {
+			ret = uvc_video_resume(stream, reset);
+			if (ret < 0)
+				uvc_queue_streamoff(&stream->queue,
+						    stream->queue.queue.type);
+			return ret;
+		}
+	}
+
+	uvc_trace(UVC_TRACE_SUSPEND, "Resume: video streaming USB interface "
+			"mismatch.\n");
+	return -EINVAL;
+}
+
+static int uvc_resume(struct usb_interface *intf)
+{
+	return __uvc_resume(intf, 0);
+}
+
+static int uvc_reset_resume(struct usb_interface *intf)
+{
+	return __uvc_resume(intf, 1);
+}
+
+/* ------------------------------------------------------------------------
+ * Module parameters
+ */
+
+static int uvc_clock_param_get(char *buffer, const struct kernel_param *kp)
+{
+	if (uvc_clock_param == CLOCK_MONOTONIC)
+		return sprintf(buffer, "CLOCK_MONOTONIC");
+	else
+		return sprintf(buffer, "CLOCK_REALTIME");
+}
+
+static int uvc_clock_param_set(const char *val, const struct kernel_param *kp)
+{
+	if (strncasecmp(val, "clock_", strlen("clock_")) == 0)
+		val += strlen("clock_");
+
+	if (strcasecmp(val, "monotonic") == 0)
+		uvc_clock_param = CLOCK_MONOTONIC;
+	else if (strcasecmp(val, "realtime") == 0)
+		uvc_clock_param = CLOCK_REALTIME;
+	else
+		return -EINVAL;
+
+	return 0;
+}
+
+module_param_call(clock, uvc_clock_param_set, uvc_clock_param_get,
+		  &uvc_clock_param, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(clock, "Video buffers timestamp clock");
+module_param_named(hwtimestamps, uvc_hw_timestamps_param, uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(hwtimestamps, "Use hardware timestamps");
+module_param_named(nodrop, uvc_no_drop_param, uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(nodrop, "Don't drop incomplete frames");
+module_param_named(quirks, uvc_quirks_param, uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(quirks, "Forced device quirks");
+module_param_named(trace, uvc_trace_param, uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(trace, "Trace level bitmask");
+module_param_named(timeout, uvc_timeout_param, uint, S_IRUGO|S_IWUSR);
+MODULE_PARM_DESC(timeout, "Streaming control requests timeout");
+
+/* ------------------------------------------------------------------------
+ * Driver initialization and cleanup
+ */
+
+static const struct uvc_device_info uvc_quirk_probe_minmax = {
+	.quirks = UVC_QUIRK_PROBE_MINMAX,
+};
+
+static const struct uvc_device_info uvc_quirk_fix_bandwidth = {
+	.quirks = UVC_QUIRK_FIX_BANDWIDTH,
+};
+
+static const struct uvc_device_info uvc_quirk_probe_def = {
+	.quirks = UVC_QUIRK_PROBE_DEF,
+};
+
+static const struct uvc_device_info uvc_quirk_stream_no_fid = {
+	.quirks = UVC_QUIRK_STREAM_NO_FID,
+};
+
+static const struct uvc_device_info uvc_quirk_force_y8 = {
+	.quirks = UVC_QUIRK_FORCE_Y8,
+};
+
+#define UVC_QUIRK_INFO(q) (kernel_ulong_t)&(struct uvc_device_info){.quirks = q}
+
+/*
+ * The Logitech cameras listed below have their interface class set to
+ * VENDOR_SPEC because they don't announce themselves as UVC devices, even
+ * though they are compliant.
+ */
+static const struct usb_device_id uvc_ids[] = {
+	/* LogiLink Wireless Webcam */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x0416,
+	  .idProduct		= 0xa91a,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_probe_minmax },
+	/* Genius eFace 2025 */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x0458,
+	  .idProduct		= 0x706e,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_probe_minmax },
+	/* Microsoft Lifecam NX-6000 */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x045e,
+	  .idProduct		= 0x00f8,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_probe_minmax },
+	/* Microsoft Lifecam NX-3000 */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x045e,
+	  .idProduct		= 0x0721,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_probe_def },
+	/* Microsoft Lifecam VX-7000 */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x045e,
+	  .idProduct		= 0x0723,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_probe_minmax },
+	/* Logitech Quickcam Fusion */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x046d,
+	  .idProduct		= 0x08c1,
+	  .bInterfaceClass	= USB_CLASS_VENDOR_SPEC,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0 },
+	/* Logitech Quickcam Orbit MP */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x046d,
+	  .idProduct		= 0x08c2,
+	  .bInterfaceClass	= USB_CLASS_VENDOR_SPEC,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0 },
+	/* Logitech Quickcam Pro for Notebook */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x046d,
+	  .idProduct		= 0x08c3,
+	  .bInterfaceClass	= USB_CLASS_VENDOR_SPEC,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0 },
+	/* Logitech Quickcam Pro 5000 */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x046d,
+	  .idProduct		= 0x08c5,
+	  .bInterfaceClass	= USB_CLASS_VENDOR_SPEC,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0 },
+	/* Logitech Quickcam OEM Dell Notebook */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x046d,
+	  .idProduct		= 0x08c6,
+	  .bInterfaceClass	= USB_CLASS_VENDOR_SPEC,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0 },
+	/* Logitech Quickcam OEM Cisco VT Camera II */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x046d,
+	  .idProduct		= 0x08c7,
+	  .bInterfaceClass	= USB_CLASS_VENDOR_SPEC,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0 },
+	/* Logitech HD Pro Webcam C920 */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x046d,
+	  .idProduct		= 0x082d,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= UVC_QUIRK_INFO(UVC_QUIRK_RESTORE_CTRLS_ON_INIT) },
+	/* Chicony CNF7129 (Asus EEE 100HE) */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x04f2,
+	  .idProduct		= 0xb071,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= UVC_QUIRK_INFO(UVC_QUIRK_RESTRICT_FRAME_RATE) },
+	/* Alcor Micro AU3820 (Future Boy PC USB Webcam) */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x058f,
+	  .idProduct		= 0x3820,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_probe_minmax },
+	/* Dell XPS m1530 */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x05a9,
+	  .idProduct		= 0x2640,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_probe_def },
+	/* Dell SP2008WFP Monitor */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x05a9,
+	  .idProduct		= 0x2641,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_probe_def },
+	/* Dell Alienware X51 */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x05a9,
+	  .idProduct		= 0x2643,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_probe_def },
+	/* Dell Studio Hybrid 140g (OmniVision webcam) */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x05a9,
+	  .idProduct		= 0x264a,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_probe_def },
+	/* Dell XPS M1330 (OmniVision OV7670 webcam) */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x05a9,
+	  .idProduct		= 0x7670,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_probe_def },
+	/* Apple Built-In iSight */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x05ac,
+	  .idProduct		= 0x8501,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= UVC_QUIRK_INFO(UVC_QUIRK_PROBE_MINMAX
+					| UVC_QUIRK_BUILTIN_ISIGHT) },
+	/* Apple Built-In iSight via iBridge */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x05ac,
+	  .idProduct		= 0x8600,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_probe_def },
+	/* Foxlink ("HP Webcam" on HP Mini 5103) */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x05c8,
+	  .idProduct		= 0x0403,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_fix_bandwidth },
+	/* Genesys Logic USB 2.0 PC Camera */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x05e3,
+	  .idProduct		= 0x0505,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_stream_no_fid },
+	/* Hercules Classic Silver */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x06f8,
+	  .idProduct		= 0x300c,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_fix_bandwidth },
+	/* ViMicro Vega */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x0ac8,
+	  .idProduct		= 0x332d,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_fix_bandwidth },
+	/* ViMicro - Minoru3D */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x0ac8,
+	  .idProduct		= 0x3410,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_fix_bandwidth },
+	/* ViMicro Venus - Minoru3D */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x0ac8,
+	  .idProduct		= 0x3420,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_fix_bandwidth },
+	/* Ophir Optronics - SPCAM 620U */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x0bd3,
+	  .idProduct		= 0x0555,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_probe_minmax },
+	/* MT6227 */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x0e8d,
+	  .idProduct		= 0x0004,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= UVC_QUIRK_INFO(UVC_QUIRK_PROBE_MINMAX
+					| UVC_QUIRK_PROBE_DEF) },
+	/* IMC Networks (Medion Akoya) */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x13d3,
+	  .idProduct		= 0x5103,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_stream_no_fid },
+	/* JMicron USB2.0 XGA WebCam */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x152d,
+	  .idProduct		= 0x0310,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_probe_minmax },
+	/* Syntek (HP Spartan) */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x174f,
+	  .idProduct		= 0x5212,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_stream_no_fid },
+	/* Syntek (Samsung Q310) */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x174f,
+	  .idProduct		= 0x5931,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_stream_no_fid },
+	/* Syntek (Packard Bell EasyNote MX52 */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x174f,
+	  .idProduct		= 0x8a12,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_stream_no_fid },
+	/* Syntek (Asus F9SG) */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x174f,
+	  .idProduct		= 0x8a31,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_stream_no_fid },
+	/* Syntek (Asus U3S) */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x174f,
+	  .idProduct		= 0x8a33,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_stream_no_fid },
+	/* Syntek (JAOtech Smart Terminal) */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x174f,
+	  .idProduct		= 0x8a34,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_stream_no_fid },
+	/* Miricle 307K */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x17dc,
+	  .idProduct		= 0x0202,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_stream_no_fid },
+	/* Lenovo Thinkpad SL400/SL500 */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x17ef,
+	  .idProduct		= 0x480b,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_stream_no_fid },
+	/* Aveo Technology USB 2.0 Camera */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x1871,
+	  .idProduct		= 0x0306,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= UVC_QUIRK_INFO(UVC_QUIRK_PROBE_MINMAX
+					| UVC_QUIRK_PROBE_EXTRAFIELDS) },
+	/* Aveo Technology USB 2.0 Camera (Tasco USB Microscope) */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x1871,
+	  .idProduct		= 0x0516,
+	  .bInterfaceClass	= USB_CLASS_VENDOR_SPEC,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0 },
+	/* Ecamm Pico iMage */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x18cd,
+	  .idProduct		= 0xcafe,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= UVC_QUIRK_INFO(UVC_QUIRK_PROBE_EXTRAFIELDS) },
+	/* Manta MM-353 Plako */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x18ec,
+	  .idProduct		= 0x3188,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_probe_minmax },
+	/* FSC WebCam V30S */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x18ec,
+	  .idProduct		= 0x3288,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_probe_minmax },
+	/* Arkmicro unbranded */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x18ec,
+	  .idProduct		= 0x3290,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_probe_def },
+	/* The Imaging Source USB CCD cameras */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x199e,
+	  .idProduct		= 0x8102,
+	  .bInterfaceClass	= USB_CLASS_VENDOR_SPEC,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0 },
+	/* Bodelin ProScopeHR */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_DEV_HI
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x19ab,
+	  .idProduct		= 0x1000,
+	  .bcdDevice_hi		= 0x0126,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= UVC_QUIRK_INFO(UVC_QUIRK_STATUS_INTERVAL) },
+	/* MSI StarCam 370i */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x1b3b,
+	  .idProduct		= 0x2951,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_probe_minmax },
+	/* Generalplus Technology Inc. 808 Camera */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x1b3f,
+	  .idProduct		= 0x2002,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_probe_minmax },
+	/* SiGma Micro USB Web Camera */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x1c4f,
+	  .idProduct		= 0x3000,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= UVC_QUIRK_INFO(UVC_QUIRK_PROBE_MINMAX
+					| UVC_QUIRK_IGNORE_SELECTOR_UNIT) },
+	/* Oculus VR Positional Tracker DK2 */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x2833,
+	  .idProduct		= 0x0201,
+	  .bInterfaceClass	= USB_CLASS_VIDEO,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_force_y8 },
+	/* Oculus VR Rift Sensor */
+	{ .match_flags		= USB_DEVICE_ID_MATCH_DEVICE
+				| USB_DEVICE_ID_MATCH_INT_INFO,
+	  .idVendor		= 0x2833,
+	  .idProduct		= 0x0211,
+	  .bInterfaceClass	= USB_CLASS_VENDOR_SPEC,
+	  .bInterfaceSubClass	= 1,
+	  .bInterfaceProtocol	= 0,
+	  .driver_info		= (kernel_ulong_t)&uvc_quirk_force_y8 },
+	/* Generic USB Video Class */
+	{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, UVC_PC_PROTOCOL_UNDEFINED) },
+	{ USB_INTERFACE_INFO(USB_CLASS_VIDEO, 1, UVC_PC_PROTOCOL_15) },
+	{}
+};
+
+MODULE_DEVICE_TABLE(usb, uvc_ids);
+
+struct uvc_driver uvc_driver = {
+	.driver = {
+		.name		= "uvcvideo",
+		.probe		= uvc_probe,
+		.disconnect	= uvc_disconnect,
+		.suspend	= uvc_suspend,
+		.resume		= uvc_resume,
+		.reset_resume	= uvc_reset_resume,
+		.id_table	= uvc_ids,
+		.supports_autosuspend = 1,
+	},
+};
+
+static int __init uvc_init(void)
+{
+	int ret;
+
+	uvc_debugfs_init();
+
+	ret = usb_register(&uvc_driver.driver);
+	if (ret < 0) {
+		uvc_debugfs_cleanup();
+		return ret;
+	}
+
+	printk(KERN_INFO DRIVER_DESC " (" DRIVER_VERSION ")\n");
+	return 0;
+}
+
+static void __exit uvc_cleanup(void)
+{
+	usb_deregister(&uvc_driver.driver);
+	uvc_debugfs_cleanup();
+}
+
+module_init(uvc_init);
+module_exit(uvc_cleanup);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRIVER_VERSION);
+
diff --git a/drivers/media/usb/uvc/uvc_entity.c b/drivers/media/usb/uvc/uvc_entity.c
new file mode 100644
index 0000000..554063c
--- /dev/null
+++ b/drivers/media/usb/uvc/uvc_entity.c
@@ -0,0 +1,128 @@
+/*
+ *      uvc_entity.c  --  USB Video Class driver
+ *
+ *      Copyright (C) 2005-2011
+ *          Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ *
+ *      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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/videodev2.h>
+
+#include <media/v4l2-common.h>
+
+#include "uvcvideo.h"
+
+static int uvc_mc_create_links(struct uvc_video_chain *chain,
+				    struct uvc_entity *entity)
+{
+	const u32 flags = MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE;
+	struct media_entity *sink;
+	unsigned int i;
+	int ret;
+
+	sink = (UVC_ENTITY_TYPE(entity) == UVC_TT_STREAMING)
+	     ? (entity->vdev ? &entity->vdev->entity : NULL)
+	     : &entity->subdev.entity;
+	if (sink == NULL)
+		return 0;
+
+	for (i = 0; i < entity->num_pads; ++i) {
+		struct media_entity *source;
+		struct uvc_entity *remote;
+		u8 remote_pad;
+
+		if (!(entity->pads[i].flags & MEDIA_PAD_FL_SINK))
+			continue;
+
+		remote = uvc_entity_by_id(chain->dev, entity->baSourceID[i]);
+		if (remote == NULL)
+			return -EINVAL;
+
+		source = (UVC_ENTITY_TYPE(remote) == UVC_TT_STREAMING)
+		       ? (remote->vdev ? &remote->vdev->entity : NULL)
+		       : &remote->subdev.entity;
+		if (source == NULL)
+			continue;
+
+		remote_pad = remote->num_pads - 1;
+		ret = media_create_pad_link(source, remote_pad,
+					       sink, i, flags);
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+
+static const struct v4l2_subdev_ops uvc_subdev_ops = {
+};
+
+void uvc_mc_cleanup_entity(struct uvc_entity *entity)
+{
+	if (UVC_ENTITY_TYPE(entity) != UVC_TT_STREAMING)
+		media_entity_cleanup(&entity->subdev.entity);
+	else if (entity->vdev != NULL)
+		media_entity_cleanup(&entity->vdev->entity);
+}
+
+static int uvc_mc_init_entity(struct uvc_video_chain *chain,
+			      struct uvc_entity *entity)
+{
+	int ret;
+
+	if (UVC_ENTITY_TYPE(entity) != UVC_TT_STREAMING) {
+		v4l2_subdev_init(&entity->subdev, &uvc_subdev_ops);
+		strlcpy(entity->subdev.name, entity->name,
+			sizeof(entity->subdev.name));
+
+		ret = media_entity_pads_init(&entity->subdev.entity,
+					entity->num_pads, entity->pads);
+
+		if (ret < 0)
+			return ret;
+
+		ret = v4l2_device_register_subdev(&chain->dev->vdev,
+						  &entity->subdev);
+	} else if (entity->vdev != NULL) {
+		ret = media_entity_pads_init(&entity->vdev->entity,
+					entity->num_pads, entity->pads);
+		if (entity->flags & UVC_ENTITY_FLAG_DEFAULT)
+			entity->vdev->entity.flags |= MEDIA_ENT_FL_DEFAULT;
+	} else
+		ret = 0;
+
+	return ret;
+}
+
+int uvc_mc_register_entities(struct uvc_video_chain *chain)
+{
+	struct uvc_entity *entity;
+	int ret;
+
+	list_for_each_entry(entity, &chain->entities, chain) {
+		ret = uvc_mc_init_entity(chain, entity);
+		if (ret < 0) {
+			uvc_printk(KERN_INFO, "Failed to initialize entity for "
+				   "entity %u\n", entity->id);
+			return ret;
+		}
+	}
+
+	list_for_each_entry(entity, &chain->entities, chain) {
+		ret = uvc_mc_create_links(chain, entity);
+		if (ret < 0) {
+			uvc_printk(KERN_INFO, "Failed to create links for "
+				   "entity %u\n", entity->id);
+			return ret;
+		}
+	}
+
+	return 0;
+}
diff --git a/drivers/media/usb/uvc/uvc_isight.c b/drivers/media/usb/uvc/uvc_isight.c
new file mode 100644
index 0000000..81e6f21
--- /dev/null
+++ b/drivers/media/usb/uvc/uvc_isight.c
@@ -0,0 +1,137 @@
+/*
+ *      uvc_isight.c  --  USB Video Class driver - iSight support
+ *
+ *	Copyright (C) 2006-2007
+ *		Ivan N. Zlatev <contact@i-nz.net>
+ *	Copyright (C) 2008-2009
+ *		Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ *
+ *      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.
+ *
+ */
+
+#include <linux/usb.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+
+#include "uvcvideo.h"
+
+/* Built-in iSight webcams implements most of UVC 1.0 except a
+ * different packet format. Instead of sending a header at the
+ * beginning of each isochronous transfer payload, the webcam sends a
+ * single header per image (on its own in a packet), followed by
+ * packets containing data only.
+ *
+ * Offset   Size (bytes)	Description
+ * ------------------------------------------------------------------
+ * 0x00	1	Header length
+ * 0x01	1	Flags (UVC-compliant)
+ * 0x02	4	Always equal to '11223344'
+ * 0x06	8	Always equal to 'deadbeefdeadface'
+ * 0x0e	16	Unknown
+ *
+ * The header can be prefixed by an optional, unknown-purpose byte.
+ */
+
+static int isight_decode(struct uvc_video_queue *queue, struct uvc_buffer *buf,
+		const u8 *data, unsigned int len)
+{
+	static const u8 hdr[] = {
+		0x11, 0x22, 0x33, 0x44,
+		0xde, 0xad, 0xbe, 0xef,
+		0xde, 0xad, 0xfa, 0xce
+	};
+
+	unsigned int maxlen, nbytes;
+	u8 *mem;
+	int is_header = 0;
+
+	if (buf == NULL)
+		return 0;
+
+	if ((len >= 14 && memcmp(&data[2], hdr, 12) == 0) ||
+	    (len >= 15 && memcmp(&data[3], hdr, 12) == 0)) {
+		uvc_trace(UVC_TRACE_FRAME, "iSight header found\n");
+		is_header = 1;
+	}
+
+	/* Synchronize to the input stream by waiting for a header packet. */
+	if (buf->state != UVC_BUF_STATE_ACTIVE) {
+		if (!is_header) {
+			uvc_trace(UVC_TRACE_FRAME, "Dropping packet (out of "
+				  "sync).\n");
+			return 0;
+		}
+
+		buf->state = UVC_BUF_STATE_ACTIVE;
+	}
+
+	/* Mark the buffer as done if we're at the beginning of a new frame.
+	 *
+	 * Empty buffers (bytesused == 0) don't trigger end of frame detection
+	 * as it doesn't make sense to return an empty buffer.
+	 */
+	if (is_header && buf->bytesused != 0) {
+		buf->state = UVC_BUF_STATE_DONE;
+		return -EAGAIN;
+	}
+
+	/* Copy the video data to the buffer. Skip header packets, as they
+	 * contain no data.
+	 */
+	if (!is_header) {
+		maxlen = buf->length - buf->bytesused;
+		mem = buf->mem + buf->bytesused;
+		nbytes = min(len, maxlen);
+		memcpy(mem, data, nbytes);
+		buf->bytesused += nbytes;
+
+		if (len > maxlen || buf->bytesused == buf->length) {
+			uvc_trace(UVC_TRACE_FRAME, "Frame complete "
+				  "(overflow).\n");
+			buf->state = UVC_BUF_STATE_DONE;
+		}
+	}
+
+	return 0;
+}
+
+void uvc_video_decode_isight(struct urb *urb, struct uvc_streaming *stream,
+			struct uvc_buffer *buf, struct uvc_buffer *meta_buf)
+{
+	int ret, i;
+
+	for (i = 0; i < urb->number_of_packets; ++i) {
+		if (urb->iso_frame_desc[i].status < 0) {
+			uvc_trace(UVC_TRACE_FRAME, "USB isochronous frame "
+				  "lost (%d).\n",
+				  urb->iso_frame_desc[i].status);
+		}
+
+		/* Decode the payload packet.
+		 * uvc_video_decode is entered twice when a frame transition
+		 * has been detected because the end of frame can only be
+		 * reliably detected when the first packet of the new frame
+		 * is processed. The first pass detects the transition and
+		 * closes the previous frame's buffer, the second pass
+		 * processes the data of the first payload of the new frame.
+		 */
+		do {
+			ret = isight_decode(&stream->queue, buf,
+					urb->transfer_buffer +
+					urb->iso_frame_desc[i].offset,
+					urb->iso_frame_desc[i].actual_length);
+
+			if (buf == NULL)
+				break;
+
+			if (buf->state == UVC_BUF_STATE_DONE ||
+			    buf->state == UVC_BUF_STATE_ERROR)
+				buf = uvc_queue_next_buffer(&stream->queue,
+							buf);
+		} while (ret == -EAGAIN);
+	}
+}
diff --git a/drivers/media/usb/uvc/uvc_metadata.c b/drivers/media/usb/uvc/uvc_metadata.c
new file mode 100644
index 0000000..cd1aec1
--- /dev/null
+++ b/drivers/media/usb/uvc/uvc_metadata.c
@@ -0,0 +1,179 @@
+/*
+ *      uvc_metadata.c  --  USB Video Class driver - Metadata handling
+ *
+ *      Copyright (C) 2016
+ *          Guennadi Liakhovetski (guennadi.liakhovetski@intel.com)
+ *
+ *      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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/videodev2.h>
+
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-vmalloc.h>
+
+#include "uvcvideo.h"
+
+/* -----------------------------------------------------------------------------
+ * V4L2 ioctls
+ */
+
+static int uvc_meta_v4l2_querycap(struct file *file, void *fh,
+				  struct v4l2_capability *cap)
+{
+	struct v4l2_fh *vfh = file->private_data;
+	struct uvc_streaming *stream = video_get_drvdata(vfh->vdev);
+	struct uvc_video_chain *chain = stream->chain;
+
+	strlcpy(cap->driver, "uvcvideo", sizeof(cap->driver));
+	strlcpy(cap->card, vfh->vdev->name, sizeof(cap->card));
+	usb_make_path(stream->dev->udev, cap->bus_info, sizeof(cap->bus_info));
+	cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING
+			  | chain->caps;
+
+	return 0;
+}
+
+static int uvc_meta_v4l2_get_format(struct file *file, void *fh,
+				    struct v4l2_format *format)
+{
+	struct v4l2_fh *vfh = file->private_data;
+	struct uvc_streaming *stream = video_get_drvdata(vfh->vdev);
+	struct v4l2_meta_format *fmt = &format->fmt.meta;
+
+	if (format->type != vfh->vdev->queue->type)
+		return -EINVAL;
+
+	memset(fmt, 0, sizeof(*fmt));
+
+	fmt->dataformat = stream->meta.format;
+	fmt->buffersize = UVC_METATADA_BUF_SIZE;
+
+	return 0;
+}
+
+static int uvc_meta_v4l2_try_format(struct file *file, void *fh,
+				    struct v4l2_format *format)
+{
+	struct v4l2_fh *vfh = file->private_data;
+	struct uvc_streaming *stream = video_get_drvdata(vfh->vdev);
+	struct uvc_device *dev = stream->dev;
+	struct v4l2_meta_format *fmt = &format->fmt.meta;
+	u32 fmeta = fmt->dataformat;
+
+	if (format->type != vfh->vdev->queue->type)
+		return -EINVAL;
+
+	memset(fmt, 0, sizeof(*fmt));
+
+	fmt->dataformat = fmeta == dev->meta_format ? fmeta : V4L2_META_FMT_UVC;
+	fmt->buffersize = UVC_METATADA_BUF_SIZE;
+
+	return 0;
+}
+
+static int uvc_meta_v4l2_set_format(struct file *file, void *fh,
+				    struct v4l2_format *format)
+{
+	struct v4l2_fh *vfh = file->private_data;
+	struct uvc_streaming *stream = video_get_drvdata(vfh->vdev);
+	struct v4l2_meta_format *fmt = &format->fmt.meta;
+	int ret;
+
+	ret = uvc_meta_v4l2_try_format(file, fh, format);
+	if (ret < 0)
+		return ret;
+
+	/*
+	 * We could in principle switch at any time, also during streaming.
+	 * Metadata buffers would still be perfectly parseable, but it's more
+	 * consistent and cleaner to disallow that.
+	 */
+	mutex_lock(&stream->mutex);
+
+	if (uvc_queue_allocated(&stream->queue))
+		ret = -EBUSY;
+	else
+		stream->meta.format = fmt->dataformat;
+
+	mutex_unlock(&stream->mutex);
+
+	return ret;
+}
+
+static int uvc_meta_v4l2_enum_formats(struct file *file, void *fh,
+				      struct v4l2_fmtdesc *fdesc)
+{
+	struct v4l2_fh *vfh = file->private_data;
+	struct uvc_streaming *stream = video_get_drvdata(vfh->vdev);
+	struct uvc_device *dev = stream->dev;
+	u32 index = fdesc->index;
+
+	if (fdesc->type != vfh->vdev->queue->type ||
+	    index > 1U || (index && !dev->meta_format))
+		return -EINVAL;
+
+	memset(fdesc, 0, sizeof(*fdesc));
+
+	fdesc->type = vfh->vdev->queue->type;
+	fdesc->index = index;
+	fdesc->pixelformat = index ? dev->meta_format : V4L2_META_FMT_UVC;
+
+	return 0;
+}
+
+static const struct v4l2_ioctl_ops uvc_meta_ioctl_ops = {
+	.vidioc_querycap		= uvc_meta_v4l2_querycap,
+	.vidioc_g_fmt_meta_cap		= uvc_meta_v4l2_get_format,
+	.vidioc_s_fmt_meta_cap		= uvc_meta_v4l2_set_format,
+	.vidioc_try_fmt_meta_cap	= uvc_meta_v4l2_try_format,
+	.vidioc_enum_fmt_meta_cap	= uvc_meta_v4l2_enum_formats,
+	.vidioc_reqbufs			= vb2_ioctl_reqbufs,
+	.vidioc_querybuf		= vb2_ioctl_querybuf,
+	.vidioc_qbuf			= vb2_ioctl_qbuf,
+	.vidioc_dqbuf			= vb2_ioctl_dqbuf,
+	.vidioc_create_bufs		= vb2_ioctl_create_bufs,
+	.vidioc_prepare_buf		= vb2_ioctl_prepare_buf,
+	.vidioc_streamon		= vb2_ioctl_streamon,
+	.vidioc_streamoff		= vb2_ioctl_streamoff,
+};
+
+/* -----------------------------------------------------------------------------
+ * V4L2 File Operations
+ */
+
+static const struct v4l2_file_operations uvc_meta_fops = {
+	.owner = THIS_MODULE,
+	.unlocked_ioctl = video_ioctl2,
+	.open = v4l2_fh_open,
+	.release = vb2_fop_release,
+	.poll = vb2_fop_poll,
+	.mmap = vb2_fop_mmap,
+};
+
+int uvc_meta_register(struct uvc_streaming *stream)
+{
+	struct uvc_device *dev = stream->dev;
+	struct video_device *vdev = &stream->meta.vdev;
+	struct uvc_video_queue *queue = &stream->meta.queue;
+
+	stream->meta.format = V4L2_META_FMT_UVC;
+
+	/*
+	 * The video interface queue uses manual locking and thus does not set
+	 * the queue pointer. Set it manually here.
+	 */
+	vdev->queue = &queue->queue;
+
+	return uvc_register_video_device(dev, stream, vdev, queue,
+					 V4L2_BUF_TYPE_META_CAPTURE,
+					 &uvc_meta_fops, &uvc_meta_ioctl_ops);
+}
diff --git a/drivers/media/usb/uvc/uvc_queue.c b/drivers/media/usb/uvc/uvc_queue.c
new file mode 100644
index 0000000..fecccb5
--- /dev/null
+++ b/drivers/media/usb/uvc/uvc_queue.c
@@ -0,0 +1,460 @@
+/*
+ *      uvc_queue.c  --  USB Video Class driver - Buffers management
+ *
+ *      Copyright (C) 2005-2010
+ *          Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ *
+ *      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.
+ *
+ */
+
+#include <linux/atomic.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/videodev2.h>
+#include <linux/vmalloc.h>
+#include <linux/wait.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-vmalloc.h>
+
+#include "uvcvideo.h"
+
+/* ------------------------------------------------------------------------
+ * Video buffers queue management.
+ *
+ * Video queues is initialized by uvc_queue_init(). The function performs
+ * basic initialization of the uvc_video_queue struct and never fails.
+ *
+ * Video buffers are managed by videobuf2. The driver uses a mutex to protect
+ * the videobuf2 queue operations by serializing calls to videobuf2 and a
+ * spinlock to protect the IRQ queue that holds the buffers to be processed by
+ * the driver.
+ */
+
+static inline struct uvc_streaming *
+uvc_queue_to_stream(struct uvc_video_queue *queue)
+{
+	return container_of(queue, struct uvc_streaming, queue);
+}
+
+static inline struct uvc_buffer *uvc_vbuf_to_buffer(struct vb2_v4l2_buffer *buf)
+{
+	return container_of(buf, struct uvc_buffer, buf);
+}
+
+/*
+ * Return all queued buffers to videobuf2 in the requested state.
+ *
+ * This function must be called with the queue spinlock held.
+ */
+static void uvc_queue_return_buffers(struct uvc_video_queue *queue,
+			       enum uvc_buffer_state state)
+{
+	enum vb2_buffer_state vb2_state = state == UVC_BUF_STATE_ERROR
+					? VB2_BUF_STATE_ERROR
+					: VB2_BUF_STATE_QUEUED;
+
+	while (!list_empty(&queue->irqqueue)) {
+		struct uvc_buffer *buf = list_first_entry(&queue->irqqueue,
+							  struct uvc_buffer,
+							  queue);
+		list_del(&buf->queue);
+		buf->state = state;
+		vb2_buffer_done(&buf->buf.vb2_buf, vb2_state);
+	}
+}
+
+/* -----------------------------------------------------------------------------
+ * videobuf2 queue operations
+ */
+
+static int uvc_queue_setup(struct vb2_queue *vq,
+			   unsigned int *nbuffers, unsigned int *nplanes,
+			   unsigned int sizes[], struct device *alloc_devs[])
+{
+	struct uvc_video_queue *queue = vb2_get_drv_priv(vq);
+	struct uvc_streaming *stream;
+	unsigned int size;
+
+	switch (vq->type) {
+	case V4L2_BUF_TYPE_META_CAPTURE:
+		size = UVC_METATADA_BUF_SIZE;
+		break;
+
+	default:
+		stream = uvc_queue_to_stream(queue);
+		size = stream->ctrl.dwMaxVideoFrameSize;
+		break;
+	}
+
+	/*
+	 * When called with plane sizes, validate them. The driver supports
+	 * single planar formats only, and requires buffers to be large enough
+	 * to store a complete frame.
+	 */
+	if (*nplanes)
+		return *nplanes != 1 || sizes[0] < size ? -EINVAL : 0;
+
+	*nplanes = 1;
+	sizes[0] = size;
+	return 0;
+}
+
+static int uvc_buffer_prepare(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct uvc_video_queue *queue = vb2_get_drv_priv(vb->vb2_queue);
+	struct uvc_buffer *buf = uvc_vbuf_to_buffer(vbuf);
+
+	if (vb->type == V4L2_BUF_TYPE_VIDEO_OUTPUT &&
+	    vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0)) {
+		uvc_trace(UVC_TRACE_CAPTURE, "[E] Bytes used out of bounds.\n");
+		return -EINVAL;
+	}
+
+	if (unlikely(queue->flags & UVC_QUEUE_DISCONNECTED))
+		return -ENODEV;
+
+	buf->state = UVC_BUF_STATE_QUEUED;
+	buf->error = 0;
+	buf->mem = vb2_plane_vaddr(vb, 0);
+	buf->length = vb2_plane_size(vb, 0);
+	if (vb->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+		buf->bytesused = 0;
+	else
+		buf->bytesused = vb2_get_plane_payload(vb, 0);
+
+	return 0;
+}
+
+static void uvc_buffer_queue(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct uvc_video_queue *queue = vb2_get_drv_priv(vb->vb2_queue);
+	struct uvc_buffer *buf = uvc_vbuf_to_buffer(vbuf);
+	unsigned long flags;
+
+	spin_lock_irqsave(&queue->irqlock, flags);
+	if (likely(!(queue->flags & UVC_QUEUE_DISCONNECTED))) {
+		list_add_tail(&buf->queue, &queue->irqqueue);
+	} else {
+		/* If the device is disconnected return the buffer to userspace
+		 * directly. The next QBUF call will fail with -ENODEV.
+		 */
+		buf->state = UVC_BUF_STATE_ERROR;
+		vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
+	}
+
+	spin_unlock_irqrestore(&queue->irqlock, flags);
+}
+
+static void uvc_buffer_finish(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct uvc_video_queue *queue = vb2_get_drv_priv(vb->vb2_queue);
+	struct uvc_streaming *stream = uvc_queue_to_stream(queue);
+	struct uvc_buffer *buf = uvc_vbuf_to_buffer(vbuf);
+
+	if (vb->state == VB2_BUF_STATE_DONE)
+		uvc_video_clock_update(stream, vbuf, buf);
+}
+
+static int uvc_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+	struct uvc_video_queue *queue = vb2_get_drv_priv(vq);
+	struct uvc_streaming *stream = uvc_queue_to_stream(queue);
+	unsigned long flags;
+	int ret;
+
+	queue->buf_used = 0;
+
+	ret = uvc_video_enable(stream, 1);
+	if (ret == 0)
+		return 0;
+
+	spin_lock_irqsave(&queue->irqlock, flags);
+	uvc_queue_return_buffers(queue, UVC_BUF_STATE_QUEUED);
+	spin_unlock_irqrestore(&queue->irqlock, flags);
+
+	return ret;
+}
+
+static void uvc_stop_streaming(struct vb2_queue *vq)
+{
+	struct uvc_video_queue *queue = vb2_get_drv_priv(vq);
+	unsigned long flags;
+
+	if (vq->type != V4L2_BUF_TYPE_META_CAPTURE)
+		uvc_video_enable(uvc_queue_to_stream(queue), 0);
+
+	spin_lock_irqsave(&queue->irqlock, flags);
+	uvc_queue_return_buffers(queue, UVC_BUF_STATE_ERROR);
+	spin_unlock_irqrestore(&queue->irqlock, flags);
+}
+
+static const struct vb2_ops uvc_queue_qops = {
+	.queue_setup = uvc_queue_setup,
+	.buf_prepare = uvc_buffer_prepare,
+	.buf_queue = uvc_buffer_queue,
+	.buf_finish = uvc_buffer_finish,
+	.wait_prepare = vb2_ops_wait_prepare,
+	.wait_finish = vb2_ops_wait_finish,
+	.start_streaming = uvc_start_streaming,
+	.stop_streaming = uvc_stop_streaming,
+};
+
+static const struct vb2_ops uvc_meta_queue_qops = {
+	.queue_setup = uvc_queue_setup,
+	.buf_prepare = uvc_buffer_prepare,
+	.buf_queue = uvc_buffer_queue,
+	.wait_prepare = vb2_ops_wait_prepare,
+	.wait_finish = vb2_ops_wait_finish,
+	.stop_streaming = uvc_stop_streaming,
+};
+
+int uvc_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type,
+		    int drop_corrupted)
+{
+	int ret;
+
+	queue->queue.type = type;
+	queue->queue.io_modes = VB2_MMAP | VB2_USERPTR;
+	queue->queue.drv_priv = queue;
+	queue->queue.buf_struct_size = sizeof(struct uvc_buffer);
+	queue->queue.mem_ops = &vb2_vmalloc_memops;
+	queue->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC
+		| V4L2_BUF_FLAG_TSTAMP_SRC_SOE;
+	queue->queue.lock = &queue->mutex;
+
+	switch (type) {
+	case V4L2_BUF_TYPE_META_CAPTURE:
+		queue->queue.ops = &uvc_meta_queue_qops;
+		break;
+	default:
+		queue->queue.io_modes |= VB2_DMABUF;
+		queue->queue.ops = &uvc_queue_qops;
+		break;
+	}
+
+	ret = vb2_queue_init(&queue->queue);
+	if (ret)
+		return ret;
+
+	mutex_init(&queue->mutex);
+	spin_lock_init(&queue->irqlock);
+	INIT_LIST_HEAD(&queue->irqqueue);
+	queue->flags = drop_corrupted ? UVC_QUEUE_DROP_CORRUPTED : 0;
+
+	return 0;
+}
+
+void uvc_queue_release(struct uvc_video_queue *queue)
+{
+	mutex_lock(&queue->mutex);
+	vb2_queue_release(&queue->queue);
+	mutex_unlock(&queue->mutex);
+}
+
+/* -----------------------------------------------------------------------------
+ * V4L2 queue operations
+ */
+
+int uvc_request_buffers(struct uvc_video_queue *queue,
+			struct v4l2_requestbuffers *rb)
+{
+	int ret;
+
+	mutex_lock(&queue->mutex);
+	ret = vb2_reqbufs(&queue->queue, rb);
+	mutex_unlock(&queue->mutex);
+
+	return ret ? ret : rb->count;
+}
+
+int uvc_query_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf)
+{
+	int ret;
+
+	mutex_lock(&queue->mutex);
+	ret = vb2_querybuf(&queue->queue, buf);
+	mutex_unlock(&queue->mutex);
+
+	return ret;
+}
+
+int uvc_create_buffers(struct uvc_video_queue *queue,
+		       struct v4l2_create_buffers *cb)
+{
+	int ret;
+
+	mutex_lock(&queue->mutex);
+	ret = vb2_create_bufs(&queue->queue, cb);
+	mutex_unlock(&queue->mutex);
+
+	return ret;
+}
+
+int uvc_queue_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf)
+{
+	int ret;
+
+	mutex_lock(&queue->mutex);
+	ret = vb2_qbuf(&queue->queue, buf);
+	mutex_unlock(&queue->mutex);
+
+	return ret;
+}
+
+int uvc_export_buffer(struct uvc_video_queue *queue,
+		      struct v4l2_exportbuffer *exp)
+{
+	int ret;
+
+	mutex_lock(&queue->mutex);
+	ret = vb2_expbuf(&queue->queue, exp);
+	mutex_unlock(&queue->mutex);
+
+	return ret;
+}
+
+int uvc_dequeue_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf,
+		       int nonblocking)
+{
+	int ret;
+
+	mutex_lock(&queue->mutex);
+	ret = vb2_dqbuf(&queue->queue, buf, nonblocking);
+	mutex_unlock(&queue->mutex);
+
+	return ret;
+}
+
+int uvc_queue_streamon(struct uvc_video_queue *queue, enum v4l2_buf_type type)
+{
+	int ret;
+
+	mutex_lock(&queue->mutex);
+	ret = vb2_streamon(&queue->queue, type);
+	mutex_unlock(&queue->mutex);
+
+	return ret;
+}
+
+int uvc_queue_streamoff(struct uvc_video_queue *queue, enum v4l2_buf_type type)
+{
+	int ret;
+
+	mutex_lock(&queue->mutex);
+	ret = vb2_streamoff(&queue->queue, type);
+	mutex_unlock(&queue->mutex);
+
+	return ret;
+}
+
+int uvc_queue_mmap(struct uvc_video_queue *queue, struct vm_area_struct *vma)
+{
+	return vb2_mmap(&queue->queue, vma);
+}
+
+#ifndef CONFIG_MMU
+unsigned long uvc_queue_get_unmapped_area(struct uvc_video_queue *queue,
+		unsigned long pgoff)
+{
+	return vb2_get_unmapped_area(&queue->queue, 0, 0, pgoff, 0);
+}
+#endif
+
+__poll_t uvc_queue_poll(struct uvc_video_queue *queue, struct file *file,
+			    poll_table *wait)
+{
+	__poll_t ret;
+
+	mutex_lock(&queue->mutex);
+	ret = vb2_poll(&queue->queue, file, wait);
+	mutex_unlock(&queue->mutex);
+
+	return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ *
+ */
+
+/*
+ * Check if buffers have been allocated.
+ */
+int uvc_queue_allocated(struct uvc_video_queue *queue)
+{
+	int allocated;
+
+	mutex_lock(&queue->mutex);
+	allocated = vb2_is_busy(&queue->queue);
+	mutex_unlock(&queue->mutex);
+
+	return allocated;
+}
+
+/*
+ * Cancel the video buffers queue.
+ *
+ * Cancelling the queue marks all buffers on the irq queue as erroneous,
+ * wakes them up and removes them from the queue.
+ *
+ * If the disconnect parameter is set, further calls to uvc_queue_buffer will
+ * fail with -ENODEV.
+ *
+ * This function acquires the irq spinlock and can be called from interrupt
+ * context.
+ */
+void uvc_queue_cancel(struct uvc_video_queue *queue, int disconnect)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&queue->irqlock, flags);
+	uvc_queue_return_buffers(queue, UVC_BUF_STATE_ERROR);
+	/* This must be protected by the irqlock spinlock to avoid race
+	 * conditions between uvc_buffer_queue and the disconnection event that
+	 * could result in an interruptible wait in uvc_dequeue_buffer. Do not
+	 * blindly replace this logic by checking for the UVC_QUEUE_DISCONNECTED
+	 * state outside the queue code.
+	 */
+	if (disconnect)
+		queue->flags |= UVC_QUEUE_DISCONNECTED;
+	spin_unlock_irqrestore(&queue->irqlock, flags);
+}
+
+struct uvc_buffer *uvc_queue_next_buffer(struct uvc_video_queue *queue,
+		struct uvc_buffer *buf)
+{
+	struct uvc_buffer *nextbuf;
+	unsigned long flags;
+
+	if ((queue->flags & UVC_QUEUE_DROP_CORRUPTED) && buf->error) {
+		buf->error = 0;
+		buf->state = UVC_BUF_STATE_QUEUED;
+		buf->bytesused = 0;
+		vb2_set_plane_payload(&buf->buf.vb2_buf, 0, 0);
+		return buf;
+	}
+
+	spin_lock_irqsave(&queue->irqlock, flags);
+	list_del(&buf->queue);
+	if (!list_empty(&queue->irqqueue))
+		nextbuf = list_first_entry(&queue->irqqueue, struct uvc_buffer,
+					   queue);
+	else
+		nextbuf = NULL;
+	spin_unlock_irqrestore(&queue->irqlock, flags);
+
+	buf->state = buf->error ? UVC_BUF_STATE_ERROR : UVC_BUF_STATE_DONE;
+	vb2_set_plane_payload(&buf->buf.vb2_buf, 0, buf->bytesused);
+	vb2_buffer_done(&buf->buf.vb2_buf, VB2_BUF_STATE_DONE);
+
+	return nextbuf;
+}
diff --git a/drivers/media/usb/uvc/uvc_status.c b/drivers/media/usb/uvc/uvc_status.c
new file mode 100644
index 0000000..0722dc6
--- /dev/null
+++ b/drivers/media/usb/uvc/uvc_status.c
@@ -0,0 +1,314 @@
+/*
+ *      uvc_status.c  --  USB Video Class driver - Status endpoint
+ *
+ *      Copyright (C) 2005-2009
+ *          Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ *
+ *      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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/input.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/input.h>
+
+#include "uvcvideo.h"
+
+/* --------------------------------------------------------------------------
+ * Input device
+ */
+#ifdef CONFIG_USB_VIDEO_CLASS_INPUT_EVDEV
+static int uvc_input_init(struct uvc_device *dev)
+{
+	struct input_dev *input;
+	int ret;
+
+	input = input_allocate_device();
+	if (input == NULL)
+		return -ENOMEM;
+
+	usb_make_path(dev->udev, dev->input_phys, sizeof(dev->input_phys));
+	strlcat(dev->input_phys, "/button", sizeof(dev->input_phys));
+
+	input->name = dev->name;
+	input->phys = dev->input_phys;
+	usb_to_input_id(dev->udev, &input->id);
+	input->dev.parent = &dev->intf->dev;
+
+	__set_bit(EV_KEY, input->evbit);
+	__set_bit(KEY_CAMERA, input->keybit);
+
+	if ((ret = input_register_device(input)) < 0)
+		goto error;
+
+	dev->input = input;
+	return 0;
+
+error:
+	input_free_device(input);
+	return ret;
+}
+
+static void uvc_input_cleanup(struct uvc_device *dev)
+{
+	if (dev->input)
+		input_unregister_device(dev->input);
+}
+
+static void uvc_input_report_key(struct uvc_device *dev, unsigned int code,
+	int value)
+{
+	if (dev->input) {
+		input_report_key(dev->input, code, value);
+		input_sync(dev->input);
+	}
+}
+
+#else
+#define uvc_input_init(dev)
+#define uvc_input_cleanup(dev)
+#define uvc_input_report_key(dev, code, value)
+#endif /* CONFIG_USB_VIDEO_CLASS_INPUT_EVDEV */
+
+/* --------------------------------------------------------------------------
+ * Status interrupt endpoint
+ */
+struct uvc_streaming_status {
+	u8	bStatusType;
+	u8	bOriginator;
+	u8	bEvent;
+	u8	bValue[];
+} __packed;
+
+struct uvc_control_status {
+	u8	bStatusType;
+	u8	bOriginator;
+	u8	bEvent;
+	u8	bSelector;
+	u8	bAttribute;
+	u8	bValue[];
+} __packed;
+
+static void uvc_event_streaming(struct uvc_device *dev,
+				struct uvc_streaming_status *status, int len)
+{
+	if (len < 3) {
+		uvc_trace(UVC_TRACE_STATUS, "Invalid streaming status event "
+				"received.\n");
+		return;
+	}
+
+	if (status->bEvent == 0) {
+		if (len < 4)
+			return;
+		uvc_trace(UVC_TRACE_STATUS, "Button (intf %u) %s len %d\n",
+			  status->bOriginator,
+			  status->bValue[0] ? "pressed" : "released", len);
+		uvc_input_report_key(dev, KEY_CAMERA, status->bValue[0]);
+	} else {
+		uvc_trace(UVC_TRACE_STATUS,
+			  "Stream %u error event %02x len %d.\n",
+			  status->bOriginator, status->bEvent, len);
+	}
+}
+
+#define UVC_CTRL_VALUE_CHANGE	0
+#define UVC_CTRL_INFO_CHANGE	1
+#define UVC_CTRL_FAILURE_CHANGE	2
+#define UVC_CTRL_MIN_CHANGE	3
+#define UVC_CTRL_MAX_CHANGE	4
+
+static struct uvc_control *uvc_event_entity_find_ctrl(struct uvc_entity *entity,
+						      u8 selector)
+{
+	struct uvc_control *ctrl;
+	unsigned int i;
+
+	for (i = 0, ctrl = entity->controls; i < entity->ncontrols; i++, ctrl++)
+		if (ctrl->info.selector == selector)
+			return ctrl;
+
+	return NULL;
+}
+
+static struct uvc_control *uvc_event_find_ctrl(struct uvc_device *dev,
+					const struct uvc_control_status *status,
+					struct uvc_video_chain **chain)
+{
+	list_for_each_entry((*chain), &dev->chains, list) {
+		struct uvc_entity *entity;
+		struct uvc_control *ctrl;
+
+		list_for_each_entry(entity, &(*chain)->entities, chain) {
+			if (entity->id != status->bOriginator)
+				continue;
+
+			ctrl = uvc_event_entity_find_ctrl(entity,
+							  status->bSelector);
+			if (ctrl)
+				return ctrl;
+		}
+	}
+
+	return NULL;
+}
+
+static bool uvc_event_control(struct urb *urb,
+			      const struct uvc_control_status *status, int len)
+{
+	static const char *attrs[] = { "value", "info", "failure", "min", "max" };
+	struct uvc_device *dev = urb->context;
+	struct uvc_video_chain *chain;
+	struct uvc_control *ctrl;
+
+	if (len < 6 || status->bEvent != 0 ||
+	    status->bAttribute >= ARRAY_SIZE(attrs)) {
+		uvc_trace(UVC_TRACE_STATUS, "Invalid control status event "
+				"received.\n");
+		return false;
+	}
+
+	uvc_trace(UVC_TRACE_STATUS, "Control %u/%u %s change len %d.\n",
+		  status->bOriginator, status->bSelector,
+		  attrs[status->bAttribute], len);
+
+	/* Find the control. */
+	ctrl = uvc_event_find_ctrl(dev, status, &chain);
+	if (!ctrl)
+		return false;
+
+	switch (status->bAttribute) {
+	case UVC_CTRL_VALUE_CHANGE:
+		return uvc_ctrl_status_event(urb, chain, ctrl, status->bValue);
+
+	case UVC_CTRL_INFO_CHANGE:
+	case UVC_CTRL_FAILURE_CHANGE:
+	case UVC_CTRL_MIN_CHANGE:
+	case UVC_CTRL_MAX_CHANGE:
+		break;
+	}
+
+	return false;
+}
+
+static void uvc_status_complete(struct urb *urb)
+{
+	struct uvc_device *dev = urb->context;
+	int len, ret;
+
+	switch (urb->status) {
+	case 0:
+		break;
+
+	case -ENOENT:		/* usb_kill_urb() called. */
+	case -ECONNRESET:	/* usb_unlink_urb() called. */
+	case -ESHUTDOWN:	/* The endpoint is being disabled. */
+	case -EPROTO:		/* Device is disconnected (reported by some
+				 * host controller). */
+		return;
+
+	default:
+		uvc_printk(KERN_WARNING, "Non-zero status (%d) in status "
+			"completion handler.\n", urb->status);
+		return;
+	}
+
+	len = urb->actual_length;
+	if (len > 0) {
+		switch (dev->status[0] & 0x0f) {
+		case UVC_STATUS_TYPE_CONTROL: {
+			struct uvc_control_status *status =
+				(struct uvc_control_status *)dev->status;
+
+			if (uvc_event_control(urb, status, len))
+				/* The URB will be resubmitted in work context. */
+				return;
+			break;
+		}
+
+		case UVC_STATUS_TYPE_STREAMING: {
+			struct uvc_streaming_status *status =
+				(struct uvc_streaming_status *)dev->status;
+
+			uvc_event_streaming(dev, status, len);
+			break;
+		}
+
+		default:
+			uvc_trace(UVC_TRACE_STATUS, "Unknown status event "
+				"type %u.\n", dev->status[0]);
+			break;
+		}
+	}
+
+	/* Resubmit the URB. */
+	urb->interval = dev->int_ep->desc.bInterval;
+	if ((ret = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
+		uvc_printk(KERN_ERR, "Failed to resubmit status URB (%d).\n",
+			ret);
+	}
+}
+
+int uvc_status_init(struct uvc_device *dev)
+{
+	struct usb_host_endpoint *ep = dev->int_ep;
+	unsigned int pipe;
+	int interval;
+
+	if (ep == NULL)
+		return 0;
+
+	uvc_input_init(dev);
+
+	dev->status = kzalloc(UVC_MAX_STATUS_SIZE, GFP_KERNEL);
+	if (dev->status == NULL)
+		return -ENOMEM;
+
+	dev->int_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (dev->int_urb == NULL) {
+		kfree(dev->status);
+		return -ENOMEM;
+	}
+
+	pipe = usb_rcvintpipe(dev->udev, ep->desc.bEndpointAddress);
+
+	/* For high-speed interrupt endpoints, the bInterval value is used as
+	 * an exponent of two. Some developers forgot about it.
+	 */
+	interval = ep->desc.bInterval;
+	if (interval > 16 && dev->udev->speed == USB_SPEED_HIGH &&
+	    (dev->quirks & UVC_QUIRK_STATUS_INTERVAL))
+		interval = fls(interval) - 1;
+
+	usb_fill_int_urb(dev->int_urb, dev->udev, pipe,
+		dev->status, UVC_MAX_STATUS_SIZE, uvc_status_complete,
+		dev, interval);
+
+	return 0;
+}
+
+void uvc_status_cleanup(struct uvc_device *dev)
+{
+	usb_kill_urb(dev->int_urb);
+	usb_free_urb(dev->int_urb);
+	kfree(dev->status);
+	uvc_input_cleanup(dev);
+}
+
+int uvc_status_start(struct uvc_device *dev, gfp_t flags)
+{
+	if (dev->int_urb == NULL)
+		return 0;
+
+	return usb_submit_urb(dev->int_urb, flags);
+}
+
+void uvc_status_stop(struct uvc_device *dev)
+{
+	usb_kill_urb(dev->int_urb);
+}
diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c
new file mode 100644
index 0000000..18a7384
--- /dev/null
+++ b/drivers/media/usb/uvc/uvc_v4l2.c
@@ -0,0 +1,1528 @@
+/*
+ *      uvc_v4l2.c  --  USB Video Class driver - V4L2 API
+ *
+ *      Copyright (C) 2005-2010
+ *          Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ *
+ *      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.
+ *
+ */
+
+#include <linux/compat.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/videodev2.h>
+#include <linux/vmalloc.h>
+#include <linux/mm.h>
+#include <linux/wait.h>
+#include <linux/atomic.h>
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ioctl.h>
+
+#include "uvcvideo.h"
+
+/* ------------------------------------------------------------------------
+ * UVC ioctls
+ */
+static int uvc_ioctl_ctrl_map(struct uvc_video_chain *chain,
+	struct uvc_xu_control_mapping *xmap)
+{
+	struct uvc_control_mapping *map;
+	unsigned int size;
+	int ret;
+
+	map = kzalloc(sizeof(*map), GFP_KERNEL);
+	if (map == NULL)
+		return -ENOMEM;
+
+	map->id = xmap->id;
+	memcpy(map->name, xmap->name, sizeof(map->name));
+	memcpy(map->entity, xmap->entity, sizeof(map->entity));
+	map->selector = xmap->selector;
+	map->size = xmap->size;
+	map->offset = xmap->offset;
+	map->v4l2_type = xmap->v4l2_type;
+	map->data_type = xmap->data_type;
+
+	switch (xmap->v4l2_type) {
+	case V4L2_CTRL_TYPE_INTEGER:
+	case V4L2_CTRL_TYPE_BOOLEAN:
+	case V4L2_CTRL_TYPE_BUTTON:
+		break;
+
+	case V4L2_CTRL_TYPE_MENU:
+		/* Prevent excessive memory consumption, as well as integer
+		 * overflows.
+		 */
+		if (xmap->menu_count == 0 ||
+		    xmap->menu_count > UVC_MAX_CONTROL_MENU_ENTRIES) {
+			ret = -EINVAL;
+			goto free_map;
+		}
+
+		size = xmap->menu_count * sizeof(*map->menu_info);
+		map->menu_info = memdup_user(xmap->menu_info, size);
+		if (IS_ERR(map->menu_info)) {
+			ret = PTR_ERR(map->menu_info);
+			goto free_map;
+		}
+
+		map->menu_count = xmap->menu_count;
+		break;
+
+	default:
+		uvc_trace(UVC_TRACE_CONTROL, "Unsupported V4L2 control type "
+			  "%u.\n", xmap->v4l2_type);
+		ret = -ENOTTY;
+		goto free_map;
+	}
+
+	ret = uvc_ctrl_add_mapping(chain, map);
+
+	kfree(map->menu_info);
+free_map:
+	kfree(map);
+
+	return ret;
+}
+
+/* ------------------------------------------------------------------------
+ * V4L2 interface
+ */
+
+/*
+ * Find the frame interval closest to the requested frame interval for the
+ * given frame format and size. This should be done by the device as part of
+ * the Video Probe and Commit negotiation, but some hardware don't implement
+ * that feature.
+ */
+static u32 uvc_try_frame_interval(struct uvc_frame *frame, u32 interval)
+{
+	unsigned int i;
+
+	if (frame->bFrameIntervalType) {
+		u32 best = -1, dist;
+
+		for (i = 0; i < frame->bFrameIntervalType; ++i) {
+			dist = interval > frame->dwFrameInterval[i]
+			     ? interval - frame->dwFrameInterval[i]
+			     : frame->dwFrameInterval[i] - interval;
+
+			if (dist > best)
+				break;
+
+			best = dist;
+		}
+
+		interval = frame->dwFrameInterval[i-1];
+	} else {
+		const u32 min = frame->dwFrameInterval[0];
+		const u32 max = frame->dwFrameInterval[1];
+		const u32 step = frame->dwFrameInterval[2];
+
+		interval = min + (interval - min + step/2) / step * step;
+		if (interval > max)
+			interval = max;
+	}
+
+	return interval;
+}
+
+static u32 uvc_v4l2_get_bytesperline(const struct uvc_format *format,
+	const struct uvc_frame *frame)
+{
+	switch (format->fcc) {
+	case V4L2_PIX_FMT_NV12:
+	case V4L2_PIX_FMT_YVU420:
+	case V4L2_PIX_FMT_YUV420:
+	case V4L2_PIX_FMT_M420:
+		return frame->wWidth;
+
+	default:
+		return format->bpp * frame->wWidth / 8;
+	}
+}
+
+static int uvc_v4l2_try_format(struct uvc_streaming *stream,
+	struct v4l2_format *fmt, struct uvc_streaming_control *probe,
+	struct uvc_format **uvc_format, struct uvc_frame **uvc_frame)
+{
+	struct uvc_format *format = NULL;
+	struct uvc_frame *frame = NULL;
+	u16 rw, rh;
+	unsigned int d, maxd;
+	unsigned int i;
+	u32 interval;
+	int ret = 0;
+	u8 *fcc;
+
+	if (fmt->type != stream->type)
+		return -EINVAL;
+
+	fcc = (u8 *)&fmt->fmt.pix.pixelformat;
+	uvc_trace(UVC_TRACE_FORMAT, "Trying format 0x%08x (%c%c%c%c): %ux%u.\n",
+			fmt->fmt.pix.pixelformat,
+			fcc[0], fcc[1], fcc[2], fcc[3],
+			fmt->fmt.pix.width, fmt->fmt.pix.height);
+
+	/* Check if the hardware supports the requested format, use the default
+	 * format otherwise.
+	 */
+	for (i = 0; i < stream->nformats; ++i) {
+		format = &stream->format[i];
+		if (format->fcc == fmt->fmt.pix.pixelformat)
+			break;
+	}
+
+	if (i == stream->nformats) {
+		format = stream->def_format;
+		fmt->fmt.pix.pixelformat = format->fcc;
+	}
+
+	/* Find the closest image size. The distance between image sizes is
+	 * the size in pixels of the non-overlapping regions between the
+	 * requested size and the frame-specified size.
+	 */
+	rw = fmt->fmt.pix.width;
+	rh = fmt->fmt.pix.height;
+	maxd = (unsigned int)-1;
+
+	for (i = 0; i < format->nframes; ++i) {
+		u16 w = format->frame[i].wWidth;
+		u16 h = format->frame[i].wHeight;
+
+		d = min(w, rw) * min(h, rh);
+		d = w*h + rw*rh - 2*d;
+		if (d < maxd) {
+			maxd = d;
+			frame = &format->frame[i];
+		}
+
+		if (maxd == 0)
+			break;
+	}
+
+	if (frame == NULL) {
+		uvc_trace(UVC_TRACE_FORMAT, "Unsupported size %ux%u.\n",
+				fmt->fmt.pix.width, fmt->fmt.pix.height);
+		return -EINVAL;
+	}
+
+	/* Use the default frame interval. */
+	interval = frame->dwDefaultFrameInterval;
+	uvc_trace(UVC_TRACE_FORMAT, "Using default frame interval %u.%u us "
+		"(%u.%u fps).\n", interval/10, interval%10, 10000000/interval,
+		(100000000/interval)%10);
+
+	/* Set the format index, frame index and frame interval. */
+	memset(probe, 0, sizeof(*probe));
+	probe->bmHint = 1;	/* dwFrameInterval */
+	probe->bFormatIndex = format->index;
+	probe->bFrameIndex = frame->bFrameIndex;
+	probe->dwFrameInterval = uvc_try_frame_interval(frame, interval);
+	/* Some webcams stall the probe control set request when the
+	 * dwMaxVideoFrameSize field is set to zero. The UVC specification
+	 * clearly states that the field is read-only from the host, so this
+	 * is a webcam bug. Set dwMaxVideoFrameSize to the value reported by
+	 * the webcam to work around the problem.
+	 *
+	 * The workaround could probably be enabled for all webcams, so the
+	 * quirk can be removed if needed. It's currently useful to detect
+	 * webcam bugs and fix them before they hit the market (providing
+	 * developers test their webcams with the Linux driver as well as with
+	 * the Windows driver).
+	 */
+	mutex_lock(&stream->mutex);
+	if (stream->dev->quirks & UVC_QUIRK_PROBE_EXTRAFIELDS)
+		probe->dwMaxVideoFrameSize =
+			stream->ctrl.dwMaxVideoFrameSize;
+
+	/* Probe the device. */
+	ret = uvc_probe_video(stream, probe);
+	mutex_unlock(&stream->mutex);
+	if (ret < 0)
+		goto done;
+
+	fmt->fmt.pix.width = frame->wWidth;
+	fmt->fmt.pix.height = frame->wHeight;
+	fmt->fmt.pix.field = V4L2_FIELD_NONE;
+	fmt->fmt.pix.bytesperline = uvc_v4l2_get_bytesperline(format, frame);
+	fmt->fmt.pix.sizeimage = probe->dwMaxVideoFrameSize;
+	fmt->fmt.pix.colorspace = format->colorspace;
+	fmt->fmt.pix.priv = 0;
+
+	if (uvc_format != NULL)
+		*uvc_format = format;
+	if (uvc_frame != NULL)
+		*uvc_frame = frame;
+
+done:
+	return ret;
+}
+
+static int uvc_v4l2_get_format(struct uvc_streaming *stream,
+	struct v4l2_format *fmt)
+{
+	struct uvc_format *format;
+	struct uvc_frame *frame;
+	int ret = 0;
+
+	if (fmt->type != stream->type)
+		return -EINVAL;
+
+	mutex_lock(&stream->mutex);
+	format = stream->cur_format;
+	frame = stream->cur_frame;
+
+	if (format == NULL || frame == NULL) {
+		ret = -EINVAL;
+		goto done;
+	}
+
+	fmt->fmt.pix.pixelformat = format->fcc;
+	fmt->fmt.pix.width = frame->wWidth;
+	fmt->fmt.pix.height = frame->wHeight;
+	fmt->fmt.pix.field = V4L2_FIELD_NONE;
+	fmt->fmt.pix.bytesperline = uvc_v4l2_get_bytesperline(format, frame);
+	fmt->fmt.pix.sizeimage = stream->ctrl.dwMaxVideoFrameSize;
+	fmt->fmt.pix.colorspace = format->colorspace;
+	fmt->fmt.pix.priv = 0;
+
+done:
+	mutex_unlock(&stream->mutex);
+	return ret;
+}
+
+static int uvc_v4l2_set_format(struct uvc_streaming *stream,
+	struct v4l2_format *fmt)
+{
+	struct uvc_streaming_control probe;
+	struct uvc_format *format;
+	struct uvc_frame *frame;
+	int ret;
+
+	if (fmt->type != stream->type)
+		return -EINVAL;
+
+	ret = uvc_v4l2_try_format(stream, fmt, &probe, &format, &frame);
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&stream->mutex);
+
+	if (uvc_queue_allocated(&stream->queue)) {
+		ret = -EBUSY;
+		goto done;
+	}
+
+	stream->ctrl = probe;
+	stream->cur_format = format;
+	stream->cur_frame = frame;
+
+done:
+	mutex_unlock(&stream->mutex);
+	return ret;
+}
+
+static int uvc_v4l2_get_streamparm(struct uvc_streaming *stream,
+		struct v4l2_streamparm *parm)
+{
+	u32 numerator, denominator;
+
+	if (parm->type != stream->type)
+		return -EINVAL;
+
+	mutex_lock(&stream->mutex);
+	numerator = stream->ctrl.dwFrameInterval;
+	mutex_unlock(&stream->mutex);
+
+	denominator = 10000000;
+	uvc_simplify_fraction(&numerator, &denominator, 8, 333);
+
+	memset(parm, 0, sizeof(*parm));
+	parm->type = stream->type;
+
+	if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+		parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
+		parm->parm.capture.capturemode = 0;
+		parm->parm.capture.timeperframe.numerator = numerator;
+		parm->parm.capture.timeperframe.denominator = denominator;
+		parm->parm.capture.extendedmode = 0;
+		parm->parm.capture.readbuffers = 0;
+	} else {
+		parm->parm.output.capability = V4L2_CAP_TIMEPERFRAME;
+		parm->parm.output.outputmode = 0;
+		parm->parm.output.timeperframe.numerator = numerator;
+		parm->parm.output.timeperframe.denominator = denominator;
+	}
+
+	return 0;
+}
+
+static int uvc_v4l2_set_streamparm(struct uvc_streaming *stream,
+		struct v4l2_streamparm *parm)
+{
+	struct uvc_streaming_control probe;
+	struct v4l2_fract timeperframe;
+	struct uvc_format *format;
+	struct uvc_frame *frame;
+	u32 interval, maxd;
+	unsigned int i;
+	int ret;
+
+	if (parm->type != stream->type)
+		return -EINVAL;
+
+	if (parm->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		timeperframe = parm->parm.capture.timeperframe;
+	else
+		timeperframe = parm->parm.output.timeperframe;
+
+	interval = uvc_fraction_to_interval(timeperframe.numerator,
+		timeperframe.denominator);
+	uvc_trace(UVC_TRACE_FORMAT, "Setting frame interval to %u/%u (%u).\n",
+		timeperframe.numerator, timeperframe.denominator, interval);
+
+	mutex_lock(&stream->mutex);
+
+	if (uvc_queue_streaming(&stream->queue)) {
+		mutex_unlock(&stream->mutex);
+		return -EBUSY;
+	}
+
+	format = stream->cur_format;
+	frame = stream->cur_frame;
+	probe = stream->ctrl;
+	probe.dwFrameInterval = uvc_try_frame_interval(frame, interval);
+	maxd = abs((s32)probe.dwFrameInterval - interval);
+
+	/* Try frames with matching size to find the best frame interval. */
+	for (i = 0; i < format->nframes && maxd != 0; i++) {
+		u32 d, ival;
+
+		if (&format->frame[i] == stream->cur_frame)
+			continue;
+
+		if (format->frame[i].wWidth != stream->cur_frame->wWidth ||
+		    format->frame[i].wHeight != stream->cur_frame->wHeight)
+			continue;
+
+		ival = uvc_try_frame_interval(&format->frame[i], interval);
+		d = abs((s32)ival - interval);
+		if (d >= maxd)
+			continue;
+
+		frame = &format->frame[i];
+		probe.bFrameIndex = frame->bFrameIndex;
+		probe.dwFrameInterval = ival;
+		maxd = d;
+	}
+
+	/* Probe the device with the new settings. */
+	ret = uvc_probe_video(stream, &probe);
+	if (ret < 0) {
+		mutex_unlock(&stream->mutex);
+		return ret;
+	}
+
+	stream->ctrl = probe;
+	stream->cur_frame = frame;
+	mutex_unlock(&stream->mutex);
+
+	/* Return the actual frame period. */
+	timeperframe.numerator = probe.dwFrameInterval;
+	timeperframe.denominator = 10000000;
+	uvc_simplify_fraction(&timeperframe.numerator,
+		&timeperframe.denominator, 8, 333);
+
+	if (parm->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		parm->parm.capture.timeperframe = timeperframe;
+	else
+		parm->parm.output.timeperframe = timeperframe;
+
+	return 0;
+}
+
+/* ------------------------------------------------------------------------
+ * Privilege management
+ */
+
+/*
+ * Privilege management is the multiple-open implementation basis. The current
+ * implementation is completely transparent for the end-user and doesn't
+ * require explicit use of the VIDIOC_G_PRIORITY and VIDIOC_S_PRIORITY ioctls.
+ * Those ioctls enable finer control on the device (by making possible for a
+ * user to request exclusive access to a device), but are not mature yet.
+ * Switching to the V4L2 priority mechanism might be considered in the future
+ * if this situation changes.
+ *
+ * Each open instance of a UVC device can either be in a privileged or
+ * unprivileged state. Only a single instance can be in a privileged state at
+ * a given time. Trying to perform an operation that requires privileges will
+ * automatically acquire the required privileges if possible, or return -EBUSY
+ * otherwise. Privileges are dismissed when closing the instance or when
+ * freeing the video buffers using VIDIOC_REQBUFS.
+ *
+ * Operations that require privileges are:
+ *
+ * - VIDIOC_S_INPUT
+ * - VIDIOC_S_PARM
+ * - VIDIOC_S_FMT
+ * - VIDIOC_REQBUFS
+ */
+static int uvc_acquire_privileges(struct uvc_fh *handle)
+{
+	/* Always succeed if the handle is already privileged. */
+	if (handle->state == UVC_HANDLE_ACTIVE)
+		return 0;
+
+	/* Check if the device already has a privileged handle. */
+	if (atomic_inc_return(&handle->stream->active) != 1) {
+		atomic_dec(&handle->stream->active);
+		return -EBUSY;
+	}
+
+	handle->state = UVC_HANDLE_ACTIVE;
+	return 0;
+}
+
+static void uvc_dismiss_privileges(struct uvc_fh *handle)
+{
+	if (handle->state == UVC_HANDLE_ACTIVE)
+		atomic_dec(&handle->stream->active);
+
+	handle->state = UVC_HANDLE_PASSIVE;
+}
+
+static int uvc_has_privileges(struct uvc_fh *handle)
+{
+	return handle->state == UVC_HANDLE_ACTIVE;
+}
+
+/* ------------------------------------------------------------------------
+ * V4L2 file operations
+ */
+
+static int uvc_v4l2_open(struct file *file)
+{
+	struct uvc_streaming *stream;
+	struct uvc_fh *handle;
+	int ret = 0;
+
+	uvc_trace(UVC_TRACE_CALLS, "uvc_v4l2_open\n");
+	stream = video_drvdata(file);
+
+	ret = usb_autopm_get_interface(stream->dev->intf);
+	if (ret < 0)
+		return ret;
+
+	/* Create the device handle. */
+	handle = kzalloc(sizeof(*handle), GFP_KERNEL);
+	if (handle == NULL) {
+		usb_autopm_put_interface(stream->dev->intf);
+		return -ENOMEM;
+	}
+
+	mutex_lock(&stream->dev->lock);
+	if (stream->dev->users == 0) {
+		ret = uvc_status_start(stream->dev, GFP_KERNEL);
+		if (ret < 0) {
+			mutex_unlock(&stream->dev->lock);
+			usb_autopm_put_interface(stream->dev->intf);
+			kfree(handle);
+			return ret;
+		}
+	}
+
+	stream->dev->users++;
+	mutex_unlock(&stream->dev->lock);
+
+	v4l2_fh_init(&handle->vfh, &stream->vdev);
+	v4l2_fh_add(&handle->vfh);
+	handle->chain = stream->chain;
+	handle->stream = stream;
+	handle->state = UVC_HANDLE_PASSIVE;
+	file->private_data = handle;
+
+	return 0;
+}
+
+static int uvc_v4l2_release(struct file *file)
+{
+	struct uvc_fh *handle = file->private_data;
+	struct uvc_streaming *stream = handle->stream;
+
+	uvc_trace(UVC_TRACE_CALLS, "uvc_v4l2_release\n");
+
+	/* Only free resources if this is a privileged handle. */
+	if (uvc_has_privileges(handle))
+		uvc_queue_release(&stream->queue);
+
+	/* Release the file handle. */
+	uvc_dismiss_privileges(handle);
+	v4l2_fh_del(&handle->vfh);
+	v4l2_fh_exit(&handle->vfh);
+	kfree(handle);
+	file->private_data = NULL;
+
+	mutex_lock(&stream->dev->lock);
+	if (--stream->dev->users == 0)
+		uvc_status_stop(stream->dev);
+	mutex_unlock(&stream->dev->lock);
+
+	usb_autopm_put_interface(stream->dev->intf);
+	return 0;
+}
+
+static int uvc_ioctl_querycap(struct file *file, void *fh,
+			      struct v4l2_capability *cap)
+{
+	struct video_device *vdev = video_devdata(file);
+	struct uvc_fh *handle = file->private_data;
+	struct uvc_video_chain *chain = handle->chain;
+	struct uvc_streaming *stream = handle->stream;
+
+	strlcpy(cap->driver, "uvcvideo", sizeof(cap->driver));
+	strlcpy(cap->card, vdev->name, sizeof(cap->card));
+	usb_make_path(stream->dev->udev, cap->bus_info, sizeof(cap->bus_info));
+	cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING
+			  | chain->caps;
+
+	return 0;
+}
+
+static int uvc_ioctl_enum_fmt(struct uvc_streaming *stream,
+			      struct v4l2_fmtdesc *fmt)
+{
+	struct uvc_format *format;
+	enum v4l2_buf_type type = fmt->type;
+	u32 index = fmt->index;
+
+	if (fmt->type != stream->type || fmt->index >= stream->nformats)
+		return -EINVAL;
+
+	memset(fmt, 0, sizeof(*fmt));
+	fmt->index = index;
+	fmt->type = type;
+
+	format = &stream->format[fmt->index];
+	fmt->flags = 0;
+	if (format->flags & UVC_FMT_FLAG_COMPRESSED)
+		fmt->flags |= V4L2_FMT_FLAG_COMPRESSED;
+	strlcpy(fmt->description, format->name, sizeof(fmt->description));
+	fmt->description[sizeof(fmt->description) - 1] = 0;
+	fmt->pixelformat = format->fcc;
+	return 0;
+}
+
+static int uvc_ioctl_enum_fmt_vid_cap(struct file *file, void *fh,
+				      struct v4l2_fmtdesc *fmt)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_streaming *stream = handle->stream;
+
+	return uvc_ioctl_enum_fmt(stream, fmt);
+}
+
+static int uvc_ioctl_enum_fmt_vid_out(struct file *file, void *fh,
+				      struct v4l2_fmtdesc *fmt)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_streaming *stream = handle->stream;
+
+	return uvc_ioctl_enum_fmt(stream, fmt);
+}
+
+static int uvc_ioctl_g_fmt_vid_cap(struct file *file, void *fh,
+				   struct v4l2_format *fmt)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_streaming *stream = handle->stream;
+
+	return uvc_v4l2_get_format(stream, fmt);
+}
+
+static int uvc_ioctl_g_fmt_vid_out(struct file *file, void *fh,
+				   struct v4l2_format *fmt)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_streaming *stream = handle->stream;
+
+	return uvc_v4l2_get_format(stream, fmt);
+}
+
+static int uvc_ioctl_s_fmt_vid_cap(struct file *file, void *fh,
+				   struct v4l2_format *fmt)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_streaming *stream = handle->stream;
+	int ret;
+
+	ret = uvc_acquire_privileges(handle);
+	if (ret < 0)
+		return ret;
+
+	return uvc_v4l2_set_format(stream, fmt);
+}
+
+static int uvc_ioctl_s_fmt_vid_out(struct file *file, void *fh,
+				   struct v4l2_format *fmt)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_streaming *stream = handle->stream;
+	int ret;
+
+	ret = uvc_acquire_privileges(handle);
+	if (ret < 0)
+		return ret;
+
+	return uvc_v4l2_set_format(stream, fmt);
+}
+
+static int uvc_ioctl_try_fmt_vid_cap(struct file *file, void *fh,
+				     struct v4l2_format *fmt)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_streaming *stream = handle->stream;
+	struct uvc_streaming_control probe;
+
+	return uvc_v4l2_try_format(stream, fmt, &probe, NULL, NULL);
+}
+
+static int uvc_ioctl_try_fmt_vid_out(struct file *file, void *fh,
+				     struct v4l2_format *fmt)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_streaming *stream = handle->stream;
+	struct uvc_streaming_control probe;
+
+	return uvc_v4l2_try_format(stream, fmt, &probe, NULL, NULL);
+}
+
+static int uvc_ioctl_reqbufs(struct file *file, void *fh,
+			     struct v4l2_requestbuffers *rb)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_streaming *stream = handle->stream;
+	int ret;
+
+	ret = uvc_acquire_privileges(handle);
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&stream->mutex);
+	ret = uvc_request_buffers(&stream->queue, rb);
+	mutex_unlock(&stream->mutex);
+	if (ret < 0)
+		return ret;
+
+	if (ret == 0)
+		uvc_dismiss_privileges(handle);
+
+	return 0;
+}
+
+static int uvc_ioctl_querybuf(struct file *file, void *fh,
+			      struct v4l2_buffer *buf)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_streaming *stream = handle->stream;
+
+	if (!uvc_has_privileges(handle))
+		return -EBUSY;
+
+	return uvc_query_buffer(&stream->queue, buf);
+}
+
+static int uvc_ioctl_qbuf(struct file *file, void *fh, struct v4l2_buffer *buf)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_streaming *stream = handle->stream;
+
+	if (!uvc_has_privileges(handle))
+		return -EBUSY;
+
+	return uvc_queue_buffer(&stream->queue, buf);
+}
+
+static int uvc_ioctl_expbuf(struct file *file, void *fh,
+			    struct v4l2_exportbuffer *exp)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_streaming *stream = handle->stream;
+
+	if (!uvc_has_privileges(handle))
+		return -EBUSY;
+
+	return uvc_export_buffer(&stream->queue, exp);
+}
+
+static int uvc_ioctl_dqbuf(struct file *file, void *fh, struct v4l2_buffer *buf)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_streaming *stream = handle->stream;
+
+	if (!uvc_has_privileges(handle))
+		return -EBUSY;
+
+	return uvc_dequeue_buffer(&stream->queue, buf,
+				  file->f_flags & O_NONBLOCK);
+}
+
+static int uvc_ioctl_create_bufs(struct file *file, void *fh,
+				  struct v4l2_create_buffers *cb)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_streaming *stream = handle->stream;
+	int ret;
+
+	ret = uvc_acquire_privileges(handle);
+	if (ret < 0)
+		return ret;
+
+	return uvc_create_buffers(&stream->queue, cb);
+}
+
+static int uvc_ioctl_streamon(struct file *file, void *fh,
+			      enum v4l2_buf_type type)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_streaming *stream = handle->stream;
+	int ret;
+
+	if (!uvc_has_privileges(handle))
+		return -EBUSY;
+
+	mutex_lock(&stream->mutex);
+	ret = uvc_queue_streamon(&stream->queue, type);
+	mutex_unlock(&stream->mutex);
+
+	return ret;
+}
+
+static int uvc_ioctl_streamoff(struct file *file, void *fh,
+			       enum v4l2_buf_type type)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_streaming *stream = handle->stream;
+
+	if (!uvc_has_privileges(handle))
+		return -EBUSY;
+
+	mutex_lock(&stream->mutex);
+	uvc_queue_streamoff(&stream->queue, type);
+	mutex_unlock(&stream->mutex);
+
+	return 0;
+}
+
+static int uvc_ioctl_enum_input(struct file *file, void *fh,
+				struct v4l2_input *input)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_video_chain *chain = handle->chain;
+	const struct uvc_entity *selector = chain->selector;
+	struct uvc_entity *iterm = NULL;
+	u32 index = input->index;
+	int pin = 0;
+
+	if (selector == NULL ||
+	    (chain->dev->quirks & UVC_QUIRK_IGNORE_SELECTOR_UNIT)) {
+		if (index != 0)
+			return -EINVAL;
+		list_for_each_entry(iterm, &chain->entities, chain) {
+			if (UVC_ENTITY_IS_ITERM(iterm))
+				break;
+		}
+		pin = iterm->id;
+	} else if (index < selector->bNrInPins) {
+		pin = selector->baSourceID[index];
+		list_for_each_entry(iterm, &chain->entities, chain) {
+			if (!UVC_ENTITY_IS_ITERM(iterm))
+				continue;
+			if (iterm->id == pin)
+				break;
+		}
+	}
+
+	if (iterm == NULL || iterm->id != pin)
+		return -EINVAL;
+
+	memset(input, 0, sizeof(*input));
+	input->index = index;
+	strlcpy(input->name, iterm->name, sizeof(input->name));
+	if (UVC_ENTITY_TYPE(iterm) == UVC_ITT_CAMERA)
+		input->type = V4L2_INPUT_TYPE_CAMERA;
+
+	return 0;
+}
+
+static int uvc_ioctl_g_input(struct file *file, void *fh, unsigned int *input)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_video_chain *chain = handle->chain;
+	int ret;
+	u8 i;
+
+	if (chain->selector == NULL ||
+	    (chain->dev->quirks & UVC_QUIRK_IGNORE_SELECTOR_UNIT)) {
+		*input = 0;
+		return 0;
+	}
+
+	ret = uvc_query_ctrl(chain->dev, UVC_GET_CUR, chain->selector->id,
+			     chain->dev->intfnum,  UVC_SU_INPUT_SELECT_CONTROL,
+			     &i, 1);
+	if (ret < 0)
+		return ret;
+
+	*input = i - 1;
+	return 0;
+}
+
+static int uvc_ioctl_s_input(struct file *file, void *fh, unsigned int input)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_video_chain *chain = handle->chain;
+	int ret;
+	u32 i;
+
+	ret = uvc_acquire_privileges(handle);
+	if (ret < 0)
+		return ret;
+
+	if (chain->selector == NULL ||
+	    (chain->dev->quirks & UVC_QUIRK_IGNORE_SELECTOR_UNIT)) {
+		if (input)
+			return -EINVAL;
+		return 0;
+	}
+
+	if (input >= chain->selector->bNrInPins)
+		return -EINVAL;
+
+	i = input + 1;
+	return uvc_query_ctrl(chain->dev, UVC_SET_CUR, chain->selector->id,
+			      chain->dev->intfnum, UVC_SU_INPUT_SELECT_CONTROL,
+			      &i, 1);
+}
+
+static int uvc_ioctl_queryctrl(struct file *file, void *fh,
+			       struct v4l2_queryctrl *qc)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_video_chain *chain = handle->chain;
+
+	return uvc_query_v4l2_ctrl(chain, qc);
+}
+
+static int uvc_ioctl_query_ext_ctrl(struct file *file, void *fh,
+				    struct v4l2_query_ext_ctrl *qec)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_video_chain *chain = handle->chain;
+	struct v4l2_queryctrl qc = { qec->id };
+	int ret;
+
+	ret = uvc_query_v4l2_ctrl(chain, &qc);
+	if (ret)
+		return ret;
+
+	qec->id = qc.id;
+	qec->type = qc.type;
+	strlcpy(qec->name, qc.name, sizeof(qec->name));
+	qec->minimum = qc.minimum;
+	qec->maximum = qc.maximum;
+	qec->step = qc.step;
+	qec->default_value = qc.default_value;
+	qec->flags = qc.flags;
+	qec->elem_size = 4;
+	qec->elems = 1;
+	qec->nr_of_dims = 0;
+	memset(qec->dims, 0, sizeof(qec->dims));
+	memset(qec->reserved, 0, sizeof(qec->reserved));
+
+	return 0;
+}
+
+static int uvc_ioctl_g_ctrl(struct file *file, void *fh,
+			    struct v4l2_control *ctrl)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_video_chain *chain = handle->chain;
+	struct v4l2_ext_control xctrl;
+	int ret;
+
+	memset(&xctrl, 0, sizeof(xctrl));
+	xctrl.id = ctrl->id;
+
+	ret = uvc_ctrl_begin(chain);
+	if (ret < 0)
+		return ret;
+
+	ret = uvc_ctrl_get(chain, &xctrl);
+	uvc_ctrl_rollback(handle);
+	if (ret < 0)
+		return ret;
+
+	ctrl->value = xctrl.value;
+	return 0;
+}
+
+static int uvc_ioctl_s_ctrl(struct file *file, void *fh,
+			    struct v4l2_control *ctrl)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_video_chain *chain = handle->chain;
+	struct v4l2_ext_control xctrl;
+	int ret;
+
+	memset(&xctrl, 0, sizeof(xctrl));
+	xctrl.id = ctrl->id;
+	xctrl.value = ctrl->value;
+
+	ret = uvc_ctrl_begin(chain);
+	if (ret < 0)
+		return ret;
+
+	ret = uvc_ctrl_set(handle, &xctrl);
+	if (ret < 0) {
+		uvc_ctrl_rollback(handle);
+		return ret;
+	}
+
+	ret = uvc_ctrl_commit(handle, &xctrl, 1);
+	if (ret < 0)
+		return ret;
+
+	ctrl->value = xctrl.value;
+	return 0;
+}
+
+static int uvc_ioctl_g_ext_ctrls(struct file *file, void *fh,
+				 struct v4l2_ext_controls *ctrls)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_video_chain *chain = handle->chain;
+	struct v4l2_ext_control *ctrl = ctrls->controls;
+	unsigned int i;
+	int ret;
+
+	if (ctrls->which == V4L2_CTRL_WHICH_DEF_VAL) {
+		for (i = 0; i < ctrls->count; ++ctrl, ++i) {
+			struct v4l2_queryctrl qc = { .id = ctrl->id };
+
+			ret = uvc_query_v4l2_ctrl(chain, &qc);
+			if (ret < 0) {
+				ctrls->error_idx = i;
+				return ret;
+			}
+
+			ctrl->value = qc.default_value;
+		}
+
+		return 0;
+	}
+
+	ret = uvc_ctrl_begin(chain);
+	if (ret < 0)
+		return ret;
+
+	for (i = 0; i < ctrls->count; ++ctrl, ++i) {
+		ret = uvc_ctrl_get(chain, ctrl);
+		if (ret < 0) {
+			uvc_ctrl_rollback(handle);
+			ctrls->error_idx = i;
+			return ret;
+		}
+	}
+
+	ctrls->error_idx = 0;
+
+	return uvc_ctrl_rollback(handle);
+}
+
+static int uvc_ioctl_s_try_ext_ctrls(struct uvc_fh *handle,
+				     struct v4l2_ext_controls *ctrls,
+				     bool commit)
+{
+	struct v4l2_ext_control *ctrl = ctrls->controls;
+	struct uvc_video_chain *chain = handle->chain;
+	unsigned int i;
+	int ret;
+
+	/* Default value cannot be changed */
+	if (ctrls->which == V4L2_CTRL_WHICH_DEF_VAL)
+		return -EINVAL;
+
+	ret = uvc_ctrl_begin(chain);
+	if (ret < 0)
+		return ret;
+
+	for (i = 0; i < ctrls->count; ++ctrl, ++i) {
+		ret = uvc_ctrl_set(handle, ctrl);
+		if (ret < 0) {
+			uvc_ctrl_rollback(handle);
+			ctrls->error_idx = commit ? ctrls->count : i;
+			return ret;
+		}
+	}
+
+	ctrls->error_idx = 0;
+
+	if (commit)
+		return uvc_ctrl_commit(handle, ctrls->controls, ctrls->count);
+	else
+		return uvc_ctrl_rollback(handle);
+}
+
+static int uvc_ioctl_s_ext_ctrls(struct file *file, void *fh,
+				 struct v4l2_ext_controls *ctrls)
+{
+	struct uvc_fh *handle = fh;
+
+	return uvc_ioctl_s_try_ext_ctrls(handle, ctrls, true);
+}
+
+static int uvc_ioctl_try_ext_ctrls(struct file *file, void *fh,
+				   struct v4l2_ext_controls *ctrls)
+{
+	struct uvc_fh *handle = fh;
+
+	return uvc_ioctl_s_try_ext_ctrls(handle, ctrls, false);
+}
+
+static int uvc_ioctl_querymenu(struct file *file, void *fh,
+			       struct v4l2_querymenu *qm)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_video_chain *chain = handle->chain;
+
+	return uvc_query_v4l2_menu(chain, qm);
+}
+
+static int uvc_ioctl_g_selection(struct file *file, void *fh,
+				 struct v4l2_selection *sel)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_streaming *stream = handle->stream;
+
+	if (sel->type != stream->type)
+		return -EINVAL;
+
+	switch (sel->target) {
+	case V4L2_SEL_TGT_CROP_DEFAULT:
+	case V4L2_SEL_TGT_CROP_BOUNDS:
+		if (stream->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+			return -EINVAL;
+		break;
+	case V4L2_SEL_TGT_COMPOSE_DEFAULT:
+	case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+		if (stream->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
+			return -EINVAL;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	sel->r.left = 0;
+	sel->r.top = 0;
+	mutex_lock(&stream->mutex);
+	sel->r.width = stream->cur_frame->wWidth;
+	sel->r.height = stream->cur_frame->wHeight;
+	mutex_unlock(&stream->mutex);
+
+	return 0;
+}
+
+static int uvc_ioctl_g_parm(struct file *file, void *fh,
+			    struct v4l2_streamparm *parm)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_streaming *stream = handle->stream;
+
+	return uvc_v4l2_get_streamparm(stream, parm);
+}
+
+static int uvc_ioctl_s_parm(struct file *file, void *fh,
+			    struct v4l2_streamparm *parm)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_streaming *stream = handle->stream;
+	int ret;
+
+	ret = uvc_acquire_privileges(handle);
+	if (ret < 0)
+		return ret;
+
+	return uvc_v4l2_set_streamparm(stream, parm);
+}
+
+static int uvc_ioctl_enum_framesizes(struct file *file, void *fh,
+				     struct v4l2_frmsizeenum *fsize)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_streaming *stream = handle->stream;
+	struct uvc_format *format = NULL;
+	struct uvc_frame *frame = NULL;
+	unsigned int index;
+	unsigned int i;
+
+	/* Look for the given pixel format */
+	for (i = 0; i < stream->nformats; i++) {
+		if (stream->format[i].fcc == fsize->pixel_format) {
+			format = &stream->format[i];
+			break;
+		}
+	}
+	if (format == NULL)
+		return -EINVAL;
+
+	/* Skip duplicate frame sizes */
+	for (i = 0, index = 0; i < format->nframes; i++) {
+		if (frame && frame->wWidth == format->frame[i].wWidth &&
+		    frame->wHeight == format->frame[i].wHeight)
+			continue;
+		frame = &format->frame[i];
+		if (index == fsize->index)
+			break;
+		index++;
+	}
+
+	if (i == format->nframes)
+		return -EINVAL;
+
+	fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+	fsize->discrete.width = frame->wWidth;
+	fsize->discrete.height = frame->wHeight;
+	return 0;
+}
+
+static int uvc_ioctl_enum_frameintervals(struct file *file, void *fh,
+					 struct v4l2_frmivalenum *fival)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_streaming *stream = handle->stream;
+	struct uvc_format *format = NULL;
+	struct uvc_frame *frame = NULL;
+	unsigned int nintervals;
+	unsigned int index;
+	unsigned int i;
+
+	/* Look for the given pixel format and frame size */
+	for (i = 0; i < stream->nformats; i++) {
+		if (stream->format[i].fcc == fival->pixel_format) {
+			format = &stream->format[i];
+			break;
+		}
+	}
+	if (format == NULL)
+		return -EINVAL;
+
+	index = fival->index;
+	for (i = 0; i < format->nframes; i++) {
+		if (format->frame[i].wWidth == fival->width &&
+		    format->frame[i].wHeight == fival->height) {
+			frame = &format->frame[i];
+			nintervals = frame->bFrameIntervalType ?: 1;
+			if (index < nintervals)
+				break;
+			index -= nintervals;
+		}
+	}
+	if (i == format->nframes)
+		return -EINVAL;
+
+	if (frame->bFrameIntervalType) {
+		fival->type = V4L2_FRMIVAL_TYPE_DISCRETE;
+		fival->discrete.numerator =
+			frame->dwFrameInterval[index];
+		fival->discrete.denominator = 10000000;
+		uvc_simplify_fraction(&fival->discrete.numerator,
+			&fival->discrete.denominator, 8, 333);
+	} else {
+		fival->type = V4L2_FRMIVAL_TYPE_STEPWISE;
+		fival->stepwise.min.numerator = frame->dwFrameInterval[0];
+		fival->stepwise.min.denominator = 10000000;
+		fival->stepwise.max.numerator = frame->dwFrameInterval[1];
+		fival->stepwise.max.denominator = 10000000;
+		fival->stepwise.step.numerator = frame->dwFrameInterval[2];
+		fival->stepwise.step.denominator = 10000000;
+		uvc_simplify_fraction(&fival->stepwise.min.numerator,
+			&fival->stepwise.min.denominator, 8, 333);
+		uvc_simplify_fraction(&fival->stepwise.max.numerator,
+			&fival->stepwise.max.denominator, 8, 333);
+		uvc_simplify_fraction(&fival->stepwise.step.numerator,
+			&fival->stepwise.step.denominator, 8, 333);
+	}
+
+	return 0;
+}
+
+static int uvc_ioctl_subscribe_event(struct v4l2_fh *fh,
+				     const struct v4l2_event_subscription *sub)
+{
+	switch (sub->type) {
+	case V4L2_EVENT_CTRL:
+		return v4l2_event_subscribe(fh, sub, 0, &uvc_ctrl_sub_ev_ops);
+	default:
+		return -EINVAL;
+	}
+}
+
+static long uvc_ioctl_default(struct file *file, void *fh, bool valid_prio,
+			      unsigned int cmd, void *arg)
+{
+	struct uvc_fh *handle = fh;
+	struct uvc_video_chain *chain = handle->chain;
+
+	switch (cmd) {
+	/* Dynamic controls. */
+	case UVCIOC_CTRL_MAP:
+		return uvc_ioctl_ctrl_map(chain, arg);
+
+	case UVCIOC_CTRL_QUERY:
+		return uvc_xu_ctrl_query(chain, arg);
+
+	default:
+		return -ENOTTY;
+	}
+}
+
+#ifdef CONFIG_COMPAT
+struct uvc_xu_control_mapping32 {
+	u32 id;
+	u8 name[32];
+	u8 entity[16];
+	u8 selector;
+
+	u8 size;
+	u8 offset;
+	u32 v4l2_type;
+	u32 data_type;
+
+	compat_caddr_t menu_info;
+	u32 menu_count;
+
+	u32 reserved[4];
+};
+
+static int uvc_v4l2_get_xu_mapping(struct uvc_xu_control_mapping *kp,
+			const struct uvc_xu_control_mapping32 __user *up)
+{
+	struct uvc_xu_control_mapping32 *p = (void *)kp;
+	compat_caddr_t info;
+	u32 count;
+
+	if (copy_from_user(p, up, sizeof(*p)))
+		return -EFAULT;
+
+	count = p->menu_count;
+	info = p->menu_info;
+
+	memset(kp->reserved, 0, sizeof(kp->reserved));
+	kp->menu_info = count ? compat_ptr(info) : NULL;
+	kp->menu_count = count;
+	return 0;
+}
+
+static int uvc_v4l2_put_xu_mapping(const struct uvc_xu_control_mapping *kp,
+			struct uvc_xu_control_mapping32 __user *up)
+{
+	if (copy_to_user(up, kp, offsetof(typeof(*up), menu_info)) ||
+	    put_user(kp->menu_count, &up->menu_count))
+		return -EFAULT;
+
+	if (clear_user(up->reserved, sizeof(up->reserved)))
+		return -EFAULT;
+
+	return 0;
+}
+
+struct uvc_xu_control_query32 {
+	u8 unit;
+	u8 selector;
+	u8 query;
+	u16 size;
+	compat_caddr_t data;
+};
+
+static int uvc_v4l2_get_xu_query(struct uvc_xu_control_query *kp,
+			const struct uvc_xu_control_query32 __user *up)
+{
+	struct uvc_xu_control_query32 v;
+
+	if (copy_from_user(&v, up, sizeof(v)))
+		return -EFAULT;
+
+	*kp = (struct uvc_xu_control_query){
+		.unit = v.unit,
+		.selector = v.selector,
+		.query = v.query,
+		.size = v.size,
+		.data = v.size ? compat_ptr(v.data) : NULL
+	};
+	return 0;
+}
+
+static int uvc_v4l2_put_xu_query(const struct uvc_xu_control_query *kp,
+			struct uvc_xu_control_query32 __user *up)
+{
+	if (copy_to_user(up, kp, offsetof(typeof(*up), data)))
+		return -EFAULT;
+	return 0;
+}
+
+#define UVCIOC_CTRL_MAP32	_IOWR('u', 0x20, struct uvc_xu_control_mapping32)
+#define UVCIOC_CTRL_QUERY32	_IOWR('u', 0x21, struct uvc_xu_control_query32)
+
+static long uvc_v4l2_compat_ioctl32(struct file *file,
+		     unsigned int cmd, unsigned long arg)
+{
+	struct uvc_fh *handle = file->private_data;
+	union {
+		struct uvc_xu_control_mapping xmap;
+		struct uvc_xu_control_query xqry;
+	} karg;
+	void __user *up = compat_ptr(arg);
+	long ret;
+
+	switch (cmd) {
+	case UVCIOC_CTRL_MAP32:
+		ret = uvc_v4l2_get_xu_mapping(&karg.xmap, up);
+		if (ret)
+			return ret;
+		ret = uvc_ioctl_ctrl_map(handle->chain, &karg.xmap);
+		if (ret)
+			return ret;
+		ret = uvc_v4l2_put_xu_mapping(&karg.xmap, up);
+		if (ret)
+			return ret;
+
+		break;
+
+	case UVCIOC_CTRL_QUERY32:
+		ret = uvc_v4l2_get_xu_query(&karg.xqry, up);
+		if (ret)
+			return ret;
+		ret = uvc_xu_ctrl_query(handle->chain, &karg.xqry);
+		if (ret)
+			return ret;
+		ret = uvc_v4l2_put_xu_query(&karg.xqry, up);
+		if (ret)
+			return ret;
+		break;
+
+	default:
+		return -ENOIOCTLCMD;
+	}
+
+	return ret;
+}
+#endif
+
+static ssize_t uvc_v4l2_read(struct file *file, char __user *data,
+		    size_t count, loff_t *ppos)
+{
+	uvc_trace(UVC_TRACE_CALLS, "uvc_v4l2_read: not implemented.\n");
+	return -EINVAL;
+}
+
+static int uvc_v4l2_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	struct uvc_fh *handle = file->private_data;
+	struct uvc_streaming *stream = handle->stream;
+
+	uvc_trace(UVC_TRACE_CALLS, "uvc_v4l2_mmap\n");
+
+	return uvc_queue_mmap(&stream->queue, vma);
+}
+
+static __poll_t uvc_v4l2_poll(struct file *file, poll_table *wait)
+{
+	struct uvc_fh *handle = file->private_data;
+	struct uvc_streaming *stream = handle->stream;
+
+	uvc_trace(UVC_TRACE_CALLS, "uvc_v4l2_poll\n");
+
+	return uvc_queue_poll(&stream->queue, file, wait);
+}
+
+#ifndef CONFIG_MMU
+static unsigned long uvc_v4l2_get_unmapped_area(struct file *file,
+		unsigned long addr, unsigned long len, unsigned long pgoff,
+		unsigned long flags)
+{
+	struct uvc_fh *handle = file->private_data;
+	struct uvc_streaming *stream = handle->stream;
+
+	uvc_trace(UVC_TRACE_CALLS, "uvc_v4l2_get_unmapped_area\n");
+
+	return uvc_queue_get_unmapped_area(&stream->queue, pgoff);
+}
+#endif
+
+const struct v4l2_ioctl_ops uvc_ioctl_ops = {
+	.vidioc_querycap = uvc_ioctl_querycap,
+	.vidioc_enum_fmt_vid_cap = uvc_ioctl_enum_fmt_vid_cap,
+	.vidioc_enum_fmt_vid_out = uvc_ioctl_enum_fmt_vid_out,
+	.vidioc_g_fmt_vid_cap = uvc_ioctl_g_fmt_vid_cap,
+	.vidioc_g_fmt_vid_out = uvc_ioctl_g_fmt_vid_out,
+	.vidioc_s_fmt_vid_cap = uvc_ioctl_s_fmt_vid_cap,
+	.vidioc_s_fmt_vid_out = uvc_ioctl_s_fmt_vid_out,
+	.vidioc_try_fmt_vid_cap = uvc_ioctl_try_fmt_vid_cap,
+	.vidioc_try_fmt_vid_out = uvc_ioctl_try_fmt_vid_out,
+	.vidioc_reqbufs = uvc_ioctl_reqbufs,
+	.vidioc_querybuf = uvc_ioctl_querybuf,
+	.vidioc_qbuf = uvc_ioctl_qbuf,
+	.vidioc_expbuf = uvc_ioctl_expbuf,
+	.vidioc_dqbuf = uvc_ioctl_dqbuf,
+	.vidioc_create_bufs = uvc_ioctl_create_bufs,
+	.vidioc_streamon = uvc_ioctl_streamon,
+	.vidioc_streamoff = uvc_ioctl_streamoff,
+	.vidioc_enum_input = uvc_ioctl_enum_input,
+	.vidioc_g_input = uvc_ioctl_g_input,
+	.vidioc_s_input = uvc_ioctl_s_input,
+	.vidioc_queryctrl = uvc_ioctl_queryctrl,
+	.vidioc_query_ext_ctrl = uvc_ioctl_query_ext_ctrl,
+	.vidioc_g_ctrl = uvc_ioctl_g_ctrl,
+	.vidioc_s_ctrl = uvc_ioctl_s_ctrl,
+	.vidioc_g_ext_ctrls = uvc_ioctl_g_ext_ctrls,
+	.vidioc_s_ext_ctrls = uvc_ioctl_s_ext_ctrls,
+	.vidioc_try_ext_ctrls = uvc_ioctl_try_ext_ctrls,
+	.vidioc_querymenu = uvc_ioctl_querymenu,
+	.vidioc_g_selection = uvc_ioctl_g_selection,
+	.vidioc_g_parm = uvc_ioctl_g_parm,
+	.vidioc_s_parm = uvc_ioctl_s_parm,
+	.vidioc_enum_framesizes = uvc_ioctl_enum_framesizes,
+	.vidioc_enum_frameintervals = uvc_ioctl_enum_frameintervals,
+	.vidioc_subscribe_event = uvc_ioctl_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+	.vidioc_default = uvc_ioctl_default,
+};
+
+const struct v4l2_file_operations uvc_fops = {
+	.owner		= THIS_MODULE,
+	.open		= uvc_v4l2_open,
+	.release	= uvc_v4l2_release,
+	.unlocked_ioctl	= video_ioctl2,
+#ifdef CONFIG_COMPAT
+	.compat_ioctl32	= uvc_v4l2_compat_ioctl32,
+#endif
+	.read		= uvc_v4l2_read,
+	.mmap		= uvc_v4l2_mmap,
+	.poll		= uvc_v4l2_poll,
+#ifndef CONFIG_MMU
+	.get_unmapped_area = uvc_v4l2_get_unmapped_area,
+#endif
+};
+
diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c
new file mode 100644
index 0000000..86a99f4
--- /dev/null
+++ b/drivers/media/usb/uvc/uvc_video.c
@@ -0,0 +1,2059 @@
+/*
+ *      uvc_video.c  --  USB Video Class driver - Video handling
+ *
+ *      Copyright (C) 2005-2010
+ *          Laurent Pinchart (laurent.pinchart@ideasonboard.com)
+ *
+ *      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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/videodev2.h>
+#include <linux/vmalloc.h>
+#include <linux/wait.h>
+#include <linux/atomic.h>
+#include <asm/unaligned.h>
+
+#include <media/v4l2-common.h>
+
+#include "uvcvideo.h"
+
+/* ------------------------------------------------------------------------
+ * UVC Controls
+ */
+
+static int __uvc_query_ctrl(struct uvc_device *dev, u8 query, u8 unit,
+			u8 intfnum, u8 cs, void *data, u16 size,
+			int timeout)
+{
+	u8 type = USB_TYPE_CLASS | USB_RECIP_INTERFACE;
+	unsigned int pipe;
+
+	pipe = (query & 0x80) ? usb_rcvctrlpipe(dev->udev, 0)
+			      : usb_sndctrlpipe(dev->udev, 0);
+	type |= (query & 0x80) ? USB_DIR_IN : USB_DIR_OUT;
+
+	return usb_control_msg(dev->udev, pipe, query, type, cs << 8,
+			unit << 8 | intfnum, data, size, timeout);
+}
+
+static const char *uvc_query_name(u8 query)
+{
+	switch (query) {
+	case UVC_SET_CUR:
+		return "SET_CUR";
+	case UVC_GET_CUR:
+		return "GET_CUR";
+	case UVC_GET_MIN:
+		return "GET_MIN";
+	case UVC_GET_MAX:
+		return "GET_MAX";
+	case UVC_GET_RES:
+		return "GET_RES";
+	case UVC_GET_LEN:
+		return "GET_LEN";
+	case UVC_GET_INFO:
+		return "GET_INFO";
+	case UVC_GET_DEF:
+		return "GET_DEF";
+	default:
+		return "<invalid>";
+	}
+}
+
+int uvc_query_ctrl(struct uvc_device *dev, u8 query, u8 unit,
+			u8 intfnum, u8 cs, void *data, u16 size)
+{
+	int ret;
+	u8 error;
+	u8 tmp;
+
+	ret = __uvc_query_ctrl(dev, query, unit, intfnum, cs, data, size,
+				UVC_CTRL_CONTROL_TIMEOUT);
+	if (likely(ret == size))
+		return 0;
+
+	uvc_printk(KERN_ERR,
+		   "Failed to query (%s) UVC control %u on unit %u: %d (exp. %u).\n",
+		   uvc_query_name(query), cs, unit, ret, size);
+
+	if (ret != -EPIPE)
+		return ret;
+
+	tmp = *(u8 *)data;
+
+	ret = __uvc_query_ctrl(dev, UVC_GET_CUR, 0, intfnum,
+			       UVC_VC_REQUEST_ERROR_CODE_CONTROL, data, 1,
+			       UVC_CTRL_CONTROL_TIMEOUT);
+
+	error = *(u8 *)data;
+	*(u8 *)data = tmp;
+
+	if (ret != 1)
+		return ret < 0 ? ret : -EPIPE;
+
+	uvc_trace(UVC_TRACE_CONTROL, "Control error %u\n", error);
+
+	switch (error) {
+	case 0:
+		/* Cannot happen - we received a STALL */
+		return -EPIPE;
+	case 1: /* Not ready */
+		return -EBUSY;
+	case 2: /* Wrong state */
+		return -EILSEQ;
+	case 3: /* Power */
+		return -EREMOTE;
+	case 4: /* Out of range */
+		return -ERANGE;
+	case 5: /* Invalid unit */
+	case 6: /* Invalid control */
+	case 7: /* Invalid Request */
+	case 8: /* Invalid value within range */
+		return -EINVAL;
+	default: /* reserved or unknown */
+		break;
+	}
+
+	return -EPIPE;
+}
+
+static void uvc_fixup_video_ctrl(struct uvc_streaming *stream,
+	struct uvc_streaming_control *ctrl)
+{
+	struct uvc_format *format = NULL;
+	struct uvc_frame *frame = NULL;
+	unsigned int i;
+
+	for (i = 0; i < stream->nformats; ++i) {
+		if (stream->format[i].index == ctrl->bFormatIndex) {
+			format = &stream->format[i];
+			break;
+		}
+	}
+
+	if (format == NULL)
+		return;
+
+	for (i = 0; i < format->nframes; ++i) {
+		if (format->frame[i].bFrameIndex == ctrl->bFrameIndex) {
+			frame = &format->frame[i];
+			break;
+		}
+	}
+
+	if (frame == NULL)
+		return;
+
+	if (!(format->flags & UVC_FMT_FLAG_COMPRESSED) ||
+	     (ctrl->dwMaxVideoFrameSize == 0 &&
+	      stream->dev->uvc_version < 0x0110))
+		ctrl->dwMaxVideoFrameSize =
+			frame->dwMaxVideoFrameBufferSize;
+
+	/* The "TOSHIBA Web Camera - 5M" Chicony device (04f2:b50b) seems to
+	 * compute the bandwidth on 16 bits and erroneously sign-extend it to
+	 * 32 bits, resulting in a huge bandwidth value. Detect and fix that
+	 * condition by setting the 16 MSBs to 0 when they're all equal to 1.
+	 */
+	if ((ctrl->dwMaxPayloadTransferSize & 0xffff0000) == 0xffff0000)
+		ctrl->dwMaxPayloadTransferSize &= ~0xffff0000;
+
+	if (!(format->flags & UVC_FMT_FLAG_COMPRESSED) &&
+	    stream->dev->quirks & UVC_QUIRK_FIX_BANDWIDTH &&
+	    stream->intf->num_altsetting > 1) {
+		u32 interval;
+		u32 bandwidth;
+
+		interval = (ctrl->dwFrameInterval > 100000)
+			 ? ctrl->dwFrameInterval
+			 : frame->dwFrameInterval[0];
+
+		/* Compute a bandwidth estimation by multiplying the frame
+		 * size by the number of video frames per second, divide the
+		 * result by the number of USB frames (or micro-frames for
+		 * high-speed devices) per second and add the UVC header size
+		 * (assumed to be 12 bytes long).
+		 */
+		bandwidth = frame->wWidth * frame->wHeight / 8 * format->bpp;
+		bandwidth *= 10000000 / interval + 1;
+		bandwidth /= 1000;
+		if (stream->dev->udev->speed == USB_SPEED_HIGH)
+			bandwidth /= 8;
+		bandwidth += 12;
+
+		/* The bandwidth estimate is too low for many cameras. Don't use
+		 * maximum packet sizes lower than 1024 bytes to try and work
+		 * around the problem. According to measurements done on two
+		 * different camera models, the value is high enough to get most
+		 * resolutions working while not preventing two simultaneous
+		 * VGA streams at 15 fps.
+		 */
+		bandwidth = max_t(u32, bandwidth, 1024);
+
+		ctrl->dwMaxPayloadTransferSize = bandwidth;
+	}
+}
+
+static size_t uvc_video_ctrl_size(struct uvc_streaming *stream)
+{
+	/*
+	 * Return the size of the video probe and commit controls, which depends
+	 * on the protocol version.
+	 */
+	if (stream->dev->uvc_version < 0x0110)
+		return 26;
+	else if (stream->dev->uvc_version < 0x0150)
+		return 34;
+	else
+		return 48;
+}
+
+static int uvc_get_video_ctrl(struct uvc_streaming *stream,
+	struct uvc_streaming_control *ctrl, int probe, u8 query)
+{
+	u16 size = uvc_video_ctrl_size(stream);
+	u8 *data;
+	int ret;
+
+	if ((stream->dev->quirks & UVC_QUIRK_PROBE_DEF) &&
+			query == UVC_GET_DEF)
+		return -EIO;
+
+	data = kmalloc(size, GFP_KERNEL);
+	if (data == NULL)
+		return -ENOMEM;
+
+	ret = __uvc_query_ctrl(stream->dev, query, 0, stream->intfnum,
+		probe ? UVC_VS_PROBE_CONTROL : UVC_VS_COMMIT_CONTROL, data,
+		size, uvc_timeout_param);
+
+	if ((query == UVC_GET_MIN || query == UVC_GET_MAX) && ret == 2) {
+		/* Some cameras, mostly based on Bison Electronics chipsets,
+		 * answer a GET_MIN or GET_MAX request with the wCompQuality
+		 * field only.
+		 */
+		uvc_warn_once(stream->dev, UVC_WARN_MINMAX, "UVC non "
+			"compliance - GET_MIN/MAX(PROBE) incorrectly "
+			"supported. Enabling workaround.\n");
+		memset(ctrl, 0, sizeof(*ctrl));
+		ctrl->wCompQuality = le16_to_cpup((__le16 *)data);
+		ret = 0;
+		goto out;
+	} else if (query == UVC_GET_DEF && probe == 1 && ret != size) {
+		/* Many cameras don't support the GET_DEF request on their
+		 * video probe control. Warn once and return, the caller will
+		 * fall back to GET_CUR.
+		 */
+		uvc_warn_once(stream->dev, UVC_WARN_PROBE_DEF, "UVC non "
+			"compliance - GET_DEF(PROBE) not supported. "
+			"Enabling workaround.\n");
+		ret = -EIO;
+		goto out;
+	} else if (ret != size) {
+		uvc_printk(KERN_ERR, "Failed to query (%u) UVC %s control : "
+			"%d (exp. %u).\n", query, probe ? "probe" : "commit",
+			ret, size);
+		ret = -EIO;
+		goto out;
+	}
+
+	ctrl->bmHint = le16_to_cpup((__le16 *)&data[0]);
+	ctrl->bFormatIndex = data[2];
+	ctrl->bFrameIndex = data[3];
+	ctrl->dwFrameInterval = le32_to_cpup((__le32 *)&data[4]);
+	ctrl->wKeyFrameRate = le16_to_cpup((__le16 *)&data[8]);
+	ctrl->wPFrameRate = le16_to_cpup((__le16 *)&data[10]);
+	ctrl->wCompQuality = le16_to_cpup((__le16 *)&data[12]);
+	ctrl->wCompWindowSize = le16_to_cpup((__le16 *)&data[14]);
+	ctrl->wDelay = le16_to_cpup((__le16 *)&data[16]);
+	ctrl->dwMaxVideoFrameSize = get_unaligned_le32(&data[18]);
+	ctrl->dwMaxPayloadTransferSize = get_unaligned_le32(&data[22]);
+
+	if (size >= 34) {
+		ctrl->dwClockFrequency = get_unaligned_le32(&data[26]);
+		ctrl->bmFramingInfo = data[30];
+		ctrl->bPreferedVersion = data[31];
+		ctrl->bMinVersion = data[32];
+		ctrl->bMaxVersion = data[33];
+	} else {
+		ctrl->dwClockFrequency = stream->dev->clock_frequency;
+		ctrl->bmFramingInfo = 0;
+		ctrl->bPreferedVersion = 0;
+		ctrl->bMinVersion = 0;
+		ctrl->bMaxVersion = 0;
+	}
+
+	/* Some broken devices return null or wrong dwMaxVideoFrameSize and
+	 * dwMaxPayloadTransferSize fields. Try to get the value from the
+	 * format and frame descriptors.
+	 */
+	uvc_fixup_video_ctrl(stream, ctrl);
+	ret = 0;
+
+out:
+	kfree(data);
+	return ret;
+}
+
+static int uvc_set_video_ctrl(struct uvc_streaming *stream,
+	struct uvc_streaming_control *ctrl, int probe)
+{
+	u16 size = uvc_video_ctrl_size(stream);
+	u8 *data;
+	int ret;
+
+	data = kzalloc(size, GFP_KERNEL);
+	if (data == NULL)
+		return -ENOMEM;
+
+	*(__le16 *)&data[0] = cpu_to_le16(ctrl->bmHint);
+	data[2] = ctrl->bFormatIndex;
+	data[3] = ctrl->bFrameIndex;
+	*(__le32 *)&data[4] = cpu_to_le32(ctrl->dwFrameInterval);
+	*(__le16 *)&data[8] = cpu_to_le16(ctrl->wKeyFrameRate);
+	*(__le16 *)&data[10] = cpu_to_le16(ctrl->wPFrameRate);
+	*(__le16 *)&data[12] = cpu_to_le16(ctrl->wCompQuality);
+	*(__le16 *)&data[14] = cpu_to_le16(ctrl->wCompWindowSize);
+	*(__le16 *)&data[16] = cpu_to_le16(ctrl->wDelay);
+	put_unaligned_le32(ctrl->dwMaxVideoFrameSize, &data[18]);
+	put_unaligned_le32(ctrl->dwMaxPayloadTransferSize, &data[22]);
+
+	if (size >= 34) {
+		put_unaligned_le32(ctrl->dwClockFrequency, &data[26]);
+		data[30] = ctrl->bmFramingInfo;
+		data[31] = ctrl->bPreferedVersion;
+		data[32] = ctrl->bMinVersion;
+		data[33] = ctrl->bMaxVersion;
+	}
+
+	ret = __uvc_query_ctrl(stream->dev, UVC_SET_CUR, 0, stream->intfnum,
+		probe ? UVC_VS_PROBE_CONTROL : UVC_VS_COMMIT_CONTROL, data,
+		size, uvc_timeout_param);
+	if (ret != size) {
+		uvc_printk(KERN_ERR, "Failed to set UVC %s control : "
+			"%d (exp. %u).\n", probe ? "probe" : "commit",
+			ret, size);
+		ret = -EIO;
+	}
+
+	kfree(data);
+	return ret;
+}
+
+int uvc_probe_video(struct uvc_streaming *stream,
+	struct uvc_streaming_control *probe)
+{
+	struct uvc_streaming_control probe_min, probe_max;
+	u16 bandwidth;
+	unsigned int i;
+	int ret;
+
+	/* Perform probing. The device should adjust the requested values
+	 * according to its capabilities. However, some devices, namely the
+	 * first generation UVC Logitech webcams, don't implement the Video
+	 * Probe control properly, and just return the needed bandwidth. For
+	 * that reason, if the needed bandwidth exceeds the maximum available
+	 * bandwidth, try to lower the quality.
+	 */
+	ret = uvc_set_video_ctrl(stream, probe, 1);
+	if (ret < 0)
+		goto done;
+
+	/* Get the minimum and maximum values for compression settings. */
+	if (!(stream->dev->quirks & UVC_QUIRK_PROBE_MINMAX)) {
+		ret = uvc_get_video_ctrl(stream, &probe_min, 1, UVC_GET_MIN);
+		if (ret < 0)
+			goto done;
+		ret = uvc_get_video_ctrl(stream, &probe_max, 1, UVC_GET_MAX);
+		if (ret < 0)
+			goto done;
+
+		probe->wCompQuality = probe_max.wCompQuality;
+	}
+
+	for (i = 0; i < 2; ++i) {
+		ret = uvc_set_video_ctrl(stream, probe, 1);
+		if (ret < 0)
+			goto done;
+		ret = uvc_get_video_ctrl(stream, probe, 1, UVC_GET_CUR);
+		if (ret < 0)
+			goto done;
+
+		if (stream->intf->num_altsetting == 1)
+			break;
+
+		bandwidth = probe->dwMaxPayloadTransferSize;
+		if (bandwidth <= stream->maxpsize)
+			break;
+
+		if (stream->dev->quirks & UVC_QUIRK_PROBE_MINMAX) {
+			ret = -ENOSPC;
+			goto done;
+		}
+
+		/* TODO: negotiate compression parameters */
+		probe->wKeyFrameRate = probe_min.wKeyFrameRate;
+		probe->wPFrameRate = probe_min.wPFrameRate;
+		probe->wCompQuality = probe_max.wCompQuality;
+		probe->wCompWindowSize = probe_min.wCompWindowSize;
+	}
+
+done:
+	return ret;
+}
+
+static int uvc_commit_video(struct uvc_streaming *stream,
+			    struct uvc_streaming_control *probe)
+{
+	return uvc_set_video_ctrl(stream, probe, 0);
+}
+
+/* -----------------------------------------------------------------------------
+ * Clocks and timestamps
+ */
+
+static inline ktime_t uvc_video_get_time(void)
+{
+	if (uvc_clock_param == CLOCK_MONOTONIC)
+		return ktime_get();
+	else
+		return ktime_get_real();
+}
+
+static void
+uvc_video_clock_decode(struct uvc_streaming *stream, struct uvc_buffer *buf,
+		       const u8 *data, int len)
+{
+	struct uvc_clock_sample *sample;
+	unsigned int header_size;
+	bool has_pts = false;
+	bool has_scr = false;
+	unsigned long flags;
+	ktime_t time;
+	u16 host_sof;
+	u16 dev_sof;
+
+	switch (data[1] & (UVC_STREAM_PTS | UVC_STREAM_SCR)) {
+	case UVC_STREAM_PTS | UVC_STREAM_SCR:
+		header_size = 12;
+		has_pts = true;
+		has_scr = true;
+		break;
+	case UVC_STREAM_PTS:
+		header_size = 6;
+		has_pts = true;
+		break;
+	case UVC_STREAM_SCR:
+		header_size = 8;
+		has_scr = true;
+		break;
+	default:
+		header_size = 2;
+		break;
+	}
+
+	/* Check for invalid headers. */
+	if (len < header_size)
+		return;
+
+	/* Extract the timestamps:
+	 *
+	 * - store the frame PTS in the buffer structure
+	 * - if the SCR field is present, retrieve the host SOF counter and
+	 *   kernel timestamps and store them with the SCR STC and SOF fields
+	 *   in the ring buffer
+	 */
+	if (has_pts && buf != NULL)
+		buf->pts = get_unaligned_le32(&data[2]);
+
+	if (!has_scr)
+		return;
+
+	/* To limit the amount of data, drop SCRs with an SOF identical to the
+	 * previous one.
+	 */
+	dev_sof = get_unaligned_le16(&data[header_size - 2]);
+	if (dev_sof == stream->clock.last_sof)
+		return;
+
+	stream->clock.last_sof = dev_sof;
+
+	host_sof = usb_get_current_frame_number(stream->dev->udev);
+	time = uvc_video_get_time();
+
+	/* The UVC specification allows device implementations that can't obtain
+	 * the USB frame number to keep their own frame counters as long as they
+	 * match the size and frequency of the frame number associated with USB
+	 * SOF tokens. The SOF values sent by such devices differ from the USB
+	 * SOF tokens by a fixed offset that needs to be estimated and accounted
+	 * for to make timestamp recovery as accurate as possible.
+	 *
+	 * The offset is estimated the first time a device SOF value is received
+	 * as the difference between the host and device SOF values. As the two
+	 * SOF values can differ slightly due to transmission delays, consider
+	 * that the offset is null if the difference is not higher than 10 ms
+	 * (negative differences can not happen and are thus considered as an
+	 * offset). The video commit control wDelay field should be used to
+	 * compute a dynamic threshold instead of using a fixed 10 ms value, but
+	 * devices don't report reliable wDelay values.
+	 *
+	 * See uvc_video_clock_host_sof() for an explanation regarding why only
+	 * the 8 LSBs of the delta are kept.
+	 */
+	if (stream->clock.sof_offset == (u16)-1) {
+		u16 delta_sof = (host_sof - dev_sof) & 255;
+		if (delta_sof >= 10)
+			stream->clock.sof_offset = delta_sof;
+		else
+			stream->clock.sof_offset = 0;
+	}
+
+	dev_sof = (dev_sof + stream->clock.sof_offset) & 2047;
+
+	spin_lock_irqsave(&stream->clock.lock, flags);
+
+	sample = &stream->clock.samples[stream->clock.head];
+	sample->dev_stc = get_unaligned_le32(&data[header_size - 6]);
+	sample->dev_sof = dev_sof;
+	sample->host_sof = host_sof;
+	sample->host_time = time;
+
+	/* Update the sliding window head and count. */
+	stream->clock.head = (stream->clock.head + 1) % stream->clock.size;
+
+	if (stream->clock.count < stream->clock.size)
+		stream->clock.count++;
+
+	spin_unlock_irqrestore(&stream->clock.lock, flags);
+}
+
+static void uvc_video_clock_reset(struct uvc_streaming *stream)
+{
+	struct uvc_clock *clock = &stream->clock;
+
+	clock->head = 0;
+	clock->count = 0;
+	clock->last_sof = -1;
+	clock->sof_offset = -1;
+}
+
+static int uvc_video_clock_init(struct uvc_streaming *stream)
+{
+	struct uvc_clock *clock = &stream->clock;
+
+	spin_lock_init(&clock->lock);
+	clock->size = 32;
+
+	clock->samples = kmalloc_array(clock->size, sizeof(*clock->samples),
+				       GFP_KERNEL);
+	if (clock->samples == NULL)
+		return -ENOMEM;
+
+	uvc_video_clock_reset(stream);
+
+	return 0;
+}
+
+static void uvc_video_clock_cleanup(struct uvc_streaming *stream)
+{
+	kfree(stream->clock.samples);
+	stream->clock.samples = NULL;
+}
+
+/*
+ * uvc_video_clock_host_sof - Return the host SOF value for a clock sample
+ *
+ * Host SOF counters reported by usb_get_current_frame_number() usually don't
+ * cover the whole 11-bits SOF range (0-2047) but are limited to the HCI frame
+ * schedule window. They can be limited to 8, 9 or 10 bits depending on the host
+ * controller and its configuration.
+ *
+ * We thus need to recover the SOF value corresponding to the host frame number.
+ * As the device and host frame numbers are sampled in a short interval, the
+ * difference between their values should be equal to a small delta plus an
+ * integer multiple of 256 caused by the host frame number limited precision.
+ *
+ * To obtain the recovered host SOF value, compute the small delta by masking
+ * the high bits of the host frame counter and device SOF difference and add it
+ * to the device SOF value.
+ */
+static u16 uvc_video_clock_host_sof(const struct uvc_clock_sample *sample)
+{
+	/* The delta value can be negative. */
+	s8 delta_sof;
+
+	delta_sof = (sample->host_sof - sample->dev_sof) & 255;
+
+	return (sample->dev_sof + delta_sof) & 2047;
+}
+
+/*
+ * uvc_video_clock_update - Update the buffer timestamp
+ *
+ * This function converts the buffer PTS timestamp to the host clock domain by
+ * going through the USB SOF clock domain and stores the result in the V4L2
+ * buffer timestamp field.
+ *
+ * The relationship between the device clock and the host clock isn't known.
+ * However, the device and the host share the common USB SOF clock which can be
+ * used to recover that relationship.
+ *
+ * The relationship between the device clock and the USB SOF clock is considered
+ * to be linear over the clock samples sliding window and is given by
+ *
+ * SOF = m * PTS + p
+ *
+ * Several methods to compute the slope (m) and intercept (p) can be used. As
+ * the clock drift should be small compared to the sliding window size, we
+ * assume that the line that goes through the points at both ends of the window
+ * is a good approximation. Naming those points P1 and P2, we get
+ *
+ * SOF = (SOF2 - SOF1) / (STC2 - STC1) * PTS
+ *     + (SOF1 * STC2 - SOF2 * STC1) / (STC2 - STC1)
+ *
+ * or
+ *
+ * SOF = ((SOF2 - SOF1) * PTS + SOF1 * STC2 - SOF2 * STC1) / (STC2 - STC1)   (1)
+ *
+ * to avoid losing precision in the division. Similarly, the host timestamp is
+ * computed with
+ *
+ * TS = ((TS2 - TS1) * PTS + TS1 * SOF2 - TS2 * SOF1) / (SOF2 - SOF1)	     (2)
+ *
+ * SOF values are coded on 11 bits by USB. We extend their precision with 16
+ * decimal bits, leading to a 11.16 coding.
+ *
+ * TODO: To avoid surprises with device clock values, PTS/STC timestamps should
+ * be normalized using the nominal device clock frequency reported through the
+ * UVC descriptors.
+ *
+ * Both the PTS/STC and SOF counters roll over, after a fixed but device
+ * specific amount of time for PTS/STC and after 2048ms for SOF. As long as the
+ * sliding window size is smaller than the rollover period, differences computed
+ * on unsigned integers will produce the correct result. However, the p term in
+ * the linear relations will be miscomputed.
+ *
+ * To fix the issue, we subtract a constant from the PTS and STC values to bring
+ * PTS to half the 32 bit STC range. The sliding window STC values then fit into
+ * the 32 bit range without any rollover.
+ *
+ * Similarly, we add 2048 to the device SOF values to make sure that the SOF
+ * computed by (1) will never be smaller than 0. This offset is then compensated
+ * by adding 2048 to the SOF values used in (2). However, this doesn't prevent
+ * rollovers between (1) and (2): the SOF value computed by (1) can be slightly
+ * lower than 4096, and the host SOF counters can have rolled over to 2048. This
+ * case is handled by subtracting 2048 from the SOF value if it exceeds the host
+ * SOF value at the end of the sliding window.
+ *
+ * Finally we subtract a constant from the host timestamps to bring the first
+ * timestamp of the sliding window to 1s.
+ */
+void uvc_video_clock_update(struct uvc_streaming *stream,
+			    struct vb2_v4l2_buffer *vbuf,
+			    struct uvc_buffer *buf)
+{
+	struct uvc_clock *clock = &stream->clock;
+	struct uvc_clock_sample *first;
+	struct uvc_clock_sample *last;
+	unsigned long flags;
+	u64 timestamp;
+	u32 delta_stc;
+	u32 y1, y2;
+	u32 x1, x2;
+	u32 mean;
+	u32 sof;
+	u64 y;
+
+	if (!uvc_hw_timestamps_param)
+		return;
+
+	spin_lock_irqsave(&clock->lock, flags);
+
+	if (clock->count < clock->size)
+		goto done;
+
+	first = &clock->samples[clock->head];
+	last = &clock->samples[(clock->head - 1) % clock->size];
+
+	/* First step, PTS to SOF conversion. */
+	delta_stc = buf->pts - (1UL << 31);
+	x1 = first->dev_stc - delta_stc;
+	x2 = last->dev_stc - delta_stc;
+	if (x1 == x2)
+		goto done;
+
+	y1 = (first->dev_sof + 2048) << 16;
+	y2 = (last->dev_sof + 2048) << 16;
+	if (y2 < y1)
+		y2 += 2048 << 16;
+
+	y = (u64)(y2 - y1) * (1ULL << 31) + (u64)y1 * (u64)x2
+	  - (u64)y2 * (u64)x1;
+	y = div_u64(y, x2 - x1);
+
+	sof = y;
+
+	uvc_trace(UVC_TRACE_CLOCK, "%s: PTS %u y %llu.%06llu SOF %u.%06llu "
+		  "(x1 %u x2 %u y1 %u y2 %u SOF offset %u)\n",
+		  stream->dev->name, buf->pts,
+		  y >> 16, div_u64((y & 0xffff) * 1000000, 65536),
+		  sof >> 16, div_u64(((u64)sof & 0xffff) * 1000000LLU, 65536),
+		  x1, x2, y1, y2, clock->sof_offset);
+
+	/* Second step, SOF to host clock conversion. */
+	x1 = (uvc_video_clock_host_sof(first) + 2048) << 16;
+	x2 = (uvc_video_clock_host_sof(last) + 2048) << 16;
+	if (x2 < x1)
+		x2 += 2048 << 16;
+	if (x1 == x2)
+		goto done;
+
+	y1 = NSEC_PER_SEC;
+	y2 = (u32)ktime_to_ns(ktime_sub(last->host_time, first->host_time)) + y1;
+
+	/* Interpolated and host SOF timestamps can wrap around at slightly
+	 * different times. Handle this by adding or removing 2048 to or from
+	 * the computed SOF value to keep it close to the SOF samples mean
+	 * value.
+	 */
+	mean = (x1 + x2) / 2;
+	if (mean - (1024 << 16) > sof)
+		sof += 2048 << 16;
+	else if (sof > mean + (1024 << 16))
+		sof -= 2048 << 16;
+
+	y = (u64)(y2 - y1) * (u64)sof + (u64)y1 * (u64)x2
+	  - (u64)y2 * (u64)x1;
+	y = div_u64(y, x2 - x1);
+
+	timestamp = ktime_to_ns(first->host_time) + y - y1;
+
+	uvc_trace(UVC_TRACE_CLOCK, "%s: SOF %u.%06llu y %llu ts %llu "
+		  "buf ts %llu (x1 %u/%u/%u x2 %u/%u/%u y1 %u y2 %u)\n",
+		  stream->dev->name,
+		  sof >> 16, div_u64(((u64)sof & 0xffff) * 1000000LLU, 65536),
+		  y, timestamp, vbuf->vb2_buf.timestamp,
+		  x1, first->host_sof, first->dev_sof,
+		  x2, last->host_sof, last->dev_sof, y1, y2);
+
+	/* Update the V4L2 buffer. */
+	vbuf->vb2_buf.timestamp = timestamp;
+
+done:
+	spin_unlock_irqrestore(&clock->lock, flags);
+}
+
+/* ------------------------------------------------------------------------
+ * Stream statistics
+ */
+
+static void uvc_video_stats_decode(struct uvc_streaming *stream,
+		const u8 *data, int len)
+{
+	unsigned int header_size;
+	bool has_pts = false;
+	bool has_scr = false;
+	u16 uninitialized_var(scr_sof);
+	u32 uninitialized_var(scr_stc);
+	u32 uninitialized_var(pts);
+
+	if (stream->stats.stream.nb_frames == 0 &&
+	    stream->stats.frame.nb_packets == 0)
+		stream->stats.stream.start_ts = ktime_get();
+
+	switch (data[1] & (UVC_STREAM_PTS | UVC_STREAM_SCR)) {
+	case UVC_STREAM_PTS | UVC_STREAM_SCR:
+		header_size = 12;
+		has_pts = true;
+		has_scr = true;
+		break;
+	case UVC_STREAM_PTS:
+		header_size = 6;
+		has_pts = true;
+		break;
+	case UVC_STREAM_SCR:
+		header_size = 8;
+		has_scr = true;
+		break;
+	default:
+		header_size = 2;
+		break;
+	}
+
+	/* Check for invalid headers. */
+	if (len < header_size || data[0] < header_size) {
+		stream->stats.frame.nb_invalid++;
+		return;
+	}
+
+	/* Extract the timestamps. */
+	if (has_pts)
+		pts = get_unaligned_le32(&data[2]);
+
+	if (has_scr) {
+		scr_stc = get_unaligned_le32(&data[header_size - 6]);
+		scr_sof = get_unaligned_le16(&data[header_size - 2]);
+	}
+
+	/* Is PTS constant through the whole frame ? */
+	if (has_pts && stream->stats.frame.nb_pts) {
+		if (stream->stats.frame.pts != pts) {
+			stream->stats.frame.nb_pts_diffs++;
+			stream->stats.frame.last_pts_diff =
+				stream->stats.frame.nb_packets;
+		}
+	}
+
+	if (has_pts) {
+		stream->stats.frame.nb_pts++;
+		stream->stats.frame.pts = pts;
+	}
+
+	/* Do all frames have a PTS in their first non-empty packet, or before
+	 * their first empty packet ?
+	 */
+	if (stream->stats.frame.size == 0) {
+		if (len > header_size)
+			stream->stats.frame.has_initial_pts = has_pts;
+		if (len == header_size && has_pts)
+			stream->stats.frame.has_early_pts = true;
+	}
+
+	/* Do the SCR.STC and SCR.SOF fields vary through the frame ? */
+	if (has_scr && stream->stats.frame.nb_scr) {
+		if (stream->stats.frame.scr_stc != scr_stc)
+			stream->stats.frame.nb_scr_diffs++;
+	}
+
+	if (has_scr) {
+		/* Expand the SOF counter to 32 bits and store its value. */
+		if (stream->stats.stream.nb_frames > 0 ||
+		    stream->stats.frame.nb_scr > 0)
+			stream->stats.stream.scr_sof_count +=
+				(scr_sof - stream->stats.stream.scr_sof) % 2048;
+		stream->stats.stream.scr_sof = scr_sof;
+
+		stream->stats.frame.nb_scr++;
+		stream->stats.frame.scr_stc = scr_stc;
+		stream->stats.frame.scr_sof = scr_sof;
+
+		if (scr_sof < stream->stats.stream.min_sof)
+			stream->stats.stream.min_sof = scr_sof;
+		if (scr_sof > stream->stats.stream.max_sof)
+			stream->stats.stream.max_sof = scr_sof;
+	}
+
+	/* Record the first non-empty packet number. */
+	if (stream->stats.frame.size == 0 && len > header_size)
+		stream->stats.frame.first_data = stream->stats.frame.nb_packets;
+
+	/* Update the frame size. */
+	stream->stats.frame.size += len - header_size;
+
+	/* Update the packets counters. */
+	stream->stats.frame.nb_packets++;
+	if (len <= header_size)
+		stream->stats.frame.nb_empty++;
+
+	if (data[1] & UVC_STREAM_ERR)
+		stream->stats.frame.nb_errors++;
+}
+
+static void uvc_video_stats_update(struct uvc_streaming *stream)
+{
+	struct uvc_stats_frame *frame = &stream->stats.frame;
+
+	uvc_trace(UVC_TRACE_STATS, "frame %u stats: %u/%u/%u packets, "
+		  "%u/%u/%u pts (%searly %sinitial), %u/%u scr, "
+		  "last pts/stc/sof %u/%u/%u\n",
+		  stream->sequence, frame->first_data,
+		  frame->nb_packets - frame->nb_empty, frame->nb_packets,
+		  frame->nb_pts_diffs, frame->last_pts_diff, frame->nb_pts,
+		  frame->has_early_pts ? "" : "!",
+		  frame->has_initial_pts ? "" : "!",
+		  frame->nb_scr_diffs, frame->nb_scr,
+		  frame->pts, frame->scr_stc, frame->scr_sof);
+
+	stream->stats.stream.nb_frames++;
+	stream->stats.stream.nb_packets += stream->stats.frame.nb_packets;
+	stream->stats.stream.nb_empty += stream->stats.frame.nb_empty;
+	stream->stats.stream.nb_errors += stream->stats.frame.nb_errors;
+	stream->stats.stream.nb_invalid += stream->stats.frame.nb_invalid;
+
+	if (frame->has_early_pts)
+		stream->stats.stream.nb_pts_early++;
+	if (frame->has_initial_pts)
+		stream->stats.stream.nb_pts_initial++;
+	if (frame->last_pts_diff <= frame->first_data)
+		stream->stats.stream.nb_pts_constant++;
+	if (frame->nb_scr >= frame->nb_packets - frame->nb_empty)
+		stream->stats.stream.nb_scr_count_ok++;
+	if (frame->nb_scr_diffs + 1 == frame->nb_scr)
+		stream->stats.stream.nb_scr_diffs_ok++;
+
+	memset(&stream->stats.frame, 0, sizeof(stream->stats.frame));
+}
+
+size_t uvc_video_stats_dump(struct uvc_streaming *stream, char *buf,
+			    size_t size)
+{
+	unsigned int scr_sof_freq;
+	unsigned int duration;
+	size_t count = 0;
+
+	/* Compute the SCR.SOF frequency estimate. At the nominal 1kHz SOF
+	 * frequency this will not overflow before more than 1h.
+	 */
+	duration = ktime_ms_delta(stream->stats.stream.stop_ts,
+				  stream->stats.stream.start_ts);
+	if (duration != 0)
+		scr_sof_freq = stream->stats.stream.scr_sof_count * 1000
+			     / duration;
+	else
+		scr_sof_freq = 0;
+
+	count += scnprintf(buf + count, size - count,
+			   "frames:  %u\npackets: %u\nempty:   %u\n"
+			   "errors:  %u\ninvalid: %u\n",
+			   stream->stats.stream.nb_frames,
+			   stream->stats.stream.nb_packets,
+			   stream->stats.stream.nb_empty,
+			   stream->stats.stream.nb_errors,
+			   stream->stats.stream.nb_invalid);
+	count += scnprintf(buf + count, size - count,
+			   "pts: %u early, %u initial, %u ok\n",
+			   stream->stats.stream.nb_pts_early,
+			   stream->stats.stream.nb_pts_initial,
+			   stream->stats.stream.nb_pts_constant);
+	count += scnprintf(buf + count, size - count,
+			   "scr: %u count ok, %u diff ok\n",
+			   stream->stats.stream.nb_scr_count_ok,
+			   stream->stats.stream.nb_scr_diffs_ok);
+	count += scnprintf(buf + count, size - count,
+			   "sof: %u <= sof <= %u, freq %u.%03u kHz\n",
+			   stream->stats.stream.min_sof,
+			   stream->stats.stream.max_sof,
+			   scr_sof_freq / 1000, scr_sof_freq % 1000);
+
+	return count;
+}
+
+static void uvc_video_stats_start(struct uvc_streaming *stream)
+{
+	memset(&stream->stats, 0, sizeof(stream->stats));
+	stream->stats.stream.min_sof = 2048;
+}
+
+static void uvc_video_stats_stop(struct uvc_streaming *stream)
+{
+	stream->stats.stream.stop_ts = ktime_get();
+}
+
+/* ------------------------------------------------------------------------
+ * Video codecs
+ */
+
+/* Video payload decoding is handled by uvc_video_decode_start(),
+ * uvc_video_decode_data() and uvc_video_decode_end().
+ *
+ * uvc_video_decode_start is called with URB data at the start of a bulk or
+ * isochronous payload. It processes header data and returns the header size
+ * in bytes if successful. If an error occurs, it returns a negative error
+ * code. The following error codes have special meanings.
+ *
+ * - EAGAIN informs the caller that the current video buffer should be marked
+ *   as done, and that the function should be called again with the same data
+ *   and a new video buffer. This is used when end of frame conditions can be
+ *   reliably detected at the beginning of the next frame only.
+ *
+ * If an error other than -EAGAIN is returned, the caller will drop the current
+ * payload. No call to uvc_video_decode_data and uvc_video_decode_end will be
+ * made until the next payload. -ENODATA can be used to drop the current
+ * payload if no other error code is appropriate.
+ *
+ * uvc_video_decode_data is called for every URB with URB data. It copies the
+ * data to the video buffer.
+ *
+ * uvc_video_decode_end is called with header data at the end of a bulk or
+ * isochronous payload. It performs any additional header data processing and
+ * returns 0 or a negative error code if an error occurred. As header data have
+ * already been processed by uvc_video_decode_start, this functions isn't
+ * required to perform sanity checks a second time.
+ *
+ * For isochronous transfers where a payload is always transferred in a single
+ * URB, the three functions will be called in a row.
+ *
+ * To let the decoder process header data and update its internal state even
+ * when no video buffer is available, uvc_video_decode_start must be prepared
+ * to be called with a NULL buf parameter. uvc_video_decode_data and
+ * uvc_video_decode_end will never be called with a NULL buffer.
+ */
+static int uvc_video_decode_start(struct uvc_streaming *stream,
+		struct uvc_buffer *buf, const u8 *data, int len)
+{
+	u8 fid;
+
+	/* Sanity checks:
+	 * - packet must be at least 2 bytes long
+	 * - bHeaderLength value must be at least 2 bytes (see above)
+	 * - bHeaderLength value can't be larger than the packet size.
+	 */
+	if (len < 2 || data[0] < 2 || data[0] > len) {
+		stream->stats.frame.nb_invalid++;
+		return -EINVAL;
+	}
+
+	fid = data[1] & UVC_STREAM_FID;
+
+	/* Increase the sequence number regardless of any buffer states, so
+	 * that discontinuous sequence numbers always indicate lost frames.
+	 */
+	if (stream->last_fid != fid) {
+		stream->sequence++;
+		if (stream->sequence)
+			uvc_video_stats_update(stream);
+	}
+
+	uvc_video_clock_decode(stream, buf, data, len);
+	uvc_video_stats_decode(stream, data, len);
+
+	/* Store the payload FID bit and return immediately when the buffer is
+	 * NULL.
+	 */
+	if (buf == NULL) {
+		stream->last_fid = fid;
+		return -ENODATA;
+	}
+
+	/* Mark the buffer as bad if the error bit is set. */
+	if (data[1] & UVC_STREAM_ERR) {
+		uvc_trace(UVC_TRACE_FRAME, "Marking buffer as bad (error bit "
+			  "set).\n");
+		buf->error = 1;
+	}
+
+	/* Synchronize to the input stream by waiting for the FID bit to be
+	 * toggled when the the buffer state is not UVC_BUF_STATE_ACTIVE.
+	 * stream->last_fid is initialized to -1, so the first isochronous
+	 * frame will always be in sync.
+	 *
+	 * If the device doesn't toggle the FID bit, invert stream->last_fid
+	 * when the EOF bit is set to force synchronisation on the next packet.
+	 */
+	if (buf->state != UVC_BUF_STATE_ACTIVE) {
+		if (fid == stream->last_fid) {
+			uvc_trace(UVC_TRACE_FRAME, "Dropping payload (out of "
+				"sync).\n");
+			if ((stream->dev->quirks & UVC_QUIRK_STREAM_NO_FID) &&
+			    (data[1] & UVC_STREAM_EOF))
+				stream->last_fid ^= UVC_STREAM_FID;
+			return -ENODATA;
+		}
+
+		buf->buf.field = V4L2_FIELD_NONE;
+		buf->buf.sequence = stream->sequence;
+		buf->buf.vb2_buf.timestamp = ktime_to_ns(uvc_video_get_time());
+
+		/* TODO: Handle PTS and SCR. */
+		buf->state = UVC_BUF_STATE_ACTIVE;
+	}
+
+	/* Mark the buffer as done if we're at the beginning of a new frame.
+	 * End of frame detection is better implemented by checking the EOF
+	 * bit (FID bit toggling is delayed by one frame compared to the EOF
+	 * bit), but some devices don't set the bit at end of frame (and the
+	 * last payload can be lost anyway). We thus must check if the FID has
+	 * been toggled.
+	 *
+	 * stream->last_fid is initialized to -1, so the first isochronous
+	 * frame will never trigger an end of frame detection.
+	 *
+	 * Empty buffers (bytesused == 0) don't trigger end of frame detection
+	 * as it doesn't make sense to return an empty buffer. This also
+	 * avoids detecting end of frame conditions at FID toggling if the
+	 * previous payload had the EOF bit set.
+	 */
+	if (fid != stream->last_fid && buf->bytesused != 0) {
+		uvc_trace(UVC_TRACE_FRAME, "Frame complete (FID bit "
+				"toggled).\n");
+		buf->state = UVC_BUF_STATE_READY;
+		return -EAGAIN;
+	}
+
+	stream->last_fid = fid;
+
+	return data[0];
+}
+
+static void uvc_video_decode_data(struct uvc_streaming *stream,
+		struct uvc_buffer *buf, const u8 *data, int len)
+{
+	unsigned int maxlen, nbytes;
+	void *mem;
+
+	if (len <= 0)
+		return;
+
+	/* Copy the video data to the buffer. */
+	maxlen = buf->length - buf->bytesused;
+	mem = buf->mem + buf->bytesused;
+	nbytes = min((unsigned int)len, maxlen);
+	memcpy(mem, data, nbytes);
+	buf->bytesused += nbytes;
+
+	/* Complete the current frame if the buffer size was exceeded. */
+	if (len > maxlen) {
+		uvc_trace(UVC_TRACE_FRAME, "Frame complete (overflow).\n");
+		buf->error = 1;
+		buf->state = UVC_BUF_STATE_READY;
+	}
+}
+
+static void uvc_video_decode_end(struct uvc_streaming *stream,
+		struct uvc_buffer *buf, const u8 *data, int len)
+{
+	/* Mark the buffer as done if the EOF marker is set. */
+	if (data[1] & UVC_STREAM_EOF && buf->bytesused != 0) {
+		uvc_trace(UVC_TRACE_FRAME, "Frame complete (EOF found).\n");
+		if (data[0] == len)
+			uvc_trace(UVC_TRACE_FRAME, "EOF in empty payload.\n");
+		buf->state = UVC_BUF_STATE_READY;
+		if (stream->dev->quirks & UVC_QUIRK_STREAM_NO_FID)
+			stream->last_fid ^= UVC_STREAM_FID;
+	}
+}
+
+/* Video payload encoding is handled by uvc_video_encode_header() and
+ * uvc_video_encode_data(). Only bulk transfers are currently supported.
+ *
+ * uvc_video_encode_header is called at the start of a payload. It adds header
+ * data to the transfer buffer and returns the header size. As the only known
+ * UVC output device transfers a whole frame in a single payload, the EOF bit
+ * is always set in the header.
+ *
+ * uvc_video_encode_data is called for every URB and copies the data from the
+ * video buffer to the transfer buffer.
+ */
+static int uvc_video_encode_header(struct uvc_streaming *stream,
+		struct uvc_buffer *buf, u8 *data, int len)
+{
+	data[0] = 2;	/* Header length */
+	data[1] = UVC_STREAM_EOH | UVC_STREAM_EOF
+		| (stream->last_fid & UVC_STREAM_FID);
+	return 2;
+}
+
+static int uvc_video_encode_data(struct uvc_streaming *stream,
+		struct uvc_buffer *buf, u8 *data, int len)
+{
+	struct uvc_video_queue *queue = &stream->queue;
+	unsigned int nbytes;
+	void *mem;
+
+	/* Copy video data to the URB buffer. */
+	mem = buf->mem + queue->buf_used;
+	nbytes = min((unsigned int)len, buf->bytesused - queue->buf_used);
+	nbytes = min(stream->bulk.max_payload_size - stream->bulk.payload_size,
+			nbytes);
+	memcpy(data, mem, nbytes);
+
+	queue->buf_used += nbytes;
+
+	return nbytes;
+}
+
+/* ------------------------------------------------------------------------
+ * Metadata
+ */
+
+/*
+ * Additionally to the payload headers we also want to provide the user with USB
+ * Frame Numbers and system time values. The resulting buffer is thus composed
+ * of blocks, containing a 64-bit timestamp in  nanoseconds, a 16-bit USB Frame
+ * Number, and a copy of the payload header.
+ *
+ * Ideally we want to capture all payload headers for each frame. However, their
+ * number is unknown and unbound. We thus drop headers that contain no vendor
+ * data and that either contain no SCR value or an SCR value identical to the
+ * previous header.
+ */
+static void uvc_video_decode_meta(struct uvc_streaming *stream,
+				  struct uvc_buffer *meta_buf,
+				  const u8 *mem, unsigned int length)
+{
+	struct uvc_meta_buf *meta;
+	size_t len_std = 2;
+	bool has_pts, has_scr;
+	unsigned long flags;
+	unsigned int sof;
+	ktime_t time;
+	const u8 *scr;
+
+	if (!meta_buf || length == 2)
+		return;
+
+	if (meta_buf->length - meta_buf->bytesused <
+	    length + sizeof(meta->ns) + sizeof(meta->sof)) {
+		meta_buf->error = 1;
+		return;
+	}
+
+	has_pts = mem[1] & UVC_STREAM_PTS;
+	has_scr = mem[1] & UVC_STREAM_SCR;
+
+	if (has_pts) {
+		len_std += 4;
+		scr = mem + 6;
+	} else {
+		scr = mem + 2;
+	}
+
+	if (has_scr)
+		len_std += 6;
+
+	if (stream->meta.format == V4L2_META_FMT_UVC)
+		length = len_std;
+
+	if (length == len_std && (!has_scr ||
+				  !memcmp(scr, stream->clock.last_scr, 6)))
+		return;
+
+	meta = (struct uvc_meta_buf *)((u8 *)meta_buf->mem + meta_buf->bytesused);
+	local_irq_save(flags);
+	time = uvc_video_get_time();
+	sof = usb_get_current_frame_number(stream->dev->udev);
+	local_irq_restore(flags);
+	put_unaligned(ktime_to_ns(time), &meta->ns);
+	put_unaligned(sof, &meta->sof);
+
+	if (has_scr)
+		memcpy(stream->clock.last_scr, scr, 6);
+
+	memcpy(&meta->length, mem, length);
+	meta_buf->bytesused += length + sizeof(meta->ns) + sizeof(meta->sof);
+
+	uvc_trace(UVC_TRACE_FRAME,
+		  "%s(): t-sys %lluns, SOF %u, len %u, flags 0x%x, PTS %u, STC %u frame SOF %u\n",
+		  __func__, ktime_to_ns(time), meta->sof, meta->length,
+		  meta->flags,
+		  has_pts ? *(u32 *)meta->buf : 0,
+		  has_scr ? *(u32 *)scr : 0,
+		  has_scr ? *(u32 *)(scr + 4) & 0x7ff : 0);
+}
+
+/* ------------------------------------------------------------------------
+ * URB handling
+ */
+
+/*
+ * Set error flag for incomplete buffer.
+ */
+static void uvc_video_validate_buffer(const struct uvc_streaming *stream,
+				      struct uvc_buffer *buf)
+{
+	if (stream->ctrl.dwMaxVideoFrameSize != buf->bytesused &&
+	    !(stream->cur_format->flags & UVC_FMT_FLAG_COMPRESSED))
+		buf->error = 1;
+}
+
+/*
+ * Completion handler for video URBs.
+ */
+
+static void uvc_video_next_buffers(struct uvc_streaming *stream,
+		struct uvc_buffer **video_buf, struct uvc_buffer **meta_buf)
+{
+	uvc_video_validate_buffer(stream, *video_buf);
+
+	if (*meta_buf) {
+		struct vb2_v4l2_buffer *vb2_meta = &(*meta_buf)->buf;
+		const struct vb2_v4l2_buffer *vb2_video = &(*video_buf)->buf;
+
+		vb2_meta->sequence = vb2_video->sequence;
+		vb2_meta->field = vb2_video->field;
+		vb2_meta->vb2_buf.timestamp = vb2_video->vb2_buf.timestamp;
+
+		(*meta_buf)->state = UVC_BUF_STATE_READY;
+		if (!(*meta_buf)->error)
+			(*meta_buf)->error = (*video_buf)->error;
+		*meta_buf = uvc_queue_next_buffer(&stream->meta.queue,
+						  *meta_buf);
+	}
+	*video_buf = uvc_queue_next_buffer(&stream->queue, *video_buf);
+}
+
+static void uvc_video_decode_isoc(struct urb *urb, struct uvc_streaming *stream,
+			struct uvc_buffer *buf, struct uvc_buffer *meta_buf)
+{
+	u8 *mem;
+	int ret, i;
+
+	for (i = 0; i < urb->number_of_packets; ++i) {
+		if (urb->iso_frame_desc[i].status < 0) {
+			uvc_trace(UVC_TRACE_FRAME, "USB isochronous frame "
+				"lost (%d).\n", urb->iso_frame_desc[i].status);
+			/* Mark the buffer as faulty. */
+			if (buf != NULL)
+				buf->error = 1;
+			continue;
+		}
+
+		/* Decode the payload header. */
+		mem = urb->transfer_buffer + urb->iso_frame_desc[i].offset;
+		do {
+			ret = uvc_video_decode_start(stream, buf, mem,
+				urb->iso_frame_desc[i].actual_length);
+			if (ret == -EAGAIN)
+				uvc_video_next_buffers(stream, &buf, &meta_buf);
+		} while (ret == -EAGAIN);
+
+		if (ret < 0)
+			continue;
+
+		uvc_video_decode_meta(stream, meta_buf, mem, ret);
+
+		/* Decode the payload data. */
+		uvc_video_decode_data(stream, buf, mem + ret,
+			urb->iso_frame_desc[i].actual_length - ret);
+
+		/* Process the header again. */
+		uvc_video_decode_end(stream, buf, mem,
+			urb->iso_frame_desc[i].actual_length);
+
+		if (buf->state == UVC_BUF_STATE_READY)
+			uvc_video_next_buffers(stream, &buf, &meta_buf);
+	}
+}
+
+static void uvc_video_decode_bulk(struct urb *urb, struct uvc_streaming *stream,
+			struct uvc_buffer *buf, struct uvc_buffer *meta_buf)
+{
+	u8 *mem;
+	int len, ret;
+
+	/*
+	 * Ignore ZLPs if they're not part of a frame, otherwise process them
+	 * to trigger the end of payload detection.
+	 */
+	if (urb->actual_length == 0 && stream->bulk.header_size == 0)
+		return;
+
+	mem = urb->transfer_buffer;
+	len = urb->actual_length;
+	stream->bulk.payload_size += len;
+
+	/* If the URB is the first of its payload, decode and save the
+	 * header.
+	 */
+	if (stream->bulk.header_size == 0 && !stream->bulk.skip_payload) {
+		do {
+			ret = uvc_video_decode_start(stream, buf, mem, len);
+			if (ret == -EAGAIN)
+				uvc_video_next_buffers(stream, &buf, &meta_buf);
+		} while (ret == -EAGAIN);
+
+		/* If an error occurred skip the rest of the payload. */
+		if (ret < 0 || buf == NULL) {
+			stream->bulk.skip_payload = 1;
+		} else {
+			memcpy(stream->bulk.header, mem, ret);
+			stream->bulk.header_size = ret;
+
+			uvc_video_decode_meta(stream, meta_buf, mem, ret);
+
+			mem += ret;
+			len -= ret;
+		}
+	}
+
+	/* The buffer queue might have been cancelled while a bulk transfer
+	 * was in progress, so we can reach here with buf equal to NULL. Make
+	 * sure buf is never dereferenced if NULL.
+	 */
+
+	/* Process video data. */
+	if (!stream->bulk.skip_payload && buf != NULL)
+		uvc_video_decode_data(stream, buf, mem, len);
+
+	/* Detect the payload end by a URB smaller than the maximum size (or
+	 * a payload size equal to the maximum) and process the header again.
+	 */
+	if (urb->actual_length < urb->transfer_buffer_length ||
+	    stream->bulk.payload_size >= stream->bulk.max_payload_size) {
+		if (!stream->bulk.skip_payload && buf != NULL) {
+			uvc_video_decode_end(stream, buf, stream->bulk.header,
+				stream->bulk.payload_size);
+			if (buf->state == UVC_BUF_STATE_READY)
+				uvc_video_next_buffers(stream, &buf, &meta_buf);
+		}
+
+		stream->bulk.header_size = 0;
+		stream->bulk.skip_payload = 0;
+		stream->bulk.payload_size = 0;
+	}
+}
+
+static void uvc_video_encode_bulk(struct urb *urb, struct uvc_streaming *stream,
+	struct uvc_buffer *buf, struct uvc_buffer *meta_buf)
+{
+	u8 *mem = urb->transfer_buffer;
+	int len = stream->urb_size, ret;
+
+	if (buf == NULL) {
+		urb->transfer_buffer_length = 0;
+		return;
+	}
+
+	/* If the URB is the first of its payload, add the header. */
+	if (stream->bulk.header_size == 0) {
+		ret = uvc_video_encode_header(stream, buf, mem, len);
+		stream->bulk.header_size = ret;
+		stream->bulk.payload_size += ret;
+		mem += ret;
+		len -= ret;
+	}
+
+	/* Process video data. */
+	ret = uvc_video_encode_data(stream, buf, mem, len);
+
+	stream->bulk.payload_size += ret;
+	len -= ret;
+
+	if (buf->bytesused == stream->queue.buf_used ||
+	    stream->bulk.payload_size == stream->bulk.max_payload_size) {
+		if (buf->bytesused == stream->queue.buf_used) {
+			stream->queue.buf_used = 0;
+			buf->state = UVC_BUF_STATE_READY;
+			buf->buf.sequence = ++stream->sequence;
+			uvc_queue_next_buffer(&stream->queue, buf);
+			stream->last_fid ^= UVC_STREAM_FID;
+		}
+
+		stream->bulk.header_size = 0;
+		stream->bulk.payload_size = 0;
+	}
+
+	urb->transfer_buffer_length = stream->urb_size - len;
+}
+
+static void uvc_video_complete(struct urb *urb)
+{
+	struct uvc_streaming *stream = urb->context;
+	struct uvc_video_queue *queue = &stream->queue;
+	struct uvc_video_queue *qmeta = &stream->meta.queue;
+	struct vb2_queue *vb2_qmeta = stream->meta.vdev.queue;
+	struct uvc_buffer *buf = NULL;
+	struct uvc_buffer *buf_meta = NULL;
+	unsigned long flags;
+	int ret;
+
+	switch (urb->status) {
+	case 0:
+		break;
+
+	default:
+		uvc_printk(KERN_WARNING, "Non-zero status (%d) in video "
+			"completion handler.\n", urb->status);
+		/* fall through */
+	case -ENOENT:		/* usb_kill_urb() called. */
+		if (stream->frozen)
+			return;
+		/* fall through */
+	case -ECONNRESET:	/* usb_unlink_urb() called. */
+	case -ESHUTDOWN:	/* The endpoint is being disabled. */
+		uvc_queue_cancel(queue, urb->status == -ESHUTDOWN);
+		if (vb2_qmeta)
+			uvc_queue_cancel(qmeta, urb->status == -ESHUTDOWN);
+		return;
+	}
+
+	spin_lock_irqsave(&queue->irqlock, flags);
+	if (!list_empty(&queue->irqqueue))
+		buf = list_first_entry(&queue->irqqueue, struct uvc_buffer,
+				       queue);
+	spin_unlock_irqrestore(&queue->irqlock, flags);
+
+	if (vb2_qmeta) {
+		spin_lock_irqsave(&qmeta->irqlock, flags);
+		if (!list_empty(&qmeta->irqqueue))
+			buf_meta = list_first_entry(&qmeta->irqqueue,
+						    struct uvc_buffer, queue);
+		spin_unlock_irqrestore(&qmeta->irqlock, flags);
+	}
+
+	stream->decode(urb, stream, buf, buf_meta);
+
+	if ((ret = usb_submit_urb(urb, GFP_ATOMIC)) < 0) {
+		uvc_printk(KERN_ERR, "Failed to resubmit video URB (%d).\n",
+			ret);
+	}
+}
+
+/*
+ * Free transfer buffers.
+ */
+static void uvc_free_urb_buffers(struct uvc_streaming *stream)
+{
+	unsigned int i;
+
+	for (i = 0; i < UVC_URBS; ++i) {
+		if (stream->urb_buffer[i]) {
+#ifndef CONFIG_DMA_NONCOHERENT
+			usb_free_coherent(stream->dev->udev, stream->urb_size,
+				stream->urb_buffer[i], stream->urb_dma[i]);
+#else
+			kfree(stream->urb_buffer[i]);
+#endif
+			stream->urb_buffer[i] = NULL;
+		}
+	}
+
+	stream->urb_size = 0;
+}
+
+/*
+ * Allocate transfer buffers. This function can be called with buffers
+ * already allocated when resuming from suspend, in which case it will
+ * return without touching the buffers.
+ *
+ * Limit the buffer size to UVC_MAX_PACKETS bulk/isochronous packets. If the
+ * system is too low on memory try successively smaller numbers of packets
+ * until allocation succeeds.
+ *
+ * Return the number of allocated packets on success or 0 when out of memory.
+ */
+static int uvc_alloc_urb_buffers(struct uvc_streaming *stream,
+	unsigned int size, unsigned int psize, gfp_t gfp_flags)
+{
+	unsigned int npackets;
+	unsigned int i;
+
+	/* Buffers are already allocated, bail out. */
+	if (stream->urb_size)
+		return stream->urb_size / psize;
+
+	/* Compute the number of packets. Bulk endpoints might transfer UVC
+	 * payloads across multiple URBs.
+	 */
+	npackets = DIV_ROUND_UP(size, psize);
+	if (npackets > UVC_MAX_PACKETS)
+		npackets = UVC_MAX_PACKETS;
+
+	/* Retry allocations until one succeed. */
+	for (; npackets > 1; npackets /= 2) {
+		for (i = 0; i < UVC_URBS; ++i) {
+			stream->urb_size = psize * npackets;
+#ifndef CONFIG_DMA_NONCOHERENT
+			stream->urb_buffer[i] = usb_alloc_coherent(
+				stream->dev->udev, stream->urb_size,
+				gfp_flags | __GFP_NOWARN, &stream->urb_dma[i]);
+#else
+			stream->urb_buffer[i] =
+			    kmalloc(stream->urb_size, gfp_flags | __GFP_NOWARN);
+#endif
+			if (!stream->urb_buffer[i]) {
+				uvc_free_urb_buffers(stream);
+				break;
+			}
+		}
+
+		if (i == UVC_URBS) {
+			uvc_trace(UVC_TRACE_VIDEO, "Allocated %u URB buffers "
+				"of %ux%u bytes each.\n", UVC_URBS, npackets,
+				psize);
+			return npackets;
+		}
+	}
+
+	uvc_trace(UVC_TRACE_VIDEO, "Failed to allocate URB buffers (%u bytes "
+		"per packet).\n", psize);
+	return 0;
+}
+
+/*
+ * Uninitialize isochronous/bulk URBs and free transfer buffers.
+ */
+static void uvc_uninit_video(struct uvc_streaming *stream, int free_buffers)
+{
+	struct urb *urb;
+	unsigned int i;
+
+	uvc_video_stats_stop(stream);
+
+	for (i = 0; i < UVC_URBS; ++i) {
+		urb = stream->urb[i];
+		if (urb == NULL)
+			continue;
+
+		usb_kill_urb(urb);
+		usb_free_urb(urb);
+		stream->urb[i] = NULL;
+	}
+
+	if (free_buffers)
+		uvc_free_urb_buffers(stream);
+}
+
+/*
+ * Compute the maximum number of bytes per interval for an endpoint.
+ */
+static unsigned int uvc_endpoint_max_bpi(struct usb_device *dev,
+					 struct usb_host_endpoint *ep)
+{
+	u16 psize;
+	u16 mult;
+
+	switch (dev->speed) {
+	case USB_SPEED_SUPER:
+	case USB_SPEED_SUPER_PLUS:
+		return le16_to_cpu(ep->ss_ep_comp.wBytesPerInterval);
+	case USB_SPEED_HIGH:
+		psize = usb_endpoint_maxp(&ep->desc);
+		mult = usb_endpoint_maxp_mult(&ep->desc);
+		return psize * mult;
+	case USB_SPEED_WIRELESS:
+		psize = usb_endpoint_maxp(&ep->desc);
+		return psize;
+	default:
+		psize = usb_endpoint_maxp(&ep->desc);
+		return psize;
+	}
+}
+
+/*
+ * Initialize isochronous URBs and allocate transfer buffers. The packet size
+ * is given by the endpoint.
+ */
+static int uvc_init_video_isoc(struct uvc_streaming *stream,
+	struct usb_host_endpoint *ep, gfp_t gfp_flags)
+{
+	struct urb *urb;
+	unsigned int npackets, i, j;
+	u16 psize;
+	u32 size;
+
+	psize = uvc_endpoint_max_bpi(stream->dev->udev, ep);
+	size = stream->ctrl.dwMaxVideoFrameSize;
+
+	npackets = uvc_alloc_urb_buffers(stream, size, psize, gfp_flags);
+	if (npackets == 0)
+		return -ENOMEM;
+
+	size = npackets * psize;
+
+	for (i = 0; i < UVC_URBS; ++i) {
+		urb = usb_alloc_urb(npackets, gfp_flags);
+		if (urb == NULL) {
+			uvc_uninit_video(stream, 1);
+			return -ENOMEM;
+		}
+
+		urb->dev = stream->dev->udev;
+		urb->context = stream;
+		urb->pipe = usb_rcvisocpipe(stream->dev->udev,
+				ep->desc.bEndpointAddress);
+#ifndef CONFIG_DMA_NONCOHERENT
+		urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
+		urb->transfer_dma = stream->urb_dma[i];
+#else
+		urb->transfer_flags = URB_ISO_ASAP;
+#endif
+		urb->interval = ep->desc.bInterval;
+		urb->transfer_buffer = stream->urb_buffer[i];
+		urb->complete = uvc_video_complete;
+		urb->number_of_packets = npackets;
+		urb->transfer_buffer_length = size;
+
+		for (j = 0; j < npackets; ++j) {
+			urb->iso_frame_desc[j].offset = j * psize;
+			urb->iso_frame_desc[j].length = psize;
+		}
+
+		stream->urb[i] = urb;
+	}
+
+	return 0;
+}
+
+/*
+ * Initialize bulk URBs and allocate transfer buffers. The packet size is
+ * given by the endpoint.
+ */
+static int uvc_init_video_bulk(struct uvc_streaming *stream,
+	struct usb_host_endpoint *ep, gfp_t gfp_flags)
+{
+	struct urb *urb;
+	unsigned int npackets, pipe, i;
+	u16 psize;
+	u32 size;
+
+	psize = usb_endpoint_maxp(&ep->desc);
+	size = stream->ctrl.dwMaxPayloadTransferSize;
+	stream->bulk.max_payload_size = size;
+
+	npackets = uvc_alloc_urb_buffers(stream, size, psize, gfp_flags);
+	if (npackets == 0)
+		return -ENOMEM;
+
+	size = npackets * psize;
+
+	if (usb_endpoint_dir_in(&ep->desc))
+		pipe = usb_rcvbulkpipe(stream->dev->udev,
+				       ep->desc.bEndpointAddress);
+	else
+		pipe = usb_sndbulkpipe(stream->dev->udev,
+				       ep->desc.bEndpointAddress);
+
+	if (stream->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
+		size = 0;
+
+	for (i = 0; i < UVC_URBS; ++i) {
+		urb = usb_alloc_urb(0, gfp_flags);
+		if (urb == NULL) {
+			uvc_uninit_video(stream, 1);
+			return -ENOMEM;
+		}
+
+		usb_fill_bulk_urb(urb, stream->dev->udev, pipe,
+			stream->urb_buffer[i], size, uvc_video_complete,
+			stream);
+#ifndef CONFIG_DMA_NONCOHERENT
+		urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+		urb->transfer_dma = stream->urb_dma[i];
+#endif
+
+		stream->urb[i] = urb;
+	}
+
+	return 0;
+}
+
+/*
+ * Initialize isochronous/bulk URBs and allocate transfer buffers.
+ */
+static int uvc_init_video(struct uvc_streaming *stream, gfp_t gfp_flags)
+{
+	struct usb_interface *intf = stream->intf;
+	struct usb_host_endpoint *ep;
+	unsigned int i;
+	int ret;
+
+	stream->sequence = -1;
+	stream->last_fid = -1;
+	stream->bulk.header_size = 0;
+	stream->bulk.skip_payload = 0;
+	stream->bulk.payload_size = 0;
+
+	uvc_video_stats_start(stream);
+
+	if (intf->num_altsetting > 1) {
+		struct usb_host_endpoint *best_ep = NULL;
+		unsigned int best_psize = UINT_MAX;
+		unsigned int bandwidth;
+		unsigned int uninitialized_var(altsetting);
+		int intfnum = stream->intfnum;
+
+		/* Isochronous endpoint, select the alternate setting. */
+		bandwidth = stream->ctrl.dwMaxPayloadTransferSize;
+
+		if (bandwidth == 0) {
+			uvc_trace(UVC_TRACE_VIDEO, "Device requested null "
+				"bandwidth, defaulting to lowest.\n");
+			bandwidth = 1;
+		} else {
+			uvc_trace(UVC_TRACE_VIDEO, "Device requested %u "
+				"B/frame bandwidth.\n", bandwidth);
+		}
+
+		for (i = 0; i < intf->num_altsetting; ++i) {
+			struct usb_host_interface *alts;
+			unsigned int psize;
+
+			alts = &intf->altsetting[i];
+			ep = uvc_find_endpoint(alts,
+				stream->header.bEndpointAddress);
+			if (ep == NULL)
+				continue;
+
+			/* Check if the bandwidth is high enough. */
+			psize = uvc_endpoint_max_bpi(stream->dev->udev, ep);
+			if (psize >= bandwidth && psize <= best_psize) {
+				altsetting = alts->desc.bAlternateSetting;
+				best_psize = psize;
+				best_ep = ep;
+			}
+		}
+
+		if (best_ep == NULL) {
+			uvc_trace(UVC_TRACE_VIDEO, "No fast enough alt setting "
+				"for requested bandwidth.\n");
+			return -EIO;
+		}
+
+		uvc_trace(UVC_TRACE_VIDEO, "Selecting alternate setting %u "
+			"(%u B/frame bandwidth).\n", altsetting, best_psize);
+
+		ret = usb_set_interface(stream->dev->udev, intfnum, altsetting);
+		if (ret < 0)
+			return ret;
+
+		ret = uvc_init_video_isoc(stream, best_ep, gfp_flags);
+	} else {
+		/* Bulk endpoint, proceed to URB initialization. */
+		ep = uvc_find_endpoint(&intf->altsetting[0],
+				stream->header.bEndpointAddress);
+		if (ep == NULL)
+			return -EIO;
+
+		ret = uvc_init_video_bulk(stream, ep, gfp_flags);
+	}
+
+	if (ret < 0)
+		return ret;
+
+	/* Submit the URBs. */
+	for (i = 0; i < UVC_URBS; ++i) {
+		ret = usb_submit_urb(stream->urb[i], gfp_flags);
+		if (ret < 0) {
+			uvc_printk(KERN_ERR, "Failed to submit URB %u "
+					"(%d).\n", i, ret);
+			uvc_uninit_video(stream, 1);
+			return ret;
+		}
+	}
+
+	/* The Logitech C920 temporarily forgets that it should not be adjusting
+	 * Exposure Absolute during init so restore controls to stored values.
+	 */
+	if (stream->dev->quirks & UVC_QUIRK_RESTORE_CTRLS_ON_INIT)
+		uvc_ctrl_restore_values(stream->dev);
+
+	return 0;
+}
+
+/* --------------------------------------------------------------------------
+ * Suspend/resume
+ */
+
+/*
+ * Stop streaming without disabling the video queue.
+ *
+ * To let userspace applications resume without trouble, we must not touch the
+ * video buffers in any way. We mark the device as frozen to make sure the URB
+ * completion handler won't try to cancel the queue when we kill the URBs.
+ */
+int uvc_video_suspend(struct uvc_streaming *stream)
+{
+	if (!uvc_queue_streaming(&stream->queue))
+		return 0;
+
+	stream->frozen = 1;
+	uvc_uninit_video(stream, 0);
+	usb_set_interface(stream->dev->udev, stream->intfnum, 0);
+	return 0;
+}
+
+/*
+ * Reconfigure the video interface and restart streaming if it was enabled
+ * before suspend.
+ *
+ * If an error occurs, disable the video queue. This will wake all pending
+ * buffers, making sure userspace applications are notified of the problem
+ * instead of waiting forever.
+ */
+int uvc_video_resume(struct uvc_streaming *stream, int reset)
+{
+	int ret;
+
+	/* If the bus has been reset on resume, set the alternate setting to 0.
+	 * This should be the default value, but some devices crash or otherwise
+	 * misbehave if they don't receive a SET_INTERFACE request before any
+	 * other video control request.
+	 */
+	if (reset)
+		usb_set_interface(stream->dev->udev, stream->intfnum, 0);
+
+	stream->frozen = 0;
+
+	uvc_video_clock_reset(stream);
+
+	if (!uvc_queue_streaming(&stream->queue))
+		return 0;
+
+	ret = uvc_commit_video(stream, &stream->ctrl);
+	if (ret < 0)
+		return ret;
+
+	return uvc_init_video(stream, GFP_NOIO);
+}
+
+/* ------------------------------------------------------------------------
+ * Video device
+ */
+
+/*
+ * Initialize the UVC video device by switching to alternate setting 0 and
+ * retrieve the default format.
+ *
+ * Some cameras (namely the Fuji Finepix) set the format and frame
+ * indexes to zero. The UVC standard doesn't clearly make this a spec
+ * violation, so try to silently fix the values if possible.
+ *
+ * This function is called before registering the device with V4L.
+ */
+int uvc_video_init(struct uvc_streaming *stream)
+{
+	struct uvc_streaming_control *probe = &stream->ctrl;
+	struct uvc_format *format = NULL;
+	struct uvc_frame *frame = NULL;
+	unsigned int i;
+	int ret;
+
+	if (stream->nformats == 0) {
+		uvc_printk(KERN_INFO, "No supported video formats found.\n");
+		return -EINVAL;
+	}
+
+	atomic_set(&stream->active, 0);
+
+	/* Alternate setting 0 should be the default, yet the XBox Live Vision
+	 * Cam (and possibly other devices) crash or otherwise misbehave if
+	 * they don't receive a SET_INTERFACE request before any other video
+	 * control request.
+	 */
+	usb_set_interface(stream->dev->udev, stream->intfnum, 0);
+
+	/* Set the streaming probe control with default streaming parameters
+	 * retrieved from the device. Webcams that don't suport GET_DEF
+	 * requests on the probe control will just keep their current streaming
+	 * parameters.
+	 */
+	if (uvc_get_video_ctrl(stream, probe, 1, UVC_GET_DEF) == 0)
+		uvc_set_video_ctrl(stream, probe, 1);
+
+	/* Initialize the streaming parameters with the probe control current
+	 * value. This makes sure SET_CUR requests on the streaming commit
+	 * control will always use values retrieved from a successful GET_CUR
+	 * request on the probe control, as required by the UVC specification.
+	 */
+	ret = uvc_get_video_ctrl(stream, probe, 1, UVC_GET_CUR);
+	if (ret < 0)
+		return ret;
+
+	/* Check if the default format descriptor exists. Use the first
+	 * available format otherwise.
+	 */
+	for (i = stream->nformats; i > 0; --i) {
+		format = &stream->format[i-1];
+		if (format->index == probe->bFormatIndex)
+			break;
+	}
+
+	if (format->nframes == 0) {
+		uvc_printk(KERN_INFO, "No frame descriptor found for the "
+			"default format.\n");
+		return -EINVAL;
+	}
+
+	/* Zero bFrameIndex might be correct. Stream-based formats (including
+	 * MPEG-2 TS and DV) do not support frames but have a dummy frame
+	 * descriptor with bFrameIndex set to zero. If the default frame
+	 * descriptor is not found, use the first available frame.
+	 */
+	for (i = format->nframes; i > 0; --i) {
+		frame = &format->frame[i-1];
+		if (frame->bFrameIndex == probe->bFrameIndex)
+			break;
+	}
+
+	probe->bFormatIndex = format->index;
+	probe->bFrameIndex = frame->bFrameIndex;
+
+	stream->def_format = format;
+	stream->cur_format = format;
+	stream->cur_frame = frame;
+
+	/* Select the video decoding function */
+	if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
+		if (stream->dev->quirks & UVC_QUIRK_BUILTIN_ISIGHT)
+			stream->decode = uvc_video_decode_isight;
+		else if (stream->intf->num_altsetting > 1)
+			stream->decode = uvc_video_decode_isoc;
+		else
+			stream->decode = uvc_video_decode_bulk;
+	} else {
+		if (stream->intf->num_altsetting == 1)
+			stream->decode = uvc_video_encode_bulk;
+		else {
+			uvc_printk(KERN_INFO, "Isochronous endpoints are not "
+				"supported for video output devices.\n");
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Enable or disable the video stream.
+ */
+int uvc_video_enable(struct uvc_streaming *stream, int enable)
+{
+	int ret;
+
+	if (!enable) {
+		uvc_uninit_video(stream, 1);
+		if (stream->intf->num_altsetting > 1) {
+			usb_set_interface(stream->dev->udev,
+					  stream->intfnum, 0);
+		} else {
+			/* UVC doesn't specify how to inform a bulk-based device
+			 * when the video stream is stopped. Windows sends a
+			 * CLEAR_FEATURE(HALT) request to the video streaming
+			 * bulk endpoint, mimic the same behaviour.
+			 */
+			unsigned int epnum = stream->header.bEndpointAddress
+					   & USB_ENDPOINT_NUMBER_MASK;
+			unsigned int dir = stream->header.bEndpointAddress
+					 & USB_ENDPOINT_DIR_MASK;
+			unsigned int pipe;
+
+			pipe = usb_sndbulkpipe(stream->dev->udev, epnum) | dir;
+			usb_clear_halt(stream->dev->udev, pipe);
+		}
+
+		uvc_video_clock_cleanup(stream);
+		return 0;
+	}
+
+	ret = uvc_video_clock_init(stream);
+	if (ret < 0)
+		return ret;
+
+	/* Commit the streaming parameters. */
+	ret = uvc_commit_video(stream, &stream->ctrl);
+	if (ret < 0)
+		goto error_commit;
+
+	ret = uvc_init_video(stream, GFP_KERNEL);
+	if (ret < 0)
+		goto error_video;
+
+	return 0;
+
+error_video:
+	usb_set_interface(stream->dev->udev, stream->intfnum, 0);
+error_commit:
+	uvc_video_clock_cleanup(stream);
+
+	return ret;
+}
diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h
new file mode 100644
index 0000000..e5f5d84
--- /dev/null
+++ b/drivers/media/usb/uvc/uvcvideo.h
@@ -0,0 +1,815 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _USB_VIDEO_H_
+#define _USB_VIDEO_H_
+
+#ifndef __KERNEL__
+#error "The uvcvideo.h header is deprecated, use linux/uvcvideo.h instead."
+#endif /* __KERNEL__ */
+
+#include <linux/kernel.h>
+#include <linux/poll.h>
+#include <linux/usb.h>
+#include <linux/usb/video.h>
+#include <linux/uvcvideo.h>
+#include <linux/videodev2.h>
+#include <linux/workqueue.h>
+#include <media/media-device.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-fh.h>
+#include <media/videobuf2-v4l2.h>
+
+/* --------------------------------------------------------------------------
+ * UVC constants
+ */
+
+#define UVC_TERM_INPUT			0x0000
+#define UVC_TERM_OUTPUT			0x8000
+#define UVC_TERM_DIRECTION(term)	((term)->type & 0x8000)
+
+#define UVC_ENTITY_TYPE(entity)		((entity)->type & 0x7fff)
+#define UVC_ENTITY_IS_UNIT(entity)	(((entity)->type & 0xff00) == 0)
+#define UVC_ENTITY_IS_TERM(entity)	(((entity)->type & 0xff00) != 0)
+#define UVC_ENTITY_IS_ITERM(entity) \
+	(UVC_ENTITY_IS_TERM(entity) && \
+	((entity)->type & 0x8000) == UVC_TERM_INPUT)
+#define UVC_ENTITY_IS_OTERM(entity) \
+	(UVC_ENTITY_IS_TERM(entity) && \
+	((entity)->type & 0x8000) == UVC_TERM_OUTPUT)
+
+
+/* ------------------------------------------------------------------------
+ * GUIDs
+ */
+#define UVC_GUID_UVC_CAMERA \
+	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01}
+#define UVC_GUID_UVC_OUTPUT \
+	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}
+#define UVC_GUID_UVC_MEDIA_TRANSPORT_INPUT \
+	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03}
+#define UVC_GUID_UVC_PROCESSING \
+	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01}
+#define UVC_GUID_UVC_SELECTOR \
+	{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \
+	 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02}
+
+#define UVC_GUID_FORMAT_MJPEG \
+	{ 'M',  'J',  'P',  'G', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_YUY2 \
+	{ 'Y',  'U',  'Y',  '2', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_YUY2_ISIGHT \
+	{ 'Y',  'U',  'Y',  '2', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0x00, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_NV12 \
+	{ 'N',  'V',  '1',  '2', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_YV12 \
+	{ 'Y',  'V',  '1',  '2', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_I420 \
+	{ 'I',  '4',  '2',  '0', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_UYVY \
+	{ 'U',  'Y',  'V',  'Y', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_Y800 \
+	{ 'Y',  '8',  '0',  '0', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_Y8 \
+	{ 'Y',  '8',  ' ',  ' ', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_Y10 \
+	{ 'Y',  '1',  '0',  ' ', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_Y12 \
+	{ 'Y',  '1',  '2',  ' ', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_Y16 \
+	{ 'Y',  '1',  '6',  ' ', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_BY8 \
+	{ 'B',  'Y',  '8',  ' ', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_BA81 \
+	{ 'B',  'A',  '8',  '1', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_GBRG \
+	{ 'G',  'B',  'R',  'G', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_GRBG \
+	{ 'G',  'R',  'B',  'G', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_RGGB \
+	{ 'R',  'G',  'G',  'B', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_BG16 \
+	{ 'B',  'G',  '1',  '6', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_GB16 \
+	{ 'G',  'B',  '1',  '6', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_RG16 \
+	{ 'R',  'G',  '1',  '6', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_GR16 \
+	{ 'G',  'R',  '1',  '6', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_RGBP \
+	{ 'R',  'G',  'B',  'P', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_BGR3 \
+	{ 0x7d, 0xeb, 0x36, 0xe4, 0x4f, 0x52, 0xce, 0x11, \
+	 0x9f, 0x53, 0x00, 0x20, 0xaf, 0x0b, 0xa7, 0x70}
+#define UVC_GUID_FORMAT_M420 \
+	{ 'M',  '4',  '2',  '0', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+
+#define UVC_GUID_FORMAT_H264 \
+	{ 'H',  '2',  '6',  '4', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_Y8I \
+	{ 'Y',  '8',  'I',  ' ', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_Y12I \
+	{ 'Y',  '1',  '2',  'I', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_Z16 \
+	{ 'Z',  '1',  '6',  ' ', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_RW10 \
+	{ 'R',  'W',  '1',  '0', 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_INVZ \
+	{ 'I',  'N',  'V',  'Z', 0x90, 0x2d, 0x58, 0x4a, \
+	 0x92, 0x0b, 0x77, 0x3f, 0x1f, 0x2c, 0x55, 0x6b}
+#define UVC_GUID_FORMAT_INZI \
+	{ 'I',  'N',  'Z',  'I', 0x66, 0x1a, 0x42, 0xa2, \
+	 0x90, 0x65, 0xd0, 0x18, 0x14, 0xa8, 0xef, 0x8a}
+#define UVC_GUID_FORMAT_INVI \
+	{ 'I',  'N',  'V',  'I', 0xdb, 0x57, 0x49, 0x5e, \
+	 0x8e, 0x3f, 0xf4, 0x79, 0x53, 0x2b, 0x94, 0x6f}
+
+#define UVC_GUID_FORMAT_D3DFMT_L8 \
+	{0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+#define UVC_GUID_FORMAT_KSMEDIA_L8_IR \
+	{0x32, 0x00, 0x00, 0x00, 0x02, 0x00, 0x10, 0x00, \
+	 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71}
+
+
+/* ------------------------------------------------------------------------
+ * Driver specific constants.
+ */
+
+#define DRIVER_VERSION		"1.1.1"
+
+/* Number of isochronous URBs. */
+#define UVC_URBS		5
+/* Maximum number of packets per URB. */
+#define UVC_MAX_PACKETS		32
+/* Maximum status buffer size in bytes of interrupt URB. */
+#define UVC_MAX_STATUS_SIZE	16
+
+#define UVC_CTRL_CONTROL_TIMEOUT	500
+#define UVC_CTRL_STREAMING_TIMEOUT	5000
+
+/* Maximum allowed number of control mappings per device */
+#define UVC_MAX_CONTROL_MAPPINGS	1024
+#define UVC_MAX_CONTROL_MENU_ENTRIES	32
+
+/* Devices quirks */
+#define UVC_QUIRK_STATUS_INTERVAL	0x00000001
+#define UVC_QUIRK_PROBE_MINMAX		0x00000002
+#define UVC_QUIRK_PROBE_EXTRAFIELDS	0x00000004
+#define UVC_QUIRK_BUILTIN_ISIGHT	0x00000008
+#define UVC_QUIRK_STREAM_NO_FID		0x00000010
+#define UVC_QUIRK_IGNORE_SELECTOR_UNIT	0x00000020
+#define UVC_QUIRK_FIX_BANDWIDTH		0x00000080
+#define UVC_QUIRK_PROBE_DEF		0x00000100
+#define UVC_QUIRK_RESTRICT_FRAME_RATE	0x00000200
+#define UVC_QUIRK_RESTORE_CTRLS_ON_INIT	0x00000400
+#define UVC_QUIRK_FORCE_Y8		0x00000800
+
+/* Format flags */
+#define UVC_FMT_FLAG_COMPRESSED		0x00000001
+#define UVC_FMT_FLAG_STREAM		0x00000002
+
+/* ------------------------------------------------------------------------
+ * Structures.
+ */
+
+struct uvc_device;
+
+/* TODO: Put the most frequently accessed fields at the beginning of
+ * structures to maximize cache efficiency.
+ */
+struct uvc_control_info {
+	struct list_head mappings;
+
+	u8 entity[16];
+	u8 index;	/* Bit index in bmControls */
+	u8 selector;
+
+	u16 size;
+	u32 flags;
+};
+
+struct uvc_control_mapping {
+	struct list_head list;
+	struct list_head ev_subs;
+
+	u32 id;
+	u8 name[32];
+	u8 entity[16];
+	u8 selector;
+
+	u8 size;
+	u8 offset;
+	enum v4l2_ctrl_type v4l2_type;
+	u32 data_type;
+
+	struct uvc_menu_info *menu_info;
+	u32 menu_count;
+
+	u32 master_id;
+	s32 master_manual;
+	u32 slave_ids[2];
+
+	s32 (*get)(struct uvc_control_mapping *mapping, u8 query,
+		   const u8 *data);
+	void (*set)(struct uvc_control_mapping *mapping, s32 value,
+		    u8 *data);
+};
+
+struct uvc_control {
+	struct uvc_entity *entity;
+	struct uvc_control_info info;
+
+	u8 index;	/* Used to match the uvc_control entry with a
+			   uvc_control_info. */
+	u8 dirty:1,
+	   loaded:1,
+	   modified:1,
+	   cached:1,
+	   initialized:1;
+
+	u8 *uvc_data;
+
+	struct uvc_fh *handle;	/* File handle that last changed the control. */
+};
+
+struct uvc_format_desc {
+	char *name;
+	u8 guid[16];
+	u32 fcc;
+};
+
+/* The term 'entity' refers to both UVC units and UVC terminals.
+ *
+ * The type field is either the terminal type (wTerminalType in the terminal
+ * descriptor), or the unit type (bDescriptorSubtype in the unit descriptor).
+ * As the bDescriptorSubtype field is one byte long, the type value will
+ * always have a null MSB for units. All terminal types defined by the UVC
+ * specification have a non-null MSB, so it is safe to use the MSB to
+ * differentiate between units and terminals as long as the descriptor parsing
+ * code makes sure terminal types have a non-null MSB.
+ *
+ * For terminals, the type's most significant bit stores the terminal
+ * direction (either UVC_TERM_INPUT or UVC_TERM_OUTPUT). The type field should
+ * always be accessed with the UVC_ENTITY_* macros and never directly.
+ */
+
+#define UVC_ENTITY_FLAG_DEFAULT		(1 << 0)
+
+struct uvc_entity {
+	struct list_head list;		/* Entity as part of a UVC device. */
+	struct list_head chain;		/* Entity as part of a video device
+					 * chain. */
+	unsigned int flags;
+
+	u8 id;
+	u16 type;
+	char name[64];
+
+	/* Media controller-related fields. */
+	struct video_device *vdev;
+	struct v4l2_subdev subdev;
+	unsigned int num_pads;
+	unsigned int num_links;
+	struct media_pad *pads;
+
+	union {
+		struct {
+			u16 wObjectiveFocalLengthMin;
+			u16 wObjectiveFocalLengthMax;
+			u16 wOcularFocalLength;
+			u8  bControlSize;
+			u8  *bmControls;
+		} camera;
+
+		struct {
+			u8  bControlSize;
+			u8  *bmControls;
+			u8  bTransportModeSize;
+			u8  *bmTransportModes;
+		} media;
+
+		struct {
+		} output;
+
+		struct {
+			u16 wMaxMultiplier;
+			u8  bControlSize;
+			u8  *bmControls;
+			u8  bmVideoStandards;
+		} processing;
+
+		struct {
+		} selector;
+
+		struct {
+			u8  guidExtensionCode[16];
+			u8  bNumControls;
+			u8  bControlSize;
+			u8  *bmControls;
+			u8  *bmControlsType;
+		} extension;
+	};
+
+	u8 bNrInPins;
+	u8 *baSourceID;
+
+	unsigned int ncontrols;
+	struct uvc_control *controls;
+};
+
+struct uvc_frame {
+	u8  bFrameIndex;
+	u8  bmCapabilities;
+	u16 wWidth;
+	u16 wHeight;
+	u32 dwMinBitRate;
+	u32 dwMaxBitRate;
+	u32 dwMaxVideoFrameBufferSize;
+	u8  bFrameIntervalType;
+	u32 dwDefaultFrameInterval;
+	u32 *dwFrameInterval;
+};
+
+struct uvc_format {
+	u8 type;
+	u8 index;
+	u8 bpp;
+	u8 colorspace;
+	u32 fcc;
+	u32 flags;
+
+	char name[32];
+
+	unsigned int nframes;
+	struct uvc_frame *frame;
+};
+
+struct uvc_streaming_header {
+	u8 bNumFormats;
+	u8 bEndpointAddress;
+	u8 bTerminalLink;
+	u8 bControlSize;
+	u8 *bmaControls;
+	/* The following fields are used by input headers only. */
+	u8 bmInfo;
+	u8 bStillCaptureMethod;
+	u8 bTriggerSupport;
+	u8 bTriggerUsage;
+};
+
+enum uvc_buffer_state {
+	UVC_BUF_STATE_IDLE	= 0,
+	UVC_BUF_STATE_QUEUED	= 1,
+	UVC_BUF_STATE_ACTIVE	= 2,
+	UVC_BUF_STATE_READY	= 3,
+	UVC_BUF_STATE_DONE	= 4,
+	UVC_BUF_STATE_ERROR	= 5,
+};
+
+struct uvc_buffer {
+	struct vb2_v4l2_buffer buf;
+	struct list_head queue;
+
+	enum uvc_buffer_state state;
+	unsigned int error;
+
+	void *mem;
+	unsigned int length;
+	unsigned int bytesused;
+
+	u32 pts;
+};
+
+#define UVC_QUEUE_DISCONNECTED		(1 << 0)
+#define UVC_QUEUE_DROP_CORRUPTED	(1 << 1)
+
+struct uvc_video_queue {
+	struct vb2_queue queue;
+	struct mutex mutex;			/* Protects queue */
+
+	unsigned int flags;
+	unsigned int buf_used;
+
+	spinlock_t irqlock;			/* Protects irqqueue */
+	struct list_head irqqueue;
+};
+
+struct uvc_video_chain {
+	struct uvc_device *dev;
+	struct list_head list;
+
+	struct list_head entities;		/* All entities */
+	struct uvc_entity *processing;		/* Processing unit */
+	struct uvc_entity *selector;		/* Selector unit */
+
+	struct mutex ctrl_mutex;		/* Protects ctrl.info */
+
+	struct v4l2_prio_state prio;		/* V4L2 priority state */
+	u32 caps;				/* V4L2 chain-wide caps */
+};
+
+struct uvc_stats_frame {
+	unsigned int size;		/* Number of bytes captured */
+	unsigned int first_data;	/* Index of the first non-empty packet */
+
+	unsigned int nb_packets;	/* Number of packets */
+	unsigned int nb_empty;		/* Number of empty packets */
+	unsigned int nb_invalid;	/* Number of packets with an invalid header */
+	unsigned int nb_errors;		/* Number of packets with the error bit set */
+
+	unsigned int nb_pts;		/* Number of packets with a PTS timestamp */
+	unsigned int nb_pts_diffs;	/* Number of PTS differences inside a frame */
+	unsigned int last_pts_diff;	/* Index of the last PTS difference */
+	bool has_initial_pts;		/* Whether the first non-empty packet has a PTS */
+	bool has_early_pts;		/* Whether a PTS is present before the first non-empty packet */
+	u32 pts;			/* PTS of the last packet */
+
+	unsigned int nb_scr;		/* Number of packets with a SCR timestamp */
+	unsigned int nb_scr_diffs;	/* Number of SCR.STC differences inside a frame */
+	u16 scr_sof;			/* SCR.SOF of the last packet */
+	u32 scr_stc;			/* SCR.STC of the last packet */
+};
+
+struct uvc_stats_stream {
+	ktime_t start_ts;		/* Stream start timestamp */
+	ktime_t stop_ts;		/* Stream stop timestamp */
+
+	unsigned int nb_frames;		/* Number of frames */
+
+	unsigned int nb_packets;	/* Number of packets */
+	unsigned int nb_empty;		/* Number of empty packets */
+	unsigned int nb_invalid;	/* Number of packets with an invalid header */
+	unsigned int nb_errors;		/* Number of packets with the error bit set */
+
+	unsigned int nb_pts_constant;	/* Number of frames with constant PTS */
+	unsigned int nb_pts_early;	/* Number of frames with early PTS */
+	unsigned int nb_pts_initial;	/* Number of frames with initial PTS */
+
+	unsigned int nb_scr_count_ok;	/* Number of frames with at least one SCR per non empty packet */
+	unsigned int nb_scr_diffs_ok;	/* Number of frames with varying SCR.STC */
+	unsigned int scr_sof_count;	/* STC.SOF counter accumulated since stream start */
+	unsigned int scr_sof;		/* STC.SOF of the last packet */
+	unsigned int min_sof;		/* Minimum STC.SOF value */
+	unsigned int max_sof;		/* Maximum STC.SOF value */
+};
+
+#define UVC_METATADA_BUF_SIZE 1024
+
+struct uvc_streaming {
+	struct list_head list;
+	struct uvc_device *dev;
+	struct video_device vdev;
+	struct uvc_video_chain *chain;
+	atomic_t active;
+
+	struct usb_interface *intf;
+	int intfnum;
+	u16 maxpsize;
+
+	struct uvc_streaming_header header;
+	enum v4l2_buf_type type;
+
+	unsigned int nformats;
+	struct uvc_format *format;
+
+	struct uvc_streaming_control ctrl;
+	struct uvc_format *def_format;
+	struct uvc_format *cur_format;
+	struct uvc_frame *cur_frame;
+
+	/* Protect access to ctrl, cur_format, cur_frame and hardware video
+	 * probe control.
+	 */
+	struct mutex mutex;
+
+	/* Buffers queue. */
+	unsigned int frozen : 1;
+	struct uvc_video_queue queue;
+	void (*decode) (struct urb *urb, struct uvc_streaming *video,
+			struct uvc_buffer *buf, struct uvc_buffer *meta_buf);
+
+	struct {
+		struct video_device vdev;
+		struct uvc_video_queue queue;
+		u32 format;
+	} meta;
+
+	/* Context data used by the bulk completion handler. */
+	struct {
+		u8 header[256];
+		unsigned int header_size;
+		int skip_payload;
+		u32 payload_size;
+		u32 max_payload_size;
+	} bulk;
+
+	struct urb *urb[UVC_URBS];
+	char *urb_buffer[UVC_URBS];
+	dma_addr_t urb_dma[UVC_URBS];
+	unsigned int urb_size;
+
+	u32 sequence;
+	u8 last_fid;
+
+	/* debugfs */
+	struct dentry *debugfs_dir;
+	struct {
+		struct uvc_stats_frame frame;
+		struct uvc_stats_stream stream;
+	} stats;
+
+	/* Timestamps support. */
+	struct uvc_clock {
+		struct uvc_clock_sample {
+			u32 dev_stc;
+			u16 dev_sof;
+			u16 host_sof;
+			ktime_t host_time;
+		} *samples;
+
+		unsigned int head;
+		unsigned int count;
+		unsigned int size;
+
+		u16 last_sof;
+		u16 sof_offset;
+
+		u8 last_scr[6];
+
+		spinlock_t lock;
+	} clock;
+};
+
+struct uvc_device {
+	struct usb_device *udev;
+	struct usb_interface *intf;
+	unsigned long warnings;
+	u32 quirks;
+	u32 meta_format;
+	int intfnum;
+	char name[32];
+
+	struct mutex lock;		/* Protects users */
+	unsigned int users;
+	atomic_t nmappings;
+
+	/* Video control interface */
+#ifdef CONFIG_MEDIA_CONTROLLER
+	struct media_device mdev;
+#endif
+	struct v4l2_device vdev;
+	u16 uvc_version;
+	u32 clock_frequency;
+
+	struct list_head entities;
+	struct list_head chains;
+
+	/* Video Streaming interfaces */
+	struct list_head streams;
+	struct kref ref;
+
+	/* Status Interrupt Endpoint */
+	struct usb_host_endpoint *int_ep;
+	struct urb *int_urb;
+	u8 *status;
+	struct input_dev *input;
+	char input_phys[64];
+
+	struct uvc_ctrl_work {
+		struct work_struct work;
+		struct urb *urb;
+		struct uvc_video_chain *chain;
+		struct uvc_control *ctrl;
+		const void *data;
+	} async_ctrl;
+};
+
+enum uvc_handle_state {
+	UVC_HANDLE_PASSIVE	= 0,
+	UVC_HANDLE_ACTIVE	= 1,
+};
+
+struct uvc_fh {
+	struct v4l2_fh vfh;
+	struct uvc_video_chain *chain;
+	struct uvc_streaming *stream;
+	enum uvc_handle_state state;
+};
+
+struct uvc_driver {
+	struct usb_driver driver;
+};
+
+/* ------------------------------------------------------------------------
+ * Debugging, printing and logging
+ */
+
+#define UVC_TRACE_PROBE		(1 << 0)
+#define UVC_TRACE_DESCR		(1 << 1)
+#define UVC_TRACE_CONTROL	(1 << 2)
+#define UVC_TRACE_FORMAT	(1 << 3)
+#define UVC_TRACE_CAPTURE	(1 << 4)
+#define UVC_TRACE_CALLS		(1 << 5)
+#define UVC_TRACE_FRAME		(1 << 7)
+#define UVC_TRACE_SUSPEND	(1 << 8)
+#define UVC_TRACE_STATUS	(1 << 9)
+#define UVC_TRACE_VIDEO		(1 << 10)
+#define UVC_TRACE_STATS		(1 << 11)
+#define UVC_TRACE_CLOCK		(1 << 12)
+
+#define UVC_WARN_MINMAX		0
+#define UVC_WARN_PROBE_DEF	1
+#define UVC_WARN_XU_GET_RES	2
+
+extern unsigned int uvc_clock_param;
+extern unsigned int uvc_no_drop_param;
+extern unsigned int uvc_trace_param;
+extern unsigned int uvc_timeout_param;
+extern unsigned int uvc_hw_timestamps_param;
+
+#define uvc_trace(flag, msg...) \
+	do { \
+		if (uvc_trace_param & flag) \
+			printk(KERN_DEBUG "uvcvideo: " msg); \
+	} while (0)
+
+#define uvc_warn_once(dev, warn, msg...) \
+	do { \
+		if (!test_and_set_bit(warn, &dev->warnings)) \
+			printk(KERN_INFO "uvcvideo: " msg); \
+	} while (0)
+
+#define uvc_printk(level, msg...) \
+	printk(level "uvcvideo: " msg)
+
+/* --------------------------------------------------------------------------
+ * Internal functions.
+ */
+
+/* Core driver */
+extern struct uvc_driver uvc_driver;
+
+struct uvc_entity *uvc_entity_by_id(struct uvc_device *dev, int id);
+
+/* Video buffers queue management. */
+int uvc_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type,
+		   int drop_corrupted);
+void uvc_queue_release(struct uvc_video_queue *queue);
+int uvc_request_buffers(struct uvc_video_queue *queue,
+			struct v4l2_requestbuffers *rb);
+int uvc_query_buffer(struct uvc_video_queue *queue,
+		     struct v4l2_buffer *v4l2_buf);
+int uvc_create_buffers(struct uvc_video_queue *queue,
+		       struct v4l2_create_buffers *v4l2_cb);
+int uvc_queue_buffer(struct uvc_video_queue *queue,
+		     struct v4l2_buffer *v4l2_buf);
+int uvc_export_buffer(struct uvc_video_queue *queue,
+		      struct v4l2_exportbuffer *exp);
+int uvc_dequeue_buffer(struct uvc_video_queue *queue,
+		       struct v4l2_buffer *v4l2_buf, int nonblocking);
+int uvc_queue_streamon(struct uvc_video_queue *queue, enum v4l2_buf_type type);
+int uvc_queue_streamoff(struct uvc_video_queue *queue, enum v4l2_buf_type type);
+void uvc_queue_cancel(struct uvc_video_queue *queue, int disconnect);
+struct uvc_buffer *uvc_queue_next_buffer(struct uvc_video_queue *queue,
+					 struct uvc_buffer *buf);
+int uvc_queue_mmap(struct uvc_video_queue *queue,
+		   struct vm_area_struct *vma);
+__poll_t uvc_queue_poll(struct uvc_video_queue *queue, struct file *file,
+			poll_table *wait);
+#ifndef CONFIG_MMU
+unsigned long uvc_queue_get_unmapped_area(struct uvc_video_queue *queue,
+					  unsigned long pgoff);
+#endif
+int uvc_queue_allocated(struct uvc_video_queue *queue);
+static inline int uvc_queue_streaming(struct uvc_video_queue *queue)
+{
+	return vb2_is_streaming(&queue->queue);
+}
+
+/* V4L2 interface */
+extern const struct v4l2_ioctl_ops uvc_ioctl_ops;
+extern const struct v4l2_file_operations uvc_fops;
+
+/* Media controller */
+int uvc_mc_register_entities(struct uvc_video_chain *chain);
+void uvc_mc_cleanup_entity(struct uvc_entity *entity);
+
+/* Video */
+int uvc_video_init(struct uvc_streaming *stream);
+int uvc_video_suspend(struct uvc_streaming *stream);
+int uvc_video_resume(struct uvc_streaming *stream, int reset);
+int uvc_video_enable(struct uvc_streaming *stream, int enable);
+int uvc_probe_video(struct uvc_streaming *stream,
+		    struct uvc_streaming_control *probe);
+int uvc_query_ctrl(struct uvc_device *dev, u8 query, u8 unit,
+		   u8 intfnum, u8 cs, void *data, u16 size);
+void uvc_video_clock_update(struct uvc_streaming *stream,
+			    struct vb2_v4l2_buffer *vbuf,
+			    struct uvc_buffer *buf);
+int uvc_meta_register(struct uvc_streaming *stream);
+
+int uvc_register_video_device(struct uvc_device *dev,
+			      struct uvc_streaming *stream,
+			      struct video_device *vdev,
+			      struct uvc_video_queue *queue,
+			      enum v4l2_buf_type type,
+			      const struct v4l2_file_operations *fops,
+			      const struct v4l2_ioctl_ops *ioctl_ops);
+
+/* Status */
+int uvc_status_init(struct uvc_device *dev);
+void uvc_status_cleanup(struct uvc_device *dev);
+int uvc_status_start(struct uvc_device *dev, gfp_t flags);
+void uvc_status_stop(struct uvc_device *dev);
+
+/* Controls */
+extern const struct v4l2_subscribed_event_ops uvc_ctrl_sub_ev_ops;
+
+int uvc_query_v4l2_ctrl(struct uvc_video_chain *chain,
+			struct v4l2_queryctrl *v4l2_ctrl);
+int uvc_query_v4l2_menu(struct uvc_video_chain *chain,
+			struct v4l2_querymenu *query_menu);
+
+int uvc_ctrl_add_mapping(struct uvc_video_chain *chain,
+			 const struct uvc_control_mapping *mapping);
+int uvc_ctrl_init_device(struct uvc_device *dev);
+void uvc_ctrl_cleanup_device(struct uvc_device *dev);
+int uvc_ctrl_restore_values(struct uvc_device *dev);
+bool uvc_ctrl_status_event(struct urb *urb, struct uvc_video_chain *chain,
+			   struct uvc_control *ctrl, const u8 *data);
+
+int uvc_ctrl_begin(struct uvc_video_chain *chain);
+int __uvc_ctrl_commit(struct uvc_fh *handle, int rollback,
+		      const struct v4l2_ext_control *xctrls,
+		      unsigned int xctrls_count);
+static inline int uvc_ctrl_commit(struct uvc_fh *handle,
+				  const struct v4l2_ext_control *xctrls,
+				  unsigned int xctrls_count)
+{
+	return __uvc_ctrl_commit(handle, 0, xctrls, xctrls_count);
+}
+static inline int uvc_ctrl_rollback(struct uvc_fh *handle)
+{
+	return __uvc_ctrl_commit(handle, 1, NULL, 0);
+}
+
+int uvc_ctrl_get(struct uvc_video_chain *chain, struct v4l2_ext_control *xctrl);
+int uvc_ctrl_set(struct uvc_fh *handle, struct v4l2_ext_control *xctrl);
+
+int uvc_xu_ctrl_query(struct uvc_video_chain *chain,
+		      struct uvc_xu_control_query *xqry);
+
+/* Utility functions */
+void uvc_simplify_fraction(u32 *numerator, u32 *denominator,
+			   unsigned int n_terms, unsigned int threshold);
+u32 uvc_fraction_to_interval(u32 numerator, u32 denominator);
+struct usb_host_endpoint *uvc_find_endpoint(struct usb_host_interface *alts,
+					    u8 epaddr);
+
+/* Quirks support */
+void uvc_video_decode_isight(struct urb *urb, struct uvc_streaming *stream,
+			     struct uvc_buffer *buf,
+			     struct uvc_buffer *meta_buf);
+
+/* debugfs and statistics */
+void uvc_debugfs_init(void);
+void uvc_debugfs_cleanup(void);
+void uvc_debugfs_init_stream(struct uvc_streaming *stream);
+void uvc_debugfs_cleanup_stream(struct uvc_streaming *stream);
+
+size_t uvc_video_stats_dump(struct uvc_streaming *stream, char *buf,
+			    size_t size);
+
+#endif
diff --git a/drivers/media/usb/zr364xx/Kconfig b/drivers/media/usb/zr364xx/Kconfig
new file mode 100644
index 0000000..ac429bc
--- /dev/null
+++ b/drivers/media/usb/zr364xx/Kconfig
@@ -0,0 +1,14 @@
+config USB_ZR364XX
+	tristate "USB ZR364XX Camera support"
+	depends on VIDEO_V4L2
+	select VIDEOBUF_GEN
+	select VIDEOBUF_VMALLOC
+	---help---
+	  Say Y here if you want to connect this type of camera to your
+	  computer's USB port.
+	  See <file:Documentation/media/v4l-drivers/zr364xx.rst> for more info
+	  and list of supported cameras.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called zr364xx.
+
diff --git a/drivers/media/usb/zr364xx/Makefile b/drivers/media/usb/zr364xx/Makefile
new file mode 100644
index 0000000..a577788
--- /dev/null
+++ b/drivers/media/usb/zr364xx/Makefile
@@ -0,0 +1,2 @@
+obj-$(CONFIG_USB_ZR364XX)       += zr364xx.o
+
diff --git a/drivers/media/usb/zr364xx/zr364xx.c b/drivers/media/usb/zr364xx/zr364xx.c
new file mode 100644
index 0000000..b888610
--- /dev/null
+++ b/drivers/media/usb/zr364xx/zr364xx.c
@@ -0,0 +1,1628 @@
+/*
+ * Zoran 364xx based USB webcam module version 0.73
+ *
+ * Allows you to use your USB webcam with V4L2 applications
+ * This is still in heavy developpement !
+ *
+ * Copyright (C) 2004  Antoine Jacquet <royale@zerezo.com>
+ * http://royale.zerezo.com/zr364xx/
+ *
+ * Heavily inspired by usb-skeleton.c, vicam.c, cpia.c and spca50x.c drivers
+ * V4L2 version inspired by meye.c driver
+ *
+ * Some video buffer code by Lamarque based on s2255drv.c and vivi.c drivers.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/usb.h>
+#include <linux/vmalloc.h>
+#include <linux/slab.h>
+#include <linux/proc_fs.h>
+#include <linux/highmem.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-fh.h>
+#include <media/v4l2-event.h>
+#include <media/videobuf-vmalloc.h>
+
+
+/* Version Information */
+#define DRIVER_VERSION "0.7.4"
+#define DRIVER_AUTHOR "Antoine Jacquet, http://royale.zerezo.com/"
+#define DRIVER_DESC "Zoran 364xx"
+
+
+/* Camera */
+#define FRAMES 1
+#define MAX_FRAME_SIZE 200000
+#define BUFFER_SIZE 0x1000
+#define CTRL_TIMEOUT 500
+
+#define ZR364XX_DEF_BUFS	4
+#define ZR364XX_READ_IDLE	0
+#define ZR364XX_READ_FRAME	1
+
+/* Debug macro */
+#define DBG(fmt, args...) \
+	do { \
+		if (debug) { \
+			printk(KERN_INFO KBUILD_MODNAME " " fmt, ##args); \
+		} \
+	} while (0)
+
+/*#define FULL_DEBUG 1*/
+#ifdef FULL_DEBUG
+#define _DBG DBG
+#else
+#define _DBG(fmt, args...)
+#endif
+
+/* Init methods, need to find nicer names for these
+ * the exact names of the chipsets would be the best if someone finds it */
+#define METHOD0 0
+#define METHOD1 1
+#define METHOD2 2
+#define METHOD3 3
+
+
+/* Module parameters */
+static int debug;
+static int mode;
+
+
+/* Module parameters interface */
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "Debug level");
+module_param(mode, int, 0644);
+MODULE_PARM_DESC(mode, "0 = 320x240, 1 = 160x120, 2 = 640x480");
+
+
+/* Devices supported by this driver
+ * .driver_info contains the init method used by the camera */
+static const struct usb_device_id device_table[] = {
+	{USB_DEVICE(0x08ca, 0x0109), .driver_info = METHOD0 },
+	{USB_DEVICE(0x041e, 0x4024), .driver_info = METHOD0 },
+	{USB_DEVICE(0x0d64, 0x0108), .driver_info = METHOD0 },
+	{USB_DEVICE(0x0546, 0x3187), .driver_info = METHOD0 },
+	{USB_DEVICE(0x0d64, 0x3108), .driver_info = METHOD0 },
+	{USB_DEVICE(0x0595, 0x4343), .driver_info = METHOD0 },
+	{USB_DEVICE(0x0bb0, 0x500d), .driver_info = METHOD0 },
+	{USB_DEVICE(0x0feb, 0x2004), .driver_info = METHOD0 },
+	{USB_DEVICE(0x055f, 0xb500), .driver_info = METHOD0 },
+	{USB_DEVICE(0x08ca, 0x2062), .driver_info = METHOD2 },
+	{USB_DEVICE(0x052b, 0x1a18), .driver_info = METHOD1 },
+	{USB_DEVICE(0x04c8, 0x0729), .driver_info = METHOD0 },
+	{USB_DEVICE(0x04f2, 0xa208), .driver_info = METHOD0 },
+	{USB_DEVICE(0x0784, 0x0040), .driver_info = METHOD1 },
+	{USB_DEVICE(0x06d6, 0x0034), .driver_info = METHOD0 },
+	{USB_DEVICE(0x0a17, 0x0062), .driver_info = METHOD2 },
+	{USB_DEVICE(0x06d6, 0x003b), .driver_info = METHOD0 },
+	{USB_DEVICE(0x0a17, 0x004e), .driver_info = METHOD2 },
+	{USB_DEVICE(0x041e, 0x405d), .driver_info = METHOD2 },
+	{USB_DEVICE(0x08ca, 0x2102), .driver_info = METHOD3 },
+	{USB_DEVICE(0x06d6, 0x003d), .driver_info = METHOD0 },
+	{}			/* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+/* frame structure */
+struct zr364xx_framei {
+	unsigned long ulState;	/* ulState:ZR364XX_READ_IDLE,
+					   ZR364XX_READ_FRAME */
+	void *lpvbits;		/* image data */
+	unsigned long cur_size;	/* current data copied to it */
+};
+
+/* image buffer structure */
+struct zr364xx_bufferi {
+	unsigned long dwFrames;			/* number of frames in buffer */
+	struct zr364xx_framei frame[FRAMES];	/* array of FRAME structures */
+};
+
+struct zr364xx_dmaqueue {
+	struct list_head	active;
+	struct zr364xx_camera	*cam;
+};
+
+struct zr364xx_pipeinfo {
+	u32 transfer_size;
+	u8 *transfer_buffer;
+	u32 state;
+	void *stream_urb;
+	void *cam;	/* back pointer to zr364xx_camera struct */
+	u32 err_count;
+	u32 idx;
+};
+
+struct zr364xx_fmt {
+	char *name;
+	u32 fourcc;
+	int depth;
+};
+
+/* image formats.  */
+static const struct zr364xx_fmt formats[] = {
+	{
+		.name = "JPG",
+		.fourcc = V4L2_PIX_FMT_JPEG,
+		.depth = 24
+	}
+};
+
+/* Camera stuff */
+struct zr364xx_camera {
+	struct usb_device *udev;	/* save off the usb device pointer */
+	struct usb_interface *interface;/* the interface for this device */
+	struct v4l2_device v4l2_dev;
+	struct v4l2_ctrl_handler ctrl_handler;
+	struct video_device vdev;	/* v4l video device */
+	struct v4l2_fh *owner;		/* owns the streaming */
+	int nb;
+	struct zr364xx_bufferi		buffer;
+	int skip;
+	int width;
+	int height;
+	int method;
+	struct mutex lock;
+
+	spinlock_t		slock;
+	struct zr364xx_dmaqueue	vidq;
+	int			last_frame;
+	int			cur_frame;
+	unsigned long		frame_count;
+	int			b_acquire;
+	struct zr364xx_pipeinfo	pipe[1];
+
+	u8			read_endpoint;
+
+	const struct zr364xx_fmt *fmt;
+	struct videobuf_queue	vb_vidq;
+	bool was_streaming;
+};
+
+/* buffer for one video frame */
+struct zr364xx_buffer {
+	/* common v4l buffer stuff -- must be first */
+	struct videobuf_buffer vb;
+	const struct zr364xx_fmt *fmt;
+};
+
+/* function used to send initialisation commands to the camera */
+static int send_control_msg(struct usb_device *udev, u8 request, u16 value,
+			    u16 index, unsigned char *cp, u16 size)
+{
+	int status;
+
+	unsigned char *transfer_buffer = kmalloc(size, GFP_KERNEL);
+	if (!transfer_buffer)
+		return -ENOMEM;
+
+	memcpy(transfer_buffer, cp, size);
+
+	status = usb_control_msg(udev,
+				 usb_sndctrlpipe(udev, 0),
+				 request,
+				 USB_DIR_OUT | USB_TYPE_VENDOR |
+				 USB_RECIP_DEVICE, value, index,
+				 transfer_buffer, size, CTRL_TIMEOUT);
+
+	kfree(transfer_buffer);
+	return status;
+}
+
+
+/* Control messages sent to the camera to initialize it
+ * and launch the capture */
+typedef struct {
+	unsigned int value;
+	unsigned int size;
+	unsigned char *bytes;
+} message;
+
+/* method 0 */
+static unsigned char m0d1[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+static unsigned char m0d2[] = { 0, 0, 0, 0, 0, 0 };
+static unsigned char m0d3[] = { 0, 0 };
+static message m0[] = {
+	{0x1f30, 0, NULL},
+	{0xd000, 0, NULL},
+	{0x3370, sizeof(m0d1), m0d1},
+	{0x2000, 0, NULL},
+	{0x2f0f, 0, NULL},
+	{0x2610, sizeof(m0d2), m0d2},
+	{0xe107, 0, NULL},
+	{0x2502, 0, NULL},
+	{0x1f70, 0, NULL},
+	{0xd000, 0, NULL},
+	{0x9a01, sizeof(m0d3), m0d3},
+	{-1, -1, NULL}
+};
+
+/* method 1 */
+static unsigned char m1d1[] = { 0xff, 0xff };
+static unsigned char m1d2[] = { 0x00, 0x00 };
+static message m1[] = {
+	{0x1f30, 0, NULL},
+	{0xd000, 0, NULL},
+	{0xf000, 0, NULL},
+	{0x2000, 0, NULL},
+	{0x2f0f, 0, NULL},
+	{0x2650, 0, NULL},
+	{0xe107, 0, NULL},
+	{0x2502, sizeof(m1d1), m1d1},
+	{0x1f70, 0, NULL},
+	{0xd000, 0, NULL},
+	{0xd000, 0, NULL},
+	{0xd000, 0, NULL},
+	{0x9a01, sizeof(m1d2), m1d2},
+	{-1, -1, NULL}
+};
+
+/* method 2 */
+static unsigned char m2d1[] = { 0xff, 0xff };
+static message m2[] = {
+	{0x1f30, 0, NULL},
+	{0xf000, 0, NULL},
+	{0x2000, 0, NULL},
+	{0x2f0f, 0, NULL},
+	{0x2650, 0, NULL},
+	{0xe107, 0, NULL},
+	{0x2502, sizeof(m2d1), m2d1},
+	{0x1f70, 0, NULL},
+	{-1, -1, NULL}
+};
+
+/* init table */
+static message *init[4] = { m0, m1, m2, m2 };
+
+
+/* JPEG static data in header (Huffman table, etc) */
+static unsigned char header1[] = {
+	0xFF, 0xD8,
+	/*
+	0xFF, 0xE0, 0x00, 0x10, 'J', 'F', 'I', 'F',
+	0x00, 0x01, 0x01, 0x00, 0x33, 0x8A, 0x00, 0x00, 0x33, 0x88,
+	*/
+	0xFF, 0xDB, 0x00, 0x84
+};
+static unsigned char header2[] = {
+	0xFF, 0xC4, 0x00, 0x1F, 0x00, 0x00, 0x01, 0x05, 0x01, 0x01, 0x01,
+	0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
+	0xFF, 0xC4, 0x00, 0xB5, 0x10, 0x00, 0x02, 0x01, 0x03, 0x03, 0x02,
+	0x04, 0x03, 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7D, 0x01,
+	0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06,
+	0x13, 0x51, 0x61, 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1,
+	0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0, 0x24, 0x33,
+	0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25,
+	0x26, 0x27, 0x28, 0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+	0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54,
+	0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67,
+	0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A,
+	0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94,
+	0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6,
+	0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8,
+	0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA,
+	0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2,
+	0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3,
+	0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFF, 0xC4, 0x00, 0x1F,
+	0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
+	0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, 0x04,
+	0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0xFF, 0xC4, 0x00, 0xB5,
+	0x11, 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, 0x07, 0x05,
+	0x04, 0x04, 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, 0x03, 0x11,
+	0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,
+	0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xA1, 0xB1, 0xC1,
+	0x09, 0x23, 0x33, 0x52, 0xF0, 0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16,
+	0x24, 0x34, 0xE1, 0x25, 0xF1, 0x17, 0x18, 0x19, 0x1A, 0x26, 0x27,
+	0x28, 0x29, 0x2A, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44,
+	0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57,
+	0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A,
+	0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x82, 0x83, 0x84,
+	0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96,
+	0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8,
+	0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA,
+	0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3,
+	0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE2, 0xE3, 0xE4, 0xE5,
+	0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7,
+	0xF8, 0xF9, 0xFA, 0xFF, 0xC0, 0x00, 0x11, 0x08, 0x00, 0xF0, 0x01,
+	0x40, 0x03, 0x01, 0x21, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01,
+	0xFF, 0xDA, 0x00, 0x0C, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11,
+	0x00, 0x3F, 0x00
+};
+static unsigned char header3;
+
+/* ------------------------------------------------------------------
+   Videobuf operations
+   ------------------------------------------------------------------*/
+
+static int buffer_setup(struct videobuf_queue *vq, unsigned int *count,
+			unsigned int *size)
+{
+	struct zr364xx_camera *cam = vq->priv_data;
+
+	*size = cam->width * cam->height * (cam->fmt->depth >> 3);
+
+	if (*count == 0)
+		*count = ZR364XX_DEF_BUFS;
+
+	if (*size * *count > ZR364XX_DEF_BUFS * 1024 * 1024)
+		*count = (ZR364XX_DEF_BUFS * 1024 * 1024) / *size;
+
+	return 0;
+}
+
+static void free_buffer(struct videobuf_queue *vq, struct zr364xx_buffer *buf)
+{
+	_DBG("%s\n", __func__);
+
+	BUG_ON(in_interrupt());
+
+	videobuf_vmalloc_free(&buf->vb);
+	buf->vb.state = VIDEOBUF_NEEDS_INIT;
+}
+
+static int buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb,
+			  enum v4l2_field field)
+{
+	struct zr364xx_camera *cam = vq->priv_data;
+	struct zr364xx_buffer *buf = container_of(vb, struct zr364xx_buffer,
+						  vb);
+	int rc;
+
+	DBG("%s, field=%d, fmt name = %s\n", __func__, field,
+	    cam->fmt ? cam->fmt->name : "");
+	if (!cam->fmt)
+		return -EINVAL;
+
+	buf->vb.size = cam->width * cam->height * (cam->fmt->depth >> 3);
+
+	if (buf->vb.baddr != 0 && buf->vb.bsize < buf->vb.size) {
+		DBG("invalid buffer prepare\n");
+		return -EINVAL;
+	}
+
+	buf->fmt = cam->fmt;
+	buf->vb.width = cam->width;
+	buf->vb.height = cam->height;
+	buf->vb.field = field;
+
+	if (buf->vb.state == VIDEOBUF_NEEDS_INIT) {
+		rc = videobuf_iolock(vq, &buf->vb, NULL);
+		if (rc < 0)
+			goto fail;
+	}
+
+	buf->vb.state = VIDEOBUF_PREPARED;
+	return 0;
+fail:
+	free_buffer(vq, buf);
+	return rc;
+}
+
+static void buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb)
+{
+	struct zr364xx_buffer *buf = container_of(vb, struct zr364xx_buffer,
+						  vb);
+	struct zr364xx_camera *cam = vq->priv_data;
+
+	_DBG("%s\n", __func__);
+
+	buf->vb.state = VIDEOBUF_QUEUED;
+	list_add_tail(&buf->vb.queue, &cam->vidq.active);
+}
+
+static void buffer_release(struct videobuf_queue *vq,
+			   struct videobuf_buffer *vb)
+{
+	struct zr364xx_buffer *buf = container_of(vb, struct zr364xx_buffer,
+						  vb);
+
+	_DBG("%s\n", __func__);
+	free_buffer(vq, buf);
+}
+
+static const struct videobuf_queue_ops zr364xx_video_qops = {
+	.buf_setup = buffer_setup,
+	.buf_prepare = buffer_prepare,
+	.buf_queue = buffer_queue,
+	.buf_release = buffer_release,
+};
+
+/********************/
+/* V4L2 integration */
+/********************/
+static int zr364xx_vidioc_streamon(struct file *file, void *priv,
+				   enum v4l2_buf_type type);
+
+static ssize_t zr364xx_read(struct file *file, char __user *buf, size_t count,
+			    loff_t * ppos)
+{
+	struct zr364xx_camera *cam = video_drvdata(file);
+	int err = 0;
+
+	_DBG("%s\n", __func__);
+
+	if (!buf)
+		return -EINVAL;
+
+	if (!count)
+		return -EINVAL;
+
+	if (mutex_lock_interruptible(&cam->lock))
+		return -ERESTARTSYS;
+
+	err = zr364xx_vidioc_streamon(file, file->private_data,
+				V4L2_BUF_TYPE_VIDEO_CAPTURE);
+	if (err == 0) {
+		DBG("%s: reading %d bytes at pos %d.\n", __func__,
+				(int) count, (int) *ppos);
+
+		/* NoMan Sux ! */
+		err = videobuf_read_one(&cam->vb_vidq, buf, count, ppos,
+					file->f_flags & O_NONBLOCK);
+	}
+	mutex_unlock(&cam->lock);
+	return err;
+}
+
+/* video buffer vmalloc implementation based partly on VIVI driver which is
+ *          Copyright (c) 2006 by
+ *                  Mauro Carvalho Chehab <mchehab--a.t--infradead.org>
+ *                  Ted Walther <ted--a.t--enumera.com>
+ *                  John Sokol <sokol--a.t--videotechnology.com>
+ *                  http://v4l.videotechnology.com/
+ *
+ */
+static void zr364xx_fillbuff(struct zr364xx_camera *cam,
+			     struct zr364xx_buffer *buf,
+			     int jpgsize)
+{
+	int pos = 0;
+	const char *tmpbuf;
+	char *vbuf = videobuf_to_vmalloc(&buf->vb);
+	unsigned long last_frame;
+
+	if (!vbuf)
+		return;
+
+	last_frame = cam->last_frame;
+	if (last_frame != -1) {
+		tmpbuf = (const char *)cam->buffer.frame[last_frame].lpvbits;
+		switch (buf->fmt->fourcc) {
+		case V4L2_PIX_FMT_JPEG:
+			buf->vb.size = jpgsize;
+			memcpy(vbuf, tmpbuf, buf->vb.size);
+			break;
+		default:
+			printk(KERN_DEBUG KBUILD_MODNAME ": unknown format?\n");
+		}
+		cam->last_frame = -1;
+	} else {
+		printk(KERN_ERR KBUILD_MODNAME ": =======no frame\n");
+		return;
+	}
+	DBG("%s: Buffer %p size= %d\n", __func__, vbuf, pos);
+	/* tell v4l buffer was filled */
+
+	buf->vb.field_count = cam->frame_count * 2;
+	v4l2_get_timestamp(&buf->vb.ts);
+	buf->vb.state = VIDEOBUF_DONE;
+}
+
+static int zr364xx_got_frame(struct zr364xx_camera *cam, int jpgsize)
+{
+	struct zr364xx_dmaqueue *dma_q = &cam->vidq;
+	struct zr364xx_buffer *buf;
+	unsigned long flags = 0;
+	int rc = 0;
+
+	DBG("wakeup: %p\n", &dma_q);
+	spin_lock_irqsave(&cam->slock, flags);
+
+	if (list_empty(&dma_q->active)) {
+		DBG("No active queue to serve\n");
+		rc = -1;
+		goto unlock;
+	}
+	buf = list_entry(dma_q->active.next,
+			 struct zr364xx_buffer, vb.queue);
+
+	if (!waitqueue_active(&buf->vb.done)) {
+		/* no one active */
+		rc = -1;
+		goto unlock;
+	}
+	list_del(&buf->vb.queue);
+	v4l2_get_timestamp(&buf->vb.ts);
+	DBG("[%p/%d] wakeup\n", buf, buf->vb.i);
+	zr364xx_fillbuff(cam, buf, jpgsize);
+	wake_up(&buf->vb.done);
+	DBG("wakeup [buf/i] [%p/%d]\n", buf, buf->vb.i);
+unlock:
+	spin_unlock_irqrestore(&cam->slock, flags);
+	return rc;
+}
+
+/* this function moves the usb stream read pipe data
+ * into the system buffers.
+ * returns 0 on success, EAGAIN if more data to process (call this
+ * function again).
+ */
+static int zr364xx_read_video_callback(struct zr364xx_camera *cam,
+					struct zr364xx_pipeinfo *pipe_info,
+					struct urb *purb)
+{
+	unsigned char *pdest;
+	unsigned char *psrc;
+	s32 idx = -1;
+	struct zr364xx_framei *frm;
+	int i = 0;
+	unsigned char *ptr = NULL;
+
+	_DBG("buffer to user\n");
+	idx = cam->cur_frame;
+	frm = &cam->buffer.frame[idx];
+
+	/* swap bytes if camera needs it */
+	if (cam->method == METHOD0) {
+		u16 *buf = (u16 *)pipe_info->transfer_buffer;
+		for (i = 0; i < purb->actual_length/2; i++)
+			swab16s(buf + i);
+	}
+
+	/* search done.  now find out if should be acquiring */
+	if (!cam->b_acquire) {
+		/* we found a frame, but this channel is turned off */
+		frm->ulState = ZR364XX_READ_IDLE;
+		return -EINVAL;
+	}
+
+	psrc = (u8 *)pipe_info->transfer_buffer;
+	ptr = pdest = frm->lpvbits;
+
+	if (frm->ulState == ZR364XX_READ_IDLE) {
+		if (purb->actual_length < 128) {
+			/* header incomplete */
+			dev_info(&cam->udev->dev,
+				 "%s: buffer (%d bytes) too small to hold jpeg header. Discarding.\n",
+				 __func__, purb->actual_length);
+			return -EINVAL;
+		}
+
+		frm->ulState = ZR364XX_READ_FRAME;
+		frm->cur_size = 0;
+
+		_DBG("jpeg header, ");
+		memcpy(ptr, header1, sizeof(header1));
+		ptr += sizeof(header1);
+		header3 = 0;
+		memcpy(ptr, &header3, 1);
+		ptr++;
+		memcpy(ptr, psrc, 64);
+		ptr += 64;
+		header3 = 1;
+		memcpy(ptr, &header3, 1);
+		ptr++;
+		memcpy(ptr, psrc + 64, 64);
+		ptr += 64;
+		memcpy(ptr, header2, sizeof(header2));
+		ptr += sizeof(header2);
+		memcpy(ptr, psrc + 128,
+		       purb->actual_length - 128);
+		ptr += purb->actual_length - 128;
+		_DBG("header : %d %d %d %d %d %d %d %d %d\n",
+		    psrc[0], psrc[1], psrc[2],
+		    psrc[3], psrc[4], psrc[5],
+		    psrc[6], psrc[7], psrc[8]);
+		frm->cur_size = ptr - pdest;
+	} else {
+		if (frm->cur_size + purb->actual_length > MAX_FRAME_SIZE) {
+			dev_info(&cam->udev->dev,
+				 "%s: buffer (%d bytes) too small to hold frame data. Discarding frame data.\n",
+				 __func__, MAX_FRAME_SIZE);
+		} else {
+			pdest += frm->cur_size;
+			memcpy(pdest, psrc, purb->actual_length);
+			frm->cur_size += purb->actual_length;
+		}
+	}
+	/*_DBG("cur_size %lu urb size %d\n", frm->cur_size,
+		purb->actual_length);*/
+
+	if (purb->actual_length < pipe_info->transfer_size) {
+		_DBG("****************Buffer[%d]full*************\n", idx);
+		cam->last_frame = cam->cur_frame;
+		cam->cur_frame++;
+		/* end of system frame ring buffer, start at zero */
+		if (cam->cur_frame == cam->buffer.dwFrames)
+			cam->cur_frame = 0;
+
+		/* frame ready */
+		/* go back to find the JPEG EOI marker */
+		ptr = pdest = frm->lpvbits;
+		ptr += frm->cur_size - 2;
+		while (ptr > pdest) {
+			if (*ptr == 0xFF && *(ptr + 1) == 0xD9
+			    && *(ptr + 2) == 0xFF)
+				break;
+			ptr--;
+		}
+		if (ptr == pdest)
+			DBG("No EOI marker\n");
+
+		/* Sometimes there is junk data in the middle of the picture,
+		 * we want to skip this bogus frames */
+		while (ptr > pdest) {
+			if (*ptr == 0xFF && *(ptr + 1) == 0xFF
+			    && *(ptr + 2) == 0xFF)
+				break;
+			ptr--;
+		}
+		if (ptr != pdest) {
+			DBG("Bogus frame ? %d\n", ++(cam->nb));
+		} else if (cam->b_acquire) {
+			/* we skip the 2 first frames which are usually buggy */
+			if (cam->skip)
+				cam->skip--;
+			else {
+				_DBG("jpeg(%lu): %d %d %d %d %d %d %d %d\n",
+				    frm->cur_size,
+				    pdest[0], pdest[1], pdest[2], pdest[3],
+				    pdest[4], pdest[5], pdest[6], pdest[7]);
+
+				zr364xx_got_frame(cam, frm->cur_size);
+			}
+		}
+		cam->frame_count++;
+		frm->ulState = ZR364XX_READ_IDLE;
+		frm->cur_size = 0;
+	}
+	/* done successfully */
+	return 0;
+}
+
+static int zr364xx_vidioc_querycap(struct file *file, void *priv,
+				   struct v4l2_capability *cap)
+{
+	struct zr364xx_camera *cam = video_drvdata(file);
+
+	strlcpy(cap->driver, DRIVER_DESC, sizeof(cap->driver));
+	strlcpy(cap->card, cam->udev->product, sizeof(cap->card));
+	strlcpy(cap->bus_info, dev_name(&cam->udev->dev),
+		sizeof(cap->bus_info));
+	cap->device_caps = V4L2_CAP_VIDEO_CAPTURE |
+			    V4L2_CAP_READWRITE |
+			    V4L2_CAP_STREAMING;
+	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+
+	return 0;
+}
+
+static int zr364xx_vidioc_enum_input(struct file *file, void *priv,
+				     struct v4l2_input *i)
+{
+	if (i->index != 0)
+		return -EINVAL;
+	strcpy(i->name, DRIVER_DESC " Camera");
+	i->type = V4L2_INPUT_TYPE_CAMERA;
+	return 0;
+}
+
+static int zr364xx_vidioc_g_input(struct file *file, void *priv,
+				  unsigned int *i)
+{
+	*i = 0;
+	return 0;
+}
+
+static int zr364xx_vidioc_s_input(struct file *file, void *priv,
+				  unsigned int i)
+{
+	if (i != 0)
+		return -EINVAL;
+	return 0;
+}
+
+static int zr364xx_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct zr364xx_camera *cam =
+		container_of(ctrl->handler, struct zr364xx_camera, ctrl_handler);
+	int temp;
+
+	switch (ctrl->id) {
+	case V4L2_CID_BRIGHTNESS:
+		/* hardware brightness */
+		send_control_msg(cam->udev, 1, 0x2001, 0, NULL, 0);
+		temp = (0x60 << 8) + 127 - ctrl->val;
+		send_control_msg(cam->udev, 1, temp, 0, NULL, 0);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int zr364xx_vidioc_enum_fmt_vid_cap(struct file *file,
+				       void *priv, struct v4l2_fmtdesc *f)
+{
+	if (f->index > 0)
+		return -EINVAL;
+	f->flags = V4L2_FMT_FLAG_COMPRESSED;
+	strcpy(f->description, formats[0].name);
+	f->pixelformat = formats[0].fourcc;
+	return 0;
+}
+
+static char *decode_fourcc(__u32 pixelformat, char *buf)
+{
+	buf[0] = pixelformat & 0xff;
+	buf[1] = (pixelformat >> 8) & 0xff;
+	buf[2] = (pixelformat >> 16) & 0xff;
+	buf[3] = (pixelformat >> 24) & 0xff;
+	buf[4] = '\0';
+	return buf;
+}
+
+static int zr364xx_vidioc_try_fmt_vid_cap(struct file *file, void *priv,
+				      struct v4l2_format *f)
+{
+	struct zr364xx_camera *cam = video_drvdata(file);
+	char pixelformat_name[5];
+
+	if (!cam)
+		return -ENODEV;
+
+	if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_JPEG) {
+		DBG("%s: unsupported pixelformat V4L2_PIX_FMT_%s\n", __func__,
+		    decode_fourcc(f->fmt.pix.pixelformat, pixelformat_name));
+		return -EINVAL;
+	}
+
+	if (!(f->fmt.pix.width == 160 && f->fmt.pix.height == 120) &&
+	    !(f->fmt.pix.width == 640 && f->fmt.pix.height == 480)) {
+		f->fmt.pix.width = 320;
+		f->fmt.pix.height = 240;
+	}
+
+	f->fmt.pix.field = V4L2_FIELD_NONE;
+	f->fmt.pix.bytesperline = f->fmt.pix.width * 2;
+	f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
+	f->fmt.pix.colorspace = V4L2_COLORSPACE_JPEG;
+	DBG("%s: V4L2_PIX_FMT_%s (%d) ok!\n", __func__,
+	    decode_fourcc(f->fmt.pix.pixelformat, pixelformat_name),
+	    f->fmt.pix.field);
+	return 0;
+}
+
+static int zr364xx_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+				    struct v4l2_format *f)
+{
+	struct zr364xx_camera *cam;
+
+	if (!file)
+		return -ENODEV;
+	cam = video_drvdata(file);
+
+	f->fmt.pix.pixelformat = formats[0].fourcc;
+	f->fmt.pix.field = V4L2_FIELD_NONE;
+	f->fmt.pix.width = cam->width;
+	f->fmt.pix.height = cam->height;
+	f->fmt.pix.bytesperline = f->fmt.pix.width * 2;
+	f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
+	f->fmt.pix.colorspace = V4L2_COLORSPACE_JPEG;
+	return 0;
+}
+
+static int zr364xx_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+				    struct v4l2_format *f)
+{
+	struct zr364xx_camera *cam = video_drvdata(file);
+	struct videobuf_queue *q = &cam->vb_vidq;
+	char pixelformat_name[5];
+	int ret = zr364xx_vidioc_try_fmt_vid_cap(file, cam, f);
+	int i;
+
+	if (ret < 0)
+		return ret;
+
+	mutex_lock(&q->vb_lock);
+
+	if (videobuf_queue_is_busy(&cam->vb_vidq)) {
+		DBG("%s queue busy\n", __func__);
+		ret = -EBUSY;
+		goto out;
+	}
+
+	if (cam->owner) {
+		DBG("%s can't change format after started\n", __func__);
+		ret = -EBUSY;
+		goto out;
+	}
+
+	cam->width = f->fmt.pix.width;
+	cam->height = f->fmt.pix.height;
+	DBG("%s: %dx%d mode selected\n", __func__,
+		 cam->width, cam->height);
+	f->fmt.pix.bytesperline = f->fmt.pix.width * 2;
+	f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline;
+	f->fmt.pix.colorspace = V4L2_COLORSPACE_JPEG;
+	cam->vb_vidq.field = f->fmt.pix.field;
+
+	if (f->fmt.pix.width == 160 && f->fmt.pix.height == 120)
+		mode = 1;
+	else if (f->fmt.pix.width == 640 && f->fmt.pix.height == 480)
+		mode = 2;
+	else
+		mode = 0;
+
+	m0d1[0] = mode;
+	m1[2].value = 0xf000 + mode;
+	m2[1].value = 0xf000 + mode;
+
+	/* special case for METHOD3, the modes are different */
+	if (cam->method == METHOD3) {
+		switch (mode) {
+		case 1:
+			m2[1].value = 0xf000 + 4;
+			break;
+		case 2:
+			m2[1].value = 0xf000 + 0;
+			break;
+		default:
+			m2[1].value = 0xf000 + 1;
+			break;
+		}
+	}
+
+	header2[437] = cam->height / 256;
+	header2[438] = cam->height % 256;
+	header2[439] = cam->width / 256;
+	header2[440] = cam->width % 256;
+
+	for (i = 0; init[cam->method][i].size != -1; i++) {
+		ret =
+		    send_control_msg(cam->udev, 1, init[cam->method][i].value,
+				     0, init[cam->method][i].bytes,
+				     init[cam->method][i].size);
+		if (ret < 0) {
+			dev_err(&cam->udev->dev,
+			   "error during resolution change sequence: %d\n", i);
+			goto out;
+		}
+	}
+
+	/* Added some delay here, since opening/closing the camera quickly,
+	 * like Ekiga does during its startup, can crash the webcam
+	 */
+	mdelay(100);
+	cam->skip = 2;
+	ret = 0;
+
+out:
+	mutex_unlock(&q->vb_lock);
+
+	DBG("%s: V4L2_PIX_FMT_%s (%d) ok!\n", __func__,
+	    decode_fourcc(f->fmt.pix.pixelformat, pixelformat_name),
+	    f->fmt.pix.field);
+	return ret;
+}
+
+static int zr364xx_vidioc_reqbufs(struct file *file, void *priv,
+			  struct v4l2_requestbuffers *p)
+{
+	struct zr364xx_camera *cam = video_drvdata(file);
+
+	if (cam->owner && cam->owner != priv)
+		return -EBUSY;
+	return videobuf_reqbufs(&cam->vb_vidq, p);
+}
+
+static int zr364xx_vidioc_querybuf(struct file *file,
+				void *priv,
+				struct v4l2_buffer *p)
+{
+	int rc;
+	struct zr364xx_camera *cam = video_drvdata(file);
+	rc = videobuf_querybuf(&cam->vb_vidq, p);
+	return rc;
+}
+
+static int zr364xx_vidioc_qbuf(struct file *file,
+				void *priv,
+				struct v4l2_buffer *p)
+{
+	int rc;
+	struct zr364xx_camera *cam = video_drvdata(file);
+	_DBG("%s\n", __func__);
+	if (cam->owner && cam->owner != priv)
+		return -EBUSY;
+	rc = videobuf_qbuf(&cam->vb_vidq, p);
+	return rc;
+}
+
+static int zr364xx_vidioc_dqbuf(struct file *file,
+				void *priv,
+				struct v4l2_buffer *p)
+{
+	int rc;
+	struct zr364xx_camera *cam = video_drvdata(file);
+	_DBG("%s\n", __func__);
+	if (cam->owner && cam->owner != priv)
+		return -EBUSY;
+	rc = videobuf_dqbuf(&cam->vb_vidq, p, file->f_flags & O_NONBLOCK);
+	return rc;
+}
+
+static void read_pipe_completion(struct urb *purb)
+{
+	struct zr364xx_pipeinfo *pipe_info;
+	struct zr364xx_camera *cam;
+	int pipe;
+
+	pipe_info = purb->context;
+	_DBG("%s %p, status %d\n", __func__, purb, purb->status);
+	if (!pipe_info) {
+		printk(KERN_ERR KBUILD_MODNAME ": no context!\n");
+		return;
+	}
+
+	cam = pipe_info->cam;
+	if (!cam) {
+		printk(KERN_ERR KBUILD_MODNAME ": no context!\n");
+		return;
+	}
+
+	/* if shutting down, do not resubmit, exit immediately */
+	if (purb->status == -ESHUTDOWN) {
+		DBG("%s, err shutdown\n", __func__);
+		pipe_info->err_count++;
+		return;
+	}
+
+	if (pipe_info->state == 0) {
+		DBG("exiting USB pipe\n");
+		return;
+	}
+
+	if (purb->actual_length > pipe_info->transfer_size) {
+		dev_err(&cam->udev->dev, "wrong number of bytes\n");
+		return;
+	}
+
+	if (purb->status == 0)
+		zr364xx_read_video_callback(cam, pipe_info, purb);
+	else {
+		pipe_info->err_count++;
+		DBG("%s: failed URB %d\n", __func__, purb->status);
+	}
+
+	pipe = usb_rcvbulkpipe(cam->udev, cam->read_endpoint);
+
+	/* reuse urb */
+	usb_fill_bulk_urb(pipe_info->stream_urb, cam->udev,
+			  pipe,
+			  pipe_info->transfer_buffer,
+			  pipe_info->transfer_size,
+			  read_pipe_completion, pipe_info);
+
+	if (pipe_info->state != 0) {
+		purb->status = usb_submit_urb(pipe_info->stream_urb,
+					      GFP_ATOMIC);
+
+		if (purb->status)
+			dev_err(&cam->udev->dev,
+				"error submitting urb (error=%i)\n",
+				purb->status);
+	} else
+		DBG("read pipe complete state 0\n");
+}
+
+static int zr364xx_start_readpipe(struct zr364xx_camera *cam)
+{
+	int pipe;
+	int retval;
+	struct zr364xx_pipeinfo *pipe_info = cam->pipe;
+	pipe = usb_rcvbulkpipe(cam->udev, cam->read_endpoint);
+	DBG("%s: start pipe IN x%x\n", __func__, cam->read_endpoint);
+
+	pipe_info->state = 1;
+	pipe_info->err_count = 0;
+	pipe_info->stream_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!pipe_info->stream_urb)
+		return -ENOMEM;
+	/* transfer buffer allocated in board_init */
+	usb_fill_bulk_urb(pipe_info->stream_urb, cam->udev,
+			  pipe,
+			  pipe_info->transfer_buffer,
+			  pipe_info->transfer_size,
+			  read_pipe_completion, pipe_info);
+
+	DBG("submitting URB %p\n", pipe_info->stream_urb);
+	retval = usb_submit_urb(pipe_info->stream_urb, GFP_KERNEL);
+	if (retval) {
+		printk(KERN_ERR KBUILD_MODNAME ": start read pipe failed\n");
+		return retval;
+	}
+
+	return 0;
+}
+
+static void zr364xx_stop_readpipe(struct zr364xx_camera *cam)
+{
+	struct zr364xx_pipeinfo *pipe_info;
+
+	if (!cam) {
+		printk(KERN_ERR KBUILD_MODNAME ": invalid device\n");
+		return;
+	}
+	DBG("stop read pipe\n");
+	pipe_info = cam->pipe;
+	if (pipe_info) {
+		if (pipe_info->state != 0)
+			pipe_info->state = 0;
+
+		if (pipe_info->stream_urb) {
+			/* cancel urb */
+			usb_kill_urb(pipe_info->stream_urb);
+			usb_free_urb(pipe_info->stream_urb);
+			pipe_info->stream_urb = NULL;
+		}
+	}
+	return;
+}
+
+/* starts acquisition process */
+static int zr364xx_start_acquire(struct zr364xx_camera *cam)
+{
+	int j;
+
+	DBG("start acquire\n");
+
+	cam->last_frame = -1;
+	cam->cur_frame = 0;
+	for (j = 0; j < FRAMES; j++) {
+		cam->buffer.frame[j].ulState = ZR364XX_READ_IDLE;
+		cam->buffer.frame[j].cur_size = 0;
+	}
+	cam->b_acquire = 1;
+	return 0;
+}
+
+static inline int zr364xx_stop_acquire(struct zr364xx_camera *cam)
+{
+	cam->b_acquire = 0;
+	return 0;
+}
+
+static int zr364xx_prepare(struct zr364xx_camera *cam)
+{
+	int res;
+	int i, j;
+
+	for (i = 0; init[cam->method][i].size != -1; i++) {
+		res = send_control_msg(cam->udev, 1, init[cam->method][i].value,
+				     0, init[cam->method][i].bytes,
+				     init[cam->method][i].size);
+		if (res < 0) {
+			dev_err(&cam->udev->dev,
+				"error during open sequence: %d\n", i);
+			return res;
+		}
+	}
+
+	cam->skip = 2;
+	cam->last_frame = -1;
+	cam->cur_frame = 0;
+	cam->frame_count = 0;
+	for (j = 0; j < FRAMES; j++) {
+		cam->buffer.frame[j].ulState = ZR364XX_READ_IDLE;
+		cam->buffer.frame[j].cur_size = 0;
+	}
+	v4l2_ctrl_handler_setup(&cam->ctrl_handler);
+	return 0;
+}
+
+static int zr364xx_vidioc_streamon(struct file *file, void *priv,
+				   enum v4l2_buf_type type)
+{
+	struct zr364xx_camera *cam = video_drvdata(file);
+	int res;
+
+	DBG("%s\n", __func__);
+
+	if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	if (cam->owner && cam->owner != priv)
+		return -EBUSY;
+
+	res = zr364xx_prepare(cam);
+	if (res)
+		return res;
+	res = videobuf_streamon(&cam->vb_vidq);
+	if (res == 0) {
+		zr364xx_start_acquire(cam);
+		cam->owner = file->private_data;
+	}
+	return res;
+}
+
+static int zr364xx_vidioc_streamoff(struct file *file, void *priv,
+				    enum v4l2_buf_type type)
+{
+	struct zr364xx_camera *cam = video_drvdata(file);
+
+	DBG("%s\n", __func__);
+	if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+	if (cam->owner && cam->owner != priv)
+		return -EBUSY;
+	zr364xx_stop_acquire(cam);
+	return videobuf_streamoff(&cam->vb_vidq);
+}
+
+
+/* open the camera */
+static int zr364xx_open(struct file *file)
+{
+	struct zr364xx_camera *cam = video_drvdata(file);
+	int err;
+
+	DBG("%s\n", __func__);
+
+	if (mutex_lock_interruptible(&cam->lock))
+		return -ERESTARTSYS;
+
+	err = v4l2_fh_open(file);
+	if (err)
+		goto out;
+
+	/* Added some delay here, since opening/closing the camera quickly,
+	 * like Ekiga does during its startup, can crash the webcam
+	 */
+	mdelay(100);
+	err = 0;
+
+out:
+	mutex_unlock(&cam->lock);
+	DBG("%s: %d\n", __func__, err);
+	return err;
+}
+
+static void zr364xx_release(struct v4l2_device *v4l2_dev)
+{
+	struct zr364xx_camera *cam =
+		container_of(v4l2_dev, struct zr364xx_camera, v4l2_dev);
+	unsigned long i;
+
+	v4l2_device_unregister(&cam->v4l2_dev);
+
+	videobuf_mmap_free(&cam->vb_vidq);
+
+	/* release sys buffers */
+	for (i = 0; i < FRAMES; i++) {
+		if (cam->buffer.frame[i].lpvbits) {
+			DBG("vfree %p\n", cam->buffer.frame[i].lpvbits);
+			vfree(cam->buffer.frame[i].lpvbits);
+		}
+		cam->buffer.frame[i].lpvbits = NULL;
+	}
+
+	v4l2_ctrl_handler_free(&cam->ctrl_handler);
+	/* release transfer buffer */
+	kfree(cam->pipe->transfer_buffer);
+	kfree(cam);
+}
+
+/* release the camera */
+static int zr364xx_close(struct file *file)
+{
+	struct zr364xx_camera *cam;
+	struct usb_device *udev;
+	int i;
+
+	DBG("%s\n", __func__);
+	cam = video_drvdata(file);
+
+	mutex_lock(&cam->lock);
+	udev = cam->udev;
+
+	if (file->private_data == cam->owner) {
+		/* turn off stream */
+		if (cam->b_acquire)
+			zr364xx_stop_acquire(cam);
+		videobuf_streamoff(&cam->vb_vidq);
+
+		for (i = 0; i < 2; i++) {
+			send_control_msg(udev, 1, init[cam->method][i].value,
+					0, init[cam->method][i].bytes,
+					init[cam->method][i].size);
+		}
+		cam->owner = NULL;
+	}
+
+	/* Added some delay here, since opening/closing the camera quickly,
+	 * like Ekiga does during its startup, can crash the webcam
+	 */
+	mdelay(100);
+	mutex_unlock(&cam->lock);
+	return v4l2_fh_release(file);
+}
+
+
+static int zr364xx_mmap(struct file *file, struct vm_area_struct *vma)
+{
+	struct zr364xx_camera *cam = video_drvdata(file);
+	int ret;
+
+	if (!cam) {
+		DBG("%s: cam == NULL\n", __func__);
+		return -ENODEV;
+	}
+	DBG("mmap called, vma=%p\n", vma);
+
+	ret = videobuf_mmap_mapper(&cam->vb_vidq, vma);
+
+	DBG("vma start=0x%08lx, size=%ld, ret=%d\n",
+		(unsigned long)vma->vm_start,
+		(unsigned long)vma->vm_end - (unsigned long)vma->vm_start, ret);
+	return ret;
+}
+
+static __poll_t zr364xx_poll(struct file *file,
+			       struct poll_table_struct *wait)
+{
+	struct zr364xx_camera *cam = video_drvdata(file);
+	struct videobuf_queue *q = &cam->vb_vidq;
+	__poll_t res = v4l2_ctrl_poll(file, wait);
+
+	_DBG("%s\n", __func__);
+
+	return res | videobuf_poll_stream(file, q, wait);
+}
+
+static const struct v4l2_ctrl_ops zr364xx_ctrl_ops = {
+	.s_ctrl = zr364xx_s_ctrl,
+};
+
+static const struct v4l2_file_operations zr364xx_fops = {
+	.owner = THIS_MODULE,
+	.open = zr364xx_open,
+	.release = zr364xx_close,
+	.read = zr364xx_read,
+	.mmap = zr364xx_mmap,
+	.unlocked_ioctl = video_ioctl2,
+	.poll = zr364xx_poll,
+};
+
+static const struct v4l2_ioctl_ops zr364xx_ioctl_ops = {
+	.vidioc_querycap	= zr364xx_vidioc_querycap,
+	.vidioc_enum_fmt_vid_cap = zr364xx_vidioc_enum_fmt_vid_cap,
+	.vidioc_try_fmt_vid_cap	= zr364xx_vidioc_try_fmt_vid_cap,
+	.vidioc_s_fmt_vid_cap	= zr364xx_vidioc_s_fmt_vid_cap,
+	.vidioc_g_fmt_vid_cap	= zr364xx_vidioc_g_fmt_vid_cap,
+	.vidioc_enum_input	= zr364xx_vidioc_enum_input,
+	.vidioc_g_input		= zr364xx_vidioc_g_input,
+	.vidioc_s_input		= zr364xx_vidioc_s_input,
+	.vidioc_streamon	= zr364xx_vidioc_streamon,
+	.vidioc_streamoff	= zr364xx_vidioc_streamoff,
+	.vidioc_reqbufs         = zr364xx_vidioc_reqbufs,
+	.vidioc_querybuf        = zr364xx_vidioc_querybuf,
+	.vidioc_qbuf            = zr364xx_vidioc_qbuf,
+	.vidioc_dqbuf           = zr364xx_vidioc_dqbuf,
+	.vidioc_log_status      = v4l2_ctrl_log_status,
+	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static const struct video_device zr364xx_template = {
+	.name = DRIVER_DESC,
+	.fops = &zr364xx_fops,
+	.ioctl_ops = &zr364xx_ioctl_ops,
+	.release = video_device_release_empty,
+};
+
+
+
+/*******************/
+/* USB integration */
+/*******************/
+static int zr364xx_board_init(struct zr364xx_camera *cam)
+{
+	struct zr364xx_pipeinfo *pipe = cam->pipe;
+	unsigned long i;
+
+	DBG("board init: %p\n", cam);
+	memset(pipe, 0, sizeof(*pipe));
+	pipe->cam = cam;
+	pipe->transfer_size = BUFFER_SIZE;
+
+	pipe->transfer_buffer = kzalloc(pipe->transfer_size,
+					GFP_KERNEL);
+	if (!pipe->transfer_buffer) {
+		DBG("out of memory!\n");
+		return -ENOMEM;
+	}
+
+	cam->b_acquire = 0;
+	cam->frame_count = 0;
+
+	/*** start create system buffers ***/
+	for (i = 0; i < FRAMES; i++) {
+		/* always allocate maximum size for system buffers */
+		cam->buffer.frame[i].lpvbits = vmalloc(MAX_FRAME_SIZE);
+
+		DBG("valloc %p, idx %lu, pdata %p\n",
+			&cam->buffer.frame[i], i,
+			cam->buffer.frame[i].lpvbits);
+		if (!cam->buffer.frame[i].lpvbits) {
+			printk(KERN_INFO KBUILD_MODNAME ": out of memory. Using less frames\n");
+			break;
+		}
+	}
+
+	if (i == 0) {
+		printk(KERN_INFO KBUILD_MODNAME ": out of memory. Aborting\n");
+		kfree(cam->pipe->transfer_buffer);
+		cam->pipe->transfer_buffer = NULL;
+		return -ENOMEM;
+	} else
+		cam->buffer.dwFrames = i;
+
+	/* make sure internal states are set */
+	for (i = 0; i < FRAMES; i++) {
+		cam->buffer.frame[i].ulState = ZR364XX_READ_IDLE;
+		cam->buffer.frame[i].cur_size = 0;
+	}
+
+	cam->cur_frame = 0;
+	cam->last_frame = -1;
+	/*** end create system buffers ***/
+
+	/* start read pipe */
+	zr364xx_start_readpipe(cam);
+	DBG(": board initialized\n");
+	return 0;
+}
+
+static int zr364xx_probe(struct usb_interface *intf,
+			 const struct usb_device_id *id)
+{
+	struct usb_device *udev = interface_to_usbdev(intf);
+	struct zr364xx_camera *cam = NULL;
+	struct usb_host_interface *iface_desc;
+	struct usb_endpoint_descriptor *endpoint;
+	struct v4l2_ctrl_handler *hdl;
+	int err;
+	int i;
+
+	DBG("probing...\n");
+
+	dev_info(&intf->dev, DRIVER_DESC " compatible webcam plugged\n");
+	dev_info(&intf->dev, "model %04x:%04x detected\n",
+		 le16_to_cpu(udev->descriptor.idVendor),
+		 le16_to_cpu(udev->descriptor.idProduct));
+
+	cam = kzalloc(sizeof(*cam), GFP_KERNEL);
+	if (!cam)
+		return -ENOMEM;
+
+	cam->v4l2_dev.release = zr364xx_release;
+	err = v4l2_device_register(&intf->dev, &cam->v4l2_dev);
+	if (err < 0) {
+		dev_err(&udev->dev, "couldn't register v4l2_device\n");
+		kfree(cam);
+		return err;
+	}
+	hdl = &cam->ctrl_handler;
+	v4l2_ctrl_handler_init(hdl, 1);
+	v4l2_ctrl_new_std(hdl, &zr364xx_ctrl_ops,
+			  V4L2_CID_BRIGHTNESS, 0, 127, 1, 64);
+	if (hdl->error) {
+		err = hdl->error;
+		dev_err(&udev->dev, "couldn't register control\n");
+		goto fail;
+	}
+	/* save the init method used by this camera */
+	cam->method = id->driver_info;
+	mutex_init(&cam->lock);
+	cam->vdev = zr364xx_template;
+	cam->vdev.lock = &cam->lock;
+	cam->vdev.v4l2_dev = &cam->v4l2_dev;
+	cam->vdev.ctrl_handler = &cam->ctrl_handler;
+	video_set_drvdata(&cam->vdev, cam);
+
+	cam->udev = udev;
+
+	switch (mode) {
+	case 1:
+		dev_info(&udev->dev, "160x120 mode selected\n");
+		cam->width = 160;
+		cam->height = 120;
+		break;
+	case 2:
+		dev_info(&udev->dev, "640x480 mode selected\n");
+		cam->width = 640;
+		cam->height = 480;
+		break;
+	default:
+		dev_info(&udev->dev, "320x240 mode selected\n");
+		cam->width = 320;
+		cam->height = 240;
+		break;
+	}
+
+	m0d1[0] = mode;
+	m1[2].value = 0xf000 + mode;
+	m2[1].value = 0xf000 + mode;
+
+	/* special case for METHOD3, the modes are different */
+	if (cam->method == METHOD3) {
+		switch (mode) {
+		case 1:
+			m2[1].value = 0xf000 + 4;
+			break;
+		case 2:
+			m2[1].value = 0xf000 + 0;
+			break;
+		default:
+			m2[1].value = 0xf000 + 1;
+			break;
+		}
+	}
+
+	header2[437] = cam->height / 256;
+	header2[438] = cam->height % 256;
+	header2[439] = cam->width / 256;
+	header2[440] = cam->width % 256;
+
+	cam->nb = 0;
+
+	DBG("dev: %p, udev %p interface %p\n", cam, cam->udev, intf);
+
+	/* set up the endpoint information  */
+	iface_desc = intf->cur_altsetting;
+	DBG("num endpoints %d\n", iface_desc->desc.bNumEndpoints);
+	for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
+		endpoint = &iface_desc->endpoint[i].desc;
+		if (!cam->read_endpoint && usb_endpoint_is_bulk_in(endpoint)) {
+			/* we found the bulk in endpoint */
+			cam->read_endpoint = endpoint->bEndpointAddress;
+		}
+	}
+
+	if (!cam->read_endpoint) {
+		err = -ENOMEM;
+		dev_err(&intf->dev, "Could not find bulk-in endpoint\n");
+		goto fail;
+	}
+
+	/* v4l */
+	INIT_LIST_HEAD(&cam->vidq.active);
+	cam->vidq.cam = cam;
+
+	usb_set_intfdata(intf, cam);
+
+	/* load zr364xx board specific */
+	err = zr364xx_board_init(cam);
+	if (!err)
+		err = v4l2_ctrl_handler_setup(hdl);
+	if (err)
+		goto fail;
+
+	spin_lock_init(&cam->slock);
+
+	cam->fmt = formats;
+
+	videobuf_queue_vmalloc_init(&cam->vb_vidq, &zr364xx_video_qops,
+				    NULL, &cam->slock,
+				    V4L2_BUF_TYPE_VIDEO_CAPTURE,
+				    V4L2_FIELD_NONE,
+				    sizeof(struct zr364xx_buffer), cam, &cam->lock);
+
+	err = video_register_device(&cam->vdev, VFL_TYPE_GRABBER, -1);
+	if (err) {
+		dev_err(&udev->dev, "video_register_device failed\n");
+		goto fail;
+	}
+
+	dev_info(&udev->dev, DRIVER_DESC " controlling device %s\n",
+		 video_device_node_name(&cam->vdev));
+	return 0;
+
+fail:
+	v4l2_ctrl_handler_free(hdl);
+	v4l2_device_unregister(&cam->v4l2_dev);
+	kfree(cam);
+	return err;
+}
+
+
+static void zr364xx_disconnect(struct usb_interface *intf)
+{
+	struct zr364xx_camera *cam = usb_get_intfdata(intf);
+
+	mutex_lock(&cam->lock);
+	usb_set_intfdata(intf, NULL);
+	dev_info(&intf->dev, DRIVER_DESC " webcam unplugged\n");
+	video_unregister_device(&cam->vdev);
+	v4l2_device_disconnect(&cam->v4l2_dev);
+
+	/* stops the read pipe if it is running */
+	if (cam->b_acquire)
+		zr364xx_stop_acquire(cam);
+
+	zr364xx_stop_readpipe(cam);
+	mutex_unlock(&cam->lock);
+	v4l2_device_put(&cam->v4l2_dev);
+}
+
+
+#ifdef CONFIG_PM
+static int zr364xx_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	struct zr364xx_camera *cam = usb_get_intfdata(intf);
+
+	cam->was_streaming = cam->b_acquire;
+	if (!cam->was_streaming)
+		return 0;
+	zr364xx_stop_acquire(cam);
+	zr364xx_stop_readpipe(cam);
+	return 0;
+}
+
+static int zr364xx_resume(struct usb_interface *intf)
+{
+	struct zr364xx_camera *cam = usb_get_intfdata(intf);
+	int res;
+
+	if (!cam->was_streaming)
+		return 0;
+
+	zr364xx_start_readpipe(cam);
+	res = zr364xx_prepare(cam);
+	if (!res)
+		zr364xx_start_acquire(cam);
+	return res;
+}
+#endif
+
+/**********************/
+/* Module integration */
+/**********************/
+
+static struct usb_driver zr364xx_driver = {
+	.name = "zr364xx",
+	.probe = zr364xx_probe,
+	.disconnect = zr364xx_disconnect,
+#ifdef CONFIG_PM
+	.suspend = zr364xx_suspend,
+	.resume = zr364xx_resume,
+	.reset_resume = zr364xx_resume,
+#endif
+	.id_table = device_table
+};
+
+module_usb_driver(zr364xx_driver);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+MODULE_VERSION(DRIVER_VERSION);